Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/adal/authentication_context.py: 30%

89 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:23 +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 

30 

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 

38 

39GLOBAL_ADAL_OPTIONS = {} 

40 

41class AuthenticationContext(object): 

42 '''Retrieves authentication tokens from Azure Active Directory. 

43 

44 For usages, check out the "sample" folder at: 

45 https://github.com/AzureAD/azure-activedirectory-library-for-python 

46 ''' 

47 

48 def __init__( 

49 self, authority, validate_authority=None, cache=None, 

50 api_version=None, timeout=None, enable_pii=False, verify_ssl=None, proxies=None): 

51 '''Creates a new AuthenticationContext object. 

52 

53 By default the authority will be checked against a list of known Azure 

54 Active Directory authorities. If the authority is not recognized as  

55 one of these well known authorities then token acquisition will fail. 

56 This behavior can be turned off via the validate_authority parameter 

57 below. 

58 

59 :param str authority: A URL that identifies a token authority. It should be of the 

60 format https://login.microsoftonline.com/your_tenant 

61 :param bool validate_authority: (optional) Turns authority validation  

62 on or off. This parameter default to true. 

63 :param TokenCache cache: (optional) Sets the token cache used by this  

64 AuthenticationContext instance. If this parameter is not set, then 

65 a default is used. Cache instances is only used by that instance of 

66 the AuthenticationContext and are not shared unless it has been 

67 manually passed during the construction of other 

68 AuthenticationContexts. 

69 :param api_version: (optional) Specifies API version using on the wire. 

70 Historically it has a hardcoded default value as "1.0". 

71 Developers have been encouraged to set it as None explicitly, 

72 which means the underlying API version will be automatically chosen. 

73 Starting from ADAL Python 1.0, this default value becomes None. 

74 :param timeout: (optional) requests timeout. How long to wait for the server to send 

75 data before giving up, as a float, or a `(connect timeout, 

76 read timeout) <timeouts>` tuple. 

77 :param enable_pii: (optional) Unless this is set to True, 

78 there will be no Personally Identifiable Information (PII) written in log. 

79 :param verify_ssl: (optional) requests verify. Either a boolean, in which case it  

80 controls whether we verify the server's TLS certificate, or a string, in which  

81 case it must be a path to a CA bundle to use. If this value is not provided, and  

82 ADAL_PYTHON_SSL_NO_VERIFY env variable is set, behavior is equivalent to  

83 verify_ssl=False. 

84 :param proxies: (optional) requests proxies. Dictionary mapping protocol to the URL  

85 of the proxy. See http://docs.python-requests.org/en/master/user/advanced/#proxies 

86 for details. 

87 ''' 

88 self.authority = Authority(authority, validate_authority is None or validate_authority) 

89 self._oauth2client = None 

90 self.correlation_id = None 

91 env_verify = 'ADAL_PYTHON_SSL_NO_VERIFY' not in os.environ 

92 verify = verify_ssl if verify_ssl is not None else env_verify 

93 if api_version is not None: 

94 warnings.warn( 

95 """The default behavior of including api-version=1.0 on the wire 

96 is now deprecated. 

97 Future version of ADAL will change the default value to None. 

98 

99 To ensure a smooth transition, you are recommended to explicitly 

100 set it to None in your code now, and test out the new behavior. 

101 

102 context = AuthenticationContext(..., api_version=None) 

103 """, DeprecationWarning) 

104 self._call_context = { 

105 'options': GLOBAL_ADAL_OPTIONS, 

106 'api_version': api_version, 

107 'verify_ssl': verify, 

108 'proxies':proxies, 

109 'timeout':timeout, 

110 "enable_pii": enable_pii, 

111 } 

112 self._token_requests_with_user_code = {} 

113 self.cache = cache or TokenCache() 

114 self._lock = threading.RLock() 

115 

116 @property 

117 def options(self): 

118 return self._call_context['options'] 

119 

120 @options.setter 

121 def options(self, val): 

122 self._call_context['options'] = val 

123 

124 def _acquire_token(self, token_func, correlation_id=None): 

125 self._call_context['log_context'] = log.create_log_context( 

126 correlation_id or self.correlation_id, self._call_context.get('enable_pii', False)) 

127 self.authority.validate(self._call_context) 

128 return token_func(self) 

129 

130 def acquire_token(self, resource, user_id, client_id): 

131 '''Gets a token for a given resource via cached tokens. 

132 

133 :param str resource: A URI that identifies the resource for which the 

134 token is valid. 

135 :param str user_id: The username of the user on behalf this application 

136 is authenticating. 

137 :param str client_id: The OAuth client id of the calling application. 

138 :returns: dic with several keys, include "accessToken" and 

139 "refreshToken". 

140 ''' 

141 def token_func(self): 

142 token_request = TokenRequest(self._call_context, self, client_id, resource) 

143 return token_request.get_token_from_cache_with_refresh(user_id) 

144 

145 return self._acquire_token(token_func) 

146 

147 def acquire_token_with_username_password(self, resource, username, password, client_id): 

148 '''Gets a token for a given resource via user credentails. 

149  

150 :param str resource: A URI that identifies the resource for which the  

151 token is valid. 

152 :param str username: The username of the user on behalf this 

153 application is authenticating. 

154 :param str password: The password of the user named in the username 

155 parameter. 

156 :param str client_id: The OAuth client id of the calling application. 

157 :returns: dict with several keys, include "accessToken" and 

158 "refreshToken". 

159 ''' 

160 def token_func(self): 

161 token_request = TokenRequest(self._call_context, self, client_id, resource) 

162 return token_request.get_token_with_username_password(username, password) 

163 

164 return self._acquire_token(token_func) 

165 

166 def acquire_token_with_client_credentials(self, resource, client_id, client_secret): 

167 '''Gets a token for a given resource via client credentials. 

168 

169 :param str resource: A URI that identifies the resource for which the  

170 token is valid. 

171 :param str client_id: The OAuth client id of the calling application. 

172 :param str client_secret: The OAuth client secret of the calling application. 

173 :returns: dict with several keys, include "accessToken". 

174 ''' 

175 def token_func(self): 

176 token_request = TokenRequest(self._call_context, self, client_id, resource) 

177 return token_request.get_token_with_client_credentials(client_secret) 

178 

179 return self._acquire_token(token_func) 

180 

181 def acquire_token_with_authorization_code(self, authorization_code, 

182 redirect_uri, resource, 

183 client_id, client_secret=None, code_verifier=None): 

184 '''Gets a token for a given resource via authorization code for a 

185 server app. 

186  

187 :param str authorization_code: An authorization code returned from a 

188 client. 

189 :param str redirect_uri: the redirect uri that was used in the 

190 authorize call. 

191 :param str resource: A URI that identifies the resource for which the 

192 token is valid. 

193 :param str client_id: The OAuth client id of the calling application. 

194 :param str client_secret: (only for confidential clients)The OAuth 

195 client secret of the calling application. This parameter if not set, 

196 defaults to None 

197 :param str code_verifier: (optional)The code verifier that was used to 

198 obtain authorization code if PKCE was used in the authorization 

199 code grant request.(usually used by public clients) This parameter if not set, 

200 defaults to None 

201 :returns: dict with several keys, include "accessToken" and 

202 "refreshToken". 

203 ''' 

204 def token_func(self): 

205 token_request = TokenRequest( 

206 self._call_context, 

207 self, 

208 client_id, 

209 resource, 

210 redirect_uri) 

211 return token_request.get_token_with_authorization_code( 

212 authorization_code, 

213 client_secret, code_verifier) 

214 

215 return self._acquire_token(token_func) 

216 

217 def acquire_token_with_refresh_token(self, refresh_token, client_id, 

218 resource, client_secret=None): 

219 '''Gets a token for a given resource via refresh tokens 

220  

221 :param str refresh_token: A refresh token returned in a tokne response 

222 from a previous invocation of acquireToken. 

223 :param str client_id: The OAuth client id of the calling application. 

224 :param str resource: A URI that identifies the resource for which the 

225 token is valid. 

226 :param str client_secret: (optional)The OAuth client secret of the 

227 calling application.  

228 :returns: dict with several keys, include "accessToken" and 

229 "refreshToken". 

230 ''' 

231 def token_func(self): 

232 token_request = TokenRequest(self._call_context, self, client_id, resource) 

233 return token_request.get_token_with_refresh_token(refresh_token, client_secret) 

234 

235 return self._acquire_token(token_func) 

236 

237 def acquire_token_with_client_certificate(self, resource, client_id, 

238 certificate, thumbprint, public_certificate=None): 

239 '''Gets a token for a given resource via certificate credentials 

240 

241 :param str resource: A URI that identifies the resource for which the 

242 token is valid. 

243 :param str client_id: The OAuth client id of the calling application. 

244 :param str certificate: A PEM encoded certificate private key. 

245 :param str thumbprint: hex encoded thumbprint of the certificate. 

246 :param str public_certificate(optional): if not None, it will be sent to the service for subject name 

247 and issuer based authentication, which is to support cert auto rolls. The value must match the 

248 certificate private key parameter. 

249 

250 Per `specs <https://tools.ietf.org/html/rfc7515#section-4.1.6>`_, 

251 "the certificate containing 

252 the public key corresponding to the key used to digitally sign the 

253 JWS MUST be the first certificate. This MAY be followed by 

254 additional certificates, with each subsequent certificate being the 

255 one used to certify the previous one." 

256 However, your certificate's issuer may use a different order. 

257 So, if your attempt ends up with an error AADSTS700027 - 

258 "The provided signature value did not match the expected signature value", 

259 you may try use only the leaf cert (in PEM/str format) instead. 

260 

261 :returns: dict with several keys, include "accessToken". 

262 ''' 

263 def token_func(self): 

264 token_request = TokenRequest(self._call_context, self, client_id, resource) 

265 return token_request.get_token_with_certificate(certificate, thumbprint, public_certificate) 

266 

267 return self._acquire_token(token_func) 

268 

269 def acquire_user_code(self, resource, client_id, language=None): 

270 '''Gets the user code info which contains user_code, device_code for 

271 authenticating user on device. 

272  

273 :param str resource: A URI that identifies the resource for which the  

274 device_code and user_code is valid for. 

275 :param str client_id: The OAuth client id of the calling application. 

276 :param str language: The language code specifying how the message 

277 should be localized to. 

278 :returns: dict contains code and uri for users to login through browser. 

279 ''' 

280 self._call_context['log_context'] = log.create_log_context( 

281 self.correlation_id, self._call_context.get('enable_pii', False)) 

282 self.authority.validate(self._call_context) 

283 code_request = CodeRequest(self._call_context, self, client_id, resource) 

284 return code_request.get_user_code_info(language) 

285 

286 def acquire_token_with_device_code(self, resource, user_code_info, client_id): 

287 '''Gets a new access token using via a device code.  

288  

289 :param str resource: A URI that identifies the resource for which the 

290 token is valid. 

291 :param dict user_code_info: The code info from the invocation of 

292 "acquire_user_code" 

293 :param str client_id: The OAuth client id of the calling application. 

294 :returns: dict with several keys, include "accessToken" and 

295 "refreshToken". 

296 ''' 

297 def token_func(self): 

298 token_request = TokenRequest(self._call_context, self, client_id, resource) 

299 

300 key = user_code_info[OAuth2DeviceCodeResponseParameters.DEVICE_CODE] 

301 with self._lock: 

302 self._token_requests_with_user_code[key] = token_request 

303 

304 token = token_request.get_token_with_device_code(user_code_info) 

305 

306 with self._lock: 

307 self._token_requests_with_user_code.pop(key, None) 

308 

309 return token 

310 

311 return self._acquire_token(token_func, user_code_info.get('correlation_id', None)) 

312 

313 def cancel_request_to_get_token_with_device_code(self, user_code_info): 

314 '''Cancels the polling request to get token with device code.  

315 

316 :param dict user_code_info: The code info from the invocation of 

317 "acquire_user_code" 

318 :returns: None 

319 ''' 

320 argument.validate_user_code_info(user_code_info) 

321 

322 key = user_code_info[OAuth2DeviceCodeResponseParameters.DEVICE_CODE] 

323 with self._lock: 

324 request = self._token_requests_with_user_code.get(key) 

325 

326 if not request: 

327 raise ValueError('No acquire_token_with_device_code existed to be cancelled') 

328 

329 request.cancel_token_request_with_device_code() 

330 self._token_requests_with_user_code.pop(key, None)