"""
Manager and Serializer for Library Folders.
"""
import galaxy.exceptions
from sqlalchemy.orm.exc import MultipleResultsFound
from sqlalchemy.orm.exc import NoResultFound
import logging
log = logging.getLogger( __name__ )
# =============================================================================
[docs]class FolderManager( object ):
"""
Interface/service object for interacting with folders.
"""
[docs] def get( self, trans, decoded_folder_id, check_manageable=False, check_accessible=True):
"""
Get the folder from the DB.
:param decoded_folder_id: decoded folder id
:type decoded_folder_id: int
:param check_manageable: flag whether the check that user can manage item
:type check_manageable: bool
:param check_accessible: flag whether to check that user can access item
:type check_accessible: bool
:returns: the requested folder
:rtype: LibraryFolder
:raises: InconsistentDatabase, RequestParameterInvalidException, InternalServerError
"""
try:
folder = trans.sa_session.query( trans.app.model.LibraryFolder ).filter( trans.app.model.LibraryFolder.table.c.id == decoded_folder_id ).one()
except MultipleResultsFound:
raise galaxy.exceptions.InconsistentDatabase( 'Multiple folders found with the same id.' )
except NoResultFound:
raise galaxy.exceptions.RequestParameterInvalidException( 'No folder found with the id provided.' )
except Exception, e:
raise galaxy.exceptions.InternalServerError( 'Error loading from the database.' + str( e ) )
folder = self.secure( trans, folder, check_manageable, check_accessible )
return folder
[docs] def secure( self, trans, folder, check_manageable=True, check_accessible=True ):
"""
Check if (a) user can manage folder or (b) folder is accessible to user.
:param folder: folder item
:type folder: LibraryFolder
:param check_manageable: flag whether to check that user can manage item
:type check_manageable: bool
:param check_accessible: flag whether to check that user can access item
:type check_accessible: bool
:returns: the original folder
:rtype: LibraryFolder
"""
# all folders are accessible to an admin
if trans.user_is_admin():
return folder
if check_manageable:
folder = self.check_manageable( trans, folder )
if check_accessible:
folder = self.check_accessible( trans, folder )
return folder
[docs] def check_manageable( self, trans, folder ):
"""
Check whether the user can manage the folder.
:returns: the original folder
:rtype: LibraryFolder
:raises: AuthenticationRequired, InsufficientPermissionsException
"""
if not trans.user:
raise galaxy.exceptions.AuthenticationRequired( "Must be logged in to manage Galaxy items.", type='error' )
current_user_roles = trans.get_current_user_roles()
if not trans.app.security_agent.can_manage_library_item( current_user_roles, folder ):
raise galaxy.exceptions.InsufficientPermissionsException( "You don't have permissions to manage this folder.", type='error' )
else:
return folder
[docs] def check_accessible( self, trans, folder ):
"""
Check whether the folder is accessible to current user.
By default every folder is accessible (contents have their own permissions).
"""
return folder
[docs] def get_folder_dict( self, trans, folder ):
"""
Return folder data in the form of a dictionary.
:param folder: folder item
:type folder: LibraryFolder
:returns: dict with data about the folder
:rtype: dictionary
"""
folder_dict = folder.to_dict( view='element' )
folder_dict = trans.security.encode_all_ids( folder_dict, True )
folder_dict[ 'id' ] = 'F' + folder_dict[ 'id' ]
if folder_dict[ 'parent_id' ] is not None:
folder_dict[ 'parent_id' ] = 'F' + folder_dict[ 'parent_id' ]
return folder_dict
[docs] def create( self, trans, parent_folder_id, new_folder_name, new_folder_description='' ):
"""
Create a new folder under the given folder.
:param parent_folder_id: decoded id
:type parent_folder_id: int
:param new_folder_name: name of the new folder
:type new_folder_name: str
:param new_folder_description: description of the folder (optional, defaults to empty string)
:type new_folder_description: str
:returns: the new folder
:rtype: LibraryFolder
:raises: InsufficientPermissionsException
"""
parent_folder = self.get( trans, parent_folder_id )
current_user_roles = trans.get_current_user_roles()
if not ( trans.user_is_admin or trans.app.security_agent.can_add_library_item( current_user_roles, parent_folder ) ):
raise galaxy.exceptions.InsufficientPermissionsException( 'You do not have proper permission to create folders under given folder.' )
new_folder = trans.app.model.LibraryFolder( name=new_folder_name, description=new_folder_description )
# We are associating the last used genome build with folders, so we will always
# initialize a new folder with the first dbkey in genome builds list which is currently
# ? unspecified (?)
new_folder.genome_build = trans.app.genome_builds.default_value
parent_folder.add_folder( new_folder )
trans.sa_session.add( new_folder )
trans.sa_session.flush()
# New folders default to having the same permissions as their parent folder
trans.app.security_agent.copy_library_permissions( trans, parent_folder, new_folder )
return new_folder
[docs] def delete( self, trans, folder, undelete=False ):
"""
Mark given folder deleted/undeleted based on the flag.
:param folder: the model object
:type folder: LibraryFolder
:param undelete: flag whether to delete (when False) or undelete
:type undelete: Bool
:returns: the folder
:rtype: LibraryFolder
:raises: ItemAccessibilityException
"""
if not trans.user_is_admin():
folder = self.check_manageable( trans, folder )
if undelete:
folder.deleted = False
else:
folder.deleted = True
trans.sa_session.add( folder )
trans.sa_session.flush()
return folder
[docs] 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
:returns: dict of current roles for all available permission types
:rtype: dictionary
"""
# 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, trans.security.encode_id( modify_role.id ) ) for modify_role in modify_roles ]
manage_folder_role_list = [ ( manage_role.name, trans.security.encode_id( manage_role.id ) ) for manage_role in manage_roles ]
add_library_item_role_list = [ ( add_role.name, trans.security.encode_id( add_role.id ) ) 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 )
[docs] def can_add_item( self, trans, folder ):
"""
Return true if the user has permissions to add item to the given folder.
"""
if trans.user_is_admin:
return True
current_user_roles = trans.get_current_user_roles()
add_roles = set( trans.app.security_agent.get_roles_for_action( folder, trans.app.security_agent.permitted_actions.LIBRARY_ADD ) )
for role in current_user_roles:
if role in add_roles:
return True
return False
[docs] def cut_the_prefix( self, encoded_folder_id ):
"""
Remove the prefix from the encoded folder id.
:param encoded_folder_id: encoded id of the Folder object with 'F' prepended
:type encoded_folder_id: string
:returns: encoded Folder id without the 'F' prefix
:rtype: string
:raises: MalformedId
"""
if ( ( len( encoded_folder_id ) % 16 == 1 ) and encoded_folder_id.startswith( 'F' ) ):
cut_id = encoded_folder_id[ 1: ]
else:
raise galaxy.exceptions.MalformedId( 'Malformed folder id ( %s ) specified, unable to decode.' % str( encoded_folder_id ) )
return cut_id
[docs] def decode_folder_id( self, trans, encoded_folder_id ):
"""
Decode the folder id given that it has already lost the prefixed 'F'.
:param encoded_folder_id: encoded id of the Folder object
:type encoded_folder_id: string
:returns: decoded Folder id
:rtype: int
:raises: MalformedId
"""
try:
decoded_id = trans.security.decode_id( encoded_folder_id )
except ValueError:
raise galaxy.exceptions.MalformedId( "Malformed folder id ( %s ) specified, unable to decode" % ( str( encoded_folder_id ) ) )
return decoded_id
[docs] def cut_and_decode( self, trans, encoded_folder_id ):
"""
Cuts the folder prefix (the prepended 'F') and returns the decoded id.
:param encoded_folder_id: encoded id of the Folder object
:type encoded_folder_id: string
:returns: decoded Folder id
:rtype: int
"""
return self.decode_folder_id( trans, self.cut_the_prefix( encoded_folder_id ) )