diff options
Diffstat (limited to 'chromium/net/http/http_auth_unittest.cc')
-rw-r--r-- | chromium/net/http/http_auth_unittest.cc | 435 |
1 files changed, 435 insertions, 0 deletions
diff --git a/chromium/net/http/http_auth_unittest.cc b/chromium/net/http/http_auth_unittest.cc new file mode 100644 index 00000000000..6f1471d472a --- /dev/null +++ b/chromium/net/http/http_auth_unittest.cc @@ -0,0 +1,435 @@ +// 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/strings/string_util.h" +#include "net/base/net_errors.h" +#include "net/dns/mock_host_resolver.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, + "", + }, + { + "WWW-Authenticate: Negotiate\n" + "WWW-Authenticate: NTLM\n", + +#if defined(USE_KERBEROS) + // 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. + HttpAuth::AUTH_SCHEME_NEGOTIATE, +#else + // On systems that don't use Kerberos fall back to NTLM. + HttpAuth::AUTH_SCHEME_NTLM, +#endif // defined(USE_KERBEROS) + "", + } + }; + 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 |