diff --git a/arches/app/models/resource.py b/arches/app/models/resource.py index be32ff0f3f6..747d75e15b7 100644 --- a/arches/app/models/resource.py +++ b/arches/app/models/resource.py @@ -337,6 +337,10 @@ def index(self, context=None): if str(self.graph_id) != str(settings.SYSTEM_SETTINGS_RESOURCE_MODEL_ID): datatype_factory = DataTypeFactory() + if not self.get_serialized_graph()["nodes"]: + logger.error("Graph for resource %s (%s) missing graph when indexing", str(self.pk), str(self.graphid)) + return + node_datatypes = { str(nodeid): datatype for nodeid, datatype in ((k["nodeid"], k["datatype"]) for k in self.get_serialized_graph()["nodes"]) } diff --git a/arches/app/models/tile.py b/arches/app/models/tile.py index e44e6ba274f..3caa321099f 100644 --- a/arches/app/models/tile.py +++ b/arches/app/models/tile.py @@ -387,7 +387,10 @@ def datatype_post_save_actions(self, request=None): try: node = SimpleNamespace(**next((x for x in self.serialized_graph["nodes"] if x["nodeid"] == nodeid), None)) except: - node = models.Node.objects.get(nodeid=nodeid) + try: + node = models.Node.objects.get(nodeid=nodeid) + except models.Node.DoesNotExist: + continue datatype = self.datatype_factory.get_instance(node.datatype) datatype.post_tile_save(self, nodeid, { "GET": request.GET if request else {}, @@ -422,7 +425,10 @@ def save(self, *args, **kwargs): with transaction.atomic(): for nodeid in self.data.keys(): - node = next(item for item in self.serialized_graph["nodes"] if item["nodeid"] == nodeid) + try: + node = next(item for item in self.serialized_graph["nodes"] if item["nodeid"] == nodeid) + except StopIteration: + continue datatype = self.datatype_factory.get_instance(node["datatype"]) datatype.pre_tile_save(self, nodeid) self.__preSave(request, context=context) diff --git a/arches/app/permissions/arches_standard.py b/arches/app/permissions/arches_standard.py index bc0a7e944b1..b21d2f27c42 100644 --- a/arches/app/permissions/arches_standard.py +++ b/arches/app/permissions/arches_standard.py @@ -44,7 +44,7 @@ import inspect from arches.app.models.models import * from django.contrib.contenttypes.models import ContentType -from arches.app.models.models import ResourceInstance, MapLayer +from arches.app.models.models import ResourceInstance, MapLayer, Plugin from arches.app.search.elasticsearch_dsl_builder import Bool, Query, Terms, Nested from arches.app.search.mappings import RESOURCES_INDEX from arches.app.utils.permission_backend import PermissionFramework, NotUserNorGroup as ArchesNotUserNorGroup @@ -374,6 +374,51 @@ def update_permissions_for_group(self, group: Group) -> None: """Hook for spotting permission updates on a group.""" ... + def get_plugins_by_permission(self, user: User, perms: str | Iterable[str] = "view_plugin") -> list[Plugin | str]: + """ + Checks which plugins a user has any explicit permissions + + Arguments: + user -- the user to check + plugins -- the plugins against which to confirm access + perms -- one or a list of permissions to be checked + + Returns: + A list of plugins that match + + """ + plugin_objs = list(Plugin.objects.all().order_by("sortorder")) + if isinstance(perms, str): + perms = (perms,) + return [plugin for plugin in plugin_objs if all(user.has_perm(perm, plugin) for perm in perms)] + + def user_has_plugin_permissions(self, user: User, plugin: Plugin | uuid.UUID | str | None, perms: str | Iterable[str] = "view_plugin") -> bool: + """ + Checks if a user has any explicit permissions to a plugin + + Arguments: + user -- the user to check + plugin -- the plugin against which to confirm access, or + perms -- one or a list of permissions to be checked + + Returns: + A list of 0, 1 or more plugin IDs that match + + """ + + plugin_obj: Plugin + if isinstance(plugin, Plugin): + plugin_obj = plugin + else: + try: + plugin = uuid.UUID(plugin) # type: ignore + plugin_obj = Plugin.objects.get(pk=plugin) + except ValueError: + plugin_obj = Plugin.objects.get(slug=plugin) + if isinstance(perms, str): + perms = (perms,) + return all(user.has_perm(perm, plugin_obj) for perm in perms) + def user_has_resource_model_permissions(self, user: User, perms: str | Iterable[str], resource: ResourceInstance | None=None, graph_id: str | None=None) -> bool: """ Checks if a user has any explicit permissions to a model's nodegroups @@ -531,7 +576,7 @@ def user_can_delete_model_nodegroups(self, user: User, resource: ResourceInstanc def user_can_read_graph(self, user: User, graph_id: str) -> bool: """ - returns a boolean denoting if a user has permmission to read a model's nodegroups + returns a boolean denoting if a user has permission to read a model's nodegroups Arguments: user -- the user to check @@ -588,7 +633,6 @@ def group_required(self, user: User, *group_names: list[str]) -> bool: # - Resource Exporter # - System Administrator - print("WAT", user, user.is_authenticated, user.is_superuser, user.groups, group_names) if user.is_authenticated: if user.is_superuser or bool(user.groups.filter(name__in=group_names)): return True diff --git a/arches/app/utils/permission_backend.py b/arches/app/utils/permission_backend.py index 0bf326e7d95..9b518064ed2 100644 --- a/arches/app/utils/permission_backend.py +++ b/arches/app/utils/permission_backend.py @@ -111,6 +111,14 @@ def user_can_write_map_layers(self, user): def process_new_user(self, instance, created): ... + @abstractmethod + def get_plugins_by_permission(self, user, perms = "view_plugin"): + ... + + @abstractmethod + def user_has_plugin_permissions(self, user, plugin, perms = "view_plugin"): + ... + @abstractmethod def user_has_resource_model_permissions(self, user, perms, resource): ... @@ -243,6 +251,12 @@ def update_permissions_for_user(instance): def update_permissions_for_group(instance): return _get_permission_framework().update_permissions_for_group(instance) +def get_plugins_by_permission(user, perms = "view_plugin"): + return _get_permission_framework().get_plugins_by_permission(user, perms=perms) + +def user_has_plugin_permissions(user, plugin, perms = "view_plugin"): + return _get_permission_framework().user_has_plugin_permissions(user, plugin, perms) + def user_has_resource_model_permissions(user, perms, resource): return _get_permission_framework().user_has_resource_model_permissions(user, perms, resource) diff --git a/arches/app/views/api.py b/arches/app/views/api.py index b92d3341701..1cc9c0c02a0 100644 --- a/arches/app/views/api.py +++ b/arches/app/views/api.py @@ -58,6 +58,8 @@ get_restricted_instances, check_resource_instance_permissions, get_nodegroups_by_perm, + user_has_plugin_permissions, + get_plugins_by_permission, ) from arches.app.utils.geo_utils import GeoUtils from arches.app.utils.permission_backend import user_is_resource_editor @@ -939,12 +941,13 @@ def get(self, request, resourceid): class Plugins(View): def get(self, request, plugin_id=None): - if plugin_id: - plugins = models.Plugin.objects.filter(pk=plugin_id) + if plugin_id is None: + plugins = get_plugins_by_permission(user=request.user) else: - plugins = models.Plugin.objects.all() - - plugins = [plugin for plugin in plugins if self.request.user.has_perm("view_plugin", plugin)] + plugins = user_has_plugin_permissions( + user=request.user, + plugins=[models.Plugin.objects.get(pk=plugin_id)] + ) return JSONResponse(plugins) diff --git a/arches/app/views/base.py b/arches/app/views/base.py index 49813cbd338..6db2bbfa8c0 100644 --- a/arches/app/views/base.py +++ b/arches/app/views/base.py @@ -31,6 +31,7 @@ get_editable_resource_types, get_resource_types_by_perm, user_can_read_map_layers, + get_plugins_by_permission, ) class BaseManagerView(TemplateView): @@ -46,10 +47,7 @@ def get_context_data(self, **kwargs): context["system_settings_graphid"] = settings.SYSTEM_SETTINGS_RESOURCE_MODEL_ID context["graph_models"] = [] context["graphs"] = "[]" - context["plugins"] = [] - for plugin in models.Plugin.objects.all().order_by("sortorder"): - if self.request.user.has_perm("view_plugin", plugin): - context["plugins"].append(plugin) + context["plugins"] = get_plugins_by_permission(user=self.request.user) createable = list(get_createable_resource_models(self.request.user)) createable.sort(key=lambda x: x.name.lower()) diff --git a/arches/app/views/plugin.py b/arches/app/views/plugin.py index 8bca1352a2a..bf550406954 100644 --- a/arches/app/views/plugin.py +++ b/arches/app/views/plugin.py @@ -23,22 +23,26 @@ from arches.app.utils.betterJSONSerializer import JSONSerializer from arches.app.utils.response import JSONResponse from arches.app.views.base import MapBaseManagerView +from arches.app.utils.permission_backend import user_has_plugin_permissions class PluginView(MapBaseManagerView): action = None def get(self, request, pluginid=None, slug=None): - if slug is not None: - plugin = models.Plugin.objects.get(slug=slug) - else: - plugin = models.Plugin.objects.get(pk=pluginid) + if not slug or pluginid: + raise Exception("Need pluginid or slug") - if not request.user.has_perm("view_plugin", plugin): - if slug is not None: - return redirect("/auth?next=/plugins/{}".format(slug)) - if slug is not None: - return redirect("/auth?next=/plugins/{}".format(pluginid)) + pluginid = slug or pluginid + plugin = None + if user_has_plugin_permissions(request.user, plugin=pluginid): + try: + pluginid = uuid.UUID(pluginid) # type: ignore + plugin = models.Plugin.objects.get(pk=pluginid) + except ValueError: + plugin = models.Plugin.objects.get(slug=pluginid) + if not plugin: + return redirect("/auth?next=/plugins/{}".format(pluginid)) if request.GET.get("json"): return JSONResponse(plugin)