[PM-12775] Password autofill should not occur within 2FA fields (#11303)

This commit is contained in:
Cesar Gonzalez 2024-10-03 16:53:45 -05:00 committed by GitHub
parent f2d3311cb4
commit bf38f2dfee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 57 additions and 32 deletions

View File

@ -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",

View File

@ -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,
); );
}; };

View File

@ -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[] = [

View File

@ -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,

View File

@ -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.

View File

@ -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);
}
});
} }
} }