114 lines
2.8 KiB
Go
114 lines
2.8 KiB
Go
package auth
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
type myjar struct {
|
|
jar map[string][]*http.Cookie
|
|
}
|
|
|
|
func (p *myjar) SetCookies(u *url.URL, cookies []*http.Cookie) {
|
|
p.jar[u.Host] = cookies
|
|
}
|
|
|
|
func (p *myjar) Cookies(u *url.URL) []*http.Cookie {
|
|
return p.jar[u.Host]
|
|
}
|
|
|
|
func Auth(username string, password string, uri string) (bool, error) {
|
|
client := &http.Client{}
|
|
jar := &myjar{}
|
|
jar.jar = make(map[string][]*http.Cookie)
|
|
client.Jar = jar
|
|
var req *http.Request
|
|
var resp *http.Response
|
|
var err error
|
|
req, err = http.NewRequest("GET", uri, nil)
|
|
resp, err = client.Do(req)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if resp.StatusCode == 401 {
|
|
var authorization map[string]string = DigestAuthParams(resp)
|
|
realmHeader := authorization["realm"]
|
|
qopHeader := authorization["qop"]
|
|
nonceHeader := authorization["nonce"]
|
|
opaqueHeader := authorization["opaque"]
|
|
realm := realmHeader
|
|
// A1
|
|
h := md5.New()
|
|
A1 := fmt.Sprintf("%s:%s:%s", username, realm, password)
|
|
io.WriteString(h, A1)
|
|
HA1 := fmt.Sprintf("%x", h.Sum(nil))
|
|
|
|
// A2
|
|
h = md5.New()
|
|
A2 := fmt.Sprintf("GET:%s", "/auth")
|
|
io.WriteString(h, A2)
|
|
HA2 := fmt.Sprintf("%x", h.Sum(nil))
|
|
|
|
// response
|
|
cnonce := RandomKey()
|
|
response := H(strings.Join([]string{HA1, nonceHeader, "00000001", cnonce, qopHeader, HA2}, ":"))
|
|
|
|
// now make header
|
|
AuthHeader := fmt.Sprintf(`Digest username="%s", realm="%s", nonce="%s", uri="%s", cnonce="%s", nc=00000001, qop=%s, response="%s", opaque="%s", algorithm=MD5`,
|
|
username, realmHeader, nonceHeader, "/auth", cnonce, qopHeader, response, opaqueHeader)
|
|
req.Header.Set("Authorization", AuthHeader)
|
|
resp, err = client.Do(req)
|
|
} else {
|
|
return false, fmt.Errorf("response status code should have been 401, it was %v", resp.StatusCode)
|
|
}
|
|
return resp.StatusCode == 200, err
|
|
}
|
|
|
|
/*
|
|
Parse Authorization header from the http.Request. Returns a map of
|
|
auth parameters or nil if the header is not a valid parsable Digest
|
|
auth header.
|
|
*/
|
|
func DigestAuthParams(r *http.Response) map[string]string {
|
|
s := strings.SplitN(r.Header.Get("Www-Authenticate"), " ", 2)
|
|
if len(s) != 2 || s[0] != "Digest" {
|
|
return nil
|
|
}
|
|
|
|
result := map[string]string{}
|
|
for _, kv := range strings.Split(s[1], ",") {
|
|
parts := strings.SplitN(kv, "=", 2)
|
|
if len(parts) != 2 {
|
|
continue
|
|
}
|
|
result[strings.Trim(parts[0], "\" ")] = strings.Trim(parts[1], "\" ")
|
|
}
|
|
return result
|
|
}
|
|
func RandomKey() string {
|
|
k := make([]byte, 12)
|
|
for bytes := 0; bytes < len(k); {
|
|
n, err := rand.Read(k[bytes:])
|
|
if err != nil {
|
|
panic("rand.Read() failed")
|
|
}
|
|
bytes += n
|
|
}
|
|
return base64.StdEncoding.EncodeToString(k)
|
|
}
|
|
|
|
/*
|
|
H function for MD5 algorithm (returns a lower-case hex MD5 digest)
|
|
*/
|
|
func H(data string) string {
|
|
digest := md5.New()
|
|
digest.Write([]byte(data))
|
|
return fmt.Sprintf("%x", digest.Sum(nil))
|
|
}
|