Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore user authentication API #13070

Merged
merged 2 commits into from
Aug 13, 2020
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
7 changes: 4 additions & 3 deletions sdk/identity/azure-identity/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Release History

## 1.4.1 (Unreleased)


## 1.5.0b1 (Unreleased)
### Added
- Application authentication APIs from 1.4.0b7

## 1.4.0 (2020-08-10)
### Added
- `DefaultAzureCredential` uses the value of environment variable
Expand Down
1 change: 1 addition & 0 deletions sdk/identity/azure-identity/MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
recursive-include samples *.py
recursive-include tests *.py
include *.md
include azure/__init__.py
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 @@ -36,6 +36,13 @@ class InteractiveBrowserCredential(InteractiveCredential):
authenticate work or school accounts.
:keyword str client_id: Client ID of the Azure Active Directory application users will sign in to. If
unspecified, the Azure CLI's ID will be used.
: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 @@ -25,6 +25,10 @@ class CertificateCredential(CertificateCredentialBase):
:keyword password: The certificate's password. If 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
: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.
"""

@log_get_token("CertificateCredential")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ class ClientSecretCredential(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.
"""

@log_get_token("ClientSecretCredential")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,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, **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 @@ -76,7 +80,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 @@ -31,6 +31,10 @@ 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.
"""

@log_get_token("SharedTokenCacheCredential")
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 @@ -44,9 +44,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)
enable_persistent_cache = kwargs.pop("enable_persistent_cache", False)
if enable_persistent_cache:
allow_unencrypted = kwargs.pop("_allow_unencrypted_cache", False)
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 @@ -31,9 +31,9 @@ def __init__(self, tenant_id, client_id, client_secret, **kwargs):
"tenant_id should be an Azure Active Directory tenant's id (also called its 'directory id')"
)

enable_persistent_cache = kwargs.pop("_enable_persistent_cache", False)
enable_persistent_cache = kwargs.pop("enable_persistent_cache", False)
if enable_persistent_cache:
allow_unencrypted = kwargs.pop("_allow_unencrypted_cache", False)
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 @@ -71,8 +71,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 @@ -97,6 +97,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 @@ -138,7 +140,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 @@ -30,7 +30,7 @@
# pylint:disable=unused-import,ungrouped-imports
from typing import Any, Iterable, List, Mapping, Optional
from .._internal import AadClientBase
from azure.identity._auth_record import AuthenticationRecord
from azure.identity import AuthenticationRecord

CacheItem = Mapping[str, str]

Expand Down Expand Up @@ -90,7 +90,7 @@ class SharedTokenCacheBase(ABC):
def __init__(self, username=None, **kwargs): # pylint:disable=unused-argument
# 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
authenticating_tenant = kwargs.pop("tenant_id", None) or self._auth_record.tenant_id
Expand Down Expand Up @@ -118,7 +118,7 @@ def _initialize(self):
return

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
2 changes: 1 addition & 1 deletion sdk/identity/azure-identity/azure/identity/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# ------------------------------------
VERSION = "1.4.1"
VERSION = "1.5.0b1"
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ class ClientSecretCredential(AsyncCredentialBase, 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(AsyncCredentialBase):
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,10 @@ class SharedTokenCacheCredential(SharedTokenCacheBase, AsyncCredentialBase):
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.
"""

async def __aenter__(self):
Expand Down
37 changes: 37 additions & 0 deletions sdk/identity/azure-identity/samples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
page_type: sample
languages:
- python
products:
- azure
- azure-identity
urlFragment: identity-samples
---

# Azure Identity Library Python Samples

## Prerequisites

You must have an [Azure subscription](https://azure.microsoft.com/free) and an
[Azure Key Vault](https://azure.microsoft.com/en-us/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/en-us/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 azure-keyvault-secrets
```

## Contents
| File | Description |
|-------------|-------------|
| control_interactive_prompts.py | demonstrates controlling when interactive credentials prompt for user interaction |
| 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