# ------------------------------------
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# ------------------------------------
import time
from typing import TYPE_CHECKING
from azure.core.credentials import AccessToken
from azure.core.exceptions import ClientAuthenticationError
from .._internal import wrap_exceptions, PublicClientCredential
if TYPE_CHECKING:
from typing import Any
[docs]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 consent from the user or
a directory admin.
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
: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.KnownAuthorities`
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.
"""
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
[docs] @wrap_exceptions
def get_token(self, *scopes, **kwargs): # pylint:disable=unused-argument
# type: (*str, **Any) -> AccessToken
"""Request an access token for `scopes`.
.. note:: This method is called by Azure SDK clients. It isn't intended for use in application code.
:param str scopes: desired scopes for the access token. This method requires at least one scope.
:rtype: :class:`azure.core.credentials.AccessToken`
:raises ~azure.core.exceptions.ClientAuthenticationError: authentication failed. The error's ``message``
attribute gives a reason. Any error response from Azure Active Directory is available as the error's
``response`` attribute.
"""
if not scopes:
raise ValueError("'get_token' requires at least one scope")
# MSAL requires scopes be a list
scopes = list(scopes) # type: ignore
now = int(time.time())
app = self._get_app()
accounts = app.get_accounts(username=self._username)
result = None
for account in accounts:
result = app.acquire_token_silent(scopes, account=account)
if result:
break
if not result:
# cache miss -> request a new token
with self._adapter:
result = 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"]))