Repository: pulp/pulpcore

Issue:
There is no way for users to prevent creation_hooks from running without editing every AccessPolicy to remove them
## Problem

Users want to fully disable the default Authorization of pulp by modifying the DEFAULT_PERMISSION_CLASSES, but even if they do the creation hooks will still be running, likely assigned unwanted permissions to various users and groups. 

## Solution

Have the calling of creation_hooks happen in side an interface on `AccessPolicyFromDB`, e.g. named `handle_creation_hooks` or some reasonable name. By delegating the running of creation hooks onto that object, when the user changes it, they won't run anymore.


Discussion:

Resolving patch:
diff --git a/pulpcore/app/access_policy.py b/pulpcore/app/access_policy.py
--- a/pulpcore/app/access_policy.py
+++ b/pulpcore/app/access_policy.py
@@ -1,8 +1,8 @@
 from rest_access_policy import AccessPolicy
-from django.db.utils import ProgrammingError
 
+from pulpcore.app.loggers import deprecation_logger
 from pulpcore.app.models import AccessPolicy as AccessPolicyModel
-from pulpcore.app.util import get_view_urlpattern
+from pulpcore.app.util import get_view_urlpattern, get_viewset_for_model
 
 
 class AccessPolicyFromDB(AccessPolicy):
@@ -10,6 +10,57 @@ class AccessPolicyFromDB(AccessPolicy):
     An AccessPolicy that loads statements from an `AccessPolicy` model instance.
     """
 
+    @staticmethod
+    def get_access_policy(view):
+        """
+        Retrieves the AccessPolicy from the DB or None if it doesn't exist.
+
+        Args:
+            view (subclass of rest_framework.view.APIView): The view or viewset to receive the
+                AccessPolicy model for.
+
+        Returns:
+            Either a `pulpcore.app.models.AccessPolicy` or None.
+        """
+        try:
+            urlpattern = get_view_urlpattern(view)
+        except AttributeError:
+            # The view does not define a `urlpattern()` method, e.g. it's not a NamedModelViewset
+            return None
+
+        try:
+            return AccessPolicyModel.objects.get(viewset_name=urlpattern)
+        except AccessPolicyModel.DoesNotExist:
+            return None
+
+    @classmethod
+    def handle_creation_hooks(cls, obj):
+        """
+        Handle the creation hooks defined in this policy for the passed in `obj`.
+
+        Args:
+            cls: The class this method belongs to.
+            obj: The model instance to have its creation hooks handled for.
+
+        """
+        viewset = get_viewset_for_model(obj)
+        access_policy = cls.get_access_policy(viewset)
+        if access_policy and access_policy.creation_hooks is not None:
+            for creation_hook in access_policy.creation_hooks:
+                function = obj.REGISTERED_CREATION_HOOKS.get(creation_hook["function"])
+                if function is not None:
+                    kwargs = creation_hook.get("parameters") or {}
+                    function(**kwargs)
+                else:
+                    # Old interface deprecated for removal in 3.20
+                    function = getattr(obj, creation_hook["function"])
+                    deprecation_logger.warn(
+                        "Calling unregistered creation hooks from the access policy is deprecated"
+                        " and may be removed with pulpcore 3.20."
+                        f"[hook={creation_hook}, viewset={access_policy.viewset_name}]."
+                    )
+                    function(creation_hook.get("permissions"), creation_hook.get("parameters"))
+
     def get_policy_statements(self, request, view):
         """
         Return the policy statements from an AccessPolicy instance matching the viewset name.
@@ -35,12 +86,9 @@ def get_policy_statements(self, request, view):
         Returns:
             The access policy statements in drf-access-policy policy structure.
         """
-        try:
-            viewset_name = get_view_urlpattern(view)
-            access_policy_obj = AccessPolicyModel.objects.get(viewset_name=viewset_name)
-        except (AccessPolicyModel.DoesNotExist, AttributeError, ProgrammingError):
+        if access_policy_obj := self.get_access_policy(view):
+            return access_policy_obj.statements
+        else:
             default_statement = [{"action": "*", "principal": "admin", "effect": "allow"}]
             policy = getattr(view, "DEFAULT_ACCESS_POLICY", {"statements": default_statement})
             return policy["statements"]
-        else:
-            return access_policy_obj.statements
diff --git a/pulpcore/app/models/access_policy.py b/pulpcore/app/models/access_policy.py
--- a/pulpcore/app/models/access_policy.py
+++ b/pulpcore/app/models/access_policy.py
@@ -1,6 +1,3 @@
-from gettext import gettext as _
-
-from django.core.exceptions import ImproperlyConfigured
 from django.contrib.auth import get_user_model
 from django.contrib.auth.models import Group as BaseGroup
 from django.db import models
@@ -10,7 +7,7 @@
 from guardian.shortcuts import assign_perm, remove_perm
 
 from pulpcore.app.models import BaseModel
-from pulpcore.app.loggers import deprecation_logger
+from pulpcore.app.util import get_viewset_for_model
 
 
 def _ensure_iterable(obj):
@@ -76,33 +73,10 @@ def __init__(self, *args, **kwargs):
 
     @hook("after_create")
     def add_perms(self):
-        try:
-            access_policy = AccessPolicy.objects.get(viewset_name=self.ACCESS_POLICY_VIEWSET_NAME)
-        except AttributeError:
-            raise ImproperlyConfigured(
-                _(
-                    "When using the `AutoAddObjPermsMixin`, plugin writers must declare an"
-                    "`ACCESS_POLICY_VIEWSET_NAME` class attribute."
-                )
-            )
-        self._handle_creation_hooks(access_policy)
-
-    def _handle_creation_hooks(self, access_policy):
-        if access_policy.creation_hooks is not None:
-            for creation_hook in access_policy.creation_hooks:
-                function = self.REGISTERED_CREATION_HOOKS.get(creation_hook["function"])
-                if function is not None:
-                    kwargs = creation_hook.get("parameters") or {}
-                    function(**kwargs)
-                else:
-                    # Old interface deprecated for removal in 3.20
-                    function = getattr(self, creation_hook["function"])
-                    deprecation_logger.warn(
-                        "Calling unregistered creation hooks from the access policy is deprecated"
-                        " and may be removed with pulpcore 3.20 "
-                        f"[hook={creation_hook}, viewset={access_policy.viewset_name}]."
-                    )
-                    function(creation_hook.get("permissions"), creation_hook.get("parameters"))
+        viewset = get_viewset_for_model(self)
+        for permission_class in viewset.get_permissions(viewset):
+            if hasattr(permission_class, "handle_creation_hooks"):
+                permission_class.handle_creation_hooks(self)
 
     def add_for_users(self, permissions, users):
         """


Test patch: