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