diff --git a/passbook/flows/urls.py b/passbook/flows/urls.py index 9198aab18..038e21d8b 100644 --- a/passbook/flows/urls.py +++ b/passbook/flows/urls.py @@ -4,6 +4,6 @@ from django.urls import path from passbook.flows.views import FlowExecutorView, FlowPermissionDeniedView urlpatterns = [ - path("/", FlowExecutorView.as_view(), name="flow-executor"), path("denied/", FlowPermissionDeniedView.as_view(), name="denied"), + path("/", FlowExecutorView.as_view(), name="flow-executor"), ] diff --git a/passbook/flows/views.py b/passbook/flows/views.py index eaa3b7e97..5774460af 100644 --- a/passbook/flows/views.py +++ b/passbook/flows/views.py @@ -151,7 +151,7 @@ class FlowExecutorView(View): def stage_invalid(self) -> HttpResponse: """Callback used stage when data is correct but a policy denies access 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() return redirect_with_qs("passbook_flows:denied", self.request.GET) diff --git a/passbook/stages/identification/forms.py b/passbook/stages/identification/forms.py index 5f6f04c75..cd03e46d4 100644 --- a/passbook/stages/identification/forms.py +++ b/passbook/stages/identification/forms.py @@ -4,9 +4,8 @@ from django.core.validators import validate_email from django.utils.translation import gettext_lazy as _ from structlog import get_logger -from passbook.lib.config import CONFIG 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() @@ -26,20 +25,22 @@ class IdentificationStageForm(forms.ModelForm): class IdentificationForm(forms.Form): """Allow users to login""" + stage: IdentificationStage + title = _("Log in to your account") uid_field = forms.CharField(label=_("")) def __init__(self, *args, **kwargs): + self.stage = kwargs.pop("stage") super().__init__(*args, **kwargs) - # TODO: Get UID Fields from stage config - if CONFIG.y("passbook.uid_fields") == ["e-mail"]: + if self.stage.user_fields == [UserFields.E_MAIL]: self.fields["uid_field"] = forms.EmailField() 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): """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")) return self.cleaned_data.get("uid_field") diff --git a/passbook/stages/identification/models.py b/passbook/stages/identification/models.py index 1e56f572e..40616d0ac 100644 --- a/passbook/stages/identification/models.py +++ b/passbook/stages/identification/models.py @@ -7,7 +7,7 @@ from passbook.flows.models import Stage class UserFields(models.TextChoices): - """Fields which the user can identifiy themselves with""" + """Fields which the user can identify themselves with""" E_MAIL = "email" USERNAME = "username" diff --git a/passbook/stages/identification/stage.py b/passbook/stages/identification/stage.py index dfe4965b7..9e9fb4b0b 100644 --- a/passbook/stages/identification/stage.py +++ b/passbook/stages/identification/stage.py @@ -23,6 +23,11 @@ class IdentificationStageView(FormView, AuthenticationStage): 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]: current_stage: IdentificationStage = self.executor.current_stage return [current_stage.template] @@ -61,6 +66,6 @@ class IdentificationStageView(FormView, AuthenticationStage): if not pre_user: LOGGER.debug("invalid_login") 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 return self.executor.stage_ok() diff --git a/passbook/stages/identification/tests.py b/passbook/stages/identification/tests.py new file mode 100644 index 000000000..2c2bf98ab --- /dev/null +++ b/passbook/stages/identification/tests.py @@ -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)