Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
3
* You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
"use strict";
6
7
const { XPCOMUtils } = ChromeUtils.import(
9
);
10
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
11
const { AppConstants } = ChromeUtils.import(
13
);
14
15
XPCOMUtils.defineLazyServiceGetters(this, {
16
gCertDB: ["@mozilla.org/security/x509certdb;1", "nsIX509CertDB"],
17
});
18
19
XPCOMUtils.defineLazyModuleGetters(this, {
23
});
24
25
const PREF_LOGLEVEL = "browser.policies.loglevel";
26
27
XPCOMUtils.defineLazyGetter(this, "log", () => {
28
let { ConsoleAPI } = ChromeUtils.import("resource://gre/modules/Console.jsm");
29
return new ConsoleAPI({
30
prefix: "Policies.jsm",
31
// tip: set maxLogLevel to "debug" and use log.debug() to create detailed
32
// messages during development. See LOG_LEVELS in Console.jsm for details.
33
maxLogLevel: "error",
34
maxLogLevelPref: PREF_LOGLEVEL,
35
});
36
});
37
38
var EXPORTED_SYMBOLS = ["Policies"];
39
40
/*
41
* ============================
42
* = POLICIES IMPLEMENTATIONS =
43
* ============================
44
*
45
* The Policies object below is where the implementation for each policy
46
* happens. An object for each policy should be defined, containing
47
* callback functions that will be called by the engine.
48
*
49
* See the _callbacks object in EnterprisePolicies.js for the list of
50
* possible callbacks and an explanation of each.
51
*
52
* Each callback will be called with two parameters:
53
* - manager
54
* This is the EnterprisePoliciesManager singleton object from
55
* EnterprisePolicies.js
56
*
57
* - param
58
* The parameter defined for this policy in policies-schema.json.
59
* It will be different for each policy. It could be a boolean,
60
* a string, an array or a complex object. All parameters have
61
* been validated according to the schema, and no unknown
62
* properties will be present on them.
63
*
64
* The callbacks will be bound to their parent policy object.
65
*/
66
var Policies = {
67
AppUpdateURL: {
68
onBeforeAddons(manager, param) {
69
setDefaultPref("app.update.url", param.href);
70
},
71
},
72
73
BlockAboutAddons: {
74
onBeforeUIStartup(manager, param) {
75
if (param) {
76
blockAboutPage(manager, "about:addons", true);
77
}
78
},
79
},
80
81
BlockAboutConfig: {
82
onBeforeUIStartup(manager, param) {
83
if (param) {
84
blockAboutPage(manager, "about:config");
85
setAndLockPref("devtools.chrome.enabled", false);
86
}
87
},
88
},
89
90
BlockAboutProfiles: {
91
onBeforeUIStartup(manager, param) {
92
if (param) {
93
blockAboutPage(manager, "about:profiles");
94
}
95
},
96
},
97
98
BlockAboutSupport: {
99
onBeforeUIStartup(manager, param) {
100
if (param) {
101
blockAboutPage(manager, "about:support");
102
}
103
},
104
},
105
106
Certificates: {
107
onBeforeAddons(manager, param) {
108
if ("ImportEnterpriseRoots" in param) {
109
setAndLockPref(
110
"security.enterprise_roots.enabled",
111
param.ImportEnterpriseRoots
112
);
113
}
114
if ("Install" in param) {
115
(async () => {
116
let dirs = [];
117
let platform = AppConstants.platform;
118
if (platform == "win") {
119
dirs = [
120
// Ugly, but there is no official way to get %USERNAME\AppData\Roaming\Mozilla.
121
Services.dirsvc.get("XREUSysExt", Ci.nsIFile).parent,
122
// Even more ugly, but there is no official way to get %USERNAME\AppData\Local\Mozilla.
123
Services.dirsvc.get("DefProfLRt", Ci.nsIFile).parent.parent,
124
];
125
} else if (platform == "macosx" || platform == "linux") {
126
dirs = [
127
// These two keys are named wrong. They return the Mozilla directory.
128
Services.dirsvc.get("XREUserNativeManifests", Ci.nsIFile),
129
Services.dirsvc.get("XRESysNativeManifests", Ci.nsIFile),
130
];
131
}
132
dirs.unshift(Services.dirsvc.get("XREAppDist", Ci.nsIFile));
133
for (let certfilename of param.Install) {
134
let certfile;
135
try {
136
certfile = Cc["@mozilla.org/file/local;1"].createInstance(
137
Ci.nsIFile
138
);
139
certfile.initWithPath(certfilename);
140
} catch (e) {
141
for (let dir of dirs) {
142
certfile = dir.clone();
143
certfile.append(
144
platform == "linux" ? "certificates" : "Certificates"
145
);
146
certfile.append(certfilename);
147
if (certfile.exists()) {
148
break;
149
}
150
}
151
}
152
let file;
153
try {
154
file = await File.createFromNsIFile(certfile);
155
} catch (e) {
156
log.error(`Unable to find certificate - ${certfilename}`);
157
continue;
158
}
159
let reader = new FileReader();
160
reader.onloadend = function() {
161
if (reader.readyState != reader.DONE) {
162
log.error(`Unable to read certificate - ${certfile.path}`);
163
return;
164
}
165
let certFile = reader.result;
166
let cert;
167
try {
168
cert = gCertDB.constructX509(certFile);
169
} catch (e) {
170
try {
171
// It might be PEM instead of DER.
172
cert = gCertDB.constructX509FromBase64(pemToBase64(certFile));
173
} catch (ex) {
174
log.error(`Unable to add certificate - ${certfile.path}`);
175
}
176
}
177
let now = Date.now() / 1000;
178
if (cert) {
179
gCertDB.asyncVerifyCertAtTime(
180
cert,
181
0x0008 /* certificateUsageSSLCA */,
182
0,
183
null,
184
now,
185
(aPRErrorCode, aVerifiedChain, aHasEVPolicy) => {
186
if (aPRErrorCode == Cr.NS_OK) {
187
// Certificate is already installed.
188
return;
189
}
190
try {
191
gCertDB.addCert(certFile, "CT,CT,");
192
} catch (e) {
193
// It might be PEM instead of DER.
194
gCertDB.addCertFromBase64(
195
pemToBase64(certFile),
196
"CT,CT,"
197
);
198
}
199
}
200
);
201
}
202
};
203
reader.readAsBinaryString(file);
204
}
205
})();
206
}
207
},
208
},
209
210
DisableAppUpdate: {
211
onBeforeAddons(manager, param) {
212
if (param) {
213
manager.disallowFeature("appUpdate");
214
}
215
},
216
},
217
218
DisableDeveloperTools: {
219
onBeforeAddons(manager, param) {
220
if (param) {
221
setAndLockPref("devtools.policy.disabled", true);
222
setAndLockPref("devtools.chrome.enabled", false);
223
224
manager.disallowFeature("devtools");
225
blockAboutPage(manager, "about:devtools");
226
blockAboutPage(manager, "about:debugging");
227
blockAboutPage(manager, "about:devtools-toolbox");
228
}
229
},
230
},
231
232
DisableMasterPasswordCreation: {
233
onBeforeUIStartup(manager, param) {
234
if (param) {
235
manager.disallowFeature("createMasterPassword");
236
}
237
},
238
},
239
240
DisableSecurityBypass: {
241
onBeforeUIStartup(manager, param) {
242
if ("InvalidCertificate" in param) {
243
setAndLockPref(
244
"security.certerror.hideAddException",
245
param.InvalidCertificate
246
);
247
}
248
249
if ("SafeBrowsing" in param) {
250
setAndLockPref(
251
"browser.safebrowsing.allowOverride",
252
!param.SafeBrowsing
253
);
254
}
255
},
256
},
257
258
Extensions: {
259
onBeforeUIStartup(manager, param) {
260
let uninstallingPromise = Promise.resolve();
261
if ("Uninstall" in param) {
262
uninstallingPromise = runOncePerModification(
263
"extensionsUninstall",
264
JSON.stringify(param.Uninstall),
265
async () => {
266
// If we're uninstalling add-ons, re-run the extensionsInstall runOnce even if it hasn't
267
// changed, which will allow add-ons to be updated.
268
Services.prefs.clearUserPref(
269
"browser.policies.runOncePerModification.extensionsInstall"
270
);
271
let addons = await AddonManager.getAddonsByIDs(param.Uninstall);
272
for (let addon of addons) {
273
if (addon) {
274
try {
275
await addon.uninstall();
276
} catch (e) {
277
// This can fail for add-ons that can't be uninstalled.
278
log.debug(`Add-on ID (${addon.id}) couldn't be uninstalled.`);
279
}
280
}
281
}
282
}
283
);
284
}
285
if ("Install" in param) {
286
runOncePerModification(
287
"extensionsInstall",
288
JSON.stringify(param.Install),
289
async () => {
290
await uninstallingPromise;
291
for (let location of param.Install) {
292
let uri;
293
try {
294
uri = Services.io.newURI(location);
295
} catch (e) {
296
// If it's not a URL, it's probably a file path.
297
// Assume location is a file path
298
// This is done for legacy support (old API)
299
try {
300
let xpiFile = new FileUtils.File(location);
301
uri = Services.io.newFileURI(xpiFile);
302
} catch (ex) {
303
log.error(`Invalid extension path location - ${location}`);
304
return;
305
}
306
}
307
installAddonFromURL(uri.spec);
308
}
309
}
310
);
311
}
312
if ("Locked" in param) {
313
for (let ID of param.Locked) {
314
manager.disallowFeature(`uninstall-extension:${ID}`);
315
manager.disallowFeature(`disable-extension:${ID}`);
316
}
317
}
318
},
319
},
320
321
ExtensionSettings: {
322
onBeforeAddons(manager, param) {
323
manager.setExtensionSettings(param);
324
},
325
},
326
327
ExtensionUpdate: {
328
onBeforeAddons(manager, param) {
329
if (!param) {
330
setAndLockPref("extensions.update.enabled", param);
331
}
332
},
333
},
334
335
InstallAddonsPermission: {
336
onBeforeUIStartup(manager, param) {
337
if ("Allow" in param) {
338
addAllowDenyPermissions("install", param.Allow, null);
339
}
340
if ("Default" in param) {
341
setAndLockPref("xpinstall.enabled", param.Default);
342
if (!param.Default) {
343
blockAboutPage(manager, "about:debugging");
344
manager.disallowFeature("xpinstall");
345
}
346
}
347
},
348
},
349
350
Preferences: {
351
onBeforeAddons(manager, param) {
352
for (let preference in param) {
353
setAndLockPref(preference, param[preference]);
354
}
355
},
356
},
357
358
Proxy: {
359
onBeforeAddons(manager, param) {
360
if (param.Locked) {
361
manager.disallowFeature("changeProxySettings");
362
ProxyPolicies.configureProxySettings(param, setAndLockPref);
363
} else {
364
ProxyPolicies.configureProxySettings(param, setDefaultPref);
365
}
366
},
367
},
368
369
RequestedLocales: {
370
onBeforeAddons(manager, param) {
371
if (Array.isArray(param)) {
372
Services.locale.requestedLocales = param;
373
} else {
374
Services.locale.requestedLocales = param.split(",");
375
}
376
},
377
},
378
379
SSLVersionMax: {
380
onBeforeAddons(manager, param) {
381
let tlsVersion;
382
switch (param) {
383
case "tls1":
384
tlsVersion = 1;
385
break;
386
case "tls1.1":
387
tlsVersion = 2;
388
break;
389
case "tls1.2":
390
tlsVersion = 3;
391
break;
392
case "tls1.3":
393
tlsVersion = 4;
394
break;
395
}
396
setAndLockPref("security.tls.version.max", tlsVersion);
397
},
398
},
399
400
SSLVersionMin: {
401
onBeforeAddons(manager, param) {
402
let tlsVersion;
403
switch (param) {
404
case "tls1":
405
tlsVersion = 1;
406
break;
407
case "tls1.1":
408
tlsVersion = 2;
409
break;
410
case "tls1.2":
411
tlsVersion = 3;
412
break;
413
case "tls1.3":
414
tlsVersion = 4;
415
break;
416
}
417
setAndLockPref("security.tls.version.min", tlsVersion);
418
},
419
},
420
};
421
422
/*
423
* ====================
424
* = HELPER FUNCTIONS =
425
* ====================
426
*
427
* The functions below are helpers to be used by several policies.
428
*/
429
430
/**
431
* setAndLockPref
432
*
433
* Sets the _default_ value of a pref, and locks it (meaning that
434
* the default value will always be returned, independent from what
435
* is stored as the user value).
436
* The value is only changed in memory, and not stored to disk.
437
*
438
* @param {string} prefName
439
* The pref to be changed
440
* @param {boolean,number,string} prefValue
441
* The value to set and lock
442
*/
443
function setAndLockPref(prefName, prefValue) {
444
setDefaultPref(prefName, prefValue, true);
445
}
446
447
/**
448
* setDefaultPref
449
*
450
* Sets the _default_ value of a pref and optionally locks it.
451
* The value is only changed in memory, and not stored to disk.
452
*
453
* @param {string} prefName
454
* The pref to be changed
455
* @param {boolean,number,string} prefValue
456
* The value to set
457
* @param {boolean} locked
458
* Optionally lock the pref
459
*/
460
function setDefaultPref(prefName, prefValue, locked = false) {
461
if (Services.prefs.prefIsLocked(prefName)) {
462
Services.prefs.unlockPref(prefName);
463
}
464
465
let defaults = Services.prefs.getDefaultBranch("");
466
467
switch (typeof prefValue) {
468
case "boolean":
469
defaults.setBoolPref(prefName, prefValue);
470
break;
471
472
case "number":
473
if (!Number.isInteger(prefValue)) {
474
throw new Error(`Non-integer value for ${prefName}`);
475
}
476
477
defaults.setIntPref(prefName, prefValue);
478
break;
479
480
case "string":
481
defaults.setStringPref(prefName, prefValue);
482
break;
483
}
484
485
if (locked) {
486
Services.prefs.lockPref(prefName);
487
}
488
}
489
490
/**
491
* addAllowDenyPermissions
492
*
493
* Helper function to call the permissions manager (Services.perms.add)
494
* for two arrays of URLs.
495
*
496
* @param {string} permissionName
497
* The name of the permission to change
498
* @param {array} allowList
499
* The list of URLs to be set as ALLOW_ACTION for the chosen permission.
500
* @param {array} blockList
501
* The list of URLs to be set as DENY_ACTION for the chosen permission.
502
*/
503
function addAllowDenyPermissions(permissionName, allowList, blockList) {
504
allowList = allowList || [];
505
blockList = blockList || [];
506
507
for (let origin of allowList) {
508
try {
509
Services.perms.addFromPrincipal(
510
Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin),
511
permissionName,
512
Ci.nsIPermissionManager.ALLOW_ACTION,
513
Ci.nsIPermissionManager.EXPIRE_POLICY
514
);
515
} catch (ex) {
516
log.error(`Added by default for ${permissionName} permission in the permission
517
manager - ${origin.href}`);
518
}
519
}
520
521
for (let origin of blockList) {
522
Services.perms.addFromPrincipal(
523
Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin),
524
permissionName,
525
Ci.nsIPermissionManager.DENY_ACTION,
526
Ci.nsIPermissionManager.EXPIRE_POLICY
527
);
528
}
529
}
530
531
/**
532
* runOnce
533
*
534
* Helper function to run a callback only once per policy.
535
*
536
* @param {string} actionName
537
* A given name which will be used to track if this callback has run.
538
* @param {Function} callback
539
* The callback to run only once.
540
*/
541
// eslint-disable-next-line no-unused-vars
542
function runOnce(actionName, callback) {
543
let prefName = `browser.policies.runonce.${actionName}`;
544
if (Services.prefs.getBoolPref(prefName, false)) {
545
log.debug(
546
`Not running action ${actionName} again because it has already run.`
547
);
548
return;
549
}
550
Services.prefs.setBoolPref(prefName, true);
551
callback();
552
}
553
554
/**
555
* runOncePerModification
556
*
557
* Helper function similar to runOnce. The difference is that runOnce runs the
558
* callback once when the policy is set, then never again.
559
* runOncePerModification runs the callback once each time the policy value
560
* changes from its previous value.
561
* If the callback that was passed is an async function, you can await on this
562
* function to await for the callback.
563
*
564
* @param {string} actionName
565
* A given name which will be used to track if this callback has run.
566
* This string will be part of a pref name.
567
* @param {string} policyValue
568
* The current value of the policy. This will be compared to previous
569
* values given to this function to determine if the policy value has
570
* changed. Regardless of the data type of the policy, this must be a
571
* string.
572
* @param {Function} callback
573
* The callback to be run when the pref value changes
574
* @returns Promise
575
* A promise that will resolve once the callback finishes running.
576
*
577
*/
578
async function runOncePerModification(actionName, policyValue, callback) {
579
let prefName = `browser.policies.runOncePerModification.${actionName}`;
580
let oldPolicyValue = Services.prefs.getStringPref(prefName, undefined);
581
if (policyValue === oldPolicyValue) {
582
log.debug(
583
`Not running action ${actionName} again because the policy's value is unchanged`
584
);
585
return Promise.resolve();
586
}
587
Services.prefs.setStringPref(prefName, policyValue);
588
return callback();
589
}
590
591
/**
592
* clearRunOnceModification
593
*
594
* Helper function that clears a runOnce policy.
595
*/
596
function clearRunOnceModification(actionName) {
597
let prefName = `browser.policies.runOncePerModification.${actionName}`;
598
Services.prefs.clearUserPref(prefName);
599
}
600
601
/**
602
* installAddonFromURL
603
*
604
* Helper function that installs an addon from a URL
605
* and verifies that the addon ID matches.
606
*/
607
function installAddonFromURL(url, extensionID) {
608
AddonManager.getInstallForURL(url, {
609
telemetryInfo: { source: "enterprise-policy" },
610
}).then(install => {
611
if (install.addon && install.addon.appDisabled) {
612
log.error(`Incompatible add-on - ${location}`);
613
install.cancel();
614
return;
615
}
616
let listener = {
617
/* eslint-disable-next-line no-shadow */
618
onDownloadEnded: install => {
619
if (extensionID && install.addon.id != extensionID) {
620
log.error(
621
`Add-on downloaded from ${url} had unexpected id (got ${
622
install.addon.id
623
} expected ${extensionID})`
624
);
625
install.removeListener(listener);
626
install.cancel();
627
}
628
if (install.addon && install.addon.appDisabled) {
629
log.error(`Incompatible add-on - ${url}`);
630
install.removeListener(listener);
631
install.cancel();
632
}
633
},
634
onDownloadFailed: () => {
635
install.removeListener(listener);
636
log.error(`Download failed - ${url}`);
637
clearRunOnceModification("extensionsInstall");
638
},
639
onInstallFailed: () => {
640
install.removeListener(listener);
641
log.error(`Installation failed - ${url}`);
642
},
643
onInstallEnded: () => {
644
install.removeListener(listener);
645
log.debug(`Installation succeeded - ${url}`);
646
},
647
};
648
install.addListener(listener);
649
install.install();
650
});
651
}
652
653
let gChromeURLSBlocked = false;
654
655
// If any about page is blocked, we block the loading of all
656
// chrome:// URLs in the browser window.
657
function blockAboutPage(manager, feature, neededOnContentProcess = false) {
658
manager.disallowFeature(feature, neededOnContentProcess);
659
if (!gChromeURLSBlocked) {
660
blockAllChromeURLs();
661
gChromeURLSBlocked = true;
662
}
663
}
664
665
let ChromeURLBlockPolicy = {
666
shouldLoad(contentLocation, loadInfo, mimeTypeGuess) {
667
let contentType = loadInfo.externalContentPolicyType;
668
if (
669
contentLocation.scheme == "chrome" &&
670
contentType == Ci.nsIContentPolicy.TYPE_DOCUMENT &&
671
loadInfo.loadingContext &&
672
loadInfo.loadingContext.baseURI == AppConstants.BROWSER_CHROME_URL &&
673
contentLocation.host != "mochitests" &&
674
contentLocation.host != "devtools"
675
) {
676
return Ci.nsIContentPolicy.REJECT_REQUEST;
677
}
678
return Ci.nsIContentPolicy.ACCEPT;
679
},
680
shouldProcess(contentLocation, loadInfo, mimeTypeGuess) {
681
return Ci.nsIContentPolicy.ACCEPT;
682
},
683
classDescription: "Policy Engine Content Policy",
684
contractID: "@mozilla-org/policy-engine-content-policy-service;1",
685
classID: Components.ID("{ba7b9118-cabc-4845-8b26-4215d2a59ed7}"),
686
QueryInterface: ChromeUtils.generateQI([Ci.nsIContentPolicy]),
687
createInstance(outer, iid) {
688
return this.QueryInterface(iid);
689
},
690
};
691
692
function blockAllChromeURLs() {
693
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
694
registrar.registerFactory(
695
ChromeURLBlockPolicy.classID,
696
ChromeURLBlockPolicy.classDescription,
697
ChromeURLBlockPolicy.contractID,
698
ChromeURLBlockPolicy
699
);
700
701
Services.catMan.addCategoryEntry(
702
"content-policy",
703
ChromeURLBlockPolicy.contractID,
704
ChromeURLBlockPolicy.contractID,
705
false,
706
true
707
);
708
}
709
710
function pemToBase64(pem) {
711
return pem
712
.replace(/-----BEGIN CERTIFICATE-----/, "")
713
.replace(/-----END CERTIFICATE-----/, "")
714
.replace(/[\r\n]/g, "");
715
}