mirror of
https://github.com/kerberos-io/onvif.git
synced 2024-05-30 09:30:57 +08:00
112 lines
2.9 KiB
Go
112 lines
2.9 KiB
Go
package onvif
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// DigestClient represents an HTTP client used for making requests authenticated
|
|
// with http digest authentication.
|
|
type DigestClient struct {
|
|
client *http.Client
|
|
username string
|
|
password string
|
|
snonce string
|
|
realm string
|
|
qop string
|
|
nonceCount uint32
|
|
}
|
|
|
|
// NewDigestClient returns a DigestClient that wraps a given standard library http Client with the given username and password
|
|
func NewDigestClient(stdClient *http.Client, username string, password string) *DigestClient {
|
|
return &DigestClient{
|
|
client: stdClient,
|
|
username: username,
|
|
password: password,
|
|
}
|
|
}
|
|
|
|
func (dc *DigestClient) Do(httpMethod string, endpoint string, soap string) (*http.Response, error) {
|
|
req, err := createHttpRequest(httpMethod, endpoint, soap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if dc.snonce != "" {
|
|
req.Header.Set("Authorization", dc.getDigestAuth(req.Method, req.URL.String()))
|
|
}
|
|
|
|
// Attempt the request using the underlying client
|
|
resp, err := dc.client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusUnauthorized {
|
|
return resp, nil
|
|
}
|
|
|
|
dc.getDigestParts(resp)
|
|
// We will need to return the response from another request, so defer a close on this one
|
|
defer resp.Body.Close()
|
|
|
|
req, err = createHttpRequest(httpMethod, endpoint, soap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header.Set("Authorization", dc.getDigestAuth(req.Method, req.URL.String()))
|
|
|
|
authedResp, err := dc.client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return authedResp, nil
|
|
}
|
|
|
|
func (dc *DigestClient) getDigestParts(resp *http.Response) {
|
|
result := map[string]string{}
|
|
authHeader := resp.Header.Get("WWW-Authenticate")
|
|
if len(authHeader) > 0 {
|
|
wantedHeaders := []string{"nonce", "realm", "qop"}
|
|
responseHeaders := strings.Split(authHeader, ",")
|
|
for _, r := range responseHeaders {
|
|
for _, w := range wantedHeaders {
|
|
if strings.Contains(r, w) {
|
|
result[w] = strings.Split(r, `"`)[1]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
dc.snonce = result["nonce"]
|
|
dc.realm = result["realm"]
|
|
dc.qop = result["qop"]
|
|
dc.nonceCount = 0
|
|
}
|
|
|
|
func getMD5(text string) string {
|
|
hasher := md5.New()
|
|
hasher.Write([]byte(text))
|
|
return hex.EncodeToString(hasher.Sum(nil))
|
|
}
|
|
|
|
func getCnonce() string {
|
|
b := make([]byte, 8)
|
|
io.ReadFull(rand.Reader, b)
|
|
return fmt.Sprintf("%x", b)[:16]
|
|
}
|
|
|
|
func (dc *DigestClient) getDigestAuth(method string, uri string) string {
|
|
ha1 := getMD5(dc.username + ":" + dc.realm + ":" + dc.password)
|
|
ha2 := getMD5(method + ":" + uri)
|
|
cnonce := getCnonce()
|
|
dc.nonceCount++
|
|
response := getMD5(fmt.Sprintf("%s:%s:%v:%s:%s:%s", ha1, dc.snonce, dc.nonceCount, cnonce, dc.qop, ha2))
|
|
authorization := fmt.Sprintf(`Digest username="%s", realm="%s", nonce="%s", uri="%s", cnonce="%s", nc="%v", qop="%s", response="%s"`,
|
|
dc.username, dc.realm, dc.snonce, uri, cnonce, dc.nonceCount, dc.qop, response)
|
|
return authorization
|
|
}
|