Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions pkgs/standards/auto_authn/auto_authn/v2/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,18 +109,20 @@ async def oidc_config():
"token id_token",
"code token id_token",
]
return {
config = {
"issuer": ISSUER,
"authorization_endpoint": f"{ISSUER}/authorize",
"token_endpoint": f"{ISSUER}/token",
"userinfo_endpoint": f"{ISSUER}/userinfo",

"jwks_uri": f"{ISSUER}{JWKS_PATH}",
"scopes_supported": scopes,
"response_types_supported": response_types,
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["RS256"],
"claims_supported": claims,
}
if settings.enable_rfc7591:
config["registration_endpoint"] = f"{ISSUER}/clients"
config["registration_endpoint"] = f"{ISSUER}/register"
return config


Expand Down
9 changes: 7 additions & 2 deletions pkgs/standards/auto_authn/auto_authn/v2/rfc7591.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from pydantic import AnyHttpUrl, BaseModel, ConfigDict, Field

from .runtime_cfg import settings
from .rfc8252 import validate_native_redirect_uri

# In-memory registry of dynamically registered clients
_CLIENT_REGISTRY: Dict[str, dict] = {}
Expand Down Expand Up @@ -63,12 +64,16 @@ def register_client(metadata: dict, *, enabled: bool | None = None) -> dict:

client_id = secrets.token_urlsafe(16)
client_secret = secrets.token_urlsafe(32)
if settings.enforce_rfc8252:
for uri in metadata.get("redirect_uris", []):
validate_native_redirect_uri(str(uri))

data = {"client_id": client_id, "client_secret": client_secret, **metadata}
_CLIENT_REGISTRY[client_id] = data
return data


@router.post("/clients", status_code=status.HTTP_201_CREATED)
@router.post("/register", status_code=status.HTTP_201_CREATED)
async def register_client_endpoint(body: ClientMetadata) -> dict:
"""HTTP endpoint implementing OAuth 2.0 Dynamic Client Registration."""

Expand All @@ -94,7 +99,7 @@ def include_rfc7591(app: FastAPI) -> None:
"""Attach the RFC 7591 router to *app* if enabled."""

if settings.enable_rfc7591 and not any(
route.path == "/clients" for route in app.routes
route.path == "/register" for route in app.routes
):
app.include_router(router)

Expand Down
2 changes: 1 addition & 1 deletion pkgs/standards/auto_authn/auto_authn/v2/rfc8932.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def get_enhanced_authorization_server_metadata() -> Dict[str, Any]:
"code_challenge_methods_supported": ["S256"],
}
if settings.enable_rfc7591:
base_metadata["registration_endpoint"] = f"{ISSUER}/clients"
base_metadata["registration_endpoint"] = f"{ISSUER}/register"

# Enhanced metadata extensions
enhanced_metadata = {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@
@pytest.mark.unit
@pytest.mark.asyncio
async def test_rfc7591_client_registration_endpoint(monkeypatch) -> None:
"""Posting RFC 7591 client metadata to `/clients` registers the client."""
"""Posting RFC 7591 client metadata to `/register` registers the client."""
app = FastAPI()
monkeypatch.setattr(settings, "enable_rfc7591", True)
include_rfc7591(app)
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
payload = {"redirect_uris": ["https://client.example/cb"]}
resp = await client.post("/clients", json=payload)
payload = {"redirect_uris": ["http://127.0.0.1/cb"]}
resp = await client.post("/register", json=payload)
assert resp.status_code == status.HTTP_201_CREATED
data = resp.json()
assert data["redirect_uris"] == payload["redirect_uris"]
Expand All @@ -34,6 +34,6 @@ async def test_rfc7591_client_registration_disabled(monkeypatch) -> None:
include_rfc7591(app)
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
payload = {"redirect_uris": ["https://client.example/cb"]}
resp = await client.post("/clients", json=payload)
payload = {"redirect_uris": ["http://127.0.0.1/cb"]}
resp = await client.post("/register", json=payload)
assert resp.status_code == status.HTTP_404_NOT_FOUND
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ def test_register_client_when_enabled() -> None:

with patch.object(rfc7591.settings, "enable_rfc7591", True):
rfc7591.reset_client_registry()
client = rfc7591.register_client({"redirect_uris": ["https://a.example/cb"]})
client = rfc7591.register_client({"redirect_uris": ["http://127.0.0.1/cb"]})
assert "client_id" in client
stored = rfc7591.get_client(client["client_id"])
assert stored is not None
assert stored["redirect_uris"] == ["https://a.example/cb"]
assert stored["redirect_uris"] == ["http://127.0.0.1/cb"]


def test_register_client_disabled_raises() -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ async def test_rfc7592_client_management_not_implemented(monkeypatch) -> None:
include_rfc7591(app)
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
resp = await client.get("/clients/some-client-id")
resp = await client.get("/register/some-client-id")
assert resp.status_code == status.HTTP_404_NOT_FOUND