diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py
index 4316cbb53b2..5d907a28a76 100644
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -103,70 +103,16 @@ class JobToOutputDatasetAssociation( object ):
self.name = name
self.dataset = dataset
-class GroupDatasetAssociation( object ):
- def __init__( self, group, dataset, permitted_actions=[] ):
- if isinstance( group, GroupDatasetAssociation ) or \
- isinstance( group, DefaultUserGroupAssociation ) or \
- isinstance( group, DefaultHistoryGroupAssociation ):
- group = group.group
- self.group = group
- if isinstance( dataset, HistoryDatasetAssociation ):
- dataset = dataset.dataset
- self.dataset = dataset
- self.permitted_actions = permitted_actions
- def add_permitted_action( self, action ):
- if action not in self.permitted_actions:
- return self.permitted_actions.append( action )
- raise 'action (%s) already exists in permitted actions list (%s: %s).' % ( action, str( self.id ), str( self.permitted_actions ) )
- def remove_permitted_action( self, action ):
- return self.permitted_actions.remove( action )
-
class Group( object ):
- public_id = None
permitted_actions = galaxy.security.get_permitted_actions( 'GROUP' )
- def __init__( self, name = None, priority = 0 ):
+ def __init__( self, name = None ):
self.name = name
- self.priority = priority
- @classmethod
- def get_public_group( cls ):
- return Group.get( cls.public_id )
- @classmethod
- def set_public_group( cls, group ):
- # We store the id instead of the object, because of alchemy sessions
- if isinstance( group, Group ):
- group = group.id
- cls.public_id = group
- @classmethod
- def guess_public_group( cls ):
- # Retrieve from database and store public group id
- group = Group.filter_by( name='public' ).first()
- cls.set_public_group( group )
class UserGroupAssociation( object ):
def __init__( self, user, group ):
self.user = user
self.group = group
-class DefaultUserGroupAssociation( object ):
- def __init__( self, user, group, permitted_actions ):
- if isinstance( group, GroupDatasetAssociation ) or \
- isinstance( group, DefaultUserGroupAssociation ) or \
- isinstance( group, DefaultHistoryGroupAssociation ):
- group = group.group
- self.user = user
- self.group = group
- self.permitted_actions = permitted_actions
-
-class DefaultHistoryGroupAssociation( object ):
- def __init__( self, history, group, permitted_actions ):
- if isinstance( group, GroupDatasetAssociation ) or \
- isinstance( group, DefaultUserGroupAssociation ) or \
- isinstance( group, DefaultHistoryGroupAssociation ):
- group = group.group
- self.history = history
- self.group = group
- self.permitted_actions = permitted_actions
-
class History( object ):
def __init__( self, id=None, name=None, user=None ):
self.id = id
@@ -239,6 +185,64 @@ class History( object ):
# self.history = history
# self.datasets = []
+class UserRoleAssociation( object ):
+ def __init__( self, user, role ):
+ self.user = user
+ self.role = role
+
+class GroupRoleAssociation( object ):
+ def __init__( self, group, role ):
+ self.group = group
+ self.role = role
+
+class Role( object ):
+ private_id = None
+ types = Bunch(
+ PRIVATE = 'private',
+ SYSTEM = 'system',
+ USER = 'user',
+ ADMIN = 'admin'
+ )
+ def __init__( self, name="", description="", type="system", deleted=False ):
+ self.name = name
+ self.description = description
+ self.type = type
+ self.deleted = deleted
+
+class ActionObjectRolesAssociation( object ):
+ """
+ Base class for an action->something->roles association, so set_roles
+ doesn't have to be a member of each class that uses it.
+ """
+ def set_roles( self, roles ):
+ """
+ Convenience method to allow roles to be a list of Roles or role ids.
+ """
+ def get_id( x ):
+ if isinstance( x, Role ):
+ return x.id
+ else:
+ return x
+ self.role_ids = map( get_id, roles )
+
+class ActionDatasetRolesAssociation( ActionObjectRolesAssociation ):
+ def __init__( self, action, dataset, roles ):
+ self.action = action
+ self.dataset = dataset
+ self.set_roles( roles )
+
+class DefaultUserPermissions( ActionObjectRolesAssociation ):
+ def __init__( self, user, action, roles ):
+ self.user = user
+ self.action = action
+ self.set_roles( roles )
+
+class DefaultHistoryPermissions( ActionObjectRolesAssociation ):
+ def __init__( self, history, action, roles ):
+ self.history = history
+ self.action = action
+ self.set_roles( roles )
+
class Dataset( object ):
states = Bunch( NEW = 'new',
QUEUED = 'queued',
diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py
index 2829c2a6f0c..5fd0d4f434c 100644
--- a/lib/galaxy/model/mapping.py
+++ b/lib/galaxy/model/mapping.py
@@ -117,7 +117,6 @@ Group.table = Table( "galaxy_group", metadata,
Column( "create_time", DateTime, default=now ),
Column( "update_time", DateTime, default=now, onupdate=now ),
Column( "name", TEXT, index=True, unique=True ),
- Column( "priority", Integer ),
Column( "deleted", Boolean, index=True, default=False ) )
UserGroupAssociation.table = Table( "user_group_association", metadata,
@@ -127,32 +126,49 @@ UserGroupAssociation.table = Table( "user_group_association", metadata,
Column( "create_time", DateTime, default=now ),
Column( "update_time", DateTime, default=now, onupdate=now ) )
-GroupDatasetAssociation.table = Table( "group_dataset_association", metadata,
+UserRoleAssociation.table = Table( "user_role_association", metadata,
Column( "id", Integer, primary_key=True ),
- Column( "group_id", Integer, ForeignKey( "galaxy_group.id" ), index=True ),
- Column( "dataset_id", Integer, ForeignKey( "dataset.id" ), index=True ),
- Column( "create_time", DateTime, default=now ),
- Column( "update_time", DateTime, default=now, onupdate=now ),
- Column( "permitted_actions", JSONType(), default=[] ) )
-
-# The following table stores the permissions that are considered the defaults for new histories when they are created by a user
-DefaultUserGroupAssociation.table = Table( "default_user_group_association", metadata,
- Column( "id", Integer, primary_key=True ),
- Column( "group_id", Integer, ForeignKey( "galaxy_group.id" ), index=True ),
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
+ Column( "role_id", Integer, ForeignKey( "role.id" ), index=True ),
Column( "create_time", DateTime, default=now ),
- Column( "update_time", DateTime, default=now, onupdate=now ),
- Column( "permitted_actions", JSONType(), default=[] ) )
+ Column( "update_time", DateTime, default=now, onupdate=now ) )
-# The following table stores the default permissions assigned to histories for datasets
-# that need permissions ( dataset permissions that cannot be determined based on ancestor )
-DefaultHistoryGroupAssociation.table = Table( "default_history_group_association", metadata,
+GroupRoleAssociation.table = Table( "group_role_association", metadata,
Column( "id", Integer, primary_key=True ),
Column( "group_id", Integer, ForeignKey( "galaxy_group.id" ), index=True ),
- Column( "history_id", Integer, ForeignKey( "history.id" ), index=True ),
+ Column( "role_id", Integer, ForeignKey( "role.id" ), index=True ),
+ Column( "create_time", DateTime, default=now ),
+ Column( "update_time", DateTime, default=now, onupdate=now ) )
+
+Role.table = Table( "role", metadata,
+ Column( "id", Integer, primary_key=True ),
Column( "create_time", DateTime, default=now ),
Column( "update_time", DateTime, default=now, onupdate=now ),
- Column( "permitted_actions", JSONType(), default=[] ) )
+ Column( "name", TEXT, index=True, unique=True ),
+ Column( "description", TEXT ),
+ Column( "type", TEXT, index=True ),
+ Column( "deleted", Boolean, index=True, default=False ) )
+
+# TODO: should role_ids be made in to another association table?
+ActionDatasetRolesAssociation.table = Table( "action_dataset_roles_association", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "create_time", DateTime, default=now ),
+ Column( "update_time", DateTime, default=now, onupdate=now ),
+ Column( "action", TEXT ),
+ Column( "dataset_id", Integer, ForeignKey( "dataset.id" ), index=True ),
+ Column( "role_ids", JSONType(), default=[] ) )
+
+DefaultUserPermissions.table = Table( "default_user_permissions", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
+ Column( "action", TEXT ),
+ Column( "role_ids", JSONType(), default=[] ) )
+
+DefaultHistoryPermissions.table = Table( "default_history_permissions", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "history_id", Integer, ForeignKey( "history.id" ), index=True ),
+ Column( "action", TEXT ),
+ Column( "role_ids", JSONType(), default=[] ) )
LibraryFolderDatasetAssociation.table = Table( "library_folder_dataset_association", metadata,
Column( "id", Integer, primary_key=True ),
@@ -399,7 +415,7 @@ assign_mapper( context, History, History.table,
) )
assign_mapper( context, User, User.table,
- properties=dict( histories=relation( History, backref="user",
+ properties=dict( histories=relation( History, backref="user",
order_by=desc(History.table.c.update_time) ),
active_histories=relation( History, primaryjoin=( ( History.table.c.user_id == User.table.c.id ) & ( not_( History.table.c.deleted ) ) ), order_by=desc( History.table.c.update_time ) ),
stored_workflow_menu_entries=relation( StoredWorkflowMenuEntry, backref="user",
@@ -408,29 +424,49 @@ assign_mapper( context, User, User.table,
) )
assign_mapper( context, Group, Group.table,
- properties=dict( users=relation( UserGroupAssociation ),
- datasets=relation( GroupDatasetAssociation ) ) )
+ properties=dict( users=relation( UserGroupAssociation ) ) )
assign_mapper( context, UserGroupAssociation, UserGroupAssociation.table,
properties=dict( user=relation( User, backref = "groups" ),
group=relation( Group, backref = "members" ) ) )
-assign_mapper( context, GroupDatasetAssociation, GroupDatasetAssociation.table,
- properties=dict( dataset=relation( Dataset, backref = "groups" ),
- group=relation( Group, backref = "group_datasets" ) ) )
+assign_mapper( context, DefaultUserPermissions, DefaultUserPermissions.table,
+ properties=dict( user=relation( User, backref = "default_permissions" ) ) )
-assign_mapper( context, DefaultUserGroupAssociation, DefaultUserGroupAssociation.table,
- properties=dict( user=relation( User, backref = "default_groups" ),
- group=relation( Group ) ) )
+assign_mapper( context, DefaultHistoryPermissions, DefaultHistoryPermissions.table,
+ properties=dict( history=relation( History, backref = "default_permissions" ) ) )
-assign_mapper( context, DefaultHistoryGroupAssociation, DefaultHistoryGroupAssociation.table,
- properties=dict( history=relation( History, backref = "default_groups" ),
- group=relation( Group ) ) )
+assign_mapper( context, Role, Role.table,
+ properties=dict(
+ users=relation( UserRoleAssociation ),
+ groups=relation( GroupRoleAssociation )
+ )
+)
+
+assign_mapper( context, UserRoleAssociation, UserRoleAssociation.table,
+ properties=dict(
+ user=relation( User, backref="roles" ),
+ role=relation( Role )
+ )
+)
+
+assign_mapper( context, GroupRoleAssociation, GroupRoleAssociation.table,
+ properties=dict(
+ group=relation( Group, backref="roles" ),
+ role=relation( Role )
+ )
+)
+
+assign_mapper( context, ActionDatasetRolesAssociation, ActionDatasetRolesAssociation.table,
+ properties=dict(
+ dataset=relation( Dataset, backref="actions" )
+ )
+)
assign_mapper( context, Library, Library.table,
properties=dict(
root_folder=relation( LibraryFolder,
- backref = backref( "library_root" ) )
+ backref=backref( "library_root" ) )
) )
assign_mapper( context, LibraryFolder, LibraryFolder.table,
@@ -605,13 +641,13 @@ def init( file_path, url, engine_options={}, create_tables=False ):
result.create_tables = create_tables
#load local galaxy security policy
result.security_agent = GalaxyRBACAgent( result )
- # Ensure group named 'public' exists
- public_group = result.Group.filter_by( name='public' ).first()
- if not public_group:
- public_group = result.security_agent.create_group( name = 'public' )
- # Store public group id
- result.security_agent.set_public_group( public_group )
- log.debug( "Public Group identified as id = %s." % ( public_group.id ) )
+ # Create private roles if necessary.
+ if not len( result.Role.select() ):
+ for user in result.User.select():
+ role = Role( name = user.email, description = 'Private Role for ' + user.email, type = 'private' )
+ role.flush()
+ ura = UserRoleAssociation( user = user, role = role )
+ ura.flush()
return result
def get_suite():
diff --git a/lib/galaxy/security/__init__.py b/lib/galaxy/security/__init__.py
index 90aaa0ac3db..7e2c462fbcc 100644
--- a/lib/galaxy/security/__init__.py
+++ b/lib/galaxy/security/__init__.py
@@ -7,52 +7,55 @@ from galaxy.util.bunch import Bunch
log = logging.getLogger(__name__)
+class Action( object ):
+ def __init__( self, action, description, model ):
+ self.action = action
+ self.description = description
+ self.model = model
+
class RBACAgent:
"""Class that handles galaxy security"""
permitted_actions = Bunch(
- # The ability to edit the metadata of the associated dataset
- DATASET_EDIT_METADATA = 'edit metadata',
- # The ability to change the permissions of a dataset (so specifically, to add and modify
- # group_dataset_association rows where the dataset is the dataset for which the permission is set).
- DATASET_MANAGE_PERMISSIONS = 'manage permissions',
- # The ability to perform any read only operation on the dataset (view, display at external site,
- # use in a job, etc).
- DATASET_ACCESS = 'access'
- )
- permitted_action_descriptions = Bunch(
- DATASET_EDIT_METADATA = "User can edit this dataset's metadata in the library",
- DATASET_MANAGE_PERMISSIONS = "User can manage the groups and group permitted actions associated with this dataset",
- DATASET_ACCESS = "User can import this dataset into their history for analysis"
+ DATASET_EDIT_METADATA = Action(
+ "edit metadata", "Role members can edit this dataset's metadata in the library", "grant" ),
+ DATASET_MANAGE_PERMISSIONS = Action(
+ "manage permissions", "Role members can manage the groups and group permitted actions associated with this dataset", "grant" ),
+ DATASET_ACCESS = Action(
+ "access", "Role members can import this dataset into their history for analysis", "restrict" )
)
+ def get_action( self, name, default=None ):
+ """
+ Get a permitted action by its dict key or action name
+ """
+ for k, v in self.permitted_actions.items():
+ if k == name or v.action == name:
+ return v
+ return default
+ def get_actions( self ):
+ """
+ Get all permitted actions as a list
+ """
+ return self.permitted_actions.__dict__.values()
def allow_action( self, user, action, **kwd ):
raise 'No valid method of checking action (%s) on %s for user %s.' % ( action, kwd, user )
def guess_derived_permissions_for_datasets( self, datasets = [] ):
raise "Unimplemented Method"
def associate_components( self, **kwd ):
raise 'No valid method of associating provided components: %s' % kwd
- def get_group( self, id ):
- raise 'No valid method of retrieving group %s' % ( id )
- def create_group( self, **kwd ):
- raise 'No valid method of creating group with %s' % ( kwd )
- def create_private_user_group( self, user ):
+ def create_private_user_role( self, user ):
raise "Unimplemented Method"
- def user_set_default_access( self, user, permissions = None, history = False, dataset = False ):
+ def get_private_user_role( self, user ):
+ raise "Unimplemented Method"
+ def user_set_default_permissions( self, user, permissions = None, history = False, dataset = False ):
raise "Unimplemented Method"
def setup_new_user( self, user ):
- self.user_set_default_access( user, history = True, dataset = True )
- self.associate_components( user=user, group=self.get_public_group() )
- def history_set_default_access( self, history, permissions=None, dataset=False ):
- raise "Unimplemented Method"
- def set_public_group( self, group ):
- raise "Unimplemented Method"
- def get_public_group( self ):
- raise "Unimplemented Method"
- def guess_public_group( self ):
+ self.create_private_user_role( user )
+ self.user_set_default_permissions( user, history = True, dataset = True )
+ #self.associate_components( user=user, group=self.get_public_group() )
+ def history_set_default_permissions( self, history, permissions=None, dataset=False, bypass_manage_permission=False ):
raise "Unimplemented Method"
def set_dataset_permissions( self, dataset, permissions ):
raise "Unimplemented Method"
- def set_dataset_permitted_actions( self, dataset ):
- raise "Unimplemented Method"
def get_component_associations( self, **kwd ):
raise "Unimplemented Method"
def components_are_associated( self, **kwd ):
@@ -63,18 +66,6 @@ class RBACAgent:
form, ensure that they match our actual permitted actions.
"""
return filter( lambda x: x is not None, [ self.permitted_actions.get( action_string ) for action_string in permitted_action_strings ] )
- def get_permitted_action_description( self, permitted_action ):
- """
- Return the description of a permitted_action, regardless of
- whether permitted_action is a key or value.
- """
- if self.permitted_action_descriptions.get( permitted_action ) is not None:
- return self.permitted_action_descriptions.get( permitted_action )
- else:
- for k, v in self.permitted_actions.items():
- if v == permitted_action:
- return self.permitted_action_descriptions.get( k )
- return permitted_action # so at least something useful is printable
class GalaxyRBACAgent( RBACAgent ):
def __init__( self, model, permitted_actions=None ):
@@ -89,184 +80,212 @@ class GalaxyRBACAgent( RBACAgent ):
"""Returns true when user has permission to perform an action"""
if not isinstance( dataset, self.model.Dataset ):
dataset = dataset.dataset
- if user is None:
- try:
- public_assoc = [ gda for gda in dataset.groups if gda.group == self.get_public_group() ][0]
- except:
- # There'll be an IndexError if the dataset doesn't have a gda with public
- return False
- if action in public_assoc.permitted_actions:
- return True
- elif user is not None:
- # Loop through permitted_actions and if allowed return true:
- # Check permitted_actions associated with dataset through groups
- for group_dataset_assoc in dataset.groups:
- if self.components_are_associated( user = user, group = group_dataset_assoc.group ):
- if action in group_dataset_assoc.permitted_actions:
- return True
- return False # No user and dataset not in public group, or user lacks permission
+ if not user:
+ if action == self.permitted_actions.DATASET_ACCESS and action.action not in [ adra.action for adra in dataset.actions ]:
+ return True # anons only get access, and only if there are no roles required for the access action
+ # other actions (or if the dataset has roles defined for the access action) fall through to the false below
+ elif action.action not in [ adra.action for adra in dataset.actions ]:
+ if action.model == 'restrict':
+ return True # implicit access to restrict-style actions if the dataset does not have the action
+ # grant-style actions fall through to the false below
+ else:
+ # collect a user's roles and their groups' roles
+ user_roles = [ ura.role for ura in user.roles ]
+ for group in [ uga.group for uga in user.groups ]:
+ for role in [ gra.role for gra in group.roles ]:
+ if role not in user_roles:
+ user_roles.append( role )
+ user_role_ids = sorted( [ r.id for r in user_roles ] )
+ for adra in dataset.actions:
+ if action.action != adra.action:
+ continue
+ # the filter() returns a list of the dataset's role ids
+ # of which the user is not a member. so an empty list
+ # means the user has all of the required roles.
+ if not filter( lambda x: x not in user_role_ids, adra.role_ids ):
+ return True # user has all of the roles required to perform the action
+ break # fall through to the false. user is missing at least one required role
+ return False # default is to reject
def guess_derived_permissions_for_datasets( self, datasets=[] ):
- """Returns a list of group/action tuples for the output dataset based upon provided datasets"""
- intersect = None
- priority_access_perms = None
+ """Returns a dict of { action : [ role, role, ... ] } for the output dataset based upon provided datasets"""
+ perms = {}
for dataset in datasets:
- # Determine access groups for output datasets - these groups are the
- # intersection across all inputs. If we end up with no intersection
- # between inputs, then we rely on priorities
- if isinstance( dataset, self.model.HistoryDatasetAssociation ):
+ if not isinstance( dataset, self.model.Dataset ):
dataset = dataset.dataset
- assocs = [ assoc for assoc in dataset.groups ]
- for assoc in assocs:
- if priority_access_perms is None or priority_access_perms[0].priority < assoc.group.priority:
- priority_access_perms = ( assoc.group, assoc.permitted_actions )
- if intersect is None:
- intersect = [ (a.group, a.permitted_actions) for a in assocs ]
- else:
- # Intersect existing perms with new perms
- #access_assocs = filter( lambda x: x.group in [ a.group for a in access_assocs ], assocs )
- new_intersect = []
- for group, actions in intersect:
- matches = filter( lambda x: x.group == group, assocs )
- # Could a dataset ever have more than one GDA with the same group id?
- if len( matches ) > 1:
- log.error( "Unable to derive permissions, duplicate group_dataset_association rows exist for group %d, dataset %d" % (group.id, dataset.id) )
- elif len( matches ) == 1:
- # compare permitted_actions
- pa_intersect = filter( lambda x: x in actions, matches[0].permitted_actions )
- new_intersect.append( ( group, pa_intersect ) )
- intersect = new_intersect
- # If we have no groups left after intersection, take the highest priority group
- if not intersect:
- if priority_access_perms:
- intersect = [ priority_access_perms ]
- return intersect
- def get_group( self, id ):
- return self.model.Group.get( id )
- raise 'No valid method of retrieving requested group by id %s' % ( str( id ) )
- def get_group_by_name( self, name ):
- return self.model.Group.filter_by( name=name ).first()
- raise 'No valid method of retrieving requested group by name %s' % ( str( name ) )
- def create_group( self, **kwd ):
- rval = self.model.Group( **kwd )
- rval.flush()
- return rval
- raise 'No valid method of creating group with %s' % ( kwd )
+ these_perms = {}
+ # initialize blank perms
+ for a in self.get_actions():
+ these_perms[ a.action ] = []
+ # collect this dataset's perms
+ for adra in dataset.actions:
+ these_perms[ adra.action ] = adra.role_ids
+ # join or intersect this dataset's permissions with others
+ for action_name, role_ids in these_perms.items():
+ if action_name not in perms.keys():
+ perms[ action_name ] = role_ids
+ else:
+ if self.get_action( action_name ).model == 'grant':
+ # intersect existing roles with new roles
+ perms[ action_name ] = filter( lambda x: x in perms[ action_name ], role_ids )
+ elif self.get_action( action_name ).model == 'restrict':
+ # join existing roles with new roles
+ perms[ action_name ].extend( filter( lambda x: x not in perms[ action_name ], role_ids ) )
+ ##perms = [ ( k, tuple( v ) ) for k, v in perms.items() ] # a list of ( action, ( role_id, role_id, ... ) ) tuples
+ return perms
def associate_components( self, **kwd ):
- assert len( kwd ) == 2, 'You must specify exactly 2 Galaxy security components to associate.'
- if 'dataset' in kwd:
- if 'group' in kwd:
- return self.associate_group_dataset( kwd['group'], kwd['dataset'] )
- elif 'permissions' in kwd:
- return self.associate_group_dataset( kwd['permissions'][0], kwd['dataset'], kwd['permissions'][1] )
- elif 'user' in kwd:
+ ##assert len( kwd ) == 2, 'You must specify exactly 2 Galaxy security components to associate.'
+ if 'user' in kwd:
if 'group' in kwd:
return self.associate_user_group( kwd['user'], kwd['group'] )
+ elif 'role' in kwd:
+ return self.associate_user_role( kwd['user'], kwd['role'] )
+ elif 'role' in kwd:
+ if 'group' in kwd:
+ return self.associate_group_role( kwd['group'], kwd['role'] )
+ if 'action' in kwd:
+ if 'dataset' in kwd and 'roles' in kwd:
+ return self.associate_action_dataset_roles( kwd['action'], kwd['dataset'], kwd['roles'] )
raise 'No valid method of associating provided components: %s' % kwd
- def associate_group_dataset( self, group, dataset, permitted_actions=[ RBACAgent.permitted_actions.DATASET_ACCESS ] ):
- # HACK: The default permitted_actions should really not be used... need to find cases where this is done and correct it
- assoc = self.model.GroupDatasetAssociation( group, dataset, permitted_actions )
- assoc.flush()
- return assoc
def associate_user_group( self, user, group ):
assoc = self.model.UserGroupAssociation( user, group )
assoc.flush()
return assoc
- def create_private_user_group( self, user ):
- # Create private group
- group_name = "%s private group" % user.email
- group = self.model.Group( name=group_name, priority=10 )
- group.flush()
- # Add user to group
- self.associate_components( group=group, user=user )
- group.flush()
- return group
- def user_set_default_access( self, user, permissions = None, history = False, dataset = False ):
- if permissions is None:
- permissions = [ ( self.create_private_user_group( user ), self.permitted_actions.__dict__.values() ) ]
- if permissions is not None:
- # Delete all of the previous DefaultUserGroupAssociation
- for duga in user.default_groups: #this is the association not the actual group
- duga.delete()
- duga.flush()
- # Add the new DefaultUserGroupAssociation
- for group, permitted_actions in permissions:
- duga = self.model.DefaultUserGroupAssociation( user, group, permitted_actions )
- duga.flush()
+ def associate_user_role( self, user, role ):
+ assoc = self.model.UserRoleAssociation( user, role )
+ assoc.flush()
+ return assoc
+ def associate_group_role( self, group, role ):
+ assoc = self.model.GroupRoleAssociation( group, role )
+ assoc.flush()
+ return assoc
+ def associate_action_dataset_roles( self, action, dataset, roles ):
+ assoc = self.model.ActionDatasetRolesAssociation( action, dataset, roles )
+ assoc.flush()
+ return assoc
+ def create_private_user_role( self, user ):
+ # Create private role
+ role = self.model.Role( name=user.email, description='Private Role for ' + user.email, type=self.model.Role.types.PRIVATE )
+ role.flush()
+ # Add user to role
+ self.associate_components( role=role, user=user )
+ return role
+ def get_private_user_role( self, user, auto_create=False ):
+ role = self.model.Role.get_by( name=user.email, type=self.model.Role.types.PRIVATE )
+ if not role:
+ if auto_create:
+ return self.create_private_user_role( user )
+ else:
+ return None
+ def user_set_default_permissions( self, user, permissions = {}, history = False, dataset = False ):
+ if user is None:
+ return None
+ if not permissions:
+ permissions = { self.permitted_actions.DATASET_MANAGE_PERMISSIONS : [ self.get_private_user_role( user, auto_create=True ) ] }
+ # Delete all of the previous defaults
+ for dup in user.default_permissions:
+ dup.delete()
+ dup.flush()
+ # Add the new defaults (if any)
+ for action, roles in permissions.items():
+ if isinstance( action, Action ):
+ action = action.action
+ dup = self.model.DefaultUserPermissions( user, action, roles )
+ dup.flush()
if history:
for history in user.active_histories:
- self.history_set_default_access( history, permissions=permissions, dataset=dataset )
- def user_get_default_access( self, user ):
- return [ ( duga.group, duga.permitted_actions ) for duga in user.default_groups ]
- def history_set_default_access( self, history, permissions=None, dataset=False ):
- if permissions is None:
- if history.user:
- permissions = self.user_get_default_access( history.user )
- else:
- permissions = [ ( self.get_public_group(), self.permitted_actions.__dict__.values() ) ]
- if permissions is not None:
- # Delete all of the previous DefaultHistoryGroupAssociations
- for dhga in history.default_groups: #this is the association not the actual group
- dhga.delete()
- dhga.flush()
- # Add the new DefaultHistoryGroupAssociations
- for group, permitted_actions in permissions:
- dhga = self.model.DefaultHistoryGroupAssociation( history, group, permitted_actions )
- dhga.flush()
+ self.history_set_default_permissions( history, permissions=permissions, dataset=dataset )
+ def user_get_default_permissions( self, user ):
+ perms = {}
+ for dup in user.default_permissions:
+ perms[ dup.action ] = dup.role_ids
+ return perms
+ def history_set_default_permissions( self, history, permissions = {}, dataset = False, bypass_manage_permission = False ):
+ if not history.user:
+ return None # default permissions on a userless history are none
+ if not permissions:
+ permissions = self.user_get_default_permissions( history.user )
+ for dhp in history.default_permissions:
+ dhp.delete()
+ dhp.flush()
+ for action, roles in permissions.items():
+ if isinstance( action, Action ):
+ action = action.action
+ dhp = self.model.DefaultHistoryPermissions( history, action, roles )
+ dhp.flush()
if dataset:
- for hda in history.datasets:
- for hda2 in hda.dataset.history_associations:
- if history.user and hda2.history not in history.user.active_histories:
- # This will occur when a user logs in and has datasets in their previously-public history.
- self.set_dataset_permissions( hda.dataset, [ ( self.get_public_group(), [ self.permitted_actions.DATASET_ACCESS ] ) ] )
- #break
+ for hda_in_history in history.datasets:
+ if len( hda_in_history.dataset.library_associations ):
+ continue # dataset has a library association, don't change the permissions
+ if len( [ hda for hda in hda_in_history.dataset.history_associations if hda.history not in history.user.histories ] ):
+ continue # dataset has a history association in a history the user doesn't own, don't change the permissions
+ # bypass is used to change permissions of datasets in a userless history when logging in
+ if bypass_manage_permission or self.allow_action( history.user, self.permitted_actions.DATASET_MANAGE_PERMISSIONS, dataset=hda_in_history.dataset ):
+ self.set_dataset_permissions( hda_in_history.dataset, permissions )
+ def history_get_default_permissions( self, history ):
+ perms = {}
+ for dhp in history.default_permissions:
+ perms[ dhp.action ] = dhp.role_ids
+ return perms
+ def set_dataset_permissions( self, dataset, permissions={} ):
+ # to delete permission on an action, pass in a blank list of role ids with that action
+ for action, role_ids in permissions.items():
+ if isinstance( action, Action ):
+ action = action.action
+ for adra in dataset.actions:
+ if adra.action != action:
+ continue
+ if not role_ids:
+ adra.delete()
else:
- # At this point, we will have already created a gda for the public group and delete_assocs
- # is set to False in order to preserve it.
- if self.allow_action( history.user, self.permitted_actions.DATASET_MANAGE_PERMISSIONS, dataset=hda.dataset ):
- self.set_dataset_permissions( hda.dataset, permissions, delete_existing_assocs=False )
- def history_get_default_access( self, history ):
- return [ ( dhga.group, dhga.permitted_actions ) for dhga in history.default_groups ]
- def get_public_group( self ):
- return self.model.Group.get_public_group()
- def set_public_group( self, group ):
- return self.model.Group.set_public_group( group )
- def guess_public_group( self ):
- return self.model.Group.guess_public_group()
- def set_dataset_permissions( self, dataset, permissions, delete_existing_assocs=True ):
- """
- Apply permissions (a list of (group, permitted_action) tuples)
- to a dataset, removing any existing permissions. permissions can
- also be a list of GroupDatasetAssociations (for simplicity).
- """
- if isinstance( dataset, self.model.HistoryDatasetAssociation ):
- dataset = dataset.dataset
- if delete_existing_assocs:
- for gda in dataset.groups:
- gda.delete()
- gda.flush()
- if permissions and isinstance( permissions[0], self.model.GroupDatasetAssociation ):
- permissions = [ ( gda.group, gda.permitted_actions ) for gda in permissions ]
- for ptuple in permissions:
- self.associate_components( dataset=dataset, permissions=ptuple )
- def get_dataset_permissions( self, dataset, group_id=None ):
+ adra.set_roles( role_ids )
+ adra.flush()
+ break
+ else:
+ if role_ids:
+ self.associate_components( action=action, dataset=dataset, roles=role_ids )
+ def get_dataset_permissions( self, dataset ):
if not isinstance( dataset, self.model.Dataset ):
dataset = dataset.dataset
- if group_id is not None:
- return [ ( gda.group, gda.permitted_actions ) for gda in dataset.groups if gda.group_id == int(group_id) ][0]
- else:
- return [ ( gda.group, gda.permitted_actions ) for gda in dataset.groups ]
+ perms = {}
+ for k, v in self.model.Dataset.permitted_actions.items():
+ for adra in dataset.actions:
+ if adra.action != v.action:
+ continue
+ perms[ v.action ] = adra.role_ids
+ break
+ else:
+ perms[ v.action ] = []
+ return perms
+ def copy_dataset_permissions( self, src, dst ):
+ if not isinstance( src, self.model.Dataset ):
+ src = src.dataset
+ if not isinstance( dst, self.model.Dataset ):
+ dst = dst.dataset
+ self.set_dataset_permissions( dst, self.get_dataset_permissions( src ) )
+ def set_entity_role_associations( self, roles=[], users=[], groups=[], delete_existing_assocs=True ):
+ for role in roles:
+ if delete_existing_assocs:
+ for a in role.users + role.groups:
+ a.delete()
+ a.flush()
+ for user in users:
+ self.associate_components( user=user, role=role )
+ for group in groups:
+ self.associate_components( group=group, role=role )
def get_component_associations( self, **kwd ):
- # TODO, Nate: Make sure this method is functionally correct.
assert len( kwd ) == 2, 'You must specify exactly 2 Galaxy security components to check for associations.'
if 'dataset' in kwd:
- if 'group' in kwd:
- return self.model.GroupDatasetAssociation.filter_by( group_id=kwd['group'].id, dataset_id=kwd['dataset'].id ).first()
+ if 'action' in kwd:
+ return self.model.ActionDatasetRolesAssociation.filter_by( action = kwd['action'].action, dataset_id = kwd['dataset'].id ).first()
elif 'user' in kwd:
if 'group' in kwd:
- return self.model.UserGroupAssociation.filter_by( group_id=kwd['group'].id, user_id=kwd['user'].id ).first()
+ return self.model.UserGroupAssociation.filter_by( group_id = kwd['group'].id, user_id = kwd['user'].id ).first()
+ elif 'role' in kwd:
+ return self.model.UserRoleAssociation.filter_by( role_id = kwd['role'].id, user_id = kwd['user'].id ).first()
+ elif 'group' in kwd:
+ if 'role' in kwd:
+ return self.model.GroupRoleAssociation.filter_by( role_id = kwd['role'].id, group_id = kwd['group'].id ).first()
raise 'No valid method of associating provided components: %s' % kwd
- def dataset_has_group( self, dataset_id, group_id ):
- return bool( self.model.GroupDatasetAssociation.filter_by( group_id=group_id, dataset_id=dataset_id ).first() )
def check_folder_contents( self, user, entry ):
"""
Return true if there are any datasets under 'folder' that the
@@ -288,7 +307,6 @@ class GalaxyRBACAgent( RBACAgent ):
else:
raise 'Passed an illegal object to check_folder_contents: %s' % type( entry )
-
def get_permitted_actions( self, filter=None ):
'''Utility method to return a subset of RBACAgent's permitted actions'''
if filter is None:
diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py
index 0468ec23ec8..7fb41c9491c 100644
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -1202,7 +1202,7 @@ class Tool:
else: visible = False
ext = fields.pop(0).lower()
child_dataset = self.app.model.HistoryDatasetAssociation( extension=ext, parent_id=outdata.id, designation=designation, visible=visible, dbkey=outdata.dbkey, create_dataset=True )
- self.app.security_agent.set_dataset_permissions( child_dataset.dataset, outdata.dataset.groups )
+ self.app.security_agent.copy_dataset_permissions( outdata.dataset, child_dataset.dataset )
# Move data from temp location to dataset location
shutil.move( filename, child_dataset.file_name )
child_dataset.flush()
@@ -1239,7 +1239,7 @@ class Tool:
ext = fields.pop(0).lower()
# Create new primary dataset
primary_data = self.app.model.HistoryDatasetAssociation( extension=ext, designation=designation, visible=visible, dbkey=outdata.dbkey, create_dataset=True )
- self.app.security_agent.set_dataset_permissions( primary_data.dataset, outdata.dataset.groups )
+ self.app.security_agent.copy_dataset_permissions( outdata.dataset, primary_data.dataset )
primary_data.flush()
# Move data from temp location to dataset location
shutil.move( filename, primary_data.file_name )
diff --git a/lib/galaxy/tools/actions/__init__.py b/lib/galaxy/tools/actions/__init__.py
index ac146bca8b8..40fca521465 100644
--- a/lib/galaxy/tools/actions/__init__.py
+++ b/lib/galaxy/tools/actions/__init__.py
@@ -84,13 +84,13 @@ class DefaultToolAction( object ):
if data.dbkey not in [None, '?']:
input_dbkey = data.dbkey
- # Determine output dataset permitted_actions list
+ # Determine output dataset permission/roles list
existing_datasets = [ inp for inp in inp_data.values() if inp ]
if existing_datasets:
output_permissions = trans.app.security_agent.guess_derived_permissions_for_datasets( existing_datasets )
else:
# No valid inputs, we will use history defaults
- output_permissions = trans.app.security_agent.history_get_default_access( trans.history )
+ output_permissions = trans.app.security_agent.history_get_default_permissions( trans.history )
# Build name for output datasets based on tool name and input names
if len( input_names ) == 1:
on_text = input_names[0]
diff --git a/lib/galaxy/tools/actions/upload.py b/lib/galaxy/tools/actions/upload.py
index b33e1a1fbd8..d21809a116d 100644
--- a/lib/galaxy/tools/actions/upload.py
+++ b/lib/galaxy/tools/actions/upload.py
@@ -78,7 +78,7 @@ class UploadToolAction( object ):
def upload_empty(self, trans, err_code, err_msg):
data = trans.app.model.HistoryDatasetAssociation( create_dataset=True )
- trans.app.security_agent.set_dataset_permissions( data.dataset, trans.app.security_agent.history_get_default_access( trans.history ) )
+ trans.app.security_agent.set_dataset_permissions( data.dataset, trans.app.security_agent.history_get_default_permissions( trans.history ) )
data.name = err_code
data.extension = "txt"
data.dbkey = "?"
@@ -172,7 +172,7 @@ class UploadToolAction( object ):
info = 'uploaded %s file' %data_type
data = trans.app.model.HistoryDatasetAssociation( history = trans.history, extension = ext, create_dataset = True )
- trans.app.security_agent.set_dataset_permissions( data.dataset, trans.app.security_agent.history_get_default_access( trans.history ) )
+ trans.app.security_agent.set_dataset_permissions( data.dataset, trans.app.security_agent.history_get_default_permissions( trans.history ) )
data.name = file_name
data.dbkey = dbkey
data.info = info
diff --git a/lib/galaxy/tools/parameters/basic.py b/lib/galaxy/tools/parameters/basic.py
index 3f9d7d6d702..2a11d79385e 100644
--- a/lib/galaxy/tools/parameters/basic.py
+++ b/lib/galaxy/tools/parameters/basic.py
@@ -984,38 +984,6 @@ class DataToolParameter( ToolParameter ):
TODO: The following must be fixed to test correctly for the new security_check tag in the DataToolParameter ( the last test below is broken )
Nate's next passs at the dataset security stuff will dramatically alter this anyway.
-
- >>> # Mock up a history (not connected to database)
- >>> from galaxy.model import History, HistoryDatasetAssociation, User, Group
- >>> from galaxy.util.bunch import Bunch
- >>> from galaxy.security import GalaxyRBACAgent
- >>> import galaxy.model
- >>> security_agent = GalaxyRBACAgent( galaxy.model )
- >>> hist = History()
- >>> hist.flush()
- >>> group = Group( 'test' )
- >>> group.flush()
- >>> Group.public_id = group.id
- >>> dataset1 = HistoryDatasetAssociation( id=1, extension='txt', create_dataset=True )
- >>> dataset2 = HistoryDatasetAssociation( id=2, extension='bed', create_dataset=True )
- >>> security_agent.set_dataset_permissions( dataset2, [ ( group, security_agent.permitted_actions.__dict__.values() ) ] )
- >>> dataset3 = HistoryDatasetAssociation( id=3, extension='fasta', create_dataset=True )
- >>> dataset4 = HistoryDatasetAssociation( id=4, extension='png', create_dataset=True )
- >>> dataset5 = HistoryDatasetAssociation( id=5, extension='interval', create_dataset=True )
- >>> security_agent.set_dataset_permissions( dataset5, [ ( group, security_agent.permitted_actions.__dict__.values() ) ] )
- >>> hist.add_dataset( dataset1 )
- >>> hist.add_dataset( dataset2 )
- >>> hist.add_dataset( dataset3 )
- >>> hist.add_dataset( dataset4 )
- >>> hist.add_dataset( dataset5 )
- >>> p = DataToolParameter( None, XML( '