Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/adal/authentication_context.py: 30%
90 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:05 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:05 +0000
1#------------------------------------------------------------------------------
2#
3# Copyright (c) Microsoft Corporation.
4# All rights reserved.
5#
6# This code is licensed under the MIT License.
7#
8# Permission is hereby granted, free of charge, to any person obtaining a copy
9# of this software and associated documentation files(the "Software"), to deal
10# in the Software without restriction, including without limitation the rights
11# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
12# copies of the Software, and to permit persons to whom the Software is
13# furnished to do so, subject to the following conditions :
14#
15# The above copyright notice and this permission notice shall be included in
16# all copies or substantial portions of the Software.
17#
18# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
21# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24# THE SOFTWARE.
25#
26#------------------------------------------------------------------------------
27import os
28import threading
29import warnings
31from .authority import Authority
32from . import argument
33from .code_request import CodeRequest
34from .token_request import TokenRequest
35from .token_cache import TokenCache
36from . import log
37from .constants import OAuth2DeviceCodeResponseParameters
40#warnings.simplefilter('default', DeprecationWarning) # Make them visible to end users
42GLOBAL_ADAL_OPTIONS = {}
44class AuthenticationContext(object):
45 '''Retrieves authentication tokens from Azure Active Directory.
47 For usages, check out the "sample" folder at:
48 https://github.com/AzureAD/azure-activedirectory-library-for-python
49 '''
51 def __init__(
52 self, authority, validate_authority=None, cache=None,
53 api_version=None, timeout=None, enable_pii=False, verify_ssl=None, proxies=None):
54 '''Creates a new AuthenticationContext object.
56 By default the authority will be checked against a list of known Azure
57 Active Directory authorities. If the authority is not recognized as
58 one of these well known authorities then token acquisition will fail.
59 This behavior can be turned off via the validate_authority parameter
60 below.
62 :param str authority: A URL that identifies a token authority. It should be of the
63 format https://login.microsoftonline.com/your_tenant
64 :param bool validate_authority: (optional) Turns authority validation
65 on or off. This parameter default to true.
66 :param TokenCache cache: (optional) Sets the token cache used by this
67 AuthenticationContext instance. If this parameter is not set, then
68 a default is used. Cache instances is only used by that instance of
69 the AuthenticationContext and are not shared unless it has been
70 manually passed during the construction of other
71 AuthenticationContexts.
72 :param api_version: (optional) Specifies API version using on the wire.
73 Historically it has a hardcoded default value as "1.0".
74 Developers have been encouraged to set it as None explicitly,
75 which means the underlying API version will be automatically chosen.
76 Starting from ADAL Python 1.0, this default value becomes None.
77 :param timeout: (optional) requests timeout. How long to wait for the server to send
78 data before giving up, as a float, or a `(connect timeout,
79 read timeout) <timeouts>` tuple.
80 :param enable_pii: (optional) Unless this is set to True,
81 there will be no Personally Identifiable Information (PII) written in log.
82 :param verify_ssl: (optional) requests verify. Either a boolean, in which case it
83 controls whether we verify the server's TLS certificate, or a string, in which
84 case it must be a path to a CA bundle to use. If this value is not provided, and
85 ADAL_PYTHON_SSL_NO_VERIFY env variable is set, behavior is equivalent to
86 verify_ssl=False.
87 :param proxies: (optional) requests proxies. Dictionary mapping protocol to the URL
88 of the proxy. See http://docs.python-requests.org/en/master/user/advanced/#proxies
89 for details.
90 '''
91 warnings.warn(
92 """ADAL Python library no longer receives any feature update or bugfix.
93Please use the new library, MSAL Python, which is easier to use, and more secure.
95MSAL Python is available here: https://pypi.org/project/msal/
97If you are building your new project,
98start using MSAL Python by choosing one of the samples that suit your need.
99https://msal-python.readthedocs.io/en/latest/#scenarios
101If you are migrating your existing ADAL-powered project into MSAL, please read
102https://learn.microsoft.com/en-us/azure/active-directory/develop/migrate-python-adal-msal
103""", DeprecationWarning)
104 self.authority = Authority(authority, validate_authority is None or validate_authority)
105 self._oauth2client = None
106 self.correlation_id = None
107 env_verify = 'ADAL_PYTHON_SSL_NO_VERIFY' not in os.environ
108 verify = verify_ssl if verify_ssl is not None else env_verify
109 if api_version is not None:
110 warnings.warn(
111 """The default behavior of including api-version=1.0 on the wire
112 is now deprecated.
113 Future version of ADAL will change the default value to None.
115 To ensure a smooth transition, you are recommended to explicitly
116 set it to None in your code now, and test out the new behavior.
118 context = AuthenticationContext(..., api_version=None)
119 """, DeprecationWarning)
120 self._call_context = {
121 'options': GLOBAL_ADAL_OPTIONS,
122 'api_version': api_version,
123 'verify_ssl': verify,
124 'proxies':proxies,
125 'timeout':timeout,
126 "enable_pii": enable_pii,
127 }
128 self._token_requests_with_user_code = {}
129 self.cache = cache or TokenCache()
130 self._lock = threading.RLock()
132 @property
133 def options(self):
134 return self._call_context['options']
136 @options.setter
137 def options(self, val):
138 self._call_context['options'] = val
140 def _acquire_token(self, token_func, correlation_id=None):
141 self._call_context['log_context'] = log.create_log_context(
142 correlation_id or self.correlation_id, self._call_context.get('enable_pii', False))
143 self.authority.validate(self._call_context)
144 return token_func(self)
146 def acquire_token(self, resource, user_id, client_id):
147 '''Gets a token for a given resource via cached tokens.
149 :param str resource: A URI that identifies the resource for which the
150 token is valid.
151 :param str user_id: The username of the user on behalf this application
152 is authenticating.
153 :param str client_id: The OAuth client id of the calling application.
154 :returns: dic with several keys, include "accessToken" and
155 "refreshToken".
156 '''
157 def token_func(self):
158 token_request = TokenRequest(self._call_context, self, client_id, resource)
159 return token_request.get_token_from_cache_with_refresh(user_id)
161 return self._acquire_token(token_func)
163 def acquire_token_with_username_password(self, resource, username, password, client_id):
164 '''Gets a token for a given resource via user credentails.
166 :param str resource: A URI that identifies the resource for which the
167 token is valid.
168 :param str username: The username of the user on behalf this
169 application is authenticating.
170 :param str password: The password of the user named in the username
171 parameter.
172 :param str client_id: The OAuth client id of the calling application.
173 :returns: dict with several keys, include "accessToken" and
174 "refreshToken".
175 '''
176 def token_func(self):
177 token_request = TokenRequest(self._call_context, self, client_id, resource)
178 return token_request.get_token_with_username_password(username, password)
180 return self._acquire_token(token_func)
182 def acquire_token_with_client_credentials(self, resource, client_id, client_secret):
183 '''Gets a token for a given resource via client credentials.
185 :param str resource: A URI that identifies the resource for which the
186 token is valid.
187 :param str client_id: The OAuth client id of the calling application.
188 :param str client_secret: The OAuth client secret of the calling application.
189 :returns: dict with several keys, include "accessToken".
190 '''
191 def token_func(self):
192 token_request = TokenRequest(self._call_context, self, client_id, resource)
193 return token_request.get_token_with_client_credentials(client_secret)
195 return self._acquire_token(token_func)
197 def acquire_token_with_authorization_code(self, authorization_code,
198 redirect_uri, resource,
199 client_id, client_secret=None, code_verifier=None):
200 '''Gets a token for a given resource via authorization code for a
201 server app.
203 :param str authorization_code: An authorization code returned from a
204 client.
205 :param str redirect_uri: the redirect uri that was used in the
206 authorize call.
207 :param str resource: A URI that identifies the resource for which the
208 token is valid.
209 :param str client_id: The OAuth client id of the calling application.
210 :param str client_secret: (only for confidential clients)The OAuth
211 client secret of the calling application. This parameter if not set,
212 defaults to None
213 :param str code_verifier: (optional)The code verifier that was used to
214 obtain authorization code if PKCE was used in the authorization
215 code grant request.(usually used by public clients) This parameter if not set,
216 defaults to None
217 :returns: dict with several keys, include "accessToken" and
218 "refreshToken".
219 '''
220 def token_func(self):
221 token_request = TokenRequest(
222 self._call_context,
223 self,
224 client_id,
225 resource,
226 redirect_uri)
227 return token_request.get_token_with_authorization_code(
228 authorization_code,
229 client_secret, code_verifier)
231 return self._acquire_token(token_func)
233 def acquire_token_with_refresh_token(self, refresh_token, client_id,
234 resource, client_secret=None):
235 '''Gets a token for a given resource via refresh tokens
237 :param str refresh_token: A refresh token returned in a tokne response
238 from a previous invocation of acquireToken.
239 :param str client_id: The OAuth client id of the calling application.
240 :param str resource: A URI that identifies the resource for which the
241 token is valid.
242 :param str client_secret: (optional)The OAuth client secret of the
243 calling application.
244 :returns: dict with several keys, include "accessToken" and
245 "refreshToken".
246 '''
247 def token_func(self):
248 token_request = TokenRequest(self._call_context, self, client_id, resource)
249 return token_request.get_token_with_refresh_token(refresh_token, client_secret)
251 return self._acquire_token(token_func)
253 def acquire_token_with_client_certificate(self, resource, client_id,
254 certificate, thumbprint, public_certificate=None):
255 '''Gets a token for a given resource via certificate credentials
257 :param str resource: A URI that identifies the resource for which the
258 token is valid.
259 :param str client_id: The OAuth client id of the calling application.
260 :param str certificate: A PEM encoded certificate private key.
261 :param str thumbprint: hex encoded thumbprint of the certificate.
262 :param str public_certificate(optional): if not None, it will be sent to the service for subject name
263 and issuer based authentication, which is to support cert auto rolls. The value must match the
264 certificate private key parameter.
266 Per `specs <https://tools.ietf.org/html/rfc7515#section-4.1.6>`_,
267 "the certificate containing
268 the public key corresponding to the key used to digitally sign the
269 JWS MUST be the first certificate. This MAY be followed by
270 additional certificates, with each subsequent certificate being the
271 one used to certify the previous one."
272 However, your certificate's issuer may use a different order.
273 So, if your attempt ends up with an error AADSTS700027 -
274 "The provided signature value did not match the expected signature value",
275 you may try use only the leaf cert (in PEM/str format) instead.
277 :returns: dict with several keys, include "accessToken".
278 '''
279 def token_func(self):
280 token_request = TokenRequest(self._call_context, self, client_id, resource)
281 return token_request.get_token_with_certificate(certificate, thumbprint, public_certificate)
283 return self._acquire_token(token_func)
285 def acquire_user_code(self, resource, client_id, language=None):
286 '''Gets the user code info which contains user_code, device_code for
287 authenticating user on device.
289 :param str resource: A URI that identifies the resource for which the
290 device_code and user_code is valid for.
291 :param str client_id: The OAuth client id of the calling application.
292 :param str language: The language code specifying how the message
293 should be localized to.
294 :returns: dict contains code and uri for users to login through browser.
295 '''
296 self._call_context['log_context'] = log.create_log_context(
297 self.correlation_id, self._call_context.get('enable_pii', False))
298 self.authority.validate(self._call_context)
299 code_request = CodeRequest(self._call_context, self, client_id, resource)
300 return code_request.get_user_code_info(language)
302 def acquire_token_with_device_code(self, resource, user_code_info, client_id):
303 '''Gets a new access token using via a device code.
305 :param str resource: A URI that identifies the resource for which the
306 token is valid.
307 :param dict user_code_info: The code info from the invocation of
308 "acquire_user_code"
309 :param str client_id: The OAuth client id of the calling application.
310 :returns: dict with several keys, include "accessToken" and
311 "refreshToken".
312 '''
313 def token_func(self):
314 token_request = TokenRequest(self._call_context, self, client_id, resource)
316 key = user_code_info[OAuth2DeviceCodeResponseParameters.DEVICE_CODE]
317 with self._lock:
318 self._token_requests_with_user_code[key] = token_request
320 token = token_request.get_token_with_device_code(user_code_info)
322 with self._lock:
323 self._token_requests_with_user_code.pop(key, None)
325 return token
327 return self._acquire_token(token_func, user_code_info.get('correlation_id', None))
329 def cancel_request_to_get_token_with_device_code(self, user_code_info):
330 '''Cancels the polling request to get token with device code.
332 :param dict user_code_info: The code info from the invocation of
333 "acquire_user_code"
334 :returns: None
335 '''
336 argument.validate_user_code_info(user_code_info)
338 key = user_code_info[OAuth2DeviceCodeResponseParameters.DEVICE_CODE]
339 with self._lock:
340 request = self._token_requests_with_user_code.get(key)
342 if not request:
343 raise ValueError('No acquire_token_with_device_code existed to be cancelled')
345 request.cancel_token_request_with_device_code()
346 self._token_requests_with_user_code.pop(key, None)