diff --git a/api/funkwhale_api/federation/dynamic_preferences_registry.py b/api/funkwhale_api/federation/dynamic_preferences_registry.py new file mode 100644 index 000000000..83d0285be --- /dev/null +++ b/api/funkwhale_api/federation/dynamic_preferences_registry.py @@ -0,0 +1,34 @@ +from django.forms import widgets + +from dynamic_preferences import types +from dynamic_preferences.registries import global_preferences_registry + +federation = types.Section('federation') + + +@global_preferences_registry.register +class FederationPrivateKey(types.StringPreference): + show_in_api = False + section = federation + name = 'private_key' + default = '' + help_text = ( + 'Instance private key, used for signing federation HTTP requests' + ) + verbose_name = ( + 'Instance private key (keep it secret, do not change it)' + ) + + +@global_preferences_registry.register +class FederationPublicKey(types.StringPreference): + show_in_api = False + section = federation + name = 'public_key' + default = '' + help_text = ( + 'Instance public key, used for signing federation HTTP requests' + ) + verbose_name = ( + 'Instance public key (do not change it)' + ) diff --git a/api/funkwhale_api/federation/management/__init__.py b/api/funkwhale_api/federation/management/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/api/funkwhale_api/federation/management/commands/__init__.py b/api/funkwhale_api/federation/management/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/api/funkwhale_api/federation/management/commands/generate_keys.py b/api/funkwhale_api/federation/management/commands/generate_keys.py new file mode 100644 index 000000000..eafe9aae3 --- /dev/null +++ b/api/funkwhale_api/federation/management/commands/generate_keys.py @@ -0,0 +1,53 @@ +from django.core.management.base import BaseCommand, CommandError +from django.db import transaction + +from dynamic_preferences.registries import global_preferences_registry + +from funkwhale_api.federation import keys + + +class Command(BaseCommand): + help = ( + 'Generate a public/private key pair for your instance,' + ' for federation purposes. If a key pair already exists, does nothing.' + ) + + def add_arguments(self, parser): + parser.add_argument( + '--replace', + action='store_true', + dest='replace', + default=False, + help='Replace existing key pair, if any', + ) + parser.add_argument( + '--noinput', '--no-input', action='store_false', dest='interactive', + help="Do NOT prompt the user for input of any kind.", + ) + + @transaction.atomic + def handle(self, *args, **options): + preferences = global_preferences_registry.manager() + existing_public = preferences['federation__public_key'] + existing_private = preferences['federation__public_key'] + + if existing_public or existing_private and not options['replace']: + raise CommandError( + 'Keys are already present! ' + 'Replace them with --replace if you know what you are doing.') + + if options['interactive']: + message = ( + 'Are you sure you want to do this?\n\n' + "Type 'yes' to continue, or 'no' to cancel: " + ) + if input(''.join(message)) != 'yes': + raise CommandError("Operation cancelled.") + private, public = keys.get_key_pair() + preferences['federation__public_key'] = public.decode('utf-8') + preferences['federation__private_key'] = private.decode('utf-8') + + self.stdout.write( + 'Your new key pair was generated.' + 'Your public key is now:\n\n{}'.format(public.decode('utf-8')) + ) diff --git a/api/tests/federation/__init__.py b/api/tests/federation/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/api/tests/federation/test_commands.py b/api/tests/federation/test_commands.py new file mode 100644 index 000000000..7c5333068 --- /dev/null +++ b/api/tests/federation/test_commands.py @@ -0,0 +1,14 @@ +from django.core.management import call_command + + +def test_generate_instance_key_pair(preferences, mocker): + mocker.patch( + 'funkwhale_api.federation.keys.get_key_pair', + return_value=(b'private', b'public')) + assert preferences['federation__public_key'] == '' + assert preferences['federation__private_key'] == '' + + call_command('generate_keys', interactive=False) + + assert preferences['federation__private_key'] == 'private' + assert preferences['federation__public_key'] == 'public'