device_oauth2_token_service.cc revision 116680a4aac90f2aa7413d9095a592090648e557
1// Copyright 2013 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/chromeos/settings/device_oauth2_token_service.h" 6 7#include <string> 8#include <vector> 9 10#include "base/bind.h" 11#include "base/memory/weak_ptr.h" 12#include "base/message_loop/message_loop.h" 13#include "base/prefs/pref_registry_simple.h" 14#include "base/prefs/pref_service.h" 15#include "base/values.h" 16#include "chrome/browser/browser_process.h" 17#include "chrome/browser/chromeos/settings/cros_settings.h" 18#include "chrome/browser/chromeos/settings/token_encryptor.h" 19#include "chrome/common/pref_names.h" 20#include "chromeos/cryptohome/system_salt_getter.h" 21#include "google_apis/gaia/gaia_constants.h" 22#include "google_apis/gaia/gaia_urls.h" 23#include "google_apis/gaia/google_service_auth_error.h" 24#include "google_apis/gaia/oauth2_access_token_fetcher_impl.h" 25#include "policy/proto/device_management_backend.pb.h" 26 27namespace chromeos { 28 29struct DeviceOAuth2TokenService::PendingRequest { 30 PendingRequest(const base::WeakPtr<RequestImpl>& request, 31 const std::string& client_id, 32 const std::string& client_secret, 33 const ScopeSet& scopes) 34 : request(request), 35 client_id(client_id), 36 client_secret(client_secret), 37 scopes(scopes) {} 38 39 const base::WeakPtr<RequestImpl> request; 40 const std::string client_id; 41 const std::string client_secret; 42 const ScopeSet scopes; 43}; 44 45DeviceOAuth2TokenService::DeviceOAuth2TokenService( 46 net::URLRequestContextGetter* getter, 47 PrefService* local_state) 48 : url_request_context_getter_(getter), 49 local_state_(local_state), 50 state_(STATE_LOADING), 51 max_refresh_token_validation_retries_(3), 52 weak_ptr_factory_(this) { 53 // Pull in the system salt. 54 SystemSaltGetter::Get()->GetSystemSalt( 55 base::Bind(&DeviceOAuth2TokenService::DidGetSystemSalt, 56 weak_ptr_factory_.GetWeakPtr())); 57} 58 59DeviceOAuth2TokenService::~DeviceOAuth2TokenService() { 60 FlushPendingRequests(false, GoogleServiceAuthError::REQUEST_CANCELED); 61 FlushTokenSaveCallbacks(false); 62} 63 64// static 65void DeviceOAuth2TokenService::RegisterPrefs(PrefRegistrySimple* registry) { 66 registry->RegisterStringPref(prefs::kDeviceRobotAnyApiRefreshToken, 67 std::string()); 68} 69 70void DeviceOAuth2TokenService::SetAndSaveRefreshToken( 71 const std::string& refresh_token, 72 const StatusCallback& result_callback) { 73 FlushPendingRequests(false, GoogleServiceAuthError::REQUEST_CANCELED); 74 75 bool waiting_for_salt = state_ == STATE_LOADING; 76 refresh_token_ = refresh_token; 77 state_ = STATE_VALIDATION_PENDING; 78 FireRefreshTokenAvailable(GetRobotAccountId()); 79 80 token_save_callbacks_.push_back(result_callback); 81 if (!waiting_for_salt) { 82 if (system_salt_.empty()) 83 FlushTokenSaveCallbacks(false); 84 else 85 EncryptAndSaveToken(); 86 } 87} 88 89bool DeviceOAuth2TokenService::RefreshTokenIsAvailable( 90 const std::string& account_id) const { 91 switch (state_) { 92 case STATE_NO_TOKEN: 93 case STATE_TOKEN_INVALID: 94 return false; 95 case STATE_LOADING: 96 case STATE_VALIDATION_PENDING: 97 case STATE_VALIDATION_STARTED: 98 case STATE_TOKEN_VALID: 99 return account_id == GetRobotAccountId(); 100 } 101 102 NOTREACHED() << "Unhandled state " << state_; 103 return false; 104} 105 106std::string DeviceOAuth2TokenService::GetRobotAccountId() const { 107 std::string result; 108 CrosSettings::Get()->GetString(kServiceAccountIdentity, &result); 109 return result; 110} 111 112void DeviceOAuth2TokenService::OnRefreshTokenResponse( 113 const std::string& access_token, 114 int expires_in_seconds) { 115 gaia_oauth_client_->GetTokenInfo( 116 access_token, 117 max_refresh_token_validation_retries_, 118 this); 119} 120 121void DeviceOAuth2TokenService::OnGetTokenInfoResponse( 122 scoped_ptr<base::DictionaryValue> token_info) { 123 std::string gaia_robot_id; 124 token_info->GetString("email", &gaia_robot_id); 125 gaia_oauth_client_.reset(); 126 127 CheckRobotAccountId(gaia_robot_id); 128} 129 130void DeviceOAuth2TokenService::OnOAuthError() { 131 gaia_oauth_client_.reset(); 132 state_ = STATE_TOKEN_INVALID; 133 FlushPendingRequests(false, GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); 134} 135 136void DeviceOAuth2TokenService::OnNetworkError(int response_code) { 137 gaia_oauth_client_.reset(); 138 139 // Go back to pending validation state. That'll allow a retry on subsequent 140 // token minting requests. 141 state_ = STATE_VALIDATION_PENDING; 142 FlushPendingRequests(false, GoogleServiceAuthError::CONNECTION_FAILED); 143} 144 145std::string DeviceOAuth2TokenService::GetRefreshToken( 146 const std::string& account_id) const { 147 switch (state_) { 148 case STATE_LOADING: 149 case STATE_NO_TOKEN: 150 case STATE_TOKEN_INVALID: 151 // This shouldn't happen: GetRefreshToken() is only called for actual 152 // token minting operations. In above states, requests are either queued 153 // or short-circuited to signal error immediately, so no actual token 154 // minting via OAuth2TokenService::FetchOAuth2Token should be triggered. 155 NOTREACHED(); 156 return std::string(); 157 case STATE_VALIDATION_PENDING: 158 case STATE_VALIDATION_STARTED: 159 case STATE_TOKEN_VALID: 160 return refresh_token_; 161 } 162 163 NOTREACHED() << "Unhandled state " << state_; 164 return std::string(); 165} 166 167net::URLRequestContextGetter* DeviceOAuth2TokenService::GetRequestContext() { 168 return url_request_context_getter_.get(); 169} 170 171void DeviceOAuth2TokenService::FetchOAuth2Token( 172 RequestImpl* request, 173 const std::string& account_id, 174 net::URLRequestContextGetter* getter, 175 const std::string& client_id, 176 const std::string& client_secret, 177 const ScopeSet& scopes) { 178 switch (state_) { 179 case STATE_VALIDATION_PENDING: 180 // If this is the first request for a token, start validation. 181 StartValidation(); 182 // fall through. 183 case STATE_LOADING: 184 case STATE_VALIDATION_STARTED: 185 // Add a pending request that will be satisfied once validation completes. 186 pending_requests_.push_back(new PendingRequest( 187 request->AsWeakPtr(), client_id, client_secret, scopes)); 188 return; 189 case STATE_NO_TOKEN: 190 FailRequest(request, GoogleServiceAuthError::USER_NOT_SIGNED_UP); 191 return; 192 case STATE_TOKEN_INVALID: 193 FailRequest(request, GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); 194 return; 195 case STATE_TOKEN_VALID: 196 // Pass through to OAuth2TokenService to satisfy the request. 197 OAuth2TokenService::FetchOAuth2Token( 198 request, account_id, getter, client_id, client_secret, scopes); 199 return; 200 } 201 202 NOTREACHED() << "Unexpected state " << state_; 203} 204 205OAuth2AccessTokenFetcher* DeviceOAuth2TokenService::CreateAccessTokenFetcher( 206 const std::string& account_id, 207 net::URLRequestContextGetter* getter, 208 OAuth2AccessTokenConsumer* consumer) { 209 std::string refresh_token = GetRefreshToken(account_id); 210 DCHECK(!refresh_token.empty()); 211 return new OAuth2AccessTokenFetcherImpl(consumer, getter, refresh_token); 212} 213 214 215void DeviceOAuth2TokenService::DidGetSystemSalt( 216 const std::string& system_salt) { 217 system_salt_ = system_salt; 218 219 // Bail out if system salt is not available. 220 if (system_salt_.empty()) { 221 LOG(ERROR) << "Failed to get system salt."; 222 FlushTokenSaveCallbacks(false); 223 state_ = STATE_NO_TOKEN; 224 FireRefreshTokensLoaded(); 225 return; 226 } 227 228 // If the token has been set meanwhile, write it to |local_state_|. 229 if (!refresh_token_.empty()) { 230 EncryptAndSaveToken(); 231 FireRefreshTokensLoaded(); 232 return; 233 } 234 235 // Otherwise, load the refresh token from |local_state_|. 236 std::string encrypted_refresh_token = 237 local_state_->GetString(prefs::kDeviceRobotAnyApiRefreshToken); 238 if (!encrypted_refresh_token.empty()) { 239 CryptohomeTokenEncryptor encryptor(system_salt_); 240 refresh_token_ = encryptor.DecryptWithSystemSalt(encrypted_refresh_token); 241 if (refresh_token_.empty()) { 242 LOG(ERROR) << "Failed to decrypt refresh token."; 243 state_ = STATE_NO_TOKEN; 244 FireRefreshTokensLoaded(); 245 return; 246 } 247 } 248 249 state_ = STATE_VALIDATION_PENDING; 250 251 // If there are pending requests, start a validation. 252 if (!pending_requests_.empty()) 253 StartValidation(); 254 255 // Announce the token. 256 FireRefreshTokenAvailable(GetRobotAccountId()); 257 FireRefreshTokensLoaded(); 258} 259 260void DeviceOAuth2TokenService::CheckRobotAccountId( 261 const std::string& gaia_robot_id) { 262 // Make sure the value returned by GetRobotAccountId has been validated 263 // against current device settings. 264 switch (CrosSettings::Get()->PrepareTrustedValues( 265 base::Bind(&DeviceOAuth2TokenService::CheckRobotAccountId, 266 weak_ptr_factory_.GetWeakPtr(), 267 gaia_robot_id))) { 268 case CrosSettingsProvider::TRUSTED: 269 // All good, compare account ids below. 270 break; 271 case CrosSettingsProvider::TEMPORARILY_UNTRUSTED: 272 // The callback passed to PrepareTrustedValues above will trigger a 273 // re-check eventually. 274 return; 275 case CrosSettingsProvider::PERMANENTLY_UNTRUSTED: 276 // There's no trusted account id, which is equivalent to no token present. 277 LOG(WARNING) << "Device settings permanently untrusted."; 278 state_ = STATE_NO_TOKEN; 279 FlushPendingRequests(false, GoogleServiceAuthError::USER_NOT_SIGNED_UP); 280 return; 281 } 282 283 std::string policy_robot_id = GetRobotAccountId(); 284 if (policy_robot_id == gaia_robot_id) { 285 state_ = STATE_TOKEN_VALID; 286 FlushPendingRequests(true, GoogleServiceAuthError::NONE); 287 } else { 288 if (gaia_robot_id.empty()) { 289 LOG(WARNING) << "Device service account owner in policy is empty."; 290 } else { 291 LOG(WARNING) << "Device service account owner in policy does not match " 292 << "refresh token owner \"" << gaia_robot_id << "\"."; 293 } 294 state_ = STATE_TOKEN_INVALID; 295 FlushPendingRequests(false, 296 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); 297 } 298} 299 300void DeviceOAuth2TokenService::EncryptAndSaveToken() { 301 DCHECK_NE(state_, STATE_LOADING); 302 303 CryptohomeTokenEncryptor encryptor(system_salt_); 304 std::string encrypted_refresh_token = 305 encryptor.EncryptWithSystemSalt(refresh_token_); 306 bool result = true; 307 if (encrypted_refresh_token.empty()) { 308 LOG(ERROR) << "Failed to encrypt refresh token; save aborted."; 309 result = false; 310 } else { 311 local_state_->SetString(prefs::kDeviceRobotAnyApiRefreshToken, 312 encrypted_refresh_token); 313 } 314 315 FlushTokenSaveCallbacks(result); 316} 317 318void DeviceOAuth2TokenService::StartValidation() { 319 DCHECK_EQ(state_, STATE_VALIDATION_PENDING); 320 DCHECK(!gaia_oauth_client_); 321 322 state_ = STATE_VALIDATION_STARTED; 323 324 gaia_oauth_client_.reset(new gaia::GaiaOAuthClient( 325 g_browser_process->system_request_context())); 326 327 GaiaUrls* gaia_urls = GaiaUrls::GetInstance(); 328 gaia::OAuthClientInfo client_info; 329 client_info.client_id = gaia_urls->oauth2_chrome_client_id(); 330 client_info.client_secret = gaia_urls->oauth2_chrome_client_secret(); 331 332 gaia_oauth_client_->RefreshToken( 333 client_info, 334 refresh_token_, 335 std::vector<std::string>(1, GaiaConstants::kOAuthWrapBridgeUserInfoScope), 336 max_refresh_token_validation_retries_, 337 this); 338} 339 340void DeviceOAuth2TokenService::FlushPendingRequests( 341 bool token_is_valid, 342 GoogleServiceAuthError::State error) { 343 std::vector<PendingRequest*> requests; 344 requests.swap(pending_requests_); 345 for (std::vector<PendingRequest*>::iterator request(requests.begin()); 346 request != requests.end(); 347 ++request) { 348 scoped_ptr<PendingRequest> scoped_request(*request); 349 if (!scoped_request->request) 350 continue; 351 352 if (token_is_valid) { 353 OAuth2TokenService::FetchOAuth2Token( 354 scoped_request->request.get(), 355 scoped_request->request->GetAccountId(), 356 GetRequestContext(), 357 scoped_request->client_id, 358 scoped_request->client_secret, 359 scoped_request->scopes); 360 } else { 361 FailRequest(scoped_request->request.get(), error); 362 } 363 } 364} 365 366void DeviceOAuth2TokenService::FlushTokenSaveCallbacks(bool result) { 367 std::vector<StatusCallback> callbacks; 368 callbacks.swap(token_save_callbacks_); 369 for (std::vector<StatusCallback>::iterator callback(callbacks.begin()); 370 callback != callbacks.end(); 371 ++callback) { 372 if (!callback->is_null()) 373 callback->Run(result); 374 } 375} 376 377void DeviceOAuth2TokenService::FailRequest( 378 RequestImpl* request, 379 GoogleServiceAuthError::State error) { 380 GoogleServiceAuthError auth_error(error); 381 base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind( 382 &RequestImpl::InformConsumer, 383 request->AsWeakPtr(), 384 auth_error, 385 std::string(), 386 base::Time())); 387} 388 389} // namespace chromeos 390