# Imports
from django.core.exceptions import PermissionDenied
from django.http import Http404, HttpResponseRedirect
from django.utils.translation import gettext_lazy as _
import logging
import re
log = logging.getLogger(__name__)
# Exports
__all__ = (
"UIAccessMixin",
)
# Mixins
[docs]class UIAccessMixin(object):
"""A UI-enabled mixin for checking user access to a view.
The primary difference between this mixin and ``superdjango.views.AccessMixin`` is that access options are defined
using the ``access_policy`` attribute of the ModelUI.
"""
error_messages = {
'access_denied': _("Access denied."),
'group_required': _("Group membership is required to access this page."),
'login_required': _("A login is required to access this page."),
'permission_required': _("Special permission is required to access this page."),
'ssl_required': _("The requested page must be accessed over a secure connection."),
}
[docs] def dispatch(self, request, *args, **kwargs):
"""Dispatch with access checks.
:param request: The current request object.
:type request: django.http.request.HttpRequest
Access is checked in the following order:
1. Check for SSL.
2. Check for an authenticated user.
3. Check for group membership.
4. Check for specific permissions.
5. Call ``self.ui.access_policy.check_other()``. See ``dispatch_other()``.
"""
# noinspection PyUnresolvedReferences
if not self.ui.access_policy.check_ssl(request, *args, **kwargs):
return self.dispatch_insecure(request)
# noinspection PyUnresolvedReferences
if not self.ui.access_policy.check_login(request, *args, **kwargs):
return self.dispatch_access_denied(request, reason="login_required")
# noinspection PyUnresolvedReferences
if not self.ui.access_policy.check_group(request, *args, **kwargs):
return self.dispatch_access_denied(request, reason="group_required")
# noinspection PyUnresolvedReferences
if not self.ui.access_policy.check_permission(request, *args, **kwargs):
return self.dispatch_access_denied(request, reason="permission_required")
# noinspection PyUnresolvedReferences
if not self.ui.access_policy.check_other(request, *args, **kwargs):
return self.dispatch_other(request)
# noinspection PyUnresolvedReferences
return super().dispatch(request, *args, *kwargs)
[docs] def dispatch_access_denied(self, request, reason="access_denied", redirect_url=None):
"""Handle authentication or permission issues during dispatch.
:param request: The current request object.
:type request: django.http.request.HttpRequest
:param reason: A reason for the permission failure.
:type reason: str
:param redirect_url: The URL to which the user should be directed.
:type redirect_url: str
"""
message = self._get_dispatch_message(reason)
# noinspection PyUnresolvedReferences
if self.ui.access_policy.raise_exception:
raise PermissionDenied(message)
# noinspection PyUnresolvedReferences
if self.ui.logging_enabled:
log.info(message)
if redirect_url is not None:
return HttpResponseRedirect(redirect_url)
return self.redirect_to_login(request.get_full_path())
[docs] def dispatch_insecure(self, request, reason="ssl_required"):
"""Handle failed SSL connections.
:param request: The current request object.
:type request: django.http.request.HttpRequest
:param reason: A reason for the permission failure.
:type reason: str
"""
message = self._get_dispatch_message(reason)
# noinspection PyUnresolvedReferences
if self.ui.access_policy.raise_exception:
raise Http404(message)
# noinspection PyUnresolvedReferences
if self.ui.logging_enabled:
log.info(message)
return HttpResponseRedirect(self.get_https_url(request))
[docs] def dispatch_other(self, request, reason="access_denied", redirect_url=None):
"""Responds to failed ``check_other()``. Override this method to provide your own handling.
:param reason: A reason for the permission failure.
:type reason: str
:param request: The current request object.
:type request: django.http.request.HttpRequest
:param redirect_url: The URL to which the user should be directed.
:type redirect_url: str
.. note::
By default, this method simply calls ``dispatch_access_denied()``.
"""
return self.dispatch_access_denied(request, reason=reason, redirect_url=redirect_url)
# noinspection PyMethodMayBeStatic
[docs] def get_https_url(self, request):
"""Get the current URL with the HTTPS protocol.
:param request: The current request object.
:type request: django.http.request.HttpRequest
"""
url = request.build_absolute_uri(request.get_full_path())
return re.sub(r'^http', 'https', url)
# noinspection PyMethodMayBeStatic
[docs] def redirect_to_login(self, path):
"""Redirect to the login view using the given path as the next URL.
:param path: The next URL after logging in.
:type path: str
"""
# Import Django's utility here to avoid AppRegistryNotReady error.
from django.contrib.auth.views import redirect_to_login
return redirect_to_login(path)
def _get_dispatch_message(self, key):
"""Get the error message for the given dispatch error.
:param key: The key name of the error message.
:type key: str
:rtype: str
"""
# noinspection PyUnresolvedReferences
if key in self.ui.access_policy.error_messages:
# noinspection PyUnresolvedReferences
return self.ui.access_policy.error_messages[key]
return self.error_messages[key]