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