summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-01-24 18:53:27 +0000
committersergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-01-24 18:53:27 +0000
commit37cdf5a3a7cb5dc8668d9d89efd3d9f37ae40080 (patch)
tree0298a368bbbd1ae01e96fa4ee79dbd85e4c8888d
parent706150c09fb423b1f50c40215c2ce73a3fd1f052 (diff)
downloadchromium_src-37cdf5a3a7cb5dc8668d9d89efd3d9f37ae40080.zip
chromium_src-37cdf5a3a7cb5dc8668d9d89efd3d9f37ae40080.tar.gz
chromium_src-37cdf5a3a7cb5dc8668d9d89efd3d9f37ae40080.tar.bz2
Add support for user-level Native Messaging hosts installation.
With this change Native Messaging hosts can be installed into user's directory without requiring admin permissions BUG=237873 R=asvitkine@chromium.org, dconnelly@chromium.org, jschuh@chromium.org, kalman@chromium.org Review URL: https://codereview.chromium.org/140583002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@246938 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/extensions/api/messaging/message_service.cc10
-rw-r--r--chrome/browser/extensions/api/messaging/native_message_process_host.cc29
-rw-r--r--chrome/browser/extensions/api/messaging/native_message_process_host.h18
-rw-r--r--chrome/browser/extensions/api/messaging/native_message_process_host_unittest.cc82
-rw-r--r--chrome/browser/extensions/api/messaging/native_messaging_apitest.cc28
-rw-r--r--chrome/browser/extensions/api/messaging/native_messaging_test_util.cc53
-rw-r--r--chrome/browser/extensions/api/messaging/native_messaging_test_util.h34
-rw-r--r--chrome/browser/extensions/api/messaging/native_process_launcher.cc26
-rw-r--r--chrome/browser/extensions/api/messaging/native_process_launcher.h4
-rw-r--r--chrome/browser/extensions/api/messaging/native_process_launcher_posix.cc32
-rw-r--r--chrome/browser/extensions/api/messaging/native_process_launcher_win.cc58
-rw-r--r--chrome/browser/policy/configuration_policy_handler_list_factory.cc6
-rw-r--r--chrome/chrome_tests_unit.gypi1
-rw-r--r--chrome/common/extensions/docs/examples/api/nativeMessaging/README.txt9
-rwxr-xr-xchrome/common/extensions/docs/examples/api/nativeMessaging/host/install_host.sh13
-rwxr-xr-xchrome/common/extensions/docs/examples/api/nativeMessaging/host/uninstall_host.sh13
-rw-r--r--chrome/common/extensions/docs/templates/articles/messaging.html11
-rw-r--r--chrome/test/data/policy/policy_test_cases.json8
-rw-r--r--components/policy/resources/policy_templates.json25
-rw-r--r--extensions/browser/extension_prefs.cc4
-rw-r--r--extensions/browser/pref_names.cc2
-rw-r--r--extensions/browser/pref_names.h3
-rw-r--r--tools/metrics/histograms/histograms.xml1
23 files changed, 345 insertions, 125 deletions
diff --git a/chrome/browser/extensions/api/messaging/message_service.cc b/chrome/browser/extensions/api/messaging/message_service.cc
index 48afc78..81577f8 100644
--- a/chrome/browser/extensions/api/messaging/message_service.cc
+++ b/chrome/browser/extensions/api/messaging/message_service.cc
@@ -370,9 +370,12 @@ void MessageService::OpenChannelToNativeApp(
return;
}
+ PrefService* pref_service = profile->GetPrefs();
+
// Verify that the host is not blocked by policies.
- if (!NativeMessageProcessHost::IsHostAllowed(profile->GetPrefs(),
- native_app_name)) {
+ NativeMessageProcessHost::PolicyPermission policy_permission =
+ NativeMessageProcessHost::IsHostAllowed(pref_service, native_app_name);
+ if (policy_permission == NativeMessageProcessHost::DISALLOW) {
DispatchOnDisconnect(source, receiver_port_id, kProhibitedByPoliciesError);
return;
}
@@ -391,7 +394,8 @@ void MessageService::OpenChannelToNativeApp(
native_view,
base::WeakPtr<NativeMessageProcessHost::Client>(
weak_factory_.GetWeakPtr()),
- source_extension_id, native_app_name, receiver_port_id);
+ source_extension_id, native_app_name, receiver_port_id,
+ policy_permission == NativeMessageProcessHost::ALLOW_ALL);
// Abandon the channel.
if (!native_process.get()) {
diff --git a/chrome/browser/extensions/api/messaging/native_message_process_host.cc b/chrome/browser/extensions/api/messaging/native_message_process_host.cc
index 3ed751a..c81446e 100644
--- a/chrome/browser/extensions/api/messaging/native_message_process_host.cc
+++ b/chrome/browser/extensions/api/messaging/native_message_process_host.cc
@@ -52,23 +52,30 @@ const char kHostInputOuputError[] =
namespace extensions {
// static
-bool NativeMessageProcessHost::IsHostAllowed(
- const PrefService* pref_service,
- const std::string& native_host_name) {
+NativeMessageProcessHost::PolicyPermission
+NativeMessageProcessHost::IsHostAllowed(const PrefService* pref_service,
+ const std::string& native_host_name) {
+ NativeMessageProcessHost::PolicyPermission allow_result = ALLOW_ALL;
+ if (pref_service->IsManagedPreference(
+ pref_names::kNativeMessagingUserLevelHosts)) {
+ if (!pref_service->GetBoolean(pref_names::kNativeMessagingUserLevelHosts))
+ allow_result = ALLOW_SYSTEM_ONLY;
+ }
+
// All native messaging hosts are allowed if there is no blacklist.
if (!pref_service->IsManagedPreference(pref_names::kNativeMessagingBlacklist))
- return true;
+ return allow_result;
const base::ListValue* blacklist =
pref_service->GetList(pref_names::kNativeMessagingBlacklist);
if (!blacklist)
- return true;
+ return allow_result;
// Check if the name or the wildcard is in the blacklist.
base::StringValue name_value(native_host_name);
base::StringValue wildcard_value("*");
if (blacklist->Find(name_value) == blacklist->end() &&
blacklist->Find(wildcard_value) == blacklist->end()) {
- return true;
+ return allow_result;
}
// The native messaging host is blacklisted. Check the whitelist.
@@ -77,10 +84,10 @@ bool NativeMessageProcessHost::IsHostAllowed(
const base::ListValue* whitelist =
pref_service->GetList(pref_names::kNativeMessagingWhitelist);
if (whitelist && whitelist->Find(name_value) != whitelist->end())
- return true;
+ return allow_result;
}
- return false;
+ return DISALLOW;
}
NativeMessageProcessHost::NativeMessageProcessHost(
@@ -119,10 +126,12 @@ scoped_ptr<NativeMessageProcessHost> NativeMessageProcessHost::Create(
base::WeakPtr<Client> weak_client_ui,
const std::string& source_extension_id,
const std::string& native_host_name,
- int destination_port) {
+ int destination_port,
+ bool allow_user_level) {
return CreateWithLauncher(weak_client_ui, source_extension_id,
native_host_name, destination_port,
- NativeProcessLauncher::CreateDefault(native_view));
+ NativeProcessLauncher::CreateDefault(
+ allow_user_level, native_view));
}
// static
diff --git a/chrome/browser/extensions/api/messaging/native_message_process_host.h b/chrome/browser/extensions/api/messaging/native_message_process_host.h
index f3c8c08..dc26adf 100644
--- a/chrome/browser/extensions/api/messaging/native_message_process_host.h
+++ b/chrome/browser/extensions/api/messaging/native_message_process_host.h
@@ -52,20 +52,26 @@ class NativeMessageProcessHost
const std::string& error_message) = 0;
};
+ // Result returned from IsHostAllowed().
+ enum PolicyPermission {
+ DISALLOW, // The host is not allowed.
+ ALLOW_SYSTEM_ONLY, // Allowed only when installed on system level.
+ ALLOW_ALL, // Allowed when installed on system or user level.
+ };
+
virtual ~NativeMessageProcessHost();
- // Verifies that the native messaging host with the specified name is allowed
- // by the system policies.
- static bool IsHostAllowed(
- const PrefService* pref_service,
- const std::string& native_host_name);
+ // Returns policy permissions for the host with the specified name.
+ static PolicyPermission IsHostAllowed(const PrefService* pref_service,
+ const std::string& native_host_name);
static scoped_ptr<NativeMessageProcessHost> Create(
gfx::NativeView native_view,
base::WeakPtr<Client> weak_client_ui,
const std::string& source_extension_id,
const std::string& native_host_name,
- int destination_port);
+ int destination_port,
+ bool allow_user_level);
// Create using specified |launcher|. Used in tests.
static scoped_ptr<NativeMessageProcessHost> CreateWithLauncher(
diff --git a/chrome/browser/extensions/api/messaging/native_message_process_host_unittest.cc b/chrome/browser/extensions/api/messaging/native_message_process_host_unittest.cc
index 05042ea..fa45e30 100644
--- a/chrome/browser/extensions/api/messaging/native_message_process_host_unittest.cc
+++ b/chrome/browser/extensions/api/messaging/native_message_process_host_unittest.cc
@@ -102,7 +102,8 @@ class NativeMessagingTest : public ::testing::Test,
protected:
NativeMessagingTest()
: current_channel_(chrome::VersionInfo::CHANNEL_DEV),
- thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {}
+ thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
+ channel_closed_(false) {}
virtual void SetUp() OVERRIDE {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
@@ -138,12 +139,15 @@ class NativeMessagingTest : public ::testing::Test,
delete parsed;
}
- if (read_message_run_loop_)
- read_message_run_loop_->Quit();
+ if (run_loop_)
+ run_loop_->Quit();
}
virtual void CloseChannel(int port_id,
const std::string& error_message) OVERRIDE {
+ channel_closed_ = true;
+ if (run_loop_)
+ run_loop_->Quit();
}
protected:
@@ -168,10 +172,11 @@ class NativeMessagingTest : public ::testing::Test,
ScopedCurrentChannel current_channel_;
scoped_ptr<NativeMessageProcessHost> native_message_process_host_;
base::FilePath user_data_dir_;
- scoped_ptr<base::RunLoop> read_message_run_loop_;
+ scoped_ptr<base::RunLoop> run_loop_;
content::TestBrowserThreadBundle thread_bundle_;
std::string last_message_;
scoped_ptr<base::DictionaryValue> last_message_parsed_;
+ bool channel_closed_;
};
// Read a single message from a local file.
@@ -182,16 +187,16 @@ TEST_F(NativeMessagingTest, SingleSendMessageRead) {
scoped_ptr<NativeProcessLauncher> launcher =
FakeLauncher::Create(temp_input_file, temp_output_file).Pass();
native_message_process_host_ = NativeMessageProcessHost::CreateWithLauncher(
- AsWeakPtr(), kTestNativeMessagingExtensionId, "empty_app.py",
+ AsWeakPtr(), ScopedTestNativeMessagingHost::kExtensionId, "empty_app.py",
0, launcher.Pass());
ASSERT_TRUE(native_message_process_host_.get());
- read_message_run_loop_.reset(new base::RunLoop());
- read_message_run_loop_->RunUntilIdle();
+ run_loop_.reset(new base::RunLoop());
+ run_loop_->RunUntilIdle();
if (last_message_.empty()) {
- read_message_run_loop_.reset(new base::RunLoop());
+ run_loop_.reset(new base::RunLoop());
native_message_process_host_->ReadNowForTesting();
- read_message_run_loop_->Run();
+ run_loop_->Run();
}
EXPECT_EQ(kTestMessage, last_message_);
}
@@ -229,7 +234,7 @@ TEST_F(NativeMessagingTest, SingleSendMessageWrite) {
scoped_ptr<NativeProcessLauncher> launcher =
FakeLauncher::CreateWithPipeInput(read_file, temp_output_file).Pass();
native_message_process_host_ = NativeMessageProcessHost::CreateWithLauncher(
- AsWeakPtr(), kTestNativeMessagingExtensionId, "empty_app.py",
+ AsWeakPtr(), ScopedTestNativeMessagingHost::kExtensionId, "empty_app.py",
0, launcher.Pass());
ASSERT_TRUE(native_message_process_host_.get());
base::RunLoop().RunUntilIdle();
@@ -252,29 +257,22 @@ TEST_F(NativeMessagingTest, SingleSendMessageWrite) {
// Test send message with a real client. The client just echo's back the text
// it received.
TEST_F(NativeMessagingTest, EchoConnect) {
- base::FilePath manifest_path = temp_dir_.path().AppendASCII(
- std::string(kTestNativeMessagingHostName) + ".json");
- ASSERT_NO_FATAL_FAILURE(CreateTestNativeHostManifest(manifest_path));
-
- std::string hosts_option = base::StringPrintf(
- "%s=%s", extensions::kTestNativeMessagingHostName,
- manifest_path.AsUTF8Unsafe().c_str());
- CommandLine::ForCurrentProcess()->AppendSwitchASCII(
- switches::kNativeMessagingHosts, hosts_option);
+ ScopedTestNativeMessagingHost test_host;
+ ASSERT_NO_FATAL_FAILURE(test_host.RegisterTestHost(false));
native_message_process_host_ = NativeMessageProcessHost::Create(
- NULL, AsWeakPtr(), kTestNativeMessagingExtensionId,
- kTestNativeMessagingHostName, 0);
+ NULL, AsWeakPtr(), ScopedTestNativeMessagingHost::kExtensionId,
+ ScopedTestNativeMessagingHost::kHostName, 0, false);
ASSERT_TRUE(native_message_process_host_.get());
native_message_process_host_->Send("{\"text\": \"Hello.\"}");
- read_message_run_loop_.reset(new base::RunLoop());
- read_message_run_loop_->Run();
+ run_loop_.reset(new base::RunLoop());
+ run_loop_->Run();
ASSERT_FALSE(last_message_.empty());
ASSERT_TRUE(last_message_parsed_);
std::string expected_url = std::string("chrome-extension://") +
- kTestNativeMessagingExtensionId + "/";
+ ScopedTestNativeMessagingHost::kExtensionId + "/";
int id;
EXPECT_TRUE(last_message_parsed_->GetInteger("id", &id));
EXPECT_EQ(1, id);
@@ -285,10 +283,9 @@ TEST_F(NativeMessagingTest, EchoConnect) {
EXPECT_TRUE(last_message_parsed_->GetString("caller_url", &url));
EXPECT_EQ(expected_url, url);
-
native_message_process_host_->Send("{\"foo\": \"bar\"}");
- read_message_run_loop_.reset(new base::RunLoop());
- read_message_run_loop_->Run();
+ run_loop_.reset(new base::RunLoop());
+ run_loop_->Run();
EXPECT_TRUE(last_message_parsed_->GetInteger("id", &id));
EXPECT_EQ(2, id);
EXPECT_TRUE(last_message_parsed_->GetString("echo.foo", &text));
@@ -297,4 +294,35 @@ TEST_F(NativeMessagingTest, EchoConnect) {
EXPECT_EQ(expected_url, url);
}
+TEST_F(NativeMessagingTest, UserLevel) {
+ ScopedTestNativeMessagingHost test_host;
+ ASSERT_NO_FATAL_FAILURE(test_host.RegisterTestHost(true));
+
+ native_message_process_host_ = NativeMessageProcessHost::Create(
+ NULL, AsWeakPtr(), ScopedTestNativeMessagingHost::kExtensionId,
+ ScopedTestNativeMessagingHost::kHostName, 0, true);
+ ASSERT_TRUE(native_message_process_host_.get());
+
+ native_message_process_host_->Send("{\"text\": \"Hello.\"}");
+ run_loop_.reset(new base::RunLoop());
+ run_loop_->Run();
+ ASSERT_FALSE(last_message_.empty());
+ ASSERT_TRUE(last_message_parsed_);
+}
+
+TEST_F(NativeMessagingTest, DisallowUserLevel) {
+ ScopedTestNativeMessagingHost test_host;
+ ASSERT_NO_FATAL_FAILURE(test_host.RegisterTestHost(true));
+
+ native_message_process_host_ = NativeMessageProcessHost::Create(
+ NULL, AsWeakPtr(), ScopedTestNativeMessagingHost::kExtensionId,
+ ScopedTestNativeMessagingHost::kHostName, 0, false);
+ ASSERT_TRUE(native_message_process_host_.get());
+ run_loop_.reset(new base::RunLoop());
+ run_loop_->Run();
+
+ // The host should fail to start.
+ ASSERT_TRUE(channel_closed_);
+}
+
} // namespace extensions
diff --git a/chrome/browser/extensions/api/messaging/native_messaging_apitest.cc b/chrome/browser/extensions/api/messaging/native_messaging_apitest.cc
index eb0919a..eac88e8 100644
--- a/chrome/browser/extensions/api/messaging/native_messaging_apitest.cc
+++ b/chrome/browser/extensions/api/messaging/native_messaging_apitest.cc
@@ -2,29 +2,17 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "base/command_line.h"
-#include "base/files/file_path.h"
-#include "base/files/scoped_temp_dir.h"
-#include "base/strings/stringprintf.h"
#include "chrome/browser/extensions/api/messaging/native_messaging_test_util.h"
#include "chrome/browser/extensions/extension_apitest.h"
-#include "chrome/common/chrome_switches.h"
-#include "chrome/common/chrome_version_info.h"
-#include "extensions/common/features/feature.h"
-IN_PROC_BROWSER_TEST_F(ExtensionApiTest, NativeMessageBasic) {
- base::ScopedTempDir temp_dir;
- ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
- base::FilePath manifest_path = temp_dir.path().AppendASCII(
- std::string(extensions::kTestNativeMessagingHostName) + ".json");
- ASSERT_NO_FATAL_FAILURE(
- extensions::CreateTestNativeHostManifest(manifest_path));
-
- std::string hosts_option = base::StringPrintf(
- "%s=%s", extensions::kTestNativeMessagingHostName,
- manifest_path.AsUTF8Unsafe().c_str());
- CommandLine::ForCurrentProcess()->AppendSwitchASCII(
- switches::kNativeMessagingHosts, hosts_option);
+IN_PROC_BROWSER_TEST_F(ExtensionApiTest, NativeMessagingBasic) {
+ extensions::ScopedTestNativeMessagingHost test_host;
+ ASSERT_NO_FATAL_FAILURE(test_host.RegisterTestHost(false));
+ ASSERT_TRUE(RunExtensionTest("native_messaging")) << message_;
+}
+IN_PROC_BROWSER_TEST_F(ExtensionApiTest, UserLevelNativeMessaging) {
+ extensions::ScopedTestNativeMessagingHost test_host;
+ ASSERT_NO_FATAL_FAILURE(test_host.RegisterTestHost(true));
ASSERT_TRUE(RunExtensionTest("native_messaging")) << message_;
}
diff --git a/chrome/browser/extensions/api/messaging/native_messaging_test_util.cc b/chrome/browser/extensions/api/messaging/native_messaging_test_util.cc
index e9cb75e..f33e0d2 100644
--- a/chrome/browser/extensions/api/messaging/native_messaging_test_util.cc
+++ b/chrome/browser/extensions/api/messaging/native_messaging_test_util.cc
@@ -9,19 +9,22 @@
#include "base/memory/scoped_ptr.h"
#include "base/path_service.h"
#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/common/chrome_paths.h"
#include "testing/gtest/include/gtest/gtest.h"
+#if defined(OS_WIN)
+#include "base/win/registry.h"
+#endif
+
namespace extensions {
-const char kTestNativeMessagingHostName[] = "com.google.chrome.test.echo";
-const char kTestNativeMessagingExtensionId[] =
- "knldjmfmopnpolahpmmgbagdohdnhkik";
+namespace {
-void CreateTestNativeHostManifest(base::FilePath manifest_path) {
+void WriteTestNativeHostManifest(base::FilePath manifest_path) {
scoped_ptr<base::DictionaryValue> manifest(new base::DictionaryValue());
- manifest->SetString("name", kTestNativeMessagingHostName);
+ manifest->SetString("name", ScopedTestNativeMessagingHost::kHostName);
manifest->SetString("description", "Native Messaging Echo Test");
manifest->SetString("type", "stdio");
@@ -37,12 +40,48 @@ void CreateTestNativeHostManifest(base::FilePath manifest_path) {
manifest->SetString("path", host_path.AsUTF8Unsafe());
scoped_ptr<base::ListValue> origins(new base::ListValue());
- origins->AppendString(base::StringPrintf("chrome-extension://%s/",
- kTestNativeMessagingExtensionId));
+ origins->AppendString(base::StringPrintf(
+ "chrome-extension://%s/", ScopedTestNativeMessagingHost::kExtensionId));
manifest->Set("allowed_origins", origins.release());
JSONFileValueSerializer serializer(manifest_path);
ASSERT_TRUE(serializer.Serialize(*manifest));
}
+} // namespace
+
+const char ScopedTestNativeMessagingHost::kHostName[] =
+ "com.google.chrome.test.echo";
+const char ScopedTestNativeMessagingHost::kExtensionId[] =
+ "knldjmfmopnpolahpmmgbagdohdnhkik";
+
+ScopedTestNativeMessagingHost::ScopedTestNativeMessagingHost() {}
+
+void ScopedTestNativeMessagingHost::RegisterTestHost(bool user_level) {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ ScopedTestNativeMessagingHost test_host;
+ base::FilePath manifest_path = temp_dir_.path().AppendASCII(
+ std::string(ScopedTestNativeMessagingHost::kHostName) + ".json");
+ ASSERT_NO_FATAL_FAILURE(WriteTestNativeHostManifest(manifest_path));
+
+#if defined(OS_WIN)
+ HKEY root_key = user_level ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE;
+ registry_override_.OverrideRegistry(root_key, L"native_messaging");
+ base::string16 key = L"SOFTWARE\\Google\\Chrome\\NativeMessagingHosts\\" +
+ base::UTF8ToUTF16(kHostName);
+ base::win::RegKey manifest_key(
+ root_key, key.c_str(),
+ KEY_SET_VALUE | KEY_CREATE_SUB_KEY | KEY_CREATE_LINK);
+ ASSERT_EQ(ERROR_SUCCESS,
+ manifest_key.WriteValue(NULL, manifest_path.value().c_str()));
+#else
+ path_override_.reset(new base::ScopedPathOverride(
+ user_level ? chrome::DIR_USER_NATIVE_MESSAGING
+ : chrome::DIR_NATIVE_MESSAGING,
+ temp_dir_.path()));
+#endif
+}
+
+ScopedTestNativeMessagingHost::~ScopedTestNativeMessagingHost() {}
+
} // namespace extensions
diff --git a/chrome/browser/extensions/api/messaging/native_messaging_test_util.h b/chrome/browser/extensions/api/messaging/native_messaging_test_util.h
index f258c6f..4118854 100644
--- a/chrome/browser/extensions/api/messaging/native_messaging_test_util.h
+++ b/chrome/browser/extensions/api/messaging/native_messaging_test_util.h
@@ -5,16 +5,38 @@
#ifndef CHROME_BROWSER_EXTENSIONS_API_MESSAGING_NATIVE_MESSAGING_TEST_UTIL_H_
#define CHROME_BROWSER_EXTENSIONS_API_MESSAGING_NATIVE_MESSAGING_TEST_UTIL_H_
-namespace base {
-class FilePath;
-}
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+
+#if defined(OS_WIN)
+#include "base/test/test_reg_util_win.h"
+#else
+#include "base/test/scoped_path_override.h"
+#endif
namespace extensions {
-extern const char kTestNativeMessagingHostName[];
-extern const char kTestNativeMessagingExtensionId[];
+class ScopedTestNativeMessagingHost {
+ public:
+ static const char kHostName[];
+ static const char kExtensionId[];
+
+ ScopedTestNativeMessagingHost();
+ ~ScopedTestNativeMessagingHost();
+
+ void RegisterTestHost(bool user_level);
+
+ private:
+ base::ScopedTempDir temp_dir_;
+
+#if defined(OS_WIN)
+ registry_util::RegistryOverrideManager registry_override_;
+#else
+ scoped_ptr<base::ScopedPathOverride> path_override_;
+#endif
-void CreateTestNativeHostManifest(base::FilePath manifest_path);
+ DISALLOW_COPY_AND_ASSIGN(ScopedTestNativeMessagingHost);
+};
} // namespace extensions
diff --git a/chrome/browser/extensions/api/messaging/native_process_launcher.cc b/chrome/browser/extensions/api/messaging/native_process_launcher.cc
index d748e46..edf29ee 100644
--- a/chrome/browser/extensions/api/messaging/native_process_launcher.cc
+++ b/chrome/browser/extensions/api/messaging/native_process_launcher.cc
@@ -60,7 +60,8 @@ base::FilePath GetHostManifestPathFromCommandLine(
// Default implementation on NativeProcessLauncher interface.
class NativeProcessLauncherImpl : public NativeProcessLauncher {
public:
- explicit NativeProcessLauncherImpl(intptr_t native_window);
+ NativeProcessLauncherImpl(bool allow_user_level_hosts,
+ intptr_t native_window);
virtual ~NativeProcessLauncherImpl();
virtual void Launch(const GURL& origin,
@@ -70,7 +71,7 @@ class NativeProcessLauncherImpl : public NativeProcessLauncher {
private:
class Core : public base::RefCountedThreadSafe<Core> {
public:
- explicit Core(intptr_t native_window);
+ Core(bool allow_user_level_hosts, intptr_t native_window);
void Launch(const GURL& origin,
const std::string& native_host_name,
LaunchedCallback callback);
@@ -96,6 +97,8 @@ class NativeProcessLauncherImpl : public NativeProcessLauncher {
bool detached_;
+ bool allow_user_level_hosts_;
+
// Handle of the native window corresponding to the extension.
intptr_t window_handle_;
@@ -107,8 +110,10 @@ class NativeProcessLauncherImpl : public NativeProcessLauncher {
DISALLOW_COPY_AND_ASSIGN(NativeProcessLauncherImpl);
};
-NativeProcessLauncherImpl::Core::Core(intptr_t window_handle)
+NativeProcessLauncherImpl::Core::Core(bool allow_user_level_hosts,
+ intptr_t window_handle)
: detached_(false),
+ allow_user_level_hosts_(allow_user_level_hosts),
window_handle_(window_handle) {
}
@@ -147,8 +152,10 @@ void NativeProcessLauncherImpl::Core::DoLaunchOnThreadPool(
// First check if the manifest location is specified in the command line.
base::FilePath manifest_path =
GetHostManifestPathFromCommandLine(native_host_name);
- if (manifest_path.empty())
- manifest_path = FindManifest(native_host_name, &error_message);
+ if (manifest_path.empty()) {
+ manifest_path =
+ FindManifest(native_host_name, allow_user_level_hosts_, &error_message);
+ }
if (manifest_path.empty()) {
LOG(ERROR) << "Can't find manifest for native messaging host "
@@ -256,8 +263,10 @@ void NativeProcessLauncherImpl::Core::PostResult(
write_file));
}
-NativeProcessLauncherImpl::NativeProcessLauncherImpl(intptr_t window_handle)
- : core_(new Core(window_handle)) {
+NativeProcessLauncherImpl::NativeProcessLauncherImpl(
+ bool allow_user_level_hosts,
+ intptr_t window_handle)
+ : core_(new Core(allow_user_level_hosts, window_handle)) {
}
NativeProcessLauncherImpl::~NativeProcessLauncherImpl() {
@@ -274,6 +283,7 @@ void NativeProcessLauncherImpl::Launch(const GURL& origin,
// static
scoped_ptr<NativeProcessLauncher> NativeProcessLauncher::CreateDefault(
+ bool allow_user_level_hosts,
gfx::NativeView native_view) {
intptr_t window_handle = 0;
#if defined(OS_WIN)
@@ -281,7 +291,7 @@ scoped_ptr<NativeProcessLauncher> NativeProcessLauncher::CreateDefault(
views::HWNDForNativeView(native_view));
#endif
return scoped_ptr<NativeProcessLauncher>(
- new NativeProcessLauncherImpl(window_handle));
+ new NativeProcessLauncherImpl(allow_user_level_hosts, window_handle));
}
} // namespace extensions
diff --git a/chrome/browser/extensions/api/messaging/native_process_launcher.h b/chrome/browser/extensions/api/messaging/native_process_launcher.h
index edc0239..601823d 100644
--- a/chrome/browser/extensions/api/messaging/native_process_launcher.h
+++ b/chrome/browser/extensions/api/messaging/native_process_launcher.h
@@ -39,6 +39,7 @@ class NativeProcessLauncher {
base::PlatformFile write_file)> LaunchedCallback;
static scoped_ptr<NativeProcessLauncher> CreateDefault(
+ bool allow_user_level_hosts,
gfx::NativeView native_view);
NativeProcessLauncher() {}
@@ -59,7 +60,10 @@ class NativeProcessLauncher {
// platform-specific .cc files.
// Finds manifest file for the native messaging host |native_host_name|.
+ // |user_level| is set to true if the manifest is installed on user level.
+ // Returns an empty path if the host with the specified name cannot be found.
static base::FilePath FindManifest(const std::string& native_host_name,
+ bool allow_user_level_hosts,
std::string* error_message);
// Launches native messaging process.
diff --git a/chrome/browser/extensions/api/messaging/native_process_launcher_posix.cc b/chrome/browser/extensions/api/messaging/native_process_launcher_posix.cc
index c420c56..f4523fc 100644
--- a/chrome/browser/extensions/api/messaging/native_process_launcher_posix.cc
+++ b/chrome/browser/extensions/api/messaging/native_process_launcher_posix.cc
@@ -14,17 +14,35 @@
namespace extensions {
+namespace {
+
+base::FilePath FindManifestInDir(int dir_key, const std::string& host_name) {
+ base::FilePath base_path;
+ if (PathService::Get(dir_key, &base_path)) {
+ base::FilePath path = base_path.Append(host_name + ".json");
+ if (base::PathExists(path))
+ return path;
+ }
+ return base::FilePath();
+}
+
+} // namespace
+
// static
base::FilePath NativeProcessLauncher::FindManifest(
- const std::string& native_host_name,
+ const std::string& host_name,
+ bool allow_user_level_hosts,
std::string* error_message) {
base::FilePath result;
- if (!PathService::Get(chrome::DIR_NATIVE_MESSAGING, &result)) {
- NOTREACHED();
- *error_message = "Can't find native messaging host " + native_host_name;
- return base::FilePath();
- }
- return result.Append(native_host_name + ".json");
+ if (allow_user_level_hosts)
+ result = FindManifestInDir(chrome::DIR_USER_NATIVE_MESSAGING, host_name);
+ if (result.empty())
+ result = FindManifestInDir(chrome::DIR_NATIVE_MESSAGING, host_name);
+
+ if (result.empty())
+ *error_message = "Can't find native messaging host " + host_name;
+
+ return result;
}
// static
diff --git a/chrome/browser/extensions/api/messaging/native_process_launcher_win.cc b/chrome/browser/extensions/api/messaging/native_process_launcher_win.cc
index 4856627..7e30427ae 100644
--- a/chrome/browser/extensions/api/messaging/native_process_launcher_win.cc
+++ b/chrome/browser/extensions/api/messaging/native_process_launcher_win.cc
@@ -26,44 +26,58 @@ const wchar_t kNativeMessagingRegistryKey[] =
namespace {
// Reads path to the native messaging host manifest from the registry. Returns
-// empty string if the path isn't found.
-base::string16 GetManifestPath(const base::string16& native_host_name,
- DWORD flags) {
+// false if the path isn't found.
+bool GetManifestPathWithFlags(HKEY root_key,
+ DWORD flags,
+ const base::string16& host_name,
+ base::string16* result) {
base::win::RegKey key;
- base::string16 result;
- if (key.Open(HKEY_LOCAL_MACHINE, kNativeMessagingRegistryKey,
+ if (key.Open(root_key, kNativeMessagingRegistryKey,
KEY_QUERY_VALUE | flags) != ERROR_SUCCESS ||
- key.OpenKey(native_host_name.c_str(),
+ key.OpenKey(host_name.c_str(),
KEY_QUERY_VALUE | flags) != ERROR_SUCCESS ||
- key.ReadValue(NULL, &result) != ERROR_SUCCESS) {
- return base::string16();
+ key.ReadValue(NULL, result) != ERROR_SUCCESS) {
+ return false;
}
- return result;
+ return true;
+}
+
+bool GetManifestPath(HKEY root_key,
+ const base::string16& host_name,
+ base::string16* result) {
+ // First check 32-bit registry and then try 64-bit.
+ return GetManifestPathWithFlags(
+ root_key, KEY_WOW64_32KEY, host_name, result) ||
+ GetManifestPathWithFlags(
+ root_key, KEY_WOW64_64KEY, host_name, result);
}
} // namespace
// static
base::FilePath NativeProcessLauncher::FindManifest(
- const std::string& native_host_name,
+ const std::string& host_name,
+ bool allow_user_level_hosts,
std::string* error_message) {
- base::string16 native_host_name_wide = base::UTF8ToUTF16(native_host_name);
-
- // First check 32-bit registry and then try 64-bit.
- base::string16 manifest_path_str =
- GetManifestPath(native_host_name_wide, KEY_WOW64_32KEY);
- if (manifest_path_str.empty())
- manifest_path_str = GetManifestPath(native_host_name_wide, KEY_WOW64_64KEY);
-
- if (manifest_path_str.empty()) {
- *error_message = "Native messaging host " + native_host_name +
- " is not registered";
+ base::string16 host_name_wide = base::UTF8ToUTF16(host_name);
+
+ // Try to find the key in HKEY_LOCAL_MACHINE and then in HKEY_CURRENT_USER.
+ base::string16 path_str;
+ bool found = false;
+ if (allow_user_level_hosts)
+ found = GetManifestPath(HKEY_CURRENT_USER, host_name_wide, &path_str);
+ if (!found)
+ found = GetManifestPath(HKEY_LOCAL_MACHINE, host_name_wide, &path_str);
+
+ if (!found) {
+ *error_message =
+ "Native messaging host " + host_name + " is not registered.";
return base::FilePath();
}
- base::FilePath manifest_path(manifest_path_str);
+ base::FilePath manifest_path(path_str);
if (!manifest_path.IsAbsolute()) {
*error_message = "Path to native messaging host manifest must be absolute.";
return base::FilePath();
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index ea5aa6d..c623a12 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -451,6 +451,12 @@ const PolicyToPreferenceMapEntry kSimplePolicyMap[] = {
prefs::kSpdyProxyAuthEnabled,
base::Value::TYPE_BOOLEAN },
#endif // defined(OS_ANDROID)
+
+#if !defined(OS_CHROMEOS) && !defined(OS_ANDROID) && !defined(OS_IOS)
+ { key::kNativeMessagingUserLevelHosts,
+ extensions::pref_names::kNativeMessagingUserLevelHosts,
+ base::Value::TYPE_BOOLEAN },
+#endif // !defined(OS_CHROMEOS) && !defined(OS_ANDROID) && !defined(OS_IOS)
};
#if !defined(OS_IOS)
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index 905db0e..4861cce 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -381,6 +381,7 @@
['OS=="android"', {
'sources/': [
['exclude', '^browser/media_galleries/'],
+ ['exclude', '^browser/extensions/api/messaging/native_messaging_'],
],
}],
['OS=="win"', {
diff --git a/chrome/common/extensions/docs/examples/api/nativeMessaging/README.txt b/chrome/common/extensions/docs/examples/api/nativeMessaging/README.txt
index 5d5ba75..4e9e68f 100644
--- a/chrome/common/extensions/docs/examples/api/nativeMessaging/README.txt
+++ b/chrome/common/extensions/docs/examples/api/nativeMessaging/README.txt
@@ -9,11 +9,16 @@ To install the host:
On Windows:
Add registry key
HKEY_LOCAL_MACHINE\SOFTWARE\Google\Chrome\NativeMessagingHosts\com.google.chrome.example.echo
+ or
+ HKEY_CURRENT_USER\SOFTWARE\Google\Chrome\NativeMessagingHosts\com.google.chrome.example.echo
and set its default value to the full path to
host\com.google.chrome.example.echo-win.json . Note that you need to have
python installed.
On Mac and Linux:
Run install_host.sh script in the host directory:
- sudo host/install_host.sh
- You can later use host/uninstall_host.sh to uninstall the host.
+ host/install_host.sh
+ By default the host is installed only for the user who runs the script, but if
+ you run it with admin privileges (i.e. 'sudo host/install_host.sh'), then the
+ host will be installed for all users. You can later use host/uninstall_host.sh
+ to uninstall the host.
diff --git a/chrome/common/extensions/docs/examples/api/nativeMessaging/host/install_host.sh b/chrome/common/extensions/docs/examples/api/nativeMessaging/host/install_host.sh
index 6c37f2a..e75c067 100755
--- a/chrome/common/extensions/docs/examples/api/nativeMessaging/host/install_host.sh
+++ b/chrome/common/extensions/docs/examples/api/nativeMessaging/host/install_host.sh
@@ -7,9 +7,18 @@ set -e
DIR="$( cd "$( dirname "$0" )" && pwd )"
if [ $(uname -s) == 'Darwin' ]; then
- TARGET_DIR='/Library/Google/Chrome/NativeMessagingHosts'
+ if [ "$(whoami)" == "root" ]; then
+ TARGET_DIR="/Library/Google/Chrome/NativeMessagingHosts"
+ else
+ TARGET_DIR=\
+ "$HOME/Library/Application Support/Google/Chrome/NativeMessagingHosts"
+ fi
else
- TARGET_DIR='/etc/opt/chrome/native-messaging-hosts'
+ if [ "$(whoami)" == "root" ]; then
+ TARGET_DIR="/etc/opt/chrome/native-messaging-hosts"
+ else
+ TARGET_DIR='$HOME/.config/google-chrome/NativeMessagingHosts'
+ fi
fi
HOST_NAME=com.google.chrome.example.echo
diff --git a/chrome/common/extensions/docs/examples/api/nativeMessaging/host/uninstall_host.sh b/chrome/common/extensions/docs/examples/api/nativeMessaging/host/uninstall_host.sh
index 06147a6..5fc4d4c 100755
--- a/chrome/common/extensions/docs/examples/api/nativeMessaging/host/uninstall_host.sh
+++ b/chrome/common/extensions/docs/examples/api/nativeMessaging/host/uninstall_host.sh
@@ -6,9 +6,18 @@
set -e
if [ $(uname -s) == 'Darwin' ]; then
- TARGET_DIR='/Library/Google/Chrome/NativeMessagingHosts'
+ if [ "$(whoami)" == "root" ]; then
+ TARGET_DIR="/Library/Google/Chrome/NativeMessagingHosts"
+ else
+ TARGET_DIR=\
+ "$HOME/Library/Application Support/Google/Chrome/NativeMessagingHosts"
+ fi
else
- TARGET_DIR='/etc/opt/chrome/native-messaging-hosts'
+ if [ "$(whoami)" == "root" ]; then
+ TARGET_DIR="/etc/opt/chrome/native-messaging-hosts"
+ else
+ TARGET_DIR='$HOME/.config/google-chrome/NativeMessagingHosts'
+ fi
fi
HOST_NAME=com.google.chrome.example.echo
diff --git a/chrome/common/extensions/docs/templates/articles/messaging.html b/chrome/common/extensions/docs/templates/articles/messaging.html
index 9fa7daa..94e9bab 100644
--- a/chrome/common/extensions/docs/templates/articles/messaging.html
+++ b/chrome/common/extensions/docs/templates/articles/messaging.html
@@ -336,16 +336,23 @@ example of the manifest file:
<dd>The manifest file can be located anywhere in the file system.
The application installer must create registry key
<code>HKEY_LOCAL_MACHINE\SOFTWARE\Google\Chrome\NativeMessagingHosts\<em>com.my_company.my_application</em></code>
+ or
+ <code>HKEY_CURRENT_USER\SOFTWARE\Google\Chrome\NativeMessagingHosts\<em>com.my_company.my_application</em></code>,
and set default value of that key to the full path to the manifest file.
</dd>
<dt>OSX:</dt>
<dd>The manifest file must be placed at
- <code>/Library/Google/Chrome/NativeMessagingHosts/<em>com.my_company.my_application</em>.json</code>
+ <code>/Library/Google/Chrome/NativeMessagingHosts/<em>com.my_company.my_application</em>.json</code>,
+ or, for applications installed on user level,
+ <code>~/Library/Application Support/Google/Chrome/NativeMessagingHosts/<em>com.my_company.my_application</em>.json</code>.
</dd>
+
<dt>Linux:</dt>
<dd>The manifest file must be placed at
- <code>/etc/opt/chrome/native-messaging-hosts/<em>com.my_company.my_application</em>.json</code>
+ <code>/etc/opt/chrome/native-messaging-hosts/<em>com.my_company.my_application</em>.json</code>,
+ or, for applications installed on user level,
+ <code>~/.config/chrome/NativeMessagingHosts/<em>com.my_company.my_application</em>.json</code>.
</dd>
</dl>
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index b78c0f9..e637872 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -1975,6 +1975,14 @@
]
},
+ "NativeMessagingUserLevelHosts": {
+ "os": ["win", "linux", "mac"],
+ "test_policy": { "NativeMessagingUserLevelHosts": false },
+ "pref_mappings": [
+ { "pref": "native_messaging.user_level_hosts" }
+ ]
+ },
+
"----- Chrome OS device policies ---------------------------------------": {},
"DevicePolicyRefreshRate": {
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index b361a22..1fddaa6 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -118,7 +118,7 @@
# persistent IDs for all fields (but not for groups!) are needed. These are
# specified by the 'id' keys of each policy. NEVER CHANGE EXISTING IDs,
# because doing so would break the deployed wire format!
-# For your editing convenience: highest ID currently used: 252
+# For your editing convenience: highest ID currently used: 253
#
# Placeholders:
# The following placeholder strings are automatically substituted:
@@ -2635,6 +2635,29 @@
By default, all native messaging hosts are whitelisted, but if all native messaging hosts have been blacklisted by policy, the whitelist can be used to override that policy.''',
'label': '''Names of the native messaging hosts to exempt from the blacklist''',
},
+ {
+ 'name': 'NativeMessagingUserLevelHosts',
+ 'type': 'main',
+ 'schema': { 'type': 'boolean' },
+ 'supported_on': ['chrome.*:34-'],
+ 'features': {
+ 'dynamic_refresh': True,
+ 'per_profile': True,
+ },
+ 'example_value': False,
+ 'id': 253,
+ 'caption': '''Allow user-level Native Messaging hosts (installed without admin permissions).''',
+ 'desc': '''Enables user-level installation of Native Messaging hosts.
+
+ If this setting is enabled then <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> allows
+ usage of Native Messaging hosts installed on user level.
+
+ If this setting is disabled then <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> will
+ only use Native Messaging hosts installed on system level.
+
+ If this setting is left not set <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>
+ will allow usage of user-level Native Messaging hosts.''',
+ },
],
},
{
diff --git a/extensions/browser/extension_prefs.cc b/extensions/browser/extension_prefs.cc
index 8e56ed7..1d3d439 100644
--- a/extensions/browser/extension_prefs.cc
+++ b/extensions/browser/extension_prefs.cc
@@ -1753,6 +1753,10 @@ void ExtensionPrefs::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
registry->RegisterListPref(pref_names::kNativeMessagingWhitelist,
user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
+ registry->RegisterBooleanPref(
+ pref_names::kNativeMessagingUserLevelHosts,
+ true, // default value
+ user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
}
template <class ExtensionIdContainer>
diff --git a/extensions/browser/pref_names.cc b/extensions/browser/pref_names.cc
index be9a6b0..2f88cfc 100644
--- a/extensions/browser/pref_names.cc
+++ b/extensions/browser/pref_names.cc
@@ -41,6 +41,8 @@ const char kLastChromeVersion[] = "extensions.last_chrome_version";
const char kLastUpdateCheck[] = "extensions.autoupdate.last_check";
const char kNativeMessagingBlacklist[] = "native_messaging.blacklist";
const char kNativeMessagingWhitelist[] = "native_messaging.whitelist";
+const char kNativeMessagingUserLevelHosts[] =
+ "native_messaging.user_level_hosts";
const char kNextUpdateCheck[] = "extensions.autoupdate.next_check";
const char kStorageGarbageCollect[] = "extensions.storage.garbagecollect";
const char kToolbar[] = "extensions.toolbar";
diff --git a/extensions/browser/pref_names.h b/extensions/browser/pref_names.h
index ed397a0..9cc0b3f 100644
--- a/extensions/browser/pref_names.h
+++ b/extensions/browser/pref_names.h
@@ -74,6 +74,9 @@ extern const char kLastUpdateCheck[];
extern const char kNativeMessagingBlacklist[];
extern const char kNativeMessagingWhitelist[];
+// Flag allowing usage of Native Messaging hosts installed on user level.
+extern const char kNativeMessagingUserLevelHosts[];
+
// Time of the next scheduled extensions auto-update checks.
extern const char kNextUpdateCheck[];
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index a350dba..cd14490 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -23898,6 +23898,7 @@ other types of suffix sets.
<int value="250" label="Enable network configuration prompt when offline"/>
<int value="251" label="Native Messaging blacklist"/>
<int value="252" label="Native Messaging whitelist"/>
+ <int value="253" label="Allow user-level Native Messaging hosts"/>
</enum>
<enum name="EnterprisePolicyInvalidations" type="int">