test_SitePermissions_temporary.js (18712B)
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 TemporaryPermissions = SitePermissions._temporaryPermissions; 11 12 const PERM_A = "foo"; 13 const PERM_B = "bar"; 14 const PERM_C = "foobar"; 15 16 const BROWSER_A = createDummyBrowser("https://example.com/foo"); 17 const BROWSER_B = createDummyBrowser("https://example.org/foo"); 18 19 const EXPIRY_MS_A = 1000000; 20 const EXPIRY_MS_B = 1000001; 21 22 function createDummyBrowser(spec) { 23 let uri = Services.io.newURI(spec); 24 return { 25 currentURI: uri, 26 contentPrincipal: Services.scriptSecurityManager.createContentPrincipal( 27 uri, 28 {} 29 ), 30 dispatchEvent: () => {}, 31 ownerGlobal: { 32 CustomEvent: class CustomEvent {}, 33 }, 34 }; 35 } 36 37 function navigateDummyBrowser(browser, uri) { 38 // Callers may pass in either uri strings or nsIURI objects. 39 if (typeof uri == "string") { 40 uri = Services.io.newURI(uri); 41 } 42 browser.currentURI = uri; 43 browser.contentPrincipal = 44 Services.scriptSecurityManager.createContentPrincipal( 45 browser.currentURI, 46 {} 47 ); 48 } 49 50 /** 51 * Tests that temporary permissions with different block states are stored 52 * (set, overwrite, delete) correctly. 53 */ 54 add_task(async function testAllowBlock() { 55 // Set two temporary permissions on the same browser. 56 SitePermissions.setForPrincipal( 57 null, 58 PERM_A, 59 SitePermissions.ALLOW, 60 SitePermissions.SCOPE_TEMPORARY, 61 BROWSER_A, 62 EXPIRY_MS_A 63 ); 64 65 SitePermissions.setForPrincipal( 66 null, 67 PERM_B, 68 SitePermissions.BLOCK, 69 SitePermissions.SCOPE_TEMPORARY, 70 BROWSER_A, 71 EXPIRY_MS_A 72 ); 73 74 // Test that the permissions have been set correctly. 75 Assert.deepEqual( 76 SitePermissions.getForPrincipal(null, PERM_A, BROWSER_A), 77 { 78 state: SitePermissions.ALLOW, 79 scope: SitePermissions.SCOPE_TEMPORARY, 80 }, 81 "SitePermissions returns expected permission state for perm A." 82 ); 83 84 Assert.deepEqual( 85 SitePermissions.getForPrincipal(null, PERM_B, BROWSER_A), 86 { 87 state: SitePermissions.BLOCK, 88 scope: SitePermissions.SCOPE_TEMPORARY, 89 }, 90 "SitePermissions returns expected permission state for perm B." 91 ); 92 93 Assert.deepEqual( 94 TemporaryPermissions.get(BROWSER_A, PERM_A), 95 { 96 id: PERM_A, 97 state: SitePermissions.ALLOW, 98 scope: SitePermissions.SCOPE_TEMPORARY, 99 }, 100 "TemporaryPermissions returns expected permission state for perm A." 101 ); 102 103 Assert.deepEqual( 104 TemporaryPermissions.get(BROWSER_A, PERM_B), 105 { 106 id: PERM_B, 107 state: SitePermissions.BLOCK, 108 scope: SitePermissions.SCOPE_TEMPORARY, 109 }, 110 "TemporaryPermissions returns expected permission state for perm B." 111 ); 112 113 // Test internal data structure of TemporaryPermissions. 114 let entry = TemporaryPermissions._stateByBrowser.get(BROWSER_A); 115 ok(entry, "Should have an entry for browser A"); 116 ok( 117 !TemporaryPermissions._stateByBrowser.has(BROWSER_B), 118 "Should have no entry for browser B" 119 ); 120 121 let { browser, uriToPerm } = entry; 122 Assert.equal( 123 browser?.get(), 124 BROWSER_A, 125 "Entry should have a weak reference to the browser." 126 ); 127 128 ok(uriToPerm, "Entry should have uriToPerm object."); 129 Assert.equal(Object.keys(uriToPerm).length, 2, "uriToPerm has 2 entries."); 130 131 let permissionsA = uriToPerm[BROWSER_A.contentPrincipal.origin]; 132 let permissionsB = 133 uriToPerm[Services.eTLD.getBaseDomain(BROWSER_A.currentURI)]; 134 135 ok(permissionsA, "Allow should be keyed under origin"); 136 ok(permissionsB, "Block should be keyed under baseDomain"); 137 138 let permissionA = permissionsA[PERM_A]; 139 let permissionB = permissionsB[PERM_B]; 140 141 Assert.equal( 142 permissionA.state, 143 SitePermissions.ALLOW, 144 "Should have correct state" 145 ); 146 let expireTimeoutA = permissionA.expireTimeout; 147 Assert.ok( 148 Number.isInteger(expireTimeoutA), 149 "Should have valid expire timeout" 150 ); 151 152 Assert.equal( 153 permissionB.state, 154 SitePermissions.BLOCK, 155 "Should have correct state" 156 ); 157 let expireTimeoutB = permissionB.expireTimeout; 158 Assert.ok( 159 Number.isInteger(expireTimeoutB), 160 "Should have valid expire timeout" 161 ); 162 163 // Overwrite permission A. 164 SitePermissions.setForPrincipal( 165 null, 166 PERM_A, 167 SitePermissions.ALLOW, 168 SitePermissions.SCOPE_TEMPORARY, 169 BROWSER_A, 170 EXPIRY_MS_B 171 ); 172 173 Assert.notEqual( 174 permissionsA[PERM_A].expireTimeout, 175 expireTimeoutA, 176 "Overwritten permission A should have new timer" 177 ); 178 179 // Overwrite permission B - this time with a non-block state which means it 180 // should be keyed by origin now. 181 SitePermissions.setForPrincipal( 182 null, 183 PERM_B, 184 SitePermissions.ALLOW, 185 SitePermissions.SCOPE_TEMPORARY, 186 BROWSER_A, 187 EXPIRY_MS_A 188 ); 189 190 let baseDomainEntry = 191 uriToPerm[Services.eTLD.getBaseDomain(BROWSER_A.currentURI)]; 192 Assert.ok( 193 !baseDomainEntry || !baseDomainEntry[PERM_B], 194 "Should not longer have baseDomain permission entry" 195 ); 196 197 permissionsB = uriToPerm[BROWSER_A.contentPrincipal.origin]; 198 permissionB = permissionsB[PERM_B]; 199 Assert.ok( 200 permissionsB && permissionB, 201 "Overwritten permission should be keyed under origin" 202 ); 203 Assert.equal( 204 permissionB.state, 205 SitePermissions.ALLOW, 206 "Should have correct updated state" 207 ); 208 Assert.notEqual( 209 permissionB.expireTimeout, 210 expireTimeoutB, 211 "Overwritten permission B should have new timer" 212 ); 213 214 // Remove permissions 215 SitePermissions.removeFromPrincipal(null, PERM_A, BROWSER_A); 216 SitePermissions.removeFromPrincipal(null, PERM_B, BROWSER_A); 217 218 // Test that permissions have been removed correctly 219 Assert.deepEqual( 220 SitePermissions.getForPrincipal(null, PERM_A, BROWSER_A), 221 { 222 state: SitePermissions.UNKNOWN, 223 scope: SitePermissions.SCOPE_PERSISTENT, 224 }, 225 "SitePermissions returns UNKNOWN state for A." 226 ); 227 228 Assert.deepEqual( 229 SitePermissions.getForPrincipal(null, PERM_B, BROWSER_A), 230 { 231 state: SitePermissions.UNKNOWN, 232 scope: SitePermissions.SCOPE_PERSISTENT, 233 }, 234 "SitePermissions returns UNKNOWN state for B." 235 ); 236 237 Assert.equal( 238 TemporaryPermissions.get(BROWSER_A, PERM_A), 239 null, 240 "TemporaryPermissions returns null for perm A." 241 ); 242 243 Assert.equal( 244 TemporaryPermissions.get(BROWSER_A, PERM_B), 245 null, 246 "TemporaryPermissions returns null for perm B." 247 ); 248 }); 249 250 /** 251 * Tests TemporaryPermissions#getAll. 252 */ 253 add_task(async function testGetAll() { 254 SitePermissions.setForPrincipal( 255 null, 256 PERM_A, 257 SitePermissions.ALLOW, 258 SitePermissions.SCOPE_TEMPORARY, 259 BROWSER_A, 260 EXPIRY_MS_A 261 ); 262 SitePermissions.setForPrincipal( 263 null, 264 PERM_B, 265 SitePermissions.BLOCK, 266 SitePermissions.SCOPE_TEMPORARY, 267 BROWSER_B, 268 EXPIRY_MS_A 269 ); 270 SitePermissions.setForPrincipal( 271 null, 272 PERM_C, 273 SitePermissions.PROMPT, 274 SitePermissions.SCOPE_TEMPORARY, 275 BROWSER_B, 276 EXPIRY_MS_A 277 ); 278 279 Assert.deepEqual(TemporaryPermissions.getAll(BROWSER_A), [ 280 { 281 id: PERM_A, 282 state: SitePermissions.ALLOW, 283 scope: SitePermissions.SCOPE_TEMPORARY, 284 }, 285 ]); 286 287 let permsBrowserB = TemporaryPermissions.getAll(BROWSER_B); 288 Assert.equal( 289 permsBrowserB.length, 290 2, 291 "There should be 2 permissions set for BROWSER_B" 292 ); 293 294 let permB; 295 let permC; 296 297 if (permsBrowserB[0].id == PERM_B) { 298 permB = permsBrowserB[0]; 299 permC = permsBrowserB[1]; 300 } else { 301 permB = permsBrowserB[1]; 302 permC = permsBrowserB[0]; 303 } 304 305 Assert.deepEqual(permB, { 306 id: PERM_B, 307 state: SitePermissions.BLOCK, 308 scope: SitePermissions.SCOPE_TEMPORARY, 309 }); 310 Assert.deepEqual(permC, { 311 id: PERM_C, 312 state: SitePermissions.PROMPT, 313 scope: SitePermissions.SCOPE_TEMPORARY, 314 }); 315 }); 316 317 /** 318 * Tests SitePermissions#clearTemporaryBlockPermissions and 319 * TemporaryPermissions#clear. 320 */ 321 add_task(async function testClear() { 322 SitePermissions.setForPrincipal( 323 null, 324 PERM_A, 325 SitePermissions.ALLOW, 326 SitePermissions.SCOPE_TEMPORARY, 327 BROWSER_A, 328 EXPIRY_MS_A 329 ); 330 SitePermissions.setForPrincipal( 331 null, 332 PERM_B, 333 SitePermissions.BLOCK, 334 SitePermissions.SCOPE_TEMPORARY, 335 BROWSER_A, 336 EXPIRY_MS_A 337 ); 338 SitePermissions.setForPrincipal( 339 null, 340 PERM_C, 341 SitePermissions.BLOCK, 342 SitePermissions.SCOPE_TEMPORARY, 343 BROWSER_B, 344 EXPIRY_MS_A 345 ); 346 347 let stateByBrowser = SitePermissions._temporaryPermissions._stateByBrowser; 348 349 Assert.ok(stateByBrowser.has(BROWSER_A), "Browser map should have BROWSER_A"); 350 Assert.ok(stateByBrowser.has(BROWSER_B), "Browser map should have BROWSER_B"); 351 352 SitePermissions.clearTemporaryBlockPermissions(BROWSER_A); 353 354 // We only clear block permissions, so we should still see PERM_A. 355 Assert.deepEqual( 356 SitePermissions.getForPrincipal(null, PERM_A, BROWSER_A), 357 { 358 state: SitePermissions.ALLOW, 359 scope: SitePermissions.SCOPE_TEMPORARY, 360 }, 361 "SitePermissions returns ALLOW state for PERM_A." 362 ); 363 // We don't clear BROWSER_B so it should still be there. 364 Assert.ok(stateByBrowser.has(BROWSER_B), "Should still have BROWSER_B."); 365 366 // Now clear allow permissions for A explicitly. 367 SitePermissions._temporaryPermissions.clear(BROWSER_A, SitePermissions.ALLOW); 368 369 Assert.ok(!stateByBrowser.has(BROWSER_A), "Should no longer have BROWSER_A."); 370 let browser = stateByBrowser.get(BROWSER_B); 371 Assert.ok(browser, "Should still have BROWSER_B"); 372 373 Assert.deepEqual( 374 SitePermissions.getForPrincipal(null, PERM_A, BROWSER_A), 375 { 376 state: SitePermissions.UNKNOWN, 377 scope: SitePermissions.SCOPE_PERSISTENT, 378 }, 379 "SitePermissions returns UNKNOWN state for PERM_A." 380 ); 381 Assert.deepEqual( 382 SitePermissions.getForPrincipal(null, PERM_B, BROWSER_A), 383 { 384 state: SitePermissions.UNKNOWN, 385 scope: SitePermissions.SCOPE_PERSISTENT, 386 }, 387 "SitePermissions returns UNKNOWN state for PERM_B." 388 ); 389 Assert.deepEqual( 390 SitePermissions.getForPrincipal(null, PERM_C, BROWSER_B), 391 { 392 state: SitePermissions.BLOCK, 393 scope: SitePermissions.SCOPE_TEMPORARY, 394 }, 395 "SitePermissions returns BLOCK state for PERM_C." 396 ); 397 398 SitePermissions._temporaryPermissions.clear(BROWSER_B); 399 400 Assert.ok(!stateByBrowser.has(BROWSER_B), "Should no longer have BROWSER_B."); 401 Assert.deepEqual( 402 SitePermissions.getForPrincipal(null, PERM_C, BROWSER_B), 403 { 404 state: SitePermissions.UNKNOWN, 405 scope: SitePermissions.SCOPE_PERSISTENT, 406 }, 407 "SitePermissions returns UNKNOWN state for PERM_C." 408 ); 409 }); 410 411 /** 412 * Tests that the temporary permissions setter calls the callback on permission 413 * expire with the associated browser. 414 */ 415 add_task(async function testCallbackOnExpiry() { 416 let promiseExpireA = new Promise(resolve => { 417 TemporaryPermissions.set( 418 BROWSER_A, 419 PERM_A, 420 SitePermissions.BLOCK, 421 100, 422 undefined, 423 resolve 424 ); 425 }); 426 let promiseExpireB = new Promise(resolve => { 427 TemporaryPermissions.set( 428 BROWSER_B, 429 PERM_A, 430 SitePermissions.BLOCK, 431 100, 432 BROWSER_B.contentPrincipal, 433 resolve 434 ); 435 }); 436 437 let [browserA, browserB] = await Promise.all([ 438 promiseExpireA, 439 promiseExpireB, 440 ]); 441 Assert.equal( 442 browserA, 443 BROWSER_A, 444 "Should get callback with browser on expiry for A" 445 ); 446 Assert.equal( 447 browserB, 448 BROWSER_B, 449 "Should get callback with browser on expiry for B" 450 ); 451 }); 452 453 /** 454 * Tests that the temporary permissions setter calls the callback on permission 455 * expire with the associated browser if the browser associated browser has 456 * changed after setting the permission. 457 */ 458 add_task(async function testCallbackOnExpiryUpdatedBrowser() { 459 let promiseExpire = new Promise(resolve => { 460 TemporaryPermissions.set( 461 BROWSER_A, 462 PERM_A, 463 SitePermissions.BLOCK, 464 200, 465 undefined, 466 resolve 467 ); 468 }); 469 470 TemporaryPermissions.copy(BROWSER_A, BROWSER_B); 471 472 let browser = await promiseExpire; 473 Assert.equal( 474 browser, 475 BROWSER_B, 476 "Should get callback with updated browser on expiry." 477 ); 478 }); 479 480 /** 481 * Tests that the permission setter throws an exception if an invalid expiry 482 * time is passed. 483 */ 484 add_task(async function testInvalidExpiryTime() { 485 let expectedError = /expireTime must be a positive integer/; 486 Assert.throws(() => { 487 SitePermissions.setForPrincipal( 488 null, 489 PERM_A, 490 SitePermissions.ALLOW, 491 SitePermissions.SCOPE_TEMPORARY, 492 BROWSER_A, 493 null 494 ); 495 }, expectedError); 496 Assert.throws(() => { 497 SitePermissions.setForPrincipal( 498 null, 499 PERM_A, 500 SitePermissions.ALLOW, 501 SitePermissions.SCOPE_TEMPORARY, 502 BROWSER_A, 503 0 504 ); 505 }, expectedError); 506 Assert.throws(() => { 507 SitePermissions.setForPrincipal( 508 null, 509 PERM_A, 510 SitePermissions.ALLOW, 511 SitePermissions.SCOPE_TEMPORARY, 512 BROWSER_A, 513 -100 514 ); 515 }, expectedError); 516 }); 517 518 /** 519 * Tests that we block by base domain but allow by origin. 520 */ 521 add_task(async function testTemporaryPermissionScope() { 522 let states = { 523 strict: { 524 same: [ 525 "https://example.com", 526 "https://example.com/sub/path", 527 "https://example.com:443", 528 "https://name:password@example.com", 529 ], 530 different: [ 531 "https://example.com", 532 "https://test1.example.com", 533 "http://example.com", 534 "http://example.org", 535 "file:///tmp/localPageA.html", 536 "file:///tmp/localPageB.html", 537 ], 538 }, 539 nonStrict: { 540 same: [ 541 "https://example.com", 542 "https://example.com/sub/path", 543 "https://example.com:443", 544 "https://test1.example.com", 545 "http://test2.test1.example.com", 546 "https://name:password@example.com", 547 "http://example.com", 548 ], 549 different: [ 550 "https://example.com", 551 "https://example.org", 552 "http://example.net", 553 ], 554 }, 555 }; 556 557 for (let state of [SitePermissions.BLOCK, SitePermissions.ALLOW]) { 558 let matchStrict = state != SitePermissions.BLOCK; 559 560 let lists = matchStrict ? states.strict : states.nonStrict; 561 562 Object.entries(lists).forEach(([type, list]) => { 563 let expectSet = type == "same"; 564 565 for (let uri of list) { 566 let browser = createDummyBrowser(uri); 567 SitePermissions.setForPrincipal( 568 null, 569 PERM_A, 570 state, 571 SitePermissions.SCOPE_TEMPORARY, 572 browser, 573 EXPIRY_MS_A 574 ); 575 576 ok(true, "origin:" + browser.contentPrincipal.origin); 577 578 for (let otherUri of list) { 579 if (uri == otherUri) { 580 continue; 581 } 582 navigateDummyBrowser(browser, otherUri); 583 ok(true, "new origin:" + browser.contentPrincipal.origin); 584 585 Assert.deepEqual( 586 SitePermissions.getForPrincipal(null, PERM_A, browser), 587 { 588 state: expectSet ? state : SitePermissions.UNKNOWN, 589 scope: expectSet 590 ? SitePermissions.SCOPE_TEMPORARY 591 : SitePermissions.SCOPE_PERSISTENT, 592 }, 593 `${ 594 state == SitePermissions.BLOCK ? "Block" : "Allow" 595 } Permission originally set for ${uri} should ${ 596 expectSet ? "not" : "also" 597 } be set for ${otherUri}.` 598 ); 599 } 600 601 SitePermissions._temporaryPermissions.clear(browser); 602 } 603 }); 604 } 605 }); 606 607 /** 608 * Tests that we can override the principal to use for keying temporary 609 * permissions. 610 */ 611 add_task(async function testOverrideBrowserURI() { 612 let testBrowser = createDummyBrowser("https://old.example.com/foo"); 613 let overrideURI = Services.io.newURI("https://test.example.org/test/path"); 614 SitePermissions.setForPrincipal( 615 Services.scriptSecurityManager.createContentPrincipal(overrideURI, {}), 616 PERM_A, 617 SitePermissions.ALLOW, 618 SitePermissions.SCOPE_TEMPORARY, 619 testBrowser, 620 EXPIRY_MS_A 621 ); 622 623 Assert.deepEqual( 624 SitePermissions.getForPrincipal(null, PERM_A, testBrowser), 625 { 626 state: SitePermissions.UNKNOWN, 627 scope: SitePermissions.SCOPE_PERSISTENT, 628 }, 629 "Permission should not be set for old URI." 630 ); 631 632 // "Navigate" to new URI 633 navigateDummyBrowser(testBrowser, overrideURI); 634 635 Assert.deepEqual( 636 SitePermissions.getForPrincipal(null, PERM_A, testBrowser), 637 { 638 state: SitePermissions.ALLOW, 639 scope: SitePermissions.SCOPE_TEMPORARY, 640 }, 641 "Permission should be set for new URI." 642 ); 643 644 SitePermissions._temporaryPermissions.clear(testBrowser); 645 }); 646 647 /** 648 * Tests that TemporaryPermissions does not throw for incompatible URI or 649 * browser.currentURI. 650 */ 651 add_task(async function testPermissionUnsupportedScheme() { 652 let aboutURI = Services.io.newURI("about:blank"); 653 654 // Incompatible override URI should not throw or store any permissions. 655 SitePermissions.setForPrincipal( 656 Services.scriptSecurityManager.createContentPrincipal(aboutURI, {}), 657 PERM_A, 658 SitePermissions.ALLOW, 659 SitePermissions.SCOPE_TEMPORARY, 660 BROWSER_A, 661 EXPIRY_MS_B 662 ); 663 Assert.ok( 664 SitePermissions._temporaryPermissions._stateByBrowser.has(BROWSER_A), 665 "Should not have stored permission for unsupported URI scheme." 666 ); 667 668 let browser = createDummyBrowser("https://example.com/"); 669 // Set a permission so we get an entry in the browser map. 670 SitePermissions.setForPrincipal( 671 null, 672 PERM_B, 673 SitePermissions.BLOCK, 674 SitePermissions.SCOPE_TEMPORARY, 675 browser 676 ); 677 678 // Change browser URI to about:blank. 679 navigateDummyBrowser(browser, aboutURI); 680 681 // Setting permission for browser with unsupported URI should not throw. 682 SitePermissions.setForPrincipal( 683 null, 684 PERM_A, 685 SitePermissions.ALLOW, 686 SitePermissions.SCOPE_TEMPORARY, 687 browser 688 ); 689 Assert.ok(true, "Set should not throw for unsupported URI"); 690 691 SitePermissions.removeFromPrincipal(null, PERM_A, browser); 692 Assert.ok(true, "Remove should not throw for unsupported URI"); 693 694 Assert.deepEqual( 695 SitePermissions.getForPrincipal(null, PERM_A, browser), 696 { 697 state: SitePermissions.UNKNOWN, 698 scope: SitePermissions.SCOPE_PERSISTENT, 699 }, 700 "Should return no permission set for unsupported URI." 701 ); 702 Assert.ok(true, "Get should not throw for unsupported URI"); 703 704 // getAll should not throw, but return empty permissions array. 705 let permissions = SitePermissions.getAllForBrowser(browser); 706 Assert.ok( 707 Array.isArray(permissions) && !permissions.length, 708 "Should return empty array for browser on about:blank" 709 ); 710 711 SitePermissions._temporaryPermissions.clear(browser); 712 });