blob: 3b0de91a6d4d98023b3908d611713da1cdbf67b7 [file] [log] [blame]
// Copyright 2020 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 "chrome/browser/device_api/managed_configuration_api.h"
#include "base/containers/contains.h"
#include "chrome/browser/device_api/managed_configuration_api_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/web_applications/policy/web_app_policy_constants.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/prefs/pref_service.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/browser_test.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"
namespace {
const char kOrigin[] = "https://example.com";
const char kConfigurationUrl1[] = "/conf1.json";
const char kConfigurationUrl2[] = "/conf2.json";
const char kConfigurationHash1[] = "asdas9jasidjd";
const char kConfigurationHash2[] = "ghi289sdfsdfk";
const char kConfigurationData1[] = R"(
{
"key1": "value1",
"key2" : 2
}
)";
const char kConfigurationData2[] = R"(
{
"key1": "value_1",
"key2" : "value_2"
}
)";
const char kConfigurationData3[] = R"(
[1]
)";
const char kKey1[] = "key1";
const char kKey2[] = "key2";
const char kKey3[] = "key3";
const char kValue1[] = "\"value1\"";
const char kValue2[] = "2";
const char kValue12[] = "\"value_1\"";
const char kValue22[] = "\"value_2\"";
struct ResponseTemplate {
std::string response_body;
bool should_post_task = false;
};
std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
std::map<std::string, ResponseTemplate> templates,
const net::test_server::HttpRequest& request) {
if (!base::Contains(templates, request.relative_url))
return std::make_unique<net::test_server::HungResponse>();
auto response_template = templates[request.relative_url];
std::unique_ptr<net::test_server::BasicHttpResponse> http_response;
if (response_template.should_post_task) {
http_response = std::make_unique<net::test_server::DelayedHttpResponse>(
base::Seconds(0));
} else {
http_response = std::make_unique<net::test_server::BasicHttpResponse>();
}
http_response->set_code(net::HTTP_OK);
http_response->set_content(response_template.response_body);
http_response->set_content_type("text/plain");
return http_response;
}
bool DictValueEquals(std::unique_ptr<base::DictionaryValue> value,
std::map<std::string, std::string> expected) {
std::map<std::string, std::string> actual;
for (auto entry : value->DictItems()) {
if (!entry.second.is_string())
return false;
actual.insert({entry.first, entry.second.GetString()});
}
return actual == expected;
}
} // namespace
class ManagedConfigurationAPITest : public InProcessBrowserTest,
public ManagedConfigurationAPI::Observer {
public:
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
origin_ = url::Origin::Create(GURL(kOrigin));
api()->AddObserver(this);
}
void TearDownOnMainThread() override {
api()->RemoveObserver(this);
InProcessBrowserTest::TearDownOnMainThread();
}
void EnableTestServer(
const std::map<std::string, ResponseTemplate> templates) {
embedded_test_server()->RegisterRequestHandler(
base::BindRepeating(&HandleRequest, templates));
ASSERT_TRUE(embedded_test_server()->Start());
}
void SetConfiguration(const std::string& conf_url,
const std::string& conf_hash) {
auto trusted_apps = std::make_unique<base::ListValue>();
auto entry = std::make_unique<base::DictionaryValue>();
entry->SetString(ManagedConfigurationAPI::kOriginKey, kOrigin);
entry->SetString(ManagedConfigurationAPI::kManagedConfigurationUrlKey,
embedded_test_server()->GetURL(conf_url).spec());
entry->SetString(ManagedConfigurationAPI::kManagedConfigurationHashKey,
conf_hash);
trusted_apps->Append(std::move(entry));
profile()->GetPrefs()->Set(prefs::kManagedConfigurationPerOrigin,
*trusted_apps);
}
void ClearConfiguration() {
profile()->GetPrefs()->Set(prefs::kManagedConfigurationPerOrigin,
base::ListValue());
}
void WaitForUpdate() {
if (!updated_) {
loop_update_ = std::make_unique<base::RunLoop>();
loop_update_->Run();
}
}
std::unique_ptr<base::DictionaryValue> GetValues(
const std::vector<std::string>& keys) {
updated_ = false;
api()->GetOriginPolicyConfiguration(
origin_, keys,
base::BindOnce(&ManagedConfigurationAPITest::OnResultObtained,
base::Unretained(this)));
// We could receive a failure asynchrounously.
if (!updated_) {
loop_get_ = std::make_unique<base::RunLoop>();
loop_get_->Run();
updated_ = false;
}
return std::move(result_);
}
void OnResultObtained(std::unique_ptr<base::DictionaryValue> result) {
updated_ = true;
result_ = std::move(result);
loop_get_->Quit();
}
void OnManagedConfigurationChanged() override {
if (loop_update_ && loop_update_->running()) {
loop_update_->Quit();
updated_ = false;
} else {
updated_ = true;
}
}
const url::Origin& GetOrigin() override { return origin(); }
ManagedConfigurationAPI* api() {
return ManagedConfigurationAPIFactory::GetForProfile(profile());
}
Profile* profile() { return browser()->profile(); }
const url::Origin& origin() { return origin_; }
private:
url::Origin origin_;
bool updated_ = false;
std::unique_ptr<base::RunLoop> loop_update_;
std::unique_ptr<base::RunLoop> loop_get_;
std::unique_ptr<base::DictionaryValue> result_;
};
IN_PROC_BROWSER_TEST_F(ManagedConfigurationAPITest,
PRE_DataIsDownloadedAndPersists) {
EnableTestServer({{kConfigurationUrl1, {kConfigurationData1}}});
SetConfiguration(kConfigurationUrl1, kConfigurationHash1);
WaitForUpdate();
ASSERT_TRUE(DictValueEquals(GetValues({kKey1, kKey2}),
{{kKey1, kValue1}, {kKey2, kValue2}}));
}
IN_PROC_BROWSER_TEST_F(ManagedConfigurationAPITest,
DataIsDownloadedAndPersists) {
// Intentionally do not handle requests so that data has to be read from
// disk.
EnableTestServer({});
SetConfiguration(kConfigurationUrl1, kConfigurationHash1);
ASSERT_TRUE(DictValueEquals(GetValues({kKey1, kKey2}),
{{kKey1, kValue1}, {kKey2, kValue2}}));
}
IN_PROC_BROWSER_TEST_F(ManagedConfigurationAPITest, AppRemovedFromPolicyList) {
EnableTestServer({{kConfigurationUrl1, {kConfigurationData1}}});
SetConfiguration(kConfigurationUrl1, kConfigurationHash1);
WaitForUpdate();
ASSERT_TRUE(DictValueEquals(GetValues({kKey1, kKey2}),
{{kKey1, kValue1}, {kKey2, kValue2}}));
ClearConfiguration();
WaitForUpdate();
ASSERT_EQ(GetValues({kKey1, kKey2}), nullptr);
}
IN_PROC_BROWSER_TEST_F(ManagedConfigurationAPITest, UnknownKeys) {
EnableTestServer({{kConfigurationUrl1, {kConfigurationData1}}});
SetConfiguration(kConfigurationUrl1, kConfigurationHash1);
WaitForUpdate();
ASSERT_TRUE(DictValueEquals(GetValues({kKey1, kKey2, kKey3}),
{{kKey1, kValue1}, {kKey2, kValue2}}));
}
IN_PROC_BROWSER_TEST_F(ManagedConfigurationAPITest, DataUpdates) {
EnableTestServer({{kConfigurationUrl1, {kConfigurationData1}},
{kConfigurationUrl2, {kConfigurationData2}}});
SetConfiguration(kConfigurationUrl1, kConfigurationHash1);
WaitForUpdate();
ASSERT_TRUE(DictValueEquals(GetValues({kKey1, kKey2}),
{{kKey1, kValue1}, {kKey2, kValue2}}));
SetConfiguration(kConfigurationUrl2, kConfigurationHash2);
WaitForUpdate();
ASSERT_TRUE(DictValueEquals(GetValues({kKey1, kKey2}),
{{kKey1, kValue12}, {kKey2, kValue22}}));
}
IN_PROC_BROWSER_TEST_F(ManagedConfigurationAPITest,
PolicyUpdateWhileDownloadingDifferentHash) {
EnableTestServer(
{{kConfigurationUrl1, {kConfigurationData1, true /* post_task */}},
{kConfigurationUrl2, {kConfigurationData2}}});
SetConfiguration(kConfigurationUrl1, kConfigurationHash1);
SetConfiguration(kConfigurationUrl2, kConfigurationHash2);
// Even though both requests were sent, the first one must be canceled,
// since the configuration hash has changed.
WaitForUpdate();
ASSERT_TRUE(DictValueEquals(GetValues({kKey1, kKey2}),
{{kKey1, kValue12}, {kKey2, kValue22}}));
}
IN_PROC_BROWSER_TEST_F(ManagedConfigurationAPITest,
PolicyUpdateWhileDownloadingSameHash) {
EnableTestServer(
{{kConfigurationUrl1, {kConfigurationData1, true /* post_task */}},
{kConfigurationUrl2, {kConfigurationData2}}});
SetConfiguration(kConfigurationUrl1, kConfigurationHash1);
SetConfiguration(kConfigurationUrl2, kConfigurationHash1);
// The second request should not be sent, since the hash did not change.
WaitForUpdate();
ASSERT_TRUE(DictValueEquals(GetValues({kKey1, kKey2}),
{{kKey1, kValue1}, {kKey2, kValue2}}));
}
IN_PROC_BROWSER_TEST_F(ManagedConfigurationAPITest,
NonDictionaryConfiguration) {
EnableTestServer({{kConfigurationUrl1, {kConfigurationData3}}});
SetConfiguration(kConfigurationUrl1, kConfigurationHash1);
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(DictValueEquals(GetValues({kKey1, kKey2}), {}));
}