Merge branch 'main' into avm99963-monorail

Merged commit 34d8229ae2b51fb1a15bd208e6fe6185c94f6266

GitOrigin-RevId: 7ee0917f93a577e475f8e09526dd144d245593f4
diff --git a/framework/permissions.py b/framework/permissions.py
index ac46af6..37f6cdc 100644
--- a/framework/permissions.py
+++ b/framework/permissions.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 and functions to implement permission checking.
 
@@ -32,10 +31,10 @@
 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 tracker_pb2
-from proto import usergroup_pb2
+from mrproto import project_pb2
+from mrproto import site_pb2
+from mrproto import tracker_pb2
+from mrproto import usergroup_pb2
 from tracker import tracker_bizobj
 
 # Constants that define permissions.
@@ -333,40 +332,53 @@
 
     # Project owners can view and edit artifacts in a LIVE project.
     (OWNER_ROLE, project_pb2.ProjectState.LIVE, WILDCARD_ACCESS):
-      OWNER_ACTIVE_PERMISSIONSET,
+        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,
+        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_ACTIVE_PERMISSIONSET,
     (COMMITTER_ROLE, project_pb2.ProjectState.ARCHIVED, WILDCARD_ACCESS):
-      COMMITTER_INACTIVE_PERMISSIONSET,
+        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_ACTIVE_PERMISSIONSET,
     (CONTRIBUTOR_ROLE, project_pb2.ProjectState.ARCHIVED, WILDCARD_ACCESS):
-      CONTRIBUTOR_INACTIVE_PERMISSIONSET,
+        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,
+    # 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,
+    # Non-members users can read archived projects with access == ANYONE.
+    (
+        USER_ROLE, project_pb2.ProjectState.ARCHIVED,
+        project_pb2.ProjectAccess.ANYONE):
+        READ_ONLY_PERMISSIONSET,
+
+    # Anonymous users can only read projects with access == ANYONE,
+    # regardless of state.
+    (
+        ANON_ROLE, project_pb2.ProjectState.LIVE,
+        project_pb2.ProjectAccess.ANYONE):
+        READ_ONLY_PERMISSIONSET,
+    (
+        ANON_ROLE, project_pb2.ProjectState.ARCHIVED,
+        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, CREATE_HOTLIST]),
-    }
+        PermissionSet([CREATE_PROJECT, CREATE_GROUP, CREATE_HOTLIST]),
+}
 
 def GetPermissions(user, effective_ids, project):
   """Return a permission set appropriate for the user and project.
@@ -428,7 +440,7 @@
 
 def UpdateIssuePermissions(
     perms, project, issue, effective_ids, granted_perms=None, config=None):
-  """Update the PermissionSet for an specific issue.
+  """Update the PermissionSet for a specific issue.
 
   Take into account granted permissions and label restrictions to filter the
   permissions, and updates the VIEW and EDIT_ISSUE permissions depending on the
@@ -488,7 +500,7 @@
   filtered_perms.update(granted_perms)
 
   # The VIEW perm might have been removed due to restrictions, but the issue
-  # owner, reporter, cc and approvers can always be an issue.
+  # owner, reporter, cc and approvers can always view an issue.
   allowed_ids = set(
       tracker_bizobj.GetCcIds(issue)
       + tracker_bizobj.GetApproverIds(issue)
@@ -507,7 +519,8 @@
 
   # The EDIT_ISSUE permission might have been removed due to restrictions, but
   # the owner always has permission to edit it.
-  if effective_ids and tracker_bizobj.GetOwnerId(issue) in effective_ids:
+  if (effective_ids and tracker_bizobj.GetOwnerId(issue) in effective_ids and
+      project and project.state != project_pb2.ProjectState.ARCHIVED):
     filtered_perms.add(EDIT_ISSUE.lower())
 
   return PermissionSet(filtered_perms, perms.consider_restrictions)
@@ -1113,6 +1126,36 @@
   return perms.CanUsePerm(EDIT_ISSUE_APPROVAL, effective_ids, project, [])
 
 
+def CanEditProjectConfig(mr, services):
+  """ Special function to check if a user can edit a project config.
+
+  This function accounts for special edge cases pertaining only to project
+  configuration editing permissions, such as checking if a project is frozen
+  for config edits or if a user is in the allowlist of users who can override
+  a config freeze.
+
+  Args:
+    mr: MonorailRequest object.
+    services: reference to database layer.
+
+  Returns:
+    True if the user can edit the project.
+  """
+  if mr.project.project_id not in settings.config_freeze_project_ids:
+    return mr.perms.CanUsePerm(
+        EDIT_PROJECT, mr.auth.effective_ids, mr.project, [])
+
+  effective_users = services.user.GetUsersByIDs(
+      mr.cnxn, list(mr.auth.effective_ids))
+
+  for _, user in effective_users.items():
+    if user.email in settings.config_freeze_override_users.get(
+        mr.project.project_id, {}):
+      return True
+
+  return False
+
+
 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):
@@ -1122,8 +1165,53 @@
   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."""
+def CanEditComponentDef(mr, services, component_def, config):
+  """ Checks if the currently logged in user can edit a component.
+
+  Args:
+    mr: MonorailRequest object.
+    services: reference to database layer.
+    component_def: the component to check permissions for.
+    config: project config of the project the component is in.
+
+  Returns:
+    True if a user can edit the given component definition."""
+  if mr.project.project_id in settings.config_freeze_project_ids:
+    return CanEditProjectConfig(mr, services)
+
+  if not mr.auth.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 mr.auth.effective_ids.isdisjoint(parent.admin_ids):
+      return True
+
+  return CanEditProjectConfig(mr, services)
+
+
+def CanEditComponentDefLegacy(
+    effective_ids, perms, project, component_def, config):
+  """ Legacy version of CanEditComponentDef for codepaths without access to mr.
+  This function is entirely used in API clients.
+
+  Args:
+    effective_ids: Set containing IDs for the user and their groups
+      linked accounts, etc.
+    perms: PermissionSet for current user.
+    project: the project the component is in.
+    component_def: the component to check permissions for.
+    config: project config of the project the component is in.
+
+  Returns:
+    True if a user can edit the given component definition."""
+  # Do not bother checking if API client users are allowlisted to override
+  # the config freeze. Only human users are currently being allowlisted.
+  if project and project.project_id in settings.config_freeze_project_ids:
+    return False
+
   if not effective_ids.isdisjoint(component_def.admin_ids):
     return True  # Component admins can edit that component.