stages/authenticator_totp: remove single device per user limit

closes #3281

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2022-07-28 21:21:48 +02:00
parent ade2d4879c
commit 2a42c203b2
2 changed files with 34 additions and 22 deletions

View File

@ -74,14 +74,6 @@ class AuthenticatorTOTPStageView(ChallengeStageView):
stage: AuthenticatorTOTPStage = self.executor.current_stage stage: AuthenticatorTOTPStage = self.executor.current_stage
devices = TOTPDevice.objects.filter(user=user)
# Currently, this stage only supports one device per user. If the user already
# has a device, just skip to the next stage
if devices.exists():
if not any(x.confirmed for x in devices):
return super().get(request, *args, **kwargs)
return self.executor.stage_ok()
TOTPDevice.objects.create( TOTPDevice.objects.create(
user=user, confirmed=False, digits=stage.digits, name="TOTP Authenticator" user=user, confirmed=False, digits=stage.digits, name="TOTP Authenticator"
) )

View File

@ -1,16 +1,20 @@
"""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 rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
from authentik.core.tests.utils import create_test_admin_user from authentik.core.tests.utils import create_test_admin_user
from authentik.flows.planner import FlowPlan
from authentik.flows.stage import StageView 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.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 dummy_get_response
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
from authentik.stages.authenticator_validate.challenge import validate_challenge_duo from authentik.stages.authenticator_validate.challenge import validate_challenge_duo
from authentik.tenants.utils import get_tenant_for_request
class AuthenticatorValidateStageDuoTests(FlowTestCase): class AuthenticatorValidateStageDuoTests(FlowTestCase):
@ -23,6 +27,12 @@ class AuthenticatorValidateStageDuoTests(FlowTestCase):
def test_device_challenge_duo(self): def test_device_challenge_duo(self):
"""Test duo""" """Test duo"""
request = self.request_factory.get("/") request = self.request_factory.get("/")
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(request)
request.session.save()
setattr(request, "tenant", get_tenant_for_request(request))
stage = AuthenticatorDuoStage.objects.create( stage = AuthenticatorDuoStage.objects.create(
name=generate_id(), name=generate_id(),
client_id=generate_id(), client_id=generate_id(),
@ -33,35 +43,45 @@ class AuthenticatorValidateStageDuoTests(FlowTestCase):
user=self.user, user=self.user,
stage=stage, stage=stage,
) )
duo_mock = MagicMock(
auth=MagicMock(
return_value={
"result": "allow",
"status": "allow",
"status_msg": "Success. Logging you in...",
}
)
)
failed_duo_mock = MagicMock(auth=MagicMock(return_value={"result": "deny"}))
with patch( with patch(
"authentik.stages.authenticator_duo.models.AuthenticatorDuoStage.client", "authentik.stages.authenticator_duo.models.AuthenticatorDuoStage.client",
duo_mock, MagicMock(
auth=MagicMock(
return_value={
"result": "allow",
"status": "allow",
"status_msg": "Success. Logging you in...",
}
)
),
): ):
self.assertEqual( self.assertEqual(
duo_device, duo_device,
validate_challenge_duo( validate_challenge_duo(
duo_device.pk, duo_device.pk,
StageView(FlowExecutorView(current_stage=stage), request=request), StageView(
FlowExecutorView(
current_stage=stage,
plan=FlowPlan(generate_id(), [], {}),
),
request=request,
),
self.user, self.user,
), ),
) )
with patch( with patch(
"authentik.stages.authenticator_duo.models.AuthenticatorDuoStage.client", "authentik.stages.authenticator_duo.models.AuthenticatorDuoStage.client",
failed_duo_mock, MagicMock(auth=MagicMock(return_value={"result": "deny"})),
): ):
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
validate_challenge_duo( validate_challenge_duo(
duo_device.pk, duo_device.pk,
StageView(FlowExecutorView(current_stage=stage), request=request), StageView(
FlowExecutorView(
current_stage=stage,
plan=FlowPlan(generate_id(), [], {}),
),
request=request,
),
self.user, self.user,
) )