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 |
---|