Skip to content

Commit

Permalink
feat: plugin permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
philtweir committed Sep 3, 2024
1 parent f8671a0 commit ab60bf1
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 23 deletions.
4 changes: 4 additions & 0 deletions arches/app/models/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
}
Expand Down
10 changes: 8 additions & 2 deletions arches/app/models/tile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 {},
Expand Down Expand Up @@ -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)
Expand Down
50 changes: 47 additions & 3 deletions arches/app/permissions/arches_standard.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions arches/app/utils/permission_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
...
Expand Down Expand Up @@ -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)

Expand Down
13 changes: 8 additions & 5 deletions arches/app/views/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
6 changes: 2 additions & 4 deletions arches/app/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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())
Expand Down
22 changes: 13 additions & 9 deletions arches/app/views/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit ab60bf1

Please sign in to comment.