diff options
author | sergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-01-24 18:53:27 +0000 |
---|---|---|
committer | sergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-01-24 18:53:27 +0000 |
commit | 37cdf5a3a7cb5dc8668d9d89efd3d9f37ae40080 (patch) | |
tree | 0298a368bbbd1ae01e96fa4ee79dbd85e4c8888d | |
parent | 706150c09fb423b1f50c40215c2ce73a3fd1f052 (diff) | |
download | chromium_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
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"> |