diff options
author | jamescook <jamescook@chromium.org> | 2015-01-16 13:28:07 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-01-16 21:28:53 +0000 |
commit | a03f13e31d30a93a8bb66ff21b36b1e16e353a4f (patch) | |
tree | 0dd0651a5006e45474dca6c118b8f6d1315216a3 /extensions | |
parent | 3095e631aa3b50c85caf5625d8b0f966ac73f1bf (diff) | |
download | chromium_src-a03f13e31d30a93a8bb66ff21b36b1e16e353a4f.zip chromium_src-a03f13e31d30a93a8bb66ff21b36b1e16e353a4f.tar.gz chromium_src-a03f13e31d30a93a8bb66ff21b36b1e16e353a4f.tar.bz2 |
app_shell: Fix chrome.identity.getAuthToken() access token generation
The old implementation was generating an access token for a given set of
scopes for the Chrome project id, not for the application's project id.
Instead, it needs to first retrieve an access token for the logged-in-user,
then use that access token to get an access token for the app's client id
and scopes.
BUG=449372
TEST=added to app_shell_unittests IdentityApiTest
Review URL: https://codereview.chromium.org/857563004
Cr-Commit-Position: refs/heads/master@{#311952}
Diffstat (limited to 'extensions')
4 files changed, 155 insertions, 16 deletions
diff --git a/extensions/shell/browser/api/identity/identity_api.cc b/extensions/shell/browser/api/identity/identity_api.cc index c87fac1..8fb4fbb 100644 --- a/extensions/shell/browser/api/identity/identity_api.cc +++ b/extensions/shell/browser/api/identity/identity_api.cc @@ -7,6 +7,8 @@ #include <set> #include <string> +#include "base/guid.h" +#include "content/public/browser/browser_context.h" #include "extensions/common/manifest_handlers/oauth2_manifest_handler.h" #include "extensions/shell/browser/shell_oauth2_token_service.h" #include "extensions/shell/common/api/identity.h" @@ -20,8 +22,31 @@ const char kIdentityApiId[] = "identity_api"; const char kErrorNoUserAccount[] = "No user account."; const char kErrorNoRefreshToken[] = "No refresh token."; const char kErrorNoScopesInManifest[] = "No scopes in manifest."; +const char kErrorUserPermissionRequired[] = + "User permission required but not available in app_shell"; } // namespace +IdentityAPI::IdentityAPI(content::BrowserContext* context) + : device_id_(base::GenerateGUID()) { +} + +IdentityAPI::~IdentityAPI() { +} + +// static +IdentityAPI* IdentityAPI::Get(content::BrowserContext* context) { + return BrowserContextKeyedAPIFactory<IdentityAPI>::Get(context); +} + +// static +BrowserContextKeyedAPIFactory<IdentityAPI>* IdentityAPI::GetFactoryInstance() { + static base::LazyInstance<BrowserContextKeyedAPIFactory<IdentityAPI>> + factory = LAZY_INSTANCE_INITIALIZER; + return factory.Pointer(); +} + +/////////////////////////////////////////////////////////////////////////////// + IdentityGetAuthTokenFunction::IdentityGetAuthTokenFunction() : OAuth2TokenService::Consumer(kIdentityApiId) { } @@ -29,6 +54,11 @@ IdentityGetAuthTokenFunction::IdentityGetAuthTokenFunction() IdentityGetAuthTokenFunction::~IdentityGetAuthTokenFunction() { } +void IdentityGetAuthTokenFunction::SetMintTokenFlowForTesting( + OAuth2MintTokenFlow* flow) { + mint_token_flow_.reset(flow); +} + ExtensionFunction::ResponseAction IdentityGetAuthTokenFunction::Run() { scoped_ptr<api::identity::GetAuthToken::Params> params( api::identity::GetAuthToken::Params::Create(*args_)); @@ -44,16 +74,18 @@ ExtensionFunction::ResponseAction IdentityGetAuthTokenFunction::Run() { // Verify that we have scopes. const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(extension()); - std::set<std::string> scopes(oauth2_info.scopes.begin(), - oauth2_info.scopes.end()); - if (scopes.empty()) + if (oauth2_info.scopes.empty()) return RespondNow(Error(kErrorNoScopesInManifest)); - AddRef(); // Balanced in OnGetTokenSuccess() and OnGetTokenFailure(). + // Balanced in OnGetTokenFailure() and in the OAuth2MintTokenFlow callbacks. + AddRef(); - // Start an asynchronous token fetch. + // First, fetch a logged-in-user access token for the Chrome project client ID + // and client secret. This token is used later to get a second access token + // that will be returned to the app. + std::set<std::string> no_scopes; access_token_request_ = - service->StartRequest(service->account_id(), scopes, this); + service->StartRequest(service->account_id(), no_scopes, this); return RespondLater(); } @@ -61,8 +93,23 @@ void IdentityGetAuthTokenFunction::OnGetTokenSuccess( const OAuth2TokenService::Request* request, const std::string& access_token, const base::Time& expiration_time) { - Respond(OneArgument(new base::StringValue(access_token))); - Release(); // Balanced in Run(). + // Tests may override the mint token flow. + if (!mint_token_flow_) { + const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(extension()); + DCHECK(!oauth2_info.scopes.empty()); + + mint_token_flow_.reset(new OAuth2MintTokenFlow( + this, + OAuth2MintTokenFlow::Parameters( + extension()->id(), + oauth2_info.client_id, + oauth2_info.scopes, + IdentityAPI::Get(browser_context())->device_id(), + OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE))); + } + + // Use the logging-in-user access token to mint an access token for this app. + mint_token_flow_->Start(browser_context()->GetRequestContext(), access_token); } void IdentityGetAuthTokenFunction::OnGetTokenFailure( @@ -72,6 +119,25 @@ void IdentityGetAuthTokenFunction::OnGetTokenFailure( Release(); // Balanced in Run(). } +void IdentityGetAuthTokenFunction::OnMintTokenSuccess( + const std::string& access_token, + int time_to_live) { + Respond(OneArgument(new base::StringValue(access_token))); + Release(); // Balanced in Run(). +} + +void IdentityGetAuthTokenFunction::OnIssueAdviceSuccess( + const IssueAdviceInfo& issue_advice) { + Respond(Error(kErrorUserPermissionRequired)); + Release(); // Balanced in Run(). +} + +void IdentityGetAuthTokenFunction::OnMintTokenFailure( + const GoogleServiceAuthError& error) { + Respond(Error(error.ToString())); + Release(); // Balanced in Run(). +} + /////////////////////////////////////////////////////////////////////////////// IdentityRemoveCachedAuthTokenFunction::IdentityRemoveCachedAuthTokenFunction() { diff --git a/extensions/shell/browser/api/identity/identity_api.h b/extensions/shell/browser/api/identity/identity_api.h index 38bf177..a3343ad 100644 --- a/extensions/shell/browser/api/identity/identity_api.h +++ b/extensions/shell/browser/api/identity/identity_api.h @@ -5,22 +5,53 @@ #ifndef EXTENSIONS_SHELL_BROWSER_API_IDENTITY_IDENTITY_API_H_ #define EXTENSIONS_SHELL_BROWSER_API_IDENTITY_IDENTITY_API_H_ +#include <string> + #include "base/macros.h" +#include "extensions/browser/browser_context_keyed_api_factory.h" #include "extensions/browser/extension_function.h" +#include "google_apis/gaia/oauth2_mint_token_flow.h" #include "google_apis/gaia/oauth2_token_service.h" namespace extensions { namespace shell { +// Storage for data used across identity function invocations. +class IdentityAPI : public BrowserContextKeyedAPI { + public: + explicit IdentityAPI(content::BrowserContext* context); + ~IdentityAPI() override; + + static IdentityAPI* Get(content::BrowserContext* context); + + const std::string& device_id() const { return device_id_; } + + // BrowserContextKeyedAPI: + static BrowserContextKeyedAPIFactory<IdentityAPI>* GetFactoryInstance(); + static const char* service_name() { return "IdentityAPI"; } + + private: + friend class BrowserContextKeyedAPIFactory<IdentityAPI>; + + // A GUID identifying this device. + // TODO(jamescook): Make this GUID stable across runs of the app, perhaps by + // storing it in a pref. + const std::string device_id_; +}; + // Returns an OAuth2 access token for a user. See the IDL file for // documentation. class IdentityGetAuthTokenFunction : public UIThreadExtensionFunction, - public OAuth2TokenService::Consumer { + public OAuth2TokenService::Consumer, + public OAuth2MintTokenFlow::Delegate { public: DECLARE_EXTENSION_FUNCTION("identity.getAuthToken", UNKNOWN); IdentityGetAuthTokenFunction(); + // Takes ownership. + void SetMintTokenFlowForTesting(OAuth2MintTokenFlow* flow); + protected: ~IdentityGetAuthTokenFunction() override; @@ -34,10 +65,20 @@ class IdentityGetAuthTokenFunction : public UIThreadExtensionFunction, void OnGetTokenFailure(const OAuth2TokenService::Request* request, const GoogleServiceAuthError& error) override; + // OAuth2MintTokenFlow::Delegate: + void OnMintTokenSuccess(const std::string& access_token, + int time_to_live) override; + void OnIssueAdviceSuccess(const IssueAdviceInfo& issue_advice) override; + void OnMintTokenFailure(const GoogleServiceAuthError& error) override; + private: - // A pending token fetch request. + // A pending token fetch request to get a login-scoped access token for the + // current user for the Chrome project id. scoped_ptr<OAuth2TokenService::Request> access_token_request_; + // A request for an access token for the current app and its scopes. + scoped_ptr<OAuth2MintTokenFlow> mint_token_flow_; + DISALLOW_COPY_AND_ASSIGN(IdentityGetAuthTokenFunction); }; diff --git a/extensions/shell/browser/api/identity/identity_api_unittest.cc b/extensions/shell/browser/api/identity/identity_api_unittest.cc index 6ed13f9..03e6f71 100644 --- a/extensions/shell/browser/api/identity/identity_api_unittest.cc +++ b/extensions/shell/browser/api/identity/identity_api_unittest.cc @@ -12,6 +12,7 @@ #include "extensions/browser/api_unittest.h" #include "extensions/common/extension_builder.h" #include "extensions/shell/browser/shell_oauth2_token_service.h" +#include "google_apis/gaia/oauth2_mint_token_flow.h" namespace extensions { namespace shell { @@ -28,11 +29,30 @@ class MockShellOAuth2TokenService : public ShellOAuth2TokenService { const ScopeSet& scopes, Consumer* consumer) override { // Immediately return success. - consumer->OnGetTokenSuccess(nullptr, "token123", base::Time()); + consumer->OnGetTokenSuccess(nullptr, "logged-in-user-token", base::Time()); return nullptr; } }; +// A mint token flow that immediately returns a known access token when started. +class MockOAuth2MintTokenFlow : public OAuth2MintTokenFlow { + public: + explicit MockOAuth2MintTokenFlow(Delegate* delegate) + : OAuth2MintTokenFlow(delegate, Parameters()), delegate_(delegate) {} + ~MockOAuth2MintTokenFlow() override {} + + // OAuth2ApiCallFlow: + void Start(net::URLRequestContextGetter* context, + const std::string& access_token) override { + EXPECT_EQ("logged-in-user-token", access_token); + delegate_->OnMintTokenSuccess("app-access-token", 12345); + } + + private: + // Cached here so OAuth2MintTokenFlow does not have to expose its delegate. + Delegate* delegate_; +}; + class IdentityApiTest : public ApiUnitTest { public: IdentityApiTest() {} @@ -64,24 +84,33 @@ class IdentityApiTest : public ApiUnitTest { // Verifies that the getAuthToken function exists and can be called without // crashing. -TEST_F(IdentityApiTest, GetAuthToken) { +TEST_F(IdentityApiTest, GetAuthTokenNoRefreshToken) { MockShellOAuth2TokenService token_service; // Calling getAuthToken() before a refresh token is available causes an error. std::string error = RunFunctionAndReturnError(new IdentityGetAuthTokenFunction, "[{}]"); EXPECT_FALSE(error.empty()); +} + +// Verifies that getAuthToken() returns an app access token. +TEST_F(IdentityApiTest, GetAuthToken) { + MockShellOAuth2TokenService token_service; // Simulate a refresh token being set. - token_service.SetRefreshToken("larry@google.com", "token123"); + token_service.SetRefreshToken("larry@google.com", "refresh-token"); + + // RunFunctionAndReturnValue takes ownership. + IdentityGetAuthTokenFunction* function = new IdentityGetAuthTokenFunction; + function->SetMintTokenFlowForTesting(new MockOAuth2MintTokenFlow(function)); // Function succeeds and returns a token (for its callback). - scoped_ptr<base::Value> result = - RunFunctionAndReturnValue(new IdentityGetAuthTokenFunction, "[{}]"); + scoped_ptr<base::Value> result = RunFunctionAndReturnValue(function, "[{}]"); ASSERT_TRUE(result.get()); std::string value; result->GetAsString(&value); - EXPECT_EQ("token123", value); + EXPECT_NE("logged-in-user-token", value); + EXPECT_EQ("app-access-token", value); } // Verifies that the removeCachedAuthToken function exists and can be called diff --git a/extensions/shell/browser/shell_browser_context_keyed_service_factories.cc b/extensions/shell/browser/shell_browser_context_keyed_service_factories.cc index f2d0bfe..9d3767f 100644 --- a/extensions/shell/browser/shell_browser_context_keyed_service_factories.cc +++ b/extensions/shell/browser/shell_browser_context_keyed_service_factories.cc @@ -5,11 +5,14 @@ #include "extensions/shell/browser/shell_browser_context_keyed_service_factories.h" #include "extensions/browser/updater/update_service_factory.h" +#include "extensions/shell/browser/api/identity/identity_api.h" namespace extensions { namespace shell { void EnsureBrowserContextKeyedServiceFactoriesBuilt() { + IdentityAPI::GetFactoryInstance(); + // TODO(rockot): Remove this once UpdateService is supported across all // extensions embedders (and namely chrome.) UpdateServiceFactory::GetInstance(); |