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 70 IdentityGetAuthTokenFunction::IdentityGetAuthTokenFunction() 71 : should_prompt_for_scopes_(false), 72 should_prompt_for_signin_(false) {} 73 74 IdentityGetAuthTokenFunction::~IdentityGetAuthTokenFunction() {} 75 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 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 147 void IdentityGetAuthTokenFunction::CompleteFunctionWithError( 148 const std::string& error) { 149 error_ = error; 150 SendResponse(false); 151 Release(); // Balanced in RunImpl. 152 } 153 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 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 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 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 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 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 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 321 void IdentityGetAuthTokenFunction::SigninSuccess() { 322 StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE); 323 } 324 325 void IdentityGetAuthTokenFunction::SigninFailed() { 326 CompleteFunctionWithError(identity_constants::kUserNotSignedIn); 327 } 328 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 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 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 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) 400 void IdentityGetAuthTokenFunction::StartDeviceLoginAccessTokenRequest() { 401 chromeos::DeviceOAuth2TokenServiceFactory::Get( 402 base::Bind(&IdentityGetAuthTokenFunction::DidGetTokenService, 403 this)); 404 } 405 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 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 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 453 void IdentityGetAuthTokenFunction::ShowLoginPopup() { 454 signin_flow_.reset(new IdentitySigninFlow(this, GetProfile())); 455 signin_flow_->Start(); 456 } 457 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 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 484 bool IdentityGetAuthTokenFunction::HasLoginToken() const { 485 ProfileOAuth2TokenService* token_service = 486 ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile()); 487 return token_service->RefreshTokenIsAvailable( 488 token_service->GetPrimaryAccountId()); 489 } 490 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 504 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 517 IdentityRemoveCachedAuthTokenFunction::IdentityRemoveCachedAuthTokenFunction() { 518 } 519 520 IdentityRemoveCachedAuthTokenFunction:: 521 ~IdentityRemoveCachedAuthTokenFunction() { 522 } 523 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 539 IdentityLaunchWebAuthFlowFunction::IdentityLaunchWebAuthFlowFunction() {} 540 541 IdentityLaunchWebAuthFlowFunction::~IdentityLaunchWebAuthFlowFunction() { 542 if (auth_flow_) 543 auth_flow_.release()->DetachDelegateAndDelete(); 544 } 545 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 572 void IdentityLaunchWebAuthFlowFunction::InitFinalRedirectURLPrefixForTest( 573 const std::string& extension_id) { 574 InitFinalRedirectURLPrefix(extension_id); 575 } 576 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 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 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 615 IdentityTokenCacheValue::IdentityTokenCacheValue() 616 : status_(CACHE_STATUS_NOTFOUND) { 617 } 618 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 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 641 IdentityTokenCacheValue::~IdentityTokenCacheValue() { 642 } 643 644 IdentityTokenCacheValue::CacheValueStatus 645 IdentityTokenCacheValue::status() const { 646 if (is_expired()) 647 return IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND; 648 else 649 return status_; 650 } 651 652 const IssueAdviceInfo& IdentityTokenCacheValue::issue_advice() const { 653 return issue_advice_; 654 } 655 656 const std::string& IdentityTokenCacheValue::token() const { 657 return token_; 658 } 659 660 bool IdentityTokenCacheValue::is_expired() const { 661 return status_ == CACHE_STATUS_NOTFOUND || 662 expiration_time_ < base::Time::Now(); 663 } 664 665 const base::Time& IdentityTokenCacheValue::expiration_time() const { 666 return expiration_time_; 667 } 668 669 IdentityAPI::IdentityAPI(Profile* profile) 670 : profile_(profile), 671 account_tracker_(profile) { 672 account_tracker_.AddObserver(this); 673 } 674 675 IdentityAPI::~IdentityAPI() {} 676 677 IdentityMintRequestQueue* IdentityAPI::mint_queue() { 678 return &mint_queue_; 679 } 680 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 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 703 void IdentityAPI::EraseAllCachedTokens() { 704 token_cache_.clear(); 705 } 706 707 const IdentityTokenCacheValue& IdentityAPI::GetCachedToken( 708 const ExtensionTokenKey& key) { 709 return token_cache_[key]; 710 } 711 712 const IdentityAPI::CachedTokens& IdentityAPI::GetAllCachedTokens() { 713 return token_cache_; 714 } 715 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 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 731 ProfileKeyedAPIFactory<IdentityAPI>* IdentityAPI::GetFactoryInstance() { 732 return &g_factory.Get(); 733 } 734 735 void IdentityAPI::OnAccountAdded(const AccountIds& ids) {} 736 737 void IdentityAPI::OnAccountRemoved(const AccountIds& ids) {} 738 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 <> 753 void ProfileKeyedAPIFactory<IdentityAPI>::DeclareFactoryDependencies() { 754 DependsOn(ExtensionSystemFactory::GetInstance()); 755 DependsOn(ProfileOAuth2TokenServiceFactory::GetInstance()); 756 } 757 758 } // namespace extensions 759