from __future__ import annotations import argparse import json import sys from pathlib import Path from typing import Any ROOT = Path(__file__).resolve().parents[1] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) import scripts.compare_1c_access_role_audit as compare_script import scripts.export_1c_access_role_audit as export_script def compare_stem(old_path: Path, new_path: Path) -> str: return f"compare-{old_path.stem.replace('.summary', '')}-{new_path.stem.replace('.summary', '')}" def verdict_for(export_summary: dict[str, Any], compare_result: dict[str, Any] | None) -> str: counts = compare_result.get("counts") if isinstance(compare_result, dict) and isinstance(compare_result.get("counts"), dict) else {} if any(int(counts.get(key) or 0) > 0 for key in ("added_users", "removed_users", "changed_access_paths")): return "changed" if str(export_summary.get("risk_level") or "").lower() in {"medium", "high"}: return "risk" return "ok" def audit_role( *, adapter_url: str, base_id: str, role: str, report_root: Path, timeout: int, user_threshold: int, limit: int, include_html: bool = True, ) -> dict[str, Any]: export_summary = export_script.export_role_audit( adapter_url=adapter_url, base_id=base_id, role=role, report_root=report_root, timeout=timeout, user_threshold=user_threshold, include_analysis=True, limit=limit, include_html=include_html, ) latest = compare_script.find_latest_summaries(report_root, base_id, role=role, count=2) compare_result: dict[str, Any] | None = None if len(latest) >= 2: new_path, old_path = latest[0], latest[1] folder = report_root / compare_script.slugify(base_id, max_length=60) stem = compare_stem(old_path, new_path) compare_result = compare_script.compare_files( old_path, new_path, output=folder / f"{stem}.json", html_output=folder / f"{stem}.html", ) export_script.update_index(report_root, base_id) return { "schema": "onec_access_role_audit_run.v1", "status": export_summary.get("status"), "base_id": base_id, "role": role, "verdict": verdict_for(export_summary, compare_result), "export": export_summary, "compare": compare_result, } def audit_config( *, config_path: Path, adapter_url: str, report_root: Path, timeout: int, limit: int, include_html: bool = True, ) -> dict[str, Any]: config = json.loads(config_path.read_text(encoding="utf-8")) roles = config.get("roles") if isinstance(config.get("roles"), list) else [] default_base_id = str(config.get("base_id") or "upo_test") results: list[dict[str, Any]] = [] for item in roles: if not isinstance(item, dict) or not item.get("role"): continue results.append( audit_role( adapter_url=adapter_url, base_id=str(item.get("base_id") or default_base_id), role=str(item.get("role")), report_root=report_root, timeout=timeout, user_threshold=int(item.get("user_threshold") or 50), limit=limit, include_html=include_html, ) ) verdicts = [str(item.get("verdict") or "") for item in results] overall = "changed" if "changed" in verdicts else "risk" if "risk" in verdicts else "ok" return { "schema": "onec_access_role_audit_batch.v1", "status": "ok" if all(item.get("status") == "ok" for item in results) else "error", "config": str(config_path), "overall_verdict": overall, "counts": {"roles": len(results), "changed": verdicts.count("changed"), "risk": verdicts.count("risk"), "ok": verdicts.count("ok")}, "results": results, } def main() -> int: parser = argparse.ArgumentParser(description="Run 1C role access audit: export, analyze, update index, compare with previous.") parser.add_argument("--adapter-url", default=export_script.DEFAULT_BASE_URL) parser.add_argument("--base-id", default="upo_test") parser.add_argument("--role") parser.add_argument("--config", type=Path, help="Run all roles from an access critical roles JSON config.") parser.add_argument("--report-root", type=Path, default=export_script.DEFAULT_REPORT_ROOT) parser.add_argument("--timeout", type=int, default=120) parser.add_argument("--limit", type=int, default=20000) parser.add_argument("--user-threshold", type=int, default=50) parser.add_argument("--no-html", action="store_true") args = parser.parse_args() if args.config: result = audit_config( config_path=args.config, adapter_url=args.adapter_url, report_root=args.report_root, timeout=args.timeout, limit=args.limit, include_html=not args.no_html, ) else: if not args.role: parser.error("--role is required unless --config is used.") result = audit_role( adapter_url=args.adapter_url, base_id=args.base_id, role=args.role, report_root=args.report_root, timeout=args.timeout, user_threshold=args.user_threshold, limit=args.limit, include_html=not args.no_html, ) print(json.dumps(result, ensure_ascii=False, indent=2)) return 0 if result.get("status") == "ok" else 1 if __name__ == "__main__": raise SystemExit(main())