← eval8_submission_v1e

fastapi_5624

failed WRONG_FIX wrong_fix · 31 tool calls · 0 s · fastapi/fastapi

Task input

🐛 Fix hierarchical security scope propagation

see defect #5623

Tool calls (0)

#ToolArgumentsResult
No trace captured.

Patch

diff --git a/tests/test_hierarchical_security_scopes.py b/tests/test_hierarchical_security_scopes.py
new file mode 100644
index 00000000..20f282c1
--- /dev/null
+++ b/tests/test_hierarchical_security_scopes.py
@@ -0,0 +1,114 @@
+from typing import List
+from fastapi import Depends, FastAPI, Security
+from fastapi.security import SecurityScopes, OAuth2PasswordBearer
+from fastapi.testclient import TestClient
+
+app = FastAPI()
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
+
+def get_scopes(security_scopes: SecurityScopes):
+    return security_scopes.scopes
+
+def get_user(security_scopes: SecurityScopes, scopes: List[str] = Depends(get_scopes)):
+    return security_scopes.scopes, scopes
+
+@app.get("/test")
+def read_test(
+    user_data: tuple = Security(get_user, scopes=["root_scope"]),
+):
+    return {"user_scopes": user_data[0], "dep_scopes": user_data[1]}
+
+client = TestClient(app)
+
+def test_hierarchical_scopes():
+    response = client.get("/test", headers={"authorization": "Bearer token"})
+    assert response.status_code == 200
+    # Both should have ["root_scope"]
+    assert response.json() == {"user_scopes": ["root_scope"], "dep_scopes": ["root_scope"]}
+
+def test_hierarchical_scopes_nested():
+    def get_nested_scopes(security_scopes: SecurityScopes):
+        return security_scopes.scopes
+
+    def get_nested_user(
+        security_scopes: SecurityScopes, 
+        nested_scopes: List[str] = Depends(get_nested_scopes)
+    ):
+        return security_scopes.scopes, nested_scopes
+
+    @app.get("/nested")
+    def read_nested(
+        user_data: tuple = Security(get_nested_user, scopes=["nested_scope"]),
+    ):
+        return {"user_scopes": user_data[0], "dep_scopes": user_data[1]}
+
+    response = client.get("/nested", headers={"authorization": "Bearer token"})
+    assert response.status_code == 200
+    assert response.json() == {"user_scopes": ["nested_scope"], "dep_scopes": ["nested_scope"]}
+
+def test_hierarchical_scopes_complex():
+    def get_deep_scopes(security_scopes: SecurityScopes):
+        return security_scopes.scopes
+
+    def get_mid_user(
+        security_scopes: SecurityScopes, 
+        deep_scopes: List[str] = Depends(get_deep_scopes)
+    ):
+        return security_scopes.scopes, deep_scopes
+
+    @app.get("/complex")
+    def read_complex(
+        user_data: tuple = Security(get_mid_user, scopes=["complex_scope"]),
+    ):
+        return {"user_scopes": user_data[0], "dep_scopes": user_data[1]}
+
+    response = client.get("/complex", headers={"authorization": "Bearer token"})
+    assert response.status_code == 200
+    assert response.json() == {"user_scopes": ["complex_scope"], "dep_scopes": ["complex_scope"]}
+
+def test_hierarchical_scopes_multiple_levels():
+    def get_level_3(security_scopes: SecurityScopes):
+        return security_scopes.scopes
+
+    def get_level_2(security_scopes: SecurityScopes, l3: List[str] = Depends(get_level_3)):
+        return security_scopes.scopes, l3
+
+    def get_level_1(security_scopes: SecurityScopes, l2: tuple = Depends(get_level_2)):
+        return security_scopes.scopes, l2
+
+    @app.get("/multi")
+    def read_multi(
+        user_data: tuple = Security(get_level_1, scopes=["multi_scope"]),
+    ):
+        return {"l1": user_data[0], "l2": user_data[1][0], "l3": user_data[1][1]}
+
+    response = client.get("/multi", headers={"authorization": "Bearer token"})
+    assert response.status_code == 200
+    assert response.json() == {"l1": ["multi_scope"], "l2": ["multi_scope"], "l3": ["multi_scope"]}
+
+def test_hierarchical_scopes_mixed():
+    def get_scopes_a(security_scopes: SecurityScopes):
+        return security_scopes.scopes
+
+    def get_user_b(
+        security_scopes: SecurityScopes, 
+        scopes_a: List[str] = Depends(get_scopes_a)
+    ):
+        return security_scopes.scopes, scopes_a
+
+    @app.get("/mixed")
+    def read_mixed(
+        user_data: tuple = Security(get_user_b, scopes=["scope_b"]),
+        other_data: List[str] = Security(get_scopes_a, scopes=["scope_a"]),
+    ):
+        return {"user_scopes": user_data[0], "dep_scopes": user_data[1], "other_scopes": other_data}
+
+    response = client.get("/mixed", headers={"authorization": "Bearer token"})
+    assert response.status_code == 200
+    # user_data should have scope_b, dep_scopes should have scope_b
+    # other_data should have scope_a
+    assert response.json() == {
+        "user_scopes": ["scope_b"], 
+        "dep_scopes": ["scope_b"], 
+        "other_scopes": ["scope_a"]
+    }
diff --git a/tests/test_hierarchical_security_scopes_fail.py b/tests/test_hierarchical_security_scopes_fail.py
new file mode 100644
index 00000000..bcafa50d
--- /dev/null
+++ b/tests/test_hierarchical_security_scopes_fail.py
@@ -0,0 +1,27 @@
+from typing import List
+from fastapi import Depends, FastAPI, Security
+from fastapi.security import SecurityScopes, OAuth2PasswordBearer
+from fastapi.testclient import TestClient
+
+app = FastAPI()
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
+
+def get_scopes(security_scopes: SecurityScopes):
+    return security_scopes.scopes
+
+def get_user(security_scopes: SecurityScopes, scopes: List[str] = Depends(get_scopes)):
+    return security_scopes.scopes, scopes
+
+@app.get("/test")
+def read_test(
+    user_data: tuple = Security(get_user, scopes=["root_scope"]),
+):
+    return {"user_scopes": user_data[0], "dep_scopes": user_data[1]}
+
+client = TestClient(app)
+
+def test_hierarchical_scopes():
+    response = client.get("/test", headers={"authorization": "Bearer token"})
+    assert response.status_code == 200
+    # Both should have ["root_scope"]
+    assert response.json() == {"user_scopes": ["root_scope"], "dep_scopes": ["root_scope"]}

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.71s