2
2
# Copyright (c) Microsoft Corporation.
3
3
# Licensed under the MIT License.
4
4
# ------------------------------------
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
12
6
7
+ from .silent import SilentAuthenticationCredential
13
8
from .. import CredentialUnavailableError
14
9
from .._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
18
12
from .._internal .shared_token_cache import NO_TOKEN , SharedTokenCacheBase
19
13
20
- try :
21
- from typing import TYPE_CHECKING
22
- except ImportError :
23
- TYPE_CHECKING = False
24
-
25
14
if TYPE_CHECKING :
26
15
# 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
29
18
from .._internal import AadClientBase
30
19
31
20
32
- class SharedTokenCacheCredential (SharedTokenCacheBase ):
21
+ class SharedTokenCacheCredential (object ):
33
22
"""Authenticates using tokens in the local cache shared between Microsoft applications.
34
23
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.
38
26
39
27
:keyword str authority: Authority of an Azure Active Directory endpoint, for example 'login.microsoftonline.com',
40
28
the authority for Azure Public Cloud (which is the default). :class:`~azure.identity.AzureAuthorityHosts`
@@ -55,21 +43,13 @@ class SharedTokenCacheCredential(SharedTokenCacheBase):
55
43
def __init__ (self , username = None , ** kwargs ):
56
44
# type: (Optional[str], **Any) -> None
57
45
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
68
48
else :
69
- super ( SharedTokenCacheCredential , self ). __init__ (username = username , ** kwargs )
49
+ self . _credential = _SharedTokenCacheCredential (username = username , ** kwargs )
70
50
71
51
@log_get_token ("SharedTokenCacheCredential" )
72
- def get_token (self , * scopes , ** kwargs ): # pylint:disable=unused-argument
52
+ def get_token (self , * scopes , ** kwargs ):
73
53
# type (*str, **Any) -> AccessToken
74
54
"""Get an access token for `scopes` from the shared cache.
75
55
@@ -78,14 +58,34 @@ def get_token(self, *scopes, **kwargs): # pylint:disable=unused-argument
78
58
This method is called automatically by Azure SDK clients.
79
59
80
60
:param str scopes: desired scopes for the access token. This method requires at least one scope.
61
+
81
62
: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
+
83
65
:rtype: :class:`azure.core.credentials.AccessToken`
66
+
84
67
:raises ~azure.identity.CredentialUnavailableError: the cache is unavailable or contains insufficient user
85
68
information
86
69
:raises ~azure.core.exceptions.ClientAuthenticationError: authentication failed. The error's ``message``
87
- attribute gives a reason.
70
+ attribute gives a reason.
88
71
"""
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
89
89
if not scopes :
90
90
raise ValueError ("'get_token' requires at least one scope" )
91
91
@@ -95,9 +95,6 @@ def get_token(self, *scopes, **kwargs): # pylint:disable=unused-argument
95
95
if not self ._cache :
96
96
raise CredentialUnavailableError (message = "Shared token cache unavailable" )
97
97
98
- if self ._auth_record :
99
- return self ._acquire_token_silent (* scopes , ** kwargs )
100
-
101
98
account = self ._get_account (self ._username , self ._tenant_id )
102
99
103
100
token = self ._get_cached_access_token (scopes , account )
@@ -114,67 +111,3 @@ def get_token(self, *scopes, **kwargs): # pylint:disable=unused-argument
114
111
def _get_auth_client (self , ** kwargs ):
115
112
# type: (**Any) -> AadClientBase
116
113
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