108 lines
3.1 KiB
Python
108 lines
3.1 KiB
Python
"""CI-Gate: Stelle sicher, dass keine Verweise auf die abgeschaffte
|
|
``sysadmin``-Rolle bzw. die alten Helper im Codebase mehr existieren.
|
|
|
|
Verbotene Symbole nach Abschluss von ``2026-04-sysadmin-authority-split``:
|
|
|
|
- ``hasSysAdminRole`` (RequestContext property)
|
|
- ``requireSysAdminRole`` (FastAPI dependency)
|
|
- ``_hasSysAdminRole`` (Hilfs-Funktion gegen die alte Rolle)
|
|
|
|
Erlaubt sind weiterhin:
|
|
|
|
- ``isSysAdmin`` (User-Flag fuer Infrastruktur-Operator)
|
|
- ``isPlatformAdmin`` (User-Flag fuer Cross-Mandate-Governance)
|
|
- ``requireSysAdmin`` / ``requirePlatformAdmin`` (FastAPI Dependencies)
|
|
|
|
Exit-Code:
|
|
|
|
- 0 -> sauber
|
|
- 1 -> Fundstellen vorhanden (CI bricht ab)
|
|
|
|
Aufruf::
|
|
|
|
python gateway/scripts/check_no_sysadmin_role.py
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Iterable, List, Tuple
|
|
|
|
_REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
_FORBIDDEN_PATTERNS: Tuple[Tuple[str, str], ...] = (
|
|
(r"\bhasSysAdminRole\b", "Use ctx.isPlatformAdmin (Governance) or ctx.isSysAdmin (Infra)"),
|
|
(r"\brequireSysAdminRole\b", "Use requirePlatformAdmin (Governance) or requireSysAdmin (Infra)"),
|
|
(r"\b_hasSysAdminRole\b", "Use User.isPlatformAdmin flag check directly"),
|
|
)
|
|
|
|
_INCLUDE_SUFFIXES = {".py", ".ts", ".tsx", ".js", ".jsx"}
|
|
|
|
_EXCLUDE_DIR_NAMES = {
|
|
".git",
|
|
"node_modules",
|
|
"dist",
|
|
"build",
|
|
"__pycache__",
|
|
".venv",
|
|
"venv",
|
|
".pytest_cache",
|
|
".mypy_cache",
|
|
"wiki",
|
|
"scripts",
|
|
}
|
|
|
|
|
|
def _shouldScan(path: Path) -> bool:
|
|
if path.suffix not in _INCLUDE_SUFFIXES:
|
|
return False
|
|
parts = set(path.parts)
|
|
if parts & _EXCLUDE_DIR_NAMES:
|
|
return False
|
|
return True
|
|
|
|
|
|
def _iterFiles(root: Path) -> Iterable[Path]:
|
|
for dirpath, dirnames, filenames in os.walk(root):
|
|
dirnames[:] = [d for d in dirnames if d not in _EXCLUDE_DIR_NAMES]
|
|
for name in filenames:
|
|
full = Path(dirpath) / name
|
|
if _shouldScan(full):
|
|
yield full
|
|
|
|
|
|
def _scanFile(path: Path) -> List[Tuple[int, str, str, str]]:
|
|
findings: List[Tuple[int, str, str, str]] = []
|
|
try:
|
|
text = path.read_text(encoding="utf-8", errors="ignore")
|
|
except Exception:
|
|
return findings
|
|
for pattern, hint in _FORBIDDEN_PATTERNS:
|
|
compiled = re.compile(pattern)
|
|
for lineNo, line in enumerate(text.splitlines(), start=1):
|
|
if compiled.search(line):
|
|
findings.append((lineNo, pattern, hint, line.rstrip()))
|
|
return findings
|
|
|
|
|
|
def _main() -> int:
|
|
findings = []
|
|
for filePath in _iterFiles(_REPO_ROOT):
|
|
for entry in _scanFile(filePath):
|
|
findings.append((filePath, *entry))
|
|
if not findings:
|
|
print("[OK] No legacy sysadmin-role references found.")
|
|
return 0
|
|
print("[FAIL] Found legacy sysadmin-role references:")
|
|
for filePath, lineNo, pattern, hint, line in findings:
|
|
rel = filePath.relative_to(_REPO_ROOT)
|
|
print(f" {rel}:{lineNo}: {pattern}\n hint: {hint}\n line: {line}")
|
|
print(f"\n[FAIL] {len(findings)} forbidden reference(s).")
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(_main())
|