-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Digest auth helper #10725
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Digest auth helper #10725
Conversation
This is the initial work by jf from aio-libs#2213, which I rebased onto the tip of master. It is fully brought back to life in subsequent commits.
This completes the implementation of the digest helper, implementing for additional hashing algorithms sha256 and sha512, as well as auth-int qop. It implements tests and documents the features. The DigestAuth feature was shimmed into the existing API so it could be used in place of BasicAuth in the auth field of ClientSession and ClientRequest.
@@ -53,7 +57,7 @@ | |||
from propcache.api import under_cached_property as reify | |||
from yarl import URL | |||
|
|||
from . import hdrs | |||
from . import client_exceptions, hdrs |
Check notice
Code scanning / CodeQL
Cyclic import Note
aiohttp.client_exceptions
9927632
to
22825c1
Compare
# key1="value1", key2=value2, key3="some value, with, commas" | ||
# | ||
# This regex attempts to parse that out | ||
pattern = re.compile( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please make this a constant so its not compiled every time
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TimMenninger commented 7 minutes ago
...
so quick
# +--> at least one char req'd | ||
) | ||
|
||
header_pairs = {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing type here
We had talked about making a more generic client middleware, but we never moved that forward. I'm inclined to accept this since we would very much like to have digest auth support in 3.x, and move to using client middleware for auth in 4.x since it would be a breaking change anyways. |
if "nonce" not in self.ctx.challenge: | ||
raise client_exceptions.ClientError("Challenge is missing nonce") | ||
|
||
realm: str = self.ctx.challenge.get("realm", "") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: personal preference here would be to assign challenge = self.ctx.challenge
so we don't have self.ctx.challenge
on every line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you also feel this way in the authenticate
function where I set all the values?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think if #10725 (comment) would be implemented it would negate the the concern in another way
CodSpeed Performance ReportMerging #10725 will not alter performanceComparing Summary
|
I agree with that - I didn't see any other good way of doing this without breaking backward compatibility. Almost requested punting this one to 4.x but it ended up not being horrible for a first attempt. @feus4177 did most of the heavy lifting anyway. |
def H(x: str) -> str: | ||
return hash_fn(x.encode()).hexdigest() | ||
|
||
def KD(s: str, d: str) -> str: | ||
return H(f"{s}:{d}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please give these docstrings. and link to sections in the RFC that define that these are so anyone who comes across this code later will know what this is doing
https://datatracker.ietf.org/doc/html/rfc2617#section-3.2.2.2
pairs = [ | ||
f'username="{self.login}"', | ||
f'realm="{realm}"', | ||
f'nonce="{nonce}"', | ||
f'uri="{path}"', | ||
f'response="{response_digest}"', | ||
f'algorithm="{algorithm}"', | ||
] | ||
if opaque: | ||
pairs.append(f'opaque="{opaque}"') | ||
if qop: | ||
pairs.append(f'qop="{qop}"') | ||
pairs.append(f"nc={ncvalue}") | ||
pairs.append(f'cnonce="{cnonce}"') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO: These need to be check for possible injection attacks in the event any of this could be user provided (it may be fine, but we need to analyze and think about it)
if "realm" in header_pairs and header_pairs["realm"]: | ||
self.ctx.challenge["realm"] = header_pairs["realm"] | ||
if "nonce" in header_pairs and header_pairs["nonce"]: | ||
self.ctx.challenge["nonce"] = header_pairs["nonce"] | ||
if "qop" in header_pairs and header_pairs["qop"]: | ||
self.ctx.challenge["qop"] = header_pairs["qop"] | ||
if "algorithm" in header_pairs and header_pairs["algorithm"]: | ||
self.ctx.challenge["algorithm"] = header_pairs["algorithm"] | ||
if "opaque" in header_pairs and header_pairs["opaque"]: | ||
self.ctx.challenge["opaque"] = header_pairs["opaque"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe could be written as a loop with a constant tuple as iteration source
def __init__(self) -> None: | ||
super().__init__() | ||
self.init = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably could make init
a classvar here and than drop __init__
... | ||
|
||
|
||
class DigestAuthContext(threading.local): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit confused why we would need thread local storage since aiohttp
is an asyncio
library, we shouldn't have multiple threads to worry about.
I was hoping to work on that reasonably soon. This PR would add several extra hooks throughout the code that we'd need to remember to remove again. It'd also be confusing to users if we add this and then deprecate it only a release or 2 later. My current understanding is that the middlewares would not be a breaking change and they could be backported. Then BasicAuth would become deprecated in favour of the middlewares. |
I'd be much happier with client middleware vs this implementation. Seems like that is closer than I expected so I'd probably stop working on this PR but keep it warm in case we run into any snags with client middleware that would prevent it from being available in 3.12. |
For clarity: Once we have the client middleware we could add a digest auth client middleware (probably much of this could be reused) |
Okay I am going to put this down for now. I really only picked it up in an effort to move 3.12 along. Feel free to ping me if/when we're ready to pick back up. |
What do these changes do?
This adds a
DigestAuth
class that can be used in lieu ofBasicAuth
for digest authorization.Note that this is just me taking a crack at polishing off the 3.12 milestone because it has features I'm interested in. I am reserving the right to abandon this if the PR is too far off from the vision of the maintainers and/or is outright wrong.
Are there changes in behavior for the user?
There are no changes to default behavior. It will continue to use
BasicAuth
, and existing uses ofBasicAuth
should be intact.Is it a substantial burden for the maintainers to support this?
Hopefully this won't be too horrible to maintain. The few burdens I can think of are:
BasicAuth
interface will necessitate changingDigestAuth
Related issue number
Continuation of #2213
Checklist
CONTRIBUTORS.txt
CHANGES/
foldername it
<issue_or_pr_num>.<type>.rst
(e.g.588.bugfix.rst
)if you don't have an issue number, change it to the pull request
number after creating the PR
.bugfix
: A bug fix for something the maintainers deemed animproper undesired behavior that got corrected to match
pre-agreed expectations.
.feature
: A new behavior, public APIs. That sort of stuff..deprecation
: A declaration of future API removals and breakingchanges in behavior.
.breaking
: When something public is removed in a breaking way.Could be deprecated in an earlier release.
.doc
: Notable updates to the documentation structure or buildprocess.
.packaging
: Notes for downstreams about unobvious side effectsand tooling. Changes in the test invocation considerations and
runtime assumptions.
.contrib
: Stuff that affects the contributor experience. e.g.Running tests, building the docs, setting up the development
environment.
.misc
: Changes that are hard to assign to any of the abovecategories.
Make sure to use full sentences with correct case and punctuation,
for example:
Use the past tense or the present tense a non-imperative mood,
referring to what's changed compared to the last released version
of this project.