Update a lot of things

This commit is contained in:
Yohann D'ANELLO 2020-03-07 13:12:17 +01:00
parent a014a97e14
commit 30ce17b644
11 changed files with 75 additions and 62 deletions

View File

@ -78,6 +78,10 @@ def save_object(sender, instance, **kwargs):
user, ip = get_user_and_ip(sender) user, ip = get_user_and_ip(sender)
from django.contrib.auth.models import AnonymousUser
if isinstance(user, AnonymousUser):
user = None
if user is not None and instance._meta.label_lower == "auth.user" and previous: if user is not None and instance._meta.label_lower == "auth.user" and previous:
# Don't save last login modifications # Don't save last login modifications
if instance.last_login != previous.last_login: if instance.last_login != previous.last_login:

View File

@ -6,7 +6,7 @@ from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User from django.contrib.auth.models import User
from .forms import ProfileForm from .forms import ProfileForm
from .models import Club, Membership, Profile, Role from .models import Club, Membership, Profile, Role, RolePermissions
class ProfileInline(admin.StackedInline): class ProfileInline(admin.StackedInline):
@ -40,3 +40,4 @@ admin.site.register(User, CustomUserAdmin)
admin.site.register(Club) admin.site.register(Club)
admin.site.register(Membership) admin.site.register(Membership)
admin.site.register(Role) admin.site.register(Role)
admin.site.register(RolePermissions)

View File

@ -1,33 +1,37 @@
from django.contribs.contenttype.models import ContentType # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from member.models import Club, Membership, RolePermissions from member.models import Club, Membership, RolePermissions
from django.contrib.auth.backends import ModelBackend
class PermissionBackend(object): class PermissionBackend(ModelBackend):
supports_object_permissions = True supports_object_permissions = True
supports_anonymous_user = False supports_anonymous_user = False
supports_inactive_user = False supports_inactive_user = False
def authenticate(self, username, password): def permissions(self, user):
return None for membership in Membership.objects.filter(user=user).all():
if not membership.valid() or membership.roles is None:
def permissions(self, user, obj):
for membership in user.memberships.all():
if not membership.valid() or membership.role is None:
continue continue
for permission in RolePermissions.objects.get(role=membership.role).permissions.objects.all(): for role_permissions in RolePermissions.objects.filter(role=membership.roles).all():
for permission in role_permissions.permissions.all():
permission = permission.about(user=user, club=membership.club) permission = permission.about(user=user, club=membership.club)
yield permission yield permission
def has_perm(self, user_obj, perm, obj=None): def has_perm(self, user_obj, perm, obj=None):
if user_obj.is_superuser:
return True
if obj is None: if obj is None:
return False return False
perm = perm.split('_', 3) perm = perm.split('_', 3)
perm_type = perm[1] perm_type = perm[1]
perm_field = perm[2] if len(perm) == 3 else None perm_field = perm[2] if len(perm) == 3 else None
return any(permission.applies(obj, perm_type, perm_field) for obj in self.permissions(user_obj, obj)) return any(permission.applies(obj, perm_type, perm_field) for permission in self.permissions(user_obj))
def has_module_perms(self, user_obj, app_label):
return False
def get_all_permissions(self, user_obj, obj=None): def get_all_permissions(self, user_obj, obj=None):
if obj is None: return list(self.permissions(user_obj))
return []
else:
return list(self.permissions(user_obj, obj))

View File

@ -154,9 +154,9 @@ class Membership(models.Model):
def valid(self): def valid(self):
if self.date_end is not None: if self.date_end is not None:
return self.date_start <= datetime.datetime.now() < self.date_end return self.date_start.toordinal() <= datetime.datetime.now().toordinal() < self.date_end.toordinal()
else: else:
return self.date_start <= datetime.datetime.now() return self.date_start.toordinal() <= datetime.datetime.now().toordinal()
class Meta: class Meta:
verbose_name = _('membership') verbose_name = _('membership')

View File

@ -1,3 +1,6 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-lateré
from django.contrib import admin from django.contrib import admin
from .models import Permission from .models import Permission

View File

@ -1,3 +1,6 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.apps import AppConfig from django.apps import AppConfig

View File

@ -1,3 +1,6 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import functools import functools
import json import json
import operator import operator
@ -24,28 +27,25 @@ class InstancedPermission:
""" """
if self.type == 'add': if self.type == 'add':
if permission_type == self.type: if permission_type == self.type:
return self.query(obj) return obj in self.model.modelclass().objects.get(self.query)
if ContentType.objects.get_for_model(obj) != self.model: if ContentType.objects.get_for_model(obj) != self.model:
# The permission does not apply to the model # The permission does not apply to the model
return False return False
if self.permission is None:
if permission_type == self.type: if permission_type == self.type:
if field_name is not None: if field_name and field_name != self.field:
return field_name == self.field
else:
return True
else:
return False return False
elif obj in self.model.objects.get(self.query): return obj in self.model.model_class().objects.filter(self.query).all()
return True
else: else:
return False return False
def __repr__(self): def __repr__(self):
if self.field: if self.field:
return _("Can {type} {model}.{field} in {permission}").format(type=self.type, model=self.model, field=self.field, permission=self.permission) return _("Can {type} {model}.{field} in {query}").format(type=self.type, model=self.model, field=self.field, query=self.query)
else: else:
return _("Can {type} {model} in {permission}").format(type=self.type, model=self.model, permission=self.permission) return _("Can {type} {model} in {query}").format(type=self.type, model=self.model, query=self.query)
def __str__(self):
return self.__repr__()
class Permission(models.Model): class Permission(models.Model):
@ -61,24 +61,24 @@ class Permission(models.Model):
# A json encoded Q object with the following grammar # A json encoded Q object with the following grammar
# query -> [] | {} (the empty query representing all objects) # query -> [] | {} (the empty query representing all objects)
# query -> ['AND', query, …] AND multiple queries # query -> ["AND", query, …] AND multiple queries
# | ['OR', query, …] OR multiple queries # | ["OR", query, …] OR multiple queries
# | ['NOT', query] Opposite of query # | ["NOT", query] Opposite of query
# query -> {key: value, …} A list of fields and values of a Q object # query -> {key: value, …} A list of fields and values of a Q object
# key -> string A field name # key -> string A field name
# value -> int | string | bool | null Literal values # value -> int | string | bool | null Literal values
# | [parameter] A parameter # | [parameter] A parameter
# | {'F': oper} An F object # | {"F": oper} An F object
# oper -> [string] A parameter # oper -> [string] A parameter
# | ['ADD', oper, …] Sum multiple F objects or literal # | ["ADD", oper, …] Sum multiple F objects or literal
# | ['SUB', oper, oper] Substract two F objects or literal # | ["SUB", oper, oper] Substract two F objects or literal
# | ['MUL', oper, …] Multiply F objects or literals # | ["MUL", oper, …] Multiply F objects or literals
# | int | string | bool | null Literal values # | int | string | bool | null Literal values
# | ['F', string] A field # | ["F", string] A field
# #
# Examples: # Examples:
# Q(is_admin=True) := {'is_admin': ['TYPE', 'bool', 'True']} # Q(is_superuser=True) := {"is_superuser": true}
# ~Q(is_admin=True) := ['NOT', {'is_admin': ['TYPE', 'bool', 'True']}] # ~Q(is_superuser=True) := ["NOT", {"is_superuser": true}]
query = models.TextField() query = models.TextField()
type = models.CharField(max_length=15, choices=PERMISSION_TYPES) type = models.CharField(max_length=15, choices=PERMISSION_TYPES)
@ -94,23 +94,22 @@ class Permission(models.Model):
if self.field and self.type not in {'view', 'change'}: if self.field and self.type not in {'view', 'change'}:
raise ValidationError(_("Specifying field applies only to view and change permission types.")) raise ValidationError(_("Specifying field applies only to view and change permission types."))
def save(self): def save(self, **kwargs):
self.full_clean() self.full_clean()
super().save() super().save()
@staticmethod @staticmethod
def compute_f(_oper, **kwargs): def compute_f(oper, **kwargs):
oper = _oper
if isinstance(oper, list): if isinstance(oper, list):
if len(oper) == 1: if len(oper) == 1:
return kwargs[oper[0]].pk return kwargs[oper[0]].pk
elif len(oper) >= 2: elif len(oper) >= 2:
if oper[0] == 'ADD': if oper[0] == 'ADD':
return functools.reduce(operator.add, [compute_f(oper, **kwargs) for oper in oper[1:]]) return functools.reduce(operator.add, [Permission.compute_f(oper, **kwargs) for oper in oper[1:]])
elif oper[0] == 'SUB': elif oper[0] == 'SUB':
return compute_f(oper[1], **kwargs) - compute_f(oper[2], **kwargs) return Permission.compute_f(oper[1], **kwargs) - Permission.compute_f(oper[2], **kwargs)
elif oper[0] == 'MUL': elif oper[0] == 'MUL':
return functools.reduce(operator.mul, [compute_f(oper, **kwargs) for oper in oper[1:]]) return functools.reduce(operator.mul, [Permission.compute_f(oper, **kwargs) for oper in oper[1:]])
elif oper[0] == 'F': elif oper[0] == 'F':
return F(oper[1]) return F(oper[1])
else: else:
@ -118,9 +117,7 @@ class Permission(models.Model):
# TODO: find a better way to crash here # TODO: find a better way to crash here
raise Exception("F is wrong") raise Exception("F is wrong")
def _about(_self, _query, **kwargs): def _about(self, query, **kwargs):
self = _self
query = _query
if self.type == 'add': if self.type == 'add':
# Handle add permission differently # Handle add permission differently
return self._about_add(query, **kwargs) return self._about_add(query, **kwargs)
@ -145,7 +142,7 @@ class Permission(models.Model):
q_kwargs[key] = kwargs[value[0]].pk q_kwargs[key] = kwargs[value[0]].pk
elif isinstance(value, dict): elif isinstance(value, dict):
# It is an F object # It is an F object
q_kwargs[key] = compute_f(query['F'], **kwargs) q_kwargs[key] = Permission.compute_f(query['F'], **kwargs)
else: else:
q_kwargs[key] = value q_kwargs[key] = value
return Q(**q_kwargs) return Q(**q_kwargs)
@ -153,16 +150,15 @@ class Permission(models.Model):
# TODO: find a better way to crash here # TODO: find a better way to crash here
raise Exception("query {} is wrong".format(self.query)) raise Exception("query {} is wrong".format(self.query))
def _about_add(_self, _query, **kwargs): def _about_add(self, _query, **kwargs):
self = _self
query = _query query = _query
if len(query) == 0: if len(query) == 0:
return lambda _: True return lambda _: True
if isinstance(query, list): if isinstance(query, list):
if query[0] == 'AND': if query[0] == 'AND':
return lambda obj: functools.reduce(operator.and_, [self._about_add(query, **kwargs)(obj) for query in query[1:]]) return lambda obj: functools.reduce(operator.and_, [self._about_add(q, **kwargs)(obj) for q in query[1:]])
elif query[0] == 'OR': elif query[0] == 'OR':
return lambda obj: functools.reduce(operator.or_, [self._about_add(query, **kwargs)(obj) for query in query[1:]]) return lambda obj: functools.reduce(operator.or_, [self._about_add(q, **kwargs)(obj) for q in query[1:]])
elif query[0] == 'NOT': elif query[0] == 'NOT':
return lambda obj: not self._about_add(query[1], **kwargs)(obj) return lambda obj: not self._about_add(query[1], **kwargs)(obj)
elif isinstance(query, dict): elif isinstance(query, dict):
@ -174,7 +170,7 @@ class Permission(models.Model):
q_kwargs[key] = kwargs[value[0]].pk q_kwargs[key] = kwargs[value[0]].pk
elif isinstance(value, dict): elif isinstance(value, dict):
# It is an F object # It is an F object
q_kwargs[key] = compute_f(query['F'], **kwargs) q_kwargs[key] = Permission.compute_f(query['F'], **kwargs)
else: else:
q_kwargs[key] = value q_kwargs[key] = value
def func(obj): def func(obj):

View File

@ -1,3 +1,6 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.test import TestCase from django.test import TestCase
# Create your tests here. # Create your tests here.

View File

@ -1,3 +1,6 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.shortcuts import render from django.shortcuts import render
# Create your views here. # Create your views here.

View File

@ -37,7 +37,6 @@ INSTALLED_APPS = [
# External apps # External apps
'polymorphic', 'polymorphic',
'guardian',
'reversion', 'reversion',
'crispy_forms', 'crispy_forms',
'django_tables2', 'django_tables2',
@ -134,8 +133,8 @@ PASSWORD_HASHERS = [
# Django Guardian object permissions # Django Guardian object permissions
AUTHENTICATION_BACKENDS = ( AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', # this is default #'django.contrib.auth.backends.ModelBackend', # this is default
'guardian.backends.ObjectPermissionBackend', 'member.backends.PermissionBackend',
'cas.backends.CASBackend', 'cas.backends.CASBackend',
) )
@ -153,8 +152,6 @@ REST_FRAMEWORK = {
ANONYMOUS_USER_NAME = None # Disable guardian anonymous user ANONYMOUS_USER_NAME = None # Disable guardian anonymous user
GUARDIAN_GET_CONTENT_TYPE = 'polymorphic.contrib.guardian.get_polymorphic_base_content_type'
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/ # https://docs.djangoproject.com/en/2.2/topics/i18n/

View File

@ -9,7 +9,6 @@ django-cas-server==1.1.0
django-crispy-forms==1.7.2 django-crispy-forms==1.7.2
django-extensions==2.1.9 django-extensions==2.1.9
django-filter==2.2.0 django-filter==2.2.0
django-guardian==2.1.0
django-polymorphic==2.0.3 django-polymorphic==2.0.3
djangorestframework==3.9.0 djangorestframework==3.9.0
django-rest-polymorphic==0.1.8 django-rest-polymorphic==0.1.8