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""" """Test Applications API"""
from django.urls import reverse from django.urls import reverse
from django.utils.encoding import force_str
from rest_framework.test import APITestCase from rest_framework.test import APITestCase
from authentik.core.models import Application from authentik.core.models import Application
@ -32,7 +31,7 @@ class TestApplicationsAPI(APITestCase):
) )
) )
self.assertEqual(response.status_code, 200) 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( response = self.client.get(
reverse( reverse(
"authentik_api:application-check-access", "authentik_api:application-check-access",
@ -40,14 +39,14 @@ class TestApplicationsAPI(APITestCase):
) )
) )
self.assertEqual(response.status_code, 200) 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): def test_list(self):
"""Test list operation without superuser_full_list""" """Test list operation without superuser_full_list"""
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.get(reverse("authentik_api:application-list")) response = self.client.get(reverse("authentik_api:application-list"))
self.assertJSONEqual( self.assertJSONEqual(
force_str(response.content), response.content.decode(),
{ {
"pagination": { "pagination": {
"next": 0, "next": 0,
@ -83,7 +82,7 @@ class TestApplicationsAPI(APITestCase):
reverse("authentik_api:application-list") + "?superuser_full_list=true" reverse("authentik_api:application-list") + "?superuser_full_list=true"
) )
self.assertJSONEqual( self.assertJSONEqual(
force_str(response.content), response.content.decode(),
{ {
"pagination": { "pagination": {
"next": 0, "next": 0,

View File

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

View File

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

View File

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

View File

@ -3,7 +3,6 @@ import json
from django.test import RequestFactory from django.test import RequestFactory
from django.urls.base import reverse from django.urls.base import reverse
from django.utils.encoding import force_str
from authentik.core.models import Application from authentik.core.models import Application
from authentik.core.tests.utils import create_test_cert, create_test_flow from authentik.core.tests.utils import create_test_cert, create_test_flow
@ -31,7 +30,7 @@ class TestJWKS(OAuthTestCase):
response = self.client.get( response = self.client.get(
reverse("authentik_providers_oauth2:jwks", kwargs={"application_slug": app.slug}) 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) self.assertEqual(len(body["keys"]), 1)
def test_hs256(self): def test_hs256(self):
@ -46,4 +45,4 @@ class TestJWKS(OAuthTestCase):
response = self.client.get( response = self.client.get(
reverse("authentik_providers_oauth2:jwks", kwargs={"application_slug": app.slug}) 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.test import RequestFactory
from django.urls import reverse from django.urls import reverse
from django.utils.encoding import force_str
from authentik.core.models import Application from authentik.core.models import Application
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow 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() new_token: RefreshToken = RefreshToken.objects.filter(user=user).first()
self.assertJSONEqual( self.assertJSONEqual(
force_str(response.content), response.content.decode(),
{ {
"access_token": new_token.access_token, "access_token": new_token.access_token,
"refresh_token": new_token.refresh_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-Credentials"], "true")
self.assertEqual(response["Access-Control-Allow-Origin"], "http://local.invalid") self.assertEqual(response["Access-Control-Allow-Origin"], "http://local.invalid")
self.assertJSONEqual( self.assertJSONEqual(
force_str(response.content), response.content.decode(),
{ {
"access_token": new_token.access_token, "access_token": new_token.access_token,
"refresh_token": new_token.refresh_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-Credentials", response)
self.assertNotIn("Access-Control-Allow-Origin", response) self.assertNotIn("Access-Control-Allow-Origin", response)
self.assertJSONEqual( self.assertJSONEqual(
force_str(response.content), response.content.decode(),
{ {
"access_token": new_token.access_token, "access_token": new_token.access_token,
"refresh_token": new_token.refresh_token, "refresh_token": new_token.refresh_token,

View File

@ -3,7 +3,6 @@ import json
from dataclasses import asdict from dataclasses import asdict
from django.urls import reverse from django.urls import reverse
from django.utils.encoding import force_str
from authentik.core.models import Application from authentik.core.models import Application
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow 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}", HTTP_AUTHORIZATION=f"Bearer {self.token.access_token}",
) )
self.assertJSONEqual( self.assertJSONEqual(
force_str(res.content), res.content.decode(),
{ {
"name": self.user.name, "name": self.user.name,
"given_name": self.user.name, "given_name": self.user.name,
@ -77,7 +76,7 @@ class TestUserinfo(OAuthTestCase):
HTTP_AUTHORIZATION=f"Bearer {self.token.access_token}", HTTP_AUTHORIZATION=f"Bearer {self.token.access_token}",
) )
self.assertJSONEqual( self.assertJSONEqual(
force_str(res.content), res.content.decode(),
{ {
"name": self.user.name, "name": self.user.name,
"given_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.test.client import RequestFactory
from django.urls.base import reverse from django.urls.base import reverse
from django.utils.encoding import force_str
from django_otp.plugins.otp_totp.models import TOTPDevice from django_otp.plugins.otp_totp.models import TOTPDevice
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
from rest_framework.test import APITestCase
from webauthn.helpers import bytes_to_base64url from webauthn.helpers import bytes_to_base64url
from authentik.core.tests.utils import create_test_admin_user 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.models import Flow, FlowStageBinding, NotConfiguredAction
from authentik.flows.tests import FlowTestCase
from authentik.lib.generators import generate_id, generate_key from authentik.lib.generators import generate_id, generate_key
from authentik.lib.tests.utils import get_request from authentik.lib.tests.utils import get_request
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice 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 from authentik.stages.identification.models import IdentificationStage, UserFields
class AuthenticatorValidateStageTests(APITestCase): class AuthenticatorValidateStageTests(FlowTestCase):
"""Test validator stage""" """Test validator stage"""
def setUp(self) -> None: def setUp(self) -> None:
@ -61,22 +59,15 @@ class AuthenticatorValidateStageTests(APITestCase):
follow=True, follow=True,
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertStageResponse(
force_str(response.content), response,
{ flow,
"type": ChallengeTypes.NATIVE.value, component="ak-stage-identification",
"component": "ak-stage-identification", password_fields=False,
"password_fields": False, primary_action="Log in",
"primary_action": "Log in", user_fields=["username"],
"flow_info": { sources=[],
"background": flow.background_url, show_source_labels=False,
"cancel_url": reverse("authentik_flows:cancel"),
"title": flow.title,
},
"user_fields": ["username"],
"sources": [],
"show_source_labels": False,
},
) )
def test_stage_validation(self): def test_stage_validation(self):

View File

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

View File

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

View File

@ -1,18 +1,16 @@
"""deny tests""" """deny tests"""
from django.urls import reverse 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.core.models import User
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.markers import StageMarker from authentik.flows.markers import StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.planner import FlowPlan from authentik.flows.planner import FlowPlan
from authentik.flows.tests import FlowTestCase
from authentik.flows.views.executor import SESSION_KEY_PLAN from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.stages.deny.models import DenyStage from authentik.stages.deny.models import DenyStage
class TestUserDenyStage(APITestCase): class TestUserDenyStage(FlowTestCase):
"""Deny tests""" """Deny tests"""
def setUp(self): def setUp(self):
@ -39,19 +37,7 @@ class TestUserDenyStage(APITestCase):
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertStageResponse(response, self.flow, component="ak-stage-access-denied")
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,
},
)
def test_valid_post(self): def test_valid_post(self):
"""Test with a valid pending user and backend""" """Test with a valid pending user and backend"""
@ -65,16 +51,4 @@ class TestUserDenyStage(APITestCase):
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertStageResponse(response, self.flow, component="ak-stage-access-denied")
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,
},
)

View File

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

View File

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

View File

@ -1,11 +1,10 @@
"""identification tests""" """identification tests"""
from django.urls import reverse 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.core.models import User
from authentik.flows.challenge import ChallengeTypes from authentik.flows.challenge import ChallengeTypes
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.tests import FlowTestCase
from authentik.lib.generators import generate_key from authentik.lib.generators import generate_key
from authentik.sources.oauth.models import OAuthSource from authentik.sources.oauth.models import OAuthSource
from authentik.stages.identification.models import IdentificationStage, UserFields 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 from authentik.stages.password.models import PasswordStage
class TestIdentificationStage(APITestCase): class TestIdentificationStage(FlowTestCase):
"""Identification tests""" """Identification tests"""
def setUp(self): def setUp(self):
@ -56,14 +55,7 @@ class TestIdentificationStage(APITestCase):
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}) url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
response = self.client.post(url, form_data) response = self.client.post(url, form_data)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
def test_valid_with_password(self): def test_valid_with_password(self):
"""Test with valid email and password in single step""" """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}) url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
response = self.client.post(url, form_data) response = self.client.post(url, form_data)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
def test_invalid_with_password(self): def test_invalid_with_password(self):
"""Test with valid email and invalid password in single step""" """Test with valid email and invalid password in single step"""
@ -95,24 +80,16 @@ class TestIdentificationStage(APITestCase):
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}) url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
response = self.client.post(url, form_data) response = self.client.post(url, form_data)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertStageResponse(
force_str(response.content), response,
{ self.flow,
"type": ChallengeTypes.NATIVE.value, component="ak-stage-identification",
"component": "ak-stage-identification", password_fields=True,
"password_fields": True, primary_action="Log in",
"primary_action": "Log in", response_errors={
"response_errors": { "non_field_errors": [{"code": "invalid", "string": "Failed to " "authenticate."}]
"non_field_errors": [
{"code": "invalid", "string": "Failed to " "authenticate."}
]
}, },
"flow_info": { sources=[
"background": self.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
"sources": [
{ {
"challenge": { "challenge": {
"component": "xak-flow-redirect", "component": "xak-flow-redirect",
@ -123,9 +100,8 @@ class TestIdentificationStage(APITestCase):
"name": "test", "name": "test",
} }
], ],
"show_source_labels": False, show_source_labels=False,
"user_fields": ["email"], user_fields=["email"],
},
) )
def test_invalid_with_username(self): def test_invalid_with_username(self):
@ -147,25 +123,17 @@ class TestIdentificationStage(APITestCase):
form_data, form_data,
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertStageResponse(
force_str(response.content), response,
{ self.flow,
"type": ChallengeTypes.NATIVE.value, component="ak-stage-identification",
"component": "ak-stage-identification", password_fields=False,
"password_fields": False, primary_action="Log in",
"primary_action": "Log in", response_errors={
"response_errors": { "non_field_errors": [{"code": "invalid", "string": "Failed to " "authenticate."}]
"non_field_errors": [
{"code": "invalid", "string": "Failed to " "authenticate."}
]
}, },
"show_source_labels": False, show_source_labels=False,
"flow_info": { sources=[
"background": self.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
"sources": [
{ {
"challenge": { "challenge": {
"component": "xak-flow-redirect", "component": "xak-flow-redirect",
@ -176,8 +144,7 @@ class TestIdentificationStage(APITestCase):
"name": "test", "name": "test",
} }
], ],
"user_fields": [], user_fields=[],
},
) )
def test_invalid_with_invalid_email(self): def test_invalid_with_invalid_email(self):
@ -209,25 +176,19 @@ class TestIdentificationStage(APITestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertStageResponse(
force_str(response.content), response,
{ self.flow,
"type": ChallengeTypes.NATIVE.value, component="ak-stage-identification",
"component": "ak-stage-identification", user_fields=["email"],
"user_fields": ["email"], password_fields=False,
"password_fields": False, enroll_url=reverse(
"enroll_url": reverse(
"authentik_core:if-flow", "authentik_core:if-flow",
kwargs={"flow_slug": "unique-enrollment-string"}, kwargs={"flow_slug": "unique-enrollment-string"},
), ),
"show_source_labels": False, show_source_labels=False,
"primary_action": "Log in", primary_action="Log in",
"flow_info": { sources=[
"background": flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": self.flow.title,
},
"sources": [
{ {
"icon_url": "/static/authentik/sources/default.svg", "icon_url": "/static/authentik/sources/default.svg",
"name": "test", "name": "test",
@ -238,7 +199,6 @@ class TestIdentificationStage(APITestCase):
}, },
} }
], ],
},
) )
def test_recovery_flow(self): def test_recovery_flow(self):
@ -259,25 +219,19 @@ class TestIdentificationStage(APITestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertStageResponse(
force_str(response.content), response,
{ self.flow,
"type": ChallengeTypes.NATIVE.value, component="ak-stage-identification",
"component": "ak-stage-identification", user_fields=["email"],
"user_fields": ["email"], password_fields=False,
"password_fields": False, recovery_url=reverse(
"recovery_url": reverse(
"authentik_core:if-flow", "authentik_core:if-flow",
kwargs={"flow_slug": "unique-recovery-string"}, kwargs={"flow_slug": "unique-recovery-string"},
), ),
"show_source_labels": False, show_source_labels=False,
"primary_action": "Log in", primary_action="Log in",
"flow_info": { sources=[
"background": flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": self.flow.title,
},
"sources": [
{ {
"challenge": { "challenge": {
"component": "xak-flow-redirect", "component": "xak-flow-redirect",
@ -288,5 +242,4 @@ class TestIdentificationStage(APITestCase):
"name": "test", "name": "test",
} }
], ],
},
) )

View File

@ -2,17 +2,16 @@
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from django.urls import reverse from django.urls import reverse
from django.utils.encoding import force_str
from django.utils.http import urlencode from django.utils.http import urlencode
from guardian.shortcuts import get_anonymous_user from guardian.shortcuts import get_anonymous_user
from rest_framework.test import APITestCase from rest_framework.test import APITestCase
from authentik.core.models import User from authentik.core.models import User
from authentik.core.tests.utils import create_test_admin_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.markers import StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan 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.tests.test_executor import TO_STAGE_RESPONSE_MOCK
from authentik.flows.views.executor import SESSION_KEY_PLAN from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.stages.invitation.models import Invitation, InvitationStage 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 from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
class TestUserLoginStage(APITestCase): class TestUserLoginStage(FlowTestCase):
"""Login tests""" """Login tests"""
def setUp(self): def setUp(self):
@ -57,18 +56,10 @@ class TestUserLoginStage(APITestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}) reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertStageResponse(
force_str(response.content), response,
{ flow=self.flow,
"component": "ak-stage-access-denied", 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": "",
},
},
) )
def test_without_invitation_continue(self): def test_without_invitation_continue(self):
@ -87,14 +78,7 @@ class TestUserLoginStage(APITestCase):
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.stage.continue_flow_without_invitation = False self.stage.continue_flow_without_invitation = False
self.stage.save() self.stage.save()
@ -119,14 +103,7 @@ class TestUserLoginStage(APITestCase):
self.assertEqual(plan.context[PLAN_CONTEXT_PROMPT], data) self.assertEqual(plan.context[PLAN_CONTEXT_PROMPT], data)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
def test_with_invitation_prompt_data(self): def test_with_invitation_prompt_data(self):
"""Test with invitation, check data in session""" """Test with invitation, check data in session"""
@ -152,14 +129,7 @@ class TestUserLoginStage(APITestCase):
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertFalse(Invitation.objects.filter(pk=invite.pk)) 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.core.exceptions import PermissionDenied
from django.urls import reverse 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.core.models import User
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.markers import StageMarker from authentik.flows.markers import StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan 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.tests.test_executor import TO_STAGE_RESPONSE_MOCK
from authentik.flows.views.executor import SESSION_KEY_PLAN from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.lib.generators import generate_key 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")) MOCK_BACKEND_AUTHENTICATE = MagicMock(side_effect=PermissionDenied("test"))
class TestPasswordStage(APITestCase): class TestPasswordStage(FlowTestCase):
"""Password tests""" """Password tests"""
def setUp(self): def setUp(self):
@ -56,18 +54,11 @@ class TestPasswordStage(APITestCase):
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertStageResponse(
force_str(response.content), response,
{ self.flow,
"component": "ak-stage-access-denied", component="ak-stage-access-denied",
"error_message": None, error_message="Unknown error",
"type": ChallengeTypes.NATIVE.value,
"flow_info": {
"background": self.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
},
) )
def test_recovery_flow_link(self): def test_recovery_flow_link(self):
@ -83,7 +74,7 @@ class TestPasswordStage(APITestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
) )
self.assertEqual(response.status_code, 200) 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): def test_valid_password(self):
"""Test with a valid pending user and valid password""" """Test with a valid pending user and valid password"""
@ -100,14 +91,7 @@ class TestPasswordStage(APITestCase):
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
def test_invalid_password(self): def test_invalid_password(self):
"""Test with a valid pending user and invalid password""" """Test with a valid pending user and invalid password"""
@ -176,16 +160,9 @@ class TestPasswordStage(APITestCase):
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertStageResponse(
force_str(response.content), response,
{ self.flow,
"component": "ak-stage-access-denied", component="ak-stage-access-denied",
"error_message": None, error_message="Unknown error",
"flow_info": {
"background": self.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
"type": ChallengeTypes.NATIVE.value,
},
) )

View File

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

View File

@ -2,20 +2,18 @@
from unittest.mock import patch from unittest.mock import patch
from django.urls import reverse 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.core.models import User
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.markers import StageMarker from authentik.flows.markers import StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan 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.tests.test_executor import TO_STAGE_RESPONSE_MOCK
from authentik.flows.views.executor import SESSION_KEY_PLAN from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.stages.user_delete.models import UserDeleteStage from authentik.stages.user_delete.models import UserDeleteStage
class TestUserDeleteStage(APITestCase): class TestUserDeleteStage(FlowTestCase):
"""Delete tests""" """Delete tests"""
def setUp(self): def setUp(self):
@ -46,19 +44,7 @@ class TestUserDeleteStage(APITestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}) reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertStageResponse(response, self.flow, component="ak-stage-access-denied")
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": "",
},
},
)
def test_user_delete_get(self): def test_user_delete_get(self):
"""Test Form render""" """Test Form render"""
@ -72,14 +58,7 @@ class TestUserDeleteStage(APITestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}) reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertFalse(User.objects.filter(username=self.username).exists()) 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}) reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertFalse(User.objects.filter(username=self.username).exists()) 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 unittest.mock import patch
from django.urls import reverse 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.core.models import User
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.markers import StageMarker from authentik.flows.markers import StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan 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.tests.test_executor import TO_STAGE_RESPONSE_MOCK
from authentik.flows.views.executor import SESSION_KEY_PLAN from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.stages.user_login.models import UserLoginStage from authentik.stages.user_login.models import UserLoginStage
class TestUserLoginStage(APITestCase): class TestUserLoginStage(FlowTestCase):
"""Login tests""" """Login tests"""
def setUp(self): def setUp(self):
@ -44,14 +42,7 @@ class TestUserLoginStage(APITestCase):
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
def test_valid_post(self): def test_valid_post(self):
"""Test with a valid pending user and backend""" """Test with a valid pending user and backend"""
@ -66,14 +57,7 @@ class TestUserLoginStage(APITestCase):
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
def test_expiry(self): def test_expiry(self):
"""Test with expiry""" """Test with expiry"""
@ -89,14 +73,7 @@ class TestUserLoginStage(APITestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}) reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
self.assertNotEqual(list(self.client.session.keys()), []) self.assertNotEqual(list(self.client.session.keys()), [])
sleep(3) sleep(3)
self.client.session.clear_expired() self.client.session.clear_expired()
@ -118,18 +95,10 @@ class TestUserLoginStage(APITestCase):
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertStageResponse(
force_str(response.content), response,
{ self.flow,
"component": "ak-stage-access-denied", 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": "",
},
},
) )
def test_inactive_account(self): def test_inactive_account(self):
@ -147,13 +116,6 @@ class TestUserLoginStage(APITestCase):
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
force_str(response.content),
{
"component": "xak-flow-redirect",
"to": reverse("authentik_core:root-redirect"),
"type": ChallengeTypes.REDIRECT.value,
},
)
response = self.client.get(reverse("authentik_api:application-list")) response = self.client.get(reverse("authentik_api:application-list"))
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)

View File

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

View File

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

View File

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

View File

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