| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "ash/constants/ash_switches.h" |
| #include "base/check.h" |
| #include "base/command_line.h" |
| #include "base/files/dir_reader_posix.h" |
| #include "base/files/file_path.h" |
| #include "base/functional/bind.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/run_loop.h" |
| #include "base/values.h" |
| #include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h" |
| #include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h" |
| #include "chrome/browser/ash/policy/core/device_cloud_policy_store_ash.h" |
| #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h" |
| #include "chrome/browser/ash/settings/device_settings_service.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/browser_process_platform_part.h" |
| #include "chrome/browser/extensions/chrome_test_extension_loader.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/common/chrome_constants.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chromeos/ash/components/dbus/session_manager/fake_session_manager_client.h" |
| #include "components/ownership/owner_key_util.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/device_management_service.h" |
| #include "components/policy/core/common/cloud/test/policy_builder.h" |
| #include "components/policy/core/common/policy_service.h" |
| #include "components/policy/core/common/policy_switches.h" |
| #include "components/policy/policy_constants.h" |
| #include "components/policy/proto/chrome_device_policy.pb.h" |
| #include "components/policy/proto/chrome_extension_policy.pb.h" |
| #include "components/policy/proto/device_management_backend.pb.h" |
| #include "content/public/test/browser_test.h" |
| #include "crypto/rsa_private_key.h" |
| #include "crypto/sha2.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/extension_system.h" |
| #include "extensions/browser/updater/extension_downloader_test_helper.h" |
| #include "extensions/common/constants.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/switches.h" |
| #include "extensions/test/result_catcher.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "net/test/embedded_test_server/http_request.h" |
| #include "net/test/embedded_test_server/http_response.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "url/gurl.h" |
| |
| namespace policy { |
| |
| namespace { |
| |
| // Tests for the rotation of the signing keys used for the device policy. |
| // |
| // The test is performed against a test policy server, which is set up for |
| // rotating the policy key automatically with each policy fetch. |
| class KeyRotationDeviceCloudPolicyTest : public DevicePolicyCrosBrowserTest { |
| public: |
| KeyRotationDeviceCloudPolicyTest(const KeyRotationDeviceCloudPolicyTest&) = |
| delete; |
| KeyRotationDeviceCloudPolicyTest& operator=( |
| const KeyRotationDeviceCloudPolicyTest&) = delete; |
| |
| protected: |
| const int kInitialPolicyValue = 123; |
| const int kSecondPolicyValue = 456; |
| const int kThirdPolicyValue = 789; |
| const char* const kPolicyKey = key::kDevicePolicyRefreshRate; |
| |
| KeyRotationDeviceCloudPolicyTest() { |
| UpdateBuiltTestPolicyValue(kInitialPolicyValue); |
| } |
| |
| void SetUpInProcessBrowserTestFixture() override { |
| DevicePolicyCrosBrowserTest::SetUpInProcessBrowserTestFixture(); |
| SetFakeDevicePolicy(); |
| UpdateServedTestPolicy(); |
| } |
| |
| void SetUpOnMainThread() override { |
| DevicePolicyCrosBrowserTest::SetUpOnMainThread(); |
| StartObservingTestPolicy(); |
| } |
| |
| void TearDownOnMainThread() override { |
| policy_change_registrar_.reset(); |
| DevicePolicyCrosBrowserTest::TearDownOnMainThread(); |
| } |
| |
| void UpdateBuiltTestPolicyValue(int test_policy_value) { |
| device_policy() |
| ->payload() |
| .mutable_device_policy_refresh_rate() |
| ->set_device_policy_refresh_rate(test_policy_value); |
| device_policy()->Build(); |
| } |
| |
| void UpdateServedTestPolicy() { |
| policy_test_server_mixin_.UpdateDevicePolicy(device_policy()->payload()); |
| } |
| |
| void StartDevicePolicyRefresh() { |
| g_browser_process->platform_part() |
| ->browser_policy_connector_ash() |
| ->GetDeviceCloudPolicyManager() |
| ->RefreshPolicies(); |
| } |
| |
| std::string GetOwnerPublicKey() const { |
| return ash::DeviceSettingsService::Get()->GetPublicKey()->as_string(); |
| } |
| |
| int GetInstalledPolicyKeyVersion() const { |
| return g_browser_process->platform_part() |
| ->browser_policy_connector_ash() |
| ->GetDeviceCloudPolicyManager() |
| ->device_store() |
| ->policy() |
| ->public_key_version(); |
| } |
| |
| int GetInstalledPolicyValue() { |
| PolicyService* const policy_service = g_browser_process->platform_part() |
| ->browser_policy_connector_ash() |
| ->GetPolicyService(); |
| const base::Value* policy_value = |
| policy_service |
| ->GetPolicies(PolicyNamespace(POLICY_DOMAIN_CHROME, |
| std::string() /* component_id */)) |
| .GetValue(kPolicyKey, base::Value::Type::INTEGER); |
| EXPECT_TRUE(policy_value); |
| EXPECT_EQ(policy_value->type(), base::Value::Type::INTEGER); |
| return policy_value->GetIfInt().value_or(-1); |
| } |
| |
| void WaitForInstalledPolicyValue(int expected_policy_value) { |
| if (GetInstalledPolicyValue() == expected_policy_value) |
| return; |
| awaited_policy_value_ = expected_policy_value; |
| // The run loop will be terminated by OnPolicyChanged() once the policy |
| // value becomes equal to the awaited value. |
| DCHECK(!policy_change_waiting_run_loop_); |
| policy_change_waiting_run_loop_ = std::make_unique<base::RunLoop>(); |
| policy_change_waiting_run_loop_->Run(); |
| policy_change_waiting_run_loop_.reset(); |
| } |
| |
| private: |
| void SetFakeDevicePolicy() { |
| device_policy() |
| ->payload() |
| .mutable_device_policy_refresh_rate() |
| ->set_device_policy_refresh_rate(kInitialPolicyValue); |
| device_policy()->Build(); |
| session_manager_client()->set_device_policy(device_policy()->GetBlob()); |
| } |
| |
| void StartObservingTestPolicy() { |
| policy_change_registrar_ = std::make_unique<PolicyChangeRegistrar>( |
| g_browser_process->platform_part() |
| ->browser_policy_connector_ash() |
| ->GetPolicyService(), |
| PolicyNamespace(POLICY_DOMAIN_CHROME, |
| std::string() /* component_id */)); |
| policy_change_registrar_->Observe( |
| kPolicyKey, |
| base::BindRepeating(&KeyRotationDeviceCloudPolicyTest::OnPolicyChanged, |
| base::Unretained(this))); |
| } |
| |
| void OnPolicyChanged(const base::Value* old_value, |
| const base::Value* new_value) { |
| if (policy_change_waiting_run_loop_ && |
| GetInstalledPolicyValue() == awaited_policy_value_) { |
| policy_change_waiting_run_loop_->Quit(); |
| } |
| } |
| |
| ash::EmbeddedPolicyTestServerMixin policy_test_server_mixin_{ |
| &mixin_host_, |
| {ash::EmbeddedPolicyTestServerMixin::ENABLE_CANNED_SIGNING_KEYS, |
| ash::EmbeddedPolicyTestServerMixin:: |
| ENABLE_AUTOMATIC_ROTATION_OF_SIGNINGKEYS}}; |
| std::unique_ptr<PolicyChangeRegistrar> policy_change_registrar_; |
| int awaited_policy_value_ = -1; |
| std::unique_ptr<base::RunLoop> policy_change_waiting_run_loop_; |
| }; |
| |
| } // namespace |
| |
| IN_PROC_BROWSER_TEST_F(KeyRotationDeviceCloudPolicyTest, Basic) { |
| // The policy has the initial value from the cache. |
| EXPECT_EQ(kInitialPolicyValue, GetInstalledPolicyValue()); |
| |
| // The server is updated to serve the second policy value, and the client |
| // fetches it. |
| UpdateBuiltTestPolicyValue(kSecondPolicyValue); |
| UpdateServedTestPolicy(); |
| StartDevicePolicyRefresh(); |
| WaitForInstalledPolicyValue(kSecondPolicyValue); |
| EXPECT_EQ(kSecondPolicyValue, GetInstalledPolicyValue()); |
| |
| // Remember the key and the version after the fetch of the second value. |
| const std::string owner_public_key = GetOwnerPublicKey(); |
| CHECK(!owner_public_key.empty()); |
| const int key_version = GetInstalledPolicyKeyVersion(); |
| |
| // The server is updated to serve the third policy value, and the client |
| // fetches it. |
| UpdateBuiltTestPolicyValue(kThirdPolicyValue); |
| UpdateServedTestPolicy(); |
| StartDevicePolicyRefresh(); |
| WaitForInstalledPolicyValue(kThirdPolicyValue); |
| EXPECT_EQ(kThirdPolicyValue, GetInstalledPolicyValue()); |
| |
| // The owner key got rotated on the client, as requested by the server, and |
| // the key version got incremented. |
| EXPECT_NE(owner_public_key, GetOwnerPublicKey()); |
| EXPECT_EQ(key_version + 1, GetInstalledPolicyKeyVersion()); |
| } |
| |
| namespace { |
| |
| // Tests how component policy is handled for extensions installed on the sign-in |
| // screen. |
| class SigninExtensionsDeviceCloudPolicyBrowserTest |
| : public DevicePolicyCrosBrowserTest { |
| public: |
| static constexpr const char* kTestExtensionId = |
| "hifnmfgfdfhmoaponfpmnlpeahiomjim"; |
| static constexpr const char* kTestExtensionPath = |
| "extensions/signin_screen_managed_storage/extension.crx"; |
| static constexpr const char* kTestExtensionUpdateManifestPath = |
| "/extensions/signin_screen_managed_storage/update_manifest.xml"; |
| static constexpr const char* kFakePolicyPath = "/test-policy.json"; |
| static constexpr const char* kFakePolicy = |
| "{\"string-policy\": {\"Value\": \"value\"}}"; |
| static constexpr int kFakePolicyPublicKeyVersion = 1; |
| |
| SigninExtensionsDeviceCloudPolicyBrowserTest() = default; |
| |
| SigninExtensionsDeviceCloudPolicyBrowserTest( |
| const SigninExtensionsDeviceCloudPolicyBrowserTest&) = delete; |
| SigninExtensionsDeviceCloudPolicyBrowserTest& operator=( |
| const SigninExtensionsDeviceCloudPolicyBrowserTest&) = delete; |
| |
| ~SigninExtensionsDeviceCloudPolicyBrowserTest() override = default; |
| |
| void SetUp() override { |
| ASSERT_TRUE(embedded_test_server()->InitializeAndListen()); |
| DevicePolicyCrosBrowserTest::SetUp(); |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| DevicePolicyCrosBrowserTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitch(ash::switches::kLoginManager); |
| command_line->AppendSwitch(ash::switches::kForceLoginManagerInTests); |
| // The test app has to be allowlisted for sign-in screen. |
| command_line->AppendSwitchASCII( |
| extensions::switches::kAllowlistedExtensionID, kTestExtensionId); |
| } |
| |
| void SetUpInProcessBrowserTestFixture() override { |
| DevicePolicyCrosBrowserTest::SetUpInProcessBrowserTestFixture(); |
| SetFakeDevicePolicy(); |
| |
| policy_test_server_mixin_.UpdateDevicePolicy(device_policy()->payload()); |
| policy_test_server_mixin_.UpdatePolicy( |
| dm_protocol::kChromeSigninExtensionPolicyType, kTestExtensionId, |
| BuildTestComponentPolicyPayload().SerializeAsString()); |
| } |
| |
| void SetUpOnMainThread() override { |
| DevicePolicyCrosBrowserTest::SetUpOnMainThread(); |
| |
| BrowserPolicyConnectorAsh* connector = |
| g_browser_process->platform_part()->browser_policy_connector_ash(); |
| connector->device_management_service()->ScheduleInitialization(0); |
| } |
| |
| // |hang_component_policy_fetch| - whether requests for the component policy |
| // download should be hung indefinitely. |
| void StartTestServer(bool hang_component_policy_fetch) { |
| embedded_test_server()->RegisterRequestHandler(base::BindRepeating( |
| &SigninExtensionsDeviceCloudPolicyBrowserTest::InterceptComponentPolicy, |
| base::Unretained(this), hang_component_policy_fetch)); |
| embedded_test_server()->RegisterRequestHandler(base::BindRepeating( |
| &SigninExtensionsDeviceCloudPolicyBrowserTest::InterceptUpdateManifest, |
| base::Unretained(this))); |
| embedded_test_server()->StartAcceptingConnections(); |
| } |
| |
| private: |
| // Intercepts the request for the test extension update manifest. |
| std::unique_ptr<net::test_server::HttpResponse> InterceptUpdateManifest( |
| const net::test_server::HttpRequest& request) { |
| if (request.GetURL().path() != kTestExtensionUpdateManifestPath) |
| return nullptr; |
| |
| // Create update manifest for the test extension, setting the extension URL |
| // with a test server URL pointing to the extension under the test data |
| // path. |
| std::string manifest_response = extensions::CreateUpdateManifest( |
| {extensions::UpdateManifestItem(kTestExtensionId) |
| .version("1.0") |
| .codebase(base::ReplaceStringPlaceholders( |
| "http://$1/$2", |
| {embedded_test_server()->host_port_pair().ToString(), |
| kTestExtensionPath}, |
| nullptr))}); |
| |
| auto response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_content_type("text/xml"); |
| response->set_content(manifest_response); |
| return response; |
| } |
| |
| // Intercepts the component policy requests. |
| // |hang| - if set, this will return a hung response, thus preventing the |
| // policy download. Otherwise, the response will contain the test policy. |
| std::unique_ptr<net::test_server::HttpResponse> InterceptComponentPolicy( |
| bool hang, |
| const net::test_server::HttpRequest& request) { |
| if (request.relative_url != kFakePolicyPath) |
| return nullptr; |
| |
| if (hang) |
| return std::make_unique<net::test_server::HungResponse>(); |
| |
| auto response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_content(kFakePolicy); |
| return response; |
| } |
| |
| void SetFakeDevicePolicy() { |
| device_policy()->policy_data().set_public_key_version( |
| kFakePolicyPublicKeyVersion); |
| |
| const GURL update_manifest_url = |
| embedded_test_server()->GetURL(kTestExtensionUpdateManifestPath); |
| const std::string policy_item_value = base::ReplaceStringPlaceholders( |
| "$1;$2", {kTestExtensionId, update_manifest_url.spec()}, nullptr); |
| |
| device_policy() |
| ->payload() |
| .mutable_device_login_screen_extensions() |
| ->add_device_login_screen_extensions(policy_item_value); |
| |
| device_policy()->Build(); |
| session_manager_client()->set_device_policy(device_policy()->GetBlob()); |
| } |
| |
| enterprise_management::ExternalPolicyData BuildTestComponentPolicyPayload() { |
| ComponentCloudPolicyBuilder builder; |
| MakeTestComponentPolicyBuilder(&builder); |
| return builder.payload(); |
| } |
| |
| void MakeTestComponentPolicyBuilder(ComponentCloudPolicyBuilder* builder) { |
| builder->policy_data().set_policy_type( |
| dm_protocol::kChromeSigninExtensionPolicyType); |
| builder->policy_data().set_settings_entity_id(kTestExtensionId); |
| builder->policy_data().set_public_key_version(kFakePolicyPublicKeyVersion); |
| builder->payload().set_download_url( |
| embedded_test_server()->GetURL(kFakePolicyPath).spec()); |
| builder->payload().set_secure_hash(crypto::SHA256HashString(kFakePolicy)); |
| builder->Build(); |
| } |
| |
| ash::EmbeddedPolicyTestServerMixin policy_test_server_mixin_{&mixin_host_}; |
| }; |
| |
| } // namespace |
| |
| // The ManagedStorage test is done in two steps: |
| // 1. Test that fetches the component policy and verifies that the fetched |
| // policy is exposed to a test extension installed into the sign-in profile. |
| // 2. Test that blocks the component policy fetch, and verifies that a test |
| // extension installed into the sign-in profile can access the component |
| // policy downloaded during the first step. |
| // PRE_ManagedStorage test handles the first step. |
| IN_PROC_BROWSER_TEST_F(SigninExtensionsDeviceCloudPolicyBrowserTest, |
| PRE_ManagedStorage) { |
| // The test app will be installed via policy, at which point its |
| // background page will be loaded. |
| extensions::ResultCatcher result_catcher; |
| StartTestServer(false /*hang_component_policy_fetch*/); |
| EXPECT_TRUE(result_catcher.GetNextResult()); |
| } |
| |
| // The second step of the ManagedStorage test, which blocks component policy |
| // download and verifies that a cached component policy is available to the test |
| // extenion. |
| // See PRE_ManagedStorage test. |
| IN_PROC_BROWSER_TEST_F(SigninExtensionsDeviceCloudPolicyBrowserTest, |
| ManagedStorage) { |
| // The test app will be installed via policy, at which point its |
| // background page will be loaded. Note that the app will not be installed |
| // before the test server is started, even if the app is installed from the |
| // extension cache - the server will be pinged at least to check whether the |
| // cached app version is the latest. |
| extensions::ResultCatcher result_catcher; |
| StartTestServer(true /*hang_component_policy_fetch*/); |
| EXPECT_TRUE(result_catcher.GetNextResult()); |
| } |
| |
| } // namespace policy |