From fddd3b572fbfdfb3d4d4b6c9f159f7687364a730 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Fri, 1 Aug 2008 15:44:10 -0400 Subject: [PATCH 01/10] RECOMMIT: First pass with adding role based access controls. Added roles and groups. Users can be associated with groups. Roles can be associated with roles, datasets, groups, and users. Currently the creator of a dataset can mark the dataset as private, preventing other users from viewing or utilizing this dataset, even if it's containing history is shared. This is done via the edit attributes page for a particular dataset. This requires enable_beta_features to be set. --- lib/galaxy/datatypes/images.py | 4 +- lib/galaxy/model/__init__.py | 574 ++++++++++++++++++++----- lib/galaxy/model/mapping.py | 170 ++++++++ lib/galaxy/tools/__init__.py | 4 +- lib/galaxy/tools/actions/__init__.py | 16 +- lib/galaxy/tools/actions/upload.py | 4 +- lib/galaxy/tools/parameters/basic.py | 24 +- lib/galaxy/web/controllers/async.py | 2 +- lib/galaxy/web/controllers/dataset.py | 47 +- lib/galaxy/web/controllers/root.py | 220 ++++++---- lib/galaxy/web/framework/__init__.py | 5 +- templates/dataset/edit_attributes.mako | 35 +- templates/root/history_common.mako | 4 +- 13 files changed, 875 insertions(+), 234 deletions(-) diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py index 90d44ab47f4..7c58d38f632 100644 --- a/lib/galaxy/datatypes/images.py +++ b/lib/galaxy/datatypes/images.py @@ -110,7 +110,7 @@ class Gmaj( data.Data ): "nobutton": "false", "urlpause" :"100", "debug": "false", - "posturl": "history_add_to?%s" % urlencode( { 'history_id': dataset.history_id, 'ext': 'maf', 'name': 'GMAJ Output on data %s' % dataset.hid, 'info': 'Added by GMAJ', 'dbkey': dataset.dbkey } ) + "posturl": "history_add_to?%s" % urlencode( { 'history_id': dataset.history_id, 'ext': 'maf', 'name': 'GMAJ Output on data %s' % dataset.hid, 'info': 'Added by GMAJ', 'dbkey': dataset.dbkey, 'copy_access_from': dataset.id } ) } class_name = "edu.psu.bx.gmaj.MajApplet.class" archive = "/static/gmaj/gmaj.jar" @@ -180,7 +180,7 @@ class Laj( data.Text ): "alignfile1": "display?id=%s" % dataset.id, "buttonlabel": "Launch LAJ", "title": "LAJ in Galaxy", - "posturl": "history_add_to?%s" % urlencode( { 'history_id': dataset.history_id, 'ext': 'lav', 'name': 'LAJ Output', 'info': 'Added by LAJ', 'dbkey': dataset.dbkey } ), + "posturl": "history_add_to?%s" % urlencode( { 'history_id': dataset.history_id, 'ext': 'lav', 'name': 'LAJ Output', 'info': 'Added by LAJ', 'dbkey': dataset.dbkey, 'copy_access_from': dataset.id } ), "noseq": "true" } class_name = "edu.psu.cse.bio.laj.LajApplet.class" diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 7d08ccb2e46..0bed6bb7d19 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -27,19 +27,99 @@ def set_datatypes_registry( d_registry ): datatypes_registry = d_registry class User( object ): - def __init__( self, email=None, password=None ): + def __init__( self, email=None, password=None, groups = [], roles = [], default_groups = [], default_roles = [] ): self.email = email self.password = password self.external = False # Relationships self.histories = [] + if not groups: + groups.append( GalaxyGroup.get( GalaxyGroup.public_id ) ) + default_groups.append( groups[-1] ) + default_groups.append( self.create_private_group() ) + group_id_added = [] + for group in groups: + if group.id not in group_id_added: + group.add_user( self ) + group_id_added.append( group.id ) + group_id_added = [] + for group in default_groups: + if group.id not in group_id_added: + user_group_assoc = DefaultUserGroupAssociation( self, group ) + user_group_assoc.flush() + group_id_added.append( group.id ) + role_id_added = [] + for role in roles: + if role.id not in role_id_added: + role.add_user( self ) + role_id_added.append( role.id ) + role_id_added = [] + for role in default_roles: + if role.id not in role_id_added: + role_group_assoc = DefaultUserRoleAssociation( self, role ) + role_group_assoc.flush() + role_id_added.append( role.id ) def set_password_cleartext( self, cleartext ): """Set 'self.password' to the digest of 'cleartext'.""" self.password = sha.new( cleartext ).hexdigest() def check_password( self, cleartext ): """Check if 'cleartext' matches 'self.password' when hashed.""" return self.password == sha.new( cleartext ).hexdigest() + def create_private_group( self ): + #create private group + group = GalaxyGroup( self.email, priority = 10 ) + group.flush() + #create private dataset access role + role = AccessRole( "%s dataset access" % self.email, list( Dataset.access_actions.__dict__.values() ), priority = 1 ) + role.flush() + #add role to group + group.add_role( role ) + #create roles for user modification of role + user_role = AccessRole( "%s role modification" % self.email, list( AccessRole.access_actions.__dict__.values() ) ) + user_role.flush() + #add role to user + user_role.add_user( self ) + #add role to role + role.add_role( user_role ) + + #create roles for user modification of group + group_role = AccessRole( "%s group modification" % self.email, list( GalaxyGroup.access_actions.__dict__.values() ) ) + group_role.flush() + #add role to group + group.add_access_role( group_role ) + #associate role and user + group_role.add_user( self ) + + #add user to group + group.add_user( self ) + group.flush() + return group + def add_group( self, group ): + return group.add_user( self ) + def has_group( self, check_group ): + return bool( UserGroupAssociation.get_by( group_id = check_group.id, user_id = self.id ) ) + def has_role( self, check_role ): + return bool( UserRoleAssociation.get_by( role_id = check_role.id, user_id = self.id ) ) + def set_default_access( self, groups = None, roles = None, history = False, dataset = False ): + if groups is not None: + for assoc in self.default_groups: #this is the association not the actual group + assoc.delete() + assoc.flush() + for group in groups: + assoc = DefaultUserGroupAssociation( self, group ) + assoc.flush() + if roles is not None: + for assoc in self.default_roles: #this is the association not the actual group + assoc.delete() + assoc.flush() + for role in roles: + assoc = DefaultUserRoleAssociation( self, role ) + assoc.flush() + if history: + for history in self.histories: + history.set_default_access( groups = groups, roles = roles, dataset = dataset ) + class Job( object ): """ A job represents a request to run a tool given input datasets, tool @@ -101,10 +181,338 @@ class JobToOutputDatasetAssociation( object ): self.name = name self.dataset = dataset +class AccessRole( object ): + dataset_actions = Bunch( VIEW = 'dataset_view', #viewing/downloading + USE = 'dataset_use', #use in jobs + ADD_ROLE = 'dataset_add_role', #dataset can be added to roles + REMOVE_ROLE = 'dataset_remove_role', #dataset can be removed from roles + ADD_GROUP = 'dataset_add_group', #dataset can be added to groups + REMOVE_GROUP = 'dataset_remove_group' ) #dataset can be removed from groups + role_actions = Bunch( ADD_DATASET = 'role_add_dataset', #add role to dataset + REMOVE_DATASET = 'role_remove_dataset', #remove role from dataset + DELETE = 'role_delete', #delete a role + MODIFY = 'role_modify', #change a role's actions, + ADD_GROUP = 'role_add_group', #add role to a group + REMOVE_GROUP = 'role_remove_group' ) #remove role from a group + group_actions = Bunch( ADD_DATASET = 'group_add_dataset', #add group to dataset + REMOVE_DATASET = 'group_remove_dataset', #remove dataset from group + DELETE = 'group_delete', #delete a group + ADD_ROLE = 'group_add_role', #add role to group + REMOVE_ROLE = 'group_remove_role', #remove role from group + ADD_USER = 'group_add_user' ) #add users to group + + access_actions = role_actions + + def __init__( self, name, actions, priority = 0 ): + self.name = name + if not isinstance( actions, list ): + actions = [ actions ] + self.actions = actions + self.priority = priority + def add_user( self, user ): + assoc = UserRoleAssociation( user, self ) + assoc.flush() + return assoc + def add_group( self, group ): + assoc = GroupRoleAssociation( group, self ) + assoc.flush() + return assoc + def add_role( self, role ): + assoc = RoleRoleAssociation( role, self ) + assoc.flush() + return assoc + def add_dataset( self, dataset ): + assoc = RoleDatasetAssociation( self, dataset ) + assoc.flush() + return assoc + +class GalaxyGroup( object ): + public_id = None + access_actions = AccessRole.group_actions + def __init__( self, name, priority = 0 ): + self.name = name + self.priority = priority + def add_user( self, user ): + assoc = UserGroupAssociation( user, self ) + assoc.flush() + return assoc + def add_role( self, role ): + return role.add_group( self ) + def add_access_role( self, role ): + assoc = GroupRoleAccessAssociation( self, role ) + assoc.flush() + return assoc + def add_dataset( self, dataset ): + assoc = GroupDatasetAssociation( self, dataset ) + assoc.flush() + return assoc + +class UserGroupAssociation( object ): + def __init__( self, user, group ): + self.user = user + self.group = group + +class RoleRoleAssociation( object ): + def __init__( self, role, target_role ): + self.role = role + self.target_role = target_role + +class GroupRoleAccessAssociation( object ): + def __init__( self, group, role ): + self.group = group + self.role = role + +class GroupRoleAssociation( object ): + def __init__( self, group, role ): + self.group = group + self.role = role + +class UserRoleAssociation( object ): + def __init__( self, user, role ): + self.user = user + self.role = role + +class GroupDatasetAssociation( object ): + def __init__( self, group, dataset ): + 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 + +class RoleDatasetAssociation( object ): + def __init__( self, role, dataset ): + if isinstance( role, RoleDatasetAssociation ) or isinstance( role, DefaultUserRoleAssociation ) or isinstance( role, DefaultHistoryRoleAssociation ): + role = role.role + self.role = role + + if isinstance( dataset, HistoryDatasetAssociation ): + dataset = dataset.dataset + self.dataset = dataset + +class DefaultUserRoleAssociation( object ): + def __init__( self, user, role ): + if isinstance( role, RoleDatasetAssociation ) or isinstance( role, DefaultUserRoleAssociation ) or isinstance( role, DefaultHistoryRoleAssociation ): + role = role.role + self.user = user + self.role = role + +class DefaultUserGroupAssociation( object ): + def __init__( self, user, group ): + if isinstance( group, GroupDatasetAssociation ) or isinstance( group, DefaultUserGroupAssociation ) or isinstance( group, DefaultHistoryGroupAssociation ): + group = group.group + self.user = user + self.group = group + +class DefaultHistoryRoleAssociation( object ): + def __init__( self, history, role ): + if isinstance( role, RoleDatasetAssociation ) or isinstance( role, DefaultUserRoleAssociation ) or isinstance( role, DefaultHistoryRoleAssociation ): + role = role.role + self.history = history + self.role = role + +class DefaultHistoryGroupAssociation( object ): + def __init__( self, history, group ): + if isinstance( group, GroupDatasetAssociation ) or isinstance( group, DefaultUserGroupAssociation ) or isinstance( group, DefaultHistoryGroupAssociation ): + group = group.group + self.history = history + self.group = group + +class Dataset( object ): + states = Bunch( NEW = 'new', + QUEUED = 'queued', + RUNNING = 'running', + OK = 'ok', + EMPTY = 'empty', + ERROR = 'error', + DISCARDED = 'discarded' ) + access_actions = AccessRole.dataset_actions + file_path = "/tmp/" + engine = None + def __init__( self, id=None, state=None, external_filename=None, extra_files_path=None, file_size=None, purgable=True, access_groups=[], access_roles=[] ): + self.id = id + self.state = state + self.deleted = False + self.purged = False + self.purgable = purgable + self.external_filename = external_filename + self._extra_files_path = extra_files_path + self.file_size = file_size + if access_groups or access_roles: + #self.flush() + for group in access_groups: + group.add_dataset( self ) + group.flush() + for role in access_roles: + role.add_dataset( self ) + role.flush() + def get_file_name( self ): + if not self.external_filename: + assert self.id is not None, "ID must be set before filename used (commit the object)" + # First try filename directly under file_path + filename = os.path.join( self.file_path, "dataset_%d.dat" % self.id ) + # Only use that filename if it already exists (backward compatibility), + # otherwise construct hashed path + if not os.path.exists( filename ): + dir = os.path.join( self.file_path, *directory_hash_id( self.id ) ) + # Create directory if it does not exist + try: + os.makedirs( dir ) + except OSError, e: + # File Exists is okay, otherwise reraise + if e.errno != errno.EEXIST: + raise + # Return filename inside hashed directory + return os.path.abspath( os.path.join( dir, "dataset_%d.dat" % self.id ) ) + else: + filename = self.external_filename + # Make filename absolute + return os.path.abspath( filename ) + + def set_file_name ( self, filename ): + if not filename: + self.external_filename = None + else: + self.external_filename = filename + + file_name = property( get_file_name, set_file_name ) + + @property + def extra_files_path( self ): + if self._extra_files_path: + path = self._extra_files_path + else: + path = os.path.join( self.file_path, "dataset_%d_files" % self.id ) + #only use path directly under self.file_path if it exists + if not os.path.exists( path ): + path = os.path.join( os.path.join( self.file_path, *directory_hash_id( self.id ) ), "dataset_%d_files" % self.id ) + # Make path absolute + return os.path.abspath( path ) + + def get_size( self ): + """Returns the size of the data on disk""" + if self.file_size: + return self.file_size + else: + try: + return os.path.getsize( self.file_name ) + except OSError: + return 0 + def set_size( self ): + """Returns the size of the data on disk""" + try: + self.file_size = os.path.getsize( self.file_name ) + except OSError: + self.file_size = 0 + def has_data( self ): + """Detects whether there is any data""" + return self.get_size() > 0 + def mark_deleted( self, include_children=True ): + self.deleted = True + def allow_action( self, user, action ): + """Returns true when user has permission to perform an action""" + + #if dataset is in public group, we always return true for viewing and using + #this may need to change when the ability to alter groups and roles is allowed + if action in [ self.access_actions.USE, self.access_actions.VIEW ] and GroupDatasetAssociation.get_by( group_id = GalaxyGroup.public_id, dataset_id = self.id ): + return True + elif user is not None: + #loop through permissions and if allowed return true: + #check roles associated directly with dataset first + for role_dataset_assoc in self.roles: + if action in role_dataset_assoc.role.actions and user.has_role( role_dataset_assoc.role ): + return True + #check roles associated with dataset through groups + for group_dataset_assoc in self.groups: + if user.has_group( group_dataset_assoc.group ): + for group_role_assoc in group_dataset_assoc.group.roles: + if action in group_role_assoc.role.actions: + return True + return False #no user and dataset not in public group, or user lacks permission + def guess_derived_groups_roles( self, other_datasets = [] ): + """Returns a list of output roles and groups based upon itself and provided datasets""" + if not other_datasets: + return [ data_group_assoc.group for data_group_assoc in self.groups ], [ data_role_assoc.role for data_role_assoc in self.roles ] + access_roles = None + priority_access_role = None + access_groups = None + priority_access_group = None + for dataset in [ self ] + other_datasets: + #determine access roles and groups for output datasets + #roles and groups for output dataset is the intersection across all inputs + #if we end up with no intersection between inputs, then we rely on priorities + if isinstance( dataset, HistoryDatasetAssociation ): + dataset = dataset.dataset + roles = [ data_role_assoc.role for data_role_assoc in dataset.roles ] + for role in roles: + if priority_access_role is None or priority_access_role.priority < role.priority: + priority_access_role = role + 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_roles is None: + access_roles = set( roles ) + access_groups = set( groups ) + else: + access_roles.intersection_update( set( roles ) ) + access_groups.intersection_update( set( groups ) ) + + #complete lists for output dataset access + if access_roles: + access_roles = list( access_roles ) + else: + access_roles = [] + if access_groups: + access_groups = list( access_groups) + else: + access_groups = [] + #if we have no roles or groups left after intersection, + #take the highest priority group or role + if not access_roles and not access_groups: + if priority_access_role and priority_access_group: + if priority_access_group.priority == priority_access_role.priority: + access_groups = [ priority_access_group ] + access_roles = [ priority_access_role ] + elif priority_access_group.priority > priority_access_role.priority: + access_groups = [ priority_access_group ] + else: + access_roles = [ priority_access_role ] + elif priority_access_role: + access_roles = [ priority_access_role ] + elif priority_access_group: + access_groups = [ priority_access_group ] + + return access_groups, access_roles + def add_group( self, group ): + return group.add_dataset( self ) + def add_role( self, role ): + return role.add_dataset( self ) + + def has_group( self, group ): + return bool( GroupDatasetAssociation.get_by( group_id = group.id, dataset_id = self.id ) ) + def has_role( self, role ): + return bool( RoleDatasetAssociation.get_by( role_id = role.id, dataset_id = self.id ) ) + + # FIXME: sqlalchemy will replace this + def _delete(self): + """Remove the file that corresponds to this data""" + try: + os.remove(self.data.file_name) + except OSError, e: + log.critical('%s delete error %s' % (self.__class__.__name__, e)) + + + class HistoryDatasetAssociation( object ): + states = Dataset.states + access_actions = Dataset.access_actions def __init__( self, id=None, hid=None, name=None, info=None, blurb=None, peek=None, extension=None, dbkey=None, metadata=None, history=None, dataset=None, deleted=False, designation=None, - parent_id=None, copied_from_history_dataset_association = None, validation_errors=None, visible=True, create_dataset = False ): + parent_id=None, copied_from_history_dataset_association = None, validation_errors=None, + visible=True, create_dataset = False, access_groups = [], access_roles = [] ): self.name = name or "Unnamed dataset" self.id = id self.hid = hid @@ -120,7 +528,7 @@ class HistoryDatasetAssociation( object ): # Relationships self.history = history if not dataset and create_dataset: - dataset = Dataset() + dataset = Dataset( access_groups = access_groups, access_roles = access_roles ) dataset.flush() self.dataset = dataset self.parent_id = parent_id @@ -131,10 +539,6 @@ class HistoryDatasetAssociation( object ): def ext( self ): return self.extension - @property - def states( self ): - return self.dataset.states - def get_dataset_state( self ): return self.dataset.state def set_dataset_state ( self, state ): @@ -252,7 +656,8 @@ class HistoryDatasetAssociation( object ): def get_converter_types(self): return self.datatype.get_converter_types( self, datatypes_registry) - def copy( self, copy_children = False, parent_id = None ): + def copy( self, copy_children = False, parent_id = None, target_user = None ): + if target_user is None: target_user = self.user des = HistoryDatasetAssociation( hid=self.hid, name=self.name, info=self.info, blurb=self.blurb, peek=self.peek, extension=self.extension, dbkey=self.dbkey, metadata=self._metadata, dataset = self.dataset, visible=self.visible, deleted=self.deleted, parent_id=parent_id, copied_from_history_dataset_association = self ) des.flush() if copy_children: @@ -274,10 +679,12 @@ class HistoryDatasetAssociation( object ): for child in self.children: child.mark_deleted() + def allow_action( self, user, action ): + return self.dataset.allow_action( user, action ) class History( object ): - def __init__( self, id=None, name=None, user=None ): + def __init__( self, id=None, name=None, user=None, default_roles = [], default_groups = [] ): self.id = id self.name = name or "Unnamed history" self.deleted = False @@ -288,6 +695,18 @@ class History( object ): self.datasets = [] self.galaxy_sessions = [] + if not default_roles: + if user: + default_roles = user.default_roles + if not default_groups: + if user: + default_groups = user.default_groups + else: + default_groups = [ GalaxyGroup.get( GalaxyGroup.public_id ) ] + + + self.set_default_access( roles = default_roles, groups = default_groups ) + def _next_hid( self ): # TODO: override this with something in the database that ensures # better integrity @@ -326,18 +745,55 @@ class History( object ): self.genome_build = genome_build self.datasets.append( dataset ) - def copy(self): - des = History() + def copy( self, target_user = None ): + if not target_user: + target_user = self.user + des = History( user = target_user ) des.flush() des.name = self.name - des.user_id = self.user_id for data in self.datasets: - new_data = data.copy( copy_children = True ) + new_data = data.copy( copy_children = True, target_user = target_user ) des.add_dataset( new_data ) new_data.flush() des.hid_counter = self.hid_counter des.flush() return des + + def set_default_access( self, groups = None, roles = None, dataset = False ): + if groups is not None: + for assoc in self.default_groups: #this is the association not the actual group + assoc.delete() + assoc.flush() + for group in groups: + assoc = DefaultHistoryGroupAssociation( self, group ) + assoc.flush() + if roles is not None: + for assoc in self.default_roles: #this is the association not the actual group + assoc.delete() + assoc.flush() + for role in roles: + assoc = DefaultHistoryRoleAssociation( self, role ) + assoc.flush() + if dataset: + for data in self.datasets: + for hda in data.dataset.history_associations: + if self.user and hda.history not in self.user.histories: + break + else: + if groups is not None: + for assoc in data.dataset.groups: #this is the association not the actual group + assoc.delete() + assoc.flush() + for group in groups: + group.add_dataset( data ) + if roles is not None: + for assoc in data.dataset.roles: #this is the association not the actual group + assoc.delete() + assoc.flush() + for role in roles: + role.add_dataset( data ) + + # class Query( object ): # def __init__( self, name=None, state=None, tool_parameters=None, history=None ): @@ -348,98 +804,6 @@ class History( object ): # self.history = history # self.datasets = [] -class Dataset( object ): - states = Bunch( NEW = 'new', - QUEUED = 'queued', - RUNNING = 'running', - OK = 'ok', - EMPTY = 'empty', - ERROR = 'error', - DISCARDED = 'discarded' ) - file_path = "/tmp/" - engine = None - def __init__( self, id=None, state=None, external_filename=None, extra_files_path=None, file_size=None, purgable=True ): - self.id = id - self.state = state - self.deleted = False - self.purged = False - self.purgable = purgable - self.external_filename = external_filename - self._extra_files_path = extra_files_path - self.file_size = file_size - - def get_file_name( self ): - if not self.external_filename: - assert self.id is not None, "ID must be set before filename used (commit the object)" - # First try filename directly under file_path - filename = os.path.join( self.file_path, "dataset_%d.dat" % self.id ) - # Only use that filename if it already exists (backward compatibility), - # otherwise construct hashed path - if not os.path.exists( filename ): - dir = os.path.join( self.file_path, *directory_hash_id( self.id ) ) - # Create directory if it does not exist - try: - os.makedirs( dir ) - except OSError, e: - # File Exists is okay, otherwise reraise - if e.errno != errno.EEXIST: - raise - # Return filename inside hashed directory - return os.path.abspath( os.path.join( dir, "dataset_%d.dat" % self.id ) ) - else: - filename = self.external_filename - # Make filename absolute - return os.path.abspath( filename ) - - def set_file_name ( self, filename ): - if not filename: - self.external_filename = None - else: - self.external_filename = filename - - file_name = property( get_file_name, set_file_name ) - - @property - def extra_files_path( self ): - if self._extra_files_path: - path = self._extra_files_path - else: - path = os.path.join( self.file_path, "dataset_%d_files" % self.id ) - #only use path directly under self.file_path if it exists - if not os.path.exists( path ): - path = os.path.join( os.path.join( self.file_path, *directory_hash_id( self.id ) ), "dataset_%d_files" % self.id ) - # Make path absolute - return os.path.abspath( path ) - - def get_size( self ): - """Returns the size of the data on disk""" - if self.file_size: - return self.file_size - else: - try: - return os.path.getsize( self.file_name ) - except OSError: - return 0 - def set_size( self ): - """Returns the size of the data on disk""" - try: - self.file_size = os.path.getsize( self.file_name ) - except OSError: - self.file_size = 0 - def has_data( self ): - """Detects whether there is any data""" - return self.get_size() > 0 - def mark_deleted( self, include_children=True ): - self.deleted = True - - # FIXME: sqlalchemy will replace this - def _delete(self): - """Remove the file that corresponds to this data""" - try: - os.remove(self.data.file_name) - except OSError, e: - log.critical('%s delete error %s' % (self.__class__.__name__, e)) - class Old_Dataset( Dataset ): pass diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index cdc8419c439..335ab183dd5 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -114,6 +114,99 @@ ValidationError.table = Table( "validation_error", metadata, Column( "err_type", TrimmedString( 64 ) ), Column( "attributes", TEXT ) ) +GalaxyGroup.table = Table( "galaxy_group", metadata, + Column( "id", Integer, primary_key=True ), + Column( "create_time", DateTime, default=now ), + Column( "update_time", DateTime, default=now, onupdate=now ), + Column( "name", TEXT ), + Column( "priority", Integer ) ) + +UserGroupAssociation.table = Table( "user_group_association", metadata, + Column( "id", Integer, primary_key=True ), + Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), + Column( "group_id", Integer, ForeignKey( "galaxy_group.id" ), index=True ), + Column( "create_time", DateTime, default=now ), + Column( "update_time", DateTime, default=now, onupdate=now ) ) + +AccessRole.table = Table( "access_role", metadata, + Column( "id", Integer, primary_key=True ), + Column( "create_time", DateTime, default=now ), + Column( "update_time", DateTime, default=now, onupdate=now ), + Column( "name", TEXT ), + Column( "actions", JSONType(), default=[] ), + Column( "priority", Integer ) ) + +UserRoleAssociation.table = Table( "user_role_association", metadata, + Column( "id", Integer, primary_key=True ), + Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), + Column( "role_id", Integer, ForeignKey( "access_role.id" ), index=True ), + Column( "create_time", DateTime, default=now ), + Column( "update_time", DateTime, default=now, onupdate=now ) ) + +GroupRoleAssociation.table = Table( "group_role_association", metadata, + Column( "id", Integer, primary_key=True ), + Column( "group_id", Integer, ForeignKey( "galaxy_group.id" ), index=True ), + Column( "role_id", Integer, ForeignKey( "access_role.id" ), index=True ), + Column( "create_time", DateTime, default=now ), + Column( "update_time", DateTime, default=now, onupdate=now ) ) + +GroupDatasetAssociation.table = Table( "group_dataset_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 ) ) + +RoleDatasetAssociation.table = Table( "role_dataset_association", metadata, + Column( "id", Integer, primary_key=True ), + Column( "role_id", Integer, ForeignKey( "access_role.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 ) ) + +RoleRoleAssociation.table = Table( "role_role_association", metadata, + Column( "id", Integer, primary_key=True ), + Column( "role_id", Integer, ForeignKey( "access_role.id" ), index=True ), + Column( "target_role_id", Integer, ForeignKey( "access_role.id" ), index=True ), + Column( "create_time", DateTime, default=now ), + Column( "update_time", DateTime, default=now, onupdate=now ) ) + +GroupRoleAccessAssociation.table = Table( "group_role_access_association", metadata, + Column( "id", Integer, primary_key=True ), + Column( "role_id", Integer, ForeignKey( "access_role.id" ), index=True ), + Column( "group_id", Integer, ForeignKey( "galaxy_group.id" ), index=True ), + Column( "create_time", DateTime, default=now ), + Column( "update_time", DateTime, default=now, onupdate=now ) ) + + +DefaultUserRoleAssociation.table = Table( "default_user_role_association", metadata, + Column( "id", Integer, primary_key=True ), + Column( "role_id", Integer, ForeignKey( "access_role.id" ), index=True ), + Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), + Column( "create_time", DateTime, default=now ), + Column( "update_time", DateTime, default=now, onupdate=now ) ) + +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( "create_time", DateTime, default=now ), + Column( "update_time", DateTime, default=now, onupdate=now ) ) + +DefaultHistoryRoleAssociation.table = Table( "default_history_role_association", metadata, + Column( "id", Integer, primary_key=True ), + Column( "role_id", Integer, ForeignKey( "access_role.id" ), index=True ), + Column( "history_id", Integer, ForeignKey( "history.id" ), index=True ), + Column( "create_time", DateTime, default=now ), + Column( "update_time", DateTime, default=now, onupdate=now ) ) + +DefaultHistoryGroupAssociation.table = Table( "default_history_group_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( "create_time", DateTime, default=now ), + Column( "update_time", DateTime, default=now, onupdate=now ) ) + Job.table = Table( "job", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), @@ -298,6 +391,56 @@ assign_mapper( context, User, User.table, collection_class=ordering_list( 'order_index' ) ) ) ) +assign_mapper( context, GalaxyGroup, GalaxyGroup.table, + properties=dict( users=relation( UserGroupAssociation ), + datasets=relation( GroupDatasetAssociation ) ) ) + +assign_mapper( context, UserGroupAssociation, UserGroupAssociation.table, + properties=dict( user=relation( User, backref = "groups" ), + group=relation( GalaxyGroup, backref = "users" ) ) ) + +assign_mapper( context, UserRoleAssociation, UserRoleAssociation.table, + properties=dict( role=relation( AccessRole, backref = "users" ), + user=relation( User, backref = "roles" ) ) ) + +assign_mapper( context, GroupRoleAssociation, GroupRoleAssociation.table, + properties=dict( role=relation( AccessRole, backref = "groups" ), + group=relation( GalaxyGroup, backref = "roles" ) ) ) + +assign_mapper( context, AccessRole, AccessRole.table ) + +assign_mapper( context, GroupDatasetAssociation, GroupDatasetAssociation.table, + properties=dict( dataset=relation( Dataset, backref = "groups" ), + group=relation( GalaxyGroup, backref = "datasets" ) ) ) + +assign_mapper( context, RoleDatasetAssociation, RoleDatasetAssociation.table, + properties=dict( dataset=relation( Dataset, backref = "roles" ), + role=relation( AccessRole ) ) ) + +assign_mapper( context, RoleRoleAssociation, RoleRoleAssociation.table, + properties=dict( role=relation( AccessRole, primaryjoin=( ( RoleRoleAssociation.table.c.role_id == AccessRole.table.c.id ) ) ), + target_role=relation( AccessRole, primaryjoin=( RoleRoleAssociation.table.c.target_role_id == AccessRole.table.c.id ), backref="roles" ) ) ) + +assign_mapper( context, GroupRoleAccessAssociation, GroupRoleAccessAssociation.table, + properties=dict( role=relation( AccessRole, backref="access_groups" ), + group=relation( GalaxyGroup, backref="access_roles" ) ) ) + +assign_mapper( context, DefaultUserRoleAssociation, DefaultUserRoleAssociation.table, + properties=dict( user=relation( User, backref = "default_roles" ), + role=relation( AccessRole ) ) ) + +assign_mapper( context, DefaultUserGroupAssociation, DefaultUserGroupAssociation.table, + properties=dict( user=relation( User, backref = "default_groups" ), + group=relation( GalaxyGroup ) ) ) + +assign_mapper( context, DefaultHistoryRoleAssociation, DefaultHistoryRoleAssociation.table, + properties=dict( history=relation( History, backref = "default_roles" ), + role=relation( AccessRole ) ) ) + +assign_mapper( context, DefaultHistoryGroupAssociation, DefaultHistoryGroupAssociation.table, + properties=dict( history=relation( History, backref = "default_groups" ), + group=relation( GalaxyGroup ) ) ) + assign_mapper( context, JobToInputDatasetAssociation, JobToInputDatasetAssociation.table, properties=dict( job=relation( Job ), dataset=relation( HistoryDatasetAssociation ) ) ) @@ -411,6 +554,33 @@ def init( file_path, url, engine_options={}, create_tables=False ): result.flush = lambda *args, **kwargs: context.current.flush( *args, **kwargs ) result.context = context result.create_tables = create_tables + #set up default table entries here, currently only exist for access controls + if result.AccessRole.count() == 0: + log.warning( "There were no access roles located, setting up default (public) access roles." ) + #create public group + public_group = result.GalaxyGroup( 'public' ) + public_group.flush() + #create public_all role + public_role = result.AccessRole( 'public', [ result.Dataset.access_actions.USE, result.Dataset.access_actions.VIEW, result.GalaxyGroup.access_actions.ADD_DATASET, result.GalaxyGroup.access_actions.REMOVE_DATASET ] ) + public_role.flush() + public_group.add_role( public_role ) + + #store public group id + GalaxyGroup.public_id = public_group.id #we use the id instead of the object, because of alchemy sessions + #add all datasets to public group + for dataset in result.Dataset.select(): + public_group.add_dataset( dataset ) + + #loop through all current users and associate with the public group + #and create and associate with user's own group + for user in result.User.select(): + public_group.add_user( user ) + private_group = user.create_private_group() + user.set_default_access( groups = [ public_group, private_group ], roles = [], history = True, dataset = True ) + else: + #retrieve from database and store public group id, assume first created group is public + GalaxyGroup.public_id = result.GalaxyGroup.select( order_by = asc( result.GalaxyGroup.table.c.create_time ) )[0].id #we use the id instead of the object, because of alchemy sessions + log.debug( "Public Group identified as id = %s." % ( GalaxyGroup.public_id ) ) return result def get_suite(): diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index a56e6a829b4..9d21d33f803 100644 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -1084,7 +1084,7 @@ class Tool: if visible == "visible": visible = True 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 ) + child_dataset = self.app.model.HistoryDatasetAssociation( extension=ext, parent_id=outdata.id, designation=designation, visible=visible, dbkey=outdata.dbkey, create_dataset=True, access_groups=outdata.dataset.groups, access_roles=outdata.dataset.roles ) # Move data from temp location to dataset location shutil.move( filename, child_dataset.file_name ) child_dataset.flush() @@ -1120,7 +1120,7 @@ class Tool: else: visible = False 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 ) + primary_data = self.app.model.HistoryDatasetAssociation( extension=ext, designation=designation, visible=visible, dbkey=outdata.dbkey, create_dataset=True, access_groups=outdata.dataset.groups, access_roles=outdata.dataset.roles ) 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 f2bb0cd8185..058caf07203 100644 --- a/lib/galaxy/tools/actions/__init__.py +++ b/lib/galaxy/tools/actions/__init__.py @@ -43,6 +43,8 @@ class DefaultToolAction( object ): assoc.flush() data = new_data break + if data and not data.allow_action( trans.user, data.access_actions.USE ): + raise "User does not have permission to use a dataset (%s) provided for input." % data.id return data if isinstance( input, DataToolParameter ): if isinstance( value, list ): @@ -79,6 +81,16 @@ class DefaultToolAction( object ): data = NoneDataset( datatypes_registry = trans.app.datatypes_registry ) if data.dbkey not in [None, '?']: input_dbkey = data.dbkey + + #determine output dataset access list + existing_datasets = [ inp for inp in inp_data.values() if inp ] + if existing_datasets: + output_access_groups, output_access_roles = existing_datasets[0].dataset.guess_derived_groups_roles( existing_datasets[1:] ) + else: + #no valid inputs, we will use history defaults + output_access_roles = [ role.role for role in trans.history.default_roles ] + output_access_groups = [ group.group for group in trans.history.default_groups ] + # Build name for output datasets based on tool name and input names if len( input_names ) == 1: on_text = input_names[0] @@ -117,7 +129,7 @@ class DefaultToolAction( object ): ext = output.format if ext == "input": ext = input_ext - data = trans.app.model.HistoryDatasetAssociation( extension=ext, create_dataset=True ) + data = trans.app.model.HistoryDatasetAssociation( extension=ext, create_dataset=True, access_groups=output_access_groups, access_roles=output_access_roles ) # Commit the dataset immediately so it gets database assigned unique id data.flush() # Create an empty file immediately @@ -183,6 +195,8 @@ class DefaultToolAction( object ): job.add_parameter( name, value ) for name, dataset in inp_data.iteritems(): if dataset: + if not dataset.allow_action( trans.user, dataset.access_actions.USE ): + raise "User does not have permission to use a dataset (%s) provided for input." % data.id job.add_input_dataset( name, dataset ) else: job.add_input_dataset( name, None ) diff --git a/lib/galaxy/tools/actions/upload.py b/lib/galaxy/tools/actions/upload.py index b3904436a63..5c68786b6b1 100644 --- a/lib/galaxy/tools/actions/upload.py +++ b/lib/galaxy/tools/actions/upload.py @@ -65,7 +65,7 @@ class UploadToolAction( object ): return dict( output=data_list[0] ) def upload_empty(self, trans, err_code, err_msg): - data = trans.app.model.HistoryDatasetAssociation( create_dataset = True ) + data = trans.app.model.HistoryDatasetAssociation( create_dataset = True, access_groups = [ group.group for group in trans.history.default_groups ], access_roles = [ role.role for role in trans.history.default_roles ] ) data.name = err_code data.extension = "txt" data.dbkey = "?" @@ -158,7 +158,7 @@ class UploadToolAction( object ): if info is None: info = 'uploaded %s file' %data_type - data = trans.app.model.HistoryDatasetAssociation( history = trans.history, extension = ext, create_dataset = True ) + data = trans.app.model.HistoryDatasetAssociation( history = trans.history, extension = ext, create_dataset = True, access_groups = [ group.group for group in trans.history.default_groups ], access_roles = [ role.role for role in trans.history.default_roles ] ) 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 b10b51bdb5e..212a691d6aa 100644 --- a/lib/galaxy/tools/parameters/basic.py +++ b/lib/galaxy/tools/parameters/basic.py @@ -979,19 +979,25 @@ class DataToolParameter( ToolParameter ): displayed as radio buttons and multiple selects as a set of checkboxes >>> # Mock up a history (not connected to database) - >>> from galaxy.model import History, HistoryDatasetAssociation + >>> from galaxy.model import History, HistoryDatasetAssociation, User, AccessRole, GalaxyGroup, GroupRoleAssociation >>> from galaxy.util.bunch import Bunch >>> hist = History() >>> hist.flush() - >>> hist.add_dataset( HistoryDatasetAssociation( id=1, extension='txt', create_dataset=True ) ) - >>> hist.add_dataset( HistoryDatasetAssociation( id=2, extension='bed', create_dataset=True ) ) - >>> hist.add_dataset( HistoryDatasetAssociation( id=3, extension='fasta', create_dataset=True ) ) - >>> hist.add_dataset( HistoryDatasetAssociation( id=4, extension='png', create_dataset=True ) ) - >>> hist.add_dataset( HistoryDatasetAssociation( id=5, extension='interval', create_dataset=True ) ) + >>> role = AccessRole( 'test', list( AccessRole.dataset_actions.__dict__.values() ) ) + >>> role.flush() + >>> group = GalaxyGroup( 'test' ) + >>> group.flush() + >>> GalaxyGroup.public_id = group.id + >>> GroupRoleAssociation( group, role ).flush() + >>> hist.add_dataset( HistoryDatasetAssociation( id=1, extension='txt', create_dataset=True, access_groups=[ group ] ) ) + >>> hist.add_dataset( HistoryDatasetAssociation( id=2, extension='bed', create_dataset=True, access_groups=[ group ] ) ) + >>> hist.add_dataset( HistoryDatasetAssociation( id=3, extension='fasta', create_dataset=True, access_groups=[ group ] ) ) + >>> hist.add_dataset( HistoryDatasetAssociation( id=4, extension='png', create_dataset=True, access_groups=[ group ] ) ) + >>> hist.add_dataset( HistoryDatasetAssociation( id=5, extension='interval', create_dataset=True, access_groups=[ group ] ) ) >>> p = DataToolParameter( None, XML( '' ) ) >>> print p.name blah - >>> print p.get_html( trans=Bunch( history=hist ) ) + >>> print p.get_html( trans=Bunch( history=hist, user=None ) ) +
+ + <% checked = "" %> + %if not data.dataset.has_group( trans.app.model.GalaxyGroup.get( trans.app.model.GalaxyGroup.public_id ) ): + <% checked = " checked" %> + %endif +
+ +
+
+
+ This will prevent other users from viewing or utilizing this dataset, even if you share your history with them. +
+
+
+
+ +
+ + + +%endif diff --git a/templates/root/history_common.mako b/templates/root/history_common.mako index 877705614ed..d7093121868 100644 --- a/templates/root/history_common.mako +++ b/templates/root/history_common.mako @@ -32,7 +32,9 @@ ## Body for history items, extra info and actions, data "peek"
- %if data_state == "queued": + %if not data.allow_action( trans.user, data.access_actions.VIEW ): +
You do not have permision to view this dataset.
+ %elif data_state == "queued":
Job is waiting to run
%elif data_state == "running":
Job is currently running
From 8fd301c2e66af7f5e95da7559c93b28961ded7a5 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Fri, 1 Aug 2008 15:45:11 -0400 Subject: [PATCH 02/10] RECOMMIT: Allow users to change the default permissions assigned to new datasets (not relying on input) for their current history. --- lib/galaxy/web/controllers/history.py | 44 +++++++++++++++++++++++++++ templates/history/options.mako | 1 + templates/history/permissions.mako | 42 +++++++++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 lib/galaxy/web/controllers/history.py create mode 100644 templates/history/permissions.mako diff --git a/lib/galaxy/web/controllers/history.py b/lib/galaxy/web/controllers/history.py new file mode 100644 index 00000000000..6483b94850c --- /dev/null +++ b/lib/galaxy/web/controllers/history.py @@ -0,0 +1,44 @@ +from galaxy.web.base.controller import * +import logging + +log = logging.getLogger( __name__ ) + +class HistoryController( BaseController ): + @web.expose + def index( self, trans, **kwd ): + raise 'Unimplemented' + + @web.expose + def set_default_permissions( self, trans, **kwd ): + """Sets the user's default permissions for the current history""" + #TODO: allow changing of default roles associated with history + if trans.user: + if 'set_permissions' in kwd: + """The user clicked the set_permissions button on the set_permissions 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.model.GalaxyGroup.get( 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 history.default_groups ] + group_in.sort() + cur_groups.sort() + if cur_groups != group_in: + history.set_default_access( groups = group_in ) + return trans.show_ok_message( 'Default history permissions have been changed.' ) + else: + return trans.show_error_message( "You did not specify any changes to this history's default permissions." ) + 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 permissions." ) diff --git a/templates/history/options.mako b/templates/history/options.mako index 4faa967742d..81cf68f30d6 100644 --- a/templates/history/options.mako +++ b/templates/history/options.mako @@ -18,6 +18,7 @@ %endif %if app.config.enable_beta_features:
  • Construct workflow from the current history
  • +
  • Change default permissions for the current history
  • %endif
  • Share current history
  • %endif diff --git a/templates/history/permissions.mako b/templates/history/permissions.mako new file mode 100644 index 00000000000..315609189cd --- /dev/null +++ b/templates/history/permissions.mako @@ -0,0 +1,42 @@ +<%inherit file="/base.mako"/> +<%def name="title()">Change Default History Permissions + +%if trans.user: +
    +
    Change Default History Permissions
    +
    +
    +
    + <% 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: + + %endfor +
    GroupInOut
    ${group.name}
    +
    + +
    + +
    + This will change the default permissions assigned to new datasets for your current history. +
    +
    +
    +
    + +
    +
    +
    +
    +%endif \ No newline at end of file From 250c5ae7595566fad5c5ecab3bc01465292a5352 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Fri, 1 Aug 2008 15:47:06 -0400 Subject: [PATCH 03/10] RECOMMIT: Allow users to specify the default permissions assigned to new histories. --- lib/galaxy/web/controllers/user.py | 34 ++++++++++++++++++++++++ templates/user/index.mako | 3 +++ templates/user/permissions.mako | 42 ++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 templates/user/permissions.mako diff --git a/lib/galaxy/web/controllers/user.py b/lib/galaxy/web/controllers/user.py index 41890611abc..bc3801137d2 100644 --- a/lib/galaxy/web/controllers/user.py +++ b/lib/galaxy/web/controllers/user.py @@ -166,3 +166,37 @@ class User( BaseController ): return trans.show_form( web.FormBuilder( web.url_for(), "Reset Password", submit_text="Submit" ) .add_text( "email", "Email", value=email, error=error ) ) + + @web.expose + def set_default_permissions( self, trans, **kwd ): + """Sets the user's default permissions for the new histories""" + #TODO: allow changing of default roles + if trans.user: + if 'set_permissions' in kwd: + """The user clicked the set_permissions button on the set_permissions form""" + group_in = [] + group_out = [] + #collect groups as entered by user + for name, value in kwd.items(): + if name.startswith( "group_" ): + group = trans.app.model.GalaxyGroup.get( 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.user.set_default_access( groups = group_in ) + return trans.show_ok_message( 'Default new history permissions have been changed.' ) + else: + return trans.show_error_message( "You did not specify any changes to new history's default permissions." ) + return trans.fill_template( 'user/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 your default permissions." ) diff --git a/templates/user/index.mako b/templates/user/index.mako index 2b11262cece..64d23dd2410 100644 --- a/templates/user/index.mako +++ b/templates/user/index.mako @@ -8,6 +8,9 @@ %else: diff --git a/templates/user/permissions.mako b/templates/user/permissions.mako new file mode 100644 index 00000000000..1b0803d44df --- /dev/null +++ b/templates/user/permissions.mako @@ -0,0 +1,42 @@ +<%inherit file="/base.mako"/> +<%def name="title()">Change Default History Permissions + +%if trans.user: +
    +
    Change Default Permissions for new Histories
    +
    +
    +
    + <% 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: + + %endfor +
    GroupInOut
    ${group.name}
    +
    + +
    + +
    + This will change the default permissions assigned to new datasets for new histories. +
    +
    +
    +
    + +
    +
    +
    +
    +%endif \ No newline at end of file From 80e56db29da96f851dcff768494987d4ac0b47ef Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Fri, 1 Aug 2008 15:47:57 -0400 Subject: [PATCH 04/10] RECOMMIT: Removing newly created history controller, and moving history_set_permissions into the root controller with the rest of the history methods. --- lib/galaxy/web/controllers/history.py | 44 --------------------------- lib/galaxy/web/controllers/root.py | 36 ++++++++++++++++++++++ templates/history/options.mako | 2 +- 3 files changed, 37 insertions(+), 45 deletions(-) delete mode 100644 lib/galaxy/web/controllers/history.py diff --git a/lib/galaxy/web/controllers/history.py b/lib/galaxy/web/controllers/history.py deleted file mode 100644 index 6483b94850c..00000000000 --- a/lib/galaxy/web/controllers/history.py +++ /dev/null @@ -1,44 +0,0 @@ -from galaxy.web.base.controller import * -import logging - -log = logging.getLogger( __name__ ) - -class HistoryController( BaseController ): - @web.expose - def index( self, trans, **kwd ): - raise 'Unimplemented' - - @web.expose - def set_default_permissions( self, trans, **kwd ): - """Sets the user's default permissions for the current history""" - #TODO: allow changing of default roles associated with history - if trans.user: - if 'set_permissions' in kwd: - """The user clicked the set_permissions button on the set_permissions 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.model.GalaxyGroup.get( 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 history.default_groups ] - group_in.sort() - cur_groups.sort() - if cur_groups != group_in: - history.set_default_access( groups = group_in ) - return trans.show_ok_message( 'Default history permissions have been changed.' ) - else: - return trans.show_error_message( "You did not specify any changes to this history's default permissions." ) - 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 permissions." ) diff --git a/lib/galaxy/web/controllers/root.py b/lib/galaxy/web/controllers/root.py index 0812ce714cf..bb6328b0a5d 100644 --- a/lib/galaxy/web/controllers/root.py +++ b/lib/galaxy/web/controllers/root.py @@ -620,6 +620,42 @@ class RootController( BaseController ): trans.log_event( "Failed to add dataset to history: %s" % ( e ) ) return trans.show_error_message("Adding File to History has Failed") + @web.expose + def history_set_default_permissions( self, trans, **kwd ): + """Sets the user's default permissions for the current history""" + #TODO: allow changing of default roles associated with history + if trans.user: + if 'set_permissions' in kwd: + """The user clicked the set_permissions button on the set_permissions 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.model.GalaxyGroup.get( 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 history.default_groups ] + group_in.sort() + cur_groups.sort() + if cur_groups != group_in: + history.set_default_access( groups = group_in ) + return trans.show_ok_message( 'Default history permissions have been changed.' ) + else: + return trans.show_error_message( "You did not specify any changes to this history's default permissions." ) + 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 permissions." ) + + @web.expose def dataset_make_primary( self, trans, id=None): """Copies a dataset and makes primary""" diff --git a/templates/history/options.mako b/templates/history/options.mako index 81cf68f30d6..1bb2794a32b 100644 --- a/templates/history/options.mako +++ b/templates/history/options.mako @@ -18,7 +18,7 @@ %endif %if app.config.enable_beta_features:
  • Construct workflow from the current history
  • -
  • Change default permissions for the current history
  • +
  • Change default permissions for the current history
  • %endif
  • Share current history %endif From 4a2efcffd15c2a34d21b3cd952da8e838526f3e3 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Tue, 5 Aug 2008 16:35:14 -0400 Subject: [PATCH 05/10] Changes to access controls, mostly cosmetic. --- lib/galaxy/model/__init__.py | 212 +++++++++++++------------ lib/galaxy/model/mapping.py | 122 ++++++++------ lib/galaxy/tools/__init__.py | 8 +- lib/galaxy/tools/actions/__init__.py | 4 +- lib/galaxy/tools/actions/upload.py | 8 +- lib/galaxy/tools/parameters/basic.py | 31 ++-- lib/galaxy/web/controllers/async.py | 4 +- lib/galaxy/web/controllers/root.py | 8 +- lib/galaxy/web/controllers/user.py | 2 +- lib/galaxy/web/framework/__init__.py | 2 +- templates/dataset/edit_attributes.mako | 2 +- 11 files changed, 238 insertions(+), 165 deletions(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 0bed6bb7d19..d74ae8a0420 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -27,38 +27,16 @@ def set_datatypes_registry( d_registry ): datatypes_registry = d_registry class User( object ): - def __init__( self, email=None, password=None, groups = [], roles = [], default_groups = [], default_roles = [] ): + def __init__( self, email=None, password=None ): self.email = email self.password = password self.external = False # Relationships self.histories = [] - if not groups: - groups.append( GalaxyGroup.get( GalaxyGroup.public_id ) ) - default_groups.append( groups[-1] ) - default_groups.append( self.create_private_group() ) - group_id_added = [] - for group in groups: - if group.id not in group_id_added: - group.add_user( self ) - group_id_added.append( group.id ) - group_id_added = [] - for group in default_groups: - if group.id not in group_id_added: - user_group_assoc = DefaultUserGroupAssociation( self, group ) - user_group_assoc.flush() - group_id_added.append( group.id ) - role_id_added = [] - for role in roles: - if role.id not in role_id_added: - role.add_user( self ) - role_id_added.append( role.id ) - role_id_added = [] - for role in default_roles: - if role.id not in role_id_added: - role_group_assoc = DefaultUserRoleAssociation( self, role ) - role_group_assoc.flush() - role_id_added.append( role.id ) + + self.set_default_access() + self.add_group( Group.get_public_group() ) + def set_password_cleartext( self, cleartext ): """Set 'self.password' to the digest of 'cleartext'.""" self.password = sha.new( cleartext ).hexdigest() @@ -66,28 +44,42 @@ class User( object ): """Check if 'cleartext' matches 'self.password' when hashed.""" return self.password == sha.new( cleartext ).hexdigest() def create_private_group( self ): + #create roles for user modification of role + user_permission = Permission( "%s role modification" % self.email, list( Role.access_actions.__dict__.values() ) ) + user_permission.flush() + user_role = Role( "%s role modification" % self.email ) + user_role.flush() + user_role.add_permission( user_permission ) + #add role to user + user_role.add_user( self ) + user_role.add_control_role( user_role ) + + #create private group - group = GalaxyGroup( self.email, priority = 10 ) + group = Group( self.email, priority = 10 ) group.flush() + #create dataset permissions + dataset_permission = Permission( "%s dataset access" % self.email, list( Dataset.access_actions.__dict__.values() ) ) + dataset_permission.flush() #create private dataset access role - role = AccessRole( "%s dataset access" % self.email, list( Dataset.access_actions.__dict__.values() ), priority = 1 ) + role = Role( "%s dataset access" % self.email, priority = 10 ) + role.add_permission( dataset_permission ) role.flush() + #add control role to role + role.add_control_role( user_role ) #add role to group group.add_role( role ) - #create roles for user modification of role - user_role = AccessRole( "%s role modification" % self.email, list( AccessRole.access_actions.__dict__.values() ) ) - user_role.flush() - #add role to user - user_role.add_user( self ) - #add role to role - role.add_role( user_role ) - #create roles for user modification of group - group_role = AccessRole( "%s group modification" % self.email, list( GalaxyGroup.access_actions.__dict__.values() ) ) + group_permission = Permission( "%s group modification" % self.email, list( Group.access_actions.__dict__.values() ) ) + group_permission.flush() + group_role = Role( "%s group modification" % self.email ) group_role.flush() + #add control role to role + group_role.add_control_role( user_role ) + group_role.add_permission( group_permission ) #add role to group - group.add_access_role( group_role ) + group.add_control_role( group_role ) #associate role and user group_role.add_user( self ) @@ -102,6 +94,9 @@ class User( object ): def has_role( self, check_role ): return bool( UserRoleAssociation.get_by( role_id = check_role.id, user_id = self.id ) ) def set_default_access( self, groups = None, roles = None, history = False, dataset = False ): + if groups is None and roles is None: + groups = [ Group.get_public_group(), self.create_private_group() ] + roles = [] if groups is not None: for assoc in self.default_groups: #this is the association not the actual group assoc.delete() @@ -181,7 +176,7 @@ class JobToOutputDatasetAssociation( object ): self.name = name self.dataset = dataset -class AccessRole( object ): +class Permission( object ): dataset_actions = Bunch( VIEW = 'dataset_view', #viewing/downloading USE = 'dataset_use', #use in jobs ADD_ROLE = 'dataset_add_role', #dataset can be added to roles @@ -200,14 +195,21 @@ class AccessRole( object ): ADD_ROLE = 'group_add_role', #add role to group REMOVE_ROLE = 'group_remove_role', #remove role from group ADD_USER = 'group_add_user' ) #add users to group - - access_actions = role_actions - - def __init__( self, name, actions, priority = 0 ): + def __init__( self, name = None, actions = [] ): self.name = name - if not isinstance( actions, list ): - actions = [ actions ] self.actions = actions + def add_action( self, action ): + if action not in self.actions: + return self.actions.append( action ) + raise 'action (%s) already exists in permissions list (%s: %s).' % ( action, self.id, self.actions ) + def remove_action( self, action ): + return self.actions.remove( action ) + +class Role( object ): + access_actions = Permission.role_actions + + def __init__( self, name, priority = 0 ): + self.name = name self.priority = priority def add_user( self, user ): assoc = UserRoleAssociation( user, self ) @@ -217,18 +219,27 @@ class AccessRole( object ): assoc = GroupRoleAssociation( group, self ) assoc.flush() return assoc - def add_role( self, role ): - assoc = RoleRoleAssociation( role, self ) + def add_permission( self, permission ): + assoc = RolePermissionAssociation( self, permission ) assoc.flush() return assoc def add_dataset( self, dataset ): assoc = RoleDatasetAssociation( self, dataset ) assoc.flush() return assoc + def add_control_role( self, role ): + assoc = RoleControlRoleAssociation( role, self ) + assoc.flush() + return assoc -class GalaxyGroup( object ): +class Group( object ): public_id = None - access_actions = AccessRole.group_actions + access_actions = Permission.group_actions + + @classmethod + def get_public_group( cls ): + return Group.get( cls.public_id ) + def __init__( self, name, priority = 0 ): self.name = name self.priority = priority @@ -238,26 +249,31 @@ class GalaxyGroup( object ): return assoc def add_role( self, role ): return role.add_group( self ) - def add_access_role( self, role ): - assoc = GroupRoleAccessAssociation( self, role ) - assoc.flush() - return assoc def add_dataset( self, dataset ): assoc = GroupDatasetAssociation( self, dataset ) assoc.flush() return assoc + def add_control_role( self, role ): + assoc = GroupControlRoleAssociation( self, role ) + assoc.flush() + return assoc + +class RolePermissionAssociation( object ): + def __init__( self, role, permission ): + self.role = role + self.permission = permission class UserGroupAssociation( object ): def __init__( self, user, group ): self.user = user self.group = group -class RoleRoleAssociation( object ): +class RoleControlRoleAssociation( object ): def __init__( self, role, target_role ): self.role = role self.target_role = target_role -class GroupRoleAccessAssociation( object ): +class GroupControlRoleAssociation( object ): def __init__( self, group, role ): self.group = group self.role = role @@ -328,10 +344,10 @@ class Dataset( object ): EMPTY = 'empty', ERROR = 'error', DISCARDED = 'discarded' ) - access_actions = AccessRole.dataset_actions + access_actions = Permission.dataset_actions file_path = "/tmp/" engine = None - def __init__( self, id=None, state=None, external_filename=None, extra_files_path=None, file_size=None, purgable=True, access_groups=[], access_roles=[] ): + def __init__( self, id=None, state=None, external_filename=None, extra_files_path=None, file_size=None, purgable=True ): self.id = id self.state = state self.deleted = False @@ -340,14 +356,7 @@ class Dataset( object ): self.external_filename = external_filename self._extra_files_path = extra_files_path self.file_size = file_size - if access_groups or access_roles: - #self.flush() - for group in access_groups: - group.add_dataset( self ) - group.flush() - for role in access_roles: - role.add_dataset( self ) - role.flush() + def get_file_name( self ): if not self.external_filename: assert self.id is not None, "ID must be set before filename used (commit the object)" @@ -416,20 +425,23 @@ class Dataset( object ): #if dataset is in public group, we always return true for viewing and using #this may need to change when the ability to alter groups and roles is allowed - if action in [ self.access_actions.USE, self.access_actions.VIEW ] and GroupDatasetAssociation.get_by( group_id = GalaxyGroup.public_id, dataset_id = self.id ): + if action in [ self.access_actions.USE, self.access_actions.VIEW ] and GroupDatasetAssociation.get_by( group_id = Group.public_id, dataset_id = self.id ): return True elif user is not None: #loop through permissions and if allowed return true: #check roles associated directly with dataset first for role_dataset_assoc in self.roles: - if action in role_dataset_assoc.role.actions and user.has_role( role_dataset_assoc.role ): - return True + if user.has_role( role_dataset_assoc.role ): + for permission in role_dataset_assoc.role.permissions: + if action in permission.permission.actions: + return True #check roles associated with dataset through groups for group_dataset_assoc in self.groups: if user.has_group( group_dataset_assoc.group ): for group_role_assoc in group_dataset_assoc.group.roles: - if action in group_role_assoc.role.actions: - return True + for permission in group_role_assoc.role.permissions: + if action in permission.permission.actions: + return True return False #no user and dataset not in public group, or user lacks permission def guess_derived_groups_roles( self, other_datasets = [] ): """Returns a list of output roles and groups based upon itself and provided datasets""" @@ -490,7 +502,22 @@ class Dataset( object ): return group.add_dataset( self ) def add_role( self, role ): return role.add_dataset( self ) - + def set_groups( self, groups ): + for assoc in self.groups: + assoc.delete() + assoc.flush() + for group in groups: + if not isinstance( group, Group ): + group = group.group + self.add_group( group ) + def set_roles( self, roles ): + for assoc in self.roles: + assoc.delete() + assoc.flush() + for role in roles: + if not isinstance( role, Role ): + role = role.role + self.add_role( role ) def has_group( self, group ): return bool( GroupDatasetAssociation.get_by( group_id = group.id, dataset_id = self.id ) ) def has_role( self, role ): @@ -512,7 +539,7 @@ class HistoryDatasetAssociation( object ): def __init__( self, id=None, hid=None, name=None, info=None, blurb=None, peek=None, extension=None, dbkey=None, metadata=None, history=None, dataset=None, deleted=False, designation=None, parent_id=None, copied_from_history_dataset_association = None, validation_errors=None, - visible=True, create_dataset = False, access_groups = [], access_roles = [] ): + visible=True, create_dataset = False ): self.name = name or "Unnamed dataset" self.id = id self.hid = hid @@ -528,7 +555,7 @@ class HistoryDatasetAssociation( object ): # Relationships self.history = history if not dataset and create_dataset: - dataset = Dataset( access_groups = access_groups, access_roles = access_roles ) + dataset = Dataset() dataset.flush() self.dataset = dataset self.parent_id = parent_id @@ -684,7 +711,7 @@ class HistoryDatasetAssociation( object ): class History( object ): - def __init__( self, id=None, name=None, user=None, default_roles = [], default_groups = [] ): + def __init__( self, id=None, name=None, user=None ): self.id = id self.name = name or "Unnamed history" self.deleted = False @@ -695,17 +722,7 @@ class History( object ): self.datasets = [] self.galaxy_sessions = [] - if not default_roles: - if user: - default_roles = user.default_roles - if not default_groups: - if user: - default_groups = user.default_groups - else: - default_groups = [ GalaxyGroup.get( GalaxyGroup.public_id ) ] - - - self.set_default_access( roles = default_roles, groups = default_groups ) + self.set_default_access() def _next_hid( self ): # TODO: override this with something in the database that ensures @@ -760,6 +777,13 @@ class History( object ): return des def set_default_access( self, groups = None, roles = None, dataset = False ): + if groups is None and roles is None: + if self.user: + groups = self.user.default_groups + roles = self.user.default_roles + else: + groups = [ Group.get_public_group() ] + roles = [] if groups is not None: for assoc in self.default_groups: #this is the association not the actual group assoc.delete() @@ -778,20 +802,12 @@ class History( object ): for data in self.datasets: for hda in data.dataset.history_associations: if self.user and hda.history not in self.user.histories: + data.dataset.set_groups( [ Group.get_public_group() ] ) + data.dataset.set_roles( [] ) break else: - if groups is not None: - for assoc in data.dataset.groups: #this is the association not the actual group - assoc.delete() - assoc.flush() - for group in groups: - group.add_dataset( data ) - if roles is not None: - for assoc in data.dataset.roles: #this is the association not the actual group - assoc.delete() - assoc.flush() - for role in roles: - role.add_dataset( data ) + data.dataset.set_groups( groups ) + data.dataset.set_roles( roles ) diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index 335ab183dd5..c3fe6561ffc 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -114,7 +114,7 @@ ValidationError.table = Table( "validation_error", metadata, Column( "err_type", TrimmedString( 64 ) ), Column( "attributes", TEXT ) ) -GalaxyGroup.table = Table( "galaxy_group", metadata, +Group.table = Table( "galaxy_group", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), @@ -128,25 +128,38 @@ UserGroupAssociation.table = Table( "user_group_association", metadata, Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ) ) -AccessRole.table = Table( "access_role", metadata, +Permission.table = Table( "permission", metadata, + Column( "id", Integer, primary_key=True ), + Column( "create_time", DateTime, default=now ), + Column( "update_time", DateTime, default=now, onupdate=now ), + Column( "name", TEXT ), + Column( "actions", JSONType(), default=[] ) ) + +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( "name", TEXT ), - Column( "actions", JSONType(), default=[] ), Column( "priority", Integer ) ) +RolePermissionAssociation.table = Table( "role_permission_association", metadata, + Column( "id", Integer, primary_key=True ), + Column( "role_id", Integer, ForeignKey( "role.id" ), index=True ), + Column( "permission_id", Integer, ForeignKey( "permission.id" ), index=True ), + Column( "create_time", DateTime, default=now ), + Column( "update_time", DateTime, default=now, onupdate=now ) ) + UserRoleAssociation.table = Table( "user_role_association", metadata, Column( "id", Integer, primary_key=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), - Column( "role_id", Integer, ForeignKey( "access_role.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 ) ) GroupRoleAssociation.table = Table( "group_role_association", metadata, Column( "id", Integer, primary_key=True ), Column( "group_id", Integer, ForeignKey( "galaxy_group.id" ), index=True ), - Column( "role_id", Integer, ForeignKey( "access_role.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 ) ) @@ -159,21 +172,21 @@ GroupDatasetAssociation.table = Table( "group_dataset_association", metadata, RoleDatasetAssociation.table = Table( "role_dataset_association", metadata, Column( "id", Integer, primary_key=True ), - Column( "role_id", Integer, ForeignKey( "access_role.id" ), index=True ), + Column( "role_id", Integer, ForeignKey( "role.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 ) ) -RoleRoleAssociation.table = Table( "role_role_association", metadata, +RoleControlRoleAssociation.table = Table( "role_control_role_association", metadata, Column( "id", Integer, primary_key=True ), - Column( "role_id", Integer, ForeignKey( "access_role.id" ), index=True ), - Column( "target_role_id", Integer, ForeignKey( "access_role.id" ), index=True ), + Column( "role_id", Integer, ForeignKey( "role.id" ), index=True ), + Column( "target_role_id", Integer, ForeignKey( "role.id" ), index=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ) ) -GroupRoleAccessAssociation.table = Table( "group_role_access_association", metadata, +GroupControlRoleAssociation.table = Table( "group_control_role_association", metadata, Column( "id", Integer, primary_key=True ), - Column( "role_id", Integer, ForeignKey( "access_role.id" ), index=True ), + Column( "role_id", Integer, ForeignKey( "role.id" ), index=True ), Column( "group_id", Integer, ForeignKey( "galaxy_group.id" ), index=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ) ) @@ -181,7 +194,7 @@ GroupRoleAccessAssociation.table = Table( "group_role_access_association", metad DefaultUserRoleAssociation.table = Table( "default_user_role_association", metadata, Column( "id", Integer, primary_key=True ), - Column( "role_id", Integer, ForeignKey( "access_role.id" ), index=True ), + Column( "role_id", Integer, ForeignKey( "role.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ) ) @@ -195,7 +208,7 @@ DefaultUserGroupAssociation.table = Table( "default_user_group_association", met DefaultHistoryRoleAssociation.table = Table( "default_history_role_association", metadata, Column( "id", Integer, primary_key=True ), - Column( "role_id", Integer, ForeignKey( "access_role.id" ), index=True ), + Column( "role_id", Integer, ForeignKey( "role.id" ), index=True ), Column( "history_id", Integer, ForeignKey( "history.id" ), index=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ) ) @@ -391,55 +404,62 @@ assign_mapper( context, User, User.table, collection_class=ordering_list( 'order_index' ) ) ) ) -assign_mapper( context, GalaxyGroup, GalaxyGroup.table, +assign_mapper( context, Group, Group.table, properties=dict( users=relation( UserGroupAssociation ), datasets=relation( GroupDatasetAssociation ) ) ) assign_mapper( context, UserGroupAssociation, UserGroupAssociation.table, properties=dict( user=relation( User, backref = "groups" ), - group=relation( GalaxyGroup, backref = "users" ) ) ) + group=relation( Group, backref = "users" ) ) ) assign_mapper( context, UserRoleAssociation, UserRoleAssociation.table, - properties=dict( role=relation( AccessRole, backref = "users" ), + properties=dict( role=relation( Role, backref = "users" ), user=relation( User, backref = "roles" ) ) ) assign_mapper( context, GroupRoleAssociation, GroupRoleAssociation.table, - properties=dict( role=relation( AccessRole, backref = "groups" ), - group=relation( GalaxyGroup, backref = "roles" ) ) ) + properties=dict( role=relation( Role, backref = "groups" ), + group=relation( Group, backref = "roles" ) ) ) + +assign_mapper( context, Permission, Permission.table ) + +assign_mapper( context, Role, Role.table ) + +assign_mapper( context, RolePermissionAssociation, RolePermissionAssociation.table, + properties=dict( role=relation( Role, backref = "permissions" ), + permission=relation( Permission, backref = "roles" ) ) ) -assign_mapper( context, AccessRole, AccessRole.table ) assign_mapper( context, GroupDatasetAssociation, GroupDatasetAssociation.table, properties=dict( dataset=relation( Dataset, backref = "groups" ), - group=relation( GalaxyGroup, backref = "datasets" ) ) ) + group=relation( Group, backref = "datasets" ) ) ) assign_mapper( context, RoleDatasetAssociation, RoleDatasetAssociation.table, properties=dict( dataset=relation( Dataset, backref = "roles" ), - role=relation( AccessRole ) ) ) + role=relation( Role ) ) ) -assign_mapper( context, RoleRoleAssociation, RoleRoleAssociation.table, - properties=dict( role=relation( AccessRole, primaryjoin=( ( RoleRoleAssociation.table.c.role_id == AccessRole.table.c.id ) ) ), - target_role=relation( AccessRole, primaryjoin=( RoleRoleAssociation.table.c.target_role_id == AccessRole.table.c.id ), backref="roles" ) ) ) +assign_mapper( context, RoleControlRoleAssociation, RoleControlRoleAssociation.table, + properties=dict( role=relation( Role, primaryjoin=( ( RoleControlRoleAssociation.table.c.role_id == Role.table.c.id ) ) ), + target_role=relation( Role, primaryjoin=( RoleControlRoleAssociation.table.c.target_role_id == Role.table.c.id ), backref="roles" ) ) ) -assign_mapper( context, GroupRoleAccessAssociation, GroupRoleAccessAssociation.table, - properties=dict( role=relation( AccessRole, backref="access_groups" ), - group=relation( GalaxyGroup, backref="access_roles" ) ) ) +assign_mapper( context, GroupControlRoleAssociation, GroupControlRoleAssociation.table, + properties=dict( role=relation( Role, backref="access_groups" ), + group=relation( Group, backref="access_roles" ) ) ) assign_mapper( context, DefaultUserRoleAssociation, DefaultUserRoleAssociation.table, properties=dict( user=relation( User, backref = "default_roles" ), - role=relation( AccessRole ) ) ) + role=relation( Role ) ) ) assign_mapper( context, DefaultUserGroupAssociation, DefaultUserGroupAssociation.table, properties=dict( user=relation( User, backref = "default_groups" ), - group=relation( GalaxyGroup ) ) ) + group=relation( Group ) ) ) assign_mapper( context, DefaultHistoryRoleAssociation, DefaultHistoryRoleAssociation.table, properties=dict( history=relation( History, backref = "default_roles" ), - role=relation( AccessRole ) ) ) + role=relation( Role ) ) ) assign_mapper( context, DefaultHistoryGroupAssociation, DefaultHistoryGroupAssociation.table, properties=dict( history=relation( History, backref = "default_groups" ), - group=relation( GalaxyGroup ) ) ) + group=relation( Group ) ) ) assign_mapper( context, JobToInputDatasetAssociation, JobToInputDatasetAssociation.table, properties=dict( job=relation( Job ), dataset=relation( HistoryDatasetAssociation ) ) ) @@ -555,32 +575,42 @@ def init( file_path, url, engine_options={}, create_tables=False ): result.context = context result.create_tables = create_tables #set up default table entries here, currently only exist for access controls - if result.AccessRole.count() == 0: + if result.Role.count() == 0: log.warning( "There were no access roles located, setting up default (public) access roles." ) #create public group - public_group = result.GalaxyGroup( 'public' ) + public_group = result.Group( 'public' ) public_group.flush() #create public_all role - public_role = result.AccessRole( 'public', [ result.Dataset.access_actions.USE, result.Dataset.access_actions.VIEW, result.GalaxyGroup.access_actions.ADD_DATASET, result.GalaxyGroup.access_actions.REMOVE_DATASET ] ) + public_role = result.Role( 'public' ) public_role.flush() public_group.add_role( public_role ) + permission = result.Permission( 'public', [ result.Dataset.access_actions.USE, result.Dataset.access_actions.VIEW, result.Group.access_actions.ADD_DATASET, result.Group.access_actions.REMOVE_DATASET ] ) + permission.flush() + public_role.add_permission( permission ) #store public group id - GalaxyGroup.public_id = public_group.id #we use the id instead of the object, because of alchemy sessions - #add all datasets to public group - for dataset in result.Dataset.select(): - public_group.add_dataset( dataset ) + Group.public_id = public_group.id #we use the id instead of the object, because of alchemy sessions - #loop through all current users and associate with the public group - #and create and associate with user's own group - for user in result.User.select(): - public_group.add_user( user ) - private_group = user.create_private_group() - user.set_default_access( groups = [ public_group, private_group ], roles = [], history = True, dataset = True ) + #loop through all histories and set up rbac on users, histories and datasets + for history in result.History.select(): + if history.user: + if not history.user.default_groups: + history.user.set_default_access( history = True, dataset = True ) + history.user.add_group( public_group ) + history.user.flush() + else: + history.set_default_access( dataset = True ) + history.flush() + #add all datasets which aren't in a history to the public group + orphans = result.Dataset.get_by( history_id = None ) + if orphans: + for dataset in orphans: + dataset.set_groups( [ public_group ] ) + dataset.set_roles( [] ) else: #retrieve from database and store public group id, assume first created group is public - GalaxyGroup.public_id = result.GalaxyGroup.select( order_by = asc( result.GalaxyGroup.table.c.create_time ) )[0].id #we use the id instead of the object, because of alchemy sessions - log.debug( "Public Group identified as id = %s." % ( GalaxyGroup.public_id ) ) + Group.public_id = result.Group.select( order_by = asc( result.Group.table.c.create_time ) )[0].id #we use the id instead of the object, because of alchemy sessions + log.debug( "Public Group identified as id = %s." % ( Group.public_id ) ) return result def get_suite(): diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index 9d21d33f803..af94a3a24ce 100644 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -1084,7 +1084,9 @@ class Tool: if visible == "visible": visible = True 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, access_groups=outdata.dataset.groups, access_roles=outdata.dataset.roles ) + child_dataset = self.app.model.HistoryDatasetAssociation( extension=ext, parent_id=outdata.id, designation=designation, visible=visible, dbkey=outdata.dbkey, create_dataset=True ) + child_dataset.dataset.set_groups( outdata.dataset.groups ) + child_dataset.dataset.set_roles( outdata.dataset.roles ) # Move data from temp location to dataset location shutil.move( filename, child_dataset.file_name ) child_dataset.flush() @@ -1120,7 +1122,9 @@ class Tool: else: visible = False 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, access_groups=outdata.dataset.groups, access_roles=outdata.dataset.roles ) + primary_data = self.app.model.HistoryDatasetAssociation( extension=ext, designation=designation, visible=visible, dbkey=outdata.dbkey, create_dataset=True ) + primary_data.dataset.set_groups( outdata.dataset.groups ) + primary_data.dataset.set_roles( outdata.dataset.roles ) 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 058caf07203..25393b29b7f 100644 --- a/lib/galaxy/tools/actions/__init__.py +++ b/lib/galaxy/tools/actions/__init__.py @@ -129,7 +129,9 @@ class DefaultToolAction( object ): ext = output.format if ext == "input": ext = input_ext - data = trans.app.model.HistoryDatasetAssociation( extension=ext, create_dataset=True, access_groups=output_access_groups, access_roles=output_access_roles ) + data = trans.app.model.HistoryDatasetAssociation( extension=ext, create_dataset=True ) + data.dataset.set_groups( output_access_groups ) + data.dataset.set_roles( output_access_roles ) # Commit the dataset immediately so it gets database assigned unique id data.flush() # Create an empty file immediately diff --git a/lib/galaxy/tools/actions/upload.py b/lib/galaxy/tools/actions/upload.py index 5c68786b6b1..6d7675a9456 100644 --- a/lib/galaxy/tools/actions/upload.py +++ b/lib/galaxy/tools/actions/upload.py @@ -65,7 +65,9 @@ class UploadToolAction( object ): return dict( output=data_list[0] ) def upload_empty(self, trans, err_code, err_msg): - data = trans.app.model.HistoryDatasetAssociation( create_dataset = True, access_groups = [ group.group for group in trans.history.default_groups ], access_roles = [ role.role for role in trans.history.default_roles ] ) + data = trans.app.model.HistoryDatasetAssociation( create_dataset = True ) + data.dataset.set_groups( trans.history.default_groups ) + data.dataset.set_roles( trans.history.default_roles ) data.name = err_code data.extension = "txt" data.dbkey = "?" @@ -158,7 +160,9 @@ class UploadToolAction( object ): if info is None: info = 'uploaded %s file' %data_type - data = trans.app.model.HistoryDatasetAssociation( history = trans.history, extension = ext, create_dataset = True, access_groups = [ group.group for group in trans.history.default_groups ], access_roles = [ role.role for role in trans.history.default_roles ] ) + data = trans.app.model.HistoryDatasetAssociation( history = trans.history, extension = ext, create_dataset = True ) + data.dataset.set_groups( trans.history.default_groups ) + data.dataset.set_roles( trans.history.default_roles ) 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 212a691d6aa..379529b8cab 100644 --- a/lib/galaxy/tools/parameters/basic.py +++ b/lib/galaxy/tools/parameters/basic.py @@ -979,21 +979,34 @@ class DataToolParameter( ToolParameter ): displayed as radio buttons and multiple selects as a set of checkboxes >>> # Mock up a history (not connected to database) - >>> from galaxy.model import History, HistoryDatasetAssociation, User, AccessRole, GalaxyGroup, GroupRoleAssociation + >>> from galaxy.model import History, HistoryDatasetAssociation, User, Role, Permission, Group, GroupRoleAssociation >>> from galaxy.util.bunch import Bunch >>> hist = History() >>> hist.flush() - >>> role = AccessRole( 'test', list( AccessRole.dataset_actions.__dict__.values() ) ) + >>> permission = Permission( 'test', list( Permission.dataset_actions.__dict__.values() ) ) + >>> permission.flush() + >>> role = Role( 'test' ) >>> role.flush() - >>> group = GalaxyGroup( 'test' ) + >>> assoc = role.add_permission( permission ) + >>> group = Group( 'test' ) >>> group.flush() - >>> GalaxyGroup.public_id = group.id + >>> Group.public_id = group.id >>> GroupRoleAssociation( group, role ).flush() - >>> hist.add_dataset( HistoryDatasetAssociation( id=1, extension='txt', create_dataset=True, access_groups=[ group ] ) ) - >>> hist.add_dataset( HistoryDatasetAssociation( id=2, extension='bed', create_dataset=True, access_groups=[ group ] ) ) - >>> hist.add_dataset( HistoryDatasetAssociation( id=3, extension='fasta', create_dataset=True, access_groups=[ group ] ) ) - >>> hist.add_dataset( HistoryDatasetAssociation( id=4, extension='png', create_dataset=True, access_groups=[ group ] ) ) - >>> hist.add_dataset( HistoryDatasetAssociation( id=5, extension='interval', create_dataset=True, access_groups=[ group ] ) ) + >>> dataset1 = HistoryDatasetAssociation( id=1, extension='txt', create_dataset=True ) + >>> dataset1.dataset.set_groups( [ group ] ) + >>> dataset2 = HistoryDatasetAssociation( id=2, extension='bed', create_dataset=True ) + >>> dataset2.dataset.set_groups( [ group ] ) + >>> dataset3 = HistoryDatasetAssociation( id=3, extension='fasta', create_dataset=True ) + >>> dataset3.dataset.set_groups( [ group ] ) + >>> dataset4 = HistoryDatasetAssociation( id=4, extension='png', create_dataset=True ) + >>> dataset4.dataset.set_groups( [ group ] ) + >>> dataset5 = HistoryDatasetAssociation( id=5, extension='interval', create_dataset=True ) + >>> dataset5.dataset.set_groups( [ group ] ) + >>> 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 diff --git a/lib/galaxy/web/controllers/async.py b/lib/galaxy/web/controllers/async.py index 4d68fb8eb0c..9278e5f3cf6 100644 --- a/lib/galaxy/web/controllers/async.py +++ b/lib/galaxy/web/controllers/async.py @@ -103,7 +103,9 @@ class ASync( BaseController ): #data.state = jobs.JOB_OK #history.datasets.add_dataset( data ) - data = trans.app.model.HistoryDatasetAssociation( create_dataset = True, extension = GALAXY_TYPE, access_groups = [ group.group for group in trans.history.default_groups ], access_roles = [ role.role for role in trans.history.default_roles ] ) + data = trans.app.model.HistoryDatasetAssociation( create_dataset = True, extension = GALAXY_TYPE ) + data.dataset.set_groups( trans.history.default_groups ) + data.dataset.set_roles( trans.history.default_roles ) 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 bb6328b0a5d..3a9f9471ef5 100644 --- a/lib/galaxy/web/controllers/root.py +++ b/lib/galaxy/web/controllers/root.py @@ -265,7 +265,7 @@ class RootController( BaseController ): if not trans.user: return trans.show_error_message( "You must be logged in if you want to change dataset permissions." ) private_dataset = 'private_dataset' - public_group = trans.app.model.GalaxyGroup.get( trans.app.model.GalaxyGroup.public_id ) + public_group = trans.app.model.Group.get_public_group() if private_dataset in kwd and data.dataset.has_group( public_group ): #check user has permision and then remove public group if data.dataset.allow_action( trans.user, data.dataset.access_actions.REMOVE_GROUP ): @@ -599,7 +599,9 @@ class RootController( BaseController ): copy_access_from = trans.app.model.HistoryDatasetAssociation.get( copy_access_from ) roles = copy_access_from.dataset.roles groups = copy_access_from.dataset.groups - data = trans.app.model.HistoryDatasetAssociation( name = name, info = info, extension = ext, dbkey = dbkey, create_dataset = True, access_groups = groups, access_roles = roles ) + data = trans.app.model.HistoryDatasetAssociation( name = name, info = info, extension = ext, dbkey = dbkey, create_dataset = True ) + data.dataset.set_groups( groups ) + data.dataset.set_roles( roles ) data.flush() data_file = open( data.file_name, "wb" ) file_data.file.seek( 0 ) @@ -633,7 +635,7 @@ class RootController( BaseController ): #collect groups as entered by user for name, value in kwd.items(): if name.startswith( "group_" ): - group = trans.app.model.GalaxyGroup.get( name.replace( "group_", "", 1 ) ) + group = trans.app.model.Group.get( name.replace( "group_", "", 1 ) ) if not group: return trans.show_error_message( 'You have specified an invalid group.' ) if value == 'in': diff --git a/lib/galaxy/web/controllers/user.py b/lib/galaxy/web/controllers/user.py index bc3801137d2..8de8eff2da0 100644 --- a/lib/galaxy/web/controllers/user.py +++ b/lib/galaxy/web/controllers/user.py @@ -179,7 +179,7 @@ class User( BaseController ): #collect groups as entered by user for name, value in kwd.items(): if name.startswith( "group_" ): - group = trans.app.model.GalaxyGroup.get( name.replace( "group_", "", 1 ) ) + group = trans.app.model.Group.get( name.replace( "group_", "", 1 ) ) if not group: return trans.show_error_message( 'You have specified an invalid group.' ) if value == 'in': diff --git a/lib/galaxy/web/framework/__init__.py b/lib/galaxy/web/framework/__init__.py index e3874e63248..111ac1a4f43 100644 --- a/lib/galaxy/web/framework/__init__.py +++ b/lib/galaxy/web/framework/__init__.py @@ -433,7 +433,7 @@ class UniverseWebTransaction( base.DefaultWebTransaction ): if history is not None and user is not None: if not history.user: #This user will now aquire previously unowned history, let set permissions to user's default - history.set_default_access( roles = [ role.role for role in user.default_roles ], groups = [ group.group for group in user.default_groups ], dataset = True ) + history.set_default_access( roles = user.default_roles, groups = user.default_groups, dataset = True ) history.user_id = user.id history.flush() self.__history = history diff --git a/templates/dataset/edit_attributes.mako b/templates/dataset/edit_attributes.mako index e159c89b3a6..aa5e0d23370 100644 --- a/templates/dataset/edit_attributes.mako +++ b/templates/dataset/edit_attributes.mako @@ -144,7 +144,7 @@ Private Dataset: <% checked = "" %> - %if not data.dataset.has_group( trans.app.model.GalaxyGroup.get( trans.app.model.GalaxyGroup.public_id ) ): + %if not data.dataset.has_group( trans.app.model.Group.get_public_group() ): <% checked = " checked" %> %endif
    From 32cb3c53414eda4cf69f7c0d520fa29427d0e447 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Wed, 6 Aug 2008 15:22:02 -0400 Subject: [PATCH 06/10] Moved security code out of model and into its own directory. --- lib/galaxy/app.py | 3 + lib/galaxy/model/__init__.py | 271 +----------------- lib/galaxy/model/mapping.py | 16 +- lib/galaxy/security/__init__.py | 362 +++++++++++++++++++++++++ lib/galaxy/tools/__init__.py | 8 +- lib/galaxy/tools/actions/__init__.py | 10 +- lib/galaxy/tools/actions/upload.py | 10 +- lib/galaxy/tools/parameters/basic.py | 23 +- lib/galaxy/web/controllers/async.py | 4 +- lib/galaxy/web/controllers/dataset.py | 2 +- lib/galaxy/web/controllers/root.py | 34 +-- lib/galaxy/web/controllers/user.py | 3 +- lib/galaxy/web/framework/__init__.py | 3 +- templates/dataset/edit_attributes.mako | 2 +- templates/root/history_common.mako | 2 +- 15 files changed, 433 insertions(+), 320 deletions(-) create mode 100644 lib/galaxy/security/__init__.py diff --git a/lib/galaxy/app.py b/lib/galaxy/app.py index 0c4ea728395..1964945012e 100644 --- a/lib/galaxy/app.py +++ b/lib/galaxy/app.py @@ -4,6 +4,7 @@ from galaxy import config, jobs, util, tools, web import galaxy.model import galaxy.model.mapping import galaxy.datatypes.registry +import galaxy.security class UniverseApplication( object ): """Encapsulates the state of a Universe application""" @@ -30,6 +31,8 @@ class UniverseApplication( object ): self.toolbox = tools.ToolBox( self.config.tool_config, self.config.tool_path, self ) #Load datatype converters self.datatypes_registry.load_datatype_converters( self.toolbox ) + #Load security policy + self.security_agent = self.model.security_agent # Start the job queue job_dispatcher = jobs.DefaultJobDispatcher( self ) self.job_queue = jobs.JobQueue( self, job_dispatcher ) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index d74ae8a0420..6e0d0922ea6 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -13,6 +13,7 @@ from galaxy import util import tempfile import galaxy.datatypes.registry from galaxy.datatypes.metadata import MetadataCollection +from galaxy.security import RBACAgent import logging log = logging.getLogger( __name__ ) @@ -34,86 +35,12 @@ class User( object ): # Relationships self.histories = [] - self.set_default_access() - self.add_group( Group.get_public_group() ) - def set_password_cleartext( self, cleartext ): """Set 'self.password' to the digest of 'cleartext'.""" self.password = sha.new( cleartext ).hexdigest() def check_password( self, cleartext ): """Check if 'cleartext' matches 'self.password' when hashed.""" return self.password == sha.new( cleartext ).hexdigest() - def create_private_group( self ): - #create roles for user modification of role - user_permission = Permission( "%s role modification" % self.email, list( Role.access_actions.__dict__.values() ) ) - user_permission.flush() - user_role = Role( "%s role modification" % self.email ) - user_role.flush() - user_role.add_permission( user_permission ) - #add role to user - user_role.add_user( self ) - user_role.add_control_role( user_role ) - - - #create private group - group = Group( self.email, priority = 10 ) - group.flush() - #create dataset permissions - dataset_permission = Permission( "%s dataset access" % self.email, list( Dataset.access_actions.__dict__.values() ) ) - dataset_permission.flush() - #create private dataset access role - role = Role( "%s dataset access" % self.email, priority = 10 ) - role.add_permission( dataset_permission ) - role.flush() - #add control role to role - role.add_control_role( user_role ) - #add role to group - group.add_role( role ) - - #create roles for user modification of group - group_permission = Permission( "%s group modification" % self.email, list( Group.access_actions.__dict__.values() ) ) - group_permission.flush() - group_role = Role( "%s group modification" % self.email ) - group_role.flush() - #add control role to role - group_role.add_control_role( user_role ) - group_role.add_permission( group_permission ) - #add role to group - group.add_control_role( group_role ) - #associate role and user - group_role.add_user( self ) - - #add user to group - group.add_user( self ) - group.flush() - return group - def add_group( self, group ): - return group.add_user( self ) - def has_group( self, check_group ): - return bool( UserGroupAssociation.get_by( group_id = check_group.id, user_id = self.id ) ) - def has_role( self, check_role ): - return bool( UserRoleAssociation.get_by( role_id = check_role.id, user_id = self.id ) ) - def set_default_access( self, groups = None, roles = None, history = False, dataset = False ): - if groups is None and roles is None: - groups = [ Group.get_public_group(), self.create_private_group() ] - roles = [] - if groups is not None: - for assoc in self.default_groups: #this is the association not the actual group - assoc.delete() - assoc.flush() - for group in groups: - assoc = DefaultUserGroupAssociation( self, group ) - assoc.flush() - if roles is not None: - for assoc in self.default_roles: #this is the association not the actual group - assoc.delete() - assoc.flush() - for role in roles: - assoc = DefaultUserRoleAssociation( self, role ) - assoc.flush() - if history: - for history in self.histories: - history.set_default_access( groups = groups, roles = roles, dataset = dataset ) class Job( object ): """ @@ -177,24 +104,10 @@ class JobToOutputDatasetAssociation( object ): self.dataset = dataset class Permission( object ): - dataset_actions = Bunch( VIEW = 'dataset_view', #viewing/downloading - USE = 'dataset_use', #use in jobs - ADD_ROLE = 'dataset_add_role', #dataset can be added to roles - REMOVE_ROLE = 'dataset_remove_role', #dataset can be removed from roles - ADD_GROUP = 'dataset_add_group', #dataset can be added to groups - REMOVE_GROUP = 'dataset_remove_group' ) #dataset can be removed from groups - role_actions = Bunch( ADD_DATASET = 'role_add_dataset', #add role to dataset - REMOVE_DATASET = 'role_remove_dataset', #remove role from dataset - DELETE = 'role_delete', #delete a role - MODIFY = 'role_modify', #change a role's actions, - ADD_GROUP = 'role_add_group', #add role to a group - REMOVE_GROUP = 'role_remove_group' ) #remove role from a group - group_actions = Bunch( ADD_DATASET = 'group_add_dataset', #add group to dataset - REMOVE_DATASET = 'group_remove_dataset', #remove dataset from group - DELETE = 'group_delete', #delete a group - ADD_ROLE = 'group_add_role', #add role to group - REMOVE_ROLE = 'group_remove_role', #remove role from group - ADD_USER = 'group_add_user' ) #add users to group + dataset_actions = RBACAgent.actions.dataset_actions + role_actions = RBACAgent.actions.role_actions + group_actions = RBACAgent.actions.group_actions + def __init__( self, name = None, actions = [] ): self.name = name self.actions = actions @@ -211,26 +124,6 @@ class Role( object ): def __init__( self, name, priority = 0 ): self.name = name self.priority = priority - def add_user( self, user ): - assoc = UserRoleAssociation( user, self ) - assoc.flush() - return assoc - def add_group( self, group ): - assoc = GroupRoleAssociation( group, self ) - assoc.flush() - return assoc - def add_permission( self, permission ): - assoc = RolePermissionAssociation( self, permission ) - assoc.flush() - return assoc - def add_dataset( self, dataset ): - assoc = RoleDatasetAssociation( self, dataset ) - assoc.flush() - return assoc - def add_control_role( self, role ): - assoc = RoleControlRoleAssociation( role, self ) - assoc.flush() - return assoc class Group( object ): public_id = None @@ -243,20 +136,6 @@ class Group( object ): def __init__( self, name, priority = 0 ): self.name = name self.priority = priority - def add_user( self, user ): - assoc = UserGroupAssociation( user, self ) - assoc.flush() - return assoc - def add_role( self, role ): - return role.add_group( self ) - def add_dataset( self, dataset ): - assoc = GroupDatasetAssociation( self, dataset ) - assoc.flush() - return assoc - def add_control_role( self, role ): - assoc = GroupControlRoleAssociation( self, role ) - assoc.flush() - return assoc class RolePermissionAssociation( object ): def __init__( self, role, permission ): @@ -420,108 +299,6 @@ class Dataset( object ): return self.get_size() > 0 def mark_deleted( self, include_children=True ): self.deleted = True - def allow_action( self, user, action ): - """Returns true when user has permission to perform an action""" - - #if dataset is in public group, we always return true for viewing and using - #this may need to change when the ability to alter groups and roles is allowed - if action in [ self.access_actions.USE, self.access_actions.VIEW ] and GroupDatasetAssociation.get_by( group_id = Group.public_id, dataset_id = self.id ): - return True - elif user is not None: - #loop through permissions and if allowed return true: - #check roles associated directly with dataset first - for role_dataset_assoc in self.roles: - if user.has_role( role_dataset_assoc.role ): - for permission in role_dataset_assoc.role.permissions: - if action in permission.permission.actions: - return True - #check roles associated with dataset through groups - for group_dataset_assoc in self.groups: - if user.has_group( group_dataset_assoc.group ): - for group_role_assoc in group_dataset_assoc.group.roles: - for permission in group_role_assoc.role.permissions: - if action in permission.permission.actions: - return True - return False #no user and dataset not in public group, or user lacks permission - def guess_derived_groups_roles( self, other_datasets = [] ): - """Returns a list of output roles and groups based upon itself and provided datasets""" - if not other_datasets: - return [ data_group_assoc.group for data_group_assoc in self.groups ], [ data_role_assoc.role for data_role_assoc in self.roles ] - access_roles = None - priority_access_role = None - access_groups = None - priority_access_group = None - for dataset in [ self ] + other_datasets: - #determine access roles and groups for output datasets - #roles and groups for output dataset is the intersection across all inputs - #if we end up with no intersection between inputs, then we rely on priorities - if isinstance( dataset, HistoryDatasetAssociation ): - dataset = dataset.dataset - roles = [ data_role_assoc.role for data_role_assoc in dataset.roles ] - for role in roles: - if priority_access_role is None or priority_access_role.priority < role.priority: - priority_access_role = role - 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_roles is None: - access_roles = set( roles ) - access_groups = set( groups ) - else: - access_roles.intersection_update( set( roles ) ) - access_groups.intersection_update( set( groups ) ) - - #complete lists for output dataset access - if access_roles: - access_roles = list( access_roles ) - else: - access_roles = [] - if access_groups: - access_groups = list( access_groups) - else: - access_groups = [] - #if we have no roles or groups left after intersection, - #take the highest priority group or role - if not access_roles and not access_groups: - if priority_access_role and priority_access_group: - if priority_access_group.priority == priority_access_role.priority: - access_groups = [ priority_access_group ] - access_roles = [ priority_access_role ] - elif priority_access_group.priority > priority_access_role.priority: - access_groups = [ priority_access_group ] - else: - access_roles = [ priority_access_role ] - elif priority_access_role: - access_roles = [ priority_access_role ] - elif priority_access_group: - access_groups = [ priority_access_group ] - - return access_groups, access_roles - def add_group( self, group ): - return group.add_dataset( self ) - def add_role( self, role ): - return role.add_dataset( self ) - def set_groups( self, groups ): - for assoc in self.groups: - assoc.delete() - assoc.flush() - for group in groups: - if not isinstance( group, Group ): - group = group.group - self.add_group( group ) - def set_roles( self, roles ): - for assoc in self.roles: - assoc.delete() - assoc.flush() - for role in roles: - if not isinstance( role, Role ): - role = role.role - self.add_role( role ) - def has_group( self, group ): - return bool( GroupDatasetAssociation.get_by( group_id = group.id, dataset_id = self.id ) ) - def has_role( self, role ): - return bool( RoleDatasetAssociation.get_by( role_id = role.id, dataset_id = self.id ) ) # FIXME: sqlalchemy will replace this def _delete(self): @@ -706,9 +483,6 @@ class HistoryDatasetAssociation( object ): for child in self.children: child.mark_deleted() - def allow_action( self, user, action ): - return self.dataset.allow_action( user, action ) - class History( object ): def __init__( self, id=None, name=None, user=None ): @@ -722,8 +496,6 @@ class History( object ): self.datasets = [] self.galaxy_sessions = [] - self.set_default_access() - def _next_hid( self ): # TODO: override this with something in the database that ensures # better integrity @@ -776,39 +548,6 @@ class History( object ): des.flush() return des - def set_default_access( self, groups = None, roles = None, dataset = False ): - if groups is None and roles is None: - if self.user: - groups = self.user.default_groups - roles = self.user.default_roles - else: - groups = [ Group.get_public_group() ] - roles = [] - if groups is not None: - for assoc in self.default_groups: #this is the association not the actual group - assoc.delete() - assoc.flush() - for group in groups: - assoc = DefaultHistoryGroupAssociation( self, group ) - assoc.flush() - if roles is not None: - for assoc in self.default_roles: #this is the association not the actual group - assoc.delete() - assoc.flush() - for role in roles: - assoc = DefaultHistoryRoleAssociation( self, role ) - assoc.flush() - if dataset: - for data in self.datasets: - for hda in data.dataset.history_associations: - if self.user and hda.history not in self.user.histories: - data.dataset.set_groups( [ Group.get_public_group() ] ) - data.dataset.set_roles( [] ) - break - else: - data.dataset.set_groups( groups ) - data.dataset.set_roles( roles ) - # class Query( object ): diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index c3fe6561ffc..746ec2f622d 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -19,6 +19,7 @@ from sqlalchemy import * from galaxy.model import * from galaxy.model.custom_types import * from galaxy.util.bunch import Bunch +from galaxy.security import GalaxyRBACAgent metadata = DynamicMetaData( threadlocal=False ) context = SessionContext( create_session ) @@ -574,6 +575,8 @@ def init( file_path, url, engine_options={}, create_tables=False ): result.flush = lambda *args, **kwargs: context.current.flush( *args, **kwargs ) result.context = context result.create_tables = create_tables + #load local galaxy security policy + result.security_agent = GalaxyRBACAgent( result ) #set up default table entries here, currently only exist for access controls if result.Role.count() == 0: log.warning( "There were no access roles located, setting up default (public) access roles." ) @@ -583,10 +586,10 @@ def init( file_path, url, engine_options={}, create_tables=False ): #create public_all role public_role = result.Role( 'public' ) public_role.flush() - public_group.add_role( public_role ) + result.security_agent.associate_components( group = public_group, role = public_role ) permission = result.Permission( 'public', [ result.Dataset.access_actions.USE, result.Dataset.access_actions.VIEW, result.Group.access_actions.ADD_DATASET, result.Group.access_actions.REMOVE_DATASET ] ) permission.flush() - public_role.add_permission( permission ) + result.security_agent.associate_components( permission = permission, role = public_role ) #store public group id Group.public_id = public_group.id #we use the id instead of the object, because of alchemy sessions @@ -595,18 +598,17 @@ def init( file_path, url, engine_options={}, create_tables=False ): for history in result.History.select(): if history.user: if not history.user.default_groups: - history.user.set_default_access( history = True, dataset = True ) - history.user.add_group( public_group ) + results.security_agent.setup_new_user( history.user ) history.user.flush() else: - history.set_default_access( dataset = True ) + result.security_agent.history_set_default_access( history, dataset = True ) history.flush() #add all datasets which aren't in a history to the public group orphans = result.Dataset.get_by( history_id = None ) if orphans: for dataset in orphans: - dataset.set_groups( [ public_group ] ) - dataset.set_roles( [] ) + result.security_agent.set_dataset_groups( dataset, [ public_group ] ) + result.security_agent.set_dataset_roles( dataset, [] ) else: #retrieve from database and store public group id, assume first created group is public Group.public_id = result.Group.select( order_by = asc( result.Group.table.c.create_time ) )[0].id #we use the id instead of the object, because of alchemy sessions diff --git a/lib/galaxy/security/__init__.py b/lib/galaxy/security/__init__.py new file mode 100644 index 00000000000..fa56d3a4310 --- /dev/null +++ b/lib/galaxy/security/__init__.py @@ -0,0 +1,362 @@ +""" +Utility functions used systemwide. + +""" +import logging +from galaxy.util.bunch import Bunch + +log = logging.getLogger(__name__) + +class RBACAgent: + """Class that handles galaxy security""" + + actions = Bunch( + dataset_actions = Bunch( VIEW = 'dataset_view', #viewing/downloading + USE = 'dataset_use', #use in jobs + ADD_ROLE = 'dataset_add_role', #dataset can be added to roles + REMOVE_ROLE = 'dataset_remove_role', #dataset can be removed from roles + ADD_GROUP = 'dataset_add_group', #dataset can be added to groups + REMOVE_GROUP = 'dataset_remove_group' ), #dataset can be removed from groups + role_actions = Bunch( ADD_DATASET = 'role_add_dataset', #add role to dataset + REMOVE_DATASET = 'role_remove_dataset', #remove role from dataset + DELETE = 'role_delete', #delete a role + MODIFY = 'role_modify', #change a role's actions, + ADD_GROUP = 'role_add_group', #add role to a group + REMOVE_GROUP = 'role_remove_group' ), #remove role from a group + group_actions = Bunch( ADD_DATASET = 'group_add_dataset', #add group to dataset + REMOVE_DATASET = 'group_remove_dataset', #remove dataset from group + DELETE = 'group_delete', #delete a group + ADD_ROLE = 'group_add_role', #add role to group + REMOVE_ROLE = 'group_remove_role', #remove role from group + ADD_USER = 'group_add_user' ) #add users to group + ) + + 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_roles_for_datasets( self, datasets = [] ): + raise "Unimplemented Method" + def associate_components( self, **kwd ): + raise 'No valid method of associating provided components: %s' % kwd + def create_private_user_group( self, user ): + raise "Unimplemented Method" + def user_set_default_access( self, user, groups = None, roles = 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, roles = None, dataset = False ): + raise "Unimplemented Method" + def get_public_group( self ): + raise "Unimplemented Method" + def set_dataset_groups( self, dataset, groups ): + raise "Unimplemented Method" + def set_dataset_roles( self, dataset, roles ): + raise "Unimplemented Method" + def get_component_associations( self, **kwd ): + raise "Unimplemented Method" + def components_are_associated( self, **kwd ): + return bool( self.get_component_associations( **kwd ) ) + +class GalaxyRBACAgent( RBACAgent ): + + def __init__( self, model, actions = None ): + self.model = model + if actions: + actions = actions + + def allow_action( self, user, action, **kwd ): + if 'dataset' in kwd: + return self.allow_dataset_action( user, action, kwd['dataset'] ) + raise 'No valid method of checking action (%s) on %s for user %s.' % ( action, kwd, user ) + def allow_dataset_action( self, user, action, dataset ): + """Returns true when user has permission to perform an action""" + + while not isinstance( dataset, self.model.Dataset ): + dataset = dataset.dataset + #if dataset is in public group, we always return true for viewing and using + #this may need to change when the ability to alter groups and roles is allowed + if action in [ self.actions.dataset_actions.USE, self.actions.dataset_actions.VIEW ] and self.components_are_associated( group = self.get_public_group(), dataset = dataset ): + return True + elif user is not None: + #loop through permissions and if allowed return true: + #check roles associated directly with dataset first + for role_dataset_assoc in dataset.roles: + if self.components_are_associated( user = user, role = role_dataset_assoc.role ): + for permission in role_dataset_assoc.role.permissions: + if action in permission.permission.actions: + return True + #check roles associated with dataset through groups + for group_dataset_assoc in dataset.groups: + if self.components_are_associated( user = user, group = group_dataset_assoc.group ): + for group_role_assoc in group_dataset_assoc.group.roles: + for permission in group_role_assoc.role.permissions: + if action in permission.permission.actions: + return True + return False #no user and dataset not in public group, or user lacks permission + def guess_derived_groups_roles_for_datasets( self, datasets = [] ): + """Returns a list of output roles and groups based upon itself and provided datasets""" + access_roles = None + priority_access_role = None + access_groups = None + priority_access_group = None + for dataset in datasets: + #determine access roles and groups for output datasets + #roles and groups for output dataset is 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 + roles = [ data_role_assoc.role for data_role_assoc in dataset.roles ] + for role in roles: + if priority_access_role is None or priority_access_role.priority < role.priority: + priority_access_role = role + 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_roles is None: + access_roles = set( roles ) + access_groups = set( groups ) + else: + access_roles.intersection_update( set( roles ) ) + access_groups.intersection_update( set( groups ) ) + + #complete lists for output dataset access + if access_roles: + access_roles = list( access_roles ) + else: + access_roles = [] + if access_groups: + access_groups = list( access_groups) + else: + access_groups = [] + #if we have no roles or groups left after intersection, + #take the highest priority group or role + if not access_roles and not access_groups: + if priority_access_role and priority_access_group: + if priority_access_group.priority == priority_access_role.priority: + access_groups = [ priority_access_group ] + access_roles = [ priority_access_role ] + elif priority_access_group.priority > priority_access_role.priority: + access_groups = [ priority_access_group ] + else: + access_roles = [ priority_access_role ] + elif priority_access_role: + access_roles = [ priority_access_role ] + elif priority_access_group: + access_groups = [ priority_access_group ] + + return access_groups, access_roles + + 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 'role' in kwd: + return self.associate_role_dataset( kwd['role'], kwd['dataset'] ) + elif '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'] ) + elif 'control_role' in kwd: + return self.associate_role_control_role( kwd['control_role'], kwd['role'] ) + elif 'target_role' in kwd: + return self.associate_role_control_role( kwd['role'], kwd['target_role'] ) + elif 'permission' in kwd: + return self.associate_role_permission( kwd['role'], kwd['permission'] ) + elif 'group' in kwd: + if 'control_role' in kwd: + return self.associate_group_control_role( kwd['group'], kwd['control_role'] ) + raise 'No valid method of associating provided components: %s' % kwd + def associate_group_dataset( self, group, dataset ): + assoc = self.model.GroupDatasetAssociation( group, dataset ) + assoc.flush() + return assoc + def associate_role_dataset( self, role, dataset ): + assoc = self.model.RoleDatasetAssociation( role, dataset ) + assoc.flush() + return assoc + def associate_user_group( self, user, group ): + assoc = self.model.UserGroupAssociation( user, group ) + assoc.flush() + return assoc + 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_role_control_role( self, control_role, role ): + assoc = self.model.RoleControlRoleAssociation( control_role, role ) + assoc.flush() + return assoc + def associate_group_control_role( self, group, role ): + assoc = self.model.GroupControlRoleAssociation( group, role ) + assoc.flush() + return assoc + def associate_role_permission( self, role, permission ): + assoc = self.model.RolePermissionAssociation( role, permission ) + assoc.flush() + return assoc + + def create_private_user_group( self, user ): + #create roles for user modification of role + user_permission = self.model.Permission( "%s role modification" % user.email, list( self.model.Role.access_actions.__dict__.values() ) ) + user_permission.flush() + user_role = self.model.Role( "%s role modification" % user.email ) + user_role.flush() + self.associate_components( role = user_role, permission = user_permission ) + self.associate_components( user = user, role = user_role ) + self.associate_components( control_role = user_role, role = user_role ) + + + #create private group + group = self.model.Group( user.email, priority = 10 ) + group.flush() + #create dataset permissions + dataset_permission = self.model.Permission( "%s dataset access" % user.email, list( self.model.Dataset.access_actions.__dict__.values() ) ) + dataset_permission.flush() + #create private dataset access role + role = self.model.Role( "%s dataset access" % user.email, priority = 10 ) + self.associate_components( role = role, permission = dataset_permission ) + role.flush() + #add control role to role + self.associate_components( control_role = user_role, role = role ) + #add role to group + self.associate_components( group = group, role = user_role ) + + #create roles for user modification of group + group_permission = self.model.Permission( "%s group modification" % user.email, list( self.model.Group.access_actions.__dict__.values() ) ) + group_permission.flush() + group_role = self.model.Role( "%s group modification" % user.email ) + group_role.flush() + #add control role to role + self.associate_components( control_role = user_role, role = group_role ) + self.associate_components( permission = group_permission, role = group_role ) + #add role to group + self.associate_components( control_role = group_role, group = group ) + #associate role and user + self.associate_components( role = group_role, user = user ) + + #add user to group + self.associate_components( group = group, user = user ) + group.flush() + return group + + + + def user_set_default_access( self, user, groups = None, roles = None, history = False, dataset = False ): + if groups is None and roles is None: + groups = [ self.get_public_group(), self.create_private_user_group( user ) ] + roles = [] + if groups 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: + assoc = self.model.DefaultUserGroupAssociation( user, group ) + assoc.flush() + if roles is not None: + for assoc in user.default_roles: #this is the association not the actual group + assoc.delete() + assoc.flush() + for role in roles: + assoc = self.model.DefaultUserRoleAssociation( user, role ) + assoc.flush() + if history: + for history in user.histories: + self.history_set_default_access( history, groups = groups, roles = roles, dataset = dataset ) + + def history_set_default_access( self, history, groups = None, roles = None, dataset = False ): + if groups is None and roles is None: + if history.user: + groups = history.user.default_groups + roles = history.user.default_roles + else: + groups = [ self.get_public_group() ] + roles = [] + if groups 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: + assoc = self.model.DefaultHistoryGroupAssociation( history, group ) + assoc.flush() + if roles is not None: + for assoc in history.default_roles: #this is the association not the actual group + assoc.delete() + assoc.flush() + for role in roles: + assoc = self.model.DefaultHistoryRoleAssociation( history, role ) + 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_roles( data.dataset, [] ) + break + else: + self.set_dataset_groups( data.dataset, groups ) + self.set_dataset_roles( data.dataset, roles ) + + def get_public_group( self ): + return self.model.Group.get_public_group() + + def set_dataset_groups( self, dataset, groups ): + if isinstance( dataset, self.model.HistoryDatasetAssociation): + dataset = dataset.dataset + for assoc in dataset.groups: + assoc.delete() + assoc.flush() + for group in groups: + if not isinstance( group, self.model.Group ): + group = group.group + self.associate_components( dataset = dataset, group = group ) + def set_dataset_roles( self, dataset, roles ): + if isinstance( dataset, self.model.HistoryDatasetAssociation): + dataset = dataset.dataset + for assoc in dataset.roles: + assoc.delete() + assoc.flush() + for role in roles: + if not isinstance( role, self.model.Role ): + role = role.role + self.associate_components( dataset = dataset, role = role ) + + + def get_component_associations( self, **kwd ): + 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.get_by( group_id = kwd['group'].id, dataset_id = kwd['dataset'].id ) + elif 'role' in kwd: + return self.model.RoleDatasetAssociation.get_by( role_id = kwd['role'].id, dataset_id = kwd['dataset'].id ) + elif 'user' in kwd: + if 'group' in kwd: + return self.model.UserGroupAssociation.get_by( group_id = kwd['group'].id, user_id = kwd['user'].id ) + elif 'role' in kwd: + return self.model.UserRoleAssociation.get_by( user_id = kwd['user'].id, role_id = kwd['role'].id ) + elif 'role' in kwd: + if 'group' in kwd: + return self.model.GroupRoleAssociation.get_by( group_id = kwd['group'].id, role_id = kwd['role'].id ) + elif 'control_role' in kwd: + return self.model.RoleControlRoleAssociation.get_by( target_role_id = kwd['role'].id, role_id = kwd['control_role'].id ) + elif 'target_role' in kwd: + return self.model.RoleControlRoleAssociation.get_by( role_id = kwd['role'].id, target_role_id = kwd['target_role'].id ) + elif 'group' in kwd: + if 'control_role' in kwd: + return self.model.GroupControlRoleAssociation.get_by( group_id = kwd['group'].id, role_id = kwd['control_role'].id ) + raise 'No valid method of associating provided components: %s' % kwd + + + + + + diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index af94a3a24ce..4f646b7b6d3 100644 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -1085,8 +1085,8 @@ 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 ) - child_dataset.dataset.set_groups( outdata.dataset.groups ) - child_dataset.dataset.set_roles( outdata.dataset.roles ) + self.app.security_agent.set_dataset_groups( child_dataset.dataset, outdata.dataset.groups ) + self.app.security_agent.set_dataset_roles( child_dataset.dataset, outdata.dataset.roles ) # Move data from temp location to dataset location shutil.move( filename, child_dataset.file_name ) child_dataset.flush() @@ -1123,8 +1123,8 @@ 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 ) - primary_data.dataset.set_groups( outdata.dataset.groups ) - primary_data.dataset.set_roles( outdata.dataset.roles ) + self.app.security_agent.set_dataset_groups( primary_data.dataset, outdata.dataset.groups ) + self.app.security_agent.set_dataset_roles( primary_data.dataset, outdata.dataset.roles ) 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 25393b29b7f..e3e042fd71c 100644 --- a/lib/galaxy/tools/actions/__init__.py +++ b/lib/galaxy/tools/actions/__init__.py @@ -43,7 +43,7 @@ class DefaultToolAction( object ): assoc.flush() data = new_data break - if data and not data.allow_action( trans.user, data.access_actions.USE ): + if data and not trans.app.security_agent.allow_action( trans.user, data.access_actions.USE, dataset = data ): raise "User does not have permission to use a dataset (%s) provided for input." % data.id return data if isinstance( input, DataToolParameter ): @@ -85,7 +85,7 @@ class DefaultToolAction( object ): #determine output dataset access list existing_datasets = [ inp for inp in inp_data.values() if inp ] if existing_datasets: - output_access_groups, output_access_roles = existing_datasets[0].dataset.guess_derived_groups_roles( existing_datasets[1:] ) + output_access_groups, output_access_roles = trans.app.security_agent.guess_derived_groups_roles_for_datasets( existing_datasets ) else: #no valid inputs, we will use history defaults output_access_roles = [ role.role for role in trans.history.default_roles ] @@ -130,10 +130,10 @@ class DefaultToolAction( object ): if ext == "input": ext = input_ext data = trans.app.model.HistoryDatasetAssociation( extension=ext, create_dataset=True ) - data.dataset.set_groups( output_access_groups ) - data.dataset.set_roles( output_access_roles ) # 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_roles( data.dataset, output_access_roles ) # Create an empty file immediately open( data.file_name, "w" ).close() # This may not be neccesary with the new parent/child associations @@ -197,7 +197,7 @@ class DefaultToolAction( object ): job.add_parameter( name, value ) for name, dataset in inp_data.iteritems(): if dataset: - if not dataset.allow_action( trans.user, dataset.access_actions.USE ): + if not trans.app.security_agent.allow_action( trans.user, dataset.access_actions.USE, dataset = dataset ): raise "User does not have permission to use a dataset (%s) provided for input." % data.id job.add_input_dataset( name, dataset ) else: diff --git a/lib/galaxy/tools/actions/upload.py b/lib/galaxy/tools/actions/upload.py index 6d7675a9456..195fb3ef884 100644 --- a/lib/galaxy/tools/actions/upload.py +++ b/lib/galaxy/tools/actions/upload.py @@ -66,9 +66,9 @@ class UploadToolAction( object ): def upload_empty(self, trans, err_code, err_msg): data = trans.app.model.HistoryDatasetAssociation( create_dataset = True ) - data.dataset.set_groups( trans.history.default_groups ) - data.dataset.set_roles( trans.history.default_roles ) - data.name = err_code + trans.app.security_agent.set_dataset_groups( data.dataset, trans.history.default_groups ) + trans.app.security_agent.set_dataset_roles( data.dataset, trans.history.default_roles ) + data.name = err_code data.extension = "txt" data.dbkey = "?" data.info = err_msg @@ -161,8 +161,8 @@ class UploadToolAction( object ): info = 'uploaded %s file' %data_type data = trans.app.model.HistoryDatasetAssociation( history = trans.history, extension = ext, create_dataset = True ) - data.dataset.set_groups( trans.history.default_groups ) - data.dataset.set_roles( trans.history.default_roles ) + trans.app.security_agent.set_dataset_groups( data.dataset, trans.history.default_groups ) + trans.app.security_agent.set_dataset_roles( data.dataset, trans.history.default_roles ) 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 379529b8cab..5c598e65406 100644 --- a/lib/galaxy/tools/parameters/basic.py +++ b/lib/galaxy/tools/parameters/basic.py @@ -981,27 +981,30 @@ class DataToolParameter( ToolParameter ): >>> # Mock up a history (not connected to database) >>> from galaxy.model import History, HistoryDatasetAssociation, User, Role, Permission, Group, GroupRoleAssociation >>> from galaxy.util.bunch import Bunch + >>> from galaxy.security import GalaxyRBACAgent + >>> import galaxy.model + >>> security_agent = GalaxyRBACAgent( galaxy.model ) >>> hist = History() >>> hist.flush() >>> permission = Permission( 'test', list( Permission.dataset_actions.__dict__.values() ) ) >>> permission.flush() >>> role = Role( 'test' ) >>> role.flush() - >>> assoc = role.add_permission( permission ) + >>> assoc = security_agent.associate_components( role = role, permission = permission ) >>> group = Group( 'test' ) >>> group.flush() >>> Group.public_id = group.id - >>> GroupRoleAssociation( group, role ).flush() + >>> assoc = security_agent.associate_components( group = group, role = role ) >>> dataset1 = HistoryDatasetAssociation( id=1, extension='txt', create_dataset=True ) - >>> dataset1.dataset.set_groups( [ group ] ) + >>> security_agent.set_dataset_groups( dataset1, [ group ] ) >>> dataset2 = HistoryDatasetAssociation( id=2, extension='bed', create_dataset=True ) - >>> dataset2.dataset.set_groups( [ group ] ) + >>> security_agent.set_dataset_groups( dataset2, [ group ] ) >>> dataset3 = HistoryDatasetAssociation( id=3, extension='fasta', create_dataset=True ) - >>> dataset3.dataset.set_groups( [ group ] ) + >>> security_agent.set_dataset_groups( dataset3, [ group ] ) >>> dataset4 = HistoryDatasetAssociation( id=4, extension='png', create_dataset=True ) - >>> dataset4.dataset.set_groups( [ group ] ) + >>> security_agent.set_dataset_groups( dataset4, [ group ] ) >>> dataset5 = HistoryDatasetAssociation( id=5, extension='interval', create_dataset=True ) - >>> dataset5.dataset.set_groups( [ group ] ) + >>> security_agent.set_dataset_groups( dataset5, [ group ] ) >>> hist.add_dataset( dataset1 ) >>> hist.add_dataset( dataset2 ) >>> hist.add_dataset( dataset3 ) @@ -1010,7 +1013,7 @@ class DataToolParameter( ToolParameter ): >>> p = DataToolParameter( None, XML( '' ) ) >>> print p.name blah - >>> print p.get_html( trans=Bunch( history=hist, user=None ) ) + >>> print p.get_html( trans=Bunch( history=hist, user=None, app=Bunch( security_agent = security_agent ) ) ) diff --git a/templates/history/options.mako b/templates/history/options.mako index 1bb2794a32b..4bebc932dca 100644 --- a/templates/history/options.mako +++ b/templates/history/options.mako @@ -18,7 +18,7 @@ %endif %if app.config.enable_beta_features:
  • Construct workflow from the current history
  • -
  • Change default permissions for the current history
  • +
  • Change default permitted actions for the current history
  • %endif
  • Share current history %endif diff --git a/templates/history/permissions.mako b/templates/history/permissions.mako index 315609189cd..6607f002c41 100644 --- a/templates/history/permissions.mako +++ b/templates/history/permissions.mako @@ -1,11 +1,11 @@ <%inherit file="/base.mako"/> -<%def name="title()">Change Default History Permissions +<%def name="title()">Change Default History Permitted Actions %if trans.user:
    -
    Change Default History Permissions
    +
    Change Default History Permitted Actions
    -
    +
    <% user_groups = [ assoc.group for assoc in trans.user.groups ] %> <% cur_groups = [ assoc.group for assoc in trans.get_history().default_groups ] %> @@ -29,12 +29,12 @@
    - This will change the default permissions assigned to new datasets for your current history. + This will change the default permitted actions assigned to new datasets for your current history.
    - +
    diff --git a/templates/root/history_common.mako b/templates/root/history_common.mako index d59e8cdcf17..607745ab20b 100644 --- a/templates/root/history_common.mako +++ b/templates/root/history_common.mako @@ -32,7 +32,7 @@ ## Body for history items, extra info and actions, data "peek"
    - %if not trans.app.security_agent.allow_action( trans.user, data.access_actions.VIEW, dataset = data.dataset ): + %if not trans.app.security_agent.allow_action( trans.user, data.permitted_actions.VIEW, dataset = data.dataset ):
    You do not have permision to view this dataset.
    %elif data_state == "queued":
    Job is waiting to run
    diff --git a/templates/user/index.mako b/templates/user/index.mako index 64d23dd2410..1047466aad0 100644 --- a/templates/user/index.mako +++ b/templates/user/index.mako @@ -9,7 +9,7 @@
  • Change your password
  • Update your email address
  • %if app.config.enable_beta_features: -
  • Change default permissions for new histories
  • +
  • Change default permitted actions for new histories
  • %endif
  • Logout
  • diff --git a/templates/user/permissions.mako b/templates/user/permissions.mako index 1b0803d44df..7b6b90f976d 100644 --- a/templates/user/permissions.mako +++ b/templates/user/permissions.mako @@ -1,11 +1,11 @@ <%inherit file="/base.mako"/> -<%def name="title()">Change Default History Permissions +<%def name="title()">Change Default History Permitted Actions %if trans.user:
    -
    Change Default Permissions for new Histories
    +
    Change Default Permitted Actions for new Histories
    -
    +
    <% user_groups = [ assoc.group for assoc in trans.user.groups ] %> <% cur_groups = [ assoc.group for assoc in trans.user.default_groups ] %> @@ -29,12 +29,12 @@
    - This will change the default permissions assigned to new datasets for new histories. + This will change the default permitted actions assigned to new datasets for new histories.
    - +
    diff --git a/tools/data_source/encode_import_code.py b/tools/data_source/encode_import_code.py index 6a706b5ab04..ddfbb0241e0 100644 --- a/tools/data_source/encode_import_code.py +++ b/tools/data_source/encode_import_code.py @@ -38,8 +38,8 @@ 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_roles( newdata.dataset, base_dataset.dataset.roles ) 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 ad6a877e353..e8816f093ff 100644 --- a/tools/data_source/microbial_import_code.py +++ b/tools/data_source/microbial_import_code.py @@ -129,8 +129,8 @@ 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_roles( newdata.dataset, base_dataset.dataset.roles ) 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 d3784661188..428486b3e37 100644 --- a/tools/maf/maf_to_bed_code.py +++ b/tools/maf/maf_to_bed_code.py @@ -32,8 +32,8 @@ 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_roles( newdata.dataset, output_data.dataset.roles ) newdata.flush() history.flush() app.model.flush() From dc9ffe68605089a3fe1e0684859c7d689a20036c Mon Sep 17 00:00:00 2001 From: Nate Coraor Date: Mon, 11 Aug 2008 16:58:48 -0400 Subject: [PATCH 10/10] Actions have been reduced to 3, and the public group is now the one named 'public' (names, for now, will be unique). --- lib/galaxy/model/__init__.py | 2 +- lib/galaxy/security/__init__.py | 14 +++----------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 58a4f596104..cadd7a78ef0 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -144,7 +144,7 @@ class Group( object ): def guess_public_group( cls ): # TODO, Nate: Make sure this method is functionally correct. #retrieve from database and store public group id, assume first created group is public - cls.set_public_group( Group.select( order_by = Group.table.c.create_time )[0] ) + cls.set_public_group( Group.select_by( name = 'public' ) ) class UserGroupAssociation( object ): def __init__( self, user, group ): diff --git a/lib/galaxy/security/__init__.py b/lib/galaxy/security/__init__.py index ebccb02e005..6582da8c368 100644 --- a/lib/galaxy/security/__init__.py +++ b/lib/galaxy/security/__init__.py @@ -14,17 +14,9 @@ log = logging.getLogger(__name__) class RBACAgent: """Class that handles galaxy security""" permitted_actions = Bunch( - dataset_actions = Bunch( VIEW = 'dataset_view', #viewing/downloading - USE = 'dataset_use', #use in jobs - ADD_GROUP = 'dataset_add_group', #dataset can be added to groups - REMOVE_GROUP = 'dataset_remove_group' #dataset can be removed from groups - ), - group_actions = Bunch( ADD_DATASET = 'group_add_dataset', #add dataset to group - REMOVE_DATASET = 'group_remove_dataset', #remove dataset from group - DELETE = 'group_delete', #delete a group - ADD_USER = 'group_add_user', #add users to group - REMOVE_USER = 'group_remove_user' #remove user from group - ) + EDIT_METADATA = 'edit_metadata', + MANAGE_PERMISSIONS = 'manage_permissions', + ACCESS = 'access' ) def allow_action( self, user, action, **kwd ): raise 'No valid method of checking action (%s) on %s for user %s.' % ( action, kwd, user )