Bug 791594 - Gray out content behind cross origin auth requests. r=pbz,Gijs,mconley

Differential Revision: https://phabricator.services.mozilla.com/D164440
This commit is contained in:
Hannah Peuckmann 2023-01-31 18:16:53 +00:00
Родитель b8e5fb26bb
Коммит cb19af7145
10 изменённых файлов: 191 добавлений и 0 удалений

Просмотреть файл

@ -322,6 +322,7 @@ class PromptParent extends JSWindowActorParent {
features: "resizable=no",
modalType: args.modalType,
allowFocusCheckbox: args.allowFocusCheckbox,
hideContent: args.isTopLevelCrossDomainAuth,
},
bag
).closedPromise;

Просмотреть файл

@ -1677,6 +1677,11 @@ toolbar[keyNav=true]:not([collapsed=true], [customizing=true]) toolbartabstop {
background-color: rgba(28, 27, 34, 0.45);
}
.dialogOverlay[hideContent="true"][topmost="true"],
#window-modal-dialog::backdrop {
background-color: var(--tabpanel-background-color);
}
/* For the window-modal dialog, the background is supplied by the HTML dialog
* backdrop, so the dialogOverlay background above "double backgrounds" - so
* we remove it here: */

Просмотреть файл

@ -9303,6 +9303,10 @@ class TabDialogBox {
* Set to true to keep the dialog open for same origin navigation.
* @param {Number} [aOptions.modalType] - The modal type to create the dialog for.
* By default, we show the dialog for tab prompts.
* @param {Boolean} [aOptions.hideContent] - When true, we are about to show a prompt that is requesting the
* users credentials for a toplevel load of a resource from a base domain different from the base domain of the currently loaded page.
* To avoid auth prompt spoofing (see bug 791594) we hide the current sites content
* (among other protection mechanisms, that are not handled here, see the bug for reference).
* @returns {Object} [result] Returns an object { closedPromise, dialog }.
* @returns {Promise} [result.closedPromise] Resolves once the dialog has been closed.
* @returns {SubDialog} [result.dialog] A reference to the opened SubDialog.
@ -9316,6 +9320,7 @@ class TabDialogBox {
keepOpenSameOriginNav,
modalType = null,
allowFocusCheckbox = false,
hideContent = false,
} = {},
...aParams
) {
@ -9358,6 +9363,7 @@ class TabDialogBox {
sizeTo,
closingCallback,
closedCallback: resolveClosed,
hideContent,
},
...aParams
);

Просмотреть файл

@ -0,0 +1,28 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
function handleRequest(request, response) {
let body;
// guest:guest
let expectedHeader = "Basic Z3Vlc3Q6Z3Vlc3Q=";
// correct login credentials provided
if (
request.hasHeader("Authorization") &&
request.getHeader("Authorization") == expectedHeader
) {
response.setStatusLine(request.httpVersion, 200, "OK, authorized");
response.setHeader("Content-Type", "text", false);
body = "success";
} else {
// incorrect credentials
response.setStatusLine(request.httpVersion, 401, "Unauthorized");
response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
response.setHeader("Content-Type", "text", false);
body = "failed";
}
response.bodyOutputStream.write(body, body.length);
}

Просмотреть файл

@ -1,4 +1,9 @@
[browser_abort_when_in_modal_state.js]
[browser_auth_spoofing_protection.js]
support-files =
redirect-crossDomain.html
redirect-sameDomain.html
auth-route.sjs
[browser_beforeunload_urlbar.js]
support-files = file_beforeunload_stop.html
[browser_closeTabSpecificPanels.js]

Просмотреть файл

@ -0,0 +1,95 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
let TEST_PATH = getRootDirectory(gTestPath).replace(
"chrome://mochitests/content",
"https://example.com"
);
const CROSS_DOMAIN_URL = TEST_PATH + "/redirect-crossDomain.html";
const SAME_DOMAIN_URL = TEST_PATH + "/redirect-sameDomain.html";
/**
* Opens a new tab with a url that ether redirects us cross or same domain
*
* @param {Boolean} doConfirmPrompt - true if we want to test the case when the user accepts the prompt,
* false if we want to test the case when the user cancels the prompt.
* @param {Boolean} crossDomain - if true we will open a url that redirects us to a cross domain url,
* if false, we will open a url that redirects us to a same domain url
*/
async function trigger401AndHandle(doConfirmPrompt, crossDomain) {
let url = crossDomain ? CROSS_DOMAIN_URL : SAME_DOMAIN_URL;
let dialogShown = waitForDialog(doConfirmPrompt, crossDomain);
await BrowserTestUtils.withNewTab(url, async function() {
await dialogShown;
});
await new Promise(resolve => {
Services.clearData.deleteData(
Ci.nsIClearDataService.CLEAR_AUTH_CACHE,
resolve
);
});
}
async function waitForDialog(doConfirmPrompt, crossDomain) {
await TestUtils.topicObserved("common-dialog-loaded");
let dialog = gBrowser.getTabDialogBox(gBrowser.selectedBrowser)
._tabDialogManager._topDialog;
let dialogDocument = dialog._frame.contentDocument;
if (crossDomain) {
Assert.equal(
dialog._overlay.getAttribute("hideContent"),
"true",
"Dialog overlay hides the current sites content"
);
} else {
Assert.equal(
dialog._overlay.getAttribute("hideContent"),
"",
"Dialog overlay does not hide the current sites content"
);
}
if (doConfirmPrompt) {
dialogDocument.getElementById("loginTextbox").value = "guest";
dialogDocument.getElementById("password1Textbox").value = "guest";
dialogDocument.getElementById("commonDialog").acceptDialog();
} else {
dialogDocument.getElementById("commonDialog").cancelDialog();
}
}
/**
* Tests that the 401 auth spoofing mechanisms apply if the 401 is from a different base domain than the current sites,
* canceling the prompt
*/
add_task(async function testCrossDomainCancel() {
await trigger401AndHandle(false, true);
});
/**
* Tests that the 401 auth spoofing mechanisms apply if the 401 is from a different base domain than the current sites,
* accepting the prompt
*/
add_task(async function testCrossDomainAccept() {
await trigger401AndHandle(true, true);
});
/**
* Tests that the 401 auth spoofing mechanisms are not triggered by a 401 within the same base domain as the current sites,
* canceling the prompt
*/
add_task(async function testSameDomainCancel() {
await trigger401AndHandle(false, false);
});
/**
* Tests that the 401 auth spoofing mechanisms are not triggered by a 401 within the same base domain as the current sites,
* accepting the prompt
*/
add_task(async function testSameDomainAccept() {
await trigger401AndHandle(true, false);
});

Просмотреть файл

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>example.com</title>
</head>
<body>
I am a friendly test page!
<script>
window.location.href="https://example.org:443/browser/browser/base/content/test/tabPrompts/auth-route.sjs";
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>example.com</title>
</head>
<body>
I am a friendly test page!
<script>
window.location.href="https://test1.example.com:443/browser/browser/base/content/test/tabPrompts/auth-route.sjs";
</script>
</body>
</html>

Просмотреть файл

@ -1118,6 +1118,24 @@ class ModalPrompter {
args.isInsecureAuth =
args.channel.URI.schemeIs("http") &&
!args.channel.loadInfo.isTopLevelLoad;
// whether we are going to prompt the user for their credentials for a different base domain.
// When true, auth prompt spoofing protection mechanisms will be triggered (see bug 791594).
args.isTopLevelCrossDomainAuth = false;
// We don't support auth prompt spoofing protections for sub resources and window prompts
if (
args.modalType == MODAL_TYPE_TAB &&
args.channel.loadInfo.isTopLevelLoad
) {
// check if this is a request from a third party
try {
args.isTopLevelCrossDomainAuth = this.browsingContext.currentWindowGlobal?.documentPrincipal?.isThirdPartyURI(
args.channel.URI
);
} catch (e) {
// isThirdPartyURI failes for about:/blob/data URIs
console.warn("nsPrompter: isThirdPartyURI failed: " + e);
}
}
} else {
args.promptPrincipal = this.browsingContext.window?.document.nodePrincipal;
}

Просмотреть файл

@ -989,6 +989,7 @@ export class SubDialogManager {
closedCallback,
allowDuplicateDialogs,
sizeTo,
hideContent,
} = {},
...aParams
) {
@ -1022,6 +1023,12 @@ export class SubDialogManager {
this._topLevelPrevActiveElement = doc.activeElement;
}
// Consumers may pass this flag to make the dialog overlay background opaque,
// effectively hiding the content behind it. For example,
// this is used by the prompt code to prevent certain http authentication spoofing scenarios.
if (hideContent) {
this._preloadDialog._overlay.setAttribute("hideContent", true);
}
this._dialogs.push(this._preloadDialog);
this._preloadDialog.open(
aURL,