From 30ce17b644c238183f5e0904c1df621dc209d323 Mon Sep 17 00:00:00 2001
From: Yohann D'ANELLO <yohann.danello@gmail.com>
Date: Sat, 7 Mar 2020 13:12:17 +0100
Subject: [PATCH] Update a lot of things

---
 apps/logs/signals.py       |  4 +++
 apps/member/admin.py       |  3 +-
 apps/member/backends.py    | 36 +++++++++++---------
 apps/member/models.py      |  4 +--
 apps/permission/admin.py   |  3 ++
 apps/permission/apps.py    |  3 ++
 apps/permission/models.py  | 70 ++++++++++++++++++--------------------
 apps/permission/tests.py   |  3 ++
 apps/permission/views.py   |  3 ++
 note_kfet/settings/base.py |  7 ++--
 requirements.txt           |  1 -
 11 files changed, 75 insertions(+), 62 deletions(-)

diff --git a/apps/logs/signals.py b/apps/logs/signals.py
index 55e0f041..13194e5b 100644
--- a/apps/logs/signals.py
+++ b/apps/logs/signals.py
@@ -78,6 +78,10 @@ def save_object(sender, instance, **kwargs):
 
     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:
         # Don't save last login modifications
         if instance.last_login != previous.last_login:
diff --git a/apps/member/admin.py b/apps/member/admin.py
index fb107377..70b00459 100644
--- a/apps/member/admin.py
+++ b/apps/member/admin.py
@@ -6,7 +6,7 @@ from django.contrib.auth.admin import UserAdmin
 from django.contrib.auth.models import User
 
 from .forms import ProfileForm
-from .models import Club, Membership, Profile, Role
+from .models import Club, Membership, Profile, Role, RolePermissions
 
 
 class ProfileInline(admin.StackedInline):
@@ -40,3 +40,4 @@ admin.site.register(User, CustomUserAdmin)
 admin.site.register(Club)
 admin.site.register(Membership)
 admin.site.register(Role)
+admin.site.register(RolePermissions)
diff --git a/apps/member/backends.py b/apps/member/backends.py
index 9ef9706f..db227cdb 100644
--- a/apps/member/backends.py
+++ b/apps/member/backends.py
@@ -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 django.contrib.auth.backends import ModelBackend
 
 
-class PermissionBackend(object):
+class PermissionBackend(ModelBackend):
     supports_object_permissions = True
     supports_anonymous_user = False
     supports_inactive_user = False
 
-    def authenticate(self, username, password):
-        return None
-
-    def permissions(self, user, obj):
-        for membership in user.memberships.all():
-            if not membership.valid() or membership.role is None:
+    def permissions(self, user):
+        for membership in Membership.objects.filter(user=user).all():
+            if not membership.valid() or membership.roles is None:
                 continue
-            for permission in RolePermissions.objects.get(role=membership.role).permissions.objects.all():
-                permission = permission.about(user=user, club=membership.club)
-                yield permission
+            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)
+                    yield permission
 
     def has_perm(self, user_obj, perm, obj=None):
+        if user_obj.is_superuser:
+            return True
+
         if obj is None:
             return False
         perm = perm.split('_', 3)
         perm_type = perm[1]
         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):
-        if obj is None:
-            return []
-        else:
-            return list(self.permissions(user_obj, obj))
+        return list(self.permissions(user_obj))
diff --git a/apps/member/models.py b/apps/member/models.py
index c90ab15c..1ca82af0 100644
--- a/apps/member/models.py
+++ b/apps/member/models.py
@@ -154,9 +154,9 @@ class Membership(models.Model):
 
     def valid(self):
         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:
-            return self.date_start <= datetime.datetime.now()
+            return self.date_start.toordinal() <= datetime.datetime.now().toordinal()
 
     class Meta:
         verbose_name = _('membership')
diff --git a/apps/permission/admin.py b/apps/permission/admin.py
index e93de0c5..f7a9b4b5 100644
--- a/apps/permission/admin.py
+++ b/apps/permission/admin.py
@@ -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 .models import Permission
diff --git a/apps/permission/apps.py b/apps/permission/apps.py
index 0f46ef08..c9c912a5 100644
--- a/apps/permission/apps.py
+++ b/apps/permission/apps.py
@@ -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
 
 
diff --git a/apps/permission/models.py b/apps/permission/models.py
index 000fe69f..9584f59f 100644
--- a/apps/permission/models.py
+++ b/apps/permission/models.py
@@ -1,3 +1,6 @@
+# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
+# SPDX-License-Identifier: GPL-3.0-or-later
+
 import functools
 import json
 import operator
@@ -24,28 +27,25 @@ class InstancedPermission:
         """
         if self.type == 'add':
             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:
             # The permission does not apply to the model
             return False
-        if self.permission is None:
-            if permission_type == self.type:
-                if field_name is not None:
-                    return field_name == self.field
-                else:
-                    return True
-            else:
+        if permission_type == self.type:
+            if field_name and field_name != self.field:
                 return False
-        elif obj in self.model.objects.get(self.query):
-            return True
+            return obj in self.model.model_class().objects.filter(self.query).all()
         else:
             return False
 
     def __repr__(self):
         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:
-            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):
@@ -61,24 +61,24 @@ class Permission(models.Model):
 
     # A json encoded Q object with the following grammar
     #  query -> [] | {}  (the empty query representing all objects)
-    #  query -> ['AND', query, …]            AND multiple queries
-    #         | ['OR', query, …]             OR multiple queries
-    #         | ['NOT', query]               Opposite of query
+    #  query -> ["AND", query, …]            AND multiple queries
+    #         | ["OR", query, …]             OR multiple queries
+    #         | ["NOT", query]               Opposite of query
     #  query -> {key: value, …}              A list of fields and values of a Q object
     #  key   -> string                       A field name
     #  value -> int | string | bool | null   Literal values
     #         | [parameter]                  A parameter
-    #         | {'F': oper}                  An F object
+    #         | {"F": oper}                  An F object
     #  oper  -> [string]                     A parameter
-    #         | ['ADD', oper, …]             Sum multiple F objects or literal
-    #         | ['SUB', oper, oper]          Substract two F objects or literal
-    #         | ['MUL', oper, …]             Multiply F objects or literals
+    #         | ["ADD", oper, …]             Sum multiple F objects or literal
+    #         | ["SUB", oper, oper]          Substract two F objects or literal
+    #         | ["MUL", oper, …]             Multiply F objects or literals
     #         | int | string | bool | null   Literal values
-    #         | ['F', string]                A field
+    #         | ["F", string]                A field
     #
     # Examples:
-    #  Q(is_admin=True)  := {'is_admin': ['TYPE', 'bool', 'True']}
-    #  ~Q(is_admin=True) := ['NOT', {'is_admin': ['TYPE', 'bool', 'True']}]
+    #  Q(is_superuser=True)  := {"is_superuser": true}
+    #  ~Q(is_superuser=True) := ["NOT", {"is_superuser": true}]
     query = models.TextField()
 
     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'}:
             raise ValidationError(_("Specifying field applies only to view and change permission types."))
 
-    def save(self):
+    def save(self, **kwargs):
         self.full_clean()
         super().save()
 
     @staticmethod
-    def compute_f(_oper, **kwargs):
-        oper = _oper
+    def compute_f(oper, **kwargs):
         if isinstance(oper, list):
             if len(oper) == 1:
                 return kwargs[oper[0]].pk
             elif len(oper) >= 2:
                 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':
-                    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':
-                    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':
                     return F(oper[1])
         else:
@@ -118,9 +117,7 @@ class Permission(models.Model):
         # TODO: find a better way to crash here
         raise Exception("F is wrong")
 
-    def _about(_self, _query, **kwargs):
-        self = _self
-        query = _query
+    def _about(self, query, **kwargs):
         if self.type == 'add':
             # Handle add permission differently
             return self._about_add(query, **kwargs)
@@ -145,7 +142,7 @@ class Permission(models.Model):
                     q_kwargs[key] = kwargs[value[0]].pk
                 elif isinstance(value, dict):
                     # It is an F object
-                    q_kwargs[key] = compute_f(query['F'], **kwargs)
+                    q_kwargs[key] = Permission.compute_f(query['F'], **kwargs)
                 else:
                     q_kwargs[key] = value
             return Q(**q_kwargs)
@@ -153,16 +150,15 @@ class Permission(models.Model):
             # TODO: find a better way to crash here
             raise Exception("query {} is wrong".format(self.query))
 
-    def _about_add(_self, _query, **kwargs):
-        self = _self
+    def _about_add(self, _query, **kwargs):
         query = _query
         if len(query) == 0:
             return lambda _: True
         if isinstance(query, list):
             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':
-                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':
                 return lambda obj: not self._about_add(query[1], **kwargs)(obj)
         elif isinstance(query, dict):
@@ -174,7 +170,7 @@ class Permission(models.Model):
                     q_kwargs[key] = kwargs[value[0]].pk
                 elif isinstance(value, dict):
                     # It is an F object
-                    q_kwargs[key] = compute_f(query['F'], **kwargs)
+                    q_kwargs[key] = Permission.compute_f(query['F'], **kwargs)
                 else:
                     q_kwargs[key] = value
             def func(obj):
diff --git a/apps/permission/tests.py b/apps/permission/tests.py
index 7ce503c2..b5d5752e 100644
--- a/apps/permission/tests.py
+++ b/apps/permission/tests.py
@@ -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
 
 # Create your tests here.
diff --git a/apps/permission/views.py b/apps/permission/views.py
index 91ea44a2..8d81fd33 100644
--- a/apps/permission/views.py
+++ b/apps/permission/views.py
@@ -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
 
 # Create your views here.
diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py
index 63b7ff24..20937fac 100644
--- a/note_kfet/settings/base.py
+++ b/note_kfet/settings/base.py
@@ -37,7 +37,6 @@ INSTALLED_APPS = [
 
     # External apps
     'polymorphic',
-    'guardian',
     'reversion',
     'crispy_forms',
     'django_tables2',
@@ -134,8 +133,8 @@ PASSWORD_HASHERS = [
 # Django Guardian object permissions
 
 AUTHENTICATION_BACKENDS = (
-    'django.contrib.auth.backends.ModelBackend',  # this is default
-    'guardian.backends.ObjectPermissionBackend',
+    #'django.contrib.auth.backends.ModelBackend',  # this is default
+    'member.backends.PermissionBackend',
     'cas.backends.CASBackend',
 )
 
@@ -153,8 +152,6 @@ REST_FRAMEWORK = {
 
 ANONYMOUS_USER_NAME = None  # Disable guardian anonymous user
 
-GUARDIAN_GET_CONTENT_TYPE = 'polymorphic.contrib.guardian.get_polymorphic_base_content_type'
-
 # Internationalization
 # https://docs.djangoproject.com/en/2.2/topics/i18n/
 
diff --git a/requirements.txt b/requirements.txt
index 244690bc..9a5eaa22 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -9,7 +9,6 @@ django-cas-server==1.1.0
 django-crispy-forms==1.7.2
 django-extensions==2.1.9
 django-filter==2.2.0
-django-guardian==2.1.0
 django-polymorphic==2.0.3
 djangorestframework==3.9.0
 django-rest-polymorphic==0.1.8
-- 
GitLab