|  | // 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_challenge_tokenizer.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_auth_scheme.h" | 
|  | #include "net/http/http_response_headers.h" | 
|  | #include "net/http/http_util.h" | 
|  | #include "net/http/mock_allow_http_auth_preferences.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"; | 
|  | HttpAuthChallengeTokenizer 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) && !defined(OS_ANDROID) | 
|  | // 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; | 
|  | MockAllowHttpAuthPreferences http_auth_preferences; | 
|  | scoped_ptr<HostResolver> host_resolver(new MockHostResolver()); | 
|  | scoped_ptr<HttpAuthHandlerRegistryFactory> http_auth_handler_factory( | 
|  | HttpAuthHandlerFactory::CreateDefault(host_resolver.get())); | 
|  | http_auth_handler_factory->SetHttpAuthPreferences(kNegotiateAuthScheme, | 
|  | &http_auth_preferences); | 
|  |  | 
|  | for (size_t i = 0; i < arraysize(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, 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 |