blob: dff70fd51543730c062e2360e4edddc2b38a759a [file] [log] [blame]
// Copyright 2017 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/signin/signin_util.h"
#include <memory>
#include "base/barrier_closure.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/supports_user_data.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/enterprise/profile_management/profile_management_features.h"
#include "chrome/browser/enterprise/util/managed_browser_utils.h"
#include "chrome/browser/policy/cloud/user_policy_signin_service_internal.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profiles_state.h"
#include "chrome/browser/signin/account_reconcilor_factory.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "components/google/core/common/google_util.h"
#include "components/policy/core/browser/signin/profile_separation_policies.h"
#include "components/prefs/pref_service.h"
#include "components/signin/core/browser/account_reconcilor.h"
#include "components/signin/public/base/signin_pref_names.h"
#include "components/signin/public/base/signin_switches.h"
#include "components/signin/public/identity_manager/account_managed_status_finder.h"
#include "components/signin/public/identity_manager/accounts_mutator.h"
#include "content/public/browser/storage_partition.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "google_apis/gaia/gaia_constants.h"
#include "google_apis/gaia/gaia_id.h"
#include "net/cookies/canonical_cookie.h"
#include "services/network/public/mojom/cookie_manager.mojom.h"
#include "ui/base/l10n/l10n_util.h"
namespace signin_util {
namespace {
enum ForceSigninPolicyCache {
NOT_CACHED = 0,
ENABLE,
DISABLE
} g_is_force_signin_enabled_cache = NOT_CACHED;
void SetForceSigninPolicy(bool enable) {
g_is_force_signin_enabled_cache = enable ? ENABLE : DISABLE;
}
} // namespace
ScopedForceSigninSetterForTesting::ScopedForceSigninSetterForTesting(
bool enable) {
SetForceSigninForTesting(enable); // IN-TEST
}
ScopedForceSigninSetterForTesting::~ScopedForceSigninSetterForTesting() {
ResetForceSigninForTesting(); // IN-TEST
}
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)
CookiesMover::CookiesMover(base::WeakPtr<Profile> source_profile,
base::WeakPtr<Profile> destination_profile,
base::OnceCallback<void()> callback)
: url_(source_profile->GetPrefs()->GetString(
prefs::kSigninInterceptionIDPCookiesUrl)),
source_profile_(std::move(source_profile)),
destination_profile_(std::move(destination_profile)),
callback_(std::move(callback)) {
CHECK(callback_);
}
CookiesMover::~CookiesMover() = default;
void CookiesMover::StartMovingCookies() {
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN)
bool allow_cookies_to_be_moved = base::FeatureList::IsEnabled(
profile_management::features::kThirdPartyProfileManagement);
#else
bool allow_cookies_to_be_moved = false;
#endif
if (!allow_cookies_to_be_moved || url_.is_empty() || !url_.is_valid()) {
std::move(callback_).Run();
return;
}
source_profile_->GetPrefs()->ClearPref(
prefs::kSigninInterceptionIDPCookiesUrl);
source_profile_->GetDefaultStoragePartition()
->GetCookieManagerForBrowserProcess()
->GetCookieList(url_, net::CookieOptions::MakeAllInclusive(),
net::CookiePartitionKeyCollection::Todo(),
base::BindOnce(&CookiesMover::OnCookiesReceived,
weak_pointer_factory_.GetWeakPtr()));
}
void CookiesMover::OnCookiesReceived(
const std::vector<net::CookieWithAccessResult>& included,
const std::vector<net::CookieWithAccessResult>& excluded) {
// If either profile was destroyed, stop the operation.
if (source_profile_.WasInvalidated() ||
destination_profile_.WasInvalidated()) {
std::move(callback_).Run();
return;
}
// We expected 2 * `cookies.size()` actions since we have to set the cookie at
// destination and delete it from the source.
base::RepeatingClosure barrier = base::BarrierClosure(
included.size() * 2, base::BindOnce(&CookiesMover::OnCookiesMoved,
weak_pointer_factory_.GetWeakPtr()));
auto* source_cookie_manager = source_profile_->GetDefaultStoragePartition()
->GetCookieManagerForBrowserProcess();
auto* destination_cookie_manager =
destination_profile_->GetDefaultStoragePartition()
->GetCookieManagerForBrowserProcess();
for (const auto& [cookie, _] : included) {
destination_cookie_manager->SetCanonicalCookie(
cookie, url_, net::CookieOptions::MakeAllInclusive(),
base::IgnoreArgs<net::CookieAccessResult>(barrier));
source_cookie_manager->DeleteCanonicalCookie(
cookie, base::IgnoreArgs<bool>(barrier));
}
}
void CookiesMover::OnCookiesMoved() {
std::move(callback_).Run();
}
#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)
bool IsForceSigninEnabled() {
if (g_is_force_signin_enabled_cache == NOT_CACHED) {
PrefService* prefs = g_browser_process->local_state();
if (prefs)
SetForceSigninPolicy(prefs->GetBoolean(prefs::kForceBrowserSignin));
else
return false;
}
return (g_is_force_signin_enabled_cache == ENABLE);
}
void SetForceSigninForTesting(bool enable) {
SetForceSigninPolicy(enable);
}
void ResetForceSigninForTesting() {
g_is_force_signin_enabled_cache = NOT_CACHED;
}
bool IsProfileDeletionAllowed(Profile* profile) {
#if BUILDFLAG(IS_CHROMEOS_LACROS)
return !profile->IsMainProfile();
#elif BUILDFLAG(IS_ANDROID)
return false;
#else
return true;
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
}
#if !BUILDFLAG(IS_ANDROID)
#if !BUILDFLAG(IS_CHROMEOS)
// Returns true if managed accounts signin are required to create a new profile
// by policies set in `profile`.
bool IsProfileSeparationEnforcedByProfile(
Profile* profile,
const std::string& intercepted_account_email) {
if (!intercepted_account_email.empty() &&
!IsAccountExemptedFromEnterpriseProfileSeparation(
profile, intercepted_account_email)) {
return true;
}
std::string legacy_policy_for_current_profile =
profile->GetPrefs()->GetString(prefs::kManagedAccountsSigninRestriction);
bool enforced_by_existing_profile = base::StartsWith(
legacy_policy_for_current_profile, "primary_account_strict");
bool enforced_at_machine_level =
base::StartsWith(legacy_policy_for_current_profile, "primary_account") &&
profile->GetPrefs()->GetBoolean(
prefs::kManagedAccountsSigninRestrictionScopeMachine);
return enforced_by_existing_profile || enforced_at_machine_level;
}
// Returns true if profile separation is enforced by
// `intercepted_account_separation_policies`.
bool IsProfileSeparationEnforcedByPolicies(
const policy::ProfileSeparationPolicies&
intercepted_account_separation_policies) {
if (intercepted_account_separation_policies.profile_separation_settings()
.value_or(policy::ProfileSeparationSettings::SUGGESTED) ==
policy::ProfileSeparationSettings::ENFORCED) {
return true;
}
std::string legacy_policy_for_intercepted_profile =
intercepted_account_separation_policies
.managed_accounts_signin_restrictions()
.value_or(std::string());
return base::StartsWith(legacy_policy_for_intercepted_profile,
"primary_account");
}
bool ProfileSeparationAllowsKeepingUnmanagedBrowsingDataInManagedProfile(
Profile* profile,
const policy::ProfileSeparationPolicies&
intercepted_account_separation_policies) {
// We should not move managed data.
if (enterprise_util::UserAcceptedAccountManagement(profile)) {
return false;
}
std::string legacy_policy_for_intercepted_profile =
intercepted_account_separation_policies
.managed_accounts_signin_restrictions()
.value_or(std::string());
std::string legacy_policy_for_current_profile =
profile->GetPrefs()->GetString(prefs::kManagedAccountsSigninRestriction);
bool allowed_by_existing_profile =
legacy_policy_for_current_profile.empty() ||
legacy_policy_for_current_profile == "none" ||
base::EndsWith(legacy_policy_for_current_profile, "keep_existing_data");
bool allowed_by_intercepted_account =
intercepted_account_separation_policies
.profile_separation_data_migration_settings()
.value_or(policy::ProfileSeparationDataMigrationSettings::
USER_OPT_IN) !=
policy::ProfileSeparationDataMigrationSettings::ALWAYS_SEPARATE &&
(legacy_policy_for_intercepted_profile.empty() ||
legacy_policy_for_intercepted_profile == "none" ||
base::EndsWith(legacy_policy_for_intercepted_profile,
"keep_existing_data"));
return allowed_by_existing_profile && allowed_by_intercepted_account;
}
bool IsAccountExemptedFromEnterpriseProfileSeparation(
Profile* profile,
const std::string& email) {
if (profile->GetPrefs()
->FindPreference(prefs::kProfileSeparationDomainExceptionList)
->IsDefaultValue()) {
return true;
}
const std::string domain = gaia::ExtractDomainName(email);
const auto& allowed_domains = profile->GetPrefs()->GetList(
prefs::kProfileSeparationDomainExceptionList);
return base::Contains(allowed_domains, base::Value(domain));
}
#endif // !BUILDFLAG(IS_CHROMEOS)
void RecordEnterpriseProfileCreationUserChoice(bool enforced_by_policy,
bool created) {
base::UmaHistogramBoolean(
enforced_by_policy
? "Signin.Enterprise.WorkProfile.ProfileCreatedWithPolicySet"
: "Signin.Enterprise.WorkProfile.ProfileCreatedwithPolicyUnset",
created);
}
#endif // !BUILDFLAG(IS_ANDROID)
PrimaryAccountError SetPrimaryAccountWithInvalidToken(
Profile* profile,
const std::string& user_email,
const GaiaId& gaia_id,
bool is_under_advanced_protection,
signin_metrics::AccessPoint access_point,
signin_metrics::SourceForRefreshTokenOperation source) {
auto* identity_manager = IdentityManagerFactory::GetForProfile(profile);
CHECK(identity_manager->FindExtendedAccountInfoByEmailAddress(user_email)
.IsEmpty());
CHECK(!identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin));
DVLOG(1) << "Adding user with gaia id <" << gaia_id << "> and email <"
<< user_email << "> with invalid refresh token.";
// Lock AccountReconcilor temporarily to prevent AddOrUpdateAccount failure
// since we have an invalid refresh token.
AccountReconcilor::Lock account_reconcilor_lock(
AccountReconcilorFactory::GetForProfile(profile));
CoreAccountId account_id =
identity_manager->GetAccountsMutator()->AddOrUpdateAccount(
gaia_id, user_email, GaiaConstants::kInvalidRefreshToken,
is_under_advanced_protection, access_point, source);
DVLOG(1) << "Account id <" << account_id.ToString()
<< "> has been added to the profile with invalid token.";
auto set_primary_account_result =
identity_manager->GetPrimaryAccountMutator()->SetPrimaryAccount(
account_id, signin::ConsentLevel::kSignin);
DVLOG(1) << "Operation of setting account id <" << account_id.ToString()
<< "> received the following result: "
<< static_cast<int>(set_primary_account_result);
return set_primary_account_result;
}
bool IsSigninPending(signin::IdentityManager* identity_manager) {
return !identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSync) &&
identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin) &&
identity_manager->HasAccountWithRefreshTokenInPersistentErrorState(
identity_manager->GetPrimaryAccountId(
signin::ConsentLevel::kSignin));
}
SignedInState GetSignedInState(
const signin::IdentityManager* identity_manager) {
if (!identity_manager) {
return SignedInState::kSignedOut;
}
if (identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSync)) {
if (identity_manager->HasAccountWithRefreshTokenInPersistentErrorState(
identity_manager->GetPrimaryAccountId(
signin::ConsentLevel::kSync))) {
return SignedInState::kSyncPaused;
}
return SignedInState::kSyncing;
}
// If explicit browser signin is not enabled, this returns `kSignedIn`
// regardless of the error state of the refresh token. There might for example
// be an error in the following two cases: (a) The account is managed. (b) The
// account is not managed, but the `SigninManager` has not been notified yet,
// which would sign the user out.
//
// If the error state of the primary account is relevant, then it needs to be
// checked in addition to this state.
if (identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin)) {
return identity_manager->HasAccountWithRefreshTokenInPersistentErrorState(
identity_manager->GetPrimaryAccountId(
signin::ConsentLevel::kSignin)) &&
switches::IsExplicitBrowserSigninUIOnDesktopEnabled()
? SignedInState::kSignInPending
: SignedInState::kSignedIn;
}
// Not signed, but at least one account is signed in on the web.
if (!identity_manager->GetAccountsWithRefreshTokens().empty()) {
return SignedInState::kWebOnlySignedIn;
}
return SignedInState::kSignedOut;
}
} // namespace signin_util