| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <memory> |
| #include <optional> |
| #include <set> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/containers/contains.h" |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/notreached.h" |
| #include "base/run_loop.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/test_future.h" |
| #include "base/test/values_test_util.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "build/buildflag.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chrome/browser/extensions/api/identity/gaia_remote_consent_flow.h" |
| #include "chrome/browser/extensions/api/identity/identity_api.h" |
| #include "chrome/browser/extensions/api/identity/identity_constants.h" |
| #include "chrome/browser/extensions/api/identity/identity_get_accounts_function.h" |
| #include "chrome/browser/extensions/api/identity/identity_get_auth_token_error.h" |
| #include "chrome/browser/extensions/api/identity/identity_get_auth_token_function.h" |
| #include "chrome/browser/extensions/api/identity/identity_get_profile_user_info_function.h" |
| #include "chrome/browser/extensions/api/identity/identity_launch_web_auth_flow_function.h" |
| #include "chrome/browser/extensions/api/identity/identity_remove_cached_auth_token_function.h" |
| #include "chrome/browser/extensions/api/identity/launch_web_auth_flow_delegate.h" |
| #include "chrome/browser/extensions/component_loader.h" |
| #include "chrome/browser/extensions/extension_apitest.h" |
| #include "chrome/browser/extensions/extension_browsertest.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/profiles/profile_test_util.h" |
| #include "chrome/browser/signin/account_consistency_mode_manager.h" |
| #include "chrome/browser/signin/account_reconcilor_factory.h" |
| #include "chrome/browser/signin/chrome_signin_client_factory.h" |
| #include "chrome/browser/signin/chrome_signin_client_test_util.h" |
| #include "chrome/browser/signin/identity_manager_factory.h" |
| #include "chrome/browser/signin/identity_test_environment_profile_adaptor.h" |
| #include "chrome/browser/sync/sync_service_factory.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/extensions/api/identity.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/testing_browser_process.h" |
| #include "components/crx_file/id_util.h" |
| #include "components/keep_alive_registry/keep_alive_types.h" |
| #include "components/keep_alive_registry/scoped_keep_alive.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/signin/core/browser/account_reconcilor.h" |
| #include "components/signin/public/base/consent_level.h" |
| #include "components/signin/public/base/list_accounts_test_utils.h" |
| #include "components/signin/public/base/signin_metrics.h" |
| #include "components/signin/public/base/signin_pref_names.h" |
| #include "components/signin/public/base/signin_switches.h" |
| #include "components/signin/public/identity_manager/accounts_mutator.h" |
| #include "components/signin/public/identity_manager/identity_manager.h" |
| #include "components/signin/public/identity_manager/identity_test_utils.h" |
| #include "components/sync/base/command_line_switches.h" |
| #include "components/sync/base/features.h" |
| #include "components/sync/base/user_selectable_type.h" |
| #include "components/sync/service/sync_service.h" |
| #include "components/sync/service/sync_user_settings.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "content/public/test/test_utils.h" |
| #include "extensions/browser/api/extensions_api_client.h" |
| #include "extensions/browser/api_test_utils.h" |
| #include "extensions/browser/extension_function_dispatcher.h" |
| #include "extensions/browser/pref_names.h" |
| #include "extensions/buildflags/buildflags.h" |
| #include "extensions/common/api/oauth2.h" |
| #include "extensions/common/extension_builder.h" |
| #include "extensions/common/extension_features.h" |
| #include "extensions/common/extension_id.h" |
| #include "extensions/common/manifest_handlers/oauth2_manifest_handler.h" |
| #include "google_apis/gaia/gaia_id.h" |
| #include "google_apis/gaia/oauth2_mint_token_flow.h" |
| #include "net/cookies/cookie_util.h" |
| #include "net/test/embedded_test_server/default_handlers.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "services/network/public/mojom/cookie_manager.mojom.h" |
| #include "services/network/test/test_url_loader_factory.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/idle/idle.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "url/gurl.h" |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/guest_view/browser/guest_view_base.h" |
| #include "components/guest_view/browser/guest_view_manager_delegate.h" |
| #include "components/guest_view/browser/guest_view_manager_factory.h" |
| #include "components/guest_view/browser/test_guest_view_manager.h" |
| #include "ui/base/idle/scoped_set_idle_state.h" |
| #include "ui/views/widget/any_widget_observer.h" |
| #include "ui/views/window/dialog_delegate.h" |
| #endif // BUILDFLAG(ENABLE_EXTENSIONS) |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| #include "chrome/browser/ash/net/network_portal_detector_test_impl.h" |
| #include "chrome/browser/ash/test/kiosk_app_logged_in_browser_test_mixin.h" |
| #include "chrome/browser/ash/test/public_account_logged_in_browser_test_mixin.h" |
| #include "chrome/browser/ash/test/web_kiosk_app_logged_in_browser_test_mixin.h" |
| #include "chrome/test/base/mixin_based_in_process_browser_test.h" |
| #include "chromeos/ash/components/browser_context_helper/browser_context_helper.h" |
| #include "chromeos/ash/components/install_attributes/stub_install_attributes.h" |
| #include "chromeos/ash/components/network/network_handler.h" |
| #include "chromeos/ash/components/network/network_state.h" |
| #include "chromeos/ash/components/network/network_state_handler.h" |
| #include "components/account_id/account_id.h" |
| #include "components/user_manager/user_manager.h" |
| #endif |
| |
| static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE)); |
| |
| using extensions::ExtensionsAPIClient; |
| using testing::_; |
| using testing::Return; |
| |
| namespace extensions { |
| |
| namespace { |
| |
| namespace errors = identity_constants; |
| namespace utils = api_test_utils; |
| |
| using api::oauth2::OAuth2Info; |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| const char kAccessToken[] = "auth_token"; |
| #endif // BUILDFLAG(ENABLE_EXTENSIONS) |
| |
| const char kExtensionId[] = "ext_id"; |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| const char kGetAuthTokenResultHistogramName[] = |
| "Signin.Extensions.GetAuthTokenResult"; |
| const char kGetAuthTokenResultAfterConsentApprovedHistogramName[] = |
| "Signin.Extensions.GetAuthTokenResult.RemoteConsentApproved"; |
| |
| const char kLaunchWebAuthFlowResultHistogramName[] = |
| "Signin.Extensions.LaunchWebAuthFlowResult"; |
| #endif // BUILDFLAG(ENABLE_EXTENSIONS) |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| void InitNetwork() { |
| const ash::NetworkState* default_network = |
| ash::NetworkHandler::Get()->network_state_handler()->DefaultNetwork(); |
| |
| auto* portal_detector = new ash::NetworkPortalDetectorTestImpl(); |
| portal_detector->SetDefaultNetworkForTesting(default_network->guid()); |
| |
| ash::network_portal_detector::InitializeForTesting(portal_detector); |
| } |
| #endif |
| |
| // Asynchronous function runner allows tests to manipulate the browser window |
| // after the call happens. |
| class AsyncFunctionRunner { |
| public: |
| void RunFunctionAsync(ExtensionFunction* function, |
| const std::string& args, |
| content::BrowserContext* browser_context) { |
| response_delegate_ = |
| std::make_unique<api_test_utils::SendResponseHelper>(function); |
| function->preserve_results_for_testing(); |
| base::Value::List parsed_args = base::test::ParseJsonList(args); |
| function->SetArgs(std::move(parsed_args)); |
| |
| if (!function->extension()) { |
| scoped_refptr<const Extension> empty_extension( |
| ExtensionBuilder("Test").Build()); |
| function->set_extension(empty_extension.get()); |
| } |
| |
| dispatcher_ = |
| std::make_unique<ExtensionFunctionDispatcher>(browser_context); |
| function->SetDispatcher(dispatcher_->AsWeakPtr()); |
| |
| function->set_has_callback(true); |
| function->RunWithValidation().Execute(); |
| } |
| |
| std::string WaitForError(ExtensionFunction* function) { |
| RunMessageLoopUntilResponse(); |
| CHECK(function->response_type()); |
| EXPECT_EQ(ExtensionFunction::ResponseType::kFailed, |
| *function->response_type()); |
| return function->GetError(); |
| } |
| |
| void WaitForOneResult(ExtensionFunction* function, base::Value* result) { |
| RunMessageLoopUntilResponse(); |
| EXPECT_TRUE(function->GetError().empty()) |
| << "Unexpected error: " << function->GetError(); |
| EXPECT_NE(nullptr, function->GetResultListForTest()); |
| |
| const auto& result_list = *function->GetResultListForTest(); |
| EXPECT_EQ(1ul, result_list.size()); |
| |
| *result = result_list[0].Clone(); |
| } |
| |
| private: |
| void RunMessageLoopUntilResponse() { |
| response_delegate_->WaitForResponse(); |
| EXPECT_TRUE(response_delegate_->has_response()); |
| } |
| |
| std::unique_ptr<api_test_utils::SendResponseHelper> response_delegate_; |
| std::unique_ptr<ExtensionFunctionDispatcher> dispatcher_; |
| }; |
| |
| class AsyncExtensionBrowserTest : public ExtensionBrowserTest { |
| protected: |
| // Provide wrappers of AsynchronousFunctionRunner for convenience. |
| void RunFunctionAsync(ExtensionFunction* function, const std::string& args) { |
| async_function_runners_[function] = std::make_unique<AsyncFunctionRunner>(); |
| async_function_runners_[function]->RunFunctionAsync(function, args, |
| profile()); |
| } |
| |
| std::string WaitForError(ExtensionFunction* function) { |
| return async_function_runners_[function]->WaitForError(function); |
| } |
| |
| void WaitForOneResult(ExtensionFunction* function, base::Value* result) { |
| async_function_runners_[function]->WaitForOneResult(function, result); |
| } |
| |
| private: |
| std::map<ExtensionFunction*, std::unique_ptr<AsyncFunctionRunner>> |
| async_function_runners_; |
| }; |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| class TestHangOAuth2MintTokenFlow : public OAuth2MintTokenFlow { |
| public: |
| TestHangOAuth2MintTokenFlow() |
| : OAuth2MintTokenFlow(nullptr, OAuth2MintTokenFlow::Parameters()) {} |
| |
| void Start(scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, |
| const std::string& access_token) override { |
| // Do nothing, simulating a hanging network call. |
| } |
| }; |
| |
| class TestOAuth2MintTokenFlow : public OAuth2MintTokenFlow { |
| public: |
| enum ResultType { |
| REMOTE_CONSENT_SUCCESS, |
| MINT_TOKEN_SUCCESS, |
| MINT_TOKEN_FAILURE, |
| MINT_TOKEN_BAD_CREDENTIALS, |
| MINT_TOKEN_SERVICE_ERROR |
| }; |
| |
| TestOAuth2MintTokenFlow(ResultType result, |
| const std::set<std::string>* requested_scopes, |
| const std::set<std::string>& granted_scopes, |
| OAuth2MintTokenFlow::Delegate* delegate) |
| : OAuth2MintTokenFlow(delegate, OAuth2MintTokenFlow::Parameters()), |
| result_(result), |
| requested_scopes_(requested_scopes), |
| granted_scopes_(granted_scopes), |
| delegate_(delegate) {} |
| |
| void Start(scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, |
| const std::string& access_token) override { |
| switch (result_) { |
| case REMOTE_CONSENT_SUCCESS: { |
| RemoteConsentResolutionData resolution_data; |
| delegate_->OnRemoteConsentSuccess(resolution_data); |
| break; |
| } |
| case MINT_TOKEN_SUCCESS: { |
| OAuth2MintTokenFlow::MintTokenResult result; |
| result.access_token = kAccessToken; |
| result.time_to_live = base::Seconds(3600); |
| // In these tests, empty `granted_scopes_` means that all requested |
| // scopes should be granted. |
| result.granted_scopes = |
| !granted_scopes_.empty() ? granted_scopes_ : *requested_scopes_; |
| delegate_->OnMintTokenSuccess(result); |
| break; |
| } |
| case MINT_TOKEN_FAILURE: { |
| GoogleServiceAuthError error(GoogleServiceAuthError::CONNECTION_FAILED); |
| delegate_->OnMintTokenFailure(error); |
| break; |
| } |
| case MINT_TOKEN_BAD_CREDENTIALS: { |
| GoogleServiceAuthError error( |
| GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); |
| delegate_->OnMintTokenFailure(error); |
| break; |
| } |
| case MINT_TOKEN_SERVICE_ERROR: { |
| GoogleServiceAuthError error = |
| GoogleServiceAuthError::FromServiceError("invalid_scope"); |
| delegate_->OnMintTokenFailure(error); |
| break; |
| } |
| } |
| } |
| |
| private: |
| ResultType result_; |
| raw_ptr<const std::set<std::string>> requested_scopes_; |
| std::set<std::string> granted_scopes_; |
| raw_ptr<OAuth2MintTokenFlow::Delegate> delegate_; |
| }; |
| |
| std::unique_ptr<net::EmbeddedTestServer> LaunchHttpsServer() { |
| std::unique_ptr<net::EmbeddedTestServer> https_server = |
| std::make_unique<net::EmbeddedTestServer>( |
| net::EmbeddedTestServer::TYPE_HTTPS); |
| https_server->ServeFilesFromSourceDirectory( |
| "chrome/test/data/extensions/api_test/identity"); |
| EXPECT_TRUE(https_server->Start()); |
| |
| return https_server; |
| } |
| |
| scoped_refptr<IdentityLaunchWebAuthFlowFunction> |
| CreateLaunchWebAuthFlowFunction() { |
| scoped_refptr<IdentityLaunchWebAuthFlowFunction> function( |
| new IdentityLaunchWebAuthFlowFunction()); |
| scoped_refptr<const Extension> empty_extension( |
| ExtensionBuilder("Test").Build()); |
| function->set_extension(empty_extension); |
| |
| return function; |
| } |
| |
| // Simulate a redirects to the expected url from the Chrome extension API via |
| // `EvalJS`. |
| // Expecting it to work with |
| // "chrome/test/data/extensions/api_test/identity/consent_page.html" web |
| // page loaded in the `auth_web_contents`. |
| // `url_prefix` is used to determine the redirect link, it will match the |
| // pattern: "https://%s.chromiumapp.org/". |
| void SimulateUrlRedirect(const std::string& url_prefix, |
| content::WebContents* auth_web_contents) { |
| ASSERT_EQ(base::Value(), |
| content::EvalJs(auth_web_contents, |
| "apply_consent(\"" + url_prefix + "\");")); |
| } |
| |
| // Similar to SimulateUrlRedirect, but uses provided url instead of the pattern |
| void SimulateCustomUrlRedirect(const std::string& redirect_url, |
| content::WebContents* auth_web_contents) { |
| ASSERT_EQ(base::Value(), |
| content::EvalJs(auth_web_contents, "window.location.replace(\"" + |
| redirect_url + "\");")); |
| } |
| #endif // BUILDFLAG(ENABLE_EXTENSIONS) |
| |
| } // namespace |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| class FakeGetAuthTokenFunction : public IdentityGetAuthTokenFunction { |
| public: |
| FakeGetAuthTokenFunction() |
| : login_access_token_result_(true), |
| auto_login_access_token_(true), |
| login_ui_result_(true), |
| scope_ui_result_(true), |
| scope_ui_async_(false), |
| scope_ui_failure_(GaiaRemoteConsentFlow::WINDOW_CLOSED), |
| login_ui_shown_(false), |
| scope_ui_shown_(false) {} |
| |
| void set_login_access_token_result(bool result) { |
| login_access_token_result_ = result; |
| } |
| |
| void set_auto_login_access_token(bool automatic) { |
| auto_login_access_token_ = automatic; |
| } |
| |
| void set_login_ui_result(bool result) { login_ui_result_ = result; } |
| |
| void push_mint_token_flow(std::unique_ptr<OAuth2MintTokenFlow> flow) { |
| flow_queue_.push(std::move(flow)); |
| } |
| |
| void push_mint_token_result( |
| TestOAuth2MintTokenFlow::ResultType result_type, |
| const std::set<std::string>& granted_scopes = {}) { |
| // If `granted_scopes` is empty, `TestOAuth2MintTokenFlow` returns the |
| // requested scopes (retrieved from `token_key`) in a mint token success |
| // flow by default. Since the scopes in `token_key` may be populated at a |
| // later time, the requested scopes cannot be immediately copied, so a |
| // pointer is passed instead. |
| const ExtensionTokenKey* token_key = GetExtensionTokenKeyForTest(); |
| push_mint_token_flow(std::make_unique<TestOAuth2MintTokenFlow>( |
| result_type, &token_key->scopes, granted_scopes, this)); |
| } |
| |
| // Sets scope UI to not complete immediately. Call |
| // CompleteOAuthApprovalDialog() or CompleteRemoteConsentDialog() after |
| // |on_scope_ui_shown| is invoked to unblock execution. |
| void set_scope_ui_async(base::OnceClosure on_scope_ui_shown) { |
| scope_ui_async_ = true; |
| on_scope_ui_shown_ = std::move(on_scope_ui_shown); |
| } |
| |
| void set_scope_ui_failure(GaiaRemoteConsentFlow::Failure failure) { |
| scope_ui_result_ = false; |
| scope_ui_failure_ = failure; |
| } |
| |
| void set_remote_consent_gaia_id(const GaiaId& gaia_id) { |
| remote_consent_gaia_id_ = gaia_id; |
| } |
| |
| bool login_ui_shown() const { return login_ui_shown_; } |
| |
| bool scope_ui_shown() const { return scope_ui_shown_; } |
| |
| std::vector<std::string> login_access_tokens() const { |
| return login_access_tokens_; |
| } |
| |
| void StartTokenKeyAccountAccessTokenRequest() override { |
| if (auto_login_access_token_) { |
| std::optional<std::string> access_token("access_token"); |
| GoogleServiceAuthError error = GoogleServiceAuthError::AuthErrorNone(); |
| if (!login_access_token_result_) { |
| access_token = std::nullopt; |
| error = GoogleServiceAuthError( |
| GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); |
| } |
| OnGetAccessTokenComplete(access_token, |
| base::Time::Now() + base::Hours(1LL), error); |
| } else { |
| // Make a request to the IdentityManager. The test now must tell the |
| // service to issue an access token (or an error). |
| IdentityGetAuthTokenFunction::StartTokenKeyAccountAccessTokenRequest(); |
| } |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| void StartDeviceAccessTokenRequest() override { |
| // In these tests requests for the device account just funnel through to |
| // requests for the token key account. |
| StartTokenKeyAccountAccessTokenRequest(); |
| } |
| #endif |
| |
| // Fix auth error on secondary account or add a new account. |
| void FixOrAddSecondaryAccount() { |
| signin::IdentityManager* identity_manager = |
| IdentityManagerFactory::GetForProfile(GetProfile()); |
| std::vector<CoreAccountInfo> accounts = |
| identity_manager->GetAccountsWithRefreshTokens(); |
| CoreAccountId primary_id = |
| identity_manager->GetPrimaryAccountId(signin::ConsentLevel::kSignin); |
| bool fixed_auth_error = false; |
| for (const auto& account_info : accounts) { |
| CoreAccountId account_id = account_info.account_id; |
| if (account_id == primary_id) { |
| continue; |
| } |
| if (identity_manager->HasAccountWithRefreshTokenInPersistentErrorState( |
| account_id)) { |
| identity_manager->GetAccountsMutator()->AddOrUpdateAccount( |
| account_info.gaia, account_info.email, "token", |
| account_info.is_under_advanced_protection, |
| signin_metrics::AccessPoint::kUnknown, |
| signin_metrics::SourceForRefreshTokenOperation::kUnknown); |
| fixed_auth_error = true; |
| } |
| } |
| if (!fixed_auth_error) { |
| if (!identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin)) { |
| // This is required to ensure the refresh token is available before |
| // 'OnPrimaryAccountChanged()` is fired. |
| AccountReconcilor::Lock reconcilor_lock( |
| AccountReconcilorFactory::GetForProfile(GetProfile())); |
| signin::MakeAccountAvailable(identity_manager, "primary@example.com"); |
| signin::SetPrimaryAccount(identity_manager, "primary@example.com", |
| signin::ConsentLevel::kSignin); |
| } else { |
| signin::MakeAccountAvailable(identity_manager, "secondary@example.com"); |
| } |
| } |
| } |
| |
| // Simulate signin through a login prompt. |
| void ShowExtensionLoginPrompt() override { |
| EXPECT_FALSE(login_ui_shown_); |
| login_ui_shown_ = true; |
| if (login_ui_result_) { |
| signin::IdentityManager* identity_manager = |
| IdentityManagerFactory::GetForProfile(GetProfile()); |
| if (IdentityAPI::GetFactoryInstance() |
| ->Get(GetProfile()) |
| ->AreExtensionsRestrictedToPrimaryAccount()) { |
| // Set a primary account. |
| ASSERT_FALSE( |
| identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin)); |
| signin::MakeAccountAvailable(identity_manager, "primary@example.com"); |
| signin::SetPrimaryAccount(identity_manager, "primary@example.com", |
| signin::ConsentLevel::kSignin); |
| } else { |
| FixOrAddSecondaryAccount(); |
| } |
| } else { |
| SigninFailed(); |
| } |
| } |
| |
| void ShowRemoteConsentDialog( |
| const RemoteConsentResolutionData& resolution_data) override { |
| scope_ui_shown_ = true; |
| if (!scope_ui_async_) { |
| CompleteRemoteConsentDialog(); |
| } else { |
| std::move(on_scope_ui_shown_).Run(); |
| } |
| } |
| |
| void CompleteRemoteConsentDialog() { |
| if (scope_ui_result_) { |
| OnGaiaRemoteConsentFlowApproved("fake_consent_result", |
| remote_consent_gaia_id_); |
| } else { |
| OnGaiaRemoteConsentFlowFailed(scope_ui_failure_); |
| } |
| } |
| |
| void StartGaiaRequest(const std::string& login_access_token) override { |
| // Save the login token used in the mint token flow so tests can see |
| // what account was used. |
| login_access_tokens_.push_back(login_access_token); |
| IdentityGetAuthTokenFunction::StartGaiaRequest(login_access_token); |
| } |
| |
| std::unique_ptr<OAuth2MintTokenFlow> CreateMintTokenFlow() override { |
| auto flow = std::move(flow_queue_.front()); |
| flow_queue_.pop(); |
| return flow; |
| } |
| |
| bool enable_granular_permissions() const { |
| return IdentityGetAuthTokenFunction::enable_granular_permissions(); |
| } |
| |
| GaiaId GetSelectedUserId() const { |
| return IdentityGetAuthTokenFunction::GetSelectedUserId(); |
| } |
| |
| private: |
| ~FakeGetAuthTokenFunction() override = default; |
| bool login_access_token_result_; |
| bool auto_login_access_token_; |
| bool login_ui_result_; |
| bool scope_ui_result_; |
| bool scope_ui_async_; |
| base::OnceClosure on_scope_ui_shown_; |
| GaiaRemoteConsentFlow::Failure scope_ui_failure_; |
| bool login_ui_shown_; |
| bool scope_ui_shown_; |
| |
| std::queue<std::unique_ptr<OAuth2MintTokenFlow>> flow_queue_; |
| |
| std::vector<std::string> login_access_tokens_; |
| |
| GaiaId remote_consent_gaia_id_; |
| }; |
| |
| class MockQueuedMintRequest : public IdentityMintRequestQueue::Request { |
| public: |
| MOCK_METHOD1(StartMintToken, void(IdentityMintRequestQueue::MintType)); |
| }; |
| #endif // BUILDFLAG(ENABLE_EXTENSIONS) |
| |
| class IdentityTestWithSignin : public AsyncExtensionBrowserTest { |
| public: |
| void SetUpBrowserContextKeyedServices( |
| content::BrowserContext* context) override { |
| AsyncExtensionBrowserTest::SetUpBrowserContextKeyedServices(context); |
| |
| IdentityTestEnvironmentProfileAdaptor:: |
| SetIdentityTestEnvironmentFactoriesOnBrowserContext(context); |
| |
| ChromeSigninClientFactory::GetInstance()->SetTestingFactory( |
| context, base::BindRepeating(&BuildChromeSigninClientWithURLLoader, |
| &test_url_loader_factory_)); |
| } |
| |
| void SetUpOnMainThread() override { |
| AsyncExtensionBrowserTest::SetUpOnMainThread(); |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| // Fake the network online state so that Gaia requests can come through. |
| InitNetwork(); |
| #endif |
| |
| identity_test_env_profile_adaptor_ = |
| std::make_unique<IdentityTestEnvironmentProfileAdaptor>(profile()); |
| |
| // This test requires these callbacks to be fired on account |
| // update/removal. |
| identity_test_env()->EnableRemovalOfExtendedAccountInfo(); |
| |
| identity_test_env()->SetTestURLLoaderFactory(&test_url_loader_factory_); |
| } |
| |
| void TearDownOnMainThread() override { |
| // Must be destroyed before the Profile. |
| identity_test_env_profile_adaptor_.reset(); |
| } |
| |
| protected: |
| // Signs in and returns the account ID of the primary account. |
| CoreAccountId SignIn(const std::string& email) { |
| auto account_info = identity_test_env()->MakePrimaryAccountAvailable( |
| email, signin::ConsentLevel::kSignin); |
| EXPECT_TRUE(identity_test_env()->identity_manager()->HasPrimaryAccount( |
| signin::ConsentLevel::kSignin)); |
| return account_info.account_id; |
| } |
| |
| IdentityAPI* id_api() { |
| return IdentityAPI::GetFactoryInstance()->Get(profile()); |
| } |
| |
| signin::IdentityTestEnvironment* identity_test_env() { |
| return identity_test_env_profile_adaptor_->identity_test_env(); |
| } |
| |
| network::TestURLLoaderFactory test_url_loader_factory_; |
| |
| std::unique_ptr<IdentityTestEnvironmentProfileAdaptor> |
| identity_test_env_profile_adaptor_; |
| }; |
| |
| class IdentityGetAccountsFunctionTest : public IdentityTestWithSignin { |
| public: |
| IdentityGetAccountsFunctionTest() = default; |
| |
| protected: |
| testing::AssertionResult ExpectGetAccounts( |
| const std::vector<std::string>& gaia_ids) { |
| scoped_refptr<IdentityGetAccountsFunction> func( |
| new IdentityGetAccountsFunction); |
| func->set_extension( |
| ExtensionBuilder("Test").SetID(kExtensionId).Build().get()); |
| if (!utils::RunFunction(func.get(), std::string("[]"), profile(), |
| api_test_utils::FunctionMode::kNone)) { |
| return GenerateFailureResult(gaia_ids, nullptr) |
| << "getAccounts did not return a result."; |
| } |
| const base::Value::List* callback_arguments_list = |
| func->GetResultListForTest(); |
| if (!callback_arguments_list) { |
| return GenerateFailureResult(gaia_ids, nullptr) << "NULL result"; |
| } |
| |
| if (callback_arguments_list->size() != 1u) { |
| return GenerateFailureResult(gaia_ids, nullptr) |
| << "Expected 1 argument but got " |
| << callback_arguments_list->size(); |
| } |
| |
| if (!(*callback_arguments_list)[0].is_list()) { |
| GenerateFailureResult(gaia_ids, nullptr) << "Result was not an array"; |
| } |
| const base::Value::List& results = (*callback_arguments_list)[0].GetList(); |
| |
| std::vector<std::string> result_ids; |
| for (const base::Value& item : results) { |
| std::optional<api::identity::AccountInfo> info = |
| api::identity::AccountInfo::FromValue(item); |
| if (info) { |
| result_ids.push_back(info->id); |
| } else { |
| return GenerateFailureResult(gaia_ids, &results); |
| } |
| } |
| |
| if (result_ids != gaia_ids) { |
| return GenerateFailureResult(gaia_ids, &results); |
| } |
| |
| return testing::AssertionResult(true); |
| } |
| |
| testing::AssertionResult GenerateFailureResult( |
| const ::std::vector<std::string>& gaia_ids, |
| const base::Value::List* results) { |
| testing::Message msg("Expected: "); |
| for (const std::string& gaia_id : gaia_ids) { |
| msg << gaia_id << " "; |
| } |
| msg << "Actual: "; |
| if (!results) { |
| msg << "NULL"; |
| } else { |
| for (const auto& result : *results) { |
| std::optional<api::identity::AccountInfo> info = |
| api::identity::AccountInfo::FromValue(result); |
| if (info) { |
| msg << info->id << " "; |
| } else { |
| msg << result << "<-" << result.type() << " "; |
| } |
| } |
| } |
| |
| return testing::AssertionFailure(msg); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(IdentityGetAccountsFunctionTest, AllAccountsOn) { |
| EXPECT_FALSE(id_api()->AreExtensionsRestrictedToPrimaryAccount()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(IdentityGetAccountsFunctionTest, NoneSignedIn) { |
| EXPECT_TRUE(ExpectGetAccounts({})); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(IdentityGetAccountsFunctionTest, NoPrimaryAccount) { |
| identity_test_env()->MakeAccountAvailable("secondary@example.com"); |
| EXPECT_TRUE(ExpectGetAccounts({})); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(IdentityGetAccountsFunctionTest, |
| PrimaryAccountHasInvalidRefreshToken) { |
| CoreAccountId primary_account_id = SignIn("primary@example.com"); |
| identity_test_env()->SetInvalidRefreshTokenForPrimaryAccount(); |
| EXPECT_TRUE(ExpectGetAccounts({"gaia_id_for_primary_example.com"})); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(IdentityGetAccountsFunctionTest, |
| PrimaryAccountSignedIn) { |
| SignIn("primary@example.com"); |
| EXPECT_TRUE(ExpectGetAccounts({"gaia_id_for_primary_example.com"})); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(IdentityGetAccountsFunctionTest, TwoAccountsSignedIn) { |
| SignIn("primary@example.com"); |
| identity_test_env()->MakeAccountAvailable("secondary@example.com"); |
| if (!id_api()->AreExtensionsRestrictedToPrimaryAccount()) { |
| EXPECT_TRUE(ExpectGetAccounts({"gaia_id_for_primary_example.com", |
| "gaia_id_for_secondary_example.com"})); |
| } else { |
| EXPECT_TRUE(ExpectGetAccounts({"gaia_id_for_primary_example.com"})); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(IdentityGetAccountsFunctionTest, SignedInOnTheWeb) { |
| identity_test_env()->MakeAccountAvailable("secondary@example.com"); |
| EXPECT_TRUE(ExpectGetAccounts({})); |
| |
| SignIn("primary@example.com"); |
| EXPECT_TRUE(ExpectGetAccounts({"gaia_id_for_primary_example.com", |
| "gaia_id_for_secondary_example.com"})); |
| } |
| |
| class IdentityGetProfileUserInfoFunctionTest : public IdentityTestWithSignin { |
| protected: |
| std::optional<api::identity::ProfileUserInfo> RunGetProfileUserInfo() { |
| scoped_refptr<IdentityGetProfileUserInfoFunction> func( |
| new IdentityGetProfileUserInfoFunction); |
| func->set_extension( |
| ExtensionBuilder("Test").SetID(kExtensionId).Build().get()); |
| std::optional<base::Value> value = |
| utils::RunFunctionAndReturnSingleResult(func.get(), "[]", profile()); |
| return api::identity::ProfileUserInfo::FromValue(*value); |
| } |
| |
| std::optional<api::identity::ProfileUserInfo> |
| RunGetProfileUserInfoWithEmail() { |
| scoped_refptr<IdentityGetProfileUserInfoFunction> func( |
| new IdentityGetProfileUserInfoFunction); |
| func->set_extension(CreateExtensionWithEmailPermission()); |
| std::optional<base::Value> value = |
| utils::RunFunctionAndReturnSingleResult(func.get(), "[]", profile()); |
| return api::identity::ProfileUserInfo::FromValue(*value); |
| } |
| |
| scoped_refptr<const Extension> CreateExtensionWithEmailPermission() { |
| return ExtensionBuilder("Test").AddAPIPermission("identity.email").Build(); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(IdentityGetProfileUserInfoFunctionTest, NotSignedIn) { |
| std::optional<api::identity::ProfileUserInfo> info = |
| RunGetProfileUserInfoWithEmail(); |
| EXPECT_TRUE(info->email.empty()); |
| EXPECT_TRUE(info->id.empty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(IdentityGetProfileUserInfoFunctionTest, ExtensionSync) { |
| constexpr char kEmail[] = "president@example.com"; |
| #if BUILDFLAG(IS_CHROMEOS) |
| identity_test_env()->MakePrimaryAccountAvailable(kEmail, |
| signin::ConsentLevel::kSync); |
| #else |
| SignIn(kEmail); |
| SyncServiceFactory::GetForProfile(profile()) |
| ->GetUserSettings() |
| ->SetSelectedTypes( |
| /*sync_everything=*/false, |
| /*types=*/{syncer::UserSelectableType::kExtensions}); |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| std::optional<api::identity::ProfileUserInfo> info = |
| RunGetProfileUserInfoWithEmail(); |
| EXPECT_EQ(kEmail, info->email); |
| EXPECT_EQ("gaia_id_for_president_example.com", info->id); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(IdentityGetProfileUserInfoFunctionTest, |
| SignedInUnconsented) { |
| identity_test_env()->MakePrimaryAccountAvailable( |
| "test@example.com", signin::ConsentLevel::kSignin); |
| std::optional<api::identity::ProfileUserInfo> info = |
| RunGetProfileUserInfoWithEmail(); |
| // The account is only returned if extensions are syncing. Whether or not |
| // extensions sync upon sign-in depends on |
| // `kReplaceSyncPromosWithSignInPromos`. |
| if (base::FeatureList::IsEnabled( |
| syncer::kReplaceSyncPromosWithSignInPromos)) { |
| EXPECT_EQ("test@example.com", info->email); |
| EXPECT_EQ("gaia_id_for_test_example.com", info->id); |
| } else { |
| EXPECT_TRUE(info->email.empty()); |
| EXPECT_TRUE(info->id.empty()); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(IdentityGetProfileUserInfoFunctionTest, |
| NotSignedInNoEmail) { |
| std::optional<api::identity::ProfileUserInfo> info = RunGetProfileUserInfo(); |
| EXPECT_TRUE(info->email.empty()); |
| EXPECT_TRUE(info->id.empty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(IdentityGetProfileUserInfoFunctionTest, |
| SignedInNoEmail) { |
| SignIn("president@example.com"); |
| std::optional<api::identity::ProfileUserInfo> info = RunGetProfileUserInfo(); |
| EXPECT_TRUE(info->email.empty()); |
| EXPECT_TRUE(info->id.empty()); |
| } |
| |
| class IdentityGetProfileUserInfoFunctionNoSyncServiceTest |
| : public IdentityGetProfileUserInfoFunctionTest { |
| public: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| IdentityGetProfileUserInfoFunctionTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitch(syncer::kDisableSync); |
| } |
| |
| void SetUpBrowserContextKeyedServices( |
| content::BrowserContext* context) override { |
| IdentityGetProfileUserInfoFunctionTest::SetUpBrowserContextKeyedServices( |
| context); |
| } |
| }; |
| |
| // Regression test for crbug.com/433499860. |
| IN_PROC_BROWSER_TEST_F(IdentityGetProfileUserInfoFunctionNoSyncServiceTest, |
| NoCrash) { |
| // SyncService is not created. |
| ASSERT_EQ(nullptr, SyncServiceFactory::GetForProfile(profile())); |
| |
| identity_test_env()->MakePrimaryAccountAvailable( |
| "test@example.com", signin::ConsentLevel::kSignin); |
| |
| // This should not crash. |
| std::optional<api::identity::ProfileUserInfo> profile_user_info = |
| RunGetProfileUserInfoWithEmail(); |
| EXPECT_TRUE(profile_user_info->email.empty()); |
| EXPECT_TRUE(profile_user_info->id.empty()); |
| } |
| |
| class IdentityGetProfileUserInfoFunctionTestWithAccountStatusParam |
| : public IdentityGetProfileUserInfoFunctionTest, |
| public ::testing::WithParamInterface<std::string> { |
| protected: |
| std::optional<api::identity::ProfileUserInfo> |
| RunGetProfileUserInfoWithAccountStatus() { |
| scoped_refptr<IdentityGetProfileUserInfoFunction> func( |
| new IdentityGetProfileUserInfoFunction); |
| func->set_extension(CreateExtensionWithEmailPermission()); |
| std::string args = base::StringPrintf(R"([{"accountStatus": "%s"}])", |
| account_status().c_str()); |
| std::optional<base::Value> value = |
| utils::RunFunctionAndReturnSingleResult(func.get(), args, profile()); |
| return api::identity::ProfileUserInfo::FromValue(*value); |
| } |
| |
| std::string account_status() { return GetParam(); } |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| IdentityGetProfileUserInfoFunctionTestWithAccountStatusParam, |
| ::testing::Values("SYNC", "ANY")); |
| |
| IN_PROC_BROWSER_TEST_P( |
| IdentityGetProfileUserInfoFunctionTestWithAccountStatusParam, |
| NotSignedIn) { |
| std::optional<api::identity::ProfileUserInfo> info = |
| RunGetProfileUserInfoWithAccountStatus(); |
| EXPECT_TRUE(info->email.empty()); |
| EXPECT_TRUE(info->id.empty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| IdentityGetProfileUserInfoFunctionTestWithAccountStatusParam, |
| ExtensionSync) { |
| constexpr char kEmail[] = "test@example.com"; |
| #if BUILDFLAG(IS_CHROMEOS) |
| identity_test_env()->MakePrimaryAccountAvailable(kEmail, |
| signin::ConsentLevel::kSync); |
| #else |
| SignIn(kEmail); |
| SyncServiceFactory::GetForProfile(profile()) |
| ->GetUserSettings() |
| ->SetSelectedTypes( |
| /*sync_everything=*/false, |
| /*types=*/{syncer::UserSelectableType::kExtensions}); |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| std::optional<api::identity::ProfileUserInfo> info = |
| RunGetProfileUserInfoWithAccountStatus(); |
| EXPECT_EQ(kEmail, info->email); |
| EXPECT_EQ("gaia_id_for_test_example.com", info->id); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| IdentityGetProfileUserInfoFunctionTestWithAccountStatusParam, |
| SignedInUnconsented) { |
| identity_test_env()->MakePrimaryAccountAvailable( |
| "test@example.com", signin::ConsentLevel::kSignin); |
| std::optional<api::identity::ProfileUserInfo> info = |
| RunGetProfileUserInfoWithAccountStatus(); |
| |
| // The unconsented (Sync off) primary account is returned conditionally, |
| // depending on the accountStatus parameter. |
| if (account_status() == "ANY") { |
| EXPECT_EQ("test@example.com", info->email); |
| EXPECT_EQ("gaia_id_for_test_example.com", info->id); |
| } else { |
| // accountStatus is SYNC or unspecified. In this case, the account is only |
| // returned if extensions are syncing. Whether or not extensions sync upon |
| // sign-in depends on `syncer::kReplaceSyncPromosWithSignInPromos`. |
| if (base::FeatureList::IsEnabled( |
| syncer::kReplaceSyncPromosWithSignInPromos)) { |
| EXPECT_EQ("test@example.com", info->email); |
| EXPECT_EQ("gaia_id_for_test_example.com", info->id); |
| } else { |
| EXPECT_TRUE(info->email.empty()); |
| EXPECT_TRUE(info->id.empty()); |
| } |
| } |
| } |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| class GetAuthTokenFunctionTest |
| : public IdentityTestWithSignin, |
| public signin::IdentityManager::DiagnosticsObserver { |
| public: |
| GetAuthTokenFunctionTest() { SetUserGestureEnabled(true); } |
| |
| ~GetAuthTokenFunctionTest() override = default; |
| |
| std::string IssueLoginAccessTokenForAccount(const CoreAccountId& account_id) { |
| std::string access_token = "access_token-" + account_id.ToString(); |
| identity_test_env() |
| ->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( |
| account_id, access_token, base::Time::Now() + base::Seconds(3600)); |
| return access_token; |
| } |
| |
| protected: |
| enum OAuth2Fields { NONE = 0, CLIENT_ID = 1, SCOPES = 2, AS_COMPONENT = 4 }; |
| |
| void SetUpOnMainThread() override { |
| IdentityTestWithSignin::SetUpOnMainThread(); |
| identity_test_env()->identity_manager()->AddDiagnosticsObserver(this); |
| signin::SetListAccountsResponseNoAccounts(&test_url_loader_factory_); |
| } |
| |
| void TearDownOnMainThread() override { |
| if (!identity_test_env_profile_adaptor_) { |
| // In some tests, we have released the profile early and removed the |
| // observer, so do nothing |
| return; |
| } |
| identity_test_env()->identity_manager()->RemoveDiagnosticsObserver(this); |
| IdentityTestWithSignin::TearDownOnMainThread(); |
| } |
| |
| // Helper to create an extension with specific OAuth2Info fields set. |
| // |fields_to_set| should be computed by using fields of Oauth2Fields enum. |
| const Extension* CreateExtension(int fields_to_set) { |
| const Extension* ext = nullptr; |
| base::FilePath manifest_path = |
| test_data_dir_.AppendASCII("api_test/identity/oauth2"); |
| base::FilePath component_manifest_path = |
| test_data_dir_.AppendASCII("api_test/identity/component_oauth2"); |
| if ((fields_to_set & AS_COMPONENT) == 0) { |
| ext = LoadExtension(manifest_path); |
| } else { |
| ext = LoadExtensionAsComponent(component_manifest_path); |
| } |
| |
| if (!ext) { |
| ADD_FAILURE() << "Cannot create extension"; |
| return nullptr; |
| } |
| |
| OAuth2Info& oauth2_info = |
| const_cast<OAuth2Info&>(OAuth2ManifestHandler::GetOAuth2Info(*ext)); |
| if ((fields_to_set & CLIENT_ID) != 0) { |
| oauth2_info.client_id = "client1"; |
| } |
| if ((fields_to_set & SCOPES) != 0) { |
| oauth2_info.scopes.push_back("scope1"); |
| oauth2_info.scopes.push_back("scope2"); |
| } |
| |
| extension_id_ = ext->id(); |
| oauth_scopes_ = std::set<std::string>(oauth2_info.scopes.begin(), |
| oauth2_info.scopes.end()); |
| return ext; |
| } |
| |
| CoreAccountInfo GetPrimaryAccountInfo() { |
| return identity_test_env()->identity_manager()->GetPrimaryAccountInfo( |
| signin::ConsentLevel::kSignin); |
| } |
| |
| CoreAccountId GetPrimaryAccountId() { |
| return identity_test_env()->identity_manager()->GetPrimaryAccountId( |
| signin::ConsentLevel::kSignin); |
| } |
| |
| IdentityTokenCacheValue CreateToken(const std::string& token, |
| base::TimeDelta time_to_live) { |
| return IdentityTokenCacheValue::CreateToken(token, oauth_scopes_, |
| time_to_live); |
| } |
| |
| // Sets a cached token for the primary account. |
| void SetCachedToken(const IdentityTokenCacheValue& token_data) { |
| SetCachedTokenForAccount(GetPrimaryAccountInfo(), token_data); |
| } |
| |
| void SetCachedTokenForAccount(const CoreAccountInfo account_info, |
| const IdentityTokenCacheValue& token_data) { |
| ExtensionTokenKey key(extension_id_, account_info, oauth_scopes_); |
| id_api()->token_cache()->SetToken(key, token_data); |
| } |
| |
| void SetCachedGaiaId(const GaiaId& gaia_id) { |
| id_api()->SetGaiaIdForExtension(extension_id_, gaia_id); |
| } |
| |
| const IdentityTokenCacheValue& GetCachedToken( |
| const CoreAccountInfo& account_info, |
| const std::set<std::string>& scopes) { |
| ExtensionTokenKey key( |
| extension_id_, |
| account_info.IsEmpty() ? GetPrimaryAccountInfo() : account_info, |
| scopes); |
| return id_api()->token_cache()->GetToken(key); |
| } |
| |
| const IdentityTokenCacheValue& GetCachedToken( |
| const CoreAccountInfo& account_info) { |
| return GetCachedToken(account_info, oauth_scopes_); |
| } |
| |
| std::optional<GaiaId> GetCachedGaiaId() { |
| return id_api()->GetGaiaIdForExtension(extension_id_); |
| } |
| |
| void QueueRequestStart(IdentityMintRequestQueue::MintType type, |
| IdentityMintRequestQueue::Request* request) { |
| ExtensionTokenKey key(extension_id_, GetPrimaryAccountInfo(), |
| oauth_scopes_); |
| id_api()->mint_queue()->RequestStart(type, key, request); |
| } |
| |
| void QueueRequestComplete(IdentityMintRequestQueue::MintType type, |
| IdentityMintRequestQueue::Request* request) { |
| ExtensionTokenKey key(extension_id_, GetPrimaryAccountInfo(), |
| oauth_scopes_); |
| id_api()->mint_queue()->RequestComplete(type, key, request); |
| } |
| |
| base::HistogramTester* histogram_tester() { return &histogram_tester_; } |
| |
| base::OnceClosure on_access_token_requested_; |
| |
| void RunGetAuthTokenFunction(ExtensionFunction* function, |
| const std::string& args, |
| Browser* browser, |
| std::string* access_token, |
| std::set<std::string>* granted_scopes) { |
| RunGetAuthTokenFunction(function, args, browser->profile(), access_token, |
| granted_scopes); |
| } |
| |
| void RunGetAuthTokenFunction(ExtensionFunction* function, |
| const std::string& args, |
| Profile* profile, |
| std::string* access_token, |
| std::set<std::string>* granted_scopes) { |
| std::optional<base::Value> result_value = |
| utils::RunFunctionAndReturnSingleResult(function, args, profile); |
| |
| ASSERT_TRUE(result_value); |
| std::optional<api::identity::GetAuthTokenResult> result = |
| api::identity::GetAuthTokenResult::FromValue(*result_value); |
| ASSERT_TRUE(result); |
| |
| EXPECT_TRUE(result->token); |
| *access_token = *result->token; |
| EXPECT_TRUE(result->granted_scopes); |
| std::set<std::string> granted_scopes_map(result->granted_scopes->begin(), |
| result->granted_scopes->end()); |
| *granted_scopes = std::move(granted_scopes_map); |
| } |
| |
| void WaitForGetAuthTokenResults( |
| ExtensionFunction* function, |
| std::string* access_token, |
| std::set<std::string>* granted_scopes, |
| AsyncFunctionRunner* function_runner = nullptr) { |
| base::Value result_value; |
| if (function_runner == nullptr) { |
| WaitForOneResult(function, &result_value); |
| } else { |
| function_runner->WaitForOneResult(function, &result_value); |
| } |
| std::optional<api::identity::GetAuthTokenResult> result = |
| api::identity::GetAuthTokenResult::FromValue(result_value); |
| ASSERT_TRUE(result); |
| |
| ASSERT_TRUE(result->token); |
| *access_token = *result->token; |
| ASSERT_TRUE(result->granted_scopes); |
| std::set<std::string> granted_scopes_map(result->granted_scopes->begin(), |
| result->granted_scopes->end()); |
| *granted_scopes = std::move(granted_scopes_map); |
| } |
| |
| void SetUserGestureEnabled(bool enabled) { |
| if (enabled) { |
| if (!user_gesture_) { |
| user_gesture_ = |
| std::make_unique<ExtensionFunction::ScopedUserGestureForTests>(); |
| } |
| return; |
| } |
| user_gesture_.reset(); |
| } |
| |
| private: |
| // signin::IdentityManager::DiagnosticsObserver: |
| void OnAccessTokenRequested(const CoreAccountId& account_id, |
| const std::string& consumer_id, |
| const signin::ScopeSet& scopes) override { |
| if (on_access_token_requested_.is_null()) { |
| return; |
| } |
| std::move(on_access_token_requested_).Run(); |
| } |
| |
| base::HistogramTester histogram_tester_; |
| ExtensionId extension_id_; |
| std::set<std::string> oauth_scopes_; |
| std::unique_ptr<ExtensionFunction::ScopedUserGestureForTests> user_gesture_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, NoClientId) { |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(SCOPES)); |
| std::string error = |
| utils::RunFunctionAndReturnError(func.get(), "[{}]", profile()); |
| EXPECT_EQ(std::string(errors::kInvalidClientId), error); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kInvalidClientId, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, NoScopes) { |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID)); |
| std::string error = |
| utils::RunFunctionAndReturnError(func.get(), "[{}]", profile()); |
| EXPECT_EQ(std::string(errors::kInvalidScopes), error); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kEmptyScopes, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, NonInteractiveNotSignedIn) { |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| std::string error = |
| utils::RunFunctionAndReturnError(func.get(), "[{}]", profile()); |
| EXPECT_EQ(std::string(errors::kUserNotSignedIn), error); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kUserNotSignedIn, 1); |
| } |
| |
| // The signin flow is simply not used on ChromeOS. |
| #if !BUILDFLAG(IS_CHROMEOS) |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| InteractiveNotSignedInShowSigninOnlyOnce) { |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_login_ui_result(false); |
| std::string error = utils::RunFunctionAndReturnError( |
| func.get(), "[{\"interactive\": true}]", profile()); |
| EXPECT_EQ(std::string(errors::kUserNotSignedIn), error); |
| EXPECT_TRUE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kSignInFailed, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| PRE_InteractiveNotSignedAndSigninNotAllowed) { |
| // kSigninAllowed cannot be set after the profile creation. Use |
| // kSigninAllowedOnNextStartup instead. |
| profile()->GetPrefs()->SetBoolean(prefs::kSigninAllowedOnNextStartup, false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| InteractiveNotSignedAndSigninNotAllowed) { |
| ASSERT_FALSE(profile()->GetPrefs()->GetBoolean(prefs::kSigninAllowed)); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_login_ui_result(false); |
| std::string error = utils::RunFunctionAndReturnError( |
| func.get(), "[{\"interactive\": true}]", profile()); |
| EXPECT_EQ(std::string(errors::kBrowserSigninNotAllowed), error); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kBrowserSigninNotAllowed, 1); |
| } |
| #endif // !BUILDFLAG(IS_CHROMEOS) |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, NonInteractiveMintFailure) { |
| SignIn("primary@example.com"); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_FAILURE); |
| std::string error = |
| utils::RunFunctionAndReturnError(func.get(), "[{}]", profile()); |
| EXPECT_TRUE(base::StartsWith(error, errors::kAuthFailure, |
| base::CompareCase::INSENSITIVE_ASCII)); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kMintTokenAuthFailure, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| NonInteractiveLoginAccessTokenFailure) { |
| SignIn("primary@example.com"); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_login_access_token_result(false); |
| std::string error = |
| utils::RunFunctionAndReturnError(func.get(), "[{}]", profile()); |
| EXPECT_TRUE(base::StartsWith(error, errors::kAuthFailure, |
| base::CompareCase::INSENSITIVE_ASCII)); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kGetAccessTokenAuthFailure, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| NonInteractiveRemoteConsentSuccess) { |
| SignIn("primary@example.com"); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(extension.get()); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::REMOTE_CONSENT_SUCCESS); |
| std::string error = |
| utils::RunFunctionAndReturnError(func.get(), "[{}]", profile()); |
| EXPECT_EQ(std::string(errors::kNoGrant), error); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_REMOTE_CONSENT, |
| GetCachedToken(CoreAccountInfo()).status()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kGaiaConsentInteractionRequired, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| NonInteractiveMintBadCredentials) { |
| SignIn("primary@example.com"); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->push_mint_token_result( |
| TestOAuth2MintTokenFlow::MINT_TOKEN_BAD_CREDENTIALS); |
| std::string error = |
| utils::RunFunctionAndReturnError(func.get(), "[{}]", profile()); |
| EXPECT_TRUE(base::StartsWith(error, errors::kAuthFailure, |
| base::CompareCase::INSENSITIVE_ASCII)); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kMintTokenAuthFailure, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| NonInteractiveMintServiceError) { |
| SignIn("primary@example.com"); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->push_mint_token_result( |
| TestOAuth2MintTokenFlow::MINT_TOKEN_SERVICE_ERROR); |
| std::string error = |
| utils::RunFunctionAndReturnError(func.get(), "[{}]", profile()); |
| EXPECT_TRUE(base::StartsWith(error, errors::kAuthFailure, |
| base::CompareCase::INSENSITIVE_ASCII)); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kMintTokenAuthFailure, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| InteractiveMintServiceErrorAccountValid) { |
| SignIn("primary@example.com"); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->push_mint_token_result( |
| TestOAuth2MintTokenFlow::MINT_TOKEN_SERVICE_ERROR); |
| std::string error = utils::RunFunctionAndReturnError( |
| func.get(), "[{\"interactive\": true}]", profile()); |
| EXPECT_TRUE(base::StartsWith(error, errors::kAuthFailure, |
| base::CompareCase::INSENSITIVE_ASCII)); |
| |
| // The login UI should not have been shown, as the user's primary account is |
| // in a valid state. |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kMintTokenAuthFailure, 1); |
| } |
| |
| // The signin flow is simply not used on Ash. |
| #if !BUILDFLAG(IS_CHROMEOS) |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| InteractiveMintServiceErrorShowSigninOnlyOnce) { |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_login_ui_result(true); |
| func->push_mint_token_result( |
| TestOAuth2MintTokenFlow::MINT_TOKEN_SERVICE_ERROR); |
| |
| // The function should complete with an error, showing the signin UI only |
| // once for the initial signin. |
| std::string error = utils::RunFunctionAndReturnError( |
| func.get(), "[{\"interactive\": true}]", profile()); |
| EXPECT_TRUE(base::StartsWith(error, errors::kAuthFailure, |
| base::CompareCase::INSENSITIVE_ASCII)); |
| EXPECT_TRUE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kMintTokenAuthFailure, 1); |
| } |
| #endif |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, NonInteractiveSuccess) { |
| SignIn("primary@example.com"); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_extension(extension.get()); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| RunGetAuthTokenFunction(func.get(), "[{}]", browser(), &access_token, |
| &granted_scopes); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| EXPECT_EQ(func->GetExtensionTokenKeyForTest()->scopes, granted_scopes); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN, |
| GetCachedToken(CoreAccountInfo()).status()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| NonInteractiveSuccessWaitForRefreshTokensLoaded) { |
| SignIn("primary@example.com"); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_extension(extension.get()); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| |
| identity_test_env()->ResetToAccountsNotYetLoadedFromDiskState(); |
| RunFunctionAsync(func.get(), "[{\"interactive\": true}]"); |
| |
| // Allow the function to start asynchronously. |
| base::RunLoop().RunUntilIdle(); |
| identity_test_env()->ReloadAccountsFromDisk(); |
| |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| WaitForGetAuthTokenResults(func.get(), &access_token, &granted_scopes); |
| EXPECT_EQ(access_token, kAccessToken); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, InteractiveLoginCanceled) { |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_login_ui_result(false); |
| std::string error = utils::RunFunctionAndReturnError( |
| func.get(), "[{\"interactive\": true}]", profile()); |
| EXPECT_EQ(std::string(errors::kUserNotSignedIn), error); |
| // Ash does not support the interactive login flow, so the login UI will never |
| // be shown on that platform. |
| #if !BUILDFLAG(IS_CHROMEOS) |
| EXPECT_TRUE(func->login_ui_shown()); |
| #endif |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kSignInFailed, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| InteractiveMintBadCredentialsAccountValid) { |
| SignIn("primary@example.com"); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->push_mint_token_result( |
| TestOAuth2MintTokenFlow::MINT_TOKEN_BAD_CREDENTIALS); |
| std::string error = utils::RunFunctionAndReturnError( |
| func.get(), "[{\"interactive\": true}]", profile()); |
| // The login UI should not be shown as the account is in a valid state. |
| EXPECT_TRUE(base::StartsWith(error, errors::kAuthFailure, |
| base::CompareCase::INSENSITIVE_ASCII)); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kMintTokenAuthFailure, 1); |
| } |
| |
| // The interactive login flow is always short-circuited out with failure on |
| // Ash, so the tests of the interactive login flow being successful are not |
| // relevant on that platform. |
| #if !BUILDFLAG(IS_CHROMEOS) |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| InteractiveLoginSuccessMintFailure) { |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_login_ui_result(true); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_FAILURE); |
| std::string error = utils::RunFunctionAndReturnError( |
| func.get(), "[{\"interactive\": true}]", profile()); |
| EXPECT_TRUE(base::StartsWith(error, errors::kAuthFailure, |
| base::CompareCase::INSENSITIVE_ASCII)); |
| EXPECT_TRUE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kMintTokenAuthFailure, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| InteractiveLoginSuccessMintBadCredentials) { |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_login_ui_result(true); |
| func->push_mint_token_result( |
| TestOAuth2MintTokenFlow::MINT_TOKEN_BAD_CREDENTIALS); |
| std::string error = utils::RunFunctionAndReturnError( |
| func.get(), "[{\"interactive\": true}]", profile()); |
| EXPECT_TRUE(base::StartsWith(error, errors::kAuthFailure, |
| base::CompareCase::INSENSITIVE_ASCII)); |
| EXPECT_TRUE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kMintTokenAuthFailure, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| InteractiveLoginSuccessLoginAccessTokenFailure) { |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_login_ui_result(true); |
| func->set_login_access_token_result(false); |
| std::string error = utils::RunFunctionAndReturnError( |
| func.get(), "[{\"interactive\": true}]", profile()); |
| EXPECT_TRUE(base::StartsWith(error, errors::kAuthFailure, |
| base::CompareCase::INSENSITIVE_ASCII)); |
| EXPECT_TRUE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kGetAccessTokenAuthFailure, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| InteractiveLoginSuccessMintSuccess) { |
| // TODO(courage): verify that account_id in token service requests |
| // is correct once manual token minting for tests is implemented. |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_login_ui_result(true); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| RunGetAuthTokenFunction(func.get(), "[{\"interactive\": true}]", browser(), |
| &access_token, &granted_scopes); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| EXPECT_EQ(func->GetExtensionTokenKeyForTest()->scopes, granted_scopes); |
| EXPECT_TRUE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| } |
| |
| #if BUILDFLAG(ENABLE_DICE_SUPPORT) |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, SignedInWebOnlyAcceptPrompt) { |
| identity_test_env()->MakeAccountAvailable("account@gmail.com", |
| {.set_cookie = true}); |
| ASSERT_FALSE(identity_test_env()->identity_manager()->HasPrimaryAccount( |
| signin::ConsentLevel::kSignin)); |
| ASSERT_FALSE(identity_test_env() |
| ->identity_manager() |
| ->GetAccountsWithRefreshTokens() |
| .empty()); |
| |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(extension.get()); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| |
| views::NamedWidgetShownWaiter widget_waiter( |
| views::test::AnyWidgetTestPasskey{}, |
| "ChromeSigninChoiceForExtensionsPrompt"); |
| |
| RunFunctionAsync(func.get(), "[{\"interactive\": true}]"); |
| views::Widget* confirmation_prompt = widget_waiter.WaitIfNeededAndGet(); |
| views::DialogDelegate* dialog_delegate = |
| confirmation_prompt->widget_delegate()->AsDialogDelegate(); |
| ASSERT_TRUE(dialog_delegate); |
| dialog_delegate->AcceptDialog(); |
| |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| WaitForGetAuthTokenResults(func.get(), &access_token, &granted_scopes); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| EXPECT_EQ(func->GetExtensionTokenKeyForTest()->scopes, granted_scopes); |
| |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, SignedInWebOnlyDeclinePrompt) { |
| identity_test_env()->MakeAccountAvailable("account@gmail.com", |
| {.set_cookie = true}); |
| ASSERT_FALSE(identity_test_env()->identity_manager()->HasPrimaryAccount( |
| signin::ConsentLevel::kSignin)); |
| ASSERT_FALSE(identity_test_env() |
| ->identity_manager() |
| ->GetAccountsWithRefreshTokens() |
| .empty()); |
| |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(extension.get()); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| |
| views::NamedWidgetShownWaiter widget_waiter( |
| views::test::AnyWidgetTestPasskey{}, |
| "ChromeSigninChoiceForExtensionsPrompt"); |
| |
| RunFunctionAsync(func.get(), "[{\"interactive\": true}]"); |
| views::Widget* confirmation_prompt = widget_waiter.WaitIfNeededAndGet(); |
| views::DialogDelegate* dialog_delegate = |
| confirmation_prompt->widget_delegate()->AsDialogDelegate(); |
| ASSERT_TRUE(dialog_delegate); |
| dialog_delegate->CancelDialog(); |
| |
| EXPECT_EQ(std::string(errors::kUserNotSignedIn), WaitForError(func.get())); |
| |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kSignInFailed, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| SignedInWebOnlyAcceptPromptMultipleFunctions) { |
| identity_test_env()->MakeAccountAvailable("account@gmail.com", |
| {.set_cookie = true}); |
| ASSERT_FALSE(identity_test_env()->identity_manager()->HasPrimaryAccount( |
| signin::ConsentLevel::kSignin)); |
| ASSERT_FALSE(identity_test_env() |
| ->identity_manager() |
| ->GetAccountsWithRefreshTokens() |
| .empty()); |
| |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| scoped_refptr<FakeGetAuthTokenFunction> func1(new FakeGetAuthTokenFunction()); |
| func1->set_extension(extension.get()); |
| func1->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| |
| scoped_refptr<FakeGetAuthTokenFunction> func2(new FakeGetAuthTokenFunction()); |
| scoped_refptr<const Extension> extension2( |
| CreateExtension(CLIENT_ID | SCOPES)); |
| func2->set_extension(extension2.get()); |
| func2->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| |
| AsyncFunctionRunner func1_runner; |
| func1_runner.RunFunctionAsync(func1.get(), "[{\"interactive\": true}]", |
| profile()); |
| |
| AsyncFunctionRunner func2_runner; |
| func2_runner.RunFunctionAsync(func2.get(), "[{\"interactive\": true}]", |
| profile()); |
| |
| views::NamedWidgetShownWaiter widget_waiter( |
| views::test::AnyWidgetTestPasskey{}, |
| "ChromeSigninChoiceForExtensionsPrompt"); |
| |
| views::Widget* confirmation_prompt = widget_waiter.WaitIfNeededAndGet(); |
| views::DialogDelegate* dialog_delegate = |
| confirmation_prompt->widget_delegate()->AsDialogDelegate(); |
| ASSERT_TRUE(dialog_delegate); |
| dialog_delegate->AcceptDialog(); |
| |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| WaitForGetAuthTokenResults(func1.get(), &access_token, &granted_scopes, |
| &func1_runner); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| EXPECT_EQ(func1->GetExtensionTokenKeyForTest()->scopes, granted_scopes); |
| |
| EXPECT_FALSE(func1->login_ui_shown()); |
| EXPECT_FALSE(func1->scope_ui_shown()); |
| |
| WaitForGetAuthTokenResults(func2.get(), &access_token, &granted_scopes, |
| &func2_runner); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| EXPECT_EQ(func2->GetExtensionTokenKeyForTest()->scopes, granted_scopes); |
| |
| EXPECT_FALSE(func2->login_ui_shown()); |
| EXPECT_FALSE(func2->scope_ui_shown()); |
| |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 2); |
| } |
| #endif |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| InteractiveLoginSuccessApprovalAborted) { |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_login_ui_result(true); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::REMOTE_CONSENT_SUCCESS); |
| func->set_scope_ui_failure(GaiaRemoteConsentFlow::WINDOW_CLOSED); |
| std::string error = utils::RunFunctionAndReturnError( |
| func.get(), "[{\"interactive\": true}]", profile()); |
| EXPECT_EQ(std::string(errors::kUserRejected), error); |
| EXPECT_TRUE(func->login_ui_shown()); |
| EXPECT_TRUE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kRemoteConsentFlowRejected, 1); |
| } |
| #endif // !BUILDFLAG(IS_CHROMEOS) |
| |
| class GetAuthTokenFunctionInteractivityTest |
| : public GetAuthTokenFunctionTest, |
| public testing::WithParamInterface< |
| IdentityGetAuthTokenFunction::InteractivityStatus> { |
| public: |
| GetAuthTokenFunctionInteractivityTest() = default; |
| |
| private: |
| void SetUpOnMainThread() override { |
| // Mock the user activity. |
| switch (GetParam()) { |
| case IdentityGetAuthTokenFunction::InteractivityStatus:: |
| kAllowedWithGesture: |
| SetUserGestureEnabled(true); |
| break; |
| case IdentityGetAuthTokenFunction::InteractivityStatus:: |
| kAllowedWithActivity: |
| idle_state_ = std::make_unique<ui::ScopedSetIdleState>( |
| ui::IdleState::IDLE_STATE_ACTIVE); |
| SetUserGestureEnabled(false); |
| ASSERT_EQ( |
| ui::CalculateIdleState(kGetAuthTokenInactivityTime.InSeconds()), |
| ui::IDLE_STATE_ACTIVE); |
| break; |
| case IdentityGetAuthTokenFunction::InteractivityStatus::kNotRequested: |
| case IdentityGetAuthTokenFunction::InteractivityStatus::kDisallowedIdle: |
| SetUserGestureEnabled(false); |
| idle_state_ = std::make_unique<ui::ScopedSetIdleState>( |
| ui::IdleState::IDLE_STATE_LOCKED); |
| ASSERT_NE( |
| ui::CalculateIdleState(kGetAuthTokenInactivityTime.InSeconds()), |
| ui::IDLE_STATE_ACTIVE); |
| break; |
| case IdentityGetAuthTokenFunction::InteractivityStatus:: |
| kDisallowedSigninDisallowed: |
| NOTREACHED(); |
| } |
| |
| GetAuthTokenFunctionTest::SetUpOnMainThread(); |
| } |
| |
| std::unique_ptr<ui::ScopedSetIdleState> idle_state_; |
| }; |
| |
| // The interactive login flow is always short-circuited out with failure on |
| // Ash, so the tests of the interactive login flow being successful are not |
| // relevant on that platform. |
| #if !BUILDFLAG(IS_CHROMEOS) |
| IN_PROC_BROWSER_TEST_P(GetAuthTokenFunctionInteractivityTest, |
| SigninInteractivityTest) { |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| ASSERT_EQ(func->user_gesture(), |
| GetParam() == IdentityGetAuthTokenFunction::InteractivityStatus:: |
| kAllowedWithGesture); |
| |
| func->set_extension(extension.get()); |
| func->set_login_ui_result(true); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::REMOTE_CONSENT_SUCCESS); |
| func->set_remote_consent_gaia_id(GaiaId("gaia_id_for_primary_example.com")); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| |
| const std::string function_args = |
| GetParam() == |
| IdentityGetAuthTokenFunction::InteractivityStatus::kNotRequested |
| ? "[{}]" |
| : "[{\"interactive\": true}]"; |
| IdentityGetAuthTokenError::State expected_error_state = |
| IdentityGetAuthTokenError::State::kNone; |
| if (GetParam() == |
| IdentityGetAuthTokenFunction::InteractivityStatus::kDisallowedIdle || |
| GetParam() == |
| IdentityGetAuthTokenFunction::InteractivityStatus::kNotRequested) { |
| // Interactivity is not allowed, return an error. |
| std::string error = |
| utils::RunFunctionAndReturnError(func.get(), function_args, profile()); |
| std::string expected_error; |
| if (GetParam() == |
| IdentityGetAuthTokenFunction::InteractivityStatus::kDisallowedIdle) { |
| expected_error = errors::kGetAuthTokenInteractivityDeniedError; |
| expected_error_state = |
| IdentityGetAuthTokenError::State::kInteractivityDenied; |
| } else { |
| expected_error = errors::kUserNotSignedIn; |
| expected_error_state = IdentityGetAuthTokenError::State::kUserNotSignedIn; |
| } |
| EXPECT_EQ(expected_error, error); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| } else { |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| RunGetAuthTokenFunction(func.get(), function_args, browser(), &access_token, |
| &granted_scopes); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| EXPECT_EQ(func->GetExtensionTokenKeyForTest()->scopes, granted_scopes); |
| EXPECT_TRUE(func->login_ui_shown()); |
| EXPECT_TRUE(func->scope_ui_shown()); |
| } |
| histogram_tester()->ExpectUniqueSample(kGetAuthTokenResultHistogramName, |
| expected_error_state, 1); |
| } |
| #endif // !BUILDFLAG(IS_CHROMEOS) |
| |
| IN_PROC_BROWSER_TEST_P(GetAuthTokenFunctionInteractivityTest, |
| ConsentInteractivityTest) { |
| SignIn("primary@example.com"); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| ASSERT_EQ(func->user_gesture(), |
| GetParam() == IdentityGetAuthTokenFunction::InteractivityStatus:: |
| kAllowedWithGesture); |
| |
| func->set_extension(extension.get()); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::REMOTE_CONSENT_SUCCESS); |
| func->set_remote_consent_gaia_id(GaiaId("gaia_id_for_primary_example.com")); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| |
| const std::string function_args = |
| GetParam() == |
| IdentityGetAuthTokenFunction::InteractivityStatus::kNotRequested |
| ? "[{}]" |
| : "[{\"interactive\": true}]"; |
| IdentityGetAuthTokenError::State expected_error_state = |
| IdentityGetAuthTokenError::State::kNone; |
| if (GetParam() == |
| IdentityGetAuthTokenFunction::InteractivityStatus::kDisallowedIdle || |
| GetParam() == |
| IdentityGetAuthTokenFunction::InteractivityStatus::kNotRequested) { |
| // Interactivity is not allowed, return an error. |
| std::string error = |
| utils::RunFunctionAndReturnError(func.get(), function_args, profile()); |
| std::string expected_error; |
| if (GetParam() == |
| IdentityGetAuthTokenFunction::InteractivityStatus::kDisallowedIdle) { |
| expected_error = errors::kGetAuthTokenInteractivityDeniedError; |
| expected_error_state = |
| IdentityGetAuthTokenError::State::kInteractivityDenied; |
| } else { |
| expected_error = errors::kNoGrant; |
| expected_error_state = |
| IdentityGetAuthTokenError::State::kGaiaConsentInteractionRequired; |
| } |
| EXPECT_EQ(expected_error, error); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| } else { |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| RunGetAuthTokenFunction(func.get(), function_args, browser(), &access_token, |
| &granted_scopes); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| EXPECT_EQ(func->GetExtensionTokenKeyForTest()->scopes, granted_scopes); |
| EXPECT_TRUE(func->scope_ui_shown()); |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN, |
| GetCachedToken(CoreAccountInfo()).status()); |
| } |
| EXPECT_FALSE(func->login_ui_shown()); |
| histogram_tester()->ExpectUniqueSample(kGetAuthTokenResultHistogramName, |
| expected_error_state, 1); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| , |
| GetAuthTokenFunctionInteractivityTest, |
| testing::Values( |
| IdentityGetAuthTokenFunction::InteractivityStatus::kNotRequested, |
| IdentityGetAuthTokenFunction::InteractivityStatus::kDisallowedIdle, |
| IdentityGetAuthTokenFunction::InteractivityStatus::kAllowedWithGesture, |
| IdentityGetAuthTokenFunction::InteractivityStatus:: |
| kAllowedWithActivity)); |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, InteractiveApprovalAborted) { |
| SignIn("primary@example.com"); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::REMOTE_CONSENT_SUCCESS); |
| func->set_scope_ui_failure(GaiaRemoteConsentFlow::WINDOW_CLOSED); |
| std::string error = utils::RunFunctionAndReturnError( |
| func.get(), "[{\"interactive\": true}]", profile()); |
| EXPECT_EQ(std::string(errors::kUserRejected), error); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_TRUE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kRemoteConsentFlowRejected, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| InteractiveApprovalLoadFailed) { |
| SignIn("primary@example.com"); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::REMOTE_CONSENT_SUCCESS); |
| func->set_scope_ui_failure(GaiaRemoteConsentFlow::LOAD_FAILED); |
| std::string error = utils::RunFunctionAndReturnError( |
| func.get(), "[{\"interactive\": true}]", profile()); |
| EXPECT_EQ(std::string(errors::kPageLoadFailure), error); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_TRUE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kRemoteConsentPageLoadFailure, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| InteractiveApprovalInvalidConsentResult) { |
| SignIn("primary@example.com"); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::REMOTE_CONSENT_SUCCESS); |
| func->set_scope_ui_failure(GaiaRemoteConsentFlow::INVALID_CONSENT_RESULT); |
| std::string error = utils::RunFunctionAndReturnError( |
| func.get(), "[{\"interactive\": true}]", profile()); |
| EXPECT_TRUE(base::StartsWith(error, errors::kInvalidConsentResult, |
| base::CompareCase::INSENSITIVE_ASCII)); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_TRUE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kInvalidConsentResult, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, InteractiveApprovalNoGrant) { |
| SignIn("primary@example.com"); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::REMOTE_CONSENT_SUCCESS); |
| func->set_scope_ui_failure(GaiaRemoteConsentFlow::NO_GRANT); |
| std::string error = utils::RunFunctionAndReturnError( |
| func.get(), "[{\"interactive\": true}]", profile()); |
| EXPECT_TRUE(base::StartsWith(error, errors::kNoGrant, |
| base::CompareCase::INSENSITIVE_ASCII)); |
| |
| // The login UI should not be shown as the account is in a valid state. |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_TRUE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kNoGrant, 1); |
| } |
| |
| // The signin flow is simply not used on Ash. |
| #if !BUILDFLAG(IS_CHROMEOS) |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| InteractiveApprovalNoGrantShowSigninUIOnlyOnce) { |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_login_ui_result(true); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::REMOTE_CONSENT_SUCCESS); |
| func->set_scope_ui_failure(GaiaRemoteConsentFlow::NO_GRANT); |
| |
| // The function should complete with an error, showing the signin UI only |
| // once for the initial signin. |
| std::string error = utils::RunFunctionAndReturnError( |
| func.get(), "[{\"interactive\": true}]", profile()); |
| EXPECT_TRUE(base::StartsWith(error, errors::kNoGrant, |
| base::CompareCase::INSENSITIVE_ASCII)); |
| |
| EXPECT_TRUE(func->login_ui_shown()); |
| EXPECT_TRUE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kNoGrant, 1); |
| } |
| #endif |
| |
| #if !BUILDFLAG(IS_MAC) |
| // Test was originally written for http://crbug.com/753014 and subsequently |
| // modified to use the remote consent flow. |
| // |
| // On macOS, closing all browsers does not shut down the browser process. |
| // TODO(http://crbug.com/756462): Figure out how to shut down the browser |
| // process on macOS and enable this test on macOS as well. |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| InteractiveSigninFailedDuringBrowserProcessShutDown) { |
| SignIn("primary@example.com"); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::REMOTE_CONSENT_SUCCESS); |
| func->set_scope_ui_failure(GaiaRemoteConsentFlow::LOAD_FAILED); |
| func->set_login_ui_result(false); |
| |
| // Closing all browsers ensures that the browser process is shutting down. |
| CloseAllBrowsers(); |
| |
| std::string error = utils::RunFunctionAndReturnError( |
| func.get(), "[{\"interactive\": true}]", profile()); |
| // Check that the remote consent dialog is shown to ensure that the remote |
| // consent flow fails. |
| EXPECT_TRUE(func->scope_ui_shown()); |
| |
| // The login screen should not be shown when the browser process is shutting |
| // down. |
| EXPECT_FALSE(func->login_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kRemoteConsentPageLoadFailure, 1); |
| } |
| #endif // !BUILDFLAG(IS_MAC) |
| |
| #if !BUILDFLAG(IS_CHROMEOS) |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| InteractiveSigninFailedDuringProfileShutDown) { |
| SignIn("primary@example.com"); |
| auto keep_alive = std::make_unique<ScopedKeepAlive>( |
| KeepAliveOrigin::BROWSER, KeepAliveRestartOption::DISABLED); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::REMOTE_CONSENT_SUCCESS); |
| // Have GetAuthTokenFunction make the request for the access token to ensure |
| // that the function doesn't immediately succeed. |
| func->set_auto_login_access_token(false); |
| |
| RunFunctionAsync(func.get(), "[{\"interactive\": true}]"); |
| identity_test_env()->identity_manager()->RemoveDiagnosticsObserver(this); |
| identity_test_env_profile_adaptor_.reset(); |
| CloseBrowserSynchronously(browser()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| |
| // The login screen should not be shown when the profile is shutting |
| // down. |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_EQ(std::string(errors::kBrowserContextShutDown), |
| WaitForError(func.get())); |
| base::SingleThreadTaskRunner::GetCurrentDefault()->DeleteSoon( |
| FROM_HERE, std::move(keep_alive)); |
| } |
| #endif // !BUILDFLAG(IS_CHROMEOS) |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, NoninteractiveQueue) { |
| SignIn("primary@example.com"); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(extension.get()); |
| |
| // Create a fake request to block the queue. |
| MockQueuedMintRequest queued_request; |
| IdentityMintRequestQueue::MintType type = |
| IdentityMintRequestQueue::MINT_TYPE_NONINTERACTIVE; |
| |
| EXPECT_CALL(queued_request, StartMintToken(type)).Times(1); |
| QueueRequestStart(type, &queued_request); |
| |
| // The real request will start processing, but wait in the queue behind |
| // the blocker. |
| RunFunctionAsync(func.get(), "[{}]"); |
| // Verify that we have fetched the login token at this point. |
| testing::Mock::VerifyAndClearExpectations(func.get()); |
| |
| // The flow will be created after the first queued request clears. |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| |
| QueueRequestComplete(type, &queued_request); |
| |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| WaitForGetAuthTokenResults(func.get(), &access_token, &granted_scopes); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| EXPECT_EQ(func->GetExtensionTokenKeyForTest()->scopes, granted_scopes); |
| |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, InteractiveQueue) { |
| SignIn("primary@example.com"); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(extension.get()); |
| |
| // Create a fake request to block the queue. |
| MockQueuedMintRequest queued_request; |
| IdentityMintRequestQueue::MintType type = |
| IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE; |
| |
| EXPECT_CALL(queued_request, StartMintToken(type)).Times(1); |
| QueueRequestStart(type, &queued_request); |
| |
| // The real request will start processing, but wait in the queue behind |
| // the blocker. |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::REMOTE_CONSENT_SUCCESS); |
| func->set_remote_consent_gaia_id(GaiaId("gaia_id_for_primary_example.com")); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| RunFunctionAsync(func.get(), "[{\"interactive\": true}]"); |
| // Verify that we have fetched the login token and run the first flow. |
| testing::Mock::VerifyAndClearExpectations(func.get()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| |
| // The UI will be displayed and a token retrieved after the first |
| // queued request clears. |
| QueueRequestComplete(type, &queued_request); |
| |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| WaitForGetAuthTokenResults(func.get(), &access_token, &granted_scopes); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| EXPECT_EQ(func->GetExtensionTokenKeyForTest()->scopes, granted_scopes); |
| |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_TRUE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, InteractiveQueueShutdown) { |
| SignIn("primary@example.com"); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(extension.get()); |
| |
| // Create a fake request to block the queue. |
| MockQueuedMintRequest queued_request; |
| IdentityMintRequestQueue::MintType type = |
| IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE; |
| |
| EXPECT_CALL(queued_request, StartMintToken(type)).Times(1); |
| QueueRequestStart(type, &queued_request); |
| |
| // The real request will start processing, but wait in the queue behind |
| // the blocker. |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::REMOTE_CONSENT_SUCCESS); |
| RunFunctionAsync(func.get(), "[{\"interactive\": true}]"); |
| // Verify that we have fetched the login token and run the first flow. |
| testing::Mock::VerifyAndClearExpectations(func.get()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| |
| // After the request is canceled, the function will complete. |
| func->OnIdentityAPIShutdown(); |
| EXPECT_EQ(std::string(errors::kBrowserContextShutDown), |
| WaitForError(func.get())); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| |
| QueueRequestComplete(type, &queued_request); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kBrowserContextShutDown, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, NoninteractiveShutdown) { |
| SignIn("primary@example.com"); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(extension.get()); |
| |
| func->push_mint_token_flow(std::make_unique<TestHangOAuth2MintTokenFlow>()); |
| RunFunctionAsync(func.get(), "[{\"interactive\": false}]"); |
| |
| // After the request is canceled, the function will complete. |
| func->OnIdentityAPIShutdown(); |
| EXPECT_EQ(std::string(errors::kBrowserContextShutDown), |
| WaitForError(func.get())); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kBrowserContextShutDown, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| InteractiveQueuedNoninteractiveFails) { |
| SignIn("primary@example.com"); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(extension.get()); |
| |
| // Create a fake request to block the interactive queue. |
| MockQueuedMintRequest queued_request; |
| IdentityMintRequestQueue::MintType type = |
| IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE; |
| |
| EXPECT_CALL(queued_request, StartMintToken(type)).Times(1); |
| QueueRequestStart(type, &queued_request); |
| |
| // Non-interactive requests fail without hitting GAIA, because a |
| // consent UI is known to be up. |
| std::string error = |
| utils::RunFunctionAndReturnError(func.get(), "[{}]", profile()); |
| EXPECT_EQ(std::string(errors::kNoGrant), error); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| |
| QueueRequestComplete(type, &queued_request); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kGaiaConsentInteractionAlreadyRunning, |
| 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, NonInteractiveCacheHit) { |
| SignIn("primary@example.com"); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(extension.get()); |
| |
| // Pre-populate the cache with a token. |
| IdentityTokenCacheValue token = |
| CreateToken(kAccessToken, base::Seconds(3600)); |
| SetCachedToken(token); |
| |
| // Get a token. Should not require a GAIA request. |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| RunGetAuthTokenFunction(func.get(), "[{}]", browser(), &access_token, |
| &granted_scopes); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| EXPECT_EQ(func->GetExtensionTokenKeyForTest()->scopes, granted_scopes); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| NonInteractiveCacheHitSecondary) { |
| AccountInfo account_info = identity_test_env()->MakePrimaryAccountAvailable( |
| "email@example.com", signin::ConsentLevel::kSignin); |
| |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(extension.get()); |
| |
| // Pre-populate the cache with a token. |
| IdentityTokenCacheValue token = |
| CreateToken(kAccessToken, base::Seconds(3600)); |
| SetCachedTokenForAccount(account_info, token); |
| |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| RunGetAuthTokenFunction(func.get(), "[{}]", browser(), &access_token, |
| &granted_scopes); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| EXPECT_EQ(func->GetExtensionTokenKeyForTest()->scopes, granted_scopes); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| NonInteractiveRemoteConsentCacheHit) { |
| SignIn("primary@example.com"); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(extension.get()); |
| |
| // Pre-populate the cache with the remote consent resolution data. |
| RemoteConsentResolutionData resolution_data; |
| IdentityTokenCacheValue token = |
| IdentityTokenCacheValue::CreateRemoteConsent(resolution_data); |
| SetCachedToken(token); |
| |
| // Should return an error without a GAIA request. |
| std::string error = |
| utils::RunFunctionAndReturnError(func.get(), "[{}]", profile()); |
| EXPECT_EQ(std::string(errors::kNoGrant), error); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kGaiaConsentInteractionRequired, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| NonInteractiveRemoteConsentApprovedCacheHit) { |
| SignIn("primary@example.com"); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(extension.get()); |
| |
| // Pre-populate the cache with the remote consent approved result. |
| IdentityTokenCacheValue token = |
| IdentityTokenCacheValue::CreateRemoteConsentApproved(std::string()); |
| SetCachedToken(token); |
| |
| // Get a token. Should not prompt for scopes. |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| RunGetAuthTokenFunction(func.get(), "[{}]", browser(), &access_token, |
| &granted_scopes); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| EXPECT_EQ(func->GetExtensionTokenKeyForTest()->scopes, granted_scopes); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, InteractiveCacheHit) { |
| SignIn("primary@example.com"); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(extension.get()); |
| |
| // Create a fake request to block the queue. |
| MockQueuedMintRequest queued_request; |
| IdentityMintRequestQueue::MintType type = |
| IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE; |
| |
| EXPECT_CALL(queued_request, StartMintToken(type)).Times(1); |
| QueueRequestStart(type, &queued_request); |
| |
| // The real request will start processing, but wait in the queue behind |
| // the blocker. |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::REMOTE_CONSENT_SUCCESS); |
| RunFunctionAsync(func.get(), "[{\"interactive\": true}]"); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Populate the cache with a token while the request is blocked. |
| IdentityTokenCacheValue token = |
| CreateToken(kAccessToken, base::Seconds(3600)); |
| SetCachedToken(token); |
| |
| // When we wake up the request, it returns the cached token without |
| // displaying a UI, or hitting GAIA. |
| QueueRequestComplete(type, &queued_request); |
| |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| WaitForGetAuthTokenResults(func.get(), &access_token, &granted_scopes); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| EXPECT_EQ(func->GetExtensionTokenKeyForTest()->scopes, granted_scopes); |
| |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| } |
| |
| // The interactive login UI is never shown on Ash, so tests of the interactive |
| // login flow being successful are not relevant on that platform. |
| #if !BUILDFLAG(IS_CHROMEOS) |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, LoginInvalidatesTokenCache) { |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_extension(extension.get()); |
| |
| // Pre-populate the cache with a token. |
| IdentityTokenCacheValue token = |
| CreateToken(kAccessToken, base::Seconds(3600)); |
| SetCachedToken(token); |
| |
| // Because the user is not signed in, the token will be removed, |
| // and we'll hit GAIA for new tokens. |
| func->set_login_ui_result(true); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::REMOTE_CONSENT_SUCCESS); |
| func->set_remote_consent_gaia_id(GaiaId("gaia_id_for_primary_example.com")); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| RunGetAuthTokenFunction(func.get(), "[{\"interactive\": true}]", browser(), |
| &access_token, &granted_scopes); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| EXPECT_EQ(func->GetExtensionTokenKeyForTest()->scopes, granted_scopes); |
| EXPECT_TRUE(func->login_ui_shown()); |
| EXPECT_TRUE(func->scope_ui_shown()); |
| |
| ExtensionTokenKey key(extension->id(), CoreAccountInfo(), granted_scopes); |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND, |
| id_api()->token_cache()->GetToken(key).status()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| } |
| #endif |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, ComponentWithChromeClientId) { |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->ignore_did_respond_for_testing(); |
| scoped_refptr<const Extension> extension( |
| CreateExtension(SCOPES | AS_COMPONENT)); |
| ASSERT_TRUE(extension.get()); |
| func->set_extension(extension.get()); |
| const OAuth2Info& oauth2_info = |
| OAuth2ManifestHandler::GetOAuth2Info(*extension); |
| EXPECT_FALSE(oauth2_info.client_id); |
| EXPECT_FALSE(func->GetOAuth2ClientId().empty()); |
| EXPECT_NE("client1", func->GetOAuth2ClientId()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, ComponentWithNormalClientId) { |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->ignore_did_respond_for_testing(); |
| scoped_refptr<const Extension> extension( |
| CreateExtension(CLIENT_ID | SCOPES | AS_COMPONENT)); |
| func->set_extension(extension.get()); |
| EXPECT_EQ("client1", func->GetOAuth2ClientId()); |
| } |
| |
| // Ensure that IdentityAPI shutdown triggers an active function call to return |
| // with an error. |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, IdentityAPIShutdown) { |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_extension(extension.get()); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| |
| // Have GetAuthTokenFunction actually make the request for the access token to |
| // ensure that the function doesn't immediately succeed. |
| func->set_auto_login_access_token(false); |
| RunFunctionAsync(func.get(), "[{}]"); |
| |
| id_api()->Shutdown(); |
| EXPECT_EQ(std::string(errors::kBrowserContextShutDown), |
| WaitForError(func.get())); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kBrowserContextShutDown, 1); |
| } |
| |
| // Ensure that when there are multiple active function calls, IdentityAPI |
| // shutdown triggers them all to return with errors. |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| IdentityAPIShutdownWithMultipleActiveTokenRequests) { |
| // Set up two extension functions, having them actually make the request for |
| // the access token to ensure that they don't immediately succeed. |
| scoped_refptr<FakeGetAuthTokenFunction> func1(new FakeGetAuthTokenFunction()); |
| scoped_refptr<const Extension> extension1( |
| CreateExtension(CLIENT_ID | SCOPES)); |
| func1->set_extension(extension1.get()); |
| func1->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| func1->set_auto_login_access_token(false); |
| |
| scoped_refptr<FakeGetAuthTokenFunction> func2(new FakeGetAuthTokenFunction()); |
| scoped_refptr<const Extension> extension2( |
| CreateExtension(CLIENT_ID | SCOPES)); |
| func2->set_extension(extension2.get()); |
| func2->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| func2->set_auto_login_access_token(false); |
| |
| // Run both functions. Note that it's necessary to use AsyncFunctionRunner |
| // directly here rather than the AsyncExtensionBrowserTest instance methods |
| // that wrap it, as each AsyncFunctionRunner instance sets itself as the |
| // delegate of exactly one function. |
| AsyncFunctionRunner func1_runner; |
| func1_runner.RunFunctionAsync(func1.get(), "[{}]", profile()); |
| |
| AsyncFunctionRunner func2_runner; |
| func2_runner.RunFunctionAsync(func2.get(), "[{}]", profile()); |
| |
| // Shut down IdentityAPI and ensure that both functions complete with an |
| // error. |
| id_api()->Shutdown(); |
| EXPECT_EQ(std::string(errors::kBrowserContextShutDown), |
| func1_runner.WaitForError(func1.get())); |
| EXPECT_EQ(std::string(errors::kBrowserContextShutDown), |
| func2_runner.WaitForError(func2.get())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, ManuallyIssueToken) { |
| CoreAccountId primary_account_id = SignIn("primary@example.com"); |
| |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_extension(extension.get()); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| |
| // Have GetAuthTokenFunction actually make the request for the access token. |
| func->set_auto_login_access_token(false); |
| |
| base::RunLoop run_loop; |
| on_access_token_requested_ = run_loop.QuitClosure(); |
| RunFunctionAsync(func.get(), "[{}]"); |
| run_loop.Run(); |
| |
| std::string primary_account_access_token = |
| IssueLoginAccessTokenForAccount(primary_account_id); |
| |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| WaitForGetAuthTokenResults(func.get(), &access_token, &granted_scopes); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| EXPECT_EQ(func->GetExtensionTokenKeyForTest()->scopes, granted_scopes); |
| |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN, |
| GetCachedToken(CoreAccountInfo()).status()); |
| EXPECT_THAT(func->login_access_tokens(), |
| testing::ElementsAre(primary_account_access_token)); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, ManuallyIssueTokenFailure) { |
| CoreAccountId primary_account_id = SignIn("primary@example.com"); |
| |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_extension(extension.get()); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| |
| // Have GetAuthTokenFunction actually make the request for the access token. |
| func->set_auto_login_access_token(false); |
| |
| base::RunLoop run_loop; |
| on_access_token_requested_ = run_loop.QuitClosure(); |
| RunFunctionAsync(func.get(), "[{}]"); |
| run_loop.Run(); |
| |
| identity_test_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithError( |
| primary_account_id, |
| GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE)); |
| |
| EXPECT_EQ( |
| std::string(errors::kAuthFailure) + |
| GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE) |
| .ToString(), |
| WaitForError(func.get())); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kGetAccessTokenAuthFailure, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| MultiDefaultUserManuallyIssueToken) { |
| CoreAccountId primary_account_id = SignIn("primary@example.com"); |
| identity_test_env()->MakeAccountAvailable("secondary@example.com"); |
| |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_extension(extension.get()); |
| func->set_auto_login_access_token(false); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| |
| base::RunLoop run_loop; |
| on_access_token_requested_ = run_loop.QuitClosure(); |
| RunFunctionAsync(func.get(), "[{}]"); |
| run_loop.Run(); |
| |
| std::string primary_account_access_token = |
| IssueLoginAccessTokenForAccount(primary_account_id); |
| |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| WaitForGetAuthTokenResults(func.get(), &access_token, &granted_scopes); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| EXPECT_EQ(func->GetExtensionTokenKeyForTest()->scopes, granted_scopes); |
| |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN, |
| GetCachedToken(CoreAccountInfo()).status()); |
| EXPECT_THAT(func->login_access_tokens(), |
| testing::ElementsAre(primary_account_access_token)); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| MultiPrimaryUserManuallyIssueToken) { |
| CoreAccountId primary_account_id = SignIn("primary@example.com"); |
| identity_test_env()->MakeAccountAvailable("secondary@example.com"); |
| |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_extension(extension.get()); |
| func->set_auto_login_access_token(false); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| |
| base::RunLoop run_loop; |
| on_access_token_requested_ = run_loop.QuitClosure(); |
| RunFunctionAsync( |
| func.get(), |
| "[{\"account\": { \"id\": \"gaia_id_for_primary_example.com\" } }]"); |
| run_loop.Run(); |
| |
| std::string primary_account_access_token = |
| IssueLoginAccessTokenForAccount(primary_account_id); |
| |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| WaitForGetAuthTokenResults(func.get(), &access_token, &granted_scopes); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| EXPECT_EQ(func->GetExtensionTokenKeyForTest()->scopes, granted_scopes); |
| |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN, |
| GetCachedToken(CoreAccountInfo()).status()); |
| EXPECT_THAT(func->login_access_tokens(), |
| testing::ElementsAre(primary_account_access_token)); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| MultiSecondaryUserManuallyIssueToken) { |
| SignIn("primary@example.com"); |
| CoreAccountInfo secondary_account = |
| identity_test_env()->MakeAccountAvailable("secondary@example.com"); |
| |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_extension(extension.get()); |
| func->set_auto_login_access_token(false); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| |
| const char kFunctionParams[] = |
| "[{\"account\": { \"id\": \"gaia_id_for_secondary_example.com\" } }]"; |
| |
| if (id_api()->AreExtensionsRestrictedToPrimaryAccount()) { |
| // Fail if extensions are restricted to the primary account. |
| std::string error = utils::RunFunctionAndReturnError( |
| func.get(), kFunctionParams, profile()); |
| EXPECT_EQ(std::string(errors::kUserNonPrimary), error); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kUserNonPrimary, 1); |
| return; |
| } |
| |
| base::RunLoop run_loop; |
| on_access_token_requested_ = run_loop.QuitClosure(); |
| RunFunctionAsync(func.get(), kFunctionParams); |
| run_loop.Run(); |
| |
| std::string secondary_account_access_token = |
| IssueLoginAccessTokenForAccount(secondary_account.account_id); |
| |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| WaitForGetAuthTokenResults(func.get(), &access_token, &granted_scopes); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| EXPECT_EQ(func->GetExtensionTokenKeyForTest()->scopes, granted_scopes); |
| |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN, |
| GetCachedToken(secondary_account).status()); |
| EXPECT_THAT(func->login_access_tokens(), |
| testing::ElementsAre(secondary_account_access_token)); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| MultiUnknownUserGetTokenFromTokenServiceFailure) { |
| SignIn("primary@example.com"); |
| identity_test_env()->MakeAccountAvailable("secondary@example.com"); |
| |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_extension(extension.get()); |
| func->set_auto_login_access_token(false); |
| |
| std::string error = utils::RunFunctionAndReturnError( |
| func.get(), "[{\"account\": { \"id\": \"unknown@example.com\" } }]", |
| profile()); |
| std::string expected_error; |
| if (id_api()->AreExtensionsRestrictedToPrimaryAccount()) { |
| EXPECT_EQ(errors::kUserNonPrimary, error); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kUserNonPrimary, 1); |
| } else { |
| EXPECT_EQ(errors::kUserNotSignedIn, error); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kUserNotSignedIn, 1); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| MultiSecondaryNonInteractiveMintFailure) { |
| // This test is only relevant if extensions see all accounts. |
| if (id_api()->AreExtensionsRestrictedToPrimaryAccount()) { |
| return; |
| } |
| |
| SignIn("primary@example.com"); |
| identity_test_env()->MakeAccountAvailable("secondary@example.com"); |
| |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_FAILURE); |
| std::string error = utils::RunFunctionAndReturnError( |
| func.get(), |
| "[{\"account\": { \"id\": \"gaia_id_for_secondary_example.com\" } }]", |
| profile()); |
| EXPECT_TRUE(base::StartsWith(error, errors::kAuthFailure, |
| base::CompareCase::INSENSITIVE_ASCII)); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kMintTokenAuthFailure, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| MultiSecondaryNonInteractiveLoginAccessTokenFailure) { |
| // This test is only relevant if extensions see all accounts. |
| if (id_api()->AreExtensionsRestrictedToPrimaryAccount()) { |
| return; |
| } |
| |
| SignIn("primary@example.com"); |
| identity_test_env()->MakeAccountAvailable("secondary@example.com"); |
| |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_login_access_token_result(false); |
| std::string error = utils::RunFunctionAndReturnError( |
| func.get(), |
| "[{\"account\": { \"id\": \"gaia_id_for_secondary_example.com\" } }]", |
| profile()); |
| EXPECT_TRUE(base::StartsWith(error, errors::kAuthFailure, |
| base::CompareCase::INSENSITIVE_ASCII)); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kGetAccessTokenAuthFailure, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| MultiSecondaryInteractiveApprovalAborted) { |
| // This test is only relevant if extensions see all accounts. |
| if (id_api()->AreExtensionsRestrictedToPrimaryAccount()) { |
| return; |
| } |
| |
| SignIn("primary@example.com"); |
| identity_test_env()->MakeAccountAvailable("secondary@example.com"); |
| |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::REMOTE_CONSENT_SUCCESS); |
| func->set_scope_ui_failure(GaiaRemoteConsentFlow::WINDOW_CLOSED); |
| std::string error = utils::RunFunctionAndReturnError( |
| func.get(), |
| "[{\"account\": { \"id\": \"gaia_id_for_secondary_example.com\" }, " |
| "\"interactive\": true}]", |
| profile()); |
| EXPECT_EQ(std::string(errors::kUserRejected), error); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_TRUE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kRemoteConsentFlowRejected, 1); |
| } |
| |
| // Tests that Chrome remembers user's choice of an account at the end of the |
| // remote consent flow. Chrome should reuse this account in the next |
| // getAuthToken() call for the same extension. |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| MultiSecondaryInteractiveRemoteConsent) { |
| CoreAccountId primary_account_id = SignIn("primary@example.com"); |
| AccountInfo secondary_account = |
| identity_test_env()->MakeAccountAvailable("secondary@example.com"); |
| const extensions::Extension* extension = CreateExtension(CLIENT_ID | SCOPES); |
| |
| { |
| scoped_refptr<FakeGetAuthTokenFunction> func( |
| new FakeGetAuthTokenFunction()); |
| func->set_extension(extension); |
| func->push_mint_token_result( |
| TestOAuth2MintTokenFlow::REMOTE_CONSENT_SUCCESS); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| func->set_remote_consent_gaia_id(secondary_account.gaia); |
| // Have GetAuthTokenFunction actually make the request for the access token. |
| func->set_auto_login_access_token(false); |
| |
| base::RunLoop run_loop; |
| on_access_token_requested_ = run_loop.QuitClosure(); |
| RunFunctionAsync(func.get(), "[{\"interactive\": true}]"); |
| run_loop.Run(); |
| |
| // The first request will be for the primary account and the second one for |
| // the account that has been returned in result of the remote consent. |
| std::string primary_account_access_token = |
| IssueLoginAccessTokenForAccount(primary_account_id); |
| |
| if (id_api()->AreExtensionsRestrictedToPrimaryAccount()) { |
| EXPECT_EQ(std::string(errors::kUserNonPrimary), WaitForError(func.get())); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kRemoteConsentUserNonPrimary, 1); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultAfterConsentApprovedHistogramName, |
| IdentityGetAuthTokenError::State::kRemoteConsentUserNonPrimary, 1); |
| return; |
| } |
| |
| std::string secondary_account_access_token = |
| IssueLoginAccessTokenForAccount(secondary_account.account_id); |
| |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| WaitForGetAuthTokenResults(func.get(), &access_token, &granted_scopes); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| EXPECT_EQ(func->GetExtensionTokenKeyForTest()->scopes, granted_scopes); |
| |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN, |
| GetCachedToken(secondary_account).status()); |
| EXPECT_EQ(secondary_account.gaia, |
| id_api()->GetGaiaIdForExtension(extension->id())); |
| EXPECT_THAT(func->login_access_tokens(), |
| testing::ElementsAre(primary_account_access_token, |
| secondary_account_access_token)); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kNone, 1); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultAfterConsentApprovedHistogramName, |
| IdentityGetAuthTokenError::State::kNone, 1); |
| } |
| |
| { |
| // Check that the next function call returns a token for the same account |
| // from the cache. |
| scoped_refptr<FakeGetAuthTokenFunction> func( |
| new FakeGetAuthTokenFunction()); |
| func->set_extension(extension); |
| |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| RunGetAuthTokenFunction(func.get(), "[{}]", browser(), &access_token, |
| &granted_scopes); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| EXPECT_EQ(func->GetExtensionTokenKeyForTest()->scopes, granted_scopes); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kNone, 2); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultAfterConsentApprovedHistogramName, |
| IdentityGetAuthTokenError::State::kNone, 1); |
| } |
| } |
| |
| // Tests two concurrent remote consent flows. Both of them should succeed. |
| // The second flow starts while the first one is blocked on an interactive mint |
| // token flow. This is a regression test for https://crbug.com/1091423. |
| IN_PROC_BROWSER_TEST_F( |
| GetAuthTokenFunctionTest, |
| RemoteConsentMultipleActiveRequests_BlockedOnInteractive) { |
| SignIn("primary@example.com"); |
| CoreAccountInfo account = GetPrimaryAccountInfo(); |
| const extensions::Extension* extension = CreateExtension(CLIENT_ID | SCOPES); |
| |
| scoped_refptr<FakeGetAuthTokenFunction> func1(new FakeGetAuthTokenFunction()); |
| func1->set_extension(extension); |
| func1->push_mint_token_result( |
| TestOAuth2MintTokenFlow::REMOTE_CONSENT_SUCCESS); |
| func1->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| func1->set_remote_consent_gaia_id(account.gaia); |
| base::RunLoop scope_ui_shown_loop; |
| func1->set_scope_ui_async(scope_ui_shown_loop.QuitClosure()); |
| |
| scoped_refptr<FakeGetAuthTokenFunction> func2(new FakeGetAuthTokenFunction()); |
| func2->set_extension(extension); |
| func2->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| func2->set_remote_consent_gaia_id(account.gaia); |
| |
| AsyncFunctionRunner func1_runner; |
| func1_runner.RunFunctionAsync(func1.get(), "[{\"interactive\": true}]", |
| profile()); |
| |
| AsyncFunctionRunner func2_runner; |
| func2_runner.RunFunctionAsync(func2.get(), "[{\"interactive\": true}]", |
| profile()); |
| |
| // Allows func2 to put a task in the queue. |
| base::RunLoop().RunUntilIdle(); |
| |
| scope_ui_shown_loop.Run(); |
| func1->CompleteRemoteConsentDialog(); |
| |
| std::string access_token1; |
| std::set<std::string> granted_scopes1; |
| WaitForGetAuthTokenResults(func1.get(), &access_token1, &granted_scopes1, |
| &func1_runner); |
| EXPECT_EQ(std::string(kAccessToken), access_token1); |
| EXPECT_EQ(func1->GetExtensionTokenKeyForTest()->scopes, granted_scopes1); |
| |
| std::string access_token2; |
| std::set<std::string> granted_scopes2; |
| WaitForGetAuthTokenResults(func2.get(), &access_token2, &granted_scopes2, |
| &func2_runner); |
| EXPECT_EQ(std::string(kAccessToken), access_token2); |
| EXPECT_EQ(func2->GetExtensionTokenKeyForTest()->scopes, granted_scopes2); |
| |
| // Only one consent ui should be shown. |
| int total_scope_ui_shown = func1->scope_ui_shown() + func2->scope_ui_shown(); |
| EXPECT_EQ(1, total_scope_ui_shown); |
| |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN, |
| GetCachedToken(account).status()); |
| |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 2); |
| } |
| |
| // Tests two concurrent remote consent flows. Both of them should succeed. |
| // The second flow starts while the first one is blocked on a non-interactive |
| // mint token flow. This is a regression test for https://crbug.com/1091423. |
| IN_PROC_BROWSER_TEST_F( |
| GetAuthTokenFunctionTest, |
| RemoteConsentMultipleActiveRequests_BlockedOnNoninteractive) { |
| SignIn("primary@example.com"); |
| CoreAccountInfo account = GetPrimaryAccountInfo(); |
| const extensions::Extension* extension = CreateExtension(CLIENT_ID | SCOPES); |
| |
| scoped_refptr<FakeGetAuthTokenFunction> func1(new FakeGetAuthTokenFunction()); |
| func1->set_extension(extension); |
| func1->push_mint_token_result( |
| TestOAuth2MintTokenFlow::REMOTE_CONSENT_SUCCESS); |
| func1->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| func1->set_remote_consent_gaia_id(account.gaia); |
| func1->set_auto_login_access_token(false); |
| |
| scoped_refptr<FakeGetAuthTokenFunction> func2(new FakeGetAuthTokenFunction()); |
| func2->set_extension(extension); |
| func2->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| func2->set_remote_consent_gaia_id(account.gaia); |
| base::RunLoop scope_ui_shown_loop; |
| func2->set_scope_ui_async(scope_ui_shown_loop.QuitClosure()); |
| |
| base::RunLoop access_token_run_loop; |
| on_access_token_requested_ = access_token_run_loop.QuitClosure(); |
| AsyncFunctionRunner func1_runner; |
| func1_runner.RunFunctionAsync(func1.get(), "[{\"interactive\": true}]", |
| profile()); |
| |
| AsyncFunctionRunner func2_runner; |
| func2_runner.RunFunctionAsync(func2.get(), "[{\"interactive\": true}]", |
| profile()); |
| |
| // Allows func2 to put a task in the queue. |
| base::RunLoop().RunUntilIdle(); |
| |
| access_token_run_loop.Run(); |
| // Let subsequent requests pass automatically. |
| func1->set_auto_login_access_token(true); |
| IssueLoginAccessTokenForAccount(account.account_id); |
| |
| scope_ui_shown_loop.Run(); |
| func2->CompleteRemoteConsentDialog(); |
| |
| std::string access_token1; |
| std::set<std::string> granted_scopes1; |
| WaitForGetAuthTokenResults(func1.get(), &access_token1, &granted_scopes1, |
| &func1_runner); |
| EXPECT_EQ(std::string(kAccessToken), access_token1); |
| EXPECT_EQ(func1->GetExtensionTokenKeyForTest()->scopes, granted_scopes1); |
| |
| std::string access_token2; |
| std::set<std::string> granted_scopes2; |
| WaitForGetAuthTokenResults(func2.get(), &access_token2, &granted_scopes2, |
| &func2_runner); |
| EXPECT_EQ(std::string(kAccessToken), access_token2); |
| EXPECT_EQ(func2->GetExtensionTokenKeyForTest()->scopes, granted_scopes2); |
| |
| // Only one consent ui should be shown. |
| int total_scope_ui_shown = func1->scope_ui_shown() + func2->scope_ui_shown(); |
| EXPECT_EQ(1, total_scope_ui_shown); |
| |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN, |
| GetCachedToken(account).status()); |
| |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 2); |
| } |
| |
| // The signin flow is simply not used on Ash. |
| #if !BUILDFLAG(IS_CHROMEOS) |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, |
| MultiSecondaryInteractiveInvalidToken) { |
| // Setup a secondary account with no valid refresh token, and try to get a |
| // auth token for it. |
| SignIn("primary@example.com"); |
| AccountInfo secondary_account = |
| identity_test_env()->MakeAccountAvailable("secondary@example.com"); |
| identity_test_env()->SetInvalidRefreshTokenForAccount( |
| secondary_account.account_id); |
| |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(extension.get()); |
| func->set_login_ui_result(true); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::REMOTE_CONSENT_SUCCESS); |
| func->set_remote_consent_gaia_id(secondary_account.gaia); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| |
| const char kFunctionParams[] = |
| "[{\"account\": { \"id\": \"gaia_id_for_secondary@example.com\" }, " |
| "\"interactive\": true}]"; |
| |
| if (id_api()->AreExtensionsRestrictedToPrimaryAccount()) { |
| // Fail if extensions are restricted to the primary account. |
| std::string error = utils::RunFunctionAndReturnError( |
| func.get(), kFunctionParams, profile()); |
| EXPECT_EQ(std::string(errors::kUserNonPrimary), error); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kUserNonPrimary, 1); |
| } else { |
| // Extensions can show the login UI for secondary accounts, and get the auth |
| // token. |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| RunGetAuthTokenFunction(func.get(), kFunctionParams, browser(), |
| &access_token, &granted_scopes); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| EXPECT_EQ(func->GetExtensionTokenKeyForTest()->scopes, granted_scopes); |
| EXPECT_TRUE(func->login_ui_shown()); |
| EXPECT_TRUE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kNone, 1); |
| } |
| } |
| #endif |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, ScopesDefault) { |
| SignIn("primary@example.com"); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_extension(extension.get()); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| RunGetAuthTokenFunction(func.get(), "[{}]", browser(), &access_token, |
| &granted_scopes); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| |
| const ExtensionTokenKey* token_key = func->GetExtensionTokenKeyForTest(); |
| EXPECT_EQ(token_key->scopes, granted_scopes); |
| EXPECT_EQ(2ul, token_key->scopes.size()); |
| EXPECT_TRUE(base::Contains(token_key->scopes, "scope1")); |
| EXPECT_TRUE(base::Contains(token_key->scopes, "scope2")); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, ScopesEmpty) { |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_extension(extension.get()); |
| |
| std::string error(utils::RunFunctionAndReturnError( |
| func.get(), "[{\"scopes\": []}]", profile())); |
| |
| EXPECT_EQ(errors::kInvalidScopes, error); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kEmptyScopes, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, ScopesEmail) { |
| SignIn("primary@example.com"); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_extension(extension.get()); |
| |
| std::set<std::string> scopes = {"email"}; |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS, |
| scopes); |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| RunGetAuthTokenFunction(func.get(), "[{\"scopes\": [\"email\"]}]", browser(), |
| &access_token, &granted_scopes); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| |
| const ExtensionTokenKey* token_key = func->GetExtensionTokenKeyForTest(); |
| EXPECT_EQ(token_key->scopes, granted_scopes); |
| EXPECT_EQ(scopes, token_key->scopes); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, ScopesEmailFooBar) { |
| SignIn("primary@example.com"); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_extension(extension.get()); |
| |
| std::set<std::string> scopes = {"email", "foo", "bar"}; |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS, |
| scopes); |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| RunGetAuthTokenFunction(func.get(), |
| "[{\"scopes\": [\"email\", \"foo\", \"bar\"]}]", |
| browser(), &access_token, &granted_scopes); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| |
| const ExtensionTokenKey* token_key = func->GetExtensionTokenKeyForTest(); |
| EXPECT_EQ(token_key->scopes, granted_scopes); |
| EXPECT_EQ(scopes, token_key->scopes); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| } |
| |
| // Ensure that the returned scopes from the function is the cached scopes and |
| // not the requested scopes. |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, SubsetMatchCacheHit) { |
| SignIn("primary@example.com"); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_extension(extension.get()); |
| |
| std::set<std::string> scopes = {"email", "foo", "bar"}; |
| IdentityTokenCacheValue token = IdentityTokenCacheValue::CreateToken( |
| kAccessToken, scopes, base::Seconds(3600)); |
| SetCachedToken(token); |
| |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| RunGetAuthTokenFunction(func.get(), "[{\"scopes\": [\"email\", \"foo\"]}]", |
| browser(), &access_token, &granted_scopes); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| EXPECT_EQ(scopes, granted_scopes); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| } |
| |
| // Ensure that the newly cached token uses the granted scopes and not the |
| // requested scopes. |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, SubsetMatchCachePopulate) { |
| SignIn("primary@example.com"); |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_extension(extension.get()); |
| |
| std::set<std::string> scopes = {"foo", "bar"}; |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS, |
| scopes); |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| RunGetAuthTokenFunction(func.get(), "[{\"scopes\": [\"email\", \"foo\"]}]", |
| browser(), &access_token, &granted_scopes); |
| |
| const IdentityTokenCacheValue& token = |
| GetCachedToken(CoreAccountInfo(), scopes); |
| EXPECT_EQ(std::string(kAccessToken), token.token()); |
| EXPECT_EQ(scopes, token.granted_scopes()); |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN, token.status()); |
| |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| } |
| |
| // Ensure that the scopes returned by the function reflects the granted scopes |
| // and not the requested scopes. |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, GranularPermissionsResponse) { |
| SignIn("primary@example.com"); |
| auto func = base::MakeRefCounted<FakeGetAuthTokenFunction>(); |
| auto extension = base::WrapRefCounted(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_extension(extension.get()); |
| |
| std::set<std::string> scopes = {"email", "foobar"}; |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS, |
| scopes); |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| RunGetAuthTokenFunction(func.get(), |
| "[{\"enableGranularPermissions\": true," |
| "\"scopes\": [\"email\", \"bar\"]}]", |
| browser(), &access_token, &granted_scopes); |
| EXPECT_EQ(kAccessToken, access_token); |
| EXPECT_EQ(scopes, granted_scopes); |
| |
| EXPECT_TRUE(func->enable_granular_permissions()); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| class GetAuthTokenFunctionDeviceLocalAccountTest |
| : public InProcessBrowserTestMixinHostSupport<GetAuthTokenFunctionTest> { |
| protected: |
| GetAuthTokenFunctionDeviceLocalAccountTest() { set_chromeos_user_ = false; } |
| |
| void RunExtensionAndVerifyNoError(bool is_extension_allowlisted) { |
| scoped_refptr<FakeGetAuthTokenFunction> func( |
| new FakeGetAuthTokenFunction()); |
| std::string extension_id = is_extension_allowlisted |
| ? "ljacajndfccfgnfohlgkdphmbnpkjflk" |
| : "test-id"; |
| func->set_extension(CreateTestExtension(extension_id)); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| auto* profile = Profile::FromBrowserContext( |
| ash::BrowserContextHelper::Get()->GetBrowserContextByUser( |
| user_manager::UserManager::Get()->GetActiveUser())); |
| RunGetAuthTokenFunction(func.get(), "[{}]", profile, &access_token, |
| &granted_scopes); |
| EXPECT_EQ(std::string(kAccessToken), access_token); |
| EXPECT_EQ(func->GetExtensionTokenKeyForTest()->scopes, granted_scopes); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kNone, 1); |
| } |
| |
| scoped_refptr<const Extension> CreateTestExtension(const std::string& id) { |
| return ExtensionBuilder("Test") |
| .SetManifestKey( |
| "oauth2", base::Value::Dict() |
| .Set("client_id", "clientId") |
| .Set("scopes", base::Value::List().Append("scope1"))) |
| .SetID(id) |
| .Build(); |
| } |
| |
| private: |
| // Set up fake install attributes to make the device appeared as |
| // enterprise-managed. |
| ash::ScopedStubInstallAttributes test_install_attributes_{ |
| ash::StubInstallAttributes::CreateCloudManaged("example.com", "fake-id")}; |
| }; |
| |
| class GetAuthTokenFunctionPublicSessionTest |
| : public GetAuthTokenFunctionDeviceLocalAccountTest { |
| private: |
| ash::PublicAccountLoggedInBrowserTestMixin mixin_{&mixin_host_, |
| "public-account"}; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionPublicSessionTest, NonAllowlisted) { |
| // GetAuthToken() should return UserNotSignedIn in public sessions for |
| // non-allowlisted extensions. |
| scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); |
| func->set_extension(CreateTestExtension("test-id")); |
| std::string error = |
| utils::RunFunctionAndReturnError(func.get(), "[]", profile()); |
| EXPECT_EQ(std::string(errors::kUserNotSignedIn), error); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, |
| IdentityGetAuthTokenError::State::kNotAllowlistedInPublicSession, 1); |
| } |
| |
| class GetAuthTokenFunctionChromeKioskTest |
| : public GetAuthTokenFunctionDeviceLocalAccountTest { |
| private: |
| ash::KioskAppLoggedInBrowserTestMixin mixin_{&mixin_host_, |
| "kiosk-app-account"}; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionChromeKioskTest, NonAllowlisted) { |
| // GetAuthToken() should return a token for non-allowlisted extensions in the |
| // Chrome Kiosk session. |
| RunExtensionAndVerifyNoError(/*is_extension_allowlisted=*/false); |
| } |
| |
| class GetAuthTokenFunctionWebKioskTest |
| : public GetAuthTokenFunctionDeviceLocalAccountTest { |
| private: |
| ash::WebKioskAppLoggedInBrowserTestMixin mixin_{&mixin_host_, |
| "web-kiosk-app-account"}; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionWebKioskTest, NonAllowlisted) { |
| // GetAuthToken() should return a token for non-allowlisted extensions in the |
| // web Kiosk session. |
| RunExtensionAndVerifyNoError(/*is_extension_allowlisted=*/false); |
| } |
| |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| // There are two parameters, which are stored in a std::pair, for these tests. |
| // |
| // std::string: the GetAuthToken arguments |
| // bool: the expected value of GetAuthToken's enable_granular_permissions |
| class GetAuthTokenFunctionEnableGranularPermissionsTest |
| : public GetAuthTokenFunctionTest, |
| public testing::WithParamInterface<std::pair<std::string, bool>> {}; |
| |
| // Provided with the arguments for GetAuthToken, ensures that GetAuthToken's |
| // enable_granular_permissions is some expected value when the |
| // 'ReturnScopesInGetAuthToken' feature flag is enabled. |
| IN_PROC_BROWSER_TEST_P(GetAuthTokenFunctionEnableGranularPermissionsTest, |
| EnableGranularPermissions) { |
| const std::string& args = GetParam().first; |
| bool expected_enable_granular_permissions = GetParam().second; |
| |
| SignIn("primary@example.com"); |
| auto func = base::MakeRefCounted<FakeGetAuthTokenFunction>(); |
| auto extension = base::WrapRefCounted(CreateExtension(CLIENT_ID | SCOPES)); |
| func->set_extension(extension.get()); |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| RunGetAuthTokenFunction(func.get(), "[{" + args + "}]", browser(), |
| &access_token, &granted_scopes); |
| EXPECT_EQ(kAccessToken, access_token); |
| EXPECT_EQ(func->GetExtensionTokenKeyForTest()->scopes, granted_scopes); |
| |
| EXPECT_EQ(expected_enable_granular_permissions, |
| func->enable_granular_permissions()); |
| EXPECT_FALSE(func->login_ui_shown()); |
| EXPECT_FALSE(func->scope_ui_shown()); |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| GetAuthTokenFunctionEnableGranularPermissionsTest, |
| testing::Values(std::make_pair("\"enableGranularPermissions\": true", true), |
| std::make_pair("\"enableGranularPermissions\": false", |
| false), |
| std::make_pair("", false))); |
| |
| class RemoveCachedAuthTokenFunctionTest : public ExtensionBrowserTest { |
| protected: |
| bool InvalidateDefaultToken() { |
| scoped_refptr<IdentityRemoveCachedAuthTokenFunction> func( |
| new IdentityRemoveCachedAuthTokenFunction); |
| func->set_extension( |
| ExtensionBuilder("Test").SetID(kExtensionId).Build().get()); |
| return utils::RunFunction( |
| func.get(), std::string("[{\"token\": \"") + kAccessToken + "\"}]", |
| profile(), api_test_utils::FunctionMode::kNone); |
| } |
| |
| IdentityAPI* id_api() { |
| return IdentityAPI::GetFactoryInstance()->Get(profile()); |
| } |
| |
| IdentityTokenCacheValue CreateToken(const std::string& token, |
| base::TimeDelta time_to_live) { |
| return IdentityTokenCacheValue::CreateToken( |
| token, std::set<std::string>({"foo"}), time_to_live); |
| } |
| |
| void SetCachedToken(const IdentityTokenCacheValue& token_data) { |
| CoreAccountInfo account_info; |
| account_info.account_id = CoreAccountId::FromGaiaId(GaiaId("test_gaia")); |
| account_info.gaia = GaiaId("test_gaia"); |
| account_info.email = "test@example.com"; |
| ExtensionTokenKey key(kExtensionId, account_info, |
| std::set<std::string>({"foo"})); |
| id_api()->token_cache()->SetToken(key, token_data); |
| } |
| |
| const IdentityTokenCacheValue& GetCachedToken() { |
| CoreAccountInfo account_info; |
| account_info.account_id = CoreAccountId::FromGaiaId(GaiaId("test_gaia")); |
| account_info.gaia = GaiaId("test_gaia"); |
| account_info.email = "test@example.com"; |
| ExtensionTokenKey key(kExtensionId, account_info, |
| std::set<std::string>({"foo"})); |
| return id_api()->token_cache()->GetToken(key); |
| } |
| }; |
| |
| class GetAuthTokenFunctionSelectedUserIdTest : public GetAuthTokenFunctionTest { |
| public: |
| // Executes a new function and checks that the selected_user_id is the |
| // expected value. The interactive and scopes field are predefined. |
| // The account id specified by the extension is optional. |
| void RunNewFunctionAndExpectSelectedUserId( |
| const scoped_refptr<const extensions::Extension>& extension, |
| const GaiaId& expected_selected_user_id, |
| const std::optional<GaiaId> requested_account = std::nullopt) { |
| auto func = base::MakeRefCounted<FakeGetAuthTokenFunction>(); |
| func->set_extension(extension); |
| RunFunctionAndExpectSelectedUserId(func, expected_selected_user_id, |
| requested_account); |
| } |
| |
| void RunFunctionAndExpectSelectedUserId( |
| const scoped_refptr<FakeGetAuthTokenFunction>& func, |
| const GaiaId& expected_selected_user_id, |
| const std::optional<GaiaId> requested_account = std::nullopt) { |
| // Stops the function right before selected_user_id would be used. |
| MockQueuedMintRequest queued_request; |
| IdentityMintRequestQueue::MintType type = |
| IdentityMintRequestQueue::MINT_TYPE_INTERACTIVE; |
| EXPECT_CALL(queued_request, StartMintToken(type)).Times(1); |
| QueueRequestStart(type, &queued_request); |
| |
| func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); |
| |
| std::string requested_account_arg = |
| requested_account.has_value() |
| ? ", \"account\": {\"id\": \"" + requested_account->ToString() + |
| "\"}" |
| : ""; |
| RunFunctionAsync(func.get(), |
| "[{\"interactive\": true" + requested_account_arg + "}]"); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(expected_selected_user_id, func->GetSelectedUserId()); |
| |
| // Resume the function |
| QueueRequestComplete(type, &queued_request); |
| |
| // Complete function and do some basic checks. |
| std::string access_token; |
| std::set<std::string> granted_scopes; |
| WaitForGetAuthTokenResults(func.get(), &access_token, &granted_scopes); |
| EXPECT_EQ(kAccessToken, access_token); |
| EXPECT_EQ(func->GetExtensionTokenKeyForTest()->scopes, granted_scopes); |
| } |
| }; |
| |
| // Tests that Chrome uses the correct selected user id value when a gaia id was |
| // cached and only the primary account is signed in. |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionSelectedUserIdTest, SingleAccount) { |
| auto extension = base::WrapRefCounted(CreateExtension(CLIENT_ID | SCOPES)); |
| SignIn("primary@example.com"); |
| CoreAccountInfo primary_account = GetPrimaryAccountInfo(); |
| |
| SetCachedGaiaId(primary_account.gaia); |
| RunNewFunctionAndExpectSelectedUserId(extension, primary_account.gaia); |
| |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| } |
| |
| // Tests that Chrome uses the correct selected user id value when a gaia id was |
| // cached for a secondary account. |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionSelectedUserIdTest, |
| MultipleAccounts) { |
| // This test requires the use of a secondary account. If extensions are |
| // restricted to primary account only, this test wouldn't make too much sense. |
| if (id_api()->AreExtensionsRestrictedToPrimaryAccount()) { |
| return; |
| } |
| |
| auto extension = base::WrapRefCounted(CreateExtension(CLIENT_ID | SCOPES)); |
| SignIn("primary@example.com"); |
| AccountInfo secondary_account = |
| identity_test_env()->MakeAccountAvailable("secondary@example.com"); |
| |
| SetCachedGaiaId(secondary_account.gaia); |
| RunNewFunctionAndExpectSelectedUserId(extension, secondary_account.gaia); |
| |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| } |
| |
| // Tests that Chrome uses the correct selected user id value when a gaia id was |
| // cached but the extension specifies an account id for a different available |
| // account. |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionSelectedUserIdTest, |
| RequestedAccountAvailable) { |
| // This test requires the use of a secondary account. If extensions are |
| // restricted to primary account only, this test wouldn't make too much sense. |
| if (id_api()->AreExtensionsRestrictedToPrimaryAccount()) { |
| return; |
| } |
| |
| auto extension = base::WrapRefCounted(CreateExtension(CLIENT_ID | SCOPES)); |
| SignIn("primary@example.com"); |
| CoreAccountInfo primary_account = GetPrimaryAccountInfo(); |
| AccountInfo secondary_account = |
| identity_test_env()->MakeAccountAvailable("secondary@example.com"); |
| |
| SetCachedGaiaId(primary_account.gaia); |
| // Run a new function with an account id specified in the arguments. |
| RunNewFunctionAndExpectSelectedUserId(extension, secondary_account.gaia, |
| secondary_account.gaia); |
| |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| } |
| |
| // The signin flow is not used on Ash. |
| #if !BUILDFLAG(IS_CHROMEOS) |
| // Tests that Chrome does not have any selected user id value if the account |
| // specified by the extension is not available. |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionSelectedUserIdTest, |
| RequestedAccountUnavailable) { |
| // This test requires the use of a secondary account. If extensions are |
| // restricted to primary account only, this test wouldn't make too much sense. |
| if (id_api()->AreExtensionsRestrictedToPrimaryAccount()) { |
| return; |
| } |
| |
| auto extension = base::WrapRefCounted(CreateExtension(CLIENT_ID | SCOPES)); |
| SignIn("primary@example.com"); |
| |
| // Run a new function with an account id specified. Since this account is not |
| // signed in, the login screen will be shown. |
| auto func = base::MakeRefCounted<FakeGetAuthTokenFunction>(); |
| func->set_extension(extension); |
| func->set_login_ui_result(true); |
| RunFunctionAndExpectSelectedUserId( |
| func, GaiaId(), GaiaId("gaia_id_for_unavailable_example.com")); |
| // The login ui still showed but another account was logged in instead. |
| EXPECT_TRUE(func->login_ui_shown()); |
| |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| } |
| |
| // Tests that Chrome uses the correct selected user id value after logging into |
| // the account requested by the extension. |
| IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionSelectedUserIdTest, |
| RequestedAccountLogin) { |
| // This test requires the use of a secondary account. If extensions are |
| // restricted to primary account only, this test wouldn't make too much sense. |
| if (id_api()->AreExtensionsRestrictedToPrimaryAccount()) { |
| return; |
| } |
| |
| auto extension = base::WrapRefCounted(CreateExtension(CLIENT_ID | SCOPES)); |
| SignIn("primary@example.com"); |
| |
| // Run a new function with an account id specified. Since this account is not |
| // signed in, the login screen will be shown. |
| auto func = base::MakeRefCounted<FakeGetAuthTokenFunction>(); |
| func->set_extension(extension); |
| func->set_login_ui_result(true); |
| RunFunctionAndExpectSelectedUserId( |
| func, GaiaId("gaia_id_for_secondary_example.com"), |
| GaiaId("gaia_id_for_secondary_example.com")); |
| EXPECT_TRUE(func->login_ui_shown()); |
| |
| histogram_tester()->ExpectUniqueSample( |
| kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, |
| 1); |
| } |
| #endif |
| |
| IN_PROC_BROWSER_TEST_F(RemoveCachedAuthTokenFunctionTest, NotFound) { |
| EXPECT_TRUE(InvalidateDefaultToken()); |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND, |
| GetCachedToken().status()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(RemoveCachedAuthTokenFunctionTest, RemoteConsent) { |
| RemoteConsentResolutionData resolution_data; |
| IdentityTokenCacheValue advice = |
| IdentityTokenCacheValue::CreateRemoteConsent(resolution_data); |
| SetCachedToken(advice); |
| EXPECT_TRUE(InvalidateDefaultToken()); |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_REMOTE_CONSENT, |
| GetCachedToken().status()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(RemoveCachedAuthTokenFunctionTest, NonMatchingToken) { |
| IdentityTokenCacheValue token = |
| CreateToken("non_matching_token", base::Seconds(3600)); |
| SetCachedToken(token); |
| EXPECT_TRUE(InvalidateDefaultToken()); |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN, |
| GetCachedToken().status()); |
| EXPECT_EQ("non_matching_token", GetCachedToken().token()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(RemoveCachedAuthTokenFunctionTest, MatchingToken) { |
| IdentityTokenCacheValue token = |
| CreateToken(kAccessToken, base::Seconds(3600)); |
| SetCachedToken(token); |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN, |
| GetCachedToken().status()); |
| EXPECT_TRUE(InvalidateDefaultToken()); |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND, |
| GetCachedToken().status()); |
| } |
| |
| class LaunchWebAuthFlowFunctionTest : public AsyncExtensionBrowserTest { |
| public: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| AsyncExtensionBrowserTest::SetUpCommandLine(command_line); |
| // Reduce performance test variance by disabling background networking. |
| command_line->AppendSwitch(switches::kDisableBackgroundNetworking); |
| } |
| |
| base::HistogramTester* histogram_tester() { return &histogram_tester_; } |
| |
| private: |
| base::HistogramTester histogram_tester_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTest, InteractionRequired) { |
| std::unique_ptr<net::EmbeddedTestServer> https_server = LaunchHttpsServer(); |
| GURL auth_url(https_server->GetURL("/interaction_required.html")); |
| |
| scoped_refptr<IdentityLaunchWebAuthFlowFunction> function = |
| CreateLaunchWebAuthFlowFunction(); |
| |
| std::string args = |
| "[{\"interactive\": false, \"url\": \"" + auth_url.spec() + "\"}]"; |
| std::string error = |
| utils::RunFunctionAndReturnError(function.get(), args, profile()); |
| |
| EXPECT_EQ(std::string(errors::kInteractionRequired), error); |
| histogram_tester()->ExpectUniqueSample( |
| kLaunchWebAuthFlowResultHistogramName, |
| IdentityLaunchWebAuthFlowFunction::Error::kInteractionRequired, 1); |
| } |
| |
| // Checks that, by default, when a page fully loads in silent mode and doesn't |
| // redirect, `launchWebAuthFlow()` terminates with an error. |
| IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTest, |
| InteractionRequiredAfterLoad) { |
| std::unique_ptr<net::EmbeddedTestServer> https_server = LaunchHttpsServer(); |
| GURL auth_url(https_server->GetURL("/redirect_after_load.html")); |
| |
| scoped_refptr<IdentityLaunchWebAuthFlowFunction> function = |
| CreateLaunchWebAuthFlowFunction(); |
| |
| std::string args = base::StringPrintf( |
| R"([{"interactive": false, "url": "%s"}])", auth_url.spec().c_str()); |
| std::string error = |
| utils::RunFunctionAndReturnError(function.get(), args, profile()); |
| |
| EXPECT_EQ(errors::kInteractionRequired, error); |
| } |
| |
| // Checks that when a page fully loads in silent mode and doesn't redirect, |
| // `launchWebAuthFlow()` terminates with an error after a specific timeout. |
| IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTest, |
| InteractionRequiredWithTimeout) { |
| std::unique_ptr<net::EmbeddedTestServer> https_server = LaunchHttpsServer(); |
| GURL auth_url(https_server->GetURL("/interaction_required.html")); |
| |
| scoped_refptr<IdentityLaunchWebAuthFlowFunction> function = |
| CreateLaunchWebAuthFlowFunction(); |
| |
| std::string args = base::StringPrintf( |
| R"([{ |
| "interactive": false, |
| "url": "%s", |
| "abortOnLoadForNonInteractive": false, |
| "timeoutMsForNonInteractive": 5000 |
| }])", |
| auth_url.spec().c_str()); |
| std::string error = |
| utils::RunFunctionAndReturnError(function.get(), args, profile()); |
| |
| // The function is expected to return `errors::kInteractionRequired` as the |
| // page is expected to be loaded within the allotted time, but a race is still |
| // possible where page load takes more than 5s, in which case |
| // `errors::kPageLoadTimedOut` is returned. Accept both errors here because we |
| // don't have a reliable way of sequencing page load before timeout in these |
| // tests. |
| EXPECT_TRUE(error == errors::kInteractionRequired || |
| error == errors::kPageLoadTimedOut); |
| } |
| |
| // Checks that when a page fails to fully load before timeout in silent mode, |
| // `launchWebAuthFlow()` terminates with an error after a specific timeout. |
| IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTest, LoadTimedOut) { |
| std::unique_ptr<net::EmbeddedTestServer> https_server = LaunchHttpsServer(); |
| GURL auth_url(https_server->GetURL("/interaction_required.html")); |
| |
| scoped_refptr<IdentityLaunchWebAuthFlowFunction> function = |
| CreateLaunchWebAuthFlowFunction(); |
| |
| std::string args = base::StringPrintf( |
| R"([{ |
| "interactive": false, |
| "url": "%s", |
| "abortOnLoadForNonInteractive": false, |
| "timeoutMsForNonInteractive": 10 |
| }])", |
| auth_url.spec().c_str()); |
| std::string error = |
| utils::RunFunctionAndReturnError(function.get(), args, profile()); |
| |
| EXPECT_EQ(errors::kPageLoadTimedOut, error); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTest, LoadFailed) { |
| std::unique_ptr<net::EmbeddedTestServer> https_server = LaunchHttpsServer(); |
| GURL auth_url(https_server->GetURL("/five_hundred.html")); |
| |
| scoped_refptr<IdentityLaunchWebAuthFlowFunction> function = |
| CreateLaunchWebAuthFlowFunction(); |
| |
| std::string args = |
| "[{\"interactive\": true, \"url\": \"" + auth_url.spec() + "\"}]"; |
| std::string error = |
| utils::RunFunctionAndReturnError(function.get(), args, profile()); |
| |
| EXPECT_EQ(std::string(errors::kPageLoadFailure), error); |
| histogram_tester()->ExpectUniqueSample( |
| kLaunchWebAuthFlowResultHistogramName, |
| IdentityLaunchWebAuthFlowFunction::Error::kPageLoadFailure, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTest, NonInteractiveSuccess) { |
| scoped_refptr<IdentityLaunchWebAuthFlowFunction> function = |
| CreateLaunchWebAuthFlowFunction(); |
| |
| function->InitFinalRedirectURLDomainsForTest("abcdefghij"); |
| std::optional<base::Value> value = utils::RunFunctionAndReturnSingleResult( |
| function.get(), |
| "[{\"interactive\": false," |
| "\"url\": \"https://abcdefghij.chromiumapp.org/callback#test\"}]", |
| profile()); |
| |
| EXPECT_TRUE(value->is_string()); |
| EXPECT_EQ(std::string("https://abcdefghij.chromiumapp.org/callback#test"), |
| value->GetString()); |
| histogram_tester()->ExpectUniqueSample( |
| kLaunchWebAuthFlowResultHistogramName, |
| IdentityLaunchWebAuthFlowFunction::Error::kNone, 1); |
| } |
| |
| // Checks that when a page fully loads in silent mode and then performs a |
| // JavaScript redirect `launchWebAuthFlow()` finishes successfully. |
| IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTest, |
| NonInteractiveSuccessAfterLoad) { |
| std::unique_ptr<net::EmbeddedTestServer> https_server = LaunchHttpsServer(); |
| GURL auth_url(https_server->GetURL("/redirect_after_load.html")); |
| |
| scoped_refptr<IdentityLaunchWebAuthFlowFunction> function = |
| CreateLaunchWebAuthFlowFunction(); |
| |
| function->InitFinalRedirectURLDomainsForTest("abcdefghij"); |
| std::string args = base::StringPrintf( |
| R"([{ |
| "interactive": false, |
| "url": "%s", |
| "abortOnLoadForNonInteractive": false, |
| "timeoutMsForNonInteractive": 20000 |
| }])", |
| auth_url.spec().c_str()); |
| std::optional<base::Value> value = |
| utils::RunFunctionAndReturnSingleResult(function.get(), args, profile()); |
| |
| EXPECT_TRUE(value->is_string()); |
| EXPECT_EQ("https://abcdefghij.chromiumapp.org/callback#test", |
| value->GetString()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTest, |
| InteractiveFirstNavigationSuccess) { |
| scoped_refptr<IdentityLaunchWebAuthFlowFunction> function = |
| CreateLaunchWebAuthFlowFunction(); |
| |
| function->InitFinalRedirectURLDomainsForTest("abcdefghij"); |
| std::optional<base::Value> value = utils::RunFunctionAndReturnSingleResult( |
| function.get(), |
| "[{\"interactive\": true," |
| "\"url\": \"https://abcdefghij.chromiumapp.org/callback#test\"}]", |
| profile()); |
| |
| EXPECT_TRUE(value->is_string()); |
| EXPECT_EQ(std::string("https://abcdefghij.chromiumapp.org/callback#test"), |
| value->GetString()); |
| histogram_tester()->ExpectUniqueSample( |
| kLaunchWebAuthFlowResultHistogramName, |
| IdentityLaunchWebAuthFlowFunction::Error::kNone, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTest, |
| InteractiveSecondNavigationSuccess) { |
| std::unique_ptr<net::EmbeddedTestServer> https_server = LaunchHttpsServer(); |
| GURL auth_url(https_server->GetURL("/redirect_to_chromiumapp.html")); |
| |
| scoped_refptr<IdentityLaunchWebAuthFlowFunction> function = |
| CreateLaunchWebAuthFlowFunction(); |
| |
| function->InitFinalRedirectURLDomainsForTest("abcdefghij"); |
| std::string args = |
| "[{\"interactive\": true, \"url\": \"" + auth_url.spec() + "\"}]"; |
| std::optional<base::Value> value = |
| utils::RunFunctionAndReturnSingleResult(function.get(), args, profile()); |
| |
| EXPECT_TRUE(value->is_string()); |
| EXPECT_EQ(std::string("https://abcdefghij.chromiumapp.org/callback#test"), |
| value->GetString()); |
| histogram_tester()->ExpectUniqueSample( |
| kLaunchWebAuthFlowResultHistogramName, |
| IdentityLaunchWebAuthFlowFunction::Error::kNone, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTest, UserCloseWindow) { |
| std::unique_ptr<net::EmbeddedTestServer> https_server = LaunchHttpsServer(); |
| GURL auth_url(https_server->GetURL("/interaction_required.html")); |
| |
| scoped_refptr<IdentityLaunchWebAuthFlowFunction> function = |
| CreateLaunchWebAuthFlowFunction(); |
| |
| content::TestNavigationObserver url_obvserver(auth_url); |
| url_obvserver.StartWatchingNewWebContents(); |
| |
| std::string args = |
| "[{\"interactive\": true, \"url\": \"" + auth_url.spec() + "\"}]"; |
| RunFunctionAsync(function.get(), args); |
| |
| url_obvserver.Wait(); |
| |
| Browser* popup_browser = chrome::FindBrowserWithTab( |
| function->GetWebAuthFlowForTesting()->web_contents()); |
| TabStripModel* tabs = popup_browser->tab_strip_model(); |
| EXPECT_NE(browser(), popup_browser); |
| ASSERT_EQ(tabs->GetActiveWebContents()->GetURL(), auth_url); |
| // Close the opened auth web contents. |
| tabs->CloseWebContentsAt(tabs->active_index(), 0); |
| |
| EXPECT_EQ(std::string(errors::kUserRejected), WaitForError(function.get())); |
| histogram_tester()->ExpectUniqueSample( |
| kLaunchWebAuthFlowResultHistogramName, |
| IdentityLaunchWebAuthFlowFunction::Error::kUserRejected, 1); |
| } |
| |
| #if !BUILDFLAG(IS_CHROMEOS) |
| IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTest, CloseBrowser) { |
| std::unique_ptr<net::EmbeddedTestServer> https_server = |
| std::make_unique<net::EmbeddedTestServer>( |
| net::EmbeddedTestServer::TYPE_HTTPS); |
| net::test_server::RegisterDefaultHandlers(https_server.get()); |
| EXPECT_TRUE(https_server->Start()); |
| // We want to interrupt the flow before `auth_url` gets loaded. To ensure that |
| // an URL doesn't load prematurely, use a default test URL that never returns |
| // a response. |
| GURL auth_url(https_server->GetURL("/hung")); |
| auto keep_alive = std::make_unique<ScopedKeepAlive>( |
| KeepAliveOrigin::BROWSER, KeepAliveRestartOption::DISABLED); |
| scoped_refptr<IdentityLaunchWebAuthFlowFunction> function = |
| CreateLaunchWebAuthFlowFunction(); |
| std::string args = |
| "[{\"interactive\": true, \"url\": \"" + auth_url.spec() + "\"}]"; |
| RunFunctionAsync(function.get(), args); |
| CloseBrowserSynchronously(browser()); |
| |
| // The ongoing navigation to auth_url will be skipped if the profile shutdown |
| // has already started, hence the error message below will reflect a shutdown |
| // context. |
| EXPECT_EQ(std::string(errors::kBrowserContextShutDown), |
| WaitForError(function.get())); |
| base::SingleThreadTaskRunner::GetCurrentDefault()->DeleteSoon( |
| FROM_HERE, std::move(keep_alive)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTest, DestroyProfile) { |
| ProfileManager* profile_manager = g_browser_process->profile_manager(); |
| base::FilePath path_profile2 = |
| profile_manager->GenerateNextProfileDirectoryPath(); |
| // Create an additional profile. |
| Profile& profile2 = |
| profiles::testing::CreateProfileSync(profile_manager, path_profile2); |
| |
| std::unique_ptr<net::EmbeddedTestServer> https_server = |
| std::make_unique<net::EmbeddedTestServer>( |
| net::EmbeddedTestServer::TYPE_HTTPS); |
| net::test_server::RegisterDefaultHandlers(https_server.get()); |
| EXPECT_TRUE(https_server->Start()); |
| // Make sure we can shutdown profile before getting response. |
| GURL auth_url(https_server->GetURL("/hung")); |
| auto keep_alive = std::make_unique<ScopedKeepAlive>( |
| KeepAliveOrigin::BROWSER, KeepAliveRestartOption::DISABLED); |
| scoped_refptr<IdentityLaunchWebAuthFlowFunction> function = |
| CreateLaunchWebAuthFlowFunction(); |
| std::string args = |
| "[{\"interactive\": true, \"url\": \"" + auth_url.spec() + "\"}]"; |
| |
| AsyncFunctionRunner func_runner; |
| func_runner.RunFunctionAsync(function.get(), args, &profile2); |
| |
| // Destroy profile while waiting for a response. |
| g_browser_process->profile_manager() |
| ->GetDeleteProfileHelper() |
| .MaybeScheduleProfileForDeletion( |
| profile2.GetPath(), base::DoNothing(), |
| ProfileMetrics::DELETE_PROFILE_USER_MANAGER); |
| |
| // The ongoing navigation to auth_url will be skipped if the profile shutdown |
| // has already started, hence the error message below will reflect a shutdown |
| // context. |
| EXPECT_EQ(std::string(errors::kBrowserContextShutDown), |
| func_runner.WaitForError(function.get())); |
| } |
| |
| #endif // !BUILDFLAG(IS_CHROMEOS) |
| |
| // Regression test for http://b/290733700. |
| IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTest, |
| SchemeOtherThanHttpOrHttpsNotAllowed) { |
| // Only http and https schemes are allowed. |
| GURL invalid_auth_url("chrome-untrusted://some_chrome_url"); |
| scoped_refptr<IdentityLaunchWebAuthFlowFunction> function = |
| CreateLaunchWebAuthFlowFunction(); |
| |
| std::string args = |
| "[{\"interactive\": true, \"url\": \"" + invalid_auth_url.spec() + "\"}]"; |
| RunFunctionAsync(function.get(), args); |
| |
| EXPECT_EQ(std::string(errors::kInvalidURLScheme), |
| WaitForError(function.get())); |
| histogram_tester()->ExpectUniqueSample( |
| kLaunchWebAuthFlowResultHistogramName, |
| IdentityLaunchWebAuthFlowFunction::Error::kInvalidURLScheme, 1); |
| } |
| |
| class LaunchWebAuthFlowFunctionTestWithBrowserTab |
| : public LaunchWebAuthFlowFunctionTest { |
| protected: |
| void RunFunctionAndWaitForNavigation( |
| IdentityLaunchWebAuthFlowFunction* function, |
| const GURL& url, |
| const std::string& args) { |
| content::TestNavigationObserver url_observer(url); |
| url_observer.StartWatchingNewWebContents(); |
| RunFunctionAsync(function, args); |
| url_observer.Wait(); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTestWithBrowserTab, |
| PageNavigateFromInitURLToFinalURL) { |
| std::unique_ptr<net::EmbeddedTestServer> https_server = LaunchHttpsServer(); |
| scoped_refptr<IdentityLaunchWebAuthFlowFunction> function = |
| CreateLaunchWebAuthFlowFunction(); |
| |
| const std::string extension_id("abcdefghij"); |
| function->InitFinalRedirectURLDomainsForTest(extension_id); |
| |
| const GURL auth_url(https_server->GetURL("/consent_page.html")); |
| const GURL final_url("https://" + extension_id + ".chromiumapp.org/"); |
| |
| const std::string args = |
| "[{\"interactive\": true, \"url\": \"" + auth_url.spec() + "\"}]"; |
| RunFunctionAndWaitForNavigation(function.get(), auth_url, args); |
| |
| SimulateUrlRedirect(extension_id, |
| function->GetWebAuthFlowForTesting()->web_contents()); |
| |
| base::Value output; |
| WaitForOneResult(function.get(), &output); |
| EXPECT_FALSE(function->GetWebAuthFlowForTesting()); |
| EXPECT_EQ(GURL(output.GetString()).Resolve("/"), final_url); |
| histogram_tester()->ExpectUniqueSample( |
| kLaunchWebAuthFlowResultHistogramName, |
| IdentityLaunchWebAuthFlowFunction::Error::kNone, 1); |
| } |
| |
| class TestDelegate : public LaunchWebAuthFlowDelegate { |
| public: |
| // LaunchWebAuthFlowDelegate: |
| void GetOptionalWindowBounds( |
| Profile* profile, |
| const std::string& extension_id, |
| base::OnceCallback<void(std::optional<gfx::Rect>)> callback) override { |
| std::move(callback).Run(kTestBounds); |
| } |
| |
| static constexpr gfx::Rect kTestBounds = gfx::Rect(23, 27, 400, 400); |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTestWithBrowserTab, |
| PopupBoundsComeFromDelegate) { |
| std::unique_ptr<net::EmbeddedTestServer> https_server = LaunchHttpsServer(); |
| GURL auth_url(https_server->GetURL("/interaction_required.html")); |
| |
| scoped_refptr<IdentityLaunchWebAuthFlowFunction> function = |
| CreateLaunchWebAuthFlowFunction(); |
| function->SetLaunchWebAuthFlowDelegateForTesting( |
| std::make_unique<TestDelegate>()); |
| |
| ui_test_utils::BrowserCreatedObserver browser_opened; |
| |
| std::string args = |
| "[{\"interactive\": true, \"url\": \"" + auth_url.spec() + "\"}]"; |
| RunFunctionAsync(function.get(), args); |
| |
| Browser* popup_browser = browser_opened.Wait(); |
| gfx::Rect bounds = popup_browser->window()->GetBounds(); |
| EXPECT_EQ(bounds.x(), TestDelegate::kTestBounds.x()); |
| EXPECT_EQ(bounds.y(), TestDelegate::kTestBounds.y()); |
| // The final width and height can contain platform-specific offsets for the |
| // window title bar, which we don't want to assert exactly here. |
| EXPECT_GE(bounds.width(), TestDelegate::kTestBounds.width()); |
| EXPECT_GE(bounds.height(), TestDelegate::kTestBounds.height()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTestWithBrowserTab, |
| PageNavigateFromInitURLToCustomFinalURL) { |
| std::unique_ptr<net::EmbeddedTestServer> https_server = LaunchHttpsServer(); |
| scoped_refptr<IdentityLaunchWebAuthFlowFunction> function = |
| CreateLaunchWebAuthFlowFunction(); |
| |
| const GURL auth_url(https_server->GetURL("/consent_page.html")); |
| const GURL final_url("example://example.com/"); |
| |
| const std::string args = |
| "[{\"interactive\": true, \"url\": \"" + auth_url.spec() + "\"}]"; |
| |
| profile()->GetPrefs()->SetDict( |
| extensions::pref_names::kOAuthRedirectUrls, |
| base::Value::Dict().Set(function->extension()->id(), |
| base::Value::List().Append(final_url.spec()))); |
| RunFunctionAndWaitForNavigation(function.get(), auth_url, args); |
| |
| SimulateCustomUrlRedirect( |
| final_url.spec() + "#some_information", |
| function->GetWebAuthFlowForTesting()->web_contents()); |
| |
| base::Value output; |
| WaitForOneResult(function.get(), &output); |
| EXPECT_FALSE(function->GetWebAuthFlowForTesting()); |
| EXPECT_EQ(GURL(output.GetString()).Resolve("/"), final_url); |
| histogram_tester()->ExpectUniqueSample( |
| kLaunchWebAuthFlowResultHistogramName, |
| IdentityLaunchWebAuthFlowFunction::Error::kNone, 1); |
| } |
| |
| // TODO(crbug.com/40259192): This test should be adapted after the |
| // implementation of the bug. Multiple TODOs in the test to fix. |
| IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTestWithBrowserTab, |
| SimilarExtensionAndArgsShouldGenerateSameFlow) { |
| std::unique_ptr<net::EmbeddedTestServer> https_server = LaunchHttpsServer(); |
| scoped_refptr<IdentityLaunchWebAuthFlowFunction> function1 = |
| CreateLaunchWebAuthFlowFunction(); |
| scoped_refptr<IdentityLaunchWebAuthFlowFunction> function2 = |
| CreateLaunchWebAuthFlowFunction(); |
| |
| const std::string extension_id("final_url"); |
| function1->InitFinalRedirectURLDomainsForTest(extension_id); |
| function2->InitFinalRedirectURLDomainsForTest(extension_id); |
| |
| const GURL auth_url(https_server->GetURL("/consent_page.html")); |
| const GURL final_url("https://" + extension_id + ".chromiumapp.org/"); |
| |
| // Same args used in both functions. |
| const std::string args = |
| "[{\"interactive\": true, \"url\": \"" + auth_url.spec() + "\"}]"; |
| |
| // Activate function1. |
| RunFunctionAndWaitForNavigation(function1.get(), auth_url, args); |
| // Activate function2. |
| RunFunctionAndWaitForNavigation(function2.get(), auth_url, args); |
| |
| content::WebContents* consent_web_contents1 = |
| function1->GetWebAuthFlowForTesting()->web_contents(); |
| content::WebContents* consent_web_contents2 = |
| function2->GetWebAuthFlowForTesting()->web_contents(); |
| // TODO(crbug.com/40259192): These two should be equal, EXPECT_EQ. |
| EXPECT_NE(consent_web_contents1, consent_web_contents2); |
| |
| // `SimulateUrlRedirect()` on first action should not affect the second |
| // function. |
| SimulateUrlRedirect(extension_id, consent_web_contents1); |
| |
| base::Value output1; |
| WaitForOneResult(function1.get(), &output1); |
| EXPECT_FALSE(function1->GetWebAuthFlowForTesting()); |
| // TODO(crbug.com/40259192): This should be EXPECT_FALSE. |
| EXPECT_TRUE(function2->GetWebAuthFlowForTesting()); |
| EXPECT_TRUE(output1.GetString().find(final_url.spec()) != std::string::npos); |
| EXPECT_TRUE(output1.GetString().find("#access_token=")); |
| |
| // TODO(crbug.com/40259192): 2 samples should be recorded instead of 1. |
| histogram_tester()->ExpectUniqueSample( |
| kLaunchWebAuthFlowResultHistogramName, |
| IdentityLaunchWebAuthFlowFunction::Error::kNone, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTestWithBrowserTab, |
| DifferentExtensionsShouldGenerateDifferentFlows) { |
| std::unique_ptr<net::EmbeddedTestServer> https_server = LaunchHttpsServer(); |
| scoped_refptr<IdentityLaunchWebAuthFlowFunction> function1 = |
| CreateLaunchWebAuthFlowFunction(); |
| scoped_refptr<IdentityLaunchWebAuthFlowFunction> function2 = |
| CreateLaunchWebAuthFlowFunction(); |
| |
| const std::string extension_id1("extension1"); |
| function1->InitFinalRedirectURLDomainsForTest(extension_id1); |
| const std::string extension_id2("extension2"); |
| function2->InitFinalRedirectURLDomainsForTest(extension_id2); |
| |
| const GURL auth_url(https_server->GetURL("/consent_page.html")); |
| // Different final_urls. |
| const GURL final_url1("https://" + extension_id1 + ".chromiumapp.org/"); |
| const GURL final_url2("https://" + extension_id2 + ".chromiumapp.org/"); |
| |
| // Same args used in both functions. |
| const std::string args = |
| "[{\"interactive\": true, \"url\": \"" + auth_url.spec() + "\"}]"; |
| |
| RunFunctionAndWaitForNavigation(function1.get(), auth_url, args); |
| RunFunctionAndWaitForNavigation(function2.get(), auth_url, args); |
| |
| content::WebContents* consent_web_contents1 = |
| function1->GetWebAuthFlowForTesting()->web_contents(); |
| content::WebContents* consent_web_contents2 = |
| function2->GetWebAuthFlowForTesting()->web_contents(); |
| EXPECT_NE(consent_web_contents1, consent_web_contents2); |
| |
| const std::string& current_consent_url2 = |
| consent_web_contents2->GetURL().spec(); |
| |
| // SimulateConsent on first action should not affect the second function. |
| SimulateUrlRedirect(extension_id1, consent_web_contents1); |
| |
| base::Value output1; |
| WaitForOneResult(function1.get(), &output1); |
| // `function2` state should remain. |
| EXPECT_EQ(current_consent_url2, consent_web_contents2->GetURL().spec()); |
| EXPECT_FALSE(function1->GetWebAuthFlowForTesting()); |
| EXPECT_TRUE(output1.GetString().find(final_url1.spec()) != std::string::npos); |
| |
| SimulateUrlRedirect(extension_id2, consent_web_contents2); |
| |
| base::Value output2; |
| WaitForOneResult(function2.get(), &output2); |
| EXPECT_FALSE(function2->GetWebAuthFlowForTesting()); |
| EXPECT_TRUE(output2.GetString().find(final_url2.spec()) != std::string::npos); |
| |
| histogram_tester()->ExpectUniqueSample( |
| kLaunchWebAuthFlowResultHistogramName, |
| IdentityLaunchWebAuthFlowFunction::Error::kNone, 2); |
| } |
| |
| // TODO(crbug.com/40259192): This test should be adapted after the |
| // implementation of the bug. |
| IN_PROC_BROWSER_TEST_F( |
| LaunchWebAuthFlowFunctionTestWithBrowserTab, |
| ExtensionWithDifferentArgsShouldGenerateDifferentFlowsInAQueue) { |
| std::unique_ptr<net::EmbeddedTestServer> https_server = LaunchHttpsServer(); |
| scoped_refptr<IdentityLaunchWebAuthFlowFunction> function1 = |
| CreateLaunchWebAuthFlowFunction(); |
| scoped_refptr<IdentityLaunchWebAuthFlowFunction> function2 = |
| CreateLaunchWebAuthFlowFunction(); |
| |
| const std::string extension_id("extension"); |
| function1->InitFinalRedirectURLDomainsForTest(extension_id); |
| |
| const GURL auth_url1(https_server->GetURL("/consent_page.html")); |
| const GURL auth_url2(https_server->GetURL("/interaction_required.html")); |
| const GURL final_url("https://" + extension_id + ".chromiumapp.org/"); |
| |
| const std::string args1 = |
| "[{\"interactive\": true, \"url\": \"" + auth_url1.spec() + "\"}]"; |
| const std::string args2 = |
| "[{\"interactive\": true, \"url\": \"" + auth_url2.spec() + "\"}]"; |
| |
| RunFunctionAndWaitForNavigation(function1.get(), auth_url1, args1); |
| RunFunctionAndWaitForNavigation(function2.get(), auth_url2, args2); |
| |
| content::WebContents* consent_web_contents1 = |
| function1->GetWebAuthFlowForTesting()->web_contents(); |
| content::WebContents* consent_web_contents2 = |
| function2->GetWebAuthFlowForTesting()->web_contents(); |
| // TODO(crbug.com/40259192): `function2->GetWebAuthFlowForTesting()` should be |
| // null after the changes since it would be in a queue. |
| EXPECT_NE(consent_web_contents1, consent_web_contents2); |
| |
| const std::string& current_consent_url2 = |
| consent_web_contents2->GetURL().spec(); |
| |
| // SimulateConsent on first action should not affect the second function. |
| SimulateUrlRedirect(extension_id, consent_web_contents1); |
| |
| base::Value output1; |
| WaitForOneResult(function1.get(), &output1); |
| // `function2` state should remain. |
| EXPECT_EQ(current_consent_url2, consent_web_contents2->GetURL().spec()); |
| EXPECT_FALSE(function1->GetWebAuthFlowForTesting()); |
| EXPECT_TRUE(output1.GetString().find(final_url.spec()) != std::string::npos); |
| |
| // TODO(crbug.com/40259192): function2 should now run, check for that once the |
| // queue is implemented. |
| |
| histogram_tester()->ExpectUniqueSample( |
| kLaunchWebAuthFlowResultHistogramName, |
| IdentityLaunchWebAuthFlowFunction::Error::kNone, 1); |
| } |
| |
| class ClearAllCachedAuthTokensFunctionTest : public AsyncExtensionBrowserTest { |
| public: |
| void SetUpOnMainThread() override { |
| AsyncExtensionBrowserTest::SetUpOnMainThread(); |
| base::FilePath manifest_path = |
| test_data_dir_.AppendASCII("api_test/identity/oauth2"); |
| extension_ = LoadExtension(manifest_path); |
| } |
| |
| void TearDownOnMainThread() override { |
| // We must clear the extension_ raw_ptr before browser shutdown is |
| // initiated. Otherwise, it will become dangling after extensions are |
| // unloaded during shutdown. |
| extension_ = nullptr; |
| AsyncExtensionBrowserTest::TearDownOnMainThread(); |
| } |
| |
| const Extension* extension() { return extension_; } |
| |
| bool RunClearAllCachedAuthTokensFunction() { |
| auto function = |
| base::MakeRefCounted<IdentityClearAllCachedAuthTokensFunction>(); |
| function->set_extension(extension_.get()); |
| return utils::RunFunction(function.get(), "[]", profile(), |
| api_test_utils::FunctionMode::kNone); |
| } |
| |
| IdentityAPI* id_api() { |
| return IdentityAPI::GetFactoryInstance()->Get(profile()); |
| } |
| |
| private: |
| raw_ptr<const Extension> extension_ = nullptr; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(ClearAllCachedAuthTokensFunctionTest, |
| EraseCachedGaiaId) { |
| id_api()->SetGaiaIdForExtension(extension()->id(), GaiaId("test_gaia")); |
| EXPECT_EQ(GaiaId("test_gaia"), |
| id_api()->GetGaiaIdForExtension(extension()->id())); |
| ASSERT_TRUE(RunClearAllCachedAuthTokensFunction()); |
| EXPECT_FALSE(id_api()->GetGaiaIdForExtension(extension()->id()).has_value()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ClearAllCachedAuthTokensFunctionTest, |
| EraseCachedTokens) { |
| ExtensionTokenKey token_key(extension()->id(), CoreAccountInfo(), {"foo"}); |
| id_api()->token_cache()->SetToken( |
| token_key, IdentityTokenCacheValue::CreateToken("access_token", {"foo"}, |
| base::Seconds(3600))); |
| EXPECT_NE(IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND, |
| id_api()->token_cache()->GetToken(token_key).status()); |
| ASSERT_TRUE(RunClearAllCachedAuthTokensFunction()); |
| EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND, |
| id_api()->token_cache()->GetToken(token_key).status()); |
| } |
| #endif // BUILDFLAG(ENABLE_EXTENSIONS) |
| |
| class OnSignInChangedEventTest : public IdentityTestWithSignin { |
| protected: |
| void SetUpOnMainThread() override { |
| // TODO(blundell): Ideally we would test fully end-to-end by injecting a |
| // JavaScript extension listener and having that listener do the |
| // verification, but it's not clear how to set that up. |
| id_api()->set_on_signin_changed_callback_for_testing( |
| base::BindRepeating(&OnSignInChangedEventTest::OnSignInEventChanged, |
| base::Unretained(this))); |
| |
| IdentityTestWithSignin::SetUpOnMainThread(); |
| } |
| |
| IdentityAPI* id_api() { |
| return IdentityAPI::GetFactoryInstance()->Get(profile()); |
| } |
| |
| // Adds an event that is expected to fire. Events are unordered, i.e., when an |
| // event fires it will be checked against all of the expected events that have |
| // been added. This is because the order of multiple events firing due to the |
| // same underlying state change is undefined in the |
| // chrome.identity.onSignInEventChanged() API. |
| void AddExpectedEvent(base::Value::List args) { |
| expected_events_.insert( |
| std::make_unique<Event>(events::IDENTITY_ON_SIGN_IN_CHANGED, |
| api::identity::OnSignInChanged::kEventName, |
| std::move(args), profile())); |
| } |
| |
| bool HasExpectedEvent() { return !expected_events_.empty(); } |
| |
| private: |
| void OnSignInEventChanged(Event* event) { |
| ASSERT_TRUE(HasExpectedEvent()); |
| |
| // Search for |event| in the set of expected events. |
| bool found_event = false; |
| const auto& event_args = event->event_args; |
| for (const auto& expected_event : expected_events_) { |
| EXPECT_EQ(expected_event->histogram_value, event->histogram_value); |
| EXPECT_EQ(expected_event->event_name, event->event_name); |
| |
| const auto& expected_event_args = expected_event->event_args; |
| if (event_args != expected_event_args) { |
| continue; |
| } |
| |
| expected_events_.erase(expected_event); |
| found_event = true; |
| break; |
| } |
| |
| if (!found_event) { |
| EXPECT_TRUE(false) << "Received bad event:"; |
| |
| LOG(INFO) << "Was expecting events with these args:"; |
| |
| for (const auto& expected_event : expected_events_) { |
| LOG(INFO) << expected_event->event_args; |
| } |
| |
| LOG(INFO) << "But received event with different args:"; |
| LOG(INFO) << event_args; |
| } |
| } |
| |
| std::set<std::unique_ptr<Event>> expected_events_; |
| }; |
| |
| // Test that an event is fired when the primary account signs in. |
| IN_PROC_BROWSER_TEST_F(OnSignInChangedEventTest, FireOnPrimaryAccountSignIn) { |
| api::identity::AccountInfo account_info; |
| account_info.id = "gaia_id_for_primary_example.com"; |
| AddExpectedEvent(api::identity::OnSignInChanged::Create(account_info, true)); |
| |
| // Sign in and verify that the callback fires. |
| SignIn("primary@example.com"); |
| |
| EXPECT_FALSE(HasExpectedEvent()); |
| } |
| |
| #if !BUILDFLAG(IS_CHROMEOS) |
| // Test that an event is fired when the primary account signs out. Only |
| // applicable in non-DICE mode, as when DICE is enabled clearing the primary |
| // account does not result in its refresh token being removed and hence does |
| // not trigger an event to fire. |
| IN_PROC_BROWSER_TEST_F(OnSignInChangedEventTest, FireOnPrimaryAccountSignOut) { |
| if (AccountConsistencyModeManager::IsDiceEnabledForProfile(profile())) { |
| return; |
| } |
| |
| api::identity::AccountInfo account_info; |
| account_info.id = "gaia_id_for_primary_example.com"; |
| AddExpectedEvent(api::identity::OnSignInChanged::Create(account_info, true)); |
| |
| SignIn("primary@example.com"); |
| |
| AddExpectedEvent(api::identity::OnSignInChanged::Create(account_info, false)); |
| |
| // Sign out and verify that the callback fires. |
| identity_test_env()->ClearPrimaryAccount(); |
| |
| EXPECT_FALSE(HasExpectedEvent()); |
| } |
| #endif // !BUILDFLAG(IS_CHROMEOS) |
| |
| // Test that an event is fired when the primary account has a refresh token |
| // invalidated. |
| IN_PROC_BROWSER_TEST_F(OnSignInChangedEventTest, |
| FireOnPrimaryAccountRefreshTokenInvalidated) { |
| api::identity::AccountInfo account_info; |
| account_info.id = "gaia_id_for_primary_example.com"; |
| AddExpectedEvent(api::identity::OnSignInChanged::Create(account_info, true)); |
| |
| CoreAccountId primary_account_id = SignIn("primary@example.com"); |
| |
| AddExpectedEvent(api::identity::OnSignInChanged::Create(account_info, true)); |
| |
| // Revoke the refresh token and verify that the callback fires. |
| identity_test_env()->SetInvalidRefreshTokenForPrimaryAccount(); |
| |
| EXPECT_FALSE(HasExpectedEvent()); |
| } |
| |
| // Test that an event is fired when the primary account has a refresh token |
| // newly available. |
| IN_PROC_BROWSER_TEST_F(OnSignInChangedEventTest, |
| FireOnPrimaryAccountRefreshTokenAvailable) { |
| api::identity::AccountInfo account_info; |
| account_info.id = "gaia_id_for_primary_example.com"; |
| AddExpectedEvent(api::identity::OnSignInChanged::Create(account_info, true)); |
| |
| CoreAccountId primary_account_id = SignIn("primary@example.com"); |
| |
| AddExpectedEvent(api::identity::OnSignInChanged::Create(account_info, true)); |
| identity_test_env()->SetInvalidRefreshTokenForPrimaryAccount(); |
| |
| account_info.id = "gaia_id_for_primary_example.com"; |
| AddExpectedEvent(api::identity::OnSignInChanged::Create(account_info, true)); |
| |
| // Make the primary account available again and check that the callback fires. |
| identity_test_env()->SetRefreshTokenForPrimaryAccount(); |
| EXPECT_FALSE(HasExpectedEvent()); |
| } |
| |
| // Test that an event is fired for changes to a secondary account. |
| IN_PROC_BROWSER_TEST_F(OnSignInChangedEventTest, FireForSecondaryAccount) { |
| api::identity::AccountInfo account_info; |
| account_info.id = "gaia_id_for_primary_example.com"; |
| AddExpectedEvent(api::identity::OnSignInChanged::Create(account_info, true)); |
| SignIn("primary@example.com"); |
| |
| account_info.id = "gaia_id_for_secondary_example.com"; |
| AddExpectedEvent(api::identity::OnSignInChanged::Create(account_info, true)); |
| |
| // Make a secondary account available again and check that the callback fires. |
| CoreAccountId secondary_account_id = |
| identity_test_env() |
| ->MakeAccountAvailable("secondary@example.com") |
| .account_id; |
| EXPECT_FALSE(HasExpectedEvent()); |
| |
| // Revoke the secondary account's refresh token and check that the callback |
| // fires. |
| AddExpectedEvent(api::identity::OnSignInChanged::Create(account_info, false)); |
| |
| identity_test_env()->RemoveRefreshTokenForAccount(secondary_account_id); |
| EXPECT_FALSE(HasExpectedEvent()); |
| } |
| |
| // Tests the chrome.identity API implemented by custom JS bindings. |
| IN_PROC_BROWSER_TEST_F(ExtensionApiTest, ChromeIdentityJsBindings) { |
| ASSERT_TRUE(RunExtensionTest("identity/js_bindings")) << message_; |
| } |
| |
| } // namespace extensions |