mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 13:21:05 +00:00
Bug 1665062: HTTPS-Only: Upgraded website creating HTTP auth prompt gets interrupted by error-page r=necko-reviewers,dragana,JulianWels
Differential Revision: https://phabricator.services.mozilla.com/D91908
This commit is contained in:
parent
c5e3e23059
commit
d43cc4af7e
@ -12,7 +12,7 @@
|
||||
#include "nsHTTPSOnlyUtils.h"
|
||||
#include "nsIConsoleService.h"
|
||||
#include "nsIHttpChannel.h"
|
||||
#include "nsIHttpChannel.h"
|
||||
#include "nsIHttpChannelInternal.h"
|
||||
#include "nsIHttpsOnlyModePermission.h"
|
||||
#include "nsIPermissionManager.h"
|
||||
#include "nsIPrincipal.h"
|
||||
@ -421,14 +421,21 @@ TestHTTPAnswerRunnable::OnStartRequest(nsIRequest* aRequest) {
|
||||
}
|
||||
|
||||
// Check if the original top-level channel which https-only is trying
|
||||
// to upgrade is already in progress. If it is, then all good, if not
|
||||
// to upgrade is already in progress or if the channel is an auth channel.
|
||||
// If it is in progress or Auth is in progress, then all good, if not
|
||||
// then let's cancel that channel so we can dispaly the exception page.
|
||||
nsCOMPtr<nsIChannel> httpsOnlyChannel = mDocumentLoadListener->GetChannel();
|
||||
if (httpsOnlyChannel) {
|
||||
nsCOMPtr<nsILoadInfo> loadInfo = httpsOnlyChannel->LoadInfo();
|
||||
uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
|
||||
if (!(httpsOnlyStatus &
|
||||
nsILoadInfo::HTTPS_ONLY_TOP_LEVEL_LOAD_IN_PROGRESS)) {
|
||||
uint32_t topLevelLoadInProgress =
|
||||
loadInfo->GetHttpsOnlyStatus() &
|
||||
nsILoadInfo::HTTPS_ONLY_TOP_LEVEL_LOAD_IN_PROGRESS;
|
||||
|
||||
nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
|
||||
do_QueryInterface(httpsOnlyChannel);
|
||||
bool isAuthChannel = false;
|
||||
Unused << httpChannelInternal->GetIsAuthChannel(&isAuthChannel);
|
||||
if (!topLevelLoadInProgress && !isAuthChannel) {
|
||||
// Only really cancel the original top-level channel if it's
|
||||
// status is still NS_OK, otherwise it might have already
|
||||
// encountered some other error and was cancelled.
|
||||
|
@ -0,0 +1,16 @@
|
||||
// Custom *.sjs file specifically for the needs of Bug 1665062
|
||||
|
||||
function handleRequest(request, response) {
|
||||
// avoid confusing cache behaviors
|
||||
response.setHeader("Cache-Control", "no-cache", false);
|
||||
|
||||
if (request.scheme === "https") {
|
||||
response.setHeader("Content-Type", "text/html;charset=utf-8", false);
|
||||
response.setStatusLine(request.httpVersion, 401, "Unauthorized");
|
||||
response.setHeader("WWW-Authenticate", "Basic realm=\"bug1665062\"");
|
||||
return;
|
||||
}
|
||||
|
||||
// we should never get here; just in case, return something unexpected
|
||||
response.write("do'h");
|
||||
}
|
@ -14,3 +14,5 @@ scheme=https
|
||||
fail-if = xorigin
|
||||
[test_http_background_request.html]
|
||||
support-files = file_http_background_request.sjs
|
||||
[test_http_background_auth_request.html]
|
||||
support-files = file_http_background_auth_request.sjs
|
||||
|
@ -0,0 +1,100 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Bug 1665062 - HTTPS-Only: Do not cancel channel if auth is in progress</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
/*
|
||||
* Description of the test:
|
||||
* We send a top-level request which results in a '401 - Unauthorized' and ensure that the
|
||||
* http background request does not accidentally treat that request as a potential timeout.
|
||||
* We make sure that ther HTTPS-Only Mode Error Page does *NOT* show up.
|
||||
*/
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.requestFlakyTimeout("When Auth is in progress, HTTPS-Only page should not show up");
|
||||
SimpleTest.requestLongerTimeout(8);
|
||||
|
||||
const EXPECTED_KICK_OFF_REQUEST =
|
||||
"http://test1.example.com/tests/dom/security/test/https-only/file_http_background_auth_request.sjs?foo";
|
||||
const EXPECTED_UPGRADE_REQUEST = EXPECTED_KICK_OFF_REQUEST.replace("http://", "https://");
|
||||
let EXPECTED_BG_REQUEST = "http://test1.example.com/";
|
||||
let requestCounter = 0;
|
||||
|
||||
function examiner() {
|
||||
SpecialPowers.addObserver(this, "specialpowers-http-notify-request");
|
||||
}
|
||||
examiner.prototype = {
|
||||
observe(subject, topic, data) {
|
||||
if (topic !== "specialpowers-http-notify-request") {
|
||||
return;
|
||||
}
|
||||
// On Android we have other requests appear here as well. Let's make
|
||||
// sure we only evaluate requests triggered by the test.
|
||||
if (!data.startsWith("http://test1.example.com") &&
|
||||
!data.startsWith("https://test1.example.com")) {
|
||||
return;
|
||||
}
|
||||
++requestCounter;
|
||||
if (requestCounter == 1) {
|
||||
is(data, EXPECTED_KICK_OFF_REQUEST, "kick off request needs to be http");
|
||||
return;
|
||||
}
|
||||
if (requestCounter == 2) {
|
||||
is(data, EXPECTED_UPGRADE_REQUEST, "upgraded request needs to be https");
|
||||
return;
|
||||
}
|
||||
if (requestCounter == 3) {
|
||||
is(data, EXPECTED_BG_REQUEST, "background request needs to be http and no sensitive info");
|
||||
return;
|
||||
}
|
||||
ok(false, "we should never get here, but just in case");
|
||||
},
|
||||
remove() {
|
||||
SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
|
||||
}
|
||||
}
|
||||
window.BackgroundRequestExaminer = new examiner();
|
||||
|
||||
// https-only top-level background request occurs after 3 seconds, hence
|
||||
// we use 5 seconds to make sure the background request did not happen.
|
||||
function resolveAfter5Seconds() {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
async function runTests() {
|
||||
await SpecialPowers.pushPrefEnv({ set: [
|
||||
["dom.security.https_only_mode", true],
|
||||
["dom.security.https_only_mode_send_http_background_request", true],
|
||||
]});
|
||||
|
||||
let testWin = window.open(EXPECTED_KICK_OFF_REQUEST, "_blank");
|
||||
|
||||
// Give the Auth Process and background request some time before moving on.
|
||||
await resolveAfter5Seconds();
|
||||
is(requestCounter, 3, "three requests total (kickoff, upgraded, background)");
|
||||
|
||||
let wrappedWin = SpecialPowers.wrap(testWin);
|
||||
is(wrappedWin.location.href, "about:blank", "displaying authentication happens on about:blank");
|
||||
is(wrappedWin.document.body.innerHTML, "", "exception page should not be displayed");
|
||||
|
||||
testWin.close();
|
||||
|
||||
window.BackgroundRequestExaminer.remove();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
runTests();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -353,6 +353,11 @@ ClassifierDummyChannel::SetupFallbackChannel(const char* aFallbackKey) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ClassifierDummyChannel::GetIsAuthChannel(bool* aIsAuthChannel) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ClassifierDummyChannel::GetThirdPartyFlags(uint32_t* aThirdPartyFlags) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
|
@ -2340,6 +2340,9 @@ HttpChannelChild::SetupFallbackChannel(const char* aFallbackKey) {
|
||||
DROP_DEAD();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
HttpChannelChild::GetIsAuthChannel(bool* aIsAuthChannel) { DROP_DEAD(); }
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// HttpChannelChild::nsICacheInfoChannel
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -101,6 +101,7 @@ class HttpChannelChild final : public PHttpChannelChild,
|
||||
void DoDiagnosticAssertWhenOnStopNotCalledOnDestroy() override;
|
||||
// nsIHttpChannelInternal
|
||||
NS_IMETHOD SetupFallbackChannel(const char* aFallbackKey) override;
|
||||
NS_IMETHOD GetIsAuthChannel(bool* aIsAuthChannel) override;
|
||||
// nsISupportsPriority
|
||||
NS_IMETHOD SetPriority(int32_t value) override;
|
||||
// nsIClassOfService
|
||||
|
@ -552,6 +552,11 @@ InterceptedHttpChannel::SetupFallbackChannel(const char* aFallbackKey) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
InterceptedHttpChannel::GetIsAuthChannel(bool* aIsAuthChannel) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
InterceptedHttpChannel::SetPriority(int32_t aPriority) {
|
||||
mPriority = clamped<int32_t>(aPriority, INT16_MIN, INT16_MAX);
|
||||
|
@ -159,6 +159,9 @@ class InterceptedHttpChannel final
|
||||
NS_IMETHOD
|
||||
SetupFallbackChannel(const char* aFallbackKey) override;
|
||||
|
||||
NS_IMETHOD
|
||||
GetIsAuthChannel(bool* aIsAuthChannel) override;
|
||||
|
||||
NS_IMETHOD
|
||||
SetPriority(int32_t aPriority) override;
|
||||
|
||||
|
@ -1275,6 +1275,11 @@ TRRServiceChannel::SetupFallbackChannel(const char* aFallbackKey) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
TRRServiceChannel::GetIsAuthChannel(bool* aIsAuthChannel) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
TRRServiceChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) {
|
||||
mCallbacks = aCallbacks;
|
||||
|
@ -73,6 +73,7 @@ class TRRServiceChannel : public HttpBaseChannel,
|
||||
const nsAString& aURL,
|
||||
const nsAString& aContentType) override;
|
||||
NS_IMETHOD SetupFallbackChannel(const char* aFallbackKey) override;
|
||||
NS_IMETHOD GetIsAuthChannel(bool* aIsAuthChannel) override;
|
||||
|
||||
NS_IMETHOD SetNotificationCallbacks(
|
||||
nsIInterfaceRequestor* aCallbacks) override;
|
||||
|
@ -356,6 +356,7 @@ nsHttpChannel::nsHttpChannel()
|
||||
mCacheOpenWithPriority(false),
|
||||
mCacheQueueSizeWhenOpen(0),
|
||||
mCachedContentIsValid(false),
|
||||
mIsAuthChannel(false),
|
||||
mAuthRetryPending(false),
|
||||
mCachedContentIsPartial(false),
|
||||
mCacheOnlyMetadata(false),
|
||||
@ -2874,6 +2875,7 @@ nsresult nsHttpChannel::ContinueProcessResponse3(nsresult rv) {
|
||||
if (rv == NS_ERROR_IN_PROGRESS) {
|
||||
// authentication prompt has been invoked and result
|
||||
// is expected asynchronously
|
||||
mIsAuthChannel = true;
|
||||
mAuthRetryPending = true;
|
||||
if (httpStatus == 407 ||
|
||||
(mTransaction && mTransaction->ProxyConnectFailed()))
|
||||
@ -2903,6 +2905,7 @@ nsresult nsHttpChannel::ContinueProcessResponse3(nsresult rv) {
|
||||
}
|
||||
rv = ProcessNormal();
|
||||
} else {
|
||||
mIsAuthChannel = true;
|
||||
mAuthRetryPending = true; // see DoAuthRetry
|
||||
}
|
||||
break;
|
||||
@ -6221,6 +6224,7 @@ NS_IMETHODIMP nsHttpChannel::OnAuthAvailable() {
|
||||
// setting mAuthRetryPending flag and resuming the transaction
|
||||
// triggers process of throwing away the unauthenticated data already
|
||||
// coming from the network
|
||||
mIsAuthChannel = true;
|
||||
mAuthRetryPending = true;
|
||||
mProxyAuthPending = false;
|
||||
LOG(("Resuming the transaction, we got credentials from user"));
|
||||
@ -7248,6 +7252,12 @@ nsHttpChannel::SetupFallbackChannel(const char* aFallbackKey) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsHttpChannel::GetIsAuthChannel(bool* aIsAuthChannel) {
|
||||
*aIsAuthChannel = mIsAuthChannel;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsHttpChannel::SetChannelIsForDownload(bool aChannelIsForDownload) {
|
||||
if (aChannelIsForDownload) {
|
||||
|
@ -152,6 +152,7 @@ class nsHttpChannel final : public HttpBaseChannel,
|
||||
NS_IMETHOD GetEncodedBodySize(uint64_t* aEncodedBodySize) override;
|
||||
// nsIHttpChannelInternal
|
||||
NS_IMETHOD SetupFallbackChannel(const char* aFallbackKey) override;
|
||||
NS_IMETHOD GetIsAuthChannel(bool* aIsAuthChannel) override;
|
||||
NS_IMETHOD SetChannelIsForDownload(bool aChannelIsForDownload) override;
|
||||
NS_IMETHOD GetNavigationStartTimeStamp(TimeStamp* aTimeStamp) override;
|
||||
NS_IMETHOD SetNavigationStartTimeStamp(TimeStamp aTimeStamp) override;
|
||||
@ -652,6 +653,7 @@ class nsHttpChannel final : public HttpBaseChannel,
|
||||
uint32_t mCacheQueueSizeWhenOpen;
|
||||
|
||||
Atomic<bool, Relaxed> mCachedContentIsValid;
|
||||
Atomic<bool> mIsAuthChannel;
|
||||
Atomic<bool> mAuthRetryPending;
|
||||
|
||||
// state flags
|
||||
|
@ -92,6 +92,12 @@ interface nsIHttpChannelInternal : nsISupports
|
||||
*/
|
||||
[must_use] void setupFallbackChannel(in string aFallbackKey);
|
||||
|
||||
/**
|
||||
* Returns true in case this channel is used for auth;
|
||||
* (the response header includes 'www-authenticate').
|
||||
*/
|
||||
[noscript, must_use] readonly attribute bool isAuthChannel;
|
||||
|
||||
/**
|
||||
* This flag is set to force relevant cookies to be sent with this load
|
||||
* even if normally they wouldn't be.
|
||||
|
Loading…
Reference in New Issue
Block a user