blob: 58f9f04d271143fe2188ee344b2ce8f0d5e0d991 [file] [log] [blame]
// 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