failed CONTEXT UNSUBMITTED agent_error · 36 tool calls · 236 s · fastapi/fastapi
🐛 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 | Arguments | Result |
|---|---|---|---|
| 1 | run_command | {
"command": "grep -rn 'APIKeyQuery' . || echo '<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.'"
} | {"status": "ok", "stdout": "./tests/test_security_api_key_query.py:2:from fastapi.security import APIKeyQuery\n./tests/test_security_api_key_query.py:8:api_key full{"status": "ok", "stdout": "./tests/test_security_api_key_query.py:2:from fastapi.security import APIKeyQuery\n./tests/test_security_api_key_query.py:8:api_key = APIKeyQuery(name=\"key\")\n./tests/test_security_api_key_query.py:57: \"security\": [{\"APIKeyQuery\": []}],\n./tests/test_security_api_key_query.py:63: \"APIKeyQuery\": {\"type\": \"apiKey\", \"name\": \"key\", \"in\": \"query\"}\n./tests/test_security_api_key_query_description.py:2:from fastapi.security import APIKeyQuery\n./tests/test_security_api_key_query_description.py:8:api_key = APIKeyQuery(name=\"key\", description=\"API Key Query\")\n./tests/test_security_api_key_query_description.py:57: \"security\": [{\"APIKeyQuery\": []}],\n./tests/test_security_api_key_query_description.py:63: \"APIKeyQuery\": {\n./tests/test_security_api_key_query_optional.py:4:from fastapi.security import APIKeyQuery\n./tests/test_security_api_key_query_optional.py:10:api_key = APIKeyQuery(name=\"key\", auto_error=False)\n./tests/test_security_api_key_query_optional.py:63: \"security\": [{\"APIKeyQuery\": []}],\n./tests/test_security_api_key_query_optional.py:69: \"APIKeyQuery\": {\"type\": \"apiKey\", \"name\": \"key\", \"in\": \"query\"}\n./docs/en/docs/release-notes.md:4462:* \u2728 Add `description` parameter to all the security scheme classes, e.g. `APIKeyQuery(name=\"key\", description=\"A very cool API key\")`. PR [#1757](https://github.com/tiangolo/fastapi/pull/1757) by [@hylkepostma](https://github.com/hylkepostma).\n./docs/en/docs/reference/security/index.md:15: APIKeyQuery,\n./docs/en/docs/reference/security/index.md:37:::: fastapi.security.APIKeyQuery\n./fastapi/security/__init__.py:3:from .api_key import APIKeyQuery as APIKeyQuery\n./fastapi/security/api_key.py:24:class APIKeyQuery(APIKeyBase):\n./fastapi/security/api_key.py:43: from fastapi.security import APIKeyQuery\n./fastapi/security/api_key.py:47: |
| 2 | run_command | {
"command": "ls -R fastapi/security"
} | {"status": "ok", "stdout": "__init__.py\napi_key.py\nbase.py\nhttp.py\noauth2.py\nopen_id_connect_url.py\nutils.py\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "__init__.py\napi_key.py\nbase.py\nhttp.py\noauth2.py\nopen_id_connect_url.py\nutils.py\n", "stderr": "", "exit_code": 0} |
| 3 | run_command | {
"command": "grep -rn 'APIKeyBase' fastapi/security | head -20"
} | {"status": "ok", "stdout": "fastapi/security/api_key.py:12:class APIKeyBase(SecurityBase):\nfastapi/security/api_key.py:24:class APIKeyQuery(APIKeyBase):\nfastafull{"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} |
| 4 | run_command | {
"command": "sed -n '1,120p' 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.secufull{"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 |
| 5 | run_command | {
"command": "sed -n '121,210p' fastapi/security/api_key.py"
} | {"status": "ok", "stdout": " the API key and integrates that into the OpenAPI documentation. It extracts\n the key value sent in the header automatically and prfull{"status": "ok", "stdout": " the API key and integrates that into the OpenAPI documentation. It extracts\n the key value sent in the header automatically and provides it as the dependency\n result. But it doesn't define how to send that 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 APIKeyHeader\n\n app = FastAPI()\n\n header_scheme = APIKeyHeader(name=\"x-key\")\n\n\n @app.get(\"/items/\")\n async def read_items(key: str = Depends(header_scheme)):\n return {\"key\": key}\n ```\n \"\"\"\n\n def __init__(\n self,\n *,\n 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 |
| 6 | run_command | {
"command": "sed -n '211,250p' fastapi/security/api_key.py"
} | {"status": "ok", "stdout": " result. But it doesn't define how to set that cookie.\n\n ## Usage\n\n Create an instance object and use that object as the dependefull{"status": "ok", "stdout": " result. But it doesn't define how to set that cookie.\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 APIKeyCookie\n\n app = FastAPI()\n\n cookie_scheme = APIKeyCookie(name=\"session\")\n\n\n @app.get(\"/items/\")\n async def read_items(session: str = Depends(cookie_scheme)):\n return {\"session\": session}\n ```\n \"\"\"\n\n def __init__(\n self,\n *,\n name: Annotated[str, Doc(\"Cookie 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", "stderr": "", "exit_code": 0} |
| 7 | run_command | {
"command": "sed -n '251,300p' 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 `/dfull{"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} |
| 8 | run_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 imfull{"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} |
| 9 | run_command | {
"command": "grep -rn 'HTTPDigest' fastapi/security | head -20"
} | {"status": "ok", "stdout": "fastapi/security/__init__.py:8:from .http import HTTPDigest as HTTPDigest\nfastapi/security/http.py:33: `HTTPDigest` in a dependencyfull{"status": "ok", "stdout": "fastapi/security/__init__.py:8:from .http import HTTPDigest as HTTPDigest\nfastapi/security/http.py:33: `HTTPDigest` in a dependency.\nfastapi/security/http.py:325:class HTTPDigest(HTTPBase):\nfastapi/security/http.py:342: from fastapi.security import HTTPAuthorizationCredentials, HTTPDigest\nfastapi/security/http.py:346: security = HTTPDigest()\nfastapi/security/http.py:384: By default, if the HTTP Digest is not provided, `HTTPDigest` will\n", "stderr": "", "exit_code": 0} |
| 10 | run_command | {
"command": "sed -n '221,324p' 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 objfull{"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 |
| 11 | run_command | {
"command": "sed -n '325,420p' fastapi/security/http.py"
} | {"status": "ok", "stdout": "class HTTPDigest(HTTPBase):\n \"\"\"\n HTTP Digest authentication.\n\n ## Usage\n\n Create an instance object and use that object asfull{"status": "ok", "stdout": "class HTTPDigest(HTTPBase):\n \"\"\"\n HTTP Digest 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, 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 authenticati |
| 12 | run_command | {
"command": "sed -n '421,450p' fastapi/security/http.py"
} | {"status": "ok", "stdout": " )\n else:\n return None\n return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials)\n", "stderr": "", "exit_code"full{"status": "ok", "stdout": " )\n else:\n return None\n return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials)\n", "stderr": "", "exit_code": 0} |
| 13 | run_command | {
"command": "sed -n '1,50p' fastapi/security/http.py"
} | {"status": "ok", "stdout": "import binascii\nfrom base64 import b64decode\nfrom typing import Optional\n\nfrom annotated_doc import Doc\nfrom fastapi.exceptionsfull{"status": "ok", "stdout": "import binascii\nfrom base64 import b64decode\nfrom typing import Optional\n\nfrom annotated_doc import Doc\nfrom fastapi.exceptions import HTTPException\nfrom fastapi.openapi.models import HTTPBase as HTTPBaseModel\nfrom fastapi.openapi.models import HTTPBearer as HTTPBearerModel\nfrom fastapi.security.base import SecurityBase\nfrom fastapi.security.utils import get_authorization_scheme_param\nfrom pydantic import BaseModel\nfrom starlette.requests import Request\nfrom starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN\nfrom typing_extensions import Annotated\n\n\nclass HTTPBasicCredentials(BaseModel):\n \"\"\"\n The HTTP Basic credentials given as the result of using `HTTPBasic` in a\n dependency.\n\n Read more about it in the\n [FastAPI docs for HTTP Basic Auth](https://fastapi.tiangolo.com/advanced/security/http-basic-auth/).\n \"\"\"\n\n username: Annotated[str, Doc(\"The HTTP Basic username.\")]\n password: Annotated[str, Doc(\"The HTTP Basic password.\")]\n\n\nclass HTTPAuthorizationCredentials(BaseModel):\n \"\"\"\n The HTTP authorization credentials in the result of using `HTTPBearer` or\n `HTTPDigest` in a dependency.\n\n The HTTP authorization header value is split by the first space.\n\n The first part is the `scheme`, the second part is the `credentials`.\n\n For example, in an HTTP Bearer token scheme, the client will send a header\n like:\n\n ```\n Authorization: Bearer deadbeef12346\n ```\n\n In this case:\n\n * `scheme` will have the value `\"Bearer\"`\n * `credentials` will have the value `\"deadbeef12346\"`\n \"\"\"\n", "stderr": "", "exit_code": 0} |
| 14 | run_command | {
"command": "grep -rn 'HTTPBasic' fastapi/security/http.py"
} | {"status": "ok", "stdout": "fastapi/security/http.py:17:class HTTPBasicCredentials(BaseModel):\nfastapi/security/http.py:19: The HTTP Basic credentials given asfull{"status": "ok", "stdout": "fastapi/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} |
| 15 | run_command | {
"command": "sed -n '98,220p' fastapi/security/http.py"
} | {"status": "ok", "stdout": "class HTTPBasic(HTTPBase):\n \"\"\"\n HTTP Basic authentication.\n\n ## Usage\n\n Create an instance object and use that object as tfull{"status": "ok", "stdout": "class HTTPBasic(HTTPBase):\n \"\"\"\n HTTP Basic 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 `HTTPBasicCredentials` object containing the\n `username` and the `password`.\n\n Read more about it in the\n [FastAPI docs for HTTP Basic Auth](https://fastapi.tiangolo.com/advanced/security/http-basic-auth/).\n\n ## Example\n\n ```python\n from typing import Annotated\n\n from fastapi import Depends, FastAPI\n from fastapi.security import HTTPBasic, HTTPBasicCredentials\n\n app = FastAPI()\n\n security = HTTPBasic()\n\n\n @app.get(\"/users/me\")\n def read_current_user(credentials: Annotated[HTTPBasicCredentials, Depends(security)]):\n return {\"username\": credentials.username, \"password\": credentials.password}\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 realm: Annotated[\n Optional[str],\n Doc(\n \"\"\"\n HTTP Basic authentication realm.\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 Basic authentication is not provided (a\n header), `HTTPBasic` will automati |
| 16 | run_command | {
"command": "grep -rn 'OpenIdConnect' fastapi/security"
} | {"status": "ok", "stdout": "fastapi/security/open_id_connect_url.py:4:from fastapi.openapi.models import OpenIdConnect as OpenIdConnectModel\nfastapi/security/ofull{"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} |
| 17 | run_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 OpenIdConnectModelfull{"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 |
| 18 | run_command | {
"command": "grep -rn 'HTTP_401_UNAUTHORIZED' fastapi/security | head -20"
} | {"status": "ok", "stdout": "fastapi/security/oauth2.py:11:from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN\nfastapi/security/oauth2.py:495full{"status": "ok", "stdout": "fastapi/security/oauth2.py:11:from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN\nfastapi/security/oauth2.py:495: status_code=HTTP_401_UNAUTHORIZED,\nfastapi/security/oauth2.py:605: status_code=HTTP_401_UNAUTHORIZED,\nfastapi/security/http.py:13:from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN\nfastapi/security/http.py:200: status_code=HTTP_401_UNAUTHORIZED,\nfastapi/security/http.py:207: status_code=HTTP_401_UNAUTHORIZED,\n", "stderr": "", "exit_code": 0} |
| 19 | run_command | {
"command": "grep -rn 'OAuth2PasswordBearer' fastapi/security | head -20"
} | {"status": "ok", "stdout": "fastapi/security/oauth2.py:392:class OAuth2PasswordBearer(OAuth2):\nfastapi/security/__init__.py:11:from .oauth2 import OAuth2Passwofull{"status": "ok", "stdout": "fastapi/security/oauth2.py:392:class OAuth2PasswordBearer(OAuth2):\nfastapi/security/__init__.py:11:from .oauth2 import OAuth2PasswordBearer as OAuth2PasswordBearer\n", "stderr": "", "exit_code": 0} |
| 20 | run_command | {
"command": "sed -n '392,500p' fastapi/security/oauth2.py"
} | {"status": "ok", "stdout": "class OAuth2PasswordBearer(OAuth2):\n \"\"\"\n OAuth2 flow for authentication using a bearer token obtained with a password.\n An infull{"status": "ok", "stdout": "class OAuth2PasswordBearer(OAuth2):\n \"\"\"\n OAuth2 flow for authentication using a bearer token obtained with a password.\n An instance of it would be used as a dependency.\n\n Read more about it in the\n [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).\n \"\"\"\n\n def __init__(\n self,\n tokenUrl: Annotated[\n str,\n Doc(\n \"\"\"\n The URL to obtain the OAuth2 token. This would be the *path operation*\n that has `OAuth2PasswordRequestForm` as a dependency.\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 scopes: Annotated[\n Optional[Dict[str, str]],\n Doc(\n \"\"\"\n The OAuth2 scopes that would be required by the *path operations* that\n use this dependency.\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 OAuth2 authentication, it will automatically cancel the request and\n send the client an error.\n\n If `auto_error` is set to `False`, when the HTTP Authorization hea |
| 21 | run_command | {
"command": "grep -rn 'OAuth2AuthorizationCodeBearer' fastapi/security | head -20"
} | {"status": "ok", "stdout": "fastapi/security/oauth2.py:504:class OAuth2AuthorizationCodeBearer(OAuth2):\nfastapi/security/__init__.py:10:from .oauth2 import OAufull{"status": "ok", "stdout": "fastapi/security/oauth2.py:504:class OAuth2AuthorizationCodeBearer(OAuth2):\nfastapi/security/__init__.py:10:from .oauth2 import OAuth2AuthorizationCodeBearer as OAuth2AuthorizationCodeBearer\n", "stderr": "", "exit_code": 0} |
| 22 | run_command | {
"command": "sed -n '504,620p' fastapi/security/oauth2.py"
} | {"status": "ok", "stdout": "class OAuth2AuthorizationCodeBearer(OAuth2):\n \"\"\"\n OAuth2 flow for authentication using a bearer token obtained with an OAuth2 full{"status": "ok", "stdout": "class OAuth2AuthorizationCodeBearer(OAuth2):\n \"\"\"\n OAuth2 flow for authentication using a bearer token obtained with an OAuth2 code\n flow. An instance of it would be used as a dependency.\n \"\"\"\n\n def __init__(\n self,\n authorizationUrl: str,\n tokenUrl: Annotated[\n str,\n Doc(\n \"\"\"\n The URL to obtain the OAuth2 token.\n \"\"\"\n ),\n ],\n refreshUrl: Annotated[\n Optional[str],\n Doc(\n \"\"\"\n The URL to refresh the token and obtain a new one.\n \"\"\"\n ),\n ] = 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 scopes: Annotated[\n Optional[Dict[str, str]],\n Doc(\n \"\"\"\n The OAuth2 scopes that would be required by the *path operations* that\n use this dependency.\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 OAuth2 authentication, it will automatically cancel the request and\n send the client an error.\n\n If `auto_error` is set to `False`, when the HTTP Auth |
| 23 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/security/api_key.py\"); s = p.read_text()\nold = \"\"\"from starlette.status import HTTP_403_FORBIDDEN\"\"\"\nnew = \"\"\"from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN\"\"\"\nassert s.count(old) == 1, (\"old text found this many times:\", s.count(old))\nt = s.replace(old, new); compile(t, str(p), \"exec\"); p.write_text(t)\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 24 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/security/api_key.py\"); s = p.read_text()\nold = \"\"\"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\"\"\"\nnew = \"\"\"class APIKeyBase(SecurityBase):\n @staticmethod\n def check_api_key(api_key: Optional[str], auto_error: bool, not_authenticated_status_code: int = HTTP_401_UNAUTHORIZED) -> Optional[str]:\n if not api_key:\n if auto_error:\n raise HTTPException(\n status_code=not_authenticated_status_code, detail=\"Not authenticated\"\n )\n return None\n return api_key\"\"\"\nassert s.count(old) == 1, (\"old text found this many times:\", s.count(old))\nt = s.replace(old, new); compile(t, str(p), \"exec\"); p.write_text(t)\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 25 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/security/api_key.py\"); s = p.read_text()\nold = \"\"\" 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 ):\"\"\"\nnew = \"\"\" 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 the API key is missing.\"),\n ] = HTTP_401_UNAUTHORIZED,\n ):\"\"\"\nassert s.count(old) == 1, (\"old text found this many times:\", s.count(old))\nt = s.replace(old, new); compile(t, str(p), \"exec\"); p.write_text(t)\nPY\n"
} | {"status": "error", "error_type": "CommandError", "error_message": " File \"<stdin>\", line 14\n Security scheme name.\nIndentationError: unexpected indent\n", full{"status": "error", "error_type": "CommandError", "error_message": " File \"<stdin>\", line 14\n Security scheme name.\nIndentationError: unexpected indent\n", "details": {"stdout": "", "stderr": " File \"<stdin>\", line 14\n Security scheme name.\nIndentationError: unexpected indent\n", "exit_code": 1}} |
| 26 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/security/api_key.py\"); s = p.read_text()\n# Find where APIKeyQuery.__init__ starts\nimport re\nmatch = re.search(r'class APIKeyQuery\\(APIKeyBase\\):.*?def __init__\\(', s, re.DOTALL)\nstart_line = match.start()\n# Let's just replace the whole __init__ block more carefully.\n# Or better, replace the parameters list.\n\n# Re-reading the file to be sure of the exact content.\n# Use a simpler replacement.\n# Since it's a lot of text, I'll use a different approach.\n# I'll just add the not_authenticated_status_code to the end of the arguments.\n\n# Finding the line with auto_error: bool,\n# and replacing it with auto_error: bool, not_authenticated_status_code: int = HTTP_401_UNAUTHORIZED,\n# But the current structure is Annotated[...]\n\n# Let's try replacing the specific part of __init__.\n# The current __init__ for APIKeyQuery ends with:\n# ] = True,\n# ):\n\n# Let's find the end of the __init__'s arguments.\n# I will search for the line just before the closing parenthesis of __init__.\n# Looking at previous sed output:\n# 101: ] = True,\n# 102: ):\n\n# Let's try this:\nlines = s.splitlines()\nfor i in range(len(lines)):\n if ' ] = True,' in lines[i] and ' ):' in lines[i+1]:\n lines[i] = lines[i] + \",\\n not_authenticated_status_code: Annotated[\\n int,\\n Doc(\\\"Status code to use when the API key is missing.\\\"),\\n ] = HTTP_401_UNAUTHORIZED\"\n break\np.write_text('\\n'.join(lines))\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 27 | run_command | {
"command": "sed -n '90,110p' fastapi/security/api_key.py"
} | {"status": "ok", "stdout": " If `auto_error` is set to `False`, when the query parameter is not\n available, instead of erroring out, the dependency result willfull{"status": "ok", "stdout": " 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 the API key is missing.\"),\n ] = HTTP_401_UNAUTHORIZED\n ):\n self.model: APIKey = APIKey(\n **{\"in\": APIKeyIn.query},\n name=name,\n description=description,\n", "stderr": "", "exit_code": 0} |
| 28 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/security/api_key.py\"); s = p.read_text()\n# Fixing the double comma\nt = s.replace(\" ] = True,,\", \" ] = True,\")\np.write_text(t)\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 29 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/security/api_key.py\"); s = p.read_text()\n# Need to update self.not_authenticated_status_code = not_authenticated_status_code in __init__\n# and update __call__ to use it in check_api_key\n\n# Let's find where self.auto_error = auto_error is and add it there.\nimport re\n# For APIKeyQuery\n# The __init__ method ends around line 115.\n# Let's just use replace.\nold = \"\"\" self.scheme_name = scheme_name or self.__class__.__name__\n self.auto_error = auto_error\"\"\"\nnew = \"\"\" self.scheme_name = scheme_name or self.__class__.__name__\n self.auto_error = auto_error\n self.not_authenticated_status_code = not_authenticated_status_code\"\"\"\n# This might occur in multiple classes (APIKeyQuery, APIKeyHeader, APIKeyCookie).\n# Let's be more careful.\nt = s.replace(old, new)\np.write_text(t)\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 30 | run_command | {
"command": "sed -n '110,120p' fastapi/security/api_key.py"
} | {"status": "ok", "stdout": " description=description,\n )\n self.scheme_name = scheme_name or self.__class__.__name__\n self.auto_error = auto_error\n self.not_full{"status": "ok", "stdout": " description=description,\n )\n self.scheme_name = scheme_name or self.__class__.__name__\n self.auto_error = auto_error\n self.not_authenticated_status_code = not_authenticated_status_code\n\n async def __call__(self, request: Request) -> Optional[str]:\n api_key = request.query_params.get(self.model.name)\n return self.check_api_key(api_key, self.auto_error)\n\n\n", "stderr": "", "exit_code": 0} |
| 31 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/security/api_key.py\"); s = p.read_text()\n# Update APIKeyQuery.__call__\nold = \"\"\" async def __call__(self, request: Request) -> Optional[str]:\n api_key = request.query_params.get(self.model.name)\n return self.check_api_key(api_key, self.auto_error)\"\"\"\nnew = \"\"\" async def __call__(self, request: Request) -> Optional[str]:\n api_key = request.query_params.get(self.model.name)\n return self.check_api_key(api_key, self.auto_error, self.not_authenticated_status_code)\"\"\"\nt = s.replace(old, new)\np.write_text(t)\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 32 | run_command | {
"command": "sed -n '115,125p' fastapi/security/api_key.py"
} | {"status": "ok", "stdout": "\n async def __call__(self, request: Request) -> Optional[str]:\n api_key = request.query_params.get(self.model.name)\n return self.full{"status": "ok", "stdout": "\n async def __call__(self, request: Request) -> Optional[str]:\n api_key = request.query_params.get(self.model.name)\n return self.check_api_key(api_key, self.auto_error, self.not_authenticated_status_code)\n\n\nclass APIKeyHeader(APIKeyBase):\n \"\"\"\n API key authentication using a header.\n\n This defines the name of the header that should be provided in the request with\n", "stderr": "", "exit_code": 0} |
| 33 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/security/api_key.py\"); s = p.read_text()\n# Now repeat for APIKeyHeader and APIKeyCookie.\n# I'll use a similar approach for adding the parameter to __init__.\nlines = s.splitlines()\nfor i in range(len(lines)):\n if ' ] = True,' in lines[i] and ' ):' in lines[i+1]:\n # This was already done for APIKeyQuery. Let's find other occurrences.\n pass\n\n# Better: use a regex or a more robust replacement for all APIKeyBase subclasses.\n# Let's just do them one by one.\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 34 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/security/api_key.py\"); s = p.read_text()\n\n# APIKeyHeader __init__\n# We want to add not_authenticated_status_code before the closing parenthesis of __init__.\n# Let's find the end of APIKeyHeader.__init__ and APIKeyCookie.__init__.\n\n# Looking at sed output for APIKeyHeader:\n# line 200: ] = True,\n# line 201: ):\n\nimport re\n# find all blocks that look like the end of an __init__ in these classes\n# This is tricky. Let's just search for the pattern again but skip the first one.\n\n# Let's use a simpler approach: replace the specific lines.\n# For APIKeyHeader:\n# 198: ] = True,\n# 199: ):\n# Wait, the line numbers changed because I added lines.\n\n# Let's search for the pattern \" ] = True,\\n ):\"\n# and replace it with the new pattern.\n# But I already did it once and I don't want to do it again for APIKeyQuery.\n# Actually, if I do it for all, it might be easier.\n\n# Let's remove the previously added not_authenticated_status_code and do it for all.\n# No, that's not good.\n\n# Let's find all occurrences of:\n# ] = True,\n# ):\n# and replace it with:\n# ] = True,\n# not_authenticated_status_code: Annotated[\n# int,\n# Doc(\"Status code to use when the API key is missing.\"),\n# ] = HTTP_401_UNAUTHORIZED,\n# ):\n\n# Wait, I'll just use replace and then fix the double comma if I messed up.\n# But I only did it for APIKeyQuery because I didn't use replace.\n\n# Let's use a regex to find all the __init__ closings.\n# The pattern is:\n# r'(\\s+\\] = True,)\\n(\\s+):'\n# This is too generic.\n\n# Let's just find the classes and their __init__ methods.\n# For APIKeyHeader\n# Search for \"class APIKeyHeader\" and then the first \" ):\" after it.\n\n# Actually, I can just use sed to add the parameter to all __init__ methods.\n# Since all three classes have the same structure for __init__.\n\n# Let's try a different way. I'll read the file and use a state machine to find __init__ closings.\nlines = s.splitlines()\nnew_lines = []\nin_init = False\nfor line in lines:\n new_lines.append(line)\n if 'def __init__(' in line:\n in_init = True\n if in_init and line.strip() == '):':\n # This is the end of the __init__ arguments.\n # But we want to insert BEFORE the closing parenthesis.\n # So we need to remove the last line added.\n new_lines.pop()\n new_lines.append(' not_authenticated_status_code: Annotated[')\n new_lines.append(' int,')\n new_lines.append(' Doc(\"Status code to use when the API key is missing.\"),')\n new_lines.append(' ] = HTTP_401_UNAUTHORIZED,')\n new_lines.append(' ):')\n in_init = False\np.write_text('\\n'.join(new_lines))\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 35 | run_command | {
"command": "sed -n '100,300p' fastapi/security/api_key.py"
} | {"status": "ok", "stdout": " ),\n ] = True,\n not_authenticated_status_code: Annotated[\n int,\n Doc(\"Status code to use when the API key is missing.\"),\n ] =full{"status": "ok", "stdout": " ),\n ] = True,\n not_authenticated_status_code: Annotated[\n int,\n Doc(\"Status code to use when the API key is missing.\"),\n ] = HTTP_401_UNAUTHORIZED\n not_authenticated_status_code: Annotated[\n int,\n Doc(\"Status code to use when the API key is missing.\"),\n ] = HTTP_401_UNAUTHORIZED,\n ):\n self.model: APIKey = APIKey(\n **{\"in\": APIKeyIn.query},\n name=name,\n description=description,\n )\n self.scheme_name = scheme_name or self.__class__.__name__\n self.auto_error = auto_error\n self.not_authenticated_status_code = not_authenticated_status_code\n\n async def __call__(self, request: Request) -> Optional[str]:\n api_key = request.query_params.get(self.model.name)\n return self.check_api_key(api_key, self.auto_error, self.not_authenticated_status_code)\n\n\nclass APIKeyHeader(APIKeyBase):\n \"\"\"\n API key authentication using a header.\n\n This defines the name of the header that should be provided in the request with\n the API key and integrates that into the OpenAPI documentation. It extracts\n the key value sent in the header automatically and provides it as the dependency\n result. But it doesn't define how to send that 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 APIKeyHeader\n\n app = FastAPI()\n\n header_scheme = APIKeyHeader(name=\"x-key\")\n\n\n @app.get(\"/items/\")\n async def read_items(key: str = Depends(header_scheme)):\n return {\"key\": key}\n ```\n \"\"\"\n\n def __init__(\n self,\n *,\n name: Annotated[str, Doc(\ |
| 36 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/security/api_key.py\"); s = p.read_text()\n# Clean up APIKeyQuery's __init__ and fix all __call__ methods.\n\n# First, let's fix the double not_authenticated_status_code in APIKeyQuery.__init__\n# I will just read the file and use a replacement for the double block.\nold = \"\"\" not_authenticated_status_code: Annotated[\n int,\n Doc(\"Status code to use when the API key is missing.\"),\n ] = HTTP_401_UNAUTHORIZED\n not_authenticated_status_code: Annotated[\n int,\n Doc(\"Status code to use when the API key is missing.\"),\n ] = HTTP_401_UNAUTHORIZED,\"\"\"\nnew = \"\"\" not_authenticated_status_code: Annotated[\n int,\n Doc(\"Status code to use when the API key is missing.\"),\n ] = HTTP_401_UNAUTHORIZED,\"\"\"\nt = s.replace(old, new)\n\n# Now let's fix all __call__ methods to use self.not_authenticated_status_code\n# The pattern is: return self.check_api_key(api_key, self.auto_error)\n# We want to replace it with: return self.check_api_key(api_key, self.auto_error, self.not_authenticated_status_code)\nimport re\nt = re.sub(r'return self.check_api_key\\(api_key, self.auto_error\\)', r'return self.check_api_key(api_key, self.auto_error, self.not_authenticated_status_code)', t)\n\np.write_text(t)\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
diff --git a/fastapi/security/api_key.py b/fastapi/security/api_key.py
index 496c815a..39ccad19 100644
--- a/fastapi/security/api_key.py
+++ b/fastapi/security/api_key.py
@@ -5,17 +5,17 @@ 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(api_key: Optional[str], auto_error: bool, not_authenticated_status_code: int = HTTP_401_UNAUTHORIZED) -> Optional[str]:
if not api_key:
if auto_error:
raise HTTPException(
- status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
+ status_code=not_authenticated_status_code, detail="Not authenticated"
)
return None
return api_key
@@ -99,6 +99,10 @@ class APIKeyQuery(APIKeyBase):
"""
),
] = True,
+ not_authenticated_status_code: Annotated[
+ int,
+ Doc("Status code to use when the API key is missing."),
+ ] = HTTP_401_UNAUTHORIZED,
):
self.model: APIKey = APIKey(
**{"in": APIKeyIn.query},
@@ -107,10 +111,11 @@ 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
async def __call__(self, request: Request) -> Optional[str]:
api_key = request.query_params.get(self.model.name)
- return self.check_api_key(api_key, self.auto_error)
+ return self.check_api_key(api_key, self.auto_error, self.not_authenticated_status_code)
class APIKeyHeader(APIKeyBase):
@@ -187,6 +192,10 @@ class APIKeyHeader(APIKeyBase):
"""
),
] = True,
+ not_authenticated_status_code: Annotated[
+ int,
+ Doc("Status code to use when the API key is missing."),
+ ] = HTTP_401_UNAUTHORIZED,
):
self.model: APIKey = APIKey(
**{"in": APIKeyIn.header},
@@ -195,10 +204,11 @@ class APIKeyHeader(APIKeyBase):
)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
+ self.not_authenticated_status_code = not_authenticated_status_code
async def __call__(self, request: Request) -> Optional[str]:
api_key = request.headers.get(self.model.name)
- return self.check_api_key(api_key, self.auto_error)
+ return self.check_api_key(api_key, self.auto_error, self.not_authenticated_status_code)
class APIKeyCookie(APIKeyBase):
@@ -275,6 +285,10 @@ class APIKeyCookie(APIKeyBase):
"""
),
] = True,
+ not_authenticated_status_code: Annotated[
+ int,
+ Doc("Status code to use when the API key is missing."),
+ ] = HTTP_401_UNAUTHORIZED,
):
self.model: APIKey = APIKey(
**{"in": APIKeyIn.cookie},
@@ -283,7 +297,8 @@ class APIKeyCookie(APIKeyBase):
)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
+ self.not_authenticated_status_code = not_authenticated_status_code
async def __call__(self, request: Request) -> Optional[str]:
api_key = request.cookies.get(self.model.name)
- return self.check_api_key(api_key, self.auto_error)
+ return self.check_api_key(api_key, self.auto_error, self.not_authenticated_status_code)
\ No newline at end of file
.F
=================================== FAILURES ===================================
_________________________ test_security_api_key_no_key _________________________
def test_security_api_key_no_key():
client = TestClient(app)
response = client.get("/users/me")
assert response.status_code == 401, response.text
assert response.json() == {"detail": "Not authenticated"}
> assert response.headers["WWW-Authenticate"] == "APIKey"
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_security_api_key_cookie.py:37:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = Headers({'content-length': '30', 'content-type': 'application/json'})
key = 'WWW-Authenticate'
def __getitem__(self, key: str) -> str:
"""
Return a single header value.
If there are multiple headers with the same key, then we concatenate
them with commas. See: https://tools.ietf.org/html/rfc7230#section-3.2.2
"""
normalized_key = key.lower().encode(self.encoding)
items = [
header_value.decode(self.encoding)
for _, header_key, header_value in self._list
if header_key == normalized_key
]
if items:
return ", ".join(items)
> raise KeyError(key)
E KeyError: 'WWW-Authenticate'
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/fastapi/lib/python3.13/site-packages/httpx/_models.py:302: KeyError
=============================== 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