identity_api.cc revision 23730a6e56a168d1879203e4b3819bb36e3d8f1f
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_service.h" 21#include "chrome/browser/profiles/profile.h" 22#include "chrome/browser/signin/profile_oauth2_token_service_factory.h" 23#include "chrome/browser/signin/signin_global_error.h" 24#include "chrome/browser/signin/signin_manager.h" 25#include "chrome/browser/signin/signin_manager_factory.h" 26#include "chrome/common/extensions/api/identity.h" 27#include "chrome/common/extensions/api/identity/oauth2_manifest_handler.h" 28#include "chrome/common/pref_names.h" 29#include "chrome/common/url_constants.h" 30#include "components/signin/core/profile_oauth2_token_service.h" 31#include "extensions/browser/event_router.h" 32#include "extensions/browser/extension_function_dispatcher.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(content::BrowserContext* context) { 69 SigninManagerBase* signin_manager = 70 SigninManagerFactory::GetForProfile(Profile::FromBrowserContext(context)); 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()->Get(GetProfile()); 166 id_api->EraseAllCachedTokens(); 167 // Display a login prompt. If the subsequent mint fails, don't display the 168 // login prompt again. 169 should_prompt_for_signin_ = false; 170 ShowLoginPopup(); 171} 172 173void IdentityGetAuthTokenFunction::StartMintTokenFlow( 174 IdentityMintRequestQueue::MintType type) { 175 mint_token_flow_type_ = type; 176 177 // Flows are serialized to prevent excessive traffic to GAIA, and 178 // to consolidate UI pop-ups. 179 IdentityAPI* id_api = 180 extensions::IdentityAPI::GetFactoryInstance()->Get(GetProfile()); 181 182 if (!should_prompt_for_scopes_) { 183 // Caller requested no interaction. 184 185 if (type == IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE) { 186 // GAIA told us to do a consent UI. 187 CompleteFunctionWithError(identity_constants::kNoGrant); 188 return; 189 } 190 if (!id_api->mint_queue()->empty( 191 IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE, *token_key_)) { 192 // Another call is going through a consent UI. 193 CompleteFunctionWithError(identity_constants::kNoGrant); 194 return; 195 } 196 } 197 id_api->mint_queue()->RequestStart(type, *token_key_, this); 198} 199 200void IdentityGetAuthTokenFunction::CompleteMintTokenFlow() { 201 IdentityMintRequestQueue::MintType type = mint_token_flow_type_; 202 203 const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension()); 204 std::set<std::string> scopes(oauth2_info.scopes.begin(), 205 oauth2_info.scopes.end()); 206 207 extensions::IdentityAPI::GetFactoryInstance() 208 ->Get(GetProfile()) 209 ->mint_queue() 210 ->RequestComplete(type, *token_key_, this); 211} 212 213void IdentityGetAuthTokenFunction::StartMintToken( 214 IdentityMintRequestQueue::MintType type) { 215 const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension()); 216 IdentityAPI* id_api = IdentityAPI::GetFactoryInstance()->Get(GetProfile()); 217 IdentityTokenCacheValue cache_entry = id_api->GetCachedToken(*token_key_); 218 IdentityTokenCacheValue::CacheValueStatus cache_status = 219 cache_entry.status(); 220 221 if (type == IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE) { 222 switch (cache_status) { 223 case IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND: 224#if defined(OS_CHROMEOS) 225 // Always force minting token for ChromeOS kiosk app. 226 if (chromeos::UserManager::Get()->IsLoggedInAsKioskApp()) { 227 gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE; 228 policy::BrowserPolicyConnectorChromeOS* connector = 229 g_browser_process->platform_part() 230 ->browser_policy_connector_chromeos(); 231 if (connector->IsEnterpriseManaged()) { 232 StartDeviceLoginAccessTokenRequest(); 233 } else { 234 StartLoginAccessTokenRequest(); 235 } 236 return; 237 } 238#endif 239 240 if (oauth2_info.auto_approve) 241 // oauth2_info.auto_approve is protected by a whitelist in 242 // _manifest_features.json hence only selected extensions take 243 // advantage of forcefully minting the token. 244 gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE; 245 else 246 gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE; 247 StartLoginAccessTokenRequest(); 248 break; 249 250 case IdentityTokenCacheValue::CACHE_STATUS_TOKEN: 251 CompleteMintTokenFlow(); 252 CompleteFunctionWithResult(cache_entry.token()); 253 break; 254 255 case IdentityTokenCacheValue::CACHE_STATUS_ADVICE: 256 CompleteMintTokenFlow(); 257 should_prompt_for_signin_ = false; 258 issue_advice_ = cache_entry.issue_advice(); 259 StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE); 260 break; 261 } 262 } else { 263 DCHECK(type == IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE); 264 265 if (cache_status == IdentityTokenCacheValue::CACHE_STATUS_TOKEN) { 266 CompleteMintTokenFlow(); 267 CompleteFunctionWithResult(cache_entry.token()); 268 } else { 269 ShowOAuthApprovalDialog(issue_advice_); 270 } 271 } 272} 273 274void IdentityGetAuthTokenFunction::OnMintTokenSuccess( 275 const std::string& access_token, int time_to_live) { 276 IdentityTokenCacheValue token(access_token, 277 base::TimeDelta::FromSeconds(time_to_live)); 278 IdentityAPI::GetFactoryInstance()->Get(GetProfile())->SetCachedToken( 279 *token_key_, token); 280 281 CompleteMintTokenFlow(); 282 CompleteFunctionWithResult(access_token); 283} 284 285void IdentityGetAuthTokenFunction::OnMintTokenFailure( 286 const GoogleServiceAuthError& error) { 287 CompleteMintTokenFlow(); 288 289 switch (error.state()) { 290 case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS: 291 case GoogleServiceAuthError::ACCOUNT_DELETED: 292 case GoogleServiceAuthError::ACCOUNT_DISABLED: 293 extensions::IdentityAPI::GetFactoryInstance() 294 ->Get(GetProfile()) 295 ->ReportAuthError(error); 296 if (should_prompt_for_signin_) { 297 // Display a login prompt and try again (once). 298 StartSigninFlow(); 299 return; 300 } 301 break; 302 default: 303 // Return error to caller. 304 break; 305 } 306 307 CompleteFunctionWithError( 308 std::string(identity_constants::kAuthFailure) + error.ToString()); 309} 310 311void IdentityGetAuthTokenFunction::OnIssueAdviceSuccess( 312 const IssueAdviceInfo& issue_advice) { 313 IdentityAPI::GetFactoryInstance()->Get(GetProfile())->SetCachedToken( 314 *token_key_, IdentityTokenCacheValue(issue_advice)); 315 CompleteMintTokenFlow(); 316 317 should_prompt_for_signin_ = false; 318 // Existing grant was revoked and we used NO_FORCE, so we got info back 319 // instead. Start a consent UI if we can. 320 issue_advice_ = issue_advice; 321 StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE); 322} 323 324void IdentityGetAuthTokenFunction::SigninSuccess() { 325 StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE); 326} 327 328void IdentityGetAuthTokenFunction::SigninFailed() { 329 CompleteFunctionWithError(identity_constants::kUserNotSignedIn); 330} 331 332void IdentityGetAuthTokenFunction::OnGaiaFlowFailure( 333 GaiaWebAuthFlow::Failure failure, 334 GoogleServiceAuthError service_error, 335 const std::string& oauth_error) { 336 CompleteMintTokenFlow(); 337 std::string error; 338 339 switch (failure) { 340 case GaiaWebAuthFlow::WINDOW_CLOSED: 341 error = identity_constants::kUserRejected; 342 break; 343 344 case GaiaWebAuthFlow::INVALID_REDIRECT: 345 error = identity_constants::kInvalidRedirect; 346 break; 347 348 case GaiaWebAuthFlow::SERVICE_AUTH_ERROR: 349 error = std::string(identity_constants::kAuthFailure) + 350 service_error.ToString(); 351 break; 352 353 case GaiaWebAuthFlow::OAUTH_ERROR: 354 error = MapOAuth2ErrorToDescription(oauth_error); 355 break; 356 357 case GaiaWebAuthFlow::LOAD_FAILED: 358 error = identity_constants::kPageLoadFailure; 359 break; 360 361 default: 362 NOTREACHED() << "Unexpected error from gaia web auth flow: " << failure; 363 error = identity_constants::kInvalidRedirect; 364 break; 365 } 366 367 CompleteFunctionWithError(error); 368} 369 370void IdentityGetAuthTokenFunction::OnGaiaFlowCompleted( 371 const std::string& access_token, 372 const std::string& expiration) { 373 374 int time_to_live; 375 if (!expiration.empty() && base::StringToInt(expiration, &time_to_live)) { 376 IdentityTokenCacheValue token_value( 377 access_token, base::TimeDelta::FromSeconds(time_to_live)); 378 IdentityAPI::GetFactoryInstance()->Get(GetProfile())->SetCachedToken( 379 *token_key_, token_value); 380 } 381 382 CompleteMintTokenFlow(); 383 CompleteFunctionWithResult(access_token); 384} 385 386void IdentityGetAuthTokenFunction::OnGetTokenSuccess( 387 const OAuth2TokenService::Request* request, 388 const std::string& access_token, 389 const base::Time& expiration_time) { 390 login_token_request_.reset(); 391 StartGaiaRequest(access_token); 392} 393 394void IdentityGetAuthTokenFunction::OnGetTokenFailure( 395 const OAuth2TokenService::Request* request, 396 const GoogleServiceAuthError& error) { 397 login_token_request_.reset(); 398 OnGaiaFlowFailure(GaiaWebAuthFlow::SERVICE_AUTH_ERROR, error, std::string()); 399} 400 401#if defined(OS_CHROMEOS) 402void IdentityGetAuthTokenFunction::StartDeviceLoginAccessTokenRequest() { 403 chromeos::DeviceOAuth2TokenService* service = 404 chromeos::DeviceOAuth2TokenServiceFactory::Get(); 405 // Since robot account refresh tokens are scoped down to [any-api] only, 406 // request access token for [any-api] instead of login. 407 OAuth2TokenService::ScopeSet scopes; 408 scopes.insert(GaiaConstants::kAnyApiOAuth2Scope); 409 login_token_request_ = 410 service->StartRequest(service->GetRobotAccountId(), 411 scopes, 412 this); 413} 414#endif 415 416void IdentityGetAuthTokenFunction::StartLoginAccessTokenRequest() { 417 ProfileOAuth2TokenService* service = 418 ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile()); 419 const std::string primary_account_id = GetPrimaryAccountId(GetProfile()); 420#if defined(OS_CHROMEOS) 421 if (chrome::IsRunningInForcedAppMode()) { 422 std::string app_client_id; 423 std::string app_client_secret; 424 if (chromeos::UserManager::Get()->GetAppModeChromeClientOAuthInfo( 425 &app_client_id, &app_client_secret)) { 426 login_token_request_ = 427 service->StartRequestForClient(primary_account_id, 428 app_client_id, 429 app_client_secret, 430 OAuth2TokenService::ScopeSet(), 431 this); 432 return; 433 } 434 } 435#endif 436 login_token_request_ = service->StartRequest( 437 primary_account_id, OAuth2TokenService::ScopeSet(), this); 438} 439 440void 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 447void IdentityGetAuthTokenFunction::ShowLoginPopup() { 448 signin_flow_.reset(new IdentitySigninFlow(this, GetProfile())); 449 signin_flow_->Start(); 450} 451 452void 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, GetProfile(), GetExtension()->id(), oauth2_info, locale)); 460 gaia_web_auth_flow_->Start(); 461} 462 463OAuth2MintTokenFlow* IdentityGetAuthTokenFunction::CreateMintTokenFlow( 464 const std::string& login_access_token) { 465 const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension()); 466 467 OAuth2MintTokenFlow* mint_token_flow = new OAuth2MintTokenFlow( 468 GetProfile()->GetRequestContext(), 469 this, 470 OAuth2MintTokenFlow::Parameters(login_access_token, 471 GetExtension()->id(), 472 oauth2_client_id_, 473 oauth2_info.scopes, 474 gaia_mint_token_mode_)); 475 return mint_token_flow; 476} 477 478bool IdentityGetAuthTokenFunction::HasLoginToken() const { 479 ProfileOAuth2TokenService* token_service = 480 ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile()); 481 return token_service->RefreshTokenIsAvailable( 482 GetPrimaryAccountId(GetProfile())); 483} 484 485std::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 498std::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 511IdentityRemoveCachedAuthTokenFunction::IdentityRemoveCachedAuthTokenFunction() { 512} 513 514IdentityRemoveCachedAuthTokenFunction:: 515 ~IdentityRemoveCachedAuthTokenFunction() { 516} 517 518bool IdentityRemoveCachedAuthTokenFunction::RunImpl() { 519 if (GetProfile()->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()->Get(GetProfile())->EraseCachedToken( 528 GetExtension()->id(), params->details.token); 529 return true; 530} 531 532IdentityLaunchWebAuthFlowFunction::IdentityLaunchWebAuthFlowFunction() {} 533 534IdentityLaunchWebAuthFlowFunction::~IdentityLaunchWebAuthFlowFunction() { 535 if (auth_flow_) 536 auth_flow_.release()->DetachDelegateAndDelete(); 537} 538 539bool IdentityLaunchWebAuthFlowFunction::RunImpl() { 540 if (GetProfile()->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, GetProfile(), auth_url, mode)); 561 auth_flow_->Start(); 562 return true; 563} 564 565void IdentityLaunchWebAuthFlowFunction::InitFinalRedirectURLPrefixForTest( 566 const std::string& extension_id) { 567 InitFinalRedirectURLPrefix(extension_id); 568} 569 570void 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 578void 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 599void IdentityLaunchWebAuthFlowFunction::OnAuthFlowURLChange( 600 const GURL& redirect_url) { 601 if (redirect_url.GetWithEmptyPath() == final_url_prefix_) { 602 SetResult(new base::StringValue(redirect_url.spec())); 603 SendResponse(true); 604 Release(); // Balanced in RunImpl. 605 } 606} 607 608IdentityTokenCacheValue::IdentityTokenCacheValue() 609 : status_(CACHE_STATUS_NOTFOUND) { 610} 611 612IdentityTokenCacheValue::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 619IdentityTokenCacheValue::IdentityTokenCacheValue( 620 const std::string& token, base::TimeDelta time_to_live) 621 : status_(CACHE_STATUS_TOKEN), 622 token_(token) { 623 // Remove 20 minutes from the ttl so cached tokens will have some time 624 // to live any time they are returned. 625 time_to_live -= base::TimeDelta::FromMinutes(20); 626 627 base::TimeDelta zero_delta; 628 if (time_to_live < zero_delta) 629 time_to_live = zero_delta; 630 631 expiration_time_ = base::Time::Now() + time_to_live; 632} 633 634IdentityTokenCacheValue::~IdentityTokenCacheValue() { 635} 636 637IdentityTokenCacheValue::CacheValueStatus 638 IdentityTokenCacheValue::status() const { 639 if (is_expired()) 640 return IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND; 641 else 642 return status_; 643} 644 645const IssueAdviceInfo& IdentityTokenCacheValue::issue_advice() const { 646 return issue_advice_; 647} 648 649const std::string& IdentityTokenCacheValue::token() const { 650 return token_; 651} 652 653bool IdentityTokenCacheValue::is_expired() const { 654 return status_ == CACHE_STATUS_NOTFOUND || 655 expiration_time_ < base::Time::Now(); 656} 657 658const base::Time& IdentityTokenCacheValue::expiration_time() const { 659 return expiration_time_; 660} 661 662IdentityAPI::IdentityAPI(content::BrowserContext* context) 663 : browser_context_(context), 664 account_tracker_(Profile::FromBrowserContext(context)) { 665 account_tracker_.AddObserver(this); 666} 667 668IdentityAPI::~IdentityAPI() {} 669 670IdentityMintRequestQueue* IdentityAPI::mint_queue() { 671 return &mint_queue_; 672} 673 674void IdentityAPI::SetCachedToken(const ExtensionTokenKey& key, 675 const IdentityTokenCacheValue& token_data) { 676 CachedTokens::iterator it = token_cache_.find(key); 677 if (it != token_cache_.end() && it->second.status() <= token_data.status()) 678 token_cache_.erase(it); 679 680 token_cache_.insert(std::make_pair(key, token_data)); 681} 682 683void IdentityAPI::EraseCachedToken(const std::string& extension_id, 684 const std::string& token) { 685 CachedTokens::iterator it; 686 for (it = token_cache_.begin(); it != token_cache_.end(); ++it) { 687 if (it->first.extension_id == extension_id && 688 it->second.status() == IdentityTokenCacheValue::CACHE_STATUS_TOKEN && 689 it->second.token() == token) { 690 token_cache_.erase(it); 691 break; 692 } 693 } 694} 695 696void IdentityAPI::EraseAllCachedTokens() { 697 token_cache_.clear(); 698} 699 700const IdentityTokenCacheValue& IdentityAPI::GetCachedToken( 701 const ExtensionTokenKey& key) { 702 return token_cache_[key]; 703} 704 705const IdentityAPI::CachedTokens& IdentityAPI::GetAllCachedTokens() { 706 return token_cache_; 707} 708 709void IdentityAPI::ReportAuthError(const GoogleServiceAuthError& error) { 710 account_tracker_.ReportAuthError(GetPrimaryAccountId(browser_context_), 711 error); 712} 713 714GoogleServiceAuthError IdentityAPI::GetAuthStatusForTest() const { 715 return account_tracker_.GetAuthStatus(); 716} 717 718void IdentityAPI::Shutdown() { 719 account_tracker_.RemoveObserver(this); 720 account_tracker_.Shutdown(); 721} 722 723static base::LazyInstance<BrowserContextKeyedAPIFactory<IdentityAPI> > 724 g_factory = LAZY_INSTANCE_INITIALIZER; 725 726// static 727BrowserContextKeyedAPIFactory<IdentityAPI>* IdentityAPI::GetFactoryInstance() { 728 return g_factory.Pointer(); 729} 730 731void IdentityAPI::OnAccountAdded(const AccountIds& ids) {} 732 733void IdentityAPI::OnAccountRemoved(const AccountIds& ids) {} 734 735void IdentityAPI::OnAccountSignInChanged(const AccountIds& ids, 736 bool is_signed_in) { 737 api::identity::AccountInfo account_info; 738 account_info.id = ids.gaia; 739 740 scoped_ptr<base::ListValue> args = 741 api::identity::OnSignInChanged::Create(account_info, is_signed_in); 742 scoped_ptr<Event> event(new Event(api::identity::OnSignInChanged::kEventName, 743 args.Pass(), 744 browser_context_)); 745 746 ExtensionSystem::Get(browser_context_)->event_router()->BroadcastEvent( 747 event.Pass()); 748} 749 750template <> 751void BrowserContextKeyedAPIFactory<IdentityAPI>::DeclareFactoryDependencies() { 752 DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory()); 753 DependsOn(ProfileOAuth2TokenServiceFactory::GetInstance()); 754} 755 756} // namespace extensions 757