From dfdbef9d36b5be051d1d747856734d70f3aefdfd Mon Sep 17 00:00:00 2001 From: Nate Coraor Date: Thu, 9 Oct 2008 17:05:57 -0400 Subject: [PATCH] Reintroduce roles. Unfinished stuff: setting default user and history permissions, user-side permissions settings on datasets, viewing associated datasets under the role page in the admin controler (is this even necessary?), filtering out roles by type. I haven't addressed tests, so the security-related functional tests will fail. I haven't looked at history sharing yet either. Cleanup to remove unused methods is needed in the admin controller. And the admin templates are a bit messy. --- lib/galaxy/model/__init__.py | 114 ++--- lib/galaxy/model/mapping.py | 114 +++-- lib/galaxy/security/__init__.py | 414 +++++++++--------- lib/galaxy/tools/__init__.py | 4 +- lib/galaxy/tools/actions/__init__.py | 4 +- lib/galaxy/tools/actions/upload.py | 4 +- lib/galaxy/tools/parameters/basic.py | 33 +- lib/galaxy/web/controllers/admin.py | 392 +++++++---------- lib/galaxy/web/controllers/async.py | 2 +- lib/galaxy/web/controllers/root.py | 58 ++- lib/galaxy/web/controllers/user.py | 2 + lib/galaxy/web/framework/__init__.py | 4 +- .../dataset_security/deleted_groups.mako | 2 - .../admin/dataset_security/group_create.mako | 2 +- templates/admin/dataset_security/groups.mako | 20 +- .../specified_users_groups.mako | 26 -- templates/admin/index.mako | 1 + templates/admin/library/browser.mako | 4 + templates/admin/library/common.mako | 167 +++---- templates/admin/library/new_dataset.mako | 24 +- templates/dataset/edit_attributes.mako | 87 ---- test/base/twilltestcase.py | 5 +- .../functional/test_security_and_libraries.py | 2 +- tools/data_source/encode_import_code.py | 2 +- tools/data_source/microbial_import_code.py | 2 +- tools/maf/maf_to_bed_code.py | 2 +- 26 files changed, 642 insertions(+), 849 deletions(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 4316cbb53b2..5d907a28a76 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -103,70 +103,16 @@ class JobToOutputDatasetAssociation( object ): self.name = name self.dataset = dataset -class GroupDatasetAssociation( object ): - def __init__( self, group, dataset, permitted_actions=[] ): - if isinstance( group, GroupDatasetAssociation ) or \ - isinstance( group, DefaultUserGroupAssociation ) or \ - isinstance( group, DefaultHistoryGroupAssociation ): - group = group.group - self.group = group - if isinstance( dataset, HistoryDatasetAssociation ): - dataset = dataset.dataset - self.dataset = dataset - self.permitted_actions = permitted_actions - def add_permitted_action( self, action ): - if action not in self.permitted_actions: - return self.permitted_actions.append( action ) - raise 'action (%s) already exists in permitted actions list (%s: %s).' % ( action, str( self.id ), str( self.permitted_actions ) ) - def remove_permitted_action( self, action ): - return self.permitted_actions.remove( action ) - class Group( object ): - public_id = None permitted_actions = galaxy.security.get_permitted_actions( 'GROUP' ) - def __init__( self, name = None, priority = 0 ): + def __init__( self, name = None ): self.name = name - self.priority = priority - @classmethod - def get_public_group( cls ): - return Group.get( cls.public_id ) - @classmethod - def set_public_group( cls, group ): - # We store the id instead of the object, because of alchemy sessions - if isinstance( group, Group ): - group = group.id - cls.public_id = group - @classmethod - def guess_public_group( cls ): - # Retrieve from database and store public group id - group = Group.filter_by( name='public' ).first() - cls.set_public_group( group ) class UserGroupAssociation( object ): def __init__( self, user, group ): self.user = user self.group = group -class DefaultUserGroupAssociation( object ): - def __init__( self, user, group, permitted_actions ): - if isinstance( group, GroupDatasetAssociation ) or \ - isinstance( group, DefaultUserGroupAssociation ) or \ - isinstance( group, DefaultHistoryGroupAssociation ): - group = group.group - self.user = user - self.group = group - self.permitted_actions = permitted_actions - -class DefaultHistoryGroupAssociation( object ): - def __init__( self, history, group, permitted_actions ): - if isinstance( group, GroupDatasetAssociation ) or \ - isinstance( group, DefaultUserGroupAssociation ) or \ - isinstance( group, DefaultHistoryGroupAssociation ): - group = group.group - self.history = history - self.group = group - self.permitted_actions = permitted_actions - class History( object ): def __init__( self, id=None, name=None, user=None ): self.id = id @@ -239,6 +185,64 @@ class History( object ): # self.history = history # self.datasets = [] +class UserRoleAssociation( object ): + def __init__( self, user, role ): + self.user = user + self.role = role + +class GroupRoleAssociation( object ): + def __init__( self, group, role ): + self.group = group + self.role = role + +class Role( object ): + private_id = None + types = Bunch( + PRIVATE = 'private', + SYSTEM = 'system', + USER = 'user', + ADMIN = 'admin' + ) + def __init__( self, name="", description="", type="system", deleted=False ): + self.name = name + self.description = description + self.type = type + self.deleted = deleted + +class ActionObjectRolesAssociation( object ): + """ + Base class for an action->something->roles association, so set_roles + doesn't have to be a member of each class that uses it. + """ + def set_roles( self, roles ): + """ + Convenience method to allow roles to be a list of Roles or role ids. + """ + def get_id( x ): + if isinstance( x, Role ): + return x.id + else: + return x + self.role_ids = map( get_id, roles ) + +class ActionDatasetRolesAssociation( ActionObjectRolesAssociation ): + def __init__( self, action, dataset, roles ): + self.action = action + self.dataset = dataset + self.set_roles( roles ) + +class DefaultUserPermissions( ActionObjectRolesAssociation ): + def __init__( self, user, action, roles ): + self.user = user + self.action = action + self.set_roles( roles ) + +class DefaultHistoryPermissions( ActionObjectRolesAssociation ): + def __init__( self, history, action, roles ): + self.history = history + self.action = action + self.set_roles( roles ) + class Dataset( object ): states = Bunch( NEW = 'new', QUEUED = 'queued', diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index 2829c2a6f0c..5fd0d4f434c 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -117,7 +117,6 @@ Group.table = Table( "galaxy_group", metadata, Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), Column( "name", TEXT, index=True, unique=True ), - Column( "priority", Integer ), Column( "deleted", Boolean, index=True, default=False ) ) UserGroupAssociation.table = Table( "user_group_association", metadata, @@ -127,32 +126,49 @@ UserGroupAssociation.table = Table( "user_group_association", metadata, Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ) ) -GroupDatasetAssociation.table = Table( "group_dataset_association", metadata, +UserRoleAssociation.table = Table( "user_role_association", metadata, Column( "id", Integer, primary_key=True ), - Column( "group_id", Integer, ForeignKey( "galaxy_group.id" ), index=True ), - Column( "dataset_id", Integer, ForeignKey( "dataset.id" ), index=True ), - Column( "create_time", DateTime, default=now ), - Column( "update_time", DateTime, default=now, onupdate=now ), - Column( "permitted_actions", JSONType(), default=[] ) ) - -# The following table stores the permissions that are considered the defaults for new histories when they are created by a user -DefaultUserGroupAssociation.table = Table( "default_user_group_association", metadata, - Column( "id", Integer, primary_key=True ), - Column( "group_id", Integer, ForeignKey( "galaxy_group.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), + Column( "role_id", Integer, ForeignKey( "role.id" ), index=True ), Column( "create_time", DateTime, default=now ), - Column( "update_time", DateTime, default=now, onupdate=now ), - Column( "permitted_actions", JSONType(), default=[] ) ) + Column( "update_time", DateTime, default=now, onupdate=now ) ) -# The following table stores the default permissions assigned to histories for datasets -# that need permissions ( dataset permissions that cannot be determined based on ancestor ) -DefaultHistoryGroupAssociation.table = Table( "default_history_group_association", metadata, +GroupRoleAssociation.table = Table( "group_role_association", metadata, Column( "id", Integer, primary_key=True ), Column( "group_id", Integer, ForeignKey( "galaxy_group.id" ), index=True ), - Column( "history_id", Integer, ForeignKey( "history.id" ), index=True ), + Column( "role_id", Integer, ForeignKey( "role.id" ), index=True ), + Column( "create_time", DateTime, default=now ), + Column( "update_time", DateTime, default=now, onupdate=now ) ) + +Role.table = Table( "role", metadata, + Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), - Column( "permitted_actions", JSONType(), default=[] ) ) + Column( "name", TEXT, index=True, unique=True ), + Column( "description", TEXT ), + Column( "type", TEXT, index=True ), + Column( "deleted", Boolean, index=True, default=False ) ) + +# TODO: should role_ids be made in to another association table? +ActionDatasetRolesAssociation.table = Table( "action_dataset_roles_association", metadata, + Column( "id", Integer, primary_key=True ), + Column( "create_time", DateTime, default=now ), + Column( "update_time", DateTime, default=now, onupdate=now ), + Column( "action", TEXT ), + Column( "dataset_id", Integer, ForeignKey( "dataset.id" ), index=True ), + Column( "role_ids", JSONType(), default=[] ) ) + +DefaultUserPermissions.table = Table( "default_user_permissions", metadata, + Column( "id", Integer, primary_key=True ), + Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), + Column( "action", TEXT ), + Column( "role_ids", JSONType(), default=[] ) ) + +DefaultHistoryPermissions.table = Table( "default_history_permissions", metadata, + Column( "id", Integer, primary_key=True ), + Column( "history_id", Integer, ForeignKey( "history.id" ), index=True ), + Column( "action", TEXT ), + Column( "role_ids", JSONType(), default=[] ) ) LibraryFolderDatasetAssociation.table = Table( "library_folder_dataset_association", metadata, Column( "id", Integer, primary_key=True ), @@ -399,7 +415,7 @@ assign_mapper( context, History, History.table, ) ) assign_mapper( context, User, User.table, - properties=dict( histories=relation( History, backref="user", + properties=dict( histories=relation( History, backref="user", order_by=desc(History.table.c.update_time) ), active_histories=relation( History, primaryjoin=( ( History.table.c.user_id == User.table.c.id ) & ( not_( History.table.c.deleted ) ) ), order_by=desc( History.table.c.update_time ) ), stored_workflow_menu_entries=relation( StoredWorkflowMenuEntry, backref="user", @@ -408,29 +424,49 @@ assign_mapper( context, User, User.table, ) ) assign_mapper( context, Group, Group.table, - properties=dict( users=relation( UserGroupAssociation ), - datasets=relation( GroupDatasetAssociation ) ) ) + properties=dict( users=relation( UserGroupAssociation ) ) ) assign_mapper( context, UserGroupAssociation, UserGroupAssociation.table, properties=dict( user=relation( User, backref = "groups" ), group=relation( Group, backref = "members" ) ) ) -assign_mapper( context, GroupDatasetAssociation, GroupDatasetAssociation.table, - properties=dict( dataset=relation( Dataset, backref = "groups" ), - group=relation( Group, backref = "group_datasets" ) ) ) +assign_mapper( context, DefaultUserPermissions, DefaultUserPermissions.table, + properties=dict( user=relation( User, backref = "default_permissions" ) ) ) -assign_mapper( context, DefaultUserGroupAssociation, DefaultUserGroupAssociation.table, - properties=dict( user=relation( User, backref = "default_groups" ), - group=relation( Group ) ) ) +assign_mapper( context, DefaultHistoryPermissions, DefaultHistoryPermissions.table, + properties=dict( history=relation( History, backref = "default_permissions" ) ) ) -assign_mapper( context, DefaultHistoryGroupAssociation, DefaultHistoryGroupAssociation.table, - properties=dict( history=relation( History, backref = "default_groups" ), - group=relation( Group ) ) ) +assign_mapper( context, Role, Role.table, + properties=dict( + users=relation( UserRoleAssociation ), + groups=relation( GroupRoleAssociation ) + ) +) + +assign_mapper( context, UserRoleAssociation, UserRoleAssociation.table, + properties=dict( + user=relation( User, backref="roles" ), + role=relation( Role ) + ) +) + +assign_mapper( context, GroupRoleAssociation, GroupRoleAssociation.table, + properties=dict( + group=relation( Group, backref="roles" ), + role=relation( Role ) + ) +) + +assign_mapper( context, ActionDatasetRolesAssociation, ActionDatasetRolesAssociation.table, + properties=dict( + dataset=relation( Dataset, backref="actions" ) + ) +) assign_mapper( context, Library, Library.table, properties=dict( root_folder=relation( LibraryFolder, - backref = backref( "library_root" ) ) + backref=backref( "library_root" ) ) ) ) assign_mapper( context, LibraryFolder, LibraryFolder.table, @@ -605,13 +641,13 @@ def init( file_path, url, engine_options={}, create_tables=False ): result.create_tables = create_tables #load local galaxy security policy result.security_agent = GalaxyRBACAgent( result ) - # Ensure group named 'public' exists - public_group = result.Group.filter_by( name='public' ).first() - if not public_group: - public_group = result.security_agent.create_group( name = 'public' ) - # Store public group id - result.security_agent.set_public_group( public_group ) - log.debug( "Public Group identified as id = %s." % ( public_group.id ) ) + # Create private roles if necessary. + if not len( result.Role.select() ): + for user in result.User.select(): + role = Role( name = user.email, description = 'Private Role for ' + user.email, type = 'private' ) + role.flush() + ura = UserRoleAssociation( user = user, role = role ) + ura.flush() return result def get_suite(): diff --git a/lib/galaxy/security/__init__.py b/lib/galaxy/security/__init__.py index 90aaa0ac3db..7e2c462fbcc 100644 --- a/lib/galaxy/security/__init__.py +++ b/lib/galaxy/security/__init__.py @@ -7,52 +7,55 @@ from galaxy.util.bunch import Bunch log = logging.getLogger(__name__) +class Action( object ): + def __init__( self, action, description, model ): + self.action = action + self.description = description + self.model = model + class RBACAgent: """Class that handles galaxy security""" permitted_actions = Bunch( - # The ability to edit the metadata of the associated dataset - DATASET_EDIT_METADATA = 'edit metadata', - # The ability to change the permissions of a dataset (so specifically, to add and modify - # group_dataset_association rows where the dataset is the dataset for which the permission is set). - DATASET_MANAGE_PERMISSIONS = 'manage permissions', - # The ability to perform any read only operation on the dataset (view, display at external site, - # use in a job, etc). - DATASET_ACCESS = 'access' - ) - permitted_action_descriptions = Bunch( - DATASET_EDIT_METADATA = "User can edit this dataset's metadata in the library", - DATASET_MANAGE_PERMISSIONS = "User can manage the groups and group permitted actions associated with this dataset", - DATASET_ACCESS = "User can import this dataset into their history for analysis" + DATASET_EDIT_METADATA = Action( + "edit metadata", "Role members can edit this dataset's metadata in the library", "grant" ), + DATASET_MANAGE_PERMISSIONS = Action( + "manage permissions", "Role members can manage the groups and group permitted actions associated with this dataset", "grant" ), + DATASET_ACCESS = Action( + "access", "Role members can import this dataset into their history for analysis", "restrict" ) ) + def get_action( self, name, default=None ): + """ + Get a permitted action by its dict key or action name + """ + for k, v in self.permitted_actions.items(): + if k == name or v.action == name: + return v + return default + def get_actions( self ): + """ + Get all permitted actions as a list + """ + return self.permitted_actions.__dict__.values() def allow_action( self, user, action, **kwd ): raise 'No valid method of checking action (%s) on %s for user %s.' % ( action, kwd, user ) def guess_derived_permissions_for_datasets( self, datasets = [] ): raise "Unimplemented Method" def associate_components( self, **kwd ): raise 'No valid method of associating provided components: %s' % kwd - def get_group( self, id ): - raise 'No valid method of retrieving group %s' % ( id ) - def create_group( self, **kwd ): - raise 'No valid method of creating group with %s' % ( kwd ) - def create_private_user_group( self, user ): + def create_private_user_role( self, user ): raise "Unimplemented Method" - def user_set_default_access( self, user, permissions = None, history = False, dataset = False ): + def get_private_user_role( self, user ): + raise "Unimplemented Method" + def user_set_default_permissions( self, user, permissions = None, history = False, dataset = False ): raise "Unimplemented Method" def setup_new_user( self, user ): - self.user_set_default_access( user, history = True, dataset = True ) - self.associate_components( user=user, group=self.get_public_group() ) - def history_set_default_access( self, history, permissions=None, dataset=False ): - raise "Unimplemented Method" - def set_public_group( self, group ): - raise "Unimplemented Method" - def get_public_group( self ): - raise "Unimplemented Method" - def guess_public_group( self ): + self.create_private_user_role( user ) + self.user_set_default_permissions( user, history = True, dataset = True ) + #self.associate_components( user=user, group=self.get_public_group() ) + def history_set_default_permissions( self, history, permissions=None, dataset=False, bypass_manage_permission=False ): raise "Unimplemented Method" def set_dataset_permissions( self, dataset, permissions ): raise "Unimplemented Method" - def set_dataset_permitted_actions( self, dataset ): - raise "Unimplemented Method" def get_component_associations( self, **kwd ): raise "Unimplemented Method" def components_are_associated( self, **kwd ): @@ -63,18 +66,6 @@ class RBACAgent: form, ensure that they match our actual permitted actions. """ return filter( lambda x: x is not None, [ self.permitted_actions.get( action_string ) for action_string in permitted_action_strings ] ) - def get_permitted_action_description( self, permitted_action ): - """ - Return the description of a permitted_action, regardless of - whether permitted_action is a key or value. - """ - if self.permitted_action_descriptions.get( permitted_action ) is not None: - return self.permitted_action_descriptions.get( permitted_action ) - else: - for k, v in self.permitted_actions.items(): - if v == permitted_action: - return self.permitted_action_descriptions.get( k ) - return permitted_action # so at least something useful is printable class GalaxyRBACAgent( RBACAgent ): def __init__( self, model, permitted_actions=None ): @@ -89,184 +80,212 @@ class GalaxyRBACAgent( RBACAgent ): """Returns true when user has permission to perform an action""" if not isinstance( dataset, self.model.Dataset ): dataset = dataset.dataset - if user is None: - try: - public_assoc = [ gda for gda in dataset.groups if gda.group == self.get_public_group() ][0] - except: - # There'll be an IndexError if the dataset doesn't have a gda with public - return False - if action in public_assoc.permitted_actions: - return True - elif user is not None: - # Loop through permitted_actions and if allowed return true: - # Check permitted_actions associated with dataset through groups - for group_dataset_assoc in dataset.groups: - if self.components_are_associated( user = user, group = group_dataset_assoc.group ): - if action in group_dataset_assoc.permitted_actions: - return True - return False # No user and dataset not in public group, or user lacks permission + if not user: + if action == self.permitted_actions.DATASET_ACCESS and action.action not in [ adra.action for adra in dataset.actions ]: + return True # anons only get access, and only if there are no roles required for the access action + # other actions (or if the dataset has roles defined for the access action) fall through to the false below + elif action.action not in [ adra.action for adra in dataset.actions ]: + if action.model == 'restrict': + return True # implicit access to restrict-style actions if the dataset does not have the action + # grant-style actions fall through to the false below + else: + # collect a user's roles and their groups' roles + user_roles = [ ura.role for ura in user.roles ] + for group in [ uga.group for uga in user.groups ]: + for role in [ gra.role for gra in group.roles ]: + if role not in user_roles: + user_roles.append( role ) + user_role_ids = sorted( [ r.id for r in user_roles ] ) + for adra in dataset.actions: + if action.action != adra.action: + continue + # the filter() returns a list of the dataset's role ids + # of which the user is not a member. so an empty list + # means the user has all of the required roles. + if not filter( lambda x: x not in user_role_ids, adra.role_ids ): + return True # user has all of the roles required to perform the action + break # fall through to the false. user is missing at least one required role + return False # default is to reject def guess_derived_permissions_for_datasets( self, datasets=[] ): - """Returns a list of group/action tuples for the output dataset based upon provided datasets""" - intersect = None - priority_access_perms = None + """Returns a dict of { action : [ role, role, ... ] } for the output dataset based upon provided datasets""" + perms = {} for dataset in datasets: - # Determine access groups for output datasets - these groups are the - # intersection across all inputs. If we end up with no intersection - # between inputs, then we rely on priorities - if isinstance( dataset, self.model.HistoryDatasetAssociation ): + if not isinstance( dataset, self.model.Dataset ): dataset = dataset.dataset - assocs = [ assoc for assoc in dataset.groups ] - for assoc in assocs: - if priority_access_perms is None or priority_access_perms[0].priority < assoc.group.priority: - priority_access_perms = ( assoc.group, assoc.permitted_actions ) - if intersect is None: - intersect = [ (a.group, a.permitted_actions) for a in assocs ] - else: - # Intersect existing perms with new perms - #access_assocs = filter( lambda x: x.group in [ a.group for a in access_assocs ], assocs ) - new_intersect = [] - for group, actions in intersect: - matches = filter( lambda x: x.group == group, assocs ) - # Could a dataset ever have more than one GDA with the same group id? - if len( matches ) > 1: - log.error( "Unable to derive permissions, duplicate group_dataset_association rows exist for group %d, dataset %d" % (group.id, dataset.id) ) - elif len( matches ) == 1: - # compare permitted_actions - pa_intersect = filter( lambda x: x in actions, matches[0].permitted_actions ) - new_intersect.append( ( group, pa_intersect ) ) - intersect = new_intersect - # If we have no groups left after intersection, take the highest priority group - if not intersect: - if priority_access_perms: - intersect = [ priority_access_perms ] - return intersect - def get_group( self, id ): - return self.model.Group.get( id ) - raise 'No valid method of retrieving requested group by id %s' % ( str( id ) ) - def get_group_by_name( self, name ): - return self.model.Group.filter_by( name=name ).first() - raise 'No valid method of retrieving requested group by name %s' % ( str( name ) ) - def create_group( self, **kwd ): - rval = self.model.Group( **kwd ) - rval.flush() - return rval - raise 'No valid method of creating group with %s' % ( kwd ) + these_perms = {} + # initialize blank perms + for a in self.get_actions(): + these_perms[ a.action ] = [] + # collect this dataset's perms + for adra in dataset.actions: + these_perms[ adra.action ] = adra.role_ids + # join or intersect this dataset's permissions with others + for action_name, role_ids in these_perms.items(): + if action_name not in perms.keys(): + perms[ action_name ] = role_ids + else: + if self.get_action( action_name ).model == 'grant': + # intersect existing roles with new roles + perms[ action_name ] = filter( lambda x: x in perms[ action_name ], role_ids ) + elif self.get_action( action_name ).model == 'restrict': + # join existing roles with new roles + perms[ action_name ].extend( filter( lambda x: x not in perms[ action_name ], role_ids ) ) + ##perms = [ ( k, tuple( v ) ) for k, v in perms.items() ] # a list of ( action, ( role_id, role_id, ... ) ) tuples + return perms def associate_components( self, **kwd ): - assert len( kwd ) == 2, 'You must specify exactly 2 Galaxy security components to associate.' - if 'dataset' in kwd: - if 'group' in kwd: - return self.associate_group_dataset( kwd['group'], kwd['dataset'] ) - elif 'permissions' in kwd: - return self.associate_group_dataset( kwd['permissions'][0], kwd['dataset'], kwd['permissions'][1] ) - elif 'user' in kwd: + ##assert len( kwd ) == 2, 'You must specify exactly 2 Galaxy security components to associate.' + if 'user' in kwd: if 'group' in kwd: return self.associate_user_group( kwd['user'], kwd['group'] ) + elif 'role' in kwd: + return self.associate_user_role( kwd['user'], kwd['role'] ) + elif 'role' in kwd: + if 'group' in kwd: + return self.associate_group_role( kwd['group'], kwd['role'] ) + if 'action' in kwd: + if 'dataset' in kwd and 'roles' in kwd: + return self.associate_action_dataset_roles( kwd['action'], kwd['dataset'], kwd['roles'] ) raise 'No valid method of associating provided components: %s' % kwd - def associate_group_dataset( self, group, dataset, permitted_actions=[ RBACAgent.permitted_actions.DATASET_ACCESS ] ): - # HACK: The default permitted_actions should really not be used... need to find cases where this is done and correct it - assoc = self.model.GroupDatasetAssociation( group, dataset, permitted_actions ) - assoc.flush() - return assoc def associate_user_group( self, user, group ): assoc = self.model.UserGroupAssociation( user, group ) assoc.flush() return assoc - def create_private_user_group( self, user ): - # Create private group - group_name = "%s private group" % user.email - group = self.model.Group( name=group_name, priority=10 ) - group.flush() - # Add user to group - self.associate_components( group=group, user=user ) - group.flush() - return group - def user_set_default_access( self, user, permissions = None, history = False, dataset = False ): - if permissions is None: - permissions = [ ( self.create_private_user_group( user ), self.permitted_actions.__dict__.values() ) ] - if permissions is not None: - # Delete all of the previous DefaultUserGroupAssociation - for duga in user.default_groups: #this is the association not the actual group - duga.delete() - duga.flush() - # Add the new DefaultUserGroupAssociation - for group, permitted_actions in permissions: - duga = self.model.DefaultUserGroupAssociation( user, group, permitted_actions ) - duga.flush() + def associate_user_role( self, user, role ): + assoc = self.model.UserRoleAssociation( user, role ) + assoc.flush() + return assoc + def associate_group_role( self, group, role ): + assoc = self.model.GroupRoleAssociation( group, role ) + assoc.flush() + return assoc + def associate_action_dataset_roles( self, action, dataset, roles ): + assoc = self.model.ActionDatasetRolesAssociation( action, dataset, roles ) + assoc.flush() + return assoc + def create_private_user_role( self, user ): + # Create private role + role = self.model.Role( name=user.email, description='Private Role for ' + user.email, type=self.model.Role.types.PRIVATE ) + role.flush() + # Add user to role + self.associate_components( role=role, user=user ) + return role + def get_private_user_role( self, user, auto_create=False ): + role = self.model.Role.get_by( name=user.email, type=self.model.Role.types.PRIVATE ) + if not role: + if auto_create: + return self.create_private_user_role( user ) + else: + return None + def user_set_default_permissions( self, user, permissions = {}, history = False, dataset = False ): + if user is None: + return None + if not permissions: + permissions = { self.permitted_actions.DATASET_MANAGE_PERMISSIONS : [ self.get_private_user_role( user, auto_create=True ) ] } + # Delete all of the previous defaults + for dup in user.default_permissions: + dup.delete() + dup.flush() + # Add the new defaults (if any) + for action, roles in permissions.items(): + if isinstance( action, Action ): + action = action.action + dup = self.model.DefaultUserPermissions( user, action, roles ) + dup.flush() if history: for history in user.active_histories: - self.history_set_default_access( history, permissions=permissions, dataset=dataset ) - def user_get_default_access( self, user ): - return [ ( duga.group, duga.permitted_actions ) for duga in user.default_groups ] - def history_set_default_access( self, history, permissions=None, dataset=False ): - if permissions is None: - if history.user: - permissions = self.user_get_default_access( history.user ) - else: - permissions = [ ( self.get_public_group(), self.permitted_actions.__dict__.values() ) ] - if permissions is not None: - # Delete all of the previous DefaultHistoryGroupAssociations - for dhga in history.default_groups: #this is the association not the actual group - dhga.delete() - dhga.flush() - # Add the new DefaultHistoryGroupAssociations - for group, permitted_actions in permissions: - dhga = self.model.DefaultHistoryGroupAssociation( history, group, permitted_actions ) - dhga.flush() + self.history_set_default_permissions( history, permissions=permissions, dataset=dataset ) + def user_get_default_permissions( self, user ): + perms = {} + for dup in user.default_permissions: + perms[ dup.action ] = dup.role_ids + return perms + def history_set_default_permissions( self, history, permissions = {}, dataset = False, bypass_manage_permission = False ): + if not history.user: + return None # default permissions on a userless history are none + if not permissions: + permissions = self.user_get_default_permissions( history.user ) + for dhp in history.default_permissions: + dhp.delete() + dhp.flush() + for action, roles in permissions.items(): + if isinstance( action, Action ): + action = action.action + dhp = self.model.DefaultHistoryPermissions( history, action, roles ) + dhp.flush() if dataset: - for hda in history.datasets: - for hda2 in hda.dataset.history_associations: - if history.user and hda2.history not in history.user.active_histories: - # This will occur when a user logs in and has datasets in their previously-public history. - self.set_dataset_permissions( hda.dataset, [ ( self.get_public_group(), [ self.permitted_actions.DATASET_ACCESS ] ) ] ) - #break + for hda_in_history in history.datasets: + if len( hda_in_history.dataset.library_associations ): + continue # dataset has a library association, don't change the permissions + if len( [ hda for hda in hda_in_history.dataset.history_associations if hda.history not in history.user.histories ] ): + continue # dataset has a history association in a history the user doesn't own, don't change the permissions + # bypass is used to change permissions of datasets in a userless history when logging in + if bypass_manage_permission or self.allow_action( history.user, self.permitted_actions.DATASET_MANAGE_PERMISSIONS, dataset=hda_in_history.dataset ): + self.set_dataset_permissions( hda_in_history.dataset, permissions ) + def history_get_default_permissions( self, history ): + perms = {} + for dhp in history.default_permissions: + perms[ dhp.action ] = dhp.role_ids + return perms + def set_dataset_permissions( self, dataset, permissions={} ): + # to delete permission on an action, pass in a blank list of role ids with that action + for action, role_ids in permissions.items(): + if isinstance( action, Action ): + action = action.action + for adra in dataset.actions: + if adra.action != action: + continue + if not role_ids: + adra.delete() else: - # At this point, we will have already created a gda for the public group and delete_assocs - # is set to False in order to preserve it. - if self.allow_action( history.user, self.permitted_actions.DATASET_MANAGE_PERMISSIONS, dataset=hda.dataset ): - self.set_dataset_permissions( hda.dataset, permissions, delete_existing_assocs=False ) - def history_get_default_access( self, history ): - return [ ( dhga.group, dhga.permitted_actions ) for dhga in history.default_groups ] - def get_public_group( self ): - return self.model.Group.get_public_group() - def set_public_group( self, group ): - return self.model.Group.set_public_group( group ) - def guess_public_group( self ): - return self.model.Group.guess_public_group() - def set_dataset_permissions( self, dataset, permissions, delete_existing_assocs=True ): - """ - Apply permissions (a list of (group, permitted_action) tuples) - to a dataset, removing any existing permissions. permissions can - also be a list of GroupDatasetAssociations (for simplicity). - """ - if isinstance( dataset, self.model.HistoryDatasetAssociation ): - dataset = dataset.dataset - if delete_existing_assocs: - for gda in dataset.groups: - gda.delete() - gda.flush() - if permissions and isinstance( permissions[0], self.model.GroupDatasetAssociation ): - permissions = [ ( gda.group, gda.permitted_actions ) for gda in permissions ] - for ptuple in permissions: - self.associate_components( dataset=dataset, permissions=ptuple ) - def get_dataset_permissions( self, dataset, group_id=None ): + adra.set_roles( role_ids ) + adra.flush() + break + else: + if role_ids: + self.associate_components( action=action, dataset=dataset, roles=role_ids ) + def get_dataset_permissions( self, dataset ): if not isinstance( dataset, self.model.Dataset ): dataset = dataset.dataset - if group_id is not None: - return [ ( gda.group, gda.permitted_actions ) for gda in dataset.groups if gda.group_id == int(group_id) ][0] - else: - return [ ( gda.group, gda.permitted_actions ) for gda in dataset.groups ] + perms = {} + for k, v in self.model.Dataset.permitted_actions.items(): + for adra in dataset.actions: + if adra.action != v.action: + continue + perms[ v.action ] = adra.role_ids + break + else: + perms[ v.action ] = [] + return perms + def copy_dataset_permissions( self, src, dst ): + if not isinstance( src, self.model.Dataset ): + src = src.dataset + if not isinstance( dst, self.model.Dataset ): + dst = dst.dataset + self.set_dataset_permissions( dst, self.get_dataset_permissions( src ) ) + def set_entity_role_associations( self, roles=[], users=[], groups=[], delete_existing_assocs=True ): + for role in roles: + if delete_existing_assocs: + for a in role.users + role.groups: + a.delete() + a.flush() + for user in users: + self.associate_components( user=user, role=role ) + for group in groups: + self.associate_components( group=group, role=role ) def get_component_associations( self, **kwd ): - # TODO, Nate: Make sure this method is functionally correct. assert len( kwd ) == 2, 'You must specify exactly 2 Galaxy security components to check for associations.' if 'dataset' in kwd: - if 'group' in kwd: - return self.model.GroupDatasetAssociation.filter_by( group_id=kwd['group'].id, dataset_id=kwd['dataset'].id ).first() + if 'action' in kwd: + return self.model.ActionDatasetRolesAssociation.filter_by( action = kwd['action'].action, dataset_id = kwd['dataset'].id ).first() elif 'user' in kwd: if 'group' in kwd: - return self.model.UserGroupAssociation.filter_by( group_id=kwd['group'].id, user_id=kwd['user'].id ).first() + return self.model.UserGroupAssociation.filter_by( group_id = kwd['group'].id, user_id = kwd['user'].id ).first() + elif 'role' in kwd: + return self.model.UserRoleAssociation.filter_by( role_id = kwd['role'].id, user_id = kwd['user'].id ).first() + elif 'group' in kwd: + if 'role' in kwd: + return self.model.GroupRoleAssociation.filter_by( role_id = kwd['role'].id, group_id = kwd['group'].id ).first() raise 'No valid method of associating provided components: %s' % kwd - def dataset_has_group( self, dataset_id, group_id ): - return bool( self.model.GroupDatasetAssociation.filter_by( group_id=group_id, dataset_id=dataset_id ).first() ) def check_folder_contents( self, user, entry ): """ Return true if there are any datasets under 'folder' that the @@ -288,7 +307,6 @@ class GalaxyRBACAgent( RBACAgent ): else: raise 'Passed an illegal object to check_folder_contents: %s' % type( entry ) - def get_permitted_actions( self, filter=None ): '''Utility method to return a subset of RBACAgent's permitted actions''' if filter is None: diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index 0468ec23ec8..7fb41c9491c 100644 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -1202,7 +1202,7 @@ class Tool: else: visible = False ext = fields.pop(0).lower() child_dataset = self.app.model.HistoryDatasetAssociation( extension=ext, parent_id=outdata.id, designation=designation, visible=visible, dbkey=outdata.dbkey, create_dataset=True ) - self.app.security_agent.set_dataset_permissions( child_dataset.dataset, outdata.dataset.groups ) + self.app.security_agent.copy_dataset_permissions( outdata.dataset, child_dataset.dataset ) # Move data from temp location to dataset location shutil.move( filename, child_dataset.file_name ) child_dataset.flush() @@ -1239,7 +1239,7 @@ class Tool: ext = fields.pop(0).lower() # Create new primary dataset primary_data = self.app.model.HistoryDatasetAssociation( extension=ext, designation=designation, visible=visible, dbkey=outdata.dbkey, create_dataset=True ) - self.app.security_agent.set_dataset_permissions( primary_data.dataset, outdata.dataset.groups ) + self.app.security_agent.copy_dataset_permissions( outdata.dataset, primary_data.dataset ) primary_data.flush() # Move data from temp location to dataset location shutil.move( filename, primary_data.file_name ) diff --git a/lib/galaxy/tools/actions/__init__.py b/lib/galaxy/tools/actions/__init__.py index ac146bca8b8..40fca521465 100644 --- a/lib/galaxy/tools/actions/__init__.py +++ b/lib/galaxy/tools/actions/__init__.py @@ -84,13 +84,13 @@ class DefaultToolAction( object ): if data.dbkey not in [None, '?']: input_dbkey = data.dbkey - # Determine output dataset permitted_actions list + # Determine output dataset permission/roles list existing_datasets = [ inp for inp in inp_data.values() if inp ] if existing_datasets: output_permissions = trans.app.security_agent.guess_derived_permissions_for_datasets( existing_datasets ) else: # No valid inputs, we will use history defaults - output_permissions = trans.app.security_agent.history_get_default_access( trans.history ) + output_permissions = trans.app.security_agent.history_get_default_permissions( trans.history ) # Build name for output datasets based on tool name and input names if len( input_names ) == 1: on_text = input_names[0] diff --git a/lib/galaxy/tools/actions/upload.py b/lib/galaxy/tools/actions/upload.py index b33e1a1fbd8..d21809a116d 100644 --- a/lib/galaxy/tools/actions/upload.py +++ b/lib/galaxy/tools/actions/upload.py @@ -78,7 +78,7 @@ class UploadToolAction( object ): def upload_empty(self, trans, err_code, err_msg): data = trans.app.model.HistoryDatasetAssociation( create_dataset=True ) - trans.app.security_agent.set_dataset_permissions( data.dataset, trans.app.security_agent.history_get_default_access( trans.history ) ) + trans.app.security_agent.set_dataset_permissions( data.dataset, trans.app.security_agent.history_get_default_permissions( trans.history ) ) data.name = err_code data.extension = "txt" data.dbkey = "?" @@ -172,7 +172,7 @@ class UploadToolAction( object ): info = 'uploaded %s file' %data_type data = trans.app.model.HistoryDatasetAssociation( history = trans.history, extension = ext, create_dataset = True ) - trans.app.security_agent.set_dataset_permissions( data.dataset, trans.app.security_agent.history_get_default_access( trans.history ) ) + trans.app.security_agent.set_dataset_permissions( data.dataset, trans.app.security_agent.history_get_default_permissions( trans.history ) ) data.name = file_name data.dbkey = dbkey data.info = info diff --git a/lib/galaxy/tools/parameters/basic.py b/lib/galaxy/tools/parameters/basic.py index 3f9d7d6d702..2a11d79385e 100644 --- a/lib/galaxy/tools/parameters/basic.py +++ b/lib/galaxy/tools/parameters/basic.py @@ -984,38 +984,6 @@ class DataToolParameter( ToolParameter ): TODO: The following must be fixed to test correctly for the new security_check tag in the DataToolParameter ( the last test below is broken ) Nate's next passs at the dataset security stuff will dramatically alter this anyway. - - >>> # Mock up a history (not connected to database) - >>> from galaxy.model import History, HistoryDatasetAssociation, User, Group - >>> from galaxy.util.bunch import Bunch - >>> from galaxy.security import GalaxyRBACAgent - >>> import galaxy.model - >>> security_agent = GalaxyRBACAgent( galaxy.model ) - >>> hist = History() - >>> hist.flush() - >>> group = Group( 'test' ) - >>> group.flush() - >>> Group.public_id = group.id - >>> dataset1 = HistoryDatasetAssociation( id=1, extension='txt', create_dataset=True ) - >>> dataset2 = HistoryDatasetAssociation( id=2, extension='bed', create_dataset=True ) - >>> security_agent.set_dataset_permissions( dataset2, [ ( group, security_agent.permitted_actions.__dict__.values() ) ] ) - >>> dataset3 = HistoryDatasetAssociation( id=3, extension='fasta', create_dataset=True ) - >>> dataset4 = HistoryDatasetAssociation( id=4, extension='png', create_dataset=True ) - >>> dataset5 = HistoryDatasetAssociation( id=5, extension='interval', create_dataset=True ) - >>> security_agent.set_dataset_permissions( dataset5, [ ( group, security_agent.permitted_actions.__dict__.values() ) ] ) - >>> hist.add_dataset( dataset1 ) - >>> hist.add_dataset( dataset2 ) - >>> hist.add_dataset( dataset3 ) - >>> hist.add_dataset( dataset4 ) - >>> hist.add_dataset( dataset5 ) - >>> p = DataToolParameter( None, XML( '' ) ) - >>> print p.name - blah - >>> print p.security_dict - {'test': ['access']} - >>> print p.get_html( trans=Bunch( history=hist, user=None, app=Bunch( security_agent = security_agent ) ) ) - """ def __init__( self, tool, elem ): @@ -1077,6 +1045,7 @@ class DataToolParameter( ToolParameter ): hid = "%s.%d" % ( parent_hid, i + 1 ) else: hid = str( hda.hid ) + # FIXME: This needs to be rewritten to use the new permissions model if not hda.dataset.state in [galaxy.model.Dataset.states.ERROR, galaxy.model.Dataset.states.DISCARDED] and hda.visible and trans.app.security_agent.allow_action( trans.user, hda.permitted_actions.DATASET_ACCESS, dataset=hda ): if self.security_dict: passed_security_check = True diff --git a/lib/galaxy/web/controllers/admin.py b/lib/galaxy/web/controllers/admin.py index 0c71bebe4d8..3c9831fe31f 100644 --- a/lib/galaxy/web/controllers/admin.py +++ b/lib/galaxy/web/controllers/admin.py @@ -59,6 +59,71 @@ class Admin( BaseController ): msg = params.msg return trans.fill_template( '/admin/dataset_security/index.mako', msg=msg ) + # Galaxy Role Stuff + @web.expose + def roles( self, trans, **kwd ): + if not self.user_is_admin( trans ): + return trans.show_error_message( no_privilege_msg ) + p = util.Params( kwd ) + msg = p.msg + if 'create' in kwd: + if p.create == 'submitted': + role = trans.app.model.Role( name = util.restore_text( p.name ), + description = util.restore_text( p.description ), + type = trans.app.model.Role.types.ADMIN ) + role.flush() + trans.response.send_redirect( url_for( action='roles', associate=True ) ) + return trans.show_form( + web.FormBuilder( action = web.url_for(), title = "Create a new role", name="role", submit_text = "Create" ) + .add_text( name = "name", label = "Name", value = "New Role" ) + .add_text( name = "description", label = "Description", value = "" ) + .add_input( 'hidden', '', 'create', 'submitted', use_label = False ) + .add_input( 'hidden', '', 'id', id, use_label = False ) ) + return trans.fill_template( '/admin/dataset_security/roles.mako', + roles=trans.app.model.Role.select(), + msg=msg ) + + @web.expose + def role( self, trans, id=None, **kwd ): + if not self.user_is_admin( trans ): + return trans.show_error_message( no_privilege_msg ) + p = util.Params( kwd ) + msg = p.msg + if not id: + return trans.show_error_message( "Galaxy can't associate roles unless you provide a role id" ) + role = trans.app.model.Role.get( id ) + if not role: + return trans.show_error_message( "The selected role is invalid" ) + if role.type == role.types.PRIVATE: + return trans.show_error_message( "The Public Access Role cannot be modified" ) + if 'submitted' in kwd: + if p.submitted == 'associate': + in_users = [ trans.app.model.User.get( x ) for x in listify( p.in_users ) ] + in_groups = [ trans.app.model.Group.get( x ) for x in listify( p.in_groups ) ] + trans.app.security_agent.set_entity_role_associations( roles=[ role ], users=in_users, groups=in_groups ) + role.refresh() + in_users = [] + out_users = [] + in_groups = [] + out_groups = [] + for user in sorted( trans.app.model.User.select(), lambda x,y: cmp( x.email, y.email ) ): + if user in [ x.user for x in role.users ]: + in_users.append( ( user.id, user.email ) ) + else: + out_users.append( ( user.id, user.email ) ) + for group in sorted( trans.app.model.Group.select(), lambda x,y: cmp( x.name, y.name ) ): + if group in [ x.group for x in role.groups ]: + in_groups.append( ( group.id, group.name ) ) + else: + out_groups.append( ( group.id, group.name ) ) + return trans.fill_template( '/admin/dataset_security/role.mako', + role = role, + in_users = in_users, + out_users = out_users, + in_groups = in_groups, + out_groups = out_groups + ) + # Galaxy Group Stuff @web.expose def groups( self, trans, **kwd ): @@ -69,46 +134,19 @@ class Admin( BaseController ): # This query retrieves groups that are not deleted and members of each group q = sa.select( ( ( galaxy.model.Group.table.c.id ).label( 'group_id' ), ( galaxy.model.Group.table.c.name ).label( 'group_name' ), - ( galaxy.model.Group.table.c.priority ).label( 'group_priority' ), sa.func.count( galaxy.model.User.table.c.id ).label( 'total_members' ) ), whereclause = galaxy.model.Group.table.c.deleted == False, from_obj = [ sa.outerjoin( galaxy.model.Group.table, galaxy.model.UserGroupAssociation.table ).outerjoin( galaxy.model.User.table ) ], group_by = [ galaxy.model.Group.table.c.id, - galaxy.model.Group.table.c.name, - galaxy.model.Group.table.c.priority ], + galaxy.model.Group.table.c.name ], order_by = [ galaxy.model.Group.table.c.name ] ) groups = [] for row in q.execute(): - total_datasets = 0 - permitted_actions = [] - # This 2nd query retrieves the number of datasets and dataset permitted_actions associated with each group - q2 = sa.select( ( ( galaxy.model.Group.table.c.id ).label( 'group_id' ), - ( galaxy.model.GroupDatasetAssociation.table.c.permitted_actions ).label( 'permitted_actions' ), - sa.func.count( galaxy.model.Dataset.table.c.id ).label( 'total_datasets' ) ), - whereclause = sa.and_( galaxy.model.Group.table.c.id == row.group_id, - galaxy.model.Dataset.table.c.deleted == False ), - from_obj = [ sa.outerjoin( galaxy.model.Group.table, - galaxy.model.GroupDatasetAssociation.table - ).outerjoin( galaxy.model.Dataset.table ) ], - group_by = [ galaxy.model.Group.table.c.id, - galaxy.model.GroupDatasetAssociation.table.c.permitted_actions ] ) - for row2 in q2.execute(): - total_datasets = row2.total_datasets - permitted_actions = [] - # There may not yet be any GroupDatasetAssociations, in which case no - # actions will be found - if row2.permitted_actions: - for action in row2.permitted_actions: - permitted_actions.append( action.encode( 'ascii' ) ) - permitted_actions.sort() groups.append( ( row.group_id, escape( row.group_name, entities ), - row.group_priority, - row.total_members, - total_datasets, - permitted_actions ) ) + row.total_members ) ) return trans.fill_template( '/admin/dataset_security/groups.mako', groups=groups, msg=msg ) @@ -143,12 +181,8 @@ class Admin( BaseController ): msg = "A group with that name already exists" trans.response.send_redirect( '/admin/create_group?msg=%s' % msg ) else: - try: - priority = int( params.priority ) - except: - priority = 0 # Create the group - group = galaxy.model.Group( name, priority ) + group = galaxy.model.Group( name ) group.flush() # Add the members members = params.members @@ -163,7 +197,7 @@ class Admin( BaseController ): # Create the UserGroupAssociation user_group_association = galaxy.model.UserGroupAssociation( user, group ) user_group_association.flush() - msg = "The new group has been created with priority %s and %s members" % ( str( priority ), str( len( members ) ) ) + msg = "The new group has been created with %s members" % str( len( members ) ) trans.response.send_redirect( '/admin/groups?msg=%s' % msg ) @web.expose def group_members( self, trans, **kwd ): @@ -279,50 +313,6 @@ class Admin( BaseController ): msg = "Group membership has been updated with a total of %s members" % len( members ) trans.response.send_redirect( '/admin/group_members?group_id=%s&group_name=%s&msg=%s' % ( str( group_id ), params.group_name, msg ) ) @web.expose - def group_dataset_permitted_actions( 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_id = int( params.group_id ) - gdas = [] - permitted_actions = [] - group = galaxy.model.Group.get( group_id ) - return trans.fill_template( '/admin/dataset_security/group_dataset_permitted_actions_edit.mako', - group=group, - gdas=group.datasets, - msg=msg ) - @web.expose - def group_dataset_permitted_actions_edit( 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 ) - permissions = {} - for gda_id in params.gdas.split(','): - permissions[gda_id] = params.get('gda_actions_%s' % gda_id, None) - if isinstance( permissions[gda_id], str ): - permissions[gda_id] = [ permissions[gda_id] ] - for gda_id, permitted_actions in permissions.items(): - if permitted_actions is None: - gda = trans.app.model.GroupDatasetAssociation.get( int( gda_id ) ) - if gda.permitted_actions is not []: - log.debug( "gda %s: permitted_actions emptied (set to none)" % gda_id ) - gda.permitted_actions = [] - gda.flush() - else: - valid_pas = trans.app.model.security_agent.convert_permitted_action_strings( permitted_actions ) - if not valid_pas: - continue # this shouldn't happen, but might as well check - gda = trans.app.model.GroupDatasetAssociation.get( int( gda_id ) ) - if sorted(gda.permitted_actions) != sorted(valid_pas): - log.debug( "gda %s: permitted_actions changed to %s" % ( gda_id, valid_pas ) ) - gda = trans.app.model.GroupDatasetAssociation.get( int( gda_id ) ) - gda.permitted_actions = valid_pas - gda.flush() - msg = "The dataset permitted actions have been updated" - trans.response.send_redirect( '/admin/groups?msg=%s' % msg ) - @web.expose def mark_group_deleted( self, trans, **kwd ): if not self.user_is_admin( trans ): return trans.show_error_message( no_privilege_msg ) @@ -343,45 +333,19 @@ class Admin( BaseController ): # This query retrieves groups that are not deleted and members of each group q = sa.select( ( ( galaxy.model.Group.table.c.id ).label( 'group_id' ), ( galaxy.model.Group.table.c.name ).label( 'group_name' ), - ( galaxy.model.Group.table.c.priority ).label( 'group_priority' ), sa.func.count( galaxy.model.User.table.c.id ).label( 'total_members' ) ), whereclause = galaxy.model.Group.table.c.deleted == True, from_obj = [ sa.outerjoin( galaxy.model.Group.table, galaxy.model.UserGroupAssociation.table ).outerjoin( galaxy.model.User.table ) ], group_by = [ galaxy.model.Group.table.c.id, - galaxy.model.Group.table.c.name, - galaxy.model.Group.table.c.priority ], + galaxy.model.Group.table.c.name ], order_by = [ galaxy.model.Group.table.c.name ] ) groups = [] for row in q.execute(): - total_datasets = 0 - permitted_actions = [] - # This 2nd query retrieves the number of datasets and dataset permitted_actions associated with each group - q2 = sa.select( ( ( galaxy.model.Group.table.c.id ).label( 'group_id' ), - ( galaxy.model.GroupDatasetAssociation.table.c.permitted_actions ).label( 'permitted_actions' ), - sa.func.count( galaxy.model.Dataset.table.c.id ).label( 'total_datasets' ) ), - whereclause = sa.and_( galaxy.model.Group.table.c.id == row.group_id, - galaxy.model.Dataset.table.c.deleted == False ), - from_obj = [ sa.outerjoin( galaxy.model.Group.table, - galaxy.model.GroupDatasetAssociation.table - ).outerjoin( galaxy.model.Dataset.table ) ], - group_by = [ galaxy.model.Group.table.c.id, - galaxy.model.GroupDatasetAssociation.table.c.permitted_actions ] ) - for row2 in q2.execute(): - total_datasets = row2.total_datasets - permitted_actions = [] - # There may not yet be any GroupDatasetAssociations, in which case no actions will be found - if row2.permitted_actions: - for action in row2.permitted_actions: - permitted_actions.append( action.encode( 'ascii' ) ) - permitted_actions.sort() groups.append( ( row.group_id, escape( row.group_name, entities ), - row.group_priority, - row.total_members, - total_datasets, - permitted_actions ) ) + row.total_members ) ) return trans.fill_template( '/admin/dataset_security/deleted_groups.mako', groups=groups, msg=msg ) @@ -449,25 +413,6 @@ class Admin( BaseController ): msg=msg ) @web.expose def specified_users_groups( self, trans, **kwd ): - def renderable( component, group_id ): - #return True if component or at least one of components contents is - #associated with group_id - if isinstance( component, trans.app.model.LibraryFolder ): - # Check the folder's datasets to see what can be rendered - for library_folder_dataset_assoc in component.active_datasets: - if renderable( library_folder_dataset_assoc, group_id ): - return True - # Check the folder's sub-folders to see what can be rendered - for library_folder in component.active_folders: - if renderable( library_folder, group_id ): - return True - elif isinstance( component, trans.app.model.LibraryFolderDatasetAssociation ): - dataset = trans.app.model.Dataset.get( component.dataset_id ) - for group_dataset_assoc in dataset.groups: - if group_dataset_assoc.group_id == group_id: - return True - return False - if not self.user_is_admin( trans ): return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) @@ -476,49 +421,15 @@ class Admin( BaseController ): user_email = unescape( params.user_email, unentities ) # Get the groups to which the user belongs q = sa.select( ( ( galaxy.model.Group.table.c.id ).label( 'group_id' ), - ( galaxy.model.Group.table.c.name ).label( 'group_name' ), - ( galaxy.model.Group.table.c.priority ).label( 'group_priority' ) ), + ( galaxy.model.Group.table.c.name ).label( 'group_name' ) ), whereclause = galaxy.model.User.table.c.id == user_id, from_obj = [ sa.outerjoin( galaxy.model.User.table, galaxy.model.UserGroupAssociation.table ).outerjoin( galaxy.model.Group.table ) ], order_by = [ 'group_name' ] ) + groups = [] for row in q.execute(): - libraries = [] - groups = [] - permitted_actions = [] - total_datasets = 0 - # Perform a 2nd query to get datasets associated with each group - q2 = sa.select( ( ( galaxy.model.Group.table.c.id ).label( 'group_id' ), - ( galaxy.model.GroupDatasetAssociation.table.c.permitted_actions ).label( 'permitted_actions' ), - sa.func.count( galaxy.model.Dataset.table.c.id ).label( 'total_datasets' ) ), - whereclause = sa.and_( galaxy.model.Group.table.c.id == row.group_id, - galaxy.model.Dataset.table.c.deleted == False ), - from_obj = [ sa.outerjoin( galaxy.model.Group.table, - galaxy.model.GroupDatasetAssociation.table - ).outerjoin( galaxy.model.Dataset.table ) ], - group_by = [ galaxy.model.Group.table.c.id, - galaxy.model.GroupDatasetAssociation.table.c.permitted_actions ] ) - for row2 in q2.execute(): - libraries = [] - permitted_actions = [] - total_datasets = row2.total_datasets - # There may not yet be any GroupDatasetAssociations, in which case no - # actions will be found - if row2.permitted_actions: - for action in row2.permitted_actions: - permitted_actions.append( action.encode( 'ascii' ) ) - permitted_actions.sort() - # If we have permitted actions, then we have at least 1 GroupDatasetAssociation, in - # which case, we can see if we have any Libraries that the user can access - - libraries = [ library for library in trans.app.model.Library.select() if renderable( library.root_folder, row2.group_id ) ] - groups.append( ( row.group_id, - escape( row.group_name, entities ), - row.group_priority, - total_datasets, - permitted_actions, - libraries ) ) + escape( row.group_name, entities ) ) ) return trans.fill_template( '/admin/dataset_security/specified_users_groups.mako', user_id=user_id, user_email=escape( user_email, entities ), @@ -672,7 +583,7 @@ class Admin( BaseController ): msg = params.msg # add_file method - def add_file( file_obj, name, extension, dbkey, last_used_build, groups, info='no info', space_to_tab=False ): + def add_file( file_obj, name, extension, dbkey, last_used_build, roles, info='no info', space_to_tab=False ): data_type = None temp_name = sniff.stream_to_file( file_obj ) @@ -718,14 +629,9 @@ class Admin( BaseController ): folder = trans.app.model.LibraryFolder.get( folder_id ) folder.add_dataset( dataset, genome_build=last_used_build ) dataset.flush() - # GroupDatasetAssociations will enable security on the dataset based on the permitted_actions - # associated with the GroupDatasetAssociation. The default permitted_actions at this point - # will be DATASET_ACCESS, but the user can change this after the file is uploaded. - permitted_actions = [ RBACAgent.permitted_actions.DATASET_ACCESS ] - for group_id in groups: - group = galaxy.model.Group.get( group_id ) - group_dataset_assoc = galaxy.model.GroupDatasetAssociation( group, dataset.dataset, permitted_actions ) - group_dataset_assoc.flush() + if roles: + adra = galaxy.model.ActionDatasetRolesAssociation( RBACAgent.permitted_actions.DATASET_ACCESS.action, dataset.dataset, roles ) + adra.flush() shutil.move( temp_name, dataset.dataset.file_name ) dataset.dataset.state = dataset.dataset.states.OK dataset.init_meta() @@ -760,16 +666,9 @@ class Admin( BaseController ): if 'space_to_tab' in kwd: if kwd['space_to_tab'] not in ["None", None]: space_to_tab = True - if 'groups' not in kwd and 'users' not in kwd and 'public' not in kwd: - msg = 'The dataset must be associated with at least 1 user or group, or be set public.' - trans.response.send_redirect( web.url_for( action='dataset', folder_id=folder_id, msg=msg ) ) - groups = [] - if 'groups' in kwd: - groups.extend( listify(kwd['groups']) ) - if 'users' in kwd: - groups.extend( listify(kwd['users']) ) - if 'public' in kwd: - groups.extend( [ trans.app.security_agent.get_public_group().id ] ) + roles = [] + if 'roles' in kwd: + roles = listify( kwd['roles'] ) temp_name = "" data_list = [] created_datasets = [] @@ -782,7 +681,7 @@ class Admin( BaseController ): extension, dbkey, last_used_build, - groups, + roles, info="uploaded file", space_to_tab=space_to_tab ) elif url_paste not in [ None, "" ]: @@ -796,7 +695,7 @@ class Admin( BaseController ): extension, dbkey, last_used_build, - groups, + roles, info="uploaded url", space_to_tab=space_to_tab ) created_datasets.append( last_dataset_created ) @@ -813,7 +712,7 @@ class Admin( BaseController ): extension, dbkey, last_used_build, - groups, + roles, info="pasted entry", space_to_tab=space_to_tab ) elif server_dir not in [ None, "", "None" ]: @@ -831,7 +730,7 @@ class Admin( BaseController ): extension, dbkey, last_used_build, - groups, + roles, info="imported file", space_to_tab=space_to_tab ) created_datasets.append( last_dataset_created ) @@ -862,19 +761,14 @@ class Admin( BaseController ): yield build_name, dbkey, ( dbkey==last_used_build ) dbkeys = get_dbkey_options( last_used_build ) # Send list of groups to the form so the dataset can be associated with 1 or more of them. - groups = [] - q = sa.select( ( ( galaxy.model.Group.table.c.id ).label( 'group_id' ), - ( galaxy.model.Group.table.c.name ).label( 'group_name' ) ), - order_by = [ galaxy.model.Group.table.c.name ] ) - for row in q.execute(): - groups.append( ( row.group_id, row.group_name ) ) - groups = sorted( groups, key=operator.itemgetter(1) ) + #roles = trans.app.model.Role.select().order_by( 'role_id' ) + roles = trans.app.model.Role.select( order_by=trans.app.model.Role.c.name ) 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, - groups=groups, + roles=roles, msg=msg ) else: if id.count( ',' ): @@ -884,92 +778,111 @@ class Admin( BaseController ): ids = None # id specified, display attributes form if id: - dataset = trans.app.model.LibraryFolderDatasetAssociation.get( id ) - if not dataset: + lda = trans.app.model.LibraryFolderDatasetAssociation.get( id ) + if not lda: return trans.show_error_message( "Invalid dataset specified" ) # Copied from edit attributes for 'regular' datasets with some additions p = util.Params(kwd, safe=False) - if p.change: + if p.update_roles: + # 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', [] ) ) ] + permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles + trans.app.security_agent.set_dataset_permissions( lda.dataset, permissions ) + lda.dataset.refresh() + elif p.change: # The user clicked the Save button on the 'Change data type' form - trans.app.datatypes_registry.change_datatype( dataset, p.datatype ) + trans.app.datatypes_registry.change_datatype( lda, p.datatype ) trans.app.model.flush() elif p.save: # The user clicked the Save button on the 'Edit Attributes' form - dataset.name = name - dataset.info = info + lda.name = name + lda.info = info # The following for loop will save all metadata_spec items - for name, spec in dataset.datatype.metadata_spec.items(): + for name, spec in lda.datatype.metadata_spec.items(): if spec.get("readonly"): continue optional = p.get("is_"+name, None) if optional and optional == 'true': # optional element... == 'true' actually means it is NOT checked (and therefore ommitted) - setattr(dataset.metadata,name,None) + setattr(lda.metadata,name,None) else: - setattr(dataset.metadata,name,spec.unwrap(p.get(name, None), p)) + setattr(lda.metadata,name,spec.unwrap(p.get(name, None), p)) - dataset.metadata.dbkey = dbkey - dataset.datatype.after_edit( dataset ) + lda.metadata.dbkey = dbkey + lda.datatype.after_edit( lda ) trans.app.model.flush() return trans.show_ok_message( "Attributes updated" ) elif p.detect: # The user clicked the Auto-detect button on the 'Edit Attributes' form - for name, spec in dataset.datatype.metadata_spec.items(): + for name, spec in lda.datatype.metadata_spec.items(): # We need to be careful about the attributes we are resetting if name != 'name' and name != 'info' and name != 'dbkey': if spec.get( 'default' ): - setattr( dataset.metadata,name,spec.unwrap( spec.get( 'default' ), spec )) - dataset.datatype.set_meta( dataset ) - dataset.datatype.after_edit( dataset ) + setattr( lda.metadata,name,spec.unwrap( spec.get( 'default' ), spec )) + lda.datatype.set_meta( lda ) + lda.datatype.after_edit( lda ) trans.app.model.flush() return trans.show_ok_message( "Attributes updated" ) elif p.delete: - dataset.deleted = True - dataset.flush() + lda.deleted = True + lda.flush() trans.response.send_redirect( web.url_for( action='library_browser' ) ) - dataset.datatype.before_edit( dataset ) - if "dbkey" in dataset.datatype.metadata_spec and not dataset.metadata.dbkey: + lda.datatype.before_edit( lda ) + if "dbkey" in lda.datatype.metadata_spec and not lda.metadata.dbkey: # Copy dbkey into metadata, for backwards compatability # This looks like it does nothing, but getting the dbkey # returns the metadata dbkey unless it is None, in which # case it resorts to the old dbkey. Setting the dbkey # sets it properly in the metadata - dataset.metadata.dbkey = dataset.dbkey + lda.metadata.dbkey = lda.dbkey metadata = list() # a list of MetadataParemeters - for name, spec in dataset.datatype.metadata_spec.items(): + for name, spec in lda.datatype.metadata_spec.items(): if spec.visible: - metadata.append( spec.wrap( dataset.metadata.get(name), dataset ) ) + metadata.append( spec.wrap( lda.metadata.get(name), lda ) ) # let's not overwrite the imported datatypes module with the variable datatypes? ldatatypes = [x for x in trans.app.datatypes_registry.datatypes_by_extension.iterkeys()] ldatatypes.sort() return trans.fill_template( "/admin/library/dataset.mako", - dataset=dataset, + dataset=lda, metadata=metadata, datatypes=ldatatypes, err=None, msg=msg ) # multiple ids specfied, display multi permission form elif ids: - datasets = [] + ldas = [] for id in [ int( id ) for id in ids ]: - d = trans.app.model.LibraryFolderDatasetAssociation.get( id ) - if d is None: + lda = trans.app.model.LibraryFolderDatasetAssociation.get( id ) + if lda is None: return trans.show_error_message( 'You specified an invalid dataset' ) - datasets.append( d ) - if len( datasets ) < 2: + ldas.append( lda ) + if len( ldas ) < 2: return trans.show_error_message( 'You must specify at least two datasets to modify permissions on' ) - # If the permissions on the first dataset don't match the intersection - # of permissions, the permissions across all datasets are not - # identical. Although, should we care, or should we just let the admin - # overwrite permissions regardless? - if trans.app.security_agent.get_dataset_permissions( datasets[0] ) != \ - trans.app.security_agent.guess_derived_permissions_for_datasets( [ d.dataset for d in datasets ] ): + if 'update_roles' in 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', [] ) ) ] + 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. + tmp = [] + for lda in ldas: + perms = trans.app.security_agent.get_dataset_permissions( lda.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" ) else: return trans.fill_template( "/admin/library/dataset.mako", - dataset=datasets ) + dataset=ldas ) def check_gzip( self, temp_name ): """ Utility method to check gzipped uploads @@ -1042,6 +955,9 @@ class Admin( BaseController ): trans.response.send_redirect( web.url_for( action='library_browser' ) ) @web.expose def datasets( self, trans, **kwd ): + """ + The datasets method is used by the dropdown box on the admin-side library browser. + """ if not self.user_is_admin( trans ): return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) @@ -1086,18 +1002,6 @@ class Admin( BaseController ): ### -- Dan: - # Delete any GroupDatasetAssociations #perhaps group assocs should only be deleted when the Dataset object is purged? - #for group_dataset_assoc in dataset.groups: - # group_dataset_assoc.delete() - # group_dataset_assoc.flush() - # Delete any HistoryDatasetAssociations #Just because we delete this Library Dataset Association from the library, doesn't mean we want to remove it from a user's history (without their knowledge even) - #for history_dataset_assoc in dataset.history_associations: - # history_dataset_assoc.deleted = True #set deleted flag to True, do not delete row - # history_dataset_assoc.flush() - # Delete the dataset - #dataset.deleted = True - #dataset.flush() - # Delete the LibraryFolderDatasetAssociation library_folder_dataset_assoc.deleted = True library_folder_dataset_assoc.flush() @@ -1144,11 +1048,13 @@ class Admin( BaseController ): 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." ) -def listify( item ): +def listify( item, return_none=False ): """ Since single params are not a single item list """ - if isinstance( item, list ): + if item is None: + return [] + elif isinstance( item, list ): return item else: return [ item ] diff --git a/lib/galaxy/web/controllers/async.py b/lib/galaxy/web/controllers/async.py index 07e8b9d066c..0e8b7369441 100644 --- a/lib/galaxy/web/controllers/async.py +++ b/lib/galaxy/web/controllers/async.py @@ -104,7 +104,7 @@ class ASync( BaseController ): #history.datasets.add_dataset( data ) data = trans.app.model.HistoryDatasetAssociation( create_dataset = True, extension = GALAXY_TYPE ) - trans.app.security_agent.set_dataset_permissions( data.dataset, trans.app.security_agent.history_get_default_access( trans.history ) ) + trans.app.security_agent.set_dataset_permissions( data.dataset, trans.app.security_agent.history_get_default_permissions( trans.history ) ) data.name = GALAXY_NAME data.dbkey = GALAXY_BUILD data.info = GALAXY_INFO diff --git a/lib/galaxy/web/controllers/root.py b/lib/galaxy/web/controllers/root.py index 26fa9b4c7f3..710535f8a12 100644 --- a/lib/galaxy/web/controllers/root.py +++ b/lib/galaxy/web/controllers/root.py @@ -271,6 +271,8 @@ class RootController( BaseController ): if target_type: msg = data.datatype.convert_dataset(trans, data, target_type) return trans.show_ok_message( msg, refresh_frames=['history'] ) + ''' + Form removed from template, needs to be remade elif p.change_permission: """The user clicked the change_permission button on the 'Change permissions' form""" if not trans.user: @@ -287,6 +289,7 @@ class RootController( BaseController ): return trans.show_ok_message( "Dataset permissions have been set.", refresh_frames=['history'] ) else: return trans.show_error_message( "You are not authorized to change this dataset's permitted actions." ) + ''' data.datatype.before_edit( data ) @@ -399,10 +402,10 @@ class RootController( BaseController ): if history: if history.user_id != None and user: assert user.id == history.user_id, "History does not belong to current user" - # Delete DefaultHistoryGroupAssociations - for default_history_group_association in history.default_groups: - default_history_group_association.delete() - default_history_group_association.flush() + # Delete DefaultHistoryPermissions + for dhp in history.default_permissions: + dhp.delete() + dhp.flush() # Mark history as deleted in db history.deleted = True history_names.append(history.name) @@ -454,6 +457,42 @@ class RootController( BaseController ): errors.append( "You must select at least one history to undelete." ) return self.history_available( trans, id=','.join( id ), show_deleted=True, ok_msg = ok_msg, error_msg = " ".join( errors ) ) + @web.expose + def history_undelete( self, trans, id=[], **kwd): + """Undeletes a list of histories, ensures that histories are owned by current user""" + history_names = [] + errors = [] + ok_msg = "" + if id: + if not isinstance( id, list ): + id = id.split( "," ) + user = trans.get_user() + for hid in id: + try: + int( hid ) + except: + errors.append( "Invalid history: %s" % str( hid ) ) + continue + history = self.app.model.History.get( hid ) + if history: + if history.user != user: + errors.append( "History does not belong to current user." ) + continue + if history.purged: + errors.append( "History has already been purged and can not be undeleted." ) + continue + history_names.append( history.name ) + history.deleted = False + else: + errors.append( "Not able to find history %s." % str( hid ) ) + trans.log_event( "History id %s marked as undeleted" % str(hid) ) + self.app.model.flush() + if history_names: + ok_msg = "Histories (%s) have been undeleted." % ", ".join( history_names ) + else: + errors.append( "You must select at least one history to undelete." ) + return self.history_available( trans, id=','.join( id ), show_deleted=True, ok_msg = ok_msg, error_msg = " ".join( errors ) ) + @web.expose def clear_history( self, trans ): """Clears the history for a user""" @@ -680,12 +719,13 @@ class RootController( BaseController ): """Adds a POSTed file to a History""" try: history = trans.app.model.History.get( history_id ) - groups = trans.app.security_agent.history_get_default_access( history ) + data = trans.app.model.HistoryDatasetAssociation( name = name, info = info, extension = ext, dbkey = dbkey, create_dataset = True ) if copy_access_from: copy_access_from = trans.app.model.HistoryDatasetAssociation.get( copy_access_from ) - group_dataset_associations = copy_access_from.dataset.groups - data = trans.app.model.HistoryDatasetAssociation( name = name, info = info, extension = ext, dbkey = dbkey, create_dataset = True ) - trans.app.security_agent.set_dataset_permissions( data.dataset, group_dataset_associations ) + trans.app.security_agent.copy_dataset_permissions( copy_access_from.dataset, data.dataset ) + else: + permissions = trans.app.security_agent.history_get_default_permissions( history ) + trans.app.security_agent.set_dataset_permissions( data.dataset, permissions ) data.flush() data_file = open( data.file_name, "wb" ) file_data.file.seek( 0 ) @@ -710,6 +750,8 @@ class RootController( BaseController ): def history_set_default_permitted_actions( self, trans, **kwd ): """Sets the user's default permitted_actions for the current history""" if trans.user: + return trans.show_error_message( "This function is not implemented" ) + # TODO: reimplement if 'set_permitted_actions' in kwd: """The user clicked the set_permitted_actions button on the set_permitted_actions form""" history = trans.get_history() diff --git a/lib/galaxy/web/controllers/user.py b/lib/galaxy/web/controllers/user.py index c5c2de4a368..aa950dd983e 100644 --- a/lib/galaxy/web/controllers/user.py +++ b/lib/galaxy/web/controllers/user.py @@ -171,6 +171,8 @@ class User( BaseController ): def set_default_permitted_actions( self, trans, **kwd ): """Sets the user's default permitted actions for the new histories""" if trans.user: + return trans.show_error_message( "This function is not implemented" ) + # TODO: reimplement if 'set_permitted_actions' in kwd: """The user clicked the set_permitted_actions button on the set_permitted_actions form""" group_args = [ k.replace('group_', '', 1) for k in kwd if k.startswith('group_') ] diff --git a/lib/galaxy/web/framework/__init__.py b/lib/galaxy/web/framework/__init__.py index b3628b98d90..32e8e22e8ad 100644 --- a/lib/galaxy/web/framework/__init__.py +++ b/lib/galaxy/web/framework/__init__.py @@ -200,7 +200,7 @@ class UniverseWebTransaction( base.DefaultWebTransaction ): history = self.app.model.History( user = self.user ) # Make sure we have an id history.flush() - self.app.security_agent.history_set_default_access( history ) + self.app.security_agent.history_set_default_permissions( history ) # Immediately associate the new history with self self.__history = history # Make sure we have a valid session to associate with the new history @@ -436,7 +436,7 @@ class UniverseWebTransaction( base.DefaultWebTransaction ): if not history.user: # This user will now acquire previously non-owned history, so set permitted actions to user's default history.user = user - self.app.security_agent.history_set_default_access( history, dataset=True ) + self.app.security_agent.history_set_default_permissions( history, dataset=True, bypass_manage_permission=True ) history.user_id = user.id history.flush() self.__history = history diff --git a/templates/admin/dataset_security/deleted_groups.mako b/templates/admin/dataset_security/deleted_groups.mako index 14f46af4459..4c5c53c7b3e 100644 --- a/templates/admin/dataset_security/deleted_groups.mako +++ b/templates/admin/dataset_security/deleted_groups.mako @@ -9,7 +9,6 @@ <%def name="render_row( group_name, group )"> Undelete ${group_name} - ${group[2]} ${group[3]} %if group[4] > 0: ${group[4]} @@ -71,7 +70,6 @@   Group - Priority Members Datasets Group Permitted Actions on Datasets diff --git a/templates/admin/dataset_security/group_create.mako b/templates/admin/dataset_security/group_create.mako index 6cb912067f6..53af8ba06ef 100644 --- a/templates/admin/dataset_security/group_create.mako +++ b/templates/admin/dataset_security/group_create.mako @@ -22,7 +22,7 @@ - + %if len( users ) == 0: diff --git a/templates/admin/dataset_security/groups.mako b/templates/admin/dataset_security/groups.mako index 63a3920684a..319186110cc 100644 --- a/templates/admin/dataset_security/groups.mako +++ b/templates/admin/dataset_security/groups.mako @@ -8,22 +8,7 @@ ## Render a row <%def name="render_row( group_name, group )"> - - - %if group[4] > 0: - - %else: - - %endif - + @@ -75,10 +60,7 @@ %endif - - - %for group in groups: diff --git a/templates/admin/dataset_security/specified_users_groups.mako b/templates/admin/dataset_security/specified_users_groups.mako index 546842b59f4..8f8e716214a 100644 --- a/templates/admin/dataset_security/specified_users_groups.mako +++ b/templates/admin/dataset_security/specified_users_groups.mako @@ -25,19 +25,11 @@ - - - - <% ctr = 0 %> %for group in groups: <% gn = unescape( group[1], unentities ) - library_ids = '' - for library_id in group[5]: - library_ids += "%s," % library_id - library_ids = library_ids.rstrip( ',' ) %> %if ctr % 2 == 1: @@ -45,24 +37,6 @@ %endif - - %if group[3] > 0: - - %else: - - %endif - - <% ctr += 1 %> %endfor diff --git a/templates/admin/index.mako b/templates/admin/index.mako index e3d642f55b3..ae75a1c5c0e 100644 --- a/templates/admin/index.mako +++ b/templates/admin/index.mako @@ -80,6 +80,7 @@
diff --git a/templates/admin/library/browser.mako b/templates/admin/library/browser.mako index 31037536dbe..5a071ad032d 100644 --- a/templates/admin/library/browser.mako +++ b/templates/admin/library/browser.mako @@ -141,6 +141,7 @@
Name:   Priority: Name:
There are no Galaxy users
${group_name}${group[2]}${group[3]}${group[4]}${group[4]} - %if len( group[5] ) == 1: - ${group[5][0]} - %elif len( group[5] ) > 1: - %for da in group[5]: - ${da}
- %endfor - %endif -
${group[2]} Mark group deleted
NamePriority MembersDatasetsGroup Permitted Actions on Datasets  
GroupPriorityDatasetsPermitted Actions on DatasetsContaining Libraries
${gn}${group[2]}${group[3]}${group[3]} - %for da in group[4]: - ${da}
- %endfor -
- %if len( group[5] ) > 0: - ${len( group[5] )} - %else: - ${len( group[5] )} - %endif -
@@ -177,3 +178,6 @@ +%else: +There are no libraries. +%endif diff --git a/templates/admin/library/common.mako b/templates/admin/library/common.mako index 7e3423e0786..528103ea2d3 100644 --- a/templates/admin/library/common.mako +++ b/templates/admin/library/common.mako @@ -1,3 +1,33 @@ +<%def name="render_select( dataset, action_key, action )"> + <% + in_roles = [] + for a in dataset.actions: + if a.action == action.action: + for role_id in a.role_ids: + in_roles.append( trans.app.model.Role.get( role_id ) ) + out_roles = filter( lambda x: x not in in_roles, trans.app.model.Role.select() ) + %> +

+
+ Roles associated:
+
+ +
+
+ Roles not associated:
+
+ +
+ + <%def name="render_permissions_forms( data_obj )"> @@ -32,118 +79,30 @@ trans.show_error_message( "Unknown object passed to render_permissions_forms" ) if id is None: id = dataset.id + %>
-
Change Existing Permissions
+
Associate with roles and set permissions
-
+ %if redirect: %endif
- <% - user_permissions = [ p for p in trans.app.security_agent.get_dataset_permissions( dataset ) if p[0].name.endswith( ' private group' ) ] - real_permissions = [ p for p in trans.app.security_agent.get_dataset_permissions( dataset ) if not p[0].name.endswith( ' private group' ) and p[0].name != 'public' ] - %> -
- Update the permissions for the users and groups currently associated with this dataset. -
-
- %if trans.app.security_agent.dataset_has_group( dataset.id, trans.app.security_agent.get_public_group().id ): -
- This dataset can be accessed by anyone (it is public).
- %endif -
-
- %if not len( user_permissions ): -   None
- %endif - %for p in user_permissions: - ${p[0].name.replace( ' private group', '' )}
-
- %for k, v in trans.app.security_agent.permitted_actions.items(): - ${trans.app.security_agent.get_permitted_action_description(k)}
- %endfor -
- %endfor -
-
- %if not len( real_permissions ): -   None
- %endif - %for p in real_permissions: - ${p[0].name}
-
- %for k, v in trans.app.security_agent.permitted_actions.items(): - ${trans.app.security_agent.get_permitted_action_description(k)}
- %endfor -
- %endfor -
+
-
- -
-
-

- -

-
Add New Users and Groups
-
-
- - %if redirect: - - %endif + %for k, v in trans.app.model.Dataset.permitted_actions.items(): +
+ ${render_select( dataset, k, v )} +
+ %endfor
-
- Select new users and groups to associate with this dataset (you can assign permissions after creating the association). -
- ## Don't display the public access checkbox if it's already public (it's shown above instead) - %if not trans.app.security_agent.dataset_has_group( dataset.id, trans.app.security_agent.get_public_group().id ): -
- This dataset can be accessed by anyone (make it public).
- %endif -
- <% - all_groups = trans.app.model.Group.select() - user_groups = [ g for g in all_groups if g.name.endswith( ' private group' ) ] - real_groups = [ g for g in all_groups if not g.name.endswith( ' private group' ) and g.name != 'public' ] - %> -
- - -

- - -

-
- To select multiple users or groups, hold ctrl or command while clicking. -
+
-
@@ -164,11 +123,11 @@ else: data_state = data.state %> - %if not trans.app.security_agent.allow_action( trans.user, data.permitted_actions.DATASET_ACCESS, dataset = data.dataset ): -
- %else: + ##%if not trans.app.security_agent.allow_action( trans.user, data.permitted_actions.DATASET_ACCESS, dataset = data.dataset ): + ##
+ ##%else:
- %endif + ##%endif ## Header row for history items (name, state, action buttons) diff --git a/templates/admin/library/new_dataset.mako b/templates/admin/library/new_dataset.mako index 0876d1a2c02..adb350c752d 100644 --- a/templates/admin/library/new_dataset.mako +++ b/templates/admin/library/new_dataset.mako @@ -80,30 +80,16 @@
- - This dataset can be accessed by anyone (make it public).
-

- <% user_groups = [ g for g in groups if g[1].endswith( ' private group' ) ] %> - <% real_groups = [ g for g in groups if not g[1].endswith( ' private group' ) and g[1] != 'public' ] %>

- - + %for role in roles: + %endfor - %if len( real_groups ): -

- - - %endif

- To select multiple users or groups, hold ctrl or command while clicking. + To select multiple roles, hold ctrl or command while clicking. More permissions can be set after the upload is complete. Selecting no roles makes a dataset public.
diff --git a/templates/dataset/edit_attributes.mako b/templates/dataset/edit_attributes.mako index 6be3d18ed84..cc2c0c04214 100644 --- a/templates/dataset/edit_attributes.mako +++ b/templates/dataset/edit_attributes.mako @@ -165,90 +165,3 @@ elif isinstance( data, trans.app.model.LibraryFolderDatasetAssociation ):

%endif - -%if trans.user and ( trans.app.security_agent.allow_action( trans.user, data.permitted_actions.DATASET_MANAGE_PERMISSIONS, dataset = data ) ): - -

-
Change Dataset Access Permissions
-
-
- -
- <% user_groups = [ assoc.group for assoc in trans.user.groups ] %> - <% dataset_group_ids = [ assoc.group.id for assoc in data.dataset.groups ] %> -
- Check each group which should have access to this dataset. -
- %for group in user_groups: - %if group.id in dataset_group_ids: - <% assoc = filter( lambda x: x.group_id == group.id, data.dataset.groups )[0] %> - %else: - <% assoc = None %> - %endif - ${group.name}
-
- %for k, v in trans.app.security_agent.permitted_actions.items(): - ${trans.app.security_agent.get_permitted_action_description(k)}
- %endfor -
- %endfor - -
- -
-
-%elif trans.user and ( trans.app.security_agent.allow_action( trans.user, data.permitted_actions.DATASET_ACCESS, dataset = data ) ): -
-
Dataset Access Permissions
-
-
- -
- %for assoc in data.dataset.groups: - ${assoc.group.name} -
    - %for action in assoc.permitted_actions: -
  • ${trans.app.security_agent.get_permitted_action_description(action)}
  • - %endfor -
- %endfor -
- You are not allowed to change the permitted actions on this dataset. -
-
-
-
-%endif - -

-

-
Copy History Item
-
- Click here to make a copy of this history item. -
-
-

diff --git a/test/base/twilltestcase.py b/test/base/twilltestcase.py index bf77ddbf15f..524ddcebaf5 100644 --- a/test/base/twilltestcase.py +++ b/test/base/twilltestcase.py @@ -530,20 +530,19 @@ class TwillTestCase( unittest.TestCase ): self.assertNotEqual(count, maxiter) # Dataset Security stuff - def create_group( self, name='New Test Group', priority='10' ): + def create_group( self, name='New Test Group' ): """Create a new group with 1 member""" self.visit_url( "%s/admin/create_group" % self.url ) form = tc.show() self.check_page_for_string( "Create Group" ) try: tc.fv( "1", "name", name ) - tc.fv( "1", "priority", priority ) # twill version 0.9 still does not allow for easily testing forms that contain # multiple fields with the same name ( e.g., check boxes ). We could attempt to determine # the number of the field on the form and use it instead of the name of the field, but we # would be forced to drop and recreate the database every time we test in order to ensure # the fields will be the same on the form ( since it is dynamically rendered from the database ). - tc.fv( "1", "3", "1" ) # 1-based form field 3 is the 1st check box named 'members', user id 1 is test@bx.psu.edu + tc.fv( "1", "2", "1" ) # 1-based form field 3 is the 1st check box named 'members', user id 1 is test@bx.psu.edu tc.submit( "create_group_button" ) except AssertionError, err: errmsg = 'Exception caught attempting to create group: %s' % str( err ) diff --git a/test/functional/test_security_and_libraries.py b/test/functional/test_security_and_libraries.py index 5b9063b6afa..4634f147cb1 100644 --- a/test/functional/test_security_and_libraries.py +++ b/test/functional/test_security_and_libraries.py @@ -53,7 +53,7 @@ class TestHistory( TwillTestCase ): self.visit_page( "admin/groups" ) # the following should have been created when account was created. self.check_page_for_string( 'test@bx.psu.edu private group' ) - self.create_group( name='New Test Group', priority='10' ) + self.create_group( name='New Test Group' ) self.visit_page( "admin/groups" ) self.check_page_for_string( "group_name=New+Test+Group" ) # twill version 0.9 still does not allow for the following test diff --git a/tools/data_source/encode_import_code.py b/tools/data_source/encode_import_code.py index 19fb17fef95..5b9754584d0 100644 --- a/tools/data_source/encode_import_code.py +++ b/tools/data_source/encode_import_code.py @@ -38,7 +38,7 @@ def exec_after_process(app, inp_data, out_data, param_dict, tool, stdout, stderr newdata.extension = file_type newdata.name = basic_name + " (" + description + ")" history.add_dataset( newdata ) - app.security_agent.set_dataset_permissions( newdata.dataset, base_dataset.dataset.groups ) + app.security_agent.copy_dataset_permissions( base_dataset.dataset, newdata.dataset ) app.model.flush() try: copyfile(filepath,newdata.file_name) diff --git a/tools/data_source/microbial_import_code.py b/tools/data_source/microbial_import_code.py index b5ccbf11c3c..7c220566055 100644 --- a/tools/data_source/microbial_import_code.py +++ b/tools/data_source/microbial_import_code.py @@ -129,7 +129,7 @@ def exec_after_process(app, inp_data, out_data, param_dict, tool, stdout, stderr newdata.extension = file_type newdata.name = basic_name + " (" + microbe_info[kingdom][org]['chrs'][chr]['data'][description]['feature'] +" for "+microbe_info[kingdom][org]['name']+":"+chr + ")" newdata.flush() - app.security_agent.set_dataset_permissions( newdata.dataset, base_dataset.dataset.groups ) + app.security_agent.copy_dataset_permissions( base_dataset.dataset, newdata.dataset ) history.add_dataset( newdata ) app.model.flush() try: diff --git a/tools/maf/maf_to_bed_code.py b/tools/maf/maf_to_bed_code.py index d28c10b73ca..38b1f2358fb 100644 --- a/tools/maf/maf_to_bed_code.py +++ b/tools/maf/maf_to_bed_code.py @@ -32,7 +32,7 @@ def exec_after_process(app, inp_data, out_data, param_dict, tool, stdout, stderr newdata.name = basic_name + " (" + dbkey + ")" newdata.flush() history.add_dataset( newdata ) - app.security_agent.set_dataset_permissions( newdata.dataset, output_data.dataset.groups ) + app.security_agent.copy_dataset_permissions( output_data.dataset, newdata.dataset ) newdata.flush() history.flush() app.model.flush()