[email protected] | 76b0f68 | 2011-03-30 16:54:54 | [diff] [blame] | 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 "net/http/http_auth_controller.h" |
| 6 | |
[email protected] | 750b2f3c | 2013-06-07 18:41:05 | [diff] [blame] | 7 | #include "base/strings/utf_string_conversions.h" |
[email protected] | 76b0f68 | 2011-03-30 16:54:54 | [diff] [blame] | 8 | #include "net/base/net_errors.h" |
[email protected] | 76b0f68 | 2011-03-30 16:54:54 | [diff] [blame] | 9 | #include "net/base/test_completion_callback.h" |
| 10 | #include "net/http/http_auth_cache.h" |
[email protected] | df41d0d8 | 2014-03-13 00:43:24 | [diff] [blame] | 11 | #include "net/http/http_auth_challenge_tokenizer.h" |
[email protected] | 76b0f68 | 2011-03-30 16:54:54 | [diff] [blame] | 12 | #include "net/http/http_auth_handler_mock.h" |
| 13 | #include "net/http/http_request_info.h" |
| 14 | #include "net/http/http_response_headers.h" |
| 15 | #include "net/http/http_util.h" |
mikecirone | f22f981 | 2016-10-04 03:40:19 | [diff] [blame] | 16 | #include "net/log/net_log_with_source.h" |
asanka | 5ffd5d7 | 2016-03-23 16:20:49 | [diff] [blame] | 17 | #include "net/ssl/ssl_info.h" |
[email protected] | 76b0f68 | 2011-03-30 16:54:54 | [diff] [blame] | 18 | #include "testing/gtest/include/gtest/gtest.h" |
| 19 | |
| 20 | namespace net { |
| 21 | |
| 22 | namespace { |
| 23 | |
| 24 | enum HandlerRunMode { |
| 25 | RUN_HANDLER_SYNC, |
| 26 | RUN_HANDLER_ASYNC |
| 27 | }; |
| 28 | |
| 29 | enum SchemeState { |
| 30 | SCHEME_IS_DISABLED, |
| 31 | SCHEME_IS_ENABLED |
| 32 | }; |
| 33 | |
[email protected] | 26d84b0 | 2011-08-31 14:07:08 | [diff] [blame] | 34 | scoped_refptr<HttpResponseHeaders> HeadersFromString(const char* string) { |
| 35 | std::string raw_string(string); |
| 36 | std::string headers_string = HttpUtil::AssembleRawHeaders( |
| 37 | raw_string.c_str(), raw_string.length()); |
| 38 | scoped_refptr<HttpResponseHeaders> headers( |
| 39 | new HttpResponseHeaders(headers_string)); |
| 40 | return headers; |
| 41 | } |
| 42 | |
[email protected] | 76b0f68 | 2011-03-30 16:54:54 | [diff] [blame] | 43 | // Runs an HttpAuthController with a single round mock auth handler |
| 44 | // that returns |handler_rv| on token generation. The handler runs in |
| 45 | // async if |run_mode| is RUN_HANDLER_ASYNC. Upon completion, the |
| 46 | // return value of the controller is tested against |
| 47 | // |expected_controller_rv|. |scheme_state| indicates whether the |
| 48 | // auth scheme used should be disabled after this run. |
| 49 | void RunSingleRoundAuthTest(HandlerRunMode run_mode, |
| 50 | int handler_rv, |
| 51 | int expected_controller_rv, |
| 52 | SchemeState scheme_state) { |
tfarina | 4283411 | 2016-09-22 13:38:20 | [diff] [blame] | 53 | NetLogWithSource dummy_log; |
[email protected] | 76b0f68 | 2011-03-30 16:54:54 | [diff] [blame] | 54 | HttpAuthCache dummy_auth_cache; |
| 55 | |
| 56 | HttpRequestInfo request; |
| 57 | request.method = "GET"; |
| 58 | request.url = GURL("http://example.com"); |
| 59 | |
[email protected] | 26d84b0 | 2011-08-31 14:07:08 | [diff] [blame] | 60 | scoped_refptr<HttpResponseHeaders> headers(HeadersFromString( |
[email protected] | 76b0f68 | 2011-03-30 16:54:54 | [diff] [blame] | 61 | "HTTP/1.1 407\r\n" |
| 62 | "Proxy-Authenticate: MOCK foo\r\n" |
[email protected] | 26d84b0 | 2011-08-31 14:07:08 | [diff] [blame] | 63 | "\r\n")); |
[email protected] | 76b0f68 | 2011-03-30 16:54:54 | [diff] [blame] | 64 | |
| 65 | HttpAuthHandlerMock::Factory auth_handler_factory; |
| 66 | HttpAuthHandlerMock* auth_handler = new HttpAuthHandlerMock(); |
| 67 | auth_handler->SetGenerateExpectation((run_mode == RUN_HANDLER_ASYNC), |
| 68 | handler_rv); |
[email protected] | 2d01c26 | 2011-08-11 23:07:08 | [diff] [blame] | 69 | auth_handler_factory.AddMockHandler(auth_handler, HttpAuth::AUTH_PROXY); |
[email protected] | 76b0f68 | 2011-03-30 16:54:54 | [diff] [blame] | 70 | auth_handler_factory.set_do_init_from_challenge(true); |
| 71 | |
| 72 | scoped_refptr<HttpAuthController> controller( |
| 73 | new HttpAuthController(HttpAuth::AUTH_PROXY, |
| 74 | GURL("http://example.com"), |
| 75 | &dummy_auth_cache, &auth_handler_factory)); |
asanka | 5ffd5d7 | 2016-03-23 16:20:49 | [diff] [blame] | 76 | SSLInfo null_ssl_info; |
| 77 | ASSERT_EQ(OK, controller->HandleAuthChallenge(headers, null_ssl_info, false, |
| 78 | false, dummy_log)); |
[email protected] | 26d84b0 | 2011-08-31 14:07:08 | [diff] [blame] | 79 | ASSERT_TRUE(controller->HaveAuthHandler()); |
[email protected] | f3cf980 | 2011-10-28 18:44:58 | [diff] [blame] | 80 | controller->ResetAuth(AuthCredentials()); |
[email protected] | 76b0f68 | 2011-03-30 16:54:54 | [diff] [blame] | 81 | EXPECT_TRUE(controller->HaveAuth()); |
| 82 | |
[email protected] | 49639fa | 2011-12-20 23:22:41 | [diff] [blame] | 83 | TestCompletionCallback callback; |
[email protected] | 76b0f68 | 2011-03-30 16:54:54 | [diff] [blame] | 84 | EXPECT_EQ((run_mode == RUN_HANDLER_ASYNC)? ERR_IO_PENDING: |
| 85 | expected_controller_rv, |
[email protected] | 49639fa | 2011-12-20 23:22:41 | [diff] [blame] | 86 | controller->MaybeGenerateAuthToken(&request, callback.callback(), |
[email protected] | 76b0f68 | 2011-03-30 16:54:54 | [diff] [blame] | 87 | dummy_log)); |
| 88 | if (run_mode == RUN_HANDLER_ASYNC) |
| 89 | EXPECT_EQ(expected_controller_rv, callback.WaitForResult()); |
| 90 | EXPECT_EQ((scheme_state == SCHEME_IS_DISABLED), |
| 91 | controller->IsAuthSchemeDisabled(HttpAuth::AUTH_SCHEME_MOCK)); |
| 92 | } |
| 93 | |
| 94 | } // namespace |
| 95 | |
| 96 | // If an HttpAuthHandler returns an error code that indicates a |
| 97 | // permanent error, the HttpAuthController should disable the scheme |
| 98 | // used and retry the request. |
| 99 | TEST(HttpAuthControllerTest, PermanentErrors) { |
| 100 | |
| 101 | // Run a synchronous handler that returns |
| 102 | // ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS. We expect a return value |
| 103 | // of OK from the controller so we can retry the request. |
| 104 | RunSingleRoundAuthTest(RUN_HANDLER_SYNC, |
| 105 | ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS, |
| 106 | OK, SCHEME_IS_DISABLED); |
| 107 | |
| 108 | // Now try an async handler that returns |
| 109 | // ERR_MISSING_AUTH_CREDENTIALS. Async and sync handlers invoke |
| 110 | // different code paths in HttpAuthController when generating |
| 111 | // tokens. |
| 112 | RunSingleRoundAuthTest(RUN_HANDLER_ASYNC, ERR_MISSING_AUTH_CREDENTIALS, OK, |
| 113 | SCHEME_IS_DISABLED); |
| 114 | |
| 115 | // If a non-permanent error is returned by the handler, then the |
| 116 | // controller should report it unchanged. |
asanka | e2257db | 2016-10-11 22:03:16 | [diff] [blame] | 117 | RunSingleRoundAuthTest(RUN_HANDLER_ASYNC, ERR_UNEXPECTED, ERR_UNEXPECTED, |
| 118 | SCHEME_IS_ENABLED); |
| 119 | |
| 120 | // ERR_INVALID_AUTH_CREDENTIALS is special. It's a non-permanet error, but |
| 121 | // the error isn't propagated, nor is the auth scheme disabled. This allows |
| 122 | // the scheme to re-attempt the authentication attempt using a different set |
| 123 | // of credentials. |
| 124 | RunSingleRoundAuthTest(RUN_HANDLER_ASYNC, ERR_INVALID_AUTH_CREDENTIALS, OK, |
| 125 | SCHEME_IS_ENABLED); |
[email protected] | 76b0f68 | 2011-03-30 16:54:54 | [diff] [blame] | 126 | } |
| 127 | |
[email protected] | 26d84b0 | 2011-08-31 14:07:08 | [diff] [blame] | 128 | // If an HttpAuthHandler indicates that it doesn't allow explicit |
| 129 | // credentials, don't prompt for credentials. |
| 130 | TEST(HttpAuthControllerTest, NoExplicitCredentialsAllowed) { |
| 131 | // Modified mock HttpAuthHandler for this test. |
| 132 | class MockHandler : public HttpAuthHandlerMock { |
| 133 | public: |
| 134 | MockHandler(int expected_rv, HttpAuth::Scheme scheme) |
| 135 | : expected_scheme_(scheme) { |
| 136 | SetGenerateExpectation(false, expected_rv); |
| 137 | } |
| 138 | |
| 139 | protected: |
asanka | 5ffd5d7 | 2016-03-23 16:20:49 | [diff] [blame] | 140 | bool Init(HttpAuthChallengeTokenizer* challenge, |
| 141 | const SSLInfo& ssl_info) override { |
| 142 | HttpAuthHandlerMock::Init(challenge, ssl_info); |
[email protected] | 26d84b0 | 2011-08-31 14:07:08 | [diff] [blame] | 143 | set_allows_default_credentials(true); |
| 144 | set_allows_explicit_credentials(false); |
| 145 | set_connection_based(true); |
| 146 | // Pretend to be SCHEME_BASIC so we can test failover logic. |
| 147 | if (challenge->scheme() == "Basic") { |
| 148 | auth_scheme_ = HttpAuth::AUTH_SCHEME_BASIC; |
| 149 | --score_; // Reduce score, so we rank below Mock. |
| 150 | set_allows_explicit_credentials(true); |
| 151 | } |
| 152 | EXPECT_EQ(expected_scheme_, auth_scheme_); |
| 153 | return true; |
| 154 | } |
| 155 | |
dcheng | b03027d | 2014-10-21 12:00:20 | [diff] [blame] | 156 | int GenerateAuthTokenImpl(const AuthCredentials* credentials, |
| 157 | const HttpRequestInfo* request, |
| 158 | const CompletionCallback& callback, |
| 159 | std::string* auth_token) override { |
[email protected] | 26d84b0 | 2011-08-31 14:07:08 | [diff] [blame] | 160 | int result = |
[email protected] | f3cf980 | 2011-10-28 18:44:58 | [diff] [blame] | 161 | HttpAuthHandlerMock::GenerateAuthTokenImpl(credentials, |
[email protected] | 26d84b0 | 2011-08-31 14:07:08 | [diff] [blame] | 162 | request, callback, |
| 163 | auth_token); |
| 164 | EXPECT_TRUE(result != OK || |
[email protected] | f3cf980 | 2011-10-28 18:44:58 | [diff] [blame] | 165 | !AllowsExplicitCredentials() || |
| 166 | !credentials->Empty()); |
[email protected] | 26d84b0 | 2011-08-31 14:07:08 | [diff] [blame] | 167 | return result; |
| 168 | } |
| 169 | |
| 170 | private: |
| 171 | HttpAuth::Scheme expected_scheme_; |
| 172 | }; |
| 173 | |
tfarina | 4283411 | 2016-09-22 13:38:20 | [diff] [blame] | 174 | NetLogWithSource dummy_log; |
[email protected] | 26d84b0 | 2011-08-31 14:07:08 | [diff] [blame] | 175 | HttpAuthCache dummy_auth_cache; |
| 176 | HttpRequestInfo request; |
| 177 | request.method = "GET"; |
| 178 | request.url = GURL("http://example.com"); |
| 179 | |
| 180 | HttpRequestHeaders request_headers; |
| 181 | scoped_refptr<HttpResponseHeaders> headers(HeadersFromString( |
| 182 | "HTTP/1.1 401\r\n" |
| 183 | "WWW-Authenticate: Mock\r\n" |
| 184 | "WWW-Authenticate: Basic\r\n" |
| 185 | "\r\n")); |
| 186 | |
| 187 | HttpAuthHandlerMock::Factory auth_handler_factory; |
| 188 | |
| 189 | // Handlers for the first attempt at authentication. AUTH_SCHEME_MOCK handler |
| 190 | // accepts the default identity and successfully constructs a token. |
| 191 | auth_handler_factory.AddMockHandler( |
| 192 | new MockHandler(OK, HttpAuth::AUTH_SCHEME_MOCK), HttpAuth::AUTH_SERVER); |
| 193 | auth_handler_factory.AddMockHandler( |
| 194 | new MockHandler(ERR_UNEXPECTED, HttpAuth::AUTH_SCHEME_BASIC), |
| 195 | HttpAuth::AUTH_SERVER); |
| 196 | |
| 197 | // Handlers for the second attempt. Neither should be used to generate a |
| 198 | // token. Instead the controller should realize that there are no viable |
| 199 | // identities to use with the AUTH_SCHEME_MOCK handler and fail. |
| 200 | auth_handler_factory.AddMockHandler( |
| 201 | new MockHandler(ERR_UNEXPECTED, HttpAuth::AUTH_SCHEME_MOCK), |
| 202 | HttpAuth::AUTH_SERVER); |
| 203 | auth_handler_factory.AddMockHandler( |
| 204 | new MockHandler(ERR_UNEXPECTED, HttpAuth::AUTH_SCHEME_BASIC), |
| 205 | HttpAuth::AUTH_SERVER); |
| 206 | |
| 207 | // Fallback handlers for the second attempt. The AUTH_SCHEME_MOCK handler |
| 208 | // should be discarded due to the disabled scheme, and the AUTH_SCHEME_BASIC |
| 209 | // handler should successfully be used to generate a token. |
| 210 | auth_handler_factory.AddMockHandler( |
| 211 | new MockHandler(ERR_UNEXPECTED, HttpAuth::AUTH_SCHEME_MOCK), |
| 212 | HttpAuth::AUTH_SERVER); |
| 213 | auth_handler_factory.AddMockHandler( |
| 214 | new MockHandler(OK, HttpAuth::AUTH_SCHEME_BASIC), |
| 215 | HttpAuth::AUTH_SERVER); |
| 216 | auth_handler_factory.set_do_init_from_challenge(true); |
| 217 | |
| 218 | scoped_refptr<HttpAuthController> controller( |
| 219 | new HttpAuthController(HttpAuth::AUTH_SERVER, |
| 220 | GURL("http://example.com"), |
| 221 | &dummy_auth_cache, &auth_handler_factory)); |
asanka | 5ffd5d7 | 2016-03-23 16:20:49 | [diff] [blame] | 222 | SSLInfo null_ssl_info; |
| 223 | ASSERT_EQ(OK, controller->HandleAuthChallenge(headers, null_ssl_info, false, |
| 224 | false, dummy_log)); |
[email protected] | 26d84b0 | 2011-08-31 14:07:08 | [diff] [blame] | 225 | ASSERT_TRUE(controller->HaveAuthHandler()); |
[email protected] | f3cf980 | 2011-10-28 18:44:58 | [diff] [blame] | 226 | controller->ResetAuth(AuthCredentials()); |
[email protected] | 26d84b0 | 2011-08-31 14:07:08 | [diff] [blame] | 227 | EXPECT_TRUE(controller->HaveAuth()); |
| 228 | |
| 229 | // Should only succeed if we are using the AUTH_SCHEME_MOCK MockHandler. |
[email protected] | 49639fa | 2011-12-20 23:22:41 | [diff] [blame] | 230 | EXPECT_EQ(OK, controller->MaybeGenerateAuthToken( |
| 231 | &request, CompletionCallback(), dummy_log)); |
[email protected] | 26d84b0 | 2011-08-31 14:07:08 | [diff] [blame] | 232 | controller->AddAuthorizationHeader(&request_headers); |
| 233 | |
| 234 | // Once a token is generated, simulate the receipt of a server response |
| 235 | // indicating that the authentication attempt was rejected. |
asanka | 5ffd5d7 | 2016-03-23 16:20:49 | [diff] [blame] | 236 | ASSERT_EQ(OK, controller->HandleAuthChallenge(headers, null_ssl_info, false, |
| 237 | false, dummy_log)); |
[email protected] | 26d84b0 | 2011-08-31 14:07:08 | [diff] [blame] | 238 | ASSERT_TRUE(controller->HaveAuthHandler()); |
[email protected] | ad65a3e | 2013-12-25 18:18:01 | [diff] [blame] | 239 | controller->ResetAuth(AuthCredentials(base::ASCIIToUTF16("Hello"), |
[email protected] | 42cba2fb | 2013-03-29 19:58:57 | [diff] [blame] | 240 | base::string16())); |
[email protected] | 26d84b0 | 2011-08-31 14:07:08 | [diff] [blame] | 241 | EXPECT_TRUE(controller->HaveAuth()); |
| 242 | EXPECT_TRUE(controller->IsAuthSchemeDisabled(HttpAuth::AUTH_SCHEME_MOCK)); |
| 243 | EXPECT_FALSE(controller->IsAuthSchemeDisabled(HttpAuth::AUTH_SCHEME_BASIC)); |
| 244 | |
| 245 | // Should only succeed if we are using the AUTH_SCHEME_BASIC MockHandler. |
[email protected] | 49639fa | 2011-12-20 23:22:41 | [diff] [blame] | 246 | EXPECT_EQ(OK, controller->MaybeGenerateAuthToken( |
| 247 | &request, CompletionCallback(), dummy_log)); |
[email protected] | 26d84b0 | 2011-08-31 14:07:08 | [diff] [blame] | 248 | } |
| 249 | |
[email protected] | 76b0f68 | 2011-03-30 16:54:54 | [diff] [blame] | 250 | } // namespace net |