diff --git a/relay/application.py b/relay/application.py index 39f7bd0..e3d7b5c 100644 --- a/relay/application.py +++ b/relay/application.py @@ -1,16 +1,8 @@ -import asyncio -import logging -import sys - from aiohttp.web import Application from . import set_app -from . import views -from .middleware import http_signatures_middleware -app = Application(middlewares=[ - http_signatures_middleware -]) +app = Application() set_app(app) diff --git a/relay/config.py b/relay/config.py index df70f2d..fd4ee34 100644 --- a/relay/config.py +++ b/relay/config.py @@ -189,7 +189,10 @@ class RelayConfig(DotDict): def is_banned_software(self, software): - return software in self.blocked_software + if not software: + return False + + return software.lower() in self.blocked_software def is_whitelisted(self, instance): diff --git a/relay/middleware.py b/relay/middleware.py deleted file mode 100644 index d1f458c..0000000 --- a/relay/middleware.py +++ /dev/null @@ -1,49 +0,0 @@ -import logging - -from aiohttp.web import HTTPUnauthorized -from json.decoder import JSONDecodeError -from urllib.parse import urlparse - -from . import misc - - -# This will probably get merged into views.inbox -async def http_signatures_middleware(app, handler): - async def http_signatures_handler(request): - request['validated'] = False - request['actor'] = None - request['actor_domain'] = None - - try: - request['data'] = await request.json() - - except JSONDecodeError: - request['data'] = None - - if 'signature' in request.headers and request.method == 'POST': - if 'actor' not in request['data']: - logging.verbose('Actor not in data') - raise HTTPUnauthorized(body='signature check failed, no actor in message') - - if app['config'].is_banned(request['data']['actor']): - logging.verbose(f'Ignored request from banned actor: {request["actor"]["id"]}') - raise HTTPUnauthorized(body='banned') - - request['actor'] = await misc.request(request['data']['actor']) - - if not request['actor']: - logging.verbose(f'Failed to fetch actor: {request["actor"]["id"]}') - raise HTTPUnauthorized('failed to fetch actor') - - actor_id = request['actor']['id'] - request['actor_domain'] = urlparse(actor_id).hostname - - if not (await misc.validate_signature(actor_id, request)): - logging.verbose(f'signature validation failed for: {actor_id}') - raise HTTPUnauthorized(body='signature check failed, signature did not match key') - - return (await handler(request)) - - return (await handler(request)) - - return http_signatures_handler diff --git a/relay/views.py b/relay/views.py index 8ddf478..f46587a 100644 --- a/relay/views.py +++ b/relay/views.py @@ -1,12 +1,13 @@ import logging import subprocess +import traceback from aiohttp.web import HTTPForbidden, HTTPUnauthorized, Response, json_response +from urllib.parse import urlparse -from . import __version__ +from . import __version__, misc from .application import app from .http_debug import STATS -from .misc import request from .processors import run_processor @@ -77,31 +78,65 @@ async def inbox(request): config = app['config'] database = app['database'] - if len(config.blocked_software): - software = await fetch_nodeinfo(request['actor_domain']) + ## reject if missing signature header + if 'signature' not in request.headers: + logging.verbose('Actor missing signature header') + raise HTTPUnauthorized(body='missing signature') - if software and software.lower() in config.blocked_software: - logging.verbose(f'Rejected actor for using specific software: {software}') - raise HTTPForbidden(body='access denied', content_type='text/plain') + ## read message and get actor id and domain + try: + data = await request.json() + actor_id = data['actor'] + actor_domain = urlparse(actor_id).hostname - ## reject if no post data or signature failed validation - if not request['data'] or not request['validated']: - logging.verbose('Rejected actor for missing post data') - raise HTTPUnauthorized(body='access denied', content_type='text/plain') + except KeyError: + logging.verbose('actor not in data') + raise HTTPUnauthorized(body='no actor in message') - ## reject if activity type isn't 'Follow' and the actor isn't following - if request['data']['type'] != 'Follow' and not database.get_inbox(request['actor_domain']): - logging.verbose(f'Rejected actor for trying to post while not following: {request["actor"]["id"]}') - raise HTTPUnauthorized(body='access denied', content_type='text/plain') + ## reject if there is no actor in the message + except: + traceback.print_exc() + logging.verbose('Failed to parse inbox message') + raise HTTPUnauthorized(body='failed to parse message') + + actor = await misc.request(actor_id) + + ## reject if actor is empty + if not actor: + logging.verbose(f'Failed to fetch actor: {actor_id}') + raise HTTPUnauthorized('failed to fetch actor') ## reject if the actor isn't whitelisted while the whiltelist is enabled - elif config.whitelist_enabled and not request['data'].is_whitelisted(request['actor']['id']): - logging.verbose(f'Rejected actor for not being in the whitelist: {request["actor"]["id"]}') - raise HTTPForbidden(body='access denied', content_type='text/plain') + elif config.whitelist_enabled and not config.is_whitelisted(actor_id): + logging.verbose(f'Rejected actor for not being in the whitelist: {actor_id}') + raise HTTPForbidden(body='access denied') - logging.debug(f">> payload {request['data']}") + ## reject if actor is banned + if app['config'].is_banned(actor_id): + logging.verbose(f'Ignored request from banned actor: {actor_id}') + raise HTTPForbidden(body='access denied') - await run_processor(request, request['data'], request['actor']) + ## reject if software used by actor is banned + if len(config.blocked_software): + software = await fetch_nodeinfo(actor_domain) + + if config.is_banned_software(software): + logging.verbose(f'Rejected actor for using specific software: {software}') + raise HTTPForbidden(body='access denied') + + ## reject if the signature is invalid + if not (await misc.validate_signature(actor_id, request)): + logging.verbose(f'signature validation failed for: {actor_id}') + raise HTTPUnauthorized(body='signature check failed, signature did not match key') + + ## reject if activity type isn't 'Follow' and the actor isn't following + if data['type'] != 'Follow' and not database.get_inbox(actor_domain): + logging.verbose(f'Rejected actor for trying to post while not following: {actor_id}') + raise HTTPUnauthorized(body='access denied') + + logging.debug(f">> payload {data}") + + await run_processor(request, data, actor) return Response(body=b'{}', content_type='application/activity+json')