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
|
import jwt
|
||||||
|
|
||||||
from hanzo_iam.models import (
|
from hanzo_iam.models import (
|
||||||
|
OIDC_USERINFO_PATH,
|
||||||
Application,
|
Application,
|
||||||
JWTClaims,
|
JWTClaims,
|
||||||
Organization,
|
Organization,
|
||||||
@@ -415,7 +416,7 @@ class AsyncIAMClient:
|
|||||||
"""
|
"""
|
||||||
http = await self._get_http()
|
http = await self._get_http()
|
||||||
response = await http.get(
|
response = await http.get(
|
||||||
"/api/userinfo",
|
OIDC_USERINFO_PATH,
|
||||||
headers={"Authorization": f"Bearer {access_token}"},
|
headers={"Authorization": f"Bearer {access_token}"},
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import httpx
|
|||||||
import jwt
|
import jwt
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
|
OIDC_USERINFO_PATH,
|
||||||
Application,
|
Application,
|
||||||
IAMConfig,
|
IAMConfig,
|
||||||
JWTClaims,
|
JWTClaims,
|
||||||
@@ -423,7 +424,7 @@ class IAMClient:
|
|||||||
UserInfo with user profile data.
|
UserInfo with user profile data.
|
||||||
"""
|
"""
|
||||||
response = self.http.get(
|
response = self.http.get(
|
||||||
"/api/userinfo",
|
OIDC_USERINFO_PATH,
|
||||||
headers={"Authorization": f"Bearer {access_token}"},
|
headers={"Authorization": f"Bearer {access_token}"},
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
@@ -1056,7 +1057,7 @@ class AsyncIAMClient:
|
|||||||
"""Get user info from userinfo endpoint."""
|
"""Get user info from userinfo endpoint."""
|
||||||
http = await self._get_http()
|
http = await self._get_http()
|
||||||
response = await http.get(
|
response = await http.get(
|
||||||
"/api/userinfo",
|
OIDC_USERINFO_PATH,
|
||||||
headers={"Authorization": f"Bearer {access_token}"},
|
headers={"Authorization": f"Bearer {access_token}"},
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ from typing import ClassVar
|
|||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
|
|
||||||
|
from hanzo_iam.models import OIDC_USERINFO_PATH
|
||||||
|
|
||||||
|
|
||||||
class IAMConfig(BaseModel):
|
class IAMConfig(BaseModel):
|
||||||
"""Configuration for Hanzo IAM client.
|
"""Configuration for Hanzo IAM client.
|
||||||
@@ -85,8 +87,8 @@ class IAMConfig(BaseModel):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def userinfo_endpoint(self) -> str:
|
def userinfo_endpoint(self) -> str:
|
||||||
"""OIDC UserInfo endpoint URL."""
|
"""OIDC UserInfo endpoint URL (HIP-0111 canonical path)."""
|
||||||
return f"{self.server_url}/api/userinfo"
|
return f"{self.server_url}{OIDC_USERINFO_PATH}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def api_base(self) -> str:
|
def api_base(self) -> str:
|
||||||
|
|||||||
@@ -34,7 +34,13 @@ from fastapi import Depends, HTTPException, status
|
|||||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||||
from jwt import PyJWKClient
|
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
|
# Global state
|
||||||
_config: IAMConfig | None = None
|
_config: IAMConfig | None = None
|
||||||
@@ -232,7 +238,7 @@ async def _fetch_user_info(token: str) -> UserInfo:
|
|||||||
HTTPException: If request fails
|
HTTPException: If request fails
|
||||||
"""
|
"""
|
||||||
config = get_config()
|
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:
|
async with httpx.AsyncClient() as client:
|
||||||
response = await client.get(
|
response = await client.get(
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ from typing import Any
|
|||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field
|
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):
|
class Organization(str, Enum):
|
||||||
"""Hanzo IAM organizations with their identity domains."""
|
"""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_ID"] = client_id
|
||||||
os.environ["IAM_CLIENT_SECRET"] = client_secret
|
os.environ["IAM_CLIENT_SECRET"] = client_secret
|
||||||
os.environ["IAM_URL"] = url
|
os.environ["IAM_URL"] = url
|
||||||
data = _iam_request("GET", "/api/get-account", auth_params=True)
|
# HIP-0111: canonical OIDC userinfo. Returns flat standard claims
|
||||||
if data.get("status") != "error":
|
# (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")
|
console.print("[green]✓[/green] Connected to IAM successfully")
|
||||||
else:
|
else:
|
||||||
console.print(
|
console.print(
|
||||||
"[yellow]⚠[/yellow] Connected but got error: "
|
"[yellow]⚠[/yellow] Connected but got error: "
|
||||||
+ data.get("msg", "unknown")
|
+ data.get("error_description", data.get("error", "unknown"))
|
||||||
)
|
)
|
||||||
except SystemExit:
|
except SystemExit:
|
||||||
console.print(
|
console.print(
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ class IAMResource(SyncAPIResource):
|
|||||||
) -> object:
|
) -> object:
|
||||||
"""Get the current authenticated account."""
|
"""Get the current authenticated account."""
|
||||||
return self._get(
|
return self._get(
|
||||||
"/iam/api/get-account",
|
"/v1/iam/oauth/userinfo",
|
||||||
options=make_request_options(
|
options=make_request_options(
|
||||||
extra_headers=extra_headers,
|
extra_headers=extra_headers,
|
||||||
extra_query=extra_query,
|
extra_query=extra_query,
|
||||||
@@ -137,7 +137,7 @@ class IAMResource(SyncAPIResource):
|
|||||||
) -> object:
|
) -> object:
|
||||||
"""Get OIDC userinfo for the current session."""
|
"""Get OIDC userinfo for the current session."""
|
||||||
return self._get(
|
return self._get(
|
||||||
"/iam/api/userinfo",
|
"/v1/iam/oauth/userinfo",
|
||||||
options=make_request_options(
|
options=make_request_options(
|
||||||
extra_headers=extra_headers,
|
extra_headers=extra_headers,
|
||||||
extra_query=extra_query,
|
extra_query=extra_query,
|
||||||
@@ -1495,7 +1495,7 @@ class AsyncIAMResource(AsyncAPIResource):
|
|||||||
) -> object:
|
) -> object:
|
||||||
"""Get the current authenticated account."""
|
"""Get the current authenticated account."""
|
||||||
return await self._get(
|
return await self._get(
|
||||||
"/iam/api/get-account",
|
"/v1/iam/oauth/userinfo",
|
||||||
options=make_request_options(
|
options=make_request_options(
|
||||||
extra_headers=extra_headers,
|
extra_headers=extra_headers,
|
||||||
extra_query=extra_query,
|
extra_query=extra_query,
|
||||||
@@ -1515,7 +1515,7 @@ class AsyncIAMResource(AsyncAPIResource):
|
|||||||
) -> object:
|
) -> object:
|
||||||
"""Get OIDC userinfo for the current session."""
|
"""Get OIDC userinfo for the current session."""
|
||||||
return await self._get(
|
return await self._get(
|
||||||
"/iam/api/userinfo",
|
"/v1/iam/oauth/userinfo",
|
||||||
options=make_request_options(
|
options=make_request_options(
|
||||||
extra_headers=extra_headers,
|
extra_headers=extra_headers,
|
||||||
extra_query=extra_query,
|
extra_query=extra_query,
|
||||||
|
|||||||
Reference in New Issue
Block a user