stages/dummy: add unittests
stages/password: improve coverage stages/user_login: improve coverage
This commit is contained in:
parent
f111604b70
commit
a3a3dde1c8
|
@ -60,12 +60,12 @@ router.register("sources/ldap", LDAPSourceViewSet)
|
||||||
router.register("sources/oauth", OAuthSourceViewSet)
|
router.register("sources/oauth", OAuthSourceViewSet)
|
||||||
|
|
||||||
router.register("policies/all", PolicyViewSet)
|
router.register("policies/all", PolicyViewSet)
|
||||||
router.register("policies/passwordexpiry", PasswordExpiryPolicyViewSet)
|
router.register("policies/expression", ExpressionPolicyViewSet)
|
||||||
router.register("policies/haveibeenpwned", HaveIBeenPwendPolicyViewSet)
|
router.register("policies/haveibeenpwned", HaveIBeenPwendPolicyViewSet)
|
||||||
router.register("policies/password", PasswordPolicyViewSet)
|
router.register("policies/password", PasswordPolicyViewSet)
|
||||||
|
router.register("policies/passwordexpiry", PasswordExpiryPolicyViewSet)
|
||||||
router.register("policies/reputation", ReputationPolicyViewSet)
|
router.register("policies/reputation", ReputationPolicyViewSet)
|
||||||
router.register("policies/webhook", WebhookPolicyViewSet)
|
router.register("policies/webhook", WebhookPolicyViewSet)
|
||||||
router.register("policies/expression", ExpressionPolicyViewSet)
|
|
||||||
|
|
||||||
router.register("providers/all", ProviderViewSet)
|
router.register("providers/all", ProviderViewSet)
|
||||||
router.register("providers/applicationgateway", ApplicationGatewayProviderViewSet)
|
router.register("providers/applicationgateway", ApplicationGatewayProviderViewSet)
|
||||||
|
@ -80,12 +80,12 @@ router.register("propertymappings/saml", SAMLPropertyMappingViewSet)
|
||||||
router.register("stages/all", StageViewSet)
|
router.register("stages/all", StageViewSet)
|
||||||
router.register("stages/captcha", CaptchaStageViewSet)
|
router.register("stages/captcha", CaptchaStageViewSet)
|
||||||
router.register("stages/email", EmailStageViewSet)
|
router.register("stages/email", EmailStageViewSet)
|
||||||
|
router.register("stages/identification", IdentificationStageViewSet)
|
||||||
router.register("stages/otp", OTPStageViewSet)
|
router.register("stages/otp", OTPStageViewSet)
|
||||||
router.register("stages/password", PasswordStageViewSet)
|
router.register("stages/password", PasswordStageViewSet)
|
||||||
router.register("stages/identification", IdentificationStageViewSet)
|
|
||||||
router.register("stages/user_login", UserLoginStageViewSet)
|
|
||||||
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_login", UserLoginStageViewSet)
|
||||||
|
|
||||||
router.register("flows", FlowViewSet)
|
router.register("flows", FlowViewSet)
|
||||||
router.register("flows/bindings", FlowStageBindingViewSet)
|
router.register("flows/bindings", FlowStageBindingViewSet)
|
||||||
|
|
|
@ -11,7 +11,6 @@ from django.views.generic import DeleteView, FormView, UpdateView
|
||||||
|
|
||||||
from passbook.core.forms.users import PasswordChangeForm, UserDetailForm
|
from passbook.core.forms.users import PasswordChangeForm, UserDetailForm
|
||||||
from passbook.lib.config import CONFIG
|
from passbook.lib.config import CONFIG
|
||||||
from passbook.stages.password.exceptions import PasswordPolicyInvalid
|
|
||||||
|
|
||||||
|
|
||||||
class UserSettingsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
|
class UserSettingsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
|
||||||
|
@ -48,20 +47,20 @@ class UserChangePasswordView(LoginRequiredMixin, FormView):
|
||||||
template_name = "login/form_with_user.html"
|
template_name = "login/form_with_user.html"
|
||||||
|
|
||||||
def form_valid(self, form: PasswordChangeForm):
|
def form_valid(self, form: PasswordChangeForm):
|
||||||
|
# TODO: Rewrite to flow
|
||||||
try:
|
try:
|
||||||
# user.set_password checks against Policies so we don't need to manually do it here
|
# user.set_password checks against Policies so we don't need to manually do it here
|
||||||
self.request.user.set_password(form.cleaned_data.get("password"))
|
self.request.user.set_password(form.cleaned_data.get("password"))
|
||||||
self.request.user.save()
|
self.request.user.save()
|
||||||
update_session_auth_hash(self.request, self.request.user)
|
update_session_auth_hash(self.request, self.request.user)
|
||||||
messages.success(self.request, _("Successfully changed password"))
|
messages.success(self.request, _("Successfully changed password"))
|
||||||
except PasswordPolicyInvalid as exc:
|
except ValueError:
|
||||||
# Manually inject error into form
|
# Manually inject error into form
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
errors = form._errors.setdefault("password_repeat", ErrorList(""))
|
errors = form._errors.setdefault("password_repeat", ErrorList(""))
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
errors = form._errors.setdefault("password", ErrorList())
|
errors = form._errors.setdefault("password", ErrorList())
|
||||||
for error in exc.messages:
|
errors.append("foo")
|
||||||
errors.append(error)
|
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
return redirect("passbook_core:overview")
|
return redirect("passbook_core:overview")
|
||||||
|
|
||||||
|
|
|
@ -352,6 +352,7 @@ for handler_name, level in _LOGGING_HANDLER_MAP.items():
|
||||||
|
|
||||||
TEST = False
|
TEST = False
|
||||||
TEST_RUNNER = "xmlrunner.extra.djangotestrunner.XMLTestRunner"
|
TEST_RUNNER = "xmlrunner.extra.djangotestrunner.XMLTestRunner"
|
||||||
|
TEST_OUTPUT_VERBOSE = 2
|
||||||
|
|
||||||
TEST_OUTPUT_FILE_NAME = "unittest.xml"
|
TEST_OUTPUT_FILE_NAME = "unittest.xml"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
"""dummy 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.stages.dummy.forms import DummyStageForm
|
||||||
|
from passbook.stages.dummy.models import DummyStage
|
||||||
|
|
||||||
|
|
||||||
|
class TestDummyStage(TestCase):
|
||||||
|
"""Dummy 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-dummy",
|
||||||
|
slug="test-dummy",
|
||||||
|
designation=FlowDesignation.AUTHENTICATION,
|
||||||
|
)
|
||||||
|
self.stage = DummyStage.objects.create(name="dummy",)
|
||||||
|
FlowStageBinding.objects.create(
|
||||||
|
flow=self.flow, stage=self.stage, order=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
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_post(self):
|
||||||
|
"""Test with valid email, check that URL redirects back to itself"""
|
||||||
|
url = reverse(
|
||||||
|
"passbook_flows:flow-executor", kwargs={"flow_slug": self.flow.slug}
|
||||||
|
)
|
||||||
|
response = self.client.post(url, {})
|
||||||
|
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(DummyStageForm(data).is_valid(), True)
|
|
@ -1,12 +0,0 @@
|
||||||
"""passbook password policy exceptions"""
|
|
||||||
from passbook.lib.sentry import SentryIgnoredException
|
|
||||||
|
|
||||||
|
|
||||||
class PasswordPolicyInvalid(SentryIgnoredException):
|
|
||||||
"""Exception raised when a Password Policy fails"""
|
|
||||||
|
|
||||||
messages = []
|
|
||||||
|
|
||||||
def __init__(self, *messages):
|
|
||||||
super().__init__()
|
|
||||||
self.messages = messages
|
|
|
@ -3,8 +3,6 @@ from django.contrib.postgres.fields import ArrayField
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from passbook.core.models import Policy, User
|
|
||||||
from passbook.core.types import UIUserSettings
|
|
||||||
from passbook.flows.models import Stage
|
from passbook.flows.models import Stage
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,26 +13,10 @@ class PasswordStage(Stage):
|
||||||
models.TextField(),
|
models.TextField(),
|
||||||
help_text=_("Selection of backends to test the password against."),
|
help_text=_("Selection of backends to test the password against."),
|
||||||
)
|
)
|
||||||
password_policies = models.ManyToManyField(Policy, blank=True)
|
|
||||||
|
|
||||||
type = "passbook.stages.password.stage.PasswordStage"
|
type = "passbook.stages.password.stage.PasswordStage"
|
||||||
form = "passbook.stages.password.forms.PasswordStageForm"
|
form = "passbook.stages.password.forms.PasswordStageForm"
|
||||||
|
|
||||||
@property
|
|
||||||
def ui_user_settings(self) -> UIUserSettings:
|
|
||||||
return UIUserSettings(
|
|
||||||
name="Change Password",
|
|
||||||
icon="pficon-key",
|
|
||||||
view_name="passbook_core:user-change-password",
|
|
||||||
)
|
|
||||||
|
|
||||||
def password_passes(self, user: User) -> bool:
|
|
||||||
"""Return true if user's password passes, otherwise False or raise Exception"""
|
|
||||||
for policy in self.policies.all():
|
|
||||||
if not policy.passes(user):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Password Stage {self.name}"
|
return f"Password Stage {self.name}"
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ class Prompt(UUIDModel):
|
||||||
return forms.IntegerField(
|
return forms.IntegerField(
|
||||||
label=_(self.label),
|
label=_(self.label),
|
||||||
widget=forms.NumberInput(attrs=attrs),
|
widget=forms.NumberInput(attrs=attrs),
|
||||||
requred=self.required,
|
required=self.required,
|
||||||
)
|
)
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ from passbook.flows.models import Flow, FlowDesignation, FlowStageBinding
|
||||||
from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
|
from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
|
||||||
from passbook.flows.views import SESSION_KEY_PLAN
|
from passbook.flows.views import SESSION_KEY_PLAN
|
||||||
from passbook.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
|
from passbook.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
|
||||||
|
from passbook.stages.user_login.forms import UserLoginStageForm
|
||||||
from passbook.stages.user_login.models import UserLoginStage
|
from passbook.stages.user_login.models import UserLoginStage
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,3 +76,8 @@ class TestUserLoginStage(TestCase):
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertEqual(response.url, reverse("passbook_flows:denied"))
|
self.assertEqual(response.url, reverse("passbook_flows:denied"))
|
||||||
|
|
||||||
|
def test_form(self):
|
||||||
|
"""Test Form"""
|
||||||
|
data = {"name": "test"}
|
||||||
|
self.assertEqual(UserLoginStageForm(data).is_valid(), True)
|
||||||
|
|
Reference in New Issue