tahoe-lafs/src/allmydata/web/logs.py

101 lines
3.0 KiB
Python
Raw Normal View History

2019-03-21 19:01:14 +00:00
from __future__ import (
print_function,
unicode_literals,
absolute_import,
division,
)
2019-03-21 07:37:47 +00:00
import json
from autobahn.twisted.resource import WebSocketResource
2019-03-21 19:01:25 +00:00
from autobahn.twisted.websocket import (
WebSocketServerFactory,
WebSocketServerProtocol,
)
from autobahn.websocket.types import ConnectionDeny
2019-03-21 07:37:47 +00:00
import eliot
2019-03-21 19:00:57 +00:00
from twisted.web.resource import (
Resource,
)
2019-03-21 19:01:25 +00:00
from allmydata.util.hashutil import (
timing_safe_compare,
)
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`.
"""
def onConnect(self, req):
2019-03-21 07:52:45 +00:00
"""
WebSocket callback
"""
2019-03-21 19:01:14 +00:00
if b'authorization' in req.headers:
auth = req.headers[b'authorization'].encode('ascii').split(b' ', 1)
2019-03-21 07:37:47 +00:00
if len(auth) == 2:
tag, token = auth
2019-03-21 19:01:14 +00:00
if tag == b"tahoe-lafs":
2019-03-21 07:37:47 +00:00
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
# 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,
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 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 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
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).
"""
factory = WebSocketServerFactory()
2019-03-21 07:37:47 +00:00
factory.tahoe_client = client
factory.protocol = TokenAuthenticatedWebSocketServerProtocol
return WebSocketResource(factory)
2019-03-21 19:00:57 +00:00
def create_log_resources(client):
logs = Resource()
logs.putChild(b"v1", create_log_streaming_resource(client))
return logs