From bce14610a52913b45744d7b59a981dec3843d8d3 Mon Sep 17 00:00:00 2001 From: hustern <100440892+hustern@users.noreply.github.com> Date: Tue, 23 Jul 2024 13:16:34 +0200 Subject: [PATCH] Add support for stale directive for digest authenticator (#80) * Improve digest authenticator to handle stale directive --- digestAuth.go | 21 ++++++++++++++++++++- digestAuth_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/digestAuth.go b/digestAuth.go index 1bcc1de3..a8e82809 100644 --- a/digestAuth.go +++ b/digestAuth.go @@ -35,7 +35,12 @@ func (d *DigestAuth) Authorize(c *http.Client, rq *http.Request, path string) er // Verify checks for authentication issues and may trigger a re-authentication func (d *DigestAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) { if rs.StatusCode == 401 { - err = NewPathError("Authorize", path, rs.StatusCode) + if isStaled(rs) { + redo = true + err = ErrAuthChanged + } else { + err = NewPathError("Authorize", path, rs.StatusCode) + } } return } @@ -162,3 +167,17 @@ func getDigestAuthorization(digestParts map[string]string) string { return authorization } + +func isStaled(rs *http.Response) bool { + header := rs.Header.Get("Www-Authenticate") + if len(header) > 0 { + directives := strings.Split(header, ",") + for i := range directives { + name, value, _ := strings.Cut(strings.Trim(directives[i], " "), "=") + if strings.EqualFold(name, "stale") { + return strings.EqualFold(value, "true") + } + } + } + return false +} diff --git a/digestAuth_test.go b/digestAuth_test.go index bb301173..4a621b70 100644 --- a/digestAuth_test.go +++ b/digestAuth_test.go @@ -1,6 +1,7 @@ package gowebdav import ( + "errors" "net/http" "strings" "testing" @@ -33,3 +34,42 @@ func TestDigestAuthAuthorize(t *testing.T) { t.Error("got wrong Authorization header: " + rq.Header.Get("Authorization")) } } + +func TestDigestAuthVerify(t *testing.T) { + a := &DigestAuth{user: "user", pw: "password", digestParts: make(map[string]string, 0)} + + // Nominal test: 200 OK response + rs := &http.Response{ + Status: "200 OK", + StatusCode: http.StatusOK, + } + + redo, err := a.Verify(nil, rs, "/") + + if err != nil { + t.Errorf("got error: %v, want nil", err) + } + + if redo { + t.Errorf("got redo: %t, want false", redo) + } + + // Digest expiration test: 401 Unauthorized response with stale directive in WWW-Authenticate header + rs = &http.Response{ + Status: "401 Unauthorized", + StatusCode: http.StatusUnauthorized, + Header: http.Header{ + "Www-Authenticate": []string{"Digest realm=\"webdav\", nonce=\"YVvALpkdBgA=931bbf2b6fa9dda227361dba38a735f005fd9f97\", algorithm=MD5, qop=\"auth\", stale=true"}, + }, + } + + redo, err = a.Verify(nil, rs, "/") + + if !errors.Is(err, ErrAuthChanged) { + t.Errorf("got error: %v, want ErrAuthChanged", err) + } + + if !redo { + t.Errorf("got redo: %t, want true", redo) + } +}