http_auth_unittest.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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/net_errors.h"
12#include "net/dns/mock_host_resolver.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
23namespace net {
24
25namespace {
26
27HttpAuthHandlerMock* 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
41HttpResponseHeaders* HeadersFromResponseText(const std::string& response) {
42  return new HttpResponseHeaders(
43      HttpUtil::AssembleRawHeaders(response.c_str(), response.length()));
44}
45
46HttpAuth::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
65TEST(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      "WWW-Authenticate: Negotiate\n"
107      "WWW-Authenticate: NTLM\n",
108
109#if defined(USE_KERBEROS)
110      // Choose Negotiate over NTLM on all platforms.
111      // TODO(ahendrickson): This may be flaky on Linux and OSX as it
112      // relies on being able to load one of the known .so files
113      // for gssapi.
114      HttpAuth::AUTH_SCHEME_NEGOTIATE,
115#else
116      // On systems that don't use Kerberos fall back to NTLM.
117      HttpAuth::AUTH_SCHEME_NTLM,
118#endif  // defined(USE_KERBEROS)
119      "",
120    }
121  };
122  GURL origin("http://www.example.com");
123  std::set<HttpAuth::Scheme> disabled_schemes;
124  MockAllowURLSecurityManager url_security_manager;
125  scoped_ptr<HostResolver> host_resolver(new MockHostResolver());
126  scoped_ptr<HttpAuthHandlerRegistryFactory> http_auth_handler_factory(
127      HttpAuthHandlerFactory::CreateDefault(host_resolver.get()));
128  http_auth_handler_factory->SetURLSecurityManager(
129      "negotiate", &url_security_manager);
130
131  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
132    // Make a HttpResponseHeaders object.
133    std::string headers_with_status_line("HTTP/1.1 401 Unauthorized\n");
134    headers_with_status_line += tests[i].headers;
135    scoped_refptr<HttpResponseHeaders> headers(
136        HeadersFromResponseText(headers_with_status_line));
137
138    scoped_ptr<HttpAuthHandler> handler;
139    HttpAuth::ChooseBestChallenge(http_auth_handler_factory.get(),
140                                  headers.get(),
141                                  HttpAuth::AUTH_SERVER,
142                                  origin,
143                                  disabled_schemes,
144                                  BoundNetLog(),
145                                  &handler);
146
147    if (handler.get()) {
148      EXPECT_EQ(tests[i].challenge_scheme, handler->auth_scheme());
149      EXPECT_STREQ(tests[i].challenge_realm, handler->realm().c_str());
150    } else {
151      EXPECT_EQ(HttpAuth::AUTH_SCHEME_MAX, tests[i].challenge_scheme);
152      EXPECT_STREQ("", tests[i].challenge_realm);
153    }
154  }
155}
156
157TEST(HttpAuthTest, HandleChallengeResponse) {
158  std::string challenge_used;
159  const char* const kMockChallenge =
160      "HTTP/1.1 401 Unauthorized\n"
161      "WWW-Authenticate: Mock token_here\n";
162  const char* const kBasicChallenge =
163      "HTTP/1.1 401 Unauthorized\n"
164      "WWW-Authenticate: Basic realm=\"happy\"\n";
165  const char* const kMissingChallenge =
166      "HTTP/1.1 401 Unauthorized\n";
167  const char* const kEmptyChallenge =
168      "HTTP/1.1 401 Unauthorized\n"
169      "WWW-Authenticate: \n";
170  const char* const kBasicAndMockChallenges =
171      "HTTP/1.1 401 Unauthorized\n"
172      "WWW-Authenticate: Basic realm=\"happy\"\n"
173      "WWW-Authenticate: Mock token_here\n";
174  const char* const kTwoMockChallenges =
175      "HTTP/1.1 401 Unauthorized\n"
176      "WWW-Authenticate: Mock token_a\n"
177      "WWW-Authenticate: Mock token_b\n";
178
179  // Request based schemes should treat any new challenges as rejections of the
180  // previous authentication attempt. (There is a slight exception for digest
181  // authentication and the stale parameter, but that is covered in the
182  // http_auth_handler_digest_unittests).
183  EXPECT_EQ(
184      HttpAuth::AUTHORIZATION_RESULT_REJECT,
185      HandleChallengeResponse(false, kMockChallenge, &challenge_used));
186  EXPECT_EQ("Mock token_here", challenge_used);
187
188  EXPECT_EQ(
189      HttpAuth::AUTHORIZATION_RESULT_REJECT,
190      HandleChallengeResponse(false, kBasicChallenge, &challenge_used));
191  EXPECT_EQ("", challenge_used);
192
193  EXPECT_EQ(
194      HttpAuth::AUTHORIZATION_RESULT_REJECT,
195      HandleChallengeResponse(false, kMissingChallenge, &challenge_used));
196  EXPECT_EQ("", challenge_used);
197
198  EXPECT_EQ(
199      HttpAuth::AUTHORIZATION_RESULT_REJECT,
200      HandleChallengeResponse(false, kEmptyChallenge, &challenge_used));
201  EXPECT_EQ("", challenge_used);
202
203  EXPECT_EQ(
204      HttpAuth::AUTHORIZATION_RESULT_REJECT,
205      HandleChallengeResponse(false, kBasicAndMockChallenges, &challenge_used));
206  EXPECT_EQ("Mock token_here", challenge_used);
207
208  EXPECT_EQ(
209      HttpAuth::AUTHORIZATION_RESULT_REJECT,
210      HandleChallengeResponse(false, kTwoMockChallenges, &challenge_used));
211  EXPECT_EQ("Mock token_a", challenge_used);
212
213  // Connection based schemes will treat new auth challenges for the same scheme
214  // as acceptance (and continuance) of the current approach. If there are
215  // no auth challenges for the same scheme, the response will be treated as
216  // a rejection.
217  EXPECT_EQ(
218      HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
219      HandleChallengeResponse(true, kMockChallenge, &challenge_used));
220  EXPECT_EQ("Mock token_here", challenge_used);
221
222  EXPECT_EQ(
223      HttpAuth::AUTHORIZATION_RESULT_REJECT,
224      HandleChallengeResponse(true, kBasicChallenge, &challenge_used));
225  EXPECT_EQ("", challenge_used);
226
227  EXPECT_EQ(
228      HttpAuth::AUTHORIZATION_RESULT_REJECT,
229      HandleChallengeResponse(true, kMissingChallenge, &challenge_used));
230  EXPECT_EQ("", challenge_used);
231
232  EXPECT_EQ(
233      HttpAuth::AUTHORIZATION_RESULT_REJECT,
234      HandleChallengeResponse(true, kEmptyChallenge, &challenge_used));
235  EXPECT_EQ("", challenge_used);
236
237  EXPECT_EQ(
238      HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
239      HandleChallengeResponse(true, kBasicAndMockChallenges, &challenge_used));
240  EXPECT_EQ("Mock token_here", challenge_used);
241
242  EXPECT_EQ(
243      HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
244      HandleChallengeResponse(true, kTwoMockChallenges, &challenge_used));
245  EXPECT_EQ("Mock token_a", challenge_used);
246}
247
248TEST(HttpAuthTest, ChallengeTokenizer) {
249  std::string challenge_str = "Basic realm=\"foobar\"";
250  HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
251                                         challenge_str.end());
252  HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
253
254  EXPECT_TRUE(parameters.valid());
255  EXPECT_EQ(std::string("Basic"), challenge.scheme());
256  EXPECT_TRUE(parameters.GetNext());
257  EXPECT_TRUE(parameters.valid());
258  EXPECT_EQ(std::string("realm"), parameters.name());
259  EXPECT_EQ(std::string("foobar"), parameters.value());
260  EXPECT_FALSE(parameters.GetNext());
261}
262
263// Use a name=value property with no quote marks.
264TEST(HttpAuthTest, ChallengeTokenizerNoQuotes) {
265  std::string challenge_str = "Basic realm=foobar@baz.com";
266  HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
267                                         challenge_str.end());
268  HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
269
270  EXPECT_TRUE(parameters.valid());
271  EXPECT_EQ(std::string("Basic"), challenge.scheme());
272  EXPECT_TRUE(parameters.GetNext());
273  EXPECT_TRUE(parameters.valid());
274  EXPECT_EQ(std::string("realm"), parameters.name());
275  EXPECT_EQ(std::string("foobar@baz.com"), parameters.value());
276  EXPECT_FALSE(parameters.GetNext());
277}
278
279// Use a name=value property with mismatching quote marks.
280TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotes) {
281  std::string challenge_str = "Basic realm=\"foobar@baz.com";
282  HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
283                                         challenge_str.end());
284  HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
285
286  EXPECT_TRUE(parameters.valid());
287  EXPECT_EQ(std::string("Basic"), challenge.scheme());
288  EXPECT_TRUE(parameters.GetNext());
289  EXPECT_TRUE(parameters.valid());
290  EXPECT_EQ(std::string("realm"), parameters.name());
291  EXPECT_EQ(std::string("foobar@baz.com"), parameters.value());
292  EXPECT_FALSE(parameters.GetNext());
293}
294
295// Use a name= property without a value and with mismatching quote marks.
296TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotesNoValue) {
297  std::string challenge_str = "Basic realm=\"";
298  HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
299                                         challenge_str.end());
300  HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
301
302  EXPECT_TRUE(parameters.valid());
303  EXPECT_EQ(std::string("Basic"), challenge.scheme());
304  EXPECT_TRUE(parameters.GetNext());
305  EXPECT_TRUE(parameters.valid());
306  EXPECT_EQ(std::string("realm"), parameters.name());
307  EXPECT_EQ(std::string(""), parameters.value());
308  EXPECT_FALSE(parameters.GetNext());
309}
310
311// Use a name=value property with mismatching quote marks and spaces in the
312// value.
313TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotesSpaces) {
314  std::string challenge_str = "Basic realm=\"foo bar";
315  HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
316                                         challenge_str.end());
317  HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
318
319  EXPECT_TRUE(parameters.valid());
320  EXPECT_EQ(std::string("Basic"), challenge.scheme());
321  EXPECT_TRUE(parameters.GetNext());
322  EXPECT_TRUE(parameters.valid());
323  EXPECT_EQ(std::string("realm"), parameters.name());
324  EXPECT_EQ(std::string("foo bar"), parameters.value());
325  EXPECT_FALSE(parameters.GetNext());
326}
327
328// Use multiple name=value properties with mismatching quote marks in the last
329// value.
330TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotesMultiple) {
331  std::string challenge_str = "Digest qop=auth-int, algorithm=md5, realm=\"foo";
332  HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
333                                         challenge_str.end());
334  HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
335
336  EXPECT_TRUE(parameters.valid());
337  EXPECT_EQ(std::string("Digest"), challenge.scheme());
338  EXPECT_TRUE(parameters.GetNext());
339  EXPECT_TRUE(parameters.valid());
340  EXPECT_EQ(std::string("qop"), parameters.name());
341  EXPECT_EQ(std::string("auth-int"), parameters.value());
342  EXPECT_TRUE(parameters.GetNext());
343  EXPECT_TRUE(parameters.valid());
344  EXPECT_EQ(std::string("algorithm"), parameters.name());
345  EXPECT_EQ(std::string("md5"), parameters.value());
346  EXPECT_TRUE(parameters.GetNext());
347  EXPECT_TRUE(parameters.valid());
348  EXPECT_EQ(std::string("realm"), parameters.name());
349  EXPECT_EQ(std::string("foo"), parameters.value());
350  EXPECT_FALSE(parameters.GetNext());
351}
352
353// Use a name= property which has no value.
354TEST(HttpAuthTest, ChallengeTokenizerNoValue) {
355  std::string challenge_str = "Digest qop=";
356  HttpAuth::ChallengeTokenizer challenge(
357      challenge_str.begin(), challenge_str.end());
358  HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
359
360  EXPECT_TRUE(parameters.valid());
361  EXPECT_EQ(std::string("Digest"), challenge.scheme());
362  EXPECT_FALSE(parameters.GetNext());
363  EXPECT_FALSE(parameters.valid());
364}
365
366// Specify multiple properties, comma separated.
367TEST(HttpAuthTest, ChallengeTokenizerMultiple) {
368  std::string challenge_str =
369      "Digest algorithm=md5, realm=\"Oblivion\", qop=auth-int";
370  HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
371                                         challenge_str.end());
372  HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
373
374  EXPECT_TRUE(parameters.valid());
375  EXPECT_EQ(std::string("Digest"), challenge.scheme());
376  EXPECT_TRUE(parameters.GetNext());
377  EXPECT_TRUE(parameters.valid());
378  EXPECT_EQ(std::string("algorithm"), parameters.name());
379  EXPECT_EQ(std::string("md5"), parameters.value());
380  EXPECT_TRUE(parameters.GetNext());
381  EXPECT_TRUE(parameters.valid());
382  EXPECT_EQ(std::string("realm"), parameters.name());
383  EXPECT_EQ(std::string("Oblivion"), parameters.value());
384  EXPECT_TRUE(parameters.GetNext());
385  EXPECT_TRUE(parameters.valid());
386  EXPECT_EQ(std::string("qop"), parameters.name());
387  EXPECT_EQ(std::string("auth-int"), parameters.value());
388  EXPECT_FALSE(parameters.GetNext());
389  EXPECT_TRUE(parameters.valid());
390}
391
392// Use a challenge which has no property.
393TEST(HttpAuthTest, ChallengeTokenizerNoProperty) {
394  std::string challenge_str = "NTLM";
395  HttpAuth::ChallengeTokenizer challenge(
396      challenge_str.begin(), challenge_str.end());
397  HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
398
399  EXPECT_TRUE(parameters.valid());
400  EXPECT_EQ(std::string("NTLM"), challenge.scheme());
401  EXPECT_FALSE(parameters.GetNext());
402}
403
404// Use a challenge with Base64 encoded token.
405TEST(HttpAuthTest, ChallengeTokenizerBase64) {
406  std::string challenge_str = "NTLM  SGVsbG8sIFdvcmxkCg===";
407  HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
408                                         challenge_str.end());
409
410  EXPECT_EQ(std::string("NTLM"), challenge.scheme());
411  // Notice the two equal statements below due to padding removal.
412  EXPECT_EQ(std::string("SGVsbG8sIFdvcmxkCg=="), challenge.base64_param());
413}
414
415TEST(HttpAuthTest, GetChallengeHeaderName) {
416  std::string name;
417
418  name = HttpAuth::GetChallengeHeaderName(HttpAuth::AUTH_SERVER);
419  EXPECT_STREQ("WWW-Authenticate", name.c_str());
420
421  name = HttpAuth::GetChallengeHeaderName(HttpAuth::AUTH_PROXY);
422  EXPECT_STREQ("Proxy-Authenticate", name.c_str());
423}
424
425TEST(HttpAuthTest, GetAuthorizationHeaderName) {
426  std::string name;
427
428  name = HttpAuth::GetAuthorizationHeaderName(HttpAuth::AUTH_SERVER);
429  EXPECT_STREQ("Authorization", name.c_str());
430
431  name = HttpAuth::GetAuthorizationHeaderName(HttpAuth::AUTH_PROXY);
432  EXPECT_STREQ("Proxy-Authorization", name.c_str());
433}
434
435}  // namespace net
436