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