flows: add test helpers to simplify and improve checking of stages, remove force_str

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2022-01-01 19:56:35 +01:00
parent 50e3d317b2
commit 90c31c2214
25 changed files with 302 additions and 653 deletions

View File

@ -1,6 +1,5 @@
"""Test Applications API"""
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import Application
@ -32,7 +31,7 @@ class TestApplicationsAPI(APITestCase):
)
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(force_str(response.content), {"messages": [], "passing": True})
self.assertJSONEqual(response.content.decode(), {"messages": [], "passing": True})
response = self.client.get(
reverse(
"authentik_api:application-check-access",
@ -40,14 +39,14 @@ class TestApplicationsAPI(APITestCase):
)
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(force_str(response.content), {"messages": ["dummy"], "passing": False})
self.assertJSONEqual(response.content.decode(), {"messages": ["dummy"], "passing": False})
def test_list(self):
"""Test list operation without superuser_full_list"""
self.client.force_login(self.user)
response = self.client.get(reverse("authentik_api:application-list"))
self.assertJSONEqual(
force_str(response.content),
response.content.decode(),
{
"pagination": {
"next": 0,
@ -83,7 +82,7 @@ class TestApplicationsAPI(APITestCase):
reverse("authentik_api:application-list") + "?superuser_full_list=true"
)
self.assertJSONEqual(
force_str(response.content),
response.content.decode(),
{
"pagination": {
"next": 0,

View File

@ -2,7 +2,6 @@
from json import loads
from django.urls.base import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import User
@ -28,5 +27,5 @@ class TestAuthenticatedSessionsAPI(APITestCase):
self.client.force_login(self.other_user)
response = self.client.get(reverse("authentik_api:authenticatedsession-list"))
self.assertEqual(response.status_code, 200)
body = loads(force_str(response.content))
body = loads(response.content.decode())
self.assertEqual(body["pagination"]["count"], 1)

View File

@ -0,0 +1,51 @@
"""Test helpers"""
from json import loads
from typing import Any, Optional
from django.http.response import HttpResponse
from django.urls.base import reverse
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.models import Flow
class FlowTestCase(APITestCase):
"""Helpers for testing flows and stages."""
# pylint: disable=invalid-name
def assertStageResponse(
self,
response: HttpResponse,
flow: Optional[Flow] = None,
user: Optional[User] = None,
**kwargs,
) -> dict[str, Any]:
"""Assert various attributes of a stage response"""
raw_response = loads(response.content.decode())
self.assertIsNotNone(raw_response["component"])
self.assertIsNotNone(raw_response["type"])
if flow:
self.assertIn("flow_info", raw_response)
self.assertEqual(raw_response["flow_info"]["background"], flow.background_url)
self.assertEqual(
raw_response["flow_info"]["cancel_url"], reverse("authentik_flows:cancel")
)
# We don't check the flow title since it will most likely go
# through ChallengeStageView.format_title() so might not match 1:1
# self.assertEqual(raw_response["flow_info"]["title"], flow.title)
self.assertIsNotNone(raw_response["flow_info"]["title"])
if user:
self.assertEqual(raw_response["pending_user"], user.username)
self.assertEqual(raw_response["pending_user_avatar"], user.avatar)
for key, expected in kwargs.items():
self.assertEqual(raw_response[key], expected)
return raw_response
# pylint: disable=invalid-name
def assertStageRedirects(self, response: HttpResponse, to: str) -> dict[str, Any]:
"""Wrapper around assertStageResponse that checks for a redirect"""
return self.assertStageResponse(
response, component="xak-flow-redirect", to=to, type=ChallengeTypes.REDIRECT.value
)

View File

@ -4,16 +4,14 @@ from unittest.mock import MagicMock, PropertyMock, patch
from django.http import HttpRequest, HttpResponse
from django.test.client import RequestFactory
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.markers import ReevaluateMarker, StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding, InvalidResponseAction
from authentik.flows.planner import FlowPlan, FlowPlanner
from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, StageView
from authentik.flows.tests import FlowTestCase
from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_PLAN, FlowExecutorView
from authentik.lib.config import CONFIG
from authentik.policies.dummy.models import DummyPolicy
@ -37,7 +35,7 @@ def to_stage_response(request: HttpRequest, source: HttpResponse):
TO_STAGE_RESPONSE_MOCK = MagicMock(side_effect=to_stage_response)
class TestFlowExecutor(APITestCase):
class TestFlowExecutor(FlowTestCase):
"""Test executor"""
def setUp(self):
@ -90,18 +88,11 @@ class TestFlowExecutor(APITestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "ak-stage-access-denied",
"error_message": FlowNonApplicableException.__doc__,
"flow_info": {
"background": flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
"type": ChallengeTypes.NATIVE.value,
},
self.assertStageResponse(
response,
flow=flow,
error_message=FlowNonApplicableException.__doc__,
component="ak-stage-access-denied",
)
@patch(
@ -283,14 +274,7 @@ class TestFlowExecutor(APITestCase):
# We do this request without the patch, so the policy results in false
response = self.client.post(exec_url)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
def test_reevaluate_keep(self):
"""Test planner with re-evaluate (everything is kept)"""
@ -360,14 +344,7 @@ class TestFlowExecutor(APITestCase):
# We do this request without the patch, so the policy results in false
response = self.client.post(exec_url)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
def test_reevaluate_remove_consecutive(self):
"""Test planner with re-evaluate (consecutive stages are removed)"""
@ -407,18 +384,7 @@ class TestFlowExecutor(APITestCase):
# First request, run the planner
response = self.client.get(exec_url)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-dummy",
"flow_info": {
"background": flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
},
)
self.assertStageResponse(response, flow, component="ak-stage-dummy")
plan: FlowPlan = self.client.session[SESSION_KEY_PLAN]
@ -441,31 +407,13 @@ class TestFlowExecutor(APITestCase):
# but it won't save it, hence we can't check the plan
response = self.client.get(exec_url)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-dummy",
"flow_info": {
"background": flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
},
)
self.assertStageResponse(response, flow, component="ak-stage-dummy")
# fourth request, this confirms the last stage (dummy4)
# We do this request without the patch, so the policy results in false
response = self.client.post(exec_url)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
def test_stageview_user_identifier(self):
"""Test PLAN_CONTEXT_PENDING_USER_IDENTIFIER"""
@ -532,35 +480,16 @@ class TestFlowExecutor(APITestCase):
# First request, run the planner
response = self.client.get(exec_url)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-identification",
"flow_info": {
"background": flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
"password_fields": False,
"primary_action": "Log in",
"sources": [],
"show_source_labels": False,
"user_fields": [UserFields.E_MAIL],
},
self.assertStageResponse(
response,
flow,
component="ak-stage-identification",
password_fields=False,
primary_action="Log in",
sources=[],
show_source_labels=False,
user_fields=[UserFields.E_MAIL],
)
response = self.client.post(exec_url, {"uid_field": "invalid-string"}, follow=True)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "ak-stage-access-denied",
"error_message": None,
"flow_info": {
"background": flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
"type": ChallengeTypes.NATIVE.value,
},
)
self.assertStageResponse(response, flow, component="ak-stage-access-denied")

View File

@ -1,16 +1,14 @@
"""Password flow tests"""
from django.urls.base import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.tests import FlowTestCase
from authentik.policies.password.models import PasswordPolicy
from authentik.stages.prompt.models import FieldTypes, Prompt, PromptStage
class TestPasswordPolicyFlow(APITestCase):
class TestPasswordPolicyFlow(FlowTestCase):
"""Test Password Policy"""
def setUp(self) -> None:
@ -53,29 +51,22 @@ class TestPasswordPolicyFlow(APITestCase):
{"password": "akadmin"},
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "ak-stage-prompt",
"fields": [
{
"field_key": "password",
"label": "PASSWORD_LABEL",
"order": 0,
"placeholder": "PASSWORD_PLACEHOLDER",
"required": True,
"type": "password",
"sub_text": "",
}
],
"flow_info": {
"background": self.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
"response_errors": {
"non_field_errors": [{"code": "invalid", "string": self.policy.error_message}]
},
"type": ChallengeTypes.NATIVE.value,
self.assertStageResponse(
response,
self.flow,
component="ak-stage-prompt",
fields=[
{
"field_key": "password",
"label": "PASSWORD_LABEL",
"order": 0,
"placeholder": "PASSWORD_PLACEHOLDER",
"required": True,
"type": "password",
"sub_text": "",
}
],
response_errors={
"non_field_errors": [{"code": "invalid", "string": self.policy.error_message}]
},
)

View File

@ -1,7 +1,6 @@
"""Test authorize view"""
from django.test import RequestFactory
from django.urls import reverse
from django.utils.encoding import force_str
from authentik.core.models import Application
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
@ -201,7 +200,7 @@ class TestAuthorize(OAuthTestCase):
)
code: AuthorizationCode = AuthorizationCode.objects.filter(user=user).first()
self.assertJSONEqual(
force_str(response.content),
response.content.decode(),
{
"component": "xak-flow-redirect",
"type": ChallengeTypes.REDIRECT.value,
@ -240,7 +239,7 @@ class TestAuthorize(OAuthTestCase):
)
token: RefreshToken = RefreshToken.objects.filter(user=user).first()
self.assertJSONEqual(
force_str(response.content),
response.content.decode(),
{
"component": "xak-flow-redirect",
"type": ChallengeTypes.REDIRECT.value,

View File

@ -3,7 +3,6 @@ import json
from django.test import RequestFactory
from django.urls.base import reverse
from django.utils.encoding import force_str
from authentik.core.models import Application
from authentik.core.tests.utils import create_test_cert, create_test_flow
@ -31,7 +30,7 @@ class TestJWKS(OAuthTestCase):
response = self.client.get(
reverse("authentik_providers_oauth2:jwks", kwargs={"application_slug": app.slug})
)
body = json.loads(force_str(response.content))
body = json.loads(response.content.decode())
self.assertEqual(len(body["keys"]), 1)
def test_hs256(self):
@ -46,4 +45,4 @@ class TestJWKS(OAuthTestCase):
response = self.client.get(
reverse("authentik_providers_oauth2:jwks", kwargs={"application_slug": app.slug})
)
self.assertJSONEqual(force_str(response.content), {})
self.assertJSONEqual(response.content.decode(), {})

View File

@ -3,7 +3,6 @@ from base64 import b64encode
from django.test import RequestFactory
from django.urls import reverse
from django.utils.encoding import force_str
from authentik.core.models import Application
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
@ -135,7 +134,7 @@ class TestToken(OAuthTestCase):
)
new_token: RefreshToken = RefreshToken.objects.filter(user=user).first()
self.assertJSONEqual(
force_str(response.content),
response.content.decode(),
{
"access_token": new_token.access_token,
"refresh_token": new_token.refresh_token,
@ -184,7 +183,7 @@ class TestToken(OAuthTestCase):
self.assertEqual(response["Access-Control-Allow-Credentials"], "true")
self.assertEqual(response["Access-Control-Allow-Origin"], "http://local.invalid")
self.assertJSONEqual(
force_str(response.content),
response.content.decode(),
{
"access_token": new_token.access_token,
"refresh_token": new_token.refresh_token,
@ -230,7 +229,7 @@ class TestToken(OAuthTestCase):
self.assertNotIn("Access-Control-Allow-Credentials", response)
self.assertNotIn("Access-Control-Allow-Origin", response)
self.assertJSONEqual(
force_str(response.content),
response.content.decode(),
{
"access_token": new_token.access_token,
"refresh_token": new_token.refresh_token,

View File

@ -3,7 +3,6 @@ import json
from dataclasses import asdict
from django.urls import reverse
from django.utils.encoding import force_str
from authentik.core.models import Application
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
@ -54,7 +53,7 @@ class TestUserinfo(OAuthTestCase):
HTTP_AUTHORIZATION=f"Bearer {self.token.access_token}",
)
self.assertJSONEqual(
force_str(res.content),
res.content.decode(),
{
"name": self.user.name,
"given_name": self.user.name,
@ -77,7 +76,7 @@ class TestUserinfo(OAuthTestCase):
HTTP_AUTHORIZATION=f"Bearer {self.token.access_token}",
)
self.assertJSONEqual(
force_str(res.content),
res.content.decode(),
{
"name": self.user.name,
"given_name": self.user.name,

View File

@ -3,15 +3,13 @@ from unittest.mock import MagicMock, patch
from django.test.client import RequestFactory
from django.urls.base import reverse
from django.utils.encoding import force_str
from django_otp.plugins.otp_totp.models import TOTPDevice
from rest_framework.exceptions import ValidationError
from rest_framework.test import APITestCase
from webauthn.helpers import bytes_to_base64url
from authentik.core.tests.utils import create_test_admin_user
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.models import Flow, FlowStageBinding, NotConfiguredAction
from authentik.flows.tests import FlowTestCase
from authentik.lib.generators import generate_id, generate_key
from authentik.lib.tests.utils import get_request
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
@ -27,7 +25,7 @@ from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
from authentik.stages.identification.models import IdentificationStage, UserFields
class AuthenticatorValidateStageTests(APITestCase):
class AuthenticatorValidateStageTests(FlowTestCase):
"""Test validator stage"""
def setUp(self) -> None:
@ -61,22 +59,15 @@ class AuthenticatorValidateStageTests(APITestCase):
follow=True,
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-identification",
"password_fields": False,
"primary_action": "Log in",
"flow_info": {
"background": flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": flow.title,
},
"user_fields": ["username"],
"sources": [],
"show_source_labels": False,
},
self.assertStageResponse(
response,
flow,
component="ak-stage-identification",
password_fields=False,
primary_action="Log in",
user_fields=["username"],
sources=[],
show_source_labels=False,
)
def test_stage_validation(self):

View File

@ -1,13 +1,11 @@
"""captcha tests"""
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.markers import StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.planner import FlowPlan
from authentik.flows.tests import FlowTestCase
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.stages.captcha.models import CaptchaStage
@ -16,7 +14,7 @@ RECAPTCHA_PUBLIC_KEY = "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI"
RECAPTCHA_PRIVATE_KEY = "6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe"
class TestCaptchaStage(APITestCase):
class TestCaptchaStage(FlowTestCase):
"""Captcha tests"""
def setUp(self):
@ -46,11 +44,4 @@ class TestCaptchaStage(APITestCase):
{"token": "PASSED"},
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))

View File

@ -2,20 +2,18 @@
from time import sleep
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import Application, User
from authentik.core.tasks import clean_expired_models
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.markers import StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, FlowPlan
from authentik.flows.tests import FlowTestCase
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.stages.consent.models import ConsentMode, ConsentStage, UserConsent
class TestConsentStage(APITestCase):
class TestConsentStage(FlowTestCase):
"""Consent tests"""
def setUp(self):
@ -46,15 +44,7 @@ class TestConsentStage(APITestCase):
)
# pylint: disable=no-member
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
# pylint: disable=no-member
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
self.assertFalse(UserConsent.objects.filter(user=self.user).exists())
def test_permanent(self):
@ -82,14 +72,7 @@ class TestConsentStage(APITestCase):
{},
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
self.assertTrue(
UserConsent.objects.filter(user=self.user, application=self.application).exists()
)
@ -121,14 +104,7 @@ class TestConsentStage(APITestCase):
{},
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
self.assertTrue(
UserConsent.objects.filter(user=self.user, application=self.application).exists()
)

View File

@ -1,18 +1,16 @@
"""deny tests"""
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.markers import StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.planner import FlowPlan
from authentik.flows.tests import FlowTestCase
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.stages.deny.models import DenyStage
class TestUserDenyStage(APITestCase):
class TestUserDenyStage(FlowTestCase):
"""Deny tests"""
def setUp(self):
@ -39,19 +37,7 @@ class TestUserDenyStage(APITestCase):
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "ak-stage-access-denied",
"error_message": None,
"flow_info": {
"background": self.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
"type": ChallengeTypes.NATIVE.value,
},
)
self.assertStageResponse(response, self.flow, component="ak-stage-access-denied")
def test_valid_post(self):
"""Test with a valid pending user and backend"""
@ -65,16 +51,4 @@ class TestUserDenyStage(APITestCase):
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "ak-stage-access-denied",
"error_message": None,
"flow_info": {
"background": self.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
"type": ChallengeTypes.NATIVE.value,
},
)
self.assertStageResponse(response, self.flow, component="ak-stage-access-denied")

View File

@ -1,15 +1,13 @@
"""dummy tests"""
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.tests import FlowTestCase
from authentik.stages.dummy.models import DummyStage
class TestDummyStage(APITestCase):
class TestDummyStage(FlowTestCase):
"""Dummy tests"""
def setUp(self):
@ -42,11 +40,4 @@ class TestDummyStage(APITestCase):
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
response = self.client.post(url, {})
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))

View File

@ -5,21 +5,19 @@ from django.core import mail
from django.core.mail.backends.locmem import EmailBackend
from django.core.mail.backends.smtp import EmailBackend as SMTPEmailBackend
from django.urls import reverse
from django.utils.encoding import force_str
from django.utils.http import urlencode
from rest_framework.test import APITestCase
from authentik.core.models import Token, User
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.markers import StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from authentik.flows.tests import FlowTestCase
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.stages.email.models import EmailStage
from authentik.stages.email.stage import QS_KEY_TOKEN
class TestEmailStage(APITestCase):
class TestEmailStage(FlowTestCase):
"""Email tests"""
def setUp(self):
@ -123,14 +121,7 @@ class TestEmailStage(APITestCase):
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
session = self.client.session
plan: FlowPlan = session[SESSION_KEY_PLAN]

View File

@ -1,11 +1,10 @@
"""identification tests"""
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.tests import FlowTestCase
from authentik.lib.generators import generate_key
from authentik.sources.oauth.models import OAuthSource
from authentik.stages.identification.models import IdentificationStage, UserFields
@ -13,7 +12,7 @@ from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.models import PasswordStage
class TestIdentificationStage(APITestCase):
class TestIdentificationStage(FlowTestCase):
"""Identification tests"""
def setUp(self):
@ -56,14 +55,7 @@ class TestIdentificationStage(APITestCase):
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
response = self.client.post(url, form_data)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
def test_valid_with_password(self):
"""Test with valid email and password in single step"""
@ -74,14 +66,7 @@ class TestIdentificationStage(APITestCase):
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
response = self.client.post(url, form_data)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
def test_invalid_with_password(self):
"""Test with valid email and invalid password in single step"""
@ -95,37 +80,28 @@ class TestIdentificationStage(APITestCase):
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
response = self.client.post(url, form_data)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-identification",
"password_fields": True,
"primary_action": "Log in",
"response_errors": {
"non_field_errors": [
{"code": "invalid", "string": "Failed to " "authenticate."}
]
},
"flow_info": {
"background": self.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
"sources": [
{
"challenge": {
"component": "xak-flow-redirect",
"to": "/source/oauth/login/test/",
"type": ChallengeTypes.REDIRECT.value,
},
"icon_url": "/static/authentik/sources/default.svg",
"name": "test",
}
],
"show_source_labels": False,
"user_fields": ["email"],
self.assertStageResponse(
response,
self.flow,
component="ak-stage-identification",
password_fields=True,
primary_action="Log in",
response_errors={
"non_field_errors": [{"code": "invalid", "string": "Failed to " "authenticate."}]
},
sources=[
{
"challenge": {
"component": "xak-flow-redirect",
"to": "/source/oauth/login/test/",
"type": ChallengeTypes.REDIRECT.value,
},
"icon_url": "/static/authentik/sources/default.svg",
"name": "test",
}
],
show_source_labels=False,
user_fields=["email"],
)
def test_invalid_with_username(self):
@ -147,37 +123,28 @@ class TestIdentificationStage(APITestCase):
form_data,
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-identification",
"password_fields": False,
"primary_action": "Log in",
"response_errors": {
"non_field_errors": [
{"code": "invalid", "string": "Failed to " "authenticate."}
]
},
"show_source_labels": False,
"flow_info": {
"background": self.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
"sources": [
{
"challenge": {
"component": "xak-flow-redirect",
"to": "/source/oauth/login/test/",
"type": ChallengeTypes.REDIRECT.value,
},
"icon_url": "/static/authentik/sources/default.svg",
"name": "test",
}
],
"user_fields": [],
self.assertStageResponse(
response,
self.flow,
component="ak-stage-identification",
password_fields=False,
primary_action="Log in",
response_errors={
"non_field_errors": [{"code": "invalid", "string": "Failed to " "authenticate."}]
},
show_source_labels=False,
sources=[
{
"challenge": {
"component": "xak-flow-redirect",
"to": "/source/oauth/login/test/",
"type": ChallengeTypes.REDIRECT.value,
},
"icon_url": "/static/authentik/sources/default.svg",
"name": "test",
}
],
user_fields=[],
)
def test_invalid_with_invalid_email(self):
@ -209,36 +176,29 @@ class TestIdentificationStage(APITestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-identification",
"user_fields": ["email"],
"password_fields": False,
"enroll_url": reverse(
"authentik_core:if-flow",
kwargs={"flow_slug": "unique-enrollment-string"},
),
"show_source_labels": False,
"primary_action": "Log in",
"flow_info": {
"background": flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": self.flow.title,
},
"sources": [
{
"icon_url": "/static/authentik/sources/default.svg",
"name": "test",
"challenge": {
"component": "xak-flow-redirect",
"to": "/source/oauth/login/test/",
"type": ChallengeTypes.REDIRECT.value,
},
}
],
},
self.assertStageResponse(
response,
self.flow,
component="ak-stage-identification",
user_fields=["email"],
password_fields=False,
enroll_url=reverse(
"authentik_core:if-flow",
kwargs={"flow_slug": "unique-enrollment-string"},
),
show_source_labels=False,
primary_action="Log in",
sources=[
{
"icon_url": "/static/authentik/sources/default.svg",
"name": "test",
"challenge": {
"component": "xak-flow-redirect",
"to": "/source/oauth/login/test/",
"type": ChallengeTypes.REDIRECT.value,
},
}
],
)
def test_recovery_flow(self):
@ -259,34 +219,27 @@ class TestIdentificationStage(APITestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-identification",
"user_fields": ["email"],
"password_fields": False,
"recovery_url": reverse(
"authentik_core:if-flow",
kwargs={"flow_slug": "unique-recovery-string"},
),
"show_source_labels": False,
"primary_action": "Log in",
"flow_info": {
"background": flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": self.flow.title,
},
"sources": [
{
"challenge": {
"component": "xak-flow-redirect",
"to": "/source/oauth/login/test/",
"type": ChallengeTypes.REDIRECT.value,
},
"icon_url": "/static/authentik/sources/default.svg",
"name": "test",
}
],
},
self.assertStageResponse(
response,
self.flow,
component="ak-stage-identification",
user_fields=["email"],
password_fields=False,
recovery_url=reverse(
"authentik_core:if-flow",
kwargs={"flow_slug": "unique-recovery-string"},
),
show_source_labels=False,
primary_action="Log in",
sources=[
{
"challenge": {
"component": "xak-flow-redirect",
"to": "/source/oauth/login/test/",
"type": ChallengeTypes.REDIRECT.value,
},
"icon_url": "/static/authentik/sources/default.svg",
"name": "test",
}
],
)

View File

@ -2,17 +2,16 @@
from unittest.mock import MagicMock, patch
from django.urls import reverse
from django.utils.encoding import force_str
from django.utils.http import urlencode
from guardian.shortcuts import get_anonymous_user
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.core.tests.utils import create_test_admin_user
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.markers import StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from authentik.flows.tests import FlowTestCase
from authentik.flows.tests.test_executor import TO_STAGE_RESPONSE_MOCK
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.stages.invitation.models import Invitation, InvitationStage
@ -25,7 +24,7 @@ from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
class TestUserLoginStage(APITestCase):
class TestUserLoginStage(FlowTestCase):
"""Login tests"""
def setUp(self):
@ -57,18 +56,10 @@ class TestUserLoginStage(APITestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "ak-stage-access-denied",
"error_message": None,
"type": ChallengeTypes.NATIVE.value,
"flow_info": {
"background": self.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
},
self.assertStageResponse(
response,
flow=self.flow,
component="ak-stage-access-denied",
)
def test_without_invitation_continue(self):
@ -87,14 +78,7 @@ class TestUserLoginStage(APITestCase):
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
self.stage.continue_flow_without_invitation = False
self.stage.save()
@ -119,14 +103,7 @@ class TestUserLoginStage(APITestCase):
self.assertEqual(plan.context[PLAN_CONTEXT_PROMPT], data)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
def test_with_invitation_prompt_data(self):
"""Test with invitation, check data in session"""
@ -152,14 +129,7 @@ class TestUserLoginStage(APITestCase):
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
self.assertFalse(Invitation.objects.filter(pk=invite.pk))

View File

@ -3,14 +3,12 @@ from unittest.mock import MagicMock, patch
from django.core.exceptions import PermissionDenied
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.markers import StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from authentik.flows.tests import FlowTestCase
from authentik.flows.tests.test_executor import TO_STAGE_RESPONSE_MOCK
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.lib.generators import generate_key
@ -20,7 +18,7 @@ from authentik.stages.password.models import PasswordStage
MOCK_BACKEND_AUTHENTICATE = MagicMock(side_effect=PermissionDenied("test"))
class TestPasswordStage(APITestCase):
class TestPasswordStage(FlowTestCase):
"""Password tests"""
def setUp(self):
@ -56,18 +54,11 @@ class TestPasswordStage(APITestCase):
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "ak-stage-access-denied",
"error_message": None,
"type": ChallengeTypes.NATIVE.value,
"flow_info": {
"background": self.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
},
self.assertStageResponse(
response,
self.flow,
component="ak-stage-access-denied",
error_message="Unknown error",
)
def test_recovery_flow_link(self):
@ -83,7 +74,7 @@ class TestPasswordStage(APITestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
)
self.assertEqual(response.status_code, 200)
self.assertIn(flow.slug, force_str(response.content))
self.assertIn(flow.slug, response.content.decode())
def test_valid_password(self):
"""Test with a valid pending user and valid password"""
@ -100,14 +91,7 @@ class TestPasswordStage(APITestCase):
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
def test_invalid_password(self):
"""Test with a valid pending user and invalid password"""
@ -176,16 +160,9 @@ class TestPasswordStage(APITestCase):
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "ak-stage-access-denied",
"error_message": None,
"flow_info": {
"background": self.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
"type": ChallengeTypes.NATIVE.value,
},
self.assertStageResponse(
response,
self.flow,
component="ak-stage-access-denied",
error_message="Unknown error",
)

View File

@ -2,23 +2,21 @@
from unittest.mock import MagicMock, patch
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.exceptions import ErrorDetail
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.core.tests.utils import create_test_admin_user
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.markers import StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.planner import FlowPlan
from authentik.flows.tests import FlowTestCase
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.policies.expression.models import ExpressionPolicy
from authentik.stages.prompt.models import FieldTypes, Prompt, PromptStage
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT, PromptChallengeResponse
class TestPromptStage(APITestCase):
class TestPromptStage(FlowTestCase):
"""Prompt tests"""
def setUp(self):
@ -123,9 +121,9 @@ class TestPromptStage(APITestCase):
)
self.assertEqual(response.status_code, 200)
for prompt in self.stage.fields.all():
self.assertIn(prompt.field_key, force_str(response.content))
self.assertIn(prompt.label, force_str(response.content))
self.assertIn(prompt.placeholder, force_str(response.content))
self.assertIn(prompt.field_key, response.content.decode())
self.assertIn(prompt.label, response.content.decode())
self.assertIn(prompt.placeholder, response.content.decode())
def test_valid_challenge_with_policy(self) -> PromptChallengeResponse:
"""Test challenge_response validation"""
@ -171,14 +169,7 @@ class TestPromptStage(APITestCase):
challenge_response.validated_data,
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
# Check that valid data has been saved
session = self.client.session

View File

@ -2,20 +2,18 @@
from unittest.mock import patch
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.markers import StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from authentik.flows.tests import FlowTestCase
from authentik.flows.tests.test_executor import TO_STAGE_RESPONSE_MOCK
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.stages.user_delete.models import UserDeleteStage
class TestUserDeleteStage(APITestCase):
class TestUserDeleteStage(FlowTestCase):
"""Delete tests"""
def setUp(self):
@ -46,19 +44,7 @@ class TestUserDeleteStage(APITestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "ak-stage-access-denied",
"error_message": None,
"type": ChallengeTypes.NATIVE.value,
"flow_info": {
"background": self.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
},
)
self.assertStageResponse(response, self.flow, component="ak-stage-access-denied")
def test_user_delete_get(self):
"""Test Form render"""
@ -72,14 +58,7 @@ class TestUserDeleteStage(APITestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
self.assertFalse(User.objects.filter(username=self.username).exists())
@ -95,13 +74,6 @@ class TestUserDeleteStage(APITestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
self.assertFalse(User.objects.filter(username=self.username).exists())

View File

@ -3,20 +3,18 @@ from time import sleep
from unittest.mock import patch
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.markers import StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from authentik.flows.tests import FlowTestCase
from authentik.flows.tests.test_executor import TO_STAGE_RESPONSE_MOCK
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.stages.user_login.models import UserLoginStage
class TestUserLoginStage(APITestCase):
class TestUserLoginStage(FlowTestCase):
"""Login tests"""
def setUp(self):
@ -44,14 +42,7 @@ class TestUserLoginStage(APITestCase):
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
def test_valid_post(self):
"""Test with a valid pending user and backend"""
@ -66,14 +57,7 @@ class TestUserLoginStage(APITestCase):
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
def test_expiry(self):
"""Test with expiry"""
@ -89,14 +73,7 @@ class TestUserLoginStage(APITestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
self.assertNotEqual(list(self.client.session.keys()), [])
sleep(3)
self.client.session.clear_expired()
@ -118,18 +95,10 @@ class TestUserLoginStage(APITestCase):
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "ak-stage-access-denied",
"error_message": None,
"type": ChallengeTypes.NATIVE.value,
"flow_info": {
"background": self.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
},
self.assertStageResponse(
response,
self.flow,
component="ak-stage-access-denied",
)
def test_inactive_account(self):
@ -147,13 +116,6 @@ class TestUserLoginStage(APITestCase):
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
response = self.client.get(reverse("authentik_api:application-list"))
self.assertEqual(response.status_code, 403)

View File

@ -1,20 +1,18 @@
"""logout tests"""
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.markers import StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from authentik.flows.tests import FlowTestCase
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
from authentik.stages.user_logout.models import UserLogoutStage
class TestUserLogoutStage(APITestCase):
class TestUserLogoutStage(FlowTestCase):
"""Logout tests"""
def setUp(self):
@ -44,15 +42,7 @@ class TestUserLogoutStage(APITestCase):
# pylint: disable=no-member
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
# pylint: disable=no-member
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
def test_valid_post(self):
"""Test with a valid pending user and backend"""
@ -69,12 +59,4 @@ class TestUserLogoutStage(APITestCase):
# pylint: disable=no-member
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
# pylint: disable=no-member
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))

View File

@ -4,23 +4,21 @@ from random import SystemRandom
from unittest.mock import patch
from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase
from authentik.core.models import USER_ATTRIBUTE_SOURCES, Group, Source, User, UserSourceConnection
from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION
from authentik.core.tests.utils import create_test_admin_user
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.markers import StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from authentik.flows.tests import FlowTestCase
from authentik.flows.tests.test_executor import TO_STAGE_RESPONSE_MOCK
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
from authentik.stages.user_write.models import UserWriteStage
class TestUserWriteStage(APITestCase):
class TestUserWriteStage(FlowTestCase):
"""Write tests"""
def setUp(self):
@ -60,14 +58,7 @@ class TestUserWriteStage(APITestCase):
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
user_qs = User.objects.filter(username=plan.context[PLAN_CONTEXT_PROMPT]["username"])
self.assertTrue(user_qs.exists())
self.assertTrue(user_qs.first().check_password(password))
@ -98,14 +89,7 @@ class TestUserWriteStage(APITestCase):
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
user_qs = User.objects.filter(username=plan.context[PLAN_CONTEXT_PROMPT]["username"])
self.assertTrue(user_qs.exists())
self.assertTrue(user_qs.first().check_password(new_password))
@ -128,18 +112,10 @@ class TestUserWriteStage(APITestCase):
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "ak-stage-access-denied",
"error_message": None,
"type": ChallengeTypes.NATIVE.value,
"flow_info": {
"background": self.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
},
self.assertStageResponse(
response,
self.flow,
component="ak-stage-access-denied",
)
@patch(
@ -163,18 +139,10 @@ class TestUserWriteStage(APITestCase):
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "ak-stage-access-denied",
"error_message": None,
"type": ChallengeTypes.NATIVE.value,
"flow_info": {
"background": self.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
},
self.assertStageResponse(
response,
self.flow,
component="ak-stage-access-denied",
)
@patch(
@ -199,16 +167,8 @@ class TestUserWriteStage(APITestCase):
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"component": "ak-stage-access-denied",
"error_message": None,
"type": ChallengeTypes.NATIVE.value,
"flow_info": {
"background": self.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
},
self.assertStageResponse(
response,
self.flow,
component="ak-stage-access-denied",
)

View File

@ -2,7 +2,6 @@
from django.test import TestCase
from django.test.client import RequestFactory
from django.urls import reverse
from django.utils.encoding import force_str
from authentik.core.tests.utils import create_test_tenant
from authentik.events.models import Event, EventAction
@ -18,7 +17,7 @@ class TestTenants(TestCase):
"""Test Current tenant API"""
tenant = create_test_tenant()
self.assertJSONEqual(
force_str(self.client.get(reverse("authentik_api:tenant-current")).content),
self.client.get(reverse("authentik_api:tenant-current")).content.decode(),
{
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
"branding_favicon": "/static/dist/assets/icons/icon.png",
@ -33,11 +32,9 @@ class TestTenants(TestCase):
Tenant.objects.all().delete()
Tenant.objects.create(domain="bar.baz", branding_title="custom")
self.assertJSONEqual(
force_str(
self.client.get(
reverse("authentik_api:tenant-current"), HTTP_HOST="foo.bar.baz"
).content
),
self.client.get(
reverse("authentik_api:tenant-current"), HTTP_HOST="foo.bar.baz"
).content.decode(),
{
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
"branding_favicon": "/static/dist/assets/icons/icon.png",
@ -51,7 +48,7 @@ class TestTenants(TestCase):
"""Test fallback tenant"""
Tenant.objects.all().delete()
self.assertJSONEqual(
force_str(self.client.get(reverse("authentik_api:tenant-current")).content),
self.client.get(reverse("authentik_api:tenant-current")).content.decode(),
{
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
"branding_favicon": "/static/dist/assets/icons/icon.png",

View File

@ -19036,9 +19036,15 @@ components:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
pending_user:
type: string
pending_user_avatar:
type: string
error_message:
type: string
required:
- pending_user
- pending_user_avatar
- type
App:
type: object