mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-05 02:41:56 +00:00
fix(oauth2): send introspection body via httpx content= not data=
_prepare_introspection_request returns a pre-encoded form body (Authlib ClientAuth may append client auth to it). The call site passed it through client.post(data=<str>), which httpx deprecates for strings (it warns 'Use content=<...> to upload raw bytes/text content') and would re-encode a mapping. Send it as raw content with the form Content-Type already set. Adds a regression test asserting the introspection POST uses content= carrying the urlencoded token (Greptile review finding).
This commit is contained in:
parent
e08b6c18d6
commit
7b1d607129
2 changed files with 50 additions and 5 deletions
|
|
@ -143,14 +143,18 @@ class Oauth2Handler:
|
|||
# OAuth2 Token Introspection (RFC 7662) - requires POST with form data
|
||||
verbose_proxy_logger.debug("Using OAuth2 introspection endpoint (POST)")
|
||||
|
||||
headers, data = Oauth2Handler._prepare_introspection_request(
|
||||
token=token,
|
||||
oauth_client_id=oauth_client_id,
|
||||
oauth_client_secret=oauth_client_secret,
|
||||
headers, introspection_body = (
|
||||
Oauth2Handler._prepare_introspection_request(
|
||||
token=token,
|
||||
oauth_client_id=oauth_client_id,
|
||||
oauth_client_secret=oauth_client_secret,
|
||||
)
|
||||
)
|
||||
|
||||
response = await client.post(
|
||||
token_info_endpoint, headers=headers, data=data
|
||||
token_info_endpoint,
|
||||
headers=headers,
|
||||
content=introspection_body,
|
||||
)
|
||||
else:
|
||||
# Generic token info endpoint - uses GET with Bearer token
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
import base64
|
||||
import os
|
||||
import sys
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
from urllib.parse import parse_qs
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, os.path.abspath("../../../.."))
|
||||
|
||||
from litellm.proxy.auth.oauth2_check import Oauth2Handler
|
||||
|
|
@ -47,3 +50,41 @@ def test_introspection_request_without_credentials_only_sends_token():
|
|||
assert "Authorization" not in headers
|
||||
parsed = parse_qs(body)
|
||||
assert parsed == {"token": ["opaque-token"]}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_check_oauth2_token_sends_introspection_body_as_raw_content(monkeypatch):
|
||||
"""The introspection POST must send the pre-encoded form body via httpx
|
||||
``content=``. ``_prepare_introspection_request`` already form-encodes the
|
||||
body (and Authlib may append client auth), so passing it through ``data=``
|
||||
would re-encode it and is deprecated for strings in httpx.
|
||||
"""
|
||||
monkeypatch.setenv(
|
||||
"OAUTH_TOKEN_INFO_ENDPOINT", "https://idp.example.com/introspect"
|
||||
)
|
||||
monkeypatch.setenv("OAUTH_CLIENT_ID", "client-id")
|
||||
monkeypatch.setenv("OAUTH_CLIENT_SECRET", "client-secret")
|
||||
|
||||
response = MagicMock()
|
||||
response.raise_for_status = MagicMock()
|
||||
response.json = MagicMock(
|
||||
return_value={"active": True, "sub": "u1", "role": "internal_user"}
|
||||
)
|
||||
|
||||
client = MagicMock()
|
||||
client.post = AsyncMock(return_value=response)
|
||||
|
||||
with (
|
||||
patch("litellm.proxy.proxy_server.premium_user", True),
|
||||
patch(
|
||||
"litellm.proxy.auth.oauth2_check.get_async_httpx_client",
|
||||
return_value=client,
|
||||
),
|
||||
):
|
||||
await Oauth2Handler.check_oauth2_token("opaque-token")
|
||||
|
||||
assert client.post.await_count == 1
|
||||
kwargs = client.post.await_args.kwargs
|
||||
assert "data" not in kwargs
|
||||
assert isinstance(kwargs["content"], str)
|
||||
assert "token=opaque-token" in kwargs["content"]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue