← eval3_submission_v2

fastapi_5624

failed WRONG_FIX wrong_fix · 13 tool calls · 65 s · fastapi/fastapi

Task input

🐛 Fix hierarchical security scope propagation

see defect #5623

Tool calls (14)

#ToolArgumentsResult
1run_command
{
 "command": "grep -rn \"scope\" fastapi/openapi/utils.py"
}
{"status": "ok", "stdout": "91: operation_security.append({security_name: security_requirement.scopes})\n", "exit_code": 0}
full
{"status": "ok", "stdout": "91:        operation_security.append({security_name: security_requirement.scopes})\n", "exit_code": 0}
2run_command
{
 "command": "grep -rn \"security\" fastapi/openapi/utils.py | head -20"
}
{"status": "ok", "stdout": "78:def get_openapi_security_definitions(\n81: security_definitions = {}\n82: operation_security = []\n83: for security_requirement i
full
{"status": "ok", "stdout": "78:def get_openapi_security_definitions(\n81:    security_definitions = {}\n82:    operation_security = []\n83:    for security_requirement in flat_dependant.security_requirements:\n84:        security_definition = jsonable_encoder(\n85:            security_requirement.security_scheme.model,\n89:        security_name = security_requirement.security_scheme.scheme_name\n90:        security_definitions[security_name] = security_definition\n91:        operation_security.append({security_name: security_requirement.scopes})\n92:    return security_definitions, operation_security\n261:    security_schemes: Dict[str, Any] = {}\n277:            security_definitions, operation_security = get_openapi_security_definitions(\n280:            if operation_security:\n281:                operation.setdefault(\"security\", []).extend(operation_security)\n282:            if security_definitions:\n283:                security_schemes.update(security_definitions)\n319:                            cb_security_schemes,\n433:    return path, security_schemes, definitions\n519:                path, security_schemes, path_definitions = result\n522:                if security_schemes:\n", "exit_code": 0}
3read_file
{
 "filepath": "fastapi/openapi/utils.py",
 "start_line": 70,
 "end_line": 110
}
{"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": " \"2XX\": \"Success\",\n \"3XX\": \"Redirection\",\n \"4XX\": \"Client Error\",\n \"5XX\":
full
{"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": "    \"2XX\": \"Success\",\n    \"3XX\": \"Redirection\",\n    \"4XX\": \"Client Error\",\n    \"5XX\": \"Server Error\",\n    \"DEFAULT\": \"Default Response\",\n}\n\n\ndef get_openapi_security_definitions(\n    flat_dependant: Dependant,\n) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]:\n    security_definitions = {}\n    operation_security = []\n    for security_requirement in flat_dependant.security_requirements:\n        security_definition = jsonable_encoder(\n            security_requirement.security_scheme.model,\n            by_alias=True,\n            exclude_none=True,\n        )\n        security_name = security_requirement.security_scheme.scheme_name\n        security_definitions[security_name] = security_definition\n        operation_security.append({security_name: security_requirement.scopes})\n    return security_definitions, operation_security\n\n\ndef _get_openapi_operation_parameters(\n    *,\n    dependant: Dependant,\n    model_name_map: ModelNameMap,\n    field_mapping: Dict[\n        Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue\n    ],\n    separate_input_output_schemas: bool = True,\n) -> List[Dict[str, Any]]:\n    parameters = []\n    flat_dependant = get_flat_dependant(dependant, skip_repeats=True)\n    path_params = _get_flat_fields_from_params(flat_dependant.path_params)\n    query_params = _get_flat_fields_from_params(flat_dependant.query_params)\n    header_params = _get_flat_fields_from_params(flat_dependant.header_params)\n    cookie_params = _get_flat_fields_from_params(flat_dependant.cookie_params)\n    parameter_groups = [\n", "start_line": 70, "end_line": 110, "total_lines": 558, "is_truncated": false}
4run_command
{
 "command": "grep -rn \"security_requirements\" fastapi/dependencies/utils.py"
}
{"status": "ok", "stdout": "156: security_requirements=dependant.security_requirements.copy(),\n171: flat_dependant.security_requirements.extend(flat_sub.securi
full
{"status": "ok", "stdout": "156:        security_requirements=dependant.security_requirements.copy(),\n171:        flat_dependant.security_requirements.extend(flat_sub.security_requirements)\n257:        dependant.security_requirements.append(security_requirement)\n", "exit_code": 0}
5read_file
{
 "end_line": 270,
 "filepath": "fastapi/dependencies/utils.py",
 "start_line": 140
}
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "def get_flat_dependant(\n dependant: Dependant,\n *,\n skip_repeats: bool = False,\n v
full
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "def get_flat_dependant(\n    dependant: Dependant,\n    *,\n    skip_repeats: bool = False,\n    visited: Optional[List[DependencyCacheKey]] = None,\n) -> Dependant:\n    if visited is None:\n        visited = []\n    visited.append(dependant.cache_key)\n\n    flat_dependant = Dependant(\n        path_params=dependant.path_params.copy(),\n        query_params=dependant.query_params.copy(),\n        header_params=dependant.header_params.copy(),\n        cookie_params=dependant.cookie_params.copy(),\n        body_params=dependant.body_params.copy(),\n        security_requirements=dependant.security_requirements.copy(),\n        use_cache=dependant.use_cache,\n        path=dependant.path,\n    )\n    for sub_dependant in dependant.dependencies:\n        if skip_repeats and sub_dependant.cache_key in visited:\n            continue\n        flat_sub = get_flat_dependant(\n            sub_dependant, skip_repeats=skip_repeats, visited=visited\n        )\n        flat_dependant.path_params.extend(flat_sub.path_params)\n        flat_dependant.query_params.extend(flat_sub.query_params)\n        flat_dependant.header_params.extend(flat_sub.header_params)\n        flat_dependant.cookie_params.extend(flat_sub.cookie_params)\n        flat_dependant.body_params.extend(flat_sub.body_params)\n        flat_dependant.security_requirements.extend(flat_sub.security_requirements)\n    return flat_dependant\n\n\ndef _get_flat_fields_from_params(fields: List[ModelField]) -> List[ModelField]:\n    if not fields:\n        return fields\n    first_field = fields[0]\n    if len(fields) == 1 and _is_model_class(first_field.type_):\n        fields_to_extract = get_cached_model_fields(first_field.type_)\n        return fields_to_extract\n    return fields\n\n\ndef get_flat_params(dependant: Dependant) -> List[ModelField]:\n    flat_dependant = get_flat_dependant(dependant, skip_repeats=True)\n    path_params = _get_flat_fie
6read_file
{
 "end_line": 310,
 "filepath": "fastapi/dependencies/utils.py",
 "start_line": 271
}
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": " and param_details.depends.scope == \"function\"\n ):\n assert dependant.call\n raise
full
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "                and param_details.depends.scope == \"function\"\n            ):\n                assert dependant.call\n                raise DependencyScopeError(\n                    f'The dependency \"{dependant.call.__name__}\" has a scope of '\n                    '\"request\", it cannot depend on dependencies with scope \"function\".'\n                )\n            use_security_scopes = security_scopes or []\n            if isinstance(param_details.depends, params.Security):\n                if param_details.depends.scopes:\n                    use_security_scopes.extend(param_details.depends.scopes)\n            sub_dependant = get_dependant(\n                path=path,\n                call=param_details.depends.dependency,\n                name=param_name,\n                security_scopes=use_security_scopes,\n                use_cache=param_details.depends.use_cache,\n                scope=param_details.depends.scope,\n            )\n            dependant.dependencies.append(sub_dependant)\n            continue\n        if add_non_field_param_to_dependency(\n            param_name=param_name,\n            type_annotation=param_details.type_annotation,\n            dependant=dependant,\n        ):\n            assert param_details.field is None, (\n                f\"Cannot specify multiple FastAPI annotations for {param_name!r}\"\n            )\n            continue\n        assert param_details.field is not None\n        if isinstance(\n            param_details.field.field_info, (params.Body, temp_pydantic_v1_params.Body)\n        ):\n            dependant.body_params.append(param_details.field)\n        else:\n            add_param_to_fields(field=param_details.field, dependant=dependant)\n    return dependant\n\n\n", "start_line": 271, "end_line": 310, "total_lines": 1024, "is_truncated": false}
7run_command
{
 "command": "grep -rn \"SecurityRequirement\" fastapi/dependencies/models.py"
}
{"status": "ok", "stdout": "19:class SecurityRequirement:\n32: security_requirements: List[SecurityRequirement] = field(default_factory=list)\n", "exit_code": 0
full
{"status": "ok", "stdout": "19:class SecurityRequirement:\n32:    security_requirements: List[SecurityRequirement] = field(default_factory=list)\n", "exit_code": 0}
8read_file
{
 "end_line": 40,
 "filepath": "fastapi/dependencies/models.py",
 "start_line": 1
}
{"status": "ok", "filepath": "fastapi/dependencies/models.py", "content": "import inspect\nimport sys\nfrom dataclasses import dataclass, field\nfrom functools
full
{"status": "ok", "filepath": "fastapi/dependencies/models.py", "content": "import inspect\nimport sys\nfrom dataclasses import dataclass, field\nfrom functools import cached_property\nfrom typing import Any, Callable, List, Optional, Sequence, Union\n\nfrom fastapi._compat import ModelField\nfrom fastapi.security.base import SecurityBase\nfrom fastapi.types import DependencyCacheKey\nfrom typing_extensions import Literal\n\nif sys.version_info >= (3, 13):  # pragma: no cover\n    from inspect import iscoroutinefunction\nelse:  # pragma: no cover\n    from asyncio import iscoroutinefunction\n\n\n@dataclass\nclass SecurityRequirement:\n    security_scheme: SecurityBase\n    scopes: Optional[Sequence[str]] = None\n\n\n@dataclass\nclass Dependant:\n    path_params: List[ModelField] = field(default_factory=list)\n    query_params: List[ModelField] = field(default_factory=list)\n    header_params: List[ModelField] = field(default_factory=list)\n    cookie_params: List[ModelField] = field(default_factory=list)\n    body_params: List[ModelField] = field(default_factory=list)\n    dependencies: List[\"Dependant\"] = field(default_factory=list)\n    security_requirements: List[SecurityRequirement] = field(default_factory=list)\n    name: Optional[str] = None\n    call: Optional[Callable[..., Any]] = None\n    request_param_name: Optional[str] = None\n    websocket_param_name: Optional[str] = None\n    http_connection_param_name: Optional[str] = None\n    response_param_name: Optional[str] = None\n    background_tasks_param_name: Optional[str] = None\n    security_scopes_param_name: Optional[str] = None\n", "start_line": 1, "end_line": 40, "total_lines": 83, "is_truncated": false}
9run_command
{
 "command": "cat > /tmp/repro.py <<'EOF'\nfrom fastapi import FastAPI, Depends, Security\nfrom fastapi.security import OAuth2PasswordBearer\nfrom fastapi.openapi.utils import get_openapi_route\nfrom fastapi.dependencies.utils import get_dependant\n\napp = FastAPI()\noauth2_scheme = OAuth2PasswordBearer(tokenUrl=\"token\")\n\ndef dep_inner(security=Security(oauth2_scheme, scopes=[\"inner\"])):\n    return security\n\ndef dep_outer(security=Security(oauth2_scheme, scopes=[\"outer\"]), inner=Depends(dep_inner)):\n    return security\n\n@app.get(\"/test\")\ndef test_route(outer=Depends(dep_outer)):\n    return {\"message\": \"hello\"}\n\n# Get the dependant for the route\n# In a real app, this is done by FastAPI during startup\ndependant = get_dependant(\n    path=\"/test\",\n    call=test_route,\n    security_scopes=None\n)\n\n# The route has a dependency dep_outer, which has a dependency dep_inner\n# Let's see what the flat dependant has for security requirements\nfrom fastapi.dependencies.utils import get_flat_dependant\nflat_dependant = get_flat_dependant(dependant)\n\nprint(f\"Security requirements: {flat_dependant.security_requirements}\")\nfor req in flat_dependant.security_requirements:\n    print(f\"Scheme: {req.security_scheme.scheme_name}, Scopes: {req.scopes}\")\n\n# Expected: both \"inner\" and \"outer\" scopes should be present for the same scheme.\n# Current behavior: they are separate SecurityRequirement objects.\n# OpenAPI expects: { \"oauth2\": [\"inner\", \"outer\"] }\nEOF\npython3 /tmp/repro.py"
}
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "Traceback (most recen
full
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "Traceback (most recent call last):\n  File \"/tmp/swe_work/eval3_submission_v2/fastapi_5624/a/tmp/repro.py\", line 3, in <module>\n    from fastapi.openapi.utils import get_openapi_route\nImportError: cannot import name 'get_openapi_route' from 'fastapi.openapi.utils' (/workspace/fastapi/openapi/utils.py)\n", "exit_code": 1}}
10run_command
{
 "command": "cat > /tmp/repro.py <<'EOF'\nfrom fastapi import FastAPI, Depends, Security\nfrom fastapi.security import OAuth2PasswordBearer\nfrom fastapi.dependencies.utils import get_dependant, get_flat_dependant\n\napp = FastAPI()\noauth2_scheme = OAuth2PasswordBearer(tokenUrl=\"token\")\n\ndef dep_inner(security=Security(oauth2_scheme, scopes=[\"inner\"])):\n    return security\n\ndef dep_outer(security=Security(oauth2_scheme, scopes=[\"outer\"]), inner=Depends(dep_inner)):\n    return security\n\n@app.get(\"/test\")\ndef test_route(outer=Depends(dep_outer)):\n    return {\"message\": \"hello\"}\n\ndependant = get_dependant(\n    path=\"/test\",\n    call=test_route,\n    security_scopes=None\n)\n\nflat_dependant = get_flat_dependant(dependant)\n\nprint(f\"Security requirements: {flat_dependant.security_requirements}\")\nfor req in flat_dependant.security_requirements:\n    print(f\"Scheme: {req.security_scheme.scheme_name}, Scopes: {req.scopes}\")\nEOF\npython3 /tmp/repro.py"
}
{"status": "ok", "stdout": "Security requirements: [SecurityRequirement(security_scheme=<fastapi.security.oauth2.OAuth2PasswordBearer object at 0x7891a8e10d70>,
full
{"status": "ok", "stdout": "Security requirements: [SecurityRequirement(security_scheme=<fastapi.security.oauth2.OAuth2PasswordBearer object at 0x7891a8e10d70>, scopes=['outer']), SecurityRequirement(security_scheme=<fastapi.security.oauth2.OAuth2PasswordBearer object at 0x7891a8e10d70>, scopes=['inner'])]\nScheme: OAuth2PasswordBearer, Scopes: ['outer']\nScheme: OAuth2PasswordBearer, Scopes: ['inner']\n", "exit_code": 0}
11read_file
{
 "end_line": 100,
 "filepath": "fastapi/openapi/utils.py",
 "start_line": 70
}
{"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": " \"2XX\": \"Success\",\n \"3XX\": \"Redirection\",\n \"4XX\": \"Client Error\",\n \"5XX\":
full
{"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": "    \"2XX\": \"Success\",\n    \"3XX\": \"Redirection\",\n    \"4XX\": \"Client Error\",\n    \"5XX\": \"Server Error\",\n    \"DEFAULT\": \"Default Response\",\n}\n\n\ndef get_openapi_security_definitions(\n    flat_dependant: Dependant,\n) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]:\n    security_definitions = {}\n    operation_security = []\n    for security_requirement in flat_dependant.security_requirements:\n        security_definition = jsonable_encoder(\n            security_requirement.security_scheme.model,\n            by_alias=True,\n            exclude_none=True,\n        )\n        security_name = security_requirement.security_scheme.scheme_name\n        security_definitions[security_name] = security_definition\n        operation_security.append({security_name: security_requirement.scopes})\n    return security_definitions, operation_security\n\n\ndef _get_openapi_operation_parameters(\n    *,\n    dependant: Dependant,\n    model_name_map: ModelNameMap,\n    field_mapping: Dict[\n        Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue\n", "start_line": 70, "end_line": 100, "total_lines": 558, "is_truncated": false}
12edit_file
{
 "filepath": "fastapi/openapi/utils.py",
 "new_string": "    security_definitions = {}\n    operation_security_map = {}\n    for security_requirement in flat_dependant.security_requirements:\n        security_definition = jsonable_encoder(\n            security_requirement.security_scheme.model,\n            by_alias=True,\n            exclude_none=True,\n        )\n        security_name = security_requirement.security_scheme.scheme_name\n        security_definitions[security_name] = security_definition\n        if security_name not in operation_security_map:\n            operation_security_map[security_name] = []\n        if security_requirement.scopes:\n            operation_security_map[security_name].extend(security_requirement.scopes)\n    operation_security = [{name: scopes} for name, scopes in operation_security_map.items()]\n    return security_definitions, operation_security",
 "old_string": "    security_definitions = {}\n    operation_security = []\n    for security_requirement in flat_dependant.security_requirements:\n        security_definition = jsonable_encoder(\n            security_requirement.security_scheme.model,\n            by_alias=True,\n            exclude_none=True,\n        )\n        security_name = security_requirement.security_scheme.scheme_name\n        security_definitions[security_name] = security_definition\n        operation_security.append({security_name: security_requirement.scopes})\n    return security_definitions, operation_security"
}
{"status": "ok", "filepath": "fastapi/openapi/utils.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/openapi/utils.py\n+++ b/fastapi/openapi/u
full
{"status": "ok", "filepath": "fastapi/openapi/utils.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/openapi/utils.py\n+++ b/fastapi/openapi/utils.py\n@@ -79,7 +79,7 @@\n     flat_dependant: Dependant,\n ) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]:\n     security_definitions = {}\n-    operation_security = []\n+    operation_security_map = {}\n     for security_requirement in flat_dependant.security_requirements:\n         security_definition = jsonable_encoder(\n             security_requirement.security_scheme.model,\n@@ -88,7 +88,11 @@\n         )\n         security_name = security_requirement.security_scheme.scheme_name\n         security_definitions[security_name] = security_definition\n-        operation_security.append({security_name: security_requirement.scopes})\n+        if security_name not in operation_security_map:\n+            operation_security_map[security_name] = []\n+        if security_requirement.scopes:\n+            operation_security_map[security_name].extend(security_requirement.scopes)\n+    operation_security = [{name: scopes} for name, scopes in operation_security_map.items()]\n     return security_definitions, operation_security\n \n \n", "is_truncated": false}
13run_command
{
 "command": "cat > /tmp/repro_openapi.py <<'EOF'\nfrom fastapi import FastAPI, Depends, Security\nfrom fastapi.security import OAuth2PasswordBearer\nfrom fastapi.dependencies.utils import get_dependant, get_flat_dependant\nfrom fastapi.openapi.utils import get_openapi_security_definitions\n\noauth2_scheme = OAuth2PasswordBearer(tokenUrl=\"token\")\n\ndef dep_inner(security=Security(oauth2_scheme, scopes=[\"inner\"])):\n    return security\n\ndef dep_outer(security=Security(oauth2_scheme, scopes=[\"outer\"]), inner=Depends(dep_inner)):\n    return security\n\ndef test_route(outer=Depends(dep_outer)):\n    return {\"message\": \"hello\"}\n\ndependant = get_dependant(\n    path=\"/test\",\n    call=test_route,\n    security_scopes=None\n)\n\nflat_dependant = get_flat_dependant(dependant)\nsecurity_definitions, operation_security = get_openapi_security_definitions(flat_dependant)\n\nprint(f\"Operation security: {operation_security}\")\nEOF\npython3 /tmp/repro_openapi.py"
}
{"status": "ok", "stdout": "Operation security: [{'OAuth2PasswordBearer': ['outer', 'inner']}]\n", "exit_code": 0}
full
{"status": "ok", "stdout": "Operation security: [{'OAuth2PasswordBearer': ['outer', 'inner']}]\n", "exit_code": 0}
14submit_patch
[]
{"status": "ok", "patch_size": 1250, "files_changed": 1}
full
{"status": "ok", "patch_size": 1250, "files_changed": 1}

Patch

diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py
index dbc93d28..38f31e5c 100644
--- a/fastapi/openapi/utils.py
+++ b/fastapi/openapi/utils.py
@@ -79,7 +79,7 @@ def get_openapi_security_definitions(
     flat_dependant: Dependant,
 ) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]:
     security_definitions = {}
-    operation_security = []
+    operation_security_map = {}
     for security_requirement in flat_dependant.security_requirements:
         security_definition = jsonable_encoder(
             security_requirement.security_scheme.model,
@@ -88,7 +88,11 @@ def get_openapi_security_definitions(
         )
         security_name = security_requirement.security_scheme.scheme_name
         security_definitions[security_name] = security_definition
-        operation_security.append({security_name: security_requirement.scopes})
+        if security_name not in operation_security_map:
+            operation_security_map[security_name] = []
+        if security_requirement.scopes:
+            operation_security_map[security_name].extend(security_requirement.scopes)
+    operation_security = [{name: scopes} for name, scopes in operation_security_map.items()]
     return security_definitions, operation_security
 
 

Test output

show
F
=================================== FAILURES ===================================
_____________________ test_security_scopes_dont_propagate ______________________

    def test_security_scopes_dont_propagate():
        response = client.get("/scopes")
        assert response.status_code == 200
>       assert response.json() == {
            "dep1": ["scope3", "scope1"],
            "dep2": ["scope3", "scope2"],
        }
E       AssertionError: assert {'dep1': ['sc...1', 'scope2']} == {'dep1': ['sc...3', 'scope2']}
E         
E         Differing items:
E         {'dep1': ['scope3', 'scope1', 'scope2']} != {'dep1': ['scope3', 'scope1']}
E         {'dep2': ['scope3', 'scope1', 'scope2']} != {'dep2': ['scope3', 'scope2']}
E         Use -v to get more diff

tests/test_security_scopes_dont_propagate.py:42: AssertionError
=============================== warnings summary ===============================
../../../../../../kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45
  /kaggle/tmp/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 warning in 0.72s