1 // Copyright (c) 2012 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/extensions/api/identity/identity_api.h"
6
7 #include <set>
8 #include <string>
9 #include <utility>
10 #include <vector>
11
12 #include "base/lazy_instance.h"
13 #include "base/prefs/pref_service.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/values.h"
17 #include "chrome/browser/app_mode/app_mode_utils.h"
18 #include "chrome/browser/browser_process.h"
19 #include "chrome/browser/chrome_notification_types.h"
20 #include "chrome/browser/extensions/extension_function_dispatcher.h"
21 #include "chrome/browser/extensions/extension_service.h"
22 #include "chrome/browser/extensions/extension_system.h"
23 #include "chrome/browser/policy/browser_policy_connector.h"
24 #include "chrome/browser/profiles/profile.h"
25 #include "chrome/browser/signin/profile_oauth2_token_service.h"
26 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
27 #include "chrome/browser/signin/signin_global_error.h"
28 #include "chrome/common/extensions/api/identity.h"
29 #include "chrome/common/extensions/api/identity/oauth2_manifest_handler.h"
30 #include "chrome/common/pref_names.h"
31 #include "chrome/common/url_constants.h"
32 #include "extensions/browser/event_router.h"
33 #include "extensions/common/extension.h"
34 #include "google_apis/gaia/gaia_urls.h"
35 #include "url/gurl.h"
36
37 #if defined(OS_CHROMEOS)
38 #include "chrome/browser/chromeos/login/user_manager.h"
39 #include "chrome/browser/chromeos/settings/device_oauth2_token_service.h"
40 #include "chrome/browser/chromeos/settings/device_oauth2_token_service_factory.h"
41 #include "google_apis/gaia/gaia_constants.h"
42 #endif
43
44 namespace extensions {
45
46 namespace identity_constants {
47 const char kInvalidClientId[] = "Invalid OAuth2 Client ID.";
48 const char kInvalidScopes[] = "Invalid OAuth2 scopes.";
49 const char kAuthFailure[] = "OAuth2 request failed: ";
50 const char kNoGrant[] = "OAuth2 not granted or revoked.";
51 const char kUserRejected[] = "The user did not approve access.";
52 const char kUserNotSignedIn[] = "The user is not signed in.";
53 const char kInteractionRequired[] = "User interaction required.";
54 const char kInvalidRedirect[] = "Did not redirect to the right URL.";
55 const char kOffTheRecord[] = "Identity API is disabled in incognito windows.";
56 const char kPageLoadFailure[] = "Authorization page could not be loaded.";
57
58 const int kCachedIssueAdviceTTLSeconds = 1;
59 } // namespace identity_constants
60
61 namespace {
62
63 static const char kChromiumDomainRedirectUrlPattern[] =
64 "https://%s.chromiumapp.org/";
65
66 } // namespace
67
68 namespace identity = api::identity;
69
IdentityGetAuthTokenFunction()70 IdentityGetAuthTokenFunction::IdentityGetAuthTokenFunction()
71 : should_prompt_for_scopes_(false),
72 should_prompt_for_signin_(false) {}
73
~IdentityGetAuthTokenFunction()74 IdentityGetAuthTokenFunction::~IdentityGetAuthTokenFunction() {}
75
RunImpl()76 bool IdentityGetAuthTokenFunction::RunImpl() {
77 if (GetProfile()->IsOffTheRecord()) {
78 error_ = identity_constants::kOffTheRecord;
79 return false;
80 }
81
82 scoped_ptr<identity::GetAuthToken::Params> params(
83 identity::GetAuthToken::Params::Create(*args_));
84 EXTENSION_FUNCTION_VALIDATE(params.get());
85 bool interactive = params->details.get() &&
86 params->details->interactive.get() &&
87 *params->details->interactive;
88
89 should_prompt_for_scopes_ = interactive;
90 should_prompt_for_signin_ = interactive;
91
92 const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
93
94 // Check that the necessary information is present in the manifest.
95 oauth2_client_id_ = GetOAuth2ClientId();
96 if (oauth2_client_id_.empty()) {
97 error_ = identity_constants::kInvalidClientId;
98 return false;
99 }
100
101 if (oauth2_info.scopes.size() == 0) {
102 error_ = identity_constants::kInvalidScopes;
103 return false;
104 }
105
106 ProfileOAuth2TokenService* token_service =
107 ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile());
108
109 std::set<std::string> scopes(oauth2_info.scopes.begin(),
110 oauth2_info.scopes.end());
111 token_key_.reset(new ExtensionTokenKey(
112 GetExtension()->id(), token_service->GetPrimaryAccountId(), scopes));
113
114 // Balanced in CompleteFunctionWithResult|CompleteFunctionWithError
115 AddRef();
116
117 #if defined(OS_CHROMEOS)
118 if (chromeos::UserManager::Get()->IsLoggedInAsKioskApp() &&
119 g_browser_process->browser_policy_connector()->IsEnterpriseManaged()) {
120 StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE);
121 return true;
122 }
123 #endif
124
125 if (!HasLoginToken()) {
126 if (!should_prompt_for_signin_) {
127 error_ = identity_constants::kUserNotSignedIn;
128 Release();
129 return false;
130 }
131 // Display a login prompt.
132 StartSigninFlow();
133 } else {
134 StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE);
135 }
136
137 return true;
138 }
139
CompleteFunctionWithResult(const std::string & access_token)140 void IdentityGetAuthTokenFunction::CompleteFunctionWithResult(
141 const std::string& access_token) {
142 SetResult(new base::StringValue(access_token));
143 SendResponse(true);
144 Release(); // Balanced in RunImpl.
145 }
146
CompleteFunctionWithError(const std::string & error)147 void IdentityGetAuthTokenFunction::CompleteFunctionWithError(
148 const std::string& error) {
149 error_ = error;
150 SendResponse(false);
151 Release(); // Balanced in RunImpl.
152 }
153
StartSigninFlow()154 void IdentityGetAuthTokenFunction::StartSigninFlow() {
155 // All cached tokens are invalid because the user is not signed in.
156 IdentityAPI* id_api =
157 extensions::IdentityAPI::GetFactoryInstance()->GetForProfile(
158 GetProfile());
159 id_api->EraseAllCachedTokens();
160 // Display a login prompt. If the subsequent mint fails, don't display the
161 // login prompt again.
162 should_prompt_for_signin_ = false;
163 ShowLoginPopup();
164 }
165
StartMintTokenFlow(IdentityMintRequestQueue::MintType type)166 void IdentityGetAuthTokenFunction::StartMintTokenFlow(
167 IdentityMintRequestQueue::MintType type) {
168 mint_token_flow_type_ = type;
169
170 // Flows are serialized to prevent excessive traffic to GAIA, and
171 // to consolidate UI pop-ups.
172 IdentityAPI* id_api =
173 extensions::IdentityAPI::GetFactoryInstance()->GetForProfile(
174 GetProfile());
175
176 if (!should_prompt_for_scopes_) {
177 // Caller requested no interaction.
178
179 if (type == IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE) {
180 // GAIA told us to do a consent UI.
181 CompleteFunctionWithError(identity_constants::kNoGrant);
182 return;
183 }
184 if (!id_api->mint_queue()->empty(
185 IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE, *token_key_)) {
186 // Another call is going through a consent UI.
187 CompleteFunctionWithError(identity_constants::kNoGrant);
188 return;
189 }
190 }
191 id_api->mint_queue()->RequestStart(type, *token_key_, this);
192 }
193
CompleteMintTokenFlow()194 void IdentityGetAuthTokenFunction::CompleteMintTokenFlow() {
195 IdentityMintRequestQueue::MintType type = mint_token_flow_type_;
196
197 const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
198 std::set<std::string> scopes(oauth2_info.scopes.begin(),
199 oauth2_info.scopes.end());
200
201 extensions::IdentityAPI::GetFactoryInstance()
202 ->GetForProfile(GetProfile())
203 ->mint_queue()
204 ->RequestComplete(type, *token_key_, this);
205 }
206
StartMintToken(IdentityMintRequestQueue::MintType type)207 void IdentityGetAuthTokenFunction::StartMintToken(
208 IdentityMintRequestQueue::MintType type) {
209 const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
210 IdentityAPI* id_api =
211 IdentityAPI::GetFactoryInstance()->GetForProfile(GetProfile());
212 IdentityTokenCacheValue cache_entry = id_api->GetCachedToken(*token_key_);
213 IdentityTokenCacheValue::CacheValueStatus cache_status =
214 cache_entry.status();
215
216 if (type == IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE) {
217 switch (cache_status) {
218 case IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND:
219 #if defined(OS_CHROMEOS)
220 // Always force minting token for ChromeOS kiosk app.
221 if (chromeos::UserManager::Get()->IsLoggedInAsKioskApp()) {
222 gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE;
223 if (g_browser_process->browser_policy_connector()->
224 IsEnterpriseManaged()) {
225 StartDeviceLoginAccessTokenRequest();
226 } else {
227 StartLoginAccessTokenRequest();
228 }
229 return;
230 }
231 #endif
232
233 if (oauth2_info.auto_approve)
234 // oauth2_info.auto_approve is protected by a whitelist in
235 // _manifest_features.json hence only selected extensions take
236 // advantage of forcefully minting the token.
237 gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE;
238 else
239 gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE;
240 StartLoginAccessTokenRequest();
241 break;
242
243 case IdentityTokenCacheValue::CACHE_STATUS_TOKEN:
244 CompleteMintTokenFlow();
245 CompleteFunctionWithResult(cache_entry.token());
246 break;
247
248 case IdentityTokenCacheValue::CACHE_STATUS_ADVICE:
249 CompleteMintTokenFlow();
250 should_prompt_for_signin_ = false;
251 issue_advice_ = cache_entry.issue_advice();
252 StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
253 break;
254 }
255 } else {
256 DCHECK(type == IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
257
258 if (cache_status == IdentityTokenCacheValue::CACHE_STATUS_TOKEN) {
259 CompleteMintTokenFlow();
260 CompleteFunctionWithResult(cache_entry.token());
261 } else {
262 ShowOAuthApprovalDialog(issue_advice_);
263 }
264 }
265 }
266
OnMintTokenSuccess(const std::string & access_token,int time_to_live)267 void IdentityGetAuthTokenFunction::OnMintTokenSuccess(
268 const std::string& access_token, int time_to_live) {
269 IdentityTokenCacheValue token(access_token,
270 base::TimeDelta::FromSeconds(time_to_live));
271 IdentityAPI::GetFactoryInstance()
272 ->GetForProfile(GetProfile())
273 ->SetCachedToken(*token_key_, token);
274
275 CompleteMintTokenFlow();
276 CompleteFunctionWithResult(access_token);
277 }
278
OnMintTokenFailure(const GoogleServiceAuthError & error)279 void IdentityGetAuthTokenFunction::OnMintTokenFailure(
280 const GoogleServiceAuthError& error) {
281 CompleteMintTokenFlow();
282
283 switch (error.state()) {
284 case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS:
285 case GoogleServiceAuthError::SERVICE_ERROR:
286 case GoogleServiceAuthError::ACCOUNT_DELETED:
287 case GoogleServiceAuthError::ACCOUNT_DISABLED:
288 extensions::IdentityAPI::GetFactoryInstance()
289 ->GetForProfile(GetProfile())
290 ->ReportAuthError(error);
291 if (should_prompt_for_signin_) {
292 // Display a login prompt and try again (once).
293 StartSigninFlow();
294 return;
295 }
296 break;
297 default:
298 // Return error to caller.
299 break;
300 }
301
302 CompleteFunctionWithError(
303 std::string(identity_constants::kAuthFailure) + error.ToString());
304 }
305
OnIssueAdviceSuccess(const IssueAdviceInfo & issue_advice)306 void IdentityGetAuthTokenFunction::OnIssueAdviceSuccess(
307 const IssueAdviceInfo& issue_advice) {
308 IdentityAPI::GetFactoryInstance()
309 ->GetForProfile(GetProfile())
310 ->SetCachedToken(*token_key_,
311 IdentityTokenCacheValue(issue_advice));
312 CompleteMintTokenFlow();
313
314 should_prompt_for_signin_ = false;
315 // Existing grant was revoked and we used NO_FORCE, so we got info back
316 // instead. Start a consent UI if we can.
317 issue_advice_ = issue_advice;
318 StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE);
319 }
320
SigninSuccess()321 void IdentityGetAuthTokenFunction::SigninSuccess() {
322 StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE);
323 }
324
SigninFailed()325 void IdentityGetAuthTokenFunction::SigninFailed() {
326 CompleteFunctionWithError(identity_constants::kUserNotSignedIn);
327 }
328
OnGaiaFlowFailure(GaiaWebAuthFlow::Failure failure,GoogleServiceAuthError service_error,const std::string & oauth_error)329 void IdentityGetAuthTokenFunction::OnGaiaFlowFailure(
330 GaiaWebAuthFlow::Failure failure,
331 GoogleServiceAuthError service_error,
332 const std::string& oauth_error) {
333 CompleteMintTokenFlow();
334 std::string error;
335
336 switch (failure) {
337 case GaiaWebAuthFlow::WINDOW_CLOSED:
338 error = identity_constants::kUserRejected;
339 break;
340
341 case GaiaWebAuthFlow::INVALID_REDIRECT:
342 error = identity_constants::kInvalidRedirect;
343 break;
344
345 case GaiaWebAuthFlow::SERVICE_AUTH_ERROR:
346 error = std::string(identity_constants::kAuthFailure) +
347 service_error.ToString();
348 break;
349
350 case GaiaWebAuthFlow::OAUTH_ERROR:
351 error = MapOAuth2ErrorToDescription(oauth_error);
352 break;
353
354 case GaiaWebAuthFlow::LOAD_FAILED:
355 error = identity_constants::kPageLoadFailure;
356 break;
357
358 default:
359 NOTREACHED() << "Unexpected error from gaia web auth flow: " << failure;
360 error = identity_constants::kInvalidRedirect;
361 break;
362 }
363
364 CompleteFunctionWithError(error);
365 }
366
OnGaiaFlowCompleted(const std::string & access_token,const std::string & expiration)367 void IdentityGetAuthTokenFunction::OnGaiaFlowCompleted(
368 const std::string& access_token,
369 const std::string& expiration) {
370
371 int time_to_live;
372 if (!expiration.empty() && base::StringToInt(expiration, &time_to_live)) {
373 IdentityTokenCacheValue token_value(
374 access_token, base::TimeDelta::FromSeconds(time_to_live));
375 IdentityAPI::GetFactoryInstance()
376 ->GetForProfile(GetProfile())
377 ->SetCachedToken(*token_key_, token_value);
378 }
379
380 CompleteMintTokenFlow();
381 CompleteFunctionWithResult(access_token);
382 }
383
OnGetTokenSuccess(const OAuth2TokenService::Request * request,const std::string & access_token,const base::Time & expiration_time)384 void IdentityGetAuthTokenFunction::OnGetTokenSuccess(
385 const OAuth2TokenService::Request* request,
386 const std::string& access_token,
387 const base::Time& expiration_time) {
388 login_token_request_.reset();
389 StartGaiaRequest(access_token);
390 }
391
OnGetTokenFailure(const OAuth2TokenService::Request * request,const GoogleServiceAuthError & error)392 void IdentityGetAuthTokenFunction::OnGetTokenFailure(
393 const OAuth2TokenService::Request* request,
394 const GoogleServiceAuthError& error) {
395 login_token_request_.reset();
396 OnGaiaFlowFailure(GaiaWebAuthFlow::SERVICE_AUTH_ERROR, error, std::string());
397 }
398
399 #if defined(OS_CHROMEOS)
StartDeviceLoginAccessTokenRequest()400 void IdentityGetAuthTokenFunction::StartDeviceLoginAccessTokenRequest() {
401 chromeos::DeviceOAuth2TokenServiceFactory::Get(
402 base::Bind(&IdentityGetAuthTokenFunction::DidGetTokenService,
403 this));
404 }
405
DidGetTokenService(chromeos::DeviceOAuth2TokenService * service)406 void IdentityGetAuthTokenFunction::DidGetTokenService(
407 chromeos::DeviceOAuth2TokenService* service) {
408 if (!service) {
409 CompleteFunctionWithError(identity_constants::kUserNotSignedIn);
410 return;
411 }
412 // Since robot account refresh tokens are scoped down to [any-api] only,
413 // request access token for [any-api] instead of login.
414 OAuth2TokenService::ScopeSet scopes;
415 scopes.insert(GaiaConstants::kAnyApiOAuth2Scope);
416 login_token_request_ =
417 service->StartRequest(service->GetRobotAccountId(),
418 scopes,
419 this);
420 }
421 #endif
422
StartLoginAccessTokenRequest()423 void IdentityGetAuthTokenFunction::StartLoginAccessTokenRequest() {
424 ProfileOAuth2TokenService* service =
425 ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile());
426 #if defined(OS_CHROMEOS)
427 if (chrome::IsRunningInForcedAppMode()) {
428 std::string app_client_id;
429 std::string app_client_secret;
430 if (chromeos::UserManager::Get()->GetAppModeChromeClientOAuthInfo(
431 &app_client_id, &app_client_secret)) {
432 login_token_request_ =
433 service->StartRequestForClient(service->GetPrimaryAccountId(),
434 app_client_id,
435 app_client_secret,
436 OAuth2TokenService::ScopeSet(),
437 this);
438 return;
439 }
440 }
441 #endif
442 login_token_request_ = service->StartRequest(
443 service->GetPrimaryAccountId(), OAuth2TokenService::ScopeSet(), this);
444 }
445
StartGaiaRequest(const std::string & login_access_token)446 void IdentityGetAuthTokenFunction::StartGaiaRequest(
447 const std::string& login_access_token) {
448 DCHECK(!login_access_token.empty());
449 mint_token_flow_.reset(CreateMintTokenFlow(login_access_token));
450 mint_token_flow_->Start();
451 }
452
ShowLoginPopup()453 void IdentityGetAuthTokenFunction::ShowLoginPopup() {
454 signin_flow_.reset(new IdentitySigninFlow(this, GetProfile()));
455 signin_flow_->Start();
456 }
457
ShowOAuthApprovalDialog(const IssueAdviceInfo & issue_advice)458 void IdentityGetAuthTokenFunction::ShowOAuthApprovalDialog(
459 const IssueAdviceInfo& issue_advice) {
460 const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
461 const std::string locale = g_browser_process->local_state()->GetString(
462 prefs::kApplicationLocale);
463
464 gaia_web_auth_flow_.reset(new GaiaWebAuthFlow(
465 this, GetProfile(), GetExtension()->id(), oauth2_info, locale));
466 gaia_web_auth_flow_->Start();
467 }
468
CreateMintTokenFlow(const std::string & login_access_token)469 OAuth2MintTokenFlow* IdentityGetAuthTokenFunction::CreateMintTokenFlow(
470 const std::string& login_access_token) {
471 const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
472
473 OAuth2MintTokenFlow* mint_token_flow = new OAuth2MintTokenFlow(
474 GetProfile()->GetRequestContext(),
475 this,
476 OAuth2MintTokenFlow::Parameters(login_access_token,
477 GetExtension()->id(),
478 oauth2_client_id_,
479 oauth2_info.scopes,
480 gaia_mint_token_mode_));
481 return mint_token_flow;
482 }
483
HasLoginToken() const484 bool IdentityGetAuthTokenFunction::HasLoginToken() const {
485 ProfileOAuth2TokenService* token_service =
486 ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile());
487 return token_service->RefreshTokenIsAvailable(
488 token_service->GetPrimaryAccountId());
489 }
490
MapOAuth2ErrorToDescription(const std::string & error)491 std::string IdentityGetAuthTokenFunction::MapOAuth2ErrorToDescription(
492 const std::string& error) {
493 const char kOAuth2ErrorAccessDenied[] = "access_denied";
494 const char kOAuth2ErrorInvalidScope[] = "invalid_scope";
495
496 if (error == kOAuth2ErrorAccessDenied)
497 return std::string(identity_constants::kUserRejected);
498 else if (error == kOAuth2ErrorInvalidScope)
499 return std::string(identity_constants::kInvalidScopes);
500 else
501 return std::string(identity_constants::kAuthFailure) + error;
502 }
503
GetOAuth2ClientId() const504 std::string IdentityGetAuthTokenFunction::GetOAuth2ClientId() const {
505 const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
506 std::string client_id = oauth2_info.client_id;
507
508 // Component apps using auto_approve may use Chrome's client ID by
509 // omitting the field.
510 if (client_id.empty() && GetExtension()->location() == Manifest::COMPONENT &&
511 oauth2_info.auto_approve) {
512 client_id = GaiaUrls::GetInstance()->oauth2_chrome_client_id();
513 }
514 return client_id;
515 }
516
IdentityRemoveCachedAuthTokenFunction()517 IdentityRemoveCachedAuthTokenFunction::IdentityRemoveCachedAuthTokenFunction() {
518 }
519
520 IdentityRemoveCachedAuthTokenFunction::
~IdentityRemoveCachedAuthTokenFunction()521 ~IdentityRemoveCachedAuthTokenFunction() {
522 }
523
RunImpl()524 bool IdentityRemoveCachedAuthTokenFunction::RunImpl() {
525 if (GetProfile()->IsOffTheRecord()) {
526 error_ = identity_constants::kOffTheRecord;
527 return false;
528 }
529
530 scoped_ptr<identity::RemoveCachedAuthToken::Params> params(
531 identity::RemoveCachedAuthToken::Params::Create(*args_));
532 EXTENSION_FUNCTION_VALIDATE(params.get());
533 IdentityAPI::GetFactoryInstance()
534 ->GetForProfile(GetProfile())
535 ->EraseCachedToken(GetExtension()->id(), params->details.token);
536 return true;
537 }
538
IdentityLaunchWebAuthFlowFunction()539 IdentityLaunchWebAuthFlowFunction::IdentityLaunchWebAuthFlowFunction() {}
540
~IdentityLaunchWebAuthFlowFunction()541 IdentityLaunchWebAuthFlowFunction::~IdentityLaunchWebAuthFlowFunction() {
542 if (auth_flow_)
543 auth_flow_.release()->DetachDelegateAndDelete();
544 }
545
RunImpl()546 bool IdentityLaunchWebAuthFlowFunction::RunImpl() {
547 if (GetProfile()->IsOffTheRecord()) {
548 error_ = identity_constants::kOffTheRecord;
549 return false;
550 }
551
552 scoped_ptr<identity::LaunchWebAuthFlow::Params> params(
553 identity::LaunchWebAuthFlow::Params::Create(*args_));
554 EXTENSION_FUNCTION_VALIDATE(params.get());
555
556 GURL auth_url(params->details.url);
557 WebAuthFlow::Mode mode =
558 params->details.interactive && *params->details.interactive ?
559 WebAuthFlow::INTERACTIVE : WebAuthFlow::SILENT;
560
561 // Set up acceptable target URLs. (Does not include chrome-extension
562 // scheme for this version of the API.)
563 InitFinalRedirectURLPrefix(GetExtension()->id());
564
565 AddRef(); // Balanced in OnAuthFlowSuccess/Failure.
566
567 auth_flow_.reset(new WebAuthFlow(this, GetProfile(), auth_url, mode));
568 auth_flow_->Start();
569 return true;
570 }
571
InitFinalRedirectURLPrefixForTest(const std::string & extension_id)572 void IdentityLaunchWebAuthFlowFunction::InitFinalRedirectURLPrefixForTest(
573 const std::string& extension_id) {
574 InitFinalRedirectURLPrefix(extension_id);
575 }
576
InitFinalRedirectURLPrefix(const std::string & extension_id)577 void IdentityLaunchWebAuthFlowFunction::InitFinalRedirectURLPrefix(
578 const std::string& extension_id) {
579 if (final_url_prefix_.is_empty()) {
580 final_url_prefix_ = GURL(base::StringPrintf(
581 kChromiumDomainRedirectUrlPattern, extension_id.c_str()));
582 }
583 }
584
OnAuthFlowFailure(WebAuthFlow::Failure failure)585 void IdentityLaunchWebAuthFlowFunction::OnAuthFlowFailure(
586 WebAuthFlow::Failure failure) {
587 switch (failure) {
588 case WebAuthFlow::WINDOW_CLOSED:
589 error_ = identity_constants::kUserRejected;
590 break;
591 case WebAuthFlow::INTERACTION_REQUIRED:
592 error_ = identity_constants::kInteractionRequired;
593 break;
594 case WebAuthFlow::LOAD_FAILED:
595 error_ = identity_constants::kPageLoadFailure;
596 break;
597 default:
598 NOTREACHED() << "Unexpected error from web auth flow: " << failure;
599 error_ = identity_constants::kInvalidRedirect;
600 break;
601 }
602 SendResponse(false);
603 Release(); // Balanced in RunImpl.
604 }
605
OnAuthFlowURLChange(const GURL & redirect_url)606 void IdentityLaunchWebAuthFlowFunction::OnAuthFlowURLChange(
607 const GURL& redirect_url) {
608 if (redirect_url.GetWithEmptyPath() == final_url_prefix_) {
609 SetResult(new base::StringValue(redirect_url.spec()));
610 SendResponse(true);
611 Release(); // Balanced in RunImpl.
612 }
613 }
614
IdentityTokenCacheValue()615 IdentityTokenCacheValue::IdentityTokenCacheValue()
616 : status_(CACHE_STATUS_NOTFOUND) {
617 }
618
IdentityTokenCacheValue(const IssueAdviceInfo & issue_advice)619 IdentityTokenCacheValue::IdentityTokenCacheValue(
620 const IssueAdviceInfo& issue_advice) : status_(CACHE_STATUS_ADVICE),
621 issue_advice_(issue_advice) {
622 expiration_time_ = base::Time::Now() + base::TimeDelta::FromSeconds(
623 identity_constants::kCachedIssueAdviceTTLSeconds);
624 }
625
IdentityTokenCacheValue(const std::string & token,base::TimeDelta time_to_live)626 IdentityTokenCacheValue::IdentityTokenCacheValue(
627 const std::string& token, base::TimeDelta time_to_live)
628 : status_(CACHE_STATUS_TOKEN),
629 token_(token) {
630 // Remove 20 minutes from the ttl so cached tokens will have some time
631 // to live any time they are returned.
632 time_to_live -= base::TimeDelta::FromMinutes(20);
633
634 base::TimeDelta zero_delta;
635 if (time_to_live < zero_delta)
636 time_to_live = zero_delta;
637
638 expiration_time_ = base::Time::Now() + time_to_live;
639 }
640
~IdentityTokenCacheValue()641 IdentityTokenCacheValue::~IdentityTokenCacheValue() {
642 }
643
644 IdentityTokenCacheValue::CacheValueStatus
status() const645 IdentityTokenCacheValue::status() const {
646 if (is_expired())
647 return IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND;
648 else
649 return status_;
650 }
651
issue_advice() const652 const IssueAdviceInfo& IdentityTokenCacheValue::issue_advice() const {
653 return issue_advice_;
654 }
655
token() const656 const std::string& IdentityTokenCacheValue::token() const {
657 return token_;
658 }
659
is_expired() const660 bool IdentityTokenCacheValue::is_expired() const {
661 return status_ == CACHE_STATUS_NOTFOUND ||
662 expiration_time_ < base::Time::Now();
663 }
664
expiration_time() const665 const base::Time& IdentityTokenCacheValue::expiration_time() const {
666 return expiration_time_;
667 }
668
IdentityAPI(Profile * profile)669 IdentityAPI::IdentityAPI(Profile* profile)
670 : profile_(profile),
671 account_tracker_(profile) {
672 account_tracker_.AddObserver(this);
673 }
674
~IdentityAPI()675 IdentityAPI::~IdentityAPI() {}
676
mint_queue()677 IdentityMintRequestQueue* IdentityAPI::mint_queue() {
678 return &mint_queue_;
679 }
680
SetCachedToken(const ExtensionTokenKey & key,const IdentityTokenCacheValue & token_data)681 void IdentityAPI::SetCachedToken(const ExtensionTokenKey& key,
682 const IdentityTokenCacheValue& token_data) {
683 CachedTokens::iterator it = token_cache_.find(key);
684 if (it != token_cache_.end() && it->second.status() <= token_data.status())
685 token_cache_.erase(it);
686
687 token_cache_.insert(std::make_pair(key, token_data));
688 }
689
EraseCachedToken(const std::string & extension_id,const std::string & token)690 void IdentityAPI::EraseCachedToken(const std::string& extension_id,
691 const std::string& token) {
692 CachedTokens::iterator it;
693 for (it = token_cache_.begin(); it != token_cache_.end(); ++it) {
694 if (it->first.extension_id == extension_id &&
695 it->second.status() == IdentityTokenCacheValue::CACHE_STATUS_TOKEN &&
696 it->second.token() == token) {
697 token_cache_.erase(it);
698 break;
699 }
700 }
701 }
702
EraseAllCachedTokens()703 void IdentityAPI::EraseAllCachedTokens() {
704 token_cache_.clear();
705 }
706
GetCachedToken(const ExtensionTokenKey & key)707 const IdentityTokenCacheValue& IdentityAPI::GetCachedToken(
708 const ExtensionTokenKey& key) {
709 return token_cache_[key];
710 }
711
GetAllCachedTokens()712 const IdentityAPI::CachedTokens& IdentityAPI::GetAllCachedTokens() {
713 return token_cache_;
714 }
715
ReportAuthError(const GoogleServiceAuthError & error)716 void IdentityAPI::ReportAuthError(const GoogleServiceAuthError& error) {
717 ProfileOAuth2TokenService* token_service =
718 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
719 account_tracker_.ReportAuthError(token_service->GetPrimaryAccountId(), error);
720 }
721
Shutdown()722 void IdentityAPI::Shutdown() {
723 account_tracker_.RemoveObserver(this);
724 account_tracker_.Shutdown();
725 }
726
727 static base::LazyInstance<ProfileKeyedAPIFactory<IdentityAPI> >
728 g_factory = LAZY_INSTANCE_INITIALIZER;
729
730 // static
GetFactoryInstance()731 ProfileKeyedAPIFactory<IdentityAPI>* IdentityAPI::GetFactoryInstance() {
732 return &g_factory.Get();
733 }
734
OnAccountAdded(const AccountIds & ids)735 void IdentityAPI::OnAccountAdded(const AccountIds& ids) {}
736
OnAccountRemoved(const AccountIds & ids)737 void IdentityAPI::OnAccountRemoved(const AccountIds& ids) {}
738
OnAccountSignInChanged(const AccountIds & ids,bool is_signed_in)739 void IdentityAPI::OnAccountSignInChanged(const AccountIds& ids,
740 bool is_signed_in) {
741 api::identity::AccountInfo account_info;
742 account_info.id = ids.gaia;
743
744 scoped_ptr<base::ListValue> args =
745 api::identity::OnSignInChanged::Create(account_info, is_signed_in);
746 scoped_ptr<Event> event(new Event(
747 api::identity::OnSignInChanged::kEventName, args.Pass(), profile_));
748
749 ExtensionSystem::Get(profile_)->event_router()->BroadcastEvent(event.Pass());
750 }
751
752 template <>
DeclareFactoryDependencies()753 void ProfileKeyedAPIFactory<IdentityAPI>::DeclareFactoryDependencies() {
754 DependsOn(ExtensionSystemFactory::GetInstance());
755 DependsOn(ProfileOAuth2TokenServiceFactory::GetInstance());
756 }
757
758 } // namespace extensions
759