userver
C++ Async Framework
Toggle main menu visibility
Loading...
Searching...
No Matches
httpdigest.py
1
# type: ignore
2
"""Helpers to act as a minimal MD5 Digest client or server from Python."""
3
4
import
hashlib
5
import
re
6
7
from
requests.auth
import
HTTPDigestAuth
8
9
# Parses both `key="value"` and `key=value` forms of digest directives.
10
_DIRECTIVE_RE = re.compile(
r'(\w+)=("[^"]*"|[^,\s]+)'
)
11
12
13
def
_md5(data: str) -> str:
14
return
hashlib.md5(data.encode(
'utf-8'
)).hexdigest()
15
16
17
def
parse_directives
(header: str) -> dict:
18
"""Parses a WWW-Authenticate or an Authorization header value."""
19
prefix, _, params = header.partition(
' '
)
20
assert
prefix ==
'Digest'
, header
21
return
{key: value.strip(
'"'
)
for
key, value
in
_DIRECTIVE_RE.findall(params)}
22
23
24
def
construct_challenge(*, auth_directives: dict, nonce: str |
None
=
None
) -> dict:
25
return
{
26
'realm'
: auth_directives[
'realm'
],
27
'nonce'
: nonce
or
auth_directives[
'nonce'
],
28
'algorithm'
: auth_directives[
'algorithm'
],
29
'qop'
:
'auth'
,
30
}
31
32
33
class
DigestAuthClient
:
34
"""Minimal stateful Digest client for functional tests."""
35
36
def
__init__(self, *, username: str, password: str):
37
self.
_digest_auth
= HTTPDigestAuth(username, password)
38
self.
_digest_auth
.init_per_thread_state()
39
40
def
construct_header
(self, *, challenge: dict, uri: str, method: str) -> str:
41
"""Builds an `Authorization: Digest ...` header value for the challenge."""
42
# pylint: disable=protected-access
43
self.
_digest_auth
._thread_local.chal = challenge
44
return
self.
_digest_auth
.build_digest_header(method, uri)
45
46
47
def
construct_header
(*, username: str, password: str, challenge: dict, uri: str, method: str) -> str:
48
"""Builds a single `Authorization: Digest ...` header value."""
49
return
DigestAuthClient
(username=username, password=password).
construct_header
(
50
challenge=challenge,
51
uri=uri,
52
method=method,
53
)
54
55
56
def
calculate_response
(
57
*,
58
username: str,
59
password: str,
60
realm: str,
61
nonce: str,
62
nonce_count: str,
63
cnonce: str,
64
qop: str,
65
method: str,
66
uri: str,
67
) -> str:
68
"""Server side counterpart: the expected value of the `response` directive."""
69
ha1 = _md5(f
'{username}:{realm}:{password}'
)
70
ha2 = _md5(f
'{method}:{uri}'
)
71
return
_md5(f
'{ha1}:{nonce}:{nonce_count}:{cnonce}:{qop}:{ha2}'
)
testsuite
pytest_plugins
pytest_userver
utils
httpdigest.py
Generated on
for userver by
Doxygen
1.17.0