1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "net/http/http_auth_controller.h"
6
7#include "base/bind.h"
8#include "base/bind_helpers.h"
9#include "base/metrics/histogram.h"
10#include "base/strings/string_util.h"
11#include "base/strings/utf_string_conversions.h"
12#include "base/threading/platform_thread.h"
13#include "net/base/auth.h"
14#include "net/base/net_util.h"
15#include "net/dns/host_resolver.h"
16#include "net/http/http_auth_handler.h"
17#include "net/http/http_auth_handler_factory.h"
18#include "net/http/http_network_session.h"
19#include "net/http/http_request_headers.h"
20#include "net/http/http_request_info.h"
21#include "net/http/http_response_headers.h"
22
23namespace net {
24
25namespace {
26
27// Returns a log message for all the response headers related to the auth
28// challenge.
29std::string AuthChallengeLogMessage(HttpResponseHeaders* headers) {
30  std::string msg;
31  std::string header_val;
32  void* iter = NULL;
33  while (headers->EnumerateHeader(&iter, "proxy-authenticate", &header_val)) {
34    msg.append("\n  Has header Proxy-Authenticate: ");
35    msg.append(header_val);
36  }
37
38  iter = NULL;
39  while (headers->EnumerateHeader(&iter, "www-authenticate", &header_val)) {
40    msg.append("\n  Has header WWW-Authenticate: ");
41    msg.append(header_val);
42  }
43
44  // RFC 4559 requires that a proxy indicate its support of NTLM/Negotiate
45  // authentication with a "Proxy-Support: Session-Based-Authentication"
46  // response header.
47  iter = NULL;
48  while (headers->EnumerateHeader(&iter, "proxy-support", &header_val)) {
49    msg.append("\n  Has header Proxy-Support: ");
50    msg.append(header_val);
51  }
52
53  return msg;
54}
55
56enum AuthEvent {
57  AUTH_EVENT_START = 0,
58  AUTH_EVENT_REJECT,
59  AUTH_EVENT_MAX,
60};
61
62enum AuthTarget {
63  AUTH_TARGET_PROXY = 0,
64  AUTH_TARGET_SECURE_PROXY,
65  AUTH_TARGET_SERVER,
66  AUTH_TARGET_SECURE_SERVER,
67  AUTH_TARGET_MAX,
68};
69
70AuthTarget DetermineAuthTarget(const HttpAuthHandler* handler) {
71  switch (handler->target()) {
72    case HttpAuth::AUTH_PROXY:
73      if (handler->origin().SchemeIsSecure())
74        return AUTH_TARGET_SECURE_PROXY;
75      else
76        return AUTH_TARGET_PROXY;
77    case HttpAuth::AUTH_SERVER:
78      if (handler->origin().SchemeIsSecure())
79        return AUTH_TARGET_SECURE_SERVER;
80      else
81        return AUTH_TARGET_SERVER;
82    default:
83      NOTREACHED();
84      return AUTH_TARGET_MAX;
85  }
86}
87
88// Records the number of authentication events per authentication scheme.
89void HistogramAuthEvent(HttpAuthHandler* handler, AuthEvent auth_event) {
90#if !defined(NDEBUG)
91  // Note: The on-same-thread check is intentionally not using a lock
92  // to protect access to first_thread. This method is meant to be only
93  // used on the same thread, in which case there are no race conditions. If
94  // there are race conditions (say, a read completes during a partial write),
95  // the DCHECK will correctly fail.
96  static base::PlatformThreadId first_thread =
97      base::PlatformThread::CurrentId();
98  DCHECK_EQ(first_thread, base::PlatformThread::CurrentId());
99#endif
100
101  HttpAuth::Scheme auth_scheme = handler->auth_scheme();
102  DCHECK(auth_scheme >= 0 && auth_scheme < HttpAuth::AUTH_SCHEME_MAX);
103
104  // Record start and rejection events for authentication.
105  //
106  // The results map to:
107  //   Basic Start: 0
108  //   Basic Reject: 1
109  //   Digest Start: 2
110  //   Digest Reject: 3
111  //   NTLM Start: 4
112  //   NTLM Reject: 5
113  //   Negotiate Start: 6
114  //   Negotiate Reject: 7
115  static const int kEventBucketsEnd =
116      HttpAuth::AUTH_SCHEME_MAX * AUTH_EVENT_MAX;
117  int event_bucket = auth_scheme * AUTH_EVENT_MAX + auth_event;
118  DCHECK(event_bucket >= 0 && event_bucket < kEventBucketsEnd);
119  UMA_HISTOGRAM_ENUMERATION("Net.HttpAuthCount", event_bucket,
120                            kEventBucketsEnd);
121
122  // Record the target of the authentication.
123  //
124  // The results map to:
125  //   Basic Proxy: 0
126  //   Basic Secure Proxy: 1
127  //   Basic Server: 2
128  //   Basic Secure Server: 3
129  //   Digest Proxy: 4
130  //   Digest Secure Proxy: 5
131  //   Digest Server: 6
132  //   Digest Secure Server: 7
133  //   NTLM Proxy: 8
134  //   NTLM Secure Proxy: 9
135  //   NTLM Server: 10
136  //   NTLM Secure Server: 11
137  //   Negotiate Proxy: 12
138  //   Negotiate Secure Proxy: 13
139  //   Negotiate Server: 14
140  //   Negotiate Secure Server: 15
141  if (auth_event != AUTH_EVENT_START)
142    return;
143  static const int kTargetBucketsEnd =
144      HttpAuth::AUTH_SCHEME_MAX * AUTH_TARGET_MAX;
145  AuthTarget auth_target = DetermineAuthTarget(handler);
146  int target_bucket = auth_scheme * AUTH_TARGET_MAX + auth_target;
147  DCHECK(target_bucket >= 0 && target_bucket < kTargetBucketsEnd);
148  UMA_HISTOGRAM_ENUMERATION("Net.HttpAuthTarget", target_bucket,
149                            kTargetBucketsEnd);
150}
151
152}  // namespace
153
154HttpAuthController::HttpAuthController(
155    HttpAuth::Target target,
156    const GURL& auth_url,
157    HttpAuthCache* http_auth_cache,
158    HttpAuthHandlerFactory* http_auth_handler_factory)
159    : target_(target),
160      auth_url_(auth_url),
161      auth_origin_(auth_url.GetOrigin()),
162      auth_path_(HttpAuth::AUTH_PROXY ? std::string() : auth_url.path()),
163      embedded_identity_used_(false),
164      default_credentials_used_(false),
165      http_auth_cache_(http_auth_cache),
166      http_auth_handler_factory_(http_auth_handler_factory) {
167}
168
169HttpAuthController::~HttpAuthController() {
170  DCHECK(CalledOnValidThread());
171}
172
173int HttpAuthController::MaybeGenerateAuthToken(
174    const HttpRequestInfo* request, const CompletionCallback& callback,
175    const BoundNetLog& net_log) {
176  DCHECK(CalledOnValidThread());
177  bool needs_auth = HaveAuth() || SelectPreemptiveAuth(net_log);
178  if (!needs_auth)
179    return OK;
180  const AuthCredentials* credentials = NULL;
181  if (identity_.source != HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS)
182    credentials = &identity_.credentials;
183  DCHECK(auth_token_.empty());
184  DCHECK(callback_.is_null());
185  int rv = handler_->GenerateAuthToken(
186      credentials, request,
187      base::Bind(&HttpAuthController::OnIOComplete, base::Unretained(this)),
188      &auth_token_);
189  if (DisableOnAuthHandlerResult(rv))
190    rv = OK;
191  if (rv == ERR_IO_PENDING)
192    callback_ = callback;
193  else
194    OnIOComplete(rv);
195  return rv;
196}
197
198bool HttpAuthController::SelectPreemptiveAuth(const BoundNetLog& net_log) {
199  DCHECK(CalledOnValidThread());
200  DCHECK(!HaveAuth());
201  DCHECK(identity_.invalid);
202
203  // Don't do preemptive authorization if the URL contains a username:password,
204  // since we must first be challenged in order to use the URL's identity.
205  if (auth_url_.has_username())
206    return false;
207
208  // SelectPreemptiveAuth() is on the critical path for each request, so it
209  // is expected to be fast. LookupByPath() is fast in the common case, since
210  // the number of http auth cache entries is expected to be very small.
211  // (For most users in fact, it will be 0.)
212  HttpAuthCache::Entry* entry = http_auth_cache_->LookupByPath(
213      auth_origin_, auth_path_);
214  if (!entry)
215    return false;
216
217  // Try to create a handler using the previous auth challenge.
218  scoped_ptr<HttpAuthHandler> handler_preemptive;
219  int rv_create = http_auth_handler_factory_->
220      CreatePreemptiveAuthHandlerFromString(entry->auth_challenge(), target_,
221                                            auth_origin_,
222                                            entry->IncrementNonceCount(),
223                                            net_log, &handler_preemptive);
224  if (rv_create != OK)
225    return false;
226
227  // Set the state
228  identity_.source = HttpAuth::IDENT_SRC_PATH_LOOKUP;
229  identity_.invalid = false;
230  identity_.credentials = entry->credentials();
231  handler_.swap(handler_preemptive);
232  return true;
233}
234
235void HttpAuthController::AddAuthorizationHeader(
236    HttpRequestHeaders* authorization_headers) {
237  DCHECK(CalledOnValidThread());
238  DCHECK(HaveAuth());
239  // auth_token_ can be empty if we encountered a permanent error with
240  // the auth scheme and want to retry.
241  if (!auth_token_.empty()) {
242    authorization_headers->SetHeader(
243        HttpAuth::GetAuthorizationHeaderName(target_), auth_token_);
244    auth_token_.clear();
245  }
246}
247
248int HttpAuthController::HandleAuthChallenge(
249    scoped_refptr<HttpResponseHeaders> headers,
250    bool do_not_send_server_auth,
251    bool establishing_tunnel,
252    const BoundNetLog& net_log) {
253  DCHECK(CalledOnValidThread());
254  DCHECK(headers.get());
255  DCHECK(auth_origin_.is_valid());
256  VLOG(1) << "The " << HttpAuth::GetAuthTargetString(target_) << " "
257          << auth_origin_ << " requested auth "
258          << AuthChallengeLogMessage(headers.get());
259
260  // Give the existing auth handler first try at the authentication headers.
261  // This will also evict the entry in the HttpAuthCache if the previous
262  // challenge appeared to be rejected, or is using a stale nonce in the Digest
263  // case.
264  if (HaveAuth()) {
265    std::string challenge_used;
266    HttpAuth::AuthorizationResult result =
267        HttpAuth::HandleChallengeResponse(handler_.get(),
268                                          headers.get(),
269                                          target_,
270                                          disabled_schemes_,
271                                          &challenge_used);
272    switch (result) {
273      case HttpAuth::AUTHORIZATION_RESULT_ACCEPT:
274        break;
275      case HttpAuth::AUTHORIZATION_RESULT_INVALID:
276        InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS);
277        break;
278      case HttpAuth::AUTHORIZATION_RESULT_REJECT:
279        HistogramAuthEvent(handler_.get(), AUTH_EVENT_REJECT);
280        InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS);
281        break;
282      case HttpAuth::AUTHORIZATION_RESULT_STALE:
283        if (http_auth_cache_->UpdateStaleChallenge(auth_origin_,
284                                                   handler_->realm(),
285                                                   handler_->auth_scheme(),
286                                                   challenge_used)) {
287          InvalidateCurrentHandler(INVALIDATE_HANDLER);
288        } else {
289          // It's possible that a server could incorrectly issue a stale
290          // response when the entry is not in the cache. Just evict the
291          // current value from the cache.
292          InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS);
293        }
294        break;
295      case HttpAuth::AUTHORIZATION_RESULT_DIFFERENT_REALM:
296        // If the server changes the authentication realm in a
297        // subsequent challenge, invalidate cached credentials for the
298        // previous realm.  If the server rejects a preemptive
299        // authorization and requests credentials for a different
300        // realm, we keep the cached credentials.
301        InvalidateCurrentHandler(
302            (identity_.source == HttpAuth::IDENT_SRC_PATH_LOOKUP) ?
303            INVALIDATE_HANDLER :
304            INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS);
305        break;
306      default:
307        NOTREACHED();
308        break;
309    }
310  }
311
312  identity_.invalid = true;
313
314  bool can_send_auth = (target_ != HttpAuth::AUTH_SERVER ||
315                        !do_not_send_server_auth);
316
317  do {
318    if (!handler_.get() && can_send_auth) {
319      // Find the best authentication challenge that we support.
320      HttpAuth::ChooseBestChallenge(http_auth_handler_factory_,
321                                    headers.get(),
322                                    target_,
323                                    auth_origin_,
324                                    disabled_schemes_,
325                                    net_log,
326                                    &handler_);
327      if (handler_.get())
328        HistogramAuthEvent(handler_.get(), AUTH_EVENT_START);
329    }
330
331    if (!handler_.get()) {
332      if (establishing_tunnel) {
333        LOG(ERROR) << "Can't perform auth to the "
334                   << HttpAuth::GetAuthTargetString(target_) << " "
335                   << auth_origin_ << " when establishing a tunnel"
336                   << AuthChallengeLogMessage(headers.get());
337
338        // We are establishing a tunnel, we can't show the error page because an
339        // active network attacker could control its contents.  Instead, we just
340        // fail to establish the tunnel.
341        DCHECK(target_ == HttpAuth::AUTH_PROXY);
342        return ERR_PROXY_AUTH_UNSUPPORTED;
343      }
344      // We found no supported challenge -- let the transaction continue so we
345      // end up displaying the error page.
346      return OK;
347    }
348
349    if (handler_->NeedsIdentity()) {
350      // Pick a new auth identity to try, by looking to the URL and auth cache.
351      // If an identity to try is found, it is saved to identity_.
352      SelectNextAuthIdentityToTry();
353    } else {
354      // Proceed with the existing identity or a null identity.
355      identity_.invalid = false;
356    }
357
358    // From this point on, we are restartable.
359
360    if (identity_.invalid) {
361      // We have exhausted all identity possibilities.
362      if (!handler_->AllowsExplicitCredentials()) {
363        // If the handler doesn't accept explicit credentials, then we need to
364        // choose a different auth scheme.
365        HistogramAuthEvent(handler_.get(), AUTH_EVENT_REJECT);
366        InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_DISABLE_SCHEME);
367      } else {
368        // Pass the challenge information back to the client.
369        PopulateAuthChallenge();
370      }
371    } else {
372      auth_info_ = NULL;
373    }
374
375    // If we get here and we don't have a handler_, that's because we
376    // invalidated it due to not having any viable identities to use with it. Go
377    // back and try again.
378    // TODO(asanka): Instead we should create a priority list of
379    //     <handler,identity> and iterate through that.
380  } while(!handler_.get());
381  return OK;
382}
383
384void HttpAuthController::ResetAuth(const AuthCredentials& credentials) {
385  DCHECK(CalledOnValidThread());
386  DCHECK(identity_.invalid || credentials.Empty());
387
388  if (identity_.invalid) {
389    // Update the credentials.
390    identity_.source = HttpAuth::IDENT_SRC_EXTERNAL;
391    identity_.invalid = false;
392    identity_.credentials = credentials;
393  }
394
395  DCHECK(identity_.source != HttpAuth::IDENT_SRC_PATH_LOOKUP);
396
397  // Add the auth entry to the cache before restarting. We don't know whether
398  // the identity is valid yet, but if it is valid we want other transactions
399  // to know about it. If an entry for (origin, handler->realm()) already
400  // exists, we update it.
401  //
402  // If identity_.source is HttpAuth::IDENT_SRC_NONE or
403  // HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS, identity_ contains no
404  // identity because identity is not required yet or we're using default
405  // credentials.
406  //
407  // TODO(wtc): For NTLM_SSPI, we add the same auth entry to the cache in
408  // round 1 and round 2, which is redundant but correct.  It would be nice
409  // to add an auth entry to the cache only once, preferrably in round 1.
410  // See http://crbug.com/21015.
411  switch (identity_.source) {
412    case HttpAuth::IDENT_SRC_NONE:
413    case HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS:
414      break;
415    default:
416      http_auth_cache_->Add(auth_origin_, handler_->realm(),
417                            handler_->auth_scheme(), handler_->challenge(),
418                            identity_.credentials, auth_path_);
419      break;
420  }
421}
422
423bool HttpAuthController::HaveAuthHandler() const {
424  return handler_.get() != NULL;
425}
426
427bool HttpAuthController::HaveAuth() const {
428  return handler_.get() && !identity_.invalid;
429}
430
431void HttpAuthController::InvalidateCurrentHandler(
432    InvalidateHandlerAction action) {
433  DCHECK(CalledOnValidThread());
434  DCHECK(handler_.get());
435
436  if (action == INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS)
437    InvalidateRejectedAuthFromCache();
438  if (action == INVALIDATE_HANDLER_AND_DISABLE_SCHEME)
439    DisableAuthScheme(handler_->auth_scheme());
440  handler_.reset();
441  identity_ = HttpAuth::Identity();
442}
443
444void HttpAuthController::InvalidateRejectedAuthFromCache() {
445  DCHECK(CalledOnValidThread());
446  DCHECK(HaveAuth());
447
448  // Clear the cache entry for the identity we just failed on.
449  // Note: we require the credentials to match before invalidating
450  // since the entry in the cache may be newer than what we used last time.
451  http_auth_cache_->Remove(auth_origin_, handler_->realm(),
452                           handler_->auth_scheme(), identity_.credentials);
453}
454
455bool HttpAuthController::SelectNextAuthIdentityToTry() {
456  DCHECK(CalledOnValidThread());
457  DCHECK(handler_.get());
458  DCHECK(identity_.invalid);
459
460  // Try to use the username:password encoded into the URL first.
461  if (target_ == HttpAuth::AUTH_SERVER && auth_url_.has_username() &&
462      !embedded_identity_used_) {
463    identity_.source = HttpAuth::IDENT_SRC_URL;
464    identity_.invalid = false;
465    // Extract the username:password from the URL.
466    base::string16 username;
467    base::string16 password;
468    GetIdentityFromURL(auth_url_, &username, &password);
469    identity_.credentials.Set(username, password);
470    embedded_identity_used_ = true;
471    // TODO(eroman): If the password is blank, should we also try combining
472    // with a password from the cache?
473    UMA_HISTOGRAM_BOOLEAN("net.HttpIdentSrcURL", true);
474    return true;
475  }
476
477  // Check the auth cache for a realm entry.
478  HttpAuthCache::Entry* entry =
479      http_auth_cache_->Lookup(auth_origin_, handler_->realm(),
480                               handler_->auth_scheme());
481
482  if (entry) {
483    identity_.source = HttpAuth::IDENT_SRC_REALM_LOOKUP;
484    identity_.invalid = false;
485    identity_.credentials = entry->credentials();
486    return true;
487  }
488
489  // Use default credentials (single sign on) if this is the first attempt
490  // at identity.  Do not allow multiple times as it will infinite loop.
491  // We use default credentials after checking the auth cache so that if
492  // single sign-on doesn't work, we won't try default credentials for future
493  // transactions.
494  if (!default_credentials_used_ && handler_->AllowsDefaultCredentials()) {
495    identity_.source = HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS;
496    identity_.invalid = false;
497    default_credentials_used_ = true;
498    return true;
499  }
500
501  return false;
502}
503
504void HttpAuthController::PopulateAuthChallenge() {
505  DCHECK(CalledOnValidThread());
506
507  // Populates response_.auth_challenge with the authentication challenge info.
508  // This info is consumed by URLRequestHttpJob::GetAuthChallengeInfo().
509
510  auth_info_ = new AuthChallengeInfo;
511  auth_info_->is_proxy = (target_ == HttpAuth::AUTH_PROXY);
512  auth_info_->challenger = HostPortPair::FromURL(auth_origin_);
513  auth_info_->scheme = HttpAuth::SchemeToString(handler_->auth_scheme());
514  auth_info_->realm = handler_->realm();
515}
516
517bool HttpAuthController::DisableOnAuthHandlerResult(int result) {
518  DCHECK(CalledOnValidThread());
519
520  switch (result) {
521    // Occurs with GSSAPI, if the user has not already logged in.
522    case ERR_MISSING_AUTH_CREDENTIALS:
523
524    // Can occur with GSSAPI or SSPI if the underlying library reports
525    // a permanent error.
526    case ERR_UNSUPPORTED_AUTH_SCHEME:
527
528    // These two error codes represent failures we aren't handling.
529    case ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS:
530    case ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS:
531
532    // Can be returned by SSPI if the authenticating authority or
533    // target is not known.
534    case ERR_MISCONFIGURED_AUTH_ENVIRONMENT:
535
536      // In these cases, disable the current scheme as it cannot
537      // succeed.
538      DisableAuthScheme(handler_->auth_scheme());
539      auth_token_.clear();
540      return true;
541
542    default:
543      return false;
544  }
545}
546
547void HttpAuthController::OnIOComplete(int result) {
548  DCHECK(CalledOnValidThread());
549  if (DisableOnAuthHandlerResult(result))
550    result = OK;
551  if (!callback_.is_null()) {
552    CompletionCallback c = callback_;
553    callback_.Reset();
554    c.Run(result);
555  }
556}
557
558scoped_refptr<AuthChallengeInfo> HttpAuthController::auth_info() {
559  DCHECK(CalledOnValidThread());
560  return auth_info_;
561}
562
563bool HttpAuthController::IsAuthSchemeDisabled(HttpAuth::Scheme scheme) const {
564  DCHECK(CalledOnValidThread());
565  return disabled_schemes_.find(scheme) != disabled_schemes_.end();
566}
567
568void HttpAuthController::DisableAuthScheme(HttpAuth::Scheme scheme) {
569  DCHECK(CalledOnValidThread());
570  disabled_schemes_.insert(scheme);
571}
572
573void HttpAuthController::DisableEmbeddedIdentity() {
574  DCHECK(CalledOnValidThread());
575  embedded_identity_used_ = true;
576}
577
578}  // namespace net
579