Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
eddea3e
Bug 2064395 - Add FELT session locking infrastructure to persist sess…
fiji-flo Aug 18, 2026
a4f1a39
Bug 2064395 - Lock or sign out the FELT session on browser close via …
jporter-dev Aug 18, 2026
ff6e6e7
Bug 2064395 - Harden FELT session unlocks against decrypt failures an…
jporter-dev Aug 18, 2026
734e74e
Bug 2064395 - Reconcile the persisted FELT token and gate on the pref
jporter-dev Aug 19, 2026
b28ff54
Bug 2064395 - Rework FELT locking config and drive Felt UI persistenc…
jporter-dev Aug 20, 2026
413a360
Bug 2064395 - Show an Unlock button on the FELT login when the entere…
jporter-dev Aug 24, 2026
4e39aa0
Bug 2064395 - Localize the FELT unlock OS auth prompt and make OS-spe…
jporter-dev Aug 24, 2026
26327a3
Bug 2064395 - Clean up FELT locking documentation
jporter-dev Aug 24, 2026
d7a5e73
Bug 2064395 - Lock the FELT session locking pref by default
jporter-dev Aug 24, 2026
7fbc6f0
Bug 2064395 - Clean up the FELT locking test setup
jporter-dev Aug 25, 2026
3f579d0
Bug 2064395 - Move locking token encryption into FeltStorage
jporter-dev Aug 25, 2026
7d6cb6a
Bug 2064395 - Show the sign-out dialog for explicit sign-out even whe…
jporter-dev Aug 26, 2026
3db3615
Bug 2064395 - Commit resumed session tokens through the StartFirefox …
jporter-dev Aug 26, 2026
b30cb48
Bug 2064395 - Add helpers for storing and clearing tokens
jporter-dev Aug 26, 2026
a51a9e5
Bug 2064395 - Only run lock on close in FELT browser
jporter-dev Aug 26, 2026
8adb9c4
Bug 2064395 - Make the SignOut policy live
jporter-dev Aug 31, 2026
5a41a0c
Bug 2064395 - Drain in-flight token refresh before locking
jporter-dev Sep 1, 2026
5f93cc5
Bug 2064395 - Stub OSKeystore in the lock-on-close marionette test
jporter-dev Sep 1, 2026
65d1136
Bug 2064395 - Add description for SignOut policy
jporter-dev Sep 2, 2026
8e27c47
Bug 2064395 - Fix credential lifecycle and exit routing for session l…
jporter-dev Sep 2, 2026
b641286
Bug 2064395 - Collect and submit device posture when unlocking a session
jporter-dev Sep 2, 2026
5f93a61
Bug 2064395 - Rework locking to send the lock intent with the exit event
jporter-dev Sep 3, 2026
1f60c29
Bug 2064395 - Rework posture collection, fix l10n strings, and fix st…
jporter-dev Sep 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions browser/app/profile/firefox.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ pref("enterprise.prompt_on_signout", true);
// or is it fine to apply it to any enterprise build?
pref("app.update.checkOnlyInstance.enabled", false);
pref("app.update.background.enabled", true);
// Allow locking the session (persist behind OS auth) instead of signing out.
pref("enterprise.locking.browser_close", false, locked);
#endif

// Set add-ons abuse report related prefs specific to Firefox Desktop.
Expand Down
1 change: 1 addition & 0 deletions browser/components/BrowserComponents.manifest
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ category browser-quit-application-granted moz-src:///browser/components/ipprotec

#ifdef MOZ_ENTERPRISE
category browser-quit-application-granted resource://gre/modules/enterprise/EnterpriseBadge.sys.mjs EnterpriseBadge.uninit
category browser-quit-application-granted resource:///modules/enterprise/EnterpriseHandler.sys.mjs EnterpriseHandler.uninit
Comment thread
lissyx marked this conversation as resolved.
#endif

category search-service-notification moz-src:///browser/components/search/SearchUIUtils.sys.mjs SearchUIUtils.showSearchServiceNotification
Expand Down
33 changes: 17 additions & 16 deletions browser/components/BrowserGlue.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1502,24 +1502,25 @@ BrowserGlue.prototype = {
// When Firefox was launched by FELT, show a signout confirmation prompt
// instead of the standard quit dialog.
if (AppConstants.MOZ_ENTERPRISE && Services.felt?.isFeltBrowser()) {
if (lazy.EnterpriseHandler.shouldShowClosePrompt()) {
aCancelQuit.QueryInterface(Ci.nsISupportsPRBool).data = true;
this._quitSource = "unknown";
const promptWindow = lazy.BrowserWindowTracker.getTopWindow({
allowFromInactiveWorkspace: true,
});
lazy.EnterpriseHandler.showSignoutPrompt(promptWindow)
.then(proceed => {
if (proceed) {
Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit);
}
})
.catch(e => {
console.error("Enterprise signout prompt failed, quitting:", e);
Services.startup.quit(Ci.nsIAppStartup.eForceQuit);
});
if (!lazy.EnterpriseHandler.shouldHandleClose()) {
return;
}
aCancelQuit.QueryInterface(Ci.nsISupportsPRBool).data = true;
this._quitSource = "unknown";
const promptWindow = lazy.BrowserWindowTracker.getTopWindow({
allowFromInactiveWorkspace: true,
});
lazy.EnterpriseHandler.showSignoutPrompt(promptWindow)
.then(proceed => {
if (proceed) {
lazy.EnterpriseHandler.lockOrSignOut();
}
})
.catch(e => {
console.error("Enterprise signout prompt failed, quitting:", e);
Services.startup.quit(Ci.nsIAppStartup.eForceQuit);
});
return;
}

// browser.warnOnQuit is a hidden global boolean to override all quit prompts.
Expand Down
148 changes: 122 additions & 26 deletions browser/components/enterprise/EnterpriseHandler.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ ChromeUtils.defineLazyGetter(lazy, "log", () => {

const PROMPT_ON_SIGNOUT_PREF = "enterprise.prompt_on_signout";
const WARN_ON_CLOSE_PREF = "browser.tabs.warnOnClose";
const LOCK_ON_CLOSE_PREF = "enterprise.locking.browser_close";

export const EnterpriseHandler = {
/**
Expand Down Expand Up @@ -52,6 +53,7 @@ export const EnterpriseHandler = {
return;
}
this.restrictEnterpriseView(window);
this._initLockingPrefObservers();
this._initUrlbarButtons(window);
},

Expand Down Expand Up @@ -94,6 +96,21 @@ export const EnterpriseHandler = {
});
},

/**
* Initialize observers for the locking prefs.
*/
_initLockingPrefObservers() {
if (Services.felt?.isFeltBrowser() && !this._lockObserversInitialized) {
this._lockObserversInitialized = true;
this._syncCloseLockIntent();
this._lockOnClosePrefObserver = () => this._syncCloseLockIntent();
Services.prefs.addObserver(
LOCK_ON_CLOSE_PREF,
this._lockOnClosePrefObserver
);
}
},

/**
* Hide away FxA appearances in the toolbar and the app menu (hamburger menu)
*
Expand All @@ -114,30 +131,48 @@ export const EnterpriseHandler = {
* @param {number} options.tabCount - The number of open tabs across all windows.
* @param {boolean} options.warnOnSignout - Whether to warn on signout.
* @param {boolean} options.warnOnCloseWithTabs - Whether to warn on close when multiple tabs are open.
* @param {boolean} options.willLock - Whether the action will lock the session rather than sign out.
* @returns {Promise<object>} The parameters for the signout/close prompt, including title, message, checkbox states, and more.
*/
async _getSignoutPromptParams({
tabCount,
warnOnSignout,
warnOnCloseWithTabs,
willLock,
} = {}) {
const hasMultipleTabs = tabCount > 1;
const hasTabsWarning = hasMultipleTabs && warnOnCloseWithTabs;

let titleId, messageId;
if (hasTabsWarning) {
const warnSuffix = warnOnSignout ? "-and-signout-warning" : "";
// Titles are action-neutral; only the message reflects lock vs sign-out.
titleId = {
id: `enterprise-close-prompt-title-with-tabcount${warnSuffix}`,
id: warnOnSignout
? "enterprise-close-prompt-title-with-tabcount-and-signout-warning"
: "enterprise-close-prompt-title-with-tabcount",
args: { tabCount },
};
let messageIdName;
if (willLock) {
messageIdName = warnOnSignout
? "enterprise-close-prompt-message-with-tabcount-and-lock-warning"
: "enterprise-close-prompt-message-with-tabcount-lock";
} else {
messageIdName = warnOnSignout
? "enterprise-close-prompt-message-with-tabcount-and-signout-warning"
: "enterprise-close-prompt-message-with-tabcount";
}
messageId = {
id: `enterprise-close-prompt-message-with-tabcount${warnSuffix}`,
id: messageIdName,
args: warnOnSignout ? { tabCount } : {},
};
} else {
titleId = { id: "enterprise-close-prompt-title" };
messageId = { id: "enterprise-close-prompt-message" };
messageId = {
id: willLock
? "enterprise-close-prompt-message-lock"
: "enterprise-close-prompt-message",
};
}

const [
Expand All @@ -150,9 +185,21 @@ export const EnterpriseHandler = {
] = await lazy.localization.formatValues([
titleId,
messageId,
{ id: "enterprise-close-prompt-primary-btn-label" },
{ id: "enterprise-close-prompt-message-reauth" },
{ id: "enterprise-close-prompt-checkbox-label" },
{
id: willLock
? "enterprise-close-prompt-primary-btn-label-lock"
: "enterprise-close-prompt-primary-btn-label",
},
{
id: willLock
? "enterprise-close-prompt-message-lock-reauth"
: "enterprise-close-prompt-message-reauth",
},
{
id: willLock
? "enterprise-close-prompt-checkbox-label-lock"
: "enterprise-close-prompt-checkbox-label",
},
{ id: "enterprise-close-prompt-tabs-checkbox-label" },
]);

Expand All @@ -172,7 +219,9 @@ export const EnterpriseHandler = {
return {
title,
message,
reauthNotice: warnOnSignout ? reauthNotice : null,
// When locking, the resume notice is always shown; when signing out it is
// only shown if the user opted into sign-out warnings.
reauthNotice: willLock || warnOnSignout ? reauthNotice : null,
acceptLabel,
checkboxes,
accepted: false,
Expand Down Expand Up @@ -218,37 +267,30 @@ export const EnterpriseHandler = {
},

/**
* Determines whether the signout/close prompt should be shown based on preferences and current state.
* Whether to run the close flow, or let the re-quit from an already-started
* enterprise shutdown through.
*
* @returns {boolean} True if the prompt should be shown, false otherwise.
* @returns {boolean} True to run the close flow, false while an enterprise
* shutdown is already underway so its re-quit proceeds.
*/
shouldShowClosePrompt() {
shouldHandleClose() {
if (this._skipSignoutPrompt) {
this._skipSignoutPrompt = false;
return false;
}
const warnOnSignout = Services.prefs.getBoolPref(
PROMPT_ON_SIGNOUT_PREF,
true
);
const warnOnCloseWithTabs = Services.prefs.getBoolPref(
WARN_ON_CLOSE_PREF,
false
);
if (!warnOnSignout && !warnOnCloseWithTabs) {
return false;
}
this._tabCount = this._countOpenTabs();
return warnOnSignout || this._tabCount > 1;
return true;
},

/**
* Shows the signout/close confirmation dialog if needed.
*
* @param {Window} window
* @param {boolean} [willLock] - Whether the resulting action will lock the
* session rather than sign out. Defaults to the locking pref; the explicit
* sign-out entry point passes false so the dialog always reflects a sign-out.
* @returns {Promise<boolean>} true if the action should proceed, false if cancelled.
*/
async showSignoutPrompt(window) {
async showSignoutPrompt(window, willLock = this.willLockOnClose) {
const warnOnSignout = Services.prefs.getBoolPref(
PROMPT_ON_SIGNOUT_PREF,
true
Expand All @@ -269,6 +311,7 @@ export const EnterpriseHandler = {
tabCount: this._tabCount,
warnOnSignout,
warnOnCloseWithTabs,
willLock,
});
this._tabCount = null;

Expand Down Expand Up @@ -310,10 +353,63 @@ export const EnterpriseHandler = {
* @param {Window} window
*/
async onSignOut(window) {
if (!(await this.showSignoutPrompt(window))) {
// Signing out explicitly always ends the session, so show sign-out wording
// even when the locking pref would lock on a plain browser close.
if (!(await this.showSignoutPrompt(window, false))) {
return;
}

lazy.initiateShutdown();
},

/**
* Whether closing the browser will lock the session (persist it behind OS
* auth to resume later) rather than sign out, per the locking pref.
*
* @returns {boolean}
*/
get willLockOnClose() {
return Services.prefs.getBoolPref(LOCK_ON_CLOSE_PREF, false);
},

/**
* Push the current close-locking preference to the browser's FELT IPC
* client, which attaches it to the exit event when a shutdown is observed.
* The value is cached there rather than read at close time so the intent
* always travels with the exit itself (a vetoed quit sends nothing).
*/
_syncCloseLockIntent() {
try {
Services.felt.setCloseLockIntent(this.willLockOnClose);
} catch (e) {
lazy.log.error(`Unable to sync close lock intent: ${e}`);
}
},

/**
* Ends the FELT session on browser close by either locking it (persisting it
* behind OS auth to resume later) or signing out, per the synced locking
* intent that FELT applies once the browser process exits.
*/
lockOrSignOut() {
Comment thread
jporter-dev marked this conversation as resolved.
this._skipSignoutPrompt = true;
if (!Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit)) {
// Vetoed by a beforeunload handler; the next close must prompt again.
this._skipSignoutPrompt = false;
}
},

/**
* Removes all observers owned by this handler.
*/
uninit() {
if (this._lockObserversInitialized) {
this._lockObserversInitialized = false;
Services.prefs.removeObserver(
LOCK_ON_CLOSE_PREF,
this._lockOnClosePrefObserver
);
this._lockOnClosePrefObserver = null;
}
},
};
21 changes: 21 additions & 0 deletions browser/components/enterprisepolicies/Policies.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3712,6 +3712,27 @@ export var Policies = {
},
},

SignOut: {
onBeforeAddons(manager, param) {
if (param.BrowserClose) {
lazy.PoliciesUtils.setAndLockPref(
"enterprise.locking.browser_close",
param.BrowserClose.Action === "lock"
);
}
},
onRemove(manager, oldParams) {
if (oldParams.BrowserClose) {
lazy.PoliciesUtils.unsetAndUnlockPref(
"enterprise.locking.browser_close"
);
// unsetAndUnlockPref restores the build default but never re-locks;
// re-lock to match the locked default the enterprise build ships.
Services.prefs.lockPref("enterprise.locking.browser_close");
}
},
},
Comment thread
jporter-dev marked this conversation as resolved.
Comment on lines +3715 to +3734

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment regarding Thunderbird


SitePolicies: {
/**
* Converts a wildcard domain into a match pattern.
Expand Down
37 changes: 37 additions & 0 deletions browser/components/enterprisepolicies/schemas/policies-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -4164,6 +4164,43 @@
"examples": [true]
},

"SignOut": {
"type": "object",
"x-category": "Authentication",
"x-compatibility": {
"firefox": {
"version_added": false
},
"firefox_esr": {
"version_added": false
},
"firefox_enterprise": {
"version_added": "156"
}
},
"x-restart-required": false,
"description": "Control whether the managed session is signed out or locked when the browser closes.",
"examples": [
{
"BrowserClose": {
"Action": "lock"
}
}
],
"properties": {
"BrowserClose": {
"type": "object",
"properties": {
"Action": {
"type": "string",
"enum": ["signout", "lock"]
}
},
"required": ["Action"]
}
}
},
Comment on lines +4167 to +4202

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make either a port of this for Thunderbird if it is a policy that should be handled there, or update allowed differences of https://searchfox.org/comm-central/source/mail/components/enterprisepolicies/tests/browser/browser_policies_differences.js

Or at least ping me when you land so I can adapt it

Also update/adapt https://docs.google.com/spreadsheets/d/1h-UmrLEBVmLVEl9eIVQlNkrP-NkgGXfxpzN5tJWCWv4/edit?pli=1&gid=1904079787#gid=1904079787 ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or at least file a bug blocking thunderbird-client-poc

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is the TB version of this: https://bugzilla.mozilla.org/show_bug.cgi?id=2060195

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we will have a different policy? Can you make sure you have a patch in the meantime to allow for the missing policy? Otherwise tests will fail and thunderbird people or myself will have to make the fix


"SitePolicies": {
"type": "array",
"x-category": "Browsing restrictions",
Expand Down
Loading