stages/identification: load uid_fields from stage in form, add more unit tests

This commit is contained in:
Jens Langhammer 2020-05-10 00:05:36 +02:00
parent fd5b2298e5
commit 8e488670ad
6 changed files with 100 additions and 10 deletions

View File

@ -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"),
] ]

View File

@ -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)

View File

@ -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")

View File

@ -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"

View File

@ -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()

View File

@ -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)