diff options
Diffstat (limited to 'net')
-rw-r--r-- | net/http/http_auth_controller.cc | 18 | ||||
-rw-r--r-- | net/http/http_network_transaction_spdy2_unittest.cc | 147 | ||||
-rw-r--r-- | net/http/http_network_transaction_spdy3_unittest.cc | 147 | ||||
-rw-r--r-- | net/url_request/url_request_unittest.cc | 30 |
4 files changed, 331 insertions, 11 deletions
diff --git a/net/http/http_auth_controller.cc b/net/http/http_auth_controller.cc index 34c2469..1ab12ad 100644 --- a/net/http/http_auth_controller.cc +++ b/net/http/http_auth_controller.cc @@ -450,17 +450,21 @@ bool HttpAuthController::SelectNextAuthIdentityToTry() { DCHECK(handler_.get()); DCHECK(identity_.invalid); - // Do not try to use the username:password encoded into the URL. At worst, - // this represents a session fixation attack against basic auth, and as it - // turns out, IE hasn't supported this for years. If a caller really wants - // to use embedded identities, the can add an URLRequest::Delegate that - // inspects the URL and supplies the username/password at OnAuthRequired() - // time. Past data shows this is used extremely infrequently in web pages, - // but continue to collect this data. + // Try to use the username:password encoded into the URL first. if (target_ == HttpAuth::AUTH_SERVER && auth_url_.has_username() && !embedded_identity_used_) { + identity_.source = HttpAuth::IDENT_SRC_URL; + identity_.invalid = false; + // Extract the username:password from the URL. + string16 username; + string16 password; + GetIdentityFromURL(auth_url_, &username, &password); + identity_.credentials.Set(username, password); embedded_identity_used_ = true; + // TODO(eroman): If the password is blank, should we also try combining + // with a password from the cache? UMA_HISTOGRAM_BOOLEAN("net.HttpIdentSrcURL", true); + return true; } // Check the auth cache for a realm entry. diff --git a/net/http/http_network_transaction_spdy2_unittest.cc b/net/http/http_network_transaction_spdy2_unittest.cc index 746cbe9..27470e0 100644 --- a/net/http/http_network_transaction_spdy2_unittest.cc +++ b/net/http/http_network_transaction_spdy2_unittest.cc @@ -3727,8 +3727,8 @@ TEST_F(HttpNetworkTransactionSpdy2Test, ResendRequestOnWriteBodyError) { // Test the request-challenge-retry sequence for basic auth when there is // an identity in the URL. The request should be sent as normal, but when -// it fails the identity from the URL is no longer used. -TEST_F(HttpNetworkTransactionSpdy2Test, IgnoreAuthIdentityInURL) { +// it fails the identity from the URL is used to answer the challenge. +TEST_F(HttpNetworkTransactionSpdy2Test, AuthIdentityInURL) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://foo:b@r@www.google.com/"); @@ -3755,17 +3755,160 @@ TEST_F(HttpNetworkTransactionSpdy2Test, IgnoreAuthIdentityInURL) { MockRead(SYNCHRONOUS, ERR_FAILED), }; + // After the challenge above, the transaction will be restarted using the + // identity from the url (foo, b@r) to answer the challenge. + MockWrite data_writes2[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJAcg==\r\n\r\n"), + }; + + MockRead data_reads2[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); TestCompletionCallback callback1; int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); EXPECT_EQ(ERR_IO_PENDING, rv); rv = callback1.WaitForResult(); EXPECT_EQ(OK, rv); + EXPECT_TRUE(trans->IsReadyToRestartForAuth()); + + TestCompletionCallback callback2; + rv = trans->RestartWithAuth(AuthCredentials(), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); EXPECT_FALSE(trans->IsReadyToRestartForAuth()); + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + // There is no challenge info, since the identity in URL worked. + EXPECT_TRUE(response->auth_challenge.get() == NULL); + + EXPECT_EQ(100, response->headers->GetContentLength()); + + // Empty the current queue. + MessageLoop::current()->RunAllPending(); +} + +// Test the request-challenge-retry sequence for basic auth when there is an +// incorrect identity in the URL. The identity from the URL should be used only +// once. +TEST_F(HttpNetworkTransactionSpdy2Test, WrongAuthIdentityInURL) { + HttpRequestInfo request; + request.method = "GET"; + // Note: the URL has a username:password in it. The password "baz" is + // wrong (should be "bar"). + request.url = GURL("http://foo:baz@www.google.com/"); + + request.load_flags = LOAD_NORMAL; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes1[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.0 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Length: 10\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + // After the challenge above, the transaction will be restarted using the + // identity from the url (foo, baz) to answer the challenge. + MockWrite data_writes2[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJheg==\r\n\r\n"), + }; + + MockRead data_reads2[] = { + MockRead("HTTP/1.0 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Length: 10\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + // After the challenge above, the transaction will be restarted using the + // identity supplied by the user (foo, bar) to answer the challenge. + MockWrite data_writes3[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + MockRead data_reads3[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + StaticSocketDataProvider data3(data_reads3, arraysize(data_reads3), + data_writes3, arraysize(data_writes3)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + session_deps.socket_factory.AddSocketDataProvider(&data3); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + EXPECT_TRUE(trans->IsReadyToRestartForAuth()); + TestCompletionCallback callback2; + rv = trans->RestartWithAuth(AuthCredentials(), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + EXPECT_FALSE(trans->IsReadyToRestartForAuth()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback3; + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback3.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback3.WaitForResult(); + EXPECT_EQ(OK, rv); + EXPECT_FALSE(trans->IsReadyToRestartForAuth()); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + // There is no challenge info, since the identity worked. + EXPECT_TRUE(response->auth_challenge.get() == NULL); + + EXPECT_EQ(100, response->headers->GetContentLength()); + // Empty the current queue. MessageLoop::current()->RunAllPending(); } diff --git a/net/http/http_network_transaction_spdy3_unittest.cc b/net/http/http_network_transaction_spdy3_unittest.cc index 2a9cbe0..6f7468d 100644 --- a/net/http/http_network_transaction_spdy3_unittest.cc +++ b/net/http/http_network_transaction_spdy3_unittest.cc @@ -3727,8 +3727,8 @@ TEST_F(HttpNetworkTransactionSpdy3Test, ResendRequestOnWriteBodyError) { // Test the request-challenge-retry sequence for basic auth when there is // an identity in the URL. The request should be sent as normal, but when -// it fails the identity from the URL is no longer used. -TEST_F(HttpNetworkTransactionSpdy3Test, IgnoreAuthIdentityInURL) { +// it fails the identity from the URL is used to answer the challenge. +TEST_F(HttpNetworkTransactionSpdy3Test, AuthIdentityInURL) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://foo:b@r@www.google.com/"); @@ -3755,17 +3755,160 @@ TEST_F(HttpNetworkTransactionSpdy3Test, IgnoreAuthIdentityInURL) { MockRead(SYNCHRONOUS, ERR_FAILED), }; + // After the challenge above, the transaction will be restarted using the + // identity from the url (foo, b@r) to answer the challenge. + MockWrite data_writes2[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJAcg==\r\n\r\n"), + }; + + MockRead data_reads2[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); TestCompletionCallback callback1; int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); EXPECT_EQ(ERR_IO_PENDING, rv); rv = callback1.WaitForResult(); EXPECT_EQ(OK, rv); + EXPECT_TRUE(trans->IsReadyToRestartForAuth()); + + TestCompletionCallback callback2; + rv = trans->RestartWithAuth(AuthCredentials(), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); EXPECT_FALSE(trans->IsReadyToRestartForAuth()); + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + // There is no challenge info, since the identity in URL worked. + EXPECT_TRUE(response->auth_challenge.get() == NULL); + + EXPECT_EQ(100, response->headers->GetContentLength()); + + // Empty the current queue. + MessageLoop::current()->RunAllPending(); +} + +// Test the request-challenge-retry sequence for basic auth when there is an +// incorrect identity in the URL. The identity from the URL should be used only +// once. +TEST_F(HttpNetworkTransactionSpdy3Test, WrongAuthIdentityInURL) { + HttpRequestInfo request; + request.method = "GET"; + // Note: the URL has a username:password in it. The password "baz" is + // wrong (should be "bar"). + request.url = GURL("http://foo:baz@www.google.com/"); + + request.load_flags = LOAD_NORMAL; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes1[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.0 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Length: 10\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + // After the challenge above, the transaction will be restarted using the + // identity from the url (foo, baz) to answer the challenge. + MockWrite data_writes2[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJheg==\r\n\r\n"), + }; + + MockRead data_reads2[] = { + MockRead("HTTP/1.0 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Length: 10\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + // After the challenge above, the transaction will be restarted using the + // identity supplied by the user (foo, bar) to answer the challenge. + MockWrite data_writes3[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + MockRead data_reads3[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + StaticSocketDataProvider data3(data_reads3, arraysize(data_reads3), + data_writes3, arraysize(data_writes3)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + session_deps.socket_factory.AddSocketDataProvider(&data3); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + EXPECT_TRUE(trans->IsReadyToRestartForAuth()); + TestCompletionCallback callback2; + rv = trans->RestartWithAuth(AuthCredentials(), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + EXPECT_FALSE(trans->IsReadyToRestartForAuth()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback3; + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback3.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback3.WaitForResult(); + EXPECT_EQ(OK, rv); + EXPECT_FALSE(trans->IsReadyToRestartForAuth()); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + // There is no challenge info, since the identity worked. + EXPECT_TRUE(response->auth_challenge.get() == NULL); + + EXPECT_EQ(100, response->headers->GetContentLength()); + // Empty the current queue. MessageLoop::current()->RunAllPending(); } diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc index 9fd411e..7a7038f 100644 --- a/net/url_request/url_request_unittest.cc +++ b/net/url_request/url_request_unittest.cc @@ -2935,6 +2935,36 @@ TEST_F(URLRequestTestHTTP, BasicAuthWithCookies) { EXPECT_TRUE(d.data_received().find("Cookie: got_challenged=true") != std::string::npos); } + + // Same test as above, except this time the restart is initiated earlier + // (without user intervention since identity is embedded in the URL). + { + TestNetworkDelegate network_delegate; // must outlive URLRequest + TestURLRequestContext context(true); + context.set_network_delegate(&network_delegate); + context.Init(); + + TestDelegate d; + + GURL::Replacements replacements; + std::string username("user2"); + std::string password("secret"); + replacements.SetUsernameStr(username); + replacements.SetPasswordStr(password); + GURL url_with_identity = url_requiring_auth.ReplaceComponents(replacements); + + URLRequest r(url_with_identity, &d); + r.set_context(&context); + r.Start(); + + MessageLoop::current()->Run(); + + EXPECT_TRUE(d.data_received().find("user2/secret") != std::string::npos); + + // Make sure we sent the cookie in the restarted transaction. + EXPECT_TRUE(d.data_received().find("Cookie: got_challenged=true") + != std::string::npos); + } } TEST_F(URLRequestTest, DelayedCookieCallback) { |