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
27 namespace chromeos {
28
29 struct DeviceOAuth2TokenService::PendingRequest {
PendingRequestchromeos::DeviceOAuth2TokenService::PendingRequest30 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
DeviceOAuth2TokenService(net::URLRequestContextGetter * getter,PrefService * local_state)45 DeviceOAuth2TokenService::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
~DeviceOAuth2TokenService()59 DeviceOAuth2TokenService::~DeviceOAuth2TokenService() {
60 FlushPendingRequests(false, GoogleServiceAuthError::REQUEST_CANCELED);
61 FlushTokenSaveCallbacks(false);
62 }
63
64 // static
RegisterPrefs(PrefRegistrySimple * registry)65 void DeviceOAuth2TokenService::RegisterPrefs(PrefRegistrySimple* registry) {
66 registry->RegisterStringPref(prefs::kDeviceRobotAnyApiRefreshToken,
67 std::string());
68 }
69
SetAndSaveRefreshToken(const std::string & refresh_token,const StatusCallback & result_callback)70 void 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
RefreshTokenIsAvailable(const std::string & account_id) const89 bool 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
GetRobotAccountId() const106 std::string DeviceOAuth2TokenService::GetRobotAccountId() const {
107 std::string result;
108 CrosSettings::Get()->GetString(kServiceAccountIdentity, &result);
109 return result;
110 }
111
OnRefreshTokenResponse(const std::string & access_token,int expires_in_seconds)112 void 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
OnGetTokenInfoResponse(scoped_ptr<base::DictionaryValue> token_info)121 void 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
OnOAuthError()130 void DeviceOAuth2TokenService::OnOAuthError() {
131 gaia_oauth_client_.reset();
132 state_ = STATE_TOKEN_INVALID;
133 FlushPendingRequests(false, GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
134 }
135
OnNetworkError(int response_code)136 void 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
GetRefreshToken(const std::string & account_id) const145 std::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
GetRequestContext()167 net::URLRequestContextGetter* DeviceOAuth2TokenService::GetRequestContext() {
168 return url_request_context_getter_.get();
169 }
170
FetchOAuth2Token(RequestImpl * request,const std::string & account_id,net::URLRequestContextGetter * getter,const std::string & client_id,const std::string & client_secret,const ScopeSet & scopes)171 void 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
CreateAccessTokenFetcher(const std::string & account_id,net::URLRequestContextGetter * getter,OAuth2AccessTokenConsumer * consumer)205 OAuth2AccessTokenFetcher* 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
DidGetSystemSalt(const std::string & system_salt)215 void 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 CryptohomeTokenEncryptor encryptor(system_salt_);
239 refresh_token_ = encryptor.DecryptWithSystemSalt(encrypted_refresh_token);
240 if (!encrypted_refresh_token.empty() && refresh_token_.empty()) {
241 LOG(ERROR) << "Failed to decrypt refresh token.";
242 state_ = STATE_NO_TOKEN;
243 FireRefreshTokensLoaded();
244 return;
245 }
246
247 state_ = STATE_VALIDATION_PENDING;
248
249 // If there are pending requests, start a validation.
250 if (!pending_requests_.empty())
251 StartValidation();
252
253 // Announce the token.
254 FireRefreshTokenAvailable(GetRobotAccountId());
255 FireRefreshTokensLoaded();
256 }
257
CheckRobotAccountId(const std::string & gaia_robot_id)258 void DeviceOAuth2TokenService::CheckRobotAccountId(
259 const std::string& gaia_robot_id) {
260 // Make sure the value returned by GetRobotAccountId has been validated
261 // against current device settings.
262 switch (CrosSettings::Get()->PrepareTrustedValues(
263 base::Bind(&DeviceOAuth2TokenService::CheckRobotAccountId,
264 weak_ptr_factory_.GetWeakPtr(),
265 gaia_robot_id))) {
266 case CrosSettingsProvider::TRUSTED:
267 // All good, compare account ids below.
268 break;
269 case CrosSettingsProvider::TEMPORARILY_UNTRUSTED:
270 // The callback passed to PrepareTrustedValues above will trigger a
271 // re-check eventually.
272 return;
273 case CrosSettingsProvider::PERMANENTLY_UNTRUSTED:
274 // There's no trusted account id, which is equivalent to no token present.
275 LOG(WARNING) << "Device settings permanently untrusted.";
276 state_ = STATE_NO_TOKEN;
277 FlushPendingRequests(false, GoogleServiceAuthError::USER_NOT_SIGNED_UP);
278 return;
279 }
280
281 std::string policy_robot_id = GetRobotAccountId();
282 if (policy_robot_id == gaia_robot_id) {
283 state_ = STATE_TOKEN_VALID;
284 FlushPendingRequests(true, GoogleServiceAuthError::NONE);
285 } else {
286 if (gaia_robot_id.empty()) {
287 LOG(WARNING) << "Device service account owner in policy is empty.";
288 } else {
289 LOG(WARNING) << "Device service account owner in policy does not match "
290 << "refresh token owner \"" << gaia_robot_id << "\".";
291 }
292 state_ = STATE_TOKEN_INVALID;
293 FlushPendingRequests(false,
294 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
295 }
296 }
297
EncryptAndSaveToken()298 void DeviceOAuth2TokenService::EncryptAndSaveToken() {
299 DCHECK_NE(state_, STATE_LOADING);
300
301 CryptohomeTokenEncryptor encryptor(system_salt_);
302 std::string encrypted_refresh_token =
303 encryptor.EncryptWithSystemSalt(refresh_token_);
304 bool result = true;
305 if (encrypted_refresh_token.empty()) {
306 LOG(ERROR) << "Failed to encrypt refresh token; save aborted.";
307 result = false;
308 } else {
309 local_state_->SetString(prefs::kDeviceRobotAnyApiRefreshToken,
310 encrypted_refresh_token);
311 }
312
313 FlushTokenSaveCallbacks(result);
314 }
315
StartValidation()316 void DeviceOAuth2TokenService::StartValidation() {
317 DCHECK_EQ(state_, STATE_VALIDATION_PENDING);
318 DCHECK(!gaia_oauth_client_);
319
320 state_ = STATE_VALIDATION_STARTED;
321
322 gaia_oauth_client_.reset(new gaia::GaiaOAuthClient(
323 g_browser_process->system_request_context()));
324
325 GaiaUrls* gaia_urls = GaiaUrls::GetInstance();
326 gaia::OAuthClientInfo client_info;
327 client_info.client_id = gaia_urls->oauth2_chrome_client_id();
328 client_info.client_secret = gaia_urls->oauth2_chrome_client_secret();
329
330 gaia_oauth_client_->RefreshToken(
331 client_info,
332 refresh_token_,
333 std::vector<std::string>(1, GaiaConstants::kOAuthWrapBridgeUserInfoScope),
334 max_refresh_token_validation_retries_,
335 this);
336 }
337
FlushPendingRequests(bool token_is_valid,GoogleServiceAuthError::State error)338 void DeviceOAuth2TokenService::FlushPendingRequests(
339 bool token_is_valid,
340 GoogleServiceAuthError::State error) {
341 std::vector<PendingRequest*> requests;
342 requests.swap(pending_requests_);
343 for (std::vector<PendingRequest*>::iterator request(requests.begin());
344 request != requests.end();
345 ++request) {
346 scoped_ptr<PendingRequest> scoped_request(*request);
347 if (!scoped_request->request)
348 continue;
349
350 if (token_is_valid) {
351 OAuth2TokenService::FetchOAuth2Token(
352 scoped_request->request.get(),
353 scoped_request->request->GetAccountId(),
354 GetRequestContext(),
355 scoped_request->client_id,
356 scoped_request->client_secret,
357 scoped_request->scopes);
358 } else {
359 FailRequest(scoped_request->request.get(), error);
360 }
361 }
362 }
363
FlushTokenSaveCallbacks(bool result)364 void DeviceOAuth2TokenService::FlushTokenSaveCallbacks(bool result) {
365 std::vector<StatusCallback> callbacks;
366 callbacks.swap(token_save_callbacks_);
367 for (std::vector<StatusCallback>::iterator callback(callbacks.begin());
368 callback != callbacks.end();
369 ++callback) {
370 if (!callback->is_null())
371 callback->Run(result);
372 }
373 }
374
FailRequest(RequestImpl * request,GoogleServiceAuthError::State error)375 void DeviceOAuth2TokenService::FailRequest(
376 RequestImpl* request,
377 GoogleServiceAuthError::State error) {
378 GoogleServiceAuthError auth_error(error);
379 base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
380 &RequestImpl::InformConsumer,
381 request->AsWeakPtr(),
382 auth_error,
383 std::string(),
384 base::Time()));
385 }
386
387 } // namespace chromeos
388