mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-23 20:23:00 +00:00
fix(iam): migrate userinfo/get-account to canonical /v1/iam/oauth/userinfo (HIP-0111) (#38)
Co-authored-by: Zach Kelling <[email protected]>
This commit is contained in:
@@ -10,6 +10,7 @@ import httpx
|
||||
import jwt
|
||||
|
||||
from hanzo_iam.models import (
|
||||
OIDC_USERINFO_PATH,
|
||||
Application,
|
||||
JWTClaims,
|
||||
Organization,
|
||||
@@ -415,7 +416,7 @@ class AsyncIAMClient:
|
||||
"""
|
||||
http = await self._get_http()
|
||||
response = await http.get(
|
||||
"/api/userinfo",
|
||||
OIDC_USERINFO_PATH,
|
||||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
@@ -15,6 +15,7 @@ import httpx
|
||||
import jwt
|
||||
|
||||
from .models import (
|
||||
OIDC_USERINFO_PATH,
|
||||
Application,
|
||||
IAMConfig,
|
||||
JWTClaims,
|
||||
@@ -423,7 +424,7 @@ class IAMClient:
|
||||
UserInfo with user profile data.
|
||||
"""
|
||||
response = self.http.get(
|
||||
"/api/userinfo",
|
||||
OIDC_USERINFO_PATH,
|
||||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
)
|
||||
response.raise_for_status()
|
||||
@@ -1056,7 +1057,7 @@ class AsyncIAMClient:
|
||||
"""Get user info from userinfo endpoint."""
|
||||
http = await self._get_http()
|
||||
response = await http.get(
|
||||
"/api/userinfo",
|
||||
OIDC_USERINFO_PATH,
|
||||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
@@ -7,6 +7,8 @@ from typing import ClassVar
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from hanzo_iam.models import OIDC_USERINFO_PATH
|
||||
|
||||
|
||||
class IAMConfig(BaseModel):
|
||||
"""Configuration for Hanzo IAM client.
|
||||
@@ -85,8 +87,8 @@ class IAMConfig(BaseModel):
|
||||
|
||||
@property
|
||||
def userinfo_endpoint(self) -> str:
|
||||
"""OIDC UserInfo endpoint URL."""
|
||||
return f"{self.server_url}/api/userinfo"
|
||||
"""OIDC UserInfo endpoint URL (HIP-0111 canonical path)."""
|
||||
return f"{self.server_url}{OIDC_USERINFO_PATH}"
|
||||
|
||||
@property
|
||||
def api_base(self) -> str:
|
||||
|
||||
@@ -34,7 +34,13 @@ from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
from jwt import PyJWKClient
|
||||
|
||||
from hanzo_iam.models import IAMConfig, JWTClaims, Organization, UserInfo
|
||||
from hanzo_iam.models import (
|
||||
OIDC_USERINFO_PATH,
|
||||
IAMConfig,
|
||||
JWTClaims,
|
||||
Organization,
|
||||
UserInfo,
|
||||
)
|
||||
|
||||
# Global state
|
||||
_config: IAMConfig | None = None
|
||||
@@ -232,7 +238,7 @@ async def _fetch_user_info(token: str) -> UserInfo:
|
||||
HTTPException: If request fails
|
||||
"""
|
||||
config = get_config()
|
||||
userinfo_url = f"{config.server_url}/api/userinfo"
|
||||
userinfo_url = f"{config.server_url}{OIDC_USERINFO_PATH}"
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(
|
||||
|
||||
@@ -8,6 +8,10 @@ from typing import Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
# Canonical OIDC UserInfo path (HIP-0111). One way; no legacy /api/ prefix,
|
||||
# no Casdoor /api/get-account. Relative to the IAM server_url root.
|
||||
OIDC_USERINFO_PATH = "/v1/iam/oauth/userinfo"
|
||||
|
||||
|
||||
class Organization(str, Enum):
|
||||
"""Hanzo IAM organizations with their identity domains."""
|
||||
|
||||
@@ -201,13 +201,17 @@ def configure(url: str, client_id: str, client_secret: str, org: str):
|
||||
os.environ["IAM_CLIENT_ID"] = client_id
|
||||
os.environ["IAM_CLIENT_SECRET"] = client_secret
|
||||
os.environ["IAM_URL"] = url
|
||||
data = _iam_request("GET", "/api/get-account", auth_params=True)
|
||||
if data.get("status") != "error":
|
||||
# HIP-0111: canonical OIDC userinfo. Returns flat standard claims
|
||||
# (sub/email/...) with no Casdoor {status,msg} envelope — a 200
|
||||
# response is itself the success signal; any failure raises via
|
||||
# raise_for_status in _iam_request.
|
||||
data = _iam_request("GET", "/v1/iam/oauth/userinfo", auth_params=True)
|
||||
if "error" not in data:
|
||||
console.print("[green]✓[/green] Connected to IAM successfully")
|
||||
else:
|
||||
console.print(
|
||||
"[yellow]⚠[/yellow] Connected but got error: "
|
||||
+ data.get("msg", "unknown")
|
||||
+ data.get("error_description", data.get("error", "unknown"))
|
||||
)
|
||||
except SystemExit:
|
||||
console.print(
|
||||
|
||||
@@ -117,7 +117,7 @@ class IAMResource(SyncAPIResource):
|
||||
) -> object:
|
||||
"""Get the current authenticated account."""
|
||||
return self._get(
|
||||
"/iam/api/get-account",
|
||||
"/v1/iam/oauth/userinfo",
|
||||
options=make_request_options(
|
||||
extra_headers=extra_headers,
|
||||
extra_query=extra_query,
|
||||
@@ -137,7 +137,7 @@ class IAMResource(SyncAPIResource):
|
||||
) -> object:
|
||||
"""Get OIDC userinfo for the current session."""
|
||||
return self._get(
|
||||
"/iam/api/userinfo",
|
||||
"/v1/iam/oauth/userinfo",
|
||||
options=make_request_options(
|
||||
extra_headers=extra_headers,
|
||||
extra_query=extra_query,
|
||||
@@ -1495,7 +1495,7 @@ class AsyncIAMResource(AsyncAPIResource):
|
||||
) -> object:
|
||||
"""Get the current authenticated account."""
|
||||
return await self._get(
|
||||
"/iam/api/get-account",
|
||||
"/v1/iam/oauth/userinfo",
|
||||
options=make_request_options(
|
||||
extra_headers=extra_headers,
|
||||
extra_query=extra_query,
|
||||
@@ -1515,7 +1515,7 @@ class AsyncIAMResource(AsyncAPIResource):
|
||||
) -> object:
|
||||
"""Get OIDC userinfo for the current session."""
|
||||
return await self._get(
|
||||
"/iam/api/userinfo",
|
||||
"/v1/iam/oauth/userinfo",
|
||||
options=make_request_options(
|
||||
extra_headers=extra_headers,
|
||||
extra_query=extra_query,
|
||||
|
||||
Reference in New Issue
Block a user