Merge branch 'main' into avm99963-monorail

Merged commit 34d8229ae2b51fb1a15bd208e6fe6185c94f6266

GitOrigin-RevId: 7ee0917f93a577e475f8e09526dd144d245593f4
diff --git a/services/api_svc_v1.py b/services/api_svc_v1.py
index 8d8f238..883d69f 100644
--- a/services/api_svc_v1.py
+++ b/services/api_svc_v1.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.
 
 """API service.
 
@@ -45,9 +44,9 @@
 from framework import ratelimiter
 from framework import sql
 from project import project_helpers
-from proto import api_pb2_v1
-from proto import project_pb2
-from proto import tracker_pb2
+from mrproto import api_pb2_v1
+from mrproto import project_pb2
+from mrproto import tracker_pb2
 from search import frontendsearchpipeline
 from services import api_pb2_v1_helpers
 from services import client_config_svc
@@ -59,6 +58,7 @@
 from tracker import tracker_bizobj
 from tracker import tracker_constants
 from tracker import tracker_helpers
+from redirect import redirect_utils
 
 from infra_libs import ts_mon
 
@@ -284,7 +284,9 @@
     if not project:
       raise exceptions.NoSuchProjectException(
           'Project %s does not exist' % project_name)
-    if project.state != project_pb2.ProjectState.LIVE:
+    # Allow to view non-live projects that were migrated.
+    if (project.state != project_pb2.ProjectState.LIVE and
+        project_name not in redirect_utils.PROJECT_REDIRECT_MAP):
       raise permissions.PermissionException(
           'API may not access project %s because it is not live'
           % project_name)
@@ -314,7 +316,6 @@
 
 @endpoints.api(name=ENDPOINTS_API_NAME, version='v1',
                description='Monorail API to manage issues.',
-               auth_level=endpoints.AUTH_LEVEL.NONE,
                allowed_client_ids=endpoints.SKIP_CLIENT_ID_CHECK,
                documentation=DOC_URL)
 class MonorailApi(remote.Service):
@@ -414,7 +415,7 @@
       http_method='POST',
       name='issues.comments.insert')
   def issues_comments_insert(self, mar, request):
-    # type (...) -> proto.api_pb2_v1.IssuesCommentsInsertResponse
+    # type (...) -> mrproto.api_pb2_v1.IssuesCommentsInsertResponse
     """Add a comment."""
     # Because we will modify issues, load from DB rather than cache.
     issue = self._services.issue.GetIssueByLocalID(
@@ -429,7 +430,7 @@
 
     # Temporary block on updating approval subfields.
     if request.updates and request.updates.fieldValues:
-      fds_by_name = {fd.field_name.lower():fd for fd in mar.config.field_defs}
+      fds_by_name = {fd.field_name.lower(): fd for fd in mar.config.field_defs}
       for fv in request.updates.fieldValues:
         # Checking for fv.approvalName is unreliable since it can be removed.
         fd = fds_by_name.get(fv.fieldName.lower())
@@ -485,22 +486,46 @@
           mar.cnxn, updates_dict['cc_remove']).values())
       updates_dict['labels_add'], updates_dict['labels_remove'] = (
           api_pb2_v1_helpers.split_remove_add(request.updates.labels))
+
+      field_helpers.ValidateLabels(
+          mar.cnxn,
+          self._services,
+          mar.project_id,
+          updates_dict.get('labels_add', []),
+          ezt_errors=mar.errors)
+      if mar.errors.AnyErrors():
+        raise endpoints.BadRequestException(
+            'Invalid field values: %s' % mar.errors.labels)
+
       blocked_on_add_strs, blocked_on_remove_strs = (
           api_pb2_v1_helpers.split_remove_add(request.updates.blockedOn))
-      updates_dict['blocked_on_add'] = api_pb2_v1_helpers.issue_global_ids(
-          blocked_on_add_strs, issue.project_id, mar,
-          self._services)
-      updates_dict['blocked_on_remove'] = api_pb2_v1_helpers.issue_global_ids(
-          blocked_on_remove_strs, issue.project_id, mar,
-          self._services)
       blocking_add_strs, blocking_remove_strs = (
           api_pb2_v1_helpers.split_remove_add(request.updates.blocking))
-      updates_dict['blocking_add'] = api_pb2_v1_helpers.issue_global_ids(
-          blocking_add_strs, issue.project_id, mar,
-          self._services)
-      updates_dict['blocking_remove'] = api_pb2_v1_helpers.issue_global_ids(
-          blocking_remove_strs, issue.project_id, mar,
-          self._services)
+      blocked_on_add_iids = api_pb2_v1_helpers.issue_global_ids(
+          blocked_on_add_strs, issue.project_id, mar, self._services)
+      blocked_on_remove_iids = api_pb2_v1_helpers.issue_global_ids(
+          blocked_on_remove_strs, issue.project_id, mar, self._services)
+      blocking_add_iids = api_pb2_v1_helpers.issue_global_ids(
+          blocking_add_strs, issue.project_id, mar, self._services)
+      blocking_remove_iids = api_pb2_v1_helpers.issue_global_ids(
+          blocking_remove_strs, issue.project_id, mar, self._services)
+      all_block = (
+          blocked_on_add_iids + blocked_on_remove_iids + blocking_add_iids +
+          blocking_remove_iids)
+      for iid in all_block:
+        # Because we will modify issues, load from DB rather than cache.
+        issue = self._services.issue.GetIssue(mar.cnxn, iid, use_cache=False)
+        project = self._services.project.GetProjectByName(
+            mar.cnxn, issue.project_name)
+        if not tracker_helpers.CanEditProjectIssue(mar, project, issue,
+                                                   mar.granted_perms):
+          raise permissions.PermissionException(
+              'User is not allowed to block with issue (%s, %d)' %
+              (issue.project_name, issue.local_id))
+      updates_dict['blocked_on_add'] = blocked_on_add_iids
+      updates_dict['blocked_on_remove'] = blocked_on_remove_iids
+      updates_dict['blocking_add'] = blocking_add_iids
+      updates_dict['blocking_remove'] = blocking_remove_iids
       components_add_strs, components_remove_strs = (
           api_pb2_v1_helpers.split_remove_add(request.updates.components))
       updates_dict['components_add'] = (
@@ -518,12 +543,11 @@
         merge_into_issue = self._services.issue.GetIssueByLocalID(
             mar.cnxn, merge_into_project.project_id, merge_local_id,
             use_cache=False)
-        merge_allowed = tracker_helpers.IsMergeAllowed(
-            merge_into_issue, mar, self._services)
-        if not merge_allowed:
+        if not tracker_helpers.CanEditProjectIssue(
+            mar, merge_into_project, merge_into_issue, mar.granted_perms):
           raise permissions.PermissionException(
-            'User is not allowed to merge into issue %s:%s' %
-            (merge_into_issue.project_name, merge_into_issue.local_id))
+              'User is not allowed to merge into issue %s:%s' %
+              (merge_into_issue.project_name, merge_into_issue.local_id))
         updates_dict['merged_into'] = merge_into_issue.issue_id
       (updates_dict['field_vals_add'], updates_dict['field_vals_remove'],
        updates_dict['fields_clear'], updates_dict['fields_labels_add'],
@@ -730,7 +754,7 @@
       http_method='POST',
       name='approvals.comments.insert')
   def approvals_comments_insert(self, mar, request):
-    # type (...) -> proto.api_pb2_v1.ApprovalsCommentsInsertResponse
+    # type (...) -> mrproto.api_pb2_v1.ApprovalsCommentsInsertResponse
     """Add an approval comment."""
     approval_fd = tracker_bizobj.FindFieldDef(
         request.approvalName, mar.config)
@@ -769,8 +793,10 @@
       if request.approvalUpdates.fieldValues:
         # Block updating field values that don't belong to the approval.
         approvals_fds_by_name = {
-            fd.field_name.lower():fd for fd in mar.config.field_defs
-            if fd.approval_id == approval_fd.field_id}
+            fd.field_name.lower(): fd
+            for fd in mar.config.field_defs
+            if fd.approval_id == approval_fd.field_id
+        }
         for fv in request.approvalUpdates.fieldValues:
           if approvals_fds_by_name.get(fv.fieldName.lower()) is None:
             raise endpoints.BadRequestException(
@@ -804,7 +830,6 @@
           raise permissions.PermissionException(
               'User is not allowed to make this status change')
         updates_dict['status'] = status
-    logging.info(time.time)
     approval_delta = tracker_bizobj.MakeApprovalDelta(
         updates_dict.get('status'), mar.auth.user_id,
         updates_dict.get('approver_ids_add', []),
@@ -903,8 +928,13 @@
     issue = self._services.issue.GetIssueByLocalID(
         mar.cnxn, mar.project_id, request.issueId)
 
+    with work_env.WorkEnv(mar, self._services) as we:
+      migrated_id = we.GetIssueMigratedID(
+          request.projectId, request.issueId, issue.labels)
+
     return api_pb2_v1_helpers.convert_issue(
-        api_pb2_v1.IssuesGetInsertResponse, issue, mar, self._services)
+        api_pb2_v1.IssuesGetInsertResponse, issue, mar, self._services,
+        migrated_id)
 
   @monorail_api_method(
       api_pb2_v1.ISSUES_INSERT_REQUEST_RESOURCE_CONTAINER,
@@ -941,6 +971,17 @@
       fields_add, _, _, fields_labels, _ = (
           api_pb2_v1_helpers.convert_field_values(
               request.fieldValues, mar, self._services))
+
+      field_helpers.ValidateLabels(
+          mar.cnxn,
+          self._services,
+          mar.project_id,
+          fields_labels,
+          ezt_errors=mar.errors)
+      if mar.errors.AnyErrors():
+        raise endpoints.BadRequestException(
+            'Invalid field values: %s' % mar.errors.labels)
+
       field_helpers.ValidateCustomFields(
           mar.cnxn, self._services, fields_add, mar.config, mar.project,
           ezt_errors=mar.errors)
@@ -1190,8 +1231,7 @@
       name='components.create')
   def components_create(self, mar, request):
     """Create a component."""
-    if not mar.perms.CanUsePerm(
-        permissions.EDIT_PROJECT, mar.auth.effective_ids, mar.project, []):
+    if not permissions.CanEditProjectConfig(mar, self._services):
       raise permissions.PermissionException(
           'User is not allowed to create components for this project')
 
@@ -1207,8 +1247,8 @@
       if not parent_def:
         raise exceptions.NoSuchComponentException(
             'Parent component %s does not exist.' % parent_path)
-      if not permissions.CanEditComponentDef(
-          mar.auth.effective_ids, mar.perms, mar.project, parent_def, config):
+      if not permissions.CanEditComponentDef(mar, self._services, parent_def,
+                                             config):
         raise permissions.PermissionException(
             'User is not allowed to add a subcomponent to component %s' %
             parent_path)
@@ -1266,8 +1306,8 @@
         mar.auth.effective_ids, mar.perms, mar.project, component_def):
       raise permissions.PermissionException(
           'User is not allowed to view this component %s' % component_path)
-    if not permissions.CanEditComponentDef(
-        mar.auth.effective_ids, mar.perms, mar.project, component_def, config):
+    if not permissions.CanEditComponentDef(mar, self._services, component_def,
+                                           config):
       raise permissions.PermissionException(
           'User is not allowed to delete this component %s' % component_path)
 
@@ -1302,8 +1342,8 @@
         mar.auth.effective_ids, mar.perms, mar.project, component_def):
       raise permissions.PermissionException(
           'User is not allowed to view this component %s' % component_path)
-    if not permissions.CanEditComponentDef(
-        mar.auth.effective_ids, mar.perms, mar.project, component_def, config):
+    if not permissions.CanEditComponentDef(mar, self._services, component_def,
+                                           config):
       raise permissions.PermissionException(
           'User is not allowed to edit this component %s' % component_path)