summaryrefslogtreecommitdiffstats
path: root/extensions
diff options
context:
space:
mode:
authorjamescook <jamescook@chromium.org>2015-01-16 13:28:07 -0800
committerCommit bot <commit-bot@chromium.org>2015-01-16 21:28:53 +0000
commita03f13e31d30a93a8bb66ff21b36b1e16e353a4f (patch)
tree0dd0651a5006e45474dca6c118b8f6d1315216a3 /extensions
parent3095e631aa3b50c85caf5625d8b0f966ac73f1bf (diff)
downloadchromium_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')
-rw-r--r--extensions/shell/browser/api/identity/identity_api.cc82
-rw-r--r--extensions/shell/browser/api/identity/identity_api.h45
-rw-r--r--extensions/shell/browser/api/identity/identity_api_unittest.cc41
-rw-r--r--extensions/shell/browser/shell_browser_context_keyed_service_factories.cc3
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();