implement more of the API

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens Langhammer 2023-09-04 15:22:15 +02:00
parent 28e1c08800
commit 4114c757b9
No known key found for this signature in database
5 changed files with 75 additions and 60 deletions

View File

@ -1,9 +1,10 @@
"""AuthenticatorMobileStage API Views"""
from django_filters.rest_framework.backends import DjangoFilterBackend
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema, inline_serializer
from rest_framework import mixins
from rest_framework.decorators import action
from rest_framework.fields import CharField, UUIDField
from rest_framework.fields import CharField
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
@ -13,8 +14,9 @@ from rest_framework.viewsets import GenericViewSet, ModelViewSet
from authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer
from authentik.stages.authenticator_mobile.api.auth import MobileDeviceTokenAuthentication
from authentik.stages.authenticator_mobile.models import MobileDevice
from authentik.stages.authenticator_mobile.models import MobileDevice, MobileDeviceToken
class MobileDeviceSerializer(ModelSerializer):
@ -26,6 +28,14 @@ class MobileDeviceSerializer(ModelSerializer):
depth = 2
class MobileDeviceEnrollmentSerializer(PassiveSerializer):
device_uid = CharField(required=True)
class MobileDeviceSetPushKeySerializer(PassiveSerializer):
firebase_key = CharField(required=True)
class MobileDeviceViewSet(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
@ -44,54 +54,19 @@ class MobileDeviceViewSet(
permission_classes = [OwnerPermissions]
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
@extend_schema(
responses={
204: "",
},
request=inline_serializer(
"MobileDeviceSetPushKeySerializer",
{
"firebase_key": CharField(required=True),
},
),
)
@action(
methods=["POST"],
detail=True,
permission_classes=[],
authentication_classes=[MobileDeviceTokenAuthentication],
)
def set_notification_key(self):
"""Called by the phone whenever the firebase key changes and we need to update it"""
device = self.get_object()
print(self.request.user)
@action(
methods=["POST"],
detail=True,
permission_classes=[],
authentication_classes=[MobileDeviceTokenAuthentication],
)
def receive_response():
"""Get response from notification on phone"""
pass
@extend_schema(
responses={
200: inline_serializer(
"MobileDeviceEnrollmentCallbackSerializer",
{"device_token": CharField(required=True), "device_uuid": UUIDField(required=True)},
{
# New API token (that will be rotated at some point)
# also used by the backend to sign requests to the cloud broker
# also used by the app to check the signature of incoming requests
"token": CharField(required=True),
},
),
},
request=inline_serializer(
"MobileDeviceEnrollmentSerializer",
{
# New API token (that will be rotated at some point)
# also used by the backend to sign requests to the cloud broker
# also used by the app to check the signature of incoming requests
"token": CharField(required=True),
},
),
request=MobileDeviceEnrollmentSerializer,
)
@action(
methods=["POST"],
@ -101,6 +76,53 @@ class MobileDeviceViewSet(
)
def enrollment_callback(self, request: Request, pk: str) -> Response:
"""Enrollment callback"""
device: MobileDevice = self.get_object()
data = MobileDeviceEnrollmentSerializer(data=request.data)
data.is_valid(raise_exception=True)
device.device_id = data.validated_data["device_uid"]
device.save()
MobileDeviceToken.objects.filter(
device=device,
).delete()
new_token = MobileDeviceToken.objects.create(
device=device,
user=device.user,
)
return Response(
data={
"token": new_token,
}
)
@extend_schema(
responses={
204: OpenApiTypes.STR,
},
request=MobileDeviceSetPushKeySerializer,
)
@action(
methods=["POST"],
detail=True,
permission_classes=[],
authentication_classes=[MobileDeviceTokenAuthentication],
)
def set_notification_key(self, request: Request) -> Response:
"""Called by the phone whenever the firebase key changes and we need to update it"""
device: MobileDevice = self.get_object()
data = MobileDeviceSetPushKeySerializer(data=request)
data.is_valid(raise_exception=True)
device.firebase_token = data.validated_data["firebase_key"]
device.save()
return Response(status=204)
@action(
methods=["POST"],
detail=True,
permission_classes=[],
authentication_classes=[MobileDeviceTokenAuthentication],
)
def receive_response(self, request: Request) -> Response:
"""Get response from notification on phone"""
print(request.data)
return Response(status=204)

View File

@ -1,4 +1,4 @@
# Generated by Django 4.2.4 on 2023-09-04 11:59
# Generated by Django 4.2.4 on 2023-09-04 13:21
import uuid
@ -66,6 +66,7 @@ class Migration(migrations.Migration):
),
("uuid", models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
("device_id", models.TextField(unique=True)),
("firebase_token", models.TextField(blank=True)),
(
"stage",
models.ForeignKey(
@ -105,7 +106,6 @@ class Migration(migrations.Migration):
default=authentik.stages.authenticator_mobile.models.default_token_key
),
),
("firebase_token", models.TextField(blank=True)),
(
"device",
models.ForeignKey(

View File

@ -69,6 +69,7 @@ class MobileDevice(SerializerModel, Device):
stage = models.ForeignKey(AuthenticatorMobileStage, on_delete=models.CASCADE)
device_id = models.TextField(unique=True)
firebase_token = models.TextField(blank=True)
@property
def serializer(self) -> Serializer:
@ -90,5 +91,3 @@ class MobileDeviceToken(ExpiringModel):
device = models.ForeignKey(MobileDevice, on_delete=models.CASCADE, null=True)
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
token = models.TextField(default=default_token_key)
firebase_token = models.TextField(blank=True)

View File

@ -10,10 +10,7 @@ from authentik.flows.challenge import (
WithUserInfoChallenge,
)
from authentik.flows.stage import ChallengeStageView
from authentik.stages.authenticator_mobile.models import (
MobileDevice,
MobileDeviceToken,
)
from authentik.stages.authenticator_mobile.models import MobileDevice, MobileDeviceToken
FLOW_PLAN_MOBILE_ENROLL = "authentik/stages/authenticator_mobile/enroll"
@ -51,6 +48,7 @@ class AuthenticatorMobileStageView(ChallengeStageView):
device = MobileDevice.objects.create(
user=self.get_pending_user(),
stage=self.executor.current_stage,
confirmed=False,
)
token = MobileDeviceToken.objects.create(
user=self.get_pending_user(),

View File

@ -35218,22 +35218,18 @@ components:
MobileDeviceEnrollmentCallback:
type: object
properties:
device_token:
token:
type: string
device_uuid:
type: string
format: uuid
required:
- device_token
- device_uuid
- token
MobileDeviceEnrollmentRequest:
type: object
properties:
token:
device_uid:
type: string
minLength: 1
required:
- token
- device_uid
MobileDeviceRequest:
type: object
description: Serializer for Mobile authenticator devices