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