summaryrefslogtreecommitdiffstats
path: root/extensions/common/manifest_handlers
diff options
context:
space:
mode:
authorjamescook <jamescook@chromium.org>2014-10-20 16:47:15 -0700
committerCommit bot <commit-bot@chromium.org>2014-10-20 23:47:34 +0000
commit71187e343e2a8005dff92649b3013ba5019f974a (patch)
tree42a3761b483d5161130a7707e1748a50d3309055 /extensions/common/manifest_handlers
parent9db73d2637e0c8c258a42509e7a2b913775f5f14 (diff)
downloadchromium_src-71187e343e2a8005dff92649b3013ba5019f974a.zip
chromium_src-71187e343e2a8005dff92649b3013ba5019f974a.tar.gz
chromium_src-71187e343e2a8005dff92649b3013ba5019f974a.tar.bz2
Move extensions manifest handler for OAuth2 to //extensions
app_shell needs the client ID and oauth2 scopes for its identity API. Just a refactor, no functional changes. BUG=424653 TEST=extensions_unittests TBR=xiyuan@chromium.org for header file rename touching chrome/browser/signin/easy_unlock_toggle_flow.cc Review URL: https://codereview.chromium.org/671553002 Cr-Commit-Position: refs/heads/master@{#300380}
Diffstat (limited to 'extensions/common/manifest_handlers')
-rw-r--r--extensions/common/manifest_handlers/oauth2_manifest_handler.cc98
-rw-r--r--extensions/common/manifest_handlers/oauth2_manifest_handler.h48
-rw-r--r--extensions/common/manifest_handlers/oauth2_manifest_unittest.cc320
3 files changed, 466 insertions, 0 deletions
diff --git a/extensions/common/manifest_handlers/oauth2_manifest_handler.cc b/extensions/common/manifest_handlers/oauth2_manifest_handler.cc
new file mode 100644
index 0000000..1d32ecb
--- /dev/null
+++ b/extensions/common/manifest_handlers/oauth2_manifest_handler.cc
@@ -0,0 +1,98 @@
+// Copyright (c) 2013 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 "extensions/common/manifest_handlers/oauth2_manifest_handler.h"
+
+#include "base/lazy_instance.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace {
+
+// Manifest keys.
+const char kClientId[] = "client_id";
+const char kScopes[] = "scopes";
+const char kAutoApprove[] = "auto_approve";
+
+} // namespace
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+namespace errors = manifest_errors;
+
+OAuth2Info::OAuth2Info() : auto_approve(false) {}
+OAuth2Info::~OAuth2Info() {}
+
+static base::LazyInstance<OAuth2Info> g_empty_oauth2_info =
+ LAZY_INSTANCE_INITIALIZER;
+
+// static
+const OAuth2Info& OAuth2Info::GetOAuth2Info(const Extension* extension) {
+ OAuth2Info* info = static_cast<OAuth2Info*>(
+ extension->GetManifestData(keys::kOAuth2));
+ return info ? *info : g_empty_oauth2_info.Get();
+}
+
+OAuth2ManifestHandler::OAuth2ManifestHandler() {
+}
+
+OAuth2ManifestHandler::~OAuth2ManifestHandler() {
+}
+
+bool OAuth2ManifestHandler::Parse(Extension* extension,
+ base::string16* error) {
+ scoped_ptr<OAuth2Info> info(new OAuth2Info);
+ const base::DictionaryValue* dict = NULL;
+ if (!extension->manifest()->GetDictionary(keys::kOAuth2, &dict)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidOAuth2ClientId);
+ return false;
+ }
+
+ // HasPath checks for whether the manifest is allowed to have
+ // oauth2.auto_approve based on whitelist, and if it is present.
+ // GetBoolean reads the value of auto_approve directly from dict to prevent
+ // duplicate checking.
+ if (extension->manifest()->HasPath(keys::kOAuth2AutoApprove) &&
+ !dict->GetBoolean(kAutoApprove, &info->auto_approve)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidOAuth2AutoApprove);
+ return false;
+ }
+
+ // Component apps using auto_approve may use Chrome's client ID by
+ // omitting the field.
+ if ((!dict->GetString(kClientId, &info->client_id) ||
+ info->client_id.empty()) &&
+ (extension->location() != Manifest::COMPONENT || !info->auto_approve)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidOAuth2ClientId);
+ return false;
+ }
+
+ const base::ListValue* list = NULL;
+ if (!dict->GetList(kScopes, &list)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidOAuth2Scopes);
+ return false;
+ }
+
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ std::string scope;
+ if (!list->GetString(i, &scope)) {
+ *error = base::ASCIIToUTF16(errors::kInvalidOAuth2Scopes);
+ return false;
+ }
+ info->scopes.push_back(scope);
+ }
+
+ extension->SetManifestData(keys::kOAuth2, info.release());
+ return true;
+}
+
+const std::vector<std::string> OAuth2ManifestHandler::Keys() const {
+ return SingleKey(keys::kOAuth2);
+}
+
+} // namespace extensions
diff --git a/extensions/common/manifest_handlers/oauth2_manifest_handler.h b/extensions/common/manifest_handlers/oauth2_manifest_handler.h
new file mode 100644
index 0000000..8939e88
--- /dev/null
+++ b/extensions/common/manifest_handlers/oauth2_manifest_handler.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2013 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.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_OAUTH2_MANIFEST_HANDLER_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_OAUTH2_MANIFEST_HANDLER_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+
+// OAuth2 info included in the extension.
+struct OAuth2Info : public Extension::ManifestData {
+ OAuth2Info();
+ virtual ~OAuth2Info();
+
+ std::string client_id;
+ std::vector<std::string> scopes;
+
+ // Indicates that approval UI can be skipped for a set of whitelisted apps.
+ bool auto_approve;
+
+ static const OAuth2Info& GetOAuth2Info(const Extension* extension);
+};
+
+// Parses the "oauth2" manifest key.
+class OAuth2ManifestHandler : public ManifestHandler {
+ public:
+ OAuth2ManifestHandler();
+ virtual ~OAuth2ManifestHandler();
+
+ virtual bool Parse(Extension* extension,
+ base::string16* error) override;
+
+ private:
+ virtual const std::vector<std::string> Keys() const override;
+
+ DISALLOW_COPY_AND_ASSIGN(OAuth2ManifestHandler);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_MANIFEST_HANDLERS_OAUTH2_MANIFEST_HANDLER_H_
diff --git a/extensions/common/manifest_handlers/oauth2_manifest_unittest.cc b/extensions/common/manifest_handlers/oauth2_manifest_unittest.cc
new file mode 100644
index 0000000..bd22eb5
--- /dev/null
+++ b/extensions/common/manifest_handlers/oauth2_manifest_unittest.cc
@@ -0,0 +1,320 @@
+// 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 "base/test/values_test_util.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/oauth2_manifest_handler.h"
+#include "extensions/common/manifest_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+namespace errors = manifest_errors;
+
+namespace {
+
+// Produces extension ID = "mdbihdcgjmagbcapkhhkjbbdlkflmbfo".
+const char kExtensionKey[] =
+ "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCV9PlZjcTIXfnlB3HXo50OlM/CnIq0y7jm"
+ "KfPVyStaWsmFB7NaVnqUXoGb9swBDfVnZ6BrupwnxL76TWEJPo+KQMJ6uz0PPdJWi2jQfZiG"
+ "iheDiKH5Gv+dVd67qf7ly8QWW0o8qmFpqBZQpksm1hOGbfsupv9W4c42tMEIicDMLQIDAQAB";
+const char kAutoApproveNotAllowedWarning[] =
+ "'oauth2.auto_approve' is not allowed for specified extension ID.";
+
+} // namespace
+
+class OAuth2ManifestTest : public ManifestTest {
+ protected:
+ enum AutoApproveValue {
+ AUTO_APPROVE_NOT_SET,
+ AUTO_APPROVE_FALSE,
+ AUTO_APPROVE_TRUE,
+ AUTO_APPROVE_INVALID
+ };
+
+ enum ClientIdValue {
+ CLIENT_ID_DEFAULT,
+ CLIENT_ID_NOT_SET,
+ CLIENT_ID_EMPTY
+ };
+
+ base::DictionaryValue* CreateManifest(
+ AutoApproveValue auto_approve,
+ bool extension_id_whitelisted,
+ ClientIdValue client_id) {
+ parsed_manifest_.reset(base::test::ParseJson(
+ "{ \n"
+ " \"name\": \"test\", \n"
+ " \"version\": \"0.1\", \n"
+ " \"manifest_version\": 2, \n"
+ " \"oauth2\": { \n"
+ " \"scopes\": [ \"scope1\" ], \n"
+ " }, \n"
+ "} \n").release());
+ base::DictionaryValue* ext_manifest;
+ EXPECT_TRUE(parsed_manifest_->GetAsDictionary(&ext_manifest));
+ switch (auto_approve) {
+ case AUTO_APPROVE_NOT_SET:
+ break;
+ case AUTO_APPROVE_FALSE:
+ ext_manifest->SetBoolean(keys::kOAuth2AutoApprove, false);
+ break;
+ case AUTO_APPROVE_TRUE:
+ ext_manifest->SetBoolean(keys::kOAuth2AutoApprove, true);
+ break;
+ case AUTO_APPROVE_INVALID:
+ ext_manifest->SetString(keys::kOAuth2AutoApprove, "incorrect value");
+ break;
+ }
+ switch (client_id) {
+ case CLIENT_ID_DEFAULT:
+ ext_manifest->SetString(keys::kOAuth2ClientId, "client1");
+ break;
+ case CLIENT_ID_NOT_SET:
+ break;
+ case CLIENT_ID_EMPTY:
+ ext_manifest->SetString(keys::kOAuth2ClientId, "");
+ }
+ if (extension_id_whitelisted)
+ ext_manifest->SetString(keys::kKey, kExtensionKey);
+ return ext_manifest;
+ }
+
+ private:
+ scoped_ptr<base::Value> parsed_manifest_;
+};
+
+TEST_F(OAuth2ManifestTest, OAuth2SectionParsing) {
+ base::DictionaryValue base_manifest;
+
+ base_manifest.SetString(keys::kName, "test");
+ base_manifest.SetString(keys::kVersion, "0.1");
+ base_manifest.SetInteger(keys::kManifestVersion, 2);
+ base_manifest.SetString(keys::kOAuth2ClientId, "client1");
+ base::ListValue* scopes = new base::ListValue();
+ scopes->Append(new base::StringValue("scope1"));
+ scopes->Append(new base::StringValue("scope2"));
+ base_manifest.Set(keys::kOAuth2Scopes, scopes);
+
+ // OAuth2 section should be parsed for an extension.
+ {
+ base::DictionaryValue ext_manifest;
+ // Lack of "app" section representa an extension. So the base manifest
+ // itself represents an extension.
+ ext_manifest.MergeDictionary(&base_manifest);
+ ext_manifest.SetString(keys::kKey, kExtensionKey);
+ ext_manifest.SetBoolean(keys::kOAuth2AutoApprove, true);
+
+ ManifestData manifest(&ext_manifest, "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest);
+ EXPECT_TRUE(extension->install_warnings().empty());
+ EXPECT_EQ("client1", OAuth2Info::GetOAuth2Info(extension.get()).client_id);
+ EXPECT_EQ(2U, OAuth2Info::GetOAuth2Info(extension.get()).scopes.size());
+ EXPECT_EQ("scope1", OAuth2Info::GetOAuth2Info(extension.get()).scopes[0]);
+ EXPECT_EQ("scope2", OAuth2Info::GetOAuth2Info(extension.get()).scopes[1]);
+ EXPECT_TRUE(OAuth2Info::GetOAuth2Info(extension.get()).auto_approve);
+ }
+
+ // OAuth2 section should be parsed for a packaged app.
+ {
+ base::DictionaryValue app_manifest;
+ app_manifest.SetString(keys::kLaunchLocalPath, "launch.html");
+ app_manifest.MergeDictionary(&base_manifest);
+
+ ManifestData manifest(&app_manifest, "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest);
+ EXPECT_TRUE(extension->install_warnings().empty());
+ EXPECT_EQ("client1", OAuth2Info::GetOAuth2Info(extension.get()).client_id);
+ EXPECT_EQ(2U, OAuth2Info::GetOAuth2Info(extension.get()).scopes.size());
+ EXPECT_EQ("scope1", OAuth2Info::GetOAuth2Info(extension.get()).scopes[0]);
+ EXPECT_EQ("scope2", OAuth2Info::GetOAuth2Info(extension.get()).scopes[1]);
+ EXPECT_FALSE(OAuth2Info::GetOAuth2Info(extension.get()).auto_approve);
+ }
+
+ // OAuth2 section should NOT be parsed for a hosted app.
+ {
+ base::DictionaryValue app_manifest;
+ app_manifest.SetString(keys::kLaunchWebURL, "http://www.google.com");
+ app_manifest.MergeDictionary(&base_manifest);
+
+ ManifestData manifest(&app_manifest, "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest);
+ EXPECT_EQ(1U, extension->install_warnings().size());
+ const extensions::InstallWarning& warning =
+ extension->install_warnings()[0];
+ EXPECT_EQ("'oauth2' is only allowed for extensions, legacy packaged apps, "
+ "and packaged apps, but this is a hosted app.",
+ warning.message);
+ EXPECT_EQ("", OAuth2Info::GetOAuth2Info(extension.get()).client_id);
+ EXPECT_TRUE(OAuth2Info::GetOAuth2Info(extension.get()).scopes.empty());
+ EXPECT_FALSE(OAuth2Info::GetOAuth2Info(extension.get()).auto_approve);
+ }
+}
+
+TEST_F(OAuth2ManifestTest, AutoApproveNotSetExtensionNotOnWhitelist) {
+ base::DictionaryValue* ext_manifest =
+ CreateManifest(AUTO_APPROVE_NOT_SET, false, CLIENT_ID_DEFAULT);
+ ManifestData manifest(ext_manifest, "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest);
+ EXPECT_TRUE(extension->install_warnings().empty());
+ EXPECT_FALSE(OAuth2Info::GetOAuth2Info(extension.get()).auto_approve);
+}
+
+TEST_F(OAuth2ManifestTest, AutoApproveFalseExtensionNotOnWhitelist) {
+ base::DictionaryValue* ext_manifest =
+ CreateManifest(AUTO_APPROVE_FALSE, false, CLIENT_ID_DEFAULT);
+ ManifestData manifest(ext_manifest, "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest);
+ EXPECT_EQ(1U, extension->install_warnings().size());
+ const extensions::InstallWarning& warning =
+ extension->install_warnings()[0];
+ EXPECT_EQ(kAutoApproveNotAllowedWarning, warning.message);
+ EXPECT_FALSE(OAuth2Info::GetOAuth2Info(extension.get()).auto_approve);
+}
+
+TEST_F(OAuth2ManifestTest, AutoApproveTrueExtensionNotOnWhitelist) {
+ base::DictionaryValue* ext_manifest =
+ CreateManifest(AUTO_APPROVE_TRUE, false, CLIENT_ID_DEFAULT);
+ ManifestData manifest(ext_manifest, "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest);
+ EXPECT_EQ(1U, extension->install_warnings().size());
+ const extensions::InstallWarning& warning =
+ extension->install_warnings()[0];
+ EXPECT_EQ(kAutoApproveNotAllowedWarning, warning.message);
+ EXPECT_FALSE(OAuth2Info::GetOAuth2Info(extension.get()).auto_approve);
+}
+
+TEST_F(OAuth2ManifestTest, AutoApproveInvalidExtensionNotOnWhitelist) {
+ base::DictionaryValue* ext_manifest =
+ CreateManifest(AUTO_APPROVE_INVALID, false, CLIENT_ID_DEFAULT);
+ ManifestData manifest(ext_manifest, "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest);
+ EXPECT_EQ(1U, extension->install_warnings().size());
+ const extensions::InstallWarning& warning =
+ extension->install_warnings()[0];
+ EXPECT_EQ(kAutoApproveNotAllowedWarning, warning.message);
+ EXPECT_FALSE(OAuth2Info::GetOAuth2Info(extension.get()).auto_approve);
+}
+
+TEST_F(OAuth2ManifestTest, AutoApproveNotSetExtensionOnWhitelist) {
+ base::DictionaryValue* ext_manifest =
+ CreateManifest(AUTO_APPROVE_NOT_SET, true, CLIENT_ID_DEFAULT);
+ ManifestData manifest(ext_manifest, "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest);
+ EXPECT_TRUE(extension->install_warnings().empty());
+ EXPECT_FALSE(OAuth2Info::GetOAuth2Info(extension.get()).auto_approve);
+}
+
+TEST_F(OAuth2ManifestTest, AutoApproveFalseExtensionOnWhitelist) {
+ base::DictionaryValue* ext_manifest =
+ CreateManifest(AUTO_APPROVE_FALSE, true, CLIENT_ID_DEFAULT);
+ ManifestData manifest(ext_manifest, "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest);
+ EXPECT_TRUE(extension->install_warnings().empty());
+ EXPECT_FALSE(OAuth2Info::GetOAuth2Info(extension.get()).auto_approve);
+}
+
+TEST_F(OAuth2ManifestTest, AutoApproveTrueExtensionOnWhitelist) {
+ base::DictionaryValue* ext_manifest =
+ CreateManifest(AUTO_APPROVE_TRUE, true, CLIENT_ID_DEFAULT);
+ ManifestData manifest(ext_manifest, "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest);
+ EXPECT_TRUE(extension->install_warnings().empty());
+ EXPECT_TRUE(OAuth2Info::GetOAuth2Info(extension.get()).auto_approve);
+}
+
+TEST_F(OAuth2ManifestTest, AutoApproveInvalidExtensionOnWhitelist) {
+ base::DictionaryValue* ext_manifest =
+ CreateManifest(AUTO_APPROVE_INVALID, true, CLIENT_ID_DEFAULT);
+ ManifestData manifest(ext_manifest, "test");
+ std::string error;
+ scoped_refptr<extensions::Extension> extension =
+ LoadExtension(manifest, &error);
+ EXPECT_EQ(
+ "Invalid value for 'oauth2.auto_approve'. Value must be true or false.",
+ error);
+}
+
+TEST_F(OAuth2ManifestTest, InvalidClientId) {
+ {
+ base::DictionaryValue* ext_manifest =
+ CreateManifest(AUTO_APPROVE_NOT_SET, false, CLIENT_ID_NOT_SET);
+ ManifestData manifest(ext_manifest, "test");
+ std::string error;
+ LoadAndExpectError(manifest, errors::kInvalidOAuth2ClientId);
+ }
+
+ {
+ base::DictionaryValue* ext_manifest =
+ CreateManifest(AUTO_APPROVE_NOT_SET, false, CLIENT_ID_EMPTY);
+ ManifestData manifest(ext_manifest, "test");
+ std::string error;
+ LoadAndExpectError(manifest, errors::kInvalidOAuth2ClientId);
+ }
+}
+
+TEST_F(OAuth2ManifestTest, ComponentInvalidClientId) {
+ // Component Apps without auto_approve must include a client ID.
+ {
+ base::DictionaryValue* ext_manifest =
+ CreateManifest(AUTO_APPROVE_NOT_SET, false, CLIENT_ID_NOT_SET);
+ ManifestData manifest(ext_manifest, "test");
+ std::string error;
+ LoadAndExpectError(manifest,
+ errors::kInvalidOAuth2ClientId,
+ extensions::Manifest::COMPONENT);
+ }
+
+ {
+ base::DictionaryValue* ext_manifest =
+ CreateManifest(AUTO_APPROVE_NOT_SET, false, CLIENT_ID_EMPTY);
+ ManifestData manifest(ext_manifest, "test");
+ std::string error;
+ LoadAndExpectError(manifest,
+ errors::kInvalidOAuth2ClientId,
+ extensions::Manifest::COMPONENT);
+ }
+}
+
+TEST_F(OAuth2ManifestTest, ComponentWithChromeClientId) {
+ {
+ base::DictionaryValue* ext_manifest =
+ CreateManifest(AUTO_APPROVE_TRUE, true, CLIENT_ID_NOT_SET);
+ ManifestData manifest(ext_manifest, "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest, extensions::Manifest::COMPONENT);
+ EXPECT_TRUE(OAuth2Info::GetOAuth2Info(extension.get()).client_id.empty());
+ }
+
+ {
+ base::DictionaryValue* ext_manifest =
+ CreateManifest(AUTO_APPROVE_TRUE, true, CLIENT_ID_EMPTY);
+ ManifestData manifest(ext_manifest, "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest, extensions::Manifest::COMPONENT);
+ EXPECT_TRUE(OAuth2Info::GetOAuth2Info(extension.get()).client_id.empty());
+ }
+}
+
+TEST_F(OAuth2ManifestTest, ComponentWithStandardClientId) {
+ base::DictionaryValue* ext_manifest =
+ CreateManifest(AUTO_APPROVE_TRUE, true, CLIENT_ID_DEFAULT);
+ ManifestData manifest(ext_manifest, "test");
+ scoped_refptr<extensions::Extension> extension =
+ LoadAndExpectSuccess(manifest, extensions::Manifest::COMPONENT);
+ EXPECT_EQ("client1", OAuth2Info::GetOAuth2Info(extension.get()).client_id);
+}
+
+} // namespace extensions