`get` method of `aioauth.collections.HTTPHeaderDict` class is not case-insensitive
Hi aioauth Team,
The get method of the class aioauth.collections.HTTPHeaderDict is not case-insensitive. Due to this, the create_token_response method of the aioauth.server.AuthorizationServer is unable to authenticate the client based on the Authorization header passed in to the token endpoint (ie. whenever client is authenticate using HTTP basic). The issue is caused indeed because the get method of HTTPHeaderDict is not case-insensitive that makes this following line to retrieve an empty string:
https://github.com/aliev/aioauth/blob/7a8ce1090eab11e207853e7f30c77f2726a25b43/aioauth/server.py#L218
The solution is as easy as override the following methods inherited from UserDict when defining HTTPHeaderDict. This is indeed the patched version of HTTPHeaderDict I am using:
class PatchedHTTPHeaderDict(HTTPHeaderDict):
"""Patch version of class `HTTPHeaderDict`."""
def __init__(self, **kw: t.Any) -> None:
"""Object initialization."""
super().__init__(**{k.lower(): v for k, v in kw.items()})
def __delitem__(self, key: str) -> None:
"""Item deletion."""
return super().__delitem__(key.lower())
def get(self, key: str, default: t.Any = None) -> t.Any:
"""Case-insentive get."""
try:
return self[key]
except KeyError:
return default
Expected Result
I was expected the code line above to retrieve the HTTP basic value from Authorization header as passed in to the original token request.
Actual Result
The code line above results in a empty string.
Reproduction Steps
Authenticate the client using HTTP Basic when calling the token endpoint. For example, like in this pytest function I have in my project where I am using the starlette.testclient.TestClient as a test http client:
class TestAuthorizationCodeGrant:
"""Test cases for authorization code grant flow."""
def test_successful_flow(
self,
fastplay_client: "TestClient",
client_info: ClientInfoTuple,
request_state: str,
http_basic: "HTTPBasicFixture",
) -> None:
"""Test a successful OAuth 2.0 authorization code flow."""
# Authorization request
authorization_response = fastplay_client.get(
"/auth/oauth2/authorize",
params={
"response_type": "code",
"client_id": client_info.client_id,
"redirect_uri": client_info.redirect_uri,
"scope": "",
"state": request_state,
},
auth=(http_basic.username, http_basic.password),
# headers={AuthNEngine.API_KEY_HEADER: api_key.key},
)
# Authorization response check
assert authorization_response.status_code == HTTPStatus.FOUND
assert authorization_response.next_request is not None
redirect_url = authorization_response.next_request.url
assert str(redirect_url).startswith(client_info.redirect_uri)
assert redirect_url.params["state"] == request_state
assert redirect_url.params["scope"] == ""
authorization_code = redirect_url.params["code"]
assert len(authorization_code) > 0
# Access token request
token_response = fastplay_client.post(
"/auth/oauth2/token",
data={
"grant_type": "authorization_code",
"code": authorization_code,
"redirect_uri": client_info.redirect_uri,
"client_id": client_info.client_id,
},
auth=(client_info.client_id, client_info.client_secret),
)
assert token_response.status_code == HTTPStatus.OK
System Information
Python Version (3.x): 3.12
aioauth Version (0.x): 1.6.0
Framework (FastAPI / starlette, aiohttp, sanic, etc.)
- fastapi 0.112.0
- starlette 0.37.2