feat(access-control-prover): 11.6 migrate the stapler recogniser through the registry

This commit is contained in:
Marius Ciepluch
2026-10-29 20:59:37 +01:00
parent 3f241bc595
commit acadf04109
7 changed files with 201 additions and 45 deletions
+1
View File
@@ -1,5 +1,6 @@
"""Entry-point mapping package: legacy recognisers plus the strategy registry."""
from . import stapler # noqa: F401 (registers the Stapler strategy at import)
from .legacy import * # noqa: F401,F403
from .legacy import _php_methods as _php_methods # re-exported internal helpers (enforcement adapter)
from .legacy import _php_routes as _php_routes
+1 -40
View File
@@ -186,7 +186,7 @@ def write_entry_points(entry_points: list[EntryPointRecord], path: Path) -> None
def _language_entry_points(target: FileTarget, text: str) -> list[EntryPointRecord]:
if target.language == "java":
return _stapler_entries(target, text) + _jetty_entries(target, text)
return _jetty_entries(target, text)
if target.language == "python":
return _python_entry_points(target, text) + _tornado_entries(target, text)
if target.language in {"javascript", "typescript"}:
@@ -522,45 +522,6 @@ def _java_methods(target: FileTarget, text: str) -> list[tuple[object, str, str]
return methods
def _stapler_entries(target: FileTarget, text: str) -> list[EntryPointRecord]:
exposed = re.compile(
r"\b(?:extends|implements)\b[^\{]*\b(?:Descriptor|Action|RootAction|Plugin|UnprotectedRootAction|AbstractDescribableImpl)\b"
)
parameters = re.compile(r"\b(?:StaplerRequest2?|StaplerResponse2?)\b|@(?:QueryParameter|AncestorInPath)\b")
records = []
for raw_node, owner, header in _java_methods(target, text):
node = cast(Any, raw_node)
name_node = node.child_by_field_name("name")
name = name_node.text.decode("utf-8")
params = node.child_by_field_name("parameters").text.decode("utf-8")
class_match = exposed.search(owner)
param_match = parameters.search(params)
if not re.search(r"\bpublic\b", header) or not re.match(r"^do[A-Z]", name) or not (class_match or param_match):
continue
public = bool(re.search(r"\bimplements\b[^\{]*\bUnprotectedRootAction\b", owner))
evidence = ["Stapler public do method"]
if class_match:
evidence.append(class_match.group())
if param_match:
evidence.append("Stapler parameter: " + param_match.group())
records.append(
_entry(
target,
node.start_point[0] + 1,
node.end_point[0] + 1,
name,
name,
"route",
"public" if public else "unknown",
"http_request",
evidence,
[],
access_declared=public,
)
)
return records
def _jetty_entries(target: FileTarget, text: str) -> list[EntryPointRecord]:
records = []
for raw_node, owner, header in _java_methods(target, text):
+68
View File
@@ -0,0 +1,68 @@
"""Task 11.6: the Stapler entry recogniser as a registered strategy.
This is a target-named strategy module: Stapler vocabulary is legitimate here
(the engine never imports this module; only the registry dispatches it). The
recognition body moved verbatim from ``mapping/legacy.py`` so existing entries
stay byte-identical; the legacy inline dispatch no longer matches this family.
"""
from __future__ import annotations
import re
from typing import TYPE_CHECKING, Any, cast
from .legacy import _entry, _java_methods
from .registry import register
if TYPE_CHECKING:
from .legacy import EntryPointRecord
class StaplerStrategy:
"""Recognise Jenkins Stapler ``do*`` handler methods on exposed types."""
identifier = "stapler"
languages: tuple[str, ...] = ("java",)
def recognize(self, target: object, text: str) -> list[EntryPointRecord]:
exposed = re.compile(
r"\b(?:extends|implements)\b[^\{]*\b(?:Descriptor|Action|RootAction|Plugin|UnprotectedRootAction|AbstractDescribableImpl)\b"
)
parameters = re.compile(r"\b(?:StaplerRequest2?|StaplerResponse2?)\b|@(?:QueryParameter|AncestorInPath)\b")
records: list[EntryPointRecord] = []
for raw_node, owner, header in _java_methods(target, text): # type: ignore[arg-type]
node = cast(Any, raw_node)
name_node = node.child_by_field_name("name")
name = name_node.text.decode("utf-8")
params = node.child_by_field_name("parameters").text.decode("utf-8")
class_match = exposed.search(owner)
param_match = parameters.search(params)
if not re.search(r"\bpublic\b", header) or not re.match(r"^do[A-Z]", name) or not (class_match or param_match):
continue
public = bool(re.search(r"\bimplements\b[^\{]*\bUnprotectedRootAction\b", owner))
evidence = ["Stapler public do method"]
if class_match:
evidence.append(class_match.group())
if param_match:
evidence.append("Stapler parameter: " + param_match.group())
records.append(
_entry(
target, # type: ignore[arg-type]
node.start_point[0] + 1,
node.end_point[0] + 1,
name,
name,
"route",
"public" if public else "unknown",
"http_request",
evidence,
[],
access_declared=public,
)
)
return records
register(StaplerStrategy())
__all__ = ["StaplerStrategy"]