// 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/settings/password/passwords_mediator.h"
#import "ios/chrome/browser/ui/settings/password/passwords_mediator+Testing.h"
#import "base/memory/raw_ptr.h"
#import "components/feature_engagement/public/event_constants.h"
#import "components/feature_engagement/public/feature_constants.h"
#import "components/feature_engagement/public/tracker.h"
#import "components/password_manager/core/browser/leak_detection_dialog_utils.h"
#import "components/password_manager/core/browser/password_manager_client.h"
#import "components/password_manager/core/browser/password_sync_util.h"
#import "components/sync/base/passphrase_enums.h"
#import "components/sync/service/sync_service_utils.h"
#import "components/sync/service/sync_user_settings.h"
#import "ios/chrome/browser/favicon/model/favicon_loader.h"
#import "ios/chrome/browser/net/model/crurl.h"
#import "ios/chrome/browser/passwords/model/password_check_observer_bridge.h"
#import "ios/chrome/browser/passwords/model/password_checkup_utils.h"
#import "ios/chrome/browser/passwords/model/password_manager_util_ios.h"
#import "ios/chrome/browser/passwords/model/save_passwords_consumer.h"
#import "ios/chrome/browser/signin/model/authentication_service.h"
#import "ios/chrome/browser/sync/model/sync_observer_bridge.h"
#import "ios/chrome/browser/ui/settings/password/account_storage_utils.h"
#import "ios/chrome/browser/ui/settings/password/passwords_consumer.h"
#import "ios/chrome/browser/ui/settings/password/passwords_table_view_constants.h"
#import "ios/chrome/browser/ui/settings/password/saved_passwords_presenter_observer.h"
#import "ios/chrome/browser/ui/settings/utils/password_auto_fill_status_manager.h"
#import "ios/chrome/common/string_util.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/common/ui/favicon/favicon_constants.h"
#import "ios/chrome/grit/ios_branded_strings.h"
#import "ios/chrome/grit/ios_strings.h"
#import "net/base/apple/url_conversions.h"
#import "ui/base/l10n/l10n_util_mac.h"
#import "url/gurl.h"
using password_manager::WarningType;
namespace {
// Struct used to count and store the number of active Password Manager widget
// promos, as the FET does not support showing multiple promos for the same FET
// feature at the same time.
struct PasswordManagerActiveWidgetPromoData
: public base::SupportsUserData::Data {
// The number of active promos.
int active_promos = 0;
// Key to use for this type in SupportsUserData
static constexpr char key[] = "PasswordManagerActiveWidgetPromoData";
};
} // namespace
@interface PasswordsMediator () <PasswordCheckObserver,
SavedPasswordsPresenterObserver,
SyncObserverModelBridge>
// Whether or not the Feature Engagement Tracker should be notified that the
// Password Manager widget promo is not displayed anymore. Will be `true` when
// the Password Manager view controller is dismissed while presenting the
// promo.
@property(nonatomic, assign)
BOOL shouldNotifyFETToDismissPasswordManagerWidgetPromo;
@end
@implementation PasswordsMediator {
// The service responsible for password check feature.
scoped_refptr<IOSChromePasswordCheckManager> _passwordCheckManager;
raw_ptr<password_manager::SavedPasswordsPresenter> _savedPasswordsPresenter;
// A helper object for passing data about changes in password check status
// and changes to compromised credentials list.
std::unique_ptr<PasswordCheckObserverBridge> _passwordCheckObserver;
// A helper object for passing data about saved passwords from a finished
// password store request to the PasswordManagerViewController.
std::unique_ptr<SavedPasswordsPresenterObserverBridge>
_passwordsPresenterObserver;
// Current state of password check.
PasswordCheckState _currentState;
// Sync observer.
std::unique_ptr<SyncObserverBridge> _syncObserver;
// Object storing the time of the previous successful re-authentication.
// This is meant to be used by the `ReauthenticationModule` for keeping
// re-authentications valid for a certain time interval within the scope
// of the Passwords Screen.
__strong NSDate* _successfulReauthTime;
// FaviconLoader is a keyed service that uses LargeIconService to retrieve
// favicon images.
raw_ptr<FaviconLoader> _faviconLoader;
// Service to know whether passwords are synced.
raw_ptr<syncer::SyncService> _syncService;
// The user pref service.
raw_ptr<PrefService> _prefService;
}
- (instancetype)initWithPasswordCheckManager:
(scoped_refptr<IOSChromePasswordCheckManager>)
passwordCheckManager
faviconLoader:(FaviconLoader*)faviconLoader
syncService:(syncer::SyncService*)syncService
prefService:(PrefService*)prefService {
self = [super init];
if (self) {
_syncService = syncService;
_prefService = prefService;
_faviconLoader = faviconLoader;
_syncObserver = std::make_unique<SyncObserverBridge>(self, syncService);
_passwordCheckManager = passwordCheckManager;
_savedPasswordsPresenter =
passwordCheckManager->GetSavedPasswordsPresenter();
DCHECK(_savedPasswordsPresenter);
_passwordCheckObserver = std::make_unique<PasswordCheckObserverBridge>(
self, _passwordCheckManager.get());
_passwordsPresenterObserver =
std::make_unique<SavedPasswordsPresenterObserverBridge>(
self, _savedPasswordsPresenter);
}
return self;
}
- (void)setConsumer:(id<PasswordsConsumer>)consumer {
if (_consumer == consumer)
return;
_consumer = consumer;
[self providePasswordsToConsumer];
_currentState = _passwordCheckManager->GetPasswordCheckState();
[self updateConsumerPasswordCheckState:_currentState];
[self.consumer
setSavingPasswordsToAccount:
password_manager::sync_util::GetPasswordSyncState(_syncService) !=
password_manager::sync_util::SyncState::kNotActive];
}
- (void)disconnect {
if (_shouldNotifyFETToDismissPasswordManagerWidgetPromo && _tracker) {
[self dismissFETIfNeeded];
}
_tracker = nullptr;
_syncObserver.reset();
_passwordsPresenterObserver.reset();
_passwordCheckObserver.reset();
_passwordCheckManager.reset();
_savedPasswordsPresenter = nullptr;
_faviconLoader = nullptr;
_prefService = nullptr;
_syncService = nullptr;
}
- (void)askFETToShowPasswordManagerWidgetPromo {
if (self.tracker && !_shouldNotifyFETToDismissPasswordManagerWidgetPromo) {
[self.consumer setShouldShowPasswordManagerWidgetPromo:
[self shouldShowPasswordManagerWidgetPromo]];
}
}
#pragma mark - PasswordManagerViewControllerDelegate
- (void)deleteCredentials:
(const std::vector<password_manager::CredentialUIEntry>&)credentials {
for (const auto& credential : credentials) {
_savedPasswordsPresenter->RemoveCredential(credential);
}
}
- (void)startPasswordCheck {
_passwordCheckManager->StartPasswordCheck(
password_manager::LeakDetectionInitiator::kBulkSyncedPasswordsCheck);
}
- (NSString*)formattedElapsedTimeSinceLastCheck {
std::optional<base::Time> lastCompletedCheck =
_passwordCheckManager->GetLastPasswordCheckTime();
return password_manager::FormatElapsedTimeSinceLastCheck(lastCompletedCheck);
}
- (NSAttributedString*)passwordCheckErrorInfo {
NSString* message;
NSDictionary* textAttributes = @{
NSForegroundColorAttributeName : [UIColor colorNamed:kTextSecondaryColor],
NSFontAttributeName :
[UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline]
};
switch (_currentState) {
case PasswordCheckState::kRunning:
case PasswordCheckState::kNoPasswords:
case PasswordCheckState::kCanceled:
case PasswordCheckState::kIdle:
return nil;
case PasswordCheckState::kSignedOut:
message =
l10n_util::GetNSString(IDS_IOS_PASSWORD_CHECKUP_ERROR_SIGNED_OUT);
break;
case PasswordCheckState::kOffline:
message = l10n_util::GetNSString(IDS_IOS_PASSWORD_CHECKUP_ERROR_OFFLINE);
break;
case PasswordCheckState::kQuotaLimit:
if ([self canUseAccountPasswordCheckup]) {
message = l10n_util::GetNSString(
IDS_IOS_PASSWORD_CHECKUP_ERROR_QUOTA_LIMIT_VISIT_GOOGLE);
NSDictionary* linkAttributes = @{
NSLinkAttributeName :
net::NSURLWithGURL(password_manager::GetPasswordCheckupURL(
password_manager::PasswordCheckupReferrer::kPasswordCheck))
};
return AttributedStringFromStringWithLink(message, textAttributes,
linkAttributes);
} else {
message =
l10n_util::GetNSString(IDS_IOS_PASSWORD_CHECKUP_ERROR_QUOTA_LIMIT);
}
break;
case PasswordCheckState::kOther:
message = l10n_util::GetNSString(IDS_IOS_PASSWORD_CHECKUP_ERROR_OTHER);
break;
}
return [[NSMutableAttributedString alloc] initWithString:message
attributes:textAttributes];
}
// Returns the on-device encryption state according to the sync service.
- (OnDeviceEncryptionState)onDeviceEncryptionState {
if (ShouldOfferTrustedVaultOptIn(_syncService)) {
return OnDeviceEncryptionStateOfferOptIn;
}
syncer::SyncUserSettings* syncUserSettings = _syncService->GetUserSettings();
if (syncUserSettings->GetPassphraseType() ==
syncer::PassphraseType::kTrustedVaultPassphrase) {
return OnDeviceEncryptionStateOptedIn;
}
return OnDeviceEncryptionStateNotShown;
}
- (BOOL)shouldShowLocalOnlyIconForCredential:
(const password_manager::CredentialUIEntry&)credential {
return password_manager::ShouldShowLocalOnlyIcon(credential, _syncService);
}
- (BOOL)shouldShowLocalOnlyIconForGroup:
(const password_manager::AffiliatedGroup&)group {
return password_manager::ShouldShowLocalOnlyIconForGroup(group, _syncService);
}
- (void)notifyFETOfPasswordManagerWidgetPromoDismissal {
if (self.tracker) {
self.tracker->NotifyEvent(
feature_engagement::events::kPasswordManagerWidgetPromoClosed);
[self dismissFETIfNeeded];
}
_shouldNotifyFETToDismissPasswordManagerWidgetPromo = NO;
}
#pragma mark - PasswordCheckObserver
- (void)passwordCheckStateDidChange:(PasswordCheckState)state {
if (state == _currentState)
return;
[self updateConsumerPasswordCheckState:state];
}
- (void)insecureCredentialsDidChange {
// Insecure password changes have no effect on UI while check is running.
if (_passwordCheckManager->GetPasswordCheckState() ==
PasswordCheckState::kRunning)
return;
[self updateConsumerPasswordCheckState:_currentState];
}
- (void)passwordCheckManagerWillShutdown {
_passwordCheckObserver.reset();
}
#pragma mark - Private Methods
// Provides passwords and blocked forms to the '_consumer'.
- (void)providePasswordsToConsumer {
[_consumer setAffiliatedGroups:_savedPasswordsPresenter->GetAffiliatedGroups()
blockedSites:_savedPasswordsPresenter->GetBlockedSites()];
}
// Updates the `_consumer` Password Check UI State and Insecure Passwords.
- (void)updateConsumerPasswordCheckState:
(PasswordCheckState)passwordCheckState {
DCHECK(self.consumer);
std::vector<password_manager::CredentialUIEntry> insecureCredentials =
_passwordCheckManager->GetInsecureCredentials();
PasswordCheckUIState passwordCheckUIState =
[self computePasswordCheckUIStateWith:passwordCheckState
insecureCredentials:insecureCredentials];
WarningType warningType = GetWarningOfHighestPriority(insecureCredentials);
NSInteger insecurePasswordsCount =
GetPasswordCountForWarningType(warningType, insecureCredentials);
[self.consumer setPasswordCheckUIState:passwordCheckUIState
insecurePasswordsCount:insecurePasswordsCount];
}
// Returns PasswordCheckUIState based on PasswordCheckState.
- (PasswordCheckUIState)
computePasswordCheckUIStateWith:(PasswordCheckState)newState
insecureCredentials:
(const std::vector<password_manager::CredentialUIEntry>&)
insecureCredentials {
BOOL wasRunning = _currentState == PasswordCheckState::kRunning;
_currentState = newState;
switch (_currentState) {
case PasswordCheckState::kRunning:
return PasswordCheckStateRunning;
case PasswordCheckState::kNoPasswords:
return PasswordCheckStateDisabled;
case PasswordCheckState::kSignedOut:
return PasswordCheckStateSignedOut;
case PasswordCheckState::kOffline:
case PasswordCheckState::kQuotaLimit:
case PasswordCheckState::kOther:
return PasswordCheckStateError;
case PasswordCheckState::kCanceled:
case PasswordCheckState::kIdle: {
if (_currentState == PasswordCheckState::kIdle && wasRunning) {
PasswordCheckUIState insecureState =
[self passwordCheckUIStateFromHighestPriorityWarningType:
insecureCredentials];
return insecureCredentials.empty() ? PasswordCheckStateSafe
: insecureState;
}
return PasswordCheckStateDefault;
}
}
}
// Returns the right PasswordCheckUIState depending on the highest priority
// warning type.
- (PasswordCheckUIState)passwordCheckUIStateFromHighestPriorityWarningType:
(const std::vector<password_manager::CredentialUIEntry>&)
insecureCredentials {
switch (GetWarningOfHighestPriority(insecureCredentials)) {
case WarningType::kCompromisedPasswordsWarning:
return PasswordCheckStateUnmutedCompromisedPasswords;
case WarningType::kReusedPasswordsWarning:
return PasswordCheckStateReusedPasswords;
case WarningType::kWeakPasswordsWarning:
return PasswordCheckStateWeakPasswords;
case WarningType::kDismissedWarningsWarning:
return PasswordCheckStateDismissedWarnings;
case WarningType::kNoInsecurePasswordsWarning:
return PasswordCheckStateSafe;
}
}
// Compute whether user is capable to run password check in Google Account.
- (BOOL)canUseAccountPasswordCheckup {
return password_manager::sync_util::GetAccountForSaving(_prefService,
_syncService) &&
!_syncService->GetUserSettings()->IsEncryptEverythingEnabled();
}
- (BOOL)shouldShowPasswordManagerWidgetPromo {
if (self.tracker) {
// First check if another active Password Manager page (e.g. in another
// window) has an active promo. If so, just return that the promo should be
// shown here without querying the FET. Only query the FET if there is no
// currently active promo.
PasswordManagerActiveWidgetPromoData* data =
static_cast<PasswordManagerActiveWidgetPromoData*>(
self.tracker->GetUserData(
PasswordManagerActiveWidgetPromoData::key));
if (data) {
data->active_promos++;
self.shouldNotifyFETToDismissPasswordManagerWidgetPromo = YES;
return YES;
} else if (self.tracker->ShouldTriggerHelpUI(
feature_engagement::
kIPHiOSPromoPasswordManagerWidgetFeature)) {
std::unique_ptr<PasswordManagerActiveWidgetPromoData> new_data =
std::make_unique<PasswordManagerActiveWidgetPromoData>();
new_data->active_promos++;
self.tracker->SetUserData(PasswordManagerActiveWidgetPromoData::key,
std::move(new_data));
self.shouldNotifyFETToDismissPasswordManagerWidgetPromo = YES;
return YES;
}
}
return NO;
}
// Check if this is the last active Password Manager showing the widget promo
// and dismisses the FET if so.
- (void)dismissFETIfNeeded {
PasswordManagerActiveWidgetPromoData* data =
static_cast<PasswordManagerActiveWidgetPromoData*>(
_tracker->GetUserData(PasswordManagerActiveWidgetPromoData::key));
if (data) {
data->active_promos--;
if (data->active_promos <= 0) {
_tracker->Dismissed(
feature_engagement::kIPHiOSPromoPasswordManagerWidgetFeature);
_tracker->RemoveUserData(PasswordManagerActiveWidgetPromoData::key);
}
} else {
_tracker->Dismissed(
feature_engagement::kIPHiOSPromoPasswordManagerWidgetFeature);
}
}
#pragma mark - SavedPasswordsPresenterObserver
- (void)savedPasswordsDidChange {
[self providePasswordsToConsumer];
}
#pragma mark SuccessfulReauthTimeAccessor
- (void)updateSuccessfulReauthTime {
_successfulReauthTime = [[NSDate alloc] init];
}
- (NSDate*)lastSuccessfulReauthTime {
return _successfulReauthTime;
}
#pragma mark - TableViewFaviconDataSource
- (void)faviconForPageURL:(CrURL*)URL
completion:(void (^)(FaviconAttributes*))completion {
BOOL fallbackToGoogleServer =
password_manager_util::IsSavingPasswordsToAccountWithNormalEncryption(
_syncService);
_faviconLoader->FaviconForPageUrl(URL.gurl, kDesiredMediumFaviconSizePt,
kMinFaviconSizePt, fallbackToGoogleServer,
completion);
}
#pragma mark - SyncObserverModelBridge
- (void)onSyncStateChanged {
[self.consumer
setSavingPasswordsToAccount:
password_manager::sync_util::GetPasswordSyncState(_syncService) !=
password_manager::sync_util::SyncState::kNotActive];
}
@end