root/galaxy-central/lib/galaxy/security/__init__.py @ 2

リビジョン 2, 54.2 KB (コミッタ: hatakeyama, 14 年 前)

import galaxy-central

行番号 
1"""
2Galaxy Security
3
4"""
5import logging, socket, operator
6from datetime import datetime, timedelta
7from galaxy.util.bunch import Bunch
8from galaxy.util import listify
9from galaxy.model.orm import *
10
11log = logging.getLogger(__name__)
12
13class Action( object ):
14    def __init__( self, action, description, model ):
15        self.action = action
16        self.description = description
17        self.model = model
18
19class RBACAgent:
20    """Class that handles galaxy security"""
21    permitted_actions = Bunch(
22        DATASET_MANAGE_PERMISSIONS = Action( "manage permissions", "Role members can manage the roles associated with permissions on this dataset", "grant" ),
23        DATASET_ACCESS = Action( "access", "Role members can import this dataset into their history for analysis", "restrict" ),
24        LIBRARY_ACCESS = Action( "access library", "Restrict access to this library to only role members", "restrict" ),
25        LIBRARY_ADD = Action( "add library item", "Role members can add library items to this library item", "grant" ),
26        LIBRARY_MODIFY = Action( "modify library item", "Role members can modify this library item", "grant" ),
27        LIBRARY_MANAGE = Action( "manage library permissions", "Role members can manage roles associated with permissions on this library item", "grant" ),
28        # Request type permissions
29        REQUEST_TYPE_ACCESS = Action( "access request_type", "Restrict access to this request_type to only role members", "restrict" )
30       
31    )
32    def get_action( self, name, default=None ):
33        """Get a permitted action by its dict key or action name"""
34        for k, v in self.permitted_actions.items():
35            if k == name or v.action == name:
36                return v
37        return default
38    def get_actions( self ):
39        """Get all permitted actions as a list of Action objects"""
40        return self.permitted_actions.__dict__.values()
41    def get_item_actions( self, action, item ):
42        raise 'No valid method of retrieving action (%s) for item %s.' % ( action, item )
43    def guess_derived_permissions_for_datasets( self, datasets = [] ):
44        raise "Unimplemented Method"
45    def can_access_dataset( self, roles, dataset ):
46        raise "Unimplemented Method"
47    def can_manage_dataset( self, roles, dataset ):
48        raise "Unimplemented Method"
49    def can_access_library( self, roles, library ):
50        raise "Unimplemented Method"
51    def can_add_library_item( self, roles, item ):
52        raise "Unimplemented Method"
53    def can_modify_library_item( self, roles, item ):
54        raise "Unimplemented Method"
55    def can_manage_library_item( self, roles, item ):
56        raise "Unimplemented Method"
57    def associate_components( self, **kwd ):
58        raise 'No valid method of associating provided components: %s' % kwd
59    def create_private_user_role( self, user ):
60        raise "Unimplemented Method"
61    def get_private_user_role( self, user ):
62        raise "Unimplemented Method"
63    def user_set_default_permissions( self, user, permissions={}, history=False, dataset=False ):
64        raise "Unimplemented Method"
65    def history_set_default_permissions( self, history, permissions=None, dataset=False, bypass_manage_permission=False ):
66        raise "Unimplemented Method"
67    def set_all_dataset_permissions( self, dataset, permissions ):
68        raise "Unimplemented Method"
69    def set_dataset_permission( self, dataset, permission ):
70        raise "Unimplemented Method"
71    def set_all_library_permissions( self, dataset, permissions ):
72        raise "Unimplemented Method"
73    def library_is_public( self, library ):
74        raise "Unimplemented Method"
75    def make_library_public( self, library ):
76        raise "Unimplemented Method"
77    def folder_is_public( self, library ):
78        raise "Unimplemented Method"
79    def make_folder_public( self, folder, count=0 ):
80        raise "Unimplemented Method"
81    def dataset_is_public( self, dataset ):
82        raise "Unimplemented Method"
83    def make_dataset_public( self, dataset ):
84        raise "Unimplemented Method"
85    def get_permissions( self, library_dataset ):
86        raise "Unimplemented Method"
87    def get_legitimate_roles( self, trans, item, cntrller ):
88        raise "Unimplemented Method"
89    def derive_roles_from_access( self, trans, item_id, cntrller, library=False, **kwd ):
90        raise "Unimplemented Method"
91    def get_component_associations( self, **kwd ):
92        raise "Unimplemented Method"
93    def components_are_associated( self, **kwd ):
94        return bool( self.get_component_associations( **kwd ) )
95    def convert_permitted_action_strings( self, permitted_action_strings ):
96        """
97        When getting permitted actions from an untrusted source like a
98        form, ensure that they match our actual permitted actions.
99        """
100        return filter( lambda x: x is not None, [ self.permitted_actions.get( action_string ) for action_string in permitted_action_strings ] )
101
102class GalaxyRBACAgent( RBACAgent ):
103    def __init__( self, model, permitted_actions=None ):
104        self.model = model
105        if permitted_actions:
106            self.permitted_actions = permitted_actions
107        # List of "library_item" objects and their associated permissions and info template objects
108        self.library_item_assocs = (
109            ( self.model.Library, self.model.LibraryPermissions ),
110            ( self.model.LibraryFolder, self.model.LibraryFolderPermissions ),
111            ( self.model.LibraryDataset, self.model.LibraryDatasetPermissions ),
112            ( self.model.LibraryDatasetDatasetAssociation, self.model.LibraryDatasetDatasetAssociationPermissions ) )
113    @property
114    def sa_session( self ):
115        """Returns a SQLAlchemy session"""
116        return self.model.context
117    def get_legitimate_roles( self, trans, item, cntrller ):
118        """
119        Return a sorted list of legitimate roles that can be associated with a permission on
120        item where item is a Library or a Dataset.  The cntrller param is the controller from
121        which the request is sent.  We cannot use trans.user_is_admin() because the controller is
122        what is important since admin users do not necessarily have permission to do things
123        on items outside of the admin view.
124        If cntrller is from the admin side ( e.g., library_admin ):
125            -if item is public, all roles, including private roles, are legitimate.
126            -if item is restricted, legitimate roles are derived from the users and groups associated
127            with each role that is associated with the access permission ( i.e., DATASET_MANAGE_PERMISSIONS or
128            LIBRARY_MANAGE ) on item.  Legitimate roles will include private roles.
129        If cntrller is not from the admin side ( e.g., root, library ):
130            -if item is public, all non-private roles, except for the current user's private role,
131            are legitimate.
132            -if item is restricted, legitimate roles are derived from the users and groups associated
133            with each role that is associated with the access permission on item.  Private roles, except
134            for the current user's private role, will be excluded.
135        """
136        admin_controller = cntrller in [ 'library_admin' ]
137        def sort_by_attr( seq, attr ):
138            """
139            Sort the sequence of objects by object's attribute
140            Arguments:
141            seq  - the list or any sequence (including immutable one) of objects to sort.
142            attr - the name of attribute to sort by
143            """
144            # Use the "Schwartzian transform"
145            # Create the auxiliary list of tuples where every i-th tuple has form
146            # (seq[i].attr, i, seq[i]) and sort it. The second item of tuple is needed not
147            # only to provide stable sorting, but mainly to eliminate comparison of objects
148            # (which can be expensive or prohibited) in case of equal attribute values.
149            intermed = map( None, map( getattr, seq, ( attr, ) * len( seq ) ), xrange( len( seq ) ), seq )
150            intermed.sort()
151            return map( operator.getitem, intermed, ( -1, ) * len( intermed ) )
152        roles = set()
153        if ( isinstance( item, self.model.Library ) and self.library_is_public( item ) ) or \
154            ( isinstance( item, self.model.Dataset ) and self.dataset_is_public( item ) ):
155            if not trans.user:
156                return trans.sa_session.query( trans.app.model.Role ) \
157                                       .filter( and_( self.model.Role.table.c.deleted==False,
158                                                      self.model.Role.table.c.type != self.model.Role.types.PRIVATE,
159                                                      self.model.Role.table.c.type != self.model.Role.types.SHARING ) ) \
160                                       .order_by( self.model.Role.table.c.name )
161            if admin_controller:
162                # The library is public and the user is an admin, so all roles are legitimate
163                for role in trans.sa_session.query( trans.app.model.Role ) \
164                                            .filter( self.model.Role.table.c.deleted==False ) \
165                                            .order_by( self.model.Role.table.c.name ):
166                    roles.add( role )
167                return sort_by_attr( [ role for role in roles ], 'name' )
168            else:
169                # Add the current user's private role
170                roles.add( self.get_private_user_role( trans.user ) )
171                # Add the current user's sharing roles
172                for role in self.get_sharing_roles( trans.user ):
173                    roles.add( role )
174                # Add all remaining non-private, non-sharing roles
175                for role in trans.sa_session.query( trans.app.model.Role ) \
176                                            .filter( and_( self.model.Role.table.c.deleted==False,
177                                                           self.model.Role.table.c.type != self.model.Role.types.PRIVATE,
178                                                           self.model.Role.table.c.type != self.model.Role.types.SHARING ) ) \
179                                            .order_by( self.model.Role.table.c.name ):
180                    roles.add( role )
181                return sort_by_attr( [ role for role in roles ], 'name' )     
182        # If item has roles associated with the access permission, we need to start with them.
183        access_roles = item.get_access_roles( trans )
184        for role in access_roles:
185            if admin_controller or self.ok_to_display( trans.user, role ):
186                roles.add( role )
187                # Each role potentially has users.  We need to find all roles that each of those users have.
188                for ura in role.users:
189                    user = ura.user
190                    for ura2 in user.roles:
191                        if admin_controller or self.ok_to_display( trans.user, ura2.role ):
192                            roles.add( ura2.role )
193                # Each role also potentially has groups which, in turn, have members ( users ).  We need to
194                # find all roles that each group's members have.
195                for gra in role.groups:
196                    group = gra.group
197                    for uga in group.users:
198                        user = uga.user
199                        for ura in user.roles:
200                            if admin_controller or self.ok_to_display( trans.user, ura.role ):
201                                roles.add( ura.role )
202        return sort_by_attr( [ role for role in roles ], 'name' )
203    def ok_to_display( self, user, role ):
204        """
205        Method for checking if:
206        - a role is private and is the current user's private role
207        - a role is a sharing role and belongs to the current user
208        """
209        if user:
210            if role.type == self.model.Role.types.PRIVATE:
211                return role == self.get_private_user_role( user )
212            if role.type == self.model.Role.types.SHARING:
213                return role in self.get_sharing_roles( user )
214            # If role.type is neither private nor sharing, it's ok to display
215            return True
216        return role.type != self.model.Role.types.PRIVATE and role.type != self.model.Role.types.SHARING
217    def allow_action( self, roles, action, item ):
218        """
219        Method for checking a permission for the current user ( based on roles ) to perform a
220        specific action on an item, which must be one of:
221        Dataset, Library, LibraryFolder, LibraryDataset, LibraryDatasetDatasetAssociation
222        """
223        item_actions = self.get_item_actions( action, item )
224        if not item_actions:
225            return action.model == 'restrict'
226        ret_val = False
227        # For DATASET_ACCESS only, user must have ALL associated roles
228        if action == self.permitted_actions.DATASET_ACCESS:
229            for item_action in item_actions:
230                if item_action.role not in roles:
231                    break
232            else:
233                ret_val = True
234        # For remaining actions, user must have any associated role
235        else:
236            for item_action in item_actions:
237                if item_action.role in roles:
238                    ret_val = True
239                    break
240        return ret_val
241    def can_access_dataset( self, roles, dataset ):
242        return self.dataset_is_public( dataset ) or self.allow_action( roles, self.permitted_actions.DATASET_ACCESS, dataset )
243    def can_manage_dataset( self, roles, dataset ):
244        return self.allow_action( roles, self.permitted_actions.DATASET_MANAGE_PERMISSIONS, dataset )
245    def can_access_library( self, roles, library ):
246        return self.library_is_public( library ) or self.allow_action( roles, self.permitted_actions.LIBRARY_ACCESS, library )
247    def can_access_library_item( self, roles, item, user ):
248        if type( item ) == self.model.Library:
249            return self.can_access_library( roles, item )
250        elif type( item ) == self.model.LibraryFolder:
251            return self.can_access_library( roles, item.parent_library ) and self.check_folder_contents( user, roles, item )[0]
252        elif type( item ) == self.model.LibraryDataset:
253            return self.can_access_library( roles, item.folder.parent_library ) and self.can_access_dataset( roles, item.library_dataset_dataset_association.dataset )
254        elif type( item ) == self.model.LibraryDatasetDatasetAssociation:
255            return self.can_access_library( roles, item.library_dataset.folder.parent_library ) and self.can_access_dataset( roles, item.dataset )
256        else:
257            log.warning( 'Unknown library item type: %s' % type ( item ) )
258            return False
259    def can_add_library_item( self, roles, item ):
260        return self.allow_action( roles, self.permitted_actions.LIBRARY_ADD, item )
261    def can_modify_library_item( self, roles, item ):
262        return self.allow_action( roles, self.permitted_actions.LIBRARY_MODIFY, item )
263    def can_manage_library_item( self, roles, item ):
264        return self.allow_action( roles, self.permitted_actions.LIBRARY_MANAGE, item )
265    def get_item_actions( self, action, item ):
266        # item must be one of: Dataset, Library, LibraryFolder, LibraryDataset, LibraryDatasetDatasetAssociation
267        return [ permission for permission in item.actions if permission.action == action.action ]
268    def guess_derived_permissions_for_datasets( self, datasets=[] ):
269        """Returns a dict of { action : [ role, role, ... ] } for the output dataset based upon provided datasets"""
270        perms = {}
271        for dataset in datasets:
272            if not isinstance( dataset, self.model.Dataset ):
273                dataset = dataset.dataset
274            these_perms = {}
275            # initialize blank perms
276            for action in self.get_actions():
277                these_perms[ action ] = []
278            # collect this dataset's perms
279            these_perms = self.get_permissions( dataset )
280            # join or intersect this dataset's permissions with others
281            for action, roles in these_perms.items():
282                if action not in perms.keys():
283                    perms[ action ] = roles
284                else:
285                    if action.model == 'grant':
286                        # intersect existing roles with new roles
287                        perms[ action ] = filter( lambda x: x in perms[ action ], roles )
288                    elif action.model == 'restrict':
289                        # join existing roles with new roles
290                        perms[ action ].extend( filter( lambda x: x not in perms[ action ], roles ) )
291        return perms
292    def associate_components( self, **kwd ):
293        if 'user' in kwd:
294            if 'group' in kwd:
295                return self.associate_user_group( kwd['user'], kwd['group'] )
296            elif 'role' in kwd:
297                return self.associate_user_role( kwd['user'], kwd['role'] )
298        elif 'role' in kwd:
299            if 'group' in kwd:
300                return self.associate_group_role( kwd['group'], kwd['role'] )
301        if 'action' in kwd:
302            if 'dataset' in kwd and 'role' in kwd:
303                return self.associate_action_dataset_role( kwd['action'], kwd['dataset'], kwd['role'] )
304        raise 'No valid method of associating provided components: %s' % kwd
305    def associate_user_group( self, user, group ):
306        assoc = self.model.UserGroupAssociation( user, group )
307        self.sa_session.add( assoc )
308        self.sa_session.flush()
309        return assoc
310    def associate_user_role( self, user, role ):
311        assoc = self.model.UserRoleAssociation( user, role )
312        self.sa_session.add( assoc )
313        self.sa_session.flush()
314        return assoc
315    def associate_group_role( self, group, role ):
316        assoc = self.model.GroupRoleAssociation( group, role )
317        self.sa_session.add( assoc )
318        self.sa_session.flush()
319        return assoc
320    def associate_action_dataset_role( self, action, dataset, role ):
321        assoc = self.model.DatasetPermissions( action, dataset, role )
322        self.sa_session.add( assoc )
323        self.sa_session.flush()
324        return assoc
325    def create_private_user_role( self, user ):
326        # Create private role
327        role = self.model.Role( name=user.email, description='Private Role for ' + user.email, type=self.model.Role.types.PRIVATE )
328        self.sa_session.add( role )
329        self.sa_session.flush()
330        # Add user to role
331        self.associate_components( role=role, user=user )
332        return role
333    def get_private_user_role( self, user, auto_create=False ):
334        role = self.sa_session.query( self.model.Role ) \
335                              .filter( and_( self.model.Role.table.c.name == user.email,
336                                             self.model.Role.table.c.type == self.model.Role.types.PRIVATE ) ) \
337                              .first()
338        if not role:
339            if auto_create:
340                return self.create_private_user_role( user )
341            else:
342                return None
343        return role
344    def get_sharing_roles( self, user ):
345        return self.sa_session.query( self.model.Role ) \
346                              .filter( and_( ( self.model.Role.table.c.name ).like( "Sharing role for: %" + user.email + "%" ),
347                                             self.model.Role.table.c.type == self.model.Role.types.SHARING ) )
348    def user_set_default_permissions( self, user, permissions={}, history=False, dataset=False, bypass_manage_permission=False, default_access_private = False ):
349        # bypass_manage_permission is used to change permissions of datasets in a userless history when logging in
350        flush_needed = False
351        if user is None:
352            return None
353        if not permissions:
354            #default permissions
355            permissions = { self.permitted_actions.DATASET_MANAGE_PERMISSIONS : [ self.get_private_user_role( user, auto_create=True ) ] }
356            #new_user_dataset_access_role_default_private is set as True in config file
357            if default_access_private:
358                permissions[ self.permitted_actions.DATASET_ACCESS ] = permissions.values()[ 0 ]
359        # Delete all of the current default permissions for the user
360        for dup in user.default_permissions:
361            self.sa_session.delete( dup )
362            flush_needed = True
363        # Add the new default permissions for the user
364        for action, roles in permissions.items():
365            if isinstance( action, Action ):
366                action = action.action
367            for dup in [ self.model.DefaultUserPermissions( user, action, role ) for role in roles ]:
368                self.sa_session.add( dup )
369                flush_needed = True
370        if flush_needed:
371            self.sa_session.flush()
372        if history:
373            for history in user.active_histories:
374                self.history_set_default_permissions( history, permissions=permissions, dataset=dataset, bypass_manage_permission=bypass_manage_permission )
375    def user_get_default_permissions( self, user ):
376        permissions = {}
377        for dup in user.default_permissions:
378            action = self.get_action( dup.action )
379            if action in permissions:
380                permissions[ action ].append( dup.role )
381            else:
382                permissions[ action ] = [ dup.role ]
383        return permissions
384    def history_set_default_permissions( self, history, permissions={}, dataset=False, bypass_manage_permission=False ):
385        # bypass_manage_permission is used to change permissions of datasets in a user-less history when logging in
386        flush_needed = False
387        user = history.user
388        if not user:
389            # default permissions on a user-less history are None
390            return None
391        if not permissions:
392            permissions = self.user_get_default_permissions( user )
393        # Delete all of the current default permission for the history
394        for dhp in history.default_permissions:
395            self.sa_session.delete( dhp )
396            flush_needed = True
397        # Add the new default permissions for the history
398        for action, roles in permissions.items():
399            if isinstance( action, Action ):
400                action = action.action
401            for dhp in [ self.model.DefaultHistoryPermissions( history, action, role ) for role in roles ]:
402                self.sa_session.add( dhp )
403                flush_needed = True
404        if flush_needed:
405            self.sa_session.flush()
406        if dataset:
407            # Only deal with datasets that are not purged
408            for hda in history.activatable_datasets:
409                dataset = hda.dataset
410                if dataset.library_associations:
411                    # Don't change permissions on a dataset associated with a library
412                    continue
413                if [ assoc for assoc in dataset.history_associations if assoc.history not in user.histories ]:
414                    # Don't change permissions on a dataset associated with a history not owned by the user
415                    continue
416                if bypass_manage_permission or self.can_manage_dataset( user.all_roles(), dataset ):
417                    self.set_all_dataset_permissions( dataset, permissions )
418    def history_get_default_permissions( self, history ):
419        permissions = {}
420        for dhp in history.default_permissions:
421            action = self.get_action( dhp.action )
422            if action in permissions:
423                permissions[ action ].append( dhp.role )
424            else:
425                permissions[ action ] = [ dhp.role ]
426        return permissions
427    def set_all_dataset_permissions( self, dataset, permissions={} ):
428        """
429        Set new permissions on a dataset, eliminating all current permissions
430        permissions looks like: { Action : [ Role, Role ] }
431        """
432        flush_needed = False
433        # Delete all of the current permissions on the dataset
434        for dp in dataset.actions:
435            self.sa_session.delete( dp )
436            flush_needed = True
437        # Add the new permissions on the dataset
438        for action, roles in permissions.items():
439            if isinstance( action, Action ):
440                action = action.action
441            for dp in [ self.model.DatasetPermissions( action, dataset, role ) for role in roles ]:
442                self.sa_session.add( dp )
443                flush_needed = True
444        if flush_needed:
445            self.sa_session.flush()
446    def set_dataset_permission( self, dataset, permission={} ):
447        """
448        Set a specific permission on a dataset, leaving all other current permissions on the dataset alone
449        permissions looks like: { Action : [ Role, Role ] }
450        """
451        flush_needed = False
452        for action, roles in permission.items():
453            if isinstance( action, Action ):
454                action = action.action
455            # Delete the current specific permission on the dataset if one exists
456            for dp in dataset.actions:
457                if dp.action == action:
458                    self.sa_session.delete( dp )
459                    flush_needed = True
460            # Add the new specific permission on the dataset
461            for dp in [ self.model.DatasetPermissions( action, dataset, role ) for role in roles ]:
462                self.sa_session.add( dp )
463                flush_needed = True
464        if flush_needed:
465            self.sa_session.flush()
466    def get_permissions( self, item ):
467        """
468        Return a dictionary containing the actions and associated roles on item
469        where item is one of Library, LibraryFolder, LibraryDatasetDatasetAssociation,
470        LibraryDataset, Dataset.  The dictionary looks like: { Action : [ Role, Role ] }.
471        """
472        permissions = {}
473        for item_permission in item.actions:
474            action = self.get_action( item_permission.action )
475            if action in permissions:
476                permissions[ action ].append( item_permission.role )
477            else:
478                permissions[ action ] = [ item_permission.role ]
479        return permissions
480    def copy_dataset_permissions( self, src, dst ):
481        if not isinstance( src, self.model.Dataset ):
482            src = src.dataset
483        if not isinstance( dst, self.model.Dataset ):
484            dst = dst.dataset
485        self.set_all_dataset_permissions( dst, self.get_permissions( src ) )
486    def privately_share_dataset( self, dataset, users = [] ):
487        intersect = None
488        for user in users:
489            roles = [ ura.role for ura in user.roles if ura.role.type == self.model.Role.types.SHARING ]
490            if intersect is None:
491                intersect = roles
492            else:
493                new_intersect = []
494                for role in roles:
495                    if role in intersect:
496                        new_intersect.append( role )
497                intersect = new_intersect
498        sharing_role = None
499        if intersect:
500            for role in intersect:
501                if not filter( lambda x: x not in users, [ ura.user for ura in role.users ] ):
502                    # only use a role if it contains ONLY the users we're sharing with
503                    sharing_role = role
504                    break
505        if sharing_role is None:
506            sharing_role = self.model.Role( name = "Sharing role for: " + ", ".join( [ u.email for u in users ] ),
507                                            type = self.model.Role.types.SHARING )
508            self.sa_session.add( sharing_role )
509            self.sa_session.flush()
510            for user in users:
511                self.associate_components( user=user, role=sharing_role )
512        self.set_dataset_permission( dataset, { self.permitted_actions.DATASET_ACCESS : [ sharing_role ] } )
513    def set_all_library_permissions( self, library_item, permissions={} ):
514        # Set new permissions on library_item, eliminating all current permissions
515        flush_needed = False
516        for role_assoc in library_item.actions:
517            self.sa_session.delete( role_assoc )
518            flush_needed = True
519        # Add the new permissions on library_item
520        for item_class, permission_class in self.library_item_assocs:
521            if isinstance( library_item, item_class ):
522                for action, roles in permissions.items():
523                    if isinstance( action, Action ):
524                        action = action.action
525                    for role_assoc in [ permission_class( action, library_item, role ) for role in roles ]:
526                        self.sa_session.add( role_assoc )
527                        flush_needed = True
528                    if isinstance( library_item, self.model.LibraryDatasetDatasetAssociation ) and \
529                        action == self.permitted_actions.LIBRARY_MANAGE.action:
530                        # Handle the special case when we are setting the LIBRARY_MANAGE_PERMISSION on a
531                        # library_dataset_dataset_association since the roles need to be applied to the
532                        # DATASET_MANAGE_PERMISSIONS permission on the associated dataset
533                        permissions = {}
534                        permissions[ self.permitted_actions.DATASET_MANAGE_PERMISSIONS ] = roles
535                        self.set_dataset_permission( library_item.dataset, permissions )
536        if flush_needed:
537            self.sa_session.flush()
538    def library_is_public( self, library, contents=False ):
539        if contents:
540            # Check all contained folders and datasets to find any that are not public
541            if not self.folder_is_public( library.root_folder ):
542                return False
543        # A library is considered public if there are no "access" actions associated with it.
544        return self.permitted_actions.LIBRARY_ACCESS.action not in [ a.action for a in library.actions ]
545    def make_library_public( self, library, contents=False ):
546        flush_needed = False
547        if contents:
548            # Make all contained folders (include deleted folders, but not purged folders), public
549            self.make_folder_public( library.root_folder )
550        # A library is considered public if there are no LIBRARY_ACCESS actions associated with it.
551        for lp in library.actions:
552            if lp.action == self.permitted_actions.LIBRARY_ACCESS.action:
553                self.sa_session.delete( lp )
554                flush_needed = True
555        if flush_needed:
556            self.sa_session.flush()
557    def folder_is_public( self, folder ):
558        for sub_folder in folder.folders:
559            if not self.folder_is_public( sub_folder ):
560                return False
561        for library_dataset in folder.datasets:
562            if not self.dataset_is_public( library_dataset.library_dataset_dataset_association.dataset ):
563                return False
564        return True
565    def make_folder_public( self, folder ):
566        # Make all of the contents (include deleted contents, but not purged contents) of folder public
567        for sub_folder in folder.folders:
568            if not sub_folder.purged:
569                self.make_folder_public( sub_folder )
570        for library_dataset in folder.datasets:
571            dataset = library_dataset.library_dataset_dataset_association.dataset
572            if not dataset.purged and not self.dataset_is_public( dataset ):
573                self.make_dataset_public( dataset )
574    def dataset_is_public( self, dataset ):
575        # A dataset is considered public if there are no "access" actions associated with it.  Any
576        # other actions ( 'manage permissions', 'edit metadata' ) are irrelevant.
577        return self.permitted_actions.DATASET_ACCESS.action not in [ a.action for a in dataset.actions ]
578    def make_dataset_public( self, dataset ):
579        # A dataset is considered public if there are no "access" actions associated with it.  Any
580        # other actions ( 'manage permissions', 'edit metadata' ) are irrelevant.
581        flush_needed = False
582        for dp in dataset.actions:
583            if dp.action == self.permitted_actions.DATASET_ACCESS.action:
584                self.sa_session.delete( dp )
585                flush_needed = True
586        if flush_needed:
587            self.sa_session.flush()
588    def derive_roles_from_access( self, trans, item_id, cntrller, library=False, **kwd ):
589        # Check the access permission on a dataset.  If library is true, item_id refers to a library.  If library
590        # is False, item_id refers to a dataset ( item_id must currently be decoded before being sent ).  The
591        # cntrller param is the calling controller, which needs to be passed to get_legitimate_roles().
592        msg = ''
593        permissions = {}
594        # accessible will be True only if at least 1 user has every role in DATASET_ACCESS_in
595        accessible = False
596        # legitimate will be True only if all roles in DATASET_ACCESS_in are in the set of roles returned from
597        # get_legitimate_roles()
598        legitimate = False
599        # private_role_found will be true only if more than 1 role is being associated with the DATASET_ACCESS
600        # permission on item, and at least 1 of the roles is private.
601        private_role_found = False
602        error = False
603        for k, v in get_permitted_actions( filter='DATASET' ).items():
604            in_roles = [ self.sa_session.query( self.model.Role ).get( x ) for x in listify( kwd.get( k + '_in', [] ) ) ]
605            if v == self.permitted_actions.DATASET_ACCESS and in_roles:
606                if library:
607                    item = self.model.Library.get( item_id )
608                else:
609                    item = self.model.Dataset.get( item_id )
610                if ( library and not self.library_is_public( item ) ) or ( not library and not self.dataset_is_public( item ) ):
611                    # Ensure that roles being associated with DATASET_ACCESS are a subset of the legitimate roles
612                    # derived from the roles associated with the access permission on item if it's not public.  This
613                    # will keep ill-legitimate roles from being associated with the DATASET_ACCESS permission on the
614                    # dataset (i.e., in the case where item is a library, if Role1 is associated with LIBRARY_ACCESS,
615                    # then only those users that have Role1 should be associated with DATASET_ACCESS.
616                    legitimate_roles = self.get_legitimate_roles( trans, item, cntrller )
617                    ill_legitimate_roles = []
618                    for role in in_roles:
619                        if role not in legitimate_roles:
620                            ill_legitimate_roles.append( role )
621                    if ill_legitimate_roles:
622                        # This condition should never occur since ill-legitimate roles are filtered out of the set of
623                        # roles displayed on the forms, but just in case there is a bug somewhere that incorrectly
624                        # filters, we'll display this message.
625                        error = True
626                        msg += "The following roles are not associated with users that have the 'access' permission on this "
627                        msg += "item, so they were incorrectly displayed: "
628                        for role in ill_legitimate_roles:
629                            msg += "%s, " % role.name
630                        msg = msg.rstrip( ", " )
631                        new_in_roles = []
632                        for role in in_roles:
633                            if role in legitimate_roles:
634                                new_in_roles.append( role )
635                        in_roles = new_in_roles
636                    else:
637                        legitimate = True
638                if len( in_roles ) > 1:
639                    # At least 1 user must have every role associated with the access
640                    # permission on this dataset, or the dataset is not accessible.
641                    # Since we have more than 1 role, none of them can be private.
642                    for role in in_roles:
643                        if role.type == self.model.Role.types.PRIVATE:
644                            private_role_found = True
645                            break
646                if len( in_roles ) == 1:
647                    accessible = True
648                else:
649                    # At least 1 user must have every role associated with the access
650                    # permission on this dataset, or the dataset is not accessible.
651                    in_roles_set = set()
652                    for role in in_roles:
653                        in_roles_set.add( role )
654                    users_set = set()
655                    for role in in_roles:
656                        for ura in role.users:
657                            users_set.add( ura.user )
658                        for gra in role.groups:
659                            group = gra.group
660                            for uga in group.users:
661                                users_set.add( uga.user )
662                    # Make sure that at least 1 user has every role being associated with the dataset.
663                    for user in users_set:
664                        user_roles_set = set()
665                        for ura in user.roles:
666                            user_roles_set.add( ura.role )
667                        if in_roles_set.issubset( user_roles_set ):
668                            accessible = True
669                            break
670                if private_role_found or not accessible:
671                    error = True
672                    # Don't set the permissions for DATASET_ACCESS if inaccessible or multiple roles with
673                    # at least 1 private, but set all other permissions.
674                    permissions[ self.get_action( v.action ) ] = []
675                    msg = "At least 1 user must have every role associated with accessing datasets.  "
676                    if private_role_found:
677                        msg += "Since you are associating more than 1 role, no private roles are allowed."
678                    if not accessible:
679                        msg += "The roles you attempted to associate for access would make the datasets in-accessible by everyone."
680                else:
681                    permissions[ self.get_action( v.action ) ] = in_roles
682            else:
683                permissions[ self.get_action( v.action ) ] = in_roles
684        return permissions, in_roles, error, msg
685    def copy_library_permissions( self, source_library_item, target_library_item, user=None ):
686        # Copy all relevant permissions from source.
687        permissions = {}
688        for role_assoc in source_library_item.actions:
689            if role_assoc.action != self.permitted_actions.LIBRARY_ACCESS.action:
690                # LIBRARY_ACCESS is a special permission that is set only at the library level.
691                if role_assoc.action in permissions:
692                    permissions[role_assoc.action].append( role_assoc.role )
693                else:
694                    permissions[role_assoc.action] = [ role_assoc.role ]
695        self.set_all_library_permissions( target_library_item, permissions )
696        if user:
697            item_class = None
698            for item_class, permission_class in self.library_item_assocs:
699                if isinstance( target_library_item, item_class ):
700                    break
701            if item_class:
702                # Make sure user's private role is included
703                private_role = self.model.security_agent.get_private_user_role( user )
704                for name, action in self.permitted_actions.items():
705                    if not permission_class.filter_by( role_id = private_role.id, action = action.action ).first():
706                        lp = permission_class( action.action, target_library_item, private_role )
707                        self.sa_session.add( lp )
708                        self.sa_session.flush()
709            else:
710                raise 'Invalid class (%s) specified for target_library_item (%s)' % \
711                    ( target_library_item.__class__, target_library_item.__class__.__name__ )
712    def show_library_item( self, user, roles, library_item, actions_to_check, hidden_folder_ids='' ):
713        """
714        This method must be sent an instance of Library() or LibraryFolder().  Recursive execution produces a
715        comma-separated string of folder ids whose folders do NOT meet the criteria for showing. Along with
716        the string, True is returned if the current user has permission to perform any 1 of actions_to_check
717        on library_item. Otherwise, cycle through all sub-folders in library_item until one is found that meets
718        this criteria, if it exists.  This method does not necessarily scan the entire library as it returns
719        when it finds the first library_item that allows user to perform any one action in actions_to_check.
720        """
721        for action in actions_to_check:
722            if self.allow_action( roles, action, library_item ):
723                return True, hidden_folder_ids
724        if isinstance( library_item, self.model.Library ):
725            return self.show_library_item( user, roles, library_item.root_folder, actions_to_check, hidden_folder_ids='' )
726        if isinstance( library_item, self.model.LibraryFolder ):
727            for folder in library_item.active_folders:
728                can_show, hidden_folder_ids = self.show_library_item( user, roles, folder, actions_to_check, hidden_folder_ids=hidden_folder_ids )
729                if can_show:
730                    return True, hidden_folder_ids
731                if hidden_folder_ids:
732                    hidden_folder_ids = '%s,%d' % ( hidden_folder_ids, folder.id )
733                else:
734                    hidden_folder_ids = '%d' % folder.id
735        return False, hidden_folder_ids
736    def get_showable_folders( self, user, roles, library_item, actions_to_check, hidden_folder_ids=[], showable_folders=[] ):
737        """
738        This method must be sent an instance of Library(), all the folders of which are scanned to determine if
739        user is allowed to perform any action in actions_to_check. The param hidden_folder_ids, if passed, should
740        contain a list of folder IDs which was generated when the library was previously scanned
741        using the same actions_to_check. A list of showable folders is generated. This method scans the entire library.
742        """
743        if isinstance( library_item, self.model.Library ):
744            return self.get_showable_folders( user, roles, library_item.root_folder, actions_to_check, showable_folders=[] )
745        if isinstance( library_item, self.model.LibraryFolder ):
746            if library_item.id not in hidden_folder_ids:
747                for action in actions_to_check:
748                    if self.allow_action( roles, action, library_item ):
749                        showable_folders.append( library_item )
750                        break
751            for folder in library_item.active_folders:
752                self.get_showable_folders( user, roles, folder, actions_to_check, showable_folders=showable_folders )
753        return showable_folders
754    def set_entity_user_associations( self, users=[], roles=[], groups=[], delete_existing_assocs=True ):
755        for user in users:
756            if delete_existing_assocs:
757                flush_needed = False
758                for a in user.non_private_roles + user.groups:
759                    self.sa_session.delete( a )
760                    flush_needed = True
761                if flush_needed:
762                    self.sa_session.flush()
763            self.sa_session.refresh( user )
764            for role in roles:
765                # Make sure we are not creating an additional association with a PRIVATE role
766                if role not in user.roles:
767                    self.associate_components( user=user, role=role )
768            for group in groups:
769                self.associate_components( user=user, group=group )
770    def set_entity_group_associations( self, groups=[], users=[], roles=[], delete_existing_assocs=True ):
771        for group in groups:
772            if delete_existing_assocs:
773                flush_needed = False
774                for a in group.roles + group.users:
775                    self.sa_session.delete( a )
776                    flush_needed = True
777                if flush_needed:
778                    self.sa_session.flush()
779            for role in roles:
780                self.associate_components( group=group, role=role )
781            for user in users:
782                self.associate_components( group=group, user=user )
783    def set_entity_role_associations( self, roles=[], users=[], groups=[], delete_existing_assocs=True ):
784        for role in roles:
785            if delete_existing_assocs:
786                flush_needed = False
787                for a in role.users + role.groups:
788                    self.sa_session.delete( a )
789                    flush_needed = True
790                if flush_needed:
791                    self.sa_session.flush()
792            for user in users:
793                self.associate_components( user=user, role=role )
794            for group in groups:
795                self.associate_components( group=group, role=role )
796    def get_component_associations( self, **kwd ):
797        assert len( kwd ) == 2, 'You must specify exactly 2 Galaxy security components to check for associations.'
798        if 'dataset' in kwd:
799            if 'action' in kwd:
800                return self.sa_session.query( self.model.DatasetPermissions ).filter_by( action = kwd['action'].action, dataset_id = kwd['dataset'].id ).first()
801        elif 'user' in kwd:
802            if 'group' in kwd:
803                return self.sa_session.query( self.model.UserGroupAssociation ).filter_by( group_id = kwd['group'].id, user_id = kwd['user'].id ).first()
804            elif 'role' in kwd:
805                return self.sa_session.query( self.model.UserRoleAssociation ).filter_by( role_id = kwd['role'].id, user_id = kwd['user'].id ).first()
806        elif 'group' in kwd:
807            if 'role' in kwd:
808                return self.sa_session.query( self.model.GroupRoleAssociation ).filter_by( role_id = kwd['role'].id, group_id = kwd['group'].id ).first()
809        raise 'No valid method of associating provided components: %s' % kwd
810    def check_folder_contents( self, user, roles, folder, hidden_folder_ids='' ):
811        """
812        This method must always be sent an instance of LibraryFolder().  Recursive execution produces a
813        comma-separated string of folder ids whose folders do NOT meet the criteria for showing.  Along
814        with the string, True is returned if the current user has permission to access folder. Otherwise,
815        cycle through all sub-folders in folder until one is found that meets this criteria, if it exists.
816        This method does not necessarily scan the entire library as it returns when it finds the first
817        folder that is accessible to user.
818        """
819        # If a folder is writeable, it's accessable and we need not go further
820        if self.can_add_library_item( roles, folder ):
821            return True, ''
822        action = self.permitted_actions.DATASET_ACCESS
823        lddas = self.sa_session.query( self.model.LibraryDatasetDatasetAssociation ) \
824                               .join( "library_dataset" ) \
825                               .filter( self.model.LibraryDataset.folder == folder ) \
826                               .join( "dataset" ) \
827                               .options( eagerload_all( "dataset.actions" ) ) \
828                               .all()
829        for ldda in lddas:
830            ldda_access_permissions = self.get_item_actions( action, ldda.dataset )
831            if not ldda_access_permissions:
832                # Dataset is public
833                return True, hidden_folder_ids
834            for ldda_access_permission in ldda_access_permissions:
835                if ldda_access_permission.role in roles:
836                    # The current user has access permission on the dataset
837                    return True, hidden_folder_ids
838        for sub_folder in folder.active_folders:
839            can_access, hidden_folder_ids = self.check_folder_contents( user, roles, sub_folder, hidden_folder_ids=hidden_folder_ids )
840            if can_access:
841                return True, hidden_folder_ids
842            if hidden_folder_ids:
843                hidden_folder_ids = '%s,%d' % ( hidden_folder_ids, sub_folder.id )
844            else:
845                hidden_folder_ids = '%d' % sub_folder.id
846        return False, hidden_folder_ids
847    #
848    # RequestType Permissions
849    #
850    def can_access_request_type( self, roles, request_type ):
851        action = self.permitted_actions.REQUEST_TYPE_ACCESS
852        request_type_actions = []
853        for permission in request_type.actions:
854            if permission.action == action.action:
855                request_type_actions.append(permission)
856        if not request_type_actions:
857            return action.model == 'restrict'
858        ret_val = False
859        for item_action in item_actions:
860            if item_action.role in roles:
861                ret_val = True
862                break
863        return ret_val
864    def set_request_type_permissions( self, request_type, permissions={} ):
865        # Set new permissions on request_type, eliminating all current permissions
866        for role_assoc in request_type.actions:
867            self.sa_session.delete( role_assoc )
868        # Add the new permissions on request_type
869        item_class = self.model.RequestType
870        permission_class = self.model.RequestTypePermissions
871        flush_needed = False
872        for action, roles in permissions.items():
873            if isinstance( action, Action ):
874                action = action.action
875            for role_assoc in [ permission_class( action, request_type, role ) for role in roles ]:
876                self.sa_session.add( role_assoc )
877                flush_needed = True
878        if flush_needed:
879            self.sa_session.flush()
880
881class HostAgent( RBACAgent ):
882    """
883    A simple security agent which allows access to datasets based on host.
884    This exists so that externals sites such as UCSC can gain access to
885    datasets which have permissions which would normally prevent such access.
886    """
887    # TODO: Make sites user configurable
888    sites = Bunch(
889        ucsc_main = ( 'hgw1.cse.ucsc.edu', 'hgw2.cse.ucsc.edu', 'hgw3.cse.ucsc.edu', 'hgw4.cse.ucsc.edu',
890                      'hgw5.cse.ucsc.edu', 'hgw6.cse.ucsc.edu', 'hgw7.cse.ucsc.edu', 'hgw8.cse.ucsc.edu' ),
891        ucsc_test = ( 'hgwdev.cse.ucsc.edu', ),
892        ucsc_archaea = ( 'lowepub.cse.ucsc.edu', )
893    )
894    def __init__( self, model, permitted_actions=None ):
895        self.model = model
896        if permitted_actions:
897            self.permitted_actions = permitted_actions
898    @property
899    def sa_session( self ):
900        """Returns a SQLAlchemy session"""
901        return self.model.context
902    def allow_action( self, addr, action, **kwd ):
903        if 'dataset' in kwd and action == self.permitted_actions.DATASET_ACCESS:
904            hda = kwd['dataset']
905            if action == self.permitted_actions.DATASET_ACCESS and action.action not in [ dp.action for dp in hda.dataset.actions ]:
906                log.debug( 'Allowing access to public dataset with hda: %i.' % hda.id )
907                return True # dataset has no roles associated with the access permission, thus is already public
908            hdadaa = self.sa_session.query( self.model.HistoryDatasetAssociationDisplayAtAuthorization ) \
909                                    .filter_by( history_dataset_association_id = hda.id ).first()
910            if not hdadaa:
911                log.debug( 'Denying access to private dataset with hda: %i.  No hdadaa record for this dataset.' % hda.id )
912                return False # no auth
913            # We could just look up the reverse of addr, but then we'd also
914            # have to verify it with the forward address and special case any
915            # IPs (instead of hosts) in the server list.
916            #
917            # This would be improved by caching, but that's what the OS's name
918            # service cache daemon is for (you ARE running nscd, right?).
919            for server in HostAgent.sites.get( hdadaa.site, [] ):
920                # We're going to search in order, but if the remote site is load
921                # balancing their connections (as UCSC does), this is okay.
922                try:
923                    if socket.gethostbyname( server ) == addr:
924                        break # remote host is in the server list
925                except ( socket.error, socket.gaierror ):
926                    pass # can't resolve, try next
927            else:
928                log.debug( 'Denying access to private dataset with hda: %i.  Remote addr is not a valid server for site: %s.' % ( hda.id, hdadaa.site ) )
929                return False # remote addr is not in the server list
930            if ( datetime.utcnow() - hdadaa.update_time ) > timedelta( seconds=60 ):
931                log.debug( 'Denying access to private dataset with hda: %i.  Authorization was granted, but has expired.' % hda.id )
932                return False # not authz'd in the last 60 seconds
933            log.debug( 'Allowing access to private dataset with hda: %i.  Remote server is: %s.' % ( hda.id, server ) )
934            return True
935        else:
936            raise 'The dataset access permission is the only valid permission in the host security agent.'
937    def set_dataset_permissions( self, hda, user, site ):
938        hdadaa = self.sa_session.query( self.model.HistoryDatasetAssociationDisplayAtAuthorization ) \
939                                .filter_by( history_dataset_association_id = hda.id ).first()
940        if hdadaa:
941            hdadaa.update_time = datetime.utcnow()
942        else:
943            hdadaa = self.model.HistoryDatasetAssociationDisplayAtAuthorization( hda=hda, user=user, site=site )
944        self.sa_session.add( hdadaa )
945        self.sa_session.flush()
946
947def get_permitted_actions( filter=None ):
948    '''Utility method to return a subset of RBACAgent's permitted actions'''
949    if filter is None:
950        return RBACAgent.permitted_actions
951    tmp_bunch = Bunch()
952    [ tmp_bunch.__dict__.__setitem__(k, v) for k, v in RBACAgent.permitted_actions.items() if k.startswith( filter ) ]
953    return tmp_bunch
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。