requests.auth.HTTPDigestAuth does not support qop=auth-int
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 54.3k
- Forks
- 10.4k
- Avg merge
- 16h 43m
- Merged PRs (30d)
- 3
Description
Summary:
When using requests.auth.HTTPDigestAuth, if the www-authenticate digest back has a qop value of 'auth-int', build_digest_header returns None, which makes http.client.HTTPConnection.putheader throw an exception because *values expects to be a set of Strings or Bytes.
Expected Result:
I expect a valid Digest in return, or at least an Exception that input data is bad
Actual Result:
TypeError Exception - expected string or bytes-like object
Reproduction Steps
import requests
requests.get('https://device.hostname/api/auth', auth=requests.auth.HTTPDigestAuth('login','password')
System Information
$ python -m requests.help
{
"chardet": {
"version": "3.0.4"
},
"cryptography": {
"version": ""
},
"idna": {
"version": "2.10"
},
"implementation": {
"name": "CPython",
"version": "3.8.6"
},
"platform": {
"release": "20.2.0",
"system": "Darwin"
},
"pyOpenSSL": {
"openssl_version": "",
"version": null
},
"requests": {
"version": "2.24.0"
},
"system_ssl": {
"version": "1010108f"
},
"urllib3": {
"version": "1.25.9"
},
"using_pyopenssl": false
}
Working Fix
requests.auth.HTTPDigestAuth.build_digest_header has the following code, which is causing the return of None:
if not qop:
respdig = KD(HA1, "%s:%s" % (nonce, HA2))
elif qop == 'auth' or 'auth' in qop.split(','):
noncebit = "%s:%s:%s:%s:%s" % (
nonce, ncvalue, cnonce, 'auth', HA2
)
respdig = KD(HA1, noncebit)
else:
# XXX handle auth-int.
return None
Patches Needed:
requests.auth.HTTPDigestAuth.build_digest_header needs to have an optional arg for body, which should be the bytes of the body of the request or None
def build_digest_header(self, method, url, body=b''):
The qop value should be put into a list:
qop = self._thread_local.chal.get('qop')
if qop:
qop = [x.strip() for x in qop.split(',')]
A2 needs to be calculated differently if auth-int:
if qop and 'auth-int' in qop:
A2 = '%s:%s:%s' % (method, path, hash_utf8(body or b''))
else:
A2 = '%s:%s' % (method, path)
And the qop test block needs to be changed:
if not qop:
respdig = KD(HA1, "%s:%s" % (nonce, HA2))
else:
if 'auth' in qop or 'auth-int' in qop:
noncebit = "%s:%s:%s:%s:%s" % (
nonce, ncvalue, cnonce, ','.join(qop), HA2
)
respdig = KD(HA1, noncebit)
else:
raise NotImplementedError('qop value of "%s" is not implemented.' % (','.join(qop)))
Also the last check before returning digest:
if qop:
base += ', qop=%s, nc=%s, cnonce="%s"' % (','.join(qop), ncvalue, cnonce)
Now to change the callers:
handle_401 needs to be changed to add the body arg:
prep.headers['Authorization'] = self.build_digest_header(
prep.method, prep.url, prep.body)
call needs to be changed to add the body arg:
r.headers['Authorization'] = self.build_digest_header(r.method, r.url, r.body)
And that should create a fully working framework for auth-int with RFC 2617
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in requests.auth.HTTPDigestAuth.build_digest_header and trace its callers in handle_401 and call, focusing on how the request body reaches digest calculation. Support qop=auth-int using the request body as described and ensure unsupported qop values raise an exception instead of returning None; done when the reproduction no longer produces the TypeError and digest authentication remains valid for existing qop cases.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- authentication, networking
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100