stages/user_logout: add logout stage
This commit is contained in:
parent
69120da45c
commit
9dec13c225
|
@ -38,6 +38,7 @@ from passbook.stages.otp.api import OTPStageViewSet
|
||||||
from passbook.stages.password.api import PasswordStageViewSet
|
from passbook.stages.password.api import PasswordStageViewSet
|
||||||
from passbook.stages.prompt.api import PromptStageViewSet, PromptViewSet
|
from passbook.stages.prompt.api import PromptStageViewSet, PromptViewSet
|
||||||
from passbook.stages.user_login.api import UserLoginStageViewSet
|
from passbook.stages.user_login.api import UserLoginStageViewSet
|
||||||
|
from passbook.stages.user_logout.api import UserLogoutStageViewSet
|
||||||
from passbook.stages.user_write.api import UserWriteStageViewSet
|
from passbook.stages.user_write.api import UserWriteStageViewSet
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
@ -86,8 +87,9 @@ router.register("stages/otp", OTPStageViewSet)
|
||||||
router.register("stages/password", PasswordStageViewSet)
|
router.register("stages/password", PasswordStageViewSet)
|
||||||
router.register("stages/prompt", PromptStageViewSet)
|
router.register("stages/prompt", PromptStageViewSet)
|
||||||
router.register("stages/prompt/prompts", PromptViewSet)
|
router.register("stages/prompt/prompts", PromptViewSet)
|
||||||
router.register("stages/user_write", UserWriteStageViewSet)
|
|
||||||
router.register("stages/user_login", UserLoginStageViewSet)
|
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", FlowViewSet)
|
||||||
router.register("flows/bindings", FlowStageBindingViewSet)
|
router.register("flows/bindings", FlowStageBindingViewSet)
|
||||||
|
|
|
@ -109,6 +109,7 @@ INSTALLED_APPS = [
|
||||||
"passbook.stages.prompt.apps.PassbookStagPromptConfig",
|
"passbook.stages.prompt.apps.PassbookStagPromptConfig",
|
||||||
"passbook.stages.identification.apps.PassbookStageIdentificationConfig",
|
"passbook.stages.identification.apps.PassbookStageIdentificationConfig",
|
||||||
"passbook.stages.user_login.apps.PassbookStageUserLoginConfig",
|
"passbook.stages.user_login.apps.PassbookStageUserLoginConfig",
|
||||||
|
"passbook.stages.user_logout.apps.PassbookStageUserLogoutConfig",
|
||||||
"passbook.stages.user_write.apps.PassbookStageUserWriteConfig",
|
"passbook.stages.user_write.apps.PassbookStageUserWriteConfig",
|
||||||
"passbook.stages.otp.apps.PassbookStageOTPConfig",
|
"passbook.stages.otp.apps.PassbookStageOTPConfig",
|
||||||
"passbook.stages.password.apps.PassbookStagePasswordConfig",
|
"passbook.stages.password.apps.PassbookStagePasswordConfig",
|
||||||
|
|
|
@ -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
|
|
@ -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"
|
|
@ -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(),
|
||||||
|
}
|
|
@ -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",),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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")
|
|
@ -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()
|
|
@ -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)
|
Reference in New Issue