blob: 850048c7b12fa99c5421fb7cb12a27f6289de8e1 [file] [log] [blame]
// Copyright 2015 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 "base/basictypes.h"
#include "base/bind.h"
#include "base/json/json_writer.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/synchronization/waitable_event.h"
#include "components/policy/core/common/fake_async_policy_loader.h"
#include "policy/policy_constants.h"
#include "remoting/host/dns_blackhole_checker.h"
#include "remoting/host/policy_watcher.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace remoting {
class MockPolicyCallback {
public:
MockPolicyCallback(){};
// TODO(lukasza): gmock cannot mock a method taking scoped_ptr<T>...
MOCK_METHOD1(OnPolicyUpdatePtr, void(const base::DictionaryValue* policies));
void OnPolicyUpdate(scoped_ptr<base::DictionaryValue> policies) {
OnPolicyUpdatePtr(policies.get());
}
MOCK_METHOD0(OnPolicyError, void());
private:
DISALLOW_COPY_AND_ASSIGN(MockPolicyCallback);
};
class PolicyWatcherTest : public testing::Test {
public:
PolicyWatcherTest() : message_loop_(base::MessageLoop::TYPE_IO) {}
void SetUp() override {
message_loop_proxy_ = base::MessageLoopProxy::current();
// Retaining a raw pointer to keep control over policy contents.
policy_loader_ = new policy::FakeAsyncPolicyLoader(message_loop_proxy_);
policy_watcher_ =
PolicyWatcher::CreateFromPolicyLoader(make_scoped_ptr(policy_loader_));
nat_true_.SetBoolean(policy::key::kRemoteAccessHostFirewallTraversal, true);
nat_false_.SetBoolean(policy::key::kRemoteAccessHostFirewallTraversal,
false);
nat_one_.SetInteger(policy::key::kRemoteAccessHostFirewallTraversal, 1);
domain_empty_.SetString(policy::key::kRemoteAccessHostDomain,
std::string());
domain_full_.SetString(policy::key::kRemoteAccessHostDomain, kHostDomain);
SetDefaults(nat_true_others_default_);
nat_true_others_default_.SetBoolean(
policy::key::kRemoteAccessHostFirewallTraversal, true);
SetDefaults(nat_false_others_default_);
nat_false_others_default_.SetBoolean(
policy::key::kRemoteAccessHostFirewallTraversal, false);
SetDefaults(domain_empty_others_default_);
domain_empty_others_default_.SetString(policy::key::kRemoteAccessHostDomain,
std::string());
SetDefaults(domain_full_others_default_);
domain_full_others_default_.SetString(policy::key::kRemoteAccessHostDomain,
kHostDomain);
nat_true_domain_empty_.SetBoolean(
policy::key::kRemoteAccessHostFirewallTraversal, true);
nat_true_domain_empty_.SetString(policy::key::kRemoteAccessHostDomain,
std::string());
nat_true_domain_full_.SetBoolean(
policy::key::kRemoteAccessHostFirewallTraversal, true);
nat_true_domain_full_.SetString(policy::key::kRemoteAccessHostDomain,
kHostDomain);
nat_false_domain_empty_.SetBoolean(
policy::key::kRemoteAccessHostFirewallTraversal, false);
nat_false_domain_empty_.SetString(policy::key::kRemoteAccessHostDomain,
std::string());
nat_false_domain_full_.SetBoolean(
policy::key::kRemoteAccessHostFirewallTraversal, false);
nat_false_domain_full_.SetString(policy::key::kRemoteAccessHostDomain,
kHostDomain);
SetDefaults(nat_true_domain_empty_others_default_);
nat_true_domain_empty_others_default_.SetBoolean(
policy::key::kRemoteAccessHostFirewallTraversal, true);
nat_true_domain_empty_others_default_.SetString(
policy::key::kRemoteAccessHostDomain, std::string());
unknown_policies_.SetString("UnknownPolicyOne", std::string());
unknown_policies_.SetString("UnknownPolicyTwo", std::string());
const char kOverrideNatTraversalToFalse[] =
"{ \"RemoteAccessHostFirewallTraversal\": false }";
nat_true_and_overridden_.SetBoolean(
policy::key::kRemoteAccessHostFirewallTraversal, true);
nat_true_and_overridden_.SetString(
policy::key::kRemoteAccessHostDebugOverridePolicies,
kOverrideNatTraversalToFalse);
pairing_true_.SetBoolean(policy::key::kRemoteAccessHostAllowClientPairing,
true);
pairing_false_.SetBoolean(policy::key::kRemoteAccessHostAllowClientPairing,
false);
gnubby_auth_true_.SetBoolean(policy::key::kRemoteAccessHostAllowGnubbyAuth,
true);
gnubby_auth_false_.SetBoolean(policy::key::kRemoteAccessHostAllowGnubbyAuth,
false);
relay_true_.SetBoolean(policy::key::kRemoteAccessHostAllowRelayedConnection,
true);
relay_false_.SetBoolean(
policy::key::kRemoteAccessHostAllowRelayedConnection, false);
port_range_full_.SetString(policy::key::kRemoteAccessHostUdpPortRange,
kPortRange);
port_range_empty_.SetString(policy::key::kRemoteAccessHostUdpPortRange,
std::string());
#if !defined(NDEBUG)
SetDefaults(nat_false_overridden_others_default_);
nat_false_overridden_others_default_.SetBoolean(
policy::key::kRemoteAccessHostFirewallTraversal, false);
nat_false_overridden_others_default_.SetString(
policy::key::kRemoteAccessHostDebugOverridePolicies,
kOverrideNatTraversalToFalse);
#endif
}
void TearDown() override {
policy_watcher_.reset();
policy_loader_ = nullptr;
base::RunLoop().RunUntilIdle();
}
protected:
void StartWatching() {
policy_watcher_->StartWatching(
base::Bind(&MockPolicyCallback::OnPolicyUpdate,
base::Unretained(&mock_policy_callback_)),
base::Bind(&MockPolicyCallback::OnPolicyError,
base::Unretained(&mock_policy_callback_)));
base::RunLoop().RunUntilIdle();
}
void SetPolicies(const base::DictionaryValue& dict) {
// Copy |dict| into |policy_bundle|.
policy::PolicyNamespace policy_namespace =
policy::PolicyNamespace(policy::POLICY_DOMAIN_CHROME, std::string());
policy::PolicyBundle policy_bundle;
policy::PolicyMap& policy_map = policy_bundle.Get(policy_namespace);
policy_map.LoadFrom(&dict, policy::POLICY_LEVEL_MANDATORY,
policy::POLICY_SCOPE_MACHINE);
// Simulate a policy file/registry/preference update.
policy_loader_->SetPolicies(policy_bundle);
policy_loader_->PostReloadOnBackgroundThread(true /* force reload asap */);
base::RunLoop().RunUntilIdle();
}
void SignalTransientErrorForTest() {
policy_watcher_->SignalTransientPolicyError();
}
MOCK_METHOD0(PostPolicyWatcherShutdown, void());
static const char* kHostDomain;
static const char* kPortRange;
base::MessageLoop message_loop_;
scoped_refptr<base::MessageLoopProxy> message_loop_proxy_;
MockPolicyCallback mock_policy_callback_;
// |policy_loader_| is owned by |policy_watcher_|. PolicyWatcherTest retains
// a raw pointer to |policy_loader_| in order to control the simulated / faked
// policy contents.
policy::FakeAsyncPolicyLoader* policy_loader_;
scoped_ptr<PolicyWatcher> policy_watcher_;
base::DictionaryValue empty_;
base::DictionaryValue nat_true_;
base::DictionaryValue nat_false_;
base::DictionaryValue nat_one_;
base::DictionaryValue domain_empty_;
base::DictionaryValue domain_full_;
base::DictionaryValue nat_true_others_default_;
base::DictionaryValue nat_false_others_default_;
base::DictionaryValue domain_empty_others_default_;
base::DictionaryValue domain_full_others_default_;
base::DictionaryValue nat_true_domain_empty_;
base::DictionaryValue nat_true_domain_full_;
base::DictionaryValue nat_false_domain_empty_;
base::DictionaryValue nat_false_domain_full_;
base::DictionaryValue nat_true_domain_empty_others_default_;
base::DictionaryValue unknown_policies_;
base::DictionaryValue nat_true_and_overridden_;
base::DictionaryValue nat_false_overridden_others_default_;
base::DictionaryValue pairing_true_;
base::DictionaryValue pairing_false_;
base::DictionaryValue gnubby_auth_true_;
base::DictionaryValue gnubby_auth_false_;
base::DictionaryValue relay_true_;
base::DictionaryValue relay_false_;
base::DictionaryValue port_range_full_;
base::DictionaryValue port_range_empty_;
private:
void SetDefaults(base::DictionaryValue& dict) {
dict.SetBoolean(policy::key::kRemoteAccessHostFirewallTraversal, true);
dict.SetBoolean(policy::key::kRemoteAccessHostAllowRelayedConnection, true);
dict.SetString(policy::key::kRemoteAccessHostUdpPortRange, "");
dict.SetBoolean(policy::key::kRemoteAccessHostRequireTwoFactor, false);
dict.SetString(policy::key::kRemoteAccessHostDomain, std::string());
dict.SetBoolean(policy::key::kRemoteAccessHostMatchUsername, false);
dict.SetString(policy::key::kRemoteAccessHostTalkGadgetPrefix,
kDefaultHostTalkGadgetPrefix);
dict.SetBoolean(policy::key::kRemoteAccessHostRequireCurtain, false);
dict.SetString(policy::key::kRemoteAccessHostTokenUrl, std::string());
dict.SetString(policy::key::kRemoteAccessHostTokenValidationUrl,
std::string());
dict.SetString(
policy::key::kRemoteAccessHostTokenValidationCertificateIssuer,
std::string());
dict.SetBoolean(policy::key::kRemoteAccessHostAllowClientPairing, true);
dict.SetBoolean(policy::key::kRemoteAccessHostAllowGnubbyAuth, true);
#if !defined(NDEBUG)
dict.SetString(policy::key::kRemoteAccessHostDebugOverridePolicies, "");
#endif
}
};
const char* PolicyWatcherTest::kHostDomain = "google.com";
const char* PolicyWatcherTest::kPortRange = "12400-12409";
MATCHER_P(IsPolicies, dict, "") {
bool equal = arg->Equals(dict);
if (!equal) {
std::string actual_value;
base::JSONWriter::WriteWithOptions(
arg, base::JSONWriter::OPTIONS_PRETTY_PRINT, &actual_value);
std::string expected_value;
base::JSONWriter::WriteWithOptions(
dict, base::JSONWriter::OPTIONS_PRETTY_PRINT, &expected_value);
*result_listener << "Policies are not equal. ";
*result_listener << "Expected policy: " << expected_value << ". ";
*result_listener << "Actual policy: " << actual_value << ".";
}
return equal;
}
TEST_F(PolicyWatcherTest, None) {
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&nat_true_others_default_)));
SetPolicies(empty_);
StartWatching();
}
TEST_F(PolicyWatcherTest, NatTrue) {
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&nat_true_others_default_)));
SetPolicies(nat_true_);
StartWatching();
}
TEST_F(PolicyWatcherTest, NatFalse) {
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&nat_false_others_default_)));
SetPolicies(nat_false_);
StartWatching();
}
TEST_F(PolicyWatcherTest, NatOne) {
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&nat_false_others_default_)));
SetPolicies(nat_one_);
StartWatching();
}
TEST_F(PolicyWatcherTest, DomainEmpty) {
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&domain_empty_others_default_)));
SetPolicies(domain_empty_);
StartWatching();
}
TEST_F(PolicyWatcherTest, DomainFull) {
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&domain_full_others_default_)));
SetPolicies(domain_full_);
StartWatching();
}
TEST_F(PolicyWatcherTest, NatNoneThenTrue) {
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&nat_true_others_default_)));
SetPolicies(empty_);
StartWatching();
SetPolicies(nat_true_);
}
TEST_F(PolicyWatcherTest, NatNoneThenTrueThenTrue) {
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&nat_true_others_default_)));
SetPolicies(empty_);
StartWatching();
SetPolicies(nat_true_);
SetPolicies(nat_true_);
}
TEST_F(PolicyWatcherTest, NatNoneThenTrueThenTrueThenFalse) {
testing::InSequence sequence;
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&nat_true_others_default_)));
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&nat_false_)));
SetPolicies(empty_);
StartWatching();
SetPolicies(nat_true_);
SetPolicies(nat_true_);
SetPolicies(nat_false_);
}
TEST_F(PolicyWatcherTest, NatNoneThenFalse) {
testing::InSequence sequence;
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&nat_true_others_default_)));
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&nat_false_)));
SetPolicies(empty_);
StartWatching();
SetPolicies(nat_false_);
}
TEST_F(PolicyWatcherTest, NatNoneThenFalseThenTrue) {
testing::InSequence sequence;
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&nat_true_others_default_)));
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&nat_false_)));
EXPECT_CALL(mock_policy_callback_, OnPolicyUpdatePtr(IsPolicies(&nat_true_)));
SetPolicies(empty_);
StartWatching();
SetPolicies(nat_false_);
SetPolicies(nat_true_);
}
TEST_F(PolicyWatcherTest, ChangeOneRepeatedlyThenTwo) {
testing::InSequence sequence;
EXPECT_CALL(
mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&nat_true_domain_empty_others_default_)));
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&domain_full_)));
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&nat_false_)));
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&domain_empty_)));
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&nat_true_domain_full_)));
SetPolicies(nat_true_domain_empty_);
StartWatching();
SetPolicies(nat_true_domain_full_);
SetPolicies(nat_false_domain_full_);
SetPolicies(nat_false_domain_empty_);
SetPolicies(nat_true_domain_full_);
}
TEST_F(PolicyWatcherTest, FilterUnknownPolicies) {
testing::InSequence sequence;
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&nat_true_others_default_)));
SetPolicies(empty_);
StartWatching();
SetPolicies(unknown_policies_);
SetPolicies(empty_);
}
TEST_F(PolicyWatcherTest, DebugOverrideNatPolicy) {
#if !defined(NDEBUG)
EXPECT_CALL(
mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&nat_false_overridden_others_default_)));
#else
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&nat_true_others_default_)));
#endif
SetPolicies(nat_true_and_overridden_);
StartWatching();
}
TEST_F(PolicyWatcherTest, PairingFalseThenTrue) {
testing::InSequence sequence;
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&nat_true_others_default_)));
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&pairing_false_)));
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&pairing_true_)));
SetPolicies(empty_);
StartWatching();
SetPolicies(pairing_false_);
SetPolicies(pairing_true_);
}
TEST_F(PolicyWatcherTest, GnubbyAuth) {
testing::InSequence sequence;
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&nat_true_others_default_)));
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&gnubby_auth_false_)));
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&gnubby_auth_true_)));
SetPolicies(empty_);
StartWatching();
SetPolicies(gnubby_auth_false_);
SetPolicies(gnubby_auth_true_);
}
TEST_F(PolicyWatcherTest, Relay) {
testing::InSequence sequence;
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&nat_true_others_default_)));
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&relay_false_)));
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&relay_true_)));
SetPolicies(empty_);
StartWatching();
SetPolicies(relay_false_);
SetPolicies(relay_true_);
}
TEST_F(PolicyWatcherTest, UdpPortRange) {
testing::InSequence sequence;
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&nat_true_others_default_)));
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&port_range_full_)));
EXPECT_CALL(mock_policy_callback_,
OnPolicyUpdatePtr(IsPolicies(&port_range_empty_)));
SetPolicies(empty_);
StartWatching();
SetPolicies(port_range_full_);
SetPolicies(port_range_empty_);
}
const int kMaxTransientErrorRetries = 5;
TEST_F(PolicyWatcherTest, SingleTransientErrorDoesntTriggerErrorCallback) {
EXPECT_CALL(mock_policy_callback_, OnPolicyError()).Times(0);
StartWatching();
SignalTransientErrorForTest();
}
TEST_F(PolicyWatcherTest, MultipleTransientErrorsTriggerErrorCallback) {
EXPECT_CALL(mock_policy_callback_, OnPolicyError());
StartWatching();
for (int i = 0; i < kMaxTransientErrorRetries; i++) {
SignalTransientErrorForTest();
}
}
TEST_F(PolicyWatcherTest, PolicyUpdateResetsTransientErrorsCounter) {
testing::InSequence s;
EXPECT_CALL(mock_policy_callback_, OnPolicyUpdatePtr(testing::_));
EXPECT_CALL(mock_policy_callback_, OnPolicyError()).Times(0);
StartWatching();
for (int i = 0; i < (kMaxTransientErrorRetries - 1); i++) {
SignalTransientErrorForTest();
}
SetPolicies(nat_true_);
for (int i = 0; i < (kMaxTransientErrorRetries - 1); i++) {
SignalTransientErrorForTest();
}
}
// Unit tests cannot instantiate PolicyWatcher on ChromeOS
// (as this requires running inside a browser process).
#ifndef OS_CHROMEOS
namespace {
void OnPolicyUpdatedDumpPolicy(scoped_ptr<base::DictionaryValue> policies) {
VLOG(1) << "OnPolicyUpdated callback received the following policies:";
for (base::DictionaryValue::Iterator iter(*policies); !iter.IsAtEnd();
iter.Advance()) {
switch (iter.value().GetType()) {
case base::Value::Type::TYPE_STRING: {
std::string value;
CHECK(iter.value().GetAsString(&value));
VLOG(1) << iter.key() << " = "
<< "string: " << '"' << value << '"';
break;
}
case base::Value::Type::TYPE_BOOLEAN: {
bool value;
CHECK(iter.value().GetAsBoolean(&value));
VLOG(1) << iter.key() << " = "
<< "boolean: " << (value ? "True" : "False");
break;
}
default: {
VLOG(1) << iter.key() << " = "
<< "unrecognized type";
break;
}
}
}
}
} // anonymous namespace
// To dump policy contents, run unit tests with the following flags:
// out/Debug/remoting_unittests --gtest_filter=*TestRealChromotingPolicy* -v=1
TEST_F(PolicyWatcherTest, TestRealChromotingPolicy) {
scoped_refptr<base::SingleThreadTaskRunner> task_runner =
base::MessageLoop::current()->task_runner();
scoped_ptr<PolicyWatcher> policy_watcher(
PolicyWatcher::Create(nullptr, task_runner));
{
base::RunLoop run_loop;
policy_watcher->StartWatching(base::Bind(OnPolicyUpdatedDumpPolicy),
base::Bind(base::DoNothing));
run_loop.RunUntilIdle();
}
// Today, the only verification offered by this test is:
// - Manual verification of policy values dumped by OnPolicyUpdatedDumpPolicy
// - Automated verification that nothing crashed
}
#endif
} // namespace remoting