Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion sdk/identity/azure-identity/azure/identity/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
# ------------------------------------
"""Credentials for Azure SDK clients."""

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


__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,6 +40,13 @@ 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,6 +33,10 @@ 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,6 +21,10 @@ 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,6 +48,13 @@ 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,6 +44,10 @@ 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 @@ -96,7 +100,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 .._auth_record import AuthenticationRecord
from .. import AuthenticationRecord
from .._internal import AadClientBase


Expand All @@ -40,12 +40,16 @@ 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,6 +33,10 @@ 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 @@ -42,7 +46,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,6 +108,8 @@ 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 @@ -149,7 +151,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", True)
allow_unencrypted = self._client_kwargs.get("allow_unencrypted_cache", False)
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,6 +23,10 @@ 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,6 +32,10 @@ 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,6 +29,8 @@ 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: 11 additions & 3 deletions sdk/identity/azure-identity/samples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,27 @@ urlFragment: identity-samples

## Prerequisites

You must have an [Azure subscription](https://azure.microsoft.com/free) to run
these samples.
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.

## Setup

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

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

## 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 |
38 changes: 38 additions & 0 deletions sdk/identity/azure-identity/samples/control_interactive_prompts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# ------------------------------------
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# ------------------------------------
"""Demonstrates controlling the timing of interactive authentication using InteractiveBrowserCredential.

DeviceCodeCredential supports the same API.
"""

import os
import sys
from azure.identity import AuthenticationRequiredError, InteractiveBrowserCredential
from azure.keyvault.secrets import SecretClient


# This sample uses Key Vault only for demonstration. Any client accepting azure-identity credentials will work the same.
VAULT_URL = os.environ.get("VAULT_URL")
if not VAULT_URL:
print("This sample expects environment variable 'VAULT_URL' to be set with the URL of a Key Vault.")
sys.exit(1)


# If it's important for your application to prompt for authentication only at certain times,
# create the credential with disable_automatic_authentication=True. This configures the credential to raise
# when interactive authentication is required, instead of immediately beginning that authentication.
credential = InteractiveBrowserCredential(disable_automatic_authentication=True)
client = SecretClient(VAULT_URL, credential)

try:
secret_names = [s.name for s in client.list_properties_of_secrets()]
except AuthenticationRequiredError as ex:
# Interactive authentication is necessary to authorize the client's request. The exception carries the
# requested authentication scopes. If you pass these to 'authenticate', it will cache an access token
# for those scopes.
credential.authenticate(scopes=ex.scopes)

# the client operation should now succeed
secret_names = [s.name for s in client.list_properties_of_secrets()]
Loading