support digest auth for caldav sources - #36
Conversation
|
Thank you for the pull request! Please note that I might take a while to review it, due to being busy with some other things at this moment. I'll read into digest authentication, test your feature, and get back to you when I find the time. |
|
One thing I can already say is that I would like this to be implemented as a separate authentication method, rather than being merged with basic authentication. From the UI side of things, let's just add it to the advanced source creation modal first. I can add it to the wizard later. Due to the way that the currently proposed implementation was built "on top" of basic authentication, you essentially double the amount of requests you need to make if you use digest authentication. A little more is required to overcome this than just splitting it into a separate authentication type. First, the digest-specific values Edit: I think that creating a new database table for the cached challenges are the way to go. Interacting with the database could be done similarly to what my OAuth 2.0 implementation does, but some more careful considerations might be needed regarding race conditions. Let's worry about that once we get there. |
Opisek
left a comment
There was a problem hiding this comment.
Thanks for the feature! There are a few things that I'd like sorted before merging. Refer to the review comments and the comments in the PR's main thread.
| return "Digest " + strings.Join(parts, ", "), nil | ||
| } | ||
|
|
||
| func (auth BasicAuth) doDigest(req *http.Request, challenge *digestChallenge) (*http.Response, *errors.ErrorTrace) { |
There was a problem hiding this comment.
Let's split it into its own authentication type.
-> func (auth DigestAuth) Do(...)
| Password string `json:"password" form:"password"` | ||
| } | ||
|
|
||
| type digestChallenge struct { |
There was a problem hiding this comment.
Let's split this into its own authentication type
-> type DigestAuth struct { ... }
Make sure to use json:"-" on the challenge-specific fields, because that is not something we should return to the user.
Edit: Later I made a comment about creating a new database table for this instead of saving the challenges directly within the source. For guidance with that, see how OAuth 2.0 interacts with the database.
| ha2 := md5Hex(fmt.Sprintf("%s:%s", req.Method, uri)) | ||
|
|
||
| response := "" | ||
| parts := []string{ |
There was a problem hiding this comment.
Since these are all later joined to a single string anyway, please use a string builder instead.
| } | ||
|
|
||
| if qop != "" { | ||
| nonceCount := "00000001" |
There was a problem hiding this comment.
We need a failure for every single request that we do, since we only ever use each received nonce once.
Instead, we should save the digest challenge and the nc in the database, so we can reuse it across multiple requests. I think it makes the most sense to make a new database table for it.
|
|
||
| func newCnonce() (string, error) { | ||
| bytes := make([]byte, 16) | ||
| _, err := rand.Read(bytes) |
There was a problem hiding this comment.
Let's use GenerateRandomBytes from luna/crypto.
| return challenge | ||
| } | ||
|
|
||
| func cloneRequest(req *http.Request) (*http.Request, error) { |
There was a problem hiding this comment.
Delay cloning the actual request until after we get the challenge (if we get one at all).
For the body, we can io.ReadAll(req.Body) at the beginning of Do, set the original request's body to the read content again, and keep it in the memory in case we need to create a request copy later after all.
Doing it that way should reduce the memory overhead and prevent the "request body is not replayable" error.
| key = strings.ToLower(strings.TrimSpace(key)) | ||
| value = strings.Trim(strings.TrimSpace(value), `"`) | ||
|
|
||
| switch key { |
There was a problem hiding this comment.
Add a default case and error out if we receive something malformed..
| return parts | ||
| } | ||
|
|
||
| func parseDigestChallenge(header string) *digestChallenge { |
| return res, nil | ||
| } | ||
|
|
||
| func (auth BasicAuth) Do(req *http.Request) (*http.Response, *errors.ErrorTrace) { |
There was a problem hiding this comment.
As already mentioned, let's implement digest as its own authentication type and revert basic authentication to how it was.
Summary
WWW-Authenticate: Digest