Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
78 changes: 3 additions & 75 deletions browser/components/enterprise/EnterpriseHandler.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ ChromeUtils.defineESModuleGetters(lazy, {
BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
ConsoleClient: "resource:///modules/enterprise/ConsoleClient.sys.mjs",
EnterpriseCommon: "resource:///modules/enterprise/EnterpriseCommon.sys.mjs",
UIState: "resource://services-sync/UIState.sys.mjs",
Weave: "resource://services-sync/main.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "log", () => {
Expand All @@ -27,7 +25,6 @@ ChromeUtils.defineLazyGetter(lazy, "log", () => {
});

const PROMPT_ON_SIGNOUT_PREF = "enterprise.promptOnSignout";
const ENTERPRISE_SYNC_ENABLED_PREF = "enterprise.sync.enabledByDefault";

export const EnterpriseHandler = {
/**
Expand All @@ -36,16 +33,15 @@ export const EnterpriseHandler = {
_signedInUser: null,

/**
* Whether the handler is initialized, hence we have retrieved the
* user information and initialized the sync state.
* Whether the handler is initialized, meaning the user information
* from the signed in user has been received from the console.
*/
_isInitialized: false,

/**
* Handles the enterprise state for each new browser window.
* On first call:
* - Make a request to the console to retrieve the user information of the signed in user.
* - Configure sync to be enabled or disable (depending on ENTERPRISE_SYNC_ENABLED_PREF)
* On every call:
* - Hide FxA toolbar button and FxA item in app menu (hamburger menu)
*
Expand All @@ -55,78 +51,10 @@ export const EnterpriseHandler = {
if (!this._isInitialized) {
lazy.log.debug("Initializing...");
await this.initUser();
this.setupSyncOnceInitialized(window);
this._isInitialized = true;
}
this.updateBadge(window);
this.restrictEnterpriseView(window);
this._isInitialized = true;
},

/**
* Check if the FxA state is initialised yet.
* - If the state is still undefined, listen for a state update
* and set up once the state update occurs.
* - If the state is initialized, set up sync immediately.
*
* @param {Window} window chrome window
*/
setupSyncOnceInitialized(window) {
const status = lazy.UIState.get().status;
if (status === lazy.UIState.get().STATUS_NOT_CONFIGURED) {
// State not configured yet.
lazy.log.debug("Waiting for FxA/Sync status to be updated");
const syncStateObserver = (_, topic) => {
switch (topic) {
case lazy.UIState.ON_UPDATE:
lazy.log.debug("Sync state has been initialized");
this.setUpSync(window);
Services.obs.removeObserver(
syncStateObserver,
lazy.UIState.ON_UPDATE
);
break;
default:
break;
}
};
Services.obs.addObserver(syncStateObserver, lazy.UIState.ON_UPDATE);
return;
}
this.setUpSync();
},

/**
* Align sync state with expected state (ENTERPRISE_SYNC_ENABLED_PREF)
* by enabling or disabling sync.
*
* @param {Window} window chrome window
*/
setUpSync(window) {
lazy.log.debug("Handling sync state.");
const isSyncCurrentlyEnabled = lazy.UIState.get().syncEnabled;
const isEnableSync = Services.prefs.getBoolPref(
ENTERPRISE_SYNC_ENABLED_PREF,
false
);

if (isSyncCurrentlyEnabled === isEnableSync) {
// Nothing to do
lazy.log.debug(
`Not changing sync state. It was already ${isSyncCurrentlyEnabled ? "enabled" : "disabled"}`
);
return;
}

if (isEnableSync) {
lazy.log.debug(`Connect sync.`);
lazy.Weave.Service.configure();
} else {
lazy.log.debug(`Disconnect sync.`);
window.gSync.disconnect({
confirm: false,
disconnectAccount: false,
});
}
},

async initUser() {
Expand Down
21 changes: 20 additions & 1 deletion browser/components/enterprisepolicies/Policies.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
WebsiteFilter: "resource:///modules/policies/WebsiteFilter.sys.mjs",
});

const PREF_LOGLEVEL = "browser.policies.loglevel";
export const PREF_LOGLEVEL = "browser.policies.loglevel";
const BROWSER_DOCUMENT_URL = AppConstants.BROWSER_CHROME_URL;
const ABOUT_CONTRACT = "@mozilla.org/network/protocol/about;1?what=";

Expand Down Expand Up @@ -3405,6 +3405,11 @@ export var PoliciesUtils = {
restoreDefaultPref(prefName) {
const values = this._savedPrefs[prefName];

if (!values) {
// No default values available.
return;
}

let defaults = Services.prefs.getDefaultBranch("");
switch (typeof values.defaultValue) {
case "number":
Expand Down Expand Up @@ -3921,3 +3926,17 @@ function processMIMEInfo(mimeInfo, realMIMEInfo) {
}
lazy.gHandlerService.store(realMIMEInfo);
}

if (AppConstants.MOZ_ENTERPRISE) {
ChromeUtils.defineESModuleGetters(lazy, {
SyncPolicy: "resource:///modules/policies/SyncPolicy.sys.mjs",
});
Policies.Sync = {
async onBeforeAddons(manager, param) {
await lazy.SyncPolicy.applySettings(manager, param);
},
async onRemove(manager, _) {
await lazy.SyncPolicy.restoreSettings(manager);
}
}
}
Copy link
Member

Choose a reason for hiding this comment

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

An alternative here is to define this as normal and do the MOZ_ENTERPRISE check within the policy to make it non-functional in the non-enterprise case. That might make it easier to find this as it then appears in the normal alphabetical list of policy declarations. But I don't have a strong opinion, leave this as it is if you like

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The stretch goal is land this in m-c (Bug 2017722), so this should be temporary anyway.

163 changes: 163 additions & 0 deletions browser/components/enterprisepolicies/helpers/SyncPolicy.sys.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */

import {
PREF_LOGLEVEL,
setAndLockPref,
unsetAndUnlockPref,
PoliciesUtils,
} from "resource:///modules/policies/Policies.sys.mjs";

import { STATUS_OK as SYNC_STATUS_OK } from "resource://services-sync/constants.sys.mjs"

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
Weave: "resource://services-sync/main.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "log", () => {
return console.createInstance({
prefix: "SyncPolicy",
maxLogLevelPref: PREF_LOGLEVEL,
});
});

const ENGINE_PREFS = {
addresses: "services.sync.engine.addresses",
addons: "services.sync.engine.addons",
bookmarks: "services.sync.engine.bookmarks",
history: "services.sync.engine.history",
openTabs: "services.sync.engine.tabs",
passwords: "services.sync.engine.passwords",
paymentMethods: "services.sync.engine.creditcards",
settings: "services.sync.engine.prefs",
};

const SYNC_FEATURE = "change-sync-state";

/**
* Customizes Sync settings (all settings are optional):
* - Whether sync is enabled/disabled
* - Which types of data to sync
* - Whether to lock the sync customization
* See SyncPolicyParams for details.
*/
export const SyncPolicy = {
/**
* Get current sync state.
*
* @returns {boolean} Whether sync is currently enabled.
*/
isSyncEnabled() {
return lazy.Weave.Status.checkSetup() == SYNC_STATUS_OK;
},

/**
* @typedef {object} SyncPolicyParams
* @property {boolean} [Enabled] Whether Sync should be enabled
* @property {boolean} [addresses] Whether syncing addresses should be enabled
* @property {boolean} [bookmarks] Whether syncing bookmarks should be enabled
* @property {boolean} [history] Whether syncing history should be enabled
* @property {boolean} [openTabs] Whether syncing openTabs should be enabled
* @property {boolean} [passwords] Whether syncing passwords should be enabled
* @property {boolean} [paymentMethods] Whether syncing paymentMethods should be enabled
* @property {boolean} [addons] Whether syncing addons should be enabled
* @property {boolean} [settings] Whether syncing settings should be enabled
* @property {boolean} [Locked] Whether to lock the customized sync settings
*/

/**
* Apply Sync settings
*
* @param {EnterprisePoliciesManager} manager
* @param {SyncPolicyParams} params
*
* @returns {Promise<void>} Resolves once all Sync settings have been applied.
*/
async applySettings(manager, params) {
lazy.log.debug("Apply Sync Settings");

// This might be an update to the Sync policy
// so restore previous sync settings
this.restoreSettings(manager);

const {
Enabled: shouldEnableSync,
Locked: shouldLock,
...typeSettings
} = params;

const isSyncEnabled = this.isSyncEnabled();

if (shouldEnableSync === true) {
lazy.log.debug("Enable Sync");
if (!isSyncEnabled) {
await this.connectSync(manager);
Copy link
Contributor

@gcp gcp Feb 18, 2026

Choose a reason for hiding this comment

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

This can throw from here: https://searchfox.org/firefox-main/rev/150ca809c7243bd6e994f28cc4a3750430783364/services/sync/modules/service.sys.mjs#977

Not sure if it can reasonably happen in our setup, but it would mean all the other policy/settings enforcement code below is skipped. We might want to eat the error and rethrow after we did that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would you suggest doing anything in Policies.sys.mjs with that error besides logging the failure? Logging we could also just do here. We could also send back an event inconsistency event to the console, but then Sync would be the only policy to inform the console about such a failure.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As a first step I wrapped connectSync and disconnectSync in a try-catch block.

}
} else if (shouldEnableSync === false) {
lazy.log.debug("Disable Sync");
if (isSyncEnabled) {
await this.disconnectSync(manager);
}
}

for (const [type, value] of Object.entries(typeSettings)) {
const pref = ENGINE_PREFS[type];
if (shouldLock) {
lazy.log.debug(`Setting and locking ${type}: ${pref} : ${value}`);
setAndLockPref(pref, value);
continue;
}
lazy.log.debug(`Setting ${type}: ${pref} : ${value}`);
PoliciesUtils.setDefaultPref(pref, value, false);
}

// Only lock the Sync feature if 'Enabled' is configured
if (shouldLock && shouldEnableSync !== undefined) {
manager.disallowFeature(SYNC_FEATURE);
}
},

/**
* Restore initial sync state.
*
* @param {EnterprisePoliciesManager} manager
*/
async restoreSettings(manager) {
if (!Services.policies.isAllowed(SYNC_FEATURE)) {
manager.allowFeature(SYNC_FEATURE);
}
for (const pref of Object.values(ENGINE_PREFS)) {
lazy.log.debug(`Unsetting ${pref}`);
unsetAndUnlockPref(pref);
}

// We don't have a way yet to restore the pre-policy sync
// state (Bug 2017719). So for now we fallback to sync enabled.
this.connectSync()
},

/**
* Disconnect sync
*/
async disconnectSync() {
try {
await lazy.Weave.Service.promiseInitialized;
await lazy.Weave.Service.startOver();
} catch (e) {
lazy.log.error(`Failed to disconnect sync: ${e}`)
}
},

/**
* Connect sync
*/
async connectSync() {
try {
await lazy.Weave.Service.configure();
} catch (e) {
lazy.log.error(`Failed to connect sync: ${e}`)
}
},
};
5 changes: 5 additions & 0 deletions browser/components/enterprisepolicies/helpers/moz.build
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ EXTRA_JS_MODULES.policies += [
"ProxyPolicies.sys.mjs",
]

if CONFIG["MOZ_ENTERPRISE"]:
EXTRA_JS_MODULES.policies += [
"SyncPolicy.sys.mjs",
]

EXTRA_PP_JS_MODULES.policies += [
"WebsiteFilter.sys.mjs",
]
36 changes: 36 additions & 0 deletions browser/components/enterprisepolicies/schemas/policies-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1697,6 +1697,42 @@
"required": ["Title", "URL"]
},

"Sync": {
"type": "object",
"properties": {
"Enabled": {
"type": "boolean"
},
"addresses": {
"type": "boolean"
},
"bookmarks": {
"type": "boolean"
},
"history": {
"type": "boolean"
},
"openTabs": {
"type": "boolean"
},
"passwords": {
"type": "boolean"
},
"paymentMethods": {
"type": "boolean"
},
"addons": {
"type": "boolean"
},
"settings": {
"type": "boolean"
},
"Locked": {
"type": "boolean"
}
}
},

"TranslateEnabled": {
"type": "boolean"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ let gSyncChooseWhatToSync = {
this._setupEventListeners();
this._adjustForPrefs();
let options = window.arguments[0];
if (options.disconnectFun) {
if (
options.disconnectFun &&
Services.policies.isAllowed("change-sync-state")
) {
// Offer 'Disconnect' functionality if it was provided
document.addEventListener("dialogextra2", function () {
options.disconnectFun().then(disconnected => {
Expand Down
6 changes: 6 additions & 0 deletions browser/components/preferences/sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,12 @@ var gSyncPane = {
"connect-another-device"
);
connectAnotherDeviceLink.setAttribute("restricted-enterprise-view", true);

if (!Services.policies.isAllowed("change-sync-state")) {
// Hide info box and "Turn on syncing..." button (visible when Sync is disabled)
const syncOffBox = document.getElementById("syncNotConfigured");
syncOffBox.setAttribute("restricted-enterprise-view", true);
}
},

_updateSyncNow(syncing) {
Expand Down
Loading