// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "google_apis/drive/auth_service.h" #include #include #include "base/bind.h" #include "base/location.h" #include "base/message_loop/message_loop.h" #include "base/metrics/histogram.h" #include "google_apis/drive/auth_service_observer.h" #include "google_apis/gaia/google_service_auth_error.h" #include "net/url_request/url_request_context_getter.h" namespace google_apis { namespace { // Used for success ratio histograms. 0 for failure, 1 for success, // 2 for no connection (likely offline). const int kSuccessRatioHistogramFailure = 0; const int kSuccessRatioHistogramSuccess = 1; const int kSuccessRatioHistogramNoConnection = 2; const int kSuccessRatioHistogramTemporaryFailure = 3; const int kSuccessRatioHistogramMaxValue = 4; // The max value is exclusive. void RecordAuthResultHistogram(int value) { UMA_HISTOGRAM_ENUMERATION("GData.AuthSuccess", value, kSuccessRatioHistogramMaxValue); } // OAuth2 authorization token retrieval request. class AuthRequest : public OAuth2TokenService::Consumer { public: AuthRequest(OAuth2TokenService* oauth2_token_service, const std::string& account_id, net::URLRequestContextGetter* url_request_context_getter, const AuthStatusCallback& callback, const std::vector& scopes); ~AuthRequest() override; private: // Overridden from OAuth2TokenService::Consumer: void OnGetTokenSuccess(const OAuth2TokenService::Request* request, const std::string& access_token, const base::Time& expiration_time) override; void OnGetTokenFailure(const OAuth2TokenService::Request* request, const GoogleServiceAuthError& error) override; AuthStatusCallback callback_; scoped_ptr request_; base::ThreadChecker thread_checker_; DISALLOW_COPY_AND_ASSIGN(AuthRequest); }; AuthRequest::AuthRequest( OAuth2TokenService* oauth2_token_service, const std::string& account_id, net::URLRequestContextGetter* url_request_context_getter, const AuthStatusCallback& callback, const std::vector& scopes) : OAuth2TokenService::Consumer("auth_service"), callback_(callback) { DCHECK(!callback_.is_null()); request_ = oauth2_token_service-> StartRequestWithContext( account_id, url_request_context_getter, OAuth2TokenService::ScopeSet(scopes.begin(), scopes.end()), this); } AuthRequest::~AuthRequest() {} // Callback for OAuth2AccessTokenFetcher on success. |access_token| is the token // used to start fetching user data. void AuthRequest::OnGetTokenSuccess(const OAuth2TokenService::Request* request, const std::string& access_token, const base::Time& expiration_time) { DCHECK(thread_checker_.CalledOnValidThread()); RecordAuthResultHistogram(kSuccessRatioHistogramSuccess); callback_.Run(HTTP_SUCCESS, access_token); delete this; } // Callback for OAuth2AccessTokenFetcher on failure. void AuthRequest::OnGetTokenFailure(const OAuth2TokenService::Request* request, const GoogleServiceAuthError& error) { DCHECK(thread_checker_.CalledOnValidThread()); LOG(WARNING) << "AuthRequest: token request using refresh token failed: " << error.ToString(); // There are many ways to fail, but if the failure is due to connection, // it's likely that the device is off-line. We treat the error differently // so that the file manager works while off-line. if (error.state() == GoogleServiceAuthError::CONNECTION_FAILED) { RecordAuthResultHistogram(kSuccessRatioHistogramNoConnection); callback_.Run(DRIVE_NO_CONNECTION, std::string()); } else if (error.state() == GoogleServiceAuthError::SERVICE_UNAVAILABLE) { RecordAuthResultHistogram(kSuccessRatioHistogramTemporaryFailure); callback_.Run(HTTP_FORBIDDEN, std::string()); } else { // Permanent auth error. RecordAuthResultHistogram(kSuccessRatioHistogramFailure); callback_.Run(HTTP_UNAUTHORIZED, std::string()); } delete this; } } // namespace AuthService::AuthService( OAuth2TokenService* oauth2_token_service, const std::string& account_id, net::URLRequestContextGetter* url_request_context_getter, const std::vector& scopes) : oauth2_token_service_(oauth2_token_service), account_id_(account_id), url_request_context_getter_(url_request_context_getter), scopes_(scopes), weak_ptr_factory_(this) { DCHECK(oauth2_token_service); // Get OAuth2 refresh token (if we have any) and register for its updates. oauth2_token_service_->AddObserver(this); has_refresh_token_ = oauth2_token_service_->RefreshTokenIsAvailable( account_id_); } AuthService::~AuthService() { oauth2_token_service_->RemoveObserver(this); } void AuthService::StartAuthentication(const AuthStatusCallback& callback) { DCHECK(thread_checker_.CalledOnValidThread()); if (HasAccessToken()) { // We already have access token. Give it back to the caller asynchronously. base::MessageLoop::current()->PostTask( FROM_HERE, base::Bind(callback, HTTP_SUCCESS, access_token_)); } else if (HasRefreshToken()) { // We have refresh token, let's get an access token. new AuthRequest(oauth2_token_service_, account_id_, url_request_context_getter_.get(), base::Bind(&AuthService::OnAuthCompleted, weak_ptr_factory_.GetWeakPtr(), callback), scopes_); } else { base::MessageLoop::current()->PostTask( FROM_HERE, base::Bind(callback, DRIVE_NOT_READY, std::string())); } } bool AuthService::HasAccessToken() const { return !access_token_.empty(); } bool AuthService::HasRefreshToken() const { return has_refresh_token_; } const std::string& AuthService::access_token() const { return access_token_; } void AuthService::ClearAccessToken() { access_token_.clear(); } void AuthService::ClearRefreshToken() { OnHandleRefreshToken(false); } void AuthService::OnAuthCompleted(const AuthStatusCallback& callback, DriveApiErrorCode error, const std::string& access_token) { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(!callback.is_null()); if (error == HTTP_SUCCESS) { access_token_ = access_token; } else if (error == HTTP_UNAUTHORIZED) { // Refreshing access token using the refresh token is failed with 401 error // (HTTP_UNAUTHORIZED). This means the current refresh token is invalid for // Drive, hence we clear the refresh token here to make HasRefreshToken() // false, thus the invalidness is clearly observable. // This is not for triggering refetch of the refresh token. UI should // show some message to encourage user to log-off and log-in again in order // to fetch new valid refresh token. ClearRefreshToken(); } callback.Run(error, access_token); } void AuthService::AddObserver(AuthServiceObserver* observer) { observers_.AddObserver(observer); } void AuthService::RemoveObserver(AuthServiceObserver* observer) { observers_.RemoveObserver(observer); } void AuthService::OnRefreshTokenAvailable(const std::string& account_id) { if (account_id == account_id_) OnHandleRefreshToken(true); } void AuthService::OnRefreshTokenRevoked(const std::string& account_id) { if (account_id == account_id_) OnHandleRefreshToken(false); } void AuthService::OnHandleRefreshToken(bool has_refresh_token) { access_token_.clear(); has_refresh_token_ = has_refresh_token; FOR_EACH_OBSERVER(AuthServiceObserver, observers_, OnOAuth2RefreshTokenChanged()); } } // namespace google_apis