diff --git a/authentik/stages/authenticator_mobile/api/device.py b/authentik/stages/authenticator_mobile/api/device.py index bdea62a62..84aa71e88 100644 --- a/authentik/stages/authenticator_mobile/api/device.py +++ b/authentik/stages/authenticator_mobile/api/device.py @@ -1,5 +1,5 @@ """AuthenticatorMobileStage API Views""" -from django.utils.translation import gettext_lazy as _ +from django.http import Http404 from django_filters.rest_framework.backends import DjangoFilterBackend from drf_spectacular.utils import OpenApiResponse, extend_schema, inline_serializer from rest_framework import mixins @@ -16,7 +16,12 @@ 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, MobileDeviceToken +from authentik.stages.authenticator_mobile.models import ( + MobileDevice, + MobileDeviceToken, + MobileTransaction, + TransactionStates, +) class MobileDeviceSerializer(ModelSerializer): @@ -66,16 +71,7 @@ class MobileDeviceResponseSerializer(PassiveSerializer): tx_id = CharField(required=True) status = ChoiceField( - ( - ( - "accept", - _("Accept"), - ), - ( - "deny", - _("Deny"), - ), - ), + TransactionStates.choices, required=True, ) @@ -193,6 +189,7 @@ class MobileDeviceViewSet( @extend_schema( responses={ 204: OpenApiResponse(description="Key successfully set"), + 404: OpenApiResponse(description="Transaction not found"), }, request=MobileDeviceResponseSerializer, ) @@ -205,7 +202,12 @@ class MobileDeviceViewSet( ) def receive_response(self, request: Request, pk: str) -> Response: """Get response from notification on phone""" - print(request.data) + data = MobileDeviceResponseSerializer(data=request.data) + data.is_valid() + transaction = MobileTransaction.objects.filter(tx_id=data.validated_data["tx_id"]).first() + if not transaction: + raise Http404 + transaction.status = data.validated_data["status"] return Response(status=204) diff --git a/authentik/stages/authenticator_mobile/migrations/0002_mobiletransaction.py b/authentik/stages/authenticator_mobile/migrations/0002_mobiletransaction.py index d8ac76926..e8358f13c 100644 --- a/authentik/stages/authenticator_mobile/migrations/0002_mobiletransaction.py +++ b/authentik/stages/authenticator_mobile/migrations/0002_mobiletransaction.py @@ -1,10 +1,12 @@ # Generated by Django 4.2.4 on 2023-09-04 18:18 -import authentik.core.models -from django.db import migrations, models -import django.db.models.deletion import uuid +import django.db.models.deletion +from django.db import migrations, models + +import authentik.core.models + class Migration(migrations.Migration): dependencies = [ diff --git a/authentik/stages/authenticator_mobile/migrations/0003_mobiletransaction_status.py b/authentik/stages/authenticator_mobile/migrations/0003_mobiletransaction_status.py new file mode 100644 index 000000000..99cf6b9c5 --- /dev/null +++ b/authentik/stages/authenticator_mobile/migrations/0003_mobiletransaction_status.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.4 on 2023-09-04 18:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("authentik_stages_authenticator_mobile", "0002_mobiletransaction"), + ] + + operations = [ + migrations.AddField( + model_name="mobiletransaction", + name="status", + field=models.TextField( + choices=[("wait", "Wait"), ("accept", "Accept"), ("deny", "Deny")], default="wait" + ), + ), + ] diff --git a/authentik/stages/authenticator_mobile/models.py b/authentik/stages/authenticator_mobile/models.py index 53b4143fb..aed0ea710 100644 --- a/authentik/stages/authenticator_mobile/models.py +++ b/authentik/stages/authenticator_mobile/models.py @@ -106,12 +106,20 @@ class MobileDevice(SerializerModel, Device): verbose_name_plural = _("Mobile Devices") +class TransactionStates(models.TextChoices): + wait = "wait" + accept = "accept" + deny = "deny" + + class MobileTransaction(ExpiringModel): """A single push transaction""" tx_id = models.UUIDField(default=uuid4, primary_key=True) device = models.ForeignKey(MobileDevice, on_delete=models.CASCADE) + status = models.TextField(choices=TransactionStates.choices, default=TransactionStates.wait) + def send_message(self, request: Optional[HttpRequest], **context): """Send mobile message""" branding = DEFAULT_TENANT.branding_title @@ -120,6 +128,9 @@ class MobileTransaction(ExpiringModel): branding = request.tenant.branding_title domain = request.get_host() message = Message( + data={ + "tx_id": str(self.tx_id), + }, notification=Notification( title=__("%(brand)s authentication request" % {"brand": branding}), body=__( @@ -144,7 +155,6 @@ class MobileTransaction(ExpiringModel): category="cat_authentik_push_authorization", ), interruption_level="time-sensitive", - tx_id=str(self.tx_id), ), ), token=self.device.firebase_token, diff --git a/schema.yml b/schema.yml index 345562071..819cc079d 100644 --- a/schema.yml +++ b/schema.yml @@ -2266,6 +2266,8 @@ paths: responses: '204': description: Key successfully set + '404': + description: Transaction not found '400': content: application/json: @@ -34354,10 +34356,12 @@ components: - tx_id MobileDeviceResponseStatusEnum: enum: + - wait - accept - deny type: string description: |- + * `wait` - Wait * `accept` - Accept * `deny` - Deny MobileDeviceSetPushKeyRequest: