device_oauth2_token_service.cc revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
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/prefs/pref_registry_simple.h"
11#include "base/prefs/pref_service.h"
12#include "base/values.h"
13#include "chrome/browser/browser_process.h"
14#include "chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.h"
15#include "chrome/browser/chromeos/settings/token_encryptor.h"
16#include "chrome/browser/policy/browser_policy_connector.h"
17#include "chrome/browser/policy/proto/cloud/device_management_backend.pb.h"
18#include "chrome/common/pref_names.h"
19#include "chromeos/cryptohome/cryptohome_library.h"
20#include "content/public/browser/browser_thread.h"
21#include "google_apis/gaia/gaia_urls.h"
22#include "google_apis/gaia/google_service_auth_error.h"
23
24namespace {
25const char kServiceScopeGetUserInfo[] =
26    "https://www.googleapis.com/auth/userinfo.email";
27}
28
29namespace chromeos {
30
31// A wrapper for the consumer passed to StartRequest, which doesn't call
32// through to the target Consumer unless the refresh token validation is
33// complete. Additionally derives from the RequestImpl, so that it
34// can be passed back to the caller and directly deleted when cancelling
35// the request.
36class DeviceOAuth2TokenService::ValidatingConsumer
37    : public OAuth2TokenService::Consumer,
38      public OAuth2TokenService::RequestImpl,
39      public gaia::GaiaOAuthClient::Delegate {
40 public:
41  explicit ValidatingConsumer(DeviceOAuth2TokenService* token_service,
42                              Consumer* consumer);
43  virtual ~ValidatingConsumer();
44
45  void StartValidation();
46
47  // OAuth2TokenService::Consumer
48  virtual void OnGetTokenSuccess(
49      const Request* request,
50      const std::string& access_token,
51      const base::Time& expiration_time) OVERRIDE;
52  virtual void OnGetTokenFailure(
53      const Request* request,
54      const GoogleServiceAuthError& error) OVERRIDE;
55
56  // gaia::GaiaOAuthClient::Delegate implementation.
57  virtual void OnRefreshTokenResponse(const std::string& access_token,
58                                      int expires_in_seconds) OVERRIDE;
59  virtual void OnGetTokenInfoResponse(scoped_ptr<DictionaryValue> token_info)
60      OVERRIDE;
61  virtual void OnOAuthError() OVERRIDE;
62  virtual void OnNetworkError(int response_code) OVERRIDE;
63
64 private:
65  void RefreshTokenIsValid(bool is_valid);
66  void InformConsumer();
67
68  DeviceOAuth2TokenService* token_service_;
69  Consumer* consumer_;
70  scoped_ptr<gaia::GaiaOAuthClient> gaia_oauth_client_;
71
72  // We don't know which will complete first: the validation or the token
73  // minting.  So, we need to cache the results so the final callback can
74  // take action.
75
76  // RefreshTokenValidationConsumer results
77  bool token_validation_done_;
78  bool token_is_valid_;
79
80  // OAuth2TokenService::Consumer results
81  bool token_fetch_done_;
82  std::string access_token_;
83  base::Time expiration_time_;
84  scoped_ptr<GoogleServiceAuthError> error_;
85};
86
87DeviceOAuth2TokenService::ValidatingConsumer::ValidatingConsumer(
88    DeviceOAuth2TokenService* token_service,
89    Consumer* consumer)
90        : OAuth2TokenService::RequestImpl(this),
91          token_service_(token_service),
92          consumer_(consumer),
93          token_validation_done_(false),
94          token_is_valid_(false),
95          token_fetch_done_(false) {
96}
97
98DeviceOAuth2TokenService::ValidatingConsumer::~ValidatingConsumer() {
99}
100
101void DeviceOAuth2TokenService::ValidatingConsumer::StartValidation() {
102  DCHECK(!gaia_oauth_client_);
103  gaia_oauth_client_.reset(new gaia::GaiaOAuthClient(
104      g_browser_process->system_request_context()));
105
106  GaiaUrls* gaia_urls = GaiaUrls::GetInstance();
107  gaia::OAuthClientInfo client_info;
108  client_info.client_id = gaia_urls->oauth2_chrome_client_id();
109  client_info.client_secret = gaia_urls->oauth2_chrome_client_secret();
110
111  gaia_oauth_client_->RefreshToken(
112      client_info,
113      token_service_->GetRefreshToken(token_service_->GetRobotAccountId()),
114      std::vector<std::string>(1, kServiceScopeGetUserInfo),
115      token_service_->max_refresh_token_validation_retries_,
116      this);
117}
118
119void DeviceOAuth2TokenService::ValidatingConsumer::OnRefreshTokenResponse(
120    const std::string& access_token,
121    int expires_in_seconds) {
122  gaia_oauth_client_->GetTokenInfo(
123      access_token,
124      token_service_->max_refresh_token_validation_retries_,
125      this);
126}
127
128void DeviceOAuth2TokenService::ValidatingConsumer::OnGetTokenInfoResponse(
129    scoped_ptr<DictionaryValue> token_info) {
130  std::string gaia_robot_id;
131  token_info->GetString("email", &gaia_robot_id);
132
133  std::string policy_robot_id = token_service_->GetRobotAccountId();
134
135  if (policy_robot_id == gaia_robot_id) {
136    RefreshTokenIsValid(true);
137  } else {
138    if (gaia_robot_id.empty()) {
139      LOG(WARNING) << "Device service account owner in policy is empty.";
140    } else {
141      LOG(INFO) << "Device service account owner in policy does not match "
142                << "refresh token owner \"" << gaia_robot_id << "\".";
143    }
144    RefreshTokenIsValid(false);
145  }
146}
147
148void DeviceOAuth2TokenService::ValidatingConsumer::OnOAuthError() {
149  RefreshTokenIsValid(false);
150}
151
152void DeviceOAuth2TokenService::ValidatingConsumer::OnNetworkError(
153    int response_code) {
154  RefreshTokenIsValid(false);
155}
156
157void DeviceOAuth2TokenService::ValidatingConsumer::OnGetTokenSuccess(
158      const Request* request,
159      const std::string& access_token,
160      const base::Time& expiration_time) {
161  DCHECK_EQ(request, this);
162  token_fetch_done_ = true;
163  access_token_ = access_token;
164  expiration_time_ = expiration_time;
165  if (token_validation_done_)
166    InformConsumer();
167}
168
169void DeviceOAuth2TokenService::ValidatingConsumer::OnGetTokenFailure(
170      const Request* request,
171      const GoogleServiceAuthError& error) {
172  DCHECK_EQ(request, this);
173  token_fetch_done_ = true;
174  error_.reset(new GoogleServiceAuthError(error.state()));
175  if (token_validation_done_)
176    InformConsumer();
177}
178
179void DeviceOAuth2TokenService::ValidatingConsumer::RefreshTokenIsValid(
180    bool is_valid) {
181  token_validation_done_ = true;
182  token_is_valid_ = is_valid;
183  token_service_->OnValidationComplete(is_valid);
184  if (token_fetch_done_)
185    InformConsumer();
186}
187
188void DeviceOAuth2TokenService::ValidatingConsumer::InformConsumer() {
189  DCHECK(token_fetch_done_);
190  DCHECK(token_validation_done_);
191
192  // Note: this object (which is also the Request instance) may be deleted in
193  // these consumer callbacks, so the callbacks must be the last line executed.
194  // Also, make copies of the parameters passed to the consumer to avoid invalid
195  // memory accesses when the consumer deletes |this| immediately.
196  if (!token_is_valid_) {
197    consumer_->OnGetTokenFailure(this, GoogleServiceAuthError(
198        GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
199  } else if (error_) {
200    GoogleServiceAuthError error_copy = *error_;
201    consumer_->OnGetTokenFailure(this, error_copy);
202  } else {
203    std::string access_token_copy = access_token_;
204    base::Time expiration_time_copy = expiration_time_;
205    consumer_->OnGetTokenSuccess(this, access_token_copy, expiration_time_copy);
206  }
207}
208
209DeviceOAuth2TokenService::DeviceOAuth2TokenService(
210    net::URLRequestContextGetter* getter,
211    PrefService* local_state,
212    TokenEncryptor* token_encryptor)
213    : refresh_token_is_valid_(false),
214      max_refresh_token_validation_retries_(3),
215      url_request_context_getter_(getter),
216      local_state_(local_state),
217      token_encryptor_(token_encryptor),
218      weak_ptr_factory_(this) {
219}
220
221DeviceOAuth2TokenService::~DeviceOAuth2TokenService() {
222}
223
224void DeviceOAuth2TokenService::OnValidationComplete(
225    bool refresh_token_is_valid) {
226  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
227  refresh_token_is_valid_ = refresh_token_is_valid;
228}
229
230// static
231void DeviceOAuth2TokenService::RegisterPrefs(PrefRegistrySimple* registry) {
232  registry->RegisterStringPref(prefs::kDeviceRobotAnyApiRefreshToken,
233                               std::string());
234}
235
236void DeviceOAuth2TokenService::SetAndSaveRefreshToken(
237    const std::string& refresh_token) {
238  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
239
240  // TODO(xiyuan): Use async GetSystemSalt after merging to M31.
241  const std::string system_salt = CryptohomeLibrary::Get()->GetSystemSaltSync();
242  if (system_salt.empty()) {
243    const int64 kRequestSystemSaltDelayMs = 500;
244    content::BrowserThread::PostDelayedTask(
245        content::BrowserThread::UI,
246        FROM_HERE,
247        base::Bind(&DeviceOAuth2TokenService::SetAndSaveRefreshToken,
248                   weak_ptr_factory_.GetWeakPtr(),
249                   refresh_token),
250        base::TimeDelta::FromMilliseconds(kRequestSystemSaltDelayMs));
251    return;
252  }
253
254  std::string encrypted_refresh_token =
255      token_encryptor_->EncryptWithSystemSalt(refresh_token);
256
257  local_state_->SetString(prefs::kDeviceRobotAnyApiRefreshToken,
258                          encrypted_refresh_token);
259}
260
261std::string DeviceOAuth2TokenService::GetRefreshToken(
262    const std::string& account_id) {
263  DCHECK_EQ(account_id, GetRobotAccountId());
264  if (refresh_token_.empty()) {
265    std::string encrypted_refresh_token =
266        local_state_->GetString(prefs::kDeviceRobotAnyApiRefreshToken);
267
268    // TODO(xiyuan): This needs a proper fix after M31.
269    LOG_IF(ERROR, CryptohomeLibrary::Get()->GetSystemSaltSync().empty())
270        << "System salt is not available for decryption";
271
272    refresh_token_ = token_encryptor_->DecryptWithSystemSalt(
273        encrypted_refresh_token);
274  }
275  return refresh_token_;
276}
277
278std::string DeviceOAuth2TokenService::GetRobotAccountId() {
279  policy::BrowserPolicyConnector* connector =
280      g_browser_process->browser_policy_connector();
281  if (connector)
282    return connector->GetDeviceCloudPolicyManager()->GetRobotAccountId();
283  return std::string();
284}
285
286net::URLRequestContextGetter* DeviceOAuth2TokenService::GetRequestContext() {
287  return url_request_context_getter_.get();
288}
289
290scoped_ptr<OAuth2TokenService::RequestImpl>
291DeviceOAuth2TokenService::CreateRequest(
292    OAuth2TokenService::Consumer* consumer) {
293  if (refresh_token_is_valid_)
294    return OAuth2TokenService::CreateRequest(consumer);
295
296  // Substitute our own consumer to wait for refresh token validation.
297  scoped_ptr<ValidatingConsumer> validating_consumer(
298      new ValidatingConsumer(this, consumer));
299  validating_consumer->StartValidation();
300  return validating_consumer.PassAs<RequestImpl>();
301}
302
303}  // namespace chromeos
304