"""Report and narrowly repair Neo4j/PostgreSQL credential references.""" import argparse import json from sqlalchemy import text def inspect_reconciliation(graph_session, platform_session): graph_records = graph_session.run( """ MATCH (n:DataSource) RETURN n.uid AS uid, n.credential_ref AS credential_ref, n.credential_version AS credential_version, n.username IS NOT NULL OR n.password IS NOT NULL AS plaintext_remaining """ ) graph = { str(record["uid"]): dict(record) for record in graph_records if record.get("uid") } rows = platform_session.execute( text( """ SELECT data_source_uid::text AS uid, credential_version, status FROM public.datasource_credentials ORDER BY data_source_uid, credential_version """ ) ).mappings() credentials = {} for row in rows: credentials.setdefault(str(row["uid"]), []).append(dict(row)) report = [] for uid in sorted(set(graph) | set(credentials)): issues = [] node = graph.get(uid) versions = credentials.get(uid, []) active = [ row for row in versions if row["status"] == "active" ] if node is None: issues.append("orphaned_credential") else: if node.get("plaintext_remaining"): issues.append("plaintext_remaining") referenced = node.get("credential_version") matching = [ row for row in versions if row["credential_version"] == referenced ] if not matching: issues.append("missing_credential") elif matching[0]["status"] == "revoked": issues.append("revoked_credential_referenced") if active and referenced != active[-1]["credential_version"]: issues.append("stale_credential_version") if issues: report.append({"data_source_uid": uid, "issues": issues}) return report def repair_one(data_source_uid, graph_session, platform_session): row = ( platform_session.execute( text( """ SELECT credential_version FROM public.datasource_credentials WHERE data_source_uid = CAST(:uid AS uuid) AND status = 'active' ORDER BY credential_version DESC LIMIT 1 """ ), {"uid": str(data_source_uid)}, ) .mappings() .one_or_none() ) if row is None: raise ValueError("no active credential is available for repair") graph_session.run( """ MATCH (n:DataSource {uid: $uid}) SET n.credential_ref = $uid, n.credential_version = $credential_version """, { "uid": str(data_source_uid), "credential_version": int(row["credential_version"]), }, ) return { "data_source_uid": str(data_source_uid), "status": "reference_repaired", } def main(): parser = argparse.ArgumentParser( description="Reconcile DataSource credential references" ) parser.add_argument("--repair", action="store_true") parser.add_argument("--uid") args = parser.parse_args() if args.repair and not args.uid: parser.error("--repair requires --uid") from app import create_app, db from app.services.neo4j_driver import neo4j_driver app = create_app() with app.app_context(): with neo4j_driver.get_session() as graph_session: report = inspect_reconciliation(graph_session, db.session) for item in report: print(json.dumps(item, ensure_ascii=False)) if args.repair: result = repair_one(args.uid, graph_session, db.session) print(json.dumps(result, ensure_ascii=False)) if __name__ == "__main__": main()