test_SitePermissions.js (16373B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ 3 */ 4 "use strict"; 5 6 const { SitePermissions } = ChromeUtils.importESModule( 7 "resource:///modules/SitePermissions.sys.mjs" 8 ); 9 10 const RESIST_FINGERPRINTING_ENABLED = Services.prefs.getBoolPref( 11 "privacy.resistFingerprinting" 12 ); 13 const MIDI_ENABLED = Services.prefs.getBoolPref("dom.webmidi.enabled"); 14 15 const SPEAKER_SELECTION_ENABLED = Services.prefs.getBoolPref( 16 "media.setsinkid.enabled" 17 ); 18 19 const LOCAL_NETWORK_ACCESS_ENABLED = Services.prefs.getBoolPref( 20 "network.lna.blocking" 21 ); 22 23 add_task(async function testPermissionsListing() { 24 let expectedPermissions = [ 25 "autoplay-media", 26 "camera", 27 "cookie", 28 "desktop-notification", 29 "focus-tab-by-prompt", 30 "geo", 31 "install", 32 "microphone", 33 "popup", 34 "screen", 35 "shortcuts", 36 "persistent-storage", 37 "storage-access", 38 "xr", 39 "3rdPartyStorage", 40 "3rdPartyFrameStorage", 41 "open-protocol-handler", 42 ]; 43 if (RESIST_FINGERPRINTING_ENABLED) { 44 // Canvas permission should be hidden unless privacy.resistFingerprinting 45 // is true. 46 expectedPermissions.push("canvas"); 47 } 48 if (MIDI_ENABLED) { 49 // Should remove this checking and add it as default after it is fully pref'd-on. 50 expectedPermissions.push("midi"); 51 expectedPermissions.push("midi-sysex"); 52 } 53 if (SPEAKER_SELECTION_ENABLED) { 54 expectedPermissions.push("speaker"); 55 } 56 if (LOCAL_NETWORK_ACCESS_ENABLED) { 57 expectedPermissions.push("localhost"); 58 expectedPermissions.push("local-network"); 59 } 60 Assert.deepEqual( 61 SitePermissions.listPermissions().sort(), 62 expectedPermissions.sort(), 63 "Correct list of all permissions" 64 ); 65 }); 66 67 add_task(async function testGetAllByPrincipal() { 68 // check that it returns an empty array on an invalid principal 69 // like a principal with an about URI, which doesn't support site permissions 70 let wrongPrincipal = 71 Services.scriptSecurityManager.createContentPrincipalFromOrigin( 72 "about:config" 73 ); 74 Assert.deepEqual(SitePermissions.getAllByPrincipal(wrongPrincipal), []); 75 76 let principal = 77 Services.scriptSecurityManager.createContentPrincipalFromOrigin( 78 "https://example.com" 79 ); 80 Assert.deepEqual(SitePermissions.getAllByPrincipal(principal), []); 81 82 SitePermissions.setForPrincipal(principal, "camera", SitePermissions.ALLOW); 83 Assert.deepEqual(SitePermissions.getAllByPrincipal(principal), [ 84 { 85 id: "camera", 86 state: SitePermissions.ALLOW, 87 scope: SitePermissions.SCOPE_PERSISTENT, 88 }, 89 ]); 90 91 SitePermissions.setForPrincipal( 92 principal, 93 "microphone", 94 SitePermissions.ALLOW, 95 SitePermissions.SCOPE_SESSION 96 ); 97 98 Services.prefs.setBoolPref("network.lna.blocking", true); 99 100 SitePermissions.setForPrincipal( 101 principal, 102 "localhost", 103 SitePermissions.ALLOW, 104 SitePermissions.SCOPE_SESSION 105 ); 106 107 SitePermissions.setForPrincipal( 108 principal, 109 "local-network", 110 SitePermissions.ALLOW, 111 SitePermissions.SCOPE_SESSION 112 ); 113 114 SitePermissions.setForPrincipal( 115 principal, 116 "desktop-notification", 117 SitePermissions.BLOCK 118 ); 119 120 Assert.deepEqual(SitePermissions.getAllByPrincipal(principal), [ 121 { 122 id: "camera", 123 state: SitePermissions.ALLOW, 124 scope: SitePermissions.SCOPE_PERSISTENT, 125 }, 126 { 127 id: "microphone", 128 state: SitePermissions.ALLOW, 129 scope: SitePermissions.SCOPE_SESSION, 130 }, 131 { 132 id: "localhost", 133 state: SitePermissions.ALLOW, 134 scope: SitePermissions.SCOPE_SESSION, 135 }, 136 { 137 id: "local-network", 138 state: SitePermissions.ALLOW, 139 scope: SitePermissions.SCOPE_SESSION, 140 }, 141 { 142 id: "desktop-notification", 143 state: SitePermissions.BLOCK, 144 scope: SitePermissions.SCOPE_PERSISTENT, 145 }, 146 ]); 147 148 SitePermissions.removeFromPrincipal(principal, "microphone"); 149 Assert.deepEqual(SitePermissions.getAllByPrincipal(principal), [ 150 { 151 id: "camera", 152 state: SitePermissions.ALLOW, 153 scope: SitePermissions.SCOPE_PERSISTENT, 154 }, 155 { 156 id: "localhost", 157 state: SitePermissions.ALLOW, 158 scope: SitePermissions.SCOPE_SESSION, 159 }, 160 { 161 id: "local-network", 162 state: SitePermissions.ALLOW, 163 scope: SitePermissions.SCOPE_SESSION, 164 }, 165 { 166 id: "desktop-notification", 167 state: SitePermissions.BLOCK, 168 scope: SitePermissions.SCOPE_PERSISTENT, 169 }, 170 ]); 171 172 SitePermissions.removeFromPrincipal(principal, "camera"); 173 SitePermissions.removeFromPrincipal(principal, "desktop-notification"); 174 SitePermissions.removeFromPrincipal(principal, "localhost"); 175 SitePermissions.removeFromPrincipal(principal, "local-network"); 176 177 Assert.deepEqual(SitePermissions.getAllByPrincipal(principal), []); 178 179 Assert.equal(Services.prefs.getIntPref("permissions.default.shortcuts"), 0); 180 SitePermissions.setForPrincipal( 181 principal, 182 "shortcuts", 183 SitePermissions.BLOCK 184 ); 185 186 // Customized preference should have been enabled, but the default should not. 187 Assert.equal(Services.prefs.getIntPref("permissions.default.shortcuts"), 0); 188 Assert.deepEqual(SitePermissions.getAllByPrincipal(principal), [ 189 { 190 id: "shortcuts", 191 state: SitePermissions.BLOCK, 192 scope: SitePermissions.SCOPE_PERSISTENT, 193 }, 194 ]); 195 196 SitePermissions.removeFromPrincipal(principal, "shortcuts"); 197 Services.prefs.clearUserPref("permissions.default.shortcuts"); 198 Services.prefs.clearUserPref("network.lna.blocking"); 199 }); 200 201 add_task(async function testGetAvailableStates() { 202 Assert.deepEqual(SitePermissions.getAvailableStates("camera"), [ 203 SitePermissions.UNKNOWN, 204 SitePermissions.ALLOW, 205 SitePermissions.BLOCK, 206 ]); 207 208 // Test available states with a default permission set. 209 Services.prefs.setIntPref( 210 "permissions.default.camera", 211 SitePermissions.ALLOW 212 ); 213 Assert.deepEqual(SitePermissions.getAvailableStates("camera"), [ 214 SitePermissions.PROMPT, 215 SitePermissions.ALLOW, 216 SitePermissions.BLOCK, 217 ]); 218 Services.prefs.clearUserPref("permissions.default.camera"); 219 220 Assert.deepEqual(SitePermissions.getAvailableStates("cookie"), [ 221 SitePermissions.ALLOW, 222 SitePermissions.ALLOW_COOKIES_FOR_SESSION, 223 SitePermissions.BLOCK, 224 ]); 225 226 Assert.deepEqual(SitePermissions.getAvailableStates("popup"), [ 227 SitePermissions.ALLOW, 228 SitePermissions.BLOCK, 229 ]); 230 }); 231 232 add_task(async function testExactHostMatch() { 233 let principal = 234 Services.scriptSecurityManager.createContentPrincipalFromOrigin( 235 "https://example.com" 236 ); 237 let subPrincipal = 238 Services.scriptSecurityManager.createContentPrincipalFromOrigin( 239 "https://test1.example.com" 240 ); 241 242 let exactHostMatched = [ 243 "autoplay-media", 244 "desktop-notification", 245 "focus-tab-by-prompt", 246 "camera", 247 "localhost", 248 "local-network", 249 "microphone", 250 "screen", 251 "geo", 252 "xr", 253 "persistent-storage", 254 "open-protocol-handler", 255 ]; 256 if (RESIST_FINGERPRINTING_ENABLED) { 257 // Canvas permission should be hidden unless privacy.resistFingerprinting 258 // is true. 259 exactHostMatched.push("canvas"); 260 } 261 if (MIDI_ENABLED) { 262 // WebMIDI is only pref'd on in nightly. 263 // Should remove this checking and add it as default after it is fully pref-on. 264 exactHostMatched.push("midi"); 265 exactHostMatched.push("midi-sysex"); 266 } 267 if (SPEAKER_SELECTION_ENABLED) { 268 exactHostMatched.push("speaker"); 269 } 270 let nonExactHostMatched = [ 271 "cookie", 272 "popup", 273 "install", 274 "shortcuts", 275 "storage-access", 276 "3rdPartyStorage", 277 "3rdPartyFrameStorage", 278 ]; 279 280 let permissions = SitePermissions.listPermissions(); 281 for (let permission of permissions) { 282 SitePermissions.setForPrincipal( 283 principal, 284 permission, 285 SitePermissions.ALLOW 286 ); 287 288 if (exactHostMatched.includes(permission)) { 289 // Check that the sub-origin does not inherit the permission from its parent. 290 Assert.equal( 291 SitePermissions.getForPrincipal(subPrincipal, permission).state, 292 SitePermissions.getDefault(permission), 293 `${permission} should exact-host match` 294 ); 295 } else if (nonExactHostMatched.includes(permission)) { 296 // Check that the sub-origin does inherit the permission from its parent. 297 Assert.equal( 298 SitePermissions.getForPrincipal(subPrincipal, permission).state, 299 SitePermissions.ALLOW, 300 `${permission} should not exact-host match` 301 ); 302 } else { 303 Assert.ok( 304 false, 305 `Found an unknown permission ${permission} in exact host match test.` + 306 "Please add new permissions from SitePermissions.sys.mjs to this test." 307 ); 308 } 309 310 // Check that the permission can be made specific to the sub-origin. 311 SitePermissions.setForPrincipal( 312 subPrincipal, 313 permission, 314 SitePermissions.PROMPT 315 ); 316 Assert.equal( 317 SitePermissions.getForPrincipal(subPrincipal, permission).state, 318 SitePermissions.PROMPT 319 ); 320 Assert.equal( 321 SitePermissions.getForPrincipal(principal, permission).state, 322 SitePermissions.ALLOW 323 ); 324 325 SitePermissions.removeFromPrincipal(subPrincipal, permission); 326 SitePermissions.removeFromPrincipal(principal, permission); 327 } 328 }); 329 330 add_task(async function testDefaultPrefs() { 331 let principal = 332 Services.scriptSecurityManager.createContentPrincipalFromOrigin( 333 "https://example.com" 334 ); 335 336 // Check that without a pref the default return value is UNKNOWN. 337 Assert.deepEqual(SitePermissions.getForPrincipal(principal, "camera"), { 338 state: SitePermissions.UNKNOWN, 339 scope: SitePermissions.SCOPE_PERSISTENT, 340 }); 341 342 // Check that the default return value changed after setting the pref. 343 Services.prefs.setIntPref( 344 "permissions.default.camera", 345 SitePermissions.BLOCK 346 ); 347 Assert.deepEqual(SitePermissions.getForPrincipal(principal, "camera"), { 348 state: SitePermissions.BLOCK, 349 scope: SitePermissions.SCOPE_PERSISTENT, 350 }); 351 352 // Check that other permissions still return UNKNOWN. 353 Assert.deepEqual(SitePermissions.getForPrincipal(principal, "microphone"), { 354 state: SitePermissions.UNKNOWN, 355 scope: SitePermissions.SCOPE_PERSISTENT, 356 }); 357 358 Assert.deepEqual(SitePermissions.getForPrincipal(principal, "localhost"), { 359 state: SitePermissions.UNKNOWN, 360 scope: SitePermissions.SCOPE_PERSISTENT, 361 }); 362 363 Assert.deepEqual( 364 SitePermissions.getForPrincipal(principal, "local-network"), 365 { 366 state: SitePermissions.UNKNOWN, 367 scope: SitePermissions.SCOPE_PERSISTENT, 368 } 369 ); 370 371 // Check that the default return value changed after changing the pref. 372 Services.prefs.setIntPref( 373 "permissions.default.camera", 374 SitePermissions.ALLOW 375 ); 376 Assert.deepEqual(SitePermissions.getForPrincipal(principal, "camera"), { 377 state: SitePermissions.ALLOW, 378 scope: SitePermissions.SCOPE_PERSISTENT, 379 }); 380 381 // Check that the preference is ignored if there is a value. 382 SitePermissions.setForPrincipal(principal, "camera", SitePermissions.BLOCK); 383 Assert.deepEqual(SitePermissions.getForPrincipal(principal, "camera"), { 384 state: SitePermissions.BLOCK, 385 scope: SitePermissions.SCOPE_PERSISTENT, 386 }); 387 388 // The preference should be honored again, after resetting the permissions. 389 SitePermissions.removeFromPrincipal(principal, "camera"); 390 Assert.deepEqual(SitePermissions.getForPrincipal(principal, "camera"), { 391 state: SitePermissions.ALLOW, 392 scope: SitePermissions.SCOPE_PERSISTENT, 393 }); 394 395 // Should be UNKNOWN after clearing the pref. 396 Services.prefs.clearUserPref("permissions.default.camera"); 397 Assert.deepEqual(SitePermissions.getForPrincipal(principal, "camera"), { 398 state: SitePermissions.UNKNOWN, 399 scope: SitePermissions.SCOPE_PERSISTENT, 400 }); 401 }); 402 403 add_task(async function testCanvasPermission() { 404 let resistFingerprinting = Services.prefs.getBoolPref( 405 "privacy.resistFingerprinting", 406 false 407 ); 408 let principal = 409 Services.scriptSecurityManager.createContentPrincipalFromOrigin( 410 "https://example.com" 411 ); 412 413 SitePermissions.setForPrincipal(principal, "canvas", SitePermissions.ALLOW); 414 415 // Canvas permission is hidden when privacy.resistFingerprinting is false. 416 Services.prefs.setBoolPref("privacy.resistFingerprinting", false); 417 Assert.equal(SitePermissions.listPermissions().indexOf("canvas"), -1); 418 Assert.equal( 419 SitePermissions.getAllByPrincipal(principal).filter( 420 permission => permission.id === "canvas" 421 ).length, 422 0 423 ); 424 425 // Canvas permission is visible when privacy.resistFingerprinting is true. 426 Services.prefs.setBoolPref("privacy.resistFingerprinting", true); 427 Assert.notEqual(SitePermissions.listPermissions().indexOf("canvas"), -1); 428 Assert.notEqual( 429 SitePermissions.getAllByPrincipal(principal).filter( 430 permission => permission.id === "canvas" 431 ).length, 432 0 433 ); 434 435 SitePermissions.removeFromPrincipal(principal, "canvas"); 436 Services.prefs.setBoolPref( 437 "privacy.resistFingerprinting", 438 resistFingerprinting 439 ); 440 }); 441 442 add_task(async function testLocalHostPermission() { 443 let lnaEnabled = Services.prefs.getBoolPref("network.lna.blocking", false); 444 let principal = 445 Services.scriptSecurityManager.createContentPrincipalFromOrigin( 446 "https://example.com" 447 ); 448 449 SitePermissions.setForPrincipal( 450 principal, 451 "localhost", 452 SitePermissions.ALLOW 453 ); 454 455 Services.prefs.setBoolPref("network.lna.blocking", false); 456 Assert.ok( 457 !SitePermissions.listPermissions().includes("localhost"), 458 "No 'localhost' permission should be present" 459 ); 460 Assert.ok( 461 !SitePermissions.getAllByPrincipal(principal).some( 462 permission => permission.id === "localhost" 463 ), 464 "No 'localhost' permission should be present" 465 ); 466 467 Services.prefs.setBoolPref("network.lna.blocking", true); 468 Assert.ok( 469 SitePermissions.listPermissions().includes("localhost"), 470 "'localhost' should be in listPermissions when blocking is enabled" 471 ); 472 Assert.ok( 473 SitePermissions.getAllByPrincipal(principal).some( 474 permission => permission.id === "localhost" 475 ), 476 "'localhost' permission should be present for principal when blocking is enabled" 477 ); 478 479 SitePermissions.removeFromPrincipal(principal, "localhost"); 480 Services.prefs.setBoolPref("network.lna.blocking", lnaEnabled); 481 }); 482 483 add_task(async function testLocalNetworkPermission() { 484 let lnaEnabled = Services.prefs.getBoolPref("network.lna.blocking", false); 485 let principal = 486 Services.scriptSecurityManager.createContentPrincipalFromOrigin( 487 "https://example.com" 488 ); 489 490 SitePermissions.setForPrincipal( 491 principal, 492 "local-network", 493 SitePermissions.ALLOW 494 ); 495 496 Services.prefs.setBoolPref("network.lna.blocking", false); 497 Assert.ok( 498 !SitePermissions.listPermissions().includes("local-network"), 499 "'local-network' should not be in listPermissions when blocking is disabled" 500 ); 501 Assert.ok( 502 !SitePermissions.getAllByPrincipal(principal).some( 503 permission => permission.id === "local-network" 504 ), 505 "'local-network' permission should not be present for principal when blocking is disabled" 506 ); 507 508 Services.prefs.setBoolPref("network.lna.blocking", true); 509 Assert.ok( 510 SitePermissions.listPermissions().includes("local-network"), 511 "'local-network' should be in listPermissions when blocking is enabled" 512 ); 513 Assert.ok( 514 SitePermissions.getAllByPrincipal(principal).some( 515 permission => permission.id === "local-network" 516 ), 517 "'local-network' permission should be present for principal when blocking is enabled" 518 ); 519 520 SitePermissions.removeFromPrincipal(principal, "local-network"); 521 Services.prefs.setBoolPref("network.lna.blocking", lnaEnabled); 522 }); 523 524 add_task(async function testFilePermissions() { 525 let principal = 526 Services.scriptSecurityManager.createContentPrincipalFromOrigin( 527 "file:///example.js" 528 ); 529 Assert.deepEqual(SitePermissions.getAllByPrincipal(principal), []); 530 531 SitePermissions.setForPrincipal(principal, "camera", SitePermissions.ALLOW); 532 Assert.deepEqual(SitePermissions.getAllByPrincipal(principal), [ 533 { 534 id: "camera", 535 state: SitePermissions.ALLOW, 536 scope: SitePermissions.SCOPE_PERSISTENT, 537 }, 538 ]); 539 SitePermissions.removeFromPrincipal(principal, "camera"); 540 Assert.deepEqual(SitePermissions.getAllByPrincipal(principal), []); 541 });