Skip to content

Commit f792549

Browse files
MikeyMCZMichal Maternaannatisch
authored
[Text Translation] Fix the Entra ID authentication on custom endpoint (#35835)
* [Text Translation] Fix the Entra ID authentication on custom endpoint * Fix build issues * fix md links * Fix documentation * Fixing PR comments * Fix changelog * Update sdk/translation/azure-ai-translation-text/azure/ai/translation/text/_patch.py Co-authored-by: Anna Tisch <[email protected]> * Added comment * Adding release info --------- Co-authored-by: Michal Materna <[email protected]> Co-authored-by: Anna Tisch <[email protected]>
1 parent b9e0b17 commit f792549

File tree

9 files changed

+186
-35
lines changed

9 files changed

+186
-35
lines changed

sdk/translation/azure-ai-translation-text/CHANGELOG.md

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
# Release History
22

3-
## 1.0.1 (Unreleased)
4-
5-
### Features Added
6-
7-
### Breaking Changes
3+
## 1.0.1 (2024-06-24)
84

95
### Bugs Fixed
10-
11-
### Other Changes
6+
- Fixed a bug where Entra Id authentication couldn't be used with custom endpoint.
127

138
## 1.0.0 (2024-05-23)
149

sdk/translation/azure-ai-translation-text/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ With the value of the `endpoint`, `credential` and a `region`, you can create th
6464

6565
```python
6666
credential = AzureKeyCredential(apikey)
67-
text_translator = TextTranslationClient(credential=credential, endpoint=endpoint, region=region)
67+
text_translator = TextTranslationClient(credential=credential, region=region)
6868
```
6969

7070
<!-- END SNIPPET -->

sdk/translation/azure-ai-translation-text/azure/ai/translation/text/_patch.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
from ._client import TextTranslationClient as ServiceClientGenerated
1313

14-
DEFAULT_TOKEN_SCOPE = "https://api.microsofttranslator.com/"
1514
DEFAULT_ENTRA_ID_SCOPE = "https://cognitiveservices.azure.com"
1615
DEFAULT_SCOPE = "/.default"
1716

@@ -79,6 +78,12 @@ def get_translation_endpoint(endpoint, api_version):
7978

8079
return translator_endpoint
8180

81+
def is_cognitive_services_scope(audience: str) -> bool:
82+
if "microsofttranslator" in audience:
83+
return True
84+
85+
return False
86+
8287

8388
def set_authentication_policy(credential, kwargs):
8489
if isinstance(credential, AzureKeyCredential):
@@ -92,7 +97,7 @@ def set_authentication_policy(credential, kwargs):
9297
elif hasattr(credential, "get_token"):
9398
if not kwargs.get("authentication_policy"):
9499
if kwargs.get("region") and kwargs.get("resource_id"):
95-
scope = kwargs.pop("audience", DEFAULT_ENTRA_ID_SCOPE).rstrip("/") + DEFAULT_SCOPE
100+
scope = kwargs.pop("audience", DEFAULT_ENTRA_ID_SCOPE).rstrip("/").rstrip(DEFAULT_SCOPE) + DEFAULT_SCOPE
96101
kwargs["authentication_policy"] = TranslatorEntraIdAuthenticationPolicy(
97102
credential,
98103
kwargs["resource_id"],
@@ -105,8 +110,12 @@ def set_authentication_policy(credential, kwargs):
105110
"""Both 'resource_id' and 'region' must be provided with a TokenCredential for
106111
regional resource authentication."""
107112
)
113+
scope: str = kwargs.pop("audience", DEFAULT_ENTRA_ID_SCOPE)
114+
if not is_cognitive_services_scope(scope):
115+
scope = scope.rstrip("/").rstrip(DEFAULT_SCOPE) + DEFAULT_SCOPE
116+
108117
kwargs["authentication_policy"] = BearerTokenCredentialPolicy(
109-
credential, *[kwargs.pop("audience", DEFAULT_TOKEN_SCOPE)], kwargs
118+
credential, scope
110119
)
111120

112121

sdk/translation/azure-ai-translation-text/azure/ai/translation/text/aio/_patch.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
from azure.core.credentials_async import AsyncTokenCredential
1515

1616
from .._patch import (
17-
DEFAULT_TOKEN_SCOPE,
1817
DEFAULT_ENTRA_ID_SCOPE,
1918
DEFAULT_SCOPE,
2019
get_translation_endpoint,
2120
TranslatorAuthenticationPolicy,
21+
is_cognitive_services_scope,
2222
)
2323

2424
from ._client import TextTranslationClient as ServiceClientGenerated
@@ -72,7 +72,7 @@ def set_authentication_policy(credential, kwargs):
7272
elif hasattr(credential, "get_token"):
7373
if not kwargs.get("authentication_policy"):
7474
if kwargs.get("region") and kwargs.get("resource_id"):
75-
scope = kwargs.pop("audience", DEFAULT_ENTRA_ID_SCOPE).rstrip("/") + DEFAULT_SCOPE
75+
scope = kwargs.pop("audience", DEFAULT_ENTRA_ID_SCOPE).rstrip("/").rstrip(DEFAULT_SCOPE) + DEFAULT_SCOPE
7676
kwargs["authentication_policy"] = AsyncTranslatorEntraIdAuthenticationPolicy(
7777
credential,
7878
kwargs["resource_id"],
@@ -85,8 +85,11 @@ def set_authentication_policy(credential, kwargs):
8585
"""Both 'resource_id' and 'region' must be provided with a TokenCredential
8686
for regional resource authentication."""
8787
)
88+
scope: str = kwargs.pop("audience", DEFAULT_ENTRA_ID_SCOPE)
89+
if not is_cognitive_services_scope(scope):
90+
scope = scope.rstrip("/").rstrip(DEFAULT_SCOPE) + DEFAULT_SCOPE
8891
kwargs["authentication_policy"] = AsyncBearerTokenCredentialPolicy(
89-
credential, *[kwargs.pop("audience", DEFAULT_TOKEN_SCOPE)], kwargs
92+
credential, scope
9093
)
9194

9295

sdk/translation/azure-ai-translation-text/samples/README.md

Lines changed: 83 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,36 +24,104 @@ Translator Service is a cloud-based neural machine translation service that is p
2424

2525
See the [README][README] of the Text Translator client library for more information, including useful links and instructions.
2626

27-
## Common scenarios samples
27+
# Create Client
2828

29+
Text Translation service is using two types of endpoints - Global and Custom. You can find more information in the [v3 Translator reference][TranslatorReference].
2930

30-
# Create Client
31+
## Global Endpoint
3132

32-
For some of these operations you can create a new `TextTranslationClient` without any authentication. You will only need your endpoint:
33+
When using Text Translation service with global endpoint, the endpoint doesn't need to be provided when creating `TextTranslationClient`. When `endpoint` is
34+
not provided, global `api.cognitive.microsofttranslator.com` is used. For [sovereign clouds][SovereignClouds], the endpoint is always required.
3335

34-
<!-- SNIPPET: sample_text_translation_client.create_text_translation_client_with_endpoint -->
36+
### Using the Subscription Key
37+
38+
When using `cognitive services key` and `region` with global endpoint, you can create `TextTranslationClient`:
39+
40+
<!-- SNIPPET: sample_text_translation_client.create_text_translation_client_with_credential -->
3541

3642
```python
37-
text_translator = TextTranslationClient(endpoint=endpoint)
43+
credential = AzureKeyCredential(apikey)
44+
text_translator = TextTranslationClient(credential=credential, region=region)
3845
```
3946

4047
<!-- END SNIPPET -->
4148

42-
The values of the `endpoint` variable can be retrieved from environment variables, configuration settings, or any other secure approach that works for your application.
49+
### Using the Cognitive Services Token
4350

44-
For other samples an overloaded constructor is provided that uses a TextTranslationCredential. In addition to `endpoint`, this function requires configuring an `apikey` and `region` to create the credential. The values of the `endpoint`, `apiKey` and `region` variables can be retrieved from environment variables, configuration settings, or any other secure approach that works for your application.
51+
For the Cognitive Services Token authentication, there is currently no implementation provided in the SDK. You can implement the solution
52+
according to the [Token Authentication documentation][TranslatorReference]. For a simple example implementation, you can refer: [StaticAccessTokenCredential][static_access_token_credential].
53+
When the token is created, you can create the `TextTranslationClient`:
4554

46-
The appropriate constructor is invoked in each sample to create a `TextTranslationClient` instance.
55+
<!-- SNIPPET: sample_text_translation_client.create_text_translation_client_with_cognitive_services_token -->
4756

48-
<!-- SNIPPET: sample_text_translation_client.create_text_translation_client_with_credential -->
57+
```python
58+
client = TextTranslationClient(credential=credential, audience="https://api.microsofttranslator.com/")
59+
```
60+
61+
<!-- END SNIPPET -->
62+
63+
### Using the Entra Id Authentication
64+
65+
The Authentication with Microsoft Entra ID on global endpoint requires bearer token generated by Azure AD, Region of the translator resource
66+
and Resource ID for your Translator resource instance. For prerequisites and more information refer to [Authentication with Microsoft Entra ID][TranslatorReference].
67+
68+
<!-- SNIPPET: sample_text_translation_client.create_text_translation_client_with_entra_id_token -->
69+
70+
```python
71+
credential = DefaultAzureCredential()
72+
client = TextTranslationClient(credential=credential, region=region, resource_id=resource_id)
73+
```
74+
75+
<!-- END SNIPPET -->
76+
77+
## Custom Endpoint
78+
79+
When using some selected features (for example Virtual Network) you need to use custom endpoints. More information can be found in [Virtual Network support][TranslatorReference].
80+
81+
### Using the Subscription Key
82+
83+
For Custom endpoint and cognitive services key combination, you don't need to provide the region:
84+
85+
<!-- SNIPPET: sample_text_translation_client.create_text_translation_client_custom_with_credential -->
4986

5087
```python
5188
credential = AzureKeyCredential(apikey)
52-
text_translator = TextTranslationClient(credential=credential, endpoint=endpoint, region=region)
89+
text_translator = TextTranslationClient(credential=credential, endpoint=endpoint)
5390
```
5491

5592
<!-- END SNIPPET -->
5693

94+
### Using the Cognitive Services Token
95+
96+
The Cognitive Services Token is not supported when using the Custom Endpoint.
97+
98+
### Using the Entra Id Authentication
99+
100+
The Authentication with Microsoft Entra ID on custom endpoint requires you to provide only bearer token generated by Azure AD:
101+
102+
<!-- SNIPPET: sample_text_translation_client.create_text_translation_client_custom_with_entra_id_token -->
103+
104+
```python
105+
credential = DefaultAzureCredential()
106+
client = TextTranslationClient(credential=credential, endpoint=endpoint)
107+
```
108+
109+
<!-- END SNIPPET -->
110+
111+
## Azure AI Translator Container
112+
113+
When using `TextTranslationClient` with the [Azure AI Translator Container][Container] without any authentication. You will only need your endpoint:
114+
115+
<!-- SNIPPET: sample_text_translation_client.create_text_translation_client_with_endpoint -->
116+
117+
```python
118+
text_translator = TextTranslationClient(endpoint=endpoint)
119+
```
120+
121+
<!-- END SNIPPET -->
122+
123+
The values of the `endpoint` variable can be retrieved from environment variables, configuration settings, or any other secure approach that works for your application.
124+
57125
# Get Languages
58126

59127
This sample demonstrates how to get languages that are supported by other operations.
@@ -803,3 +871,8 @@ raise
803871
[breaksentence_sample]: https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/translation/azure-ai-translation-text/samples/sample_text_translation_break_sentence.py
804872
[dictionarylookup_sample]: https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/translation/azure-ai-translation-text/samples/sample_text_translation_dictionary_lookup.py
805873
[dictionaryexamples_sample]: https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/translation/azure-ai-translation-text/samples/sample_text_translation_dictionary_examples.py
874+
[static_access_token_credential]: https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/translation/azure-ai-translation-text/tests/static_access_token_credential.py
875+
876+
[Container]: https://learn.microsoft.com/azure/ai-services/translator/containers/overview
877+
[TranslatorReference]: https://learn.microsoft.com/azure/ai-services/translator/reference/v3-0-reference
878+
[SovereignClouds]: https://learn.microsoft.com/azure/ai-services/translator/sovereign-clouds

sdk/translation/azure-ai-translation-text/samples/sample_text_translation_client.py

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,24 @@
1818
1) AZURE_TEXT_TRANSLATION_ENDPOINT - the endpoint to your Text Translation resource.
1919
Note: the endpoint must be formatted to use the custom domain name for your resource:
2020
https:\\<NAME-OF-YOUR-RESOURCE>.cognitiveservices.azure.com\
21-
22-
The create_text_translation_client_with_credential call requires additional variables:
21+
2322
2) AZURE_TEXT_TRANSLATION_APIKEY - the API key to your Text Translation resource.
2423
3) AZURE_TEXT_TRANSLATION_REGION - the Azure Region of your Text Translation resource.
24+
4) AZURE_TEXT_TRANSLATION_RESOURCE_ID - the Azure Resource Id path
2525
"""
2626

2727
import os
2828

29+
from static_access_token_credential import StaticAccessTokenCredential
30+
2931

3032
# -------------------------------------------------------------------------
3133
# Text translation client
3234
# -------------------------------------------------------------------------
3335
def create_text_translation_client_with_endpoint():
3436
from azure.ai.translation.text import TextTranslationClient
3537

36-
endpoint = os.environ["AZURE_TEXT_TRANSLATION_ENDPOINT"]
38+
endpoint = "http://localhost"
3739
# [START create_text_translation_client_with_endpoint]
3840
text_translator = TextTranslationClient(endpoint=endpoint)
3941
# [END create_text_translation_client_with_endpoint]
@@ -44,11 +46,58 @@ def create_text_translation_client_with_credential():
4446
from azure.ai.translation.text import TextTranslationClient
4547
from azure.core.credentials import AzureKeyCredential
4648

47-
endpoint = os.environ["AZURE_TEXT_TRANSLATION_ENDPOINT"]
4849
apikey = os.environ["AZURE_TEXT_TRANSLATION_APIKEY"]
4950
region = os.environ["AZURE_TEXT_TRANSLATION_REGION"]
5051
# [START create_text_translation_client_with_credential]
5152
credential = AzureKeyCredential(apikey)
52-
text_translator = TextTranslationClient(credential=credential, endpoint=endpoint, region=region)
53+
text_translator = TextTranslationClient(credential=credential, region=region)
5354
# [END create_text_translation_client_with_credential]
5455
return text_translator
56+
57+
def create_text_translation_client_custom_with_credential():
58+
from azure.ai.translation.text import TextTranslationClient
59+
from azure.core.credentials import AzureKeyCredential
60+
61+
endpoint = os.environ["AZURE_TEXT_TRANSLATION_ENDPOINT"]
62+
apikey = os.environ["AZURE_TEXT_TRANSLATION_APIKEY"]
63+
# [START create_text_translation_client_custom_with_credential]
64+
credential = AzureKeyCredential(apikey)
65+
text_translator = TextTranslationClient(credential=credential, endpoint=endpoint)
66+
# [END create_text_translation_client_custom_with_credential]
67+
return text_translator
68+
69+
def create_text_translation_client_with_cognitive_services_token():
70+
from azure.ai.translation.text import TextTranslationClient
71+
from azure.core.credentials import TokenCredential
72+
73+
apikey = os.environ["AZURE_TEXT_TRANSLATION_APIKEY"]
74+
region = os.environ["AZURE_TEXT_TRANSLATION_REGION"]
75+
76+
credential: TokenCredential = StaticAccessTokenCredential(apikey, region)
77+
78+
# [START create_text_translation_client_with_cognitive_services_token]
79+
client = TextTranslationClient(credential=credential, audience="https://api.microsofttranslator.com/")
80+
# [END create_text_translation_client_with_cognitive_services_token]
81+
82+
def create_text_translation_client_with_entra_id_token():
83+
from azure.ai.translation.text import TextTranslationClient
84+
from azure.identity import DefaultAzureCredential
85+
86+
region = os.environ["AZURE_TEXT_TRANSLATION_REGION"]
87+
resource_id = os.environ["AZURE_TEXT_TRANSLATION_RESOURCE_ID"]
88+
89+
# [START create_text_translation_client_with_entra_id_token]
90+
credential = DefaultAzureCredential()
91+
client = TextTranslationClient(credential=credential, region=region, resource_id=resource_id)
92+
# [END create_text_translation_client_with_entra_id_token]
93+
94+
def create_text_translation_client_custom_with_entra_id_token():
95+
from azure.ai.translation.text import TextTranslationClient
96+
from azure.identity import DefaultAzureCredential
97+
98+
endpoint = os.environ["AZURE_TEXT_TRANSLATION_ENDPOINT"]
99+
100+
# [START create_text_translation_client_custom_with_entra_id_token]
101+
credential = DefaultAzureCredential()
102+
client = TextTranslationClient(credential=credential, endpoint=endpoint)
103+
# [END create_text_translation_client_custom_with_entra_id_token]

sdk/translation/azure-ai-translation-text/tests/test_translation.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ def test_token(self, **kwargs):
290290
endpoint = kwargs.get("translation_text_endpoint")
291291
apikey = kwargs.get("translation_text_apikey")
292292
region = kwargs.get("translation_text_region")
293-
client = self.create_client_token(endpoint, apikey, region)
293+
client = self.create_client_token(endpoint, apikey, region, "https://api.microsofttranslator.com/")
294294

295295
to_language = ["cs"]
296296
input_text_elements = ["This is a test."]
@@ -319,3 +319,21 @@ def test_translate_aad(self, **kwargs):
319319
assert len(response[0].translations) == 1
320320
assert response[0].translations[0].to == "cs"
321321
assert response[0].translations[0].text is not None
322+
323+
@pytest.mark.skip
324+
@TextTranslationPreparer()
325+
@recorded_by_proxy
326+
def test_translate_aad_custom(self, **kwargs):
327+
endpoint = kwargs.get("translation_text_custom_endpoint")
328+
token_credential = self.get_mt_credential(False)
329+
client = self.create_text_translation_client_custom_with_aad(token_credential, endpoint=endpoint)
330+
331+
from_language = "es"
332+
to_language = ["cs"]
333+
input_text_elements = ["Hola mundo"]
334+
response = client.translate(body=input_text_elements, to_language=to_language, from_language=from_language)
335+
336+
assert len(response) == 1
337+
assert len(response[0].translations) == 1
338+
assert response[0].translations[0].to == "cs"
339+
assert response[0].translations[0].text is not None

sdk/translation/azure-ai-translation-text/tests/test_translation_async.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ async def test_token(self, **kwargs):
312312
endpoint = kwargs.get("translation_text_endpoint")
313313
apikey = kwargs.get("translation_text_apikey")
314314
region = kwargs.get("translation_text_region")
315-
client = self.create_async_client_token(endpoint, apikey, region)
315+
client = self.create_async_client_token(endpoint, apikey, region, "https://api.microsofttranslator.com/")
316316

317317
to_language = ["cs"]
318318
input_text_elements = ["This is a test."]

sdk/translation/azure-ai-translation-text/tests/testcase.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,19 @@ def create_client(self, endpoint, apikey, region):
2222
client = TextTranslationClient(endpoint=endpoint, credential=credential, region=region)
2323
return client
2424

25-
def create_client_token(self, endpoint, apikey, region):
25+
def create_client_token(self, endpoint, apikey, region, audience):
2626
credential = StaticAccessTokenCredential(apikey, region)
27-
client = TextTranslationClient(endpoint=endpoint, credential=credential)
27+
client = TextTranslationClient(endpoint=endpoint, credential=credential, audience=audience)
2828
return client
2929

3030
def create_text_translation_client_with_aad(self, innerCredential, aadRegion, aadResourceId):
3131
text_translator = TextTranslationClient(credential=innerCredential, resource_id=aadResourceId, region=aadRegion)
3232
return text_translator
3333

34+
def create_text_translation_client_custom_with_aad(self, innerCredential, endpoint):
35+
text_translator = TextTranslationClient(credential=innerCredential, endpoint=endpoint)
36+
return text_translator
37+
3438
def create_async_getlanguage_client(self, endpoint):
3539
from azure.ai.translation.text.aio import TextTranslationClient as TextTranslationClientAsync
3640

@@ -44,11 +48,11 @@ def create_async_client(self, endpoint, apikey, region):
4448
client = TextTranslationClientAsync(endpoint=endpoint, credential=credential, region=region)
4549
return client
4650

47-
def create_async_client_token(self, endpoint, apikey, region):
51+
def create_async_client_token(self, endpoint, apikey, region, audience):
4852
credential = StaticAccessTokenCredential(apikey, region)
4953
from azure.ai.translation.text.aio import TextTranslationClient as TextTranslationClientAsync
5054

51-
client = TextTranslationClientAsync(endpoint=endpoint, credential=credential)
55+
client = TextTranslationClientAsync(endpoint=endpoint, credential=credential, audience=audience)
5256
return client
5357

5458
def create_async_text_translation_client_with_aad(self, innerCredential, aadRegion, aadResourceId):

0 commit comments

Comments
 (0)