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