chromium/ios/chrome/browser/credential_provider/model/credential_provider_migrator.mm

// Copyright 2021 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/credential_provider/model/credential_provider_migrator.h"

#import "components/password_manager/core/browser/password_form.h"
#import "components/password_manager/core/browser/password_store/password_store_interface.h"
#import "components/sync/protocol/webauthn_credential_specifics.pb.h"
#import "components/webauthn/core/browser/passkey_model.h"
#import "ios/chrome/browser/credential_provider/model/archivable_credential+password_form.h"
#import "ios/chrome/common/credential_provider/archivable_credential+passkey.h"
#import "ios/chrome/common/credential_provider/user_defaults_credential_store.h"

using password_manager::PasswordStoreInterface;

NSErrorDomain const kCredentialProviderMigratorErrorDomain =
    @"kCredentialProviderMigratorErrorDomain";

typedef enum : NSInteger {
  CredentialProviderMigratorErrorAlreadyRunning,
} CredentialProviderMigratorErrors;

@interface CredentialProviderMigrator () {
  // Passkey store.
  raw_ptr<webauthn::PasskeyModel> _passkeyStore;
}

// Key used to retrieve the temporal storage.
@property(nonatomic, copy) NSString* key;

// User defaults containing the temporal storage.
@property(nonatomic, copy) NSUserDefaults* userDefaults;

// Temporal store containing the passwords created in CPE extension.
@property(nonatomic, strong) UserDefaultsCredentialStore* temporalStore;

// Password manager store, where passwords will be migrated to.
@property(nonatomic, assign) scoped_refptr<PasswordStoreInterface>
    passwordStore;

@end

@implementation CredentialProviderMigrator

- (instancetype)initWithUserDefaults:(NSUserDefaults*)userDefaults
                                 key:(NSString*)key
                       passwordStore:
                           (scoped_refptr<PasswordStoreInterface>)passwordStore
                        passkeyStore:(webauthn::PasskeyModel*)passkeyStore {
  self = [super init];
  if (self) {
    _key = key;
    _userDefaults = userDefaults;
    _passwordStore = passwordStore;
    _passkeyStore = passkeyStore;
  }
  return self;
}

- (void)startMigrationWithCompletion:(void (^)(BOOL success,
                                               NSError* error))completion {
  if (self.temporalStore) {
    NSError* error =
        [NSError errorWithDomain:kCredentialProviderMigratorErrorDomain
                            code:CredentialProviderMigratorErrorAlreadyRunning
                        userInfo:nil];
    completion(NO, error);
    return;
  }
  self.temporalStore = [[UserDefaultsCredentialStore alloc]
      initWithUserDefaults:self.userDefaults
                       key:self.key];
  NSArray<id<Credential>>* credentials = self.temporalStore.credentials.copy;
  for (id<Credential> credential in credentials) {
    if (credential.isPasskey) {
      // TODO(crbug.com/330355124): this can happen too early (before
      // PasskeySyncBridge::store_ is created in _passkeyStore) if it happens
      // at startup, so add a mechanism to re-trigger the migration later.
      if (!_passkeyStore || !_passkeyStore->IsReady()) {
        continue;
      }

      sync_pb::WebauthnCredentialSpecifics passkey =
          PasskeyFromCredential(credential);
      _passkeyStore->CreatePasskey(passkey);
    } else {
      password_manager::PasswordForm form =
          PasswordFormFromCredential(credential);
      self.passwordStore->AddLogin(form);
    }
    [self.temporalStore
        removeCredentialWithRecordIdentifier:credential.recordIdentifier];
  }
  __weak __typeof__(self) weakSelf = self;
  [self.temporalStore saveDataWithCompletion:^(NSError* error) {
    DCHECK(!error);
    weakSelf.temporalStore = nil;
    completion(error == nil, error);
  }];
}

@end