blob: ef874610bbe5fe466aae82d686555308a4aba660 [file] [log] [blame]
// 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/child_accounts/secondary_account_consent_logger.h"
#include <utility>
#include "base/bind.h"
#include "base/guid.h"
#include "base/json/json_writer.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "chrome/browser/supervised_user/child_accounts/kids_management_api.h"
#include "chrome/browser/supervised_user/supervised_user_constants.h"
#include "chromeos/constants/chromeos_pref_names.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/identity_manager/access_token_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/primary_account_access_token_fetcher.h"
#include "google_apis/gaia/core_account_id.h"
#include "google_apis/gaia/gaia_constants.h"
#include "google_apis/gaia/oauth2_access_token_manager.h"
#include "net/base/load_flags.h"
#include "net/http/http_status_code.h"
#include "net/traffic_annotation/network_traffic_annotation.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"
#include "url/gurl.h"
namespace {
constexpr char kConsentApiPath[] =
"people/me/consentsForSecondaryAccounts:create";
// Format of the text version is "v<GERRIT_CL_NUMBER>" with number of the last
// CL where strings with information for parents were changed.
constexpr char kConsentScreenTextVersion[] = "v2153049";
constexpr int kNumConsentLogRetries = 1;
constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("secondary_account_consent_logger",
R"(
semantics {
sender: "Consent logger for EDU account login flow"
description:
"Logs parent consent for addition of secondary accounts"
trigger:
"Triggered when child user adds account via EDU account addition"
"flow."
data:
"The request is authenticated with an OAuth2 access token "
"identifying the Google account. Secondary account email, parent"
"RAPT and obfuscated gaia id of the parent are sent."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: NO
setting: "This feature cannot be disabled by settings."
policy_exception_justification: "Not implemented."
})");
std::string GetOrCreateEduCoexistenceId(PrefService* pref_service) {
std::string id = pref_service->GetString(chromeos::prefs::kEduCoexistenceId);
if (id.empty()) {
id = base::GenerateGUID();
pref_service->SetString(chromeos::prefs::kEduCoexistenceId, id);
}
return id;
}
} // namespace
// static
void SecondaryAccountConsentLogger::RegisterPrefs(
PrefRegistrySimple* registry) {
registry->RegisterStringPref(chromeos::prefs::kEduCoexistenceId,
std::string() /* default_value */);
}
SecondaryAccountConsentLogger::SecondaryAccountConsentLogger(
signin::IdentityManager* identity_manager,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
PrefService* pref_service,
const std::string& secondary_account_email,
const std::string& parent_obfuscated_gaia_id,
const std::string& re_auth_proof_token,
base::OnceCallback<void(Result)> callback)
: primary_account_id_(identity_manager->GetPrimaryAccountId(
signin::ConsentLevel::kNotRequired)),
identity_manager_(identity_manager),
url_loader_factory_(std::move(url_loader_factory)),
pref_service_(pref_service),
secondary_account_email_(secondary_account_email),
parent_obfuscated_gaia_id_(parent_obfuscated_gaia_id),
re_auth_proof_token_(re_auth_proof_token),
callback_(std::move(callback)) {}
SecondaryAccountConsentLogger::~SecondaryAccountConsentLogger() = default;
void SecondaryAccountConsentLogger::StartLogging() {
OAuth2AccessTokenManager::ScopeSet scopes{
GaiaConstants::kKidManagementPrivilegedOAuth2Scope};
DCHECK(!access_token_fetcher_);
access_token_fetcher_ =
std::make_unique<signin::PrimaryAccountAccessTokenFetcher>(
"secondary_account_consent_logger", identity_manager_, scopes,
base::BindOnce(
&SecondaryAccountConsentLogger::OnAccessTokenFetchComplete,
base::Unretained(this)),
signin::PrimaryAccountAccessTokenFetcher::Mode::
kWaitUntilAvailable /*mode*/,
signin::ConsentLevel::kNotRequired /*consent*/);
}
void SecondaryAccountConsentLogger::OnAccessTokenFetchComplete(
GoogleServiceAuthError error,
signin::AccessTokenInfo access_token_info) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
access_token_fetcher_.reset();
if (error.state() != GoogleServiceAuthError::NONE) {
DLOG(WARNING) << "Failed to get an access token: " << error.ToString();
std::move(callback_).Run(Result::kTokenError);
return;
}
access_token_ = access_token_info.token;
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = kids_management_api::GetURL(kConsentApiPath);
resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
resource_request->method = "POST";
resource_request->headers.SetHeader(
net::HttpRequestHeaders::kAuthorization,
base::StringPrintf(supervised_users::kAuthorizationHeaderFormat,
access_token_.c_str()));
simple_url_loader_ = network::SimpleURLLoader::Create(
std::move(resource_request), kTrafficAnnotation);
std::string upload_data;
base::JSONWriter::Write(CreateRequestBody(), &upload_data);
simple_url_loader_->AttachStringForUpload(upload_data,
"application/json; charset=UTF-8");
simple_url_loader_->SetRetryOptions(
kNumConsentLogRetries, network::SimpleURLLoader::RETRY_ON_NETWORK_CHANGE);
simple_url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
url_loader_factory_.get(),
base::BindOnce(&SecondaryAccountConsentLogger::OnSimpleLoaderComplete,
base::Unretained(this)));
}
base::DictionaryValue SecondaryAccountConsentLogger::CreateRequestBody() const {
// See google3/google/internal/kidsmanagement/v1/kidsmanagement_messages.proto
// > CreateConsentForSecondaryAccountRequest
base::DictionaryValue request_body;
request_body.SetStringKey("person_id", "me");
base::DictionaryValue consent;
consent.SetStringKey("chrome_os_unicorn_edu_coexistence_id",
GetOrCreateEduCoexistenceId(pref_service_));
consent.SetStringKey("secondary_account_email", secondary_account_email_);
consent.SetStringKey("parent_id", parent_obfuscated_gaia_id_);
consent.SetStringKey("parent_rapt", re_auth_proof_token_);
consent.SetStringKey("text_version", kConsentScreenTextVersion);
request_body.SetKey("chrome_os_consent", std::move(consent));
return request_body;
}
void SecondaryAccountConsentLogger::OnSimpleLoaderComplete(
std::unique_ptr<std::string> response_body) {
int response_code = -1;
if (simple_url_loader_->ResponseInfo() &&
simple_url_loader_->ResponseInfo()->headers) {
response_code =
simple_url_loader_->ResponseInfo()->headers->response_code();
}
OnSimpleLoaderCompleteInternal(simple_url_loader_->NetError(), response_code);
}
void SecondaryAccountConsentLogger::OnSimpleLoaderCompleteInternal(
int net_error,
int response_code) {
if (response_code == net::HTTP_UNAUTHORIZED && !access_token_expired_) {
DVLOG(1) << "Access token expired, retrying";
access_token_expired_ = true;
OAuth2AccessTokenManager::ScopeSet scopes{
GaiaConstants::kKidManagementPrivilegedOAuth2Scope};
identity_manager_->RemoveAccessTokenFromCache(primary_account_id_, scopes,
access_token_);
StartLogging();
return;
}
if (response_code != net::HTTP_OK || net_error != net::OK) {
DLOG(WARNING) << "HTTP error " << response_code << ", NetError "
<< net_error;
std::move(callback_).Run(Result::kNetworkError);
return;
}
std::move(callback_).Run(Result::kSuccess);
}