Do not announce robot account token before account ID is available
[chromium-blink-merge.git] / chrome / browser / chromeos / settings / device_oauth2_token_service.cc
blob1dc6bde934c3f50fbc53cff2d0e33b5f09c50b46
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"
7 #include <string>
8 #include <vector>
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"
28 namespace chromeos {
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)
35 : request(request),
36 client_id(client_id),
37 client_secret(client_secret),
38 scopes(scopes) {}
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,
61 base::Bind(
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);
76 // static
77 void DeviceOAuth2TokenService::RegisterPrefs(PrefRegistrySimple* registry) {
78 registry->RegisterStringPref(prefs::kDeviceRobotAnyApiRefreshToken,
79 std::string());
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);
101 else
102 EncryptAndSaveToken();
106 bool DeviceOAuth2TokenService::RefreshTokenIsAvailable(
107 const std::string& account_id) const {
108 switch (state_) {
109 case STATE_NO_TOKEN:
110 case STATE_TOKEN_INVALID:
111 return false;
112 case STATE_LOADING:
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_;
120 return false;
123 std::string DeviceOAuth2TokenService::GetRobotAccountId() const {
124 std::string result;
125 CrosSettings::Get()->GetString(kServiceAccountIdentity, &result);
126 return result;
129 void DeviceOAuth2TokenService::OnRefreshTokenResponse(
130 const std::string& access_token,
131 int expires_in_seconds) {
132 gaia_oauth_client_->GetTokenInfo(
133 access_token,
134 max_refresh_token_validation_retries_,
135 this);
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 {
164 switch (state_) {
165 case STATE_LOADING:
166 case STATE_NO_TOKEN:
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.
172 NOTREACHED();
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) {
195 switch (state_) {
196 case STATE_VALIDATION_PENDING:
197 // If this is the first request for a token, start validation.
198 StartValidation();
199 // fall through.
200 case STATE_LOADING:
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));
205 return;
206 case STATE_NO_TOKEN:
207 FailRequest(request, GoogleServiceAuthError::USER_NOT_SIGNED_UP);
208 return;
209 case STATE_TOKEN_INVALID:
210 FailRequest(request, GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
211 return;
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);
216 return;
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();
242 return;
245 // If the token has been set meanwhile, write it to |local_state_|.
246 if (!refresh_token_.empty()) {
247 EncryptAndSaveToken();
248 FireRefreshTokensLoaded();
249 return;
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();
262 return;
266 state_ = STATE_VALIDATION_PENDING;
268 // If there are pending requests, start a validation.
269 if (!pending_requests_.empty())
270 StartValidation();
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(),
284 gaia_robot_id))) {
285 case CrosSettingsProvider::TRUSTED:
286 // All good, compare account ids below.
287 break;
288 case CrosSettingsProvider::TEMPORARILY_UNTRUSTED:
289 // The callback passed to PrepareTrustedValues above will trigger a
290 // re-check eventually.
291 return;
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);
297 return;
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);
304 } else {
305 if (gaia_robot_id.empty()) {
306 LOG(WARNING) << "Device service account owner in policy is empty.";
307 } else {
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_);
323 bool result = true;
324 if (encrypted_refresh_token.empty()) {
325 LOG(ERROR) << "Failed to encrypt refresh token; save aborted.";
326 result = false;
327 } else {
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(
350 client_info,
351 refresh_token_,
352 std::vector<std::string>(1, GaiaConstants::kOAuthWrapBridgeUserInfoScope),
353 max_refresh_token_validation_retries_,
354 this);
357 void DeviceOAuth2TokenService::FlushPendingRequests(
358 bool token_is_valid,
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();
364 ++request) {
365 scoped_ptr<PendingRequest> scoped_request(*request);
366 if (!scoped_request->request)
367 continue;
369 if (token_is_valid) {
370 OAuth2TokenService::FetchOAuth2Token(
371 scoped_request->request.get(),
372 scoped_request->request->GetAccountId(),
373 GetRequestContext(),
374 scoped_request->client_id,
375 scoped_request->client_secret,
376 scoped_request->scopes);
377 } else {
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();
388 ++callback) {
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(),
401 auth_error,
402 std::string(),
403 base::Time()));
406 } // namespace chromeos