Просмотр исходного кода

fix(oidc): raise callback code/state max_length from 512 to 2048 (#1024)

Facebook and some other OAuth providers issue authorization codes that
exceed 512 characters. Pydantic rejected these with 422 string_too_long.
The OAuth spec defines no maximum code length; 2048 aligns with common
provider limits.

Also adds three integration tests to verify 512-char and 2048-char codes
are accepted while 2049-char codes are correctly rejected.
Sn0rrii 4 месяцев назад
Родитель
Сommit
e958b10f75
2 измененных файлов с 41 добавлено и 8 удалено
  1. 2 2
      backend/app/api/routes/mfa.py
  2. 39 6
      backend/tests/integration/test_mfa_api.py

+ 2 - 2
backend/app/api/routes/mfa.py

@@ -1189,8 +1189,8 @@ async def oidc_authorize(
 
 
 @router.get("/oidc/callback")
 @router.get("/oidc/callback")
 async def oidc_callback(
 async def oidc_callback(
-    code: str | None = Query(default=None, max_length=512),
-    state: str | None = Query(default=None, max_length=512),
+    code: str | None = Query(default=None, max_length=2048),
+    state: str | None = Query(default=None, max_length=2048),
     error: str | None = Query(default=None, max_length=256),
     error: str | None = Query(default=None, max_length=256),
     db: AsyncSession = Depends(get_db),
     db: AsyncSession = Depends(get_db),
 ) -> RedirectResponse:
 ) -> RedirectResponse:

+ 39 - 6
backend/tests/integration/test_mfa_api.py

@@ -3030,9 +3030,7 @@ class TestOIDCIssuerUrlTrailingSlash:
 
 
     @pytest.mark.asyncio
     @pytest.mark.asyncio
     @pytest.mark.integration
     @pytest.mark.integration
-    async def test_trailing_slash_issuer_url_fetches_correct_discovery_url(
-        self, async_client: AsyncClient
-    ):
+    async def test_trailing_slash_issuer_url_fetches_correct_discovery_url(self, async_client: AsyncClient):
         from unittest.mock import AsyncMock, MagicMock, patch
         from unittest.mock import AsyncMock, MagicMock, patch
 
 
         issuer_with_slash = "https://authentik.example.com/application/o/bambuddy/"
         issuer_with_slash = "https://authentik.example.com/application/o/bambuddy/"
@@ -3082,9 +3080,7 @@ class TestOIDCIssuerUrlTrailingSlash:
 
 
     @pytest.mark.asyncio
     @pytest.mark.asyncio
     @pytest.mark.integration
     @pytest.mark.integration
-    async def test_iss_claim_trailing_slash_accepted(
-        self, async_client: AsyncClient, db_session: AsyncSession
-    ):
+    async def test_iss_claim_trailing_slash_accepted(self, async_client: AsyncClient, db_session: AsyncSession):
         """Provider configured without trailing slash, Authentik JWT iss has trailing slash.
         """Provider configured without trailing slash, Authentik JWT iss has trailing slash.
 
 
         Both sides must be normalised before comparison so the login succeeds.
         Both sides must be normalised before comparison so the login succeeds.
@@ -3197,3 +3193,40 @@ class TestOIDCIssuerUrlTrailingSlash:
             "Trailing slash mismatch in iss claim must not cause token_validation_failed"
             "Trailing slash mismatch in iss claim must not cause token_validation_failed"
         )
         )
         assert "oidc_token=" in location, f"Expected oidc_token in redirect, got: {location}"
         assert "oidc_token=" in location, f"Expected oidc_token in redirect, got: {location}"
+
+
+class TestOIDCCallbackCodeLength:
+    """OIDC callback code query param must accept up to 2048 characters (OAuth spec)."""
+
+    @pytest.mark.asyncio
+    @pytest.mark.integration
+    async def test_code_512_chars_accepted(self, async_client: AsyncClient):
+        """A 512-character code (old limit) must not be rejected with 422."""
+        code = "a" * 512
+        resp = await async_client.get(
+            f"/api/v1/auth/oidc/callback?code={code}&state=bogus-state",
+            follow_redirects=False,
+        )
+        assert resp.status_code != 422, "512-char code must not be rejected by Pydantic"
+
+    @pytest.mark.asyncio
+    @pytest.mark.integration
+    async def test_code_2048_chars_accepted(self, async_client: AsyncClient):
+        """A 2048-character code must not be rejected with 422."""
+        code = "a" * 2048
+        resp = await async_client.get(
+            f"/api/v1/auth/oidc/callback?code={code}&state=bogus-state",
+            follow_redirects=False,
+        )
+        assert resp.status_code != 422, "2048-char code must not be rejected by Pydantic"
+
+    @pytest.mark.asyncio
+    @pytest.mark.integration
+    async def test_code_2049_chars_rejected(self, async_client: AsyncClient):
+        """A 2049-character code must be rejected with 422."""
+        code = "a" * 2049
+        resp = await async_client.get(
+            f"/api/v1/auth/oidc/callback?code={code}&state=bogus-state",
+            follow_redirects=False,
+        )
+        assert resp.status_code == 422, "2049-char code must be rejected by Pydantic"