diff --git a/relay/config.py b/relay/config.py index 45fcf40..951b6f3 100644 --- a/relay/config.py +++ b/relay/config.py @@ -5,6 +5,14 @@ from pathlib import Path from urllib.parse import urlparse +relay_software_names = [ + 'activityrelay', + 'aoderelay', + 'social.seattle.wa.us-relay', + 'unciarelay' +] + + class DotDict(dict): def __getattr__(self, k): try: diff --git a/relay/manage.py b/relay/manage.py index 213ecdb..336be3c 100644 --- a/relay/manage.py +++ b/relay/manage.py @@ -9,14 +9,15 @@ import platform from aiohttp.web import AppRunner, TCPSite from cachetools import LRUCache -from . import app, misc, views -from .config import DotDict, RelayConfig +from . import app, misc, views, __version__ +from .config import DotDict, RelayConfig, relay_software_names from .database import RelayDatabase from .misc import check_open_port, follow_remote_actor, unfollow_remote_actor @click.group('cli', context_settings={'show_default': True}, invoke_without_command=True) @click.option('--config', '-c', default='relay.yaml', help='path to the relay\'s config') +@click.version_option(version=__version__, prog_name='ActivityRelay') @click.pass_context def cli(ctx, config): app['is_docker'] = bool(os.environ.get('DOCKER_RUNNING')) @@ -42,61 +43,42 @@ def cli(ctx, config): relay_run.callback() -@cli.command('list') -@click.argument('type', required=False, default='inbox') -def relay_list(type): - 'List all following instances' - - assert type in [None, 'inbox', 'ban', 'whitelist'] - - config = app['config'] - database = app['database'] - - if not type or type == 'inbox': - click.echo('Connected to the following instances or relays:') - - for inbox in database.inboxes: - click.echo(f'- {inbox}') - - elif type == 'ban': - click.echo('Banned instances:') - - for instance in config.blocked_instances: - click.echo(f'- {instance}') - - click.echo('\nBanned software:') - - for software in config.blocked_software: - click.echo(f'- {software}') - - elif type == 'whitelist': - click.echo('Whitelisted instances:') - - for instance in config.whitelist: - click.echo(f'- {instance}') +@cli.group('inbox') +@click.pass_context +def cli_inbox(ctx): + 'Manage the inboxes in the database' + pass -@cli.command('follow') +@cli_inbox.command('list') +def cli_inbox_list(): + 'List the connected instances or relays' + + click.echo('Connected to the following instances or relays:') + + for inbox in app['database'].inboxes: + click.echo(f'- {inbox}') + + +@cli_inbox.command('follow') @click.argument('actor') -def relay_follow(actor): +def cli_inbox_follow(actor): 'Follow an actor (Relay must be running)' - loop = asyncio.new_event_loop() - loop.run_until_complete(handle_follow_actor(actor)) + run_in_loop(handle_follow_actor, actor) -@cli.command('unfollow') +@cli_inbox.command('unfollow') @click.argument('actor') -def relay_follow(actor): +def cli_inbox_unfollow(actor): 'Unfollow an actor (Relay must be running)' - loop = asyncio.new_event_loop() - loop.run_until_complete(handle_unfollow_actor(actor)) + run_in_loop(handle_unfollow_actor(actor)) -@cli.command('add') +@cli_inbox.command('add') @click.argument('inbox') -def relay_add(inbox): +def cli_inbox_add(inbox): 'Add an inbox to the database' database = app['database'] @@ -122,9 +104,9 @@ def relay_add(inbox): click.echo(f'Added inbox to the database: {inbox}') -@cli.command('remove') +@cli_inbox.command('remove') @click.argument('inbox') -def relay_remove(inbox): +def cli_inbox_remove(inbox): 'Remove an inbox from the database' database = app['database'] @@ -139,58 +121,158 @@ def relay_remove(inbox): click.echo(f'Removed inbox from the database: {inbox}') -# todo: add nested groups -@cli.command('ban') -@click.argument('type') -@click.argument('target') -def relay_ban(type, target): - 'Ban an instance or software' +@cli.group('instance') +def cli_instance(): + 'Manage instance bans' + pass - assert type in ['instance', 'software'] + +@cli_instance.command('list') +def cli_instance_list(): + 'List all banned instances' + + click.echo('Banned instances or relays:') + + for domain in app['config'].blocked_instances: + click.echo(f'- {domain}') + + +@cli_instance.command('ban') +@click.argument('target') +def cli_instance_ban(target): + 'Ban an instance and remove the associated inbox if it exists' config = app['config'] database = app['database'] inbox = database.get_inbox(target) - bancmd = getattr(config, f'ban_{type}') - - if bancmd(target): + if config.ban_instance(target): config.save() if inbox: database.del_inbox(inbox) database.save() - click.echo(f'Banned {type}: {target}') + click.echo(f'Banned instance: {target}') return - click.echo(f'{type.title()} already banned: {target}') + click.echo(f'Instance already banned: {target}') -@cli.command('unban') -@click.argument('type') +@cli_instance.command('unban') @click.argument('target') -def relay_unban(type, target): - 'Unban an instance or software' - - assert type in ['instance', 'software'] +def cli_instance_unban(target): + 'Unban an instance' config = app['config'] - database = app['database'] - unbancmd = getattr(config, f'unban_{type}') - - if unbancmd(target): + if config.unban_instance(target): config.save() - return click.echo(f'Unbanned {type}: {target}') + click.echo(f'Unbanned instance: {target}') + return - return click.echo(f'{type.title()} is not banned: {target}') + click.echo(f'Instance wasn\'t banned: {target}') -@cli.command('allow') +@cli.group('software') +def cli_software(): + 'Manage banned software' + pass + + +@cli_software.command('list') +def cli_software_list(): + 'List all banned software' + + click.echo('Banned software:') + + for software in app['config'].blocked_software: + click.echo(f'- {software}') + + +@cli_software.command('ban') +@click.option('--fetch-nodeinfo/--ignore-nodeinfo', '-f', 'fetch_nodeinfo', default=False, + help='Treat NAME like a domain and try to fet the software name from nodeinfo' +) +@click.argument('name') +def cli_software_ban(name, fetch_nodeinfo): + 'Ban software. Use RELAYS for NAME to ban relays' + + config = app['config'] + + if name == 'RELAYS': + for name in relay_software_names: + config.ban_software(name) + + config.save() + return click.echo('Banned all relay software') + + if fetch_nodeinfo: + software = run_in_loop(fetch_nodeinfo, name) + + if not software: + click.echo(f'Failed to fetch software name from domain: {name}') + + name = software + + if config.ban_software(name): + config.save() + return click.echo(f'Banned software: {name}') + + click.echo(f'Software already banned: {name}') + + +@cli_software.command('unban') +@click.option('--fetch-nodeinfo/--ignore-nodeinfo', '-f', 'fetch_nodeinfo', default=False, + help='Treat NAME like a domain and try to fet the software name from nodeinfo' +) +@click.argument('name') +def cli_software_unban(name, fetch_nodeinfo): + 'Ban software. Use RELAYS for NAME to unban relays' + + config = app['config'] + + if name == 'RELAYS': + for name in relay_software_names: + config.unban_software(name) + + config.save() + return click.echo('Unbanned all relay software') + + if fetch_nodeinfo: + software = run_in_loop(fetch_nodeinfo, name) + + if not software: + click.echo(f'Failed to fetch software name from domain: {name}') + + name = software + + if config.unban_software(name): + config.save() + return click.echo(f'Unbanned software: {name}') + + click.echo(f'Software wasn\'t banned: {name}') + + + +@cli.group('whitelist') +def cli_whitelist(): + 'Manage the instance whitelist' + pass + + +@cli_whitelist.command('list') +def cli_whitelist_list(): + click.echo('Current whitelisted domains') + + for domain in app['config'].whitelist: + click.echo(f'- {domain}') + + +@cli_whitelist.command('add') @click.argument('instance') -def relay_allow(instance): +def cli_whitelist_add(instance): 'Add an instance to the whitelist' config = app['config'] @@ -202,9 +284,9 @@ def relay_allow(instance): click.echo(f'Instance added to the whitelist: {instance}') -@cli.command('deny') +@cli_whitelist.command('remove') @click.argument('instance') -def relay_deny(instance): +def cli_whitelist_remove(instance): 'Remove an instance from the whitelist' config = app['config'] @@ -293,6 +375,11 @@ def relay_run(): loop.run_forever() +def run_in_loop(func, *args, **kwargs): + loop = asyncio.new_event_loop() + return loop.run_until_complete(func(*args, **kwargs)) + + async def handle_follow_actor(app, target): config = app['config'] diff --git a/relay/misc.py b/relay/misc.py index 90c30e0..184f577 100644 --- a/relay/misc.py +++ b/relay/misc.py @@ -136,7 +136,7 @@ async def fetch_actor_key(actor): async def fetch_nodeinfo(domain): nodeinfo_url = None - wk_nodeinfo = await request(f'https://{domain}/.well-known/nodeinfo', sign=False) + wk_nodeinfo = await request(f'https://{domain}/.well-known/nodeinfo', sign_headers=False) if not wk_nodeinfo: return @@ -149,7 +149,7 @@ async def fetch_nodeinfo(domain): if not nodeinfo_url: return - nodeinfo_data = await request(nodeinfo_url, sign=False) + nodeinfo_data = await request(nodeinfo_url, sign_headers=False) try: return nodeinfo_data['software']['name']