chromium/ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_mediator.mm

// Copyright 2023 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/password_checkup/password_checkup_mediator.h"

#import "base/memory/raw_ptr.h"
#import "ios/chrome/browser/passwords/model/ios_chrome_password_check_manager.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/ui/settings/password/password_checkup/password_checkup_consumer.h"
#import "ios/chrome/grit/ios_branded_strings.h"
#import "ui/base/l10n/l10n_util.h"

using password_manager::InsecurePasswordCounts;

namespace {

// Returns true if a password check error occured.
bool DidPasswordCheckupFail(PasswordCheckState currentState) {
  switch (currentState) {
    case PasswordCheckState::kRunning:
    case PasswordCheckState::kNoPasswords:
    case PasswordCheckState::kCanceled:
    case PasswordCheckState::kIdle:
      return false;
    case PasswordCheckState::kOffline:
    case PasswordCheckState::kSignedOut:
    case PasswordCheckState::kQuotaLimit:
    case PasswordCheckState::kOther:
      return true;
  }
}

}  // namespace

@interface PasswordCheckupMediator () <PasswordCheckObserver> {
  // The service responsible for password check feature.
  scoped_refptr<IOSChromePasswordCheckManager> _passwordCheckManager;

  // A helper object for passing data about changes in password check status
  // and changes to compromised credentials list.
  std::unique_ptr<PasswordCheckObserverBridge> _passwordCheckObserver;

  // Current password counts associated with the different insecure types.
  InsecurePasswordCounts _currentInsecurePasswordCounts;

  // The string containing the timestamp of the last completed check.
  NSString* _formattedElapsedTimeSinceLastCheck;

  // Current number of affiliated groups for which the user has saved passwords.
  int _currentAffiliatedGroupCount;
}

// Current state of password check.
@property(nonatomic, assign) PasswordCheckState currentState;

@end

@implementation PasswordCheckupMediator

- (instancetype)initWithPasswordCheckManager:
    (scoped_refptr<IOSChromePasswordCheckManager>)passwordCheckManager {
  self = [super init];
  if (self) {
    _passwordCheckManager = passwordCheckManager;
    _passwordCheckObserver = std::make_unique<PasswordCheckObserverBridge>(
        self, _passwordCheckManager.get());
    _currentState = _passwordCheckManager->GetPasswordCheckState();
  }
  return self;
}

- (void)setConsumer:(id<PasswordCheckupConsumer>)consumer {
  if (_consumer == consumer) {
    return;
  }
  _consumer = consumer;

  [self updateConsumer];
}

- (void)disconnect {
  _passwordCheckObserver.reset();
  _passwordCheckManager.reset();
}

#pragma mark - PasswordCheckupViewControllerDelegate

- (void)startPasswordCheck {
  _passwordCheckManager->StartPasswordCheck(
      password_manager::LeakDetectionInitiator::kBulkSyncedPasswordsCheck);
}

#pragma mark - PasswordCheckObserver

- (void)passwordCheckStateDidChange:(PasswordCheckState)state {
  self.currentState = state;
}

- (void)insecureCredentialsDidChange {
  // Insecure password changes have no effect on UI while check is running.
  if (_passwordCheckManager->GetPasswordCheckState() ==
      PasswordCheckState::kRunning) {
    return;
  }

  [self updateConsumer];
}

- (void)passwordCheckManagerWillShutdown {
  _passwordCheckObserver.reset();
}

#pragma mark - Setters

- (void)setCurrentState:(PasswordCheckState)state {
  if (state == _currentState) {
    return;
  }
  _currentState = state;
  [self updateConsumer];
}

#pragma mark - Private Methods

// Updates the `_consumer` PasswordCheckupHomepageState, the number of
// affiliated groups and the the insecure password counts.
- (void)updateConsumer {
  DCHECK(self.consumer);

  PasswordCheckupHomepageState passwordCheckupHomepageState =
      [self computePasswordCheckupHomepageState];

  if (!DidPasswordCheckupFail(_currentState)) {
    std::vector<password_manager::CredentialUIEntry> insecureCredentials =
        _passwordCheckManager->GetInsecureCredentials();
    _currentInsecurePasswordCounts =
        password_manager::CountInsecurePasswordsPerInsecureType(
            insecureCredentials);

    _currentAffiliatedGroupCount =
        _passwordCheckManager->GetSavedPasswordsPresenter()
            ->GetAffiliatedGroups()
            .size();

    _formattedElapsedTimeSinceLastCheck =
        [self formattedElapsedTimeSinceLastCheck];
  }

  [self.consumer
         setPasswordCheckupHomepageState:passwordCheckupHomepageState
                  insecurePasswordCounts:_currentInsecurePasswordCounts
      formattedElapsedTimeSinceLastCheck:_formattedElapsedTimeSinceLastCheck];
  [self.consumer setAffiliatedGroupCount:_currentAffiliatedGroupCount];
  if (DidPasswordCheckupFail(_currentState)) {
    [self.consumer showErrorDialogWithMessage:[self computeErrorDialogMessage]];
  }
}

// Returns PasswordCheckupHomepageState based on the current PasswordCheckState.
- (PasswordCheckupHomepageState)computePasswordCheckupHomepageState {
  switch (_currentState) {
    case PasswordCheckState::kRunning:
      return PasswordCheckupHomepageStateRunning;
    case PasswordCheckState::kNoPasswords:
      return PasswordCheckupHomepageStateDisabled;
    case PasswordCheckState::kSignedOut:
    case PasswordCheckState::kOffline:
    case PasswordCheckState::kQuotaLimit:
    case PasswordCheckState::kOther:
      return PasswordCheckupHomepageStateDone;
    case PasswordCheckState::kCanceled:
    case PasswordCheckState::kIdle:
      return PasswordCheckupHomepageStateDone;
  }
}

// Returns the string containing the timestamp of the last password check.
- (NSString*)formattedElapsedTimeSinceLastCheck {
  std::optional<base::Time> lastCompletedCheck =
      _passwordCheckManager->GetLastPasswordCheckTime();
  return password_manager::FormatElapsedTimeSinceLastCheck(
      lastCompletedCheck, /*use_title_case=*/true);
}

// Computes the error message to display in the error dialog.
- (NSString*)computeErrorDialogMessage {
  switch (_currentState) {
    case PasswordCheckState::kRunning:
    case PasswordCheckState::kNoPasswords:
    case PasswordCheckState::kCanceled:
    case PasswordCheckState::kIdle:
      return nil;
    case PasswordCheckState::kOffline:
      return l10n_util::GetNSString(IDS_IOS_PASSWORD_CHECKUP_ERROR_OFFLINE);
    case PasswordCheckState::kSignedOut:
    case PasswordCheckState::kQuotaLimit:
    case PasswordCheckState::kOther:
      return l10n_util::GetNSString(IDS_IOS_PASSWORD_CHECKUP_ERROR_OTHER);
  }
}

@end