1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "google_apis/drive/auth_service.h" 6 7#include <string> 8#include <vector> 9 10#include "base/bind.h" 11#include "base/location.h" 12#include "base/message_loop/message_loop.h" 13#include "base/metrics/histogram.h" 14#include "google_apis/drive/auth_service_observer.h" 15#include "google_apis/gaia/google_service_auth_error.h" 16#include "net/url_request/url_request_context_getter.h" 17 18namespace google_apis { 19 20namespace { 21 22// Used for success ratio histograms. 0 for failure, 1 for success, 23// 2 for no connection (likely offline). 24const int kSuccessRatioHistogramFailure = 0; 25const int kSuccessRatioHistogramSuccess = 1; 26const int kSuccessRatioHistogramNoConnection = 2; 27const int kSuccessRatioHistogramTemporaryFailure = 3; 28const int kSuccessRatioHistogramMaxValue = 4; // The max value is exclusive. 29 30void RecordAuthResultHistogram(int value) { 31 UMA_HISTOGRAM_ENUMERATION("GData.AuthSuccess", 32 value, 33 kSuccessRatioHistogramMaxValue); 34} 35 36// OAuth2 authorization token retrieval request. 37class AuthRequest : public OAuth2TokenService::Consumer { 38 public: 39 AuthRequest(OAuth2TokenService* oauth2_token_service, 40 const std::string& account_id, 41 net::URLRequestContextGetter* url_request_context_getter, 42 const AuthStatusCallback& callback, 43 const std::vector<std::string>& scopes); 44 virtual ~AuthRequest(); 45 46 private: 47 // Overridden from OAuth2TokenService::Consumer: 48 virtual void OnGetTokenSuccess(const OAuth2TokenService::Request* request, 49 const std::string& access_token, 50 const base::Time& expiration_time) OVERRIDE; 51 virtual void OnGetTokenFailure(const OAuth2TokenService::Request* request, 52 const GoogleServiceAuthError& error) OVERRIDE; 53 54 AuthStatusCallback callback_; 55 scoped_ptr<OAuth2TokenService::Request> request_; 56 base::ThreadChecker thread_checker_; 57 58 DISALLOW_COPY_AND_ASSIGN(AuthRequest); 59}; 60 61AuthRequest::AuthRequest( 62 OAuth2TokenService* oauth2_token_service, 63 const std::string& account_id, 64 net::URLRequestContextGetter* url_request_context_getter, 65 const AuthStatusCallback& callback, 66 const std::vector<std::string>& scopes) 67 : OAuth2TokenService::Consumer("auth_service"), 68 callback_(callback) { 69 DCHECK(!callback_.is_null()); 70 request_ = oauth2_token_service-> 71 StartRequestWithContext( 72 account_id, 73 url_request_context_getter, 74 OAuth2TokenService::ScopeSet(scopes.begin(), scopes.end()), 75 this); 76} 77 78AuthRequest::~AuthRequest() {} 79 80// Callback for OAuth2AccessTokenFetcher on success. |access_token| is the token 81// used to start fetching user data. 82void AuthRequest::OnGetTokenSuccess(const OAuth2TokenService::Request* request, 83 const std::string& access_token, 84 const base::Time& expiration_time) { 85 DCHECK(thread_checker_.CalledOnValidThread()); 86 87 RecordAuthResultHistogram(kSuccessRatioHistogramSuccess); 88 callback_.Run(HTTP_SUCCESS, access_token); 89 delete this; 90} 91 92// Callback for OAuth2AccessTokenFetcher on failure. 93void AuthRequest::OnGetTokenFailure(const OAuth2TokenService::Request* request, 94 const GoogleServiceAuthError& error) { 95 DCHECK(thread_checker_.CalledOnValidThread()); 96 97 LOG(WARNING) << "AuthRequest: token request using refresh token failed: " 98 << error.ToString(); 99 100 // There are many ways to fail, but if the failure is due to connection, 101 // it's likely that the device is off-line. We treat the error differently 102 // so that the file manager works while off-line. 103 if (error.state() == GoogleServiceAuthError::CONNECTION_FAILED) { 104 RecordAuthResultHistogram(kSuccessRatioHistogramNoConnection); 105 callback_.Run(GDATA_NO_CONNECTION, std::string()); 106 } else if (error.state() == GoogleServiceAuthError::SERVICE_UNAVAILABLE) { 107 RecordAuthResultHistogram(kSuccessRatioHistogramTemporaryFailure); 108 callback_.Run(HTTP_FORBIDDEN, std::string()); 109 } else { 110 // Permanent auth error. 111 RecordAuthResultHistogram(kSuccessRatioHistogramFailure); 112 callback_.Run(HTTP_UNAUTHORIZED, std::string()); 113 } 114 delete this; 115} 116 117} // namespace 118 119AuthService::AuthService( 120 OAuth2TokenService* oauth2_token_service, 121 const std::string& account_id, 122 net::URLRequestContextGetter* url_request_context_getter, 123 const std::vector<std::string>& scopes) 124 : oauth2_token_service_(oauth2_token_service), 125 account_id_(account_id), 126 url_request_context_getter_(url_request_context_getter), 127 scopes_(scopes), 128 weak_ptr_factory_(this) { 129 DCHECK(oauth2_token_service); 130 131 // Get OAuth2 refresh token (if we have any) and register for its updates. 132 oauth2_token_service_->AddObserver(this); 133 has_refresh_token_ = oauth2_token_service_->RefreshTokenIsAvailable( 134 account_id_); 135} 136 137AuthService::~AuthService() { 138 oauth2_token_service_->RemoveObserver(this); 139} 140 141void AuthService::StartAuthentication(const AuthStatusCallback& callback) { 142 DCHECK(thread_checker_.CalledOnValidThread()); 143 144 if (HasAccessToken()) { 145 // We already have access token. Give it back to the caller asynchronously. 146 base::MessageLoop::current()->PostTask( 147 FROM_HERE, base::Bind(callback, HTTP_SUCCESS, access_token_)); 148 } else if (HasRefreshToken()) { 149 // We have refresh token, let's get an access token. 150 new AuthRequest(oauth2_token_service_, 151 account_id_, 152 url_request_context_getter_.get(), 153 base::Bind(&AuthService::OnAuthCompleted, 154 weak_ptr_factory_.GetWeakPtr(), 155 callback), 156 scopes_); 157 } else { 158 base::MessageLoop::current()->PostTask( 159 FROM_HERE, base::Bind(callback, GDATA_NOT_READY, std::string())); 160 } 161} 162 163bool AuthService::HasAccessToken() const { 164 return !access_token_.empty(); 165} 166 167bool AuthService::HasRefreshToken() const { 168 return has_refresh_token_; 169} 170 171const std::string& AuthService::access_token() const { 172 return access_token_; 173} 174 175void AuthService::ClearAccessToken() { 176 access_token_.clear(); 177} 178 179void AuthService::ClearRefreshToken() { 180 OnHandleRefreshToken(false); 181} 182 183void AuthService::OnAuthCompleted(const AuthStatusCallback& callback, 184 GDataErrorCode error, 185 const std::string& access_token) { 186 DCHECK(thread_checker_.CalledOnValidThread()); 187 DCHECK(!callback.is_null()); 188 189 if (error == HTTP_SUCCESS) { 190 access_token_ = access_token; 191 } else if (error == HTTP_UNAUTHORIZED) { 192 // Refreshing access token using the refresh token is failed with 401 error 193 // (HTTP_UNAUTHORIZED). This means the current refresh token is invalid for 194 // Drive, hence we clear the refresh token here to make HasRefreshToken() 195 // false, thus the invalidness is clearly observable. 196 // This is not for triggering refetch of the refresh token. UI should 197 // show some message to encourage user to log-off and log-in again in order 198 // to fetch new valid refresh token. 199 ClearRefreshToken(); 200 } 201 202 callback.Run(error, access_token); 203} 204 205void AuthService::AddObserver(AuthServiceObserver* observer) { 206 observers_.AddObserver(observer); 207} 208 209void AuthService::RemoveObserver(AuthServiceObserver* observer) { 210 observers_.RemoveObserver(observer); 211} 212 213void AuthService::OnRefreshTokenAvailable(const std::string& account_id) { 214 if (account_id == account_id_) 215 OnHandleRefreshToken(true); 216} 217 218void AuthService::OnRefreshTokenRevoked(const std::string& account_id) { 219 if (account_id == account_id_) 220 OnHandleRefreshToken(false); 221} 222 223void AuthService::OnHandleRefreshToken(bool has_refresh_token) { 224 access_token_.clear(); 225 has_refresh_token_ = has_refresh_token; 226 227 FOR_EACH_OBSERVER(AuthServiceObserver, 228 observers_, 229 OnOAuth2RefreshTokenChanged()); 230} 231 232} // namespace google_apis 233