diff --git a/authentik/stages/authenticator_mobile/api/device.py b/authentik/stages/authenticator_mobile/api/device.py index f9302c2a4..df4655d95 100644 --- a/authentik/stages/authenticator_mobile/api/device.py +++ b/authentik/stages/authenticator_mobile/api/device.py @@ -1,10 +1,11 @@ """AuthenticatorMobileStage API Views""" from django.http import Http404 +from django.utils.timezone import now from django_filters.rest_framework.backends import DjangoFilterBackend from drf_spectacular.utils import OpenApiResponse, extend_schema, inline_serializer from rest_framework import mixins from rest_framework.decorators import action -from rest_framework.fields import CharField, ChoiceField, UUIDField +from rest_framework.fields import CharField, ChoiceField, JSONField, UUIDField from rest_framework.filters import OrderingFilter, SearchFilter from rest_framework.permissions import IsAdminUser from rest_framework.request import Request @@ -24,15 +25,6 @@ from authentik.stages.authenticator_mobile.models import ( ) -class MobileDeviceSerializer(ModelSerializer): - """Serializer for Mobile authenticator devices""" - - class Meta: - model = MobileDevice - fields = ["pk", "name"] - depth = 2 - - class MobileDeviceInfoSerializer(PassiveSerializer): """Info about a mobile device""" @@ -47,6 +39,19 @@ class MobileDeviceInfoSerializer(PassiveSerializer): hostname = CharField() app_version = CharField() + others = JSONField() + + +class MobileDeviceSerializer(ModelSerializer): + """Serializer for Mobile authenticator devices""" + + last_checkin = MobileDeviceInfoSerializer(read_only=True) + + class Meta: + model = MobileDevice + fields = ["pk", "name", "state", "last_checkin"] + depth = 2 + class MobileDeviceCheckInSerializer(PassiveSerializer): """Check info into authentik""" @@ -213,6 +218,29 @@ class MobileDeviceViewSet( transaction.save() return Response(status=204) + @extend_schema( + responses={ + 204: OpenApiResponse(description="Checked in"), + }, + request=MobileDeviceInfoSerializer, + ) + @action( + methods=["POST"], + detail=True, + permission_classes=[], + filter_backends=[], + authentication_classes=[MobileDeviceTokenAuthentication], + ) + def check_in(self, request: Request, pk: str) -> Response: + """Check in data about a device""" + data = MobileDeviceInfoSerializer(data=request.data) + data.is_valid(raise_exception=True) + device: MobileDevice = self.get_object() + device.last_checkin = now() + device.state = data.validated_data + device.save() + return Response(status=204) + class AdminMobileDeviceViewSet(ModelViewSet): """Viewset for Mobile authenticator devices (for admins)""" diff --git a/authentik/stages/authenticator_mobile/migrations/0004_mobiledevice_last_checkin_mobiledevice_state.py b/authentik/stages/authenticator_mobile/migrations/0004_mobiledevice_last_checkin_mobiledevice_state.py new file mode 100644 index 000000000..8342266f1 --- /dev/null +++ b/authentik/stages/authenticator_mobile/migrations/0004_mobiledevice_last_checkin_mobiledevice_state.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.4 on 2023-09-05 13:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("authentik_stages_authenticator_mobile", "0003_mobiletransaction_status"), + ] + + operations = [ + migrations.AddField( + model_name="mobiledevice", + name="last_checkin", + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name="mobiledevice", + name="state", + field=models.JSONField(default=dict), + ), + ] diff --git a/authentik/stages/authenticator_mobile/models.py b/authentik/stages/authenticator_mobile/models.py index c552e2677..523222d11 100644 --- a/authentik/stages/authenticator_mobile/models.py +++ b/authentik/stages/authenticator_mobile/models.py @@ -93,6 +93,9 @@ class MobileDevice(SerializerModel, Device): device_id = models.TextField(unique=True) firebase_token = models.TextField(blank=True) + state = models.JSONField(default=dict) + last_checkin = models.DateTimeField(auto_now=True) + @property def serializer(self) -> Serializer: from authentik.stages.authenticator_mobile.api.device import MobileDeviceSerializer @@ -108,6 +111,8 @@ class MobileDevice(SerializerModel, Device): class TransactionStates(models.TextChoices): + """States a transaction can be in""" + wait = "wait" accept = "accept" deny = "deny" diff --git a/authentik/stages/authenticator_mobile/urls.py b/authentik/stages/authenticator_mobile/urls.py index bd8bbacc3..efdc405de 100644 --- a/authentik/stages/authenticator_mobile/urls.py +++ b/authentik/stages/authenticator_mobile/urls.py @@ -1,10 +1,11 @@ """API URLs""" +from rest_framework import routers + from authentik.stages.authenticator_mobile.api.device import ( AdminMobileDeviceViewSet, MobileDeviceViewSet, ) from authentik.stages.authenticator_mobile.api.stage import AuthenticatorMobileStageViewSet -from rest_framework import routers # Separate router which is used for the subset-schema generation # for the cloud-gateway we (currently) only want the mobile device endpoints diff --git a/blueprints/schema.json b/blueprints/schema.json index 9823d406b..8187135b9 100644 --- a/blueprints/schema.json +++ b/blueprints/schema.json @@ -6040,6 +6040,11 @@ "minLength": 1, "title": "Name", "description": "The human-readable name of this device." + }, + "state": { + "type": "object", + "additionalProperties": true, + "title": "State" } }, "required": [] diff --git a/schema.yml b/schema.yml index 7fd141ce9..7a07ac2ec 100644 --- a/schema.yml +++ b/schema.yml @@ -2165,6 +2165,43 @@ paths: schema: $ref: '#/components/schemas/GenericError' description: '' + /authenticators/mobile/{uuid}/check_in/: + post: + operationId: authenticators_mobile_check_in_create + description: Check in data about a device + parameters: + - in: path + name: uuid + schema: + type: string + format: uuid + description: A UUID string identifying this Mobile Device. + required: true + tags: + - authenticators + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/MobileDeviceInfoRequest' + required: true + security: + - mobile_device_token: [] + responses: + '204': + description: Checked in + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' /authenticators/mobile/{uuid}/enrollment_callback/: post: operationId: authenticators_mobile_enrollment_callback_create @@ -34271,7 +34308,15 @@ components: type: string description: The human-readable name of this device. maxLength: 64 + state: + type: object + additionalProperties: {} + last_checkin: + allOf: + - $ref: '#/components/schemas/MobileDeviceInfo' + readOnly: true required: + - last_checkin - name MobileDeviceEnrollmentCallback: type: object @@ -34307,6 +34352,30 @@ components: description: |- * `success` - Success * `waiting` - Waiting + MobileDeviceInfo: + type: object + description: Info about a mobile device + properties: + platform: + $ref: '#/components/schemas/PlatformEnum' + os_version: + type: string + model: + type: string + hostname: + type: string + app_version: + type: string + others: + type: object + additionalProperties: {} + required: + - app_version + - hostname + - model + - os_version + - others + - platform MobileDeviceInfoRequest: type: object description: Info about a mobile device @@ -34325,11 +34394,15 @@ components: app_version: type: string minLength: 1 + others: + type: object + additionalProperties: {} required: - app_version - hostname - model - os_version + - others - platform MobileDeviceRequest: type: object @@ -34344,6 +34417,9 @@ components: minLength: 1 description: The human-readable name of this device. maxLength: 64 + state: + type: object + additionalProperties: {} required: - name MobileDeviceResponseRequest: @@ -38130,6 +38206,9 @@ components: minLength: 1 description: The human-readable name of this device. maxLength: 64 + state: + type: object + additionalProperties: {} PatchedNotificationRequest: type: object description: Notification Serializer diff --git a/schemas/authentik-cloud-gateway.yml b/schemas/authentik-cloud-gateway.yml index 0af4cd6bd..523f6bae4 100644 --- a/schemas/authentik-cloud-gateway.yml +++ b/schemas/authentik-cloud-gateway.yml @@ -209,6 +209,43 @@ paths: schema: $ref: '#/components/schemas/GenericError' description: '' + /authenticators/mobile/{uuid}/check_in/: + post: + operationId: authenticators_mobile_check_in_create + description: Check in data about a device + parameters: + - in: path + name: uuid + schema: + type: string + format: uuid + description: A UUID string identifying this Mobile Device. + required: true + tags: + - authenticators + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/MobileDeviceInfoRequest' + required: true + security: + - mobile_device_token: [] + responses: + '204': + description: Checked in + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/GenericError' + description: '' /authenticators/mobile/{uuid}/enrollment_callback/: post: operationId: authenticators_mobile_enrollment_callback_create @@ -435,7 +472,15 @@ components: type: string description: The human-readable name of this device. maxLength: 64 + state: + type: object + additionalProperties: {} + last_checkin: + allOf: + - $ref: '#/components/schemas/MobileDeviceInfo' + readOnly: true required: + - last_checkin - name MobileDeviceEnrollmentCallback: type: object @@ -471,6 +516,30 @@ components: description: |- * `success` - Success * `waiting` - Waiting + MobileDeviceInfo: + type: object + description: Info about a mobile device + properties: + platform: + $ref: '#/components/schemas/PlatformEnum' + os_version: + type: string + model: + type: string + hostname: + type: string + app_version: + type: string + others: + type: object + additionalProperties: {} + required: + - app_version + - hostname + - model + - os_version + - others + - platform MobileDeviceInfoRequest: type: object description: Info about a mobile device @@ -489,11 +558,15 @@ components: app_version: type: string minLength: 1 + others: + type: object + additionalProperties: {} required: - app_version - hostname - model - os_version + - others - platform MobileDeviceRequest: type: object @@ -508,6 +581,9 @@ components: minLength: 1 description: The human-readable name of this device. maxLength: 64 + state: + type: object + additionalProperties: {} required: - name MobileDeviceResponseRequest: @@ -591,6 +667,9 @@ components: minLength: 1 description: The human-readable name of this device. maxLength: 64 + state: + type: object + additionalProperties: {} PlatformEnum: enum: - ios