[2] | 1 | """ |
---|
| 2 | Galaxy Tool Shed Security |
---|
| 3 | """ |
---|
| 4 | import logging, socket, operator |
---|
| 5 | from datetime import datetime, timedelta |
---|
| 6 | from galaxy.util.bunch import Bunch |
---|
| 7 | from galaxy.util import listify |
---|
| 8 | from galaxy.model.orm import * |
---|
| 9 | from galaxy.webapps.community.controllers.common import get_versions |
---|
| 10 | |
---|
| 11 | log = logging.getLogger(__name__) |
---|
| 12 | |
---|
| 13 | class Action( object ): |
---|
| 14 | def __init__( self, action, description, model ): |
---|
| 15 | self.action = action |
---|
| 16 | self.description = description |
---|
| 17 | self.model = model |
---|
| 18 | |
---|
| 19 | class RBACAgent: |
---|
| 20 | """Class that handles galaxy community space security""" |
---|
| 21 | permitted_actions = Bunch() |
---|
| 22 | def associate_components( self, **kwd ): |
---|
| 23 | raise 'No valid method of associating provided components: %s' % kwd |
---|
| 24 | def associate_user_role( self, user, role ): |
---|
| 25 | raise 'No valid method of associating a user with a role' |
---|
| 26 | def convert_permitted_action_strings( self, permitted_action_strings ): |
---|
| 27 | """ |
---|
| 28 | When getting permitted actions from an untrusted source like a |
---|
| 29 | form, ensure that they match our actual permitted actions. |
---|
| 30 | """ |
---|
| 31 | return filter( lambda x: x is not None, [ self.permitted_actions.get( action_string ) for action_string in permitted_action_strings ] ) |
---|
| 32 | def create_private_user_role( self, user ): |
---|
| 33 | raise "Unimplemented Method" |
---|
| 34 | def get_action( self, name, default=None ): |
---|
| 35 | """Get a permitted action by its dict key or action name""" |
---|
| 36 | for k, v in self.permitted_actions.items(): |
---|
| 37 | if k == name or v.action == name: |
---|
| 38 | return v |
---|
| 39 | return default |
---|
| 40 | def get_actions( self ): |
---|
| 41 | """Get all permitted actions as a list of Action objects""" |
---|
| 42 | return self.permitted_actions.__dict__.values() |
---|
| 43 | def get_item_actions( self, action, item ): |
---|
| 44 | raise 'No valid method of retrieving action (%s) for item %s.' % ( action, item ) |
---|
| 45 | def get_private_user_role( self, user ): |
---|
| 46 | raise "Unimplemented Method" |
---|
| 47 | |
---|
| 48 | class CommunityRBACAgent( RBACAgent ): |
---|
| 49 | def __init__( self, model, permitted_actions=None ): |
---|
| 50 | self.model = model |
---|
| 51 | if permitted_actions: |
---|
| 52 | self.permitted_actions = permitted_actions |
---|
| 53 | @property |
---|
| 54 | def sa_session( self ): |
---|
| 55 | """Returns a SQLAlchemy session""" |
---|
| 56 | return self.model.context |
---|
| 57 | def allow_action( self, roles, action, item ): |
---|
| 58 | """ |
---|
| 59 | Method for checking a permission for the current user ( based on roles ) to perform a |
---|
| 60 | specific action on an item |
---|
| 61 | """ |
---|
| 62 | item_actions = self.get_item_actions( action, item ) |
---|
| 63 | if not item_actions: |
---|
| 64 | return action.model == 'restrict' |
---|
| 65 | ret_val = False |
---|
| 66 | for item_action in item_actions: |
---|
| 67 | if item_action.role in roles: |
---|
| 68 | ret_val = True |
---|
| 69 | break |
---|
| 70 | return ret_val |
---|
| 71 | def associate_components( self, **kwd ): |
---|
| 72 | if 'user' in kwd: |
---|
| 73 | if 'group' in kwd: |
---|
| 74 | return self.associate_user_group( kwd['user'], kwd['group'] ) |
---|
| 75 | elif 'role' in kwd: |
---|
| 76 | return self.associate_user_role( kwd['user'], kwd['role'] ) |
---|
| 77 | elif 'role' in kwd: |
---|
| 78 | if 'group' in kwd: |
---|
| 79 | return self.associate_group_role( kwd['group'], kwd['role'] ) |
---|
| 80 | elif 'tool' in kwd: |
---|
| 81 | return self.associate_tool_category( kwd['tool'], kwd['category'] ) |
---|
| 82 | raise 'No valid method of associating provided components: %s' % kwd |
---|
| 83 | def associate_group_role( self, group, role ): |
---|
| 84 | assoc = self.model.GroupRoleAssociation( group, role ) |
---|
| 85 | self.sa_session.add( assoc ) |
---|
| 86 | self.sa_session.flush() |
---|
| 87 | return assoc |
---|
| 88 | def associate_user_group( self, user, group ): |
---|
| 89 | assoc = self.model.UserGroupAssociation( user, group ) |
---|
| 90 | self.sa_session.add( assoc ) |
---|
| 91 | self.sa_session.flush() |
---|
| 92 | return assoc |
---|
| 93 | def associate_user_role( self, user, role ): |
---|
| 94 | assoc = self.model.UserRoleAssociation( user, role ) |
---|
| 95 | self.sa_session.add( assoc ) |
---|
| 96 | self.sa_session.flush() |
---|
| 97 | return assoc |
---|
| 98 | def associate_tool_category( self, tool, category ): |
---|
| 99 | assoc = self.model.ToolCategoryAssociation( tool, category ) |
---|
| 100 | self.sa_session.add( assoc ) |
---|
| 101 | self.sa_session.flush() |
---|
| 102 | return assoc |
---|
| 103 | def create_private_user_role( self, user ): |
---|
| 104 | # Create private role |
---|
| 105 | role = self.model.Role( name=user.email, description='Private Role for ' + user.email, type=self.model.Role.types.PRIVATE ) |
---|
| 106 | self.sa_session.add( role ) |
---|
| 107 | self.sa_session.flush() |
---|
| 108 | # Add user to role |
---|
| 109 | self.associate_components( role=role, user=user ) |
---|
| 110 | return role |
---|
| 111 | def get_item_actions( self, action, item ): |
---|
| 112 | # item must be one of: Dataset, Library, LibraryFolder, LibraryDataset, LibraryDatasetDatasetAssociation |
---|
| 113 | return [ permission for permission in item.actions if permission.action == action.action ] |
---|
| 114 | def get_private_user_role( self, user, auto_create=False ): |
---|
| 115 | role = self.sa_session.query( self.model.Role ) \ |
---|
| 116 | .filter( and_( self.model.Role.table.c.name == user.email, |
---|
| 117 | self.model.Role.table.c.type == self.model.Role.types.PRIVATE ) ) \ |
---|
| 118 | .first() |
---|
| 119 | if not role: |
---|
| 120 | if auto_create: |
---|
| 121 | return self.create_private_user_role( user ) |
---|
| 122 | else: |
---|
| 123 | return None |
---|
| 124 | return role |
---|
| 125 | def set_entity_group_associations( self, groups=[], users=[], roles=[], delete_existing_assocs=True ): |
---|
| 126 | for group in groups: |
---|
| 127 | if delete_existing_assocs: |
---|
| 128 | for a in group.roles + group.users: |
---|
| 129 | self.sa_session.delete( a ) |
---|
| 130 | self.sa_session.flush() |
---|
| 131 | for role in roles: |
---|
| 132 | self.associate_components( group=group, role=role ) |
---|
| 133 | for user in users: |
---|
| 134 | self.associate_components( group=group, user=user ) |
---|
| 135 | def set_entity_role_associations( self, roles=[], users=[], groups=[], delete_existing_assocs=True ): |
---|
| 136 | for role in roles: |
---|
| 137 | if delete_existing_assocs: |
---|
| 138 | for a in role.users + role.groups: |
---|
| 139 | self.sa_session.delete( a ) |
---|
| 140 | self.sa_session.flush() |
---|
| 141 | for user in users: |
---|
| 142 | self.associate_components( user=user, role=role ) |
---|
| 143 | for group in groups: |
---|
| 144 | self.associate_components( group=group, role=role ) |
---|
| 145 | def set_entity_user_associations( self, users=[], roles=[], groups=[], delete_existing_assocs=True ): |
---|
| 146 | for user in users: |
---|
| 147 | if delete_existing_assocs: |
---|
| 148 | for a in user.non_private_roles + user.groups: |
---|
| 149 | self.sa_session.delete( a ) |
---|
| 150 | self.sa_session.flush() |
---|
| 151 | self.sa_session.refresh( user ) |
---|
| 152 | for role in roles: |
---|
| 153 | # Make sure we are not creating an additional association with a PRIVATE role |
---|
| 154 | if role not in user.roles: |
---|
| 155 | self.associate_components( user=user, role=role ) |
---|
| 156 | for group in groups: |
---|
| 157 | self.associate_components( user=user, group=group ) |
---|
| 158 | def set_entity_category_associations( self, tools=[], categories=[], delete_existing_assocs=True ): |
---|
| 159 | for tool in tools: |
---|
| 160 | if delete_existing_assocs: |
---|
| 161 | for a in tool.categories: |
---|
| 162 | self.sa_session.delete( a ) |
---|
| 163 | self.sa_session.flush() |
---|
| 164 | self.sa_session.refresh( tool ) |
---|
| 165 | for category in categories: |
---|
| 166 | self.associate_components( tool=tool, category=category ) |
---|
| 167 | def can_rate( self, user, user_is_admin, cntrller, item ): |
---|
| 168 | # The current user can rate and review the item if they are an admin or if |
---|
| 169 | # they did not upload the item and the item is approved or archived. |
---|
| 170 | if user and user_is_admin and cntrller == 'admin': |
---|
| 171 | return True |
---|
| 172 | if cntrller in [ 'tool' ] and ( item.is_approved or item.is_archived ) and user != item.user: |
---|
| 173 | return True |
---|
| 174 | return False |
---|
| 175 | def can_approve_or_reject( self, user, user_is_admin, cntrller, item ): |
---|
| 176 | # The current user can approve or reject the item if the user |
---|
| 177 | # is an admin, and the item's state is WAITING. |
---|
| 178 | return user and user_is_admin and cntrller=='admin' and item.is_waiting |
---|
| 179 | def can_delete( self, user, user_is_admin, cntrller, item ): |
---|
| 180 | # The current user can delete the item if they are an admin or if they uploaded the |
---|
| 181 | # item and in either case the item's state is not DELETED. |
---|
| 182 | if user and user_is_admin and cntrller == 'admin': |
---|
| 183 | can_delete = not item.is_deleted |
---|
| 184 | elif cntrller in [ 'tool' ]: |
---|
| 185 | can_delete = user==item.user and not item.is_deleted |
---|
| 186 | else: |
---|
| 187 | can_delete = False |
---|
| 188 | return can_delete |
---|
| 189 | def can_download( self, user, user_is_admin, cntrller, item ): |
---|
| 190 | # The current user can download the item if they are an admin or if the |
---|
| 191 | # item's state is not one of: NEW, WAITING. |
---|
| 192 | if user and user_is_admin and cntrller == 'admin': |
---|
| 193 | return True |
---|
| 194 | elif cntrller in [ 'tool' ]: |
---|
| 195 | can_download = not( item.is_new or item.is_waiting ) |
---|
| 196 | else: |
---|
| 197 | can_download = False |
---|
| 198 | return can_download |
---|
| 199 | def can_edit( self, user, user_is_admin, cntrller, item ): |
---|
| 200 | # The current user can edit the item if they are an admin or if they uploaded the item |
---|
| 201 | # and the item's state is one of: NEW, REJECTED. |
---|
| 202 | if user and user_is_admin and cntrller == 'admin': |
---|
| 203 | return True |
---|
| 204 | if cntrller in [ 'tool' ]: |
---|
| 205 | return user and user==item.user and ( item.is_new or item.is_rejected ) |
---|
| 206 | return False |
---|
| 207 | def can_purge( self, user, user_is_admin, cntrller ): |
---|
| 208 | # The current user can purge the item if they are an admin. |
---|
| 209 | return user and user_is_admin and cntrller == 'admin' |
---|
| 210 | def can_upload_new_version( self, user, item ): |
---|
| 211 | # The current user can upload a new version as long as the item's state is not NEW or WAITING. |
---|
| 212 | if not user: |
---|
| 213 | return False |
---|
| 214 | versions = get_versions( item ) |
---|
| 215 | state_ok = True |
---|
| 216 | for version in versions: |
---|
| 217 | if version.is_new or version.is_waiting: |
---|
| 218 | state_ok = False |
---|
| 219 | break |
---|
| 220 | return state_ok |
---|
| 221 | def can_view( self, user, user_is_admin, cntrller, item ): |
---|
| 222 | # The current user can view the item if they are an admin or if they uploaded the item |
---|
| 223 | # or if the item's state is APPROVED. |
---|
| 224 | if user and user_is_admin and cntrller == 'admin': |
---|
| 225 | return True |
---|
| 226 | if cntrller in [ 'tool' ] and item.is_approved: |
---|
| 227 | return True |
---|
| 228 | return user and user==item.user |
---|
| 229 | def get_all_action_permissions( self, user, user_is_admin, cntrller, item ): |
---|
| 230 | """Get all permitted actions on item for the current user""" |
---|
| 231 | can_edit = self.can_edit( cntrller, user, user_is_admin, item ) |
---|
| 232 | can_view = self.can_view( cntrller, user, user_is_admin, item ) |
---|
| 233 | can_upload_new_version = self.can_upload_new_version( user, item ) |
---|
| 234 | visible_versions = self.get_visible_versions( user, user_is_admin, cntrller, item ) |
---|
| 235 | can_approve_or_reject = self.can_approve_or_reject( user, user_is_admin, cntrller, item ) |
---|
| 236 | can_delete = self.can_delete( user, user_is_admin, cntrller, item ) |
---|
| 237 | return can_edit, can_view, can_upload_new_version, can_delete, visible_versions, can_approve_or_reject |
---|
| 238 | def get_visible_versions( self, user, user_is_admin, cntrller, item ): |
---|
| 239 | # All previous versions of item can be displayed if the current user is an admin |
---|
| 240 | # or they uploaded item. Otherwise, only versions whose state is APPROVED or |
---|
| 241 | # ARCHIVED will be displayed. |
---|
| 242 | if user and user_is_admin and cntrller == 'admin': |
---|
| 243 | visible_versions = get_versions( item ) |
---|
| 244 | elif cntrller in [ 'tool' ]: |
---|
| 245 | visible_versions = [] |
---|
| 246 | for version in get_versions( item ): |
---|
| 247 | if version.is_approved or version.is_archived or version.user == user: |
---|
| 248 | visible_versions.append( version ) |
---|
| 249 | else: |
---|
| 250 | visible_versions = [] |
---|
| 251 | return visible_versions |
---|
| 252 | |
---|
| 253 | def get_permitted_actions( filter=None ): |
---|
| 254 | '''Utility method to return a subset of RBACAgent's permitted actions''' |
---|
| 255 | if filter is None: |
---|
| 256 | return RBACAgent.permitted_actions |
---|
| 257 | tmp_bunch = Bunch() |
---|
| 258 | [ tmp_bunch.__dict__.__setitem__(k, v) for k, v in RBACAgent.permitted_actions.items() if k.startswith( filter ) ] |
---|
| 259 | return tmp_bunch |
---|