From ba9f2718aaeb03ef7b1c461a359f148255849ffe Mon Sep 17 00:00:00 2001 From: Izalia Mae Date: Fri, 18 Nov 2022 16:41:14 -0500 Subject: [PATCH] use new request properties and only fetch nodeinfo on follow --- relay/processors.py | 89 ++++++++++++++++++++++++--------------------- relay/views.py | 78 +++++++++++++++++++-------------------- 2 files changed, 84 insertions(+), 83 deletions(-) diff --git a/relay/processors.py b/relay/processors.py index a6ced5a..4ccb0d1 100644 --- a/relay/processors.py +++ b/relay/processors.py @@ -6,92 +6,97 @@ from uuid import uuid4 from . import misc -async def handle_relay(request, actor, data, nodeinfo): - if data.objectid in request.app.cache.objects: - logging.verbose(f'already relayed {data.objectid}') +async def handle_relay(request): + if request.message.objectid in request.cache.objects: + logging.verbose(f'already relayed {request.message.objectid}') return - logging.verbose(f'Relaying post from {data.actorid}') + logging.verbose(f'Relaying post from {request.message.actorid}') message = misc.Message.new_announce( - host = request.app.config.host, - object = data.objectid + host = request.config.host, + object = request.message.objectid ) logging.debug(f'>> relay: {message}') - inboxes = misc.distill_inboxes(actor, data.objectid) + inboxes = misc.distill_inboxes(request.actor, request.message.objectid) futures = [misc.request(inbox, data=message) for inbox in inboxes] asyncio.ensure_future(asyncio.gather(*futures)) - request.app.cache.objects[data.objectid] = message.id + request.cache.objects[request.message.objectid] = message.id -async def handle_forward(request, actor, data, nodeinfo): - if data.id in request.app.cache.objects: - logging.verbose(f'already forwarded {data.id}') +async def handle_forward(request): + if request.message.id in request.cache.objects: + logging.verbose(f'already forwarded {request.message.id}') return message = misc.Message.new_announce( - host = request.app.config.host, - object = data + host = request.config.host, + object = request.message ) - logging.verbose(f'Forwarding post from {actor.id}') - logging.debug(f'>> Relay {data}') + logging.verbose(f'Forwarding post from {request.actor.id}') + logging.debug(f'>> Relay {request.message}') - inboxes = misc.distill_inboxes(actor, data.id) + inboxes = misc.distill_inboxes(request.actor, request.message.id) futures = [misc.request(inbox, data=message) for inbox in inboxes] asyncio.ensure_future(asyncio.gather(*futures)) - request.app.cache.objects[data.id] = message.id + request.cache.objects[request.message.id] = message.id -async def handle_follow(request, actor, data, nodeinfo): - if not request.app.database.add_inbox(actor.shared_inbox, data.id): - request.app.database.set_followid(actor.id, data.id) +async def handle_follow(request): + nodeinfo = await misc.fetch_nodeinfo(request.actor.domain) + software = nodeinfo.swname if nodeinfo else None - request.app.database.save() + ## reject if software used by actor is banned + if request.config.is_banned_software(software): + return logging.verbose(f'Rejected follow from actor for using specific software: actor={request.actor.id}, software={software}') + + request.database.add_inbox(request.actor.shared_inbox, request.message.id, software) + request.database.save() await misc.request( - actor.shared_inbox, + request.actor.shared_inbox, misc.Message.new_response( - host = request.app.config.host, - actor = actor.id, - followid = data.id, + host = request.config.host, + actor = request.actor.id, + followid = request.message.id, accept = True ) ) # Are Akkoma and Pleroma the only two that expect a follow back? # Ignoring only Mastodon for now - if nodeinfo.swname != 'mastodon': + if software != 'mastodon': await misc.request( - actor.shared_inbox, + request.actor.shared_inbox, misc.Message.new_follow( - host = request.app.config.host, - actor = actor.id + host = request.config.host, + actor = request.actor.id ) ) -async def handle_undo(request, actor, data, nodeinfo): +async def handle_undo(request): ## If the object is not a Follow, forward it - if data['object']['type'] != 'Follow': - return await handle_forward(request, actor, data, nodeinfo) + if request.message.object.type != 'Follow': + return await handle_forward(request) - if not request.app.database.del_inbox(actor.domain, data.id): + if not request.database.del_inbox(request.actor.domain, request.message.id): return - request.app.database.save() + request.database.save() message = misc.Message.new_unfollow( - host = request.app.config.host, - actor = actor.id, - follow = data + host = request.config.host, + actor = request.actor.id, + follow = request.message ) - await misc.request(actor.shared_inbox, message) + await misc.request(request.actor.shared_inbox, message) processors = { @@ -104,9 +109,9 @@ processors = { } -async def run_processor(request, actor, data, nodeinfo): - if data.type not in processors: +async def run_processor(request): + if request.message.type not in processors: return - logging.verbose(f'New "{data.type}" from actor: {actor.id}') - return await processors[data.type](request, actor, data, nodeinfo) + logging.verbose(f'New "{request.message.type}" from actor: {request.actor.id}') + return await processors[request.message.type](request) diff --git a/relay/views.py b/relay/views.py index 44a1686..a223d6d 100644 --- a/relay/views.py +++ b/relay/views.py @@ -33,10 +33,10 @@ def register_route(method, path): @register_route('GET', '/') async def home(request): - targets = '
'.join(request.app.database.hostnames) - note = request.app.config.note - count = len(request.app.database.hostnames) - host = request.app.config.host + targets = '
'.join(request.database.hostnames) + note = request.config.note + count = len(request.database.hostnames) + host = request.config.host text = f""" @@ -64,8 +64,8 @@ a:hover {{ color: #8AF; }} @register_route('GET', '/actor') async def actor(request): data = Message.new_actor( - host = request.app.config.host, - pubkey = request.app.database.pubkey + host = request.config.host, + pubkey = request.database.pubkey ) return Response.new(data, ctype='activity') @@ -74,67 +74,63 @@ async def actor(request): @register_route('POST', '/inbox') @register_route('POST', '/actor') async def inbox(request): - config = request.app.config - database = request.app.database + config = request.config + database = request.database ## reject if missing signature header - try: - signature = DotDict.new_from_signature(request.headers['signature']) - - except KeyError: + if not request.signature: logging.verbose('Actor missing signature header') raise HTTPUnauthorized(body='missing signature') - ## read message try: - data = await request.json(loads=Message.new_from_json) + request['message'] = await request.json(loads=Message.new_from_json) + + ## reject if there is no message + if not request.message: + logging.verbose('empty message') + return Response.new_error(400, 'missing message', 'json') ## reject if there is no actor in the message - if 'actor' not in data: - logging.verbose('actor not in data') + if 'actor' not in request.message: + logging.verbose('actor not in message') return Response.new_error(400, 'no actor in message', 'json') except: + ## this code should hopefully never get called traceback.print_exc() logging.verbose('Failed to parse inbox message') return Response.new_error(400, 'failed to parse message', 'json') - actor = await misc.request(signature.keyid) - nodeinfo = await misc.fetch_nodeinfo(actor.domain) + request['actor'] = await misc.request(request.signature.keyid) ## reject if actor is empty - if not actor: - logging.verbose(f'Failed to fetch actor: {actor.id}') + if not request.actor: + logging.verbose(f'Failed to fetch actor: {request.actor.id}') return Response.new_error(400, 'failed to fetch actor', 'json') ## reject if the actor isn't whitelisted while the whiltelist is enabled - elif config.whitelist_enabled and not config.is_whitelisted(actor.domain): - logging.verbose(f'Rejected actor for not being in the whitelist: {actor.id}') + if config.whitelist_enabled and not config.is_whitelisted(request.actor.domain): + logging.verbose(f'Rejected actor for not being in the whitelist: {request.actor.id}') return Response.new_error(403, 'access denied', 'json') ## reject if actor is banned - if request.app['config'].is_banned(actor.domain): + if request.config.is_banned(request.actor.domain): logging.verbose(f'Ignored request from banned actor: {actor.id}') return Response.new_error(403, 'access denied', 'json') - ## reject if software used by actor is banned - if config.is_banned_software(nodeinfo.swname): - logging.verbose(f'Rejected actor for using specific software: {nodeinfo.swname}') - return Response.new_error(403, 'access denied', 'json') - ## reject if the signature is invalid - if not (await misc.validate_signature(actor, signature, request)): + if not (await misc.validate_signature(request.actor, request.signature, request)): logging.verbose(f'signature validation failed for: {actor.id}') return Response.new_error(401, 'signature check failed', 'json') ## 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}') + if request.message.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}') return Response.new_error(401, 'access denied', 'json') - logging.debug(f">> payload {data}") + logging.debug(f">> payload {request.message.to_json(4)}") - await run_processor(request, actor, data, nodeinfo) + await run_processor(request) return Response.new(status=202) @@ -146,15 +142,15 @@ async def webfinger(request): except KeyError: return Response.new_error(400, 'missing \'resource\' query key', 'json') - if subject != f'acct:relay@{request.app.config.host}': + if subject != f'acct:relay@{request.config.host}': return Response.new_error(404, 'user not found', 'json') data = { 'subject': subject, - 'aliases': [request.app.config.actor], + 'aliases': [request.config.actor], 'links': [ - {'href': request.app.config.actor, 'rel': 'self', 'type': 'application/activity+json'}, - {'href': request.app.config.actor, 'rel': 'self', 'type': 'application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"'} + {'href': request.config.actor, 'rel': 'self', 'type': 'application/activity+json'}, + {'href': request.config.actor, 'rel': 'self', 'type': 'application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"'} ] } @@ -165,7 +161,7 @@ async def webfinger(request): async def nodeinfo_2_0(request): niversion = request.match_info['version'][:3] data = { - 'openRegistrations': not request.app.config.whitelist_enabled, + 'openRegistrations': not request.config.whitelist_enabled, 'protocols': ['activitypub'], 'services': { 'inbound': [], @@ -182,7 +178,7 @@ async def nodeinfo_2_0(request): } }, 'metadata': { - 'peers': request.app.database.hostnames + 'peers': request.database.hostnames }, 'version': niversion } @@ -196,8 +192,8 @@ async def nodeinfo_2_0(request): @register_route('GET', '/.well-known/nodeinfo') async def nodeinfo_wellknown(request): data = WKNodeinfo.new( - v20 = f'https://{request.app.config.host}/nodeinfo/2.0.json', - v21 = f'https://{request.app.config.host}/nodeinfo/2.1.json' + v20 = f'https://{request.config.host}/nodeinfo/2.0.json', + v21 = f'https://{request.config.host}/nodeinfo/2.1.json' ) return Response.new(data, ctype='json')