22# Copyright (c) Microsoft Corporation.
33# Licensed under the MIT License.
44# ------------------------------------
5- import os
6- import time
7-
8- from msal .application import PublicClientApplication
9-
10- from azure .core .credentials import AccessToken
11- from azure .core .exceptions import ClientAuthenticationError
5+ from typing import TYPE_CHECKING
126
7+ from .silent import SilentAuthenticationCredential
138from .. import CredentialUnavailableError
149from .._constants import DEVELOPER_SIGN_ON_CLIENT_ID
15- from .._internal import AadClient , resolve_tenant , validate_tenant_id
16- from .._internal .decorators import log_get_token , wrap_exceptions
17- from .._internal .msal_client import MsalClient
10+ from .._internal import AadClient
11+ from .._internal .decorators import log_get_token
1812from .._internal .shared_token_cache import NO_TOKEN , SharedTokenCacheBase
1913
20- try :
21- from typing import TYPE_CHECKING
22- except ImportError :
23- TYPE_CHECKING = False
24-
2514if TYPE_CHECKING :
2615 # pylint:disable=unused-import,ungrouped-imports
27- from typing import Any , Dict , Optional
28- from .. import AuthenticationRecord
16+ from typing import Any , Optional
17+ from azure . core . credentials import TokenCredential
2918 from .._internal import AadClientBase
3019
3120
32- class SharedTokenCacheCredential (SharedTokenCacheBase ):
21+ class SharedTokenCacheCredential (object ):
3322 """Authenticates using tokens in the local cache shared between Microsoft applications.
3423
35- :param str username:
36- Username (typically an email address) of the user to authenticate as. This is used when the local cache
37- contains tokens for multiple identities.
24+ :param str username: Username (typically an email address) of the user to authenticate as. This is used when the
25+ local cache contains tokens for multiple identities.
3826
3927 :keyword str authority: Authority of an Azure Active Directory endpoint, for example 'login.microsoftonline.com',
4028 the authority for Azure Public Cloud (which is the default). :class:`~azure.identity.AzureAuthorityHosts`
@@ -55,21 +43,13 @@ class SharedTokenCacheCredential(SharedTokenCacheBase):
5543 def __init__ (self , username = None , ** kwargs ):
5644 # type: (Optional[str], **Any) -> None
5745
58- self ._auth_record = kwargs .pop ("authentication_record" , None ) # type: Optional[AuthenticationRecord]
59- if self ._auth_record :
60- # authenticate in the tenant that produced the record unless "tenant_id" specifies another
61- self ._tenant_id = kwargs .pop ("tenant_id" , None ) or self ._auth_record .tenant_id
62- validate_tenant_id (self ._tenant_id )
63- self ._allow_multitenant = kwargs .pop ("allow_multitenant_authentication" , False )
64- self ._cache = kwargs .pop ("_cache" , None )
65- self ._client_applications = {} # type: Dict[str, PublicClientApplication]
66- self ._msal_client = MsalClient (** kwargs )
67- self ._initialized = False
46+ if "authentication_record" in kwargs :
47+ self ._credential = SilentAuthenticationCredential (** kwargs ) # type: TokenCredential
6848 else :
69- super ( SharedTokenCacheCredential , self ). __init__ (username = username , ** kwargs )
49+ self . _credential = _SharedTokenCacheCredential (username = username , ** kwargs )
7050
7151 @log_get_token ("SharedTokenCacheCredential" )
72- def get_token (self , * scopes , ** kwargs ): # pylint:disable=unused-argument
52+ def get_token (self , * scopes , ** kwargs ):
7353 # type (*str, **Any) -> AccessToken
7454 """Get an access token for `scopes` from the shared cache.
7555
@@ -78,14 +58,34 @@ def get_token(self, *scopes, **kwargs): # pylint:disable=unused-argument
7858 This method is called automatically by Azure SDK clients.
7959
8060 :param str scopes: desired scopes for the access token. This method requires at least one scope.
61+
8162 :keyword str claims: additional claims required in the token, such as those returned in a resource provider's
82- claims challenge following an authorization failure
63+ claims challenge following an authorization failure
64+
8365 :rtype: :class:`azure.core.credentials.AccessToken`
66+
8467 :raises ~azure.identity.CredentialUnavailableError: the cache is unavailable or contains insufficient user
8568 information
8669 :raises ~azure.core.exceptions.ClientAuthenticationError: authentication failed. The error's ``message``
87- attribute gives a reason.
70+ attribute gives a reason.
8871 """
72+ return self ._credential .get_token (* scopes , ** kwargs )
73+
74+ @staticmethod
75+ def supported ():
76+ # type: () -> bool
77+ """Whether the shared token cache is supported on the current platform.
78+
79+ :rtype: bool
80+ """
81+ return SharedTokenCacheBase .supported ()
82+
83+
84+ class _SharedTokenCacheCredential (SharedTokenCacheBase ):
85+ """The original SharedTokenCacheCredential, which doesn't use msal.ClientApplication"""
86+
87+ def get_token (self , * scopes , ** kwargs ):
88+ # type (*str, **Any) -> AccessToken
8989 if not scopes :
9090 raise ValueError ("'get_token' requires at least one scope" )
9191
@@ -95,9 +95,6 @@ def get_token(self, *scopes, **kwargs): # pylint:disable=unused-argument
9595 if not self ._cache :
9696 raise CredentialUnavailableError (message = "Shared token cache unavailable" )
9797
98- if self ._auth_record :
99- return self ._acquire_token_silent (* scopes , ** kwargs )
100-
10198 account = self ._get_account (self ._username , self ._tenant_id )
10299
103100 token = self ._get_cached_access_token (scopes , account )
@@ -114,67 +111,3 @@ def get_token(self, *scopes, **kwargs): # pylint:disable=unused-argument
114111 def _get_auth_client (self , ** kwargs ):
115112 # type: (**Any) -> AadClientBase
116113 return AadClient (client_id = DEVELOPER_SIGN_ON_CLIENT_ID , ** kwargs )
117-
118- def _initialize (self ):
119- if self ._initialized :
120- return
121-
122- if not self ._auth_record :
123- super (SharedTokenCacheCredential , self )._initialize ()
124- return
125-
126- self ._load_cache ()
127- self ._initialized = True
128-
129- def _get_client_application (self , ** kwargs ):
130- tenant_id = resolve_tenant (self ._tenant_id , self ._allow_multitenant , ** kwargs )
131- if tenant_id not in self ._client_applications :
132- # CP1 = can handle claims challenges (CAE)
133- capabilities = None if "AZURE_IDENTITY_DISABLE_CP1" in os .environ else ["CP1" ]
134- self ._client_applications [tenant_id ] = PublicClientApplication (
135- client_id = self ._auth_record .client_id ,
136- authority = "https://{}/{}" .format (self ._auth_record .authority , tenant_id ),
137- token_cache = self ._cache ,
138- http_client = self ._msal_client ,
139- client_capabilities = capabilities
140- )
141- return self ._client_applications [tenant_id ]
142-
143- @wrap_exceptions
144- def _acquire_token_silent (self , * scopes , ** kwargs ):
145- # type: (*str, **Any) -> AccessToken
146- """Silently acquire a token from MSAL. Requires an AuthenticationRecord."""
147-
148- # this won't be None when this method is called by get_token but we check anyway to satisfy mypy
149- if self ._auth_record is None :
150- raise CredentialUnavailableError ("Initialization failed" )
151-
152- result = None
153-
154- client_application = self ._get_client_application (** kwargs )
155- accounts_for_user = client_application .get_accounts (username = self ._auth_record .username )
156- if not accounts_for_user :
157- raise CredentialUnavailableError ("The cache contains no account matching the given AuthenticationRecord." )
158-
159- for account in accounts_for_user :
160- if account .get ("home_account_id" ) != self ._auth_record .home_account_id :
161- continue
162-
163- now = int (time .time ())
164- result = client_application .acquire_token_silent_with_error (
165- list (scopes ), account = account , claims_challenge = kwargs .get ("claims" )
166- )
167- if result and "access_token" in result and "expires_in" in result :
168- return AccessToken (result ["access_token" ], now + int (result ["expires_in" ]))
169-
170- # if we get this far, the cache contained a matching account but MSAL failed to authenticate it silently
171- if result :
172- # cache contains a matching refresh token but STS returned an error response when MSAL tried to use it
173- message = "Token acquisition failed"
174- details = result .get ("error_description" ) or result .get ("error" )
175- if details :
176- message += ": {}" .format (details )
177- raise ClientAuthenticationError (message = message )
178-
179- # cache doesn't contain a matching refresh (or access) token
180- raise CredentialUnavailableError (message = NO_TOKEN .format (self ._auth_record .username ))
0 commit comments