Skip to content

Commit c7f4214

Browse files
musjjkhaneliman
authored andcommitted
firefox: new permission checks for extensions
Add two new options to customize how extension permissions are checked: - `extensions.exhaustivePermissions` Ensures that the permissions requested by all extensions managed by home-manager are authorized - `extensions.exactPermissions` When enabled, the user must authorize only the permissions that the extensions requests, not more nor less.
1 parent b27e551 commit c7f4214

File tree

4 files changed

+303
-17
lines changed

4 files changed

+303
-17
lines changed

modules/programs/firefox/mkFirefoxModule.nix

Lines changed: 79 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,35 @@ in
704704
type = types.bool;
705705
};
706706

707+
exhaustivePermissions = mkOption {
708+
description = ''
709+
When enabled, the user must authorize requested
710+
permissions for all extensions from
711+
{option}`${moduleName}.profiles.<profile>.extensions.packages`
712+
in
713+
{option}`${moduleName}.profiles.<profile>.extensions.settings.<extensionID>.permissions`
714+
'';
715+
default = false;
716+
example = true;
717+
type = types.bool;
718+
};
719+
720+
exactPermissions = mkOption {
721+
description = ''
722+
When enabled,
723+
{option}`${moduleName}.profiles.<profile>.extensions.settings.<extensionID>.permissions`
724+
must specify the exact set of permissions that the
725+
extension will request.
726+
727+
This means that if the authorized permissions are
728+
broader than what the extension requests, the
729+
assertion will fail.
730+
'';
731+
default = false;
732+
example = true;
733+
type = types.bool;
734+
};
735+
707736
settings = mkOption {
708737
default = { };
709738
example = literalExpression ''
@@ -801,39 +830,72 @@ in
801830
}
802831
]
803832
++ (builtins.concatMap (
804-
{ name, value }:
833+
{ addonId, meta, ... }:
805834
let
806-
packages = builtins.filter (pkg: pkg.addonId == name) config.extensions.packages;
807-
package = builtins.head packages;
808-
unauthorized = lib.subtractLists value.permissions package.meta.mozPermissions;
835+
permissions = config.extensions.settings.${addonId}.permissions or null;
836+
requireCheck = config.extensions.exhaustivePermissions || permissions != null;
837+
authorizedPermissions = lib.optionals (permissions != null) permissions;
838+
missingPermissions = lib.subtractLists authorizedPermissions meta.mozPermissions;
839+
redundantPermissions = lib.subtractLists meta.mozPermissions authorizedPermissions;
840+
checkSatisfied =
841+
if config.extensions.exactPermissions then
842+
missingPermissions == [ ] && redundantPermissions == [ ]
843+
else
844+
missingPermissions == [ ];
845+
errorMessage =
846+
if
847+
config.extensions.exactPermissions && missingPermissions != [ ] && redundantPermissions != [ ]
848+
then
849+
''
850+
Extension ${addonId} requests permissions that weren't
851+
authorized: ${builtins.toJSON missingPermissions}.
852+
Additionally, the following permissions were authorized,
853+
but extension ${addonId} did not request them:
854+
${builtins.toJSON redundantPermissions}.
855+
Consider adjusting the permissions in''
856+
else if config.extensions.exactPermissions && redundantPermissions != [ ] then
857+
''
858+
The following permissions were authorized, but extension
859+
${addonId} did not request them: ${builtins.toJSON redundantPermissions}.
860+
Consider removing the redundant permissions from''
861+
else
862+
''
863+
Extension ${addonId} requests permissions that weren't
864+
authorized: ${builtins.toJSON missingPermissions}.
865+
Consider adding the missing permissions to'';
809866
in
810867
[
811868
{
812-
assertion = value.permissions == null || length packages == 1;
869+
assertion = !requireCheck || checkSatisfied;
813870
message = ''
814-
Must have exactly one extension with addonId '${name}'
815-
in '${lib.showOption profilePath}.extensions.packages' but found ${toString (length packages)}.
816-
'';
817-
}
818-
819-
{
820-
assertion = value.permissions == null || length packages != 1 || unauthorized == [ ];
821-
message = ''
822-
Extension ${name} requests permissions that weren't
823-
authorized: ${builtins.toJSON unauthorized}.
824-
Consider adding the missing permissions to
871+
${errorMessage}
825872
'${
826873
lib.showAttrPath (
827874
profilePath
828875
++ [
829876
"extensions"
830-
name
877+
addonId
831878
]
832879
)
833880
}.permissions'.
834881
'';
835882
}
836883
]
884+
) config.extensions.packages)
885+
++ (builtins.concatMap (
886+
{ name, value }:
887+
let
888+
packages = builtins.filter (pkg: pkg.addonId == name) config.extensions.packages;
889+
in
890+
[
891+
{
892+
assertion = value.permissions == null || length packages == 1;
893+
message = ''
894+
Must have exactly one extension with addonId '${name}'
895+
in '${lib.showOption profilePath}.extensions.packages' but found ${toString (length packages)}.
896+
'';
897+
}
898+
]
837899
) (lib.attrsToList config.extensions.settings))
838900
++ config.bookmarks.assertions;
839901
};

tests/modules/programs/firefox/common.nix

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ builtins.mapAttrs
2020
"${name}-profiles-duplicate-ids" = ./profiles/duplicate-ids.nix;
2121
"${name}-profiles-extensions" = ./profiles/extensions;
2222
"${name}-profiles-extensions-assertions" = ./profiles/extensions/assertions.nix;
23+
"${name}-profiles-extensions-exhaustive" = ./profiles/extensions/exhaustive.nix;
24+
"${name}-profiles-extensions-exact" = ./profiles/extensions/exact.nix;
2325
"${name}-profiles-overwrite" = ./profiles/overwrite;
2426
"${name}-profiles-search" = ./profiles/search;
2527
"${name}-profiles-settings" = ./profiles/settings;
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
modulePath:
2+
{ config, lib, ... }:
3+
4+
let
5+
6+
firefoxMockOverlay = import ../../setup-firefox-mock-overlay.nix modulePath;
7+
8+
uBlockStubPkg = config.lib.test.mkStubPackage {
9+
name = "ublock-origin-dummy";
10+
extraAttrs = {
11+
addonId = "[email protected]";
12+
meta.mozPermissions = [
13+
"privacy"
14+
"storage"
15+
"tabs"
16+
];
17+
};
18+
};
19+
20+
sponsorBlockStubPkg = config.lib.test.mkStubPackage {
21+
name = "sponsorblock";
22+
extraAttrs = {
23+
addonId = "[email protected]";
24+
meta.mozPermissions = [
25+
"storage"
26+
"scripting"
27+
"https://sponsor.ajay.app/*"
28+
];
29+
};
30+
};
31+
32+
noscriptStubPkg = config.lib.test.mkStubPackage {
33+
name = "noscript";
34+
extraAttrs = {
35+
addonId = "{73a6fe31-595d-460b-a920-fcc0f8843232}";
36+
meta.mozPermissions = [
37+
"contextMenus"
38+
"storage"
39+
"tabs"
40+
];
41+
};
42+
};
43+
44+
bitwardenStubPkg = config.lib.test.mkStubPackage {
45+
name = "bitwarden";
46+
extraAttrs = {
47+
addonId = "{446900e4-71c2-419f-a6a7-df9c091e268b}";
48+
meta.mozPermissions = [
49+
"webNavigation"
50+
"webRequest"
51+
"webRequestBlocking"
52+
];
53+
};
54+
};
55+
in
56+
{
57+
imports = [ firefoxMockOverlay ];
58+
59+
config = lib.mkIf config.test.enableBig (
60+
lib.setAttrByPath modulePath {
61+
enable = true;
62+
profiles.extensions = {
63+
extensions = {
64+
packages = [
65+
uBlockStubPkg
66+
sponsorBlockStubPkg
67+
noscriptStubPkg
68+
bitwardenStubPkg
69+
];
70+
exactPermissions = true;
71+
settings = {
72+
${uBlockStubPkg.addonId} = {
73+
permissions = [
74+
"privacy"
75+
"storage"
76+
];
77+
};
78+
${sponsorBlockStubPkg.addonId} = {
79+
permissions = [
80+
"storage"
81+
"scripting"
82+
"https://sponsor.ajay.app/*"
83+
"<all_urls>"
84+
];
85+
};
86+
${noscriptStubPkg.addonId} = {
87+
permissions = [
88+
"storage"
89+
"tabs"
90+
"https://github.com/*"
91+
];
92+
};
93+
${bitwardenStubPkg.addonId} = {
94+
permissions = [
95+
"webNavigation"
96+
"webRequest"
97+
"webRequestBlocking"
98+
];
99+
};
100+
};
101+
};
102+
};
103+
}
104+
// {
105+
test.asserts.assertions.expected = [
106+
''
107+
Extension ${uBlockStubPkg.addonId} requests permissions that weren't
108+
authorized: ["tabs"].
109+
Consider adding the missing permissions to
110+
'${lib.showOption modulePath}.profiles.extensions.extensions."${uBlockStubPkg.addonId}".permissions'.
111+
''
112+
''
113+
The following permissions were authorized, but extension
114+
${sponsorBlockStubPkg.addonId} did not request them: ["<all_urls>"].
115+
Consider removing the redundant permissions from
116+
'${lib.showOption modulePath}.profiles.extensions.extensions."${sponsorBlockStubPkg.addonId}".permissions'.
117+
''
118+
''
119+
Extension ${noscriptStubPkg.addonId} requests permissions that weren't
120+
authorized: ["contextMenus"].
121+
Additionally, the following permissions were authorized,
122+
but extension ${noscriptStubPkg.addonId} did not request them:
123+
["https://github.com/*"].
124+
Consider adjusting the permissions in
125+
'${lib.showOption modulePath}.profiles.extensions.extensions."${noscriptStubPkg.addonId}".permissions'.
126+
''
127+
];
128+
}
129+
);
130+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
modulePath:
2+
{ config, lib, ... }:
3+
4+
let
5+
6+
firefoxMockOverlay = import ../../setup-firefox-mock-overlay.nix modulePath;
7+
8+
uBlockStubPkg = config.lib.test.mkStubPackage {
9+
name = "ublock-origin-dummy";
10+
extraAttrs = {
11+
addonId = "[email protected]";
12+
meta.mozPermissions = [
13+
"<all_urls>"
14+
"http://*/*"
15+
"https://github.com/*"
16+
];
17+
};
18+
};
19+
20+
sponsorBlockStubPkg = config.lib.test.mkStubPackage {
21+
name = "sponsorblock";
22+
extraAttrs = {
23+
addonId = "[email protected]";
24+
meta.mozPermissions = [
25+
"storage"
26+
"scripting"
27+
"https://sponsor.ajay.app/*"
28+
];
29+
};
30+
};
31+
32+
noscriptStubPkg = config.lib.test.mkStubPackage {
33+
name = "noscript";
34+
extraAttrs = {
35+
addonId = "{73a6fe31-595d-460b-a920-fcc0f8843232}";
36+
meta.mozPermissions = [
37+
"contextMenus"
38+
"storage"
39+
"tabs"
40+
];
41+
};
42+
};
43+
in
44+
{
45+
imports = [ firefoxMockOverlay ];
46+
47+
config = lib.mkIf config.test.enableBig (
48+
lib.setAttrByPath modulePath {
49+
enable = true;
50+
profiles.extensions = {
51+
extensions = {
52+
packages = [
53+
uBlockStubPkg
54+
sponsorBlockStubPkg
55+
noscriptStubPkg
56+
];
57+
exhaustivePermissions = true;
58+
settings = {
59+
${uBlockStubPkg.addonId} = {
60+
permissions = [
61+
"<all_urls>"
62+
"http://*/*"
63+
"https://github.com/*"
64+
];
65+
};
66+
${noscriptStubPkg.addonId} = {
67+
permissions = [
68+
"contextMenus"
69+
];
70+
};
71+
};
72+
};
73+
};
74+
}
75+
// {
76+
test.asserts.assertions.expected = [
77+
''
78+
Extension ${sponsorBlockStubPkg.addonId} requests permissions that weren't
79+
authorized: ["storage","scripting","https://sponsor.ajay.app/*"].
80+
Consider adding the missing permissions to
81+
'${lib.showOption modulePath}.profiles.extensions.extensions."${sponsorBlockStubPkg.addonId}".permissions'.
82+
''
83+
''
84+
Extension ${noscriptStubPkg.addonId} requests permissions that weren't
85+
authorized: ["storage","tabs"].
86+
Consider adding the missing permissions to
87+
'${lib.showOption modulePath}.profiles.extensions.extensions."${noscriptStubPkg.addonId}".permissions'.
88+
''
89+
];
90+
}
91+
);
92+
}

0 commit comments

Comments
 (0)