blob: 23a2d4644f6dcadd9d341760a71d94ac3ec56791 [file] [log] [blame]
# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
"""Helper functions and classes used by the project pages."""
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
import logging
import re
import settings
from google.appengine.api import app_identity
from framework import framework_bizobj
from framework import framework_views
from framework import gcs_helpers
from framework import permissions
from project import project_constants
from project import project_views
from proto import project_pb2
_RE_EMAIL_SEPARATORS = re.compile(r'\s|,|;')
def BuildProjectMembers(cnxn, project, user_service):
"""Gather data for the members section of a project page.
cnxn: connection to SQL database.
project: Project PB of current project.
user_service: an instance of UserService for user persistence.
A dictionary suitable for use with EZT.
# First, get all needed info on all users in one batch of requests.
users_by_id = framework_views.MakeAllUserViews(
cnxn, user_service, AllProjectMembers(project))
# Second, group the user proxies by role for display.
owner_proxies = [users_by_id[owner_id]
for owner_id in project.owner_ids]
committer_proxies = [users_by_id[committer_id]
for committer_id in project.committer_ids]
contributor_proxies = [users_by_id[contrib_id]
for contrib_id in project.contributor_ids]
return {
'owners': owner_proxies,
'committers': committer_proxies,
'contributors': contributor_proxies,
'all_members': list(users_by_id.values()),
def BuildProjectAccessOptions(project):
"""Return a list of project access values for use in an HTML menu.
project: current Project PB, or None when creating a new project.
A list of ProjectAccessView objects that can be used in EZT.
access_levels = [project_pb2.ProjectAccess.ANYONE,
access_views = []
for access in access_levels:
# Offer the allowed access levels. When editing an existing project,
# its current access level may always be kept, even if it is no longer
# in the list of allowed access levels for new projects.
if (access in settings.allowed_access_levels or
(project and access == project.access)):
return access_views
def ParseUsernames(cnxn, user_service, usernames_text):
"""Parse all usernames from a text field and return a list of user IDs.
cnxn: connection to SQL database.
user_service: an instance of UserService for user persistence.
usernames_text: string that the user entered into a form field for a list
of email addresses. Or, None if the browser did not send that value.
A set of user IDs for the users named. Or, an empty set if the
usernames_field was not in post_data.
if not usernames_text: # The user did not enter any addresses.
return set()
email_list = _RE_EMAIL_SEPARATORS.split(usernames_text)
# skip empty strings between consecutive separators
email_list = [email for email in email_list if email]
id_dict = user_service.LookupUserIDs(cnxn, email_list, autocreate=True)
return set(id_dict.values())
def ParseProjectAccess(project, access_num_str):
"""Parse and validate the "access" field out of post_data.
project: Project PB for the project that was edited, or None if the
user is creating a new project.
access_num_str: string of digits from the users POST that identifies
the desired project access level. Or, None if that widget was not
offered to the user.
An enum project access level, or None if the user did not specify
any value or if the value specified was invalid.
access = None
if access_num_str:
access_number = int(access_num_str)
available_access_levels = BuildProjectAccessOptions(project)
allowed_access_choices = [access_view.key for access_view
in available_access_levels]
if access_number in allowed_access_choices:
access = project_pb2.ProjectAccess(access_number)
return access
def MembersWithoutGivenIDs(project, exclude_ids):
"""Return three lists of member user IDs, with member_ids not in them."""
owner_ids = [user_id for user_id in project.owner_ids
if user_id not in exclude_ids]
committer_ids = [user_id for user_id in project.committer_ids
if user_id not in exclude_ids]
contributor_ids = [user_id for user_id in project.contributor_ids
if user_id not in exclude_ids]
return owner_ids, committer_ids, contributor_ids
def MembersWithGivenIDs(project, new_member_ids, role):
"""Return three lists of member IDs with the new IDs in the right one.
project: Project PB for the project to get current members from.
new_member_ids: set of user IDs for members being added.
role: string name of the role that new_member_ids should be granted.
Three lists of member IDs with new_member_ids added to the appropriate
list and removed from any other role.
ValueError: if the role is not one of owner, committer, or contributor.
owner_ids, committer_ids, contributor_ids = MembersWithoutGivenIDs(
project, new_member_ids)
if role == 'owner':
elif role == 'committer':
elif role == 'contributor':
raise ValueError()
return owner_ids, committer_ids, contributor_ids
def UsersInvolvedInProject(project):
"""Return a set of all user IDs referenced in the Project."""
result = set()
result.update([perm.member_id for perm in project.extra_perms])
return result
def UsersWithPermsInProject(project, perms_needed, users_by_id,
# Users that have the given permission are stored in direct_users_for_perm,
# users whose effective ids have the given permission are stored in
# indirect_users_for_perm.
direct_users_for_perm = {perm: set() for perm in perms_needed}
indirect_users_for_perm = {perm: set() for perm in perms_needed}
# Iterate only over users that have extra permissions, so we don't
# have to search the extra perms more than once for each user.
for extra_perm_pb in project.extra_perms:
extra_perms = set(perm.lower() for perm in extra_perm_pb.perms)
for perm, users in direct_users_for_perm.items():
if perm.lower() in extra_perms:
# Then, iterate over all users, but don't compute extra permissions.
for user_id, user_view in users_by_id.items():
effective_ids = effective_ids_by_user[user_id].union([user_id])
user_perms = permissions.GetPermissions(
user_view.user, effective_ids, project)
for perm, users in direct_users_for_perm.items():
if not effective_ids.isdisjoint(users):
if user_perms.HasPerm(perm, None, None, []):
for perm, users in direct_users_for_perm.items():
return direct_users_for_perm
def GetThumbnailUrl(gcs_id):
# type: (str) -> str
"""Derive the thumbnail url for a given GCS object ID."""
bucket_name = app_identity.get_default_gcs_bucket_name()
return gcs_helpers.SignUrl(bucket_name, gcs_id + '-thumbnail')
def IsValidProjectName(s):
# type: (string) -> bool
"""Return true if the given string is a valid project name."""
return (
project_constants.RE_PROJECT_NAME.match(s) and
len(s) <= project_constants.MAX_PROJECT_NAME_LENGTH)
def AllProjectMembers(project):
# type: (proto.project_pb2.Project) -> Sequence[int]
"""Return a list of user IDs of all members in the given project."""
return project.owner_ids + project.committer_ids + project.contributor_ids