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.
5 #include "net/http/http_auth_controller.h"
8 #include "base/bind_helpers.h"
9 #include "base/metrics/histogram_macros.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"
27 // Returns a log message for all the response headers related to the auth
29 std::string
AuthChallengeLogMessage(HttpResponseHeaders
* headers
) {
31 std::string header_val
;
33 while (headers
->EnumerateHeader(&iter
, "proxy-authenticate", &header_val
)) {
34 msg
.append("\n Has header Proxy-Authenticate: ");
35 msg
.append(header_val
);
39 while (headers
->EnumerateHeader(&iter
, "www-authenticate", &header_val
)) {
40 msg
.append("\n Has header WWW-Authenticate: ");
41 msg
.append(header_val
);
44 // RFC 4559 requires that a proxy indicate its support of NTLM/Negotiate
45 // authentication with a "Proxy-Support: Session-Based-Authentication"
48 while (headers
->EnumerateHeader(&iter
, "proxy-support", &header_val
)) {
49 msg
.append("\n Has header Proxy-Support: ");
50 msg
.append(header_val
);
63 AUTH_TARGET_PROXY
= 0,
64 AUTH_TARGET_SECURE_PROXY
,
66 AUTH_TARGET_SECURE_SERVER
,
70 AuthTarget
DetermineAuthTarget(const HttpAuthHandler
* handler
) {
71 switch (handler
->target()) {
72 case HttpAuth::AUTH_PROXY
:
73 if (handler
->origin().SchemeIsCryptographic())
74 return AUTH_TARGET_SECURE_PROXY
;
76 return AUTH_TARGET_PROXY
;
77 case HttpAuth::AUTH_SERVER
:
78 if (handler
->origin().SchemeIsCryptographic())
79 return AUTH_TARGET_SECURE_SERVER
;
81 return AUTH_TARGET_SERVER
;
84 return AUTH_TARGET_MAX
;
88 // Records the number of authentication events per authentication scheme.
89 void HistogramAuthEvent(HttpAuthHandler
* handler
, AuthEvent auth_event
) {
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());
101 HttpAuth::Scheme auth_scheme
= handler
->auth_scheme();
102 DCHECK(auth_scheme
>= 0 && auth_scheme
< HttpAuth::AUTH_SCHEME_MAX
);
104 // Record start and rejection events for authentication.
106 // The results map to:
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
,
122 // Record the target of the authentication.
124 // The results map to:
126 // Basic Secure Proxy: 1
128 // Basic Secure Server: 3
130 // Digest Secure Proxy: 5
132 // Digest Secure Server: 7
134 // NTLM Secure Proxy: 9
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
)
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
,
154 HttpAuthController::HttpAuthController(
155 HttpAuth::Target target
,
156 const GURL
& auth_url
,
157 HttpAuthCache
* http_auth_cache
,
158 HttpAuthHandlerFactory
* http_auth_handler_factory
)
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
) {
169 HttpAuthController::~HttpAuthController() {
170 DCHECK(CalledOnValidThread());
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
);
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)),
189 if (DisableOnAuthHandlerResult(rv
))
191 if (rv
== ERR_IO_PENDING
)
192 callback_
= callback
;
198 bool HttpAuthController::SelectPreemptiveAuth(const BoundNetLog
& net_log
) {
199 DCHECK(CalledOnValidThread());
201 DCHECK(identity_
.invalid
);
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())
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_
);
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_
,
222 entry
->IncrementNonceCount(),
223 net_log
, &handler_preemptive
);
228 identity_
.source
= HttpAuth::IDENT_SRC_PATH_LOOKUP
;
229 identity_
.invalid
= false;
230 identity_
.credentials
= entry
->credentials();
231 handler_
.swap(handler_preemptive
);
235 void HttpAuthController::AddAuthorizationHeader(
236 HttpRequestHeaders
* authorization_headers
) {
237 DCHECK(CalledOnValidThread());
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_
);
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());
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
265 std::string challenge_used
;
266 HttpAuth::AuthorizationResult result
=
267 HttpAuth::HandleChallengeResponse(handler_
.get(),
273 case HttpAuth::AUTHORIZATION_RESULT_ACCEPT
:
275 case HttpAuth::AUTHORIZATION_RESULT_INVALID
:
276 InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS
);
278 case HttpAuth::AUTHORIZATION_RESULT_REJECT
:
279 HistogramAuthEvent(handler_
.get(), AUTH_EVENT_REJECT
);
280 InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS
);
282 case HttpAuth::AUTHORIZATION_RESULT_STALE
:
283 if (http_auth_cache_
->UpdateStaleChallenge(auth_origin_
,
285 handler_
->auth_scheme(),
287 InvalidateCurrentHandler(INVALIDATE_HANDLER
);
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
);
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
) ?
304 INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS
);
312 identity_
.invalid
= true;
314 bool can_send_auth
= (target_
!= HttpAuth::AUTH_SERVER
||
315 !do_not_send_server_auth
);
318 if (!handler_
.get() && can_send_auth
) {
319 // Find the best authentication challenge that we support.
320 HttpAuth::ChooseBestChallenge(http_auth_handler_factory_
,
328 HistogramAuthEvent(handler_
.get(), AUTH_EVENT_START
);
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());
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
;
344 // We found no supported challenge -- let the transaction continue so we
345 // end up displaying the error page.
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();
354 // Proceed with the existing identity or a null identity.
355 identity_
.invalid
= false;
358 // From this point on, we are restartable.
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
);
368 // Pass the challenge information back to the client.
369 PopulateAuthChallenge();
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());
384 void HttpAuthController::ResetAuth(const AuthCredentials
& credentials
) {
385 DCHECK(CalledOnValidThread());
386 DCHECK(identity_
.invalid
|| credentials
.Empty());
388 if (identity_
.invalid
) {
389 // Update the credentials.
390 identity_
.source
= HttpAuth::IDENT_SRC_EXTERNAL
;
391 identity_
.invalid
= false;
392 identity_
.credentials
= credentials
;
395 DCHECK(identity_
.source
!= HttpAuth::IDENT_SRC_PATH_LOOKUP
);
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.
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
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
:
416 http_auth_cache_
->Add(auth_origin_
, handler_
->realm(),
417 handler_
->auth_scheme(), handler_
->challenge(),
418 identity_
.credentials
, auth_path_
);
423 bool HttpAuthController::HaveAuthHandler() const {
424 return handler_
.get() != NULL
;
427 bool HttpAuthController::HaveAuth() const {
428 return handler_
.get() && !identity_
.invalid
;
431 void HttpAuthController::InvalidateCurrentHandler(
432 InvalidateHandlerAction action
) {
433 DCHECK(CalledOnValidThread());
434 DCHECK(handler_
.get());
436 if (action
== INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS
)
437 InvalidateRejectedAuthFromCache();
438 if (action
== INVALIDATE_HANDLER_AND_DISABLE_SCHEME
)
439 DisableAuthScheme(handler_
->auth_scheme());
441 identity_
= HttpAuth::Identity();
444 void HttpAuthController::InvalidateRejectedAuthFromCache() {
445 DCHECK(CalledOnValidThread());
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
);
455 bool HttpAuthController::SelectNextAuthIdentityToTry() {
456 DCHECK(CalledOnValidThread());
457 DCHECK(handler_
.get());
458 DCHECK(identity_
.invalid
);
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);
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());
483 identity_
.source
= HttpAuth::IDENT_SRC_REALM_LOOKUP
;
484 identity_
.invalid
= false;
485 identity_
.credentials
= entry
->credentials();
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
494 if (!default_credentials_used_
&& handler_
->AllowsDefaultCredentials()) {
495 identity_
.source
= HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS
;
496 identity_
.invalid
= false;
497 default_credentials_used_
= true;
504 void HttpAuthController::PopulateAuthChallenge() {
505 DCHECK(CalledOnValidThread());
507 // Populates response_.auth_challenge with the authentication challenge info.
508 // This info is consumed by URLRequestHttpJob::GetAuthChallengeInfo().
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();
517 bool HttpAuthController::DisableOnAuthHandlerResult(int result
) {
518 DCHECK(CalledOnValidThread());
521 // Occurs with GSSAPI, if the user has not already logged in.
522 case ERR_MISSING_AUTH_CREDENTIALS
:
524 // Can occur with GSSAPI or SSPI if the underlying library reports
525 // a permanent error.
526 case ERR_UNSUPPORTED_AUTH_SCHEME
:
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
:
532 // Can be returned by SSPI if the authenticating authority or
533 // target is not known.
534 case ERR_MISCONFIGURED_AUTH_ENVIRONMENT
:
536 // In these cases, disable the current scheme as it cannot
538 DisableAuthScheme(handler_
->auth_scheme());
547 void HttpAuthController::OnIOComplete(int result
) {
548 DCHECK(CalledOnValidThread());
549 if (DisableOnAuthHandlerResult(result
))
551 if (!callback_
.is_null()) {
552 CompletionCallback c
= callback_
;
558 scoped_refptr
<AuthChallengeInfo
> HttpAuthController::auth_info() {
559 DCHECK(CalledOnValidThread());
563 bool HttpAuthController::IsAuthSchemeDisabled(HttpAuth::Scheme scheme
) const {
564 DCHECK(CalledOnValidThread());
565 return disabled_schemes_
.find(scheme
) != disabled_schemes_
.end();
568 void HttpAuthController::DisableAuthScheme(HttpAuth::Scheme scheme
) {
569 DCHECK(CalledOnValidThread());
570 disabled_schemes_
.insert(scheme
);
573 void HttpAuthController::DisableEmbeddedIdentity() {
574 DCHECK(CalledOnValidThread());
575 embedded_identity_used_
= true;