stages/identification: load uid_fields from stage in form, add more unit tests
This commit is contained in:
parent
fd5b2298e5
commit
8e488670ad
|
@ -4,6 +4,6 @@ from django.urls import path
|
||||||
from passbook.flows.views import FlowExecutorView, FlowPermissionDeniedView
|
from passbook.flows.views import FlowExecutorView, FlowPermissionDeniedView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("<slug:flow_slug>/", FlowExecutorView.as_view(), name="flow-executor"),
|
|
||||||
path("denied/", FlowPermissionDeniedView.as_view(), name="denied"),
|
path("denied/", FlowPermissionDeniedView.as_view(), name="denied"),
|
||||||
|
path("<slug:flow_slug>/", FlowExecutorView.as_view(), name="flow-executor"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -151,7 +151,7 @@ class FlowExecutorView(View):
|
||||||
def stage_invalid(self) -> HttpResponse:
|
def stage_invalid(self) -> HttpResponse:
|
||||||
"""Callback used stage when data is correct but a policy denies access
|
"""Callback used stage when data is correct but a policy denies access
|
||||||
or the user account is disabled."""
|
or the user account is disabled."""
|
||||||
LOGGER.debug("User invalid", flow_slug=self.flow.slug)
|
LOGGER.debug("Stage invalid", flow_slug=self.flow.slug)
|
||||||
self.cancel()
|
self.cancel()
|
||||||
return redirect_with_qs("passbook_flows:denied", self.request.GET)
|
return redirect_with_qs("passbook_flows:denied", self.request.GET)
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,8 @@ from django.core.validators import validate_email
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.lib.config import CONFIG
|
|
||||||
from passbook.lib.utils.ui import human_list
|
from passbook.lib.utils.ui import human_list
|
||||||
from passbook.stages.identification.models import IdentificationStage
|
from passbook.stages.identification.models import IdentificationStage, UserFields
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
@ -26,20 +25,22 @@ class IdentificationStageForm(forms.ModelForm):
|
||||||
class IdentificationForm(forms.Form):
|
class IdentificationForm(forms.Form):
|
||||||
"""Allow users to login"""
|
"""Allow users to login"""
|
||||||
|
|
||||||
|
stage: IdentificationStage
|
||||||
|
|
||||||
title = _("Log in to your account")
|
title = _("Log in to your account")
|
||||||
uid_field = forms.CharField(label=_(""))
|
uid_field = forms.CharField(label=_(""))
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.stage = kwargs.pop("stage")
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
# TODO: Get UID Fields from stage config
|
if self.stage.user_fields == [UserFields.E_MAIL]:
|
||||||
if CONFIG.y("passbook.uid_fields") == ["e-mail"]:
|
|
||||||
self.fields["uid_field"] = forms.EmailField()
|
self.fields["uid_field"] = forms.EmailField()
|
||||||
self.fields["uid_field"].label = human_list(
|
self.fields["uid_field"].label = human_list(
|
||||||
[x.title() for x in CONFIG.y("passbook.uid_fields")]
|
[y.title() for x, y in UserFields.choices]
|
||||||
)
|
)
|
||||||
|
|
||||||
def clean_uid_field(self):
|
def clean_uid_field(self):
|
||||||
"""Validate uid_field after EmailValidator if 'email' is the only selected uid_fields"""
|
"""Validate uid_field after EmailValidator if 'email' is the only selected uid_fields"""
|
||||||
if CONFIG.y("passbook.uid_fields") == ["email"]:
|
if self.stage.user_fields == [UserFields.E_MAIL]:
|
||||||
validate_email(self.cleaned_data.get("uid_field"))
|
validate_email(self.cleaned_data.get("uid_field"))
|
||||||
return self.cleaned_data.get("uid_field")
|
return self.cleaned_data.get("uid_field")
|
||||||
|
|
|
@ -7,7 +7,7 @@ from passbook.flows.models import Stage
|
||||||
|
|
||||||
|
|
||||||
class UserFields(models.TextChoices):
|
class UserFields(models.TextChoices):
|
||||||
"""Fields which the user can identifiy themselves with"""
|
"""Fields which the user can identify themselves with"""
|
||||||
|
|
||||||
E_MAIL = "email"
|
E_MAIL = "email"
|
||||||
USERNAME = "username"
|
USERNAME = "username"
|
||||||
|
|
|
@ -23,6 +23,11 @@ class IdentificationStageView(FormView, AuthenticationStage):
|
||||||
|
|
||||||
form_class = IdentificationForm
|
form_class = IdentificationForm
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
kwargs = super().get_form_kwargs()
|
||||||
|
kwargs["stage"] = self.executor.current_stage
|
||||||
|
return kwargs
|
||||||
|
|
||||||
def get_template_names(self) -> List[str]:
|
def get_template_names(self) -> List[str]:
|
||||||
current_stage: IdentificationStage = self.executor.current_stage
|
current_stage: IdentificationStage = self.executor.current_stage
|
||||||
return [current_stage.template]
|
return [current_stage.template]
|
||||||
|
@ -61,6 +66,6 @@ class IdentificationStageView(FormView, AuthenticationStage):
|
||||||
if not pre_user:
|
if not pre_user:
|
||||||
LOGGER.debug("invalid_login")
|
LOGGER.debug("invalid_login")
|
||||||
messages.error(self.request, _("Failed to authenticate."))
|
messages.error(self.request, _("Failed to authenticate."))
|
||||||
return self.executor.stage_invalid()
|
return self.form_invalid(form)
|
||||||
self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = pre_user
|
self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = pre_user
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
"""identification tests"""
|
||||||
|
from django.shortcuts import reverse
|
||||||
|
from django.test import Client, TestCase
|
||||||
|
|
||||||
|
from passbook.core.models import User
|
||||||
|
from passbook.flows.models import Flow, FlowDesignation, FlowStageBinding
|
||||||
|
from passbook.sources.oauth.models import OAuthSource
|
||||||
|
from passbook.stages.identification.models import (
|
||||||
|
IdentificationStage,
|
||||||
|
Templates,
|
||||||
|
UserFields,
|
||||||
|
)
|
||||||
|
from passbook.stages.login.models import LoginStage
|
||||||
|
|
||||||
|
|
||||||
|
class TestIdentificationStage(TestCase):
|
||||||
|
"""Identification tests"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.user = User.objects.create(username="unittest", email="test@beryju.org")
|
||||||
|
self.client = Client()
|
||||||
|
|
||||||
|
self.flow = Flow.objects.create(
|
||||||
|
name="test-identification",
|
||||||
|
slug="test-identification",
|
||||||
|
designation=FlowDesignation.AUTHENTICATION,
|
||||||
|
)
|
||||||
|
FlowStageBinding.objects.create(
|
||||||
|
flow=self.flow,
|
||||||
|
stage=IdentificationStage.objects.create(
|
||||||
|
name="identification",
|
||||||
|
user_fields=[UserFields.E_MAIL],
|
||||||
|
template=Templates.DEFAULT_LOGIN,
|
||||||
|
),
|
||||||
|
order=0,
|
||||||
|
)
|
||||||
|
FlowStageBinding.objects.create(
|
||||||
|
flow=self.flow, stage=LoginStage.objects.create(name="login",), order=1
|
||||||
|
)
|
||||||
|
|
||||||
|
# OAuthSource for the login view
|
||||||
|
OAuthSource.objects.create(name="test", slug="test")
|
||||||
|
|
||||||
|
def test_valid_render(self):
|
||||||
|
"""Test that View renders correctly"""
|
||||||
|
response = self.client.get(
|
||||||
|
reverse(
|
||||||
|
"passbook_flows:flow-executor", kwargs={"flow_slug": self.flow.slug}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_valid_with_email(self):
|
||||||
|
"""Test with valid email, check that URL redirects back to itself"""
|
||||||
|
form_data = {"uid_field": self.user.email}
|
||||||
|
url = reverse(
|
||||||
|
"passbook_flows:flow-executor", kwargs={"flow_slug": self.flow.slug}
|
||||||
|
)
|
||||||
|
response = self.client.post(url, form_data,)
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response.url, url)
|
||||||
|
|
||||||
|
def test_invalid_with_username(self):
|
||||||
|
"""Test invalid with username (user exists but stage only allows e-mail)"""
|
||||||
|
form_data = {"uid_field": self.user.username}
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"passbook_flows:flow-executor", kwargs={"flow_slug": self.flow.slug}
|
||||||
|
),
|
||||||
|
form_data,
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_invalid_with_invalid_email(self):
|
||||||
|
"""Test with invalid e-mail (user doesn't exist) -> Will return to login form"""
|
||||||
|
form_data = {"uid_field": self.user.email + "test"}
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"passbook_flows:flow-executor", kwargs={"flow_slug": self.flow.slug}
|
||||||
|
),
|
||||||
|
form_data,
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
Reference in New Issue