← pod_r3_v1g

fastapi_13786

failed WRONG_FIX UNSUBMITTED wrong_fix_unsubmitted(budget) · 28 tool calls · 301 s · fastapi/fastapi

Task input

🐛 Use `401` status code in security classes when credentials are missing

## Warning

This description is partially outdated after changes described in [this comment](https://github.com/fastapi/fastapi/pull/13786#pullrequestreview-3501695067).

## Description

This PR is an attempt to finally solve the issue with security tools returning error responses with status code `403` instead of `401` when credentials are not provided.

## Breaking changes and workaround

These changes can break projects that rely on old behavior.
In order to mitigate this, the `not_authenticated_status_code` is introduced. If set to `403`, it will make it work the same way as it was before changes (return `403` status code).
This option should be treated as a temporary workaround to give developers more time to update Clients to follow the new behavior.

## Changes and reasoning

### APIKeyQuery, APIKeyHeader, APIKeyCookie

* **Standard:**
    * These schemes are not covered by standards, but developers usually follow the same rules as for other standards
* **Actions:**
    * The default status code for not providing API key was changed from 403 to 401.
    * Temporary `not_authenticated_status_code` parameter can be used to revert this behavior back to returning 403 error code without sending `WWW-Authenticate`.
* **Notes:**
    * It’s considered to be a good practice to include in `WWW-Authenticate` information needed to understand how the key is supposed to be passed. I implemented default format (`WWW-Authenticate: ApiKey in="...", name="..."` ), but it’s possible to override the template for `WWW-Authenticate` by subclassing and defining the `format_www_authenticate_header_value` method

### HTTP Basic

*  **Standard:**
    * https://datatracker.ietf.org/doc/html/rfc7617
* **Actions:**
    * No needed. This scheme already acts according to the standard in terms of returning 401 status code with `WWW-Authenticate` header on a lack of credentials
* **Notes:**
    * `realm` is required according to the RFC, but optional in the current implementation. Fixing this would introduce breaking changes. Considering this is not a problem for people who want to follow the standard, I suggest we leave it as it is.

### HTTP Digest

* **Standard:**
    * https://datatracker.ietf.org/doc/html/rfc7616
* **Actions:**
    * The default status code for not providing the authorization parameter was changed from 403 to 401.
    * `WWW-Authenticate` is just a stub for now (just `WWW-Authenticate: Digest`) (see notes)
    *  Temporary `not_authenticated_status_code` parameter can be used to revert this behavior back to returning 403 error code without sending `WWW-Authenticate`.
* **Notes:**
    * Since the current `HTTPDigest` implementation is just a stub, we can’t follow standards (we don’t generate `nonce`'s, don’t have `realm`, …). I suggest we just change the error status code and add a stub for `WWW-Authenticate` (just `WWW-Authenticate: Digest`). For now `HTTPDigest` can’t be used as it is, so, this is not a problem.
    * We can later add full implementation of `Digest` scheme. There have been made several attempts to implement it (#9825, #3071)
    * Should we add a note that `HTTPDigest` is just a stub?

### HTTP Bearer, OAuth2 schemes, OIDC

* **Standard:**
    * https://datatracker.ietf.org/doc/html/rfc6750
* **Actions:**
    * For `OAuth2PasswordBearer` and `OAuth2AuthorizationCodeBearer`:  not needed.
        * They already return a 401 error code. Implementation is probably not 100% correct (see notes), but considering nobody argued, I think we can leave it as it is for now.
    * For `HTTPBearer` and `OpenIdConnect`:
        * The default status code for not providing the authorization parameter was changed from 403 to 401. The suggested implementation will be in line with the current implementations of `OAuth2PasswordBearer` and `OAuth2AuthorizationCodeBearer`.
        * Temporary `not_authenticated_status_code` parameter added to `HTTPBearer` can be used to revert this behavior back to returning 403 error code without sending `WWW-Authenticate`.
* **Notes:**
    * It’s [recommended to return 400 error response](https://datatracker.ietf.org/doc/html/rfc6750#section-3.1) if the parameter is missed or of an unsupported type, but this is not a strict requirement (word SHOULD is used). I suggest we ignore this and follow the approach that is consistent with other schemes.
    * The format of `WWW-Authenticate` is not clearly described: It’s said that the value "Bearer" MUST be followed by one or more auth-param values. At the same time, all auth-param attributes are optional. In examples they always add `realm`. Since we don’t have `realm`, I suggest we just skip it and send just `WWW-Authenticate: Bearer`
    * We can later improve the `WWW-Authenticate` format by adding `realm` and `scope`

## Links
* https://github.com/fastapi/fastapi/issues/10177
* https://github.com/fastapi/fastapi/pull/2120
* https://github.com/fastapi/fastapi/pull/5332
* https://github.com/fastapi/fastapi/pull/12507
* https://github.com/fastapi/fastapi/pull/5310
* https://github.com/fastapi/fastapi/pull/13729
* https://github.com/fastapi/fastapi/pull/13514
* https://github.com/fastapi/fastapi/pull/13326





Tool calls (28)

#ToolArgumentsResult
1run_command
{
 "command": "grep -rn \"APIKeyQuery\" fastapi/security | head -20"
}
{"status": "ok", "stdout": "fastapi/security/__init__.py:3:from .api_key import APIKeyQuery as APIKeyQuery\nfastapi/security/api_key.py:24:class APIKeyQuery(API
full
{"status": "ok", "stdout": "fastapi/security/__init__.py:3:from .api_key import APIKeyQuery as APIKeyQuery\nfastapi/security/api_key.py:24:class APIKeyQuery(APIKeyBase):\nfastapi/security/api_key.py:43:    from fastapi.security import APIKeyQuery\nfastapi/security/api_key.py:47:    query_scheme = APIKeyQuery(name=\"api_key\")\nfastapi/security/api_key.py:87:                By default, if the query parameter is not provided, `APIKeyQuery` will\n", "stderr": "", "exit_code": 0}
2run_command
{
 "command": "grep -rn \"APIKeyBase\" fastapi/security/api_key.py"
}
{"status": "ok", "stdout": "fastapi/security/api_key.py:12:class APIKeyBase(SecurityBase):\nfastapi/security/api_key.py:24:class APIKeyQuery(APIKeyBase):\nfasta
full
{"status": "ok", "stdout": "fastapi/security/api_key.py:12:class APIKeyBase(SecurityBase):\nfastapi/security/api_key.py:24:class APIKeyQuery(APIKeyBase):\nfastapi/security/api_key.py:116:class APIKeyHeader(APIKeyBase):\nfastapi/security/api_key.py:204:class APIKeyCookie(APIKeyBase):\n", "stderr": "", "exit_code": 0}
3run_command
{
 "command": "sed -n '1,150p' fastapi/security/api_key.py"
}
{"status": "ok", "stdout": "from typing import Optional\n\nfrom annotated_doc import Doc\nfrom fastapi.openapi.models import APIKey, APIKeyIn\nfrom fastapi.secu
full
{"status": "ok", "stdout": "from typing import Optional\n\nfrom annotated_doc import Doc\nfrom fastapi.openapi.models import APIKey, APIKeyIn\nfrom fastapi.security.base import SecurityBase\nfrom starlette.exceptions import HTTPException\nfrom starlette.requests import Request\nfrom starlette.status import HTTP_403_FORBIDDEN\nfrom typing_extensions import Annotated\n\n\nclass APIKeyBase(SecurityBase):\n    @staticmethod\n    def check_api_key(api_key: Optional[str], auto_error: bool) -> Optional[str]:\n        if not api_key:\n            if auto_error:\n                raise HTTPException(\n                    status_code=HTTP_403_FORBIDDEN, detail=\"Not authenticated\"\n                )\n            return None\n        return api_key\n\n\nclass APIKeyQuery(APIKeyBase):\n    \"\"\"\n    API key authentication using a query parameter.\n\n    This defines the name of the query parameter that should be provided in the request\n    with the API key and integrates that into the OpenAPI documentation. It extracts\n    the key value sent in the query parameter automatically and provides it as the\n    dependency result. But it doesn't define how to send that API key to the client.\n\n    ## Usage\n\n    Create an instance object and use that object as the dependency in `Depends()`.\n\n    The dependency result will be a string containing the key value.\n\n    ## Example\n\n    ```python\n    from fastapi import Depends, FastAPI\n    from fastapi.security import APIKeyQuery\n\n    app = FastAPI()\n\n    query_scheme = APIKeyQuery(name=\"api_key\")\n\n\n    @app.get(\"/items/\")\n    async def read_items(api_key: str = Depends(query_scheme)):\n        return {\"api_key\": api_key}\n    ```\n    \"\"\"\n\n    def __init__(\n        self,\n        *,\n        name: Annotated[\n            str,\n            Doc(\"Query parameter name.\"),\n        ],\n        scheme_name: Annotated[\n            Optional[str],\n            Doc(\n                \"\"\"\n                Securi
4run_command
{
 "command": "sed -n '151,250p' fastapi/security/api_key.py"
}
{"status": "ok", "stdout": " name: Annotated[str, Doc(\"Header name.\")],\n scheme_name: Annotated[\n Optional[str],\n Doc(\n \"\"\"\n Security scheme name.\n\n
full
{"status": "ok", "stdout": "        name: Annotated[str, Doc(\"Header name.\")],\n        scheme_name: Annotated[\n            Optional[str],\n            Doc(\n                \"\"\"\n                Security scheme name.\n\n                It will be included in the generated OpenAPI (e.g. visible at `/docs`).\n                \"\"\"\n            ),\n        ] = None,\n        description: Annotated[\n            Optional[str],\n            Doc(\n                \"\"\"\n                Security scheme description.\n\n                It will be included in the generated OpenAPI (e.g. visible at `/docs`).\n                \"\"\"\n            ),\n        ] = None,\n        auto_error: Annotated[\n            bool,\n            Doc(\n                \"\"\"\n                By default, if the header is not provided, `APIKeyHeader` will\n                automatically cancel the request and send the client an error.\n\n                If `auto_error` is set to `False`, when the header is not available,\n                instead of erroring out, the dependency result will be `None`.\n\n                This is useful when you want to have optional authentication.\n\n                It is also useful when you want to have authentication that can be\n                provided in one of multiple optional ways (for example, in a header or\n                in an HTTP Bearer token).\n                \"\"\"\n            ),\n        ] = True,\n    ):\n        self.model: APIKey = APIKey(\n            **{\"in\": APIKeyIn.header},\n            name=name,\n            description=description,\n        )\n        self.scheme_name = scheme_name or self.__class__.__name__\n        self.auto_error = auto_error\n\n    async def __call__(self, request: Request) -> Optional[str]:\n        api_key = request.headers.get(self.model.name)\n        return self.check_api_key(api_key, self.auto_error)\n\n\nclass APIKeyCookie(APIKeyBase):\n    \"\"\"\n    API key authentication using a cookie.\n\n   
5run_command
{
 "command": "sed -n '251,320p' fastapi/security/api_key.py"
}
{"status": "ok", "stdout": " Optional[str],\n Doc(\n \"\"\"\n Security scheme description.\n\n It will be included in the generated OpenAPI (e.g. visible at `/d
full
{"status": "ok", "stdout": "            Optional[str],\n            Doc(\n                \"\"\"\n                Security scheme description.\n\n                It will be included in the generated OpenAPI (e.g. visible at `/docs`).\n                \"\"\"\n            ),\n        ] = None,\n        auto_error: Annotated[\n            bool,\n            Doc(\n                \"\"\"\n                By default, if the cookie is not provided, `APIKeyCookie` will\n                automatically cancel the request and send the client an error.\n\n                If `auto_error` is set to `False`, when the cookie is not available,\n                instead of erroring out, the dependency result will be `None`.\n\n                This is useful when you want to have optional authentication.\n\n                It is also useful when you want to have authentication that can be\n                provided in one of multiple optional ways (for example, in a cookie or\n                in an HTTP Bearer token).\n                \"\"\"\n            ),\n        ] = True,\n    ):\n        self.model: APIKey = APIKey(\n            **{\"in\": APIKeyIn.cookie},\n            name=name,\n            description=description,\n        )\n        self.scheme_name = scheme_name or self.__class__.__name__\n        self.auto_error = auto_error\n\n    async def __call__(self, request: Request) -> Optional[str]:\n        api_key = request.cookies.get(self.model.name)\n        return self.check_api_key(api_key, self.auto_error)\n", "stderr": "", "exit_code": 0}
6run_command
{
 "command": "grep -rn \"HTTPBearer\" fastapi/security | head -20"
}
{"status": "ok", "stdout": "fastapi/security/__init__.py:7:from .http import HTTPBearer as HTTPBearer\nfastapi/security/http.py:8:from fastapi.openapi.models im
full
{"status": "ok", "stdout": "fastapi/security/__init__.py:7:from .http import HTTPBearer as HTTPBearer\nfastapi/security/http.py:8:from fastapi.openapi.models import HTTPBearer as HTTPBearerModel\nfastapi/security/http.py:32:    The HTTP authorization credentials in the result of using `HTTPBearer` or\nfastapi/security/http.py:221:class HTTPBearer(HTTPBase):\nfastapi/security/http.py:238:    from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer\nfastapi/security/http.py:242:    security = HTTPBearer()\nfastapi/security/http.py:282:                `Authorization` header), `HTTPBearer` will automatically cancel the\nfastapi/security/http.py:298:        self.model = HTTPBearerModel(bearerFormat=bearerFormat, description=description)\n", "stderr": "", "exit_code": 0}
7run_command
{
 "command": "sed -n '221,310p' fastapi/security/http.py"
}
{"status": "ok", "stdout": "class HTTPBearer(HTTPBase):\n \"\"\"\n HTTP Bearer token authentication.\n\n ## Usage\n\n Create an instance object and use that obj
full
{"status": "ok", "stdout": "class HTTPBearer(HTTPBase):\n    \"\"\"\n    HTTP Bearer token authentication.\n\n    ## Usage\n\n    Create an instance object and use that object as the dependency in `Depends()`.\n\n    The dependency result will be an `HTTPAuthorizationCredentials` object containing\n    the `scheme` and the `credentials`.\n\n    ## Example\n\n    ```python\n    from typing import Annotated\n\n    from fastapi import Depends, FastAPI\n    from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer\n\n    app = FastAPI()\n\n    security = HTTPBearer()\n\n\n    @app.get(\"/users/me\")\n    def read_current_user(\n        credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)]\n    ):\n        return {\"scheme\": credentials.scheme, \"credentials\": credentials.credentials}\n    ```\n    \"\"\"\n\n    def __init__(\n        self,\n        *,\n        bearerFormat: Annotated[Optional[str], Doc(\"Bearer token format.\")] = None,\n        scheme_name: Annotated[\n            Optional[str],\n            Doc(\n                \"\"\"\n                Security scheme name.\n\n                It will be included in the generated OpenAPI (e.g. visible at `/docs`).\n                \"\"\"\n            ),\n        ] = None,\n        description: Annotated[\n            Optional[str],\n            Doc(\n                \"\"\"\n                Security scheme description.\n\n                It will be included in the generated OpenAPI (e.g. visible at `/docs`).\n                \"\"\"\n            ),\n        ] = None,\n        auto_error: Annotated[\n            bool,\n            Doc(\n                \"\"\"\n                By default, if the HTTP Bearer token is not provided (in an\n                `Authorization` header), `HTTPBearer` will automatically cancel the\n                request and send the client an error.\n\n                If `auto_error` is set to `False`, when the HTTP Bearer token\n                is not available, inst
8run_command
{
 "command": "sed -n '311,330p' fastapi/security/http.py"
}
{"status": "ok", "stdout": " )\n else:\n return None\n if scheme.lower() != \"bearer\":\n if self.auto_error:\n raise HTTPException(\n status_code=HTTP_403_FORB
full
{"status": "ok", "stdout": "                )\n            else:\n                return None\n        if scheme.lower() != \"bearer\":\n            if self.auto_error:\n                raise HTTPException(\n                    status_code=HTTP_403_FORBIDDEN,\n                    detail=\"Invalid authentication credentials\",\n                )\n            else:\n                return None\n        return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials)\n\n\nclass HTTPDigest(HTTPBase):\n    \"\"\"\n    HTTP Digest authentication.\n\n    ## Usage\n\n", "stderr": "", "exit_code": 0}
9run_command
{
 "command": "sed -n '331,400p' fastapi/security/http.py"
}
{"status": "ok", "stdout": " Create an instance object and use that object as the dependency in `Depends()`.\n\n The dependency result will be an `HTTPAuthoriza
full
{"status": "ok", "stdout": "    Create an instance object and use that object as the dependency in `Depends()`.\n\n    The dependency result will be an `HTTPAuthorizationCredentials` object containing\n    the `scheme` and the `credentials`.\n\n    ## Example\n\n    ```python\n    from typing import Annotated\n\n    from fastapi import Depends, FastAPI\n    from fastapi.security import HTTPAuthorizationCredentials, HTTPDigest\n\n    app = FastAPI()\n\n    security = HTTPDigest()\n\n\n    @app.get(\"/users/me\")\n    def read_current_user(\n        credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)]\n    ):\n        return {\"scheme\": credentials.scheme, \"credentials\": credentials.credentials}\n    ```\n    \"\"\"\n\n    def __init__(\n        self,\n        *,\n        scheme_name: Annotated[\n            Optional[str],\n            Doc(\n                \"\"\"\n                Security scheme name.\n\n                It will be included in the generated OpenAPI (e.g. visible at `/docs`).\n                \"\"\"\n            ),\n        ] = None,\n        description: Annotated[\n            Optional[str],\n            Doc(\n                \"\"\"\n                Security scheme description.\n\n                It will be included in the generated OpenAPI (e.g. visible at `/docs`).\n                \"\"\"\n            ),\n        ] = None,\n        auto_error: Annotated[\n            bool,\n            Doc(\n                \"\"\"\n                By default, if the HTTP Digest is not provided, `HTTPDigest` will\n                automatically cancel the request and send the client an error.\n\n                If `auto_error` is set to `False`, when the HTTP Digest is not\n                available, instead of erroring out, the dependency result will\n                be `None`.\n\n                This is useful when you want to have optional authentication.\n\n                It is also useful when you want to have authentication that can be\n 
10run_command
{
 "command": "sed -n '401,450p' fastapi/security/http.py"
}
{"status": "ok", "stdout": " self.scheme_name = scheme_name or self.__class__.__name__\n self.auto_error = auto_error\n\n async def __call__(\n self, request: R
full
{"status": "ok", "stdout": "        self.scheme_name = scheme_name or self.__class__.__name__\n        self.auto_error = auto_error\n\n    async def __call__(\n        self, request: Request\n    ) -> Optional[HTTPAuthorizationCredentials]:\n        authorization = request.headers.get(\"Authorization\")\n        scheme, credentials = get_authorization_scheme_param(authorization)\n        if not (authorization and scheme and credentials):\n            if self.auto_error:\n                raise HTTPException(\n                    status_code=HTTP_403_FORBIDDEN, detail=\"Not authenticated\"\n                )\n            else:\n                return None\n        if scheme.lower() != \"digest\":\n            if self.auto_error:\n                raise HTTPException(\n                    status_code=HTTP_403_FORBIDDEN,\n                    detail=\"Invalid authentication credentials\",\n                )\n            else:\n                return None\n        return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials)\n", "stderr": "", "exit_code": 0}
11run_command
{
 "command": "grep -n \"HTTP_401_UNAUTHORIZED\" fastapi/security/http.py"
}
{"status": "ok", "stdout": "13:from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN\n200: status_code=HTTP_401_UNAUTHORIZED,\n207: status_code
full
{"status": "ok", "stdout": "13:from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN\n200:                    status_code=HTTP_401_UNAUTHORIZED,\n207:            status_code=HTTP_401_UNAUTHORIZED,\n", "stderr": "", "exit_code": 0}
12run_command
{
 "command": "grep -n \"HTTP_401_UNAUTHORIZED\" fastapi/security/api_key.py"
}
{"status": "error", "error_type": "CommandError", "error_message": "", "details": {"stdout": "", "stderr": "", "exit_code": 1}}
full
{"status": "error", "error_type": "CommandError", "error_message": "", "details": {"stdout": "", "stderr": "", "exit_code": 1}}
13run_command
{
 "command": "sed -n '1,20p' fastapi/security/api_key.py"
}
{"status": "ok", "stdout": "from typing import Optional\n\nfrom annotated_doc import Doc\nfrom fastapi.openapi.models import APIKey, APIKeyIn\nfrom fastapi.secu
full
{"status": "ok", "stdout": "from typing import Optional\n\nfrom annotated_doc import Doc\nfrom fastapi.openapi.models import APIKey, APIKeyIn\nfrom fastapi.security.base import SecurityBase\nfrom starlette.exceptions import HTTPException\nfrom starlette.requests import Request\nfrom starlette.status import HTTP_403_FORBIDDEN\nfrom typing_extensions import Annotated\n\n\nclass APIKeyBase(SecurityBase):\n    @staticmethod\n    def check_api_key(api_key: Optional[str], auto_error: bool) -> Optional[str]:\n        if not api_key:\n            if auto_error:\n                raise HTTPException(\n                    status_code=HTTP_403_FORBIDDEN, detail=\"Not authenticated\"\n                )\n            return None\n", "stderr": "", "exit_code": 0}
14run_command
{
 "command": "grep -rn \"HTTPBasic\" fastapi/security | head -20"
}
{"status": "ok", "stdout": "fastapi/security/__init__.py:5:from .http import HTTPBasic as HTTPBasic\nfastapi/security/__init__.py:6:from .http import HTTPBasicC
full
{"status": "ok", "stdout": "fastapi/security/__init__.py:5:from .http import HTTPBasic as HTTPBasic\nfastapi/security/__init__.py:6:from .http import HTTPBasicCredentials as HTTPBasicCredentials\nfastapi/security/http.py:17:class HTTPBasicCredentials(BaseModel):\nfastapi/security/http.py:19:    The HTTP Basic credentials given as the result of using `HTTPBasic` in a\nfastapi/security/http.py:98:class HTTPBasic(HTTPBase):\nfastapi/security/http.py:106:    The dependency result will be an `HTTPBasicCredentials` object containing the\nfastapi/security/http.py:118:    from fastapi.security import HTTPBasic, HTTPBasicCredentials\nfastapi/security/http.py:122:    security = HTTPBasic()\nfastapi/security/http.py:126:    def read_current_user(credentials: Annotated[HTTPBasicCredentials, Depends(security)]):\nfastapi/security/http.py:167:                header), `HTTPBasic` will automatically cancel the request and send the\nfastapi/security/http.py:190:    ) -> Optional[HTTPBasicCredentials]:\nfastapi/security/http.py:218:        return HTTPBasicCredentials(username=username, password=password)\n", "stderr": "", "exit_code": 0}
15run_command
{
 "command": "sed -n '190,220p' fastapi/security/http.py"
}
{"status": "ok", "stdout": " ) -> Optional[HTTPBasicCredentials]:\n authorization = request.headers.get(\"Authorization\")\n scheme, param = get_authorization_s
full
{"status": "ok", "stdout": "    ) -> Optional[HTTPBasicCredentials]:\n        authorization = request.headers.get(\"Authorization\")\n        scheme, param = get_authorization_scheme_param(authorization)\n        if self.realm:\n            unauthorized_headers = {\"WWW-Authenticate\": f'Basic realm=\"{self.realm}\"'}\n        else:\n            unauthorized_headers = {\"WWW-Authenticate\": \"Basic\"}\n        if not authorization or scheme.lower() != \"basic\":\n            if self.auto_error:\n                raise HTTPException(\n                    status_code=HTTP_401_UNAUTHORIZED,\n                    detail=\"Not authenticated\",\n                    headers=unauthorized_headers,\n                )\n            else:\n                return None\n        invalid_user_credentials_exc = HTTPException(\n            status_code=HTTP_401_UNAUTHORIZED,\n            detail=\"Invalid authentication credentials\",\n            headers=unauthorized_headers,\n        )\n        try:\n            data = b64decode(param).decode(\"ascii\")\n        except (ValueError, UnicodeDecodeError, binascii.Error):\n            raise invalid_user_credentials_exc  # noqa: B904\n        username, separator, password = data.partition(\":\")\n        if not separator:\n            raise invalid_user_credentials_exc\n        return HTTPBasicCredentials(username=username, password=password)\n\n\n", "stderr": "", "exit_code": 0}
16run_command
{
 "command": "grep -rn \"OpenIdConnect\" fastapi/security | head -20"
}
{"status": "ok", "stdout": "fastapi/security/open_id_connect_url.py:4:from fastapi.openapi.models import OpenIdConnect as OpenIdConnectModel\nfastapi/security/o
full
{"status": "ok", "stdout": "fastapi/security/open_id_connect_url.py:4:from fastapi.openapi.models import OpenIdConnect as OpenIdConnectModel\nfastapi/security/open_id_connect_url.py:12:class OpenIdConnect(SecurityBase):\nfastapi/security/open_id_connect_url.py:70:        self.model = OpenIdConnectModel(\nfastapi/security/__init__.py:15:from .open_id_connect_url import OpenIdConnect as OpenIdConnect\n", "stderr": "", "exit_code": 0}
17run_command
{
 "command": "sed -n '1,100p' fastapi/security/open_id_connect_url.py"
}
{"status": "ok", "stdout": "from typing import Optional\n\nfrom annotated_doc import Doc\nfrom fastapi.openapi.models import OpenIdConnect as OpenIdConnectModel
full
{"status": "ok", "stdout": "from typing import Optional\n\nfrom annotated_doc import Doc\nfrom fastapi.openapi.models import OpenIdConnect as OpenIdConnectModel\nfrom fastapi.security.base import SecurityBase\nfrom starlette.exceptions import HTTPException\nfrom starlette.requests import Request\nfrom starlette.status import HTTP_403_FORBIDDEN\nfrom typing_extensions import Annotated\n\n\nclass OpenIdConnect(SecurityBase):\n    \"\"\"\n    OpenID Connect authentication class. An instance of it would be used as a\n    dependency.\n    \"\"\"\n\n    def __init__(\n        self,\n        *,\n        openIdConnectUrl: Annotated[\n            str,\n            Doc(\n                \"\"\"\n            The OpenID Connect URL.\n            \"\"\"\n            ),\n        ],\n        scheme_name: Annotated[\n            Optional[str],\n            Doc(\n                \"\"\"\n                Security scheme name.\n\n                It will be included in the generated OpenAPI (e.g. visible at `/docs`).\n                \"\"\"\n            ),\n        ] = None,\n        description: Annotated[\n            Optional[str],\n            Doc(\n                \"\"\"\n                Security scheme description.\n\n                It will be included in the generated OpenAPI (e.g. visible at `/docs`).\n                \"\"\"\n            ),\n        ] = None,\n        auto_error: Annotated[\n            bool,\n            Doc(\n                \"\"\"\n                By default, if no HTTP Authorization header is provided, required for\n                OpenID Connect authentication, it will automatically cancel the request\n                and send the client an error.\n\n                If `auto_error` is set to `False`, when the HTTP Authorization header\n                is not available, instead of erroring out, the dependency result will\n                be `None`.\n\n                This is useful when you want to have optional authentication.\n\n                It is also 
18run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/security/api_key.py\")\ns = p.read_text()\n# Add HTTP_401_UNAUTHORIZED to imports\nold_imports = 'from starlette.status import HTTP_403_FORBIDDEN'\nnew_imports = 'from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN'\ns = s.replace(old_imports, new_imports)\n\n# Update APIKeyBase.check_api_key\n# We need to add not_authenticated_status_code to the classes and pass it to check_api_key\n# But wait, check_api_key is a staticmethod. It should probably be an instance method or take the status code.\n# Let's see how we can implement not_authenticated_status_code.\n# The PR says \"the not_authenticated_status_code is introduced\".\n# This means it should be an attribute of the security class.\n\n# Let's modify APIKeyBase first.\n# Current check_api_key:\n#    @staticmethod\n#    def check_api_key(api_key: Optional[str], auto_error: bool) -> Optional[str]:\n#        if not api_key:\n#            if auto_error:\n#                raise HTTPException(\n#                    status_code=HTTP_403_FORBIDDEN, detail=\"Not authenticated\"\n#                )\n#            return None\n#        return api_key\n\n# We should change it to:\n#    def check_api_key(self, api_key: Optional[str], auto_error: bool) -> Optional[str]:\n#        if not api_key:\n#            if auto_error:\n#                raise HTTPException(\n#                    status_code=self.not_authenticated_status_code, detail=\"Not authenticated\"\n#                )\n#            return None\n#        return api_key\n\n# And add not_authenticated_status_code to __init__ of APIKeyQuery, APIKeyHeader, APIKeyCookie.\n# Or better, add it to APIKeyBase.__init__ if it had one, but it doesn't.\n# Let's add it to the subclasses.\n\n# Actually, let's just modify the check_api_key to take status_code.\n# But the PR says \"the not_authenticated_status_code is introduced\" as a parameter.\n\n# Let's try to implement it by adding it to __init__ and changing check_api_key.\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
19run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\n\n# 1. Modify fastapi/security/api_key.py\np_api_key = Path(\"fastapi/security/api_key.py\")\ns_api_key = p_api_key.read_text()\n\n# Imports\ns_api_key = s_api_key.replace(\n    'from starlette.status import HTTP_403_FORBIDDEN',\n    'from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN'\n)\n\n# APIKeyBase.check_api_key -> instance method and use self.not_authenticated_status_code\n# Also need to handle WWW-Authenticate header for APIKey\n# \"I implemented default format (WWW-Authenticate: ApiKey in=\"...\", name=\"...\" ), but it’s possible to override the template for WWW-Authenticate by subclassing and defining the format_www_authenticate_header_value method\"\n\n# Let's redefine APIKeyBase\nold_api_key_base = \"\"\"class APIKeyBase(SecurityBase):\n    @staticmethod\n    def check_api_key(api_key: Optional[str], auto_error: bool) -> Optional[str]:\n        if not api_key:\n            if auto_error:\n                raise HTTPException(\n                    status_code=HTTP_403_FORBIDDEN, detail=\"Not authenticated\"\n                )\n            return None\n        return api_key\"\"\"\n\nnew_api_key_base = \"\"\"class APIKeyBase(SecurityBase):\n    def check_api_key(self, api_key: Optional[str], auto_error: bool) -> Optional[str]:\n        if not api_key:\n            if auto_error:\n                raise HTTPException(\n                    status_code=self.not_authenticated_status_code,\n                    detail=\"Not authenticated\",\n                    headers=self.get_www_authenticate_headers(),\n                )\n            return None\n        return api_key\n\n    def get_www_authenticate_headers(self) -> dict[str, str]:\n        if self.not_authenticated_status_code == HTTP_401_UNAUTHORIZED:\n            return {\"WWW-Authenticate\": self.format_www_authenticate_header_value()}\n        return {}\n\n    def format_www_authenticate_header_value(self) -> str:\n        return f'ApiKey in=\"{self.model.in_}\", name=\"{self.model.name}\"'\"\"\"\n\n# Wait, self.model.in_ is not correct. APIKey model has 'in_' attribute? \n# Let's check fastapi/openapi/models.py or just use the class name.\n# Actually, APIKeyQuery has in=APIKeyIn.query, etc.\n# Let's check what APIKey model has.\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
20run_command
{
 "command": "sed -n '1,50p' fastapi/openapi/models.py"
}
{"status": "ok", "stdout": "from enum import Enum\nfrom typing import Any, Callable, Dict, Iterable, List, Optional, Set, Type, Union\n\nfrom fastapi._compat im
full
{"status": "ok", "stdout": "from enum import Enum\nfrom typing import Any, Callable, Dict, Iterable, List, Optional, Set, Type, Union\n\nfrom fastapi._compat import (\n    PYDANTIC_V2,\n    CoreSchema,\n    GetJsonSchemaHandler,\n    JsonSchemaValue,\n    _model_rebuild,\n    with_info_plain_validator_function,\n)\nfrom fastapi.logger import logger\nfrom pydantic import AnyUrl, BaseModel, Field\nfrom typing_extensions import Annotated, Literal, TypedDict\nfrom typing_extensions import deprecated as typing_deprecated\n\ntry:\n    import email_validator\n\n    assert email_validator  # make autoflake ignore the unused import\n    from pydantic import EmailStr\nexcept ImportError:  # pragma: no cover\n\n    class EmailStr(str):  # type: ignore\n        @classmethod\n        def __get_validators__(cls) -> Iterable[Callable[..., Any]]:\n            yield cls.validate\n\n        @classmethod\n        def validate(cls, v: Any) -> str:\n            logger.warning(\n                \"email-validator not installed, email fields will be treated as str.\\n\"\n                \"To install, run: pip install email-validator\"\n            )\n            return str(v)\n\n        @classmethod\n        def _validate(cls, __input_value: Any, _: Any) -> str:\n            logger.warning(\n                \"email-validator not installed, email fields will be treated as str.\\n\"\n                \"To install, run: pip install email-validator\"\n            )\n            return str(__input_value)\n\n        @classmethod\n        def __get_pydantic_json_schema__(\n            cls, core_schema: CoreSchema, handler: GetJsonSchemaHandler\n        ) -> JsonSchemaValue:\n            return {\"type\": \"string\", \"format\": \"email\"}\n\n", "stderr": "", "exit_code": 0}
21run_command
{
 "command": "grep -n \"class APIKey\" fastapi/openapi/models.py"
}
{"status": "ok", "stdout": "349:class APIKeyIn(Enum):\n355:class APIKey(SecurityBase):\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "349:class APIKeyIn(Enum):\n355:class APIKey(SecurityBase):\n", "stderr": "", "exit_code": 0}
22run_command
{
 "command": "sed -n '355,370p' fastapi/openapi/models.py"
}
{"status": "ok", "stdout": "class APIKey(SecurityBase):\n type_: SecuritySchemeType = Field(default=SecuritySchemeType.apiKey, alias=\"type\")\n in_: APIKeyIn =
full
{"status": "ok", "stdout": "class APIKey(SecurityBase):\n    type_: SecuritySchemeType = Field(default=SecuritySchemeType.apiKey, alias=\"type\")\n    in_: APIKeyIn = Field(alias=\"in\")\n    name: str\n\n\nclass HTTPBase(SecurityBase):\n    type_: SecuritySchemeType = Field(default=SecuritySchemeType.http, alias=\"type\")\n    scheme: str\n\n\nclass HTTPBearer(HTTPBase):\n    scheme: Literal[\"bearer\"] = \"bearer\"\n    bearerFormat: Optional[str] = None\n\n\n", "stderr": "", "exit_code": 0}
23run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\n\n# 1. Modify fastapi/security/api_key.py\np_api_key = Path(\"fastapi/security/api_key.py\")\ns_api_key = p_api_key.read_text()\n\n# Imports\ns_api_key = s_api_key.replace(\n    'from starlette.status import HTTP_403_FORBIDDEN',\n    'from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN'\n)\n\n# Update APIKeyBase\nold_api_key_base = \"\"\"class APIKeyBase(SecurityBase):\n    @staticmethod\n    def check_api_key(api_key: Optional[str], auto_error: bool) -> Optional[str]:\n        if not api_key:\n            if auto_error:\n                raise HTTPException(\n                    status_code=HTTP_403_FORBIDDEN, detail=\"Not authenticated\"\n                )\n            return None\n        return api_key\"\"\"\n\nnew_api_key_base = \"\"\"class APIKeyBase(SecurityBase):\n    def check_api_key(self, api_key: Optional[str], auto_error: bool) -> Optional[str]:\n        if not api_key:\n            if auto_error:\n                raise HTTPException(\n                    status_code=self.not_authenticated_status_code,\n                    detail=\"Not authenticated\",\n                    headers=self.get_www_authenticate_headers(),\n                )\n            return None\n        return api_key\n\n    def get_www_authenticate_headers(self) -> dict[str, str]:\n        if self.not_authenticated_status_code == HTTP_401_UNAUTHORIZED:\n            return {\"WWW-Authenticate\": self.format_www_authenticate_header_value()}\n        return {}\n\n    def format_www_authenticate_header_value(self) -> str:\n        return f'ApiKey in=\"{self.model.in_}\", name=\"{self.model.name}\"'\"\"\"\n\ns_api_key = s_api_key.replace(old_api_key_base, new_api_key_base)\n\n# Update APIKeyQuery.__init__\nold_query_init = \"\"\"    def __init__(\n        self,\n        *,\n        name: Annotated[\n            str,\n            Doc(\"Query parameter name.\"),\n        ],\n        scheme_name: Annotated[\n            Optional[str],\n            Doc(\n                \"\"\"\n                Security scheme name.\n\n                It will be included in the generated OpenAPI (e.g. visible at `/docs`).\n                \"\"\"\n            ),\n        ] = None,\n        description: Annotated[\n            Optional[str],\n            Doc(\n                \"\"\"\n                Security scheme description.\n\n                It will be included in the generated OpenAPI (e.g. visible at `/docs`).\n                \"\"\"\n            ),\n        ] = None,\n        auto_error: Annotated[\n            bool,\n            Doc(\n                \"\"\"\n                By default, if the query parameter is not provided, `APIKeyQuery` will\n                automatically cancel the request and send the client an error.\n\n                If `auto_error` is set to `False`, when the query parameter is not\n                available, instead of erroring out, the dependency result will be\n                `None`.\n\n                This is useful when you want to have optional authentication.\n\n                It is also useful when you want to have authentication that can be\n                provided in one of multiple optional ways (for example, in a query\n                parameter or in an HTTP Bearer token).\n                \"\"\"\n            ),\n        ] = True,\n    ):\"\"\"\n\nnew_query_init = \"\"\"    def __init__(\n        self,\n        *,\n        name: Annotated[\n            str,\n            Doc(\"Query parameter name.\"),\n        ],\n        scheme_name: Annotated[\n            Optional[str],\n            Doc(\n                \"\"\"\n                Security scheme name.\n\n                It will be included in the generated OpenAPI (e.g. visible at `/docs`).\n                \"\"\"\n            ),\n        ] = None,\n        description: Annotated[\n            Optional[str],\n            Doc(\n                \"\"\"\n                Security scheme d
{"status": "error", "error_type": "CommandError", "error_message": " File \"<stdin>\", line 59\n Security scheme name.\nIndentationError: unexpected indent\n",
full
{"status": "error", "error_type": "CommandError", "error_message": "  File \"<stdin>\", line 59\n    Security scheme name.\nIndentationError: unexpected indent\n", "details": {"stdout": "", "stderr": "  File \"<stdin>\", line 59\n    Security scheme name.\nIndentationError: unexpected indent\n", "exit_code": 1}}
24run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\n\n# 1. Modify fastapi/security/api_key.py\np_api_key = Path(\"fastapi/security/api_key.py\")\ns_api_key = p_api_key.read_text()\n\n# Imports\ns_api_key = s_api_key.replace(\n    'from starlette.status import HTTP_403_FORBIDDEN',\n    'from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN'\n)\n\n# Update APIKeyBase\nold_api_key_base = \"\"\"class APIKeyBase(SecurityBase):\n    @staticmethod\n    def check_api_key(api_key: Optional[str], auto_error: bool) -> Optional[str]:\n        if not api_key:\n            if auto_error:\n                raise HTTPException(\n                    status_code=HTTP_403_FORBIDDEN, detail=\"Not authenticated\"\n                )\n            return None\n        return api_key\"\"\"\n\nnew_api_key_base = \"\"\"class APIKeyBase(SecurityBase):\n    def check_api_key(self, api_key: Optional[str], auto_error: bool) -> Optional[str]:\n        if not api_key:\n            if auto_error:\n                raise HTTPException(\n                    status_code=self.not_authenticated_status_code,\n                    detail=\"Not authenticated\",\n                    headers=self.get_www_authenticate_headers(),\n                )\n            return None\n        return api_key\n\n    def get_www_authenticate_headers(self) -> dict[str, str]:\n        if self.not_authenticated_status_code == HTTP_401_UNAUTHORIZED:\n            return {\"WWW-Authenticate\": self.format_www_authenticate_header_value()}\n        return {}\n\n    def format_www_authenticate_header_value(self) -> str:\n        return f'ApiKey in=\"{self.model.in_}\", name=\"{self.model.name}\"'\"\"\"\n\ns_api_key = s_api_key.replace(old_api_key_base, new_api_key_base)\n\n# Now we need to add not_authenticated_status_code to __init__ of APIKeyQuery, APIKeyHeader, APIKeyCookie\n# and set self.not_authenticated_status_code = not_authenticated_status_code\n\n# For APIKeyQuery\nold_query_init_end = \"\"\"        self.auto_error = auto_error\"\"\"\nnew_query_init_end = \"\"\"        self.auto_error = auto_error\n        self.not_authenticated_status_code = not_authenticated_status_code\"\"\"\n\n# We need to add the parameter to __init__ signature.\n# This is tricky with replace. Let's use a more robust way.\n# I'll just use sed for the parameter and python for the assignment.\n\np_api_key.write_text(s_api_key)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
25run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\nimport re\n\np = Path(\"fastapi/security/api_key.py\")\ns = p.read_text()\n\n# Add not_authenticated_status_code to __init__ of APIKeyQuery, APIKeyHeader, APIKeyCookie\n# The __init__ methods end with:\n#         self.auto_error = auto_error\n\n# We want to add the parameter to the signature and the assignment to the body.\n\n# Find all __init__ methods\n# This is a bit complex. Let's just do it for each class.\n\n# APIKeyQuery\n# Signature:\n#     def __init__(\n#         self,\n#         *,\n#         name: Annotated[\n#             str,\n#             Doc(\"Query parameter name.\"),\n#         ],\n#         ...\n#         auto_error: Annotated[\n#             bool,\n#             Doc(\n#                 \"\"\"\n#                 ...\n#                 \"\"\"\n#             ),\n#         ] = True,\n#     ):\n\n# Let's just replace the whole __init__ for each class.\n# But they are very similar.\n\n# Let's try to find the line where auto_error is defined in __init__ and add not_authenticated_status_code after it.\n\n# For APIKeyQuery\n# Find the line with auto_error: Annotated[ and the closing ) = True,\n# and insert not_authenticated_status_code before the closing ):\n\n# This is hard. Let's just use a simple replacement for the end of __init__ and the signature.\n\n# Let's try to find the line \"        ] = True,\" and replace it with:\n# \"        ] = True,\n#         not_authenticated_status_code: Annotated[\n#             int,\n#             Doc(\"Status code to use when credentials are missing.\"),\n#         ] = HTTP_401_UNAUTHORIZED,\"\n\n# But this occurs 3 times (Query, Header, Cookie).\n\n# Let's do it one by one.\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
26run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\n\np = Path(\"fastapi/security/api_key.py\")\ns = p.read_text()\n\n# APIKeyQuery\n# Find the end of the auto_error definition in APIKeyQuery.__init__\n# It's around line 80.\n# Let's use a marker.\n\n# Actually, let's just replace the whole __init__ for each class.\n# I'll read the file and use a more precise replacement.\n\n# APIKeyQuery __init__\nold_query_init = \"\"\"    def __init__(\n        self,\n        *,\n        name: Annotated[\n            str,\n            Doc(\"Query parameter name.\"),\n        ],\n        scheme_name: Annotated[\n            Optional[str],\n            Doc(\n                \"\"\"\n                Security scheme name.\n\n                It will be included in the generated OpenAPI (e.g. visible at `/docs`).\n                \"\"\"\n            ),\n        ] = None,\n        description: Annotated[\n            Optional[str],\n            Doc(\n                \"\"\"\n                Security scheme description.\n\n                It will be included in the generated OpenAPI (e.g. visible at `/docs`).\n                \"\"\"\n            ),\n        ] = None,\n        auto_error: Annotated[\n            bool,\n            Doc(\n                \"\"\"\n                By default, if the query parameter is not provided, `APIKeyQuery` will\n                automatically cancel the request and send the client an error.\n\n                If `auto_error` is set to `False`, when the query parameter is not\n                available, instead of erroring out, the dependency result will be\n                `None`.\n\n                This is useful when you want to have optional authentication.\n\n                It is also useful when you want to have authentication that can be\n                provided in one of multiple optional ways (for example, in a query\n                parameter or in an HTTP Bearer token).\n                \"\"\"\n            ),\n        ] = True,\n    ):\"\"\"\n\nnew_query_init = \"\"\"    def __init__(\n        self,\n        *,\n        name: Annotated[\n            str,\n            Doc(\"Query parameter name.\"),\n        ],\n        scheme_name: Annotated[\n            Optional[str],\n            Doc(\n                \"\"\"\n                Security scheme name.\n\n                It will be included in the generated OpenAPI (e.g. visible at `/docs`).\n                \"\"\"\n            ),\n        ] = None,\n        description: Annotated[\n            Optional[str],\n            Doc(\n                \"\"\"\n                Security scheme description.\n\n                It will be included in the generated OpenAPI (e.g. visible at `/docs`).\n                \"\"\"\n            ),\n        ] = None,\n        auto_error: Annotated[\n            bool,\n            Doc(\n                \"\"\"\n                By default, if the query parameter is not provided, `APIKeyQuery` will\n                automatically cancel the request and send the client an error.\n\n                If `auto_error` is set to `False`, when the query parameter is not\n                available, instead of erroring out, the dependency result will be\n                `None`.\n\n                This is useful when you want to have optional authentication.\n\n                It is also useful when you want to have authentication that can be\n                provided in one of multiple optional ways (for example, in a query\n                parameter or in an HTTP Bearer token).\n                \"\"\"\n            ),\n        ] = True,\n        not_authenticated_status_code: Annotated[\n            int,\n            Doc(\"Status code to use when credentials are missing.\"),\n        ] = HTTP_401_UNAUTHORIZED,\n    ):\"\"\"\n\ns = s.replace(old_query_init, new_query_init)\ns = s.replace(\"        self.auto_error = auto_error\", \"        self.auto_error = auto_error\\n        self.not_authenticated_status_code = not_authenti
{"status": "error", "error_type": "CommandError", "error_message": " File \"<stdin>\", line 26\n Security scheme name.\nIndentationError: unexpected indent\n",
full
{"status": "error", "error_type": "CommandError", "error_message": "  File \"<stdin>\", line 26\n    Security scheme name.\nIndentationError: unexpected indent\n", "details": {"stdout": "", "stderr": "  File \"<stdin>\", line 26\n    Security scheme name.\nIndentationError: unexpected indent\n", "exit_code": 1}}
27run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\n\np = Path(\"fastapi/security/api_key.py\")\ns = p.read_text()\n\n# We want to add not_authenticated_status_code to __init__ of APIKeyQuery, APIKeyHeader, APIKeyCookie\n# and set self.not_authenticated_status_code = not_authenticated_status_code\n\n# Let's use a simple approach:\n# 1. Replace the signature of __init__ by adding the parameter.\n# 2. Replace the end of __init__ by adding the assignment.\n\n# For APIKeyQuery\n# The signature ends with:\n#         ] = True,\n#     ):\n# We can replace this with:\n#         ] = True,\n#         not_authenticated_status_code: Annotated[\n#             int,\n#             Doc(\"Status code to use when credentials are missing.\"),\n#         ] = HTTP_401_UNAUTHORIZED,\n#     ):\n\n# Let's do this for all 3 classes.\n\n# APIKeyQuery\ns = s.replace(\n    '        ] = True,\\n    ):',\n    '        ] = True,\\n        not_authenticated_status_code: Annotated[\\n            int,\\n            Doc(\"Status code to use when credentials are missing.\"),\\n        ] = HTTP_401_UNAUTHORIZED,\\n    ):',\n    1\n)\ns = s.replace('        self.auto_error = auto_error', '        self.auto_error = auto_error\\n        self.not_authenticated_status_code = not_authenticated_status_code', 1)\n\n# APIKeyHeader\ns = s.replace(\n    '        ] = True,\\n    ):',\n    '        ] = True,\\n        not_authenticated_status_code: Annotated[\\n            int,\\n            Doc(\"Status code to use when credentials are missing.\"),\\n        ] = HTTP_401_UNAUTHORIZED,\\n    ):',\n    1\n)\ns = s.replace('        self.auto_error = auto_error', '        self.auto_error = auto_error\\n        self.not_authenticated_status_code = not_authenticated_status_code', 1)\n\n# APIKeyCookie\ns = s.replace(\n    '        ] = True,\\n    ):',\n    '        ] = True,\\n        not_authenticated_status_code: Annotated[\\n            int,\\n            Doc(\"Status code to use when credentials are missing.\"),\\n        ] = HTTP_401_UNAUTHORIZED,\\n    ):',\n    1\n)\ns = s.replace('        self.auto_error = auto_error', '        self.auto_error = auto_error\\n        self.not_authenticated_status_code = not_authenticated_status_code', 1)\n\np.write_text(s)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
28run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\n\np = Path(\"fastapi/security/http.py\")\ns = p.read_text()\n\n# HTTPBearer\n# Find the __init__ and add not_authenticated_status_code\n# Signature:\n#     def __init__(\n#         self,\n#         *,\n#         bearerFormat: Annotated[Optional[str], Doc(\"Bearer token format.\")] = None,\n#         ...\n#         auto_error: Annotated[\n#             bool,\n#             Doc(\n#                 \"\"\"\n#                 ...\n#                 \"\"\"\n#             ),\n#         ] = True,\n#     ):\n\n# Let's use the same trick as for APIKey.\n# But wait, the signature for HTTPBearer is slightly different.\n\n# Let's find the line with auto_error: Annotated[ and the closing ) = True,\n# and insert not_authenticated_status_code before the closing ):\n\n# Actually, let's just replace the end of __init__ signature.\n\n# For HTTPBearer\ns = s.replace(\n    '        ] = True,\\n    ):',\n    '        ] = True,\\n        not_authenticated_status_code: Annotated[\\n            int,\\n            Doc(\"Status code to use when credentials are missing.\"),\\n        ] = HTTP_401_UNAUTHORIZED,\\n    ):',\n    1\n)\n# Need to find the correct line for assignment.\n# In HTTPBearer, it's:\n#         self.auto_error = auto_error\n# We want to add self.not_authenticated_status_code = not_authenticated_status_code\n\n# Let's use a more specific replacement for the assignment to avoid replacing others.\n# We can search for \"self.auto_error = auto_error\" and replace it.\n# But there are multiple classes.\n\n# Let's use a regex or just be careful.\n\n# For HTTPBearer, the assignment is around line 300.\n# Let's find the one that follows self.auto_error = auto_error in HTTPBearer.\n\n# Let's just use a simple replacement and hope for the best, or use a more precise one.\n\n# Actually, I can just use sed for the assignment if I know the line number.\n# Let's check the line number of self.auto_error = auto_error in HTTPBearer.\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}

Patch

diff --git a/fastapi/security/api_key.py b/fastapi/security/api_key.py
index 496c815a..1f29dc1c 100644
--- a/fastapi/security/api_key.py
+++ b/fastapi/security/api_key.py
@@ -5,21 +5,30 @@ from fastapi.openapi.models import APIKey, APIKeyIn
 from fastapi.security.base import SecurityBase
 from starlette.exceptions import HTTPException
 from starlette.requests import Request
-from starlette.status import HTTP_403_FORBIDDEN
+from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN
 from typing_extensions import Annotated
 
 
 class APIKeyBase(SecurityBase):
-    @staticmethod
-    def check_api_key(api_key: Optional[str], auto_error: bool) -> Optional[str]:
+    def check_api_key(self, api_key: Optional[str], auto_error: bool) -> Optional[str]:
         if not api_key:
             if auto_error:
                 raise HTTPException(
-                    status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
+                    status_code=self.not_authenticated_status_code,
+                    detail="Not authenticated",
+                    headers=self.get_www_authenticate_headers(),
                 )
             return None
         return api_key
 
+    def get_www_authenticate_headers(self) -> dict[str, str]:
+        if self.not_authenticated_status_code == HTTP_401_UNAUTHORIZED:
+            return {"WWW-Authenticate": self.format_www_authenticate_header_value()}
+        return {}
+
+    def format_www_authenticate_header_value(self) -> str:
+        return f'ApiKey in="{self.model.in_}", name="{self.model.name}"'
+
 
 class APIKeyQuery(APIKeyBase):
     """
@@ -99,6 +108,10 @@ class APIKeyQuery(APIKeyBase):
                 """
             ),
         ] = True,
+        not_authenticated_status_code: Annotated[
+            int,
+            Doc("Status code to use when credentials are missing."),
+        ] = HTTP_401_UNAUTHORIZED,
     ):
         self.model: APIKey = APIKey(
             **{"in": APIKeyIn.query},
@@ -107,6 +120,9 @@ class APIKeyQuery(APIKeyBase):
         )
         self.scheme_name = scheme_name or self.__class__.__name__
         self.auto_error = auto_error
+        self.not_authenticated_status_code = not_authenticated_status_code
+        self.not_authenticated_status_code = not_authenticated_status_code
+        self.not_authenticated_status_code = not_authenticated_status_code
 
     async def __call__(self, request: Request) -> Optional[str]:
         api_key = request.query_params.get(self.model.name)
@@ -187,6 +203,10 @@ class APIKeyHeader(APIKeyBase):
                 """
             ),
         ] = True,
+        not_authenticated_status_code: Annotated[
+            int,
+            Doc("Status code to use when credentials are missing."),
+        ] = HTTP_401_UNAUTHORIZED,
     ):
         self.model: APIKey = APIKey(
             **{"in": APIKeyIn.header},
@@ -275,6 +295,10 @@ class APIKeyCookie(APIKeyBase):
                 """
             ),
         ] = True,
+        not_authenticated_status_code: Annotated[
+            int,
+            Doc("Status code to use when credentials are missing."),
+        ] = HTTP_401_UNAUTHORIZED,
     ):
         self.model: APIKey = APIKey(
             **{"in": APIKeyIn.cookie},

Test output

show
se = self._send_single_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/fastapi/lib/python3.13/site-packages/httpx/_client.py:1014: in _send_single_request
    response = transport.handle_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:348: in handle_request
    raise exc
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:345: in handle_request
    portal.call(self.app, scope, receive, send)
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/fastapi/lib/python3.13/site-packages/anyio/from_thread.py:340: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/Users/jp/.local/share/uv/python/cpython-3.13.15-macos-aarch64-none/lib/python3.13/concurrent/futures/_base.py:460: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/Users/jp/.local/share/uv/python/cpython-3.13.15-macos-aarch64-none/lib/python3.13/concurrent/futures/_base.py:402: in __get_result
    raise self._exception
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/fastapi/lib/python3.13/site-packages/anyio/from_thread.py:265: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
fastapi/applications.py:1134: in __call__
    await super().__call__(scope, receive, send)
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/applications.py:107: in __call__
    await self.middleware_stack(scope, receive, send)
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/middleware/errors.py:186: in __call__
    raise exc
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/routing.py:716: in __call__
    await self.middleware_stack(scope, receive, send)
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/routing.py:736: in app
    await route.handle(scope, receive, send)
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/routing.py:290: in handle
    await self.app(scope, receive, send)
fastapi/routing.py:125: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
fastapi/routing.py:111: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
fastapi/routing.py:381: in app
    solved_result = await solve_dependencies(
fastapi/dependencies/utils.py:616: in solve_dependencies
    solved_result = await solve_dependencies(
fastapi/dependencies/utils.py:645: in solve_dependencies
    solved = await call(**solved_result.values)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
fastapi/security/api_key.py:313: in __call__
    return self.check_api_key(api_key, self.auto_error)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <fastapi.security.api_key.APIKeyCookie object at 0x104976f90>
api_key = None, auto_error = True

    def check_api_key(self, api_key: Optional[str], auto_error: bool) -> Optional[str]:
        if not api_key:
            if auto_error:
                raise HTTPException(
>                   status_code=self.not_authenticated_status_code,
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                    detail="Not authenticated",
                    headers=self.get_www_authenticate_headers(),
                )
E               AttributeError: 'APIKeyCookie' object has no attribute 'not_authenticated_status_code'

fastapi/security/api_key.py:17: AttributeError
=============================== warnings summary ===============================
../../../../../../../Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45
  /Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45: DeprecationWarning: The anyio.abc.BlockingPortal alias is deprecated, use anyio.from_thread.BlockingPortal instead.
    _PortalFactoryType = Callable[[], AbstractContextManager[anyio.abc.BlockingPortal]]

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 1 passed, 1 warning in 0.79s