Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(452)

Side by Side Diff: appengine/monorail/framework/permissions.py

Issue 1868553004: Open Source Monorail (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Rebase Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 # Copyright 2016 The Chromium Authors. All rights reserved.
2 # Use of this source code is govered by a BSD-style
3 # license that can be found in the LICENSE file or at
4 # https://developers.google.com/open-source/licenses/bsd
5
6 """Classes and functions to implement permission checking.
7
8 The main data structure is a simple map from (user role, project status,
9 project_access_level) to specific perms.
10
11 A perm is simply a string that indicates that the user has a given
12 permission. The servlets and templates can test whether the current
13 user has permission to see a UI element or perform an action by
14 testing for the presence of the corresponding perm in the user's
15 permission set.
16
17 The user role is one of admin, owner, member, outsider user, or anon.
18 The project status is one of the project states defined in project_pb2,
19 or a special constant defined below. Likewise for access level.
20 """
21
22 import logging
23 import time
24
25 from third_party import ezt
26
27 import settings
28 from framework import framework_bizobj
29 from framework import framework_constants
30 from proto import project_pb2
31 from proto import site_pb2
32 from proto import usergroup_pb2
33 from tracker import tracker_bizobj
34
35 # Constants that define permissions.
36 # Note that perms with a leading "_" can never be granted
37 # to users who are not site admins.
38 VIEW = 'View'
39 EDIT_PROJECT = 'EditProject'
40 CREATE_PROJECT = 'CreateProject'
41 PUBLISH_PROJECT = '_PublishProject' # for making "doomed" projects LIVE
42 VIEW_DEBUG = '_ViewDebug' # on-page debugging info
43 EDIT_OTHER_USERS = '_EditOtherUsers' # can edit other user's prefs, ban, etc.
44 CUSTOMIZE_PROCESS = 'CustomizeProcess' # can use some enterprise features
45 VIEW_EXPIRED_PROJECT = '_ViewExpiredProject' # view long-deleted projects
46 # View the list of contributors even in hub-and-spoke projects.
47 VIEW_CONTRIBUTOR_LIST = 'ViewContributorList'
48
49 # Quota
50 VIEW_QUOTA = 'ViewQuota'
51 EDIT_QUOTA = 'EditQuota'
52
53 # Permissions for editing user groups
54 CREATE_GROUP = 'CreateGroup'
55 EDIT_GROUP = 'EditGroup'
56 DELETE_GROUP = 'DeleteGroup'
57 VIEW_GROUP = 'ViewGroup'
58
59 # Perms for Source tools
60 # TODO(jrobbins): Monorail is just issue tracking with no version control, so
61 # phase out use of the term "Commit", sometime after Monorail's initial launch.
62 COMMIT = 'Commit'
63
64 # Perms for issue tracking
65 CREATE_ISSUE = 'CreateIssue'
66 EDIT_ISSUE = 'EditIssue'
67 EDIT_ISSUE_OWNER = 'EditIssueOwner'
68 EDIT_ISSUE_SUMMARY = 'EditIssueSummary'
69 EDIT_ISSUE_STATUS = 'EditIssueStatus'
70 EDIT_ISSUE_CC = 'EditIssueCc'
71 DELETE_ISSUE = 'DeleteIssue'
72 ADD_ISSUE_COMMENT = 'AddIssueComment'
73 VIEW_INBOUND_MESSAGES = 'ViewInboundMessages'
74 # Note, there is no separate DELETE_ATTACHMENT perm. We
75 # allow a user to delete an attachment iff they could soft-delete
76 # the comment that holds the attachment.
77
78 # Note: the "_" in the perm name makes it impossible for a
79 # project owner to grant it to anyone as an extra perm.
80 ADMINISTER_SITE = '_AdministerSite'
81
82 # Permissions to soft-delete artifact comment
83 DELETE_ANY = 'DeleteAny'
84 DELETE_OWN = 'DeleteOwn'
85
86 # Granting this allows owners to delegate some team management work.
87 EDIT_ANY_MEMBER_NOTES = 'EditAnyMemberNotes'
88
89 # Permission to star/unstar any artifact.
90 SET_STAR = 'SetStar'
91
92 # Permission to flag any artifact as spam.
93 FLAG_SPAM = 'FlagSpam'
94 VERDICT_SPAM = 'VerdictSpam'
95 MODERATE_SPAM = 'ModerateSpam'
96
97 STANDARD_ADMIN_PERMISSIONS = [
98 EDIT_PROJECT, CREATE_PROJECT, PUBLISH_PROJECT, VIEW_DEBUG,
99 EDIT_OTHER_USERS, CUSTOMIZE_PROCESS,
100 VIEW_QUOTA, EDIT_QUOTA, ADMINISTER_SITE,
101 EDIT_ANY_MEMBER_NOTES, VERDICT_SPAM, MODERATE_SPAM]
102
103 STANDARD_ISSUE_PERMISSIONS = [
104 VIEW, EDIT_ISSUE, ADD_ISSUE_COMMENT, DELETE_ISSUE, FLAG_SPAM]
105
106 # Monorail has no source control, but keep COMMIT for backward compatability.
107 STANDARD_SOURCE_PERMISSIONS = [COMMIT]
108
109 STANDARD_COMMENT_PERMISSIONS = [DELETE_OWN, DELETE_ANY]
110
111 STANDARD_OTHER_PERMISSIONS = [CREATE_ISSUE, FLAG_SPAM, SET_STAR]
112
113 STANDARD_PERMISSIONS = (STANDARD_ADMIN_PERMISSIONS +
114 STANDARD_ISSUE_PERMISSIONS +
115 STANDARD_SOURCE_PERMISSIONS +
116 STANDARD_COMMENT_PERMISSIONS +
117 STANDARD_OTHER_PERMISSIONS)
118
119 # roles
120 SITE_ADMIN_ROLE = 'admin'
121 OWNER_ROLE = 'owner'
122 COMMITTER_ROLE = 'committer'
123 CONTRIBUTOR_ROLE = 'contributor'
124 USER_ROLE = 'user'
125 ANON_ROLE = 'anon'
126
127 # Project state out-of-band values for keys
128 UNDEFINED_STATUS = 'undefined_status'
129 UNDEFINED_ACCESS = 'undefined_access'
130 WILDCARD_ACCESS = 'wildcard_access'
131
132
133 class PermissionSet(object):
134 """Class to represent the set of permissions available to the user."""
135
136 def __init__(self, perm_names, consider_restrictions=True):
137 """Create a PermissionSet with the given permissions.
138
139 Args:
140 perm_names: a list of permission name strings.
141 consider_restrictions: if true, the user's permissions can be blocked
142 by restriction labels on an artifact. Project owners and site
143 admins do not consider restrictions so that they cannot
144 "lock themselves out" of editing an issue.
145 """
146 self.perm_names = frozenset(p.lower() for p in perm_names)
147 self.consider_restrictions = consider_restrictions
148
149 def __getattr__(self, perm_name):
150 """Easy permission testing in EZT. E.g., [if-any perms.format_drive]."""
151 return ezt.boolean(self.HasPerm(perm_name, None, None))
152
153 def CanUsePerm(
154 self, perm_name, effective_ids, project, restriction_labels,
155 granted_perms=None):
156 """Return True if the user can use the given permission.
157
158 Args:
159 perm_name: string name of permission, e.g., 'EditIssue'.
160 effective_ids: set of int user IDs for the user (including any groups),
161 or an empty set if user is not signed in.
162 project: Project PB for the project being accessed, or None if not
163 in a project.
164 restriction_labels: list of strings that restrict permission usage.
165 granted_perms: optional list of lowercase strings of permissions that the
166 user is granted only within the scope of one issue, e.g., by being
167 named in a user-type custom field that grants permissions.
168
169 Restriction labels have 3 parts, e.g.:
170 'Restrict-EditIssue-InnerCircle' blocks the use of just the
171 EditIssue permission, unless the user also has the InnerCircle
172 permission. This allows fine-grained restrictions on specific
173 actions, such as editing, commenting, or deleting.
174
175 Restriction labels and permissions are case-insensitive.
176
177 Returns:
178 True if the user can use the given permission, or False
179 if they cannot (either because they don't have that permission
180 or because it is blocked by a relevant restriction label).
181 """
182 # TODO(jrobbins): room for performance improvement: avoid set creation and
183 # repeated string operations.
184 granted_perms = granted_perms or set()
185 perm_lower = perm_name.lower()
186 if perm_lower in granted_perms:
187 return True
188
189 needed_perms = {perm_lower}
190 if self.consider_restrictions:
191 for label in restriction_labels:
192 label = label.lower()
193 # format: Restrict-Action-ToThisPerm
194 _kw, requested_perm, needed_perm = label.split('-', 2)
195 if requested_perm == perm_lower and needed_perm not in granted_perms:
196 needed_perms.add(needed_perm)
197
198 if not effective_ids:
199 effective_ids = {framework_constants.NO_USER_SPECIFIED}
200 # Id X might have perm A and Y might have B, if both A and B are needed
201 # True should be returned.
202 for perm in needed_perms:
203 if not any(
204 self.HasPerm(perm, user_id, project) for user_id in effective_ids):
205 return False
206
207 return True
208
209 def HasPerm(self, perm_name, user_id, project):
210 """Return True if the user has the given permission (ignoring user groups).
211
212 Args:
213 perm_name: string name of permission, e.g., 'EditIssue'.
214 user_id: int user id of the user, or None if user is not signed in.
215 project: Project PB for the project being accessed, or None if not
216 in a project.
217
218 Returns:
219 True if the user has the given perm.
220 """
221 # TODO(jrobbins): room for performance improvement: pre-compute
222 # extra perms (maybe merge them into the perms object), avoid
223 # redundant call to lower().
224 extra_perms = [p.lower() for p in GetExtraPerms(project, user_id)]
225 perm_name = perm_name.lower()
226 return perm_name in self.perm_names or perm_name in extra_perms
227
228 def DebugString(self):
229 """Return a useful string to show when debugging."""
230 return 'PermissionSet(%s)' % ', '.join(sorted(self.perm_names))
231
232 def __repr__(self):
233 return '%s(%r)' % (self.__class__.__name__, self.perm_names)
234
235
236 EMPTY_PERMISSIONSET = PermissionSet([])
237
238 READ_ONLY_PERMISSIONSET = PermissionSet([VIEW])
239
240 USER_PERMISSIONSET = PermissionSet([
241 VIEW, FLAG_SPAM, SET_STAR,
242 CREATE_ISSUE, ADD_ISSUE_COMMENT,
243 DELETE_OWN])
244
245 CONTRIBUTOR_ACTIVE_PERMISSIONSET = PermissionSet(
246 [VIEW,
247 FLAG_SPAM, SET_STAR,
248 CREATE_ISSUE, ADD_ISSUE_COMMENT,
249 DELETE_OWN])
250
251 CONTRIBUTOR_INACTIVE_PERMISSIONSET = PermissionSet(
252 [VIEW])
253
254 COMMITTER_ACTIVE_PERMISSIONSET = PermissionSet(
255 [VIEW, COMMIT, VIEW_CONTRIBUTOR_LIST,
256 FLAG_SPAM, SET_STAR, VIEW_QUOTA,
257 CREATE_ISSUE, ADD_ISSUE_COMMENT, EDIT_ISSUE, VIEW_INBOUND_MESSAGES,
258 DELETE_OWN])
259
260 COMMITTER_INACTIVE_PERMISSIONSET = PermissionSet(
261 [VIEW, VIEW_CONTRIBUTOR_LIST,
262 VIEW_INBOUND_MESSAGES, VIEW_QUOTA])
263
264 OWNER_ACTIVE_PERMISSIONSET = PermissionSet(
265 [VIEW, VIEW_CONTRIBUTOR_LIST, EDIT_PROJECT, COMMIT,
266 FLAG_SPAM, VERDICT_SPAM, SET_STAR, VIEW_QUOTA,
267 CREATE_ISSUE, ADD_ISSUE_COMMENT, EDIT_ISSUE, DELETE_ISSUE,
268 VIEW_INBOUND_MESSAGES,
269 DELETE_ANY, EDIT_ANY_MEMBER_NOTES],
270 consider_restrictions=False)
271
272 OWNER_INACTIVE_PERMISSIONSET = PermissionSet(
273 [VIEW, VIEW_CONTRIBUTOR_LIST, EDIT_PROJECT,
274 VIEW_INBOUND_MESSAGES, VIEW_QUOTA],
275 consider_restrictions=False)
276
277 ADMIN_PERMISSIONSET = PermissionSet(
278 [VIEW, VIEW_CONTRIBUTOR_LIST,
279 CREATE_PROJECT, EDIT_PROJECT, PUBLISH_PROJECT, VIEW_DEBUG,
280 COMMIT, CUSTOMIZE_PROCESS, FLAG_SPAM, VERDICT_SPAM, SET_STAR,
281 ADMINISTER_SITE, VIEW_EXPIRED_PROJECT, EDIT_OTHER_USERS,
282 VIEW_QUOTA, EDIT_QUOTA,
283 CREATE_ISSUE, ADD_ISSUE_COMMENT, EDIT_ISSUE, DELETE_ISSUE,
284 VIEW_INBOUND_MESSAGES,
285 DELETE_ANY, EDIT_ANY_MEMBER_NOTES,
286 CREATE_GROUP, EDIT_GROUP, DELETE_GROUP, VIEW_GROUP,
287 MODERATE_SPAM],
288 consider_restrictions=False)
289
290 GROUP_IMPORT_BORG_PERMISSIONSET = PermissionSet(
291 [CREATE_GROUP, VIEW_GROUP, EDIT_GROUP])
292
293
294 # Permissions for project pages, e.g., the project summary page
295 _PERMISSIONS_TABLE = {
296
297 # Project owners can view and edit artifacts in a LIVE project.
298 (OWNER_ROLE, project_pb2.ProjectState.LIVE, WILDCARD_ACCESS):
299 OWNER_ACTIVE_PERMISSIONSET,
300
301 # Project owners can view, but not edit artifacts in ARCHIVED.
302 # Note: EDIT_PROJECT is not enough permission to change an ARCHIVED project
303 # back to LIVE if a delete_time was set.
304 (OWNER_ROLE, project_pb2.ProjectState.ARCHIVED, WILDCARD_ACCESS):
305 OWNER_INACTIVE_PERMISSIONSET,
306
307 # Project members can view their own project, regardless of state.
308 (COMMITTER_ROLE, project_pb2.ProjectState.LIVE, WILDCARD_ACCESS):
309 COMMITTER_ACTIVE_PERMISSIONSET,
310 (COMMITTER_ROLE, project_pb2.ProjectState.ARCHIVED, WILDCARD_ACCESS):
311 COMMITTER_INACTIVE_PERMISSIONSET,
312
313 # Project contributors can view their own project, regardless of state.
314 (CONTRIBUTOR_ROLE, project_pb2.ProjectState.LIVE, WILDCARD_ACCESS):
315 CONTRIBUTOR_ACTIVE_PERMISSIONSET,
316 (CONTRIBUTOR_ROLE, project_pb2.ProjectState.ARCHIVED, WILDCARD_ACCESS):
317 CONTRIBUTOR_INACTIVE_PERMISSIONSET,
318
319 # Non-members users can read and comment in projects with access == ANYONE
320 (USER_ROLE, project_pb2.ProjectState.LIVE,
321 project_pb2.ProjectAccess.ANYONE):
322 USER_PERMISSIONSET,
323
324 # Anonymous users can only read projects with access == ANYONE.
325 (ANON_ROLE, project_pb2.ProjectState.LIVE,
326 project_pb2.ProjectAccess.ANYONE):
327 READ_ONLY_PERMISSIONSET,
328
329 # Permissions for site pages, e.g., creating a new project
330 (USER_ROLE, UNDEFINED_STATUS, UNDEFINED_ACCESS):
331 PermissionSet([CREATE_PROJECT, CREATE_GROUP]),
332 }
333
334
335 def GetPermissions(user, effective_ids, project):
336 """Return a permission set appropriate for the user and project.
337
338 Args:
339 user: The User PB for the signed-in user, or None for anon users.
340 effective_ids: set of int user IDs for the current user and all user
341 groups that s/he is a member of. This will be an empty set for
342 anonymous users.
343 project: either a Project protobuf, or None for a page whose scope is
344 wider than a single project.
345
346 Returns:
347 a PermissionSet object for the current user and project (or for
348 site-wide operations if project is None).
349
350 If an exact match for the user's role and project status is found, that is
351 returned. Otherwise, we look for permissions for the user's role that is
352 not specific to any project status, or not specific to any project access
353 level. If neither of those are defined, we give the user an empty
354 permission set.
355 """
356 # Site admins get ADMIN_PERMISSIONSET regardless of groups or projects.
357 if user and user.is_site_admin:
358 return ADMIN_PERMISSIONSET
359
360 # Grant the borg job permission to view/edit groups
361 if user and user.email == settings.borg_service_account:
362 return GROUP_IMPORT_BORG_PERMISSIONSET
363
364 # Anon users don't need to accumulate anything.
365 if not effective_ids:
366 role, status, access = _GetPermissionKey(None, project)
367 return _LookupPermset(role, status, access)
368
369 effective_perms = set()
370 consider_restrictions = True
371
372 # Check for signed-in user with no roles in the current project.
373 if not project or not framework_bizobj.UserIsInProject(
374 project, effective_ids):
375 role, status, access = _GetPermissionKey(None, project)
376 return _LookupPermset(USER_ROLE, status, access)
377
378 # Signed-in user gets the union of all his/her PermissionSets from the table.
379 for user_id in effective_ids:
380 role, status, access = _GetPermissionKey(user_id, project)
381 role_perms = _LookupPermset(role, status, access)
382 # Accumulate a union of all the user's permissions.
383 effective_perms.update(role_perms.perm_names)
384 # If any role allows the user to ignore restriction labels, then
385 # ignore them overall.
386 if not role_perms.consider_restrictions:
387 consider_restrictions = False
388
389 return PermissionSet(
390 effective_perms, consider_restrictions=consider_restrictions)
391
392
393 def _LookupPermset(role, status, access):
394 """Lookup the appropriate PermissionSet in _PERMISSIONS_TABLE.
395
396 Args:
397 role: a string indicating the user's role in the project.
398 status: a Project PB status value, or UNDEFINED_STATUS.
399 access: a Project PB access value, or UNDEFINED_ACCESS.
400
401 Returns:
402 A PermissionSet that is appropriate for that kind of user in that
403 project context.
404 """
405 if (role, status, access) in _PERMISSIONS_TABLE:
406 return _PERMISSIONS_TABLE[(role, status, access)]
407 elif (role, status, WILDCARD_ACCESS) in _PERMISSIONS_TABLE:
408 return _PERMISSIONS_TABLE[(role, status, WILDCARD_ACCESS)]
409 else:
410 return EMPTY_PERMISSIONSET
411
412
413 def _GetPermissionKey(user_id, project, expired_before=None):
414 """Return a permission lookup key appropriate for the user and project."""
415 if user_id is None:
416 role = ANON_ROLE
417 elif project and IsExpired(project, expired_before=expired_before):
418 role = USER_ROLE # Do not honor roles in expired projects.
419 elif project and user_id in project.owner_ids:
420 role = OWNER_ROLE
421 elif project and user_id in project.committer_ids:
422 role = COMMITTER_ROLE
423 elif project and user_id in project.contributor_ids:
424 role = CONTRIBUTOR_ROLE
425 else:
426 role = USER_ROLE
427
428 # TODO(jrobbins): re-implement same_org
429
430 if project is None:
431 status = UNDEFINED_STATUS
432 else:
433 status = project.state
434
435 if project is None:
436 access = UNDEFINED_ACCESS
437 else:
438 access = project.access
439
440 return role, status, access
441
442
443 def GetExtraPerms(project, member_id):
444 """Return a list of extra perms for the user in the project.
445
446 Args:
447 project: Project PB for the current project.
448 member_id: user id of a project owner, member, or contributor.
449
450 Returns:
451 A list of strings for the extra perms granted to the
452 specified user in this project. The list will often be empty.
453 """
454
455 extra_perms = FindExtraPerms(project, member_id)
456
457 if extra_perms:
458 return list(extra_perms.perms)
459 else:
460 return []
461
462
463 def FindExtraPerms(project, member_id):
464 """Return a ExtraPerms PB for the given user in the project.
465
466 Args:
467 project: Project PB for the current project, or None if the user is
468 not currently in a project.
469 member_id: user ID of a project owner, member, or contributor.
470
471 Returns:
472 An ExtraPerms PB, or None.
473 """
474 if not project:
475 # TODO(jrobbins): maybe define extra perms for site-wide operations.
476 return None
477
478 # Users who have no current role cannot have any extra perms. Don't
479 # consider effective_ids (which includes user groups) for this check.
480 if not framework_bizobj.UserIsInProject(project, {member_id}):
481 return None
482
483 for extra_perms in project.extra_perms:
484 if extra_perms.member_id == member_id:
485 return extra_perms
486
487 return None
488
489
490 def GetCustomPermissions(project):
491 """Return a sorted iterable of custom perms granted in a project."""
492 custom_permissions = set()
493 for extra_perms in project.extra_perms:
494 for perm in extra_perms.perms:
495 if perm not in STANDARD_PERMISSIONS:
496 custom_permissions.add(perm)
497
498 return sorted(custom_permissions)
499
500
501 def UserCanViewProject(user, effective_ids, project, expired_before=None):
502 """Return True if the user can view the given project.
503
504 Args:
505 user: User protobuf for the user trying to view the project.
506 effective_ids: set of int user IDs of the user trying to view the project
507 (including any groups), or an empty set for anonymous users.
508 project: the Project protobuf to check.
509 expired_before: option time value for testing.
510
511 Returns:
512 True if the user should be allowed to view the project.
513 """
514 perms = GetPermissions(user, effective_ids, project)
515
516 if IsExpired(project, expired_before=expired_before):
517 needed_perm = VIEW_EXPIRED_PROJECT
518 else:
519 needed_perm = VIEW
520
521 return perms.CanUsePerm(needed_perm, effective_ids, project, [])
522
523
524 def IsExpired(project, expired_before=None):
525 """Return True if a project deletion has been pending long enough already.
526
527 Args:
528 project: The project being viewed.
529 expired_before: If supplied, this method will return True only if the
530 project expired before the given time.
531
532 Returns:
533 True if the project is eligible for reaping.
534 """
535 if project.state != project_pb2.ProjectState.ARCHIVED:
536 return False
537
538 if expired_before is None:
539 expired_before = int(time.time())
540
541 return project.delete_time and project.delete_time < expired_before
542
543
544 def CanDelete(logged_in_user_id, effective_ids, perms, deleted_by_user_id,
545 creator_user_id, project, restrictions, granted_perms=None):
546 """Returns true if user has delete permission.
547
548 Args:
549 logged_in_user_id: int user id of the logged in user.
550 effective_ids: set of int user IDs for the user (including any groups),
551 or an empty set if user is not signed in.
552 perms: instance of PermissionSet describing the current user's permissions.
553 deleted_by_user_id: int user ID of the user having previously deleted this
554 comment, or None, if the comment has never been deleted.
555 creator_user_id: int user ID of the user having created this comment.
556 project: Project PB for the project being accessed, or None if not
557 in a project.
558 restrictions: list of strings that restrict permission usage.
559 granted_perms: optional list of strings of permissions that the user is
560 granted only within the scope of one issue, e.g., by being named in
561 a user-type custom field that grants permissions.
562
563 Returns:
564 True if the logged in user has delete permissions.
565 """
566
567 # User is not logged in or has no permissions.
568 if not logged_in_user_id or not perms:
569 return False
570
571 # Site admin or project owners can delete any comment.
572 permit_delete_any = perms.CanUsePerm(
573 DELETE_ANY, effective_ids, project, restrictions,
574 granted_perms=granted_perms)
575 if permit_delete_any:
576 return True
577
578 # Users cannot undelete unless they deleted.
579 if deleted_by_user_id and deleted_by_user_id != logged_in_user_id:
580 return False
581
582 # Users can delete their own items.
583 permit_delete_own = perms.CanUsePerm(
584 DELETE_OWN, effective_ids, project, restrictions)
585 if permit_delete_own and creator_user_id == logged_in_user_id:
586 return True
587
588 return False
589
590
591 def CanView(effective_ids, perms, project, restrictions, granted_perms=None):
592 """Checks if user has permission to view an issue."""
593 return perms.CanUsePerm(
594 VIEW, effective_ids, project, restrictions, granted_perms=granted_perms)
595
596
597 def CanCreateProject(perms):
598 """Return True if the given user may create a project.
599
600 Args:
601 perms: Permissionset for the current user.
602
603 Returns:
604 True if the user should be allowed to create a project.
605 """
606 # "ANYONE" means anyone who has the needed perm.
607 if (settings.project_creation_restriction ==
608 site_pb2.UserTypeRestriction.ANYONE):
609 return perms.HasPerm(CREATE_PROJECT, None, None)
610
611 if (settings.project_creation_restriction ==
612 site_pb2.UserTypeRestriction.ADMIN_ONLY):
613 return perms.HasPerm(ADMINISTER_SITE, None, None)
614
615 return False
616
617
618 def CanCreateGroup(perms):
619 """Return True if the given user may create a user group.
620
621 Args:
622 perms: Permissionset for the current user.
623
624 Returns:
625 True if the user should be allowed to create a group.
626 """
627 # "ANYONE" means anyone who has the needed perm.
628 if (settings.group_creation_restriction ==
629 site_pb2.UserTypeRestriction.ANYONE):
630 return perms.HasPerm(CREATE_GROUP, None, None)
631
632 if (settings.group_creation_restriction ==
633 site_pb2.UserTypeRestriction.ADMIN_ONLY):
634 return perms.HasPerm(ADMINISTER_SITE, None, None)
635
636 return False
637
638
639 def CanEditGroup(perms, effective_ids, group_owner_ids):
640 """Return True if the given user may edit a user group.
641
642 Args:
643 perms: Permissionset for the current user.
644 effective_ids: set of user IDs for the logged in user.
645 group_owner_ids: set of user IDs of the user group owners.
646
647 Returns:
648 True if the user should be allowed to edit the group.
649 """
650 return (perms.HasPerm(EDIT_GROUP, None, None) or
651 not effective_ids.isdisjoint(group_owner_ids))
652
653
654 def CanViewGroup(perms, effective_ids, group_settings, member_ids, owner_ids,
655 user_project_ids):
656 """Return True if the given user may view a user group.
657
658 Args:
659 perms: Permissionset for the current user.
660 effective_ids: set of user IDs for the logged in user.
661 group_settings: PB of UserGroupSettings.
662 member_ids: A list of member ids of this user group.
663 owner_ids: A list of owner ids of this user group.
664 user_project_ids: A list of project ids which the user has a role.
665
666 Returns:
667 True if the user should be allowed to view the group.
668 """
669 if perms.HasPerm(VIEW_GROUP, None, None):
670 return True
671 # The user could view this group with membership of some projects which are
672 # friends of the group.
673 if (group_settings.friend_projects and user_project_ids
674 and (set(group_settings.friend_projects) & set(user_project_ids))):
675 return True
676 visibility = group_settings.who_can_view_members
677 if visibility == usergroup_pb2.MemberVisibility.OWNERS:
678 return not effective_ids.isdisjoint(owner_ids)
679 elif visibility == usergroup_pb2.MemberVisibility.MEMBERS:
680 return (not effective_ids.isdisjoint(member_ids) or
681 not effective_ids.isdisjoint(owner_ids))
682 else:
683 return True
684
685
686 def IsBanned(user, user_view):
687 """Return True if this user is banned from using our site."""
688 if user is None:
689 return False # Anyone is welcome to browse
690
691 if user.banned:
692 return True # We checked the "Banned" checkbox for this user.
693
694 if user_view:
695 if user_view.domain in settings.banned_user_domains:
696 return True # Some spammers create many accounts with the same domain.
697
698 return False
699
700
701 def CanViewContributorList(mr):
702 """Return True if we should display the list project contributors.
703
704 This is used on the project summary page, when deciding to offer the
705 project People page link, and when generating autocomplete options
706 that include project members.
707
708 Args:
709 mr: commonly used info parsed from the request.
710
711 Returns:
712 True if we should display the project contributor list.
713 """
714 if not mr.project:
715 return False # We are not even in a project context.
716
717 if not mr.project.only_owners_see_contributors:
718 return True # Contributor list is not resticted.
719
720 # If it is hub-and-spoke, check for the perm that allows the user to
721 # view it anyway.
722 return mr.perms.HasPerm(
723 VIEW_CONTRIBUTOR_LIST, mr.auth.user_id, mr.project)
724
725
726 def ShouldCheckForAbandonment(mr):
727 """Return True if user should be warned before changing/deleting their role.
728
729 Args:
730 mr: common info parsed from the user's request.
731
732 Returns:
733 True if user should be warned before changing/deleting their role.
734 """
735 # Note: No need to warn admins because they won't lose access anyway.
736 if mr.perms.CanUsePerm(
737 ADMINISTER_SITE, mr.auth.effective_ids, mr.project, []):
738 return False
739
740 return mr.perms.CanUsePerm(
741 EDIT_PROJECT, mr.auth.effective_ids, mr.project, [])
742
743
744 # For speed, we remember labels that we have already classified as being
745 # restriction labels or not being restriction labels. These sets are for
746 # restrictions in general, not for any particular perm.
747 _KNOWN_RESTRICTION_LABELS = set()
748 _KNOWN_NON_RESTRICTION_LABELS = set()
749
750
751 def IsRestrictLabel(label, perm=''):
752 """Returns True if a given label is a restriction label.
753
754 Args:
755 label: string for the label to examine.
756 perm: a permission that can be restricted (e.g. 'View' or 'Edit').
757 Defaults to '' to mean 'any'.
758
759 Returns:
760 True if a given label is a restriction label (of the specified perm)
761 """
762 if label in _KNOWN_NON_RESTRICTION_LABELS:
763 return False
764 if not perm and label in _KNOWN_RESTRICTION_LABELS:
765 return True
766
767 prefix = ('restrict-%s-' % perm.lower()) if perm else 'restrict-'
768 is_restrict = label.lower().startswith(prefix) and label.count('-') >= 2
769
770 if is_restrict:
771 _KNOWN_RESTRICTION_LABELS.add(label)
772 elif not perm:
773 _KNOWN_NON_RESTRICTION_LABELS.add(label)
774
775 return is_restrict
776
777
778 def HasRestrictions(issue, perm=''):
779 """Return True if the issue has any restrictions (on the specified perm)."""
780 return (
781 any(IsRestrictLabel(lab, perm=perm) for lab in issue.labels) or
782 any(IsRestrictLabel(lab, perm=perm) for lab in issue.derived_labels))
783
784
785 def GetRestrictions(issue):
786 """Return a list of restriction labels on the given issue."""
787 if not issue:
788 return []
789
790 return [lab.lower() for lab in tracker_bizobj.GetLabels(issue)
791 if IsRestrictLabel(lab)]
792
793
794 def CanViewIssue(
795 effective_ids, perms, project, issue, allow_viewing_deleted=False,
796 granted_perms=None):
797 """Checks if user has permission to view an artifact.
798
799 Args:
800 effective_ids: set of user IDs for the logged in user and any user
801 group memberships. Should be an empty set for anon users.
802 perms: PermissionSet for the user.
803 project: Project PB for the project that contains this issue.
804 issue: Issue PB for the issue being viewed.
805 allow_viewing_deleted: True if the user should be allowed to view
806 deleted artifacts.
807 granted_perms: optional list of strings of permissions that the user is
808 granted only within the scope of one issue, e.g., by being named in
809 a user-type custom field that grants permissions.
810
811 Returns:
812 True iff the user can view the specified issue.
813 """
814 if issue.deleted and not allow_viewing_deleted:
815 # No one can view a deleted issue. If the user can undelete, that
816 # goes through the custom 404 page.
817 return False
818
819 # Check to see if the user can view anything in the project.
820 if not perms.CanUsePerm(VIEW, effective_ids, project, []):
821 return False
822
823 if not HasRestrictions(issue):
824 return True
825
826 return CanViewRestrictedIssueInVisibleProject(
827 effective_ids, perms, project, issue, granted_perms=granted_perms)
828
829
830 def CanViewRestrictedIssueInVisibleProject(
831 effective_ids, perms, project, issue, granted_perms=None):
832 """Return True if the user can view this issue. Assumes project is OK."""
833 # The reporter, owner, and CC'd users can always see the issue.
834 # In effect, these fields override artifact restriction labels.
835 if effective_ids:
836 if (issue.reporter_id in effective_ids or
837 tracker_bizobj.GetOwnerId(issue) in effective_ids or
838 not effective_ids.isdisjoint(tracker_bizobj.GetCcIds(issue))):
839 return True
840
841 # Otherwise, apply the usual permission checking.
842 return CanView(
843 effective_ids, perms, project, GetRestrictions(issue),
844 granted_perms=granted_perms)
845
846
847 def CanEditIssue(effective_ids, perms, project, issue, granted_perms=None):
848 """Return True if a user can edit an issue.
849
850 Args:
851 effective_ids: set of user IDs for the logged in user and any user
852 group memberships. Should be an empty set for anon users.
853 perms: PermissionSet for the user.
854 project: Project PB for the project that contains this issue.
855 issue: Issue PB for the issue being viewed.
856 granted_perms: optional list of strings of permissions that the user is
857 granted only within the scope of one issue, e.g., by being named in
858 a user-type custom field that grants permissions.
859
860 Returns:
861 True iff the user can edit the specified issue.
862 """
863 # TODO(jrobbins): We need to actually grant View+EditIssue in most cases.
864 # So, always grant View whenever there is any granted perm.
865 if not CanViewIssue(
866 effective_ids, perms, project, issue, granted_perms=granted_perms):
867 return False
868
869 # The issue owner can always edit the issue.
870 if effective_ids:
871 if tracker_bizobj.GetOwnerId(issue) in effective_ids:
872 return True
873
874 # Otherwise, apply the usual permission checking.
875 return perms.CanUsePerm(
876 EDIT_ISSUE, effective_ids, project, GetRestrictions(issue),
877 granted_perms=granted_perms)
878
879
880 def CanCommentIssue(effective_ids, perms, project, issue, granted_perms=None):
881 """Return True if a user can comment on an issue."""
882
883 return perms.CanUsePerm(
884 ADD_ISSUE_COMMENT, effective_ids, project,
885 GetRestrictions(issue), granted_perms=granted_perms)
886
887
888 def CanViewComponentDef(effective_ids, perms, project, component_def):
889 """Return True if a user can view the given component definition."""
890 if not effective_ids.isdisjoint(component_def.admin_ids):
891 return True # Component admins can view that component.
892
893 # TODO(jrobbins): check restrictions on the component definition.
894 return perms.CanUsePerm(VIEW, effective_ids, project, [])
895
896
897 def CanEditComponentDef(effective_ids, perms, project, component_def, config):
898 """Return True if a user can edit the given component definition."""
899 if not effective_ids.isdisjoint(component_def.admin_ids):
900 return True # Component admins can edit that component.
901
902 # Check to see if user is admin of any parent component.
903 parent_components = tracker_bizobj.FindAncestorComponents(
904 config, component_def)
905 for parent in parent_components:
906 if not effective_ids.isdisjoint(parent.admin_ids):
907 return True
908
909 return perms.CanUsePerm(EDIT_PROJECT, effective_ids, project, [])
910
911
912 def CanViewFieldDef(effective_ids, perms, project, field_def):
913 """Return True if a user can view the given field definition."""
914 if not effective_ids.isdisjoint(field_def.admin_ids):
915 return True # Field admins can view that field.
916
917 # TODO(jrobbins): check restrictions on the field definition.
918 return perms.CanUsePerm(VIEW, effective_ids, project, [])
919
920
921 def CanEditFieldDef(effective_ids, perms, project, field_def):
922 """Return True if a user can edit the given field definition."""
923 if not effective_ids.isdisjoint(field_def.admin_ids):
924 return True # Field admins can edit that field.
925
926 return perms.CanUsePerm(EDIT_PROJECT, effective_ids, project, [])
927
928
929 def CanViewTemplate(effective_ids, perms, project, template):
930 """Return True if a user can view the given issue template."""
931 if not effective_ids.isdisjoint(template.admin_ids):
932 return True # template admins can view that template.
933
934 # Members-only templates are only shown to members, other templates are
935 # shown to any user that is generally allowed to view project content.
936 if template.members_only:
937 return framework_bizobj.UserIsInProject(project, effective_ids)
938 else:
939 return perms.CanUsePerm(VIEW, effective_ids, project, [])
940
941
942 def CanEditTemplate(effective_ids, perms, project, template):
943 """Return True if a user can edit the given field definition."""
944 if not effective_ids.isdisjoint(template.admin_ids):
945 return True # Template admins can edit that template.
946
947 return perms.CanUsePerm(EDIT_PROJECT, effective_ids, project, [])
948
949
950 class Error(Exception):
951 """Base class for errors from this module."""
952
953
954 class PermissionException(Error):
955 """The user is not authorized to make the current request."""
956
957
958 class BannedUserException(Error):
959 """The user has been banned from using our service."""
OLDNEW
« no previous file with comments | « appengine/monorail/framework/pbproxy_test_pb2.py ('k') | appengine/monorail/framework/profiler.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698