diff --git a/backend/app/feature_guards.py b/backend/app/feature_guards.py index c4d3847..c1ea94f 100644 --- a/backend/app/feature_guards.py +++ b/backend/app/feature_guards.py @@ -55,9 +55,15 @@ async def require_portal_access(request: Request, user: dict = Depends(get_curre check(user, "requests" if item.get("kind") == "request" else "issues") elif path.endswith("/items") and request.method == "POST": payload = await request.json() - check(user, "new_requests" if isinstance(payload, dict) and payload.get("kind", "request") == "request" else "issues") + kind = str(payload.get("kind") or "").strip().lower() if isinstance(payload, dict) else "" + check(user, "new_requests" if not kind or kind == "request" else "issues") elif path.endswith(("/items", "/overview")) and request.query_params.get("kind"): - check(user, "requests" if request.query_params["kind"] == "request" else "issues") + kind = request.query_params["kind"].strip().lower() + if not kind: + check(user, "requests") + check(user, "issues") + else: + check(user, "requests" if kind == "request" else "issues") else: # Unfiltered lists/overview can include both kinds. check(user, "requests") diff --git a/backend/tests/test_feature_access.py b/backend/tests/test_feature_access.py index 6d52829..3dfd1b5 100644 --- a/backend/tests/test_feature_access.py +++ b/backend/tests/test_feature_access.py @@ -108,3 +108,13 @@ class FeatureAccessTests(TempDatabaseMixin, unittest.TestCase): with self.assertRaises(StopAsyncIteration): await anext(iterator) asyncio.run(scenario()) + + def test_legacy_portal_kind_normalization_cannot_bypass_permissions(self): + update_permissions({'requests': False, 'new_requests': False, 'issues': True}, self.user['username']) + for kind in ['request', 'REQUEST', ' Request ', ' ', '']: + with self.subTest(kind=kind): + self.assertEqual(self.client.get('/portal/items', params={'kind': kind}).status_code, 403) + self.assertEqual(self.client.get('/portal/overview', params={'kind': kind}).status_code, 403) + self.assertEqual(self.client.post('/portal/items', json={'kind': kind}).status_code, 403) + self.assertEqual(self.client.post('/portal/items', json={'kind': None}).status_code, 403) + self.assertEqual(self.client.post('/portal/items', json={}).status_code, 403)