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