2022-12-06 14:47:42 +01:00
|
|
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
2023-03-15 02:19:16 +01:00
|
|
|
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
|
2022-06-14 17:10:53 +02:00
|
|
|
import { TotpService } from "@bitwarden/common/abstractions/totp.service";
|
[AC-1266] Enums filename conventions (#5140)
* refactor: update clientType enum
* refactor: update deviceType filename
* refactor: update encryptedExportType filename
* refactor: update encryptionType filename
* refactor: update eventType filename
* refactor: update fieldType filename
* refactor: update fileUploadType filename
* refactor: update hashPurpose filename
* refactor: update htmlStorageLocation filename
* refactor: update kdfType filename
* refactor: update keySuffixOptions filename
* refactor: update linkedIdType filename
* refactor: update logLevelType filename
* refactor: update nativeMessagingVersion filename
* refactor: update notificationType filename
* refactor: update productType filename
* refactor: update secureNoteType filename
* refactor: update stateVersion filename
* refactor: update storageLocation filename
* refactor: update themeType filename
* refactor: update uriMatchType filename
* fix: update kdfType classes missed in initial pass, refs AC-1266
* fix: missing import update for device-type
* refactor: add barrel file for enums and update pathed import statements, refs AC-1266
* fix: incorrect import statements for web, refs AC-1266
* fix: missed import statement updates (browser), refs AC-1266
* fix: missed import statement changes (cli), refs AC-1266
* fix: missed import statement changes (desktop), refs AC-1266
* fix: prettier, refs AC-1266
* refactor: (libs) update relative paths to use barrel file, refs AC-1266
* fix: missed find/replace import statements for SecureNoteType, refs AC-1266
* refactor: apply .enum suffix to enums folder and modify leftover relative paths, refs AC-1266
* fix: find/replace errors for native-messaging-version, refs AC-1266
2023-04-05 05:42:21 +02:00
|
|
|
import { EventType, FieldType, UriMatchType } from "@bitwarden/common/enums";
|
2023-06-06 22:34:53 +02:00
|
|
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
2023-01-31 22:08:37 +01:00
|
|
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
|
|
|
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
|
|
|
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
|
|
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
|
|
|
import { FieldView } from "@bitwarden/common/vault/models/view/field.view";
|
2020-08-12 22:05:12 +02:00
|
|
|
|
2023-06-06 22:34:53 +02:00
|
|
|
import { BrowserApi } from "../../platform/browser/browser-api";
|
|
|
|
import { BrowserStateService } from "../../platform/services/abstractions/browser-state.service";
|
2023-01-31 22:08:37 +01:00
|
|
|
import AutofillField from "../models/autofill-field";
|
|
|
|
import AutofillPageDetails from "../models/autofill-page-details";
|
|
|
|
import AutofillScript from "../models/autofill-script";
|
2022-01-27 22:22:51 +01:00
|
|
|
|
2022-08-10 03:30:26 +02:00
|
|
|
import {
|
|
|
|
AutoFillOptions,
|
|
|
|
AutofillService as AutofillServiceInterface,
|
|
|
|
PageDetail,
|
|
|
|
FormData,
|
|
|
|
} from "./abstractions/autofill.service";
|
2023-02-22 22:03:05 +01:00
|
|
|
import {
|
|
|
|
AutoFillConstants,
|
|
|
|
CreditCardAutoFillConstants,
|
|
|
|
IdentityAutoFillConstants,
|
|
|
|
} from "./autofill-constants";
|
2022-01-04 16:48:57 +01:00
|
|
|
|
2022-08-10 03:30:26 +02:00
|
|
|
export interface GenerateFillScriptOptions {
|
|
|
|
skipUsernameOnlyFill: boolean;
|
|
|
|
onlyEmptyFields: boolean;
|
|
|
|
onlyVisibleFields: boolean;
|
|
|
|
fillNewPassword: boolean;
|
|
|
|
cipher: CipherView;
|
2023-03-15 02:19:16 +01:00
|
|
|
tabUrl: string;
|
|
|
|
defaultUriMatch: UriMatchType;
|
2022-08-10 03:30:26 +02:00
|
|
|
}
|
|
|
|
|
2018-01-11 20:45:27 +01:00
|
|
|
export default class AutofillService implements AutofillServiceInterface {
|
2022-01-04 16:41:07 +01:00
|
|
|
constructor(
|
|
|
|
private cipherService: CipherService,
|
2022-11-23 23:26:57 +01:00
|
|
|
private stateService: BrowserStateService,
|
2022-01-04 16:41:07 +01:00
|
|
|
private totpService: TotpService,
|
2022-12-06 14:47:42 +01:00
|
|
|
private eventCollectionService: EventCollectionService,
|
2023-03-15 02:19:16 +01:00
|
|
|
private logService: LogService,
|
|
|
|
private settingsService: SettingsService
|
2022-01-04 16:41:07 +01:00
|
|
|
) {}
|
|
|
|
|
2022-08-10 03:30:26 +02:00
|
|
|
getFormsWithPasswordFields(pageDetails: AutofillPageDetails): FormData[] {
|
|
|
|
const formData: FormData[] = [];
|
2022-01-04 16:41:07 +01:00
|
|
|
|
[SG-1026 / PM-1125] - Document / Improve Form Detection in Notification Bar (#4798)
* SG-1026 - Documenting / slight refactoring of notification-bar - WIP
* SG-1026 - More documentation WIP
* SG-1026 - Continued documentation of notification bar + testing theories for specific sites as part of research to identify areas for possible improvement + added types where appropriate.
* SG-1026 - getSubmitButton docs
* SG-1026 - Autofill Service tweak - On account creation (ex: talkshoe.com), even if the pageDetails contained a valid form to watch, the loadPasswordFields method parameter for fillNewPassword being false for inputs with autoCompleteType of "new-password" would cause the account creation form to not be watched (null form data returned to notification bar). Setting this to true will help capture more account creations in the above specified scenario.
* SG-1026 - Additional documentation / comment clean up
* SG-1026 - Remove unused pageDetails array
* SG-1026 - These changes address form detection issues for the password change form on talkshoe.com: (1) Update autofill.service getFormsWithPasswordFields(...) method to group autofill.js found password type fields under a single form in a very specific scenario where the most likely case is that it is a password change form with poorly designed mark up in a SPA (2) Notification bar - when listening to a form, we must use both the loginButtonNames and the changePasswordButton names as we don't know what type of form we are listening to (3) Notification bar - on page change, we must empty out the watched forms array to prevent forms w/ the same opId being added to the array on SPA url change (4) Notification bar - getSubmitButton update - If we cannot find a submit button within a form, try going up one level to the parent element and searching again (+ added save to changePasswordButtonNames). (5) Notification bar - when listening to a form with a submit button, we can attach the formOpId to the button so we can only have DOM traversal in one location and retrieve the form off the button later on in the form submission logic. For now, I'm just adding it as a fallback, but it could be the primary approach with more testing.
* SG-1026 - On first load of the notification-bar content script, we should start observing the DOM immediately so we properly catch rendered forms instead of waiting for a second. This was especially prevelant on refreshing the password change form page on talkshoe.com.
* SG-1026 - Due to the previous, timeout based nature of the calls to collectPageDetailsIfNeeded (now handlePageChange), the mutation observer could get setup late and miss forms loading (ex: refreshing a password change page on talkshoe.com). DOM observation is now setup as fast as possible on page load for SPAs/Non SPAs and on change for SPAs by having the mutation observer itself detect page change and deterministically calling handlePageChange(). However, with these changes, page detail collection still only occurs after a minimum of ~1 second whether or not it was triggered from the mutation observer detecting forms being injected onto the page or the scheduleHandlePageChange running (which has a theoretical maximum time to page detail collection of ~1.999 seconds but this does require the mutation observer to miss the page change in a SPA which shouldn't happen).
* SG-1026 - Identified issue with current form retrieval step in autofill service which prevents multi-step account creation forms from being returned to the notification-bar content script from the notification.background.ts script.
* SG-1026 - Add logic to formSubmitted to try and successfully process multi-step login form (email then password on https://login.live.com/login.srf) with next button that gets swapped out for a true submit button in order to prompt for saving user credentials if not in Bitwarden. This logic works *sometimes* as the submit button page change often stops the submit button event listeners from being able to fire and send the login to the background script. However, that is a separate issue to be solved, and sometimes is better than never. This type of logic might be useful in solving the multi-step account creation form on https://signup.live.com/signup but that will require additional changes to the autofill service which current intercepts forms without passwords and prevents them from reaching the notification-bar.ts content script.
* SG-1026 - Add note explaining the persistence of the content script
* SG-1026 - Update stack overflow link to improve clarity.
---------
Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com>
2023-04-13 21:59:31 +02:00
|
|
|
const passwordFields = AutofillService.loadPasswordFields(pageDetails, true, true, false, true);
|
|
|
|
|
|
|
|
// TODO: this logic prevents multi-step account creation forms (that just start with email)
|
|
|
|
// from being passed on to the notification bar content script - even if autofill.js found the form and email field.
|
|
|
|
// ex: https://signup.live.com/
|
2022-01-04 16:41:07 +01:00
|
|
|
if (passwordFields.length === 0) {
|
|
|
|
return formData;
|
|
|
|
}
|
2020-08-12 22:05:12 +02:00
|
|
|
|
[SG-1026 / PM-1125] - Document / Improve Form Detection in Notification Bar (#4798)
* SG-1026 - Documenting / slight refactoring of notification-bar - WIP
* SG-1026 - More documentation WIP
* SG-1026 - Continued documentation of notification bar + testing theories for specific sites as part of research to identify areas for possible improvement + added types where appropriate.
* SG-1026 - getSubmitButton docs
* SG-1026 - Autofill Service tweak - On account creation (ex: talkshoe.com), even if the pageDetails contained a valid form to watch, the loadPasswordFields method parameter for fillNewPassword being false for inputs with autoCompleteType of "new-password" would cause the account creation form to not be watched (null form data returned to notification bar). Setting this to true will help capture more account creations in the above specified scenario.
* SG-1026 - Additional documentation / comment clean up
* SG-1026 - Remove unused pageDetails array
* SG-1026 - These changes address form detection issues for the password change form on talkshoe.com: (1) Update autofill.service getFormsWithPasswordFields(...) method to group autofill.js found password type fields under a single form in a very specific scenario where the most likely case is that it is a password change form with poorly designed mark up in a SPA (2) Notification bar - when listening to a form, we must use both the loginButtonNames and the changePasswordButton names as we don't know what type of form we are listening to (3) Notification bar - on page change, we must empty out the watched forms array to prevent forms w/ the same opId being added to the array on SPA url change (4) Notification bar - getSubmitButton update - If we cannot find a submit button within a form, try going up one level to the parent element and searching again (+ added save to changePasswordButtonNames). (5) Notification bar - when listening to a form with a submit button, we can attach the formOpId to the button so we can only have DOM traversal in one location and retrieve the form off the button later on in the form submission logic. For now, I'm just adding it as a fallback, but it could be the primary approach with more testing.
* SG-1026 - On first load of the notification-bar content script, we should start observing the DOM immediately so we properly catch rendered forms instead of waiting for a second. This was especially prevelant on refreshing the password change form page on talkshoe.com.
* SG-1026 - Due to the previous, timeout based nature of the calls to collectPageDetailsIfNeeded (now handlePageChange), the mutation observer could get setup late and miss forms loading (ex: refreshing a password change page on talkshoe.com). DOM observation is now setup as fast as possible on page load for SPAs/Non SPAs and on change for SPAs by having the mutation observer itself detect page change and deterministically calling handlePageChange(). However, with these changes, page detail collection still only occurs after a minimum of ~1 second whether or not it was triggered from the mutation observer detecting forms being injected onto the page or the scheduleHandlePageChange running (which has a theoretical maximum time to page detail collection of ~1.999 seconds but this does require the mutation observer to miss the page change in a SPA which shouldn't happen).
* SG-1026 - Identified issue with current form retrieval step in autofill service which prevents multi-step account creation forms from being returned to the notification-bar content script from the notification.background.ts script.
* SG-1026 - Add logic to formSubmitted to try and successfully process multi-step login form (email then password on https://login.live.com/login.srf) with next button that gets swapped out for a true submit button in order to prompt for saving user credentials if not in Bitwarden. This logic works *sometimes* as the submit button page change often stops the submit button event listeners from being able to fire and send the login to the background script. However, that is a separate issue to be solved, and sometimes is better than never. This type of logic might be useful in solving the multi-step account creation form on https://signup.live.com/signup but that will require additional changes to the autofill service which current intercepts forms without passwords and prevents them from reaching the notification-bar.ts content script.
* SG-1026 - Add note explaining the persistence of the content script
* SG-1026 - Update stack overflow link to improve clarity.
---------
Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com>
2023-04-13 21:59:31 +02:00
|
|
|
// Back up check for cases where there are several password fields detected,
|
|
|
|
// but they are not all part of the form b/c of bad HTML
|
|
|
|
|
|
|
|
// gather password fields that don't have an enclosing form
|
|
|
|
const passwordFieldsWithoutForm = passwordFields.filter((pf) => pf.form === undefined);
|
|
|
|
const formKeys = Object.keys(pageDetails.forms);
|
|
|
|
const formCount = formKeys.length;
|
|
|
|
|
|
|
|
// if we have 3 password fields and only 1 form, and there are password fields that are not within a form
|
|
|
|
// but there is at least one password field within the form, then most likely this is a poorly built password change form
|
|
|
|
if (passwordFields.length === 3 && formCount == 1 && passwordFieldsWithoutForm.length > 0) {
|
|
|
|
// Only one form so get the singular form key
|
|
|
|
const soloFormKey = formKeys[0];
|
|
|
|
|
|
|
|
const atLeastOnePasswordFieldWithinSoloForm =
|
|
|
|
passwordFields.filter((pf) => pf.form !== null && pf.form === soloFormKey).length > 0;
|
|
|
|
|
|
|
|
if (atLeastOnePasswordFieldWithinSoloForm) {
|
|
|
|
// We have a form with at least one password field,
|
|
|
|
// so let's make an assumption that the password fields without a form are actually part of this form
|
|
|
|
passwordFieldsWithoutForm.forEach((pf) => {
|
|
|
|
pf.form = soloFormKey;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
for (const formKey in pageDetails.forms) {
|
2022-02-24 18:14:04 +01:00
|
|
|
// eslint-disable-next-line
|
2022-01-04 16:41:07 +01:00
|
|
|
if (!pageDetails.forms.hasOwnProperty(formKey)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const formPasswordFields = passwordFields.filter((pf) => formKey === pf.form);
|
|
|
|
if (formPasswordFields.length > 0) {
|
|
|
|
let uf = this.findUsernameField(pageDetails, formPasswordFields[0], false, false, false);
|
|
|
|
if (uf == null) {
|
|
|
|
// not able to find any viewable username fields. maybe there are some "hidden" ones?
|
|
|
|
uf = this.findUsernameField(pageDetails, formPasswordFields[0], true, true, false);
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
2022-01-04 16:41:07 +01:00
|
|
|
formData.push({
|
|
|
|
form: pageDetails.forms[formKey],
|
|
|
|
password: formPasswordFields[0],
|
|
|
|
username: uf,
|
|
|
|
passwords: formPasswordFields,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
return formData;
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2023-03-15 02:19:16 +01:00
|
|
|
/**
|
|
|
|
* Autofills a given tab with a given login item
|
|
|
|
* @param options Instructions about the autofill operation, including tab and login item
|
|
|
|
* @returns The TOTP code of the successfully autofilled login, if any
|
|
|
|
*/
|
|
|
|
async doAutoFill(options: AutoFillOptions): Promise<string> {
|
2022-07-21 19:56:09 +02:00
|
|
|
const tab = options.tab;
|
2022-01-04 16:41:07 +01:00
|
|
|
if (!tab || !options.cipher || !options.pageDetails || !options.pageDetails.length) {
|
|
|
|
throw new Error("Nothing to auto-fill.");
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
|
|
|
|
2022-08-10 03:30:26 +02:00
|
|
|
let totpPromise: Promise<string> = null;
|
|
|
|
|
2022-01-27 22:22:51 +01:00
|
|
|
const canAccessPremium = await this.stateService.getCanAccessPremium();
|
2023-03-15 02:19:16 +01:00
|
|
|
const defaultUriMatch = (await this.stateService.getDefaultUriMatch()) ?? UriMatchType.Domain;
|
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
let didAutofill = false;
|
2022-08-10 03:30:26 +02:00
|
|
|
options.pageDetails.forEach((pd) => {
|
2022-01-04 16:41:07 +01:00
|
|
|
// make sure we're still on correct tab
|
|
|
|
if (pd.tab.id !== tab.id || pd.tab.url !== tab.url) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const fillScript = this.generateFillScript(pd.details, {
|
|
|
|
skipUsernameOnlyFill: options.skipUsernameOnlyFill || false,
|
|
|
|
onlyEmptyFields: options.onlyEmptyFields || false,
|
|
|
|
onlyVisibleFields: options.onlyVisibleFields || false,
|
|
|
|
fillNewPassword: options.fillNewPassword || false,
|
|
|
|
cipher: options.cipher,
|
2023-03-15 02:19:16 +01:00
|
|
|
tabUrl: tab.url,
|
|
|
|
defaultUriMatch: defaultUriMatch,
|
2022-01-04 16:41:07 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
if (!fillScript || !fillScript.script || !fillScript.script.length) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-03-15 02:19:16 +01:00
|
|
|
if (
|
|
|
|
fillScript.untrustedIframe &&
|
|
|
|
options.allowUntrustedIframe != undefined &&
|
|
|
|
!options.allowUntrustedIframe
|
|
|
|
) {
|
|
|
|
this.logService.info("Auto-fill on page load was blocked due to an untrusted iframe.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
// Add a small delay between operations
|
|
|
|
fillScript.properties.delay_between_operations = 20;
|
|
|
|
|
|
|
|
didAutofill = true;
|
|
|
|
if (!options.skipLastUsed) {
|
|
|
|
this.cipherService.updateLastUsedDate(options.cipher.id);
|
|
|
|
}
|
|
|
|
|
|
|
|
BrowserApi.tabSendMessage(
|
|
|
|
tab,
|
|
|
|
{
|
|
|
|
command: "fillForm",
|
|
|
|
fillScript: fillScript,
|
|
|
|
url: tab.url,
|
|
|
|
},
|
|
|
|
{ frameId: pd.frameId }
|
|
|
|
);
|
|
|
|
|
|
|
|
if (
|
|
|
|
options.cipher.type !== CipherType.Login ||
|
|
|
|
totpPromise ||
|
|
|
|
!options.cipher.login.totp ||
|
|
|
|
(!canAccessPremium && !options.cipher.organizationUseTotp)
|
|
|
|
) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-06-23 09:53:42 +02:00
|
|
|
totpPromise = this.stateService.getDisableAutoTotpCopy().then((disabled) => {
|
|
|
|
if (!disabled) {
|
2022-01-04 16:41:07 +01:00
|
|
|
return this.totpService.getCode(options.cipher.login.totp);
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
2022-01-04 16:41:07 +01:00
|
|
|
return null;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
if (didAutofill) {
|
2022-12-06 14:47:42 +01:00
|
|
|
this.eventCollectionService.collect(EventType.Cipher_ClientAutofilled, options.cipher.id);
|
2022-01-04 16:41:07 +01:00
|
|
|
if (totpPromise != null) {
|
|
|
|
return await totpPromise;
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw new Error("Did not auto-fill.");
|
|
|
|
}
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2023-03-15 02:19:16 +01:00
|
|
|
/**
|
|
|
|
* Autofills the specified tab with the next login item from the cache
|
|
|
|
* @param pageDetails The data scraped from the page
|
|
|
|
* @param tab The tab to be autofilled
|
|
|
|
* @param fromCommand Whether the autofill is triggered by a keyboard shortcut (`true`) or autofill on page load (`false`)
|
|
|
|
* @returns The TOTP code of the successfully autofilled login, if any
|
|
|
|
*/
|
|
|
|
async doAutoFillOnTab(
|
|
|
|
pageDetails: PageDetail[],
|
|
|
|
tab: chrome.tabs.Tab,
|
|
|
|
fromCommand: boolean
|
|
|
|
): Promise<string> {
|
2022-01-04 16:41:07 +01:00
|
|
|
let cipher: CipherView;
|
|
|
|
if (fromCommand) {
|
|
|
|
cipher = await this.cipherService.getNextCipherForUrl(tab.url);
|
|
|
|
} else {
|
|
|
|
const lastLaunchedCipher = await this.cipherService.getLastLaunchedForUrl(tab.url, true);
|
|
|
|
if (
|
|
|
|
lastLaunchedCipher &&
|
|
|
|
Date.now().valueOf() - lastLaunchedCipher.localData?.lastLaunched?.valueOf() < 30000
|
|
|
|
) {
|
|
|
|
cipher = lastLaunchedCipher;
|
|
|
|
} else {
|
|
|
|
cipher = await this.cipherService.getLastUsedForUrl(tab.url, true);
|
|
|
|
}
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-10-11 18:24:33 +02:00
|
|
|
if (cipher == null || cipher.reprompt !== CipherRepromptType.None) {
|
|
|
|
return null;
|
2022-01-04 16:41:07 +01:00
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
const totpCode = await this.doAutoFill({
|
2022-07-21 19:56:09 +02:00
|
|
|
tab: tab,
|
2022-01-04 16:41:07 +01:00
|
|
|
cipher: cipher,
|
|
|
|
pageDetails: pageDetails,
|
|
|
|
skipLastUsed: !fromCommand,
|
|
|
|
skipUsernameOnlyFill: !fromCommand,
|
|
|
|
onlyEmptyFields: !fromCommand,
|
|
|
|
onlyVisibleFields: !fromCommand,
|
|
|
|
fillNewPassword: fromCommand,
|
2023-03-15 02:19:16 +01:00
|
|
|
allowUntrustedIframe: fromCommand,
|
2022-01-04 16:41:07 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
// Update last used index as autofill has succeed
|
|
|
|
if (fromCommand) {
|
|
|
|
this.cipherService.updateLastUsedIndexForUrl(tab.url);
|
|
|
|
}
|
2020-05-05 22:59:20 +02:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
return totpCode;
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2023-03-15 02:19:16 +01:00
|
|
|
/**
|
|
|
|
* Autofills the active tab with the next login item from the cache
|
|
|
|
* @param pageDetails The data scraped from the page
|
|
|
|
* @param fromCommand Whether the autofill is triggered by a keyboard shortcut (`true`) or autofill on page load (`false`)
|
|
|
|
* @returns The TOTP code of the successfully autofilled login, if any
|
|
|
|
*/
|
|
|
|
async doAutoFillActiveTab(pageDetails: PageDetail[], fromCommand: boolean): Promise<string> {
|
2022-08-10 03:30:26 +02:00
|
|
|
const tab = await this.getActiveTab();
|
|
|
|
if (!tab || !tab.url) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
return await this.doAutoFillOnTab(pageDetails, tab, fromCommand);
|
|
|
|
}
|
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
// Helpers
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-08-10 03:30:26 +02:00
|
|
|
private async getActiveTab(): Promise<chrome.tabs.Tab> {
|
2022-01-04 16:41:07 +01:00
|
|
|
const tab = await BrowserApi.getTabFromCurrentWindow();
|
|
|
|
if (!tab) {
|
|
|
|
throw new Error("No tab found.");
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
return tab;
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-08-10 03:30:26 +02:00
|
|
|
private generateFillScript(
|
|
|
|
pageDetails: AutofillPageDetails,
|
|
|
|
options: GenerateFillScriptOptions
|
|
|
|
): AutofillScript {
|
2022-01-04 16:41:07 +01:00
|
|
|
if (!pageDetails || !options.cipher) {
|
|
|
|
return null;
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
let fillScript = new AutofillScript(pageDetails.documentUUID);
|
|
|
|
const filledFields: { [id: string]: AutofillField } = {};
|
|
|
|
const fields = options.cipher.fields;
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
if (fields && fields.length) {
|
|
|
|
const fieldNames: string[] = [];
|
2020-10-26 23:13:09 +01:00
|
|
|
|
2022-08-10 03:30:26 +02:00
|
|
|
fields.forEach((f) => {
|
|
|
|
if (AutofillService.hasValue(f.name)) {
|
2022-01-04 16:41:07 +01:00
|
|
|
fieldNames.push(f.name.toLowerCase());
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
2022-01-04 16:41:07 +01:00
|
|
|
});
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-08-10 03:30:26 +02:00
|
|
|
pageDetails.fields.forEach((field) => {
|
2022-02-24 18:14:04 +01:00
|
|
|
// eslint-disable-next-line
|
2022-01-04 16:41:07 +01:00
|
|
|
if (filledFields.hasOwnProperty(field.opid)) {
|
|
|
|
return;
|
2021-05-03 20:56:38 +02:00
|
|
|
}
|
2021-05-05 05:57:50 +02:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
if (!field.viewable && field.tagName !== "span") {
|
|
|
|
return;
|
2020-10-09 13:31:02 +02:00
|
|
|
}
|
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
const matchingIndex = this.findMatchingFieldIndex(field, fieldNames);
|
|
|
|
if (matchingIndex > -1) {
|
|
|
|
const matchingField: FieldView = fields[matchingIndex];
|
2022-08-10 03:30:26 +02:00
|
|
|
let val: string;
|
2022-01-04 16:41:07 +01:00
|
|
|
if (matchingField.type === FieldType.Linked) {
|
|
|
|
// Assumption: Linked Field is not being used to autofill a boolean value
|
2022-08-10 03:30:26 +02:00
|
|
|
val = options.cipher.linkedFieldValue(matchingField.linkedId) as string;
|
2022-01-04 16:41:07 +01:00
|
|
|
} else {
|
|
|
|
val = matchingField.value;
|
|
|
|
if (val == null && matchingField.type === FieldType.Boolean) {
|
|
|
|
val = "false";
|
|
|
|
}
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
filledFields[field.opid] = field;
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.fillByOpid(fillScript, field, val);
|
2018-01-12 20:44:44 +01:00
|
|
|
}
|
2022-01-04 16:41:07 +01:00
|
|
|
});
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
switch (options.cipher.type) {
|
|
|
|
case CipherType.Login:
|
|
|
|
fillScript = this.generateLoginFillScript(fillScript, pageDetails, filledFields, options);
|
|
|
|
break;
|
|
|
|
case CipherType.Card:
|
|
|
|
fillScript = this.generateCardFillScript(fillScript, pageDetails, filledFields, options);
|
|
|
|
break;
|
|
|
|
case CipherType.Identity:
|
|
|
|
fillScript = this.generateIdentityFillScript(
|
|
|
|
fillScript,
|
|
|
|
pageDetails,
|
|
|
|
filledFields,
|
|
|
|
options
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return null;
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
return fillScript;
|
|
|
|
}
|
|
|
|
|
|
|
|
private generateLoginFillScript(
|
|
|
|
fillScript: AutofillScript,
|
2022-08-10 03:30:26 +02:00
|
|
|
pageDetails: AutofillPageDetails,
|
2022-01-04 16:41:07 +01:00
|
|
|
filledFields: { [id: string]: AutofillField },
|
2022-08-10 03:30:26 +02:00
|
|
|
options: GenerateFillScriptOptions
|
2022-01-04 16:41:07 +01:00
|
|
|
): AutofillScript {
|
|
|
|
if (!options.cipher.login) {
|
|
|
|
return null;
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
const passwords: AutofillField[] = [];
|
|
|
|
const usernames: AutofillField[] = [];
|
|
|
|
let pf: AutofillField = null;
|
|
|
|
let username: AutofillField = null;
|
|
|
|
const login = options.cipher.login;
|
2022-11-04 16:44:21 +01:00
|
|
|
fillScript.savedUrls =
|
|
|
|
login?.uris?.filter((u) => u.match != UriMatchType.Never).map((u) => u.uri) ?? [];
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2023-04-06 05:30:26 +02:00
|
|
|
fillScript.untrustedIframe = this.inUntrustedIframe(pageDetails.url, options);
|
2023-03-15 02:19:16 +01:00
|
|
|
|
2022-08-10 03:30:26 +02:00
|
|
|
let passwordFields = AutofillService.loadPasswordFields(
|
2022-01-04 16:41:07 +01:00
|
|
|
pageDetails,
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
options.onlyEmptyFields,
|
|
|
|
options.fillNewPassword
|
|
|
|
);
|
|
|
|
if (!passwordFields.length && !options.onlyVisibleFields) {
|
|
|
|
// not able to find any viewable password fields. maybe there are some "hidden" ones?
|
2022-08-10 03:30:26 +02:00
|
|
|
passwordFields = AutofillService.loadPasswordFields(
|
2022-01-04 16:41:07 +01:00
|
|
|
pageDetails,
|
|
|
|
true,
|
|
|
|
true,
|
|
|
|
options.onlyEmptyFields,
|
|
|
|
options.fillNewPassword
|
|
|
|
);
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
for (const formKey in pageDetails.forms) {
|
2022-02-24 18:14:04 +01:00
|
|
|
// eslint-disable-next-line
|
2022-01-04 16:41:07 +01:00
|
|
|
if (!pageDetails.forms.hasOwnProperty(formKey)) {
|
|
|
|
continue;
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
const passwordFieldsForForm: AutofillField[] = [];
|
|
|
|
passwordFields.forEach((passField) => {
|
|
|
|
if (formKey === passField.form) {
|
|
|
|
passwordFieldsForForm.push(passField);
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
2022-01-04 16:41:07 +01:00
|
|
|
});
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
passwordFields.forEach((passField) => {
|
|
|
|
pf = passField;
|
|
|
|
passwords.push(pf);
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
if (login.username) {
|
|
|
|
username = this.findUsernameField(pageDetails, pf, false, false, false);
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
if (!username && !options.onlyVisibleFields) {
|
|
|
|
// not able to find any viewable username fields. maybe there are some "hidden" ones?
|
|
|
|
username = this.findUsernameField(pageDetails, pf, true, true, false);
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
if (username) {
|
|
|
|
usernames.push(username);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
if (passwordFields.length && !passwords.length) {
|
|
|
|
// The page does not have any forms with password fields. Use the first password field on the page and the
|
|
|
|
// input field just before it as the username.
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
pf = passwordFields[0];
|
|
|
|
passwords.push(pf);
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
if (login.username && pf.elementNumber > 0) {
|
|
|
|
username = this.findUsernameField(pageDetails, pf, false, false, true);
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
if (!username && !options.onlyVisibleFields) {
|
|
|
|
// not able to find any viewable username fields. maybe there are some "hidden" ones?
|
|
|
|
username = this.findUsernameField(pageDetails, pf, true, true, true);
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
if (username) {
|
|
|
|
usernames.push(username);
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
2022-01-04 16:41:07 +01:00
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
if (!passwordFields.length && !options.skipUsernameOnlyFill) {
|
|
|
|
// No password fields on this page. Let's try to just fuzzy fill the username.
|
2022-08-10 03:30:26 +02:00
|
|
|
pageDetails.fields.forEach((f) => {
|
2022-01-04 16:41:07 +01:00
|
|
|
if (
|
|
|
|
f.viewable &&
|
|
|
|
(f.type === "text" || f.type === "email" || f.type === "tel") &&
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.fieldIsFuzzyMatch(f, AutoFillConstants.UsernameFieldNames)
|
2022-01-04 16:41:07 +01:00
|
|
|
) {
|
|
|
|
usernames.push(f);
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
2022-01-04 16:41:07 +01:00
|
|
|
});
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
usernames.forEach((u) => {
|
2022-02-24 18:14:04 +01:00
|
|
|
// eslint-disable-next-line
|
2022-01-04 16:41:07 +01:00
|
|
|
if (filledFields.hasOwnProperty(u.opid)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
filledFields[u.opid] = u;
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.fillByOpid(fillScript, u, login.username);
|
2022-01-04 16:41:07 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
passwords.forEach((p) => {
|
2022-02-24 18:14:04 +01:00
|
|
|
// eslint-disable-next-line
|
2022-01-04 16:41:07 +01:00
|
|
|
if (filledFields.hasOwnProperty(p.opid)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
filledFields[p.opid] = p;
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.fillByOpid(fillScript, p, login.password);
|
2022-01-04 16:41:07 +01:00
|
|
|
});
|
|
|
|
|
2022-08-10 03:30:26 +02:00
|
|
|
fillScript = AutofillService.setFillScriptForFocus(filledFields, fillScript);
|
2022-01-04 16:41:07 +01:00
|
|
|
return fillScript;
|
|
|
|
}
|
|
|
|
|
|
|
|
private generateCardFillScript(
|
|
|
|
fillScript: AutofillScript,
|
2022-08-10 03:30:26 +02:00
|
|
|
pageDetails: AutofillPageDetails,
|
2022-01-04 16:41:07 +01:00
|
|
|
filledFields: { [id: string]: AutofillField },
|
2022-08-10 03:30:26 +02:00
|
|
|
options: GenerateFillScriptOptions
|
2022-01-04 16:41:07 +01:00
|
|
|
): AutofillScript {
|
|
|
|
if (!options.cipher.card) {
|
|
|
|
return null;
|
|
|
|
}
|
2021-09-30 22:02:13 +02:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
const fillFields: { [id: string]: AutofillField } = {};
|
2018-03-22 17:28:24 +01:00
|
|
|
|
2022-08-10 03:30:26 +02:00
|
|
|
pageDetails.fields.forEach((f) => {
|
|
|
|
if (AutofillService.forCustomFieldsOnly(f)) {
|
2022-01-04 16:41:07 +01:00
|
|
|
return;
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
if (this.isExcludedType(f.type, AutoFillConstants.ExcludedAutofillTypes)) {
|
|
|
|
return;
|
|
|
|
}
|
2017-11-24 21:49:13 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
for (let i = 0; i < CreditCardAutoFillConstants.CardAttributes.length; i++) {
|
|
|
|
const attr = CreditCardAutoFillConstants.CardAttributes[i];
|
2022-02-24 18:14:04 +01:00
|
|
|
// eslint-disable-next-line
|
2022-01-04 16:41:07 +01:00
|
|
|
if (!f.hasOwnProperty(attr) || !f[attr] || !f.viewable) {
|
|
|
|
continue;
|
2017-11-24 21:49:13 +01:00
|
|
|
}
|
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
// ref https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill
|
|
|
|
// ref https://developers.google.com/web/fundamentals/design-and-ux/input/forms/
|
|
|
|
if (
|
|
|
|
!fillFields.cardholderName &&
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.isFieldMatch(
|
2022-01-04 16:41:07 +01:00
|
|
|
f[attr],
|
|
|
|
CreditCardAutoFillConstants.CardHolderFieldNames,
|
|
|
|
CreditCardAutoFillConstants.CardHolderFieldNameValues
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
fillFields.cardholderName = f;
|
|
|
|
break;
|
|
|
|
} else if (
|
|
|
|
!fillFields.number &&
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.isFieldMatch(
|
2022-01-04 16:41:07 +01:00
|
|
|
f[attr],
|
|
|
|
CreditCardAutoFillConstants.CardNumberFieldNames,
|
|
|
|
CreditCardAutoFillConstants.CardNumberFieldNameValues
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
fillFields.number = f;
|
|
|
|
break;
|
|
|
|
} else if (
|
|
|
|
!fillFields.exp &&
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.isFieldMatch(
|
2022-01-04 16:41:07 +01:00
|
|
|
f[attr],
|
|
|
|
CreditCardAutoFillConstants.CardExpiryFieldNames,
|
|
|
|
CreditCardAutoFillConstants.CardExpiryFieldNameValues
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
fillFields.exp = f;
|
|
|
|
break;
|
|
|
|
} else if (
|
|
|
|
!fillFields.expMonth &&
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.isFieldMatch(f[attr], CreditCardAutoFillConstants.ExpiryMonthFieldNames)
|
2022-01-04 16:41:07 +01:00
|
|
|
) {
|
|
|
|
fillFields.expMonth = f;
|
|
|
|
break;
|
|
|
|
} else if (
|
|
|
|
!fillFields.expYear &&
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.isFieldMatch(f[attr], CreditCardAutoFillConstants.ExpiryYearFieldNames)
|
2022-01-04 16:41:07 +01:00
|
|
|
) {
|
|
|
|
fillFields.expYear = f;
|
|
|
|
break;
|
|
|
|
} else if (
|
|
|
|
!fillFields.code &&
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.isFieldMatch(f[attr], CreditCardAutoFillConstants.CVVFieldNames)
|
2022-01-04 16:41:07 +01:00
|
|
|
) {
|
|
|
|
fillFields.code = f;
|
|
|
|
break;
|
|
|
|
} else if (
|
|
|
|
!fillFields.brand &&
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.isFieldMatch(f[attr], CreditCardAutoFillConstants.CardBrandFieldNames)
|
2022-01-04 16:41:07 +01:00
|
|
|
) {
|
|
|
|
fillFields.brand = f;
|
|
|
|
break;
|
2019-01-14 19:37:00 +01:00
|
|
|
}
|
2022-01-04 16:41:07 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const card = options.cipher.card;
|
|
|
|
this.makeScriptAction(fillScript, card, fillFields, filledFields, "cardholderName");
|
|
|
|
this.makeScriptAction(fillScript, card, fillFields, filledFields, "number");
|
|
|
|
this.makeScriptAction(fillScript, card, fillFields, filledFields, "code");
|
|
|
|
this.makeScriptAction(fillScript, card, fillFields, filledFields, "brand");
|
|
|
|
|
2022-08-10 03:30:26 +02:00
|
|
|
if (fillFields.expMonth && AutofillService.hasValue(card.expMonth)) {
|
2022-01-04 16:41:07 +01:00
|
|
|
let expMonth: string = card.expMonth;
|
|
|
|
|
|
|
|
if (fillFields.expMonth.selectInfo && fillFields.expMonth.selectInfo.options) {
|
|
|
|
let index: number = null;
|
|
|
|
const siOptions = fillFields.expMonth.selectInfo.options;
|
|
|
|
if (siOptions.length === 12) {
|
|
|
|
index = parseInt(card.expMonth, null) - 1;
|
|
|
|
} else if (siOptions.length === 13) {
|
|
|
|
if (
|
|
|
|
siOptions[0][0] != null &&
|
|
|
|
siOptions[0][0] !== "" &&
|
|
|
|
(siOptions[12][0] == null || siOptions[12][0] === "")
|
|
|
|
) {
|
|
|
|
index = parseInt(card.expMonth, null) - 1;
|
|
|
|
} else {
|
|
|
|
index = parseInt(card.expMonth, null);
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
if (index != null) {
|
|
|
|
const option = siOptions[index];
|
|
|
|
if (option.length > 1) {
|
|
|
|
expMonth = option[1];
|
|
|
|
}
|
2018-03-16 02:16:40 +01:00
|
|
|
}
|
2022-01-04 16:41:07 +01:00
|
|
|
} else if (
|
|
|
|
(this.fieldAttrsContain(fillFields.expMonth, "mm") ||
|
|
|
|
fillFields.expMonth.maxLength === 2) &&
|
|
|
|
expMonth.length === 1
|
|
|
|
) {
|
|
|
|
expMonth = "0" + expMonth;
|
|
|
|
}
|
|
|
|
|
|
|
|
filledFields[fillFields.expMonth.opid] = fillFields.expMonth;
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.fillByOpid(fillScript, fillFields.expMonth, expMonth);
|
2022-01-04 16:41:07 +01:00
|
|
|
}
|
2018-03-16 02:16:40 +01:00
|
|
|
|
2022-08-10 03:30:26 +02:00
|
|
|
if (fillFields.expYear && AutofillService.hasValue(card.expYear)) {
|
2022-01-04 16:41:07 +01:00
|
|
|
let expYear: string = card.expYear;
|
|
|
|
if (fillFields.expYear.selectInfo && fillFields.expYear.selectInfo.options) {
|
|
|
|
for (let i = 0; i < fillFields.expYear.selectInfo.options.length; i++) {
|
|
|
|
const o: [string, string] = fillFields.expYear.selectInfo.options[i];
|
|
|
|
if (o[0] === card.expYear || o[1] === card.expYear) {
|
|
|
|
expYear = o[1];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
o[1].length === 2 &&
|
|
|
|
card.expYear.length === 4 &&
|
|
|
|
o[1] === card.expYear.substring(2)
|
|
|
|
) {
|
|
|
|
expYear = o[1];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
const colonIndex = o[1].indexOf(":");
|
|
|
|
if (colonIndex > -1 && o[1].length > colonIndex + 1) {
|
|
|
|
const val = o[1].substring(colonIndex + 2);
|
|
|
|
if (val != null && val.trim() !== "" && val === card.expYear) {
|
|
|
|
expYear = o[1];
|
|
|
|
break;
|
2018-03-16 02:16:40 +01:00
|
|
|
}
|
2022-01-04 16:41:07 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (
|
|
|
|
this.fieldAttrsContain(fillFields.expYear, "yyyy") ||
|
|
|
|
fillFields.expYear.maxLength === 4
|
|
|
|
) {
|
|
|
|
if (expYear.length === 2) {
|
|
|
|
expYear = "20" + expYear;
|
|
|
|
}
|
|
|
|
} else if (
|
|
|
|
this.fieldAttrsContain(fillFields.expYear, "yy") ||
|
|
|
|
fillFields.expYear.maxLength === 2
|
|
|
|
) {
|
|
|
|
if (expYear.length === 4) {
|
|
|
|
expYear = expYear.substr(2);
|
|
|
|
}
|
|
|
|
}
|
2018-03-16 02:16:40 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
filledFields[fillFields.expYear.opid] = fillFields.expYear;
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.fillByOpid(fillScript, fillFields.expYear, expYear);
|
2018-03-16 02:16:40 +01:00
|
|
|
}
|
|
|
|
|
2022-08-10 03:30:26 +02:00
|
|
|
if (
|
|
|
|
fillFields.exp &&
|
|
|
|
AutofillService.hasValue(card.expMonth) &&
|
|
|
|
AutofillService.hasValue(card.expYear)
|
|
|
|
) {
|
2022-01-04 16:41:07 +01:00
|
|
|
const fullMonth = ("0" + card.expMonth).slice(-2);
|
|
|
|
|
|
|
|
let fullYear: string = card.expYear;
|
|
|
|
let partYear: string = null;
|
|
|
|
if (fullYear.length === 2) {
|
|
|
|
partYear = fullYear;
|
|
|
|
fullYear = "20" + fullYear;
|
|
|
|
} else if (fullYear.length === 4) {
|
|
|
|
partYear = fullYear.substr(2, 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
let exp: string = null;
|
|
|
|
for (let i = 0; i < CreditCardAutoFillConstants.MonthAbbr.length; i++) {
|
|
|
|
if (
|
|
|
|
this.fieldAttrsContain(
|
|
|
|
fillFields.exp,
|
|
|
|
CreditCardAutoFillConstants.MonthAbbr[i] +
|
|
|
|
"/" +
|
|
|
|
CreditCardAutoFillConstants.YearAbbrShort[i]
|
|
|
|
) &&
|
|
|
|
partYear != null
|
|
|
|
) {
|
|
|
|
exp = fullMonth + "/" + partYear;
|
|
|
|
} else if (
|
|
|
|
this.fieldAttrsContain(
|
|
|
|
fillFields.exp,
|
|
|
|
CreditCardAutoFillConstants.MonthAbbr[i] +
|
|
|
|
"/" +
|
|
|
|
CreditCardAutoFillConstants.YearAbbrLong[i]
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
exp = fullMonth + "/" + fullYear;
|
|
|
|
} else if (
|
|
|
|
this.fieldAttrsContain(
|
|
|
|
fillFields.exp,
|
|
|
|
CreditCardAutoFillConstants.YearAbbrShort[i] +
|
|
|
|
"/" +
|
|
|
|
CreditCardAutoFillConstants.MonthAbbr[i]
|
|
|
|
) &&
|
|
|
|
partYear != null
|
|
|
|
) {
|
|
|
|
exp = partYear + "/" + fullMonth;
|
|
|
|
} else if (
|
|
|
|
this.fieldAttrsContain(
|
|
|
|
fillFields.exp,
|
|
|
|
CreditCardAutoFillConstants.YearAbbrLong[i] +
|
|
|
|
"/" +
|
|
|
|
CreditCardAutoFillConstants.MonthAbbr[i]
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
exp = fullYear + "/" + fullMonth;
|
|
|
|
} else if (
|
|
|
|
this.fieldAttrsContain(
|
|
|
|
fillFields.exp,
|
|
|
|
CreditCardAutoFillConstants.MonthAbbr[i] +
|
|
|
|
"-" +
|
|
|
|
CreditCardAutoFillConstants.YearAbbrShort[i]
|
|
|
|
) &&
|
|
|
|
partYear != null
|
|
|
|
) {
|
|
|
|
exp = fullMonth + "-" + partYear;
|
|
|
|
} else if (
|
|
|
|
this.fieldAttrsContain(
|
|
|
|
fillFields.exp,
|
|
|
|
CreditCardAutoFillConstants.MonthAbbr[i] +
|
|
|
|
"-" +
|
|
|
|
CreditCardAutoFillConstants.YearAbbrLong[i]
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
exp = fullMonth + "-" + fullYear;
|
|
|
|
} else if (
|
|
|
|
this.fieldAttrsContain(
|
|
|
|
fillFields.exp,
|
|
|
|
CreditCardAutoFillConstants.YearAbbrShort[i] +
|
|
|
|
"-" +
|
|
|
|
CreditCardAutoFillConstants.MonthAbbr[i]
|
|
|
|
) &&
|
|
|
|
partYear != null
|
|
|
|
) {
|
|
|
|
exp = partYear + "-" + fullMonth;
|
|
|
|
} else if (
|
|
|
|
this.fieldAttrsContain(
|
|
|
|
fillFields.exp,
|
|
|
|
CreditCardAutoFillConstants.YearAbbrLong[i] +
|
|
|
|
"-" +
|
|
|
|
CreditCardAutoFillConstants.MonthAbbr[i]
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
exp = fullYear + "-" + fullMonth;
|
|
|
|
} else if (
|
|
|
|
this.fieldAttrsContain(
|
|
|
|
fillFields.exp,
|
|
|
|
CreditCardAutoFillConstants.YearAbbrShort[i] + CreditCardAutoFillConstants.MonthAbbr[i]
|
|
|
|
) &&
|
|
|
|
partYear != null
|
|
|
|
) {
|
|
|
|
exp = partYear + fullMonth;
|
|
|
|
} else if (
|
|
|
|
this.fieldAttrsContain(
|
|
|
|
fillFields.exp,
|
|
|
|
CreditCardAutoFillConstants.YearAbbrLong[i] + CreditCardAutoFillConstants.MonthAbbr[i]
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
exp = fullYear + fullMonth;
|
|
|
|
} else if (
|
|
|
|
this.fieldAttrsContain(
|
|
|
|
fillFields.exp,
|
|
|
|
CreditCardAutoFillConstants.MonthAbbr[i] + CreditCardAutoFillConstants.YearAbbrShort[i]
|
|
|
|
) &&
|
|
|
|
partYear != null
|
|
|
|
) {
|
|
|
|
exp = fullMonth + partYear;
|
|
|
|
} else if (
|
|
|
|
this.fieldAttrsContain(
|
|
|
|
fillFields.exp,
|
|
|
|
CreditCardAutoFillConstants.MonthAbbr[i] + CreditCardAutoFillConstants.YearAbbrLong[i]
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
exp = fullMonth + fullYear;
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
if (exp != null) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
if (exp == null) {
|
|
|
|
exp = fullYear + "-" + fullMonth;
|
|
|
|
}
|
2021-09-30 22:02:13 +02:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
this.makeScriptActionWithValue(fillScript, exp, fillFields.exp, filledFields);
|
|
|
|
}
|
2018-03-22 17:28:24 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
return fillScript;
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2023-03-15 02:19:16 +01:00
|
|
|
/**
|
2023-04-06 05:30:26 +02:00
|
|
|
* Determines whether an iframe is potentially dangerous ("untrusted") to autofill
|
2023-03-15 02:19:16 +01:00
|
|
|
* @param pageUrl The url of the page/iframe, usually from AutofillPageDetails
|
2023-04-06 05:30:26 +02:00
|
|
|
* @param options The GenerateFillScript options
|
|
|
|
* @returns `true` if the iframe is untrusted and a warning should be shown, `false` otherwise
|
2023-03-15 02:19:16 +01:00
|
|
|
*/
|
2023-04-06 05:30:26 +02:00
|
|
|
private inUntrustedIframe(pageUrl: string, options: GenerateFillScriptOptions): boolean {
|
|
|
|
// If the pageUrl (from the content script) matches the tabUrl (from the sender tab), we are not in an iframe
|
|
|
|
// This also avoids a false positive if no URI is saved and the user triggers auto-fill anyway
|
|
|
|
if (pageUrl === options.tabUrl) {
|
|
|
|
return false;
|
2023-03-15 02:19:16 +01:00
|
|
|
}
|
|
|
|
|
2023-04-06 05:30:26 +02:00
|
|
|
// Check the pageUrl against cipher URIs using the configured match detection.
|
|
|
|
// Remember: if we are in this function, the tabUrl already matches a saved URI for the login.
|
|
|
|
// We need to verify the pageUrl also matches.
|
|
|
|
const equivalentDomains = this.settingsService.getEquivalentDomains(pageUrl);
|
|
|
|
const matchesUri = options.cipher.login.matchesUri(
|
|
|
|
pageUrl,
|
|
|
|
equivalentDomains,
|
|
|
|
options.defaultUriMatch
|
|
|
|
);
|
|
|
|
return !matchesUri;
|
2023-03-15 02:19:16 +01:00
|
|
|
}
|
|
|
|
|
2022-08-10 03:30:26 +02:00
|
|
|
private fieldAttrsContain(field: AutofillField, containsVal: string) {
|
2022-01-04 16:41:07 +01:00
|
|
|
if (!field) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
let doesContain = false;
|
|
|
|
CreditCardAutoFillConstants.CardAttributesExtended.forEach((attr) => {
|
2022-02-24 18:14:04 +01:00
|
|
|
// eslint-disable-next-line
|
2022-01-04 16:41:07 +01:00
|
|
|
if (doesContain || !field.hasOwnProperty(attr) || !field[attr]) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let val = field[attr];
|
|
|
|
val = val.replace(/ /g, "").toLowerCase();
|
|
|
|
doesContain = val.indexOf(containsVal) > -1;
|
|
|
|
});
|
|
|
|
|
|
|
|
return doesContain;
|
|
|
|
}
|
|
|
|
|
|
|
|
private generateIdentityFillScript(
|
|
|
|
fillScript: AutofillScript,
|
2022-08-10 03:30:26 +02:00
|
|
|
pageDetails: AutofillPageDetails,
|
2022-01-04 16:41:07 +01:00
|
|
|
filledFields: { [id: string]: AutofillField },
|
2022-08-10 03:30:26 +02:00
|
|
|
options: GenerateFillScriptOptions
|
2022-01-04 16:41:07 +01:00
|
|
|
): AutofillScript {
|
|
|
|
if (!options.cipher.identity) {
|
|
|
|
return null;
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
const fillFields: { [id: string]: AutofillField } = {};
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-08-10 03:30:26 +02:00
|
|
|
pageDetails.fields.forEach((f) => {
|
|
|
|
if (AutofillService.forCustomFieldsOnly(f)) {
|
2022-01-04 16:41:07 +01:00
|
|
|
return;
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
if (this.isExcludedType(f.type, AutoFillConstants.ExcludedAutofillTypes)) {
|
|
|
|
return;
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
for (let i = 0; i < IdentityAutoFillConstants.IdentityAttributes.length; i++) {
|
|
|
|
const attr = IdentityAutoFillConstants.IdentityAttributes[i];
|
2022-02-24 18:14:04 +01:00
|
|
|
// eslint-disable-next-line
|
2022-01-04 16:41:07 +01:00
|
|
|
if (!f.hasOwnProperty(attr) || !f[attr] || !f.viewable) {
|
|
|
|
continue;
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
// ref https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill
|
|
|
|
// ref https://developers.google.com/web/fundamentals/design-and-ux/input/forms/
|
|
|
|
if (
|
|
|
|
!fillFields.name &&
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.isFieldMatch(
|
2022-01-04 16:41:07 +01:00
|
|
|
f[attr],
|
|
|
|
IdentityAutoFillConstants.FullNameFieldNames,
|
|
|
|
IdentityAutoFillConstants.FullNameFieldNameValues
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
fillFields.name = f;
|
|
|
|
break;
|
|
|
|
} else if (
|
|
|
|
!fillFields.firstName &&
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.FirstnameFieldNames)
|
2022-01-04 16:41:07 +01:00
|
|
|
) {
|
|
|
|
fillFields.firstName = f;
|
|
|
|
break;
|
|
|
|
} else if (
|
|
|
|
!fillFields.middleName &&
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.MiddlenameFieldNames)
|
2022-01-04 16:41:07 +01:00
|
|
|
) {
|
|
|
|
fillFields.middleName = f;
|
|
|
|
break;
|
|
|
|
} else if (
|
|
|
|
!fillFields.lastName &&
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.LastnameFieldNames)
|
2022-01-04 16:41:07 +01:00
|
|
|
) {
|
|
|
|
fillFields.lastName = f;
|
|
|
|
break;
|
|
|
|
} else if (
|
|
|
|
!fillFields.title &&
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.TitleFieldNames)
|
2022-01-04 16:41:07 +01:00
|
|
|
) {
|
|
|
|
fillFields.title = f;
|
|
|
|
break;
|
|
|
|
} else if (
|
|
|
|
!fillFields.email &&
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.EmailFieldNames)
|
2022-01-04 16:41:07 +01:00
|
|
|
) {
|
|
|
|
fillFields.email = f;
|
|
|
|
break;
|
|
|
|
} else if (
|
|
|
|
!fillFields.address &&
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.isFieldMatch(
|
2022-01-04 16:41:07 +01:00
|
|
|
f[attr],
|
|
|
|
IdentityAutoFillConstants.AddressFieldNames,
|
|
|
|
IdentityAutoFillConstants.AddressFieldNameValues
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
fillFields.address = f;
|
|
|
|
break;
|
|
|
|
} else if (
|
|
|
|
!fillFields.address1 &&
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address1FieldNames)
|
2022-01-04 16:41:07 +01:00
|
|
|
) {
|
|
|
|
fillFields.address1 = f;
|
|
|
|
break;
|
|
|
|
} else if (
|
|
|
|
!fillFields.address2 &&
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address2FieldNames)
|
2022-01-04 16:41:07 +01:00
|
|
|
) {
|
|
|
|
fillFields.address2 = f;
|
|
|
|
break;
|
|
|
|
} else if (
|
|
|
|
!fillFields.address3 &&
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address3FieldNames)
|
2022-01-04 16:41:07 +01:00
|
|
|
) {
|
|
|
|
fillFields.address3 = f;
|
|
|
|
break;
|
|
|
|
} else if (
|
|
|
|
!fillFields.postalCode &&
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.PostalCodeFieldNames)
|
2022-01-04 16:41:07 +01:00
|
|
|
) {
|
|
|
|
fillFields.postalCode = f;
|
|
|
|
break;
|
|
|
|
} else if (
|
|
|
|
!fillFields.city &&
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CityFieldNames)
|
2022-01-04 16:41:07 +01:00
|
|
|
) {
|
|
|
|
fillFields.city = f;
|
|
|
|
break;
|
|
|
|
} else if (
|
|
|
|
!fillFields.state &&
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.StateFieldNames)
|
2022-01-04 16:41:07 +01:00
|
|
|
) {
|
|
|
|
fillFields.state = f;
|
|
|
|
break;
|
|
|
|
} else if (
|
|
|
|
!fillFields.country &&
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CountryFieldNames)
|
2022-01-04 16:41:07 +01:00
|
|
|
) {
|
|
|
|
fillFields.country = f;
|
|
|
|
break;
|
|
|
|
} else if (
|
|
|
|
!fillFields.phone &&
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.PhoneFieldNames)
|
2022-01-04 16:41:07 +01:00
|
|
|
) {
|
|
|
|
fillFields.phone = f;
|
|
|
|
break;
|
|
|
|
} else if (
|
|
|
|
!fillFields.username &&
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.UserNameFieldNames)
|
2022-01-04 16:41:07 +01:00
|
|
|
) {
|
|
|
|
fillFields.username = f;
|
|
|
|
break;
|
|
|
|
} else if (
|
|
|
|
!fillFields.company &&
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CompanyFieldNames)
|
2022-01-04 16:41:07 +01:00
|
|
|
) {
|
|
|
|
fillFields.company = f;
|
|
|
|
break;
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
2022-01-04 16:41:07 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const identity = options.cipher.identity;
|
|
|
|
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "title");
|
|
|
|
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "firstName");
|
|
|
|
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "middleName");
|
|
|
|
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "lastName");
|
|
|
|
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "address1");
|
|
|
|
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "address2");
|
|
|
|
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "address3");
|
|
|
|
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "city");
|
|
|
|
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "postalCode");
|
|
|
|
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "company");
|
|
|
|
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "email");
|
|
|
|
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "phone");
|
|
|
|
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "username");
|
|
|
|
|
|
|
|
let filledState = false;
|
|
|
|
if (fillFields.state && identity.state && identity.state.length > 2) {
|
|
|
|
const stateLower = identity.state.toLowerCase();
|
|
|
|
const isoState =
|
|
|
|
IdentityAutoFillConstants.IsoStates[stateLower] ||
|
|
|
|
IdentityAutoFillConstants.IsoProvinces[stateLower];
|
|
|
|
if (isoState) {
|
|
|
|
filledState = true;
|
|
|
|
this.makeScriptActionWithValue(fillScript, isoState, fillFields.state, filledFields);
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
if (!filledState) {
|
|
|
|
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "state");
|
2018-03-22 17:28:24 +01:00
|
|
|
}
|
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
let filledCountry = false;
|
|
|
|
if (fillFields.country && identity.country && identity.country.length > 2) {
|
|
|
|
const countryLower = identity.country.toLowerCase();
|
|
|
|
const isoCountry = IdentityAutoFillConstants.IsoCountries[countryLower];
|
|
|
|
if (isoCountry) {
|
|
|
|
filledCountry = true;
|
|
|
|
this.makeScriptActionWithValue(fillScript, isoCountry, fillFields.country, filledFields);
|
|
|
|
}
|
2017-11-11 05:47:42 +01:00
|
|
|
}
|
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
if (!filledCountry) {
|
|
|
|
this.makeScriptAction(fillScript, identity, fillFields, filledFields, "country");
|
2017-12-06 15:11:37 +01:00
|
|
|
}
|
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
if (fillFields.name && (identity.firstName || identity.lastName)) {
|
|
|
|
let fullName = "";
|
2022-08-10 03:30:26 +02:00
|
|
|
if (AutofillService.hasValue(identity.firstName)) {
|
2022-01-04 16:41:07 +01:00
|
|
|
fullName = identity.firstName;
|
|
|
|
}
|
2022-08-10 03:30:26 +02:00
|
|
|
if (AutofillService.hasValue(identity.middleName)) {
|
2022-01-04 16:41:07 +01:00
|
|
|
if (fullName !== "") {
|
|
|
|
fullName += " ";
|
2017-12-06 15:11:37 +01:00
|
|
|
}
|
2022-01-04 16:41:07 +01:00
|
|
|
fullName += identity.middleName;
|
|
|
|
}
|
2022-08-10 03:30:26 +02:00
|
|
|
if (AutofillService.hasValue(identity.lastName)) {
|
2022-01-04 16:41:07 +01:00
|
|
|
if (fullName !== "") {
|
|
|
|
fullName += " ";
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
2022-01-04 16:41:07 +01:00
|
|
|
fullName += identity.lastName;
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
this.makeScriptActionWithValue(fillScript, fullName, fillFields.name, filledFields);
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
|
|
|
|
2022-08-10 03:30:26 +02:00
|
|
|
if (fillFields.address && AutofillService.hasValue(identity.address1)) {
|
2022-01-04 16:41:07 +01:00
|
|
|
let address = "";
|
2022-08-10 03:30:26 +02:00
|
|
|
if (AutofillService.hasValue(identity.address1)) {
|
2022-01-04 16:41:07 +01:00
|
|
|
address = identity.address1;
|
|
|
|
}
|
2022-08-10 03:30:26 +02:00
|
|
|
if (AutofillService.hasValue(identity.address2)) {
|
2022-01-04 16:41:07 +01:00
|
|
|
if (address !== "") {
|
|
|
|
address += ", ";
|
|
|
|
}
|
|
|
|
address += identity.address2;
|
|
|
|
}
|
2022-08-10 03:30:26 +02:00
|
|
|
if (AutofillService.hasValue(identity.address3)) {
|
2022-01-04 16:41:07 +01:00
|
|
|
if (address !== "") {
|
|
|
|
address += ", ";
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
2022-01-04 16:41:07 +01:00
|
|
|
address += identity.address3;
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
this.makeScriptActionWithValue(fillScript, address, fillFields.address, filledFields);
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
return fillScript;
|
|
|
|
}
|
|
|
|
|
|
|
|
private isExcludedType(type: string, excludedTypes: string[]) {
|
|
|
|
return excludedTypes.indexOf(type) > -1;
|
|
|
|
}
|
|
|
|
|
2022-08-10 03:30:26 +02:00
|
|
|
private static isFieldMatch(
|
|
|
|
value: string,
|
|
|
|
options: string[],
|
|
|
|
containsOptions?: string[]
|
|
|
|
): boolean {
|
2022-01-04 16:41:07 +01:00
|
|
|
value = value
|
|
|
|
.trim()
|
|
|
|
.toLowerCase()
|
|
|
|
.replace(/[^a-zA-Z0-9]+/g, "");
|
|
|
|
for (let i = 0; i < options.length; i++) {
|
|
|
|
let option = options[i];
|
|
|
|
const checkValueContains = containsOptions == null || containsOptions.indexOf(option) > -1;
|
|
|
|
option = option.toLowerCase().replace(/-/g, "");
|
|
|
|
if (value === option || (checkValueContains && value.indexOf(option) > -1)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2019-01-20 05:02:07 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
private makeScriptAction(
|
|
|
|
fillScript: AutofillScript,
|
|
|
|
cipherData: any,
|
|
|
|
fillFields: { [id: string]: AutofillField },
|
|
|
|
filledFields: { [id: string]: AutofillField },
|
|
|
|
dataProp: string,
|
|
|
|
fieldProp?: string
|
|
|
|
) {
|
|
|
|
fieldProp = fieldProp || dataProp;
|
|
|
|
this.makeScriptActionWithValue(
|
|
|
|
fillScript,
|
|
|
|
cipherData[dataProp],
|
|
|
|
fillFields[fieldProp],
|
|
|
|
filledFields
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
private makeScriptActionWithValue(
|
|
|
|
fillScript: AutofillScript,
|
|
|
|
dataValue: any,
|
|
|
|
field: AutofillField,
|
|
|
|
filledFields: { [id: string]: AutofillField }
|
|
|
|
) {
|
|
|
|
let doFill = false;
|
2022-08-10 03:30:26 +02:00
|
|
|
if (AutofillService.hasValue(dataValue) && field) {
|
2022-01-04 16:41:07 +01:00
|
|
|
if (field.type === "select-one" && field.selectInfo && field.selectInfo.options) {
|
|
|
|
for (let i = 0; i < field.selectInfo.options.length; i++) {
|
|
|
|
const option = field.selectInfo.options[i];
|
|
|
|
for (let j = 0; j < option.length; j++) {
|
2022-08-10 03:30:26 +02:00
|
|
|
if (
|
|
|
|
AutofillService.hasValue(option[j]) &&
|
|
|
|
option[j].toLowerCase() === dataValue.toLowerCase()
|
|
|
|
) {
|
2022-01-04 16:41:07 +01:00
|
|
|
doFill = true;
|
|
|
|
if (option.length > 1) {
|
|
|
|
dataValue = option[1];
|
|
|
|
}
|
|
|
|
break;
|
2017-11-13 19:07:59 +01:00
|
|
|
}
|
2022-01-04 16:41:07 +01:00
|
|
|
}
|
2017-11-13 19:07:59 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
if (doFill) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
doFill = true;
|
|
|
|
}
|
2017-11-13 19:07:59 +01:00
|
|
|
}
|
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
if (doFill) {
|
|
|
|
filledFields[field.opid] = field;
|
2022-08-10 03:30:26 +02:00
|
|
|
AutofillService.fillByOpid(fillScript, field, dataValue);
|
2019-01-20 05:02:07 +01:00
|
|
|
}
|
2022-01-04 16:41:07 +01:00
|
|
|
}
|
|
|
|
|
2022-08-10 03:30:26 +02:00
|
|
|
static loadPasswordFields(
|
2022-01-04 16:41:07 +01:00
|
|
|
pageDetails: AutofillPageDetails,
|
|
|
|
canBeHidden: boolean,
|
|
|
|
canBeReadOnly: boolean,
|
|
|
|
mustBeEmpty: boolean,
|
|
|
|
fillNewPassword: boolean
|
|
|
|
) {
|
|
|
|
const arr: AutofillField[] = [];
|
|
|
|
pageDetails.fields.forEach((f) => {
|
2022-08-10 03:30:26 +02:00
|
|
|
if (AutofillService.forCustomFieldsOnly(f)) {
|
2022-01-04 16:41:07 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const isPassword = f.type === "password";
|
|
|
|
const valueIsLikePassword = (value: string) => {
|
|
|
|
if (value == null) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Removes all whitespace, _ and - characters
|
2022-02-24 18:14:04 +01:00
|
|
|
// eslint-disable-next-line
|
2022-01-04 16:41:07 +01:00
|
|
|
const cleanedValue = value.toLowerCase().replace(/[\s_\-]/g, "");
|
2019-01-20 05:02:07 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
if (cleanedValue.indexOf("password") < 0) {
|
|
|
|
return false;
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
2017-11-13 19:07:59 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
if (AutoFillConstants.PasswordFieldIgnoreList.some((i) => cleanedValue.indexOf(i) > -1)) {
|
|
|
|
return false;
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
return true;
|
|
|
|
};
|
|
|
|
const isLikePassword = () => {
|
|
|
|
if (f.type !== "text") {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (valueIsLikePassword(f.htmlID)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (valueIsLikePassword(f.htmlName)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (valueIsLikePassword(f.placeholder)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
if (
|
|
|
|
!f.disabled &&
|
|
|
|
(canBeReadOnly || !f.readonly) &&
|
|
|
|
(isPassword || isLikePassword()) &&
|
|
|
|
(canBeHidden || f.viewable) &&
|
|
|
|
(!mustBeEmpty || f.value == null || f.value.trim() === "") &&
|
|
|
|
(fillNewPassword || f.autoCompleteType !== "new-password")
|
|
|
|
) {
|
|
|
|
arr.push(f);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return arr;
|
|
|
|
}
|
|
|
|
|
|
|
|
private findUsernameField(
|
|
|
|
pageDetails: AutofillPageDetails,
|
|
|
|
passwordField: AutofillField,
|
|
|
|
canBeHidden: boolean,
|
|
|
|
canBeReadOnly: boolean,
|
|
|
|
withoutForm: boolean
|
|
|
|
) {
|
|
|
|
let usernameField: AutofillField = null;
|
|
|
|
for (let i = 0; i < pageDetails.fields.length; i++) {
|
|
|
|
const f = pageDetails.fields[i];
|
2022-08-10 03:30:26 +02:00
|
|
|
if (AutofillService.forCustomFieldsOnly(f)) {
|
2022-01-04 16:41:07 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (f.elementNumber >= passwordField.elementNumber) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
!f.disabled &&
|
|
|
|
(canBeReadOnly || !f.readonly) &&
|
|
|
|
(withoutForm || f.form === passwordField.form) &&
|
|
|
|
(canBeHidden || f.viewable) &&
|
|
|
|
(f.type === "text" || f.type === "email" || f.type === "tel")
|
|
|
|
) {
|
|
|
|
usernameField = f;
|
|
|
|
|
|
|
|
if (this.findMatchingFieldIndex(f, AutoFillConstants.UsernameFieldNames) > -1) {
|
|
|
|
// We found an exact match. No need to keep looking.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
return usernameField;
|
|
|
|
}
|
|
|
|
|
|
|
|
private findMatchingFieldIndex(field: AutofillField, names: string[]): number {
|
|
|
|
for (let i = 0; i < names.length; i++) {
|
|
|
|
if (names[i].indexOf("=") > -1) {
|
|
|
|
if (this.fieldPropertyIsPrefixMatch(field, "htmlID", names[i], "id")) {
|
|
|
|
return i;
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
2022-01-04 16:41:07 +01:00
|
|
|
if (this.fieldPropertyIsPrefixMatch(field, "htmlName", names[i], "name")) {
|
|
|
|
return i;
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
2022-01-04 16:41:07 +01:00
|
|
|
if (this.fieldPropertyIsPrefixMatch(field, "label-tag", names[i], "label")) {
|
|
|
|
return i;
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
2022-01-04 16:41:07 +01:00
|
|
|
if (this.fieldPropertyIsPrefixMatch(field, "label-aria", names[i], "label")) {
|
|
|
|
return i;
|
2017-12-06 17:11:52 +01:00
|
|
|
}
|
2022-01-04 16:41:07 +01:00
|
|
|
if (this.fieldPropertyIsPrefixMatch(field, "placeholder", names[i], "placeholder")) {
|
|
|
|
return i;
|
2017-12-06 17:11:52 +01:00
|
|
|
}
|
2022-01-04 16:41:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (this.fieldPropertyIsMatch(field, "htmlID", names[i])) {
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
if (this.fieldPropertyIsMatch(field, "htmlName", names[i])) {
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
if (this.fieldPropertyIsMatch(field, "label-tag", names[i])) {
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
if (this.fieldPropertyIsMatch(field, "label-aria", names[i])) {
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
if (this.fieldPropertyIsMatch(field, "placeholder", names[i])) {
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
private fieldPropertyIsPrefixMatch(
|
|
|
|
field: any,
|
|
|
|
property: string,
|
|
|
|
name: string,
|
|
|
|
prefix: string,
|
|
|
|
separator = "="
|
|
|
|
): boolean {
|
|
|
|
if (name.indexOf(prefix + separator) === 0) {
|
|
|
|
const sepIndex = name.indexOf(separator);
|
|
|
|
const val = name.substring(sepIndex + 1);
|
|
|
|
return val != null && this.fieldPropertyIsMatch(field, property, val);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
private fieldPropertyIsMatch(field: any, property: string, name: string): boolean {
|
|
|
|
let fieldVal = field[property] as string;
|
2022-08-10 03:30:26 +02:00
|
|
|
if (!AutofillService.hasValue(fieldVal)) {
|
2022-01-04 16:41:07 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
fieldVal = fieldVal.trim().replace(/(?:\r\n|\r|\n)/g, "");
|
|
|
|
if (name.startsWith("regex=")) {
|
|
|
|
try {
|
|
|
|
const regexParts = name.split("=", 2);
|
|
|
|
if (regexParts.length === 2) {
|
|
|
|
const regex = new RegExp(regexParts[1], "i");
|
|
|
|
return regex.test(fieldVal);
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
2022-01-04 16:41:07 +01:00
|
|
|
} catch (e) {
|
|
|
|
this.logService.error(e);
|
|
|
|
}
|
|
|
|
} else if (name.startsWith("csv=")) {
|
|
|
|
const csvParts = name.split("=", 2);
|
|
|
|
if (csvParts.length === 2) {
|
|
|
|
const csvVals = csvParts[1].split(",");
|
|
|
|
for (let i = 0; i < csvVals.length; i++) {
|
|
|
|
const val = csvVals[i];
|
|
|
|
if (val != null && val.trim().toLowerCase() === fieldVal.toLowerCase()) {
|
2018-11-25 04:54:30 +01:00
|
|
|
return true;
|
2022-01-04 16:41:07 +01:00
|
|
|
}
|
2018-11-25 04:54:30 +01:00
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
return false;
|
2022-01-04 16:41:07 +01:00
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
return fieldVal.toLowerCase() === name;
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-08-10 03:30:26 +02:00
|
|
|
static fieldIsFuzzyMatch(field: AutofillField, names: string[]): boolean {
|
|
|
|
if (AutofillService.hasValue(field.htmlID) && this.fuzzyMatch(names, field.htmlID)) {
|
2022-01-04 16:41:07 +01:00
|
|
|
return true;
|
|
|
|
}
|
2022-08-10 03:30:26 +02:00
|
|
|
if (AutofillService.hasValue(field.htmlName) && this.fuzzyMatch(names, field.htmlName)) {
|
2022-01-04 16:41:07 +01:00
|
|
|
return true;
|
|
|
|
}
|
2022-08-10 03:30:26 +02:00
|
|
|
if (
|
|
|
|
AutofillService.hasValue(field["label-tag"]) &&
|
|
|
|
this.fuzzyMatch(names, field["label-tag"])
|
|
|
|
) {
|
2022-01-04 16:41:07 +01:00
|
|
|
return true;
|
|
|
|
}
|
2022-08-10 03:30:26 +02:00
|
|
|
if (AutofillService.hasValue(field.placeholder) && this.fuzzyMatch(names, field.placeholder)) {
|
2022-01-04 16:41:07 +01:00
|
|
|
return true;
|
|
|
|
}
|
2022-08-10 03:30:26 +02:00
|
|
|
if (
|
|
|
|
AutofillService.hasValue(field["label-left"]) &&
|
|
|
|
this.fuzzyMatch(names, field["label-left"])
|
|
|
|
) {
|
2022-01-04 16:41:07 +01:00
|
|
|
return true;
|
|
|
|
}
|
2022-08-10 03:30:26 +02:00
|
|
|
if (
|
|
|
|
AutofillService.hasValue(field["label-top"]) &&
|
|
|
|
this.fuzzyMatch(names, field["label-top"])
|
|
|
|
) {
|
2022-01-04 16:41:07 +01:00
|
|
|
return true;
|
|
|
|
}
|
2022-08-10 03:30:26 +02:00
|
|
|
if (
|
|
|
|
AutofillService.hasValue(field["label-aria"]) &&
|
|
|
|
this.fuzzyMatch(names, field["label-aria"])
|
|
|
|
) {
|
2022-01-04 16:41:07 +01:00
|
|
|
return true;
|
|
|
|
}
|
2017-12-06 17:11:52 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
return false;
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-08-10 03:30:26 +02:00
|
|
|
private static fuzzyMatch(options: string[], value: string): boolean {
|
2022-01-04 16:41:07 +01:00
|
|
|
if (options == null || options.length === 0 || value == null || value === "") {
|
|
|
|
return false;
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
value = value
|
|
|
|
.replace(/(?:\r\n|\r|\n)/g, "")
|
|
|
|
.trim()
|
|
|
|
.toLowerCase();
|
|
|
|
|
|
|
|
for (let i = 0; i < options.length; i++) {
|
|
|
|
if (value.indexOf(options[i]) > -1) {
|
|
|
|
return true;
|
|
|
|
}
|
2017-12-06 17:11:52 +01:00
|
|
|
}
|
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
return false;
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-08-10 03:30:26 +02:00
|
|
|
static hasValue(str: string): boolean {
|
2022-01-04 16:41:07 +01:00
|
|
|
return str && str !== "";
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-08-10 03:30:26 +02:00
|
|
|
static setFillScriptForFocus(
|
2022-01-04 16:41:07 +01:00
|
|
|
filledFields: { [id: string]: AutofillField },
|
|
|
|
fillScript: AutofillScript
|
|
|
|
): AutofillScript {
|
|
|
|
let lastField: AutofillField = null;
|
|
|
|
let lastPasswordField: AutofillField = null;
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
for (const opid in filledFields) {
|
2022-02-24 18:14:04 +01:00
|
|
|
// eslint-disable-next-line
|
2022-01-04 16:41:07 +01:00
|
|
|
if (filledFields.hasOwnProperty(opid) && filledFields[opid].viewable) {
|
|
|
|
lastField = filledFields[opid];
|
2017-11-06 20:16:06 +01:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
if (filledFields[opid].type === "password") {
|
|
|
|
lastPasswordField = filledFields[opid];
|
|
|
|
}
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|
2019-04-21 03:29:13 +02:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
// Prioritize password field over others.
|
|
|
|
if (lastPasswordField) {
|
|
|
|
fillScript.script.push(["focus_by_opid", lastPasswordField.opid]);
|
|
|
|
} else if (lastField) {
|
|
|
|
fillScript.script.push(["focus_by_opid", lastField.opid]);
|
2019-04-21 03:29:13 +02:00
|
|
|
}
|
2021-09-30 22:02:13 +02:00
|
|
|
|
2022-01-04 16:41:07 +01:00
|
|
|
return fillScript;
|
|
|
|
}
|
|
|
|
|
2022-08-10 03:30:26 +02:00
|
|
|
static fillByOpid(fillScript: AutofillScript, field: AutofillField, value: string): void {
|
2022-01-04 16:41:07 +01:00
|
|
|
if (field.maxLength && value && value.length > field.maxLength) {
|
|
|
|
value = value.substr(0, value.length);
|
2021-09-30 22:02:13 +02:00
|
|
|
}
|
2022-01-04 16:41:07 +01:00
|
|
|
if (field.tagName !== "span") {
|
|
|
|
fillScript.script.push(["click_on_opid", field.opid]);
|
|
|
|
fillScript.script.push(["focus_by_opid", field.opid]);
|
|
|
|
}
|
|
|
|
fillScript.script.push(["fill_by_opid", field.opid, value]);
|
|
|
|
}
|
|
|
|
|
2022-08-10 03:30:26 +02:00
|
|
|
static forCustomFieldsOnly(field: AutofillField): boolean {
|
2022-01-04 16:41:07 +01:00
|
|
|
return field.tagName === "span";
|
|
|
|
}
|
2017-11-06 20:16:06 +01:00
|
|
|
}
|