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

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: Factor out WebSocketDispatchOnFinishOpeningHandshake() into a separate function. 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/tools/testserver/testserver.py ('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 0a6b99be4d60f68404d97f5cda56d7af7f20ee8b..faf06476a80e7c1db939a714bd883d3d7b57956f 100644
--- a/net/websockets/websocket_stream_test.cc
+++ b/net/websockets/websocket_stream_test.cc
@@ -55,6 +55,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
@@ -118,10 +141,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,
@@ -174,8 +202,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(
@@ -233,6 +261,128 @@ 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:
+ 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;
Johnny 2014/07/08 20:27:40 Trailing underscores?
Adam Rice 2014/07/09 06:35:01 Okay, that's weird. In my brain this was a struct,
+ std::string request2;
+ std::string response1;
+ std::string response2;
+ MockRead reads1[2];
+ MockWrite writes1[1];
+ MockRead reads2[1];
+ MockWrite writes2[1];
+};
+
+// 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.
@@ -925,8 +1075,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(),
@@ -941,8 +1090,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(),
@@ -955,8 +1103,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(),
@@ -998,15 +1145,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());
Johnny 2014/07/08 20:27:40 Why is ownership of |socket_data| passed to WebSoc
Adam Rice 2014/07/09 06:35:01 Short explanation: WebSocketDeterministicMockClien
Johnny 2014/07/09 16:16:17 SGTM.
+ socket_data_raw_ptr->Run();
stream_request_.reset();
RunUntilIdle();
EXPECT_FALSE(has_failed());
@@ -1039,14 +1186,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_);
@@ -1060,8 +1207,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",
@@ -1084,8 +1230,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();
@@ -1096,6 +1241,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/tools/testserver/testserver.py ('K') | « net/websockets/websocket_stream.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698