Commit 8d7ed25c authored by ricea@chromium.org's avatar ricea@chromium.org

Map WebSocket URL schemes to HTTP URL schemes for auth purposes.

This permits WebSocket connections to inherit credentials from HTTP pages, and
matches the behaviour of other browsers.

Design doc: https://docs.google.com/a/chromium.org/document/d/129rLtf5x3hvhP5rayLiSxnEjOXS8Z7EnLJgBL4CdwjI/edit

Also consider any 401 or 407 results that reach the WebSocketStream
URLRequest::Delegate to be unrecoverable errors.

Also ensure that the response headers are reported back to the renderer
when the developer tools are open and a 401 error happens.

BUG=123862

Review URL: https://codereview.chromium.org/336263005

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@282307 0039d316-1c4b-4281-b951-d872f2087c98
parent 41c6d548
This diff is collapsed.
...@@ -19,6 +19,11 @@ Multiple tests may share one resource, or URI handler. ...@@ -19,6 +19,11 @@ Multiple tests may share one resource, or URI handler.
content::TitleWatcher. content::TitleWatcher.
Used by WorkerTest.WebSocketSharedWorker. Used by WorkerTest.WebSocketSharedWorker.
- connect_to.html : A page which makes a connection to the WebSocket server
specified in the "url" parameter,
eg. connect_to.html?url=ws://localhost/echo Sets the title to "PASS" if
connection succeeds and "FAIL" otherwise.
- websocket_worker_simple.js : A JavaScript runs on Workers created from the - websocket_worker_simple.js : A JavaScript runs on Workers created from the
websocket_shared_worker.html. websocket_shared_worker.html.
Used by WorkerTest.WebSocketSharedWorker. Used by WorkerTest.WebSocketSharedWorker.
......
<!DOCTYPE html>
<html>
<head>
<title>test ws connection</title>
<script type="text/javascript">
var href = window.location.href;
var queryBegin = href.indexOf('?url=');
if (queryBegin == -1) {
console.log("Failed to find ?url= in URL");
document.title = 'FAIL';
throw "FAILURE";
}
var url = href.slice(queryBegin + 5);
// Do connection test.
var ws = new WebSocket(url);
ws.onopen = function()
{
// Set document title to 'PASS'. The test observer catches this title changes
// to know the result.
document.title = 'PASS';
}
ws.onclose = function()
{
// Set document title to 'FAIL'.
document.title = 'FAIL';
}
</script>
</head>
</html>
...@@ -61,6 +61,7 @@ ...@@ -61,6 +61,7 @@
#include "net/ssl/ssl_cert_request_info.h" #include "net/ssl/ssl_cert_request_info.h"
#include "net/ssl/ssl_connection_status_flags.h" #include "net/ssl/ssl_connection_status_flags.h"
#include "url/gurl.h" #include "url/gurl.h"
#include "url/url_canon.h"
#if defined(SPDY_PROXY_AUTH_ORIGIN) #if defined(SPDY_PROXY_AUTH_ORIGIN)
#include <algorithm> #include <algorithm>
...@@ -1545,6 +1546,17 @@ GURL HttpNetworkTransaction::AuthURL(HttpAuth::Target target) const { ...@@ -1545,6 +1546,17 @@ GURL HttpNetworkTransaction::AuthURL(HttpAuth::Target target) const {
proxy_info_.proxy_server().host_port_pair().ToString()); proxy_info_.proxy_server().host_port_pair().ToString());
} }
case HttpAuth::AUTH_SERVER: case HttpAuth::AUTH_SERVER:
if (ForWebSocketHandshake()) {
const GURL& url = request_->url;
url::Replacements<char> ws_to_http;
if (url.SchemeIs("ws")) {
ws_to_http.SetScheme("http", url::Component(0, 4));
} else {
DCHECK(url.SchemeIs("wss"));
ws_to_http.SetScheme("https", url::Component(0, 5));
}
return url.ReplaceComponents(ws_to_http);
}
return request_->url; return request_->url;
default: default:
return GURL(); return GURL();
......
...@@ -165,7 +165,8 @@ const char BaseTestServer::kLocalhost[] = "127.0.0.1"; ...@@ -165,7 +165,8 @@ const char BaseTestServer::kLocalhost[] = "127.0.0.1";
BaseTestServer::BaseTestServer(Type type, const std::string& host) BaseTestServer::BaseTestServer(Type type, const std::string& host)
: type_(type), : type_(type),
started_(false), started_(false),
log_to_console_(false) { log_to_console_(false),
ws_basic_auth_(false) {
Init(host); Init(host);
} }
...@@ -173,7 +174,8 @@ BaseTestServer::BaseTestServer(Type type, const SSLOptions& ssl_options) ...@@ -173,7 +174,8 @@ BaseTestServer::BaseTestServer(Type type, const SSLOptions& ssl_options)
: ssl_options_(ssl_options), : ssl_options_(ssl_options),
type_(type), type_(type),
started_(false), started_(false),
log_to_console_(false) { log_to_console_(false),
ws_basic_auth_(false) {
DCHECK(UsingSSL(type)); DCHECK(UsingSSL(type));
Init(GetHostname(type, ssl_options)); Init(GetHostname(type, ssl_options));
} }
...@@ -384,6 +386,11 @@ bool BaseTestServer::GenerateArguments(base::DictionaryValue* arguments) const { ...@@ -384,6 +386,11 @@ bool BaseTestServer::GenerateArguments(base::DictionaryValue* arguments) const {
if (VLOG_IS_ON(1) || log_to_console_) if (VLOG_IS_ON(1) || log_to_console_)
arguments->Set("log-to-console", base::Value::CreateNullValue()); arguments->Set("log-to-console", base::Value::CreateNullValue());
if (ws_basic_auth_) {
DCHECK(type_ == TYPE_WS || type_ == TYPE_WSS);
arguments->Set("ws-basic-auth", base::Value::CreateNullValue());
}
if (UsingSSL(type_)) { if (UsingSSL(type_)) {
// Check the certificate arguments of the HTTPS server. // Check the certificate arguments of the HTTPS server.
base::FilePath certificate_path(certificates_dir_); base::FilePath certificate_path(certificates_dir_);
......
...@@ -242,6 +242,12 @@ class BaseTestServer { ...@@ -242,6 +242,12 @@ class BaseTestServer {
type == BaseTestServer::TYPE_WSS; type == BaseTestServer::TYPE_WSS;
} }
// Enable HTTP basic authentication. Currently this only works for TYPE_WS and
// TYPE_WSS.
void set_websocket_basic_auth(bool ws_basic_auth) {
ws_basic_auth_ = ws_basic_auth;
}
protected: protected:
virtual ~BaseTestServer(); virtual ~BaseTestServer();
Type type() const { return type_; } Type type() const { return type_; }
...@@ -308,6 +314,9 @@ class BaseTestServer { ...@@ -308,6 +314,9 @@ class BaseTestServer {
// Enables logging of the server to the console. // Enables logging of the server to the console.
bool log_to_console_; bool log_to_console_;
// Is WebSocket basic HTTP authentication enabled?
bool ws_basic_auth_;
scoped_ptr<ScopedPortException> allowed_port_; scoped_ptr<ScopedPortException> allowed_port_;
DISALLOW_COPY_AND_ASSIGN(BaseTestServer); DISALLOW_COPY_AND_ASSIGN(BaseTestServer);
......
...@@ -103,6 +103,7 @@ class WebSocketOptions: ...@@ -103,6 +103,7 @@ class WebSocketOptions:
self.tls_client_ca = None self.tls_client_ca = None
self.tls_module = 'ssl' self.tls_module = 'ssl'
self.use_basic_auth = False self.use_basic_auth = False
self.basic_auth_credential = 'Basic ' + base64.b64encode('test:test')
class RecordingSSLSessionCache(object): class RecordingSSLSessionCache(object):
...@@ -2024,6 +2025,7 @@ class ServerRunner(testserver_base.TestServerRunner): ...@@ -2024,6 +2025,7 @@ class ServerRunner(testserver_base.TestServerRunner):
print 'WebSocket server started on %s://%s:%d...' % \ print 'WebSocket server started on %s://%s:%d...' % \
(scheme, host, server.server_port) (scheme, host, server.server_port)
server_data['port'] = server.server_port server_data['port'] = server.server_port
websocket_options.use_basic_auth = self.options.ws_basic_auth
elif self.options.server_type == SERVER_TCP_ECHO: elif self.options.server_type == SERVER_TCP_ECHO:
# Used for generating the key (randomly) that encodes the "echo request" # Used for generating the key (randomly) that encodes the "echo request"
# message. # message.
...@@ -2205,6 +2207,10 @@ class ServerRunner(testserver_base.TestServerRunner): ...@@ -2205,6 +2207,10 @@ class ServerRunner(testserver_base.TestServerRunner):
'support for exactly one protocol, http/1.1') 'support for exactly one protocol, http/1.1')
self.option_parser.add_option('--file-root-url', default='/files/', self.option_parser.add_option('--file-root-url', default='/files/',
help='Specify a root URL for files served.') help='Specify a root URL for files served.')
# TODO(ricea): Generalize this to support basic auth for HTTP too.
self.option_parser.add_option('--ws-basic-auth', action='store_true',
dest='ws_basic_auth',
help='Enable basic-auth for WebSocket')
if __name__ == '__main__': if __name__ == '__main__':
......
...@@ -539,20 +539,11 @@ void WebSocketBasicHandshakeStream::ReadResponseHeadersCallback( ...@@ -539,20 +539,11 @@ void WebSocketBasicHandshakeStream::ReadResponseHeadersCallback(
} }
void WebSocketBasicHandshakeStream::OnFinishOpeningHandshake() { void WebSocketBasicHandshakeStream::OnFinishOpeningHandshake() {
DCHECK(connect_delegate_);
DCHECK(http_response_info_); DCHECK(http_response_info_);
scoped_refptr<HttpResponseHeaders> headers = http_response_info_->headers; WebSocketDispatchOnFinishOpeningHandshake(connect_delegate_,
// If the headers are too large, HttpStreamParser will just not parse them at url_,
// all. http_response_info_->headers,
if (headers) { http_response_info_->response_time);
scoped_ptr<WebSocketHandshakeResponseInfo> response(
new WebSocketHandshakeResponseInfo(url_,
headers->response_code(),
headers->GetStatusText(),
headers,
http_response_info_->response_time));
connect_delegate_->OnFinishOpeningHandshake(response.Pass());
}
} }
int WebSocketBasicHandshakeStream::ValidateResponse(int rv) { int WebSocketBasicHandshakeStream::ValidateResponse(int rv) {
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "base/metrics/sparse_histogram.h" #include "base/metrics/sparse_histogram.h"
#include "net/base/load_flags.h" #include "net/base/load_flags.h"
#include "net/http/http_request_headers.h" #include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h" #include "net/http/http_status_code.h"
#include "net/url_request/url_request.h" #include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context.h"
...@@ -133,7 +134,18 @@ class StreamRequestImpl : public WebSocketStreamRequest { ...@@ -133,7 +134,18 @@ class StreamRequestImpl : public WebSocketStreamRequest {
break; break;
} }
} }
connect_delegate_->OnFailure(failure_message_); ReportFailureWithMessage(failure_message_);
}
void ReportFailureWithMessage(const std::string& failure_message) {
connect_delegate_->OnFailure(failure_message);
}
void OnFinishOpeningHandshake() {
WebSocketDispatchOnFinishOpeningHandshake(connect_delegate(),
url_request_.url(),
url_request_.response_headers(),
url_request_.response_time());
} }
WebSocketStream::ConnectDelegate* connect_delegate() const { WebSocketStream::ConnectDelegate* connect_delegate() const {
...@@ -198,7 +210,16 @@ void Delegate::OnResponseStarted(URLRequest* request) { ...@@ -198,7 +210,16 @@ void Delegate::OnResponseStarted(URLRequest* request) {
return; return;
case HTTP_UNAUTHORIZED: case HTTP_UNAUTHORIZED:
result_ = FAILED;
owner_->OnFinishOpeningHandshake();
owner_->ReportFailureWithMessage(
"HTTP Authentication failed; no valid credentials available");
return;
case HTTP_PROXY_AUTHENTICATION_REQUIRED: case HTTP_PROXY_AUTHENTICATION_REQUIRED:
result_ = FAILED;
owner_->OnFinishOpeningHandshake();
owner_->ReportFailureWithMessage("Proxy authentication failed");
return; return;
default: default:
...@@ -285,4 +306,20 @@ scoped_ptr<WebSocketStreamRequest> CreateAndConnectStreamForTesting( ...@@ -285,4 +306,20 @@ scoped_ptr<WebSocketStreamRequest> CreateAndConnectStreamForTesting(
return request.PassAs<WebSocketStreamRequest>(); return request.PassAs<WebSocketStreamRequest>();
} }
void WebSocketDispatchOnFinishOpeningHandshake(
WebSocketStream::ConnectDelegate* connect_delegate,
const GURL& url,
const scoped_refptr<HttpResponseHeaders>& headers,
base::Time response_time) {
DCHECK(connect_delegate);
if (headers) {
connect_delegate->OnFinishOpeningHandshake(make_scoped_ptr(
new WebSocketHandshakeResponseInfo(url,
headers->response_code(),
headers->GetStatusText(),
headers,
response_time)));
}
}
} // namespace net } // namespace net
...@@ -10,8 +10,10 @@ ...@@ -10,8 +10,10 @@
#include "base/basictypes.h" #include "base/basictypes.h"
#include "base/callback_forward.h" #include "base/callback_forward.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "base/memory/scoped_vector.h" #include "base/memory/scoped_vector.h"
#include "base/time/time.h"
#include "net/base/completion_callback.h" #include "net/base/completion_callback.h"
#include "net/base/net_export.h" #include "net/base/net_export.h"
#include "net/websockets/websocket_event_interface.h" #include "net/websockets/websocket_event_interface.h"
...@@ -198,6 +200,16 @@ class NET_EXPORT_PRIVATE WebSocketStream { ...@@ -198,6 +200,16 @@ class NET_EXPORT_PRIVATE WebSocketStream {
DISALLOW_COPY_AND_ASSIGN(WebSocketStream); DISALLOW_COPY_AND_ASSIGN(WebSocketStream);
}; };
// A helper function used in the implementation of CreateAndConnectStream() and
// WebSocketBasicHandshakeStream. It creates a WebSocketHandshakeResponseInfo
// object and dispatches it to the OnFinishOpeningHandshake() method of the
// supplied |connect_delegate|.
void WebSocketDispatchOnFinishOpeningHandshake(
WebSocketStream::ConnectDelegate* connect_delegate,
const GURL& gurl,
const scoped_refptr<HttpResponseHeaders>& headers,
base::Time response_time);
} // namespace net } // namespace net
#endif // NET_WEBSOCKETS_WEBSOCKET_STREAM_H_ #endif // NET_WEBSOCKETS_WEBSOCKET_STREAM_H_
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment