Mercurial > repos > shellac > guppy_basecaller
comparison env/lib/python3.7/site-packages/requests/auth.py @ 5:9b1c78e6ba9c draft default tip
"planemo upload commit 6c0a8142489327ece472c84e558c47da711a9142"
author | shellac |
---|---|
date | Mon, 01 Jun 2020 08:59:25 -0400 |
parents | 79f47841a781 |
children |
comparison
equal
deleted
inserted
replaced
4:79f47841a781 | 5:9b1c78e6ba9c |
---|---|
1 # -*- coding: utf-8 -*- | |
2 | |
3 """ | |
4 requests.auth | |
5 ~~~~~~~~~~~~~ | |
6 | |
7 This module contains the authentication handlers for Requests. | |
8 """ | |
9 | |
10 import os | |
11 import re | |
12 import time | |
13 import hashlib | |
14 import threading | |
15 import warnings | |
16 | |
17 from base64 import b64encode | |
18 | |
19 from .compat import urlparse, str, basestring | |
20 from .cookies import extract_cookies_to_jar | |
21 from ._internal_utils import to_native_string | |
22 from .utils import parse_dict_header | |
23 | |
24 CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded' | |
25 CONTENT_TYPE_MULTI_PART = 'multipart/form-data' | |
26 | |
27 | |
28 def _basic_auth_str(username, password): | |
29 """Returns a Basic Auth string.""" | |
30 | |
31 # "I want us to put a big-ol' comment on top of it that | |
32 # says that this behaviour is dumb but we need to preserve | |
33 # it because people are relying on it." | |
34 # - Lukasa | |
35 # | |
36 # These are here solely to maintain backwards compatibility | |
37 # for things like ints. This will be removed in 3.0.0. | |
38 if not isinstance(username, basestring): | |
39 warnings.warn( | |
40 "Non-string usernames will no longer be supported in Requests " | |
41 "3.0.0. Please convert the object you've passed in ({!r}) to " | |
42 "a string or bytes object in the near future to avoid " | |
43 "problems.".format(username), | |
44 category=DeprecationWarning, | |
45 ) | |
46 username = str(username) | |
47 | |
48 if not isinstance(password, basestring): | |
49 warnings.warn( | |
50 "Non-string passwords will no longer be supported in Requests " | |
51 "3.0.0. Please convert the object you've passed in ({!r}) to " | |
52 "a string or bytes object in the near future to avoid " | |
53 "problems.".format(type(password)), | |
54 category=DeprecationWarning, | |
55 ) | |
56 password = str(password) | |
57 # -- End Removal -- | |
58 | |
59 if isinstance(username, str): | |
60 username = username.encode('latin1') | |
61 | |
62 if isinstance(password, str): | |
63 password = password.encode('latin1') | |
64 | |
65 authstr = 'Basic ' + to_native_string( | |
66 b64encode(b':'.join((username, password))).strip() | |
67 ) | |
68 | |
69 return authstr | |
70 | |
71 | |
72 class AuthBase(object): | |
73 """Base class that all auth implementations derive from""" | |
74 | |
75 def __call__(self, r): | |
76 raise NotImplementedError('Auth hooks must be callable.') | |
77 | |
78 | |
79 class HTTPBasicAuth(AuthBase): | |
80 """Attaches HTTP Basic Authentication to the given Request object.""" | |
81 | |
82 def __init__(self, username, password): | |
83 self.username = username | |
84 self.password = password | |
85 | |
86 def __eq__(self, other): | |
87 return all([ | |
88 self.username == getattr(other, 'username', None), | |
89 self.password == getattr(other, 'password', None) | |
90 ]) | |
91 | |
92 def __ne__(self, other): | |
93 return not self == other | |
94 | |
95 def __call__(self, r): | |
96 r.headers['Authorization'] = _basic_auth_str(self.username, self.password) | |
97 return r | |
98 | |
99 | |
100 class HTTPProxyAuth(HTTPBasicAuth): | |
101 """Attaches HTTP Proxy Authentication to a given Request object.""" | |
102 | |
103 def __call__(self, r): | |
104 r.headers['Proxy-Authorization'] = _basic_auth_str(self.username, self.password) | |
105 return r | |
106 | |
107 | |
108 class HTTPDigestAuth(AuthBase): | |
109 """Attaches HTTP Digest Authentication to the given Request object.""" | |
110 | |
111 def __init__(self, username, password): | |
112 self.username = username | |
113 self.password = password | |
114 # Keep state in per-thread local storage | |
115 self._thread_local = threading.local() | |
116 | |
117 def init_per_thread_state(self): | |
118 # Ensure state is initialized just once per-thread | |
119 if not hasattr(self._thread_local, 'init'): | |
120 self._thread_local.init = True | |
121 self._thread_local.last_nonce = '' | |
122 self._thread_local.nonce_count = 0 | |
123 self._thread_local.chal = {} | |
124 self._thread_local.pos = None | |
125 self._thread_local.num_401_calls = None | |
126 | |
127 def build_digest_header(self, method, url): | |
128 """ | |
129 :rtype: str | |
130 """ | |
131 | |
132 realm = self._thread_local.chal['realm'] | |
133 nonce = self._thread_local.chal['nonce'] | |
134 qop = self._thread_local.chal.get('qop') | |
135 algorithm = self._thread_local.chal.get('algorithm') | |
136 opaque = self._thread_local.chal.get('opaque') | |
137 hash_utf8 = None | |
138 | |
139 if algorithm is None: | |
140 _algorithm = 'MD5' | |
141 else: | |
142 _algorithm = algorithm.upper() | |
143 # lambdas assume digest modules are imported at the top level | |
144 if _algorithm == 'MD5' or _algorithm == 'MD5-SESS': | |
145 def md5_utf8(x): | |
146 if isinstance(x, str): | |
147 x = x.encode('utf-8') | |
148 return hashlib.md5(x).hexdigest() | |
149 hash_utf8 = md5_utf8 | |
150 elif _algorithm == 'SHA': | |
151 def sha_utf8(x): | |
152 if isinstance(x, str): | |
153 x = x.encode('utf-8') | |
154 return hashlib.sha1(x).hexdigest() | |
155 hash_utf8 = sha_utf8 | |
156 elif _algorithm == 'SHA-256': | |
157 def sha256_utf8(x): | |
158 if isinstance(x, str): | |
159 x = x.encode('utf-8') | |
160 return hashlib.sha256(x).hexdigest() | |
161 hash_utf8 = sha256_utf8 | |
162 elif _algorithm == 'SHA-512': | |
163 def sha512_utf8(x): | |
164 if isinstance(x, str): | |
165 x = x.encode('utf-8') | |
166 return hashlib.sha512(x).hexdigest() | |
167 hash_utf8 = sha512_utf8 | |
168 | |
169 KD = lambda s, d: hash_utf8("%s:%s" % (s, d)) | |
170 | |
171 if hash_utf8 is None: | |
172 return None | |
173 | |
174 # XXX not implemented yet | |
175 entdig = None | |
176 p_parsed = urlparse(url) | |
177 #: path is request-uri defined in RFC 2616 which should not be empty | |
178 path = p_parsed.path or "/" | |
179 if p_parsed.query: | |
180 path += '?' + p_parsed.query | |
181 | |
182 A1 = '%s:%s:%s' % (self.username, realm, self.password) | |
183 A2 = '%s:%s' % (method, path) | |
184 | |
185 HA1 = hash_utf8(A1) | |
186 HA2 = hash_utf8(A2) | |
187 | |
188 if nonce == self._thread_local.last_nonce: | |
189 self._thread_local.nonce_count += 1 | |
190 else: | |
191 self._thread_local.nonce_count = 1 | |
192 ncvalue = '%08x' % self._thread_local.nonce_count | |
193 s = str(self._thread_local.nonce_count).encode('utf-8') | |
194 s += nonce.encode('utf-8') | |
195 s += time.ctime().encode('utf-8') | |
196 s += os.urandom(8) | |
197 | |
198 cnonce = (hashlib.sha1(s).hexdigest()[:16]) | |
199 if _algorithm == 'MD5-SESS': | |
200 HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce)) | |
201 | |
202 if not qop: | |
203 respdig = KD(HA1, "%s:%s" % (nonce, HA2)) | |
204 elif qop == 'auth' or 'auth' in qop.split(','): | |
205 noncebit = "%s:%s:%s:%s:%s" % ( | |
206 nonce, ncvalue, cnonce, 'auth', HA2 | |
207 ) | |
208 respdig = KD(HA1, noncebit) | |
209 else: | |
210 # XXX handle auth-int. | |
211 return None | |
212 | |
213 self._thread_local.last_nonce = nonce | |
214 | |
215 # XXX should the partial digests be encoded too? | |
216 base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \ | |
217 'response="%s"' % (self.username, realm, nonce, path, respdig) | |
218 if opaque: | |
219 base += ', opaque="%s"' % opaque | |
220 if algorithm: | |
221 base += ', algorithm="%s"' % algorithm | |
222 if entdig: | |
223 base += ', digest="%s"' % entdig | |
224 if qop: | |
225 base += ', qop="auth", nc=%s, cnonce="%s"' % (ncvalue, cnonce) | |
226 | |
227 return 'Digest %s' % (base) | |
228 | |
229 def handle_redirect(self, r, **kwargs): | |
230 """Reset num_401_calls counter on redirects.""" | |
231 if r.is_redirect: | |
232 self._thread_local.num_401_calls = 1 | |
233 | |
234 def handle_401(self, r, **kwargs): | |
235 """ | |
236 Takes the given response and tries digest-auth, if needed. | |
237 | |
238 :rtype: requests.Response | |
239 """ | |
240 | |
241 # If response is not 4xx, do not auth | |
242 # See https://github.com/psf/requests/issues/3772 | |
243 if not 400 <= r.status_code < 500: | |
244 self._thread_local.num_401_calls = 1 | |
245 return r | |
246 | |
247 if self._thread_local.pos is not None: | |
248 # Rewind the file position indicator of the body to where | |
249 # it was to resend the request. | |
250 r.request.body.seek(self._thread_local.pos) | |
251 s_auth = r.headers.get('www-authenticate', '') | |
252 | |
253 if 'digest' in s_auth.lower() and self._thread_local.num_401_calls < 2: | |
254 | |
255 self._thread_local.num_401_calls += 1 | |
256 pat = re.compile(r'digest ', flags=re.IGNORECASE) | |
257 self._thread_local.chal = parse_dict_header(pat.sub('', s_auth, count=1)) | |
258 | |
259 # Consume content and release the original connection | |
260 # to allow our new request to reuse the same one. | |
261 r.content | |
262 r.close() | |
263 prep = r.request.copy() | |
264 extract_cookies_to_jar(prep._cookies, r.request, r.raw) | |
265 prep.prepare_cookies(prep._cookies) | |
266 | |
267 prep.headers['Authorization'] = self.build_digest_header( | |
268 prep.method, prep.url) | |
269 _r = r.connection.send(prep, **kwargs) | |
270 _r.history.append(r) | |
271 _r.request = prep | |
272 | |
273 return _r | |
274 | |
275 self._thread_local.num_401_calls = 1 | |
276 return r | |
277 | |
278 def __call__(self, r): | |
279 # Initialize per-thread state, if needed | |
280 self.init_per_thread_state() | |
281 # If we have a saved nonce, skip the 401 | |
282 if self._thread_local.last_nonce: | |
283 r.headers['Authorization'] = self.build_digest_header(r.method, r.url) | |
284 try: | |
285 self._thread_local.pos = r.body.tell() | |
286 except AttributeError: | |
287 # In the case of HTTPDigestAuth being reused and the body of | |
288 # the previous request was a file-like object, pos has the | |
289 # file position of the previous body. Ensure it's set to | |
290 # None. | |
291 self._thread_local.pos = None | |
292 r.register_hook('response', self.handle_401) | |
293 r.register_hook('response', self.handle_redirect) | |
294 self._thread_local.num_401_calls = 1 | |
295 | |
296 return r | |
297 | |
298 def __eq__(self, other): | |
299 return all([ | |
300 self.username == getattr(other, 'username', None), | |
301 self.password == getattr(other, 'password', None) | |
302 ]) | |
303 | |
304 def __ne__(self, other): | |
305 return not self == other |