diff --git a/eng/tox/install_depend_packages.py b/eng/tox/install_depend_packages.py index 764f5af8f5fb..0f4b5be7fd55 100644 --- a/eng/tox/install_depend_packages.py +++ b/eng/tox/install_depend_packages.py @@ -130,7 +130,7 @@ def filter_dev_requirements(setup_py_path, released_packages, temp_dir): filtered_req = [ req for req in requirements - if os.path.basename(req.replace('\n', '')) not in req_to_exclude + if os.path.basename(req.replace('\n', '')) not in req_to_exclude and not any([req.startswith(i) for i in req_to_exclude]) ] logging.info("Filtered dev requirements: %s", filtered_req) diff --git a/sdk/identity/azure-identity/CHANGELOG.md b/sdk/identity/azure-identity/CHANGELOG.md index 64847d7e5a20..0e813ebba247 100644 --- a/sdk/identity/azure-identity/CHANGELOG.md +++ b/sdk/identity/azure-identity/CHANGELOG.md @@ -1,6 +1,6 @@ # Release History -## 1.7.0b5 (Unreleased) +## 1.7.0 (2021-10-12) ### Breaking Changes > These changes do not impact the API of stable versions such as 1.6.0. @@ -10,7 +10,10 @@ The multitenant authentication feature can be totally disabled by setting the environment variable `AZURE_IDENTITY_DISABLE_MULTITENANTAUTH` to `True`. - `azure.identity.RegionalAuthority` is removed. -- `regional_authority` argument is removed for `CertificateCredential` and `ClientSecretCredential` +- `regional_authority` argument is removed for `CertificateCredential` and `ClientSecretCredential`. +- `AzureApplicationCredential` is removed. +- `client_credential` in the ctor of `OnBehalfOfCredential` is removed. Please use `client_secret` or `client_certificate` instead. +- Make `user_assertion` in the ctor of `OnBehalfOfCredential` a keyword only argument. ## 1.7.0b4 (2021-09-09) diff --git a/sdk/identity/azure-identity/azure/identity/__init__.py b/sdk/identity/azure-identity/azure/identity/__init__.py index 0969ad2e0504..bfecb1e0252a 100644 --- a/sdk/identity/azure-identity/azure/identity/__init__.py +++ b/sdk/identity/azure-identity/azure/identity/__init__.py @@ -9,7 +9,6 @@ from ._constants import AzureAuthorityHosts, KnownAuthorities from ._credentials import ( AuthorizationCodeCredential, - AzureApplicationCredential, AzureCliCredential, AzurePowerShellCredential, CertificateCredential, @@ -32,7 +31,6 @@ "AuthenticationRecord", "AuthenticationRequiredError", "AuthorizationCodeCredential", - "AzureApplicationCredential", "AzureAuthorityHosts", "AzureCliCredential", "AzurePowerShellCredential", diff --git a/sdk/identity/azure-identity/azure/identity/_credentials/__init__.py b/sdk/identity/azure-identity/azure/identity/_credentials/__init__.py index 11c7b26db428..9099480d1958 100644 --- a/sdk/identity/azure-identity/azure/identity/_credentials/__init__.py +++ b/sdk/identity/azure-identity/azure/identity/_credentials/__init__.py @@ -2,7 +2,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ -from .application import AzureApplicationCredential from .authorization_code import AuthorizationCodeCredential from .azure_powershell import AzurePowerShellCredential from .browser import InteractiveBrowserCredential @@ -22,7 +21,6 @@ __all__ = [ "AuthorizationCodeCredential", - "AzureApplicationCredential", "AzureCliCredential", "AzurePowerShellCredential", "CertificateCredential", diff --git a/sdk/identity/azure-identity/azure/identity/_credentials/on_behalf_of.py b/sdk/identity/azure-identity/azure/identity/_credentials/on_behalf_of.py index 70f3c407ca16..b9d6a3e0dd9f 100644 --- a/sdk/identity/azure-identity/azure/identity/_credentials/on_behalf_of.py +++ b/sdk/identity/azure-identity/azure/identity/_credentials/on_behalf_of.py @@ -3,7 +3,7 @@ # Licensed under the MIT License. # ------------------------------------ import time -from typing import cast, TYPE_CHECKING +from typing import TYPE_CHECKING import six @@ -33,42 +33,50 @@ class OnBehalfOfCredential(MsalCredential, GetTokenMixin): :param str tenant_id: ID of the service principal's tenant. Also called its "directory" ID. :param str client_id: the service principal's client ID - :param client_credential: a credential to authenticate the service principal, either one of its client secrets (a - string) or the bytes of a certificate in PEM or PKCS12 format including the private key - :type client_credential: str or bytes - :param str user_assertion: the access token the credential will use as the user assertion when requesting - on-behalf-of tokens + :keyword str client_secret: Optional. A client secret to authenticate the service principal. + Either **client_secret** or **client_certificate** must be provided. + :keyword bytes client_certificate: Optional. The bytes of a certificate in PEM or PKCS12 format including + the private key to authenticate the service principal. Either **client_secret** or **client_certificate** must + be provided. + :keyword str user_assertion: Required. The access token the credential will use as the user assertion when + requesting on-behalf-of tokens :keyword str authority: Authority of an Azure Active Directory endpoint, for example "login.microsoftonline.com", the authority for Azure Public Cloud (which is the default). :class:`~azure.identity.AzureAuthorityHosts` defines authorities for other clouds. - :keyword password: a certificate password. Used only when **client_credential** is certificate bytes. If this value + :keyword password: a certificate password. Used only when **client_certificate** is provided. If this value is a unicode string, it will be encoded as UTF-8. If the certificate requires a different encoding, pass appropriately encoded bytes instead. :paramtype password: str or bytes """ - def __init__(self, tenant_id, client_id, client_credential, user_assertion, **kwargs): - # type: (str, str, Union[bytes, str], str, **Any) -> None - credential = cast("Union[Dict, str]", client_credential) - if isinstance(client_credential, six.binary_type): + def __init__(self, tenant_id, client_id, **kwargs): + # type: (str, str, **Any) -> None + self._assertion = kwargs.pop("user_assertion", None) + if not self._assertion: + raise TypeError('"user_assertion" is required.') + client_certificate = kwargs.pop("client_certificate", None) + client_secret = kwargs.pop("client_secret", None) + + if client_certificate: + if client_secret: + raise ValueError('Specifying both "client_certificate" and "client_secret" is not valid.') try: credential = get_client_credential( - certificate_path=None, password=kwargs.pop("password", None), certificate_data=client_credential + certificate_path=None, password=kwargs.pop("password", None), certificate_data=client_certificate ) except ValueError as ex: - # client_credential isn't a valid cert. On 2.7 str == bytes and we ignore this exception because we - # can't tell whether the caller intended to provide a cert. On Python 3 we can say the caller provided - # either an invalid cert, or a client secret as bytes; both are errors. - if six.PY3: - message = ( - '"client_credential" should be either a client secret (a string)' - + " or the bytes of a certificate in PEM or PKCS12 format" - ) - six.raise_from(ValueError(message), ex) + # client_certificate isn't a valid cert. + message = ( + '"client_certificate" is not a valid certificate in PEM or PKCS12 format' + ) + six.raise_from(ValueError(message), ex) + elif client_secret: + credential = client_secret + else: + raise TypeError('Either "client_certificate" or "client_secret" must be provided') super(OnBehalfOfCredential, self).__init__(client_id, credential, tenant_id=tenant_id, **kwargs) - self._assertion = user_assertion self._auth_record = None # type: Optional[AuthenticationRecord] @wrap_exceptions diff --git a/sdk/identity/azure-identity/azure/identity/_version.py b/sdk/identity/azure-identity/azure/identity/_version.py index 9b8985182a86..a2bfa78a7ba5 100644 --- a/sdk/identity/azure-identity/azure/identity/_version.py +++ b/sdk/identity/azure-identity/azure/identity/_version.py @@ -2,4 +2,4 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ -VERSION = "1.7.0b5" +VERSION = "1.7.0" diff --git a/sdk/identity/azure-identity/azure/identity/aio/__init__.py b/sdk/identity/azure-identity/azure/identity/aio/__init__.py index f2de3b977f9b..aca540f8d4d9 100644 --- a/sdk/identity/azure-identity/azure/identity/aio/__init__.py +++ b/sdk/identity/azure-identity/azure/identity/aio/__init__.py @@ -6,7 +6,6 @@ from ._credentials import ( AuthorizationCodeCredential, - AzureApplicationCredential, AzureCliCredential, AzurePowerShellCredential, CertificateCredential, @@ -23,7 +22,6 @@ __all__ = [ "AuthorizationCodeCredential", - "AzureApplicationCredential", "AzureCliCredential", "AzurePowerShellCredential", "CertificateCredential", diff --git a/sdk/identity/azure-identity/azure/identity/aio/_credentials/__init__.py b/sdk/identity/azure-identity/azure/identity/aio/_credentials/__init__.py index 62aad87e0a63..fa8ec74ff44c 100644 --- a/sdk/identity/azure-identity/azure/identity/aio/_credentials/__init__.py +++ b/sdk/identity/azure-identity/azure/identity/aio/_credentials/__init__.py @@ -2,7 +2,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ -from .application import AzureApplicationCredential from .authorization_code import AuthorizationCodeCredential from .azure_powershell import AzurePowerShellCredential from .chained import ChainedTokenCredential @@ -19,7 +18,6 @@ __all__ = [ "AuthorizationCodeCredential", - "AzureApplicationCredential", "AzureCliCredential", "AzurePowerShellCredential", "CertificateCredential", diff --git a/sdk/identity/azure-identity/azure/identity/aio/_credentials/on_behalf_of.py b/sdk/identity/azure-identity/azure/identity/aio/_credentials/on_behalf_of.py index 202de061d3ae..0312c5286b35 100644 --- a/sdk/identity/azure-identity/azure/identity/aio/_credentials/on_behalf_of.py +++ b/sdk/identity/azure-identity/azure/identity/aio/_credentials/on_behalf_of.py @@ -30,16 +30,18 @@ class OnBehalfOfCredential(AsyncContextManager, GetTokenMixin): :param str tenant_id: ID of the service principal's tenant. Also called its "directory" ID. :param str client_id: the service principal's client ID - :param client_credential: a credential to authenticate the service principal, either one of its client secrets (a - string) or the bytes of a certificate in PEM or PKCS12 format including the private key - :paramtype client_credential: str or bytes - :param str user_assertion: the access token the credential will use as the user assertion when requesting - on-behalf-of tokens + :keyword str client_secret: Optional. A client secret to authenticate the service principal. + Either **client_secret** or **client_certificate** must be provided. + :keyword bytes client_certificate: Optional. The bytes of a certificate in PEM or PKCS12 format including + the private key to authenticate the service principal. Either **client_secret** or **client_certificate** must + be provided. + :keyword str user_assertion: Required. The access token the credential will use as the user assertion when + requesting on-behalf-of tokens :keyword str authority: Authority of an Azure Active Directory endpoint, for example "login.microsoftonline.com", the authority for Azure Public Cloud (which is the default). :class:`~azure.identity.AzureAuthorityHosts` defines authorities for other clouds. - :keyword password: a certificate password. Used only when **client_credential** is certificate bytes. If this value + :keyword password: a certificate password. Used only when **client_certificate** is provided. If this value is a unicode string, it will be encoded as UTF-8. If the certificate requires a different encoding, pass appropriately encoded bytes instead. :paramtype password: str or bytes @@ -49,31 +51,37 @@ def __init__( self, tenant_id: str, client_id: str, - client_credential: "Union[bytes, str]", + *, + client_certificate: bytes = None, + client_secret: str = None, user_assertion: str, **kwargs: "Any" ) -> None: super().__init__() validate_tenant_id(tenant_id) - if isinstance(client_credential, bytes): + self._assertion = user_assertion + + if client_certificate: + if client_secret: + raise ValueError('Specifying both "client_certificate" and "client_secret" is not valid.') try: - cert = get_client_credential(None, kwargs.pop("password", None), client_credential) + cert = get_client_credential(None, kwargs.pop("password", None), client_certificate) except ValueError as ex: message = ( - '"client_credential" should be either a client secret (a string)' - + " or the bytes of a certificate in PEM or PKCS12 format" + '"client_certificate" is not a valid certificate in PEM or PKCS12 format' ) raise ValueError(message) from ex self._client_credential = AadClientCertificate( cert["private_key"], password=cert.get("passphrase") ) # type: Union[str, AadClientCertificate] + elif client_secret: + self._client_credential = client_secret else: - self._client_credential = client_credential + raise TypeError('Either "client_certificate" or "client_secret" must be provided') # note AadClient handles "authority" and any pipeline kwargs self._client = AadClient(tenant_id, client_id, **kwargs) - self._assertion = user_assertion async def __aenter__(self): await self._client.__aenter__() diff --git a/sdk/identity/azure-identity/dev_requirements.txt b/sdk/identity/azure-identity/dev_requirements.txt index 414d718794ab..2d1b99a781a5 100644 --- a/sdk/identity/azure-identity/dev_requirements.txt +++ b/sdk/identity/azure-identity/dev_requirements.txt @@ -2,5 +2,6 @@ aiohttp>=3.0; python_version >= '3.5' mock;python_version<"3.3" typing_extensions>=3.7.2 +cryptography<=3.4.8 -e ../../../tools/azure-sdk-tools -e ../../../tools/azure-devtools \ No newline at end of file diff --git a/sdk/identity/azure-identity/tests/test_application_credential.py b/sdk/identity/azure-identity/tests/test_application_credential.py index 0910f8d9f8d1..0d63f47a2134 100644 --- a/sdk/identity/azure-identity/tests/test_application_credential.py +++ b/sdk/identity/azure-identity/tests/test_application_credential.py @@ -5,7 +5,8 @@ import os from azure.core.credentials import AccessToken -from azure.identity import AzureApplicationCredential, CredentialUnavailableError +from azure.identity import CredentialUnavailableError +from azure.identity._credentials.application import AzureApplicationCredential from azure.identity._constants import EnvironmentVariables import pytest from six.moves.urllib_parse import urlparse diff --git a/sdk/identity/azure-identity/tests/test_application_credential_async.py b/sdk/identity/azure-identity/tests/test_application_credential_async.py index 5a4407dcfbb2..1677bafb5a60 100644 --- a/sdk/identity/azure-identity/tests/test_application_credential_async.py +++ b/sdk/identity/azure-identity/tests/test_application_credential_async.py @@ -7,7 +7,7 @@ from azure.core.credentials import AccessToken from azure.identity import CredentialUnavailableError -from azure.identity.aio import AzureApplicationCredential +from azure.identity.aio._credentials.application import AzureApplicationCredential from azure.identity._constants import EnvironmentVariables import pytest from six.moves.urllib_parse import urlparse diff --git a/sdk/identity/azure-identity/tests/test_azure_application.py b/sdk/identity/azure-identity/tests/test_azure_application.py index 9879200eb8d3..0945a1a1823d 100644 --- a/sdk/identity/azure-identity/tests/test_azure_application.py +++ b/sdk/identity/azure-identity/tests/test_azure_application.py @@ -7,7 +7,7 @@ except ImportError: from mock import patch # type: ignore -from azure.identity import AzureApplicationCredential +from azure.identity._credentials.application import AzureApplicationCredential from azure.identity._constants import EnvironmentVariables diff --git a/sdk/identity/azure-identity/tests/test_azure_application_async.py b/sdk/identity/azure-identity/tests/test_azure_application_async.py index c3970134b2fb..80c3586da3c4 100644 --- a/sdk/identity/azure-identity/tests/test_azure_application_async.py +++ b/sdk/identity/azure-identity/tests/test_azure_application_async.py @@ -4,7 +4,7 @@ # ------------------------------------ from unittest.mock import patch -from azure.identity.aio import AzureApplicationCredential +from azure.identity.aio._credentials.application import AzureApplicationCredential from azure.identity._constants import EnvironmentVariables diff --git a/sdk/identity/azure-identity/tests/test_context_manager.py b/sdk/identity/azure-identity/tests/test_context_manager.py index 2292578a398d..2bedbbca973b 100644 --- a/sdk/identity/azure-identity/tests/test_context_manager.py +++ b/sdk/identity/azure-identity/tests/test_context_manager.py @@ -7,8 +7,8 @@ except ImportError: from mock import MagicMock, patch # type: ignore +from azure.identity._credentials.application import AzureApplicationCredential from azure.identity import ( - AzureApplicationCredential, AzureCliCredential, AzurePowerShellCredential, AuthorizationCodeCredential, @@ -62,7 +62,7 @@ def get_credential(self, **kwargs): CredentialFixture(InteractiveBrowserCredential), CredentialFixture( OnBehalfOfCredential, - {kwarg: "..." for kwarg in ("tenant_id", "client_id", "client_credential", "user_assertion")}, + {kwarg: "..." for kwarg in ("tenant_id", "client_id", "client_secret", "user_assertion")}, ), CredentialFixture(UsernamePasswordCredential, {"client_id": "...", "username": "...", "password": "..."}), CredentialFixture(VisualStudioCodeCredential, ctor_patch_factory=lambda: patch(GET_USER_SETTINGS, lambda: {})), diff --git a/sdk/identity/azure-identity/tests/test_obo.py b/sdk/identity/azure-identity/tests/test_obo.py index 413b149be398..70ee13ced413 100644 --- a/sdk/identity/azure-identity/tests/test_obo.py +++ b/sdk/identity/azure-identity/tests/test_obo.py @@ -77,7 +77,7 @@ def test_obo(self): client_id, self.obo_settings["username"], self.obo_settings["password"], tenant_id=tenant_id ) assertion = user_credential.get_token(self.obo_settings["scope"]).token - credential = OnBehalfOfCredential(tenant_id, client_id, self.obo_settings["client_secret"], assertion) + credential = OnBehalfOfCredential(tenant_id, client_id, client_secret=self.obo_settings["client_secret"], user_assertion=assertion) credential.get_token(self.obo_settings["scope"]) def test_obo_cert(self): @@ -88,7 +88,7 @@ def test_obo_cert(self): client_id, self.obo_settings["username"], self.obo_settings["password"], tenant_id=tenant_id ) assertion = user_credential.get_token(self.obo_settings["scope"]).token - credential = OnBehalfOfCredential(tenant_id, client_id, self.obo_settings["cert_bytes"], assertion) + credential = OnBehalfOfCredential(tenant_id, client_id, client_certificate=self.obo_settings["cert_bytes"], user_assertion=assertion) credential.get_token(self.obo_settings["scope"]) @@ -111,7 +111,7 @@ def send(request, **_): transport = Mock(send=Mock(wraps=send)) credential = OnBehalfOfCredential( - first_tenant, "client-id", "secret", "assertion", transport=transport + first_tenant, "client-id", client_secret="secret", user_assertion="assertion", transport=transport ) token = credential.get_token("scope") assert token.token == first_token @@ -140,7 +140,7 @@ def test_authority(authority): return_value=Mock(acquire_token_on_behalf_of=lambda *_, **__: {"access_token": "**", "expires_in": 42}) ) - credential = OnBehalfOfCredential(tenant_id, "client-id", "secret", "assertion", authority=authority) + credential = OnBehalfOfCredential(tenant_id, "client-id", client_secret="secret", user_assertion="assertion", authority=authority) with patch("msal.ConfidentialClientApplication", mock_ctor): # must call get_token because the credential constructs the MSAL application lazily credential.get_token("scope") @@ -152,7 +152,7 @@ def test_authority(authority): # authority can be configured via environment variable with patch.dict("os.environ", {EnvironmentVariables.AZURE_AUTHORITY_HOST: authority}, clear=True): - credential = OnBehalfOfCredential(tenant_id, "client-id", "secret", "assertion") + credential = OnBehalfOfCredential(tenant_id, "client-id", client_secret="secret", user_assertion="assertion") with patch("msal.ConfidentialClientApplication", mock_ctor): credential.get_token("scope") @@ -165,16 +165,16 @@ def test_tenant_id_validation(): """The credential should raise ValueError when given an invalid tenant_id""" valid_ids = {"c878a2ab-8ef4-413b-83a0-199afb84d7fb", "contoso.onmicrosoft.com", "organizations", "common"} for tenant in valid_ids: - OnBehalfOfCredential(tenant, "client-id", "secret", "assertion") + OnBehalfOfCredential(tenant, "client-id", client_secret="secret", user_assertion="assertion") invalid_ids = {"my tenant", "my_tenant", "/", "\\", '"my-tenant"', "'my-tenant'"} for tenant in invalid_ids: with pytest.raises(ValueError): - OnBehalfOfCredential(tenant, "client-id", "secret", "assertion") + OnBehalfOfCredential(tenant, "client-id", client_secret="secret", user_assertion="assertion") def test_no_scopes(): """The credential should raise ValueError when get_token is called with no scopes""" - credential = OnBehalfOfCredential("tenant-id", "client-id", "client-secret", "assertion") + credential = OnBehalfOfCredential("tenant-id", "client-id", client_secret="client-secret", user_assertion="assertion") with pytest.raises(ValueError): credential.get_token() @@ -192,10 +192,20 @@ def send(request, **_): credential = OnBehalfOfCredential( "tenant-id", "client-id", - "client-secret", - "assertion", + client_secret="client-secret", + user_assertion="assertion", policies=[ContentDecodePolicy(), policy], transport=Mock(send=send), ) credential.get_token("scope") assert policy.on_request.called + +def test_no_user_assertion(): + """The credential should raise ValueError when ctoring with no user_assertion""" + with pytest.raises(TypeError): + credential = OnBehalfOfCredential("tenant-id", "client-id", client_secret="client-secret") + +def test_no_client_credential(): + """The credential should raise ValueError when ctoring with no client_secret or client_certificate""" + with pytest.raises(TypeError): + credential = OnBehalfOfCredential("tenant-id", "client-id", user_assertion="assertion") diff --git a/sdk/identity/azure-identity/tests/test_obo_async.py b/sdk/identity/azure-identity/tests/test_obo_async.py index c39957be0afd..442e96335fb0 100644 --- a/sdk/identity/azure-identity/tests/test_obo_async.py +++ b/sdk/identity/azure-identity/tests/test_obo_async.py @@ -29,7 +29,7 @@ async def test_obo(self): client_id, self.obo_settings["username"], self.obo_settings["password"], tenant_id=tenant_id ) assertion = user_credential.get_token(self.obo_settings["scope"]).token - credential = OnBehalfOfCredential(tenant_id, client_id, client_secret, assertion) + credential = OnBehalfOfCredential(tenant_id, client_id, client_secret=client_secret, user_assertion=assertion) await credential.get_token(self.obo_settings["scope"]) @RecordedTestCase.await_prepared_test @@ -41,14 +41,14 @@ async def test_obo_cert(self): client_id, self.obo_settings["username"], self.obo_settings["password"], tenant_id=tenant_id ) assertion = user_credential.get_token(self.obo_settings["scope"]).token - credential = OnBehalfOfCredential(tenant_id, client_id, self.obo_settings["cert_bytes"], assertion) + credential = OnBehalfOfCredential(tenant_id, client_id, client_certificate=self.obo_settings["cert_bytes"], user_assertion=assertion) await credential.get_token(self.obo_settings["scope"]) @pytest.mark.asyncio async def test_close(): transport = AsyncMockTransport() - credential = OnBehalfOfCredential("tenant-id", "client-id", "client-secret", "assertion", transport=transport) + credential = OnBehalfOfCredential("tenant-id", "client-id", client_secret="client-secret", user_assertion="assertion", transport=transport) await credential.close() @@ -58,7 +58,7 @@ async def test_close(): @pytest.mark.asyncio async def test_context_manager(): transport = AsyncMockTransport() - credential = OnBehalfOfCredential("tenant-id", "client-id", "client-secret", "assertion", transport=transport) + credential = OnBehalfOfCredential("tenant-id", "client-id", client_secret="client-secret", user_assertion="assertion", transport=transport) async with credential: assert transport.__aenter__.call_count == 1 @@ -85,7 +85,7 @@ async def send(request, **_): transport = Mock(send=Mock(wraps=send)) credential = OnBehalfOfCredential( - first_tenant, "client-id", "secret", "assertion", transport=transport + first_tenant, "client-id", client_secret="secret", user_assertion="assertion", transport=transport ) token = await credential.get_token("scope") assert token.token == first_token @@ -122,14 +122,14 @@ async def send(request, **_): transport = Mock(send=send) credential = OnBehalfOfCredential( - tenant_id, "client-id", "secret", "assertion", authority=authority, transport=transport + tenant_id, "client-id", client_secret="secret", user_assertion="assertion", authority=authority, transport=transport ) token = await credential.get_token("scope") assert token.token == expected_token # authority can be configured via environment variable with patch.dict("os.environ", {EnvironmentVariables.AZURE_AUTHORITY_HOST: authority}, clear=True): - credential = OnBehalfOfCredential(tenant_id, "client-id", "secret", "assertion", transport=transport) + credential = OnBehalfOfCredential(tenant_id, "client-id", client_secret="secret", user_assertion="assertion", transport=transport) token = await credential.get_token("scope") assert token.token == expected_token @@ -148,8 +148,8 @@ async def send(request, **_): credential = OnBehalfOfCredential( "tenant-id", "client-id", - "client-secret", - "assertion", + client_secret="client-secret", + user_assertion="assertion", policies=[ContentDecodePolicy(), policy], transport=Mock(send=send), ) @@ -160,7 +160,7 @@ async def send(request, **_): def test_invalid_cert(): """The credential should raise ValueError when given invalid cert bytes""" with pytest.raises(ValueError): - OnBehalfOfCredential("tenant-id", "client-id", b"not a cert", "assertion") + OnBehalfOfCredential("tenant-id", "client-id", client_certificate=b"not a cert", user_assertion="assertion") @pytest.mark.asyncio @@ -183,7 +183,7 @@ async def send(request, **_): assert request.body["refresh_token"] == refresh_token return mock_response(json_payload=build_aad_response(access_token=second_token)) - credential = OnBehalfOfCredential("tenant-id", "client-id", "secret", "assertion", transport=Mock(send=send)) + credential = OnBehalfOfCredential("tenant-id", "client-id", client_secret="secret", user_assertion="assertion", transport=Mock(send=send)) token = await credential.get_token("scope") assert token.token == first_token @@ -197,16 +197,28 @@ def test_tenant_id_validation(): """The credential should raise ValueError when given an invalid tenant_id""" valid_ids = {"c878a2ab-8ef4-413b-83a0-199afb84d7fb", "contoso.onmicrosoft.com", "organizations", "common"} for tenant in valid_ids: - OnBehalfOfCredential(tenant, "client-id", "secret", "assertion") + OnBehalfOfCredential(tenant, "client-id", client_secret="secret", user_assertion="assertion") invalid_ids = {"my tenant", "my_tenant", "/", "\\", '"my-tenant"', "'my-tenant'"} for tenant in invalid_ids: with pytest.raises(ValueError): - OnBehalfOfCredential(tenant, "client-id", "secret", "assertion") + OnBehalfOfCredential(tenant, "client-id", client_secret="secret", user_assertion="assertion") @pytest.mark.asyncio async def test_no_scopes(): """The credential should raise ValueError when get_token is called with no scopes""" - credential = OnBehalfOfCredential("tenant-id", "client-id", "client-secret", "assertion") + credential = OnBehalfOfCredential("tenant-id", "client-id", client_secret="client-secret", user_assertion="assertion") with pytest.raises(ValueError): await credential.get_token() + +@pytest.mark.asyncio +async def test_no_user_assertion(): + """The credential should raise ValueError when ctoring with no user_assertion""" + with pytest.raises(TypeError): + credential = OnBehalfOfCredential("tenant-id", "client-id", client_secret="client-secret") + +@pytest.mark.asyncio +async def test_no_client_credential(): + """The credential should raise ValueError when ctoring with no client_secret or client_certificate""" + with pytest.raises(TypeError): + credential = OnBehalfOfCredential("tenant-id", "client-id", user_assertion="assertion")