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