Home | History | Annotate | Download | only in http
      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 
     23 namespace net {
     24 
     25 namespace {
     26 
     27 // Returns a log message for all the response headers related to the auth
     28 // challenge.
     29 std::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 
     56 enum AuthEvent {
     57   AUTH_EVENT_START = 0,
     58   AUTH_EVENT_REJECT,
     59   AUTH_EVENT_MAX,
     60 };
     61 
     62 enum 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 
     70 AuthTarget 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.
     89 void 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 
    154 HttpAuthController::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 
    169 HttpAuthController::~HttpAuthController() {
    170   DCHECK(CalledOnValidThread());
    171 }
    172 
    173 int 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 
    198 bool 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 
    235 void 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 
    248 int 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 
    384 void 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 
    423 bool HttpAuthController::HaveAuthHandler() const {
    424   return handler_.get() != NULL;
    425 }
    426 
    427 bool HttpAuthController::HaveAuth() const {
    428   return handler_.get() && !identity_.invalid;
    429 }
    430 
    431 void 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 
    444 void 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 
    455 bool 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 
    504 void 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 
    517 bool 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 
    547 void 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 
    558 scoped_refptr<AuthChallengeInfo> HttpAuthController::auth_info() {
    559   DCHECK(CalledOnValidThread());
    560   return auth_info_;
    561 }
    562 
    563 bool HttpAuthController::IsAuthSchemeDisabled(HttpAuth::Scheme scheme) const {
    564   DCHECK(CalledOnValidThread());
    565   return disabled_schemes_.find(scheme) != disabled_schemes_.end();
    566 }
    567 
    568 void HttpAuthController::DisableAuthScheme(HttpAuth::Scheme scheme) {
    569   DCHECK(CalledOnValidThread());
    570   disabled_schemes_.insert(scheme);
    571 }
    572 
    573 }  // namespace net
    574