Commit b3e00d1a authored by Renato Silva's avatar Renato Silva Committed by Commit Bot

Chrome OS OOBE - Add the MarketingBackendConnector component

MarketingBackendConnector communicates over the Access Points API
to set the user's preference regarding marketing emails. The component
will be used on the last screen of OOBE - Marketing opt-in screen.

Bug: 1056672
Change-Id: I7f3b43b80395ab19c1dfc3313b91453f2e314bd8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2128492
Commit-Queue: Renato Silva <rrsilva@google.com>
Reviewed-by: default avatarDenis Kuznetsov [CET] <antrim@chromium.org>
Cr-Commit-Position: refs/heads/master@{#755927}
parent 123f3704
...@@ -1433,6 +1433,8 @@ source_set("chromeos") { ...@@ -1433,6 +1433,8 @@ source_set("chromeos") {
"login/login_screen_extensions_storage_cleaner.cc", "login/login_screen_extensions_storage_cleaner.cc",
"login/login_screen_extensions_storage_cleaner.h", "login/login_screen_extensions_storage_cleaner.h",
"login/login_wizard.h", "login/login_wizard.h",
"login/marketing_backend_connector.cc",
"login/marketing_backend_connector.h",
"login/mojo_system_info_dispatcher.cc", "login/mojo_system_info_dispatcher.cc",
"login/mojo_system_info_dispatcher.h", "login/mojo_system_info_dispatcher.h",
"login/oobe_configuration.cc", "login/oobe_configuration.cc",
......
// Copyright 2020 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/chromeos/login/marketing_backend_connector.h"
#include <cstddef>
#include "base/json/json_writer.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chromeos/constants/chromeos_switches.h"
#include "components/signin/public/identity_manager/access_token_info.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/consent_level.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/scope_set.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "net/base/load_flags.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_status_code.h"
namespace chromeos {
namespace {
// The scope that will be used to access the ChromebookEmailService API.
const char kChromebookOAuth2Scope[] =
"https://www.googleapis.com/auth/pixelbook.email.preferences";
// API Endpoint
const char kAccessPointsApiEndpoint[] = "https://accesspoints.googleapis.com/";
const char kChromebookEmailServicePath[] = "v2/chromebookEmailPreferences";
constexpr size_t kResponseMaxBodySize = 4 * 1024 * 1024; // 4MiB
const std::string GetEndpoint() {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(switches::kMarketingOptInUrl)) {
return command_line->GetSwitchValueASCII(switches::kMarketingOptInUrl);
} else {
return kAccessPointsApiEndpoint;
}
}
const GURL GetChromebookServiceEndpoint() {
return GURL(GetEndpoint() + std::string(kChromebookEmailServicePath));
}
// UMA Metrics
void RecordUMAHistogram(MarketingBackendConnector::UmaEvent event) {
// TODO (https://crbug.com/1056672)
}
std::unique_ptr<network::ResourceRequest> GetResourceRequest() {
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = GetChromebookServiceEndpoint();
resource_request->load_flags = net::LOAD_DISABLE_CACHE;
resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
resource_request->method = "POST";
return resource_request;
}
} // namespace
void MarketingBackendConnector::UpdateChromebookEmailPreferences() {
DCHECK(chromeos::ProfileHelper::Get());
DCHECK(user_manager::UserManager::Get());
VLOG(1) << "Subscribing the user to all chromebook email campaigns.";
const user_manager::User* user =
user_manager::UserManager::Get()->GetPrimaryUser();
if (!user)
return;
Profile* profile = chromeos::ProfileHelper::Get()->GetProfileByUser(user);
if (!profile)
return;
scoped_refptr<MarketingBackendConnector> ref =
new MarketingBackendConnector(profile);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::Bind(&MarketingBackendConnector::PerformRequest, ref));
}
MarketingBackendConnector::MarketingBackendConnector(Profile* profile)
: profile_(profile) {}
void MarketingBackendConnector::PerformRequest() {
StartTokenFetch();
}
void MarketingBackendConnector::StartTokenFetch() {
signin::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(profile_);
if (!identity_manager) {
RecordUMAHistogram(UmaEvent::ERROR_OTHER);
return;
}
signin::ScopeSet chromebook_scope;
chromebook_scope.insert(kChromebookOAuth2Scope);
token_fetcher_ = std::make_unique<signin::PrimaryAccountAccessTokenFetcher>(
"MarketingBackendConnector", identity_manager, chromebook_scope,
base::BindOnce(&MarketingBackendConnector::OnAccessTokenRequestCompleted,
this),
signin::PrimaryAccountAccessTokenFetcher::Mode::kImmediate);
}
void MarketingBackendConnector::OnAccessTokenRequestCompleted(
GoogleServiceAuthError error,
signin::AccessTokenInfo access_token_info) {
token_fetcher_.reset();
if (error.state() == GoogleServiceAuthError::NONE) {
access_token_ = access_token_info.token;
VLOG(2) << "Token fetch succeeded.";
SetTokenAndStartRequest();
} else {
VLOG(1) << "Auth Error: " << error.ToString();
RecordUMAHistogram(UmaEvent::ERROR_AUTH);
}
}
void MarketingBackendConnector::SetTokenAndStartRequest() {
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("chromebook_mail_api", R"(
semantics {
sender: "Chrome OS Marketing Opt-In Screen"
description:
"Communication with the Chromebook Email API to change the user's"
"preference regarding marketing emails. It is only used on the"
"last screen of the Chrome OS OOBE - Marketing Opt-In Screen.
trigger:
"The request is triggered when the user opts-in for marketing"
"emails by enabling the toggle on the marketing opt-in screen."
data:
"The only transmitted information is the country and the language"
"of the user's account. This information is used for delivering"
"emails to the user in the requested language."
destination: GOOGLE_OWNED_SERVICE
}
policy {
setting:
"Not opting-in to the emails will not generate a request."
cookies_allowed: NO
policy_exception_justification:
"Managed users are not presented with the option to opt-in."
}
})");
auto resource_request = GetResourceRequest();
resource_request->headers.SetHeader(net::HttpRequestHeaders::kAuthorization,
std::string("Bearer ") + access_token_);
simple_url_loader_ = network::SimpleURLLoader::Create(
std::move(resource_request), traffic_annotation);
simple_url_loader_->SetAllowHttpErrorResults(true);
simple_url_loader_->AttachStringForUpload(GetRequestContent(),
"application/json");
url_loader_factory_ = profile_->GetURLLoaderFactory();
simple_url_loader_->DownloadToString(
url_loader_factory_.get(),
base::BindOnce(&MarketingBackendConnector::OnSimpleLoaderComplete, this),
kResponseMaxBodySize);
}
void MarketingBackendConnector::OnSimpleLoaderComplete(
std::unique_ptr<std::string> response_body) {
int response_code = -1;
std::string raw_header;
if (simple_url_loader_->ResponseInfo() &&
simple_url_loader_->ResponseInfo()->headers) {
response_code =
simple_url_loader_->ResponseInfo()->headers->response_code();
}
std::string data;
if (response_body)
data = std::move(*response_body);
OnSimpleLoaderCompleteInternal(response_code, data);
}
void MarketingBackendConnector::OnSimpleLoaderCompleteInternal(
int response_code,
const std::string& data) {
VLOG(2) << "Response Code = " << response_code << " Data = " << data;
switch (response_code) {
case net::HTTP_OK: {
VLOG(1) << "Successfully set the user preferences on the server.";
RecordUMAHistogram(UmaEvent::SUCCESS);
return;
}
case net::HTTP_INTERNAL_SERVER_ERROR: {
VLOG(1) << "Internal server error occurred.";
RecordUMAHistogram(UmaEvent::ERROR_SERVER_INTERNAL);
return;
}
// Retry once in case of a timeout.
case net::HTTP_REQUEST_TIMEOUT: {
RecordUMAHistogram(UmaEvent::ERROR_REQUEST_TIMEOUT);
return;
}
}
// Failure. There is nothing we can do at this point.
RecordUMAHistogram(UmaEvent::ERROR_OTHER);
}
std::string MarketingBackendConnector::GetRequestContent() {
base::Value request_dict(base::Value::Type::DICTIONARY);
request_dict.SetKey("country_code", base::Value("us"));
request_dict.SetKey("language", base::Value("en"));
std::string request_content;
base::JSONWriter::Write(request_dict, &request_content);
return request_content;
}
MarketingBackendConnector::~MarketingBackendConnector() {}
} // namespace chromeos
// Copyright 2020 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.
#ifndef CHROME_BROWSER_CHROMEOS_LOGIN_MARKETING_BACKEND_CONNECTOR_H_
#define CHROME_BROWSER_CHROMEOS_LOGIN_MARKETING_BACKEND_CONNECTOR_H_
#include <memory>
#include <string>
#include "base/bind.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "chrome/browser/profiles/profile.h"
#include "components/signin/public/identity_manager/primary_account_access_token_fetcher.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
namespace chromeos {
class MarketingBackendConnector
: public base::RefCountedThreadSafe<MarketingBackendConnector> {
public:
MarketingBackendConnector(const MarketingBackendConnector&) = delete;
MarketingBackendConnector& operator=(const MarketingBackendConnector&) =
delete;
// A fire and forget method to be called on the marketing opt-in screen.
// It will create an instance of MarketingBackendConnectorthat calls the
// backend to update the user preferences.
static void UpdateChromebookEmailPreferences();
enum class UmaEvent {
// Differentiate between users who have a default opt-in vs default opt-out
USER_OPTED_IN_WHEN_DEFAULT_IS_OPT_IN,
USER_OPTED_IN_WHEN_DEFAULT_IS_OPT_OUT,
USER_OPTED_OUT_WHEN_DEFAULT_IS_OPT_IN,
USER_OPTED_OUT_WHEN_DEFAULT_IS_OPT_OUT,
// Successfully set the user preference on the server
SUCCESS,
// Possible errors to keep track of.
ERROR_SERVER_INTERNAL,
ERROR_REQUEST_TIMEOUT,
ERROR_AUTH,
ERROR_OTHER
};
private:
explicit MarketingBackendConnector(Profile* user_profile);
virtual ~MarketingBackendConnector();
// Sends a request to the server to subscribe the user to all campaigns.
void PerformRequest();
// Starts the token fetch process.
void StartTokenFetch();
// Handles the token fetch response.
void OnAccessTokenRequestCompleted(GoogleServiceAuthError error,
signin::AccessTokenInfo access_token_info);
// Sets the authentication token in the request header and starts the request
void SetTokenAndStartRequest();
// Handles responses from the SimpleURLLoader
void OnSimpleLoaderComplete(std::unique_ptr<std::string> response_body);
void OnSimpleLoaderCompleteInternal(int response_code,
const std::string& data);
// Generates the content of the request to be sent based on the country and
// the language.
std::string GetRequestContent();
// Internal
std::unique_ptr<signin::PrimaryAccountAccessTokenFetcher> token_fetcher_;
std::unique_ptr<network::SimpleURLLoader> simple_url_loader_;
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
std::string access_token_;
Profile* profile_ = nullptr;
friend class base::RefCountedThreadSafe<MarketingBackendConnector>;
};
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_LOGIN_MARKETING_BACKEND_CONNECTOR_H_
...@@ -267,6 +267,9 @@ const char kEnableHoudini[] = "enable-houdini"; ...@@ -267,6 +267,9 @@ const char kEnableHoudini[] = "enable-houdini";
// Enables the use of Houdini 64-bit library for ARM binary translation. // Enables the use of Houdini 64-bit library for ARM binary translation.
const char kEnableHoudini64[] = "enable-houdini64"; const char kEnableHoudini64[] = "enable-houdini64";
// Determines the URL to be used when calling the backend.
const char kMarketingOptInUrl[] = "marketing-opt-in-url";
// Enables the use of NDK translation library for ARM binary translation. // Enables the use of NDK translation library for ARM binary translation.
const char kEnableNdkTranslation[] = "enable-ndk-translation"; const char kEnableNdkTranslation[] = "enable-ndk-translation";
......
...@@ -163,6 +163,7 @@ COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kKernelnextRestrictVMs[]; ...@@ -163,6 +163,7 @@ COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kKernelnextRestrictVMs[];
COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kLoginManager[]; COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kLoginManager[];
COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kLoginProfile[]; COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kLoginProfile[];
COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kLoginUser[]; COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kLoginUser[];
COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kMarketingOptInUrl[];
COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kNaturalScrollDefault[]; COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kNaturalScrollDefault[];
COMPONENT_EXPORT(CHROMEOS_CONSTANTS) COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
extern const char kNeedArcMigrationPolicyCheck[]; extern const char kNeedArcMigrationPolicyCheck[];
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment