// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include <set> #include <string> #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/string_util.h" #include "net/base/mock_host_resolver.h" #include "net/base/net_errors.h" #include "net/http/http_auth.h" #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 "net/http/mock_allow_url_security_manager.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 = "Basic"; 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())); } HttpAuth::AuthorizationResult HandleChallengeResponse( bool connection_based, const std::string& headers_text, std::string* challenge_used) { scoped_ptr<HttpAuthHandlerMock> mock_handler( CreateMockHandler(connection_based)); std::set<HttpAuth::Scheme> disabled_schemes; scoped_refptr<HttpResponseHeaders> headers( HeadersFromResponseText(headers_text)); return HttpAuth::HandleChallengeResponse( mock_handler.get(), headers.get(), HttpAuth::AUTH_SERVER, disabled_schemes, challenge_used); } } // namespace TEST(HttpAuthTest, ChooseBestChallenge) { static const struct { const char* headers; HttpAuth::Scheme challenge_scheme; const char* challenge_realm; } tests[] = { { // Basic is the only challenge type, pick it. "Y: Digest realm=\"X\", nonce=\"aaaaaaaaaa\"\n" "www-authenticate: Basic realm=\"BasicRealm\"\n", HttpAuth::AUTH_SCHEME_BASIC, "BasicRealm", }, { // Fake is the only challenge type, but it is unsupported. "Y: Digest realm=\"FooBar\", nonce=\"aaaaaaaaaa\"\n" "www-authenticate: Fake realm=\"FooBar\"\n", HttpAuth::AUTH_SCHEME_MAX, "", }, { // Pick Digest over Basic. "www-authenticate: Basic realm=\"FooBar\"\n" "www-authenticate: Fake realm=\"FooBar\"\n" "www-authenticate: nonce=\"aaaaaaaaaa\"\n" "www-authenticate: Digest realm=\"DigestRealm\", nonce=\"aaaaaaaaaa\"\n", HttpAuth::AUTH_SCHEME_DIGEST, "DigestRealm", }, { // Handle an empty header correctly. "Y: Digest realm=\"X\", nonce=\"aaaaaaaaaa\"\n" "www-authenticate:\n", HttpAuth::AUTH_SCHEME_MAX, "", }, { // Choose Negotiate over NTLM on all platforms. // TODO(ahendrickson): This may be flaky on Linux and OSX as it // relies on being able to load one of the known .so files // for gssapi. "WWW-Authenticate: Negotiate\n" "WWW-Authenticate: NTLM\n", HttpAuth::AUTH_SCHEME_NEGOTIATE, "", } }; GURL origin("http://www.example.com"); std::set<HttpAuth::Scheme> disabled_schemes; MockAllowURLSecurityManager url_security_manager; scoped_ptr<HostResolver> host_resolver(new MockHostResolver()); scoped_ptr<HttpAuthHandlerRegistryFactory> http_auth_handler_factory( HttpAuthHandlerFactory::CreateDefault(host_resolver.get())); http_auth_handler_factory->SetURLSecurityManager( "negotiate", &url_security_manager); 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<HttpResponseHeaders> headers( HeadersFromResponseText(headers_with_status_line)); scoped_ptr<HttpAuthHandler> handler; HttpAuth::ChooseBestChallenge(http_auth_handler_factory.get(), headers.get(), HttpAuth::AUTH_SERVER, origin, disabled_schemes, BoundNetLog(), &handler); if (handler.get()) { EXPECT_EQ(tests[i].challenge_scheme, handler->auth_scheme()); EXPECT_STREQ(tests[i].challenge_realm, handler->realm().c_str()); } else { EXPECT_EQ(HttpAuth::AUTH_SCHEME_MAX, tests[i].challenge_scheme); EXPECT_STREQ("", tests[i].challenge_realm); } } } TEST(HttpAuthTest, HandleChallengeResponse) { std::string challenge_used; const char* const kMockChallenge = "HTTP/1.1 401 Unauthorized\n" "WWW-Authenticate: Mock token_here\n"; const char* const kBasicChallenge = "HTTP/1.1 401 Unauthorized\n" "WWW-Authenticate: Basic realm=\"happy\"\n"; const char* const kMissingChallenge = "HTTP/1.1 401 Unauthorized\n"; const char* const kEmptyChallenge = "HTTP/1.1 401 Unauthorized\n" "WWW-Authenticate: \n"; const char* const kBasicAndMockChallenges = "HTTP/1.1 401 Unauthorized\n" "WWW-Authenticate: Basic realm=\"happy\"\n" "WWW-Authenticate: Mock token_here\n"; const char* const kTwoMockChallenges = "HTTP/1.1 401 Unauthorized\n" "WWW-Authenticate: Mock token_a\n" "WWW-Authenticate: Mock token_b\n"; // Request based schemes should treat any new challenges as rejections of the // previous authentication attempt. (There is a slight exception for digest // authentication and the stale parameter, but that is covered in the // http_auth_handler_digest_unittests). EXPECT_EQ( HttpAuth::AUTHORIZATION_RESULT_REJECT, HandleChallengeResponse(false, kMockChallenge, &challenge_used)); EXPECT_EQ("Mock token_here", challenge_used); EXPECT_EQ( HttpAuth::AUTHORIZATION_RESULT_REJECT, HandleChallengeResponse(false, kBasicChallenge, &challenge_used)); EXPECT_EQ("", challenge_used); EXPECT_EQ( HttpAuth::AUTHORIZATION_RESULT_REJECT, HandleChallengeResponse(false, kMissingChallenge, &challenge_used)); EXPECT_EQ("", challenge_used); EXPECT_EQ( HttpAuth::AUTHORIZATION_RESULT_REJECT, HandleChallengeResponse(false, kEmptyChallenge, &challenge_used)); EXPECT_EQ("", challenge_used); EXPECT_EQ( HttpAuth::AUTHORIZATION_RESULT_REJECT, HandleChallengeResponse(false, kBasicAndMockChallenges, &challenge_used)); EXPECT_EQ("Mock token_here", challenge_used); EXPECT_EQ( HttpAuth::AUTHORIZATION_RESULT_REJECT, HandleChallengeResponse(false, kTwoMockChallenges, &challenge_used)); EXPECT_EQ("Mock token_a", challenge_used); // Connection based schemes will treat new auth challenges for the same scheme // as acceptance (and continuance) of the current approach. If there are // no auth challenges for the same scheme, the response will be treated as // a rejection. EXPECT_EQ( HttpAuth::AUTHORIZATION_RESULT_ACCEPT, HandleChallengeResponse(true, kMockChallenge, &challenge_used)); EXPECT_EQ("Mock token_here", challenge_used); EXPECT_EQ( HttpAuth::AUTHORIZATION_RESULT_REJECT, HandleChallengeResponse(true, kBasicChallenge, &challenge_used)); EXPECT_EQ("", challenge_used); EXPECT_EQ( HttpAuth::AUTHORIZATION_RESULT_REJECT, HandleChallengeResponse(true, kMissingChallenge, &challenge_used)); EXPECT_EQ("", challenge_used); EXPECT_EQ( HttpAuth::AUTHORIZATION_RESULT_REJECT, HandleChallengeResponse(true, kEmptyChallenge, &challenge_used)); EXPECT_EQ("", challenge_used); EXPECT_EQ( HttpAuth::AUTHORIZATION_RESULT_ACCEPT, HandleChallengeResponse(true, kBasicAndMockChallenges, &challenge_used)); EXPECT_EQ("Mock token_here", challenge_used); EXPECT_EQ( HttpAuth::AUTHORIZATION_RESULT_ACCEPT, HandleChallengeResponse(true, kTwoMockChallenges, &challenge_used)); EXPECT_EQ("Mock token_a", challenge_used); } TEST(HttpAuthTest, ChallengeTokenizer) { std::string challenge_str = "Basic realm=\"foobar\""; HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(), challenge_str.end()); HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs(); EXPECT_TRUE(parameters.valid()); EXPECT_EQ(std::string("Basic"), challenge.scheme()); EXPECT_TRUE(parameters.GetNext()); EXPECT_TRUE(parameters.valid()); EXPECT_EQ(std::string("realm"), parameters.name()); EXPECT_EQ(std::string("foobar"), parameters.value()); EXPECT_FALSE(parameters.GetNext()); } // Use a name=value property with no quote marks. TEST(HttpAuthTest, ChallengeTokenizerNoQuotes) { std::string challenge_str = "Basic realm=foobar@baz.com"; HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(), challenge_str.end()); HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs(); EXPECT_TRUE(parameters.valid()); EXPECT_EQ(std::string("Basic"), challenge.scheme()); EXPECT_TRUE(parameters.GetNext()); EXPECT_TRUE(parameters.valid()); EXPECT_EQ(std::string("realm"), parameters.name()); EXPECT_EQ(std::string("foobar@baz.com"), parameters.value()); EXPECT_FALSE(parameters.GetNext()); } // Use a name=value property with mismatching quote marks. TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotes) { std::string challenge_str = "Basic realm=\"foobar@baz.com"; HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(), challenge_str.end()); HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs(); EXPECT_TRUE(parameters.valid()); EXPECT_EQ(std::string("Basic"), challenge.scheme()); EXPECT_TRUE(parameters.GetNext()); EXPECT_TRUE(parameters.valid()); EXPECT_EQ(std::string("realm"), parameters.name()); EXPECT_EQ(std::string("foobar@baz.com"), parameters.value()); EXPECT_FALSE(parameters.GetNext()); } // Use a name= property without a value and with mismatching quote marks. TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotesNoValue) { std::string challenge_str = "Basic realm=\""; HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(), challenge_str.end()); HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs(); EXPECT_TRUE(parameters.valid()); EXPECT_EQ(std::string("Basic"), challenge.scheme()); EXPECT_TRUE(parameters.GetNext()); EXPECT_TRUE(parameters.valid()); EXPECT_EQ(std::string("realm"), parameters.name()); EXPECT_EQ(std::string(""), parameters.value()); EXPECT_FALSE(parameters.GetNext()); } // Use a name=value property with mismatching quote marks and spaces in the // value. TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotesSpaces) { std::string challenge_str = "Basic realm=\"foo bar"; HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(), challenge_str.end()); HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs(); EXPECT_TRUE(parameters.valid()); EXPECT_EQ(std::string("Basic"), challenge.scheme()); EXPECT_TRUE(parameters.GetNext()); EXPECT_TRUE(parameters.valid()); EXPECT_EQ(std::string("realm"), parameters.name()); EXPECT_EQ(std::string("foo bar"), parameters.value()); EXPECT_FALSE(parameters.GetNext()); } // Use multiple name=value properties with mismatching quote marks in the last // value. TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotesMultiple) { std::string challenge_str = "Digest qop=auth-int, algorithm=md5, realm=\"foo"; HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(), challenge_str.end()); HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs(); EXPECT_TRUE(parameters.valid()); EXPECT_EQ(std::string("Digest"), challenge.scheme()); EXPECT_TRUE(parameters.GetNext()); EXPECT_TRUE(parameters.valid()); EXPECT_EQ(std::string("qop"), parameters.name()); EXPECT_EQ(std::string("auth-int"), parameters.value()); EXPECT_TRUE(parameters.GetNext()); EXPECT_TRUE(parameters.valid()); EXPECT_EQ(std::string("algorithm"), parameters.name()); EXPECT_EQ(std::string("md5"), parameters.value()); EXPECT_TRUE(parameters.GetNext()); EXPECT_TRUE(parameters.valid()); EXPECT_EQ(std::string("realm"), parameters.name()); EXPECT_EQ(std::string("foo"), parameters.value()); EXPECT_FALSE(parameters.GetNext()); } // Use a name= property which has no value. TEST(HttpAuthTest, ChallengeTokenizerNoValue) { std::string challenge_str = "Digest qop="; HttpAuth::ChallengeTokenizer challenge( challenge_str.begin(), challenge_str.end()); HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs(); EXPECT_TRUE(parameters.valid()); EXPECT_EQ(std::string("Digest"), challenge.scheme()); EXPECT_FALSE(parameters.GetNext()); EXPECT_FALSE(parameters.valid()); } // Specify multiple properties, comma separated. TEST(HttpAuthTest, ChallengeTokenizerMultiple) { std::string challenge_str = "Digest algorithm=md5, realm=\"Oblivion\", qop=auth-int"; HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(), challenge_str.end()); HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs(); EXPECT_TRUE(parameters.valid()); EXPECT_EQ(std::string("Digest"), challenge.scheme()); EXPECT_TRUE(parameters.GetNext()); EXPECT_TRUE(parameters.valid()); EXPECT_EQ(std::string("algorithm"), parameters.name()); EXPECT_EQ(std::string("md5"), parameters.value()); EXPECT_TRUE(parameters.GetNext()); EXPECT_TRUE(parameters.valid()); EXPECT_EQ(std::string("realm"), parameters.name()); EXPECT_EQ(std::string("Oblivion"), parameters.value()); EXPECT_TRUE(parameters.GetNext()); EXPECT_TRUE(parameters.valid()); EXPECT_EQ(std::string("qop"), parameters.name()); EXPECT_EQ(std::string("auth-int"), parameters.value()); EXPECT_FALSE(parameters.GetNext()); EXPECT_TRUE(parameters.valid()); } // Use a challenge which has no property. TEST(HttpAuthTest, ChallengeTokenizerNoProperty) { std::string challenge_str = "NTLM"; HttpAuth::ChallengeTokenizer challenge( challenge_str.begin(), challenge_str.end()); HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs(); EXPECT_TRUE(parameters.valid()); EXPECT_EQ(std::string("NTLM"), challenge.scheme()); EXPECT_FALSE(parameters.GetNext()); } // Use a challenge with Base64 encoded token. TEST(HttpAuthTest, ChallengeTokenizerBase64) { std::string challenge_str = "NTLM SGVsbG8sIFdvcmxkCg==="; HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(), challenge_str.end()); EXPECT_EQ(std::string("NTLM"), challenge.scheme()); // Notice the two equal statements below due to padding removal. EXPECT_EQ(std::string("SGVsbG8sIFdvcmxkCg=="), challenge.base64_param()); } TEST(HttpAuthTest, GetChallengeHeaderName) { std::string name; name = HttpAuth::GetChallengeHeaderName(HttpAuth::AUTH_SERVER); EXPECT_STREQ("WWW-Authenticate", name.c_str()); name = HttpAuth::GetChallengeHeaderName(HttpAuth::AUTH_PROXY); EXPECT_STREQ("Proxy-Authenticate", name.c_str()); } TEST(HttpAuthTest, GetAuthorizationHeaderName) { std::string name; name = HttpAuth::GetAuthorizationHeaderName(HttpAuth::AUTH_SERVER); EXPECT_STREQ("Authorization", name.c_str()); name = HttpAuth::GetAuthorizationHeaderName(HttpAuth::AUTH_PROXY); EXPECT_STREQ("Proxy-Authorization", name.c_str()); } } // namespace net