87 lines
		
	
	
		
			2.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			87 lines
		
	
	
		
			2.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#!/usr/bin/env python3
 | 
						|
"""Ensure Authority policy client configs use the fine-grained scope set."""
 | 
						|
 | 
						|
from __future__ import annotations
 | 
						|
 | 
						|
import sys
 | 
						|
from pathlib import Path
 | 
						|
 | 
						|
EXPECTED_SCOPES = (
 | 
						|
    "policy:read",
 | 
						|
    "policy:author",
 | 
						|
    "policy:review",
 | 
						|
    "policy:simulate",
 | 
						|
    "findings:read",
 | 
						|
)
 | 
						|
 | 
						|
 | 
						|
def extract_scopes(lines: list[str], start_index: int) -> tuple[str, ...] | None:
 | 
						|
    for offset in range(1, 12):
 | 
						|
        if start_index + offset >= len(lines):
 | 
						|
            break
 | 
						|
        line = lines[start_index + offset].strip()
 | 
						|
        if not line:
 | 
						|
            continue
 | 
						|
        if line.startswith("scopes:"):
 | 
						|
            try:
 | 
						|
                raw = line.split("[", 1)[1].rsplit("]", 1)[0]
 | 
						|
            except IndexError:
 | 
						|
                return None
 | 
						|
            scopes = tuple(scope.strip().strip('"') for scope in raw.split(","))
 | 
						|
            scopes = tuple(scope for scope in scopes if scope)
 | 
						|
            return scopes
 | 
						|
    return None
 | 
						|
 | 
						|
 | 
						|
def validate(path: Path) -> list[str]:
 | 
						|
    errors: list[str] = []
 | 
						|
    try:
 | 
						|
        text = path.read_text(encoding="utf-8")
 | 
						|
    except FileNotFoundError:
 | 
						|
        return [f"{path}: missing file"]
 | 
						|
 | 
						|
    if "policy:write" in text or "policy:submit" in text:
 | 
						|
        errors.append(f"{path}: contains legacy policy scope names (policy:write/policy:submit)")
 | 
						|
 | 
						|
    lines = text.splitlines()
 | 
						|
    client_indices = [idx for idx, line in enumerate(lines) if 'clientId: "policy-cli"' in line]
 | 
						|
    if not client_indices:
 | 
						|
        errors.append(f"{path}: policy-cli client registration not found")
 | 
						|
        return errors
 | 
						|
 | 
						|
    for idx in client_indices:
 | 
						|
        scopes = extract_scopes(lines, idx)
 | 
						|
        if scopes is None:
 | 
						|
            errors.append(f"{path}: unable to parse scopes for policy-cli client")
 | 
						|
            continue
 | 
						|
        if tuple(sorted(scopes)) != tuple(sorted(EXPECTED_SCOPES)):
 | 
						|
            errors.append(
 | 
						|
                f"{path}: unexpected policy-cli scopes {scopes}; expected {EXPECTED_SCOPES}"
 | 
						|
            )
 | 
						|
 | 
						|
    return errors
 | 
						|
 | 
						|
 | 
						|
def main(argv: list[str]) -> int:
 | 
						|
    repo_root = Path(__file__).resolve().parents[1]
 | 
						|
    targets = [
 | 
						|
        repo_root / "etc" / "authority.yaml",
 | 
						|
        repo_root / "etc" / "authority.yaml.sample",
 | 
						|
    ]
 | 
						|
 | 
						|
    failures: list[str] = []
 | 
						|
    for target in targets:
 | 
						|
        failures.extend(validate(target))
 | 
						|
 | 
						|
    if failures:
 | 
						|
        for message in failures:
 | 
						|
            print(f"error: {message}", file=sys.stderr)
 | 
						|
        return 1
 | 
						|
 | 
						|
    print("policy scope verification passed")
 | 
						|
    return 0
 | 
						|
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    raise SystemExit(main(sys.argv))
 |