diff --git a/aleksis/core/mixins.py b/aleksis/core/mixins.py index 8ba220c1610321971f6b7c96ca20b7ce65fe0d5b..c4ec43e40839d788b9fecfeedc12403c5892c0c5 100644 --- a/aleksis/core/mixins.py +++ b/aleksis/core/mixins.py @@ -7,8 +7,10 @@ from django.db.models import QuerySet from django.forms.models import ModelFormMetaclass, ModelForm from easyaudit.models import CRUDEvent +from guardian.admin import GuardedModelAdmin from jsonstore.fields import JSONField, JSONFieldMixin from material.base import LayoutNode, Layout +from rules.contrib.admin import ObjectPermissionsModelAdmin class CRUDMixin(models.Model): @@ -226,3 +228,7 @@ class ExtensibleForm(ModelForm, metaclass=_ExtensibleFormMetaclass): cls.base_layout.append(node) cls.layout = Layout(*cls.base_layout) + + +class BaseModelAdmin(GuardedModelAdmin, ObjectPermissionsModelAdmin): + pass diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py index cac277a711763ed431e3ccd213842151d1969213..94338cf8bcec2032251864bc09434767a6bf0307 100644 --- a/aleksis/core/settings.py +++ b/aleksis/core/settings.py @@ -188,7 +188,6 @@ AUTH_PASSWORD_VALIDATORS = [ # Authentication backends are dynamically populated AUTHENTICATION_BACKENDS = [] -AUTHENTICATION_BACKENDS.append("rules.permissions.ObjectPermissionBackend") if _settings.get("ldap.uri", None): # LDAP dependencies are not necessarily installed, so import them here @@ -568,5 +567,4 @@ GUARDIAN_RAISE_403 = True ANONYMOUS_USER_NAME = None # Append authentication backends -AUTHENTICATION_BACKENDS.append("guardian.backends.ObjectPermissionBackend") -#AUTHENTICATION_BACKENDS.append("rules.permissions.ObjectPermissionBackend") +AUTHENTICATION_BACKENDS.append("rules.permissions.ObjectPermissionBackend") diff --git a/aleksis/core/util/predicates.py b/aleksis/core/util/predicates.py new file mode 100644 index 0000000000000000000000000000000000000000..7195cd45195effc014e36be78bc3117f1581ce31 --- /dev/null +++ b/aleksis/core/util/predicates.py @@ -0,0 +1,86 @@ +from django.contrib.auth.backends import ModelBackend +from django.contrib.auth.models import User +from django.db.models import Model +from django.http import HttpRequest +from guardian.backends import ObjectPermissionBackend +from guardian.shortcuts import get_objects_for_user +from rules import predicate + +from aleksis.core.util.core_helpers import has_person + +# 1. Global permissions (view all, add, change all, delete all) +# 2. Object permissions (view, change, delete) +# 3. Rules + + +def permission_validator(request: HttpRequest, perm: str) -> bool: + """ Checks whether the request user has a permission """ + + if request.user: + return request.user.has_perm(perm) + return False + + +def check_global_permission(user: User, perm: str) -> bool: + """ Checks whether a user has a global permission """ + + return ModelBackend().has_perm(user, perm) + + +def check_object_permission(user: User, perm: str, obj: Model) -> bool: + """ Checks whether a user has a permission on a object """ + + return ObjectPermissionBackend().has_perm(user, perm, obj) + + +def has_global_perm(perm: str): + """ Builds predicate which checks whether a user has a global permission """ + + name = "has_global_perm:{}".format(perm) + + @predicate(name) + def fn(user: User) -> bool: + return check_global_permission(user, perm) + + return fn + + +def has_object_perm(perm: str): + """ Builds predicate which checks whether a user has a permission on a object """ + + name = "has_global_perm:{}".format(perm) + + @predicate(name) + def fn(user: User, obj: Model) -> bool: + if not obj: + return False + return check_object_permission(user, perm, obj) + + return fn + + +def has_any_object(perm: str, klass): + """ Build predicate which checks whether a user has access to objects with the provided permission """ + + name = "has_any_object:{}".format(perm) + + @predicate(name) + def fn(user: User) -> bool: + objs = get_objects_for_user(user, perm, klass) + return len(objs) > 0 + + return fn + + +@predicate +def has_person_predicate(user: User) -> bool: + """ Predicate which checks whether a user has a linked person """ + + return has_person(user) + + +@predicate() +def is_person(user: User, obj: Model) -> bool: + """ Predicate which checks if the provided object is the person linked to the user object """ + + return user.person == obj