diff --git a/lib/galaxy/config.py b/lib/galaxy/config.py index 560062ed8cd..a8df982d53c 100644 --- a/lib/galaxy/config.py +++ b/lib/galaxy/config.py @@ -38,10 +38,12 @@ class Configuration( object ): self.id_secret = kwargs.get( "id_secret", "USING THE DEFAULT IS NOT SECURE!" ) self.use_remote_user = string_as_bool( kwargs.get( "use_remote_user", "False" ) ) self.remote_user_maildomain = kwargs.get( "remote_user_maildomain", None ) + self.require_login = string_as_bool( kwargs.get( "require_login", "False" ) ) + self.allow_user_creation = string_as_bool( kwargs.get( "allow_user_creation", "True" ) ) self.template_path = resolve_path( kwargs.get( "template_path", "templates" ), self.root ) self.template_cache = resolve_path( kwargs.get( "template_cache_path", "database/compiled_templates" ), self.root ) self.local_job_queue_workers = int( kwargs.get( "local_job_queue_workers", "5" ) ) - self.cluster_job_queue_workers = int( kwargs.get( "cluster_job_queue_workers", "5" ) ) + self.cluster_job_queue_workers = int( kwargs.get( "cluster_job_queue_workers", "3" ) ) self.job_scheduler_policy = kwargs.get("job_scheduler_policy", "FIFO") self.job_queue_cleanup_interval = int( kwargs.get("job_queue_cleanup_interval", "5") ) self.job_working_directory = resolve_path( kwargs.get( "job_working_directory", "database/job_working_directory" ), self.root ) diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index d992300719c..4696c0ca1ad 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -391,7 +391,10 @@ assign_mapper( context, HistoryDatasetAssociation, HistoryDatasetAssociation.tab children=relation( HistoryDatasetAssociation, primaryjoin=( HistoryDatasetAssociation.table.c.parent_id == HistoryDatasetAssociation.table.c.id ), - backref=backref( "parent", primaryjoin=( HistoryDatasetAssociation.table.c.parent_id == HistoryDatasetAssociation.table.c.id ), remote_side=[HistoryDatasetAssociation.table.c.id], uselist=False ) ) + backref=backref( "parent", primaryjoin=( HistoryDatasetAssociation.table.c.parent_id == HistoryDatasetAssociation.table.c.id ), remote_side=[HistoryDatasetAssociation.table.c.id], uselist=False ) ), + visible_children=relation( + HistoryDatasetAssociation, + primaryjoin=( ( HistoryDatasetAssociation.table.c.parent_id == HistoryDatasetAssociation.table.c.id ) & ( HistoryDatasetAssociation.table.c.visible == True ) ) ) ) ) assign_mapper( context, Dataset, Dataset.table, @@ -531,6 +534,9 @@ assign_mapper( context, LibraryFolderDatasetAssociation, LibraryFolderDatasetAss LibraryFolderDatasetAssociation, primaryjoin=( LibraryFolderDatasetAssociation.table.c.parent_id == LibraryFolderDatasetAssociation.table.c.id ), backref=backref( "parent", primaryjoin=( LibraryFolderDatasetAssociation.table.c.parent_id == LibraryFolderDatasetAssociation.table.c.id ), remote_side=[LibraryFolderDatasetAssociation.table.c.id] ) ), + visible_children=relation( + LibraryFolderDatasetAssociation, + primaryjoin=( ( LibraryFolderDatasetAssociation.table.c.parent_id == LibraryFolderDatasetAssociation.table.c.id ) & ( LibraryFolderDatasetAssociation.table.c.visible == True ) ) ), tags=relation( LibraryTagDatasetAssociation, primaryjoin=( LibraryFolderDatasetAssociation.table.c.id == LibraryTagDatasetAssociation.table.c.dataset_id ), diff --git a/lib/galaxy/util/__init__.py b/lib/galaxy/util/__init__.py index 5c7d068f861..f82997f532c 100644 --- a/lib/galaxy/util/__init__.py +++ b/lib/galaxy/util/__init__.py @@ -246,6 +246,18 @@ def string_as_bool( string ): else: return False +def listify( item ): + """ + Make a single item a single item list, or return a list if passed a + list. Passing a None returns an empty list. + """ + if item is None: + return [] + elif isinstance( item, list ): + return item + else: + return [ item ] + def commaify(amount): orig = amount new = re.sub("^(-?\d+)(\d{3})", '\g<1>,\g<2>', amount) diff --git a/lib/galaxy/web/__init__.py b/lib/galaxy/web/__init__.py index 508e2b35037..15bb7888368 100644 --- a/lib/galaxy/web/__init__.py +++ b/lib/galaxy/web/__init__.py @@ -2,5 +2,5 @@ The Galaxy web application. """ -from framework import expose, json, require_login, url_for, error, form, FormBuilder +from framework import expose, json, require_login, require_admin, url_for, error, form, FormBuilder diff --git a/lib/galaxy/web/controllers/admin.py b/lib/galaxy/web/controllers/admin.py index d96588f41f1..7ca8d20233f 100644 --- a/lib/galaxy/web/controllers/admin.py +++ b/lib/galaxy/web/controllers/admin.py @@ -13,68 +13,53 @@ import sqlalchemy as sa import logging log = logging.getLogger( __name__ ) -entities = { '@': 'FuNkYaT' } -unentities = { 'FuNkYaT' : '@' } -no_privilege_msg = "You must have Galaxy administrator privileges to use this feature." - class Admin( BaseController ): - def user_is_admin( self, trans ): - admin_users = trans.app.config.get( "admin_users", "" ).split( "," ) - if not admin_users: - return False - user = trans.get_user() - if not user: - return False - if not user.email in admin_users: - return False - return True @web.expose + @web.require_admin def index( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) msg = params.msg - return trans.fill_template( '/admin/index.mako', msg=msg ) + messagetype = params.get( 'messagetype', 'done' ) + return trans.fill_template( '/admin/index.mako', msg=msg, messagetype=messagetype ) @web.expose + @web.require_admin def center( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) return trans.fill_template( '/admin/center.mako' ) @web.expose + @web.require_admin def reload_tool( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) msg = params.msg - return trans.fill_template( '/admin/reload_tool.mako', toolbox=self.app.toolbox, msg=msg ) + messagetype = params.get( 'messagetype', 'done' ) + return trans.fill_template( '/admin/reload_tool.mako', toolbox=self.app.toolbox, msg=msg, messagetype=messagetype ) @web.expose + @web.require_admin def tool_reload( self, trans, tool_version=None, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) tool_id = params.tool_id self.app.toolbox.reload( tool_id ) msg = 'Reloaded tool: ' + tool_id - return trans.fill_template( '/admin/reload_tool.mako', toolbox=self.app.toolbox, msg=msg ) + return trans.fill_template( '/admin/reload_tool.mako', toolbox=self.app.toolbox, msg=msg, messagetype='done' ) # Galaxy Role Stuff @web.expose + @web.require_admin def roles( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) msg = params.msg + messagetype = params.get( 'messagetype', 'done' ) return trans.fill_template( '/admin/dataset_security/roles.mako', roles=trans.app.model.Role.query() \ .filter( trans.app.model.Role.table.c.type != trans.app.model.Role.types.PRIVATE ) \ .order_by( trans.app.model.Role.table.c.name ).all(), - msg=msg ) + msg=msg, + messagetype=messagetype ) @web.expose + @web.require_admin def create_role( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) msg = params.msg + messagetype = params.get( 'messagetype', 'done' ) users=trans.app.model.User.query().order_by( trans.app.model.User.table.c.email ).all() groups = trans.app.model.Group.query() \ .filter( galaxy.model.Group.table.c.deleted==False ) \ @@ -83,21 +68,20 @@ class Admin( BaseController ): return trans.fill_template( '/admin/dataset_security/role_create.mako', users=users, groups=groups, - msg=msg ) + msg=msg, + messagetype=messagetype ) @web.expose + @web.require_admin def new_role( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) - msg = params.msg name = params.name description = params.description if not name or not description: msg = "Please enter a name and a description" - trans.response.send_redirect( '/admin/create_role?msg=%s' % msg ) + trans.response.send_redirect( web.url_for( action='create_role', msg=msg, messagetype='error' ) ) elif trans.app.model.Role.filter_by( name=name ).first(): msg = "A role with that name already exists" - trans.response.send_redirect( '/admin/create_role?msg=%s' % msg ) + trans.response.send_redirect( web.url_for( action='create_role', msg=msg, messagetype='error' ) ) else: # Create the role role = galaxy.model.Role( name=name, @@ -105,27 +89,27 @@ class Admin( BaseController ): type=trans.app.model.Role.types.ADMIN ) role.flush() # Add the users - users = listify( params.users ) + users = util.listify( params.users ) for user_id in users: user = galaxy.model.User.get( user_id ) # Create the UserRoleAssociation ura = galaxy.model.UserRoleAssociation( user, role ) ura.flush() # Add the groups - groups = listify( params.groups ) + groups = util.listify( params.groups ) for group_id in groups: group = galaxy.model.Group.get( group_id ) # Create the GroupRoleAssociation gra = galaxy.model.GroupRoleAssociation( group, role ) gra.flush() msg = "The new role has been created with %s associated users and %s associated groups" % ( str( len( users ) ), str( len( groups ) ) ) - trans.response.send_redirect( '/admin/roles?msg=%s' % msg ) + trans.response.send_redirect( web.url_for( action='roles', msg=msg, messagetype='done' ) ) @web.expose + @web.require_admin def role( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) msg = params.msg + messagetype = params.get( 'messagetype', 'done' ) role = trans.app.model.Role.get( int( params.role_id ) ) in_users = [] out_users = [] @@ -173,15 +157,14 @@ class Admin( BaseController ): in_groups=in_groups, out_groups=out_groups, library_dataset_actions=library_dataset_actions, - msg=msg ) + msg=msg, + messagetype=messagetype ) @web.expose + @web.require_admin def role_members_edit( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) - msg = params.msg role = galaxy.model.Role.get( int( params.role_id ) ) - in_users = [ trans.app.model.User.get( x ) for x in listify( params.in_users ) ] + in_users = [ trans.app.model.User.get( x ) for x in util.listify( params.in_users ) ] for ura in role.users: user = trans.app.model.User.get( ura.user_id ) if user not in in_users: @@ -196,28 +179,26 @@ class Admin( BaseController ): if role == dhp.role: dhp.delete() dhp.flush() - in_groups = [ trans.app.model.Group.get( x ) for x in listify( params.in_groups ) ] + in_groups = [ trans.app.model.Group.get( x ) for x in util.listify( params.in_groups ) ] trans.app.security_agent.set_entity_role_associations( roles=[ role ], users=in_users, groups=in_groups ) role.refresh() msg = "The role has been updated with %s associated users and %s associated groups" % ( str( len( in_users ) ), str( len( in_groups ) ) ) - trans.response.send_redirect( '/admin/roles?msg=%s' % msg ) + trans.response.send_redirect( web.url_for( action='roles', msg=msg, messagetype='done' ) ) @web.expose + @web.require_admin def mark_role_deleted( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) - msg = params.msg role = galaxy.model.Role.get( int( params.role_id ) ) role.deleted = True role.flush() msg = "The role has been marked as deleted." - trans.response.send_redirect( '/admin/roles?msg=%s' % msg ) + trans.response.send_redirect( web.url_for( action='roles', msg=msg, messagetype='done' ) ) @web.expose + @web.require_admin def deleted_roles( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) msg = params.msg + messagetype = params.get( 'messagetype', 'done' ) # Build a list of tuples which are roles followed by lists of groups and users # [ ( role, [ group, group, group ], [ user, user ] ), ( role, [ group, group ], [ user ] ) ] roles_groups_users = [] @@ -235,24 +216,21 @@ class Admin( BaseController ): roles_groups_users.append( ( role, groups, users ) ) return trans.fill_template( '/admin/dataset_security/deleted_roles.mako', roles_groups_users=roles_groups_users, - msg=msg ) + msg=msg, + messagetype=messagetype ) @web.expose + @web.require_admin def undelete_role( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) - msg = params.msg role = galaxy.model.Role.get( int( params.role_id ) ) role.deleted = False role.flush() msg = "The role has been marked as not deleted." - trans.response.send_redirect( '/admin/roles?msg=%s' % msg ) + trans.response.send_redirect( web.url_for( action='roles', msg=msg, messagetype='done' ) ) @web.expose + @web.require_admin def purge_role( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) - msg = params.msg role = galaxy.model.Role.get( int( params.role_id ) ) # Delete UserRoleAssociations for ura in role.users: @@ -278,15 +256,15 @@ class Admin( BaseController ): role.delete() role.flush() msg = "The role has been purged from the database." - trans.response.send_redirect( '/admin/deleted_roles?msg=%s' % msg ) + trans.response.send_redirect( web.url_for( action='deleted_roles', msg=msg, messagetype='done' ) ) # Galaxy Group Stuff @web.expose + @web.require_admin def groups( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) msg = params.msg + messagetype = params.get( 'messagetype', 'done' ) # Build a list of tuples which are groups followed by lists of members and roles # [ ( group, [ member, member, member ], [ role, role ] ), ( group, [ member, member ], [ role ] ) ] groups_members_roles = [] @@ -304,13 +282,14 @@ class Admin( BaseController ): groups_members_roles.append( ( group, members, roles ) ) return trans.fill_template( '/admin/dataset_security/groups.mako', groups_members_roles=groups_members_roles, - msg=msg ) + msg=msg, + messagetype=messagetype ) @web.expose + @web.require_admin def create_group( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) msg = params.msg + messagetype = params.get( 'messagetype', 'done' ) users=trans.app.model.User.query().order_by( trans.app.model.User.table.c.email ).all() roles = trans.app.model.Role.query() \ .filter( and_( galaxy.model.Role.table.c.deleted == False, @@ -320,26 +299,25 @@ class Admin( BaseController ): return trans.fill_template( '/admin/dataset_security/group_create.mako', users=users, roles=roles, - msg=msg ) + msg=msg, + messagetype=messagetype ) @web.expose + @web.require_admin def new_group( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) - msg = params.msg name = params.name if not name: msg = "Please enter a name" - trans.response.send_redirect( '/admin/create_group?msg=%s' % msg ) + trans.response.send_redirect( web.url_for( action='create_group', msg=msg, messagetype='error' ) ) elif trans.app.model.Group.filter_by( name=name ).first(): msg = "A group with that name already exists" - trans.response.send_redirect( '/admin/create_group?msg=%s' % msg ) + trans.response.send_redirect( web.url_for( action='create_group', msg=msg, messagetype='error' ) ) else: # Create the group group = galaxy.model.Group( name ) group.flush() # Add the members - members = listify( params.members ) + members = util.listify( params.members ) for user_id in members: user = galaxy.model.User.get( user_id ) # Create the UserGroupAssociation @@ -357,13 +335,13 @@ class Admin( BaseController ): gra = galaxy.model.GroupRoleAssociation( group, role ) gra.flush() msg = "The new group has been created with %s members and %s associated roles" % ( str( len( members ) ), str( len( roles ) ) ) - trans.response.send_redirect( '/admin/groups?msg=%s' % msg ) + trans.response.send_redirect( web.url_for( action='groups', msg=msg, messagetype='done' ) ) @web.expose + @web.require_admin def group_members_edit( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) msg = params.msg + messagetype = params.get( 'messagetype', 'done' ) group = galaxy.model.Group.get( int( params.group_id ) ) members = [] for uga in group.members: @@ -372,14 +350,14 @@ class Admin( BaseController ): group=group, members=members, users=galaxy.model.User.query().order_by( galaxy.model.User.table.c.email ).all(), - msg=msg ) + msg=msg, + messagetype=messagetype ) @web.expose + @web.require_admin def update_group_members( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) group_id = int( params.group_id ) - members = listify( params.members ) + members = util.listify( params.members ) group = galaxy.model.Group.get( group_id ) # This is tricky since we have default association tables with # records referring to members of this group. Because of this, @@ -399,15 +377,15 @@ class Admin( BaseController ): uga = galaxy.model.UserGroupAssociation( user, group ) uga.flush() msg = "Group membership has been updated with a total of %s members" % len( members ) - trans.response.send_redirect( '/admin/groups?msg=%s' % msg ) + trans.response.send_redirect( web.url_for( action='groups', msg=msg, messagetype='done' ) ) # TODO: We probably don't want the following 2 methods since managing roles should be # restricted to the Role page due to private roles and rules governing them @web.expose + @web.require_admin def group_roles_edit( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) msg = params.msg + messagetype = params.get( 'messagetype', 'done' ) group = galaxy.model.Group.get( int( params.group_id ) ) group_roles = [] for gra in group.roles: @@ -416,14 +394,14 @@ class Admin( BaseController ): group=group, group_roles=group_roles, roles=galaxy.model.Role.query().order_by( galaxy.model.Role.table.c.name ).all(), - msg=msg ) + msg=msg, + messagetype=messagetype ) @web.expose + @web.require_admin def update_group_roles( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) group_id = int( params.group_id ) - roles = listify( params.roles ) + roles = util.listify( params.roles ) group = galaxy.model.Group.get( group_id ) # This is tricky since we have default association tables with # records referring to members of this group. Because of this, @@ -443,24 +421,22 @@ class Admin( BaseController ): gra = galaxy.model.GroupRoleAssociation( group, role ) gra.flush() msg = "Group updated with a total of %s associated roles" % len( roles ) - trans.response.send_redirect( '/admin/groups?msg=%s' % msg ) + trans.response.send_redirect( web.url_for( action='groups', msg=msg, messagetype='done' ) ) @web.expose + @web.require_admin def mark_group_deleted( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) - msg = params.msg group = galaxy.model.Group.get( int( params.group_id ) ) group.deleted = True group.flush() msg = "The group has been marked as deleted." - trans.response.send_redirect( '/admin/groups?msg=%s' % msg ) + trans.response.send_redirect( web.url_for( action='groups', msg=msg, messagetype='done' ) ) @web.expose + @web.require_admin def deleted_groups( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) msg = params.msg + messagetype = params.get( 'messagetype', 'done' ) # Build a list of tuples which are groups followed by lists of members and roles # [ ( group, [ member, member, member ], [ role, role ] ), ( group, [ member, member ], [ role ] ) ] groups_members_roles = [] @@ -478,24 +454,21 @@ class Admin( BaseController ): groups_members_roles.append( ( group, members, roles ) ) return trans.fill_template( '/admin/dataset_security/deleted_groups.mako', groups_members_roles=groups_members_roles, - msg=msg ) + msg=msg, + messagetype=messagetype ) @web.expose + @web.require_admin def undelete_group( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) - msg = params.msg group = galaxy.model.Group.get( int( params.group_id ) ) group.deleted = False group.flush() msg = "The group has been marked as not deleted." - trans.response.send_redirect( '/admin/groups?msg=%s' % msg ) + trans.response.send_redirect( web.url_for( action='groups', msg=msg, messagetype='done' ) ) @web.expose + @web.require_admin def purge_group( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) - msg = params.msg group = galaxy.model.Group.get( int( params.group_id ) ) # Delete UserGroupAssociations for uga in group.users: @@ -509,15 +482,15 @@ class Admin( BaseController ): group.delete() group.flush() msg = "The group has been purged from the database." - trans.response.send_redirect( '/admin/deleted_groups?msg=%s' % msg ) + trans.response.send_redirect( web.url_for( action='deleted_groups', msg=msg, messagetype='done' ) ) # Galaxy User Stuff @web.expose + @web.require_admin def users( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) msg = params.msg + messagetype = params.get( 'messagetype', 'done' ) # Build a list of tuples which are users followed by lists of groups and roles # [ ( user, [ group, group, group ], [ role, role ] ), ( user, [ group, group ], [ role ] ) ] users_groups_roles = [] @@ -532,14 +505,15 @@ class Admin( BaseController ): users_groups_roles.append( ( user, groups, roles ) ) return trans.fill_template( '/admin/dataset_security/users.mako', users_groups_roles=users_groups_roles, - msg=msg ) + msg=msg, + messagetype=messagetype ) @web.expose + @web.require_admin def user( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) user_id = params.user_id msg = params.msg + messagetype = params.get( 'messagetype', 'done' ) user = trans.app.model.User.get( user_id ) # Get the groups and roles to which the user belongs groups = trans.app.model.Group.query() \ @@ -553,13 +527,14 @@ class Admin( BaseController ): user=user, groups=groups, roles=roles, - msg=msg ) + msg=msg, + messagetype=messagetype ) @web.expose + @web.require_admin def user_groups_edit( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) msg = params.msg + messagetype = params.get( 'messagetype', 'done' ) user = galaxy.model.User.get( int( params.user_id ) ) user_groups = [] for uga in user.groups: @@ -572,14 +547,14 @@ class Admin( BaseController ): user=user, user_groups=user_groups, groups=groups, - msg=msg ) + msg=msg, + messagetype=messagetype ) @web.expose + @web.require_admin def update_user_groups( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) user_id = int( params.user_id ) - groups = listify( params.groups ) + groups = util.listify( params.groups ) user = galaxy.model.User.get( user_id ) # First remove existing UserGroupAssociations that are not in the received groups param for uga in user.groups: @@ -594,27 +569,30 @@ class Admin( BaseController ): uga = galaxy.model.UserGroupAssociation( user, group ) uga.flush() msg = "The user now belongs to a total of %s groups" % len( groups ) - trans.response.send_redirect( '/admin/users?msg=%s' % msg ) + trans.response.send_redirect( web.url_for( action='users', msg=msg, messagetype='done' ) ) # Galaxy Library Stuff @web.expose + @web.require_admin def library_browser( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) msg = params.msg + messagetype = params.get( 'messagetype', 'done' ) + created_lfda_ids = params.get( 'created_lfda_ids', '' ) return trans.fill_template( '/admin/library/browser.mako', libraries=trans.app.model.Library.filter( trans.app.model.Library.table.c.deleted==False ) \ .order_by( trans.app.model.Library.name ).all(), + created_lfda_ids=created_lfda_ids, deleted=False, - msg=msg ) + msg=msg, + messagetype=messagetype ) libraries = library_browser @web.expose + @web.require_admin def library( self, trans, id=None, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) msg = params.msg + messagetype = params.get( 'messagetype', 'done' ) if params.get( 'new', False ): action = 'new' elif params.get( 'rename', False ): @@ -622,9 +600,11 @@ class Admin( BaseController ): elif params.get( 'delete', False ): action = 'delete' else: - return trans.show_error_message( "You must specify a valid action ( new, rename, delete ) to perform on a library." ) + msg = 'Invalid action attempted on library' + return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) ) if not id and not action == 'new': - return trans.show_error_message( "You must specify a library to %s." % action ) + msg = "You must specify a library to %s." % action + return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) ) if not action == 'new': library = trans.app.model.Library.get( int( id ) ) if action == 'new': @@ -635,19 +615,27 @@ class Admin( BaseController ): root_folder.flush() library.root_folder = root_folder library.flush() - return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg ) ) - return trans.fill_template( '/admin/library/new_library.mako', msg=msg ) + msg = 'The new library named %s has been created' % library.name + return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='done' ) ) + return trans.fill_template( '/admin/library/new_library.mako', msg=msg, messagetype=messagetype ) elif action == 'rename': if params.rename == 'submitted': - if params.get( 'root_folder', None ): - root_folder = library.root_folder - root_folder.name = util.restore_text( params.name ) - root_folder.flush() - library.name = util.restore_text( params.name ) - library.description = util.restore_text( params.description ) - library.flush() - return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg ) ) - return trans.fill_template( '/admin/library/rename_library.mako', library=library, msg=msg ) + new_name = util.restore_text( params.name ) + new_description = util.restore_text( params.description ) + if not new_name: + msg = 'Enter a valid name' + return trans.fill_template( '/admin/library/rename_library.mako', library=library, msg=msg, messagetype='error' ) + else: + if params.get( 'root_folder', False ): + root_folder = library.root_folder + root_folder.name = new_name + root_folder.flush() + library.name = new_name + library.description = new_description + library.flush() + msg = 'The library has been renamed to %s' % new_name + return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='done' ) ) + return trans.fill_template( '/admin/library/rename_library.mako', library=library, msg=msg, messagetype=messagetype ) elif action == 'delete': def delete_folder( library_folder ): for folder in library_folder.active_folders: @@ -665,26 +653,25 @@ class Admin( BaseController ): library.deleted = True library.flush() msg = 'The library and all of its contents have been marked deleted' - return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg ) ) + return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='done' ) ) @web.expose + @web.require_admin def deleted_libraries( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) msg = params.msg + messagetype = params.get( 'messagetype', 'done' ) libraries=trans.app.model.Library.filter( and_( trans.app.model.Library.table.c.deleted==True, trans.app.model.Library.table.c.purged==False ) ) \ .order_by( trans.app.model.Library.table.c.name ).all() return trans.fill_template( '/admin/library/browser.mako', libraries=libraries, deleted=True, - msg=msg ) + msg=msg, + messagetype=messagetype ) @web.expose + @web.require_admin def undelete_library( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) - msg = params.msg library = galaxy.model.Library.get( int( params.id ) ) def undelete_folder( library_folder ): for folder in library_folder.folders: @@ -698,13 +685,11 @@ class Admin( BaseController ): library.deleted = False library.flush() msg = "The library and all of its contents have been marked not deleted" - return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg ) ) + return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='done' ) ) @web.expose + @web.require_admin def purge_library( self, trans, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) - msg = params.msg library = galaxy.model.Library.get( int( params.id ) ) def purge_folder( library_folder ): for lf in library_folder.folders: @@ -727,13 +712,13 @@ class Admin( BaseController ): library.purged = True library.flush() msg = "The library and all of its contents have been purged, datasets will be removed from disk via the cleanup_datasets script" - return trans.response.send_redirect( web.url_for( action='deleted_libraries', msg=msg ) ) + return trans.response.send_redirect( web.url_for( action='deleted_libraries', msg=msg, messagetype='done' ) ) @web.expose + @web.require_admin def folder( self, trans, id, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) msg = params.msg + messagetype = params.get( 'messagetype', 'done' ) if params.get( 'new', False ): action = 'new' elif params.get( 'rename', False ): @@ -741,10 +726,12 @@ class Admin( BaseController ): elif params.get( 'delete', False ): action = 'delete' else: - return trans.show_error_message( "You must specify a valid action ( new, rename, delete ) to perform on a folder." ) + msg = "Invalid action attempted on folder." + return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) ) folder = trans.app.model.LibraryFolder.get( id ) if not folder: - return trans.show_error_message( "Invalid folder specified, id: %s" % str( id ) ) + msg = "Invalid folder specified, id: %s" % str( id ) + return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) ) if action == 'new': if params.new == 'submitted': new_folder = trans.app.model.LibraryFolder( name=util.restore_text( params.name ), @@ -755,17 +742,26 @@ class Admin( BaseController ): new_folder.genome_build = util.dbnames.default_value folder.add_folder( new_folder ) new_folder.flush() - return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg ) ) - return trans.fill_template( '/admin/library/new_folder.mako', folder=folder, msg=msg ) + msg = 'The new folder named %s has been added to this library' % new_folder.name + return trans.response.send_redirect( web.url_for( action='folder', id=new_folder.id, msg=msg, messagetype='done' ) ) + return trans.fill_template( '/admin/library/new_folder.mako', folder=folder, msg=msg, messagetype=messagetype ) elif action == 'rename': if params.rename == 'submitted': - folder.name = util.restore_text( params.name ) - folder.description = util.restore_text( params.description ) - folder.flush() - return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg ) ) - return trans.fill_template( '/admin/library/rename_folder.mako', folder=folder, msg=msg ) + new_name = util.restore_text( params.name ) + new_description = util.restore_text( params.description ) + if not new_name: + msg = 'Enter a valid name' + return trans.fill_template( '/admin/library/rename_folder.mako', folder=folder, msg=msg, messagetype='error' ) + else: + folder.name = new_name + folder.description = new_description + folder.flush() + msg = 'The folder has been renamed to %s' % new_name + return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='done' ) ) + return trans.fill_template( '/admin/library/rename_folder.mako', folder=folder, msg=msg, messagetype=messagetype ) elif action == 'delete': def delete_folder( folder ): + folder.refresh() for subfolder in folder.active_folders: delete_folder( subfolder ) for lfda in folder.active_datasets: @@ -774,12 +770,11 @@ class Admin( BaseController ): folder.deleted = True folder.flush() delete_folder( folder ) - msg = 'The folder and all of its contents have been marked deleted' - return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg ) ) + msg = 'The folder %s and all of its contents have been marked deleted' % folder.name + return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='done' ) ) @web.expose + @web.require_admin def dataset( self, trans, id=None, name="Unnamed", info='no info', extension=None, folder_id=None, dbkey=None, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) if isinstance( dbkey, list ): last_used_build = dbkey[0] else: @@ -790,6 +785,7 @@ class Admin( BaseController ): data_files = [] params = util.Params( kwd ) msg = params.msg + messagetype = params.get( 'messagetype', 'done' ) # add_file method def add_file( file_obj, name, extension, dbkey, last_used_build, roles, info='no info', space_to_tab=False ): @@ -862,7 +858,6 @@ class Admin( BaseController ): # Dataset upload if params.get( 'new_dataset_button', False ): # Copied from upload tool action - last_dataset_created = None data_file = params.get( 'file_data', '' ) url_paste = params.get( 'url_paste', '' ) server_dir = params.get( 'server_dir', 'None' ) @@ -871,44 +866,45 @@ class Admin( BaseController ): msg = 'Select a file, enter a URL or Text, or select a server directory.' else: msg = 'Select a file, enter a URL or enter Text.' - trans.response.send_redirect( web.url_for( action='dataset', folder_id=folder_id, msg=msg ) ) + trans.response.send_redirect( web.url_for( action='dataset', folder_id=folder_id, msg=msg, messagetype='done' ) ) space_to_tab = params.get( 'space_to_tab', False ) if space_to_tab and space_to_tab not in [ "None", None ]: space_to_tab = True roles = [] role_ids = params.get( 'roles', [] ) - for role_id in listify( role_ids ): + for role_id in util.listify( role_ids ): roles.append( galaxy.model.Role.get( role_id ) ) temp_name = "" data_list = [] - created_datasets = [] + created_lfda_ids = '' if 'filename' in dir( data_file ): file_name = data_file.filename file_name = file_name.split( '\\' )[-1] file_name = file_name.split( '/' )[-1] - last_dataset_created = add_file( data_file.file, - file_name, - extension, - dbkey, - last_used_build, - roles, - info="uploaded file", - space_to_tab=space_to_tab ) + created_lfda = add_file( data_file.file, + file_name, + extension, + dbkey, + last_used_build, + roles, + info="uploaded file", + space_to_tab=space_to_tab ) + created_lfda_ids = str( created_lfda.id ) elif url_paste not in [ None, "" ]: if url_paste.lower().find( 'http://' ) >= 0 or url_paste.lower().find( 'ftp://' ) >= 0: url_paste = url_paste.replace( '\r', '' ).split( '\n' ) for line in url_paste: line = line.rstrip( '\r\n' ) if line: - last_dataset_created = add_file( urllib.urlopen( line ), - line, - extension, - dbkey, - last_used_build, - roles, - info="uploaded url", - space_to_tab=space_to_tab ) - created_datasets.append( last_dataset_created ) + created_lfda = add_file( urllib.urlopen( line ), + line, + extension, + dbkey, + last_used_build, + roles, + info="uploaded url", + space_to_tab=space_to_tab ) + created_lfda_ids = '%s,%s' % ( created_lfda_ids, str( created_lfda.id ) ) else: is_valid = False for line in url_paste: @@ -917,14 +913,15 @@ class Admin( BaseController ): is_valid = True break if is_valid: - last_dataset_created = add_file( StringIO.StringIO( url_paste ), - 'Pasted Entry', - extension, - dbkey, - last_used_build, - roles, - info="pasted entry", - space_to_tab=space_to_tab ) + created_lfda = add_file( StringIO.StringIO( url_paste ), + 'Pasted Entry', + extension, + dbkey, + last_used_build, + roles, + info="pasted entry", + space_to_tab=space_to_tab ) + created_lfda_ids = '%s,%s' % ( created_lfda_ids, str( created_lfda.id ) ) elif server_dir not in [ None, "", "None" ]: full_dir = os.path.join( trans.app.config.library_import_dir, server_dir ) try: @@ -935,31 +932,23 @@ class Admin( BaseController ): full_file = os.path.join( full_dir, file ) if not os.path.isfile( full_file ): continue - last_dataset_created = add_file( open( full_file, 'rb' ), - file, - extension, - dbkey, - last_used_build, - roles, - info="imported file", - space_to_tab=space_to_tab ) - created_datasets.append( last_dataset_created ) - if len( created_datasets ) > 1: - trans.response.send_redirect( web.url_for( - action = 'library_browser', - msg = "%i new datasets added to the library. Click here if you'd like to edit the permissions on these datasets." % ( - len( created_datasets ), - web.url_for( action='dataset', id=",".join( [ str(d.id) for d in created_datasets ] ) ) - ) - ) ) - elif last_dataset_created is not None: - trans.response.send_redirect( web.url_for( - action = 'library_browser', - msg = "New dataset added to the library. Click here if you'd like to edit the permissions or attributes on this dataset." % - web.url_for( action='dataset', id=last_dataset_created.id ) - ) ) + created_lfda = add_file( open( full_file, 'rb' ), + file, + extension, + dbkey, + last_used_build, + roles, + info="imported file", + space_to_tab=space_to_tab ) + created_lfda_ids = '%s,%s' % ( created_lfda_ids, str( created_lfda.id ) ) + if created_lfda_ids: + created_lfda_ids = created_lfda_ids.lstrip( ',' ) + total_added = len( created_lfda_ids.split( ',' ) ) + msg = "%i new datasets added to the library ( each is selected below ). Click the Go button at the bottom of this page to edit the permissions on these datasets." % total_added + trans.response.send_redirect( web.url_for( action='library_browser', created_lfda_ids=created_lfda_ids, msg=msg, messagetype='done' ) ) else: - return trans.show_error_message( 'Upload failed' ) + msg = "Upload failed" + trans.response.send_redirect( web.url_for( action='library_browser', created_lfda_ids=created_lfda_ids, msg=msg, messagetype='error' ) ) # No dataset(s) specified, display upload form elif not id: @@ -971,17 +960,18 @@ class Admin( BaseController ): yield build_name, dbkey, ( dbkey==last_used_build ) dbkeys = get_dbkey_options( last_used_build ) # Send list of roles to the form so the dataset can be associated with 1 or more of them. - roles = trans.app.model.Role.query().order_by( trans.app.model.Role.c.name ).all() + roles = trans.app.model.Role.filter( trans.app.model.Role.c.type != trans.app.model.Role.types.PRIVATE ).order_by( trans.app.model.Role.c.name ).all() return trans.fill_template( '/admin/library/new_dataset.mako', folder_id=folder_id, file_formats=file_formats, dbkeys=dbkeys, last_used_build=last_used_build, roles=roles, - msg=msg ) + msg=msg, + messagetype=messagetype ) else: if id.count( ',' ): - ids = id.split(',') + ids = id.split( ',' ) id = None else: ids = None @@ -989,7 +979,8 @@ class Admin( BaseController ): if id: lda = trans.app.model.LibraryFolderDatasetAssociation.get( id ) if not lda: - return trans.show_error_message( "Invalid dataset specified, id: %s" %str( id ) ) + msg = "Invalid dataset specified, id: %s" %str( id ) + return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) ) # Copied from edit attributes for 'regular' datasets with some additions p = util.Params(kwd, safe=False) @@ -997,7 +988,7 @@ class Admin( BaseController ): # The user clicked the Save button on the 'Associate With Roles' form permissions = {} for k, v in trans.app.model.Dataset.permitted_actions.items(): - in_roles = [ trans.app.model.Role.get( x ) for x in listify( p.get( k + '_in', [] ) ) ] + in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( p.get( k + '_in', [] ) ) ] permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles trans.app.security_agent.set_dataset_permissions( lda.dataset, permissions ) lda.dataset.refresh() @@ -1023,7 +1014,8 @@ class Admin( BaseController ): lda.metadata.dbkey = dbkey lda.datatype.after_edit( lda ) trans.app.model.flush() - return trans.show_ok_message( "Attributes updated" ) + msg = 'Attributes updated for dataset %s' % lda.name + return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='done' ) ) elif p.detect: # The user clicked the Auto-detect button on the 'Edit Attributes' form for name, spec in lda.datatype.metadata_spec.items(): @@ -1034,11 +1026,14 @@ class Admin( BaseController ): lda.datatype.set_meta( lda ) lda.datatype.after_edit( lda ) trans.app.model.flush() - return trans.show_ok_message( "Attributes updated" ) + msg = 'Attributes updated for dataset %s' % lda.name + return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='done' ) ) elif p.delete: + # The user selected the "Remove this dataset from the library" pop-up menu option lda.deleted = True lda.flush() - trans.response.send_redirect( web.url_for( action='library_browser' ) ) + msg = 'Dataset %s has been removed from this library' % lda.name + trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='done' ) ) lda.datatype.before_edit( lda ) if "dbkey" in lda.datatype.metadata_spec and not lda.metadata.dbkey: # Copy dbkey into metadata, for backwards compatability @@ -1055,67 +1050,82 @@ class Admin( BaseController ): dataset=lda, datatypes=ldatatypes, err=None, - msg=msg ) - # multiple ids specfied, display multi permission form + msg=msg, + messagetype=messagetype ) + # multiple ids specfied, display permission form for each one... elif ids: - ldas = [] + lfdas = [] for id in [ int( id ) for id in ids ]: - lda = trans.app.model.LibraryFolderDatasetAssociation.get( id ) - if lda is None: - return trans.show_error_message( 'You specified an invalid dataset' ) - ldas.append( lda ) - if len( ldas ) < 2: - return trans.show_error_message( 'You must specify at least two datasets to modify permissions on' ) + lfda = trans.app.model.LibraryFolderDatasetAssociation.get( id ) + if lfda is None: + msg = 'You specified an invalid dataset id: %s' %str( id ) + trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) ) + lfdas.append( lfda ) + if len( lfdas ) < 2: + msg = 'You must specify at least two datasets on which to modify permissions, ids you sent: %s' % str( ids ) + trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) ) if 'update_roles' in kwd: - p = util.Params( kwd ) + #p = util.Params( kwd ) permissions = {} for k, v in trans.app.model.Dataset.permitted_actions.items(): - in_roles = [ trans.app.model.Role.get( x ) for x in listify( p.get( k + '_in', [] ) ) ] + in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( params.get( k + '_in', [] ) ) ] permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles - for lda in ldas: - trans.app.security_agent.set_dataset_permissions( lda.dataset, permissions ) - lda.dataset.refresh() - # Ensure that the permissions across all datasets are identical. Otherwise, we can't update together. + for lfda in lfdas: + trans.app.security_agent.set_dataset_permissions( lfda.dataset, permissions ) + lfda.dataset.refresh() + msg = 'Permissions and roles have been updated on %d datasets' % len( lfdas ) + trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='done' ) ) + # Ensure that the permissions across all datasets are identical. Otherwise, we can't update together. tmp = [] - for lda in ldas: - perms = trans.app.security_agent.get_dataset_permissions( lda.dataset ) + for lfda in lfdas: + perms = trans.app.security_agent.get_dataset_permissions( lfda.dataset ) if perms not in tmp: tmp.append( perms ) if len( tmp ) != 1: - return trans.show_error_message( "The datasets you selected do not have identical permissions, so they can not be updated together" ) + msg = 'The datasets you selected do not have identical permissions, so they can not be updated together' + trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) ) else: - return trans.fill_template( "/admin/library/dataset.mako", - dataset=ldas ) + return trans.fill_template( "/admin/library/dataset.mako", dataset=lfdas ) @web.expose def add_dataset_to_folder_from_history( self, trans, ids="", folder_id=None, **kwd ): - if not isinstance( ids, list ): - if ids: - ids = ids.split( "," ) - else: - ids = [] + if not self.user_is_admin( trans ): + return trans.show_error_message( no_privilege_msg ) try: folder = trans.app.model.LibraryFolder.get( folder_id ) except: - folder = None - if folder is None: - return trans.show_error_message( "You must provide a valid target folder." ) - error_msg = ok_msg = "" - dataset_names = [] - if ids: - for data_id in ids: - data = trans.app.model.HistoryDatasetAssociation.get( data_id ) - if data: - data.to_library_dataset_folder_association( target_folder = folder ) - dataset_names.append( data.name ) + msg = "Invalid folder id: %s" % str( folder_id ) + return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) ) + params = util.Params( kwd ) + msg = params.get( 'msg', None ) + messagetype = params.get( 'messagetype', 'done' ) + # See if the current history is empty + history=trans.get_history() + if not history.active_datasets: + msg = 'Your current history is empty' + return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) ) + if params.get( 'add_dataset_from_history_button', False ): + if not isinstance( ids, list ): + if ids: + ids = ids.split( "," ) else: - error_msg += "A requested dataset (%s) was invalid. " % ( data_id ) - if dataset_names: - ok_msg = "Added datasets (%s) to the library folder." % ( ", ".join( dataset_names ) ) - return trans.fill_template( "/admin/library/add_dataset_from_history.mako", - history=trans.get_history(), - folder=folder, - ok_msg=ok_msg, - error_msg=error_msg ) + ids = [] + dataset_names = [] + if ids: + for data_id in ids: + data = trans.app.model.HistoryDatasetAssociation.get( data_id ) + if data: + data.to_library_dataset_folder_association( target_folder = folder ) + dataset_names.append( data.name ) + else: + msg = "The requested dataset id '%s' is invalid" % str( data_id ) + return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) ) + if dataset_names: + msg = "Added the following datasets to the library folder: %s" % ( ", ".join( dataset_names ) ) + return trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='done' ) ) + else: + msg = 'Select at least one dataset from the list' + messagetype = 'error' + return trans.fill_template( "/admin/library/add_dataset_from_history.mako", history=history, folder=folder, msg=msg, messagetype=messagetype ) def check_gzip( self, temp_name ): """ @@ -1134,106 +1144,53 @@ class Admin( BaseController ): # return( True, False ) return ( True, True ) @web.expose + @web.require_admin def datasets( self, trans, **kwd ): # This method is used by the select list labeled "Perform action on selected datasets" on the admin library browser. - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) msg = params.msg + messagetype = params.get( 'messagetype', 'done' ) if params.get( 'action_on_datasets_button', False ): if not params.dataset_ids: msg = "At least one dataset must be selected for %s" % params.action - trans.response.send_redirect( web.url_for( action='library_browser', msg=msg ) ) - dataset_ids = listify( params.dataset_ids ) + trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) ) + dataset_ids = util.listify( params.dataset_ids ) if params.action == 'edit': - trans.response.send_redirect( web.url_for( action='dataset', id=",".join( dataset_ids ) ) ) + trans.response.send_redirect( web.url_for( action='dataset', id=",".join( dataset_ids ), msg=msg, messagetype=messagetype ) ) elif params.action == 'delete': for id in dataset_ids: lfda = trans.app.model.LibraryFolderDatasetAssociation.get( id ) lfda.deleted = True lfda.flush() msg = "The selected datasets have been removed from this library" - trans.response.send_redirect( web.url_for( action='library_browser', msg=msg ) ) + trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='done' ) ) else: msg = "Action '%s' is not yet implemented" % str( params.action ) - trans.response.send_redirect( web.url_for( action='library_browser', msg=msg ) ) + trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype='error' ) ) else: - trans.response.send_redirect( web.url_for( action='library_browser', msg=msg ) ) + trans.response.send_redirect( web.url_for( action='library_browser', msg=msg, messagetype=messagetype ) ) @web.expose + @web.require_admin def delete_dataset( self, trans, id=None, **kwd): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) if id: # id is a LibraryFolderDatasetAssociation.id - library_folder_dataset_assoc = trans.app.model.LibraryFolderDatasetAssociation.get( id ) - self._delete_dataset( library_folder_dataset_assoc ) - trans.log_event( "Dataset id %s deleted from library folder id %s" % ( str( id ), str( library_folder_dataset_assoc.folder.id ) ) ) - trans.response.send_redirect( web.url_for( action = 'folder', id = library_folder_dataset_assoc.folder.id, msg = 'The dataset was deleted from the folder' ) ) - return trans.show_error_message( "You did not specify a dataset to delete." ) - - def _delete_dataset( self, library_folder_dataset_assoc ): - #dataset = library_folder_dataset_assoc.dataset - # TODO: assuming 1 to 1 mapping between Dataset -> LibraryFolders ( i.e., is can the same - # dataset record be shared across LibraryFolders? - # Confirm that things should be deleted as follows - - ### Deleting the base dataset will delete datasets that exist in user's histories - ### ( LDA.dataset == HDA.dataset ) - ### Shouldn't this be a separate option? - ### For Now, I am commenting out logic that acts on the dataset directly - ### -- Dan: - - - # Delete the LibraryFolderDatasetAssociation - library_folder_dataset_assoc.deleted = True - library_folder_dataset_assoc.flush() - - @web.expose - def delete_folder( self, trans, id=None, **kwd): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) - if id: - if 'confirm' not in kwd: - return trans.show_warn_message( 'Click here to confirm folder deletion.' % web.url_for( action = 'delete_folder', id = id, confirm=True ) ) - # id is a LibraryFolder.id - folder = trans.app.model.LibraryFolder.get( id ) - self._delete_folder( folder ) - - trans.log_event( "Folder id %s deleted." % id ) - - if folder.library_root: - trans.response.send_redirect( web.url_for( action = 'library', id = folder.library_root[0].id, msg = 'You have deleted the root folder.' ) ) - trans.response.send_redirect( web.url_for( action = 'folder', id = folder.parent_id, msg = 'The folder was deleted.' ) ) - return trans.show_error_message( "You did not specify a folder to delete." ) - - def _delete_folder( self, folder ): - for lfda in folder.active_datasets: - self._delete_dataset( lfda ) - for folder in folder.active_folders: - self._delete_folder( folder ) - folder.deleted = True - folder.flush() - - @web.expose - def delete_library( self, trans, id=None, **kwd): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) - if id: - if 'confirm' not in kwd: - return trans.show_warn_message( 'Click here to confirm library deletion.' % web.url_for( action = 'delete_library', id = id, confirm=True ) ) - # id is a LibraryFolder.id - library = trans.app.model.Library.get( id ) - self._delete_folder( library.root_folder ) - library.deleted = True - library.flush() - trans.log_event( "Library id %s deleted." % id ) - trans.response.send_redirect( web.url_for( action = 'libraries', msg = 'You have deleted the library %s.' % library.id ) ) - return trans.show_error_message( "You did not specify a library to delete." ) + lfda = trans.app.model.LibraryFolderDatasetAssociation.get( id ) + lfda.deleted = True + lfda.flush() + msg = "Dataset %s was deleted from library folder %s" % ( lfda.name, lfda.folder.name ) + trans.response.send_redirect( web.url_for( action='folder', + id=str( lfda.folder.id ), + msg=msg, + messagetype='done' ) ) + msg = "You did not specify a dataset to delete." + return trans.response.send_redirect( web.url_for( action='folder', + id=str( lfda.folder.id ), + msg=msg, + messagetype='error' ) ) @web.expose + @web.require_admin def memdump( self, trans, ids = 'None', sorts = 'None', pages = 'None', new_id = None, new_sort = None, **kwd ): - if not self.user_is_admin( trans ): - return trans.show_error_message( no_privilege_msg ) if self.app.memdump is None: return trans.show_error_message( "Memdump is not enabled (set use_memdump = True in universe_wsgi.ini)" ) heap = self.app.memdump.get() @@ -1271,14 +1228,3 @@ class Admin( BaseController ): breadcrumb += ".theone" heap = heap.theone return trans.fill_template( '/admin/memdump.mako', heap = heap, ids = ids, sorts = sorts, breadcrumb = breadcrumb, msg = msg ) - -def listify( item, return_none=False ): - """ - Since single params are not a single item list - """ - if item is None: - return [] - elif isinstance( item, list ): - return item - else: - return [ item ] diff --git a/lib/galaxy/web/controllers/root.py b/lib/galaxy/web/controllers/root.py index eb6ca6f4b90..5d7949ed347 100644 --- a/lib/galaxy/web/controllers/root.py +++ b/lib/galaxy/web/controllers/root.py @@ -25,7 +25,10 @@ class RootController( BaseController ): @web.expose def tool_menu( self, trans ): - return trans.fill_template('/root/tool_menu.mako', toolbox=self.get_toolbox() ) + if trans.app.config.require_login and not trans.user: + return trans.fill_template( '/no_access.mako', message = 'Please log in to access Galaxy tools.' ) + else: + return trans.fill_template('/root/tool_menu.mako', toolbox=self.get_toolbox() ) @web.expose def tool_help( self, trans, id ): @@ -51,6 +54,8 @@ class RootController( BaseController ): NOTE: No longer accepts "id" or "template" options for security reasons. """ history = trans.get_history() + if trans.app.config.require_login and not trans.user: + return trans.fill_template( '/no_access.mako', message = 'Please log in to access Galaxy histories.' ) if as_xml: trans.response.set_content_type('text/xml') return trans.fill_template_mako( "root/history_as_xml.mako", history=history ) diff --git a/lib/galaxy/web/controllers/user.py b/lib/galaxy/web/controllers/user.py index 075474869b1..695bda82dd1 100644 --- a/lib/galaxy/web/controllers/user.py +++ b/lib/galaxy/web/controllers/user.py @@ -9,6 +9,17 @@ from random import choice log = logging.getLogger( __name__ ) +require_login_template = """ +

Welcome to Galaxy

+ +

+ This installation of Galaxy has been configured such that only users who are logged in may use it.%s +

+

+""" +require_login_nocreation_template = require_login_template % "" +require_login_creation_template = require_login_template % " If you don't already have an account, you may create one." + class User( BaseController ): @web.expose @@ -75,6 +86,10 @@ class User( BaseController ): m0 = trans.app.memory_usage.memory() email_error = password_error = None # Attempt login + if trans.app.config.require_login: + refresh_frames = [ 'masthead', 'history', 'tools' ] + else: + refresh_frames = [ 'masthead', 'history' ] if email or password: user = trans.app.model.User.filter_by( email=email ).first() if not user: @@ -87,33 +102,50 @@ class User( BaseController ): else: trans.handle_user_login( user ) trans.log_event( "User logged in" ) - return trans.show_ok_message( "Now logged in as " + user.email, refresh_frames=['masthead', 'history'] ) + return trans.show_ok_message( "Now logged in as " + user.email, refresh_frames=refresh_frames ) if trans.app.memory_usage: m1 = trans.app.memory_usage.memory( m0, pretty=True ) log.info( "End of user/login, memory used increased by %s" % m1 ) - return trans.show_form( - web.FormBuilder( web.url_for(), "Login", submit_text="Login" ) - .add_text( "email", "Email address", value=email, error=email_error ) + form = web.FormBuilder( web.url_for(), "Login", submit_text="Login" ) \ + .add_text( "email", "Email address", value=email, error=email_error ) \ .add_password( "password", "Password", value='', error=password_error, - help="Forgot password? Reset here" % web.url_for( action='reset_password' ) ) ) + help="Forgot password? Reset here" % web.url_for( action='reset_password' ) ) + if trans.app.config.require_login: + if trans.app.config.allow_user_creation: + return trans.show_form( form, header = require_login_creation_template % web.url_for( action = 'create' ) ) + else: + return trans.show_form( form, header = require_login_nocreation_template ) + else: + return trans.show_form( form ) + @web.expose def logout( self, trans ): if trans.app.memory_usage: # Keep track of memory usage m0 = trans.app.memory_usage.memory() + if trans.app.config.require_login: + refresh_frames = [ 'masthead', 'history', 'tools' ] + else: + refresh_frames = [ 'masthead', 'history' ] # Since logging an event requires a session, we'll log prior to ending the session trans.log_event( "User logged out" ) trans.handle_user_logout() if trans.app.memory_usage: m1 = trans.app.memory_usage.memory( m0, pretty=True ) log.info( "End of user/logout, memory used increased by %s" % m1 ) - return trans.show_ok_message( "You are no longer logged in", refresh_frames=['masthead', 'history'] ) + return trans.show_ok_message( "You are no longer logged in.", refresh_frames=refresh_frames ) @web.expose - def create( self, trans, email='', password='', confirm='',subscribe=False ): + def create( self, trans, email='', password='', confirm='', subscribe=False ): if trans.app.memory_usage: # Keep track of memory usage m0 = trans.app.memory_usage.memory() + if trans.app.config.require_login: + refresh_frames = [ 'masthead', 'history', 'tools' ] + else: + refresh_frames = [ 'masthead', 'history' ] + if not trans.app.config.allow_user_creation and not trans.user_is_admin(): + return trans.show_error_message( 'User registration is disabled. Please contact your local Galaxy administrator for an account.' ) email_error = password_error = confirm_error = None if email: if len( email ) == 0 or "@" not in email or "." not in email: @@ -130,29 +162,36 @@ class User( BaseController ): user = trans.app.model.User( email=email ) user.set_password_cleartext( password ) user.flush() - trans.app.security_agent.setup_new_user( user ) - trans.handle_user_login( user ) - trans.log_event( "User created a new account" ) - trans.log_event( "User logged in" ) + if trans.user_is_admin(): + trans.app.security_agent.create_private_user_role( user ) + trans.app.security_agent.user_set_default_permissions( user ) + trans.log_event( "Admin created a new account" ) + msg = 'Created account ' + user.email + else: + trans.app.security_agent.setup_new_user( user ) + trans.handle_user_login( user ) + trans.log_event( "User created a new account" ) + trans.log_event( "User logged in" ) + msg = 'Now logged in as ' + user.email #subscribe user to email list if subscribe: mail = os.popen("%s -t" % trans.app.config.sendmail_path, 'w') mail.write("To: %s\nFrom: %s\nSubject: Join Mailing List\n\nJoin Mailing list." % (trans.app.config.mailing_join_addr,email) ) if mail.close(): - return trans.show_warn_message( "Now logged in as " + user.email+". However, subscribing to the mailing list has failed.", refresh_frames=['masthead', 'history'] ) + return trans.show_warn_message( msg + ". However, subscribing to the mailing list has failed.", refresh_frames=refresh_frames ) if trans.app.memory_usage: m1 = trans.app.memory_usage.memory( m0, pretty=True ) log.info( "End of user/create, memory used increased by %s" % m1 ) - return trans.show_ok_message( "Now logged in as " + user.email, refresh_frames=['masthead', 'history'] ) + return trans.show_ok_message( msg, refresh_frames=refresh_frames ) return trans.show_form( web.FormBuilder( web.url_for(), "Create account", submit_text="Create" ) .add_text( "email", "Email address", value=email, error=email_error ) - .add_password( "password", "Password", value='', error=password_error ) - .add_password( "confirm", "Confirm password", value='', error=confirm_error ) + .add_password( "password", "Password", value='', error=password_error ) + .add_password( "confirm", "Confirm password", value='', error=confirm_error ) .add_input( "checkbox","Subscribe To Mailing List","subscribe", value='subscribe' ) ) @web.expose - def reset_password(self, trans, email=None, **kwd): + def reset_password( self, trans, email=None, **kwd ): error = '' reset_user = trans.app.model.User.filter_by( email=email ).first() user = trans.get_user() @@ -167,11 +206,11 @@ class User( BaseController ): mail = os.popen("%s -t" % trans.app.config.sendmail_path, 'w') mail.write("To: %s\nFrom: no-reply@%s\nSubject: Galaxy Password Reset\n\nYour password has been reset to \"%s\" (no quotes)." % (email, trans.request.remote_addr, new_pass) ) if mail.close(): - return trans.show_ok_message( "Failed to reset password! If this problem persist, submit a bug report.") + return trans.show_error_message( 'Failed to reset password. If this problem persists, please submit a bug report.' ) reset_user.set_password_cleartext( new_pass ) reset_user.flush() trans.log_event( "User reset password: %s" % email ) - return trans.show_ok_message( "Password has been reset and emailed to: %s." % email) + return trans.show_ok_message( "Password has been reset and emailed to: %s. Click here to return to the login form." % ( email, web.url_for( action='login' ) ) ) elif email != None: error = "The specified user does not exist" return trans.show_form( diff --git a/lib/galaxy/web/framework/__init__.py b/lib/galaxy/web/framework/__init__.py index c415c9c023c..ff51ec616c1 100644 --- a/lib/galaxy/web/framework/__init__.py +++ b/lib/galaxy/web/framework/__init__.py @@ -60,6 +60,19 @@ def require_login( verb="perform this action" ): return decorator return argcatcher +def require_admin( func ): + def decorator( self, trans, *args, **kwargs ): + admin_users = trans.app.config.get( "admin_users", "" ).split( "," ) + if not admin_users: + return trans.show_error_message( "You must be an administrator to access this feature, and no administrators are set in the Galaxy configuration." ) + user = trans.get_user() + if not user: + return trans.show_error_message( "You must be an administrator to access this feature, and currently you are not logged in." ) + if not user.email in admin_users: + return trans.show_error_message( "You must be an administrator to access this feature." ) + return func( self, trans, *args, **kwargs ) + return decorator + NOT_SET = object() class MessageException( Exception ): @@ -117,6 +130,8 @@ class UniverseWebTransaction( base.DefaultWebTransaction ): self.workflow_building_mode = False # Always have a valid galaxy session self.__ensure_valid_session() + if self.app.config.require_login: + self.__ensure_logged_in_user( environ ) @property def sa_session( self ): """ @@ -240,6 +255,18 @@ class UniverseWebTransaction( base.DefaultWebTransaction ): if prev_galaxy_session: objects_to_flush.append( prev_galaxy_session ) sa_session.flush( objects_to_flush ) + def __ensure_logged_in_user( self, environ ): + allowed_paths = ( + url_for( controller='root', action='index' ), + url_for( controller='root', action='tool_menu' ), + url_for( controller='root', action='masthead' ), + url_for( controller='root', action='history' ), + url_for( controller='user', action='login' ), + url_for( controller='user', action='create' ), + url_for( controller='user', action='reset_password' ), + ) + if self.galaxy_session.user is None and environ['PATH_INFO'] not in allowed_paths: + self.response.send_redirect( url_for( controller='root', action='index' ) ) def __create_new_session( self, prev_galaxy_session=None, user_for_new_session=None ): """ Create a new GalaxySession for this request, possibly with a connection @@ -369,7 +396,13 @@ class UniverseWebTransaction( base.DefaultWebTransaction ): self.galaxy_session.user = user self.sa_session.flush( [ self.galaxy_session ] ) user = property( get_user, set_user ) - + + def user_is_admin( self ): + admin_users = self.app.config.get( "admin_users", "" ).split( "," ) + if self.user and admin_users and self.user.email in admin_users: + return True + return False + def get_toolbox(self): """Returns the application toolbox""" return self.app.toolbox @@ -415,12 +448,12 @@ class UniverseWebTransaction( base.DefaultWebTransaction ): Convenience method for displaying an warn message. See `show_message`. """ return self.show_message( message, 'warning', refresh_frames ) - def show_form( self, form ): + def show_form( self, form, header=None ): """ Convenience method for displaying a simple page with a single HTML form. """ - return self.fill_template( "form.mako", form=form ) + return self.fill_template( "form.mako", form=form, header=header ) def fill_template(self, filename, **kwargs): """ Fill in a template, putting any keyword arguments on the context. diff --git a/lib/galaxy/web/framework/base.py b/lib/galaxy/web/framework/base.py index 6dff0719d2a..5bef6415223 100644 --- a/lib/galaxy/web/framework/base.py +++ b/lib/galaxy/web/framework/base.py @@ -84,15 +84,19 @@ class WebApplication( object ): friendly objects, finds the appropriate method to handle the request and calls it. """ - # Setup the transaction - trans = self.transaction_factory( environ ) # Map url using routes - path_info = trans.request.path_info + path_info = environ.get( 'PATH_INFO', '' ) map = self.mapper.match( path_info ) if map == None: raise httpexceptions.HTTPNotFound( "No route for " + path_info ) - # Save the complete mapper dict, we pop things off so they don't get passed down - raw_map = dict( map ) + # Setup routes + rc = routes.request_config() + rc.mapper = self.mapper + rc.mapper_dict = map + rc.environ = environ + # Setup the transaction + trans = self.transaction_factory( environ ) + rc.redirect = trans.response.send_redirect # Get the controller class controller_name = map.pop( 'controller', None ) controller = self.controllers.get( controller_name, None ) @@ -111,12 +115,6 @@ class WebApplication( object ): # Is the method callable if not callable( method ): raise httpexceptions.HTTPNotFound( "Action not callable for " + path_info ) - # Setup routes - rc = routes.request_config() - rc.mapper = self.mapper - rc.mapper_dict = raw_map - rc.environ = environ - rc.redirect = trans.response.send_redirect # Combine mapper args and query string / form args and call kwargs = trans.request.params.mixed() kwargs.update( map ) @@ -336,4 +334,4 @@ def flatten( seq ): for y in flatten( x, encoding ): yield y else: - yield x \ No newline at end of file + yield x diff --git a/static/scripts/galaxy.panels.js b/static/scripts/galaxy.panels.js index 7463991fbf9..efe2ccb0a2a 100644 --- a/static/scripts/galaxy.panels.js +++ b/static/scripts/galaxy.panels.js @@ -79,7 +79,12 @@ function make_left_panel( panel_el, center_el, border_el ) { } } ).find( "div" ).show();; - + var force_panel = function( op ) { + if ( ( hidden && op == 'show' ) || ( ! hidden && op == 'hide' ) ) { + toggle(); + } + } + return { force_panel: force_panel }; }; function make_right_panel( panel_el, center_el, border_el ) { @@ -173,7 +178,12 @@ function make_right_panel( panel_el, center_el, border_el ) { } } ).find( "div" ).show(); - return { handle_minwidth_hint: handle_minwidth_hint }; + var force_panel = function( op ) { + if ( ( hidden && op == 'show' ) || ( ! hidden && op == 'hide' ) ) { + toggle(); + } + } + return { handle_minwidth_hint: handle_minwidth_hint, force_panel: force_panel }; }; // Modal dialog boxes @@ -234,4 +244,4 @@ function make_popupmenu( button_element, options ) { } ); }; $( button_element ).click( click ); -}; \ No newline at end of file +}; diff --git a/static/scripts/packed/galaxy.panels.js b/static/scripts/packed/galaxy.panels.js index 241d997a112..fd71a3f43d6 100644 --- a/static/scripts/packed/galaxy.panels.js +++ b/static/scripts/packed/galaxy.panels.js @@ -1 +1 @@ -var hidden_width=7;var border_tweak=9;var jq=jQuery;function ensure_dd_helper(){if(jq("#DD-helper").length==0){$("

").css({background:"white",opacity:0,zIndex:9000,position:"absolute",top:0,left:0,width:"100%",height:"100%"}).appendTo("body").hide()}}function make_left_panel(E,A,B){var D=false;var C=null;resize=function(F){var G=F;if(F<0){F=0}jq(E).css("width",F);jq(B).css("left",G);jq(A).css("left",F+7);if(document.recalc){document.recalc()}};toggle=function(){if(D){jq(B).removeClass("hover");jq(B).animate({left:C},"fast");jq(E).css("left",-C).show().animate({left:0},"fast",function(){resize(C);jq(B).removeClass("hidden")});D=false}else{C=jq(B).position().left;jq(A).css("left",hidden_width);if(document.recalc){document.recalc()}jq(B).removeClass("hover");jq(E).animate({left:-C},"fast");jq(B).animate({left:-1},"fast",function(){jq(this).addClass("hidden")});D=true}};jq(B).hover(function(){jq(this).addClass("hover")},function(){jq(this).removeClass("hover")}).draggable({start:function(F,G){jq("#DD-helper").show()},stop:function(F,G){jq("#DD-helper").hide();return false},drag:function(F,G){x=G.position.left;x=Math.min(400,Math.max(100,x));if(D){jq(E).css("left",0);jq(B).removeClass("hidden");D=false}resize(x);G.position.left=x;G.position.top=$(this).data("draggable").originalPosition.top},click:function(){toggle()}}).find("div").show()}function make_right_panel(A,E,G){var I=false;var F=false;var C=null;var D=function(J){jq(A).css("width",J);jq(E).css("right",J+9);jq(G).css("right",J).css("left","");if(document.recalc){document.recalc()}};var H=function(){if(I){jq(G).removeClass("hover");jq(G).animate({right:C},"fast");jq(A).css("right",-C).show().animate({right:0},"fast",function(){D(C);jq(G).removeClass("hidden")});I=false}else{C=jq(document).width()-jq(G).position().left-border_tweak;jq(E).css("right",hidden_width+1);if(document.recalc){document.recalc()}jq(G).removeClass("hover");jq(A).animate({right:-C},"fast");jq(G).animate({right:-1},"fast",function(){jq(this).addClass("hidden")});I=true}F=false};var B=function(J){var K=jq(E).width()-(I?C:0);if(K").text(F).click(G));A.append(" ")});A.show()}else{A.hide()}var A=$(".dialog-box").find(".extra_buttons").html("");if(C){$.each(C,function(F,G){A.append($("