2019-03-21 07:37:47 +00:00
|
|
|
import json
|
|
|
|
|
2019-03-21 00:14:47 +00:00
|
|
|
from autobahn.twisted.resource import WebSocketResource
|
|
|
|
from autobahn.twisted.websocket import WebSocketServerFactory
|
|
|
|
from autobahn.twisted.websocket import WebSocketServerProtocol
|
|
|
|
from autobahn.websocket.types import ConnectionDeny
|
|
|
|
|
|
|
|
from twisted.web import resource, server
|
|
|
|
from twisted.python.failure import Failure
|
|
|
|
|
2019-03-21 07:37:47 +00:00
|
|
|
import eliot
|
|
|
|
|
2019-03-21 00:14:47 +00:00
|
|
|
from allmydata.util.hashutil import timing_safe_compare
|
|
|
|
from .common import humanize_failure
|
|
|
|
|
|
|
|
|
|
|
|
class TokenAuthenticatedWebSocketServerProtocol(WebSocketServerProtocol):
|
|
|
|
"""
|
2019-03-21 07:52:45 +00:00
|
|
|
A WebSocket protocol that looks for an `Authorization:` header
|
|
|
|
with a `tahoe-lafs` scheme and a token matching our private config
|
|
|
|
for `api_auth_token`.
|
2019-03-21 00:14:47 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
def onConnect(self, req):
|
2019-03-21 07:52:45 +00:00
|
|
|
"""
|
|
|
|
WebSocket callback
|
|
|
|
"""
|
2019-03-21 00:14:47 +00:00
|
|
|
if 'authorization' in req.headers:
|
2019-03-21 07:37:47 +00:00
|
|
|
auth = req.headers['authorization'].encode('ascii').split(' ', 1)
|
|
|
|
if len(auth) == 2:
|
|
|
|
tag, token = auth
|
|
|
|
if tag == "tahoe-lafs":
|
|
|
|
if timing_safe_compare(token, self.factory.tahoe_client.get_auth_token()):
|
|
|
|
# we don't care what WebSocket sub-protocol is
|
|
|
|
# negotiated, nor do we need to send headers to the
|
|
|
|
# client, so we ask Autobahn to just allow this
|
|
|
|
# connection with the defaults. We could return a
|
|
|
|
# (headers, protocol) pair here instead if required.
|
|
|
|
return None
|
2019-03-21 00:14:47 +00:00
|
|
|
|
|
|
|
# everything else -- i.e. no Authorization header, or it's
|
|
|
|
# wrong -- means we deny the websocket connection
|
|
|
|
raise ConnectionDeny(
|
2019-03-21 07:37:47 +00:00
|
|
|
code=ConnectionDeny.NOT_ACCEPTABLE,
|
2019-03-21 00:14:47 +00:00
|
|
|
reason=u"Invalid or missing token"
|
|
|
|
)
|
|
|
|
|
2019-03-21 07:37:47 +00:00
|
|
|
def _received_eliot_log(self, message):
|
2019-03-21 07:52:45 +00:00
|
|
|
"""
|
|
|
|
While this WebSocket connection is open, this function is
|
|
|
|
registered as an eliot destination
|
|
|
|
"""
|
2019-03-21 07:37:47 +00:00
|
|
|
# probably want a try/except around here? what do we do if
|
2019-03-21 07:52:45 +00:00
|
|
|
# transmission fails or anything else bad happens?
|
2019-03-21 07:37:47 +00:00
|
|
|
self.sendMessage(json.dumps(message))
|
2019-03-21 00:14:47 +00:00
|
|
|
|
2019-03-21 07:37:47 +00:00
|
|
|
def onOpen(self):
|
2019-03-21 07:52:45 +00:00
|
|
|
"""
|
|
|
|
WebSocket callback
|
|
|
|
"""
|
2019-03-21 07:37:47 +00:00
|
|
|
eliot.add_destination(self._received_eliot_log)
|
2019-03-21 00:14:47 +00:00
|
|
|
|
2019-03-21 07:37:47 +00:00
|
|
|
def onClose(self, wasClean, code, reason):
|
2019-03-21 07:52:45 +00:00
|
|
|
"""
|
|
|
|
WebSocket callback
|
|
|
|
"""
|
2019-03-21 07:37:47 +00:00
|
|
|
try:
|
|
|
|
eliot.remove_destination(self._received_eliot_log)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
2019-03-21 00:14:47 +00:00
|
|
|
|
|
|
|
|
2019-03-21 17:58:46 +00:00
|
|
|
def create_log_streaming_resource(client):
|
2019-03-21 07:37:47 +00:00
|
|
|
"""
|
|
|
|
Create a new resource that accepts WebSocket connections if they
|
|
|
|
include a correct `Authorization: tahoe-lafs <api_auth_token>`
|
|
|
|
header (where `api_auth_token` matches the private configuration
|
|
|
|
value).
|
|
|
|
"""
|
2019-03-21 17:58:46 +00:00
|
|
|
factory = WebSocketServerFactory()
|
2019-03-21 07:37:47 +00:00
|
|
|
factory.tahoe_client = client
|
|
|
|
factory.protocol = TokenAuthenticatedWebSocketServerProtocol
|
|
|
|
return WebSocketResource(factory)
|