summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorcbentzel@chromium.org <cbentzel@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-09-11 14:03:30 +0000
committercbentzel@chromium.org <cbentzel@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-09-11 14:03:30 +0000
commiteca50e128ff1bc41bc0cc1d3fdf2e015ba459d4c (patch)
treeae0368388f38766781c5ddff86c9e0e2c0c9c362
parent4630db4630bc415cc3b7be70bce87160559810df (diff)
downloadchromium_src-eca50e128ff1bc41bc0cc1d3fdf2e015ba459d4c.zip
chromium_src-eca50e128ff1bc41bc0cc1d3fdf2e015ba459d4c.tar.gz
chromium_src-eca50e128ff1bc41bc0cc1d3fdf2e015ba459d4c.tar.bz2
Fix multi-round authentication.
In the case of Negotiate, authentication can look like C: GET S: 401, WWW-Authenticate: Negotiate C: GET, WWW-Authorization: Negotiate <client_token_1> S: 401, WWW-Authenticate: Negotiate <server_token_1> C: GET, WWW-Authorization: Negotiate <client_token_2> S: 401, WWW-Authenticate: Negotiate <server_token_2> on that third challenge, the handler was reported as being in "the final round" and this was treated as a rejection of the authentication attempt. After that, the new challenge token was used by a new auth handler that hadn't established a security context, and an ERR_INVALID_HANDLE would be returned. This CL also does some prep work to correctly handle the "stale=true" value for Digest authentication, but I decided to defer the HttpAuthCache changes needed for that to a separate CL since this was large enough. BUG=53282 TEST=net_unittests. Unfortunately, I haven't been able to set up a proxy/server to do more than two auth challenges, but this does happen in the wild. Review URL: http://codereview.chromium.org/3360017 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@59188 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--net/http/http_auth.cc42
-rw-r--r--net/http/http_auth.h40
-rw-r--r--net/http/http_auth_cache_unittest.cc6
-rw-r--r--net/http/http_auth_controller.cc53
-rw-r--r--net/http/http_auth_controller.h3
-rw-r--r--net/http/http_auth_gssapi_posix.cc86
-rw-r--r--net/http/http_auth_gssapi_posix.h4
-rw-r--r--net/http/http_auth_gssapi_posix_unittest.cc123
-rw-r--r--net/http/http_auth_handler.h21
-rw-r--r--net/http/http_auth_handler_basic.cc20
-rw-r--r--net/http/http_auth_handler_basic.h5
-rw-r--r--net/http/http_auth_handler_basic_unittest.cc46
-rw-r--r--net/http/http_auth_handler_digest.cc39
-rw-r--r--net/http/http_auth_handler_digest.h7
-rw-r--r--net/http/http_auth_handler_digest_unittest.cc30
-rw-r--r--net/http/http_auth_handler_mock.cc11
-rw-r--r--net/http/http_auth_handler_mock.h4
-rw-r--r--net/http/http_auth_handler_negotiate.cc11
-rw-r--r--net/http/http_auth_handler_negotiate.h5
-rw-r--r--net/http/http_auth_handler_ntlm.cc37
-rw-r--r--net/http/http_auth_handler_ntlm.h13
-rw-r--r--net/http/http_auth_handler_ntlm_portable.cc4
-rw-r--r--net/http/http_auth_handler_ntlm_win.cc4
-rw-r--r--net/http/http_auth_sspi_win.cc32
-rw-r--r--net/http/http_auth_sspi_win.h7
-rw-r--r--net/http/http_auth_sspi_win_unittest.cc102
-rw-r--r--net/http/http_auth_unittest.cc188
-rw-r--r--net/http/http_network_transaction_unittest.cc14
-rw-r--r--net/http/mock_sspi_library_win.cc10
-rw-r--r--net/socket_stream/socket_stream.cc9
30 files changed, 692 insertions, 284 deletions
diff --git a/net/http/http_auth.cc b/net/http/http_auth.cc
index bb8c805..aaa4de0 100644
--- a/net/http/http_auth.cc
+++ b/net/http/http_auth.cc
@@ -28,21 +28,7 @@ void HttpAuth::ChooseBestChallenge(
const BoundNetLog& net_log,
scoped_ptr<HttpAuthHandler>* handler) {
DCHECK(http_auth_handler_factory);
-
- // A connection-based authentication scheme must continue to use the
- // existing handler object in |*handler|.
- if (handler->get() && (*handler)->is_connection_based() &&
- (disabled_schemes.find((*handler)->scheme()) == disabled_schemes.end())) {
- const std::string header_name = GetChallengeHeaderName(target);
- std::string challenge;
- void* iter = NULL;
- while (headers->EnumerateHeader(&iter, header_name, &challenge)) {
- ChallengeTokenizer props(challenge.begin(), challenge.end());
- if (LowerCaseEqualsASCII(props.scheme(), (*handler)->scheme().c_str()) &&
- (*handler)->InitFromChallenge(&props, target, origin, net_log))
- return;
- }
- }
+ DCHECK(handler->get() == NULL);
// Choose the challenge whose authentication handler gives the maximum score.
scoped_ptr<HttpAuthHandler> best;
@@ -65,6 +51,32 @@ void HttpAuth::ChooseBestChallenge(
handler->swap(best);
}
+// static
+HttpAuth::AuthorizationResult HttpAuth::HandleChallengeResponse(
+ HttpAuthHandler* handler,
+ const HttpResponseHeaders* headers,
+ Target target,
+ const std::set<std::string>& disabled_schemes) {
+ const std::string& current_scheme = handler->scheme();
+ if (disabled_schemes.find(current_scheme) != disabled_schemes.end())
+ return HttpAuth::AUTHORIZATION_RESULT_REJECT;
+ const std::string header_name = GetChallengeHeaderName(target);
+ void* iter = NULL;
+ std::string challenge;
+ HttpAuth::AuthorizationResult authorization_result =
+ HttpAuth::AUTHORIZATION_RESULT_INVALID;
+ while (headers->EnumerateHeader(&iter, header_name, &challenge)) {
+ HttpAuth::ChallengeTokenizer props(challenge.begin(), challenge.end());
+ if (!LowerCaseEqualsASCII(props.scheme(), current_scheme.c_str()))
+ continue;
+ authorization_result = handler->HandleAnotherChallenge(&props);
+ if (authorization_result != HttpAuth::AUTHORIZATION_RESULT_INVALID)
+ return authorization_result;
+ }
+ // Finding no matches is equivalent to rejection
+ return HttpAuth::AUTHORIZATION_RESULT_REJECT;
+}
+
void HttpAuth::ChallengeTokenizer::Init(std::string::const_iterator begin,
std::string::const_iterator end) {
// The first space-separated token is the auth-scheme.
diff --git a/net/http/http_auth.h b/net/http/http_auth.h
index 347bdec..d7d0347 100644
--- a/net/http/http_auth.h
+++ b/net/http/http_auth.h
@@ -25,7 +25,6 @@ class HttpResponseHeaders;
// Utility class for http authentication.
class HttpAuth {
public:
-
// Http authentication can be done the the proxy server, origin server,
// or both. This enum tracks who the target is.
enum Target {
@@ -37,6 +36,24 @@ class HttpAuth {
AUTH_NUM_TARGETS = 2,
};
+ // What the HTTP WWW-Authenticate/Proxy-Authenticate headers indicate about
+ // the previous authorization attempt.
+ enum AuthorizationResult {
+ AUTHORIZATION_RESULT_ACCEPT, // The authorization attempt was accepted,
+ // although there still may be additional
+ // rounds of challenges.
+
+ AUTHORIZATION_RESULT_REJECT, // The authorization attempt was rejected.
+
+ AUTHORIZATION_RESULT_STALE, // (Digest) The nonce used in the
+ // authorization attempt is stale, but
+ // otherwise the attempt was valid.
+
+ AUTHORIZATION_RESULT_INVALID, // The authentication challenge headers are
+ // poorly formed (the authorization attempt
+ // itself may have been fine).
+ };
+
// Describes where the identity used for authentication came from.
enum IdentitySource {
// Came from nowhere -- the identity is not initialized.
@@ -88,19 +105,13 @@ class HttpAuth {
// Iterate through the challenge headers, and pick the best one that
// we support. Obtains the implementation class for handling the challenge,
- // and passes it back in |*handler|. If the existing handler in |*handler|
- // should continue to be used (such as for the NTLM authentication scheme),
- // |*handler| is unchanged. If no supported challenge was found, |*handler|
- // is set to NULL.
+ // and passes it back in |*handler|. If no supported challenge was found,
+ // |*handler| is set to NULL.
//
// |disabled_schemes| is the set of schemes that we should not use.
//
- // |origin| is used by the NTLM authentication scheme to construct the
- // service principal name. It is ignored by other schemes.
- //
- // TODO(wtc): Continuing to use the existing handler in |*handler| (for
- // NTLM) is new behavior. Rename ChooseBestChallenge to fully encompass
- // what it does now.
+ // |origin| is used by the NTLM and Negotiation authentication scheme to
+ // construct the service principal name. It is ignored by other schemes.
static void ChooseBestChallenge(
HttpAuthHandlerFactory* http_auth_handler_factory,
const HttpResponseHeaders* headers,
@@ -110,6 +121,13 @@ class HttpAuth {
const BoundNetLog& net_log,
scoped_ptr<HttpAuthHandler>* handler);
+ // Handle a response to a previous authentication attempt.
+ static AuthorizationResult HandleChallengeResponse(
+ HttpAuthHandler* handler,
+ const HttpResponseHeaders* headers,
+ Target target,
+ const std::set<std::string>& disabled_schemes);
+
// ChallengeTokenizer breaks up a challenge string into the the auth scheme
// and parameter list, according to RFC 2617 Sec 1.2:
// challenge = auth-scheme 1*SP 1#auth-param
diff --git a/net/http/http_auth_cache_unittest.cc b/net/http/http_auth_cache_unittest.cc
index 5db49ee..7b68ae3 100644
--- a/net/http/http_auth_cache_unittest.cc
+++ b/net/http/http_auth_cache_unittest.cc
@@ -10,7 +10,6 @@
#include "net/base/net_errors.h"
#include "net/http/http_auth_cache.h"
#include "net/http/http_auth_handler.h"
-
#include "testing/gtest/include/gtest/gtest.h"
namespace net {
@@ -29,6 +28,11 @@ class MockAuthHandler : public HttpAuthHandler {
properties_ = 0;
}
+ HttpAuth::AuthorizationResult HandleAnotherChallenge(
+ HttpAuth::ChallengeTokenizer* challenge) {
+ return HttpAuth::AUTHORIZATION_RESULT_REJECT;
+ }
+
protected:
virtual bool Init(HttpAuth::ChallengeTokenizer* challenge) {
return false; // Unused.
diff --git a/net/http/http_auth_controller.cc b/net/http/http_auth_controller.cc
index e753b59..5cdb1c6 100644
--- a/net/http/http_auth_controller.cc
+++ b/net/http/http_auth_controller.cc
@@ -4,6 +4,7 @@
#include "net/http/http_auth_controller.h"
+#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "net/base/auth.h"
#include "net/base/host_resolver.h"
@@ -151,27 +152,40 @@ int HttpAuthController::HandleAuthChallenge(
const BoundNetLog& net_log) {
DCHECK(headers);
DCHECK(auth_origin_.is_valid());
-
LOG(INFO) << "The " << HttpAuth::GetAuthTargetString(target_) << " "
<< auth_origin_ << " requested auth"
<< AuthChallengeLogMessage(headers.get());
- // The auth we tried just failed, hence it can't be valid. Remove it from
- // the cache so it won't be used again.
- // TODO(wtc): IsFinalRound is not the right condition. In a multi-round
- // auth sequence, the server may fail the auth in round 1 if our first
- // authorization header is broken. We should inspect response_.headers to
- // determine if the server already failed the auth or wants us to continue.
- // See http://crbug.com/21015.
- if (HaveAuth() && handler_->IsFinalRound()) {
- InvalidateRejectedAuthFromCache();
- handler_.reset();
- identity_ = HttpAuth::Identity();
+ // Give the existing auth handler first try at the authentication headers.
+ // This will also evict the entry in the HttpAuthCache if the previous
+ // challenge appeared to be rejected, or is using a stale nonce in the Digest
+ // case.
+ if (HaveAuth()) {
+ HttpAuth::AuthorizationResult result = HttpAuth::HandleChallengeResponse(
+ handler_.get(), headers, target_, disabled_schemes_);
+ switch (result) {
+ case HttpAuth::AUTHORIZATION_RESULT_ACCEPT:
+ break;
+ case HttpAuth::AUTHORIZATION_RESULT_INVALID:
+ case HttpAuth::AUTHORIZATION_RESULT_REJECT:
+ InvalidateCurrentHandler();
+ break;
+ case HttpAuth::AUTHORIZATION_RESULT_STALE:
+ // TODO(cbentzel): Support "stale" invalidation in HttpAuthCache.
+ // Right now this does the same invalidation as before.
+ InvalidateCurrentHandler();
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
}
identity_.invalid = true;
- if (target_ != HttpAuth::AUTH_SERVER || !do_not_send_server_auth) {
+ bool can_send_auth = (target_ != HttpAuth::AUTH_SERVER ||
+ !do_not_send_server_auth);
+ if (!handler_.get() && can_send_auth) {
// Find the best authentication challenge that we support.
HttpAuth::ChooseBestChallenge(session_->http_auth_handler_factory(),
headers, target_, auth_origin_,
@@ -203,9 +217,6 @@ int HttpAuthController::HandleAuthChallenge(
SelectNextAuthIdentityToTry();
} else {
// Proceed with the existing identity or a null identity.
- //
- // TODO(wtc): Add a safeguard against infinite transaction restarts, if
- // the server keeps returning "NTLM".
identity_.invalid = false;
}
@@ -263,6 +274,12 @@ void HttpAuthController::ResetAuth(const string16& username,
}
}
+void HttpAuthController::InvalidateCurrentHandler() {
+ InvalidateRejectedAuthFromCache();
+ handler_.reset();
+ identity_ = HttpAuth::Identity();
+}
+
void HttpAuthController::InvalidateRejectedAuthFromCache() {
DCHECK(HaveAuth());
@@ -302,8 +319,8 @@ bool HttpAuthController::SelectNextAuthIdentityToTry() {
// Check the auth cache for a realm entry.
HttpAuthCache::Entry* entry =
- session_->auth_cache()->Lookup(auth_origin_, handler_->realm(),
- handler_->scheme());
+ session_->auth_cache()->Lookup(auth_origin_, handler_->realm(),
+ handler_->scheme());
if (entry) {
identity_.source = HttpAuth::IDENT_SRC_REALM_LOOKUP;
diff --git a/net/http/http_auth_controller.h b/net/http/http_auth_controller.h
index 721bce9..5ee156c 100644
--- a/net/http/http_auth_controller.h
+++ b/net/http/http_auth_controller.h
@@ -81,6 +81,9 @@ class HttpAuthController : public base::RefCounted<HttpAuthController> {
// cache entry's data and returns true.
bool SelectPreemptiveAuth(const BoundNetLog& net_log);
+ // Invalidates the current handler, including cache.
+ void InvalidateCurrentHandler();
+
// Invalidates any auth cache entries after authentication has failed.
// The identity that was rejected is |identity_|.
void InvalidateRejectedAuthFromCache();
diff --git a/net/http/http_auth_gssapi_posix.cc b/net/http/http_auth_gssapi_posix.cc
index 2c605a0..db8f712 100644
--- a/net/http/http_auth_gssapi_posix.cc
+++ b/net/http/http_auth_gssapi_posix.cc
@@ -114,7 +114,7 @@ std::string DisplayCode(GSSAPILibrary* gssapi_lib,
// individual message may exceed |kMaxMsgLength|, and the final result
// will not exceed |kMaxMsgLength|*2-1.
for (int i = 0; i < kMaxDisplayIterations && rv.size() < kMaxMsgLength;
- ++i) {
+ ++i) {
OM_uint32 min_stat;
gss_buffer_desc_struct msg = GSS_C_EMPTY_BUFFER;
OM_uint32 maj_stat =
@@ -482,26 +482,26 @@ base::NativeLibrary GSSAPISharedLibrary::LoadSharedLibrary() {
return NULL;
}
-#define BIND(lib, x) \
- gss_##x##_type x = reinterpret_cast<gss_##x##_type>( \
- base::GetFunctionPointerFromNativeLibrary(lib, "gss_" #x)); \
- if (x == NULL) { \
- LOG(WARNING) << "Unable to bind function \"" << "gss_" #x << "\""; \
- return false; \
+#define BIND(lib, x) \
+ gss_##x##_type x = reinterpret_cast<gss_##x##_type>( \
+ base::GetFunctionPointerFromNativeLibrary(lib, "gss_" #x)); \
+ if (x == NULL) { \
+ LOG(WARNING) << "Unable to bind function \"" << "gss_" #x << "\""; \
+ return false; \
}
bool GSSAPISharedLibrary::BindMethods(base::NativeLibrary lib) {
DCHECK(lib != NULL);
- BIND(lib, import_name)
- BIND(lib, release_name)
- BIND(lib, release_buffer)
- BIND(lib, display_name)
- BIND(lib, display_status)
- BIND(lib, init_sec_context)
- BIND(lib, wrap_size_limit)
- BIND(lib, delete_sec_context)
- BIND(lib, inquire_context)
+ BIND(lib, import_name);
+ BIND(lib, release_name);
+ BIND(lib, release_buffer);
+ BIND(lib, display_name);
+ BIND(lib, display_status);
+ BIND(lib, init_sec_context);
+ BIND(lib, wrap_size_limit);
+ BIND(lib, delete_sec_context);
+ BIND(lib, inquire_context);
import_name_ = import_name;
release_name_ = release_name;
@@ -638,14 +638,14 @@ OM_uint32 GSSAPISharedLibrary::inquire_context(
int* open) {
DCHECK(initialized_);
return inquire_context_(minor_status,
- context_handle,
- src_name,
- targ_name,
- lifetime_rec,
- mech_type,
- ctx_flags,
- locally_initiated,
- open);
+ context_handle,
+ src_name,
+ targ_name,
+ lifetime_rec,
+ mech_type,
+ ctx_flags,
+ locally_initiated,
+ open);
}
GSSAPILibrary* GSSAPILibrary::GetDefault() {
return Singleton<GSSAPISharedLibrary>::get();
@@ -695,35 +695,40 @@ bool HttpAuthGSSAPI::NeedsIdentity() const {
return decoded_server_auth_token_.empty();
}
-bool HttpAuthGSSAPI::IsFinalRound() const {
- return !NeedsIdentity();
-}
-
void HttpAuthGSSAPI::Delegate() {
can_delegate_ = true;
}
-bool HttpAuthGSSAPI::ParseChallenge(HttpAuth::ChallengeTokenizer* tok) {
+HttpAuth::AuthorizationResult HttpAuthGSSAPI::ParseChallenge(
+ HttpAuth::ChallengeTokenizer* tok) {
// Verify the challenge's auth-scheme.
if (!tok->valid() ||
!LowerCaseEqualsASCII(tok->scheme(), StringToLowerASCII(scheme_).c_str()))
- return false;
+ return HttpAuth::AUTHORIZATION_RESULT_INVALID;
tok->set_expect_base64_token(true);
if (!tok->GetNext()) {
- decoded_server_auth_token_.clear();
- return true;
+ // If a context has already been established, an empty Negotiate challenge
+ // should be treated as a rejection of the current attempt.
+ if (scoped_sec_context_.get() != GSS_C_NO_CONTEXT)
+ return HttpAuth::AUTHORIZATION_RESULT_REJECT;
+ DCHECK(decoded_server_auth_token_.empty());
+ return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
+ } else {
+ // If a context has not already been established, additional tokens should
+ // not be present in the auth challenge.
+ if (scoped_sec_context_.get() == GSS_C_NO_CONTEXT)
+ return HttpAuth::AUTHORIZATION_RESULT_INVALID;
}
+ // Make sure the additional token is base64 encoded.
std::string encoded_auth_token = tok->value();
std::string decoded_auth_token;
bool base64_rv = base::Base64Decode(encoded_auth_token, &decoded_auth_token);
- if (!base64_rv) {
- LOG(ERROR) << "Base64 decoding of auth token failed.";
- return false;
- }
+ if (!base64_rv)
+ return HttpAuth::AUTHORIZATION_RESULT_INVALID;
decoded_server_auth_token_ = decoded_auth_token;
- return true;
+ return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
}
int HttpAuthGSSAPI::GenerateAuthToken(const string16* username,
@@ -735,10 +740,9 @@ int HttpAuthGSSAPI::GenerateAuthToken(const string16* username,
gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
input_token.length = decoded_server_auth_token_.length();
- input_token.value =
- (input_token.length > 0) ?
- const_cast<char*>(decoded_server_auth_token_.data()) :
- NULL;
+ input_token.value = (input_token.length > 0) ?
+ const_cast<char*>(decoded_server_auth_token_.data()) :
+ NULL;
gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
ScopedBuffer scoped_output_token(&output_token, library_);
int rv = GetNextSecurityToken(spn, &input_token, &output_token);
diff --git a/net/http/http_auth_gssapi_posix.h b/net/http/http_auth_gssapi_posix.h
index 7f6610e..3ea1131 100644
--- a/net/http/http_auth_gssapi_posix.h
+++ b/net/http/http_auth_gssapi_posix.h
@@ -223,9 +223,9 @@ class HttpAuthGSSAPI {
bool Init();
bool NeedsIdentity() const;
- bool IsFinalRound() const;
- bool ParseChallenge(HttpAuth::ChallengeTokenizer* tok);
+ HttpAuth::AuthorizationResult ParseChallenge(
+ HttpAuth::ChallengeTokenizer* tok);
// Generates an authentication token.
// The return value is an error code. If it's not |OK|, the value of
diff --git a/net/http/http_auth_gssapi_posix_unittest.cc b/net/http/http_auth_gssapi_posix_unittest.cc
index e66bf85..62bae71 100644
--- a/net/http/http_auth_gssapi_posix_unittest.cc
+++ b/net/http/http_auth_gssapi_posix_unittest.cc
@@ -8,6 +8,7 @@
#include "base/logging.h"
#include "base/native_library.h"
#include "base/scoped_ptr.h"
+#include "net/base/net_errors.h"
#include "net/http/mock_gssapi_library_posix.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -46,6 +47,29 @@ void CopyBuffer(gss_buffer_t dest, const gss_buffer_t src) {
SetBuffer(dest, src->value, src->length);
}
+const char kInitialAuthResponse[] = "Mary had a little lamb";
+
+void EstablishInitialContext(test::MockGSSAPILibrary* library) {
+ test::GssContextMockImpl context_info(
+ "localhost", // Source name
+ "example.com", // Target name
+ 23, // Lifetime
+ *GSS_C_NT_HOSTBASED_SERVICE, // Mechanism
+ 0, // Context flags
+ 1, // Locally initiated
+ 0); // Open
+ gss_buffer_desc in_buffer = {0, NULL};
+ gss_buffer_desc out_buffer = {arraysize(kInitialAuthResponse),
+ const_cast<char*>(kInitialAuthResponse)};
+ library->ExpectSecurityContext(
+ "Negotiate",
+ GSS_S_CONTINUE_NEEDED,
+ 0,
+ context_info,
+ in_buffer,
+ out_buffer);
+}
+
} // namespace
TEST(HttpAuthGSSAPIPOSIXTest, GSSAPIStartup) {
@@ -144,4 +168,103 @@ TEST(HttpAuthGSSAPIPOSIXTest, GSSAPICycle) {
GSS_C_NO_BUFFER);
}
+TEST(HttpAuthGSSAPITest, ParseChallenge_FirstRound) {
+ // The first round should just consist of an unadorned "Negotiate" header.
+ test::MockGSSAPILibrary mock_library;
+ HttpAuthGSSAPI auth_gssapi(&mock_library, "Negotiate",
+ CHROME_GSS_KRB5_MECH_OID_DESC);
+ std::string challenge_text = "Negotiate";
+ HttpAuth::ChallengeTokenizer challenge(challenge_text.begin(),
+ challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ auth_gssapi.ParseChallenge(&challenge));
+}
+
+TEST(HttpAuthGSSAPITest, ParseChallenge_TwoRounds) {
+ // The first round should just have "Negotiate", and the second round should
+ // have a valid base64 token associated with it.
+ test::MockGSSAPILibrary mock_library;
+ HttpAuthGSSAPI auth_gssapi(&mock_library, "Negotiate",
+ CHROME_GSS_KRB5_MECH_OID_DESC);
+ std::string first_challenge_text = "Negotiate";
+ HttpAuth::ChallengeTokenizer first_challenge(first_challenge_text.begin(),
+ first_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ auth_gssapi.ParseChallenge(&first_challenge));
+
+ // Generate an auth token and create another thing.
+ EstablishInitialContext(&mock_library);
+ std::string auth_token;
+ EXPECT_EQ(OK, auth_gssapi.GenerateAuthToken(NULL, NULL,
+ L"HTTP/intranet.google.com",
+ &auth_token));
+
+ std::string second_challenge_text = "Negotiate Zm9vYmFy";
+ HttpAuth::ChallengeTokenizer second_challenge(second_challenge_text.begin(),
+ second_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ auth_gssapi.ParseChallenge(&second_challenge));
+}
+
+TEST(HttpAuthGSSAPITest, ParseChallenge_UnexpectedTokenFirstRound) {
+ // If the first round challenge has an additional authentication token, it
+ // should be treated as an invalid challenge from the server.
+ test::MockGSSAPILibrary mock_library;
+ HttpAuthGSSAPI auth_gssapi(&mock_library, "Negotiate",
+ CHROME_GSS_KRB5_MECH_OID_DESC);
+ std::string challenge_text = "Negotiate Zm9vYmFy";
+ HttpAuth::ChallengeTokenizer challenge(challenge_text.begin(),
+ challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_INVALID,
+ auth_gssapi.ParseChallenge(&challenge));
+}
+
+TEST(HttpAuthGSSAPITest, ParseChallenge_MissingTokenSecondRound) {
+ // If a later-round challenge is simply "Negotiate", it should be treated as
+ // an authentication challenge rejection from the server or proxy.
+ test::MockGSSAPILibrary mock_library;
+ HttpAuthGSSAPI auth_gssapi(&mock_library, "Negotiate",
+ CHROME_GSS_KRB5_MECH_OID_DESC);
+ std::string first_challenge_text = "Negotiate";
+ HttpAuth::ChallengeTokenizer first_challenge(first_challenge_text.begin(),
+ first_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ auth_gssapi.ParseChallenge(&first_challenge));
+
+ EstablishInitialContext(&mock_library);
+ std::string auth_token;
+ EXPECT_EQ(OK, auth_gssapi.GenerateAuthToken(NULL, NULL,
+ L"HTTP/intranet.google.com",
+ &auth_token));
+ std::string second_challenge_text = "Negotiate";
+ HttpAuth::ChallengeTokenizer second_challenge(second_challenge_text.begin(),
+ second_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_REJECT,
+ auth_gssapi.ParseChallenge(&second_challenge));
+}
+
+TEST(HttpAuthGSSAPITest, ParseChallenge_NonBase64EncodedToken) {
+ // If a later-round challenge has an invalid base64 encoded token, it should
+ // be treated as an invalid challenge.
+ test::MockGSSAPILibrary mock_library;
+ HttpAuthGSSAPI auth_gssapi(&mock_library, "Negotiate",
+ CHROME_GSS_KRB5_MECH_OID_DESC);
+ std::string first_challenge_text = "Negotiate";
+ HttpAuth::ChallengeTokenizer first_challenge(first_challenge_text.begin(),
+ first_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ auth_gssapi.ParseChallenge(&first_challenge));
+
+ EstablishInitialContext(&mock_library);
+ std::string auth_token;
+ EXPECT_EQ(OK, auth_gssapi.GenerateAuthToken(NULL, NULL,
+ L"HTTP/intranet.google.com",
+ &auth_token));
+ std::string second_challenge_text = "Negotiate =happyjoy=";
+ HttpAuth::ChallengeTokenizer second_challenge(second_challenge_text.begin(),
+ second_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_INVALID,
+ auth_gssapi.ParseChallenge(&second_challenge));
+}
+
} // namespace net
diff --git a/net/http/http_auth_handler.h b/net/http/http_auth_handler.h
index 2895387..4c03a01 100644
--- a/net/http/http_auth_handler.h
+++ b/net/http/http_auth_handler.h
@@ -38,6 +38,22 @@ class HttpAuthHandler {
const GURL& origin,
const BoundNetLog& net_log);
+ // Determines how the previous authorization attempt was received.
+ //
+ // This is called when the server/proxy responds with a 401/407 after an
+ // earlier authorization attempt. Although this normally means that the
+ // previous attempt was rejected, in multi-round schemes such as
+ // NTLM+Negotiate it may indicate that another round of challenge+response
+ // is required. For Digest authentication it may also mean that the previous
+ // attempt used a stale nonce (and nonce-count) and that a new attempt should
+ // be made with a different nonce provided in the challenge.
+ //
+ // |challenge| must be non-NULL and have already tokenized the
+ // authentication scheme, but none of the tokens occuring after the
+ // authentication scheme.
+ virtual HttpAuth::AuthorizationResult HandleAnotherChallenge(
+ HttpAuth::ChallengeTokenizer* challenge) = 0;
+
// Generates an authentication token, potentially asynchronously.
//
// When |username| and |password| are NULL, the default credentials for
@@ -107,11 +123,6 @@ class HttpAuthHandler {
// sequence used by a connection-based authentication scheme.
virtual bool NeedsIdentity() { return true; }
- // Returns true if this is the final round of the authentication sequence.
- // For Basic and Digest, the method always returns true because they are
- // single-round schemes.
- virtual bool IsFinalRound() { return true; }
-
// Returns whether the default credentials may be used for the |origin| passed
// into |InitFromChallenge|. If true, the user does not need to be prompted
// for username and password to establish credentials.
diff --git a/net/http/http_auth_handler_basic.cc b/net/http/http_auth_handler_basic.cc
index 31edf23..355ce8b 100644
--- a/net/http/http_auth_handler_basic.cc
+++ b/net/http/http_auth_handler_basic.cc
@@ -26,19 +26,35 @@ bool HttpAuthHandlerBasic::Init(HttpAuth::ChallengeTokenizer* challenge) {
scheme_ = "basic";
score_ = 1;
properties_ = 0;
+ return ParseChallenge(challenge);
+}
+bool HttpAuthHandlerBasic::ParseChallenge(
+ HttpAuth::ChallengeTokenizer* challenge) {
// Verify the challenge's auth-scheme.
if (!challenge->valid() ||
!LowerCaseEqualsASCII(challenge->scheme(), "basic"))
return false;
// Extract the realm (may be missing).
+ std::string realm;
while (challenge->GetNext()) {
if (LowerCaseEqualsASCII(challenge->name(), "realm"))
- realm_ = challenge->unquoted_value();
+ realm = challenge->unquoted_value();
}
- return challenge->valid();
+ if (!challenge->valid())
+ return false;
+
+ realm_ = realm;
+ return true;
+}
+
+HttpAuth::AuthorizationResult HttpAuthHandlerBasic::HandleAnotherChallenge(
+ HttpAuth::ChallengeTokenizer* challenge) {
+ // Basic authentication is always a single round, so any responses should
+ // be treated as a rejection.
+ return HttpAuth::AUTHORIZATION_RESULT_REJECT;
}
int HttpAuthHandlerBasic::GenerateAuthTokenImpl(
diff --git a/net/http/http_auth_handler_basic.h b/net/http/http_auth_handler_basic.h
index 3204e94..a9031bb 100644
--- a/net/http/http_auth_handler_basic.h
+++ b/net/http/http_auth_handler_basic.h
@@ -31,6 +31,9 @@ class HttpAuthHandlerBasic : public HttpAuthHandler {
scoped_ptr<HttpAuthHandler>* handler);
};
+ HttpAuth::AuthorizationResult HandleAnotherChallenge(
+ HttpAuth::ChallengeTokenizer* challenge);
+
protected:
virtual bool Init(HttpAuth::ChallengeTokenizer* challenge);
@@ -42,6 +45,8 @@ class HttpAuthHandlerBasic : public HttpAuthHandler {
private:
~HttpAuthHandlerBasic() {}
+
+ bool ParseChallenge(HttpAuth::ChallengeTokenizer* challenge);
};
} // namespace net
diff --git a/net/http/http_auth_handler_basic_unittest.cc b/net/http/http_auth_handler_basic_unittest.cc
index 61ce864..f2ddbebc 100644
--- a/net/http/http_auth_handler_basic_unittest.cc
+++ b/net/http/http_auth_handler_basic_unittest.cc
@@ -66,6 +66,52 @@ TEST(HttpAuthHandlerBasicTest, InitFromChallenge) {
OK,
"",
},
+
+ // Realm is valid.
+ {
+ "Basic realm=\"test_realm\"",
+ OK,
+ "test_realm",
+ },
+
+ // The parser ignores tokens which aren't known.
+ {
+ "Basic realm=\"test_realm\",unknown_token=foobar",
+ OK,
+ "test_realm",
+ },
+
+ // The parser skips over tokens which aren't known.
+ {
+ "Basic unknown_token=foobar,realm=\"test_realm\"",
+ OK,
+ "test_realm",
+ },
+
+#if 0
+ // TODO(cbentzel): It's unclear what the parser should do in these cases.
+ // It seems like this should either be treated as invalid,
+ // or the spaces should be used as a separator.
+ {
+ "Basic realm=\"test_realm\" unknown_token=foobar",
+ OK,
+ "test_realm",
+ },
+
+ // The parser skips over tokens which aren't known.
+ {
+ "Basic unknown_token=foobar realm=\"test_realm\"",
+ OK,
+ "test_realm",
+ },
+#endif
+
+ // The parser fails when the first token is not "Basic".
+ {
+ "Negotiate",
+ ERR_INVALID_RESPONSE,
+ ""
+ },
};
HttpAuthHandlerBasic::Factory factory;
GURL origin("http://www.example.com");
diff --git a/net/http/http_auth_handler_digest.cc b/net/http/http_auth_handler_digest.cc
index ebb2494..289e0c7 100644
--- a/net/http/http_auth_handler_digest.cc
+++ b/net/http/http_auth_handler_digest.cc
@@ -173,8 +173,8 @@ std::string HttpAuthHandlerDigest::AssembleCredentials(
std::string nc = StringPrintf("%08x", nonce_count);
// TODO(eroman): is this the right encoding?
- std::string authorization = std::string("Digest username=") +
- HttpUtil::Quote(UTF16ToUTF8(username));
+ std::string authorization = (std::string("Digest username=") +
+ HttpUtil::Quote(UTF16ToUTF8(username)));
authorization += ", realm=" + HttpUtil::Quote(realm_);
authorization += ", nonce=" + HttpUtil::Quote(nonce_);
authorization += ", uri=" + HttpUtil::Quote(path);
@@ -201,6 +201,31 @@ std::string HttpAuthHandlerDigest::AssembleCredentials(
return authorization;
}
+bool HttpAuthHandlerDigest::Init(HttpAuth::ChallengeTokenizer* challenge) {
+ return ParseChallenge(challenge);
+}
+
+HttpAuth::AuthorizationResult HttpAuthHandlerDigest::HandleAnotherChallenge(
+ HttpAuth::ChallengeTokenizer* challenge) {
+ // Even though Digest is not connection based, a "second round" is parsed
+ // to differentiate between stale and rejected responses.
+ // Note that the state of the current handler is not mutated - this way if
+ // there is a rejection the realm hasn't changed.
+ if (!challenge->valid() ||
+ !LowerCaseEqualsASCII(challenge->scheme(), "digest"))
+ return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+
+ // Try to find the "stale" value.
+ while (challenge->GetNext()) {
+ if (!LowerCaseEqualsASCII(challenge->name(), "stale"))
+ continue;
+ if (LowerCaseEqualsASCII(challenge->unquoted_value(), "true"))
+ return HttpAuth::AUTHORIZATION_RESULT_STALE;
+ }
+
+ return HttpAuth::AUTHORIZATION_RESULT_REJECT;
+}
+
// The digest challenge header looks like:
// WWW-Authenticate: Digest
// [realm="<realm-value>"]
@@ -231,9 +256,10 @@ bool HttpAuthHandlerDigest::ParseChallenge(
qop_ = QOP_UNSPECIFIED;
realm_ = nonce_ = domain_ = opaque_ = std::string();
+ // FAIL -- Couldn't match auth-scheme.
if (!challenge->valid() ||
!LowerCaseEqualsASCII(challenge->scheme(), "digest"))
- return false; // FAIL -- Couldn't match auth-scheme.
+ return false;
// Loop through all the properties.
while (challenge->GetNext()) {
@@ -242,17 +268,18 @@ bool HttpAuthHandlerDigest::ParseChallenge(
return false;
}
+ // FAIL -- couldn't parse a property.
if (!ParseChallengeProperty(challenge->name(), challenge->unquoted_value()))
- return false; // FAIL -- couldn't parse a property.
+ return false;
}
// Check if tokenizer failed.
if (!challenge->valid())
- return false; // FAIL
+ return false;
// Check that a minimum set of properties were provided.
if (nonce_.empty())
- return false; // FAIL
+ return false;
return true;
}
diff --git a/net/http/http_auth_handler_digest.h b/net/http/http_auth_handler_digest.h
index ad70ac1..0c38641 100644
--- a/net/http/http_auth_handler_digest.h
+++ b/net/http/http_auth_handler_digest.h
@@ -32,10 +32,11 @@ class HttpAuthHandlerDigest : public HttpAuthHandler {
scoped_ptr<HttpAuthHandler>* handler);
};
+ HttpAuth::AuthorizationResult HandleAnotherChallenge(
+ HttpAuth::ChallengeTokenizer* challenge);
+
protected:
- virtual bool Init(HttpAuth::ChallengeTokenizer* challenge) {
- return ParseChallenge(challenge);
- }
+ virtual bool Init(HttpAuth::ChallengeTokenizer* challenge);
virtual int GenerateAuthTokenImpl(const string16* username,
const string16* password,
diff --git a/net/http/http_auth_handler_digest_unittest.cc b/net/http/http_auth_handler_digest_unittest.cc
index 5129001..8026613 100644
--- a/net/http/http_auth_handler_digest_unittest.cc
+++ b/net/http/http_auth_handler_digest_unittest.cc
@@ -291,4 +291,34 @@ TEST(HttpAuthHandlerDigestTest, AssembleCredentials) {
}
}
+TEST(HttpAuthHandlerDigest, HandleAnotherChallenge_Failed) {
+ scoped_ptr<HttpAuthHandlerDigest::Factory> factory(
+ new HttpAuthHandlerDigest::Factory());
+ scoped_ptr<HttpAuthHandler> handler;
+ std::string default_challenge =
+ "Digest realm=\"Oblivion\", nonce=\"nonce-value\"";
+ GURL origin("intranet.google.com");
+ int rv = factory->CreateAuthHandlerFromString(
+ default_challenge, HttpAuth::AUTH_SERVER, origin, BoundNetLog(),
+ &handler);
+ EXPECT_EQ(OK, rv);
+
+ HttpAuth::ChallengeTokenizer tok_default(default_challenge.begin(),
+ default_challenge.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_REJECT,
+ handler->HandleAnotherChallenge(&tok_default));
+
+ std::string stale_challenge = default_challenge + ", stale=true";
+ HttpAuth::ChallengeTokenizer tok_stale(stale_challenge.begin(),
+ stale_challenge.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_STALE,
+ handler->HandleAnotherChallenge(&tok_stale));
+
+ std::string stale_false_challenge = default_challenge + ", stale=false";
+ HttpAuth::ChallengeTokenizer tok_stale_false(stale_false_challenge.begin(),
+ stale_false_challenge.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_REJECT,
+ handler->HandleAnotherChallenge(&tok_stale_false));
+}
+
} // namespace net
diff --git a/net/http/http_auth_handler_mock.cc b/net/http/http_auth_handler_mock.cc
index 9f0011d..90602d0 100644
--- a/net/http/http_auth_handler_mock.cc
+++ b/net/http/http_auth_handler_mock.cc
@@ -5,6 +5,7 @@
#include "net/http/http_auth_handler_mock.h"
#include "base/message_loop.h"
+#include "base/string_util.h"
#include "net/base/net_errors.h"
#include "net/http/http_request_info.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -77,6 +78,16 @@ bool HttpAuthHandlerMock::Init(HttpAuth::ChallengeTokenizer* challenge) {
return true;
}
+HttpAuth::AuthorizationResult HttpAuthHandlerMock::HandleAnotherChallenge(
+ HttpAuth::ChallengeTokenizer* challenge) {
+ if (!is_connection_based())
+ return HttpAuth::AUTHORIZATION_RESULT_REJECT;
+ if (!challenge->valid() ||
+ !LowerCaseEqualsASCII(challenge->scheme(), "mock"))
+ return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+ return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
+}
+
int HttpAuthHandlerMock::GenerateAuthTokenImpl(const string16* username,
const string16* password,
const HttpRequestInfo* request,
diff --git a/net/http/http_auth_handler_mock.h b/net/http/http_auth_handler_mock.h
index 618ec7a..bef8b2b 100644
--- a/net/http/http_auth_handler_mock.h
+++ b/net/http/http_auth_handler_mock.h
@@ -41,7 +41,6 @@ class HttpAuthHandlerMock : public HttpAuthHandler {
CompletionCallback* callback);
virtual bool NeedsIdentity() { return first_round_; }
- virtual bool IsFinalRound() { return false; }
void SetGenerateExpectation(bool async, int rv);
@@ -53,6 +52,9 @@ class HttpAuthHandlerMock : public HttpAuthHandler {
return request_url_;
}
+ HttpAuth::AuthorizationResult HandleAnotherChallenge(
+ HttpAuth::ChallengeTokenizer* challenge);
+
// The Factory class simply returns the same handler each time
// CreateAuthHandler is called.
class Factory : public HttpAuthHandlerFactory {
diff --git a/net/http/http_auth_handler_negotiate.cc b/net/http/http_auth_handler_negotiate.cc
index 27e2571..c66ea45 100644
--- a/net/http/http_auth_handler_negotiate.cc
+++ b/net/http/http_auth_handler_negotiate.cc
@@ -95,6 +95,13 @@ bool HttpAuthHandlerNegotiate::Init(HttpAuth::ChallengeTokenizer* challenge) {
scheme_ = "negotiate";
score_ = 4;
properties_ = ENCRYPTS_IDENTITY | IS_CONNECTION_BASED;
+ HttpAuth::AuthorizationResult auth_result =
+ auth_system_.ParseChallenge(challenge);
+ return (auth_result == HttpAuth::AUTHORIZATION_RESULT_ACCEPT);
+}
+
+HttpAuth::AuthorizationResult HttpAuthHandlerNegotiate::HandleAnotherChallenge(
+ HttpAuth::ChallengeTokenizer* challenge) {
return auth_system_.ParseChallenge(challenge);
}
@@ -103,10 +110,6 @@ bool HttpAuthHandlerNegotiate::NeedsIdentity() {
return auth_system_.NeedsIdentity();
}
-bool HttpAuthHandlerNegotiate::IsFinalRound() {
- return auth_system_.IsFinalRound();
-}
-
bool HttpAuthHandlerNegotiate::AllowsDefaultCredentials() {
if (target_ == HttpAuth::AUTH_PROXY)
return true;
diff --git a/net/http/http_auth_handler_negotiate.h b/net/http/http_auth_handler_negotiate.h
index ca9a577..2ff4a9b 100644
--- a/net/http/http_auth_handler_negotiate.h
+++ b/net/http/http_auth_handler_negotiate.h
@@ -107,10 +107,11 @@ class HttpAuthHandlerNegotiate : public HttpAuthHandler {
virtual bool NeedsIdentity();
- virtual bool IsFinalRound();
-
virtual bool AllowsDefaultCredentials();
+ virtual HttpAuth::AuthorizationResult HandleAnotherChallenge(
+ HttpAuth::ChallengeTokenizer* challenge);
+
// These are public for unit tests
std::wstring CreateSPN(const AddressList& address_list, const GURL& orign);
const std::wstring& spn() const { return spn_; }
diff --git a/net/http/http_auth_handler_ntlm.cc b/net/http/http_auth_handler_ntlm.cc
index 3a7d25e..dddddb4 100644
--- a/net/http/http_auth_handler_ntlm.cc
+++ b/net/http/http_auth_handler_ntlm.cc
@@ -92,27 +92,48 @@ int HttpAuthHandlerNTLM::GenerateAuthTokenImpl(
#endif
}
-// The NTLM challenge header looks like:
-// WWW-Authenticate: NTLM auth-data
-bool HttpAuthHandlerNTLM::ParseChallenge(
- HttpAuth::ChallengeTokenizer* tok) {
+bool HttpAuthHandlerNTLM::Init(HttpAuth::ChallengeTokenizer* tok) {
scheme_ = "ntlm";
score_ = 3;
properties_ = ENCRYPTS_IDENTITY | IS_CONNECTION_BASED;
+ return ParseChallenge(tok, true) == HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
+}
+
+HttpAuth::AuthorizationResult HttpAuthHandlerNTLM::HandleAnotherChallenge(
+ HttpAuth::ChallengeTokenizer* challenge) {
+ return ParseChallenge(challenge, false);
+}
+
+// The NTLM challenge header looks like:
+// WWW-Authenticate: NTLM auth-data
+HttpAuth::AuthorizationResult HttpAuthHandlerNTLM::ParseChallenge(
+ HttpAuth::ChallengeTokenizer* tok, bool initial_challenge) {
#if defined(NTLM_SSPI)
+ // auth_sspi_ contains state for whether or not this is the initial challenge.
return auth_sspi_.ParseChallenge(tok);
#else
+ // TODO(cbentzel): Most of the logic between SSPI, GSSAPI, and portable NTLM
+ // authentication parsing could probably be shared - just need to know if
+ // there was previously a challenge round.
auth_data_.clear();
// Verify the challenge's auth-scheme.
if (!tok->valid() || !LowerCaseEqualsASCII(tok->scheme(), "ntlm"))
- return false;
+ return HttpAuth::AUTHORIZATION_RESULT_INVALID;
tok->set_expect_base64_token(true);
- if (tok->GetNext())
- auth_data_.assign(tok->value_begin(), tok->value_end());
- return true;
+ if (!tok->GetNext()) {
+ if (!initial_challenge)
+ return HttpAuth::AUTHORIZATION_RESULT_REJECT;
+ return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
+ } else {
+ if (initial_challenge)
+ return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+ }
+
+ auth_data_.assign(tok->value_begin(), tok->value_end());
+ return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
#endif // defined(NTLM_SSPI)
}
diff --git a/net/http/http_auth_handler_ntlm.h b/net/http/http_auth_handler_ntlm.h
index 00d6905..831e43d 100644
--- a/net/http/http_auth_handler_ntlm.h
+++ b/net/http/http_auth_handler_ntlm.h
@@ -108,14 +108,13 @@ class HttpAuthHandlerNTLM : public HttpAuthHandler {
virtual bool NeedsIdentity();
- virtual bool IsFinalRound();
-
virtual bool AllowsDefaultCredentials();
+ virtual HttpAuth::AuthorizationResult HandleAnotherChallenge(
+ HttpAuth::ChallengeTokenizer* challenge);
+
protected:
- virtual bool Init(HttpAuth::ChallengeTokenizer* tok) {
- return ParseChallenge(tok);
- }
+ virtual bool Init(HttpAuth::ChallengeTokenizer* tok);
virtual int GenerateAuthTokenImpl(const string16* username,
const string16* password,
@@ -138,8 +137,8 @@ class HttpAuthHandlerNTLM : public HttpAuthHandler {
#endif
// Parse the challenge, saving the results into this instance.
- // Returns true on success.
- bool ParseChallenge(HttpAuth::ChallengeTokenizer* tok);
+ HttpAuth::AuthorizationResult ParseChallenge(
+ HttpAuth::ChallengeTokenizer* tok, bool initial_challenge);
// Given an input token received from the server, generate the next output
// token to be sent to the server.
diff --git a/net/http/http_auth_handler_ntlm_portable.cc b/net/http/http_auth_handler_ntlm_portable.cc
index e16d32c..d3abc98 100644
--- a/net/http/http_auth_handler_ntlm_portable.cc
+++ b/net/http/http_auth_handler_ntlm_portable.cc
@@ -653,10 +653,6 @@ bool HttpAuthHandlerNTLM::NeedsIdentity() {
return !auth_data_.empty();
}
-bool HttpAuthHandlerNTLM::IsFinalRound() {
- return !auth_data_.empty();
-}
-
bool HttpAuthHandlerNTLM::AllowsDefaultCredentials() {
// Default credentials are not supported in the portable implementation of
// NTLM, but are supported in the SSPI implementation.
diff --git a/net/http/http_auth_handler_ntlm_win.cc b/net/http/http_auth_handler_ntlm_win.cc
index c1990d37..1b7d1ac 100644
--- a/net/http/http_auth_handler_ntlm_win.cc
+++ b/net/http/http_auth_handler_ntlm_win.cc
@@ -34,10 +34,6 @@ bool HttpAuthHandlerNTLM::NeedsIdentity() {
return auth_sspi_.NeedsIdentity();
}
-bool HttpAuthHandlerNTLM::IsFinalRound() {
- return auth_sspi_.IsFinalRound();
-}
-
bool HttpAuthHandlerNTLM::AllowsDefaultCredentials() {
if (target_ == HttpAuth::AUTH_PROXY)
return true;
diff --git a/net/http/http_auth_sspi_win.cc b/net/http/http_auth_sspi_win.cc
index 6ff9529..03742e4 100644
--- a/net/http/http_auth_sspi_win.cc
+++ b/net/http/http_auth_sspi_win.cc
@@ -129,10 +129,6 @@ bool HttpAuthSSPI::NeedsIdentity() const {
return decoded_server_auth_token_.empty();
}
-bool HttpAuthSSPI::IsFinalRound() const {
- return !decoded_server_auth_token_.empty();
-}
-
void HttpAuthSSPI::Delegate() {
can_delegate_ = true;
}
@@ -144,27 +140,35 @@ void HttpAuthSSPI::ResetSecurityContext() {
}
}
-bool HttpAuthSSPI::ParseChallenge(HttpAuth::ChallengeTokenizer* tok) {
+HttpAuth::AuthorizationResult HttpAuthSSPI::ParseChallenge(
+ HttpAuth::ChallengeTokenizer* tok) {
// Verify the challenge's auth-scheme.
if (!tok->valid() ||
!LowerCaseEqualsASCII(tok->scheme(), StringToLowerASCII(scheme_).c_str()))
- return false;
+ return HttpAuth::AUTHORIZATION_RESULT_INVALID;
tok->set_expect_base64_token(true);
if (!tok->GetNext()) {
- decoded_server_auth_token_.clear();
- return true;
+ // If a context has already been established, an empty challenge
+ // should be treated as a rejection of the current attempt.
+ if (SecIsValidHandle(&ctxt_))
+ return HttpAuth::AUTHORIZATION_RESULT_REJECT;
+ DCHECK(decoded_server_auth_token_.empty());
+ return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
+ } else {
+ // If a context has not already been established, additional tokens should
+ // not be present in the auth challenge.
+ if (!SecIsValidHandle(&ctxt_))
+ return HttpAuth::AUTHORIZATION_RESULT_INVALID;
}
std::string encoded_auth_token = tok->value();
std::string decoded_auth_token;
bool base64_rv = base::Base64Decode(encoded_auth_token, &decoded_auth_token);
- if (!base64_rv) {
- LOG(ERROR) << "Base64 decoding of auth token failed.";
- return false;
- }
+ if (!base64_rv)
+ return HttpAuth::AUTHORIZATION_RESULT_INVALID;
decoded_server_auth_token_ = decoded_auth_token;
- return true;
+ return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
}
int HttpAuthSSPI::GenerateAuthToken(const string16* username,
@@ -174,7 +178,7 @@ int HttpAuthSSPI::GenerateAuthToken(const string16* username,
DCHECK((username == NULL) == (password == NULL));
// Initial challenge.
- if (!IsFinalRound()) {
+ if (!SecIsValidHandle(&cred_)) {
int rv = OnFirstRound(username, password);
if (rv != OK)
return rv;
diff --git a/net/http/http_auth_sspi_win.h b/net/http/http_auth_sspi_win.h
index 482ab6b..c4df58e 100644
--- a/net/http/http_auth_sspi_win.h
+++ b/net/http/http_auth_sspi_win.h
@@ -79,9 +79,9 @@ class HttpAuthSSPI {
~HttpAuthSSPI();
bool NeedsIdentity() const;
- bool IsFinalRound() const;
- bool ParseChallenge(HttpAuth::ChallengeTokenizer* tok);
+ HttpAuth::AuthorizationResult ParseChallenge(
+ HttpAuth::ChallengeTokenizer* tok);
// Generates an authentication token for the service specified by the
// Service Principal Name |spn| and stores the value in |*auth_token|.
@@ -102,8 +102,7 @@ class HttpAuthSSPI {
void Delegate();
private:
- int OnFirstRound(const string16* username,
- const string16* password);
+ int OnFirstRound(const string16* username, const string16* password);
int GetNextSecurityToken(
const std::wstring& spn,
diff --git a/net/http/http_auth_sspi_win_unittest.cc b/net/http/http_auth_sspi_win_unittest.cc
index fdef793..331dcd6 100644
--- a/net/http/http_auth_sspi_win_unittest.cc
+++ b/net/http/http_auth_sspi_win_unittest.cc
@@ -22,6 +22,8 @@ void MatchDomainUserAfterSplit(const std::wstring& combined,
EXPECT_EQ(expected_user, actual_user);
}
+const ULONG kMaxTokenLength = 100;
+
} // namespace
TEST(HttpAuthSSPITest, SplitUserAndDomain) {
@@ -36,7 +38,7 @@ TEST(HttpAuthSSPITest, DetermineMaxTokenLength_Normal) {
MockSSPILibrary mock_library;
mock_library.ExpectQuerySecurityPackageInfo(L"NTLM", SEC_E_OK, &package_info);
- ULONG max_token_length = 100;
+ ULONG max_token_length = kMaxTokenLength;
int rv = DetermineMaxTokenLength(&mock_library, L"NTLM", &max_token_length);
EXPECT_EQ(OK, rv);
EXPECT_EQ(1337, max_token_length);
@@ -46,7 +48,7 @@ TEST(HttpAuthSSPITest, DetermineMaxTokenLength_InvalidPackage) {
MockSSPILibrary mock_library;
mock_library.ExpectQuerySecurityPackageInfo(L"Foo", SEC_E_SECPKG_NOT_FOUND,
NULL);
- ULONG max_token_length = 100;
+ ULONG max_token_length = kMaxTokenLength;
int rv = DetermineMaxTokenLength(&mock_library, L"Foo", &max_token_length);
EXPECT_EQ(ERR_UNSUPPORTED_AUTH_SCHEME, rv);
// |DetermineMaxTokenLength()| interface states that |max_token_length| should
@@ -54,4 +56,100 @@ TEST(HttpAuthSSPITest, DetermineMaxTokenLength_InvalidPackage) {
EXPECT_EQ(100, max_token_length);
}
+TEST(HttpAuthSSPITest, ParseChallenge_FirstRound) {
+ // The first round should just consist of an unadorned "Negotiate" header.
+ MockSSPILibrary mock_library;
+ HttpAuthSSPI auth_sspi(&mock_library, "Negotiate",
+ NEGOSSP_NAME, kMaxTokenLength);
+ std::string challenge_text = "Negotiate";
+ HttpAuth::ChallengeTokenizer challenge(challenge_text.begin(),
+ challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ auth_sspi.ParseChallenge(&challenge));
+}
+
+TEST(HttpAuthSSPITest, ParseChallenge_TwoRounds) {
+ // The first round should just have "Negotiate", and the second round should
+ // have a valid base64 token associated with it.
+ MockSSPILibrary mock_library;
+ HttpAuthSSPI auth_sspi(&mock_library, "Negotiate",
+ NEGOSSP_NAME, kMaxTokenLength);
+ std::string first_challenge_text = "Negotiate";
+ HttpAuth::ChallengeTokenizer first_challenge(first_challenge_text.begin(),
+ first_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ auth_sspi.ParseChallenge(&first_challenge));
+
+ // Generate an auth token and create another thing.
+ std::string auth_token;
+ EXPECT_EQ(OK, auth_sspi.GenerateAuthToken(NULL, NULL,
+ L"HTTP/intranet.google.com",
+ &auth_token));
+
+ std::string second_challenge_text = "Negotiate Zm9vYmFy";
+ HttpAuth::ChallengeTokenizer second_challenge(second_challenge_text.begin(),
+ second_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ auth_sspi.ParseChallenge(&second_challenge));
+}
+
+TEST(HttpAuthSSPITest, ParseChallenge_UnexpectedTokenFirstRound) {
+ // If the first round challenge has an additional authentication token, it
+ // should be treated as an invalid challenge from the server.
+ MockSSPILibrary mock_library;
+ HttpAuthSSPI auth_sspi(&mock_library, "Negotiate",
+ NEGOSSP_NAME, kMaxTokenLength);
+ std::string challenge_text = "Negotiate Zm9vYmFy";
+ HttpAuth::ChallengeTokenizer challenge(challenge_text.begin(),
+ challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_INVALID,
+ auth_sspi.ParseChallenge(&challenge));
+}
+
+TEST(HttpAuthSSPITest, ParseChallenge_MissingTokenSecondRound) {
+ // If a later-round challenge is simply "Negotiate", it should be treated as
+ // an authentication challenge rejection from the server or proxy.
+ MockSSPILibrary mock_library;
+ HttpAuthSSPI auth_sspi(&mock_library, "Negotiate",
+ NEGOSSP_NAME, kMaxTokenLength);
+ std::string first_challenge_text = "Negotiate";
+ HttpAuth::ChallengeTokenizer first_challenge(first_challenge_text.begin(),
+ first_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ auth_sspi.ParseChallenge(&first_challenge));
+
+ std::string auth_token;
+ EXPECT_EQ(OK, auth_sspi.GenerateAuthToken(NULL, NULL,
+ L"HTTP/intranet.google.com",
+ &auth_token));
+ std::string second_challenge_text = "Negotiate";
+ HttpAuth::ChallengeTokenizer second_challenge(second_challenge_text.begin(),
+ second_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_REJECT,
+ auth_sspi.ParseChallenge(&second_challenge));
+}
+
+TEST(HttpAuthSSPITest, ParseChallenge_NonBase64EncodedToken) {
+ // If a later-round challenge has an invalid base64 encoded token, it should
+ // be treated as an invalid challenge.
+ MockSSPILibrary mock_library;
+ HttpAuthSSPI auth_sspi(&mock_library, "Negotiate",
+ NEGOSSP_NAME, kMaxTokenLength);
+ std::string first_challenge_text = "Negotiate";
+ HttpAuth::ChallengeTokenizer first_challenge(first_challenge_text.begin(),
+ first_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ auth_sspi.ParseChallenge(&first_challenge));
+
+ std::string auth_token;
+ EXPECT_EQ(OK, auth_sspi.GenerateAuthToken(NULL, NULL,
+ L"HTTP/intranet.google.com",
+ &auth_token));
+ std::string second_challenge_text = "Negotiate =happyjoy=";
+ HttpAuth::ChallengeTokenizer second_challenge(second_challenge_text.begin(),
+ second_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_INVALID,
+ auth_sspi.ParseChallenge(&second_challenge));
+}
+
} // namespace net
diff --git a/net/http/http_auth_unittest.cc b/net/http/http_auth_unittest.cc
index cdc7e16..4d8ddee 100644
--- a/net/http/http_auth_unittest.cc
+++ b/net/http/http_auth_unittest.cc
@@ -13,12 +13,36 @@
#include "net/http/http_auth_filter.h"
#include "net/http/http_auth_handler.h"
#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_auth_handler_mock.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace net {
+namespace {
+
+HttpAuthHandlerMock* CreateMockHandler(bool connection_based) {
+ HttpAuthHandlerMock* auth_handler = new HttpAuthHandlerMock();
+ auth_handler->set_connection_based(connection_based);
+ std::string challenge_text = "Mock";
+ HttpAuth::ChallengeTokenizer challenge(challenge_text.begin(),
+ challenge_text.end());
+ GURL origin("www.example.com");
+ EXPECT_TRUE(auth_handler->InitFromChallenge(&challenge,
+ HttpAuth::AUTH_SERVER,
+ origin,
+ BoundNetLog()));
+ return auth_handler;
+}
+
+HttpResponseHeaders* HeadersFromResponseText(const std::string& response) {
+ return new HttpResponseHeaders(
+ HttpUtil::AssembleRawHeaders(response.c_str(), response.length()));
+}
+
+} // namespace
+
TEST(HttpAuthTest, ChooseBestChallenge) {
static const struct {
const char* headers;
@@ -82,11 +106,8 @@ TEST(HttpAuthTest, ChooseBestChallenge) {
// Make a HttpResponseHeaders object.
std::string headers_with_status_line("HTTP/1.1 401 Unauthorized\n");
headers_with_status_line += tests[i].headers;
- scoped_refptr<net::HttpResponseHeaders> headers(
- new net::HttpResponseHeaders(
- net::HttpUtil::AssembleRawHeaders(
- headers_with_status_line.c_str(),
- headers_with_status_line.length())));
+ scoped_refptr<HttpResponseHeaders> headers(
+ HeadersFromResponseText(headers_with_status_line));
scoped_ptr<HttpAuthHandler> handler;
HttpAuth::ChooseBestChallenge(http_auth_handler_factory.get(),
@@ -107,130 +128,49 @@ TEST(HttpAuthTest, ChooseBestChallenge) {
}
}
-TEST(HttpAuthTest, ChooseBestChallengeConnectionBasedNTLM) {
- static const struct {
- const char* headers;
- const char* challenge_realm;
- } tests[] = {
- {
- "WWW-Authenticate: NTLM\r\n",
-
- "",
- },
- {
- "WWW-Authenticate: NTLM "
- "TlRMTVNTUAACAAAADAAMADgAAAAFgokCTroKF1e/DRcAAAAAAAAAALo"
- "AugBEAAAABQEoCgAAAA9HAE8ATwBHAEwARQACAAwARwBPAE8ARwBMAE"
- "UAAQAaAEEASwBFAEUAUwBBAFIAQQAtAEMATwBSAFAABAAeAGMAbwByA"
- "HAALgBnAG8AbwBnAGwAZQAuAGMAbwBtAAMAQABhAGsAZQBlAHMAYQBy"
- "AGEALQBjAG8AcgBwAC4AYQBkAC4AYwBvAHIAcAAuAGcAbwBvAGcAbAB"
- "lAC4AYwBvAG0ABQAeAGMAbwByAHAALgBnAG8AbwBnAGwAZQAuAGMAbw"
- "BtAAAAAAA=\r\n",
-
- // Realm is empty.
- "",
- }
- };
- GURL origin("http://www.example.com");
+TEST(HttpAuthTest, HandleChallengeResponse_RequestBased) {
+ scoped_ptr<HttpAuthHandlerMock> mock_handler(CreateMockHandler(false));
std::set<std::string> disabled_schemes;
-
- scoped_ptr<HttpAuthHandlerFactory> http_auth_handler_factory(
- HttpAuthHandlerFactory::CreateDefault());
- scoped_ptr<HttpAuthHandler> handler;
-
- for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
- // Make a HttpResponseHeaders object.
- std::string headers_with_status_line("HTTP/1.1 401 Unauthorized\n");
- headers_with_status_line += tests[i].headers;
- scoped_refptr<net::HttpResponseHeaders> headers(
- new net::HttpResponseHeaders(
- net::HttpUtil::AssembleRawHeaders(
- headers_with_status_line.c_str(),
- headers_with_status_line.length())));
-
- // possibly_deleted_old_handler may point to deleted memory
- // after ChooseBestChallenge has been called, and should not
- // be dereferenced.
- HttpAuthHandler* possibly_deleted_old_handler = handler.get();
- HttpAuth::ChooseBestChallenge(http_auth_handler_factory.get(),
- headers.get(),
- HttpAuth::AUTH_SERVER,
- origin,
- disabled_schemes,
- BoundNetLog(),
- &handler);
- EXPECT_TRUE(handler != NULL);
- // Since NTLM is connection-based, we should continue to use the existing
- // handler rather than creating a new one.
- if (i != 0)
- EXPECT_EQ(possibly_deleted_old_handler, handler.get());
- ASSERT_NE(reinterpret_cast<net::HttpAuthHandler *>(NULL), handler.get());
- EXPECT_STREQ(tests[i].challenge_realm, handler->realm().c_str());
- }
+ scoped_refptr<HttpResponseHeaders> headers(
+ HeadersFromResponseText(
+ "HTTP/1.1 401 Unauthorized\n"
+ "WWW-Authenticate: Mock token_here\n"));
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_REJECT,
+ HttpAuth::HandleChallengeResponse(
+ mock_handler.get(),
+ headers.get(),
+ HttpAuth::AUTH_SERVER,
+ disabled_schemes));
}
-TEST(HttpAuthTest, ChooseBestChallengeConnectionBasedNegotiate) {
- static const struct {
- const char* headers;
- const char* challenge_realm;
- } tests[] = {
- {
- "WWW-Authenticate: Negotiate\r\n",
-
- "",
- },
- {
- "WWW-Authenticate: Negotiate "
- "TlRMTVNTUAACAAAADAAMADgAAAAFgokCTroKF1e/DRcAAAAAAAAAALo"
- "AugBEAAAABQEoCgAAAA9HAE8ATwBHAEwARQACAAwARwBPAE8ARwBMAE"
- "UAAQAaAEEASwBFAEUAUwBBAFIAQQAtAEMATwBSAFAABAAeAGMAbwByA"
- "HAALgBnAG8AbwBnAGwAZQAuAGMAbwBtAAMAQABhAGsAZQBlAHMAYQBy"
- "AGEALQBjAG8AcgBwAC4AYQBkAC4AYwBvAHIAcAAuAGcAbwBvAGcAbAB"
- "lAC4AYwBvAG0ABQAeAGMAbwByAHAALgBnAG8AbwBnAGwAZQAuAGMAbw"
- "BtAAAAAAA=\r\n",
-
- // Realm is empty.
- "",
- }
- };
- GURL origin("http://www.example.com");
+TEST(HttpAuthTest, HandleChallengeResponse_ConnectionBased) {
+ scoped_ptr<HttpAuthHandlerMock> mock_handler(CreateMockHandler(true));
std::set<std::string> disabled_schemes;
- URLSecurityManagerAllow url_security_manager;
- scoped_ptr<HttpAuthHandlerRegistryFactory> http_auth_handler_factory(
- HttpAuthHandlerFactory::CreateDefault());
- http_auth_handler_factory->SetURLSecurityManager(
- "negotiate", &url_security_manager);
-
- scoped_ptr<HttpAuthHandler> handler;
- for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
- // Make a HttpResponseHeaders object.
- std::string headers_with_status_line("HTTP/1.1 401 Unauthorized\n");
- headers_with_status_line += tests[i].headers;
- scoped_refptr<net::HttpResponseHeaders> headers(
- new net::HttpResponseHeaders(
- net::HttpUtil::AssembleRawHeaders(
- headers_with_status_line.c_str(),
- headers_with_status_line.length())));
-
- HttpAuthHandler* old_handler = handler.get();
- HttpAuth::ChooseBestChallenge(http_auth_handler_factory.get(),
- headers.get(),
- HttpAuth::AUTH_SERVER,
- origin,
- disabled_schemes,
- BoundNetLog(),
- &handler);
-
- EXPECT_TRUE(handler != NULL);
- // Since Negotiate is connection-based, we should continue to use the
- // existing handler rather than creating a new one.
- if (i != 0)
- EXPECT_EQ(old_handler, handler.get());
-
- ASSERT_NE(reinterpret_cast<net::HttpAuthHandler *>(NULL), handler.get());
+ scoped_refptr<HttpResponseHeaders> headers(
+ HeadersFromResponseText(
+ "HTTP/1.1 401 Unauthorized\n"
+ "WWW-Authenticate: Mock token_here\n"));
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ HttpAuth::HandleChallengeResponse(
+ mock_handler.get(),
+ headers.get(),
+ HttpAuth::AUTH_SERVER,
+ disabled_schemes));
+}
- EXPECT_STREQ(tests[i].challenge_realm, handler->realm().c_str());
- }
+TEST(HttpAuthTest, HandleChallengeResponse_ConnectionBasedNoMatch) {
+ scoped_ptr<HttpAuthHandlerMock> mock_handler(CreateMockHandler(true));
+ std::set<std::string> disabled_schemes;
+ scoped_refptr<HttpResponseHeaders> headers(
+ HeadersFromResponseText(
+ "HTTP/1.1 401 Unauthorized\n"
+ "WWW-Authenticate: Basic realm=\"happy\"\n"));
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_REJECT,
+ HttpAuth::HandleChallengeResponse(
+ mock_handler.get(),
+ headers.get(),
+ HttpAuth::AUTH_SERVER,
+ disabled_schemes));
}
TEST(HttpAuthTest, ChallengeTokenizer) {
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc
index 5ecd6fd..ef27954 100644
--- a/net/http/http_network_transaction_unittest.cc
+++ b/net/http/http_network_transaction_unittest.cc
@@ -6654,6 +6654,8 @@ TEST_F(HttpNetworkTransactionTest, MultiRoundAuth) {
kGetAuth,
// Third round
kGetAuth,
+ // Fourth round
+ kGetAuth
};
MockRead reads[] = {
// First round
@@ -6661,6 +6663,8 @@ TEST_F(HttpNetworkTransactionTest, MultiRoundAuth) {
// Second round
kServerChallenge,
// Third round
+ kServerChallenge,
+ // Fourth round
kSuccess,
};
StaticSocketDataProvider data_provider(reads, arraysize(reads),
@@ -6696,6 +6700,16 @@ TEST_F(HttpNetworkTransactionTest, MultiRoundAuth) {
response = trans->GetResponseInfo();
ASSERT_FALSE(response == NULL);
EXPECT_TRUE(response->auth_challenge.get() == NULL);
+
+ // Fourth round
+ auth_handler->SetGenerateExpectation(false, OK);
+ rv = trans->RestartWithAuth(string16(), string16(), &callback);
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ response = trans->GetResponseInfo();
+ ASSERT_FALSE(response == NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
}
class TLSDecompressionFailureSocketDataProvider : public SocketDataProvider {
diff --git a/net/http/mock_sspi_library_win.cc b/net/http/mock_sspi_library_win.cc
index d0dae51..c3d875c 100644
--- a/net/http/mock_sspi_library_win.cc
+++ b/net/http/mock_sspi_library_win.cc
@@ -50,6 +50,10 @@ SECURITY_STATUS MockSSPILibrary::InitializeSecurityContext(
uint8* buf = reinterpret_cast<uint8 *>(out_buffer->pvBuffer);
buf[0] = 0xAB;
buf[1] = 0xBA;
+
+ // Fill in phNewContext with arbitrary value if it's invalid.
+ if (phNewContext != phContext)
+ phNewContext->dwLower = phNewContext->dwUpper = ((ULONG_PTR) ((INT_PTR)0));
return SEC_E_OK;
}
@@ -75,8 +79,10 @@ SECURITY_STATUS MockSSPILibrary::FreeCredentialsHandle(
}
SECURITY_STATUS MockSSPILibrary::DeleteSecurityContext(PCtxtHandle phContext) {
- ADD_FAILURE();
- return ERROR_CALL_NOT_IMPLEMENTED;
+ EXPECT_TRUE(phContext->dwLower == ((ULONG_PTR) ((INT_PTR) 0)));
+ EXPECT_TRUE(phContext->dwLower == ((ULONG_PTR) ((INT_PTR) 0)));
+ SecInvalidateHandle(phContext);
+ return SEC_E_OK;
}
SECURITY_STATUS MockSSPILibrary::FreeContextBuffer(PVOID pvContextBuffer) {
diff --git a/net/socket_stream/socket_stream.cc b/net/socket_stream/socket_stream.cc
index ce82c9b..382571a 100644
--- a/net/socket_stream/socket_stream.cc
+++ b/net/socket_stream/socket_stream.cc
@@ -930,10 +930,11 @@ int SocketStream::HandleAuthChallenge(const HttpResponseHeaders* headers) {
LOG(INFO) << "The proxy " << auth_origin << " requested auth";
- // The auth we tried just failed, hence it can't be valid.
- // Remove it from the cache so it won't be used again.
- if (auth_handler_.get() && !auth_identity_.invalid &&
- auth_handler_->IsFinalRound()) {
+ // TODO(cbentzel): Since SocketStream only suppports basic authentication
+ // right now, another challenge is always treated as a rejection.
+ // Ultimately this should be converted to use HttpAuthController like the
+ // HttpNetworkTransaction has.
+ if (auth_handler_.get() && !auth_identity_.invalid) {
if (auth_identity_.source != HttpAuth::IDENT_SRC_PATH_LOOKUP)
auth_cache_.Remove(auth_origin,
auth_handler_->realm(),