stages/authenticator_validate: fix logic error when multiple authenticator devices can be selected
closes #2290 Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
0dfecc6ae2
commit
cc1509cf57
|
@ -87,16 +87,20 @@ class AuthenticatorValidationChallengeResponse(ChallengeResponse):
|
||||||
def validate_selected_challenge(self, challenge: dict) -> dict:
|
def validate_selected_challenge(self, challenge: dict) -> dict:
|
||||||
"""Check which challenge the user has selected. Actual logic only used for SMS stage."""
|
"""Check which challenge the user has selected. Actual logic only used for SMS stage."""
|
||||||
# First check if the challenge is valid
|
# First check if the challenge is valid
|
||||||
|
allowed = False
|
||||||
for device_challenge in self.stage.request.session.get(SESSION_DEVICE_CHALLENGES):
|
for device_challenge in self.stage.request.session.get(SESSION_DEVICE_CHALLENGES):
|
||||||
if device_challenge.get("device_class", "") != challenge.get("device_class", ""):
|
if device_challenge.get("device_class", "") == challenge.get(
|
||||||
raise ValidationError("invalid challenge selected")
|
"device_class", ""
|
||||||
if device_challenge.get("device_uid", "") != challenge.get("device_uid", ""):
|
) and device_challenge.get("device_uid", "") == challenge.get("device_uid", ""):
|
||||||
|
allowed = True
|
||||||
|
if not allowed:
|
||||||
raise ValidationError("invalid challenge selected")
|
raise ValidationError("invalid challenge selected")
|
||||||
|
|
||||||
if challenge.get("device_class", "") != "sms":
|
if challenge.get("device_class", "") != "sms":
|
||||||
return challenge
|
return challenge
|
||||||
devices = SMSDevice.objects.filter(pk=int(challenge.get("device_uid", "0")))
|
devices = SMSDevice.objects.filter(pk=int(challenge.get("device_uid", "0")))
|
||||||
if not devices.exists():
|
if not devices.exists():
|
||||||
raise ValidationError("device does not exist")
|
raise ValidationError("invalid challenge selected")
|
||||||
select_challenge(self.stage.request, devices.first())
|
select_challenge(self.stage.request, devices.first())
|
||||||
return challenge
|
return challenge
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
"""Test validator stage"""
|
"""Test validator stage"""
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
from django.contrib.sessions.middleware import SessionMiddleware
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
from django.urls.base import reverse
|
from django.urls.base import reverse
|
||||||
from django_otp.plugins.otp_totp.models import TOTPDevice
|
from django_otp.plugins.otp_totp.models import TOTPDevice
|
||||||
|
@ -9,10 +10,18 @@ from webauthn.helpers import bytes_to_base64url
|
||||||
|
|
||||||
from authentik.core.tests.utils import create_test_admin_user
|
from authentik.core.tests.utils import create_test_admin_user
|
||||||
from authentik.flows.models import Flow, FlowStageBinding, NotConfiguredAction
|
from authentik.flows.models import Flow, FlowStageBinding, NotConfiguredAction
|
||||||
|
from authentik.flows.stage import StageView
|
||||||
from authentik.flows.tests import FlowTestCase
|
from authentik.flows.tests import FlowTestCase
|
||||||
|
from authentik.flows.views.executor import FlowExecutorView
|
||||||
from authentik.lib.generators import generate_id, generate_key
|
from authentik.lib.generators import generate_id, generate_key
|
||||||
from authentik.lib.tests.utils import get_request
|
from authentik.lib.tests.utils import dummy_get_response, get_request
|
||||||
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
|
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
|
||||||
|
from authentik.stages.authenticator_sms.models import (
|
||||||
|
AuthenticatorSMSStage,
|
||||||
|
SMSAuthTypes,
|
||||||
|
SMSDevice,
|
||||||
|
SMSProviders,
|
||||||
|
)
|
||||||
from authentik.stages.authenticator_validate.api import AuthenticatorValidateStageSerializer
|
from authentik.stages.authenticator_validate.api import AuthenticatorValidateStageSerializer
|
||||||
from authentik.stages.authenticator_validate.challenge import (
|
from authentik.stages.authenticator_validate.challenge import (
|
||||||
get_challenge_for_device,
|
get_challenge_for_device,
|
||||||
|
@ -21,6 +30,10 @@ from authentik.stages.authenticator_validate.challenge import (
|
||||||
validate_challenge_webauthn,
|
validate_challenge_webauthn,
|
||||||
)
|
)
|
||||||
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage
|
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage
|
||||||
|
from authentik.stages.authenticator_validate.stage import (
|
||||||
|
SESSION_DEVICE_CHALLENGES,
|
||||||
|
AuthenticatorValidationChallengeResponse,
|
||||||
|
)
|
||||||
from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
|
from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
|
||||||
from authentik.stages.identification.models import IdentificationStage, UserFields
|
from authentik.stages.identification.models import IdentificationStage, UserFields
|
||||||
|
|
||||||
|
@ -159,3 +172,39 @@ class AuthenticatorValidateStageTests(FlowTestCase):
|
||||||
):
|
):
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
validate_challenge_duo(duo_device.pk, request, self.user)
|
validate_challenge_duo(duo_device.pk, request, self.user)
|
||||||
|
|
||||||
|
def test_validate_selected_challenge(self):
|
||||||
|
"""Test validate_selected_challenge"""
|
||||||
|
# Prepare request with session
|
||||||
|
request = self.request_factory.get("/")
|
||||||
|
|
||||||
|
middleware = SessionMiddleware(dummy_get_response)
|
||||||
|
middleware.process_request(request)
|
||||||
|
request.session[SESSION_DEVICE_CHALLENGES] = [
|
||||||
|
{
|
||||||
|
"device_class": "static",
|
||||||
|
"device_uid": "1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"device_class": "totp",
|
||||||
|
"device_uid": "2",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
request.session.save()
|
||||||
|
|
||||||
|
res = AuthenticatorValidationChallengeResponse()
|
||||||
|
res.stage = StageView(FlowExecutorView())
|
||||||
|
res.stage.request = request
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
res.validate_selected_challenge(
|
||||||
|
{
|
||||||
|
"device_class": "baz",
|
||||||
|
"device_uid": "quox",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
res.validate_selected_challenge(
|
||||||
|
{
|
||||||
|
"device_class": "static",
|
||||||
|
"device_uid": "1",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
Reference in New Issue