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