1 new commit in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/2c732150b88e/ Changeset: 2c732150b88e User: dannon Date: 2014-08-19 17:55:40 Summary: Merged in martenson/galaxy-central-marten (pull request #451) UI + API for library, folder, library dataset and dataset permissions Affected #: 33 files diff -r 6f92d5b8bd12b72dee5465be693344b9175344b7 -r 2c732150b88ec8a9638a1c14d9db6e7f0d925ba3 lib/galaxy/security/__init__.py --- a/lib/galaxy/security/__init__.py +++ b/lib/galaxy/security/__init__.py @@ -95,6 +95,9 @@ def set_all_library_permissions( self, trans, dataset, permissions ): raise "Unimplemented Method" + def set_library_item_permission( self, library_item, permission ): + raise "Unimplemented Method" + def library_is_public( self, library ): raise "Unimplemented Method" @@ -208,74 +211,115 @@ roles.add( role ) return self.sort_by_attr( [ role for role in roles ], 'name' ) - def get_valid_dataset_roles( self, trans, dataset, query, page, page_limit ): + def get_roles_for_action( self, item, action ): + """ + Return a list containing the roles associated with given action on given item + where item is one of Library, LibraryFolder, LibraryDatasetDatasetAssociation, + LibraryDataset, Dataset. + """ + roles = [] + for item_permission in item.actions: + permission_action = self.get_action( item_permission.action ) + if permission_action == action: + roles.append( item_permission.role ) + return roles + + def get_valid_roles( self, trans, item, query=None, page=None, page_limit=None, is_library_access=False ): """ This method retrieves the list of possible roles that user can select - in the dataset permissions form. Admins can select any role so the + in the item permissions form. Admins can select any role so the results are paginated in order to save the bandwidth and to speed things up. - Standard users can select their own private role, any fo their + Standard users can select their own private role, any of their sharing roles and any public role (not private and not sharing). """ roles = [] if query is not None: query = query.replace( '_', '/_' ).replace( '%', '/%' ).replace( '/', '//' ) search_query = query + '%' + log.debug('search_query: ' + str(search_query)) + # Limit the query only to get the page needed - limit = page * page_limit + if page is not None and page_limit is not None: + paginated = True + limit = page * page_limit + else: + paginated = False + + total_count = None - # Admins see it all - if trans.user_is_admin(): - # Add all roles that fit the query + if isinstance( item, self.model.Library ) and self.library_is_public( item ): + is_public_item = True + elif isinstance( item, self.model.Dataset ) and self.dataset_is_public( item ): + is_public_item = True + elif isinstance( item, self.model.LibraryFolder ): + is_public_item = True + else: + is_public_item = False + + # For public items and for library access admins can choose from all roles + if trans.user_is_admin() and ( is_public_item or is_library_access ): + # Add all non-deleted roles that fit the query db_query = trans.sa_session.query( trans.app.model.Role ).filter( self.model.Role.table.c.deleted == False ) if query is not None: db_query = db_query.filter( self.model.Role.table.c.name.like( search_query, escape='/' ) ) - for role in ( db_query.order_by( self.model.Role.table.c.name ).limit( limit ) ): + total_count = db_query.count() + if paginated: + # Takes the least number of results from beginning that includes the requested page + roles = db_query.order_by( self.model.Role.table.c.name ).limit( limit ).all() + page_start = ( page * page_limit ) - page_limit + page_end = page_start + page_limit + if total_count < page_start: + # Return empty list if there are less results than the requested position + roles = [] + else: + roles = roles[ page_start:page_end ] + else: + roles = db_query.order_by( self.model.Role.table.c.name ) + + # Non-admin and public item + elif is_public_item: + # Add the current user's private role + roles.append( self.get_private_user_role( trans.user ) ) + # Add the current user's sharing roles + for role in self.get_sharing_roles( trans.user ): roles.append( role ) - # Take last page of the selection - roles = roles[ ( -page_limit ): ] - - # Non-admins see the list of relevant roles + # Add all remaining non-private, non-sharing roles + for role in trans.sa_session.query( trans.app.model.Role ) \ + .filter( and_( self.model.Role.table.c.deleted == False, + self.model.Role.table.c.type != self.model.Role.types.PRIVATE, + self.model.Role.table.c.type != self.model.Role.types.SHARING ) ) \ + .order_by( self.model.Role.table.c.name ): + roles.append( role ) + # User is not admin and item is not public + # User will see all the roles derived from the access roles on the item else: - if self.dataset_is_public( dataset ): - # Add the current user's private role - roles.append( self.get_private_user_role( trans.user ) ) - # Add the current user's sharing roles - for role in self.get_sharing_roles( trans.user ): + # If item has roles associated with the access permission, we need to start with them. + access_roles = item.get_access_roles( trans ) + for role in access_roles: + if trans.user_is_admin() or self.ok_to_display( trans.user, role ): roles.append( role ) - # Add all remaining non-private, non-sharing roles - for role in trans.sa_session.query( trans.app.model.Role ) \ - .filter( and_( self.model.Role.table.c.deleted == False, - self.model.Role.table.c.type != self.model.Role.types.PRIVATE, - self.model.Role.table.c.type != self.model.Role.types.SHARING ) ) \ - .order_by( self.model.Role.table.c.name ): - roles.append( role ) - - else: - # If item has roles associated with the access permission, we need to start with them. - access_roles = dataset.get_access_roles( trans ) - for role in access_roles: - if trans.user_is_admin() or self.ok_to_display( trans.user, role ): - roles.append( role ) - # Each role potentially has users. We need to find all roles that each of those users have. - for ura in role.users: - user = ura.user - for ura2 in user.roles: - if trans.user_is_admin() or self.ok_to_display( trans.user, ura2.role ): - roles.append( ura2.role ) - # Each role also potentially has groups which, in turn, have members ( users ). We need to - # find all roles that each group's members have. - for gra in role.groups: - group = gra.group - for uga in group.users: - user = uga.user - for ura in user.roles: - if trans.user_is_admin() or self.ok_to_display( trans.user, ura.role ): - roles.append( ura.role ) + # Each role potentially has users. We need to find all roles that each of those users have. + for ura in role.users: + user = ura.user + for ura2 in user.roles: + if trans.user_is_admin() or self.ok_to_display( trans.user, ura2.role ): + roles.append( ura2.role ) + # Each role also potentially has groups which, in turn, have members ( users ). We need to + # find all roles that each group's members have. + for gra in role.groups: + group = gra.group + for uga in group.users: + user = uga.user + for ura in user.roles: + if trans.user_is_admin() or self.ok_to_display( trans.user, ura.role ): + roles.append( ura.role ) # Omit duplicated roles by converting to set return_roles = set( roles ) - return self.sort_by_attr( [ role for role in return_roles ], 'name' ) + if total_count is None: + total_count = len( return_roles ) + return self.sort_by_attr( [ role for role in return_roles ], 'name' ), total_count def get_legitimate_roles( self, trans, item, cntrller ): """ @@ -828,8 +872,8 @@ def set_all_dataset_permissions( self, dataset, permissions={} ): """ - Set new permissions on a dataset, eliminating all current permissions - permissions looks like: { Action : [ Role, Role ] } + Set new full permissions on a dataset, eliminating all current permissions. + Permission looks like: { Action : [ Role, Role ] } """ # Make sure that DATASET_MANAGE_PERMISSIONS is associated with at least 1 role has_dataset_manage_permissions = False @@ -861,8 +905,8 @@ def set_dataset_permission( self, dataset, permission={} ): """ - Set a specific permission on a dataset, leaving all other current permissions on the dataset alone - permissions looks like: { Action : [ Role, Role ] } + Set a specific permission on a dataset, leaving all other current permissions on the dataset alone. + Permission looks like: { Action.action : [ Role, Role ] } """ flush_needed = False for action, roles in permission.items(): @@ -987,6 +1031,32 @@ if flush_needed: self.sa_session.flush() + def set_library_item_permission( self, library_item, permission={} ): + """ + Set a specific permission on a library item, leaving all other current permissions on the item alone. + Permission looks like: { Action.action : [ Role, Role ] } + """ + flush_needed = False + for action, roles in permission.items(): + if isinstance( action, Action ): + action = action.action + # Delete the current specific permission on the library item if one exists + for item_permission in library_item.actions: + if item_permission.action == action: + self.sa_session.delete( item_permission ) + flush_needed = True + # Add the new specific permission on the library item + if isinstance( library_item, self.model.LibraryDataset ): + for item_permission in [ self.model.LibraryDatasetPermissions( action, library_item, role ) for role in roles ]: + self.sa_session.add( item_permission ) + flush_needed = True + elif isinstance ( library_item, self.model.LibraryPermissions): + for item_permission in [ self.model.LibraryPermissions( action, library_item, role ) for role in roles ]: + self.sa_session.add( item_permission ) + flush_needed = True + if flush_needed: + self.sa_session.flush() + def library_is_public( self, library, contents=False ): if contents: # Check all contained folders and datasets to find any that are not public diff -r 6f92d5b8bd12b72dee5465be693344b9175344b7 -r 2c732150b88ec8a9638a1c14d9db6e7f0d925ba3 lib/galaxy/webapps/galaxy/api/folder_contents.py --- a/lib/galaxy/webapps/galaxy/api/folder_contents.py +++ b/lib/galaxy/webapps/galaxy/api/folder_contents.py @@ -44,6 +44,7 @@ :raises: MalformedId, InconsistentDatabase, ObjectNotFound, InternalServerError """ + is_admin = trans.user_is_admin() deleted = kwd.get( 'include_deleted', 'missing' ) try: deleted = util.asbool( deleted ) @@ -105,7 +106,7 @@ folder_contents = [] update_time = '' create_time = '' - # Go through every accessible item (folders, datasets) in the folder and include its meta-data. + # Go through every accessible item (folders, datasets) in the folder and include its metadata. for content_item in self._load_folder_contents( trans, folder, deleted ): return_item = {} encoded_id = trans.security.encode_id( content_item.id ) @@ -114,36 +115,27 @@ if content_item.api_type == 'folder': encoded_id = 'F' + encoded_id - # Check whether user can modify current folder - can_modify = False - if trans.user_is_admin(): - can_modify = True - elif trans.user: - can_modify = trans.app.security_agent.can_modify_library_item( current_user_roles, folder ) - return_item.update( dict( can_modify=can_modify ) ) + can_modify = is_admin or ( trans.user and trans.app.security_agent.can_modify_library_item( current_user_roles, folder ) ) + can_manage = is_admin or ( trans.user and trans.app.security_agent.can_manage_library_item( current_user_roles, folder ) ) + return_item.update( dict( can_modify=can_modify, can_manage=can_manage ) ) if content_item.api_type == 'file': - # Is the dataset public or private? - # When both are False the dataset is 'restricted' - is_private = False - is_unrestricted = False - if trans.app.security_agent.dataset_is_public( content_item.library_dataset_dataset_association.dataset ): - is_unrestricted = True + # Is the dataset public or private? + # When both are False the dataset is 'restricted' + # Access rights are checked on the dataset level, not on the ld or ldda level to maintain consistency + is_unrestricted = trans.app.security_agent.dataset_is_public( content_item.library_dataset_dataset_association.dataset ) + if trans.user and trans.app.security_agent.dataset_is_private_to_user( trans, content_item ): + is_private = True else: - is_unrestricted = False - if trans.user: - is_private = trans.app.security_agent.dataset_is_private_to_user( trans, content_item ) + is_private = False # Can user manage the permissions on the dataset? - can_manage = False - if trans.user_is_admin(): - can_manage = True - elif trans.user: - can_manage = trans.app.security_agent.can_manage_dataset( current_user_roles, content_item.library_dataset_dataset_association.dataset ) + can_manage = is_admin or (trans.user and trans.app.security_agent.can_manage_dataset( current_user_roles, content_item.library_dataset_dataset_association.dataset ) ) nice_size = util.nice_size( int( content_item.library_dataset_dataset_association.get_size() ) ) library_dataset_dict = content_item.to_dict() + return_item.update( dict( data_type=library_dataset_dict[ 'data_type' ], date_uploaded=library_dataset_dict[ 'date_uploaded' ], is_unrestricted=is_unrestricted, @@ -151,7 +143,7 @@ can_manage=can_manage, file_size=nice_size ) ) - # For every item include the default meta-data + # For every item include the default metadata return_item.update( dict( id=encoded_id, type=content_item.api_type, name=content_item.name, @@ -165,14 +157,17 @@ full_path = self.build_path( trans, folder )[ ::-1 ] # Check whether user can add items to the current folder - can_add_library_item = trans.user_is_admin() or trans.app.security_agent.can_add_library_item( current_user_roles, folder ) + can_add_library_item = is_admin or trans.app.security_agent.can_add_library_item( current_user_roles, folder ) - # Check whether user can modify current folder - can_modify_folder = trans.app.security_agent.can_modify_library_item( current_user_roles, folder ) - + # Check whether user can modify the current folder + can_modify_folder = is_admin or trans.app.security_agent.can_modify_library_item( current_user_roles, folder ) + parent_library_id = None + if folder.parent_library is not None: + parent_library_id = trans.security.encode_id( folder.parent_library.id ) metadata = dict( full_path=full_path, can_add_library_item=can_add_library_item, - can_modify_folder=can_modify_folder ) + can_modify_folder=can_modify_folder, + parent_library_id=parent_library_id ) folder_container = dict( metadata=metadata, folder_contents=folder_contents ) return folder_container diff -r 6f92d5b8bd12b72dee5465be693344b9175344b7 -r 2c732150b88ec8a9638a1c14d9db6e7f0d925ba3 lib/galaxy/webapps/galaxy/api/folders.py --- a/lib/galaxy/webapps/galaxy/api/folders.py +++ b/lib/galaxy/webapps/galaxy/api/folders.py @@ -8,6 +8,7 @@ # import socket # import traceback # import string +from galaxy import util from galaxy import web from galaxy import exceptions from galaxy.web import _future_expose_api as expose_api @@ -86,27 +87,15 @@ elif name is None: raise exceptions.RequestParameterMissingException( "Missing required parameter 'name'." ) - # encoded_parent_folder_id should be prefixed by 'F' encoded_parent_folder_id = self.__cut_the_prefix( encoded_parent_folder_id ) - try: - decoded_parent_folder_id = trans.security.decode_id( encoded_parent_folder_id ) - except ValueError: - raise exceptions.MalformedId( "Malformed folder id ( %s ) specified, unable to decode" % ( str( id ) ) ) - - try: - parent_folder = trans.sa_session.query( trans.app.model.LibraryFolder ).filter( trans.app.model.LibraryFolder.table.c.id == decoded_parent_folder_id ).one() - except MultipleResultsFound: - raise exceptions.InconsistentDatabase( 'Multiple folders found with the same id.' ) - except NoResultFound: - raise exceptions.RequestParameterInvalidException( 'No folder found with the id provided.' ) - except Exception, e: - raise exceptions.InternalServerError( 'Error loading from the database.' + str( e ) ) - + decoded_parent_folder_id = self.__decode_folder_id( trans, encoded_parent_folder_id ) + parent_folder = self.__load_folder( trans, decoded_parent_folder_id ) + library = parent_folder.parent_library if library.deleted: raise exceptions.ObjectAttributeInvalidException( 'You cannot create folder within a deleted library. Undelete it first.' ) - # TODO: refactor the functionality for use here instead of calling another controller + # TODO: refactor the functionality for use in manager instead of calling another controller params = dict( [ ( "name", name ), ( "description", description ) ] ) status, output = trans.webapp.controllers['library_common'].create_folder( trans, 'api', encoded_parent_folder_id, '', **params ) @@ -126,6 +115,160 @@ else: raise exceptions.InternalServerError( 'Error while creating a folder.' + str( e ) ) + @expose_api + def get_permissions( self, trans, encoded_folder_id, **kwd ): + """ + * GET /api/folders/{id}/permissions + + Load all permissions for the given folder id and return it. + + :param encoded_folder_id: the encoded id of the folder + :type encoded_folder_id: an encoded id string + + :param scope: either 'current' or 'available' + :type scope: string + + :returns: dictionary with all applicable permissions' values + :rtype: dictionary + + :raises: ObjectNotFound, InsufficientPermissionsException + """ + current_user_roles = trans.get_current_user_roles() + is_admin = trans.user_is_admin() + + encoded_folder_id = self.__cut_the_prefix( encoded_folder_id ) + decoded_folder_id = self.__decode_folder_id( trans, encoded_folder_id ) + folder = self.__load_folder( trans, decoded_folder_id ) + + if not ( is_admin or trans.app.security_agent.can_manage_library_item( current_user_roles, library ) ): + raise exceptions.InsufficientPermissionsException( 'You do not have proper permission to access permissions of this folder.' ) + + scope = kwd.get( 'scope', None ) + + if scope == 'current' or scope is None: + return self._get_current_roles( trans, folder ) + + # Return roles that are available to select. + elif scope == 'available': + page = kwd.get( 'page', None ) + if page is not None: + page = int( page ) + else: + page = 1 + + page_limit = kwd.get( 'page_limit', None ) + if page_limit is not None: + page_limit = int( page_limit ) + else: + page_limit = 10 + + query = kwd.get( 'q', None ) + + roles, total_roles = trans.app.security_agent.get_valid_roles( trans, folder, query, page, page_limit ) + + return_roles = [] + for role in roles: + return_roles.append( dict( id=role.name, name=role.name, type=role.type ) ) + return dict( roles=return_roles, page=page, page_limit=page_limit, total=total_roles ) + else: + raise exceptions.RequestParameterInvalidException( "The value of 'scope' parameter is invalid. Alllowed values: current, available" ) + + @expose_api + def set_permissions( self, trans, encoded_folder_id, **kwd ): + """ + def set_permissions( self, trans, encoded_folder_id, **kwd ): + *POST /api/folders/{encoded_folder_id}/permissions + + :param encoded_folder_id: the encoded id of the folder to set the permissions of + :type encoded_folder_id: an encoded id string + + :param action: (required) describes what action should be performed + available actions: set_permissions + :type action: string + + :param add_ids[]: list of Role.name defining roles that should have add item permission on the folder + :type add_ids[]: string or list + :param manage_ids[]: list of Role.name defining roles that should have manage permission on the folder + :type manage_ids[]: string or list + :param modify_ids[]: list of Role.name defining roles that should have modify permission on the folder + :type modify_ids[]: string or list + + :rtype: dictionary + :returns: dict of current roles for all available permission types + + :raises: RequestParameterInvalidException, ObjectNotFound, InsufficientPermissionsException, InternalServerError + RequestParameterMissingException + """ + is_admin = trans.user_is_admin() + current_user_roles = trans.get_current_user_roles() + + decoded_folder_id = self.__decode_folder_id( trans, self.__cut_the_prefix( encoded_folder_id ) ) + folder = self.__load_folder( trans, decoded_folder_id ) + if not ( is_admin or trans.app.security_agent.can_manage_library_item( current_user_roles, folder ) ): + raise exceptions.InsufficientPermissionsException( 'You do not have proper permission to modify permissions of this folder.' ) + + new_add_roles_ids = util.listify( kwd.get( 'add_ids[]', None ) ) + new_manage_roles_ids = util.listify( kwd.get( 'manage_ids[]', None ) ) + new_modify_roles_ids = util.listify( kwd.get( 'modify_ids[]', None ) ) + + action = kwd.get( 'action', None ) + if action is None: + raise exceptions.RequestParameterMissingException( 'The mandatory parameter "action" is missing.' ) + elif action == 'set_permissions': + + # ADD TO LIBRARY ROLES + valid_add_roles = [] + invalid_add_roles_names = [] + for role_id in new_add_roles_ids: + role = self._load_role( trans, role_id ) + # Check whether role is in the set of allowed roles + valid_roles, total_roles = trans.app.security_agent.get_valid_roles( trans, folder ) + if role in valid_roles: + valid_add_roles.append( role ) + else: + invalid_add_roles_names.append( role_id ) + if len( invalid_add_roles_names ) > 0: + log.warning( "The following roles could not be added to the add library item permission: " + str( invalid_add_roles_names ) ) + + # MANAGE FOLDER ROLES + valid_manage_roles = [] + invalid_manage_roles_names = [] + for role_id in new_manage_roles_ids: + role = self._load_role( trans, role_id ) + # Check whether role is in the set of allowed roles + valid_roles, total_roles = trans.app.security_agent.get_valid_roles( trans, folder ) + if role in valid_roles: + valid_manage_roles.append( role ) + else: + invalid_manage_roles_names.append( role_id ) + if len( invalid_manage_roles_names ) > 0: + log.warning( "The following roles could not be added to the manage folder permission: " + str( invalid_manage_roles_names ) ) + + # MODIFY FOLDER ROLES + valid_modify_roles = [] + invalid_modify_roles_names = [] + for role_id in new_modify_roles_ids: + role = self._load_role( trans, role_id ) + # Check whether role is in the set of allowed roles + valid_roles, total_roles = trans.app.security_agent.get_valid_roles( trans, folder ) + if role in valid_roles: + valid_modify_roles.append( role ) + else: + invalid_modify_roles_names.append( role_id ) + if len( invalid_modify_roles_names ) > 0: + log.warning( "The following roles could not be added to the modify folder permission: " + str( invalid_modify_roles_names ) ) + + permissions = { trans.app.security_agent.permitted_actions.LIBRARY_ADD : valid_add_roles } + permissions.update( { trans.app.security_agent.permitted_actions.LIBRARY_MANAGE : valid_manage_roles } ) + permissions.update( { trans.app.security_agent.permitted_actions.LIBRARY_MODIFY : valid_modify_roles } ) + + trans.app.security_agent.set_all_library_permissions( trans, folder, permissions ) + else: + raise exceptions.RequestParameterInvalidException( 'The mandatory parameter "action" has an invalid value.' + 'Allowed values are: "set_permissions"' ) + + return self._get_current_roles( trans, folder ) + @web.expose_api def update( self, trans, id, library_id, payload, **kwd ): """ @@ -134,9 +277,81 @@ """ raise exceptions.NotImplemented( 'Updating folder through this endpoint is not implemented yet.' ) - def __cut_the_prefix(self, encoded_id): - + def __cut_the_prefix( self, encoded_id ): + """ + Remove the prefix from the encoded folder id. + """ if ( ( len( encoded_id ) % 16 == 1 ) and encoded_id.startswith( 'F' ) ): - return encoded_id[ 1: ] + cut_id = encoded_id[ 1: ] else: raise exceptions.MalformedId( 'Malformed folder id ( %s ) specified, unable to decode.' % str( encoded_id ) ) + return cut_id + + def __decode_folder_id( self, trans, encoded_id ): + """ + Decode the folder id given that it has already lost the prefixed 'F'. + """ + try: + decoded_id = trans.security.decode_id( encoded_id ) + except ValueError: + raise exceptions.MalformedId( "Malformed folder id ( %s ) specified, unable to decode" % ( str( encoded_id ) ) ) + return decoded_id + + def __load_folder( self, trans, folder_id ): + """ + Load the folder from the DB. + """ + try: + folder = trans.sa_session.query( trans.app.model.LibraryFolder ).filter( trans.app.model.LibraryFolder.table.c.id == folder_id ).one() + except MultipleResultsFound: + raise exceptions.InconsistentDatabase( 'Multiple folders found with the same id.' ) + except NoResultFound: + raise exceptions.RequestParameterInvalidException( 'No folder found with the id provided.' ) + except Exception, e: + raise exceptions.InternalServerError( 'Error loading from the database.' + str( e ) ) + return folder + + + def _get_current_roles( self, trans, folder ): + """ + Find all roles currently connected to relevant permissions + on the folder. + + :param folder: the model object + :type folder: LibraryFolder + + :rtype: dictionary + :returns: dict of current roles for all available permission types + """ + # Omit duplicated roles by converting to set + modify_roles = set( trans.app.security_agent.get_roles_for_action( folder, trans.app.security_agent.permitted_actions.LIBRARY_MODIFY ) ) + manage_roles = set( trans.app.security_agent.get_roles_for_action( folder, trans.app.security_agent.permitted_actions.LIBRARY_MANAGE ) ) + add_roles = set( trans.app.security_agent.get_roles_for_action( folder, trans.app.security_agent.permitted_actions.LIBRARY_ADD ) ) + + modify_folder_role_list = [ modify_role.name for modify_role in modify_roles ] + manage_folder_role_list = [ manage_role.name for manage_role in manage_roles ] + add_library_item_role_list = [ add_role.name for add_role in add_roles ] + + return dict( modify_folder_role_list=modify_folder_role_list, manage_folder_role_list=manage_folder_role_list, add_library_item_role_list=add_library_item_role_list ) + + def _load_role( self, trans, role_name ): + """ + Method loads the role from the DB based on the given role name. + + :param role_name: name of the role to load from the DB + :type role_name: string + + :rtype: Role + :returns: the loaded Role object + + :raises: InconsistentDatabase, RequestParameterInvalidException, InternalServerError + """ + try: + role = trans.sa_session.query( trans.app.model.Role ).filter( trans.model.Role.table.c.name == role_name ).one() + except MultipleResultsFound: + raise exceptions.InconsistentDatabase( 'Multiple roles found with the same name. Name: ' + str( role_name ) ) + except NoResultFound: + raise exceptions.RequestParameterInvalidException( 'No role found with the name provided. Name: ' + str( role_name ) ) + except Exception, e: + raise exceptions.InternalServerError( 'Error loading from the database.' + str(e)) + return role diff -r 6f92d5b8bd12b72dee5465be693344b9175344b7 -r 2c732150b88ec8a9638a1c14d9db6e7f0d925ba3 lib/galaxy/webapps/galaxy/api/lda_datasets.py --- a/lib/galaxy/webapps/galaxy/api/lda_datasets.py +++ b/lib/galaxy/webapps/galaxy/api/lda_datasets.py @@ -22,6 +22,8 @@ from galaxy.security import Action from galaxy.util.streamball import StreamBall from galaxy.web.base.controller import BaseAPIController, UsesVisualizationMixin +from sqlalchemy.orm.exc import MultipleResultsFound +from sqlalchemy.orm.exc import NoResultFound import logging log = logging.getLogger( __name__ ) @@ -41,101 +43,332 @@ :rtype: dictionary :returns: detailed dataset information from base controller + .. seealso:: :attr:`galaxy.web.base.controller.UsesLibraryMixinItems.get_library_dataset` """ try: - dataset = self.get_library_dataset( trans, id=id, check_ownership=False, check_accessible=True ) + library_dataset = self.get_library_dataset( trans, id=id, check_ownership=False, check_accessible=True ) except Exception: - raise exceptions.ObjectNotFound( 'Requested dataset was not found.' ) + raise exceptions.ObjectNotFound( 'Requested library_dataset was not found.' ) + + current_user_roles = trans.get_current_user_roles() # Build the full path for breadcrumb purposes. - full_path = self._build_path( trans, dataset.folder ) - dataset_item = ( trans.security.encode_id( dataset.id ), dataset.name ) + full_path = self._build_path( trans, library_dataset.folder ) + dataset_item = ( trans.security.encode_id( library_dataset.id ), library_dataset.name ) full_path.insert(0, dataset_item) full_path = full_path[ ::-1 ] - nice_size = util.nice_size( int( dataset.library_dataset_dataset_association.get_size() ) ) + # Find expired versions of the library dataset + expired_ldda_versions = [] + for expired_ldda in library_dataset.expired_datasets: + expired_ldda_versions.append( ( trans.security.encode_id( expired_ldda.id ), expired_ldda.name ) ) - date_uploaded = dataset.library_dataset_dataset_association.create_time.strftime( "%Y-%m-%d %I:%M %p" ) - - rval = trans.security.encode_all_ids( dataset.to_dict() ) - rval[ 'deleted' ] = dataset.deleted + rval = trans.security.encode_all_ids( library_dataset.to_dict() ) + if len(expired_ldda_versions) > 0: + rval[ 'has_versions' ] = True + rval[ 'expired_versions' ] = expired_ldda_versions + rval[ 'deleted' ] = library_dataset.deleted rval[ 'folder_id' ] = 'F' + rval[ 'folder_id' ] rval[ 'full_path' ] = full_path - rval[ 'file_size' ] = nice_size - rval[ 'date_uploaded' ] = date_uploaded + rval[ 'file_size' ] = util.nice_size( int( library_dataset.library_dataset_dataset_association.get_size() ) ) + rval[ 'date_uploaded' ] = library_dataset.library_dataset_dataset_association.create_time.strftime( "%Y-%m-%d %I:%M %p" ) + rval[ 'can_user_modify' ] = trans.app.security_agent.can_modify_library_item( current_user_roles, library_dataset) or trans.user_is_admin() + rval[ 'is_unrestricted' ] = trans.app.security_agent.dataset_is_public( library_dataset.library_dataset_dataset_association.dataset ) + + # Manage dataset permission is always attached to the dataset itself, not the the ld or ldda to maintain consistency + rval[ 'can_user_manage' ] = trans.app.security_agent.can_manage_dataset( current_user_roles, library_dataset.library_dataset_dataset_association.dataset) or trans.user_is_admin() + return rval + + @expose_api_anonymous + def show_version( self, trans, encoded_dataset_id, encoded_ldda_id, **kwd ): + """ + show_version( self, trans, encoded_dataset_id, encoded_ldda_id, **kwd ): + * GET /api/libraries/datasets/:encoded_dataset_id/versions/:encoded_ldda_id + Displays information about specific version of the library_dataset (i.e. ldda). + + :param encoded_dataset_id: the encoded id of the dataset to query + :type encoded_dataset_id: an encoded id string + + :param encoded_ldda_id: the encoded id of the ldda to query + :type encoded_ldda_id: an encoded id string + + :rtype: dictionary + :returns: dict of ldda's details + """ + try: + library_dataset = self.get_library_dataset( trans, id=encoded_dataset_id, check_ownership=False, check_accessible=True ) + except Exception: + raise exceptions.ObjectNotFound( 'Requested library_dataset was not found.' ) + + try: + ldda = self.get_library_dataset_dataset_association( trans, id=encoded_ldda_id, check_ownership=False, check_accessible=False ) + except Exception, e: + raise exceptions.ObjectNotFound( 'Requested version of library dataset was not found.' + str(e) ) + + if ldda not in library_dataset.expired_datasets: + raise exceptions.ObjectNotFound( 'Given library dataset does not have the requested version.' ) + + rval = trans.security.encode_all_ids( ldda.to_dict() ) return rval @expose_api def show_roles( self, trans, encoded_dataset_id, **kwd ): """ show_roles( self, trans, id, **kwd ): - GET /api/libraries/datasets/{encoded_dataset_id}/permissions: - Displays information about current and available roles + * GET /api/libraries/datasets/{encoded_dataset_id}/permissions + Displays information about current or available roles for a given dataset permission. + + :param encoded_dataset_id: the encoded id of the dataset to query + :type encoded_dataset_id: an encoded id string + + :param scope: either 'current' or 'available' + :type scope: string + + :rtype: dictionary + :returns: either dict of current roles for all permission types or + dict of available roles to choose from (is the same for any permission type) """ + current_user_roles = trans.get_current_user_roles() - - page = kwd.get( 'page', None ) - if page is not None: - page = int( page ) - - page_limit = kwd.get( 'page_limit', None ) - if page_limit is not None: - page_limit = int( page_limit ) - - query = kwd.get( 'q', None ) - - if page is None: - page = 1 - - if page_limit is None: - page_limit = 10 - - try: - library_dataset = self.get_library_dataset( trans, id=encoded_dataset_id, check_ownership=False, check_accessible=False ) - except Exception, e: - raise exceptions.ObjectNotFound( 'Requested dataset was not found.' + str(e) ) - library = library_dataset.folder.parent_library - dataset = library_dataset.library_dataset_dataset_association.dataset - - can_manage = trans.app.security_agent.can_manage_dataset( current_user_roles, dataset ) or trans.user_is_admin() - if not can_manage: - raise exceptions.InsufficientPermissionsException( 'You do not have proper permissions to access permissions.' ) - - roles = trans.app.security_agent.get_valid_dataset_roles( trans, dataset, query, page, page_limit ) - - total_roles = len( roles ) - return_roles = [] - for role in roles: - return_roles.append( dict( id=role.name, name=role.name, type=role.type ) ) - - return dict( roles=return_roles, page=page, page_limit=page_limit, total=total_roles ) - - @expose_api - def get_roles( self, trans, encoded_dataset_id, **kwd ): try: library_dataset = self.get_library_dataset( trans, id=encoded_dataset_id, check_ownership=False, check_accessible=False ) except Exception, e: raise exceptions.ObjectNotFound( 'Requested dataset was not found.' + str(e) ) dataset = library_dataset.library_dataset_dataset_association.dataset - roles = dataset.get_access_roles( trans ) + # User has to have manage permissions permission in order to see the roles. + can_manage = trans.app.security_agent.can_manage_dataset( current_user_roles, dataset ) or trans.user_is_admin() + if not can_manage: + raise exceptions.InsufficientPermissionsException( 'You do not have proper permission to access permissions.' ) - # roles = dataset.get_manage_permissions_roles( trans ) + scope = kwd.get( 'scope', None ) - # roles = trans.app.security_agent.get_current_dataset_roles( trans, dataset, trans.app.security_agent.permitted_actions.DATASET_ACCESS ) - # Omit duplicated roles by converting to set - roles = set( roles ) - return [ role.name for role in roles ] + if scope == 'current' or scope is None: + return self._get_current_roles( trans, library_dataset ) + + # Return roles that are available to select. + elif scope == 'available': + page = kwd.get( 'page', None ) + if page is not None: + page = int( page ) + else: + page = 1 + + page_limit = kwd.get( 'page_limit', None ) + if page_limit is not None: + page_limit = int( page_limit ) + else: + page_limit = 10 + + query = kwd.get( 'q', None ) + + roles, total_roles = trans.app.security_agent.get_valid_roles( trans, dataset, query, page, page_limit ) + + return_roles = [] + for role in roles: + return_roles.append( dict( id=role.name, name=role.name, type=role.type ) ) + return dict( roles=return_roles, page=page, page_limit=page_limit, total=total_roles ) + else: + raise exceptions.RequestParameterInvalidException( "The value of 'scope' parameter is invalid. Alllowed values: current, available" ) + + def _get_current_roles( self, trans, library_dataset): + """ + Find all roles currently connected to relevant permissions + on the library dataset and the underlying dataset. + + :param library_dataset: the model object + :type library_dataset: LibraryDataset + + :rtype: dictionary + :returns: dict of current roles for all available permission types + """ + dataset = library_dataset.library_dataset_dataset_association.dataset + + # Omit duplicated roles by converting to set + access_roles = set( dataset.get_access_roles( trans ) ) + modify_roles = set( trans.app.security_agent.get_roles_for_action( library_dataset, trans.app.security_agent.permitted_actions.LIBRARY_MODIFY ) ) + manage_roles = set( dataset.get_manage_permissions_roles( trans ) ) + + access_dataset_role_list = [ access_role.name for access_role in access_roles ] + manage_dataset_role_list = [ manage_role.name for manage_role in manage_roles ] + modify_item_role_list = [ modify_role.name for modify_role in modify_roles ] + + return dict( access_dataset_roles=access_dataset_role_list, modify_item_roles=modify_item_role_list, manage_dataset_roles=manage_dataset_role_list ) + + @expose_api + def update_permissions( self, trans, encoded_dataset_id, **kwd ): + """ + def update( self, trans, encoded_dataset_id, **kwd ): + *POST /api/libraries/datasets/{encoded_dataset_id}/permissions + + :param encoded_dataset_id: the encoded id of the dataset to update permissions of + :type encoded_dataset_id: an encoded id string + + :param action: (required) describes what action should be performed + available actions: make_private, remove_restrictions, set_permissions + :type action: string + + :param access_ids[]: list of Role.name defining roles that should have access permission on the dataset + :type access_ids[]: string or list + :param manage_ids[]: list of Role.name defining roles that should have manage permission on the dataset + :type manage_ids[]: string or list + :param modify_ids[]: list of Role.name defining roles that should have modify permission on the library dataset item + :type modify_ids[]: string or list + + :rtype: dictionary + :returns: dict of current roles for all available permission types + + :raises: RequestParameterInvalidException, ObjectNotFound, InsufficientPermissionsException, InternalServerError + RequestParameterMissingException + """ + try: + library_dataset = self.get_library_dataset( trans, id=encoded_dataset_id, check_ownership=False, check_accessible=False ) + except Exception, e: + raise exceptions.ObjectNotFound( 'Requested dataset was not found.' + str(e) ) + + dataset = library_dataset.library_dataset_dataset_association.dataset + + current_user_roles = trans.get_current_user_roles() + can_manage = trans.app.security_agent.can_manage_dataset( current_user_roles, dataset ) or trans.user_is_admin() + if not can_manage: + raise exceptions.InsufficientPermissionsException( 'You do not have proper permissions to manage permissions on this dataset.' ) + + new_access_roles_ids = kwd.get( 'access_ids[]', None ) + new_manage_roles_ids = kwd.get( 'manage_ids[]', None ) + new_modify_roles_ids = kwd.get( 'modify_ids[]', None ) + + action = kwd.get( 'action', None ) + if action is None: + raise exceptions.RequestParameterMissingException( 'The mandatory parameter "action" is missing.' ) + elif action == 'remove_restrictions': + trans.app.security_agent.make_dataset_public( dataset ) + if not trans.app.security_agent.dataset_is_public( dataset ): + raise exceptions.InternalServerError( 'An error occured while making dataset public.' ) + elif action == 'make_private': + trans.app.security_agent.make_dataset_public( dataset ) + private_role = trans.app.security_agent.get_private_user_role( trans.user ) + dp = trans.app.model.DatasetPermissions( trans.app.security_agent.permitted_actions.DATASET_ACCESS.action, dataset, private_role ) + trans.sa_session.add( dp ) + trans.sa_session.flush() + if not trans.app.security_agent.dataset_is_private_to_user( trans, library_dataset ): + raise exceptions.InternalServerError( 'An error occured while making dataset private.' ) + elif action == 'set_permissions': + + # ACCESS DATASET ROLES + valid_access_roles = [] + invalid_access_roles_names = [] + if new_access_roles_ids is None: + trans.app.security_agent.make_dataset_public( dataset ) + else: + # Check whether we receive only one role, then it is not a list so we make it into one. + if isinstance(new_access_roles_ids, basestring): + new_access_roles_ids = [ new_access_roles_ids ] + for role_id in new_access_roles_ids: + role = self._load_role( trans, role_id ) + + # Check whether role is in the set of allowed roles + valid_roles, total_roles = trans.app.security_agent.get_valid_roles( trans, dataset ) + if role in valid_roles: + valid_access_roles.append( role ) + else: + invalid_access_roles_names.append( role_id ) + if len( invalid_access_roles_names ) > 0: + log.warning( "The following roles could not be added to the dataset access permission: " + str( invalid_access_roles_names ) ) + + access_permission = dict( access=valid_access_roles ) + trans.app.security_agent.set_dataset_permission( dataset, access_permission ) + + # MANAGE DATASET ROLES + valid_manage_roles = [] + invalid_manage_roles_names = [] + new_manage_roles_ids = util.listify( new_manage_roles_ids ) + + # Load all access roles to check + active_access_roles = dataset.get_access_roles( trans ) + + for role_id in new_manage_roles_ids: + role = self._load_role( trans, role_id ) + + # Check whether role is in the set of access roles + if role in active_access_roles: + valid_manage_roles.append( role ) + else: + invalid_manage_roles_names.append( role_id ) + + if len( invalid_manage_roles_names ) > 0: + log.warning( "The following roles could not be added to the dataset manage permission: " + str( invalid_manage_roles_names ) ) + + manage_permission = { trans.app.security_agent.permitted_actions.DATASET_MANAGE_PERMISSIONS : valid_manage_roles } + trans.app.security_agent.set_dataset_permission( dataset, manage_permission ) + + # MODIFY LIBRARY ITEM ROLES + valid_modify_roles = [] + invalid_modify_roles_names = [] + new_modify_roles_ids = util.listify( new_modify_roles_ids ) + + # Load all access roles to check + active_access_roles = dataset.get_access_roles( trans ) + + for role_id in new_modify_roles_ids: + role = self._load_role( trans, role_id ) + + # Check whether role is in the set of access roles + if role in active_access_roles: + valid_modify_roles.append( role ) + else: + invalid_modify_roles_names.append( role_id ) + + if len( invalid_modify_roles_names ) > 0: + log.warning( "The following roles could not be added to the dataset modify permission: " + str( invalid_modify_roles_names ) ) + + modify_permission = { trans.app.security_agent.permitted_actions.LIBRARY_MODIFY : valid_modify_roles } + trans.app.security_agent.set_library_item_permission( library_dataset, modify_permission ) + + else: + raise exceptions.RequestParameterInvalidException( 'The mandatory parameter "action" has an invalid value.' + 'Allowed values are: "remove_restrictions", "make_private", "set_permissions"' ) + + return self._get_current_roles( trans, library_dataset ) + + def _load_role( self, trans, role_name ): + """ + Method loads the role from the DB based on the given role name. + + :param role_name: name of the role to load from the DB + :type role_name: string + + :rtype: Role + :returns: the loaded Role object + + :raises: InconsistentDatabase, RequestParameterInvalidException, InternalServerError + """ + try: + role = trans.sa_session.query( trans.app.model.Role ).filter( trans.model.Role.table.c.name == role_name ).one() + except MultipleResultsFound: + raise exceptions.InconsistentDatabase( 'Multiple roles found with the same name. Name: ' + str( role_name ) ) + except NoResultFound: + raise exceptions.RequestParameterInvalidException( 'No role found with the name provided. Name: ' + str( role_name ) ) + except Exception, e: + raise exceptions.InternalServerError( 'Error loading from the database.' + str(e)) + return role @expose_api def delete( self, trans, encoded_dataset_id, **kwd ): """ delete( self, trans, encoded_dataset_id, **kwd ): * DELETE /api/libraries/datasets/{encoded_dataset_id} - Marks the dataset deleted or undeletes it based on the value - of the undelete flag (if present). + Marks the dataset deleted or undeleted based on the value + of the undelete flag. + If the flag is not present it is considered False and the + item is marked deleted. + + :param encoded_dataset_id: the encoded id of the dataset to change + :type encoded_dataset_id: an encoded id string + + :rtype: dictionary + :returns: dict containing information about the dataset """ undelete = util.string_as_bool( kwd.get( 'undelete', False ) ) try: diff -r 6f92d5b8bd12b72dee5465be693344b9175344b7 -r 2c732150b88ec8a9638a1c14d9db6e7f0d925ba3 lib/galaxy/webapps/galaxy/api/libraries.py --- a/lib/galaxy/webapps/galaxy/api/libraries.py +++ b/lib/galaxy/webapps/galaxy/api/libraries.py @@ -7,6 +7,8 @@ from galaxy.web import _future_expose_api_anonymous as expose_api_anonymous from galaxy.model.orm import and_, not_, or_ from galaxy.web.base.controller import BaseAPIController +from sqlalchemy.orm.exc import MultipleResultsFound +from sqlalchemy.orm.exc import NoResultFound import logging log = logging.getLogger( __name__ ) @@ -30,10 +32,11 @@ .. seealso:: :attr:`galaxy.model.Library.dict_collection_visible_keys` """ + is_admin = trans.user_is_admin() query = trans.sa_session.query( trans.app.model.Library ) deleted = kwd.get( 'deleted', 'missing' ) try: - if not trans.user_is_admin(): + if not is_admin: # non-admins can't see deleted libraries deleted = False else: @@ -46,15 +49,17 @@ # given value wasn't true/false but the user is admin so we don't filter on this parameter at all pass - current_user_role_ids = [ role.id for role in trans.get_current_user_roles() ] - library_access_action = trans.app.security_agent.permitted_actions.LIBRARY_ACCESS.action - restricted_library_ids = [ lp.library_id for lp in ( trans.sa_session.query( trans.model.LibraryPermissions ) - .filter( trans.model.LibraryPermissions.table.c.action == library_access_action ) - .distinct() ) ] - accessible_restricted_library_ids = [ lp.library_id for lp in ( trans.sa_session.query( trans.model.LibraryPermissions ) - .filter( and_( trans.model.LibraryPermissions.table.c.action == library_access_action, - trans.model.LibraryPermissions.table.c.role_id.in_( current_user_role_ids ) ) ) ) ] - query = query.filter( or_( not_( trans.model.Library.table.c.id.in_( restricted_library_ids ) ), trans.model.Library.table.c.id.in_( accessible_restricted_library_ids ) ) ) + if not is_admin: + # non-admins can see only allowed and public libraries + current_user_role_ids = [ role.id for role in trans.get_current_user_roles() ] + library_access_action = trans.app.security_agent.permitted_actions.LIBRARY_ACCESS.action + restricted_library_ids = [ lp.library_id for lp in ( trans.sa_session.query( trans.model.LibraryPermissions ) + .filter( trans.model.LibraryPermissions.table.c.action == library_access_action ) + .distinct() ) ] + accessible_restricted_library_ids = [ lp.library_id for lp in ( trans.sa_session.query( trans.model.LibraryPermissions ) + .filter( and_( trans.model.LibraryPermissions.table.c.action == library_access_action, + trans.model.LibraryPermissions.table.c.role_id.in_( current_user_role_ids ) ) ) ) ] + query = query.filter( or_( not_( trans.model.Library.table.c.id.in_( restricted_library_ids ) ), trans.model.Library.table.c.id.in_( accessible_restricted_library_ids ) ) ) libraries = [] for library in query: item = library.to_dict( view='element', value_mapper={ 'id': trans.security.encode_id, 'root_folder_id': trans.security.encode_id } ) @@ -95,8 +100,14 @@ """ library_id = id deleted = util.string_as_bool( deleted ) + library = self._load_library( trans, library_id, deleted ) + if not library or not ( trans.user_is_admin() or trans.app.security_agent.can_access_library( trans.get_current_user_roles(), library ) ): + raise exceptions.ObjectNotFound( 'Library with the id provided ( %s ) was not found' % id ) + return library.to_dict( view='element', value_mapper={ 'id': trans.security.encode_id, 'root_folder_id': trans.security.encode_id } ) + + def _load_library( self, trans, encoded_library_id, deleted=False ): try: - decoded_library_id = trans.security.decode_id( library_id ) + decoded_library_id = trans.security.decode_id( encoded_library_id ) except TypeError: raise exceptions.MalformedId( 'Malformed library id ( %s ) specified, unable to decode.' % id ) try: @@ -104,9 +115,7 @@ assert library.deleted == deleted except Exception: library = None - if not library or not ( trans.user_is_admin() or trans.app.security_agent.can_access_library( trans.get_current_user_roles(), library ) ): - raise exceptions.ObjectNotFound( 'Library with the id provided ( %s ) was not found' % id ) - return library.to_dict( view='element', value_mapper={ 'id': trans.security.encode_id, 'root_folder_id': trans.security.encode_id } ) + return library @expose_api def create( self, trans, payload, **kwd ): @@ -250,3 +259,230 @@ trans.sa_session.add( library ) trans.sa_session.flush() return library.to_dict( view='element', value_mapper={ 'id': trans.security.encode_id, 'root_folder_id': trans.security.encode_id } ) + + @expose_api + def get_permissions( self, trans, encoded_library_id, **kwd ): + """ + * GET /api/libraries/{id}/permissions + + Load all permissions for the given library id and return it. + + :param encoded_library_id: the encoded id of the library + :type encoded_library_id: an encoded id string + + :param scope: either 'current' or 'available' + :type scope: string + + :param is_library_access: indicates whether the roles available for the library access are requested + :type is_library_access: bool + + :returns: dictionary with all applicable permissions' values + :rtype: dictionary + + :raises: ObjectNotFound, InsufficientPermissionsException + """ + current_user_roles = trans.get_current_user_roles() + is_admin = trans.user_is_admin() + library = self._load_library( trans, encoded_library_id ) + if not library or not ( is_admin or trans.app.security_agent.can_access_library( current_user_roles, library ) ): + raise exceptions.ObjectNotFound( 'Library with the id provided ( %s ) was not found' % id ) + if not ( is_admin or trans.app.security_agent.can_manage_library_item( current_user_roles, library ) ): + raise exceptions.InsufficientPermissionsException( 'You do not have proper permission to access permissions of this library.' ) + + scope = kwd.get( 'scope', None ) + is_library_access = util.string_as_bool( kwd.get( 'is_library_access', False ) ) + + if scope == 'current' or scope is None: + return self._get_current_roles( trans, library ) + + # Return roles that are available to select. + elif scope == 'available': + page = kwd.get( 'page', None ) + if page is not None: + page = int( page ) + else: + page = 1 + + page_limit = kwd.get( 'page_limit', None ) + if page_limit is not None: + page_limit = int( page_limit ) + else: + page_limit = 10 + + query = kwd.get( 'q', None ) + + roles, total_roles = trans.app.security_agent.get_valid_roles( trans, library, query, page, page_limit, is_library_access ) + + return_roles = [] + for role in roles: + return_roles.append( dict( id=role.name, name=role.name, type=role.type ) ) + return dict( roles=return_roles, page=page, page_limit=page_limit, total=total_roles ) + else: + raise exceptions.RequestParameterInvalidException( "The value of 'scope' parameter is invalid. Alllowed values: current, available" ) + + def _load_role( self, trans, role_name ): + """ + Method loads the role from the DB based on the given role name. + + :param role_name: name of the role to load from the DB + :type role_name: string + + :rtype: Role + :returns: the loaded Role object + + :raises: InconsistentDatabase, RequestParameterInvalidException, InternalServerError + """ + try: + role = trans.sa_session.query( trans.app.model.Role ).filter( trans.model.Role.table.c.name == role_name ).one() + except MultipleResultsFound: + raise exceptions.InconsistentDatabase( 'Multiple roles found with the same name. Name: ' + str( role_name ) ) + except NoResultFound: + raise exceptions.RequestParameterInvalidException( 'No role found with the name provided. Name: ' + str( role_name ) ) + except Exception, e: + raise exceptions.InternalServerError( 'Error loading from the database.' + str(e)) + return role + + @expose_api + def set_permissions( self, trans, encoded_library_id, **kwd ): + """ + def set_permissions( self, trans, encoded_dataset_id, **kwd ): + *POST /api/libraries/{encoded_library_id}/permissions + + :param encoded_library_id: the encoded id of the library to set the permissions of + :type encoded_library_id: an encoded id string + + :param action: (required) describes what action should be performed + available actions: remove_restrictions, set_permissions + :type action: string + + :param access_ids[]: list of Role.name defining roles that should have access permission on the library + :type access_ids[]: string or list + :param add_ids[]: list of Role.name defining roles that should have add item permission on the library + :type add_ids[]: string or list + :param manage_ids[]: list of Role.name defining roles that should have manage permission on the library + :type manage_ids[]: string or list + :param modify_ids[]: list of Role.name defining roles that should have modify permission on the library + :type modify_ids[]: string or list + + :rtype: dictionary + :returns: dict of current roles for all available permission types + + :raises: RequestParameterInvalidException, ObjectNotFound, InsufficientPermissionsException, InternalServerError + RequestParameterMissingException + """ + is_admin = trans.user_is_admin() + current_user_roles = trans.get_current_user_roles() + library = self._load_library( trans, encoded_library_id ) + if not library or not ( is_admin or trans.app.security_agent.can_access_library( current_user_roles, library ) ): + raise exceptions.ObjectNotFound( 'Library with the id provided ( %s ) was not found' % id ) + if not ( is_admin or trans.app.security_agent.can_manage_library_item( current_user_roles, library ) ): + raise exceptions.InsufficientPermissionsException( 'You do not have proper permission to modify permissions of this library.' ) + + new_access_roles_ids = util.listify( kwd.get( 'access_ids[]', None ) ) + new_add_roles_ids = util.listify( kwd.get( 'add_ids[]', None ) ) + new_manage_roles_ids = util.listify( kwd.get( 'manage_ids[]', None ) ) + new_modify_roles_ids = util.listify( kwd.get( 'modify_ids[]', None ) ) + + action = kwd.get( 'action', None ) + if action is None: + raise exceptions.RequestParameterMissingException( 'The mandatory parameter "action" is missing.' ) + elif action == 'remove_restrictions': + trans.app.security_agent.make_library_public( library ) + if not trans.app.security_agent.library_is_public( library ): + raise exceptions.InternalServerError( 'An error occured while making library public.' ) + elif action == 'set_permissions': + + # ACCESS LIBRARY ROLES + valid_access_roles = [] + invalid_access_roles_names = [] + for role_id in new_access_roles_ids: + role = self._load_role( trans, role_id ) + # Check whether role is in the set of allowed roles + valid_roles, total_roles = trans.app.security_agent.get_valid_roles( trans, library, is_library_access=True ) + if role in valid_roles: + valid_access_roles.append( role ) + else: + invalid_access_roles_names.append( role_id ) + if len( invalid_access_roles_names ) > 0: + log.warning( "The following roles could not be added to the library access permission: " + str( invalid_access_roles_names ) ) + + # ADD TO LIBRARY ROLES + valid_add_roles = [] + invalid_add_roles_names = [] + for role_id in new_add_roles_ids: + role = self._load_role( trans, role_id ) + # Check whether role is in the set of allowed roles + valid_roles, total_roles = trans.app.security_agent.get_valid_roles( trans, library ) + if role in valid_roles: + valid_add_roles.append( role ) + else: + invalid_add_roles_names.append( role_id ) + if len( invalid_add_roles_names ) > 0: + log.warning( "The following roles could not be added to the add library item permission: " + str( invalid_add_roles_names ) ) + + # MANAGE LIBRARY ROLES + valid_manage_roles = [] + invalid_manage_roles_names = [] + for role_id in new_manage_roles_ids: + role = self._load_role( trans, role_id ) + # Check whether role is in the set of allowed roles + valid_roles, total_roles = trans.app.security_agent.get_valid_roles( trans, library ) + if role in valid_roles: + valid_manage_roles.append( role ) + else: + invalid_manage_roles_names.append( role_id ) + if len( invalid_manage_roles_names ) > 0: + log.warning( "The following roles could not be added to the manage library permission: " + str( invalid_manage_roles_names ) ) + + # MODIFY LIBRARY ROLES + valid_modify_roles = [] + invalid_modify_roles_names = [] + for role_id in new_modify_roles_ids: + role = self._load_role( trans, role_id ) + # Check whether role is in the set of allowed roles + valid_roles, total_roles = trans.app.security_agent.get_valid_roles( trans, library ) + if role in valid_roles: + valid_modify_roles.append( role ) + else: + invalid_modify_roles_names.append( role_id ) + if len( invalid_modify_roles_names ) > 0: + log.warning( "The following roles could not be added to the modify library permission: " + str( invalid_modify_roles_names ) ) + + permissions = { trans.app.security_agent.permitted_actions.LIBRARY_ACCESS : valid_access_roles } + permissions.update( { trans.app.security_agent.permitted_actions.LIBRARY_ADD : valid_add_roles } ) + permissions.update( { trans.app.security_agent.permitted_actions.LIBRARY_MANAGE : valid_manage_roles } ) + permissions.update( { trans.app.security_agent.permitted_actions.LIBRARY_MODIFY : valid_modify_roles } ) + + trans.app.security_agent.set_all_library_permissions( trans, library, permissions ) + else: + raise exceptions.RequestParameterInvalidException( 'The mandatory parameter "action" has an invalid value.' + 'Allowed values are: "remove_restrictions", set_permissions"' ) + + return self._get_current_roles( trans, library ) + + + def _get_current_roles( self, trans, library): + """ + Find all roles currently connected to relevant permissions + on the library. + + :param library: the model object + :type library: Library + + :rtype: dictionary + :returns: dict of current roles for all available permission types + """ + # Omit duplicated roles by converting to set + access_roles = set( library.get_access_roles( trans ) ) + modify_roles = set( trans.app.security_agent.get_roles_for_action( library, trans.app.security_agent.permitted_actions.LIBRARY_MODIFY ) ) + manage_roles = set( trans.app.security_agent.get_roles_for_action( library, trans.app.security_agent.permitted_actions.LIBRARY_MANAGE ) ) + add_roles = set( trans.app.security_agent.get_roles_for_action( library, trans.app.security_agent.permitted_actions.LIBRARY_ADD ) ) + + access_library_role_list = [ access_role.name for access_role in access_roles ] + modify_library_role_list = [ modify_role.name for modify_role in modify_roles ] + manage_library_role_list = [ manage_role.name for manage_role in manage_roles ] + add_library_item_role_list = [ add_role.name for add_role in add_roles ] + + return dict( access_library_role_list=access_library_role_list, modify_library_role_list=modify_library_role_list, manage_library_role_list=manage_library_role_list, add_library_item_role_list=add_library_item_role_list ) + + diff -r 6f92d5b8bd12b72dee5465be693344b9175344b7 -r 2c732150b88ec8a9638a1c14d9db6e7f0d925ba3 lib/galaxy/webapps/galaxy/api/permissions.py --- a/lib/galaxy/webapps/galaxy/api/permissions.py +++ b/lib/galaxy/webapps/galaxy/api/permissions.py @@ -11,6 +11,7 @@ log = logging.getLogger( __name__ ) + class PermissionsController( BaseAPIController ): # Method not ideally named @@ -49,4 +50,3 @@ item = library.to_dict( view='element', value_mapper={ 'id' : trans.security.encode_id , 'root_folder_id' : trans.security.encode_id } ) return item - diff -r 6f92d5b8bd12b72dee5465be693344b9175344b7 -r 2c732150b88ec8a9638a1c14d9db6e7f0d925ba3 lib/galaxy/webapps/galaxy/buildapp.py --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -241,25 +241,43 @@ '/api/libraries/:id', controller='libraries', action='update', - conditions=dict( method=[ "PATCH", 'PUT' ] ) ) + conditions=dict( method=[ "PATCH", "PUT" ] ) ) - webapp.mapper.connect( 'show_lda_item', + webapp.mapper.connect( 'show_library_permissions', + '/api/libraries/:encoded_library_id/permissions', + controller='libraries', + action='get_permissions', + conditions=dict( method=[ "GET" ] ) ) + + webapp.mapper.connect( 'set_library_permissions', + '/api/libraries/:encoded_library_id/permissions', + controller='libraries', + action='set_permissions', + conditions=dict( method=[ "POST" ] ) ) + + webapp.mapper.connect( 'show_ld_item', '/api/libraries/datasets/:id', controller='lda_datasets', action='show', conditions=dict( method=[ "GET" ] ) ) + webapp.mapper.connect( 'show_version_of_ld_item', + '/api/libraries/datasets/:encoded_dataset_id/versions/:encoded_ldda_id', + controller='lda_datasets', + action='show_version', + conditions=dict( method=[ "GET" ] ) ) + webapp.mapper.connect( 'show_legitimate_lda_roles', '/api/libraries/datasets/:encoded_dataset_id/permissions', controller='lda_datasets', action='show_roles', conditions=dict( method=[ "GET" ] ) ) - webapp.mapper.connect( 'show_legitimate_lda_roles', - '/api/libraries/datasets/:encoded_dataset_id/permissions/current', + webapp.mapper.connect( 'update_lda_permissions', + '/api/libraries/datasets/:encoded_dataset_id/permissions', controller='lda_datasets', - action='get_roles', - conditions=dict( method=[ "GET" ] ) ) + action='update_permissions', + conditions=dict( method=[ "POST" ] ) ) webapp.mapper.connect( 'delete_lda_item', '/api/libraries/datasets/:encoded_dataset_id', @@ -313,6 +331,18 @@ 'folders', path_prefix='/api' ) + webapp.mapper.connect( 'show_folder_permissions', + '/api/folders/:encoded_folder_id/permissions', + controller='folders', + action='get_permissions', + conditions=dict( method=[ "GET" ] ) ) + + webapp.mapper.connect( 'set_folder_permissions', + '/api/folders/:encoded_folder_id/permissions', + controller='folders', + action='set_permissions', + conditions=dict( method=[ "POST" ] ) ) + webapp.mapper.resource( 'content', 'contents', controller='folder_contents', diff -r 6f92d5b8bd12b72dee5465be693344b9175344b7 -r 2c732150b88ec8a9638a1c14d9db6e7f0d925ba3 static/scripts/galaxy.library.js --- a/static/scripts/galaxy.library.js +++ b/static/scripts/galaxy.library.js @@ -12,7 +12,9 @@ "mvc/library/library-librarylist-view", "mvc/library/library-librarytoolbar-view", "mvc/library/library-foldertoolbar-view", - "mvc/library/library-dataset-view" + "mvc/library/library-dataset-view", + "mvc/library/library-library-view", + "mvc/library/library-folder-view" ], function(mod_masthead, mod_utils, @@ -23,7 +25,9 @@ mod_librarylist_view, mod_librarytoolbar_view, mod_foldertoolbar_view, - mod_library_dataset_view + mod_library_dataset_view, + mod_library_library_view, + mod_library_folder_view ) { // ============================================================================ @@ -36,11 +40,14 @@ }, routes: { - "" : "libraries", - "folders/:id" : "folder_content", - "folders/:folder_id/datasets/:dataset_id" : "dataset_detail", - "folders/:folder_id/datasets/:dataset_id/permissions" : "dataset_permissions", - "folders/:folder_id/download/:format" : "download" + "" : "libraries", + "library/:library_id/permissions" : "library_permissions", + "folders/:folder_id/permissions" : "folder_permissions", + "folders/:id" : "folder_content", + "folders/:folder_id/datasets/:dataset_id" : "dataset_detail", + "folders/:folder_id/datasets/:dataset_id/permissions" : "dataset_permissions", + "folders/:folder_id/datasets/:dataset_id/versions/:ldda_id" : "dataset_version", + "folders/:folder_id/download/:format" : "download" }, back: function() { @@ -72,6 +79,7 @@ libraryToolbarView: null, libraryListView: null, library_router: null, + libraryView: null, folderToolbarView: null, folderListView: null, datasetView: null, @@ -112,6 +120,12 @@ } Galaxy.libraries.datasetView = new mod_library_dataset_view.LibraryDatasetView({id: dataset_id}); }); + this.library_router.on('route:dataset_version', function(folder_id, dataset_id, ldda_id){ + if (Galaxy.libraries.datasetView){ + Galaxy.libraries.datasetView.$el.unbind('click'); + } + Galaxy.libraries.datasetView = new mod_library_dataset_view.LibraryDatasetView({id: dataset_id, ldda_id: ldda_id, show_version: true}); + }); this.library_router.on('route:dataset_permissions', function(folder_id, dataset_id){ if (Galaxy.libraries.datasetView){ @@ -120,6 +134,20 @@ Galaxy.libraries.datasetView = new mod_library_dataset_view.LibraryDatasetView({id: dataset_id, show_permissions: true}); }); + this.library_router.on('route:library_permissions', function(library_id){ + if (Galaxy.libraries.libraryView){ + Galaxy.libraries.libraryView.$el.unbind('click'); + } + Galaxy.libraries.libraryView = new mod_library_library_view.LibraryView({id: library_id, show_permissions: true}); + }); + + this.library_router.on('route:folder_permissions', function(folder_id){ + if (Galaxy.libraries.folderView){ + Galaxy.libraries.folderView.$el.unbind('click'); + } + Galaxy.libraries.folderView = new mod_library_folder_view.FolderView({id: folder_id, show_permissions: true}); + }); + Backbone.history.start({pushState: false}); } }); This diff is so big that we needed to truncate the remainder. Repository URL: https://bitbucket.org/galaxy/galaxy-central/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.