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.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()

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.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

View file

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

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'))
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')

View file

@ -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}))