summaryrefslogtreecommitdiffstats
path: root/toolkit/components/enterprisepolicies/macOSPoliciesParser.sys.mjs
blob: 09f67bba636fab1a415fdb1d901c695b72b0966c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/* 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 { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const PREF_LOGLEVEL = "browser.policies.loglevel";

const lazy = {};

XPCOMUtils.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;
}