| // Copyright 2018 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 "components/consent_auditor/consent_auditor_impl.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/memory/weak_ptr.h" |
| #include "base/sha1.h" |
| #include "base/test/simple_test_clock.h" |
| #include "base/time/default_clock.h" |
| #include "components/consent_auditor/pref_names.h" |
| #include "components/prefs/testing_pref_service.h" |
| #include "components/sync/model/fake_model_type_controller_delegate.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ArcPlayTermsOfServiceConsent = |
| sync_pb::UserConsentTypes::ArcPlayTermsOfServiceConsent; |
| using AssistantActivityControlConsent = |
| sync_pb::UserConsentTypes::AssistantActivityControlConsent; |
| using SyncConsent = sync_pb::UserConsentTypes::SyncConsent; |
| using sync_pb::UserConsentSpecifics; |
| using sync_pb::UserConsentTypes; |
| |
| namespace consent_auditor { |
| |
| namespace { |
| |
| const char kLocalConsentDescriptionKey[] = "description"; |
| const char kLocalConsentConfirmationKey[] = "confirmation"; |
| const char kLocalConsentVersionKey[] = "version"; |
| const char kLocalConsentLocaleKey[] = "locale"; |
| |
| // Fake product version for testing. |
| const char kCurrentAppVersion[] = "1.2.3.4"; |
| const char kCurrentAppLocale[] = "en-US"; |
| |
| // Fake account ID for testing. |
| const char kAccountId[] = "testing_account_id"; |
| |
| // A helper function to load the |description|, |confirmation|, |version|, |
| // and |locale|, in that order, from a record for the |feature| in |
| // the |consents| dictionary. |
| void LoadEntriesFromLocalConsentRecord(const base::Value* consents, |
| const std::string& feature, |
| std::string* description, |
| std::string* confirmation, |
| std::string* version, |
| std::string* locale) { |
| SCOPED_TRACE(::testing::Message() << "|feature| = " << feature); |
| |
| const base::Value* record = |
| consents->FindKeyOfType(feature, base::Value::Type::DICTIONARY); |
| ASSERT_TRUE(record); |
| SCOPED_TRACE(::testing::Message() << "|record| = " << record); |
| |
| const base::Value* description_entry = |
| record->FindKey(kLocalConsentDescriptionKey); |
| const base::Value* confirmation_entry = |
| record->FindKey(kLocalConsentConfirmationKey); |
| const base::Value* version_entry = record->FindKey(kLocalConsentVersionKey); |
| const base::Value* locale_entry = record->FindKey(kLocalConsentLocaleKey); |
| |
| ASSERT_TRUE(description_entry); |
| ASSERT_TRUE(confirmation_entry); |
| ASSERT_TRUE(version_entry); |
| ASSERT_TRUE(locale_entry); |
| |
| *description = description_entry->GetString(); |
| *confirmation = confirmation_entry->GetString(); |
| *version = version_entry->GetString(); |
| *locale = locale_entry->GetString(); |
| } |
| |
| class FakeConsentSyncBridge : public ConsentSyncBridge { |
| public: |
| ~FakeConsentSyncBridge() override = default; |
| |
| // ConsentSyncBridge implementation. |
| void RecordConsent( |
| std::unique_ptr<sync_pb::UserConsentSpecifics> specifics) override { |
| DCHECK(specifics); |
| recorded_user_consents_.push_back(*specifics); |
| } |
| |
| base::WeakPtr<syncer::ModelTypeControllerDelegate> GetControllerDelegate() |
| override { |
| return delegate_; |
| } |
| |
| // Fake methods. |
| void SetControllerDelegate( |
| base::WeakPtr<syncer::ModelTypeControllerDelegate> delegate) { |
| delegate_ = delegate; |
| } |
| |
| const std::vector<UserConsentSpecifics>& GetRecordedUserConsents() const { |
| return recorded_user_consents_; |
| } |
| |
| private: |
| base::WeakPtr<syncer::ModelTypeControllerDelegate> delegate_; |
| std::vector<UserConsentSpecifics> recorded_user_consents_; |
| }; |
| |
| } // namespace |
| |
| class ConsentAuditorImplTest : public testing::Test { |
| public: |
| void SetUp() override { |
| pref_service_ = std::make_unique<TestingPrefServiceSimple>(); |
| // Use normal clock by default. |
| clock_ = base::DefaultClock::GetInstance(); |
| consent_sync_bridge_ = std::make_unique<FakeConsentSyncBridge>(); |
| ConsentAuditorImpl::RegisterProfilePrefs(pref_service_->registry()); |
| app_version_ = kCurrentAppVersion; |
| app_locale_ = kCurrentAppLocale; |
| BuildConsentAuditorImpl(); |
| } |
| |
| // TODO(vitaliii): Add a real builder class instead. |
| void BuildConsentAuditorImpl() { |
| consent_auditor_ = std::make_unique<ConsentAuditorImpl>( |
| pref_service_.get(), std::move(consent_sync_bridge_), app_version_, |
| app_locale_, clock_); |
| } |
| |
| // These have no effect before |BuildConsentAuditorImpl|. |
| void SetAppVersion(const std::string& new_app_version) { |
| app_version_ = new_app_version; |
| } |
| void SetAppLocale(const std::string& new_app_locale) { |
| app_locale_ = new_app_locale; |
| } |
| void SetConsentSyncBridge(std::unique_ptr<ConsentSyncBridge> bridge) { |
| consent_sync_bridge_ = std::move(bridge); |
| } |
| void SetClock(base::Clock* clock) { clock_ = clock; } |
| |
| ConsentAuditorImpl* consent_auditor() { return consent_auditor_.get(); } |
| PrefService* pref_service() const { return pref_service_.get(); } |
| |
| private: |
| std::unique_ptr<ConsentAuditorImpl> consent_auditor_; |
| base::Clock* clock_; |
| |
| std::unique_ptr<TestingPrefServiceSimple> pref_service_; |
| std::string app_version_; |
| std::string app_locale_; |
| std::unique_ptr<ConsentSyncBridge> consent_sync_bridge_; |
| }; |
| |
| TEST_F(ConsentAuditorImplTest, LocalConsentPrefRepresentation) { |
| SetAppVersion(kCurrentAppVersion); |
| SetAppLocale(kCurrentAppLocale); |
| SetConsentSyncBridge(std::make_unique<FakeConsentSyncBridge>()); |
| BuildConsentAuditorImpl(); |
| |
| // No consents are written at first. |
| EXPECT_FALSE(pref_service()->HasPrefPath(prefs::kLocalConsentsDictionary)); |
| |
| // Record a consent and check that it appears in the prefs. |
| const std::string kFeature1Description = "This will enable feature 1."; |
| const std::string kFeature1Confirmation = "OK."; |
| consent_auditor()->RecordLocalConsent("feature1", kFeature1Description, |
| kFeature1Confirmation); |
| ASSERT_TRUE(pref_service()->HasPrefPath(prefs::kLocalConsentsDictionary)); |
| const base::DictionaryValue* consents = |
| pref_service()->GetDictionary(prefs::kLocalConsentsDictionary); |
| ASSERT_TRUE(consents); |
| std::string description; |
| std::string confirmation; |
| std::string version; |
| std::string locale; |
| LoadEntriesFromLocalConsentRecord(consents, "feature1", &description, |
| &confirmation, &version, &locale); |
| EXPECT_EQ(kFeature1Description, description); |
| EXPECT_EQ(kFeature1Confirmation, confirmation); |
| EXPECT_EQ(kCurrentAppVersion, version); |
| EXPECT_EQ(kCurrentAppLocale, locale); |
| |
| // Do the same for another feature. |
| const std::string kFeature2Description = "Enable feature 2?"; |
| const std::string kFeature2Confirmation = "Yes."; |
| consent_auditor()->RecordLocalConsent("feature2", kFeature2Description, |
| kFeature2Confirmation); |
| LoadEntriesFromLocalConsentRecord(consents, "feature2", &description, |
| &confirmation, &version, &locale); |
| EXPECT_EQ(kFeature2Description, description); |
| EXPECT_EQ(kFeature2Confirmation, confirmation); |
| EXPECT_EQ(kCurrentAppVersion, version); |
| EXPECT_EQ(kCurrentAppLocale, locale); |
| |
| // They are two separate records; the latter did not overwrite the former. |
| EXPECT_EQ(2u, consents->size()); |
| EXPECT_TRUE( |
| consents->FindKeyOfType("feature1", base::Value::Type::DICTIONARY)); |
| |
| // Overwrite an existing consent, this time use a different product version |
| // and a different locale. |
| const std::string kFeature2NewDescription = "Re-enable feature 2?"; |
| const std::string kFeature2NewConfirmation = "Yes again."; |
| const std::string kFeature2NewAppVersion = "5.6.7.8"; |
| const std::string kFeature2NewAppLocale = "de"; |
| SetAppVersion(kFeature2NewAppVersion); |
| SetAppLocale(kFeature2NewAppLocale); |
| SetConsentSyncBridge(std::make_unique<FakeConsentSyncBridge>()); |
| // We rebuild consent auditor to emulate restarting Chrome. This is the only |
| // way to change app version or app locale. |
| BuildConsentAuditorImpl(); |
| |
| consent_auditor()->RecordLocalConsent("feature2", kFeature2NewDescription, |
| kFeature2NewConfirmation); |
| LoadEntriesFromLocalConsentRecord(consents, "feature2", &description, |
| &confirmation, &version, &locale); |
| EXPECT_EQ(kFeature2NewDescription, description); |
| EXPECT_EQ(kFeature2NewConfirmation, confirmation); |
| EXPECT_EQ(kFeature2NewAppVersion, version); |
| EXPECT_EQ(kFeature2NewAppLocale, locale); |
| |
| // We still have two records. |
| EXPECT_EQ(2u, consents->size()); |
| } |
| |
| TEST_F(ConsentAuditorImplTest, RecordGaiaConsentAsUserConsent) { |
| auto wrapped_fake_bridge = std::make_unique<FakeConsentSyncBridge>(); |
| FakeConsentSyncBridge* fake_bridge = wrapped_fake_bridge.get(); |
| base::SimpleTestClock test_clock; |
| |
| SetConsentSyncBridge(std::move(wrapped_fake_bridge)); |
| SetAppVersion(kCurrentAppVersion); |
| SetAppLocale(kCurrentAppLocale); |
| SetClock(&test_clock); |
| BuildConsentAuditorImpl(); |
| |
| std::vector<int> kDescriptionMessageIds = {12, 37, 42}; |
| int kConfirmationMessageId = 47; |
| |
| base::Time now; |
| ASSERT_TRUE(base::Time::FromUTCString("2017-11-14T15:15:38Z", &now)); |
| test_clock.SetNow(now); |
| |
| SyncConsent sync_consent; |
| sync_consent.set_status(UserConsentTypes::GIVEN); |
| sync_consent.set_confirmation_grd_id(kConfirmationMessageId); |
| for (int id : kDescriptionMessageIds) { |
| sync_consent.add_description_grd_ids(id); |
| } |
| consent_auditor()->RecordSyncConsent(kAccountId, sync_consent); |
| |
| std::vector<UserConsentSpecifics> consents = |
| fake_bridge->GetRecordedUserConsents(); |
| ASSERT_EQ(1U, consents.size()); |
| UserConsentSpecifics consent = consents[0]; |
| |
| EXPECT_EQ(now.since_origin().InMicroseconds(), |
| consent.client_consent_time_usec()); |
| EXPECT_EQ(kAccountId, consent.account_id()); |
| EXPECT_EQ(kCurrentAppLocale, consent.locale()); |
| |
| EXPECT_TRUE(consent.has_sync_consent()); |
| const SyncConsent& actual_sync_consent = consent.sync_consent(); |
| EXPECT_EQ(3, actual_sync_consent.description_grd_ids_size()); |
| EXPECT_EQ(kDescriptionMessageIds[0], |
| actual_sync_consent.description_grd_ids(0)); |
| EXPECT_EQ(kDescriptionMessageIds[1], |
| actual_sync_consent.description_grd_ids(1)); |
| EXPECT_EQ(kDescriptionMessageIds[2], |
| actual_sync_consent.description_grd_ids(2)); |
| EXPECT_EQ(kConfirmationMessageId, actual_sync_consent.confirmation_grd_id()); |
| } |
| |
| TEST_F(ConsentAuditorImplTest, RecordArcPlayConsentRevocation) { |
| auto wrapped_fake_bridge = std::make_unique<FakeConsentSyncBridge>(); |
| FakeConsentSyncBridge* fake_bridge = wrapped_fake_bridge.get(); |
| base::SimpleTestClock test_clock; |
| |
| SetConsentSyncBridge(std::move(wrapped_fake_bridge)); |
| SetAppVersion(kCurrentAppVersion); |
| SetAppLocale(kCurrentAppLocale); |
| SetClock(&test_clock); |
| BuildConsentAuditorImpl(); |
| |
| std::vector<int> kDescriptionMessageIds = {12, 37, 42}; |
| int kConfirmationMessageId = 47; |
| |
| base::Time now; |
| ASSERT_TRUE(base::Time::FromUTCString("2017-11-14T15:15:38Z", &now)); |
| test_clock.SetNow(now); |
| |
| ArcPlayTermsOfServiceConsent play_consent; |
| play_consent.set_status(UserConsentTypes::NOT_GIVEN); |
| play_consent.set_confirmation_grd_id(kConfirmationMessageId); |
| for (int id : kDescriptionMessageIds) { |
| play_consent.add_description_grd_ids(id); |
| } |
| play_consent.set_consent_flow(ArcPlayTermsOfServiceConsent::SETTING_CHANGE); |
| consent_auditor()->RecordArcPlayConsent(kAccountId, play_consent); |
| |
| std::vector<UserConsentSpecifics> consents = |
| fake_bridge->GetRecordedUserConsents(); |
| ASSERT_EQ(1U, consents.size()); |
| UserConsentSpecifics consent = consents[0]; |
| |
| EXPECT_EQ(kAccountId, consent.account_id()); |
| EXPECT_EQ(kCurrentAppLocale, consent.locale()); |
| |
| EXPECT_TRUE(consent.has_arc_play_terms_of_service_consent()); |
| const ArcPlayTermsOfServiceConsent& actual_play_consent = |
| consent.arc_play_terms_of_service_consent(); |
| EXPECT_EQ(UserConsentTypes::NOT_GIVEN, actual_play_consent.status()); |
| EXPECT_EQ(ArcPlayTermsOfServiceConsent::SETTING_CHANGE, |
| actual_play_consent.consent_flow()); |
| EXPECT_EQ(3, actual_play_consent.description_grd_ids_size()); |
| EXPECT_EQ(kDescriptionMessageIds[0], |
| actual_play_consent.description_grd_ids(0)); |
| EXPECT_EQ(kDescriptionMessageIds[1], |
| actual_play_consent.description_grd_ids(1)); |
| EXPECT_EQ(kDescriptionMessageIds[2], |
| actual_play_consent.description_grd_ids(2)); |
| EXPECT_EQ(kConfirmationMessageId, actual_play_consent.confirmation_grd_id()); |
| } |
| |
| TEST_F(ConsentAuditorImplTest, RecordArcPlayConsent) { |
| auto wrapped_fake_bridge = std::make_unique<FakeConsentSyncBridge>(); |
| FakeConsentSyncBridge* fake_bridge = wrapped_fake_bridge.get(); |
| base::SimpleTestClock test_clock; |
| |
| SetConsentSyncBridge(std::move(wrapped_fake_bridge)); |
| SetAppVersion(kCurrentAppVersion); |
| SetAppLocale(kCurrentAppLocale); |
| SetClock(&test_clock); |
| BuildConsentAuditorImpl(); |
| |
| int kConfirmationMessageId = 47; |
| |
| base::Time now; |
| ASSERT_TRUE(base::Time::FromUTCString("2017-11-14T15:15:38Z", &now)); |
| test_clock.SetNow(now); |
| |
| ArcPlayTermsOfServiceConsent play_consent; |
| play_consent.set_status(UserConsentTypes::GIVEN); |
| play_consent.set_confirmation_grd_id(kConfirmationMessageId); |
| play_consent.set_consent_flow(ArcPlayTermsOfServiceConsent::SETUP); |
| |
| // Verify the hash: 2fd4e1c6 7a2d28fc ed849ee1 bb76e739 1b93eb12. |
| const char play_tos_hash[] = {0x2f, 0xd4, 0xe1, 0xc6, 0x7a, 0x2d, 0x28, |
| 0xfc, 0xed, 0x84, 0x9e, 0xe1, 0xbb, 0x76, |
| 0xe7, 0x39, 0x1b, 0x93, 0xeb, 0x12}; |
| play_consent.set_play_terms_of_service_hash( |
| std::string(play_tos_hash, base::kSHA1Length)); |
| play_consent.set_play_terms_of_service_text_length(7); |
| |
| consent_auditor()->RecordArcPlayConsent(kAccountId, play_consent); |
| |
| std::vector<UserConsentSpecifics> consents = |
| fake_bridge->GetRecordedUserConsents(); |
| ASSERT_EQ(1U, consents.size()); |
| UserConsentSpecifics consent = consents[0]; |
| |
| EXPECT_EQ(kAccountId, consent.account_id()); |
| EXPECT_EQ(kCurrentAppLocale, consent.locale()); |
| |
| EXPECT_TRUE(consent.has_arc_play_terms_of_service_consent()); |
| const ArcPlayTermsOfServiceConsent& actual_play_consent = |
| consent.arc_play_terms_of_service_consent(); |
| |
| EXPECT_EQ(7, actual_play_consent.play_terms_of_service_text_length()); |
| EXPECT_EQ(std::string(play_tos_hash, base::kSHA1Length), |
| actual_play_consent.play_terms_of_service_hash()); |
| |
| EXPECT_EQ(kConfirmationMessageId, actual_play_consent.confirmation_grd_id()); |
| EXPECT_EQ(ArcPlayTermsOfServiceConsent::SETUP, |
| actual_play_consent.consent_flow()); |
| EXPECT_EQ(UserConsentTypes::GIVEN, actual_play_consent.status()); |
| } |
| |
| TEST_F(ConsentAuditorImplTest, ShouldReturnSyncDelegateWhenBridgePresent) { |
| auto fake_bridge = std::make_unique<FakeConsentSyncBridge>(); |
| |
| syncer::FakeModelTypeControllerDelegate fake_delegate( |
| syncer::ModelType::USER_CONSENTS); |
| auto expected_delegate_ptr = fake_delegate.GetWeakPtr(); |
| DCHECK(expected_delegate_ptr); |
| fake_bridge->SetControllerDelegate(expected_delegate_ptr); |
| |
| SetConsentSyncBridge(std::move(fake_bridge)); |
| BuildConsentAuditorImpl(); |
| |
| // There is a bridge (i.e. separate sync type for consents is enabled), thus, |
| // there should be a delegate as well. |
| EXPECT_EQ(expected_delegate_ptr.get(), |
| consent_auditor()->GetControllerDelegate().get()); |
| } |
| |
| TEST_F(ConsentAuditorImplTest, RecordAssistantActivityControlConsent) { |
| auto wrapped_fake_bridge = std::make_unique<FakeConsentSyncBridge>(); |
| FakeConsentSyncBridge* fake_bridge = wrapped_fake_bridge.get(); |
| base::SimpleTestClock test_clock; |
| |
| SetConsentSyncBridge(std::move(wrapped_fake_bridge)); |
| SetAppVersion(kCurrentAppVersion); |
| SetAppLocale(kCurrentAppLocale); |
| SetClock(&test_clock); |
| BuildConsentAuditorImpl(); |
| |
| AssistantActivityControlConsent assistant_consent; |
| assistant_consent.set_status(UserConsentTypes::GIVEN); |
| |
| const char ui_audit_key[] = {0x67, 0x23, 0x78}; |
| assistant_consent.set_ui_audit_key(std::string(ui_audit_key, 3)); |
| |
| consent_auditor()->RecordAssistantActivityControlConsent(kAccountId, |
| assistant_consent); |
| |
| std::vector<UserConsentSpecifics> consents = |
| fake_bridge->GetRecordedUserConsents(); |
| ASSERT_EQ(1U, consents.size()); |
| UserConsentSpecifics consent = consents[0]; |
| |
| EXPECT_EQ(kAccountId, consent.account_id()); |
| EXPECT_EQ(kCurrentAppLocale, consent.locale()); |
| |
| EXPECT_EQ(true, consent.has_assistant_activity_control_consent()); |
| EXPECT_EQ(UserConsentTypes::GIVEN, |
| consent.assistant_activity_control_consent().status()); |
| EXPECT_EQ(std::string(ui_audit_key, 3), |
| consent.assistant_activity_control_consent().ui_audit_key()); |
| } |
| |
| } // namespace consent_auditor |