flows: migrate access denied message to webcompoennts
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
c6c4636b9b
commit
cfe7bc8155
|
@ -75,6 +75,12 @@ class WithUserInfoChallenge(Challenge):
|
||||||
pending_user_avatar = CharField()
|
pending_user_avatar = CharField()
|
||||||
|
|
||||||
|
|
||||||
|
class AccessDeniedChallenge(Challenge):
|
||||||
|
"""Challenge when a flow's active stage calls `stage_invalid()`."""
|
||||||
|
|
||||||
|
error_message = CharField(required=False)
|
||||||
|
|
||||||
|
|
||||||
class PermissionSerializer(Serializer):
|
class PermissionSerializer(Serializer):
|
||||||
"""Permission used for consent"""
|
"""Permission used for consent"""
|
||||||
|
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
{% extends 'login/base.html' %}
|
|
||||||
|
|
||||||
{% load static %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load authentik_utils %}
|
|
||||||
|
|
||||||
{% block card_title %}
|
|
||||||
{% trans 'Permission denied' %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
{% trans 'Permission denied' %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block card %}
|
|
||||||
<form method="POST" class="pf-c-form">
|
|
||||||
{% csrf_token %}
|
|
||||||
{% include 'partials/form.html' %}
|
|
||||||
<div class="pf-c-form__group">
|
|
||||||
<p>
|
|
||||||
<i class="pf-icon pf-icon-error-circle-o"></i>
|
|
||||||
{% trans 'Request has been denied.' %}
|
|
||||||
</p>
|
|
||||||
{% if error %}
|
|
||||||
<hr>
|
|
||||||
<p>
|
|
||||||
{{ error }}
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
{% if policy_result %}
|
|
||||||
<hr>
|
|
||||||
<em>
|
|
||||||
{% trans 'Explanation:' %}
|
|
||||||
</em>
|
|
||||||
<ul class="pf-c-list">
|
|
||||||
{% for source_result in policy_result.source_results %}
|
|
||||||
<li>
|
|
||||||
{% blocktrans with name=source_result.source_policy.name result=source_result.passing %}
|
|
||||||
Policy '{{ name }}' returned result '{{ result }}'
|
|
||||||
{% endblocktrans %}
|
|
||||||
{% if source_result.messages %}
|
|
||||||
<ul class="pf-c-list">
|
|
||||||
{% for message in source_result.messages %}
|
|
||||||
<li>{{ message }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% if 'back' in request.GET %}
|
|
||||||
<a href="{% back %}" class="btn btn-primary btn-block btn-lg">{% trans 'Back' %}</a>
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
|
@ -17,7 +17,6 @@ from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, StageVie
|
||||||
from authentik.flows.views import NEXT_ARG_NAME, SESSION_KEY_PLAN, FlowExecutorView
|
from authentik.flows.views 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
|
||||||
from authentik.policies.http import AccessDeniedResponse
|
|
||||||
from authentik.policies.models import PolicyBinding
|
from authentik.policies.models import PolicyBinding
|
||||||
from authentik.policies.types import PolicyResult
|
from authentik.policies.types import PolicyResult
|
||||||
from authentik.stages.dummy.models import DummyStage
|
from authentik.stages.dummy.models import DummyStage
|
||||||
|
@ -89,8 +88,15 @@ class TestFlowExecutor(TestCase):
|
||||||
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.assertIsInstance(response, AccessDeniedResponse)
|
self.assertJSONEqual(
|
||||||
self.assertInHTML(FlowNonApplicableException.__doc__, response.rendered_content)
|
force_str(response.content),
|
||||||
|
{
|
||||||
|
"component": "ak-stage-access-denied",
|
||||||
|
"error_message": FlowNonApplicableException.__doc__,
|
||||||
|
"title": "",
|
||||||
|
"type": ChallengeTypes.native.value,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
@patch(
|
@patch(
|
||||||
"authentik.flows.views.to_stage_response",
|
"authentik.flows.views.to_stage_response",
|
||||||
|
|
|
@ -17,6 +17,7 @@ from structlog.stdlib import BoundLogger, get_logger
|
||||||
from authentik.core.models import USER_ATTRIBUTE_DEBUG
|
from authentik.core.models import USER_ATTRIBUTE_DEBUG
|
||||||
from authentik.events.models import cleanse_dict
|
from authentik.events.models import cleanse_dict
|
||||||
from authentik.flows.challenge import (
|
from authentik.flows.challenge import (
|
||||||
|
AccessDeniedChallenge,
|
||||||
Challenge,
|
Challenge,
|
||||||
ChallengeResponse,
|
ChallengeResponse,
|
||||||
ChallengeTypes,
|
ChallengeTypes,
|
||||||
|
@ -34,7 +35,6 @@ from authentik.flows.planner import (
|
||||||
)
|
)
|
||||||
from authentik.lib.utils.reflection import class_to_path
|
from authentik.lib.utils.reflection import class_to_path
|
||||||
from authentik.lib.utils.urls import is_url_absolute, redirect_with_qs
|
from authentik.lib.utils.urls import is_url_absolute, redirect_with_qs
|
||||||
from authentik.policies.http import AccessDeniedResponse
|
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
# Argument used to redirect user after login
|
# Argument used to redirect user after login
|
||||||
|
@ -212,10 +212,16 @@ class FlowExecutorView(APIView):
|
||||||
is a superuser."""
|
is a superuser."""
|
||||||
self._logger.debug("f(exec): Stage invalid")
|
self._logger.debug("f(exec): Stage invalid")
|
||||||
self.cancel()
|
self.cancel()
|
||||||
response = AccessDeniedResponse(
|
response = HttpChallengeResponse(
|
||||||
self.request, template="flows/denied_shell.html"
|
AccessDeniedChallenge(
|
||||||
|
{
|
||||||
|
"error_message": error_message,
|
||||||
|
"title": self.flow.title,
|
||||||
|
"type": ChallengeTypes.native.value,
|
||||||
|
"component": "ak-stage-access-denied",
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
response.error_message = error_message
|
|
||||||
return to_stage_response(self.request, response)
|
return to_stage_response(self.request, response)
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self):
|
||||||
|
|
|
@ -4,6 +4,7 @@ from django.urls import reverse
|
||||||
from django.utils.encoding import force_str
|
from django.utils.encoding import force_str
|
||||||
|
|
||||||
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
|
||||||
|
@ -42,7 +43,15 @@ class TestUserDenyStage(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertIn("Permission denied", force_str(response.content))
|
self.assertJSONEqual(
|
||||||
|
force_str(response.content),
|
||||||
|
{
|
||||||
|
"component": "ak-stage-access-denied",
|
||||||
|
"error_message": None,
|
||||||
|
"title": "",
|
||||||
|
"type": ChallengeTypes.native.value,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def test_form(self):
|
def test_form(self):
|
||||||
"""Test Form"""
|
"""Test Form"""
|
||||||
|
|
|
@ -7,12 +7,12 @@ from django.utils.encoding import force_str
|
||||||
from guardian.shortcuts import get_anonymous_user
|
from guardian.shortcuts import get_anonymous_user
|
||||||
|
|
||||||
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.test_views import TO_STAGE_RESPONSE_MOCK
|
from authentik.flows.tests.test_views import TO_STAGE_RESPONSE_MOCK
|
||||||
from authentik.flows.views import SESSION_KEY_PLAN
|
from authentik.flows.views import SESSION_KEY_PLAN
|
||||||
from authentik.policies.http import AccessDeniedResponse
|
|
||||||
from authentik.stages.invitation.forms import InvitationStageForm
|
from authentik.stages.invitation.forms import InvitationStageForm
|
||||||
from authentik.stages.invitation.models import Invitation, InvitationStage
|
from authentik.stages.invitation.models import Invitation, InvitationStage
|
||||||
from authentik.stages.invitation.stage import INVITATION_TOKEN_KEY, PLAN_CONTEXT_PROMPT
|
from authentik.stages.invitation.stage import INVITATION_TOKEN_KEY, PLAN_CONTEXT_PROMPT
|
||||||
|
@ -61,7 +61,15 @@ class TestUserLoginStage(TestCase):
|
||||||
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.assertIsInstance(response, AccessDeniedResponse)
|
self.assertJSONEqual(
|
||||||
|
force_str(response.content),
|
||||||
|
{
|
||||||
|
"component": "ak-stage-access-denied",
|
||||||
|
"error_message": None,
|
||||||
|
"title": "",
|
||||||
|
"type": ChallengeTypes.native.value,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def test_without_invitation_continue(self):
|
def test_without_invitation_continue(self):
|
||||||
"""Test without any invitation, continue_flow_without_invitation is set."""
|
"""Test without any invitation, continue_flow_without_invitation is set."""
|
||||||
|
|
|
@ -9,12 +9,12 @@ from django.urls import reverse
|
||||||
from django.utils.encoding import force_str
|
from django.utils.encoding import force_str
|
||||||
|
|
||||||
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.test_views import TO_STAGE_RESPONSE_MOCK
|
from authentik.flows.tests.test_views import TO_STAGE_RESPONSE_MOCK
|
||||||
from authentik.flows.views import SESSION_KEY_PLAN
|
from authentik.flows.views import SESSION_KEY_PLAN
|
||||||
from authentik.policies.http import AccessDeniedResponse
|
|
||||||
from authentik.stages.password.models import PasswordStage
|
from authentik.stages.password.models import PasswordStage
|
||||||
|
|
||||||
MOCK_BACKEND_AUTHENTICATE = MagicMock(side_effect=PermissionDenied("test"))
|
MOCK_BACKEND_AUTHENTICATE = MagicMock(side_effect=PermissionDenied("test"))
|
||||||
|
@ -66,7 +66,15 @@ class TestPasswordStage(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertIsInstance(response, AccessDeniedResponse)
|
self.assertJSONEqual(
|
||||||
|
force_str(response.content),
|
||||||
|
{
|
||||||
|
"component": "ak-stage-access-denied",
|
||||||
|
"error_message": None,
|
||||||
|
"title": "",
|
||||||
|
"type": ChallengeTypes.native.value,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def test_recovery_flow_link(self):
|
def test_recovery_flow_link(self):
|
||||||
"""Test link to the default recovery flow"""
|
"""Test link to the default recovery flow"""
|
||||||
|
@ -192,4 +200,12 @@ class TestPasswordStage(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertIsInstance(response, AccessDeniedResponse)
|
self.assertJSONEqual(
|
||||||
|
force_str(response.content),
|
||||||
|
{
|
||||||
|
"component": "ak-stage-access-denied",
|
||||||
|
"error_message": None,
|
||||||
|
"title": "",
|
||||||
|
"type": ChallengeTypes.native.value,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
|
@ -6,12 +6,12 @@ from django.urls import reverse
|
||||||
from django.utils.encoding import force_str
|
from django.utils.encoding import force_str
|
||||||
|
|
||||||
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.test_views import TO_STAGE_RESPONSE_MOCK
|
from authentik.flows.tests.test_views import TO_STAGE_RESPONSE_MOCK
|
||||||
from authentik.flows.views import SESSION_KEY_PLAN
|
from authentik.flows.views import SESSION_KEY_PLAN
|
||||||
from authentik.policies.http import AccessDeniedResponse
|
|
||||||
from authentik.stages.user_delete.models import UserDeleteStage
|
from authentik.stages.user_delete.models import UserDeleteStage
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,7 +49,15 @@ class TestUserDeleteStage(TestCase):
|
||||||
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.assertIsInstance(response, AccessDeniedResponse)
|
self.assertJSONEqual(
|
||||||
|
force_str(response.content),
|
||||||
|
{
|
||||||
|
"component": "ak-stage-access-denied",
|
||||||
|
"error_message": None,
|
||||||
|
"title": "",
|
||||||
|
"type": ChallengeTypes.native.value,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def test_user_delete_get(self):
|
def test_user_delete_get(self):
|
||||||
"""Test Form render"""
|
"""Test Form render"""
|
||||||
|
|
|
@ -6,12 +6,12 @@ from django.urls import reverse
|
||||||
from django.utils.encoding import force_str
|
from django.utils.encoding import force_str
|
||||||
|
|
||||||
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.test_views import TO_STAGE_RESPONSE_MOCK
|
from authentik.flows.tests.test_views import TO_STAGE_RESPONSE_MOCK
|
||||||
from authentik.flows.views import SESSION_KEY_PLAN
|
from authentik.flows.views import SESSION_KEY_PLAN
|
||||||
from authentik.policies.http import AccessDeniedResponse
|
|
||||||
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
|
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
|
||||||
from authentik.stages.user_login.forms import UserLoginStageForm
|
from authentik.stages.user_login.forms import UserLoginStageForm
|
||||||
from authentik.stages.user_login.models import UserLoginStage
|
from authentik.stages.user_login.models import UserLoginStage
|
||||||
|
@ -74,7 +74,15 @@ class TestUserLoginStage(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertIsInstance(response, AccessDeniedResponse)
|
self.assertJSONEqual(
|
||||||
|
force_str(response.content),
|
||||||
|
{
|
||||||
|
"component": "ak-stage-access-denied",
|
||||||
|
"error_message": None,
|
||||||
|
"title": "",
|
||||||
|
"type": ChallengeTypes.native.value,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
@patch(
|
@patch(
|
||||||
"authentik.flows.views.to_stage_response",
|
"authentik.flows.views.to_stage_response",
|
||||||
|
@ -95,7 +103,15 @@ class TestUserLoginStage(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertIsInstance(response, AccessDeniedResponse)
|
self.assertJSONEqual(
|
||||||
|
force_str(response.content),
|
||||||
|
{
|
||||||
|
"component": "ak-stage-access-denied",
|
||||||
|
"error_message": None,
|
||||||
|
"title": "",
|
||||||
|
"type": ChallengeTypes.native.value,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def test_form(self):
|
def test_form(self):
|
||||||
"""Test Form"""
|
"""Test Form"""
|
||||||
|
|
|
@ -8,12 +8,12 @@ from django.urls import reverse
|
||||||
from django.utils.encoding import force_str
|
from django.utils.encoding import force_str
|
||||||
|
|
||||||
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.test_views import TO_STAGE_RESPONSE_MOCK
|
from authentik.flows.tests.test_views import TO_STAGE_RESPONSE_MOCK
|
||||||
from authentik.flows.views import SESSION_KEY_PLAN
|
from authentik.flows.views import SESSION_KEY_PLAN
|
||||||
from authentik.policies.http import AccessDeniedResponse
|
|
||||||
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
|
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
|
||||||
from authentik.stages.user_write.forms import UserWriteStageForm
|
from authentik.stages.user_write.forms import UserWriteStageForm
|
||||||
from authentik.stages.user_write.models import UserWriteStage
|
from authentik.stages.user_write.models import UserWriteStage
|
||||||
|
@ -126,7 +126,15 @@ class TestUserWriteStage(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertIsInstance(response, AccessDeniedResponse)
|
self.assertJSONEqual(
|
||||||
|
force_str(response.content),
|
||||||
|
{
|
||||||
|
"component": "ak-stage-access-denied",
|
||||||
|
"error_message": None,
|
||||||
|
"title": "",
|
||||||
|
"type": ChallengeTypes.native.value,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def test_form(self):
|
def test_form(self):
|
||||||
"""Test Form"""
|
"""Test Form"""
|
||||||
|
|
|
@ -20,6 +20,7 @@ import "./stages/email/EmailStage";
|
||||||
import "./stages/identification/IdentificationStage";
|
import "./stages/identification/IdentificationStage";
|
||||||
import "./stages/password/PasswordStage";
|
import "./stages/password/PasswordStage";
|
||||||
import "./stages/prompt/PromptStage";
|
import "./stages/prompt/PromptStage";
|
||||||
|
import "./access_denied/FlowAccessDenied";
|
||||||
import { ShellChallenge, RedirectChallenge } from "../api/Flows";
|
import { ShellChallenge, RedirectChallenge } from "../api/Flows";
|
||||||
import { IdentificationChallenge } from "./stages/identification/IdentificationStage";
|
import { IdentificationChallenge } from "./stages/identification/IdentificationStage";
|
||||||
import { PasswordChallenge } from "./stages/password/PasswordStage";
|
import { PasswordChallenge } from "./stages/password/PasswordStage";
|
||||||
|
@ -38,6 +39,7 @@ import { DEFAULT_CONFIG } from "../api/Config";
|
||||||
import { ifDefined } from "lit-html/directives/if-defined";
|
import { ifDefined } from "lit-html/directives/if-defined";
|
||||||
import { until } from "lit-html/directives/until";
|
import { until } from "lit-html/directives/until";
|
||||||
import { TITLE_SUFFIX } from "../elements/router/RouterOutlet";
|
import { TITLE_SUFFIX } from "../elements/router/RouterOutlet";
|
||||||
|
import { AccessDeniedChallenge } from "./access_denied/FlowAccessDenied";
|
||||||
|
|
||||||
@customElement("ak-flow-executor")
|
@customElement("ak-flow-executor")
|
||||||
export class FlowExecutor extends LitElement implements StageHost {
|
export class FlowExecutor extends LitElement implements StageHost {
|
||||||
|
@ -175,6 +177,8 @@ export class FlowExecutor extends LitElement implements StageHost {
|
||||||
return html`${unsafeHTML((this.challenge as ShellChallenge).body)}`;
|
return html`${unsafeHTML((this.challenge as ShellChallenge).body)}`;
|
||||||
case ChallengeTypeEnum.Native:
|
case ChallengeTypeEnum.Native:
|
||||||
switch (this.challenge.component) {
|
switch (this.challenge.component) {
|
||||||
|
case "ak-stage-access-denied":
|
||||||
|
return html`<ak-stage-access-denied .host=${this} .challenge=${this.challenge as AccessDeniedChallenge}></ak-stage-access-denied>`;
|
||||||
case "ak-stage-identification":
|
case "ak-stage-identification":
|
||||||
return html`<ak-stage-identification .host=${this} .challenge=${this.challenge as IdentificationChallenge}></ak-stage-identification>`;
|
return html`<ak-stage-identification .host=${this} .challenge=${this.challenge as IdentificationChallenge}></ak-stage-identification>`;
|
||||||
case "ak-stage-password":
|
case "ak-stage-password":
|
||||||
|
|
82
web/src/flows/access_denied/FlowAccessDenied.ts
Normal file
82
web/src/flows/access_denied/FlowAccessDenied.ts
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
import { Challenge } from "authentik-api";
|
||||||
|
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
|
||||||
|
import { BaseStage } from "../stages/base";
|
||||||
|
import PFLogin from "@patternfly/patternfly/components/Login/login.css";
|
||||||
|
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
|
||||||
|
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||||
|
import PFForm from "@patternfly/patternfly/components/Form/form.css";
|
||||||
|
import PFList from "@patternfly/patternfly/components/List/list.css";
|
||||||
|
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
|
||||||
|
import AKGlobal from "../../authentik.css";
|
||||||
|
import { gettext } from "django";
|
||||||
|
|
||||||
|
import "../../elements/EmptyState";
|
||||||
|
|
||||||
|
export interface AccessDeniedChallenge extends Challenge {
|
||||||
|
error_message?: string;
|
||||||
|
policy_result?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("ak-stage-access-denied")
|
||||||
|
export class FlowAccessDenied extends BaseStage {
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
challenge?: AccessDeniedChallenge;
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return [PFBase, PFLogin, PFForm, PFList, PFFormControl, PFTitle, AKGlobal];
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): TemplateResult {
|
||||||
|
if (!this.challenge) {
|
||||||
|
return html`<ak-empty-state
|
||||||
|
?loading="${true}"
|
||||||
|
header=${gettext("Loading")}>
|
||||||
|
</ak-empty-state>`;
|
||||||
|
}
|
||||||
|
return html`<header class="pf-c-login__main-header">
|
||||||
|
<h1 class="pf-c-title pf-m-3xl">
|
||||||
|
${this.challenge.title}
|
||||||
|
</h1>
|
||||||
|
</header>
|
||||||
|
<div class="pf-c-login__main-body">
|
||||||
|
<form method="POST" class="pf-c-form">
|
||||||
|
<div class="pf-c-form__group">
|
||||||
|
<p>
|
||||||
|
<i class="pf-icon pf-icon-error-circle-o"></i>
|
||||||
|
${gettext("Request has been denied.")}
|
||||||
|
</p>
|
||||||
|
${this.challenge?.error_message &&
|
||||||
|
html`<hr>
|
||||||
|
<p>${this.challenge.error_message}</p>`}
|
||||||
|
${this.challenge.policy_result &&
|
||||||
|
html`<hr>
|
||||||
|
<em>
|
||||||
|
${gettext("Explanation:")}
|
||||||
|
</em>
|
||||||
|
<ul class="pf-c-list">
|
||||||
|
{% for source_result in policy_result.source_results %}
|
||||||
|
<li>
|
||||||
|
{% blocktrans with name=source_result.source_policy.name result=source_result.passing %}
|
||||||
|
Policy '{{ name }}' returned result '{{ result }}'
|
||||||
|
{% endblocktrans %}
|
||||||
|
{% if source_result.messages %}
|
||||||
|
<ul class="pf-c-list">
|
||||||
|
{% for message in source_result.messages %}
|
||||||
|
<li>{{ message }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>`}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<footer class="pf-c-login__main-footer">
|
||||||
|
<ul class="pf-c-login__main-footer-links">
|
||||||
|
</ul>
|
||||||
|
</footer>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in a new issue