Index: net/http/http_network_transaction_unittest.cc |
=================================================================== |
--- net/http/http_network_transaction_unittest.cc (revision 11487) |
+++ net/http/http_network_transaction_unittest.cc (working copy) |
@@ -8,6 +8,7 @@ |
#include "net/base/client_socket_factory.h" |
#include "net/base/test_completion_callback.h" |
#include "net/base/upload_data.h" |
+#include "net/http/http_auth_handler_ntlm.h" |
#include "net/http/http_network_session.h" |
#include "net/http/http_network_transaction.h" |
#include "net/http/http_transaction_unittest.h" |
@@ -295,6 +296,40 @@ |
str->append(row, sizeof_row); |
} |
+// Alternative functions that eliminate randomness and dependency on the local |
+// host name so that the generated NTLM messages are reproducible. |
+void MyGenerateRandom1(uint8* output, size_t n) { |
+ static const uint8 bytes[] = { |
+ 0x55, 0x29, 0x66, 0x26, 0x6b, 0x9c, 0x73, 0x54 |
+ }; |
+ static size_t current_byte = 0; |
+ for (size_t i = 0; i < n; ++i) { |
+ output[i] = bytes[current_byte++]; |
+ current_byte %= arraysize(bytes); |
+ } |
+} |
+ |
+void MyGenerateRandom2(uint8* output, size_t n) { |
+ static const uint8 bytes[] = { |
+ 0x96, 0x79, 0x85, 0xe7, 0x49, 0x93, 0x70, 0xa1, |
+ 0x4e, 0xe7, 0x87, 0x45, 0x31, 0x5b, 0xd3, 0x1f |
+ }; |
+ static size_t current_byte = 0; |
+ for (size_t i = 0; i < n; ++i) { |
+ output[i] = bytes[current_byte++]; |
+ current_byte %= arraysize(bytes); |
+ } |
+} |
+ |
+void MyGetHostName(char* name, size_t namelen) { |
+ static const char hostname[] = "WTC-WIN7"; |
+ if (namelen >= arraysize(hostname)) { |
+ memcpy(name, hostname, arraysize(hostname)); |
+ } else { |
+ name[0] = '\0'; |
+ } |
+} |
+ |
//----------------------------------------------------------------------------- |
TEST_F(HttpNetworkTransactionTest, Basic) { |
@@ -1535,10 +1570,15 @@ |
EXPECT_EQ(100, response->headers->GetContentLength()); |
} |
-// Test NTLM authentication. |
-// TODO(wtc): This test doesn't work because we need to control the 8 random |
-// bytes and the "workstation name" for a deterministic expected result. |
-TEST_F(HttpNetworkTransactionTest, DISABLED_NTLMAuth) { |
+// The NTLM authentication unit tests were generated by capturing the HTTP |
+// requests and responses using Fiddler 2 and inspecting the generated random |
+// bytes in the debugger. |
+ |
+// Enter the correct password and authenticate successfully. |
+TEST_F(HttpNetworkTransactionTest, NTLMAuth1) { |
+ net::HttpAuthHandlerNTLM::SetGenerateRandomProc(MyGenerateRandom1); |
+ net::HttpAuthHandlerNTLM::SetHostNameProc(MyGetHostName); |
+ |
scoped_ptr<net::ProxyService> proxy_service(CreateNullProxyService()); |
scoped_ptr<net::HttpTransaction> trans(new net::HttpNetworkTransaction( |
CreateSession(proxy_service.get()), &mock_socket_factory)); |
@@ -1583,18 +1623,18 @@ |
MockWrite("GET /kids/login.aspx HTTP/1.1\r\n" |
"Host: 172.22.68.17\r\n" |
"Connection: keep-alive\r\n" |
- "Authorization: NTLM TlRMTVNTUAADAAAAGAAYAHAAAAAYABgAiA" |
- "AAAAAAAABAAAAAGAAYAEAAAAAYABgAWAAAAAAAAAAAAAAABYIIAHQA" |
- "ZQBzAHQAaQBuAGcALQBuAHQAbABtAHcAdABjAGgAYQBuAGcALQBjAG" |
- "8AcgBwAMertjYHfqUhAAAAAAAAAAAAAAAAAAAAAEP3kddZKtMDMssm" |
- "KYA6SCllVGUeyoQppQ==\r\n\r\n"), |
+ "Authorization: NTLM TlRMTVNTUAADAAAAGAAYAGgAAAAYABgAgA" |
+ "AAAAAAAABAAAAAGAAYAEAAAAAQABAAWAAAAAAAAAAAAAAABYIIAHQA" |
+ "ZQBzAHQAaQBuAGcALQBuAHQAbABtAFcAVABDAC0AVwBJAE4ANwBVKW" |
+ "Yma5xzVAAAAAAAAAAAAAAAAAAAAACH+gWcm+YsP9Tqb9zCR3WAeZZX" |
+ "ahlhx5I=\r\n\r\n"), |
}; |
MockRead data_reads2[] = { |
// The origin server responds with a Type 2 message. |
MockRead("HTTP/1.1 401 Access Denied\r\n"), |
MockRead("WWW-Authenticate: NTLM " |
- "TlRMTVNTUAACAAAADAAMADgAAAAFgokCTroKF1e/DRcAAAAAAAAAALo" |
+ "TlRMTVNTUAACAAAADAAMADgAAAAFgokCjGpMpPGlYKkAAAAAAAAAALo" |
"AugBEAAAABQEoCgAAAA9HAE8ATwBHAEwARQACAAwARwBPAE8ARwBMAE" |
"UAAQAaAEEASwBFAEUAUwBBAFIAQQAtAEMATwBSAFAABAAeAGMAbwByA" |
"HAALgBnAG8AbwBnAGwAZQAuAGMAbwBtAAMAQABhAGsAZQBlAHMAYQBy" |
@@ -1643,10 +1683,6 @@ |
EXPECT_EQ(L"", response->auth_challenge->realm); |
EXPECT_EQ(L"ntlm", response->auth_challenge->scheme); |
- // Pass a null identity to the first RestartWithAuth. |
- // TODO(wtc): In the future we may pass the actual identity to the first |
- // RestartWithAuth. |
- |
TestCompletionCallback callback2; |
rv = trans->RestartWithAuth(L"testing-ntlm", L"testing-ntlm", &callback2); |
@@ -1660,6 +1696,204 @@ |
EXPECT_EQ(13, response->headers->GetContentLength()); |
} |
+// Enter a wrong password, and then the correct one. |
+TEST_F(HttpNetworkTransactionTest, NTLMAuth2) { |
+ net::HttpAuthHandlerNTLM::SetGenerateRandomProc(MyGenerateRandom2); |
+ net::HttpAuthHandlerNTLM::SetHostNameProc(MyGetHostName); |
+ |
+ scoped_ptr<net::ProxyService> proxy_service(CreateNullProxyService()); |
+ scoped_ptr<net::HttpTransaction> trans(new net::HttpNetworkTransaction( |
+ CreateSession(proxy_service.get()), &mock_socket_factory)); |
+ |
+ net::HttpRequestInfo request; |
+ request.method = "GET"; |
+ request.url = GURL("http://172.22.68.17/kids/login.aspx"); |
+ request.load_flags = 0; |
+ |
+ MockWrite data_writes1[] = { |
+ MockWrite("GET /kids/login.aspx HTTP/1.1\r\n" |
+ "Host: 172.22.68.17\r\n" |
+ "Connection: keep-alive\r\n\r\n"), |
+ }; |
+ |
+ MockRead data_reads1[] = { |
+ MockRead("HTTP/1.1 401 Access Denied\r\n"), |
+ // Negotiate and NTLM are often requested together. We only support NTLM. |
+ MockRead("WWW-Authenticate: Negotiate\r\n"), |
+ MockRead("WWW-Authenticate: NTLM\r\n"), |
+ MockRead("Connection: close\r\n"), |
+ MockRead("Content-Length: 42\r\n"), |
+ MockRead("Content-Type: text/html\r\n"), |
+ MockRead("Proxy-Support: Session-Based-Authentication\r\n\r\n"), |
+ // Missing content -- won't matter, as connection will be reset. |
+ MockRead(false, net::ERR_UNEXPECTED), |
+ }; |
+ |
+ MockWrite data_writes2[] = { |
+ // After automatically restarting with a null identity, this is the |
+ // request we should be issuing -- the final header line contains a Type |
+ // 1 message. |
+ MockWrite("GET /kids/login.aspx HTTP/1.1\r\n" |
+ "Host: 172.22.68.17\r\n" |
+ "Connection: keep-alive\r\n" |
+ "Authorization: NTLM " |
+ "TlRMTVNTUAABAAAAB4IIAAAAAAAAAAAAAAAAAAAAAAA=\r\n\r\n"), |
+ |
+ // After calling trans->RestartWithAuth(), we should send a Type 3 message |
+ // (the credentials for the origin server). The second request continues |
+ // on the same connection. |
+ MockWrite("GET /kids/login.aspx HTTP/1.1\r\n" |
+ "Host: 172.22.68.17\r\n" |
+ "Connection: keep-alive\r\n" |
+ "Authorization: NTLM TlRMTVNTUAADAAAAGAAYAGgAAAAYABgAgA" |
+ "AAAAAAAABAAAAAGAAYAEAAAAAQABAAWAAAAAAAAAAAAAAABYIIAHQA" |
+ "ZQBzAHQAaQBuAGcALQBuAHQAbABtAFcAVABDAC0AVwBJAE4ANwCWeY" |
+ "XnSZNwoQAAAAAAAAAAAAAAAAAAAADLa34/phTTKzNTWdub+uyFleOj" |
+ "4Ww7b7E=\r\n\r\n"), |
+ }; |
+ |
+ MockRead data_reads2[] = { |
+ // The origin server responds with a Type 2 message. |
+ MockRead("HTTP/1.1 401 Access Denied\r\n"), |
+ MockRead("WWW-Authenticate: NTLM " |
+ "TlRMTVNTUAACAAAADAAMADgAAAAFgokCbVWUZezVGpAAAAAAAAAAALo" |
+ "AugBEAAAABQEoCgAAAA9HAE8ATwBHAEwARQACAAwARwBPAE8ARwBMAE" |
+ "UAAQAaAEEASwBFAEUAUwBBAFIAQQAtAEMATwBSAFAABAAeAGMAbwByA" |
+ "HAALgBnAG8AbwBnAGwAZQAuAGMAbwBtAAMAQABhAGsAZQBlAHMAYQBy" |
+ "AGEALQBjAG8AcgBwAC4AYQBkAC4AYwBvAHIAcAAuAGcAbwBvAGcAbAB" |
+ "lAC4AYwBvAG0ABQAeAGMAbwByAHAALgBnAG8AbwBnAGwAZQAuAGMAbw" |
+ "BtAAAAAAA=\r\n"), |
+ MockRead("Content-Length: 42\r\n"), |
+ MockRead("Content-Type: text/html\r\n"), |
+ MockRead("Proxy-Support: Session-Based-Authentication\r\n\r\n"), |
+ MockRead("You are not authorized to view this page\r\n"), |
+ |
+ // Wrong password. |
+ MockRead("HTTP/1.1 401 Access Denied\r\n"), |
+ MockRead("WWW-Authenticate: Negotiate\r\n"), |
+ MockRead("WWW-Authenticate: NTLM\r\n"), |
+ MockRead("Connection: close\r\n"), |
+ MockRead("Content-Length: 42\r\n"), |
+ MockRead("Content-Type: text/html\r\n"), |
+ MockRead("Proxy-Support: Session-Based-Authentication\r\n\r\n"), |
+ // Missing content -- won't matter, as connection will be reset. |
+ MockRead(false, net::ERR_UNEXPECTED), |
+ }; |
+ |
+ MockWrite data_writes3[] = { |
+ // After automatically restarting with a null identity, this is the |
+ // request we should be issuing -- the final header line contains a Type |
+ // 1 message. |
+ MockWrite("GET /kids/login.aspx HTTP/1.1\r\n" |
+ "Host: 172.22.68.17\r\n" |
+ "Connection: keep-alive\r\n" |
+ "Authorization: NTLM " |
+ "TlRMTVNTUAABAAAAB4IIAAAAAAAAAAAAAAAAAAAAAAA=\r\n\r\n"), |
+ |
+ // After calling trans->RestartWithAuth(), we should send a Type 3 message |
+ // (the credentials for the origin server). The second request continues |
+ // on the same connection. |
+ MockWrite("GET /kids/login.aspx HTTP/1.1\r\n" |
+ "Host: 172.22.68.17\r\n" |
+ "Connection: keep-alive\r\n" |
+ "Authorization: NTLM TlRMTVNTUAADAAAAGAAYAGgAAAAYABgAgA" |
+ "AAAAAAAABAAAAAGAAYAEAAAAAQABAAWAAAAAAAAAAAAAAABYIIAHQA" |
+ "ZQBzAHQAaQBuAGcALQBuAHQAbABtAFcAVABDAC0AVwBJAE4ANwBO54" |
+ "dFMVvTHwAAAAAAAAAAAAAAAAAAAACS7sT6Uzw7L0L//WUqlIaVWpbI" |
+ "+4MUm7c=\r\n\r\n"), |
+ }; |
+ |
+ MockRead data_reads3[] = { |
+ // The origin server responds with a Type 2 message. |
+ MockRead("HTTP/1.1 401 Access Denied\r\n"), |
+ MockRead("WWW-Authenticate: NTLM " |
+ "TlRMTVNTUAACAAAADAAMADgAAAAFgokCL24VN8dgOR8AAAAAAAAAALo" |
+ "AugBEAAAABQEoCgAAAA9HAE8ATwBHAEwARQACAAwARwBPAE8ARwBMAE" |
+ "UAAQAaAEEASwBFAEUAUwBBAFIAQQAtAEMATwBSAFAABAAeAGMAbwByA" |
+ "HAALgBnAG8AbwBnAGwAZQAuAGMAbwBtAAMAQABhAGsAZQBlAHMAYQBy" |
+ "AGEALQBjAG8AcgBwAC4AYQBkAC4AYwBvAHIAcAAuAGcAbwBvAGcAbAB" |
+ "lAC4AYwBvAG0ABQAeAGMAbwByAHAALgBnAG8AbwBnAGwAZQAuAGMAbw" |
+ "BtAAAAAAA=\r\n"), |
+ MockRead("Content-Length: 42\r\n"), |
+ MockRead("Content-Type: text/html\r\n"), |
+ MockRead("Proxy-Support: Session-Based-Authentication\r\n\r\n"), |
+ MockRead("You are not authorized to view this page\r\n"), |
+ |
+ // Lastly we get the desired content. |
+ MockRead("HTTP/1.1 200 OK\r\n"), |
+ MockRead("Content-Type: text/html; charset=utf-8\r\n"), |
+ MockRead("Content-Length: 13\r\n\r\n"), |
+ MockRead("Please Login\r\n"), |
+ MockRead(false, net::OK), |
+ }; |
+ |
+ MockSocket data1; |
+ data1.reads = data_reads1; |
+ data1.writes = data_writes1; |
+ MockSocket data2; |
+ data2.reads = data_reads2; |
+ data2.writes = data_writes2; |
+ MockSocket data3; |
+ data3.reads = data_reads3; |
+ data3.writes = data_writes3; |
+ mock_sockets[0] = &data1; |
+ mock_sockets[1] = &data2; |
+ mock_sockets[2] = &data3; |
+ mock_sockets[3] = NULL; |
+ |
+ TestCompletionCallback callback1; |
+ |
+ int rv = trans->Start(&request, &callback1); |
+ EXPECT_EQ(net::ERR_IO_PENDING, rv); |
+ |
+ rv = callback1.WaitForResult(); |
+ EXPECT_EQ(net::OK, rv); |
+ |
+ const net::HttpResponseInfo* response = trans->GetResponseInfo(); |
+ EXPECT_FALSE(response == NULL); |
+ |
+ // The password prompt info should have been set in response->auth_challenge. |
+ EXPECT_FALSE(response->auth_challenge.get() == NULL); |
+ |
+ // TODO(eroman): this should really include the effective port (80) |
+ EXPECT_EQ(L"172.22.68.17", response->auth_challenge->host); |
+ EXPECT_EQ(L"", response->auth_challenge->realm); |
+ EXPECT_EQ(L"ntlm", response->auth_challenge->scheme); |
+ |
+ TestCompletionCallback callback2; |
+ |
+ // Enter the wrong password. |
+ rv = trans->RestartWithAuth(L"testing-ntlm", L"wrongpassword", &callback2); |
+ EXPECT_EQ(net::ERR_IO_PENDING, rv); |
+ |
+ rv = callback2.WaitForResult(); |
+ EXPECT_EQ(net::OK, rv); |
+ |
+ response = trans->GetResponseInfo(); |
+ EXPECT_FALSE(response == NULL); |
+ |
+ // The password prompt info should have been set in response->auth_challenge. |
+ EXPECT_FALSE(response->auth_challenge.get() == NULL); |
+ |
+ // TODO(eroman): this should really include the effective port (80) |
+ EXPECT_EQ(L"172.22.68.17", response->auth_challenge->host); |
+ EXPECT_EQ(L"", response->auth_challenge->realm); |
+ EXPECT_EQ(L"ntlm", response->auth_challenge->scheme); |
+ |
+ TestCompletionCallback callback3; |
+ |
+ // Now enter the right password. |
+ rv = trans->RestartWithAuth(L"testing-ntlm", L"testing-ntlm", &callback3); |
+ EXPECT_EQ(net::ERR_IO_PENDING, rv); |
+ |
+ rv = callback3.WaitForResult(); |
+ EXPECT_EQ(net::OK, rv); |
+ |
+ response = trans->GetResponseInfo(); |
+ EXPECT_TRUE(response->auth_challenge.get() == NULL); |
+ EXPECT_EQ(13, response->headers->GetContentLength()); |
+} |
+ |
// Test reading a server response which has only headers, and no body. |
// After some maximum number of bytes is consumed, the transaction should |
// fail with ERR_RESPONSE_HEADERS_TOO_BIG. |