| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129 |
- """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()
|