blob: 62fb6597dcdf88b0a47070ce26f5be519a28ff01 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/chrome_notification_types.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_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/component_loader.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/extensions/extension_function_test_utils.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/account_fetcher_service_factory.h"
#include "chrome/browser/signin/account_tracker_service_factory.h"
#include "chrome/browser/signin/fake_gaia_cookie_manager_service_builder.h"
#include "chrome/browser/signin/fake_profile_oauth2_token_service_builder.h"
#include "chrome/browser/signin/fake_signin_manager_builder.h"
#include "chrome/browser/signin/gaia_cookie_manager_service_factory.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
#include "chrome/browser/signin/signin_manager_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/api/identity.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/crx_file/id_util.h"
#include "components/guest_view/browser/guest_view_base.h"
#include "components/prefs/pref_service.h"
#include "components/signin/core/browser/account_fetcher_service.h"
#include "components/signin/core/browser/account_tracker_service.h"
#include "components/signin/core/browser/fake_gaia_cookie_manager_service.h"
#include "components/signin/core/browser/fake_profile_oauth2_token_service.h"
#include "components/signin/core/browser/profile_management_switches.h"
#include "components/signin/core/browser/signin_manager.h"
#include "components/signin/core/browser/signin_pref_names.h"
#include "components/signin/core/browser/signin_switches.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/api_test_utils.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/manifest_handlers/oauth2_manifest_handler.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "google_apis/gaia/oauth2_mint_token_flow.h"
#include "google_apis/gaia/oauth2_token_service.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/identity/public/cpp/identity_manager.h"
#include "services/identity/public/cpp/identity_test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/login/users/mock_user_manager.h"
#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
#include "chrome/browser/chromeos/settings/stub_install_attributes.h"
#include "components/user_manager/scoped_user_manager.h"
#endif
using guest_view::GuestViewBase;
using testing::_;
using testing::Return;
namespace extensions {
namespace {
namespace errors = identity_constants;
namespace utils = extension_function_test_utils;
static const char kAccessToken[] = "auth_token";
static const char kExtensionId[] = "ext_id";
// Asynchronous function runner allows tests to manipulate the browser window
// after the call happens.
class AsyncFunctionRunner {
public:
void RunFunctionAsync(UIThreadExtensionFunction* function,
const std::string& args,
content::BrowserContext* browser_context) {
response_delegate_.reset(new api_test_utils::SendResponseHelper(function));
std::unique_ptr<base::ListValue> parsed_args(utils::ParseList(args));
EXPECT_TRUE(parsed_args.get()) <<
"Could not parse extension function arguments: " << args;
function->SetArgs(parsed_args.get());
if (!function->extension()) {
scoped_refptr<Extension> empty_extension(
ExtensionBuilder("Test").Build());
function->set_extension(empty_extension.get());
}
function->set_browser_context(browser_context);
function->set_has_callback(true);
function->RunWithValidation()->Execute();
}
std::string WaitForError(UIThreadExtensionFunction* function) {
RunMessageLoopUntilResponse();
CHECK(function->response_type());
EXPECT_EQ(ExtensionFunction::FAILED, *function->response_type());
return function->GetError();
}
base::Value* WaitForSingleResult(UIThreadExtensionFunction* function) {
RunMessageLoopUntilResponse();
EXPECT_TRUE(function->GetError().empty()) << "Unexpected error: "
<< function->GetError();
const base::Value* single_result = NULL;
if (function->GetResultList() != NULL &&
function->GetResultList()->Get(0, &single_result)) {
return single_result->DeepCopy();
}
return NULL;
}
private:
void RunMessageLoopUntilResponse() {
response_delegate_->WaitForResponse();
EXPECT_TRUE(response_delegate_->has_response());
}
std::unique_ptr<api_test_utils::SendResponseHelper> response_delegate_;
};
class AsyncExtensionBrowserTest : public ExtensionBrowserTest {
protected:
// Provide wrappers of AsynchronousFunctionRunner for convenience.
void RunFunctionAsync(UIThreadExtensionFunction* function,
const std::string& args) {
async_function_runner_ = std::make_unique<AsyncFunctionRunner>();
async_function_runner_->RunFunctionAsync(function, args,
browser()->profile());
}
std::string WaitForError(UIThreadExtensionFunction* function) {
return async_function_runner_->WaitForError(function);
}
base::Value* WaitForSingleResult(UIThreadExtensionFunction* function) {
return async_function_runner_->WaitForSingleResult(function);
}
private:
std::unique_ptr<AsyncFunctionRunner> async_function_runner_;
};
class TestHangOAuth2MintTokenFlow : public OAuth2MintTokenFlow {
public:
TestHangOAuth2MintTokenFlow()
: OAuth2MintTokenFlow(NULL, OAuth2MintTokenFlow::Parameters()) {}
void Start(net::URLRequestContextGetter* context,
const std::string& access_token) override {
// Do nothing, simulating a hanging network call.
}
};
class TestOAuth2MintTokenFlow : public OAuth2MintTokenFlow {
public:
enum ResultType {
ISSUE_ADVICE_SUCCESS,
MINT_TOKEN_SUCCESS,
MINT_TOKEN_FAILURE,
MINT_TOKEN_BAD_CREDENTIALS,
MINT_TOKEN_SERVICE_ERROR
};
TestOAuth2MintTokenFlow(ResultType result,
OAuth2MintTokenFlow::Delegate* delegate)
: OAuth2MintTokenFlow(delegate, OAuth2MintTokenFlow::Parameters()),
result_(result),
delegate_(delegate) {}
void Start(net::URLRequestContextGetter* context,
const std::string& access_token) override {
switch (result_) {
case ISSUE_ADVICE_SUCCESS: {
IssueAdviceInfo info;
delegate_->OnIssueAdviceSuccess(info);
break;
}
case MINT_TOKEN_SUCCESS: {
delegate_->OnMintTokenSuccess(kAccessToken, 3600);
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_;
OAuth2MintTokenFlow::Delegate* delegate_;
};
// Waits for a specific GURL to generate a NOTIFICATION_LOAD_STOP event and
// saves a pointer to the window embedding the WebContents, which can be later
// closed.
class WaitForGURLAndCloseWindow : public content::WindowedNotificationObserver {
public:
explicit WaitForGURLAndCloseWindow(GURL url)
: WindowedNotificationObserver(
content::NOTIFICATION_LOAD_STOP,
content::NotificationService::AllSources()),
url_(url),
embedder_web_contents_(nullptr) {}
// NotificationObserver:
void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) override {
content::NavigationController* web_auth_flow_controller =
content::Source<content::NavigationController>(source).ptr();
content::WebContents* web_contents =
web_auth_flow_controller->GetWebContents();
if (web_contents->GetURL() == url_) {
// It is safe to keep the pointer here, because we know in a test, that
// the WebContents won't go away before CloseEmbedderWebContents is
// called. Don't copy this code to production.
GuestViewBase* guest = GuestViewBase::FromWebContents(web_contents);
embedder_web_contents_ = guest->embedder_web_contents();
// Condtionally invoke parent class so that Wait will not exit
// until the target URL arrives.
content::WindowedNotificationObserver::Observe(type, source, details);
}
}
// Closes the window embedding the WebContents. The action is separated from
// the Observe method to make sure the list of observers is not deleted,
// while some event is already being processed. (That causes ASAN failures.)
void CloseEmbedderWebContents() {
if (embedder_web_contents_)
embedder_web_contents_->Close();
}
private:
GURL url_;
content::WebContents* embedder_web_contents_;
};
} // namespace
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_failure_(GaiaWebAuthFlow::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 set_mint_token_flow(std::unique_ptr<OAuth2MintTokenFlow> flow) {
flow_ = std::move(flow);
}
void set_mint_token_result(TestOAuth2MintTokenFlow::ResultType result_type) {
set_mint_token_flow(
std::make_unique<TestOAuth2MintTokenFlow>(result_type, this));
}
void set_scope_ui_failure(GaiaWebAuthFlow::Failure failure) {
scope_ui_result_ = false;
scope_ui_failure_ = failure;
}
void set_scope_ui_service_error(const GoogleServiceAuthError& service_error) {
scope_ui_result_ = false;
scope_ui_failure_ = GaiaWebAuthFlow::SERVICE_AUTH_ERROR;
scope_ui_service_error_ = service_error;
}
void set_scope_ui_oauth_error(const std::string& oauth_error) {
scope_ui_result_ = false;
scope_ui_failure_ = GaiaWebAuthFlow::OAUTH_ERROR;
scope_ui_oauth_error_ = oauth_error;
}
bool login_ui_shown() const { return login_ui_shown_; }
bool scope_ui_shown() const { return scope_ui_shown_; }
std::string login_access_token() const { return login_access_token_; }
void StartLoginAccessTokenRequest() override {
if (auto_login_access_token_) {
base::Optional<std::string> access_token("access_token");
GoogleServiceAuthError error = GoogleServiceAuthError::AuthErrorNone();
if (!login_access_token_result_) {
access_token = base::nullopt;
error = GoogleServiceAuthError(
GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
}
OnGetAccessTokenComplete(
access_token, base::Time::Now() + base::TimeDelta::FromHours(1LL),
error);
} else {
// Make a request to the token service. The test now must tell
// the token service to issue an access token (or an error).
IdentityGetAuthTokenFunction::StartLoginAccessTokenRequest();
}
}
#if defined(OS_CHROMEOS)
void StartDeviceLoginAccessTokenRequest() override {
StartLoginAccessTokenRequest();
}
#endif
void ShowLoginPopup() override {
EXPECT_FALSE(login_ui_shown_);
login_ui_shown_ = true;
if (login_ui_result_) {
::identity::AccountState account_state;
account_state.has_refresh_token = true;
account_state.is_primary_account = true;
OnPrimaryAccountAvailable(
SigninManagerFactory::GetForProfile(GetProfile())
->GetAuthenticatedAccountInfo(),
account_state);
} else {
SigninFailed();
}
}
void ShowOAuthApprovalDialog(const IssueAdviceInfo& issue_advice) override {
scope_ui_shown_ = true;
if (scope_ui_result_) {
OnGaiaFlowCompleted(kAccessToken, "3600");
} else if (scope_ui_failure_ == GaiaWebAuthFlow::SERVICE_AUTH_ERROR) {
OnGaiaFlowFailure(scope_ui_failure_, scope_ui_service_error_, "");
} else {
GoogleServiceAuthError error(GoogleServiceAuthError::NONE);
OnGaiaFlowFailure(scope_ui_failure_, error, scope_ui_oauth_error_);
}
}
void StartGaiaRequest(const std::string& login_access_token) override {
EXPECT_TRUE(login_access_token_.empty());
// Save the login token used in the mint token flow so tests can see
// what account was used.
login_access_token_ = login_access_token;
IdentityGetAuthTokenFunction::StartGaiaRequest(login_access_token);
}
OAuth2MintTokenFlow* CreateMintTokenFlow() override {
return flow_.release();
}
private:
~FakeGetAuthTokenFunction() override {}
bool login_access_token_result_;
bool auto_login_access_token_;
bool login_ui_result_;
bool scope_ui_result_;
GaiaWebAuthFlow::Failure scope_ui_failure_;
GoogleServiceAuthError scope_ui_service_error_;
std::string scope_ui_oauth_error_;
bool login_ui_shown_;
bool scope_ui_shown_;
std::unique_ptr<OAuth2MintTokenFlow> flow_;
std::string login_access_token_;
};
class MockQueuedMintRequest : public IdentityMintRequestQueue::Request {
public:
MOCK_METHOD1(StartMintToken, void(IdentityMintRequestQueue::MintType));
};
class IdentityTestWithSignin : public AsyncExtensionBrowserTest {
public:
void SetUpInProcessBrowserTestFixture() override {
AsyncExtensionBrowserTest::SetUpInProcessBrowserTestFixture();
will_create_browser_context_services_subscription_ =
BrowserContextDependencyManager::GetInstance()
->RegisterWillCreateBrowserContextServicesCallbackForTesting(
base::Bind(
&IdentityTestWithSignin::OnWillCreateBrowserContextServices,
base::Unretained(this)));
}
void OnWillCreateBrowserContextServices(content::BrowserContext* context) {
// Replace the signin manager and token service with fakes. Do this ahead of
// creating the browser so that a bunch of classes don't register as
// observers and end up needing to unregister when the fake is substituted.
SigninManagerFactory::GetInstance()->SetTestingFactory(
context, &BuildFakeSigninManagerBase);
ProfileOAuth2TokenServiceFactory::GetInstance()->SetTestingFactory(
context, &BuildFakeProfileOAuth2TokenService);
GaiaCookieManagerServiceFactory::GetInstance()->SetTestingFactory(
context, &BuildFakeGaiaCookieManagerService);
// Ensure that AccountFetcherService is (1) created at all and (2) created
// early enough for it to observe the Profile initialization process and
// loading of tokens by PO2TS. Explicitly forcing this setup (which happens
// naturally in production) is necessary for the flow of
// AccountTrackerService having accounts removed when tokens are revoked
// with PO2TS to work as expected in this testing context.
// TODO(blundell): Change these tests to interact with
// IdentityTestEnvironment once the production code is changed to interact
// with IdentityManager.
AccountFetcherServiceFactory::GetInstance()->GetForProfile(
Profile::FromBrowserContext(context));
}
void SetUpOnMainThread() override {
AsyncExtensionBrowserTest::SetUpOnMainThread();
// Grab references to the fake signin manager and token service.
signin_manager_ = static_cast<FakeSigninManagerForTesting*>(
SigninManagerFactory::GetInstance()->GetForProfile(profile()));
ASSERT_TRUE(signin_manager_);
token_service_ = static_cast<FakeProfileOAuth2TokenService*>(
ProfileOAuth2TokenServiceFactory::GetInstance()->GetForProfile(
profile()));
ASSERT_TRUE(token_service_);
GaiaCookieManagerServiceFactory::GetInstance()
->GetForProfile(profile())
->Init();
#if defined(OS_CHROMEOS)
// On ChromeOS, ProfileOAuth2TokenService does not fire
// OnRefreshTokensLoaded() in text contexts. However, AccountFetcherService
// must receive this call in order to forward later
// OnRefreshToken{Available, Revoked} callbacks on to AccountTrackerService
// as expected. Hence, we make that call explicitly here.
// TODO(blundell): Hide this detail when converting this code to interact
// with IdentityTestEnvironment.
AccountFetcherServiceFactory::GetInstance()
->GetForProfile(profile())
->OnRefreshTokensLoaded();
#endif
}
protected:
// Returns the account ID of the created account.
std::string SignIn(const std::string& email) {
identity::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(profile());
identity::MakePrimaryAccountAvailable(signin_manager_, token_service_,
identity_manager, email);
return identity_manager->GetPrimaryAccountInfo().account_id;
}
std::string AddAccount(const std::string& email) {
std::string account_id = SeedAccountInfo(email);
token_service_->UpdateCredentials(account_id, "refresh_token");
return account_id;
}
std::string SeedAccountInfo(const std::string& email) {
std::string gaia = "gaia_id_for_" + email;
AccountTrackerService* account_tracker =
AccountTrackerServiceFactory::GetForProfile(profile());
return account_tracker->SeedAccountInfo(gaia, email);
}
FakeSigninManagerForTesting* signin_manager_;
FakeProfileOAuth2TokenService* token_service_;
std::unique_ptr<
base::CallbackList<void(content::BrowserContext*)>::Subscription>
will_create_browser_context_services_subscription_;
};
class IdentityGetAccountsFunctionTest : public IdentityTestWithSignin {
void SetUpCommandLine(base::CommandLine* command_line) override {
ExtensionBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kExtensionsMultiAccount);
}
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("[]"), browser(),
api_test_utils::NONE)) {
return GenerateFailureResult(gaia_ids, NULL)
<< "getAccounts did not return a result.";
}
const base::ListValue* callback_arguments = func->GetResultList();
if (!callback_arguments)
return GenerateFailureResult(gaia_ids, NULL) << "NULL result";
if (callback_arguments->GetSize() != 1) {
return GenerateFailureResult(gaia_ids, NULL)
<< "Expected 1 argument but got " << callback_arguments->GetSize();
}
const base::ListValue* results;
if (!callback_arguments->GetList(0, &results))
GenerateFailureResult(gaia_ids, NULL) << "Result was not an array";
std::set<std::string> result_ids;
for (const base::Value& item : *results) {
std::unique_ptr<api::identity::AccountInfo> info =
api::identity::AccountInfo::FromValue(item);
if (info.get())
result_ids.insert(info->id);
else
return GenerateFailureResult(gaia_ids, results);
}
for (const std::string& gaia_id : gaia_ids) {
if (result_ids.find(gaia_id) == result_ids.end())
return GenerateFailureResult(gaia_ids, results);
}
return testing::AssertionResult(true);
}
testing::AssertionResult GenerateFailureResult(
const ::std::vector<std::string>& gaia_ids,
const base::ListValue* 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::unique_ptr<api::identity::AccountInfo> info =
api::identity::AccountInfo::FromValue(result);
if (info.get())
msg << info->id << " ";
else
msg << result << "<-" << result.type() << " ";
}
}
return testing::AssertionFailure(msg);
}
};
IN_PROC_BROWSER_TEST_F(IdentityGetAccountsFunctionTest, MultiAccountOn) {
EXPECT_TRUE(signin::IsExtensionsMultiAccount());
}
IN_PROC_BROWSER_TEST_F(IdentityGetAccountsFunctionTest, NoneSignedIn) {
EXPECT_TRUE(ExpectGetAccounts({}));
}
IN_PROC_BROWSER_TEST_F(IdentityGetAccountsFunctionTest, NoPrimaryAccount) {
AddAccount("secondary@example.com");
EXPECT_TRUE(ExpectGetAccounts({}));
}
IN_PROC_BROWSER_TEST_F(IdentityGetAccountsFunctionTest,
PrimaryAccountHasNoRefreshToken) {
std::string primary_account_id = SignIn("primary@example.com");
// TODO(treib,blundell): This breaks IdentityManager. RevokeCredentials causes
// the account to be removed from AccountTrackerService (via
// AccountFetcherService::OnRefreshTokenRevoked), which triggers DCHECKs in
// IdentityManager::GetPrimaryAccountInfo.
token_service_->RevokeCredentials(primary_account_id);
EXPECT_TRUE(ExpectGetAccounts({}));
}
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");
AddAccount("secondary@example.com");
EXPECT_TRUE(ExpectGetAccounts({"gaia_id_for_primary@example.com",
"gaia_id_for_secondary@example.com"}));
}
class IdentityOldProfilesGetAccountsFunctionTest
: public IdentityGetAccountsFunctionTest {
void SetUpCommandLine(base::CommandLine* command_line) override {
ExtensionBrowserTest::SetUpCommandLine(command_line);
// Don't add the multi-account switch that parent class would have.
}
};
IN_PROC_BROWSER_TEST_F(IdentityOldProfilesGetAccountsFunctionTest,
MultiAccountOff) {
EXPECT_FALSE(signin::IsExtensionsMultiAccount());
}
IN_PROC_BROWSER_TEST_F(IdentityOldProfilesGetAccountsFunctionTest,
TwoAccountsSignedIn) {
SignIn("primary@example.com");
AddAccount("secondary@example.com");
EXPECT_TRUE(ExpectGetAccounts({"gaia_id_for_primary@example.com"}));
}
class IdentityGetProfileUserInfoFunctionTest : public IdentityTestWithSignin {
protected:
std::unique_ptr<api::identity::ProfileUserInfo> RunGetProfileUserInfo() {
scoped_refptr<IdentityGetProfileUserInfoFunction> func(
new IdentityGetProfileUserInfoFunction);
func->set_extension(
ExtensionBuilder("Test").SetID(kExtensionId).Build().get());
std::unique_ptr<base::Value> value(
utils::RunFunctionAndReturnSingleResult(func.get(), "[]", browser()));
return api::identity::ProfileUserInfo::FromValue(*value);
}
std::unique_ptr<api::identity::ProfileUserInfo>
RunGetProfileUserInfoWithEmail() {
scoped_refptr<IdentityGetProfileUserInfoFunction> func(
new IdentityGetProfileUserInfoFunction);
func->set_extension(CreateExtensionWithEmailPermission());
std::unique_ptr<base::Value> value(
utils::RunFunctionAndReturnSingleResult(func.get(), "[]", browser()));
return api::identity::ProfileUserInfo::FromValue(*value);
}
private:
scoped_refptr<Extension> CreateExtensionWithEmailPermission() {
return ExtensionBuilder("Test").AddPermission("identity.email").Build();
}
};
IN_PROC_BROWSER_TEST_F(IdentityGetProfileUserInfoFunctionTest, NotSignedIn) {
std::unique_ptr<api::identity::ProfileUserInfo> info =
RunGetProfileUserInfoWithEmail();
EXPECT_TRUE(info->email.empty());
EXPECT_TRUE(info->id.empty());
}
IN_PROC_BROWSER_TEST_F(IdentityGetProfileUserInfoFunctionTest, SignedIn) {
SignIn("president@example.com");
std::unique_ptr<api::identity::ProfileUserInfo> info =
RunGetProfileUserInfoWithEmail();
EXPECT_EQ("president@example.com", info->email);
EXPECT_EQ("gaia_id_for_president@example.com", info->id);
}
IN_PROC_BROWSER_TEST_F(IdentityGetProfileUserInfoFunctionTest,
NotSignedInNoEmail) {
std::unique_ptr<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::unique_ptr<api::identity::ProfileUserInfo> info =
RunGetProfileUserInfo();
EXPECT_TRUE(info->email.empty());
EXPECT_TRUE(info->id.empty());
}
class GetAuthTokenFunctionTest
: public IdentityTestWithSignin,
public OAuth2TokenService::DiagnosticsObserver {
public:
void SetUpCommandLine(base::CommandLine* command_line) override {
IdentityTestWithSignin::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kExtensionsMultiAccount);
}
std::string IssueLoginAccessTokenForAccount(const std::string& account_id) {
std::string access_token = "access_token-" + account_id;
token_service_->IssueAllTokensForAccount(
account_id, access_token,
base::Time::Now() + base::TimeDelta::FromSeconds(3600));
return access_token;
}
protected:
enum OAuth2Fields {
NONE = 0,
CLIENT_ID = 1,
SCOPES = 2,
AS_COMPONENT = 4
};
void SetUpOnMainThread() override {
IdentityTestWithSignin::SetUpOnMainThread();
token_service_->AddDiagnosticsObserver(this);
}
void TearDownOnMainThread() override {
token_service_->RemoveDiagnosticsObserver(this);
IdentityTestWithSignin::TearDownOnMainThread();
}
~GetAuthTokenFunctionTest() override {}
// 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;
base::FilePath manifest_path =
test_data_dir_.AppendASCII("platform_apps/oauth2");
base::FilePath component_manifest_path =
test_data_dir_.AppendASCII("packaged_app/component_oauth2");
if ((fields_to_set & AS_COMPONENT) == 0)
ext = LoadExtension(manifest_path);
else
ext = LoadExtensionAsComponent(component_manifest_path);
OAuth2Info& oauth2_info =
const_cast<OAuth2Info&>(OAuth2Info::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;
}
IdentityAPI* id_api() {
return IdentityAPI::GetFactoryInstance()->Get(browser()->profile());
}
const std::string& GetPrimaryAccountId() {
SigninManagerBase* signin_manager =
SigninManagerFactory::GetForProfile(browser()->profile());
return signin_manager->GetAuthenticatedAccountId();
}
void SetCachedToken(const IdentityTokenCacheValue& token_data) {
ExtensionTokenKey key(extension_id_, GetPrimaryAccountId(), oauth_scopes_);
id_api()->SetCachedToken(key, token_data);
}
const IdentityTokenCacheValue& GetCachedToken(const std::string& account_id) {
ExtensionTokenKey key(
extension_id_, account_id.empty() ? GetPrimaryAccountId() : account_id,
oauth_scopes_);
return id_api()->GetCachedToken(key);
}
void QueueRequestStart(IdentityMintRequestQueue::MintType type,
IdentityMintRequestQueue::Request* request) {
ExtensionTokenKey key(extension_id_, GetPrimaryAccountId(), oauth_scopes_);
id_api()->mint_queue()->RequestStart(type, key, request);
}
void QueueRequestComplete(IdentityMintRequestQueue::MintType type,
IdentityMintRequestQueue::Request* request) {
ExtensionTokenKey key(extension_id_, GetPrimaryAccountId(), oauth_scopes_);
id_api()->mint_queue()->RequestComplete(type, key, request);
}
base::OnceClosure on_access_token_requested_;
private:
// OAuth2TokenService::DiagnosticsObserver:
void OnAccessTokenRequested(
const std::string& account_id,
const std::string& consumer_id,
const OAuth2TokenService::ScopeSet& scopes) override {
if (on_access_token_requested_.is_null())
return;
std::move(on_access_token_requested_).Run();
}
std::string extension_id_;
std::set<std::string> oauth_scopes_;
};
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(), "[{}]", browser());
EXPECT_EQ(std::string(errors::kInvalidClientId), error);
EXPECT_FALSE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
}
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(), "[{}]", browser());
EXPECT_EQ(std::string(errors::kInvalidScopes), error);
EXPECT_FALSE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
}
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(), "[{}]", browser());
EXPECT_EQ(std::string(errors::kUserNotSignedIn), error);
EXPECT_FALSE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
}
// The signin flow is simply not used on ChromeOS.
#if !defined(OS_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}]", browser());
EXPECT_EQ(std::string(errors::kUserNotSignedIn), error);
EXPECT_TRUE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
}
#endif
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->set_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_FAILURE);
std::string error =
utils::RunFunctionAndReturnError(func.get(), "[{}]", browser());
EXPECT_TRUE(base::StartsWith(error, errors::kAuthFailure,
base::CompareCase::INSENSITIVE_ASCII));
EXPECT_FALSE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
}
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(), "[{}]", browser());
EXPECT_TRUE(base::StartsWith(error, errors::kAuthFailure,
base::CompareCase::INSENSITIVE_ASCII));
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
NonInteractiveMintAdviceSuccess) {
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->set_mint_token_result(TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS);
std::string error =
utils::RunFunctionAndReturnError(func.get(), "[{}]", browser());
EXPECT_EQ(std::string(errors::kNoGrant), error);
EXPECT_FALSE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_ADVICE,
GetCachedToken(std::string()).status());
}
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->set_mint_token_result(
TestOAuth2MintTokenFlow::MINT_TOKEN_BAD_CREDENTIALS);
std::string error =
utils::RunFunctionAndReturnError(func.get(), "[{}]", browser());
EXPECT_TRUE(base::StartsWith(error, errors::kAuthFailure,
base::CompareCase::INSENSITIVE_ASCII));
EXPECT_FALSE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
}
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->set_mint_token_result(
TestOAuth2MintTokenFlow::MINT_TOKEN_SERVICE_ERROR);
std::string error =
utils::RunFunctionAndReturnError(func.get(), "[{}]", browser());
EXPECT_TRUE(base::StartsWith(error, errors::kAuthFailure,
base::CompareCase::INSENSITIVE_ASCII));
EXPECT_FALSE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
}
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->set_mint_token_result(
TestOAuth2MintTokenFlow::MINT_TOKEN_SERVICE_ERROR);
std::string error = utils::RunFunctionAndReturnError(
func.get(), "[{\"interactive\": true}]", browser());
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());
}
// The signin flow is simply not used on ChromeOS.
#if !defined(OS_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->set_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}]", browser());
EXPECT_TRUE(base::StartsWith(error, errors::kAuthFailure,
base::CompareCase::INSENSITIVE_ASCII));
EXPECT_TRUE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
}
#endif
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
NoOptionsSuccess) {
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->set_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS);
std::unique_ptr<base::Value> value(
utils::RunFunctionAndReturnSingleResult(func.get(), "[]", browser()));
std::string access_token;
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
EXPECT_FALSE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN,
GetCachedToken(std::string()).status());
}
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->set_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS);
std::unique_ptr<base::Value> value(
utils::RunFunctionAndReturnSingleResult(func.get(), "[{}]", browser()));
std::string access_token;
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
EXPECT_FALSE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN,
GetCachedToken(std::string()).status());
}
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}]", browser());
EXPECT_EQ(std::string(errors::kUserNotSignedIn), error);
// ChromeOS does not support the interactive login flow, so the login UI will
// never be shown on that platform.
#if !defined(OS_CHROMEOS)
EXPECT_TRUE(func->login_ui_shown());
#endif
EXPECT_FALSE(func->scope_ui_shown());
}
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->set_mint_token_result(
TestOAuth2MintTokenFlow::MINT_TOKEN_BAD_CREDENTIALS);
std::string error = utils::RunFunctionAndReturnError(
func.get(), "[{\"interactive\": true}]", browser());
// 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());
}
// The interactive login flow is always short-circuited out with failure on
// ChromeOS, so the tests of the interactive login flow being successful are not
// relevant on that platform.
#if !defined(OS_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->set_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_FAILURE);
std::string error = utils::RunFunctionAndReturnError(
func.get(), "[{\"interactive\": true}]", browser());
EXPECT_TRUE(base::StartsWith(error, errors::kAuthFailure,
base::CompareCase::INSENSITIVE_ASCII));
EXPECT_TRUE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
}
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->set_mint_token_result(
TestOAuth2MintTokenFlow::MINT_TOKEN_BAD_CREDENTIALS);
std::string error = utils::RunFunctionAndReturnError(
func.get(), "[{\"interactive\": true}]", browser());
EXPECT_TRUE(base::StartsWith(error, errors::kAuthFailure,
base::CompareCase::INSENSITIVE_ASCII));
EXPECT_TRUE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
}
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}]", browser());
EXPECT_TRUE(base::StartsWith(error, errors::kAuthFailure,
base::CompareCase::INSENSITIVE_ASCII));
EXPECT_TRUE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
}
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->set_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS);
std::unique_ptr<base::Value> value(utils::RunFunctionAndReturnSingleResult(
func.get(), "[{\"interactive\": true}]", browser()));
std::string access_token;
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
EXPECT_TRUE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
}
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->set_mint_token_result(TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS);
func->set_scope_ui_failure(GaiaWebAuthFlow::WINDOW_CLOSED);
std::string error = utils::RunFunctionAndReturnError(
func.get(), "[{\"interactive\": true}]", browser());
EXPECT_EQ(std::string(errors::kUserRejected), error);
EXPECT_TRUE(func->login_ui_shown());
EXPECT_TRUE(func->scope_ui_shown());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
InteractiveLoginSuccessApprovalSuccess) {
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->set_mint_token_result(TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS);
std::unique_ptr<base::Value> value(utils::RunFunctionAndReturnSingleResult(
func.get(), "[{\"interactive\": true}]", browser()));
std::string access_token;
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
EXPECT_TRUE(func->login_ui_shown());
EXPECT_TRUE(func->scope_ui_shown());
}
#endif
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->set_mint_token_result(TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS);
func->set_scope_ui_failure(GaiaWebAuthFlow::WINDOW_CLOSED);
std::string error = utils::RunFunctionAndReturnError(
func.get(), "[{\"interactive\": true}]", browser());
EXPECT_EQ(std::string(errors::kUserRejected), error);
EXPECT_FALSE(func->login_ui_shown());
EXPECT_TRUE(func->scope_ui_shown());
}
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->set_mint_token_result(TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS);
func->set_scope_ui_failure(GaiaWebAuthFlow::LOAD_FAILED);
std::string error = utils::RunFunctionAndReturnError(
func.get(), "[{\"interactive\": true}]", browser());
EXPECT_EQ(std::string(errors::kPageLoadFailure), error);
EXPECT_FALSE(func->login_ui_shown());
EXPECT_TRUE(func->scope_ui_shown());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
InteractiveApprovalInvalidRedirect) {
SignIn("primary@example.com");
scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction());
func->set_extension(CreateExtension(CLIENT_ID | SCOPES));
func->set_mint_token_result(TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS);
func->set_scope_ui_failure(GaiaWebAuthFlow::INVALID_REDIRECT);
std::string error = utils::RunFunctionAndReturnError(
func.get(), "[{\"interactive\": true}]", browser());
EXPECT_EQ(std::string(errors::kInvalidRedirect), error);
EXPECT_FALSE(func->login_ui_shown());
EXPECT_TRUE(func->scope_ui_shown());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
InteractiveApprovalConnectionFailure) {
SignIn("primary@example.com");
scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction());
func->set_extension(CreateExtension(CLIENT_ID | SCOPES));
func->set_mint_token_result(TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS);
func->set_scope_ui_service_error(
GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED));
std::string error = utils::RunFunctionAndReturnError(
func.get(), "[{\"interactive\": true}]", browser());
EXPECT_TRUE(base::StartsWith(error, errors::kAuthFailure,
base::CompareCase::INSENSITIVE_ASCII));
EXPECT_FALSE(func->login_ui_shown());
EXPECT_TRUE(func->scope_ui_shown());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
InteractiveApprovalServiceErrorAccountValid) {
SignIn("primary@example.com");
scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction());
func->set_extension(CreateExtension(CLIENT_ID | SCOPES));
func->set_mint_token_result(TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS);
func->set_scope_ui_service_error(
GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_ERROR));
std::string error = utils::RunFunctionAndReturnError(
func.get(), "[{\"interactive\": true}]", browser());
EXPECT_TRUE(base::StartsWith(error, errors::kAuthFailure,
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());
}
// The signin flow is simply not used on ChromeOS.
#if !defined(OS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
InteractiveApprovalServiceErrorShowSigninUIOnlyOnce) {
scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction());
func->set_login_ui_result(true);
func->set_extension(CreateExtension(CLIENT_ID | SCOPES));
func->set_mint_token_result(TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS);
func->set_scope_ui_service_error(
GoogleServiceAuthError(GoogleServiceAuthError::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}]", browser());
EXPECT_TRUE(base::StartsWith(error, errors::kAuthFailure,
base::CompareCase::INSENSITIVE_ASCII));
EXPECT_TRUE(func->login_ui_shown());
EXPECT_TRUE(func->scope_ui_shown());
}
#endif
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
InteractiveApprovalOAuthErrors) {
SignIn("primary@example.com");
scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES));
std::map<std::string, std::string> error_map;
error_map.insert(std::make_pair("access_denied", errors::kUserRejected));
error_map.insert(std::make_pair("invalid_scope", errors::kInvalidScopes));
error_map.insert(std::make_pair(
"unmapped_error", std::string(errors::kAuthFailure) + "unmapped_error"));
for (std::map<std::string, std::string>::const_iterator
it = error_map.begin();
it != error_map.end();
++it) {
scoped_refptr<FakeGetAuthTokenFunction> func(
new FakeGetAuthTokenFunction());
func->set_extension(extension.get());
// Make sure we don't get a cached issue_advice result, which would cause
// flow to be leaked.
id_api()->EraseAllCachedTokens();
func->set_mint_token_result(TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS);
func->set_scope_ui_oauth_error(it->first);
std::string error = utils::RunFunctionAndReturnError(
func.get(), "[{\"interactive\": true}]", browser());
EXPECT_EQ(it->second, error);
EXPECT_FALSE(func->login_ui_shown());
EXPECT_TRUE(func->scope_ui_shown());
}
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, InteractiveApprovalSuccess) {
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->set_mint_token_result(TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS);
std::unique_ptr<base::Value> value(utils::RunFunctionAndReturnSingleResult(
func.get(), "[{\"interactive\": true}]", browser()));
std::string access_token;
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
EXPECT_FALSE(func->login_ui_shown());
EXPECT_TRUE(func->scope_ui_shown());
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN,
GetCachedToken(std::string()).status());
}
#if !defined(OS_MACOSX)
// Test for http://crbug.com/753014
//
// 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->set_mint_token_result(TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS);
func->set_scope_ui_service_error(
GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
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}]", browser());
// Check that the OAuth approval dialog is shown to ensure that the Gaia flow
// fails with an |SERVICE_AUTH_ERROR| error (with |INVALID_GAIA_CREDENTIALS|
// service error). This reproduces the crash conditions in bug
// http://crbug.com/753014.
// This condition may be fragile as it depends on the identity manager not
// being destroyed before the OAuth approval dialog is shown.
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());
}
#endif // !defined(OS_MACOSX)
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->set_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS);
QueueRequestComplete(type, &queued_request);
std::unique_ptr<base::Value> value(WaitForSingleResult(func.get()));
std::string access_token;
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
EXPECT_FALSE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
}
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->set_mint_token_result(TestOAuth2MintTokenFlow::ISSUE_ADVICE_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::unique_ptr<base::Value> value(WaitForSingleResult(func.get()));
std::string access_token;
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
EXPECT_FALSE(func->login_ui_shown());
EXPECT_TRUE(func->scope_ui_shown());
}
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->set_mint_token_result(TestOAuth2MintTokenFlow::ISSUE_ADVICE_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::kCanceled), WaitForError(func.get()));
EXPECT_FALSE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
QueueRequestComplete(type, &queued_request);
}
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->set_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::kCanceled), WaitForError(func.get()));
}
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(), "[{}]", browser());
EXPECT_EQ(std::string(errors::kNoGrant), error);
EXPECT_FALSE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
QueueRequestComplete(type, &queued_request);
}
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(kAccessToken,
base::TimeDelta::FromSeconds(3600));
SetCachedToken(token);
// Get a token. Should not require a GAIA request.
std::unique_ptr<base::Value> value(
utils::RunFunctionAndReturnSingleResult(func.get(), "[{}]", browser()));
std::string access_token;
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
EXPECT_FALSE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
NonInteractiveIssueAdviceCacheHit) {
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 advice
IssueAdviceInfo info;
IdentityTokenCacheValue token(info);
SetCachedToken(token);
// Should return an error without a GAIA request.
std::string error = utils::RunFunctionAndReturnError(
func.get(), "[{}]", browser());
EXPECT_EQ(std::string(errors::kNoGrant), error);
EXPECT_FALSE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
}
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->set_mint_token_result(TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS);
RunFunctionAsync(func.get(), "[{\"interactive\": true}]");
// Populate the cache with a token while the request is blocked.
IdentityTokenCacheValue token(kAccessToken,
base::TimeDelta::FromSeconds(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::unique_ptr<base::Value> value(WaitForSingleResult(func.get()));
std::string access_token;
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
EXPECT_FALSE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
}
// The interactive login UI is never shown on ChromeOS, so tests of the
// interactive login flow being successful are not relevant on that platform.
#if !defined(OS_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(kAccessToken,
base::TimeDelta::FromSeconds(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->set_mint_token_result(TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS);
std::unique_ptr<base::Value> value(utils::RunFunctionAndReturnSingleResult(
func.get(), "[{\"interactive\": true}]", browser()));
std::string access_token;
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
EXPECT_TRUE(func->login_ui_shown());
EXPECT_TRUE(func->scope_ui_shown());
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN,
GetCachedToken(std::string()).status());
}
#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));
func->set_extension(extension.get());
const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(extension.get());
EXPECT_TRUE(oauth2_info.client_id.empty());
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->set_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::kCanceled), WaitForError(func.get()));
}
// 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->set_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->set_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(), "[{}]", browser()->profile());
AsyncFunctionRunner func2_runner;
func2_runner.RunFunctionAsync(func2.get(), "[{}]", browser()->profile());
// Shut down IdentityAPI and ensure that both functions complete with an
// error.
id_api()->Shutdown();
EXPECT_EQ(std::string(errors::kCanceled),
func1_runner.WaitForError(func1.get()));
EXPECT_EQ(std::string(errors::kCanceled),
func2_runner.WaitForError(func2.get()));
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, ManuallyIssueToken) {
std::string 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->set_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::unique_ptr<base::Value> value(WaitForSingleResult(func.get()));
std::string access_token;
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN,
GetCachedToken(std::string()).status());
EXPECT_EQ(primary_account_access_token, func->login_access_token());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, ManuallyIssueTokenFailure) {
std::string 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->set_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();
token_service_->IssueErrorForAllPendingRequestsForAccount(
primary_account_id,
GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE));
EXPECT_EQ(
std::string(errors::kAuthFailure) +
GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE)
.ToString(),
WaitForError(func.get()));
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
MultiDefaultUserManuallyIssueToken) {
std::string primary_account_id = SignIn("primary@example.com");
SeedAccountInfo("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->set_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::unique_ptr<base::Value> value(WaitForSingleResult(func.get()));
std::string access_token;
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN,
GetCachedToken(std::string()).status());
EXPECT_EQ(primary_account_access_token, func->login_access_token());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
MultiPrimaryUserManuallyIssueToken) {
std::string primary_account_id = SignIn("primary@example.com");
AddAccount("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->set_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::unique_ptr<base::Value> value(WaitForSingleResult(func.get()));
std::string access_token;
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN,
GetCachedToken(std::string()).status());
EXPECT_EQ(primary_account_access_token, func->login_access_token());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
MultiSecondaryUserManuallyIssueToken) {
std::string primary_account_id = SignIn("primary@example.com");
std::string secondary_account_id = AddAccount("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->set_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_secondary@example.com\" } }]");
run_loop.Run();
std::string secondary_account_access_token =
IssueLoginAccessTokenForAccount(secondary_account_id);
std::unique_ptr<base::Value> value(WaitForSingleResult(func.get()));
std::string access_token;
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN,
GetCachedToken(secondary_account_id).status());
EXPECT_EQ(secondary_account_access_token, func->login_access_token());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
MultiUnknownUserGetTokenFromTokenServiceFailure) {
SignIn("primary@example.com");
AddAccount("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\" } }]",
browser());
EXPECT_EQ(std::string(errors::kUserNotSignedIn), error);
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
MultiSecondaryNonInteractiveMintFailure) {
SignIn("primary@example.com");
AddAccount("secondary@example.com");
scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction());
func->set_extension(CreateExtension(CLIENT_ID | SCOPES));
func->set_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_FAILURE);
std::string error = utils::RunFunctionAndReturnError(
func.get(),
"[{\"account\": { \"id\": \"gaia_id_for_secondary@example.com\" } }]",
browser());
EXPECT_TRUE(base::StartsWith(error, errors::kAuthFailure,
base::CompareCase::INSENSITIVE_ASCII));
EXPECT_FALSE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
MultiSecondaryNonInteractiveLoginAccessTokenFailure) {
SignIn("primary@example.com");
AddAccount("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\" } }]",
browser());
EXPECT_TRUE(base::StartsWith(error, errors::kAuthFailure,
base::CompareCase::INSENSITIVE_ASCII));
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
MultiSecondaryInteractiveApprovalAborted) {
SignIn("primary@example.com");
AddAccount("secondary@example.com");
scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction());
func->set_extension(CreateExtension(CLIENT_ID | SCOPES));
func->set_mint_token_result(TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS);
func->set_scope_ui_failure(GaiaWebAuthFlow::WINDOW_CLOSED);
std::string error = utils::RunFunctionAndReturnError(
func.get(),
"[{\"account\": { \"id\": \"gaia_id_for_secondary@example.com\" }, "
"\"interactive\": "
"true}]",
browser());
EXPECT_EQ(std::string(errors::kUserRejected), error);
EXPECT_FALSE(func->login_ui_shown());
EXPECT_TRUE(func->scope_ui_shown());
}
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->set_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS);
std::unique_ptr<base::Value> value(
utils::RunFunctionAndReturnSingleResult(func.get(), "[{}]", browser()));
std::string access_token;
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
const ExtensionTokenKey* token_key = func->GetExtensionTokenKeyForTest();
EXPECT_EQ(2ul, token_key->scopes.size());
EXPECT_TRUE(base::ContainsKey(token_key->scopes, "scope1"));
EXPECT_TRUE(base::ContainsKey(token_key->scopes, "scope2"));
}
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\": []}]", browser()));
EXPECT_EQ(errors::kInvalidScopes, error);
}
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());
func->set_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS);
std::unique_ptr<base::Value> value(utils::RunFunctionAndReturnSingleResult(
func.get(), "[{\"scopes\": [\"email\"]}]", browser()));
std::string access_token;
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
const ExtensionTokenKey* token_key = func->GetExtensionTokenKeyForTest();
EXPECT_EQ(1ul, token_key->scopes.size());
EXPECT_TRUE(base::ContainsKey(token_key->scopes, "email"));
}
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());
func->set_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS);
std::unique_ptr<base::Value> value(utils::RunFunctionAndReturnSingleResult(
func.get(), "[{\"scopes\": [\"email\", \"foo\", \"bar\"]}]", browser()));
std::string access_token;
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
const ExtensionTokenKey* token_key = func->GetExtensionTokenKeyForTest();
EXPECT_EQ(3ul, token_key->scopes.size());
EXPECT_TRUE(base::ContainsKey(token_key->scopes, "email"));
EXPECT_TRUE(base::ContainsKey(token_key->scopes, "foo"));
EXPECT_TRUE(base::ContainsKey(token_key->scopes, "bar"));
}
#if defined(OS_CHROMEOS)
class GetAuthTokenFunctionPublicSessionTest : public GetAuthTokenFunctionTest {
public:
GetAuthTokenFunctionPublicSessionTest()
: user_manager_(new chromeos::MockUserManager) {}
protected:
void SetUpInProcessBrowserTestFixture() override {
GetAuthTokenFunctionTest::SetUpInProcessBrowserTestFixture();
// Set up the user manager to fake a public session.
EXPECT_CALL(*user_manager_, IsLoggedInAsKioskApp())
.WillRepeatedly(Return(false));
EXPECT_CALL(*user_manager_, IsLoggedInAsPublicAccount())
.WillRepeatedly(Return(true));
// Set up fake install attributes to make the device appeared as
// enterprise-managed.
std::unique_ptr<chromeos::StubInstallAttributes> attributes =
std::make_unique<chromeos::StubInstallAttributes>();
attributes->SetCloudManaged("example.com", "fake-id");
policy::BrowserPolicyConnectorChromeOS::SetInstallAttributesForTesting(
attributes.release());
}
scoped_refptr<Extension> CreateTestExtension(const std::string& id) {
return ExtensionBuilder("Test")
.MergeManifest(
DictionaryBuilder()
.Set("oauth2",
DictionaryBuilder()
.Set("client_id", "clientId")
.Set("scopes", ListBuilder().Append("scope1").Build())
.Build())
.Build())
.SetID(id)
.Build();
}
// Owned by |user_manager_enabler|.
chromeos::MockUserManager* user_manager_;
};
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionPublicSessionTest, NonWhitelisted) {
// GetAuthToken() should return UserNotSignedIn in public sessions for
// non-whitelisted extensions.
user_manager::ScopedUserManager user_manager_enabler(
base::WrapUnique(user_manager_));
scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction());
func->set_extension(CreateTestExtension("test-id"));
std::string error = utils::RunFunctionAndReturnError(
func.get(), "[]", browser());
EXPECT_EQ(std::string(errors::kUserNotSignedIn), error);
EXPECT_FALSE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
}
// TODO(crbug.com/830052): This test is flaky.
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionPublicSessionTest,
DISABLED_Whitelisted) {
// GetAuthToken() should return a token for whitelisted extensions.
user_manager::ScopedUserManager user_manager_enabler(
base::WrapUnique(user_manager_));
scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction());
func->set_extension(CreateTestExtension("ljacajndfccfgnfohlgkdphmbnpkjflk"));
func->set_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS);
std::unique_ptr<base::Value> value(
utils::RunFunctionAndReturnSingleResult(func.get(), "[{}]", browser()));
std::string access_token;
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
}
#endif
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 + "\"}]",
browser(), api_test_utils::NONE);
}
IdentityAPI* id_api() {
return IdentityAPI::GetFactoryInstance()->Get(browser()->profile());
}
void SetCachedToken(const IdentityTokenCacheValue& token_data) {
ExtensionTokenKey key(
kExtensionId, "test@example.com", std::set<std::string>());
id_api()->SetCachedToken(key, token_data);
}
const IdentityTokenCacheValue& GetCachedToken() {
return id_api()->GetCachedToken(ExtensionTokenKey(
kExtensionId, "test@example.com", std::set<std::string>()));
}
};
IN_PROC_BROWSER_TEST_F(RemoveCachedAuthTokenFunctionTest, NotFound) {
EXPECT_TRUE(InvalidateDefaultToken());
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_NOTFOUND,
GetCachedToken().status());
}
IN_PROC_BROWSER_TEST_F(RemoveCachedAuthTokenFunctionTest, Advice) {
IssueAdviceInfo info;
IdentityTokenCacheValue advice(info);
SetCachedToken(advice);
EXPECT_TRUE(InvalidateDefaultToken());
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_ADVICE,
GetCachedToken().status());
}
IN_PROC_BROWSER_TEST_F(RemoveCachedAuthTokenFunctionTest, NonMatchingToken) {
IdentityTokenCacheValue token("non_matching_token",
base::TimeDelta::FromSeconds(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(kAccessToken,
base::TimeDelta::FromSeconds(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);
}
};
#if defined(OS_LINUX)
// This test times out on Linux MSan Tests.
// See https://crbug.com/831848 .
#define MAYBE_UserCloseWindow DISABLED_UserCloseWindow
#else
#define MAYBE_UserCloseWindow UserCloseWindow
#endif
IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTest, MAYBE_UserCloseWindow) {
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.ServeFilesFromSourceDirectory(
"chrome/test/data/extensions/api_test/identity");
ASSERT_TRUE(https_server.Start());
GURL auth_url(https_server.GetURL("/interaction_required.html"));
scoped_refptr<IdentityLaunchWebAuthFlowFunction> function(
new IdentityLaunchWebAuthFlowFunction());
scoped_refptr<Extension> empty_extension(ExtensionBuilder("Test").Build());
function->set_extension(empty_extension.get());
WaitForGURLAndCloseWindow popup_observer(auth_url);
std::string args = "[{\"interactive\": true, \"url\": \"" +
auth_url.spec() + "\"}]";
RunFunctionAsync(function.get(), args);
popup_observer.Wait();
popup_observer.CloseEmbedderWebContents();
EXPECT_EQ(std::string(errors::kUserRejected), WaitForError(function.get()));
}
IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTest, InteractionRequired) {
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.ServeFilesFromSourceDirectory(
"chrome/test/data/extensions/api_test/identity");
ASSERT_TRUE(https_server.Start());
GURL auth_url(https_server.GetURL("/interaction_required.html"));
scoped_refptr<IdentityLaunchWebAuthFlowFunction> function(
new IdentityLaunchWebAuthFlowFunction());
scoped_refptr<Extension> empty_extension(ExtensionBuilder("Test").Build());
function->set_extension(empty_extension.get());
std::string args = "[{\"interactive\": false, \"url\": \"" +
auth_url.spec() + "\"}]";
std::string error =
utils::RunFunctionAndReturnError(function.get(), args, browser());
EXPECT_EQ(std::string(errors::kInteractionRequired), error);
}
IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTest, LoadFailed) {
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.ServeFilesFromSourceDirectory(
"chrome/test/data/extensions/api_test/identity");
ASSERT_TRUE(https_server.Start());
GURL auth_url(https_server.GetURL("/five_hundred.html"));
scoped_refptr<IdentityLaunchWebAuthFlowFunction> function(
new IdentityLaunchWebAuthFlowFunction());
scoped_refptr<Extension> empty_extension(ExtensionBuilder("Test").Build());
function->set_extension(empty_extension.get());
std::string args = "[{\"interactive\": true, \"url\": \"" +
auth_url.spec() + "\"}]";
std::string error =
utils::RunFunctionAndReturnError(function.get(), args, browser());
EXPECT_EQ(std::string(errors::kPageLoadFailure), error);
}
IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTest, NonInteractiveSuccess) {
scoped_refptr<IdentityLaunchWebAuthFlowFunction> function(
new IdentityLaunchWebAuthFlowFunction());
scoped_refptr<Extension> empty_extension(ExtensionBuilder("Test").Build());
function->set_extension(empty_extension.get());
function->InitFinalRedirectURLPrefixForTest("abcdefghij");
std::unique_ptr<base::Value> value(utils::RunFunctionAndReturnSingleResult(
function.get(),
"[{\"interactive\": false,"
"\"url\": \"https://abcdefghij.chromiumapp.org/callback#test\"}]",
browser()));
std::string url;
EXPECT_TRUE(value->GetAsString(&url));
EXPECT_EQ(std::string("https://abcdefghij.chromiumapp.org/callback#test"),
url);
}
IN_PROC_BROWSER_TEST_F(
LaunchWebAuthFlowFunctionTest, InteractiveFirstNavigationSuccess) {
scoped_refptr<IdentityLaunchWebAuthFlowFunction> function(
new IdentityLaunchWebAuthFlowFunction());
scoped_refptr<Extension> empty_extension(ExtensionBuilder("Test").Build());
function->set_extension(empty_extension.get());
function->InitFinalRedirectURLPrefixForTest("abcdefghij");
std::unique_ptr<base::Value> value(utils::RunFunctionAndReturnSingleResult(
function.get(),
"[{\"interactive\": true,"
"\"url\": \"https://abcdefghij.chromiumapp.org/callback#test\"}]",
browser()));
std::string url;
EXPECT_TRUE(value->GetAsString(&url));
EXPECT_EQ(std::string("https://abcdefghij.chromiumapp.org/callback#test"),
url);
}
IN_PROC_BROWSER_TEST_F(LaunchWebAuthFlowFunctionTest,
DISABLED_InteractiveSecondNavigationSuccess) {
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.ServeFilesFromSourceDirectory(
"chrome/test/data/extensions/api_test/identity");
ASSERT_TRUE(https_server.Start());
GURL auth_url(https_server.GetURL("/redirect_to_chromiumapp.html"));
scoped_refptr<IdentityLaunchWebAuthFlowFunction> function(
new IdentityLaunchWebAuthFlowFunction());
scoped_refptr<Extension> empty_extension(ExtensionBuilder("Test").Build());
function->set_extension(empty_extension.get());
function->InitFinalRedirectURLPrefixForTest("abcdefghij");
std::string args = "[{\"interactive\": true, \"url\": \"" +
auth_url.spec() + "\"}]";
std::unique_ptr<base::Value> value(
utils::RunFunctionAndReturnSingleResult(function.get(), args, browser()));
std::string url;
EXPECT_TRUE(value->GetAsString(&url));
EXPECT_EQ(std::string("https://abcdefghij.chromiumapp.org/callback#test"),
url);
}
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::Bind(&OnSignInChangedEventTest::OnSignInEventChanged,
base::Unretained(this)));
IdentityTestWithSignin::SetUpOnMainThread();
}
IdentityAPI* id_api() {
return IdentityAPI::GetFactoryInstance()->Get(browser()->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(std::unique_ptr<base::ListValue> args) {
expected_events_.insert(
std::make_unique<Event>(events::IDENTITY_ON_SIGN_IN_CHANGED,
api::identity::OnSignInChanged::kEventName,
std::move(args), browser()->profile()));
}
bool HasExpectedEvent() { return expected_events_.size(); }
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.get();
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.get();
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.get());
}
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 !defined(OS_CHROMEOS)
// Test that an event is fired when the primary account signs out.
IN_PROC_BROWSER_TEST_F(OnSignInChangedEventTest, FireOnPrimaryAccountSignOut) {
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.
signin_manager_->ForceSignOut();
EXPECT_FALSE(HasExpectedEvent());
}
#endif // !defined(OS_CHROMEOS)
// Test that an event is fired when the primary account has a refresh token
// revoked.
IN_PROC_BROWSER_TEST_F(OnSignInChangedEventTest,
FireOnPrimaryAccountRefreshTokenRevoked) {
api::identity::AccountInfo account_info;
account_info.id = "gaia_id_for_primary@example.com";
AddExpectedEvent(api::identity::OnSignInChanged::Create(account_info, true));
std::string primary_account_id = SignIn("primary@example.com");
AddExpectedEvent(api::identity::OnSignInChanged::Create(account_info, false));
// Revoke the refresh token and verify that the callback fires.
// TODO(treib,blundell): This breaks IdentityManager.
token_service_->RevokeCredentials(primary_account_id);
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));
std::string primary_account_id = SignIn("primary@example.com");
AddExpectedEvent(api::identity::OnSignInChanged::Create(account_info, false));
// TODO(treib,blundell): This breaks IdentityManager.
token_service_->RevokeCredentials(primary_account_id);
account_info.id = "gaia_id_for_primary@example.com";
AddExpectedEvent(api::identity::OnSignInChanged::Create(account_info, true));
// Make the primary account's refresh token available and check that the
// callback fires. Note that we must call AddAccount() here as the account's
// information must be present in the AccountTrackerService as well.
AddAccount("primary@example.com");
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's refresh token available and check that the
// callback fires. Note that we must call AddAccount() here as the account's
// information must be present in the AccountTrackerService as well.
std::string secondary_account_id = AddAccount("secondary@example.com");
EXPECT_FALSE(HasExpectedEvent());
// Revoke the secondary account's refresh token and check that the callback
// fires.
AddExpectedEvent(api::identity::OnSignInChanged::Create(account_info, false));
token_service_->RevokeCredentials(secondary_account_id);
EXPECT_FALSE(HasExpectedEvent());
}
} // namespace extensions
// Tests the chrome.identity API implemented by custom JS bindings .
IN_PROC_BROWSER_TEST_F(ExtensionApiTest, ChromeIdentityJsBindings) {
ASSERT_TRUE(RunExtensionTest("identity/js_bindings")) << message_;
}