summaryrefslogtreecommitdiffstats
path: root/toolkit/components/enterprisepolicies/macOSPoliciesParser.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/enterprisepolicies/macOSPoliciesParser.sys.mjs')
-rw-r--r--toolkit/components/enterprisepolicies/macOSPoliciesParser.sys.mjs158
1 files changed, 158 insertions, 0 deletions
diff --git a/toolkit/components/enterprisepolicies/macOSPoliciesParser.sys.mjs b/toolkit/components/enterprisepolicies/macOSPoliciesParser.sys.mjs
new file mode 100644
index 0000000000..9786d3b374
--- /dev/null
+++ b/toolkit/components/enterprisepolicies/macOSPoliciesParser.sys.mjs
@@ -0,0 +1,158 @@
+/* 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/. */
+
+const PREF_LOGLEVEL = "browser.policies.loglevel";
+
+const lazy = {};
+
+ChromeUtils.defineLazyGetter(lazy, "log", () => {
+ let { ConsoleAPI } = ChromeUtils.importESModule(
+ "resource://gre/modules/Console.sys.mjs"
+ );
+ return new ConsoleAPI({
+ prefix: "macOSPoliciesParser.jsm",
+ // tip: set maxLogLevel to "debug" and use log.debug() to create detailed
+ // messages during development. See LOG_LEVELS in Console.sys.mjs for details.
+ maxLogLevel: "error",
+ maxLogLevelPref: PREF_LOGLEVEL,
+ });
+});
+
+export var macOSPoliciesParser = {
+ readPolicies(reader) {
+ let nativePolicies = reader.readPreferences();
+ if (!nativePolicies) {
+ return null;
+ }
+
+ nativePolicies = this.unflatten(nativePolicies);
+ nativePolicies = this.removeUnknownPolicies(nativePolicies);
+
+ // Need an extra check here so we don't
+ // JSON.stringify if we aren't in debug mode
+ if (lazy.log.maxLogLevel == "debug") {
+ lazy.log.debug(JSON.stringify(nativePolicies, null, 2));
+ }
+
+ return nativePolicies;
+ },
+
+ removeUnknownPolicies(policies) {
+ let { schema } = ChromeUtils.importESModule(
+ "resource:///modules/policies/schema.sys.mjs"
+ );
+
+ for (let policyName of Object.keys(policies)) {
+ if (!schema.properties.hasOwnProperty(policyName)) {
+ lazy.log.debug(`Removing unknown policy: ${policyName}`);
+ delete policies[policyName];
+ }
+ }
+
+ return policies;
+ },
+
+ unflatten(input, delimiter = "__") {
+ let ret = {};
+
+ for (let key of Object.keys(input)) {
+ if (!key.includes(delimiter)) {
+ // Short-circuit for policies that are not specified in
+ // the flat format.
+ ret[key] = input[key];
+ continue;
+ }
+
+ lazy.log.debug(`Unflattening policy key "${key}".`);
+
+ let subkeys = key.split(delimiter);
+
+ // `obj`: is the intermediate step into the unflattened
+ // return object. For example, for an input:
+ //
+ // Foo__Bar__Baz: 5,
+ //
+ // when the subkey being iterated is Bar, then `obj` will be
+ // the Bar object being constructed, as represented below:
+ //
+ // ret = {
+ // Foo = {
+ // Bar = { <---- obj
+ // Baz: 5,
+ // }
+ // }
+ // }
+ let obj = ret;
+
+ // Iterate until the second to last subkey, as the last one
+ // needs special handling afterwards.
+ for (let i = 0; i < subkeys.length - 1; i++) {
+ let subkey = subkeys[i];
+
+ if (!isValidSubkey(subkey)) {
+ lazy.log.error(
+ `Error in key ${key}: can't use indexes bigger than 50.`
+ );
+ continue;
+ }
+
+ if (!obj[subkey]) {
+ // if this subkey hasn't been seen yet, create the object
+ // for it, which could be an array if the next subkey is
+ // a number.
+ //
+ // For example, in the following examples:
+ // A)
+ // Foo__Bar__0
+ // Foo__Bar__1
+ //
+ // B)
+ // Foo__Bar__Baz
+ // Foo__Bar__Qux
+ //
+ // If the subkey being analysed right now is Bar, then in example A
+ // we'll create an array to accomodate the numeric entries.
+ // Otherwise, if it's example B, we'll create an object to host all
+ // the named keys.
+ if (Number.isInteger(Number(subkeys[i + 1]))) {
+ obj[subkey] = [];
+ } else {
+ obj[subkey] = {};
+ }
+ }
+
+ obj = obj[subkey];
+ }
+
+ let lastSubkey = subkeys[subkeys.length - 1];
+ if (!isValidSubkey(lastSubkey)) {
+ lazy.log.error(
+ `Error in key ${key}: can't use indexes bigger than 50.`
+ );
+ continue;
+ }
+
+ // In the last subkey, we assign it the value by accessing the input
+ // object again with the full key. For example, in the case:
+ //
+ // input = {"Foo__Bar__Baz": 5}
+ //
+ // what we're doing in practice is:
+ //
+ // ret["Foo"]["Bar"]["Baz"] = input["Foo__Bar__Baz"];
+ // \_______ _______/ |
+ // v |
+ // obj last subkey
+
+ obj[lastSubkey] = input[key];
+ }
+
+ return ret;
+ },
+};
+
+function isValidSubkey(subkey) {
+ let valueAsNumber = Number(subkey);
+ return Number.isNaN(valueAsNumber) || valueAsNumber <= 50;
+}