Merge pull request 'test: forward-direction issue-comment permission separation (#137 follow-up)' (#140) from tests/issue-137-permission-separation into master

This commit was merged in pull request #140.
This commit is contained in:
2026-07-05 01:47:26 -05:00
+96
View File
@@ -35,6 +35,7 @@ from mcp_server import ( # noqa: E402
gitea_create_issue_comment,
)
from gitea_auth import get_profile # noqa: E402
import gitea_config # noqa: E402
FAKE_AUTH = "Basic dGVzdDp0ZXN0"
@@ -2186,3 +2187,98 @@ class TestIssueCommentTools(unittest.TestCase):
with self.assertRaises(ValueError):
gitea_create_issue_comment(
issue_number=9, body="x", remote="nope")
# ---------------------------------------------------------------------------
# Issue-comment permission separation (#137 follow-up; #139 groundwork)
# ---------------------------------------------------------------------------
class TestIssueCommentPermissionSeparation(unittest.TestCase):
"""gitea.issue.comment grants exactly issue-thread commenting.
#138 added the ``issue.comment`` alias and granted it in the example
configs. These are the forward-direction separation guards: holding the
permission must never imply issue close, PR review/approve/merge, or
branch push / repo commit — and the shipped examples must keep granting
it so the live-config migration gap (#137) cannot silently regress.
"""
ISSUE_COMMENT_ONLY = ["gitea.issue.comment"]
def test_issue_comment_does_not_imply_issue_close(self):
ok, reason = gitea_config.check_operation(
"gitea.issue.close", self.ISSUE_COMMENT_ONLY)
self.assertFalse(ok)
self.assertEqual(reason, "not-allowed")
def test_issue_comment_does_not_imply_review_or_merge(self):
for op in ("gitea.pr.review", "gitea.pr.approve", "gitea.pr.merge",
"gitea.pr.request_changes"):
ok, _ = gitea_config.check_operation(op, self.ISSUE_COMMENT_ONLY)
self.assertFalse(ok, msg=f"{op} must not be implied")
def test_issue_comment_does_not_imply_authoring_ops(self):
for op in ("gitea.branch.push", "gitea.repo.commit",
"gitea.branch.create", "gitea.pr.create"):
ok, _ = gitea_config.check_operation(op, self.ISSUE_COMMENT_ONLY)
self.assertFalse(ok, msg=f"{op} must not be implied")
def test_pr_comment_does_not_imply_issue_comment(self):
ok, reason = gitea_config.check_operation(
"gitea.issue.comment", ["gitea.read", "gitea.pr.comment"])
self.assertFalse(ok)
self.assertEqual(reason, "not-allowed")
def test_pre_137_author_op_set_fails_closed(self):
# Exactly the live prgs-author op set that blocked the #51 audit
# comment before the #137/#138 migration.
env = {"GITEA_PROFILE_NAME": "gitea-author",
"GITEA_ALLOWED_OPERATIONS":
"gitea.branch.create,gitea.branch.push,gitea.pr.comment,"
"gitea.pr.create,gitea.read,gitea.repo.commit"}
with patch.dict(os.environ, env, clear=True):
result = gitea_create_issue_comment(
issue_number=51, body="audit findings", remote="prgs")
self.assertFalse(result["success"])
self.assertIn("profile is not allowed to gitea.issue.comment",
result["reasons"])
# -- shipped examples must keep granting the op --------------------------
@staticmethod
def _example(name):
path = os.path.join(os.path.dirname(__file__), "..", name)
with open(path, encoding="utf-8") as fh:
return json.load(fh)
def test_v2_example_profiles_grant_issue_comment(self):
cfg = self._example("gitea-mcp.v2-contexts.example.json")
for name, profile in cfg["profiles"].items():
allowed = profile.get("allowed_operations", [])
ok, _ = gitea_config.check_operation(
"gitea.issue.comment", allowed,
profile.get("forbidden_operations", []))
self.assertTrue(
ok, msg=f"example profile {name} must grant issue comments")
def test_v2_example_preserves_role_separation(self):
cfg = self._example("gitea-mcp.v2-contexts.example.json")
author = cfg["profiles"]["example-author"]
reviewer = cfg["profiles"]["example-reviewer"]
for op in ("gitea.pr.approve", "gitea.pr.merge"):
ok, _ = gitea_config.check_operation(
op, author["allowed_operations"],
author.get("forbidden_operations", []))
self.assertFalse(ok, msg=f"author example must not allow {op}")
for op in ("gitea.branch.push", "gitea.repo.commit"):
ok, _ = gitea_config.check_operation(
op, reviewer["allowed_operations"],
reviewer.get("forbidden_operations", []))
self.assertFalse(ok, msg=f"reviewer example must not allow {op}")
def test_v1_example_reviewer_grants_issue_comment(self):
cfg = self._example("gitea-mcp.example.json")
reviewer = cfg["profiles"]["mdcps-reviewer"]
ok, _ = gitea_config.check_operation(
"gitea.issue.comment", reviewer["allowed_operations"],
reviewer.get("forbidden_operations", []))
self.assertTrue(ok)