From 24ea4c0a521b2253f2d86f07def25e92240150c1 Mon Sep 17 00:00:00 2001
From: Yohann D'ANELLO <yohann.danello@gmail.com>
Date: Fri, 20 Mar 2020 15:58:14 +0100
Subject: [PATCH] Comment code

---
 apps/permission/apps.py               |  1 -
 apps/permission/backends.py           | 13 +++++++++++
 apps/permission/models.py             | 32 +++++++++++++++++++++++++--
 apps/permission/permissions.py        |  5 +++++
 apps/permission/templatetags/perms.py |  6 +++++
 apps/permission/tests.py              |  6 -----
 apps/permission/views.py              |  6 -----
 note_kfet/settings/base.py            |  9 +++-----
 8 files changed, 57 insertions(+), 21 deletions(-)
 delete mode 100644 apps/permission/tests.py
 delete mode 100644 apps/permission/views.py

diff --git a/apps/permission/apps.py b/apps/permission/apps.py
index c0caa41b..2287fec4 100644
--- a/apps/permission/apps.py
+++ b/apps/permission/apps.py
@@ -9,7 +9,6 @@ class PermissionConfig(AppConfig):
     name = 'permission'
 
     def ready(self):
-        # noinspection PyUnresolvedReferences
         from . import signals
         pre_save.connect(signals.pre_save_object)
         pre_delete.connect(signals.pre_delete_object)
diff --git a/apps/permission/backends.py b/apps/permission/backends.py
index a3b49ae0..3d911b1a 100644
--- a/apps/permission/backends.py
+++ b/apps/permission/backends.py
@@ -13,18 +13,31 @@ from member.models import Membership, Club
 
 
 class PermissionBackend(ModelBackend):
+    """
+    Manage permissions of users
+    """
     supports_object_permissions = True
     supports_anonymous_user = False
     supports_inactive_user = False
 
     @staticmethod
     def permissions(user, model, type):
+        """
+        List all permissions of the given user that applies to a given model and a give type
+        :param user: The owner of the permissions
+        :param model: The model that the permissions shoud apply
+        :param type: The type of the permissions: view, change, add or delete
+        :return: A generator of the requested permissions
+        """
         for permission in Permission.objects.annotate(club=F("rolepermissions__role__membership__club")) \
                 .filter(
             rolepermissions__role__membership__user=user,
             model__app_label=model.app_label,  # For polymorphic models, we don't filter on model type
             type=type,
         ).all():
+            if not isinstance(model, permission.model.__class__):
+                continue
+
             club = Club.objects.get(pk=permission.club)
             permission = permission.about(
                 user=user,
diff --git a/apps/permission/models.py b/apps/permission/models.py
index 1c076918..109c1875 100644
--- a/apps/permission/models.py
+++ b/apps/permission/models.py
@@ -57,6 +57,10 @@ class InstancedPermission:
             return False
 
     def update_query(self):
+        """
+        The query is not analysed in a first time. It is analysed at most once if needed.
+        :return:
+        """
         if not self.query:
             # noinspection PyProtectedMember
             self.query = Permission._about(self.raw_query, **self.kwargs)
@@ -72,6 +76,10 @@ class InstancedPermission:
 
 
 class PermissionMask(models.Model):
+    """
+    Permissions that are hidden behind a mask
+    """
+
     rank = models.PositiveSmallIntegerField(
         unique=True,
         verbose_name=_('rank'),
@@ -106,9 +114,9 @@ class Permission(models.Model):
     #  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
+    #         | [parameter, …]               A parameter. See compute_param for more details.
     #         | {"F": oper}                  An F object
-    #  oper  -> [string]                     A parameter
+    #  oper  -> [string, …]                  A parameter. See compute_param for more details.
     #         | ["ADD", oper, …]             Sum multiple F objects or literal
     #         | ["SUB", oper, oper]          Substract two F objects or literal
     #         | ["MUL", oper, …]             Multiply F objects or literals
@@ -164,6 +172,18 @@ class Permission(models.Model):
 
     @staticmethod
     def compute_param(value, **kwargs):
+        """
+        A parameter is given by a list. The first argument is the name of the parameter.
+        The parameters are the user, the club, and some classes (Note, ...)
+        If there are more arguments in the list, then attributes are queried.
+        For example, ["user", "note", "balance"] will return the balance of the note of the user.
+        If an argument is a list, then this is interpreted with a function call:
+            First argument is the name of the function, next arguments are parameters, and if there is a dict,
+            then the dict is given as kwargs.
+            For example: NoteUser.objects.filter(user__memberships__club__name="Kfet").all() is translated by:
+            ["NoteUser", "objects", ["filter", {"user__memberships__club__name": "Kfet"}], ["all"]]
+        """
+
         if not isinstance(value, list):
             return value
 
@@ -192,6 +212,12 @@ class Permission(models.Model):
 
     @staticmethod
     def _about(query, **kwargs):
+        """
+        Translate JSON query into a Q query.
+        :param query: The JSON query
+        :param kwargs: Additional params
+        :return: A Q object
+        """
         if len(query) == 0:
             # The query is either [] or {} and
             # applies to all objects of the model
@@ -204,6 +230,8 @@ class Permission(models.Model):
                 return functools.reduce(operator.or_, [Permission._about(query, **kwargs) for query in query[1:]])
             elif query[0] == 'NOT':
                 return ~Permission._about(query[1], **kwargs)
+            else:
+                return Q(pk=F("pk"))
         elif isinstance(query, dict):
             q_kwargs = {}
             for key in query:
diff --git a/apps/permission/permissions.py b/apps/permission/permissions.py
index 1cbae474..d9816a63 100644
--- a/apps/permission/permissions.py
+++ b/apps/permission/permissions.py
@@ -7,6 +7,11 @@ SAFE_METHODS = ('HEAD', 'OPTIONS', )
 
 
 class StrongDjangoObjectPermissions(DjangoObjectPermissions):
+    """
+    Default DjangoObjectPermissions grant view permission to all.
+    This is a simple patch of this class that controls view access.
+    """
+
     perms_map = {
         'GET': ['%(app_label)s.view_%(model_name)s'],
         'OPTIONS': [],
diff --git a/apps/permission/templatetags/perms.py b/apps/permission/templatetags/perms.py
index 33cd46a6..8f2a0006 100644
--- a/apps/permission/templatetags/perms.py
+++ b/apps/permission/templatetags/perms.py
@@ -12,6 +12,9 @@ from permission.backends import PermissionBackend
 
 @stringfilter
 def not_empty_model_list(model_name):
+    """
+    Return True if and only if the current user has right to see any object of the given model.
+    """
     user = get_current_authenticated_user()
     session = get_current_session()
     if user is None:
@@ -29,6 +32,9 @@ def not_empty_model_list(model_name):
 
 @stringfilter
 def not_empty_model_change_list(model_name):
+    """
+    Return True if and only if the current user has right to change any object of the given model.
+    """
     user = get_current_authenticated_user()
     session = get_current_session()
     if user is None:
diff --git a/apps/permission/tests.py b/apps/permission/tests.py
deleted file mode 100644
index b5d5752e..00000000
--- a/apps/permission/tests.py
+++ /dev/null
@@ -1,6 +0,0 @@
-# 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
deleted file mode 100644
index 8d81fd33..00000000
--- a/apps/permission/views.py
+++ /dev/null
@@ -1,6 +0,0 @@
-# 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 e56555bd..bf983f3b 100644
--- a/note_kfet/settings/base.py
+++ b/note_kfet/settings/base.py
@@ -127,17 +127,14 @@ PASSWORD_HASHERS = [
     'member.hashers.CustomNK15Hasher',
 ]
 
-# Django Guardian object permissions
-
 AUTHENTICATION_BACKENDS = (
-    'permission.backends.PermissionBackend',
-    'cas.backends.CASBackend',
+    'permission.backends.PermissionBackend',  # Custom role-based permission system
+    'cas.backends.CASBackend',  # For CAS connections
 )
 
 REST_FRAMEWORK = {
-    # Use Django's standard `django.contrib.auth` permissions,
-    # or allow read-only access for unauthenticated users.
     'DEFAULT_PERMISSION_CLASSES': [
+        # Control API access with our role-based permission system
         'permission.permissions.StrongDjangoObjectPermissions',
     ],
     'DEFAULT_AUTHENTICATION_CLASSES': [
-- 
GitLab