From 6929968a5ebd7d9fa49dccc0b42cc3ee2d5c0e0f Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Tue, 16 Jul 2019 09:53:22 -0700 Subject: [PATCH 1/8] credential wrapping MSAL public application --- .../azure-identity/azure/identity/_internal/__init__.py | 4 ++-- .../azure/identity/_internal/msal_credentials.py | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/sdk/identity/azure-identity/azure/identity/_internal/__init__.py b/sdk/identity/azure-identity/azure/identity/_internal/__init__.py index 9ea29a25784d..cfdf935b801c 100644 --- a/sdk/identity/azure-identity/azure/identity/_internal/__init__.py +++ b/sdk/identity/azure-identity/azure/identity/_internal/__init__.py @@ -2,5 +2,5 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ -from .msal_credentials import ConfidentialClientCredential -from .msal_transport_adapter import MsalTransportResponse, MsalTransportAdapter +from .msal_credentials import ConfidentialClientCredential, PublicClientCredential +from .msal_transport_adapter import MsalTransportAdapter, MsalTransportResponse diff --git a/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py b/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py index 9bf44cbb3219..51032114a21b 100644 --- a/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py +++ b/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py @@ -86,3 +86,11 @@ def get_token(self, *scopes): raise ClientAuthenticationError(message="authentication failed: {}".format(result.get("error_description"))) return AccessToken(result["access_token"], now + int(result["expires_in"])) + + +class PublicClientCredential(MsalCredential): + """Wraps an MSAL PublicClientApplication with the TokenCredential API""" + + def __init__(self, **kwargs): + # type: (Any) -> None + super(PublicClientCredential, self).__init__(app_class=msal.PublicClientApplication, **kwargs) From 197b2680d3d4665dddae1a745e4275544bf28f8f Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Tue, 16 Jul 2019 09:54:02 -0700 Subject: [PATCH 2/8] UsernamePasswordCredential --- .../azure-identity/azure/identity/__init__.py | 2 + .../identity/_internal/msal_credentials.py | 6 +- .../azure/identity/credentials.py | 58 +++++++++++++++++++ .../azure-identity/tests/test_identity.py | 33 +++++++++++ 4 files changed, 98 insertions(+), 1 deletion(-) diff --git a/sdk/identity/azure-identity/azure/identity/__init__.py b/sdk/identity/azure-identity/azure/identity/__init__.py index 512b415a27ec..fd23d51c8e32 100644 --- a/sdk/identity/azure-identity/azure/identity/__init__.py +++ b/sdk/identity/azure-identity/azure/identity/__init__.py @@ -8,6 +8,7 @@ ClientSecretCredential, EnvironmentCredential, ManagedIdentityCredential, + UsernamePasswordCredential, ) @@ -35,4 +36,5 @@ def __init__(self, **kwargs): "DefaultAzureCredential", "EnvironmentCredential", "ManagedIdentityCredential", + "UsernamePasswordCredential", ] diff --git a/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py b/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py index 51032114a21b..d03c3be4cdff 100644 --- a/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py +++ b/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py @@ -93,4 +93,8 @@ class PublicClientCredential(MsalCredential): def __init__(self, **kwargs): # type: (Any) -> None - super(PublicClientCredential, self).__init__(app_class=msal.PublicClientApplication, **kwargs) + super(PublicClientCredential, self).__init__( + app_class=msal.PublicClientApplication, + authority="https://login.microsoftonline.com/organizations", + **kwargs + ) diff --git a/sdk/identity/azure-identity/azure/identity/credentials.py b/sdk/identity/azure-identity/azure/identity/credentials.py index d53edf8e2c62..d3bf3f02899d 100644 --- a/sdk/identity/azure-identity/azure/identity/credentials.py +++ b/sdk/identity/azure-identity/azure/identity/credentials.py @@ -6,6 +6,7 @@ Credentials for Azure SDK authentication. """ import os +import time from azure.core import Configuration from azure.core.credentials import AccessToken @@ -14,6 +15,7 @@ from ._authn_client import AuthnClient from ._base import ClientSecretCredentialBase, CertificateCredentialBase +from ._internal import PublicClientCredential from ._managed_identity import ImdsCredential, MsiCredential from .constants import Endpoints, EnvironmentVariables @@ -233,3 +235,59 @@ def _get_error_message(history): else: attempts.append(credential.__class__.__name__) return "No valid token received. {}".format(". ".join(attempts)) + + +class UsernamePasswordCredential(PublicClientCredential): + """ + Authenticates a user with a username and password. In general, Microsoft doesn't recommend this kind of + authentication, because it's less secure than other authentication flows. + + Authentication with this credential is not interactive, so it is **not compatible with any form of + multi-factor authentication or consent prompting**. The application must already have the user's consent. + + This credential can only authenticate work and school accounts; Microsoft accounts are not supported. + See this document for more information about account types: + https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/sign-up-organization + + :param str client_id: the application's client ID + :param str username: the user's username (usually an email address) + :param str password: the user's password + """ + + def __init__(self, client_id, username, password, **kwargs): + # type: (str, str, str, Any) -> None + super(UsernamePasswordCredential, self).__init__(client_id=client_id, **kwargs) + self._username = username + self._password = password + + def get_token(self, *scopes): + # type (*str) -> AccessToken + """ + Request an access token for `scopes`. + + :param str scopes: desired scopes for the token + :rtype: :class:`azure.core.credentials.AccessToken` + :raises: :class:`azure.core.exceptions.ClientAuthenticationError` + """ + + # MSAL requires scopes be a list + scopes = list(scopes) # type: ignore + now = int(time.time()) + + accounts = self._app.get_accounts(username=self._username) + result = None + for account in accounts: + result = self._app.acquire_token_silent(scopes, account=account) + if result: + break + + if not result: + # cache miss -> request a new token + result = self._app.acquire_token_by_username_password( + username=self._username, password=self._password, scopes=scopes + ) + + if "access_token" not in result: + raise ClientAuthenticationError(message="authentication failed: {}".format(result.get("error_description"))) + + return AccessToken(result["access_token"], now + int(result["expires_in"])) diff --git a/sdk/identity/azure-identity/tests/test_identity.py b/sdk/identity/azure-identity/tests/test_identity.py index b017792595a8..6a5da4b400a5 100644 --- a/sdk/identity/azure-identity/tests/test_identity.py +++ b/sdk/identity/azure-identity/tests/test_identity.py @@ -21,6 +21,7 @@ EnvironmentCredential, ManagedIdentityCredential, ChainedTokenCredential, + UsernamePasswordCredential, ) from azure.identity._managed_identity import ImdsCredential from azure.identity.constants import EnvironmentVariables @@ -239,3 +240,35 @@ def test_imds_credential_retries(): def test_default_credential(): DefaultAzureCredential() + + +def test_username_password_credential(): + expected_token = "access-token" + tenant_id = "guid" + transport = validating_transport( + requests=[Request()] * 2, # not validating requests because they're formed by MSAL + responses=[ + # tenant discovery, then a token request + mock_response(json_payload={"authorization_endpoint": "https://a/b", "token_endpoint": "https://a/b"}), + mock_response( + json_payload={ + "access_token": expected_token, + "expires_in": 42, + "token_type": "Bearer", + "ext_expires_in": 42, + } + ), + ], + ) + + credential = UsernamePasswordCredential( + client_id="some-guid", + tenant_id=tenant_id, + username="user@azure", + password="secret_password", + transport=transport, + instance_discovery=False, # kwargs are passed to MSAL; this one prevents an AAD verification request + ) + + token = credential.get_token("scope") + assert token.token == expected_token From 360bbcc36a69f0ac0dde8ca5db3f3d8f2a92ec03 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Fri, 19 Jul 2019 09:44:13 -0700 Subject: [PATCH 3/8] tenant id is superfluous --- sdk/identity/azure-identity/tests/test_identity.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sdk/identity/azure-identity/tests/test_identity.py b/sdk/identity/azure-identity/tests/test_identity.py index 6a5da4b400a5..4e7fb13b4f6b 100644 --- a/sdk/identity/azure-identity/tests/test_identity.py +++ b/sdk/identity/azure-identity/tests/test_identity.py @@ -244,11 +244,10 @@ def test_default_credential(): def test_username_password_credential(): expected_token = "access-token" - tenant_id = "guid" transport = validating_transport( requests=[Request()] * 2, # not validating requests because they're formed by MSAL responses=[ - # tenant discovery, then a token request + # expecting tenant discovery then a token request mock_response(json_payload={"authorization_endpoint": "https://a/b", "token_endpoint": "https://a/b"}), mock_response( json_payload={ @@ -263,7 +262,6 @@ def test_username_password_credential(): credential = UsernamePasswordCredential( client_id="some-guid", - tenant_id=tenant_id, username="user@azure", password="secret_password", transport=transport, From 61aebd75f589e3c25c784d90f02117fc8ff86be4 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Fri, 19 Jul 2019 10:11:39 -0700 Subject: [PATCH 4/8] accept tenant kwarg --- .../azure/identity/_internal/msal_credentials.py | 2 +- sdk/identity/azure-identity/azure/identity/credentials.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py b/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py index d03c3be4cdff..d36d89be8b25 100644 --- a/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py +++ b/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py @@ -95,6 +95,6 @@ def __init__(self, **kwargs): # type: (Any) -> None super(PublicClientCredential, self).__init__( app_class=msal.PublicClientApplication, - authority="https://login.microsoftonline.com/organizations", + authority="https://login.microsoftonline.com/" + kwargs.pop("tenant", "organizations"), **kwargs ) diff --git a/sdk/identity/azure-identity/azure/identity/credentials.py b/sdk/identity/azure-identity/azure/identity/credentials.py index d3bf3f02899d..bea7ed60f1f7 100644 --- a/sdk/identity/azure-identity/azure/identity/credentials.py +++ b/sdk/identity/azure-identity/azure/identity/credentials.py @@ -252,6 +252,10 @@ class UsernamePasswordCredential(PublicClientCredential): :param str client_id: the application's client ID :param str username: the user's username (usually an email address) :param str password: the user's password + + **Keyword arguments:** + + *tenant (str)* - ID or domain associated with an Azure Active Directory tenant """ def __init__(self, client_id, username, password, **kwargs): From 2eacc9f7da0a9c67e363e5c083b861325f0fed25 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Tue, 23 Jul 2019 16:19:52 -0700 Subject: [PATCH 5/8] update docstring --- sdk/identity/azure-identity/azure/identity/credentials.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/identity/azure-identity/azure/identity/credentials.py b/sdk/identity/azure-identity/azure/identity/credentials.py index bea7ed60f1f7..d4359e50de64 100644 --- a/sdk/identity/azure-identity/azure/identity/credentials.py +++ b/sdk/identity/azure-identity/azure/identity/credentials.py @@ -255,7 +255,8 @@ class UsernamePasswordCredential(PublicClientCredential): **Keyword arguments:** - *tenant (str)* - ID or domain associated with an Azure Active Directory tenant + *tenant (str)* - a tenant ID or a domain associated with a tenant. If not provided, the credential defaults to the + 'organizations' tenant. """ def __init__(self, client_id, username, password, **kwargs): From 227b2181e71b73d0a4eb7ae6e53cf3fc7bb28d75 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Wed, 24 Jul 2019 13:50:28 -0700 Subject: [PATCH 6/8] add username/password auth to EnvironmentCredential --- .../azure/identity/constants.py | 4 +++ .../azure/identity/credentials.py | 35 ++++++++++++------- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/sdk/identity/azure-identity/azure/identity/constants.py b/sdk/identity/azure-identity/azure/identity/constants.py index 5b50dd98625b..1c2608e5da8b 100644 --- a/sdk/identity/azure-identity/azure/identity/constants.py +++ b/sdk/identity/azure-identity/azure/identity/constants.py @@ -13,6 +13,10 @@ class EnvironmentVariables: AZURE_CLIENT_CERTIFICATE_PATH = "AZURE_CLIENT_CERTIFICATE_PATH" CERT_VARS = (AZURE_CLIENT_ID, AZURE_CLIENT_CERTIFICATE_PATH, AZURE_TENANT_ID) + AZURE_USERNAME = "AZURE_USERNAME" + AZURE_PASSWORD = "AZURE_PASSWORD" + USERNAME_PASSWORD_VARS = (AZURE_CLIENT_ID, AZURE_USERNAME, AZURE_PASSWORD) + MSI_ENDPOINT = "MSI_ENDPOINT" MSI_SECRET = "MSI_SECRET" diff --git a/sdk/identity/azure-identity/azure/identity/credentials.py b/sdk/identity/azure-identity/azure/identity/credentials.py index d4359e50de64..88af66f22b12 100644 --- a/sdk/identity/azure-identity/azure/identity/credentials.py +++ b/sdk/identity/azure-identity/azure/identity/credentials.py @@ -98,23 +98,29 @@ def get_token(self, *scopes): class EnvironmentCredential: """ - Authenticates as a service principal using a client ID/secret pair or a certificate, - depending on environment variable settings. - - These environment variables are required: + Authenticates as a service principal using a client secret or a certificate, or as a user with a username and + password, depending on environment variable settings. Configuration is attempted in this order, using these + environment variables: + Service principal with secret: - **AZURE_CLIENT_ID**: the service principal's client ID + - **AZURE_CLIENT_SECRET**: one of the service principal's client secrets - **AZURE_TENANT_ID**: ID of the service principal's tenant. Also called its 'directory' ID. - Additionally, set **one** of these to configure client secret or certificate authentication: - - - **AZURE_CLIENT_SECRET**: one of the service principal's client secrets + Service principal with certificate: + - **AZURE_CLIENT_ID**: the service principal's client ID - **AZURE_CLIENT_CERTIFICATE_PATH**: path to a PEM-encoded certificate file including the private key + - **AZURE_TENANT_ID**: ID of the service principal's tenant. Also called its 'directory' ID. + + User with username and password: + - **AZURE_CLIENT_ID**: the application's client ID + - **AZURE_USERNAME**: a username (usually an email address) + - **AZURE_PASSWORD**: that user's password """ def __init__(self, **kwargs): # type: (Mapping[str, Any]) -> None - self._credential = None # type: Optional[Union[CertificateCredential, ClientSecretCredential]] + self._credential = None # type: Optional[EnvironmentCredentialTypes] if all(os.environ.get(v) is not None for v in EnvironmentVariables.CLIENT_SECRET_VARS): self._credential = ClientSecretCredential( @@ -130,6 +136,14 @@ def __init__(self, **kwargs): certificate_path=os.environ[EnvironmentVariables.AZURE_CLIENT_CERTIFICATE_PATH], **kwargs ) + elif all(os.environ.get(v) is not None for v in EnvironmentVariables.USERNAME_PASSWORD_VARS): + self._credential = UsernamePasswordCredential( + client_id=os.environ[EnvironmentVariables.AZURE_CLIENT_ID], + username=os.environ[EnvironmentVariables.AZURE_USERNAME], + password=os.environ[EnvironmentVariables.AZURE_PASSWORD], + tenant=os.environ.get(EnvironmentVariables.AZURE_TENANT_ID), # optional for username/password auth + **kwargs + ) def get_token(self, *scopes): # type (*str) -> AccessToken @@ -141,10 +155,7 @@ def get_token(self, *scopes): :raises: :class:`azure.core.exceptions.ClientAuthenticationError` """ if not self._credential: - message = "Missing environment settings. To authenticate with one of the service principal's client secrets, set {}. To authenticate with a certificate, set {}.".format( - ", ".join(EnvironmentVariables.CLIENT_SECRET_VARS), ", ".join(EnvironmentVariables.CERT_VARS) - ) - raise ClientAuthenticationError(message=message) + raise ClientAuthenticationError(message="Incomplete environment configuration.") return self._credential.get_token(*scopes) From 565ba29ba23bd8dbf2290765b51a8527d6ce8492 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Tue, 30 Jul 2019 06:19:10 -0700 Subject: [PATCH 7/8] make MsalCredential an ABC --- .../identity/_internal/msal_credentials.py | 92 +++++++++++-------- .../azure/identity/credentials.py | 8 +- 2 files changed, 61 insertions(+), 39 deletions(-) diff --git a/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py b/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py index d36d89be8b25..83906bf71c2e 100644 --- a/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py +++ b/sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py @@ -5,35 +5,40 @@ """Credentials wrapping MSAL applications and delegating token acquisition and caching to them. This entails monkeypatching MSAL's OAuth client with an adapter substituting an azure-core pipeline for Requests. """ - +import abc import time +import msal +from azure.core.credentials import AccessToken +from azure.core.exceptions import ClientAuthenticationError + +from .msal_transport_adapter import MsalTransportAdapter + try: - from typing import TYPE_CHECKING -except ImportError: - TYPE_CHECKING = False + ABC = abc.ABC +except AttributeError: # Python 2.7, abc exists, but not ABC + ABC = abc.ABCMeta("ABC", (object,), {"__slots__": ()}) # type: ignore try: from unittest import mock except ImportError: # python < 3.3 import mock # type: ignore +try: + from typing import TYPE_CHECKING +except ImportError: + TYPE_CHECKING = False + if TYPE_CHECKING: # pylint:disable=unused-import - from typing import Any, Mapping, Optional, Union + from typing import Any, Mapping, Optional, Type, Union -from azure.core.credentials import AccessToken -from azure.core.exceptions import ClientAuthenticationError -import msal - -from .msal_transport_adapter import MsalTransportAdapter - -class MsalCredential(object): +class MsalCredential(ABC): """Base class for credentials wrapping MSAL applications""" - def __init__(self, client_id, authority, app_class, client_credential=None, **kwargs): - # type: (str, str, msal.ClientApplication, Optional[Union[str, Mapping[str, str]]], Any) -> None + def __init__(self, client_id, authority, client_credential=None, **kwargs): + # type: (str, str, Optional[Union[str, Mapping[str, str]]], Any) -> None self._authority = authority self._client_credential = client_credential self._client_id = client_id @@ -41,35 +46,35 @@ def __init__(self, client_id, authority, app_class, client_credential=None, **kw self._adapter = kwargs.pop("msal_adapter", None) or MsalTransportAdapter(**kwargs) # postpone creating the wrapped application because its initializer uses the network - self._app_class = app_class self._msal_app = None # type: Optional[msal.ClientApplication] - @property - def _app(self): + @abc.abstractmethod + def get_token(self, *scopes): + # type: (str) -> AccessToken + pass + + @abc.abstractmethod + def _get_app(self): # type: () -> msal.ClientApplication - """The wrapped MSAL application""" + pass - if not self._msal_app: - # MSAL application initializers use msal.authority to send AAD tenant discovery requests - with mock.patch("msal.authority.requests", self._adapter): - app = self._app_class( - client_id=self._client_id, client_credential=self._client_credential, authority=self._authority - ) + def _create_app(self, cls): + # type: (Type[msal.ClientApplication]) -> msal.ClientApplication + """Creates an MSAL application, patching msal.authority to use an azure-core pipeline during tenant discovery""" - # monkeypatch the app to replace requests.Session with MsalTransportAdapter - app.client.session = self._adapter - self._msal_app = app + # MSAL application initializers use msal.authority to send AAD tenant discovery requests + with mock.patch("msal.authority.requests", self._adapter): + app = cls(client_id=self._client_id, client_credential=self._client_credential, authority=self._authority) - return self._msal_app + # monkeypatch the app to replace requests.Session with MsalTransportAdapter + app.client.session = self._adapter + + return app class ConfidentialClientCredential(MsalCredential): """Wraps an MSAL ConfidentialClientApplication with the TokenCredential API""" - def __init__(self, **kwargs): - # type: (Any) -> None - super(ConfidentialClientCredential, self).__init__(app_class=msal.ConfidentialClientApplication, **kwargs) - def get_token(self, *scopes): # type: (str) -> AccessToken @@ -79,7 +84,7 @@ def get_token(self, *scopes): # First try to get a cached access token or if a refresh token is cached, redeem it for an access token. # Failing that, acquire a new token. - app = self._app # type: msal.ConfidentialClientApplication + app = self._get_app() result = app.acquire_token_silent(scopes, account=None) or app.acquire_token_for_client(scopes) if "access_token" not in result: @@ -87,6 +92,12 @@ def get_token(self, *scopes): return AccessToken(result["access_token"], now + int(result["expires_in"])) + def _get_app(self): + # type: () -> msal.ConfidentialClientApplication + if not self._msal_app: + self._msal_app = self._create_app(msal.ConfidentialClientApplication) + return self._msal_app + class PublicClientCredential(MsalCredential): """Wraps an MSAL PublicClientApplication with the TokenCredential API""" @@ -94,7 +105,16 @@ class PublicClientCredential(MsalCredential): def __init__(self, **kwargs): # type: (Any) -> None super(PublicClientCredential, self).__init__( - app_class=msal.PublicClientApplication, - authority="https://login.microsoftonline.com/" + kwargs.pop("tenant", "organizations"), - **kwargs + authority="https://login.microsoftonline.com/" + kwargs.pop("tenant", "organizations"), **kwargs ) + + @abc.abstractmethod + def get_token(self, *scopes): + # type: (str) -> AccessToken + pass + + def _get_app(self): + # type: () -> msal.PublicClientApplication + if not self._msal_app: + self._msal_app = self._create_app(msal.PublicClientApplication) + return self._msal_app diff --git a/sdk/identity/azure-identity/azure/identity/credentials.py b/sdk/identity/azure-identity/azure/identity/credentials.py index 88af66f22b12..2e09a306aa71 100644 --- a/sdk/identity/azure-identity/azure/identity/credentials.py +++ b/sdk/identity/azure-identity/azure/identity/credentials.py @@ -28,6 +28,7 @@ # pylint:disable=unused-import from typing import Any, Dict, Mapping, Optional, Union from azure.core.credentials import TokenCredential + EnvironmentCredentialTypes = Union["CertificateCredential", "ClientSecretCredential", "UsernamePasswordCredential"] # pylint:disable=too-few-public-methods @@ -290,16 +291,17 @@ def get_token(self, *scopes): scopes = list(scopes) # type: ignore now = int(time.time()) - accounts = self._app.get_accounts(username=self._username) + app = self._get_app() + accounts = app.get_accounts(username=self._username) result = None for account in accounts: - result = self._app.acquire_token_silent(scopes, account=account) + result = app.acquire_token_silent(scopes, account=account) if result: break if not result: # cache miss -> request a new token - result = self._app.acquire_token_by_username_password( + result = app.acquire_token_by_username_password( username=self._username, password=self._password, scopes=scopes ) From 02acbaf3e2bbebf5c0e01cfd1b174427b91e5c30 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Tue, 30 Jul 2019 06:19:28 -0700 Subject: [PATCH 8/8] rename module --- .../azure-keyvault-keys/tests/{helpers.py => keys_helpers.py} | 0 sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth.py | 2 +- .../azure-keyvault-keys/tests/test_challenge_auth_async.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename sdk/keyvault/azure-keyvault-keys/tests/{helpers.py => keys_helpers.py} (100%) diff --git a/sdk/keyvault/azure-keyvault-keys/tests/helpers.py b/sdk/keyvault/azure-keyvault-keys/tests/keys_helpers.py similarity index 100% rename from sdk/keyvault/azure-keyvault-keys/tests/helpers.py rename to sdk/keyvault/azure-keyvault-keys/tests/keys_helpers.py diff --git a/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth.py b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth.py index 04ead0b933b1..13054cc0b1d2 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth.py +++ b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth.py @@ -18,7 +18,7 @@ from azure.keyvault.keys._shared import ChallengeAuthPolicy, HttpChallenge, HttpChallengeCache import pytest -from helpers import mock_response, Request, validating_transport +from keys_helpers import mock_response, Request, validating_transport def test_challenge_cache(): diff --git a/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py index c4d72b1c7550..27cda5f91d8e 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py +++ b/sdk/keyvault/azure-keyvault-keys/tests/test_challenge_auth_async.py @@ -19,7 +19,7 @@ from azure.keyvault.keys._shared import AsyncChallengeAuthPolicy, HttpChallenge, HttpChallengeCache import pytest -from helpers import async_validating_transport, mock_response, Request +from keys_helpers import async_validating_transport, mock_response, Request @pytest.mark.asyncio