Merge branch 'main' into avm99963-monorail
Merged commit 34d8229ae2b51fb1a15bd208e6fe6185c94f6266
GitOrigin-RevId: 7ee0917f93a577e475f8e09526dd144d245593f4
diff --git a/businesslogic/work_env.py b/businesslogic/work_env.py
index d15d67f..58d52cb 100644
--- a/businesslogic/work_env.py
+++ b/businesslogic/work_env.py
@@ -1,7 +1,6 @@
-# Copyright 2017 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
-# https://developers.google.com/open-source/licenses/bsd
+# Copyright 2017 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
"""WorkEnv is a context manager and API for high-level operations.
@@ -68,6 +67,7 @@
from framework import framework_helpers
from framework import framework_views
from framework import permissions
+from redirect import redirectissue
from search import frontendsearchpipeline
from services import features_svc
from services import tracker_fulltext
@@ -79,10 +79,10 @@
from tracker import tracker_constants
from tracker import tracker_helpers
from project import project_helpers
-from proto import features_pb2
-from proto import project_pb2
-from proto import tracker_pb2
-from proto import user_pb2
+from mrproto import features_pb2
+from mrproto import project_pb2
+from mrproto import tracker_pb2
+from mrproto import user_pb2
# TODO(jrobbins): break this file into one facade plus ~5
@@ -176,7 +176,8 @@
permitted = self._UserCanUsePermInIssue(issue, perm)
if not permitted:
raise permissions.PermissionException(
- 'User lacks permission %r in issue' % perm)
+ 'User lacks permission %r in issue %s %d', perm, issue.project_name,
+ issue.local_id)
def _AssertUserCanModifyIssues(
self, issue_delta_pairs, is_description_change, comment_content=None):
@@ -216,6 +217,15 @@
delta.merged_into, use_cache=False, allow_viewing_deleted=True)
self._AssertPermInIssue(merged_into_issue, permissions.EDIT_ISSUE)
+ # User cannot modify blocking issues on issues they cannot edit.
+ all_block = (
+ delta.blocked_on_add + delta.blocking_add +
+ delta.blocked_on_remove + delta.blocking_remove)
+ for block_iid in all_block:
+ blocked_issue = self.GetIssue(
+ block_iid, use_cache=False, allow_viewing_deleted=True)
+ self._AssertPermInIssue(blocked_issue, permissions.EDIT_ISSUE)
+
# User cannot change values for restricted fields they cannot edit.
field_ids = [fv.field_id for fv in delta.field_vals_add]
field_ids.extend([fv.field_id for fv in delta.field_vals_remove])
@@ -225,7 +235,7 @@
self._AssertUserCanEditFieldsAndEnumMaskedLabels(
project, config, field_ids, labels)
except permissions.PermissionException as e:
- err_agg.AddErrorMessage(e.message)
+ err_agg.AddErrorMessage(str(e))
if issue_perms.HasPerm(permissions.EDIT_ISSUE, self.mc.auth.user_id,
project):
@@ -324,7 +334,7 @@
try:
self._AssertUserCanEditValueForFieldDef(project, fd)
except permissions.PermissionException as e:
- err_agg.AddErrorMessage(e.message)
+ err_agg.AddErrorMessage(str(e))
def _AssertUserCanViewFieldDef(self, project, field):
"""Make sure the user may view the field."""
@@ -395,9 +405,12 @@
# the results are filtered by permission to view each project.
with self.mc.profiler.Phase('list projects for %r' % self.mc.auth.user_id):
- project_ids = self.services.project.GetVisibleLiveProjects(
- self.mc.cnxn, self.mc.auth.user_pb, self.mc.auth.effective_ids,
- domain=domain, use_cache=use_cache)
+ project_ids = self.services.project.GetVisibleProjects(
+ self.mc.cnxn,
+ self.mc.auth.user_pb,
+ self.mc.auth.effective_ids,
+ domain=domain,
+ use_cache=use_cache)
return project_ids
@@ -935,7 +948,7 @@
'Ancestor path %s is invalid.' % ancestor_path)
project_perms = permissions.GetPermissions(
self.mc.auth.user_pb, self.mc.auth.effective_ids, project)
- if not permissions.CanEditComponentDef(
+ if not permissions.CanEditComponentDefLegacy(
self.mc.auth.effective_ids, project_perms, project, ancestor_def,
config):
raise permissions.PermissionException(
@@ -976,7 +989,7 @@
project_perms = permissions.GetPermissions(
self.mc.auth.user_pb, self.mc.auth.effective_ids, project)
- if not permissions.CanEditComponentDef(
+ if not permissions.CanEditComponentDefLegacy(
self.mc.auth.effective_ids, project_perms, project, component_def,
config):
raise permissions.PermissionException(
@@ -1031,14 +1044,14 @@
owner_id, # type: int
cc_ids, # type: Sequence[int]
labels, # type: Sequence[str]
- field_values, # type: Sequence[proto.tracker_pb2.FieldValue]
+ field_values, # type: Sequence[mrproto.tracker_pb2.FieldValue]
component_ids, # type: Sequence[int]
marked_description, # type: str
blocked_on=None, # type: Sequence[int]
blocking=None, # type: Sequence[int]
attachments=None, # type: Sequence[Tuple[str, str, str]]
- phases=None, # type: Sequence[proto.tracker_pb2.Phase]
- approval_values=None, # type: Sequence[proto.tracker_pb2.ApprovalValue]
+ phases=None, # type: Sequence[mrproto.tracker_pb2.Phase]
+ approval_values=None, # type: Sequence[mrproto.tracker_pb2.ApprovalValue]
send_email=True, # type: bool
reporter_id=None, # type: int
timestamp=None, # type: int
@@ -1046,7 +1059,8 @@
dangling_blocking=None, # type: Sequence[DanglingIssueRef]
raise_filter_errors=True, # type: bool
):
- # type: (...) -> (proto.tracker_pb2.Issue, proto.tracker_pb2.IssueComment)
+ # type: (...) ->
+ # (mrproto.tracker_pb2.Issue, mrproto.tracker_pb2.IssueComment)
"""Create and store a new issue with all the given information.
Args:
@@ -1134,6 +1148,7 @@
issue.owner_modified_timestamp = timestamp
issue.status_modified_timestamp = timestamp
issue.component_modified_timestamp = timestamp
+ issue.migration_modified_timestamp = timestamp
# Validate the issue
tracker_helpers.AssertValidIssueForCreate(
@@ -1273,7 +1288,18 @@
self._AssertPermInIssue(issue, permissions.DELETE_ISSUE)
self._AssertPermInProject(permissions.EDIT_ISSUE, target_project)
- if permissions.GetRestrictions(issue):
+ restrictions = permissions.GetRestrictions(issue)
+ # Issues with allowed labels may move between allowed projects.
+ # Context: https://crbug.com/monorail/11894
+ allowed_project_names = ['chromium', 'webrtc']
+ allowed_labels = frozenset(
+ ['restrict-view-securityteam', 'restrict-view-securitynotify'])
+ if (target_project.project_name.lower()
+ in allowed_project_names) and (issue.project_name.lower()
+ in allowed_project_names):
+ restrictions = set(restrictions) - allowed_labels
+
+ if restrictions:
raise exceptions.InputException(
'Issues with Restrict labels are not allowed to be moved')
@@ -1398,7 +1424,7 @@
group_by_spec, # type: str
sort_spec, # type: str
use_cached_searches, # type: bool
- project=None # type: proto.Project
+ project=None # type: mrproto.Project
):
# type: (...) -> search.frontendsearchpipeline.FrontendSearchPipeline
"""Do an issue search w/ mc + passed in args to return a pipeline object.
@@ -1527,7 +1553,7 @@
self._AssertUserCanViewIssue(
issue, allow_viewing_deleted=allow_viewing_deleted)
except permissions.PermissionException as e:
- permission_err_agg.AddErrorMessage(e.message)
+ permission_err_agg.AddErrorMessage(str(e))
return issues_by_id
@@ -1600,6 +1626,25 @@
issue, allow_viewing_deleted=allow_viewing_deleted)
return issue
+ def ExtractMigratedIdFromLabels(self, labels):
+ """Returns the issue ID from a migration label if present."""
+ # Assume that there's only one migrated label.
+ # Or at least drop any labels besides the first one.
+ if labels is not None:
+ for label in labels:
+ lower_label = label.lower()
+ for prefix in settings.migrated_buganizer_issue_prefixes:
+ if lower_label.startswith(prefix):
+ return label.replace(prefix, '')
+ return None
+
+ def GetIssueMigratedID(self, project_name, local_id, labels=None):
+ """Return the redirect id for a specific issue."""
+ migrated_id = redirectissue.RedirectIssue.Get(project_name, local_id)
+ if migrated_id is not None:
+ return migrated_id
+ return self.ExtractMigratedIdFromLabels(labels)
+
def GetRelatedIssueRefs(self, issues):
"""Return a dict {iid: (project_name, local_id)} for all related issues."""
related_iids = set()
@@ -1609,7 +1654,6 @@
related_iids.update(issue.blocking_iids)
if issue.merged_into:
related_iids.add(issue.merged_into)
- logging.info('related_iids is %r', related_iids)
return self.services.issue.LookupIssueRefs(self.mc.cnxn, related_iids)
def GetIssueRefs(self, issue_ids):
@@ -1647,7 +1691,7 @@
def BulkUpdateIssueApprovalsV3(
self, delta_specifications, comment_content, send_email):
# type: (Sequence[Tuple[int, int, tracker_pb2.ApprovalDelta]]], str,
- # Boolean -> Sequence[proto.tracker_pb2.ApprovalValue]
+ # Boolean -> Sequence[mrproto.tracker_pb2.ApprovalValue]
"""Executes the ApprovalDeltas.
Args:
@@ -1691,10 +1735,10 @@
send_email=True,
kept_attachments=None,
update_perms=False):
- # type: (int, int, proto.tracker_pb2.ApprovalDelta, str, Boolean,
- # Optional[Sequence[proto.tracker_pb2.Attachment]], Optional[Boolean],
+ # type: (int, int, mrproto.tracker_pb2.ApprovalDelta, str, Boolean,
+ # Optional[Sequence[mrproto.tracker_pb2.Attachment]], Optional[Boolean],
# Optional[Sequence[int]], Optional[Boolean]) ->
- # (proto.tracker_pb2.ApprovalValue, proto.tracker_pb2.IssueComment)
+ # (mrproto.tracker_pb2.ApprovalValue, mrproto.tracker_pb2.IssueComment)
"""Update an issue's approval.
Raises:
@@ -1769,7 +1813,7 @@
def ConvertIssueApprovalsTemplate(
self, config, issue, template_name, comment_content, send_email=True):
- # type: (proto.tracker_pb2.ProjectIssueConfig, proto.tracker_pb2.Issue,
+ # type: (mrproto.tracker_pb2.ProjectIssueConfig, mrproto.tracker_pb2.Issue,
# str, str, Optional[Boolean] )
"""Convert an issue's existing approvals structure to match the one of
the given template.
@@ -1853,6 +1897,7 @@
# Reject attempts to merge an issue into an issue we cannot view and edit.
merged_into_issue = self.GetIssue(
delta.merged_into, use_cache=False, allow_viewing_deleted=True)
+ self._AssertPermInIssue(merged_into_issue, permissions.EDIT_ISSUE)
self._AssertPermInIssue(issue, permissions.EDIT_ISSUE)
# Reject attempts to merge an issue into itself.
if issue.issue_id == delta.merged_into:
@@ -1864,6 +1909,15 @@
comment_content) > tracker_constants.MAX_COMMENT_CHARS:
raise exceptions.InputException('Comment is too long')
+ # Reject attempts to modifying blocking issues we cannot edit.
+ all_block = (
+ delta.blocked_on_add + delta.blocking_add + delta.blocked_on_remove +
+ delta.blocking_remove)
+ for block_iid in all_block:
+ blocked_issue = self.GetIssue(
+ block_iid, use_cache=False, allow_viewing_deleted=True)
+ self._AssertPermInIssue(blocked_issue, permissions.EDIT_ISSUE)
+
# Reject attempts to block on issue on itself.
if (issue.issue_id in delta.blocked_on_add
or issue.issue_id in delta.blocking_add):
@@ -1877,7 +1931,13 @@
field_ids = [fv.field_id for fv in delta.field_vals_add]
field_ids.extend([fvr.field_id for fvr in delta.field_vals_remove])
field_ids.extend(delta.fields_clear)
+
labels = itertools.chain(delta.labels_add, delta.labels_remove)
+ labels_err_msg = field_helpers.ValidateLabels(
+ self.mc.cnxn, self.services, issue.project_id, delta.labels_add)
+ if labels_err_msg:
+ raise exceptions.InputException(labels_err_msg)
+
self._AssertUserCanEditFieldsAndEnumMaskedLabels(
project, config, field_ids, labels)
@@ -2062,6 +2122,7 @@
changes.issues_to_update_dict[issue.issue_id] = issue
issue.modified_timestamp = now_timestamp
+ issue.migration_modified_timestamp = now_timestamp
if (iid in changes.old_owners_by_iid and
old_owner != tracker_bizobj.GetOwnerId(issue)):
@@ -2142,8 +2203,8 @@
self.mc.cnxn, pid, attachment_bytes_used=new_bytes_used, commit=False)
# Reindex issues and commit all DB changes.
- issues_to_reindex = set(
- comments_by_iid.keys() + impacted_comments_by_iid.keys())
+ issues_to_reindex = (
+ set(comments_by_iid.keys()) | set(impacted_comments_by_iid.keys()))
if issues_to_reindex:
self.services.issue.EnqueueIssuesForIndexing(
self.mc.cnxn, issues_to_reindex, commit=False)
@@ -2168,9 +2229,9 @@
# Group issues for each unique delta by project because
# SendIssueBulkChangeNotification cannot handle cross-project
# notifications and hostports are specific to each project.
- issues_by_pid = collections.defaultdict(set)
+ issues_by_pid = collections.defaultdict(list)
for issue in issues:
- issues_by_pid[issue.project_id].add(issue)
+ issues_by_pid[issue.project_id].append(issue)
for project_issues in issues_by_pid.values():
# Send one email to involved users for the issue.
if len(project_issues) == 1:
@@ -3806,20 +3867,6 @@
self.services.features.UpdateHotlistItemsFields(
self.mc.cnxn, hotlist_id, new_notes=new_notes)
- def expungeUsersFromStars(self, user_ids):
- """Wipes any starred user or user's stars from all star services.
-
- This method will not commit the operation. This method will not
- make changes to in-memory data.
- """
-
- self.services.project_star.ExpungeStarsByUsers(self.mc.cnxn, user_ids)
- self.services.issue_star.ExpungeStarsByUsers(self.mc.cnxn, user_ids)
- self.services.hotlist_star.ExpungeStarsByUsers(self.mc.cnxn, user_ids)
- self.services.user_star.ExpungeStarsByUsers(self.mc.cnxn, user_ids)
- for user_id in user_ids:
- self.services.user_star.ExpungeStars(self.mc.cnxn, user_id, commit=False)
-
# Permissions
# ListFooPermission methods will return the list of permissions in addition to