flows: move complete denied view and template to flows

This commit is contained in:
Jens Langhammer 2020-09-14 21:52:43 +02:00
parent 3cf558d594
commit 40614a65fc
7 changed files with 88 additions and 105 deletions

View file

@ -22,6 +22,7 @@ from passbook.lib.models import CreatedUpdatedModel
from passbook.policies.models import PolicyBindingModel from passbook.policies.models import PolicyBindingModel
LOGGER = get_logger() LOGGER = get_logger()
PASSBOOK_USER_DEBUG = "passbook_user_debug"
def default_token_duration(): def default_token_duration():

View file

@ -1,25 +0,0 @@
{# Template used by bad_request_message within flows #}
{% extends 'login/base.html' %}
{% load static %}
{% load i18n %}
{% load passbook_utils %}
{% block title %}
{% trans card_title %}
{% endblock %}
{% block card_title %}
{% trans card_title %}
{% endblock %}
{% block card %}
<form method="POST" class="pf-c-form">
{% if message %}
<h3>{% trans message %}</h3>
{% endif %}
{% if 'back' in request.GET %}
<a href="{% back %}" class="btn btn-primary btn-block btn-lg">{% trans 'Back' %}</a>
{% endif %}
</form>
{% endblock %}

View file

@ -1,29 +0,0 @@
{% extends 'login/base_full.html' %}
{% load static %}
{% load i18n %}
{% load passbook_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 'Access denied' %}
</p>
</div>
{% if 'back' in request.GET %}
<a href="{% back %}" class="btn btn-primary btn-block btn-lg">{% trans 'Back' %}</a>
{% endif %}
</form>
{% endblock %}

View file

@ -1,30 +0,0 @@
"""passbook util view tests"""
import string
from random import SystemRandom
from django.test import RequestFactory, TestCase
from passbook.core.models import User
from passbook.core.views.utils import PermissionDeniedView
class TestUtilViews(TestCase):
"""Test Utility Views"""
def setUp(self):
self.user = User.objects.create_superuser(
username="unittest user",
email="unittest@example.com",
password="".join(
SystemRandom().choice(string.ascii_uppercase + string.digits)
for _ in range(8)
),
)
self.factory = RequestFactory()
def test_permission_denied_view(self):
"""Test PermissionDeniedView"""
request = self.factory.get("something")
request.user = self.user
response = PermissionDeniedView.as_view()(request)
self.assertEqual(response.status_code, 200)

View file

@ -1,14 +0,0 @@
"""passbook core utils view"""
from django.utils.translation import gettext as _
from django.views.generic import TemplateView
class PermissionDeniedView(TemplateView):
"""Generic Permission denied view"""
template_name = "login/denied.html"
title = _("Permission denied.")
def get_context_data(self, **kwargs):
kwargs["title"] = self.title
return super().get_context_data(**kwargs)

View file

@ -0,0 +1,57 @@
{% extends 'login/base_full.html' %}
{% load static %}
{% load i18n %}
{% load passbook_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 'Access 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 %}

View file

@ -12,18 +12,18 @@ from django.http import (
from django.shortcuts import get_object_or_404, redirect, render, reverse from django.shortcuts import get_object_or_404, redirect, render, reverse
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.translation import gettext as _
from django.views.decorators.clickjacking import xframe_options_sameorigin from django.views.decorators.clickjacking import xframe_options_sameorigin
from django.views.generic import TemplateView, View from django.views.generic import TemplateView, View
from structlog import get_logger from structlog import get_logger
from passbook.audit.models import cleanse_dict from passbook.audit.models import cleanse_dict
from passbook.core.views.utils import PermissionDeniedView from passbook.core.models import PASSBOOK_USER_DEBUG
from passbook.flows.exceptions import EmptyFlowException, FlowNonApplicableException from passbook.flows.exceptions import EmptyFlowException, FlowNonApplicableException
from passbook.flows.models import Flow, FlowDesignation, Stage from passbook.flows.models import Flow, FlowDesignation, Stage
from passbook.flows.planner import FlowPlan, FlowPlanner from passbook.flows.planner import FlowPlan, FlowPlanner
from passbook.lib.utils.reflection import class_to_path from passbook.lib.utils.reflection import class_to_path
from passbook.lib.utils.urls import is_url_absolute, redirect_with_qs from passbook.lib.utils.urls import is_url_absolute, redirect_with_qs
from passbook.lib.views import bad_request_message
LOGGER = get_logger() LOGGER = get_logger()
# Argument used to redirect user after login # Argument used to redirect user after login
@ -31,6 +31,8 @@ NEXT_ARG_NAME = "next"
SESSION_KEY_PLAN = "passbook_flows_plan" SESSION_KEY_PLAN = "passbook_flows_plan"
SESSION_KEY_APPLICATION_PRE = "passbook_flows_application_pre" SESSION_KEY_APPLICATION_PRE = "passbook_flows_application_pre"
SESSION_KEY_GET = "passbook_flows_get" SESSION_KEY_GET = "passbook_flows_get"
SESSION_KEY_DENIED_ERROR = "passbook_flows_denied_error"
SESSION_KEY_DENIED_POLICY_RESULT = "passbook_flows_denied_policy_result"
@method_decorator(xframe_options_sameorigin, name="dispatch") @method_decorator(xframe_options_sameorigin, name="dispatch")
@ -54,9 +56,9 @@ class FlowExecutorView(View):
LOGGER.debug("f(exec): Redirecting to next on fail") LOGGER.debug("f(exec): Redirecting to next on fail")
return redirect(self.request.GET.get(NEXT_ARG_NAME)) return redirect(self.request.GET.get(NEXT_ARG_NAME))
message = exc.__doc__ if exc.__doc__ else str(exc) message = exc.__doc__ if exc.__doc__ else str(exc)
self.cancel()
return to_stage_response( return to_stage_response(
self.request, self.request, self.stage_invalid(error_message=message)
bad_request_message(self.request, message, template="error/embedded.html"),
) )
def dispatch(self, request: HttpRequest, flow_slug: str) -> HttpResponse: def dispatch(self, request: HttpRequest, flow_slug: str) -> HttpResponse:
@ -196,11 +198,19 @@ class FlowExecutorView(View):
) )
return self._flow_done() return self._flow_done()
def stage_invalid(self) -> HttpResponse: def stage_invalid(self, error_message: Optional[str] = None) -> HttpResponse:
"""Callback used stage when data is correct but a policy denies access """Callback used stage when data is correct but a policy denies access
or the user account is disabled.""" or the user account is disabled.
Optionally, an exception can be passed, which will be shown if the current user
is a superuser."""
LOGGER.debug("f(exec): Stage invalid", flow_slug=self.flow.slug) LOGGER.debug("f(exec): Stage invalid", flow_slug=self.flow.slug)
self.cancel() self.cancel()
if self.request.user and self.request.user.is_authenticated:
if self.request.user.is_superuser or self.request.user.attributes.get(
PASSBOOK_USER_DEBUG, False
):
self.request.session[SESSION_KEY_DENIED_ERROR] = error_message
return redirect_with_qs("passbook_flows:denied", self.request.GET) return redirect_with_qs("passbook_flows:denied", self.request.GET)
def cancel(self): def cancel(self):
@ -215,9 +225,22 @@ class FlowExecutorView(View):
del self.request.session[key] del self.request.session[key]
class FlowPermissionDeniedView(PermissionDeniedView): class FlowPermissionDeniedView(TemplateView):
"""User could not be authenticated""" """User could not be authenticated"""
template_name = "flows/denied.html"
title = _("Permission denied.")
def get_context_data(self, **kwargs):
kwargs["title"] = self.title
if SESSION_KEY_DENIED_ERROR in self.request.session:
kwargs["error"] = self.request.session[SESSION_KEY_DENIED_ERROR]
if SESSION_KEY_DENIED_POLICY_RESULT in self.request.session:
kwargs["policy_result"] = self.request.session[
SESSION_KEY_DENIED_POLICY_RESULT
]
return super().get_context_data(**kwargs)
class FlowExecutorShellView(TemplateView): class FlowExecutorShellView(TemplateView):
"""Executor Shell view, loads a dummy card with a spinner """Executor Shell view, loads a dummy card with a spinner