| // 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 "net/http/http_auth.h" |
| |
| #include <memory> |
| #include <set> |
| #include <string> |
| |
| #include "base/memory/ref_counted.h" |
| #include "base/strings/string_util.h" |
| #include "build/build_config.h" |
| #include "net/base/net_errors.h" |
| #include "net/dns/mock_host_resolver.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 "net/log/net_log_with_source.h" |
| #include "net/net_buildflags.h" |
| #include "net/ssl/ssl_info.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| std::unique_ptr<HttpAuthHandlerMock> CreateMockHandler(bool connection_based) { |
| std::unique_ptr<HttpAuthHandlerMock> auth_handler = |
| std::make_unique<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"); |
| SSLInfo null_ssl_info; |
| EXPECT_TRUE(auth_handler->InitFromChallenge(&challenge, HttpAuth::AUTH_SERVER, |
| null_ssl_info, origin, |
| NetLogWithSource())); |
| return auth_handler; |
| } |
| |
| scoped_refptr<HttpResponseHeaders> HeadersFromResponseText( |
| const std::string& response) { |
| return scoped_refptr<HttpResponseHeaders>(new HttpResponseHeaders( |
| HttpUtil::AssembleRawHeaders(response.c_str(), response.length()))); |
| } |
| |
| HttpAuth::AuthorizationResult HandleChallengeResponse( |
| bool connection_based, |
| const std::string& headers_text, |
| std::string* challenge_used) { |
| std::unique_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, |
| 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 BUILDFLAG(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 // BUILDFLAG(USE_KERBEROS) |
| "", |
| }}; |
| GURL origin("http://www.example.com"); |
| std::set<HttpAuth::Scheme> disabled_schemes; |
| MockAllowHttpAuthPreferences http_auth_preferences; |
| std::unique_ptr<HostResolver> host_resolver(new MockHostResolver()); |
| std::unique_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); |
| |
| SSLInfo null_ssl_info; |
| std::unique_ptr<HttpAuthHandler> handler; |
| HttpAuth::ChooseBestChallenge(http_auth_handler_factory.get(), *headers, |
| null_ssl_info, HttpAuth::AUTH_SERVER, origin, |
| disabled_schemes, NetLogWithSource(), |
| &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 |