From 3802816b69b2bca3341e3386b3dd38d47986fed7 Mon Sep 17 00:00:00 2001 From: Nate Coraor Date: Wed, 20 Aug 2008 18:11:04 -0400 Subject: [PATCH] User interface to permissions, and a bunch of changes to ensure that both permitted_actions and groups are set appropriately on datasets. Somewhat tested and mostly working, but needs more testing. Greg: If a userless session has a history, if that user logs in, the DefaultHistoryGroupAssociation for the current history as well as the datasets in that history are supposed to be updated with the permissions in DefaultUserGroupAssociation, but this isn't happening. Possibly because the default permissions on userless histories create datasets in the public group with only the DATASET_ACCESS permission (or possibly for another reason). What are the implications of giving public users DATASET_MANAGE_PERMISSIONS? --- lib/galaxy/model/mapping.py | 2 +- lib/galaxy/security/__init__.py | 165 ++++++++++++--------- lib/galaxy/tools/__init__.py | 6 +- lib/galaxy/tools/actions/__init__.py | 7 +- lib/galaxy/tools/actions/upload.py | 6 +- lib/galaxy/tools/parameters/basic.py | 10 +- lib/galaxy/web/controllers/async.py | 3 +- lib/galaxy/web/controllers/dataset.py | 2 +- lib/galaxy/web/controllers/root.py | 69 ++++----- lib/galaxy/web/controllers/user.py | 36 ++--- lib/galaxy/web/framework/__init__.py | 3 +- static/june_2007_style/blue/base.css | 6 +- templates/dataset/edit_attributes.mako | 85 ++++++++--- templates/history/permissions.mako | 56 +++++-- templates/user/permissions.mako | 55 +++++-- tools/data_source/encode_import_code.py | 3 +- tools/data_source/microbial_import_code.py | 3 +- tools/maf/maf_to_bed_code.py | 3 +- 18 files changed, 299 insertions(+), 221 deletions(-) diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index cd4aa9c3ee9..4922b4fd7d5 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -619,7 +619,7 @@ def init( file_path, url, engine_options={}, create_tables=False ): orphans = result.Dataset.get_by( history_id = None ) if orphans: for dataset in orphans: - result.security_agent.set_dataset_groups( dataset, [ public_group ] ) + result.security_agent.set_dataset_permissions( dataset, [ ( public_group, result.security_agent.permitted_actions.DATASET_ACCESS ) ] ) else: result.security_agent.guess_public_group() log.debug( "Public Group identified as id = %s." % ( Group.public_id ) ) diff --git a/lib/galaxy/security/__init__.py b/lib/galaxy/security/__init__.py index 0eaba298d10..9c497437120 100644 --- a/lib/galaxy/security/__init__.py +++ b/lib/galaxy/security/__init__.py @@ -7,10 +7,6 @@ from galaxy.util.bunch import Bunch log = logging.getLogger(__name__) -# TODO, Nate: Think about whether the following permitted actions are appropriate for the dataset and -# group objects. What should be the default "public" permitted actions? Make sure that the public group -# and public datasets are set with the correct permitted actions. Also make sure that "private" settings -# are correct when an authenticated user creates things inside their "private" environment. class RBACAgent: """Class that handles galaxy security""" permitted_actions = Bunch( @@ -23,9 +19,14 @@ class RBACAgent: # use in a job, etc). DATASET_ACCESS = 'dataset_access' ) + permitted_action_descriptions = Bunch( + DATASET_EDIT_METADATA = "Edit this dataset's metadata in the library", + DATASET_MANAGE_PERMISSIONS = "Manage the groups associated with this dataset (and those groups' permissions on the dataset)", + DATASET_ACCESS = "View, import, and perform analyses on this dataset" + ) 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_groups_permitted_actions_for_datasets( self, datasets = [] ): + 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 @@ -35,12 +36,12 @@ class RBACAgent: raise 'No valid method of creating group with %s' % ( kwd ) def create_private_user_group( self, user ): raise "Unimplemented Method" - def user_set_default_access( self, user, groups = None, history = False, dataset = False ): + def user_set_default_access( 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, groups=None, dataset=False ): + def history_set_default_access( self, history, permissions=None, dataset=False ): raise "Unimplemented Method" def set_public_group( self, group ): raise "Unimplemented Method" @@ -48,7 +49,7 @@ class RBACAgent: raise "Unimplemented Method" def guess_public_group( self ): raise "Unimplemented Method" - def set_dataset_groups( self, dataset, groups ): + def set_dataset_permissions( self, dataset, permissions ): raise "Unimplemented Method" def set_dataset_permitted_actions( self, dataset ): raise "Unimplemented Method" @@ -56,6 +57,24 @@ class RBACAgent: raise "Unimplemented Method" def components_are_associated( self, **kwd ): return bool( self.get_component_associations( **kwd ) ) + def convert_permitted_action_strings( self, permitted_action_strings ): + """ + When getting permitted actions from an untrusted source like a + 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 ): @@ -83,35 +102,41 @@ class GalaxyRBACAgent( RBACAgent ): if action in group_dataset_assoc.permitted_actions: return True return False # No user and dataset not in public group, or user lacks permission - def guess_derived_groups_for_datasets( self, datasets=[] ): - # TODO, Nate: Make sure this method is functionally correct. - """Returns a list of groups for the output dataset based upon itself and provided datasets""" - access_groups = None - priority_access_group = None + 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 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 ): dataset = dataset.dataset - groups = [ data_group_assoc.group for data_group_assoc in dataset.groups ] - for group in groups: - if priority_access_group is None or priority_access_group.priority < group.priority: - priority_access_group = group - if access_groups is None: - access_groups = set( groups ) + 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: - access_groups.intersection_update( set( groups ) ) - # Complete lists for output dataset access - if access_groups: - access_groups = list( access_groups) - else: - access_groups = [] + # 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 access_groups: - if priority_access_group: - access_groups = [ priority_access_group ] - return access_groups + 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 %s' % ( id ) @@ -125,29 +150,18 @@ class GalaxyRBACAgent( RBACAgent ): 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: if 'group' in kwd: return self.associate_user_group( kwd['user'], kwd['group'] ) raise 'No valid method of associating provided components: %s' % kwd - def disassociate_components( self, **kwd ): - assert len( kwd ) == 2, 'You must specify exactly 2 Galaxy security components to disassociate.' - if 'dataset' in kwd: - if 'group' in kwd: - return self.disassociate_group_dataset( kwd['group'], kwd['dataset'] ) - raise 'No valid method of associating provided components: %s' % kwd - def associate_group_dataset( self, group, dataset, permitted_actions=[] ): - if not permitted_actions: - if isinstance( dataset.permitted_actions, Bunch ): - permitted_actions = dataset.permitted_actions.__dict__.values() - else: - permitted_actions = dataset.permitted_actions + 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 + log.debug("In associate_permissions_dataset, dataset: %d, group: %d, permitted_actions: %s" % ( dataset.id, group.id, permitted_actions ) ) assoc = self.model.GroupDatasetAssociation( group, dataset, permitted_actions ) assoc.flush() return assoc - def disassociate_group_dataset( self, group, dataset ): - assoc = self.model.GroupDatasetAssociation.selectone_by( group_id = group.id, dataset_id = dataset.id ) - assoc.delete() - assoc.flush() def associate_user_group( self, user, group ): assoc = self.model.UserGroupAssociation( user, group ) assoc.flush() @@ -161,66 +175,69 @@ class GalaxyRBACAgent( RBACAgent ): self.associate_components( group=group, user=user ) group.flush() return group - def user_set_default_access( self, user, groups = None, history = False, dataset = False ): - # TODO, Nate: Make sure this method is functionally correct with permitted actions set appropriately. - if groups is None: - groups = [ self.create_private_user_group( user ) ] - if groups is not None: + 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: for assoc in user.default_groups: #this is the association not the actual group assoc.delete() assoc.flush() - for group in groups: - if isinstance( group, self.model.Group ): - permitted_actions = group.permitted_actions.__dict__.values() - else: - permitted_actions = group.permitted_actions + for group, permitted_actions in permissions: + log.debug("In user_set_default_access, user: %s, group: %s, permitted_actions: %s" % (user.email, group.name, str( permitted_actions))) assoc = self.model.DefaultUserGroupAssociation( user, group, permitted_actions ) assoc.flush() if history: for history in user.histories: - self.history_set_default_access( history, groups=groups, dataset=dataset ) - def history_set_default_access( self, history, groups=None, dataset=False ): - # TODO, Nate: Make sure this method is functionally correct with permitted actions set appropriately. - if groups is None: + 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: - groups = [ assoc.group for assoc in history.user.default_groups ] + permissions = self.user_get_default_access( history.user ) else: - groups = [ self.get_public_group() ] - if groups is not None: + # FIXME: should this be all permissions? + permissions = [ ( self.get_public_group(), [ self.permitted_actions.DATASET_ACCESS ] ) ] + if permissions is not None: for assoc in history.default_groups: #this is the association not the actual group assoc.delete() assoc.flush() - for group in groups: - if isinstance( group, self.model.Group ): - permitted_actions = group.permitted_actions.__dict__.values() - else: - permitted_actions = group.permitted_actions + for group, permitted_actions in permissions: + log.debug("In history_set_default_access, history: %s, group: %s, permitted_actions: %s" % (history.id, group.name, str( permitted_actions))) assoc = self.model.DefaultHistoryGroupAssociation( history, group, permitted_actions ) assoc.flush() if dataset: for data in history.datasets: for hda in data.dataset.history_associations: if history.user and hda.history not in history.user.histories: - self.set_dataset_groups( data.dataset, [ self.get_public_group() ] ) + self.set_dataset_permissions( data.dataset, [ ( self.get_public_group(), [ self.permitted_actions.DATASET_ACCESS ] ) ] ) break else: - self.set_dataset_groups( data.dataset, groups ) + self.set_dataset_permissions( data.dataset, permissions ) + 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_groups( self, dataset, groups ): + def set_dataset_permissions( self, dataset, permissions ): + """ + 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 for group_dataset_assoc in dataset.groups: group_dataset_assoc.delete() group_dataset_assoc.flush() - for group in groups: - if not isinstance( group, self.model.Group ): - group = group.group - self.associate_components( dataset=dataset, group=group ) + if isinstance( permissions[0], self.model.GroupDatasetAssociation ): + permissions = [ ( gda.group, gda.permitted_actions ) for gda in permissions ] + for ptuple in permissions: + log.debug("In set_dataset_permissions, before elf.associate_components, dataset: %s, group: %s, permitted_actions: %s" % ( str(dataset.id), str(ptuple[0].id), str(ptuple[1]))) + self.associate_components( dataset=dataset, permissions=ptuple ) 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.' diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index 7b5c4b0d305..4030cadd47e 100644 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -1085,8 +1085,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 ) - # TODO, Nate: Make sure the following is functionally correct. - self.app.security_agent.set_dataset_groups( child_dataset.dataset, outdata.dataset.groups ) + self.app.security_agent.set_dataset_permissions( child_dataset.dataset, outdata.dataset.groups ) # Move data from temp location to dataset location shutil.move( filename, child_dataset.file_name ) child_dataset.flush() @@ -1123,8 +1122,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 ) - # TODO, Nate: Make sure the following is functionally correct. - self.app.security_agent.set_dataset_groups( primary_data.dataset, outdata.dataset.groups ) + self.app.security_agent.set_dataset_permissions( primary_data.dataset, outdata.dataset.groups ) 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 bd74d188ee6..efc0ce99893 100644 --- a/lib/galaxy/tools/actions/__init__.py +++ b/lib/galaxy/tools/actions/__init__.py @@ -86,11 +86,10 @@ class DefaultToolAction( object ): # Determine output dataset permitted_actions list existing_datasets = [ inp for inp in inp_data.values() if inp ] if existing_datasets: - output_access_groups = trans.app.security_agent.guess_derived_groups_for_datasets( 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_access_groups = [ group.group for group in trans.history.default_groups ] - + output_permissions = trans.app.security_agent.history_get_default_access( trans.history ) # Build name for output datasets based on tool name and input names if len( input_names ) == 1: on_text = input_names[0] @@ -132,7 +131,7 @@ class DefaultToolAction( object ): data = trans.app.model.HistoryDatasetAssociation( extension=ext, create_dataset=True ) # Commit the dataset immediately so it gets database assigned unique id data.flush() - trans.app.security_agent.set_dataset_groups( data.dataset, output_access_groups ) + trans.app.security_agent.set_dataset_permissions( data.dataset, output_permissions ) # Create an empty file immediately open( data.file_name, "w" ).close() # This may not be neccesary with the new parent/child associations diff --git a/lib/galaxy/tools/actions/upload.py b/lib/galaxy/tools/actions/upload.py index a9623ae40e0..9fba4fe467d 100644 --- a/lib/galaxy/tools/actions/upload.py +++ b/lib/galaxy/tools/actions/upload.py @@ -66,8 +66,7 @@ class UploadToolAction( object ): def upload_empty(self, trans, err_code, err_msg): data = trans.app.model.HistoryDatasetAssociation( create_dataset=True ) - # TODO, Nate: Make sure the following is appropriate. - trans.app.security_agent.set_dataset_groups( data.dataset, trans.history.default_groups ) + trans.app.security_agent.set_dataset_permissions( data.dataset, trans.app.security_agent.history_get_default_access( trans.history ) ) data.name = err_code data.extension = "txt" data.dbkey = "?" @@ -161,8 +160,7 @@ class UploadToolAction( object ): info = 'uploaded %s file' %data_type data = trans.app.model.HistoryDatasetAssociation( history = trans.history, extension = ext, create_dataset = True ) - # TODO, Nate: Make sure the following is appropriate. - trans.app.security_agent.set_dataset_groups( data.dataset, trans.history.default_groups ) + trans.app.security_agent.set_dataset_permissions( data.dataset, trans.app.security_agent.history_get_default_access( 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 b626e665472..4167629927b 100644 --- a/lib/galaxy/tools/parameters/basic.py +++ b/lib/galaxy/tools/parameters/basic.py @@ -992,15 +992,15 @@ class DataToolParameter( ToolParameter ): >>> group.flush() >>> Group.public_id = group.id >>> dataset1 = HistoryDatasetAssociation( id=1, extension='txt', create_dataset=True ) - >>> security_agent.set_dataset_groups( dataset1, [ group ] ) + >>> security_agent.set_dataset_permissions( dataset1, [ ( group, security_agent.permitted_actions.__dict__.values() ) ] ) >>> dataset2 = HistoryDatasetAssociation( id=2, extension='bed', create_dataset=True ) - >>> security_agent.set_dataset_groups( dataset2, [ group ] ) + >>> security_agent.set_dataset_permissions( dataset2, [ ( group, security_agent.permitted_actions.__dict__.values() ) ] ) >>> dataset3 = HistoryDatasetAssociation( id=3, extension='fasta', create_dataset=True ) - >>> security_agent.set_dataset_groups( dataset3, [ group ] ) + >>> security_agent.set_dataset_permissions( dataset3, [ ( group, security_agent.permitted_actions.__dict__.values() ) ] ) >>> dataset4 = HistoryDatasetAssociation( id=4, extension='png', create_dataset=True ) - >>> security_agent.set_dataset_groups( dataset4, [ group ] ) + >>> security_agent.set_dataset_permissions( dataset4, [ ( group, security_agent.permitted_actions.__dict__.values() ) ] ) >>> dataset5 = HistoryDatasetAssociation( id=5, extension='interval', create_dataset=True ) - >>> security_agent.set_dataset_groups( dataset5, [ group ] ) + >>> 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 ) diff --git a/lib/galaxy/web/controllers/async.py b/lib/galaxy/web/controllers/async.py index dcb6a0c1be5..f0e4ec01e7f 100644 --- a/lib/galaxy/web/controllers/async.py +++ b/lib/galaxy/web/controllers/async.py @@ -104,8 +104,7 @@ class ASync( BaseController ): #history.datasets.add_dataset( data ) data = trans.app.model.HistoryDatasetAssociation( create_dataset = True, extension = GALAXY_TYPE ) - # TODO, Nate: Make sure the following is functionally correct. - trans.app.security_agent.set_dataset_groups( data.dataset, trans.history.default_groups ) + trans.app.security_agent.set_dataset_permissions( data.dataset, trans.app.security_agent.history_get_default_access( trans.history ) ) data.name = GALAXY_NAME data.dbkey = GALAXY_BUILD data.info = GALAXY_INFO diff --git a/lib/galaxy/web/controllers/dataset.py b/lib/galaxy/web/controllers/dataset.py index 3d43b5bef5e..8e7a09bfc9d 100644 --- a/lib/galaxy/web/controllers/dataset.py +++ b/lib/galaxy/web/controllers/dataset.py @@ -126,4 +126,4 @@ class DatasetInterface( BaseController ): except: raise paste.httpexceptions.HTTPNotFound( "File Not Found (%s)." % ( filename ) ) else: - raise paste.httpexceptions.HTTPForbidden( "You are not permitted to access this dataset." ) + return trans.show_error_message( "You are not privileged to access this dataset." ) diff --git a/lib/galaxy/web/controllers/root.py b/lib/galaxy/web/controllers/root.py index 6850b069cb7..83ab538e87f 100644 --- a/lib/galaxy/web/controllers/root.py +++ b/lib/galaxy/web/controllers/root.py @@ -14,8 +14,6 @@ import urllib log = logging.getLogger( __name__ ) class RootController( BaseController ): - # TODO, Nate: This is where a lot of the new dataset security stuff is managed. - # Make sure it is is functionally correct. @web.expose def default(self, trans, target1=None, target2=None, **kwd): @@ -266,23 +264,18 @@ class RootController( BaseController ): """The user clicked the change_permission button on the 'Change permissions' form""" if not trans.user: return trans.show_error_message( "You must be logged in if you want to change dataset permitted actions." ) - private_dataset = 'private_dataset' - public_group = trans.app.security_agent.get_public_group() - if private_dataset in kwd and trans.app.security_agent.dataset_has_group( data.dataset.id, public_group.id ): - #check user has permission and then remove public group - if trans.app.security_agent.allow_action( trans.user, data.dataset.permitted_actions.DATASET_MANAGE_PERMISSIONS, dataset = data.dataset ): - trans.app.security_agent.disassociate_components( dataset = data, group = public_group ) - else: - return trans.show_error_message( "You are not authorized to change this dataset's permitted actions." ) - elif private_dataset not in kwd and not trans.app.security_agent.dataset_has_group( data.dataset.id, public_group.id ): - #check user has permission and then add public group - if trans.app.security_agent.allow_action( trans.user, data.dataset.permitted_actions.DATASET_MANAGE_PERMISSIONS, dataset = data.dataset ): - trans.app.security_agent.associate_components( dataset = data, group = public_group) - else: - return trans.show_error_message( "You are not authorized to change this dataset's permitted actions." ) + if trans.app.security_agent.allow_action( trans.user, data.dataset.permitted_actions.DATASET_MANAGE_PERMISSIONS, dataset = data.dataset ): + group_args = [ k.replace('group_', '', 1) for k in kwd if k.startswith('group_') ] + group_ids_checked = filter( lambda x: not x.count('_'), group_args ) + permissions = [] + for group_id in group_ids_checked: + action_strings = [ action.replace(group_id + '_', '', 1) for action in group_args if action.startswith(group_id + '_') ] + actions = trans.app.security_agent.convert_permitted_action_strings( action_strings ) + permissions.append( ( trans.app.security_agent.get_group( group_id ), actions ) ) + trans.app.security_agent.set_dataset_permissions( data.dataset, permissions ) + return trans.show_ok_message( "Dataset permissions have been set.", refresh_frames=['history'] ) else: - return trans.show_error_message( "You have not specified a valid change of permitted actions." ) - return trans.show_ok_message( 'Permitted actions have been changed.', refresh_frames=['history'] ) + return trans.show_error_message( "You are not authorized to change this dataset's permitted actions." ) data.datatype.before_edit( data ) @@ -596,12 +589,12 @@ class RootController( BaseController ): """Adds a POSTed file to a History""" try: history = trans.app.model.History.get( history_id ) - groups = history.default_groups + groups = trans.app.security_agent.history_get_default_access( history ) if copy_access_from: copy_access_from = trans.app.model.HistoryDatasetAssociation.get( copy_access_from ) - groups = copy_access_from.dataset.groups + 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_groups( data.dataset, groups ) + trans.app.security_agent.set_dataset_permissions( data.dataset, group_dataset_associations ) data.flush() data_file = open( data.file_name, "wb" ) file_data.file.seek( 0 ) @@ -629,34 +622,24 @@ class RootController( BaseController ): if 'set_permitted_actions' in kwd: """The user clicked the set_permitted_actions button on the set_permitted_actions form""" history = trans.get_history() - group_in = [] - group_out = [] - # Collect groups as entered by user - for name, value in kwd.items(): - if name.startswith( "group_" ): - group = trans.app.security_agent.get_group( name.replace( "group_", "", 1 ) ) - if not group: - return trans.show_error_message( 'You have specified an invalid group.' ) - if value == 'in': - group_in.append( group ) - else: - group_out.append( group ) - if not group_in: + group_args = [ k.replace('group_', '', 1) for k in kwd if k.startswith('group_') ] + group_ids_checked = filter( lambda x: not x.count('_'), group_args ) + if not group_ids_checked: return trans.show_error_message( "You must specify at least one default group." ) - cur_groups = [ assoc.group for assoc in history.default_groups ] - group_in.sort() - cur_groups.sort() - if cur_groups != group_in: - trans.app.security_agent.history_set_default_access( history, groups=group_in ) - return trans.show_ok_message( 'Default history permitted actions have been changed.' ) - else: - return trans.show_error_message( "You did not specify any changes to this history's default permitted actions." ) + permissions = [] + for group_id in group_ids_checked: + group = trans.app.security_agent.get_group( group_id ) + if not group: + return trans.show_error_message( 'You have specified an invalid group.' ) + action_strings = [ action.replace(group_id + '_', '', 1) for action in group_args if action.startswith(group_id + '_') ] + permissions.append( ( group, trans.app.security_agent.convert_permitted_action_strings( action_strings ) ) ) + trans.app.security_agent.history_set_default_access( history, permissions = permissions ) + return trans.show_ok_message( 'Default history permitted actions have been changed.' ) return trans.fill_template( 'history/permissions.mako' ) else: #user not logged in, history group must be only public return trans.show_error_message( "You must be logged in to change a history's default permitted actions." ) - @web.expose def dataset_make_primary( self, trans, id=None): """Copies a dataset and makes primary""" diff --git a/lib/galaxy/web/controllers/user.py b/lib/galaxy/web/controllers/user.py index d46abb922c5..5b00894d1f9 100644 --- a/lib/galaxy/web/controllers/user.py +++ b/lib/galaxy/web/controllers/user.py @@ -170,33 +170,23 @@ class User( BaseController ): @web.expose def set_default_permitted_actions( self, trans, **kwd ): - # TODO, Nate: Make sure this method is functionally correct. """Sets the user's default permitted actions for the new histories""" if trans.user: if 'set_permitted_actions' in kwd: """The user clicked the set_permitted_actions button on the set_permitted_actions form""" - group_in = [] - group_out = [] - # Collect groups as entered by user - for name, value in kwd.items(): - if name.startswith( "group_" ): - group = trans.app.security_agent.get_group( name.replace( "group_", "", 1 ) ) - if not group: - return trans.show_error_message( 'You have specified an invalid group.' ) - if value == 'in': - group_in.append( group ) - else: - group_out.append( group ) - if not group_in: - return trans.show_error_message( "You must specify at least one default group." ) - cur_groups = [ assoc.group for assoc in trans.user.default_groups ] - group_in.sort() - cur_groups.sort() - if cur_groups != group_in: - trans.app.security_agent.user_set_default_access( trans.user, groups = group_in ) - return trans.show_ok_message( 'Default new history permitted actions have been changed.' ) - else: - return trans.show_error_message( "You did not specify any changes to new history's default permitted actions." ) + group_args = [ k.replace('group_', '', 1) for k in kwd if k.startswith('group_') ] + group_ids_checked = filter( lambda x: not x.count('_'), group_args ) + if not group_ids_checked: + return trans.show_error_message( "You must specify at least one default group." ) + permissions = [] + for group_id in group_ids_checked: + group = trans.app.security_agent.get_group( group_id ) + if not group: + return trans.show_error_message( 'You have specified an invalid group.' ) + action_strings = [ action.replace(group_id + '_', '', 1) for action in group_args if action.startswith(group_id + '_') ] + permissions.append( ( group, trans.app.security_agent.convert_permitted_action_strings( action_strings ) ) ) + trans.app.security_agent.user_set_default_access( trans.user, permissions ) + return trans.show_ok_message( 'Default new history permitted actions have been changed.' ) return trans.fill_template( 'user/permissions.mako' ) else: # User not logged in, history group must be only public diff --git a/lib/galaxy/web/framework/__init__.py b/lib/galaxy/web/framework/__init__.py index 87f099a681d..bd3741a939d 100644 --- a/lib/galaxy/web/framework/__init__.py +++ b/lib/galaxy/web/framework/__init__.py @@ -433,10 +433,9 @@ class UniverseWebTransaction( base.DefaultWebTransaction ): galaxy_session.flush() self.__galaxy_session = galaxy_session if history is not None and user is not None: - # TODO, Nate: Make sure the following is functionally correct if not history.user: # This user will now acquire previously non-owned history, so set permitted actions to user's default - self.app.security_agent.history_set_default_access( history, groups=user.default_groups, dataset=True ) + self.app.security_agent.history_set_default_access( history, dataset=True ) history.user_id = user.id history.flush() self.__history = history diff --git a/static/june_2007_style/blue/base.css b/static/june_2007_style/blue/base.css index e0ac97ea56b..2cf80e80722 100644 --- a/static/june_2007_style/blue/base.css +++ b/static/june_2007_style/blue/base.css @@ -414,4 +414,8 @@ div.popupmenu-item:hover { .popup-arrow:hover { color: black; -} \ No newline at end of file +} + +div.permissionContainer { + padding-left: 20px; +} diff --git a/templates/dataset/edit_attributes.mako b/templates/dataset/edit_attributes.mako index c54f6eb0d1a..712b2eccfc5 100644 --- a/templates/dataset/edit_attributes.mako +++ b/templates/dataset/edit_attributes.mako @@ -133,33 +133,80 @@

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

-
Change Permitted Actions
+
Change Dataset Access Permissions
- - <% checked = "" %> - %if not trans.app.security_agent.dataset_has_group( data.id, trans.app.model.Group.get_public_group().id ): - <% checked = " checked" %> - %endif -
- -
-
-
- This will prevent other users from viewing or utilizing this dataset, even if you share your history with them. -
-
-
-
+ <% 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 do not have permission to edit this dataset's permissions. +
+
+
+
%endif diff --git a/templates/history/permissions.mako b/templates/history/permissions.mako index 6607f002c41..013481d9351 100644 --- a/templates/history/permissions.mako +++ b/templates/history/permissions.mako @@ -2,6 +2,23 @@ <%def name="title()">Change Default History Permitted Actions %if trans.user: +
Change Default History Permitted Actions
@@ -9,27 +26,34 @@
<% user_groups = [ assoc.group for assoc in trans.user.groups ] %> <% cur_groups = [ assoc.group for assoc in trans.get_history().default_groups ] %> -
- - %for group in user_groups: - + + checked + %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 -
GroupInOut
${group.name}
-
- This will change the default permitted actions assigned to new datasets for your current history. + This will change the default permitted actions assigned + to new datasets in your current history. You may also + specify the defaults for new histories via the + user options. +
@@ -39,4 +63,4 @@
-%endif \ No newline at end of file +%endif diff --git a/templates/user/permissions.mako b/templates/user/permissions.mako index 7b6b90f976d..e451bc71c29 100644 --- a/templates/user/permissions.mako +++ b/templates/user/permissions.mako @@ -2,6 +2,23 @@ <%def name="title()">Change Default History Permitted Actions %if trans.user: +
Change Default Permitted Actions for new Histories
@@ -9,27 +26,33 @@
<% user_groups = [ assoc.group for assoc in trans.user.groups ] %> <% cur_groups = [ assoc.group for assoc in trans.user.default_groups ] %> -
- - %for group in user_groups: - + + checked + %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 -
GroupInOut
${group.name}
-
- This will change the default permitted actions assigned to new datasets for new histories. + This will change the default permitted actions assigned + to new datasets in new histories. You may also specify + per-history defaults via the + history options.
@@ -39,4 +62,4 @@
-%endif \ No newline at end of file +%endif diff --git a/tools/data_source/encode_import_code.py b/tools/data_source/encode_import_code.py index ddfbb0241e0..19fb17fef95 100644 --- a/tools/data_source/encode_import_code.py +++ b/tools/data_source/encode_import_code.py @@ -38,8 +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 ) - #TODO, Nate: Make sure the following is functionally correct - app.security_agent.set_dataset_groups( newdata.dataset, base_dataset.dataset.groups ) + app.security_agent.set_dataset_permissions( newdata.dataset, base_dataset.dataset.groups ) 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 e8816f093ff..b5ccbf11c3c 100644 --- a/tools/data_source/microbial_import_code.py +++ b/tools/data_source/microbial_import_code.py @@ -129,8 +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() - #TODO, Nate: Make sure the following is functionally correct - app.security_agent.set_dataset_groups( newdata.dataset, base_dataset.dataset.groups ) + app.security_agent.set_dataset_permissions( newdata.dataset, base_dataset.dataset.groups ) 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 428486b3e37..d28c10b73ca 100644 --- a/tools/maf/maf_to_bed_code.py +++ b/tools/maf/maf_to_bed_code.py @@ -32,8 +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 ) - #TODO, Nate: Make sure the following is functionally correct - app.security_agent.set_dataset_groups( newdata.dataset, output_data.dataset.groups ) + app.security_agent.set_dataset_permissions( newdata.dataset, output_data.dataset.groups ) newdata.flush() history.flush() app.model.flush()