diff --git a/modules/programs/firefox/mkFirefoxModule.nix b/modules/programs/firefox/mkFirefoxModule.nix index 01b4ae1c3..252566624 100644 --- a/modules/programs/firefox/mkFirefoxModule.nix +++ b/modules/programs/firefox/mkFirefoxModule.nix @@ -704,6 +704,35 @@ in type = types.bool; }; + exhaustivePermissions = mkOption { + description = '' + When enabled, the user must authorize requested + permissions for all extensions from + {option}`${moduleName}.profiles..extensions.packages` + in + {option}`${moduleName}.profiles..extensions.settings..permissions` + ''; + default = false; + example = true; + type = types.bool; + }; + + exactPermissions = mkOption { + description = '' + When enabled, + {option}`${moduleName}.profiles..extensions.settings..permissions` + must specify the exact set of permissions that the + extension will request. + + This means that if the authorized permissions are + broader than what the extension requests, the + assertion will fail. + ''; + default = false; + example = true; + type = types.bool; + }; + settings = mkOption { default = { }; example = literalExpression '' @@ -800,12 +829,63 @@ in ''; } ] + ++ (builtins.concatMap ( + { addonId, meta, ... }: + let + permissions = config.extensions.settings.${addonId}.permissions or null; + requireCheck = config.extensions.exhaustivePermissions || permissions != null; + authorizedPermissions = lib.optionals (permissions != null) permissions; + missingPermissions = lib.subtractLists authorizedPermissions meta.mozPermissions; + redundantPermissions = lib.subtractLists meta.mozPermissions authorizedPermissions; + checkSatisfied = + if config.extensions.exactPermissions then + missingPermissions == [ ] && redundantPermissions == [ ] + else + missingPermissions == [ ]; + errorMessage = + if + config.extensions.exactPermissions && missingPermissions != [ ] && redundantPermissions != [ ] + then + '' + Extension ${addonId} requests permissions that weren't + authorized: ${builtins.toJSON missingPermissions}. + Additionally, the following permissions were authorized, + but extension ${addonId} did not request them: + ${builtins.toJSON redundantPermissions}. + Consider adjusting the permissions in'' + else if config.extensions.exactPermissions && redundantPermissions != [ ] then + '' + The following permissions were authorized, but extension + ${addonId} did not request them: ${builtins.toJSON redundantPermissions}. + Consider removing the redundant permissions from'' + else + '' + Extension ${addonId} requests permissions that weren't + authorized: ${builtins.toJSON missingPermissions}. + Consider adding the missing permissions to''; + in + [ + { + assertion = !requireCheck || checkSatisfied; + message = '' + ${errorMessage} + '${ + lib.showAttrPath ( + profilePath + ++ [ + "extensions" + addonId + ] + ) + }.permissions'. + ''; + } + ] + ) config.extensions.packages) ++ (builtins.concatMap ( { name, value }: let packages = builtins.filter (pkg: pkg.addonId == name) config.extensions.packages; - package = builtins.head packages; - unauthorized = lib.subtractLists value.permissions package.meta.mozPermissions; in [ { @@ -815,24 +895,6 @@ in in '${lib.showOption profilePath}.extensions.packages' but found ${toString (length packages)}. ''; } - - { - assertion = value.permissions == null || length packages != 1 || unauthorized == [ ]; - message = '' - Extension ${name} requests permissions that weren't - authorized: ${builtins.toJSON unauthorized}. - Consider adding the missing permissions to - '${ - lib.showAttrPath ( - profilePath - ++ [ - "extensions" - name - ] - ) - }.permissions'. - ''; - } ] ) (lib.attrsToList config.extensions.settings)) ++ config.bookmarks.assertions; diff --git a/tests/modules/programs/firefox/common.nix b/tests/modules/programs/firefox/common.nix index f7f4a8cd0..14218705e 100644 --- a/tests/modules/programs/firefox/common.nix +++ b/tests/modules/programs/firefox/common.nix @@ -20,6 +20,8 @@ builtins.mapAttrs "${name}-profiles-duplicate-ids" = ./profiles/duplicate-ids.nix; "${name}-profiles-extensions" = ./profiles/extensions; "${name}-profiles-extensions-assertions" = ./profiles/extensions/assertions.nix; + "${name}-profiles-extensions-exhaustive" = ./profiles/extensions/exhaustive.nix; + "${name}-profiles-extensions-exact" = ./profiles/extensions/exact.nix; "${name}-profiles-overwrite" = ./profiles/overwrite; "${name}-profiles-search" = ./profiles/search; "${name}-profiles-settings" = ./profiles/settings; diff --git a/tests/modules/programs/firefox/profiles/extensions/exact.nix b/tests/modules/programs/firefox/profiles/extensions/exact.nix new file mode 100644 index 000000000..8fb4c1890 --- /dev/null +++ b/tests/modules/programs/firefox/profiles/extensions/exact.nix @@ -0,0 +1,130 @@ +modulePath: +{ config, lib, ... }: + +let + + firefoxMockOverlay = import ../../setup-firefox-mock-overlay.nix modulePath; + + uBlockStubPkg = config.lib.test.mkStubPackage { + name = "ublock-origin-dummy"; + extraAttrs = { + addonId = "uBlock0@raymondhill.net"; + meta.mozPermissions = [ + "privacy" + "storage" + "tabs" + ]; + }; + }; + + sponsorBlockStubPkg = config.lib.test.mkStubPackage { + name = "sponsorblock"; + extraAttrs = { + addonId = "sponsorBlocker@ajay.app"; + meta.mozPermissions = [ + "storage" + "scripting" + "https://sponsor.ajay.app/*" + ]; + }; + }; + + noscriptStubPkg = config.lib.test.mkStubPackage { + name = "noscript"; + extraAttrs = { + addonId = "{73a6fe31-595d-460b-a920-fcc0f8843232}"; + meta.mozPermissions = [ + "contextMenus" + "storage" + "tabs" + ]; + }; + }; + + bitwardenStubPkg = config.lib.test.mkStubPackage { + name = "bitwarden"; + extraAttrs = { + addonId = "{446900e4-71c2-419f-a6a7-df9c091e268b}"; + meta.mozPermissions = [ + "webNavigation" + "webRequest" + "webRequestBlocking" + ]; + }; + }; +in +{ + imports = [ firefoxMockOverlay ]; + + config = lib.mkIf config.test.enableBig ( + lib.setAttrByPath modulePath { + enable = true; + profiles.extensions = { + extensions = { + packages = [ + uBlockStubPkg + sponsorBlockStubPkg + noscriptStubPkg + bitwardenStubPkg + ]; + exactPermissions = true; + settings = { + ${uBlockStubPkg.addonId} = { + permissions = [ + "privacy" + "storage" + ]; + }; + ${sponsorBlockStubPkg.addonId} = { + permissions = [ + "storage" + "scripting" + "https://sponsor.ajay.app/*" + "" + ]; + }; + ${noscriptStubPkg.addonId} = { + permissions = [ + "storage" + "tabs" + "https://github.com/*" + ]; + }; + ${bitwardenStubPkg.addonId} = { + permissions = [ + "webNavigation" + "webRequest" + "webRequestBlocking" + ]; + }; + }; + }; + }; + } + // { + test.asserts.assertions.expected = [ + '' + Extension ${uBlockStubPkg.addonId} requests permissions that weren't + authorized: ["tabs"]. + Consider adding the missing permissions to + '${lib.showOption modulePath}.profiles.extensions.extensions."${uBlockStubPkg.addonId}".permissions'. + '' + '' + The following permissions were authorized, but extension + ${sponsorBlockStubPkg.addonId} did not request them: [""]. + Consider removing the redundant permissions from + '${lib.showOption modulePath}.profiles.extensions.extensions."${sponsorBlockStubPkg.addonId}".permissions'. + '' + '' + Extension ${noscriptStubPkg.addonId} requests permissions that weren't + authorized: ["contextMenus"]. + Additionally, the following permissions were authorized, + but extension ${noscriptStubPkg.addonId} did not request them: + ["https://github.com/*"]. + Consider adjusting the permissions in + '${lib.showOption modulePath}.profiles.extensions.extensions."${noscriptStubPkg.addonId}".permissions'. + '' + ]; + } + ); +} diff --git a/tests/modules/programs/firefox/profiles/extensions/exhaustive.nix b/tests/modules/programs/firefox/profiles/extensions/exhaustive.nix new file mode 100644 index 000000000..90a0f6665 --- /dev/null +++ b/tests/modules/programs/firefox/profiles/extensions/exhaustive.nix @@ -0,0 +1,92 @@ +modulePath: +{ config, lib, ... }: + +let + + firefoxMockOverlay = import ../../setup-firefox-mock-overlay.nix modulePath; + + uBlockStubPkg = config.lib.test.mkStubPackage { + name = "ublock-origin-dummy"; + extraAttrs = { + addonId = "uBlock0@raymondhill.net"; + meta.mozPermissions = [ + "" + "http://*/*" + "https://github.com/*" + ]; + }; + }; + + sponsorBlockStubPkg = config.lib.test.mkStubPackage { + name = "sponsorblock"; + extraAttrs = { + addonId = "sponsorBlocker@ajay.app"; + meta.mozPermissions = [ + "storage" + "scripting" + "https://sponsor.ajay.app/*" + ]; + }; + }; + + noscriptStubPkg = config.lib.test.mkStubPackage { + name = "noscript"; + extraAttrs = { + addonId = "{73a6fe31-595d-460b-a920-fcc0f8843232}"; + meta.mozPermissions = [ + "contextMenus" + "storage" + "tabs" + ]; + }; + }; +in +{ + imports = [ firefoxMockOverlay ]; + + config = lib.mkIf config.test.enableBig ( + lib.setAttrByPath modulePath { + enable = true; + profiles.extensions = { + extensions = { + packages = [ + uBlockStubPkg + sponsorBlockStubPkg + noscriptStubPkg + ]; + exhaustivePermissions = true; + settings = { + ${uBlockStubPkg.addonId} = { + permissions = [ + "" + "http://*/*" + "https://github.com/*" + ]; + }; + ${noscriptStubPkg.addonId} = { + permissions = [ + "contextMenus" + ]; + }; + }; + }; + }; + } + // { + test.asserts.assertions.expected = [ + '' + Extension ${sponsorBlockStubPkg.addonId} requests permissions that weren't + authorized: ["storage","scripting","https://sponsor.ajay.app/*"]. + Consider adding the missing permissions to + '${lib.showOption modulePath}.profiles.extensions.extensions."${sponsorBlockStubPkg.addonId}".permissions'. + '' + '' + Extension ${noscriptStubPkg.addonId} requests permissions that weren't + authorized: ["storage","tabs"]. + Consider adding the missing permissions to + '${lib.showOption modulePath}.profiles.extensions.extensions."${noscriptStubPkg.addonId}".permissions'. + '' + ]; + } + ); +}