nsHttpChannelAuthProvider.cpp (56341B)
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/. */ 6 7 // HttpLog.h should generally be included first 8 #include "HttpLog.h" 9 10 #include "mozilla/BasePrincipal.h" 11 #include "mozilla/Components.h" 12 #include "mozilla/StoragePrincipalHelper.h" 13 #include "mozilla/Tokenizer.h" 14 #include "MockHttpAuth.h" 15 #include "nsHttpChannelAuthProvider.h" 16 #include "nsCRT.h" 17 #include "nsNetUtil.h" 18 #include "nsHttpHandler.h" 19 #include "nsIHttpAuthenticator.h" 20 #include "nsIHttpChannelInternal.h" 21 #include "nsIAuthPrompt2.h" 22 #include "nsIAuthPromptProvider.h" 23 #include "nsIInterfaceRequestor.h" 24 #include "nsIInterfaceRequestorUtils.h" 25 #include "nsEscape.h" 26 #include "nsAuthInformationHolder.h" 27 #include "nsIStringBundle.h" 28 #include "nsIPromptService.h" 29 #include "netCore.h" 30 #include "nsIHttpAuthenticableChannel.h" 31 #include "nsIURI.h" 32 #include "nsContentUtils.h" 33 #include "nsHttp.h" 34 #include "nsHttpBasicAuth.h" 35 #include "nsHttpDigestAuth.h" 36 #ifdef MOZ_AUTH_EXTENSION 37 # include "nsHttpNegotiateAuth.h" 38 #endif 39 #include "nsHttpNTLMAuth.h" 40 #include "nsServiceManagerUtils.h" 41 #include "nsIURL.h" 42 #include "mozilla/StaticPrefs_network.h" 43 #include "mozilla/StaticPrefs_prompts.h" 44 #include "nsIProxiedChannel.h" 45 #include "nsIProxyInfo.h" 46 47 namespace mozilla::net { 48 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 52 53 #define MAX_DISPLAYED_USER_LENGTH 64 54 #define MAX_DISPLAYED_HOST_LENGTH 64 55 56 static void GetOriginAttributesSuffix(nsIChannel* aChan, nsACString& aSuffix) { 57 OriginAttributes oa; 58 59 // Deliberately ignoring the result and going with defaults 60 if (aChan) { 61 StoragePrincipalHelper::GetOriginAttributesForNetworkState(aChan, oa); 62 } 63 64 oa.CreateSuffix(aSuffix); 65 } 66 67 nsHttpChannelAuthProvider::nsHttpChannelAuthProvider() 68 : mProxyAuth(false), 69 mTriedProxyAuth(false), 70 mTriedHostAuth(false), 71 mCrossOrigin(false), 72 mConnectionBased(false), 73 mHttpHandler(gHttpHandler) {} 74 75 nsHttpChannelAuthProvider::~nsHttpChannelAuthProvider() { 76 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 77 MOZ_ASSERT(!mAuthChannel, "Disconnect wasn't called"); 78 } 79 80 NS_IMETHODIMP 81 nsHttpChannelAuthProvider::Init(nsIHttpAuthenticableChannel* channel) { 82 MOZ_ASSERT(channel, "channel expected!"); 83 84 mAuthChannel = channel; 85 86 nsresult rv = mAuthChannel->GetURI(getter_AddRefs(mURI)); 87 if (NS_FAILED(rv)) return rv; 88 89 rv = mAuthChannel->GetIsSSL(&mUsingSSL); 90 if (NS_FAILED(rv)) return rv; 91 92 nsCOMPtr<nsIProxiedChannel> proxied(channel); 93 if (proxied) { 94 nsCOMPtr<nsIProxyInfo> pi; 95 rv = proxied->GetProxyInfo(getter_AddRefs(pi)); 96 if (NS_FAILED(rv)) return rv; 97 98 if (pi) { 99 nsAutoCString proxyType; 100 rv = pi->GetType(proxyType); 101 if (NS_FAILED(rv)) return rv; 102 103 mProxyUsingSSL = proxyType.EqualsLiteral("https"); 104 } 105 } 106 107 rv = mURI->GetAsciiHost(mHost); 108 if (NS_FAILED(rv)) return rv; 109 110 // reject the URL if it doesn't specify a host 111 if (mHost.IsEmpty()) return NS_ERROR_MALFORMED_URI; 112 113 rv = mURI->GetPort(&mPort); 114 if (NS_FAILED(rv)) return rv; 115 116 nsCOMPtr<nsIChannel> bareChannel = do_QueryInterface(channel); 117 mIsPrivate = NS_UsePrivateBrowsing(bareChannel); 118 119 return NS_OK; 120 } 121 122 NS_IMETHODIMP 123 nsHttpChannelAuthProvider::ProcessAuthentication(uint32_t httpStatus, 124 bool SSLConnectFailed) { 125 LOG( 126 ("nsHttpChannelAuthProvider::ProcessAuthentication " 127 "[this=%p channel=%p code=%u SSLConnectFailed=%d]\n", 128 this, mAuthChannel, httpStatus, SSLConnectFailed)); 129 130 MOZ_ASSERT(mAuthChannel, "Channel not initialized"); 131 132 nsCOMPtr<nsIProxyInfo> proxyInfo; 133 nsresult rv = mAuthChannel->GetProxyInfo(getter_AddRefs(proxyInfo)); 134 if (NS_FAILED(rv)) return rv; 135 if (proxyInfo) { 136 mProxyInfo = do_QueryInterface(proxyInfo); 137 if (!mProxyInfo) return NS_ERROR_NO_INTERFACE; 138 } 139 140 nsAutoCString challenges; 141 mProxyAuth = (httpStatus == 407); 142 143 rv = PrepareForAuthentication(mProxyAuth); 144 if (NS_FAILED(rv)) return rv; 145 146 if (mProxyAuth) { 147 // only allow a proxy challenge if we have a proxy server configured. 148 // otherwise, we could inadvertently expose the user's proxy 149 // credentials to an origin server. We could attempt to proceed as 150 // if we had received a 401 from the server, but why risk flirting 151 // with trouble? IE similarly rejects 407s when a proxy server is 152 // not configured, so there's no reason not to do the same. 153 if (!UsingHttpProxy()) { 154 LOG(("rejecting 407 when proxy server not configured!\n")); 155 return NS_ERROR_UNEXPECTED; 156 } 157 if (UsingSSL() && !SSLConnectFailed) { 158 // we need to verify that this challenge came from the proxy 159 // server itself, and not some server on the other side of the 160 // SSL tunnel. 161 LOG(("rejecting 407 from origin server!\n")); 162 return NS_ERROR_UNEXPECTED; 163 } 164 rv = mAuthChannel->GetProxyChallenges(challenges); 165 } else { 166 rv = mAuthChannel->GetWWWChallenges(challenges); 167 } 168 if (NS_FAILED(rv)) return rv; 169 170 nsAutoCString creds; 171 rv = GetCredentials(challenges, mProxyAuth, creds); 172 if (rv == NS_ERROR_IN_PROGRESS || rv == NS_ERROR_BASIC_HTTP_AUTH_DISABLED) { 173 return rv; 174 } 175 176 if (NS_FAILED(rv)) { 177 LOG(("unable to authenticate\n")); 178 } else { 179 // set the authentication credentials 180 if (mProxyAuth) { 181 rv = mAuthChannel->SetProxyCredentials(creds); 182 } else { 183 rv = mAuthChannel->SetWWWCredentials(creds); 184 } 185 } 186 return rv; 187 } 188 189 NS_IMETHODIMP 190 nsHttpChannelAuthProvider::AddAuthorizationHeaders( 191 bool aDontUseCachedWWWCreds) { 192 LOG( 193 ("nsHttpChannelAuthProvider::AddAuthorizationHeaders? " 194 "[this=%p channel=%p]\n", 195 this, mAuthChannel)); 196 197 MOZ_ASSERT(mAuthChannel, "Channel not initialized"); 198 199 nsCOMPtr<nsIProxyInfo> proxyInfo; 200 nsresult rv = mAuthChannel->GetProxyInfo(getter_AddRefs(proxyInfo)); 201 if (NS_FAILED(rv)) return rv; 202 if (proxyInfo) { 203 mProxyInfo = do_QueryInterface(proxyInfo); 204 if (!mProxyInfo) return NS_ERROR_NO_INTERFACE; 205 } 206 207 uint32_t loadFlags; 208 rv = mAuthChannel->GetLoadFlags(&loadFlags); 209 if (NS_FAILED(rv)) return rv; 210 211 // this getter never fails 212 nsHttpAuthCache* authCache = gHttpHandler->AuthCache(mIsPrivate); 213 214 // check if proxy credentials should be sent 215 if (!ProxyHost().IsEmpty() && UsingHttpProxy()) { 216 SetAuthorizationHeader(authCache, nsHttp::Proxy_Authorization, "http"_ns, 217 ProxyHost(), ProxyPort(), 218 ""_ns, // proxy has no path 219 mProxyIdent); 220 } 221 222 if (loadFlags & nsIRequest::LOAD_ANONYMOUS) { 223 LOG(("Skipping Authorization header for anonymous load\n")); 224 return NS_OK; 225 } 226 227 if (aDontUseCachedWWWCreds) { 228 LOG( 229 ("Authorization header already present:" 230 " skipping adding auth header from cache\n")); 231 return NS_OK; 232 } 233 234 // check if server credentials should be sent 235 nsAutoCString path, scheme; 236 if (NS_SUCCEEDED(GetCurrentPath(path)) && 237 NS_SUCCEEDED(mURI->GetScheme(scheme))) { 238 SetAuthorizationHeader(authCache, nsHttp::Authorization, scheme, Host(), 239 Port(), path, mIdent); 240 } 241 242 return NS_OK; 243 } 244 245 NS_IMETHODIMP 246 nsHttpChannelAuthProvider::Cancel(nsresult status) { 247 MOZ_ASSERT(mAuthChannel, "Channel not initialized"); 248 249 if (mAsyncPromptAuthCancelable) { 250 mAsyncPromptAuthCancelable->Cancel(status); 251 mAsyncPromptAuthCancelable = nullptr; 252 } 253 254 if (mGenerateCredentialsCancelable) { 255 mGenerateCredentialsCancelable->Cancel(status); 256 mGenerateCredentialsCancelable = nullptr; 257 } 258 return NS_OK; 259 } 260 261 NS_IMETHODIMP 262 nsHttpChannelAuthProvider::Disconnect(nsresult status) { 263 mAuthChannel = nullptr; 264 265 if (mAsyncPromptAuthCancelable) { 266 mAsyncPromptAuthCancelable->Cancel(status); 267 mAsyncPromptAuthCancelable = nullptr; 268 } 269 270 if (mGenerateCredentialsCancelable) { 271 mGenerateCredentialsCancelable->Cancel(status); 272 mGenerateCredentialsCancelable = nullptr; 273 } 274 275 NS_IF_RELEASE(mProxyAuthContinuationState); 276 NS_IF_RELEASE(mAuthContinuationState); 277 278 return NS_OK; 279 } 280 281 // helper function for getting an auth prompt from an interface requestor 282 static void GetAuthPrompt(nsIInterfaceRequestor* ifreq, bool proxyAuth, 283 nsIAuthPrompt2** result) { 284 if (!ifreq) return; 285 286 uint32_t promptReason; 287 if (proxyAuth) { 288 promptReason = nsIAuthPromptProvider::PROMPT_PROXY; 289 } else { 290 promptReason = nsIAuthPromptProvider::PROMPT_NORMAL; 291 } 292 293 nsCOMPtr<nsIAuthPromptProvider> promptProvider = do_GetInterface(ifreq); 294 if (promptProvider) { 295 promptProvider->GetAuthPrompt(promptReason, NS_GET_IID(nsIAuthPrompt2), 296 reinterpret_cast<void**>(result)); 297 } else { 298 NS_QueryAuthPrompt2(ifreq, result); 299 } 300 } 301 302 // generate credentials for the given challenge, and update the auth cache. 303 nsresult nsHttpChannelAuthProvider::GenCredsAndSetEntry( 304 nsIHttpAuthenticator* auth, bool proxyAuth, const nsACString& scheme, 305 const nsACString& host, int32_t port, const nsACString& directory, 306 const nsACString& realm, const nsACString& challenge, 307 const nsHttpAuthIdentity& ident, nsCOMPtr<nsISupports>& sessionState, 308 nsACString& result) { 309 nsresult rv; 310 nsISupports* ss = sessionState; 311 312 // set informations that depend on whether 313 // we're authenticating against a proxy 314 // or a webserver 315 nsISupports** continuationState; 316 317 if (proxyAuth) { 318 continuationState = &mProxyAuthContinuationState; 319 } else { 320 continuationState = &mAuthContinuationState; 321 } 322 323 rv = auth->GenerateCredentialsAsync( 324 mAuthChannel, this, challenge, proxyAuth, ident.Domain(), ident.User(), 325 ident.Password(), ss, *continuationState, 326 getter_AddRefs(mGenerateCredentialsCancelable)); 327 if (NS_SUCCEEDED(rv)) { 328 // Calling generate credentials async, results will be dispatched to the 329 // main thread by calling OnCredsGenerated method 330 return NS_ERROR_IN_PROGRESS; 331 } 332 333 uint32_t generateFlags; 334 rv = auth->GenerateCredentials( 335 mAuthChannel, challenge, proxyAuth, ident.Domain(), ident.User(), 336 ident.Password(), &ss, &*continuationState, &generateFlags, result); 337 338 sessionState.swap(ss); 339 if (NS_FAILED(rv)) return rv; 340 341 // don't log this in release build since it could contain sensitive info. 342 #ifdef DEBUG 343 LOG(("generated creds: %s\n", result.BeginReading())); 344 #endif 345 346 return UpdateCache(auth, scheme, host, port, directory, realm, challenge, 347 ident, result, generateFlags, sessionState, proxyAuth); 348 } 349 350 nsresult nsHttpChannelAuthProvider::UpdateCache( 351 nsIHttpAuthenticator* auth, const nsACString& scheme, 352 const nsACString& host, int32_t port, const nsACString& directory, 353 const nsACString& realm, const nsACString& challenge, 354 const nsHttpAuthIdentity& ident, const nsACString& creds, 355 uint32_t generateFlags, nsISupports* sessionState, bool aProxyAuth) { 356 nsresult rv; 357 358 uint32_t authFlags; 359 rv = auth->GetAuthFlags(&authFlags); 360 if (NS_FAILED(rv)) return rv; 361 362 // find out if this authenticator allows reuse of credentials and/or 363 // challenge. 364 bool saveCreds = 365 0 != (authFlags & nsIHttpAuthenticator::REUSABLE_CREDENTIALS); 366 bool saveChallenge = 367 0 != (authFlags & nsIHttpAuthenticator::REUSABLE_CHALLENGE); 368 369 bool saveIdentity = 370 0 == (generateFlags & nsIHttpAuthenticator::USING_INTERNAL_IDENTITY); 371 372 // this getter never fails 373 nsHttpAuthCache* authCache = gHttpHandler->AuthCache(mIsPrivate); 374 375 nsAutoCString suffix; 376 if (!aProxyAuth) { 377 // We don't isolate proxy credentials cache entries with the origin suffix 378 // as it would only annoy users with authentication dialogs popping up. 379 nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel); 380 GetOriginAttributesSuffix(chan, suffix); 381 } 382 383 // create a cache entry. we do this even though we don't yet know that 384 // these credentials are valid b/c we need to avoid prompting the user 385 // more than once in case the credentials are valid. 386 // 387 // if the credentials are not reusable, then we don't bother sticking 388 // them in the auth cache. 389 rv = authCache->SetAuthEntry(scheme, host, port, directory, realm, 390 saveCreds ? creds : ""_ns, 391 saveChallenge ? challenge : ""_ns, suffix, 392 saveIdentity ? &ident : nullptr, sessionState); 393 return rv; 394 } 395 396 NS_IMETHODIMP nsHttpChannelAuthProvider::ClearProxyIdent() { 397 LOG(("nsHttpChannelAuthProvider::ClearProxyIdent [this=%p]\n", this)); 398 399 mProxyIdent.Clear(); 400 return NS_OK; 401 } 402 403 nsresult nsHttpChannelAuthProvider::PrepareForAuthentication(bool proxyAuth) { 404 LOG( 405 ("nsHttpChannelAuthProvider::PrepareForAuthentication " 406 "[this=%p channel=%p]\n", 407 this, mAuthChannel)); 408 409 if (!proxyAuth) { 410 // reset the current proxy continuation state because our last 411 // authentication attempt was completed successfully. 412 NS_IF_RELEASE(mProxyAuthContinuationState); 413 LOG((" proxy continuation state has been reset")); 414 } 415 416 if (!UsingHttpProxy() || mProxyAuthType.IsEmpty()) return NS_OK; 417 418 // We need to remove any Proxy_Authorization header left over from a 419 // non-request based authentication handshake (e.g., for NTLM auth). 420 421 nsresult rv; 422 nsCOMPtr<nsIHttpAuthenticator> precedingAuth; 423 nsCString proxyAuthType; 424 rv = GetAuthenticator(mProxyAuthType, proxyAuthType, 425 getter_AddRefs(precedingAuth)); 426 if (NS_FAILED(rv)) return rv; 427 428 uint32_t precedingAuthFlags; 429 rv = precedingAuth->GetAuthFlags(&precedingAuthFlags); 430 if (NS_FAILED(rv)) return rv; 431 432 if (!(precedingAuthFlags & nsIHttpAuthenticator::REQUEST_BASED)) { 433 nsAutoCString challenges; 434 rv = mAuthChannel->GetProxyChallenges(challenges); 435 if (NS_FAILED(rv)) { 436 // delete the proxy authorization header because we weren't 437 // asked to authenticate 438 rv = mAuthChannel->SetProxyCredentials(""_ns); 439 if (NS_FAILED(rv)) return rv; 440 LOG((" cleared proxy authorization header")); 441 } 442 } 443 444 return NS_OK; 445 } 446 447 class MOZ_STACK_CLASS ChallengeParser final : Tokenizer { 448 public: 449 explicit ChallengeParser(const nsACString& aChallenges) 450 : Tokenizer(aChallenges, nullptr, "") { 451 Record(); 452 } 453 454 Maybe<nsDependentCSubstring> GetNext() { 455 Token t; 456 nsDependentCSubstring result; 457 458 bool inQuote = false; 459 460 while (Next(t)) { 461 if (t.Type() == TOKEN_EOL) { 462 Claim(result, ClaimInclusion::EXCLUDE_LAST); 463 SkipWhites(WhiteSkipping::INCLUDE_NEW_LINE); 464 Record(); 465 inQuote = false; 466 if (!result.IsEmpty()) { 467 return Some(result); 468 } 469 } else if (t.Equals(Token::Char(',')) && !inQuote) { 470 // Sometimes we get multiple challenges separated by a comma. 471 // This is not great, as it's slightly ambiguous. We check if something 472 // is a new challenge by matching agains <param_name> = 473 // If the , isn't followed by a word and = then most likely 474 // it is the name of an authType. 475 476 const char* prevCursorPos = mCursor; 477 const char* prevRollbackPos = mRollback; 478 479 auto hasWordAndEqual = [&]() { 480 SkipWhites(); 481 nsDependentCSubstring word; 482 if (!ReadWord(word)) { 483 return false; 484 } 485 SkipWhites(); 486 return Check(Token::Char('=')); 487 }; 488 if (!hasWordAndEqual()) { 489 // This is not a parameter. It means the `,` character starts a 490 // different challenge. 491 // We'll revert the cursor and return the contents so far. 492 mCursor = prevCursorPos; 493 mRollback = prevRollbackPos; 494 Claim(result, ClaimInclusion::EXCLUDE_LAST); 495 SkipWhites(); 496 Record(); 497 if (!result.IsEmpty()) { 498 return Some(result); 499 } 500 } 501 } else if (t.Equals(Token::Char('"'))) { 502 inQuote = !inQuote; 503 } 504 } 505 506 Claim(result, Tokenizer::ClaimInclusion::INCLUDE_LAST); 507 SkipWhites(); 508 Record(); 509 if (!result.IsEmpty()) { 510 return Some(result); 511 } 512 return Nothing{}; 513 } 514 }; 515 516 enum ChallengeRank { 517 Unknown = 0, 518 Basic = 1, 519 Digest = 2, 520 NTLM = 3, 521 Negotiate = 4, 522 }; 523 524 ChallengeRank Rank(const nsACString& aChallenge) { 525 if (StringBeginsWith(aChallenge, "Negotiate"_ns, 526 nsCaseInsensitiveCStringComparator)) { 527 return ChallengeRank::Negotiate; 528 } 529 530 if (StringBeginsWith(aChallenge, "NTLM"_ns, 531 nsCaseInsensitiveCStringComparator)) { 532 return ChallengeRank::NTLM; 533 } 534 535 if (StringBeginsWith(aChallenge, "Digest"_ns, 536 nsCaseInsensitiveCStringComparator)) { 537 return ChallengeRank::Digest; 538 } 539 540 if (StringBeginsWith(aChallenge, "Basic"_ns, 541 nsCaseInsensitiveCStringComparator)) { 542 return ChallengeRank::Basic; 543 } 544 545 return ChallengeRank::Unknown; 546 } 547 548 nsresult nsHttpChannelAuthProvider::GetCredentials( 549 const nsACString& aChallenges, bool proxyAuth, nsCString& creds) { 550 LOG(("nsHttpChannelAuthProvider::GetCredentials")); 551 nsAutoCString challenges(aChallenges); 552 553 using AuthChallenge = struct AuthChallenge { 554 nsDependentCSubstring challenge; 555 uint16_t algorithm = 0; 556 ChallengeRank rank = ChallengeRank::Unknown; 557 558 void operator=(const AuthChallenge& aOther) { 559 challenge.Rebind(aOther.challenge, 0); 560 algorithm = aOther.algorithm; 561 rank = aOther.rank; 562 } 563 }; 564 565 nsTArray<AuthChallenge> cc; 566 567 ChallengeParser p(challenges); 568 while (true) { 569 auto next = p.GetNext(); 570 if (next.isNothing()) { 571 break; 572 } 573 AuthChallenge ac{next.ref(), 0}; 574 nsAutoCString realm, domain, nonce, opaque; 575 bool stale = false; 576 uint16_t qop = 0; 577 ac.rank = Rank(ac.challenge); 578 if (StringBeginsWith(ac.challenge, "Digest"_ns, 579 nsCaseInsensitiveCStringComparator)) { 580 (void)nsHttpDigestAuth::ParseChallenge(ac.challenge, realm, domain, nonce, 581 opaque, &stale, &ac.algorithm, 582 &qop); 583 } 584 cc.AppendElement(ac); 585 } 586 587 // Returns true if an authorization is in progress 588 auto authInProgress = [&]() -> bool { 589 return proxyAuth ? mProxyAuthContinuationState : mAuthContinuationState; 590 }; 591 592 // We shouldn't sort if authorization is already in progress 593 // otherwise we might end up picking the wrong one. See bug 1805666 594 if (!authInProgress() || 595 StaticPrefs::network_auth_sort_challenge_in_progress()) { 596 cc.StableSort([](const AuthChallenge& lhs, const AuthChallenge& rhs) { 597 // Different auth types 598 if (lhs.rank != rhs.rank) { 599 return lhs.rank < rhs.rank ? 1 : -1; 600 } 601 602 // If they're the same auth type, and not a Digest, then we treat them 603 // as equal (don't reorder them). 604 if (lhs.rank != ChallengeRank::Digest) { 605 return 0; 606 } 607 608 if (lhs.algorithm == rhs.algorithm) { 609 return 0; 610 } 611 612 return lhs.algorithm < rhs.algorithm ? 1 : -1; 613 }); 614 } 615 616 nsAutoCString scheme; 617 618 // If a preference to enable basic HTTP Auth is unset and the scheme is HTTP, 619 // check if Basic is the sole available authentication challenge. 620 if (NS_SUCCEEDED(mURI->GetScheme(scheme)) && scheme == "http"_ns && 621 !StaticPrefs::network_http_basic_http_auth_enabled() && 622 cc[0].rank == ChallengeRank::Basic) { 623 // HTTP Auth and "Basic" is the sole available authentication. 624 return NS_ERROR_BASIC_HTTP_AUTH_DISABLED; 625 } 626 627 nsCOMPtr<nsIHttpAuthenticator> auth; 628 nsCString authType; // force heap allocation to enable string sharing since 629 // we'll be assigning this value into mAuthType. 630 631 // set informations that depend on whether we're authenticating against a 632 // proxy or a webserver 633 nsISupports** currentContinuationState; 634 nsCString* currentAuthType; 635 636 if (proxyAuth) { 637 currentContinuationState = &mProxyAuthContinuationState; 638 currentAuthType = &mProxyAuthType; 639 } else { 640 currentContinuationState = &mAuthContinuationState; 641 currentAuthType = &mAuthType; 642 } 643 644 nsresult rv = NS_ERROR_NOT_AVAILABLE; 645 bool gotCreds = false; 646 647 // figure out which challenge we can handle and which authenticator to use. 648 for (size_t i = 0; i < cc.Length(); i++) { 649 rv = GetAuthenticator(cc[i].challenge, authType, getter_AddRefs(auth)); 650 LOG(("trying auth for %s", authType.get())); 651 if (NS_SUCCEEDED(rv)) { 652 // 653 // if we've already selected an auth type from a previous challenge 654 // received while processing this channel, then skip others until 655 // we find a challenge corresponding to the previously tried auth 656 // type. 657 // 658 if (!currentAuthType->IsEmpty() && authType != *currentAuthType) continue; 659 660 // 661 // we allow the routines to run all the way through before we 662 // decide if they are valid. 663 // 664 // we don't worry about the auth cache being altered because that 665 // would have been the last step, and if the error is from updating 666 // the authcache it wasn't really altered anyway. -CTN 667 // 668 // at this point the code is really only useful for client side 669 // errors (it will not automatically fail over to do a different 670 // auth type if the server keeps rejecting what is being sent, even 671 // if a particular auth method only knows 1 thing, like a 672 // non-identity based authentication method) 673 // 674 rv = GetCredentialsForChallenge(cc[i].challenge, authType, proxyAuth, 675 auth, creds); 676 if (NS_SUCCEEDED(rv)) { 677 gotCreds = true; 678 *currentAuthType = authType; 679 680 break; 681 } 682 if (rv == NS_ERROR_IN_PROGRESS) { 683 // authentication prompt has been invoked and result is 684 // expected asynchronously, save current challenge being 685 // processed and all remaining challenges to use later in 686 // OnAuthAvailable and now immediately return 687 mCurrentChallenge = cc[i].challenge; 688 // imperfect; does not save server-side preference ordering. 689 // instead, continues with remaining string as provided by client 690 mRemainingChallenges.Truncate(); 691 while (i + 1 < cc.Length()) { 692 i++; 693 mRemainingChallenges.Append(cc[i].challenge); 694 mRemainingChallenges.Append("\n"_ns); 695 } 696 return rv; 697 } 698 699 // reset the auth type and continuation state 700 NS_IF_RELEASE(*currentContinuationState); 701 currentAuthType->Truncate(); 702 } 703 } 704 705 if (!gotCreds && !currentAuthType->IsEmpty()) { 706 // looks like we never found the auth type we were looking for. 707 // reset the auth type and continuation state, and try again. 708 currentAuthType->Truncate(); 709 NS_IF_RELEASE(*currentContinuationState); 710 711 rv = GetCredentials(challenges, proxyAuth, creds); 712 } 713 714 return rv; 715 } 716 717 nsresult nsHttpChannelAuthProvider::GetAuthorizationMembers( 718 bool proxyAuth, nsACString& scheme, nsCString& host, int32_t& port, 719 nsACString& path, nsHttpAuthIdentity*& ident, 720 nsISupports**& continuationState) { 721 if (proxyAuth) { 722 MOZ_ASSERT(UsingHttpProxy(), 723 "proxyAuth is true, but no HTTP proxy is configured!"); 724 725 host = ProxyHost(); 726 port = ProxyPort(); 727 ident = &mProxyIdent; 728 scheme.AssignLiteral("http"); 729 730 continuationState = &mProxyAuthContinuationState; 731 } else { 732 host = Host(); 733 port = Port(); 734 ident = &mIdent; 735 736 nsresult rv; 737 rv = GetCurrentPath(path); 738 if (NS_FAILED(rv)) return rv; 739 740 rv = mURI->GetScheme(scheme); 741 if (NS_FAILED(rv)) return rv; 742 743 continuationState = &mAuthContinuationState; 744 } 745 746 return NS_OK; 747 } 748 749 nsresult nsHttpChannelAuthProvider::GetCredentialsForChallenge( 750 const nsACString& aChallenge, const nsACString& aAuthType, bool proxyAuth, 751 nsIHttpAuthenticator* auth, nsCString& creds) { 752 LOG( 753 ("nsHttpChannelAuthProvider::GetCredentialsForChallenge " 754 "[this=%p channel=%p proxyAuth=%d challenges=%s]\n", 755 this, mAuthChannel, proxyAuth, nsCString(aChallenge).get())); 756 757 // this getter never fails 758 nsHttpAuthCache* authCache = gHttpHandler->AuthCache(mIsPrivate); 759 760 uint32_t authFlags; 761 nsresult rv = auth->GetAuthFlags(&authFlags); 762 if (NS_FAILED(rv)) return rv; 763 764 nsAutoCString realm; 765 ParseRealm(aChallenge, realm); 766 767 // if no realm, then use the auth type as the realm. ToUpperCase so the 768 // ficticious realm stands out a bit more. 769 // XXX this will cause some single signon misses! 770 // XXX this was meant to be used with NTLM, which supplies no realm. 771 /* 772 if (realm.IsEmpty()) { 773 realm = authType; 774 ToUpperCase(realm); 775 } 776 */ 777 778 // set informations that depend on whether 779 // we're authenticating against a proxy 780 // or a webserver 781 nsAutoCString host; 782 int32_t port; 783 nsHttpAuthIdentity* ident; 784 nsAutoCString path, scheme; 785 bool identFromURI = false; 786 nsISupports** continuationState; 787 788 rv = GetAuthorizationMembers(proxyAuth, scheme, host, port, path, ident, 789 continuationState); 790 if (NS_FAILED(rv)) return rv; 791 792 uint32_t loadFlags; 793 rv = mAuthChannel->GetLoadFlags(&loadFlags); 794 if (NS_FAILED(rv)) return rv; 795 796 // Fill only for non-proxy auth, proxy credentials are not OA-isolated. 797 nsAutoCString suffix; 798 799 if (!proxyAuth) { 800 nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel); 801 GetOriginAttributesSuffix(chan, suffix); 802 803 // if this is the first challenge, then try using the identity 804 // specified in the URL. 805 if (mIdent.IsEmpty()) { 806 GetIdentityFromURI(authFlags, mIdent); 807 identFromURI = !mIdent.IsEmpty(); 808 } 809 810 if ((loadFlags & nsIRequest::LOAD_ANONYMOUS) && !identFromURI) { 811 LOG(("Skipping authentication for anonymous non-proxy request\n")); 812 return NS_ERROR_NOT_AVAILABLE; 813 } 814 815 // Let explicit URL credentials pass 816 // regardless of the LOAD_ANONYMOUS flag 817 } else if ((loadFlags & nsIRequest::LOAD_ANONYMOUS) && !UsingHttpProxy()) { 818 LOG(("Skipping authentication for anonymous non-proxy request\n")); 819 return NS_ERROR_NOT_AVAILABLE; 820 } 821 822 // 823 // if we already tried some credentials for this transaction, then 824 // we need to possibly clear them from the cache, unless the credentials 825 // in the cache have changed, in which case we'd want to give them a 826 // try instead. 827 // 828 RefPtr<nsHttpAuthEntry> entry; 829 (void)authCache->GetAuthEntryForDomain(scheme, host, port, realm, suffix, 830 entry); 831 832 // hold reference to the auth session state (in case we clear our 833 // reference to the entry). 834 nsCOMPtr<nsISupports> sessionStateGrip; 835 if (entry) sessionStateGrip = entry->mMetaData; 836 837 // remember if we already had the continuation state. it means we are in 838 // the middle of the authentication exchange and the connection must be 839 // kept sticky then (and only then). 840 bool authAtProgress = !!*continuationState; 841 842 // for digest auth, maybe our cached nonce value simply timed out... 843 bool identityInvalid; 844 nsISupports* sessionState = sessionStateGrip; 845 rv = auth->ChallengeReceived(mAuthChannel, aChallenge, proxyAuth, 846 &sessionState, &*continuationState, 847 &identityInvalid); 848 sessionStateGrip.swap(sessionState); 849 if (NS_FAILED(rv)) return rv; 850 851 LOG((" identity invalid = %d\n", identityInvalid)); 852 853 if (mConnectionBased && identityInvalid) { 854 // If the flag is set and identity is invalid, it means we received the 855 // first challange for a new negotiation round after negotiating a 856 // connection based auth failed (invalid password). The mConnectionBased 857 // flag is set later for the newly received challenge, so here it reflects 858 // the previous 401/7 response schema. 859 rv = mAuthChannel->CloseStickyConnection(); 860 MOZ_ASSERT(NS_SUCCEEDED(rv)); 861 if (!proxyAuth) { 862 // We must clear proxy ident in the following scenario + explanation: 863 // - we are authenticating to an NTLM proxy and an NTLM server 864 // - we successfully authenticated to the proxy, mProxyIdent keeps 865 // the user name/domain and password, the identity has also been cached 866 // - we just threw away the connection because we are now asking for 867 // creds for the server (WWW auth) 868 // - hence, we will have to auth to the proxy again as well 869 // - if we didn't clear the proxy identity, it would be considered 870 // as non-valid and we would ask the user again ; clearing it forces 871 // use of the cached identity and not asking the user again 872 ClearProxyIdent(); 873 } 874 } 875 876 mConnectionBased = !!(authFlags & nsIHttpAuthenticator::CONNECTION_BASED); 877 878 // It's legal if the peer closes the connection after the first 401/7. 879 // Making the connection sticky will prevent its restart giving the user 880 // a 'network reset' error every time. Hence, we mark the connection 881 // as restartable. 882 mAuthChannel->ConnectionRestartable(!authAtProgress); 883 884 if (identityInvalid) { 885 if (entry) { 886 if (ident->Equals(entry->Identity())) { 887 if (!identFromURI) { 888 LOG((" clearing bad auth cache entry\n")); 889 // ok, we've already tried this user identity, so clear the 890 // corresponding entry from the auth cache. 891 authCache->ClearAuthEntry(scheme, host, port, realm, suffix); 892 entry = nullptr; 893 ident->Clear(); 894 } 895 } else if (!identFromURI || 896 (ident->User() == entry->Identity().User() && 897 !(loadFlags & (nsIChannel::LOAD_ANONYMOUS | 898 nsIChannel::LOAD_EXPLICIT_CREDENTIALS)))) { 899 LOG((" taking identity from auth cache\n")); 900 // the password from the auth cache is more likely to be 901 // correct than the one in the URL. at least, we know that it 902 // works with the given username. it is possible for a server 903 // to distinguish logons based on the supplied password alone, 904 // but that would be quite unusual... and i don't think we need 905 // to worry about such unorthodox cases. 906 *ident = entry->Identity(); 907 identFromURI = false; 908 if (entry->Creds()[0] != '\0') { 909 LOG((" using cached credentials!\n")); 910 creds.Assign(entry->Creds()); 911 return entry->AddPath(path); 912 } 913 } 914 } else if (!identFromURI) { 915 // hmm... identity invalid, but no auth entry! the realm probably 916 // changed (see bug 201986). 917 ident->Clear(); 918 } 919 920 if (!entry && ident->IsEmpty()) { 921 uint32_t level = nsIAuthPrompt2::LEVEL_NONE; 922 if ((!proxyAuth && mUsingSSL) || (proxyAuth && mProxyUsingSSL)) { 923 level = nsIAuthPrompt2::LEVEL_SECURE; 924 } else if (authFlags & nsIHttpAuthenticator::IDENTITY_ENCRYPTED) { 925 level = nsIAuthPrompt2::LEVEL_PW_ENCRYPTED; 926 } 927 928 // Depending on the pref setting, the authentication dialog may be 929 // blocked for all sub-resources, blocked for cross-origin 930 // sub-resources, or always allowed for sub-resources. 931 // For more details look at the bug 647010. 932 // BlockPrompt will set mCrossOrigin parameter as well. 933 if (BlockPrompt(proxyAuth)) { 934 LOG(( 935 "nsHttpChannelAuthProvider::GetCredentialsForChallenge: " 936 "Prompt is blocked [this=%p pref=%d img-pref=%d " 937 "non-web-content-triggered-pref=%d]\n", 938 this, StaticPrefs::network_auth_subresource_http_auth_allow(), 939 StaticPrefs:: 940 network_auth_subresource_img_cross_origin_http_auth_allow(), 941 StaticPrefs:: 942 network_auth_non_web_content_triggered_resources_http_auth_allow())); 943 return NS_ERROR_ABORT; 944 } 945 946 // at this point we are forced to interact with the user to get 947 // their username and password for this domain. 948 rv = PromptForIdentity(level, proxyAuth, realm, aAuthType, authFlags, 949 *ident); 950 if (NS_FAILED(rv)) return rv; 951 identFromURI = false; 952 } 953 } 954 955 // get credentials for the given user:pass 956 // 957 // always store the credentials we're trying now so that they will be used 958 // on subsequent links. This will potentially remove good credentials from 959 // the cache. This is ok as we don't want to use cached credentials if the 960 // user specified something on the URI or in another manner. This is so 961 // that we don't transparently authenticate as someone they're not 962 // expecting to authenticate as. 963 // 964 nsCString result; 965 rv = GenCredsAndSetEntry(auth, proxyAuth, scheme, host, port, path, realm, 966 aChallenge, *ident, sessionStateGrip, creds); 967 return rv; 968 } 969 970 bool nsHttpChannelAuthProvider::BlockPrompt(bool proxyAuth) { 971 // Verify that it's ok to prompt for credentials here, per spec 972 // http://xhr.spec.whatwg.org/#the-send%28%29-method 973 974 nsCOMPtr<nsIHttpChannelInternal> chanInternal = 975 do_QueryInterface(mAuthChannel); 976 MOZ_ASSERT(chanInternal); 977 978 if (chanInternal->GetBlockAuthPrompt()) { 979 LOG( 980 ("nsHttpChannelAuthProvider::BlockPrompt: Prompt is blocked " 981 "[this=%p channel=%p]\n", 982 this, mAuthChannel)); 983 return true; 984 } 985 986 if (proxyAuth) { 987 // Do not block auth-dialog if this is a proxy authentication. 988 return false; 989 } 990 991 nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel); 992 nsCOMPtr<nsILoadInfo> loadInfo = chan->LoadInfo(); 993 994 // We will treat loads w/o loadInfo as a top level document. 995 bool topDoc = true; 996 bool xhr = false; 997 bool nonWebContent = false; 998 999 if (loadInfo->GetExternalContentPolicyType() != 1000 ExtContentPolicy::TYPE_DOCUMENT) { 1001 topDoc = false; 1002 } 1003 1004 if (!topDoc) { 1005 nsCOMPtr<nsIPrincipal> triggeringPrinc = loadInfo->TriggeringPrincipal(); 1006 if (triggeringPrinc->IsSystemPrincipal()) { 1007 nonWebContent = true; 1008 } 1009 } 1010 1011 if (loadInfo->GetExternalContentPolicyType() == 1012 ExtContentPolicy::TYPE_XMLHTTPREQUEST) { 1013 xhr = true; 1014 } 1015 1016 if (!topDoc && !xhr) { 1017 nsCOMPtr<nsIURI> topURI; 1018 (void)chanInternal->GetTopWindowURI(getter_AddRefs(topURI)); 1019 if (topURI) { 1020 mCrossOrigin = !NS_SecurityCompareURIs(topURI, mURI, true); 1021 } else { 1022 nsIPrincipal* loadingPrinc = loadInfo->GetLoadingPrincipal(); 1023 MOZ_ASSERT(loadingPrinc); 1024 mCrossOrigin = !loadingPrinc->IsSameOrigin(mURI); 1025 } 1026 } 1027 1028 if (!topDoc && 1029 !StaticPrefs:: 1030 network_auth_non_web_content_triggered_resources_http_auth_allow() && 1031 nonWebContent) { 1032 return true; 1033 } 1034 1035 switch (StaticPrefs::network_auth_subresource_http_auth_allow()) { 1036 case SUBRESOURCE_AUTH_DIALOG_DISALLOW_ALL: 1037 // Do not open the http-authentication credentials dialog for 1038 // the sub-resources. 1039 return !topDoc && !xhr; 1040 case SUBRESOURCE_AUTH_DIALOG_DISALLOW_CROSS_ORIGIN: 1041 // Open the http-authentication credentials dialog for 1042 // the sub-resources only if they are not cross-origin. 1043 return !topDoc && !xhr && mCrossOrigin; 1044 case SUBRESOURCE_AUTH_DIALOG_ALLOW_ALL: 1045 // Allow the http-authentication dialog for subresources. 1046 // If pref network.auth.subresource-img-cross-origin-http-auth-allow 1047 // is set, http-authentication dialog for image subresources is 1048 // blocked. 1049 if (mCrossOrigin && 1050 !StaticPrefs:: 1051 network_auth_subresource_img_cross_origin_http_auth_allow() && 1052 loadInfo && 1053 ((loadInfo->GetExternalContentPolicyType() == 1054 ExtContentPolicy::TYPE_IMAGE) || 1055 (loadInfo->GetExternalContentPolicyType() == 1056 ExtContentPolicy::TYPE_IMAGESET))) { 1057 return true; 1058 } 1059 return false; 1060 default: 1061 // This is an invalid value. 1062 MOZ_ASSERT(false, "A non valid value!"); 1063 } 1064 return false; 1065 } 1066 1067 inline void GetAuthType(const nsACString& aChallenge, nsCString& authType) { 1068 auto spaceIndex = aChallenge.FindChar(' '); 1069 authType = Substring(aChallenge, 0, spaceIndex); 1070 // normalize to lowercase 1071 ToLowerCase(authType); 1072 } 1073 1074 nsresult nsHttpChannelAuthProvider::GetAuthenticator( 1075 const nsACString& aChallenge, nsCString& authType, 1076 nsIHttpAuthenticator** auth) { 1077 LOG(("nsHttpChannelAuthProvider::GetAuthenticator [this=%p channel=%p]\n", 1078 this, mAuthChannel)); 1079 1080 GetAuthType(aChallenge, authType); 1081 1082 nsCOMPtr<nsIHttpAuthenticator> authenticator; 1083 #ifdef MOZ_AUTH_EXTENSION 1084 if (authType.EqualsLiteral("negotiate")) { 1085 authenticator = nsHttpNegotiateAuth::GetOrCreate(); 1086 } else 1087 #endif 1088 if (authType.EqualsLiteral("basic")) { 1089 authenticator = nsHttpBasicAuth::GetOrCreate(); 1090 } else if (authType.EqualsLiteral("digest")) { 1091 authenticator = nsHttpDigestAuth::GetOrCreate(); 1092 } else if (authType.EqualsLiteral("ntlm")) { 1093 authenticator = nsHttpNTLMAuth::GetOrCreate(); 1094 } else if (authType.EqualsLiteral("mock_auth") && 1095 PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) { 1096 authenticator = MockHttpAuth::Create(); 1097 } else { 1098 return NS_ERROR_FACTORY_NOT_REGISTERED; 1099 } 1100 1101 if (!authenticator) { 1102 // If called during shutdown it's possible that the singleton authenticator 1103 // was already cleared so we have a null one here. 1104 return NS_ERROR_NOT_AVAILABLE; 1105 } 1106 1107 MOZ_ASSERT(authenticator); 1108 authenticator.forget(auth); 1109 1110 return NS_OK; 1111 } 1112 1113 // buf contains "domain\user" 1114 static void ParseUserDomain(const nsAString& buf, nsDependentSubstring& user, 1115 nsDependentSubstring& domain) { 1116 auto backslashPos = buf.FindChar(u'\\'); 1117 if (backslashPos != kNotFound) { 1118 domain.Rebind(buf, 0, backslashPos); 1119 user.Rebind(buf, backslashPos + 1); 1120 } 1121 } 1122 1123 void nsHttpChannelAuthProvider::GetIdentityFromURI(uint32_t authFlags, 1124 nsHttpAuthIdentity& ident) { 1125 LOG(("nsHttpChannelAuthProvider::GetIdentityFromURI [this=%p channel=%p]\n", 1126 this, mAuthChannel)); 1127 1128 bool hasUserPass; 1129 if (NS_FAILED(mURI->GetHasUserPass(&hasUserPass)) || !hasUserPass) { 1130 return; 1131 } 1132 1133 nsAutoString userBuf; 1134 nsAutoString passBuf; 1135 1136 // XXX i18n 1137 nsAutoCString buf; 1138 nsresult rv = mURI->GetUsername(buf); 1139 if (NS_FAILED(rv)) { 1140 return; 1141 } 1142 NS_UnescapeURL(buf); 1143 CopyUTF8toUTF16(buf, userBuf); 1144 1145 rv = mURI->GetPassword(buf); 1146 if (NS_FAILED(rv)) { 1147 return; 1148 } 1149 NS_UnescapeURL(buf); 1150 CopyUTF8toUTF16(buf, passBuf); 1151 1152 nsDependentSubstring user(userBuf, 0); 1153 nsDependentSubstring domain(EmptyString(), 0); 1154 1155 if (authFlags & nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN) { 1156 ParseUserDomain(userBuf, user, domain); 1157 } 1158 1159 ident = nsHttpAuthIdentity(domain, user, passBuf); 1160 } 1161 1162 void nsHttpChannelAuthProvider::ParseRealm(const nsACString& aChallenge, 1163 nsACString& realm) { 1164 // 1165 // From RFC2617 section 1.2, the realm value is defined as such: 1166 // 1167 // realm = "realm" "=" realm-value 1168 // realm-value = quoted-string 1169 // 1170 // but, we'll accept anything after the the "=" up to the first space, or 1171 // end-of-line, if the string is not quoted. 1172 // 1173 1174 Tokenizer t(aChallenge); 1175 1176 // The challenge begins with the authType. 1177 // If we can't find that something has probably gone wrong. 1178 t.SkipWhites(); 1179 nsDependentCSubstring authType; 1180 if (!t.ReadWord(authType)) { 1181 return; 1182 } 1183 1184 // Will return true if the tokenizer advanced the cursor - false otherwise. 1185 auto readParam = [&](nsDependentCSubstring& key, nsAutoCString& value) { 1186 key.Rebind(EmptyCString(), 0); 1187 value.Truncate(); 1188 1189 t.SkipWhites(); 1190 if (!t.ReadWord(key)) { 1191 return false; 1192 } 1193 t.SkipWhites(); 1194 if (!t.CheckChar('=')) { 1195 return true; 1196 } 1197 t.SkipWhites(); 1198 1199 Tokenizer::Token token1; 1200 1201 t.Record(); 1202 if (!t.Next(token1)) { 1203 return true; 1204 } 1205 nsDependentCSubstring sub; 1206 bool hasQuote = false; 1207 if (token1.Equals(Tokenizer::Token::Char('"'))) { 1208 hasQuote = true; 1209 } else { 1210 t.Claim(sub, Tokenizer::ClaimInclusion::INCLUDE_LAST); 1211 value.Append(sub); 1212 } 1213 t.Record(); 1214 Tokenizer::Token token2; 1215 while (t.Next(token2)) { 1216 if (hasQuote && token2.Equals(Tokenizer::Token::Char('"')) && 1217 !token1.Equals(Tokenizer::Token::Char('\\'))) { 1218 break; 1219 } 1220 if (!hasQuote && (token2.Type() == Tokenizer::TokenType::TOKEN_WS || 1221 token2.Type() == Tokenizer::TokenType::TOKEN_EOL)) { 1222 break; 1223 } 1224 1225 t.Claim(sub, Tokenizer::ClaimInclusion::INCLUDE_LAST); 1226 if (!sub.Equals(R"(\)")) { 1227 value.Append(sub); 1228 } 1229 t.Record(); 1230 token1 = token2; 1231 } 1232 return true; 1233 }; 1234 1235 while (!t.CheckEOF()) { 1236 nsDependentCSubstring key; 1237 nsAutoCString value; 1238 // If we couldn't read anything, and the input isn't followed by a , 1239 // then we exit. 1240 if (!readParam(key, value) && !t.Check(Tokenizer::Token::Char(','))) { 1241 break; 1242 } 1243 // When we find the first instance of realm we exit. 1244 // Theoretically there should be only one instance and we should fail 1245 // if there are more, but we're trying to preserve existing behaviour. 1246 if (key.Equals("realm"_ns, nsCaseInsensitiveCStringComparator)) { 1247 realm = value; 1248 break; 1249 } 1250 } 1251 } 1252 1253 class nsHTTPAuthInformation : public nsAuthInformationHolder { 1254 public: 1255 nsHTTPAuthInformation(uint32_t aFlags, const nsString& aRealm, 1256 const nsACString& aAuthType) 1257 : nsAuthInformationHolder(aFlags, aRealm, aAuthType) {} 1258 1259 void SetToHttpAuthIdentity(uint32_t authFlags, nsHttpAuthIdentity& identity); 1260 }; 1261 1262 void nsHTTPAuthInformation::SetToHttpAuthIdentity( 1263 uint32_t authFlags, nsHttpAuthIdentity& identity) { 1264 identity = nsHttpAuthIdentity(Domain(), User(), Password()); 1265 } 1266 1267 nsresult nsHttpChannelAuthProvider::PromptForIdentity( 1268 uint32_t level, bool proxyAuth, const nsACString& realm, 1269 const nsACString& authType, uint32_t authFlags, nsHttpAuthIdentity& ident) { 1270 LOG(("nsHttpChannelAuthProvider::PromptForIdentity [this=%p channel=%p]\n", 1271 this, mAuthChannel)); 1272 1273 nsresult rv; 1274 1275 nsCOMPtr<nsIInterfaceRequestor> callbacks; 1276 rv = mAuthChannel->GetNotificationCallbacks(getter_AddRefs(callbacks)); 1277 if (NS_FAILED(rv)) return rv; 1278 1279 nsCOMPtr<nsILoadGroup> loadGroup; 1280 rv = mAuthChannel->GetLoadGroup(getter_AddRefs(loadGroup)); 1281 if (NS_FAILED(rv)) return rv; 1282 1283 nsCOMPtr<nsIAuthPrompt2> authPrompt; 1284 GetAuthPrompt(callbacks, proxyAuth, getter_AddRefs(authPrompt)); 1285 if (!authPrompt && loadGroup) { 1286 nsCOMPtr<nsIInterfaceRequestor> cbs; 1287 loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs)); 1288 GetAuthPrompt(cbs, proxyAuth, getter_AddRefs(authPrompt)); 1289 } 1290 if (!authPrompt) return NS_ERROR_NO_INTERFACE; 1291 1292 // XXX i18n: need to support non-ASCII realm strings (see bug 41489) 1293 NS_ConvertASCIItoUTF16 realmU(realm); 1294 1295 // prompt the user... 1296 uint32_t promptFlags = 0; 1297 if (proxyAuth) { 1298 promptFlags |= nsIAuthInformation::AUTH_PROXY; 1299 if (mTriedProxyAuth) promptFlags |= nsIAuthInformation::PREVIOUS_FAILED; 1300 mTriedProxyAuth = true; 1301 } else { 1302 promptFlags |= nsIAuthInformation::AUTH_HOST; 1303 if (mTriedHostAuth) promptFlags |= nsIAuthInformation::PREVIOUS_FAILED; 1304 mTriedHostAuth = true; 1305 } 1306 1307 if (authFlags & nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN) { 1308 promptFlags |= nsIAuthInformation::NEED_DOMAIN; 1309 } 1310 1311 if (mCrossOrigin) { 1312 promptFlags |= nsIAuthInformation::CROSS_ORIGIN_SUB_RESOURCE; 1313 } 1314 1315 RefPtr<nsHTTPAuthInformation> holder = 1316 new nsHTTPAuthInformation(promptFlags, realmU, authType); 1317 if (!holder) return NS_ERROR_OUT_OF_MEMORY; 1318 1319 nsCOMPtr<nsIChannel> channel(do_QueryInterface(mAuthChannel, &rv)); 1320 if (NS_FAILED(rv)) return rv; 1321 1322 rv = authPrompt->AsyncPromptAuth(channel, this, nullptr, level, holder, 1323 getter_AddRefs(mAsyncPromptAuthCancelable)); 1324 1325 if (NS_SUCCEEDED(rv)) { 1326 // indicate using this error code that authentication prompt 1327 // result is expected asynchronously 1328 rv = NS_ERROR_IN_PROGRESS; 1329 } else { 1330 // Fall back to synchronous prompt 1331 bool retval = false; 1332 rv = authPrompt->PromptAuth(channel, level, holder, &retval); 1333 if (NS_FAILED(rv)) return rv; 1334 1335 if (!retval) { 1336 rv = NS_ERROR_ABORT; 1337 } else { 1338 holder->SetToHttpAuthIdentity(authFlags, ident); 1339 } 1340 } 1341 1342 if (mConnectionBased) { 1343 // Connection can be reset by the server in the meantime user is entering 1344 // the credentials. Result would be just a "Connection was reset" error. 1345 // Hence, we drop the current regardless if the user would make it on time 1346 // to provide credentials. 1347 // It's OK to send the NTLM type 1 message (response to the plain "NTLM" 1348 // challenge) on a new connection. 1349 { 1350 DebugOnly<nsresult> rv = mAuthChannel->CloseStickyConnection(); 1351 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1352 } 1353 } 1354 1355 return rv; 1356 } 1357 1358 NS_IMETHODIMP nsHttpChannelAuthProvider::OnAuthAvailable( 1359 nsISupports* aContext, nsIAuthInformation* aAuthInfo) { 1360 LOG(("nsHttpChannelAuthProvider::OnAuthAvailable [this=%p channel=%p]", this, 1361 mAuthChannel)); 1362 1363 mAsyncPromptAuthCancelable = nullptr; 1364 if (!mAuthChannel) return NS_OK; 1365 1366 nsresult rv; 1367 1368 nsAutoCString host; 1369 int32_t port; 1370 nsHttpAuthIdentity* ident; 1371 nsAutoCString path, scheme; 1372 nsISupports** continuationState; 1373 rv = GetAuthorizationMembers(mProxyAuth, scheme, host, port, path, ident, 1374 continuationState); 1375 if (NS_FAILED(rv)) OnAuthCancelled(aContext, false); 1376 1377 nsAutoCString realm; 1378 ParseRealm(mCurrentChallenge, realm); 1379 1380 nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel); 1381 nsAutoCString suffix; 1382 if (!mProxyAuth) { 1383 // Fill only for non-proxy auth, proxy credentials are not OA-isolated. 1384 GetOriginAttributesSuffix(chan, suffix); 1385 } 1386 1387 nsHttpAuthCache* authCache = gHttpHandler->AuthCache(mIsPrivate); 1388 RefPtr<nsHttpAuthEntry> entry; 1389 (void)authCache->GetAuthEntryForDomain(scheme, host, port, realm, suffix, 1390 entry); 1391 1392 nsCOMPtr<nsISupports> sessionStateGrip; 1393 if (entry) sessionStateGrip = entry->mMetaData; 1394 1395 nsAuthInformationHolder* holder = 1396 static_cast<nsAuthInformationHolder*>(aAuthInfo); 1397 *ident = 1398 nsHttpAuthIdentity(holder->Domain(), holder->User(), holder->Password()); 1399 1400 nsAutoCString unused; 1401 nsCOMPtr<nsIHttpAuthenticator> auth; 1402 rv = GetAuthenticator(mCurrentChallenge, unused, getter_AddRefs(auth)); 1403 if (NS_FAILED(rv)) { 1404 MOZ_ASSERT(false, "GetAuthenticator failed"); 1405 OnAuthCancelled(aContext, true); 1406 return NS_OK; 1407 } 1408 1409 nsCString creds; 1410 rv = GenCredsAndSetEntry(auth, mProxyAuth, scheme, host, port, path, realm, 1411 mCurrentChallenge, *ident, sessionStateGrip, creds); 1412 1413 mCurrentChallenge.Truncate(); 1414 if (NS_FAILED(rv)) { 1415 OnAuthCancelled(aContext, true); 1416 return NS_OK; 1417 } 1418 1419 return ContinueOnAuthAvailable(creds); 1420 } 1421 1422 NS_IMETHODIMP nsHttpChannelAuthProvider::OnAuthCancelled(nsISupports* aContext, 1423 bool userCancel) { 1424 LOG(("nsHttpChannelAuthProvider::OnAuthCancelled [this=%p channel=%p]", this, 1425 mAuthChannel)); 1426 1427 mAsyncPromptAuthCancelable = nullptr; 1428 if (!mAuthChannel) return NS_OK; 1429 1430 // When user cancels or auth fails we want to close the connection for 1431 // connection based schemes like NTLM. Some servers don't like re-negotiation 1432 // on the same connection. 1433 nsresult rv; 1434 if (mConnectionBased) { 1435 rv = mAuthChannel->CloseStickyConnection(); 1436 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1437 mConnectionBased = false; 1438 } 1439 1440 nsCOMPtr<nsIChannel> channel = do_QueryInterface(mAuthChannel); 1441 if (channel) { 1442 nsresult status; 1443 (void)channel->GetStatus(&status); 1444 if (NS_FAILED(status)) { 1445 // If the channel is already cancelled, there is no need to deal with the 1446 // rest challenges. 1447 LOG((" Clear mRemainingChallenges, since mAuthChannel is cancelled")); 1448 mRemainingChallenges.Truncate(); 1449 } 1450 } 1451 1452 if (userCancel) { 1453 if (!mRemainingChallenges.IsEmpty()) { 1454 // there are still some challenges to process, do so 1455 1456 // Get rid of current continuationState to avoid reusing it in 1457 // next challenges since it is no longer relevant. 1458 if (mProxyAuth) { 1459 NS_IF_RELEASE(mProxyAuthContinuationState); 1460 } else { 1461 NS_IF_RELEASE(mAuthContinuationState); 1462 } 1463 nsAutoCString creds; 1464 rv = GetCredentials(mRemainingChallenges, mProxyAuth, creds); 1465 if (NS_SUCCEEDED(rv)) { 1466 // GetCredentials loaded the credentials from the cache or 1467 // some other way in a synchronous manner, process those 1468 // credentials now 1469 mRemainingChallenges.Truncate(); 1470 return ContinueOnAuthAvailable(creds); 1471 } 1472 if (rv == NS_ERROR_IN_PROGRESS) { 1473 // GetCredentials successfully queued another authprompt for 1474 // a challenge from the list, we are now waiting for the user 1475 // to provide the credentials 1476 return NS_OK; 1477 } 1478 1479 // otherwise, we failed... 1480 } 1481 1482 mRemainingChallenges.Truncate(); 1483 } 1484 1485 rv = mAuthChannel->OnAuthCancelled(userCancel); 1486 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1487 1488 return NS_OK; 1489 } 1490 1491 NS_IMETHODIMP nsHttpChannelAuthProvider::OnCredsGenerated( 1492 const nsACString& aGeneratedCreds, uint32_t aFlags, nsresult aResult, 1493 nsISupports* aSessionState, nsISupports* aContinuationState) { 1494 nsresult rv; 1495 1496 MOZ_ASSERT(NS_IsMainThread()); 1497 1498 // When channel is closed, do not proceed 1499 if (!mAuthChannel) { 1500 return NS_OK; 1501 } 1502 1503 mGenerateCredentialsCancelable = nullptr; 1504 1505 if (NS_FAILED(aResult)) { 1506 return OnAuthCancelled(nullptr, true); 1507 } 1508 1509 // We want to update m(Proxy)AuthContinuationState in case it was changed by 1510 // nsHttpNegotiateAuth::GenerateCredentials 1511 nsCOMPtr<nsISupports> contState(aContinuationState); 1512 if (mProxyAuth) { 1513 contState.swap(mProxyAuthContinuationState); 1514 } else { 1515 contState.swap(mAuthContinuationState); 1516 } 1517 1518 nsCOMPtr<nsIHttpAuthenticator> auth; 1519 nsAutoCString unused; 1520 rv = GetAuthenticator(mCurrentChallenge, unused, getter_AddRefs(auth)); 1521 NS_ENSURE_SUCCESS(rv, rv); 1522 1523 nsAutoCString host; 1524 int32_t port; 1525 nsHttpAuthIdentity* ident; 1526 nsAutoCString directory, scheme; 1527 nsISupports** unusedContinuationState; 1528 1529 // Get realm from challenge 1530 nsAutoCString realm; 1531 ParseRealm(mCurrentChallenge, realm); 1532 1533 rv = GetAuthorizationMembers(mProxyAuth, scheme, host, port, directory, ident, 1534 unusedContinuationState); 1535 if (NS_FAILED(rv)) return rv; 1536 1537 rv = 1538 UpdateCache(auth, scheme, host, port, directory, realm, mCurrentChallenge, 1539 *ident, aGeneratedCreds, aFlags, aSessionState, mProxyAuth); 1540 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1541 mCurrentChallenge.Truncate(); 1542 1543 rv = ContinueOnAuthAvailable(aGeneratedCreds); 1544 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1545 return NS_OK; 1546 } 1547 1548 nsresult nsHttpChannelAuthProvider::ContinueOnAuthAvailable( 1549 const nsACString& creds) { 1550 nsresult rv; 1551 if (mProxyAuth) { 1552 rv = mAuthChannel->SetProxyCredentials(creds); 1553 } else { 1554 rv = mAuthChannel->SetWWWCredentials(creds); 1555 } 1556 if (NS_FAILED(rv)) return rv; 1557 1558 // drop our remaining list of challenges. We don't need them, because we 1559 // have now authenticated against a challenge and will be sending that 1560 // information to the server (or proxy). If it doesn't accept our 1561 // authentication it'll respond with failure and resend the challenge list 1562 mRemainingChallenges.Truncate(); 1563 1564 (void)mAuthChannel->OnAuthAvailable(); 1565 1566 return NS_OK; 1567 } 1568 1569 void nsHttpChannelAuthProvider::SetAuthorizationHeader( 1570 nsHttpAuthCache* authCache, const nsHttpAtom& header, 1571 const nsACString& scheme, const nsACString& host, int32_t port, 1572 const nsACString& path, nsHttpAuthIdentity& ident) { 1573 RefPtr<nsHttpAuthEntry> entry; 1574 nsresult rv; 1575 1576 // set informations that depend on whether 1577 // we're authenticating against a proxy 1578 // or a webserver 1579 nsISupports** continuationState; 1580 1581 nsAutoCString suffix; 1582 if (header == nsHttp::Proxy_Authorization) { 1583 continuationState = &mProxyAuthContinuationState; 1584 1585 if (mProxyInfo) { 1586 nsAutoCString type; 1587 mProxyInfo->GetType(type); 1588 if (type.EqualsLiteral("https") || type.EqualsLiteral("masque")) { 1589 // Let this be overriden by anything from the cache. 1590 auto const& pa = mProxyInfo->ProxyAuthorizationHeader(); 1591 if (!pa.IsEmpty()) { 1592 rv = mAuthChannel->SetProxyCredentials(pa); 1593 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1594 } 1595 } 1596 } 1597 } else { 1598 continuationState = &mAuthContinuationState; 1599 1600 nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel); 1601 GetOriginAttributesSuffix(chan, suffix); 1602 } 1603 1604 rv = authCache->GetAuthEntryForPath(scheme, host, port, path, suffix, entry); 1605 if (NS_SUCCEEDED(rv)) { 1606 // if we are trying to add a header for origin server auth and if the 1607 // URL contains an explicit username, then try the given username first. 1608 // we only want to do this, however, if we know the URL requires auth 1609 // based on the presence of an auth cache entry for this URL (which is 1610 // true since we are here). but, if the username from the URL matches 1611 // the username from the cache, then we should prefer the password 1612 // stored in the cache since that is most likely to be valid. 1613 if (header == nsHttp::Authorization && entry->Domain()[0] == '\0') { 1614 GetIdentityFromURI(0, ident); 1615 // if the usernames match, then clear the ident so we will pick 1616 // up the one from the auth cache instead. 1617 // when this is undesired, specify LOAD_EXPLICIT_CREDENTIALS load 1618 // flag. 1619 if (ident.User() == entry->User()) { 1620 uint32_t loadFlags; 1621 if (NS_SUCCEEDED(mAuthChannel->GetLoadFlags(&loadFlags)) && 1622 !(loadFlags & nsIChannel::LOAD_EXPLICIT_CREDENTIALS)) { 1623 ident.Clear(); 1624 } 1625 } 1626 } 1627 bool identFromURI; 1628 if (ident.IsEmpty()) { 1629 ident = entry->Identity(); 1630 identFromURI = false; 1631 } else { 1632 identFromURI = true; 1633 } 1634 1635 nsCString temp; // this must have the same lifetime as creds 1636 nsAutoCString creds(entry->Creds()); 1637 // we can only send a preemptive Authorization header if we have either 1638 // stored credentials or a stored challenge from which to derive 1639 // credentials. if the identity is from the URI, then we cannot use 1640 // the stored credentials. 1641 if ((creds.IsEmpty() || identFromURI) && !entry->Challenge().IsEmpty()) { 1642 nsCOMPtr<nsIHttpAuthenticator> auth; 1643 nsAutoCString unused; 1644 rv = GetAuthenticator(entry->Challenge(), unused, getter_AddRefs(auth)); 1645 if (NS_SUCCEEDED(rv)) { 1646 bool proxyAuth = (header == nsHttp::Proxy_Authorization); 1647 rv = GenCredsAndSetEntry(auth, proxyAuth, scheme, host, port, path, 1648 entry->Realm(), entry->Challenge(), ident, 1649 entry->mMetaData, temp); 1650 if (NS_SUCCEEDED(rv)) creds = temp; 1651 1652 // make sure the continuation state is null since we do not 1653 // support mixing preemptive and 'multirequest' authentication. 1654 NS_IF_RELEASE(*continuationState); 1655 } 1656 } 1657 if (!creds.IsEmpty()) { 1658 LOG((" adding \"%s\" request header\n", header.get())); 1659 if (header == nsHttp::Proxy_Authorization) { 1660 rv = mAuthChannel->SetProxyCredentials(creds); 1661 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1662 } else { 1663 rv = mAuthChannel->SetWWWCredentials(creds); 1664 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1665 } 1666 } else { 1667 ident.Clear(); // don't remember the identity 1668 } 1669 } 1670 } 1671 1672 nsresult nsHttpChannelAuthProvider::GetCurrentPath(nsACString& path) { 1673 nsresult rv; 1674 nsCOMPtr<nsIURL> url = do_QueryInterface(mURI); 1675 if (url) { 1676 rv = url->GetDirectory(path); 1677 } else { 1678 rv = mURI->GetPathQueryRef(path); 1679 } 1680 return rv; 1681 } 1682 1683 NS_IMPL_ISUPPORTS(nsHttpChannelAuthProvider, nsICancelable, 1684 nsIHttpChannelAuthProvider, nsIAuthPromptCallback, 1685 nsIHttpAuthenticatorCallback) 1686 1687 } // namespace mozilla::net