Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hhhrrrttt222111
GitHub Repository: hhhrrrttt222111/Dorkify
Path: blob/master/venv/Lib/site-packages/requests/auth.py
811 views
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
306
307