Get an org-scoped ``otoat_`` token, exchanging the ``otuat_`` if needed. 1. Check cache in ``~/.opentrace/org_tokens/{org_id}.json`` 2. If missing/expired, exchange ``otuat_`` + org via API 3. Cache and return Raises :class:`RuntimeError` if not logged in or exchange fails.
(org_id: str)
| 462 | |
| 463 | |
| 464 | def resolve_org_token(org_id: str) -> dict[str, Any]: |
| 465 | """Get an org-scoped ``otoat_`` token, exchanging the ``otuat_`` if needed. |
| 466 | |
| 467 | 1. Check cache in ``~/.opentrace/org_tokens/{org_id}.json`` |
| 468 | 2. If missing/expired, exchange ``otuat_`` + org via API |
| 469 | 3. Cache and return |
| 470 | |
| 471 | Raises :class:`RuntimeError` if not logged in or exchange fails. |
| 472 | """ |
| 473 | from opentrace_agent.cli.credentials import load_org_token, save_org_token |
| 474 | |
| 475 | # Check cache |
| 476 | cached = load_org_token(org_id) |
| 477 | if cached is not None: |
| 478 | return cached |
| 479 | |
| 480 | # Load user token |
| 481 | user_tokens = load_tokens() |
| 482 | if not user_tokens or not user_tokens.get("access_token"): |
| 483 | raise RuntimeError("Not logged in. Run 'opentraceai login' first.") |
| 484 | |
| 485 | user_token = user_tokens["access_token"] |
| 486 | |
| 487 | # Handle legacy: if user has an old otoat_ token from before this change |
| 488 | if user_token.startswith("otoat_"): |
| 489 | raise RuntimeError( |
| 490 | "Your login token is org-scoped (legacy format). " |
| 491 | "Run 'opentraceai logout' then 'opentraceai login' to get a user token." |
| 492 | ) |
| 493 | |
| 494 | # Exchange otuat_ for org-scoped otoat_ |
| 495 | disco = discover() |
| 496 | issuer = disco.get("issuer", ISSUER) |
| 497 | exchange_url = f"{issuer}/oauth/exchange-org-token" |
| 498 | |
| 499 | body = json.dumps({"org": org_id}).encode() |
| 500 | req = urllib.request.Request( |
| 501 | exchange_url, |
| 502 | data=body, |
| 503 | headers={ |
| 504 | "Content-Type": "application/json", |
| 505 | "Accept": "application/json", |
| 506 | "Authorization": f"Bearer {user_token}", |
| 507 | }, |
| 508 | ) |
| 509 | |
| 510 | try: |
| 511 | with urllib.request.urlopen(req, timeout=30) as resp: |
| 512 | result = json.loads(resp.read()) |
| 513 | except urllib.error.HTTPError as exc: |
| 514 | error_body = exc.read().decode("utf-8", errors="replace") |
| 515 | if exc.code == 404: |
| 516 | raise RuntimeError( |
| 517 | f"Organization '{org_id}' not found. Check your .opentrace/config.yaml org value." |
| 518 | ) from exc |
| 519 | if exc.code == 401: |
| 520 | raise RuntimeError("User token expired or invalid. Run 'opentraceai login' to re-authenticate.") from exc |
| 521 | raise RuntimeError(f"Org token exchange failed ({exc.code}): {error_body}") from exc |
no test coverage detected