diff --git a/passbook/core/models.py b/passbook/core/models.py index 2b5b186c63..43cdbe0ce7 100644 --- a/passbook/core/models.py +++ b/passbook/core/models.py @@ -22,6 +22,7 @@ from passbook.lib.models import CreatedUpdatedModel from passbook.policies.models import PolicyBindingModel LOGGER = get_logger() +PASSBOOK_USER_DEBUG = "passbook_user_debug" def default_token_duration(): diff --git a/passbook/core/templates/error/embedded.html b/passbook/core/templates/error/embedded.html deleted file mode 100644 index ddd0986e8b..0000000000 --- a/passbook/core/templates/error/embedded.html +++ /dev/null @@ -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 %} -
- {% if message %} -

{% trans message %}

- {% endif %} - {% if 'back' in request.GET %} - {% trans 'Back' %} - {% endif %} -
-{% endblock %} diff --git a/passbook/core/templates/login/denied.html b/passbook/core/templates/login/denied.html deleted file mode 100644 index cbf4d87688..0000000000 --- a/passbook/core/templates/login/denied.html +++ /dev/null @@ -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 %} -
- {% csrf_token %} - {% include 'partials/form.html' %} -
-

- - {% trans 'Access denied' %} -

-
- {% if 'back' in request.GET %} - {% trans 'Back' %} - {% endif %} -
-{% endblock %} diff --git a/passbook/core/tests/test_views_utils.py b/passbook/core/tests/test_views_utils.py deleted file mode 100644 index 3e61918fa3..0000000000 --- a/passbook/core/tests/test_views_utils.py +++ /dev/null @@ -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) diff --git a/passbook/core/views/utils.py b/passbook/core/views/utils.py deleted file mode 100644 index 7ad222334d..0000000000 --- a/passbook/core/views/utils.py +++ /dev/null @@ -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) diff --git a/passbook/flows/templates/flows/denied.html b/passbook/flows/templates/flows/denied.html new file mode 100644 index 0000000000..24127083d4 --- /dev/null +++ b/passbook/flows/templates/flows/denied.html @@ -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 %} +
+ {% csrf_token %} + {% include 'partials/form.html' %} +
+

+ + {% trans 'Access denied' %} +

+ {% if error %} +
+

+ {{ error }} +

+ {% endif %} + {% if policy_result %} +
+ + {% trans 'Explanation:' %} + + + {% endif %} +
+ {% if 'back' in request.GET %} + {% trans 'Back' %} + {% endif %} +
+{% endblock %} diff --git a/passbook/flows/views.py b/passbook/flows/views.py index 75769a267c..08b878b794 100644 --- a/passbook/flows/views.py +++ b/passbook/flows/views.py @@ -12,18 +12,18 @@ from django.http import ( from django.shortcuts import get_object_or_404, redirect, render, reverse from django.template.response import TemplateResponse 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.generic import TemplateView, View from structlog import get_logger 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.models import Flow, FlowDesignation, Stage from passbook.flows.planner import FlowPlan, FlowPlanner from passbook.lib.utils.reflection import class_to_path from passbook.lib.utils.urls import is_url_absolute, redirect_with_qs -from passbook.lib.views import bad_request_message LOGGER = get_logger() # Argument used to redirect user after login @@ -31,6 +31,8 @@ NEXT_ARG_NAME = "next" SESSION_KEY_PLAN = "passbook_flows_plan" SESSION_KEY_APPLICATION_PRE = "passbook_flows_application_pre" 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") @@ -54,9 +56,9 @@ class FlowExecutorView(View): LOGGER.debug("f(exec): Redirecting to next on fail") return redirect(self.request.GET.get(NEXT_ARG_NAME)) message = exc.__doc__ if exc.__doc__ else str(exc) + self.cancel() return to_stage_response( - self.request, - bad_request_message(self.request, message, template="error/embedded.html"), + self.request, self.stage_invalid(error_message=message) ) def dispatch(self, request: HttpRequest, flow_slug: str) -> HttpResponse: @@ -196,11 +198,19 @@ class FlowExecutorView(View): ) 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 - 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) 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) def cancel(self): @@ -215,9 +225,22 @@ class FlowExecutorView(View): del self.request.session[key] -class FlowPermissionDeniedView(PermissionDeniedView): +class FlowPermissionDeniedView(TemplateView): """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): """Executor Shell view, loads a dummy card with a spinner