// 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.
/**
* @fileoverview PasswordManagerProxy is an abstraction over
* chrome.passwordsPrivate which facilitates testing.
*/
export type BlockedSite = chrome.passwordsPrivate.ExceptionEntry;
export type AccountStorageOptInStateChangedListener = (optInState: boolean) =>
void;
export type CredentialsChangedListener =
(credentials: chrome.passwordsPrivate.PasswordUiEntry[]) => void;
export type PasswordCheckStatusChangedListener =
(status: chrome.passwordsPrivate.PasswordCheckStatus) => void;
export type BlockedSitesListChangedListener = (entries: BlockedSite[]) => void;
export type PasswordsFileExportProgressListener =
(progress: chrome.passwordsPrivate.PasswordExportProgress) => void;
export type PasswordManagerAuthTimeoutListener = () => void;
/**
* Represents different interactions the user can perform on the Password Check
* page.
*
* These values are persisted to logs. Entries should not be renumbered and
* numeric values should never be reused.
*
* Needs to stay in sync with PasswordCheckInteraction in enums.xml and
* password_manager_metrics_util.h.
*/
export enum PasswordCheckInteraction {
START_CHECK_AUTOMATICALLY = 0,
START_CHECK_MANUALLY = 1,
STOP_CHECK = 2,
CHANGE_PASSWORD = 3,
EDIT_PASSWORD = 4,
REMOVE_PASSWORD = 5,
SHOW_PASSWORD = 6,
MUTE_PASSWORD = 7,
UNMUTE_PASSWORD = 8,
CHANGE_PASSWORD_AUTOMATICALLY = 9,
// Must be last.
COUNT = 10,
}
/**
* Should be kept in sync with
* |password_manager::metrics_util::PasswordViewPageInteractions|.
* These values are persisted to logs. Entries should not be renumbered and
* numeric values should never be reused.
*/
export enum PasswordViewPageInteractions {
CREDENTIAL_ROW_CLICKED = 0,
CREDENTIAL_FOUND = 1,
CREDENTIAL_NOT_FOUND = 2,
USERNAME_COPY_BUTTON_CLICKED = 3,
PASSWORD_COPY_BUTTON_CLICKED = 4,
PASSWORD_SHOW_BUTTON_CLICKED = 5,
PASSWORD_EDIT_BUTTON_CLICKED = 6,
PASSWORD_DELETE_BUTTON_CLICKED = 7,
CREDENTIAL_EDITED = 8,
TIMED_OUT_IN_EDIT_DIALOG = 9,
TIMED_OUT_IN_VIEW_PAGE = 10,
CREDENTIAL_REQUESTED_BY_URL = 11,
PASSKEY_DISPLAY_NAME_COPY_BUTTON_CLICKED = 12,
PASSKEY_DELETE_BUTTON_CLICKED = 13,
PASSKEY_EDIT_BUTTON_CLICKED = 14,
// Must be last.
COUNT = 15,
}
/**
* Interface for all callbacks to the password API.
*/
export interface PasswordManagerProxy {
/**
* Add an observer to the list of saved passwords.
*/
addSavedPasswordListChangedListener(listener: CredentialsChangedListener):
void;
/**
* Remove an observer from the list of saved passwords.
*/
removeSavedPasswordListChangedListener(listener: CredentialsChangedListener):
void;
/**
* Add an observer to the list of blocked sites.
*/
addBlockedSitesListChangedListener(listener: BlockedSitesListChangedListener):
void;
/**
* Remove an observer from the list of blocked sites.
*/
removeBlockedSitesListChangedListener(
listener: BlockedSitesListChangedListener): void;
/**
* Add an observer to the passwords check status change.
*/
addPasswordCheckStatusListener(listener: PasswordCheckStatusChangedListener):
void;
/**
* Remove an observer to the passwords check status change.
*/
removePasswordCheckStatusListener(
listener: PasswordCheckStatusChangedListener): void;
/**
* Add an observer to the insecure passwords change.
*/
addInsecureCredentialsListener(listener: CredentialsChangedListener): void;
/**
* Remove an observer to the insecure passwords change.
*/
removeInsecureCredentialsListener(listener: CredentialsChangedListener): void;
/**
* Request the list of saved passwords.
*/
getSavedPasswordList(): Promise<chrome.passwordsPrivate.PasswordUiEntry[]>;
/**
* Request grouped credentials.
*/
getCredentialGroups(): Promise<chrome.passwordsPrivate.CredentialGroup[]>;
/**
* Request the list of blocked sites.
*/
getBlockedSitesList(): Promise<BlockedSite[]>;
/**
* Requests the current status of the check.
*/
getPasswordCheckStatus():
Promise<chrome.passwordsPrivate.PasswordCheckStatus>;
/**
* Requests the latest information about insecure credentials.
*/
getInsecureCredentials(): Promise<chrome.passwordsPrivate.PasswordUiEntry[]>;
/**
* Requests the latest information about insecure credentials.
*/
getCredentialsWithReusedPassword():
Promise<chrome.passwordsPrivate.PasswordUiEntryList[]>;
/**
* Requests the start of the bulk password check.
*/
startBulkPasswordCheck(): Promise<void>;
/**
* Records a given interaction on the Password Check page.
*/
recordPasswordCheckInteraction(interaction: PasswordCheckInteraction): void;
/**
* Records a given interaction on the Password details page.
*/
recordPasswordViewInteraction(interaction: PasswordViewPageInteractions):
void;
/**
* Triggers the shortcut creation dialog.
*/
showAddShortcutDialog(): void;
/**
* Gets the list of full (with note and password) credentials for given ids.
* @param ids The id for the password entries being retrieved.
* @return A promise that resolves to |PasswordUiEntry[]|.
*/
requestCredentialsDetails(ids: number[]):
Promise<chrome.passwordsPrivate.PasswordUiEntry[]>;
/**
* Gets the saved password for a given id and reason.
* @param id The id for the password entry being being retrieved.
* @param reason The reason why the plaintext password is requested.
* @return A promise that resolves to the plaintext password.
*/
requestPlaintextPassword(
id: number,
reason: chrome.passwordsPrivate.PlaintextReason): Promise<string>;
/**
* Saves a new password entry described by the given |options|.
* @param options Details about a new password and storage to be used.
* @return A promise that resolves when the new entry is added.
*/
addPassword(options: chrome.passwordsPrivate.AddPasswordOptions):
Promise<void>;
/**
* Fetches family members (password share recipients).
* @return A promise that resolves the FamilyFetchResults.
*/
fetchFamilyMembers(): Promise<chrome.passwordsPrivate.FamilyFetchResults>;
/**
* Sends sharing invitations to the recipients.
* @param id The id of the password entry to be shared.
* @param recipients The list of selected recipients.
*/
sharePassword(
id: number, recipients: chrome.passwordsPrivate.RecipientInfo[]): void;
/**
* Updates the given credential. Not all parameters can be updated.
* @param credential the credential to update.
* @return A promise that resolves if the credential was found and updated,
* rejects otherwise.
*/
changeCredential(credential: chrome.passwordsPrivate.PasswordUiEntry):
Promise<void>;
/**
* Should remove the credential and notify that the list has changed.
* @param id The id for the credential being removed. No-op if |id| is not in
* the list.
* @param fromStores The store from which credential should be removed.
*/
removeCredential(
id: number, fromStores: chrome.passwordsPrivate.PasswordStoreSet): void;
/**
* Should remove the blocked site and notify that the list has changed.
* @param id The id for the blocked url entry being removed. No-op if |id|
* is not in the list.
*/
removeBlockedSite(id: number): void;
/**
* Dismisses / mutes the |insecureCredential| in the passwords store.
*/
muteInsecureCredential(insecureCredential:
chrome.passwordsPrivate.PasswordUiEntry): void;
/**
* Restores / unmutes the |insecureCredential| in the passwords store.
*/
unmuteInsecureCredential(insecureCredential:
chrome.passwordsPrivate.PasswordUiEntry): void;
/**
* Should undo the last saved password or exception removal and notify that
* the list has changed.
*/
undoRemoveSavedPasswordOrException(): void;
/**
* Triggers the dialog for importing passwords.
* @return A promise that resolves to the import results.
*/
importPasswords(toStore: chrome.passwordsPrivate.PasswordStoreSet):
Promise<chrome.passwordsPrivate.ImportResults>;
/**
* Resumes the password import process when user has selected which passwords
* to replace.
* @return A promise that resolves to the |ImportResults|.
*/
continueImport(selectedIds: number[]):
Promise<chrome.passwordsPrivate.ImportResults>;
/**
* Resets the PasswordImporter if it is in the CONFLICTS/FINISHED state and
* the user closes the dialog. Only when the PasswordImporter is in FINISHED
* state, |deleteFile| option is taken into account.
* @param deleteFile Whether to trigger deletion of the last imported file.
*/
resetImporter(deleteFile: boolean): Promise<void>;
/**
* Queries the status of any ongoing export.
*/
requestExportProgressStatus():
Promise<chrome.passwordsPrivate.ExportProgressStatus>;
/**
* Triggers the dialog for exporting passwords.
*/
exportPasswords(): Promise<void>;
/**
* Add an observer to the export progress.
*/
addPasswordsFileExportProgressListener(
listener: PasswordsFileExportProgressListener): void;
/**
* Remove an observer from the export progress.
*/
removePasswordsFileExportProgressListener(
listener: PasswordsFileExportProgressListener): void;
/**
* Switches Biometric authentication before filling state after
* successful authentication.
* @return A promise that resolves with authentication result.
*/
switchBiometricAuthBeforeFillingState(): Promise<boolean>;
/**
* Shows the file with the exported passwords in the OS shell.
*/
showExportedFileInShell(filePath: string): void;
/**
* Requests whether the given |url| meets the requirements to save a password
* for it (e.g. valid, has proper scheme etc.).
* @return A promise that resolves to the corresponding URLCollection on
* success and to null otherwise.
*/
getUrlCollection(url: string):
Promise<chrome.passwordsPrivate.UrlCollection|null>;
/**
* Add an observer for authentication timeout.
*/
addPasswordManagerAuthTimeoutListener(
listener: PasswordManagerAuthTimeoutListener): void;
/**
* Remove the specified observer for authentication timeout.
*/
removePasswordManagerAuthTimeoutListener(
listener: PasswordManagerAuthTimeoutListener): void;
/**
* Requests extension of authentication validity.
*/
extendAuthValidity(): void;
/**
* Add an observer to the account storage opt-in state.
*/
addAccountStorageOptInStateListener(
listener: AccountStorageOptInStateChangedListener): void;
/**
* Remove an observer to the account storage opt-in state.
*/
removeAccountStorageOptInStateListener(
listener: AccountStorageOptInStateChangedListener): void;
/**
* Requests the account-storage opt-in state of the current user.
* @return A promise that resolves to the opt-in state.
*/
isOptedInForAccountStorage(): Promise<boolean>;
/**
* Triggers the opt-in or opt-out flow for the account storage.
* @param optIn Whether the user wants to opt in or opt out.
*/
optInForAccountStorage(optIn: boolean): void;
/**
* Requests whether the account store is a default location for saving
* passwords. False means the device store is a default one. Must be called
* when the current user has already opted-in for account storage.
* @return A promise that resolves to whether the account store is default.
*/
isAccountStoreDefault(): Promise<boolean>;
/**
* Moves a list of passwords from the device to the account
* @param ids The ids for the password entries being moved.
*/
movePasswordsToAccount(ids: number[]): void;
/** Dismiss the menu notifications for the Safety Hub password module. */
dismissSafetyHubPasswordMenuNotification(): void;
/** Starts the flow for changing Password Manager PIN. */
changePasswordManagerPin(): Promise<boolean>;
/** Checks whether changing the Password Manager PIN is possible. */
isPasswordManagerPinAvailable(): Promise<boolean>;
/**
* Starts the flow for disconnecting the Cloud Authenticator
* (Passkeys Enclave).
*/
disconnectCloudAuthenticator(): Promise<boolean>;
/**
* Checks whether the Chrome client is connected to the Cloud Authenticator
* (Passkeys Enclave).
*/
isConnectedToCloudAuthenticator(): Promise<boolean>;
/**
* Deletes all password manager data (passwords, passkeys, etc.)
*/
deleteAllPasswordManagerData(): Promise<boolean>;
}
/**
* Implementation that accesses the private API.
*/
export class PasswordManagerImpl implements PasswordManagerProxy {
addSavedPasswordListChangedListener(listener: CredentialsChangedListener) {
chrome.passwordsPrivate.onSavedPasswordsListChanged.addListener(listener);
}
removeSavedPasswordListChangedListener(listener: CredentialsChangedListener) {
chrome.passwordsPrivate.onSavedPasswordsListChanged.removeListener(
listener);
}
addBlockedSitesListChangedListener(listener:
BlockedSitesListChangedListener) {
chrome.passwordsPrivate.onPasswordExceptionsListChanged.addListener(
listener);
}
removeBlockedSitesListChangedListener(listener:
BlockedSitesListChangedListener) {
chrome.passwordsPrivate.onPasswordExceptionsListChanged.removeListener(
listener);
}
addPasswordCheckStatusListener(listener: PasswordCheckStatusChangedListener) {
chrome.passwordsPrivate.onPasswordCheckStatusChanged.addListener(listener);
}
removePasswordCheckStatusListener(listener:
PasswordCheckStatusChangedListener) {
chrome.passwordsPrivate.onPasswordCheckStatusChanged.removeListener(
listener);
}
addInsecureCredentialsListener(listener: CredentialsChangedListener) {
chrome.passwordsPrivate.onInsecureCredentialsChanged.addListener(listener);
}
removeInsecureCredentialsListener(listener: CredentialsChangedListener) {
chrome.passwordsPrivate.onInsecureCredentialsChanged.removeListener(
listener);
}
getSavedPasswordList() {
return chrome.passwordsPrivate.getSavedPasswordList().catch(() => []);
}
getCredentialGroups() {
return chrome.passwordsPrivate.getCredentialGroups();
}
getBlockedSitesList() {
return chrome.passwordsPrivate.getPasswordExceptionList().catch(() => []);
}
getPasswordCheckStatus() {
return chrome.passwordsPrivate.getPasswordCheckStatus();
}
getInsecureCredentials() {
return chrome.passwordsPrivate.getInsecureCredentials();
}
getCredentialsWithReusedPassword() {
return chrome.passwordsPrivate.getCredentialsWithReusedPassword();
}
startBulkPasswordCheck() {
return chrome.passwordsPrivate.startPasswordCheck();
}
recordPasswordCheckInteraction(interaction: PasswordCheckInteraction) {
chrome.metricsPrivate.recordEnumerationValue(
'PasswordManager.BulkCheck.UserAction', interaction,
PasswordCheckInteraction.COUNT);
}
recordPasswordViewInteraction(interaction: PasswordViewPageInteractions) {
chrome.metricsPrivate.recordEnumerationValue(
'PasswordManager.PasswordViewPage.UserActions', interaction,
PasswordViewPageInteractions.COUNT);
}
showAddShortcutDialog() {
chrome.passwordsPrivate.showAddShortcutDialog();
}
requestCredentialsDetails(ids: number[]) {
return chrome.passwordsPrivate.requestCredentialsDetails(ids);
}
requestPlaintextPassword(
id: number, reason: chrome.passwordsPrivate.PlaintextReason) {
return chrome.passwordsPrivate.requestPlaintextPassword(id, reason);
}
addPassword(options: chrome.passwordsPrivate.AddPasswordOptions) {
return chrome.passwordsPrivate.addPassword(options);
}
changeCredential(credential: chrome.passwordsPrivate.PasswordUiEntry) {
return chrome.passwordsPrivate.changeCredential(credential);
}
removeCredential(
id: number, fromStores: chrome.passwordsPrivate.PasswordStoreSet) {
chrome.passwordsPrivate.removeCredential(id, fromStores);
}
removeBlockedSite(id: number) {
chrome.passwordsPrivate.removePasswordException(id);
}
muteInsecureCredential(insecureCredential:
chrome.passwordsPrivate.PasswordUiEntry) {
chrome.passwordsPrivate.muteInsecureCredential(insecureCredential);
}
unmuteInsecureCredential(insecureCredential:
chrome.passwordsPrivate.PasswordUiEntry) {
chrome.passwordsPrivate.unmuteInsecureCredential(insecureCredential);
}
undoRemoveSavedPasswordOrException() {
chrome.passwordsPrivate.undoRemoveSavedPasswordOrException();
}
fetchFamilyMembers() {
return chrome.passwordsPrivate.fetchFamilyMembers();
}
sharePassword(
id: number, recipients: chrome.passwordsPrivate.RecipientInfo[]) {
chrome.passwordsPrivate.sharePassword(id, recipients);
}
importPasswords(toStore: chrome.passwordsPrivate.PasswordStoreSet) {
return chrome.passwordsPrivate.importPasswords(toStore);
}
continueImport(selectedIds: number[]) {
return chrome.passwordsPrivate.continueImport(selectedIds);
}
resetImporter(deleteFile: boolean) {
return chrome.passwordsPrivate.resetImporter(deleteFile);
}
requestExportProgressStatus() {
return chrome.passwordsPrivate.requestExportProgressStatus();
}
exportPasswords() {
return chrome.passwordsPrivate.exportPasswords();
}
addPasswordsFileExportProgressListener(
listener: PasswordsFileExportProgressListener) {
chrome.passwordsPrivate.onPasswordsFileExportProgress.addListener(listener);
}
removePasswordsFileExportProgressListener(
listener: PasswordsFileExportProgressListener) {
chrome.passwordsPrivate.onPasswordsFileExportProgress.removeListener(
listener);
}
switchBiometricAuthBeforeFillingState() {
return chrome.passwordsPrivate.switchBiometricAuthBeforeFillingState();
}
showExportedFileInShell(filePath: string) {
chrome.passwordsPrivate.showExportedFileInShell(filePath);
}
getUrlCollection(url: string) {
return chrome.passwordsPrivate.getUrlCollection(url);
}
addPasswordManagerAuthTimeoutListener(
listener: PasswordManagerAuthTimeoutListener) {
chrome.passwordsPrivate.onPasswordManagerAuthTimeout.addListener(listener);
}
removePasswordManagerAuthTimeoutListener(
listener: PasswordManagerAuthTimeoutListener) {
chrome.passwordsPrivate.onPasswordManagerAuthTimeout.removeListener(
listener);
}
extendAuthValidity() {
chrome.passwordsPrivate.extendAuthValidity();
}
addAccountStorageOptInStateListener(
listener: AccountStorageOptInStateChangedListener) {
chrome.passwordsPrivate.onAccountStorageOptInStateChanged.addListener(
listener);
}
removeAccountStorageOptInStateListener(
listener: AccountStorageOptInStateChangedListener) {
chrome.passwordsPrivate.onAccountStorageOptInStateChanged.removeListener(
listener);
}
isOptedInForAccountStorage() {
return chrome.passwordsPrivate.isOptedInForAccountStorage();
}
optInForAccountStorage(optIn: boolean) {
chrome.passwordsPrivate.optInForAccountStorage(optIn);
}
isAccountStoreDefault() {
return chrome.passwordsPrivate.isAccountStoreDefault();
}
movePasswordsToAccount(ids: number[]) {
chrome.passwordsPrivate.movePasswordsToAccount(ids);
}
dismissSafetyHubPasswordMenuNotification() {
chrome.send('dismissSafetyHubPasswordMenuNotification');
}
changePasswordManagerPin() {
return chrome.passwordsPrivate.changePasswordManagerPin();
}
isPasswordManagerPinAvailable() {
return chrome.passwordsPrivate.isPasswordManagerPinAvailable();
}
disconnectCloudAuthenticator() {
return chrome.passwordsPrivate.disconnectCloudAuthenticator();
}
isConnectedToCloudAuthenticator() {
return chrome.passwordsPrivate.isConnectedToCloudAuthenticator();
}
deleteAllPasswordManagerData() {
return chrome.passwordsPrivate.deleteAllPasswordManagerData();
}
static getInstance(): PasswordManagerProxy {
return instance || (instance = new PasswordManagerImpl());
}
static setInstance(obj: PasswordManagerProxy) {
instance = obj;
}
}
let instance: PasswordManagerProxy|null = null;