netwerk/protocol/http/nsHttpChannelAuthProvider.cpp

Thu, 15 Jan 2015 21:03:48 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 21:03:48 +0100
branch
TOR_BUG_9701
changeset 11
deefc01c0e14
permissions
-rw-r--r--

Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)

michael@0 1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
michael@0 2 /* vim:set expandtab ts=4 sw=4 sts=4 cin: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 // HttpLog.h should generally be included first
michael@0 8 #include "HttpLog.h"
michael@0 9
michael@0 10 #include "nsHttpChannelAuthProvider.h"
michael@0 11 #include "nsNetUtil.h"
michael@0 12 #include "nsHttpHandler.h"
michael@0 13 #include "nsIHttpAuthenticator.h"
michael@0 14 #include "nsIAuthPrompt2.h"
michael@0 15 #include "nsIAuthPromptProvider.h"
michael@0 16 #include "nsIInterfaceRequestor.h"
michael@0 17 #include "nsIInterfaceRequestorUtils.h"
michael@0 18 #include "nsEscape.h"
michael@0 19 #include "nsAuthInformationHolder.h"
michael@0 20 #include "nsIStringBundle.h"
michael@0 21 #include "nsIPrompt.h"
michael@0 22 #include "netCore.h"
michael@0 23 #include "nsIHttpAuthenticableChannel.h"
michael@0 24 #include "nsIURI.h"
michael@0 25
michael@0 26 namespace mozilla {
michael@0 27 namespace net {
michael@0 28
michael@0 29 static void
michael@0 30 GetAppIdAndBrowserStatus(nsIChannel* aChan, uint32_t* aAppId, bool* aInBrowserElem)
michael@0 31 {
michael@0 32 nsCOMPtr<nsILoadContext> loadContext;
michael@0 33 if (aChan) {
michael@0 34 NS_QueryNotificationCallbacks(aChan, loadContext);
michael@0 35 }
michael@0 36 if (!loadContext) {
michael@0 37 *aAppId = NECKO_NO_APP_ID;
michael@0 38 *aInBrowserElem = false;
michael@0 39 } else {
michael@0 40 loadContext->GetAppId(aAppId);
michael@0 41 loadContext->GetIsInBrowserElement(aInBrowserElem);
michael@0 42 }
michael@0 43 }
michael@0 44
michael@0 45 nsHttpChannelAuthProvider::nsHttpChannelAuthProvider()
michael@0 46 : mAuthChannel(nullptr)
michael@0 47 , mIsPrivate(false)
michael@0 48 , mProxyAuthContinuationState(nullptr)
michael@0 49 , mAuthContinuationState(nullptr)
michael@0 50 , mProxyAuth(false)
michael@0 51 , mTriedProxyAuth(false)
michael@0 52 , mTriedHostAuth(false)
michael@0 53 , mSuppressDefensiveAuth(false)
michael@0 54 , mHttpHandler(gHttpHandler)
michael@0 55 {
michael@0 56 }
michael@0 57
michael@0 58 nsHttpChannelAuthProvider::~nsHttpChannelAuthProvider()
michael@0 59 {
michael@0 60 MOZ_ASSERT(!mAuthChannel, "Disconnect wasn't called");
michael@0 61 }
michael@0 62
michael@0 63 NS_IMETHODIMP
michael@0 64 nsHttpChannelAuthProvider::Init(nsIHttpAuthenticableChannel *channel)
michael@0 65 {
michael@0 66 MOZ_ASSERT(channel, "channel expected!");
michael@0 67
michael@0 68 mAuthChannel = channel;
michael@0 69
michael@0 70 nsresult rv = mAuthChannel->GetURI(getter_AddRefs(mURI));
michael@0 71 if (NS_FAILED(rv)) return rv;
michael@0 72
michael@0 73 mAuthChannel->GetIsSSL(&mUsingSSL);
michael@0 74 if (NS_FAILED(rv)) return rv;
michael@0 75
michael@0 76 rv = mURI->GetAsciiHost(mHost);
michael@0 77 if (NS_FAILED(rv)) return rv;
michael@0 78
michael@0 79 // reject the URL if it doesn't specify a host
michael@0 80 if (mHost.IsEmpty())
michael@0 81 return NS_ERROR_MALFORMED_URI;
michael@0 82
michael@0 83 rv = mURI->GetPort(&mPort);
michael@0 84 if (NS_FAILED(rv)) return rv;
michael@0 85
michael@0 86 nsCOMPtr<nsIChannel> bareChannel = do_QueryInterface(channel);
michael@0 87 mIsPrivate = NS_UsePrivateBrowsing(bareChannel);
michael@0 88
michael@0 89 return NS_OK;
michael@0 90 }
michael@0 91
michael@0 92 NS_IMETHODIMP
michael@0 93 nsHttpChannelAuthProvider::ProcessAuthentication(uint32_t httpStatus,
michael@0 94 bool SSLConnectFailed)
michael@0 95 {
michael@0 96 LOG(("nsHttpChannelAuthProvider::ProcessAuthentication "
michael@0 97 "[this=%p channel=%p code=%u SSLConnectFailed=%d]\n",
michael@0 98 this, mAuthChannel, httpStatus, SSLConnectFailed));
michael@0 99
michael@0 100 MOZ_ASSERT(mAuthChannel, "Channel not initialized");
michael@0 101
michael@0 102 nsCOMPtr<nsIProxyInfo> proxyInfo;
michael@0 103 nsresult rv = mAuthChannel->GetProxyInfo(getter_AddRefs(proxyInfo));
michael@0 104 if (NS_FAILED(rv)) return rv;
michael@0 105 if (proxyInfo) {
michael@0 106 mProxyInfo = do_QueryInterface(proxyInfo);
michael@0 107 if (!mProxyInfo) return NS_ERROR_NO_INTERFACE;
michael@0 108 }
michael@0 109
michael@0 110 nsAutoCString challenges;
michael@0 111 mProxyAuth = (httpStatus == 407);
michael@0 112
michael@0 113 rv = PrepareForAuthentication(mProxyAuth);
michael@0 114 if (NS_FAILED(rv))
michael@0 115 return rv;
michael@0 116
michael@0 117 if (mProxyAuth) {
michael@0 118 // only allow a proxy challenge if we have a proxy server configured.
michael@0 119 // otherwise, we could inadvertently expose the user's proxy
michael@0 120 // credentials to an origin server. We could attempt to proceed as
michael@0 121 // if we had received a 401 from the server, but why risk flirting
michael@0 122 // with trouble? IE similarly rejects 407s when a proxy server is
michael@0 123 // not configured, so there's no reason not to do the same.
michael@0 124 if (!UsingHttpProxy()) {
michael@0 125 LOG(("rejecting 407 when proxy server not configured!\n"));
michael@0 126 return NS_ERROR_UNEXPECTED;
michael@0 127 }
michael@0 128 if (UsingSSL() && !SSLConnectFailed) {
michael@0 129 // we need to verify that this challenge came from the proxy
michael@0 130 // server itself, and not some server on the other side of the
michael@0 131 // SSL tunnel.
michael@0 132 LOG(("rejecting 407 from origin server!\n"));
michael@0 133 return NS_ERROR_UNEXPECTED;
michael@0 134 }
michael@0 135 rv = mAuthChannel->GetProxyChallenges(challenges);
michael@0 136 }
michael@0 137 else
michael@0 138 rv = mAuthChannel->GetWWWChallenges(challenges);
michael@0 139 if (NS_FAILED(rv)) return rv;
michael@0 140
michael@0 141 nsAutoCString creds;
michael@0 142 rv = GetCredentials(challenges.get(), mProxyAuth, creds);
michael@0 143 if (rv == NS_ERROR_IN_PROGRESS)
michael@0 144 return rv;
michael@0 145 if (NS_FAILED(rv))
michael@0 146 LOG(("unable to authenticate\n"));
michael@0 147 else {
michael@0 148 // set the authentication credentials
michael@0 149 if (mProxyAuth)
michael@0 150 rv = mAuthChannel->SetProxyCredentials(creds);
michael@0 151 else
michael@0 152 rv = mAuthChannel->SetWWWCredentials(creds);
michael@0 153 }
michael@0 154 return rv;
michael@0 155 }
michael@0 156
michael@0 157 NS_IMETHODIMP
michael@0 158 nsHttpChannelAuthProvider::AddAuthorizationHeaders()
michael@0 159 {
michael@0 160 LOG(("nsHttpChannelAuthProvider::AddAuthorizationHeaders? "
michael@0 161 "[this=%p channel=%p]\n", this, mAuthChannel));
michael@0 162
michael@0 163 MOZ_ASSERT(mAuthChannel, "Channel not initialized");
michael@0 164
michael@0 165 nsCOMPtr<nsIProxyInfo> proxyInfo;
michael@0 166 nsresult rv = mAuthChannel->GetProxyInfo(getter_AddRefs(proxyInfo));
michael@0 167 if (NS_FAILED(rv)) return rv;
michael@0 168 if (proxyInfo) {
michael@0 169 mProxyInfo = do_QueryInterface(proxyInfo);
michael@0 170 if (!mProxyInfo) return NS_ERROR_NO_INTERFACE;
michael@0 171 }
michael@0 172
michael@0 173 uint32_t loadFlags;
michael@0 174 rv = mAuthChannel->GetLoadFlags(&loadFlags);
michael@0 175 if (NS_FAILED(rv)) return rv;
michael@0 176
michael@0 177 // this getter never fails
michael@0 178 nsHttpAuthCache *authCache = gHttpHandler->AuthCache(mIsPrivate);
michael@0 179
michael@0 180 // check if proxy credentials should be sent
michael@0 181 const char *proxyHost = ProxyHost();
michael@0 182 if (proxyHost && UsingHttpProxy())
michael@0 183 SetAuthorizationHeader(authCache, nsHttp::Proxy_Authorization,
michael@0 184 "http", proxyHost, ProxyPort(),
michael@0 185 nullptr, // proxy has no path
michael@0 186 mProxyIdent);
michael@0 187
michael@0 188 if (loadFlags & nsIRequest::LOAD_ANONYMOUS) {
michael@0 189 LOG(("Skipping Authorization header for anonymous load\n"));
michael@0 190 return NS_OK;
michael@0 191 }
michael@0 192
michael@0 193 // check if server credentials should be sent
michael@0 194 nsAutoCString path, scheme;
michael@0 195 if (NS_SUCCEEDED(GetCurrentPath(path)) &&
michael@0 196 NS_SUCCEEDED(mURI->GetScheme(scheme))) {
michael@0 197 SetAuthorizationHeader(authCache, nsHttp::Authorization,
michael@0 198 scheme.get(),
michael@0 199 Host(),
michael@0 200 Port(),
michael@0 201 path.get(),
michael@0 202 mIdent);
michael@0 203 }
michael@0 204
michael@0 205 return NS_OK;
michael@0 206 }
michael@0 207
michael@0 208 NS_IMETHODIMP
michael@0 209 nsHttpChannelAuthProvider::CheckForSuperfluousAuth()
michael@0 210 {
michael@0 211 LOG(("nsHttpChannelAuthProvider::CheckForSuperfluousAuth? "
michael@0 212 "[this=%p channel=%p]\n", this, mAuthChannel));
michael@0 213
michael@0 214 MOZ_ASSERT(mAuthChannel, "Channel not initialized");
michael@0 215
michael@0 216 // we've been called because it has been determined that this channel is
michael@0 217 // getting loaded without taking the userpass from the URL. if the URL
michael@0 218 // contained a userpass, then (provided some other conditions are true),
michael@0 219 // we'll give the user an opportunity to abort the channel as this might be
michael@0 220 // an attempt to spoof a different site (see bug 232567).
michael@0 221 if (!ConfirmAuth(NS_LITERAL_STRING("SuperfluousAuth"), true)) {
michael@0 222 // calling cancel here sets our mStatus and aborts the HTTP
michael@0 223 // transaction, which prevents OnDataAvailable events.
michael@0 224 mAuthChannel->Cancel(NS_ERROR_ABORT);
michael@0 225 return NS_ERROR_ABORT;
michael@0 226 }
michael@0 227 return NS_OK;
michael@0 228 }
michael@0 229
michael@0 230 NS_IMETHODIMP
michael@0 231 nsHttpChannelAuthProvider::Cancel(nsresult status)
michael@0 232 {
michael@0 233 MOZ_ASSERT(mAuthChannel, "Channel not initialized");
michael@0 234
michael@0 235 if (mAsyncPromptAuthCancelable) {
michael@0 236 mAsyncPromptAuthCancelable->Cancel(status);
michael@0 237 mAsyncPromptAuthCancelable = nullptr;
michael@0 238 }
michael@0 239 return NS_OK;
michael@0 240 }
michael@0 241
michael@0 242 NS_IMETHODIMP
michael@0 243 nsHttpChannelAuthProvider::Disconnect(nsresult status)
michael@0 244 {
michael@0 245 mAuthChannel = nullptr;
michael@0 246
michael@0 247 if (mAsyncPromptAuthCancelable) {
michael@0 248 mAsyncPromptAuthCancelable->Cancel(status);
michael@0 249 mAsyncPromptAuthCancelable = nullptr;
michael@0 250 }
michael@0 251
michael@0 252 NS_IF_RELEASE(mProxyAuthContinuationState);
michael@0 253 NS_IF_RELEASE(mAuthContinuationState);
michael@0 254
michael@0 255 return NS_OK;
michael@0 256 }
michael@0 257
michael@0 258 // buf contains "domain\user"
michael@0 259 static void
michael@0 260 ParseUserDomain(char16_t *buf,
michael@0 261 const char16_t **user,
michael@0 262 const char16_t **domain)
michael@0 263 {
michael@0 264 char16_t *p = buf;
michael@0 265 while (*p && *p != '\\') ++p;
michael@0 266 if (!*p)
michael@0 267 return;
michael@0 268 *p = '\0';
michael@0 269 *domain = buf;
michael@0 270 *user = p + 1;
michael@0 271 }
michael@0 272
michael@0 273 // helper function for setting identity from raw user:pass
michael@0 274 static void
michael@0 275 SetIdent(nsHttpAuthIdentity &ident,
michael@0 276 uint32_t authFlags,
michael@0 277 char16_t *userBuf,
michael@0 278 char16_t *passBuf)
michael@0 279 {
michael@0 280 const char16_t *user = userBuf;
michael@0 281 const char16_t *domain = nullptr;
michael@0 282
michael@0 283 if (authFlags & nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN)
michael@0 284 ParseUserDomain(userBuf, &user, &domain);
michael@0 285
michael@0 286 ident.Set(domain, user, passBuf);
michael@0 287 }
michael@0 288
michael@0 289 // helper function for getting an auth prompt from an interface requestor
michael@0 290 static void
michael@0 291 GetAuthPrompt(nsIInterfaceRequestor *ifreq, bool proxyAuth,
michael@0 292 nsIAuthPrompt2 **result)
michael@0 293 {
michael@0 294 if (!ifreq)
michael@0 295 return;
michael@0 296
michael@0 297 uint32_t promptReason;
michael@0 298 if (proxyAuth)
michael@0 299 promptReason = nsIAuthPromptProvider::PROMPT_PROXY;
michael@0 300 else
michael@0 301 promptReason = nsIAuthPromptProvider::PROMPT_NORMAL;
michael@0 302
michael@0 303 nsCOMPtr<nsIAuthPromptProvider> promptProvider = do_GetInterface(ifreq);
michael@0 304 if (promptProvider)
michael@0 305 promptProvider->GetAuthPrompt(promptReason,
michael@0 306 NS_GET_IID(nsIAuthPrompt2),
michael@0 307 reinterpret_cast<void**>(result));
michael@0 308 else
michael@0 309 NS_QueryAuthPrompt2(ifreq, result);
michael@0 310 }
michael@0 311
michael@0 312 // generate credentials for the given challenge, and update the auth cache.
michael@0 313 nsresult
michael@0 314 nsHttpChannelAuthProvider::GenCredsAndSetEntry(nsIHttpAuthenticator *auth,
michael@0 315 bool proxyAuth,
michael@0 316 const char *scheme,
michael@0 317 const char *host,
michael@0 318 int32_t port,
michael@0 319 const char *directory,
michael@0 320 const char *realm,
michael@0 321 const char *challenge,
michael@0 322 const nsHttpAuthIdentity &ident,
michael@0 323 nsCOMPtr<nsISupports> &sessionState,
michael@0 324 char **result)
michael@0 325 {
michael@0 326 nsresult rv;
michael@0 327 uint32_t authFlags;
michael@0 328
michael@0 329 rv = auth->GetAuthFlags(&authFlags);
michael@0 330 if (NS_FAILED(rv)) return rv;
michael@0 331
michael@0 332 nsISupports *ss = sessionState;
michael@0 333
michael@0 334 // set informations that depend on whether
michael@0 335 // we're authenticating against a proxy
michael@0 336 // or a webserver
michael@0 337 nsISupports **continuationState;
michael@0 338
michael@0 339 if (proxyAuth) {
michael@0 340 continuationState = &mProxyAuthContinuationState;
michael@0 341 } else {
michael@0 342 continuationState = &mAuthContinuationState;
michael@0 343 }
michael@0 344
michael@0 345 uint32_t generateFlags;
michael@0 346 rv = auth->GenerateCredentials(mAuthChannel,
michael@0 347 challenge,
michael@0 348 proxyAuth,
michael@0 349 ident.Domain(),
michael@0 350 ident.User(),
michael@0 351 ident.Password(),
michael@0 352 &ss,
michael@0 353 &*continuationState,
michael@0 354 &generateFlags,
michael@0 355 result);
michael@0 356
michael@0 357 sessionState.swap(ss);
michael@0 358 if (NS_FAILED(rv)) return rv;
michael@0 359
michael@0 360 // don't log this in release build since it could contain sensitive info.
michael@0 361 #ifdef DEBUG
michael@0 362 LOG(("generated creds: %s\n", *result));
michael@0 363 #endif
michael@0 364
michael@0 365 // find out if this authenticator allows reuse of credentials and/or
michael@0 366 // challenge.
michael@0 367 bool saveCreds =
michael@0 368 0 != (authFlags & nsIHttpAuthenticator::REUSABLE_CREDENTIALS);
michael@0 369 bool saveChallenge =
michael@0 370 0 != (authFlags & nsIHttpAuthenticator::REUSABLE_CHALLENGE);
michael@0 371
michael@0 372 bool saveIdentity =
michael@0 373 0 == (generateFlags & nsIHttpAuthenticator::USING_INTERNAL_IDENTITY);
michael@0 374
michael@0 375 // this getter never fails
michael@0 376 nsHttpAuthCache *authCache = gHttpHandler->AuthCache(mIsPrivate);
michael@0 377
michael@0 378 nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
michael@0 379 uint32_t appId;
michael@0 380 bool isInBrowserElement;
michael@0 381 GetAppIdAndBrowserStatus(chan, &appId, &isInBrowserElement);
michael@0 382
michael@0 383 // create a cache entry. we do this even though we don't yet know that
michael@0 384 // these credentials are valid b/c we need to avoid prompting the user
michael@0 385 // more than once in case the credentials are valid.
michael@0 386 //
michael@0 387 // if the credentials are not reusable, then we don't bother sticking
michael@0 388 // them in the auth cache.
michael@0 389 rv = authCache->SetAuthEntry(scheme, host, port, directory, realm,
michael@0 390 saveCreds ? *result : nullptr,
michael@0 391 saveChallenge ? challenge : nullptr,
michael@0 392 appId, isInBrowserElement,
michael@0 393 saveIdentity ? &ident : nullptr,
michael@0 394 sessionState);
michael@0 395 return rv;
michael@0 396 }
michael@0 397
michael@0 398 nsresult
michael@0 399 nsHttpChannelAuthProvider::PrepareForAuthentication(bool proxyAuth)
michael@0 400 {
michael@0 401 LOG(("nsHttpChannelAuthProvider::PrepareForAuthentication "
michael@0 402 "[this=%p channel=%p]\n", this, mAuthChannel));
michael@0 403
michael@0 404 if (!proxyAuth) {
michael@0 405 // reset the current proxy continuation state because our last
michael@0 406 // authentication attempt was completed successfully.
michael@0 407 NS_IF_RELEASE(mProxyAuthContinuationState);
michael@0 408 LOG((" proxy continuation state has been reset"));
michael@0 409 }
michael@0 410
michael@0 411 if (!UsingHttpProxy() || mProxyAuthType.IsEmpty())
michael@0 412 return NS_OK;
michael@0 413
michael@0 414 // We need to remove any Proxy_Authorization header left over from a
michael@0 415 // non-request based authentication handshake (e.g., for NTLM auth).
michael@0 416
michael@0 417 nsAutoCString contractId;
michael@0 418 contractId.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX);
michael@0 419 contractId.Append(mProxyAuthType);
michael@0 420
michael@0 421 nsresult rv;
michael@0 422 nsCOMPtr<nsIHttpAuthenticator> precedingAuth =
michael@0 423 do_GetService(contractId.get(), &rv);
michael@0 424 if (NS_FAILED(rv))
michael@0 425 return rv;
michael@0 426
michael@0 427 uint32_t precedingAuthFlags;
michael@0 428 rv = precedingAuth->GetAuthFlags(&precedingAuthFlags);
michael@0 429 if (NS_FAILED(rv))
michael@0 430 return rv;
michael@0 431
michael@0 432 if (!(precedingAuthFlags & nsIHttpAuthenticator::REQUEST_BASED)) {
michael@0 433 nsAutoCString challenges;
michael@0 434 rv = mAuthChannel->GetProxyChallenges(challenges);
michael@0 435 if (NS_FAILED(rv)) {
michael@0 436 // delete the proxy authorization header because we weren't
michael@0 437 // asked to authenticate
michael@0 438 rv = mAuthChannel->SetProxyCredentials(EmptyCString());
michael@0 439 if (NS_FAILED(rv)) return rv;
michael@0 440 LOG((" cleared proxy authorization header"));
michael@0 441 }
michael@0 442 }
michael@0 443
michael@0 444 return NS_OK;
michael@0 445 }
michael@0 446
michael@0 447 nsresult
michael@0 448 nsHttpChannelAuthProvider::GetCredentials(const char *challenges,
michael@0 449 bool proxyAuth,
michael@0 450 nsAFlatCString &creds)
michael@0 451 {
michael@0 452 nsCOMPtr<nsIHttpAuthenticator> auth;
michael@0 453 nsAutoCString challenge;
michael@0 454
michael@0 455 nsCString authType; // force heap allocation to enable string sharing since
michael@0 456 // we'll be assigning this value into mAuthType.
michael@0 457
michael@0 458 // set informations that depend on whether we're authenticating against a
michael@0 459 // proxy or a webserver
michael@0 460 nsISupports **currentContinuationState;
michael@0 461 nsCString *currentAuthType;
michael@0 462
michael@0 463 if (proxyAuth) {
michael@0 464 currentContinuationState = &mProxyAuthContinuationState;
michael@0 465 currentAuthType = &mProxyAuthType;
michael@0 466 } else {
michael@0 467 currentContinuationState = &mAuthContinuationState;
michael@0 468 currentAuthType = &mAuthType;
michael@0 469 }
michael@0 470
michael@0 471 nsresult rv = NS_ERROR_NOT_AVAILABLE;
michael@0 472 bool gotCreds = false;
michael@0 473
michael@0 474 // figure out which challenge we can handle and which authenticator to use.
michael@0 475 for (const char *eol = challenges - 1; eol; ) {
michael@0 476 const char *p = eol + 1;
michael@0 477
michael@0 478 // get the challenge string (LF separated -- see nsHttpHeaderArray)
michael@0 479 if ((eol = strchr(p, '\n')) != nullptr)
michael@0 480 challenge.Assign(p, eol - p);
michael@0 481 else
michael@0 482 challenge.Assign(p);
michael@0 483
michael@0 484 rv = GetAuthenticator(challenge.get(), authType, getter_AddRefs(auth));
michael@0 485 if (NS_SUCCEEDED(rv)) {
michael@0 486 //
michael@0 487 // if we've already selected an auth type from a previous challenge
michael@0 488 // received while processing this channel, then skip others until
michael@0 489 // we find a challenge corresponding to the previously tried auth
michael@0 490 // type.
michael@0 491 //
michael@0 492 if (!currentAuthType->IsEmpty() && authType != *currentAuthType)
michael@0 493 continue;
michael@0 494
michael@0 495 //
michael@0 496 // we allow the routines to run all the way through before we
michael@0 497 // decide if they are valid.
michael@0 498 //
michael@0 499 // we don't worry about the auth cache being altered because that
michael@0 500 // would have been the last step, and if the error is from updating
michael@0 501 // the authcache it wasn't really altered anyway. -CTN
michael@0 502 //
michael@0 503 // at this point the code is really only useful for client side
michael@0 504 // errors (it will not automatically fail over to do a different
michael@0 505 // auth type if the server keeps rejecting what is being sent, even
michael@0 506 // if a particular auth method only knows 1 thing, like a
michael@0 507 // non-identity based authentication method)
michael@0 508 //
michael@0 509 rv = GetCredentialsForChallenge(challenge.get(), authType.get(),
michael@0 510 proxyAuth, auth, creds);
michael@0 511 if (NS_SUCCEEDED(rv)) {
michael@0 512 gotCreds = true;
michael@0 513 *currentAuthType = authType;
michael@0 514
michael@0 515 break;
michael@0 516 }
michael@0 517 else if (rv == NS_ERROR_IN_PROGRESS) {
michael@0 518 // authentication prompt has been invoked and result is
michael@0 519 // expected asynchronously, save current challenge being
michael@0 520 // processed and all remaining challenges to use later in
michael@0 521 // OnAuthAvailable and now immediately return
michael@0 522 mCurrentChallenge = challenge;
michael@0 523 mRemainingChallenges = eol ? eol+1 : nullptr;
michael@0 524 return rv;
michael@0 525 }
michael@0 526
michael@0 527 // reset the auth type and continuation state
michael@0 528 NS_IF_RELEASE(*currentContinuationState);
michael@0 529 currentAuthType->Truncate();
michael@0 530 }
michael@0 531 }
michael@0 532
michael@0 533 if (!gotCreds && !currentAuthType->IsEmpty()) {
michael@0 534 // looks like we never found the auth type we were looking for.
michael@0 535 // reset the auth type and continuation state, and try again.
michael@0 536 currentAuthType->Truncate();
michael@0 537 NS_IF_RELEASE(*currentContinuationState);
michael@0 538
michael@0 539 rv = GetCredentials(challenges, proxyAuth, creds);
michael@0 540 }
michael@0 541
michael@0 542 return rv;
michael@0 543 }
michael@0 544
michael@0 545 nsresult
michael@0 546 nsHttpChannelAuthProvider::GetAuthorizationMembers(bool proxyAuth,
michael@0 547 nsCSubstring& scheme,
michael@0 548 const char*& host,
michael@0 549 int32_t& port,
michael@0 550 nsCSubstring& path,
michael@0 551 nsHttpAuthIdentity*& ident,
michael@0 552 nsISupports**& continuationState)
michael@0 553 {
michael@0 554 if (proxyAuth) {
michael@0 555 MOZ_ASSERT (UsingHttpProxy(),
michael@0 556 "proxyAuth is true, but no HTTP proxy is configured!");
michael@0 557
michael@0 558 host = ProxyHost();
michael@0 559 port = ProxyPort();
michael@0 560 ident = &mProxyIdent;
michael@0 561 scheme.AssignLiteral("http");
michael@0 562
michael@0 563 continuationState = &mProxyAuthContinuationState;
michael@0 564 }
michael@0 565 else {
michael@0 566 host = Host();
michael@0 567 port = Port();
michael@0 568 ident = &mIdent;
michael@0 569
michael@0 570 nsresult rv;
michael@0 571 rv = GetCurrentPath(path);
michael@0 572 if (NS_FAILED(rv)) return rv;
michael@0 573
michael@0 574 rv = mURI->GetScheme(scheme);
michael@0 575 if (NS_FAILED(rv)) return rv;
michael@0 576
michael@0 577 continuationState = &mAuthContinuationState;
michael@0 578 }
michael@0 579
michael@0 580 return NS_OK;
michael@0 581 }
michael@0 582
michael@0 583 nsresult
michael@0 584 nsHttpChannelAuthProvider::GetCredentialsForChallenge(const char *challenge,
michael@0 585 const char *authType,
michael@0 586 bool proxyAuth,
michael@0 587 nsIHttpAuthenticator *auth,
michael@0 588 nsAFlatCString &creds)
michael@0 589 {
michael@0 590 LOG(("nsHttpChannelAuthProvider::GetCredentialsForChallenge "
michael@0 591 "[this=%p channel=%p proxyAuth=%d challenges=%s]\n",
michael@0 592 this, mAuthChannel, proxyAuth, challenge));
michael@0 593
michael@0 594 // this getter never fails
michael@0 595 nsHttpAuthCache *authCache = gHttpHandler->AuthCache(mIsPrivate);
michael@0 596
michael@0 597 uint32_t authFlags;
michael@0 598 nsresult rv = auth->GetAuthFlags(&authFlags);
michael@0 599 if (NS_FAILED(rv)) return rv;
michael@0 600
michael@0 601 nsAutoCString realm;
michael@0 602 ParseRealm(challenge, realm);
michael@0 603
michael@0 604 // if no realm, then use the auth type as the realm. ToUpperCase so the
michael@0 605 // ficticious realm stands out a bit more.
michael@0 606 // XXX this will cause some single signon misses!
michael@0 607 // XXX this was meant to be used with NTLM, which supplies no realm.
michael@0 608 /*
michael@0 609 if (realm.IsEmpty()) {
michael@0 610 realm = authType;
michael@0 611 ToUpperCase(realm);
michael@0 612 }
michael@0 613 */
michael@0 614
michael@0 615 // set informations that depend on whether
michael@0 616 // we're authenticating against a proxy
michael@0 617 // or a webserver
michael@0 618 const char *host;
michael@0 619 int32_t port;
michael@0 620 nsHttpAuthIdentity *ident;
michael@0 621 nsAutoCString path, scheme;
michael@0 622 bool identFromURI = false;
michael@0 623 nsISupports **continuationState;
michael@0 624
michael@0 625 rv = GetAuthorizationMembers(proxyAuth, scheme, host, port,
michael@0 626 path, ident, continuationState);
michael@0 627 if (NS_FAILED(rv)) return rv;
michael@0 628
michael@0 629 uint32_t loadFlags;
michael@0 630 rv = mAuthChannel->GetLoadFlags(&loadFlags);
michael@0 631 if (NS_FAILED(rv)) return rv;
michael@0 632
michael@0 633 if (!proxyAuth) {
michael@0 634 // if this is the first challenge, then try using the identity
michael@0 635 // specified in the URL.
michael@0 636 if (mIdent.IsEmpty()) {
michael@0 637 GetIdentityFromURI(authFlags, mIdent);
michael@0 638 identFromURI = !mIdent.IsEmpty();
michael@0 639 }
michael@0 640
michael@0 641 if ((loadFlags & nsIRequest::LOAD_ANONYMOUS) && !identFromURI) {
michael@0 642 LOG(("Skipping authentication for anonymous non-proxy request\n"));
michael@0 643 return NS_ERROR_NOT_AVAILABLE;
michael@0 644 }
michael@0 645
michael@0 646 // Let explicit URL credentials pass
michael@0 647 // regardless of the LOAD_ANONYMOUS flag
michael@0 648 }
michael@0 649 else if ((loadFlags & nsIRequest::LOAD_ANONYMOUS) && !UsingHttpProxy()) {
michael@0 650 LOG(("Skipping authentication for anonymous non-proxy request\n"));
michael@0 651 return NS_ERROR_NOT_AVAILABLE;
michael@0 652 }
michael@0 653
michael@0 654 nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
michael@0 655 uint32_t appId;
michael@0 656 bool isInBrowserElement;
michael@0 657 GetAppIdAndBrowserStatus(chan, &appId, &isInBrowserElement);
michael@0 658
michael@0 659 //
michael@0 660 // if we already tried some credentials for this transaction, then
michael@0 661 // we need to possibly clear them from the cache, unless the credentials
michael@0 662 // in the cache have changed, in which case we'd want to give them a
michael@0 663 // try instead.
michael@0 664 //
michael@0 665 nsHttpAuthEntry *entry = nullptr;
michael@0 666 authCache->GetAuthEntryForDomain(scheme.get(), host, port,
michael@0 667 realm.get(), appId,
michael@0 668 isInBrowserElement, &entry);
michael@0 669
michael@0 670 // hold reference to the auth session state (in case we clear our
michael@0 671 // reference to the entry).
michael@0 672 nsCOMPtr<nsISupports> sessionStateGrip;
michael@0 673 if (entry)
michael@0 674 sessionStateGrip = entry->mMetaData;
michael@0 675
michael@0 676 // for digest auth, maybe our cached nonce value simply timed out...
michael@0 677 bool identityInvalid;
michael@0 678 nsISupports *sessionState = sessionStateGrip;
michael@0 679 rv = auth->ChallengeReceived(mAuthChannel,
michael@0 680 challenge,
michael@0 681 proxyAuth,
michael@0 682 &sessionState,
michael@0 683 &*continuationState,
michael@0 684 &identityInvalid);
michael@0 685 sessionStateGrip.swap(sessionState);
michael@0 686 if (NS_FAILED(rv)) return rv;
michael@0 687
michael@0 688 LOG((" identity invalid = %d\n", identityInvalid));
michael@0 689
michael@0 690 if (identityInvalid) {
michael@0 691 if (entry) {
michael@0 692 if (ident->Equals(entry->Identity())) {
michael@0 693 if (!identFromURI) {
michael@0 694 LOG((" clearing bad auth cache entry\n"));
michael@0 695 // ok, we've already tried this user identity, so clear the
michael@0 696 // corresponding entry from the auth cache.
michael@0 697 authCache->ClearAuthEntry(scheme.get(), host,
michael@0 698 port, realm.get(),
michael@0 699 appId, isInBrowserElement);
michael@0 700 entry = nullptr;
michael@0 701 ident->Clear();
michael@0 702 }
michael@0 703 }
michael@0 704 else if (!identFromURI ||
michael@0 705 (nsCRT::strcmp(ident->User(),
michael@0 706 entry->Identity().User()) == 0 &&
michael@0 707 !(loadFlags &
michael@0 708 (nsIChannel::LOAD_ANONYMOUS |
michael@0 709 nsIChannel::LOAD_EXPLICIT_CREDENTIALS)))) {
michael@0 710 LOG((" taking identity from auth cache\n"));
michael@0 711 // the password from the auth cache is more likely to be
michael@0 712 // correct than the one in the URL. at least, we know that it
michael@0 713 // works with the given username. it is possible for a server
michael@0 714 // to distinguish logons based on the supplied password alone,
michael@0 715 // but that would be quite unusual... and i don't think we need
michael@0 716 // to worry about such unorthodox cases.
michael@0 717 ident->Set(entry->Identity());
michael@0 718 identFromURI = false;
michael@0 719 if (entry->Creds()[0] != '\0') {
michael@0 720 LOG((" using cached credentials!\n"));
michael@0 721 creds.Assign(entry->Creds());
michael@0 722 return entry->AddPath(path.get());
michael@0 723 }
michael@0 724 }
michael@0 725 }
michael@0 726 else if (!identFromURI) {
michael@0 727 // hmm... identity invalid, but no auth entry! the realm probably
michael@0 728 // changed (see bug 201986).
michael@0 729 ident->Clear();
michael@0 730 }
michael@0 731
michael@0 732 if (!entry && ident->IsEmpty()) {
michael@0 733 uint32_t level = nsIAuthPrompt2::LEVEL_NONE;
michael@0 734 if (mUsingSSL)
michael@0 735 level = nsIAuthPrompt2::LEVEL_SECURE;
michael@0 736 else if (authFlags & nsIHttpAuthenticator::IDENTITY_ENCRYPTED)
michael@0 737 level = nsIAuthPrompt2::LEVEL_PW_ENCRYPTED;
michael@0 738
michael@0 739 // at this point we are forced to interact with the user to get
michael@0 740 // their username and password for this domain.
michael@0 741 rv = PromptForIdentity(level, proxyAuth, realm.get(),
michael@0 742 authType, authFlags, *ident);
michael@0 743 if (NS_FAILED(rv)) return rv;
michael@0 744 identFromURI = false;
michael@0 745 }
michael@0 746 }
michael@0 747
michael@0 748 if (identFromURI) {
michael@0 749 // Warn the user before automatically using the identity from the URL
michael@0 750 // to automatically log them into a site (see bug 232567).
michael@0 751 if (!ConfirmAuth(NS_LITERAL_STRING("AutomaticAuth"), false)) {
michael@0 752 // calling cancel here sets our mStatus and aborts the HTTP
michael@0 753 // transaction, which prevents OnDataAvailable events.
michael@0 754 mAuthChannel->Cancel(NS_ERROR_ABORT);
michael@0 755 // this return code alone is not equivalent to Cancel, since
michael@0 756 // it only instructs our caller that authentication failed.
michael@0 757 // without an explicit call to Cancel, our caller would just
michael@0 758 // load the page that accompanies the HTTP auth challenge.
michael@0 759 return NS_ERROR_ABORT;
michael@0 760 }
michael@0 761 }
michael@0 762
michael@0 763 //
michael@0 764 // get credentials for the given user:pass
michael@0 765 //
michael@0 766 // always store the credentials we're trying now so that they will be used
michael@0 767 // on subsequent links. This will potentially remove good credentials from
michael@0 768 // the cache. This is ok as we don't want to use cached credentials if the
michael@0 769 // user specified something on the URI or in another manner. This is so
michael@0 770 // that we don't transparently authenticate as someone they're not
michael@0 771 // expecting to authenticate as.
michael@0 772 //
michael@0 773 nsXPIDLCString result;
michael@0 774 rv = GenCredsAndSetEntry(auth, proxyAuth, scheme.get(), host, port,
michael@0 775 path.get(), realm.get(), challenge, *ident,
michael@0 776 sessionStateGrip, getter_Copies(result));
michael@0 777 if (NS_SUCCEEDED(rv))
michael@0 778 creds = result;
michael@0 779 return rv;
michael@0 780 }
michael@0 781
michael@0 782 inline void
michael@0 783 GetAuthType(const char *challenge, nsCString &authType)
michael@0 784 {
michael@0 785 const char *p;
michael@0 786
michael@0 787 // get the challenge type
michael@0 788 if ((p = strchr(challenge, ' ')) != nullptr)
michael@0 789 authType.Assign(challenge, p - challenge);
michael@0 790 else
michael@0 791 authType.Assign(challenge);
michael@0 792 }
michael@0 793
michael@0 794 nsresult
michael@0 795 nsHttpChannelAuthProvider::GetAuthenticator(const char *challenge,
michael@0 796 nsCString &authType,
michael@0 797 nsIHttpAuthenticator **auth)
michael@0 798 {
michael@0 799 LOG(("nsHttpChannelAuthProvider::GetAuthenticator [this=%p channel=%p]\n",
michael@0 800 this, mAuthChannel));
michael@0 801
michael@0 802 GetAuthType(challenge, authType);
michael@0 803
michael@0 804 // normalize to lowercase
michael@0 805 ToLowerCase(authType);
michael@0 806
michael@0 807 nsAutoCString contractid;
michael@0 808 contractid.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX);
michael@0 809 contractid.Append(authType);
michael@0 810
michael@0 811 return CallGetService(contractid.get(), auth);
michael@0 812 }
michael@0 813
michael@0 814 void
michael@0 815 nsHttpChannelAuthProvider::GetIdentityFromURI(uint32_t authFlags,
michael@0 816 nsHttpAuthIdentity &ident)
michael@0 817 {
michael@0 818 LOG(("nsHttpChannelAuthProvider::GetIdentityFromURI [this=%p channel=%p]\n",
michael@0 819 this, mAuthChannel));
michael@0 820
michael@0 821 nsAutoString userBuf;
michael@0 822 nsAutoString passBuf;
michael@0 823
michael@0 824 // XXX i18n
michael@0 825 nsAutoCString buf;
michael@0 826 mURI->GetUsername(buf);
michael@0 827 if (!buf.IsEmpty()) {
michael@0 828 NS_UnescapeURL(buf);
michael@0 829 CopyASCIItoUTF16(buf, userBuf);
michael@0 830 mURI->GetPassword(buf);
michael@0 831 if (!buf.IsEmpty()) {
michael@0 832 NS_UnescapeURL(buf);
michael@0 833 CopyASCIItoUTF16(buf, passBuf);
michael@0 834 }
michael@0 835 }
michael@0 836
michael@0 837 if (!userBuf.IsEmpty()) {
michael@0 838 SetIdent(ident, authFlags, (char16_t *) userBuf.get(),
michael@0 839 (char16_t *) passBuf.get());
michael@0 840 }
michael@0 841 }
michael@0 842
michael@0 843 void
michael@0 844 nsHttpChannelAuthProvider::ParseRealm(const char *challenge,
michael@0 845 nsACString &realm)
michael@0 846 {
michael@0 847 //
michael@0 848 // From RFC2617 section 1.2, the realm value is defined as such:
michael@0 849 //
michael@0 850 // realm = "realm" "=" realm-value
michael@0 851 // realm-value = quoted-string
michael@0 852 //
michael@0 853 // but, we'll accept anything after the the "=" up to the first space, or
michael@0 854 // end-of-line, if the string is not quoted.
michael@0 855 //
michael@0 856
michael@0 857 const char *p = PL_strcasestr(challenge, "realm=");
michael@0 858 if (p) {
michael@0 859 bool has_quote = false;
michael@0 860 p += 6;
michael@0 861 if (*p == '"') {
michael@0 862 has_quote = true;
michael@0 863 p++;
michael@0 864 }
michael@0 865
michael@0 866 const char *end;
michael@0 867 if (has_quote) {
michael@0 868 end = p;
michael@0 869 while (*end) {
michael@0 870 if (*end == '\\') {
michael@0 871 // escaped character, store that one instead if not zero
michael@0 872 if (!*++end)
michael@0 873 break;
michael@0 874 }
michael@0 875 else if (*end == '\"')
michael@0 876 // end of string
michael@0 877 break;
michael@0 878
michael@0 879 realm.Append(*end);
michael@0 880 ++end;
michael@0 881 }
michael@0 882 }
michael@0 883 else {
michael@0 884 // realm given without quotes
michael@0 885 end = strchr(p, ' ');
michael@0 886 if (end)
michael@0 887 realm.Assign(p, end - p);
michael@0 888 else
michael@0 889 realm.Assign(p);
michael@0 890 }
michael@0 891 }
michael@0 892 }
michael@0 893
michael@0 894
michael@0 895 class nsHTTPAuthInformation : public nsAuthInformationHolder {
michael@0 896 public:
michael@0 897 nsHTTPAuthInformation(uint32_t aFlags, const nsString& aRealm,
michael@0 898 const nsCString& aAuthType)
michael@0 899 : nsAuthInformationHolder(aFlags, aRealm, aAuthType) {}
michael@0 900
michael@0 901 void SetToHttpAuthIdentity(uint32_t authFlags,
michael@0 902 nsHttpAuthIdentity& identity);
michael@0 903 };
michael@0 904
michael@0 905 void
michael@0 906 nsHTTPAuthInformation::SetToHttpAuthIdentity(uint32_t authFlags,
michael@0 907 nsHttpAuthIdentity& identity)
michael@0 908 {
michael@0 909 identity.Set(Domain().get(), User().get(), Password().get());
michael@0 910 }
michael@0 911
michael@0 912 nsresult
michael@0 913 nsHttpChannelAuthProvider::PromptForIdentity(uint32_t level,
michael@0 914 bool proxyAuth,
michael@0 915 const char *realm,
michael@0 916 const char *authType,
michael@0 917 uint32_t authFlags,
michael@0 918 nsHttpAuthIdentity &ident)
michael@0 919 {
michael@0 920 LOG(("nsHttpChannelAuthProvider::PromptForIdentity [this=%p channel=%p]\n",
michael@0 921 this, mAuthChannel));
michael@0 922
michael@0 923 nsresult rv;
michael@0 924
michael@0 925 nsCOMPtr<nsIInterfaceRequestor> callbacks;
michael@0 926 rv = mAuthChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
michael@0 927 if (NS_FAILED(rv)) return rv;
michael@0 928
michael@0 929 nsCOMPtr<nsILoadGroup> loadGroup;
michael@0 930 rv = mAuthChannel->GetLoadGroup(getter_AddRefs(loadGroup));
michael@0 931 if (NS_FAILED(rv)) return rv;
michael@0 932
michael@0 933 nsCOMPtr<nsIAuthPrompt2> authPrompt;
michael@0 934 GetAuthPrompt(callbacks, proxyAuth, getter_AddRefs(authPrompt));
michael@0 935 if (!authPrompt && loadGroup) {
michael@0 936 nsCOMPtr<nsIInterfaceRequestor> cbs;
michael@0 937 loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
michael@0 938 GetAuthPrompt(cbs, proxyAuth, getter_AddRefs(authPrompt));
michael@0 939 }
michael@0 940 if (!authPrompt)
michael@0 941 return NS_ERROR_NO_INTERFACE;
michael@0 942
michael@0 943 // XXX i18n: need to support non-ASCII realm strings (see bug 41489)
michael@0 944 NS_ConvertASCIItoUTF16 realmU(realm);
michael@0 945
michael@0 946 // prompt the user...
michael@0 947 uint32_t promptFlags = 0;
michael@0 948 if (proxyAuth)
michael@0 949 {
michael@0 950 promptFlags |= nsIAuthInformation::AUTH_PROXY;
michael@0 951 if (mTriedProxyAuth)
michael@0 952 promptFlags |= nsIAuthInformation::PREVIOUS_FAILED;
michael@0 953 mTriedProxyAuth = true;
michael@0 954 }
michael@0 955 else {
michael@0 956 promptFlags |= nsIAuthInformation::AUTH_HOST;
michael@0 957 if (mTriedHostAuth)
michael@0 958 promptFlags |= nsIAuthInformation::PREVIOUS_FAILED;
michael@0 959 mTriedHostAuth = true;
michael@0 960 }
michael@0 961
michael@0 962 if (authFlags & nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN)
michael@0 963 promptFlags |= nsIAuthInformation::NEED_DOMAIN;
michael@0 964
michael@0 965 nsRefPtr<nsHTTPAuthInformation> holder =
michael@0 966 new nsHTTPAuthInformation(promptFlags, realmU,
michael@0 967 nsDependentCString(authType));
michael@0 968 if (!holder)
michael@0 969 return NS_ERROR_OUT_OF_MEMORY;
michael@0 970
michael@0 971 nsCOMPtr<nsIChannel> channel(do_QueryInterface(mAuthChannel, &rv));
michael@0 972 if (NS_FAILED(rv)) return rv;
michael@0 973
michael@0 974 rv =
michael@0 975 authPrompt->AsyncPromptAuth(channel, this, nullptr, level, holder,
michael@0 976 getter_AddRefs(mAsyncPromptAuthCancelable));
michael@0 977
michael@0 978 if (NS_SUCCEEDED(rv)) {
michael@0 979 // indicate using this error code that authentication prompt
michael@0 980 // result is expected asynchronously
michael@0 981 rv = NS_ERROR_IN_PROGRESS;
michael@0 982 }
michael@0 983 else {
michael@0 984 // Fall back to synchronous prompt
michael@0 985 bool retval = false;
michael@0 986 rv = authPrompt->PromptAuth(channel, level, holder, &retval);
michael@0 987 if (NS_FAILED(rv))
michael@0 988 return rv;
michael@0 989
michael@0 990 if (!retval)
michael@0 991 rv = NS_ERROR_ABORT;
michael@0 992 else
michael@0 993 holder->SetToHttpAuthIdentity(authFlags, ident);
michael@0 994 }
michael@0 995
michael@0 996 // remember that we successfully showed the user an auth dialog
michael@0 997 if (!proxyAuth)
michael@0 998 mSuppressDefensiveAuth = true;
michael@0 999
michael@0 1000 return rv;
michael@0 1001 }
michael@0 1002
michael@0 1003 NS_IMETHODIMP nsHttpChannelAuthProvider::OnAuthAvailable(nsISupports *aContext,
michael@0 1004 nsIAuthInformation *aAuthInfo)
michael@0 1005 {
michael@0 1006 LOG(("nsHttpChannelAuthProvider::OnAuthAvailable [this=%p channel=%p]",
michael@0 1007 this, mAuthChannel));
michael@0 1008
michael@0 1009 mAsyncPromptAuthCancelable = nullptr;
michael@0 1010 if (!mAuthChannel)
michael@0 1011 return NS_OK;
michael@0 1012
michael@0 1013 nsresult rv;
michael@0 1014
michael@0 1015 const char *host;
michael@0 1016 int32_t port;
michael@0 1017 nsHttpAuthIdentity *ident;
michael@0 1018 nsAutoCString path, scheme;
michael@0 1019 nsISupports **continuationState;
michael@0 1020 rv = GetAuthorizationMembers(mProxyAuth, scheme, host, port,
michael@0 1021 path, ident, continuationState);
michael@0 1022 if (NS_FAILED(rv))
michael@0 1023 OnAuthCancelled(aContext, false);
michael@0 1024
michael@0 1025 nsAutoCString realm;
michael@0 1026 ParseRealm(mCurrentChallenge.get(), realm);
michael@0 1027
michael@0 1028 nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
michael@0 1029 uint32_t appId;
michael@0 1030 bool isInBrowserElement;
michael@0 1031 GetAppIdAndBrowserStatus(chan, &appId, &isInBrowserElement);
michael@0 1032
michael@0 1033 nsHttpAuthCache *authCache = gHttpHandler->AuthCache(mIsPrivate);
michael@0 1034 nsHttpAuthEntry *entry = nullptr;
michael@0 1035 authCache->GetAuthEntryForDomain(scheme.get(), host, port,
michael@0 1036 realm.get(), appId,
michael@0 1037 isInBrowserElement,
michael@0 1038 &entry);
michael@0 1039
michael@0 1040 nsCOMPtr<nsISupports> sessionStateGrip;
michael@0 1041 if (entry)
michael@0 1042 sessionStateGrip = entry->mMetaData;
michael@0 1043
michael@0 1044 nsAuthInformationHolder* holder =
michael@0 1045 static_cast<nsAuthInformationHolder*>(aAuthInfo);
michael@0 1046 ident->Set(holder->Domain().get(),
michael@0 1047 holder->User().get(),
michael@0 1048 holder->Password().get());
michael@0 1049
michael@0 1050 nsAutoCString unused;
michael@0 1051 nsCOMPtr<nsIHttpAuthenticator> auth;
michael@0 1052 rv = GetAuthenticator(mCurrentChallenge.get(), unused,
michael@0 1053 getter_AddRefs(auth));
michael@0 1054 if (NS_FAILED(rv)) {
michael@0 1055 MOZ_ASSERT(false, "GetAuthenticator failed");
michael@0 1056 OnAuthCancelled(aContext, true);
michael@0 1057 return NS_OK;
michael@0 1058 }
michael@0 1059
michael@0 1060 nsXPIDLCString creds;
michael@0 1061 rv = GenCredsAndSetEntry(auth, mProxyAuth,
michael@0 1062 scheme.get(), host, port, path.get(),
michael@0 1063 realm.get(), mCurrentChallenge.get(), *ident,
michael@0 1064 sessionStateGrip, getter_Copies(creds));
michael@0 1065
michael@0 1066 mCurrentChallenge.Truncate();
michael@0 1067 if (NS_FAILED(rv)) {
michael@0 1068 OnAuthCancelled(aContext, true);
michael@0 1069 return NS_OK;
michael@0 1070 }
michael@0 1071
michael@0 1072 return ContinueOnAuthAvailable(creds);
michael@0 1073 }
michael@0 1074
michael@0 1075 NS_IMETHODIMP nsHttpChannelAuthProvider::OnAuthCancelled(nsISupports *aContext,
michael@0 1076 bool userCancel)
michael@0 1077 {
michael@0 1078 LOG(("nsHttpChannelAuthProvider::OnAuthCancelled [this=%p channel=%p]",
michael@0 1079 this, mAuthChannel));
michael@0 1080
michael@0 1081 mAsyncPromptAuthCancelable = nullptr;
michael@0 1082 if (!mAuthChannel)
michael@0 1083 return NS_OK;
michael@0 1084
michael@0 1085 if (userCancel) {
michael@0 1086 if (!mRemainingChallenges.IsEmpty()) {
michael@0 1087 // there are still some challenges to process, do so
michael@0 1088 nsresult rv;
michael@0 1089
michael@0 1090 nsAutoCString creds;
michael@0 1091 rv = GetCredentials(mRemainingChallenges.get(), mProxyAuth, creds);
michael@0 1092 if (NS_SUCCEEDED(rv)) {
michael@0 1093 // GetCredentials loaded the credentials from the cache or
michael@0 1094 // some other way in a synchronous manner, process those
michael@0 1095 // credentials now
michael@0 1096 mRemainingChallenges.Truncate();
michael@0 1097 return ContinueOnAuthAvailable(creds);
michael@0 1098 }
michael@0 1099 else if (rv == NS_ERROR_IN_PROGRESS) {
michael@0 1100 // GetCredentials successfully queued another authprompt for
michael@0 1101 // a challenge from the list, we are now waiting for the user
michael@0 1102 // to provide the credentials
michael@0 1103 return NS_OK;
michael@0 1104 }
michael@0 1105
michael@0 1106 // otherwise, we failed...
michael@0 1107 }
michael@0 1108
michael@0 1109 mRemainingChallenges.Truncate();
michael@0 1110 }
michael@0 1111
michael@0 1112 mAuthChannel->OnAuthCancelled(userCancel);
michael@0 1113
michael@0 1114 return NS_OK;
michael@0 1115 }
michael@0 1116
michael@0 1117 nsresult
michael@0 1118 nsHttpChannelAuthProvider::ContinueOnAuthAvailable(const nsCSubstring& creds)
michael@0 1119 {
michael@0 1120 nsresult rv;
michael@0 1121 if (mProxyAuth)
michael@0 1122 rv = mAuthChannel->SetProxyCredentials(creds);
michael@0 1123 else
michael@0 1124 rv = mAuthChannel->SetWWWCredentials(creds);
michael@0 1125 if (NS_FAILED(rv)) return rv;
michael@0 1126
michael@0 1127 // drop our remaining list of challenges. We don't need them, because we
michael@0 1128 // have now authenticated against a challenge and will be sending that
michael@0 1129 // information to the server (or proxy). If it doesn't accept our
michael@0 1130 // authentication it'll respond with failure and resend the challenge list
michael@0 1131 mRemainingChallenges.Truncate();
michael@0 1132
michael@0 1133 mAuthChannel->OnAuthAvailable();
michael@0 1134
michael@0 1135 return NS_OK;
michael@0 1136 }
michael@0 1137
michael@0 1138 bool
michael@0 1139 nsHttpChannelAuthProvider::ConfirmAuth(const nsString &bundleKey,
michael@0 1140 bool doYesNoPrompt)
michael@0 1141 {
michael@0 1142 // skip prompting the user if
michael@0 1143 // 1) we've already prompted the user
michael@0 1144 // 2) we're not a toplevel channel
michael@0 1145 // 3) the userpass length is less than the "phishy" threshold
michael@0 1146
michael@0 1147 uint32_t loadFlags;
michael@0 1148 nsresult rv = mAuthChannel->GetLoadFlags(&loadFlags);
michael@0 1149 if (NS_FAILED(rv))
michael@0 1150 return true;
michael@0 1151
michael@0 1152 if (mSuppressDefensiveAuth ||
michael@0 1153 !(loadFlags & nsIChannel::LOAD_INITIAL_DOCUMENT_URI))
michael@0 1154 return true;
michael@0 1155
michael@0 1156 nsAutoCString userPass;
michael@0 1157 rv = mURI->GetUserPass(userPass);
michael@0 1158 if (NS_FAILED(rv) ||
michael@0 1159 (userPass.Length() < gHttpHandler->PhishyUserPassLength()))
michael@0 1160 return true;
michael@0 1161
michael@0 1162 // we try to confirm by prompting the user. if we cannot do so, then
michael@0 1163 // assume the user said ok. this is done to keep things working in
michael@0 1164 // embedded builds, where the string bundle might not be present, etc.
michael@0 1165
michael@0 1166 nsCOMPtr<nsIStringBundleService> bundleService =
michael@0 1167 do_GetService(NS_STRINGBUNDLE_CONTRACTID);
michael@0 1168 if (!bundleService)
michael@0 1169 return true;
michael@0 1170
michael@0 1171 nsCOMPtr<nsIStringBundle> bundle;
michael@0 1172 bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(bundle));
michael@0 1173 if (!bundle)
michael@0 1174 return true;
michael@0 1175
michael@0 1176 nsAutoCString host;
michael@0 1177 rv = mURI->GetHost(host);
michael@0 1178 if (NS_FAILED(rv))
michael@0 1179 return true;
michael@0 1180
michael@0 1181 nsAutoCString user;
michael@0 1182 rv = mURI->GetUsername(user);
michael@0 1183 if (NS_FAILED(rv))
michael@0 1184 return true;
michael@0 1185
michael@0 1186 NS_ConvertUTF8toUTF16 ucsHost(host), ucsUser(user);
michael@0 1187 const char16_t *strs[2] = { ucsHost.get(), ucsUser.get() };
michael@0 1188
michael@0 1189 nsXPIDLString msg;
michael@0 1190 bundle->FormatStringFromName(bundleKey.get(), strs, 2, getter_Copies(msg));
michael@0 1191 if (!msg)
michael@0 1192 return true;
michael@0 1193
michael@0 1194 nsCOMPtr<nsIInterfaceRequestor> callbacks;
michael@0 1195 rv = mAuthChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
michael@0 1196 if (NS_FAILED(rv))
michael@0 1197 return true;
michael@0 1198
michael@0 1199 nsCOMPtr<nsILoadGroup> loadGroup;
michael@0 1200 rv = mAuthChannel->GetLoadGroup(getter_AddRefs(loadGroup));
michael@0 1201 if (NS_FAILED(rv))
michael@0 1202 return true;
michael@0 1203
michael@0 1204 nsCOMPtr<nsIPrompt> prompt;
michael@0 1205 NS_QueryNotificationCallbacks(callbacks, loadGroup, NS_GET_IID(nsIPrompt),
michael@0 1206 getter_AddRefs(prompt));
michael@0 1207 if (!prompt)
michael@0 1208 return true;
michael@0 1209
michael@0 1210 // do not prompt again
michael@0 1211 mSuppressDefensiveAuth = true;
michael@0 1212
michael@0 1213 bool confirmed;
michael@0 1214 if (doYesNoPrompt) {
michael@0 1215 int32_t choice;
michael@0 1216 bool checkState = false;
michael@0 1217 rv = prompt->ConfirmEx(nullptr, msg,
michael@0 1218 nsIPrompt::BUTTON_POS_1_DEFAULT +
michael@0 1219 nsIPrompt::STD_YES_NO_BUTTONS,
michael@0 1220 nullptr, nullptr, nullptr, nullptr,
michael@0 1221 &checkState, &choice);
michael@0 1222 if (NS_FAILED(rv))
michael@0 1223 return true;
michael@0 1224
michael@0 1225 confirmed = choice == 0;
michael@0 1226 }
michael@0 1227 else {
michael@0 1228 rv = prompt->Confirm(nullptr, msg, &confirmed);
michael@0 1229 if (NS_FAILED(rv))
michael@0 1230 return true;
michael@0 1231 }
michael@0 1232
michael@0 1233 return confirmed;
michael@0 1234 }
michael@0 1235
michael@0 1236 void
michael@0 1237 nsHttpChannelAuthProvider::SetAuthorizationHeader(nsHttpAuthCache *authCache,
michael@0 1238 nsHttpAtom header,
michael@0 1239 const char *scheme,
michael@0 1240 const char *host,
michael@0 1241 int32_t port,
michael@0 1242 const char *path,
michael@0 1243 nsHttpAuthIdentity &ident)
michael@0 1244 {
michael@0 1245 nsHttpAuthEntry *entry = nullptr;
michael@0 1246 nsresult rv;
michael@0 1247
michael@0 1248 // set informations that depend on whether
michael@0 1249 // we're authenticating against a proxy
michael@0 1250 // or a webserver
michael@0 1251 nsISupports **continuationState;
michael@0 1252
michael@0 1253 if (header == nsHttp::Proxy_Authorization) {
michael@0 1254 continuationState = &mProxyAuthContinuationState;
michael@0 1255 } else {
michael@0 1256 continuationState = &mAuthContinuationState;
michael@0 1257 }
michael@0 1258
michael@0 1259 nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
michael@0 1260 uint32_t appId;
michael@0 1261 bool isInBrowserElement;
michael@0 1262 GetAppIdAndBrowserStatus(chan, &appId, &isInBrowserElement);
michael@0 1263
michael@0 1264 rv = authCache->GetAuthEntryForPath(scheme, host, port, path,
michael@0 1265 appId, isInBrowserElement, &entry);
michael@0 1266 if (NS_SUCCEEDED(rv)) {
michael@0 1267 // if we are trying to add a header for origin server auth and if the
michael@0 1268 // URL contains an explicit username, then try the given username first.
michael@0 1269 // we only want to do this, however, if we know the URL requires auth
michael@0 1270 // based on the presence of an auth cache entry for this URL (which is
michael@0 1271 // true since we are here). but, if the username from the URL matches
michael@0 1272 // the username from the cache, then we should prefer the password
michael@0 1273 // stored in the cache since that is most likely to be valid.
michael@0 1274 if (header == nsHttp::Authorization && entry->Domain()[0] == '\0') {
michael@0 1275 GetIdentityFromURI(0, ident);
michael@0 1276 // if the usernames match, then clear the ident so we will pick
michael@0 1277 // up the one from the auth cache instead.
michael@0 1278 // when this is undesired, specify LOAD_EXPLICIT_CREDENTIALS load
michael@0 1279 // flag.
michael@0 1280 if (nsCRT::strcmp(ident.User(), entry->User()) == 0) {
michael@0 1281 uint32_t loadFlags;
michael@0 1282 if (NS_SUCCEEDED(mAuthChannel->GetLoadFlags(&loadFlags)) &&
michael@0 1283 !(loadFlags & nsIChannel::LOAD_EXPLICIT_CREDENTIALS)) {
michael@0 1284 ident.Clear();
michael@0 1285 }
michael@0 1286 }
michael@0 1287 }
michael@0 1288 bool identFromURI;
michael@0 1289 if (ident.IsEmpty()) {
michael@0 1290 ident.Set(entry->Identity());
michael@0 1291 identFromURI = false;
michael@0 1292 }
michael@0 1293 else
michael@0 1294 identFromURI = true;
michael@0 1295
michael@0 1296 nsXPIDLCString temp;
michael@0 1297 const char *creds = entry->Creds();
michael@0 1298 const char *challenge = entry->Challenge();
michael@0 1299 // we can only send a preemptive Authorization header if we have either
michael@0 1300 // stored credentials or a stored challenge from which to derive
michael@0 1301 // credentials. if the identity is from the URI, then we cannot use
michael@0 1302 // the stored credentials.
michael@0 1303 if ((!creds[0] || identFromURI) && challenge[0]) {
michael@0 1304 nsCOMPtr<nsIHttpAuthenticator> auth;
michael@0 1305 nsAutoCString unused;
michael@0 1306 rv = GetAuthenticator(challenge, unused, getter_AddRefs(auth));
michael@0 1307 if (NS_SUCCEEDED(rv)) {
michael@0 1308 bool proxyAuth = (header == nsHttp::Proxy_Authorization);
michael@0 1309 rv = GenCredsAndSetEntry(auth, proxyAuth, scheme, host, port,
michael@0 1310 path, entry->Realm(), challenge, ident,
michael@0 1311 entry->mMetaData, getter_Copies(temp));
michael@0 1312 if (NS_SUCCEEDED(rv))
michael@0 1313 creds = temp.get();
michael@0 1314
michael@0 1315 // make sure the continuation state is null since we do not
michael@0 1316 // support mixing preemptive and 'multirequest' authentication.
michael@0 1317 NS_IF_RELEASE(*continuationState);
michael@0 1318 }
michael@0 1319 }
michael@0 1320 if (creds[0]) {
michael@0 1321 LOG((" adding \"%s\" request header\n", header.get()));
michael@0 1322 if (header == nsHttp::Proxy_Authorization)
michael@0 1323 mAuthChannel->SetProxyCredentials(nsDependentCString(creds));
michael@0 1324 else
michael@0 1325 mAuthChannel->SetWWWCredentials(nsDependentCString(creds));
michael@0 1326
michael@0 1327 // suppress defensive auth prompting for this channel since we know
michael@0 1328 // that we already prompted at least once this session. we only do
michael@0 1329 // this for non-proxy auth since the URL's userpass is not used for
michael@0 1330 // proxy auth.
michael@0 1331 if (header == nsHttp::Authorization)
michael@0 1332 mSuppressDefensiveAuth = true;
michael@0 1333 }
michael@0 1334 else
michael@0 1335 ident.Clear(); // don't remember the identity
michael@0 1336 }
michael@0 1337 }
michael@0 1338
michael@0 1339 nsresult
michael@0 1340 nsHttpChannelAuthProvider::GetCurrentPath(nsACString &path)
michael@0 1341 {
michael@0 1342 nsresult rv;
michael@0 1343 nsCOMPtr<nsIURL> url = do_QueryInterface(mURI);
michael@0 1344 if (url)
michael@0 1345 rv = url->GetDirectory(path);
michael@0 1346 else
michael@0 1347 rv = mURI->GetPath(path);
michael@0 1348 return rv;
michael@0 1349 }
michael@0 1350
michael@0 1351 NS_IMPL_ISUPPORTS(nsHttpChannelAuthProvider, nsICancelable,
michael@0 1352 nsIHttpChannelAuthProvider, nsIAuthPromptCallback)
michael@0 1353
michael@0 1354 } // namespace mozilla::net
michael@0 1355 } // namespace mozilla

mercurial