stages/identification: migrate from core to separate stage
This commit is contained in:
@ -1,38 +1,14 @@
|
||||
"""passbook core authentication forms"""
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import validate_email
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.models import User
|
||||
from passbook.lib.config import CONFIG
|
||||
from passbook.lib.utils.ui import human_list
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
class LoginForm(forms.Form):
|
||||
"""Allow users to login"""
|
||||
|
||||
title = _("Log in to your account")
|
||||
uid_field = forms.CharField(label=_(""))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if CONFIG.y("passbook.uid_fields") == ["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")]
|
||||
)
|
||||
|
||||
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"]:
|
||||
validate_email(self.cleaned_data.get("uid_field"))
|
||||
return self.cleaned_data.get("uid_field")
|
||||
|
||||
|
||||
class SignUpForm(forms.Form):
|
||||
"""SignUp Form"""
|
||||
|
||||
|
||||
@ -5,9 +5,8 @@ from random import SystemRandom
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from passbook.core.forms.authentication import LoginForm, SignUpForm
|
||||
from passbook.core.forms.authentication import SignUpForm
|
||||
from passbook.core.models import User
|
||||
from passbook.flows.models import Flow, FlowDesignation
|
||||
|
||||
|
||||
class TestAuthenticationViews(TestCase):
|
||||
@ -40,20 +39,6 @@ class TestAuthenticationViews(TestCase):
|
||||
response = self.client.get(reverse("passbook_core:auth-sign-up"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_login_view(self):
|
||||
"""Test account.login view (Anonymous)"""
|
||||
self.client.logout()
|
||||
response = self.client.get(reverse("passbook_core:auth-login"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# test login with post
|
||||
form = LoginForm(self.login_data)
|
||||
self.assertTrue(form.is_valid())
|
||||
|
||||
response = self.client.post(
|
||||
reverse("passbook_core:auth-login"), data=form.cleaned_data
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_logout_view(self):
|
||||
"""Test account.logout view"""
|
||||
self.client.force_login(self.user)
|
||||
@ -66,24 +51,6 @@ class TestAuthenticationViews(TestCase):
|
||||
response = self.client.get(reverse("passbook_core:auth-logout"))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_login_view_auth(self):
|
||||
"""Test account.login view (Authenticated)"""
|
||||
self.client.force_login(self.user)
|
||||
response = self.client.get(reverse("passbook_core:auth-login"))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_login_view_post(self):
|
||||
"""Test account.login view POST (Anonymous)"""
|
||||
login_response = self.client.post(
|
||||
reverse("passbook_core:auth-login"), data=self.login_data
|
||||
)
|
||||
self.assertEqual(login_response.status_code, 302)
|
||||
flow = Flow.objects.get(designation=FlowDesignation.AUTHENTICATION)
|
||||
expected = reverse(
|
||||
"passbook_flows:flow-executor", kwargs={"flow_slug": flow.slug}
|
||||
)
|
||||
self.assertEqual(login_response.url, expected)
|
||||
|
||||
def test_sign_up_view_post(self):
|
||||
"""Test account.sign_up view POST (Anonymous)"""
|
||||
form = SignUpForm(self.sign_up_data)
|
||||
|
||||
@ -2,10 +2,16 @@
|
||||
from django.urls import path
|
||||
|
||||
from passbook.core.views import authentication, overview, user
|
||||
from passbook.flows.models import FlowDesignation
|
||||
from passbook.flows.views import ToDefaultFlow
|
||||
|
||||
urlpatterns = [
|
||||
# Authentication views
|
||||
path("auth/login/", authentication.LoginView.as_view(), name="auth-login"),
|
||||
path(
|
||||
"auth/login/",
|
||||
ToDefaultFlow.as_view(designation=FlowDesignation.AUTHENTICATION),
|
||||
name="auth-login",
|
||||
),
|
||||
path("auth/logout/", authentication.LogoutView.as_view(), name="auth-logout"),
|
||||
path("auth/sign_up/", authentication.SignUpView.as_view(), name="auth-sign-up"),
|
||||
path(
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
"""passbook core authentication views"""
|
||||
from typing import Dict, Optional
|
||||
from typing import Dict
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import login, logout
|
||||
@ -12,87 +12,15 @@ from django.views import View
|
||||
from django.views.generic import FormView
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.forms.authentication import LoginForm, SignUpForm
|
||||
from passbook.core.models import Invitation, Nonce, Source, User
|
||||
from passbook.core.forms.authentication import SignUpForm
|
||||
from passbook.core.models import Invitation, Nonce, User
|
||||
from passbook.core.signals import invitation_used, user_signed_up
|
||||
from passbook.flows.models import Flow, FlowDesignation
|
||||
from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner
|
||||
from passbook.flows.views import SESSION_KEY_PLAN
|
||||
from passbook.lib.config import CONFIG
|
||||
from passbook.lib.utils.urls import redirect_with_qs
|
||||
from passbook.stages.password.exceptions import PasswordPolicyInvalid
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
class LoginView(UserPassesTestMixin, FormView):
|
||||
"""Allow users to sign in"""
|
||||
|
||||
template_name = "login/form.html"
|
||||
form_class = LoginForm
|
||||
success_url = "."
|
||||
|
||||
# Allow only not authenticated users to login
|
||||
def test_func(self):
|
||||
return self.request.user.is_authenticated is False
|
||||
|
||||
def handle_no_permission(self):
|
||||
if "next" in self.request.GET:
|
||||
return redirect(self.request.GET.get("next"))
|
||||
return redirect(reverse("passbook_core:overview"))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs["config"] = CONFIG.y("passbook")
|
||||
kwargs["title"] = _("Log in to your account")
|
||||
kwargs["primary_action"] = _("Log in")
|
||||
kwargs["show_sign_up_notice"] = CONFIG.y("passbook.sign_up.enabled")
|
||||
kwargs["sources"] = []
|
||||
sources = (
|
||||
Source.objects.filter(enabled=True).order_by("name").select_subclasses()
|
||||
)
|
||||
for source in sources:
|
||||
ui_login_button = source.ui_login_button
|
||||
if ui_login_button:
|
||||
kwargs["sources"].append(ui_login_button)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_user(self, uid_value) -> Optional[User]:
|
||||
"""Find user instance. Returns None if no user was found."""
|
||||
for search_field in CONFIG.y("passbook.uid_fields"):
|
||||
# Workaround for E-Mail -> email
|
||||
if search_field == "e-mail":
|
||||
search_field = "email"
|
||||
users = User.objects.filter(**{search_field: uid_value})
|
||||
if users.exists():
|
||||
LOGGER.debug("Found user", user=users.first(), uid_field=search_field)
|
||||
return users.first()
|
||||
return None
|
||||
|
||||
def form_valid(self, form: LoginForm) -> HttpResponse:
|
||||
"""Form data is valid"""
|
||||
pre_user = self.get_user(form.cleaned_data.get("uid_field"))
|
||||
if not pre_user:
|
||||
# No user found
|
||||
return self.invalid_login(self.request)
|
||||
# We run the Flow planner here so we can pass the Pending user in the context
|
||||
flow = get_object_or_404(Flow, designation=FlowDesignation.AUTHENTICATION)
|
||||
planner = FlowPlanner(flow)
|
||||
plan = planner.plan(self.request)
|
||||
plan.context[PLAN_CONTEXT_PENDING_USER] = pre_user
|
||||
self.request.session[SESSION_KEY_PLAN] = plan
|
||||
return redirect_with_qs(
|
||||
"passbook_flows:flow-executor", self.request.GET, flow_slug=flow.slug,
|
||||
)
|
||||
|
||||
def invalid_login(
|
||||
self, request: HttpRequest, disabled_user: User = None
|
||||
) -> HttpResponse:
|
||||
"""Handle login for disabled users/invalid login attempts"""
|
||||
LOGGER.debug("invalid_login", user=disabled_user)
|
||||
messages.error(request, _("Failed to authenticate."))
|
||||
return self.render_to_response(self.get_context_data())
|
||||
|
||||
|
||||
class LogoutView(LoginRequiredMixin, View):
|
||||
"""Log current user out"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user