From 0f4a3b7a328f1052ada54f1932f5eea87678cee9 Mon Sep 17 00:00:00 2001 From: mattiagiupponi Date: Thu, 3 Nov 2022 18:11:15 +0100 Subject: [PATCH] [Fixes #10251] Review geonode management command set_layers_permissions --- .../commands/set_layers_permissions.py | 177 +++++++++++++----- 1 file changed, 130 insertions(+), 47 deletions(-) diff --git a/geonode/layers/management/commands/set_layers_permissions.py b/geonode/layers/management/commands/set_layers_permissions.py index 72e569e3e0f..2860dea5ba3 100644 --- a/geonode/layers/management/commands/set_layers_permissions.py +++ b/geonode/layers/management/commands/set_layers_permissions.py @@ -17,9 +17,18 @@ # ######################################################################### -from django.core.management.base import BaseCommand +import copy +import logging from argparse import RawTextHelpFormatter -from geonode.layers.utils import set_datasets_permissions + +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group +from django.core.management.base import BaseCommand +from geonode.layers.models import Dataset +from geonode.resource.manager import resource_manager +from geonode.security.permissions import PermSpec, PermSpecCompact + +logger = logging.getLogger("geonode.layers.management.set_layers_permissions") class Command(BaseCommand): @@ -48,67 +57,141 @@ def create_parser(self, *args, **kwargs): def add_arguments(self, parser): parser.add_argument( - '-r', - '--resources', - dest='resources', - nargs='*', + "-r", + "--resources", + dest="resources", + nargs="*", type=str, default=None, - help='Resources names for which permissions will be assigned to. ' - 'Default value: None (all the layers will be considered). ' - 'Multiple choices can be typed with white space separator.' - 'A Note: names with white spaces must be typed inside quotation marks.' + help="Resources names for which permissions will be assigned to. " + "Default value: None (all the layers will be considered). " + "Multiple choices can be typed with white space separator." + "A Note: names with white spaces must be typed inside quotation marks.", ) parser.add_argument( - '-p', - '--permission', - dest='permission', + "-p", + "--permission", + dest="permission", type=str, default=None, - help='Permissions to be assigned. ' - 'Allowed values are: read (r), write (w), download (d) and owner (o).' + help="Permissions to be assigned. " "Allowed values are: view (r), download (w), edit (d) and manage (m).", ) parser.add_argument( - '-u', - '--users', - dest='users', - nargs='*', + "-u", + "--users", + dest="users", + nargs="*", type=str, - default=None, - help='Users for which permissions will be assigned to. ' - 'Multiple choices can be typed with white space separator.' + default=[], + help="Users for which permissions will be assigned to. " + "Multiple choices can be typed with white space separator.", ) parser.add_argument( - '-g', - '--groups', - dest='groups', - nargs='*', + "-g", + "--groups", + dest="groups", + nargs="*", type=str, - default=None, - help='Groups for which permissions will be assigned to. ' - 'Multiple choices can be typed with white space separator.' + default=[], + help="Groups for which permissions will be assigned to. " + "Multiple choices can be typed with white space separator.", ) parser.add_argument( - '-d', - '--delete', - dest='delete_flag', - action='store_true', + "-d", + "--delete", + dest="delete_flag", + action="store_true", default=False, - help='Delete permission if it exists.' + help="Delete permission if it exists.", ) def handle(self, *args, **options): # Retrieving the arguments - resources_names = options.get('resources') - permissions_name = options.get('permission') - users_usernames = options.get('users') - groups_names = options.get('groups') - delete_flag = options.get('delete_flag') - set_datasets_permissions( - permissions_name, - resources_names, - users_usernames, - groups_names, - delete_flag, - verbose=True - ) + permissions_name = options.get("permission").replace(" ", "") + + resources_pk = [x.replace(" ", "") for x in options.get("resources", [])] + if resources_pk: + resources_pk = resources_pk[0].split(",") + users_usernames = [x.replace(" ", "") for x in options.get("users", [])] + if users_usernames: + users_usernames = users_usernames[0].split(",") + + groups_names = [x.replace(" ", "") for x in options.get("groups", [])] + if groups_names: + groups_names = groups_names[0].split(",") + + delete_flag = options.get("delete_flag") + + if isinstance(permissions_name, list): + # it accept one kind of permission per request + raise Exception("Only one permission name must be specified") + + if not users_usernames and not groups_names: + raise Exception("Groups or Usernames must be specified") + + not_found = [] + final_perms_payload = {} + + for rpk in resources_pk: + resource = Dataset.objects.filter(pk=rpk) + if not resource.exists(): + not_found.append(rpk) + logger.error(f"Resource named: {rpk} not found, skipping....") + continue + else: + # creating the payload from the CompactPermissions like we do in the UI. + # the result will be a payload with the compact permissions list required + # for the selected resource + resource = resource.first() + # getting the actual permissions available for the dataset + original_perms = PermSpec(resource.get_all_level_info(), resource) + new_perms_payload = {"organizations": [], "users": [], "groups": []} + # if the username is specified, we add them to the payload with the compact + # perm value + if users_usernames: + User = get_user_model() + for _user in users_usernames: + try: + new_perms_payload["users"].append( + {"id": User.objects.get(username=_user).pk, "permissions": permissions_name} + ) + except User.DoesNotExist: + logger.warning(f"The user {_user} does not exists. " "It has been skipped.") + # GROUPS + # if the group is specified, we add them to the payload with the compact + # perm value + if groups_names: + for group_name in groups_names: + try: + new_perms_payload["groups"].append( + {"id": Group.objects.get(name=group_name).pk, "permissions": permissions_name} + ) + except Group.DoesNotExist: + logger.warning(f"The group {group_name} does not exists. " "It has been skipped.") + # Using the compact permissions payload to calculate the permissions + # that we want to give for each user/group + # This part is in common with the permissions API + new_compact_perms = PermSpecCompact(new_perms_payload, resource) + copy_compact_perms = copy.deepcopy(new_compact_perms) + + perms_spec_compact_resource = PermSpecCompact(original_perms.compact, resource) + perms_spec_compact_resource.merge(new_compact_perms) + + final_perms_payload = perms_spec_compact_resource.extended + # if the delete flag is set, we must delete the permissions for the input user/group + if delete_flag: + # since is a delete operation, we must remove the users/group from the resource + # so this will return the updated dict without the user/groups to be removed + final_perms_payload["users"] = { + _user: _perms + for _user, _perms in perms_spec_compact_resource.extended["users"].items() + if _user not in copy_compact_perms.extended["users"] + } + final_perms_payload["groups"] = { + _group: _perms + for _group, _perms in perms_spec_compact_resource.extended["groups"].items() + if _user not in copy_compact_perms.extended["groups"] + } + + # calling the resource manager to set the permissions + resource_manager.set_permissions(resource.uuid, instance=resource, permissions=final_perms_payload)