Implement organization rules

This commit is contained in:
Tobias Kunze 2025-03-20 16:50:56 +01:00
parent f11c1ca5bc
commit 21ff6fe7d0
5 changed files with 41 additions and 33 deletions

View file

@ -1,8 +1,9 @@
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rules.contrib.models import RulesModelBase, RulesModelMixin
class ServalaModelMixin(models.Model): class ServalaModelMixin(RulesModelMixin, models.Model, metaclass=RulesModelBase):
created_at = models.DateTimeField( created_at = models.DateTimeField(
auto_now_add=True, editable=False, verbose_name=_("Created") auto_now_add=True, editable=False, verbose_name=_("Created")
) )

View file

@ -1,3 +1,4 @@
import rules
import urlman import urlman
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
@ -6,7 +7,8 @@ from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django_scopes import ScopedManager from django_scopes import ScopedManager
from .mixins import ServalaModelMixin from servala.core import rules as perms
from servala.core.models.mixins import ServalaModelMixin
class Organization(ServalaModelMixin, models.Model): class Organization(ServalaModelMixin, models.Model):
@ -65,6 +67,12 @@ class Organization(ServalaModelMixin, models.Model):
class Meta: class Meta:
verbose_name = _("Organization") verbose_name = _("Organization")
verbose_name_plural = _("Organizations") verbose_name_plural = _("Organizations")
rules_permissions = {
"view": rules.is_staff | perms.is_organization_member,
"change": rules.is_staff | perms.is_organization_admin,
"delete": rules.is_staff | perms.is_organization_owner,
"add": rules.is_authenticated,
}
def __str__(self): def __str__(self):
return self.name return self.name

View file

@ -1,4 +1,8 @@
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager from django.contrib.auth.models import (
AbstractBaseUser,
BaseUserManager,
PermissionsMixin,
)
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -32,7 +36,7 @@ class UserManager(BaseUserManager):
return self.create_user(email, password, **extra_fields) return self.create_user(email, password, **extra_fields)
class User(ServalaModelMixin, AbstractBaseUser): class User(ServalaModelMixin, PermissionsMixin, AbstractBaseUser):
"""The Django model provides a password and last_login field.""" """The Django model provides a password and last_login field."""
objects = UserManager() objects = UserManager()
@ -71,31 +75,3 @@ class User(ServalaModelMixin, AbstractBaseUser):
def normalize_username(self, username): def normalize_username(self, username):
return super().normalize_username(username).strip().lower() return super().normalize_username(username).strip().lower()
def has_perm(self, perm, obj=None):
"""
Return True if the user has the specified permission.
Superusers automatically have all permissions.
"""
return self.is_superuser
def has_module_perms(self, app_label):
"""
Return True if the user has any permissions in the given app label.
Superusers automatically have all permissions.
"""
return self.is_superuser
def get_all_permissions(self, obj=None):
"""
Return a set of permission strings that the user has.
Superusers have all permissions.
"""
if self.is_superuser:
from django.contrib.auth.models import Permission
return {
f"{perm.content_type.app_label}.{perm.codename}"
for perm in Permission.objects.all()
}
return set()

23
src/servala/core/rules.py Normal file
View file

@ -0,0 +1,23 @@
import rules
def has_organization_role(user, org, roles):
memberships = org.memberships.all().filter(user=user)
if roles:
memberships = memberships.filter(role__in=roles)
return memberships.exists()
@rules.predicate
def is_organization_owner(user, org):
return has_organization_role(user, org, ["owner"])
@rules.predicate
def is_organization_admin(user, org):
return has_organization_role(user, org, ["owner", "admin"])
@rules.predicate
def is_organization_member(user, org):
return has_organization_role(user, org, None)

View file

@ -97,7 +97,7 @@ INSTALLED_APPS = [
"django.contrib.staticfiles", "django.contrib.staticfiles",
"django.forms", "django.forms",
"template_partials", "template_partials",
"rules", "rules.apps.AutodiscoverRulesConfig",
# The frontend app is loaded early in order to supersede some allauth views/behaviour # The frontend app is loaded early in order to supersede some allauth views/behaviour
"servala.frontend", "servala.frontend",
"allauth", "allauth",