// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/policy/enrollment/account_status_check_fetcher.h"
#include <string>
#include <utility>
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/uuid.h"
#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/enterprise/util/managed_browser_utils.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "components/policy/core/common/cloud/device_management_service.h"
#include "components/policy/core/common/cloud/dm_auth.h"
#include "components/policy/core/common/cloud/dmserver_job_configurations.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
namespace policy {
namespace {
namespace em = ::enterprise_management;
AccountStatus::Type ParseAccountStatusType(
const em::CheckUserAccountResponse& response,
const std::string& email) {
if (!response.has_user_account_type()) {
return AccountStatus::Type::kUnknown;
}
if (response.user_account_type() ==
em::CheckUserAccountResponse::UNKNOWN_USER_ACCOUNT_TYPE) {
return AccountStatus::Type::kUnknown;
}
if (response.user_account_type() == em::CheckUserAccountResponse::CONSUMER) {
const std::string domain = gaia::ExtractDomainName(email);
if (enterprise_util::IsKnownConsumerDomain(domain)) {
return AccountStatus::Type::kConsumerWithConsumerDomain;
}
return AccountStatus::Type::kConsumerWithBusinessDomain;
}
if (response.user_account_type() == em::CheckUserAccountResponse::DASHER) {
return AccountStatus::Type::kDasher;
}
if (response.user_account_type() == em::CheckUserAccountResponse::NOT_EXIST) {
if (!response.has_domain_verified()) {
return AccountStatus::Type::kUnknown;
}
if (response.domain_verified()) {
return AccountStatus::Type::kOrganisationalAccountVerified;
}
return AccountStatus::Type::kOrganisationalAccountUnverified;
}
return AccountStatus::Type::kUnknown;
}
EnrollmentNudgePolicyFetchResult ParseEnrollmentNudgePolicy(
const em::CheckUserAccountResponse& response) {
if (!response.has_enrollment_nudge_type()) {
return EnrollmentNudgePolicyFetchResult::kNoPolicyInResponse;
}
switch (response.enrollment_nudge_type()) {
case em::CheckUserAccountResponse::UNKNOWN_ENROLLMENT_NUDGE_TYPE:
return EnrollmentNudgePolicyFetchResult::kUnknown;
case em::CheckUserAccountResponse::NONE:
return EnrollmentNudgePolicyFetchResult::kAllowConsumerSignIn;
case em::CheckUserAccountResponse::ENROLLMENT_REQUIRED:
return EnrollmentNudgePolicyFetchResult::kEnrollmentRequired;
}
LOG(ERROR)
<< "Unexpected enrollment nudge policy value received from DM server: "
<< static_cast<int>(response.enrollment_nudge_type());
return EnrollmentNudgePolicyFetchResult::kUnknown;
}
void RecordAccountStatusCheckResult(AccountStatus::Type value) {
base::UmaHistogramEnumeration("Enterprise.AccountStatusCheckResult", value);
}
void RecordEnrollmentNudgePolicyFetchResult(
EnrollmentNudgePolicyFetchResult value) {
base::UmaHistogramEnumeration("Enterprise.EnrollmentNudge.PolicyFetchResult",
value);
}
} // namespace
bool operator==(const AccountStatus& lhs, const AccountStatus& rhs) {
return lhs.type == rhs.type &&
lhs.enrollment_required == rhs.enrollment_required;
}
bool operator!=(const AccountStatus& lhs, const AccountStatus& rhs) {
return !(lhs == rhs);
}
AccountStatusCheckFetcher::AccountStatusCheckFetcher(
const std::string& canonicalized_email)
: AccountStatusCheckFetcher(
canonicalized_email,
g_browser_process->platform_part()
->browser_policy_connector_ash()
->device_management_service(),
g_browser_process->system_network_context_manager()
->GetSharedURLLoaderFactory()) {}
AccountStatusCheckFetcher::AccountStatusCheckFetcher(
const std::string& canonicalized_email,
DeviceManagementService* service,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: email_(canonicalized_email),
service_(service),
url_loader_factory_(url_loader_factory),
random_device_id_(base::Uuid::GenerateRandomV4().AsLowercaseString()) {}
AccountStatusCheckFetcher::~AccountStatusCheckFetcher() = default;
void AccountStatusCheckFetcher::Fetch(FetchCallback callback,
bool fetch_enrollment_nudge_policy) {
CHECK(!callback_);
CHECK(callback);
callback_ = std::move(callback);
is_fetching_enrollment_nudge_policy_ = fetch_enrollment_nudge_policy;
std::unique_ptr<DMServerJobConfiguration> config =
std::make_unique<DMServerJobConfiguration>(
service_,
DeviceManagementService::JobConfiguration::TYPE_CHECK_USER_ACCOUNT,
random_device_id_, /*critical=*/false, DMAuth::NoAuth(),
/*oauth_token=*/std::nullopt, url_loader_factory_,
base::BindOnce(
&AccountStatusCheckFetcher::OnAccountStatusCheckReceived,
weak_ptr_factory_.GetWeakPtr()));
em::CheckUserAccountRequest* request =
config->request()->mutable_check_user_account_request();
request->set_user_email(email_);
request->set_enrollment_nudge_request(fetch_enrollment_nudge_policy);
fetch_request_job_ = service_->CreateJob(std::move(config));
}
void AccountStatusCheckFetcher::OnAccountStatusCheckReceived(
DMServerJobResult result) {
// TODO(crbug.com/40805389): Logging as "WARNING" to make sure it's preserved
// in the logs.
LOG(WARNING) << "Account check response received. DM Status: "
<< result.dm_status;
fetch_request_job_.reset();
std::string user_id;
bool fetch_succeeded = false;
AccountStatus account_status = {.type = AccountStatus::Type::kUnknown,
.enrollment_required = false};
switch (result.dm_status) {
case DM_STATUS_SUCCESS: {
if (!result.response.has_check_user_account_response()) {
LOG(WARNING) << "Invalid Account check response.";
break;
}
// Fetch has succeeded.
fetch_succeeded = true;
const em::CheckUserAccountResponse& response =
result.response.check_user_account_response();
const EnrollmentNudgePolicyFetchResult enrollment_nudge_policy =
ParseEnrollmentNudgePolicy(response);
account_status = {
.type = ParseAccountStatusType(response, email_),
.enrollment_required =
enrollment_nudge_policy ==
EnrollmentNudgePolicyFetchResult::kEnrollmentRequired};
if (is_fetching_enrollment_nudge_policy_) {
RecordEnrollmentNudgePolicyFetchResult(enrollment_nudge_policy);
} else {
// This call records UMA which is intended to reflect the account status
// checks in enrollment flow. Enrollment nudge use-cases should not
// affect it.
RecordAccountStatusCheckResult(account_status.type);
}
if (account_status.enrollment_required &&
account_status.type != AccountStatus::Type::kDasher) {
LOG(ERROR)
<< "Unexpected response from DM Server: Enrollment Nudge policy is "
"set to require enrollment for a non-Dasher account.";
account_status.enrollment_required = false;
}
break;
}
default: { // All other error cases
LOG(ERROR) << "Account check failed. DM Status: " << result.dm_status;
break;
}
}
std::move(callback_).Run(fetch_succeeded, account_status);
}
} // namespace policy