remove old api secret

This commit is contained in:
Laura Klünder 2023-12-01 17:40:12 +01:00
parent 5c203a7a2b
commit cf765acc00
6 changed files with 46 additions and 22 deletions

View file

@ -2,13 +2,20 @@ import string
from django.conf import settings from django.conf import settings
from django.db import models 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.crypto import constant_time_compare, get_random_string
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
class SecretQuerySet(models.QuerySet): class SecretQuerySet(models.QuerySet):
def get_by_secret(self, secret): 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): class Secret(models.Model):
@ -22,6 +29,8 @@ class Secret(models.Model):
scope_mesh = models.BooleanField(_('mesh access'), default=False) scope_mesh = models.BooleanField(_('mesh access'), default=False)
valid_until = models.DateTimeField(null=True, verbose_name=_('valid_until')) valid_until = models.DateTimeField(null=True, verbose_name=_('valid_until'))
objects = models.Manager.from_queryset(SecretQuerySet)()
def scopes_display(self): def scopes_display(self):
return [ return [
field.verbose_name for field in self._meta.get_fields() field.verbose_name for field in self._meta.get_fields()

View file

@ -5,8 +5,6 @@ from importlib import import_module
from django.contrib.auth import get_user as auth_get_user from django.contrib.auth import get_user as auth_get_user
from django.contrib.auth.models import AnonymousUser 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 django.utils.functional import SimpleLazyObject, lazy
from ninja.security import HttpBearer from ninja.security import HttpBearer
@ -77,10 +75,7 @@ class APITokenAuth(HttpBearer):
) )
elif token.startswith("secret:"): elif token.startswith("secret:"):
try: try:
secret = Secret.objects.filter( secret = Secret.objects.get_by_secret(token.removeprefix("secret:")).get()
Q(api_secret=token.removeprefix("secret:")),
Q(valid_until__isnull=True) | Q(valid_until__gte=timezone.now()),
).select_related("user", "user__permissions").get()
except Secret.DoesNotExist: except Secret.DoesNotExist:
raise APITokenInvalid raise APITokenInvalid

View file

@ -15,6 +15,7 @@ from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext_lazy from django.utils.translation import ngettext_lazy
from c3nav.api.models import Secret
from c3nav.control.models import UserPermissions, UserSpaceAccess from c3nav.control.models import UserPermissions, UserSpaceAccess
from c3nav.mapdata.forms import I18nModelFormMixin from c3nav.mapdata.forms import I18nModelFormMixin
from c3nav.mapdata.models import MapUpdate, Space from c3nav.mapdata.models import MapUpdate, Space
@ -159,8 +160,10 @@ class AccessPermissionForm(Form):
def get_signed_data(self, key=None): def get_signed_data(self, key=None):
# todo: yep, we stil need to fix this # todo: yep, we stil need to fix this
if not self.author.permissions.api_secret: try:
raise ValueError('Author has no api secret.') 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 = { data = {
'id': self.data['access_restrictions'], 'id': self.data['access_restrictions'],
'time': int(time.time()), 'time': int(time.time()),
@ -170,8 +173,7 @@ class AccessPermissionForm(Form):
if key is not None: if key is not None:
data['key'] = key data['key'] = key
data = json.dumps(data, separators=(',', ':')) data = json.dumps(data, separators=(',', ':'))
signature = hmac.new(self.author.permissions.api_secret.encode(), signature = hmac.new(api_secret, msg=data.encode(), digestmod=hashlib.sha256).digest()
msg=data.encode(), digestmod=hashlib.sha256).digest()
return '%s:%s' % (data, binascii.b2a_base64(signature).strip().decode()) return '%s:%s' % (data, binascii.b2a_base64(signature).strip().decode())
@classmethod @classmethod
@ -230,16 +232,19 @@ class AccessPermissionForm(Form):
except User.DoesNotExist: except User.DoesNotExist:
raise SignedPermissionDataError('Author does not exist.') raise SignedPermissionDataError('Author does not exist.')
try: api_secrets = author.api_secrets.filter(
api_secret = author.permissions.api_secret scope_grant_permission=True
except AttributeError: ).valid_only().values_list('api_secret', flat=True)
if not api_secrets:
raise SignedPermissionDataError('Author has no API secret.') raise SignedPermissionDataError('Author has no API secret.')
for api_secret in api_secrets:
verify_signature = binascii.b2a_base64(hmac.new(api_secret.encode(), verify_signature = binascii.b2a_base64(hmac.new(api_secret.encode(),
msg=raw_data.encode(), digestmod=hashlib.sha256).digest()) msg=raw_data.encode(), digestmod=hashlib.sha256).digest())
print(verify_signature, signature) if signature == verify_signature.strip().decode():
if signature != verify_signature.strip().decode(): break
raise SignedPermissionDataError('Invalid signature.') else:
raise SignedPermissionDataError('Invalid signature.') # todo: test this!!
form = cls(author=author, expire_date=valid_until, data={ form = cls(author=author, expire_date=valid_until, data={
'access_restrictions': str(restrictions), 'access_restrictions': str(restrictions),

View file

@ -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",
),
]

View file

@ -37,8 +37,6 @@ class UserPermissions(models.Model):
mesh_control = models.BooleanField(default=False, verbose_name=_('can access mesh control')) 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: class Meta:
verbose_name = _('User Permissions') verbose_name = _('User Permissions')
verbose_name_plural = _('User Permissions') verbose_name_plural = _('User Permissions')

View file

@ -22,7 +22,7 @@ def grant_access(request): # todo: make class based view
token = form.get_token() token = form.get_token()
token.save() token.save()
# todo: this still needs fixing # todo: this still needs fixing
if settings.DEBUG and request.user_permissions.api_secret: if settings.DEBUG:
signed_data = form.get_signed_data() signed_data = form.get_signed_data()
print('/?'+urlencode({'access': signed_data})) print('/?'+urlencode({'access': signed_data}))
return redirect(reverse('control.access.qr', kwargs={'token': token.token})) return redirect(reverse('control.access.qr', kwargs={'token': token.token}))