test_media_scope_matches_media_gate.py 1.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243
  1. """A route takes its printers from a media token only if its permission check does too."""
  2. from __future__ import annotations
  3. import ast
  4. from pathlib import Path
  5. API = Path(__file__).resolve().parents[2] / "app" / "api"
  6. MEDIA_SCOPE = {"MediaOrRequestPrinterScope", "get_media_or_request_printer_scope"}
  7. HEADER_PRINCIPAL = {"RequestPrinterScope", "get_printer_scope_if_auth_enabled", "RequestActor"}
  8. def _names(node: ast.AST) -> set[str]:
  9. found = set()
  10. for sub in ast.walk(node):
  11. if isinstance(sub, ast.Name):
  12. found.add(sub.id)
  13. elif isinstance(sub, ast.Attribute):
  14. found.add(sub.attr)
  15. return found
  16. def _route_dependencies(path: Path):
  17. for node in ast.walk(ast.parse(path.read_text())):
  18. if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) or not node.decorator_list:
  19. continue
  20. args = node.args
  21. parts = [*args.defaults, *[d for d in args.kw_defaults if d is not None]]
  22. parts += [a.annotation for a in [*args.args, *args.kwonlyargs] if a.annotation is not None]
  23. names = set().union(*(_names(p) for p in parts)) if parts else set()
  24. yield f"{path.relative_to(API)}:{node.name}", names
  25. def test_a_route_takes_its_caller_from_one_place():
  26. offenders = []
  27. for path in sorted(API.rglob("*.py")):
  28. for route, names in _route_dependencies(path):
  29. media_gate = any(name.startswith("require_media_token_") for name in names)
  30. if names & MEDIA_SCOPE and not media_gate:
  31. offenders.append(f"{route}: media-token printers without a media-token check")
  32. if media_gate and names & HEADER_PRINCIPAL:
  33. offenders.append(f"{route}: media-token check with a header-based caller")
  34. assert offenders == []