diff --git a/geonode/resource/manager.py b/geonode/resource/manager.py index f5322a46c39..4d8842c8a70 100644 --- a/geonode/resource/manager.py +++ b/geonode/resource/manager.py @@ -45,7 +45,12 @@ from geonode.thumbs.utils import ThumbnailAlgorithms from geonode.documents.tasks import create_document_thumbnail from geonode.security.permissions import PermSpecCompact, DATA_STYLABLE_RESOURCES_SUBTYPES -from geonode.security.utils import perms_as_set, get_user_groups, skip_registered_members_common_group +from geonode.security.utils import ( + perms_as_set, + get_user_groups, + skip_registered_members_common_group, +) +from geonode.security.registry import permissions_registry from . import settings as rm_settings from .utils import update_resource, resourcebase_post_save @@ -574,11 +579,12 @@ def set_permissions( else: _permissions = None - # Fixup Advanced Workflow permissions - _perm_spec = AdvancedSecurityWorkflowManager.get_permissions( - _resource.uuid, - instance=_resource, - permissions=_permissions, + """ + Align _perm_spec based on the permissions handlers + """ + _perm_spec = permissions_registry.fixup_perms( + _resource, + _permissions, created=created, approval_status_changed=approval_status_changed, group_status_changed=group_status_changed, diff --git a/geonode/security/apps.py b/geonode/security/apps.py index 3475f4d05f1..a5646d72bd0 100644 --- a/geonode/security/apps.py +++ b/geonode/security/apps.py @@ -22,3 +22,9 @@ class GeoNodeSecurityAppConfig(AppConfig): name = "geonode.security" verbose_name = "GeoNode Security" + + def ready(self): + super().ready() + from geonode.security.registry import permissions_registry + + permissions_registry.init_registry() diff --git a/geonode/security/handlers.py b/geonode/security/handlers.py new file mode 100644 index 00000000000..ddfba19a539 --- /dev/null +++ b/geonode/security/handlers.py @@ -0,0 +1,59 @@ +######################################################################### +# +# Copyright (C) 2024 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### +from abc import ABC + +from geonode.security.utils import AdvancedSecurityWorkflowManager + + +class BasePermissionsHandler(ABC): + """ + Abstract permissions handler. + This is the base class, all the permissions instances should + inherit from this class. + All the flows that touches the permissions will use this class + (example advanced workflow) + """ + + def __str__(self): + return f"{self.__module__}.{self.__class__.__name__}" + + def __repr__(self): + return self.__str__() + + @staticmethod + def fixup_perms(instance, perms_payload, *args, **kwargs): + return perms_payload + + +class AdvancedWorkflowPermissionsHandler(BasePermissionsHandler): + """ + Handler that takes care of adjusting the permissions for the advanced workflow + """ + + @staticmethod + def fixup_perms(instance, perms_payload, *args, **kwargs): + # Fixup Advanced Workflow permissions + return AdvancedSecurityWorkflowManager.get_permissions( + instance.uuid, + instance=instance, + permissions=perms_payload, + created=kwargs.get("created"), + approval_status_changed=kwargs.get("approval_status_changed"), + group_status_changed=kwargs.get("group_status_changed"), + ) diff --git a/geonode/security/registry.py b/geonode/security/registry.py new file mode 100644 index 00000000000..512315186ee --- /dev/null +++ b/geonode/security/registry.py @@ -0,0 +1,65 @@ +######################################################################### +# +# Copyright (C) 2024 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### +from django.conf import settings +from django.utils.module_loading import import_string +from geonode.security.handlers import BasePermissionsHandler + + +class PermissionsHandlerRegistry: + + REGISTRY = [] + + def init_registry(self): + self._register() + self.sanity_checks() + + def add(self, module_path): + item = import_string(module_path)() + self.__check_item(item) + self.REGISTRY.append(item) + + def reset(self): + self.REGISTRY = [] + + def _register(self): + for module_path in settings.PERMISSIONS_HANDLERS: + self.add(module_path) + + def sanity_checks(self): + for item in self.REGISTRY: + self.__check_item(item) + + def __check_item(self, item): + """ + Ensure that the handler is a subclass of BasePermissionsHandler + """ + if not isinstance(item, BasePermissionsHandler): + raise Exception(f"Handler {item} is not a subclass of BasePermissionsHandler") + + def fixup_perms(self, instance, payload, *args, **kwargs): + for handler in self.REGISTRY: + payload = handler.fixup_perms(instance, payload, *args, **kwargs) + return payload + + @classmethod + def get_registry(cls): + return PermissionsHandlerRegistry.REGISTRY + + +permissions_registry = PermissionsHandlerRegistry() diff --git a/geonode/security/tests.py b/geonode/security/tests.py index 2c634ecabea..39671ab5b2d 100644 --- a/geonode/security/tests.py +++ b/geonode/security/tests.py @@ -47,6 +47,7 @@ from geonode.layers.models import Dataset from geonode.documents.models import Document from geonode.compat import ensure_string +from geonode.security.handlers import BasePermissionsHandler from geonode.upload.models import ResourceHandlerInfo from geonode.utils import check_ogc_backend from geonode.tests.utils import check_dataset @@ -56,6 +57,7 @@ from geonode.groups.models import Group, GroupMember, GroupProfile from geonode.layers.populate_datasets_data import create_dataset_data from geonode.base.auth import create_auth_token, get_or_create_token +from geonode.security.registry import permissions_registry from geonode.base.models import Configuration, UserGeoLimit, GroupGeoLimit from geonode.base.populate_test_data import ( @@ -2662,3 +2664,43 @@ def test_user_can_publish(self): # setting back the owner to admin self.dataset.owner = self.admin self.dataset.save() + + +class DummyPermissionsHandler(BasePermissionsHandler): + @staticmethod + def fixup_perms(instance, perms_payload, *args, **kwargs): + return {"perms": ["this", "is", "fake"]} + + +@override_settings(PERMISSIONS_HANDLERS=["geonode.security.handlers.AdvancedWorkflowPermissionsHandler"]) +class TestPermissionsRegistry(GeoNodeBaseTestSupport): + """ + Test to verify the permissions registry + """ + + def tearDown(self): + permissions_registry.reset() + + def test_registry_is_correctly_initiated(self): + """ + The permissions registry should initiated correctly + """ + permissions_registry.init_registry() + self.assertIsNotNone(permissions_registry.get_registry()) + + def test_new_handler_is_registered(self): + permissions_registry.add("geonode.security.tests.DummyPermissionsHandler") + reg = permissions_registry.get_registry() + self.assertTrue("geonode.security.tests.DummyPermissionsHandler" in (str(r) for r in reg)) + + def test_should_raise_exception_if_is_not_subclass(self): + with self.assertRaises(Exception): + permissions_registry.add(int) + + def test_handler_should_handle_the_perms_payload(self): + # create resource + instance = create_single_dataset("fake_dataset") + # adding the dummy at the end, means will win over the other handler + permissions_registry.add("geonode.security.tests.DummyPermissionsHandler") + perms = permissions_registry.fixup_perms(instance, instance.get_all_level_info()) + self.assertDictEqual({"perms": ["this", "is", "fake"]}, perms) diff --git a/geonode/settings.py b/geonode/settings.py index 2be775af909..1d05645f1f8 100644 --- a/geonode/settings.py +++ b/geonode/settings.py @@ -2327,3 +2327,6 @@ def get_geonode_catalogue_service(): ] INSTALLED_APPS += ("geonode.assets",) GEONODE_APPS += ("geonode.assets",) + + +PERMISSIONS_HANDLERS = ["geonode.security.handlers.AdvancedWorkflowPermissionsHandler"]