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
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      // 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
152TEST(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
243TEST(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.
259TEST(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.
275TEST(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.
291TEST(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.
308TEST(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.
325TEST(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.
349TEST(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.
362TEST(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.
388TEST(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.
400TEST(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
410TEST(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
420TEST(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