blob: 041ccb6a9b8eea2dce489baf3e4c81cf4c115b26 [file] [log] [blame]
// Copyright 2013 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 "chrome/browser/chromeos/settings/device_oauth2_token_service.h"
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/prefs/pref_registry_simple.h"
#include "base/prefs/pref_service.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/settings/cros_settings.h"
#include "chrome/browser/chromeos/settings/token_encryptor.h"
#include "chrome/common/pref_names.h"
#include "chromeos/cryptohome/system_salt_getter.h"
#include "google_apis/gaia/gaia_constants.h"
#include "google_apis/gaia/gaia_urls.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
#include "policy/proto/device_management_backend.pb.h"
namespace chromeos {
struct DeviceOAuth2TokenService::PendingRequest {
PendingRequest(const base::WeakPtr<RequestImpl>& request,
const std::string& client_id,
const std::string& client_secret,
const ScopeSet& scopes)
: request(request),
client_id(client_id),
client_secret(client_secret),
scopes(scopes) {}
const base::WeakPtr<RequestImpl> request;
const std::string client_id;
const std::string client_secret;
const ScopeSet scopes;
};
DeviceOAuth2TokenService::DeviceOAuth2TokenService(
net::URLRequestContextGetter* getter,
PrefService* local_state)
: url_request_context_getter_(getter),
local_state_(local_state),
state_(STATE_LOADING),
max_refresh_token_validation_retries_(3),
weak_ptr_factory_(this) {
// Pull in the system salt.
SystemSaltGetter::Get()->GetSystemSalt(
base::Bind(&DeviceOAuth2TokenService::DidGetSystemSalt,
weak_ptr_factory_.GetWeakPtr()));
}
DeviceOAuth2TokenService::~DeviceOAuth2TokenService() {
FlushPendingRequests(false, GoogleServiceAuthError::REQUEST_CANCELED);
FlushTokenSaveCallbacks(false);
}
// static
void DeviceOAuth2TokenService::RegisterPrefs(PrefRegistrySimple* registry) {
registry->RegisterStringPref(prefs::kDeviceRobotAnyApiRefreshToken,
std::string());
}
void DeviceOAuth2TokenService::SetAndSaveRefreshToken(
const std::string& refresh_token,
const StatusCallback& result_callback) {
FlushPendingRequests(false, GoogleServiceAuthError::REQUEST_CANCELED);
bool waiting_for_salt = state_ == STATE_LOADING;
refresh_token_ = refresh_token;
state_ = STATE_VALIDATION_PENDING;
FireRefreshTokenAvailable(GetRobotAccountId());
token_save_callbacks_.push_back(result_callback);
if (!waiting_for_salt) {
if (system_salt_.empty())
FlushTokenSaveCallbacks(false);
else
EncryptAndSaveToken();
}
}
bool DeviceOAuth2TokenService::RefreshTokenIsAvailable(
const std::string& account_id) const {
switch (state_) {
case STATE_NO_TOKEN:
case STATE_TOKEN_INVALID:
return false;
case STATE_LOADING:
case STATE_VALIDATION_PENDING:
case STATE_VALIDATION_STARTED:
case STATE_TOKEN_VALID:
return account_id == GetRobotAccountId();
}
NOTREACHED() << "Unhandled state " << state_;
return false;
}
std::string DeviceOAuth2TokenService::GetRobotAccountId() const {
std::string result;
CrosSettings::Get()->GetString(kServiceAccountIdentity, &result);
return result;
}
void DeviceOAuth2TokenService::OnRefreshTokenResponse(
const std::string& access_token,
int expires_in_seconds) {
gaia_oauth_client_->GetTokenInfo(
access_token,
max_refresh_token_validation_retries_,
this);
}
void DeviceOAuth2TokenService::OnGetTokenInfoResponse(
scoped_ptr<base::DictionaryValue> token_info) {
std::string gaia_robot_id;
token_info->GetString("email", &gaia_robot_id);
gaia_oauth_client_.reset();
CheckRobotAccountId(gaia_robot_id);
}
void DeviceOAuth2TokenService::OnOAuthError() {
gaia_oauth_client_.reset();
state_ = STATE_TOKEN_INVALID;
FlushPendingRequests(false, GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
}
void DeviceOAuth2TokenService::OnNetworkError(int response_code) {
gaia_oauth_client_.reset();
// Go back to pending validation state. That'll allow a retry on subsequent
// token minting requests.
state_ = STATE_VALIDATION_PENDING;
FlushPendingRequests(false, GoogleServiceAuthError::CONNECTION_FAILED);
}
std::string DeviceOAuth2TokenService::GetRefreshToken(
const std::string& account_id) const {
switch (state_) {
case STATE_LOADING:
case STATE_NO_TOKEN:
case STATE_TOKEN_INVALID:
// This shouldn't happen: GetRefreshToken() is only called for actual
// token minting operations. In above states, requests are either queued
// or short-circuited to signal error immediately, so no actual token
// minting via OAuth2TokenService::FetchOAuth2Token should be triggered.
NOTREACHED();
return std::string();
case STATE_VALIDATION_PENDING:
case STATE_VALIDATION_STARTED:
case STATE_TOKEN_VALID:
return refresh_token_;
}
NOTREACHED() << "Unhandled state " << state_;
return std::string();
}
net::URLRequestContextGetter* DeviceOAuth2TokenService::GetRequestContext() {
return url_request_context_getter_.get();
}
void DeviceOAuth2TokenService::FetchOAuth2Token(
RequestImpl* request,
const std::string& account_id,
net::URLRequestContextGetter* getter,
const std::string& client_id,
const std::string& client_secret,
const ScopeSet& scopes) {
switch (state_) {
case STATE_VALIDATION_PENDING:
// If this is the first request for a token, start validation.
StartValidation();
// fall through.
case STATE_LOADING:
case STATE_VALIDATION_STARTED:
// Add a pending request that will be satisfied once validation completes.
pending_requests_.push_back(new PendingRequest(
request->AsWeakPtr(), client_id, client_secret, scopes));
return;
case STATE_NO_TOKEN:
FailRequest(request, GoogleServiceAuthError::USER_NOT_SIGNED_UP);
return;
case STATE_TOKEN_INVALID:
FailRequest(request, GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
return;
case STATE_TOKEN_VALID:
// Pass through to OAuth2TokenService to satisfy the request.
OAuth2TokenService::FetchOAuth2Token(
request, account_id, getter, client_id, client_secret, scopes);
return;
}
NOTREACHED() << "Unexpected state " << state_;
}
OAuth2AccessTokenFetcher* DeviceOAuth2TokenService::CreateAccessTokenFetcher(
const std::string& account_id,
net::URLRequestContextGetter* getter,
OAuth2AccessTokenConsumer* consumer) {
std::string refresh_token = GetRefreshToken(account_id);
DCHECK(!refresh_token.empty());
return new OAuth2AccessTokenFetcherImpl(consumer, getter, refresh_token);
}
void DeviceOAuth2TokenService::DidGetSystemSalt(
const std::string& system_salt) {
system_salt_ = system_salt;
// Bail out if system salt is not available.
if (system_salt_.empty()) {
LOG(ERROR) << "Failed to get system salt.";
FlushTokenSaveCallbacks(false);
state_ = STATE_NO_TOKEN;
FireRefreshTokensLoaded();
return;
}
// If the token has been set meanwhile, write it to |local_state_|.
if (!refresh_token_.empty()) {
EncryptAndSaveToken();
FireRefreshTokensLoaded();
return;
}
// Otherwise, load the refresh token from |local_state_|.
std::string encrypted_refresh_token =
local_state_->GetString(prefs::kDeviceRobotAnyApiRefreshToken);
if (!encrypted_refresh_token.empty()) {
CryptohomeTokenEncryptor encryptor(system_salt_);
refresh_token_ = encryptor.DecryptWithSystemSalt(encrypted_refresh_token);
if (refresh_token_.empty()) {
LOG(ERROR) << "Failed to decrypt refresh token.";
state_ = STATE_NO_TOKEN;
FireRefreshTokensLoaded();
return;
}
}
state_ = STATE_VALIDATION_PENDING;
// If there are pending requests, start a validation.
if (!pending_requests_.empty())
StartValidation();
// Announce the token.
FireRefreshTokenAvailable(GetRobotAccountId());
FireRefreshTokensLoaded();
}
void DeviceOAuth2TokenService::CheckRobotAccountId(
const std::string& gaia_robot_id) {
// Make sure the value returned by GetRobotAccountId has been validated
// against current device settings.
switch (CrosSettings::Get()->PrepareTrustedValues(
base::Bind(&DeviceOAuth2TokenService::CheckRobotAccountId,
weak_ptr_factory_.GetWeakPtr(),
gaia_robot_id))) {
case CrosSettingsProvider::TRUSTED:
// All good, compare account ids below.
break;
case CrosSettingsProvider::TEMPORARILY_UNTRUSTED:
// The callback passed to PrepareTrustedValues above will trigger a
// re-check eventually.
return;
case CrosSettingsProvider::PERMANENTLY_UNTRUSTED:
// There's no trusted account id, which is equivalent to no token present.
LOG(WARNING) << "Device settings permanently untrusted.";
state_ = STATE_NO_TOKEN;
FlushPendingRequests(false, GoogleServiceAuthError::USER_NOT_SIGNED_UP);
return;
}
std::string policy_robot_id = GetRobotAccountId();
if (policy_robot_id == gaia_robot_id) {
state_ = STATE_TOKEN_VALID;
FlushPendingRequests(true, GoogleServiceAuthError::NONE);
} else {
if (gaia_robot_id.empty()) {
LOG(WARNING) << "Device service account owner in policy is empty.";
} else {
LOG(WARNING) << "Device service account owner in policy does not match "
<< "refresh token owner \"" << gaia_robot_id << "\".";
}
state_ = STATE_TOKEN_INVALID;
FlushPendingRequests(false,
GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
}
}
void DeviceOAuth2TokenService::EncryptAndSaveToken() {
DCHECK_NE(state_, STATE_LOADING);
CryptohomeTokenEncryptor encryptor(system_salt_);
std::string encrypted_refresh_token =
encryptor.EncryptWithSystemSalt(refresh_token_);
bool result = true;
if (encrypted_refresh_token.empty()) {
LOG(ERROR) << "Failed to encrypt refresh token; save aborted.";
result = false;
} else {
local_state_->SetString(prefs::kDeviceRobotAnyApiRefreshToken,
encrypted_refresh_token);
}
FlushTokenSaveCallbacks(result);
}
void DeviceOAuth2TokenService::StartValidation() {
DCHECK_EQ(state_, STATE_VALIDATION_PENDING);
DCHECK(!gaia_oauth_client_);
state_ = STATE_VALIDATION_STARTED;
gaia_oauth_client_.reset(new gaia::GaiaOAuthClient(
g_browser_process->system_request_context()));
GaiaUrls* gaia_urls = GaiaUrls::GetInstance();
gaia::OAuthClientInfo client_info;
client_info.client_id = gaia_urls->oauth2_chrome_client_id();
client_info.client_secret = gaia_urls->oauth2_chrome_client_secret();
gaia_oauth_client_->RefreshToken(
client_info,
refresh_token_,
std::vector<std::string>(1, GaiaConstants::kOAuthWrapBridgeUserInfoScope),
max_refresh_token_validation_retries_,
this);
}
void DeviceOAuth2TokenService::FlushPendingRequests(
bool token_is_valid,
GoogleServiceAuthError::State error) {
std::vector<PendingRequest*> requests;
requests.swap(pending_requests_);
for (std::vector<PendingRequest*>::iterator request(requests.begin());
request != requests.end();
++request) {
scoped_ptr<PendingRequest> scoped_request(*request);
if (!scoped_request->request)
continue;
if (token_is_valid) {
OAuth2TokenService::FetchOAuth2Token(
scoped_request->request.get(),
scoped_request->request->GetAccountId(),
GetRequestContext(),
scoped_request->client_id,
scoped_request->client_secret,
scoped_request->scopes);
} else {
FailRequest(scoped_request->request.get(), error);
}
}
}
void DeviceOAuth2TokenService::FlushTokenSaveCallbacks(bool result) {
std::vector<StatusCallback> callbacks;
callbacks.swap(token_save_callbacks_);
for (std::vector<StatusCallback>::iterator callback(callbacks.begin());
callback != callbacks.end();
++callback) {
if (!callback->is_null())
callback->Run(result);
}
}
void DeviceOAuth2TokenService::FailRequest(
RequestImpl* request,
GoogleServiceAuthError::State error) {
GoogleServiceAuthError auth_error(error);
base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
&RequestImpl::InformConsumer,
request->AsWeakPtr(),
auth_error,
std::string(),
base::Time()));
}
} // namespace chromeos