Admin permissions

This commit is contained in:
Kumi 2020-06-03 17:05:18 +02:00
parent 31b0eed298
commit 5faa1e403a
19 changed files with 208 additions and 26 deletions

View file

@ -1,10 +1,11 @@
from django.urls import reverse
class NavItem:
def __init__(self, name: str, icon: str, path: str):
def __init__(self, name: str, icon: str, path: str, permissions: list=[]):
self.__name = name
self.__icon = icon
self.__path = path
self.__permissions = permissions
@property
def name(self):
@ -18,11 +19,16 @@ class NavItem:
def path(self):
return self.__path if (self.__path.startswith("/") or "://" in self.__path) else reverse(self.__path)
@property
def permissions(self):
return self.__permissions
class NavSection:
def __init__(self, name: str, icon: str = ""):
def __init__(self, name: str, icon: str = "", permissions: list=[]):
self.__items = []
self.__name = name
self.__icon = icon
self.__permissions = permissions
@property
def name(self):
@ -32,6 +38,10 @@ class NavSection:
def icon(self):
return self.__icon
@property
def permissions(self):
return self.__permissions
def add_item(self, item: NavItem):
if not item in self.__items:
self.__items.append(item)

2
core/exceptions/auth.py Normal file
View file

@ -0,0 +1,2 @@
class InsufficientPermissionsException(Exception):
pass

View file

@ -1,20 +1,36 @@
from django.contrib.auth.mixins import AccessMixin
from django.contrib.messages import error
from django.views.decorators.cache import never_cache
from django.contrib.auth.views import redirect_to_login
from core.models.profiles import AdminProfile
from core.exceptions.auth import InsufficientPermissionsException
class AdminMixin(AccessMixin):
permissions = []
@never_cache
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
self.permission_denied_message = "You must be logged in to access this area."
else:
try:
AdminProfile.objects.get(user=request.user)
user_permissions = AdminProfile.objects.get(user=request.user).permissions
for permission in self.permissions:
if not permission in user_permissions:
raise InsufficientPermissionsException(f"Missing permission: {permission}")
return super().dispatch(request, *args, **kwargs)
except AdminProfile.DoesNotExist:
self.permission_denied_message = "You must be an administrator to access this area."
except InsufficientPermissionsException:
self.permission_denied_message = "You do not have the necessary permissions to access this page."
return self.handle_no_permission()
return self.handle_no_permission()
def handle_no_permission(self):
if self.raise_exception:
raise PermissionDenied(self.get_permission_denied_message())
error(self.request, self.permission_denied_message)
return redirect_to_login(self.request.get_full_path(), self.get_login_url(), self.get_redirect_field_name())

View file

@ -24,11 +24,23 @@ class AdminProfile(Profile):
role = LongCharField()
image = ImageField(null=True, blank=True, upload_to=generate_storage_filename)
display_name = LongCharField("Internal Display Name", null=True, blank=True)
permission_string = TextField(blank=True, default="")
@property
def get_internal_name(self):
return self.display_name if self.display_name else self.user.get_full_name
@property
def permissions(self):
return self.permission_string.split(",")
@permissions.setter
def permissions(self, permissions):
if isinstance(permissions, str):
self.permission_string = permissions
else:
self.permission_string = ",".join(permissions)
class ClientProfile(Profile):
company = LongCharField(null=True, blank=True)
address1 = LongCharField()

View file

@ -0,0 +1,15 @@
import importlib
from django.conf import settings
admin_permissions = []
for module in ["core"] + settings.EXPEPHALON_MODULES:
try:
mop = importlib.import_module(f"{module}.permissions")
for admin_permission in mop.ADMIN_PERMISSIONS:
admin_permissions.append(admin_permission)
except (AttributeError, ModuleNotFoundError):
continue
admin_permissions = tuple(admin_permissions)

97
core/permissions.py Normal file
View file

@ -0,0 +1,97 @@
ADMIN_PERMISSIONS = []
# Client Administration Permissions
ADMIN_PERMISSIONS.append(("read_clients", "Read Clients"))
ADMIN_PERMISSIONS.append(("modify_clients", "Modify Clients"))
ADMIN_PERMISSIONS.append(("delete_clients", "Delete Clients"))
ADMIN_PERMISSIONS.append(("create_clients", "Create Clients"))
# Client Group Administration Permissions
ADMIN_PERMISSIONS.append(("read_clientgroups", "Read Client Groups"))
ADMIN_PERMISSIONS.append(("modify_clientgroups", "Modify Client Groups"))
ADMIN_PERMISSIONS.append(("delete_clientgroups", "Delete Client Groups"))
ADMIN_PERMISSIONS.append(("create_clientgroups", "Create Client Groups"))
# Quoting Permissions
ADMIN_PERMISSIONS.append(("read_quotes", "Read Quotes"))
ADMIN_PERMISSIONS.append(("modify_quotes", "Modify Quotes"))
ADMIN_PERMISSIONS.append(("delete_quotes", "Delete Quotes"))
ADMIN_PERMISSIONS.append(("create_quotes", "Create Quotes"))
# Invoicing Permissions
ADMIN_PERMISSIONS.append(("read_invoices", "Read Invoices"))
ADMIN_PERMISSIONS.append(("modify_invoices", "Modify Invoices"))
ADMIN_PERMISSIONS.append(("delete_invoices", "Delete Invoices"))
ADMIN_PERMISSIONS.append(("create_invoices", "Create Invoices"))
# Billable Permissions
ADMIN_PERMISSIONS.append(("read_billables", "Read Billable Items"))
ADMIN_PERMISSIONS.append(("modify_billables", "Modify Billable Items"))
ADMIN_PERMISSIONS.append(("delete_billables", "Delete Billable Items"))
ADMIN_PERMISSIONS.append(("create_billables", "Create Billable Items"))
# Product Permissions
ADMIN_PERMISSIONS.append(("read_products", "Read Products"))
ADMIN_PERMISSIONS.append(("modify_products", "Modify Products"))
ADMIN_PERMISSIONS.append(("delete_products", "Delete Products"))
ADMIN_PERMISSIONS.append(("create_products", "Create Products"))
# Product Group Permissions
ADMIN_PERMISSIONS.append(("read_productgroups", "Read Product Groups"))
ADMIN_PERMISSIONS.append(("modify_productgroups", "Modify Product Groups"))
ADMIN_PERMISSIONS.append(("delete_productgroups", "Delete Product Groups"))
ADMIN_PERMISSIONS.append(("create_productgroups", "Create Product Groups"))
# Backend User Permissions
ADMIN_PERMISSIONS.append(("read_admins", "Read Administrators"))
ADMIN_PERMISSIONS.append(("modify_admins", "Modify Administrators"))
ADMIN_PERMISSIONS.append(("delete_admins", "Delete Administrators"))
ADMIN_PERMISSIONS.append(("create_admins", "Create Administrators"))
# Brand Permissions
ADMIN_PERMISSIONS.append(("read_brands", "Read Brands"))
ADMIN_PERMISSIONS.append(("modify_brands", "Modify Brands"))
ADMIN_PERMISSIONS.append(("delete_brands", "Delete Brands"))
ADMIN_PERMISSIONS.append(("create_brands", "Create Brands"))
# Firewall Permissions
ADMIN_PERMISSIONS.append(("read_firewall", "Read Firewall Rules"))
ADMIN_PERMISSIONS.append(("modify_firewall", "Modify Firewall Rules"))
ADMIN_PERMISSIONS.append(("delete_firewall", "Delete Firewall Rules"))
ADMIN_PERMISSIONS.append(("create_firewall", "Create Firewall Rules"))
# Currency Permissions
ADMIN_PERMISSIONS.append(("read_currencies", "Read Currencies"))
ADMIN_PERMISSIONS.append(("modify_currencies", "Modify Currencies"))
ADMIN_PERMISSIONS.append(("delete_currencies", "Delete Currencies"))
ADMIN_PERMISSIONS.append(("create_currencies", "Create Currencies"))
# Tax Permissions
ADMIN_PERMISSIONS.append(("read_taxes", "Read Taxes"))
ADMIN_PERMISSIONS.append(("modify_taxes", "Modify Taxes"))
ADMIN_PERMISSIONS.append(("delete_taxes", "Delete Taxes"))
ADMIN_PERMISSIONS.append(("create_taxes", "Create Taxes"))
# Database Setting Permissions
ADMIN_PERMISSIONS.append(("read_dbsettings", "Read Database Settings"))
ADMIN_PERMISSIONS.append(("modify_dbsettings", "Modify Database Settings"))
ADMIN_PERMISSIONS.append(("delete_dbsettings", "Delete Database Settings"))
ADMIN_PERMISSIONS.append(("create_dbsettings", "Create Database Settings"))
# Superuser Permissions
ADMIN_PERMISSIONS.append(("grant_permissions", "Grant Permissions"))
ADMIN_PERMISSIONS.append(("grant_all_permissions", "Grant all Permissions"))

View file

@ -0,0 +1,20 @@
from django import template
from core.models.profiles import AdminProfile
register = template.Library()
@register.simple_tag(takes_context=True)
def admin_has_permissions(context, permissions):
user = context["request"].user
user_permissions = user.profile.permissions
if isinstance(permissions, str):
permissions = permissions.split(",")
if isinstance(user.profile, AdminProfile):
for permission in permissions:
if not permission in user_permissions:
return False
return True
return False

View file

@ -5,18 +5,11 @@ from django.conf import settings
from core.views.backend.dbsettings import *
from core.views.auth import *
from core.views.backend.profiles import *
from core.views.generic import *
from core.views.backend.generic import *
from core.views.backend.brands import *
from core.views.backend.firewall import *
from core.views.backend.invoices import *
class DashboardView(BackendTemplateView):
template_name = f"{settings.EXPEPHALON_BACKEND}/index.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = "Dashboard"
return context
from core.views.backend.dashboard import *
class BackendNotImplementedView(BackendTemplateView):
template_name = f"{settings.EXPEPHALON_BACKEND}/notimplemented.html"

View file

@ -2,7 +2,7 @@ from django.conf import settings
from django.urls import reverse_lazy
from core.models.brands import Brand
from core.views.generic import BackendListView, BackendUpdateView, BackendDeleteView, BackendCreateView
from core.views.backend.generic import BackendListView, BackendUpdateView, BackendDeleteView, BackendCreateView
class BrandListView(BackendListView):
template_name = f"{settings.EXPEPHALON_BACKEND}/brands/index.html"

View file

@ -2,7 +2,7 @@ from django.conf import settings
from django.urls import reverse_lazy
from core.models.local import Currency
from core.views.generic import BackendListView, BackendUpdateView, BackendDeleteView, BackendCreateView
from core.views.backend.generic import BackendListView, BackendUpdateView, BackendDeleteView, BackendCreateView
class CurrencyListView(BackendListView):
template_name = f"{settings.EXPEPHALON_BACKEND}/currencies/index.html"

View file

@ -0,0 +1,11 @@
from core.views.backend.generic import BackendTemplateView
from django.conf import settings
class DashboardView(BackendTemplateView):
template_name = f"{settings.EXPEPHALON_BACKEND}/index.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = "Dashboard"
return context

View file

@ -2,7 +2,7 @@ from django.conf import settings
from django.urls import reverse_lazy
from dbsettings.models import Setting
from core.views.generic import BackendListView, BackendUpdateView, BackendDeleteView, BackendCreateView
from core.views.backend.generic import BackendListView, BackendUpdateView, BackendDeleteView, BackendCreateView
class DBSettingsListView(BackendListView):
template_name = f"{settings.EXPEPHALON_BACKEND}/dbsettings/index.html"

View file

@ -2,7 +2,7 @@ from django.conf import settings
from django.urls import reverse_lazy
from core.models.auth import IPLimit
from core.views.generic import BackendListView, BackendUpdateView, BackendDeleteView, BackendCreateView
from core.views.backend.generic import BackendListView, BackendUpdateView, BackendDeleteView, BackendCreateView
class RateLimitListView(BackendListView):
template_name = f"{settings.EXPEPHALON_BACKEND}/firewall/index.html"

View file

@ -1,4 +1,5 @@
from django.views.generic import TemplateView, ListView, CreateView, FormView, DeleteView, UpdateView
from core.mixins.auth import AdminMixin
class BackendTemplateView(AdminMixin, TemplateView):

View file

@ -2,7 +2,7 @@ from django.conf import settings
from django.urls import reverse_lazy
from core.models.invoices import Invoice, InvoiceItem
from core.views.generic import BackendListView, BackendUpdateView, BackendDeleteView, BackendCreateView
from core.views.backend.generic import BackendListView, BackendUpdateView, BackendDeleteView, BackendCreateView
class InvoiceListView(BackendListView):
template_name = f"{settings.EXPEPHALON_BACKEND}/invoices/index.html"

View file

@ -5,7 +5,7 @@ from django.contrib import messages
from core.models import AdminProfile, ClientProfile, ClientGroup
from core.forms.profiles import AdminEditForm, AdminCreateForm, ClientForm
from core.views.generic import BackendFormView, BackendListView, BackendDeleteView, BackendUpdateView, BackendCreateView
from core.views.backend.generic import BackendFormView, BackendListView, BackendDeleteView, BackendUpdateView, BackendCreateView
from core.helpers.auth import request_password
### AdminProfiles

View file

@ -175,7 +175,7 @@ LOGGING = {
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
'format': '%(levelname)s %(asctime)s %(name)s %(process)d %(thread)d %(message)s'
},
'simple': {
'format': '%(levelname)s %(message)s'

View file

@ -1,6 +1,7 @@
{% load static %}
{% load navigation %}
{% load dbsetting %}
{% load permissions %}
{% dbsetting "core.title" as sitetitle %}
<!doctype html>
<html lang="en">
@ -161,6 +162,8 @@
<ul class="vertical-nav-menu">
{% get_nav_sections_by_name "backend_main" as nav %}
{% for section in nav %}
{% admin_has_permissions section.permissions as check %}
{% if check %}
<li class="app-sidebar__heading">{{ section.name }}</li>
{% for item in section.items %}
<li>
@ -170,6 +173,7 @@
</a>
</li>
{% endfor %}
{% endif %}
{% endfor %}
</ul>
</div>

View file

@ -1,4 +1,5 @@
{% extends "backend/base.html" %}
{% load static %}
{% block content %}
<div class="app-page-title">
<div class="page-title-wrapper">
@ -86,7 +87,7 @@
<div class="widget-content p-0">
<div class="widget-content-wrapper">
<div class="widget-content-left mr-3">
<img width="42" class="rounded-circle" src="assets/images/avatars/9.jpg" alt="">
<img width="42" class="rounded-circle" src="{% static "backend/images/blank.png" %}" alt="">
</div>
<div class="widget-content-left">
<div class="widget-heading">Ella-Rose Henry</div>
@ -108,7 +109,7 @@
<div class="widget-content p-0">
<div class="widget-content-wrapper">
<div class="widget-content-left mr-3">
<img width="42" class="rounded-circle" src="assets/images/avatars/5.jpg" alt="">
<img width="42" class="rounded-circle" src="{% static "backend/images/blank.png" %}" alt="">
</div>
<div class="widget-content-left">
<div class="widget-heading">Ruben Tillman</div>
@ -130,7 +131,7 @@
<div class="widget-content p-0">
<div class="widget-content-wrapper">
<div class="widget-content-left mr-3">
<img width="42" class="rounded-circle" src="assets/images/avatars/4.jpg" alt="">
<img width="42" class="rounded-circle" src="{% static "backend/images/blank.png" %}" alt="">
</div>
<div class="widget-content-left">
<div class="widget-heading">Vinnie Wagstaff</div>
@ -152,7 +153,7 @@
<div class="widget-content p-0">
<div class="widget-content-wrapper">
<div class="widget-content-left mr-3">
<img width="42" class="rounded-circle" src="assets/images/avatars/3.jpg" alt="">
<img width="42" class="rounded-circle" src="{% static "backend/images/blank.png" %}" alt="">
</div>
<div class="widget-content-left">
<div class="widget-heading">Ella-Rose Henry</div>
@ -174,7 +175,7 @@
<div class="widget-content p-0">
<div class="widget-content-wrapper">
<div class="widget-content-left mr-3">
<img width="42" class="rounded-circle" src="assets/images/avatars/2.jpg" alt="">
<img width="42" class="rounded-circle" src="{% static "backend/images/blank.png" %}" alt="">
</div>
<div class="widget-content-left">
<div class="widget-heading">Ruben Tillman</div>