зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
b8e5fb26bb
Коммит
cb19af7145
|
@ -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,
|
||||
|
|
Загрузка…
Ссылка в новой задаче