Home | History | Annotate | Download | only in http
      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 
     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 
     41 HttpResponseHeaders* HeadersFromResponseText(const std::string& response) {
     42   return new HttpResponseHeaders(
     43       HttpUtil::AssembleRawHeaders(response.c_str(), response.length()));
     44 }
     45 
     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 
     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 
    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 
    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.
    259 TEST(HttpAuthTest, ChallengeTokenizerNoQuotes) {
    260   std::string challenge_str = "Basic realm=foobar (at) 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 (at) baz.com"), parameters.value());
    271   EXPECT_FALSE(parameters.GetNext());
    272 }
    273 
    274 // Use a name=value property with mismatching quote marks.
    275 TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotes) {
    276   std::string challenge_str = "Basic realm=\"foobar (at) 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 (at) baz.com"), parameters.value());
    287   EXPECT_FALSE(parameters.GetNext());
    288 }
    289 
    290 // Use a name= property without a value and with mismatching quote marks.
    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.
    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.
    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.
    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.
    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.
    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.
    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 
    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 
    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