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