From ed90f0f68364e197722394ee134eb93c584bd7a2 Mon Sep 17 00:00:00 2001 From: Greg Von Kuster Date: Fri, 28 Nov 2008 15:32:15 -0500 Subject: [PATCH] Fixes and new features for data security and libraries, requires db schema change and new config setting. - add ability for an admin to reset another user's password - add ability for an admin user to delete another user's account ( requires config setting to display feature ) - add ability for an admin user to manage deleted users ( undelete / purge ) - more code cleanup in the admin controller - several new functional tests and existing functional test improvements SQL commands to alter db schema: ALTER TABLE galaxy_user ADD COLUMN deleted BOOLEAN DEFAULT FALSE; CREATE INDEX ix_galaxy_user_deleted ON galaxy_user USING btree (deleted); ALTER TABLE galaxy_user ADD COLUMN purged BOOLEAN DEFAULT FALSE; CREATE INDEX ix_galaxy_user_purged ON galaxy_user USING btree (purged); New config setting: # Can an admin user delete user accounts? #allow_user_deletion = False --- lib/galaxy/config.py | 9 +- lib/galaxy/model/__init__.py | 3 + lib/galaxy/model/mapping.py | 4 +- lib/galaxy/web/controllers/admin.py | 356 +++++++++++----- lib/galaxy/web/controllers/root.py | 2 +- lib/galaxy/web/controllers/user.py | 22 +- lib/galaxy/web/controllers/workflow.py | 4 +- lib/galaxy/web/framework/__init__.py | 18 +- templates/admin/dataset_security/users.mako | 7 + templates/admin/library/browser.mako | 2 +- templates/admin/user/create.mako | 43 ++ templates/admin/user/deleted_users.mako | 91 +++++ templates/admin/user/reset_password.mako | 35 ++ test/base/twilltestcase.py | 115 ++++-- test/functional/__init__.py | 7 +- .../functional/test_security_and_libraries.py | 381 ++++++++++++++---- universe_wsgi.ini.sample | 3 + 17 files changed, 844 insertions(+), 258 deletions(-) create mode 100644 templates/admin/user/create.mako create mode 100644 templates/admin/user/deleted_users.mako create mode 100644 templates/admin/user/reset_password.mako diff --git a/lib/galaxy/config.py b/lib/galaxy/config.py index a8df982d53c..332c16a2491 100644 --- a/lib/galaxy/config.py +++ b/lib/galaxy/config.py @@ -40,6 +40,7 @@ class Configuration( object ): 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.allow_user_deletion = string_as_bool( kwargs.get( "allow_user_deletion", "False" ) ) 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" ) ) @@ -58,10 +59,10 @@ class Configuration( object ): self.pbs_dataset_server = kwargs.get('pbs_dataset_server', "" ) self.pbs_dataset_path = kwargs.get('pbs_dataset_path', "" ) self.pbs_stage_path = kwargs.get('pbs_stage_path', "" ) - self.use_heartbeat = string_as_bool( kwargs.get( 'use_heartbeat', False ) ) - self.use_memdump = string_as_bool( kwargs.get( 'use_memdump', False ) ) - self.log_memory_usage = string_as_bool( kwargs.get( 'log_memory_usage', False ) ) - self.log_events = string_as_bool( kwargs.get( 'log_events', False ) ) + self.use_heartbeat = string_as_bool( kwargs.get( 'use_heartbeat', 'False' ) ) + self.use_memdump = string_as_bool( kwargs.get( 'use_memdump', 'False' ) ) + self.log_memory_usage = string_as_bool( kwargs.get( 'log_memory_usage', 'False' ) ) + self.log_events = string_as_bool( kwargs.get( 'log_events', 'False' ) ) self.ucsc_display_sites = kwargs.get( 'ucsc_display_sites', "main,test,archaea" ).lower().split(",") self.gbrowse_display_sites = kwargs.get( 'gbrowse_display_sites', "wormbase,flybase,elegans" ).lower().split(",") self.brand = kwargs.get( 'brand', None ) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 5d6bf13d240..66fe0130b36 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -32,6 +32,8 @@ class User( object ): self.email = email self.password = password self.external = False + self.deleted = False + self.purged = False # Relationships self.histories = [] @@ -143,6 +145,7 @@ class Group( object ): permitted_actions = galaxy.security.get_permitted_actions( 'GROUP' ) def __init__( self, name = None ): self.name = name + self.deleted = False class UserGroupAssociation( object ): def __init__( self, user, group ): diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index 4696c0ca1ad..86aa72579cd 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -43,7 +43,9 @@ User.table = Table( "galaxy_user", metadata, Column( "update_time", DateTime, default=now, onupdate=now ), Column( "email", TrimmedString( 255 ), nullable=False ), Column( "password", TrimmedString( 40 ), nullable=False ), - Column( "external", Boolean, default=False ) ) + Column( "external", Boolean, default=False ), + Column( "deleted", Boolean, index=True, default=False ), + Column( "purged", Boolean, index=True, default=False ) ) History.table = Table( "history", metadata, Column( "id", Integer, primary_key=True), diff --git a/lib/galaxy/web/controllers/admin.py b/lib/galaxy/web/controllers/admin.py index dc72c913abe..832a0d4f74f 100644 --- a/lib/galaxy/web/controllers/admin.py +++ b/lib/galaxy/web/controllers/admin.py @@ -3,12 +3,7 @@ from galaxy import util, datatypes from galaxy.web.base.controller import * from galaxy.datatypes import sniff from galaxy.security import RBACAgent -import galaxy.model from galaxy.model.orm import * -from xml.sax.saxutils import escape, unescape -import pkg_resources -pkg_resources.require( "SQLAlchemy >= 0.4" ) -import sqlalchemy as sa import logging log = logging.getLogger( __name__ ) @@ -60,9 +55,9 @@ class Admin( BaseController ): 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() + users = trans.app.model.User.filter( trans.app.model.User.table.c.deleted==False ).order_by( trans.app.model.User.table.c.email ).all() groups = trans.app.model.Group.query() \ - .filter( galaxy.model.Group.table.c.deleted==False ) \ + .filter( trans.app.model.Group.table.c.deleted==False ) \ .order_by( trans.app.model.Group.table.c.name ) \ .all() return trans.fill_template( '/admin/dataset_security/role_create.mako', @@ -84,23 +79,21 @@ class Admin( BaseController ): trans.response.send_redirect( web.url_for( action='create_role', msg=msg, messagetype='error' ) ) else: # Create the role - role = galaxy.model.Role( name=name, - description=description, - type=trans.app.model.Role.types.ADMIN ) + role = trans.app.model.Role( name=name, description=description, type=trans.app.model.Role.types.ADMIN ) role.flush() # Add the users users = util.listify( params.users ) for user_id in users: - user = galaxy.model.User.get( user_id ) + user = trans.app.model.User.get( user_id ) # Create the UserRoleAssociation - ura = galaxy.model.UserRoleAssociation( user, role ) + ura = trans.app.model.UserRoleAssociation( user, role ) ura.flush() # Add the groups groups = util.listify( params.groups ) for group_id in groups: - group = galaxy.model.Group.get( group_id ) + group = trans.app.model.Group.get( group_id ) # Create the GroupRoleAssociation - gra = galaxy.model.GroupRoleAssociation( group, role ) + gra = trans.app.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( web.url_for( action='roles', msg=msg, messagetype='done' ) ) @@ -115,7 +108,7 @@ class Admin( BaseController ): out_users = [] in_groups = [] out_groups = [] - for user in trans.app.model.User.query().order_by( trans.app.model.User.table.c.email ).all(): + for user in trans.app.model.User.filter( trans.app.model.User.table.c.deleted==False ).order_by( trans.app.model.User.table.c.email ).all(): if user in [ x.user for x in role.users ]: in_users.append( ( user.id, user.email ) ) else: @@ -163,7 +156,7 @@ class Admin( BaseController ): @web.require_admin def role_members_edit( self, trans, **kwd ): params = util.Params( kwd ) - role = galaxy.model.Role.get( int( params.role_id ) ) + role = trans.app.model.Role.get( int( params.role_id ) ) 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 ) @@ -188,7 +181,7 @@ class Admin( BaseController ): @web.require_admin def mark_role_deleted( self, trans, **kwd ): params = util.Params( kwd ) - role = galaxy.model.Role.get( int( params.role_id ) ) + role = trans.app.model.Role.get( int( params.role_id ) ) role.deleted = True role.flush() msg = "The role has been marked as deleted." @@ -202,17 +195,17 @@ class Admin( BaseController ): # 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 = [] - roles = galaxy.model.Role.query() \ - .filter( galaxy.model.Role.table.c.deleted==True ) \ - .order_by( galaxy.model.Role.table.c.name ) \ + roles = trans.app.model.Role.query() \ + .filter( trans.app.model.Role.table.c.deleted==True ) \ + .order_by( trans.app.model.Role.table.c.name ) \ .all() for role in roles: groups = [] for gra in role.groups: - groups.append( galaxy.model.Group.get( gra.group_id ) ) + groups.append( trans.app.model.Group.get( gra.group_id ) ) users = [] for ura in role.users: - users.append( galaxy.model.User.get( ura.user_id ) ) + users.append( trans.app.model.User.get( ura.user_id ) ) roles_groups_users.append( ( role, groups, users ) ) return trans.fill_template( '/admin/dataset_security/deleted_roles.mako', roles_groups_users=roles_groups_users, @@ -222,7 +215,7 @@ class Admin( BaseController ): @web.require_admin def undelete_role( self, trans, **kwd ): params = util.Params( kwd ) - role = galaxy.model.Role.get( int( params.role_id ) ) + role = trans.app.model.Role.get( int( params.role_id ) ) role.deleted = False role.flush() msg = "The role has been marked as not deleted." @@ -230,8 +223,19 @@ class Admin( BaseController ): @web.expose @web.require_admin def purge_role( self, trans, **kwd ): + # This method should only be called for a Role that has previously been deleted. + # Purging a deleted Role deletes all of the following from the database: + # - UserRoleAssociations where role_id == Role.id + # - DefaultUserPermissions where role_id == Role.id + # - DefaultHistoryPermissions where role_id == Role.id + # - GroupRoleAssociations where role_id == Role.id + # - ActionDatasetRoleAssociations where role_id == Role.id params = util.Params( kwd ) - role = galaxy.model.Role.get( int( params.role_id ) ) + role = trans.app.model.Role.get( int( params.role_id ) ) + if not role.deleted: + # We should never reach here, but just in case there is a bug somewhere... + msg = "The role has not been deleted, so it cannot be purged." + trans.response.send_redirect( web.url_for( action='roles', msg=msg, messagetype='error' ) ) # Delete UserRoleAssociations for ura in role.users: user = trans.app.model.User.get( ura.user_id ) @@ -252,10 +256,12 @@ class Admin( BaseController ): for gra in role.groups: gra.delete() gra.flush() - # Delete the Role - role.delete() - role.flush() - msg = "The role has been purged from the database." + # Delete ActionDatasetRoleAssociations + for adra in role.actions: + adra.delete() + adra.flush() + msg = "The following have been purged from the database for the role: " + msg += "DefaultUserPermissions, DefaultHistoryPermissions, UserRoleAssociations, GroupRoleAssociations, ActionDatasetRoleAssociations." trans.response.send_redirect( web.url_for( action='deleted_roles', msg=msg, messagetype='done' ) ) # Galaxy Group Stuff @@ -268,17 +274,17 @@ class Admin( BaseController ): # 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 = [] - groups = galaxy.model.Group.query() \ - .filter( galaxy.model.Group.table.c.deleted==False ) \ - .order_by( galaxy.model.Group.table.c.name ) \ + groups = trans.app.model.Group.query() \ + .filter( trans.app.model.Group.table.c.deleted==False ) \ + .order_by( trans.app.model.Group.table.c.name ) \ .all() for group in groups: members = [] for uga in group.members: - members.append( galaxy.model.User.get( uga.user_id ) ) + members.append( trans.app.model.User.get( uga.user_id ) ) roles = [] for gra in group.roles: - roles.append( galaxy.model.Role.get( gra.role_id ) ) + roles.append( trans.app.model.Role.get( gra.role_id ) ) groups_members_roles.append( ( group, members, roles ) ) return trans.fill_template( '/admin/dataset_security/groups.mako', groups_members_roles=groups_members_roles, @@ -290,10 +296,10 @@ class Admin( BaseController ): 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() + users = trans.app.model.User.filter( trans.app.model.User.table.c.deleted==False ).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, - galaxy.model.Role.table.c.type != trans.app.model.Role.types.PRIVATE ) ) \ + .filter( and_( trans.app.model.Role.table.c.deleted == False, + trans.app.model.Role.table.c.type != trans.app.model.Role.types.PRIVATE ) ) \ .order_by( trans.app.model.Role.table.c.name ) \ .all() return trans.fill_template( '/admin/dataset_security/group_create.mako', @@ -314,14 +320,14 @@ class Admin( BaseController ): trans.response.send_redirect( web.url_for( action='create_group', msg=msg, messagetype='error' ) ) else: # Create the group - group = galaxy.model.Group( name ) + group = trans.app.model.Group( name ) group.flush() # Add the members members = util.listify( params.members ) for user_id in members: - user = galaxy.model.User.get( user_id ) + user = trans.app.model.User.get( user_id ) # Create the UserGroupAssociation - uga = galaxy.model.UserGroupAssociation( user, group ) + uga = trans.app.model.UserGroupAssociation( user, group ) uga.flush() # Add the roles roles = params.roles @@ -330,9 +336,9 @@ class Admin( BaseController ): elif roles is None: roles = [] for role_id in roles: - role = galaxy.model.Role.get( role_id ) + role = trans.app.model.Role.get( role_id ) # Create the GroupRoleAssociation - gra = galaxy.model.GroupRoleAssociation( group, role ) + gra = trans.app.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( web.url_for( action='groups', msg=msg, messagetype='done' ) ) @@ -342,14 +348,15 @@ class Admin( BaseController ): params = util.Params( kwd ) msg = params.msg messagetype = params.get( 'messagetype', 'done' ) - group = galaxy.model.Group.get( int( params.group_id ) ) + group = trans.app.model.Group.get( int( params.group_id ) ) members = [] for uga in group.members: - members.append ( galaxy.model.User.get( uga.user_id ) ) + members.append ( trans.app.model.User.get( uga.user_id ) ) + users = trans.app.model.User.filter( trans.app.model.User.table.c.deleted==False ).order_by( trans.app.model.User.table.c.email ).all() return trans.fill_template( '/admin/dataset_security/group_members_edit.mako', group=group, members=members, - users=galaxy.model.User.query().order_by( galaxy.model.User.table.c.email ).all(), + users=users, msg=msg, messagetype=messagetype ) @web.expose @@ -358,7 +365,7 @@ class Admin( BaseController ): params = util.Params( kwd ) group_id = int( params.group_id ) members = util.listify( params.members ) - group = galaxy.model.Group.get( group_id ) + group = trans.app.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, # we'll need to handle changes to the member list rather than the @@ -372,9 +379,9 @@ class Admin( BaseController ): uga.flush() # Then add all new members to the group for user_id in members: - user = galaxy.model.User.get( user_id ) + user = trans.app.model.User.get( user_id ) if user not in group.members: - uga = galaxy.model.UserGroupAssociation( user, group ) + uga = trans.app.model.UserGroupAssociation( user, group ) uga.flush() msg = "Group membership has been updated with a total of %s members" % len( members ) trans.response.send_redirect( web.url_for( action='groups', msg=msg, messagetype='done' ) ) @@ -386,14 +393,14 @@ class Admin( BaseController ): params = util.Params( kwd ) msg = params.msg messagetype = params.get( 'messagetype', 'done' ) - group = galaxy.model.Group.get( int( params.group_id ) ) + group = trans.app.model.Group.get( int( params.group_id ) ) group_roles = [] for gra in group.roles: - group_roles.append ( galaxy.model.Role.get( gra.role_id ) ) + group_roles.append ( trans.app.model.Role.get( gra.role_id ) ) return trans.fill_template( '/admin/dataset_security/group_roles_edit.mako', group=group, group_roles=group_roles, - roles=galaxy.model.Role.query().order_by( galaxy.model.Role.table.c.name ).all(), + roles=trans.app.model.Role.query().order_by( trans.app.model.Role.table.c.name ).all(), msg=msg, messagetype=messagetype ) @web.expose @@ -402,7 +409,7 @@ class Admin( BaseController ): params = util.Params( kwd ) group_id = int( params.group_id ) roles = util.listify( params.roles ) - group = galaxy.model.Group.get( group_id ) + group = trans.app.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, # we'll need to handle changes to the member list rather than the @@ -416,9 +423,9 @@ class Admin( BaseController ): gra.flush() # Then add all new roles to the group for role_id in roles: - role = galaxy.model.Role.get( role_id ) + role = trans.app.model.Role.get( role_id ) if role not in group.roles: - gra = galaxy.model.GroupRoleAssociation( group, role ) + gra = trans.app.model.GroupRoleAssociation( group, role ) gra.flush() msg = "Group updated with a total of %s associated roles" % len( roles ) trans.response.send_redirect( web.url_for( action='groups', msg=msg, messagetype='done' ) ) @@ -426,7 +433,7 @@ class Admin( BaseController ): @web.require_admin def mark_group_deleted( self, trans, **kwd ): params = util.Params( kwd ) - group = galaxy.model.Group.get( int( params.group_id ) ) + group = trans.app.model.Group.get( int( params.group_id ) ) group.deleted = True group.flush() msg = "The group has been marked as deleted." @@ -440,17 +447,17 @@ class Admin( BaseController ): # 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 = [] - groups = galaxy.model.Group.query() \ - .filter( galaxy.model.Group.table.c.deleted==True ) \ - .order_by( galaxy.model.Group.table.c.name ) \ + groups = trans.app.model.Group.query() \ + .filter( trans.app.model.Group.table.c.deleted==True ) \ + .order_by( trans.app.model.Group.table.c.name ) \ .all() for group in groups: members = [] for uga in group.members: - members.append( galaxy.model.User.get( uga.user_id ) ) + members.append( trans.app.model.User.get( uga.user_id ) ) roles = [] for gra in group.roles: - roles.append( galaxy.model.Role.get( gra.role_id ) ) + roles.append( trans.app.model.Role.get( gra.role_id ) ) groups_members_roles.append( ( group, members, roles ) ) return trans.fill_template( '/admin/dataset_security/deleted_groups.mako', groups_members_roles=groups_members_roles, @@ -460,7 +467,7 @@ class Admin( BaseController ): @web.require_admin def undelete_group( self, trans, **kwd ): params = util.Params( kwd ) - group = galaxy.model.Group.get( int( params.group_id ) ) + group = trans.app.model.Group.get( int( params.group_id ) ) group.deleted = False group.flush() msg = "The group has been marked as not deleted." @@ -468,8 +475,14 @@ class Admin( BaseController ): @web.expose @web.require_admin def purge_group( self, trans, **kwd ): + # This method should only be called for a Group that has previously been deleted. + # Purging a deleted Group simply deletes all UserGroupAssociations and GroupRoleAssociations. params = util.Params( kwd ) - group = galaxy.model.Group.get( int( params.group_id ) ) + group = trans.app.model.Group.get( int( params.group_id ) ) + if not group.deleted: + # We should never reach here, but just in case there is a bug somewhere... + msg = "The group has not been deleted, so it cannot be purged." + trans.response.send_redirect( web.url_for( action='groups', msg=msg, messagetype='error' ) ) # Delete UserGroupAssociations for uga in group.users: uga.delete() @@ -479,27 +492,40 @@ class Admin( BaseController ): gra.delete() gra.flush() # Delete the Group - group.delete() - group.flush() - msg = "The group has been purged from the database." + msg = "The following have been purged from the database for the group: UserGroupAssociations, GroupRoleAssociations." trans.response.send_redirect( web.url_for( action='deleted_groups', msg=msg, messagetype='done' ) ) # Galaxy User Stuff @web.expose @web.require_admin - def create_new_user( self, trans, email='', password='', confirm='', subscribe=False ): - email_error = password_error = confirm_error = None - if email: + def create_new_user( self, trans, **kwd ): + params = util.Params( kwd ) + msg = params.msg + email = '' + password = '' + confirm = '' + subscribe = False + messagetype = params.get( 'messagetype', 'done' ) + if 'user_create_button' in kwd: + if 'email' in kwd: + email = kwd[ 'email' ] + if 'password' in kwd: + password = kwd[ 'password' ] + if 'confirm' in kwd: + confirm = kwd[ 'confirm' ] + if 'subscribe' in kwd: + subscribe = kwd[ 'subscribe' ] + messagetype = 'error' if len( email ) == 0 or "@" not in email or "." not in email: - email_error = "Please enter a real email address" + msg = "Please enter a real email address" elif len( email) > 255: - email_error = "Email address exceeds maximum allowable length" - elif trans.app.model.User.filter_by( email=email ).first(): - email_error = "User with that email already exists" + msg = "Email address exceeds maximum allowable length" + elif trans.app.model.User.filter( trans.app.model.User.table.c.email==email ).first(): + msg = "User with that email already exists" elif len( password ) < 6: - password_error = "Please use a password of at least 6 characters" + msg = "Please use a password of at least 6 characters" elif password != confirm: - confirm_error = "Passwords do not match" + msg = "Passwords do not match" else: user = trans.app.model.User( email=email ) user.set_password_cleartext( password ) @@ -507,25 +533,147 @@ class Admin( BaseController ): trans.app.security_agent.create_private_user_role( user ) trans.app.security_agent.user_set_default_permissions( user, history=False, dataset=False ) trans.log_event( "Admin created a new account for user %s" % email ) - msg = 'Created new account' + msg = 'Created new user account' messagetype = 'done' #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 ) ) + mail.write( "To: %s\nFrom: %s\nSubject: Join Mailing List\n\nJoin Mailing list." % ( trans.app.config.mailing_join_addr, email ) ) if mail.close(): msg + ". However, subscribing to the mailing list has failed." messagetype = 'error' - trans.response.send_redirect( web.url_for( action='users', msg=msg, messagetype=messagetype ) ) - # TODO: make this a mako template - 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_input( "checkbox","Subscribe To Mailing List","subscribe", value='subscribe' ) ) - - + trans.response.send_redirect( web.url_for( action='users', msg=msg, messagetype=messagetype ) ) + return trans.fill_template( '/admin/user/create.mako', + msg=msg, + messagetype=messagetype, + email=email, + password=password, + confirm=confirm, + subscribe=subscribe ) + @web.expose + @web.require_admin + def reset_user_password( self, trans, **kwd ): + params = util.Params( kwd ) + msg = params.msg + user_id = int( params.user_id ) + user = trans.app.model.User.filter( trans.app.model.User.table.c.id==user_id ).first() + password = '' + confirm = '' + messagetype = params.get( 'messagetype', 'done' ) + if 'reset_user_password_button' in kwd: + if 'password' in kwd: + password = kwd[ 'password' ] + if 'confirm' in kwd: + confirm = kwd[ 'confirm' ] + messagetype = 'error' + if len( password ) < 6: + msg = "Please use a password of at least 6 characters" + elif password != confirm: + msg = "Passwords do not match" + else: + user.set_password_cleartext( password ) + user.flush() + trans.log_event( "Admin reset password for user %s" % user.email ) + msg = 'Password reset' + messagetype = 'done' + trans.response.send_redirect( web.url_for( action='users', msg=msg, messagetype=messagetype ) ) + return trans.fill_template( '/admin/user/reset_password.mako', + msg=msg, + messagetype=messagetype, + user=user, + password=password, + confirm=confirm ) + @web.expose + @web.require_admin + def mark_user_deleted( self, trans, **kwd ): + params = util.Params( kwd ) + msg = params.msg + messagetype = params.get( 'messagetype', 'done' ) + user = trans.app.model.User.get( int( params.user_id ) ) + user.deleted = True + user.flush() + msg = "The user has been marked as deleted." + trans.response.send_redirect( web.url_for( action='users', msg=msg, messagetype='done' ) ) + @web.expose + @web.require_admin + def undelete_user( self, trans, **kwd ): + params = util.Params( kwd ) + user = trans.app.model.User.get( int( params.user_id ) ) + user.deleted = False + user.flush() + msg = "The user has been marked as not deleted." + trans.response.send_redirect( web.url_for( action='users', msg=msg, messagetype='done' ) ) + @web.expose + @web.require_admin + def purge_user( self, trans, **kwd ): + # This method should only be called for a User that has previously been deleted. + # We keep the User in the database ( marked as purged ), and stuff associated + # with the user's private role in case we want the ability to unpurge the user + # some time in the future. + # Purging a deleted User deletes all of the following: + # - DefaultUserPermissions where user_id == User.id EXCEPT FOR THE PRIVATE ROLE + # - History where user_id = User.id + # - DefaultHistoryPermissions where history_id == History.id EXCEPT FOR THE PRIVATE ROLE + # - HistoryDatasetAssociation where history_id = History.id + # - Dataset where HistoryDatasetAssociation.dataset_id = Dataset.id + # - UserGroupAssociation where user_id == User.id + # - UserRoleAssociation where user_id == User.id EXCEPT FOR THE PRIVATE ROLE + # Purging Histories and Datasets must be handled via the cleanup_datasets.py script + params = util.Params( kwd ) + user = trans.app.model.User.get( int( params.user_id ) ) + if not user.deleted: + # We should never reach here, but just in case there is a bug somewhere... + msg = "The account has not been deleted, so it cannot be purged." + trans.response.send_redirect( web.url_for( action='users', msg=msg, messagetype='error' ) ) + private_role = trans.app.security_agent.get_private_user_role( user ) + # Delete DefaultUserPermissions EXCEPT FOR THE PRIVATE ROLE + for dup in user.default_permissions: + if dup.role_id != private_role.id: + dup.delete() + dup.flush() + # Delete History + for h in user.active_histories: + h.refresh() + # Delete DefaultHistoryPermissions EXCEPT FOR THE PRIVATE ROLE + for dp in h.default_permissions: + if dp.role_id != private_role.id: + dp.delete() + dp.flush() + for hda in h.active_datasets: + # Delete HistoryDatasetAssociation + d = trans.app.model.Dataset.get( hda.dataset_id ) + # Delete Dataset + if not d.deleted: + d.deleted = True + d.flush() + hda.deleted = True + hda.flush() + h.deleted = True + h.flush() + # Delete UserGroupAssociations + for uga in user.groups: + uga.delete() + uga.flush() + # Delete UserRoleAssociations EXCEPT FOR THE PRIVATE ROLE + for ura in user.roles: + if ura.role_id != private_role.id: + ura.delete() + ura.flush() + # Purge the user + user.purged = True + user.flush() + msg = "The user has been marked as purged." + trans.response.send_redirect( web.url_for( action='deleted_users', msg=msg, messagetype='done' ) ) + @web.expose + @web.require_admin + def deleted_users( self, trans, **kwd ): + params = util.Params( kwd ) + msg = params.msg + messagetype = params.get( 'messagetype', 'done' ) + users = trans.app.model.User.filter( and_( trans.app.model.User.table.c.deleted==True, trans.app.model.User.table.c.purged==False ) ) \ + .order_by( trans.app.model.User.table.c.email ) \ + .all() + return trans.fill_template( '/admin/user/deleted_users.mako', users=users, msg=msg, messagetype=messagetype ) @web.expose @web.require_admin def users( self, trans, **kwd ): @@ -535,24 +683,25 @@ class Admin( BaseController ): # 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 = [] - users = trans.app.model.User.query().order_by( trans.app.model.User.table.c.email ).all() + users = trans.app.model.User.filter( trans.app.model.User.table.c.deleted==False ).order_by( trans.app.model.User.table.c.email ).all() for user in users: groups = [] for uga in user.groups: - groups.append( galaxy.model.Group.get( uga.group_id ) ) + groups.append( trans.app.model.Group.get( uga.group_id ) ) roles = [] for ura in user.non_private_roles: - roles.append( galaxy.model.Role.get( ura.role_id ) ) + roles.append( trans.app.model.Role.get( ura.role_id ) ) users_groups_roles.append( ( user, groups, roles ) ) return trans.fill_template( '/admin/dataset_security/users.mako', users_groups_roles=users_groups_roles, + allow_user_deletion=trans.app.config.allow_user_deletion, msg=msg, messagetype=messagetype ) @web.expose @web.require_admin def user( self, trans, **kwd ): params = util.Params( kwd ) - user_id = params.user_id + user_id = int( params.user_id ) msg = params.msg messagetype = params.get( 'messagetype', 'done' ) user = trans.app.model.User.get( user_id ) @@ -560,7 +709,7 @@ class Admin( BaseController ): groups = trans.app.model.Group.query() \ .select_from( ( outerjoin( trans.app.model.Group, trans.app.model.UserGroupAssociation ) ) \ .outerjoin( trans.app.model.User ) ) \ - .filter( and_( trans.app.model.Group.deleted == False, trans.app.model.User.id == user_id ) ) \ + .filter( and_( trans.app.model.Group.deleted==False, trans.app.model.User.id==user_id ) ) \ .order_by( trans.app.model.Group.table.c.name ) \ .all() roles = user.all_roles() @@ -576,13 +725,13 @@ class Admin( BaseController ): params = util.Params( kwd ) msg = params.msg messagetype = params.get( 'messagetype', 'done' ) - user = galaxy.model.User.get( int( params.user_id ) ) + user = trans.app.model.User.get( int( params.user_id ) ) user_groups = [] for uga in user.groups: - user_groups.append ( galaxy.model.Group.get( uga.group_id ) ) - groups = galaxy.model.Group.query() \ - .filter( galaxy.model.Group.table.c.deleted==False ) \ - .order_by( galaxy.model.Group.table.c.name ) \ + user_groups.append ( trans.app.model.Group.get( uga.group_id ) ) + groups = trans.app.model.Group.query() \ + .filter( trans.app.model.Group.table.c.deleted==False ) \ + .order_by( trans.app.model.Group.table.c.name ) \ .all() return trans.fill_template( '/admin/dataset_security/user_groups_edit.mako', user=user, @@ -596,7 +745,7 @@ class Admin( BaseController ): params = util.Params( kwd ) user_id = int( params.user_id ) groups = util.listify( params.groups ) - user = galaxy.model.User.get( user_id ) + user = trans.app.model.User.get( user_id ) # First remove existing UserGroupAssociations that are not in the received groups param for uga in user.groups: if uga.group_id not in groups: @@ -605,9 +754,9 @@ class Admin( BaseController ): uga.flush() # Then add all new groups to the user for group_id in groups: - group = galaxy.model.Group.get( group_id ) + group = trans.app.model.Group.get( group_id ) if group not in user.groups: - uga = galaxy.model.UserGroupAssociation( user, group ) + uga = trans.app.model.UserGroupAssociation( user, group ) uga.flush() msg = "The user now belongs to a total of %s groups" % len( groups ) trans.response.send_redirect( web.url_for( action='users', msg=msg, messagetype='done' ) ) @@ -713,7 +862,7 @@ class Admin( BaseController ): @web.require_admin def undelete_library( self, trans, **kwd ): params = util.Params( kwd ) - library = galaxy.model.Library.get( int( params.id ) ) + library = trans.app.model.Library.get( int( params.id ) ) def undelete_folder( library_folder ): for folder in library_folder.folders: undelete_folder( folder ) @@ -731,7 +880,7 @@ class Admin( BaseController ): @web.require_admin def purge_library( self, trans, **kwd ): params = util.Params( kwd ) - library = galaxy.model.Library.get( int( params.id ) ) + library = trans.app.model.Library.get( int( params.id ) ) def purge_folder( library_folder ): for lf in library_folder.folders: purge_folder( lf ) @@ -877,7 +1026,7 @@ class Admin( BaseController ): dataset.flush() if roles: for role in roles: - adra = galaxy.model.ActionDatasetRoleAssociation( RBACAgent.permitted_actions.DATASET_ACCESS.action, dataset.dataset, role ) + adra = trans.app.model.ActionDatasetRoleAssociation( RBACAgent.permitted_actions.DATASET_ACCESS.action, dataset.dataset, role ) adra.flush() shutil.move( temp_name, dataset.dataset.file_name ) dataset.dataset.state = dataset.dataset.states.OK @@ -914,7 +1063,7 @@ class Admin( BaseController ): roles = [] role_ids = params.get( 'roles', [] ) for role_id in util.listify( role_ids ): - roles.append( galaxy.model.Role.get( role_id ) ) + roles.append( trans.app.model.Role.get( role_id ) ) temp_name = "" data_list = [] created_lfda_ids = '' @@ -1139,7 +1288,8 @@ class Admin( BaseController ): msg = params.get( 'msg', None ) messagetype = params.get( 'messagetype', 'done' ) # See if the current history is empty - history=trans.get_history() + history = trans.get_history() + history.refresh() 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' ) ) diff --git a/lib/galaxy/web/controllers/root.py b/lib/galaxy/web/controllers/root.py index beb32d121fa..5cb9c8d48df 100644 --- a/lib/galaxy/web/controllers/root.py +++ b/lib/galaxy/web/controllers/root.py @@ -527,7 +527,7 @@ class RootController( BaseController ): if not email: return trans.fill_template("/history/share.mako", histories=histories, email=email, send_to_err=send_to_err) user = trans.get_user() - send_to_user = trans.app.model.User.filter_by( email=email ).first() + send_to_user = trans.app.model.User.filter( trans.app.model.User.table.c.email==email ).first() p = util.Params( kwd ) if p.action and p.action == "no_share": trans.response.send_redirect( url_for( action='history_options' ) ) diff --git a/lib/galaxy/web/controllers/user.py b/lib/galaxy/web/controllers/user.py index 9e197c021dd..7f65379f858 100644 --- a/lib/galaxy/web/controllers/user.py +++ b/lib/galaxy/web/controllers/user.py @@ -88,12 +88,13 @@ class User( BaseController ): else: refresh_frames = [ 'masthead', 'history' ] if email or password: - user = trans.app.model.User.filter_by( email=email ).first() + user = trans.app.model.User.filter( trans.app.model.User.table.c.email==email ).first() if not user: email_error = "No such user" + elif user.deleted: + email_error = "This account has been marked deleted, contact your Galaxy administrator to restore the account." elif user.external: - return trans.show_error_message( "This account was created for use with an external authentication " - + "method. Please contact your local Galaxy administrator to activate it." ) + email_error = "This account was created for use with an external authentication method, contact your local Galaxy administrator to activate it." elif not user.check_password( password ): password_error = "Invalid password" else: @@ -118,9 +119,6 @@ class User( BaseController ): @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: @@ -128,9 +126,6 @@ class User( BaseController ): # 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 ) msg = "You are no longer logged in." if trans.app.config.require_login: msg += ' Click here to return to the login page.' % web.url_for( controller='user', action='login' ) @@ -148,9 +143,10 @@ class User( BaseController ): if email: if len( email ) == 0 or "@" not in email or "." not in email: email_error = "Please enter a real email address" - elif len( email) > 255: + elif len( email ) > 255: email_error = "Email address exceeds maximum allowable length" - elif trans.app.model.User.filter_by( email=email ).first(): + elif trans.app.model.User.filter( and_( trans.app.model.User.table.c.email==email, + trans.app.model.User.table.c.deleted==False ) ).first(): email_error = "User with that email already exists" elif len( password ) < 6: password_error = "Please use a password of at least 6 characters" @@ -182,11 +178,11 @@ class User( BaseController ): @web.expose def reset_password( self, trans, email=None, **kwd ): error = '' - reset_user = trans.app.model.User.filter_by( email=email ).first() + reset_user = trans.app.model.User.filter( trans.app.model.User.table.c.email==email ).first() user = trans.get_user() if reset_user: if user and user.id != reset_user.id: - error = "You may only reset your own password" + error = "You may only reset your own password" else: chars = string.letters + string.digits new_pass = "" diff --git a/lib/galaxy/web/controllers/workflow.py b/lib/galaxy/web/controllers/workflow.py index 35c6eed0d0b..beaa684818a 100644 --- a/lib/galaxy/web/controllers/workflow.py +++ b/lib/galaxy/web/controllers/workflow.py @@ -13,6 +13,7 @@ from galaxy.util.bunch import Bunch from galaxy.util.topsort import topsort, topsort_levels, CycleError from galaxy.workflow.modules import * from galaxy.model.mapping import desc +from galaxy.model.orm import * class WorkflowController( BaseController ): @@ -48,7 +49,8 @@ class WorkflowController( BaseController ): # Load workflow from database stored = get_stored_workflow( trans, id ) if email: - other = model.User.filter_by( email=email ).first() + other = model.User.filter( and_( model.user.table.c.email==email, + model.User.table.c.deleted==False ) ).first() if not other: mtype = "error" msg = ( "User '%s' does not exist" % email ) diff --git a/lib/galaxy/web/framework/__init__.py b/lib/galaxy/web/framework/__init__.py index af93111117f..59324a80479 100644 --- a/lib/galaxy/web/framework/__init__.py +++ b/lib/galaxy/web/framework/__init__.py @@ -221,8 +221,7 @@ class UniverseWebTransaction( base.DefaultWebTransaction ): galaxy_session.user = self.__get_or_create_remote_user( remote_user_email ) galaxy_session_requires_flush = True elif galaxy_session.user.email != remote_user_email: - # Session exists but is not associated with the correct - # remote user + # Session exists but is not associated with the correct remote user invalidate_existing_session = True user_for_new_session = self.__get_or_create_remote_user( remote_user_email ) log.warning( "User logged in as '%s' externally, but has a cookie as '%s' invalidating session", @@ -293,16 +292,27 @@ class UniverseWebTransaction( base.DefaultWebTransaction ): def __get_or_create_remote_user( self, remote_user_email ): """ Return the user in $HTTP_REMOTE_USER and create if necessary - Caller is responsible for flushing the returned user. """ # remote_user middleware ensures HTTP_REMOTE_USER exists - user = self.app.model.User.filter_by( email=remote_user_email ).first() + user = self.app.model.User.filter( self.app.model.User.table.c.email==remote_user_email ).first() if user is None: user = self.app.model.User( email=remote_user_email ) user.set_password_cleartext( 'external' ) user.external = True self.log_event( "Automatically created account '%s'", user.email ) + # TODO: make sure this correctly handles deleted / purged users + elif user.deleted: + if user.purged: + # If the user has been purged, all associations have been deleted except for the private role + # and the DefaultUserPermissions and DefaultHistoryPermissions associated with it. We'll + # restore the user, but all of their previous histories and other associations will have been + # deleted. + user.purged = False + # If the user was not purged, the state of all of their associations at the time they were deleted + # will have been preserved. + user.deleted = False + user.flush() return user def __update_session_cookie( self ): """ diff --git a/templates/admin/dataset_security/users.mako b/templates/admin/dataset_security/users.mako index 39dad937182..e149d23cdb4 100644 --- a/templates/admin/dataset_security/users.mako +++ b/templates/admin/dataset_security/users.mako @@ -16,7 +16,11 @@ ${user.email}
+ Reset password Change associated groups + %if allow_user_deletion: + Mark user deleted + %endif
@@ -44,6 +48,9 @@ %if msg: diff --git a/templates/admin/library/browser.mako b/templates/admin/library/browser.mako index ed9da94ade4..f268a8a11dd 100644 --- a/templates/admin/library/browser.mako +++ b/templates/admin/library/browser.mako @@ -103,7 +103,7 @@ Create a new sub-folder in this folder Rename this folder %if subfolder: - Remove this folder and its contents from the library + Remove this folder and its contents from the library %endif %endif diff --git a/templates/admin/user/create.mako b/templates/admin/user/create.mako new file mode 100644 index 00000000000..e119e29e6c0 --- /dev/null +++ b/templates/admin/user/create.mako @@ -0,0 +1,43 @@ +<%inherit file="/base.mako"/> +<%namespace file="/message.mako" import="render_msg" /> + +%if msg: + ${render_msg( msg, messagetype )} +%endif + +
+
Create account
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+ +
+
+
diff --git a/templates/admin/user/deleted_users.mako b/templates/admin/user/deleted_users.mako new file mode 100644 index 00000000000..0847cf25a63 --- /dev/null +++ b/templates/admin/user/deleted_users.mako @@ -0,0 +1,91 @@ +<%inherit file="/base.mako"/> +<%namespace file="/message.mako" import="render_msg" /> + +## Render a row +<%def name="render_row( user, ctr, anchored, curr_anchor )"> + %if ctr % 2 == 1: + + %else: + + %endif + + ${user.email} + +
+ Undelete + Purge +
+ %if not anchored: + +
top
+ %endif + + + + +

Deleted Users

+ +%if msg: + ${render_msg( msg, messagetype )} +%endif + +%if len( users ) == 0: + There are no deleted Galaxy users +%else: + + <% + render_quick_find = len( users ) > 50 + ctr = 0 + %> + %if render_quick_find: + <% + anchors = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'] + anchor_loc = 0 + anchored = False + curr_anchor = 'A' + %> + + + + %endif + + + + %for ctr, user in enumerate( users ): + %if render_quick_find and not user.email.upper().startswith( curr_anchor ): + <% anchored = False %> + %endif + %if render_quick_find and user.email.upper().startswith( curr_anchor ): + %if not anchored: + ${render_row( user, ctr, anchored, curr_anchor )} + <% anchored = True %> + %else: + ${render_row( user, ctr, anchored, curr_anchor )} + %endif + %elif render_quick_find: + %for anchor in anchors[ anchor_loc: ]: + %if user.email.upper().startswith( anchor ): + %if not anchored: + <% curr_anchor = anchor %> + ${render_row( user, ctr, anchored, curr_anchor )} + <% anchored = True %> + %else: + ${render_row( user, ctr, anchored, curr_anchor )} + %endif + <% + anchor_loc = anchors.index( anchor ) + break + %> + %endif + %endfor + %else: + ${render_row( user, ctr, True, '' )} + %endif + %endfor +
+ Jump to letter: + %for a in anchors: + | ${a} + %endfor +
Email
+%endif diff --git a/templates/admin/user/reset_password.mako b/templates/admin/user/reset_password.mako new file mode 100644 index 00000000000..5c0e92de957 --- /dev/null +++ b/templates/admin/user/reset_password.mako @@ -0,0 +1,35 @@ +<%inherit file="/base.mako"/> +<%namespace file="/message.mako" import="render_msg" /> + +%if msg: + ${render_msg( msg, messagetype )} +%endif + +
+
Reset user password
+
+
+ +
+ + ${user.email} +
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+ +
+
+
diff --git a/test/base/twilltestcase.py b/test/base/twilltestcase.py index dff00121fc0..9288f4c58da 100644 --- a/test/base/twilltestcase.py +++ b/test/base/twilltestcase.py @@ -362,16 +362,13 @@ class TwillTestCase( unittest.TestCase ): self.assertTrue( genome_build == dbkey ) # Functions associated with user accounts - def create( self, email='test@bx.psu.edu', password='testuser', confirm='testuser' ): - self.visit_page( "user/create?email=%s&password=%s&confirm=%s" %(email, password, confirm) ) - try: - self.check_page_for_string( "User with that email already exists" ) - except: - self.check_page_for_string( "Now logged in as %s" %email ) - self.home() - # Make sure a new private role was created for the user - self.visit_page( "user/set_default_permissions" ) - self.check_page_for_string( email ) + def create( self, email='test@bx.psu.edu', password='testuser' ): + self.visit_page( "user/create?email=%s&password=%s&confirm=%s" % ( email, password, password ) ) + self.check_page_for_string( "Now logged in as %s" %email ) + self.home() + # Make sure a new private role was created for the user + self.visit_page( "user/set_default_permissions" ) + self.check_page_for_string( email ) self.home() def user_set_default_permissions( self, permissions_out=[], permissions_in=[], role_id=2 ): # role.id = 2 is Private Role for test2@bx.psu.edu # NOTE: Twill has a bug that requires the ~/user/permissions page to contain at least 1 option value @@ -405,14 +402,19 @@ class TwillTestCase( unittest.TestCase ): self.last_page() self.check_page_for_string( 'Default history permissions have been changed.' ) self.home() - def login( self, email='test@bx.psu.edu', password='testuser'): + def login( self, email='test@bx.psu.edu', password='testuser' ): # test@bx.psu.edu is configured as an admin user - self.create( email=email, password=password, confirm=password ) - self.visit_page( "user/login?email=%s&password=%s" % (email, password) ) - self.check_page_for_string( "Now logged in as %s" %email ) - self.home() + try: + self.create( email=email, password=password ) + except: + self.home() + self.visit_page( "user/login?email=%s&password=%s" % ( email, password ) ) + self.last_page() + self.check_page_for_string( "Now logged in as %s" %email ) + self.home() def logout( self ): self.visit_page( "user/logout" ) + self.last_page() self.check_page_for_string( "You are no longer logged in" ) self.home() # Functions associated with browsers, cookies, HTML forms and page visits @@ -581,7 +583,41 @@ class TwillTestCase( unittest.TestCase ): self.assertNotEqual(count, maxiter) # Dataset Security stuff - def create_role( self, name='New Test Role', description="Very cool new test role", user_ids=[], group_ids=[], private_role='' ): + def create_new_account_as_admin( self, email='test4@bx.psu.edu', password='testuser' ): + """Create a new account for another user""" + self.visit_url( "%s/admin/create_new_user?email=%s&password=%s&confirm=%s&user_create_button=%s" \ + % ( self.url, email, password, password, 'Create' ) ) + self.last_page() + self.check_page_for_string( "Created new user account" ) + self.home() + def reset_password_as_admin( self, user_id=4, password='testreset' ): + """Reset a user password""" + self.visit_url( "%s/admin/reset_user_password?user_id=%s" % ( self.url, str( user_id ) ) ) + tc.fv( "1", "password", password ) + tc.fv( "1", "confirm", password ) + tc.submit( "reset_user_password_button" ) + self.last_page() + self.check_page_for_string( "Password reset" ) + self.home() + def mark_user_deleted( self, user_id=4 ): + """Mark a user as deleted""" + self.visit_url( "%s/admin/mark_user_deleted?user_id=%s" % ( self.url, str( user_id ) ) ) + self.last_page() + self.check_page_for_string( "The user has been marked as deleted." ) + self.home() + def undelete_user( self, user_id ): + """Undelete a user""" + self.visit_url( "%s/admin/undelete_user?user_id=%s" % ( self.url, user_id ) ) + self.last_page() + self.check_page_for_string( 'The user has been marked as not deleted' ) + self.home() + def purge_user( self, user_id ): + """Purge a user account""" + self.visit_url( "%s/admin/purge_user?user_id=%s" % ( self.url, user_id ) ) + self.last_page() + self.check_page_for_string( 'The user has been marked as purged.' ) + self.home() + def create_role( self, name='Role One', description="This is Role One", user_ids=[], group_ids=[], private_role='' ): """Create a new role""" self.visit_url( "%s/admin/create_role" % self.url ) form = tc.show() @@ -632,15 +668,15 @@ class TwillTestCase( unittest.TestCase ): self.last_page() self.check_page_for_string( 'The role has been marked as not deleted' ) self.home() - def purge_role( self, role_id, deleted=False ): + def purge_role( self, role_id ): """Purge an existing role""" - if not deleted: - self.mark_role_deleted( role_id ) self.visit_url( "%s/admin/purge_role?role_id=%s" % ( self.url, role_id ) ) self.last_page() - self.check_page_for_string( 'The role has been purged from the database' ) + msg = "The following have been purged from the database for the role: " + msg += "DefaultUserPermissions, DefaultHistoryPermissions, UserRoleAssociations, GroupRoleAssociations, ActionDatasetRoleAssociations." + self.check_page_for_string( msg ) self.home() - def create_group( self, name='New Test Group', user_ids=[], role_ids=[] ): + def create_group( self, name='Group One', user_ids=[], role_ids=[] ): """Create a new group with 2 members and 1 associated role""" self.visit_url( "%s/admin/create_group" % self.url ) form = tc.show() @@ -685,23 +721,17 @@ class TwillTestCase( unittest.TestCase ): self.home() raise AssertionError( 'Exception caught attempting to create group: %s' % str( err ) ) self.home() - def associate_groups_with_role( self, role_id, group_ids=[] ): + def associate_groups_with_role( self, role_id, group_names=[] ): """Add groups to an existing role""" # NOTE: To get this to work with twill, all select lists must contain at least 1 option value - # or twill throws an exception, which is: ParseError: OPTION outside of SELECT + # before tc.submit or twill throws an exception, which is: ParseError: OPTION outside of SELECT self.visit_url( "%s/admin/role?role_id=%s" % ( self.url, role_id ) ) self.check_page_for_string( 'Groups associated with' ) - # All groups must be in the out_groups form field - try: - for group in groups: - tc.fv( "1", "7", group_id ) # form field 7 is the select list named out_groups, note the buttons... - tc.submit( "groups_add_button" ) - tc.submit( "role_button" ) - except AssertionError, err: - self.home() - raise AssertionError( 'Exception caught attempting to associated groups with a role: %s' % str( err ) ) - except: - pass + # All group_ids passed in MUST be in the out_groups form field + for group_name in group_names: + tc.fv( "1", "out_groups", group_name ) # note the buttons... + tc.submit( "groups_add_button" ) + tc.submit( "role_button" ) self.home() def mark_group_deleted( self, group_id ): """Mark a group as deleted""" @@ -715,17 +745,15 @@ class TwillTestCase( unittest.TestCase ): self.last_page() self.check_page_for_string( 'The group has been marked as not deleted' ) self.home() - def purge_group( self, group_id, deleted=False ): + def purge_group( self, group_id ): """Purge an existing group""" - if not deleted: - self.mark_group_deleted( group_id ) self.visit_url( "%s/admin/purge_group?group_id=%s" % ( self.url, group_id ) ) self.last_page() - self.check_page_for_string( 'The group has been purged from the database' ) + self.check_page_for_string( "The following have been purged from the database for the group: UserGroupAssociations, GroupRoleAssociations." ) self.home() # Library stuff - def create_library( self, name='New Test Library', description='New Test Library Description' ): + def create_library( self, name='Library One', description='This is Library One' ): """Create a new library""" try: self.visit_url( "%s/admin/library?new=True" % self.url ) @@ -738,7 +766,7 @@ class TwillTestCase( unittest.TestCase ): self.home() raise AssertionError( 'Exception caught attempting to create library: %s' % str( err ) ) self.home() - def rename_library( self, library_id, name='New Test Library Renamed', description='New Test Library Description Re-described', root_folder='' ): + def rename_library( self, library_id, name='Library One Renamed', description='This is Library One Re-described', root_folder='' ): """Rename a library""" try: self.visit_url( "%s/admin/library?rename=True&id=%s" % ( self.url, library_id ) ) @@ -759,7 +787,7 @@ class TwillTestCase( unittest.TestCase ): self.home() raise AssertionError( 'Exception caught attempting to rename a library: %s' % str( err ) ) self.home() - def add_folder( self, folder_id, name='New Test Folder', description='New Test Folder Description' ): + def add_folder( self, folder_id, name='Folder One', description='NThis is Folder One' ): """Create a new folder""" try: self.visit_url( "%s/admin/folder?id=%s&new=True" % ( self.url, folder_id ) ) @@ -772,7 +800,7 @@ class TwillTestCase( unittest.TestCase ): self.home() raise AssertionError( 'Exception caught attempting to create a new folder: %s' % str( err ) ) self.home() - def rename_folder( self, folder_id, name='New Test Folder Renamed', description='New Test Folder Description Re-described' ): + def rename_folder( self, folder_id, name='Folder One Renamed', description='This is Folder One Re-described' ): """Rename a Folder""" try: self.visit_url( "%s/admin/folder?rename=True&id=%s" % ( self.url, folder_id ) ) @@ -811,7 +839,6 @@ class TwillTestCase( unittest.TestCase ): # Create a new history self.new_history() self.upload_file( "1.bed" ) - self.verify_dataset_correctness( "1.bed" ) self.visit_url( "%s/admin/add_dataset_to_folder_from_history?folder_id=%s" % ( self.url, folder_id ) ) self.last_page() self.check_page_for_string( 'Active datasets in your current history' ) @@ -822,7 +849,7 @@ class TwillTestCase( unittest.TestCase ): self.check_page_for_string( 'Added the following datasets to the library folder: 1.bed' ) except AssertionError, err: self.home() - raise AssertionError( 'Exception caught attempting to create add a dataset to a folder: %s' % str( err ) ) + raise AssertionError( 'Exception caught attempting to add a dataset to a folder: %s' % str( err ) ) self.home() def add_datasets_from_library_dir( self, folder_id, extension='auto', dbkey='hg18', roles_tuple=[] ): """Add a directory of datasets to a folder""" diff --git a/test/functional/__init__.py b/test/functional/__init__.py index 828bb247ef6..95722061d54 100644 --- a/test/functional/__init__.py +++ b/test/functional/__init__.py @@ -37,7 +37,6 @@ def setup(): galaxy_test_port = os.environ.get( 'GALAXY_TEST_PORT', default_galaxy_test_port ) start_server = 'GALAXY_TEST_EXTERNAL' not in os.environ - if start_server: if 'GALAXY_TEST_DBPATH' in os.environ: db_path = os.environ['GALAXY_TEST_DBPATH'] @@ -73,10 +72,12 @@ def setup(): tool_path = "tools", test_conf = "test.conf", log_destination = "stdout", - use_heartbeat=False, + use_heartbeat = False, + allow_user_creation = True, + allow_user_deletion = True, admin_users = 'test@bx.psu.edu', library_import_dir = galaxy_test_file_dir, - global_conf= { "__file__": "universe_wsgi.ini.sample" } ) + global_conf = { "__file__": "universe_wsgi.ini.sample" } ) log.info( "Embedded Universe application started" ) diff --git a/test/functional/test_security_and_libraries.py b/test/functional/test_security_and_libraries.py index 8a95da8122e..f8a8a1664d7 100644 --- a/test/functional/test_security_and_libraries.py +++ b/test/functional/test_security_and_libraries.py @@ -1,4 +1,3 @@ -import sys import galaxy.model from galaxy.model.orm import * from base.twilltestcase import * @@ -6,7 +5,7 @@ from base.twilltestcase import * not_logged_in_security_msg = 'You must be logged in as an administrator to access this feature.' logged_in_security_msg = 'You must be an administrator to access this feature.' -class TestHistory( TwillTestCase ): +class TestSecurityAndLibraries( TwillTestCase ): def test_00_admin_features_when_not_logged_in( self ): """Testing admin_features when not logged in""" self.logout() @@ -113,9 +112,11 @@ class TestHistory( TwillTestCase ): self.new_history() latest_history = galaxy.model.History.query().order_by( desc( galaxy.model.History.table.c.create_time ) ).first() if not latest_history.default_permissions: - raise AssertionError( 'No DefaultHistoryPermissions were created for history id %d when DefaultHistoryPermissions were changed' % latest_history.id ) + raise AssertionError( 'No DefaultHistoryPermissions were created for history id %d when DefaultHistoryPermissions were changed' % \ + latest_history.id ) if len( latest_history.default_permissions ) != len( galaxy.model.Dataset.permitted_actions.items() ): - raise AssertionError( '%d DefaultHistoryPermissions were created for history id %d, should have been %d' % ( len( latest_history.default_permissions ), latest_history.id, len( galaxy.model.Dataset.permitted_actions ) ) ) + raise AssertionError( '%d DefaultHistoryPermissions were created for history id %d, should have been %d' % \ + ( len( latest_history.default_permissions ), latest_history.id, len( galaxy.model.Dataset.permitted_actions ) ) ) dhps = [] for dhp in latest_history.default_permissions: dhps.append( dhp.action ) @@ -131,7 +132,8 @@ class TestHistory( TwillTestCase ): if not latest_dataset.actions: raise AssertionError( 'No ActionDatasetRoleAssociations were created for dataset id %d when it was created' % latest_dataset.id ) if len( latest_dataset.actions ) != len( latest_history.default_permissions ): - raise AssertionError( '%d ActionDatasetRoleAssociations were created for dataset id %d when it was created ( should have been %d )' % ( len( latest_dataset.actions ), latest_dataset.id, len( latest_history.default_permissions ) ) ) + raise AssertionError( '%d ActionDatasetRoleAssociations were created for dataset id %d when it was created ( should have been %d )' % \ + ( len( latest_dataset.actions ), latest_dataset.id, len( latest_history.default_permissions ) ) ) adras = [] for adra in latest_dataset.actions: adras.append( adra.action ) @@ -171,7 +173,8 @@ class TestHistory( TwillTestCase ): # Change DefaultHistoryPermissions for the current history self.history_set_default_permissions( permissions_out=permissions_out, permissions_in=permissions_in, role_id=role_id ) if not latest_history.default_permissions: - raise AssertionError( 'No DefaultHistoryPermissions were created for history id %d when DefaultHistoryPermissions were changed' % latest_history.id ) + raise AssertionError( 'No DefaultHistoryPermissions were created for history id %d when DefaultHistoryPermissions were changed' % \ + latest_history.id ) if len( latest_history.default_permissions ) != len( actions_in ): raise AssertionError( '%d DefaultHistoryPermissions were created for history id %d, should have been %d' \ % ( len( latest_history.default_permissions ), latest_history.id, len( permissions_in ) ) ) @@ -189,7 +192,8 @@ class TestHistory( TwillTestCase ): if not latest_dataset.actions: raise AssertionError( 'No ActionDatasetRoleAssociations were created for dataset id %d when it was created' % latest_dataset.id ) if len( latest_dataset.actions ) != len( latest_history.default_permissions ): - raise AssertionError( '%d ActionDatasetRoleAssociations were created for dataset id %d when it was created ( should have been %d )' % ( len( latest_dataset.actions ), latest_dataset.id, len( latest_history.default_permissions ) ) ) + raise AssertionError( '%d ActionDatasetRoleAssociations were created for dataset id %d when it was created ( should have been %d )' \ + % ( len( latest_dataset.actions ), latest_dataset.id, len( latest_history.default_permissions ) ) ) adras = [] for adra in latest_dataset.actions: adras.append( adra.action ) @@ -197,55 +201,201 @@ class TestHistory( TwillTestCase ): adras.sort() self.home() self.logout() - def test_12_create_role( self ): - """Testing creating new non-private role with 2 members""" - self.login( email=testuser1.email ) - name = 'New Test Role' - description = 'Very cool new test role' - self.create_role( name=name, description=description, user_ids=[ str( testuser1.id ), str( testuser2.id ) ], private_role=testuser1.email ) + def test_12_create_new_user_account_as_admin( self ): + """Testing creating a new user account as admin""" + self.login( email='test@bx.psu.edu' ) + email = 'test4@bx.psu.edu' + password = 'testuser' + self.create_new_account_as_admin( email=email, password=password ) + # Get the user object for later tests + global testuser4 + testuser4 = galaxy.model.User.filter( galaxy.model.User.table.c.email=='test4@bx.psu.edu' ).first() + # Make sure DefaultUserPermissions were created + if not testuser4.default_permissions: + raise AssertionError( 'No DefaultUserPermissions were created for user %s when the admin created the account' % email ) + # Make sure a private role was created for the user + if not testuser4.roles: + raise AssertionError( 'No UserRoleAssociations were created for user %s when the admin created the account' % email ) + if len( testuser4.roles ) != 1: + raise AssertionError( '%d UserRoleAssociations were created for user %s when the admin created the account ( should have been 1 )' \ + % len( testuser4.roles ) ) + for ura in testuser4.roles: + role = galaxy.model.Role.get( ura.role_id ) + if role.type != 'private': + raise AssertionError( 'Role created for user %s when the admin created the account is not private, type is' \ + % str( role.type ) ) + # Make sure a history was not created + histories = galaxy.model.History.filter( galaxy.model.History.table.c.user_id==testuser4.id ).all() + if histories: + raise AssertionError( 'Histories were incorrectly created for user %s when the admin created the account' % email ) + # Make sure the user was not associated with any groups + if testuser4.groups: + raise AssertionError( 'Groups were incorrectly associated with user %s when the admin created the account' % email ) + def test_15_reset_password_as_admin( self ): + """Testing reseting a user password as admin""" + email = 'test4@bx.psu.edu' + self.reset_password_as_admin( user_id=testuser4.id, password='testreset' ) + self.home() + self.logout() + def test_18_login_after_password_reset( self ): + """Testing logging in after an admin reset a password - tests DefaultHistoryPermissions for accounts created by an admin""" + self.login( email='test4@bx.psu.edu', password='testreset' ) + # Make sure a History and HistoryDefaultPermissions exist for the user + latest_history = galaxy.model.History.query().order_by( desc( galaxy.model.History.table.c.create_time ) ).first() + if not latest_history.user_id == testuser4.id: + raise AssertionError( 'A history was not created for user %s when he logged in' % email ) + if not latest_history.default_permissions: + raise AssertionError( 'No DefaultHistoryPermissions were created for history id %d when it was created' % latest_history.id ) + if len( latest_history.default_permissions ) > 1: + raise AssertionError( 'More than 1 DefaultHistoryPermissions were created for history id %d when it was created' % latest_history.id ) + dhp = galaxy.model.DefaultHistoryPermissions.filter( galaxy.model.DefaultHistoryPermissions.table.c.history_id==latest_history.id ).first() + if not dhp.action == galaxy.model.Dataset.permitted_actions.DATASET_MANAGE_PERMISSIONS.action: + raise AssertionError( 'The DefaultHistoryPermission.action for history id %d is "%s", but it should be "%s"' \ + % ( latest_history.id, dhp.action, galaxy.model.Dataset.permitted_actions.DATASET_MANAGE_PERMISSIONS.action ) ) + # Upload a file to create a HistoryDatasetAssociation + self.upload_file( '1.bed' ) + self.home() + self.logout() + def test_21_mark_user_deleted( self ): + """Testing marking a user account as deleted""" + self.login( email='test@bx.psu.edu' ) + self.mark_user_deleted( user_id=testuser4.id ) + def test_24_undelete_user( self ): + """Testing undeleting a user account""" + self.undelete_user( user_id=testuser4.id ) + def test_27_create_role( self ): + """Testing creating new non-private role with 3 members""" + name = 'Role One' + description = 'This is Role One' + user_ids=[ str( testuser1.id ), str( testuser2.id ), str( testuser4.id ) ] + self.create_role( name=name, description=description, user_ids=user_ids, private_role=testuser1.email ) # Get the role object for later tests - global new_test_role - new_test_role = galaxy.model.Role.filter( galaxy.model.Role.table.c.name==name ).first() - def test_15_create_group( self ): - """Testing creating new group with 2 members and 1 associated role""" - name = 'New Test Group' - self.create_group( name=name, user_ids=[ str( testuser1.id ), str( testuser2.id ) ], role_ids=[ str( new_test_role.id ) ] ) + global role_one + role_one = galaxy.model.Role.filter( galaxy.model.Role.table.c.name==name ).first() + # Make sure UserRoleAssociations are correct + if not role_one.users: + raise AssertionError( 'No UserRoleAssociations were created for role id %d when it was created with 3 members' % role_one.id ) + if len( role_one.users ) != len( user_ids ): + raise AssertionError( '%d UserRoleAssociations were created for role id %d when it was created ( should have been %d )' \ + % ( len( role_one.users ), role_one.id, len( user_ids ) ) ) + # Each user should now have 2 role associations, their private role and role_one + for user in [ testuser1, testuser2, testuser4 ]: + user.refresh() + if not user.roles: + raise AssertionError( 'No UserRoleAssociations were created for user %s when a new role was created' % user.email ) + if len( user.roles ) != 2: + raise AssertionError( '%d UserRoleAssociations are associated with user %s ( should be 2 )' % ( len( user.roles ), user.email ) ) + def test_30_create_group( self ): + """Testing creating new group with 3 members and 1 associated role""" + name = 'Group One' + user_ids=[ str( testuser1.id ), str( testuser2.id ), str( testuser4.id ) ] + role_ids=[ str( role_one.id ) ] + self.create_group( name=name, user_ids=user_ids, role_ids=role_ids ) # Get the group object for later tests - global new_test_group - new_test_group = galaxy.model.Group.filter( galaxy.model.Group.table.c.name==name ).first() - def test_18_add_group_member( self ): + global group_one + group_one = galaxy.model.Group.filter( galaxy.model.Group.table.c.name==name ).first() + # Make sure UserGroupAssociations are correct + if not group_one.users: + raise AssertionError( 'No UserGroupAssociations were created for group id %d when it was created with 3 members' % group_one.id ) + if len( group_one.users ) != len( user_ids ): + raise AssertionError( '%d UserGroupAssociations were created for group id %d when it was created ( should have been %d )' \ + % ( len( group_one.users ), group_one.id, len( user_ids ) ) ) + # Each user should now have 1 group association, group_one + for user in [ testuser1, testuser2, testuser4 ]: + user.refresh() + if not user.groups: + raise AssertionError( 'No UserGroupAssociations were created for user %s when a new group was created' % user.email ) + if len( user.groups ) != 1: + raise AssertionError( '%d UserGroupAssociations are associated with user %s ( should be 1 )' % ( len( user.groups ), user.email ) ) + # Make sure GroupRoleAssociations are correct + if not group_one.roles: + raise AssertionError( 'No GroupRoleAssociations were created for group id %d when it was created with 3 members' % group_one.id ) + if len( group_one.roles ) != len( role_ids ): + raise AssertionError( '%d GroupRoleAssociations were created for group id %d when it was created ( should have been %d )' \ + % ( len( group_one.roles ), group_one.id, len( role_ids ) ) ) + def test_33_add_group_member( self ): """Testing editing membership of an existing group""" - name = 'Another Test Group' + name = 'Group Two' self.create_group( name=name ) # Get the group object for later tests - global another_test_group - another_test_group = galaxy.model.Group.filter( galaxy.model.Group.table.c.name==name ).first() - self.add_group_members( str( another_test_group.id ), [ str( testuser3.id ) ] ) - self.visit_url( "%s/admin/group_members_edit?group_id=%s" % ( self.url, str( another_test_group.id ) ) ) + global group_two + group_two = galaxy.model.Group.filter( galaxy.model.Group.table.c.name==name ).first() + user_ids = [ str( testuser3.id ) ] + self.add_group_members( str( group_two.id ), user_ids ) + self.visit_url( "%s/admin/group_members_edit?group_id=%s" % ( self.url, str( group_two.id ) ) ) self.check_page_for_string( testuser3.email ) - def test_21_associate_groups_with_role( self ): + # Make sure UserGroupAssociations are correct + if not group_two.users: + raise AssertionError( 'No UserGroupAssociations were created for group id %d when %d members were added' \ + % ( group_two.id, len( user_ids ) ) ) + if len( group_two.users ) != len( user_ids ): + raise AssertionError( '%d UserGroupAssociations were created for group id %d when %d members were added' \ + % ( len( group_two.users ), group_two.id, len( user_ids ) ) ) + # Create another group -needed for the following test + name = 'Group Three' + self.create_group( name=name ) + # Get the group object for later tests + global group_three + group_three = galaxy.model.Group.filter( galaxy.model.Group.table.c.name==name ).first() + def test_36_associate_groups_with_role( self ): """Testing adding existing groups to an existing role""" # NOTE: To get this to work with twill, all select lists on the ~/admin/role page must contain at least # 1 option value or twill throws an exception, which is: ParseError: OPTION outside of SELECT - # Due to this bug in twill, we create the role, associating it with at least 1 user and 1 group... - name = 'Another Test Role' - description = 'Another cool new test role' + # Due to this bug in twill, we create the role, associating it with at least 1 user and 1 group. We + # also must ensure that each of the form fields will contain at least 1 value prior to submitting the form, + # so we had to create group_three in the previous test + name = 'Role Two' + description = 'This is Role Two' + user_ids=[ str( testuser1.id ) ] + group_ids=[ str( group_two.id ) ] + private_role=testuser1.email + # STEP 1: create the role self.create_role( name=name, description=description, - user_ids=[ str( testuser1.id ) ], - group_ids=[ str( another_test_group.id ) ], - private_role=testuser1.email ) + user_ids=user_ids, + group_ids=group_ids, + private_role=private_role ) # Get the role object for later tests - global another_test_role - another_test_role = galaxy.model.Role.filter( galaxy.model.Role.table.c.name==name ).first() - # ...and then we associate the role with a group not yet associated - self.associate_groups_with_role( str( another_test_role.id ), group_ids=[ str( new_test_group.id ) ] ) - self.visit_page( 'admin/roles' ) - self.check_page_for_string( new_test_group.name ) - def test_24_create_library( self ): + global role_two + role_two = galaxy.model.Role.filter( galaxy.model.Role.table.c.name==name ).first() + # Make sure UserRoleAssociations are correct + if not role_two.users: + raise AssertionError( 'No UserRoleAssociations were created for role id %d when it was created with %d members' \ + % ( role_two.id, len( user_ids ) ) ) + if len( role_two.users ) != len( user_ids ): + raise AssertionError( '%d UserRoleAssociations were created for role id %d when it was created with %d members' \ + % ( len( role_two.users ), role_two.id, len( user_ids ) ) ) + # testuser1 should now have 3 role associations, private role, role_one, another+test_role + for user in [ testuser1 ]: + user.refresh() + if not user.roles: + raise AssertionError( 'No UserRoleAssociations were created for user %s when a new role was created' % user.email ) + if len( user.roles ) != 3: + raise AssertionError( '%d UserRoleAssociations are associated with user %s ( should be 3 )' % ( len( user.roles ), user.email ) ) + # Make sure GroupRoleAssociations are correct + if not role_two.groups: + raise AssertionError( 'No GroupRoleAssociations were created for role id %d when it was created with %d groups' \ + % ( role_two.id, len( group_ids ) ) ) + if len( role_two.groups ) != len( group_ids ): + raise AssertionError( '%d GroupRoleAssociations were created for role id %d when it was created ( should have been %d )' \ + % ( len( role_two.groups ), role_two.id, len( group_ids ) ) ) + # The group should also now be associated with 1 role + for group in [ group_two ]: + group.refresh() + if not group.roles: + raise AssertionError( 'No GroupRoleAssociations were created for group id %d when a new role was created' % group.id ) + if len( group.roles ) != 1: + raise AssertionError( '%d GroupRoleAssociations are associated with group id %d ( should be 1 )' % ( len( group.roles ), group.id ) ) + # STEP 2: associate the role with a group not yet associated + # TODO: Twill throws an exception on this... + #group_names = [ group_one.name ] + #self.associate_groups_with_role( str( role_two.id ), group_names=group_names ) + #self.visit_page( 'admin/roles' ) + #self.check_page_for_string( group_one.name ) + def test_39_create_library( self ): """Testing creating new library""" - name = 'New Test Library' - description = 'New Test Library Description' + name = 'Library One' + description = 'This is Library One' self.create_library( name=name, description=description ) self.visit_page( 'admin/libraries' ) self.check_page_for_string( name ) @@ -254,20 +404,20 @@ class TestHistory( TwillTestCase ): library = galaxy.model.Library.filter( and_( galaxy.model.Library.table.c.name==name, galaxy.model.Library.table.c.description==description, galaxy.model.Library.table.c.deleted==False ) ).first() - def test_27_rename_library( self ): + def test_42_rename_library( self ): """Testing renaming a library""" - self.rename_library( str( library.id ), name='New Test Library Renamed', description='New Test Library Description Re-described', root_folder='on' ) + self.rename_library( str( library.id ), name='Library One Renamed', description='This is Library One Re-described', root_folder='on' ) self.visit_page( 'admin/libraries' ) - self.check_page_for_string( "New Test Library Renamed" ) + self.check_page_for_string( "Library One Renamed" ) # Rename it back to what it was originally - self.rename_library( str( library.id ), name='New Test Library', description='New Test Library Description', root_folder='on' ) - def test_30_rename_root_folder( self ): + self.rename_library( str( library.id ), name='Library One', description='This is Library One', root_folder='on' ) + def test_45_rename_root_folder( self ): """Testing renaming a library root folder""" folder = library.root_folder - self.rename_folder( str( folder.id ), name='New Test Library Root Folder', description='New Test Library Root Folder Description' ) + self.rename_folder( str( folder.id ), name='Library One Root Folder', description='This is Library One root folder' ) self.visit_page( 'admin/libraries' ) - self.check_page_for_string( "New Test Library Root Folder" ) - def test_33_add_public_dataset_to_root_folder( self ): + self.check_page_for_string( "Library One Root Folder" ) + def test_48_add_public_dataset_to_root_folder( self ): """Testing adding a public dataset to a library root folder""" folder = library.root_folder self.add_dataset( '1.bed', str( folder.id ), extension='bed', dbkey='hg18', roles=[] ) @@ -275,7 +425,7 @@ class TestHistory( TwillTestCase ): self.check_page_for_string( "1.bed" ) self.check_page_for_string( "bed" ) self.check_page_for_string( "hg18" ) - def test_36_copy_dataset_from_history_to_root_folder( self ): + def test_51_copy_dataset_from_history_to_root_folder( self ): """Testing copying a dataset from the current history to a library root folder""" folder = library.root_folder self.add_dataset_to_folder_from_history( str( folder.id ) ) @@ -289,40 +439,41 @@ class TestHistory( TwillTestCase ): raise AssertionError( 'More than 1 ActionDatasetRoleAssociations created for dataset id: %d' % last_dataset_created.id ) for adra in adras: if not adra.action == 'manage permissions': - raise AssertionError( 'ActionDatasetRoleAssociation.action "%s" is not the DefaultHistoryPermission setting, which is "manage permissions"' % str( adra.action ) ) - def test_39_add_new_folder( self ): + raise AssertionError( 'ActionDatasetRoleAssociation.action "%s" is not the DefaultHistoryPermission setting, which is "manage permissions"' % \ + str( adra.action ) ) + def test_54_add_new_folder( self ): """Testing adding a folder to a library root folder""" root_folder = library.root_folder - name = 'New Test Folder' - description = 'New Test Folder Description' + name = 'Folder One' + description = 'This is Folder One' self.add_folder( str( root_folder.id ), name=name, description=description ) global new_test_folder new_test_folder = galaxy.model.LibraryFolder.filter( and_( galaxy.model.LibraryFolder.table.c.parent_id==root_folder.id, galaxy.model.LibraryFolder.table.c.name==name, galaxy.model.LibraryFolder.table.c.description==description ) ).first() self.visit_page( 'admin/libraries' ) - self.check_page_for_string( "New Test Folder" ) - def test_42_add_datasets_from_library_dir( self ): + self.check_page_for_string( "Folder One" ) + def test_57_add_datasets_from_library_dir( self ): """Testing adding several datasets from library directory to sub-folder""" - roles_tuple = [ ( str( new_test_role.id ), new_test_role.description ) ] + roles_tuple = [ ( str( role_one.id ), role_one.description ) ] self.add_datasets_from_library_dir( str( new_test_folder.id ), roles_tuple=roles_tuple ) - def test_45_mark_group_deleted( self ): + def test_60_mark_group_deleted( self ): """Testing marking a group as deleted""" self.visit_page( "admin/groups" ) - self.check_page_for_string( another_test_group.name ) - self.mark_group_deleted( str( another_test_group.id ) ) - def test_48_undelete_group( self ): + self.check_page_for_string( group_two.name ) + self.mark_group_deleted( str( group_two.id ) ) + def test_63_undelete_group( self ): """Testing undeleting a deleted group""" - self.undelete_group( str( another_test_group.id ) ) - def test_51_mark_role_deleted( self ): + self.undelete_group( str( group_two.id ) ) + def test_66_mark_role_deleted( self ): """Testing marking a role as deleted""" self.visit_page( "admin/roles" ) - self.check_page_for_string( another_test_role.description ) - self.mark_role_deleted( str( another_test_role.id ) ) - def test_54_undelete_role( self ): + self.check_page_for_string( role_two.description ) + self.mark_role_deleted( str( role_two.id ) ) + def test_69_undelete_role( self ): """Testing undeleting a deleted role""" - self.undelete_role( str( another_test_role.id ) ) - def test_57_mark_library_deleted( self ): + self.undelete_role( str( role_two.id ) ) + def test_72_mark_library_deleted( self ): """Testing marking a library as deleted""" self.mark_library_deleted( str( library.id ) ) # Make sure the library was deleted @@ -334,19 +485,21 @@ class TestHistory( TwillTestCase ): folder.refresh() # Make sure all of the library_folders are deleted if not folder.deleted: - raise AssertionError( 'The library_folder named "%s" has not been marked as deleted ( library.id: %s ).' % ( folder.name, str( library.id ) ) ) + raise AssertionError( 'The library_folder named "%s" has not been marked as deleted ( library.id: %s ).' % \ + ( folder.name, str( library.id ) ) ) check_folder( folder ) # Make sure all of the library_folder_dataset_associations are deleted for lfda in library_folder.datasets: lfda.refresh() if not lfda.deleted: - raise AssertionError( 'The library_folder_dataset_association id %s named "%s" has not been marked as deleted ( library.id: %s ).' % ( str( lfda.id ), lfda.name, str( library.id ) ) ) + raise AssertionError( 'The library_folder_dataset_association id %s named "%s" has not been marked as deleted ( library.id: %s ).' % \ + ( str( lfda.id ), lfda.name, str( library.id ) ) ) # Make sure none of the datasets have been deleted since that should occur only when the library is purged lfda.dataset.refresh() if lfda.dataset.deleted: raise AssertionError( 'The dataset with id "%s" has been marked as deleted when it should not have been.' % lfda.dataset.id ) check_folder( library.root_folder ) - def test_60_mark_library_undeleted( self ): + def test_75_mark_library_undeleted( self ): """Testing marking a library as not deleted""" self.mark_library_undeleted( str( library.id ) ) # Make sure the library is undeleted @@ -358,13 +511,15 @@ class TestHistory( TwillTestCase ): folder.refresh() # Make sure all of the library_folders are undeleted if folder.deleted: - raise AssertionError( 'The library_folder id %s named "%s" has not been marked as undeleted ( library.id: %s ).' % ( str( folder.id ), folder.name, str( library.id ) ) ) + raise AssertionError( 'The library_folder id %s named "%s" has not been marked as undeleted ( library.id: %s ).' % \ + ( str( folder.id ), folder.name, str( library.id ) ) ) check_folder( folder ) # Make sure all of the library_folder_dataset_associations are undeleted for lfda in library_folder.datasets: lfda.refresh() if lfda.deleted: - raise AssertionError( 'The library_folder_dataset_association id %s named "%s" has not been marked as undeleted ( library.id: %s ).' % ( str( lfda.id ), lfda.name, str( library.id ) ) ) + raise AssertionError( 'The library_folder_dataset_association id %s named "%s" has not been marked as undeleted ( library.id: %s ).' % \ + ( str( lfda.id ), lfda.name, str( library.id ) ) ) # Make sure all of the datasets have been undeleted if lfda.dataset.deleted: raise AssertionError( 'The dataset with id "%s" has not been marked as undeleted.' % lfda.dataset.id ) @@ -374,10 +529,55 @@ class TestHistory( TwillTestCase ): # Make sure the library is deleted again library.refresh() if not library.deleted: - raise AssertionError( 'The library id %s named "%s" has not been marked as deleted after it was undeleted.' % ( str( library.id ), library.name ) ) - def test_63_purge_group( self ): + raise AssertionError( 'The library id %s named "%s" has not been marked as deleted after it was undeleted.' % \ + ( str( library.id ), library.name ) ) + def test_78_purge_user( self ): + """Testing purging a user account""" + self.mark_user_deleted( user_id=testuser4.id ) + self.purge_user( user_id=testuser4.id ) + testuser4.refresh() + if not testuser4.purged: + raise AssertionError( 'User %s was not marked as purged.' % testuser4.email ) + # Make sure DefaultUserPermissions deleted EXCEPT FOR THE PRIVATE ROLE + if len( testuser4.default_permissions ) != 1: + raise AssertionError( 'DefaultUserPermissions for user %s were not deleted.' % testuser4.email ) + for dup in testuser4.default_permissions: + role = galaxy.model.Role.get( dup.role_id ) + if role.type != 'private': + raise AssertionError( 'DefaultUserPermissions for user %s are not related with the private role.' % testuser4.email ) + # Make sure History deleted + for history in testuser4.histories: + if not history.deleted: + raise AssertionError( 'User %s has active history id %d after their account was marked as purged.' % ( testuser4.email, hda.id ) ) + # Make sure DefaultHistoryPermissions deleted EXCEPT FOR THE PRIVATE ROLE + if len( history.default_permissions ) != 1: + raise AssertionError( 'DefaultHistoryPermissions for history id %d were not deleted.' % history.id ) + for dhp in history.default_permissions: + role = galaxy.model.Role.get( dhp.role_id ) + if role.type != 'private': + raise AssertionError( 'DefaultHistoryPermissions for history id %d are not related with the private role.' % history.id ) + # Make sure HistoryDatasetAssociation deleted + for hda in history.datasets: + if not hda.deleted: + raise AssertionError( 'HistoryDatasetAssociation id %d was not deleted.' % hda.id ) + # Make sure Dataset deleted + d = galaxy.model.Dataset.filter( galaxy.model.Dataset.table.c.id==hda.dataset_id ).first() + if not d.deleted: + raise AssertionError( 'Dataset id %d was not deleted.' % d.id ) + # Make sure UserGroupAssociations deleted + if testuser4.groups: + raise AssertionError( 'User %s has active group id %d after their account was marked as purged.' % ( testuser4.email, uga.id ) ) + # Make sure UserRoleAssociations deleted EXCEPT FOR THE PRIVATE ROLE + if len( testuser4.roles ) != 1: + raise AssertionError( 'UserRoleAssociations for user %s were not deleted.' % testuser4.email ) + for ura in testuser4.roles: + role = galaxy.model.Role.get( ura.role_id ) + if role.type != 'private': + raise AssertionError( 'UserRoleAssociations for user %s are not related with the private role.' % testuser4.email ) + def test_81_purge_group( self ): """Testing purging a group""" - group_id = str( another_test_group.id ) + group_id = str( group_two.id ) + self.mark_group_deleted( group_id ) self.purge_group( group_id ) # Make sure there are no UserGroupAssociations uga = galaxy.model.UserGroupAssociation.filter( galaxy.model.UserGroupAssociation.table.c.group_id == group_id ).all() @@ -387,10 +587,23 @@ class TestHistory( TwillTestCase ): gra = galaxy.model.GroupRoleAssociation.filter( galaxy.model.GroupRoleAssociation.table.c.group_id == group_id ).all() if gra: raise AssertionError( "Purging the group did not delete the GroupRoleAssociations for group_id '%s'" % group_id ) - def test_66_purge_role( self ): + def test_84_purge_role( self ): """Testing purging a role""" - role_id = str( another_test_role.id ) + role_id = str( role_two.id ) + self.mark_role_deleted( role_id ) self.purge_role( role_id ) + # Make sure there are no UserRoleAssociations + uras = galaxy.model.UserRoleAssociation.filter( galaxy.model.UserRoleAssociation.table.c.role_id == role_id ).all() + if uras: + raise AssertionError( "Purging the role did not delete the UserRoleAssociations for role_id '%s'" % role_id ) + # Make sure there are no DefaultUserPermissions associated with the Role + dups = galaxy.model.DefaultUserPermissions.filter( galaxy.model.DefaultUserPermissions.table.c.role_id == role_id ).all() + if dups: + raise AssertionError( "Purging the role did not delete the DefaultUserPermissions for role_id '%s'" % role_id ) + # Make sure there are no DefaultHistoryPermissions associated with the Role + dhps = galaxy.model.DefaultHistoryPermissions.filter( galaxy.model.DefaultHistoryPermissions.table.c.role_id == role_id ).all() + if dhps: + raise AssertionError( "Purging the role did not delete the DefaultHistoryPermissions for role_id '%s'" % role_id ) # Make sure there are no GroupRoleAssociations gra = galaxy.model.GroupRoleAssociation.filter( galaxy.model.GroupRoleAssociation.table.c.role_id == role_id ).all() if gra: @@ -399,7 +612,7 @@ class TestHistory( TwillTestCase ): adra = galaxy.model.ActionDatasetRoleAssociation.filter( galaxy.model.ActionDatasetRoleAssociation.table.c.role_id == role_id ).all() if adra: raise AssertionError( "Purging the role did not delete the ActionDatasetRoleAssociations for role_id '%s'" % role_id ) - def test_69_purge_library( self ): + def test_87_purge_library( self ): """Testing purging a library""" self.purge_library( str( library.id ) ) # Make sure the library was purged @@ -417,10 +630,12 @@ class TestHistory( TwillTestCase ): for lfda in library_folder.datasets: lfda.refresh() if not lfda.deleted: - raise AssertionError( 'The library_folder_dataset_association id %s named "%s" has not been marked as deleted.' % ( str( lfda.id ), lfda.name ) ) + raise AssertionError( 'The library_folder_dataset_association id %s named "%s" has not been marked as deleted.' % \ + ( str( lfda.id ), lfda.name ) ) # Make sure all of the datasets have been deleted dataset = lfda.dataset dataset.refresh() if not dataset.deleted: - raise AssertionError( 'The dataset with id "%s" has not been marked as deleted when it should have been.' % str( lfda.dataset.id ) ) + raise AssertionError( 'The dataset with id "%s" has not been marked as deleted when it should have been.' % \ + str( lfda.dataset.id ) ) check_folder( library.root_folder ) diff --git a/universe_wsgi.ini.sample b/universe_wsgi.ini.sample index c6bc12a88ce..63021acc775 100644 --- a/universe_wsgi.ini.sample +++ b/universe_wsgi.ini.sample @@ -141,6 +141,9 @@ use_interactive = True # Can users register new accounts? #allow_user_creation = True +# Can an admin user delete user accounts? +#allow_user_deletion = False + # ---- Job Execution -------------------------------------------------------- # Number of concurrent jobs to run (local job runner)