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.
5 #include "chrome/browser/chromeos/settings/device_oauth2_token_service.h"
10 #include "base/bind.h"
11 #include "base/bind_helpers.h"
12 #include "base/memory/weak_ptr.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/prefs/pref_registry_simple.h"
15 #include "base/prefs/pref_service.h"
16 #include "base/values.h"
17 #include "chrome/browser/browser_process.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 "chromeos/settings/cros_settings_names.h"
22 #include "google_apis/gaia/gaia_constants.h"
23 #include "google_apis/gaia/gaia_urls.h"
24 #include "google_apis/gaia/google_service_auth_error.h"
25 #include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
26 #include "policy/proto/device_management_backend.pb.h"
30 struct DeviceOAuth2TokenService::PendingRequest
{
31 PendingRequest(const base::WeakPtr
<RequestImpl
>& request
,
32 const std::string
& client_id
,
33 const std::string
& client_secret
,
34 const ScopeSet
& scopes
)
37 client_secret(client_secret
),
40 const base::WeakPtr
<RequestImpl
> request
;
41 const std::string client_id
;
42 const std::string client_secret
;
43 const ScopeSet scopes
;
46 void DeviceOAuth2TokenService::OnServiceAccountIdentityChanged() {
47 if (!GetRobotAccountId().empty() && !refresh_token_
.empty())
48 FireRefreshTokenAvailable(GetRobotAccountId());
51 DeviceOAuth2TokenService::DeviceOAuth2TokenService(
52 net::URLRequestContextGetter
* getter
,
53 PrefService
* local_state
)
54 : url_request_context_getter_(getter
),
55 local_state_(local_state
),
56 state_(STATE_LOADING
),
57 max_refresh_token_validation_retries_(3),
58 service_account_identity_subscription_(
59 CrosSettings::Get()->AddSettingsObserver(
60 kServiceAccountIdentity
,
62 &DeviceOAuth2TokenService::OnServiceAccountIdentityChanged
,
63 base::Unretained(this))).Pass()),
64 weak_ptr_factory_(this) {
65 // Pull in the system salt.
66 SystemSaltGetter::Get()->GetSystemSalt(
67 base::Bind(&DeviceOAuth2TokenService::DidGetSystemSalt
,
68 weak_ptr_factory_
.GetWeakPtr()));
71 DeviceOAuth2TokenService::~DeviceOAuth2TokenService() {
72 FlushPendingRequests(false, GoogleServiceAuthError::REQUEST_CANCELED
);
73 FlushTokenSaveCallbacks(false);
77 void DeviceOAuth2TokenService::RegisterPrefs(PrefRegistrySimple
* registry
) {
78 registry
->RegisterStringPref(prefs::kDeviceRobotAnyApiRefreshToken
,
82 void DeviceOAuth2TokenService::SetAndSaveRefreshToken(
83 const std::string
& refresh_token
,
84 const StatusCallback
& result_callback
) {
85 FlushPendingRequests(false, GoogleServiceAuthError::REQUEST_CANCELED
);
87 bool waiting_for_salt
= state_
== STATE_LOADING
;
88 refresh_token_
= refresh_token
;
89 state_
= STATE_VALIDATION_PENDING
;
91 // If the robot account ID is not available yet, do not announce the token. It
92 // will be done from OnServiceAccountIdentityChanged() once the robot account
93 // ID becomes available as well.
94 if (!GetRobotAccountId().empty())
95 FireRefreshTokenAvailable(GetRobotAccountId());
97 token_save_callbacks_
.push_back(result_callback
);
98 if (!waiting_for_salt
) {
99 if (system_salt_
.empty())
100 FlushTokenSaveCallbacks(false);
102 EncryptAndSaveToken();
106 bool DeviceOAuth2TokenService::RefreshTokenIsAvailable(
107 const std::string
& account_id
) const {
110 case STATE_TOKEN_INVALID
:
113 case STATE_VALIDATION_PENDING
:
114 case STATE_VALIDATION_STARTED
:
115 case STATE_TOKEN_VALID
:
116 return account_id
== GetRobotAccountId();
119 NOTREACHED() << "Unhandled state " << state_
;
123 std::string
DeviceOAuth2TokenService::GetRobotAccountId() const {
125 CrosSettings::Get()->GetString(kServiceAccountIdentity
, &result
);
129 void DeviceOAuth2TokenService::OnRefreshTokenResponse(
130 const std::string
& access_token
,
131 int expires_in_seconds
) {
132 gaia_oauth_client_
->GetTokenInfo(
134 max_refresh_token_validation_retries_
,
138 void DeviceOAuth2TokenService::OnGetTokenInfoResponse(
139 scoped_ptr
<base::DictionaryValue
> token_info
) {
140 std::string gaia_robot_id
;
141 token_info
->GetString("email", &gaia_robot_id
);
142 gaia_oauth_client_
.reset();
144 CheckRobotAccountId(gaia_robot_id
);
147 void DeviceOAuth2TokenService::OnOAuthError() {
148 gaia_oauth_client_
.reset();
149 state_
= STATE_TOKEN_INVALID
;
150 FlushPendingRequests(false, GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS
);
153 void DeviceOAuth2TokenService::OnNetworkError(int response_code
) {
154 gaia_oauth_client_
.reset();
156 // Go back to pending validation state. That'll allow a retry on subsequent
157 // token minting requests.
158 state_
= STATE_VALIDATION_PENDING
;
159 FlushPendingRequests(false, GoogleServiceAuthError::CONNECTION_FAILED
);
162 std::string
DeviceOAuth2TokenService::GetRefreshToken(
163 const std::string
& account_id
) const {
167 case STATE_TOKEN_INVALID
:
168 // This shouldn't happen: GetRefreshToken() is only called for actual
169 // token minting operations. In above states, requests are either queued
170 // or short-circuited to signal error immediately, so no actual token
171 // minting via OAuth2TokenService::FetchOAuth2Token should be triggered.
173 return std::string();
174 case STATE_VALIDATION_PENDING
:
175 case STATE_VALIDATION_STARTED
:
176 case STATE_TOKEN_VALID
:
177 return refresh_token_
;
180 NOTREACHED() << "Unhandled state " << state_
;
181 return std::string();
184 net::URLRequestContextGetter
* DeviceOAuth2TokenService::GetRequestContext() {
185 return url_request_context_getter_
.get();
188 void DeviceOAuth2TokenService::FetchOAuth2Token(
189 RequestImpl
* request
,
190 const std::string
& account_id
,
191 net::URLRequestContextGetter
* getter
,
192 const std::string
& client_id
,
193 const std::string
& client_secret
,
194 const ScopeSet
& scopes
) {
196 case STATE_VALIDATION_PENDING
:
197 // If this is the first request for a token, start validation.
201 case STATE_VALIDATION_STARTED
:
202 // Add a pending request that will be satisfied once validation completes.
203 pending_requests_
.push_back(new PendingRequest(
204 request
->AsWeakPtr(), client_id
, client_secret
, scopes
));
207 FailRequest(request
, GoogleServiceAuthError::USER_NOT_SIGNED_UP
);
209 case STATE_TOKEN_INVALID
:
210 FailRequest(request
, GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS
);
212 case STATE_TOKEN_VALID
:
213 // Pass through to OAuth2TokenService to satisfy the request.
214 OAuth2TokenService::FetchOAuth2Token(
215 request
, account_id
, getter
, client_id
, client_secret
, scopes
);
219 NOTREACHED() << "Unexpected state " << state_
;
222 OAuth2AccessTokenFetcher
* DeviceOAuth2TokenService::CreateAccessTokenFetcher(
223 const std::string
& account_id
,
224 net::URLRequestContextGetter
* getter
,
225 OAuth2AccessTokenConsumer
* consumer
) {
226 std::string refresh_token
= GetRefreshToken(account_id
);
227 DCHECK(!refresh_token
.empty());
228 return new OAuth2AccessTokenFetcherImpl(consumer
, getter
, refresh_token
);
232 void DeviceOAuth2TokenService::DidGetSystemSalt(
233 const std::string
& system_salt
) {
234 system_salt_
= system_salt
;
236 // Bail out if system salt is not available.
237 if (system_salt_
.empty()) {
238 LOG(ERROR
) << "Failed to get system salt.";
239 FlushTokenSaveCallbacks(false);
240 state_
= STATE_NO_TOKEN
;
241 FireRefreshTokensLoaded();
245 // If the token has been set meanwhile, write it to |local_state_|.
246 if (!refresh_token_
.empty()) {
247 EncryptAndSaveToken();
248 FireRefreshTokensLoaded();
252 // Otherwise, load the refresh token from |local_state_|.
253 std::string encrypted_refresh_token
=
254 local_state_
->GetString(prefs::kDeviceRobotAnyApiRefreshToken
);
255 if (!encrypted_refresh_token
.empty()) {
256 CryptohomeTokenEncryptor
encryptor(system_salt_
);
257 refresh_token_
= encryptor
.DecryptWithSystemSalt(encrypted_refresh_token
);
258 if (refresh_token_
.empty()) {
259 LOG(ERROR
) << "Failed to decrypt refresh token.";
260 state_
= STATE_NO_TOKEN
;
261 FireRefreshTokensLoaded();
266 state_
= STATE_VALIDATION_PENDING
;
268 // If there are pending requests, start a validation.
269 if (!pending_requests_
.empty())
272 // Announce the token.
273 FireRefreshTokenAvailable(GetRobotAccountId());
274 FireRefreshTokensLoaded();
277 void DeviceOAuth2TokenService::CheckRobotAccountId(
278 const std::string
& gaia_robot_id
) {
279 // Make sure the value returned by GetRobotAccountId has been validated
280 // against current device settings.
281 switch (CrosSettings::Get()->PrepareTrustedValues(base::Bind(
282 &DeviceOAuth2TokenService::CheckRobotAccountId
,
283 weak_ptr_factory_
.GetWeakPtr(),
285 case CrosSettingsProvider::TRUSTED
:
286 // All good, compare account ids below.
288 case CrosSettingsProvider::TEMPORARILY_UNTRUSTED
:
289 // The callback passed to PrepareTrustedValues above will trigger a
290 // re-check eventually.
292 case CrosSettingsProvider::PERMANENTLY_UNTRUSTED
:
293 // There's no trusted account id, which is equivalent to no token present.
294 LOG(WARNING
) << "Device settings permanently untrusted.";
295 state_
= STATE_NO_TOKEN
;
296 FlushPendingRequests(false, GoogleServiceAuthError::USER_NOT_SIGNED_UP
);
300 std::string policy_robot_id
= GetRobotAccountId();
301 if (policy_robot_id
== gaia_robot_id
) {
302 state_
= STATE_TOKEN_VALID
;
303 FlushPendingRequests(true, GoogleServiceAuthError::NONE
);
305 if (gaia_robot_id
.empty()) {
306 LOG(WARNING
) << "Device service account owner in policy is empty.";
308 LOG(WARNING
) << "Device service account owner in policy does not match "
309 << "refresh token owner \"" << gaia_robot_id
<< "\".";
311 state_
= STATE_TOKEN_INVALID
;
312 FlushPendingRequests(false,
313 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS
);
317 void DeviceOAuth2TokenService::EncryptAndSaveToken() {
318 DCHECK_NE(state_
, STATE_LOADING
);
320 CryptohomeTokenEncryptor
encryptor(system_salt_
);
321 std::string encrypted_refresh_token
=
322 encryptor
.EncryptWithSystemSalt(refresh_token_
);
324 if (encrypted_refresh_token
.empty()) {
325 LOG(ERROR
) << "Failed to encrypt refresh token; save aborted.";
328 local_state_
->SetString(prefs::kDeviceRobotAnyApiRefreshToken
,
329 encrypted_refresh_token
);
332 FlushTokenSaveCallbacks(result
);
335 void DeviceOAuth2TokenService::StartValidation() {
336 DCHECK_EQ(state_
, STATE_VALIDATION_PENDING
);
337 DCHECK(!gaia_oauth_client_
);
339 state_
= STATE_VALIDATION_STARTED
;
341 gaia_oauth_client_
.reset(new gaia::GaiaOAuthClient(
342 g_browser_process
->system_request_context()));
344 GaiaUrls
* gaia_urls
= GaiaUrls::GetInstance();
345 gaia::OAuthClientInfo client_info
;
346 client_info
.client_id
= gaia_urls
->oauth2_chrome_client_id();
347 client_info
.client_secret
= gaia_urls
->oauth2_chrome_client_secret();
349 gaia_oauth_client_
->RefreshToken(
352 std::vector
<std::string
>(1, GaiaConstants::kOAuthWrapBridgeUserInfoScope
),
353 max_refresh_token_validation_retries_
,
357 void DeviceOAuth2TokenService::FlushPendingRequests(
359 GoogleServiceAuthError::State error
) {
360 std::vector
<PendingRequest
*> requests
;
361 requests
.swap(pending_requests_
);
362 for (std::vector
<PendingRequest
*>::iterator
request(requests
.begin());
363 request
!= requests
.end();
365 scoped_ptr
<PendingRequest
> scoped_request(*request
);
366 if (!scoped_request
->request
)
369 if (token_is_valid
) {
370 OAuth2TokenService::FetchOAuth2Token(
371 scoped_request
->request
.get(),
372 scoped_request
->request
->GetAccountId(),
374 scoped_request
->client_id
,
375 scoped_request
->client_secret
,
376 scoped_request
->scopes
);
378 FailRequest(scoped_request
->request
.get(), error
);
383 void DeviceOAuth2TokenService::FlushTokenSaveCallbacks(bool result
) {
384 std::vector
<StatusCallback
> callbacks
;
385 callbacks
.swap(token_save_callbacks_
);
386 for (std::vector
<StatusCallback
>::iterator
callback(callbacks
.begin());
387 callback
!= callbacks
.end();
389 if (!callback
->is_null())
390 callback
->Run(result
);
394 void DeviceOAuth2TokenService::FailRequest(
395 RequestImpl
* request
,
396 GoogleServiceAuthError::State error
) {
397 GoogleServiceAuthError
auth_error(error
);
398 base::MessageLoop::current()->PostTask(FROM_HERE
, base::Bind(
399 &RequestImpl::InformConsumer
,
400 request
->AsWeakPtr(),
406 } // namespace chromeos