blob: d53be495bb29a74624c010882fc35bec38813c24 [file] [log] [blame]
// 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 <string>
#include <vector>
#include "base/bind.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/metrics/histogram_macros.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "google_apis/drive/auth_service_observer.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "services/identity/public/cpp/access_token_fetcher.h"
#include "services/identity/public/cpp/access_token_info.h"
#include "services/network/public/cpp/shared_url_loader_factory.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:
AuthRequest(identity::IdentityManager* identity_manager,
const std::string& account_id,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
const AuthStatusCallback& callback,
const std::vector<std::string>& scopes);
~AuthRequest();
private:
void OnAccessTokenFetchComplete(GoogleServiceAuthError error,
identity::AccessTokenInfo token_info);
AuthStatusCallback callback_;
std::unique_ptr<identity::AccessTokenFetcher> access_token_fetcher_;
base::ThreadChecker thread_checker_;
DISALLOW_COPY_AND_ASSIGN(AuthRequest);
};
AuthRequest::AuthRequest(
identity::IdentityManager* identity_manager,
const std::string& account_id,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
const AuthStatusCallback& callback,
const std::vector<std::string>& scopes)
: callback_(callback) {
DCHECK(identity_manager);
DCHECK(!callback_.is_null());
access_token_fetcher_ = identity_manager->CreateAccessTokenFetcherForAccount(
account_id, "auth_service", url_loader_factory,
identity::ScopeSet(scopes.begin(), scopes.end()),
base::BindOnce(&AuthRequest::OnAccessTokenFetchComplete,
base::Unretained(this)),
identity::AccessTokenFetcher::Mode::kImmediate);
}
AuthRequest::~AuthRequest() {}
void AuthRequest::OnAccessTokenFetchComplete(
GoogleServiceAuthError error,
identity::AccessTokenInfo token_info) {
DCHECK(thread_checker_.CalledOnValidThread());
if (error.state() == GoogleServiceAuthError::NONE) {
RecordAuthResultHistogram(kSuccessRatioHistogramSuccess);
callback_.Run(HTTP_SUCCESS, token_info.token);
} else {
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(
identity::IdentityManager* identity_manager,
const std::string& account_id,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
const std::vector<std::string>& scopes)
: identity_manager_(identity_manager),
account_id_(account_id),
url_loader_factory_(url_loader_factory),
scopes_(scopes),
weak_ptr_factory_(this) {
DCHECK(identity_manager_);
identity_manager_->AddObserver(this);
has_refresh_token_ =
identity_manager_->HasAccountWithRefreshToken(account_id_);
}
AuthService::~AuthService() {
identity_manager_->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::ThreadTaskRunnerHandle::Get()->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(identity_manager_, account_id_, url_loader_factory_,
base::Bind(&AuthService::OnAuthCompleted,
weak_ptr_factory_.GetWeakPtr(), callback),
scopes_);
} else {
base::ThreadTaskRunnerHandle::Get()->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::OnRefreshTokenUpdatedForAccount(
const CoreAccountInfo& account_info) {
if (account_info.account_id == account_id_)
OnHandleRefreshToken(true);
}
void AuthService::OnRefreshTokenRemovedForAccount(
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 (auto& observer : observers_)
observer.OnOAuth2RefreshTokenChanged();
}
} // namespace google_apis