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