Skip to content

Commit

Permalink
[translation] AAD support (Azure#18892)
Browse files Browse the repository at this point in the history
* add support for aad authentication

* add samples and snippets for aad auth

* changelog, readme, dev requirements update

* add basic test cases for aad auth

* fix envar

* review feedback
  • Loading branch information
kristapratico authored May 26, 2021
1 parent 8a125f5 commit 29f6900
Show file tree
Hide file tree
Showing 13 changed files with 223 additions and 33 deletions.
2 changes: 2 additions & 0 deletions sdk/translation/azure-ai-translation-document/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ This version of the SDK defaults to the latest supported service version, which

**New features**

- Authentication using `azure-identity` credentials now supported.
- see the [Azure Identity documentation](https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/identity/azure-identity/README.md) for more information.
- Added paging and filtering options to `list_all_document_statuses` and `list_submitted_jobs`.

**Dependency updates**
Expand Down
31 changes: 31 additions & 0 deletions sdk/translation/azure-ai-translation-document/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,33 @@ credential = AzureKeyCredential("<api_key>")
document_translation_client = DocumentTranslationClient(endpoint, credential)
```

#### Create the client with an Azure Active Directory credential

`AzureKeyCredential` authentication is used in the examples in this getting started guide, but you can also
authenticate with Azure Active Directory using the [azure-identity][azure_identity] library.

To use the [DefaultAzureCredential][default_azure_credential] type shown below, or other credential types provided
with the Azure SDK, please install the `azure-identity` package:

```pip install azure-identity```

You will also need to [register a new AAD application and grant access][register_aad_app] to your
Translator resource by assigning the `"Cognitive Services User"` role to your service principal.

Once completed, set the values of the client ID, tenant ID, and client secret of the AAD application as environment variables:
`AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_SECRET`.

```python
from azure.identity import DefaultAzureCredential
from azure.ai.translation.document import DocumentTranslationClient
credential = DefaultAzureCredential()

document_translation_client = DocumentTranslationClient(
endpoint="https://<resource-name>.cognitiveservices.azure.com/",
credential=credential
)
```

## Key concepts

The Document Translation service requires that you upload your files to an Azure Blob Storage source container and provide
Expand Down Expand Up @@ -380,6 +407,10 @@ This project has adopted the [Microsoft Open Source Code of Conduct][code_of_con
[azure_cli_endpoint_lookup]: https://docs.microsoft.com/cli/azure/cognitiveservices/account?view=azure-cli-latest#az-cognitiveservices-account-show
[azure_portal_get_endpoint]: https://docs.microsoft.com/azure/cognitive-services/translator/document-translation/get-started-with-document-translation?tabs=csharp#get-your-custom-domain-name-and-subscription-key
[cognitive_authentication_api_key]: https://docs.microsoft.com/azure/cognitive-services/translator/document-translation/get-started-with-document-translation?tabs=csharp#get-your-subscription-key
[register_aad_app]: https://docs.microsoft.com/azure/cognitive-services/authentication?tabs=powershell#authenticate-with-azure-active-directory
[custom_subdomain]: https://docs.microsoft.com/azure/cognitive-services/authentication#create-a-resource-with-a-custom-subdomain
[azure_identity]: https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/identity/azure-identity
[default_azure_credential]: https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/identity/azure-identity#defaultazurecredential

[sdk_logging_docs]: https://docs.microsoft.com/azure/developer/python/azure-sdk-logging

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@
# Licensed under the MIT License.
# ------------------------------------

from typing import Any, TYPE_CHECKING, List
from typing import Any, TYPE_CHECKING, List, Union
from azure.core.tracing.decorator import distributed_trace
from azure.core.polling import LROPoller
from azure.core.polling.base_polling import LROBasePolling
from azure.core.credentials import AzureKeyCredential
from azure.core.pipeline.policies import AzureKeyCredentialPolicy
from ._generated import BatchDocumentTranslationClient as _BatchDocumentTranslationClient
from ._generated.models import TranslationStatus as _TranslationStatus
from ._models import (
Expand All @@ -20,26 +18,27 @@
)
from ._user_agent import USER_AGENT
from ._polling import TranslationPolling
from ._helpers import get_http_logging_policy, convert_datetime
from ._helpers import get_http_logging_policy, convert_datetime, get_authentication_policy
if TYPE_CHECKING:
from azure.core.paging import ItemPaged

COGNITIVE_KEY_HEADER = "Ocp-Apim-Subscription-Key"
from azure.core.credentials import TokenCredential, AzureKeyCredential


class DocumentTranslationClient(object): # pylint: disable=r0205

def __init__(self, endpoint, credential, **kwargs):
# type: (str, AzureKeyCredential, **Any) -> None
# type: (str, Union[AzureKeyCredential, TokenCredential], Any) -> None
"""DocumentTranslationClient is your interface to the Document Translation service.
Use the client to translate whole documents while preserving source document
structure and text formatting.
:param str endpoint: Supported Document Translation endpoint (protocol and hostname, for example:
https://<resource-name>.cognitiveservices.azure.com/).
:param credential: Credential needed for the client to connect to Azure.
Currently only API key authentication is supported.
:type credential: :class:`~azure.core.credentials.AzureKeyCredential`
:param credential: Credentials needed for the client to connect to Azure.
This is an instance of AzureKeyCredential if using an API key or a token
credential from :mod:`azure.identity`.
:type credential: :class:`~azure.core.credentials.AzureKeyCredential` or
:class:`~azure.core.credentials.TokenCredential`
:keyword api_version:
The API version of the service to use for requests. It defaults to the latest service version.
Setting to an older version may result in reduced feature compatibility.
Expand All @@ -53,16 +52,19 @@ def __init__(self, endpoint, credential, **kwargs):
:language: python
:dedent: 4
:caption: Creating the DocumentTranslationClient with an endpoint and API key.
.. literalinclude:: ../samples/sample_authentication.py
:start-after: [START create_dt_client_with_aad]
:end-before: [END create_dt_client_with_aad]
:language: python
:dedent: 4
:caption: Creating the DocumentTranslationClient with a token credential.
"""
self._endpoint = endpoint
self._credential = credential
self._api_version = kwargs.pop('api_version', None)

if credential is None:
raise ValueError("Parameter 'credential' must not be None.")
authentication_policy = AzureKeyCredentialPolicy(
name=COGNITIVE_KEY_HEADER, credential=credential
)
authentication_policy = get_authentication_policy(credential)

self._client = _BatchDocumentTranslationClient(
endpoint=endpoint,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,27 @@
import datetime
from typing import Union
import six
from azure.core.credentials import AzureKeyCredential
from azure.core.pipeline.policies import AzureKeyCredentialPolicy
from azure.core.pipeline.policies import HttpLoggingPolicy
COGNITIVE_KEY_HEADER = "Ocp-Apim-Subscription-Key"


def get_authentication_policy(credential):
authentication_policy = None
if credential is None:
raise ValueError("Parameter 'credential' must not be None.")
if isinstance(credential, AzureKeyCredential):
authentication_policy = AzureKeyCredentialPolicy(
name=COGNITIVE_KEY_HEADER, credential=credential
)
elif credential is not None and not hasattr(credential, "get_token"):
raise TypeError(
"Unsupported credential: {}. Use an instance of AzureKeyCredential "
"or a token credential from azure.identity".format(type(credential))
)

return authentication_policy


def get_http_logging_policy(**kwargs):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@
# Licensed under the MIT License.
# ------------------------------------

from typing import Any, List
from typing import Any, List, Union, TYPE_CHECKING
from azure.core.tracing.decorator_async import distributed_trace_async
from azure.core.tracing.decorator import distributed_trace
from azure.core.polling import AsyncLROPoller
from azure.core.polling.async_base_polling import AsyncLROBasePolling
from azure.core.async_paging import AsyncItemPaged
from azure.core.credentials import AzureKeyCredential
from azure.core.pipeline.policies import AzureKeyCredentialPolicy
from .._generated.aio import BatchDocumentTranslationClient as _BatchDocumentTranslationClient
from .._user_agent import USER_AGENT
from .._generated.models import (
Expand All @@ -23,25 +21,29 @@
FileFormat,
DocumentStatusResult
)
from .._helpers import get_http_logging_policy, convert_datetime
from .._helpers import get_http_logging_policy, convert_datetime, get_authentication_policy
from .._polling import TranslationPolling
COGNITIVE_KEY_HEADER = "Ocp-Apim-Subscription-Key"
if TYPE_CHECKING:
from azure.core.credentials import AzureKeyCredential
from azure.core.credentials_async import AsyncTokenCredential


class DocumentTranslationClient(object):

def __init__(
self, endpoint: str, credential: "AzureKeyCredential", **kwargs: Any
self, endpoint: str, credential: Union["AzureKeyCredential", "AsyncTokenCredential"], **kwargs: Any
) -> None:
"""DocumentTranslationClient is your interface to the Document Translation service.
Use the client to translate whole documents while preserving source document
structure and text formatting.
:param str endpoint: Supported Document Translation endpoint (protocol and hostname, for example:
https://<resource-name>.cognitiveservices.azure.com/).
:param credential: Credential needed for the client to connect to Azure.
Currently only API key authentication is supported.
:type credential: :class:`~azure.core.credentials.AzureKeyCredential`
:param credential: Credentials needed for the client to connect to Azure.
This is an instance of AzureKeyCredential if using an API key or a token
credential from :mod:`azure.identity`.
:type credential: :class:`~azure.core.credentials.AzureKeyCredential` or
:class:`~azure.core.credentials.TokenCredential`
:keyword api_version:
The API version of the service to use for requests. It defaults to the latest service version.
Setting to an older version may result in reduced feature compatibility.
Expand All @@ -55,16 +57,19 @@ def __init__(
:language: python
:dedent: 4
:caption: Creating the DocumentTranslationClient with an endpoint and API key.
.. literalinclude:: ../samples/async_samples/sample_authentication_async.py
:start-after: [START create_dt_client_with_aad_async]
:end-before: [END create_dt_client_with_aad_async]
:language: python
:dedent: 4
:caption: Creating the DocumentTranslationClient with a token credential.
"""
self._endpoint = endpoint
self._credential = credential
self._api_version = kwargs.pop('api_version', None)

if credential is None:
raise ValueError("Parameter 'credential' must not be None.")
authentication_policy = AzureKeyCredentialPolicy(
name=COGNITIVE_KEY_HEADER, credential=credential
)
authentication_policy = get_authentication_policy(credential)
self._client = _BatchDocumentTranslationClient(
endpoint=endpoint,
credential=credential, # type: ignore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
-e ../../../tools/azure-devtools
../../core/azure-core
../../storage/azure-storage-blob
../../nspkg/azure-ai-translation-nspkg
-e ../../identity/azure-identity
aiohttp>=3.0; python_version >= '3.5'
pytz
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ urlFragment: documenttranslation-samples
These code samples show common scenario operations with the Azure Document Translation client library.
The async versions of the samples require Python 3.6 or later.

You can authenticate your client with a Document Translation API key:
You can authenticate your client with a Document Translation API key or through Azure Active Directory with a token credential from [azure-identity][azure_identity]:
* See [sample_authentication.py][sample_authentication] and [sample_authentication_async.py][sample_authentication_async] for how to authenticate in the above cases.

These sample programs show common scenarios for the Document Translation client's offerings.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@
DESCRIPTION:
This sample demonstrates how to authenticate to the Document Translation service.
There is currently only one supported method of authentication:
There are two supported methods of authentication:
1) Use a Document Translation API key with AzureKeyCredential from azure.core.credentials
2) Use a token credential from azure-identity to authenticate with Azure Active Directory
See more details about authentication here:
https://docs.microsoft.com/azure/cognitive-services/authentication
Note: the endpoint must be formatted to use the custom domain name for your resource:
https://<NAME-OF-YOUR-RESOURCE>.cognitiveservices.azure.com/
Expand All @@ -24,6 +28,9 @@
Set the environment variables with your own values before running the sample:
1) AZURE_DOCUMENT_TRANSLATION_ENDPOINT - the endpoint to your Document Translation resource.
2) AZURE_DOCUMENT_TRANSLATION_KEY - your Document Translation API key
3) AZURE_CLIENT_ID - the client ID of your active directory application.
4) AZURE_TENANT_ID - the tenant ID of your active directory application.
5) AZURE_CLIENT_SECRET - the secret of your active directory application.
"""

import os
Expand All @@ -46,8 +53,28 @@ async def sample_authentication_api_key_async():
result = await document_translation_client.get_document_formats()


def sample_authentication_with_azure_active_directory_async():
# [START create_dt_client_with_aad_async]
"""DefaultAzureCredential will use the values from these environment
variables: AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_CLIENT_SECRET
"""
from azure.identity.aio import DefaultAzureCredential
from azure.ai.translation.document.aio import DocumentTranslationClient

endpoint = os.environ["AZURE_DOCUMENT_TRANSLATION_ENDPOINT"]
credential = DefaultAzureCredential()

document_translation_client = DocumentTranslationClient(endpoint, credential)
# [END create_dt_client_with_aad_async]

# make calls with authenticated client
async with document_translation_client:
result = document_translation_client.get_document_formats()


async def main():
await sample_authentication_api_key_async()
await sample_authentication_with_azure_active_directory_async()

if __name__ == '__main__':
loop = asyncio.get_event_loop()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@
DESCRIPTION:
This sample demonstrates how to authenticate to the Document Translation service.
There is currently only one supported method of authentication:
There are two supported methods of authentication:
1) Use a Document Translation API key with AzureKeyCredential from azure.core.credentials
2) Use a token credential from azure-identity to authenticate with Azure Active Directory
See more details about authentication here:
https://docs.microsoft.com/azure/cognitive-services/authentication
Note: the endpoint must be formatted to use the custom domain name for your resource:
https://<NAME-OF-YOUR-RESOURCE>.cognitiveservices.azure.com/
Expand All @@ -24,6 +28,9 @@
Set the environment variables with your own values before running the sample:
1) AZURE_DOCUMENT_TRANSLATION_ENDPOINT - the endpoint to your Document Translation resource.
2) AZURE_DOCUMENT_TRANSLATION_KEY - your Document Translation API key
3) AZURE_CLIENT_ID - the client ID of your active directory application.
4) AZURE_TENANT_ID - the tenant ID of your active directory application.
5) AZURE_CLIENT_SECRET - the secret of your active directory application.
"""

import os
Expand All @@ -44,5 +51,24 @@ def sample_authentication_api_key():
result = document_translation_client.get_document_formats()


def sample_authentication_with_azure_active_directory():
# [START create_dt_client_with_aad]
"""DefaultAzureCredential will use the values from these environment
variables: AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_CLIENT_SECRET
"""
from azure.identity import DefaultAzureCredential
from azure.ai.translation.document import DocumentTranslationClient

endpoint = os.environ["AZURE_DOCUMENT_TRANSLATION_ENDPOINT"]
credential = DefaultAzureCredential()

document_translation_client = DocumentTranslationClient(endpoint, credential)
# [END create_dt_client_with_aad]

# make calls with authenticated client
result = document_translation_client.get_document_formats()


if __name__ == '__main__':
sample_authentication_api_key()
sample_authentication_with_azure_active_directory()
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# ------------------------------------

import os
from testcase import DocumentTranslationTest, Document
from azure.ai.translation.document import DocumentTranslationInput, TranslationTarget

Expand All @@ -11,6 +13,15 @@ class AsyncDocumentTranslationTest(DocumentTranslationTest):
def __init__(self, method_name):
super(AsyncDocumentTranslationTest, self).__init__(method_name)

def generate_oauth_token(self):
if self.is_live:
from azure.identity.aio import ClientSecretCredential
return ClientSecretCredential(
os.getenv("TRANSLATION_TENANT_ID"),
os.getenv("TRANSLATION_CLIENT_ID"),
os.getenv("TRANSLATION_CLIENT_SECRET"),
)

async def _submit_and_validate_translation_job_async(self, async_client, translation_inputs, total_docs_count=None):
# submit job
job_details = await async_client.create_translation_job(translation_inputs)
Expand Down
Loading

0 comments on commit 29f6900

Please sign in to comment.