diff options
author | jamescook <jamescook@chromium.org> | 2014-10-20 16:47:15 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-10-20 23:47:34 +0000 |
commit | 71187e343e2a8005dff92649b3013ba5019f974a (patch) | |
tree | 42a3761b483d5161130a7707e1748a50d3309055 /extensions/common/manifest_handlers | |
parent | 9db73d2637e0c8c258a42509e7a2b913775f5f14 (diff) | |
download | chromium_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')
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 |