blob: 1a250b9dc67b9e4f2238d3c1fcc1cabee9fe7e55 [file] [log] [blame]
// 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