diff --git a/authentik/stages/authenticator_mobile/api/device.py b/authentik/stages/authenticator_mobile/api/device.py index 74cb528c9..c54d935d4 100644 --- a/authentik/stages/authenticator_mobile/api/device.py +++ b/authentik/stages/authenticator_mobile/api/device.py @@ -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) diff --git a/authentik/stages/authenticator_mobile/migrations/0001_initial.py b/authentik/stages/authenticator_mobile/migrations/0001_initial.py index f550f738d..ea1dbc544 100644 --- a/authentik/stages/authenticator_mobile/migrations/0001_initial.py +++ b/authentik/stages/authenticator_mobile/migrations/0001_initial.py @@ -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( diff --git a/authentik/stages/authenticator_mobile/models.py b/authentik/stages/authenticator_mobile/models.py index 67542bae1..af1cef60e 100644 --- a/authentik/stages/authenticator_mobile/models.py +++ b/authentik/stages/authenticator_mobile/models.py @@ -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) diff --git a/authentik/stages/authenticator_mobile/stage.py b/authentik/stages/authenticator_mobile/stage.py index 4d19d1fc1..3e9da05db 100644 --- a/authentik/stages/authenticator_mobile/stage.py +++ b/authentik/stages/authenticator_mobile/stage.py @@ -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(), diff --git a/schema.yml b/schema.yml index 8cb03f5bd..4e576240a 100644 --- a/schema.yml +++ b/schema.yml @@ -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