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""" """AuthenticatorMobileStage API Views"""
from django_filters.rest_framework.backends import DjangoFilterBackend from django_filters.rest_framework.backends import DjangoFilterBackend
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema, inline_serializer from drf_spectacular.utils import extend_schema, inline_serializer
from rest_framework import mixins from rest_framework import mixins
from rest_framework.decorators import action 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.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import IsAdminUser from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request 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.api.authorization import OwnerFilter, OwnerPermissions
from authentik.core.api.used_by import UsedByMixin 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.api.auth import MobileDeviceTokenAuthentication
from authentik.stages.authenticator_mobile.models import MobileDevice from authentik.stages.authenticator_mobile.models import MobileDevice, MobileDeviceToken
class MobileDeviceSerializer(ModelSerializer): class MobileDeviceSerializer(ModelSerializer):
@ -26,6 +28,14 @@ class MobileDeviceSerializer(ModelSerializer):
depth = 2 depth = 2
class MobileDeviceEnrollmentSerializer(PassiveSerializer):
device_uid = CharField(required=True)
class MobileDeviceSetPushKeySerializer(PassiveSerializer):
firebase_key = CharField(required=True)
class MobileDeviceViewSet( class MobileDeviceViewSet(
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.UpdateModelMixin, mixins.UpdateModelMixin,
@ -44,54 +54,19 @@ class MobileDeviceViewSet(
permission_classes = [OwnerPermissions] permission_classes = [OwnerPermissions]
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter] 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( @extend_schema(
responses={ responses={
200: inline_serializer( 200: inline_serializer(
"MobileDeviceEnrollmentCallbackSerializer", "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( request=MobileDeviceEnrollmentSerializer,
"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),
},
),
) )
@action( @action(
methods=["POST"], methods=["POST"],
@ -101,6 +76,53 @@ class MobileDeviceViewSet(
) )
def enrollment_callback(self, request: Request, pk: str) -> Response: def enrollment_callback(self, request: Request, pk: str) -> Response:
"""Enrollment callback""" """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) print(request.data)
return Response(status=204) 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 import uuid
@ -66,6 +66,7 @@ class Migration(migrations.Migration):
), ),
("uuid", models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ("uuid", models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
("device_id", models.TextField(unique=True)), ("device_id", models.TextField(unique=True)),
("firebase_token", models.TextField(blank=True)),
( (
"stage", "stage",
models.ForeignKey( models.ForeignKey(
@ -105,7 +106,6 @@ class Migration(migrations.Migration):
default=authentik.stages.authenticator_mobile.models.default_token_key default=authentik.stages.authenticator_mobile.models.default_token_key
), ),
), ),
("firebase_token", models.TextField(blank=True)),
( (
"device", "device",
models.ForeignKey( models.ForeignKey(

View File

@ -69,6 +69,7 @@ class MobileDevice(SerializerModel, Device):
stage = models.ForeignKey(AuthenticatorMobileStage, on_delete=models.CASCADE) stage = models.ForeignKey(AuthenticatorMobileStage, on_delete=models.CASCADE)
device_id = models.TextField(unique=True) device_id = models.TextField(unique=True)
firebase_token = models.TextField(blank=True)
@property @property
def serializer(self) -> Serializer: def serializer(self) -> Serializer:
@ -90,5 +91,3 @@ class MobileDeviceToken(ExpiringModel):
device = models.ForeignKey(MobileDevice, on_delete=models.CASCADE, null=True) device = models.ForeignKey(MobileDevice, on_delete=models.CASCADE, null=True)
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
token = models.TextField(default=default_token_key) 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, WithUserInfoChallenge,
) )
from authentik.flows.stage import ChallengeStageView from authentik.flows.stage import ChallengeStageView
from authentik.stages.authenticator_mobile.models import ( from authentik.stages.authenticator_mobile.models import MobileDevice, MobileDeviceToken
MobileDevice,
MobileDeviceToken,
)
FLOW_PLAN_MOBILE_ENROLL = "authentik/stages/authenticator_mobile/enroll" FLOW_PLAN_MOBILE_ENROLL = "authentik/stages/authenticator_mobile/enroll"
@ -51,6 +48,7 @@ class AuthenticatorMobileStageView(ChallengeStageView):
device = MobileDevice.objects.create( device = MobileDevice.objects.create(
user=self.get_pending_user(), user=self.get_pending_user(),
stage=self.executor.current_stage, stage=self.executor.current_stage,
confirmed=False,
) )
token = MobileDeviceToken.objects.create( token = MobileDeviceToken.objects.create(
user=self.get_pending_user(), user=self.get_pending_user(),

View File

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