stages/user_logout: add logout stage

This commit is contained in:
Jens Langhammer 2020-05-11 01:12:14 +02:00
parent 69120da45c
commit 9dec13c225
11 changed files with 184 additions and 1 deletions

View file

@ -38,6 +38,7 @@ from passbook.stages.otp.api import OTPStageViewSet
from passbook.stages.password.api import PasswordStageViewSet
from passbook.stages.prompt.api import PromptStageViewSet, PromptViewSet
from passbook.stages.user_login.api import UserLoginStageViewSet
from passbook.stages.user_logout.api import UserLogoutStageViewSet
from passbook.stages.user_write.api import UserWriteStageViewSet
LOGGER = get_logger()
@ -86,8 +87,9 @@ router.register("stages/otp", OTPStageViewSet)
router.register("stages/password", PasswordStageViewSet)
router.register("stages/prompt", PromptStageViewSet)
router.register("stages/prompt/prompts", PromptViewSet)
router.register("stages/user_write", UserWriteStageViewSet)
router.register("stages/user_login", UserLoginStageViewSet)
router.register("stages/user_logout", UserLogoutStageViewSet)
router.register("stages/user_write", UserWriteStageViewSet)
router.register("flows", FlowViewSet)
router.register("flows/bindings", FlowStageBindingViewSet)

View file

@ -109,6 +109,7 @@ INSTALLED_APPS = [
"passbook.stages.prompt.apps.PassbookStagPromptConfig",
"passbook.stages.identification.apps.PassbookStageIdentificationConfig",
"passbook.stages.user_login.apps.PassbookStageUserLoginConfig",
"passbook.stages.user_logout.apps.PassbookStageUserLogoutConfig",
"passbook.stages.user_write.apps.PassbookStageUserWriteConfig",
"passbook.stages.otp.apps.PassbookStageOTPConfig",
"passbook.stages.password.apps.PassbookStagePasswordConfig",

View file

View file

@ -0,0 +1,24 @@
"""Logout Stage API Views"""
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from passbook.stages.user_logout.models import UserLogoutStage
class UserLogoutStageSerializer(ModelSerializer):
"""UserLogoutStage Serializer"""
class Meta:
model = UserLogoutStage
fields = [
"pk",
"name",
]
class UserLogoutStageViewSet(ModelViewSet):
"""UserLogoutStage Viewset"""
queryset = UserLogoutStage.objects.all()
serializer_class = UserLogoutStageSerializer

View file

@ -0,0 +1,10 @@
"""passbook logout stage app config"""
from django.apps import AppConfig
class PassbookStageUserLogoutConfig(AppConfig):
"""passbook logout stage config"""
name = "passbook.stages.user_logout"
label = "passbook_stages_user_logout"
verbose_name = "passbook Stages.User Logout"

View file

@ -0,0 +1,16 @@
"""passbook flows logout forms"""
from django import forms
from passbook.stages.user_logout.models import UserLogoutStage
class UserLogoutStageForm(forms.ModelForm):
"""Form to create/edit UserLogoutStage instances"""
class Meta:
model = UserLogoutStage
fields = ["name"]
widgets = {
"name": forms.TextInput(),
}

View file

@ -0,0 +1,37 @@
# Generated by Django 3.0.5 on 2020-05-10 22:56
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
("passbook_flows", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="UserLogoutStage",
fields=[
(
"stage_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="passbook_flows.Stage",
),
),
],
options={
"verbose_name": "User Logout Stage",
"verbose_name_plural": "User Logout Stages",
},
bases=("passbook_flows.stage",),
),
]

View file

@ -0,0 +1,19 @@
"""logout stage models"""
from django.utils.translation import gettext_lazy as _
from passbook.flows.models import Stage
class UserLogoutStage(Stage):
"""Logout stage, allows a user to identify themselves to authenticate."""
type = "passbook.stages.user_logout.stage.UserLogoutStageView"
form = "passbook.stages.user_logout.forms.UserLogoutStageForm"
def __str__(self):
return f"User Logout Stage {self.name}"
class Meta:
verbose_name = _("User Logout Stage")
verbose_name_plural = _("User Logout Stages")

View file

@ -0,0 +1,22 @@
"""Logout stage logic"""
from django.contrib.auth import logout
from django.http import HttpRequest, HttpResponse
from structlog import get_logger
from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER
from passbook.flows.stage import AuthenticationStage
LOGGER = get_logger()
class UserLogoutStageView(AuthenticationStage):
"""Finalise Authentication flow by logging the user in"""
def get(self, request: HttpRequest) -> HttpResponse:
logout(self.request)
LOGGER.debug(
"Logged out",
user=self.executor.plan.context[PLAN_CONTEXT_PENDING_USER],
flow_slug=self.executor.flow.slug,
)
return self.executor.stage_ok()

View file

@ -0,0 +1,52 @@
"""logout 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.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from passbook.flows.views import SESSION_KEY_PLAN
from passbook.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
from passbook.stages.user_logout.forms import UserLogoutStageForm
from passbook.stages.user_logout.models import UserLogoutStage
class TestUserLogoutStage(TestCase):
"""Logout 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-logout",
slug="test-logout",
designation=FlowDesignation.AUTHENTICATION,
)
self.stage = UserLogoutStage.objects.create(name="logout")
FlowStageBinding.objects.create(flow=self.flow, stage=self.stage, order=2)
def test_valid_password(self):
"""Test with a valid pending user and backend"""
plan = FlowPlan(flow_pk=self.flow.pk.hex, stages=[self.stage])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
plan.context[
PLAN_CONTEXT_AUTHENTICATION_BACKEND
] = "django.contrib.auth.backends.ModelBackend"
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
response = self.client.get(
reverse(
"passbook_flows:flow-executor", kwargs={"flow_slug": self.flow.slug}
)
)
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, reverse("passbook_core:overview"))
def test_form(self):
"""Test Form"""
data = {"name": "test"}
self.assertEqual(UserLogoutStageForm(data).is_valid(), True)