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