Skip to content

Commit

Permalink
feat!: enhance and add feature into digest auth flow #914
Browse files Browse the repository at this point in the history
- redesign the digest auth internals, cleanup, and improvements
- add 'auth-int' QOP support
- add and update digest hash functions
- remove the 'Request.SetDigestAuth' method, keep the option only at the client level
  • Loading branch information
jeevatkm committed Nov 24, 2024
1 parent c2d0b84 commit cda801e
Show file tree
Hide file tree
Showing 7 changed files with 608 additions and 402 deletions.
30 changes: 13 additions & 17 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -575,33 +575,29 @@ func (c *Client) SetAuthScheme(scheme string) *Client {
return c
}

// SetDigestAuth method sets the Digest Access auth scheme for the client. If a server responds with 401 and sends
// a Digest challenge in the WWW-Authenticate header, requests will be resent with the appropriate Authorization header.
// SetDigestAuth method sets the Digest Auth transport with provided credentials in the client.
// If a server responds with 401 and sends a Digest challenge in the header `WWW-Authenticate`,
// the request will be resent with the appropriate digest `Authorization` header.
//
// For Example: To set the Digest scheme with user "Mufasa" and password "Circle Of Life"
//
// client.SetDigestAuth("Mufasa", "Circle Of Life")
//
// Information about Digest Access Authentication can be found in [RFC 7616].
//
// See [Request.SetDigestAuth].
// NOTE:
// - On the QOP `auth-int` scenario, the request body is read into memory to
// compute the body hash that consumes additional memory usage.
// - It is recommended to create a dedicated client instance for digest auth,
// as it does digest auth for all the requests raised by the client.
//
// [RFC 7616]: https://datatracker.ietf.org/doc/html/rfc7616
func (c *Client) SetDigestAuth(username, password string) *Client {
c.lock.Lock()
oldTransport := c.httpClient.Transport
c.lock.Unlock()
c.AddRequestMiddleware(func(c *Client, _ *Request) error {
c.httpClient.Transport = &digestTransport{
credentials: credentials{username, password},
transport: oldTransport,
}
return nil
})
c.AddResponseMiddleware(func(c *Client, _ *Response) error {
c.httpClient.Transport = oldTransport
return nil
})
dt := &digestTransport{
credentials: &credentials{username, password},
transport: c.Transport(),
}
c.SetTransport(dt)
return c
}

Expand Down
73 changes: 0 additions & 73 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,79 +90,6 @@ func TestClientAuthScheme(t *testing.T) {

}

func TestClientDigestAuth(t *testing.T) {
conf := defaultDigestServerConf()
ts := createDigestServer(t, conf)
defer ts.Close()

c := dcnl().
SetBaseURL(ts.URL+"/").
SetDigestAuth(conf.username, conf.password)

resp, err := c.R().
SetResult(&AuthSuccess{}).
Get(conf.uri)
assertError(t, err)
assertEqual(t, http.StatusOK, resp.StatusCode())

t.Logf("Result Success: %q", resp.Result().(*AuthSuccess))
logResponse(t, resp)
}

func TestClientDigestSession(t *testing.T) {
conf := defaultDigestServerConf()
conf.algo = "MD5-sess"
conf.qop = "auth, auth-int"
ts := createDigestServer(t, conf)
defer ts.Close()

c := dcnl().
SetBaseURL(ts.URL+"/").
SetDigestAuth(conf.username, conf.password)

resp, err := c.R().
SetResult(&AuthSuccess{}).
Get(conf.uri)
assertError(t, err)
assertEqual(t, http.StatusOK, resp.StatusCode())

t.Logf("Result Success: %q", resp.Result().(*AuthSuccess))
logResponse(t, resp)
}

func TestClientDigestErrors(t *testing.T) {
type test struct {
mutateConf func(*digestServerConfig)
expect error
}
tests := []test{
{mutateConf: func(c *digestServerConfig) { c.algo = "BAD_ALGO" }, expect: ErrDigestAlgNotSupported},
{mutateConf: func(c *digestServerConfig) { c.qop = "bad-qop" }, expect: ErrDigestQopNotSupported},
{mutateConf: func(c *digestServerConfig) { c.qop = "" }, expect: ErrDigestNoQop},
{mutateConf: func(c *digestServerConfig) { c.charset = "utf-16" }, expect: ErrDigestCharset},
{mutateConf: func(c *digestServerConfig) { c.uri = "/bad" }, expect: ErrDigestBadChallenge},
{mutateConf: func(c *digestServerConfig) { c.uri = "/unknown_param" }, expect: ErrDigestBadChallenge},
{mutateConf: func(c *digestServerConfig) { c.uri = "/missing_value" }, expect: ErrDigestBadChallenge},
{mutateConf: func(c *digestServerConfig) { c.uri = "/unclosed_quote" }, expect: ErrDigestBadChallenge},
{mutateConf: func(c *digestServerConfig) { c.uri = "/no_challenge" }, expect: ErrDigestBadChallenge},
{mutateConf: func(c *digestServerConfig) { c.uri = "/status_500" }, expect: nil},
}

for _, tc := range tests {
conf := defaultDigestServerConf()
tc.mutateConf(conf)
ts := createDigestServer(t, conf)

c := dcnl().
SetBaseURL(ts.URL+"/").
SetDigestAuth(conf.username, conf.password)

_, err := c.R().Get(conf.uri)
assertErrorIs(t, tc.expect, err)
ts.Close()
}
}

func TestClientResponseMiddleware(t *testing.T) {
ts := createGenericServer(t)
defer ts.Close()
Expand Down
Loading

0 comments on commit cda801e

Please sign in to comment.