зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1820807 - redirect channel for auth retries. r=necko-reviewers,kershaw,valentin
Differential Revision: https://phabricator.services.mozilla.com/D182698
This commit is contained in:
Родитель
c465047858
Коммит
4c719e836d
|
@ -11100,6 +11100,12 @@
|
|||
value: true
|
||||
mirror: always
|
||||
|
||||
# whether to redirect the channel for auth redirects. See Bug 1820807
|
||||
- name: network.auth.use_redirect_for_retries
|
||||
type: RelaxedAtomicBool
|
||||
value: @IS_EARLY_BETA_OR_EARLIER@
|
||||
mirror: always
|
||||
|
||||
# Whether to allow truncated brotli with empty output. This also fixes
|
||||
# throwing an erroring when receiving highly compressed brotli files when
|
||||
# no content type is specified (Bug 1715401). This pref can be removed after
|
||||
|
|
|
@ -52,6 +52,14 @@ interface nsIChannelEventSink : nsISupports
|
|||
*/
|
||||
const unsigned long REDIRECT_STS_UPGRADE = 1 << 3;
|
||||
|
||||
/**
|
||||
* This is a internal redirect used to handle http authentication retries.
|
||||
* Upon receiving a 401 or 407 the channel gets redirected to a new channel
|
||||
* (same URL) that performs the request with the appropriate credentials.
|
||||
* Auth retry to the server must be made after redirecting to a new channel
|
||||
*/
|
||||
const unsigned long REDIRECT_AUTH_RETRY = 1 << 4;
|
||||
|
||||
/**
|
||||
* Called when a redirect occurs. This may happen due to an HTTP 3xx status
|
||||
* code. The purpose of this method is to notify the sink that a redirect
|
||||
|
|
|
@ -5864,6 +5864,12 @@ HttpBaseChannel::SetNavigationStartTimeStamp(TimeStamp aTimeStamp) {
|
|||
|
||||
nsresult HttpBaseChannel::CheckRedirectLimit(uint32_t aRedirectFlags) const {
|
||||
if (aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
|
||||
// for internal redirect due to auth retry we do not have any limit
|
||||
// as we might restrict the number of times a user might retry
|
||||
// authentication
|
||||
if (aRedirectFlags & nsIChannelEventSink::REDIRECT_AUTH_RETRY) {
|
||||
return NS_OK;
|
||||
}
|
||||
// Some platform features, like Service Workers, depend on internal
|
||||
// redirects. We should allow some number of internal redirects above
|
||||
// and beyond the normal redirect limit so these features continue
|
||||
|
|
|
@ -1757,6 +1757,10 @@ HttpChannelParent::GetRemoteType(nsACString& aRemoteType) {
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
bool HttpChannelParent::IsRedirectDueToAuthRetry(uint32_t redirectFlags) {
|
||||
return (redirectFlags & nsIChannelEventSink::REDIRECT_AUTH_RETRY);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// HttpChannelParent::nsIParentRedirectingChannel
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -1785,24 +1789,30 @@ HttpChannelParent::StartRedirect(nsIChannel* newChannel, uint32_t redirectFlags,
|
|||
return NS_BINDING_ABORTED;
|
||||
}
|
||||
|
||||
// If this is an internal redirect for service worker interception, then
|
||||
// hide it from the child process. The original e10s interception code
|
||||
// was not designed with this in mind and its not necessary to replace
|
||||
// the HttpChannelChild/Parent objects in this case.
|
||||
// If this is an internal redirect for service worker interception or
|
||||
// internal redirect due to auth retries, then hide it from the child
|
||||
// process. The original e10s interception code was not designed with this
|
||||
// in mind and its not necessary to replace the HttpChannelChild/Parent
|
||||
// objects in this case.
|
||||
if (redirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
|
||||
nsCOMPtr<nsIInterceptedChannel> oldIntercepted =
|
||||
do_QueryInterface(static_cast<nsIChannel*>(mChannel.get()));
|
||||
nsCOMPtr<nsIInterceptedChannel> newIntercepted =
|
||||
do_QueryInterface(newChannel);
|
||||
|
||||
// We only want to hide the special internal redirect from nsHttpChannel
|
||||
// to InterceptedHttpChannel. We want to allow through internal redirects
|
||||
// 1. We only want to hide the special internal redirects from
|
||||
// nsHttpChannel to InterceptedHttpChannel.
|
||||
// 2. We want to allow through internal redirects
|
||||
// initiated from the InterceptedHttpChannel even if they are to another
|
||||
// InterceptedHttpChannel, except the interception reset, since
|
||||
// corresponding HttpChannelChild/Parent objects can be reused for reset
|
||||
// case.
|
||||
// 3. If this is an internal redirect due to auth retry then we will
|
||||
// hide it from the child process
|
||||
|
||||
if ((!oldIntercepted && newIntercepted) ||
|
||||
(oldIntercepted && !newIntercepted && oldIntercepted->IsReset())) {
|
||||
(oldIntercepted && !newIntercepted && oldIntercepted->IsReset()) ||
|
||||
(IsRedirectDueToAuthRetry(redirectFlags))) {
|
||||
// We need to move across the reserved and initial client information
|
||||
// to the new channel. Normally this would be handled by the child
|
||||
// ClientChannelHelper, but that is not notified of this redirect since
|
||||
|
|
|
@ -248,6 +248,9 @@ class HttpChannelParent final : public nsIInterfaceRequestor,
|
|||
// That is, we may suspend the channel if the ODA-s to child process are not
|
||||
// consumed quickly enough. Otherwise, memory explosion could happen.
|
||||
bool NeedFlowControl();
|
||||
|
||||
bool IsRedirectDueToAuthRetry(uint32_t redirectFlags);
|
||||
|
||||
int32_t mSendWindowSize;
|
||||
|
||||
friend class HttpBackgroundChannelParent;
|
||||
|
|
|
@ -494,7 +494,7 @@ void nsHttpChannel::HandleContinueCancellingByURLClassifier(
|
|||
}
|
||||
|
||||
nsresult nsHttpChannel::OnBeforeConnect() {
|
||||
nsresult rv;
|
||||
nsresult rv = NS_OK;
|
||||
|
||||
// Check if request was cancelled during suspend AFTER on-modify-request
|
||||
if (mCanceled) {
|
||||
|
@ -507,18 +507,6 @@ nsresult nsHttpChannel::OnBeforeConnect() {
|
|||
return AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect);
|
||||
}
|
||||
|
||||
// Check to see if we should redirect this channel to the unstripped URI. To
|
||||
// revert the query stripping if the loading channel is in the content
|
||||
// blocking allow list.
|
||||
if (ContentBlockingAllowList::Check(this)) {
|
||||
nsCOMPtr<nsIURI> unstrippedURI;
|
||||
mLoadInfo->GetUnstrippedURI(getter_AddRefs(unstrippedURI));
|
||||
|
||||
if (unstrippedURI) {
|
||||
return AsyncCall(&nsHttpChannel::HandleAsyncRedirectToUnstrippedURI);
|
||||
}
|
||||
}
|
||||
|
||||
// Note that we are only setting the "Upgrade-Insecure-Requests" request
|
||||
// header for *all* navigational requests instead of all requests as
|
||||
// defined in the spec, see:
|
||||
|
@ -531,8 +519,27 @@ nsresult nsHttpChannel::OnBeforeConnect() {
|
|||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
if (LoadAuthRedirectedChannel()) {
|
||||
// This channel is a result of a redirect due to auth retry
|
||||
// We have already checked for HSTS upgarde in the redirecting channel.
|
||||
// We can safely skip those checks
|
||||
return ContinueOnBeforeConnect(false, rv);
|
||||
}
|
||||
|
||||
SecFetch::AddSecFetchHeader(this);
|
||||
|
||||
// Check to see if we should redirect this channel to the unstripped URI. To
|
||||
// revert the query stripping if the loading channel is in the content
|
||||
// blocking allow list.
|
||||
if (ContentBlockingAllowList::Check(this)) {
|
||||
nsCOMPtr<nsIURI> unstrippedURI;
|
||||
mLoadInfo->GetUnstrippedURI(getter_AddRefs(unstrippedURI));
|
||||
|
||||
if (unstrippedURI) {
|
||||
return AsyncCall(&nsHttpChannel::HandleAsyncRedirectToUnstrippedURI);
|
||||
}
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPrincipal> resultPrincipal;
|
||||
if (!mURI->SchemeIs("https")) {
|
||||
nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
|
||||
|
@ -755,6 +762,15 @@ nsresult nsHttpChannel::ContinueOnBeforeConnect(bool aShouldUpgrade,
|
|||
mCaps |= NS_HTTP_DISALLOW_HTTPS_RR;
|
||||
}
|
||||
|
||||
if (mTransactionSticky) {
|
||||
MOZ_ASSERT(LoadAuthRedirectedChannel());
|
||||
// this means this is a redirected channel channel due to auth retry and a
|
||||
// connection based auth scheme was used
|
||||
// we have a reference to the old-transaction with sticky connection which
|
||||
// we need to use
|
||||
mCaps |= NS_HTTP_STICKY_CONNECTION;
|
||||
}
|
||||
|
||||
mCaps |= NS_HTTP_TRR_FLAGS_FROM_MODE(nsIRequest::GetTRRMode());
|
||||
|
||||
// Finalize ConnectionInfo flags before SpeculativeConnect
|
||||
|
@ -928,7 +944,7 @@ nsresult nsHttpChannel::ContinueConnect() {
|
|||
}
|
||||
|
||||
// hit the net...
|
||||
return DoConnect();
|
||||
return DoConnect(mTransactionSticky);
|
||||
}
|
||||
|
||||
nsresult nsHttpChannel::DoConnect(HttpTransactionShell* aTransWithStickyConn) {
|
||||
|
@ -2420,10 +2436,11 @@ nsresult nsHttpChannel::ContinueProcessResponse3(nsresult rv) {
|
|||
// The transaction has been internally restarted. We want to
|
||||
// authenticate to the proxy again, so reuse either cached credentials
|
||||
// or use default credentials for NTLM/Negotiate. This prevents
|
||||
// considering the previously used creadentials as invalid.
|
||||
// considering the previously used credentials as invalid.
|
||||
mAuthProvider->ClearProxyIdent();
|
||||
}
|
||||
if (MOZ_UNLIKELY(LoadCustomAuthHeader()) && httpStatus == 401) {
|
||||
if (!LoadAuthRedirectedChannel() &&
|
||||
MOZ_UNLIKELY(LoadCustomAuthHeader()) && httpStatus == 401) {
|
||||
// When a custom auth header fails, we don't want to try
|
||||
// any cached credentials, nor we want to ask the user.
|
||||
// It's up to the consumer to re-try w/o setting a custom
|
||||
|
@ -2485,7 +2502,14 @@ nsresult nsHttpChannel::ContinueProcessResponse3(nsresult rv) {
|
|||
rv = ProcessNormal();
|
||||
} else {
|
||||
mIsAuthChannel = true;
|
||||
mAuthRetryPending = true; // see DoAuthRetry
|
||||
mAuthRetryPending = true;
|
||||
if (StaticPrefs::network_auth_use_redirect_for_retries()) {
|
||||
if (NS_SUCCEEDED(RedirectToNewChannelForAuthRetry())) {
|
||||
return NS_OK;
|
||||
}
|
||||
mAuthRetryPending = false;
|
||||
rv = ProcessNormal();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -2894,7 +2918,114 @@ void nsHttpChannel::HandleAsyncRedirectToUnstrippedURI() {
|
|||
}
|
||||
}
|
||||
}
|
||||
nsresult nsHttpChannel::RedirectToNewChannelForAuthRetry() {
|
||||
LOG(("nsHttpChannel::RedirectToNewChannelForAuthRetry %p", this));
|
||||
nsresult rv = NS_OK;
|
||||
|
||||
nsCOMPtr<nsILoadInfo> redirectLoadInfo = CloneLoadInfoForRedirect(
|
||||
mURI, nsIChannelEventSink::REDIRECT_INTERNAL |
|
||||
nsIChannelEventSink::REDIRECT_AUTH_RETRY);
|
||||
|
||||
nsCOMPtr<nsIIOService> ioService;
|
||||
|
||||
rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIChannel> newChannel;
|
||||
rv = gHttpHandler->NewProxiedChannel(mURI, mProxyInfo, mProxyResolveFlags,
|
||||
mProxyURI, mLoadInfo,
|
||||
getter_AddRefs(newChannel));
|
||||
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = SetupReplacementChannel(mURI, newChannel, true,
|
||||
nsIChannelEventSink::REDIRECT_INTERNAL |
|
||||
nsIChannelEventSink::REDIRECT_AUTH_RETRY);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// rewind the upload stream
|
||||
if (mUploadStream) {
|
||||
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
|
||||
nsresult rv = NS_ERROR_NO_INTERFACE;
|
||||
if (seekable) {
|
||||
rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
|
||||
}
|
||||
|
||||
// This should not normally happen, but it's possible that big memory
|
||||
// blobs originating in the other process can't be rewinded.
|
||||
// In that case we just fail the request, otherwise the content length
|
||||
// will not match and this load will never complete.
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(newChannel);
|
||||
|
||||
MOZ_ASSERT(mAuthProvider);
|
||||
httpChannelImpl->mAuthProvider = std::move(mAuthProvider);
|
||||
|
||||
httpChannelImpl->mProxyInfo = mProxyInfo;
|
||||
|
||||
if ((mCaps & NS_HTTP_STICKY_CONNECTION) ||
|
||||
mTransaction->HasStickyConnection()) {
|
||||
mConnectionInfo = mTransaction->GetConnInfo();
|
||||
|
||||
httpChannelImpl->mTransactionSticky = mTransaction;
|
||||
|
||||
if (mTransaction->Http2Disabled()) {
|
||||
httpChannelImpl->mCaps |= NS_HTTP_DISALLOW_SPDY;
|
||||
}
|
||||
if (mTransaction->Http3Disabled()) {
|
||||
httpChannelImpl->mCaps |= NS_HTTP_DISALLOW_HTTP3;
|
||||
}
|
||||
}
|
||||
httpChannelImpl->mCaps |= NS_HTTP_STICKY_CONNECTION;
|
||||
if (LoadAuthConnectionRestartable()) {
|
||||
httpChannelImpl->mCaps |= NS_HTTP_CONNECTION_RESTARTABLE;
|
||||
} else {
|
||||
httpChannelImpl->mCaps &= ~NS_HTTP_CONNECTION_RESTARTABLE;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mConnectionInfo);
|
||||
httpChannelImpl->mConnectionInfo = mConnectionInfo->Clone();
|
||||
|
||||
// we need to store the state to skip unnecessary checks in the new channel
|
||||
httpChannelImpl->StoreAuthRedirectedChannel(true);
|
||||
|
||||
// We must copy proxy and auth header to the new channel.
|
||||
// Although the new channel can populate auth headers from auth cache, we
|
||||
// would still like to use the auth headers generated in this channel. The
|
||||
// main reason for doing this is that certain connection-based/stateful auth
|
||||
// schemes like NTLM will fail when we try generate the credentials more than
|
||||
// the number of times the server has presented us the challenge due to the
|
||||
// usage of nonce in generating the credentials Copying the auth header will
|
||||
// bypass generation of the credentials
|
||||
nsAutoCString authVal;
|
||||
if (NS_SUCCEEDED(GetRequestHeader("Proxy-Authorization"_ns, authVal))) {
|
||||
httpChannelImpl->SetRequestHeader("Proxy-Authorization"_ns, authVal, false);
|
||||
}
|
||||
if (NS_SUCCEEDED(GetRequestHeader("Authorization"_ns, authVal))) {
|
||||
httpChannelImpl->SetRequestHeader("Authorization"_ns, authVal, false);
|
||||
}
|
||||
|
||||
httpChannelImpl->SetBlockAuthPrompt(LoadBlockAuthPrompt());
|
||||
mRedirectChannel = newChannel;
|
||||
|
||||
PushRedirectAsyncFunc(&nsHttpChannel::OpenRedirectChannel);
|
||||
rv = gHttpHandler->AsyncOnChannelRedirect(
|
||||
this, newChannel,
|
||||
nsIChannelEventSink::REDIRECT_INTERNAL |
|
||||
nsIChannelEventSink::REDIRECT_AUTH_RETRY);
|
||||
|
||||
if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
AutoRedirectVetoNotifier notifier(this, rv);
|
||||
mRedirectChannel = nullptr;
|
||||
PopRedirectAsyncFunc(&nsHttpChannel::OpenRedirectChannel);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
nsresult nsHttpChannel::StartRedirectChannelToURI(nsIURI* upgradedURI,
|
||||
uint32_t flags) {
|
||||
nsresult rv = NS_OK;
|
||||
|
@ -2982,6 +3113,7 @@ nsresult nsHttpChannel::ContinueAsyncRedirectChannelToURI(nsresult rv) {
|
|||
nsresult nsHttpChannel::OpenRedirectChannel(nsresult rv) {
|
||||
AutoRedirectVetoNotifier notifier(this, rv);
|
||||
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
// Make sure to do this after we received redirect veto answer,
|
||||
// i.e. after all sinks had been notified
|
||||
mRedirectChannel->SetOriginalURI(mOriginalURI);
|
||||
|
@ -3017,43 +3149,19 @@ nsresult nsHttpChannel::AsyncDoReplaceWithProxy(nsIProxyInfo* pi) {
|
|||
// Inform consumers about this fake redirect
|
||||
mRedirectChannel = newChannel;
|
||||
|
||||
PushRedirectAsyncFunc(&nsHttpChannel::ContinueDoReplaceWithProxy);
|
||||
PushRedirectAsyncFunc(&nsHttpChannel::OpenRedirectChannel);
|
||||
rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags);
|
||||
|
||||
if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
AutoRedirectVetoNotifier notifier(this, rv);
|
||||
PopRedirectAsyncFunc(&nsHttpChannel::ContinueDoReplaceWithProxy);
|
||||
PopRedirectAsyncFunc(&nsHttpChannel::OpenRedirectChannel);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult nsHttpChannel::ContinueDoReplaceWithProxy(nsresult rv) {
|
||||
AutoRedirectVetoNotifier notifier(this, rv);
|
||||
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
MOZ_ASSERT(mRedirectChannel, "No redirect channel?");
|
||||
|
||||
// Make sure to do this after we received redirect veto answer,
|
||||
// i.e. after all sinks had been notified
|
||||
mRedirectChannel->SetOriginalURI(mOriginalURI);
|
||||
|
||||
// open new channel
|
||||
rv = mRedirectChannel->AsyncOpen(mListener);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
mStatus = NS_BINDING_REDIRECTED;
|
||||
|
||||
notifier.RedirectSucceeded();
|
||||
|
||||
ReleaseListeners();
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult nsHttpChannel::ResolveProxy() {
|
||||
LOG(("nsHttpChannel::ResolveProxy [this=%p]\n", this));
|
||||
|
||||
|
@ -5417,6 +5525,11 @@ NS_IMETHODIMP nsHttpChannel::OnAuthAvailable() {
|
|||
mTransactionPump->Resume();
|
||||
}
|
||||
|
||||
if (StaticPrefs::network_auth_use_redirect_for_retries()) {
|
||||
return CallOrWaitForResume(
|
||||
[](auto* self) { return self->RedirectToNewChannelForAuthRetry(); });
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -6069,7 +6182,8 @@ void nsHttpChannel::AsyncOpenFinal(TimeStamp aTimeStamp) {
|
|||
// yes, this channel will be canceled by channel classifier. Chances are the
|
||||
// lookup is not needed so CheckIsTrackerWithLocalTable() will return an
|
||||
// error and then we can MaybeResolveProxyAndBeginConnect() right away.
|
||||
if (NS_ShouldClassifyChannel(this)) {
|
||||
// We skip the check in case this is an internal redirected channel
|
||||
if (!LoadAuthRedirectedChannel() && NS_ShouldClassifyChannel(this)) {
|
||||
RefPtr<nsHttpChannel> self = this;
|
||||
willCallback = NS_SUCCEEDED(
|
||||
AsyncUrlChannelClassifier::CheckChannel(this, [self]() -> void {
|
||||
|
@ -6353,7 +6467,12 @@ nsresult nsHttpChannel::BeginConnect() {
|
|||
mConnectionInfo->SetNoSpdy(true);
|
||||
}
|
||||
|
||||
mAuthProvider = new nsHttpChannelAuthProvider();
|
||||
// We can be passed with the auth provider if this channel was
|
||||
// a result of redirect due to auth retry
|
||||
if (!mAuthProvider) {
|
||||
mAuthProvider = new nsHttpChannelAuthProvider();
|
||||
}
|
||||
|
||||
rv = mAuthProvider->Init(this);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
|
@ -6416,8 +6535,10 @@ nsresult nsHttpChannel::BeginConnect() {
|
|||
if (mCanceled) {
|
||||
return mStatus;
|
||||
}
|
||||
|
||||
bool shouldBeClassified = NS_ShouldClassifyChannel(this);
|
||||
// skip classifier checks if this channel was the result of internal auth
|
||||
// redirect
|
||||
bool shouldBeClassified =
|
||||
!LoadAuthRedirectedChannel() && NS_ShouldClassifyChannel(this);
|
||||
|
||||
if (shouldBeClassified) {
|
||||
if (LoadChannelClassifierCancellationPending()) {
|
||||
|
@ -6474,7 +6595,8 @@ nsresult nsHttpChannel::MaybeStartDNSPrefetch() {
|
|||
// be correct, and even when it isn't, the timing still represents _a_
|
||||
// valid DNS lookup timing for the site, even if it is not _the_
|
||||
// timing we used.
|
||||
if (mLoadFlags & (LOAD_NO_NETWORK_IO | LOAD_ONLY_FROM_CACHE)) {
|
||||
if ((mLoadFlags & (LOAD_NO_NETWORK_IO | LOAD_ONLY_FROM_CACHE)) ||
|
||||
LoadAuthRedirectedChannel()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -7502,7 +7624,8 @@ nsHttpChannel::OnStopRequest(nsIRequest* request, nsresult status) {
|
|||
|
||||
if (mTransaction) {
|
||||
// determine if we should call DoAuthRetry
|
||||
bool authRetry = mAuthRetryPending && NS_SUCCEEDED(status);
|
||||
bool authRetry = (!StaticPrefs::network_auth_use_redirect_for_retries() &&
|
||||
mAuthRetryPending && NS_SUCCEEDED(status));
|
||||
StoreStronglyFramed(mTransaction->ResponseIsComplete());
|
||||
LOG(("nsHttpChannel %p has a strongly framed transaction: %d", this,
|
||||
LoadStronglyFramed()));
|
||||
|
@ -7541,16 +7664,13 @@ nsHttpChannel::OnStopRequest(nsIRequest* request, nsresult status) {
|
|||
// the reposnse headers yet on the socket thread (found connection based
|
||||
// auth schema).
|
||||
|
||||
if ((mAuthRetryPending || NS_FAILED(status)) && transactionWithStickyConn) {
|
||||
if (NS_FAILED(status)) {
|
||||
// Close (don't reuse) the sticky connection if it's in the middle
|
||||
// of an NTLM negotiation and this channel has been cancelled.
|
||||
// There are proxy servers known to get confused when we send
|
||||
// a new request over such a half-stated connection.
|
||||
if (!LoadAuthConnectionRestartable()) {
|
||||
LOG((" not reusing a half-authenticated sticky connection"));
|
||||
transactionWithStickyConn->DontReuseConnection();
|
||||
}
|
||||
if ((NS_FAILED(status)) && transactionWithStickyConn) {
|
||||
// Close (don't reuse) the sticky connection if this channel has been
|
||||
// cancelled. There are proxy servers known to get confused when we send
|
||||
// a new request over such a half-stated connection.
|
||||
if (!LoadAuthConnectionRestartable()) {
|
||||
LOG((" not reusing a half-authenticated sticky connection"));
|
||||
transactionWithStickyConn->DontReuseConnection();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7953,6 +8073,8 @@ nsresult nsHttpChannel::ContinueOnStopRequest(nsresult aStatus, bool aIsFromNet,
|
|||
// The prefetch needs to be released on the main thread
|
||||
mDNSPrefetch = nullptr;
|
||||
|
||||
mTransactionSticky = nullptr;
|
||||
|
||||
// notify "http-on-stop-connect" observers
|
||||
gHttpHandler->OnStopRequest(this);
|
||||
|
||||
|
|
|
@ -386,7 +386,6 @@ class nsHttpChannel final : public HttpBaseChannel,
|
|||
// proxy specific methods
|
||||
[[nodiscard]] nsresult ProxyFailover();
|
||||
[[nodiscard]] nsresult AsyncDoReplaceWithProxy(nsIProxyInfo*);
|
||||
[[nodiscard]] nsresult ContinueDoReplaceWithProxy(nsresult);
|
||||
[[nodiscard]] nsresult ResolveProxy();
|
||||
|
||||
// cache specific methods
|
||||
|
@ -528,6 +527,9 @@ class nsHttpChannel final : public HttpBaseChannel,
|
|||
// resolve in firing a ServiceWorker FetchEvent.
|
||||
[[nodiscard]] nsresult RedirectToInterceptedChannel();
|
||||
|
||||
// Start an internal redirect to a new channel for auth retry
|
||||
[[nodiscard]] nsresult RedirectToNewChannelForAuthRetry();
|
||||
|
||||
// Determines and sets content type in the cache entry. It's called when
|
||||
// writing a new entry. The content type is used in cache internally only.
|
||||
void SetCachedContentType();
|
||||
|
@ -570,6 +572,7 @@ class nsHttpChannel final : public HttpBaseChannel,
|
|||
|
||||
nsCOMPtr<nsIRequest> mTransactionPump;
|
||||
RefPtr<HttpTransactionShell> mTransaction;
|
||||
RefPtr<HttpTransactionShell> mTransactionSticky;
|
||||
|
||||
uint64_t mLogicalOffset{0};
|
||||
|
||||
|
@ -700,7 +703,8 @@ class nsHttpChannel final : public HttpBaseChannel,
|
|||
// Only set to true when we receive an HTTPSSVC record before the
|
||||
// transaction is created.
|
||||
(uint32_t, HTTPSSVCTelemetryReported, 1),
|
||||
(uint32_t, EchConfigUsed, 1)
|
||||
(uint32_t, EchConfigUsed, 1),
|
||||
(uint32_t, AuthRedirectedChannel, 1)
|
||||
))
|
||||
// clang-format on
|
||||
|
||||
|
|
|
@ -33,6 +33,8 @@ function AuthPrompt1(flags) {
|
|||
this.flags = flags;
|
||||
}
|
||||
|
||||
var initialChannelId = -1;
|
||||
|
||||
AuthPrompt1.prototype = {
|
||||
user: "guest",
|
||||
pass: "guest",
|
||||
|
@ -269,6 +271,20 @@ var listener = {
|
|||
do_throw("Expecting an HTTP channel");
|
||||
}
|
||||
|
||||
if (
|
||||
Services.prefs.getBoolPref("network.auth.use_redirect_for_retries") &&
|
||||
// we should skip redirect check if we do not expect to succeed
|
||||
this.expectedCode == 200
|
||||
) {
|
||||
// ensure channel ids are initialized
|
||||
Assert.notEqual(initialChannelId, -1);
|
||||
|
||||
// for each request we must use a unique channel ID.
|
||||
// See Bug 1820807
|
||||
var chan = request.QueryInterface(Ci.nsIIdentChannel);
|
||||
Assert.notEqual(initialChannelId, chan.channelId);
|
||||
}
|
||||
|
||||
Assert.equal(request.responseStatus, this.expectedCode);
|
||||
// The request should be succeeded if we expect 200
|
||||
Assert.equal(request.requestSucceeded, this.expectedCode == 200);
|
||||
|
@ -285,7 +301,7 @@ var listener = {
|
|||
|
||||
onStopRequest: function test_onStopR(request, status) {
|
||||
Assert.equal(status, Cr.NS_ERROR_ABORT);
|
||||
|
||||
initialChannelId = -1;
|
||||
this.nextTest();
|
||||
},
|
||||
};
|
||||
|
@ -308,12 +324,25 @@ function makeChan(
|
|||
});
|
||||
}
|
||||
|
||||
var ChannelCreationObserver = {
|
||||
QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
|
||||
observe(aSubject, aTopic, aData) {
|
||||
if (aTopic == "http-on-opening-request") {
|
||||
initialChannelId = aSubject.QueryInterface(Ci.nsIIdentChannel).channelId;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var httpserv = null;
|
||||
|
||||
function setup() {
|
||||
httpserv = new HttpServer();
|
||||
|
||||
httpserv.registerPathHandler("/auth", authHandler);
|
||||
httpserv.registerPathHandler(
|
||||
"/auth/stored/wrong/credentials/",
|
||||
authHandlerWrongStoredCredentials
|
||||
);
|
||||
httpserv.registerPathHandler("/auth/ntlm/simple", authNtlmSimple);
|
||||
httpserv.registerPathHandler("/auth/realm", authRealm);
|
||||
httpserv.registerPathHandler("/auth/non_ascii", authNonascii);
|
||||
|
@ -338,6 +367,7 @@ function setup() {
|
|||
registerCleanupFunction(async () => {
|
||||
await httpserv.stop();
|
||||
});
|
||||
Services.obs.addObserver(ChannelCreationObserver, "http-on-opening-request");
|
||||
}
|
||||
setup();
|
||||
|
||||
|
@ -351,86 +381,100 @@ async function openAndListen(chan) {
|
|||
.clearAll();
|
||||
}
|
||||
|
||||
add_task(async function test_noauth() {
|
||||
async function test_noauth() {
|
||||
var chan = makeChan(URL + "/auth", URL);
|
||||
|
||||
listener.expectedCode = 401; // Unauthorized
|
||||
await openAndListen(chan);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_returnfalse1() {
|
||||
async function test_returnfalse1() {
|
||||
var chan = makeChan(URL + "/auth", URL);
|
||||
|
||||
chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 1);
|
||||
listener.expectedCode = 401; // Unauthorized
|
||||
await openAndListen(chan);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_wrongpw1() {
|
||||
async function test_wrongpw1() {
|
||||
var chan = makeChan(URL + "/auth", URL);
|
||||
|
||||
chan.notificationCallbacks = new Requestor(FLAG_WRONG_PASSWORD, 1);
|
||||
listener.expectedCode = 200; // OK
|
||||
await openAndListen(chan);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_prompt1() {
|
||||
async function test_prompt1() {
|
||||
var chan = makeChan(URL + "/auth", URL);
|
||||
|
||||
chan.notificationCallbacks = new Requestor(0, 1);
|
||||
listener.expectedCode = 200; // OK
|
||||
await openAndListen(chan);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_prompt1CrossOrigin() {
|
||||
async function test_prompt1CrossOrigin() {
|
||||
var chan = makeChan(URL + "/auth", "http://example.org");
|
||||
|
||||
chan.notificationCallbacks = new Requestor(16, 1);
|
||||
listener.expectedCode = 200; // OK
|
||||
await openAndListen(chan);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_prompt2CrossOrigin() {
|
||||
async function test_prompt2CrossOrigin() {
|
||||
var chan = makeChan(URL + "/auth", "http://example.org");
|
||||
|
||||
chan.notificationCallbacks = new Requestor(16, 2);
|
||||
listener.expectedCode = 200; // OK
|
||||
await openAndListen(chan);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_returnfalse2() {
|
||||
async function test_returnfalse2() {
|
||||
var chan = makeChan(URL + "/auth", URL);
|
||||
|
||||
chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
|
||||
listener.expectedCode = 401; // Unauthorized
|
||||
await openAndListen(chan);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_wrongpw2() {
|
||||
async function test_wrongpw2() {
|
||||
var chan = makeChan(URL + "/auth", URL);
|
||||
|
||||
chan.notificationCallbacks = new Requestor(FLAG_WRONG_PASSWORD, 2);
|
||||
listener.expectedCode = 200; // OK
|
||||
await openAndListen(chan);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_prompt2() {
|
||||
var requestNum = 0;
|
||||
var expectedRequestNum = 0;
|
||||
async function test_wrong_stored_passwd() {
|
||||
// tests that we don't retry auth requests for incorrect custom credentials passed during channel creation
|
||||
requestNum = 0;
|
||||
expectedRequestNum = 1;
|
||||
var chan = makeChan(URL + "/auth/stored/wrong/credentials/", URL);
|
||||
chan.nsIHttpChannel.setRequestHeader("Authorization", "wrong_cred", false);
|
||||
chan.notificationCallbacks = new Requestor(0, 1);
|
||||
listener.expectedCode = 401; // Unauthorized
|
||||
|
||||
await openAndListen(chan);
|
||||
}
|
||||
|
||||
async function test_prompt2() {
|
||||
var chan = makeChan(URL + "/auth", URL);
|
||||
|
||||
chan.notificationCallbacks = new Requestor(0, 2);
|
||||
listener.expectedCode = 200; // OK
|
||||
await openAndListen(chan);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_ntlm() {
|
||||
async function test_ntlm() {
|
||||
var chan = makeChan(URL + "/auth/ntlm/simple", URL);
|
||||
|
||||
chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
|
||||
listener.expectedCode = 401; // Unauthorized
|
||||
await openAndListen(chan);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_basicrealm() {
|
||||
async function test_basicrealm() {
|
||||
var chan = makeChan(URL + "/auth/realm", URL);
|
||||
|
||||
let requestor = new RealmTestRequestor();
|
||||
|
@ -438,97 +482,97 @@ add_task(async function test_basicrealm() {
|
|||
listener.expectedCode = 401; // Unauthorized
|
||||
await openAndListen(chan);
|
||||
Assert.equal(requestor.promptRealm, '"foo_bar');
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_nonascii() {
|
||||
async function test_nonascii() {
|
||||
var chan = makeChan(URL + "/auth/non_ascii", URL);
|
||||
|
||||
chan.notificationCallbacks = new Requestor(FLAG_NON_ASCII_USER_PASSWORD, 2);
|
||||
listener.expectedCode = 200; // OK
|
||||
await openAndListen(chan);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_digest_noauth() {
|
||||
async function test_digest_noauth() {
|
||||
var chan = makeChan(URL + "/auth/digest_md5", URL);
|
||||
|
||||
// chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
|
||||
listener.expectedCode = 401; // Unauthorized
|
||||
await openAndListen(chan);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_digest_md5() {
|
||||
async function test_digest_md5() {
|
||||
var chan = makeChan(URL + "/auth/digest_md5", URL);
|
||||
|
||||
chan.notificationCallbacks = new Requestor(0, 2);
|
||||
listener.expectedCode = 200; // OK
|
||||
await openAndListen(chan);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_digest_md5sess() {
|
||||
async function test_digest_md5sess() {
|
||||
var chan = makeChan(URL + "/auth/digest_md5sess", URL);
|
||||
|
||||
chan.notificationCallbacks = new Requestor(0, 2);
|
||||
listener.expectedCode = 200; // OK
|
||||
await openAndListen(chan);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_digest_sha256() {
|
||||
async function test_digest_sha256() {
|
||||
var chan = makeChan(URL + "/auth/digest_sha256", URL);
|
||||
|
||||
chan.notificationCallbacks = new Requestor(0, 2);
|
||||
listener.expectedCode = 200; // OK
|
||||
await openAndListen(chan);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_digest_sha256sess() {
|
||||
async function test_digest_sha256sess() {
|
||||
var chan = makeChan(URL + "/auth/digest_sha256sess", URL);
|
||||
|
||||
chan.notificationCallbacks = new Requestor(0, 2);
|
||||
listener.expectedCode = 200; // OK
|
||||
await openAndListen(chan);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_digest_sha256_md5() {
|
||||
async function test_digest_sha256_md5() {
|
||||
var chan = makeChan(URL + "/auth/digest_sha256_md5", URL);
|
||||
|
||||
chan.notificationCallbacks = new Requestor(0, 2);
|
||||
listener.expectedCode = 200; // OK
|
||||
await openAndListen(chan);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_digest_md5_sha256() {
|
||||
async function test_digest_md5_sha256() {
|
||||
var chan = makeChan(URL + "/auth/digest_md5_sha256", URL);
|
||||
|
||||
chan.notificationCallbacks = new Requestor(0, 2);
|
||||
listener.expectedCode = 200; // OK
|
||||
await openAndListen(chan);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_digest_md5_sha256_oneline() {
|
||||
async function test_digest_md5_sha256_oneline() {
|
||||
var chan = makeChan(URL + "/auth/digest_md5_sha256_oneline", URL);
|
||||
|
||||
chan.notificationCallbacks = new Requestor(0, 2);
|
||||
listener.expectedCode = 200; // OK
|
||||
await openAndListen(chan);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_digest_bogus_user() {
|
||||
async function test_digest_bogus_user() {
|
||||
var chan = makeChan(URL + "/auth/digest_md5", URL);
|
||||
chan.notificationCallbacks = new Requestor(FLAG_BOGUS_USER, 2);
|
||||
listener.expectedCode = 401; // unauthorized
|
||||
await openAndListen(chan);
|
||||
});
|
||||
}
|
||||
|
||||
// Test header "WWW-Authenticate: Digest" - bug 1338876.
|
||||
add_task(async function test_short_digest() {
|
||||
async function test_short_digest() {
|
||||
var chan = makeChan(URL + "/auth/short_digest", URL);
|
||||
chan.notificationCallbacks = new Requestor(FLAG_NO_REALM, 2);
|
||||
listener.expectedCode = 401; // OK
|
||||
await openAndListen(chan);
|
||||
});
|
||||
}
|
||||
|
||||
// Test that COOP/COEP are processed even though asyncPromptAuth is cancelled.
|
||||
add_task(async function test_corp_coep() {
|
||||
async function test_corp_coep() {
|
||||
var chan = makeChan(
|
||||
URL + "/corp-coep",
|
||||
URL,
|
||||
|
@ -548,10 +592,10 @@ add_task(async function test_corp_coep() {
|
|||
chan.getResponseHeader("cross-origin-opener-policy"),
|
||||
"same-origin"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// XXX(valentin): this makes tests fail if it's not run last. Why?
|
||||
add_task(async function test_nonascii_xhr() {
|
||||
async function test_nonascii_xhr() {
|
||||
await new Promise(resolve => {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", URL + "/auth/non_ascii", true, "é", "é");
|
||||
|
@ -564,7 +608,49 @@ add_task(async function test_nonascii_xhr() {
|
|||
};
|
||||
xhr.send(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
let auth_tests = [
|
||||
test_noauth,
|
||||
test_returnfalse1,
|
||||
test_wrongpw1,
|
||||
test_wrong_stored_passwd,
|
||||
test_prompt1,
|
||||
test_prompt1CrossOrigin,
|
||||
test_prompt2CrossOrigin,
|
||||
test_returnfalse2,
|
||||
test_wrongpw2,
|
||||
test_prompt2,
|
||||
test_ntlm,
|
||||
test_basicrealm,
|
||||
test_nonascii,
|
||||
test_digest_noauth,
|
||||
test_digest_md5,
|
||||
test_digest_md5sess,
|
||||
test_digest_sha256,
|
||||
test_digest_sha256sess,
|
||||
test_digest_sha256_md5,
|
||||
test_digest_md5_sha256,
|
||||
test_digest_md5_sha256_oneline,
|
||||
test_digest_bogus_user,
|
||||
test_short_digest,
|
||||
test_corp_coep,
|
||||
test_nonascii_xhr,
|
||||
];
|
||||
|
||||
for (let auth_test of auth_tests) {
|
||||
add_task(
|
||||
{ pref_set: [["network.auth.use_redirect_for_retries", false]] },
|
||||
auth_test
|
||||
);
|
||||
}
|
||||
|
||||
for (let auth_test of auth_tests) {
|
||||
add_task(
|
||||
{ pref_set: [["network.auth.use_redirect_for_retries", true]] },
|
||||
auth_test
|
||||
);
|
||||
}
|
||||
|
||||
// PATH HANDLERS
|
||||
|
||||
|
@ -593,6 +679,23 @@ function authHandler(metadata, response) {
|
|||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
function authHandlerWrongStoredCredentials(metadata, response) {
|
||||
var body;
|
||||
if (++requestNum > expectedRequestNum) {
|
||||
response.setStatusLine(metadata.httpVersion, 500, "");
|
||||
} else {
|
||||
response.setStatusLine(
|
||||
metadata.httpVersion,
|
||||
401,
|
||||
"Unauthorized" + requestNum
|
||||
);
|
||||
response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
|
||||
}
|
||||
|
||||
body = "failed";
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
// /auth/ntlm/simple
|
||||
function authNtlmSimple(metadata, response) {
|
||||
response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
|
||||
|
|
|
@ -179,7 +179,6 @@ function failedAuth(metadata, response) {
|
|||
case 2:
|
||||
// Proxy - Expecting a type 3 Authenticate message from the client
|
||||
// Respond with a 407 to indicate invalid credentials
|
||||
//
|
||||
authorization = metadata.getHeader("Proxy-Authorization");
|
||||
authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
|
||||
Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 3 message");
|
||||
|
|
Загрузка…
Ссылка в новой задаче