Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(694)

Unified Diff: net/websockets/websocket_stream_test.cc

Issue 336263005: Map WebSocket URL schemes to HTTP URL schemes for auth purposes. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Replace accidentally removed #include <string> Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« net/http/http_network_transaction.cc ('K') | « net/websockets/websocket_stream.cc ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: net/websockets/websocket_stream_test.cc
diff --git a/net/websockets/websocket_stream_test.cc b/net/websockets/websocket_stream_test.cc
index b7229f93188a43543b4871d98f5f418de70503ff..653e367b36ac96a074e479fcf74314c2e044b6f8 100644
--- a/net/websockets/websocket_stream_test.cc
+++ b/net/websockets/websocket_stream_test.cc
@@ -56,6 +56,29 @@ std::vector<HeaderKeyValuePair> ToVector(const HttpResponseHeaders& headers) {
return result;
}
+// Simple builder for a DeterministicSocketData object to save repetitive code.
+// It always sets the connect data to MockConnect(SYNCHRONOUS, OK), so it cannot
+// be used in tests where the connect fails. In practice, those tests never have
+// any read/write data and so can't benefit from it anyway. The arrays are not
+// copied. It is up to the caller to ensure they stay in scope until the test
+// ends.
+template <size_t reads_count, size_t writes_count>
+scoped_ptr<DeterministicSocketData> BuildSocketData(
+ MockRead (&reads)[reads_count],
+ MockWrite (&writes)[writes_count]) {
+ scoped_ptr<DeterministicSocketData> socket_data(
+ new DeterministicSocketData(reads, reads_count, writes, writes_count));
+ socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
+ socket_data->SetStop(reads_count + writes_count);
+ return socket_data.Pass();
+}
+
+// Builder for a DeterministicSocketData that expects nothing. This does not
+// set the connect data, so the calling code must do that explicitly.
+scoped_ptr<DeterministicSocketData> BuildNullSocketData() {
+ return make_scoped_ptr(new DeterministicSocketData(NULL, 0, NULL, 0));
+}
+
// A sub-class of WebSocketHandshakeStreamCreateHelper which always sets a
// deterministic key to use in the WebSocket handshake.
class DeterministicKeyWebSocketHandshakeStreamCreateHelper
@@ -111,10 +134,15 @@ class WebSocketStreamCreateTest : public ::testing::Test {
const std::vector<std::string>& sub_protocols,
const std::string& origin,
scoped_ptr<DeterministicSocketData> socket_data) {
- url_request_context_host_.AddRawExpectations(socket_data.Pass());
+ AddRawExpectations(socket_data.Pass());
CreateAndConnectStream(socket_url, sub_protocols, origin);
}
+ // Add additional raw expectations for sockets created before the final one.
+ void AddRawExpectations(scoped_ptr<DeterministicSocketData> socket_data) {
+ url_request_context_host_.AddRawExpectations(socket_data.Pass());
+ }
+
// A wrapper for CreateAndConnectStreamForTesting that knows about our default
// parameters.
void CreateAndConnectStream(const std::string& socket_url,
@@ -167,8 +195,8 @@ class WebSocketStreamCreateTest : public ::testing::Test {
virtual void OnStartOpeningHandshake(
scoped_ptr<WebSocketHandshakeRequestInfo> request) OVERRIDE {
- if (owner_->request_info_)
- ADD_FAILURE();
+ // Can be called multiple times (in the case of HTTP auth). Last call
+ // wins.
owner_->request_info_ = request.Pass();
}
virtual void OnFinishOpeningHandshake(
@@ -226,6 +254,132 @@ class WebSocketStreamCreateExtensionTest : public WebSocketStreamCreateTest {
}
};
+// Common code to construct expectations for authentication tests that receive
+// the auth challenge on one connection and then create a second connection to
+// send the authenticated request on.
+class CommonAuthTestHelper {
+ public:
+ CommonAuthTestHelper() : reads1_(), writes1_(), reads2_(), writes2_() {}
+
+ scoped_ptr<DeterministicSocketData> BuildSocketData1(
+ const std::string& response) {
+ request1_ = WebSocketStandardRequest("/", "http://localhost", "");
+ writes1_[0] = MockWrite(SYNCHRONOUS, 0, request1_.c_str());
+ response1_ = response;
+ reads1_[0] = MockRead(SYNCHRONOUS, 1, response1_.c_str());
+ reads1_[1] = MockRead(SYNCHRONOUS, OK, 2); // Close connection
+
+ return BuildSocketData(reads1_, writes1_);
+ }
+
+ scoped_ptr<DeterministicSocketData> BuildSocketData2(
+ const std::string& request,
+ const std::string& response) {
+ request2_ = request;
+ response2_ = response;
+ writes2_[0] = MockWrite(SYNCHRONOUS, 0, request2_.c_str());
+ reads2_[0] = MockRead(SYNCHRONOUS, 1, response2_.c_str());
+ return BuildSocketData(reads2_, writes2_);
+ }
+
+ private:
+ // These need to be object-scoped since they have to remain valid until all
+ // socket operations in the test are complete.
+ std::string request1_;
+ std::string request2_;
+ std::string response1_;
+ std::string response2_;
+ MockRead reads1_[2];
+ MockWrite writes1_[1];
+ MockRead reads2_[1];
+ MockWrite writes2_[1];
+
+ DISALLOW_COPY_AND_ASSIGN(CommonAuthTestHelper);
+};
+
+// Data and methods for BasicAuth tests.
+class WebSocketStreamCreateBasicAuthTest : public WebSocketStreamCreateTest {
+ protected:
+ void CreateAndConnectAuthHandshake(const std::string& url,
+ const std::string& base64_user_pass,
+ const std::string& response2) {
+ AddRawExpectations(helper_.BuildSocketData1(kUnauthorizedResponse));
+
+ static const char request2format[] =
+ "GET / HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: Upgrade\r\n"
+ "Pragma: no-cache\r\n"
+ "Cache-Control: no-cache\r\n"
+ "Authorization: Basic %s\r\n"
+ "Upgrade: websocket\r\n"
+ "Origin: http://localhost\r\n"
+ "Sec-WebSocket-Version: 13\r\n"
+ "User-Agent:\r\n"
+ "Accept-Encoding: gzip,deflate\r\n"
+ "Accept-Language: en-us,fr\r\n"
+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
+ "Sec-WebSocket-Extensions: permessage-deflate; "
+ "client_max_window_bits\r\n"
+ "\r\n";
+ const std::string request =
+ base::StringPrintf(request2format, base64_user_pass.c_str());
+ CreateAndConnectRawExpectations(
+ url,
+ NoSubProtocols(),
+ "http://localhost",
+ helper_.BuildSocketData2(request, response2));
+ }
+
+ static const char kUnauthorizedResponse[];
+
+ CommonAuthTestHelper helper_;
+};
+
+class WebSocketStreamCreateDigestAuthTest : public WebSocketStreamCreateTest {
+ protected:
+ static const char kUnauthorizedResponse[];
+ static const char kAuthorizedRequest[];
+
+ CommonAuthTestHelper helper_;
+};
+
+const char WebSocketStreamCreateBasicAuthTest::kUnauthorizedResponse[] =
+ "HTTP/1.1 401 Unauthorized\r\n"
+ "Content-Length: 0\r\n"
+ "WWW-Authenticate: Basic realm=\"camelot\"\r\n"
+ "\r\n";
+
+// These negotiation values are borrowed from
+// http_auth_handler_digest_unittest.cc. Feel free to come up with new ones if
+// you are bored. Only the weakest (no qop) variants of Digest authentication
+// can be tested by this method, because the others involve random input.
+const char WebSocketStreamCreateDigestAuthTest::kUnauthorizedResponse[] =
+ "HTTP/1.1 401 Unauthorized\r\n"
+ "Content-Length: 0\r\n"
+ "WWW-Authenticate: Digest realm=\"Oblivion\", nonce=\"nonce-value\"\r\n"
+ "\r\n";
+
+const char WebSocketStreamCreateDigestAuthTest::kAuthorizedRequest[] =
+ "GET / HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: Upgrade\r\n"
+ "Pragma: no-cache\r\n"
+ "Cache-Control: no-cache\r\n"
+ "Authorization: Digest username=\"FooBar\", realm=\"Oblivion\", "
+ "nonce=\"nonce-value\", uri=\"/\", "
+ "response=\"f72ff54ebde2f928860f806ec04acd1b\"\r\n"
+ "Upgrade: websocket\r\n"
+ "Origin: http://localhost\r\n"
+ "Sec-WebSocket-Version: 13\r\n"
+ "User-Agent:\r\n"
+ "Accept-Encoding: gzip,deflate\r\n"
+ "Accept-Language: en-us,fr\r\n"
+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
+ "Sec-WebSocket-Extensions: permessage-deflate; "
+ "client_max_window_bits\r\n"
+ "\r\n";
+
class WebSocketStreamCreateUMATest : public ::testing::Test {
public:
// This enum should match with the enum in Delegate in websocket_stream.cc.
@@ -918,8 +1072,7 @@ TEST_F(WebSocketStreamCreateTest, Cancellation) {
// Connect failure must look just like negotiation failure.
TEST_F(WebSocketStreamCreateTest, ConnectionFailure) {
- scoped_ptr<DeterministicSocketData> socket_data(
- new DeterministicSocketData(NULL, 0, NULL, 0));
+ scoped_ptr<DeterministicSocketData> socket_data(BuildNullSocketData());
socket_data->set_connect_data(
MockConnect(SYNCHRONOUS, ERR_CONNECTION_REFUSED));
CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(),
@@ -934,8 +1087,7 @@ TEST_F(WebSocketStreamCreateTest, ConnectionFailure) {
// Connect timeout must look just like any other failure.
TEST_F(WebSocketStreamCreateTest, ConnectionTimeout) {
- scoped_ptr<DeterministicSocketData> socket_data(
- new DeterministicSocketData(NULL, 0, NULL, 0));
+ scoped_ptr<DeterministicSocketData> socket_data(BuildNullSocketData());
socket_data->set_connect_data(
MockConnect(ASYNC, ERR_CONNECTION_TIMED_OUT));
CreateAndConnectRawExpectations("ws://localhost/", NoSubProtocols(),
@@ -948,8 +1100,7 @@ TEST_F(WebSocketStreamCreateTest, ConnectionTimeout) {
// Cancellation during connect works.
TEST_F(WebSocketStreamCreateTest, CancellationDuringConnect) {
- scoped_ptr<DeterministicSocketData> socket_data(
- new DeterministicSocketData(NULL, 0, NULL, 0));
+ scoped_ptr<DeterministicSocketData> socket_data(BuildNullSocketData());
socket_data->set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING));
CreateAndConnectRawExpectations("ws://localhost/",
NoSubProtocols(),
@@ -991,15 +1142,15 @@ TEST_F(WebSocketStreamCreateTest, CancellationDuringRead) {
MockRead reads[] = {
MockRead(ASYNC, 1, "HTTP/1.1 101 Switching Protocols\r\nUpgr"),
};
- DeterministicSocketData* socket_data(new DeterministicSocketData(
- reads, arraysize(reads), writes, arraysize(writes)));
- socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
+ scoped_ptr<DeterministicSocketData> socket_data(
+ BuildSocketData(reads, writes));
socket_data->SetStop(1);
+ DeterministicSocketData* socket_data_raw_ptr = socket_data.get();
CreateAndConnectRawExpectations("ws://localhost/",
NoSubProtocols(),
"http://localhost",
- make_scoped_ptr(socket_data));
- socket_data->Run();
+ socket_data.Pass());
+ socket_data_raw_ptr->Run();
stream_request_.reset();
RunUntilIdle();
EXPECT_FALSE(has_failed());
@@ -1032,14 +1183,14 @@ TEST_F(WebSocketStreamCreateTest, NoResponse) {
std::string request = WebSocketStandardRequest("/", "http://localhost", "");
MockWrite writes[] = {MockWrite(ASYNC, request.data(), request.size(), 0)};
MockRead reads[] = {MockRead(ASYNC, 0, 1)};
- DeterministicSocketData* socket_data(new DeterministicSocketData(
- reads, arraysize(reads), writes, arraysize(writes)));
- socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
+ scoped_ptr<DeterministicSocketData> socket_data(
+ BuildSocketData(reads, writes));
+ DeterministicSocketData* socket_data_raw_ptr = socket_data.get();
CreateAndConnectRawExpectations("ws://localhost/",
NoSubProtocols(),
"http://localhost",
- make_scoped_ptr(socket_data));
- socket_data->RunFor(2);
+ socket_data.Pass());
+ socket_data_raw_ptr->RunFor(2);
EXPECT_TRUE(has_failed());
EXPECT_FALSE(stream_);
EXPECT_FALSE(response_info_);
@@ -1053,8 +1204,7 @@ TEST_F(WebSocketStreamCreateTest, SelfSignedCertificateFailure) {
ssl_data_[0]->cert =
ImportCertFromFile(GetTestCertsDirectory(), "unittest.selfsigned.der");
ASSERT_TRUE(ssl_data_[0]->cert);
- scoped_ptr<DeterministicSocketData> raw_socket_data(
- new DeterministicSocketData(NULL, 0, NULL, 0));
+ scoped_ptr<DeterministicSocketData> raw_socket_data(BuildNullSocketData());
CreateAndConnectRawExpectations("wss://localhost/",
NoSubProtocols(),
"http://localhost",
@@ -1077,8 +1227,7 @@ TEST_F(WebSocketStreamCreateTest, SelfSignedCertificateSuccess) {
ssl_data_.push_back(ssl_data.release());
ssl_data.reset(new SSLSocketDataProvider(ASYNC, OK));
ssl_data_.push_back(ssl_data.release());
- url_request_context_host_.AddRawExpectations(
- make_scoped_ptr(new DeterministicSocketData(NULL, 0, NULL, 0)));
+ url_request_context_host_.AddRawExpectations(BuildNullSocketData());
CreateAndConnectStandard(
"wss://localhost/", "/", NoSubProtocols(), "http://localhost", "", "");
RunUntilIdle();
@@ -1089,6 +1238,60 @@ TEST_F(WebSocketStreamCreateTest, SelfSignedCertificateSuccess) {
EXPECT_TRUE(stream_);
}
+// If the server requests authorisation, but we have no credentials, the
+// connection should fail cleanly.
+TEST_F(WebSocketStreamCreateBasicAuthTest, FailureNoCredentials) {
+ CreateAndConnectCustomResponse("ws://localhost/",
+ "/",
+ NoSubProtocols(),
+ "http://localhost",
+ "",
+ kUnauthorizedResponse);
+ RunUntilIdle();
+ EXPECT_TRUE(has_failed());
+ EXPECT_EQ("HTTP Authentication failed; no valid credentials available",
+ failure_message());
+ EXPECT_TRUE(response_info_);
+}
+
+TEST_F(WebSocketStreamCreateBasicAuthTest, SuccessPasswordInUrl) {
+ CreateAndConnectAuthHandshake("ws://foo:bar@localhost/",
+ "Zm9vOmJhcg==",
+ WebSocketStandardResponse(std::string()));
+ RunUntilIdle();
+ EXPECT_FALSE(has_failed());
+ EXPECT_TRUE(stream_);
+ ASSERT_TRUE(response_info_);
+ EXPECT_EQ(101, response_info_->status_code);
+}
+
+TEST_F(WebSocketStreamCreateBasicAuthTest, FailureIncorrectPasswordInUrl) {
+ CreateAndConnectAuthHandshake(
+ "ws://foo:baz@localhost/", "Zm9vOmJheg==", kUnauthorizedResponse);
+ RunUntilIdle();
+ EXPECT_TRUE(has_failed());
+ EXPECT_TRUE(response_info_);
+}
+
+// Digest auth has the same connection semantics as Basic auth, so we can
+// generally assume that whatever works for Basic auth will also work for
+// Digest. There's just one test here, to confirm that it works at all.
+TEST_F(WebSocketStreamCreateDigestAuthTest, DigestPasswordInUrl) {
+ AddRawExpectations(helper_.BuildSocketData1(kUnauthorizedResponse));
+
+ CreateAndConnectRawExpectations(
+ "ws://FooBar:pass@localhost/",
+ NoSubProtocols(),
+ "http://localhost",
+ helper_.BuildSocketData2(kAuthorizedRequest,
+ WebSocketStandardResponse(std::string())));
+ RunUntilIdle();
+ EXPECT_FALSE(has_failed());
+ EXPECT_TRUE(stream_);
+ ASSERT_TRUE(response_info_);
+ EXPECT_EQ(101, response_info_->status_code);
+}
+
TEST_F(WebSocketStreamCreateUMATest, Incomplete) {
const std::string name("Net.WebSocket.HandshakeResult");
scoped_ptr<base::HistogramSamples> original(GetSamples(name));
« net/http/http_network_transaction.cc ('K') | « net/websockets/websocket_stream.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698