blob: 0ca823f79ddd94544ea4ea7ee4f3f8bdfaedab35 [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.
#import "ios/chrome/browser/settings/ui_bundled/password/password_settings/password_settings_coordinator.h"
#import <UIKit/UIKit.h>
#import "base/debug/dump_without_crashing.h"
#import "base/i18n/message_formatter.h"
#import "base/metrics/histogram_functions.h"
#import "base/metrics/user_metrics.h"
#import "base/strings/sys_string_conversions.h"
#import "components/google/core/common/google_util.h"
#import "components/keyed_service/core/service_access_type.h"
#import "components/password_manager/core/browser/ui/saved_passwords_presenter.h"
#import "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/affiliations/model/ios_chrome_affiliation_service_factory.h"
#import "ios/chrome/browser/passwords/model/ios_chrome_account_password_store_factory.h"
#import "ios/chrome/browser/passwords/model/ios_chrome_profile_password_store_factory.h"
#import "ios/chrome/browser/passwords/model/metrics/ios_password_manager_metrics.h"
#import "ios/chrome/browser/passwords/model/metrics/ios_password_manager_visits_recorder.h"
#import "ios/chrome/browser/settings/ui_bundled/elements/enterprise_info_popover_view_controller.h"
#import "ios/chrome/browser/settings/ui_bundled/password/create_password_manager_title_view.h"
#import "ios/chrome/browser/settings/ui_bundled/password/password_settings/password_bulk_move_handler.h"
#import "ios/chrome/browser/settings/ui_bundled/password/password_settings/password_export_handler.h"
#import "ios/chrome/browser/settings/ui_bundled/password/password_settings/password_settings_constants.h"
#import "ios/chrome/browser/settings/ui_bundled/password/password_settings/password_settings_coordinator_delegate.h"
#import "ios/chrome/browser/settings/ui_bundled/password/password_settings/password_settings_mediator.h"
#import "ios/chrome/browser/settings/ui_bundled/password/password_settings/password_settings_metrics_utils.h"
#import "ios/chrome/browser/settings/ui_bundled/password/password_settings/password_settings_view_controller.h"
#import "ios/chrome/browser/settings/ui_bundled/password/password_settings/scoped_password_settings_reauth_module_override.h"
#import "ios/chrome/browser/settings/ui_bundled/password/passwords_in_other_apps/passwords_in_other_apps_coordinator.h"
#import "ios/chrome/browser/settings/ui_bundled/password/reauthentication/reauthentication_coordinator.h"
#import "ios/chrome/browser/settings/ui_bundled/settings_navigation_controller.h"
#import "ios/chrome/browser/settings/ui_bundled/utils/password_utils.h"
#import "ios/chrome/browser/shared/coordinator/alert/alert_coordinator.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/profile/profile_ios.h"
#import "ios/chrome/browser/shared/model/url/chrome_url_constants.h"
#import "ios/chrome/browser/shared/public/commands/application_commands.h"
#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
#import "ios/chrome/browser/shared/public/commands/open_new_tab_command.h"
#import "ios/chrome/browser/shared/public/commands/snackbar_commands.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/browser/signin/model/authentication_service.h"
#import "ios/chrome/browser/signin/model/authentication_service_factory.h"
#import "ios/chrome/browser/signin/model/identity_manager_factory.h"
#import "ios/chrome/browser/signin/model/system_identity.h"
#import "ios/chrome/browser/signin/model/trusted_vault_client_backend.h"
#import "ios/chrome/browser/signin/model/trusted_vault_client_backend_factory.h"
#import "ios/chrome/browser/sync/model/sync_service_factory.h"
#import "ios/chrome/browser/webauthn/model/ios_passkey_model_factory.h"
#import "ios/chrome/common/ui/elements/branded_navigation_item_title_view.h"
#import "ios/chrome/common/ui/reauthentication/reauthentication_module.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.h"
#import "ui/base/l10n/l10n_util_mac.h"
namespace {
// The user action for when the bulk move passwords to account confirmation
// dialog's cancel button is clicked.
constexpr const char* kBulkMovePasswordsToAccountConfirmationDialogCancelled =
"Mobile.PasswordsSettings.BulkSavePasswordsToAccountDialog.Cancelled";
// The user action for when the bulk move passwords to account confirmation
// dialog's accept button is clicked.
constexpr const char* kBulkMovePasswordsToAccountConfirmationDialogAccepted =
"Mobile.PasswordsSettings.BulkSavePasswordsToAccountDialog.Accepted";
// The user action for when the delete all saved data button is clicked.
constexpr const char* kDeleteAllSavedDataButtonClicked =
"IOS.PasswordManager.Settings.DeleteAllSavedData.Clicked";
// Represents the code of an error returned when the user dismisses the update
// GPM Pin flow by clicking the "Cancel" button. This should not be treated as
// an actual error.
const NSInteger kErrorUserDismissedUpdateGPMPinFlow = -105;
} // namespace
// Methods to update state in response to actions taken in the Export
// ActivityViewController.
@protocol ExportActivityViewControllerDelegate <NSObject>
// Used to reset the export state when the activity view disappears.
- (void)resetExport;
@end
// Convenience wrapper around ActivityViewController for presenting share sheet
// at the end of the export flow. We do not use completionWithItemsHandler
// because it fails sometimes; see crbug.com/820053.
@interface ExportActivityViewController : UIActivityViewController
- (instancetype)initWithActivityItems:(NSArray*)activityItems
delegate:(id<ExportActivityViewControllerDelegate>)
delegate;
@end
@implementation ExportActivityViewController {
__weak id<ExportActivityViewControllerDelegate> _weakDelegate;
}
- (instancetype)initWithActivityItems:(NSArray*)activityItems
delegate:(id<ExportActivityViewControllerDelegate>)
delegate {
self = [super initWithActivityItems:activityItems applicationActivities:nil];
if (self) {
_weakDelegate = delegate;
}
return self;
}
- (void)viewDidDisappear:(BOOL)animated {
[_weakDelegate resetExport];
[super viewDidDisappear:animated];
}
@end
@interface PasswordSettingsCoordinator () <
ExportActivityViewControllerDelegate,
BulkMoveLocalPasswordsToAccountHandler,
PasswordExportHandler,
PasswordsInOtherAppsCoordinatorDelegate,
PopoverLabelViewControllerDelegate,
ReauthenticationCoordinatorDelegate,
SettingsNavigationControllerDelegate>
@end
@implementation PasswordSettingsCoordinator {
// Main view controller for this coordinator.
PasswordSettingsViewController* _passwordSettingsViewController;
// The presented SettingsNavigationController containing
// `passwordSettingsViewController`.
SettingsNavigationController* _settingsNavigationController;
// The coupled mediator.
PasswordSettingsMediator* _mediator;
// Command dispatcher.
__weak id<ApplicationCommands> _dispatcher;
// Module handling reauthentication before accessing sensitive data.
ReauthenticationModule* _reauthModule;
// Coordinator for the "Passwords in Other Apps" screen.
PasswordsInOtherAppsCoordinator* _passwordsInOtherAppsCoordinator;
// Coordinator for blocking Password Settings until Local Authentication is
// passed. Used for requiring authentication when opening Password Settings
// from outside the Password Manager and when the app is
// backgrounded/foregrounded with Password Settings opened.
ReauthenticationCoordinator* _reauthCoordinator;
// Service which gives us a view on users' saved passwords.
std::unique_ptr<password_manager::SavedPasswordsPresenter>
_savedPasswordsPresenter;
// Alert informing the user that passwords are being prepared for
// export.
UIAlertController* _preparingPasswordsAlert;
// For recording visits after successful authentication.
IOSPasswordManagerVisitsRecorder* _visitsRecorder;
// Identity of the user. Can be nil if there is no primary account.
id<SystemIdentity> _identity;
// Coordinator for displaying errors in update GPM PIN flow.
AlertCoordinator* _updateGPMPinErrorCoordinator;
}
#pragma mark - ChromeCoordinator
- (void)start {
ProfileIOS* profile = self.browser->GetProfile();
_reauthModule = password_manager::BuildReauthenticationModule();
_savedPasswordsPresenter =
std::make_unique<password_manager::SavedPasswordsPresenter>(
IOSChromeAffiliationServiceFactory::GetForProfile(profile),
IOSChromeProfilePasswordStoreFactory::GetForProfile(
profile, ServiceAccessType::EXPLICIT_ACCESS),
IOSChromeAccountPasswordStoreFactory::GetForProfile(
profile, ServiceAccessType::EXPLICIT_ACCESS),
IOSPasskeyModelFactory::GetForProfile(profile));
_identity =
AuthenticationServiceFactory::GetForProfile(profile)->GetPrimaryIdentity(
signin::ConsentLevel::kSignin);
_mediator = [[PasswordSettingsMediator alloc]
initWithReauthenticationModule:_reauthModule
savedPasswordsPresenter:_savedPasswordsPresenter.get()
bulkMovePasswordsToAccountHandler:self
exportHandler:self
prefService:profile->GetPrefs()
identityManager:IdentityManagerFactory::GetForProfile(
profile)
syncService:SyncServiceFactory::GetForProfile(
profile)
trustedVaultClientBackend:TrustedVaultClientBackendFactory::
GetForProfile(profile)
identity:_identity];
_dispatcher = static_cast<id<ApplicationCommands>>(
self.browser->GetCommandDispatcher());
_passwordSettingsViewController =
[[PasswordSettingsViewController alloc] init];
_passwordSettingsViewController.presentationDelegate = self;
_settingsNavigationController = [[SettingsNavigationController alloc]
initWithRootViewController:_passwordSettingsViewController
browser:self.browser
delegate:self];
_mediator.consumer = _passwordSettingsViewController;
_passwordSettingsViewController.delegate = _mediator;
_visitsRecorder = [[IOSPasswordManagerVisitsRecorder alloc]
initWithPasswordManagerSurface:password_manager::PasswordManagerSurface::
kPasswordSettings];
// Only record visit if no auth is required, otherwise wait for successful
// auth.
if (_skipAuthenticationOnStart) {
[_visitsRecorder maybeRecordVisitMetric];
}
[self startReauthCoordinatorWithAuthOnStart:!_skipAuthenticationOnStart];
[self.baseViewController presentViewController:_settingsNavigationController
animated:YES
completion:nil];
}
- (void)stop {
[self stopWithUIDismissal:YES];
}
#pragma mark - PasswordSettingsCoordinator
- (void)stopWithUIDismissal:(BOOL)shouldDismissUI {
if (shouldDismissUI) {
[_settingsNavigationController.presentingViewController
dismissViewControllerAnimated:NO
completion:nil];
}
[_passwordsInOtherAppsCoordinator stop];
_passwordsInOtherAppsCoordinator.delegate = nil;
_passwordsInOtherAppsCoordinator = nil;
_passwordSettingsViewController.presentationDelegate = nil;
_passwordSettingsViewController.delegate = nil;
_passwordSettingsViewController = nil;
[_settingsNavigationController cleanUpSettings];
_settingsNavigationController = nil;
_preparingPasswordsAlert = nil;
_dispatcher = nil;
_reauthModule = nil;
[_mediator disconnect];
_mediator.consumer = nil;
_mediator = nil;
_savedPasswordsPresenter.reset();
[self stopReauthenticationCoordinator];
}
#pragma mark - PasswordSettingsPresentationDelegate
- (void)startDeletionFlow {
base::RecordAction(base::UserMetricsAction(kDeleteAllSavedDataButtonClicked));
CredentialCounts counts = [_mediator passwordAndPasskeyCounts];
NSString* alertDescription;
if (counts.passwordCounts == 0 && counts.passkeyCounts == 0) {
alertDescription = l10n_util::GetNSString(
IDS_IOS_PASSWORD_SETTINGS_DELETE_ALL_CREDENTIALS_DESCRIPTION_BLOCK_SITES_ONLY);
} else if (counts.passwordCounts == 0) {
alertDescription = base::SysUTF16ToNSString(
base::i18n::MessageFormatter::FormatWithNamedArgs(
l10n_util::GetStringUTF16(
IDS_IOS_PASSWORD_SETTINGS_DELETE_ALL_CREDENTIALS_DESCRIPTION_NO_PASSWORDS),
"passkey_count", counts.passkeyCounts));
} else if (counts.passkeyCounts == 0) {
alertDescription = base::SysUTF16ToNSString(
base::i18n::MessageFormatter::FormatWithNamedArgs(
l10n_util::GetStringUTF16(
IDS_IOS_PASSWORD_SETTINGS_DELETE_ALL_CREDENTIALS_DESCRIPTION_NO_PASSKEYS),
"password_count", counts.passwordCounts));
} else {
alertDescription = base::SysUTF16ToNSString(
base::i18n::MessageFormatter::FormatWithNamedArgs(
l10n_util::GetStringUTF16(
IDS_IOS_PASSWORD_SETTINGS_DELETE_ALL_CREDENTIALS_DESCRIPTION),
"password_count", counts.passwordCounts, "passkey_count",
counts.passkeyCounts));
}
UIAlertController* deletionConfirmation = [UIAlertController
alertControllerWithTitle:
l10n_util::GetNSString(
IDS_IOS_PASSWORD_SETTINGS_DELETE_ALL_CREDENTIALS_TITLE)
message:alertDescription
preferredStyle:UIAlertControllerStyleActionSheet];
UIAlertAction* cancelAction = [UIAlertAction
actionWithTitle:l10n_util::GetNSString(IDS_IOS_CANCEL_PASSWORD_DELETION)
style:UIAlertActionStyleCancel
handler:^(UIAlertAction* action) {
base::UmaHistogramEnumeration(
"IOS.PasswordManager.Settings."
"DeleteAllSavedCredentialsActions",
password_manager::IOSDeleteAllSavedCredentialsActions::
kDeleteAllSavedCredentialsCancelled);
}];
[deletionConfirmation addAction:cancelAction];
__weak __typeof(self) weakSelf = self;
UIAlertAction* deleteAction = [UIAlertAction
actionWithTitle:l10n_util::GetNSString(IDS_IOS_DELETE_ACTION_TITLE)
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction* action) {
base::UmaHistogramEnumeration(
"IOS.PasswordManager.Settings."
"DeleteAllSavedCredentialsActions",
password_manager::IOSDeleteAllSavedCredentialsActions::
kDeleteAllSavedCredentialsConfirmed);
[weakSelf showReauthDialogForDeletion];
}];
[deletionConfirmation addAction:deleteAction];
deletionConfirmation.popoverPresentationController.sourceView =
[_passwordSettingsViewController sourceViewForAlerts];
deletionConfirmation.popoverPresentationController.sourceRect =
[_passwordSettingsViewController sourceRectForCredentialDeletionAlerts];
[_passwordSettingsViewController presentViewController:deletionConfirmation
animated:YES
completion:nil];
}
- (void)startExportFlow {
UIAlertController* exportConfirmation = [UIAlertController
alertControllerWithTitle:nil
message:l10n_util::GetNSString(
IDS_IOS_EXPORT_PASSWORDS_ALERT_MESSAGE)
preferredStyle:UIAlertControllerStyleActionSheet];
exportConfirmation.view.accessibilityIdentifier =
kPasswordSettingsExportConfirmViewId;
UIAlertAction* cancelAction =
[UIAlertAction actionWithTitle:l10n_util::GetNSString(
IDS_IOS_EXPORT_PASSWORDS_CANCEL_BUTTON)
style:UIAlertActionStyleCancel
handler:^(UIAlertAction* action){
}];
[exportConfirmation addAction:cancelAction];
__weak __typeof(self) weakSelf = self;
UIAlertAction* exportAction = [UIAlertAction
actionWithTitle:l10n_util::GetNSString(IDS_IOS_EXPORT_PASSWORDS)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
[weakSelf onStartExportFlowConfirmed];
}];
[exportConfirmation addAction:exportAction];
exportConfirmation.popoverPresentationController.sourceView =
[_passwordSettingsViewController sourceViewForAlerts];
exportConfirmation.popoverPresentationController.sourceRect =
[_passwordSettingsViewController sourceRectForPasswordExportAlerts];
[_passwordSettingsViewController presentViewController:exportConfirmation
animated:YES
completion:nil];
}
- (void)showManagedPrefInfoForSourceView:(UIButton*)sourceView {
// EnterpriseInfoPopoverViewController automatically handles reenabling the
// `sourceView`, so we don't need to add any dismiss handlers or delegation,
// just present the bubble.
EnterpriseInfoPopoverViewController* bubbleViewController =
[[EnterpriseInfoPopoverViewController alloc] initWithEnterpriseName:nil];
bubbleViewController.delegate = self;
// Set the anchor and arrow direction of the bubble.
bubbleViewController.popoverPresentationController.sourceView = sourceView;
bubbleViewController.popoverPresentationController.sourceRect =
sourceView.bounds;
bubbleViewController.popoverPresentationController.permittedArrowDirections =
UIPopoverArrowDirectionAny;
[_passwordSettingsViewController presentViewController:bubbleViewController
animated:YES
completion:nil];
}
- (void)showPasswordsInOtherAppsScreen {
DCHECK(!_passwordsInOtherAppsCoordinator);
[self stopReauthCoordinatorBeforeStartingChildCoordinator];
_passwordsInOtherAppsCoordinator = [[PasswordsInOtherAppsCoordinator alloc]
initWithBaseNavigationController:_settingsNavigationController
browser:self.browser];
_passwordsInOtherAppsCoordinator.delegate = self;
[_passwordsInOtherAppsCoordinator start];
}
- (void)showOnDeviceEncryptionSetUp {
GURL URL = google_util::AppendGoogleLocaleParam(
GURL(kOnDeviceEncryptionOptInURL),
GetApplicationContext()->GetApplicationLocale());
OpenNewTabCommand* command = [OpenNewTabCommand commandWithURLFromChrome:URL];
[_dispatcher closePresentedViewsAndOpenURL:command];
}
- (void)showOnDeviceEncryptionHelp {
GURL URL = GURL(kOnDeviceEncryptionLearnMoreURL);
OpenNewTabCommand* command = [OpenNewTabCommand commandWithURLFromChrome:URL];
[_dispatcher closePresentedViewsAndOpenURL:command];
}
- (void)showChangeGPMPinDialog {
if (![_reauthModule canAttemptReauth]) {
[self
showSetPasscodeDialogWithContent:
l10n_util::GetNSString(
IDS_IOS_PASSWORD_SETTINGS_CHANGE_PIN_SET_UP_PASSCODE_CONTENT)];
return;
}
__weak __typeof(self) weakSelf = self;
void (^onReauthFinished)(ReauthenticationResult) =
^(ReauthenticationResult result) {
// Reauth can't be skipped for this flow.
CHECK(result != ReauthenticationResult::kSkipped);
if (result == ReauthenticationResult::kSuccess) {
[weakSelf updateGPMPinForAccount];
}
};
[_reauthModule
attemptReauthWithLocalizedReason:l10n_util::GetNSString(
IDS_IOS_PASSWORD_SETTINGS_CHANGE_PIN)
canReusePreviousAuth:NO
handler:onReauthFinished];
}
#pragma mark - PopoverLabelViewControllerDelegate
- (void)didTapLinkURL:(NSURL*)URL {
[_dispatcher
openURLInNewTab:[OpenNewTabCommand
commandWithURLFromChrome:net::GURLWithNSURL(URL)
inIncognito:NO]];
}
#pragma mark - BulkMoveLocalPasswordsToAccountHandler
- (void)showAuthenticationForMovePasswordsToAccountWithMessage:
(NSString*)message {
[_mediator userDidStartBulkMoveLocalPasswordsToAccountFlow];
}
- (void)showConfirmationDialogWithAlertTitle:(NSString*)alertTitle
alertDescription:(NSString*)alertDescription {
// Create the confirmation alert.
UIAlertController* movePasswordsConfirmation = [UIAlertController
alertControllerWithTitle:alertTitle
message:alertDescription
preferredStyle:UIAlertControllerStyleActionSheet];
movePasswordsConfirmation.view.accessibilityIdentifier =
kPasswordSettingsBulkMovePasswordsToAccountAlertViewId;
// Create the cancel action.
UIAlertAction* cancelAction = [UIAlertAction
actionWithTitle:
l10n_util::GetNSString(
IDS_IOS_PASSWORD_SETTINGS_BULK_UPLOAD_PASSWORDS_ALERT_CANCEL)
style:UIAlertActionStyleCancel
handler:^(UIAlertAction* action) {
base::RecordAction(base::UserMetricsAction(
kBulkMovePasswordsToAccountConfirmationDialogCancelled));
}];
[movePasswordsConfirmation addAction:cancelAction];
// Create the accept action (i.e. move passwords to account).
__weak __typeof(self) weakSelf = self;
UIAlertAction* movePasswordsAction = [UIAlertAction
actionWithTitle:
l10n_util::GetNSString(
IDS_IOS_PASSWORD_SETTINGS_BULK_UPLOAD_PASSWORDS_ALERT_BUTTON)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action) {
base::RecordAction(base::UserMetricsAction(
kBulkMovePasswordsToAccountConfirmationDialogAccepted));
[weakSelf
showAuthenticationForMovePasswordsToAccountWithMessage:
alertTitle];
}];
[movePasswordsConfirmation addAction:movePasswordsAction];
movePasswordsConfirmation.popoverPresentationController.sourceView =
[_passwordSettingsViewController sourceViewForAlerts];
movePasswordsConfirmation.popoverPresentationController.sourceRect =
[_passwordSettingsViewController sourceRectForBulkMovePasswordsToAccount];
// Show the alert.
[_passwordSettingsViewController
presentViewController:movePasswordsConfirmation
animated:YES
completion:nil];
}
- (void)showMovedToAccountSnackbarWithPasswordCount:(int)count
userEmail:(std::string)email {
std::u16string pattern = l10n_util::GetStringUTF16(
IDS_IOS_PASSWORD_SETTINGS_BULK_UPLOAD_PASSWORDS_SNACKBAR_MESSAGE);
std::u16string result = base::i18n::MessageFormatter::FormatWithNamedArgs(
pattern, "COUNT", count, "EMAIL", base::UTF8ToUTF16(email));
TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
id<SnackbarCommands> handler = HandlerForProtocol(
self.browser->GetCommandDispatcher(), SnackbarCommands);
[handler showSnackbarWithMessage:base::SysUTF16ToNSString(result)
buttonText:nil
messageAction:nil
completionAction:nil];
}
#pragma mark - PasswordExportHandler
- (void)showActivityViewWithActivityItems:(NSArray*)activityItems
completionHandler:(void (^)(NSString* activityType,
BOOL completed,
NSArray* returnedItems,
NSError* activityError))
completionHandler {
ExportActivityViewController* activityViewController =
[[ExportActivityViewController alloc] initWithActivityItems:activityItems
delegate:self];
NSArray* excludedActivityTypes = @[
UIActivityTypeAddToReadingList, UIActivityTypeAirDrop,
UIActivityTypeCopyToPasteboard, UIActivityTypeOpenInIBooks,
UIActivityTypePostToFacebook, UIActivityTypePostToFlickr,
UIActivityTypePostToTencentWeibo, UIActivityTypePostToTwitter,
UIActivityTypePostToVimeo, UIActivityTypePostToWeibo, UIActivityTypePrint
];
[activityViewController setExcludedActivityTypes:excludedActivityTypes];
[activityViewController setCompletionWithItemsHandler:completionHandler];
UIView* sourceView = [_passwordSettingsViewController sourceViewForAlerts];
CGRect sourceRect =
[_passwordSettingsViewController sourceRectForPasswordExportAlerts];
activityViewController.modalPresentationStyle = UIModalPresentationPopover;
activityViewController.popoverPresentationController.sourceView = sourceView;
activityViewController.popoverPresentationController.sourceRect = sourceRect;
activityViewController.popoverPresentationController
.permittedArrowDirections =
UIPopoverArrowDirectionUp | UIPopoverArrowDirectionDown;
[self presentViewControllerForExportFlow:activityViewController];
}
- (void)showExportErrorAlertWithLocalizedReason:(NSString*)localizedReason {
UIAlertController* alertController = [UIAlertController
alertControllerWithTitle:l10n_util::GetNSString(
IDS_IOS_EXPORT_PASSWORDS_FAILED_ALERT_TITLE)
message:localizedReason
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* okAction =
[UIAlertAction actionWithTitle:l10n_util::GetNSString(IDS_OK)
style:UIAlertActionStyleDefault
handler:nil];
[alertController addAction:okAction];
[self presentViewControllerForExportFlow:alertController];
}
- (void)showPreparingPasswordsAlert {
_preparingPasswordsAlert = [UIAlertController
alertControllerWithTitle:
l10n_util::GetNSString(IDS_IOS_EXPORT_PASSWORDS_PREPARING_ALERT_TITLE)
message:nil
preferredStyle:UIAlertControllerStyleAlert];
__weak __typeof(self) weakSelf = self;
UIAlertAction* cancelAction =
[UIAlertAction actionWithTitle:l10n_util::GetNSString(
IDS_IOS_EXPORT_PASSWORDS_CANCEL_BUTTON)
style:UIAlertActionStyleCancel
handler:^(UIAlertAction*) {
[weakSelf onExportFlowCancelled];
}];
[_preparingPasswordsAlert addAction:cancelAction];
[_passwordSettingsViewController
presentViewController:_preparingPasswordsAlert
animated:YES
completion:nil];
}
- (void)showSetPasscodeForPasswordExportDialog {
[self showSetPasscodeDialogWithContent:
l10n_util::GetNSString(
IDS_IOS_SETTINGS_EXPORT_PASSWORDS_SET_UP_SCREENLOCK_CONTENT)];
}
#pragma mark - ExportActivityViewControllerDelegate
- (void)resetExport {
[_mediator userDidCompleteExportFlow];
}
#pragma mark - PasswordsInOtherAppsCoordinatorDelegate
- (void)passwordsInOtherAppsCoordinatorDidRemove:
(PasswordsInOtherAppsCoordinator*)coordinator {
DCHECK_EQ(_passwordsInOtherAppsCoordinator, coordinator);
[_passwordsInOtherAppsCoordinator stop];
_passwordsInOtherAppsCoordinator.delegate = nil;
_passwordsInOtherAppsCoordinator = nil;
[self restartReauthCoordinator];
}
#pragma mark - PasswordManagerReauthenticationDelegate
- (void)dismissPasswordManagerAfterFailedReauthentication {
[_delegate dismissPasswordManagerAfterFailedReauthentication];
}
#pragma mark - SettingsNavigationControllerDelegate
- (void)closeSettings {
// Dismiss UI and notify parent coordinator.
__weak __typeof(self) weakSelf = self;
[self.baseViewController dismissViewControllerAnimated:YES
completion:^{
[weakSelf settingsWasDismissed];
}];
}
- (void)settingsWasDismissed {
[self.delegate passwordSettingsCoordinatorDidRemove:self];
}
#pragma mark - ReauthenticationCoordinatorDelegate
- (void)successfulReauthenticationWithCoordinator:
(ReauthenticationCoordinator*)coordinator {
[_visitsRecorder maybeRecordVisitMetric];
}
- (void)dismissUIAfterFailedReauthenticationWithCoordinator:
(ReauthenticationCoordinator*)coordinator {
CHECK_EQ(_reauthCoordinator, coordinator);
[_delegate dismissPasswordManagerAfterFailedReauthentication];
}
- (void)willPushReauthenticationViewController {
// Cancel password export flow before authentication UI is presented.
if (_preparingPasswordsAlert.beingPresented) {
[_preparingPasswordsAlert dismissViewControllerAnimated:NO completion:nil];
[_mediator exportFlowCanceled];
_preparingPasswordsAlert = nil;
}
}
#pragma mark - Private
// Closes the settings and load the passcode help article in a new tab.
- (void)showPasscodeHelp {
GURL URL = GURL(kPasscodeArticleURL);
OpenNewTabCommand* command = [OpenNewTabCommand commandWithURLFromChrome:URL];
[_dispatcher closePresentedViewsAndOpenURL:command];
}
// Helper to show the "set passcode" dialog with customizable content.
- (void)showSetPasscodeDialogWithContent:(NSString*)content {
UIAlertController* alertController = [UIAlertController
alertControllerWithTitle:l10n_util::GetNSString(
IDS_IOS_SETTINGS_SET_UP_SCREENLOCK_TITLE)
message:content
preferredStyle:UIAlertControllerStyleAlert];
__weak __typeof(self) weakSelf = self;
UIAlertAction* learnAction = [UIAlertAction
actionWithTitle:l10n_util::GetNSString(
IDS_IOS_SETTINGS_SET_UP_SCREENLOCK_LEARN_HOW)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction*) {
[weakSelf showPasscodeHelp];
}];
[alertController addAction:learnAction];
UIAlertAction* okAction =
[UIAlertAction actionWithTitle:l10n_util::GetNSString(IDS_OK)
style:UIAlertActionStyleDefault
handler:nil];
[alertController addAction:okAction];
alertController.preferredAction = okAction;
[_passwordSettingsViewController presentViewController:alertController
animated:YES
completion:nil];
}
// Helper method for presenting several ViewControllers used in the export flow.
// Ensures that the "Preparing passwords" alert is dismissed when something is
// ready to replace it.
- (void)presentViewControllerForExportFlow:(UIViewController*)viewController {
if (_preparingPasswordsAlert.beingPresented) {
__weak __typeof(self) weakSelf = self;
[_preparingPasswordsAlert
dismissViewControllerAnimated:YES
completion:^{
[weakSelf presentViewControllerForExportFlow:
viewController];
}];
} else {
[_passwordSettingsViewController presentViewController:viewController
animated:YES
completion:nil];
}
}
// Starts reauthCoordinator.
// - authOnStart: Pass `YES` to cover Password Settings with an empty view
// controller until successful Local Authentication when reauthCoordinator
// starts.
//
// Local authentication is required every time the current
// scene is backgrounded and foregrounded until reauthCoordinator is stopped.
- (void)startReauthCoordinatorWithAuthOnStart:(BOOL)authOnStart {
if (_reauthCoordinator) {
// The previous reauth coordinator should have been stopped and deallocated
// by now. Create a crash report without crashing and gracefully handle the
// error by cleaning up the old coordinator.
base::debug::DumpWithoutCrashing();
[_reauthCoordinator stopAndPopViewController];
}
_reauthCoordinator = [[ReauthenticationCoordinator alloc]
initWithBaseNavigationController:_settingsNavigationController
browser:self.browser
reauthenticationModule:_reauthModule
authOnStart:authOnStart];
_reauthCoordinator.delegate = self;
[_reauthCoordinator start];
}
// Stops reauthCoordinator.
- (void)stopReauthenticationCoordinator {
[_reauthCoordinator stop];
_reauthCoordinator.delegate = nil;
_reauthCoordinator = nil;
}
// Stop reauth coordinator when a child coordinator will be started.
//
// Needed so reauth coordinator doesn't block for reauth if the scene state
// changes while the child coordinator is presenting its content. The child
// coordinator will add its own reauth coordinator to block its content for
// reauth.
- (void)stopReauthCoordinatorBeforeStartingChildCoordinator {
// See PasswordsCoordinator
// stopReauthCoordinatorBeforeStartingChildCoordinator.
[_reauthCoordinator stopAndPopViewController];
_reauthCoordinator.delegate = nil;
_reauthCoordinator = nil;
}
// Starts reauthCoordinator after a child coordinator content was dismissed.
- (void)restartReauthCoordinator {
// Restart reauth coordinator so it monitors scene state changes and requests
// local authentication after the scene goes to the background.
[self startReauthCoordinatorWithAuthOnStart:NO];
}
// Starts `_updateGPMPinErrorCoordinator` from the currently visible view
// controller (which should be the update GPM Pin VC). The cancel completion
// should close both of them.
- (void)startUpdateGPMPinErrorCoordinator {
NSString* title =
l10n_util::GetNSString(IDS_IOS_PASSWORD_SETTINGS_UPDATE_PIN_ERROR_TITLE);
NSString* message =
l10n_util::GetNSString(IDS_IOS_PASSWORD_SETTINGS_UPDATE_PIN_ERROR);
NSString* buttonTitle =
l10n_util::GetNSString(IDS_IOS_PASSWORD_SETTINGS_UPDATE_PIN_ERROR_BUTTON);
[_updateGPMPinErrorCoordinator stop];
_updateGPMPinErrorCoordinator = [[AlertCoordinator alloc]
initWithBaseViewController:_settingsNavigationController
.visibleViewController
browser:self.browser
title:title
message:message];
__weak __typeof(self) weakSelf = self;
[_updateGPMPinErrorCoordinator
addItemWithTitle:buttonTitle
action:^() {
[weakSelf dismissUpdateGPMPinViewController];
[weakSelf stopUpdateGPMPinErrorCoordinator];
}
style:UIAlertActionStyleCancel];
[_updateGPMPinErrorCoordinator start];
}
// Stops `_updateGPMPinErrorCoordinator`.
- (void)stopUpdateGPMPinErrorCoordinator {
[_updateGPMPinErrorCoordinator stop];
_updateGPMPinErrorCoordinator = nil;
}
// Dismisses the view controller displayed by trusted vault client backend for
// the update GPM Pin flow.
- (void)dismissUpdateGPMPinViewController {
[_settingsNavigationController.topViewController
dismissViewControllerAnimated:YES
completion:nil];
}
// Starts the deletion all credentials flow after the user confirmed the
// corresponding alerts.
- (void)onStartDeletionFlowConfirmed {
[_mediator userDidStartDeleteFlow];
}
// Starts the export passwords flow after the user confirmed the corresponding
// alert.
- (void)onStartExportFlowConfirmed {
[_mediator userDidStartExportFlow];
}
// Cancels the password export flow.
- (void)onExportFlowCancelled {
[_mediator exportFlowCanceled];
}
// Handles update GPM Pin flow completion. If there is an `error` other than
// user dismissing the flow by clicking "Cancel", presents the error alert.
// Otherwise, dismisses the UI.
- (void)updateGPMPinFinishedWithError:(NSError*)error {
if (error && error.code != kErrorUserDismissedUpdateGPMPinFlow) {
[self startUpdateGPMPinErrorCoordinator];
} else {
[self dismissUpdateGPMPinViewController];
}
}
// Starts the update GPM Pin flow. This should happen after succesful reauth.
- (void)updateGPMPinForAccount {
__weak __typeof(self) weakSelf = self;
TrustedVaultClientBackendFactory::GetForProfile(self.browser->GetProfile())
->UpdateGPMPinForAccount(
_identity, trusted_vault::SecurityDomainId::kPasskeys,
_settingsNavigationController,
password_manager::CreatePasswordManagerTitleView(
l10n_util::GetNSString(IDS_IOS_PASSWORD_MANAGER)),
base::BindOnce(^(NSError* error) {
[weakSelf updateGPMPinFinishedWithError:error];
}));
}
- (void)showReauthDialogForDeletion {
if (![_reauthModule canAttemptReauth]) {
[self
showSetPasscodeDialogWithContent:
l10n_util::GetNSString(
IDS_IOS_SETTINGS_DELETE_ALL_CREDENTIALS_SET_UP_PASSCODE_CONTENT)];
} else {
[self startAuthentication];
}
}
- (void)startAuthentication {
__weak __typeof(self) weakSelf = self;
void (^onReauthFinished)(ReauthenticationResult) =
^(ReauthenticationResult result) {
// Reauth can't be skipped for this flow.
CHECK_NE(result, ReauthenticationResult::kSkipped);
if (result == ReauthenticationResult::kSuccess) {
[weakSelf onStartDeletionFlowConfirmed];
}
};
[_reauthModule
attemptReauthWithLocalizedReason:
l10n_util::GetNSString(IDS_IOS_SETTINGS_DELETE_ALL_CREDENTIALS)
canReusePreviousAuth:NO
handler:onReauthFinished];
}
@end