feat: implement DeleteCurrentUserPAT RPC by AmanGIT07 · Pull Request #1460 · raystack/frontier

Script used to verify tuples for a pat_id:

#!/usr/bin/env python3
"""
Check if any SpiceDB tuples still exist for a given PAT ID.

Usage:
  python3 scripts/check_pat_tuples.py <pat-id>
  python3 scripts/check_pat_tuples.py 34b04e9e-14ee-4bd6-9175-9787d77c1295
"""

import argparse
import subprocess
import sys


def zed_read(resource: str, relation: str, subject: str, zed_flags: list[str]) -> str:
    """Read relationships from SpiceDB using positional args.

    Usage: zed relationship read <resource_type:optional_id> <optional_relation> <optional_subject_type:optional_id>
    """
    cmd = ["zed", "relationship", "read", resource]
    if relation:
        cmd.append(relation)
    if subject:
        cmd.append(subject)
    cmd += zed_flags
    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode != 0 and result.stderr.strip():
        print(f"  zed error: {result.stderr.strip()}")
    return result.stdout.strip()


def main():
    parser = argparse.ArgumentParser(description="Check SpiceDB tuples for a PAT")
    parser.add_argument("pat_id", help="The PAT UUID to check")
    parser.add_argument("--endpoint", default="localhost:50051", help="SpiceDB gRPC endpoint")
    parser.add_argument("--token", default="frontier", help="SpiceDB preshared key")
    args = parser.parse_args()

    zed_flags = [
        "--endpoint", args.endpoint,
        "--token", args.token,
        "--insecure",
        "--consistency-full",
    ]

    pat_subject = f"app/pat:{args.pat_id}"
    found_any = False

    # 1. Check rolebinding bearer tuples: app/rolebinding:X#bearer@app/pat:<id>
    print(f"\n1. Rolebinding bearer tuples (app/rolebinding where subject = {pat_subject}):")
    rels = zed_read("app/rolebinding", "bearer", pat_subject, zed_flags)
    if rels:
        for line in rels.split("\n"):
            if line.strip():
                print(f"   {line.strip()}")
                found_any = True
    else:
        print("   (none)")

    # 2. Check grant tuples on organizations/projects for each rolebinding
    rolebinding_ids = []
    if rels:
        for line in rels.split("\n"):
            # format: app/rolebinding:<id> bearer app/pat:<pat_id>
            stripped = line.strip()
            if stripped and "app/rolebinding:" in stripped:
                rb_id = stripped.split()[0].replace("app/rolebinding:", "")
                rolebinding_ids.append(rb_id)

    if rolebinding_ids:
        print(f"\n2. Grant tuples for rolebindings ({len(rolebinding_ids)} found):")
        for rb_id in rolebinding_ids:
            rb_subject = f"app/rolebinding:{rb_id}"
            # Check org grants (granted and pat_granted relations)
            for relation in ["granted", "pat_granted"]:
                org_rels = zed_read("app/organization", relation, rb_subject, zed_flags)
                if org_rels:
                    for line in org_rels.split("\n"):
                        if line.strip():
                            print(f"   {line.strip()}")
                            found_any = True
            # Check project grants
            proj_rels = zed_read("app/project", "granted", rb_subject, zed_flags)
            if proj_rels:
                for line in proj_rels.split("\n"):
                    if line.strip():
                        print(f"   {line.strip()}")
                        found_any = True
    else:
        print(f"\n2. Grant tuples: (skipped, no rolebindings found)")

    # 3. Check role tuples: app/rolebinding:X#role@app/role:Y
    if rolebinding_ids:
        print(f"\n3. Role tuples for rolebindings:")
        for rb_id in rolebinding_ids:
            role_rels = zed_read(f"app/rolebinding:{rb_id}", "", "", zed_flags)
            if role_rels:
                for line in role_rels.split("\n"):
                    if line.strip():
                        print(f"   {line.strip()}")
                        found_any = True
    else:
        print(f"\n3. Role tuples: (skipped, no rolebindings found)")

    # Summary
    print(f"\n{'=' * 60}")
    if found_any:
        print(f"RESULT: Tuples STILL EXIST for PAT {args.pat_id}")
        sys.exit(1)
    else:
        print(f"RESULT: No tuples found for PAT {args.pat_id} - deletion successful")


if __name__ == "__main__":
    main()