Policies.jsm 33.2 KB
Newer Older
1
2
3
4
5
6
/* 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/. */

"use strict";

7
8
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.import("resource://gre/modules/Services.jsm");
9
ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
10
11
12
XPCOMUtils.defineLazyServiceGetter(this, "gXulStore",
                                   "@mozilla.org/xul/xulstore;1",
                                   "nsIXULStore");
13

14
XPCOMUtils.defineLazyModuleGetters(this, {
15
  AddonManager: "resource://gre/modules/AddonManager.jsm",
16
  BookmarksPolicies: "resource:///modules/policies/BookmarksPolicies.jsm",
17
  CustomizableUI: "resource:///modules/CustomizableUI.jsm",
18
19
  ProxyPolicies: "resource:///modules/policies/ProxyPolicies.jsm",
  WebsiteFilter: "resource:///modules/policies/WebsiteFilter.jsm",
20
21
});

22
const PREF_LOGLEVEL           = "browser.policies.loglevel";
23
const BROWSER_DOCUMENT_URL    = AppConstants.BROWSER_CHROME_URL;
24
25

XPCOMUtils.defineLazyGetter(this, "log", () => {
26
  let { ConsoleAPI } = ChromeUtils.import("resource://gre/modules/Console.jsm", {});
27
28
29
30
31
32
33
34
35
  return new ConsoleAPI({
    prefix: "Policies.jsm",
    // tip: set maxLogLevel to "debug" and use log.debug() to create detailed
    // messages during development. See LOG_LEVELS in Console.jsm for details.
    maxLogLevel: "error",
    maxLogLevelPref: PREF_LOGLEVEL,
  });
});

36
var EXPORTED_SYMBOLS = ["Policies"];
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
/*
 * ============================
 * = POLICIES IMPLEMENTATIONS =
 * ============================
 *
 * The Policies object below is where the implementation for each policy
 * happens. An object for each policy should be defined, containing
 * callback functions that will be called by the engine.
 *
 * See the _callbacks object in EnterprisePolicies.js for the list of
 * possible callbacks and an explanation of each.
 *
 * Each callback will be called with two parameters:
 * - manager
 *   This is the EnterprisePoliciesManager singleton object from
 *   EnterprisePolicies.js
 *
 * - param
 *   The parameter defined for this policy in policies-schema.json.
 *   It will be different for each policy. It could be a boolean,
 *   a string, an array or a complex object. All parameters have
 *   been validated according to the schema, and no unknown
 *   properties will be present on them.
 *
 * The callbacks will be bound to their parent policy object.
 */
64
var Policies = {
65
66
67
  "AppUpdateURL": {
    onBeforeAddons(manager, param) {
      setDefaultPref("app.update.url", param.href);
68
    },
69
70
  },

71
72
73
74
75
76
77
78
79
80
81
  "Authentication": {
    onBeforeAddons(manager, param) {
      if ("SPNEGO" in param) {
        setAndLockPref("network.negotiate-auth.trusted-uris", param.SPNEGO.join(", "));
      }
      if ("Delegated" in param) {
        setAndLockPref("network.negotiate-auth.delegation-uris", param.Delegated.join(", "));
      }
      if ("NTLM" in param) {
        setAndLockPref("network.automatic-ntlm-auth.trusted-uris", param.NTLM.join(", "));
      }
82
83
84
85
86
87
88
89
      if ("AllowNonFQDN" in param) {
        if (param.AllowNonFQDN.NTLM) {
          setAndLockPref("network.automatic-ntlm-auth.allow-non-fqdn", param.AllowNonFQDN.NTLM);
        }
        if (param.AllowNonFQDN.SPNEGO) {
          setAndLockPref("network.negotiate-auth.allow-non-fqdn", param.AllowNonFQDN.SPNEGO);
        }
      }
90
    },
91
92
  },

93
94
95
  "BlockAboutAddons": {
    onBeforeUIStartup(manager, param) {
      if (param) {
96
        blockAboutPage(manager, "about:addons", true);
97
      }
98
    },
99
100
  },

101
  "BlockAboutConfig": {
102
    onBeforeUIStartup(manager, param) {
103
      if (param) {
104
        blockAboutPage(manager, "about:config");
105
        setAndLockPref("devtools.chrome.enabled", false);
106
      }
107
    },
108
  },
109

110
111
112
  "BlockAboutProfiles": {
    onBeforeUIStartup(manager, param) {
      if (param) {
113
        blockAboutPage(manager, "about:profiles");
114
      }
115
    },
116
117
  },

118
119
120
  "BlockAboutSupport": {
    onBeforeUIStartup(manager, param) {
      if (param) {
121
        blockAboutPage(manager, "about:support");
122
      }
123
    },
124
125
  },

126
127
128
  "Bookmarks": {
    onAllWindowsRestored(manager, param) {
      BookmarksPolicies.processBookmarks(param);
129
    },
130
131
  },

132
133
134
  "Certificates": {
    onBeforeAddons(manager, param) {
      if ("ImportEnterpriseRoots" in param) {
135
        setAndLockPref("security.enterprise_roots.enabled", param.ImportEnterpriseRoots);
136
      }
137
    },
138
139
  },

140
  "Cookies": {
141
    onBeforeUIStartup(manager, param) {
142
      addAllowDenyPermissions("cookie", param.Allow, param.Block);
143
144

      if (param.Block) {
145
        const hosts = param.Block.map(url => url.hostname).sort().join("\n");
146
147
        runOncePerModification("clearCookiesForBlockedHosts", hosts, () => {
          for (let blocked of param.Block) {
148
            Services.cookies.removeCookiesWithOriginAttributes("{}", blocked.hostname);
149
150
151
          }
        });
      }
152
153
154

      if (param.Default !== undefined ||
          param.AcceptThirdParty !== undefined ||
155
          param.RejectTracker !== undefined ||
156
157
158
159
160
          param.Locked) {
        const ACCEPT_COOKIES = 0;
        const REJECT_THIRD_PARTY_COOKIES = 1;
        const REJECT_ALL_COOKIES = 2;
        const REJECT_UNVISITED_THIRD_PARTY = 3;
161
        const REJECT_TRACKER = 4;
162
163
164
165
166

        let newCookieBehavior = ACCEPT_COOKIES;
        if (param.Default !== undefined && !param.Default) {
          newCookieBehavior = REJECT_ALL_COOKIES;
        } else if (param.AcceptThirdParty) {
167
          if (param.AcceptThirdParty == "never") {
168
169
170
171
            newCookieBehavior = REJECT_THIRD_PARTY_COOKIES;
          } else if (param.AcceptThirdParty == "from-visited") {
            newCookieBehavior = REJECT_UNVISITED_THIRD_PARTY;
          }
172
173
        } else if (param.RejectTracker !== undefined && param.RejectTracker) {
          newCookieBehavior = REJECT_TRACKER;
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
        }

        if (param.Locked) {
          setAndLockPref("network.cookie.cookieBehavior", newCookieBehavior);
        } else {
          setDefaultPref("network.cookie.cookieBehavior", newCookieBehavior);
        }
      }

      const KEEP_COOKIES_UNTIL_EXPIRATION = 0;
      const KEEP_COOKIES_UNTIL_END_OF_SESSION = 2;

      if (param.ExpireAtSessionEnd !== undefined || param.Locked) {
        let newLifetimePolicy = KEEP_COOKIES_UNTIL_EXPIRATION;
        if (param.ExpireAtSessionEnd) {
          newLifetimePolicy = KEEP_COOKIES_UNTIL_END_OF_SESSION;
        }

        if (param.Locked) {
          setAndLockPref("network.cookie.lifetimePolicy", newLifetimePolicy);
        } else {
          setDefaultPref("network.cookie.lifetimePolicy", newLifetimePolicy);
        }
      }
198
    },
199
200
  },

201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
  "DNSOverHTTPS": {
    onBeforeAddons(manager, param) {
      if ("Enabled" in param) {
        let mode = param.Enabled ? 2 : 5;
        if (param.Locked) {
          setAndLockPref("network.trr.mode", mode);
        } else {
          setDefaultPref("network.trr.mode", mode);
        }
      }
      if (param.ProviderURL) {
        if (param.Locked) {
          setAndLockPref("network.trr.uri", param.ProviderURL.href);
        } else {
          setDefaultPref("network.trr.uri", param.ProviderURL.href);
        }
      }
    },
  },

221
222
223
224
  "DisableAppUpdate": {
    onBeforeAddons(manager, param) {
      if (param) {
        manager.disallowFeature("appUpdate");
225
      }
226
    },
227
228
  },

229
  "DisableBuiltinPDFViewer": {
230
    onBeforeAddons(manager, param) {
231
      if (param) {
232
        setAndLockPref("pdfjs.disabled", true);
233
      }
234
    },
235
236
  },

237
238
239
240
241
242
243
  "DisableDeveloperTools": {
    onBeforeAddons(manager, param) {
      if (param) {
        setAndLockPref("devtools.policy.disabled", true);
        setAndLockPref("devtools.chrome.enabled", false);

        manager.disallowFeature("devtools");
244
245
246
        blockAboutPage(manager, "about:devtools");
        blockAboutPage(manager, "about:debugging");
        blockAboutPage(manager, "about:devtools-toolbox");
247
      }
248
    },
249
250
  },

251
252
253
254
255
  "DisableFeedbackCommands": {
    onBeforeUIStartup(manager, param) {
      if (param) {
        manager.disallowFeature("feedbackCommands");
      }
256
    },
257
258
  },

259
260
261
262
263
  "DisableFirefoxAccounts": {
    onBeforeAddons(manager, param) {
      if (param) {
        setAndLockPref("identity.fxaccounts.enabled", false);
      }
264
    },
265
266
  },

267
268
  "DisableFirefoxScreenshots": {
    onBeforeAddons(manager, param) {
269
      if (param) {
270
271
        setAndLockPref("extensions.screenshots.disabled", true);
      }
272
    },
273
274
  },

275
276
  "DisableFirefoxStudies": {
    onBeforeAddons(manager, param) {
277
      if (param) {
278
279
        manager.disallowFeature("Shield");
      }
280
    },
281
282
  },

283
284
285
  "DisableForgetButton": {
    onProfileAfterChange(manager, param) {
      if (param) {
286
        setAndLockPref("privacy.panicButton.enabled", false);
287
      }
288
    },
289
290
  },

291
292
  "DisableFormHistory": {
    onBeforeUIStartup(manager, param) {
293
      if (param) {
294
295
        setAndLockPref("browser.formfill.enable", false);
      }
296
    },
297
298
  },

299
300
301
302
303
  "DisableMasterPasswordCreation": {
    onBeforeUIStartup(manager, param) {
      if (param) {
        manager.disallowFeature("createMasterPassword");
      }
304
    },
305
306
  },

307
308
309
310
311
  "DisablePocket": {
    onBeforeAddons(manager, param) {
      if (param) {
        setAndLockPref("extensions.pocket.enabled", false);
      }
312
    },
313
314
  },

315
316
317
318
  "DisablePrivateBrowsing": {
    onBeforeAddons(manager, param) {
      if (param) {
        manager.disallowFeature("privatebrowsing");
319
        blockAboutPage(manager, "about:privatebrowsing", true);
320
321
        setAndLockPref("browser.privatebrowsing.autostart", false);
      }
322
    },
323
324
  },

325
326
327
328
329
330
  "DisableProfileImport": {
    onBeforeUIStartup(manager, param) {
      if (param) {
        manager.disallowFeature("profileImport");
        setAndLockPref("browser.newtabpage.activity-stream.migrationExpired", true);
      }
331
    },
332
333
  },

334
335
336
337
338
339
  "DisableProfileRefresh": {
    onBeforeUIStartup(manager, param) {
      if (param) {
        manager.disallowFeature("profileRefresh");
        setAndLockPref("browser.disableResetPrompt", true);
      }
340
    },
341
342
  },

343
344
345
346
347
  "DisableSafeMode": {
    onBeforeUIStartup(manager, param) {
      if (param) {
        manager.disallowFeature("safeMode");
      }
348
    },
349
350
  },

351
352
  "DisableSecurityBypass": {
    onBeforeUIStartup(manager, param) {
353
354
355
356
      if ("InvalidCertificate" in param) {
        setAndLockPref("security.certerror.hideAddException", param.InvalidCertificate);
      }

357
358
359
      if ("SafeBrowsing" in param) {
        setAndLockPref("browser.safebrowsing.allowOverride", !param.SafeBrowsing);
      }
360
    },
361
362
  },

363
364
365
  "DisableSetDesktopBackground": {
    onBeforeUIStartup(manager, param) {
      if (param) {
366
        manager.disallowFeature("setDesktopBackground");
367
      }
368
    },
369
370
371
  },

  "DisableSystemAddonUpdate": {
372
373
374
375
    onBeforeAddons(manager, param) {
      if (param) {
        manager.disallowFeature("SysAddonUpdate");
      }
376
    },
377
378
  },

379
380
381
382
383
  "DisableTelemetry": {
    onBeforeAddons(manager, param) {
      if (param) {
        setAndLockPref("datareporting.healthreport.uploadEnabled", false);
        setAndLockPref("datareporting.policy.dataSubmissionEnabled", false);
384
        blockAboutPage(manager, "about:telemetry");
385
      }
386
    },
387
388
  },

389
  "DisplayBookmarksToolbar": {
390
    onBeforeUIStartup(manager, param) {
391
392
393
394
395
396
397
      let value = (!param).toString();
      // This policy is meant to change the default behavior, not to force it.
      // If this policy was alreay applied and the user chose to re-hide the
      // bookmarks toolbar, do not show it again.
      runOncePerModification("displayBookmarksToolbar", value, () => {
        gXulStore.setValue(BROWSER_DOCUMENT_URL, "PersonalToolbar", "collapsed", value);
      });
398
    },
399
400
  },

401
  "DisplayMenuBar": {
402
    onBeforeUIStartup(manager, param) {
403
      let value = (!param).toString();
404
405
406
        // This policy is meant to change the default behavior, not to force it.
        // If this policy was alreay applied and the user chose to re-hide the
        // menu bar, do not show it again.
407
408
409
      runOncePerModification("displayMenuBar", value, () => {
        gXulStore.setValue(BROWSER_DOCUMENT_URL, "toolbar-menubar", "autohide", value);
      });
410
    },
411
412
  },

413
  "DontCheckDefaultBrowser": {
414
    onBeforeUIStartup(manager, param) {
415
      setAndLockPref("browser.shell.checkDefaultBrowser", false);
416
    },
417
418
  },

419
420
  "EnableTrackingProtection": {
    onBeforeUIStartup(manager, param) {
421
422
423
424
425
426
427
428
      if (param.Value) {
        if (param.Locked) {
          setAndLockPref("privacy.trackingprotection.enabled", true);
          setAndLockPref("privacy.trackingprotection.pbmode.enabled", true);
        } else {
          setDefaultPref("privacy.trackingprotection.enabled", true);
          setDefaultPref("privacy.trackingprotection.pbmode.enabled", true);
        }
429
      } else {
430
431
        setAndLockPref("privacy.trackingprotection.enabled", false);
        setAndLockPref("privacy.trackingprotection.pbmode.enabled", false);
432
      }
433
    },
434
435
  },

436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
  "Extensions": {
    onBeforeUIStartup(manager, param) {
      if ("Install" in param) {
        runOncePerModification("extensionsInstall", JSON.stringify(param.Install), () => {
          for (let location of param.Install) {
            let url;
            if (location.includes("://")) {
              // Assume location is an URI
              url = location;
            } else {
              // Assume location is a file path
              let xpiFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
              try {
                xpiFile.initWithPath(location);
              } catch (e) {
                log.error(`Invalid extension path location - ${location}`);
                continue;
              }
              url = Services.io.newFileURI(xpiFile).spec;
            }
456
457
            AddonManager.getInstallForURL(url, "application/x-xpinstall", null, null, null, null, null,
                                          {source: "enterprise-policy"}).then(install => {
458
459
460
461
462
              if (install.addon && install.addon.appDisabled) {
                log.error(`Incompatible add-on - ${location}`);
                install.cancel();
                return;
              }
463
              let listener = {
464
465
466
467
468
469
470
471
              /* eslint-disable-next-line no-shadow */
                onDownloadEnded: (install) => {
                  if (install.addon && install.addon.appDisabled) {
                    log.error(`Incompatible add-on - ${location}`);
                    install.removeListener(listener);
                    install.cancel();
                  }
                },
472
                onDownloadFailed: () => {
473
                  install.removeListener(listener);
474
475
476
                  log.error(`Download failed - ${location}`);
                },
                onInstallFailed: () => {
477
                  install.removeListener(listener);
478
479
480
                  log.error(`Installation failed - ${location}`);
                },
                onInstallEnded: () => {
481
                  install.removeListener(listener);
482
                  log.debug(`Installation succeeded - ${location}`);
483
                },
484
485
486
              };
              install.addListener(listener);
              install.install();
487
            });
488
489
490
491
          }
        });
      }
      if ("Uninstall" in param) {
492
493
494
495
496
497
498
499
500
        runOncePerModification("extensionsUninstall", JSON.stringify(param.Uninstall), async () => {
          let addons = await AddonManager.getAddonsByIDs(param.Uninstall);
          for (let addon of addons) {
            if (addon) {
              try {
                addon.uninstall();
              } catch (e) {
                // This can fail for add-ons that can't be uninstalled.
                // Just ignore.
501
502
              }
            }
503
          }
504
505
506
507
508
509
510
        });
      }
      if ("Locked" in param) {
        for (let ID of param.Locked) {
          manager.disallowFeature(`modify-extension:${ID}`);
        }
      }
511
    },
512
513
  },

514
  "FlashPlugin": {
515
    onBeforeUIStartup(manager, param) {
516
      addAllowDenyPermissions("plugin:flash", param.Allow, param.Block);
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534

      const FLASH_NEVER_ACTIVATE = 0;
      const FLASH_ASK_TO_ACTIVATE = 1;
      const FLASH_ALWAYS_ACTIVATE = 2;

      let flashPrefVal;
      if (param.Default === undefined) {
        flashPrefVal = FLASH_ASK_TO_ACTIVATE;
      } else if (param.Default) {
        flashPrefVal = FLASH_ALWAYS_ACTIVATE;
      } else {
        flashPrefVal = FLASH_NEVER_ACTIVATE;
      }
      if (param.Locked) {
        setAndLockPref("plugin.state.flash", flashPrefVal);
      } else if (param.Default !== undefined) {
        setDefaultPref("plugin.state.flash", flashPrefVal);
      }
535
    },
536
537
  },

538
539
540
541
542
  "HardwareAcceleration": {
    onBeforeAddons(manager, param) {
      if (!param) {
        setAndLockPref("layers.acceleration.disabled", true);
      }
543
    },
544
545
  },

546
547
548
549
550
  "Homepage": {
    onBeforeUIStartup(manager, param) {
      // |homepages| will be a string containing a pipe-separated ('|') list of
      // URLs because that is what the "Home page" section of about:preferences
      // (and therefore what the pref |browser.startup.homepage|) accepts.
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
      if (param.URL) {
        let homepages = param.URL.href;
        if (param.Additional && param.Additional.length > 0) {
          homepages += "|" + param.Additional.map(url => url.href).join("|");
        }
        if (param.Locked) {
          setAndLockPref("browser.startup.homepage", homepages);
          setAndLockPref("pref.browser.homepage.disable_button.current_page", true);
          setAndLockPref("pref.browser.homepage.disable_button.bookmark_page", true);
          setAndLockPref("pref.browser.homepage.disable_button.restore_default", true);
        } else {
          setDefaultPref("browser.startup.homepage", homepages);
          runOncePerModification("setHomepage", homepages, () => {
            Services.prefs.clearUserPref("browser.startup.homepage");
          });
        }
567
      }
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
      if (param.StartPage) {
        let prefValue;
        switch (param.StartPage) {
          case "none":
            prefValue = 0;
            break;
          case "homepage":
            prefValue = 1;
            break;
          case "previous-session":
            prefValue = 3;
            break;
        }
        if (param.Locked) {
          setAndLockPref("browser.startup.page", prefValue);
        } else {
          setDefaultPref("browser.startup.page", prefValue);
        }
586
      }
587
    },
588
589
  },

590
  "InstallAddonsPermission": {
591
    onBeforeUIStartup(manager, param) {
592
593
594
595
596
      if ("Allow" in param) {
        addAllowDenyPermissions("install", param.Allow, null);
      }
      if ("Default" in param) {
        setAndLockPref("xpinstall.enabled", param.Default);
597
        if (!param.Default) {
598
          blockAboutPage(manager, "about:debugging");
599
        }
600
      }
601
    },
602
603
  },

604
605
606
607
608
  "NoDefaultBookmarks": {
    onProfileAfterChange(manager, param) {
      if (param) {
        manager.disallowFeature("defaultBookmarks");
      }
609
    },
610
611
  },

612
613
614
  "OfferToSaveLogins": {
    onBeforeUIStartup(manager, param) {
      setAndLockPref("signon.rememberSignons", param);
615
    },
616
617
  },

618
619
  "OverrideFirstRunPage": {
    onProfileAfterChange(manager, param) {
620
      let url = param ? param.href : "";
621
      setAndLockPref("startup.homepage_welcome_url", url);
622
    },
623
624
  },

625
626
  "OverridePostUpdatePage": {
    onProfileAfterChange(manager, param) {
627
      let url = param ? param.href : "";
628
629
630
631
632
      setAndLockPref("startup.homepage_override_url", url);
      // The pref startup.homepage_override_url is only used
      // as a fallback when the update.xml file hasn't provided
      // a specific post-update URL.
      manager.disallowFeature("postUpdateCustomPage");
633
    },
634
635
  },

636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
  "Permissions": {
    onBeforeUIStartup(manager, param) {
      if (param.Camera) {
        addAllowDenyPermissions("camera", param.Camera.Allow, param.Camera.Block);
        setDefaultPermission("camera", param.Camera);
      }

      if (param.Microphone) {
        addAllowDenyPermissions("microphone", param.Microphone.Allow, param.Microphone.Block);
        setDefaultPermission("microphone", param.Microphone);
      }

      if (param.Location) {
        addAllowDenyPermissions("geo", param.Location.Allow, param.Location.Block);
        setDefaultPermission("geo", param.Location);
      }

      if (param.Notifications) {
        addAllowDenyPermissions("desktop-notification", param.Notifications.Allow, param.Notifications.Block);
        setDefaultPermission("desktop-notification", param.Notifications);
      }
657
    },
658
659
  },

660
  "PopupBlocking": {
661
    onBeforeUIStartup(manager, param) {
662
      addAllowDenyPermissions("popup", param.Allow, null);
663
664
665
666
667
668
669
670
671
672

      if (param.Locked) {
        let blockValue = true;
        if (param.Default !== undefined && !param.Default) {
          blockValue = false;
        }
        setAndLockPref("dom.disable_open_during_load", blockValue);
      } else if (param.Default !== undefined) {
        setDefaultPref("dom.disable_open_during_load", !!param.Default);
      }
673
    },
674
675
  },

676
677
678
679
680
681
682
683
  "Proxy": {
    onBeforeAddons(manager, param) {
      if (param.Locked) {
        manager.disallowFeature("changeProxySettings");
        ProxyPolicies.configureProxySettings(param, setAndLockPref);
      } else {
        ProxyPolicies.configureProxySettings(param, setDefaultPref);
      }
684
    },
685
  },
686

687
688
689
690
691
692
  "RequestedLocales": {
    onBeforeAddons(manager, param) {
      Services.locale.requestedLocales = param;
    },
  },

693
694
695
696
697
698
699
700
701
702
703
704
705
  "SanitizeOnShutdown": {
    onBeforeUIStartup(manager, param) {
      setAndLockPref("privacy.sanitize.sanitizeOnShutdown", param);
      if (param) {
        setAndLockPref("privacy.clearOnShutdown.cache", true);
        setAndLockPref("privacy.clearOnShutdown.cookies", true);
        setAndLockPref("privacy.clearOnShutdown.downloads", true);
        setAndLockPref("privacy.clearOnShutdown.formdata", true);
        setAndLockPref("privacy.clearOnShutdown.history", true);
        setAndLockPref("privacy.clearOnShutdown.sessions", true);
        setAndLockPref("privacy.clearOnShutdown.siteSettings", true);
        setAndLockPref("privacy.clearOnShutdown.offlineApps", true);
      }
706
    },
707
708
  },

709
710
711
712
713
714
715
716
717
718
719
720
721
  "SearchBar": {
    onAllWindowsRestored(manager, param) {
      // This policy is meant to change the default behavior, not to force it.
      // If this policy was already applied and the user chose move the search
      // bar, don't move it again.
      runOncePerModification("searchInNavBar", param, () => {
        if (param == "separate") {
          CustomizableUI.addWidgetToArea("search-container", CustomizableUI.AREA_NAVBAR,
          CustomizableUI.getPlacementOfWidget("urlbar-container").position + 1);
        } else if (param == "unified") {
          CustomizableUI.removeWidgetFromArea("search-container");
        }
      });
722
    },
723
724
  },

725
  "SearchEngines": {
726
727
728
729
730
    onBeforeUIStartup(manager, param) {
      if (param.PreventInstalls) {
        manager.disallowFeature("installSearchEngine", true);
      }
    },
731
732
    onAllWindowsRestored(manager, param) {
      Services.search.init(() => {
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
        if (param.Remove) {
          // Only rerun if the list of engine names has changed.
          runOncePerModification("removeSearchEngines",
                                 JSON.stringify(param.Remove),
                                 () => {
            for (let engineName of param.Remove) {
              let engine = Services.search.getEngineByName(engineName);
              if (engine) {
                try {
                  Services.search.removeEngine(engine);
                } catch (ex) {
                  log.error("Unable to remove the search engine", ex);
                }
              }
            }
          });
        }
750
751
752
753
754
755
756
757
758
        if (param.Add) {
          // Only rerun if the list of engine names has changed.
          let engineNameList = param.Add.map(engine => engine.Name);
          runOncePerModification("addSearchEngines",
                                 JSON.stringify(engineNameList),
                                 () => {
            for (let newEngine of param.Add) {
              let newEngineParameters = {
                template:    newEngine.URLTemplate,
759
                iconURL:     newEngine.IconURL ? newEngine.IconURL.href : null,
760
761
762
763
                alias:       newEngine.Alias,
                description: newEngine.Description,
                method:      newEngine.Method,
                suggestURL:  newEngine.SuggestURLTemplate,
764
                extensionID: "set-via-policy",
765
                queryCharset: "UTF-8",
766
767
768
769
770
771
772
773
774
775
776
              };
              try {
                Services.search.addEngineWithDetails(newEngine.Name,
                                                     newEngineParameters);
              } catch (ex) {
                log.error("Unable to add search engine", ex);
              }
            }
          });
        }
        if (param.Default) {
777
          runOncePerModification("setDefaultSearchEngine", param.Default, () => {
778
779
780
781
782
783
784
785
786
787
788
789
790
            let defaultEngine;
            try {
              defaultEngine = Services.search.getEngineByName(param.Default);
              if (!defaultEngine) {
                throw "No engine by that name could be found";
              }
            } catch (ex) {
              log.error(`Search engine lookup failed when attempting to set ` +
                        `the default engine. Requested engine was ` +
                        `"${param.Default}".`, ex);
            }
            if (defaultEngine) {
              try {
791
                Services.search.currentEngine = defaultEngine;
792
793
794
795
796
797
798
              } catch (ex) {
                log.error("Unable to set the default search engine", ex);
              }
            }
          });
        }
      });
799
    },
800
801
  },

802
803
804
  "SecurityDevices": {
    onProfileAfterChange(manager, param) {
      let securityDevices = param;
805
806
807
808
809
810
811
812
      let pkcs11db = Cc["@mozilla.org/security/pkcs11moduledb;1"].getService(Ci.nsIPKCS11ModuleDB);
      let moduleList = pkcs11db.listModules();
      for (let deviceName in securityDevices) {
        let foundModule = false;
        for (let module of moduleList) {
          if (module && module.libName === securityDevices[deviceName]) {
            foundModule = true;
            break;
813
814
          }
        }
815
816
817
818
819
820
821
822
823
824
        if (foundModule) {
          continue;
        }
        try {
          pkcs11db.addModule(deviceName, securityDevices[deviceName], 0, 0);
        } catch (ex) {
          log.error(`Unable to add security device ${deviceName}`);
          log.debug(ex);
        }
      }
825
826
827
    },
  },

828
829
830
  "WebsiteFilter": {
    onBeforeUIStartup(manager, param) {
      this.filter = new WebsiteFilter(param.Block || [], param.Exceptions || []);
831
    },
832
833
  },

834
};
835
836
837
838
839
840
841
842
843

/*
 * ====================
 * = HELPER FUNCTIONS =
 * ====================
 *
 * The functions below are helpers to be used by several policies.
 */

844
845
846
847
848
849
850
851
852
853
854
855
856
/**
 * setAndLockPref
 *
 * Sets the _default_ value of a pref, and locks it (meaning that
 * the default value will always be returned, independent from what
 * is stored as the user value).
 * The value is only changed in memory, and not stored to disk.
 *
 * @param {string} prefName
 *        The pref to be changed
 * @param {boolean,number,string} prefValue
 *        The value to set and lock
 */
857
858
859
860
861
function setAndLockPref(prefName, prefValue) {
  if (Services.prefs.prefIsLocked(prefName)) {
    Services.prefs.unlockPref(prefName);
  }

862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
  setDefaultPref(prefName, prefValue);

  Services.prefs.lockPref(prefName);
}

/**
 * setDefaultPref
 *
 * Sets the _default_ value of a pref.
 * The value is only changed in memory, and not stored to disk.
 *
 * @param {string} prefName
 *        The pref to be changed
 * @param {boolean,number,string} prefValue
 *        The value to set
 */
function setDefaultPref(prefName, prefValue) {
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
  let defaults = Services.prefs.getDefaultBranch("");

  switch (typeof(prefValue)) {
    case "boolean":
      defaults.setBoolPref(prefName, prefValue);
      break;

    case "number":
      if (!Number.isInteger(prefValue)) {
        throw new Error(`Non-integer value for ${prefName}`);
      }

      defaults.setIntPref(prefName, prefValue);
      break;

    case "string":
      defaults.setStringPref(prefName, prefValue);
      break;
  }
}
899

900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
/**
 * setDefaultPermission
 *
 * Helper function to set preferences appropriately for the policy
 *
 * @param {string} policyName
 *        The name of the policy to set
 * @param {object} policyParam
 *        The object containing param for the policy
 */
function setDefaultPermission(policyName, policyParam) {
  if ("BlockNewRequests" in policyParam) {
    let prefName = "permissions.default." + policyName;

    if (policyParam.BlockNewRequests) {
      if (policyParam.Locked) {
        setAndLockPref(prefName, 2);
      } else {
        setDefaultPref(prefName, 2);
      }
    } else if (policyParam.Locked) {
      setAndLockPref(prefName, 0);
    } else {
      setDefaultPref(prefName, 0);
    }
  }
}

928
929
930
931
932
933
934
935
936
937
938
939
940
/**
 * addAllowDenyPermissions
 *
 * Helper function to call the permissions manager (Services.perms.add)
 * for two arrays of URLs.
 *
 * @param {string} permissionName
 *        The name of the permission to change
 * @param {array} allowList
 *        The list of URLs to be set as ALLOW_ACTION for the chosen permission.
 * @param {array} blockList
 *        The list of URLs to be set as DENY_ACTION for the chosen permission.
 */
941
942
943
944
945
function addAllowDenyPermissions(permissionName, allowList, blockList) {
  allowList = allowList || [];
  blockList = blockList || [];

  for (let origin of allowList) {
946
    try {
947
      Services.perms.add(Services.io.newURI(origin.href),
948
949
950
951
952
                         permissionName,
                         Ci.nsIPermissionManager.ALLOW_ACTION,
                         Ci.nsIPermissionManager.EXPIRE_POLICY);
    } catch (ex) {
      log.error(`Added by default for ${permissionName} permission in the permission
953
      manager - ${origin.href}`);
954
    }
955
956
957
  }

  for (let origin of blockList) {
958
    Services.perms.add(Services.io.newURI(origin.href),
959
960
961
962
963
                       permissionName,
                       Ci.nsIPermissionManager.DENY_ACTION,
                       Ci.nsIPermissionManager.EXPIRE_POLICY);
  }
}
964
965
966
967
968
969
970
971
972
973
974

/**
 * runOnce
 *
 * Helper function to run a callback only once per policy.
 *
 * @param {string} actionName
 *        A given name which will be used to track if this callback has run.
 * @param {Functon} callback
 *        The callback to run only once.
 */
975
 // eslint-disable-next-line no-unused-vars
976
977
978
979
980
981
982
function runOnce(actionName, callback) {
  let prefName = `browser.policies.runonce.${actionName}`;
  if (Services.prefs.getBoolPref(prefName, false)) {
    log.debug(`Not running action ${actionName} again because it has already run.`);
    return;
  }
  Services.prefs.setBoolPref(prefName, true);
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
  callback();
}

/**
 * runOncePerModification
 *
 * Helper function similar to runOnce. The difference is that runOnce runs the
 * callback once when the policy is set, then never again.
 * runOncePerModification runs the callback once each time the policy value
 * changes from its previous value.
 *
 * @param {string} actionName
 *        A given name which will be used to track if this callback has run.
 *        This string will be part of a pref name.
 * @param {string} policyValue
 *        The current value of the policy. This will be compared to previous
 *        values given to this function to determine if the policy value has
 *        changed. Regardless of the data type of the policy, this must be a