blob: b0c9e55d2fc3c07a0d09270ae31d330aea1db6ba [file] [log] [blame]
// 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 <string>
#include "base/base64url.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/memory/ref_counted.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/policy/chrome_browser_policy_connector.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/policy/profile_policy_connector_factory.h"
#include "chrome/browser/policy/test/local_policy_test_server.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/chrome_paths.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/policy/core/common/cloud/cloud_policy_client.h"
#include "components/policy/core/common/cloud/cloud_policy_constants.h"
#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
#include "components/policy/core/common/cloud/policy_builder.h"
#include "components/policy/core/common/policy_service.h"
#include "components/policy/core/common/policy_switches.h"
#include "components/policy/core/common/policy_test_utils.h"
#include "components/policy/proto/chrome_extension_policy.pb.h"
#include "components/policy/proto/cloud_policy.pb.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "extensions/common/extension.h"
#include "extensions/test/extension_test_message_listener.h"
#include "net/url_request/url_request_context_getter.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/policy/user_cloud_policy_manager_chromeos.h"
#include "chrome/browser/chromeos/policy/user_policy_manager_factory_chromeos.h"
#include "chromeos/chromeos_switches.h"
#else
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/policy/cloud/user_cloud_policy_manager_factory.h"
#include "chrome/browser/signin/signin_manager_factory.h"
#include "components/policy/core/common/cloud/user_cloud_policy_manager.h"
#include "components/signin/core/browser/signin_manager.h"
#endif
using testing::InvokeWithoutArgs;
using testing::Mock;
using testing::Return;
using testing::_;
namespace em = enterprise_management;
namespace policy {
namespace {
const char kDMToken[] = "dmtoken";
const char kDeviceID[] = "deviceid";
const char kTestExtension[] = "kjmkgkdkpedkejedfhmfcenooemhbpbo";
const base::FilePath::CharType kTestExtensionPath[] =
FILE_PATH_LITERAL("extensions/managed_extension");
const char kTestPolicy[] =
"{"
" \"Name\": {"
" \"Value\": \"disable_all_the_things\""
" }"
"}";
const char kTestExtension2[] = "behllobkkfkfnphdnhnkndlbkcpglgmj";
const base::FilePath::CharType kTestExtension2Path[] =
FILE_PATH_LITERAL("extensions/managed_extension2");
const char kTestPolicyJSON[] = "{\"Name\":\"disable_all_the_things\"}";
const char kTestPolicy2[] =
"{"
" \"Another\": {"
" \"Value\": \"turn_it_off\""
" }"
"}";
const char kTestPolicy2JSON[] = "{\"Another\":\"turn_it_off\"}";
} // namespace
class ComponentCloudPolicyTest : public extensions::ExtensionBrowserTest {
protected:
ComponentCloudPolicyTest() {}
~ComponentCloudPolicyTest() override {}
void SetUpCommandLine(base::CommandLine* command_line) override {
extensions::ExtensionBrowserTest::SetUpCommandLine(command_line);
#if defined(OS_CHROMEOS)
// ExtensionBrowserTest sets the login users to a non-managed value;
// replace it. This is the default username sent in policy blobs from the
// testserver.
command_line->AppendSwitchASCII(::chromeos::switches::kLoginUser,
PolicyBuilder::kFakeUsername);
// Let policy code know that policy is not required to be cached at startup
// (it can be loaded asynchronously).
command_line->AppendSwitchASCII(
::chromeos::switches::kProfileRequiresPolicy, "false");
#endif
}
void SetUpInProcessBrowserTestFixture() override {
test_server_.RegisterClient(kDMToken, kDeviceID);
EXPECT_TRUE(test_server_.UpdatePolicyData(
dm_protocol::kChromeExtensionPolicyType, kTestExtension, kTestPolicy));
ASSERT_TRUE(test_server_.Start());
std::string url = test_server_.GetServiceURL().spec();
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
command_line->AppendSwitchASCII(switches::kDeviceManagementUrl, url);
extensions::ExtensionBrowserTest::SetUpInProcessBrowserTestFixture();
}
void SetUpOnMainThread() override {
extensions::ExtensionBrowserTest::SetUpOnMainThread();
ASSERT_TRUE(PolicyServiceIsEmpty(g_browser_process->policy_service()))
<< "Pre-existing policies in this machine will make this test fail.";
// Install the initial extension.
ExtensionTestMessageListener ready_listener("ready", false);
event_listener_.reset(new ExtensionTestMessageListener("event", true));
extension_ = LoadExtension(kTestExtensionPath);
ASSERT_TRUE(extension_.get());
ASSERT_EQ(kTestExtension, extension_->id());
EXPECT_TRUE(ready_listener.WaitUntilSatisfied());
// And start with a signed-in user.
SignInAndRegister();
// The extension will receive an update event.
EXPECT_TRUE(event_listener_->WaitUntilSatisfied());
}
void TearDownOnMainThread() override {
event_listener_.reset();
extensions::ExtensionBrowserTest::TearDownOnMainThread();
}
scoped_refptr<const extensions::Extension> LoadExtension(
const base::FilePath::CharType* path) {
base::FilePath full_path;
if (!base::PathService::Get(chrome::DIR_TEST_DATA, &full_path)) {
ADD_FAILURE();
return NULL;
}
scoped_refptr<const extensions::Extension> extension(
extensions::ExtensionBrowserTest::LoadExtension(
full_path.Append(path)));
if (!extension.get()) {
ADD_FAILURE();
return NULL;
}
return extension;
}
void SignInAndRegister() {
BrowserPolicyConnector* connector =
g_browser_process->browser_policy_connector();
connector->ScheduleServiceInitialization(0);
#if defined(OS_CHROMEOS)
UserCloudPolicyManagerChromeOS* policy_manager =
UserPolicyManagerFactoryChromeOS::GetCloudPolicyManagerForProfile(
browser()->profile());
ASSERT_TRUE(policy_manager);
#else
// Mock a signed-in user. This is used by the UserCloudPolicyStore to pass
// the account id to the UserCloudPolicyValidator.
SigninManager* signin_manager =
SigninManagerFactory::GetForProfile(browser()->profile());
ASSERT_TRUE(signin_manager);
signin_manager->StartSignInWithRefreshToken(
"", "account_id", "12345", PolicyBuilder::kFakeUsername,
SigninManager::OAuthTokenFetchedCallback());
UserCloudPolicyManager* policy_manager =
UserCloudPolicyManagerFactory::GetForBrowserContext(
browser()->profile());
ASSERT_TRUE(policy_manager);
policy_manager->SetSigninAccountId(
PolicyBuilder::GetFakeAccountIdForTesting());
policy_manager->Connect(
g_browser_process->local_state(),
g_browser_process->system_request_context(),
UserCloudPolicyManager::CreateCloudPolicyClient(
connector->device_management_service(),
g_browser_process->system_request_context(),
g_browser_process->system_network_context_manager()
->GetSharedURLLoaderFactory()));
#endif // defined(OS_CHROMEOS)
// Register the cloud policy client.
client_ = policy_manager->core()->client();
ASSERT_TRUE(client_);
base::RunLoop run_loop;
MockCloudPolicyClientObserver observer;
EXPECT_CALL(observer, OnRegistrationStateChanged(_))
.WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
client_->AddObserver(&observer);
client_->SetupRegistration(
kDMToken, kDeviceID,
std::vector<std::string>() /* user_affiliation_ids */);
run_loop.Run();
Mock::VerifyAndClearExpectations(&observer);
client_->RemoveObserver(&observer);
}
#if !defined(OS_CHROMEOS)
void SignOut() {
SigninManager* signin_manager =
SigninManagerFactory::GetForProfile(browser()->profile());
ASSERT_TRUE(signin_manager);
signin_manager->SignOut(signin_metrics::SIGNOUT_TEST,
signin_metrics::SignoutDelete::IGNORE_METRIC);
}
#endif
void RefreshPolicies() {
ProfilePolicyConnector* profile_connector =
ProfilePolicyConnectorFactory::GetForBrowserContext(
browser()->profile());
PolicyService* policy_service = profile_connector->policy_service();
base::RunLoop run_loop;
policy_service->RefreshPolicies(run_loop.QuitClosure());
run_loop.Run();
}
LocalPolicyTestServer test_server_;
scoped_refptr<const extensions::Extension> extension_;
std::unique_ptr<ExtensionTestMessageListener> event_listener_;
CloudPolicyClient* client_ = nullptr;
};
IN_PROC_BROWSER_TEST_F(ComponentCloudPolicyTest, FetchExtensionPolicy) {
// Read the initial policy.
ExtensionTestMessageListener policy_listener(kTestPolicyJSON, false);
event_listener_->Reply("get-policy-Name");
EXPECT_TRUE(policy_listener.WaitUntilSatisfied());
}
IN_PROC_BROWSER_TEST_F(ComponentCloudPolicyTest, UpdateExtensionPolicy) {
// Read the initial policy.
ExtensionTestMessageListener policy_listener(kTestPolicyJSON, true);
event_listener_->Reply("get-policy-Name");
EXPECT_TRUE(policy_listener.WaitUntilSatisfied());
// Update the policy at the server and reload policy.
event_listener_.reset(new ExtensionTestMessageListener("event", true));
policy_listener.Reply("idle");
EXPECT_TRUE(test_server_.UpdatePolicyData(
dm_protocol::kChromeExtensionPolicyType, kTestExtension, kTestPolicy2));
RefreshPolicies();
// Check that the update event was received, and verify the new policy
// values.
EXPECT_TRUE(event_listener_->WaitUntilSatisfied());
// This policy was removed.
ExtensionTestMessageListener policy_listener1("{}", true);
event_listener_->Reply("get-policy-Name");
EXPECT_TRUE(policy_listener1.WaitUntilSatisfied());
ExtensionTestMessageListener policy_listener2(kTestPolicy2JSON, false);
policy_listener1.Reply("get-policy-Another");
EXPECT_TRUE(policy_listener2.WaitUntilSatisfied());
}
// Flaky on Mac. http://crbug.com/816647
#if defined(OS_MACOSX)
#define MAYBE_InstallNewExtension DISABLED_InstallNewExtension
#else
#define MAYBE_InstallNewExtension InstallNewExtension
#endif
IN_PROC_BROWSER_TEST_F(ComponentCloudPolicyTest, MAYBE_InstallNewExtension) {
event_listener_->Reply("idle");
event_listener_.reset();
EXPECT_TRUE(test_server_.UpdatePolicyData(
dm_protocol::kChromeExtensionPolicyType, kTestExtension2, kTestPolicy2));
// Installing a new extension doesn't trigger another policy fetch because
// the server always sends down the list of all extensions that have policy.
// Fetch now that the configuration has been updated and before installing
// the extension.
RefreshPolicies();
ExtensionTestMessageListener result_listener("ok", false);
result_listener.set_failure_message("fail");
scoped_refptr<const extensions::Extension> extension2 =
LoadExtension(kTestExtension2Path);
ASSERT_TRUE(extension2.get());
ASSERT_EQ(kTestExtension2, extension2->id());
// This extension only sends the 'policy' signal once it receives the policy,
// and after verifying it has the expected value. Otherwise it sends 'fail'.
EXPECT_TRUE(result_listener.WaitUntilSatisfied());
}
// Signing out on Chrome OS is a different process from signing out on the
// Desktop platforms. On Chrome OS the session is ended, and the user goes back
// to the sign-in screen; the Profile data is not affected. On the Desktop the
// session goes on though, and all the signed-in services are disconnected;
// in particular, the policy caches are dropped if the user signs out.
// This test verifies that when the user signs out then any existing component
// policy caches are dropped, and that it's still possible to sign back in and
// get policy for components working again.
#if !defined(OS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(ComponentCloudPolicyTest, SignOutAndBackIn) {
// Read the initial policy.
ExtensionTestMessageListener initial_policy_listener(kTestPolicyJSON, true);
event_listener_->Reply("get-policy-Name");
EXPECT_TRUE(initial_policy_listener.WaitUntilSatisfied());
// Verify that the policy cache exists.
std::string cache_key;
base::Base64UrlEncode(
"extension-policy", base::Base64UrlEncodePolicy::INCLUDE_PADDING,
&cache_key);
std::string cache_subkey;
base::Base64UrlEncode(
kTestExtension, base::Base64UrlEncodePolicy::INCLUDE_PADDING,
&cache_subkey);
base::ScopedAllowBlockingForTesting allow_blocking;
base::FilePath cache_path = browser()->profile()->GetPath()
.Append(FILE_PATH_LITERAL("Policy"))
.Append(FILE_PATH_LITERAL("Components"))
.AppendASCII(cache_key)
.AppendASCII(cache_subkey);
EXPECT_TRUE(base::PathExists(cache_path));
// Now sign-out. The policy cache should be removed, and the extension should
// get an empty policy update.
ExtensionTestMessageListener event_listener("event", true);
initial_policy_listener.Reply("idle");
SignOut();
EXPECT_TRUE(event_listener.WaitUntilSatisfied());
// The extension got an update event; verify that the policy was empty.
ExtensionTestMessageListener signout_policy_listener("{}", false);
event_listener.Reply("get-policy-Name");
EXPECT_TRUE(signout_policy_listener.WaitUntilSatisfied());
// Verify that the cache is gone.
EXPECT_FALSE(base::PathExists(cache_path));
// Verify that the policy is fetched again if the user signs back in.
ExtensionTestMessageListener event_listener2("event", true);
SignInAndRegister();
EXPECT_TRUE(event_listener2.WaitUntilSatisfied());
// The extension got updated policy; verify it.
ExtensionTestMessageListener signin_policy_listener(kTestPolicyJSON, false);
event_listener2.Reply("get-policy-Name");
EXPECT_TRUE(signin_policy_listener.WaitUntilSatisfied());
// And the cache is back.
EXPECT_TRUE(base::PathExists(cache_path));
}
#endif
// Test of the component cloud policy when the policy test server is configured
// to perform the signing key rotation for each policy fetch.
class KeyRotationComponentCloudPolicyTest : public ComponentCloudPolicyTest {
protected:
void SetUpInProcessBrowserTestFixture() override {
test_server_.EnableAutomaticRotationOfSigningKeys();
ComponentCloudPolicyTest::SetUpInProcessBrowserTestFixture();
}
int GetFetchedPolicyPublicKeyVersion(const std::string& extension_id) {
const em::PolicyFetchResponse* fetched_policy = client_->GetPolicyFor(
dm_protocol::kChromeExtensionPolicyType, extension_id);
if (!fetched_policy || !fetched_policy->has_policy_data())
return -1;
em::PolicyData policy_data;
if (!policy_data.ParseFromString(fetched_policy->policy_data()) ||
!policy_data.has_public_key_version())
return -1;
return policy_data.public_key_version();
}
};
IN_PROC_BROWSER_TEST_F(KeyRotationComponentCloudPolicyTest, Basic) {
// Read the initial policy.
ExtensionTestMessageListener policy_listener(kTestPolicyJSON, true);
event_listener_->Reply("get-policy-Name");
EXPECT_TRUE(policy_listener.WaitUntilSatisfied());
const int public_key_version =
GetFetchedPolicyPublicKeyVersion(kTestExtension);
EXPECT_NE(-1, public_key_version);
// Update the policy at the server and reload the policy, causing also the key
// rotation to be performed by the policy test server.
event_listener_.reset(new ExtensionTestMessageListener("event", true));
policy_listener.Reply("idle");
EXPECT_TRUE(test_server_.UpdatePolicyData(
dm_protocol::kChromeExtensionPolicyType, kTestExtension, kTestPolicy2));
RefreshPolicies();
// Check that the update event was received, and verify that the policy has
// the new value and that the key rotation happened.
EXPECT_TRUE(event_listener_->WaitUntilSatisfied());
const int new_public_key_version =
GetFetchedPolicyPublicKeyVersion(kTestExtension);
EXPECT_LT(public_key_version, new_public_key_version);
ExtensionTestMessageListener policy_listener1("{}", true);
event_listener_->Reply("get-policy-Name");
EXPECT_TRUE(policy_listener1.WaitUntilSatisfied());
ExtensionTestMessageListener policy_listener2(kTestPolicy2JSON, false);
policy_listener1.Reply("get-policy-Another");
EXPECT_TRUE(policy_listener2.WaitUntilSatisfied());
}
} // namespace policy