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