aliev / aioauth

Asynchronous OAuth 2.0 provider for Python 3
https://aliev.me/aioauth
MIT License
214 stars 19 forks source link

`get` method of `aioauth.collections.HTTPHeaderDict` class is not case-insensitive #97

Open PrieJos opened 3 months ago

PrieJos commented 3 months ago

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