summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorÉtienne Loks <etienne.loks@iggdrasil.net>2024-10-23 18:51:15 +0200
committerÉtienne Loks <etienne.loks@iggdrasil.net>2025-02-19 14:43:48 +0100
commit6f59b9e36a0971b3deb44562062a878eb26beedf (patch)
treee22db164f77fc0ba6e30a539350bb5a37f36f5a6
parentbe063a7032971db7c00a160595e69e1e67dd2c9f (diff)
downloadIshtar-6f59b9e36a0971b3deb44562062a878eb26beedf.tar.bz2
Ishtar-6f59b9e36a0971b3deb44562062a878eb26beedf.zip
✨ permissions refactoring: generate permissions, adapt permissions checks
-rw-r--r--archaeological_context_records/models.py7
-rw-r--r--archaeological_context_records/tests.py73
-rw-r--r--archaeological_context_records/wizards.py2
-rw-r--r--archaeological_files/models.py8
-rw-r--r--archaeological_files/wizards.py2
-rw-r--r--archaeological_finds/models_finds.py7
-rw-r--r--archaeological_finds/models_treatments.py15
-rw-r--r--archaeological_finds/tests.py40
-rw-r--r--archaeological_finds/wizards.py6
-rw-r--r--archaeological_operations/models.py14
-rw-r--r--archaeological_operations/tests.py70
-rw-r--r--archaeological_operations/views.py5
-rw-r--r--archaeological_operations/wizards.py10
-rw-r--r--archaeological_warehouse/tests.py24
-rw-r--r--ishtar_common/admin.py3
-rw-r--r--ishtar_common/models.py154
-rw-r--r--ishtar_common/models_common.py7
-rw-r--r--ishtar_common/utils.py14
-rw-r--r--ishtar_common/views_item.py1
-rw-r--r--ishtar_common/wizards.py36
20 files changed, 389 insertions, 109 deletions
diff --git a/archaeological_context_records/models.py b/archaeological_context_records/models.py
index 2611b7dfd..a88a7d161 100644
--- a/archaeological_context_records/models.py
+++ b/archaeological_context_records/models.py
@@ -1234,7 +1234,8 @@ class ContextRecord(
@classmethod
def get_owns(
- cls, user, menu_filtr=None, limit=None, values=None, get_short_menu_class=None
+ cls, user, menu_filtr=None, limit=None, values=None, get_short_menu_class=None,
+ no_auth_check=False, query=False
):
replace_query = None
if menu_filtr and "operation" in menu_filtr:
@@ -1245,7 +1246,11 @@ class ContextRecord(
limit=limit,
values=values,
get_short_menu_class=get_short_menu_class,
+ no_auth_check=no_auth_check,
+ query=query
)
+ if query:
+ return owns
return cls._return_get_owns(owns, values, get_short_menu_class)
def full_label(self):
diff --git a/archaeological_context_records/tests.py b/archaeological_context_records/tests.py
index efeab4670..8a16facf5 100644
--- a/archaeological_context_records/tests.py
+++ b/archaeological_context_records/tests.py
@@ -23,7 +23,7 @@ import locale
from django.apps import apps
from django.conf import settings
-from django.contrib.auth.models import Permission
+from django.contrib.auth.models import Permission, Group
from django.core.exceptions import ValidationError
from django.core.files.uploadedfile import SimpleUploadedFile
from django.template.defaultfilters import slugify
@@ -32,14 +32,15 @@ from django.urls import reverse
from django.utils.translation import pgettext_lazy
from ishtar_common.models import (
- IshtarSiteProfile,
+ Area,
+ get_current_profile,
ImporterModel,
ImporterType,
- UserProfile,
+ IshtarSiteProfile,
+ IshtarUser,
ProfileType,
Town,
- Area,
- get_current_profile,
+ UserProfile,
)
from ishtar_common import forms_common
@@ -945,21 +946,35 @@ class ContextRecordPermissionTest(ContextRecordInit, TestCase):
fixtures = CONTEXT_RECORD_TOWNS_FIXTURES
def setUp(self):
- IshtarSiteProfile.objects.create()
- self.username, self.password, self.user = create_superuser()
- self.alt_username, self.alt_password, self.alt_user = create_user()
- self.alt_user.user_permissions.add(
+ profile_type = ProfileType.objects.create(
+ label="xxCollaborateur",
+ txt_idx="xxcollaborator",
+ )
+ gp = Group.objects.create(name="xxUE rattachées : voir et modification")
+ gp.permissions.add(
Permission.objects.get(codename="view_own_contextrecord")
)
- self.alt_user.user_permissions.add(
+ gp.permissions.add(
Permission.objects.get(codename="change_own_contextrecord")
)
+ profile_type.groups.add(gp)
+
+ IshtarSiteProfile.objects.create()
+ self.username, self.password, self.user = create_superuser()
+ self.alt_username, self.alt_password, self.alt_user = create_user()
+
+ UserProfile.objects.create(
+ profile_type=profile_type,
+ person=self.alt_user.ishtaruser.person,
+ current=True,
+ )
+
# nosec: hard coded password for test purposes
self.alt_username2, self.alt_password2, self.alt_user2 = create_user( # nosec
username="luke", password="iamyourfather"
)
profile = UserProfile.objects.create(
- profile_type=ProfileType.objects.get(txt_idx="collaborator"),
+ profile_type=profile_type,
person=self.alt_user2.ishtaruser.person,
current=True,
)
@@ -984,6 +999,9 @@ class ContextRecordPermissionTest(ContextRecordInit, TestCase):
self.cr_1 = self.context_records[0]
self.cr_2 = self.context_records[1]
+ self.alt_user.ishtaruser.generate_permission()
+ self.alt_user2.ishtaruser.generate_permission()
+
def test_own_search(self):
# no result when no authentification
c = Client()
@@ -1390,29 +1408,42 @@ class ContextRecordRelationTest(ContextRecordInit, TestCase):
fixtures = CONTEXT_RECORD_TOWNS_FIXTURES
def setUp(self):
- # nosec: hard coded password for test purposes
- self.username, self.password, user = create_user( # nosec
- username="Gandalf", password="ushallpass"
+ profile_type = ProfileType.objects.create(
+ label="xxCollaborateur",
+ txt_idx="xxcollaborator",
)
- user.user_permissions.add(
+ gp = Group.objects.create(name="xxUE rattachées : voir et modification")
+ gp.permissions.add(
Permission.objects.get(codename="view_own_contextrecord")
)
- user.user_permissions.add(
+ gp.permissions.add(
Permission.objects.get(codename="change_own_contextrecord")
)
+ profile_type.groups.add(gp)
+
+ # nosec: hard coded password for test purposes
+ self.username, self.password, user = create_user( # nosec
+ username="Gandalf", password="ushallpass"
+ )
+ UserProfile.objects.create(
+ profile_type=profile_type,
+ person=user.ishtaruser.person,
+ current=True,
+ )
self.create_context_record({"label": "CR 1"}, user=user)
self.create_context_record({"label": "CR 2"}, user=user)
self.create_context_record({"label": "CR 3"}, user=user)
+ IshtarUser.objects.get(pk=user.pk).generate_permission()
self.username2, self.password2, user2 = create_user( # nosec
username="Saroumane", password="ushallnotpass"
)
- user2.user_permissions.add(
- Permission.objects.get(codename="view_own_contextrecord")
- )
- user2.user_permissions.add(
- Permission.objects.get(codename="change_own_contextrecord")
+ UserProfile.objects.create(
+ profile_type=profile_type,
+ person=user2.ishtaruser.person,
+ current=True,
)
+ IshtarUser.objects.get(pk=user2.pk).generate_permission()
self.cr1, self.cr2, self.cr3 = self.context_records
self.sym_rel_type, __ = models.RelationType.objects.get_or_create(
symmetrical=True, txt_idx="sym", logical_relation="equal"
diff --git a/archaeological_context_records/wizards.py b/archaeological_context_records/wizards.py
index 35b4e02b4..4524681fc 100644
--- a/archaeological_context_records/wizards.py
+++ b/archaeological_context_records/wizards.py
@@ -31,7 +31,7 @@ class RecordSearch(SearchWizard):
class RecordWizard(Wizard):
model = models.ContextRecord
- edit = False
+ modification = False
wizard_done_window = reverse_lazy("show-contextrecord")
relations_step_key = "relations"
redirect_url = "record_modification"
diff --git a/archaeological_files/models.py b/archaeological_files/models.py
index 1f164c30d..778b1c251 100644
--- a/archaeological_files/models.py
+++ b/archaeological_files/models.py
@@ -1060,11 +1060,15 @@ class File(
@classmethod
def get_owns(
- cls, user, menu_filtr=None, limit=None, values=None, get_short_menu_class=False
+ cls, user, menu_filtr=None, limit=None, values=None, get_short_menu_class=False,
+ no_auth_check=False, query=False
):
owns = super(File, cls).get_owns(
- user, limit=limit, values=values, get_short_menu_class=get_short_menu_class
+ user, limit=limit, values=values, get_short_menu_class=get_short_menu_class,
+ no_auth_check=no_auth_check, query=query
)
+ if query:
+ return owns
return cls._return_get_owns(owns, values, get_short_menu_class)
def get_dynamic_values(self, prefix, values, filtr=None):
diff --git a/archaeological_files/wizards.py b/archaeological_files/wizards.py
index 8178e1c11..dd3f1e828 100644
--- a/archaeological_files/wizards.py
+++ b/archaeological_files/wizards.py
@@ -242,7 +242,7 @@ class FileAdministrativeActWizard(OperationAdministrativeActWizard):
class FileEditAdministrativeActWizard(FileAdministrativeActWizard):
model = AdministrativeAct
- edit = True
+ modification = True
def get_associated_item(self, dct):
return self.get_current_object().associated_file
diff --git a/archaeological_finds/models_finds.py b/archaeological_finds/models_finds.py
index 1863ba450..9ba25cc83 100644
--- a/archaeological_finds/models_finds.py
+++ b/archaeological_finds/models_finds.py
@@ -3023,7 +3023,8 @@ class Find(
@classmethod
def get_owns(
- cls, user, menu_filtr=None, limit=None, values=None, get_short_menu_class=None
+ cls, user, menu_filtr=None, limit=None, values=None, get_short_menu_class=None,
+ no_auth_check=False, query=False
):
replace_query = None
if menu_filtr and "contextrecord" in menu_filtr:
@@ -3034,7 +3035,11 @@ class Find(
limit=limit,
values=values,
get_short_menu_class=get_short_menu_class,
+ no_auth_check=no_auth_check,
+ query=query
)
+ if query:
+ return owns
return cls._return_get_owns(owns, values, get_short_menu_class)
def _generate_cached_label(self):
diff --git a/archaeological_finds/models_treatments.py b/archaeological_finds/models_treatments.py
index d23843226..5ba50728b 100644
--- a/archaeological_finds/models_treatments.py
+++ b/archaeological_finds/models_treatments.py
@@ -341,7 +341,8 @@ class Treatment(
@classmethod
def get_owns(
- cls, user, menu_filtr=None, limit=None, values=None, get_short_menu_class=None
+ cls, user, menu_filtr=None, limit=None, values=None, get_short_menu_class=None,
+ no_auth_check=False, query=False
):
replace_query = None
if menu_filtr:
@@ -356,7 +357,11 @@ class Treatment(
limit=limit,
values=values,
get_short_menu_class=get_short_menu_class,
+ no_auth_check=no_auth_check,
+ query=query
)
+ if query:
+ return owns
return cls._return_get_owns(owns, values, get_short_menu_class)
def get_query_operations(self):
@@ -1346,11 +1351,15 @@ class TreatmentFile(
@classmethod
def get_owns(
- cls, user, menu_filtr=None, limit=None, values=None, get_short_menu_class=None
+ cls, user, menu_filtr=None, limit=None, values=None, get_short_menu_class=None,
+ no_auth_check=False, query=False
):
owns = super(TreatmentFile, cls).get_owns(
- user, limit=limit, values=values, get_short_menu_class=get_short_menu_class
+ user, limit=limit, values=values, get_short_menu_class=get_short_menu_class,
+ no_auth_check=no_auth_check, query=query
)
+ if query:
+ return owns
return cls._return_get_owns(owns, values, get_short_menu_class)
def _generate_cached_label(self):
diff --git a/archaeological_finds/tests.py b/archaeological_finds/tests.py
index 16fb575c7..5df18cf64 100644
--- a/archaeological_finds/tests.py
+++ b/archaeological_finds/tests.py
@@ -27,7 +27,7 @@ from rest_framework.test import APITestCase
from rest_framework.authtoken.models import Token
from django.conf import settings
-from django.contrib.auth.models import User, Permission, ContentType
+from django.contrib.auth.models import User, Permission, ContentType, Group
from django.core.files import File
from django.core.files.uploadedfile import SimpleUploadedFile
from django.db.utils import IntegrityError
@@ -1923,23 +1923,42 @@ class FindPermissionTest(FindInit, TestCase):
model = models.Find
def setUp(self):
- self.username, self.password, self.user = create_superuser()
- self.alt_username, self.alt_password, self.alt_user = create_user()
+ profile_type = ProfileType.objects.create(
+ label="xxCollaborateur",
+ txt_idx="xxcollaborator",
+ )
+ gp = Group.objects.create(name="xxMobilier rattachées : voir et modification")
ct_find = ContentType.objects.get(
app_label="archaeological_finds", model="find"
)
- self.alt_user.user_permissions.add(
- Permission.objects.get(codename="view_own_find", content_type=ct_find)
+ gp.permissions.add(
+ Permission.objects.get(
+ codename="view_own_find",
+ content_type=ct_find
+ )
)
- self.alt_user.user_permissions.add(
- Permission.objects.get(codename="change_own_find", content_type=ct_find)
+ gp.permissions.add(
+ Permission.objects.get(
+ codename="change_own_find",
+ content_type=ct_find
+ )
)
+ profile_type.groups.add(gp)
+
+ self.username, self.password, self.user = create_superuser()
+ self.alt_username, self.alt_password, self.alt_user = create_user()
+ profile = UserProfile.objects.create(
+ profile_type=profile_type,
+ person=self.alt_user.ishtaruser.person,
+ current=True,
+ )
+
# nosec: hard coded password for test purposes
self.alt_username2, self.alt_password2, self.alt_user2 = create_user( # nosec
username="luke", password="iamyourfather"
)
profile = UserProfile.objects.create(
- profile_type=ProfileType.objects.get(txt_idx="collaborator"),
+ profile_type=profile_type,
person=self.alt_user2.ishtaruser.person,
current=True,
)
@@ -1973,6 +1992,9 @@ class FindPermissionTest(FindInit, TestCase):
self.find_2 = self.finds[-1]
self.operations[-1].towns.add(town)
+ self.alt_user.ishtaruser.generate_permission()
+ self.alt_user2.ishtaruser.generate_permission()
+
def test_own_search(self):
# no result when no authentification
c = Client()
@@ -1985,7 +2007,7 @@ class FindPermissionTest(FindInit, TestCase):
response = c.get(reverse("get-find"))
# only one "own" context record available
content = response.content.decode()
- self.assertTrue(json.loads(content))
+ self.assertTrue(content)
self.assertEqual(json.loads(content)["recordsTotal"], 1)
# area filter
diff --git a/archaeological_finds/wizards.py b/archaeological_finds/wizards.py
index d6647b81c..c70721eac 100644
--- a/archaeological_finds/wizards.py
+++ b/archaeological_finds/wizards.py
@@ -506,7 +506,7 @@ class TreatmentAdministrativeActWizard(OperationAdministrativeActWizard):
class TreatmentEditAdministrativeActWizard(TreatmentAdministrativeActWizard):
model = AdministrativeAct
- edit = True
+ modification = True
def get_associated_item(self, dct):
return self.get_current_object().treatment
@@ -583,7 +583,7 @@ class TreatmentFileAdministrativeActWizard(OperationAdministrativeActWizard):
class TreatmentFileEditAdministrativeActWizard(TreatmentFileAdministrativeActWizard):
model = AdministrativeAct
- edit = True
+ modification = True
def get_associated_item(self, dct):
return self.get_current_object().treatment_file
@@ -600,7 +600,7 @@ class FindBasketWizard(Wizard):
class FindBasketEditWizard(FindBasketWizard):
- edit = True
+ modification = True
alt_is_own_method = "get_write_query_owns"
def get_form_kwargs(self, step, **kwargs):
diff --git a/archaeological_operations/models.py b/archaeological_operations/models.py
index a074adc9a..2fafa56ed 100644
--- a/archaeological_operations/models.py
+++ b/archaeological_operations/models.py
@@ -902,7 +902,8 @@ class ArchaeologicalSite(
@classmethod
def get_owns(
- cls, user, menu_filtr=None, limit=None, values=None, get_short_menu_class=None
+ cls, user, menu_filtr=None, limit=None, values=None, get_short_menu_class=None,
+ no_auth_check=False, query=False
):
replace_query = None
if menu_filtr and "operation" in menu_filtr:
@@ -914,7 +915,11 @@ class ArchaeologicalSite(
limit=limit,
values=values,
get_short_menu_class=get_short_menu_class,
+ no_auth_check=no_auth_check,
+ query=query
)
+ if query:
+ return owns
return cls._return_get_owns(owns, values, get_short_menu_class)
def _generate_cached_label(self):
@@ -1782,7 +1787,8 @@ class Operation(
@classmethod
def get_owns(
- cls, user, menu_filtr=None, limit=None, values=None, get_short_menu_class=None
+ cls, user, menu_filtr=None, limit=None, values=None, get_short_menu_class=None,
+ no_auth_check=False, query=False
):
replace_query = None
if menu_filtr and "file" in menu_filtr:
@@ -1794,7 +1800,11 @@ class Operation(
limit=limit,
values=values,
get_short_menu_class=get_short_menu_class,
+ no_auth_check=no_auth_check,
+ query=query
)
+ if query:
+ return owns
return cls._return_get_owns(owns, values, get_short_menu_class)
def __str__(self):
diff --git a/archaeological_operations/tests.py b/archaeological_operations/tests.py
index b0b9c9dae..2c5946e87 100644
--- a/archaeological_operations/tests.py
+++ b/archaeological_operations/tests.py
@@ -3480,18 +3480,26 @@ class OperationPermissionTest(TestCase, OperationInitTest):
def setUp(self):
IshtarSiteProfile.objects.get_or_create(slug="default", active=True)
self.username, self.password, self.user = create_superuser()
+
self.alt_username, self.alt_password, self.alt_user = create_user()
- self.alt_user.user_permissions.add(
- Permission.objects.get(codename="view_own_operation")
+ profile_type = ProfileType.objects.create(
+ label="xxCollaborateur",
+ txt_idx="xxcollaborator",
)
- self.alt_user.user_permissions.add(
- Permission.objects.get(codename="change_own_operation")
+ UserProfile.objects.create(
+ profile_type=profile_type,
+ person=self.alt_user.ishtaruser.person,
+ current=True,
)
+ gp = Group.objects.create(name="xxOpérations rattachées : voir et modification")
+ gp.permissions.add(Permission.objects.get(codename="view_own_operation"))
+ gp.permissions.add(Permission.objects.get(codename="change_own_operation"))
+ profile_type.groups.add(gp)
+
# nosec: hard coded password for test purposes
self.alt_username2, self.alt_password2, self.alt_user2 = create_user( # nosec
username="luke", password="iamyourfather"
)
- profile_type = ProfileType.objects.get(txt_idx="collaborator")
profile = UserProfile.objects.create(
profile_type=profile_type,
person=self.alt_user2.ishtaruser.person,
@@ -3503,11 +3511,51 @@ class OperationPermissionTest(TestCase, OperationInitTest):
profile.areas.add(area)
self.orgas = self.create_orgas(self.user)
- self.operations = self.create_operation(self.user, self.orgas[0])
- self.operations += self.create_operation(self.alt_user, self.orgas[0])
+ self.create_operation(self.user, self.orgas[0])
+ self.operations = self.create_operation(self.alt_user, self.orgas[0])
self.operations[1].towns.add(town)
self.item = self.operations[0]
+ def test_permission_generation(self):
+ alt_user = IshtarUser.objects.get(pk=self.alt_user.pk)
+ self.assertFalse(
+ alt_user.user_ptr.has_perm(
+ "archaeological_operations.change_own_operation",
+ self.operations[1]
+ )
+ )
+ self.assertFalse(
+ alt_user.user_ptr.has_perm(
+ "archaeological_operations.view_own_operation",
+ self.operations[1]
+ )
+ )
+ alt_user.generate_permission()
+ self.assertTrue(
+ alt_user.user_ptr.has_perm(
+ "archaeological_operations.view_own_operation",
+ self.operations[1]
+ )
+ )
+ self.assertTrue(
+ alt_user.user_ptr.has_perm(
+ "archaeological_operations.change_own_operation",
+ self.operations[1]
+ )
+ )
+ # general permission is assigned
+ self.assertTrue(
+ alt_user.user_ptr.has_perm(
+ "archaeological_operations.change_own_operation",
+ )
+ )
+ self.assertFalse(
+ alt_user.user_ptr.has_perm(
+ "archaeological_operations.change_own_operation",
+ self.operations[0]
+ )
+ )
+
def test_own_search(self):
# no result when no authentification
c = Client()
@@ -3515,6 +3563,8 @@ class OperationPermissionTest(TestCase, OperationInitTest):
self.assertTrue(not json.loads(response.content.decode()))
# possession
+ alt_user = IshtarUser.objects.get(pk=self.alt_user.pk)
+ alt_user.generate_permission()
c = Client()
c.login(username=self.alt_username, password=self.alt_password)
response = c.get(reverse("get-operation"), {"year": "2010"})
@@ -3531,6 +3581,8 @@ class OperationPermissionTest(TestCase, OperationInitTest):
self.assertEqual(json.loads(response.content.decode())["recordsTotal"], 1)
# area filter
+ alt_user2 = IshtarUser.objects.get(pk=self.alt_user2.pk)
+ alt_user2.generate_permission()
c = Client()
c.login(username=self.alt_username2, password=self.alt_password2)
response = c.get(reverse("get-operation"), {"year": "2010"})
@@ -3557,6 +3609,8 @@ class OperationPermissionTest(TestCase, OperationInitTest):
self.assertRedirects(response, "/")
# possession
+ alt_user = IshtarUser.objects.get(pk=self.alt_user.pk)
+ alt_user.generate_permission()
c = Client()
c.login(username=self.alt_username, password=self.alt_password)
response = c.get(reverse("operation_modify", args=[operation_pk2]), follow=True)
@@ -3574,6 +3628,8 @@ class OperationPermissionTest(TestCase, OperationInitTest):
)
# area filter
+ alt_user2 = IshtarUser.objects.get(pk=self.alt_user2.pk)
+ alt_user2.generate_permission()
c = Client()
c.login(username=self.alt_username2, password=self.alt_password2)
response = c.get(reverse("operation_modify", args=[operation_pk2]), follow=True)
diff --git a/archaeological_operations/views.py b/archaeological_operations/views.py
index 7b6a56597..270411bfd 100644
--- a/archaeological_operations/views.py
+++ b/archaeological_operations/views.py
@@ -540,7 +540,10 @@ def get_relation_modify(model, model_relation, formset_class, url_name,
except model.DoesNotExist:
raise Http404()
if "_own_" in current_right:
- if not item.is_own(request.user):
+ if not request.user.has_perm(current_right, item):
+ raise PermissionDenied()
+ elif current_right:
+ if not request.user.has_perm(current_right):
raise PermissionDenied()
relations = model_relation.objects.filter(left_record_id=pk).all()
form_kwargs = {"left_record": item}
diff --git a/archaeological_operations/wizards.py b/archaeological_operations/wizards.py
index ac8aaf40d..b510aa4cc 100644
--- a/archaeological_operations/wizards.py
+++ b/archaeological_operations/wizards.py
@@ -21,7 +21,6 @@ import logging
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
-from django.http import Http404
from django.shortcuts import render
from django.urls import reverse
from ishtar_common.utils import ugettext_lazy as _
@@ -34,7 +33,6 @@ from ishtar_common.models import get_current_profile
from ishtar_common.wizards import (
Wizard,
ClosingWizard,
- DeletionWizard,
SearchWizard,
MultipleDeletionWizard,
)
@@ -240,7 +238,7 @@ class OperationDeletionWizard(MultipleDeletionWizard):
class OperationAdministrativeActWizard(OperationWizard):
- edit = False
+ modification = False
wizard_done_window = reverse_lazy("show-administrativeact")
current_obj_slug = "administrativeactop"
ref_object_key = "operation"
@@ -303,7 +301,7 @@ class OperationAdministrativeActWizard(OperationWizard):
dct["history_modifier"] = self.request.user
if "pk" in dct:
dct.pop("pk")
- if self.edit:
+ if self.modification:
admact = self.get_current_object()
for k in dct:
if hasattr(admact, k):
@@ -358,7 +356,7 @@ class OperationAdministrativeActWizard(OperationWizard):
return res
def get_form_initial(self, step, data=None):
- if not self.edit:
+ if not self.modification:
return {}
initial = super().get_form_initial(step)
return initial
@@ -366,7 +364,7 @@ class OperationAdministrativeActWizard(OperationWizard):
class OperationEditAdministrativeActWizard(OperationAdministrativeActWizard):
model = models.AdministrativeAct
- edit = True
+ modification = True
def get_associated_item(self, dct):
return self.get_current_object().operation
diff --git a/archaeological_warehouse/tests.py b/archaeological_warehouse/tests.py
index 96df83024..e92298c9f 100644
--- a/archaeological_warehouse/tests.py
+++ b/archaeological_warehouse/tests.py
@@ -20,7 +20,7 @@
import datetime
import json
-from django.contrib.auth.models import Permission, User
+from django.contrib.auth.models import Permission, Group
from django.db.utils import IntegrityError
from django.test.client import Client
from django.urls import reverse
@@ -38,10 +38,11 @@ from ishtar_common.tests import (
create_superuser,
)
-from ishtar_common.models import IshtarSiteProfile
+from ishtar_common.models import IshtarSiteProfile, ProfileType, UserProfile
from archaeological_operations.models import Operation
from archaeological_context_records.models import ContextRecord
-from archaeological_finds.models import Find, MaterialType, Treatment, TreatmentType, FindTreatment, TreatmentState
+from archaeological_finds.models import \
+ Find, MaterialType, Treatment, TreatmentType, FindTreatment, TreatmentState
from archaeological_warehouse import models, views, forms, serializers
@@ -808,7 +809,22 @@ class ContainerTest(FindInit, TestCase):
container_2.index = 42000
container_2.save()
username, password, user = create_user()
- user.user_permissions.add(Permission.objects.get(codename="view_warehouse"))
+
+ profile_type = ProfileType.objects.create(
+ label="xxCollaborateur",
+ txt_idx="xxcollaborator",
+ )
+ gp = Group.objects.create(name="xxLieu de conservation : voir")
+ gp.permissions.add(Permission.objects.get(codename="view_warehouse"))
+ gp.permissions.add(Permission.objects.get(codename="view_own_container"))
+ profile_type.groups.add(gp)
+ UserProfile.objects.create(
+ profile_type=profile_type,
+ person=user.ishtaruser.person,
+ current=True,
+ )
+ user.ishtaruser.generate_permission()
+
client = Client()
client.login(username=username, password=password)
url = "/autocomplete-container/{}/".format(self.main_warehouse.pk)
diff --git a/ishtar_common/admin.py b/ishtar_common/admin.py
index f67a99e01..743d643a3 100644
--- a/ishtar_common/admin.py
+++ b/ishtar_common/admin.py
@@ -1655,6 +1655,9 @@ class ProfileTypeAdmin(GeneralTypeAdmin):
return Http404()
permissions_needed = set()
permissions_not_needed = set()
+ for model in ("basefind", "import", "biographicalnote"):
+ for perm_type in ("add", "change", "delete", "view"):
+ permissions_not_needed.add((perm_type, model))
for group in obj.groups.all():
for perm in group.permissions.all():
sp = perm.codename.split("_")
diff --git a/ishtar_common/models.py b/ishtar_common/models.py
index 8fcb9edbb..0c92f2f10 100644
--- a/ishtar_common/models.py
+++ b/ishtar_common/models.py
@@ -27,6 +27,7 @@ from bs4 import BeautifulSoup
import bleach
import copy
import datetime
+from guardian.models import UserObjectPermission
import inspect
from importlib import import_module
from jinja2 import TemplateSyntaxError, UndefinedError
@@ -297,7 +298,7 @@ class ValueGetter:
def _get_values_update_sub_filter(self, filtr, prefix):
if not filtr:
return
- return [k[len(prefix) :] for k in filtr if k.startswith(prefix)]
+ return [k[len(prefix):] for k in filtr if k.startswith(prefix)]
def get_extra_values(self, prefix="", no_values=False, filtr=None, **kwargs):
return {}
@@ -3540,6 +3541,81 @@ class UserProfile(models.Model):
new_item.external_sources.add(src)
return new_item
+ def _generate_permission(self, ishtar_user, content_type, permission_request):
+ item_ids = []
+ model_class = content_type.model_class()
+ if permission_request.include_associated_items:
+ item_ids += model_class.filter(
+ ishtar_users__pk=ishtar_user.pk
+ ).values_list("pk", flat=True)
+ if permission_request.include_upstream_items:
+ # TODO....
+ item_ids += model_class.get_ids_from_upper_permissions(ishtar_user.user_ptr.pk)
+ if permission_request.request or permission_request.limit_to_attached_areas:
+ # TODO
+ pass
+ query = model_class.objects
+ return item_ids
+
+ def generate_permission(self, content_type):
+ ishtar_user = self.person.ishtaruser
+
+ # add base permissions
+ for group in self.profile_type.groups.all():
+ for perm in group.permissions.all():
+ ishtar_user.user_ptr.user_permissions.add(perm)
+ q_has_perm = self.profile_type.groups.filter(
+ permissions__content_type=content_type,
+ permissions__codename__contains="_own_"
+ )
+ if not q_has_perm.count(): # no permission to generate
+ return
+
+ permissions = []
+ for group in q_has_perm.all():
+ permissions += list(group.permissions.values_list("pk", flat=True))
+ q_req = self.profile_type.permission_requests.filter(
+ model=content_type, active=True
+ )
+ item_ids = []
+ if not q_req.count():
+ # TODO v5: delete old behaviour
+ """
+ print(f"WARNING: no permission request for content {content_type.name} and profile {self}")
+ print("Using old behaviour")
+ """
+ model_class = content_type.model_class()
+ query = model_class.get_owns(user=ishtar_user, query=True, no_auth_check=True)
+ if query:
+ item_ids = list(
+ model_class.objects.filter(query).values_list("pk", flat=True)
+ )
+ else:
+ for perm_request in q_req.all():
+ item_ids += self._generate_permission(
+ ishtar_user, content_type, perm_request
+ )
+ user_id = ishtar_user.user_ptr.pk
+ object_permissions = []
+ item_ids = list(set(item_ids))
+ permissions = list(set(permissions))
+ for permission_id in permissions:
+ exclude = list(UserObjectPermission.objects.filter(
+ content_type_id=content_type.pk, permission_id=permission_id,
+ user_id=user_id
+ ).values_list("object_pk", flat=True))
+ object_permissions += [
+ UserObjectPermission(
+ object_pk=str(item_id),
+ content_type_id=content_type.pk,
+ permission_id=permission_id,
+ user_id=user_id
+ )
+ for item_id in item_ids if str(item_id) not in exclude
+ ]
+ if object_permissions:
+ UserObjectPermission.objects.bulk_create(object_permissions)
+
def save(
self,
force_insert=False,
@@ -3692,8 +3768,8 @@ class IshtarUser(FullSearch):
_("Advanced shortcut menu"), default=False
)
# latest news read by the user
- latest_news_version = models.CharField(_("Latest news version"), default="", blank=True,
- max_length=20)
+ latest_news_version = models.CharField(_("Latest news version"), default="",
+ blank=True, max_length=20)
display_news = models.BooleanField(_("Display news"), default=True)
display_forum_entries = models.BooleanField(_("Display forum entries"), default=True)
@@ -3787,32 +3863,43 @@ class IshtarUser(FullSearch):
if obj:
return self.user_ptr.has_perm(permission, obj)
return self.user_ptr.has_perm(permission)
- """
- res = (
- bool(
- self.profiles.filter(
- current=True, profile_type__txt_idx=permission
- ).count()
- )
- or bool(
- self.profiles.filter(
- current=True,
- profile_type__groups__permissions__codename=permission,
- ).count()
- )
- or bool(
- self.ishtaruser.user_ptr.groups.filter(
- permissions__codename__in=[permission]
- ).count()
- )
- or bool(
- self.ishtaruser.user_ptr.user_permissions.filter(
- codename__in=[permission]
- ).count()
- )
- )
- return res
- """
+
+ def generate_permission(self):
+ # models to treat first in this order to manage cascade permissions
+ model_names = [
+ ("archaeological_operations", "operation"),
+ ("archaeological_context_records", "contextrecord"),
+ ("archaeological_warehouse", "warehouse"),
+ ("archaeological_finds", "treatment"),
+ ("archaeological_warehouse", "container"),
+ ("archaeological_finds", "find"),
+ ]
+ # cascade permission to treat at the end
+ last_model_names = [
+ ("ishtar_common", "document"),
+ ("ishtar_common", "geovectordata"),
+ ("ishtar_common", "import"),
+ ]
+ for ct in ContentType.objects.all():
+ name = (ct.app_label, ct.model)
+ klass = ct.model_class()
+ if not klass or not getattr(klass, "SHOW_URL", None) \
+ or name in model_names \
+ or name in last_model_names:
+ continue
+ model_names.append(name)
+ model_names += last_model_names
+ content_types = [
+ ContentType.objects.get(app_label=app, model=model_name)
+ for app, model_name in model_names
+ ]
+ # clean all permission first
+ UserObjectPermission.objects.filter(user_id=self.pk).delete()
+ self.user_ptr.user_permissions.clear()
+
+ for ct in content_types:
+ for profile in self.person.profiles.all():
+ profile.generate_permission(ct)
def full_label(self):
return self.person.full_label()
@@ -5014,7 +5101,14 @@ class Document(
def get_query_owns(cls, ishtaruser):
query_own_list = []
for rel_model in cls.RELATED_MODELS:
- klass = getattr(cls, rel_model).remote_field.related_model
+ r = getattr(cls, rel_model)
+ if hasattr(r, "remote_field"):
+ r = getattr(cls, rel_model).remote_field
+ else:
+ r = getattr(cls, rel_model).rel
+ klass = r.related_model
+ if not hasattr(klass, "_get_query_owns_dicts"):
+ continue
q_own_dct = klass._get_query_owns_dicts(ishtaruser)
if q_own_dct:
query_own_list.append((rel_model + "__", q_own_dct))
diff --git a/ishtar_common/models_common.py b/ishtar_common/models_common.py
index e92d7d55d..28f5aba00 100644
--- a/ishtar_common/models_common.py
+++ b/ishtar_common/models_common.py
@@ -1629,13 +1629,14 @@ class BaseHistorizedItem(
)
}
- class Meta:
- abstract = True
-
@classmethod
def get_verbose_name(cls):
return cls._meta.verbose_name
+ @classmethod
+ def get_ids_from_upper_permissions(cls, user_id):
+ return []
+
def is_locked(self, user=None):
if not user:
return self.locked
diff --git a/ishtar_common/utils.py b/ishtar_common/utils.py
index 8de745874..09c470823 100644
--- a/ishtar_common/utils.py
+++ b/ishtar_common/utils.py
@@ -395,7 +395,8 @@ class OwnPerms:
"""
Check if the current object is owned by the user
"""
- print("ishtar_common/utils.py - 370 - DELETE")
+ print("ishtar_common/utils.py - 377 - DELETE")
+ raise
IshtarUser = apps.get_model("ishtar_common", "IshtarUser")
if isinstance(user, IshtarUser):
ishtaruser = user
@@ -467,13 +468,18 @@ class OwnPerms:
values=None,
get_short_menu_class=False,
menu_filtr=None,
+ no_auth_check=False,
+ query=False
):
"""
Get Own items
"""
+ return_query = query
+ query = None
if not replace_query:
replace_query = {}
- if hasattr(user, "is_authenticated") and not user.is_authenticated:
+ if hasattr(user, "is_authenticated") and not user.is_authenticated \
+ and not no_auth_check:
returned = cls.objects.filter(pk__isnull=True)
if values:
returned = []
@@ -502,6 +508,10 @@ class OwnPerms:
if values:
returned = []
return returned
+ if return_query:
+ if query:
+ return query
+ return replace_query
if query:
q = cls.objects.filter(query)
else: # replace_query
diff --git a/ishtar_common/views_item.py b/ishtar_common/views_item.py
index a88cb48b3..64dee8872 100644
--- a/ishtar_common/views_item.py
+++ b/ishtar_common/views_item.py
@@ -12,7 +12,6 @@ import requests
# nosec: no user input used
import subprocess # nosec
from tempfile import NamedTemporaryFile
-import unidecode
from django.conf import settings
from django.contrib.auth.models import Permission
diff --git a/ishtar_common/wizards.py b/ishtar_common/wizards.py
index e41c4c811..1da9b9f4d 100644
--- a/ishtar_common/wizards.py
+++ b/ishtar_common/wizards.py
@@ -35,7 +35,7 @@ from formtools.wizard.views import (
from django.contrib.sites.models import Site
from django.core.exceptions import ObjectDoesNotExist
from django.core.files.images import ImageFile
-from django.core.files.storage import default_storage, FileSystemStorage
+from django.core.files.storage import FileSystemStorage
from django.core.mail import send_mail
from django.db.models.fields.files import FileField, ImageFieldFile
from django.db.models.fields.related import ManyToManyField
@@ -50,7 +50,7 @@ from django.utils.safestring import mark_safe
from ishtar_common import models, models_rest
from ishtar_common.forms import CustomForm, reverse_lazy
-from ishtar_common.utils import get_all_field_names, get_person_gdpr_log, MultiValueDict, \
+from ishtar_common.utils import get_all_field_names, get_person_gdpr_log, MultiValueDict,\
put_session_message
logger = logging.getLogger(__name__)
@@ -154,6 +154,7 @@ class Wizard(IshtarWizard):
label = ""
translated_keys = []
modification = None # True when the wizard modify an item
+ deletion = True # True on deletion
storage_name = "formtools.wizard.storage.session.SessionStorage"
wizard_done_template = "ishtar/wizard/wizard_done.html"
wizard_done_window = ""
@@ -211,16 +212,9 @@ class Wizard(IshtarWizard):
self.steps = StepsHelper(self)
current_object = self.get_current_object()
- ishtaruser = (
- request.user.ishtaruser if hasattr(request.user, "ishtaruser") else None
- )
-
# not the first step and current object is not owned
if self.steps and self.steps.first != step and current_object:
- is_own = current_object.is_own(
- ishtaruser, alt_query_own=self.alt_is_own_method
- )
- if not is_own:
+ if not self.verify_permission(request, current_object):
messages.add_message(
request,
messages.WARNING,
@@ -230,6 +224,23 @@ class Wizard(IshtarWizard):
return
return True
+ def verify_permission(self, request, current_object=None):
+ meta = self.model._meta
+ perm = f"{meta.app_label}."
+ if self.modification:
+ perm += "change"
+ elif self.deletion:
+ perm += "delete"
+ else:
+ perm += "add"
+ base_perm = f"{perm}_{meta.model_name}"
+ if request.user.has_perm(base_perm):
+ return True
+ if not current_object:
+ return False
+ own_perm = f"{perm}_own_{meta.model_name}"
+ return request.user.has_perm(own_perm, current_object)
+
def dispatch(self, request, *args, **kwargs):
self.current_right = kwargs.get("current_right", None)
step = kwargs.get("step", None)
@@ -241,7 +252,6 @@ class Wizard(IshtarWizard):
self.filter_owns_items = True
else:
self.filter_owns_items = False
-
return super(Wizard, self).dispatch(request, *args, **kwargs)
def get_prefix(self, request, *args, **kwargs):
@@ -1714,6 +1724,8 @@ class DocumentSearch(SearchWizard):
class DeletionWizard(Wizard):
+ deletion = True
+
def __init__(self, *args, **kwargs):
if (not hasattr(self, "fields") or not self.fields) and (
hasattr(self, "model") and hasattr(self.model, "TABLE_COLS")
@@ -1790,6 +1802,8 @@ class MultipleItemWizard(Wizard):
class MultipleDeletionWizard(MultipleItemWizard):
+ deletion = True
+
def __init__(self, *args, **kwargs):
if (not hasattr(self, "fields") or not self.fields) and (
hasattr(self, "model") and hasattr(self.model, "TABLE_COLS")