From 69fc7a52deddc1bdd70e315f3b323e0f40bf2334 Mon Sep 17 00:00:00 2001 From: Ronald Krist <34395689+SilvyPuzzlewell@users.noreply.github.com> Date: Wed, 4 Dec 2024 07:26:56 +0100 Subject: [PATCH] =?UTF-8?q?has=5Fpermission=20check=20for=20link=20creatio?= =?UTF-8?q?n=20and=20class=20for=20combining=20multip=E2=80=A6=20(#227)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * has_permission check for link creation and class for combining multiple conditions * has published record * has published record cjecl * allowing use of limited set of links for search results * links template class comes from service config * deprecated calling link condtions from .records * moved link conditions to services.config using & and | model of combining * file_service for file_record_class getter * list_files in presets * format * version bump * format and exception checking * type checking and correct exception message * Version bump --------- Co-authored-by: Ronald Krist Co-authored-by: Mirek Simek --- oarepo_runtime/datastreams/utils.py | 4 +- oarepo_runtime/records/__init__.py | 46 +++++--- oarepo_runtime/services/config/__init__.py | 14 +++ .../services/config/link_conditions.py | 101 ++++++++++++++++++ .../services/config/permissions_presets.py | 3 + oarepo_runtime/services/results.py | 9 +- setup.cfg | 2 +- 7 files changed, 162 insertions(+), 17 deletions(-) create mode 100644 oarepo_runtime/services/config/link_conditions.py diff --git a/oarepo_runtime/datastreams/utils.py b/oarepo_runtime/datastreams/utils.py index cf78ae8..6b21465 100644 --- a/oarepo_runtime/datastreams/utils.py +++ b/oarepo_runtime/datastreams/utils.py @@ -48,7 +48,6 @@ def get_record_service_for_record_deprecated(record): if service_record == type(record): return svc - def get_file_service_for_record_class(record_class): if not record_class: return None @@ -60,6 +59,9 @@ def get_file_service_for_record_class(record_class): continue return svc +def get_file_service_for_file_record_class(file_record_class): + record_class = file_record_class.record_cls + return get_file_service_for_record_class(record_class) def get_file_service_for_record_service( record_service, check_draft_files=True, record=None diff --git a/oarepo_runtime/records/__init__.py b/oarepo_runtime/records/__init__.py index 61a66f6..19bbddb 100644 --- a/oarepo_runtime/records/__init__.py +++ b/oarepo_runtime/records/__init__.py @@ -1,5 +1,6 @@ from typing import Type +from deprecated import deprecated from invenio_records_resources.records import Record @@ -11,20 +12,39 @@ def select_record_for_update(record_cls: Type[Record], persistent_identifier): return record_cls(obj.data, model=obj) -def is_published_record(record, ctx): - """Shortcut for links to determine if record is a published record.""" - return not getattr(record, "is_draft", False) +@deprecated("Moved to oarepo_runtime.services.config.link_conditions") +def is_published_record_function(): + """Shortcut for links to determine if record is a published. + This function is deprecated. Use oarepo_runtime.services.config.is_published_record instead. + """ + from oarepo_runtime.services.config.link_conditions import is_published_record -def is_draft_record(record, ctx): - """Shortcut for links to determine if record is a draft record.""" - return getattr(record, "is_draft", False) + return is_published_record() -def has_draft(record, ctx): - """Shortcut for links to determine if record is either a draft or a published one with a draft associated.""" - if getattr(record, "is_draft", False): - return True - if getattr(record, "has_draft", False): - return True - return False +@deprecated("Moved to oarepo_runtime.services.config.link_conditions") +def is_draft_record_function(): + """Shortcut for links to determine if record is a draft record. + + This function is deprecated. Use oarepo_runtime.services.config.is_draft_record instead. + """ + from oarepo_runtime.services.config.link_conditions import is_draft_record + + return is_draft_record() + + +@deprecated("Moved to oarepo_runtime.services.config.link_conditions") +def has_draft_function(): + """Shortcut for links to determine if record is either a draft or a published one with a draft associated. + + This function is deprecated. Use oarepo_runtime.services.config.has_draft instead. + """ + from oarepo_runtime.services.config.link_conditions import has_draft + + return has_draft() + + +is_published_record = is_published_record_function() +is_draft = is_draft_record_function() +has_draft = has_draft_function() diff --git a/oarepo_runtime/services/config/__init__.py b/oarepo_runtime/services/config/__init__.py index 604f69c..4b5bf52 100644 --- a/oarepo_runtime/services/config/__init__.py +++ b/oarepo_runtime/services/config/__init__.py @@ -1,3 +1,11 @@ +from .link_conditions import ( + has_draft, + has_permission, + has_permission_file_service, + has_published_record, + is_draft_record, + is_published_record, +) from .permissions_presets import ( AuthenticatedPermissionPolicy, EveryonePermissionPolicy, @@ -12,4 +20,10 @@ "ReadOnlyPermissionPolicy", "EveryonePermissionPolicy", "AuthenticatedPermissionPolicy", + "is_published_record", + "is_draft_record", + "has_draft", + "has_permission", + "has_permission_file_service", + "has_published_record", ) diff --git a/oarepo_runtime/services/config/link_conditions.py b/oarepo_runtime/services/config/link_conditions.py new file mode 100644 index 0000000..5c68662 --- /dev/null +++ b/oarepo_runtime/services/config/link_conditions.py @@ -0,0 +1,101 @@ +from abc import abstractmethod + +from invenio_pidstore.errors import PIDDoesNotExistError, PIDUnregistered +from invenio_records_resources.records.api import FileRecord +from invenio_records.api import RecordBase +from invenio_records_resources.records.api import Record +from logging import getLogger +from ...datastreams.utils import ( + get_file_service_for_file_record_class, + get_file_service_for_record_class, + get_record_service_for_record, +) +log = getLogger(__name__) + +class Condition: + + @abstractmethod + def __call__(self, obj, ctx: dict): + raise NotImplementedError + + def __and__(self, other): + return type( + "CompositeCondition", + (Condition,), + {"__call__": lambda _, obj, ctx: self(obj, ctx) and other(obj, ctx)}, + )() + + def __or__(self, other): + return type( + "CompositeCondition", + (Condition,), + {"__call__": lambda _, obj, ctx: self(obj, ctx) or other(obj, ctx)}, + )() + + +class is_published_record(Condition): + """Shortcut for links to determine if record is a published record.""" + + def __call__(self, obj: Record, ctx: dict): + return not getattr(obj, "is_draft", False) + + +class is_draft_record(Condition): + """Shortcut for links to determine if record is a draft record.""" + + def __call__(self, obj: Record, ctx: dict): + return getattr(obj, "is_draft", False) + + +class has_draft(Condition): + """Shortcut for links to determine if record is either a draft or a published one with a draft associated.""" + + def __call__(self, obj: Record, ctx: dict): + if getattr(obj, "is_draft", False): + return True + if getattr(obj, "has_draft", False): + return True + return False + + +class has_permission(Condition): + def __init__(self, action_name): + self.action_name = action_name + + def __call__(self, obj: RecordBase, ctx: dict): + if isinstance(obj, FileRecord): + obj = obj.record + service = get_record_service_for_record(obj) + try: + return service.check_permission( + action_name=self.action_name, record=obj, **ctx + ) + except Exception as e: + log.exception(f"Unexpected exception {e}.") + + + +class has_permission_file_service(has_permission): + + def __call__(self, obj: RecordBase, ctx: dict): + if isinstance(obj, FileRecord): + service = get_file_service_for_file_record_class(type(obj)) + else: + service = get_file_service_for_record_class(type(obj)) + try: + return service.check_permission( + action_name=self.action_name, record=obj, **ctx + ) + except Exception as e: + log.exception(f"Unexpected exception {e}.") + + +class has_published_record(Condition): + + def __call__(self, obj: Record, ctx: dict): + service = get_record_service_for_record(obj) + try: + service.record_cls.pid.resolve(obj["id"]) + except (PIDUnregistered, PIDDoesNotExistError): + return False + return True diff --git a/oarepo_runtime/services/config/permissions_presets.py b/oarepo_runtime/services/config/permissions_presets.py index ada960a..5ea8235 100644 --- a/oarepo_runtime/services/config/permissions_presets.py +++ b/oarepo_runtime/services/config/permissions_presets.py @@ -56,6 +56,7 @@ class ReadOnlyPermissionPolicy(RecordPermissionPolicy): can_read_files = [AnyUser(), SystemProcess()] can_update_files = [SystemProcess()] can_delete_files = [SystemProcess()] + can_list_files = [SystemProcess()] can_edit = [SystemProcess()] can_new_version = [SystemProcess()] @@ -92,6 +93,7 @@ class EveryonePermissionPolicy(RecordPermissionPolicy): can_read_files = [SystemProcess(), AnyUser()] can_update_files = [SystemProcess(), AnyUser()] can_delete_files = [SystemProcess(), AnyUser()] + can_list_files = [SystemProcess(), AnyUser()] can_edit = [SystemProcess(), AnyUser()] can_new_version = [SystemProcess(), AnyUser()] @@ -128,6 +130,7 @@ class AuthenticatedPermissionPolicy(RecordPermissionPolicy): can_read_files = [SystemProcess(), AnyUser()] can_update_files = [SystemProcess(), AuthenticatedUser()] can_delete_files = [SystemProcess(), AuthenticatedUser()] + can_list_files = [SystemProcess(), AuthenticatedUser()] can_edit = [SystemProcess(), AuthenticatedUser()] can_new_version = [SystemProcess(), AuthenticatedUser()] diff --git a/oarepo_runtime/services/results.py b/oarepo_runtime/services/results.py index aa6a713..146f15c 100644 --- a/oarepo_runtime/services/results.py +++ b/oarepo_runtime/services/results.py @@ -114,7 +114,12 @@ def hits(self): record=record, ), ) - if self._links_item_tpl: + if hasattr(self._service.config, "links_search_item"): + links_tpl = self._service.config.search_item_links_template( + self._service.config.links_search_item + ) + projection["links"] = links_tpl.expand(self._identity, record) + elif self._links_item_tpl: projection["links"] = self._links_item_tpl.expand( self._identity, record ) @@ -122,7 +127,7 @@ def hits(self): for c in self.components: c.update_data( identity=self._identity, - record=self._record, + record=record, projection=projection, expand=self._expand, ) diff --git a/setup.cfg b/setup.cfg index 75a58f0..f40334e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = oarepo-runtime -version = 1.5.75 +version = 1.5.76 description = A set of runtime extensions of Invenio repository authors = Alzbeta Pokorna readme = README.md