1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
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 <set>
6 #include <string>
7
8 #include "base/memory/ref_counted.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/string_util.h"
11 #include "net/base/mock_host_resolver.h"
12 #include "net/base/net_errors.h"
13 #include "net/http/http_auth.h"
14 #include "net/http/http_auth_filter.h"
15 #include "net/http/http_auth_handler.h"
16 #include "net/http/http_auth_handler_factory.h"
17 #include "net/http/http_auth_handler_mock.h"
18 #include "net/http/http_response_headers.h"
19 #include "net/http/http_util.h"
20 #include "net/http/mock_allow_url_security_manager.h"
21 #include "testing/gtest/include/gtest/gtest.h"
22
23 namespace net {
24
25 namespace {
26
CreateMockHandler(bool connection_based)27 HttpAuthHandlerMock* CreateMockHandler(bool connection_based) {
28 HttpAuthHandlerMock* auth_handler = new HttpAuthHandlerMock();
29 auth_handler->set_connection_based(connection_based);
30 std::string challenge_text = "Basic";
31 HttpAuth::ChallengeTokenizer challenge(challenge_text.begin(),
32 challenge_text.end());
33 GURL origin("www.example.com");
34 EXPECT_TRUE(auth_handler->InitFromChallenge(&challenge,
35 HttpAuth::AUTH_SERVER,
36 origin,
37 BoundNetLog()));
38 return auth_handler;
39 }
40
HeadersFromResponseText(const std::string & response)41 HttpResponseHeaders* HeadersFromResponseText(const std::string& response) {
42 return new HttpResponseHeaders(
43 HttpUtil::AssembleRawHeaders(response.c_str(), response.length()));
44 }
45
HandleChallengeResponse(bool connection_based,const std::string & headers_text,std::string * challenge_used)46 HttpAuth::AuthorizationResult HandleChallengeResponse(
47 bool connection_based,
48 const std::string& headers_text,
49 std::string* challenge_used) {
50 scoped_ptr<HttpAuthHandlerMock> mock_handler(
51 CreateMockHandler(connection_based));
52 std::set<HttpAuth::Scheme> disabled_schemes;
53 scoped_refptr<HttpResponseHeaders> headers(
54 HeadersFromResponseText(headers_text));
55 return HttpAuth::HandleChallengeResponse(
56 mock_handler.get(),
57 headers.get(),
58 HttpAuth::AUTH_SERVER,
59 disabled_schemes,
60 challenge_used);
61 }
62
63 } // namespace
64
TEST(HttpAuthTest,ChooseBestChallenge)65 TEST(HttpAuthTest, ChooseBestChallenge) {
66 static const struct {
67 const char* headers;
68 HttpAuth::Scheme challenge_scheme;
69 const char* challenge_realm;
70 } tests[] = {
71 {
72 // Basic is the only challenge type, pick it.
73 "Y: Digest realm=\"X\", nonce=\"aaaaaaaaaa\"\n"
74 "www-authenticate: Basic realm=\"BasicRealm\"\n",
75
76 HttpAuth::AUTH_SCHEME_BASIC,
77 "BasicRealm",
78 },
79 {
80 // Fake is the only challenge type, but it is unsupported.
81 "Y: Digest realm=\"FooBar\", nonce=\"aaaaaaaaaa\"\n"
82 "www-authenticate: Fake realm=\"FooBar\"\n",
83
84 HttpAuth::AUTH_SCHEME_MAX,
85 "",
86 },
87 {
88 // Pick Digest over Basic.
89 "www-authenticate: Basic realm=\"FooBar\"\n"
90 "www-authenticate: Fake realm=\"FooBar\"\n"
91 "www-authenticate: nonce=\"aaaaaaaaaa\"\n"
92 "www-authenticate: Digest realm=\"DigestRealm\", nonce=\"aaaaaaaaaa\"\n",
93
94 HttpAuth::AUTH_SCHEME_DIGEST,
95 "DigestRealm",
96 },
97 {
98 // Handle an empty header correctly.
99 "Y: Digest realm=\"X\", nonce=\"aaaaaaaaaa\"\n"
100 "www-authenticate:\n",
101
102 HttpAuth::AUTH_SCHEME_MAX,
103 "",
104 },
105 {
106 // Choose Negotiate over NTLM on all platforms.
107 // TODO(ahendrickson): This may be flaky on Linux and OSX as it
108 // relies on being able to load one of the known .so files
109 // for gssapi.
110 "WWW-Authenticate: Negotiate\n"
111 "WWW-Authenticate: NTLM\n",
112
113 HttpAuth::AUTH_SCHEME_NEGOTIATE,
114 "",
115 }
116 };
117 GURL origin("http://www.example.com");
118 std::set<HttpAuth::Scheme> disabled_schemes;
119 MockAllowURLSecurityManager url_security_manager;
120 scoped_ptr<HostResolver> host_resolver(new MockHostResolver());
121 scoped_ptr<HttpAuthHandlerRegistryFactory> http_auth_handler_factory(
122 HttpAuthHandlerFactory::CreateDefault(host_resolver.get()));
123 http_auth_handler_factory->SetURLSecurityManager(
124 "negotiate", &url_security_manager);
125
126 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
127 // Make a HttpResponseHeaders object.
128 std::string headers_with_status_line("HTTP/1.1 401 Unauthorized\n");
129 headers_with_status_line += tests[i].headers;
130 scoped_refptr<HttpResponseHeaders> headers(
131 HeadersFromResponseText(headers_with_status_line));
132
133 scoped_ptr<HttpAuthHandler> handler;
134 HttpAuth::ChooseBestChallenge(http_auth_handler_factory.get(),
135 headers.get(),
136 HttpAuth::AUTH_SERVER,
137 origin,
138 disabled_schemes,
139 BoundNetLog(),
140 &handler);
141
142 if (handler.get()) {
143 EXPECT_EQ(tests[i].challenge_scheme, handler->auth_scheme());
144 EXPECT_STREQ(tests[i].challenge_realm, handler->realm().c_str());
145 } else {
146 EXPECT_EQ(HttpAuth::AUTH_SCHEME_MAX, tests[i].challenge_scheme);
147 EXPECT_STREQ("", tests[i].challenge_realm);
148 }
149 }
150 }
151
TEST(HttpAuthTest,HandleChallengeResponse)152 TEST(HttpAuthTest, HandleChallengeResponse) {
153 std::string challenge_used;
154 const char* const kMockChallenge =
155 "HTTP/1.1 401 Unauthorized\n"
156 "WWW-Authenticate: Mock token_here\n";
157 const char* const kBasicChallenge =
158 "HTTP/1.1 401 Unauthorized\n"
159 "WWW-Authenticate: Basic realm=\"happy\"\n";
160 const char* const kMissingChallenge =
161 "HTTP/1.1 401 Unauthorized\n";
162 const char* const kEmptyChallenge =
163 "HTTP/1.1 401 Unauthorized\n"
164 "WWW-Authenticate: \n";
165 const char* const kBasicAndMockChallenges =
166 "HTTP/1.1 401 Unauthorized\n"
167 "WWW-Authenticate: Basic realm=\"happy\"\n"
168 "WWW-Authenticate: Mock token_here\n";
169 const char* const kTwoMockChallenges =
170 "HTTP/1.1 401 Unauthorized\n"
171 "WWW-Authenticate: Mock token_a\n"
172 "WWW-Authenticate: Mock token_b\n";
173
174 // Request based schemes should treat any new challenges as rejections of the
175 // previous authentication attempt. (There is a slight exception for digest
176 // authentication and the stale parameter, but that is covered in the
177 // http_auth_handler_digest_unittests).
178 EXPECT_EQ(
179 HttpAuth::AUTHORIZATION_RESULT_REJECT,
180 HandleChallengeResponse(false, kMockChallenge, &challenge_used));
181 EXPECT_EQ("Mock token_here", challenge_used);
182
183 EXPECT_EQ(
184 HttpAuth::AUTHORIZATION_RESULT_REJECT,
185 HandleChallengeResponse(false, kBasicChallenge, &challenge_used));
186 EXPECT_EQ("", challenge_used);
187
188 EXPECT_EQ(
189 HttpAuth::AUTHORIZATION_RESULT_REJECT,
190 HandleChallengeResponse(false, kMissingChallenge, &challenge_used));
191 EXPECT_EQ("", challenge_used);
192
193 EXPECT_EQ(
194 HttpAuth::AUTHORIZATION_RESULT_REJECT,
195 HandleChallengeResponse(false, kEmptyChallenge, &challenge_used));
196 EXPECT_EQ("", challenge_used);
197
198 EXPECT_EQ(
199 HttpAuth::AUTHORIZATION_RESULT_REJECT,
200 HandleChallengeResponse(false, kBasicAndMockChallenges, &challenge_used));
201 EXPECT_EQ("Mock token_here", challenge_used);
202
203 EXPECT_EQ(
204 HttpAuth::AUTHORIZATION_RESULT_REJECT,
205 HandleChallengeResponse(false, kTwoMockChallenges, &challenge_used));
206 EXPECT_EQ("Mock token_a", challenge_used);
207
208 // Connection based schemes will treat new auth challenges for the same scheme
209 // as acceptance (and continuance) of the current approach. If there are
210 // no auth challenges for the same scheme, the response will be treated as
211 // a rejection.
212 EXPECT_EQ(
213 HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
214 HandleChallengeResponse(true, kMockChallenge, &challenge_used));
215 EXPECT_EQ("Mock token_here", challenge_used);
216
217 EXPECT_EQ(
218 HttpAuth::AUTHORIZATION_RESULT_REJECT,
219 HandleChallengeResponse(true, kBasicChallenge, &challenge_used));
220 EXPECT_EQ("", challenge_used);
221
222 EXPECT_EQ(
223 HttpAuth::AUTHORIZATION_RESULT_REJECT,
224 HandleChallengeResponse(true, kMissingChallenge, &challenge_used));
225 EXPECT_EQ("", challenge_used);
226
227 EXPECT_EQ(
228 HttpAuth::AUTHORIZATION_RESULT_REJECT,
229 HandleChallengeResponse(true, kEmptyChallenge, &challenge_used));
230 EXPECT_EQ("", challenge_used);
231
232 EXPECT_EQ(
233 HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
234 HandleChallengeResponse(true, kBasicAndMockChallenges, &challenge_used));
235 EXPECT_EQ("Mock token_here", challenge_used);
236
237 EXPECT_EQ(
238 HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
239 HandleChallengeResponse(true, kTwoMockChallenges, &challenge_used));
240 EXPECT_EQ("Mock token_a", challenge_used);
241 }
242
TEST(HttpAuthTest,ChallengeTokenizer)243 TEST(HttpAuthTest, ChallengeTokenizer) {
244 std::string challenge_str = "Basic realm=\"foobar\"";
245 HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
246 challenge_str.end());
247 HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
248
249 EXPECT_TRUE(parameters.valid());
250 EXPECT_EQ(std::string("Basic"), challenge.scheme());
251 EXPECT_TRUE(parameters.GetNext());
252 EXPECT_TRUE(parameters.valid());
253 EXPECT_EQ(std::string("realm"), parameters.name());
254 EXPECT_EQ(std::string("foobar"), parameters.value());
255 EXPECT_FALSE(parameters.GetNext());
256 }
257
258 // Use a name=value property with no quote marks.
TEST(HttpAuthTest,ChallengeTokenizerNoQuotes)259 TEST(HttpAuthTest, ChallengeTokenizerNoQuotes) {
260 std::string challenge_str = "Basic realm=foobar@baz.com";
261 HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
262 challenge_str.end());
263 HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
264
265 EXPECT_TRUE(parameters.valid());
266 EXPECT_EQ(std::string("Basic"), challenge.scheme());
267 EXPECT_TRUE(parameters.GetNext());
268 EXPECT_TRUE(parameters.valid());
269 EXPECT_EQ(std::string("realm"), parameters.name());
270 EXPECT_EQ(std::string("foobar@baz.com"), parameters.value());
271 EXPECT_FALSE(parameters.GetNext());
272 }
273
274 // Use a name=value property with mismatching quote marks.
TEST(HttpAuthTest,ChallengeTokenizerMismatchedQuotes)275 TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotes) {
276 std::string challenge_str = "Basic realm=\"foobar@baz.com";
277 HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
278 challenge_str.end());
279 HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
280
281 EXPECT_TRUE(parameters.valid());
282 EXPECT_EQ(std::string("Basic"), challenge.scheme());
283 EXPECT_TRUE(parameters.GetNext());
284 EXPECT_TRUE(parameters.valid());
285 EXPECT_EQ(std::string("realm"), parameters.name());
286 EXPECT_EQ(std::string("foobar@baz.com"), parameters.value());
287 EXPECT_FALSE(parameters.GetNext());
288 }
289
290 // Use a name= property without a value and with mismatching quote marks.
TEST(HttpAuthTest,ChallengeTokenizerMismatchedQuotesNoValue)291 TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotesNoValue) {
292 std::string challenge_str = "Basic realm=\"";
293 HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
294 challenge_str.end());
295 HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
296
297 EXPECT_TRUE(parameters.valid());
298 EXPECT_EQ(std::string("Basic"), challenge.scheme());
299 EXPECT_TRUE(parameters.GetNext());
300 EXPECT_TRUE(parameters.valid());
301 EXPECT_EQ(std::string("realm"), parameters.name());
302 EXPECT_EQ(std::string(""), parameters.value());
303 EXPECT_FALSE(parameters.GetNext());
304 }
305
306 // Use a name=value property with mismatching quote marks and spaces in the
307 // value.
TEST(HttpAuthTest,ChallengeTokenizerMismatchedQuotesSpaces)308 TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotesSpaces) {
309 std::string challenge_str = "Basic realm=\"foo bar";
310 HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
311 challenge_str.end());
312 HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
313
314 EXPECT_TRUE(parameters.valid());
315 EXPECT_EQ(std::string("Basic"), challenge.scheme());
316 EXPECT_TRUE(parameters.GetNext());
317 EXPECT_TRUE(parameters.valid());
318 EXPECT_EQ(std::string("realm"), parameters.name());
319 EXPECT_EQ(std::string("foo bar"), parameters.value());
320 EXPECT_FALSE(parameters.GetNext());
321 }
322
323 // Use multiple name=value properties with mismatching quote marks in the last
324 // value.
TEST(HttpAuthTest,ChallengeTokenizerMismatchedQuotesMultiple)325 TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotesMultiple) {
326 std::string challenge_str = "Digest qop=auth-int, algorithm=md5, realm=\"foo";
327 HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
328 challenge_str.end());
329 HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
330
331 EXPECT_TRUE(parameters.valid());
332 EXPECT_EQ(std::string("Digest"), challenge.scheme());
333 EXPECT_TRUE(parameters.GetNext());
334 EXPECT_TRUE(parameters.valid());
335 EXPECT_EQ(std::string("qop"), parameters.name());
336 EXPECT_EQ(std::string("auth-int"), parameters.value());
337 EXPECT_TRUE(parameters.GetNext());
338 EXPECT_TRUE(parameters.valid());
339 EXPECT_EQ(std::string("algorithm"), parameters.name());
340 EXPECT_EQ(std::string("md5"), parameters.value());
341 EXPECT_TRUE(parameters.GetNext());
342 EXPECT_TRUE(parameters.valid());
343 EXPECT_EQ(std::string("realm"), parameters.name());
344 EXPECT_EQ(std::string("foo"), parameters.value());
345 EXPECT_FALSE(parameters.GetNext());
346 }
347
348 // Use a name= property which has no value.
TEST(HttpAuthTest,ChallengeTokenizerNoValue)349 TEST(HttpAuthTest, ChallengeTokenizerNoValue) {
350 std::string challenge_str = "Digest qop=";
351 HttpAuth::ChallengeTokenizer challenge(
352 challenge_str.begin(), challenge_str.end());
353 HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
354
355 EXPECT_TRUE(parameters.valid());
356 EXPECT_EQ(std::string("Digest"), challenge.scheme());
357 EXPECT_FALSE(parameters.GetNext());
358 EXPECT_FALSE(parameters.valid());
359 }
360
361 // Specify multiple properties, comma separated.
TEST(HttpAuthTest,ChallengeTokenizerMultiple)362 TEST(HttpAuthTest, ChallengeTokenizerMultiple) {
363 std::string challenge_str =
364 "Digest algorithm=md5, realm=\"Oblivion\", qop=auth-int";
365 HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
366 challenge_str.end());
367 HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
368
369 EXPECT_TRUE(parameters.valid());
370 EXPECT_EQ(std::string("Digest"), challenge.scheme());
371 EXPECT_TRUE(parameters.GetNext());
372 EXPECT_TRUE(parameters.valid());
373 EXPECT_EQ(std::string("algorithm"), parameters.name());
374 EXPECT_EQ(std::string("md5"), parameters.value());
375 EXPECT_TRUE(parameters.GetNext());
376 EXPECT_TRUE(parameters.valid());
377 EXPECT_EQ(std::string("realm"), parameters.name());
378 EXPECT_EQ(std::string("Oblivion"), parameters.value());
379 EXPECT_TRUE(parameters.GetNext());
380 EXPECT_TRUE(parameters.valid());
381 EXPECT_EQ(std::string("qop"), parameters.name());
382 EXPECT_EQ(std::string("auth-int"), parameters.value());
383 EXPECT_FALSE(parameters.GetNext());
384 EXPECT_TRUE(parameters.valid());
385 }
386
387 // Use a challenge which has no property.
TEST(HttpAuthTest,ChallengeTokenizerNoProperty)388 TEST(HttpAuthTest, ChallengeTokenizerNoProperty) {
389 std::string challenge_str = "NTLM";
390 HttpAuth::ChallengeTokenizer challenge(
391 challenge_str.begin(), challenge_str.end());
392 HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
393
394 EXPECT_TRUE(parameters.valid());
395 EXPECT_EQ(std::string("NTLM"), challenge.scheme());
396 EXPECT_FALSE(parameters.GetNext());
397 }
398
399 // Use a challenge with Base64 encoded token.
TEST(HttpAuthTest,ChallengeTokenizerBase64)400 TEST(HttpAuthTest, ChallengeTokenizerBase64) {
401 std::string challenge_str = "NTLM SGVsbG8sIFdvcmxkCg===";
402 HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
403 challenge_str.end());
404
405 EXPECT_EQ(std::string("NTLM"), challenge.scheme());
406 // Notice the two equal statements below due to padding removal.
407 EXPECT_EQ(std::string("SGVsbG8sIFdvcmxkCg=="), challenge.base64_param());
408 }
409
TEST(HttpAuthTest,GetChallengeHeaderName)410 TEST(HttpAuthTest, GetChallengeHeaderName) {
411 std::string name;
412
413 name = HttpAuth::GetChallengeHeaderName(HttpAuth::AUTH_SERVER);
414 EXPECT_STREQ("WWW-Authenticate", name.c_str());
415
416 name = HttpAuth::GetChallengeHeaderName(HttpAuth::AUTH_PROXY);
417 EXPECT_STREQ("Proxy-Authenticate", name.c_str());
418 }
419
TEST(HttpAuthTest,GetAuthorizationHeaderName)420 TEST(HttpAuthTest, GetAuthorizationHeaderName) {
421 std::string name;
422
423 name = HttpAuth::GetAuthorizationHeaderName(HttpAuth::AUTH_SERVER);
424 EXPECT_STREQ("Authorization", name.c_str());
425
426 name = HttpAuth::GetAuthorizationHeaderName(HttpAuth::AUTH_PROXY);
427 EXPECT_STREQ("Proxy-Authorization", name.c_str());
428 }
429
430 } // namespace net
431