mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
fix(azure_ai): recognize real Search doc endpoints so teams can read/write via passthrough
The Azure AI Search vector store config declared its write endpoint as
`PUT /docs` and its read endpoints as only `/docs/search`. The passthrough
permission gate (`is_allowed_to_call_vector_store_endpoint`) derives a
read/write permission type by matching the request route against those
lists, and a route matching neither resolves to `None` and raises a 403
before the caller's `allowed_vector_store_indexes` grant is ever checked.
Two real Azure routes fell through that gap for non-admins: document
upload/merge/delete is `POST /docs/index` (not `PUT /docs`), and get
index details is `GET /indexes/{name}` (no `/docs/search` suffix). So a
team with a valid write or read grant still got 403 on upload and on
reading index details, while admins slipped through because they skip the
gate entirely.
Correct the map: read is any GET under `/indexes/` (get details, stats,
count, and the GET form of search) plus `POST /docs/search`; write is
`POST /docs/index`. Index lifecycle (create/update/delete the index
itself) stays proxy-admin only because it is handled first by the
separate lifecycle check on POST/PUT/DELETE/PATCH, so this does not let a
team create or delete indexes.
Add regression tests that exercise the real AzureAIVectorStoreConfig map:
a write-granted team may upload, a read-granted team may search and get
index details, a team missing the matching grant is still denied, and a
team cannot manage index lifecycle even with a write grant.
This commit is contained in:
parent
0a73e0f8d9
commit
83efa9f630
2 changed files with 94 additions and 2 deletions
|
|
@ -38,8 +38,8 @@ class AzureAIVectorStoreConfig(BaseVectorStoreConfig, BaseAzureLLM):
|
|||
|
||||
def get_vector_store_endpoints_by_type(self) -> VectorStoreIndexEndpoints:
|
||||
return {
|
||||
"read": [("GET", "/docs/search"), ("POST", "/docs/search")],
|
||||
"write": [("PUT", "/docs")],
|
||||
"read": [("GET", "/indexes/"), ("POST", "/docs/search")],
|
||||
"write": [("POST", "/docs/index")],
|
||||
}
|
||||
|
||||
def get_auth_credentials(self, litellm_params: dict) -> BaseVectorStoreAuthCredentials:
|
||||
|
|
|
|||
|
|
@ -2928,3 +2928,95 @@ class TestUpdateVectorStoreAccessControlAndRedaction:
|
|||
params = response["vector_store"]["litellm_params"]
|
||||
assert params["api_key"] == REDACTED_BY_LITELM_STRING
|
||||
assert params["api_base"] == "https://api.openai.com/v1"
|
||||
|
||||
|
||||
class TestAzureAIDocumentWritePassthroughPermission:
|
||||
"""Regression tests for the Azure AI Search passthrough write mapping.
|
||||
|
||||
Azure's batch document write/merge/delete endpoint is
|
||||
``POST /indexes/{name}/docs/index``. A non-admin team holding a ``write``
|
||||
grant on the index must be allowed to call it, while index lifecycle
|
||||
(create / update / delete the index itself) stays proxy-admin only.
|
||||
|
||||
These exercise the real ``AzureAIVectorStoreConfig`` endpoint map on
|
||||
purpose (no mocked provider config), so reverting the map to the old
|
||||
``("PUT", "/docs")`` entry makes ``test_team_with_write_grant_can_upload``
|
||||
fail.
|
||||
"""
|
||||
|
||||
INDEX = "my-index"
|
||||
|
||||
def _request(self, method: str, path: str) -> MagicMock:
|
||||
request = MagicMock(spec=Request)
|
||||
request.method = method
|
||||
request.url.path = path
|
||||
return request
|
||||
|
||||
def _team_member(self, permissions: list) -> MagicMock:
|
||||
user = MagicMock(spec=UserAPIKeyAuth)
|
||||
user.user_role = None
|
||||
user.metadata = {"allowed_vector_store_indexes": [{"index_name": self.INDEX, "index_permissions": permissions}]}
|
||||
user.team_metadata = None
|
||||
return user
|
||||
|
||||
def test_team_with_write_grant_can_upload(self):
|
||||
result = is_allowed_to_call_vector_store_endpoint(
|
||||
provider=LlmProviders.AZURE_AI,
|
||||
index_name=self.INDEX,
|
||||
request=self._request("POST", f"/azure_ai/indexes/{self.INDEX}/docs/index"),
|
||||
user_api_key_dict=self._team_member(["read", "write"]),
|
||||
)
|
||||
assert result is True
|
||||
|
||||
def test_team_without_write_grant_cannot_upload(self):
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
is_allowed_to_call_vector_store_endpoint(
|
||||
provider=LlmProviders.AZURE_AI,
|
||||
index_name=self.INDEX,
|
||||
request=self._request("POST", f"/azure_ai/indexes/{self.INDEX}/docs/index"),
|
||||
user_api_key_dict=self._team_member(["read"]),
|
||||
)
|
||||
assert exc_info.value.status_code == 403
|
||||
|
||||
def test_team_with_read_grant_can_search(self):
|
||||
result = is_allowed_to_call_vector_store_endpoint(
|
||||
provider=LlmProviders.AZURE_AI,
|
||||
index_name=self.INDEX,
|
||||
request=self._request("POST", f"/azure_ai/indexes/{self.INDEX}/docs/search"),
|
||||
user_api_key_dict=self._team_member(["read"]),
|
||||
)
|
||||
assert result is True
|
||||
|
||||
def test_team_with_read_grant_can_get_index_details(self):
|
||||
result = is_allowed_to_call_vector_store_endpoint(
|
||||
provider=LlmProviders.AZURE_AI,
|
||||
index_name=self.INDEX,
|
||||
request=self._request("GET", f"/azure_ai/indexes/{self.INDEX}"),
|
||||
user_api_key_dict=self._team_member(["read"]),
|
||||
)
|
||||
assert result is True
|
||||
|
||||
def test_team_without_read_grant_cannot_get_index_details(self):
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
is_allowed_to_call_vector_store_endpoint(
|
||||
provider=LlmProviders.AZURE_AI,
|
||||
index_name=self.INDEX,
|
||||
request=self._request("GET", f"/azure_ai/indexes/{self.INDEX}"),
|
||||
user_api_key_dict=self._team_member(["write"]),
|
||||
)
|
||||
assert exc_info.value.status_code == 403
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"method, operation",
|
||||
[("PUT", "update"), ("DELETE", "delete")],
|
||||
)
|
||||
def test_team_cannot_manage_index_lifecycle_even_with_write_grant(self, method, operation):
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
is_allowed_to_call_vector_store_endpoint(
|
||||
provider=LlmProviders.AZURE_AI,
|
||||
index_name=self.INDEX,
|
||||
request=self._request(method, f"/azure_ai/indexes/{self.INDEX}?api-version=2024-07-01"),
|
||||
user_api_key_dict=self._team_member(["read", "write"]),
|
||||
)
|
||||
assert exc_info.value.status_code == 403
|
||||
assert f"Only proxy admins can {operation}" in exc_info.value.detail
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue