• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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