Home | History | Annotate | Download | only in http
      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 
     21 namespace net {
     22 
     23 namespace {
     24 
     25 // Returns a log message for all the response headers related to the auth
     26 // challenge.
     27 std::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 
     54 enum AuthEvent {
     55   AUTH_EVENT_START = 0,
     56   AUTH_EVENT_REJECT,
     57   AUTH_EVENT_MAX,
     58 };
     59 
     60 enum 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 
     68 AuthTarget 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.
     87 void 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 
    152 HttpAuthController::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 
    170 HttpAuthController::~HttpAuthController() {
    171   DCHECK(CalledOnValidThread());
    172   user_callback_ = NULL;
    173 }
    174 
    175 int 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 
    204 bool 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 
    242 void 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 
    255 int 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 
    369 void 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 
    411 bool HttpAuthController::HaveAuthHandler() const {
    412   return handler_.get() != NULL;
    413 }
    414 
    415 bool HttpAuthController::HaveAuth() const {
    416   return handler_.get() && !identity_.invalid;
    417 }
    418 
    419 void 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 
    429 void 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 
    441 bool 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 
    489 void 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 
    504 bool 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 
    534 void 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 
    545 scoped_refptr<AuthChallengeInfo> HttpAuthController::auth_info() {
    546   DCHECK(CalledOnValidThread());
    547   return auth_info_;
    548 }
    549 
    550 bool HttpAuthController::IsAuthSchemeDisabled(HttpAuth::Scheme scheme) const {
    551   DCHECK(CalledOnValidThread());
    552   return disabled_schemes_.find(scheme) != disabled_schemes_.end();
    553 }
    554 
    555 void HttpAuthController::DisableAuthScheme(HttpAuth::Scheme scheme) {
    556   DCHECK(CalledOnValidThread());
    557   disabled_schemes_.insert(scheme);
    558 }
    559 
    560 }  // namespace net
    561