diff --git a/authentik/stages/authenticator_validate/challenge.py b/authentik/stages/authenticator_validate/challenge.py index 3a0b4e4ef..78b858557 100644 --- a/authentik/stages/authenticator_validate/challenge.py +++ b/authentik/stages/authenticator_validate/challenge.py @@ -105,18 +105,58 @@ def get_webauthn_challenge( ) -def select_challenge(request: HttpRequest, device: Device): +def select_challenge(request: HttpRequest,stage_view: StageView, device: Device): """Callback when the user selected a challenge in the frontend.""" if isinstance(device, SMSDevice): - select_challenge_sms(request, device) + select_challenge_sms(request, stage_view, device) + if isinstance(device, MobileDevice): + select_challenge_mobile(request, stage_view, device) -def select_challenge_sms(request: HttpRequest, device: SMSDevice): +def select_challenge_sms(request: HttpRequest, stage_view: StageView, device: SMSDevice): """Send SMS""" device.generate_token() device.stage.send(device.token, device) +def select_challenge_mobile(request: HttpRequest, stage_view: StageView, device: MobileDevice): + """Send mobile notification""" + # Get additional context for push + push_context = { + __("Domain"): stage_view.request.get_host(), + } + if SESSION_KEY_APPLICATION_PRE in stage_view.request.session: + push_context[__("Application")] = stage_view.request.session.get( + SESSION_KEY_APPLICATION_PRE, Application() + ).name + + try: + transaction = device.create_transaction() + transaction.send_message(stage_view.request, **push_context) + status = transaction.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=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, + message=f"Failed to Mobile authenticate user: {str(exc)}", + user=user, + ).from_http(stage_view.request, user) + raise ValidationError("Mobile denied access", code="denied") + + def validate_challenge_code(code: str, stage_view: StageView, user: User) -> Device: """Validate code-based challenges. We test against every device, on purpose, as the user mustn't choose between totp and static devices.""" diff --git a/authentik/stages/authenticator_validate/stage.py b/authentik/stages/authenticator_validate/stage.py index 5fdc99833..ce88d094e 100644 --- a/authentik/stages/authenticator_validate/stage.py +++ b/authentik/stages/authenticator_validate/stage.py @@ -127,7 +127,7 @@ class AuthenticatorValidationChallengeResponse(ChallengeResponse): devices = SMSDevice.objects.filter(pk=int(challenge.get("device_uid", "0"))) if not devices.exists(): raise ValidationError("invalid challenge selected") - select_challenge(self.stage.request, devices.first()) + select_challenge(self.stage.request, self.stage, devices.first()) return challenge def validate_selected_stage(self, stage_pk: str) -> str: