# Copyright 2016 The Johns Hopkins University Applied Physics Laboratory # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from django.db import transaction from django.contrib.auth.models import Group from django.contrib.auth.models import Permission from rest_framework.views import APIView from rest_framework import status from rest_framework.response import Response from guardian.shortcuts import get_perms_for_model, get_objects_for_group, get_perms from bosscore.models import Collection, Experiment, Channel, BossGroup from bosscore.permissions import BossPermissionManager, check_is_member_or_maintainer from bosscore.constants import PUBLIC_GRP from bosscore.error import BossHTTPError, BossError, ErrorCodes, BossResourceNotFoundError,\ BossGroupNotFoundError, BossPermissionError from bosscore.privileges import check_role class ResourceUserPermission(APIView): """ View to assign Permissions to resource instances """ @staticmethod def get_object(collection, experiment=None, channel=None): """ Return the resource from the request Args: collection: Collection name from the request experiment: Experiment name from the request channel: Channel name Returns: Instance of the resource from the request """ try: if collection and experiment and channel: # Channel specified collection_obj = Collection.objects.get(name=collection) experiment_obj = Experiment.objects.get(name=experiment, collection=collection_obj) obj = Channel.objects.get(name=channel, experiment=experiment_obj) resource_type = 'channel' elif collection and experiment: # Experiment collection_obj = Collection.objects.get(name=collection) obj = Experiment.objects.get(name=experiment, collection=collection_obj) resource_type = 'experiment' elif collection: obj = Collection.objects.get(name=collection) resource_type = 'collection' else: return None return (obj, resource_type) except Collection.DoesNotExist: raise BossError("{} does not exist".format(collection), ErrorCodes.RESOURCE_NOT_FOUND) except Experiment.DoesNotExist: raise BossError("{} does not exist".format(experiment), ErrorCodes.RESOURCE_NOT_FOUND) except Channel.DoesNotExist: raise BossError("{} does not exist".format(channel), ErrorCodes.RESOURCE_NOT_FOUND) # @check_role("resource-manager") def get(self, request): """Return a list of permissions Get the list of the permissions for a group on a resource. These determine the access for the users in the group on the resource Args: request: Django Rest framework request Returns: List of permissions """ try: obj_list = [] group = request.query_params.get('group', None) collection = request.query_params.get('collection', None) experiment = request.query_params.get('experiment', None) channel = request.query_params.get('channel', None) # get permission sets for models col_perms = [perm.codename for perm in get_perms_for_model(Collection)] exp_perms = [perm.codename for perm in get_perms_for_model(Experiment)] channel_perms = [perm.codename for perm in get_perms_for_model(Channel)] resource_object = self.get_object(collection, experiment, channel) if group and resource_object: group = Group.objects.get(name=group) resource_type = resource_object[1] # filtering on both group and resource resource = resource_object[0] perms = get_perms(group, resource) if len(perms) == 0: # Nothing to return data = {'permission-sets': []} return Response(data, status=status.HTTP_200_OK) if resource_type == 'collection': obj = {'group': group.name, 'collection': resource.name, 'permissions': perms} obj_list.append(obj) data = {'permission-sets': obj_list} elif resource_type == 'experiment': obj = {'group': group.name, 'collection': resource.collection.name, 'experiment': resource.name, 'permissions': perms} obj_list.append(obj) data = {'permission-sets': obj_list} else: obj = {'group': group.name, 'collection': resource.experiment.collection.name, 'experiment': resource.experiment.name, 'channel': resource.name, 'permissions': perms} obj_list.append(obj) data = {'permission-sets': obj_list} elif resource_object and not group: # filtering on resource resource = resource_object[0] resource_type = resource_object[1] list_member_groups = request.user.groups.all() for group in list_member_groups: perms = get_perms(group, resource) if resource_type == 'collection' and len(perms) > 0: obj = {'group': group.name, 'collection': resource.name, 'permissions': perms} obj_list.append(obj) elif resource_type == 'experiment' and len(perms) > 0: obj = {'group': group.name, 'collection': resource.collection.name, 'experiment': resource.name, 'permissions': perms} obj_list.append(obj) elif resource_type == 'channel' and len(perms) > 0: obj = {'group': group.name, 'collection': resource.experiment.collection.name, 'experiment': resource.experiment.name, 'channel': resource.name, 'permissions': perms} obj_list.append(obj) data = {'permission-sets': obj_list} elif group and not resource_object: # filtering on group group = Group.objects.get(name=group) col_list = get_objects_for_group(group, perms=col_perms, klass=Collection, any_perm=True) for col in col_list: col_perms = get_perms(group, col) obj = {'group': group.name, 'collection': col.name, 'permissions': col_perms} obj_list.append(obj) exp_list = get_objects_for_group(group, perms=exp_perms, klass=Experiment, any_perm=True) for exp in exp_list: exp_perms = get_perms(group, exp) obj_list.append({'group': group.name, 'collection': exp.collection.name, 'experiment': exp.name, 'permissions': exp_perms}) ch_list = get_objects_for_group(group, perms=channel_perms, klass=Channel, any_perm=True) for channel in ch_list: channel_perms = get_perms(group, channel) obj_list.append({'group': group.name, 'collection': channel.experiment.collection.name, 'experiment': channel.experiment.name, 'channel': channel.name, 'permissions': channel_perms}) data = {'permission-sets': obj_list} else: # no filtering list_member_groups = request.user.groups.all() for group in list_member_groups: col_list = get_objects_for_group(group, perms=col_perms, klass=Collection, any_perm=True) for col in col_list: col_perms = get_perms(group, col) obj = {'group': group.name, 'collection': col.name, 'permissions': col_perms} obj_list.append(obj) exp_list = get_objects_for_group(group, perms=exp_perms, klass=Experiment, any_perm=True) for exp in exp_list: exp_perms = get_perms(group, exp) obj_list.append({'group': group.name, 'collection': exp.collection.name, 'experiment': exp.name, 'permissions': col_perms}) ch_list = get_objects_for_group(group, perms=channel_perms, klass=Channel, any_perm=True) for channel in ch_list: channel_perms = get_perms(group, channel) obj_list.append({'group': group.name, 'collection': channel.experiment.collection.name, 'experiment': channel.experiment.name, 'channel': channel.name, 'permissions': col_perms}) data = {'permission-sets': obj_list} return Response(data, status=status.HTTP_200_OK) except Group.DoesNotExist: return BossGroupNotFoundError(group) except Permission.DoesNotExist: return BossHTTPError("Invalid permissions in post".format(request.data['permissions']), ErrorCodes.UNRECOGNIZED_PERMISSION) except BossError as err: return err.to_http() @transaction.atomic @check_role("resource-manager") def post(self, request): """ Add permissions to a resource Add new permissions for a existing group and resource object Args: request: Django rest framework request Returns: Http status code """ if 'permissions' not in request.data: return BossHTTPError("Permission are not included in the request", ErrorCodes.INVALID_URL) else: perm_list = dict(request.data)['permissions'] if 'group' not in request.data: return BossHTTPError("Group are not included in the request", ErrorCodes.INVALID_URL) if 'collection' not in request.data: return BossHTTPError("Invalid resource or missing resource name in request", ErrorCodes.INVALID_URL) group_name = request.data.get('group', None) collection = request.data.get('collection', None) experiment = request.data.get('experiment', None) channel = request.data.get('channel', None) try: # public group can only have read permission if group_name == PUBLIC_GRP and not (set(perm_list).issubset({'read', 'read_volumetric_data'})): return BossHTTPError("The public group can only have read permissions", ErrorCodes.INVALID_POST_ARGUMENT) # If the user is not a member or maintainer of the group, they cannot assign permissions if not check_is_member_or_maintainer(request.user, group_name): return BossHTTPError('The user {} is not a member or maintainer of the group {} '. format(request.user.username, group_name), ErrorCodes.MISSING_PERMISSION) resource_object = self.get_object(collection, experiment, channel) if resource_object is None: return BossHTTPError("Unable to validate the resource", ErrorCodes.UNABLE_TO_VALIDATE) resource = resource_object[0] if request.user.has_perm("assign_group", resource): BossPermissionManager.add_permissions_group(group_name, resource, perm_list) return Response(status=status.HTTP_201_CREATED) else: return BossPermissionError('assign group', collection) except Group.DoesNotExist: return BossGroupNotFoundError(group_name) except Permission.DoesNotExist: return BossHTTPError("Invalid permissions in post".format(request.data['permissions']), ErrorCodes.UNRECOGNIZED_PERMISSION) except BossError as err: return err.to_http() @transaction.atomic @check_role("resource-manager") def patch(self, request): """ Patch permissions for a resource Remove specific permissions for a existing group and resource object Args: request: Django rest framework request Returns: Http status code """ if 'permissions' not in request.data: return BossHTTPError("Permission are not included in the request", ErrorCodes. UNABLE_TO_VALIDATE) else: perm_list = dict(request.data)['permissions'] if 'group' not in request.data: return BossHTTPError("Group are not included in the request", ErrorCodes. UNABLE_TO_VALIDATE) if 'collection' not in request.data: return BossHTTPError("Invalid resource or missing resource name in request", ErrorCodes. UNABLE_TO_VALIDATE) group_name = request.data.get('group', None) collection = request.data.get('collection', None) experiment = request.data.get('experiment', None) channel = request.data.get('channel', None) try: # public group can only have read permission if group_name == PUBLIC_GRP and (len(perm_list) != 1 or perm_list[0] != 'read'): return BossHTTPError("The public group can only have read permissions", ErrorCodes.INVALID_POST_ARGUMENT) # If the user is not a member or maintainer of the group, they cannot patch permissions if not check_is_member_or_maintainer(request.user, group_name): return BossHTTPError('The user {} is not a member or maintainer of the group {} '. format(request.user.username, group_name), ErrorCodes.MISSING_PERMISSION) resource_object = self.get_object(collection, experiment, channel) if resource_object is None: return BossHTTPError("Unable to validate the resource", ErrorCodes.UNABLE_TO_VALIDATE) resource = resource_object[0] # remove all existing permission for the group if request.user.has_perm("remove_group", resource) and request.user.has_perm("assign_group", resource): BossPermissionManager.delete_all_permissions_group(group_name, resource) BossPermissionManager.add_permissions_group(group_name, resource, perm_list) return Response(status=status.HTTP_200_OK) else: return BossPermissionError('remove group', resource.name) except Group.DoesNotExist: return BossGroupNotFoundError(group_name) except Permission.DoesNotExist: return BossHTTPError("Invalid permissions in post".format(request.data['permissions']), ErrorCodes.UNRECOGNIZED_PERMISSION) except BossError as err: return err.to_http() @transaction.atomic @check_role("resource-manager") def delete(self, request): """ Delete permissions for a resource object Remove specific permissions for a existing group and resource object Args: request: Django rest framework request Returns: Http status code """ if 'group' not in request.query_params: return BossHTTPError("Group are not included in the request", ErrorCodes.INVALID_URL) if 'collection' not in request.query_params: return BossHTTPError("Invalid resource or missing resource name in request", ErrorCodes.INVALID_URL) group_name = request.query_params.get('group', None) collection = request.query_params.get('collection', None) experiment = request.query_params.get('experiment', None) channel = request.query_params.get('channel', None) try: if not check_is_member_or_maintainer(request.user, group_name): return BossHTTPError('The user {} is not a member or maintainer of the group {} '. format(request.user.username, group_name), ErrorCodes.MISSING_PERMISSION) resource_object = self.get_object(collection, experiment, channel) if resource_object is None: return BossHTTPError("Unable to validate the resource", ErrorCodes.UNABLE_TO_VALIDATE) if request.user.has_perm("remove_group", resource_object[0]): BossPermissionManager.delete_all_permissions_group(group_name, resource_object[0]) return Response(status=status.HTTP_204_NO_CONTENT) else: return BossPermissionError('remove group', resource_object[0].name) except Group.DoesNotExist: return BossGroupNotFoundError(group_name) except Permission.DoesNotExist: return BossHTTPError("Invalid permissions in post".format(request.data['permissions']), ErrorCodes.UNRECOGNIZED_PERMISSION) except Exception as e: return BossHTTPError("{}".format(e), ErrorCodes.UNHANDLED_EXCEPTION) except BossError as err: return err.to_http()