Bug 1915672 - Add Telemetry for OSAuth for Formautofill and about:logins. r=dimi,mtigley,settings-reviewers,firefox-desktop-core-reviewers ,joschmidt

Differential Revision: https://phabricator.services.mozilla.com/D232339
This commit is contained in:
Helena
2025-01-15 21:55:20 +00:00
parent 2023a49747
commit 651b2cfe37
15 changed files with 292 additions and 35 deletions

View File

@@ -3160,6 +3160,21 @@ BrowserGlue.prototype = {
},
},
{
name: "OS Authentication telemetry",
task: () => {
const osAuthForCc = lazy.FormAutofillUtils.getOSAuthEnabled(
lazy.FormAutofillUtils.AUTOFILL_CREDITCARDS_REAUTH_PREF
);
const osAuthForPw = lazy.LoginHelper.getOSAuthEnabled(
lazy.LoginHelper.OS_AUTH_FOR_PASSWORDS_PREF
);
Glean.formautofill.osAuthEnabled.set(osAuthForCc);
Glean.pwmgr.osAuthEnabled.set(osAuthForPw);
},
},
{
name: "browser-startup-idle-tasks-finished",
task: () => {

View File

@@ -121,13 +121,17 @@ export class AboutLoginsChild extends JSWindowActorChild {
* @param resolve Callback that is called with result of authentication.
* @param messageId The string ID that corresponds to a string stored in aboutLogins.ftl.
* This string will be displayed only when the OS auth dialog is used.
* @param reason The reason for requesting reauthentication, used for telemetry.
*/
async promptForPrimaryPassword(resolve, messageId) {
async promptForPrimaryPassword(resolve, messageId, reason) {
gPrimaryPasswordPromise = {
resolve,
};
that.sendAsyncMessage("AboutLogins:PrimaryPasswordRequest", messageId);
that.sendAsyncMessage("AboutLogins:PrimaryPasswordRequest", {
messageId,
reason,
});
return gPrimaryPasswordPromise;
},

View File

@@ -131,7 +131,10 @@ export class AboutLoginsParent extends JSWindowActorParent {
break;
}
case "AboutLogins:PrimaryPasswordRequest": {
await this.#primaryPasswordRequest(message.data);
await this.#primaryPasswordRequest(
message.data.messageId,
message.data.reason
);
break;
}
case "AboutLogins:Subscribe": {
@@ -245,7 +248,7 @@ export class AboutLoginsParent extends JSWindowActorParent {
this.#ownerGlobal.openPreferences("privacy-logins");
}
async #primaryPasswordRequest(messageId) {
async #primaryPasswordRequest(messageId, reason) {
if (!messageId) {
throw new Error("AboutLogins:PrimaryPasswordRequest: no messageId.");
}
@@ -277,7 +280,8 @@ export class AboutLoginsParent extends JSWindowActorParent {
isOSAuthEnabled,
AboutLogins._authExpirationTime,
messageText.value,
captionText.value
captionText.value,
reason
);
this.sendAsyncMessage("AboutLogins:PrimaryPasswordResponse", {
result: isAuthorized,
@@ -394,12 +398,14 @@ export class AboutLoginsParent extends JSWindowActorParent {
]);
}
let reason = "export_logins";
let { isAuthorized, telemetryEvent } = await lazy.LoginHelper.requestReauth(
this.browsingContext.embedderElement,
true,
null, // Prompt regardless of a recent prompt
messageText.value,
captionText.value
captionText.value,
reason
);
let { name, extra = {}, value = null } = telemetryEvent;

View File

@@ -51,9 +51,13 @@ export function setKeyboardAccessForNonDialogElements(enableKeyboardAccess) {
});
}
export function promptForPrimaryPassword(messageId) {
export function promptForPrimaryPassword(messageId, reason) {
return new Promise(resolve => {
window.AboutLoginsUtils.promptForPrimaryPassword(resolve, messageId);
window.AboutLoginsUtils.promptForPrimaryPassword(
resolve,
messageId,
reason
);
});
}

View File

@@ -422,9 +422,11 @@ export default class LoginItem extends HTMLElement {
}
// We prompt for the primary password when entering edit mode already.
const reason = "reveal_logins";
if (this._revealCheckbox.checked && !this.dataset.editing) {
let primaryPasswordAuth = await promptForPrimaryPassword(
"about-logins-reveal-password-os-auth-dialog-message"
"about-logins-reveal-password-os-auth-dialog-message",
reason
);
if (!primaryPasswordAuth) {
this._revealCheckbox.checked = false;
@@ -468,8 +470,10 @@ export default class LoginItem extends HTMLElement {
}
async handleCopyPasswordClick({ currentTarget }) {
let reason = "copy_logins";
let primaryPasswordAuth = await promptForPrimaryPassword(
"about-logins-copy-password-os-auth-dialog-message"
"about-logins-copy-password-os-auth-dialog-message",
reason
);
if (!primaryPasswordAuth) {
return;
@@ -547,8 +551,10 @@ export default class LoginItem extends HTMLElement {
}
async handleEditEvent() {
let reason = "edit_logins";
let primaryPasswordAuth = await promptForPrimaryPassword(
"about-logins-edit-login-os-auth-dialog-message2"
"about-logins-edit-login-os-auth-dialog-message2",
reason
);
if (!primaryPasswordAuth) {
return;

View File

@@ -80,7 +80,7 @@ Object.defineProperty(window, "AboutLoginsUtils", {
setFocus(element) {
return element.focus();
},
async promptForPrimaryPassword(resolve, _messageId) {
async promptForPrimaryPassword(resolve, _messageId, _reason) {
resolve(true);
},
doLoginsMatch(login1, login2) {

View File

@@ -2984,12 +2984,23 @@ var gPrivacyPane = {
},
]);
let win = Services.wm.getMostRecentBrowserWindow();
// Note on Glean collection: because OSKeyStore.ensureLoggedIn() is not wrapped in
// verifyOSAuth(), it will be documenting "success" for unsupported platforms
// and won't record "fail_error", only "fail_user_canceled"
let loggedIn = await OSKeyStore.ensureLoggedIn(
messageText.value,
captionText.value,
win,
false
);
const result = loggedIn.authenticated ? "success" : "fail_user_canceled";
Glean.pwmgr.promptShownOsReauth.record({
trigger: "toggle_pref_primary_password",
result,
});
if (!loggedIn.authenticated) {
return;
}
@@ -3091,10 +3102,20 @@ var gPrivacyPane = {
osReauthCheckbox.ownerGlobal.docShell.chromeEventHandler.ownerGlobal;
// Calling OSKeyStore.ensureLoggedIn() instead of LoginHelper.verifyOSAuth()
// since we want to authenticate user each time this stting is changed.
// since we want to authenticate user each time this setting is changed.
// Note on Glean collection: because OSKeyStore.ensureLoggedIn() is not wrapped in
// verifyOSAuth(), it will be documenting "success" for unsupported platforms
// and won't record "fail_error", only "fail_user_canceled"
let isAuthorized = (
await OSKeyStore.ensureLoggedIn(messageText, captionText, win, false)
).authenticated;
Glean.pwmgr.promptShownOsReauth.record({
trigger: "toggle_pref_os_auth",
result: isAuthorized ? "success" : "fail_user_canceled",
});
if (!isAuthorized) {
osReauthCheckbox.checked = !osReauthCheckbox.checked;
return;
@@ -3105,6 +3126,10 @@ var gPrivacyPane = {
LoginHelper.OS_AUTH_FOR_PASSWORDS_PREF,
osReauthCheckbox.checked
);
Glean.pwmgr.requireOsReauthToggle.record({
toggle_state: osReauthCheckbox.checked,
});
},
_initOSAuthentication() {

View File

@@ -378,15 +378,28 @@ export class ManageCreditCards extends ManageRecords {
"autofill-edit-payment-method-os-prompt-windows",
"autofill-edit-payment-method-os-prompt-other"
);
const verified = await lazy.FormAutofillUtils.verifyUserOSAuth(
let verified;
let result;
try {
verified = await lazy.FormAutofillUtils.verifyUserOSAuth(
FormAutofill.AUTOFILL_CREDITCARDS_REAUTH_PREF,
promptMessage
);
result = verified ? "success" : "fail_user_canceled";
} catch (ex) {
result = "fail_error";
throw ex;
} finally {
Glean.formautofill.promptShownOsReauth.record({
trigger: "edit",
result,
});
}
if (!verified) {
return;
}
}
let decryptedCCNumObj = {};
if (creditCard && creditCard["cc-number-encrypted"]) {
try {

View File

@@ -335,8 +335,13 @@ FormAutofillPreferences.prototype = {
"autofill-creditcard-os-auth-dialog-caption"
);
let win = target.ownerGlobal.docShell.chromeEventHandler.ownerGlobal;
// Calling OSKeyStore.ensureLoggedIn() instead of FormAutofillUtils.verifyOSAuth()
// since we want to authenticate user each time this stting is changed.
// since we want to authenticate user each time this setting is changed.
// Note on Glean collection: because OSKeyStore.ensureLoggedIn() is not wrapped in
// verifyOSAuth(), it will be documenting "success" for unsupported platforms
// and won't record "fail_error", only "fail_user_canceled"
let isAuthorized = (
await lazy.OSKeyStore.ensureLoggedIn(
messageText,
@@ -345,6 +350,11 @@ FormAutofillPreferences.prototype = {
false
)
).authenticated;
Glean.formautofill.promptShownOsReauth.record({
trigger: "toggle_pref_os_auth",
result: isAuthorized ? "success" : "fail_user_canceled",
});
if (!isAuthorized) {
target.checked = !target.checked;
break;
@@ -355,6 +365,9 @@ FormAutofillPreferences.prototype = {
AUTOFILL_CREDITCARDS_REAUTH_PREF,
target.checked
);
Glean.formautofill.requireOsReauthToggle.record({
toggle_state: target.checked,
});
} else if (target == this.refs.savedAddressesBtn) {
target.ownerGlobal.gSubDialog.open(MANAGE_ADDRESSES_URL);
} else if (target == this.refs.savedCreditCardsBtn) {

View File

@@ -342,6 +342,69 @@ formautofill:
expires: never
telemetry_mirror: FORMAUTOFILL_AVAILABILITY
prompt_shown_os_reauth:
type: event
description: >
Captures the method of user interaction initiating the prompt and the result of the prompt.
Possible triggers include:
"autofill" when a user attempts to autofill a payment method.
"edit" when a user edits a login in about:preferences
"toggle_pref_os_auth" when a user toggles "Autofill / Require device sign in" in about:preferences
Possible results include:
"success" should be used when the user is shown the OS Auth prompt and successfully authenticates.
"fail_user_canceled" should be used when the user cancels the authentication prompt. The user may or may not have provided an incorrect password before cancelling.
"fail_error" should be used when an unexpected exception is encountered.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1915672
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1915672
notification_emails:
- autofill@lists.mozilla.org
- passwords-dev@mozilla.org
expires: never
extra_keys:
trigger:
description: >
Which user interaction triggered the event.
type: string
result:
description: >
The result of the OS Authentication.
type: string
require_os_reauth_toggle:
type: event
description: >
Toggle states include:
True means the OS Auth preference is enabled.
False means it is disabled.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1915672
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1915672
notification_emails:
- autofill@lists.mozilla.org
- passwords-dev@mozilla.org
expires: never
extra_keys:
toggle_state:
description: >
The toggle state after the event.
type: boolean
os_auth_enabled:
type: boolean
description: >
Check at startup whether OS Authentication has been enabled for credit cards.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1915672
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1915672
notification_emails:
- autofill@lists.mozilla.org
- passwords-dev@mozilla.org
expires: never
address:
show_capture_doorhanger:
type: event

View File

@@ -606,16 +606,27 @@ export class FormAutofillCreditCardSection extends FormAutofillSection {
"autofill-use-payment-method-os-prompt-windows",
"autofill-use-payment-method-os-prompt-other"
);
const decrypted = await this.getDecryptedString(
let decrypted;
let result;
try {
decrypted = await this.getDecryptedString(
profile["cc-number-encrypted"],
promptMessage
);
result = decrypted ? "success" : "fail_user_canceled";
} catch (ex) {
result = "fail_error";
throw ex;
} finally {
Glean.formautofill.promptShownOsReauth.record({
trigger: "autofill",
result,
});
}
if (!decrypted) {
// Early return if the decrypted is empty or undefined
return false;
}
profile["cc-number"] = decrypted;
}
return true;

View File

@@ -1599,13 +1599,15 @@ export const LoginHelper = {
* @param expirationTime Optional timestamp indicating next required re-authentication
* @param messageText Formatted and localized string to be displayed when the OS auth dialog is used.
* @param captionText Formatted and localized string to be displayed when the OS auth dialog is used.
* @param reason The reason for requesting reauthentication, used for telemetry.
*/
async requestReauth(
browser,
OSReauthEnabled,
expirationTime,
messageText,
captionText
captionText,
reason
) {
let isAuthorized = false;
let telemetryEvent;
@@ -1644,13 +1646,25 @@ export const LoginHelper = {
}
// Use the OS auth dialog if there is no primary password
if (!token.hasPassword && OSReauthEnabled) {
let isAuthorized = await this.verifyUserOSAuth(
let result;
try {
isAuthorized = await this.verifyUserOSAuth(
OS_AUTH_FOR_PASSWORDS_PREF,
messageText,
captionText,
browser.ownerGlobal,
false
);
result = isAuthorized ? "success" : "fail_user_canceled";
} catch (ex) {
result = "fail_error";
throw ex;
} finally {
Glean.pwmgr.promptShownOsReauth.record({
trigger: reason,
result,
});
}
let value = lazy.OSKeyStore.canReauth()
? "success"
: "success_unsupported_platform";

View File

@@ -377,6 +377,78 @@ pwmgr:
extra_keys: *pwmgr_reauthenticate_extra
telemetry_mirror: Pwmgr_Reauthenticate_OsAuth
prompt_shown_os_reauth:
type: event
description: >
Captures the method of user interaction initiating the prompt and the result of the prompt.
Possible triggers include:
"copy_logins" when a user copies a password in about:logins
"copy_cpm" when a user copies a password in contextual password manager
"edit_logins" when a user edits a login in about:logins
"edit_cpm" when a user edits a login in contextual password manager
"export_logins" when a user exports all passwords in about:logins
"export_cpm" when a user exports all passwords in contextual password manager
"reveal_logins" when a user reveals a password a login in about:logins
"reveal_cpm" when a user reveals a password a login in contextual password manager
"toggle_pref_primary_password" when a user toggles "Use a Primary Password" in about:preferences
"toggle_pref_os_auth" when a user toggles "Passwords / Require device sign in" in about:preferences
Possible results include:
"success" should be used when the user is shown the OS Auth prompt and successfully authenticates.
"fail_user_canceled" should be used when the user cancels the authentication prompt. The user may or may not have provided an incorrect password before cancelling.
"fail_error" should be used when an unexpected exception is encountered.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1915672
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1915672
notification_emails:
- autofill@lists.mozilla.org
- passwords-dev@mozilla.org
expires: never
extra_keys:
trigger:
description: >
Which user interaction triggered the event.
type: string
result:
description: >
The result of the OS Authentication.
type: string
require_os_reauth_toggle:
type: event
description: >
Captures the state of the toggle after the change was made.
Toggle states include:
True means the OS Auth preference is enabeled.
False means it is disabled.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1915672
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1915672
notification_emails:
- autofill@lists.mozilla.org
- passwords-dev@mozilla.org
expires: never
extra_keys:
toggle_state:
description: >
The toggle state after the event.
type: boolean
os_auth_enabled:
type: boolean
description: >
Check at startup whether OS Authentication has been enabled for passwords.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1915672
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1915672
notification_emails:
- autofill@lists.mozilla.org
- passwords-dev@mozilla.org
expires: never
open_management_aboutprotections:
type: event
description: >

View File

@@ -197,12 +197,21 @@ export class MegalistViewModel {
}
async #promptForReauth(command) {
// used for recording telemetry
const reasonMap = {
Copy: "copy_cpm",
Reveal: "reveal_cpm",
Edit: "edit_cpm",
};
const reason = reasonMap[command.id];
const { isAuthorized } = await lazy.LoginHelper.requestReauth(
lazy.BrowserWindowTracker.getTopWindow().gBrowser,
this.getOSAuthEnabled(),
this.#authExpirationTime,
command.OSAuthPromptMessage,
command.OSAuthCaptionMessage
command.OSAuthCaptionMessage,
reason
);
if (isAuthorized) {

View File

@@ -519,12 +519,14 @@ export class LoginDataSource extends DataSourceBase {
LoginHelper.OS_AUTH_FOR_PASSWORDS_PREF
);
const reason = "export_cpm";
let { isAuthorized, telemetryEvent } = await LoginHelper.requestReauth(
browsingContext,
isOSAuthEnabled,
null, // Prompt regardless of a recent prompt
this.#exportPasswordsStrings.OSReauthMessage,
this.#exportPasswordsStrings.OSAuthDialogCaption
this.#exportPasswordsStrings.OSAuthDialogCaption,
reason
);
let { name, extra = {}, value = null } = telemetryEvent;