diff options
Diffstat (limited to 'net/http')
-rw-r--r-- | net/http/http_auth_controller.cc | 18 | ||||
-rw-r--r-- | net/http/http_network_transaction_unittest.cc | 153 |
2 files changed, 161 insertions, 10 deletions
diff --git a/net/http/http_auth_controller.cc b/net/http/http_auth_controller.cc index 07565a1..8a23969 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_unittest.cc b/net/http/http_network_transaction_unittest.cc index b961fdd..75b6a98 100644 --- a/net/http/http_network_transaction_unittest.cc +++ b/net/http/http_network_transaction_unittest.cc @@ -3718,12 +3718,12 @@ TEST_F(HttpNetworkTransactionTest, 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(HttpNetworkTransactionTest, IgnoreAuthIdentityInURL) { +// it fails the identity from the URL is used to answer the challenge. +TEST_F(HttpNetworkTransactionTest, AuthIdentityInURL) { HttpRequestInfo request; request.method = "GET"; + // Note: the URL has a username:password in it. request.url = GURL("http://foo:b@r@www.google.com/"); - request.load_flags = LOAD_NORMAL; SessionDependencies session_deps; scoped_ptr<HttpTransaction> trans( @@ -3733,6 +3733,8 @@ TEST_F(HttpNetworkTransactionTest, IgnoreAuthIdentityInURL) { // will need to be unescaped by HttpNetworkTransaction. EXPECT_EQ("b%40r", request.url.password()); + request.load_flags = LOAD_NORMAL; + MockWrite data_writes1[] = { MockWrite("GET / HTTP/1.1\r\n" "Host: www.google.com\r\n" @@ -3746,17 +3748,162 @@ TEST_F(HttpNetworkTransactionTest, IgnoreAuthIdentityInURL) { MockRead(false, 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(false, 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(HttpNetworkTransactionTest, 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(false, 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(false, 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(false, 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(); } |