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:
@@ -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: () => {
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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: >
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user