blob: 4432a47700d5ae0748895654e99a45d8636918ce [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/authentication/signin/signin_utils.h"
#import "base/command_line.h"
#import "base/strings/sys_string_conversions.h"
#import "base/time/time.h"
#import "base/version.h"
#import "components/policy/policy_constants.h"
#import "components/prefs/pref_service.h"
#import "components/signin/ios/browser/features.h"
#import "ios/chrome/app/tests_hook.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/browser_state/chrome_browser_state.h"
#import "ios/chrome/browser/signin/authentication_service.h"
#import "ios/chrome/browser/signin/authentication_service_factory.h"
#import "ios/chrome/browser/signin/chrome_account_manager_service.h"
#import "ios/chrome/browser/signin/chrome_account_manager_service_factory.h"
#import "ios/chrome/browser/signin/system_identity.h"
#import "ios/chrome/browser/sync/sync_setup_service.h"
#import "ios/chrome/browser/sync/sync_setup_service_factory.h"
#import "ios/chrome/browser/ui/authentication/signin/signin_constants.h"
#import "ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_constants.h"
#import "net/base/network_change_notifier.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// Maximum delay to wait for fetching the account capabilities before showing
// the sign-in upgrade promo. If fetching the account capabilities takes more
// than the delay, then the promo is suppressed - it may be shown on the next
// start-up.
constexpr base::TimeDelta kShowSigninUpgradePromoMaxDelay =
base::Milliseconds(200);
// Converts an array of identities to a set of gaia ids.
NSSet<NSString*>* GaiaIdSetWithIdentities(
NSArray<id<SystemIdentity>>* identities) {
NSMutableSet* gaia_id_set = [NSMutableSet set];
for (id<SystemIdentity> identity in identities) {
[gaia_id_set addObject:identity.gaiaID];
}
return [gaia_id_set copy];
}
// Returns whether the gaia ids `recorded_gaia_ids` is a strict subset of the
// current `identities` (i.e. all the gaia ids are in identities but there is
// at least one new identity).
bool IsStrictSubset(NSArray<NSString*>* recorded_gaia_ids,
NSArray<id<SystemIdentity>>* identities) {
// Optimisation for the case of a nil or empty `recorded_gaia_ids`.
// This allow not special casing the construction of the NSSet (as
// -[NSSet setWithArray:] does not support nil for the array).
if (recorded_gaia_ids.count == 0)
return identities.count > 0;
NSSet<NSString*>* recorded_gaia_ids_set =
[NSSet setWithArray:recorded_gaia_ids];
NSSet<NSString*>* identities_gaia_ids_set =
GaiaIdSetWithIdentities(identities);
return [recorded_gaia_ids_set isSubsetOfSet:identities_gaia_ids_set] &&
![recorded_gaia_ids_set isEqualToSet:identities_gaia_ids_set];
}
} // namespace
#pragma mark - Public
namespace signin {
base::TimeDelta GetWaitThresholdForCapabilities() {
const base::CommandLine* command_line =
base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(
signin::kWaitThresholdMillisecondsForCapabilitiesApi)) {
std::string delayString = command_line->GetSwitchValueASCII(
signin::kWaitThresholdMillisecondsForCapabilitiesApi);
int commandLineDelay = 0;
if (base::StringToInt(delayString, &commandLineDelay)) {
return base::Milliseconds(commandLineDelay);
}
}
return kShowSigninUpgradePromoMaxDelay;
}
bool ShouldPresentUserSigninUpgrade(ChromeBrowserState* browser_state,
const base::Version& current_version) {
DCHECK(browser_state);
DCHECK(current_version.IsValid());
if (tests_hook::DisableUpgradeSigninPromo())
return false;
if (browser_state->IsOffTheRecord())
return false;
// There will be an error shown if the user chooses to sign in or select
// another account while offline.
if (net::NetworkChangeNotifier::IsOffline())
return false;
// Sign-in can be disabled by policy or through user Settings.
AuthenticationService* authentication_service =
AuthenticationServiceFactory::GetForBrowserState(browser_state);
switch (authentication_service->GetServiceStatus()) {
case AuthenticationService::ServiceStatus::SigninDisabledByUser:
case AuthenticationService::ServiceStatus::SigninDisabledByInternal:
case AuthenticationService::ServiceStatus::SigninDisabledByPolicy:
return false;
case AuthenticationService::ServiceStatus::SigninForcedByPolicy:
case AuthenticationService::ServiceStatus::SigninAllowed:
break;
}
AuthenticationService* auth_service =
AuthenticationServiceFactory::GetForBrowserState(browser_state);
// Do not show the SSO promo if the user is already syncing.
if (auth_service->HasPrimaryIdentity(signin::ConsentLevel::kSync)) {
return false;
}
// Don't show the promo if there are no identities. This should be tested
// before ForceStartupSigninPromo() to avoid any DCHECK failures if
// ForceStartupSigninPromo() returns true.
ChromeAccountManagerService* account_manager_service =
ChromeAccountManagerServiceFactory::GetForBrowserState(browser_state);
NSArray<id<SystemIdentity>>* identities =
account_manager_service->GetAllIdentities();
if (identities.count == 0)
return false;
// Used for testing purposes only.
if (signin::ForceStartupSigninPromo())
return true;
// Show the promo at most every two major versions.
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
NSString* version_string =
[defaults stringForKey:kDisplayedSSORecallForMajorVersionKey];
const base::Version version_shown(base::SysNSStringToUTF8(version_string));
// If the version was not set, we need to set it in order to wait 2 major
// releases to show the sign-in promo.
if (!version_shown.IsValid()) {
[defaults setObject:base::SysUTF8ToNSString(current_version.GetString())
forKey:kDisplayedSSORecallForMajorVersionKey];
return false;
}
// Wait 2 major releases to show the sign-in promo.
if (current_version.components()[0] - version_shown.components()[0] < 2) {
return false;
}
// The sign-in promo should be shown twice, even if no account has been added.
NSInteger display_count =
[defaults integerForKey:kSigninPromoViewDisplayCountKey];
if (display_count <= 1)
return true;
// Otherwise, it can be shown only if a new account has been added.
NSArray<NSString*>* last_known_gaia_id_list =
[defaults arrayForKey:kLastShownAccountGaiaIdVersionKey];
return IsStrictSubset(last_known_gaia_id_list, identities);
}
void RecordUpgradePromoSigninStarted(
ChromeAccountManagerService* account_manager_service,
const base::Version& current_version) {
DCHECK(account_manager_service);
DCHECK(current_version.IsValid());
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:base::SysUTF8ToNSString(current_version.GetString())
forKey:kDisplayedSSORecallForMajorVersionKey];
NSArray<id<SystemIdentity>>* identities =
account_manager_service->GetAllIdentities();
NSSet<NSString*>* gaia_id_set = GaiaIdSetWithIdentities(identities);
[defaults setObject:gaia_id_set.allObjects
forKey:kLastShownAccountGaiaIdVersionKey];
NSInteger display_count =
[defaults integerForKey:kSigninPromoViewDisplayCountKey];
++display_count;
[defaults setInteger:display_count forKey:kSigninPromoViewDisplayCountKey];
}
IdentitySigninState GetPrimaryIdentitySigninState(
ChromeBrowserState* browser_state) {
AuthenticationService* auth_service =
AuthenticationServiceFactory::GetForBrowserState(browser_state);
SyncSetupService* syncSetupService =
SyncSetupServiceFactory::GetForBrowserState(browser_state);
if (auth_service->HasPrimaryIdentity(signin::ConsentLevel::kSync) &&
syncSetupService->IsInitialSyncFeatureSetupComplete()) {
return IdentitySigninStateSignedInWithSyncEnabled;
} else if (auth_service->HasPrimaryIdentity(signin::ConsentLevel::kSignin)) {
return IdentitySigninStateSignedInWithSyncDisabled;
} else {
return IdentitySigninStateSignedOut;
}
}
} // namespace signin