Commit 5b266c80 authored by Randall Leeds's avatar Randall Leeds

Check cross site request token for WebSocket

http://www.christian-schneider.net/CrossSiteWebSocketHijacking.html
parent 7ac29e8a
......@@ -6,6 +6,7 @@ imports = [
'h.account'
'h.helpers'
'h.identity'
'h.session'
'h.streamer'
]
......@@ -57,7 +58,10 @@ configure = [
basePattern = baseURI.replace /\/[^\/]*$/, '/**.html'
$sceDelegateProvider.resourceUrlWhitelist ['self', basePattern]
streamerProvider.url = baseURI.replace('http', 'ws') + 'ws'
streamerProvider.urlFn = ['xsrf', (xsrf) ->
base = baseURI.replace(/^http/, 'ws')
"#{base}ws?csrf_token=#{xsrf.token}"
]
]
......
......@@ -40,19 +40,13 @@ class SessionProvider
# });
###
$get: [
'$http', '$q', '$resource', 'documentHelpers', 'flash',
($http, $q, $resource, documentHelpers, flash) ->
'$http', '$q', '$resource', 'documentHelpers', 'flash', 'xsrf',
($http, $q, $resource, documentHelpers, flash, xsrf) ->
actions = {}
provider = this
# Capture the state of the cross site request forgery token.
# If cookies are blocked this is our only way to get it.
xsrfToken = null
prepare = (data, headersGetter) ->
if xsrfToken
headers = headersGetter()
headers[$http.defaults.xsrfHeaderName] = xsrfToken
headersGetter()[$http.defaults.xsrfHeaderName] = xsrf.token
return angular.toJson data
process = (data, headersGetter) ->
......@@ -68,7 +62,7 @@ class SessionProvider
for q, msgs of data.flash
flash q, msgs
xsrfToken = model.csrf
xsrf.token = model.csrf
# Return the model
model
......@@ -85,3 +79,4 @@ class SessionProvider
angular.module('h.session')
.provider('session', SessionProvider)
.value('xsrf', token: null)
......@@ -6,20 +6,21 @@ ST_OPEN = 3
# @ngdoc service
# @name Streamer
#
# @param {String} url The base URL for the socket connection
# @param {String} urlFn A function that will be called with injections to
# generate the socket URL.
#
# @description
# Provides access to the streamer websocket.
###
class Streamer
constructor: (transport, url) ->
constructor: (transport, urlFn) ->
this.onmessage = ->
this._failCount = 0
this._queue = []
this._state = ST_CLOSED
this._transport = transport
this._url = url
this._urlFn = urlFn
###*
# @ngdoc method
......@@ -34,7 +35,7 @@ class Streamer
return
self = this
this._sock = new this._transport(this._url)
this._sock = new this._transport(this._urlFn())
this._state = ST_CONNECTING
this._sock.onopen = ->
......@@ -112,9 +113,10 @@ setAjaxClientId = (clientId) ->
streamerProvider = ->
provider = {}
provider.url = null
provider.$get = ['$window', ($window) ->
new Streamer($window.WebSocket, provider.url)
provider.urlFn = null
provider.$get = ['$injector', '$window', ($injector, $window) ->
urlFn = angular.bind $injector, $injector.invoke, provider.urlFn
new Streamer($window.WebSocket, urlFn)
]
return provider
......
......@@ -23,10 +23,12 @@ describe 'session', ->
describe 'sessionService', ->
$httpBackend = null
session = null
xsrf = null
beforeEach inject (_$httpBackend_, _session_) ->
beforeEach inject (_$httpBackend_, _session_, _xsrf_) ->
$httpBackend = _$httpBackend_
session = _session_
xsrf = _xsrf_
describe '#<action>()', ->
url = '/login'
......@@ -60,15 +62,17 @@ describe 'session', ->
assert.match result.reason, response.reason, 'the reason is present'
it 'should capture and send the xsrf token', ->
xsrf = 'deadbeef'
token = 'deadbeef'
headers =
'Accept': 'application/json, text/plain, */*'
'Content-Type': 'application/json;charset=utf-8'
'X-XSRF-TOKEN': xsrf
model = {csrf: xsrf}
'X-XSRF-TOKEN': token
model = {csrf: token}
request = $httpBackend.expectPOST(url).respond({model})
result = session.login({})
$httpBackend.flush()
assert.equal xsrf.token, token
$httpBackend.expectPOST(url, {}, headers).respond({})
session.login({})
$httpBackend.flush()
......@@ -17,7 +17,8 @@ describe 'streamer', ->
WebSocket = sandbox.stub().returns(fakeSock)
$provide.decorator '$window', ($delegate) ->
angular.extend $delegate, {WebSocket}
streamerProvider.url = 'http://magicstreemz/giraffe'
$provide.value 'webSocketUrl', 'wss://magicstreemz/giraffe'
streamerProvider.urlFn = (webSocketUrl) -> webSocketUrl
return
beforeEach inject (_streamer_) ->
......@@ -26,10 +27,14 @@ describe 'streamer', ->
afterEach ->
sandbox.restore()
it 'calls the transport function with the new keyword', ->
streamer.open()
assert.calledWithNew(WebSocket)
it 'creates a socket with the correct base URL', ->
streamer.open()
assert.calledWith(WebSocket, 'http://magicstreemz/giraffe')
assert.calledWith(WebSocket, 'wss://magicstreemz/giraffe')
it 'does not open another socket while a socket is connecting', ->
streamer.open()
......
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