[PM-12775] Password autofill should not occur within 2FA fields (#11303)
This commit is contained in:
parent
f2d3311cb4
commit
bf38f2dfee
|
@ -74,7 +74,7 @@ describe("AutofillInit", () => {
|
||||||
Object.defineProperty(document, "readyState", { value: "complete", writable: true });
|
Object.defineProperty(document, "readyState", { value: "complete", writable: true });
|
||||||
|
|
||||||
autofillInit.init();
|
autofillInit.init();
|
||||||
jest.advanceTimersByTime(250);
|
jest.advanceTimersByTime(750);
|
||||||
|
|
||||||
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("bgCollectPageDetails", {
|
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("bgCollectPageDetails", {
|
||||||
sender: "autofillInit",
|
sender: "autofillInit",
|
||||||
|
|
|
@ -78,7 +78,7 @@ class AutofillInit implements AutofillInitInterface {
|
||||||
this.clearCollectPageDetailsOnLoadTimeout();
|
this.clearCollectPageDetailsOnLoadTimeout();
|
||||||
this.collectPageDetailsOnLoadTimeout = setTimeout(
|
this.collectPageDetailsOnLoadTimeout = setTimeout(
|
||||||
() => this.sendExtensionMessage("bgCollectPageDetails", { sender: "autofillInit" }),
|
() => this.sendExtensionMessage("bgCollectPageDetails", { sender: "autofillInit" }),
|
||||||
250,
|
750,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -34,28 +34,30 @@ export class AutoFillConstants {
|
||||||
"totpcode",
|
"totpcode",
|
||||||
"2facode",
|
"2facode",
|
||||||
"approvals_code",
|
"approvals_code",
|
||||||
"code",
|
|
||||||
"mfacode",
|
"mfacode",
|
||||||
"otc",
|
|
||||||
"otc-code",
|
"otc-code",
|
||||||
"otp",
|
"onetimecode",
|
||||||
"otp-code",
|
"otp-code",
|
||||||
"otpcode",
|
"otpcode",
|
||||||
"pin",
|
"onetimepassword",
|
||||||
"security_code",
|
"security_code",
|
||||||
"twofactor",
|
"twofactor",
|
||||||
"twofa",
|
"twofa",
|
||||||
"twofactorcode",
|
"twofactorcode",
|
||||||
"verificationCode",
|
"verificationCode",
|
||||||
|
"verification code",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
static readonly AmbiguousTotpFieldNames: string[] = ["code", "pin", "otc", "otp"];
|
||||||
|
|
||||||
static readonly SearchFieldNames: string[] = ["search", "query", "find", "go"];
|
static readonly SearchFieldNames: string[] = ["search", "query", "find", "go"];
|
||||||
|
|
||||||
static readonly FieldIgnoreList: string[] = ["captcha", "findanything", "forgot"];
|
static readonly FieldIgnoreList: string[] = ["captcha", "findanything", "forgot"];
|
||||||
|
|
||||||
static readonly PasswordFieldExcludeList: string[] = [
|
static readonly PasswordFieldExcludeList: string[] = [
|
||||||
|
"hint",
|
||||||
...AutoFillConstants.FieldIgnoreList,
|
...AutoFillConstants.FieldIgnoreList,
|
||||||
"onetimepassword",
|
...AutoFillConstants.TotpFieldNames,
|
||||||
];
|
];
|
||||||
|
|
||||||
static readonly ExcludedAutofillLoginTypes: string[] = [
|
static readonly ExcludedAutofillLoginTypes: string[] = [
|
||||||
|
|
|
@ -2260,29 +2260,23 @@ describe("AutofillService", () => {
|
||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(AutofillService.fieldIsFuzzyMatch).toHaveBeenCalledTimes(4);
|
expect(AutofillService.fieldIsFuzzyMatch).toHaveBeenCalledWith(
|
||||||
expect(AutofillService.fieldIsFuzzyMatch).toHaveBeenNthCalledWith(
|
|
||||||
1,
|
|
||||||
usernameField,
|
usernameField,
|
||||||
AutoFillConstants.UsernameFieldNames,
|
AutoFillConstants.UsernameFieldNames,
|
||||||
);
|
);
|
||||||
expect(AutofillService.fieldIsFuzzyMatch).toHaveBeenNthCalledWith(
|
expect(AutofillService.fieldIsFuzzyMatch).toHaveBeenCalledWith(
|
||||||
2,
|
|
||||||
emailField,
|
emailField,
|
||||||
AutoFillConstants.UsernameFieldNames,
|
AutoFillConstants.UsernameFieldNames,
|
||||||
);
|
);
|
||||||
expect(AutofillService.fieldIsFuzzyMatch).toHaveBeenNthCalledWith(
|
expect(AutofillService.fieldIsFuzzyMatch).toHaveBeenCalledWith(
|
||||||
3,
|
|
||||||
telephoneField,
|
telephoneField,
|
||||||
AutoFillConstants.UsernameFieldNames,
|
AutoFillConstants.UsernameFieldNames,
|
||||||
);
|
);
|
||||||
expect(AutofillService.fieldIsFuzzyMatch).toHaveBeenNthCalledWith(
|
expect(AutofillService.fieldIsFuzzyMatch).toHaveBeenCalledWith(
|
||||||
4,
|
|
||||||
totpField,
|
totpField,
|
||||||
AutoFillConstants.UsernameFieldNames,
|
AutoFillConstants.UsernameFieldNames,
|
||||||
);
|
);
|
||||||
expect(AutofillService.fieldIsFuzzyMatch).not.toHaveBeenNthCalledWith(
|
expect(AutofillService.fieldIsFuzzyMatch).not.toHaveBeenCalledWith(
|
||||||
5,
|
|
||||||
nonViewableField,
|
nonViewableField,
|
||||||
AutoFillConstants.UsernameFieldNames,
|
AutoFillConstants.UsernameFieldNames,
|
||||||
);
|
);
|
||||||
|
@ -2328,6 +2322,7 @@ describe("AutofillService", () => {
|
||||||
|
|
||||||
it("will not attempt to fuzzy match a totp field if totp autofill is not allowed", async () => {
|
it("will not attempt to fuzzy match a totp field if totp autofill is not allowed", async () => {
|
||||||
options.allowTotpAutofill = false;
|
options.allowTotpAutofill = false;
|
||||||
|
jest.spyOn(autofillService as any, "findMatchingFieldIndex");
|
||||||
|
|
||||||
await autofillService["generateLoginFillScript"](
|
await autofillService["generateLoginFillScript"](
|
||||||
fillScript,
|
fillScript,
|
||||||
|
@ -2336,7 +2331,7 @@ describe("AutofillService", () => {
|
||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(AutofillService.fieldIsFuzzyMatch).not.toHaveBeenCalledWith(
|
expect(autofillService["findMatchingFieldIndex"]).not.toHaveBeenCalledWith(
|
||||||
expect.anything(),
|
expect.anything(),
|
||||||
AutoFillConstants.TotpFieldNames,
|
AutoFillConstants.TotpFieldNames,
|
||||||
);
|
);
|
||||||
|
@ -2386,7 +2381,6 @@ describe("AutofillService", () => {
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
expect(AutofillService.fieldIsFuzzyMatch).not.toHaveBeenCalled();
|
|
||||||
expect(AutofillService.fillByOpid).toHaveBeenCalledTimes(2);
|
expect(AutofillService.fillByOpid).toHaveBeenCalledTimes(2);
|
||||||
expect(AutofillService.fillByOpid).toHaveBeenNthCalledWith(
|
expect(AutofillService.fillByOpid).toHaveBeenNthCalledWith(
|
||||||
1,
|
1,
|
||||||
|
|
|
@ -887,7 +887,10 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
options.allowTotpAutofill &&
|
options.allowTotpAutofill &&
|
||||||
f.viewable &&
|
f.viewable &&
|
||||||
(f.type === "text" || f.type === "number") &&
|
(f.type === "text" || f.type === "number") &&
|
||||||
(AutofillService.fieldIsFuzzyMatch(f, AutoFillConstants.TotpFieldNames) ||
|
(AutofillService.fieldIsFuzzyMatch(f, [
|
||||||
|
...AutoFillConstants.TotpFieldNames,
|
||||||
|
...AutoFillConstants.AmbiguousTotpFieldNames,
|
||||||
|
]) ||
|
||||||
f.autoCompleteType === "one-time-code")
|
f.autoCompleteType === "one-time-code")
|
||||||
) {
|
) {
|
||||||
totps.push(f);
|
totps.push(f);
|
||||||
|
@ -2558,6 +2561,11 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We want to avoid treating TOTP fields as password fields
|
||||||
|
if (AutofillService.fieldIsFuzzyMatch(f, AutoFillConstants.TotpFieldNames)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const isLikePassword = () => {
|
const isLikePassword = () => {
|
||||||
if (f.type !== "text") {
|
if (f.type !== "text") {
|
||||||
return false;
|
return false;
|
||||||
|
@ -2670,12 +2678,18 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||||
(withoutForm || f.form === passwordField.form) &&
|
(withoutForm || f.form === passwordField.form) &&
|
||||||
(canBeHidden || f.viewable) &&
|
(canBeHidden || f.viewable) &&
|
||||||
(f.type === "text" || f.type === "number") &&
|
(f.type === "text" || f.type === "number") &&
|
||||||
AutofillService.fieldIsFuzzyMatch(f, AutoFillConstants.TotpFieldNames)
|
AutofillService.fieldIsFuzzyMatch(f, [
|
||||||
|
...AutoFillConstants.TotpFieldNames,
|
||||||
|
...AutoFillConstants.AmbiguousTotpFieldNames,
|
||||||
|
])
|
||||||
) {
|
) {
|
||||||
totpField = f;
|
totpField = f;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.findMatchingFieldIndex(f, AutoFillConstants.TotpFieldNames) > -1 ||
|
this.findMatchingFieldIndex(f, [
|
||||||
|
...AutoFillConstants.TotpFieldNames,
|
||||||
|
...AutoFillConstants.AmbiguousTotpFieldNames,
|
||||||
|
]) > -1 ||
|
||||||
f.autoCompleteType === "one-time-code"
|
f.autoCompleteType === "one-time-code"
|
||||||
) {
|
) {
|
||||||
// We found an exact match. No need to keep looking.
|
// We found an exact match. No need to keep looking.
|
||||||
|
|
|
@ -30,7 +30,6 @@ export class InlineMenuFieldQualificationService
|
||||||
this.webAuthnAutocompleteValue,
|
this.webAuthnAutocompleteValue,
|
||||||
]);
|
]);
|
||||||
private fieldIgnoreListString = AutoFillConstants.FieldIgnoreList.join(",");
|
private fieldIgnoreListString = AutoFillConstants.FieldIgnoreList.join(",");
|
||||||
private passwordFieldExcludeListString = AutoFillConstants.PasswordFieldExcludeList.join(",");
|
|
||||||
private currentPasswordAutocompleteValue = "current-password";
|
private currentPasswordAutocompleteValue = "current-password";
|
||||||
private newPasswordAutoCompleteValue = "new-password";
|
private newPasswordAutoCompleteValue = "new-password";
|
||||||
private autofillFieldKeywordsMap: AutofillKeywordsMap = new WeakMap();
|
private autofillFieldKeywordsMap: AutofillKeywordsMap = new WeakMap();
|
||||||
|
@ -927,7 +926,7 @@ export class InlineMenuFieldQualificationService
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return !(this.passwordFieldExcludeListString.indexOf(cleanedValue) > -1);
|
return !AutoFillConstants.PasswordFieldExcludeList.some((i) => cleanedValue.indexOf(i) > -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1094,13 +1093,29 @@ export class InlineMenuFieldQualificationService
|
||||||
];
|
];
|
||||||
const keywordsSet = new Set<string>();
|
const keywordsSet = new Set<string>();
|
||||||
for (let i = 0; i < keywords.length; i++) {
|
for (let i = 0; i < keywords.length; i++) {
|
||||||
if (typeof keywords[i] === "string") {
|
if (keywords[i] && typeof keywords[i] === "string") {
|
||||||
keywords[i]
|
let keywordEl = keywords[i].toLowerCase();
|
||||||
.toLowerCase()
|
keywordsSet.add(keywordEl);
|
||||||
.replace(/-/g, "")
|
|
||||||
.replace(/[^a-zA-Z0-9]+/g, "|")
|
// Remove hyphens from all potential keywords, we want to treat these as a single word.
|
||||||
.split("|")
|
keywordEl = keywordEl.replace(/-/g, "");
|
||||||
.forEach((keyword) => keywordsSet.add(keyword));
|
|
||||||
|
// Split the keyword by non-alphanumeric characters to get the keywords without treating a space as a separator.
|
||||||
|
keywordEl.split(/[^\p{L}\d]+/gu).forEach((keyword) => {
|
||||||
|
if (keyword) {
|
||||||
|
keywordsSet.add(keyword);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Collapse all spaces and split by non-alphanumeric characters to get the keywords
|
||||||
|
keywordEl
|
||||||
|
.replace(/\s/g, "")
|
||||||
|
.split(/[^\p{L}\d]+/gu)
|
||||||
|
.forEach((keyword) => {
|
||||||
|
if (keyword) {
|
||||||
|
keywordsSet.add(keyword);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue