diff --git a/api/routes/s3_signed_url.py b/api/routes/s3_signed_url.py index b749f98c..9129a6c0 100644 --- a/api/routes/s3_signed_url.py +++ b/api/routes/s3_signed_url.py @@ -47,8 +47,10 @@ def _extract_org_id_from_key(key: str) -> Optional[int]: """Try to extract an organization ID from a storage key. Matches known org-scoped keys of the form ``{prefix}/{org_id}/...`` where - *org_id* is a positive integer. Returns ``None`` when the pattern does not - match. + *org_id* is a positive integer. Workflow recordings require an additional + path segment so they cannot be confused with legacy workflow-run artifacts + such as ``recordings/{run_id}/user.wav``. Returns ``None`` when the pattern + does not match. """ parts = key.split("/") if ( @@ -57,6 +59,9 @@ def _extract_org_id_from_key(key: str) -> Optional[int]: and parts[1].isdigit() ): return int(parts[1]) + recording_match = re.fullmatch(r"recordings/(\d+)/[a-zA-Z0-9_-]+/.+", key) + if recording_match: + return int(recording_match.group(1)) return None @@ -178,8 +183,8 @@ async def get_signed_url( Access Control: * Known org-scoped keys (for example ``campaigns/{org_id}/...`` and - ``knowledge_base/{org_id}/...``) are authorized by matching the org_id - against the requesting user's organization. + ``recordings/{org_id}/{recording_id}/...``) are authorized by matching the + org_id against the requesting user's organization. * Legacy keys (``recordings/{run_id}.wav``, ``transcripts/{run_id}.txt``) are authorized via the workflow run they belong to. * Superusers can request any key. diff --git a/api/tests/test_s3_signed_url.py b/api/tests/test_s3_signed_url.py index 95156dcb..a9dfe6d0 100644 --- a/api/tests/test_s3_signed_url.py +++ b/api/tests/test_s3_signed_url.py @@ -18,11 +18,13 @@ def test_legacy_recording_keys_do_not_fall_through_to_org_scoped_auth(): assert _extract_org_id_from_key("recordings/1855.wav") is None assert _extract_org_id_from_key("recordings/1855/other.wav") is None + assert _extract_org_id_from_key("recordings/1855/user.wav/nested") is None def test_known_org_scoped_keys_extract_org_id(): assert _extract_org_id_from_key("campaigns/42/source.csv") == 42 assert _extract_org_id_from_key("knowledge_base/42/document/file.pdf") == 42 + assert _extract_org_id_from_key("recordings/42/greeting-123/greeting.wav") == 42 assert _extract_legacy_workflow_run_id("campaigns/42/source.csv") is None