From cf765acc002b838c8d199a7e5a4841bb412f16e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Fri, 1 Dec 2023 17:40:12 +0100 Subject: [PATCH] remove old api secret --- src/c3nav/api/models.py | 11 ++++++- src/c3nav/api/newauth.py | 7 +---- src/c3nav/control/forms.py | 29 +++++++++++-------- .../0011_remove_userpermissions_api_secret.py | 17 +++++++++++ src/c3nav/control/models.py | 2 -- src/c3nav/control/views/access.py | 2 +- 6 files changed, 46 insertions(+), 22 deletions(-) create mode 100644 src/c3nav/control/migrations/0011_remove_userpermissions_api_secret.py diff --git a/src/c3nav/api/models.py b/src/c3nav/api/models.py index 3d1ae021..fcff3674 100644 --- a/src/c3nav/api/models.py +++ b/src/c3nav/api/models.py @@ -2,13 +2,20 @@ import string from django.conf import settings from django.db import models +from django.db.models import Q +from django.utils import timezone from django.utils.crypto import constant_time_compare, get_random_string from django.utils.translation import gettext_lazy as _ class SecretQuerySet(models.QuerySet): def get_by_secret(self, secret): - self.filter(secret=secret, ) + return self.filter(api_secret=secret).valid_only() + + def valid_only(self): + return self.filter( + Q(valid_until__isnull=True) | Q(valid_until__gte=timezone.now()), + ) class Secret(models.Model): @@ -22,6 +29,8 @@ class Secret(models.Model): scope_mesh = models.BooleanField(_('mesh access'), default=False) valid_until = models.DateTimeField(null=True, verbose_name=_('valid_until')) + objects = models.Manager.from_queryset(SecretQuerySet)() + def scopes_display(self): return [ field.verbose_name for field in self._meta.get_fields() diff --git a/src/c3nav/api/newauth.py b/src/c3nav/api/newauth.py index 0fb9baf7..5f94f66b 100644 --- a/src/c3nav/api/newauth.py +++ b/src/c3nav/api/newauth.py @@ -5,8 +5,6 @@ from importlib import import_module from django.contrib.auth import get_user as auth_get_user from django.contrib.auth.models import AnonymousUser -from django.db.models import Q -from django.utils import timezone from django.utils.functional import SimpleLazyObject, lazy from ninja.security import HttpBearer @@ -77,10 +75,7 @@ class APITokenAuth(HttpBearer): ) elif token.startswith("secret:"): try: - secret = Secret.objects.filter( - Q(api_secret=token.removeprefix("secret:")), - Q(valid_until__isnull=True) | Q(valid_until__gte=timezone.now()), - ).select_related("user", "user__permissions").get() + secret = Secret.objects.get_by_secret(token.removeprefix("secret:")).get() except Secret.DoesNotExist: raise APITokenInvalid diff --git a/src/c3nav/control/forms.py b/src/c3nav/control/forms.py index 58ceef84..8fa9071b 100644 --- a/src/c3nav/control/forms.py +++ b/src/c3nav/control/forms.py @@ -15,6 +15,7 @@ from django.utils import timezone from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext_lazy +from c3nav.api.models import Secret from c3nav.control.models import UserPermissions, UserSpaceAccess from c3nav.mapdata.forms import I18nModelFormMixin from c3nav.mapdata.models import MapUpdate, Space @@ -159,8 +160,10 @@ class AccessPermissionForm(Form): def get_signed_data(self, key=None): # todo: yep, we stil need to fix this - if not self.author.permissions.api_secret: - raise ValueError('Author has no api secret.') + try: + api_secret = self.author.api_secrets.filter(scope_grant_permission=True).valid_only().get().api_secret + except Secret.DoesNotExist: + raise ValueError('Author has no feasable api secret.') data = { 'id': self.data['access_restrictions'], 'time': int(time.time()), @@ -170,8 +173,7 @@ class AccessPermissionForm(Form): if key is not None: data['key'] = key data = json.dumps(data, separators=(',', ':')) - signature = hmac.new(self.author.permissions.api_secret.encode(), - msg=data.encode(), digestmod=hashlib.sha256).digest() + signature = hmac.new(api_secret, msg=data.encode(), digestmod=hashlib.sha256).digest() return '%s:%s' % (data, binascii.b2a_base64(signature).strip().decode()) @classmethod @@ -230,16 +232,19 @@ class AccessPermissionForm(Form): except User.DoesNotExist: raise SignedPermissionDataError('Author does not exist.') - try: - api_secret = author.permissions.api_secret - except AttributeError: + api_secrets = author.api_secrets.filter( + scope_grant_permission=True + ).valid_only().values_list('api_secret', flat=True) + if not api_secrets: raise SignedPermissionDataError('Author has no API secret.') - verify_signature = binascii.b2a_base64(hmac.new(api_secret.encode(), - msg=raw_data.encode(), digestmod=hashlib.sha256).digest()) - print(verify_signature, signature) - if signature != verify_signature.strip().decode(): - raise SignedPermissionDataError('Invalid signature.') + for api_secret in api_secrets: + verify_signature = binascii.b2a_base64(hmac.new(api_secret.encode(), + msg=raw_data.encode(), digestmod=hashlib.sha256).digest()) + if signature == verify_signature.strip().decode(): + break + else: + raise SignedPermissionDataError('Invalid signature.') # todo: test this!! form = cls(author=author, expire_date=valid_until, data={ 'access_restrictions': str(restrictions), diff --git a/src/c3nav/control/migrations/0011_remove_userpermissions_api_secret.py b/src/c3nav/control/migrations/0011_remove_userpermissions_api_secret.py new file mode 100644 index 00000000..9b994ca1 --- /dev/null +++ b/src/c3nav/control/migrations/0011_remove_userpermissions_api_secret.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.3 on 2023-12-01 16:38 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("control", "0010_userpermissions_mesh_control"), + ("api", "0003_rename_token_logintoken_secret"), + ] + + operations = [ + migrations.RemoveField( + model_name="userpermissions", + name="api_secret", + ), + ] diff --git a/src/c3nav/control/models.py b/src/c3nav/control/models.py index 54414b76..31c833e8 100644 --- a/src/c3nav/control/models.py +++ b/src/c3nav/control/models.py @@ -37,8 +37,6 @@ class UserPermissions(models.Model): mesh_control = models.BooleanField(default=False, verbose_name=_('can access mesh control')) - api_secret = models.CharField(null=True, blank=True, max_length=64, verbose_name=_('API secret')) - class Meta: verbose_name = _('User Permissions') verbose_name_plural = _('User Permissions') diff --git a/src/c3nav/control/views/access.py b/src/c3nav/control/views/access.py index 98d818ed..9c208203 100644 --- a/src/c3nav/control/views/access.py +++ b/src/c3nav/control/views/access.py @@ -22,7 +22,7 @@ def grant_access(request): # todo: make class based view token = form.get_token() token.save() # todo: this still needs fixing - if settings.DEBUG and request.user_permissions.api_secret: + if settings.DEBUG: signed_data = form.get_signed_data() print('/?'+urlencode({'access': signed_data})) return redirect(reverse('control.access.qr', kwargs={'token': token.token}))