implement more

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens Langhammer 2023-09-04 20:46:33 +02:00
parent ad5ccf8062
commit c55f88d4df
No known key found for this signature in database
4 changed files with 33 additions and 13 deletions

View File

@ -4,7 +4,7 @@ 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
from rest_framework.fields import CharField, ChoiceField, UUIDField
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
@ -70,7 +70,7 @@ class MobileDeviceSetPushKeySerializer(PassiveSerializer):
class MobileDeviceResponseSerializer(PassiveSerializer):
"""Response from push sent to phone"""
tx_id = CharField(required=True)
tx_id = UUIDField(required=True)
status = ChoiceField(
TransactionStates.choices,
required=True,
@ -205,11 +205,12 @@ class MobileDeviceViewSet(
def receive_response(self, request: Request, pk: str) -> Response:
"""Get response from notification on phone"""
data = MobileDeviceResponseSerializer(data=request.data)
data.is_valid()
data.is_valid(raise_exception=True)
transaction = MobileTransaction.objects.filter(tx_id=data.validated_data["tx_id"]).first()
if not transaction:
raise Http404
transaction.status = data.validated_data["status"]
transaction.save()
return Response(status=204)

View File

@ -1,4 +1,5 @@
"""Mobile authenticator stage"""
from time import sleep
from typing import Optional
from uuid import uuid4
@ -128,9 +129,6 @@ 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=__(
@ -155,6 +153,7 @@ class MobileTransaction(ExpiringModel):
category="cat_authentik_push_authorization",
),
interruption_level="time-sensitive",
tx_id=str(self.tx_id),
),
),
token=self.device.firebase_token,
@ -166,6 +165,20 @@ class MobileTransaction(ExpiringModel):
LOGGER.warning("failed to push", exc=exc)
return True
def wait_for_response(self, max_checks=30) -> TransactionStates:
"""Wait for a change in status"""
checks = 0
while True:
self.refresh_from_db()
if self.status in [TransactionStates.accept, TransactionStates.deny]:
self.delete()
return self.status
checks += 1
if checks > max_checks:
self.delete()
raise TimeoutError()
sleep(1)
class MobileDeviceToken(ExpiringModel):
"""Mobile device token"""

View File

@ -26,7 +26,11 @@ from authentik.root.middleware import ClientIPMiddleware
from authentik.stages.authenticator import match_token
from authentik.stages.authenticator.models import Device
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
from authentik.stages.authenticator_mobile.models import MobileDevice, MobileTransaction
from authentik.stages.authenticator_mobile.models import (
MobileDevice,
MobileTransaction,
TransactionStates,
)
from authentik.stages.authenticator_sms.models import SMSDevice
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage, DeviceClasses
from authentik.stages.authenticator_webauthn.models import UserVerification, WebAuthnDevice
@ -194,20 +198,22 @@ def validate_challenge_mobile(device_pk: str, stage_view: StageView, user: User)
try:
tx = MobileTransaction.objects.create(device=device)
response = tx.send_message(stage_view.request, **push_context)
# {'result': 'allow', 'status': 'allow', 'status_msg': 'Success. Logging you in...'}
if not response:
LOGGER.debug("mobile push response", result=response)
tx.send_message(stage_view.request, **push_context)
status = tx.wait_for_response()
if status == TransactionStates.deny:
LOGGER.debug("mobile push response", result=status)
login_failed.send(
sender=__name__,
credentials={"username": user.username},
request=stage_view.request,
stage=stage_view.executor.current_stage,
device_class=DeviceClasses.MOBILE.value,
mobile_response=response,
mobile_response=status,
)
raise ValidationError("Mobile denied access", code="denied")
return device
except TimeoutError:
raise ValidationError("Mobile push notification timed out.")
except RuntimeError as exc:
Event.new(
EventAction.CONFIGURATION_ERROR,

View File

@ -35340,7 +35340,7 @@ components:
properties:
tx_id:
type: string
minLength: 1
format: uuid
status:
$ref: '#/components/schemas/MobileDeviceResponseStatusEnum'
required: