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

Unified 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « appengine/monorail/framework/pbproxy_test_pb2.py ('k') | appengine/monorail/framework/profiler.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: appengine/monorail/framework/permissions.py
diff --git a/appengine/monorail/framework/permissions.py b/appengine/monorail/framework/permissions.py
new file mode 100644
index 0000000000000000000000000000000000000000..e5b8404be8364b652acd4d2eead146469f96aba7
--- /dev/null
+++ b/appengine/monorail/framework/permissions.py
@@ -0,0 +1,959 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is govered by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+"""Classes and functions to implement permission checking.
+
+The main data structure is a simple map from (user role, project status,
+project_access_level) to specific perms.
+
+A perm is simply a string that indicates that the user has a given
+permission. The servlets and templates can test whether the current
+user has permission to see a UI element or perform an action by
+testing for the presence of the corresponding perm in the user's
+permission set.
+
+The user role is one of admin, owner, member, outsider user, or anon.
+The project status is one of the project states defined in project_pb2,
+or a special constant defined below. Likewise for access level.
+"""
+
+import logging
+import time
+
+from third_party import ezt
+
+import settings
+from framework import framework_bizobj
+from framework import framework_constants
+from proto import project_pb2
+from proto import site_pb2
+from proto import usergroup_pb2
+from tracker import tracker_bizobj
+
+# Constants that define permissions.
+# Note that perms with a leading "_" can never be granted
+# to users who are not site admins.
+VIEW = 'View'
+EDIT_PROJECT = 'EditProject'
+CREATE_PROJECT = 'CreateProject'
+PUBLISH_PROJECT = '_PublishProject' # for making "doomed" projects LIVE
+VIEW_DEBUG = '_ViewDebug' # on-page debugging info
+EDIT_OTHER_USERS = '_EditOtherUsers' # can edit other user's prefs, ban, etc.
+CUSTOMIZE_PROCESS = 'CustomizeProcess' # can use some enterprise features
+VIEW_EXPIRED_PROJECT = '_ViewExpiredProject' # view long-deleted projects
+# View the list of contributors even in hub-and-spoke projects.
+VIEW_CONTRIBUTOR_LIST = 'ViewContributorList'
+
+# Quota
+VIEW_QUOTA = 'ViewQuota'
+EDIT_QUOTA = 'EditQuota'
+
+# Permissions for editing user groups
+CREATE_GROUP = 'CreateGroup'
+EDIT_GROUP = 'EditGroup'
+DELETE_GROUP = 'DeleteGroup'
+VIEW_GROUP = 'ViewGroup'
+
+# Perms for Source tools
+# TODO(jrobbins): Monorail is just issue tracking with no version control, so
+# phase out use of the term "Commit", sometime after Monorail's initial launch.
+COMMIT = 'Commit'
+
+# Perms for issue tracking
+CREATE_ISSUE = 'CreateIssue'
+EDIT_ISSUE = 'EditIssue'
+EDIT_ISSUE_OWNER = 'EditIssueOwner'
+EDIT_ISSUE_SUMMARY = 'EditIssueSummary'
+EDIT_ISSUE_STATUS = 'EditIssueStatus'
+EDIT_ISSUE_CC = 'EditIssueCc'
+DELETE_ISSUE = 'DeleteIssue'
+ADD_ISSUE_COMMENT = 'AddIssueComment'
+VIEW_INBOUND_MESSAGES = 'ViewInboundMessages'
+# Note, there is no separate DELETE_ATTACHMENT perm. We
+# allow a user to delete an attachment iff they could soft-delete
+# the comment that holds the attachment.
+
+# Note: the "_" in the perm name makes it impossible for a
+# project owner to grant it to anyone as an extra perm.
+ADMINISTER_SITE = '_AdministerSite'
+
+# Permissions to soft-delete artifact comment
+DELETE_ANY = 'DeleteAny'
+DELETE_OWN = 'DeleteOwn'
+
+# Granting this allows owners to delegate some team management work.
+EDIT_ANY_MEMBER_NOTES = 'EditAnyMemberNotes'
+
+# Permission to star/unstar any artifact.
+SET_STAR = 'SetStar'
+
+# Permission to flag any artifact as spam.
+FLAG_SPAM = 'FlagSpam'
+VERDICT_SPAM = 'VerdictSpam'
+MODERATE_SPAM = 'ModerateSpam'
+
+STANDARD_ADMIN_PERMISSIONS = [
+ EDIT_PROJECT, CREATE_PROJECT, PUBLISH_PROJECT, VIEW_DEBUG,
+ EDIT_OTHER_USERS, CUSTOMIZE_PROCESS,
+ VIEW_QUOTA, EDIT_QUOTA, ADMINISTER_SITE,
+ EDIT_ANY_MEMBER_NOTES, VERDICT_SPAM, MODERATE_SPAM]
+
+STANDARD_ISSUE_PERMISSIONS = [
+ VIEW, EDIT_ISSUE, ADD_ISSUE_COMMENT, DELETE_ISSUE, FLAG_SPAM]
+
+# Monorail has no source control, but keep COMMIT for backward compatability.
+STANDARD_SOURCE_PERMISSIONS = [COMMIT]
+
+STANDARD_COMMENT_PERMISSIONS = [DELETE_OWN, DELETE_ANY]
+
+STANDARD_OTHER_PERMISSIONS = [CREATE_ISSUE, FLAG_SPAM, SET_STAR]
+
+STANDARD_PERMISSIONS = (STANDARD_ADMIN_PERMISSIONS +
+ STANDARD_ISSUE_PERMISSIONS +
+ STANDARD_SOURCE_PERMISSIONS +
+ STANDARD_COMMENT_PERMISSIONS +
+ STANDARD_OTHER_PERMISSIONS)
+
+# roles
+SITE_ADMIN_ROLE = 'admin'
+OWNER_ROLE = 'owner'
+COMMITTER_ROLE = 'committer'
+CONTRIBUTOR_ROLE = 'contributor'
+USER_ROLE = 'user'
+ANON_ROLE = 'anon'
+
+# Project state out-of-band values for keys
+UNDEFINED_STATUS = 'undefined_status'
+UNDEFINED_ACCESS = 'undefined_access'
+WILDCARD_ACCESS = 'wildcard_access'
+
+
+class PermissionSet(object):
+ """Class to represent the set of permissions available to the user."""
+
+ def __init__(self, perm_names, consider_restrictions=True):
+ """Create a PermissionSet with the given permissions.
+
+ Args:
+ perm_names: a list of permission name strings.
+ consider_restrictions: if true, the user's permissions can be blocked
+ by restriction labels on an artifact. Project owners and site
+ admins do not consider restrictions so that they cannot
+ "lock themselves out" of editing an issue.
+ """
+ self.perm_names = frozenset(p.lower() for p in perm_names)
+ self.consider_restrictions = consider_restrictions
+
+ def __getattr__(self, perm_name):
+ """Easy permission testing in EZT. E.g., [if-any perms.format_drive]."""
+ return ezt.boolean(self.HasPerm(perm_name, None, None))
+
+ def CanUsePerm(
+ self, perm_name, effective_ids, project, restriction_labels,
+ granted_perms=None):
+ """Return True if the user can use the given permission.
+
+ Args:
+ perm_name: string name of permission, e.g., 'EditIssue'.
+ effective_ids: set of int user IDs for the user (including any groups),
+ or an empty set if user is not signed in.
+ project: Project PB for the project being accessed, or None if not
+ in a project.
+ restriction_labels: list of strings that restrict permission usage.
+ granted_perms: optional list of lowercase strings of permissions that the
+ user is granted only within the scope of one issue, e.g., by being
+ named in a user-type custom field that grants permissions.
+
+ Restriction labels have 3 parts, e.g.:
+ 'Restrict-EditIssue-InnerCircle' blocks the use of just the
+ EditIssue permission, unless the user also has the InnerCircle
+ permission. This allows fine-grained restrictions on specific
+ actions, such as editing, commenting, or deleting.
+
+ Restriction labels and permissions are case-insensitive.
+
+ Returns:
+ True if the user can use the given permission, or False
+ if they cannot (either because they don't have that permission
+ or because it is blocked by a relevant restriction label).
+ """
+ # TODO(jrobbins): room for performance improvement: avoid set creation and
+ # repeated string operations.
+ granted_perms = granted_perms or set()
+ perm_lower = perm_name.lower()
+ if perm_lower in granted_perms:
+ return True
+
+ needed_perms = {perm_lower}
+ if self.consider_restrictions:
+ for label in restriction_labels:
+ label = label.lower()
+ # format: Restrict-Action-ToThisPerm
+ _kw, requested_perm, needed_perm = label.split('-', 2)
+ if requested_perm == perm_lower and needed_perm not in granted_perms:
+ needed_perms.add(needed_perm)
+
+ if not effective_ids:
+ effective_ids = {framework_constants.NO_USER_SPECIFIED}
+ # Id X might have perm A and Y might have B, if both A and B are needed
+ # True should be returned.
+ for perm in needed_perms:
+ if not any(
+ self.HasPerm(perm, user_id, project) for user_id in effective_ids):
+ return False
+
+ return True
+
+ def HasPerm(self, perm_name, user_id, project):
+ """Return True if the user has the given permission (ignoring user groups).
+
+ Args:
+ perm_name: string name of permission, e.g., 'EditIssue'.
+ user_id: int user id of the user, or None if user is not signed in.
+ project: Project PB for the project being accessed, or None if not
+ in a project.
+
+ Returns:
+ True if the user has the given perm.
+ """
+ # TODO(jrobbins): room for performance improvement: pre-compute
+ # extra perms (maybe merge them into the perms object), avoid
+ # redundant call to lower().
+ extra_perms = [p.lower() for p in GetExtraPerms(project, user_id)]
+ perm_name = perm_name.lower()
+ return perm_name in self.perm_names or perm_name in extra_perms
+
+ def DebugString(self):
+ """Return a useful string to show when debugging."""
+ return 'PermissionSet(%s)' % ', '.join(sorted(self.perm_names))
+
+ def __repr__(self):
+ return '%s(%r)' % (self.__class__.__name__, self.perm_names)
+
+
+EMPTY_PERMISSIONSET = PermissionSet([])
+
+READ_ONLY_PERMISSIONSET = PermissionSet([VIEW])
+
+USER_PERMISSIONSET = PermissionSet([
+ VIEW, FLAG_SPAM, SET_STAR,
+ CREATE_ISSUE, ADD_ISSUE_COMMENT,
+ DELETE_OWN])
+
+CONTRIBUTOR_ACTIVE_PERMISSIONSET = PermissionSet(
+ [VIEW,
+ FLAG_SPAM, SET_STAR,
+ CREATE_ISSUE, ADD_ISSUE_COMMENT,
+ DELETE_OWN])
+
+CONTRIBUTOR_INACTIVE_PERMISSIONSET = PermissionSet(
+ [VIEW])
+
+COMMITTER_ACTIVE_PERMISSIONSET = PermissionSet(
+ [VIEW, COMMIT, VIEW_CONTRIBUTOR_LIST,
+ FLAG_SPAM, SET_STAR, VIEW_QUOTA,
+ CREATE_ISSUE, ADD_ISSUE_COMMENT, EDIT_ISSUE, VIEW_INBOUND_MESSAGES,
+ DELETE_OWN])
+
+COMMITTER_INACTIVE_PERMISSIONSET = PermissionSet(
+ [VIEW, VIEW_CONTRIBUTOR_LIST,
+ VIEW_INBOUND_MESSAGES, VIEW_QUOTA])
+
+OWNER_ACTIVE_PERMISSIONSET = PermissionSet(
+ [VIEW, VIEW_CONTRIBUTOR_LIST, EDIT_PROJECT, COMMIT,
+ FLAG_SPAM, VERDICT_SPAM, SET_STAR, VIEW_QUOTA,
+ CREATE_ISSUE, ADD_ISSUE_COMMENT, EDIT_ISSUE, DELETE_ISSUE,
+ VIEW_INBOUND_MESSAGES,
+ DELETE_ANY, EDIT_ANY_MEMBER_NOTES],
+ consider_restrictions=False)
+
+OWNER_INACTIVE_PERMISSIONSET = PermissionSet(
+ [VIEW, VIEW_CONTRIBUTOR_LIST, EDIT_PROJECT,
+ VIEW_INBOUND_MESSAGES, VIEW_QUOTA],
+ consider_restrictions=False)
+
+ADMIN_PERMISSIONSET = PermissionSet(
+ [VIEW, VIEW_CONTRIBUTOR_LIST,
+ CREATE_PROJECT, EDIT_PROJECT, PUBLISH_PROJECT, VIEW_DEBUG,
+ COMMIT, CUSTOMIZE_PROCESS, FLAG_SPAM, VERDICT_SPAM, SET_STAR,
+ ADMINISTER_SITE, VIEW_EXPIRED_PROJECT, EDIT_OTHER_USERS,
+ VIEW_QUOTA, EDIT_QUOTA,
+ CREATE_ISSUE, ADD_ISSUE_COMMENT, EDIT_ISSUE, DELETE_ISSUE,
+ VIEW_INBOUND_MESSAGES,
+ DELETE_ANY, EDIT_ANY_MEMBER_NOTES,
+ CREATE_GROUP, EDIT_GROUP, DELETE_GROUP, VIEW_GROUP,
+ MODERATE_SPAM],
+ consider_restrictions=False)
+
+GROUP_IMPORT_BORG_PERMISSIONSET = PermissionSet(
+ [CREATE_GROUP, VIEW_GROUP, EDIT_GROUP])
+
+
+# Permissions for project pages, e.g., the project summary page
+_PERMISSIONS_TABLE = {
+
+ # Project owners can view and edit artifacts in a LIVE project.
+ (OWNER_ROLE, project_pb2.ProjectState.LIVE, WILDCARD_ACCESS):
+ OWNER_ACTIVE_PERMISSIONSET,
+
+ # Project owners can view, but not edit artifacts in ARCHIVED.
+ # Note: EDIT_PROJECT is not enough permission to change an ARCHIVED project
+ # back to LIVE if a delete_time was set.
+ (OWNER_ROLE, project_pb2.ProjectState.ARCHIVED, WILDCARD_ACCESS):
+ OWNER_INACTIVE_PERMISSIONSET,
+
+ # Project members can view their own project, regardless of state.
+ (COMMITTER_ROLE, project_pb2.ProjectState.LIVE, WILDCARD_ACCESS):
+ COMMITTER_ACTIVE_PERMISSIONSET,
+ (COMMITTER_ROLE, project_pb2.ProjectState.ARCHIVED, WILDCARD_ACCESS):
+ COMMITTER_INACTIVE_PERMISSIONSET,
+
+ # Project contributors can view their own project, regardless of state.
+ (CONTRIBUTOR_ROLE, project_pb2.ProjectState.LIVE, WILDCARD_ACCESS):
+ CONTRIBUTOR_ACTIVE_PERMISSIONSET,
+ (CONTRIBUTOR_ROLE, project_pb2.ProjectState.ARCHIVED, WILDCARD_ACCESS):
+ CONTRIBUTOR_INACTIVE_PERMISSIONSET,
+
+ # Non-members users can read and comment in projects with access == ANYONE
+ (USER_ROLE, project_pb2.ProjectState.LIVE,
+ project_pb2.ProjectAccess.ANYONE):
+ USER_PERMISSIONSET,
+
+ # Anonymous users can only read projects with access == ANYONE.
+ (ANON_ROLE, project_pb2.ProjectState.LIVE,
+ project_pb2.ProjectAccess.ANYONE):
+ READ_ONLY_PERMISSIONSET,
+
+ # Permissions for site pages, e.g., creating a new project
+ (USER_ROLE, UNDEFINED_STATUS, UNDEFINED_ACCESS):
+ PermissionSet([CREATE_PROJECT, CREATE_GROUP]),
+ }
+
+
+def GetPermissions(user, effective_ids, project):
+ """Return a permission set appropriate for the user and project.
+
+ Args:
+ user: The User PB for the signed-in user, or None for anon users.
+ effective_ids: set of int user IDs for the current user and all user
+ groups that s/he is a member of. This will be an empty set for
+ anonymous users.
+ project: either a Project protobuf, or None for a page whose scope is
+ wider than a single project.
+
+ Returns:
+ a PermissionSet object for the current user and project (or for
+ site-wide operations if project is None).
+
+ If an exact match for the user's role and project status is found, that is
+ returned. Otherwise, we look for permissions for the user's role that is
+ not specific to any project status, or not specific to any project access
+ level. If neither of those are defined, we give the user an empty
+ permission set.
+ """
+ # Site admins get ADMIN_PERMISSIONSET regardless of groups or projects.
+ if user and user.is_site_admin:
+ return ADMIN_PERMISSIONSET
+
+ # Grant the borg job permission to view/edit groups
+ if user and user.email == settings.borg_service_account:
+ return GROUP_IMPORT_BORG_PERMISSIONSET
+
+ # Anon users don't need to accumulate anything.
+ if not effective_ids:
+ role, status, access = _GetPermissionKey(None, project)
+ return _LookupPermset(role, status, access)
+
+ effective_perms = set()
+ consider_restrictions = True
+
+ # Check for signed-in user with no roles in the current project.
+ if not project or not framework_bizobj.UserIsInProject(
+ project, effective_ids):
+ role, status, access = _GetPermissionKey(None, project)
+ return _LookupPermset(USER_ROLE, status, access)
+
+ # Signed-in user gets the union of all his/her PermissionSets from the table.
+ for user_id in effective_ids:
+ role, status, access = _GetPermissionKey(user_id, project)
+ role_perms = _LookupPermset(role, status, access)
+ # Accumulate a union of all the user's permissions.
+ effective_perms.update(role_perms.perm_names)
+ # If any role allows the user to ignore restriction labels, then
+ # ignore them overall.
+ if not role_perms.consider_restrictions:
+ consider_restrictions = False
+
+ return PermissionSet(
+ effective_perms, consider_restrictions=consider_restrictions)
+
+
+def _LookupPermset(role, status, access):
+ """Lookup the appropriate PermissionSet in _PERMISSIONS_TABLE.
+
+ Args:
+ role: a string indicating the user's role in the project.
+ status: a Project PB status value, or UNDEFINED_STATUS.
+ access: a Project PB access value, or UNDEFINED_ACCESS.
+
+ Returns:
+ A PermissionSet that is appropriate for that kind of user in that
+ project context.
+ """
+ if (role, status, access) in _PERMISSIONS_TABLE:
+ return _PERMISSIONS_TABLE[(role, status, access)]
+ elif (role, status, WILDCARD_ACCESS) in _PERMISSIONS_TABLE:
+ return _PERMISSIONS_TABLE[(role, status, WILDCARD_ACCESS)]
+ else:
+ return EMPTY_PERMISSIONSET
+
+
+def _GetPermissionKey(user_id, project, expired_before=None):
+ """Return a permission lookup key appropriate for the user and project."""
+ if user_id is None:
+ role = ANON_ROLE
+ elif project and IsExpired(project, expired_before=expired_before):
+ role = USER_ROLE # Do not honor roles in expired projects.
+ elif project and user_id in project.owner_ids:
+ role = OWNER_ROLE
+ elif project and user_id in project.committer_ids:
+ role = COMMITTER_ROLE
+ elif project and user_id in project.contributor_ids:
+ role = CONTRIBUTOR_ROLE
+ else:
+ role = USER_ROLE
+
+ # TODO(jrobbins): re-implement same_org
+
+ if project is None:
+ status = UNDEFINED_STATUS
+ else:
+ status = project.state
+
+ if project is None:
+ access = UNDEFINED_ACCESS
+ else:
+ access = project.access
+
+ return role, status, access
+
+
+def GetExtraPerms(project, member_id):
+ """Return a list of extra perms for the user in the project.
+
+ Args:
+ project: Project PB for the current project.
+ member_id: user id of a project owner, member, or contributor.
+
+ Returns:
+ A list of strings for the extra perms granted to the
+ specified user in this project. The list will often be empty.
+ """
+
+ extra_perms = FindExtraPerms(project, member_id)
+
+ if extra_perms:
+ return list(extra_perms.perms)
+ else:
+ return []
+
+
+def FindExtraPerms(project, member_id):
+ """Return a ExtraPerms PB for the given user in the project.
+
+ Args:
+ project: Project PB for the current project, or None if the user is
+ not currently in a project.
+ member_id: user ID of a project owner, member, or contributor.
+
+ Returns:
+ An ExtraPerms PB, or None.
+ """
+ if not project:
+ # TODO(jrobbins): maybe define extra perms for site-wide operations.
+ return None
+
+ # Users who have no current role cannot have any extra perms. Don't
+ # consider effective_ids (which includes user groups) for this check.
+ if not framework_bizobj.UserIsInProject(project, {member_id}):
+ return None
+
+ for extra_perms in project.extra_perms:
+ if extra_perms.member_id == member_id:
+ return extra_perms
+
+ return None
+
+
+def GetCustomPermissions(project):
+ """Return a sorted iterable of custom perms granted in a project."""
+ custom_permissions = set()
+ for extra_perms in project.extra_perms:
+ for perm in extra_perms.perms:
+ if perm not in STANDARD_PERMISSIONS:
+ custom_permissions.add(perm)
+
+ return sorted(custom_permissions)
+
+
+def UserCanViewProject(user, effective_ids, project, expired_before=None):
+ """Return True if the user can view the given project.
+
+ Args:
+ user: User protobuf for the user trying to view the project.
+ effective_ids: set of int user IDs of the user trying to view the project
+ (including any groups), or an empty set for anonymous users.
+ project: the Project protobuf to check.
+ expired_before: option time value for testing.
+
+ Returns:
+ True if the user should be allowed to view the project.
+ """
+ perms = GetPermissions(user, effective_ids, project)
+
+ if IsExpired(project, expired_before=expired_before):
+ needed_perm = VIEW_EXPIRED_PROJECT
+ else:
+ needed_perm = VIEW
+
+ return perms.CanUsePerm(needed_perm, effective_ids, project, [])
+
+
+def IsExpired(project, expired_before=None):
+ """Return True if a project deletion has been pending long enough already.
+
+ Args:
+ project: The project being viewed.
+ expired_before: If supplied, this method will return True only if the
+ project expired before the given time.
+
+ Returns:
+ True if the project is eligible for reaping.
+ """
+ if project.state != project_pb2.ProjectState.ARCHIVED:
+ return False
+
+ if expired_before is None:
+ expired_before = int(time.time())
+
+ return project.delete_time and project.delete_time < expired_before
+
+
+def CanDelete(logged_in_user_id, effective_ids, perms, deleted_by_user_id,
+ creator_user_id, project, restrictions, granted_perms=None):
+ """Returns true if user has delete permission.
+
+ Args:
+ logged_in_user_id: int user id of the logged in user.
+ effective_ids: set of int user IDs for the user (including any groups),
+ or an empty set if user is not signed in.
+ perms: instance of PermissionSet describing the current user's permissions.
+ deleted_by_user_id: int user ID of the user having previously deleted this
+ comment, or None, if the comment has never been deleted.
+ creator_user_id: int user ID of the user having created this comment.
+ project: Project PB for the project being accessed, or None if not
+ in a project.
+ restrictions: list of strings that restrict permission usage.
+ granted_perms: optional list of strings of permissions that the user is
+ granted only within the scope of one issue, e.g., by being named in
+ a user-type custom field that grants permissions.
+
+ Returns:
+ True if the logged in user has delete permissions.
+ """
+
+ # User is not logged in or has no permissions.
+ if not logged_in_user_id or not perms:
+ return False
+
+ # Site admin or project owners can delete any comment.
+ permit_delete_any = perms.CanUsePerm(
+ DELETE_ANY, effective_ids, project, restrictions,
+ granted_perms=granted_perms)
+ if permit_delete_any:
+ return True
+
+ # Users cannot undelete unless they deleted.
+ if deleted_by_user_id and deleted_by_user_id != logged_in_user_id:
+ return False
+
+ # Users can delete their own items.
+ permit_delete_own = perms.CanUsePerm(
+ DELETE_OWN, effective_ids, project, restrictions)
+ if permit_delete_own and creator_user_id == logged_in_user_id:
+ return True
+
+ return False
+
+
+def CanView(effective_ids, perms, project, restrictions, granted_perms=None):
+ """Checks if user has permission to view an issue."""
+ return perms.CanUsePerm(
+ VIEW, effective_ids, project, restrictions, granted_perms=granted_perms)
+
+
+def CanCreateProject(perms):
+ """Return True if the given user may create a project.
+
+ Args:
+ perms: Permissionset for the current user.
+
+ Returns:
+ True if the user should be allowed to create a project.
+ """
+ # "ANYONE" means anyone who has the needed perm.
+ if (settings.project_creation_restriction ==
+ site_pb2.UserTypeRestriction.ANYONE):
+ return perms.HasPerm(CREATE_PROJECT, None, None)
+
+ if (settings.project_creation_restriction ==
+ site_pb2.UserTypeRestriction.ADMIN_ONLY):
+ return perms.HasPerm(ADMINISTER_SITE, None, None)
+
+ return False
+
+
+def CanCreateGroup(perms):
+ """Return True if the given user may create a user group.
+
+ Args:
+ perms: Permissionset for the current user.
+
+ Returns:
+ True if the user should be allowed to create a group.
+ """
+ # "ANYONE" means anyone who has the needed perm.
+ if (settings.group_creation_restriction ==
+ site_pb2.UserTypeRestriction.ANYONE):
+ return perms.HasPerm(CREATE_GROUP, None, None)
+
+ if (settings.group_creation_restriction ==
+ site_pb2.UserTypeRestriction.ADMIN_ONLY):
+ return perms.HasPerm(ADMINISTER_SITE, None, None)
+
+ return False
+
+
+def CanEditGroup(perms, effective_ids, group_owner_ids):
+ """Return True if the given user may edit a user group.
+
+ Args:
+ perms: Permissionset for the current user.
+ effective_ids: set of user IDs for the logged in user.
+ group_owner_ids: set of user IDs of the user group owners.
+
+ Returns:
+ True if the user should be allowed to edit the group.
+ """
+ return (perms.HasPerm(EDIT_GROUP, None, None) or
+ not effective_ids.isdisjoint(group_owner_ids))
+
+
+def CanViewGroup(perms, effective_ids, group_settings, member_ids, owner_ids,
+ user_project_ids):
+ """Return True if the given user may view a user group.
+
+ Args:
+ perms: Permissionset for the current user.
+ effective_ids: set of user IDs for the logged in user.
+ group_settings: PB of UserGroupSettings.
+ member_ids: A list of member ids of this user group.
+ owner_ids: A list of owner ids of this user group.
+ user_project_ids: A list of project ids which the user has a role.
+
+ Returns:
+ True if the user should be allowed to view the group.
+ """
+ if perms.HasPerm(VIEW_GROUP, None, None):
+ return True
+ # The user could view this group with membership of some projects which are
+ # friends of the group.
+ if (group_settings.friend_projects and user_project_ids
+ and (set(group_settings.friend_projects) & set(user_project_ids))):
+ return True
+ visibility = group_settings.who_can_view_members
+ if visibility == usergroup_pb2.MemberVisibility.OWNERS:
+ return not effective_ids.isdisjoint(owner_ids)
+ elif visibility == usergroup_pb2.MemberVisibility.MEMBERS:
+ return (not effective_ids.isdisjoint(member_ids) or
+ not effective_ids.isdisjoint(owner_ids))
+ else:
+ return True
+
+
+def IsBanned(user, user_view):
+ """Return True if this user is banned from using our site."""
+ if user is None:
+ return False # Anyone is welcome to browse
+
+ if user.banned:
+ return True # We checked the "Banned" checkbox for this user.
+
+ if user_view:
+ if user_view.domain in settings.banned_user_domains:
+ return True # Some spammers create many accounts with the same domain.
+
+ return False
+
+
+def CanViewContributorList(mr):
+ """Return True if we should display the list project contributors.
+
+ This is used on the project summary page, when deciding to offer the
+ project People page link, and when generating autocomplete options
+ that include project members.
+
+ Args:
+ mr: commonly used info parsed from the request.
+
+ Returns:
+ True if we should display the project contributor list.
+ """
+ if not mr.project:
+ return False # We are not even in a project context.
+
+ if not mr.project.only_owners_see_contributors:
+ return True # Contributor list is not resticted.
+
+ # If it is hub-and-spoke, check for the perm that allows the user to
+ # view it anyway.
+ return mr.perms.HasPerm(
+ VIEW_CONTRIBUTOR_LIST, mr.auth.user_id, mr.project)
+
+
+def ShouldCheckForAbandonment(mr):
+ """Return True if user should be warned before changing/deleting their role.
+
+ Args:
+ mr: common info parsed from the user's request.
+
+ Returns:
+ True if user should be warned before changing/deleting their role.
+ """
+ # Note: No need to warn admins because they won't lose access anyway.
+ if mr.perms.CanUsePerm(
+ ADMINISTER_SITE, mr.auth.effective_ids, mr.project, []):
+ return False
+
+ return mr.perms.CanUsePerm(
+ EDIT_PROJECT, mr.auth.effective_ids, mr.project, [])
+
+
+# For speed, we remember labels that we have already classified as being
+# restriction labels or not being restriction labels. These sets are for
+# restrictions in general, not for any particular perm.
+_KNOWN_RESTRICTION_LABELS = set()
+_KNOWN_NON_RESTRICTION_LABELS = set()
+
+
+def IsRestrictLabel(label, perm=''):
+ """Returns True if a given label is a restriction label.
+
+ Args:
+ label: string for the label to examine.
+ perm: a permission that can be restricted (e.g. 'View' or 'Edit').
+ Defaults to '' to mean 'any'.
+
+ Returns:
+ True if a given label is a restriction label (of the specified perm)
+ """
+ if label in _KNOWN_NON_RESTRICTION_LABELS:
+ return False
+ if not perm and label in _KNOWN_RESTRICTION_LABELS:
+ return True
+
+ prefix = ('restrict-%s-' % perm.lower()) if perm else 'restrict-'
+ is_restrict = label.lower().startswith(prefix) and label.count('-') >= 2
+
+ if is_restrict:
+ _KNOWN_RESTRICTION_LABELS.add(label)
+ elif not perm:
+ _KNOWN_NON_RESTRICTION_LABELS.add(label)
+
+ return is_restrict
+
+
+def HasRestrictions(issue, perm=''):
+ """Return True if the issue has any restrictions (on the specified perm)."""
+ return (
+ any(IsRestrictLabel(lab, perm=perm) for lab in issue.labels) or
+ any(IsRestrictLabel(lab, perm=perm) for lab in issue.derived_labels))
+
+
+def GetRestrictions(issue):
+ """Return a list of restriction labels on the given issue."""
+ if not issue:
+ return []
+
+ return [lab.lower() for lab in tracker_bizobj.GetLabels(issue)
+ if IsRestrictLabel(lab)]
+
+
+def CanViewIssue(
+ effective_ids, perms, project, issue, allow_viewing_deleted=False,
+ granted_perms=None):
+ """Checks if user has permission to view an artifact.
+
+ Args:
+ effective_ids: set of user IDs for the logged in user and any user
+ group memberships. Should be an empty set for anon users.
+ perms: PermissionSet for the user.
+ project: Project PB for the project that contains this issue.
+ issue: Issue PB for the issue being viewed.
+ allow_viewing_deleted: True if the user should be allowed to view
+ deleted artifacts.
+ granted_perms: optional list of strings of permissions that the user is
+ granted only within the scope of one issue, e.g., by being named in
+ a user-type custom field that grants permissions.
+
+ Returns:
+ True iff the user can view the specified issue.
+ """
+ if issue.deleted and not allow_viewing_deleted:
+ # No one can view a deleted issue. If the user can undelete, that
+ # goes through the custom 404 page.
+ return False
+
+ # Check to see if the user can view anything in the project.
+ if not perms.CanUsePerm(VIEW, effective_ids, project, []):
+ return False
+
+ if not HasRestrictions(issue):
+ return True
+
+ return CanViewRestrictedIssueInVisibleProject(
+ effective_ids, perms, project, issue, granted_perms=granted_perms)
+
+
+def CanViewRestrictedIssueInVisibleProject(
+ effective_ids, perms, project, issue, granted_perms=None):
+ """Return True if the user can view this issue. Assumes project is OK."""
+ # The reporter, owner, and CC'd users can always see the issue.
+ # In effect, these fields override artifact restriction labels.
+ if effective_ids:
+ if (issue.reporter_id in effective_ids or
+ tracker_bizobj.GetOwnerId(issue) in effective_ids or
+ not effective_ids.isdisjoint(tracker_bizobj.GetCcIds(issue))):
+ return True
+
+ # Otherwise, apply the usual permission checking.
+ return CanView(
+ effective_ids, perms, project, GetRestrictions(issue),
+ granted_perms=granted_perms)
+
+
+def CanEditIssue(effective_ids, perms, project, issue, granted_perms=None):
+ """Return True if a user can edit an issue.
+
+ Args:
+ effective_ids: set of user IDs for the logged in user and any user
+ group memberships. Should be an empty set for anon users.
+ perms: PermissionSet for the user.
+ project: Project PB for the project that contains this issue.
+ issue: Issue PB for the issue being viewed.
+ granted_perms: optional list of strings of permissions that the user is
+ granted only within the scope of one issue, e.g., by being named in
+ a user-type custom field that grants permissions.
+
+ Returns:
+ True iff the user can edit the specified issue.
+ """
+ # TODO(jrobbins): We need to actually grant View+EditIssue in most cases.
+ # So, always grant View whenever there is any granted perm.
+ if not CanViewIssue(
+ effective_ids, perms, project, issue, granted_perms=granted_perms):
+ return False
+
+ # The issue owner can always edit the issue.
+ if effective_ids:
+ if tracker_bizobj.GetOwnerId(issue) in effective_ids:
+ return True
+
+ # Otherwise, apply the usual permission checking.
+ return perms.CanUsePerm(
+ EDIT_ISSUE, effective_ids, project, GetRestrictions(issue),
+ granted_perms=granted_perms)
+
+
+def CanCommentIssue(effective_ids, perms, project, issue, granted_perms=None):
+ """Return True if a user can comment on an issue."""
+
+ return perms.CanUsePerm(
+ ADD_ISSUE_COMMENT, effective_ids, project,
+ GetRestrictions(issue), granted_perms=granted_perms)
+
+
+def CanViewComponentDef(effective_ids, perms, project, component_def):
+ """Return True if a user can view the given component definition."""
+ if not effective_ids.isdisjoint(component_def.admin_ids):
+ return True # Component admins can view that component.
+
+ # TODO(jrobbins): check restrictions on the component definition.
+ return perms.CanUsePerm(VIEW, effective_ids, project, [])
+
+
+def CanEditComponentDef(effective_ids, perms, project, component_def, config):
+ """Return True if a user can edit the given component definition."""
+ if not effective_ids.isdisjoint(component_def.admin_ids):
+ return True # Component admins can edit that component.
+
+ # Check to see if user is admin of any parent component.
+ parent_components = tracker_bizobj.FindAncestorComponents(
+ config, component_def)
+ for parent in parent_components:
+ if not effective_ids.isdisjoint(parent.admin_ids):
+ return True
+
+ return perms.CanUsePerm(EDIT_PROJECT, effective_ids, project, [])
+
+
+def CanViewFieldDef(effective_ids, perms, project, field_def):
+ """Return True if a user can view the given field definition."""
+ if not effective_ids.isdisjoint(field_def.admin_ids):
+ return True # Field admins can view that field.
+
+ # TODO(jrobbins): check restrictions on the field definition.
+ return perms.CanUsePerm(VIEW, effective_ids, project, [])
+
+
+def CanEditFieldDef(effective_ids, perms, project, field_def):
+ """Return True if a user can edit the given field definition."""
+ if not effective_ids.isdisjoint(field_def.admin_ids):
+ return True # Field admins can edit that field.
+
+ return perms.CanUsePerm(EDIT_PROJECT, effective_ids, project, [])
+
+
+def CanViewTemplate(effective_ids, perms, project, template):
+ """Return True if a user can view the given issue template."""
+ if not effective_ids.isdisjoint(template.admin_ids):
+ return True # template admins can view that template.
+
+ # Members-only templates are only shown to members, other templates are
+ # shown to any user that is generally allowed to view project content.
+ if template.members_only:
+ return framework_bizobj.UserIsInProject(project, effective_ids)
+ else:
+ return perms.CanUsePerm(VIEW, effective_ids, project, [])
+
+
+def CanEditTemplate(effective_ids, perms, project, template):
+ """Return True if a user can edit the given field definition."""
+ if not effective_ids.isdisjoint(template.admin_ids):
+ return True # Template admins can edit that template.
+
+ return perms.CanUsePerm(EDIT_PROJECT, effective_ids, project, [])
+
+
+class Error(Exception):
+ """Base class for errors from this module."""
+
+
+class PermissionException(Error):
+ """The user is not authorized to make the current request."""
+
+
+class BannedUserException(Error):
+ """The user has been banned from using our service."""
« 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