diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index bbcbc74b7..33bf462bd 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,11 +7,24 @@ hide: ## Latest Changes +## 0.120.4 + +### Fixes + +* 🐛 Fix security schemes in OpenAPI when added at the top level app. PR [#14266](https://github.com/fastapi/fastapi/pull/14266) by [@YuriiMotov](https://github.com/YuriiMotov). + +## 0.120.3 + ### Refactors +* ♻️ Reduce internal cyclic recursion in dependencies, from 2 functions calling each other to 1 calling itself. PR [#14256](https://github.com/fastapi/fastapi/pull/14256) by [@tiangolo](https://github.com/tiangolo). * ♻️ Refactor internals of dependencies, simplify code and remove `get_param_sub_dependant`. PR [#14255](https://github.com/fastapi/fastapi/pull/14255) by [@tiangolo](https://github.com/tiangolo). * ♻️ Refactor internals of dependencies, simplify using dataclasses. PR [#14254](https://github.com/fastapi/fastapi/pull/14254) by [@tiangolo](https://github.com/tiangolo). +### Docs + +* 📝 Update note for untranslated pages. PR [#14257](https://github.com/fastapi/fastapi/pull/14257) by [@YuriiMotov](https://github.com/YuriiMotov). + ## 0.120.2 ### Fixes diff --git a/docs/missing-translation.md b/docs/missing-translation.md index c2882e90e..bfff84766 100644 --- a/docs/missing-translation.md +++ b/docs/missing-translation.md @@ -1,7 +1,9 @@ /// warning -The current page still doesn't have a translation for this language. +This page hasn’t been translated into your language yet. 🌍 -But you can help translating it: [Contributing](https://fastapi.tiangolo.com/contributing/){.internal-link target=_blank}. +We’re currently switching to an automated translation system 🤖, which will help keep all translations complete and up to date. + +Learn more: [Contributing – Translations](https://fastapi.tiangolo.com/contributing/#translations){.internal-link target=_blank} /// diff --git a/fastapi/__init__.py b/fastapi/__init__.py index a4c17a6bd..93555bfdc 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.120.2" +__version__ = "0.120.4" from starlette import status as status diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index e31dab4be..6e808cd19 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -251,6 +251,14 @@ def get_dependant( path_param_names = get_path_param_names(path) endpoint_signature = get_typed_signature(call) signature_params = endpoint_signature.parameters + if isinstance(call, SecurityBase): + use_scopes: List[str] = [] + if isinstance(call, (OAuth2, OpenIdConnect)): + use_scopes = security_scopes + security_requirement = SecurityRequirement( + security_scheme=call, scopes=use_scopes + ) + dependant.security_requirements.append(security_requirement) for param_name, param in signature_params.items(): is_path_param = param_name in path_param_names param_details = analyze_param( @@ -279,16 +287,6 @@ def get_dependant( use_cache=param_details.depends.use_cache, scope=param_details.depends.scope, ) - if isinstance(param_details.depends.dependency, SecurityBase): - use_scopes: List[str] = [] - if isinstance( - param_details.depends.dependency, (OAuth2, OpenIdConnect) - ): - use_scopes = use_security_scopes - security_requirement = SecurityRequirement( - security_scheme=param_details.depends.dependency, scopes=use_scopes - ) - sub_dependant.security_requirements.append(security_requirement) dependant.dependencies.append(sub_dependant) continue if add_non_field_param_to_dependency( diff --git a/tests/test_top_level_security_scheme_in_openapi.py b/tests/test_top_level_security_scheme_in_openapi.py new file mode 100644 index 000000000..e2de31af5 --- /dev/null +++ b/tests/test_top_level_security_scheme_in_openapi.py @@ -0,0 +1,60 @@ +# Test security scheme at the top level, including OpenAPI +# Ref: https://github.com/fastapi/fastapi/discussions/14263 +# Ref: https://github.com/fastapi/fastapi/issues/14271 +from fastapi import Depends, FastAPI +from fastapi.security import HTTPBearer +from fastapi.testclient import TestClient +from inline_snapshot import snapshot + +app = FastAPI() + +bearer_scheme = HTTPBearer() + + +@app.get("/", dependencies=[Depends(bearer_scheme)]) +async def get_root(): + return {"message": "Hello, World!"} + + +client = TestClient(app) + + +def test_get_root(): + response = client.get("/", headers={"Authorization": "Bearer token"}) + assert response.status_code == 200, response.text + assert response.json() == {"message": "Hello, World!"} + + +def test_get_root_no_token(): + response = client.get("/") + assert response.status_code == 403, response.text + assert response.json() == {"detail": "Not authenticated"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "get": { + "summary": "Get Root", + "operationId": "get_root__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "security": [{"HTTPBearer": []}], + } + } + }, + "components": { + "securitySchemes": {"HTTPBearer": {"type": "http", "scheme": "bearer"}} + }, + } + )