1 // Copyright 2011 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/http/http_auth.h"
6
7 #include <memory>
8 #include <set>
9 #include <string>
10
11 #include "base/memory/ref_counted.h"
12 #include "base/strings/string_util.h"
13 #include "build/build_config.h"
14 #include "net/base/net_errors.h"
15 #include "net/base/network_isolation_key.h"
16 #include "net/dns/mock_host_resolver.h"
17 #include "net/http/http_auth_challenge_tokenizer.h"
18 #include "net/http/http_auth_filter.h"
19 #include "net/http/http_auth_handler.h"
20 #include "net/http/http_auth_handler_factory.h"
21 #include "net/http/http_auth_handler_mock.h"
22 #include "net/http/http_auth_scheme.h"
23 #include "net/http/http_response_headers.h"
24 #include "net/http/http_util.h"
25 #include "net/http/mock_allow_http_auth_preferences.h"
26 #include "net/log/net_log_with_source.h"
27 #include "net/net_buildflags.h"
28 #include "net/ssl/ssl_info.h"
29 #include "testing/gtest/include/gtest/gtest.h"
30 #include "url/gurl.h"
31 #include "url/scheme_host_port.h"
32
33 namespace net {
34
35 namespace {
36
CreateMockHandler(bool connection_based)37 std::unique_ptr<HttpAuthHandlerMock> CreateMockHandler(bool connection_based) {
38 std::unique_ptr<HttpAuthHandlerMock> auth_handler =
39 std::make_unique<HttpAuthHandlerMock>();
40 auth_handler->set_connection_based(connection_based);
41 std::string challenge_text = "Basic";
42 HttpAuthChallengeTokenizer challenge(challenge_text.begin(),
43 challenge_text.end());
44 url::SchemeHostPort scheme_host_port(GURL("https://www.example.com"));
45 SSLInfo null_ssl_info;
46 EXPECT_TRUE(auth_handler->InitFromChallenge(
47 &challenge, HttpAuth::AUTH_SERVER, null_ssl_info,
48 NetworkAnonymizationKey(), scheme_host_port, NetLogWithSource()));
49 return auth_handler;
50 }
51
HeadersFromResponseText(const std::string & response)52 scoped_refptr<HttpResponseHeaders> HeadersFromResponseText(
53 const std::string& response) {
54 return base::MakeRefCounted<HttpResponseHeaders>(
55 HttpUtil::AssembleRawHeaders(response));
56 }
57
HandleChallengeResponse(bool connection_based,const std::string & headers_text,std::string * challenge_used)58 HttpAuth::AuthorizationResult HandleChallengeResponse(
59 bool connection_based,
60 const std::string& headers_text,
61 std::string* challenge_used) {
62 std::unique_ptr<HttpAuthHandlerMock> mock_handler =
63 CreateMockHandler(connection_based);
64 std::set<HttpAuth::Scheme> disabled_schemes;
65 scoped_refptr<HttpResponseHeaders> headers =
66 HeadersFromResponseText(headers_text);
67 return HttpAuth::HandleChallengeResponse(mock_handler.get(), *headers,
68 HttpAuth::AUTH_SERVER,
69 disabled_schemes, challenge_used);
70 }
71
72 } // namespace
73
TEST(HttpAuthTest,ChooseBestChallenge)74 TEST(HttpAuthTest, ChooseBestChallenge) {
75 static const struct {
76 const char* headers;
77 HttpAuth::Scheme challenge_scheme;
78 const char* challenge_realm;
79 } tests[] = {
80 {
81 // Basic is the only challenge type, pick it.
82 "Y: Digest realm=\"X\", nonce=\"aaaaaaaaaa\"\n"
83 "www-authenticate: Basic realm=\"BasicRealm\"\n",
84
85 HttpAuth::AUTH_SCHEME_BASIC,
86 "BasicRealm",
87 },
88 {
89 // Fake is the only challenge type, but it is unsupported.
90 "Y: Digest realm=\"FooBar\", nonce=\"aaaaaaaaaa\"\n"
91 "www-authenticate: Fake realm=\"FooBar\"\n",
92
93 HttpAuth::AUTH_SCHEME_MAX,
94 "",
95 },
96 {
97 // Pick Digest over Basic.
98 "www-authenticate: Basic realm=\"FooBar\"\n"
99 "www-authenticate: Fake realm=\"FooBar\"\n"
100 "www-authenticate: nonce=\"aaaaaaaaaa\"\n"
101 "www-authenticate: Digest realm=\"DigestRealm\", "
102 "nonce=\"aaaaaaaaaa\"\n",
103
104 HttpAuth::AUTH_SCHEME_DIGEST,
105 "DigestRealm",
106 },
107 {
108 // Handle an empty header correctly.
109 "Y: Digest realm=\"X\", nonce=\"aaaaaaaaaa\"\n"
110 "www-authenticate:\n",
111
112 HttpAuth::AUTH_SCHEME_MAX,
113 "",
114 },
115 {
116 "WWW-Authenticate: Negotiate\n"
117 "WWW-Authenticate: NTLM\n",
118
119 #if BUILDFLAG(USE_KERBEROS) && !BUILDFLAG(IS_ANDROID)
120 // Choose Negotiate over NTLM on all platforms.
121 // TODO(ahendrickson): This may be flaky on Linux and OSX as
122 // it relies on being able to load one of the known .so files
123 // for gssapi.
124 HttpAuth::AUTH_SCHEME_NEGOTIATE,
125 #else
126 // On systems that don't use Kerberos fall back to NTLM.
127 HttpAuth::AUTH_SCHEME_NTLM,
128 #endif // BUILDFLAG(USE_KERBEROS)
129 "",
130 },
131 };
132 url::SchemeHostPort scheme_host_port(GURL("http://www.example.com"));
133 std::set<HttpAuth::Scheme> disabled_schemes;
134 MockAllowHttpAuthPreferences http_auth_preferences;
135 auto host_resolver = std::make_unique<MockHostResolver>();
136 std::unique_ptr<HttpAuthHandlerRegistryFactory> http_auth_handler_factory(
137 HttpAuthHandlerFactory::CreateDefault());
138 http_auth_handler_factory->SetHttpAuthPreferences(kNegotiateAuthScheme,
139 &http_auth_preferences);
140
141 for (const auto& test : tests) {
142 // Make a HttpResponseHeaders object.
143 std::string headers_with_status_line("HTTP/1.1 401 Unauthorized\n");
144 headers_with_status_line += test.headers;
145 scoped_refptr<HttpResponseHeaders> headers =
146 HeadersFromResponseText(headers_with_status_line);
147
148 SSLInfo null_ssl_info;
149 std::unique_ptr<HttpAuthHandler> handler;
150 HttpAuth::ChooseBestChallenge(
151 http_auth_handler_factory.get(), *headers, null_ssl_info,
152 NetworkAnonymizationKey(), HttpAuth::AUTH_SERVER, scheme_host_port,
153 disabled_schemes, NetLogWithSource(), host_resolver.get(), &handler);
154
155 if (handler.get()) {
156 EXPECT_EQ(test.challenge_scheme, handler->auth_scheme());
157 EXPECT_STREQ(test.challenge_realm, handler->realm().c_str());
158 } else {
159 EXPECT_EQ(HttpAuth::AUTH_SCHEME_MAX, test.challenge_scheme);
160 EXPECT_STREQ("", test.challenge_realm);
161 }
162 }
163 }
164
TEST(HttpAuthTest,HandleChallengeResponse)165 TEST(HttpAuthTest, HandleChallengeResponse) {
166 std::string challenge_used;
167 const char* const kMockChallenge =
168 "HTTP/1.1 401 Unauthorized\n"
169 "WWW-Authenticate: Mock token_here\n";
170 const char* const kBasicChallenge =
171 "HTTP/1.1 401 Unauthorized\n"
172 "WWW-Authenticate: Basic realm=\"happy\"\n";
173 const char* const kMissingChallenge =
174 "HTTP/1.1 401 Unauthorized\n";
175 const char* const kEmptyChallenge =
176 "HTTP/1.1 401 Unauthorized\n"
177 "WWW-Authenticate: \n";
178 const char* const kBasicAndMockChallenges =
179 "HTTP/1.1 401 Unauthorized\n"
180 "WWW-Authenticate: Basic realm=\"happy\"\n"
181 "WWW-Authenticate: Mock token_here\n";
182 const char* const kTwoMockChallenges =
183 "HTTP/1.1 401 Unauthorized\n"
184 "WWW-Authenticate: Mock token_a\n"
185 "WWW-Authenticate: Mock token_b\n";
186
187 // Request based schemes should treat any new challenges as rejections of the
188 // previous authentication attempt. (There is a slight exception for digest
189 // authentication and the stale parameter, but that is covered in the
190 // http_auth_handler_digest_unittests).
191 EXPECT_EQ(
192 HttpAuth::AUTHORIZATION_RESULT_REJECT,
193 HandleChallengeResponse(false, kMockChallenge, &challenge_used));
194 EXPECT_EQ("Mock token_here", challenge_used);
195
196 EXPECT_EQ(
197 HttpAuth::AUTHORIZATION_RESULT_REJECT,
198 HandleChallengeResponse(false, kBasicChallenge, &challenge_used));
199 EXPECT_EQ("", challenge_used);
200
201 EXPECT_EQ(
202 HttpAuth::AUTHORIZATION_RESULT_REJECT,
203 HandleChallengeResponse(false, kMissingChallenge, &challenge_used));
204 EXPECT_EQ("", challenge_used);
205
206 EXPECT_EQ(
207 HttpAuth::AUTHORIZATION_RESULT_REJECT,
208 HandleChallengeResponse(false, kEmptyChallenge, &challenge_used));
209 EXPECT_EQ("", challenge_used);
210
211 EXPECT_EQ(
212 HttpAuth::AUTHORIZATION_RESULT_REJECT,
213 HandleChallengeResponse(false, kBasicAndMockChallenges, &challenge_used));
214 EXPECT_EQ("Mock token_here", challenge_used);
215
216 EXPECT_EQ(
217 HttpAuth::AUTHORIZATION_RESULT_REJECT,
218 HandleChallengeResponse(false, kTwoMockChallenges, &challenge_used));
219 EXPECT_EQ("Mock token_a", challenge_used);
220
221 // Connection based schemes will treat new auth challenges for the same scheme
222 // as acceptance (and continuance) of the current approach. If there are
223 // no auth challenges for the same scheme, the response will be treated as
224 // a rejection.
225 EXPECT_EQ(
226 HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
227 HandleChallengeResponse(true, kMockChallenge, &challenge_used));
228 EXPECT_EQ("Mock token_here", challenge_used);
229
230 EXPECT_EQ(
231 HttpAuth::AUTHORIZATION_RESULT_REJECT,
232 HandleChallengeResponse(true, kBasicChallenge, &challenge_used));
233 EXPECT_EQ("", challenge_used);
234
235 EXPECT_EQ(
236 HttpAuth::AUTHORIZATION_RESULT_REJECT,
237 HandleChallengeResponse(true, kMissingChallenge, &challenge_used));
238 EXPECT_EQ("", challenge_used);
239
240 EXPECT_EQ(
241 HttpAuth::AUTHORIZATION_RESULT_REJECT,
242 HandleChallengeResponse(true, kEmptyChallenge, &challenge_used));
243 EXPECT_EQ("", challenge_used);
244
245 EXPECT_EQ(
246 HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
247 HandleChallengeResponse(true, kBasicAndMockChallenges, &challenge_used));
248 EXPECT_EQ("Mock token_here", challenge_used);
249
250 EXPECT_EQ(
251 HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
252 HandleChallengeResponse(true, kTwoMockChallenges, &challenge_used));
253 EXPECT_EQ("Mock token_a", challenge_used);
254 }
255
TEST(HttpAuthTest,GetChallengeHeaderName)256 TEST(HttpAuthTest, GetChallengeHeaderName) {
257 std::string name;
258
259 name = HttpAuth::GetChallengeHeaderName(HttpAuth::AUTH_SERVER);
260 EXPECT_STREQ("WWW-Authenticate", name.c_str());
261
262 name = HttpAuth::GetChallengeHeaderName(HttpAuth::AUTH_PROXY);
263 EXPECT_STREQ("Proxy-Authenticate", name.c_str());
264 }
265
TEST(HttpAuthTest,GetAuthorizationHeaderName)266 TEST(HttpAuthTest, GetAuthorizationHeaderName) {
267 std::string name;
268
269 name = HttpAuth::GetAuthorizationHeaderName(HttpAuth::AUTH_SERVER);
270 EXPECT_STREQ("Authorization", name.c_str());
271
272 name = HttpAuth::GetAuthorizationHeaderName(HttpAuth::AUTH_PROXY);
273 EXPECT_STREQ("Proxy-Authorization", name.c_str());
274 }
275
276 } // namespace net
277