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_challenge_tokenizer.h"
15#include "net/http/http_auth_filter.h"
16#include "net/http/http_auth_handler.h"
17#include "net/http/http_auth_handler_factory.h"
18#include "net/http/http_auth_handler_mock.h"
19#include "net/http/http_response_headers.h"
20#include "net/http/http_util.h"
21#include "net/http/mock_allow_url_security_manager.h"
22#include "testing/gtest/include/gtest/gtest.h"
23
24namespace net {
25
26namespace {
27
28HttpAuthHandlerMock* CreateMockHandler(bool connection_based) {
29  HttpAuthHandlerMock* auth_handler = new HttpAuthHandlerMock();
30  auth_handler->set_connection_based(connection_based);
31  std::string challenge_text = "Basic";
32  HttpAuthChallengeTokenizer challenge(challenge_text.begin(),
33                                         challenge_text.end());
34  GURL origin("www.example.com");
35  EXPECT_TRUE(auth_handler->InitFromChallenge(&challenge,
36                                              HttpAuth::AUTH_SERVER,
37                                              origin,
38                                              BoundNetLog()));
39  return auth_handler;
40}
41
42HttpResponseHeaders* HeadersFromResponseText(const std::string& response) {
43  return new HttpResponseHeaders(
44      HttpUtil::AssembleRawHeaders(response.c_str(), response.length()));
45}
46
47HttpAuth::AuthorizationResult HandleChallengeResponse(
48    bool connection_based,
49    const std::string& headers_text,
50    std::string* challenge_used) {
51  scoped_ptr<HttpAuthHandlerMock> mock_handler(
52      CreateMockHandler(connection_based));
53  std::set<HttpAuth::Scheme> disabled_schemes;
54  scoped_refptr<HttpResponseHeaders> headers(
55      HeadersFromResponseText(headers_text));
56  return HttpAuth::HandleChallengeResponse(
57      mock_handler.get(),
58      headers.get(),
59      HttpAuth::AUTH_SERVER,
60      disabled_schemes,
61      challenge_used);
62}
63
64}  // namespace
65
66TEST(HttpAuthTest, ChooseBestChallenge) {
67  static const struct {
68    const char* headers;
69    HttpAuth::Scheme challenge_scheme;
70    const char* challenge_realm;
71  } tests[] = {
72    {
73      // Basic is the only challenge type, pick it.
74      "Y: Digest realm=\"X\", nonce=\"aaaaaaaaaa\"\n"
75      "www-authenticate: Basic realm=\"BasicRealm\"\n",
76
77      HttpAuth::AUTH_SCHEME_BASIC,
78      "BasicRealm",
79    },
80    {
81      // Fake is the only challenge type, but it is unsupported.
82      "Y: Digest realm=\"FooBar\", nonce=\"aaaaaaaaaa\"\n"
83      "www-authenticate: Fake realm=\"FooBar\"\n",
84
85      HttpAuth::AUTH_SCHEME_MAX,
86      "",
87    },
88    {
89      // Pick Digest over Basic.
90      "www-authenticate: Basic realm=\"FooBar\"\n"
91      "www-authenticate: Fake realm=\"FooBar\"\n"
92      "www-authenticate: nonce=\"aaaaaaaaaa\"\n"
93      "www-authenticate: Digest realm=\"DigestRealm\", nonce=\"aaaaaaaaaa\"\n",
94
95      HttpAuth::AUTH_SCHEME_DIGEST,
96      "DigestRealm",
97    },
98    {
99      // Handle an empty header correctly.
100      "Y: Digest realm=\"X\", nonce=\"aaaaaaaaaa\"\n"
101      "www-authenticate:\n",
102
103      HttpAuth::AUTH_SCHEME_MAX,
104      "",
105    },
106    {
107      "WWW-Authenticate: Negotiate\n"
108      "WWW-Authenticate: NTLM\n",
109
110#if defined(USE_KERBEROS)
111      // Choose Negotiate over NTLM on all platforms.
112      // TODO(ahendrickson): This may be flaky on Linux and OSX as it
113      // relies on being able to load one of the known .so files
114      // for gssapi.
115      HttpAuth::AUTH_SCHEME_NEGOTIATE,
116#else
117      // On systems that don't use Kerberos fall back to NTLM.
118      HttpAuth::AUTH_SCHEME_NTLM,
119#endif  // defined(USE_KERBEROS)
120      "",
121    }
122  };
123  GURL origin("http://www.example.com");
124  std::set<HttpAuth::Scheme> disabled_schemes;
125  MockAllowURLSecurityManager url_security_manager;
126  scoped_ptr<HostResolver> host_resolver(new MockHostResolver());
127  scoped_ptr<HttpAuthHandlerRegistryFactory> http_auth_handler_factory(
128      HttpAuthHandlerFactory::CreateDefault(host_resolver.get()));
129  http_auth_handler_factory->SetURLSecurityManager(
130      "negotiate", &url_security_manager);
131
132  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
133    // Make a HttpResponseHeaders object.
134    std::string headers_with_status_line("HTTP/1.1 401 Unauthorized\n");
135    headers_with_status_line += tests[i].headers;
136    scoped_refptr<HttpResponseHeaders> headers(
137        HeadersFromResponseText(headers_with_status_line));
138
139    scoped_ptr<HttpAuthHandler> handler;
140    HttpAuth::ChooseBestChallenge(http_auth_handler_factory.get(),
141                                  headers.get(),
142                                  HttpAuth::AUTH_SERVER,
143                                  origin,
144                                  disabled_schemes,
145                                  BoundNetLog(),
146                                  &handler);
147
148    if (handler.get()) {
149      EXPECT_EQ(tests[i].challenge_scheme, handler->auth_scheme());
150      EXPECT_STREQ(tests[i].challenge_realm, handler->realm().c_str());
151    } else {
152      EXPECT_EQ(HttpAuth::AUTH_SCHEME_MAX, tests[i].challenge_scheme);
153      EXPECT_STREQ("", tests[i].challenge_realm);
154    }
155  }
156}
157
158TEST(HttpAuthTest, HandleChallengeResponse) {
159  std::string challenge_used;
160  const char* const kMockChallenge =
161      "HTTP/1.1 401 Unauthorized\n"
162      "WWW-Authenticate: Mock token_here\n";
163  const char* const kBasicChallenge =
164      "HTTP/1.1 401 Unauthorized\n"
165      "WWW-Authenticate: Basic realm=\"happy\"\n";
166  const char* const kMissingChallenge =
167      "HTTP/1.1 401 Unauthorized\n";
168  const char* const kEmptyChallenge =
169      "HTTP/1.1 401 Unauthorized\n"
170      "WWW-Authenticate: \n";
171  const char* const kBasicAndMockChallenges =
172      "HTTP/1.1 401 Unauthorized\n"
173      "WWW-Authenticate: Basic realm=\"happy\"\n"
174      "WWW-Authenticate: Mock token_here\n";
175  const char* const kTwoMockChallenges =
176      "HTTP/1.1 401 Unauthorized\n"
177      "WWW-Authenticate: Mock token_a\n"
178      "WWW-Authenticate: Mock token_b\n";
179
180  // Request based schemes should treat any new challenges as rejections of the
181  // previous authentication attempt. (There is a slight exception for digest
182  // authentication and the stale parameter, but that is covered in the
183  // http_auth_handler_digest_unittests).
184  EXPECT_EQ(
185      HttpAuth::AUTHORIZATION_RESULT_REJECT,
186      HandleChallengeResponse(false, kMockChallenge, &challenge_used));
187  EXPECT_EQ("Mock token_here", challenge_used);
188
189  EXPECT_EQ(
190      HttpAuth::AUTHORIZATION_RESULT_REJECT,
191      HandleChallengeResponse(false, kBasicChallenge, &challenge_used));
192  EXPECT_EQ("", challenge_used);
193
194  EXPECT_EQ(
195      HttpAuth::AUTHORIZATION_RESULT_REJECT,
196      HandleChallengeResponse(false, kMissingChallenge, &challenge_used));
197  EXPECT_EQ("", challenge_used);
198
199  EXPECT_EQ(
200      HttpAuth::AUTHORIZATION_RESULT_REJECT,
201      HandleChallengeResponse(false, kEmptyChallenge, &challenge_used));
202  EXPECT_EQ("", challenge_used);
203
204  EXPECT_EQ(
205      HttpAuth::AUTHORIZATION_RESULT_REJECT,
206      HandleChallengeResponse(false, kBasicAndMockChallenges, &challenge_used));
207  EXPECT_EQ("Mock token_here", challenge_used);
208
209  EXPECT_EQ(
210      HttpAuth::AUTHORIZATION_RESULT_REJECT,
211      HandleChallengeResponse(false, kTwoMockChallenges, &challenge_used));
212  EXPECT_EQ("Mock token_a", challenge_used);
213
214  // Connection based schemes will treat new auth challenges for the same scheme
215  // as acceptance (and continuance) of the current approach. If there are
216  // no auth challenges for the same scheme, the response will be treated as
217  // a rejection.
218  EXPECT_EQ(
219      HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
220      HandleChallengeResponse(true, kMockChallenge, &challenge_used));
221  EXPECT_EQ("Mock token_here", challenge_used);
222
223  EXPECT_EQ(
224      HttpAuth::AUTHORIZATION_RESULT_REJECT,
225      HandleChallengeResponse(true, kBasicChallenge, &challenge_used));
226  EXPECT_EQ("", challenge_used);
227
228  EXPECT_EQ(
229      HttpAuth::AUTHORIZATION_RESULT_REJECT,
230      HandleChallengeResponse(true, kMissingChallenge, &challenge_used));
231  EXPECT_EQ("", challenge_used);
232
233  EXPECT_EQ(
234      HttpAuth::AUTHORIZATION_RESULT_REJECT,
235      HandleChallengeResponse(true, kEmptyChallenge, &challenge_used));
236  EXPECT_EQ("", challenge_used);
237
238  EXPECT_EQ(
239      HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
240      HandleChallengeResponse(true, kBasicAndMockChallenges, &challenge_used));
241  EXPECT_EQ("Mock token_here", challenge_used);
242
243  EXPECT_EQ(
244      HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
245      HandleChallengeResponse(true, kTwoMockChallenges, &challenge_used));
246  EXPECT_EQ("Mock token_a", challenge_used);
247}
248
249TEST(HttpAuthTest, GetChallengeHeaderName) {
250  std::string name;
251
252  name = HttpAuth::GetChallengeHeaderName(HttpAuth::AUTH_SERVER);
253  EXPECT_STREQ("WWW-Authenticate", name.c_str());
254
255  name = HttpAuth::GetChallengeHeaderName(HttpAuth::AUTH_PROXY);
256  EXPECT_STREQ("Proxy-Authenticate", name.c_str());
257}
258
259TEST(HttpAuthTest, GetAuthorizationHeaderName) {
260  std::string name;
261
262  name = HttpAuth::GetAuthorizationHeaderName(HttpAuth::AUTH_SERVER);
263  EXPECT_STREQ("Authorization", name.c_str());
264
265  name = HttpAuth::GetAuthorizationHeaderName(HttpAuth::AUTH_PROXY);
266  EXPECT_STREQ("Proxy-Authorization", name.c_str());
267}
268
269}  // namespace net
270