1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set expandtab ts=4 sw=2 sts=2 cin: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 // HttpLog.h should generally be included first
10 #include "mozilla/BasePrincipal.h"
11 #include "mozilla/StoragePrincipalHelper.h"
12 #include "mozilla/Tokenizer.h"
13 #include "MockHttpAuth.h"
14 #include "nsHttpChannelAuthProvider.h"
16 #include "nsNetUtil.h"
17 #include "nsHttpHandler.h"
18 #include "nsIHttpAuthenticator.h"
19 #include "nsIHttpChannelInternal.h"
20 #include "nsIAuthPrompt2.h"
21 #include "nsIAuthPromptProvider.h"
22 #include "nsIInterfaceRequestor.h"
23 #include "nsIInterfaceRequestorUtils.h"
25 #include "nsAuthInformationHolder.h"
26 #include "nsIStringBundle.h"
27 #include "nsIPromptService.h"
29 #include "nsIHttpAuthenticableChannel.h"
31 #include "nsContentUtils.h"
33 #include "nsHttpBasicAuth.h"
34 #include "nsHttpDigestAuth.h"
35 #ifdef MOZ_AUTH_EXTENSION
36 # include "nsHttpNegotiateAuth.h"
38 #include "nsHttpNTLMAuth.h"
39 #include "nsServiceManagerUtils.h"
41 #include "mozilla/StaticPrefs_network.h"
42 #include "mozilla/StaticPrefs_prompts.h"
43 #include "mozilla/Telemetry.h"
44 #include "nsIProxiedChannel.h"
45 #include "nsIProxyInfo.h"
47 namespace mozilla::net
{
49 #define SUBRESOURCE_AUTH_DIALOG_DISALLOW_ALL 0
50 #define SUBRESOURCE_AUTH_DIALOG_DISALLOW_CROSS_ORIGIN 1
51 #define SUBRESOURCE_AUTH_DIALOG_ALLOW_ALL 2
53 #define HTTP_AUTH_DIALOG_TOP_LEVEL_DOC 29
54 #define HTTP_AUTH_DIALOG_SAME_ORIGIN_SUBRESOURCE 30
55 #define HTTP_AUTH_DIALOG_SAME_ORIGIN_XHR 31
56 #define HTTP_AUTH_DIALOG_NON_WEB_CONTENT 32
58 #define HTTP_AUTH_BASIC_INSECURE 0
59 #define HTTP_AUTH_BASIC_SECURE 1
60 #define HTTP_AUTH_DIGEST_INSECURE 2
61 #define HTTP_AUTH_DIGEST_SECURE 3
62 #define HTTP_AUTH_NTLM_INSECURE 4
63 #define HTTP_AUTH_NTLM_SECURE 5
64 #define HTTP_AUTH_NEGOTIATE_INSECURE 6
65 #define HTTP_AUTH_NEGOTIATE_SECURE 7
67 #define MAX_DISPLAYED_USER_LENGTH 64
68 #define MAX_DISPLAYED_HOST_LENGTH 64
70 static void GetOriginAttributesSuffix(nsIChannel
* aChan
, nsACString
& aSuffix
) {
73 // Deliberately ignoring the result and going with defaults
75 StoragePrincipalHelper::GetOriginAttributesForNetworkState(aChan
, oa
);
78 oa
.CreateSuffix(aSuffix
);
81 nsHttpChannelAuthProvider::nsHttpChannelAuthProvider()
83 mTriedProxyAuth(false),
84 mTriedHostAuth(false),
85 mSuppressDefensiveAuth(false),
87 mConnectionBased(false),
88 mHttpHandler(gHttpHandler
) {}
90 nsHttpChannelAuthProvider::~nsHttpChannelAuthProvider() {
91 MOZ_RELEASE_ASSERT(NS_IsMainThread());
92 MOZ_ASSERT(!mAuthChannel
, "Disconnect wasn't called");
96 nsHttpChannelAuthProvider::Init(nsIHttpAuthenticableChannel
* channel
) {
97 MOZ_ASSERT(channel
, "channel expected!");
99 mAuthChannel
= channel
;
101 nsresult rv
= mAuthChannel
->GetURI(getter_AddRefs(mURI
));
102 if (NS_FAILED(rv
)) return rv
;
104 rv
= mAuthChannel
->GetIsSSL(&mUsingSSL
);
105 if (NS_FAILED(rv
)) return rv
;
107 nsCOMPtr
<nsIProxiedChannel
> proxied(channel
);
109 nsCOMPtr
<nsIProxyInfo
> pi
;
110 rv
= proxied
->GetProxyInfo(getter_AddRefs(pi
));
111 if (NS_FAILED(rv
)) return rv
;
114 nsAutoCString proxyType
;
115 rv
= pi
->GetType(proxyType
);
116 if (NS_FAILED(rv
)) return rv
;
118 mProxyUsingSSL
= proxyType
.EqualsLiteral("https");
122 rv
= mURI
->GetAsciiHost(mHost
);
123 if (NS_FAILED(rv
)) return rv
;
125 // reject the URL if it doesn't specify a host
126 if (mHost
.IsEmpty()) return NS_ERROR_MALFORMED_URI
;
128 rv
= mURI
->GetPort(&mPort
);
129 if (NS_FAILED(rv
)) return rv
;
131 nsCOMPtr
<nsIChannel
> bareChannel
= do_QueryInterface(channel
);
132 mIsPrivate
= NS_UsePrivateBrowsing(bareChannel
);
138 nsHttpChannelAuthProvider::ProcessAuthentication(uint32_t httpStatus
,
139 bool SSLConnectFailed
) {
141 ("nsHttpChannelAuthProvider::ProcessAuthentication "
142 "[this=%p channel=%p code=%u SSLConnectFailed=%d]\n",
143 this, mAuthChannel
, httpStatus
, SSLConnectFailed
));
145 MOZ_ASSERT(mAuthChannel
, "Channel not initialized");
147 nsCOMPtr
<nsIProxyInfo
> proxyInfo
;
148 nsresult rv
= mAuthChannel
->GetProxyInfo(getter_AddRefs(proxyInfo
));
149 if (NS_FAILED(rv
)) return rv
;
151 mProxyInfo
= do_QueryInterface(proxyInfo
);
152 if (!mProxyInfo
) return NS_ERROR_NO_INTERFACE
;
155 nsAutoCString challenges
;
156 mProxyAuth
= (httpStatus
== 407);
158 rv
= PrepareForAuthentication(mProxyAuth
);
159 if (NS_FAILED(rv
)) return rv
;
162 // only allow a proxy challenge if we have a proxy server configured.
163 // otherwise, we could inadvertently expose the user's proxy
164 // credentials to an origin server. We could attempt to proceed as
165 // if we had received a 401 from the server, but why risk flirting
166 // with trouble? IE similarly rejects 407s when a proxy server is
167 // not configured, so there's no reason not to do the same.
168 if (!UsingHttpProxy()) {
169 LOG(("rejecting 407 when proxy server not configured!\n"));
170 return NS_ERROR_UNEXPECTED
;
172 if (UsingSSL() && !SSLConnectFailed
) {
173 // we need to verify that this challenge came from the proxy
174 // server itself, and not some server on the other side of the
176 LOG(("rejecting 407 from origin server!\n"));
177 return NS_ERROR_UNEXPECTED
;
179 rv
= mAuthChannel
->GetProxyChallenges(challenges
);
181 rv
= mAuthChannel
->GetWWWChallenges(challenges
);
183 if (NS_FAILED(rv
)) return rv
;
186 rv
= GetCredentials(challenges
, mProxyAuth
, creds
);
187 if (rv
== NS_ERROR_IN_PROGRESS
) return rv
;
189 LOG(("unable to authenticate\n"));
191 // set the authentication credentials
193 rv
= mAuthChannel
->SetProxyCredentials(creds
);
195 rv
= mAuthChannel
->SetWWWCredentials(creds
);
202 nsHttpChannelAuthProvider::AddAuthorizationHeaders(
203 bool aDontUseCachedWWWCreds
) {
205 ("nsHttpChannelAuthProvider::AddAuthorizationHeaders? "
206 "[this=%p channel=%p]\n",
207 this, mAuthChannel
));
209 MOZ_ASSERT(mAuthChannel
, "Channel not initialized");
211 nsCOMPtr
<nsIProxyInfo
> proxyInfo
;
212 nsresult rv
= mAuthChannel
->GetProxyInfo(getter_AddRefs(proxyInfo
));
213 if (NS_FAILED(rv
)) return rv
;
215 mProxyInfo
= do_QueryInterface(proxyInfo
);
216 if (!mProxyInfo
) return NS_ERROR_NO_INTERFACE
;
220 rv
= mAuthChannel
->GetLoadFlags(&loadFlags
);
221 if (NS_FAILED(rv
)) return rv
;
223 // this getter never fails
224 nsHttpAuthCache
* authCache
= gHttpHandler
->AuthCache(mIsPrivate
);
226 // check if proxy credentials should be sent
227 if (!ProxyHost().IsEmpty() && UsingHttpProxy()) {
228 SetAuthorizationHeader(authCache
, nsHttp::Proxy_Authorization
, "http"_ns
,
229 ProxyHost(), ProxyPort(),
230 ""_ns
, // proxy has no path
234 if (loadFlags
& nsIRequest::LOAD_ANONYMOUS
) {
235 LOG(("Skipping Authorization header for anonymous load\n"));
239 if (aDontUseCachedWWWCreds
) {
241 ("Authorization header already present:"
242 " skipping adding auth header from cache\n"));
246 // check if server credentials should be sent
247 nsAutoCString path
, scheme
;
248 if (NS_SUCCEEDED(GetCurrentPath(path
)) &&
249 NS_SUCCEEDED(mURI
->GetScheme(scheme
))) {
250 SetAuthorizationHeader(authCache
, nsHttp::Authorization
, scheme
, Host(),
251 Port(), path
, mIdent
);
258 nsHttpChannelAuthProvider::CheckForSuperfluousAuth() {
260 ("nsHttpChannelAuthProvider::CheckForSuperfluousAuth? "
261 "[this=%p channel=%p]\n",
262 this, mAuthChannel
));
264 MOZ_ASSERT(mAuthChannel
, "Channel not initialized");
266 // we've been called because it has been determined that this channel is
267 // getting loaded without taking the userpass from the URL. if the URL
268 // contained a userpass, then (provided some other conditions are true),
269 // we'll give the user an opportunity to abort the channel as this might be
270 // an attempt to spoof a different site (see bug 232567).
271 if (!ConfirmAuth("SuperfluousAuth", true)) {
272 // calling cancel here sets our mStatus and aborts the HTTP
273 // transaction, which prevents OnDataAvailable events.
274 Unused
<< mAuthChannel
->Cancel(NS_ERROR_ABORT
);
275 return NS_ERROR_ABORT
;
281 nsHttpChannelAuthProvider::Cancel(nsresult status
) {
282 MOZ_ASSERT(mAuthChannel
, "Channel not initialized");
284 if (mAsyncPromptAuthCancelable
) {
285 mAsyncPromptAuthCancelable
->Cancel(status
);
286 mAsyncPromptAuthCancelable
= nullptr;
289 if (mGenerateCredentialsCancelable
) {
290 mGenerateCredentialsCancelable
->Cancel(status
);
291 mGenerateCredentialsCancelable
= nullptr;
297 nsHttpChannelAuthProvider::Disconnect(nsresult status
) {
298 mAuthChannel
= nullptr;
300 if (mAsyncPromptAuthCancelable
) {
301 mAsyncPromptAuthCancelable
->Cancel(status
);
302 mAsyncPromptAuthCancelable
= nullptr;
305 if (mGenerateCredentialsCancelable
) {
306 mGenerateCredentialsCancelable
->Cancel(status
);
307 mGenerateCredentialsCancelable
= nullptr;
310 NS_IF_RELEASE(mProxyAuthContinuationState
);
311 NS_IF_RELEASE(mAuthContinuationState
);
316 // helper function for getting an auth prompt from an interface requestor
317 static void GetAuthPrompt(nsIInterfaceRequestor
* ifreq
, bool proxyAuth
,
318 nsIAuthPrompt2
** result
) {
321 uint32_t promptReason
;
323 promptReason
= nsIAuthPromptProvider::PROMPT_PROXY
;
325 promptReason
= nsIAuthPromptProvider::PROMPT_NORMAL
;
328 nsCOMPtr
<nsIAuthPromptProvider
> promptProvider
= do_GetInterface(ifreq
);
329 if (promptProvider
) {
330 promptProvider
->GetAuthPrompt(promptReason
, NS_GET_IID(nsIAuthPrompt2
),
331 reinterpret_cast<void**>(result
));
333 NS_QueryAuthPrompt2(ifreq
, result
);
337 // generate credentials for the given challenge, and update the auth cache.
338 nsresult
nsHttpChannelAuthProvider::GenCredsAndSetEntry(
339 nsIHttpAuthenticator
* auth
, bool proxyAuth
, const nsACString
& scheme
,
340 const nsACString
& host
, int32_t port
, const nsACString
& directory
,
341 const nsACString
& realm
, const nsACString
& challenge
,
342 const nsHttpAuthIdentity
& ident
, nsCOMPtr
<nsISupports
>& sessionState
,
343 nsACString
& result
) {
345 nsISupports
* ss
= sessionState
;
347 // set informations that depend on whether
348 // we're authenticating against a proxy
350 nsISupports
** continuationState
;
353 continuationState
= &mProxyAuthContinuationState
;
355 continuationState
= &mAuthContinuationState
;
358 rv
= auth
->GenerateCredentialsAsync(
359 mAuthChannel
, this, challenge
, proxyAuth
, ident
.Domain(), ident
.User(),
360 ident
.Password(), ss
, *continuationState
,
361 getter_AddRefs(mGenerateCredentialsCancelable
));
362 if (NS_SUCCEEDED(rv
)) {
363 // Calling generate credentials async, results will be dispatched to the
364 // main thread by calling OnCredsGenerated method
365 return NS_ERROR_IN_PROGRESS
;
368 uint32_t generateFlags
;
369 rv
= auth
->GenerateCredentials(
370 mAuthChannel
, challenge
, proxyAuth
, ident
.Domain(), ident
.User(),
371 ident
.Password(), &ss
, &*continuationState
, &generateFlags
, result
);
373 sessionState
.swap(ss
);
374 if (NS_FAILED(rv
)) return rv
;
376 // don't log this in release build since it could contain sensitive info.
378 LOG(("generated creds: %s\n", result
.BeginReading()));
381 return UpdateCache(auth
, scheme
, host
, port
, directory
, realm
, challenge
,
382 ident
, result
, generateFlags
, sessionState
, proxyAuth
);
385 nsresult
nsHttpChannelAuthProvider::UpdateCache(
386 nsIHttpAuthenticator
* auth
, const nsACString
& scheme
,
387 const nsACString
& host
, int32_t port
, const nsACString
& directory
,
388 const nsACString
& realm
, const nsACString
& challenge
,
389 const nsHttpAuthIdentity
& ident
, const nsACString
& creds
,
390 uint32_t generateFlags
, nsISupports
* sessionState
, bool aProxyAuth
) {
394 rv
= auth
->GetAuthFlags(&authFlags
);
395 if (NS_FAILED(rv
)) return rv
;
397 // find out if this authenticator allows reuse of credentials and/or
400 0 != (authFlags
& nsIHttpAuthenticator::REUSABLE_CREDENTIALS
);
402 0 != (authFlags
& nsIHttpAuthenticator::REUSABLE_CHALLENGE
);
405 0 == (generateFlags
& nsIHttpAuthenticator::USING_INTERNAL_IDENTITY
);
407 // this getter never fails
408 nsHttpAuthCache
* authCache
= gHttpHandler
->AuthCache(mIsPrivate
);
410 nsAutoCString suffix
;
412 // We don't isolate proxy credentials cache entries with the origin suffix
413 // as it would only annoy users with authentication dialogs popping up.
414 nsCOMPtr
<nsIChannel
> chan
= do_QueryInterface(mAuthChannel
);
415 GetOriginAttributesSuffix(chan
, suffix
);
418 // create a cache entry. we do this even though we don't yet know that
419 // these credentials are valid b/c we need to avoid prompting the user
420 // more than once in case the credentials are valid.
422 // if the credentials are not reusable, then we don't bother sticking
423 // them in the auth cache.
424 rv
= authCache
->SetAuthEntry(scheme
, host
, port
, directory
, realm
,
425 saveCreds
? creds
: ""_ns
,
426 saveChallenge
? challenge
: ""_ns
, suffix
,
427 saveIdentity
? &ident
: nullptr, sessionState
);
431 NS_IMETHODIMP
nsHttpChannelAuthProvider::ClearProxyIdent() {
432 LOG(("nsHttpChannelAuthProvider::ClearProxyIdent [this=%p]\n", this));
438 nsresult
nsHttpChannelAuthProvider::PrepareForAuthentication(bool proxyAuth
) {
440 ("nsHttpChannelAuthProvider::PrepareForAuthentication "
441 "[this=%p channel=%p]\n",
442 this, mAuthChannel
));
445 // reset the current proxy continuation state because our last
446 // authentication attempt was completed successfully.
447 NS_IF_RELEASE(mProxyAuthContinuationState
);
448 LOG((" proxy continuation state has been reset"));
451 if (!UsingHttpProxy() || mProxyAuthType
.IsEmpty()) return NS_OK
;
453 // We need to remove any Proxy_Authorization header left over from a
454 // non-request based authentication handshake (e.g., for NTLM auth).
457 nsCOMPtr
<nsIHttpAuthenticator
> precedingAuth
;
458 nsCString proxyAuthType
;
459 rv
= GetAuthenticator(mProxyAuthType
, proxyAuthType
,
460 getter_AddRefs(precedingAuth
));
461 if (NS_FAILED(rv
)) return rv
;
463 uint32_t precedingAuthFlags
;
464 rv
= precedingAuth
->GetAuthFlags(&precedingAuthFlags
);
465 if (NS_FAILED(rv
)) return rv
;
467 if (!(precedingAuthFlags
& nsIHttpAuthenticator::REQUEST_BASED
)) {
468 nsAutoCString challenges
;
469 rv
= mAuthChannel
->GetProxyChallenges(challenges
);
471 // delete the proxy authorization header because we weren't
472 // asked to authenticate
473 rv
= mAuthChannel
->SetProxyCredentials(""_ns
);
474 if (NS_FAILED(rv
)) return rv
;
475 LOG((" cleared proxy authorization header"));
482 class MOZ_STACK_CLASS ChallengeParser final
: Tokenizer
{
484 explicit ChallengeParser(const nsACString
& aChallenges
)
485 : Tokenizer(aChallenges
, nullptr, "") {
489 Maybe
<nsDependentCSubstring
> GetNext() {
491 nsDependentCSubstring result
;
493 bool inQuote
= false;
496 if (t
.Type() == TOKEN_EOL
) {
497 Claim(result
, ClaimInclusion::EXCLUDE_LAST
);
498 SkipWhites(WhiteSkipping::INCLUDE_NEW_LINE
);
501 if (!result
.IsEmpty()) {
504 } else if (t
.Equals(Token::Char(',')) && !inQuote
) {
505 // Sometimes we get multiple challenges separated by a comma.
506 // This is not great, as it's slightly ambiguous. We check if something
507 // is a new challenge by matching agains <param_name> =
508 // If the , isn't followed by a word and = then most likely
509 // it is the name of an authType.
511 const char* prevCursorPos
= mCursor
;
512 const char* prevRollbackPos
= mRollback
;
514 auto hasWordAndEqual
= [&]() {
516 nsDependentCSubstring word
;
517 if (!ReadWord(word
)) {
521 return Check(Token::Char('='));
523 if (!hasWordAndEqual()) {
524 // This is not a parameter. It means the `,` character starts a
525 // different challenge.
526 // We'll revert the cursor and return the contents so far.
527 mCursor
= prevCursorPos
;
528 mRollback
= prevRollbackPos
;
529 Claim(result
, ClaimInclusion::EXCLUDE_LAST
);
532 if (!result
.IsEmpty()) {
536 } else if (t
.Equals(Token::Char('"'))) {
541 Claim(result
, Tokenizer::ClaimInclusion::INCLUDE_LAST
);
544 if (!result
.IsEmpty()) {
559 ChallengeRank
Rank(const nsACString
& aChallenge
) {
560 if (StringBeginsWith(aChallenge
, "Negotiate"_ns
,
561 nsCaseInsensitiveCStringComparator
)) {
562 return ChallengeRank::Negotiate
;
565 if (StringBeginsWith(aChallenge
, "NTLM"_ns
,
566 nsCaseInsensitiveCStringComparator
)) {
567 return ChallengeRank::NTLM
;
570 if (StringBeginsWith(aChallenge
, "Digest"_ns
,
571 nsCaseInsensitiveCStringComparator
)) {
572 return ChallengeRank::Digest
;
575 if (StringBeginsWith(aChallenge
, "Basic"_ns
,
576 nsCaseInsensitiveCStringComparator
)) {
577 return ChallengeRank::Basic
;
580 return ChallengeRank::Unknown
;
583 nsresult
nsHttpChannelAuthProvider::GetCredentials(
584 const nsACString
& aChallenges
, bool proxyAuth
, nsCString
& creds
) {
585 LOG(("nsHttpChannelAuthProvider::GetCredentials"));
586 nsAutoCString
challenges(aChallenges
);
588 using AuthChallenge
= struct AuthChallenge
{
589 nsDependentCSubstring challenge
;
590 uint16_t algorithm
= 0;
591 ChallengeRank rank
= ChallengeRank::Unknown
;
593 void operator=(const AuthChallenge
& aOther
) {
594 challenge
.Rebind(aOther
.challenge
, 0);
595 algorithm
= aOther
.algorithm
;
600 nsTArray
<AuthChallenge
> cc
;
602 ChallengeParser
p(challenges
);
604 auto next
= p
.GetNext();
605 if (next
.isNothing()) {
608 AuthChallenge ac
{next
.ref(), 0};
609 nsAutoCString realm
, domain
, nonce
, opaque
;
612 ac
.rank
= Rank(ac
.challenge
);
613 if (StringBeginsWith(ac
.challenge
, "Digest"_ns
,
614 nsCaseInsensitiveCStringComparator
)) {
615 Unused
<< nsHttpDigestAuth::ParseChallenge(ac
.challenge
, realm
, domain
,
616 nonce
, opaque
, &stale
,
617 &ac
.algorithm
, &qop
);
619 cc
.AppendElement(ac
);
622 cc
.StableSort([](const AuthChallenge
& lhs
, const AuthChallenge
& rhs
) {
623 if (StaticPrefs::network_auth_choose_most_secure_challenge()) {
624 // Different auth types
625 if (lhs
.rank
!= rhs
.rank
) {
626 return lhs
.rank
< rhs
.rank
? 1 : -1;
629 // If they're the same auth type, and not a Digest, then we treat them
630 // as equal (don't reorder them).
631 if (lhs
.rank
!= ChallengeRank::Digest
) {
635 // Non-digest challenges should not be reordered when the pref is off.
636 if (lhs
.algorithm
== 0 || rhs
.algorithm
== 0) {
642 if (lhs
.algorithm
== rhs
.algorithm
) {
645 return lhs
.algorithm
< rhs
.algorithm
? 1 : -1;
648 nsCOMPtr
<nsIHttpAuthenticator
> auth
;
649 nsCString authType
; // force heap allocation to enable string sharing since
650 // we'll be assigning this value into mAuthType.
652 // set informations that depend on whether we're authenticating against a
653 // proxy or a webserver
654 nsISupports
** currentContinuationState
;
655 nsCString
* currentAuthType
;
658 currentContinuationState
= &mProxyAuthContinuationState
;
659 currentAuthType
= &mProxyAuthType
;
661 currentContinuationState
= &mAuthContinuationState
;
662 currentAuthType
= &mAuthType
;
665 nsresult rv
= NS_ERROR_NOT_AVAILABLE
;
666 bool gotCreds
= false;
668 // figure out which challenge we can handle and which authenticator to use.
669 for (size_t i
= 0; i
< cc
.Length(); i
++) {
670 rv
= GetAuthenticator(cc
[i
].challenge
, authType
, getter_AddRefs(auth
));
671 LOG(("trying auth for %s", authType
.get()));
672 if (NS_SUCCEEDED(rv
)) {
674 // if we've already selected an auth type from a previous challenge
675 // received while processing this channel, then skip others until
676 // we find a challenge corresponding to the previously tried auth
679 if (!currentAuthType
->IsEmpty() && authType
!= *currentAuthType
) continue;
682 // we allow the routines to run all the way through before we
683 // decide if they are valid.
685 // we don't worry about the auth cache being altered because that
686 // would have been the last step, and if the error is from updating
687 // the authcache it wasn't really altered anyway. -CTN
689 // at this point the code is really only useful for client side
690 // errors (it will not automatically fail over to do a different
691 // auth type if the server keeps rejecting what is being sent, even
692 // if a particular auth method only knows 1 thing, like a
693 // non-identity based authentication method)
695 rv
= GetCredentialsForChallenge(cc
[i
].challenge
, authType
, proxyAuth
,
697 if (NS_SUCCEEDED(rv
)) {
699 *currentAuthType
= authType
;
703 if (rv
== NS_ERROR_IN_PROGRESS
) {
704 // authentication prompt has been invoked and result is
705 // expected asynchronously, save current challenge being
706 // processed and all remaining challenges to use later in
707 // OnAuthAvailable and now immediately return
708 mCurrentChallenge
= cc
[i
].challenge
;
709 // imperfect; does not save server-side preference ordering.
710 // instead, continues with remaining string as provided by client
711 mRemainingChallenges
.Truncate();
712 while (i
+ 1 < cc
.Length()) {
714 mRemainingChallenges
.Append(cc
[i
].challenge
);
715 mRemainingChallenges
.Append("\n"_ns
);
720 // reset the auth type and continuation state
721 NS_IF_RELEASE(*currentContinuationState
);
722 currentAuthType
->Truncate();
726 if (!gotCreds
&& !currentAuthType
->IsEmpty()) {
727 // looks like we never found the auth type we were looking for.
728 // reset the auth type and continuation state, and try again.
729 currentAuthType
->Truncate();
730 NS_IF_RELEASE(*currentContinuationState
);
732 rv
= GetCredentials(challenges
, proxyAuth
, creds
);
738 nsresult
nsHttpChannelAuthProvider::GetAuthorizationMembers(
739 bool proxyAuth
, nsACString
& scheme
, nsCString
& host
, int32_t& port
,
740 nsACString
& path
, nsHttpAuthIdentity
*& ident
,
741 nsISupports
**& continuationState
) {
743 MOZ_ASSERT(UsingHttpProxy(),
744 "proxyAuth is true, but no HTTP proxy is configured!");
748 ident
= &mProxyIdent
;
749 scheme
.AssignLiteral("http");
751 continuationState
= &mProxyAuthContinuationState
;
758 rv
= GetCurrentPath(path
);
759 if (NS_FAILED(rv
)) return rv
;
761 rv
= mURI
->GetScheme(scheme
);
762 if (NS_FAILED(rv
)) return rv
;
764 continuationState
= &mAuthContinuationState
;
770 nsresult
nsHttpChannelAuthProvider::GetCredentialsForChallenge(
771 const nsACString
& aChallenge
, const nsACString
& aAuthType
, bool proxyAuth
,
772 nsIHttpAuthenticator
* auth
, nsCString
& creds
) {
774 ("nsHttpChannelAuthProvider::GetCredentialsForChallenge "
775 "[this=%p channel=%p proxyAuth=%d challenges=%s]\n",
776 this, mAuthChannel
, proxyAuth
, nsCString(aChallenge
).get()));
778 // this getter never fails
779 nsHttpAuthCache
* authCache
= gHttpHandler
->AuthCache(mIsPrivate
);
782 nsresult rv
= auth
->GetAuthFlags(&authFlags
);
783 if (NS_FAILED(rv
)) return rv
;
786 ParseRealm(aChallenge
, realm
);
788 // if no realm, then use the auth type as the realm. ToUpperCase so the
789 // ficticious realm stands out a bit more.
790 // XXX this will cause some single signon misses!
791 // XXX this was meant to be used with NTLM, which supplies no realm.
793 if (realm.IsEmpty()) {
799 // set informations that depend on whether
800 // we're authenticating against a proxy
804 nsHttpAuthIdentity
* ident
;
805 nsAutoCString path
, scheme
;
806 bool identFromURI
= false;
807 nsISupports
** continuationState
;
809 rv
= GetAuthorizationMembers(proxyAuth
, scheme
, host
, port
, path
, ident
,
811 if (NS_FAILED(rv
)) return rv
;
814 rv
= mAuthChannel
->GetLoadFlags(&loadFlags
);
815 if (NS_FAILED(rv
)) return rv
;
817 // Fill only for non-proxy auth, proxy credentials are not OA-isolated.
818 nsAutoCString suffix
;
821 nsCOMPtr
<nsIChannel
> chan
= do_QueryInterface(mAuthChannel
);
822 GetOriginAttributesSuffix(chan
, suffix
);
824 // if this is the first challenge, then try using the identity
825 // specified in the URL.
826 if (mIdent
.IsEmpty()) {
827 GetIdentityFromURI(authFlags
, mIdent
);
828 identFromURI
= !mIdent
.IsEmpty();
831 if ((loadFlags
& nsIRequest::LOAD_ANONYMOUS
) && !identFromURI
) {
832 LOG(("Skipping authentication for anonymous non-proxy request\n"));
833 return NS_ERROR_NOT_AVAILABLE
;
836 // Let explicit URL credentials pass
837 // regardless of the LOAD_ANONYMOUS flag
838 } else if ((loadFlags
& nsIRequest::LOAD_ANONYMOUS
) && !UsingHttpProxy()) {
839 LOG(("Skipping authentication for anonymous non-proxy request\n"));
840 return NS_ERROR_NOT_AVAILABLE
;
844 // if we already tried some credentials for this transaction, then
845 // we need to possibly clear them from the cache, unless the credentials
846 // in the cache have changed, in which case we'd want to give them a
849 nsHttpAuthEntry
* entry
= nullptr;
850 Unused
<< authCache
->GetAuthEntryForDomain(scheme
, host
, port
, realm
, suffix
,
853 // hold reference to the auth session state (in case we clear our
854 // reference to the entry).
855 nsCOMPtr
<nsISupports
> sessionStateGrip
;
856 if (entry
) sessionStateGrip
= entry
->mMetaData
;
858 // remember if we already had the continuation state. it means we are in
859 // the middle of the authentication exchange and the connection must be
860 // kept sticky then (and only then).
861 bool authAtProgress
= !!*continuationState
;
863 // for digest auth, maybe our cached nonce value simply timed out...
864 bool identityInvalid
;
865 nsISupports
* sessionState
= sessionStateGrip
;
866 rv
= auth
->ChallengeReceived(mAuthChannel
, aChallenge
, proxyAuth
,
867 &sessionState
, &*continuationState
,
869 sessionStateGrip
.swap(sessionState
);
870 if (NS_FAILED(rv
)) return rv
;
872 LOG((" identity invalid = %d\n", identityInvalid
));
874 if (mConnectionBased
&& identityInvalid
) {
875 // If the flag is set and identity is invalid, it means we received the
876 // first challange for a new negotiation round after negotiating a
877 // connection based auth failed (invalid password). The mConnectionBased
878 // flag is set later for the newly received challenge, so here it reflects
879 // the previous 401/7 response schema.
880 rv
= mAuthChannel
->CloseStickyConnection();
881 MOZ_ASSERT(NS_SUCCEEDED(rv
));
883 // We must clear proxy ident in the following scenario + explanation:
884 // - we are authenticating to an NTLM proxy and an NTLM server
885 // - we successfully authenticated to the proxy, mProxyIdent keeps
886 // the user name/domain and password, the identity has also been cached
887 // - we just threw away the connection because we are now asking for
888 // creds for the server (WWW auth)
889 // - hence, we will have to auth to the proxy again as well
890 // - if we didn't clear the proxy identity, it would be considered
891 // as non-valid and we would ask the user again ; clearing it forces
892 // use of the cached identity and not asking the user again
897 mConnectionBased
= !!(authFlags
& nsIHttpAuthenticator::CONNECTION_BASED
);
899 // It's legal if the peer closes the connection after the first 401/7.
900 // Making the connection sticky will prevent its restart giving the user
901 // a 'network reset' error every time. Hence, we mark the connection
903 mAuthChannel
->ConnectionRestartable(!authAtProgress
);
905 if (identityInvalid
) {
907 if (ident
->Equals(entry
->Identity())) {
909 LOG((" clearing bad auth cache entry\n"));
910 // ok, we've already tried this user identity, so clear the
911 // corresponding entry from the auth cache.
912 authCache
->ClearAuthEntry(scheme
, host
, port
, realm
, suffix
);
916 } else if (!identFromURI
||
917 (ident
->User() == entry
->Identity().User() &&
918 !(loadFlags
& (nsIChannel::LOAD_ANONYMOUS
|
919 nsIChannel::LOAD_EXPLICIT_CREDENTIALS
)))) {
920 LOG((" taking identity from auth cache\n"));
921 // the password from the auth cache is more likely to be
922 // correct than the one in the URL. at least, we know that it
923 // works with the given username. it is possible for a server
924 // to distinguish logons based on the supplied password alone,
925 // but that would be quite unusual... and i don't think we need
926 // to worry about such unorthodox cases.
927 *ident
= entry
->Identity();
928 identFromURI
= false;
929 if (entry
->Creds()[0] != '\0') {
930 LOG((" using cached credentials!\n"));
931 creds
.Assign(entry
->Creds());
932 return entry
->AddPath(path
);
935 } else if (!identFromURI
) {
936 // hmm... identity invalid, but no auth entry! the realm probably
937 // changed (see bug 201986).
941 if (!entry
&& ident
->IsEmpty()) {
942 uint32_t level
= nsIAuthPrompt2::LEVEL_NONE
;
943 if ((!proxyAuth
&& mUsingSSL
) || (proxyAuth
&& mProxyUsingSSL
)) {
944 level
= nsIAuthPrompt2::LEVEL_SECURE
;
945 } else if (authFlags
& nsIHttpAuthenticator::IDENTITY_ENCRYPTED
) {
946 level
= nsIAuthPrompt2::LEVEL_PW_ENCRYPTED
;
949 // Collect statistics on how frequently the various types of HTTP
950 // authentication are used over SSL and non-SSL connections.
951 if (Telemetry::CanRecordPrereleaseData()) {
952 if ("basic"_ns
.Equals(aAuthType
, nsCaseInsensitiveCStringComparator
)) {
953 Telemetry::Accumulate(
954 Telemetry::HTTP_AUTH_TYPE_STATS
,
955 UsingSSL() ? HTTP_AUTH_BASIC_SECURE
: HTTP_AUTH_BASIC_INSECURE
);
956 } else if ("digest"_ns
.Equals(aAuthType
,
957 nsCaseInsensitiveCStringComparator
)) {
958 Telemetry::Accumulate(
959 Telemetry::HTTP_AUTH_TYPE_STATS
,
960 UsingSSL() ? HTTP_AUTH_DIGEST_SECURE
: HTTP_AUTH_DIGEST_INSECURE
);
961 } else if ("ntlm"_ns
.Equals(aAuthType
,
962 nsCaseInsensitiveCStringComparator
)) {
963 Telemetry::Accumulate(
964 Telemetry::HTTP_AUTH_TYPE_STATS
,
965 UsingSSL() ? HTTP_AUTH_NTLM_SECURE
: HTTP_AUTH_NTLM_INSECURE
);
966 } else if ("negotiate"_ns
.Equals(aAuthType
,
967 nsCaseInsensitiveCStringComparator
)) {
968 Telemetry::Accumulate(Telemetry::HTTP_AUTH_TYPE_STATS
,
969 UsingSSL() ? HTTP_AUTH_NEGOTIATE_SECURE
970 : HTTP_AUTH_NEGOTIATE_INSECURE
);
974 // Depending on the pref setting, the authentication dialog may be
975 // blocked for all sub-resources, blocked for cross-origin
976 // sub-resources, or always allowed for sub-resources.
977 // For more details look at the bug 647010.
978 // BlockPrompt will set mCrossOrigin parameter as well.
979 if (BlockPrompt(proxyAuth
)) {
981 "nsHttpChannelAuthProvider::GetCredentialsForChallenge: "
982 "Prompt is blocked [this=%p pref=%d img-pref=%d "
983 "non-web-content-triggered-pref=%d]\n",
984 this, StaticPrefs::network_auth_subresource_http_auth_allow(),
986 network_auth_subresource_img_cross_origin_http_auth_allow(),
988 network_auth_non_web_content_triggered_resources_http_auth_allow()));
989 return NS_ERROR_ABORT
;
992 // at this point we are forced to interact with the user to get
993 // their username and password for this domain.
994 rv
= PromptForIdentity(level
, proxyAuth
, realm
, aAuthType
, authFlags
,
996 if (NS_FAILED(rv
)) return rv
;
997 identFromURI
= false;
1002 // Warn the user before automatically using the identity from the URL
1003 // to automatically log them into a site (see bug 232567).
1004 if (!ConfirmAuth("AutomaticAuth", false)) {
1005 // calling cancel here sets our mStatus and aborts the HTTP
1006 // transaction, which prevents OnDataAvailable events.
1007 rv
= mAuthChannel
->Cancel(NS_ERROR_ABORT
);
1008 MOZ_ASSERT(NS_SUCCEEDED(rv
));
1009 // this return code alone is not equivalent to Cancel, since
1010 // it only instructs our caller that authentication failed.
1011 // without an explicit call to Cancel, our caller would just
1012 // load the page that accompanies the HTTP auth challenge.
1013 return NS_ERROR_ABORT
;
1018 // get credentials for the given user:pass
1020 // always store the credentials we're trying now so that they will be used
1021 // on subsequent links. This will potentially remove good credentials from
1022 // the cache. This is ok as we don't want to use cached credentials if the
1023 // user specified something on the URI or in another manner. This is so
1024 // that we don't transparently authenticate as someone they're not
1025 // expecting to authenticate as.
1028 rv
= GenCredsAndSetEntry(auth
, proxyAuth
, scheme
, host
, port
, path
, realm
,
1029 aChallenge
, *ident
, sessionStateGrip
, creds
);
1033 bool nsHttpChannelAuthProvider::BlockPrompt(bool proxyAuth
) {
1034 // Verify that it's ok to prompt for credentials here, per spec
1035 // http://xhr.spec.whatwg.org/#the-send%28%29-method
1037 nsCOMPtr
<nsIHttpChannelInternal
> chanInternal
=
1038 do_QueryInterface(mAuthChannel
);
1039 MOZ_ASSERT(chanInternal
);
1041 if (chanInternal
->GetBlockAuthPrompt()) {
1043 ("nsHttpChannelAuthProvider::BlockPrompt: Prompt is blocked "
1044 "[this=%p channel=%p]\n",
1045 this, mAuthChannel
));
1050 // Do not block auth-dialog if this is a proxy authentication.
1054 nsCOMPtr
<nsIChannel
> chan
= do_QueryInterface(mAuthChannel
);
1055 nsCOMPtr
<nsILoadInfo
> loadInfo
= chan
->LoadInfo();
1057 // We will treat loads w/o loadInfo as a top level document.
1060 bool nonWebContent
= false;
1062 if (loadInfo
->GetExternalContentPolicyType() !=
1063 ExtContentPolicy::TYPE_DOCUMENT
) {
1068 nsCOMPtr
<nsIPrincipal
> triggeringPrinc
= loadInfo
->TriggeringPrincipal();
1069 if (triggeringPrinc
->IsSystemPrincipal()) {
1070 nonWebContent
= true;
1074 if (loadInfo
->GetExternalContentPolicyType() ==
1075 ExtContentPolicy::TYPE_XMLHTTPREQUEST
) {
1079 if (!topDoc
&& !xhr
) {
1080 nsCOMPtr
<nsIURI
> topURI
;
1081 Unused
<< chanInternal
->GetTopWindowURI(getter_AddRefs(topURI
));
1083 mCrossOrigin
= !NS_SecurityCompareURIs(topURI
, mURI
, true);
1085 nsIPrincipal
* loadingPrinc
= loadInfo
->GetLoadingPrincipal();
1086 MOZ_ASSERT(loadingPrinc
);
1087 mCrossOrigin
= !loadingPrinc
->IsSameOrigin(mURI
);
1091 if (Telemetry::CanRecordPrereleaseData()) {
1093 Telemetry::Accumulate(Telemetry::HTTP_AUTH_DIALOG_STATS_3
,
1094 HTTP_AUTH_DIALOG_TOP_LEVEL_DOC
);
1095 } else if (nonWebContent
) {
1096 Telemetry::Accumulate(Telemetry::HTTP_AUTH_DIALOG_STATS_3
,
1097 HTTP_AUTH_DIALOG_NON_WEB_CONTENT
);
1098 } else if (!mCrossOrigin
) {
1100 Telemetry::Accumulate(Telemetry::HTTP_AUTH_DIALOG_STATS_3
,
1101 HTTP_AUTH_DIALOG_SAME_ORIGIN_XHR
);
1103 Telemetry::Accumulate(Telemetry::HTTP_AUTH_DIALOG_STATS_3
,
1104 HTTP_AUTH_DIALOG_SAME_ORIGIN_SUBRESOURCE
);
1107 Telemetry::Accumulate(
1108 Telemetry::HTTP_AUTH_DIALOG_STATS_3
,
1109 static_cast<uint32_t>(loadInfo
->GetExternalContentPolicyType()));
1115 network_auth_non_web_content_triggered_resources_http_auth_allow() &&
1120 switch (StaticPrefs::network_auth_subresource_http_auth_allow()) {
1121 case SUBRESOURCE_AUTH_DIALOG_DISALLOW_ALL
:
1122 // Do not open the http-authentication credentials dialog for
1123 // the sub-resources.
1124 return !topDoc
&& !xhr
;
1125 case SUBRESOURCE_AUTH_DIALOG_DISALLOW_CROSS_ORIGIN
:
1126 // Open the http-authentication credentials dialog for
1127 // the sub-resources only if they are not cross-origin.
1128 return !topDoc
&& !xhr
&& mCrossOrigin
;
1129 case SUBRESOURCE_AUTH_DIALOG_ALLOW_ALL
:
1130 // Allow the http-authentication dialog for subresources.
1131 // If pref network.auth.subresource-img-cross-origin-http-auth-allow
1132 // is set, http-authentication dialog for image subresources is
1136 network_auth_subresource_img_cross_origin_http_auth_allow() &&
1138 ((loadInfo
->GetExternalContentPolicyType() ==
1139 ExtContentPolicy::TYPE_IMAGE
) ||
1140 (loadInfo
->GetExternalContentPolicyType() ==
1141 ExtContentPolicy::TYPE_IMAGESET
))) {
1146 // This is an invalid value.
1147 MOZ_ASSERT(false, "A non valid value!");
1152 inline void GetAuthType(const nsACString
& aChallenge
, nsCString
& authType
) {
1153 auto spaceIndex
= aChallenge
.FindChar(' ');
1154 authType
= Substring(aChallenge
, 0, spaceIndex
);
1155 // normalize to lowercase
1156 ToLowerCase(authType
);
1159 nsresult
nsHttpChannelAuthProvider::GetAuthenticator(
1160 const nsACString
& aChallenge
, nsCString
& authType
,
1161 nsIHttpAuthenticator
** auth
) {
1162 LOG(("nsHttpChannelAuthProvider::GetAuthenticator [this=%p channel=%p]\n",
1163 this, mAuthChannel
));
1165 GetAuthType(aChallenge
, authType
);
1167 nsCOMPtr
<nsIHttpAuthenticator
> authenticator
;
1168 #ifdef MOZ_AUTH_EXTENSION
1169 if (authType
.EqualsLiteral("negotiate")) {
1170 authenticator
= nsHttpNegotiateAuth::GetOrCreate();
1173 if (authType
.EqualsLiteral("basic")) {
1174 authenticator
= nsHttpBasicAuth::GetOrCreate();
1175 } else if (authType
.EqualsLiteral("digest")) {
1176 authenticator
= nsHttpDigestAuth::GetOrCreate();
1177 } else if (authType
.EqualsLiteral("ntlm")) {
1178 authenticator
= nsHttpNTLMAuth::GetOrCreate();
1179 } else if (authType
.EqualsLiteral("mock_auth") &&
1180 PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) {
1181 authenticator
= MockHttpAuth::Create();
1183 return NS_ERROR_FACTORY_NOT_REGISTERED
;
1186 if (!authenticator
) {
1187 // If called during shutdown it's possible that the singleton authenticator
1188 // was already cleared so we have a null one here.
1189 return NS_ERROR_NOT_AVAILABLE
;
1192 MOZ_ASSERT(authenticator
);
1193 authenticator
.forget(auth
);
1198 // buf contains "domain\user"
1199 static void ParseUserDomain(const nsAString
& buf
, nsDependentSubstring
& user
,
1200 nsDependentSubstring
& domain
) {
1201 auto backslashPos
= buf
.FindChar(u
'\\');
1202 if (backslashPos
!= kNotFound
) {
1203 domain
.Rebind(buf
, 0, backslashPos
);
1204 user
.Rebind(buf
, backslashPos
+ 1);
1208 void nsHttpChannelAuthProvider::GetIdentityFromURI(uint32_t authFlags
,
1209 nsHttpAuthIdentity
& ident
) {
1210 LOG(("nsHttpChannelAuthProvider::GetIdentityFromURI [this=%p channel=%p]\n",
1211 this, mAuthChannel
));
1213 nsAutoString userBuf
;
1214 nsAutoString passBuf
;
1218 mURI
->GetUsername(buf
);
1219 if (!buf
.IsEmpty()) {
1220 NS_UnescapeURL(buf
);
1221 CopyUTF8toUTF16(buf
, userBuf
);
1222 mURI
->GetPassword(buf
);
1223 if (!buf
.IsEmpty()) {
1224 NS_UnescapeURL(buf
);
1225 CopyUTF8toUTF16(buf
, passBuf
);
1229 if (!userBuf
.IsEmpty()) {
1230 nsDependentSubstring
user(userBuf
, 0);
1231 nsDependentSubstring
domain(u
""_ns
, 0);
1233 if (authFlags
& nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN
) {
1234 ParseUserDomain(userBuf
, user
, domain
);
1237 ident
= nsHttpAuthIdentity(domain
, user
, passBuf
);
1241 void nsHttpChannelAuthProvider::ParseRealm(const nsACString
& aChallenge
,
1242 nsACString
& realm
) {
1244 // From RFC2617 section 1.2, the realm value is defined as such:
1246 // realm = "realm" "=" realm-value
1247 // realm-value = quoted-string
1249 // but, we'll accept anything after the the "=" up to the first space, or
1250 // end-of-line, if the string is not quoted.
1253 Tokenizer
t(aChallenge
);
1255 // The challenge begins with the authType.
1256 // If we can't find that something has probably gone wrong.
1258 nsDependentCSubstring authType
;
1259 if (!t
.ReadWord(authType
)) {
1263 // Will return true if the tokenizer advanced the cursor - false otherwise.
1264 auto readParam
= [&](nsDependentCSubstring
& key
, nsAutoCString
& value
) {
1265 key
.Rebind(EmptyCString(), 0);
1269 if (!t
.ReadWord(key
)) {
1273 if (!t
.CheckChar('=')) {
1278 Tokenizer::Token token1
;
1281 if (!t
.Next(token1
)) {
1284 nsDependentCSubstring sub
;
1285 bool hasQuote
= false;
1286 if (token1
.Equals(Tokenizer::Token::Char('"'))) {
1289 t
.Claim(sub
, Tokenizer::ClaimInclusion::INCLUDE_LAST
);
1293 Tokenizer::Token token2
;
1294 while (t
.Next(token2
)) {
1295 if (hasQuote
&& token2
.Equals(Tokenizer::Token::Char('"')) &&
1296 !token1
.Equals(Tokenizer::Token::Char('\\'))) {
1299 if (!hasQuote
&& (token2
.Type() == Tokenizer::TokenType::TOKEN_WS
||
1300 token2
.Type() == Tokenizer::TokenType::TOKEN_EOL
)) {
1304 t
.Claim(sub
, Tokenizer::ClaimInclusion::INCLUDE_LAST
);
1305 if (!sub
.Equals(R
"(\)")) {
1314 while (!t
.CheckEOF()) {
1315 nsDependentCSubstring key
;
1316 nsAutoCString value
;
1317 // If we couldn't read anything, and the input isn't followed by a ,
1319 if (!readParam(key
, value
) && !t
.Check(Tokenizer::Token::Char(','))) {
1322 // When we find the first instance of realm we exit.
1323 // Theoretically there should be only one instance and we should fail
1324 // if there are more, but we're trying to preserve existing behaviour.
1325 if (key
.Equals("realm"_ns
, nsCaseInsensitiveCStringComparator
)) {
1332 class nsHTTPAuthInformation
: public nsAuthInformationHolder
{
1334 nsHTTPAuthInformation(uint32_t aFlags
, const nsString
& aRealm
,
1335 const nsACString
& aAuthType
)
1336 : nsAuthInformationHolder(aFlags
, aRealm
, aAuthType
) {}
1338 void SetToHttpAuthIdentity(uint32_t authFlags
, nsHttpAuthIdentity
& identity
);
1341 void nsHTTPAuthInformation::SetToHttpAuthIdentity(
1342 uint32_t authFlags
, nsHttpAuthIdentity
& identity
) {
1343 identity
= nsHttpAuthIdentity(Domain(), User(), Password());
1346 nsresult
nsHttpChannelAuthProvider::PromptForIdentity(
1347 uint32_t level
, bool proxyAuth
, const nsACString
& realm
,
1348 const nsACString
& authType
, uint32_t authFlags
, nsHttpAuthIdentity
& ident
) {
1349 LOG(("nsHttpChannelAuthProvider::PromptForIdentity [this=%p channel=%p]\n",
1350 this, mAuthChannel
));
1354 nsCOMPtr
<nsIInterfaceRequestor
> callbacks
;
1355 rv
= mAuthChannel
->GetNotificationCallbacks(getter_AddRefs(callbacks
));
1356 if (NS_FAILED(rv
)) return rv
;
1358 nsCOMPtr
<nsILoadGroup
> loadGroup
;
1359 rv
= mAuthChannel
->GetLoadGroup(getter_AddRefs(loadGroup
));
1360 if (NS_FAILED(rv
)) return rv
;
1362 nsCOMPtr
<nsIAuthPrompt2
> authPrompt
;
1363 GetAuthPrompt(callbacks
, proxyAuth
, getter_AddRefs(authPrompt
));
1364 if (!authPrompt
&& loadGroup
) {
1365 nsCOMPtr
<nsIInterfaceRequestor
> cbs
;
1366 loadGroup
->GetNotificationCallbacks(getter_AddRefs(cbs
));
1367 GetAuthPrompt(cbs
, proxyAuth
, getter_AddRefs(authPrompt
));
1369 if (!authPrompt
) return NS_ERROR_NO_INTERFACE
;
1371 // XXX i18n: need to support non-ASCII realm strings (see bug 41489)
1372 NS_ConvertASCIItoUTF16
realmU(realm
);
1374 // prompt the user...
1375 uint32_t promptFlags
= 0;
1377 promptFlags
|= nsIAuthInformation::AUTH_PROXY
;
1378 if (mTriedProxyAuth
) promptFlags
|= nsIAuthInformation::PREVIOUS_FAILED
;
1379 mTriedProxyAuth
= true;
1381 promptFlags
|= nsIAuthInformation::AUTH_HOST
;
1382 if (mTriedHostAuth
) promptFlags
|= nsIAuthInformation::PREVIOUS_FAILED
;
1383 mTriedHostAuth
= true;
1386 if (authFlags
& nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN
) {
1387 promptFlags
|= nsIAuthInformation::NEED_DOMAIN
;
1391 promptFlags
|= nsIAuthInformation::CROSS_ORIGIN_SUB_RESOURCE
;
1394 RefPtr
<nsHTTPAuthInformation
> holder
=
1395 new nsHTTPAuthInformation(promptFlags
, realmU
, authType
);
1396 if (!holder
) return NS_ERROR_OUT_OF_MEMORY
;
1398 nsCOMPtr
<nsIChannel
> channel(do_QueryInterface(mAuthChannel
, &rv
));
1399 if (NS_FAILED(rv
)) return rv
;
1401 rv
= authPrompt
->AsyncPromptAuth(channel
, this, nullptr, level
, holder
,
1402 getter_AddRefs(mAsyncPromptAuthCancelable
));
1404 if (NS_SUCCEEDED(rv
)) {
1405 // indicate using this error code that authentication prompt
1406 // result is expected asynchronously
1407 rv
= NS_ERROR_IN_PROGRESS
;
1409 // Fall back to synchronous prompt
1410 bool retval
= false;
1411 rv
= authPrompt
->PromptAuth(channel
, level
, holder
, &retval
);
1412 if (NS_FAILED(rv
)) return rv
;
1415 rv
= NS_ERROR_ABORT
;
1417 holder
->SetToHttpAuthIdentity(authFlags
, ident
);
1421 // remember that we successfully showed the user an auth dialog
1422 if (!proxyAuth
) mSuppressDefensiveAuth
= true;
1424 if (mConnectionBased
) {
1425 // Connection can be reset by the server in the meantime user is entering
1426 // the credentials. Result would be just a "Connection was reset" error.
1427 // Hence, we drop the current regardless if the user would make it on time
1428 // to provide credentials.
1429 // It's OK to send the NTLM type 1 message (response to the plain "NTLM"
1430 // challenge) on a new connection.
1432 DebugOnly
<nsresult
> rv
= mAuthChannel
->CloseStickyConnection();
1433 MOZ_ASSERT(NS_SUCCEEDED(rv
));
1440 NS_IMETHODIMP
nsHttpChannelAuthProvider::OnAuthAvailable(
1441 nsISupports
* aContext
, nsIAuthInformation
* aAuthInfo
) {
1442 LOG(("nsHttpChannelAuthProvider::OnAuthAvailable [this=%p channel=%p]", this,
1445 mAsyncPromptAuthCancelable
= nullptr;
1446 if (!mAuthChannel
) return NS_OK
;
1452 nsHttpAuthIdentity
* ident
;
1453 nsAutoCString path
, scheme
;
1454 nsISupports
** continuationState
;
1455 rv
= GetAuthorizationMembers(mProxyAuth
, scheme
, host
, port
, path
, ident
,
1457 if (NS_FAILED(rv
)) OnAuthCancelled(aContext
, false);
1459 nsAutoCString realm
;
1460 ParseRealm(mCurrentChallenge
, realm
);
1462 nsCOMPtr
<nsIChannel
> chan
= do_QueryInterface(mAuthChannel
);
1463 nsAutoCString suffix
;
1465 // Fill only for non-proxy auth, proxy credentials are not OA-isolated.
1466 GetOriginAttributesSuffix(chan
, suffix
);
1469 nsHttpAuthCache
* authCache
= gHttpHandler
->AuthCache(mIsPrivate
);
1470 nsHttpAuthEntry
* entry
= nullptr;
1471 Unused
<< authCache
->GetAuthEntryForDomain(scheme
, host
, port
, realm
, suffix
,
1474 nsCOMPtr
<nsISupports
> sessionStateGrip
;
1475 if (entry
) sessionStateGrip
= entry
->mMetaData
;
1477 nsAuthInformationHolder
* holder
=
1478 static_cast<nsAuthInformationHolder
*>(aAuthInfo
);
1480 nsHttpAuthIdentity(holder
->Domain(), holder
->User(), holder
->Password());
1482 nsAutoCString unused
;
1483 nsCOMPtr
<nsIHttpAuthenticator
> auth
;
1484 rv
= GetAuthenticator(mCurrentChallenge
, unused
, getter_AddRefs(auth
));
1485 if (NS_FAILED(rv
)) {
1486 MOZ_ASSERT(false, "GetAuthenticator failed");
1487 OnAuthCancelled(aContext
, true);
1492 rv
= GenCredsAndSetEntry(auth
, mProxyAuth
, scheme
, host
, port
, path
, realm
,
1493 mCurrentChallenge
, *ident
, sessionStateGrip
, creds
);
1495 mCurrentChallenge
.Truncate();
1496 if (NS_FAILED(rv
)) {
1497 OnAuthCancelled(aContext
, true);
1501 return ContinueOnAuthAvailable(creds
);
1504 NS_IMETHODIMP
nsHttpChannelAuthProvider::OnAuthCancelled(nsISupports
* aContext
,
1506 LOG(("nsHttpChannelAuthProvider::OnAuthCancelled [this=%p channel=%p]", this,
1509 mAsyncPromptAuthCancelable
= nullptr;
1510 if (!mAuthChannel
) return NS_OK
;
1512 // When user cancels or auth fails we want to close the connection for
1513 // connection based schemes like NTLM. Some servers don't like re-negotiation
1514 // on the same connection.
1516 if (mConnectionBased
) {
1517 rv
= mAuthChannel
->CloseStickyConnection();
1518 MOZ_ASSERT(NS_SUCCEEDED(rv
));
1519 mConnectionBased
= false;
1522 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(mAuthChannel
);
1525 Unused
<< channel
->GetStatus(&status
);
1526 if (NS_FAILED(status
)) {
1527 // If the channel is already cancelled, there is no need to deal with the
1529 LOG((" Clear mRemainingChallenges, since mAuthChannel is cancelled"));
1530 mRemainingChallenges
.Truncate();
1535 if (!mRemainingChallenges
.IsEmpty()) {
1536 // there are still some challenges to process, do so
1538 // Get rid of current continuationState to avoid reusing it in
1539 // next challenges since it is no longer relevant.
1541 NS_IF_RELEASE(mProxyAuthContinuationState
);
1543 NS_IF_RELEASE(mAuthContinuationState
);
1545 nsAutoCString creds
;
1546 rv
= GetCredentials(mRemainingChallenges
, mProxyAuth
, creds
);
1547 if (NS_SUCCEEDED(rv
)) {
1548 // GetCredentials loaded the credentials from the cache or
1549 // some other way in a synchronous manner, process those
1551 mRemainingChallenges
.Truncate();
1552 return ContinueOnAuthAvailable(creds
);
1554 if (rv
== NS_ERROR_IN_PROGRESS
) {
1555 // GetCredentials successfully queued another authprompt for
1556 // a challenge from the list, we are now waiting for the user
1557 // to provide the credentials
1561 // otherwise, we failed...
1564 mRemainingChallenges
.Truncate();
1567 rv
= mAuthChannel
->OnAuthCancelled(userCancel
);
1568 MOZ_ASSERT(NS_SUCCEEDED(rv
));
1573 NS_IMETHODIMP
nsHttpChannelAuthProvider::OnCredsGenerated(
1574 const nsACString
& aGeneratedCreds
, uint32_t aFlags
, nsresult aResult
,
1575 nsISupports
* aSessionState
, nsISupports
* aContinuationState
) {
1578 MOZ_ASSERT(NS_IsMainThread());
1580 // When channel is closed, do not proceed
1581 if (!mAuthChannel
) {
1585 mGenerateCredentialsCancelable
= nullptr;
1587 if (NS_FAILED(aResult
)) {
1588 return OnAuthCancelled(nullptr, true);
1591 // We want to update m(Proxy)AuthContinuationState in case it was changed by
1592 // nsHttpNegotiateAuth::GenerateCredentials
1593 nsCOMPtr
<nsISupports
> contState(aContinuationState
);
1595 contState
.swap(mProxyAuthContinuationState
);
1597 contState
.swap(mAuthContinuationState
);
1600 nsCOMPtr
<nsIHttpAuthenticator
> auth
;
1601 nsAutoCString unused
;
1602 rv
= GetAuthenticator(mCurrentChallenge
, unused
, getter_AddRefs(auth
));
1603 NS_ENSURE_SUCCESS(rv
, rv
);
1607 nsHttpAuthIdentity
* ident
;
1608 nsAutoCString directory
, scheme
;
1609 nsISupports
** unusedContinuationState
;
1611 // Get realm from challenge
1612 nsAutoCString realm
;
1613 ParseRealm(mCurrentChallenge
, realm
);
1615 rv
= GetAuthorizationMembers(mProxyAuth
, scheme
, host
, port
, directory
, ident
,
1616 unusedContinuationState
);
1617 if (NS_FAILED(rv
)) return rv
;
1620 UpdateCache(auth
, scheme
, host
, port
, directory
, realm
, mCurrentChallenge
,
1621 *ident
, aGeneratedCreds
, aFlags
, aSessionState
, mProxyAuth
);
1622 MOZ_ASSERT(NS_SUCCEEDED(rv
));
1623 mCurrentChallenge
.Truncate();
1625 rv
= ContinueOnAuthAvailable(aGeneratedCreds
);
1626 MOZ_ASSERT(NS_SUCCEEDED(rv
));
1630 nsresult
nsHttpChannelAuthProvider::ContinueOnAuthAvailable(
1631 const nsACString
& creds
) {
1634 rv
= mAuthChannel
->SetProxyCredentials(creds
);
1636 rv
= mAuthChannel
->SetWWWCredentials(creds
);
1638 if (NS_FAILED(rv
)) return rv
;
1640 // drop our remaining list of challenges. We don't need them, because we
1641 // have now authenticated against a challenge and will be sending that
1642 // information to the server (or proxy). If it doesn't accept our
1643 // authentication it'll respond with failure and resend the challenge list
1644 mRemainingChallenges
.Truncate();
1646 Unused
<< mAuthChannel
->OnAuthAvailable();
1651 bool nsHttpChannelAuthProvider::ConfirmAuth(const char* bundleKey
,
1652 bool doYesNoPrompt
) {
1653 // skip prompting the user if
1654 // 1) prompts are disabled by pref
1655 // 2) we've already prompted the user
1656 // 3) we're not a toplevel channel
1657 // 4) the userpass length is less than the "phishy" threshold
1659 if (!StaticPrefs::network_auth_confirmAuth_enabled()) {
1664 nsresult rv
= mAuthChannel
->GetLoadFlags(&loadFlags
);
1665 if (NS_FAILED(rv
)) return true;
1667 if (mSuppressDefensiveAuth
||
1668 !(loadFlags
& nsIChannel::LOAD_INITIAL_DOCUMENT_URI
)) {
1672 nsAutoCString userPass
;
1673 rv
= mURI
->GetUserPass(userPass
);
1674 if (NS_FAILED(rv
) ||
1675 (userPass
.Length() < gHttpHandler
->PhishyUserPassLength())) {
1679 // we try to confirm by prompting the user. if we cannot do so, then
1680 // assume the user said ok. this is done to keep things working in
1681 // embedded builds, where the string bundle might not be present, etc.
1683 nsCOMPtr
<nsIStringBundleService
> bundleService
=
1684 do_GetService(NS_STRINGBUNDLE_CONTRACTID
);
1685 if (!bundleService
) return true;
1687 nsCOMPtr
<nsIStringBundle
> bundle
;
1688 bundleService
->CreateBundle(NECKO_MSGS_URL
, getter_AddRefs(bundle
));
1689 if (!bundle
) return true;
1692 rv
= mURI
->GetHost(host
);
1693 if (NS_FAILED(rv
)) return true;
1696 rv
= mURI
->GetUsername(user
);
1697 if (NS_FAILED(rv
)) return true;
1699 NS_ConvertUTF8toUTF16
ucsHost(host
), ucsUser(user
);
1701 size_t userLength
= ucsUser
.Length();
1702 if (userLength
> MAX_DISPLAYED_USER_LENGTH
) {
1703 size_t desiredLength
= MAX_DISPLAYED_USER_LENGTH
;
1704 // Don't cut off right before a low surrogate. Just include it.
1705 if (NS_IS_LOW_SURROGATE(ucsUser
[desiredLength
])) {
1708 ucsUser
.Replace(desiredLength
, userLength
- desiredLength
,
1709 nsContentUtils::GetLocalizedEllipsis());
1712 size_t hostLen
= ucsHost
.Length();
1713 if (hostLen
> MAX_DISPLAYED_HOST_LENGTH
) {
1714 size_t cutPoint
= hostLen
- MAX_DISPLAYED_HOST_LENGTH
;
1715 // Likewise, don't cut off right before a low surrogate here.
1716 // Keep the low surrogate
1717 if (NS_IS_LOW_SURROGATE(ucsHost
[cutPoint
])) {
1720 // It's possible cutPoint was 1 and is now 0. Only insert the ellipsis
1721 // if we're actually removing anything.
1723 ucsHost
.Replace(0, cutPoint
, nsContentUtils::GetLocalizedEllipsis());
1727 AutoTArray
<nsString
, 2> strs
= {ucsHost
, ucsUser
};
1730 rv
= bundle
->FormatStringFromName(bundleKey
, strs
, msg
);
1731 if (NS_FAILED(rv
)) return true;
1733 nsCOMPtr
<nsIInterfaceRequestor
> callbacks
;
1734 rv
= mAuthChannel
->GetNotificationCallbacks(getter_AddRefs(callbacks
));
1735 if (NS_FAILED(rv
)) return true;
1737 nsCOMPtr
<nsILoadGroup
> loadGroup
;
1738 rv
= mAuthChannel
->GetLoadGroup(getter_AddRefs(loadGroup
));
1739 if (NS_FAILED(rv
)) return true;
1741 nsCOMPtr
<nsIPromptService
> promptSvc
=
1742 do_GetService("@mozilla.org/prompter;1", &rv
);
1743 if (NS_FAILED(rv
) || !promptSvc
) {
1747 // do not prompt again
1748 mSuppressDefensiveAuth
= true;
1750 // Get current browsing context to use as prompt parent
1751 nsCOMPtr
<nsIChannel
> chan
= do_QueryInterface(mAuthChannel
);
1756 nsCOMPtr
<nsILoadInfo
> loadInfo
= chan
->LoadInfo();
1757 RefPtr
<mozilla::dom::BrowsingContext
> browsingContext
;
1758 loadInfo
->GetBrowsingContext(getter_AddRefs(browsingContext
));
1761 if (doYesNoPrompt
) {
1763 bool checkState
= false;
1764 rv
= promptSvc
->ConfirmExBC(
1765 browsingContext
, StaticPrefs::prompts_modalType_confirmAuth(), nullptr,
1767 nsIPromptService::BUTTON_POS_1_DEFAULT
+
1768 nsIPromptService::STD_YES_NO_BUTTONS
,
1769 nullptr, nullptr, nullptr, nullptr, &checkState
, &choice
);
1770 if (NS_FAILED(rv
)) return true;
1772 confirmed
= choice
== 0;
1774 rv
= promptSvc
->ConfirmBC(browsingContext
,
1775 StaticPrefs::prompts_modalType_confirmAuth(),
1776 nullptr, msg
.get(), &confirmed
);
1777 if (NS_FAILED(rv
)) return true;
1783 void nsHttpChannelAuthProvider::SetAuthorizationHeader(
1784 nsHttpAuthCache
* authCache
, const nsHttpAtom
& header
,
1785 const nsACString
& scheme
, const nsACString
& host
, int32_t port
,
1786 const nsACString
& path
, nsHttpAuthIdentity
& ident
) {
1787 nsHttpAuthEntry
* entry
= nullptr;
1790 // set informations that depend on whether
1791 // we're authenticating against a proxy
1793 nsISupports
** continuationState
;
1795 nsAutoCString suffix
;
1796 if (header
== nsHttp::Proxy_Authorization
) {
1797 continuationState
= &mProxyAuthContinuationState
;
1801 mProxyInfo
->GetType(type
);
1802 if (type
.EqualsLiteral("https")) {
1803 // Let this be overriden by anything from the cache.
1804 auto const& pa
= mProxyInfo
->ProxyAuthorizationHeader();
1805 if (!pa
.IsEmpty()) {
1806 rv
= mAuthChannel
->SetProxyCredentials(pa
);
1807 MOZ_ASSERT(NS_SUCCEEDED(rv
));
1812 continuationState
= &mAuthContinuationState
;
1814 nsCOMPtr
<nsIChannel
> chan
= do_QueryInterface(mAuthChannel
);
1815 GetOriginAttributesSuffix(chan
, suffix
);
1818 rv
= authCache
->GetAuthEntryForPath(scheme
, host
, port
, path
, suffix
, &entry
);
1819 if (NS_SUCCEEDED(rv
)) {
1820 // if we are trying to add a header for origin server auth and if the
1821 // URL contains an explicit username, then try the given username first.
1822 // we only want to do this, however, if we know the URL requires auth
1823 // based on the presence of an auth cache entry for this URL (which is
1824 // true since we are here). but, if the username from the URL matches
1825 // the username from the cache, then we should prefer the password
1826 // stored in the cache since that is most likely to be valid.
1827 if (header
== nsHttp::Authorization
&& entry
->Domain()[0] == '\0') {
1828 GetIdentityFromURI(0, ident
);
1829 // if the usernames match, then clear the ident so we will pick
1830 // up the one from the auth cache instead.
1831 // when this is undesired, specify LOAD_EXPLICIT_CREDENTIALS load
1833 if (ident
.User() == entry
->User()) {
1835 if (NS_SUCCEEDED(mAuthChannel
->GetLoadFlags(&loadFlags
)) &&
1836 !(loadFlags
& nsIChannel::LOAD_EXPLICIT_CREDENTIALS
)) {
1842 if (ident
.IsEmpty()) {
1843 ident
= entry
->Identity();
1844 identFromURI
= false;
1846 identFromURI
= true;
1849 nsCString temp
; // this must have the same lifetime as creds
1850 nsAutoCString
creds(entry
->Creds());
1851 // we can only send a preemptive Authorization header if we have either
1852 // stored credentials or a stored challenge from which to derive
1853 // credentials. if the identity is from the URI, then we cannot use
1854 // the stored credentials.
1855 if ((creds
.IsEmpty() || identFromURI
) && !entry
->Challenge().IsEmpty()) {
1856 nsCOMPtr
<nsIHttpAuthenticator
> auth
;
1857 nsAutoCString unused
;
1858 rv
= GetAuthenticator(entry
->Challenge(), unused
, getter_AddRefs(auth
));
1859 if (NS_SUCCEEDED(rv
)) {
1860 bool proxyAuth
= (header
== nsHttp::Proxy_Authorization
);
1861 rv
= GenCredsAndSetEntry(auth
, proxyAuth
, scheme
, host
, port
, path
,
1862 entry
->Realm(), entry
->Challenge(), ident
,
1863 entry
->mMetaData
, temp
);
1864 if (NS_SUCCEEDED(rv
)) creds
= temp
;
1866 // make sure the continuation state is null since we do not
1867 // support mixing preemptive and 'multirequest' authentication.
1868 NS_IF_RELEASE(*continuationState
);
1871 if (!creds
.IsEmpty()) {
1872 LOG((" adding \"%s\" request header\n", header
.get()));
1873 if (header
== nsHttp::Proxy_Authorization
) {
1874 rv
= mAuthChannel
->SetProxyCredentials(creds
);
1875 MOZ_ASSERT(NS_SUCCEEDED(rv
));
1877 rv
= mAuthChannel
->SetWWWCredentials(creds
);
1878 MOZ_ASSERT(NS_SUCCEEDED(rv
));
1881 // suppress defensive auth prompting for this channel since we know
1882 // that we already prompted at least once this session. we only do
1883 // this for non-proxy auth since the URL's userpass is not used for
1885 if (header
== nsHttp::Authorization
) mSuppressDefensiveAuth
= true;
1887 ident
.Clear(); // don't remember the identity
1892 nsresult
nsHttpChannelAuthProvider::GetCurrentPath(nsACString
& path
) {
1894 nsCOMPtr
<nsIURL
> url
= do_QueryInterface(mURI
);
1896 rv
= url
->GetDirectory(path
);
1898 rv
= mURI
->GetPathQueryRef(path
);
1903 NS_IMPL_ISUPPORTS(nsHttpChannelAuthProvider
, nsICancelable
,
1904 nsIHttpChannelAuthProvider
, nsIAuthPromptCallback
,
1905 nsIHttpAuthenticatorCallback
)
1907 } // namespace mozilla::net