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