Skip to content
Merged
19 changes: 17 additions & 2 deletions sdk/identity/azure-identity/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@
### Breaking Changes
- Renamed optional `CertificateCredential` keyword argument `send_certificate`
to `send_certificate_chain`
- Removed application authentication APIs added in prior betas. These will be
reintroduced in 1.6.0b1. Passing the keyword arguments below
generally won't cause a runtime error, but the arguments have no effect.
- Removed `authenticate` method from `DeviceCodeCredential`,
`InteractiveBrowserCredential`, and `UsernamePasswordCredential`
- Removed `allow_unencrypted_cache` and `enable_persistent_cache` keyword
arguments from `CertificateCredential`, `ClientSecretCredential`,
`DeviceCodeCredential`, `InteractiveBrowserCredential`, and
`UsernamePasswordCredential`
- Removed `disable_automatic_authentication` keyword argument from
`DeviceCodeCredential` and `InteractiveBrowserCredential`
- Removed `allow_unencrypted_cache` keyword argument from
`SharedTokenCacheCredential`
- Removed classes `AuthenticationRecord` and `AuthenticationRequiredError`
- Removed `identity_config` keyword argument from `ManagedIdentityCredential`

### Changed
- `DeviceCodeCredential` parameter `client_id` is now optional. When not
Expand Down Expand Up @@ -47,7 +62,7 @@
([#11346](https://github.com/Azure/azure-sdk-for-python/issues/11346))
- `DefaultAzureCredential` allows specifying the client ID of a user-assigned
managed identity via keyword argument `managed_identity_client_id`
([#12991](https://github.com/Azure/azure-sdk-for-python/issues/12991))
([#12991](https://github.com/Azure/azure-sdk-for-python/issues/12991))
- `CertificateCredential` supports Subject Name/Issuer authentication when
created with `send_certificate=True`. The async `CertificateCredential`
(`azure.identity.aio.CertificateCredential`) will support this in a
Expand All @@ -63,7 +78,7 @@
([#12696](https://github.com/Azure/azure-sdk-for-python/issues/12696))
- `InteractiveBrowserCredential` keyword argument `redirect_uri` enables
authentication with a user-specified application having a custom redirect URI
([#13344](https://github.com/Azure/azure-sdk-for-python/issues/13344))
([#13344](https://github.com/Azure/azure-sdk-for-python/issues/13344))

### Breaking changes
- Removed `authentication_record` keyword argument from the async
Expand Down
5 changes: 1 addition & 4 deletions sdk/identity/azure-identity/azure/identity/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
# ------------------------------------
"""Credentials for Azure SDK clients."""

from ._auth_record import AuthenticationRecord
from ._exceptions import AuthenticationRequiredError, CredentialUnavailableError
from ._exceptions import CredentialUnavailableError
from ._constants import AzureAuthorityHosts, KnownAuthorities
from ._credentials import (
AzureCliCredential,
Expand All @@ -25,8 +24,6 @@


__all__ = [
"AuthenticationRecord",
"AuthenticationRequiredError",
"AuthorizationCodeCredential",
"AzureAuthorityHosts",
"AzureCliCredential",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def _request_token(self, *scopes, **kwargs):

def _get_client_args(**kwargs):
# type: (dict) -> Optional[dict]
identity_config = kwargs.pop("identity_config", None) or {}
identity_config = kwargs.pop("_identity_config", None) or {}

url = os.environ.get(EnvironmentVariables.MSI_ENDPOINT)
secret = os.environ.get(EnvironmentVariables.MSI_SECRET)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,6 @@ class InteractiveBrowserCredential(InteractiveCredential):
Active Directory, for example "http://localhost:8400". This is only required when passing a value for
`client_id`, and must match a redirect URI in the application's registration. The credential must be able to
bind a socket to this URI.
:keyword AuthenticationRecord authentication_record: :class:`AuthenticationRecord` returned by :func:`authenticate`
:keyword bool disable_automatic_authentication: if True, :func:`get_token` will raise
:class:`AuthenticationRequiredError` when user interaction is required to acquire a token. Defaults to False.
:keyword bool enable_persistent_cache: if True, the credential will store tokens in a persistent cache shared by
other user credentials. Defaults to False.
:keyword bool allow_unencrypted_cache: if True, the credential will fall back to a plaintext cache on platforms
where encryption is unavailable. Default to False. Has no effect when `enable_persistent_cache` is False.
:keyword int timeout: seconds to wait for the user to complete authentication. Defaults to 300 (5 minutes).
"""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,6 @@ class CertificateCredential(ClientCredentialBase):
:keyword bool send_certificate_chain: if True, the credential will send the public certificate chain in the x5c
header of each token request's JWT. This is required for Subject Name/Issuer (SNI) authentication. Defaults
to False.
:keyword bool enable_persistent_cache: if True, the credential will store tokens in a persistent cache. Defaults to
False.
:keyword bool allow_unencrypted_cache: if True, the credential will fall back to a plaintext cache when encryption
is unavailable. Default to False. Has no effect when `enable_persistent_cache` is False.
"""

def __init__(self, tenant_id, client_id, certificate_path, **kwargs):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,6 @@ class ClientSecretCredential(ClientCredentialBase):
: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 bool enable_persistent_cache: if True, the credential will store tokens in a persistent cache. Defaults to
False.
:keyword bool allow_unencrypted_cache: if True, the credential will fall back to a plaintext cache when encryption
is unavailable. Default to False. Has no effect when `enable_persistent_cache` is False.
"""

def __init__(self, tenant_id, client_id, client_secret, **kwargs):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,6 @@ class DeviceCodeCredential(InteractiveCredential):
- ``expires_on`` (datetime.datetime) the UTC time at which the code will expire
If this argument isn't provided, the credential will print instructions to stdout.
:paramtype prompt_callback: Callable[str, str, ~datetime.datetime]
:keyword AuthenticationRecord authentication_record: :class:`AuthenticationRecord` returned by :func:`authenticate`
:keyword bool disable_automatic_authentication: if True, :func:`get_token` will raise
:class:`AuthenticationRequiredError` when user interaction is required to acquire a token. Defaults to False.
:keyword bool enable_persistent_cache: if True, the credential will store tokens in a persistent cache shared by
other user credentials. Defaults to False.
:keyword bool allow_unencrypted_cache: if True, the credential will fall back to a plaintext cache on platforms
where encryption is unavailable. Default to False. Has no effect when `enable_persistent_cache` is False.
"""

def __init__(self, client_id=DEVELOPER_SIGN_ON_CLIENT_ID, **kwargs):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,6 @@ class ManagedIdentityCredential(object):
the keyword arguments.

:keyword str client_id: a user-assigned identity's client ID. This is supported in all hosting environments.
:keyword identity_config: a mapping ``{parameter_name: value}`` specifying a user-assigned identity by its object
or resource ID, for example ``{"object_id": "..."}``. Check the documentation for your hosting environment to
learn what values it expects.
:paramtype identity_config: Mapping[str, str]
"""

def __init__(self, **kwargs):
Expand Down Expand Up @@ -95,7 +91,7 @@ def get_token(self, *scopes, **kwargs):
class _ManagedIdentityBase(object):
def __init__(self, endpoint, client_cls, config=None, client_id=None, **kwargs):
# type: (str, Type, Optional[Configuration], Optional[str], **Any) -> None
self._identity_config = kwargs.pop("identity_config", None) or {}
self._identity_config = kwargs.pop("_identity_config", None) or {}
if client_id:
if os.environ.get(EnvironmentVariables.MSI_ENDPOINT) and os.environ.get(EnvironmentVariables.MSI_SECRET):
# App Service: version 2017-09-1 accepts client ID as parameter "clientid"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
if TYPE_CHECKING:
# pylint:disable=unused-import,ungrouped-imports
from typing import Any, Optional
from .. import AuthenticationRecord
from .._auth_record import AuthenticationRecord
from .._internal import AadClientBase


Expand All @@ -40,16 +40,12 @@ class SharedTokenCacheCredential(SharedTokenCacheBase):
defines authorities for other clouds.
:keyword str tenant_id: an Azure Active Directory tenant ID. Used to select an account when the cache contains
tokens for multiple identities.
:keyword AuthenticationRecord authentication_record: an authentication record returned by a user credential such as
:class:`DeviceCodeCredential` or :class:`InteractiveBrowserCredential`
:keyword bool allow_unencrypted_cache: if True, the credential will fall back to a plaintext cache when encryption
is unavailable. Defaults to False.
"""

def __init__(self, username=None, **kwargs):
# type: (Optional[str], **Any) -> None

self._auth_record = kwargs.pop("authentication_record", None) # type: Optional[AuthenticationRecord]
self._auth_record = kwargs.pop("_authentication_record", None) # type: Optional[AuthenticationRecord]
if self._auth_record:
# authenticate in the tenant that produced the record unless "tenant_id" specifies another
self._tenant_id = kwargs.pop("tenant_id", None) or self._auth_record.tenant_id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,6 @@ class UsernamePasswordCredential(InteractiveCredential):
defines authorities for other clouds.
:keyword str tenant_id: tenant ID or a domain associated with a tenant. If not provided, defaults to the
'organizations' tenant, which supports only Azure Active Directory work or school accounts.
:keyword bool enable_persistent_cache: if True, the credential will store tokens in a persistent cache shared by
other user credentials. Defaults to False.
:keyword bool allow_unencrypted_cache: if True, the credential will fall back to a plaintext cache on platforms
where encryption is unavailable. Default to False. Has no effect when `enable_persistent_cache` is False.
"""

def __init__(self, client_id, username, password, **kwargs):
Expand All @@ -46,7 +42,7 @@ def __init__(self, client_id, username, password, **kwargs):
# first time it's asked for a token. However, we want to ensure this first authentication is not silent, to
# validate the given password. This class therefore doesn't document the authentication_record argument, and we
# discard it here.
kwargs.pop("authentication_record", None)
kwargs.pop("_authentication_record", None)
super(UsernamePasswordCredential, self).__init__(client_id=client_id, **kwargs)
self._username = username
self._password = password
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ def __init__(self, tenant_id, client_id, certificate_path, **kwargs):

self._certificate = AadClientCertificate(pem_bytes, password=password)

enable_persistent_cache = kwargs.pop("enable_persistent_cache", False)
if enable_persistent_cache:
allow_unencrypted = kwargs.pop("allow_unencrypted_cache", False)
_enable_persistent_cache = kwargs.pop("_enable_persistent_cache", False)
if _enable_persistent_cache:
allow_unencrypted = kwargs.pop("_allow_unencrypted_cache", False)
cache = load_service_principal_cache(allow_unencrypted)
else:
cache = TokenCache()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ class ClientCredentialBase(MsalCredential, GetTokenMixin):
"""Base class for credentials authenticating a service principal with a certificate or secret"""

def __init__(self, **kwargs):
if kwargs.pop("enable_persistent_cache", False):
allow_unencrypted = kwargs.pop("allow_unencrypted_cache", False)
if kwargs.pop("_enable_persistent_cache", False):
allow_unencrypted = kwargs.pop("_allow_unencrypted_cache", False)
cache = load_service_principal_cache(allow_unencrypted)
else:
cache = msal.TokenCache()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ def __init__(self, tenant_id, client_id, client_secret, **kwargs):
)
validate_tenant_id(tenant_id)

enable_persistent_cache = kwargs.pop("enable_persistent_cache", False)
if enable_persistent_cache:
allow_unencrypted = kwargs.pop("allow_unencrypted_cache", False)
_enable_persistent_cache = kwargs.pop("_enable_persistent_cache", False)
if _enable_persistent_cache:
allow_unencrypted = kwargs.pop("_allow_unencrypted_cache", False)
cache = load_service_principal_cache(allow_unencrypted)
else:
cache = TokenCache()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ def _build_auth_record(response):

class InteractiveCredential(MsalCredential):
def __init__(self, **kwargs):
self._disable_automatic_authentication = kwargs.pop("disable_automatic_authentication", False)
self._auth_record = kwargs.pop("authentication_record", None) # type: Optional[AuthenticationRecord]
self._disable_automatic_authentication = kwargs.pop("_disable_automatic_authentication", False)
self._auth_record = kwargs.pop("_authentication_record", None) # type: Optional[AuthenticationRecord]
if self._auth_record:
kwargs.pop("client_id", None) # authentication_record overrides client_id argument
tenant_id = kwargs.pop("tenant_id", None) or self._auth_record.tenant_id
Expand All @@ -108,8 +108,6 @@ def get_token(self, *scopes, **kwargs):
required data, state, or platform support
:raises ~azure.core.exceptions.ClientAuthenticationError: authentication failed. The error's ``message``
attribute gives a reason.
:raises AuthenticationRequiredError: user interaction is necessary to acquire a token, and the credential is
configured not to begin this automatically. Call :func:`authenticate` to begin interactive authentication.
"""
if not scopes:
message = "'get_token' requires at least one scope"
Expand Down Expand Up @@ -151,7 +149,7 @@ def get_token(self, *scopes, **kwargs):
_LOGGER.info("%s.get_token succeeded", self.__class__.__name__)
return AccessToken(result["access_token"], now + int(result["expires_in"]))

def authenticate(self, **kwargs):
def _authenticate(self, **kwargs):
# type: (**Any) -> AuthenticationRecord
"""Interactively authenticate a user.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ def __init__(self, client_id, client_credential=None, **kwargs):

self._cache = kwargs.pop("_cache", None) # internal, for use in tests
if not self._cache:
if kwargs.pop("enable_persistent_cache", False):
allow_unencrypted = kwargs.pop("allow_unencrypted_cache", False)
if kwargs.pop("_enable_persistent_cache", False):
allow_unencrypted = kwargs.pop("_allow_unencrypted_cache", False)
self._cache = load_user_cache(allow_unencrypted)
else:
self._cache = msal.TokenCache()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def _initialize(self):

def _load_cache(self):
if not self._cache and self.supported():
allow_unencrypted = self._client_kwargs.get("allow_unencrypted_cache", False)
allow_unencrypted = self._client_kwargs.get("_allow_unencrypted_cache", True)
try:
self._cache = load_user_cache(allow_unencrypted)
except Exception: # pylint:disable=broad-except
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ class ClientSecretCredential(AsyncContextManager, ClientSecretCredentialBase):
: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 bool enable_persistent_cache: if True, the credential will store tokens in a persistent cache. Defaults to
False.
:keyword bool allow_unencrypted_cache: if True, the credential will fall back to a plaintext cache when encryption
is unavailable. Default to False. Has no effect when `enable_persistent_cache` is False.
"""

async def __aenter__(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@ class ManagedIdentityCredential(AsyncContextManager):
the keyword arguments.

:keyword str client_id: a user-assigned identity's client ID. This is supported in all hosting environments.
:keyword identity_config: a mapping ``{parameter_name: value}`` specifying a user-assigned identity by its object
or resource ID, for example ``{"object_id": "..."}``. Check the documentation for your hosting environment to
learn what values it expects.
:paramtype identity_config: Mapping[str, str]
"""

def __init__(self, **kwargs: "Any") -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ class SharedTokenCacheCredential(SharedTokenCacheBase, AsyncContextManager):
defines authorities for other clouds.
:keyword str tenant_id: an Azure Active Directory tenant ID. Used to select an account when the cache contains
tokens for multiple identities.
:keyword bool allow_unencrypted_cache: if True, the credential will fall back to a plaintext cache when encryption
is unavailable. Defaults to False.
"""

async def __aenter__(self):
Expand Down
14 changes: 3 additions & 11 deletions sdk/identity/azure-identity/samples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,19 @@ urlFragment: identity-samples

## Prerequisites

You must have an [Azure subscription](https://azure.microsoft.com/free) and an
[Azure Key Vault](https://azure.microsoft.com/services/key-vault/) to run
these samples. You can create a Key Vault in the
[Azure Portal](https://portal.azure.com/#create/Microsoft.KeyVault) or with the
[Azure CLI](https://docs.microsoft.com/azure/key-vault/secrets/quick-create-cli).

Azure Key Vault is used only to demonstrate authentication. Azure Identity has
the same API for all compatible client libraries.
You must have an [Azure subscription](https://azure.microsoft.com/free) to run
these samples.

## Setup

To run these samples, first install the Azure Identity and Key Vault Secrets
client libraries:

```commandline
pip install azure-identity azure-keyvault-secrets
pip install azure-identity
```

## Contents
| File | Description |
|-------------|-------------|
| control_interactive_prompts.py | demonstrates controlling when interactive credentials prompt for user interaction |
| custom_credentials.py | demonstrates custom credential implementation |
| user_authentication.py | demonstrates user authentication API for applications |
Loading