diff options
author | courage@chromium.org <courage@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-05 17:18:01 +0000 |
---|---|---|
committer | courage@chromium.org <courage@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-05 17:18:01 +0000 |
commit | 47600beff0c5d467201cca76757e039e0a863fdb (patch) | |
tree | 334e8d3fc0e6a4f6ad98877a5aed28725994c94f | |
parent | a4470ecccba04d91fadd470ef7f2c453644f7418 (diff) | |
download | chromium_src-47600beff0c5d467201cca76757e039e0a863fdb.zip chromium_src-47600beff0c5d467201cca76757e039e0a863fdb.tar.gz chromium_src-47600beff0c5d467201cca76757e039e0a863fdb.tar.bz2 |
Multiple account support in chrome.identity.getAuthToken
This change adds an optional "account" field in the TokenDetails
parameter to getAuthToken. When this field is populated with an object
identifying a valid account signed in on the current profile,
getAuthToken will return a token for the specified account.
When the parameter is not present, getAuthToken behaves as before,
returning tokens for the primary profile user.
BUG=258515
Review URL: https://codereview.chromium.org/293063002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@275173 0039d316-1c4b-4281-b951-d872f2087c98
9 files changed, 354 insertions, 56 deletions
diff --git a/chrome/browser/extensions/api/identity/account_tracker.cc b/chrome/browser/extensions/api/identity/account_tracker.cc index 6afc9f9..a93f505 100644 --- a/chrome/browser/extensions/api/identity/account_tracker.cc +++ b/chrome/browser/extensions/api/identity/account_tracker.cc @@ -77,6 +77,20 @@ std::vector<AccountIds> AccountTracker::GetAccounts() const { return accounts; } +std::string AccountTracker::FindAccountKeyByGaiaId(const std::string& gaia_id) { + for (std::map<std::string, AccountState>::const_iterator it = + accounts_.begin(); + it != accounts_.end(); + ++it) { + const AccountState& state = it->second; + if (state.ids.gaia == gaia_id) { + return state.ids.account_key; + } + } + + return std::string(); +} + void AccountTracker::OnRefreshTokenAvailable(const std::string& account_id) { // Ignore refresh tokens if there is no primary account ID at all. if (signin_manager_account_id().empty()) diff --git a/chrome/browser/extensions/api/identity/account_tracker.h b/chrome/browser/extensions/api/identity/account_tracker.h index 7c8db2c..3cd3a98 100644 --- a/chrome/browser/extensions/api/identity/account_tracker.h +++ b/chrome/browser/extensions/api/identity/account_tracker.h @@ -64,6 +64,7 @@ class AccountTracker : public OAuth2TokenService::Observer, // have been fetched. The primary account for the profile will be first // in the vector. Additional accounts will be in order of their gaia IDs. std::vector<AccountIds> GetAccounts() const; + std::string FindAccountKeyByGaiaId(const std::string& gaia_id); // OAuth2TokenService::Observer implementation. virtual void OnRefreshTokenAvailable(const std::string& account_key) OVERRIDE; diff --git a/chrome/browser/extensions/api/identity/gaia_web_auth_flow.cc b/chrome/browser/extensions/api/identity/gaia_web_auth_flow.cc index 5da652d..49d4d1e 100644 --- a/chrome/browser/extensions/api/identity/gaia_web_auth_flow.cc +++ b/chrome/browser/extensions/api/identity/gaia_web_auth_flow.cc @@ -20,11 +20,11 @@ namespace extensions { GaiaWebAuthFlow::GaiaWebAuthFlow(Delegate* delegate, Profile* profile, + const std::string& account_id, const std::string& extension_id, const OAuth2Info& oauth2_info, const std::string& locale) - : delegate_(delegate), - profile_(profile) { + : delegate_(delegate), profile_(profile), account_id_(account_id) { const char kOAuth2RedirectPathFormat[] = "/%s#"; const char kOAuth2AuthorizeFormat[] = "?response_type=token&approval_prompt=force&authuser=0&" @@ -64,10 +64,7 @@ void GaiaWebAuthFlow::Start() { ubertoken_fetcher_.reset(new UbertokenFetcher(token_service, this, profile_->GetRequestContext())); - SigninManagerBase* signin_manager = - SigninManagerFactory::GetForProfile(profile_); - ubertoken_fetcher_->StartFetchingToken( - signin_manager->GetAuthenticatedAccountId()); + ubertoken_fetcher_->StartFetchingToken(account_id_); } void GaiaWebAuthFlow::OnUbertokenSuccess(const std::string& token) { @@ -87,6 +84,7 @@ void GaiaWebAuthFlow::OnUbertokenSuccess(const std::string& token) { } void GaiaWebAuthFlow::OnUbertokenFailure(const GoogleServiceAuthError& error) { + DVLOG(1) << "OnUbertokenFailure: " << error.error_message(); delegate_->OnGaiaFlowFailure( GaiaWebAuthFlow::SERVICE_AUTH_ERROR, error, std::string()); } @@ -99,6 +97,7 @@ void GaiaWebAuthFlow::OnAuthFlowFailure(WebAuthFlow::Failure failure) { gaia_failure = GaiaWebAuthFlow::WINDOW_CLOSED; break; case WebAuthFlow::LOAD_FAILED: + DVLOG(1) << "OnAuthFlowFailure LOAD_FAILED"; gaia_failure = GaiaWebAuthFlow::LOAD_FAILED; break; default: diff --git a/chrome/browser/extensions/api/identity/gaia_web_auth_flow.h b/chrome/browser/extensions/api/identity/gaia_web_auth_flow.h index 684fb0b..1b2abae 100644 --- a/chrome/browser/extensions/api/identity/gaia_web_auth_flow.h +++ b/chrome/browser/extensions/api/identity/gaia_web_auth_flow.h @@ -14,7 +14,7 @@ namespace extensions { // Implements a web-based OAuth2 scope approval dialog. This flow has // four parts: -// 1. Fetch an ubertoken for the signed-in user. +// 1. Fetch an ubertoken for a signed-in user. // 2. Use the ubertoken to get session cookies using MergeSession. // 3. Start the OAuth flow and wait for final redirect. // 4. Parse results from the fragment component of the final redirect URI. @@ -60,6 +60,7 @@ class GaiaWebAuthFlow : public UbertokenConsumer, public WebAuthFlow::Delegate { GaiaWebAuthFlow(Delegate* delegate, Profile* profile, + const std::string& account_id, const std::string& extension_id, const OAuth2Info& oauth2_info, const std::string& locale); @@ -84,6 +85,7 @@ class GaiaWebAuthFlow : public UbertokenConsumer, public WebAuthFlow::Delegate { Delegate* delegate_; Profile* profile_; + std::string account_id_; chrome::HostDesktopType host_desktop_type_; std::string redirect_scheme_; std::string redirect_path_prefix_; @@ -94,6 +96,6 @@ class GaiaWebAuthFlow : public UbertokenConsumer, public WebAuthFlow::Delegate { DISALLOW_COPY_AND_ASSIGN(GaiaWebAuthFlow); }; -} // extensions +} // namespace extensions #endif // CHROME_BROWSER_EXTENSIONS_API_IDENTITY_GAIA_WEB_AUTH_FLOW_H_ diff --git a/chrome/browser/extensions/api/identity/gaia_web_auth_flow_unittest.cc b/chrome/browser/extensions/api/identity/gaia_web_auth_flow_unittest.cc index a55c533..4b61e04 100644 --- a/chrome/browser/extensions/api/identity/gaia_web_auth_flow_unittest.cc +++ b/chrome/browser/extensions/api/identity/gaia_web_auth_flow_unittest.cc @@ -33,6 +33,7 @@ class TestGaiaWebAuthFlow : public GaiaWebAuthFlow { GoogleServiceAuthError::State ubertoken_error_state) : GaiaWebAuthFlow(delegate, NULL, + "account_id", "extension_id", oauth2_info, "en-us"), diff --git a/chrome/browser/extensions/api/identity/identity_api.cc b/chrome/browser/extensions/api/identity/identity_api.cc index d3571e9..17c973a 100644 --- a/chrome/browser/extensions/api/identity/identity_api.cc +++ b/chrome/browser/extensions/api/identity/identity_api.cc @@ -169,6 +169,7 @@ const IdentityAPI::CachedTokens& IdentityAPI::GetAllCachedTokens() { } std::vector<std::string> IdentityAPI::GetAccounts() const { + const std::string primary_account_id = GetPrimaryAccountId(browser_context_); const std::vector<AccountIds> ids = account_tracker_.GetAccounts(); std::vector<std::string> gaia_ids; @@ -185,6 +186,10 @@ std::vector<std::string> IdentityAPI::GetAccounts() const { return gaia_ids; } +std::string IdentityAPI::FindAccountKeyByGaiaId(const std::string& gaia_id) { + return account_tracker_.FindAccountKeyByGaiaId(gaia_id); +} + void IdentityAPI::ReportAuthError(const GoogleServiceAuthError& error) { account_tracker_.ReportAuthError(GetPrimaryAccountId(browser_context_), error); @@ -317,8 +322,28 @@ bool IdentityGetAuthTokenFunction::RunAsync() { std::set<std::string> scopes(oauth2_info.scopes.begin(), oauth2_info.scopes.end()); - token_key_.reset(new ExtensionTokenKey( - GetExtension()->id(), GetPrimaryAccountId(GetProfile()), scopes)); + + std::string account_key = GetPrimaryAccountId(GetProfile()); + + if (params->details->account.get()) { + std::string detail_key = + extensions::IdentityAPI::GetFactoryInstance() + ->Get(GetProfile()) + ->FindAccountKeyByGaiaId(params->details->account->id); + + if (detail_key != account_key) { + if (detail_key.empty() || !switches::IsExtensionsMultiAccount()) { + // TODO(courage): should this be a different error? + error_ = identity_constants::kUserNotSignedIn; + return false; + } + + account_key = detail_key; + } + } + + token_key_.reset( + new ExtensionTokenKey(GetExtension()->id(), account_key, scopes)); // From here on out, results must be returned asynchronously. StartAsyncRun(); @@ -645,7 +670,6 @@ void IdentityGetAuthTokenFunction::StartDeviceLoginAccessTokenRequest() { void IdentityGetAuthTokenFunction::StartLoginAccessTokenRequest() { ProfileOAuth2TokenService* service = ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile()); - const std::string primary_account_id = GetPrimaryAccountId(GetProfile()); #if defined(OS_CHROMEOS) if (chrome::IsRunningInForcedAppMode()) { std::string app_client_id; @@ -653,7 +677,7 @@ void IdentityGetAuthTokenFunction::StartLoginAccessTokenRequest() { if (chromeos::UserManager::Get()->GetAppModeChromeClientOAuthInfo( &app_client_id, &app_client_secret)) { login_token_request_ = - service->StartRequestForClient(primary_account_id, + service->StartRequestForClient(token_key_->account_id, app_client_id, app_client_secret, OAuth2TokenService::ScopeSet(), @@ -663,7 +687,7 @@ void IdentityGetAuthTokenFunction::StartLoginAccessTokenRequest() { } #endif login_token_request_ = service->StartRequest( - primary_account_id, OAuth2TokenService::ScopeSet(), this); + token_key_->account_id, OAuth2TokenService::ScopeSet(), this); } void IdentityGetAuthTokenFunction::StartGaiaRequest( @@ -684,8 +708,12 @@ void IdentityGetAuthTokenFunction::ShowOAuthApprovalDialog( const std::string locale = g_browser_process->local_state()->GetString( prefs::kApplicationLocale); - gaia_web_auth_flow_.reset(new GaiaWebAuthFlow( - this, GetProfile(), GetExtension()->id(), oauth2_info, locale)); + gaia_web_auth_flow_.reset(new GaiaWebAuthFlow(this, + GetProfile(), + token_key_->account_id, + GetExtension()->id(), + oauth2_info, + locale)); gaia_web_auth_flow_->Start(); } @@ -707,8 +735,7 @@ OAuth2MintTokenFlow* IdentityGetAuthTokenFunction::CreateMintTokenFlow( bool IdentityGetAuthTokenFunction::HasLoginToken() const { ProfileOAuth2TokenService* token_service = ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile()); - return token_service->RefreshTokenIsAvailable( - GetPrimaryAccountId(GetProfile())); + return token_service->RefreshTokenIsAvailable(token_key_->account_id); } std::string IdentityGetAuthTokenFunction::MapOAuth2ErrorToDescription( diff --git a/chrome/browser/extensions/api/identity/identity_api.h b/chrome/browser/extensions/api/identity/identity_api.h index 8551c1c..351aad9 100644 --- a/chrome/browser/extensions/api/identity/identity_api.h +++ b/chrome/browser/extensions/api/identity/identity_api.h @@ -110,6 +110,7 @@ class IdentityAPI : public BrowserContextKeyedAPI, // Account queries. std::vector<std::string> GetAccounts() const; + std::string FindAccountKeyByGaiaId(const std::string& gaia_id); // Global error reporting. void ReportAuthError(const GoogleServiceAuthError& error); @@ -194,6 +195,26 @@ class IdentityGetAuthTokenFunction : public ChromeAsyncExtensionFunction, protected: virtual ~IdentityGetAuthTokenFunction(); + // IdentitySigninFlow::Delegate implementation: + virtual void SigninSuccess() OVERRIDE; + virtual void SigninFailed() OVERRIDE; + + // GaiaWebAuthFlow::Delegate implementation: + virtual void OnGaiaFlowFailure(GaiaWebAuthFlow::Failure failure, + GoogleServiceAuthError service_error, + const std::string& oauth_error) OVERRIDE; + virtual void OnGaiaFlowCompleted(const std::string& access_token, + const std::string& expiration) OVERRIDE; + + // OAuth2TokenService::Consumer implementation: + virtual void OnGetTokenSuccess(const OAuth2TokenService::Request* request, + const std::string& access_token, + const base::Time& expiration_time) OVERRIDE; + virtual void OnGetTokenFailure(const OAuth2TokenService::Request* request, + const GoogleServiceAuthError& error) OVERRIDE; + + scoped_ptr<OAuth2TokenService::Request> login_token_request_; + private: FRIEND_TEST_ALL_PREFIXES(GetAuthTokenFunctionTest, ComponentWithChromeClientId); @@ -228,24 +249,6 @@ class IdentityGetAuthTokenFunction : public ChromeAsyncExtensionFunction, virtual void OnIssueAdviceSuccess( const IssueAdviceInfo& issue_advice) OVERRIDE; - // IdentitySigninFlow::Delegate implementation: - virtual void SigninSuccess() OVERRIDE; - virtual void SigninFailed() OVERRIDE; - - // GaiaWebAuthFlow::Delegate implementation: - virtual void OnGaiaFlowFailure(GaiaWebAuthFlow::Failure failure, - GoogleServiceAuthError service_error, - const std::string& oauth_error) OVERRIDE; - virtual void OnGaiaFlowCompleted(const std::string& access_token, - const std::string& expiration) OVERRIDE; - - // OAuth2TokenService::Consumer implementation: - virtual void OnGetTokenSuccess(const OAuth2TokenService::Request* request, - const std::string& access_token, - const base::Time& expiration_time) OVERRIDE; - virtual void OnGetTokenFailure(const OAuth2TokenService::Request* request, - const GoogleServiceAuthError& error) OVERRIDE; - // IdentityAPI::ShutdownObserver implementation: virtual void OnShutdown() OVERRIDE; @@ -290,7 +293,6 @@ class IdentityGetAuthTokenFunction : public ChromeAsyncExtensionFunction, IssueAdviceInfo issue_advice_; scoped_ptr<GaiaWebAuthFlow> gaia_web_auth_flow_; scoped_ptr<IdentitySigninFlow> signin_flow_; - scoped_ptr<OAuth2TokenService::Request> login_token_request_; }; class IdentityRemoveCachedAuthTokenFunction diff --git a/chrome/browser/extensions/api/identity/identity_apitest.cc b/chrome/browser/extensions/api/identity/identity_apitest.cc index c0eb892..4e1ca7f 100644 --- a/chrome/browser/extensions/api/identity/identity_apitest.cc +++ b/chrome/browser/extensions/api/identity/identity_apitest.cc @@ -19,6 +19,10 @@ #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/guest_view/guest_view_base.h" #include "chrome/browser/profiles/profile.h" +#include "chrome/browser/signin/fake_profile_oauth2_token_service.h" +#include "chrome/browser/signin/fake_profile_oauth2_token_service_builder.h" +#include "chrome/browser/signin/fake_signin_manager.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" @@ -347,13 +351,107 @@ class MockGetAuthTokenFunction : public IdentityGetAuthTokenFunction { bool scope_ui_shown_; }; +// TODO(courage): Replace MockGetAuthTokenFunction with +// FakeGetAuthTokenFunction in all tests. + +class FakeGetAuthTokenFunction : public IdentityGetAuthTokenFunction { + public: + FakeGetAuthTokenFunction() + : login_access_token_result_(true), + login_ui_result_(true), + scope_ui_result_(true), + login_ui_shown_(false), + scope_ui_shown_(false) {} + + void set_login_access_token_result(bool result) { + login_access_token_result_ = result; + } + + void set_login_ui_result(bool result) { login_ui_result_ = result; } + + void set_mint_token_flow(scoped_ptr<OAuth2MintTokenFlow> flow) { + flow_ = flow.Pass(); + } + + void set_scope_ui_failure(GaiaWebAuthFlow::Failure failure) { + scope_ui_result_ = false; + scope_ui_failure_ = failure; + } + + 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_; } + + virtual void ShowLoginPopup() OVERRIDE { + EXPECT_FALSE(login_ui_shown_); + login_ui_shown_ = true; + if (login_ui_result_) + SigninSuccess(); + else + SigninFailed(); + } + + virtual 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) { + GoogleServiceAuthError error(GoogleServiceAuthError::CONNECTION_FAILED); + OnGaiaFlowFailure(scope_ui_failure_, error, ""); + } else { + GoogleServiceAuthError error(GoogleServiceAuthError::NONE); + OnGaiaFlowFailure(scope_ui_failure_, error, scope_ui_oauth_error_); + } + } + + virtual OAuth2MintTokenFlow* CreateMintTokenFlow( + const std::string& login_access_token) OVERRIDE { + EXPECT_TRUE(login_access_token_.empty()); + login_access_token_ = login_access_token; + return flow_.release(); + } + + private: + virtual ~FakeGetAuthTokenFunction() {} + bool login_access_token_result_; + bool login_ui_result_; + bool scope_ui_result_; + GaiaWebAuthFlow::Failure scope_ui_failure_; + std::string scope_ui_oauth_error_; + bool login_ui_shown_; + bool scope_ui_shown_; + + scoped_ptr<OAuth2MintTokenFlow> flow_; + + std::string login_access_token_; +}; + class MockQueuedMintRequest : public IdentityMintRequestQueue::Request { public: MOCK_METHOD1(StartMintToken, void(IdentityMintRequestQueue::MintType)); }; +AccountIds CreateIds(std::string email, std::string obfid) { + AccountIds ids; + ids.account_key = email; + ids.email = email; + ids.gaia = obfid; + return ids; +} + class IdentityGetAccountsFunctionTest : public ExtensionBrowserTest { virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { + ExtensionBrowserTest::SetUpCommandLine(command_line); command_line->AppendSwitch(switches::kExtensionsMultiAccount); } @@ -363,14 +461,6 @@ class IdentityGetAccountsFunctionTest : public ExtensionBrowserTest { ids, is_signed_in); } - AccountIds CreateIds(std::string email, std::string obfid) { - AccountIds ids; - ids.account_key = email; - ids.email = email; - ids.gaia = obfid; - return ids; - } - testing::AssertionResult ExpectGetAccounts( const std::vector<std::string>& accounts) { scoped_refptr<IdentityGetAccountsFunction> func( @@ -492,6 +582,60 @@ IN_PROC_BROWSER_TEST_F(IdentityOldProfilesGetAccountsFunctionTest, } class GetAuthTokenFunctionTest : public AsyncExtensionBrowserTest { + public: + virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { + AsyncExtensionBrowserTest::SetUpCommandLine(command_line); + command_line->AppendSwitch(switches::kExtensionsMultiAccount); + } + + virtual void SetUpInProcessBrowserTestFixture() OVERRIDE { + AsyncExtensionBrowserTest::SetUpInProcessBrowserTestFixture(); + + will_create_browser_context_services_subscription_ = + BrowserContextDependencyManager::GetInstance() + ->RegisterWillCreateBrowserContextServicesCallbackForTesting( + base::Bind(&GetAuthTokenFunctionTest:: + OnWillCreateBrowserContextServices, + base::Unretained(this))) + .Pass(); + } + + 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, &FakeSigninManagerBase::Build); + ProfileOAuth2TokenServiceFactory::GetInstance()->SetTestingFactory( + context, &BuildFakeProfileOAuth2TokenService); + } + + virtual 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_); + } + + void SignIn(const std::string account_key) { +#if defined(OS_CHROMEOS) + signin_manager_->SetAuthenticatedUsername(account_key); +#else + signin_manager_->SignIn(account_key, "password"); +#endif + } + + void SetAccountState(AccountIds ids, bool is_signed_in) { + IdentityAPI::GetFactoryInstance()->Get(profile())->SetAccountStateForTest( + ids, is_signed_in); + } + protected: enum OAuth2Fields { NONE = 0, @@ -500,6 +644,9 @@ class GetAuthTokenFunctionTest : public AsyncExtensionBrowserTest { AS_COMPONENT = 4 }; + FakeSigninManagerForTesting* signin_manager_; + FakeProfileOAuth2TokenService* token_service_; + virtual ~GetAuthTokenFunctionTest() {} // Helper to create an extension with specific OAuth2Info fields set. @@ -544,8 +691,10 @@ class GetAuthTokenFunctionTest : public AsyncExtensionBrowserTest { id_api()->SetCachedToken(key, token_data); } - const IdentityTokenCacheValue& GetCachedToken() { - ExtensionTokenKey key(extension_id_, GetPrimaryAccountId(), oauth_scopes_); + const IdentityTokenCacheValue& GetCachedToken(std::string account_id) { + if (account_id.empty()) + account_id = GetPrimaryAccountId(); + ExtensionTokenKey key(extension_id_, account_id, oauth_scopes_); return id_api()->GetCachedToken(key); } @@ -564,6 +713,9 @@ class GetAuthTokenFunctionTest : public AsyncExtensionBrowserTest { private: std::string extension_id_; std::set<std::string> oauth_scopes_; + + scoped_ptr<base::CallbackList<void(content::BrowserContext*)>::Subscription> + will_create_browser_context_services_subscription_; }; IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, @@ -644,7 +796,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, EXPECT_FALSE(func->scope_ui_shown()); EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_ADVICE, - GetCachedToken().status()); + GetCachedToken(std::string()).status()); } IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, @@ -707,7 +859,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, EXPECT_FALSE(func->login_ui_shown()); EXPECT_FALSE(func->scope_ui_shown()); EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN, - GetCachedToken().status()); + GetCachedToken(std::string()).status()); } IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, @@ -963,7 +1115,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, EXPECT_TRUE(func->scope_ui_shown()); EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN, - GetCachedToken().status()); + GetCachedToken(std::string()).status()); } IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, NoninteractiveQueue) { @@ -1224,7 +1376,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, EXPECT_TRUE(func->login_ui_shown()); EXPECT_TRUE(func->scope_ui_shown()); EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN, - GetCachedToken().status()); + GetCachedToken(std::string()).status()); } IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, ComponentWithChromeClientId) { @@ -1246,6 +1398,98 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, ComponentWithNormalClientId) { EXPECT_EQ("client1", func->GetOAuth2ClientId()); } +IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, MultiDefaultUser) { + SignIn("primary@example.com"); + token_service_->IssueRefreshTokenForUser("primary@example.com", + "refresh_token"); + SetAccountState(CreateIds("primary@example.com", "1"), true); + SetAccountState(CreateIds("secondary@example.com", "2"), true); + + scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); + scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); + func->set_extension(extension.get()); + func->set_mint_token_flow( + scoped_ptr<OAuth2MintTokenFlow>(new TestOAuth2MintTokenFlow( + TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS, func.get()))); + + RunFunctionAsync(func.get(), "[{}]"); + + token_service_->IssueAllTokensForAccount( + "primary@example.com", + "access_token-primary@example.com", + base::Time::Now() + base::TimeDelta::FromSeconds(3600)); + + scoped_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("access_token-primary@example.com", func->login_access_token()); +} + +IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, MultiPrimaryUser) { + SignIn("primary@example.com"); + token_service_->IssueRefreshTokenForUser("primary@example.com", + "refresh_token"); + SetAccountState(CreateIds("primary@example.com", "1"), true); + SetAccountState(CreateIds("secondary@example.com", "2"), true); + + scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); + scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); + func->set_extension(extension.get()); + func->set_mint_token_flow( + scoped_ptr<OAuth2MintTokenFlow>(new TestOAuth2MintTokenFlow( + TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS, func.get()))); + + RunFunctionAsync(func.get(), "[{\"account\": { \"id\": \"1\" } }]"); + + token_service_->IssueAllTokensForAccount( + "primary@example.com", + "access_token-primary@example.com", + base::Time::Now() + base::TimeDelta::FromSeconds(3600)); + + scoped_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("access_token-primary@example.com", func->login_access_token()); +} + +IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, MultiSecondaryUser) { + SignIn("primary@example.com"); + token_service_->IssueRefreshTokenForUser("secondary@example.com", + "refresh_token"); + SetAccountState(CreateIds("primary@example.com", "1"), true); + SetAccountState(CreateIds("secondary@example.com", "2"), true); + + scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); + scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); + func->set_extension(extension.get()); + func->set_mint_token_flow( + scoped_ptr<OAuth2MintTokenFlow>(new TestOAuth2MintTokenFlow( + TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS, func.get()))); + + RunFunctionAsync(func.get(), "[{\"account\": { \"id\": \"2\" } }]"); + + token_service_->IssueAllTokensForAccount( + "secondary@example.com", + "access_token-secondary@example.com", + base::Time::Now() + base::TimeDelta::FromSeconds(3600)); + + scoped_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@example.com").status()); + EXPECT_EQ("access_token-secondary@example.com", func->login_access_token()); +} + +// TODO(courage): negative cases for secondary accounts + class RemoveCachedAuthTokenFunctionTest : public ExtensionBrowserTest { protected: bool InvalidateDefaultToken() { @@ -1317,6 +1561,7 @@ IN_PROC_BROWSER_TEST_F(RemoveCachedAuthTokenFunctionTest, MatchingToken) { class LaunchWebAuthFlowFunctionTest : public AsyncExtensionBrowserTest { public: virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { + AsyncExtensionBrowserTest::SetUpCommandLine(command_line); // Reduce performance test variance by disabling background networking. command_line->AppendSwitch(switches::kDisableBackgroundNetworking); } diff --git a/chrome/common/extensions/api/identity.idl b/chrome/common/extensions/api/identity.idl index 18bd591..b4bbe93 100644 --- a/chrome/common/extensions/api/identity.idl +++ b/chrome/common/extensions/api/identity.idl @@ -5,6 +5,12 @@ // Use the <code>chrome.identity</code> API to get OAuth2 access tokens. namespace identity { + dictionary AccountInfo { + // A unique identifier for the account. This ID will not change + // for the lifetime of the account. + DOMString id; + }; + dictionary TokenDetails { // Fetching a token may require the user to sign-in to Chrome, or // approve the application's requested scopes. If the interactive @@ -13,6 +19,13 @@ namespace identity { // <code>false</code> or omitted, <code>getAuthToken</code> will // return failure any time a prompt would be required. boolean? interactive; + + // The account ID whose token should be returned. If not + // specified, the primary account for the profile will be used. + // + // <code>account</code> is only supported when the + // "enable-new-profile-management" flag is set. + AccountInfo? account; }; dictionary InvalidTokenDetails { @@ -39,12 +52,6 @@ namespace identity { boolean? interactive; }; - dictionary AccountInfo { - // A unique identifier for the account. This ID will not change - // for the lifetime of the account. - DOMString id; - }; - callback GetAuthTokenCallback = void (optional DOMString token); callback GetAccountsCallback = void (AccountInfo[] accounts); callback InvalidateAuthTokenCallback = void (); |