| // 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 <utility> |
| |
| #include "base/metrics/histogram_macros.h" |
| #include "base/sha1.h" |
| #include "base/values.h" |
| #include "components/consent_auditor/pref_names.h" |
| #include "components/prefs/pref_registry_simple.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/prefs/scoped_user_pref_update.h" |
| #include "components/sync/driver/sync_driver_switches.h" |
| #include "components/sync/model/model_type_sync_bridge.h" |
| #include "components/sync/user_events/user_event_service.h" |
| |
| using ArcPlayTermsOfServiceConsent = |
| sync_pb::UserConsentTypes::ArcPlayTermsOfServiceConsent; |
| using sync_pb::UserConsentTypes; |
| using sync_pb::UserConsentSpecifics; |
| using sync_pb::UserEventSpecifics; |
| |
| namespace consent_auditor { |
| |
| namespace { |
| |
| const char kLocalConsentDescriptionKey[] = "description"; |
| const char kLocalConsentConfirmationKey[] = "confirmation"; |
| const char kLocalConsentVersionKey[] = "version"; |
| const char kLocalConsentLocaleKey[] = "locale"; |
| |
| bool IsSeparateConsentTypeEnabled() { |
| return base::FeatureList::IsEnabled(switches::kSyncUserConsentSeparateType); |
| } |
| |
| UserEventSpecifics::UserConsent::Feature FeatureToUserEventProtoEnum( |
| consent_auditor::Feature feature) { |
| switch (feature) { |
| case consent_auditor::Feature::CHROME_SYNC: |
| return UserEventSpecifics::UserConsent::CHROME_SYNC; |
| case consent_auditor::Feature::PLAY_STORE: |
| return UserEventSpecifics::UserConsent::PLAY_STORE; |
| case consent_auditor::Feature::BACKUP_AND_RESTORE: |
| return UserEventSpecifics::UserConsent::BACKUP_AND_RESTORE; |
| case consent_auditor::Feature::GOOGLE_LOCATION_SERVICE: |
| return UserEventSpecifics::UserConsent::GOOGLE_LOCATION_SERVICE; |
| case consent_auditor::Feature::CHROME_UNIFIED_CONSENT: |
| return UserEventSpecifics::UserConsent::CHROME_UNIFIED_CONSENT; |
| case consent_auditor::Feature::ASSISTANT_ACTIVITY_CONTROL: |
| return UserEventSpecifics::UserConsent::ASSISTANT_ACTIVITY_CONTROL; |
| } |
| NOTREACHED(); |
| return UserEventSpecifics::UserConsent::FEATURE_UNSPECIFIED; |
| } |
| |
| UserConsentTypes::ConsentStatus StatusToProtoEnum( |
| consent_auditor::ConsentStatus status) { |
| switch (status) { |
| case consent_auditor::ConsentStatus::NOT_GIVEN: |
| return UserConsentTypes::NOT_GIVEN; |
| case consent_auditor::ConsentStatus::GIVEN: |
| return UserConsentTypes::GIVEN; |
| } |
| NOTREACHED(); |
| return UserConsentTypes::CONSENT_STATUS_UNSPECIFIED; |
| } |
| |
| ConsentStatus ConvertConsentStatus( |
| UserConsentTypes::ConsentStatus consent_status) { |
| DCHECK_NE(consent_status, |
| UserConsentTypes::ConsentStatus:: |
| UserConsentTypes_ConsentStatus_CONSENT_STATUS_UNSPECIFIED); |
| |
| if (consent_status == |
| UserConsentTypes::ConsentStatus::UserConsentTypes_ConsentStatus_GIVEN) { |
| return ConsentStatus::GIVEN; |
| } |
| return ConsentStatus::NOT_GIVEN; |
| } |
| |
| std::unique_ptr<sync_pb::UserConsentSpecifics> CreateUserConsentSpecifics( |
| const std::string& account_id, |
| const std::string& locale, |
| base::Clock* clock) { |
| std::unique_ptr<sync_pb::UserConsentSpecifics> specifics = |
| std::make_unique<sync_pb::UserConsentSpecifics>(); |
| specifics->set_account_id(account_id); |
| specifics->set_client_consent_time_usec( |
| clock->Now().since_origin().InMicroseconds()); |
| specifics->set_locale(locale); |
| |
| return specifics; |
| } |
| |
| } // namespace |
| |
| ConsentAuditorImpl::ConsentAuditorImpl( |
| PrefService* pref_service, |
| std::unique_ptr<syncer::ConsentSyncBridge> consent_sync_bridge, |
| syncer::UserEventService* user_event_service, |
| const std::string& app_version, |
| const std::string& app_locale, |
| base::Clock* clock) |
| : pref_service_(pref_service), |
| consent_sync_bridge_(std::move(consent_sync_bridge)), |
| user_event_service_(user_event_service), |
| app_version_(app_version), |
| app_locale_(app_locale), |
| clock_(clock) { |
| if (IsSeparateConsentTypeEnabled()) { |
| DCHECK(consent_sync_bridge_ && !user_event_service_); |
| } else { |
| DCHECK(user_event_service_ && !consent_sync_bridge_); |
| } |
| DCHECK(pref_service_); |
| } |
| |
| ConsentAuditorImpl::~ConsentAuditorImpl() {} |
| |
| void ConsentAuditorImpl::Shutdown() { |
| user_event_service_ = nullptr; |
| } |
| |
| // static |
| void ConsentAuditorImpl::RegisterProfilePrefs(PrefRegistrySimple* registry) { |
| registry->RegisterDictionaryPref(prefs::kLocalConsentsDictionary); |
| } |
| |
| void ConsentAuditorImpl::RecordGaiaConsent( |
| const std::string& account_id, |
| Feature feature, |
| const std::vector<int>& description_grd_ids, |
| int confirmation_grd_id, |
| ConsentStatus status) { |
| DCHECK(!account_id.empty()) << "No signed-in account specified."; |
| |
| if (!base::FeatureList::IsEnabled(switches::kSyncUserConsentEvents)) |
| return; |
| |
| DCHECK_LE(feature, consent_auditor::Feature::FEATURE_LAST); |
| |
| switch (status) { |
| case ConsentStatus::GIVEN: |
| UMA_HISTOGRAM_ENUMERATION( |
| "Privacy.ConsentAuditor.ConsentGiven.Feature", feature, |
| static_cast<int>(consent_auditor::Feature::FEATURE_LAST) + 1); |
| break; |
| case ConsentStatus::NOT_GIVEN: |
| UMA_HISTOGRAM_ENUMERATION( |
| "Privacy.ConsentAuditor.ConsentNotGiven.Feature", feature, |
| static_cast<int>(consent_auditor::Feature::FEATURE_LAST) + 1); |
| break; |
| } |
| |
| DCHECK(!IsSeparateConsentTypeEnabled()); |
| // TODO(msramek): Pass in the actual account id. |
| std::unique_ptr<sync_pb::UserEventSpecifics> specifics = |
| ConstructUserEventSpecifics(account_id, feature, description_grd_ids, |
| confirmation_grd_id, status); |
| user_event_service_->RecordUserEvent(std::move(specifics)); |
| } |
| |
| std::unique_ptr<sync_pb::UserEventSpecifics> |
| ConsentAuditorImpl::ConstructUserEventSpecifics( |
| const std::string& account_id, |
| Feature feature, |
| const std::vector<int>& description_grd_ids, |
| int confirmation_grd_id, |
| ConsentStatus status) { |
| DCHECK(!IsSeparateConsentTypeEnabled()); |
| |
| auto specifics = std::make_unique<sync_pb::UserEventSpecifics>(); |
| specifics->set_event_time_usec(clock_->Now().since_origin().InMicroseconds()); |
| auto* consent = specifics->mutable_user_consent(); |
| consent->set_account_id(account_id); |
| consent->set_feature(FeatureToUserEventProtoEnum(feature)); |
| for (int id : description_grd_ids) { |
| consent->add_description_grd_ids(id); |
| } |
| consent->set_confirmation_grd_id(confirmation_grd_id); |
| consent->set_locale(app_locale_); |
| consent->set_status(StatusToProtoEnum(status)); |
| return specifics; |
| } |
| |
| void ConsentAuditorImpl::RecordArcPlayConsent( |
| const std::string& account_id, |
| const ArcPlayTermsOfServiceConsent& consent) { |
| if (IsSeparateConsentTypeEnabled()) { |
| std::unique_ptr<sync_pb::UserConsentSpecifics> specifics = |
| CreateUserConsentSpecifics(account_id, app_locale_, clock_); |
| |
| sync_pb::UserConsentTypes::ArcPlayTermsOfServiceConsent* arc_play_consent = |
| specifics->mutable_arc_play_terms_of_service_consent(); |
| arc_play_consent->CopyFrom(consent); |
| consent_sync_bridge_->RecordConsent(std::move(specifics)); |
| } else { |
| std::vector<int> description_grd_ids; |
| if (consent.consent_flow() == |
| ArcPlayTermsOfServiceConsent::SETTING_CHANGE) { |
| for (int grd_id : consent.description_grd_ids()) { |
| description_grd_ids.push_back(grd_id); |
| } |
| } else { |
| description_grd_ids.push_back( |
| consent.play_terms_of_service_text_length()); |
| |
| // TODO(markusheintz): The code below is a copy from the ARC code base. |
| // This will go away when the consent proto is set on the user consent |
| // specifics proto. |
| const std::string& hash_str = consent.play_terms_of_service_hash(); |
| DCHECK_EQ(base::kSHA1Length, hash_str.size()); |
| const uint8_t* hash = reinterpret_cast<const uint8_t*>(hash_str.data()); |
| for (size_t i = 0; i < base::kSHA1Length; i += 4) { |
| uint32_t acc = |
| hash[i] << 24 | hash[i + 1] << 16 | hash[i + 2] << 8 | hash[i + 3]; |
| description_grd_ids.push_back(static_cast<int>(acc)); |
| } |
| } |
| |
| RecordGaiaConsent(account_id, Feature::PLAY_STORE, description_grd_ids, |
| consent.confirmation_grd_id(), |
| ConvertConsentStatus(consent.status())); |
| } |
| } |
| |
| void ConsentAuditorImpl::RecordArcGoogleLocationServiceConsent( |
| const std::string& account_id, |
| const UserConsentTypes::ArcGoogleLocationServiceConsent& consent) { |
| if (IsSeparateConsentTypeEnabled()) { |
| std::unique_ptr<sync_pb::UserConsentSpecifics> specifics = |
| CreateUserConsentSpecifics(account_id, app_locale_, clock_); |
| |
| sync_pb::UserConsentTypes::ArcGoogleLocationServiceConsent* |
| arc_google_location_service_consent = |
| specifics->mutable_arc_location_service_consent(); |
| arc_google_location_service_consent->CopyFrom(consent); |
| consent_sync_bridge_->RecordConsent(std::move(specifics)); |
| } else { |
| std::vector<int> description_grd_ids(consent.description_grd_ids().begin(), |
| consent.description_grd_ids().end()); |
| |
| RecordGaiaConsent(account_id, Feature::GOOGLE_LOCATION_SERVICE, |
| description_grd_ids, consent.confirmation_grd_id(), |
| ConvertConsentStatus(consent.status())); |
| } |
| } |
| |
| void ConsentAuditorImpl::RecordArcBackupAndRestoreConsent( |
| const std::string& account_id, |
| const UserConsentTypes::ArcBackupAndRestoreConsent& consent) { |
| if (IsSeparateConsentTypeEnabled()) { |
| std::unique_ptr<sync_pb::UserConsentSpecifics> specifics = |
| CreateUserConsentSpecifics(account_id, app_locale_, clock_); |
| |
| sync_pb::UserConsentTypes::ArcBackupAndRestoreConsent* |
| arc_backup_and_restore_consent = |
| specifics->mutable_arc_backup_and_restore_consent(); |
| arc_backup_and_restore_consent->CopyFrom(consent); |
| consent_sync_bridge_->RecordConsent(std::move(specifics)); |
| } else { |
| std::vector<int> description_grd_ids(consent.description_grd_ids().begin(), |
| consent.description_grd_ids().end()); |
| |
| RecordGaiaConsent(account_id, Feature::BACKUP_AND_RESTORE, |
| description_grd_ids, consent.confirmation_grd_id(), |
| ConvertConsentStatus(consent.status())); |
| } |
| } |
| |
| void ConsentAuditorImpl::RecordSyncConsent( |
| const std::string& account_id, |
| const UserConsentTypes::SyncConsent& consent) { |
| if (IsSeparateConsentTypeEnabled()) { |
| std::unique_ptr<sync_pb::UserConsentSpecifics> specifics = |
| CreateUserConsentSpecifics(account_id, app_locale_, clock_); |
| |
| sync_pb::UserConsentTypes::SyncConsent* sync_consent = |
| specifics->mutable_sync_consent(); |
| sync_consent->CopyFrom(consent); |
| consent_sync_bridge_->RecordConsent(std::move(specifics)); |
| } else { |
| std::vector<int> description_grd_ids(consent.description_grd_ids().begin(), |
| consent.description_grd_ids().end()); |
| |
| RecordGaiaConsent(account_id, Feature::CHROME_SYNC, description_grd_ids, |
| consent.confirmation_grd_id(), |
| ConvertConsentStatus(consent.status())); |
| } |
| } |
| |
| void ConsentAuditorImpl::RecordUnifiedConsent( |
| const std::string& account_id, |
| const sync_pb::UserConsentTypes::UnifiedConsent& consent) { |
| if (IsSeparateConsentTypeEnabled()) { |
| std::unique_ptr<sync_pb::UserConsentSpecifics> specifics = |
| CreateUserConsentSpecifics(account_id, app_locale_, clock_); |
| |
| sync_pb::UserConsentTypes::UnifiedConsent* unified_consent = |
| specifics->mutable_unified_consent(); |
| unified_consent->CopyFrom(consent); |
| consent_sync_bridge_->RecordConsent(std::move(specifics)); |
| } else { |
| std::vector<int> description_grd_ids(consent.description_grd_ids().begin(), |
| consent.description_grd_ids().end()); |
| |
| RecordGaiaConsent(account_id, Feature::CHROME_UNIFIED_CONSENT, |
| description_grd_ids, consent.confirmation_grd_id(), |
| ConvertConsentStatus(consent.status())); |
| } |
| } |
| |
| void ConsentAuditorImpl::RecordAssistantActivityControlConsent( |
| const std::string& account_id, |
| const sync_pb::UserConsentTypes::AssistantActivityControlConsent& consent) { |
| // TODO(markusheintz): Turn this into a DCHECK once the fallback is not |
| // needed. |
| if (IsSeparateConsentTypeEnabled()) { |
| std::unique_ptr<sync_pb::UserConsentSpecifics> specifics = |
| CreateUserConsentSpecifics(account_id, app_locale_, clock_); |
| sync_pb::UserConsentTypes::AssistantActivityControlConsent* |
| assistant_consent = |
| specifics->mutable_assistant_activity_control_consent(); |
| assistant_consent->CopyFrom(consent); |
| |
| consent_sync_bridge_->RecordConsent(std::move(specifics)); |
| } else { |
| // TODO(markusheintz): This code was only added in case we have to fallback |
| // to the deprecated way of recording user consents via the |
| // UserEventSpecifics. Remove if not needed. |
| std::vector<int> description_grd_ids; |
| const std::string& ui_audit_key = consent.ui_audit_key(); |
| description_grd_ids.push_back(ui_audit_key.length()); |
| for (size_t i = 0; i < ui_audit_key.length(); i++) { |
| description_grd_ids.push_back(ui_audit_key[i]); |
| } |
| std::unique_ptr<sync_pb::UserEventSpecifics> user_event_specifics = |
| ConstructUserEventSpecifics(account_id, |
| Feature::ASSISTANT_ACTIVITY_CONTROL, |
| description_grd_ids, |
| 0, // No confirmation grd id recorded. |
| ConvertConsentStatus(consent.status())); |
| user_event_service_->RecordUserEvent(std::move(user_event_specifics)); |
| } |
| } |
| |
| void ConsentAuditorImpl::RecordLocalConsent( |
| const std::string& feature, |
| const std::string& description_text, |
| const std::string& confirmation_text) { |
| DictionaryPrefUpdate consents_update(pref_service_, |
| prefs::kLocalConsentsDictionary); |
| base::DictionaryValue* consents = consents_update.Get(); |
| DCHECK(consents); |
| |
| base::DictionaryValue record; |
| record.SetKey(kLocalConsentDescriptionKey, base::Value(description_text)); |
| record.SetKey(kLocalConsentConfirmationKey, base::Value(confirmation_text)); |
| record.SetKey(kLocalConsentVersionKey, base::Value(app_version_)); |
| record.SetKey(kLocalConsentLocaleKey, base::Value(app_locale_)); |
| |
| consents->SetKey(feature, std::move(record)); |
| } |
| |
| base::WeakPtr<syncer::ModelTypeControllerDelegate> |
| ConsentAuditorImpl::GetControllerDelegate() { |
| if (consent_sync_bridge_) { |
| return consent_sync_bridge_->GetControllerDelegate(); |
| } |
| return base::WeakPtr<syncer::ModelTypeControllerDelegate>(); |
| } |
| |
| } // namespace consent_auditor |