From fddd3b572fbfdfb3d4d4b6c9f159f7687364a730 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Fri, 1 Aug 2008 15:44:10 -0400 Subject: [PATCH 01/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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/21] 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 ) From df6ab911b103db183ba3d270e83a58f3b1d504d8 Mon Sep 17 00:00:00 2001 From: Nate Coraor Date: Tue, 12 Aug 2008 14:19:45 -0400 Subject: [PATCH 11/21] Fix references to actions removed in a previous commit. --- lib/galaxy/model/__init__.py | 6 ++---- lib/galaxy/security/__init__.py | 19 +++++++++++++++---- lib/galaxy/tools/actions/__init__.py | 4 ++-- lib/galaxy/tools/parameters/basic.py | 4 ++-- lib/galaxy/web/controllers/dataset.py | 4 ++-- lib/galaxy/web/controllers/root.py | 11 +++++++---- templates/dataset/edit_attributes.mako | 2 ++ templates/root/history_common.mako | 4 ++-- 8 files changed, 34 insertions(+), 20 deletions(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index cadd7a78ef0..c47235036a0 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -104,8 +104,6 @@ class JobToOutputDatasetAssociation( object ): self.dataset = dataset class GroupDatasetAssociation( object ): - dataset_actions = RBACAgent.permitted_actions.dataset_actions - group_actions = RBACAgent.permitted_actions.group_actions def __init__( self, group, dataset, permitted_actions=[] ): if isinstance( group, GroupDatasetAssociation ) or \ isinstance( group, DefaultUserGroupAssociation ) or \ @@ -125,7 +123,7 @@ class GroupDatasetAssociation( object ): class Group( object ): public_id = None - permitted_actions = GroupDatasetAssociation.group_actions + permitted_actions = galaxy.security.get_permitted_actions( 'GROUP' ) def __init__( self, name = None, priority = 0 ): self.name = name self.priority = priority @@ -179,7 +177,7 @@ class Dataset( object ): EMPTY = 'empty', ERROR = 'error', DISCARDED = 'discarded' ) - permitted_actions = GroupDatasetAssociation.dataset_actions + permitted_actions = galaxy.security.get_permitted_actions( 'DATASET' ) file_path = "/tmp/" engine = None def __init__( self, id=None, state=None, external_filename=None, extra_files_path=None, file_size=None, purgable=True ): diff --git a/lib/galaxy/security/__init__.py b/lib/galaxy/security/__init__.py index 6582da8c368..94315714c4f 100644 --- a/lib/galaxy/security/__init__.py +++ b/lib/galaxy/security/__init__.py @@ -14,9 +14,9 @@ log = logging.getLogger(__name__) class RBACAgent: """Class that handles galaxy security""" permitted_actions = Bunch( - EDIT_METADATA = 'edit_metadata', - MANAGE_PERMISSIONS = 'manage_permissions', - ACCESS = 'access' + DATASET_EDIT_METADATA = 'dataset_edit_metadata', + DATASET_MANAGE_PERMISSIONS = 'dataset_manage_permissions', + DATASET_ACCESS = 'dataset_access' ) def allow_action( self, user, action, **kwd ): raise 'No valid method of checking action (%s) on %s for user %s.' % ( action, kwd, user ) @@ -68,7 +68,7 @@ class GalaxyRBACAgent( RBACAgent ): 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 permitted_actions is allowed - if action in [ self.permitted_actions.dataset_actions.USE, self.permitted_actions.dataset_actions.VIEW ] and \ + if action == self.permitted_actions.DATASET_ACCESS and \ self.components_are_associated( group = self.get_public_group(), dataset = dataset ): return True elif user is not None: @@ -229,3 +229,14 @@ class GalaxyRBACAgent( RBACAgent ): if 'group' in kwd: return self.model.UserGroupAssociation.get_by( group_id = kwd['group'].id, user_id = kwd['user'].id ) raise 'No valid method of associating provided components: %s' % kwd + +def get_permitted_actions( self, filter=None ): + '''Utility method to return a subset of RBACAgent's permitted actions''' + if filter is None: + return RBACAgent.permitted_actions + if not filter.endswith('_'): + filter += '_' + tmp_bunch = Bunch() + [tmp_bunch.__dict__.__setitem__(k, v) for k, v in \ + RBACAgent.permitted_actions.items() if k.startswith(filter)] + return tmp_bunch diff --git a/lib/galaxy/tools/actions/__init__.py b/lib/galaxy/tools/actions/__init__.py index deb54fe6a0a..bd74d188ee6 100644 --- a/lib/galaxy/tools/actions/__init__.py +++ b/lib/galaxy/tools/actions/__init__.py @@ -44,7 +44,7 @@ class DefaultToolAction( object ): data = new_data break # TODO, Nate: Make sure the permitted actions here are appropriate. - if data and not trans.app.security_agent.allow_action( trans.user, data.permitted_actions.USE, dataset=data ): + if data and not trans.app.security_agent.allow_action( trans.user, data.permitted_actions.DATASET_ACCESS, dataset=data ): raise "User does not have permission to use a dataset (%s) provided for input." % data.id return data if isinstance( input, DataToolParameter ): @@ -197,7 +197,7 @@ class DefaultToolAction( object ): for name, dataset in inp_data.iteritems(): if dataset: # TODO, Nate: Make sure the permitted actions here are appropriate. - if not trans.app.security_agent.allow_action( trans.user, dataset.permitted_actions.USE, dataset=dataset ): + if not trans.app.security_agent.allow_action( trans.user, dataset.permitted_actions.DATASET_ACCESS, 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/parameters/basic.py b/lib/galaxy/tools/parameters/basic.py index d889d9dd79d..b626e665472 100644 --- a/lib/galaxy/tools/parameters/basic.py +++ b/lib/galaxy/tools/parameters/basic.py @@ -1065,7 +1065,7 @@ class DataToolParameter( ToolParameter ): hid = "%s.%d" % ( parent_hid, i + 1 ) else: hid = str( data.hid ) - if not data.deleted and data.state not in [data.states.ERROR] and data.visible and trans.app.security_agent.allow_action( trans.user, data.permitted_actions.USE, dataset = data ): + if not data.deleted and data.state not in [data.states.ERROR] and data.visible and trans.app.security_agent.allow_action( trans.user, data.permitted_actions.DATASET_ACCESS, dataset = data ): if self.options and data.get_dbkey() != filter_value: continue if isinstance( data.datatype, self.formats): @@ -1079,7 +1079,7 @@ class DataToolParameter( ToolParameter ): data = datasets[0] elif not self.converter_safe( other_values, trans ): continue - if not trans.app.security_agent.allow_action( trans.user, data.permitted_actions.USE, dataset = data ): + if not trans.app.security_agent.allow_action( trans.user, data.permitted_actions.DATASET_ACCESS, dataset = data ): continue selected = ( value and ( data in value ) ) field.add_option( "%s: (as %s) %s" % ( hid, target_ext, data.name[:30] ), data.id, selected ) diff --git a/lib/galaxy/web/controllers/dataset.py b/lib/galaxy/web/controllers/dataset.py index 0f2f52dcc01..d38537509cd 100644 --- a/lib/galaxy/web/controllers/dataset.py +++ b/lib/galaxy/web/controllers/dataset.py @@ -107,7 +107,7 @@ class DatasetInterface( BaseController ): if not data: raise paste.httpexceptions.HTTPRequestRangeNotSatisfiable( "Invalid reference dataset." ) # TODO, Nate: Make sure the following is functionally correct. - if trans.app.security_agent.allow_action( trans.user, data.permitted_actions.VIEW, dataset = data ): + if trans.app.security_agent.allow_action( trans.user, data.permitted_actions.DATASET_ACCESS, dataset = data ): if filename is None or filename.lower() == "index": mime = trans.app.datatypes_registry.get_mimetype_by_extension( data.extension.lower() ) trans.response.set_content_type(mime) @@ -127,4 +127,4 @@ class DatasetInterface( BaseController ): except: raise paste.httpexceptions.HTTPNotFound( "File Not Found (%s)." % ( filename ) ) else: - raise paste.httpexceptions.HTTPForbidden( "You are not privileged to access this dataset." ) \ No newline at end of file + raise paste.httpexceptions.HTTPForbidden( "You are not privileged to access this dataset." ) diff --git a/lib/galaxy/web/controllers/root.py b/lib/galaxy/web/controllers/root.py index e6328ba3c8a..8841c46eedd 100644 --- a/lib/galaxy/web/controllers/root.py +++ b/lib/galaxy/web/controllers/root.py @@ -139,7 +139,7 @@ class RootController( BaseController ): except: return "Dataset id '%s' is invalid" %str( id ) if data: - if trans.app.security_agent.allow_action( trans.user, data.permitted_actions.VIEW, dataset = data ): + if trans.app.security_agent.allow_action( trans.user, data.permitted_actions.DATASET_ACCESS, dataset = data ): mime = trans.app.datatypes_registry.get_mimetype_by_extension( data.extension.lower() ) trans.response.set_content_type(mime) if tofile: @@ -171,7 +171,7 @@ class RootController( BaseController ): if data: child = data.get_child_by_designation( designation ) if child: - if trans.app.security_agent.allow_action( trans.user, child.permitted_actions.VIEW, dataset = child ): + if trans.app.security_agent.allow_action( trans.user, child.permitted_actions.DATASET_ACCESS, dataset = child ): return self.display( trans, id=child.id, tofile=tofile, toext=toext ) else: return "You are not privileged to access this dataset." @@ -184,7 +184,7 @@ class RootController( BaseController ): """Returns a file in a format that can successfully be displayed in display_app""" data = self.app.model.HistoryDatasetAssociation.get( id ) if data: - if trans.app.security_agent.allow_action( trans.user, data.permitted_actions.VIEW, dataset = data ): + if trans.app.security_agent.allow_action( trans.user, data.permitted_actions.DATASET_ACCESS, dataset = data ): trans.response.set_content_type( data.get_mime() ) trans.log_event( "Formatted dataset id %s for display at %s" % ( str( id ), display_app ) ) return data.as_display_type( display_app, **kwd ) @@ -219,7 +219,7 @@ class RootController( BaseController ): return trans.show_error_message( "Problem retrieving dataset id %s with history id %s." % ( str( id ), str( hid ) ) ) if data.history.user is not None and data.history.user != trans.user: return trans.show_error_message( "This instance of a dataset (%s) in a history does not belong to you." % ( data.id ) ) - if trans.app.security_agent.allow_action( trans.user, data.permitted_actions.USE, dataset = data ): + if trans.app.security_agent.allow_action( trans.user, data.permitted_actions.DATASET_ACCESS, dataset = data ): p = util.Params(kwd, safe=False) if p.change: @@ -262,6 +262,8 @@ class RootController( BaseController ): if target_type: msg = data.datatype.convert_dataset(trans, data, target_type) return trans.show_ok_message( msg, refresh_frames=['history'] ) + ''' + # Users can't currently change permissions or groups elif p.change_permision: """The user clicked the change_permision button on the 'Change permissions' form""" if not trans.user: @@ -283,6 +285,7 @@ class RootController( BaseController ): else: return trans.show_error_message( "You have not specified a valid change of permitted actions." ) return trans.show_ok_message( 'Permitted actions have been changed.', refresh_frames=['history'] ) + ''' data.datatype.before_edit( data ) diff --git a/templates/dataset/edit_attributes.mako b/templates/dataset/edit_attributes.mako index 314b7960e78..1479b15c20a 100644 --- a/templates/dataset/edit_attributes.mako +++ b/templates/dataset/edit_attributes.mako @@ -133,6 +133,7 @@

    +<%doc> %if trans.app.config.enable_beta_features and trans.user and ( trans.app.security_agent.allow_action( trans.user, data.permitted_actions.REMOVE_GROUP, dataset = data ) or trans.app.security_agent.allow_action( trans.user, data.permitted_actions.ADD_GROUP, dataset = data ) ):

    Change Permitted Actions
    @@ -163,3 +164,4 @@
    %endif + diff --git a/templates/root/history_common.mako b/templates/root/history_common.mako index 607745ab20b..aa9a5c238a5 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.permitted_actions.VIEW, dataset = data.dataset ): + %if not trans.app.security_agent.allow_action( trans.user, data.permitted_actions.DATASET_ACCESS, dataset = data.dataset ):
    You do not have permision to view this dataset.
    %elif data_state == "queued":
    Job is waiting to run
    @@ -102,4 +102,4 @@
    - \ No newline at end of file + From d4474cd22fb0184c83f1d3e6180f3eed4fd68f6e Mon Sep 17 00:00:00 2001 From: Nate Coraor Date: Tue, 12 Aug 2008 14:53:22 -0400 Subject: [PATCH 12/21] Change back the way we guess the public group (as per Greg) and add a deleted column to Group so groups can be undeleted. ALTER TABLE galaxy_group ADD COLUMN deleted BOOLEAN; --- lib/galaxy/model/__init__.py | 2 +- lib/galaxy/model/mapping.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index c47235036a0..7dda26375bb 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -142,7 +142,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_by( name = 'public' ) ) + cls.set_public_group( Group.select( order_by = Group.table.c.create_time )[0] ) class UserGroupAssociation( object ): def __init__( self, user, group ): diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index c99899d4e5a..8c11ec41a2c 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -120,7 +120,8 @@ Group.table = Table( "galaxy_group", metadata, Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), Column( "name", TEXT ), - Column( "priority", Integer ) ) + Column( "priority", Integer ), + Column( "deleted", Boolean, index=True, default=False ) ) UserGroupAssociation.table = Table( "user_group_association", metadata, Column( "id", Integer, primary_key=True ), From da0b09039cdb013e0ad15c325bc500abf003f7e3 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Tue, 12 Aug 2008 16:56:26 -0400 Subject: [PATCH 13/21] Initial check in of library functionality. The ui is Very rough, at best. Libraries can be accessed by users through a new tool under datasources. Libraries can be created and accessed for modification via a link on the admin page. Currently, all library datasets are currently associated with the public group. Tags have been defined (tables and mappings) and can be associated with a dataset or a folder, but there is currenty no interface for displaying or creating these. Creation and editing of a library should be an admin only function, however this check is not currently present. The creation/editing page for a dataset is currently hard coded, it should be dynamic (i.e. datatypes and dbkeys) Brief description of a library: A library is composed of a name, description and a root folder. A folder is composed of a name, description, and a parent folder (none when is the root), where a folder can have many or none child folders and datasets. A library dataset is composed of most of the same attributes as a history dataset, but it is linked via a folder_id to a folder (instead of a history_id to a history). Ordering of the library is maintained by the 'order_id' attribute of each folder and dataset object (which is populated by the item_count attribute of a folder) --- lib/galaxy/model/__init__.py | 123 ++- lib/galaxy/model/mapping.py | 118 ++- lib/galaxy/security/__init__.py | 2 +- lib/galaxy/web/controllers/library.py | 218 +++++ lib/galaxy/web/framework/__init__.py | 7 +- templates/admin_main.mako | 6 + templates/form.mako | 2 + templates/library/admin_list_libraries.mako | 16 + templates/library/manage_dataset.mako | 100 ++ templates/library/manage_folder.mako | 191 ++++ templates/library/manage_library.mako | 46 + templates/library/new_dataset.mako | 997 ++++++++++++++++++++ templates/library/user_list_libraries.mako | 14 + templates/library/user_view_library.mako | 58 ++ tool_conf.xml.sample | 1 + tools/data_source/access_libraries.xml | 11 + 16 files changed, 1883 insertions(+), 27 deletions(-) create mode 100644 lib/galaxy/web/controllers/library.py create mode 100644 templates/library/admin_list_libraries.mako create mode 100644 templates/library/manage_dataset.mako create mode 100644 templates/library/manage_folder.mako create mode 100644 templates/library/manage_library.mako create mode 100644 templates/library/new_dataset.mako create mode 100644 templates/library/user_list_libraries.mako create mode 100644 templates/library/user_view_library.mako create mode 100644 tools/data_source/access_libraries.xml diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 7dda26375bb..e6637b2ad7f 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -262,16 +262,16 @@ class Dataset( object ): except OSError, e: log.critical('%s delete error %s' % (self.__class__.__name__, e)) -class HistoryDatasetAssociation( object ): + +class DatasetInstance( object ): + """A base class for all 'dataset instances', HDAs, LDAs, etc""" states = Dataset.states permitted_actions = Dataset.permitted_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, validation_errors=None, visible=True, create_dataset = False ): self.name = name or "Unnamed dataset" self.id = id - self.hid = hid self.info = info self.blurb = blurb self.peek = peek @@ -282,14 +282,12 @@ class HistoryDatasetAssociation( object ): self.deleted = deleted self.visible = visible # Relationships - self.history = history if not dataset and create_dataset: dataset = Dataset() dataset.flush() self.dataset = dataset self.parent_id = parent_id self.validation_errors = validation_errors - self.copied_from_history_dataset_association = copied_from_history_dataset_association @property def ext( self ): @@ -399,10 +397,7 @@ class HistoryDatasetAssociation( object ): valid.append( assoc.dataset ) return valid def clear_associated_files( self, metadata_safe = False, purge = False ): - #metadata_safe = True means to only clear when assoc.metadata_safe == False - for assoc in self.implicitly_converted_datasets: - if not metadata_safe or not assoc.metadata_safe: - assoc.clear( purge = purge ) + raise 'Unimplemented' def get_child_by_designation(self, designation): for child in self.children: if child.designation == designation: @@ -411,17 +406,6 @@ 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, 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: - for child in self.children: - child_copy = child.copy( copy_children = copy_children, parent_id = des.id ) - des.set_peek() #in some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs - des.flush() - return des def add_validation_error( self, validation_error ): self.validation_errors.append( validation_error ) @@ -436,6 +420,35 @@ class HistoryDatasetAssociation( object ): child.mark_deleted() + +class HistoryDatasetAssociation( DatasetInstance ): + def __init__( self, hid = None, history = None, copied_from_history_dataset_association = None, copied_from_library_folder_dataset_association = None, **kwd ): + DatasetInstance.__init__( self, **kwd ) + self.hid = hid + # Relationships + self.history = history + self.copied_from_history_dataset_association = copied_from_history_dataset_association + self.copied_from_library_folder_dataset_association = copied_from_library_folder_dataset_association + + def copy( self, copy_children = False, parent_id = None ): + print "self.dataset", self.dataset + + 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 ) + print "des data", des.dataset + des.flush() + if copy_children: + for child in self.children: + child_copy = child.copy( copy_children = copy_children, parent_id = des.id ) + des.set_peek() #in some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs + des.flush() + return des + + def clear_associated_files( self, metadata_safe = False, purge = False ): + #metadata_safe = True means to only clear when assoc.metadata_safe == False + for assoc in self.implicitly_converted_datasets: + if not metadata_safe or not assoc.metadata_safe: + assoc.clear( purge = purge ) + class History( object ): def __init__( self, id=None, name=None, user=None ): self.id = id @@ -499,9 +512,75 @@ class History( object ): des.hid_counter = self.hid_counter des.flush() return des - +class Library( object ): + def __init__( self, name = None, description = None, root_folder = None ): + self.name = name or "Unnamed library" + self.description = description + self.root_folder = root_folder + +class LibraryFolder( object ): + def __init__( self, name = None, description = None, order_id = None ): + self.name = name or "Unnamed folder" + self.description = description + self.item_count = item_count + self.order_id = order_id + def add_dataset( self, dataset ): + dataset.folder_id = self.id + dataset.order_id = self.item_count + self.item_count += 1 + def add_folder( self, folder ): + folder.parent_id = self.id + folder.order_id = self.item_count + self.item_count += 1 + +class LibraryFolderDatasetAssociation( DatasetInstance ): + def __init__( self, folder = None, order_id = None, copied_from_history_dataset_association = None, copied_from_library_folder_dataset_association = None, **kwd ): + DatasetInstance.__init__( self, **kwd ) + self.folder = folder + self.order_id = order_id + self.copied_from_history_dataset_association = copied_from_history_dataset_association + self.copied_from_library_folder_dataset_association = copied_from_library_folder_dataset_association + + def to_history_dataset_association( self, parent_id = None ): + des = HistoryDatasetAssociation( 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_library_folder_dataset_association = self ) + des.flush() + for child in self.children: + child_copy = child.to_history_dataset_association( parent_id = des.id ) + des.set_peek() #in some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs + des.flush() + return des + + + def copy( self, copy_children = False, parent_id = None ): + des = LibraryFolderDatasetAssociation( 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_library_folder_dataset_association = self ) + des.flush() + if copy_children: + for child in self.children: + child_copy = child.copy( copy_children = copy_children, parent_id = des.id ) + des.set_peek() #in some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs + des.flush() + return des + + def clear_associated_files( self, metadata_safe = False, purge = False ): + return + + +class LibraryTag( object ): + def __init__( self, tag ): + self.tag = tag + +class LibraryTagFolderAssociation( object ): + def __init__( self, tag, folder ): + self.tag = tag + self.folder = folder + +class LibraryTagDatasetAssociation( object ): + def __init__( self, tag, dataset ): + self.tag = tag + self.dataset = dataset + # class Query( object ): # def __init__( self, name=None, state=None, tool_parameters=None, history=None ): # self.name = name or "Unnamed query" diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index 8c11ec41a2c..6a8f982fc81 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -74,6 +74,7 @@ HistoryDatasetAssociation.table = Table( "history_dataset_association", metadata Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), Column( "copied_from_history_dataset_association_id", Integer, ForeignKey( "history_dataset_association.id" ), nullable=True ), + Column( "copied_from_library_folder_dataset_association_id", Integer, ForeignKey( "library_folder_dataset_association.id" ), nullable=True ), Column( "hid", Integer ), Column( "name", TrimmedString( 255 ) ), Column( "info", TrimmedString( 255 ) ), @@ -157,6 +158,66 @@ DefaultHistoryGroupAssociation.table = Table( "default_history_group_association Column( "update_time", DateTime, default=now, onupdate=now ), Column( "permitted_actions", JSONType(), default=[] ) ) +LibraryFolderDatasetAssociation.table = Table( "library_folder_dataset_association", metadata, + Column( "id", Integer, primary_key=True ), + Column( "dataset_id", Integer, ForeignKey( "dataset.id" ), index=True ), + Column( "folder_id", Integer, ForeignKey( "library_folder.id" ), index=True ), + Column( "order_id", Integer ), + Column( "create_time", DateTime, default=now ), + Column( "update_time", DateTime, default=now, onupdate=now ), + Column( "copied_from_history_dataset_association_id", Integer, ForeignKey( "history_dataset_association.id", use_alter=True, name='history_dataset_association_dataset_id_fkey' ), nullable=True ), + Column( "copied_from_library_folder_dataset_association_id", Integer, ForeignKey( "library_folder_dataset_association.id", use_alter=True, name='library_folder_dataset_association_id_fkey' ), nullable=True ), + Column( "name", TrimmedString( 255 ) ), + Column( "info", TrimmedString( 255 ) ), + Column( "blurb", TrimmedString( 255 ) ), + Column( "peek" , TEXT ), + Column( "extension", TrimmedString( 64 ) ), + Column( "metadata", MetadataType(), key="_metadata" ), + Column( "parent_id", Integer, ForeignKey( "library_folder_dataset_association.id" ), nullable=True ), + Column( "designation", TrimmedString( 255 ) ), + Column( "deleted", Boolean, index=True, default=False ), + Column( "visible", Boolean ) ) + +Library.table = Table( "library", metadata, + Column( "id", Integer, primary_key=True ), + Column( "root_folder_id", Integer, ForeignKey( "library_folder.id" ), index=True ), + Column( "create_time", DateTime, default=now ), + Column( "update_time", DateTime, default=now, onupdate=now ), + Column( "name", TEXT ), + Column( "description", TEXT ) ) + + +LibraryFolder.table = Table( "library_folder", metadata, + Column( "id", Integer, primary_key=True ), + Column( "parent_id", Integer, ForeignKey( "library_folder.id" ), nullable = True, index=True ), + Column( "create_time", DateTime, default=now ), + Column( "update_time", DateTime, default=now, onupdate=now ), + Column( "name", TEXT ), + Column( "description", TEXT ), + Column( "order_id", Integer ), + Column( "item_count", Integer ) ) + +LibraryTag.table = Table( "library_tag", metadata, + Column( "id", Integer, primary_key=True ), + Column( "create_time", DateTime, default=now ), + Column( "update_time", DateTime, default=now, onupdate=now ), + Column( "text", TEXT ) ) + +LibraryTagFolderAssociation.table = Table( "library_tag_folder_association", metadata, + Column( "id", Integer, primary_key=True ), + Column( "folder_id", Integer, ForeignKey( "library_folder.id" ), index=True ), + Column( "tag_id", Integer, ForeignKey( "library_tag.id" ), index=True ), + Column( "create_time", DateTime, default=now ), + Column( "update_time", DateTime, default=now, onupdate=now ) ) + +LibraryTagDatasetAssociation.table = Table( "library_tag_dataset_association", metadata, + Column( "id", Integer, primary_key=True ), + Column( "dataset_id", Integer, ForeignKey( "library_folder_dataset_association.id" ), index=True ), + Column( "tag_id", Integer, ForeignKey( "library_tag.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 +359,10 @@ assign_mapper( context, HistoryDatasetAssociation, HistoryDatasetAssociation.tab HistoryDatasetAssociation, primaryjoin=( HistoryDatasetAssociation.table.c.copied_from_history_dataset_association_id == HistoryDatasetAssociation.table.c.id ), backref=backref( "copied_from_history_dataset_association", primaryjoin=( HistoryDatasetAssociation.table.c.copied_from_history_dataset_association_id == HistoryDatasetAssociation.table.c.id ), remote_side=[HistoryDatasetAssociation.table.c.id] ) ), + copied_to_library_folder_dataset_associations=relation( + LibraryFolderDatasetAssociation, + primaryjoin=( HistoryDatasetAssociation.table.c.copied_from_library_folder_dataset_association_id == LibraryFolderDatasetAssociation.table.c.id ), + backref=backref( "copied_from_history_dataset_association", primaryjoin=( HistoryDatasetAssociation.table.c.copied_from_library_folder_dataset_association_id == LibraryFolderDatasetAssociation.table.c.id ), remote_side=[LibraryFolderDatasetAssociation.table.c.id] ) ), implicitly_converted_datasets=relation( ImplicitlyConvertedDatasetAssociation, primaryjoin=( ImplicitlyConvertedDatasetAssociation.table.c.hda_parent_id == HistoryDatasetAssociation.table.c.id ) ), @@ -311,7 +376,10 @@ assign_mapper( context, Dataset, Dataset.table, properties=dict( history_associations=relation( HistoryDatasetAssociation, - primaryjoin=( Dataset.table.c.id == HistoryDatasetAssociation.table.c.dataset_id ) ) + primaryjoin=( Dataset.table.c.id == HistoryDatasetAssociation.table.c.dataset_id ) ), + library_associations=relation( + LibraryFolderDatasetAssociation, + primaryjoin=( Dataset.table.c.id == LibraryFolderDatasetAssociation.table.c.dataset_id ) ) ) ) @@ -365,6 +433,54 @@ assign_mapper( context, DefaultHistoryGroupAssociation, DefaultHistoryGroupAssoc properties=dict( history=relation( History, backref = "default_groups" ), group=relation( Group ) ) ) +assign_mapper( context, Library, Library.table, + properties=dict( + root_folder=relation( LibraryFolder, + backref = backref( "library_root" ) ) + ) ) + +assign_mapper( context, LibraryFolder, LibraryFolder.table, + properties=dict( + folders=relation( + LibraryFolder, + primaryjoin=( LibraryFolder.table.c.parent_id == LibraryFolder.table.c.id ), + backref=backref( "parent", primaryjoin=( LibraryFolder.table.c.parent_id == LibraryFolder.table.c.id ), remote_side=[LibraryFolder.table.c.id] ) ), + tags=relation( + LibraryTagFolderAssociation, + primaryjoin=( LibraryFolder.table.c.id == LibraryTagFolderAssociation.table.c.folder_id ), + backref=backref( "folders" ) ) + ) ) + +assign_mapper( context, LibraryFolderDatasetAssociation, LibraryFolderDatasetAssociation.table, + properties=dict( + dataset=relation( Dataset ), + folder=relation( + LibraryFolder, + backref=backref( "datasets" ) ), + copied_to_library_folder_dataset_associations=relation( + LibraryFolderDatasetAssociation, + primaryjoin=( LibraryFolderDatasetAssociation.table.c.copied_from_library_folder_dataset_association_id == LibraryFolderDatasetAssociation.table.c.id ), + backref=backref( "copied_from_library_folder_dataset_association", primaryjoin=( LibraryFolderDatasetAssociation.table.c.copied_from_library_folder_dataset_association_id == LibraryFolderDatasetAssociation.table.c.id ), remote_side=[LibraryFolderDatasetAssociation.table.c.id] ) ), + children=relation( + LibraryFolderDatasetAssociation, + primaryjoin=( LibraryFolderDatasetAssociation.table.c.parent_id == LibraryFolderDatasetAssociation.table.c.id ), + backref=backref( "parent", primaryjoin=( LibraryFolderDatasetAssociation.table.c.parent_id == LibraryFolderDatasetAssociation.table.c.id ), remote_side=[LibraryFolderDatasetAssociation.table.c.id] ) ), + tags=relation( + LibraryTagDatasetAssociation, + primaryjoin=( LibraryFolderDatasetAssociation.table.c.id == LibraryTagDatasetAssociation.table.c.dataset_id ), + backref=backref( "datasets" ) ) + ) ) + +assign_mapper( context, LibraryTag, LibraryTag.table ) + +assign_mapper( context, LibraryTagFolderAssociation, LibraryTagFolderAssociation.table, + properties=dict( tag=relation( LibraryTag ), + folder=relation( LibraryFolder ) ) ) + +assign_mapper( context, LibraryTagDatasetAssociation, LibraryTagDatasetAssociation.table, + properties=dict( tag=relation( LibraryTag ), + dataset=relation( LibraryFolderDatasetAssociation ) ) ) + assign_mapper( context, JobToInputDatasetAssociation, JobToInputDatasetAssociation.table, properties=dict( job=relation( Job ), dataset=relation( HistoryDatasetAssociation ) ) ) diff --git a/lib/galaxy/security/__init__.py b/lib/galaxy/security/__init__.py index 94315714c4f..0ba0bf6b621 100644 --- a/lib/galaxy/security/__init__.py +++ b/lib/galaxy/security/__init__.py @@ -1,5 +1,5 @@ """ -Utility functions used systemwide. +Galaxy Security """ import logging diff --git a/lib/galaxy/web/controllers/library.py b/lib/galaxy/web/controllers/library.py new file mode 100644 index 00000000000..c8b1f6f0573 --- /dev/null +++ b/lib/galaxy/web/controllers/library.py @@ -0,0 +1,218 @@ + +from galaxy.web.base.controller import * +from galaxy.datatypes import sniff +import logging, shutil, StringIO + +log = logging.getLogger( __name__ ) + +class Library( BaseController ): + + @web.expose + def index( self, trans, library_id = None, import_ids = [], **kwd ): + #use for importing an entry into your history + if import_ids: + if not isinstance( import_ids, list ): + import_ids = [import_ids] + history = trans.get_history() + for id in import_ids: + dataset = trans.app.model.LibraryFolderDatasetAssociation.get( id ).to_history_dataset_association() + history.add_dataset( dataset ) + dataset.flush() + history.flush() + return trans.show_ok_message( "%i datasets have been imported into your history" % len( import_ids ), refresh_frames=['history'] ) + elif library_id: + return trans.fill_template( 'library/user_view_library.mako', library = trans.app.model.Library.get( library_id ) ) + return trans.fill_template( 'library/user_list_libraries.mako', libraries = trans.app.model.Library.select() ) + + #make admin only + @web.expose + def manage_libraries( self, trans, **kwd ): + return trans.fill_template( 'library/admin_list_libraries.mako', libraries = trans.app.model.Library.select() ) + + #make admin only + @web.expose + def manage_library( self, trans, id=None, name="Unnamed", description=None, **kwd ): + if 'create_library' in kwd: + library = trans.app.model.Library( name = name, description = description ) + root_folder = trans.app.model.LibraryFolder( name = name, description = description ) + root_folder.flush() + library.root_folder = root_folder + library.flush() + trans.response.send_redirect( web.url_for( action='manage_folder', id = root_folder.id ) ) + elif id is None: + return trans.show_form( + web.FormBuilder( action = web.url_for(), title = "Create a new Library", name = "create_library", submit_text = "Submit" ) + .add_text( name = "name", label = "Name", value = "Unnamed", error = None, help = None ) + .add_text( name = "description", label = "Description", value = None, error = None, help = None ) + .add_input( 'hidden', "Create Library", 'create_library', use_label = False ) ) + library = trans.app.model.Library.get( id ) + if library: + return trans.fill_template( 'library/manage_library.mako', library = library ) + else: + return trans.show_error_message( "Invalid library specified" ) + + #make admin only + @web.expose + def manage_folder( self, trans, id=None, name="Unnamed", description=None, parent_id = None, **kwd ): + if 'create_folder' in kwd: + folder = trans.app.model.LibraryFolder( name = name, description = description ) + if parent_id: + parent_folder = trans.app.model.LibraryFolder.get( parent_id ) + parent_folder.add_folder( folder ) + folder.flush() + trans.response.send_redirect( web.url_for( action='manage_folder', id = folder.id ) ) + elif id is None: + return trans.show_form( + web.FormBuilder( action = web.url_for(), title = "Create a new Folder", name = "create_folder", submit_text = "Submit" ) + .add_text( name = "name", label = "Name", value = "Unnamed", error = None, help = None ) + .add_text( name = "description", label = "Description", value = None, error = None, help = None ) + .add_input( 'hidden', None, 'parent_id', value = parent_id, use_label = False ) + .add_input( 'hidden', "Create Folder", 'create_folder', use_label = False ) ) + folder = trans.app.model.LibraryFolder.get( id ) + if folder: + msg = '' + if 'rename_folder' in kwd: + folder.name = name + folder.description = description + folder.flush() + msg = 'Folder has been renamed.' + return trans.fill_template( 'library/manage_folder.mako', folder = folder, msg = msg ) + else: + return trans.show_error_message( "Invalid folder specified" ) + + + #make admin only + @web.expose + def manage_dataset( self, trans, id=None, name="Unnamed", info = 'no info', extension = None, folder_id = None, dbkey = None, **kwd ): + data_files = [] + def add_file( file_obj, name, extension, dbkey, info = 'no info', space_to_tab = False ): + data_type = None + temp_name = sniff.stream_to_file( file_obj ) + if space_to_tab: + line_count = sniff.convert_newlines_sep2tabs( temp_name ) + else: + line_count = sniff.convert_newlines( temp_name ) + if extension == 'auto': + data_type = sniff.guess_ext( temp_name, sniff_order=trans.app.datatypes_registry.sniff_order ) + else: + data_type = extension + dataset = trans.app.model.LibraryFolderDatasetAssociation( name = name, info = info, extension = data_type, dbkey = dbkey, create_dataset = True ) + folder = trans.app.model.LibraryFolder.get( folder_id ) + folder.add_dataset( dataset ) + dataset.flush() + # TODO, SET SECURTY INTERACTIVELY ON DATASET, right now everything is public + trans.app.security_agent.set_dataset_groups( dataset.dataset, [trans.app.security_agent.get_public_group()] ) + shutil.move( temp_name, dataset.dataset.file_name ) + dataset.dataset.state = dataset.dataset.states.OK + dataset.init_meta() + if line_count is not None: + try: + dataset.set_peek( line_count=line_count ) + except: + dataset.set_peek() + else: + dataset.set_peek() + dataset.set_size() + + if dataset.missing_meta(): + dataset.datatype.set_meta( dataset ) + trans.app.model.flush() + + return dataset + if 'create_dataset' in kwd: + #copied from upload tool action + last_dataset_created = None + data_file = kwd['file_data'] + url_paste = kwd['url_paste'] + space_to_tab = False + if 'space_to_tab' in kwd: + if kwd['space_to_tab'] not in ["None", None]: + space_to_tab = True + temp_name = "" + data_list = [] + + if 'filename' in dir( data_file ): + file_name = data_file.filename + file_name = file_name.split( '\\' )[-1] + file_name = file_name.split( '/' )[-1] + last_dataset_created = add_file( data_file.file, file_name, extension, dbkey, info="uploaded file", space_to_tab = space_to_tab ) + elif url_paste not in [ None, "" ]: + if url_paste.lower().find( 'http://' ) >= 0 or url_paste.lower().find( 'ftp://' ) >= 0: + url_paste = url_paste.replace( '\r', '' ).split( '\n' ) + for line in url_paste: + line = line.rstrip( '\r\n' ) + if line: + last_dataset_created = add_file( urllib.urlopen( line ), line, extension, dbkey, info="uploaded url", space_to_tab=space_to_tab ) + else: + is_valid = False + for line in url_paste: + line = line.rstrip( '\r\n' ) + if line: + is_valid = True + break + if is_valid: + last_dataset_created = add_file( StringIO.StringIO( url_paste ), 'Pasted Entry', extension, dbkey, info="pasted entry", space_to_tab=space_to_tab ) + trans.response.send_redirect( web.url_for( action='manage_dataset', id = last_dataset_created.id ) ) + #return self.manage_dataset( trans, id = last_dataset_created.id ) + elif id is None: + return trans.fill_template( 'library/new_dataset.mako', folder_id = folder_id ) + dataset = trans.app.model.LibraryFolderDatasetAssociation.get( id ) + if dataset: + #copied from edit attributes for 'regular' datasets + p = util.Params(kwd, safe=False) + if p.change: + # The user clicked the Save button on the 'Change data type' form + trans.app.datatypes_registry.change_datatype( dataset, p.datatype ) + trans.app.model.flush() + elif p.save: + # The user clicked the Save button on the 'Edit Attributes' form + dataset.name = name + dataset.info = info + + # The following for loop will save all metadata_spec items + for name, spec in dataset.datatype.metadata_spec.items(): + if spec.get("readonly"): + continue + optional = p.get("is_"+name, None) + if optional and optional == 'true': + # optional element... == 'true' actually means it is NOT checked (and therefore ommitted) + setattr(dataset.metadata,name,None) + else: + setattr(dataset.metadata,name,spec.unwrap(p.get(name, None), p)) + + dataset.datatype.after_edit( dataset ) + trans.app.model.flush() + return trans.show_ok_message( "Attributes updated" ) + elif p.detect: + # The user clicked the Auto-detect button on the 'Edit Attributes' form + for name, spec in dataset.datatype.metadata_spec.items(): + # We need to be careful about the attributes we are resetting + if name != 'name' and name != 'info' and name != 'dbkey': + if spec.get( 'default' ): + setattr( dataset.metadata,name,spec.unwrap( spec.get( 'default' ), spec )) + dataset.datatype.set_meta( dataset ) + dataset.datatype.after_edit( dataset ) + trans.app.model.flush() + return trans.show_ok_message( "Attributes updated" ) + + dataset.datatype.before_edit( dataset ) + + if "dbkey" in dataset.datatype.metadata_spec and not dataset.metadata.dbkey: + # Copy dbkey into metadata, for backwards compatability + # This looks like it does nothing, but getting the dbkey + # returns the metadata dbkey unless it is None, in which + # case it resorts to the old dbkey. Setting the dbkey + # sets it properly in the metadata + dataset.metadata.dbkey = dataset.dbkey + metadata = list() + # a list of MetadataParemeters + for name, spec in dataset.datatype.metadata_spec.items(): + if spec.visible: + metadata.append( spec.wrap( dataset.metadata.get(name), dataset ) ) + # let's not overwrite the imported datatypes module with the variable datatypes? + ldatatypes = [x for x in trans.app.datatypes_registry.datatypes_by_extension.iterkeys()] + ldatatypes.sort() + return trans.fill_template( "/library/manage_dataset.mako", dataset=dataset, metadata=metadata, + datatypes=ldatatypes, err=None ) + else: + return trans.show_error_message( "Invalid dataset specified" ) diff --git a/lib/galaxy/web/framework/__init__.py b/lib/galaxy/web/framework/__init__.py index 529cb6156b0..124d1549453 100644 --- a/lib/galaxy/web/framework/__init__.py +++ b/lib/galaxy/web/framework/__init__.py @@ -527,8 +527,8 @@ class FormBuilder( object ): self.action = action self.submit_text = submit_text self.inputs = [] - def add_input( self, type, name, label, value=None, error=None, help=None ): - self.inputs.append( FormInput( type, label, name, value, error, help ) ) + def add_input( self, type, name, label, value=None, error=None, help=None, use_label=True ): + self.inputs.append( FormInput( type, label, name, value, error, help, use_label ) ) return self def add_text( self, name, label, value=None, error=None, help=None ): return self.add_input( 'text', label, name, value, error, help ) @@ -539,13 +539,14 @@ class FormInput( object ): """ Simple class describing a form input element """ - def __init__( self, type, name, label, value=None, error=None, help=None ): + def __init__( self, type, name, label, value=None, error=None, help=None, use_label=True ): self.type = type self.name = name self.label = label self.value = value self.error = error self.help = help + self.use_label = use_label class FormData( object ): """ diff --git a/templates/admin_main.mako b/templates/admin_main.mako index f9c26121124..02e39576685 100644 --- a/templates/admin_main.mako +++ b/templates/admin_main.mako @@ -29,5 +29,11 @@ + + + Manage Libraries + + + diff --git a/templates/form.mako b/templates/form.mako index c003de02585..42fdb745ba3 100644 --- a/templates/form.mako +++ b/templates/form.mako @@ -21,9 +21,11 @@ $(function(){ cls += " form-row-error" %>
    + %if input.use_label: + %endif
    diff --git a/templates/library/admin_list_libraries.mako b/templates/library/admin_list_libraries.mako new file mode 100644 index 00000000000..ffe82787da2 --- /dev/null +++ b/templates/library/admin_list_libraries.mako @@ -0,0 +1,16 @@ +<%inherit file="/base.mako"/> +<%def name="title()">View Libraries + +
    +
    Manage Libraries
    +
    + %for library in libraries: + + %endfor + +
    +
    diff --git a/templates/library/manage_dataset.mako b/templates/library/manage_dataset.mako new file mode 100644 index 00000000000..f04615def51 --- /dev/null +++ b/templates/library/manage_dataset.mako @@ -0,0 +1,100 @@ +<%inherit file="/base.mako"/> +<%def name="title()">Edit Dataset Attributes + + +<%def name="datatype( dataset, datatypes )"> + + + +
    +
    Edit Attributes
    +
    +
    + +
    + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    + %for element in metadata: +
    + +
    + ${element.get_html()} +
    +
    +
    + %endfor +
    + +
    +
    +
    + +
    + +
    +
    + This will inspect the dataset and attempt to correct the above column values + if they are not accurate. +
    +
    +
    +
    + +

    + + +

    +
    Change data type
    +
    +
    + +
    + +
    + ${datatype( dataset, datatypes )} +
    + +
    + This will change the datatype of the existing dataset + but not modify its contents. Use this if Galaxy + has incorrectly guessed the type of your dataset. +
    +
    +
    +
    + +
    +
    +
    +
    + +manage containing folder +

    diff --git a/templates/library/manage_folder.mako b/templates/library/manage_folder.mako new file mode 100644 index 00000000000..19e12555625 --- /dev/null +++ b/templates/library/manage_folder.mako @@ -0,0 +1,191 @@ +<%inherit file="/base.mako"/> + +<%def name="render_component( component )"> + <% + if isinstance( component, trans.app.model.LibraryFolder ): + return render_folder( component ) + elif isinstance( component, trans.app.model.LibraryFolderDatasetAssociation ): + return render_dataset( component ) + %> + + + +## Render the dataset `data` as history item, using `hid` as the displayed id +<%def name="render_dataset( data )"> + <% + if data.state in ['no state','',None]: + data_state = "queued" + else: + data_state = data.state + %> + ##

    +
    +
    ${data.display_name()}
    +
    +
    + + ## Header row for history items (name, state, action buttons) + +
    + %if data_state != 'ok': +
    + %endif +
    +
    + ##display data + edit attributes + ##delete +
    + ##${data.display_name()} +
    + + ## Body for history items, extra info and actions, data "peek" + +
    + %if data_state == "queued": +
    Job is waiting to run
    + %elif data_state == "running": +
    Job is currently running
    + %elif data_state == "error": +
    + An error occurred running this job: ${data.display_info().strip()}, + report this error +
    + %elif data_state == "empty": +
    No data: ${data.display_info()}
    + %elif data_state == "ok": +
    + ${data.blurb}, + format: ${data.ext}, + database: + %if data.dbkey == '?': + ${data.dbkey} + %else: + ${data.dbkey} + %endif +
    +
    Info: ${data.display_info()}
    + %if data.peek != "no peek": +
    ${data.display_peek()}
    + %endif + %else: +
    Error: unknown dataset state "${data_state}".
    + %endif + + ## Recurse for child datasets + + +
    +
    +
    + + +## Render a folder +<%def name="render_folder( this_folder )"> + +
    +
    Contents of Folder: ${this_folder.name}
    +
    +
    +
    + <% + components = list( this_folder.folders ) + list( this_folder.datasets ) + components = [ ( getattr( components[i], "order_id" ), i, components [i] ) for i in xrange( len( components ) ) ] + components.sort() + components = [ tup[-1] for tup in components ] + %> + %for component in components: + ${render_component( component )} + %endfor + +
    +
    +
    + +
    + +
    +
    + + + + + + +<%def name="title()">Manage Folder: ${folder.name} + +
    +
    Change Folder Attributes
    +
    +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    + + + +
    + +
    +
    Manage Folder Contents: ${folder.name}
    +
    +
    + %if folder.parent: + up a level + %elif folder.library_root: + manage library + %endif +
    +
    +
    + ${render_folder( folder )} +
    + +
    + +
    + +
    +
    + + diff --git a/templates/library/manage_library.mako b/templates/library/manage_library.mako new file mode 100644 index 00000000000..5d94dfb6b78 --- /dev/null +++ b/templates/library/manage_library.mako @@ -0,0 +1,46 @@ +<%inherit file="/base.mako"/> +<%def name="title()">Manage Library + + +
    +
    Edit a Library: ${library.name}
    +
    +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + +
    + + +
    + +
    + + +
    + +
    + +
    + diff --git a/templates/library/new_dataset.mako b/templates/library/new_dataset.mako new file mode 100644 index 00000000000..1c517cdfede --- /dev/null +++ b/templates/library/new_dataset.mako @@ -0,0 +1,997 @@ +<%inherit file="/base.mako"/> +<%def name="title()">Create New Library Dataset + +
    +
    Create a new Library Dataset
    +
    +
    + + + +
    + + +
    + + +
    + + +
    + +
    + + +
    + +
    + Here you may specify a list of URLs (one per line) or paste the contents of a file. +
    + + + +
    + +
    + +
    + + +
    Yes
    + +
    + + Use this option if you are entering intervals by hand. +
    + + +
    + +
    + +
    + + +
    + +
    + Which format? See help below +
    + + +
    + +
    + +
    + + +##this should be generated dynamically +
    + + + +
    + +
    + + +
    + +
    + + +
    +
    +
    + diff --git a/templates/library/user_list_libraries.mako b/templates/library/user_list_libraries.mako new file mode 100644 index 00000000000..23a2b3b9ad1 --- /dev/null +++ b/templates/library/user_list_libraries.mako @@ -0,0 +1,14 @@ +<%inherit file="/base.mako"/> +<%def name="title()">View Libraries + +
    +
    View Library
    +
    + %for library in libraries: + + %endfor + +
    +
    diff --git a/templates/library/user_view_library.mako b/templates/library/user_view_library.mako new file mode 100644 index 00000000000..3b948558f28 --- /dev/null +++ b/templates/library/user_view_library.mako @@ -0,0 +1,58 @@ +<%inherit file="/base.mako"/> + +<%def name="render_component( component )"> + <% + if isinstance( component, trans.app.model.LibraryFolder ): + return render_folder( component ) + elif isinstance( component, trans.app.model.LibraryFolderDatasetAssociation ): + return render_dataset( component ) + %> + + + +## Render the dataset `data` as history item, using `hid` as the displayed id +<%def name="render_dataset( data )"> +
    + ${data.name} +
    + + +## Render a folder +<%def name="render_folder( this_folder )"> + +
    + Folder: ${this_folder.name} + <% + components = list( this_folder.folders ) + list( this_folder.datasets ) + components = [ ( getattr( components[i], "order_id" ), i, components [i] ) for i in xrange( len( components ) ) ] + components.sort() + components = [ tup[-1] for tup in components ] + %> +
    + %for component in components: + ${render_component( component )} + %endfor +
    +
    + + + + + + + +<%def name="title()">View Library: ${library.name} + +
    +
    Import from Library: ${library.name}
    +
    +
    + ${render_folder( library.root_folder )} + +
    + +
    + +
    +
    + diff --git a/tool_conf.xml.sample b/tool_conf.xml.sample index e46f0c54ad1..69ccfe583ee 100644 --- a/tool_conf.xml.sample +++ b/tool_conf.xml.sample @@ -12,6 +12,7 @@ +
    diff --git a/tools/data_source/access_libraries.xml b/tools/data_source/access_libraries.xml new file mode 100644 index 00000000000..ffd383c481a --- /dev/null +++ b/tools/data_source/access_libraries.xml @@ -0,0 +1,11 @@ + + + + stored locally + + + + + + + From f0396dbc188aa381e9fa2d953900583e08e010ad Mon Sep 17 00:00:00 2001 From: Greg Von Kuster Date: Thu, 14 Aug 2008 16:18:06 -0400 Subject: [PATCH 14/21] First pass at administration components that incorporate dataset security and libraries. --- lib/galaxy/config.py | 2 +- lib/galaxy/model/__init__.py | 15 +- lib/galaxy/model/mapping.py | 12 +- lib/galaxy/security/__init__.py | 7 +- lib/galaxy/web/controllers/admin.py | 680 +++++++++++- lib/galaxy/web/controllers/library.py | 201 +--- .../admin/dataset_security/group_create.mako | 96 ++ .../group_dataset_permitted_actions_edit.mako | 71 ++ .../admin/dataset_security/group_members.mako | 47 + .../dataset_security/group_members_edit.mako | 114 ++ templates/admin/dataset_security/groups.mako | 67 ++ templates/admin/dataset_security/index.mako | 13 + .../specified_users_groups.mako | 56 + templates/admin/dataset_security/users.mako | 80 ++ templates/admin/index.mako | 13 + templates/admin/library/dataset.mako | 85 ++ templates/admin/library/folder.mako | 158 +++ templates/admin/library/libraries.mako | 19 + templates/admin/library/library.mako | 35 + templates/admin/library/new_dataset.mako | 953 +++++++++++++++++ templates/admin/reload_tool.mako | 28 + templates/admin_main.mako | 39 - templates/library/admin_list_libraries.mako | 16 - templates/library/libraries.mako | 13 + .../{user_view_library.mako => library.mako} | 52 +- templates/library/manage_dataset.mako | 100 -- templates/library/manage_folder.mako | 191 ---- templates/library/manage_library.mako | 46 - templates/library/new_dataset.mako | 997 ------------------ templates/library/user_list_libraries.mako | 14 - templates/root/masthead.mako | 3 + universe_wsgi.ini.sample | 4 +- 32 files changed, 2557 insertions(+), 1670 deletions(-) create mode 100644 templates/admin/dataset_security/group_create.mako create mode 100644 templates/admin/dataset_security/group_dataset_permitted_actions_edit.mako create mode 100644 templates/admin/dataset_security/group_members.mako create mode 100644 templates/admin/dataset_security/group_members_edit.mako create mode 100644 templates/admin/dataset_security/groups.mako create mode 100644 templates/admin/dataset_security/index.mako create mode 100644 templates/admin/dataset_security/specified_users_groups.mako create mode 100644 templates/admin/dataset_security/users.mako create mode 100644 templates/admin/index.mako create mode 100644 templates/admin/library/dataset.mako create mode 100644 templates/admin/library/folder.mako create mode 100644 templates/admin/library/libraries.mako create mode 100644 templates/admin/library/library.mako create mode 100644 templates/admin/library/new_dataset.mako create mode 100644 templates/admin/reload_tool.mako delete mode 100644 templates/admin_main.mako delete mode 100644 templates/library/admin_list_libraries.mako create mode 100644 templates/library/libraries.mako rename templates/library/{user_view_library.mako => library.mako} (58%) delete mode 100644 templates/library/manage_dataset.mako delete mode 100644 templates/library/manage_folder.mako delete mode 100644 templates/library/manage_library.mako delete mode 100644 templates/library/new_dataset.mako delete mode 100644 templates/library/user_list_libraries.mako diff --git a/lib/galaxy/config.py b/lib/galaxy/config.py index 432499bef7a..ac54a889b7a 100644 --- a/lib/galaxy/config.py +++ b/lib/galaxy/config.py @@ -45,7 +45,7 @@ class Configuration( object ): self.job_scheduler_policy = kwargs.get("job_scheduler_policy", "FIFO") self.job_queue_cleanup_interval = int( kwargs.get("job_queue_cleanup_interval", "5") ) self.job_working_directory = resolve_path( kwargs.get( "job_working_directory", "database/job_working_directory" ), self.root ) - self.admin_pass = kwargs.get('admin_pass',"galaxy") + self.admin_users = kwargs.get( "admin_users", "" ) self.sendmail_path = kwargs.get('sendmail_path',"/usr/sbin/sendmail") self.mailing_join_addr = kwargs.get('mailing_join_addr',"galaxy-user-join@bx.psu.edu") self.error_email_to = kwargs.get( 'error_email_to', None ) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index e6637b2ad7f..460b6433b1d 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -129,20 +129,18 @@ class Group( object ): self.priority = priority @classmethod def get_public_group( cls ): - # TODO, Nate: Make sure this method is functionally correct. return Group.get( cls.public_id ) @classmethod def set_public_group( cls, group ): - # TODO, Nate: Make sure this method is functionally correct. - #we store the id instead of the object, because of alchemy sessions + # We store the id instead of the object, because of alchemy sessions if isinstance( group, Group ): group = group.id cls.public_id = group @classmethod def guess_public_group( cls ): - # 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] ) + # Retrieve from database and store public group id + group = Group.select_by( name='public' )[0] + cls.set_public_group( group ) class UserGroupAssociation( object ): def __init__( self, user, group ): @@ -262,7 +260,6 @@ class Dataset( object ): except OSError, e: log.critical('%s delete error %s' % (self.__class__.__name__, e)) - class DatasetInstance( object ): """A base class for all 'dataset instances', HDAs, LDAs, etc""" states = Dataset.states @@ -419,8 +416,6 @@ class DatasetInstance( object ): for child in self.children: child.mark_deleted() - - class HistoryDatasetAssociation( DatasetInstance ): def __init__( self, hid = None, history = None, copied_from_history_dataset_association = None, copied_from_library_folder_dataset_association = None, **kwd ): DatasetInstance.__init__( self, **kwd ) @@ -521,7 +516,7 @@ class Library( object ): self.root_folder = root_folder class LibraryFolder( object ): - def __init__( self, name = None, description = None, order_id = None ): + def __init__( self, name = None, description = None, item_count = 0, order_id = None ): self.name = name or "Unnamed folder" self.description = description self.item_count = item_count diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index 6a8f982fc81..e6f132693da 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -139,9 +139,7 @@ GroupDatasetAssociation.table = Table( "group_dataset_association", metadata, Column( "update_time", DateTime, default=now, onupdate=now ), Column( "permitted_actions", JSONType(), default=[] ) ) -# TODO, Nate: Need to better understand what these Default tables are for and add appropriate -# comments here to clarify them. Need to ensure that they should include the permitted_actions -# columns, and if so, that they are correctly populated. +# The following table stores the permissions that are considered the defaults for new histories when they are created by a user DefaultUserGroupAssociation.table = Table( "default_user_group_association", metadata, Column( "id", Integer, primary_key=True ), Column( "group_id", Integer, ForeignKey( "galaxy_group.id" ), index=True ), @@ -150,6 +148,8 @@ DefaultUserGroupAssociation.table = Table( "default_user_group_association", met Column( "update_time", DateTime, default=now, onupdate=now ), Column( "permitted_actions", JSONType(), default=[] ) ) +# The following table stores the default permissions assigned to histories for datasets +# that need permissions ( dataset permissions that cannot be determined based on ancestor ) DefaultHistoryGroupAssociation.table = Table( "default_history_group_association", metadata, Column( "id", Integer, primary_key=True ), Column( "group_id", Integer, ForeignKey( "galaxy_group.id" ), index=True ), @@ -417,10 +417,6 @@ assign_mapper( context, UserGroupAssociation, UserGroupAssociation.table, properties=dict( user=relation( User, backref = "groups" ), group=relation( Group, backref = "users" ) ) ) - -# TODO, Nate: Need to make sure we have optimal performance - may need more mappers... -# if we have a user and a list of datasets, what is the fastest -# way to ask whether the user has a certain action on all of them. assign_mapper( context, GroupDatasetAssociation, GroupDatasetAssociation.table, properties=dict( dataset=relation( Dataset, backref = "groups" ), group=relation( Group, backref = "datasets" ) ) ) @@ -609,7 +605,7 @@ def init( file_path, url, engine_options={}, create_tables=False ): if result.Group.count() == 0: log.warning( "There were no groups located, setting up default (public) group." ) # Create public group - public_group = result.security_agent.create_group( name = 'public' ) + public_group = result.security_agent.create_group( name='public' ) # Store public group id result.security_agent.set_public_group( public_group ) # Loop through all histories and set up rbac on users, histories and datasets diff --git a/lib/galaxy/security/__init__.py b/lib/galaxy/security/__init__.py index 0ba0bf6b621..ed73ec2364e 100644 --- a/lib/galaxy/security/__init__.py +++ b/lib/galaxy/security/__init__.py @@ -13,9 +13,14 @@ log = logging.getLogger(__name__) # are correct when an authenticated user creates things inside their "private" environment. class RBACAgent: """Class that handles galaxy security""" - permitted_actions = Bunch( + permitted_actions = Bunch( + # The ability to edit the metadata of the associated dataset DATASET_EDIT_METADATA = 'dataset_edit_metadata', + # The ability to change the permissions of a dataset (so specifically, to add and modify + # group_dataset_association rows where the dataset is the dataset for which the permission is set). DATASET_MANAGE_PERMISSIONS = 'dataset_manage_permissions', + # The ability to perform any read only operation on the dataset (view, display at external site, + # use in a job, etc). DATASET_ACCESS = 'dataset_access' ) def allow_action( self, user, action, **kwd ): diff --git a/lib/galaxy/web/controllers/admin.py b/lib/galaxy/web/controllers/admin.py index fd9b4be90d8..3367ce6bc36 100644 --- a/lib/galaxy/web/controllers/admin.py +++ b/lib/galaxy/web/controllers/admin.py @@ -1,24 +1,676 @@ +import shutil, StringIO from galaxy.web.base.controller import * -import logging, sets, time +from galaxy.datatypes import sniff +from galaxy.security import RBACAgent +import galaxy.model +from xml.sax.saxutils import escape, unescape +import pkg_resources +pkg_resources.require( "sqlalchemy>=0.3" ) +import sqlalchemy as sa +import logging log = logging.getLogger( __name__ ) +entities = { '@': 'FuNkYaT' } +unentities = { 'FuNkYaT' : '@' } +no_privilege_msg = "You must have Galaxy administrator privileges to use this feature." + class Admin( BaseController ): + def user_is_admin( self, trans ): + admin_users = trans.app.config.get( "admin_users", "" ).split( "," ) + if not admin_users: + return False + user = trans.get_user() + if not user: + return False + if not user.email in admin_users: + return False + return True @web.expose def index( self, trans, **kwd ): - msg = '' - if 'action' in kwd: - if kwd['action'] == "tool_reload": - msg = self.tool_reload( **kwd ) - return trans.fill_template( 'admin_main.mako', toolbox=self.app.toolbox, msg=msg ) - - def tool_reload( self, tool_version=None, **kwd ): + if not self.user_is_admin( trans ): + return trans.show_error_message( no_privilege_msg ) params = util.Params( kwd ) - if params.passwd==self.app.config.admin_pass: - tool_id = params.tool_id - self.app.toolbox.reload( tool_id ) - msg = 'Reloaded tool: ' + tool_id + msg = params.msg + return trans.fill_template( '/admin/index.mako', msg=msg ) + @web.expose + def reload_tool( self, trans, **kwd ): + if not self.user_is_admin( trans ): + return trans.show_error_message( no_privilege_msg ) + params = util.Params( kwd ) + msg = params.msg + return trans.fill_template( '/admin/reload_tool.mako', toolbox=self.app.toolbox, msg=msg ) + @web.expose + def tool_reload( self, trans, tool_version=None, **kwd ): + if not self.user_is_admin( trans ): + return trans.show_error_message( no_privilege_msg ) + params = util.Params( kwd ) + tool_id = params.tool_id + self.app.toolbox.reload( tool_id ) + msg = 'Reloaded tool: ' + tool_id + return trans.fill_template( '/admin/reload_tool.mako', toolbox=self.app.toolbox, msg=msg ) + @web.expose + def dataset_security( self, trans, **kwd ): + if not self.user_is_admin( trans ): + return trans.show_error_message( no_privilege_msg ) + params = util.Params( kwd ) + msg = params.msg + return trans.fill_template( '/admin/dataset_security/index.mako', msg=msg ) + + # Galaxy Group Stuff + @web.expose + def groups( self, trans, **kwd ): + if not self.user_is_admin( trans ): + return trans.show_error_message( no_privilege_msg ) + params = util.Params( kwd ) + msg = params.msg + # This query retrieves groups that are not deleted and members of each group + q = sa.select( ( ( galaxy.model.Group.table.c.id ).label( 'group_id' ), + ( galaxy.model.Group.table.c.name ).label( 'group_name' ), + ( galaxy.model.Group.table.c.priority ).label( 'group_priority' ), + sa.func.count( galaxy.model.User.table.c.id ).label( 'total_members' ) ), + whereclause = galaxy.model.Group.table.c.deleted == False, + from_obj = [ sa.outerjoin( galaxy.model.Group.table, + galaxy.model.UserGroupAssociation.table + ).outerjoin( galaxy.model.User.table ) ], + group_by = [ galaxy.model.Group.table.c.id, + galaxy.model.Group.table.c.name, + galaxy.model.Group.table.c.priority ], + order_by = [ galaxy.model.Group.table.c.name ] ) + groups = [] + for row in q.execute(): + # This 2nd query retrieves the number of datasets and dataset permitted_actions associated with each group + q2 = sa.select( ( ( galaxy.model.Group.table.c.id ).label( 'group_id' ), + ( galaxy.model.GroupDatasetAssociation.table.c.permitted_actions ).label( 'permitted_actions' ), + sa.func.count( galaxy.model.Dataset.table.c.id ).label( 'total_datasets' ) ), + whereclause = galaxy.model.Group.table.c.id == row.group_id, + from_obj = [ sa.outerjoin( galaxy.model.Group.table, + galaxy.model.GroupDatasetAssociation.table + ).outerjoin( galaxy.model.Dataset.table ) ], + group_by = [ galaxy.model.Group.table.c.id, + galaxy.model.GroupDatasetAssociation.table.c.permitted_actions ] ) + for row2 in q2.execute(): + total_datasets = row2.total_datasets + permitted_actions = [] + # There may not yet be any GroupDatasetAssociations, in which case no + # actions will be found + if row2.permitted_actions: + for action in row2.permitted_actions: + permitted_actions.append( action.encode( 'ascii' ) ) + permitted_actions.sort() + groups.append( ( row.group_id, + escape( row.group_name, entities ), + row.group_priority, + row.total_members, + total_datasets, + permitted_actions ) ) + return trans.fill_template( '/admin/dataset_security/groups.mako', + groups=groups, + msg=msg ) + @web.expose + def create_group( self, trans, **kwd ): + if not self.user_is_admin( trans ): + return trans.show_error_message( no_privilege_msg ) + params = util.Params( kwd ) + msg = params.msg + q = sa.select( ( ( galaxy.model.User.table.c.id ).label( 'user_id' ), + ( galaxy.model.User.table.c.email ).label( 'user_email') ), + from_obj = [ galaxy.model.User.table ], + order_by = [ galaxy.model.User.table.c.email ] ) + users = [] + for row in q.execute(): + users.append( ( row.user_id, + escape( row.user_email, entities ) ) ) + return trans.fill_template( '/admin/dataset_security/group_create.mako', users=users, msg=msg ) + @web.expose + def new_group( self, trans, **kwd ): + if not self.user_is_admin( trans ): + return trans.show_error_message( no_privilege_msg ) + params = util.Params( kwd ) + msg = params.msg + name = unescape( params.name, unentities ) + if not name: + msg = "Please enter a name" + trans.response.send_redirect( '/admin/create_group?msg=%s' % msg ) else: - msg = 'Invalid password' - return msg + try: + priority = int( params.priority ) + except: + priority = 0 + # Create the group + group = galaxy.model.Group( name, priority ) + group.flush() + # Add the members + members = params.members + for user_id in members: + user = galaxy.model.User.get( user_id ) + # Create the UserGroupAssociation + user_group_association = galaxy.model.UserGroupAssociation( user, group ) + user_group_association.flush() + msg = "The new group has been created with priority %s and %s members" % ( str( priority ), str( len( members ) ) ) + trans.response.send_redirect( '/admin/groups?msg=%s' % msg ) + @web.expose + def group_members( self, trans, **kwd ): + if not self.user_is_admin( trans ): + return trans.show_error_message( no_privilege_msg ) + params = util.Params( kwd ) + msg = params.msg + group_id = params.group_id + group_name = unescape( params.group_name, unentities ) + # This query retrieves all members of the group + q = sa.select( ( ( galaxy.model.User.table.c.id ).label( 'user_id' ), + ( galaxy.model.User.table.c.email ).label( 'user_email' ) ), + whereclause = galaxy.model.UserGroupAssociation.table.c.group_id == group_id, + from_obj = [ sa.outerjoin( galaxy.model.UserGroupAssociation.table, + galaxy.model.User.table ) ], + order_by = [ 'user_email' ] ) + members = [] + for row in q.execute(): + members.append( ( row.user_id, + escape( row.user_email, entities ) ) ) + return trans.fill_template( '/admin/dataset_security/group_members.mako', + group_id=group_id, + group_name=escape( group_name, entities ), + members=members, + msg=msg ) + @web.expose + def group_members_edit( self, trans, **kwd ): + if not self.user_is_admin( trans ): + return trans.show_error_message( no_privilege_msg ) + params = util.Params( kwd ) + msg = params.msg + group_id = params.group_id + group_name = unescape( params.group_name, unentities ) + members = params.members + # First get all users + q = sa.select( ( ( galaxy.model.User.table.c.id ).label( 'user_id' ), + ( galaxy.model.User.table.c.email ).label( 'user_email' ) ), + order_by = [ 'user_email' ] ) + users = [] + for row in q.execute(): + users.append( ( row.user_id, + escape( row.user_email, entities ) ) ) + # Then get members of the group + q = sa.select( ( ( galaxy.model.User.table.c.id ).label( 'user_id' ), + ( galaxy.model.User.table.c.email ).label( 'user_email' ) ), + whereclause = galaxy.model.UserGroupAssociation.table.c.group_id == group_id, + from_obj = [ sa.outerjoin( galaxy.model.UserGroupAssociation.table, + galaxy.model.User.table ) ], + order_by = [ 'user_email' ] ) + members = [] + for row in q.execute(): + members.append( ( row.user_id, + escape( row.user_email, entities ) ) ) + return trans.fill_template( '/admin/dataset_security/group_members_edit.mako', + group_id=group_id, + group_name=escape( group_name, entities ), + users=users, + members=members, + msg=msg ) + @web.expose + def update_group_members( self, trans, **kwd ): + if not self.user_is_admin( trans ): + return trans.show_error_message( no_privilege_msg ) + params = util.Params( kwd ) + group_id = int( params.group_id ) + members = params.members + if members and not isinstance( members, list ): + # mako passes singleton lists as strings for some reason + members = [ members ] + # Handle case where admin removed all members from group + elif members is None: + members = [] + group = galaxy.model.Group.get( group_id ) + # This is tricky since we have default association tables with + # records referring to members of this group. Because of this, + # we'll need to handle changes to the member list rather than the + # simpler approach of deleting all existing members and creating + # new records for user_ids in the received members param. + # First remove existing members that are not in the received members param + for user_group_assoc in group.users: + if user_group_assoc.user_id not in members: + user = galaxy.model.User.get( user_group_assoc.user_id ) + # Delete DefaultUserGroupAssociations + for default_user_group_association in user.default_groups: + if default_user_group_association.group_id == group_id: + default_user_group_association.delete() + default_user_group_association.flush() + break # Should only be 1 record + # Delete DefaultHistoryGroupAssociations + for history in user.histories: + for default_history_group_association in history.default_groups: + if default_history_group_association.group_id == group_id: + default_history_group_association.delete() + default_history_group_association.flush() + # Delete the UserGroupAssociation + user_group_assoc.delete() + user_group_assoc.flush() + # Then add all new members to the group + for user_id in members: + user = galaxy.model.User.get( user_id ) + if user not in group.users: + user_group_association = galaxy.model.UserGroupAssociation( user, group ) + user_group_association.flush() + msg = "Group membership has been updated with a total of %s members" % len( members ) + trans.response.send_redirect( '/admin/group_members?group_id=%s&group_name=%s&msg=%s' % ( str( group_id ), params.group_name, msg ) ) + @web.expose + def group_dataset_permitted_actions( self, trans, **kwd ): + if not self.user_is_admin( trans ): + return trans.show_error_message( no_privilege_msg ) + params = util.Params( kwd ) + msg = params.msg + group_id = int( params.group_id ) + group_name = unescape( params.group_name, unentities ) + # Need to get all actions to send to the form + dataset_actions = [] + dpas = RBACAgent.permitted_actions + for dpa in dpas.items(): + if dpa[0].startswith( 'DATASET' ): + dataset_actions.append( dpa[1] ) + dataset_actions.sort() + q = sa.select( ( ( galaxy.model.Group.table.c.priority ).label( 'group_priority' ), + ( galaxy.model.GroupDatasetAssociation.table.c.permitted_actions ).label( 'permitted_actions' ) ), + whereclause = galaxy.model.GroupDatasetAssociation.table.c.id == group_id, + from_obj = [ sa.outerjoin( galaxy.model.Group.table, + galaxy.model.GroupDatasetAssociation.table ) ] ) + gdas = [] + for row in q.execute(): + permitted_actions = [] + # Although there may be GroupDatasetAssociations, there may not be any permitted_actions on them + if row.permitted_actions: + for action in row.permitted_actions: + permitted_actions.append( action.encode( 'ascii' ) ) + permitted_actions.sort() + gdas.append( ( row.group_priority, + permitted_actions ) ) + break # Just need 1 row + return trans.fill_template( '/admin/dataset_security/group_dataset_permitted_actions_edit.mako', + group_id=group_id, + group_name=escape( group_name, entities ), + gdas=gdas, + dataset_actions=dataset_actions, + msg=msg ) + @web.expose + def group_dataset_permitted_actions_edit( self, trans, **kwd ): + if not self.user_is_admin( trans ): + return trans.show_error_message( no_privilege_msg ) + params = util.Params( kwd ) + group_id = int( params.group_id ) + actions = params.actions + if actions and not isinstance( actions, list ): + actions = [ actions ] + # Update the permitted_actions for every GroupDatasetAssociation of the Group + q = sa.update( galaxy.model.GroupDatasetAssociation.table, + whereclause = galaxy.model.GroupDatasetAssociation.table.c.group_id == group_id, + values = { galaxy.model.GroupDatasetAssociation.table.c.permitted_actions : actions } ) + result = q.execute() + msg = "The dataset permitted actions for the group have been updated, affecting %d rows in the group_dataset_association table" % result.rowcount + trans.response.send_redirect( '/admin/groups?msg=%s' % msg ) + @web.expose + def mark_group_deleted( self, trans, **kwd ): + if not self.user_is_admin( trans ): + return trans.show_error_message( no_privilege_msg ) + params = util.Params( kwd ) + msg = params.msg + group_id = params.group_id + group = galaxy.model.Group.get( group_id ) + group.deleted = True + group.flush() + msg = "The group has been marked as deleted." + trans.response.send_redirect( '/admin/groups?msg=%s' % msg ) + @web.expose + def deleted_groups( self, trans, **kwd ): + if not self.user_is_admin( trans ): + return trans.show_error_message( no_privilege_msg ) + params = util.Params( kwd ) + msg = params.msg + # This query retrieves groups that are not deleted and members of each group + q = sa.select( ( ( galaxy.model.Group.table.c.id ).label( 'group_id' ), + ( galaxy.model.Group.table.c.name ).label( 'group_name' ), + ( galaxy.model.Group.table.c.priority ).label( 'group_priority' ), + sa.func.count( galaxy.model.User.table.c.id ).label( 'total_members' ) ), + whereclause = galaxy.model.Group.table.c.deleted == True, + from_obj = [ sa.outerjoin( galaxy.model.Group.table, + galaxy.model.UserGroupAssociation.table + ).outerjoin( galaxy.model.User.table ) ], + group_by = [ galaxy.model.Group.table.c.id, + galaxy.model.Group.table.c.name, + galaxy.model.Group.table.c.priority ], + order_by = [ galaxy.model.Group.table.c.name ] ) + groups = [] + for row in q.execute(): + # This 2nd query retrieves the number of datasets and dataset permitted_actions associated with each group + q2 = sa.select( ( ( galaxy.model.Group.table.c.id ).label( 'group_id' ), + ( galaxy.model.GroupDatasetAssociation.table.c.permitted_actions ).label( 'permitted_actions' ), + sa.func.count( galaxy.model.Dataset.table.c.id ).label( 'total_datasets' ) ), + whereclause = galaxy.model.Group.table.c.id == row.group_id, + from_obj = [ sa.outerjoin( galaxy.model.Group.table, + galaxy.model.GroupDatasetAssociation.table + ).outerjoin( galaxy.model.Dataset.table ) ], + group_by = [ galaxy.model.Group.table.c.id, + galaxy.model.GroupDatasetAssociation.table.c.permitted_actions ] ) + for row2 in q2.execute(): + total_datasets = row2.total_datasets + permitted_actions = [] + # There may not yet be any GroupDatasetAssociations, in which case no + # actions will be found + if row2.permitted_actions: + for action in row2.permitted_actions: + permitted_actions.append( action.encode( 'ascii' ) ) + permitted_actions.sort() + groups.append( ( row.group_id, + escape( row.group_name, entities ), + row.group_priority, + row.total_members, + total_datasets, + permitted_actions ) ) + return trans.fill_template( '/admin/dataset_security/deleted_groups.mako', + groups=groups, + msg=msg ) + @web.expose + def undelete_group( self, trans, **kwd ): + if not self.user_is_admin( trans ): + return trans.show_error_message( no_privilege_msg ) + params = util.Params( kwd ) + msg = params.msg + group_id = params.group_id + group = galaxy.model.Group.get( group_id ) + group.deleted = False + group.flush() + msg = "The group has been marked as not deleted." + trans.response.send_redirect( '/admin/groups?msg=%s' % msg ) + @web.expose + def purge_group( self, trans, **kwd ): + if not self.user_is_admin( trans ): + return trans.show_error_message( no_privilege_msg ) + params = util.Params( kwd ) + msg = params.msg + group_id = params.group_id + group = galaxy.model.Group.get( group_id ) + # Remove members and all associations + for user_group_assoc in group.users: + user = galaxy.model.User.get( user_group_assoc.user_id ) + # Delete DefaultUserGroupAssociations + for default_user_group_association in user.default_groups: + if default_user_group_association.group_id == group_id: + default_user_group_association.delete() + default_user_group_association.flush() + break # Should only be 1 record + # Delete DefaultHistoryGroupAssociations + for history in user.histories: + for default_history_group_association in history.default_groups: + if default_history_group_association.group_id == group_id: + default_history_group_association.delete() + default_history_group_association.flush() + # Delete the UserGroupAssociation + user_group_assoc.delete() + user_group_assoc.flush() + # Delete the Group + group.delete() + group.flush() + msg = "The group has been purged from the database." + trans.response.send_redirect( '/admin/deleted_groups?msg=%s' % msg ) + + # Galaxy User Stuff + @web.expose + def users( self, trans, **kwd ): + if not self.user_is_admin( trans ): + return trans.show_error_message( no_privilege_msg ) + params = util.Params( kwd ) + msg = params.msg + q = sa.select( ( ( galaxy.model.User.table.c.id ).label( 'user_id' ), + ( galaxy.model.User.table.c.email ).label( 'user_email') ), + from_obj = [ galaxy.model.User.table ], + order_by = [ galaxy.model.User.table.c.email ] ) + users = [] + for row in q.execute(): + users.append( ( row.user_id, + escape( row.user_email, entities ) ) ) + return trans.fill_template( '/admin/dataset_security/users.mako', + users=users, + msg=msg ) + @web.expose + def specified_users_groups( self, trans, **kwd ): + if not self.user_is_admin( trans ): + return trans.show_error_message( no_privilege_msg ) + params = util.Params( kwd ) + msg = params.msg + user_id = int( params.user_id ) + user_email = unescape( params.user_email, unentities ) + # Get the groups to which the user belongs + q = sa.select( ( ( galaxy.model.Group.table.c.id ).label( 'group_id' ), + ( galaxy.model.Group.table.c.name ).label( 'group_name' ), + ( galaxy.model.Group.table.c.priority ).label( 'group_priority' ) ), + whereclause = galaxy.model.User.table.c.id == user_id, + from_obj = [ sa.outerjoin( galaxy.model.User.table, + galaxy.model.UserGroupAssociation.table ).outerjoin( galaxy.model.Group.table ) ], + order_by = [ 'group_name' ] ) + groups = [] + for row in q.execute(): + # Perform a 2nd query to get datasets associated with each group + q2 = sa.select( ( ( galaxy.model.Group.table.c.id ).label( 'group_id' ), + ( galaxy.model.GroupDatasetAssociation.table.c.permitted_actions ).label( 'permitted_actions' ), + sa.func.count( galaxy.model.Dataset.table.c.id ).label( 'total_datasets' ) ), + whereclause = galaxy.model.Group.table.c.id == row.group_id, + from_obj = [ sa.outerjoin( galaxy.model.Group.table, + galaxy.model.GroupDatasetAssociation.table + ).outerjoin( galaxy.model.Dataset.table ) ], + group_by = [ galaxy.model.Group.table.c.id, + galaxy.model.GroupDatasetAssociation.table.c.permitted_actions ] ) + for row2 in q2.execute(): + total_datasets = row2.total_datasets + permitted_actions = [] + # There may not yet be any GroupDatasetAssociations, in which case no + # actions will be found + if row2.permitted_actions: + for action in row2.permitted_actions: + permitted_actions.append( action.encode( 'ascii' ) ) + permitted_actions.sort() + groups.append( ( row.group_id, + escape( row.group_name, entities ), + row.group_priority, + row2.total_datasets, + permitted_actions ) ) + return trans.fill_template( '/admin/dataset_security/specified_users_groups.mako', + user_id=user_id, + user_email=escape( user_email, entities ), + groups=groups, + msg=msg ) + + # Galaxy Library Stuff + @web.expose + def libraries( self, trans, **kwd ): + if not self.user_is_admin( trans ): + return trans.show_error_message( no_privilege_msg ) + return trans.fill_template( '/admin/library/libraries.mako', libraries=trans.app.model.Library.select() ) + @web.expose + def library( self, trans, id=None, name="Unnamed", description=None, **kwd ): + if not self.user_is_admin( trans ): + return trans.show_error_message( no_privilege_msg ) + if 'create_library' in kwd: + library = trans.app.model.Library( name=name, description=description ) + root_folder = trans.app.model.LibraryFolder( name=name, description=description ) + root_folder.flush() + library.root_folder = root_folder + library.flush() + trans.response.send_redirect( web.url_for( action='folder', id = root_folder.id ) ) + elif id is None: + return trans.show_form( + web.FormBuilder( action = web.url_for(), title = "Create a new Library", name = "create_library", submit_text = "Submit" ) + .add_text( name = "name", label = "Name", value = "Unnamed", error = None, help = None ) + .add_text( name = "description", label = "Description", value = None, error = None, help = None ) + .add_input( 'hidden', "Create Library", 'create_library', use_label = False ) ) + library = trans.app.model.Library.get( id ) + if library: + return trans.fill_template( '/admin/library/library.mako', library = library ) + else: + return trans.show_error_message( "Invalid library specified" ) + @web.expose + def folder( self, trans, id=None, name="Unnamed", description=None, parent_id = None, **kwd ): + if not self.user_is_admin( trans ): + return trans.show_error_message( no_privilege_msg ) + if 'create_folder' in kwd: + folder = trans.app.model.LibraryFolder( name = name, description = description ) + if parent_id: + parent_folder = trans.app.model.LibraryFolder.get( parent_id ) + parent_folder.add_folder( folder ) + folder.flush() + trans.response.send_redirect( web.url_for( action='folder', id = folder.id ) ) + elif id is None: + return trans.show_form( + web.FormBuilder( action = web.url_for(), title = "Create a new Folder", name = "create_folder", submit_text = "Submit" ) + .add_text( name = "name", label = "Name", value = "Unnamed", error = None, help = None ) + .add_text( name = "description", label = "Description", value = None, error = None, help = None ) + .add_input( 'hidden', None, 'parent_id', value = parent_id, use_label = False ) + .add_input( 'hidden', "Create Folder", 'create_folder', use_label = False ) ) + folder = trans.app.model.LibraryFolder.get( id ) + if folder: + msg = '' + if 'rename_folder' in kwd: + folder.name = name + folder.description = description + folder.flush() + msg = 'Folder has been renamed.' + return trans.fill_template( '/admin/library/folder.mako', folder=folder, msg=msg ) + else: + return trans.show_error_message( "Invalid folder specified" ) + @web.expose + def dataset( self, trans, id=None, name="Unnamed", info='no info', extension=None, folder_id=None, dbkey=None, **kwd ): + if not self.user_is_admin( trans ): + return trans.show_error_message( no_privilege_msg ) + data_files = [] + def add_file( file_obj, name, extension, dbkey, info = 'no info', space_to_tab = False ): + data_type = None + temp_name = sniff.stream_to_file( file_obj ) + if space_to_tab: + line_count = sniff.convert_newlines_sep2tabs( temp_name ) + else: + line_count = sniff.convert_newlines( temp_name ) + if extension == 'auto': + data_type = sniff.guess_ext( temp_name, sniff_order=trans.app.datatypes_registry.sniff_order ) + else: + data_type = extension + dataset = trans.app.model.LibraryFolderDatasetAssociation( name = name, info = info, extension = data_type, dbkey = dbkey, create_dataset = True ) + folder = trans.app.model.LibraryFolder.get( folder_id ) + folder.add_dataset( dataset ) + dataset.flush() + # TODO, SET SECURTY INTERACTIVELY ON DATASET, right now everything is public + trans.app.security_agent.set_dataset_groups( dataset.dataset, [trans.app.security_agent.get_public_group()] ) + shutil.move( temp_name, dataset.dataset.file_name ) + dataset.dataset.state = dataset.dataset.states.OK + dataset.init_meta() + if line_count is not None: + try: + dataset.set_peek( line_count=line_count ) + except: + dataset.set_peek() + else: + dataset.set_peek() + dataset.set_size() + + if dataset.missing_meta(): + dataset.datatype.set_meta( dataset ) + trans.app.model.flush() + + return dataset + if 'create_dataset' in kwd: + #copied from upload tool action + last_dataset_created = None + data_file = kwd['file_data'] + url_paste = kwd['url_paste'] + space_to_tab = False + if 'space_to_tab' in kwd: + if kwd['space_to_tab'] not in ["None", None]: + space_to_tab = True + temp_name = "" + data_list = [] + + if 'filename' in dir( data_file ): + file_name = data_file.filename + file_name = file_name.split( '\\' )[-1] + file_name = file_name.split( '/' )[-1] + last_dataset_created = add_file( data_file.file, file_name, extension, dbkey, info="uploaded file", space_to_tab = space_to_tab ) + elif url_paste not in [ None, "" ]: + if url_paste.lower().find( 'http://' ) >= 0 or url_paste.lower().find( 'ftp://' ) >= 0: + url_paste = url_paste.replace( '\r', '' ).split( '\n' ) + for line in url_paste: + line = line.rstrip( '\r\n' ) + if line: + last_dataset_created = add_file( urllib.urlopen( line ), line, extension, dbkey, info="uploaded url", space_to_tab=space_to_tab ) + else: + is_valid = False + for line in url_paste: + line = line.rstrip( '\r\n' ) + if line: + is_valid = True + break + if is_valid: + last_dataset_created = add_file( StringIO.StringIO( url_paste ), 'Pasted Entry', extension, dbkey, info="pasted entry", space_to_tab=space_to_tab ) + trans.response.send_redirect( web.url_for( action='dataset', id = last_dataset_created.id ) ) + #return self.dataset( trans, id = last_dataset_created.id ) + elif id is None: + return trans.fill_template( '/admin/library/new_dataset.mako', folder_id = folder_id ) + dataset = trans.app.model.LibraryFolderDatasetAssociation.get( id ) + if dataset: + #copied from edit attributes for 'regular' datasets + p = util.Params(kwd, safe=False) + if p.change: + # The user clicked the Save button on the 'Change data type' form + trans.app.datatypes_registry.change_datatype( dataset, p.datatype ) + trans.app.model.flush() + elif p.save: + # The user clicked the Save button on the 'Edit Attributes' form + dataset.name = name + dataset.info = info + + # The following for loop will save all metadata_spec items + for name, spec in dataset.datatype.metadata_spec.items(): + if spec.get("readonly"): + continue + optional = p.get("is_"+name, None) + if optional and optional == 'true': + # optional element... == 'true' actually means it is NOT checked (and therefore ommitted) + setattr(dataset.metadata,name,None) + else: + setattr(dataset.metadata,name,spec.unwrap(p.get(name, None), p)) + + dataset.datatype.after_edit( dataset ) + trans.app.model.flush() + return trans.show_ok_message( "Attributes updated" ) + elif p.detect: + # The user clicked the Auto-detect button on the 'Edit Attributes' form + for name, spec in dataset.datatype.metadata_spec.items(): + # We need to be careful about the attributes we are resetting + if name != 'name' and name != 'info' and name != 'dbkey': + if spec.get( 'default' ): + setattr( dataset.metadata,name,spec.unwrap( spec.get( 'default' ), spec )) + dataset.datatype.set_meta( dataset ) + dataset.datatype.after_edit( dataset ) + trans.app.model.flush() + return trans.show_ok_message( "Attributes updated" ) + + dataset.datatype.before_edit( dataset ) + + if "dbkey" in dataset.datatype.metadata_spec and not dataset.metadata.dbkey: + # Copy dbkey into metadata, for backwards compatability + # This looks like it does nothing, but getting the dbkey + # returns the metadata dbkey unless it is None, in which + # case it resorts to the old dbkey. Setting the dbkey + # sets it properly in the metadata + dataset.metadata.dbkey = dataset.dbkey + metadata = list() + # a list of MetadataParemeters + for name, spec in dataset.datatype.metadata_spec.items(): + if spec.visible: + metadata.append( spec.wrap( dataset.metadata.get(name), dataset ) ) + # let's not overwrite the imported datatypes module with the variable datatypes? + ldatatypes = [x for x in trans.app.datatypes_registry.datatypes_by_extension.iterkeys()] + ldatatypes.sort() + return trans.fill_template( "/admin/library/dataset.mako", + dataset=dataset, + metadata=metadata, + datatypes=ldatatypes, + err=None ) + else: + return trans.show_error_message( "Invalid dataset specified" ) diff --git a/lib/galaxy/web/controllers/library.py b/lib/galaxy/web/controllers/library.py index c8b1f6f0573..94e7b5731e0 100644 --- a/lib/galaxy/web/controllers/library.py +++ b/lib/galaxy/web/controllers/library.py @@ -1,12 +1,10 @@ from galaxy.web.base.controller import * -from galaxy.datatypes import sniff -import logging, shutil, StringIO +import logging log = logging.getLogger( __name__ ) class Library( BaseController ): - @web.expose def index( self, trans, library_id = None, import_ids = [], **kwd ): #use for importing an entry into your history @@ -21,198 +19,5 @@ class Library( BaseController ): history.flush() return trans.show_ok_message( "%i datasets have been imported into your history" % len( import_ids ), refresh_frames=['history'] ) elif library_id: - return trans.fill_template( 'library/user_view_library.mako', library = trans.app.model.Library.get( library_id ) ) - return trans.fill_template( 'library/user_list_libraries.mako', libraries = trans.app.model.Library.select() ) - - #make admin only - @web.expose - def manage_libraries( self, trans, **kwd ): - return trans.fill_template( 'library/admin_list_libraries.mako', libraries = trans.app.model.Library.select() ) - - #make admin only - @web.expose - def manage_library( self, trans, id=None, name="Unnamed", description=None, **kwd ): - if 'create_library' in kwd: - library = trans.app.model.Library( name = name, description = description ) - root_folder = trans.app.model.LibraryFolder( name = name, description = description ) - root_folder.flush() - library.root_folder = root_folder - library.flush() - trans.response.send_redirect( web.url_for( action='manage_folder', id = root_folder.id ) ) - elif id is None: - return trans.show_form( - web.FormBuilder( action = web.url_for(), title = "Create a new Library", name = "create_library", submit_text = "Submit" ) - .add_text( name = "name", label = "Name", value = "Unnamed", error = None, help = None ) - .add_text( name = "description", label = "Description", value = None, error = None, help = None ) - .add_input( 'hidden', "Create Library", 'create_library', use_label = False ) ) - library = trans.app.model.Library.get( id ) - if library: - return trans.fill_template( 'library/manage_library.mako', library = library ) - else: - return trans.show_error_message( "Invalid library specified" ) - - #make admin only - @web.expose - def manage_folder( self, trans, id=None, name="Unnamed", description=None, parent_id = None, **kwd ): - if 'create_folder' in kwd: - folder = trans.app.model.LibraryFolder( name = name, description = description ) - if parent_id: - parent_folder = trans.app.model.LibraryFolder.get( parent_id ) - parent_folder.add_folder( folder ) - folder.flush() - trans.response.send_redirect( web.url_for( action='manage_folder', id = folder.id ) ) - elif id is None: - return trans.show_form( - web.FormBuilder( action = web.url_for(), title = "Create a new Folder", name = "create_folder", submit_text = "Submit" ) - .add_text( name = "name", label = "Name", value = "Unnamed", error = None, help = None ) - .add_text( name = "description", label = "Description", value = None, error = None, help = None ) - .add_input( 'hidden', None, 'parent_id', value = parent_id, use_label = False ) - .add_input( 'hidden', "Create Folder", 'create_folder', use_label = False ) ) - folder = trans.app.model.LibraryFolder.get( id ) - if folder: - msg = '' - if 'rename_folder' in kwd: - folder.name = name - folder.description = description - folder.flush() - msg = 'Folder has been renamed.' - return trans.fill_template( 'library/manage_folder.mako', folder = folder, msg = msg ) - else: - return trans.show_error_message( "Invalid folder specified" ) - - - #make admin only - @web.expose - def manage_dataset( self, trans, id=None, name="Unnamed", info = 'no info', extension = None, folder_id = None, dbkey = None, **kwd ): - data_files = [] - def add_file( file_obj, name, extension, dbkey, info = 'no info', space_to_tab = False ): - data_type = None - temp_name = sniff.stream_to_file( file_obj ) - if space_to_tab: - line_count = sniff.convert_newlines_sep2tabs( temp_name ) - else: - line_count = sniff.convert_newlines( temp_name ) - if extension == 'auto': - data_type = sniff.guess_ext( temp_name, sniff_order=trans.app.datatypes_registry.sniff_order ) - else: - data_type = extension - dataset = trans.app.model.LibraryFolderDatasetAssociation( name = name, info = info, extension = data_type, dbkey = dbkey, create_dataset = True ) - folder = trans.app.model.LibraryFolder.get( folder_id ) - folder.add_dataset( dataset ) - dataset.flush() - # TODO, SET SECURTY INTERACTIVELY ON DATASET, right now everything is public - trans.app.security_agent.set_dataset_groups( dataset.dataset, [trans.app.security_agent.get_public_group()] ) - shutil.move( temp_name, dataset.dataset.file_name ) - dataset.dataset.state = dataset.dataset.states.OK - dataset.init_meta() - if line_count is not None: - try: - dataset.set_peek( line_count=line_count ) - except: - dataset.set_peek() - else: - dataset.set_peek() - dataset.set_size() - - if dataset.missing_meta(): - dataset.datatype.set_meta( dataset ) - trans.app.model.flush() - - return dataset - if 'create_dataset' in kwd: - #copied from upload tool action - last_dataset_created = None - data_file = kwd['file_data'] - url_paste = kwd['url_paste'] - space_to_tab = False - if 'space_to_tab' in kwd: - if kwd['space_to_tab'] not in ["None", None]: - space_to_tab = True - temp_name = "" - data_list = [] - - if 'filename' in dir( data_file ): - file_name = data_file.filename - file_name = file_name.split( '\\' )[-1] - file_name = file_name.split( '/' )[-1] - last_dataset_created = add_file( data_file.file, file_name, extension, dbkey, info="uploaded file", space_to_tab = space_to_tab ) - elif url_paste not in [ None, "" ]: - if url_paste.lower().find( 'http://' ) >= 0 or url_paste.lower().find( 'ftp://' ) >= 0: - url_paste = url_paste.replace( '\r', '' ).split( '\n' ) - for line in url_paste: - line = line.rstrip( '\r\n' ) - if line: - last_dataset_created = add_file( urllib.urlopen( line ), line, extension, dbkey, info="uploaded url", space_to_tab=space_to_tab ) - else: - is_valid = False - for line in url_paste: - line = line.rstrip( '\r\n' ) - if line: - is_valid = True - break - if is_valid: - last_dataset_created = add_file( StringIO.StringIO( url_paste ), 'Pasted Entry', extension, dbkey, info="pasted entry", space_to_tab=space_to_tab ) - trans.response.send_redirect( web.url_for( action='manage_dataset', id = last_dataset_created.id ) ) - #return self.manage_dataset( trans, id = last_dataset_created.id ) - elif id is None: - return trans.fill_template( 'library/new_dataset.mako', folder_id = folder_id ) - dataset = trans.app.model.LibraryFolderDatasetAssociation.get( id ) - if dataset: - #copied from edit attributes for 'regular' datasets - p = util.Params(kwd, safe=False) - if p.change: - # The user clicked the Save button on the 'Change data type' form - trans.app.datatypes_registry.change_datatype( dataset, p.datatype ) - trans.app.model.flush() - elif p.save: - # The user clicked the Save button on the 'Edit Attributes' form - dataset.name = name - dataset.info = info - - # The following for loop will save all metadata_spec items - for name, spec in dataset.datatype.metadata_spec.items(): - if spec.get("readonly"): - continue - optional = p.get("is_"+name, None) - if optional and optional == 'true': - # optional element... == 'true' actually means it is NOT checked (and therefore ommitted) - setattr(dataset.metadata,name,None) - else: - setattr(dataset.metadata,name,spec.unwrap(p.get(name, None), p)) - - dataset.datatype.after_edit( dataset ) - trans.app.model.flush() - return trans.show_ok_message( "Attributes updated" ) - elif p.detect: - # The user clicked the Auto-detect button on the 'Edit Attributes' form - for name, spec in dataset.datatype.metadata_spec.items(): - # We need to be careful about the attributes we are resetting - if name != 'name' and name != 'info' and name != 'dbkey': - if spec.get( 'default' ): - setattr( dataset.metadata,name,spec.unwrap( spec.get( 'default' ), spec )) - dataset.datatype.set_meta( dataset ) - dataset.datatype.after_edit( dataset ) - trans.app.model.flush() - return trans.show_ok_message( "Attributes updated" ) - - dataset.datatype.before_edit( dataset ) - - if "dbkey" in dataset.datatype.metadata_spec and not dataset.metadata.dbkey: - # Copy dbkey into metadata, for backwards compatability - # This looks like it does nothing, but getting the dbkey - # returns the metadata dbkey unless it is None, in which - # case it resorts to the old dbkey. Setting the dbkey - # sets it properly in the metadata - dataset.metadata.dbkey = dataset.dbkey - metadata = list() - # a list of MetadataParemeters - for name, spec in dataset.datatype.metadata_spec.items(): - if spec.visible: - metadata.append( spec.wrap( dataset.metadata.get(name), dataset ) ) - # let's not overwrite the imported datatypes module with the variable datatypes? - ldatatypes = [x for x in trans.app.datatypes_registry.datatypes_by_extension.iterkeys()] - ldatatypes.sort() - return trans.fill_template( "/library/manage_dataset.mako", dataset=dataset, metadata=metadata, - datatypes=ldatatypes, err=None ) - else: - return trans.show_error_message( "Invalid dataset specified" ) + return trans.fill_template( '/library/library.mako', library=trans.app.model.Library.get( library_id ) ) + return trans.fill_template( '/library/libraries.mako', libraries=trans.app.model.Library.select() ) diff --git a/templates/admin/dataset_security/group_create.mako b/templates/admin/dataset_security/group_create.mako new file mode 100644 index 00000000000..28596e03fb3 --- /dev/null +++ b/templates/admin/dataset_security/group_create.mako @@ -0,0 +1,96 @@ +<%inherit file="/base.mako"/> + +<% + from galaxy.web.controllers.admin import entities, unentities + from xml.sax.saxutils import escape, unescape +%> + +<%def name="title()">Create Group +
    +
    + Libraries  |   + Groups  |   + Users +
    +

    Create Group

    + + %if msg: + + %endif + + + +

    ${msg}

    + + + + + + %if len( users ) == 0: + + %else: + + + + + + + %else: + + %endif + + <% ctr += 1 %> + %endfor + + + %endif + + +
    Name:   Priority:
    There are no Galaxy users
    Add Members to Group - Quick Find
    + |A|B|C|D|E|F + |G|H|I|J|K|L + |M|N|O|P|Q|R + |S|T|U|V|W|X + |Y|Z +
    + <% + ctr = 0 + anchors = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'] + anchor_loc = 0 + anchored = False + curr_anchor = 'A' + %> + %for user in users: + <% email = unescape( user[1], unentities ) %> + %if not email.upper().startswith( curr_anchor ): + <% anchored = False %> + %endif + %if ctr % 2 == 1: +
    + %if email.upper().startswith( curr_anchor ): + %if not anchored: +

    + <% anchored = True %> + %endif + ${email} + %else: + %for anchor in anchors[ anchor_loc: ]: + %if email.upper().startswith( anchor ): + %if not anchored: +

    + <% + curr_anchor = anchor + anchored = True + %> + %endif + ${email} + <% + anchor_loc = anchors.index( anchor ) + break + %> + %endif + %endfor + %endif +
    +
    +
    diff --git a/templates/admin/dataset_security/group_dataset_permitted_actions_edit.mako b/templates/admin/dataset_security/group_dataset_permitted_actions_edit.mako new file mode 100644 index 00000000000..58406d7edbf --- /dev/null +++ b/templates/admin/dataset_security/group_dataset_permitted_actions_edit.mako @@ -0,0 +1,71 @@ +<%inherit file="/base.mako"/> + +<% + from galaxy.web.controllers.admin import entities, unentities + from xml.sax.saxutils import escape, unescape +%> + +<% gn = unescape( group_name, unentities ) %> + +<%def name="title()">Permitted Actions on Datasets +
    +
    + Libraries  |   + Groups  |   + Users +
    +

    Manage Permitted Actions on Datasets for Group '${gn}'

    + + %if msg: + + %endif + + %if len( gdas ) == 0: + + %else: + + + + + + <% ctr = 0 %> + + %for gda in gdas: + %if ctr % 2 == 1: + + %else: + + %endif + + + + + <% ctr += 1 %> + %endfor + + + %endif +

    ${msg}

     
    There is no Galaxy group named '${gn}'
    GroupPriorityPermitted Actions on Datasets
    ${gn}${gda[0]} + %for da in dataset_actions: + <% check = False %> + %for action in gda[1]: + %if action == da: + <% + check = True + break + %> + %endif + %endfor + %if check: + + %else: + + %endif + ${da}
    + %endfor +
    +
    +
    diff --git a/templates/admin/dataset_security/group_members.mako b/templates/admin/dataset_security/group_members.mako new file mode 100644 index 00000000000..36e407959f6 --- /dev/null +++ b/templates/admin/dataset_security/group_members.mako @@ -0,0 +1,47 @@ +<%inherit file="/base.mako"/> + +<% + from galaxy.web.controllers.admin import entities, unentities + from xml.sax.saxutils import escape, unescape +%> + +<% gn = unescape( group_name, unentities ) %> + +<%def name="title()">Create Group +
    +
    + Libraries  |   + Groups  |   + Users +
    + +
    Members of Group '${gn}'
    + + %if msg: + + %endif + + %if len( members ) == 0: + + %else: + <% ctr = 0 %> + %for member in members: + <% email = unescape( member[1], unentities ) %> + %if ctr % 2 == 1: + + %else: + + %endif + + + <% ctr += 1 %> + %endfor + %endif +

    ${msg}

     
    Group '${gn}' contains no members
    ${email}
    +
    diff --git a/templates/admin/dataset_security/group_members_edit.mako b/templates/admin/dataset_security/group_members_edit.mako new file mode 100644 index 00000000000..c7defaa7697 --- /dev/null +++ b/templates/admin/dataset_security/group_members_edit.mako @@ -0,0 +1,114 @@ +<%inherit file="/base.mako"/> + +<% + from galaxy.web.controllers.admin import entities, unentities + from xml.sax.saxutils import escape, unescape +%> + +<%def name="title()">Manage Group Membership +
    +
    + Libraries  |   + Groups  |   + Users +
    + + %if msg: + + %endif + <% gn = unescape( group_name, unentities ) %> + + + + +

    ${msg}

     
    + + + %if len( users ) == 0: + + %else: + + + + + + + %else: + + %endif + + <% ctr += 1 %> + %endfor + + + %endif + + +
    There are no Galaxy users
    Members of '${gn}' - Quick Find
    + |A|B|C|D|E|F + |G|H|I|J|K|L + |M|N|O|P|Q|R + |S|T|U|V|W|X + |Y|Z +
    + <% + ctr = 0 + anchors = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'] + anchor_loc = 0 + anchored = False + curr_anchor = 'A' + %> + %for user in users: + <% + email = unescape( user[1], unentities ) + check = False + %> + %for member in members: + <% member_email = unescape( member[1], unentities ) %> + %if email == member_email: + <% + check = True + break + %> + %endif + %endfor + %if not email.upper().startswith( curr_anchor ): + <% anchored = False %> + %endif + %if ctr % 2 == 1: +
    + %if email.upper().startswith( curr_anchor ): + %if not anchored: +

    + <% anchored = True %> + %endif + %if check: + ${email} + %else: + ${email} + %endif + %else: + %for anchor in anchors[ anchor_loc: ]: + %if email.upper().startswith( anchor ): + %if not anchored: +

    + <% + curr_anchor = anchor + anchored = True + %> + %endif + %if check: + ${email} + %else: + ${email} + %endif + <% + anchor_loc = anchors.index( anchor ) + break + %> + %endif + %endfor + %endif +
    +
    +
    diff --git a/templates/admin/dataset_security/groups.mako b/templates/admin/dataset_security/groups.mako new file mode 100644 index 00000000000..0687802932e --- /dev/null +++ b/templates/admin/dataset_security/groups.mako @@ -0,0 +1,67 @@ +<%inherit file="/base.mako"/> + +<% + from galaxy.web.controllers.admin import entities, unentities + from xml.sax.saxutils import escape, unescape +%> + +<%def name="title()">Groups +
    +
    + Libraries  |   + Users +
    + +

    Groups

    + + %if msg: + + %endif + %if len( groups ) == 0: + + %else: + + + + + + + + + <% ctr = 0 %> + %for group in groups: + <% group_name = unescape( group[1], unentities ) %> + %if ctr % 2 == 1: + + %else: + + %endif + + + + %if group[4] > 0: + + %else: + + %endif + + + + <% ctr += 1 %> + %endfor + %endif +

    ${msg}

    There are no Galaxy groups
    GroupPriorityMembersDatasetsGroup Permitted Actions on Datasets 
    ${group_name}${group[2]}${group[3]}${group[4]}${group[4]} + %if len( group[5] ) == 1: + ${group[5][0]} + %elif len( group[5] ) > 1: + %for da in group[5]: + ${da}
    + %endfor + %endif +
    Mark group deleted
    +
    diff --git a/templates/admin/dataset_security/index.mako b/templates/admin/dataset_security/index.mako new file mode 100644 index 00000000000..1a013ec1fc6 --- /dev/null +++ b/templates/admin/dataset_security/index.mako @@ -0,0 +1,13 @@ +<%inherit file="/base.mako"/> + +<%def name="title()">Dataset Security +
    +
    Dataset Security
    + + %if msg: + + %endif + + +

    ${msg}

    Groups
    Users
    +
    diff --git a/templates/admin/dataset_security/specified_users_groups.mako b/templates/admin/dataset_security/specified_users_groups.mako new file mode 100644 index 00000000000..8a392a67e31 --- /dev/null +++ b/templates/admin/dataset_security/specified_users_groups.mako @@ -0,0 +1,56 @@ +<%inherit file="/base.mako"/> + +<% + from galaxy.web.controllers.admin import entities, unentities + from xml.sax.saxutils import escape, unescape +%> + +<% email = unescape( user_email, unentities ) %> + +<%def name="title()">Create Group +
    +
    + Libraries  |   + Groups  |   + Users +
    +

    Groups of which '${email}' is a member

    + + %if msg: + + %endif + %if len( groups ) == 0: + + %else: + + + + + + + <% ctr = 0 %> + %for group in groups: + <% gn = unescape( group[1], unentities ) %> + %if ctr % 2 == 1: + + %else: + + %endif + + + %if group[3] > 0: + + %else: + + %endif + + + <% ctr += 1 %> + %endfor + %endif +

    ${msg}

    User '${email}' belongs to no groups
    GroupPriorityDatasetsPermitted Actions on Datasets
    ${gn}${group[2]}${group[3]}${group[3]} + %for da in group[4]: + ${da}
    + %endfor +
    +
    diff --git a/templates/admin/dataset_security/users.mako b/templates/admin/dataset_security/users.mako new file mode 100644 index 00000000000..22abae62d54 --- /dev/null +++ b/templates/admin/dataset_security/users.mako @@ -0,0 +1,80 @@ +<%inherit file="/base.mako"/> + +<% + from galaxy.web.controllers.admin import entities, unentities + from xml.sax.saxutils import escape, unescape +%> + +<%def name="title()">Users +
    +
    + Groups  |   + Libraries +
    + + %if msg: + + %endif + + %if len( users ) == 0: + + %else: + + + + + <% + ctr = 0 + anchors = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'] + anchor_loc = 0 + anchored = False + curr_anchor = 'A' + %> + %for user in users: + <% email = unescape( user[1], unentities ) %> + %if not email.upper().startswith( curr_anchor ): + <% anchored = False %> + %endif + %if ctr % 2 == 1: + + %else: + + %endif + + <% ctr += 1 %> + %endfor + + %endif +

    ${msg}

     
    There are no Galaxy users
    Galaxy Users - Quick Find
    + |A|B|C|D|E|F + |G|H|I|J|K|L + |M|N|O|P|Q|R + |S|T|U|V|W|X + |Y|Z +
    + %if email.upper().startswith( curr_anchor ): + %if not anchored: +

    + <% anchored = True %> + %endif + ${email} + %else: + %for anchor in anchors[ anchor_loc: ]: + %if email.upper().startswith( anchor ): + %if not anchored: +

    + <% + curr_anchor = anchor + anchored = True + %> + %endif + ${email} + <% + anchor_loc = anchors.index( anchor ) + break + %> + %endif + %endfor + %endif +
    +
    diff --git a/templates/admin/index.mako b/templates/admin/index.mako new file mode 100644 index 00000000000..84e90c84b55 --- /dev/null +++ b/templates/admin/index.mako @@ -0,0 +1,13 @@ +<%inherit file="/base.mako"/> + +
    +

    Galaxy Administration

    + + %if msg: + + %endif + + + +

    ${msg}

    Dataset Security
    Libraries
    Reload a tool while the Galaxy server is running
    +
    diff --git a/templates/admin/library/dataset.mako b/templates/admin/library/dataset.mako new file mode 100644 index 00000000000..52a35099045 --- /dev/null +++ b/templates/admin/library/dataset.mako @@ -0,0 +1,85 @@ +<%inherit file="/base.mako"/> + +<%def name="title()">Edit Dataset Attributes +<%def name="datatype( dataset, datatypes )"> + + +
    +
    Edit Attributes
    +
    +
    + +
    + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    + %for element in metadata: +
    + +
    + ${element.get_html()} +
    +
    +
    + %endfor +
    + +
    +
    +
    + +
    + +
    +
    + This will inspect the dataset and attempt to correct the above column values + if they are not accurate. +
    +
    +
    +
    +

    +

    +
    Change data type
    +
    +
    + +
    + +
    + ${datatype( dataset, datatypes )} +
    +
    + This will change the datatype of the existing dataset + but not modify its contents. Use this if Galaxy + has incorrectly guessed the type of your dataset. +
    +
    +
    +
    + +
    +
    +
    +
    +manage containing folder +

    diff --git a/templates/admin/library/folder.mako b/templates/admin/library/folder.mako new file mode 100644 index 00000000000..b4b11c74b36 --- /dev/null +++ b/templates/admin/library/folder.mako @@ -0,0 +1,158 @@ +<%inherit file="/base.mako"/> + +<%def name="render_component( component )"> + <% + if isinstance( component, trans.app.model.LibraryFolder ): + return render_folder( component ) + elif isinstance( component, trans.app.model.LibraryFolderDatasetAssociation ): + return render_dataset( component ) + %> + +## Render the dataset `data` as history item, using `hid` as the displayed id +<%def name="render_dataset( data )"> + <% + if data.state in ['no state','',None]: + data_state = "queued" + else: + data_state = data.state + %> +

    +
    ${data.display_name()}
    +
    +
    + ## Header row for history items (name, state, action buttons) +
    + %if data_state != 'ok': +
    + %endif +
    +
    + edit attributes +
    + ##${data.display_name()} +
    + ## Body for history items, extra info and actions, data "peek" +
    + %if data_state == "queued": +
    Job is waiting to run
    + %elif data_state == "running": +
    Job is currently running
    + %elif data_state == "error": +
    + An error occurred running this job: ${data.display_info().strip()}, + report this error +
    + %elif data_state == "empty": +
    No data: ${data.display_info()}
    + %elif data_state == "ok": +
    + ${data.blurb}, + format: ${data.ext}, + database: + %if data.dbkey == '?': + ${data.dbkey} + %else: + ${data.dbkey} + %endif +
    +
    Info: ${data.display_info()}
    + %if data.peek != "no peek": +
    ${data.display_peek()}
    + %endif + %else: +
    Error: unknown dataset state "${data_state}".
    + %endif + ## Recurse for child datasets +
    +
    +
    + +## Render a folder +<%def name="render_folder( this_folder )"> +
    +
    Contents of Folder: ${this_folder.name}
    +
    +
    + <% + components = list( this_folder.folders ) + list( this_folder.datasets ) + components = [ ( getattr( components[i], "order_id" ), i, components [i] ) for i in xrange( len( components ) ) ] + components.sort() + components = [ tup[-1] for tup in components ] + %> + %for component in components: + ${render_component( component )} + %endfor +
    +
    + +
    +
    +
    + +<%def name="title()">Manage Folder: ${folder.name} +
    +
    + Libraries  |   + Groups  |   + Users +
    +
    Change Folder Attributes
    +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    Manage Folder Contents: ${folder.name}
    +
    +
    + %if folder.parent: + Up a Level + %elif folder.library_root: + Manage Library + %endif +
    +
    +
    + ${render_folder( folder )} +
    +
    +
    +
    diff --git a/templates/admin/library/libraries.mako b/templates/admin/library/libraries.mako new file mode 100644 index 00000000000..7e44442e1aa --- /dev/null +++ b/templates/admin/library/libraries.mako @@ -0,0 +1,19 @@ +<%inherit file="/base.mako"/> + +<%def name="title()">Libraries +
    +
    + Groups  |   + Users +
    + +
    +
    Galaxy Libraries
    +
    + %for library in libraries: + + %endfor +
    +
    diff --git a/templates/admin/library/library.mako b/templates/admin/library/library.mako new file mode 100644 index 00000000000..5d979af8abb --- /dev/null +++ b/templates/admin/library/library.mako @@ -0,0 +1,35 @@ +<%inherit file="/base.mako"/> + +<%def name="title()">Library +
    +
    + Libraries  |   + Groups  |   + Users +
    +
    Manage Library '${library.name}'
    +
    +
    + +
    + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    + + +
     
    +
    +
    + diff --git a/templates/admin/library/new_dataset.mako b/templates/admin/library/new_dataset.mako new file mode 100644 index 00000000000..241e121ebeb --- /dev/null +++ b/templates/admin/library/new_dataset.mako @@ -0,0 +1,953 @@ +<%inherit file="/base.mako"/> + +<%def name="title()">Create New Library Dataset +
    +
    + Libraries  |   + Groups  |   + Users +
    +
    Create a new Library Dataset
    +
    +
    + +
    + +
    +
    +
    +
    + +
    +
    + Here you may specify a list of URLs (one per line) or paste the contents of a file. +
    +
    +
    +
    + +
    Yes
    +
    + Use this option if you are entering intervals by hand. +
    +
    +
    +
    + +
    + +
    +
    + Which format? See help below +
    +
    +
    +
    + + ##this should be generated dynamically +
    +
    +
    +
    +
    + +
    +
    +
    +
    diff --git a/templates/admin/reload_tool.mako b/templates/admin/reload_tool.mako new file mode 100644 index 00000000000..1d050ea4764 --- /dev/null +++ b/templates/admin/reload_tool.mako @@ -0,0 +1,28 @@ +<%inherit file="/base.mako"/> + +
    +

    Reload a Tool

    + + %if msg: + + %endif + + + +

    ${msg}

    +
    +

    + Reload tool: + + +

    +
    +
    +
    diff --git a/templates/admin_main.mako b/templates/admin_main.mako deleted file mode 100644 index 02e39576685..00000000000 --- a/templates/admin_main.mako +++ /dev/null @@ -1,39 +0,0 @@ -<%inherit file="/base.mako"/> -<%def name="title()">Galaxy Administration - - - - - - - - - - - - -
    -

    Galaxy Administration

    - %if msg: -

    ${msg}

    - %endif -
    -
    -

    Admin password:

    -

    - Reload tool: - - -

    -
    -
    - Manage Libraries -
    - diff --git a/templates/library/admin_list_libraries.mako b/templates/library/admin_list_libraries.mako deleted file mode 100644 index ffe82787da2..00000000000 --- a/templates/library/admin_list_libraries.mako +++ /dev/null @@ -1,16 +0,0 @@ -<%inherit file="/base.mako"/> -<%def name="title()">View Libraries - -
    -
    Manage Libraries
    -
    - %for library in libraries: - - %endfor - -
    -
    diff --git a/templates/library/libraries.mako b/templates/library/libraries.mako new file mode 100644 index 00000000000..247333f709c --- /dev/null +++ b/templates/library/libraries.mako @@ -0,0 +1,13 @@ +<%inherit file="/base.mako"/> + +<%def name="title()">View Libraries +
    +
    View Library
    +
    + %for library in libraries: + + %endfor +
    +
    diff --git a/templates/library/user_view_library.mako b/templates/library/library.mako similarity index 58% rename from templates/library/user_view_library.mako rename to templates/library/library.mako index 3b948558f28..2706efcde25 100644 --- a/templates/library/user_view_library.mako +++ b/templates/library/library.mako @@ -1,58 +1,44 @@ <%inherit file="/base.mako"/> <%def name="render_component( component )"> - <% + <% if isinstance( component, trans.app.model.LibraryFolder ): - return render_folder( component ) + return render_folder( component ) elif isinstance( component, trans.app.model.LibraryFolderDatasetAssociation ): - return render_dataset( component ) - %> + return render_dataset( component ) + %> - - ## Render the dataset `data` as history item, using `hid` as the displayed id <%def name="render_dataset( data )"> -
    +
    ${data.name} -
    +
    - ## Render a folder <%def name="render_folder( this_folder )"> -
    - Folder: ${this_folder.name} - <% - components = list( this_folder.folders ) + list( this_folder.datasets ) - components = [ ( getattr( components[i], "order_id" ), i, components [i] ) for i in xrange( len( components ) ) ] - components.sort() - components = [ tup[-1] for tup in components ] - %> -
    - %for component in components: - ${render_component( component )} - %endfor -
    + Folder: ${this_folder.name} + <% + components = list( this_folder.folders ) + list( this_folder.datasets ) + components = [ ( getattr( components[i], "order_id" ), i, components [i] ) for i in xrange( len( components ) ) ] + components.sort() + components = [ tup[-1] for tup in components ] + %> +
    + %for component in components: + ${render_component( component )} + %endfor +
    - - - - - - <%def name="title()">View Library: ${library.name} - -
    +
    Import from Library: ${library.name}
    ${render_folder( library.root_folder )} -
    -
    - diff --git a/templates/library/manage_dataset.mako b/templates/library/manage_dataset.mako deleted file mode 100644 index f04615def51..00000000000 --- a/templates/library/manage_dataset.mako +++ /dev/null @@ -1,100 +0,0 @@ -<%inherit file="/base.mako"/> -<%def name="title()">Edit Dataset Attributes - - -<%def name="datatype( dataset, datatypes )"> - - - -
    -
    Edit Attributes
    -
    -
    - -
    - -
    - -
    -
    -
    -
    - -
    - -
    -
    -
    - %for element in metadata: -
    - -
    - ${element.get_html()} -
    -
    -
    - %endfor -
    - -
    -
    -
    - -
    - -
    -
    - This will inspect the dataset and attempt to correct the above column values - if they are not accurate. -
    -
    -
    -
    - -

    - - -

    -
    Change data type
    -
    -
    - -
    - -
    - ${datatype( dataset, datatypes )} -
    - -
    - This will change the datatype of the existing dataset - but not modify its contents. Use this if Galaxy - has incorrectly guessed the type of your dataset. -
    -
    -
    -
    - -
    -
    -
    -
    - -manage containing folder -

    diff --git a/templates/library/manage_folder.mako b/templates/library/manage_folder.mako deleted file mode 100644 index 19e12555625..00000000000 --- a/templates/library/manage_folder.mako +++ /dev/null @@ -1,191 +0,0 @@ -<%inherit file="/base.mako"/> - -<%def name="render_component( component )"> - <% - if isinstance( component, trans.app.model.LibraryFolder ): - return render_folder( component ) - elif isinstance( component, trans.app.model.LibraryFolderDatasetAssociation ): - return render_dataset( component ) - %> - - - -## Render the dataset `data` as history item, using `hid` as the displayed id -<%def name="render_dataset( data )"> - <% - if data.state in ['no state','',None]: - data_state = "queued" - else: - data_state = data.state - %> - ##

    -
    -
    ${data.display_name()}
    -
    -
    - - ## Header row for history items (name, state, action buttons) - -
    - %if data_state != 'ok': -
    - %endif -
    -
    - ##display data - edit attributes - ##delete -
    - ##${data.display_name()} -
    - - ## Body for history items, extra info and actions, data "peek" - -
    - %if data_state == "queued": -
    Job is waiting to run
    - %elif data_state == "running": -
    Job is currently running
    - %elif data_state == "error": -
    - An error occurred running this job: ${data.display_info().strip()}, - report this error -
    - %elif data_state == "empty": -
    No data: ${data.display_info()}
    - %elif data_state == "ok": -
    - ${data.blurb}, - format: ${data.ext}, - database: - %if data.dbkey == '?': - ${data.dbkey} - %else: - ${data.dbkey} - %endif -
    -
    Info: ${data.display_info()}
    - %if data.peek != "no peek": -
    ${data.display_peek()}
    - %endif - %else: -
    Error: unknown dataset state "${data_state}".
    - %endif - - ## Recurse for child datasets - - -
    -
    -
    - - -## Render a folder -<%def name="render_folder( this_folder )"> - -
    -
    Contents of Folder: ${this_folder.name}
    -
    -
    -
    - <% - components = list( this_folder.folders ) + list( this_folder.datasets ) - components = [ ( getattr( components[i], "order_id" ), i, components [i] ) for i in xrange( len( components ) ) ] - components.sort() - components = [ tup[-1] for tup in components ] - %> - %for component in components: - ${render_component( component )} - %endfor - -
    -
    -
    - -
    - -
    -
    - - - - - - -<%def name="title()">Manage Folder: ${folder.name} - -
    -
    Change Folder Attributes
    -
    -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    - - - -
    - -
    -
    Manage Folder Contents: ${folder.name}
    -
    -
    - %if folder.parent: - up a level - %elif folder.library_root: - manage library - %endif -
    -
    -
    - ${render_folder( folder )} -
    - -
    - -
    - -
    -
    -
    - diff --git a/templates/library/manage_library.mako b/templates/library/manage_library.mako deleted file mode 100644 index 5d94dfb6b78..00000000000 --- a/templates/library/manage_library.mako +++ /dev/null @@ -1,46 +0,0 @@ -<%inherit file="/base.mako"/> -<%def name="title()">Manage Library - - -
    -
    Edit a Library: ${library.name}
    -
    -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - -
    - - -
    - -
    - - -
    - -
    - -
    - diff --git a/templates/library/new_dataset.mako b/templates/library/new_dataset.mako deleted file mode 100644 index 1c517cdfede..00000000000 --- a/templates/library/new_dataset.mako +++ /dev/null @@ -1,997 +0,0 @@ -<%inherit file="/base.mako"/> -<%def name="title()">Create New Library Dataset - -
    -
    Create a new Library Dataset
    -
    -
    - - - -
    - - -
    - - -
    - - -
    - -
    - - -
    - -
    - Here you may specify a list of URLs (one per line) or paste the contents of a file. -
    - - - -
    - -
    - -
    - - -
    Yes
    - -
    - - Use this option if you are entering intervals by hand. -
    - - -
    - -
    - -
    - - -
    - -
    - Which format? See help below -
    - - -
    - -
    - -
    - - -##this should be generated dynamically -
    - - - -
    - -
    - - -
    - -
    - - -
    -
    -
    - diff --git a/templates/library/user_list_libraries.mako b/templates/library/user_list_libraries.mako deleted file mode 100644 index 23a2b3b9ad1..00000000000 --- a/templates/library/user_list_libraries.mako +++ /dev/null @@ -1,14 +0,0 @@ -<%inherit file="/base.mako"/> -<%def name="title()">View Libraries - -
    -
    View Library
    -
    - %for library in libraries: - - %endfor - -
    -
    diff --git a/templates/root/masthead.mako b/templates/root/masthead.mako index 58d8f1776db..097d7707acc 100644 --- a/templates/root/masthead.mako +++ b/templates/root/masthead.mako @@ -20,6 +20,9 @@ | wiki | screencasts | blog + %if admin_user == "true": + | admin + %endif     diff --git a/universe_wsgi.ini.sample b/universe_wsgi.ini.sample index 6dcdf061f94..58ca578eb15 100644 --- a/universe_wsgi.ini.sample +++ b/universe_wsgi.ini.sample @@ -77,8 +77,8 @@ use_lint = false # NEVER enable this on a public site (even test or QA) use_interactive = true -# Admin Password -admin_pass = galaxy +# Admin Users - this should be a comma-separated list of valid Galaxy users +#admin_users = user1@bx.psu.edu,user2@bx.psu.edu # path to sendmail sendmail_path = /usr/sbin/sendmail From d392b887ec32dd4ba4e76fc2d8b57e780e8b5e43 Mon Sep 17 00:00:00 2001 From: Nate Coraor Date: Thu, 14 Aug 2008 17:44:06 -0400 Subject: [PATCH 15/21] Fix a permitted_actions bug, and re-enable the ability for a user to make a single dataset private or public. And fix a few typos. --- lib/galaxy/security/__init__.py | 20 ++++++++++++++++---- lib/galaxy/web/controllers/async.py | 2 +- lib/galaxy/web/controllers/root.py | 17 +++++++---------- templates/dataset/edit_attributes.mako | 10 ++++------ templates/root/history_common.mako | 2 +- 5 files changed, 29 insertions(+), 22 deletions(-) diff --git a/lib/galaxy/security/__init__.py b/lib/galaxy/security/__init__.py index ed73ec2364e..7709c5a1916 100644 --- a/lib/galaxy/security/__init__.py +++ b/lib/galaxy/security/__init__.py @@ -81,9 +81,8 @@ class GalaxyRBACAgent( RBACAgent ): # Check permitted_actions associated with dataset through groups for group_dataset_assoc in dataset.groups: if self.components_are_associated( user = user, group = group_dataset_assoc.group ): - for pa in group_dataset_assoc.permitted_actions: - if action in pa.permitted_actions.actions: - return True + if action in group_dataset_assoc.permitted_actions: + return True return False # No user and dataset not in public group, or user lacks permission def guess_derived_groups_for_datasets( self, datasets=[] ): # TODO, Nate: Make sure this method is functionally correct. @@ -132,6 +131,12 @@ class GalaxyRBACAgent( RBACAgent ): if 'group' in kwd: return self.associate_user_group( kwd['user'], kwd['group'] ) raise 'No valid method of associating provided components: %s' % kwd + def disassociate_components( self, **kwd ): + assert len( kwd ) == 2, 'You must specify exactly 2 Galaxy security components to disassociate.' + if 'dataset' in kwd: + if 'group' in kwd: + return self.disassociate_group_dataset( kwd['group'], kwd['dataset'] ) + raise 'No valid method of associating provided components: %s' % kwd def associate_group_dataset( self, group, dataset, permitted_actions=[] ): # TODO, Nate: Make sure this method is functionally correct. # TODO: For now, just take the dataset's permitted_actions, but we need to make sure @@ -145,6 +150,11 @@ class GalaxyRBACAgent( RBACAgent ): assoc = self.model.GroupDatasetAssociation( group, dataset, permitted_actions ) assoc.flush() return assoc + def disassociate_group_dataset( self, group, dataset ): + log.debug("In disassociate_group_dataset, removing %s -> %s" % (group.id, dataset.id)) + assoc = self.model.GroupDatasetAssociation.selectone_by( group_id = group.id, dataset_id = dataset.id ) + assoc.delete() + assoc.flush() def associate_user_group( self, user, group ): assoc = self.model.UserGroupAssociation( user, group ) assoc.flush() @@ -182,7 +192,7 @@ class GalaxyRBACAgent( RBACAgent ): # TODO, Nate: Make sure this method is functionally correct with permitted actions set appropriately. if groups is None: if history.user: - groups = history.user.default_groups + groups = [ assoc.group for assoc in history.user.default_groups ] else: groups = [ self.get_public_group() ] if groups is not None: @@ -234,6 +244,8 @@ class GalaxyRBACAgent( RBACAgent ): if 'group' in kwd: return self.model.UserGroupAssociation.get_by( group_id = kwd['group'].id, user_id = kwd['user'].id ) raise 'No valid method of associating provided components: %s' % kwd + def dataset_has_group( self, dataset_id, group_id ): + return bool( self.model.GroupDatasetAssociation.get_by( group_id = group_id, dataset_id = dataset_id ) ) def get_permitted_actions( self, filter=None ): '''Utility method to return a subset of RBACAgent's permitted actions''' diff --git a/lib/galaxy/web/controllers/async.py b/lib/galaxy/web/controllers/async.py index 004868434d8..dcb6a0c1be5 100644 --- a/lib/galaxy/web/controllers/async.py +++ b/lib/galaxy/web/controllers/async.py @@ -60,7 +60,7 @@ class ASync( BaseController ): if STATUS == 'OK': key = hmac.new( trans.app.config.tool_secret, "%d:%d" % ( data.id, data.history_id), sha ).hexdigest() if key != data_secret: - return "You do not have permision to alter data %s." % data_id + return "You do not have permission to alter data %s." % data_id # push the job into the queue data.state = data.blurb = data.states.RUNNING log.debug('executing tool %s' % tool.id) diff --git a/lib/galaxy/web/controllers/root.py b/lib/galaxy/web/controllers/root.py index 8841c46eedd..dc58f02f2ee 100644 --- a/lib/galaxy/web/controllers/root.py +++ b/lib/galaxy/web/controllers/root.py @@ -262,30 +262,27 @@ class RootController( BaseController ): if target_type: msg = data.datatype.convert_dataset(trans, data, target_type) return trans.show_ok_message( msg, refresh_frames=['history'] ) - ''' - # Users can't currently change permissions or groups - elif p.change_permision: - """The user clicked the change_permision button on the 'Change permissions' form""" + elif p.change_permission: + """The user clicked the change_permission button on the 'Change permissions' form""" if not trans.user: return trans.show_error_message( "You must be logged in if you want to change dataset permitted actions." ) private_dataset = 'private_dataset' public_group = trans.app.security_agent.get_public_group() - if private_dataset in kwd and data.dataset.has_group( public_group ): + if private_dataset in kwd and trans.app.security_agent.dataset_has_group( data.dataset.id, public_group.id ): #check user has permission and then remove public group - if trans.app.security_agent.allow_action( trans.user, data.dataset.permitted_actions.REMOVE_GROUP, dataset = data.dataset ): - trans.app.security_agent.remove_component_association( dataset = data, group = public_group ) + if trans.app.security_agent.allow_action( trans.user, data.dataset.permitted_actions.DATASET_MANAGE_PERMISSIONS, dataset = data.dataset ): + trans.app.security_agent.disassociate_components( dataset = data, group = public_group ) else: return trans.show_error_message( "You are not authorized to change this dataset's permitted actions." ) - elif private_dataset not in kwd and not data.dataset.has_group( public_group ): + elif private_dataset not in kwd and not trans.app.security_agent.dataset_has_group( data.dataset.id, public_group.id ): #check user has permission and then add public group - if trans.app.security_agent.allow_action( trans.user, data.dataset.permitted_actions.ADD_GROUP, dataset = data.dataset ): + if trans.app.security_agent.allow_action( trans.user, data.dataset.permitted_actions.DATASET_MANAGE_PERMISSIONS, dataset = data.dataset ): trans.app.security_agent.associate_components( dataset = data, group = public_group) else: return trans.show_error_message( "You are not authorized to change this dataset's permitted actions." ) else: return trans.show_error_message( "You have not specified a valid change of permitted actions." ) return trans.show_ok_message( 'Permitted actions have been changed.', refresh_frames=['history'] ) - ''' data.datatype.before_edit( data ) diff --git a/templates/dataset/edit_attributes.mako b/templates/dataset/edit_attributes.mako index 1479b15c20a..c54f6eb0d1a 100644 --- a/templates/dataset/edit_attributes.mako +++ b/templates/dataset/edit_attributes.mako @@ -133,19 +133,18 @@

    -<%doc> -%if trans.app.config.enable_beta_features and trans.user and ( trans.app.security_agent.allow_action( trans.user, data.permitted_actions.REMOVE_GROUP, dataset = data ) or trans.app.security_agent.allow_action( trans.user, data.permitted_actions.ADD_GROUP, dataset = data ) ): +%if trans.app.config.enable_beta_features and trans.user and ( trans.app.security_agent.allow_action( trans.user, data.permitted_actions.DATASET_MANAGE_PERMISSIONS, dataset = data ) ):

    Change Permitted Actions
    -
    +
    <% checked = "" %> - %if not data.dataset.has_group( trans.app.model.Group.get_public_group() ): + %if not trans.app.security_agent.dataset_has_group( data.id, trans.app.model.Group.get_public_group().id ): <% checked = " checked" %> %endif
    @@ -158,10 +157,9 @@
    - +
    %endif - diff --git a/templates/root/history_common.mako b/templates/root/history_common.mako index aa9a5c238a5..cd3225e7994 100644 --- a/templates/root/history_common.mako +++ b/templates/root/history_common.mako @@ -33,7 +33,7 @@
    %if not trans.app.security_agent.allow_action( trans.user, data.permitted_actions.DATASET_ACCESS, dataset = data.dataset ): -
    You do not have permision to view this dataset.
    +
    You do not have permission to view this dataset.
    %elif data_state == "queued":
    Job is waiting to run
    %elif data_state == "running": From a22b2ba86d82f1c7f16f57506d9e8bbe6c2f7e34 Mon Sep 17 00:00:00 2001 From: Nate Coraor Date: Thu, 14 Aug 2008 18:19:38 -0400 Subject: [PATCH 16/21] Default to only adding datasets to a user's private group, and ensure externally authenticated users get their defaults set up. --- lib/galaxy/security/__init__.py | 2 +- lib/galaxy/web/framework/__init__.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/galaxy/security/__init__.py b/lib/galaxy/security/__init__.py index 7709c5a1916..81db90994b6 100644 --- a/lib/galaxy/security/__init__.py +++ b/lib/galaxy/security/__init__.py @@ -171,7 +171,7 @@ class GalaxyRBACAgent( RBACAgent ): def user_set_default_access( self, user, groups = None, history = False, dataset = False ): # TODO, Nate: Make sure this method is functionally correct with permitted actions set appropriately. if groups is None: - groups = [ self.get_public_group(), self.create_private_user_group( user ) ] + groups = [ self.create_private_user_group( user ) ] if groups is not None: for assoc in user.default_groups: #this is the association not the actual group assoc.delete() diff --git a/lib/galaxy/web/framework/__init__.py b/lib/galaxy/web/framework/__init__.py index 124d1549453..87f099a681d 100644 --- a/lib/galaxy/web/framework/__init__.py +++ b/lib/galaxy/web/framework/__init__.py @@ -272,6 +272,7 @@ class UniverseWebTransaction( base.DefaultWebTransaction ): user.set_password_cleartext( 'external' ) user.external = True user.flush() + self.app.security_agent.setup_new_user( user ) self.log_event( "Automatically created account '%s'" % user.email ) return user def get_cookie_user( self ): From 849a462b5c6c3631e3e3c12c09521a4cf50dd722 Mon Sep 17 00:00:00 2001 From: Greg Von Kuster Date: Fri, 15 Aug 2008 09:07:02 -0400 Subject: [PATCH 17/21] Fix for sharing a history with another user, still need to apply security checks. --- lib/galaxy/model/__init__.py | 71 ++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 11 deletions(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 460b6433b1d..599ab72a78b 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -417,7 +417,12 @@ class DatasetInstance( object ): child.mark_deleted() class HistoryDatasetAssociation( DatasetInstance ): - def __init__( self, hid = None, history = None, copied_from_history_dataset_association = None, copied_from_library_folder_dataset_association = None, **kwd ): + def __init__( self, + hid = None, + history = None, + copied_from_history_dataset_association = None, + copied_from_library_folder_dataset_association = None, + **kwd ): DatasetInstance.__init__( self, **kwd ) self.hid = hid # Relationships @@ -426,10 +431,19 @@ class HistoryDatasetAssociation( DatasetInstance ): self.copied_from_library_folder_dataset_association = copied_from_library_folder_dataset_association def copy( self, copy_children = False, parent_id = None ): - print "self.dataset", self.dataset - - 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 ) - print "des data", des.dataset + 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: for child in self.children: @@ -501,14 +515,13 @@ class History( object ): des.flush() des.name = self.name for data in self.datasets: - new_data = data.copy( copy_children = True, target_user = target_user ) + new_data = data.copy( copy_children = True ) des.add_dataset( new_data ) new_data.flush() des.hid_counter = self.hid_counter des.flush() return des - class Library( object ): def __init__( self, name = None, description = None, root_folder = None ): self.name = name or "Unnamed library" @@ -531,7 +544,12 @@ class LibraryFolder( object ): self.item_count += 1 class LibraryFolderDatasetAssociation( DatasetInstance ): - def __init__( self, folder = None, order_id = None, copied_from_history_dataset_association = None, copied_from_library_folder_dataset_association = None, **kwd ): + def __init__( self, + folder = None, + order_id = None, + copied_from_history_dataset_association = None, + copied_from_library_folder_dataset_association = None, + **kwd ): DatasetInstance.__init__( self, **kwd ) self.folder = folder self.order_id = order_id @@ -539,7 +557,18 @@ class LibraryFolderDatasetAssociation( DatasetInstance ): self.copied_from_library_folder_dataset_association = copied_from_library_folder_dataset_association def to_history_dataset_association( self, parent_id = None ): - des = HistoryDatasetAssociation( 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_library_folder_dataset_association = self ) + des = HistoryDatasetAssociation( 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_library_folder_dataset_association = self ) des.flush() for child in self.children: child_copy = child.to_history_dataset_association( parent_id = des.id ) @@ -549,7 +578,18 @@ class LibraryFolderDatasetAssociation( DatasetInstance ): def copy( self, copy_children = False, parent_id = None ): - des = LibraryFolderDatasetAssociation( 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_library_folder_dataset_association = self ) + des = LibraryFolderDatasetAssociation( 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_library_folder_dataset_association = self ) des.flush() if copy_children: for child in self.children: @@ -626,7 +666,16 @@ class Event( object ): self.message = message class GalaxySession( object ): - def __init__( self, id=None, user=None, remote_host=None, remote_addr=None, referer=None, current_history_id=None, session_key=None, is_valid=False, prev_session_id=None ): + def __init__( self, + id=None, + user=None, + remote_host=None, + remote_addr=None, + referer=None, + current_history_id=None, + session_key=None, + is_valid=False, + prev_session_id=None ): self.id = id self.user = user self.remote_host = remote_host From 199e3327df08b8ebf497936fdcd29c4fc07d01ce Mon Sep 17 00:00:00 2001 From: Greg Von Kuster Date: Fri, 15 Aug 2008 16:40:13 -0400 Subject: [PATCH 18/21] Feature enhancements and cleanup for dataset security and libraries. 1) DefaultHistoryGroupAssociations are now deleted when a history is deleted. 2) New user's private group name now includes 'private group' 3) When an admin adds a new dataset to a library folder, they can associate it with 1 or more groups and then edit the permitted_actions on the edit page for the dataset. 4) Other miscellaneous cleanup --- lib/galaxy/model/mapping.py | 2 - lib/galaxy/security/__init__.py | 23 +- lib/galaxy/web/controllers/admin.py | 128 +++- lib/galaxy/web/controllers/dataset.py | 5 +- lib/galaxy/web/controllers/root.py | 7 +- templates/admin/library/dataset.mako | 35 + templates/admin/library/folder.mako | 4 +- templates/admin/library/new_dataset.mako | 919 +---------------------- 8 files changed, 184 insertions(+), 939 deletions(-) diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index e6f132693da..2303c9eb338 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -186,7 +186,6 @@ Library.table = Table( "library", metadata, Column( "name", TEXT ), Column( "description", TEXT ) ) - LibraryFolder.table = Table( "library_folder", metadata, Column( "id", Integer, primary_key=True ), Column( "parent_id", Integer, ForeignKey( "library_folder.id" ), nullable = True, index=True ), @@ -217,7 +216,6 @@ LibraryTagDatasetAssociation.table = Table( "library_tag_dataset_association", m 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 ), diff --git a/lib/galaxy/security/__init__.py b/lib/galaxy/security/__init__.py index 81db90994b6..c6790b2d7e3 100644 --- a/lib/galaxy/security/__init__.py +++ b/lib/galaxy/security/__init__.py @@ -21,7 +21,7 @@ class RBACAgent: DATASET_MANAGE_PERMISSIONS = 'dataset_manage_permissions', # The ability to perform any read only operation on the dataset (view, display at external site, # use in a job, etc). - DATASET_ACCESS = 'dataset_access' + DATASET_ACCESSS = 'dataset_access' ) def allow_action( self, user, action, **kwd ): raise 'No valid method of checking action (%s) on %s for user %s.' % ( action, kwd, user ) @@ -67,9 +67,8 @@ class GalaxyRBACAgent( RBACAgent ): 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 ): - # TODO, Nate: Make sure this method is functionally correct. """Returns true when user has permission to perform an action""" - while not isinstance( dataset, self.model.Dataset ): + if 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 permitted_actions is allowed @@ -122,7 +121,6 @@ class GalaxyRBACAgent( RBACAgent ): return rval raise 'No valid method of creating group with %s' % ( kwd ) def associate_components( self, **kwd ): - # TODO, Nate: Make sure this method is functionally correct. assert len( kwd ) == 2, 'You must specify exactly 2 Galaxy security components to associate.' if 'dataset' in kwd: if 'group' in kwd: @@ -138,15 +136,11 @@ class GalaxyRBACAgent( RBACAgent ): return self.disassociate_group_dataset( kwd['group'], kwd['dataset'] ) raise 'No valid method of associating provided components: %s' % kwd def associate_group_dataset( self, group, dataset, permitted_actions=[] ): - # TODO, Nate: Make sure this method is functionally correct. - # TODO: For now, just take the dataset's permitted_actions, but we need to make sure - # we can associate group permitted_actions if necessary - need to look into this... if not permitted_actions: if isinstance( dataset.permitted_actions, Bunch ): permitted_actions = dataset.permitted_actions.__dict__.values() else: permitted_actions = dataset.permitted_actions - log.debug("In associate_group_dataset, permitted_actions: %s" %str(permitted_actions) ) assoc = self.model.GroupDatasetAssociation( group, dataset, permitted_actions ) assoc.flush() return assoc @@ -160,9 +154,9 @@ class GalaxyRBACAgent( RBACAgent ): assoc.flush() return assoc def create_private_user_group( self, user ): - # TODO, Nate: Make sure this method is functionally correct. # Create private group - group = self.model.Group( user.email, priority = 10 ) + group_name = "%s private group" % user.email + group = self.model.Group( name=group_name, priority=10 ) group.flush() # Add user to group self.associate_components( group=group, user=user ) @@ -177,12 +171,10 @@ class GalaxyRBACAgent( RBACAgent ): assoc.delete() assoc.flush() for group in groups: - log.debug("In user_set_default_access, group: %s" %str(group)) if isinstance( group, self.model.Group ): permitted_actions = group.permitted_actions.__dict__.values() else: permitted_actions = group.permitted_actions - log.debug("In user_set_default_access, permitted_actions: %s" % str( permitted_actions)) assoc = self.model.DefaultUserGroupAssociation( user, group, permitted_actions ) assoc.flush() if history: @@ -200,12 +192,10 @@ class GalaxyRBACAgent( RBACAgent ): assoc.delete() assoc.flush() for group in groups: - log.debug("In history_set_default_access, group: %s" %str(group)) if isinstance( group, self.model.Group ): permitted_actions = group.permitted_actions.__dict__.values() else: permitted_actions = group.permitted_actions - log.debug("In history_set_default_access, permitted_actions: %s" % str( permitted_actions)) assoc = self.model.DefaultHistoryGroupAssociation( history, group, permitted_actions ) assoc.flush() if dataset: @@ -223,7 +213,6 @@ class GalaxyRBACAgent( RBACAgent ): def guess_public_group( self ): return self.model.Group.guess_public_group() def set_dataset_groups( self, dataset, groups ): - # TODO, Nate: Make sure this method is functionally correct. if isinstance( dataset, self.model.HistoryDatasetAssociation ): dataset = dataset.dataset for group_dataset_assoc in dataset.groups: @@ -232,7 +221,6 @@ class GalaxyRBACAgent( RBACAgent ): for group in groups: if not isinstance( group, self.model.Group ): group = group.group - log.debug("In set_dataset_groups, before elf.associate_components, dataset: %s, group: %s" % ( str(dataset), str(group))) self.associate_components( dataset=dataset, group=group ) def get_component_associations( self, **kwd ): # TODO, Nate: Make sure this method is functionally correct. @@ -254,6 +242,5 @@ def get_permitted_actions( self, filter=None ): if not filter.endswith('_'): filter += '_' tmp_bunch = Bunch() - [tmp_bunch.__dict__.__setitem__(k, v) for k, v in \ - RBACAgent.permitted_actions.items() if k.startswith(filter)] + [tmp_bunch.__dict__.__setitem__(k, v) for k, v in RBACAgent.permitted_actions.items() if k.startswith(filter)] return tmp_bunch diff --git a/lib/galaxy/web/controllers/admin.py b/lib/galaxy/web/controllers/admin.py index 3367ce6bc36..55f4f850646 100644 --- a/lib/galaxy/web/controllers/admin.py +++ b/lib/galaxy/web/controllers/admin.py @@ -1,5 +1,6 @@ -import shutil, StringIO +import shutil, StringIO, operator +from galaxy import util from galaxy.web.base.controller import * from galaxy.datatypes import sniff from galaxy.security import RBACAgent @@ -540,7 +541,7 @@ class Admin( BaseController ): if not self.user_is_admin( trans ): return trans.show_error_message( no_privilege_msg ) data_files = [] - def add_file( file_obj, name, extension, dbkey, info = 'no info', space_to_tab = False ): + def add_file( file_obj, name, extension, dbkey, groups, info='no info', space_to_tab=False ): data_type = None temp_name = sniff.stream_to_file( file_obj ) if space_to_tab: @@ -555,8 +556,14 @@ class Admin( BaseController ): folder = trans.app.model.LibraryFolder.get( folder_id ) folder.add_dataset( dataset ) dataset.flush() - # TODO, SET SECURTY INTERACTIVELY ON DATASET, right now everything is public - trans.app.security_agent.set_dataset_groups( dataset.dataset, [trans.app.security_agent.get_public_group()] ) + # GroupDatasetAssociations will enable security on the dataset based on the permitted_actions + # associated with the GroupDatasetAssociation. The default permitted_actions at this point + # will be DATASET_ACCESS, but the user can change this after the file is uploaded. + permitted_actions = [ RBACAgent.permitted_actions.DATASET_ACCESS ] + for group_id in groups: + group = galaxy.model.Group.get( group_id ) + group_dataset_assoc = galaxy.model.GroupDatasetAssociation( group, dataset.dataset, permitted_actions ) + group_dataset_assoc.flush() shutil.move( temp_name, dataset.dataset.file_name ) dataset.dataset.state = dataset.dataset.states.OK dataset.init_meta() @@ -575,7 +582,7 @@ class Admin( BaseController ): return dataset if 'create_dataset' in kwd: - #copied from upload tool action + # Copied from upload tool action last_dataset_created = None data_file = kwd['file_data'] url_paste = kwd['url_paste'] @@ -583,6 +590,12 @@ class Admin( BaseController ): if 'space_to_tab' in kwd: if kwd['space_to_tab'] not in ["None", None]: space_to_tab = True + groups = kwd['groups'] + if groups and not isinstance( groups, list ): + # mako sends singleton lists as a string + groups = [ groups ] + if groups is None: + groups = [] temp_name = "" data_list = [] @@ -590,14 +603,26 @@ class Admin( BaseController ): file_name = data_file.filename file_name = file_name.split( '\\' )[-1] file_name = file_name.split( '/' )[-1] - last_dataset_created = add_file( data_file.file, file_name, extension, dbkey, info="uploaded file", space_to_tab = space_to_tab ) + last_dataset_created = add_file( data_file.file, + file_name, + extension, + dbkey, + groups, + info="uploaded file", + space_to_tab=space_to_tab ) elif url_paste not in [ None, "" ]: if url_paste.lower().find( 'http://' ) >= 0 or url_paste.lower().find( 'ftp://' ) >= 0: url_paste = url_paste.replace( '\r', '' ).split( '\n' ) for line in url_paste: line = line.rstrip( '\r\n' ) if line: - last_dataset_created = add_file( urllib.urlopen( line ), line, extension, dbkey, info="uploaded url", space_to_tab=space_to_tab ) + last_dataset_created = add_file( urllib.urlopen( line ), + line, + extension, + dbkey, + groups, + info="uploaded url", + space_to_tab=space_to_tab ) else: is_valid = False for line in url_paste: @@ -606,16 +631,73 @@ class Admin( BaseController ): is_valid = True break if is_valid: - last_dataset_created = add_file( StringIO.StringIO( url_paste ), 'Pasted Entry', extension, dbkey, info="pasted entry", space_to_tab=space_to_tab ) - trans.response.send_redirect( web.url_for( action='dataset', id = last_dataset_created.id ) ) - #return self.dataset( trans, id = last_dataset_created.id ) + last_dataset_created = add_file( StringIO.StringIO( url_paste ), + 'Pasted Entry', + extension, + dbkey, + groups, + info="pasted entry", + space_to_tab=space_to_tab ) + trans.response.send_redirect( web.url_for( action='dataset', id=last_dataset_created.id ) ) elif id is None: - return trans.fill_template( '/admin/library/new_dataset.mako', folder_id = folder_id ) + # Send list of data formats to the form so the "extension" select list can be populated dynamically + file_formats = trans.app.datatypes_registry.upload_file_formats + # Send list of genome builds to the form so the "dbkey" select list can be populated dynamically + def get_dbkey_options(): + last_used_build = trans.history.genome_build + for dbkey, build_name in util.dbnames: + yield build_name, dbkey, ( dbkey==last_used_build ) + dbkeys = get_dbkey_options() + # Send list of groups to the form so the dataset can be associated with 1 or more of them. + groups = [] + q = sa.select( ( ( galaxy.model.Group.table.c.id ).label( 'group_id' ), + ( galaxy.model.Group.table.c.name ).label( 'group_name' ) ), + order_by = [ galaxy.model.Group.table.c.name ] ) + for row in q.execute(): + groups.append( ( row.group_id, row.group_name ) ) + groups = sorted( groups, key=operator.itemgetter(1) ) + return trans.fill_template( '/admin/library/new_dataset.mako', + folder_id=folder_id, + file_formats=file_formats, + dbkeys=dbkeys, + groups=groups ) dataset = trans.app.model.LibraryFolderDatasetAssociation.get( id ) if dataset: - #copied from edit attributes for 'regular' datasets + # Copied from edit attributes for 'regular' datasets with some additions p = util.Params(kwd, safe=False) - if p.change: + if p.change_permitted_actions: + # The user clicked the Save button on the 'Group Associations' form + actions = p.actions + if actions and not isinstance( actions, list ): + actions = [ actions ] + if actions is None: + actions = [] + # actions is a list of comma-separated strings consisting of group_id and permitted_action, + # something like: ['6,dataset_access', '6,dataset_edit_metadata']. We'll parse them and + # create a dict whose keys are groups_id and values are permitted_actions + gdpa_dict = {} + for action in actions: + group_id, dpa = action.split( ',' ) + group_id = int( group_id ) + if group_id in gdpa_dict.keys(): + gdpa_dict[ group_id ].append( dpa ) + else: + gdpa_dict[ group_id ] = [ dpa ] + # Check to see if we need to delete any GroupDatasetAssociations. This occurs if + # the user unchecked all boxes for a group + for group_dataset_assoc in dataset.dataset.groups: + if group_dataset_assoc.group_id not in gdpa_dict.keys(): + group_dataset_assoc.delete() + group_dataset_assoc.flush() + # Use the dict to update the permitted actions for each GroupDatasetAssociaton + for group_id in gdpa_dict: + actions = gdpa_dict[ group_id ] + # Update the permitted_actions for every GroupDatasetAssociation of the Group + q = sa.update( galaxy.model.GroupDatasetAssociation.table, + whereclause = galaxy.model.GroupDatasetAssociation.table.c.group_id == group_id, + values = { galaxy.model.GroupDatasetAssociation.table.c.permitted_actions : actions } ) + result = q.execute() + elif p.change: # The user clicked the Save button on the 'Change data type' form trans.app.datatypes_registry.change_datatype( dataset, p.datatype ) trans.app.model.flush() @@ -623,7 +705,6 @@ class Admin( BaseController ): # The user clicked the Save button on the 'Edit Attributes' form dataset.name = name dataset.info = info - # The following for loop will save all metadata_spec items for name, spec in dataset.datatype.metadata_spec.items(): if spec.get("readonly"): @@ -651,7 +732,20 @@ class Admin( BaseController ): return trans.show_ok_message( "Attributes updated" ) dataset.datatype.before_edit( dataset ) - + # Get all actions to send to the form + dataset_actions = [] + dpas = RBACAgent.permitted_actions + for dpa in dpas.items(): + if dpa[0].startswith( 'DATASET' ): + dataset_actions.append( dpa[1] ) + dataset_actions.sort() + # Get the permitted_actions of each GroupDatasetAssociation to send to the form + gdas = [] + # Refresh the dataset to ensure we have a valid set of DatasetGroupAssociations + dataset.dataset.refresh() + for group_dataset_assoc in dataset.dataset.groups: + group = galaxy.model.Group.get( group_dataset_assoc.group_id ) + gdas.append( ( group.id, group.name, group_dataset_assoc.permitted_actions ) ) if "dbkey" in dataset.datatype.metadata_spec and not dataset.metadata.dbkey: # Copy dbkey into metadata, for backwards compatability # This looks like it does nothing, but getting the dbkey @@ -670,7 +764,9 @@ class Admin( BaseController ): return trans.fill_template( "/admin/library/dataset.mako", dataset=dataset, metadata=metadata, - datatypes=ldatatypes, + datatypes=ldatatypes, + dataset_actions=dataset_actions, + gdas=gdas, err=None ) else: return trans.show_error_message( "Invalid dataset specified" ) diff --git a/lib/galaxy/web/controllers/dataset.py b/lib/galaxy/web/controllers/dataset.py index d38537509cd..3d43b5bef5e 100644 --- a/lib/galaxy/web/controllers/dataset.py +++ b/lib/galaxy/web/controllers/dataset.py @@ -105,8 +105,7 @@ class DatasetInterface( BaseController ): """Catches the dataset id and displays file contents as directed""" data = trans.app.model.HistoryDatasetAssociation.get( dataset_id ) if not data: - raise paste.httpexceptions.HTTPRequestRangeNotSatisfiable( "Invalid reference dataset." ) - # TODO, Nate: Make sure the following is functionally correct. + raise paste.httpexceptions.HTTPRequestRangeNotSatisfiable( "Invalid reference dataset." ) if trans.app.security_agent.allow_action( trans.user, data.permitted_actions.DATASET_ACCESS, dataset = data ): if filename is None or filename.lower() == "index": mime = trans.app.datatypes_registry.get_mimetype_by_extension( data.extension.lower() ) @@ -127,4 +126,4 @@ class DatasetInterface( BaseController ): except: raise paste.httpexceptions.HTTPNotFound( "File Not Found (%s)." % ( filename ) ) else: - raise paste.httpexceptions.HTTPForbidden( "You are not privileged to access this dataset." ) + raise paste.httpexceptions.HTTPForbidden( "You are not permitted to access this dataset." ) diff --git a/lib/galaxy/web/controllers/root.py b/lib/galaxy/web/controllers/root.py index dc58f02f2ee..6850b069cb7 100644 --- a/lib/galaxy/web/controllers/root.py +++ b/lib/galaxy/web/controllers/root.py @@ -395,8 +395,13 @@ class RootController( BaseController ): if history: if history.user_id != None and user: assert user.id == history.user_id, "History does not belong to current user" - history_names.append(history.name) + # Delete DefaultHistoryGroupAssociations + for default_history_group_association in history.default_groups: + default_history_group_association.delete() + default_history_group_association.flush() + # Mark history as deleted in db history.deleted = True + history_names.append(history.name) # If deleting the current history, make a new current. if history == trans.get_history(): trans.new_history() diff --git a/templates/admin/library/dataset.mako b/templates/admin/library/dataset.mako index 52a35099045..b522a05256d 100644 --- a/templates/admin/library/dataset.mako +++ b/templates/admin/library/dataset.mako @@ -13,6 +13,41 @@ %endfor +
    +
    Group Associations
    +
    +
    + + %for gda in gdas: +
    + ${gda[1]} +
    +
    +
    + %for da in dataset_actions: + <% check = False %> + %for action in gda[2]: + %if action == da: + <% + check = True + break + %> + %endif + %endfor + %if check: + + %else: + + %endif + ${da}
    + %endfor +
    +
    + %endfor +
    +
    +
    +
    Edit Attributes
    diff --git a/templates/admin/library/folder.mako b/templates/admin/library/folder.mako index b4b11c74b36..e93b64e7989 100644 --- a/templates/admin/library/folder.mako +++ b/templates/admin/library/folder.mako @@ -94,9 +94,9 @@
    diff --git a/templates/admin/library/new_dataset.mako b/templates/admin/library/new_dataset.mako index 241e121ebeb..35a8106fd81 100644 --- a/templates/admin/library/new_dataset.mako +++ b/templates/admin/library/new_dataset.mako @@ -37,24 +37,9 @@
    @@ -64,889 +49,29 @@
    - ##this should be generated dynamically
    -
    + +
    - + + Multi-select list - hold the appropriate key while clicking to select multiple columns +
    + +
    +
    +
    +
    +
    From 140d8b5f7d5aaadf3f0ffbceced65041b0652456 Mon Sep 17 00:00:00 2001 From: Greg Von Kuster Date: Mon, 18 Aug 2008 09:00:53 -0400 Subject: [PATCH 19/21] Fix a typo in the security agent ( my last commit ) that broke a bunch of stuff. --- lib/galaxy/security/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/galaxy/security/__init__.py b/lib/galaxy/security/__init__.py index c6790b2d7e3..b36881505e0 100644 --- a/lib/galaxy/security/__init__.py +++ b/lib/galaxy/security/__init__.py @@ -21,7 +21,7 @@ class RBACAgent: DATASET_MANAGE_PERMISSIONS = 'dataset_manage_permissions', # The ability to perform any read only operation on the dataset (view, display at external site, # use in a job, etc). - DATASET_ACCESSS = 'dataset_access' + DATASET_ACCESS = 'dataset_access' ) def allow_action( self, user, action, **kwd ): raise 'No valid method of checking action (%s) on %s for user %s.' % ( action, kwd, user ) @@ -68,6 +68,7 @@ class GalaxyRBACAgent( RBACAgent ): 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""" + log.debug("In allow_dataset_action, user: %s, action: %s, dataset: %s" % ( str(user), str(action), str(dataset))) if not isinstance( dataset, self.model.Dataset ): dataset = dataset.dataset # If dataset is in public group, we always return true for viewing and using @@ -238,9 +239,11 @@ class GalaxyRBACAgent( RBACAgent ): def get_permitted_actions( self, filter=None ): '''Utility method to return a subset of RBACAgent's permitted actions''' if filter is None: + log.debug("In get_permitted_actions, returning RBACAgent.permitted_actions: %s" % str( RBACAgent.permitted_actions)) return RBACAgent.permitted_actions if not filter.endswith('_'): filter += '_' tmp_bunch = Bunch() [tmp_bunch.__dict__.__setitem__(k, v) for k, v in RBACAgent.permitted_actions.items() if k.startswith(filter)] + log.debug("In get_permitted_actions, returning tmp_bunch: %s" % str( tmp_bunch)) return tmp_bunch From 751cca164bb68cf24325f89be9f877ac4814527d Mon Sep 17 00:00:00 2001 From: Greg Von Kuster Date: Mon, 18 Aug 2008 11:21:31 -0400 Subject: [PATCH 20/21] LibraryFolders now track last used dbkey ( silimar to histories ), so when a new dataset is added to a folder, the last used dbkey is selected. --- lib/galaxy/model/__init__.py | 36 +++----------- lib/galaxy/model/mapping.py | 4 +- lib/galaxy/security/__init__.py | 4 -- lib/galaxy/web/controllers/admin.py | 60 ++++++++++++++++-------- templates/admin/library/new_dataset.mako | 6 ++- 5 files changed, 53 insertions(+), 57 deletions(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 599ab72a78b..4ef5a34744e 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -285,34 +285,26 @@ class DatasetInstance( object ): self.dataset = dataset self.parent_id = parent_id self.validation_errors = validation_errors - @property def ext( self ): return self.extension - def get_dataset_state( self ): return self.dataset.state def set_dataset_state ( self, state ): self.dataset.state = state self.dataset.flush() #flush here, because hda.flush() won't flush the Dataset object state = property( get_dataset_state, set_dataset_state ) - def get_file_name( self ): return self.dataset.get_file_name() - def set_file_name (self, filename): return self.dataset.set_file_name( filename ) - file_name = property( get_file_name, set_file_name ) - @property def extra_files_path( self ): return self.dataset.extra_files_path - @property def datatype( self ): return datatypes_registry.get_datatype_by_extension( self.extension ) - def get_metadata( self ): if not self._metadata: self._metadata = dict() @@ -321,11 +313,8 @@ class DatasetInstance( object ): # Needs to accept a MetadataCollection, a bunch, or a dict self._metadata = dict( bunch.items() ) metadata = property( get_metadata, set_metadata ) - - """ - This provide backwards compatibility with using the old dbkey - field in the database. That field now maps to "old_dbkey" (see mapping.py). - """ + # This provide backwards compatibility with using the old dbkey + # field in the database. That field now maps to "old_dbkey" (see mapping.py). def get_dbkey( self ): dbkey = self.metadata.dbkey if not isinstance(dbkey, list): dbkey = [dbkey] @@ -343,7 +332,6 @@ class DatasetInstance( object ): #else: # self.old_dbkey = value dbkey = property( get_dbkey, set_dbkey ) - def change_datatype( self, new_ext ): self.clear_associated_files() datatypes_registry.change_datatype( self, new_ext ) @@ -400,16 +388,12 @@ class DatasetInstance( object ): if child.designation == designation: return child return None - def get_converter_types(self): return self.datatype.get_converter_types( self, datatypes_registry) - def add_validation_error( self, validation_error ): self.validation_errors.append( validation_error ) - def extend_validation_errors( self, validation_errors ): self.validation_errors.extend(validation_errors) - def mark_deleted( self, include_children=True ): self.deleted = True if include_children: @@ -429,7 +413,6 @@ class HistoryDatasetAssociation( DatasetInstance ): self.history = history self.copied_from_history_dataset_association = copied_from_history_dataset_association self.copied_from_library_folder_dataset_association = copied_from_library_folder_dataset_association - def copy( self, copy_children = False, parent_id = None ): des = HistoryDatasetAssociation( hid=self.hid, name=self.name, @@ -451,7 +434,6 @@ class HistoryDatasetAssociation( DatasetInstance ): des.set_peek() #in some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs des.flush() return des - def clear_associated_files( self, metadata_safe = False, purge = False ): #metadata_safe = True means to only clear when assoc.metadata_safe == False for assoc in self.implicitly_converted_datasets: @@ -469,7 +451,6 @@ class History( object ): self.user = user self.datasets = [] self.galaxy_sessions = [] - def _next_hid( self ): # TODO: override this with something in the database that ensures # better integrity @@ -481,13 +462,11 @@ class History( object ): if dataset.hid > last_hid: last_hid = dataset.hid return last_hid + 1 - def add_galaxy_session( self, galaxy_session, association=None ): if association is None: self.galaxy_sessions.append( GalaxySessionToHistoryAssociation( galaxy_session, self ) ) else: self.galaxy_sessions.append( association ) - def add_dataset( self, dataset, parent_id=None, genome_build=None, set_hid = True ): if isinstance( dataset, Dataset ): dataset = HistoryDatasetAssociation( dataset = dataset ) @@ -507,7 +486,6 @@ class History( object ): if genome_build not in [None, '?']: self.genome_build = genome_build self.datasets.append( dataset ) - def copy( self, target_user = None ): if not target_user: target_user = self.user @@ -534,10 +512,13 @@ class LibraryFolder( object ): self.description = description self.item_count = item_count self.order_id = order_id - def add_dataset( self, dataset ): + self.genome_build = None + def add_dataset( self, dataset, genome_build=None ): dataset.folder_id = self.id dataset.order_id = self.item_count self.item_count += 1 + if genome_build not in [None, '?']: + self.genome_build = genome_build def add_folder( self, folder ): folder.parent_id = self.id folder.order_id = self.item_count @@ -555,7 +536,6 @@ class LibraryFolderDatasetAssociation( DatasetInstance ): self.order_id = order_id self.copied_from_history_dataset_association = copied_from_history_dataset_association self.copied_from_library_folder_dataset_association = copied_from_library_folder_dataset_association - def to_history_dataset_association( self, parent_id = None ): des = HistoryDatasetAssociation( name=self.name, info=self.info, @@ -575,8 +555,6 @@ class LibraryFolderDatasetAssociation( DatasetInstance ): des.set_peek() #in some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs des.flush() return des - - def copy( self, copy_children = False, parent_id = None ): des = LibraryFolderDatasetAssociation( name=self.name, info=self.info, @@ -597,11 +575,9 @@ class LibraryFolderDatasetAssociation( DatasetInstance ): des.set_peek() #in some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs des.flush() return des - def clear_associated_files( self, metadata_safe = False, purge = False ): return - class LibraryTag( object ): def __init__( self, tag ): self.tag = tag diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index 2303c9eb338..cd4aa9c3ee9 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -66,7 +66,6 @@ History.table = Table( "history", metadata, # Column( "state", String( 64 ) ), # Column( "tool_parameters", Pickle() ) ) - HistoryDatasetAssociation.table = Table( "history_dataset_association", metadata, Column( "id", Integer, primary_key=True ), Column( "history_id", Integer, ForeignKey( "history.id" ), index=True ), @@ -194,7 +193,8 @@ LibraryFolder.table = Table( "library_folder", metadata, Column( "name", TEXT ), Column( "description", TEXT ), Column( "order_id", Integer ), - Column( "item_count", Integer ) ) + Column( "item_count", Integer ), + Column( "genome_build", TrimmedString( 40 ) ) ) LibraryTag.table = Table( "library_tag", metadata, Column( "id", Integer, primary_key=True ), diff --git a/lib/galaxy/security/__init__.py b/lib/galaxy/security/__init__.py index b36881505e0..0eaba298d10 100644 --- a/lib/galaxy/security/__init__.py +++ b/lib/galaxy/security/__init__.py @@ -68,7 +68,6 @@ class GalaxyRBACAgent( RBACAgent ): 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""" - log.debug("In allow_dataset_action, user: %s, action: %s, dataset: %s" % ( str(user), str(action), str(dataset))) if not isinstance( dataset, self.model.Dataset ): dataset = dataset.dataset # If dataset is in public group, we always return true for viewing and using @@ -146,7 +145,6 @@ class GalaxyRBACAgent( RBACAgent ): assoc.flush() return assoc def disassociate_group_dataset( self, group, dataset ): - log.debug("In disassociate_group_dataset, removing %s -> %s" % (group.id, dataset.id)) assoc = self.model.GroupDatasetAssociation.selectone_by( group_id = group.id, dataset_id = dataset.id ) assoc.delete() assoc.flush() @@ -239,11 +237,9 @@ class GalaxyRBACAgent( RBACAgent ): def get_permitted_actions( self, filter=None ): '''Utility method to return a subset of RBACAgent's permitted actions''' if filter is None: - log.debug("In get_permitted_actions, returning RBACAgent.permitted_actions: %s" % str( RBACAgent.permitted_actions)) return RBACAgent.permitted_actions if not filter.endswith('_'): filter += '_' tmp_bunch = Bunch() [tmp_bunch.__dict__.__setitem__(k, v) for k, v in RBACAgent.permitted_actions.items() if k.startswith(filter)] - log.debug("In get_permitted_actions, returning tmp_bunch: %s" % str( tmp_bunch)) return tmp_bunch diff --git a/lib/galaxy/web/controllers/admin.py b/lib/galaxy/web/controllers/admin.py index 55f4f850646..cb8aad42c78 100644 --- a/lib/galaxy/web/controllers/admin.py +++ b/lib/galaxy/web/controllers/admin.py @@ -513,6 +513,10 @@ class Admin( BaseController ): return trans.show_error_message( no_privilege_msg ) if 'create_folder' in kwd: folder = trans.app.model.LibraryFolder( name = name, description = description ) + # We are associating the last used genome_build with folders, so we will always + # initialize a new folder with the first dbkey in util.dbnames which is currently + # ? unspecified (?) + folder.genome_build = util.dbnames.default_value if parent_id: parent_folder = trans.app.model.LibraryFolder.get( parent_id ) parent_folder.add_folder( folder ) @@ -540,8 +544,17 @@ class Admin( BaseController ): def dataset( self, trans, id=None, name="Unnamed", info='no info', extension=None, folder_id=None, dbkey=None, **kwd ): if not self.user_is_admin( trans ): return trans.show_error_message( no_privilege_msg ) + if isinstance( dbkey, list ): + last_used_build = dbkey[0] + else: + last_used_build = dbkey + if folder_id and not last_used_build: + folder = trans.app.model.LibraryFolder.get( folder_id ) + last_used_build = folder.genome_build data_files = [] - def add_file( file_obj, name, extension, dbkey, groups, info='no info', space_to_tab=False ): + + # add_file method + def add_file( file_obj, name, extension, dbkey, last_used_build, groups, info='no info', space_to_tab=False ): data_type = None temp_name = sniff.stream_to_file( file_obj ) if space_to_tab: @@ -552,9 +565,13 @@ class Admin( BaseController ): data_type = sniff.guess_ext( temp_name, sniff_order=trans.app.datatypes_registry.sniff_order ) else: data_type = extension - dataset = trans.app.model.LibraryFolderDatasetAssociation( name = name, info = info, extension = data_type, dbkey = dbkey, create_dataset = True ) + dataset = trans.app.model.LibraryFolderDatasetAssociation( name=name, + info=info, + extension=data_type, + dbkey=dbkey, + create_dataset=True ) folder = trans.app.model.LibraryFolder.get( folder_id ) - folder.add_dataset( dataset ) + folder.add_dataset( dataset, genome_build=last_used_build ) dataset.flush() # GroupDatasetAssociations will enable security on the dataset based on the permitted_actions # associated with the GroupDatasetAssociation. The default permitted_actions at this point @@ -575,12 +592,12 @@ class Admin( BaseController ): else: dataset.set_peek() dataset.set_size() - if dataset.missing_meta(): dataset.datatype.set_meta( dataset ) trans.app.model.flush() - return dataset + # END add_file method + if 'create_dataset' in kwd: # Copied from upload tool action last_dataset_created = None @@ -603,12 +620,13 @@ class Admin( BaseController ): file_name = data_file.filename file_name = file_name.split( '\\' )[-1] file_name = file_name.split( '/' )[-1] - last_dataset_created = add_file( data_file.file, - file_name, - extension, - dbkey, + last_dataset_created = add_file( data_file.file, + file_name, + extension, + dbkey, + last_used_build, groups, - info="uploaded file", + info="uploaded file", space_to_tab=space_to_tab ) elif url_paste not in [ None, "" ]: if url_paste.lower().find( 'http://' ) >= 0 or url_paste.lower().find( 'ftp://' ) >= 0: @@ -618,10 +636,11 @@ class Admin( BaseController ): if line: last_dataset_created = add_file( urllib.urlopen( line ), line, - extension, - dbkey, + extension, + dbkey, + last_used_build, groups, - info="uploaded url", + info="uploaded url", space_to_tab=space_to_tab ) else: is_valid = False @@ -632,22 +651,22 @@ class Admin( BaseController ): break if is_valid: last_dataset_created = add_file( StringIO.StringIO( url_paste ), - 'Pasted Entry', - extension, - dbkey, + 'Pasted Entry', + extension, + dbkey, + last_used_build, groups, - info="pasted entry", + info="pasted entry", space_to_tab=space_to_tab ) trans.response.send_redirect( web.url_for( action='dataset', id=last_dataset_created.id ) ) elif id is None: # Send list of data formats to the form so the "extension" select list can be populated dynamically file_formats = trans.app.datatypes_registry.upload_file_formats # Send list of genome builds to the form so the "dbkey" select list can be populated dynamically - def get_dbkey_options(): - last_used_build = trans.history.genome_build + def get_dbkey_options( last_used_build ): for dbkey, build_name in util.dbnames: yield build_name, dbkey, ( dbkey==last_used_build ) - dbkeys = get_dbkey_options() + dbkeys = get_dbkey_options( last_used_build ) # Send list of groups to the form so the dataset can be associated with 1 or more of them. groups = [] q = sa.select( ( ( galaxy.model.Group.table.c.id ).label( 'group_id' ), @@ -660,6 +679,7 @@ class Admin( BaseController ): folder_id=folder_id, file_formats=file_formats, dbkeys=dbkeys, + last_used_build=last_used_build, groups=groups ) dataset = trans.app.model.LibraryFolderDatasetAssociation.get( id ) if dataset: diff --git a/templates/admin/library/new_dataset.mako b/templates/admin/library/new_dataset.mako index 35a8106fd81..3c641288b74 100644 --- a/templates/admin/library/new_dataset.mako +++ b/templates/admin/library/new_dataset.mako @@ -52,7 +52,11 @@
    From f50ee0fff3fcf803778f3a38f76852ac2c987365 Mon Sep 17 00:00:00 2001 From: Greg Von Kuster Date: Tue, 19 Aug 2008 10:04:30 -0400 Subject: [PATCH 21/21] Added security to libraries. Non-admin users can now only see libraries that contain datasets associated with the user's groups, and each library will only display those datasets that are associated with the user's groups. --- lib/galaxy/web/controllers/admin.py | 6 +- lib/galaxy/web/controllers/library.py | 81 ++++++++++++++++++++++++-- templates/admin/library/dataset.mako | 45 +++++++------- templates/library/libraries.mako | 6 +- templates/library/library.mako | 31 +++++++++- tools/data_source/access_libraries.xml | 14 ++--- 6 files changed, 142 insertions(+), 41 deletions(-) diff --git a/lib/galaxy/web/controllers/admin.py b/lib/galaxy/web/controllers/admin.py index cb8aad42c78..f0ccc2f4f07 100644 --- a/lib/galaxy/web/controllers/admin.py +++ b/lib/galaxy/web/controllers/admin.py @@ -703,6 +703,8 @@ class Admin( BaseController ): gdpa_dict[ group_id ].append( dpa ) else: gdpa_dict[ group_id ] = [ dpa ] + # Refresh the Dataset to ensure we have a valid set of DatasetGroupAssociations + dataset.dataset.refresh() # Check to see if we need to delete any GroupDatasetAssociations. This occurs if # the user unchecked all boxes for a group for group_dataset_assoc in dataset.dataset.groups: @@ -761,9 +763,11 @@ class Admin( BaseController ): dataset_actions.sort() # Get the permitted_actions of each GroupDatasetAssociation to send to the form gdas = [] - # Refresh the dataset to ensure we have a valid set of DatasetGroupAssociations + # Refresh the Dataset to ensure we have a valid set of GroupDatasetAssociations dataset.dataset.refresh() for group_dataset_assoc in dataset.dataset.groups: + # Refresh the GroupDatasetAssociation to ensure we have a valid set of permitted_actions + group_dataset_assoc.refresh() group = galaxy.model.Group.get( group_dataset_assoc.group_id ) gdas.append( ( group.id, group.name, group_dataset_assoc.permitted_actions ) ) if "dbkey" in dataset.datatype.metadata_spec and not dataset.metadata.dbkey: diff --git a/lib/galaxy/web/controllers/library.py b/lib/galaxy/web/controllers/library.py index 94e7b5731e0..43c45a58402 100644 --- a/lib/galaxy/web/controllers/library.py +++ b/lib/galaxy/web/controllers/library.py @@ -6,9 +6,12 @@ log = logging.getLogger( __name__ ) class Library( BaseController ): @web.expose - def index( self, trans, library_id = None, import_ids = [], **kwd ): - #use for importing an entry into your history + def index( self, trans, library_id=None, import_ids=[], **kwd ): + # Need user to get associated Groups and Datasets + user = trans.get_user() + libraries = [] if import_ids: + # Used for importing a dataset into a user's history if not isinstance( import_ids, list ): import_ids = [import_ids] history = trans.get_history() @@ -19,5 +22,75 @@ class Library( BaseController ): history.flush() return trans.show_ok_message( "%i datasets have been imported into your history" % len( import_ids ), refresh_frames=['history'] ) elif library_id: - return trans.fill_template( '/library/library.mako', library=trans.app.model.Library.get( library_id ) ) - return trans.fill_template( '/library/libraries.mako', libraries=trans.app.model.Library.select() ) + # Since permitted_actions are kept with the GroupDatasetAssociation, each accessible Library will only + # display the subset of [ it's complete set of ] datasets that the user has permission to access. We + # pass group_ids so this can be handled in the template. + if not user: + group_ids = [ trans.app.model.Group.select_by( name='public' )[0].id ] + else: + group_ids = [] + for user_group_assoc in user.groups: + group_ids.append( user_group_assoc.group_id ) + library = trans.app.model.Library.get( library_id ) + return trans.fill_template( '/library/library.mako', library=library, group_ids=group_ids ) + if user: + # Only display libraries that contain datasets associated with the user's groups + group_ids = [] + for user_group_assoc in user.groups: + group = trans.app.model.Group.get( user_group_assoc.group_id ) + group_ids.append( group.id ) + libs = trans.app.model.Library.select() + for library in libs: + user_can_access = False + # Check for public datasets in the Library's root folder + for library_folder_dataset_assoc in library.root_folder.datasets: + if user_can_access: + break + dataset = trans.app.model.Dataset.get( library_folder_dataset_assoc.dataset_id ) + for group_dataset_assoc in dataset.groups: + if group_dataset_assoc.group_id in group_ids: + libraries.append( library ) + user_can_access = True + break + for folder in library.root_folder.folders: + if user_can_access: + break + for library_folder_dataset_assoc in folder.datasets: + if user_can_access: + break + dataset = trans.app.model.Dataset.get( library_folder_dataset_assoc.dataset_id ) + for group_dataset_assoc in dataset.groups: + if group_dataset_assoc.group_id in group_ids: + libraries.append( library ) + user_can_access = True + break + else: + # Only display libraries that contain datasets associated with the public group + group_ids = [ trans.app.model.Group.select_by( name='public' )[0].id ] + libs = trans.app.model.Library.select() + for library in libs: + public_library = False + # Check for public datasets in the Library's root folder + for library_folder_dataset_assoc in library.root_folder.datasets: + if public_library: + break + dataset = trans.app.model.Dataset.get( library_folder_dataset_assoc.dataset_id ) + for group_dataset_assoc in dataset.groups: + if group_dataset_assoc.group_id in group_ids: + libraries.append( library ) + public_library = True + break + # Check for public datasets in the root folder's sub-folders + for folder in library.root_folder.folders: + if public_library: + break + for library_folder_dataset_assoc in folder.datasets: + if public_library: + break + dataset = trans.app.model.Dataset.get( library_folder_dataset_assoc.dataset_id ) + for group_dataset_assoc in dataset.groups: + if group_dataset_assoc.group_id in group_ids: + libraries.append( library ) + public_library = True + break + return trans.fill_template( '/library/libraries.mako', group_ids=group_ids, libraries=libraries ) diff --git a/templates/admin/library/dataset.mako b/templates/admin/library/dataset.mako index b522a05256d..a7877e54479 100644 --- a/templates/admin/library/dataset.mako +++ b/templates/admin/library/dataset.mako @@ -1,6 +1,7 @@ <%inherit file="/base.mako"/> <%def name="title()">Edit Dataset Attributes + <%def name="datatype( dataset, datatypes )"> + +<%def name="group_dataset_permitted_actions( dataset_actions, gda )"> + %for da in dataset_actions: + <% check = False %> + %for action in gda[2]: + %if action == da: + <% + check = True + break + %> + %endif + %endfor + %if check: + + %else: + + %endif + ${da}
    + %endfor + +
    Group Associations
    %for gda in gdas: -
    - ${gda[1]} -
    +
    ${gda[1]}
    - %for da in dataset_actions: - <% check = False %> - %for action in gda[2]: - %if action == da: - <% - check = True - break - %> - %endif - %endfor - %if check: - - %else: - - %endif - ${da}
    - %endfor -
    + ${group_dataset_permitted_actions( dataset_actions, gda )}
    %endfor
    diff --git a/templates/library/libraries.mako b/templates/library/libraries.mako index 247333f709c..1176bd2162a 100644 --- a/templates/library/libraries.mako +++ b/templates/library/libraries.mako @@ -1,12 +1,12 @@ <%inherit file="/base.mako"/> -<%def name="title()">View Libraries +<%def name="title()">Libraries You Can Access
    -
    View Library
    +
    Libraries You Can Access
    %for library in libraries: %endfor
    diff --git a/templates/library/library.mako b/templates/library/library.mako index 2706efcde25..373ff9c4118 100644 --- a/templates/library/library.mako +++ b/templates/library/library.mako @@ -3,17 +3,41 @@ <%def name="render_component( component )"> <% if isinstance( component, trans.app.model.LibraryFolder ): - return render_folder( component ) + render = False + # Check the folder's datasets to see what can be rendered + for library_folder_dataset_assoc in component.datasets: + if render: + break + dataset = trans.app.model.Dataset.get( library_folder_dataset_assoc.dataset_id ) + for group_dataset_assoc in dataset.groups: + if group_dataset_assoc.group_id in group_ids: + render = True + break + # TODO: Do we need to upgrade sqlalchemy? The following shouldn't be necessary if the mappers work correctly. + # Check the folder's sub-folders to see what can be rendered + for library_folder in component.folders: + render_component( library_folder ) + if render: + return render_folder( component ) elif isinstance( component, trans.app.model.LibraryFolderDatasetAssociation ): - return render_dataset( component ) + render = False + dataset = trans.app.model.Dataset.get( component.dataset_id ) + for group_dataset_assoc in dataset.groups: + if group_dataset_assoc.group_id in group_ids: + render = True + break + if render: + return render_dataset( component ) %> + ## Render the dataset `data` as history item, using `hid` as the displayed id <%def name="render_dataset( data )">
    ${data.name}
    + ## Render a folder <%def name="render_folder( this_folder )">
    @@ -31,11 +55,12 @@
    + <%def name="title()">View Library: ${library.name}
    Import from Library: ${library.name}
    - + ${render_folder( library.root_folder )}
    diff --git a/tools/data_source/access_libraries.xml b/tools/data_source/access_libraries.xml index ffd383c481a..e6dabfbf1b1 100644 --- a/tools/data_source/access_libraries.xml +++ b/tools/data_source/access_libraries.xml @@ -1,11 +1,7 @@ - - stored locally - - - - - - - + stored locally + + + + \ No newline at end of file