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/strings/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 
     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       "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 
    157 TEST(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 
    248 TEST(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.
    264 TEST(HttpAuthTest, ChallengeTokenizerNoQuotes) {
    265   std::string challenge_str = "Basic realm=foobar (at) 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 (at) baz.com"), parameters.value());
    276   EXPECT_FALSE(parameters.GetNext());
    277 }
    278 
    279 // Use a name=value property with mismatching quote marks.
    280 TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotes) {
    281   std::string challenge_str = "Basic realm=\"foobar (at) 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 (at) baz.com"), parameters.value());
    292   EXPECT_FALSE(parameters.GetNext());
    293 }
    294 
    295 // Use a name= property without a value and with mismatching quote marks.
    296 TEST(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.
    313 TEST(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.
    330 TEST(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.
    354 TEST(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.
    367 TEST(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.
    393 TEST(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.
    405 TEST(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 
    415 TEST(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 
    425 TEST(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