xref: /aosp_15_r20/external/cronet/net/http/http_auth_controller_unittest.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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_controller.h"
6 
7 #include <algorithm>
8 #include <utility>
9 
10 #include "base/ranges/algorithm.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/test/task_environment.h"
13 #include "net/base/net_errors.h"
14 #include "net/base/test_completion_callback.h"
15 #include "net/dns/mock_host_resolver.h"
16 #include "net/http/http_auth_cache.h"
17 #include "net/http/http_auth_challenge_tokenizer.h"
18 #include "net/http/http_auth_handler_mock.h"
19 #include "net/http/http_request_info.h"
20 #include "net/http/http_response_headers.h"
21 #include "net/http/http_util.h"
22 #include "net/log/net_log_event_type.h"
23 #include "net/log/net_log_with_source.h"
24 #include "net/log/test_net_log.h"
25 #include "net/log/test_net_log_util.h"
26 #include "net/ssl/ssl_info.h"
27 #include "testing/gtest/include/gtest/gtest.h"
28 
29 namespace net {
30 
31 namespace {
32 
33 enum HandlerRunMode {
34   RUN_HANDLER_SYNC,
35   RUN_HANDLER_ASYNC
36 };
37 
38 enum SchemeState {
39   SCHEME_IS_DISABLED,
40   SCHEME_IS_ENABLED
41 };
42 
HeadersFromString(const char * string)43 scoped_refptr<HttpResponseHeaders> HeadersFromString(const char* string) {
44   return base::MakeRefCounted<HttpResponseHeaders>(
45       HttpUtil::AssembleRawHeaders(string));
46 }
47 
48 // Runs an HttpAuthController with a single round mock auth handler
49 // that returns |handler_rv| on token generation.  The handler runs in
50 // async if |run_mode| is RUN_HANDLER_ASYNC.  Upon completion, the
51 // return value of the controller is tested against
52 // |expected_controller_rv|.  |scheme_state| indicates whether the
53 // auth scheme used should be disabled after this run.
RunSingleRoundAuthTest(HandlerRunMode run_mode,int handler_rv,int expected_controller_rv,SchemeState scheme_state,const NetLogWithSource & net_log=NetLogWithSource ())54 void RunSingleRoundAuthTest(
55     HandlerRunMode run_mode,
56     int handler_rv,
57     int expected_controller_rv,
58     SchemeState scheme_state,
59     const NetLogWithSource& net_log = NetLogWithSource()) {
60   HttpAuthCache dummy_auth_cache(
61       false /* key_server_entries_by_network_anonymization_key */);
62 
63   HttpRequestInfo request;
64   request.method = "GET";
65   request.url = GURL("http://example.com");
66 
67   scoped_refptr<HttpResponseHeaders> headers(HeadersFromString(
68       "HTTP/1.1 407\r\n"
69       "Proxy-Authenticate: MOCK foo\r\n"
70       "\r\n"));
71 
72   HttpAuthHandlerMock::Factory auth_handler_factory;
73   auto auth_handler = std::make_unique<HttpAuthHandlerMock>();
74   auth_handler->SetGenerateExpectation((run_mode == RUN_HANDLER_ASYNC),
75                                        handler_rv);
76   auth_handler_factory.AddMockHandler(std::move(auth_handler),
77                                       HttpAuth::AUTH_PROXY);
78   auth_handler_factory.set_do_init_from_challenge(true);
79   auto host_resolver = std::make_unique<MockHostResolver>();
80 
81   scoped_refptr<HttpAuthController> controller(
82       base::MakeRefCounted<HttpAuthController>(
83           HttpAuth::AUTH_PROXY, GURL("http://example.com"),
84           NetworkAnonymizationKey(), &dummy_auth_cache, &auth_handler_factory,
85           host_resolver.get()));
86   SSLInfo null_ssl_info;
87   ASSERT_EQ(OK, controller->HandleAuthChallenge(headers, null_ssl_info, false,
88                                                 false, net_log));
89   ASSERT_TRUE(controller->HaveAuthHandler());
90   controller->ResetAuth(AuthCredentials());
91   EXPECT_TRUE(controller->HaveAuth());
92 
93   TestCompletionCallback callback;
94   EXPECT_EQ(
95       (run_mode == RUN_HANDLER_ASYNC) ? ERR_IO_PENDING : expected_controller_rv,
96       controller->MaybeGenerateAuthToken(&request, callback.callback(),
97                                          net_log));
98   if (run_mode == RUN_HANDLER_ASYNC)
99     EXPECT_EQ(expected_controller_rv, callback.WaitForResult());
100   EXPECT_EQ((scheme_state == SCHEME_IS_DISABLED),
101             controller->IsAuthSchemeDisabled(HttpAuth::AUTH_SCHEME_MOCK));
102 }
103 
104 }  // namespace
105 
106 // If an HttpAuthHandler returns an error code that indicates a
107 // permanent error, the HttpAuthController should disable the scheme
108 // used and retry the request.
TEST(HttpAuthControllerTest,PermanentErrors)109 TEST(HttpAuthControllerTest, PermanentErrors) {
110   base::test::TaskEnvironment task_environment;
111 
112   // Run a synchronous handler that returns
113   // ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS.  We expect a return value
114   // of OK from the controller so we can retry the request.
115   RunSingleRoundAuthTest(RUN_HANDLER_SYNC,
116                          ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS, OK,
117                          SCHEME_IS_DISABLED);
118 
119   // Now try an async handler that returns
120   // ERR_MISSING_AUTH_CREDENTIALS.  Async and sync handlers invoke
121   // different code paths in HttpAuthController when generating
122   // tokens. For this particular error the scheme state depends on
123   // the AllowsExplicitCredentials of the handler (which equals true for
124   // the mock handler). If it's true we expect the same behaviour as
125   // for ERR_INVALID_AUTH_CREDENTIALS so we pass SCHEME_IS_ENABLED.
126   RunSingleRoundAuthTest(RUN_HANDLER_ASYNC, ERR_MISSING_AUTH_CREDENTIALS, OK,
127                          SCHEME_IS_ENABLED);
128 
129   // If a non-permanent error is returned by the handler, then the
130   // controller should report it unchanged.
131   RunSingleRoundAuthTest(RUN_HANDLER_ASYNC, ERR_UNEXPECTED, ERR_UNEXPECTED,
132                          SCHEME_IS_ENABLED);
133 
134   // ERR_INVALID_AUTH_CREDENTIALS is special. It's a non-permanet error, but
135   // the error isn't propagated, nor is the auth scheme disabled. This allows
136   // the scheme to re-attempt the authentication attempt using a different set
137   // of credentials.
138   RunSingleRoundAuthTest(RUN_HANDLER_ASYNC, ERR_INVALID_AUTH_CREDENTIALS, OK,
139                          SCHEME_IS_ENABLED);
140 }
141 
142 // Verify that the controller logs appropriate lifetime events.
TEST(HttpAuthControllerTest,Logging)143 TEST(HttpAuthControllerTest, Logging) {
144   base::test::TaskEnvironment task_environment;
145   RecordingNetLogObserver net_log_observer;
146 
147   RunSingleRoundAuthTest(RUN_HANDLER_SYNC, OK, OK, SCHEME_IS_ENABLED,
148                          NetLogWithSource::Make(NetLogSourceType::NONE));
149   auto entries = net_log_observer.GetEntries();
150 
151   // There should be at least two events.
152   ASSERT_GE(entries.size(), 2u);
153 
154   auto begin =
155       base::ranges::find_if(entries, [](const NetLogEntry& e) {
156         if (e.type != NetLogEventType::AUTH_CONTROLLER ||
157             e.phase != NetLogEventPhase::BEGIN)
158           return false;
159 
160         auto target = GetOptionalStringValueFromParams(e, "target");
161         auto url = GetOptionalStringValueFromParams(e, "url");
162         if (!target || !url)
163           return false;
164 
165         EXPECT_EQ("proxy", *target);
166         EXPECT_EQ("http://example.com/", *url);
167         return true;
168       });
169   EXPECT_TRUE(begin != entries.end());
170   EXPECT_TRUE(std::any_of(++begin, entries.end(), [](const NetLogEntry& e) {
171     return e.type == NetLogEventType::AUTH_CONTROLLER &&
172            e.phase == NetLogEventPhase::END;
173   }));
174 }
175 
176 // If an HttpAuthHandler indicates that it doesn't allow explicit
177 // credentials, don't prompt for credentials.
TEST(HttpAuthControllerTest,NoExplicitCredentialsAllowed)178 TEST(HttpAuthControllerTest, NoExplicitCredentialsAllowed) {
179   // Modified mock HttpAuthHandler for this test.
180   class MockHandler : public HttpAuthHandlerMock {
181    public:
182     MockHandler(int expected_rv, HttpAuth::Scheme scheme)
183         : expected_scheme_(scheme) {
184       SetGenerateExpectation(false, expected_rv);
185     }
186 
187    protected:
188     bool Init(
189         HttpAuthChallengeTokenizer* challenge,
190         const SSLInfo& ssl_info,
191         const NetworkAnonymizationKey& network_anonymization_key) override {
192       HttpAuthHandlerMock::Init(challenge, ssl_info, network_anonymization_key);
193       set_allows_default_credentials(true);
194       set_allows_explicit_credentials(false);
195       set_connection_based(true);
196       // Pretend to be SCHEME_BASIC so we can test failover logic.
197       if (challenge->auth_scheme() == "basic") {
198         auth_scheme_ = HttpAuth::AUTH_SCHEME_BASIC;
199         --score_;  // Reduce score, so we rank below Mock.
200         set_allows_explicit_credentials(true);
201       }
202       EXPECT_EQ(expected_scheme_, auth_scheme_);
203       return true;
204     }
205 
206     int GenerateAuthTokenImpl(const AuthCredentials* credentials,
207                               const HttpRequestInfo* request,
208                               CompletionOnceCallback callback,
209                               std::string* auth_token) override {
210       int result = HttpAuthHandlerMock::GenerateAuthTokenImpl(
211           credentials, request, std::move(callback), auth_token);
212       EXPECT_TRUE(result != OK ||
213                   !AllowsExplicitCredentials() ||
214                   !credentials->Empty());
215       return result;
216     }
217 
218    private:
219     HttpAuth::Scheme expected_scheme_;
220   };
221 
222   NetLogWithSource dummy_log;
223   HttpAuthCache dummy_auth_cache(
224       false /* key_server_entries_by_network_anonymization_key */);
225   HttpRequestInfo request;
226   request.method = "GET";
227   request.url = GURL("http://example.com");
228 
229   HttpRequestHeaders request_headers;
230   scoped_refptr<HttpResponseHeaders> headers(HeadersFromString(
231       "HTTP/1.1 401\r\n"
232       "WWW-Authenticate: Mock\r\n"
233       "WWW-Authenticate: Basic\r\n"
234       "\r\n"));
235 
236   HttpAuthHandlerMock::Factory auth_handler_factory;
237 
238   // Handlers for the first attempt at authentication.  AUTH_SCHEME_MOCK handler
239   // accepts the default identity and successfully constructs a token.
240   auth_handler_factory.AddMockHandler(
241       std::make_unique<MockHandler>(OK, HttpAuth::AUTH_SCHEME_MOCK),
242       HttpAuth::AUTH_SERVER);
243   auth_handler_factory.AddMockHandler(
244       std::make_unique<MockHandler>(ERR_UNEXPECTED,
245                                     HttpAuth::AUTH_SCHEME_BASIC),
246       HttpAuth::AUTH_SERVER);
247 
248   // Handlers for the second attempt.  Neither should be used to generate a
249   // token.  Instead the controller should realize that there are no viable
250   // identities to use with the AUTH_SCHEME_MOCK handler and fail.
251   auth_handler_factory.AddMockHandler(
252       std::make_unique<MockHandler>(ERR_UNEXPECTED, HttpAuth::AUTH_SCHEME_MOCK),
253       HttpAuth::AUTH_SERVER);
254   auth_handler_factory.AddMockHandler(
255       std::make_unique<MockHandler>(ERR_UNEXPECTED,
256                                     HttpAuth::AUTH_SCHEME_BASIC),
257       HttpAuth::AUTH_SERVER);
258 
259   // Fallback handlers for the second attempt.  The AUTH_SCHEME_MOCK handler
260   // should be discarded due to the disabled scheme, and the AUTH_SCHEME_BASIC
261   // handler should successfully be used to generate a token.
262   auth_handler_factory.AddMockHandler(
263       std::make_unique<MockHandler>(ERR_UNEXPECTED, HttpAuth::AUTH_SCHEME_MOCK),
264       HttpAuth::AUTH_SERVER);
265   auth_handler_factory.AddMockHandler(
266       std::make_unique<MockHandler>(OK, HttpAuth::AUTH_SCHEME_BASIC),
267       HttpAuth::AUTH_SERVER);
268   auth_handler_factory.set_do_init_from_challenge(true);
269 
270   auto host_resolver = std::make_unique<MockHostResolver>();
271 
272   scoped_refptr<HttpAuthController> controller(
273       base::MakeRefCounted<HttpAuthController>(
274           HttpAuth::AUTH_SERVER, GURL("http://example.com"),
275           NetworkAnonymizationKey(), &dummy_auth_cache, &auth_handler_factory,
276           host_resolver.get()));
277   SSLInfo null_ssl_info;
278   ASSERT_EQ(OK, controller->HandleAuthChallenge(headers, null_ssl_info, false,
279                                                 false, dummy_log));
280   ASSERT_TRUE(controller->HaveAuthHandler());
281   controller->ResetAuth(AuthCredentials());
282   EXPECT_TRUE(controller->HaveAuth());
283 
284   // Should only succeed if we are using the AUTH_SCHEME_MOCK MockHandler.
285   EXPECT_EQ(OK, controller->MaybeGenerateAuthToken(
286                     &request, CompletionOnceCallback(), dummy_log));
287   controller->AddAuthorizationHeader(&request_headers);
288 
289   // Once a token is generated, simulate the receipt of a server response
290   // indicating that the authentication attempt was rejected.
291   ASSERT_EQ(OK, controller->HandleAuthChallenge(headers, null_ssl_info, false,
292                                                 false, dummy_log));
293   ASSERT_TRUE(controller->HaveAuthHandler());
294   controller->ResetAuth(AuthCredentials(u"Hello", std::u16string()));
295   EXPECT_TRUE(controller->HaveAuth());
296   EXPECT_TRUE(controller->IsAuthSchemeDisabled(HttpAuth::AUTH_SCHEME_MOCK));
297   EXPECT_FALSE(controller->IsAuthSchemeDisabled(HttpAuth::AUTH_SCHEME_BASIC));
298 
299   // Should only succeed if we are using the AUTH_SCHEME_BASIC MockHandler.
300   EXPECT_EQ(OK, controller->MaybeGenerateAuthToken(
301                     &request, CompletionOnceCallback(), dummy_log));
302 }
303 
304 }  // namespace net
305