From b4e7a088cf4c38f431d48e7b8020baaf6e71098e Mon Sep 17 00:00:00 2001
From: jackie / Andrea Ida Malkah Klaura <jackie@diebin.at>
Date: Sun, 11 Apr 2021 14:19:27 +0200
Subject: [PATCH] add management command to create OIDC client

---
 .../management/commands/create_oidc_client.py | 101 ++++++++++++++++++
 1 file changed, 101 insertions(+)
 create mode 100644 program/management/commands/create_oidc_client.py

diff --git a/program/management/commands/create_oidc_client.py b/program/management/commands/create_oidc_client.py
new file mode 100644
index 00000000..b60503ca
--- /dev/null
+++ b/program/management/commands/create_oidc_client.py
@@ -0,0 +1,101 @@
+from django.core.management.base import BaseCommand, CommandError
+import json, sys, random, string
+from oidc_provider.models import Client, ResponseType
+
+
+class Command(BaseCommand):
+    help = 'Sets up an OIDC client / relaying party. For details check out the' + \
+        'section on Relying Parties at https://django-oidc-provider.readthedocs.io'
+
+    def add_arguments(self, parser):
+        parser.add_argument('name', type=str,
+            help='A label that you associate with this client')
+        parser.add_argument('client_type', type=str, choices=['public', 'confidential'],
+            help='The type of client can be either public or confidential')
+        parser.add_argument('--no-require-consent', dest='require_consent', action='store_false',
+            help='By default user consent is required. Use this to skip user consent.')
+        parser.add_argument('--no-reuse-consent', dest='reuse_consent', action='store_false',
+            help='By default user consent will be reused. Use this if the user should provide consent on every login.')
+        parser.set_defaults(require_consent=True, reuse_consent=True)
+        parser.add_argument('-u', '--redirect-uri', type=str, action='append',
+            help='Redirect URI after successful authentication. Can be used more than once.')
+        parser.add_argument('-p', '--post-logout-redirect', type=str, action='append',
+            help='Post logout redirect URI. Can be used more than once.')
+        parser.add_argument('-s', '--scope', type=str, action='append',
+            help='Authorized scope values for this client. Can be used more than once.')
+        parser.add_argument('-r', dest='response_types', action='append',
+            choices=['code', 'id_token', 'id_token token', 'code token', 'code id_token', 'code id_token token'],
+            help='The type of response the client will get.')
+        parser.add_argument('-i', '--id-only', dest='id_only', action='store_true',
+            help='Do not print anything else then the ID of the newly created client '+\
+                 '(and the client secret in case of confidential clients).')
+        parser.set_defaults(id_only=False)
+
+
+    def handle(self, *args, **options):
+        # generate a new client ID and secret
+        client_id = False
+        counter = 0
+        while not client_id:
+            client_id = random.randint(100000, 999999)
+            counter += 1
+            if counter > 10000:
+                raise CommandError('Could not find a free client_id. Already'+\
+                    ' tried 10000 times. There seems to be something seriously'+\
+                    ' wrong with your setup. Please inspect manually.')
+            try:
+                Client.objects.get(client_id=client_id)
+            except Client.DoesNotExist:
+                pass
+            else:
+                client_id = False
+
+        client_secret = ''
+        if options['client_type'] == 'confidential':
+            client_secret = ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(32))
+
+        # initialize lists if no option was provided
+        if options['redirect_uri'] is None:
+            options['redirect_uri'] = []
+        if options['post_logout_redirect'] is None:
+            options['post_logout_redirect'] = []
+        if options['scope'] is None:
+            options['scope'] = []
+
+        if not options["id_only"]:
+            self.stdout.write(f'Creating client with name {options["name"]}')
+        try:
+            c = Client(
+                client_id=client_id,
+                client_secret=client_secret,
+                name=options['name'], client_type=options['client_type'],
+                redirect_uris=options['redirect_uri'],
+                require_consent=options['require_consent'],
+                reuse_consent=options['reuse_consent'],
+                post_logout_redirect_uris=options['post_logout_redirect'],
+                scope=options['scope'],
+            )
+            c.save()
+        except:
+            raise CommandError('Could not create an OpenID connect client' +\
+                f' due to the following error: {sys.exc_info()}')
+
+
+        if options['response_types']:
+            try:
+                for r_value in options['response_types']:
+                    r = ResponseType.objects.get(value=r_value)
+                    c.response_types.add(r)
+            except:
+                raise CommandError('Client was stored, but could not set response_types'+\
+                    f' due to the following error: {sys.exc_info()}')
+
+        if options["id_only"]:
+            if options['client_type'] == 'confidential':
+                self.stdout.write(f'{c.client_id} {c.client_secret}')
+            else:
+                self.stdout.write(f'{c.client_id}')
+        else:
+            self.stdout.write(f'Successfully created new OIDC client, with ID: {c.client_id}')
+            if options['client_type'] == 'confidential':
+                self.stdout.write(f'The secret for this confidential client is: {c.client_secret}')
-- 
GitLab