blob: a1174343c8a986ef7be168f03b1d48ea0b22ba48 [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 "chrome/browser/ash/arc/session/arc_requirement_checker.h"
#include "ash/components/arc/arc_features.h"
#include "ash/components/arc/arc_prefs.h"
#include "ash/components/arc/arc_util.h"
#include "chrome/browser/ash/arc/arc_optin_uma.h"
#include "chrome/browser/ash/arc/arc_util.h"
#include "chrome/browser/ash/arc/optin/arc_terms_of_service_default_negotiator.h"
#include "chrome/browser/ash/arc/optin/arc_terms_of_service_oobe_negotiator.h"
#include "chrome/browser/ash/arc/policy/arc_policy_util.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/net/system_network_context_manager.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_thread.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
namespace arc {
namespace {
constexpr base::TimeDelta kWaitForPoliciesTimeout = base::Seconds(20);
// Flags used to control behaviors for tests.
// TODO(b/241886729): Remove or simplify these flags.
// Allows the session manager to skip creating UI in unit tests.
bool g_ui_enabled = true;
// Allows the session manager to create ArcTermsOfServiceOobeNegotiator in
// tests, even when the tests are set to skip creating UI.
bool g_enable_arc_terms_of_service_oobe_negotiator_in_tests = false;
absl::optional<bool> g_enable_check_android_management_in_tests;
policy::DeviceManagementService* GetDeviceManagementService() {
policy::BrowserPolicyConnectorAsh* const connector =
g_browser_process->platform_part()->browser_policy_connector_ash();
return connector->device_management_service();
}
// Returns the Device Account Id. Assumes that |profile| is the only Profile
// on Chrome OS.
CoreAccountId GetDeviceAccountId(Profile* profile) {
const auto* const identity_manager =
IdentityManagerFactory::GetForProfile(profile);
// The account is the same whether or not the user consented to browser sync.
return identity_manager->GetPrimaryAccountId(signin::ConsentLevel::kSignin);
}
std::unique_ptr<ArcAndroidManagementChecker> CreateAndroidManagementChecker(
Profile* profile,
bool retry_on_error) {
signin::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(profile);
const CoreAccountId device_account_id = GetDeviceAccountId(profile);
return std::make_unique<ArcAndroidManagementChecker>(
profile, identity_manager, device_account_id, retry_on_error,
std::make_unique<policy::AndroidManagementClientImpl>(
GetDeviceManagementService(),
g_browser_process->system_network_context_manager()
->GetSharedURLLoaderFactory(),
device_account_id, identity_manager));
}
} // namespace
// static
ArcRequirementChecker::AndroidManagementCheckerFactory
ArcRequirementChecker::GetDefaultAndroidManagementCheckerFactory() {
return base::BindRepeating(&CreateAndroidManagementChecker);
}
ArcRequirementChecker::ArcRequirementChecker(
Profile* profile,
ArcSupportHost* support_host,
AndroidManagementCheckerFactory android_management_checker_factory)
: profile_(profile),
support_host_(support_host),
android_management_checker_factory_(android_management_checker_factory) {}
ArcRequirementChecker::~ArcRequirementChecker() {
profile_->GetProfilePolicyConnector()->policy_service()->RemoveObserver(
policy::POLICY_DOMAIN_CHROME, this);
}
void ArcRequirementChecker::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void ArcRequirementChecker::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
// static
void ArcRequirementChecker::SetUiEnabledForTesting(bool enabled) {
g_ui_enabled = enabled;
}
// static
void ArcRequirementChecker::SetArcTermsOfServiceOobeNegotiatorEnabledForTesting(
bool enabled) {
g_enable_arc_terms_of_service_oobe_negotiator_in_tests = enabled;
}
// static
void ArcRequirementChecker::EnableCheckAndroidManagementForTesting(
bool enable) {
g_enable_check_android_management_in_tests = enable;
}
void ArcRequirementChecker::EmulateRequirementCheckCompletionForTesting() {
if (state_ == State::kNegotiatingTermsOfService)
OnTermsOfServiceNegotiated(true);
if (state_ == State::kCheckingAndroidManagement) {
OnAndroidManagementChecked(
ArcAndroidManagementChecker::CheckResult::ALLOWED);
}
}
void ArcRequirementChecker::StartRequirementChecks(
bool is_terms_of_service_negotiation_needed,
StartRequirementChecksCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_EQ(state_, State::kStopped);
DCHECK(profile_);
DCHECK(!terms_of_service_negotiator_);
DCHECK(!requirement_check_callback_);
state_ = State::kNegotiatingTermsOfService;
requirement_check_callback_ = std::move(callback);
if (!is_terms_of_service_negotiation_needed) {
// Moves to next state, Android management check, immediately, as if
// Terms of Service negotiation is done successfully.
StartAndroidManagementCheck();
return;
}
if (IsArcOobeOptInActive()) {
if (!g_enable_arc_terms_of_service_oobe_negotiator_in_tests &&
!g_ui_enabled) {
return;
}
VLOG(1) << "Use OOBE negotiator.";
terms_of_service_negotiator_ =
std::make_unique<ArcTermsOfServiceOobeNegotiator>();
} else if (support_host_) {
VLOG(1) << "Use default negotiator.";
terms_of_service_negotiator_ =
std::make_unique<ArcTermsOfServiceDefaultNegotiator>(
profile_->GetPrefs(), support_host_);
} else {
DCHECK(!g_ui_enabled) << "Negotiator is not created on production.";
return;
}
terms_of_service_negotiator_->StartNegotiation(
base::BindOnce(&ArcRequirementChecker::OnTermsOfServiceNegotiated,
weak_ptr_factory_.GetWeakPtr()));
}
void ArcRequirementChecker::StartBackgroundChecks(
StartBackgroundChecksCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_EQ(state_, State::kStopped);
DCHECK(!android_management_checker_);
DCHECK(!background_check_callback_);
DCHECK(!wait_for_policy_timer_.IsRunning());
state_ = State::kCheckingAndroidManagementBackground;
background_check_callback_ = std::move(callback);
// Skip Android management check for testing.
if (!g_enable_check_android_management_in_tests.value_or(g_ui_enabled))
return;
android_management_checker_ = android_management_checker_factory_.Run(
profile_, true /* retry_on_error */);
android_management_checker_->StartCheck(base::BindOnce(
&ArcRequirementChecker::OnBackgroundAndroidManagementChecked,
weak_ptr_factory_.GetWeakPtr()));
}
void ArcRequirementChecker::OnFirstPoliciesLoaded(policy::PolicyDomain domain) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_EQ(state_, State::kWaitingForPoliciesBackground);
DCHECK_EQ(domain, policy::POLICY_DOMAIN_CHROME);
wait_for_policy_timer_.Stop();
OnFirstPoliciesLoadedOrTimeout();
}
void ArcRequirementChecker::OnTermsOfServiceNegotiated(bool accepted) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_EQ(state_, State::kNegotiatingTermsOfService);
DCHECK(profile_);
DCHECK(terms_of_service_negotiator_ || !g_ui_enabled);
DCHECK(requirement_check_callback_);
terms_of_service_negotiator_.reset();
if (!accepted) {
VLOG(1) << "Terms of services declined";
state_ = State::kStopped;
std::move(requirement_check_callback_)
.Run(RequirementCheckResult::kTermsOfServicesDeclined);
return;
}
// Terms were accepted.
VLOG(1) << "Terms of services accepted";
profile_->GetPrefs()->SetBoolean(prefs::kArcTermsAccepted, true);
StartAndroidManagementCheck();
}
void ArcRequirementChecker::StartAndroidManagementCheck() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_EQ(state_, State::kNegotiatingTermsOfService);
state_ = State::kCheckingAndroidManagement;
// Show loading UI only if ARC support app's window is already shown.
// User may not see any ARC support UI if everything needed is done in
// background. In such a case, showing loading UI here (then closed sometime
// soon later) would look just noisy.
if (support_host_ &&
support_host_->ui_page() != ArcSupportHost::UIPage::NO_PAGE) {
support_host_->ShowArcLoading();
}
for (auto& observer : observers_)
observer.OnArcOptInManagementCheckStarted();
if (!g_ui_enabled)
return;
android_management_checker_ = android_management_checker_factory_.Run(
profile_, false /* retry_on_error */);
android_management_checker_->StartCheck(
base::BindOnce(&ArcRequirementChecker::OnAndroidManagementChecked,
weak_ptr_factory_.GetWeakPtr()));
}
void ArcRequirementChecker::OnAndroidManagementChecked(
ArcAndroidManagementChecker::CheckResult result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_EQ(state_, State::kCheckingAndroidManagement);
DCHECK(android_management_checker_ || !g_ui_enabled);
DCHECK(requirement_check_callback_);
android_management_checker_.reset();
state_ = State::kStopped;
switch (result) {
case ArcAndroidManagementChecker::CheckResult::ALLOWED:
std::move(requirement_check_callback_).Run(RequirementCheckResult::kOk);
return;
case ArcAndroidManagementChecker::CheckResult::DISALLOWED:
std::move(requirement_check_callback_)
.Run(RequirementCheckResult::kDisallowedByAndroidManagement);
return;
case ArcAndroidManagementChecker::CheckResult::ERROR:
std::move(requirement_check_callback_)
.Run(RequirementCheckResult::kAndroidManagementCheckError);
return;
}
NOTREACHED();
}
void ArcRequirementChecker::OnBackgroundAndroidManagementChecked(
ArcAndroidManagementChecker::CheckResult result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_EQ(state_, State::kCheckingAndroidManagementBackground);
DCHECK(background_check_callback_);
if (g_enable_check_android_management_in_tests.value_or(true)) {
DCHECK(android_management_checker_);
android_management_checker_.reset();
}
switch (result) {
case ArcAndroidManagementChecker::CheckResult::ALLOWED:
// Do nothing. ARC should be started already.
state_ = State::kStopped;
std::move(background_check_callback_)
.Run(BackgroundCheckResult::kNoActionRequired);
break;
case ArcAndroidManagementChecker::CheckResult::DISALLOWED:
if (base::FeatureList::IsEnabled(
arc::kEnableUnmanagedToManagedTransitionFeature)) {
state_ = State::kWaitingForPoliciesBackground;
WaitForPoliciesLoad();
} else {
state_ = State::kStopped;
std::move(background_check_callback_)
.Run(BackgroundCheckResult::kArcShouldBeDisabled);
}
break;
case ArcAndroidManagementChecker::CheckResult::ERROR:
// This code should not be reached. For background check,
// retry_on_error should be set.
NOTREACHED();
}
}
void ArcRequirementChecker::WaitForPoliciesLoad() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_EQ(state_, State::kWaitingForPoliciesBackground);
auto* policy_service =
profile_->GetProfilePolicyConnector()->policy_service();
// User might be transitioning to managed state, wait for policies load
// to confirm.
if (policy_service->IsFirstPolicyLoadComplete(policy::POLICY_DOMAIN_CHROME)) {
OnFirstPoliciesLoadedOrTimeout();
} else {
profile_->GetProfilePolicyConnector()->policy_service()->AddObserver(
policy::POLICY_DOMAIN_CHROME, this);
wait_for_policy_timer_.Start(
FROM_HERE, kWaitForPoliciesTimeout,
base::BindOnce(&ArcRequirementChecker::OnFirstPoliciesLoadedOrTimeout,
weak_ptr_factory_.GetWeakPtr()));
}
}
void ArcRequirementChecker::OnFirstPoliciesLoadedOrTimeout() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_EQ(state_, State::kWaitingForPoliciesBackground);
DCHECK(background_check_callback_);
state_ = State::kStopped;
profile_->GetProfilePolicyConnector()->policy_service()->RemoveObserver(
policy::POLICY_DOMAIN_CHROME, this);
// OnFirstPoliciesLoaded callback is triggered for both unmanaged and managed
// users, we need to check user state here.
// If timeout comes before policies are loaded, we fallback to calling
// SetArcPlayStoreEnabledForProfile(profile_, false).
if (arc::policy_util::IsAccountManaged(profile_)) {
// User has become managed, notify ARC by setting transition preference,
// which is eventually passed to ARC via ArcSession parameters.
profile_->GetPrefs()->SetInteger(
arc::prefs::kArcManagementTransition,
static_cast<int>(arc::ArcManagementTransition::UNMANAGED_TO_MANAGED));
// Restart ARC to perform managed re-provisioning.
// kArcIsManaged and kArcSignedIn are not reset during the restart.
// In case of successful re-provisioning, OnProvisioningFinished is called
// and kArcIsManaged is updated.
// In case of re-provisioning failure, ARC data is removed and transition
// preference is reset.
// In case Chrome is terminated during re-provisioning, user transition will
// be detected in ProfileManager::InitProfileUserPrefs, on next startup.
std::move(background_check_callback_)
.Run(BackgroundCheckResult::kArcShouldBeRestarted);
} else {
std::move(background_check_callback_)
.Run(BackgroundCheckResult::kArcShouldBeDisabled);
}
}
} // namespace arc