blob: 8213129f6a18b36f7adbf4cc1ed276f4ce39c90b [file] [log] [blame]
// Copyright 2015 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/auth/arc_auth_service.h"
#include <optional>
#include <utility>
#include <vector>
#include "ash/components/arc/arc_browser_context_keyed_service_factory_base.h"
#include "ash/components/arc/arc_features.h"
#include "ash/components/arc/arc_prefs.h"
#include "ash/components/arc/arc_util.h"
#include "ash/components/arc/mojom/auth.mojom-shared.h"
#include "ash/components/arc/session/arc_bridge_service.h"
#include "ash/components/arc/session/arc_management_transition.h"
#include "ash/components/arc/session/arc_service_manager.h"
#include "ash/constants/ash_switches.h"
#include "ash/webui/settings/public/constants/routes.mojom.h"
#include "base/command_line.h"
#include "base/containers/flat_set.h"
#include "base/functional/bind.h"
#include "base/memory/singleton.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "chrome/browser/ash/account_manager/account_apps_availability_factory.h"
#include "chrome/browser/ash/account_manager/account_manager_util.h"
#include "chrome/browser/ash/app_list/arc/arc_data_removal_dialog.h"
#include "chrome/browser/ash/arc/arc_optin_uma.h"
#include "chrome/browser/ash/arc/arc_util.h"
#include "chrome/browser/ash/arc/auth/arc_background_auth_code_fetcher.h"
#include "chrome/browser/ash/arc/auth/arc_robot_auth_code_fetcher.h"
#include "chrome/browser/ash/arc/policy/arc_policy_util.h"
#include "chrome/browser/ash/arc/session/arc_provisioning_result.h"
#include "chrome/browser/ash/arc/session/arc_session_manager.h"
#include "chrome/browser/ash/login/demo_mode/demo_session.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/signin/signin_ui_util.h"
#include "chrome/browser/ui/settings_window_manager_chromeos.h"
#include "chrome/browser/ui/webui/signin/ash/inline_login_dialog.h"
#include "chrome/common/webui_url_constants.h"
#include "components/account_manager_core/account_manager_facade.h"
#include "components/account_manager_core/chromeos/account_manager_facade_factory.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/base/consent_level.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/storage_partition.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
// Enable VLOG level 1.
#undef ENABLED_VLOG_LEVEL
#define ENABLED_VLOG_LEVEL 1
namespace arc {
namespace {
// Singleton factory for ArcAuthService.
class ArcAuthServiceFactory
: public internal::ArcBrowserContextKeyedServiceFactoryBase<
ArcAuthService,
ArcAuthServiceFactory> {
public:
// Factory name used by ArcBrowserContextKeyedServiceFactoryBase.
static constexpr const char* kName = "ArcAuthServiceFactory";
static ArcAuthServiceFactory* GetInstance() {
return base::Singleton<ArcAuthServiceFactory>::get();
}
private:
friend struct base::DefaultSingletonTraits<ArcAuthServiceFactory>;
ArcAuthServiceFactory() {
DependsOn(IdentityManagerFactory::GetInstance());
DependsOn(ash::AccountAppsAvailabilityFactory::GetInstance());
}
~ArcAuthServiceFactory() override = default;
};
mojom::ChromeAccountType GetAccountType(const Profile* profile) {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
DCHECK(command_line);
if (command_line->HasSwitch(
ash::switches::kDemoModeForceArcOfflineProvision)) {
return mojom::ChromeAccountType::OFFLINE_DEMO_ACCOUNT;
}
if (profile->IsChild()) {
return mojom::ChromeAccountType::CHILD_ACCOUNT;
}
auto* demo_session = ash::DemoSession::Get();
if (demo_session && demo_session->started()) {
// Internally, demo mode is implemented as a public session, and should
// generally follow normal robot account provisioning flow. Offline enrolled
// demo mode is an exception, as it is expected to work purely offline, with
// a (fake) robot account not known to auth service - this means that it has
// to go through different, offline provisioning flow.
DCHECK(IsRobotOrOfflineDemoAccountMode());
return mojom::ChromeAccountType::ROBOT_ACCOUNT;
}
return IsRobotOrOfflineDemoAccountMode()
? mojom::ChromeAccountType::ROBOT_ACCOUNT
: mojom::ChromeAccountType::USER_ACCOUNT;
}
mojom::AccountInfoPtr CreateAccountInfo(bool is_enforced,
const std::string& auth_info,
const std::string& account_name,
mojom::ChromeAccountType account_type,
bool is_managed) {
mojom::AccountInfoPtr account_info = mojom::AccountInfo::New();
account_info->account_name = account_name;
if (!is_enforced) {
account_info->auth_code = std::nullopt;
} else {
account_info->auth_code = auth_info;
}
account_info->account_type = account_type;
account_info->is_managed = is_managed;
return account_info;
}
bool IsPrimaryGaiaAccount(const std::string& gaia_id) {
// |GetPrimaryUser| is fine because ARC is only available on the first
// (Primary) account that participates in multi-signin.
const user_manager::User* user =
user_manager::UserManager::Get()->GetPrimaryUser();
DCHECK(user);
return user->GetAccountId().GetAccountType() == AccountType::GOOGLE &&
user->GetAccountId().GetGaiaId() == gaia_id;
}
bool IsPrimaryOrDeviceLocalAccount(
const signin::IdentityManager* identity_manager,
const std::string& account_name) {
// |GetPrimaryUser| is fine because ARC is only available on the first
// (Primary) account that participates in multi-signin.
const user_manager::User* user =
user_manager::UserManager::Get()->GetPrimaryUser();
DCHECK(user);
// There is no Gaia user for device local accounts, but in this case there is
// always only a primary account.
if (user->IsDeviceLocalAccount()) {
return true;
}
const AccountInfo account_info =
identity_manager->FindExtendedAccountInfoByEmailAddress(account_name);
if (account_info.IsEmpty()) {
return false;
}
DCHECK(!account_info.gaia.empty());
return IsPrimaryGaiaAccount(account_info.gaia);
}
// See //ash/components/arc/mojom/auth.mojom RequestPrimaryAccount() for the
// spec. See also go/arc-primary-account.
std::string GetAccountName(Profile* profile) {
switch (GetAccountType(profile)) {
case mojom::ChromeAccountType::USER_ACCOUNT:
[[fallthrough]];
case mojom::ChromeAccountType::CHILD_ACCOUNT:
// IdentityManager::GetPrimaryAccountInfo(
// signin::ConsentLevel::kSignin).email might be more appropriate
// here, but this is what we have done historically.
return ash::ProfileHelper::Get()
->GetUserByProfile(profile)
->GetDisplayEmail();
case mojom::ChromeAccountType::ROBOT_ACCOUNT:
[[fallthrough]];
case mojom::ChromeAccountType::OFFLINE_DEMO_ACCOUNT:
return std::string();
case mojom::ChromeAccountType::UNKNOWN:
NOTREACHED();
return std::string();
}
}
void OnFetchPrimaryAccountInfoCompleted(
ArcAuthService::RequestAccountInfoCallback callback,
bool persistent_error,
mojom::ArcAuthCodeStatus status,
mojom::AccountInfoPtr account_info) {
std::move(callback).Run(std::move(status), std::move(account_info),
persistent_error);
}
void CompleteFetchPrimaryAccountInfoWithMetrics(
ArcAuthService::RequestPrimaryAccountInfoCallback callback,
mojom::ArcAuthCodeStatus status,
mojom::AccountInfoPtr account_info) {
base::UmaHistogramEnumeration(
kArcAuthRequestAccountInfoResultPrimaryHistogramName, status);
std::move(callback).Run(std::move(status), std::move(account_info));
}
void CompleteFetchSecondaryAccountInfoWithMetrics(
ArcAuthService::RequestAccountInfoCallback callback,
mojom::ArcAuthCodeStatus status,
mojom::AccountInfoPtr account_info,
bool persistent_error) {
base::UmaHistogramEnumeration(
kArcAuthRequestAccountInfoResultSecondaryHistogramName, status);
std::move(callback).Run(std::move(status), std::move(account_info),
persistent_error);
}
} // namespace
// static
const char ArcAuthService::kArcServiceName[] = "arc::ArcAuthService";
// static
ArcAuthService* ArcAuthService::GetForBrowserContext(
content::BrowserContext* context) {
return ArcAuthServiceFactory::GetForBrowserContext(context);
}
ArcAuthService::ArcAuthService(content::BrowserContext* browser_context,
ArcBridgeService* arc_bridge_service)
: profile_(Profile::FromBrowserContext(browser_context)),
identity_manager_(IdentityManagerFactory::GetForProfile(profile_)),
arc_bridge_service_(arc_bridge_service),
url_loader_factory_(profile_->GetDefaultStoragePartition()
->GetURLLoaderFactoryForBrowserProcess()) {
arc_bridge_service_->auth()->SetHost(this);
arc_bridge_service_->auth()->AddObserver(this);
ArcSessionManager::Get()->AddObserver(this);
identity_manager_->AddObserver(this);
if (ash::IsAccountManagerAvailable(profile_) &&
ash::AccountAppsAvailability::IsArcAccountRestrictionsEnabled()) {
account_apps_availability_ =
ash::AccountAppsAvailabilityFactory::GetForProfile(profile_);
account_apps_availability_->AddObserver(this);
}
}
ArcAuthService::~ArcAuthService() {
ArcSessionManager::Get()->RemoveObserver(this);
arc_bridge_service_->auth()->RemoveObserver(this);
arc_bridge_service_->auth()->SetHost(nullptr);
}
void ArcAuthService::GetGoogleAccountsInArc(
GetGoogleAccountsInArcCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(pending_get_arc_accounts_callback_.is_null())
<< "Cannot have more than one pending GetGoogleAccountsInArc request";
if (!arc::IsArcProvisioned(profile_)) {
std::move(callback).Run(std::vector<mojom::ArcAccountInfoPtr>());
return;
}
if (!arc_bridge_service_->auth()->IsConnected()) {
pending_get_arc_accounts_callback_ = std::move(callback);
// Will be retried in |OnConnectionReady|.
return;
}
DispatchAccountsInArc(std::move(callback));
}
void ArcAuthService::RequestPrimaryAccount(
RequestPrimaryAccountCallback callback) {
std::move(callback).Run(GetAccountName(profile_), GetAccountType(profile_));
}
void ArcAuthService::OnConnectionReady() {
// `TriggerAccountsPushToArc()` will not be triggered for the first session,
// when ARC has not been provisioned yet. For the first session, an account
// push will be triggered by `OnArcInitialStart()`, after a successful device
// provisioning.
// For the second and subsequent sessions, `arc::IsArcProvisioned()` will be
// `true`.
if (arc::IsArcProvisioned(profile_)) {
TriggerAccountsPushToArc(false /* filter_primary_account */);
}
if (pending_get_arc_accounts_callback_) {
DispatchAccountsInArc(std::move(pending_get_arc_accounts_callback_));
}
// Report main account resolution status for provisioned devices.
if (!IsArcProvisioned(profile_)) {
return;
}
auto* instance = ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->auth(),
GetMainAccountResolutionStatus);
if (!instance) {
return;
}
instance->GetMainAccountResolutionStatus(
base::BindOnce(&ArcAuthService::OnMainAccountResolutionStatus,
weak_ptr_factory_.GetWeakPtr()));
}
void ArcAuthService::OnConnectionClosed() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
pending_token_requests_.clear();
}
void ArcAuthService::OnAuthorizationResult(mojom::ArcSignInResultPtr result,
mojom::ArcSignInAccountPtr account) {
ArcProvisioningResult provisioning_result(std::move(result));
if (account->is_initial_signin()) {
// UMA for initial signin is updated from ArcSessionManager.
ArcSessionManager::Get()->OnProvisioningFinished(provisioning_result);
return;
}
// Re-auth shouldn't be triggered for non-Gaia device local accounts.
if (!user_manager::UserManager::Get()->IsLoggedInAsUserWithGaiaAccount()) {
NOTREACHED() << "Shouldn't re-auth for non-Gaia accounts";
return;
}
const ProvisioningStatus status = GetProvisioningStatus(provisioning_result);
if (!account->is_account_name() || !account->get_account_name() ||
account->get_account_name().value().empty() ||
IsPrimaryOrDeviceLocalAccount(identity_manager_,
account->get_account_name().value())) {
// Reauthorization for the Primary Account.
// The check for |!account_name.has_value()| is for backwards compatibility
// with older ARC versions, for which Mojo will set |account_name| to
// empty/null.
UpdateReauthorizationResultUMA(status, profile_);
} else {
UpdateSecondarySigninResultUMA(status);
}
}
void ArcAuthService::ReportMetrics(mojom::MetricsType metrics_type,
int32_t value) {
switch (metrics_type) {
case mojom::MetricsType::NETWORK_WAITING_TIME_MILLISECONDS:
UpdateAuthTiming("Arc.Auth.NetworkWait.TimeDelta",
base::Milliseconds(value), profile_);
break;
case mojom::MetricsType::CHECKIN_ATTEMPTS:
UpdateAuthCheckinAttempts(value, profile_);
break;
case mojom::MetricsType::CHECKIN_TIME_MILLISECONDS:
UpdateAuthTiming("Arc.Auth.Checkin.TimeDelta", base::Milliseconds(value),
profile_);
break;
case mojom::MetricsType::SIGNIN_TIME_MILLISECONDS:
UpdateAuthTiming("Arc.Auth.SignIn.TimeDelta", base::Milliseconds(value),
profile_);
break;
case mojom::MetricsType::ACCOUNT_CHECK_MILLISECONDS:
UpdateAuthTiming("Arc.Auth.AccountCheck.TimeDelta",
base::Milliseconds(value), profile_);
break;
}
}
void ArcAuthService::ReportAccountCheckStatus(
mojom::AccountCheckStatus status) {
UpdateAuthAccountCheckStatus(status, profile_);
}
void ArcAuthService::ReportAccountReauthReason(mojom::ReauthReason reason) {
UpdateAccountReauthReason(reason, profile_);
}
void ArcAuthService::ReportManagementChangeStatus(
mojom::ManagementChangeStatus status) {
UpdateSupervisionTransitionResultUMA(status);
switch (status) {
case mojom::ManagementChangeStatus::CLOUD_DPC_DISABLED:
case mojom::ManagementChangeStatus::CLOUD_DPC_ALREADY_DISABLED:
case mojom::ManagementChangeStatus::CLOUD_DPC_ENABLED:
case mojom::ManagementChangeStatus::CLOUD_DPC_ALREADY_ENABLED:
profile_->GetPrefs()->SetInteger(
prefs::kArcManagementTransition,
static_cast<int>(ArcManagementTransition::NO_TRANSITION));
// TODO(brunokim): notify potential observers.
break;
case mojom::ManagementChangeStatus::CLOUD_DPC_DISABLING_FAILED:
case mojom::ManagementChangeStatus::CLOUD_DPC_ENABLING_FAILED:
LOG(ERROR) << "Management transition failed: " << status;
ShowDataRemovalConfirmationDialog(
profile_, base::BindOnce(&ArcAuthService::OnDataRemovalAccepted,
weak_ptr_factory_.GetWeakPtr()));
break;
case mojom::ManagementChangeStatus::INVALID_MANAGEMENT_STATE:
NOTREACHED() << "Invalid status of management transition: " << status;
}
}
void ArcAuthService::RequestPrimaryAccountInfo(
RequestPrimaryAccountInfoCallback callback) {
// This is the provisioning flow.
FetchPrimaryAccountInfo(
true /* initial_signin */,
base::BindOnce(&CompleteFetchPrimaryAccountInfoWithMetrics,
std::move(callback)));
}
void ArcAuthService::RequestAccountInfo(const std::string& account_name,
RequestAccountInfoCallback callback) {
// This is the post provisioning flow.
// This request could have come for re-authenticating an existing account in
// ARC, or for signing in a new Secondary Account.
// Check if |account_name| points to a Secondary Account.
if (!IsPrimaryOrDeviceLocalAccount(identity_manager_, account_name)) {
FetchSecondaryAccountInfo(
account_name,
base::BindOnce(&CompleteFetchSecondaryAccountInfoWithMetrics,
std::move(callback)));
return;
}
// TODO(solovey): Check secondary account ARC sign-in statistics and send
// |persistent_error| == true for primary account for cases when refresh token
// has persistent error.
FetchPrimaryAccountInfo(
false /* initial_signin */,
base::BindOnce(
&CompleteFetchPrimaryAccountInfoWithMetrics,
base::BindOnce(&OnFetchPrimaryAccountInfoCompleted,
std::move(callback), false /* persistent_error */)));
}
void ArcAuthService::FetchPrimaryAccountInfo(
bool initial_signin,
RequestPrimaryAccountInfoCallback callback) {
const mojom::ChromeAccountType account_type = GetAccountType(profile_);
if (IsArcOptInVerificationDisabled()) {
std::move(callback).Run(
mojom::ArcAuthCodeStatus::SUCCESS,
CreateAccountInfo(false /* is_enforced */,
std::string() /* auth_info */,
std::string() /* auth_name */, account_type,
policy_util::IsAccountManaged(profile_)));
return;
}
if (account_type == mojom::ChromeAccountType::OFFLINE_DEMO_ACCOUNT) {
// Skip account auth code fetch for offline enrolled demo mode.
std::move(callback).Run(
mojom::ArcAuthCodeStatus::SUCCESS,
CreateAccountInfo(true /* is_enforced */, std::string() /* auth_info */,
std::string() /* auth_name */, account_type,
true /* is_managed */));
return;
}
// For non-AD enrolled devices an auth code is fetched.
std::unique_ptr<ArcAuthCodeFetcher> auth_code_fetcher;
if (account_type == mojom::ChromeAccountType::ROBOT_ACCOUNT) {
// For robot accounts, which are used in kiosk and public session mode
// (which includes online demo sessions), use Robot auth code fetching.
auth_code_fetcher = std::make_unique<ArcRobotAuthCodeFetcher>();
if (url_loader_factory_for_testing_set_) {
static_cast<ArcRobotAuthCodeFetcher*>(auth_code_fetcher.get())
->SetURLLoaderFactoryForTesting(url_loader_factory_);
}
} else {
// Optionally retrieve auth code in silent mode. Use the "unconsented"
// primary account because this class doesn't care about browser sync
// consent.
DCHECK(identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSignin));
auth_code_fetcher = CreateArcBackgroundAuthCodeFetcher(
identity_manager_->GetPrimaryAccountId(signin::ConsentLevel::kSignin),
initial_signin);
}
// Add the request to |pending_token_requests_| first, before starting a token
// fetch. In case the callback is called immediately, we do not want to add an
// already completed request to |pending_token_requests_|.
auto* auth_code_fetcher_ptr = auth_code_fetcher.get();
pending_token_requests_.emplace_back(std::move(auth_code_fetcher));
auth_code_fetcher_ptr->Fetch(
base::BindOnce(&ArcAuthService::OnPrimaryAccountAuthCodeFetched,
weak_ptr_factory_.GetWeakPtr(), auth_code_fetcher_ptr,
std::move(callback)));
}
void ArcAuthService::IsAccountManagerAvailable(
IsAccountManagerAvailableCallback callback) {
std::move(callback).Run(ash::IsAccountManagerAvailable(profile_));
}
void ArcAuthService::HandleAddAccountRequest() {
DCHECK(ash::IsAccountManagerAvailable(profile_));
::GetAccountManagerFacade(profile_->GetPath().value())
->ShowAddAccountDialog(
account_manager::AccountManagerFacade::AccountAdditionSource::kArc);
}
void ArcAuthService::HandleRemoveAccountRequest(const std::string& email) {
DCHECK(ash::IsAccountManagerAvailable(profile_));
// TODO(b/326488045) Update Settings path to kPeopleSectionPath when Settings
// revamp is launched.
chrome::SettingsWindowManager::GetInstance()->ShowOSSettings(
profile_, chromeos::settings::mojom::kMyAccountsSubpagePath);
}
void ArcAuthService::HandleUpdateCredentialsRequest(const std::string& email) {
DCHECK(ash::IsAccountManagerAvailable(profile_));
::GetAccountManagerFacade(profile_->GetPath().value())
->ShowReauthAccountDialog(
account_manager::AccountManagerFacade::AccountAdditionSource::kArc,
email, base::DoNothing());
}
void ArcAuthService::OnRefreshTokenUpdatedForAccount(
const CoreAccountInfo& account_info) {
// Should be consistent with OnAccountAvailableInArc.
// TODO(crbug/1260909): Remove IdentityManager::Observer implementation.
if (ash::AccountAppsAvailability::IsArcAccountRestrictionsEnabled()) {
return;
}
UpsertAccountToArc(account_info);
}
void ArcAuthService::OnExtendedAccountInfoRemoved(
const AccountInfo& account_info) {
// Should be consistent with OnAccountUnavailableInArc.
// TODO(crbug/1260909): Remove IdentityManager::Observer implementation.
if (ash::AccountAppsAvailability::IsArcAccountRestrictionsEnabled()) {
return;
}
DCHECK(!IsPrimaryGaiaAccount(account_info.gaia));
RemoveAccountFromArc(account_info.email);
}
void ArcAuthService::OnAccountAvailableInArc(
const account_manager::Account& account) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(ash::AccountAppsAvailability::IsArcAccountRestrictionsEnabled());
DCHECK(ash::IsAccountManagerAvailable(profile_));
CoreAccountInfo account_info =
identity_manager_->FindExtendedAccountInfoByEmailAddress(
account.raw_email);
// If account doesn't have a refresh token, `account_info` will be empty. In
// this case `OnAccountAvailableInArc` will be called again after the refresh
// token is loaded.
if (account_info.IsEmpty()) {
VLOG(1) << "Ignoring account update because CoreAccountInfo is empty for "
"account: "
<< account.raw_email;
return;
}
UpsertAccountToArc(account_info);
}
void ArcAuthService::OnAccountUnavailableInArc(
const account_manager::Account& account) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(ash::AccountAppsAvailability::IsArcAccountRestrictionsEnabled());
DCHECK(ash::IsAccountManagerAvailable(profile_));
DCHECK(!IsPrimaryGaiaAccount(account.key.id()));
RemoveAccountFromArc(account.raw_email);
}
void ArcAuthService::OnArcInitialStart() {
TriggerAccountsPushToArc(true /* filter_primary_account */);
}
void ArcAuthService::Shutdown() {
identity_manager_->RemoveObserver(this);
if (account_apps_availability_) {
account_apps_availability_->RemoveObserver(this);
}
}
void ArcAuthService::UpsertAccountToArc(const CoreAccountInfo& account_info) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!ash::IsAccountManagerAvailable(profile_)) {
return;
}
// Ignore the update if ARC has not been provisioned yet.
if (!arc::IsArcProvisioned(profile_)) {
return;
}
if (identity_manager_->HasAccountWithRefreshTokenInPersistentErrorState(
account_info.account_id)) {
VLOG(1) << "Ignoring account update due to lack of a valid token: "
<< account_info.email;
return;
}
auto* instance = ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->auth(),
OnAccountUpdated);
if (!instance) {
return;
}
const std::string account_name = account_info.email;
DCHECK(!account_name.empty());
instance->OnAccountUpdated(account_name, mojom::AccountUpdateType::UPSERT);
}
void ArcAuthService::RemoveAccountFromArc(const std::string& email) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!ash::IsAccountManagerAvailable(profile_)) {
return;
}
// Ignore the update if ARC has not been provisioned yet.
if (!arc::IsArcProvisioned(profile_)) {
return;
}
auto* instance = ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->auth(),
OnAccountUpdated);
if (!instance) {
return;
}
DCHECK(!email.empty());
instance->OnAccountUpdated(email, mojom::AccountUpdateType::REMOVAL);
}
void ArcAuthService::OnPrimaryAccountAuthCodeFetched(
ArcAuthCodeFetcher* fetcher,
RequestPrimaryAccountInfoCallback callback,
bool success,
const std::string& auth_code) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// |fetcher| will be invalid after this.
DeletePendingTokenRequest(fetcher);
if (success) {
const std::string& full_account_id = GetAccountName(profile_);
std::move(callback).Run(
mojom::ArcAuthCodeStatus::SUCCESS,
CreateAccountInfo(!IsArcOptInVerificationDisabled(), auth_code,
full_account_id, GetAccountType(profile_),
policy_util::IsAccountManaged(profile_)));
} else if (ash::DemoSession::Get() && ash::DemoSession::Get()->started()) {
// For demo sessions, if auth code fetch failed (e.g. because the device is
// offline), fall back to accountless offline demo mode provisioning.
std::move(callback).Run(
mojom::ArcAuthCodeStatus::SUCCESS,
CreateAccountInfo(true /* is_enforced */, std::string() /* auth_info */,
std::string() /* auth_name */,
mojom::ChromeAccountType::OFFLINE_DEMO_ACCOUNT,
true /* is_managed */));
} else {
// Send error to ARC.
std::move(callback).Run(
mojom::ArcAuthCodeStatus::CHROME_SERVER_COMMUNICATION_ERROR, nullptr);
}
}
void ArcAuthService::FetchSecondaryAccountInfo(
const std::string& account_name,
RequestAccountInfoCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
AccountInfo account_info =
identity_manager_->FindExtendedAccountInfoByEmailAddress(account_name);
if (account_info.IsEmpty()) {
// Account is in ARC, but not in Chrome OS Account Manager.
std::move(callback).Run(mojom::ArcAuthCodeStatus::CHROME_ACCOUNT_NOT_FOUND,
nullptr /* account_info */,
true /* persistent_error */);
return;
}
const CoreAccountId& account_id = account_info.account_id;
DCHECK(!account_id.empty());
if (identity_manager_->HasAccountWithRefreshTokenInPersistentErrorState(
account_id)) {
std::move(callback).Run(
mojom::ArcAuthCodeStatus::CHROME_SERVER_COMMUNICATION_ERROR,
nullptr /* account_info */, true /* persistent_error */);
return;
}
std::unique_ptr<ArcBackgroundAuthCodeFetcher> fetcher =
CreateArcBackgroundAuthCodeFetcher(account_id,
false /* initial_signin */);
// Add the request to |pending_token_requests_| first, before starting a
// token fetch. In case the callback is called immediately, we do not want
// to add an already completed request to |pending_token_requests_|.
auto* fetcher_ptr = fetcher.get();
pending_token_requests_.emplace_back(std::move(fetcher));
fetcher_ptr->Fetch(
base::BindOnce(&ArcAuthService::OnSecondaryAccountAuthCodeFetched,
weak_ptr_factory_.GetWeakPtr(), account_name, fetcher_ptr,
std::move(callback)));
}
void ArcAuthService::OnSecondaryAccountAuthCodeFetched(
const std::string& account_name,
ArcBackgroundAuthCodeFetcher* fetcher,
RequestAccountInfoCallback callback,
bool success,
const std::string& auth_code) {
// |fetcher| will be invalid after this.
DeletePendingTokenRequest(fetcher);
if (success) {
std::move(callback).Run(
mojom::ArcAuthCodeStatus::SUCCESS,
CreateAccountInfo(true /* is_enforced */, auth_code, account_name,
mojom::ChromeAccountType::USER_ACCOUNT,
false /* is_managed */),
false /* persistent_error*/);
return;
}
AccountInfo account_info =
identity_manager_->FindExtendedAccountInfoByEmailAddress(account_name);
// Take care of the case when the user removes an account immediately after
// adding/re-authenticating it.
if (!account_info.IsEmpty()) {
const bool is_persistent_error =
identity_manager_->HasAccountWithRefreshTokenInPersistentErrorState(
account_info.account_id);
std::move(callback).Run(
mojom::ArcAuthCodeStatus::CHROME_SERVER_COMMUNICATION_ERROR,
nullptr /* account_info */, is_persistent_error);
return;
}
std::move(callback).Run(mojom::ArcAuthCodeStatus::CHROME_ACCOUNT_NOT_FOUND,
nullptr /* account_info */, true);
}
void ArcAuthService::DeletePendingTokenRequest(ArcFetcherBase* fetcher) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
for (auto it = pending_token_requests_.begin();
it != pending_token_requests_.end(); ++it) {
if (it->get() == fetcher) {
pending_token_requests_.erase(it);
return;
}
}
// We should not have received a call to delete a |fetcher| that was not in
// |pending_token_requests_|.
NOTREACHED();
}
void ArcAuthService::SetURLLoaderFactoryForTesting(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) {
url_loader_factory_ = std::move(url_loader_factory);
url_loader_factory_for_testing_set_ = true;
}
void ArcAuthService::OnDataRemovalAccepted(bool accepted) {
if (!accepted) {
return;
}
if (!IsArcPlayStoreEnabledForProfile(profile_)) {
return;
}
VLOG(1)
<< "Request for data removal on child transition failure is confirmed";
ArcSessionManager::Get()->RequestArcDataRemoval();
ArcSessionManager::Get()->StopAndEnableArc();
}
std::unique_ptr<ArcBackgroundAuthCodeFetcher>
ArcAuthService::CreateArcBackgroundAuthCodeFetcher(
const CoreAccountId& account_id,
bool initial_signin) {
const AccountInfo account_info =
identity_manager_->FindExtendedAccountInfoByAccountId(account_id);
DCHECK(!account_info.IsEmpty());
auto fetcher = std::make_unique<ArcBackgroundAuthCodeFetcher>(
url_loader_factory_, profile_, account_id, initial_signin,
IsPrimaryGaiaAccount(account_info.gaia));
return fetcher;
}
void ArcAuthService::TriggerAccountsPushToArc(bool filter_primary_account) {
if (!ash::IsAccountManagerAvailable(profile_)) {
return;
}
VLOG(1) << "Pushing accounts to ARC "
<< (filter_primary_account ? "without primary account"
: "with primary account");
if (ash::AccountAppsAvailability::IsArcAccountRestrictionsEnabled()) {
VLOG(1) << "Using AccountAppsAvailability to get available accounts";
account_apps_availability_->GetAccountsAvailableInArc(
base::BindOnce(&ArcAuthService::CompleteAccountsPushToArc,
weak_ptr_factory_.GetWeakPtr(), filter_primary_account));
return;
}
const std::vector<CoreAccountInfo> accounts =
identity_manager_->GetAccountsWithRefreshTokens();
for (const CoreAccountInfo& account : accounts) {
if (filter_primary_account && IsPrimaryGaiaAccount(account.gaia)) {
continue;
}
OnRefreshTokenUpdatedForAccount(account);
}
}
void ArcAuthService::CompleteAccountsPushToArc(
bool filter_primary_account,
const base::flat_set<account_manager::Account>& accounts) {
DCHECK(ash::AccountAppsAvailability::IsArcAccountRestrictionsEnabled());
std::vector<mojom::ArcAccountInfoPtr> arc_accounts =
std::vector<mojom::ArcAccountInfoPtr>();
for (const auto& account : accounts) {
DCHECK(account.key.account_type() == account_manager::AccountType::kGaia);
if (filter_primary_account && IsPrimaryGaiaAccount(account.key.id())) {
continue;
}
arc_accounts.emplace_back(mojom::ArcAccountInfo::New(
/*email=*/account.raw_email, /*gaia_id=*/account.key.id()));
}
auto* instance =
ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->auth(), SetAccounts);
if (!instance) {
VLOG(1) << "SetAccounts API is not available in ARC. Fallback to "
"OnAccountAvailableInArc";
for (const auto& account : accounts) {
DCHECK(account.key.account_type() == account_manager::AccountType::kGaia);
if (filter_primary_account && IsPrimaryGaiaAccount(account.key.id())) {
continue;
}
OnAccountAvailableInArc(account);
}
return;
}
instance->SetAccounts(std::move(arc_accounts));
}
void ArcAuthService::DispatchAccountsInArc(
GetGoogleAccountsInArcCallback callback) {
auto* instance = ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->auth(),
GetGoogleAccounts);
if (!instance) {
// Complete the callback so that it is not kept waiting forever.
std::move(callback).Run(std::vector<mojom::ArcAccountInfoPtr>());
return;
}
instance->GetGoogleAccounts(std::move(callback));
}
void ArcAuthService::OnMainAccountResolutionStatus(
mojom::MainAccountResolutionStatus status) {
UpdateMainAccountResolutionStatus(profile_, status);
}
// static
void ArcAuthService::EnsureFactoryBuilt() {
ArcAuthServiceFactory::GetInstance();
}
} // namespace arc