blob: 8028da88c34fa1f048385090f5b3d9f4e4b9ac22 [file] [log] [blame]
// Copyright 2014 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/safe_browsing/incident_reporting/preference_validation_delegate.h"
#include <stddef.h>
#include <memory>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/values.h"
#include "chrome/browser/safe_browsing/incident_reporting/incident.h"
#include "chrome/browser/safe_browsing/incident_reporting/mock_incident_receiver.h"
#include "components/safe_browsing/proto/csd.pb.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::IsNull;
using ::testing::NiceMock;
using ::testing::WithArg;
using ValueState =
prefs::mojom::TrackedPreferenceValidationDelegate::ValueState;
namespace {
const char kPrefPath[] = "atomic.pref";
}
// A basic test harness that creates a delegate instance for which it stores all
// incidents. Tests can push data to the delegate and verify that the test
// instance was provided with the expected data.
class PreferenceValidationDelegateTest : public testing::Test {
protected:
typedef std::vector<std::unique_ptr<safe_browsing::Incident>> IncidentVector;
void SetUp() override {
testing::Test::SetUp();
invalid_keys_.push_back(std::string("one"));
invalid_keys_.push_back(std::string("two"));
external_validation_invalid_keys_.push_back(std::string("three"));
std::unique_ptr<safe_browsing::MockIncidentReceiver> receiver(
new NiceMock<safe_browsing::MockIncidentReceiver>());
ON_CALL(*receiver, DoAddIncidentForProfile(IsNull(), _))
.WillByDefault(WithArg<1>(TakeIncidentToVector(&incidents_)));
instance_.reset(new safe_browsing::PreferenceValidationDelegate(
nullptr, std::move(receiver)));
}
static void ExpectValueStatesEquate(
ValueState store_state,
ValueState external_validation_value_state,
safe_browsing::
ClientIncidentReport_IncidentData_TrackedPreferenceIncident_ValueState
incident_state) {
typedef safe_browsing::
ClientIncidentReport_IncidentData_TrackedPreferenceIncident TPIncident;
switch (store_state) {
case ValueState::CLEARED:
EXPECT_EQ(TPIncident::CLEARED, incident_state);
break;
case ValueState::CHANGED:
EXPECT_EQ(TPIncident::CHANGED, incident_state);
break;
case ValueState::UNTRUSTED_UNKNOWN_VALUE:
EXPECT_EQ(TPIncident::UNTRUSTED_UNKNOWN_VALUE, incident_state);
break;
default:
if (external_validation_value_state == ValueState::CLEARED) {
EXPECT_EQ(TPIncident::BYPASS_CLEARED, incident_state);
} else if (external_validation_value_state == ValueState::CHANGED) {
EXPECT_EQ(TPIncident::BYPASS_CHANGED, incident_state);
} else {
FAIL() << "unexpected store state";
}
break;
}
}
static void ExpectKeysEquate(
const std::vector<std::string>& store_keys,
const google::protobuf::RepeatedPtrField<std::string>& incident_keys) {
ASSERT_EQ(store_keys.size(), static_cast<size_t>(incident_keys.size()));
for (int i = 0; i < incident_keys.size(); ++i) {
EXPECT_EQ(store_keys[i], incident_keys.Get(i));
}
}
IncidentVector incidents_;
std::vector<std::string> invalid_keys_;
std::vector<std::string> external_validation_invalid_keys_;
std::unique_ptr<prefs::mojom::TrackedPreferenceValidationDelegate> instance_;
};
// Tests that a NULL value results in an incident with no value.
TEST_F(PreferenceValidationDelegateTest, NullValue) {
instance_->OnAtomicPreferenceValidation(
kPrefPath, base::nullopt, ValueState::CLEARED, ValueState::UNSUPPORTED,
false /* is_personal */);
std::unique_ptr<safe_browsing::ClientIncidentReport_IncidentData> incident(
incidents_.back()->TakePayload());
EXPECT_FALSE(incident->tracked_preference().has_atomic_value());
EXPECT_EQ(
safe_browsing::
ClientIncidentReport_IncidentData_TrackedPreferenceIncident::CLEARED,
incident->tracked_preference().value_state());
}
// Tests that all supported value types can be stringified into an incident. The
// parameters for the test are the type of value to test and the expected value
// string.
class PreferenceValidationDelegateValues
: public PreferenceValidationDelegateTest,
public testing::WithParamInterface<
std::tuple<base::Value::Type, const char*>> {
protected:
void SetUp() override {
PreferenceValidationDelegateTest::SetUp();
value_type_ = std::get<0>(GetParam());
expected_value_ = std::get<1>(GetParam());
}
static base::Value MakeValue(base::Value::Type value_type) {
using base::Value;
switch (value_type) {
case Value::Type::NONE:
return Value();
case Value::Type::BOOLEAN:
return Value(false);
case Value::Type::INTEGER:
return Value(47);
case Value::Type::DOUBLE:
return Value(0.47);
case Value::Type::STRING:
return Value("i have a spleen");
case Value::Type::DICTIONARY: {
Value value(base::Value::Type::DICTIONARY);
value.SetKey("twenty-two", Value(22));
value.SetKey("forty-seven", Value(47));
return value;
}
case Value::Type::LIST: {
Value value(base::Value::Type::LIST);
value.GetList().emplace_back(22);
value.GetList().emplace_back(47);
return value;
}
default:
ADD_FAILURE() << "unsupported value type " << value_type;
}
return Value();
}
base::Value::Type value_type_;
const char* expected_value_;
};
TEST_P(PreferenceValidationDelegateValues, Value) {
instance_->OnAtomicPreferenceValidation(
kPrefPath, MakeValue(value_type_), ValueState::CLEARED,
ValueState::UNSUPPORTED, false /* is_personal */);
ASSERT_EQ(1U, incidents_.size());
std::unique_ptr<safe_browsing::ClientIncidentReport_IncidentData> incident(
incidents_.back()->TakePayload());
EXPECT_EQ(std::string(expected_value_),
incident->tracked_preference().atomic_value());
}
INSTANTIATE_TEST_CASE_P(
Values,
PreferenceValidationDelegateValues,
// On Android, make_tuple(..., "null") doesn't compile due to the error:
// testing/gtest/include/gtest/internal/gtest-tuple.h:246:48:
// error: array used as initializer
testing::Values(
std::make_tuple(base::Value::Type::NONE, const_cast<char*>("null")),
std::make_tuple(base::Value::Type::BOOLEAN, const_cast<char*>("false")),
std::make_tuple(base::Value::Type::INTEGER, const_cast<char*>("47")),
std::make_tuple(base::Value::Type::DOUBLE, const_cast<char*>("0.47")),
std::make_tuple(base::Value::Type::STRING,
const_cast<char*>("i have a spleen")),
std::make_tuple(
base::Value::Type::DICTIONARY,
const_cast<char*>("{\"forty-seven\":47,\"twenty-two\":22}")),
std::make_tuple(base::Value::Type::LIST,
const_cast<char*>("[22,47]"))));
// Tests that no incidents are reported for relevant combinations of ValueState.
class PreferenceValidationDelegateNoIncident
: public PreferenceValidationDelegateTest,
public testing::WithParamInterface<std::tuple<ValueState, ValueState>> {
protected:
void SetUp() override {
PreferenceValidationDelegateTest::SetUp();
value_state_ = std::get<0>(GetParam());
external_validation_value_state_ = std::get<1>(GetParam());
}
ValueState value_state_;
ValueState external_validation_value_state_;
};
TEST_P(PreferenceValidationDelegateNoIncident, Atomic) {
instance_->OnAtomicPreferenceValidation(
kPrefPath, base::make_optional<base::Value>(), value_state_,
external_validation_value_state_, false /* is_personal */);
EXPECT_EQ(0U, incidents_.size());
}
TEST_P(PreferenceValidationDelegateNoIncident, Split) {
instance_->OnSplitPreferenceValidation(
kPrefPath, invalid_keys_, external_validation_invalid_keys_, value_state_,
external_validation_value_state_, false /* is_personal */);
EXPECT_EQ(0U, incidents_.size());
}
INSTANTIATE_TEST_CASE_P(
NoIncident,
PreferenceValidationDelegateNoIncident,
testing::Combine(testing::Values(ValueState::UNCHANGED,
ValueState::SECURE_LEGACY,
ValueState::TRUSTED_UNKNOWN_VALUE),
testing::Values(ValueState::UNCHANGED,
ValueState::UNSUPPORTED,
ValueState::UNTRUSTED_UNKNOWN_VALUE)));
// Tests that incidents are reported for relevant combinations of ValueState and
// impersonal/personal.
class PreferenceValidationDelegateWithIncident
: public PreferenceValidationDelegateTest,
public testing::WithParamInterface<
std::tuple<ValueState, ValueState, bool>> {
protected:
void SetUp() override {
PreferenceValidationDelegateTest::SetUp();
value_state_ = std::get<0>(GetParam());
external_validation_value_state_ = std::get<1>(GetParam());
is_personal_ = std::get<2>(GetParam());
}
ValueState value_state_;
ValueState external_validation_value_state_;
bool is_personal_;
};
TEST_P(PreferenceValidationDelegateWithIncident, Atomic) {
instance_->OnAtomicPreferenceValidation(
kPrefPath, base::make_optional<base::Value>(), value_state_,
external_validation_value_state_, is_personal_);
ASSERT_EQ(1U, incidents_.size());
std::unique_ptr<safe_browsing::ClientIncidentReport_IncidentData> incident(
incidents_.back()->TakePayload());
EXPECT_TRUE(incident->has_tracked_preference());
const safe_browsing::
ClientIncidentReport_IncidentData_TrackedPreferenceIncident& tp_incident =
incident->tracked_preference();
EXPECT_EQ(kPrefPath, tp_incident.path());
EXPECT_EQ(0, tp_incident.split_key_size());
if (!is_personal_) {
EXPECT_TRUE(tp_incident.has_atomic_value());
EXPECT_EQ(std::string("null"), tp_incident.atomic_value());
} else {
EXPECT_FALSE(tp_incident.has_atomic_value());
}
EXPECT_TRUE(tp_incident.has_value_state());
ExpectValueStatesEquate(value_state_, external_validation_value_state_,
tp_incident.value_state());
}
TEST_P(PreferenceValidationDelegateWithIncident, Split) {
instance_->OnSplitPreferenceValidation(
kPrefPath, invalid_keys_, external_validation_invalid_keys_, value_state_,
external_validation_value_state_, is_personal_);
ASSERT_EQ(1U, incidents_.size());
std::unique_ptr<safe_browsing::ClientIncidentReport_IncidentData> incident(
incidents_.back()->TakePayload());
EXPECT_TRUE(incident->has_tracked_preference());
const safe_browsing::
ClientIncidentReport_IncidentData_TrackedPreferenceIncident& tp_incident =
incident->tracked_preference();
EXPECT_EQ(kPrefPath, tp_incident.path());
EXPECT_FALSE(tp_incident.has_atomic_value());
if (!is_personal_) {
if (value_state_ == ValueState::CLEARED ||
value_state_ == ValueState::CHANGED ||
value_state_ == ValueState::UNTRUSTED_UNKNOWN_VALUE) {
ExpectKeysEquate(invalid_keys_, tp_incident.split_key());
} else {
ExpectKeysEquate(external_validation_invalid_keys_,
tp_incident.split_key());
}
} else {
EXPECT_EQ(0, tp_incident.split_key_size());
}
EXPECT_TRUE(tp_incident.has_value_state());
ExpectValueStatesEquate(value_state_, external_validation_value_state_,
tp_incident.value_state());
}
INSTANTIATE_TEST_CASE_P(
WithIncident,
PreferenceValidationDelegateWithIncident,
testing::Combine(testing::Values(ValueState::CLEARED,
ValueState::CHANGED,
ValueState::UNTRUSTED_UNKNOWN_VALUE),
testing::Values(ValueState::UNCHANGED,
ValueState::UNSUPPORTED,
ValueState::UNTRUSTED_UNKNOWN_VALUE),
testing::Bool()));
INSTANTIATE_TEST_CASE_P(
WithBypassIncident,
PreferenceValidationDelegateWithIncident,
testing::Combine(testing::Values(ValueState::UNCHANGED,
ValueState::SECURE_LEGACY,
ValueState::TRUSTED_UNKNOWN_VALUE),
testing::Values(ValueState::CHANGED, ValueState::CLEARED),
testing::Bool()));
INSTANTIATE_TEST_CASE_P(
WithIncidentIgnoreBypass,
PreferenceValidationDelegateWithIncident,
testing::Combine(testing::Values(ValueState::CLEARED,
ValueState::CHANGED,
ValueState::UNTRUSTED_UNKNOWN_VALUE),
testing::Values(ValueState::CHANGED, ValueState::CLEARED),
testing::Bool()));