blob: befb6e067143cd7668bfe7c70d2fc79cbb73380e [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/policy/messaging_layer/upload/upload_client.h"
#include "base/base64.h"
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/json/json_writer.h"
#include "base/test/task_environment.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/policy/messaging_layer/upload/record_handler_impl.h"
#include "components/account_id/account_id.h"
#include "components/policy/core/common/cloud/dm_token.h"
#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
#include "components/policy/proto/record.pb.h"
#include "components/policy/proto/record_constants.pb.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/browser_task_environment.h"
#include "services/network/test/test_network_connection_tracker.h"
#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
#include "chrome/test/base/testing_profile.h"
#include "components/user_manager/scoped_user_manager.h"
#endif // OS_CHROMEOS
namespace reporting {
namespace {
using ::policy::MockCloudPolicyClient;
using ::testing::_;
using ::testing::AllOf;
using ::testing::Gt;
using ::testing::Invoke;
using ::testing::InvokeArgument;
using ::testing::IsEmpty;
using ::testing::MockFunction;
using ::testing::Not;
using ::testing::Property;
using ::testing::StrictMock;
using ::testing::WithArgs;
MATCHER_P(EqualsProto,
message,
"Match a proto Message equal to the matcher's argument.") {
std::string expected_serialized, actual_serialized;
message.SerializeToString(&expected_serialized);
arg.SerializeToString(&actual_serialized);
return expected_serialized == actual_serialized;
}
// Usage (in tests only):
//
// TestEvent<ResType> e;
// ... Do some async work passing e.cb() as a completion callback of
// base::OnceCallback<void(ResType* res)> type which also may perform some
// other action specified by |done| callback provided by the caller.
// ... = e.result(); // Will wait for e.cb() to be called and return the
// collected result.
//
template <typename ResType>
class TestEvent {
public:
TestEvent() : run_loop_(std::make_unique<base::RunLoop>()) {}
~TestEvent() = default;
TestEvent(const TestEvent& other) = delete;
TestEvent& operator=(const TestEvent& other) = delete;
ResType result() {
run_loop_->Run();
return std::forward<ResType>(result_);
}
// Completion callback to hand over to the processing method.
base::OnceCallback<void(ResType res)> cb() {
return base::BindOnce(
[](base::RunLoop* run_loop, ResType* result, ResType res) {
*result = std::forward<ResType>(res);
run_loop->Quit();
},
base::Unretained(run_loop_.get()), base::Unretained(&result_));
}
private:
std::unique_ptr<base::RunLoop> run_loop_;
ResType result_;
};
class TestCallbackWaiter {
public:
TestCallbackWaiter() : run_loop_(std::make_unique<base::RunLoop>()) {}
virtual void Signal() { run_loop_->Quit(); }
void CompleteExpectSequencingInformation(SequencingInformation expected,
SequencingInformation info) {
EXPECT_THAT(info, EqualsProto(expected));
Signal();
}
void Wait() { run_loop_->Run(); }
protected:
std::unique_ptr<base::RunLoop> run_loop_;
};
class TestCallbackWaiterWithCounter : public TestCallbackWaiter {
public:
explicit TestCallbackWaiterWithCounter(size_t counter_limit)
: counter_limit_(counter_limit) {
DCHECK_GT(counter_limit, 0u);
}
void Signal() override {
const size_t old_count = counter_limit_.fetch_sub(1);
DCHECK_GT(old_count, 0u);
if (old_count > 1) {
return;
}
run_loop_->Quit();
}
private:
std::atomic<size_t> counter_limit_;
};
// Helper function composes JSON represented as base::Value from Sequencing
// information in request.
base::Value ValueFromSucceededSequencingInfo(
const base::Optional<base::Value> request) {
EXPECT_TRUE(request.has_value());
EXPECT_TRUE(request.value().is_dict());
base::Value response(base::Value::Type::DICTIONARY);
// Retrieve and process data
const base::Value* const encrypted_record_list =
request.value().FindListKey("encryptedRecord");
EXPECT_TRUE(encrypted_record_list != nullptr);
EXPECT_FALSE(encrypted_record_list->GetList().empty());
// Retrieve and process sequencing information
const base::Value* seq_info =
encrypted_record_list->GetList().rbegin()->FindDictKey(
"sequencingInformation");
EXPECT_TRUE(seq_info != nullptr);
response.SetPath("lastSucceedUploadedRecord", seq_info->Clone());
// If attach_encryption_settings it true, process that.
const auto attach_encryption_settings =
request.value().FindBoolKey("attachEncryptionSettings");
if (attach_encryption_settings.has_value() &&
attach_encryption_settings.value()) {
base::Value encryption_settings{base::Value::Type::DICTIONARY};
std::string public_key;
base::Base64Encode("PUBLIC KEY", &public_key);
encryption_settings.SetStringKey("publicKey", public_key);
encryption_settings.SetIntKey("publicKeyId", 12345);
std::string public_key_signature;
base::Base64Encode("PUBLIC KEY SIG", &public_key_signature);
encryption_settings.SetStringKey("publicKeySignature",
public_key_signature);
response.SetPath("encryptionSettings", std::move(encryption_settings));
}
return response;
}
class UploadClientTest : public ::testing::TestWithParam<bool> {
public:
UploadClientTest() = default;
protected:
void SetUp() override {
#if defined(OS_CHROMEOS)
// Set up fake primary profile.
auto mock_user_manager =
std::make_unique<testing::NiceMock<chromeos::FakeChromeUserManager>>();
profile_ = std::make_unique<TestingProfile>(
base::FilePath(FILE_PATH_LITERAL("/home/chronos/u-0123456789abcdef")));
const AccountId account_id(AccountId::FromUserEmailGaiaId(
profile_->GetProfileUserName(), "12345"));
const user_manager::User* user =
mock_user_manager->AddPublicAccountUser(account_id);
mock_user_manager->UserLoggedIn(account_id, user->username_hash(),
/*browser_restart=*/false,
/*is_child=*/false);
user_manager_ = std::make_unique<user_manager::ScopedUserManager>(
std::move(mock_user_manager));
#endif // OS_CHROMEOS
}
void TearDown() override {
#if defined(OS_CHROMEOS)
user_manager_.reset();
profile_.reset();
#endif // OS_CHROMEOS
}
bool need_encryption_key() const { return GetParam(); }
content::BrowserTaskEnvironment task_envrionment_;
#if defined(OS_CHROMEOS)
std::unique_ptr<TestingProfile> profile_;
std::unique_ptr<user_manager::ScopedUserManager> user_manager_;
#endif // OS_CHROMEOS
};
using TestEncryptionKeyAttached = MockFunction<void(SignedEncryptionInfo)>;
TEST_P(UploadClientTest, CreateUploadClientAndUploadRecords) {
const int kExpectedCallTimes = 10;
const uint64_t kGenerationId = 1234;
base::Value data{base::Value::Type::DICTIONARY};
data.SetKey("TEST_KEY", base::Value("TEST_VALUE"));
std::string json_data;
ASSERT_TRUE(base::JSONWriter::Write(data, &json_data));
WrappedRecord wrapped_record;
Record* record = wrapped_record.mutable_record();
record->set_data(json_data);
record->set_destination(Destination::UPLOAD_EVENTS);
std::string serialized_record;
wrapped_record.SerializeToString(&serialized_record);
std::unique_ptr<std::vector<EncryptedRecord>> records =
std::make_unique<std::vector<EncryptedRecord>>();
for (int i = 0; i < kExpectedCallTimes; i++) {
EncryptedRecord encrypted_record;
encrypted_record.set_encrypted_wrapped_record(serialized_record);
SequencingInformation* sequencing_information =
encrypted_record.mutable_sequencing_information();
sequencing_information->set_sequencing_id(i);
sequencing_information->set_generation_id(kGenerationId);
sequencing_information->set_priority(Priority::IMMEDIATE);
records->push_back(encrypted_record);
}
TestCallbackWaiterWithCounter waiter(kExpectedCallTimes);
StrictMock<TestEncryptionKeyAttached> encryption_key_attached;
EXPECT_CALL(
encryption_key_attached,
Call(AllOf(Property(&SignedEncryptionInfo::public_asymmetric_key,
Not(IsEmpty())),
Property(&SignedEncryptionInfo::public_key_id, Gt(0)),
Property(&SignedEncryptionInfo::signature, Not(IsEmpty())))))
.Times(need_encryption_key() ? 1 : 0);
auto encryption_key_attached_cb =
base::BindRepeating(&TestEncryptionKeyAttached::Call,
base::Unretained(&encryption_key_attached));
auto client = std::make_unique<MockCloudPolicyClient>();
client->SetDMToken(
policy::DMToken::CreateValidTokenForTesting("FAKE_DM_TOKEN").value());
EXPECT_CALL(*client, UploadEncryptedReport(_, _, _))
.WillRepeatedly(WithArgs<0, 2>(Invoke(
[&waiter](base::Value request,
policy::CloudPolicyClient::ResponseCallback response_cb) {
std::move(response_cb)
.Run(ValueFromSucceededSequencingInfo(std::move(request)));
base::ThreadPool::PostTask(
FROM_HERE, {base::TaskPriority::BEST_EFFORT},
base::BindOnce(&TestCallbackWaiterWithCounter::Signal,
base::Unretained(&waiter)));
})));
TestCallbackWaiter completion_callback_waiter;
UploadClient::ReportSuccessfulUploadCallback completion_cb =
base::BindRepeating(
&TestCallbackWaiter::CompleteExpectSequencingInformation,
base::Unretained(&completion_callback_waiter),
records->back().sequencing_information());
TestEvent<StatusOr<std::unique_ptr<UploadClient>>> e;
UploadClient::Create(client.get(), completion_cb, encryption_key_attached_cb,
e.cb());
StatusOr<std::unique_ptr<UploadClient>> upload_client_result = e.result();
ASSERT_OK(upload_client_result) << upload_client_result.status();
auto upload_client = std::move(upload_client_result.ValueOrDie());
auto enqueue_result =
upload_client->EnqueueUpload(need_encryption_key(), std::move(records));
EXPECT_TRUE(enqueue_result.ok());
waiter.Wait();
completion_callback_waiter.Wait();
}
INSTANTIATE_TEST_SUITE_P(NeedOrNoNeedKey, UploadClientTest, testing::Bool());
} // namespace
} // namespace reporting