browser_loginStatus.js (15883B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 XPCOMUtils.defineLazyServiceGetter( 8 this, 9 "IdentityCredentialStorageService", 10 "@mozilla.org/browser/identity-credential-storage-service;1", 11 Ci.nsIIdentityCredentialStorageService 12 ); 13 14 const TEST_URL = "https://example.com/"; 15 const TEST_XORIGIN_URL = "https://example.net/"; 16 const TEST_LOGIN_STATUS_BASE = 17 TEST_URL + 18 "browser/dom/credentialmanagement/identity/tests/browser/server_loginStatus.sjs?status="; 19 const TEST_LOGIN_STATUS_XORIGIN_BASE = 20 TEST_XORIGIN_URL + 21 "browser/dom/credentialmanagement/identity/tests/browser/server_loginStatus.sjs?status="; 22 23 /** 24 * Perform a test with a function that should change a page's login status. 25 * 26 * This function opens a new foreground tab, then calls stepFn with two arguments: 27 * the Browser of the new tab and the value that should be set as the login status. 28 * This repeats for various values of the header, making sure the status is correct 29 * after each instance. 30 * 31 * @param {Function(Browser, string) => Promise} stepFn - The function to update login status, 32 * @param {string} - An optional description describing the test case, used in assertion descriptions. 33 */ 34 async function login_logout_sequence(stepFn, desc = "") { 35 // Open a test page, get its principal 36 let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); 37 const principal = gBrowser.contentPrincipal; 38 39 // Make sure we don't have a starting permission 40 let permission = Services.perms.testPermissionFromPrincipal( 41 principal, 42 "self-reported-logged-in" 43 ); 44 Assert.equal( 45 permission, 46 Services.perms.UNKNOWN_ACTION, 47 "Permission correctly not initialized in test of " + desc 48 ); 49 50 // Try using a bad value for the argument 51 await stepFn(tab.linkedBrowser, "should-reject"); 52 permission = Services.perms.testPermissionFromPrincipal( 53 principal, 54 "self-reported-logged-in" 55 ); 56 Assert.equal( 57 permission, 58 Services.perms.UNKNOWN_ACTION, 59 "Permission not altered by bad enum value in test of " + desc 60 ); 61 62 // Try logging in 63 await stepFn(tab.linkedBrowser, "logged-in"); 64 permission = Services.perms.testPermissionFromPrincipal( 65 principal, 66 "self-reported-logged-in" 67 ); 68 Assert.equal( 69 permission, 70 Services.perms.ALLOW_ACTION, 71 "Permission stored correcty for `logged-in` in test of " + desc 72 ); 73 74 // Try logging out 75 await stepFn(tab.linkedBrowser, "logged-out"); 76 permission = Services.perms.testPermissionFromPrincipal( 77 principal, 78 "self-reported-logged-in" 79 ); 80 Assert.equal( 81 permission, 82 Services.perms.DENY_ACTION, 83 "Permission stored correcty for `logged-out` in test of " + desc 84 ); 85 86 // Try using a bad value for the argument, after it's already been set 87 await stepFn(tab.linkedBrowser, "should-reject"); 88 permission = Services.perms.testPermissionFromPrincipal( 89 principal, 90 "self-reported-logged-in" 91 ); 92 Assert.equal( 93 permission, 94 Services.perms.DENY_ACTION, 95 "Permission not altered by bad enum value in test of " + desc 96 ); 97 98 // Try logging in again 99 await stepFn(tab.linkedBrowser, "logged-in"); 100 permission = Services.perms.testPermissionFromPrincipal( 101 principal, 102 "self-reported-logged-in" 103 ); 104 Assert.equal( 105 permission, 106 Services.perms.ALLOW_ACTION, 107 "Permission stored correcty for `logged-in` in test of " + desc 108 ); 109 110 // Make sure we don't have any extra permissinons laying about 111 let permissions = Services.perms.getAllByTypes(["self-reported-logged-in"]); 112 Assert.equal( 113 permissions.length, 114 1, 115 "One permission must be left after all modifications in test of " + desc 116 ); 117 118 // Clear the permission 119 Services.perms.removeByType("self-reported-logged-in"); 120 121 // Close tabs. 122 await BrowserTestUtils.removeTab(tab); 123 } 124 125 /** 126 * Perform a test with a function that should NOT change a page's login status. 127 * 128 * This function opens a new foreground tab, then calls stepFn with two arguments: 129 * the Browser of the new tab and the value that should be set as the login status. 130 * Then it makes sure that no permission has been set for the login status. 131 * 132 * @param {Function(Browser, string) => Promise} stepFn - The function to update login status, 133 * @param {string} - An optional description describing the test case, used in assertion descriptions. 134 */ 135 async function login_doesnt_work(stepFn, desc = "") { 136 // Open a test page, get its principal 137 let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); 138 const principal = gBrowser.contentPrincipal; 139 140 // Make sure we don't have a starting permission 141 let permission = Services.perms.testPermissionFromPrincipal( 142 principal, 143 "self-reported-logged-in" 144 ); 145 Assert.equal( 146 permission, 147 Services.perms.UNKNOWN_ACTION, 148 "Permission correctly not initialized in test of " + desc 149 ); 150 151 // Try logging in 152 await stepFn(tab.linkedBrowser, "logged-in"); 153 permission = Services.perms.testPermissionFromPrincipal( 154 principal, 155 "self-reported-logged-in" 156 ); 157 Assert.equal( 158 permission, 159 Services.perms.UNKNOWN_ACTION, 160 "Permission not set for `logged-in` in test of " + desc 161 ); 162 163 // Make sure we don't have any extra permissinons laying about 164 let permissions = Services.perms.getAllByTypes(["self-reported-logged-in"]); 165 Assert.equal(permissions.length, 0, "No permission set in test of " + desc); 166 167 // Clear the permission 168 Services.perms.removeByType("self-reported-logged-in"); 169 170 // Close tabs. 171 await BrowserTestUtils.removeTab(tab); 172 } 173 174 // Function that we can use to set the status and return with a string for the different possible outcomes 175 async function setStatusInContent(status) { 176 try { 177 let result = await content.navigator.login.setStatus(status); 178 if (result === undefined) { 179 return "resolved with undefined"; 180 } 181 return "resolved with defined"; 182 } catch (err) { 183 if (err.name == "TypeError") { 184 return "rejected with TypeError"; 185 } 186 if (err.name == "SecurityError") { 187 return "rejected with SecurityError"; 188 } 189 return "rejected with other error"; 190 } 191 } 192 193 add_task(async function test_logiStatus_js() { 194 let setLoginStatusInJavascript = async function (browser, value) { 195 let loginResult = await SpecialPowers.spawn( 196 browser, 197 [value], 198 setStatusInContent 199 ); 200 if (value == "logged-in" || value == "logged-out") { 201 Assert.equal( 202 "resolved with undefined", 203 loginResult, 204 "Successful call resolves with `undefined`" 205 ); 206 } else { 207 Assert.equal( 208 loginResult, 209 "rejected with TypeError", 210 "Unsuccessful JS call rejects with TypeError" 211 ); 212 } 213 }; 214 215 await login_logout_sequence(setLoginStatusInJavascript, "javascript API"); 216 }); 217 218 add_task(async function test_loginStatus_js_frame() { 219 let setLoginStatusInSubframeJavascript = async function (browser, value) { 220 const iframeBC = await SpecialPowers.spawn( 221 browser, 222 [TEST_URL], 223 async url => { 224 const iframe = content.document.createElement("iframe"); 225 await new Promise(resolve => { 226 iframe.addEventListener("load", resolve, { once: true }); 227 iframe.src = url; 228 content.document.body.appendChild(iframe); 229 }); 230 231 return iframe.browsingContext; 232 } 233 ); 234 let loginResult = await SpecialPowers.spawn( 235 iframeBC, 236 [value], 237 setStatusInContent 238 ); 239 if (value == "logged-in" || value == "logged-out") { 240 Assert.equal( 241 "resolved with undefined", 242 loginResult, 243 "Successful call resolves with `undefined`" 244 ); 245 } else { 246 Assert.equal( 247 loginResult, 248 "rejected with TypeError", 249 "Unsuccessful JS call rejects with TypeError" 250 ); 251 } 252 }; 253 254 await login_logout_sequence( 255 setLoginStatusInSubframeJavascript, 256 "javascript API" 257 ); 258 }); 259 260 add_task(async function test_loginStatus_js_xorigin_frame() { 261 let setLoginStatusInSubframeJavascript = async function (browser, value) { 262 const iframeBC = await SpecialPowers.spawn( 263 browser, 264 [TEST_XORIGIN_URL], 265 async url => { 266 const iframe = content.document.createElement("iframe"); 267 await new Promise(resolve => { 268 iframe.addEventListener("load", resolve, { once: true }); 269 iframe.src = url; 270 content.document.body.appendChild(iframe); 271 }); 272 273 return iframe.browsingContext; 274 } 275 ); 276 let loginResult = await SpecialPowers.spawn( 277 iframeBC, 278 [value], 279 setStatusInContent 280 ); 281 if (value == "logged-in" || value == "logged-out") { 282 Assert.equal( 283 loginResult, 284 "rejected with SecurityError", 285 "Cross origin JS call with correct enum rejects with SecurityError" 286 ); 287 } else { 288 Assert.equal( 289 loginResult, 290 "rejected with TypeError", 291 "Unsuccessful JS call rejects with TypeError" 292 ); 293 } 294 }; 295 296 await login_doesnt_work(setLoginStatusInSubframeJavascript, "javascript API"); 297 }); 298 299 add_task(async function test_loginStatus_js_xorigin_ancestor_frame() { 300 let setLoginStatusInSubframeJavascript = async function (browser, value) { 301 const iframeBC = await SpecialPowers.spawn( 302 browser, 303 [TEST_XORIGIN_URL], 304 async url => { 305 const iframe = content.document.createElement("iframe"); 306 await new Promise(resolve => { 307 iframe.addEventListener("load", resolve, { once: true }); 308 iframe.src = url; 309 content.document.body.appendChild(iframe); 310 }); 311 312 return iframe.browsingContext; 313 } 314 ); 315 const innerIframeBC = await SpecialPowers.spawn( 316 iframeBC, 317 [TEST_URL], 318 async url => { 319 const iframe = content.document.createElement("iframe"); 320 await new Promise(resolve => { 321 iframe.addEventListener("load", resolve, { once: true }); 322 iframe.src = url; 323 content.document.body.appendChild(iframe); 324 }); 325 326 return iframe.browsingContext; 327 } 328 ); 329 let loginResult = await SpecialPowers.spawn( 330 innerIframeBC, 331 [value], 332 setStatusInContent 333 ); 334 if (value == "logged-in" || value == "logged-out") { 335 Assert.equal( 336 loginResult, 337 "rejected with SecurityError", 338 "Cross origin JS call with correct enum rejects with SecurityError" 339 ); 340 } else { 341 Assert.equal( 342 loginResult, 343 "rejected with TypeError", 344 "Unsuccessful JS call rejects with TypeError" 345 ); 346 } 347 }; 348 349 await login_doesnt_work(setLoginStatusInSubframeJavascript, "javascript API"); 350 }); 351 352 add_task(async function test_login_logout_document_headers() { 353 let setLoginStatusInDocumentHeader = async function (browser, value) { 354 let loaded = BrowserTestUtils.browserLoaded(browser, false, TEST_URL); 355 BrowserTestUtils.startLoadingURIString( 356 browser, 357 TEST_LOGIN_STATUS_BASE + value 358 ); 359 await loaded; 360 }; 361 362 await login_logout_sequence( 363 setLoginStatusInDocumentHeader, 364 "document redirect" 365 ); 366 }); 367 368 add_task(async function test_loginStatus_subresource_headers() { 369 let setLoginStatusViaHeader = async function (browser, value) { 370 await SpecialPowers.spawn( 371 browser, 372 [TEST_LOGIN_STATUS_BASE + value], 373 async function (url) { 374 await content.fetch(url); 375 } 376 ); 377 }; 378 379 await login_logout_sequence(setLoginStatusViaHeader, "subresource header"); 380 }); 381 382 add_task(async function test_loginStatus_xorigin_subresource_headers() { 383 let setLoginStatusViaHeader = async function (browser, value) { 384 await SpecialPowers.spawn( 385 browser, 386 [TEST_LOGIN_STATUS_XORIGIN_BASE + value], 387 async function (url) { 388 await content.fetch(url, { mode: "no-cors" }); 389 } 390 ); 391 }; 392 await login_doesnt_work( 393 setLoginStatusViaHeader, 394 "xorigin subresource header" 395 ); 396 }); 397 398 add_task( 399 async function test_loginStatus_xorigin_subdocument_subresource_headers() { 400 let setLoginStatusViaSubdocumentSubresource = async function ( 401 browser, 402 value 403 ) { 404 const iframeBC = await SpecialPowers.spawn( 405 browser, 406 [TEST_XORIGIN_URL], 407 async url => { 408 const iframe = content.document.createElement("iframe"); 409 await new Promise(resolve => { 410 iframe.addEventListener("load", resolve, { once: true }); 411 iframe.src = url; 412 content.document.body.appendChild(iframe); 413 }); 414 415 return iframe.browsingContext; 416 } 417 ); 418 await SpecialPowers.spawn( 419 iframeBC, 420 [TEST_LOGIN_STATUS_BASE + value], 421 async function (url) { 422 await content.fetch(url, { mode: "no-cors" }); 423 } 424 ); 425 }; 426 await login_doesnt_work( 427 setLoginStatusViaSubdocumentSubresource, 428 "xorigin subresource header in xorigin frame" 429 ); 430 } 431 ); 432 433 add_task( 434 async function test_loginStatus_xorigin_subdocument_xorigin_subresource_headers() { 435 let setLoginStatusViaSubdocumentSubresource = async function ( 436 browser, 437 value 438 ) { 439 const iframeBC = await SpecialPowers.spawn( 440 browser, 441 [TEST_XORIGIN_URL], 442 async url => { 443 const iframe = content.document.createElement("iframe"); 444 await new Promise(resolve => { 445 iframe.addEventListener("load", resolve, { once: true }); 446 iframe.src = url; 447 content.document.body.appendChild(iframe); 448 }); 449 450 return iframe.browsingContext; 451 } 452 ); 453 await SpecialPowers.spawn( 454 iframeBC, 455 [TEST_LOGIN_STATUS_XORIGIN_BASE + value], 456 async function (url) { 457 await content.fetch(url, { mode: "no-cors" }); 458 } 459 ); 460 }; 461 await login_doesnt_work( 462 setLoginStatusViaSubdocumentSubresource, 463 "xorigin subresource header in xorigin frame" 464 ); 465 } 466 ); 467 468 add_task(async function test_loginStatus_subdocument_subresource_headers() { 469 let setLoginStatusViaSubdocumentSubresource = async function ( 470 browser, 471 value 472 ) { 473 const iframeBC = await SpecialPowers.spawn( 474 browser, 475 [TEST_URL], 476 async url => { 477 const iframe = content.document.createElement("iframe"); 478 await new Promise(resolve => { 479 iframe.addEventListener("load", resolve, { once: true }); 480 iframe.src = url; 481 content.document.body.appendChild(iframe); 482 }); 483 484 return iframe.browsingContext; 485 } 486 ); 487 await SpecialPowers.spawn( 488 iframeBC, 489 [TEST_LOGIN_STATUS_BASE + value], 490 async function (url) { 491 await content.fetch(url, { mode: "no-cors" }); 492 } 493 ); 494 }; 495 await login_logout_sequence( 496 setLoginStatusViaSubdocumentSubresource, 497 "subresource header in frame" 498 ); 499 }); 500 501 add_task(async function test_loginStatus_js_disconnected_doc_crash() { 502 let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); 503 let complete = await SpecialPowers.spawn( 504 tab.linkedBrowser, 505 [], 506 async function () { 507 const doc1 = new content.Document(); 508 const iframe = content.document.createElementNS( 509 "http://www.w3.org/1999/xhtml", 510 "iframe" 511 ); 512 content.document.documentElement.appendChild(iframe); 513 const cw = iframe.contentWindow; 514 const element = content.document.querySelector("*"); 515 doc1.adoptNode(element); 516 517 try { 518 await cw.navigator.login.setStatus("logged-out"); 519 } catch (_) {} 520 try { 521 await cw.navigator.login.setStatus("logged-in"); 522 } catch (_) {} 523 return true; 524 } 525 ); 526 Assert.ok(complete, "Did not crash"); 527 await BrowserTestUtils.removeTab(tab); 528 });