import aputils import asyncio import logging import subprocess import traceback from pathlib import Path from urllib.parse import urlparse from . import __version__, misc from .misc import DotDict, Message, Response from .processors import run_processor routes = [] version = __version__ if Path(__file__).parent.parent.joinpath('.git').exists(): try: commit_label = subprocess.check_output(["git", "rev-parse", "HEAD"]).strip().decode('ascii') version = f'{__version__} {commit_label}' except: pass def register_route(method, *paths): def wrapper(func): for path in paths: routes.append([method, path, func]) return func return wrapper @register_route('GET', '/') async def home(request, s): hostnames = s.get_hostnames() config = s.get_config_all() targets = '
'.join(hostnames) note = config.description count = len(hostnames) host = request.config.host text = f""" ActivityPub Relay at {host}

This is an Activity Relay for fediverse instances.

{note}

You may subscribe to this relay with the address: https://{host}/actor

To host your own relay, you may download the code at this address: https://git.pleroma.social/pleroma/relay


List of {count} registered instances:
{targets}

""" return Response.new(text, ctype='html') @register_route('GET', '/actor', '/inbox') async def actor(request, s): data = Message.new_actor( host = request.config.host, pubkey = request.app.signer.pubkey, name = s.get_config('name'), description = s.get_config('description'), locked = s.get_config('require_approval') ) return Response.new(data, ctype='activity') @register_route('POST', '/actor', '/inbox') async def inbox(request, s): ## reject if missing signature header if not request.signature: logging.verbose('Actor missing signature header') raise HTTPUnauthorized(body='missing signature') domain = urlparse(request.signature.keyid).hostname ## reject if actor is banned if s.get_ban('domain', domain): logging.verbose(f'Ignored request from banned actor: {domain}') return Response.new_error(403, 'access denied', 'json') try: 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 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') request['actor'] = await request.app.client.get(request.signature.keyid, sign_headers=True) ## reject if actor is empty if not request.actor: ## ld signatures aren't handled atm, so just ignore it if request['message'].type == 'Delete': logging.verbose(f'Instance sent a delete which cannot be handled') return Response.new(status=202) logging.verbose(f'Failed to fetch actor: {request.signature.keyid}') return Response.new_error(400, 'failed to fetch actor', 'json') request['instance'] = s.get_instance(request.actor.shared_inbox) config = s.get_config_all() ## reject if the signature is invalid try: await request.actor.signer.validate_aiohttp_request(request) except aputils.SignatureValidationError as e: logging.verbose(f'signature validation failed for: {actor.id}') logging.debug(str(e)) return Response.new_error(401, str(e), 'json') ## reject if activity type isn't 'Follow' and the actor isn't following if request.message.type != 'Follow' and (not request.instance or not request.instance.joined): 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 {request.message.to_json(4)}") asyncio.ensure_future(run_processor(request)) return Response.new(status=202) @register_route('GET', '/.well-known/webfinger') async def webfinger(request): try: subject = request.query['resource'] except KeyError: return Response.new_error(400, 'missing \'resource\' query key', 'json') if subject != f'acct:relay@{request.config.host}': return Response.new_error(404, 'user not found', 'json') data = aputils.Webfinger.new( handle = 'relay', domain = request.config.host, actor = request.config.actor ) return Response.new(data, ctype='json') @register_route('GET', '/nodeinfo/{version:\d.\d\.json}') async def nodeinfo(request, s): niversion = request.match_info['version'][:3] data = dict( name = 'activityrelay', version = version, protocols = ['activitypub'], open_regs = not s.get_config('whitelist'), users = 1, metadata = {'peers': s.get_hostnames()} ) if niversion == '2.1': data['repo'] = 'https://git.pleroma.social/pleroma/relay' return Response.new(aputils.Nodeinfo.new(**data), ctype='json') @register_route('GET', '/.well-known/nodeinfo') async def nodeinfo_wellknown(request): data = aputils.WellKnownNodeinfo.new_template(request.config.host) return Response.new(data, ctype='json')