blob: 7b6f34edb89a7c58aad19965c842725041bb7536 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/commerce/core/account_checker.h"
#include "base/json/json_writer.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/commerce/core/commerce_constants.h"
#include "components/commerce/core/commerce_feature_list.h"
#include "components/commerce/core/commerce_utils.h"
#include "components/commerce/core/pref_names.h"
#include "components/endpoint_fetcher/endpoint_fetcher.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/identity_manager/account_capabilities.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/sync/base/features.h"
#include "components/sync/service/sync_service_utils.h"
#include "components/sync/service/sync_user_settings.h"
#include "components/unified_consent/url_keyed_data_collection_consent_helper.h"
#include "google_apis/gaia/gaia_constants.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/data_decoder/public/cpp/data_decoder.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
using endpoint_fetcher::EndpointFetcher;
using endpoint_fetcher::EndpointResponse;
namespace {
constexpr base::TimeDelta kTimeout = base::Milliseconds(10000);
const char kPriceTrackEmailPref[] = "price_track_email";
const char kPreferencesKey[] = "preferences";
} // namespace
namespace commerce {
const char kNotificationsPrefUrl[] =
"https://memex-pa.googleapis.com/v1/notifications/preferences";
AccountChecker::AccountChecker(
std::string country,
std::string locale,
PrefService* pref_service,
signin::IdentityManager* identity_manager,
syncer::SyncService* sync_service,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: country_(country),
locale_(locale),
pref_service_(pref_service),
identity_manager_(identity_manager),
sync_service_(sync_service),
url_loader_factory_(url_loader_factory),
weak_ptr_factory_(this) {
// TODO(crbug.com/40239641): Avoid pushing the fetched pref value to the
// server again.
if (pref_service) {
pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
pref_change_registrar_->Init(pref_service);
pref_change_registrar_->Add(
kPriceEmailNotificationsEnabled,
base::BindRepeating(&AccountChecker::OnPriceEmailPrefChanged,
weak_ptr_factory_.GetWeakPtr()));
}
}
AccountChecker::~AccountChecker() = default;
bool AccountChecker::IsSignedIn() {
if (base::FeatureList::IsEnabled(
syncer::kReplaceSyncPromosWithSignInPromos)) {
return identity_manager_ &&
identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSignin);
}
// The feature is not enabled, fallback to old behavior.
// TODO(crbug.com/40067058): Delete ConsentLevel::kSync usage once
// kReplaceSyncPromosWithSignInPromos is launched on all platforms. See
// ConsentLevel::kSync documentation for details.
return identity_manager_ &&
identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSync);
}
bool AccountChecker::IsSyncTypeEnabled(syncer::UserSelectableType type) {
return sync_service_ && sync_service_->GetUserSettings() &&
sync_service_->GetUserSettings()->GetSelectedTypes().Has(type);
}
bool AccountChecker::IsSyncAvailable() {
return sync_service_ &&
sync_service_->GetTransportState() !=
syncer::SyncService::TransportState::DISABLED &&
sync_service_->GetTransportState() !=
syncer::SyncService::TransportState::PAUSED &&
sync_service_->GetTransportState() !=
syncer::SyncService::TransportState::PENDING_DESIRED_CONFIGURATION;
}
bool AccountChecker::IsAnonymizedUrlDataCollectionEnabled() {
return pref_service_ &&
unified_consent::UrlKeyedDataCollectionConsentHelper::
NewAnonymizedDataCollectionConsentHelper(pref_service_)
->IsEnabled();
}
bool AccountChecker::IsSubjectToParentalControls() {
if (!identity_manager_) {
return false;
}
AccountCapabilities capabilities =
identity_manager_
->FindExtendedAccountInfo(identity_manager_->GetPrimaryAccountInfo(
signin::ConsentLevel::kSignin))
.capabilities;
return capabilities.is_subject_to_parental_controls() ==
signin::Tribool::kTrue;
}
bool AccountChecker::CanUseModelExecutionFeatures() {
if (!identity_manager_) {
return false;
}
AccountCapabilities capabilities =
identity_manager_
->FindExtendedAccountInfo(identity_manager_->GetPrimaryAccountInfo(
signin::ConsentLevel::kSignin))
.capabilities;
return capabilities.can_use_model_execution_features() ==
signin::Tribool::kTrue;
}
std::string AccountChecker::GetCountry() {
return country_;
}
std::string AccountChecker::GetLocale() {
return locale_;
}
PrefService* AccountChecker::GetPrefs() {
return pref_service_.get();
}
void AccountChecker::FetchPriceEmailPref() {
if (!IsSignedIn()) {
return;
}
is_waiting_for_pref_fetch_completion_ = true;
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation(
"chrome_commerce_price_email_pref_fetcher",
R"(
semantics {
sender: "Chrome Shopping"
description:
"Check whether the user paused receiving price drop emails."
"If it is paused, we need to update the preference value to "
"correctly reflect the user's choice in Chrome settings."
trigger:
"Every time when the user opens the Chrome settings."
data:
"The request includes an OAuth2 token authenticating the user. The "
"response includes a map of commerce notification preference key "
"strings to current user opt-in status."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: NO
setting:
"This fetch is only enabled for signed-in users. There's no "
"direct Chromium's setting to disable this, but users can manage "
"their preferences by visiting myactivity.google.com."
chrome_policy {
BrowserSignin {
policy_options {mode: MANDATORY}
BrowserSignin: 0
}
}
})");
auto endpoint_fetcher = CreateEndpointFetcher(
kOAuthName, GURL(kNotificationsPrefUrl),
endpoint_fetcher::HttpMethod::kGet, kContentType,
std::vector<std::string>{GaiaConstants::kChromeMemexOAuth2Scope},
kTimeout, kEmptyPostData, traffic_annotation);
endpoint_fetcher.get()->Fetch(base::BindOnce(
&AccountChecker::HandleFetchPriceEmailPrefResponse,
weak_ptr_factory_.GetWeakPtr(), std::move(endpoint_fetcher)));
}
void AccountChecker::HandleFetchPriceEmailPrefResponse(
std::unique_ptr<EndpointFetcher> endpoint_fetcher,
std::unique_ptr<EndpointResponse> responses) {
data_decoder::DataDecoder::ParseJsonIsolated(
responses->response,
base::BindOnce(&AccountChecker::OnFetchPriceEmailPrefJsonParsed,
weak_ptr_factory_.GetWeakPtr()));
}
void AccountChecker::OnFetchPriceEmailPrefJsonParsed(
data_decoder::DataDecoder::ValueOrError result) {
// Only update the pref if we're still waiting for the pref fetch completion.
// If users update the pref faster than we hear back from the server fetch,
// the fetched result should be discarded.
if (pref_service_ && is_waiting_for_pref_fetch_completion_ &&
result.has_value() && result->is_dict()) {
if (auto* preferences_map = result->GetDict().FindDict(kPreferencesKey)) {
if (std::optional<bool> price_email_pref =
preferences_map->FindBool(kPriceTrackEmailPref)) {
// Only set the pref value when necessary since it could affect
// PrefService::Preference::IsDefaultValue().
if (pref_service_->GetBoolean(kPriceEmailNotificationsEnabled) !=
*price_email_pref) {
ignore_next_email_pref_change_ = true;
pref_service_->SetBoolean(kPriceEmailNotificationsEnabled,
*price_email_pref);
}
}
}
}
is_waiting_for_pref_fetch_completion_ = false;
}
void AccountChecker::OnPriceEmailPrefChanged() {
// If users update the pref faster than we hear back from the server fetch,
// the fetched result should be discarded.
is_waiting_for_pref_fetch_completion_ = false;
if (ignore_next_email_pref_change_) {
ignore_next_email_pref_change_ = false;
return;
}
if (!IsSignedIn() || !pref_service_) {
return;
}
// Send the new value to server.
base::Value::Dict post_json = base::Value::Dict().Set(
kPreferencesKey,
base::Value::Dict().Set(
kPriceTrackEmailPref,
pref_service_->GetBoolean(kPriceEmailNotificationsEnabled)));
std::string post_data;
base::JSONWriter::Write(post_json, &post_data);
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation(
"chrome_commerce_price_email_pref_sender",
R"(
semantics {
sender: "Chrome Shopping"
description:
"Send the user's choice on whether to receive price drop emails."
trigger:
"Every time when the user changes their preference in the Chrome "
"settings."
data:
"The map of commerce notification preference key strings to the "
"new opt-in status. The request also includes an OAuth2 token "
"authenticating the user."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: NO
setting:
"This fetch is only enabled for signed-in users. There's no "
"direct Chromium's setting to disable this, but users can manage "
"their preferences by visiting myactivity.google.com."
chrome_policy {
BrowserSignin {
policy_options {mode: MANDATORY}
BrowserSignin: 0
}
}
})");
auto endpoint_fetcher = CreateEndpointFetcher(
kOAuthName, GURL(kNotificationsPrefUrl),
endpoint_fetcher::HttpMethod::kPost, kContentType,
std::vector<std::string>{GaiaConstants::kChromeMemexOAuth2Scope},
kTimeout, post_data, traffic_annotation);
endpoint_fetcher.get()->Fetch(base::BindOnce(
&AccountChecker::HandleSendPriceEmailPrefResponse,
weak_ptr_factory_.GetWeakPtr(), std::move(endpoint_fetcher)));
}
void AccountChecker::HandleSendPriceEmailPrefResponse(
std::unique_ptr<EndpointFetcher> endpoint_fetcher,
std::unique_ptr<EndpointResponse> responses) {
data_decoder::DataDecoder::ParseJsonIsolated(
responses->response,
base::BindOnce(&AccountChecker::OnSendPriceEmailPrefJsonParsed,
weak_ptr_factory_.GetWeakPtr()));
}
void AccountChecker::OnSendPriceEmailPrefJsonParsed(
data_decoder::DataDecoder::ValueOrError result) {
if (pref_service_ && result.has_value() && result->is_dict()) {
if (auto* preferences_map = result->GetDict().FindDict(kPreferencesKey)) {
if (auto price_email_pref =
preferences_map->FindBool(kPriceTrackEmailPref)) {
if (pref_service_->GetBoolean(kPriceEmailNotificationsEnabled) !=
*price_email_pref) {
VLOG(1) << "Fail to update the price email pref";
}
}
}
}
}
std::unique_ptr<EndpointFetcher> AccountChecker::CreateEndpointFetcher(
const std::string& oauth_consumer_name,
const GURL& url,
const endpoint_fetcher::HttpMethod http_method,
const std::string& content_type,
const std::vector<std::string>& scopes,
const base::TimeDelta& timeout,
const std::string& post_data,
const net::NetworkTrafficAnnotationTag& annotation_tag) {
// TODO(crbug.com/40067058): Delete ConsentLevel::kSync usage once
// kReplaceSyncPromosWithSignInPromos is launched on all platforms. See
// ConsentLevel::kSync documentation for details.
signin::ConsentLevel consent_level =
base::FeatureList::IsEnabled(syncer::kReplaceSyncPromosWithSignInPromos)
? signin::ConsentLevel::kSignin
: signin::ConsentLevel::kSync;
EndpointFetcher::RequestParams::Builder request_params =
EndpointFetcher::RequestParams::Builder(http_method, annotation_tag);
request_params.SetUrl(url)
.SetContentType(content_type)
.SetAuthType(endpoint_fetcher::OAUTH)
.SetOauthScopes(scopes)
.SetConsentLevel(consent_level)
.SetTimeout(timeout)
.SetOauthConsumerName(oauth_consumer_name)
.SetPostData(post_data);
MaybeUseAlternateShoppingServer(request_params);
return std::make_unique<EndpointFetcher>(
url_loader_factory_, identity_manager_, request_params.Build());
}
} // namespace commerce