blob: 37f6cdc7bee741aa2cd5bbd0b1d38b54379939fc [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001# Copyright 2016 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
Copybara854996b2021-09-07 19:36:02 +00004
5"""Classes and functions to implement permission checking.
6
7The main data structure is a simple map from (user role, project status,
8project_access_level) to specific perms.
9
10A perm is simply a string that indicates that the user has a given
11permission. The servlets and templates can test whether the current
12user has permission to see a UI element or perform an action by
13testing for the presence of the corresponding perm in the user's
14permission set.
15
16The user role is one of admin, owner, member, outsider user, or anon.
17The project status is one of the project states defined in project_pb2,
18or a special constant defined below. Likewise for access level.
19"""
20from __future__ import print_function
21from __future__ import division
22from __future__ import absolute_import
23
24import bisect
25import collections
26import logging
27import time
28
29import ezt
30
31import settings
32from framework import framework_bizobj
33from framework import framework_constants
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010034from mrproto import project_pb2
35from mrproto import site_pb2
36from mrproto import tracker_pb2
37from mrproto import usergroup_pb2
Copybara854996b2021-09-07 19:36:02 +000038from tracker import tracker_bizobj
39
40# Constants that define permissions.
41# Note that perms with a leading "_" can never be granted
42# to users who are not site admins.
43VIEW = 'View'
44EDIT_PROJECT = 'EditProject'
45CREATE_PROJECT = 'CreateProject'
46PUBLISH_PROJECT = '_PublishProject' # for making "doomed" projects LIVE
47VIEW_DEBUG = '_ViewDebug' # on-page debugging info
48EDIT_OTHER_USERS = '_EditOtherUsers' # can edit other user's prefs, ban, etc.
49CUSTOMIZE_PROCESS = 'CustomizeProcess' # can use some enterprise features
50VIEW_EXPIRED_PROJECT = '_ViewExpiredProject' # view long-deleted projects
51# View the list of contributors even in hub-and-spoke projects.
52VIEW_CONTRIBUTOR_LIST = 'ViewContributorList'
53
54# Quota
55VIEW_QUOTA = 'ViewQuota'
56EDIT_QUOTA = 'EditQuota'
57
58# Permissions for editing user groups
59CREATE_GROUP = 'CreateGroup'
60EDIT_GROUP = 'EditGroup'
61DELETE_GROUP = 'DeleteGroup'
62VIEW_GROUP = 'ViewGroup'
63
64# Perms for Source tools
65# TODO(jrobbins): Monorail is just issue tracking with no version control, so
66# phase out use of the term "Commit", sometime after Monorail's initial launch.
67COMMIT = 'Commit'
68
69# Perms for issue tracking
70CREATE_ISSUE = 'CreateIssue'
71EDIT_ISSUE = 'EditIssue'
72EDIT_ISSUE_OWNER = 'EditIssueOwner'
73EDIT_ISSUE_SUMMARY = 'EditIssueSummary'
74EDIT_ISSUE_STATUS = 'EditIssueStatus'
75EDIT_ISSUE_CC = 'EditIssueCc'
76EDIT_ISSUE_APPROVAL = 'EditIssueApproval'
77DELETE_ISSUE = 'DeleteIssue'
78# This allows certain API clients to attribute comments to other users.
79# The permission is not offered in the UI, but it can be typed in as
80# a custom permission name. The ID of the API client is also recorded.
81IMPORT_COMMENT = 'ImportComment'
82ADD_ISSUE_COMMENT = 'AddIssueComment'
83VIEW_INBOUND_MESSAGES = 'ViewInboundMessages'
84CREATE_HOTLIST = 'CreateHotlist'
85# Note, there is no separate DELETE_ATTACHMENT perm. We
86# allow a user to delete an attachment iff they could soft-delete
87# the comment that holds the attachment.
88
89# Note: the "_" in the perm name makes it impossible for a
90# project owner to grant it to anyone as an extra perm.
91ADMINISTER_SITE = '_AdministerSite'
92
93# Permissions to soft-delete artifact comment
94DELETE_ANY = 'DeleteAny'
95DELETE_OWN = 'DeleteOwn'
96
97# Granting this allows owners to delegate some team management work.
98EDIT_ANY_MEMBER_NOTES = 'EditAnyMemberNotes'
99
100# Permission to star/unstar any artifact.
101SET_STAR = 'SetStar'
102
103# Permission to flag any artifact as spam.
104FLAG_SPAM = 'FlagSpam'
105VERDICT_SPAM = 'VerdictSpam'
Copybara854996b2021-09-07 19:36:02 +0000106
107# Permissions for custom fields.
108EDIT_FIELD_DEF = 'EditFieldDef'
109EDIT_FIELD_DEF_VALUE = 'EditFieldDefValue'
110
111# Permissions for user hotlists.
112ADMINISTER_HOTLIST = 'AdministerHotlist'
113EDIT_HOTLIST = 'EditHotlist'
114VIEW_HOTLIST = 'ViewHotlist'
115HOTLIST_OWNER_PERMISSIONS = [ADMINISTER_HOTLIST, EDIT_HOTLIST]
116HOTLIST_EDITOR_PERMISSIONS = [EDIT_HOTLIST]
117
118RESTRICTED_APPROVAL_STATUSES = [
119 tracker_pb2.ApprovalStatus.NA,
120 tracker_pb2.ApprovalStatus.APPROVED,
121 tracker_pb2.ApprovalStatus.NOT_APPROVED]
122
123STANDARD_ADMIN_PERMISSIONS = [
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200124 EDIT_PROJECT, CREATE_PROJECT, PUBLISH_PROJECT, VIEW_DEBUG, EDIT_OTHER_USERS,
125 CUSTOMIZE_PROCESS, VIEW_QUOTA, EDIT_QUOTA, ADMINISTER_SITE,
126 EDIT_ANY_MEMBER_NOTES, VERDICT_SPAM
127]
Copybara854996b2021-09-07 19:36:02 +0000128
129STANDARD_ISSUE_PERMISSIONS = [
130 VIEW, EDIT_ISSUE, ADD_ISSUE_COMMENT, DELETE_ISSUE, FLAG_SPAM]
131
132# Monorail has no source control, but keep COMMIT for backward compatability.
133STANDARD_SOURCE_PERMISSIONS = [COMMIT]
134
135STANDARD_COMMENT_PERMISSIONS = [DELETE_OWN, DELETE_ANY]
136
137STANDARD_OTHER_PERMISSIONS = [CREATE_ISSUE, FLAG_SPAM, SET_STAR]
138
139STANDARD_PERMISSIONS = (STANDARD_ADMIN_PERMISSIONS +
140 STANDARD_ISSUE_PERMISSIONS +
141 STANDARD_SOURCE_PERMISSIONS +
142 STANDARD_COMMENT_PERMISSIONS +
143 STANDARD_OTHER_PERMISSIONS)
144
145# roles
146SITE_ADMIN_ROLE = 'admin'
147OWNER_ROLE = 'owner'
148COMMITTER_ROLE = 'committer'
149CONTRIBUTOR_ROLE = 'contributor'
150USER_ROLE = 'user'
151ANON_ROLE = 'anon'
152
153# Project state out-of-band values for keys
154UNDEFINED_STATUS = 'undefined_status'
155UNDEFINED_ACCESS = 'undefined_access'
156WILDCARD_ACCESS = 'wildcard_access'
157
158
159class PermissionSet(object):
160 """Class to represent the set of permissions available to the user."""
161
162 def __init__(self, perm_names, consider_restrictions=True):
163 """Create a PermissionSet with the given permissions.
164
165 Args:
166 perm_names: a list of permission name strings.
167 consider_restrictions: if true, the user's permissions can be blocked
168 by restriction labels on an artifact. Project owners and site
169 admins do not consider restrictions so that they cannot
170 "lock themselves out" of editing an issue.
171 """
172 self.perm_names = frozenset(p.lower() for p in perm_names)
173 self.consider_restrictions = consider_restrictions
174
175 def __getattr__(self, perm_name):
176 """Easy permission testing in EZT. E.g., [if-any perms.format_drive]."""
177 return ezt.boolean(self.HasPerm(perm_name, None, None))
178
179 def CanUsePerm(
180 self, perm_name, effective_ids, project, restriction_labels,
181 granted_perms=None):
182 """Return True if the user can use the given permission.
183
184 Args:
185 perm_name: string name of permission, e.g., 'EditIssue'.
186 effective_ids: set of int user IDs for the user (including any groups),
187 or an empty set if user is not signed in.
188 project: Project PB for the project being accessed, or None if not
189 in a project.
190 restriction_labels: list of strings that restrict permission usage.
191 granted_perms: optional list of lowercase strings of permissions that the
192 user is granted only within the scope of one issue, e.g., by being
193 named in a user-type custom field that grants permissions.
194
195 Restriction labels have 3 parts, e.g.:
196 'Restrict-EditIssue-InnerCircle' blocks the use of just the
197 EditIssue permission, unless the user also has the InnerCircle
198 permission. This allows fine-grained restrictions on specific
199 actions, such as editing, commenting, or deleting.
200
201 Restriction labels and permissions are case-insensitive.
202
203 Returns:
204 True if the user can use the given permission, or False
205 if they cannot (either because they don't have that permission
206 or because it is blocked by a relevant restriction label).
207 """
208 # TODO(jrobbins): room for performance improvement: avoid set creation and
209 # repeated string operations.
210 granted_perms = granted_perms or set()
211 perm_lower = perm_name.lower()
212 if perm_lower in granted_perms:
213 return True
214
215 needed_perms = {perm_lower}
216 if self.consider_restrictions:
217 for label in restriction_labels:
218 label = label.lower()
219 # format: Restrict-Action-ToThisPerm
220 _kw, requested_perm, needed_perm = label.split('-', 2)
221 if requested_perm == perm_lower and needed_perm not in granted_perms:
222 needed_perms.add(needed_perm)
223
224 if not effective_ids:
225 effective_ids = {framework_constants.NO_USER_SPECIFIED}
226
227 # Get all extra perms for all effective ids.
228 # Id X might have perm A and Y might have B, if both A and B are needed
229 # True should be returned.
230 extra_perms = set()
231 for user_id in effective_ids:
232 extra_perms.update(p.lower() for p in GetExtraPerms(project, user_id))
233 return all(self.HasPerm(perm, None, None, extra_perms)
234 for perm in needed_perms)
235
236 def HasPerm(self, perm_name, user_id, project, extra_perms=None):
237 """Return True if the user has the given permission (ignoring user groups).
238
239 Args:
240 perm_name: string name of permission, e.g., 'EditIssue'.
241 user_id: int user id of the user, or None if user is not signed in.
242 project: Project PB for the project being accessed, or None if not
243 in a project.
244 extra_perms: list of extra perms. If not given, GetExtraPerms will be
245 called to get them.
246
247 Returns:
248 True if the user has the given perm.
249 """
250 perm_name = perm_name.lower()
251
252 # Return early if possible.
253 if perm_name in self.perm_names:
254 return True
255
256 if extra_perms is None:
257 # TODO(jrobbins): room for performance improvement: pre-compute
258 # extra perms (maybe merge them into the perms object), avoid
259 # redundant call to lower().
260 return any(
261 p.lower() == perm_name
262 for p in GetExtraPerms(project, user_id))
263
264 return perm_name in extra_perms
265
266 def DebugString(self):
267 """Return a useful string to show when debugging."""
268 return 'PermissionSet(%s)' % ', '.join(sorted(self.perm_names))
269
270 def __repr__(self):
271 return '%s(%r)' % (self.__class__.__name__, self.perm_names)
272
273
274EMPTY_PERMISSIONSET = PermissionSet([])
275
276READ_ONLY_PERMISSIONSET = PermissionSet([VIEW])
277
278USER_PERMISSIONSET = PermissionSet([
279 VIEW, FLAG_SPAM, SET_STAR,
280 CREATE_ISSUE, ADD_ISSUE_COMMENT,
281 DELETE_OWN])
282
283CONTRIBUTOR_ACTIVE_PERMISSIONSET = PermissionSet(
284 [VIEW,
285 FLAG_SPAM, VERDICT_SPAM, SET_STAR,
286 CREATE_ISSUE, ADD_ISSUE_COMMENT,
287 DELETE_OWN])
288
289CONTRIBUTOR_INACTIVE_PERMISSIONSET = PermissionSet(
290 [VIEW])
291
292COMMITTER_ACTIVE_PERMISSIONSET = PermissionSet(
293 [VIEW, COMMIT, VIEW_CONTRIBUTOR_LIST,
294 FLAG_SPAM, VERDICT_SPAM, SET_STAR, VIEW_QUOTA,
295 CREATE_ISSUE, ADD_ISSUE_COMMENT, EDIT_ISSUE, VIEW_INBOUND_MESSAGES,
296 DELETE_OWN])
297
298COMMITTER_INACTIVE_PERMISSIONSET = PermissionSet(
299 [VIEW, VIEW_CONTRIBUTOR_LIST,
300 VIEW_INBOUND_MESSAGES, VIEW_QUOTA])
301
302OWNER_ACTIVE_PERMISSIONSET = PermissionSet(
303 [VIEW, VIEW_CONTRIBUTOR_LIST, EDIT_PROJECT, COMMIT,
304 FLAG_SPAM, VERDICT_SPAM, SET_STAR, VIEW_QUOTA,
305 CREATE_ISSUE, ADD_ISSUE_COMMENT, EDIT_ISSUE, DELETE_ISSUE,
306 VIEW_INBOUND_MESSAGES,
307 DELETE_ANY, EDIT_ANY_MEMBER_NOTES],
308 consider_restrictions=False)
309
310OWNER_INACTIVE_PERMISSIONSET = PermissionSet(
311 [VIEW, VIEW_CONTRIBUTOR_LIST, EDIT_PROJECT,
312 VIEW_INBOUND_MESSAGES, VIEW_QUOTA],
313 consider_restrictions=False)
314
315ADMIN_PERMISSIONSET = PermissionSet(
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200316 [
317 VIEW, VIEW_CONTRIBUTOR_LIST, CREATE_PROJECT, EDIT_PROJECT,
318 PUBLISH_PROJECT, VIEW_DEBUG, COMMIT, CUSTOMIZE_PROCESS, FLAG_SPAM,
319 VERDICT_SPAM, SET_STAR, ADMINISTER_SITE, VIEW_EXPIRED_PROJECT,
320 EDIT_OTHER_USERS, VIEW_QUOTA, EDIT_QUOTA, CREATE_ISSUE,
321 ADD_ISSUE_COMMENT, EDIT_ISSUE, DELETE_ISSUE, EDIT_ISSUE_APPROVAL,
322 VIEW_INBOUND_MESSAGES, DELETE_ANY, EDIT_ANY_MEMBER_NOTES, CREATE_GROUP,
323 EDIT_GROUP, DELETE_GROUP, VIEW_GROUP, CREATE_HOTLIST
324 ],
325 consider_restrictions=False)
Copybara854996b2021-09-07 19:36:02 +0000326
327GROUP_IMPORT_BORG_PERMISSIONSET = PermissionSet(
328 [CREATE_GROUP, VIEW_GROUP, EDIT_GROUP])
329
330# Permissions for project pages, e.g., the project summary page
331_PERMISSIONS_TABLE = {
332
333 # Project owners can view and edit artifacts in a LIVE project.
334 (OWNER_ROLE, project_pb2.ProjectState.LIVE, WILDCARD_ACCESS):
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100335 OWNER_ACTIVE_PERMISSIONSET,
Copybara854996b2021-09-07 19:36:02 +0000336
337 # Project owners can view, but not edit artifacts in ARCHIVED.
338 # Note: EDIT_PROJECT is not enough permission to change an ARCHIVED project
339 # back to LIVE if a delete_time was set.
340 (OWNER_ROLE, project_pb2.ProjectState.ARCHIVED, WILDCARD_ACCESS):
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100341 OWNER_INACTIVE_PERMISSIONSET,
Copybara854996b2021-09-07 19:36:02 +0000342
343 # Project members can view their own project, regardless of state.
344 (COMMITTER_ROLE, project_pb2.ProjectState.LIVE, WILDCARD_ACCESS):
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100345 COMMITTER_ACTIVE_PERMISSIONSET,
Copybara854996b2021-09-07 19:36:02 +0000346 (COMMITTER_ROLE, project_pb2.ProjectState.ARCHIVED, WILDCARD_ACCESS):
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100347 COMMITTER_INACTIVE_PERMISSIONSET,
Copybara854996b2021-09-07 19:36:02 +0000348
349 # Project contributors can view their own project, regardless of state.
350 (CONTRIBUTOR_ROLE, project_pb2.ProjectState.LIVE, WILDCARD_ACCESS):
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100351 CONTRIBUTOR_ACTIVE_PERMISSIONSET,
Copybara854996b2021-09-07 19:36:02 +0000352 (CONTRIBUTOR_ROLE, project_pb2.ProjectState.ARCHIVED, WILDCARD_ACCESS):
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100353 CONTRIBUTOR_INACTIVE_PERMISSIONSET,
Copybara854996b2021-09-07 19:36:02 +0000354
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100355 # Non-members users can read and comment in projects with access == ANYONE.
356 (
357 USER_ROLE, project_pb2.ProjectState.LIVE,
358 project_pb2.ProjectAccess.ANYONE):
359 USER_PERMISSIONSET,
Copybara854996b2021-09-07 19:36:02 +0000360
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100361 # Non-members users can read archived projects with access == ANYONE.
362 (
363 USER_ROLE, project_pb2.ProjectState.ARCHIVED,
364 project_pb2.ProjectAccess.ANYONE):
365 READ_ONLY_PERMISSIONSET,
366
367 # Anonymous users can only read projects with access == ANYONE,
368 # regardless of state.
369 (
370 ANON_ROLE, project_pb2.ProjectState.LIVE,
371 project_pb2.ProjectAccess.ANYONE):
372 READ_ONLY_PERMISSIONSET,
373 (
374 ANON_ROLE, project_pb2.ProjectState.ARCHIVED,
375 project_pb2.ProjectAccess.ANYONE):
376 READ_ONLY_PERMISSIONSET,
Copybara854996b2021-09-07 19:36:02 +0000377
378 # Permissions for site pages, e.g., creating a new project
379 (USER_ROLE, UNDEFINED_STATUS, UNDEFINED_ACCESS):
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100380 PermissionSet([CREATE_PROJECT, CREATE_GROUP, CREATE_HOTLIST]),
381}
Copybara854996b2021-09-07 19:36:02 +0000382
383def GetPermissions(user, effective_ids, project):
384 """Return a permission set appropriate for the user and project.
385
386 Args:
387 user: The User PB for the signed-in user, or None for anon users.
388 effective_ids: set of int user IDs for the current user and all user
389 groups that they are a member of. This will be an empty set for
390 anonymous users.
391 project: either a Project protobuf, or None for a page whose scope is
392 wider than a single project.
393
394 Returns:
395 a PermissionSet object for the current user and project (or for
396 site-wide operations if project is None).
397
398 If an exact match for the user's role and project status is found, that is
399 returned. Otherwise, we look for permissions for the user's role that is
400 not specific to any project status, or not specific to any project access
401 level. If neither of those are defined, we give the user an empty
402 permission set.
403 """
404 # Site admins get ADMIN_PERMISSIONSET regardless of groups or projects.
405 if user and user.is_site_admin:
406 return ADMIN_PERMISSIONSET
407
408 # Grant the borg job permission to view/edit groups
409 if user and user.email == settings.borg_service_account:
410 return GROUP_IMPORT_BORG_PERMISSIONSET
411
412 # Anon users don't need to accumulate anything.
413 if not effective_ids:
414 role, status, access = _GetPermissionKey(None, project)
415 return _LookupPermset(role, status, access)
416
417 effective_perms = set()
418 consider_restrictions = True
419
420 # Check for signed-in user with no roles in the current project.
421 if not project or not framework_bizobj.UserIsInProject(
422 project, effective_ids):
423 role, status, access = _GetPermissionKey(None, project)
424 return _LookupPermset(USER_ROLE, status, access)
425
426 # Signed-in user gets the union of all their PermissionSets from the table.
427 for user_id in effective_ids:
428 role, status, access = _GetPermissionKey(user_id, project)
429 role_perms = _LookupPermset(role, status, access)
430 # Accumulate a union of all the user's permissions.
431 effective_perms.update(role_perms.perm_names)
432 # If any role allows the user to ignore restriction labels, then
433 # ignore them overall.
434 if not role_perms.consider_restrictions:
435 consider_restrictions = False
436
437 return PermissionSet(
438 effective_perms, consider_restrictions=consider_restrictions)
439
440
441def UpdateIssuePermissions(
442 perms, project, issue, effective_ids, granted_perms=None, config=None):
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100443 """Update the PermissionSet for a specific issue.
Copybara854996b2021-09-07 19:36:02 +0000444
445 Take into account granted permissions and label restrictions to filter the
446 permissions, and updates the VIEW and EDIT_ISSUE permissions depending on the
447 role of the user in the issue (i.e. owner, reporter, cc or approver).
448
449 Args:
450 perms: The PermissionSet to update.
451 project: The Project PB for the issue project.
452 issue: The Issue PB.
453 effective_ids: Set of int user IDs for the current user and all user
454 groups that they are a member of. This will be an empty set for
455 anonymous users.
456 granted_perms: optional list of strings of permissions that the user is
457 granted only within the scope of one issue, e.g., by being named in
458 a user-type custom field that grants permissions.
459 config: optional ProjectIssueConfig PB where granted perms should be
460 extracted from, if granted_perms is not given.
461 """
462 if config:
463 granted_perms = tracker_bizobj.GetGrantedPerms(
464 issue, effective_ids, config)
465 elif granted_perms is None:
466 granted_perms = []
467
468 # If the user has no permission to view the project, it has no permissions on
469 # this issue.
470 if not perms.HasPerm(VIEW, None, None):
471 return EMPTY_PERMISSIONSET
472
473 # Compute the restrictions for the given issue and store them in a dictionary
474 # of {perm: set(needed_perms)}.
475 restrictions = collections.defaultdict(set)
476 if perms.consider_restrictions:
477 for label in GetRestrictions(issue):
478 label = label.lower()
479 # format: Restrict-Action-ToThisPerm
480 _, requested_perm, needed_perm = label.split('-', 2)
481 restrictions[requested_perm.lower()].add(needed_perm.lower())
482
483 # Store the user permissions, and the extra permissions of all effective IDs
484 # in the given project.
485 all_perms = set(perms.perm_names)
486 for effective_id in effective_ids:
487 all_perms.update(p.lower() for p in GetExtraPerms(project, effective_id))
488
489 # And filter them applying the restriction labels.
490 filtered_perms = set()
491 for perm_name in all_perms:
492 perm_name = perm_name.lower()
493 restricted = any(
494 restriction not in all_perms and restriction not in granted_perms
495 for restriction in restrictions.get(perm_name, []))
496 if not restricted:
497 filtered_perms.add(perm_name)
498
499 # Add any granted permissions.
500 filtered_perms.update(granted_perms)
501
502 # The VIEW perm might have been removed due to restrictions, but the issue
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100503 # owner, reporter, cc and approvers can always view an issue.
Copybara854996b2021-09-07 19:36:02 +0000504 allowed_ids = set(
505 tracker_bizobj.GetCcIds(issue)
506 + tracker_bizobj.GetApproverIds(issue)
507 + [issue.reporter_id, tracker_bizobj.GetOwnerId(issue)])
508 if effective_ids and not allowed_ids.isdisjoint(effective_ids):
509 filtered_perms.add(VIEW.lower())
510
511 # If the issue is deleted, only the VIEW and DELETE_ISSUE permissions are
512 # relevant.
513 if issue.deleted:
514 if VIEW.lower() not in filtered_perms:
515 return EMPTY_PERMISSIONSET
516 if DELETE_ISSUE.lower() in filtered_perms:
517 return PermissionSet([VIEW, DELETE_ISSUE], perms.consider_restrictions)
518 return PermissionSet([VIEW], perms.consider_restrictions)
519
520 # The EDIT_ISSUE permission might have been removed due to restrictions, but
521 # the owner always has permission to edit it.
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100522 if (effective_ids and tracker_bizobj.GetOwnerId(issue) in effective_ids and
523 project and project.state != project_pb2.ProjectState.ARCHIVED):
Copybara854996b2021-09-07 19:36:02 +0000524 filtered_perms.add(EDIT_ISSUE.lower())
525
526 return PermissionSet(filtered_perms, perms.consider_restrictions)
527
528
529def _LookupPermset(role, status, access):
530 """Lookup the appropriate PermissionSet in _PERMISSIONS_TABLE.
531
532 Args:
533 role: a string indicating the user's role in the project.
534 status: a Project PB status value, or UNDEFINED_STATUS.
535 access: a Project PB access value, or UNDEFINED_ACCESS.
536
537 Returns:
538 A PermissionSet that is appropriate for that kind of user in that
539 project context.
540 """
541 if (role, status, access) in _PERMISSIONS_TABLE:
542 return _PERMISSIONS_TABLE[(role, status, access)]
543 elif (role, status, WILDCARD_ACCESS) in _PERMISSIONS_TABLE:
544 return _PERMISSIONS_TABLE[(role, status, WILDCARD_ACCESS)]
545 else:
546 return EMPTY_PERMISSIONSET
547
548
549def _GetPermissionKey(user_id, project, expired_before=None):
550 """Return a permission lookup key appropriate for the user and project."""
551 if user_id is None:
552 role = ANON_ROLE
553 elif project and IsExpired(project, expired_before=expired_before):
554 role = USER_ROLE # Do not honor roles in expired projects.
555 elif project and user_id in project.owner_ids:
556 role = OWNER_ROLE
557 elif project and user_id in project.committer_ids:
558 role = COMMITTER_ROLE
559 elif project and user_id in project.contributor_ids:
560 role = CONTRIBUTOR_ROLE
561 else:
562 role = USER_ROLE
563
564 if project is None:
565 status = UNDEFINED_STATUS
566 else:
567 status = project.state
568
569 if project is None:
570 access = UNDEFINED_ACCESS
571 else:
572 access = project.access
573
574 return role, status, access
575
576
577def GetExtraPerms(project, member_id):
578 """Return a list of extra perms for the user in the project.
579
580 Args:
581 project: Project PB for the current project.
582 member_id: user id of a project owner, member, or contributor.
583
584 Returns:
585 A list of strings for the extra perms granted to the
586 specified user in this project. The list will often be empty.
587 """
588
589 _, extra_perms = FindExtraPerms(project, member_id)
590
591 if extra_perms:
592 return list(extra_perms.perms)
593 else:
594 return []
595
596
597def FindExtraPerms(project, member_id):
598 """Return a ExtraPerms PB for the given user in the project.
599
600 Args:
601 project: Project PB for the current project, or None if the user is
602 not currently in a project.
603 member_id: user ID of a project owner, member, or contributor.
604
605 Returns:
606 A pair (idx, extra_perms).
607 * If project is None or member_id is not part of the project, both are None.
608 * If member_id has no extra_perms, extra_perms is None, and idx points to
609 the position where it should go to keep the ExtraPerms sorted in project.
610 * Otherwise, idx is the position of member_id in the project's extra_perms,
611 and extra_perms is an ExtraPerms PB.
612 """
613 class ExtraPermsView(object):
614 def __len__(self):
615 return len(project.extra_perms)
616 def __getitem__(self, idx):
617 return project.extra_perms[idx].member_id
618
619 if not project:
620 # TODO(jrobbins): maybe define extra perms for site-wide operations.
621 return None, None
622
623 # Users who have no current role cannot have any extra perms. Don't
624 # consider effective_ids (which includes user groups) for this check.
625 if not framework_bizobj.UserIsInProject(project, {member_id}):
626 return None, None
627
628 extra_perms_view = ExtraPermsView()
629 # Find the index of the first extra_perms.member_id greater than or equal to
630 # member_id.
631 idx = bisect.bisect_left(extra_perms_view, member_id)
632 if idx >= len(project.extra_perms) or extra_perms_view[idx] > member_id:
633 return idx, None
634 return idx, project.extra_perms[idx]
635
636
637def GetCustomPermissions(project):
638 """Return a sorted iterable of custom perms granted in a project."""
639 custom_permissions = set()
640 for extra_perms in project.extra_perms:
641 for perm in extra_perms.perms:
642 if perm not in STANDARD_PERMISSIONS:
643 custom_permissions.add(perm)
644
645 return sorted(custom_permissions)
646
647
648def UserCanViewProject(user, effective_ids, project, expired_before=None):
649 """Return True if the user can view the given project.
650
651 Args:
652 user: User protobuf for the user trying to view the project.
653 effective_ids: set of int user IDs of the user trying to view the project
654 (including any groups), or an empty set for anonymous users.
655 project: the Project protobuf to check.
656 expired_before: option time value for testing.
657
658 Returns:
659 True if the user should be allowed to view the project.
660 """
661 perms = GetPermissions(user, effective_ids, project)
662
663 if IsExpired(project, expired_before=expired_before):
664 needed_perm = VIEW_EXPIRED_PROJECT
665 else:
666 needed_perm = VIEW
667
668 return perms.CanUsePerm(needed_perm, effective_ids, project, [])
669
670
671def IsExpired(project, expired_before=None):
672 """Return True if a project deletion has been pending long enough already.
673
674 Args:
675 project: The project being viewed.
676 expired_before: If supplied, this method will return True only if the
677 project expired before the given time.
678
679 Returns:
680 True if the project is eligible for reaping.
681 """
682 if project.state != project_pb2.ProjectState.ARCHIVED:
683 return False
684
685 if expired_before is None:
686 expired_before = int(time.time())
687
688 return project.delete_time and project.delete_time < expired_before
689
690
691def CanDeleteComment(comment, commenter, user_id, perms):
692 """Returns true if the user can (un)delete the given comment.
693
694 UpdateIssuePermissions must have been called first.
695
696 Args:
697 comment: An IssueComment PB object.
698 commenter: An User PB object with the user who created the comment.
699 user_id: The ID of the user whose permission we want to check.
700 perms: The PermissionSet with the issue permissions.
701
702 Returns:
703 True if the user can (un)delete the comment.
704 """
705 # User is not logged in or has no permissions.
706 if not user_id or not perms:
707 return False
708
709 # Nobody can (un)delete comments by banned users or spam comments, which
710 # should be un-flagged instead.
711 if commenter.banned or comment.is_spam:
712 return False
713
714 # Site admin or project owners can delete any comment.
715 permit_delete_any = perms.HasPerm(DELETE_ANY, None, None, [])
716 if permit_delete_any:
717 return True
718
719 # Users cannot undelete unless they deleted.
720 if comment.deleted_by and comment.deleted_by != user_id:
721 return False
722
723 # Users can delete their own items.
724 permit_delete_own = perms.HasPerm(DELETE_OWN, None, None, [])
725 if permit_delete_own and comment.user_id == user_id:
726 return True
727
728 return False
729
730
731def CanFlagComment(comment, commenter, comment_reporters, user_id, perms):
732 """Returns true if the user can flag the given comment.
733
734 UpdateIssuePermissions must have been called first.
735 Assumes that the user has permission to view the issue.
736
737 Args:
738 comment: An IssueComment PB object.
739 commenter: An User PB object with the user who created the comment.
740 comment_reporters: A collection of user IDs who flagged the comment as spam.
741 user_id: The ID of the user for whom we're checking permissions.
742 perms: The PermissionSet with the issue permissions.
743
744 Returns:
745 A tuple (can_flag, is_flagged).
746 can_flag is True if the user can flag the comment. and is_flagged is True
747 if the user sees the comment marked as spam.
748 """
749 # Nobody can flag comments by banned users.
750 if commenter.banned:
751 return False, comment.is_spam
752
753 # If a comment was deleted for a reason other than being spam, nobody can
754 # flag or un-flag it.
755 if comment.deleted_by and not comment.is_spam:
756 return False, comment.is_spam
757
758 # A user with the VerdictSpam permission sees whether the comment is flagged
759 # as spam or not, and can mark it as flagged or un-flagged.
760 # If the comment is flagged as spam, all users see it as flagged, but only
761 # those with the VerdictSpam can un-flag it.
762 permit_verdict_spam = perms.HasPerm(VERDICT_SPAM, None, None, [])
763 if permit_verdict_spam or comment.is_spam:
764 return permit_verdict_spam, comment.is_spam
765
766 # Otherwise, the comment is not marked as flagged and the user doesn't have
767 # the VerdictSpam permission.
768 # They are able to report a comment as spam if they have the FlagSpam
769 # permission, and they see the comment as flagged if the have previously
770 # reported it as spam.
771 permit_flag_spam = perms.HasPerm(FLAG_SPAM, None, None, [])
772 return permit_flag_spam, user_id in comment_reporters
773
774
775def CanViewComment(comment, commenter, user_id, perms):
776 """Returns true if the user can view the given comment.
777
778 UpdateIssuePermissions must have been called first.
779 Assumes that the user has permission to view the issue.
780
781 Args:
782 comment: An IssueComment PB object.
783 commenter: An User PB object with the user who created the comment.
784 user_id: The ID of the user whose permission we want to check.
785 perms: The PermissionSet with the issue permissions.
786
787 Returns:
788 True if the user can view the comment.
789 """
790 # Nobody can view comments by banned users.
791 if commenter.banned:
792 return False
793
794 # Only users with the permission to un-flag comments can view flagged
795 # comments.
796 if comment.is_spam:
797 # If the comment is marked as spam, whether the user can un-flag the comment
798 # or not doesn't depend on who reported it as spam.
799 can_flag, _ = CanFlagComment(comment, commenter, [], user_id, perms)
800 return can_flag
801
802 # Only users with the permission to un-delete comments can view deleted
803 # comments.
804 if comment.deleted_by:
805 return CanDeleteComment(comment, commenter, user_id, perms)
806
807 return True
808
809
810def CanViewInboundMessage(comment, user_id, perms):
811 """Returns true if the user can view the given comment's inbound message.
812
813 UpdateIssuePermissions must have been called first.
814 Assumes that the user has permission to view the comment.
815
816 Args:
817 comment: An IssueComment PB object.
818 commenter: An User PB object with the user who created the comment.
819 user_id: The ID of the user whose permission we want to check.
820 perms: The PermissionSet with the issue permissions.
821
822 Returns:
823 True if the user can view the comment's inbound message.
824 """
825 return (perms.HasPerm(VIEW_INBOUND_MESSAGES, None, None, [])
826 or comment.user_id == user_id)
827
828
829def CanView(effective_ids, perms, project, restrictions, granted_perms=None):
830 """Checks if user has permission to view an issue."""
831 return perms.CanUsePerm(
832 VIEW, effective_ids, project, restrictions, granted_perms=granted_perms)
833
834
835def CanCreateProject(perms):
836 """Return True if the given user may create a project.
837
838 Args:
839 perms: Permissionset for the current user.
840
841 Returns:
842 True if the user should be allowed to create a project.
843 """
844 # "ANYONE" means anyone who has the needed perm.
845 if (settings.project_creation_restriction ==
846 site_pb2.UserTypeRestriction.ANYONE):
847 return perms.HasPerm(CREATE_PROJECT, None, None)
848
849 if (settings.project_creation_restriction ==
850 site_pb2.UserTypeRestriction.ADMIN_ONLY):
851 return perms.HasPerm(ADMINISTER_SITE, None, None)
852
853 return False
854
855
856def CanCreateGroup(perms):
857 """Return True if the given user may create a user group.
858
859 Args:
860 perms: Permissionset for the current user.
861
862 Returns:
863 True if the user should be allowed to create a group.
864 """
865 # "ANYONE" means anyone who has the needed perm.
866 if (settings.group_creation_restriction ==
867 site_pb2.UserTypeRestriction.ANYONE):
868 return perms.HasPerm(CREATE_GROUP, None, None)
869
870 if (settings.group_creation_restriction ==
871 site_pb2.UserTypeRestriction.ADMIN_ONLY):
872 return perms.HasPerm(ADMINISTER_SITE, None, None)
873
874 return False
875
876
877def CanEditGroup(perms, effective_ids, group_owner_ids):
878 """Return True if the given user may edit a user group.
879
880 Args:
881 perms: Permissionset for the current user.
882 effective_ids: set of user IDs for the logged in user.
883 group_owner_ids: set of user IDs of the user group owners.
884
885 Returns:
886 True if the user should be allowed to edit the group.
887 """
888 return (perms.HasPerm(EDIT_GROUP, None, None) or
889 not effective_ids.isdisjoint(group_owner_ids))
890
891
892def CanViewGroupMembers(perms, effective_ids, group_settings, member_ids,
893 owner_ids, user_project_ids):
894 """Return True if the given user may view a user group's members.
895
896 Args:
897 perms: Permissionset for the current user.
898 effective_ids: set of user IDs for the logged in user.
899 group_settings: PB of UserGroupSettings.
900 member_ids: A list of member ids of this user group.
901 owner_ids: A list of owner ids of this user group.
902 user_project_ids: A list of project ids which the user has a role.
903
904 Returns:
905 True if the user should be allowed to view the group's members.
906 """
907 if perms.HasPerm(VIEW_GROUP, None, None):
908 return True
909 # The user could view this group with membership of some projects which are
910 # friends of the group.
911 if (group_settings.friend_projects and user_project_ids
912 and (set(group_settings.friend_projects) & set(user_project_ids))):
913 return True
914 visibility = group_settings.who_can_view_members
915 if visibility == usergroup_pb2.MemberVisibility.OWNERS:
916 return not effective_ids.isdisjoint(owner_ids)
917 elif visibility == usergroup_pb2.MemberVisibility.MEMBERS:
918 return (not effective_ids.isdisjoint(member_ids) or
919 not effective_ids.isdisjoint(owner_ids))
920 else:
921 return True
922
923
924def IsBanned(user, user_view):
925 """Return True if this user is banned from using our site."""
926 if user is None:
927 return False # Anyone is welcome to browse
928
929 if user.banned:
930 return True # We checked the "Banned" checkbox for this user.
931
932 if user_view:
933 if user_view.domain in settings.banned_user_domains:
934 return True # Some spammers create many accounts with the same domain.
935
936 if '+' in (user.email or ''):
937 # Spammers can make plus-addr Google accounts in unexpected domains.
938 return True
939
940 return False
941
942
943def CanBan(mr, services):
944 """Return True if the user is allowed to ban other users, site-wide."""
945 if mr.perms.HasPerm(ADMINISTER_SITE, None, None):
946 return True
947
948 owned, _, _ = services.project.GetUserRolesInAllProjects(mr.cnxn,
949 mr.auth.effective_ids)
950 return len(owned) > 0
951
952
953def CanExpungeUsers(mr):
954 """Return True is the user is allowed to delete user accounts."""
955 return mr.perms.HasPerm(ADMINISTER_SITE, None, None)
956
957
958def CanViewContributorList(mr, project):
959 """Return True if we should display the list project contributors.
960
961 This is used on the project summary page, when deciding to offer the
962 project People page link, and when generating autocomplete options
963 that include project members.
964
965 Args:
966 mr: commonly used info parsed from the request.
967 project: the Project we're interested in.
968
969 Returns:
970 True if we should display the project contributor list.
971 """
972 if not project:
973 return False # We are not even in a project context.
974
975 if not project.only_owners_see_contributors:
976 return True # Contributor list is not resticted.
977
978 # If it is hub-and-spoke, check for the perm that allows the user to
979 # view it anyway.
980 return mr.perms.HasPerm(
981 VIEW_CONTRIBUTOR_LIST, mr.auth.user_id, project)
982
983
984def ShouldCheckForAbandonment(mr):
985 """Return True if user should be warned before changing/deleting their role.
986
987 Args:
988 mr: common info parsed from the user's request.
989
990 Returns:
991 True if user should be warned before changing/deleting their role.
992 """
993 # Note: No need to warn admins because they won't lose access anyway.
994 if mr.perms.CanUsePerm(
995 ADMINISTER_SITE, mr.auth.effective_ids, mr.project, []):
996 return False
997
998 return mr.perms.CanUsePerm(
999 EDIT_PROJECT, mr.auth.effective_ids, mr.project, [])
1000
1001
1002# For speed, we remember labels that we have already classified as being
1003# restriction labels or not being restriction labels. These sets are for
1004# restrictions in general, not for any particular perm.
1005_KNOWN_RESTRICTION_LABELS = set()
1006_KNOWN_NON_RESTRICTION_LABELS = set()
1007
1008
1009def IsRestrictLabel(label, perm=''):
1010 """Returns True if a given label is a restriction label.
1011
1012 Args:
1013 label: string for the label to examine.
1014 perm: a permission that can be restricted (e.g. 'View' or 'Edit').
1015 Defaults to '' to mean 'any'.
1016
1017 Returns:
1018 True if a given label is a restriction label (of the specified perm)
1019 """
1020 if label in _KNOWN_NON_RESTRICTION_LABELS:
1021 return False
1022 if not perm and label in _KNOWN_RESTRICTION_LABELS:
1023 return True
1024
1025 prefix = ('restrict-%s-' % perm.lower()) if perm else 'restrict-'
1026 is_restrict = label.lower().startswith(prefix) and label.count('-') >= 2
1027
1028 if is_restrict:
1029 _KNOWN_RESTRICTION_LABELS.add(label)
1030 elif not perm:
1031 _KNOWN_NON_RESTRICTION_LABELS.add(label)
1032
1033 return is_restrict
1034
1035
1036def HasRestrictions(issue, perm=''):
1037 """Return True if the issue has any restrictions (on the specified perm)."""
1038 return (
1039 any(IsRestrictLabel(lab, perm=perm) for lab in issue.labels) or
1040 any(IsRestrictLabel(lab, perm=perm) for lab in issue.derived_labels))
1041
1042
1043def GetRestrictions(issue, perm=''):
1044 """Return a list of restriction labels on the given issue."""
1045 if not issue:
1046 return []
1047
1048 return [lab.lower() for lab in tracker_bizobj.GetLabels(issue)
1049 if IsRestrictLabel(lab, perm=perm)]
1050
1051
1052def CanViewIssue(
1053 effective_ids, perms, project, issue, allow_viewing_deleted=False,
1054 granted_perms=None):
1055 """Checks if user has permission to view an artifact.
1056
1057 Args:
1058 effective_ids: set of user IDs for the logged in user and any user
1059 group memberships. Should be an empty set for anon users.
1060 perms: PermissionSet for the user.
1061 project: Project PB for the project that contains this issue.
1062 issue: Issue PB for the issue being viewed.
1063 allow_viewing_deleted: True if the user should be allowed to view
1064 deleted artifacts.
1065 granted_perms: optional list of strings of permissions that the user is
1066 granted only within the scope of one issue, e.g., by being named in
1067 a user-type custom field that grants permissions.
1068
1069 Returns:
1070 True iff the user can view the specified issue.
1071 """
1072 if issue.deleted and not allow_viewing_deleted:
1073 return False
1074
1075 perms = UpdateIssuePermissions(
1076 perms, project, issue, effective_ids, granted_perms=granted_perms)
1077 return perms.HasPerm(VIEW, None, None)
1078
1079
1080def CanEditIssue(effective_ids, perms, project, issue, granted_perms=None):
1081 """Return True if a user can edit an issue.
1082
1083 Args:
1084 effective_ids: set of user IDs for the logged in user and any user
1085 group memberships. Should be an empty set for anon users.
1086 perms: PermissionSet for the user.
1087 project: Project PB for the project that contains this issue.
1088 issue: Issue PB for the issue being viewed.
1089 granted_perms: optional list of strings of permissions that the user is
1090 granted only within the scope of one issue, e.g., by being named in
1091 a user-type custom field that grants permissions.
1092
1093 Returns:
1094 True iff the user can edit the specified issue.
1095 """
1096 perms = UpdateIssuePermissions(
1097 perms, project, issue, effective_ids, granted_perms=granted_perms)
1098 return perms.HasPerm(EDIT_ISSUE, None, None)
1099
1100
1101def CanCommentIssue(effective_ids, perms, project, issue, granted_perms=None):
1102 """Return True if a user can comment on an issue."""
1103
1104 return perms.CanUsePerm(
1105 ADD_ISSUE_COMMENT, effective_ids, project,
1106 GetRestrictions(issue), granted_perms=granted_perms)
1107
1108
1109def CanUpdateApprovalStatus(
1110 effective_ids, perms, project, approver_ids, new_status):
1111 """Return True if a user can change the approval status to the new status."""
1112 if not effective_ids.isdisjoint(approver_ids):
1113 return True # Approval approvers can always change the approval status
1114
1115 if new_status not in RESTRICTED_APPROVAL_STATUSES:
1116 return True
1117
1118 return perms.CanUsePerm(EDIT_ISSUE_APPROVAL, effective_ids, project, [])
1119
1120
1121def CanUpdateApprovers(effective_ids, perms, project, current_approver_ids):
1122 """Return True if a user can edit the list of approvers for an approval."""
1123 if not effective_ids.isdisjoint(current_approver_ids):
1124 return True
1125
1126 return perms.CanUsePerm(EDIT_ISSUE_APPROVAL, effective_ids, project, [])
1127
1128
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001129def CanEditProjectConfig(mr, services):
1130 """ Special function to check if a user can edit a project config.
1131
1132 This function accounts for special edge cases pertaining only to project
1133 configuration editing permissions, such as checking if a project is frozen
1134 for config edits or if a user is in the allowlist of users who can override
1135 a config freeze.
1136
1137 Args:
1138 mr: MonorailRequest object.
1139 services: reference to database layer.
1140
1141 Returns:
1142 True if the user can edit the project.
1143 """
1144 if mr.project.project_id not in settings.config_freeze_project_ids:
1145 return mr.perms.CanUsePerm(
1146 EDIT_PROJECT, mr.auth.effective_ids, mr.project, [])
1147
1148 effective_users = services.user.GetUsersByIDs(
1149 mr.cnxn, list(mr.auth.effective_ids))
1150
1151 for _, user in effective_users.items():
1152 if user.email in settings.config_freeze_override_users.get(
1153 mr.project.project_id, {}):
1154 return True
1155
1156 return False
1157
1158
Copybara854996b2021-09-07 19:36:02 +00001159def CanViewComponentDef(effective_ids, perms, project, component_def):
1160 """Return True if a user can view the given component definition."""
1161 if not effective_ids.isdisjoint(component_def.admin_ids):
1162 return True # Component admins can view that component.
1163
1164 # TODO(jrobbins): check restrictions on the component definition.
1165 return perms.CanUsePerm(VIEW, effective_ids, project, [])
1166
1167
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001168def CanEditComponentDef(mr, services, component_def, config):
1169 """ Checks if the currently logged in user can edit a component.
1170
1171 Args:
1172 mr: MonorailRequest object.
1173 services: reference to database layer.
1174 component_def: the component to check permissions for.
1175 config: project config of the project the component is in.
1176
1177 Returns:
1178 True if a user can edit the given component definition."""
1179 if mr.project.project_id in settings.config_freeze_project_ids:
1180 return CanEditProjectConfig(mr, services)
1181
1182 if not mr.auth.effective_ids.isdisjoint(component_def.admin_ids):
1183 return True # Component admins can edit that component.
1184
1185 # Check to see if user is admin of any parent component.
1186 parent_components = tracker_bizobj.FindAncestorComponents(
1187 config, component_def)
1188 for parent in parent_components:
1189 if not mr.auth.effective_ids.isdisjoint(parent.admin_ids):
1190 return True
1191
1192 return CanEditProjectConfig(mr, services)
1193
1194
1195def CanEditComponentDefLegacy(
1196 effective_ids, perms, project, component_def, config):
1197 """ Legacy version of CanEditComponentDef for codepaths without access to mr.
1198 This function is entirely used in API clients.
1199
1200 Args:
1201 effective_ids: Set containing IDs for the user and their groups
1202 linked accounts, etc.
1203 perms: PermissionSet for current user.
1204 project: the project the component is in.
1205 component_def: the component to check permissions for.
1206 config: project config of the project the component is in.
1207
1208 Returns:
1209 True if a user can edit the given component definition."""
1210 # Do not bother checking if API client users are allowlisted to override
1211 # the config freeze. Only human users are currently being allowlisted.
1212 if project and project.project_id in settings.config_freeze_project_ids:
1213 return False
1214
Copybara854996b2021-09-07 19:36:02 +00001215 if not effective_ids.isdisjoint(component_def.admin_ids):
1216 return True # Component admins can edit that component.
1217
1218 # Check to see if user is admin of any parent component.
1219 parent_components = tracker_bizobj.FindAncestorComponents(
1220 config, component_def)
1221 for parent in parent_components:
1222 if not effective_ids.isdisjoint(parent.admin_ids):
1223 return True
1224
1225 return perms.CanUsePerm(EDIT_PROJECT, effective_ids, project, [])
1226
1227
1228def CanViewFieldDef(effective_ids, perms, project, field_def):
1229 """Return True if a user can view the given field definition."""
1230 if not effective_ids.isdisjoint(field_def.admin_ids):
1231 return True # Field admins can view that field.
1232
1233 # TODO(jrobbins): check restrictions on the field definition.
1234 return perms.CanUsePerm(VIEW, effective_ids, project, [])
1235
1236
1237def CanEditFieldDef(effective_ids, perms, project, field_def):
1238 """Return True if a user can edit the given field definition."""
1239 if not effective_ids.isdisjoint(field_def.admin_ids):
1240 return True # Field admins can edit that field.
1241
1242 return perms.CanUsePerm(EDIT_PROJECT, effective_ids, project, [])
1243
1244
1245def CanEditValueForFieldDef(effective_ids, perms, project, field_def):
1246 """Return True if a user can edit the given field definition value.
1247 This method does not check that a user can edit the project issues."""
1248 if not effective_ids:
1249 return False
1250 if not field_def.is_restricted_field:
1251 return True
1252 if not effective_ids.isdisjoint(field_def.editor_ids):
1253 return True
1254 return CanEditFieldDef(effective_ids, perms, project, field_def)
1255
1256
1257def CanViewTemplate(effective_ids, perms, project, template):
1258 """Return True if a user can view the given issue template."""
1259 if not effective_ids.isdisjoint(template.admin_ids):
1260 return True # template admins can view that template.
1261
1262 # Members-only templates are only shown to members, other templates are
1263 # shown to any user that is generally allowed to view project content.
1264 if template.members_only:
1265 return framework_bizobj.UserIsInProject(project, effective_ids)
1266 else:
1267 return perms.CanUsePerm(VIEW, effective_ids, project, [])
1268
1269
1270def CanEditTemplate(effective_ids, perms, project, template):
1271 """Return True if a user can edit the given field definition."""
1272 if not effective_ids.isdisjoint(template.admin_ids):
1273 return True # Template admins can edit that template.
1274
1275 return perms.CanUsePerm(EDIT_PROJECT, effective_ids, project, [])
1276
1277
1278def CanViewHotlist(effective_ids, perms, hotlist):
1279 """Return True if a user can view the given hotlist."""
1280 if not hotlist.is_private or perms.HasPerm(ADMINISTER_SITE, None, None):
1281 return True
1282
1283 return any([user_id in (hotlist.owner_ids + hotlist.editor_ids)
1284 for user_id in effective_ids])
1285
1286
1287def CanEditHotlist(effective_ids, perms, hotlist):
1288 """Return True if a user is editor(add/remove issues and change rankings)."""
1289 return perms.HasPerm(ADMINISTER_SITE, None, None) or any(
1290 [user_id in (hotlist.owner_ids + hotlist.editor_ids)
1291 for user_id in effective_ids])
1292
1293
1294def CanAdministerHotlist(effective_ids, perms, hotlist):
1295 """Return True if user is owner(add/remove members, edit/delete hotlist)."""
1296 return perms.HasPerm(ADMINISTER_SITE, None, None) or any(
1297 [user_id in hotlist.owner_ids for user_id in effective_ids])
1298
1299
1300def CanCreateHotlist(perms):
1301 """Return True if the given user may create a hotlist.
1302
1303 Args:
1304 perms: Permissionset for the current user.
1305
1306 Returns:
1307 True if the user should be allowed to create a hotlist.
1308 """
1309 if (settings.hotlist_creation_restriction ==
1310 site_pb2.UserTypeRestriction.ANYONE):
1311 return perms.HasPerm(CREATE_HOTLIST, None, None)
1312
1313 if (settings.hotlist_creation_restriction ==
1314 site_pb2.UserTypeRestriction.ADMIN_ONLY):
1315 return perms.HasPerm(ADMINISTER_SITE, None, None)
1316
1317
1318class Error(Exception):
1319 """Base class for errors from this module."""
1320
1321
1322class PermissionException(Error):
1323 """The user is not authorized to make the current request."""
1324
1325
1326class BannedUserException(Error):
1327 """The user has been banned from using our service."""