Skip to content

Commit 21d3aef

Browse files
fix: Only support RBAC with authenticate using managed identity (#1857)
1 parent 7435501 commit 21d3aef

File tree

15 files changed

+267
-2088
lines changed

15 files changed

+267
-2088
lines changed

code/backend/batch/utilities/helpers/env_helper.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ def __load_config(self, **kwargs) -> None:
165165
"Unsupported DATABASE_TYPE. Please set DATABASE_TYPE to 'CosmosDB' or 'PostgreSQL'."
166166
)
167167

168-
self.AZURE_AUTH_TYPE = os.getenv("AZURE_AUTH_TYPE", "keys")
168+
self.AZURE_AUTH_TYPE = os.getenv("AZURE_AUTH_TYPE", "rbac")
169169
# Azure OpenAI
170170
self.AZURE_OPENAI_RESOURCE = os.getenv("AZURE_OPENAI_RESOURCE", "")
171171
# Fetch AZURE_OPENAI_MODEL_INFO from environment
@@ -233,6 +233,7 @@ def __load_config(self, **kwargs) -> None:
233233
self.AZURE_COMPUTER_VISION_VECTORIZE_IMAGE_MODEL_VERSION = os.getenv(
234234
"AZURE_COMPUTER_VISION_VECTORIZE_IMAGE_MODEL_VERSION", "2023-04-15"
235235
)
236+
self.FUNCTION_KEY = os.getenv("FUNCTION_KEY", "")
236237

237238
# Initialize Azure keys based on authentication type and environment settings.
238239
# When AZURE_AUTH_TYPE is "rbac", azure keys are None or an empty string.
@@ -241,6 +242,7 @@ def __load_config(self, **kwargs) -> None:
241242
self.AZURE_OPENAI_API_KEY = ""
242243
self.AZURE_SPEECH_KEY = None
243244
self.AZURE_COMPUTER_VISION_KEY = None
245+
self.FUNCTION_KEY = self.secretHelper.get_secret("FUNCTION_KEY")
244246
else:
245247
self.AZURE_SEARCH_KEY = self.secretHelper.get_secret("AZURE_SEARCH_KEY")
246248
self.AZURE_OPENAI_API_KEY = self.secretHelper.get_secret(
@@ -268,7 +270,6 @@ def __load_config(self, **kwargs) -> None:
268270
os.environ["OPENAI_API_VERSION"] = self.OPENAI_API_VERSION
269271
# Azure Functions - Batch processing
270272
self.BACKEND_URL = os.getenv("BACKEND_URL", "http://localhost:7071")
271-
self.FUNCTION_KEY = os.getenv("FUNCTION_KEY")
272273
self.AzureWebJobsStorage = os.getenv("AzureWebJobsStorage", "")
273274
self.DOCUMENT_PROCESSING_QUEUE_NAME = os.getenv(
274275
"DOCUMENT_PROCESSING_QUEUE_NAME", "doc-processing"

code/tests/test_app.py

Lines changed: 1 addition & 287 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
This module tests the entry point for the application.
33
"""
44

5-
from unittest.mock import AsyncMock, MagicMock, Mock, patch, ANY
5+
from unittest.mock import AsyncMock, MagicMock, Mock, patch
66

77
from openai import RateLimitError, BadRequestError, InternalServerError
88
import pytest
@@ -604,240 +604,6 @@ def setup_method(self):
604604
),
605605
]
606606

607-
@patch(
608-
"backend.batch.utilities.search.azure_search_handler.AzureSearchHelper._index_not_exists"
609-
)
610-
@patch("create_app.AzureOpenAI")
611-
@patch(
612-
"backend.batch.utilities.helpers.config.config_helper.ConfigHelper.get_active_config_or_default"
613-
)
614-
@patch(
615-
"backend.batch.utilities.helpers.azure_blob_storage_client.generate_container_sas"
616-
)
617-
def test_conversation_azure_byod_returns_correct_response_when_streaming_with_data_keys(
618-
self,
619-
generate_container_sas_mock: MagicMock,
620-
get_active_config_or_default_mock,
621-
azure_openai_mock: MagicMock,
622-
index_not_exists_mock,
623-
env_helper_mock: MagicMock,
624-
client: FlaskClient,
625-
):
626-
"""Test that the Azure BYOD conversation endpoint returns the correct response."""
627-
# given
628-
openai_client_mock = azure_openai_mock.return_value
629-
openai_client_mock.chat.completions.create.return_value = (
630-
self.mock_streamed_response
631-
)
632-
633-
get_active_config_or_default_mock.return_value.prompts.use_on_your_data_format = (
634-
False
635-
)
636-
get_active_config_or_default_mock.return_value.prompts.conversational_flow = (
637-
"byod"
638-
)
639-
generate_container_sas_mock.return_value = "mock-sas"
640-
index_not_exists_mock.return_value = False
641-
642-
# when
643-
response = client.post(
644-
"/api/conversation",
645-
headers={"content-type": "application/json"},
646-
json=self.body,
647-
)
648-
649-
# then
650-
assert response.status_code == 200
651-
652-
# The response is JSON lines
653-
data = str(response.data, "utf-8")
654-
assert (
655-
data
656-
== r"""{"id": "response.id", "model": "mock-openai-model", "created": 0, "object": "response.object", "choices": [{"messages": [{"content": "{\"citations\": [{\"content\": \"[title](source)\\n\\n\\ncontent\", \"id\": \"doc_id\", \"chunk_id\": 46, \"title\": \"title\", \"filepath\": \"title\", \"url\": \"[title](source)\"}]}", "end_turn": false, "role": "tool"}, {"content": "", "end_turn": false, "role": "assistant"}]}]}
657-
{"id": "response.id", "model": "mock-openai-model", "created": 0, "object": "response.object", "choices": [{"messages": [{"content": "{\"citations\": [{\"content\": \"[title](source)\\n\\n\\ncontent\", \"id\": \"doc_id\", \"chunk_id\": 46, \"title\": \"title\", \"filepath\": \"title\", \"url\": \"[title](source)\"}]}", "end_turn": false, "role": "tool"}, {"content": "A question\n?", "end_turn": false, "role": "assistant"}]}]}
658-
{"id": "response.id", "model": "mock-openai-model", "created": 0, "object": "response.object", "choices": [{"messages": [{"content": "{\"citations\": [{\"content\": \"[title](source)\\n\\n\\ncontent\", \"id\": \"doc_id\", \"chunk_id\": 46, \"title\": \"title\", \"filepath\": \"title\", \"url\": \"[title](source)\"}]}", "end_turn": false, "role": "tool"}, {"content": "A question\n?", "end_turn": true, "role": "assistant"}]}]}
659-
"""
660-
)
661-
662-
azure_openai_mock.assert_called_once_with(
663-
azure_endpoint=AZURE_OPENAI_ENDPOINT,
664-
api_version=AZURE_OPENAI_API_VERSION,
665-
api_key=AZURE_OPENAI_API_KEY,
666-
)
667-
668-
openai_client_mock.chat.completions.create.assert_called_once_with(
669-
model=AZURE_OPENAI_MODEL,
670-
messages=self.body["messages"],
671-
temperature=0.5,
672-
max_tokens=500,
673-
top_p=0.8,
674-
stop=["\n", "STOP"],
675-
stream=True,
676-
extra_body={
677-
"data_sources": [
678-
{
679-
"type": "azure_search",
680-
"parameters": {
681-
"authentication": {
682-
"type": "api_key",
683-
"key": AZURE_SEARCH_KEY,
684-
},
685-
"endpoint": AZURE_SEARCH_SERVICE,
686-
"index_name": AZURE_SEARCH_INDEX,
687-
"fields_mapping": {
688-
"content_fields": ["field1", "field2"],
689-
"vector_fields": [AZURE_SEARCH_CONTENT_VECTOR_COLUMN],
690-
"title_field": AZURE_SEARCH_TITLE_COLUMN,
691-
"url_field": env_helper_mock.AZURE_SEARCH_FIELDS_METADATA,
692-
"filepath_field": AZURE_SEARCH_FILENAME_COLUMN,
693-
"source_field": AZURE_SEARCH_SOURCE_COLUMN,
694-
"text_field": AZURE_SEARCH_TEXT_COLUMN,
695-
"layoutText_field": AZURE_SEARCH_LAYOUT_TEXT_COLUMN,
696-
},
697-
"filter": AZURE_SEARCH_FILTER,
698-
"in_scope": AZURE_SEARCH_ENABLE_IN_DOMAIN,
699-
"top_n_documents": AZURE_SEARCH_TOP_K,
700-
"embedding_dependency": {
701-
"type": "deployment_name",
702-
"deployment_name": AZURE_OPENAI_EMBEDDING_MODEL,
703-
},
704-
"query_type": "vector_semantic_hybrid",
705-
"semantic_configuration": AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG,
706-
"role_information": AZURE_OPENAI_SYSTEM_MESSAGE,
707-
},
708-
}
709-
]
710-
},
711-
)
712-
713-
@patch(
714-
"backend.batch.utilities.search.azure_search_handler.AzureSearchHelper._index_not_exists"
715-
)
716-
@patch("create_app.AzureOpenAI")
717-
@patch(
718-
"backend.batch.utilities.helpers.config.config_helper.ConfigHelper.get_active_config_or_default"
719-
)
720-
@patch(
721-
"backend.batch.utilities.helpers.azure_blob_storage_client.generate_container_sas"
722-
)
723-
def test_conversation_azure_byod_returns_correct_response_when_streaming_with_data_rbac(
724-
self,
725-
generate_container_sas_mock: MagicMock,
726-
get_active_config_or_default_mock,
727-
azure_openai_mock: MagicMock,
728-
index_not_exists_mock,
729-
env_helper_mock: MagicMock,
730-
client: FlaskClient,
731-
):
732-
"""Test that the Azure BYOD conversation endpoint returns the correct response."""
733-
# given
734-
env_helper_mock.is_auth_type_keys.return_value = False
735-
get_active_config_or_default_mock.return_value.prompts.conversational_flow = (
736-
"byod"
737-
)
738-
generate_container_sas_mock.return_value = "mock-sas"
739-
openai_client_mock = azure_openai_mock.return_value
740-
openai_client_mock.chat.completions.create.return_value = (
741-
self.mock_streamed_response
742-
)
743-
index_not_exists_mock.return_value = False
744-
745-
# when
746-
response = client.post(
747-
"/api/conversation",
748-
headers={"content-type": "application/json"},
749-
json=self.body,
750-
)
751-
752-
# then
753-
assert response.status_code == 200
754-
755-
# The response is JSON lines
756-
data = str(response.data, "utf-8")
757-
assert (
758-
data
759-
== r"""{"id": "response.id", "model": "mock-openai-model", "created": 0, "object": "response.object", "choices": [{"messages": [{"content": "{\"citations\": [{\"content\": \"[title](source)\\n\\n\\ncontent\", \"id\": \"doc_id\", \"chunk_id\": 46, \"title\": \"title\", \"filepath\": \"title\", \"url\": \"[title](source)\"}]}", "end_turn": false, "role": "tool"}, {"content": "", "end_turn": false, "role": "assistant"}]}]}
760-
{"id": "response.id", "model": "mock-openai-model", "created": 0, "object": "response.object", "choices": [{"messages": [{"content": "{\"citations\": [{\"content\": \"[title](source)\\n\\n\\ncontent\", \"id\": \"doc_id\", \"chunk_id\": 46, \"title\": \"title\", \"filepath\": \"title\", \"url\": \"[title](source)\"}]}", "end_turn": false, "role": "tool"}, {"content": "A question\n?", "end_turn": false, "role": "assistant"}]}]}
761-
{"id": "response.id", "model": "mock-openai-model", "created": 0, "object": "response.object", "choices": [{"messages": [{"content": "{\"citations\": [{\"content\": \"[title](source)\\n\\n\\ncontent\", \"id\": \"doc_id\", \"chunk_id\": 46, \"title\": \"title\", \"filepath\": \"title\", \"url\": \"[title](source)\"}]}", "end_turn": false, "role": "tool"}, {"content": "A question\n?", "end_turn": true, "role": "assistant"}]}]}
762-
"""
763-
)
764-
765-
azure_openai_mock.assert_called_once_with(
766-
azure_endpoint=AZURE_OPENAI_ENDPOINT,
767-
api_version=AZURE_OPENAI_API_VERSION,
768-
azure_ad_token_provider=ANY,
769-
)
770-
771-
kwargs = openai_client_mock.chat.completions.create.call_args.kwargs
772-
773-
assert kwargs["extra_body"]["data_sources"][0]["parameters"][
774-
"authentication"
775-
] == {
776-
"type": "system_assigned_managed_identity",
777-
}
778-
779-
@patch(
780-
"backend.batch.utilities.search.azure_search_handler.AzureSearchHelper._index_not_exists"
781-
)
782-
@patch("create_app.AzureOpenAI")
783-
@patch(
784-
"backend.batch.utilities.helpers.config.config_helper.ConfigHelper.get_active_config_or_default"
785-
)
786-
@patch(
787-
"backend.batch.utilities.helpers.azure_blob_storage_client.generate_container_sas"
788-
)
789-
def test_conversation_azure_byod_returns_correct_response_when_not_streaming_with_data(
790-
self,
791-
generate_container_sas_mock: MagicMock,
792-
get_active_config_or_default_mock,
793-
azure_openai_mock: MagicMock,
794-
index_not_exists_mock,
795-
env_helper_mock: MagicMock,
796-
client: FlaskClient,
797-
):
798-
"""Test that the Azure BYOD conversation endpoint returns the correct response."""
799-
# given
800-
env_helper_mock.SHOULD_STREAM = False
801-
get_active_config_or_default_mock.return_value.prompts.conversational_flow = (
802-
"byod"
803-
)
804-
generate_container_sas_mock.return_value = "mock-sas"
805-
index_not_exists_mock.return_value = False
806-
openai_client_mock = azure_openai_mock.return_value
807-
openai_client_mock.chat.completions.create.return_value = self.mock_response
808-
809-
# when
810-
response = client.post(
811-
"/api/conversation",
812-
headers={"content-type": "application/json"},
813-
json=self.body,
814-
)
815-
816-
# then
817-
assert response.status_code == 200
818-
assert response.json == {
819-
"id": "response.id",
820-
"model": "mock-model",
821-
"created": 0,
822-
"object": "response.object",
823-
"choices": [
824-
{
825-
"messages": [
826-
{
827-
"content": '{"citations": [{"content": "[title](source)\\n\\n\\ncontent", "id": "doc_id", "chunk_id": 46, "title": "title", "filepath": "title", "url": "[title](source)"}]}',
828-
"end_turn": False,
829-
"role": "tool",
830-
},
831-
{
832-
"content": self.content,
833-
"end_turn": True,
834-
"role": "assistant",
835-
},
836-
]
837-
}
838-
],
839-
}
840-
841607
@patch("create_app.conversation_with_data")
842608
@patch(
843609
"backend.batch.utilities.helpers.config.config_helper.ConfigHelper.get_active_config_or_default"
@@ -1159,55 +925,3 @@ def test_conversation_azure_byod_returns_correct_response_when_streaming_without
1159925
data
1160926
== '{"id": "response.id", "model": "mock-openai-model", "created": 0, "object": "response.object", "choices": [{"messages": [{"role": "assistant", "content": "mock content"}]}]}\n'
1161927
)
1162-
1163-
@patch(
1164-
"backend.batch.utilities.search.azure_search_handler.AzureSearchHelper._index_not_exists"
1165-
)
1166-
@patch("create_app.AzureOpenAI")
1167-
@patch(
1168-
"backend.batch.utilities.helpers.config.config_helper.ConfigHelper.get_active_config_or_default"
1169-
)
1170-
@patch(
1171-
"backend.batch.utilities.helpers.azure_blob_storage_client.generate_container_sas"
1172-
)
1173-
def test_conversation_azure_byod_uses_semantic_config(
1174-
self,
1175-
generate_container_sas_mock: MagicMock,
1176-
get_active_config_or_default_mock,
1177-
azure_openai_mock: MagicMock,
1178-
index_not_exists_mock,
1179-
client: FlaskClient,
1180-
):
1181-
"""Test that the Azure BYOD conversation endpoint uses the semantic configuration."""
1182-
# given
1183-
get_active_config_or_default_mock.return_value.prompts.conversational_flow = (
1184-
"byod"
1185-
)
1186-
generate_container_sas_mock.return_value = "mock-sas"
1187-
openai_client_mock = azure_openai_mock.return_value
1188-
openai_client_mock.chat.completions.create.return_value = (
1189-
self.mock_streamed_response
1190-
)
1191-
index_not_exists_mock.return_value = False
1192-
# when
1193-
response = client.post(
1194-
"/api/conversation",
1195-
headers={"content-type": "application/json"},
1196-
json=self.body,
1197-
)
1198-
1199-
# then
1200-
assert response.status_code == 200
1201-
1202-
kwargs = openai_client_mock.chat.completions.create.call_args.kwargs
1203-
1204-
assert (
1205-
kwargs["extra_body"]["data_sources"][0]["parameters"]["query_type"]
1206-
== "vector_semantic_hybrid"
1207-
)
1208-
assert (
1209-
kwargs["extra_body"]["data_sources"][0]["parameters"][
1210-
"semantic_configuration"
1211-
]
1212-
== "test-config"
1213-
)

0 commit comments

Comments
 (0)