| 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.
|
|
|