| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/extensions/api/identity/identity_api.h" | 5 #include "chrome/browser/extensions/api/identity/identity_api.h" |
| 6 | 6 |
| 7 #include <stddef.h> | 7 #include <stddef.h> |
| 8 | 8 |
| 9 #include <memory> | 9 #include <memory> |
| 10 #include <set> | 10 #include <set> |
| (...skipping 29 matching lines...) Expand all Loading... |
| 40 #include "extensions/browser/event_router.h" | 40 #include "extensions/browser/event_router.h" |
| 41 #include "extensions/browser/extension_function_dispatcher.h" | 41 #include "extensions/browser/extension_function_dispatcher.h" |
| 42 #include "extensions/common/extension.h" | 42 #include "extensions/common/extension.h" |
| 43 #include "extensions/common/extension_l10n_util.h" | 43 #include "extensions/common/extension_l10n_util.h" |
| 44 #include "extensions/common/manifest_handlers/oauth2_manifest_handler.h" | 44 #include "extensions/common/manifest_handlers/oauth2_manifest_handler.h" |
| 45 #include "extensions/common/permissions/permission_set.h" | 45 #include "extensions/common/permissions/permission_set.h" |
| 46 #include "extensions/common/permissions/permissions_data.h" | 46 #include "extensions/common/permissions/permissions_data.h" |
| 47 #include "google_apis/gaia/gaia_urls.h" | 47 #include "google_apis/gaia/gaia_urls.h" |
| 48 #include "url/gurl.h" | 48 #include "url/gurl.h" |
| 49 | 49 |
| 50 #if defined(OS_CHROMEOS) | |
| 51 #include "chrome/browser/chromeos/login/session/user_session_manager.h" | |
| 52 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h" | |
| 53 #include "chrome/browser/chromeos/settings/device_oauth2_token_service.h" | |
| 54 #include "chrome/browser/chromeos/settings/device_oauth2_token_service_factory.h
" | |
| 55 #include "components/user_manager/user_manager.h" | |
| 56 #include "google_apis/gaia/gaia_constants.h" | |
| 57 #endif | |
| 58 | |
| 59 namespace extensions { | 50 namespace extensions { |
| 60 | 51 |
| 61 namespace { | |
| 62 | |
| 63 #if defined(OS_CHROMEOS) | |
| 64 // The list of apps that are allowed to use the Identity API to retrieve the | |
| 65 // token from the device robot account in a public session. | |
| 66 const char* const kPublicSessionAllowedOrigins[] = { | |
| 67 // Chrome Remote Desktop - Chromium branding. | |
| 68 "chrome-extension://ljacajndfccfgnfohlgkdphmbnpkjflk/", | |
| 69 // Chrome Remote Desktop - Official branding. | |
| 70 "chrome-extension://gbchcmhmhahfdphkhkmpfmihenigjmpp/"}; | |
| 71 #endif | |
| 72 | |
| 73 std::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 | |
| 81 namespace identity = api::identity; | 52 namespace identity = api::identity; |
| 82 | 53 |
| 83 IdentityTokenCacheValue::IdentityTokenCacheValue() | 54 IdentityTokenCacheValue::IdentityTokenCacheValue() |
| 84 : status_(CACHE_STATUS_NOTFOUND) {} | 55 : status_(CACHE_STATUS_NOTFOUND) {} |
| 85 | 56 |
| 86 IdentityTokenCacheValue::IdentityTokenCacheValue( | 57 IdentityTokenCacheValue::IdentityTokenCacheValue( |
| 87 const IssueAdviceInfo& issue_advice) | 58 const IssueAdviceInfo& issue_advice) |
| 88 : status_(CACHE_STATUS_ADVICE), issue_advice_(issue_advice) { | 59 : status_(CACHE_STATUS_ADVICE), issue_advice_(issue_advice) { |
| 89 expiration_time_ = | 60 expiration_time_ = |
| 90 base::Time::Now() + base::TimeDelta::FromSeconds( | 61 base::Time::Now() + base::TimeDelta::FromSeconds( |
| (...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 179 const IdentityTokenCacheValue& IdentityAPI::GetCachedToken( | 150 const IdentityTokenCacheValue& IdentityAPI::GetCachedToken( |
| 180 const ExtensionTokenKey& key) { | 151 const ExtensionTokenKey& key) { |
| 181 return token_cache_[key]; | 152 return token_cache_[key]; |
| 182 } | 153 } |
| 183 | 154 |
| 184 const IdentityAPI::CachedTokens& IdentityAPI::GetAllCachedTokens() { | 155 const IdentityAPI::CachedTokens& IdentityAPI::GetAllCachedTokens() { |
| 185 return token_cache_; | 156 return token_cache_; |
| 186 } | 157 } |
| 187 | 158 |
| 188 std::vector<std::string> IdentityAPI::GetAccounts() const { | 159 std::vector<std::string> IdentityAPI::GetAccounts() const { |
| 189 const std::string primary_account_id = GetPrimaryAccountId(browser_context_); | |
| 190 const std::vector<gaia::AccountIds> ids = account_tracker_.GetAccounts(); | 160 const std::vector<gaia::AccountIds> ids = account_tracker_.GetAccounts(); |
| 191 std::vector<std::string> gaia_ids; | 161 std::vector<std::string> gaia_ids; |
| 192 | 162 |
| 193 if (switches::IsExtensionsMultiAccount()) { | 163 if (switches::IsExtensionsMultiAccount()) { |
| 194 for (std::vector<gaia::AccountIds>::const_iterator it = ids.begin(); | 164 for (std::vector<gaia::AccountIds>::const_iterator it = ids.begin(); |
| 195 it != ids.end(); | 165 it != ids.end(); |
| 196 ++it) { | 166 ++it) { |
| 197 gaia_ids.push_back(it->gaia); | 167 gaia_ids.push_back(it->gaia); |
| 198 } | 168 } |
| 199 } else if (ids.size() >= 1) { | 169 } else if (ids.size() >= 1) { |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 275 it != gaia_ids.end(); | 245 it != gaia_ids.end(); |
| 276 ++it) { | 246 ++it) { |
| 277 api::identity::AccountInfo account_info; | 247 api::identity::AccountInfo account_info; |
| 278 account_info.id = *it; | 248 account_info.id = *it; |
| 279 infos->Append(account_info.ToValue()); | 249 infos->Append(account_info.ToValue()); |
| 280 } | 250 } |
| 281 | 251 |
| 282 return RespondNow(OneArgument(std::move(infos))); | 252 return RespondNow(OneArgument(std::move(infos))); |
| 283 } | 253 } |
| 284 | 254 |
| 285 IdentityGetAuthTokenFunction::IdentityGetAuthTokenFunction() | |
| 286 : OAuth2TokenService::Consumer("extensions_identity_api"), | |
| 287 interactive_(false), | |
| 288 should_prompt_for_scopes_(false), | |
| 289 should_prompt_for_signin_(false) { | |
| 290 } | |
| 291 | |
| 292 IdentityGetAuthTokenFunction::~IdentityGetAuthTokenFunction() { | |
| 293 TRACE_EVENT_ASYNC_END0("identity", "IdentityGetAuthTokenFunction", this); | |
| 294 } | |
| 295 | |
| 296 bool IdentityGetAuthTokenFunction::RunAsync() { | |
| 297 TRACE_EVENT_ASYNC_BEGIN1("identity", | |
| 298 "IdentityGetAuthTokenFunction", | |
| 299 this, | |
| 300 "extension", | |
| 301 extension()->id()); | |
| 302 | |
| 303 if (GetProfile()->IsOffTheRecord()) { | |
| 304 error_ = identity_constants::kOffTheRecord; | |
| 305 return false; | |
| 306 } | |
| 307 | |
| 308 std::unique_ptr<identity::GetAuthToken::Params> params( | |
| 309 identity::GetAuthToken::Params::Create(*args_)); | |
| 310 EXTENSION_FUNCTION_VALIDATE(params.get()); | |
| 311 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 bool is_kiosk = user_manager::UserManager::Get()->IsLoggedInAsKioskApp(); | |
| 371 bool is_public_session = | |
| 372 user_manager::UserManager::Get()->IsLoggedInAsPublicAccount(); | |
| 373 | |
| 374 if (connector->IsEnterpriseManaged() && (is_kiosk || is_public_session)) { | |
| 375 if (is_public_session && !IsOriginWhitelistedInPublicSession()) { | |
| 376 CompleteFunctionWithError(identity_constants::kUserNotSignedIn); | |
| 377 return true; | |
| 378 } | |
| 379 | |
| 380 StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE); | |
| 381 return true; | |
| 382 } | |
| 383 #endif | |
| 384 | |
| 385 if (!HasLoginToken()) { | |
| 386 if (!should_prompt_for_signin_) { | |
| 387 CompleteFunctionWithError(identity_constants::kUserNotSignedIn); | |
| 388 return true; | |
| 389 } | |
| 390 // Display a login prompt. | |
| 391 StartSigninFlow(); | |
| 392 } else { | |
| 393 StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE); | |
| 394 } | |
| 395 | |
| 396 return true; | |
| 397 } | |
| 398 | |
| 399 void IdentityGetAuthTokenFunction::StartAsyncRun() { | |
| 400 // Balanced in CompleteAsyncRun | |
| 401 AddRef(); | |
| 402 extensions::IdentityAPI::GetFactoryInstance() | |
| 403 ->Get(GetProfile()) | |
| 404 ->set_get_auth_token_function(this); | |
| 405 } | |
| 406 | |
| 407 void IdentityGetAuthTokenFunction::CompleteAsyncRun(bool success) { | |
| 408 extensions::IdentityAPI::GetFactoryInstance() | |
| 409 ->Get(GetProfile()) | |
| 410 ->set_get_auth_token_function(nullptr); | |
| 411 | |
| 412 SendResponse(success); | |
| 413 Release(); // Balanced in StartAsyncRun | |
| 414 } | |
| 415 | |
| 416 void IdentityGetAuthTokenFunction::CompleteFunctionWithResult( | |
| 417 const std::string& access_token) { | |
| 418 SetResult(base::MakeUnique<base::StringValue>(access_token)); | |
| 419 CompleteAsyncRun(true); | |
| 420 } | |
| 421 | |
| 422 void IdentityGetAuthTokenFunction::CompleteFunctionWithError( | |
| 423 const std::string& error) { | |
| 424 TRACE_EVENT_ASYNC_STEP_PAST1("identity", | |
| 425 "IdentityGetAuthTokenFunction", | |
| 426 this, | |
| 427 "CompleteFunctionWithError", | |
| 428 "error", | |
| 429 error); | |
| 430 error_ = error; | |
| 431 CompleteAsyncRun(false); | |
| 432 } | |
| 433 | |
| 434 void IdentityGetAuthTokenFunction::StartSigninFlow() { | |
| 435 // All cached tokens are invalid because the user is not signed in. | |
| 436 IdentityAPI* id_api = | |
| 437 extensions::IdentityAPI::GetFactoryInstance()->Get(GetProfile()); | |
| 438 id_api->EraseAllCachedTokens(); | |
| 439 // Display a login prompt. If the subsequent mint fails, don't display the | |
| 440 // login prompt again. | |
| 441 should_prompt_for_signin_ = false; | |
| 442 ShowLoginPopup(); | |
| 443 } | |
| 444 | |
| 445 void IdentityGetAuthTokenFunction::StartMintTokenFlow( | |
| 446 IdentityMintRequestQueue::MintType type) { | |
| 447 mint_token_flow_type_ = type; | |
| 448 | |
| 449 // Flows are serialized to prevent excessive traffic to GAIA, and | |
| 450 // to consolidate UI pop-ups. | |
| 451 IdentityAPI* id_api = | |
| 452 extensions::IdentityAPI::GetFactoryInstance()->Get(GetProfile()); | |
| 453 | |
| 454 if (!should_prompt_for_scopes_) { | |
| 455 // Caller requested no interaction. | |
| 456 | |
| 457 if (type == IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE) { | |
| 458 // GAIA told us to do a consent UI. | |
| 459 CompleteFunctionWithError(identity_constants::kNoGrant); | |
| 460 return; | |
| 461 } | |
| 462 if (!id_api->mint_queue()->empty( | |
| 463 IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE, *token_key_)) { | |
| 464 // Another call is going through a consent UI. | |
| 465 CompleteFunctionWithError(identity_constants::kNoGrant); | |
| 466 return; | |
| 467 } | |
| 468 } | |
| 469 id_api->mint_queue()->RequestStart(type, *token_key_, this); | |
| 470 } | |
| 471 | |
| 472 void IdentityGetAuthTokenFunction::CompleteMintTokenFlow() { | |
| 473 IdentityMintRequestQueue::MintType type = mint_token_flow_type_; | |
| 474 | |
| 475 extensions::IdentityAPI::GetFactoryInstance() | |
| 476 ->Get(GetProfile()) | |
| 477 ->mint_queue() | |
| 478 ->RequestComplete(type, *token_key_, this); | |
| 479 } | |
| 480 | |
| 481 void IdentityGetAuthTokenFunction::StartMintToken( | |
| 482 IdentityMintRequestQueue::MintType type) { | |
| 483 TRACE_EVENT_ASYNC_STEP_PAST1("identity", | |
| 484 "IdentityGetAuthTokenFunction", | |
| 485 this, | |
| 486 "StartMintToken", | |
| 487 "type", | |
| 488 type); | |
| 489 | |
| 490 const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(extension()); | |
| 491 IdentityAPI* id_api = IdentityAPI::GetFactoryInstance()->Get(GetProfile()); | |
| 492 IdentityTokenCacheValue cache_entry = id_api->GetCachedToken(*token_key_); | |
| 493 IdentityTokenCacheValue::CacheValueStatus cache_status = | |
| 494 cache_entry.status(); | |
| 495 | |
| 496 if (type == IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE) { | |
| 497 switch (cache_status) { | |
| 498 case IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND: | |
| 499 #if defined(OS_CHROMEOS) | |
| 500 // Always force minting token for ChromeOS kiosk app and public session. | |
| 501 if (user_manager::UserManager::Get()->IsLoggedInAsPublicAccount() && | |
| 502 !IsOriginWhitelistedInPublicSession()) { | |
| 503 CompleteFunctionWithError(identity_constants::kUserNotSignedIn); | |
| 504 return; | |
| 505 } | |
| 506 | |
| 507 if (user_manager::UserManager::Get()->IsLoggedInAsKioskApp() || | |
| 508 user_manager::UserManager::Get()->IsLoggedInAsPublicAccount()) { | |
| 509 gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE; | |
| 510 policy::BrowserPolicyConnectorChromeOS* connector = | |
| 511 g_browser_process->platform_part() | |
| 512 ->browser_policy_connector_chromeos(); | |
| 513 if (connector->IsEnterpriseManaged()) { | |
| 514 StartDeviceLoginAccessTokenRequest(); | |
| 515 } else { | |
| 516 StartLoginAccessTokenRequest(); | |
| 517 } | |
| 518 return; | |
| 519 } | |
| 520 #endif | |
| 521 | |
| 522 if (oauth2_info.auto_approve) | |
| 523 // oauth2_info.auto_approve is protected by a whitelist in | |
| 524 // _manifest_features.json hence only selected extensions take | |
| 525 // advantage of forcefully minting the token. | |
| 526 gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE; | |
| 527 else | |
| 528 gaia_mint_token_mode_ = OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE; | |
| 529 StartLoginAccessTokenRequest(); | |
| 530 break; | |
| 531 | |
| 532 case IdentityTokenCacheValue::CACHE_STATUS_TOKEN: | |
| 533 CompleteMintTokenFlow(); | |
| 534 CompleteFunctionWithResult(cache_entry.token()); | |
| 535 break; | |
| 536 | |
| 537 case IdentityTokenCacheValue::CACHE_STATUS_ADVICE: | |
| 538 CompleteMintTokenFlow(); | |
| 539 should_prompt_for_signin_ = false; | |
| 540 issue_advice_ = cache_entry.issue_advice(); | |
| 541 StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE); | |
| 542 break; | |
| 543 } | |
| 544 } else { | |
| 545 DCHECK(type == IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE); | |
| 546 | |
| 547 if (cache_status == IdentityTokenCacheValue::CACHE_STATUS_TOKEN) { | |
| 548 CompleteMintTokenFlow(); | |
| 549 CompleteFunctionWithResult(cache_entry.token()); | |
| 550 } else { | |
| 551 ShowOAuthApprovalDialog(issue_advice_); | |
| 552 } | |
| 553 } | |
| 554 } | |
| 555 | |
| 556 void IdentityGetAuthTokenFunction::OnMintTokenSuccess( | |
| 557 const std::string& access_token, int time_to_live) { | |
| 558 TRACE_EVENT_ASYNC_STEP_PAST0("identity", | |
| 559 "IdentityGetAuthTokenFunction", | |
| 560 this, | |
| 561 "OnMintTokenSuccess"); | |
| 562 | |
| 563 IdentityTokenCacheValue token(access_token, | |
| 564 base::TimeDelta::FromSeconds(time_to_live)); | |
| 565 IdentityAPI::GetFactoryInstance()->Get(GetProfile())->SetCachedToken( | |
| 566 *token_key_, token); | |
| 567 | |
| 568 CompleteMintTokenFlow(); | |
| 569 CompleteFunctionWithResult(access_token); | |
| 570 } | |
| 571 | |
| 572 void IdentityGetAuthTokenFunction::OnMintTokenFailure( | |
| 573 const GoogleServiceAuthError& error) { | |
| 574 TRACE_EVENT_ASYNC_STEP_PAST1("identity", | |
| 575 "IdentityGetAuthTokenFunction", | |
| 576 this, | |
| 577 "OnMintTokenFailure", | |
| 578 "error", | |
| 579 error.ToString()); | |
| 580 CompleteMintTokenFlow(); | |
| 581 switch (error.state()) { | |
| 582 case GoogleServiceAuthError::SERVICE_ERROR: | |
| 583 if (interactive_) { | |
| 584 StartSigninFlow(); | |
| 585 return; | |
| 586 } | |
| 587 break; | |
| 588 case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS: | |
| 589 case GoogleServiceAuthError::ACCOUNT_DELETED: | |
| 590 case GoogleServiceAuthError::ACCOUNT_DISABLED: | |
| 591 // TODO(courage): flush ticket and retry once | |
| 592 if (should_prompt_for_signin_) { | |
| 593 // Display a login prompt and try again (once). | |
| 594 StartSigninFlow(); | |
| 595 return; | |
| 596 } | |
| 597 break; | |
| 598 default: | |
| 599 // Return error to caller. | |
| 600 break; | |
| 601 } | |
| 602 | |
| 603 CompleteFunctionWithError( | |
| 604 std::string(identity_constants::kAuthFailure) + error.ToString()); | |
| 605 } | |
| 606 | |
| 607 void IdentityGetAuthTokenFunction::OnIssueAdviceSuccess( | |
| 608 const IssueAdviceInfo& issue_advice) { | |
| 609 TRACE_EVENT_ASYNC_STEP_PAST0("identity", | |
| 610 "IdentityGetAuthTokenFunction", | |
| 611 this, | |
| 612 "OnIssueAdviceSuccess"); | |
| 613 | |
| 614 IdentityAPI::GetFactoryInstance()->Get(GetProfile())->SetCachedToken( | |
| 615 *token_key_, IdentityTokenCacheValue(issue_advice)); | |
| 616 CompleteMintTokenFlow(); | |
| 617 | |
| 618 should_prompt_for_signin_ = false; | |
| 619 // Existing grant was revoked and we used NO_FORCE, so we got info back | |
| 620 // instead. Start a consent UI if we can. | |
| 621 issue_advice_ = issue_advice; | |
| 622 StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE); | |
| 623 } | |
| 624 | |
| 625 void IdentityGetAuthTokenFunction::SigninSuccess() { | |
| 626 TRACE_EVENT_ASYNC_STEP_PAST0("identity", | |
| 627 "IdentityGetAuthTokenFunction", | |
| 628 this, | |
| 629 "SigninSuccess"); | |
| 630 | |
| 631 // If there was no account associated this profile before the | |
| 632 // sign-in, we may not have an account_id in the token_key yet. | |
| 633 if (token_key_->account_id.empty()) { | |
| 634 token_key_->account_id = GetPrimaryAccountId(GetProfile()); | |
| 635 } | |
| 636 | |
| 637 StartMintTokenFlow(IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE); | |
| 638 } | |
| 639 | |
| 640 void IdentityGetAuthTokenFunction::SigninFailed() { | |
| 641 TRACE_EVENT_ASYNC_STEP_PAST0("identity", | |
| 642 "IdentityGetAuthTokenFunction", | |
| 643 this, | |
| 644 "SigninFailed"); | |
| 645 CompleteFunctionWithError(identity_constants::kUserNotSignedIn); | |
| 646 } | |
| 647 | |
| 648 void IdentityGetAuthTokenFunction::OnGaiaFlowFailure( | |
| 649 GaiaWebAuthFlow::Failure failure, | |
| 650 GoogleServiceAuthError service_error, | |
| 651 const std::string& oauth_error) { | |
| 652 CompleteMintTokenFlow(); | |
| 653 std::string error; | |
| 654 | |
| 655 switch (failure) { | |
| 656 case GaiaWebAuthFlow::WINDOW_CLOSED: | |
| 657 error = identity_constants::kUserRejected; | |
| 658 break; | |
| 659 | |
| 660 case GaiaWebAuthFlow::INVALID_REDIRECT: | |
| 661 error = identity_constants::kInvalidRedirect; | |
| 662 break; | |
| 663 | |
| 664 case GaiaWebAuthFlow::SERVICE_AUTH_ERROR: | |
| 665 // If this is really an authentication error and not just a transient | |
| 666 // network error, and this is an interactive request for a signed-in | |
| 667 // user, then we show signin UI instead of failing. | |
| 668 if (service_error.state() != GoogleServiceAuthError::CONNECTION_FAILED && | |
| 669 service_error.state() != | |
| 670 GoogleServiceAuthError::SERVICE_UNAVAILABLE && | |
| 671 interactive_ && HasLoginToken()) { | |
| 672 StartSigninFlow(); | |
| 673 return; | |
| 674 } | |
| 675 error = std::string(identity_constants::kAuthFailure) + | |
| 676 service_error.ToString(); | |
| 677 break; | |
| 678 | |
| 679 case GaiaWebAuthFlow::OAUTH_ERROR: | |
| 680 error = MapOAuth2ErrorToDescription(oauth_error); | |
| 681 break; | |
| 682 | |
| 683 case GaiaWebAuthFlow::LOAD_FAILED: | |
| 684 error = identity_constants::kPageLoadFailure; | |
| 685 break; | |
| 686 | |
| 687 default: | |
| 688 NOTREACHED() << "Unexpected error from gaia web auth flow: " << failure; | |
| 689 error = identity_constants::kInvalidRedirect; | |
| 690 break; | |
| 691 } | |
| 692 | |
| 693 CompleteFunctionWithError(error); | |
| 694 } | |
| 695 | |
| 696 void IdentityGetAuthTokenFunction::OnGaiaFlowCompleted( | |
| 697 const std::string& access_token, | |
| 698 const std::string& expiration) { | |
| 699 TRACE_EVENT_ASYNC_STEP_PAST0("identity", | |
| 700 "IdentityGetAuthTokenFunction", | |
| 701 this, | |
| 702 "OnGaiaFlowCompleted"); | |
| 703 int time_to_live; | |
| 704 if (!expiration.empty() && base::StringToInt(expiration, &time_to_live)) { | |
| 705 IdentityTokenCacheValue token_value( | |
| 706 access_token, base::TimeDelta::FromSeconds(time_to_live)); | |
| 707 IdentityAPI::GetFactoryInstance()->Get(GetProfile())->SetCachedToken( | |
| 708 *token_key_, token_value); | |
| 709 } | |
| 710 | |
| 711 CompleteMintTokenFlow(); | |
| 712 CompleteFunctionWithResult(access_token); | |
| 713 } | |
| 714 | |
| 715 void IdentityGetAuthTokenFunction::OnGetTokenSuccess( | |
| 716 const OAuth2TokenService::Request* request, | |
| 717 const std::string& access_token, | |
| 718 const base::Time& expiration_time) { | |
| 719 TRACE_EVENT_ASYNC_STEP_PAST1("identity", | |
| 720 "IdentityGetAuthTokenFunction", | |
| 721 this, | |
| 722 "OnGetTokenSuccess", | |
| 723 "account", | |
| 724 request->GetAccountId()); | |
| 725 login_token_request_.reset(); | |
| 726 StartGaiaRequest(access_token); | |
| 727 } | |
| 728 | |
| 729 void IdentityGetAuthTokenFunction::OnGetTokenFailure( | |
| 730 const OAuth2TokenService::Request* request, | |
| 731 const GoogleServiceAuthError& error) { | |
| 732 TRACE_EVENT_ASYNC_STEP_PAST1("identity", | |
| 733 "IdentityGetAuthTokenFunction", | |
| 734 this, | |
| 735 "OnGetTokenFailure", | |
| 736 "error", | |
| 737 error.ToString()); | |
| 738 login_token_request_.reset(); | |
| 739 OnGaiaFlowFailure(GaiaWebAuthFlow::SERVICE_AUTH_ERROR, error, std::string()); | |
| 740 } | |
| 741 | |
| 742 void IdentityGetAuthTokenFunction::Shutdown() { | |
| 743 gaia_web_auth_flow_.reset(); | |
| 744 signin_flow_.reset(); | |
| 745 login_token_request_.reset(); | |
| 746 extensions::IdentityAPI::GetFactoryInstance() | |
| 747 ->Get(GetProfile()) | |
| 748 ->mint_queue() | |
| 749 ->RequestCancel(*token_key_, this); | |
| 750 CompleteFunctionWithError(identity_constants::kCanceled); | |
| 751 } | |
| 752 | |
| 753 #if defined(OS_CHROMEOS) | |
| 754 void IdentityGetAuthTokenFunction::StartDeviceLoginAccessTokenRequest() { | |
| 755 chromeos::DeviceOAuth2TokenService* service = | |
| 756 chromeos::DeviceOAuth2TokenServiceFactory::Get(); | |
| 757 // Since robot account refresh tokens are scoped down to [any-api] only, | |
| 758 // request access token for [any-api] instead of login. | |
| 759 OAuth2TokenService::ScopeSet scopes; | |
| 760 scopes.insert(GaiaConstants::kAnyApiOAuth2Scope); | |
| 761 login_token_request_ = | |
| 762 service->StartRequest(service->GetRobotAccountId(), | |
| 763 scopes, | |
| 764 this); | |
| 765 } | |
| 766 | |
| 767 bool IdentityGetAuthTokenFunction::IsOriginWhitelistedInPublicSession() { | |
| 768 DCHECK(extension()); | |
| 769 GURL extension_url = extension()->url(); | |
| 770 for (size_t i = 0; i < arraysize(kPublicSessionAllowedOrigins); i++) { | |
| 771 URLPattern allowed_origin(URLPattern::SCHEME_ALL, | |
| 772 kPublicSessionAllowedOrigins[i]); | |
| 773 if (allowed_origin.MatchesSecurityOrigin(extension_url)) { | |
| 774 return true; | |
| 775 } | |
| 776 } | |
| 777 return false; | |
| 778 } | |
| 779 #endif | |
| 780 | |
| 781 void IdentityGetAuthTokenFunction::StartLoginAccessTokenRequest() { | |
| 782 ProfileOAuth2TokenService* service = | |
| 783 ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile()); | |
| 784 #if defined(OS_CHROMEOS) | |
| 785 if (chrome::IsRunningInForcedAppMode()) { | |
| 786 std::string app_client_id; | |
| 787 std::string app_client_secret; | |
| 788 if (chromeos::UserSessionManager::GetInstance()-> | |
| 789 GetAppModeChromeClientOAuthInfo(&app_client_id, | |
| 790 &app_client_secret)) { | |
| 791 login_token_request_ = | |
| 792 service->StartRequestForClient(token_key_->account_id, | |
| 793 app_client_id, | |
| 794 app_client_secret, | |
| 795 OAuth2TokenService::ScopeSet(), | |
| 796 this); | |
| 797 return; | |
| 798 } | |
| 799 } | |
| 800 #endif | |
| 801 login_token_request_ = service->StartRequest( | |
| 802 token_key_->account_id, OAuth2TokenService::ScopeSet(), this); | |
| 803 } | |
| 804 | |
| 805 void IdentityGetAuthTokenFunction::StartGaiaRequest( | |
| 806 const std::string& login_access_token) { | |
| 807 DCHECK(!login_access_token.empty()); | |
| 808 mint_token_flow_.reset(CreateMintTokenFlow()); | |
| 809 mint_token_flow_->Start(GetProfile()->GetRequestContext(), | |
| 810 login_access_token); | |
| 811 } | |
| 812 | |
| 813 void IdentityGetAuthTokenFunction::ShowLoginPopup() { | |
| 814 signin_flow_.reset(new IdentitySigninFlow(this, GetProfile())); | |
| 815 signin_flow_->Start(); | |
| 816 } | |
| 817 | |
| 818 void IdentityGetAuthTokenFunction::ShowOAuthApprovalDialog( | |
| 819 const IssueAdviceInfo& issue_advice) { | |
| 820 const std::string locale = extension_l10n_util::CurrentLocaleOrDefault(); | |
| 821 | |
| 822 gaia_web_auth_flow_.reset(new GaiaWebAuthFlow( | |
| 823 this, GetProfile(), token_key_.get(), oauth2_client_id_, locale)); | |
| 824 gaia_web_auth_flow_->Start(); | |
| 825 } | |
| 826 | |
| 827 OAuth2MintTokenFlow* IdentityGetAuthTokenFunction::CreateMintTokenFlow() { | |
| 828 SigninClient* signin_client = | |
| 829 ChromeSigninClientFactory::GetForProfile(GetProfile()); | |
| 830 std::string signin_scoped_device_id = | |
| 831 signin_client->GetSigninScopedDeviceId(); | |
| 832 OAuth2MintTokenFlow* mint_token_flow = new OAuth2MintTokenFlow( | |
| 833 this, | |
| 834 OAuth2MintTokenFlow::Parameters( | |
| 835 extension()->id(), | |
| 836 oauth2_client_id_, | |
| 837 std::vector<std::string>(token_key_->scopes.begin(), | |
| 838 token_key_->scopes.end()), | |
| 839 signin_scoped_device_id, | |
| 840 gaia_mint_token_mode_)); | |
| 841 return mint_token_flow; | |
| 842 } | |
| 843 | |
| 844 bool IdentityGetAuthTokenFunction::HasLoginToken() const { | |
| 845 ProfileOAuth2TokenService* token_service = | |
| 846 ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile()); | |
| 847 return token_service->RefreshTokenIsAvailable(token_key_->account_id); | |
| 848 } | |
| 849 | |
| 850 std::string IdentityGetAuthTokenFunction::MapOAuth2ErrorToDescription( | |
| 851 const std::string& error) { | |
| 852 const char kOAuth2ErrorAccessDenied[] = "access_denied"; | |
| 853 const char kOAuth2ErrorInvalidScope[] = "invalid_scope"; | |
| 854 | |
| 855 if (error == kOAuth2ErrorAccessDenied) | |
| 856 return std::string(identity_constants::kUserRejected); | |
| 857 else if (error == kOAuth2ErrorInvalidScope) | |
| 858 return std::string(identity_constants::kInvalidScopes); | |
| 859 else | |
| 860 return std::string(identity_constants::kAuthFailure) + error; | |
| 861 } | |
| 862 | |
| 863 std::string IdentityGetAuthTokenFunction::GetOAuth2ClientId() const { | |
| 864 const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(extension()); | |
| 865 std::string client_id = oauth2_info.client_id; | |
| 866 | |
| 867 // Component apps using auto_approve may use Chrome's client ID by | |
| 868 // omitting the field. | |
| 869 if (client_id.empty() && extension()->location() == Manifest::COMPONENT && | |
| 870 oauth2_info.auto_approve) { | |
| 871 client_id = GaiaUrls::GetInstance()->oauth2_chrome_client_id(); | |
| 872 } | |
| 873 return client_id; | |
| 874 } | |
| 875 | |
| 876 } // namespace extensions | 255 } // namespace extensions |
| OLD | NEW |