Add an experimental identity API for platform apps.
This adds a way for platform apps to access the user's signed into chrome account and generate an OAuth2 token:
- chrome.experimental.identity.getAuthToken
BUG=none
TEST=ExtensionApiTest.Identity
Review URL: http://codereview.chromium.org/9474005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@128591 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/common/common_resources.grd b/chrome/common/common_resources.grd
index 1c829ff7..b1e17d57 100644
--- a/chrome/common/common_resources.grd
+++ b/chrome/common/common_resources.grd
@@ -31,6 +31,7 @@
<include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_DOWNLOADS" file="extensions\api\experimental.downloads.json" type="BINDATA" />
<include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_EXTENSION" file="extensions\api\experimental.extension.json" type="BINDATA" />
<include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_FONTSSETTINGS" file="extensions\api\experimental.fontSettings.json" type="BINDATA" />
+ <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_IDENTITY" file="extensions\api\experimental.identity.json" type="BINDATA" />
<include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_INFOBARS" file="extensions\api\experimental.infobars.json" type="BINDATA" />
<include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_INPUT_UI" file="extensions\api\experimental.input.ui.json" type="BINDATA" />
<include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_INPUT_VIRTUALKEYBOARD" file="extensions\api\experimental.input.virtualKeyboard.json" type="BINDATA" />
diff --git a/chrome/common/extensions/api/_manifest_features.json b/chrome/common/extensions/api/_manifest_features.json
index bad73fd8..9823c27 100644
--- a/chrome/common/extensions/api/_manifest_features.json
+++ b/chrome/common/extensions/api/_manifest_features.json
@@ -107,6 +107,9 @@
"extension", "packaged_app", "hosted_app", "platform_app"
]
},
+ "oauth2": {
+ "extension_types": ["platform_app"]
+ },
"page_action": {
"extension_types": ["extension"]
},
diff --git a/chrome/common/extensions/api/experimental.identity.json b/chrome/common/extensions/api/experimental.identity.json
new file mode 100644
index 0000000..14fb881
--- /dev/null
+++ b/chrome/common/extensions/api/experimental.identity.json
@@ -0,0 +1,28 @@
+[
+ {
+ "namespace": "experimental.identity",
+ "nodoc": true,
+ "types": [],
+ "events": [],
+ "functions": [
+ {
+ "name": "getAuthToken",
+ "type": "function",
+ "description": "Gets an OAuth2 access token as specified by the manifest.",
+ "parameters": [
+ {
+ "name": "callback",
+ "type": "function",
+ "parameters": [
+ {
+ "name": "token",
+ "type": "string",
+ "description": "OAuth2 access token as specified by the manifest."
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/chrome/common/extensions/api/extension_api.cc b/chrome/common/extensions/api/extension_api.cc
index 8571417..22f6793 100644
--- a/chrome/common/extensions/api/extension_api.cc
+++ b/chrome/common/extensions/api/extension_api.cc
@@ -158,6 +158,8 @@
IDR_EXTENSION_API_JSON_EXPERIMENTAL_EXTENSION);
unloaded_schemas_["experimental.fontSettings"] = ReadFromResource(
IDR_EXTENSION_API_JSON_EXPERIMENTAL_FONTSSETTINGS);
+ unloaded_schemas_["experimental.identity"] = ReadFromResource(
+ IDR_EXTENSION_API_JSON_EXPERIMENTAL_IDENTITY);
unloaded_schemas_["experimental.infobars"] = ReadFromResource(
IDR_EXTENSION_API_JSON_EXPERIMENTAL_INFOBARS);
unloaded_schemas_["experimental.input.ui"] = ReadFromResource(
diff --git a/chrome/common/extensions/docs/js/api_page_generator.js b/chrome/common/extensions/docs/js/api_page_generator.js
index 44708af1..8c8322f5 100644
--- a/chrome/common/extensions/docs/js/api_page_generator.js
+++ b/chrome/common/extensions/docs/js/api_page_generator.js
@@ -37,6 +37,7 @@
'../api/experimental.downloads.json',
'../api/experimental.extension.json',
'../api/experimental.fontSettings.json',
+ '../api/experimental.identity.json',
'../api/experimental.infobars.json',
'../api/experimental.input.ui.json',
'../api/experimental.input.virtualKeyboard.json',
diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc
index 484b731d..549566d 100644
--- a/chrome/common/extensions/extension.cc
+++ b/chrome/common/extensions/extension.cc
@@ -238,6 +238,9 @@
Extension::TtsVoice::TtsVoice() {}
Extension::TtsVoice::~TtsVoice() {}
+Extension::OAuth2Info::OAuth2Info() {}
+Extension::OAuth2Info::~OAuth2Info() {}
+
Extension::ExtensionKeybinding::ExtensionKeybinding() {}
Extension::ExtensionKeybinding::~ExtensionKeybinding() {}
@@ -1029,12 +1032,41 @@
if (!LoadExtent(keys::kWebURLs, &extent_,
errors::kInvalidWebURLs, errors::kInvalidWebURL, error) ||
!LoadLaunchURL(error) ||
- !LoadLaunchContainer(error))
+ !LoadLaunchContainer(error) ||
+ !LoadOAuth2Info(error))
return false;
return true;
}
+bool Extension::LoadOAuth2Info(string16* error) {
+ if (!manifest_->HasKey(keys::kOAuth2))
+ return true;
+
+ if (!manifest_->GetString(keys::kOAuth2ClientId, &oauth2_info_.client_id) ||
+ oauth2_info_.client_id.empty()) {
+ *error = ASCIIToUTF16(errors::kInvalidOAuth2ClientId);
+ return false;
+ }
+
+ ListValue* list = NULL;
+ if (!manifest_->GetList(keys::kOAuth2Scopes, &list)) {
+ *error = ASCIIToUTF16(errors::kInvalidOAuth2Scopes);
+ return false;
+ }
+
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ std::string scope;
+ if (!list->GetString(i, &scope)) {
+ *error = ASCIIToUTF16(errors::kInvalidOAuth2Scopes);
+ return false;
+ }
+ oauth2_info_.scopes.push_back(scope);
+ }
+
+ return true;
+}
+
bool Extension::LoadExtent(const char* key,
URLPatternSet* extent,
const char* list_error,
diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h
index 039a446..cf6a3fd 100644
--- a/chrome/common/extensions/extension.h
+++ b/chrome/common/extensions/extension.h
@@ -183,6 +183,15 @@
std::set<std::string> event_types;
};
+ // OAuth2 info included in the extension.
+ struct OAuth2Info {
+ OAuth2Info();
+ ~OAuth2Info();
+
+ std::string client_id;
+ std::vector<std::string> scopes;
+ };
+
enum InitFromValueFlags {
NO_FLAGS = 0,
@@ -591,6 +600,7 @@
bool incognito_split_mode() const { return incognito_split_mode_; }
bool offline_enabled() const { return offline_enabled_; }
const std::vector<TtsVoice>& tts_voices() const { return tts_voices_; }
+ const OAuth2Info& oauth2_info() const { return oauth2_info_; }
const std::vector<webkit_glue::WebIntentServiceData>&
intents_services() const {
return intents_services_;
@@ -802,6 +812,9 @@
ExtensionAction* LoadExtensionActionHelper(
const base::DictionaryValue* extension_action, string16* error);
+ // Helper method that loads the OAuth2 info from the 'oauth2' manifest key.
+ bool LoadOAuth2Info(string16* error);
+
// Returns true if the extension has more than one "UI surface". For example,
// an extension that has a browser action and a page action.
bool HasMultipleUISurfaces() const;
@@ -1002,6 +1015,9 @@
// List of text-to-speech voices that this extension provides, if any.
std::vector<TtsVoice> tts_voices_;
+ // The OAuth2 client id and scopes, if specified by the extension.
+ OAuth2Info oauth2_info_;
+
// List of intent services that this extension provides, if any.
std::vector<webkit_glue::WebIntentServiceData> intents_services_;
diff --git a/chrome/common/extensions/extension_manifest_constants.cc b/chrome/common/extensions/extension_manifest_constants.cc
index 24691f7..d1fa89e 100644
--- a/chrome/common/extensions/extension_manifest_constants.cc
+++ b/chrome/common/extensions/extension_manifest_constants.cc
@@ -67,6 +67,9 @@
const char kNaClModules[] = "nacl_modules";
const char kNaClModulesMIMEType[] = "mime_type";
const char kNaClModulesPath[] = "path";
+const char kOAuth2[] = "oauth2";
+const char kOAuth2ClientId[] = "oauth2.client_id";
+const char kOAuth2Scopes[] = "oauth2.scopes";
const char kOfflineEnabled[] = "offline_enabled";
const char kOmnibox[] = "omnibox";
const char kOmniboxKeyword[] = "omnibox.keyword";
@@ -324,6 +327,10 @@
"Invalid value for 'nacl_modules[*].path'.";
const char kInvalidNaClModulesMIMEType[] =
"Invalid value for 'nacl_modules[*].mime_type'.";
+const char kInvalidOAuth2ClientId[] =
+ "Invalid value for 'oauth2.client_id'.";
+const char kInvalidOAuth2Scopes[] =
+ "Invalid value for 'oauth2.scopes'.";
const char kInvalidOfflineEnabled[] =
"Invalid value for 'offline_enabled'.";
const char kInvalidOmniboxKeyword[] =
@@ -455,4 +462,3 @@
"Extensions cannot install plugins on Chrome OS";
#endif
} // namespace extension_manifest_errors
-
diff --git a/chrome/common/extensions/extension_manifest_constants.h b/chrome/common/extensions/extension_manifest_constants.h
index f68c0e9c..8392858 100644
--- a/chrome/common/extensions/extension_manifest_constants.h
+++ b/chrome/common/extensions/extension_manifest_constants.h
@@ -75,6 +75,9 @@
extern const char kNaClModulesMIMEType[];
extern const char kNaClModulesPath[];
extern const char kName[];
+ extern const char kOAuth2[];
+ extern const char kOAuth2ClientId[];
+ extern const char kOAuth2Scopes[];
extern const char kOfflineEnabled[];
extern const char kOmnibox[];
extern const char kOmniboxKeyword[];
@@ -237,6 +240,8 @@
extern const char kInvalidNaClModulesMIMEType[];
extern const char kInvalidNaClModulesPath[];
extern const char kInvalidName[];
+ extern const char kInvalidOAuth2ClientId[];
+ extern const char kInvalidOAuth2Scopes[];
extern const char kInvalidOfflineEnabled[];
extern const char kInvalidOmniboxKeyword[];
extern const char kInvalidOptionsPage[];
diff --git a/chrome/common/extensions/extension_permission_set.cc b/chrome/common/extensions/extension_permission_set.cc
index 0c90e477..c2cd6c3 100644
--- a/chrome/common/extensions/extension_permission_set.cc
+++ b/chrome/common/extensions/extension_permission_set.cc
@@ -198,7 +198,6 @@
kUnlimitedStorage, "unlimitedStorage", 0,
ExtensionPermissionMessage::kNone, kFlagCannotBeOptional);
-
// Register hosted and packaged app permissions.
info->RegisterPermission(
kAppNotifications, "appNotifications", 0,
diff --git a/chrome/common/net/gaia/google_service_auth_error.cc b/chrome/common/net/gaia/google_service_auth_error.cc
index 78595eb..20f3d1a6 100644
--- a/chrome/common/net/gaia/google_service_auth_error.cc
+++ b/chrome/common/net/gaia/google_service_auth_error.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// 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.
@@ -7,6 +7,7 @@
#include <string>
#include "base/logging.h"
+#include "base/stringprintf.h"
#include "base/values.h"
#include "net/base/net_errors.h"
@@ -96,6 +97,37 @@
return value;
}
+std::string GoogleServiceAuthError::ToString() const {
+ switch (state_) {
+ case NONE:
+ return "";
+ case INVALID_GAIA_CREDENTIALS:
+ return "Invalid credentials.";
+ case USER_NOT_SIGNED_UP:
+ return "Not authorized.";
+ case CONNECTION_FAILED:
+ return base::StringPrintf("Connection failed (%d).", network_error_);
+ case CAPTCHA_REQUIRED:
+ return base::StringPrintf("CAPTCHA required (%s).",
+ captcha_.token.c_str());
+ case ACCOUNT_DELETED:
+ return "Account deleted.";
+ case ACCOUNT_DISABLED:
+ return "Account disabled.";
+ case SERVICE_UNAVAILABLE:
+ return "Service unavailable; try again later.";
+ case TWO_FACTOR:
+ return "2-step verification required.";
+ case REQUEST_CANCELED:
+ return "Request canceled.";
+ case HOSTED_NOT_ALLOWED:
+ return "Google account required.";
+ default:
+ NOTREACHED();
+ return std::string();
+ }
+}
+
GoogleServiceAuthError::GoogleServiceAuthError(State s, int error)
: state_(s),
captcha_("", GURL(), GURL()),
diff --git a/chrome/common/net/gaia/google_service_auth_error.h b/chrome/common/net/gaia/google_service_auth_error.h
index 74cc11d6..da67bf4 100644
--- a/chrome/common/net/gaia/google_service_auth_error.h
+++ b/chrome/common/net/gaia/google_service_auth_error.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// 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.
@@ -118,6 +118,9 @@
// ownership of returned dictionary.
base::DictionaryValue* ToValue() const;
+ // Returns a message describing the error.
+ std::string ToString() const;
+
private:
GoogleServiceAuthError(State s, int error);
diff --git a/chrome/common/net/gaia/oauth2_mint_token_flow.cc b/chrome/common/net/gaia/oauth2_mint_token_flow.cc
index 4e7029db..feef388 100644
--- a/chrome/common/net/gaia/oauth2_mint_token_flow.cc
+++ b/chrome/common/net/gaia/oauth2_mint_token_flow.cc
@@ -8,10 +8,29 @@
#include <vector>
#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/message_loop.h"
+#include "chrome/common/chrome_switches.h"
#include "chrome/common/net/gaia/gaia_urls.h"
+#include "chrome/common/net/gaia/google_service_auth_error.h"
using net::URLRequestContextGetter;
+namespace {
+
+OAuth2MintTokenFlow::InterceptorForTests* g_interceptor_for_tests = NULL;
+
+} // namespace
+
+// static
+void OAuth2MintTokenFlow::SetInterceptorForTests(
+ OAuth2MintTokenFlow::InterceptorForTests* interceptor) {
+ CHECK(CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType));
+ CHECK(NULL == g_interceptor_for_tests); // Only one at a time.
+ g_interceptor_for_tests = interceptor;
+}
+
OAuth2MintTokenFlow::OAuth2MintTokenFlow(
URLRequestContextGetter* context,
Delegate* delegate)
@@ -32,6 +51,27 @@
client_id_ = client_id;
scopes_ = scopes;
+ if (g_interceptor_for_tests) {
+ std::string auth_token;
+ GoogleServiceAuthError error = GoogleServiceAuthError::None();
+
+ // We use PostTask, instead of calling the delegate directly, because the
+ // message loop will run a few times before we notify the delegate in the
+ // real implementation.
+ if (g_interceptor_for_tests->DoIntercept(this, &auth_token, &error)) {
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&OAuth2MintTokenFlow::Delegate::OnMintTokenSuccess,
+ base::Unretained(delegate_), auth_token));
+ } else {
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&OAuth2MintTokenFlow::Delegate::OnMintTokenFailure,
+ base::Unretained(delegate_), error));
+ }
+ return;
+ }
+
BeginGetLoginAccessToken();
}
@@ -40,6 +80,7 @@
login_access_token_ = access_token;
EndGetLoginAccessToken(NULL);
}
+
void OAuth2MintTokenFlow::OnGetTokenFailure(
const GoogleServiceAuthError& error) {
EndGetLoginAccessToken(&error);
diff --git a/chrome/common/net/gaia/oauth2_mint_token_flow.h b/chrome/common/net/gaia/oauth2_mint_token_flow.h
index 31d1958..ee06145 100644
--- a/chrome/common/net/gaia/oauth2_mint_token_flow.h
+++ b/chrome/common/net/gaia/oauth2_mint_token_flow.h
@@ -35,6 +35,17 @@
virtual void OnMintTokenFailure(const GoogleServiceAuthError& error) { }
};
+ // An interceptor for tests.
+ class InterceptorForTests {
+ public:
+ // Returns true if the success callback should be called and false for
+ // failures.
+ virtual bool DoIntercept(const OAuth2MintTokenFlow* flow,
+ std::string* access_token,
+ GoogleServiceAuthError* error) = 0;
+ };
+ static void SetInterceptorForTests(InterceptorForTests* interceptor);
+
OAuth2MintTokenFlow(net::URLRequestContextGetter* context,
Delegate* delegate);
virtual ~OAuth2MintTokenFlow();
@@ -57,11 +68,11 @@
const std::string& client_id() const { return client_id_; }
protected:
- // Helper to create an instnace of access token fetcher.
+ // Helper to create an instance of access token fetcher.
// Caller owns the returned instance.
virtual OAuth2AccessTokenFetcher* CreateAccessTokenFetcher();
- // Helper to create an instnace of mint token fetcher.
+ // Helper to create an instance of mint token fetcher.
// Caller owns the returned instance.
virtual OAuth2MintTokenFetcher* CreateMintTokenFetcher();