Merge branch 'main' into avm99963-monorail

Merged commit 34d8229ae2b51fb1a15bd208e6fe6185c94f6266

GitOrigin-RevId: 7ee0917f93a577e475f8e09526dd144d245593f4
diff --git a/tracker/issuebulkedit.py b/tracker/issuebulkedit.py
index 3ea4d3f..f902a86 100644
--- a/tracker/issuebulkedit.py
+++ b/tracker/issuebulkedit.py
@@ -1,7 +1,6 @@
-# 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
-# https://developers.google.com/open-source/licenses/bsd
+# Copyright 2016 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Classes that implement the issue bulk edit page and related forms.
 
@@ -24,7 +23,6 @@
 from features import filterrules_helpers
 from features import send_notifications
 from framework import exceptions
-from framework import flaskservlet
 from framework import framework_constants
 from framework import framework_views
 from framework import permissions
@@ -42,7 +40,7 @@
   """IssueBulkEdit lists multiple issues and allows an edit to all of them."""
 
   _PAGE_TEMPLATE = 'tracker/issue-bulk-edit-page.ezt'
-  _MAIN_TAB_MODE = flaskservlet.FlaskServlet.MAIN_TAB_ISSUES
+  _MAIN_TAB_MODE = servlet.Servlet.MAIN_TAB_ISSUES
   _SECONDS_OVERHEAD = 4
   _SECONDS_PER_UPDATE = 0.12
   _SLOWNESS_THRESHOLD = 10
@@ -171,47 +169,45 @@
     """
     if not mr.local_id_list:
       logging.info('missing issue local IDs, probably tampered')
-      #TODO: switch when convert /p to flask
-      # self.response.status_code = http_client.BAD_REQUEST
-      self.response.status = http_client.BAD_REQUEST
+      self.response.status_code = http_client.BAD_REQUEST
       return
 
     # Check that the user is logged in; anon users cannot update issues.
     if not mr.auth.user_id:
       logging.info('user was not logged in, cannot update issue')
-      #TODO: switch when convert /p to flask
-      # self.response.status_code = http_client.BAD_REQUEST
-      self.response.status = http_client.BAD_REQUEST
+      self.response.status_code = http_client.BAD_REQUEST
       return
 
     # Check that the user has permission to add a comment, and to enter
     # metadata if they are trying to do that.
     if not self.CheckPerm(mr, permissions.ADD_ISSUE_COMMENT):
       logging.info('user has no permission to add issue comment')
-      #TODO: switch when convert /p to flask
-      # self.response.status_code = http_client.BAD_REQUEST
-      self.response.status = http_client.BAD_REQUEST
+      self.response.status_code = http_client.BAD_REQUEST
       return
 
     if not self.CheckPerm(mr, permissions.EDIT_ISSUE):
       logging.info('user has no permission to edit issue metadata')
-      #TODO: switch when convert /p to flask
-      # self.response.status_code = http_client.BAD_REQUEST
-      self.response.status = http_client.BAD_REQUEST
+      self.response.status_code = http_client.BAD_REQUEST
       return
 
     move_to = post_data.get('move_to', '').lower()
     if move_to and not self.CheckPerm(mr, permissions.DELETE_ISSUE):
       logging.info('user has no permission to move issue')
-      #TODO: switch when convert /p to flask
-      # self.response.status_code = http_client.BAD_REQUEST
-      self.response.status = http_client.BAD_REQUEST
+      self.response.status_code = http_client.BAD_REQUEST
       return
 
     config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
 
     parsed = tracker_helpers.ParseIssueRequest(
         mr.cnxn, post_data, self.services, mr.errors, mr.project_name)
+
+    field_helpers.ValidateLabels(
+        mr.cnxn,
+        self.services,
+        mr.project_id,
+        parsed.labels,
+        ezt_errors=mr.errors)
+
     bounce_labels = (
         parsed.labels[:] +
         ['-%s' % lr for lr in parsed.labels_remove])
@@ -341,6 +337,35 @@
             issue for issue in editable_issues
             if not permissions.GetRestrictions(issue)]
 
+      # Check that we can modify issues we want to block with.
+      if post_data.get('blocked_on'):
+        for issue_ref in post_data.get('blocked_on').split(','):
+          if not issue_ref:
+            continue
+          project_name, iid = tracker_bizobj.ParseIssueRef(issue_ref)
+          project_name = project_name or mr.project_name
+          project = self.services.project.GetProjectByName(
+              mr.cnxn, project_name)
+          issue = self.services.issue.GetIssueByLocalID(
+              mr.cnxn, project.project_id, iid, use_cache=False)
+          if not self._CheckEditIssuePermissions(mr, project, issue):
+            mr.errors.blocked_on = 'Target issue %s cannot be modified' % (iid)
+            break
+
+      if post_data.get('blocking'):
+        for issue_ref in post_data.get('blocking').split(','):
+          if not issue_ref:
+            continue
+          project_name, iid = tracker_bizobj.ParseIssueRef(issue_ref)
+          project_name = project_name or mr.project_name
+          project = self.services.project.GetProjectByName(
+              mr.cnxn, project_name)
+          issue = self.services.issue.GetIssueByLocalID(
+              mr.cnxn, project.project_id, iid, use_cache=False)
+          if not self._CheckEditIssuePermissions(mr, project, issue):
+            mr.errors.blocking = 'Target issue %s cannot be modified' % (iid)
+            break
+
       # If 'Duplicate' status is specified ensure there are no permission issues
       # with the issue we want to merge with.
       if post_data.get('merge_into'):
@@ -349,9 +374,10 @@
               mr.cnxn, self.services, mr.project_name, post_data, parsed.status,
               config, issue, mr.errors)
           if merge_into_issue:
-            merge_allowed = tracker_helpers.IsMergeAllowed(
-                merge_into_issue, mr, self.services)
-            if not merge_allowed:
+            project = self.services.project.GetProjectByName(
+                mr.cnxn, issue.project_name)
+            if not self._CheckEditIssuePermissions(mr, project,
+                                                   merge_into_issue):
               mr.errors.merge_into_id = 'Target issue %s cannot be modified' % (
                                             merge_into_issue.local_id)
               break
@@ -483,8 +509,15 @@
     return tracker_helpers.FormatIssueListURL(
         mr, config, saved=len(mr.local_id_list), ts=int(time.time()))
 
-  # def GetIssueBulkEdit(self, **kwargs):
-  #   return self.handler(**kwargs)
+  def GetIssueBulkEdit(self, **kwargs):
+    return self.handler(**kwargs)
 
-  # def PostIssueBulkEdit(self, **kwargs):
-  #   return self.handler(**kwargs)
+  def PostIssueBulkEdit(self, **kwargs):
+    return self.handler(**kwargs)
+
+  def _CheckEditIssuePermissions(self, mr, project, issue):
+    config = self.services.config.GetProjectConfig(mr.cnxn, project.project_id)
+    granted_perms = tracker_bizobj.GetGrantedPerms(
+        issue, mr.auth.effective_ids, config)
+    return tracker_helpers.CanEditProjectIssue(
+        mr, project, issue, granted_perms)