From 3a136d81c4b8138e509bd25c73343ea215ddff2d Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Thu, 6 Oct 2022 18:35:23 +0200 Subject: [PATCH 1/2] a2_rbac: move signal handlers from django_rbac (#69902) --- src/authentic2/a2_rbac/apps.py | 21 ++++++++- src/authentic2/a2_rbac/managers.py | 8 +++- .../migrations/0024_fix_self_admin_perm.py | 3 +- src/authentic2/a2_rbac/models.py | 8 +++- src/authentic2/a2_rbac/signal_handlers.py | 45 ++++++++++++++++++- .../a2_rbac}/signals.py | 0 src/authentic2/a2_rbac/utils.py | 7 ++- .../management/commands/check-and-repair.py | 2 +- src/django_rbac/apps.py | 24 ---------- src/django_rbac/managers.py | 6 +-- src/django_rbac/models.py | 9 ---- src/django_rbac/signal_handlers.py | 44 ------------------ tests/api/test_all.py | 2 +- tests/test_a2_rbac.py | 4 +- tests/test_api_client.py | 3 +- tests/test_commands.py | 11 ++++- tests/test_manager.py | 3 +- tests/test_user_manager.py | 2 +- 18 files changed, 100 insertions(+), 102 deletions(-) rename src/{django_rbac => authentic2/a2_rbac}/signals.py (100%) delete mode 100644 src/django_rbac/signal_handlers.py diff --git a/src/authentic2/a2_rbac/apps.py b/src/authentic2/a2_rbac/apps.py index f19122af2..a5fcc1c4b 100644 --- a/src/authentic2/a2_rbac/apps.py +++ b/src/authentic2/a2_rbac/apps.py @@ -25,9 +25,28 @@ class Authentic2RBACConfig(AppConfig): from django.db.models.signals import post_delete, post_migrate, post_save from authentic2.models import Service + from django_rbac import utils - from . import models, signal_handlers + from . import models, signal_handlers, signals + # update role parenting when new role parenting is created + post_save.connect(signal_handlers.role_parenting_post_save, sender=utils.get_role_parenting_model()) + # update role parenting when role parenting is deleted + post_delete.connect( + signal_handlers.role_parenting_post_delete, sender=utils.get_role_parenting_model() + ) + # or soft-created + signals.post_soft_create.connect( + signal_handlers.role_parenting_post_soft_delete, sender=utils.get_role_parenting_model() + ) + # or soft-deleted + signals.post_soft_delete.connect( + signal_handlers.role_parenting_post_soft_delete, sender=utils.get_role_parenting_model() + ) + # create CRUD operations and admin + post_migrate.connect(signal_handlers.create_base_operations, sender=self) + # update role parenting in post migrate + post_migrate.connect(signal_handlers.fix_role_parenting_closure, sender=self) # update rbac on save to contenttype, ou and roles post_save.connect(signal_handlers.update_rbac_on_ou_post_save, sender=models.OrganizationalUnit) post_delete.connect(signal_handlers.update_rbac_on_ou_post_delete, sender=models.OrganizationalUnit) diff --git a/src/authentic2/a2_rbac/managers.py b/src/authentic2/a2_rbac/managers.py index 9dbc137ae..d435e26e1 100644 --- a/src/authentic2/a2_rbac/managers.py +++ b/src/authentic2/a2_rbac/managers.py @@ -19,7 +19,6 @@ from django.contrib.contenttypes.models import ContentType from authentic2.a2_rbac import models from django_rbac.managers import AbstractBaseManager from django_rbac.managers import RoleManager as BaseRoleManager -from django_rbac.models import ADMIN_OP from django_rbac.utils import get_operation @@ -35,7 +34,7 @@ class RoleManager(BaseRoleManager): name, slug, ou=None, - operation=ADMIN_OP, + operation=None, update_name=False, update_slug=False, permissions=(), @@ -43,6 +42,11 @@ class RoleManager(BaseRoleManager): create=True, ): '''Get or create the role of manager's of this object instance''' + from .models import ADMIN_OP + + if operation is None: + operation = ADMIN_OP + kwargs = {} assert not ou or isinstance( instance, ContentType diff --git a/src/authentic2/a2_rbac/migrations/0024_fix_self_admin_perm.py b/src/authentic2/a2_rbac/migrations/0024_fix_self_admin_perm.py index b09b45956..4abfe27c3 100644 --- a/src/authentic2/a2_rbac/migrations/0024_fix_self_admin_perm.py +++ b/src/authentic2/a2_rbac/migrations/0024_fix_self_admin_perm.py @@ -2,8 +2,7 @@ from django.db import migrations -from authentic2.a2_rbac.models import MANAGE_MEMBERS_OP -from django_rbac.models import CHANGE_OP +from authentic2.a2_rbac.models import CHANGE_OP, MANAGE_MEMBERS_OP def update_self_administration_perm(apps, schema_editor): diff --git a/src/authentic2/a2_rbac/models.py b/src/authentic2/a2_rbac/models.py index 6cbbd0b67..8c3d11ca3 100644 --- a/src/authentic2/a2_rbac/models.py +++ b/src/authentic2/a2_rbac/models.py @@ -38,7 +38,7 @@ from authentic2.utils.cache import GlobalCache from authentic2.validators import HexaColourValidator from django_rbac import managers as rbac_managers from django_rbac import utils as rbac_utils -from django_rbac.models import VIEW_OP, Operation +from django_rbac.models import Operation from . import app_settings, fields, managers @@ -723,6 +723,12 @@ GenericRelation(Permission, content_type_field='target_ct', object_id_field='tar ) +ADMIN_OP = Operation.register(name=pgettext_lazy('permission', 'Management'), slug='admin') +CHANGE_OP = Operation.register(name=pgettext_lazy('permission', 'Change'), slug='change') +DELETE_OP = Operation.register(name=pgettext_lazy('permission', 'Delete'), slug='delete') +ADD_OP = Operation.register(name=pgettext_lazy('permission', 'Add'), slug='add') +VIEW_OP = Operation.register(name=pgettext_lazy('permission', 'View'), slug='view') +SEARCH_OP = Operation.register(name=pgettext_lazy('permission', 'Search'), slug='search') CHANGE_PASSWORD_OP = Operation.register(name=_('Change password'), slug='change_password') RESET_PASSWORD_OP = Operation.register(name=_('Password reset'), slug='reset_password') ACTIVATE_OP = Operation.register(name=_('Activation'), slug='activate') diff --git a/src/authentic2/a2_rbac/signal_handlers.py b/src/authentic2/a2_rbac/signal_handlers.py index efebbea7c..907a71f0a 100644 --- a/src/authentic2/a2_rbac/signal_handlers.py +++ b/src/authentic2/a2_rbac/signal_handlers.py @@ -23,7 +23,7 @@ from django.utils.translation import override from authentic2.a2_rbac.models import OrganizationalUnit, Role from authentic2.utils.misc import get_fk_model from django_rbac.managers import defer_update_transitive_closure -from django_rbac.utils import get_operation +from django_rbac.utils import get_operation, get_role_parenting_model def create_default_ou(app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, **kwargs): @@ -103,3 +103,46 @@ def create_default_permissions(app_config, verbosity=2, interactive=True, using= get_operation(CHANGE_EMAIL_OP) get_operation(MANAGE_MEMBERS_OP) get_operation(MANAGE_AUTHORIZATIONS_OP) + + +def role_parenting_post_save(sender, instance, raw, created, **kwargs): + '''Close the role parenting relation after instance creation''' + if raw: # do nothing if save comes from fixture loading + return + if not instance.direct: # do nothing if instance is not direct + return + sender.objects.update_transitive_closure() + + +def role_parenting_post_delete(sender, instance, **kwargs): + '''Close the role parenting relation after instance deletion''' + if not instance.direct: # do nothing if instance is not direct + return + sender.objects.update_transitive_closure() + + +def role_parenting_post_soft_delete(sender, instance, **kwargs): + '''Close the role parenting relation after instance soft-deletion''' + sender.objects.update_transitive_closure() + + +def create_base_operations(app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, **kwargs): + '''Create some basic operations, matching permissions from Django''' + from . import models + + if not router.allow_migrate(using, models.Operation): + return + + get_operation(models.ADD_OP) + get_operation(models.CHANGE_OP) + get_operation(models.DELETE_OP) + get_operation(models.VIEW_OP) + get_operation(models.ADMIN_OP) + get_operation(models.SEARCH_OP) + + +def fix_role_parenting_closure(app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, **kwargs): + '''Close the role parenting relation after migrations''' + if not router.allow_migrate(using, get_role_parenting_model()): + return + get_role_parenting_model().objects.update_transitive_closure() diff --git a/src/django_rbac/signals.py b/src/authentic2/a2_rbac/signals.py similarity index 100% rename from src/django_rbac/signals.py rename to src/authentic2/a2_rbac/signals.py diff --git a/src/authentic2/a2_rbac/utils.py b/src/authentic2/a2_rbac/utils.py index ff80b8d4f..5c62cbe0d 100644 --- a/src/authentic2/a2_rbac/utils.py +++ b/src/authentic2/a2_rbac/utils.py @@ -19,7 +19,6 @@ from django.contrib.contenttypes.models import ContentType from django.utils.text import slugify from django_rbac import utils as rbac_utils -from django_rbac.models import SEARCH_OP, VIEW_OP from . import models @@ -38,7 +37,7 @@ def get_default_ou_pk(): def get_view_user_perm(ou=None): User = get_user_model() view_user_perm, dummy = models.Permission.objects.get_or_create( - operation=rbac_utils.get_operation(VIEW_OP), + operation=rbac_utils.get_operation(models.VIEW_OP), target_ct=ContentType.objects.get_for_model(ContentType), target_id=ContentType.objects.get_for_model(User).pk, ou__isnull=ou is None, @@ -50,14 +49,14 @@ def get_view_user_perm(ou=None): def get_search_ou_perm(ou=None): if ou: view_ou_perm, dummy = models.Permission.objects.get_or_create( - operation=rbac_utils.get_operation(SEARCH_OP), + operation=rbac_utils.get_operation(models.SEARCH_OP), target_ct=ContentType.objects.get_for_model(ou), target_id=ou.pk, ou__isnull=True, ) else: view_ou_perm, dummy = models.Permission.objects.get_or_create( - operation=rbac_utils.get_operation(SEARCH_OP), + operation=rbac_utils.get_operation(models.SEARCH_OP), target_ct=ContentType.objects.get_for_model(ContentType), target_id=ContentType.objects.get_for_model(models.OrganizationalUnit).pk, ou__isnull=True, diff --git a/src/authentic2/management/commands/check-and-repair.py b/src/authentic2/management/commands/check-and-repair.py index cc36a0c31..a44dcfbad 100644 --- a/src/authentic2/management/commands/check-and-repair.py +++ b/src/authentic2/management/commands/check-and-repair.py @@ -29,10 +29,10 @@ from django.db.transaction import atomic from django.utils.timezone import localtime from authentic2 import app_settings +from authentic2.a2_rbac.models import ADMIN_OP from authentic2.a2_rbac.models import OrganizationalUnit as OU from authentic2.a2_rbac.models import Permission, Role from authentic2.custom_user.models import User -from django_rbac.models import ADMIN_OP from django_rbac.utils import get_operation try: diff --git a/src/django_rbac/apps.py b/src/django_rbac/apps.py index cde0be3fc..f363fff04 100644 --- a/src/django_rbac/apps.py +++ b/src/django_rbac/apps.py @@ -4,27 +4,3 @@ from django.apps import AppConfig class DjangoRBACConfig(AppConfig): name = 'django_rbac' verbose_name = 'RBAC engine for Django' - - def ready(self): - from django.db.models.signals import post_delete, post_migrate, post_save - - from . import signal_handlers, signals, utils - - # update role parenting when new role parenting is created - post_save.connect(signal_handlers.role_parenting_post_save, sender=utils.get_role_parenting_model()) - # update role parenting when role parenting is deleted - post_delete.connect( - signal_handlers.role_parenting_post_delete, sender=utils.get_role_parenting_model() - ) - # or soft-created - signals.post_soft_create.connect( - signal_handlers.role_parenting_post_soft_delete, sender=utils.get_role_parenting_model() - ) - # or soft-deleted - signals.post_soft_delete.connect( - signal_handlers.role_parenting_post_soft_delete, sender=utils.get_role_parenting_model() - ) - # create CRUD operations and admin - post_migrate.connect(signal_handlers.create_base_operations, sender=self) - # update role parenting in post migrate - post_migrate.connect(signal_handlers.fix_role_parenting_closure, sender=self) diff --git a/src/django_rbac/managers.py b/src/django_rbac/managers.py index fda515a3f..cd2d5b9bb 100644 --- a/src/django_rbac/managers.py +++ b/src/django_rbac/managers.py @@ -9,7 +9,9 @@ from django.db.models import query from django.db.models.query import Prefetch, Q from django.db.transaction import atomic -from . import signals, utils +from authentic2.a2_rbac import signals + +from . import utils class AbstractBaseManager(models.Manager): @@ -210,8 +212,6 @@ class RoleParentingManager(models.Manager): signals.post_soft_create.send(sender=self.model, instance=rp) def soft_delete(self, parent, child): - from . import signals - qs = self.filter(parent=parent, child=child, deleted__isnull=True, direct=True) with atomic(savepoint=False): rp = qs.first() diff --git a/src/django_rbac/models.py b/src/django_rbac/models.py index 858dd6635..90e5b2256 100644 --- a/src/django_rbac/models.py +++ b/src/django_rbac/models.py @@ -1,6 +1,5 @@ from django.db import models from django.utils.translation import gettext_lazy as _ -from django.utils.translation import pgettext_lazy from . import managers @@ -32,11 +31,3 @@ class Operation(models.Model): Operation._meta.natural_key = ['slug'] - - -ADMIN_OP = Operation.register(name=pgettext_lazy('permission', 'Management'), slug='admin') -CHANGE_OP = Operation.register(name=pgettext_lazy('permission', 'Change'), slug='change') -DELETE_OP = Operation.register(name=pgettext_lazy('permission', 'Delete'), slug='delete') -ADD_OP = Operation.register(name=pgettext_lazy('permission', 'Add'), slug='add') -VIEW_OP = Operation.register(name=pgettext_lazy('permission', 'View'), slug='view') -SEARCH_OP = Operation.register(name=pgettext_lazy('permission', 'Search'), slug='search') diff --git a/src/django_rbac/signal_handlers.py b/src/django_rbac/signal_handlers.py deleted file mode 100644 index 15facf21c..000000000 --- a/src/django_rbac/signal_handlers.py +++ /dev/null @@ -1,44 +0,0 @@ -from django.db import DEFAULT_DB_ALIAS, router - -from . import models, utils - - -def role_parenting_post_save(sender, instance, raw, created, **kwargs): - '''Close the role parenting relation after instance creation''' - if raw: # do nothing if save comes from fixture loading - return - if not instance.direct: # do nothing if instance is not direct - return - sender.objects.update_transitive_closure() - - -def role_parenting_post_delete(sender, instance, **kwargs): - '''Close the role parenting relation after instance deletion''' - if not instance.direct: # do nothing if instance is not direct - return - sender.objects.update_transitive_closure() - - -def role_parenting_post_soft_delete(sender, instance, **kwargs): - '''Close the role parenting relation after instance soft-deletion''' - sender.objects.update_transitive_closure() - - -def create_base_operations(app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, **kwargs): - '''Create some basic operations, matching permissions from Django''' - if not router.allow_migrate(using, models.Operation): - return - - utils.get_operation(models.ADD_OP) - utils.get_operation(models.CHANGE_OP) - utils.get_operation(models.DELETE_OP) - utils.get_operation(models.VIEW_OP) - utils.get_operation(models.ADMIN_OP) - utils.get_operation(models.SEARCH_OP) - - -def fix_role_parenting_closure(app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, **kwargs): - '''Close the role parenting relation after migrations''' - if not router.allow_migrate(using, utils.get_role_parenting_model()): - return - utils.get_role_parenting_model().objects.update_transitive_closure() diff --git a/tests/api/test_all.py b/tests/api/test_all.py index 72dcfd935..f94a09689 100644 --- a/tests/api/test_all.py +++ b/tests/api/test_all.py @@ -32,6 +32,7 @@ from django.utils.encoding import force_str from django.utils.text import slugify from requests.models import Response +from authentic2.a2_rbac.models import SEARCH_OP from authentic2.a2_rbac.models import OrganizationalUnit as OU from authentic2.a2_rbac.models import Role from authentic2.a2_rbac.utils import get_default_ou @@ -40,7 +41,6 @@ from authentic2.custom_user.models import Profile, ProfileType from authentic2.models import APIClient, Attribute, AttributeValue, AuthorizedRole, PasswordReset, Service from authentic2.utils.misc import good_next_url from authentic2_idp_cas.models import Service as CASService -from django_rbac.models import SEARCH_OP from ..utils import assert_event, basic_authorization_header, get_link_from_mail, login diff --git a/tests/test_a2_rbac.py b/tests/test_a2_rbac.py index ba29a46b7..24fe0e594 100644 --- a/tests/test_a2_rbac.py +++ b/tests/test_a2_rbac.py @@ -19,14 +19,14 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.core.management import call_command -from authentic2.a2_rbac.models import MANAGE_MEMBERS_OP +from authentic2.a2_rbac.models import CHANGE_OP, MANAGE_MEMBERS_OP from authentic2.a2_rbac.models import OrganizationalUnit as OU from authentic2.a2_rbac.models import Permission, Role, RoleAttribute from authentic2.a2_rbac.utils import get_default_ou from authentic2.custom_user.models import User from authentic2.models import Service from authentic2.utils.misc import get_hex_uuid -from django_rbac.models import CHANGE_OP, Operation +from django_rbac.models import Operation from tests.utils import login, request_select2, scoped_db_fixture diff --git a/tests/test_api_client.py b/tests/test_api_client.py index e488b0c92..4450e66b2 100644 --- a/tests/test_api_client.py +++ b/tests/test_api_client.py @@ -6,9 +6,8 @@ from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType from django.urls import reverse -from authentic2.a2_rbac.models import Role +from authentic2.a2_rbac.models import ADD_OP, SEARCH_OP, VIEW_OP, Role from authentic2.models import APIClient -from django_rbac.models import ADD_OP, SEARCH_OP, VIEW_OP User = get_user_model() diff --git a/tests/test_commands.py b/tests/test_commands.py index b684a3c81..fe7be9d78 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -27,13 +27,20 @@ from django.contrib.contenttypes.models import ContentType from django.utils.timezone import now from jwcrypto.jwk import JWK, JWKSet -from authentic2.a2_rbac.models import MANAGE_MEMBERS_OP, VIEW_OP, OrganizationalUnit, Permission, Role +from authentic2.a2_rbac.models import ( + ADMIN_OP, + MANAGE_MEMBERS_OP, + VIEW_OP, + OrganizationalUnit, + Permission, + Role, +) from authentic2.a2_rbac.utils import get_default_ou from authentic2.apps.journal.models import Event from authentic2.custom_user.models import DeletedUser from authentic2.models import UserExternalId from authentic2_auth_oidc.models import OIDCAccount, OIDCProvider -from django_rbac.models import ADMIN_OP, Operation +from django_rbac.models import Operation from django_rbac.utils import get_operation from .utils import call_command, login diff --git a/tests/test_manager.py b/tests/test_manager.py index 9d2f4c055..503b84e0b 100644 --- a/tests/test_manager.py +++ b/tests/test_manager.py @@ -28,7 +28,7 @@ from django.urls import reverse from django.utils.encoding import force_bytes, force_str from webtest import Upload -from authentic2.a2_rbac.models import MANAGE_MEMBERS_OP +from authentic2.a2_rbac.models import MANAGE_MEMBERS_OP, VIEW_OP from authentic2.a2_rbac.models import OrganizationalUnit as OU from authentic2.a2_rbac.models import Permission, Role from authentic2.a2_rbac.utils import get_default_ou @@ -37,7 +37,6 @@ from authentic2.models import APIClient, Service from authentic2.validators import EmailValidator from authentic2_idp_oidc import app_settings as oidc_app_settings from authentic2_idp_oidc.models import OIDCClaim, OIDCClient -from django_rbac.models import VIEW_OP from django_rbac.utils import get_operation from .utils import assert_event, get_link_from_mail, login, request_select2, text_content diff --git a/tests/test_user_manager.py b/tests/test_user_manager.py index a85b90cb1..7363dff66 100644 --- a/tests/test_user_manager.py +++ b/tests/test_user_manager.py @@ -27,6 +27,7 @@ from django.contrib.contenttypes.models import ContentType from django.urls import reverse from webtest import Upload +from authentic2.a2_rbac.models import VIEW_OP from authentic2.a2_rbac.models import OrganizationalUnit as OU from authentic2.a2_rbac.models import Permission, Role from authentic2.a2_rbac.utils import get_default_ou, get_view_user_perm @@ -35,7 +36,6 @@ from authentic2.custom_user.models import User from authentic2.manager import user_import from authentic2.models import Attribute, AttributeValue from authentic2_idp_oidc.models import OIDCAuthorization, OIDCClient -from django_rbac.models import VIEW_OP from django_rbac.utils import get_operation from .utils import get_link_from_mail, login, logout -- 2.35.1