mirror of
https://github.com/taigrr/wtf
synced 2025-01-18 04:03:14 -08:00
188 lines
5.5 KiB
Go
188 lines
5.5 KiB
Go
package gerrit
|
|
|
|
import (
|
|
"crypto/md5" // nolint: gosec
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
// ErrWWWAuthenticateHeaderMissing is returned by digestAuthHeader when the WWW-Authenticate header is missing
|
|
ErrWWWAuthenticateHeaderMissing = errors.New("WWW-Authenticate header is missing")
|
|
|
|
// ErrWWWAuthenticateHeaderInvalid is returned by digestAuthHeader when the WWW-Authenticate invalid
|
|
ErrWWWAuthenticateHeaderInvalid = errors.New("WWW-Authenticate header is invalid")
|
|
|
|
// ErrWWWAuthenticateHeaderNotDigest is returned by digestAuthHeader when the WWW-Authenticate header is not 'Digest'
|
|
ErrWWWAuthenticateHeaderNotDigest = errors.New("WWW-Authenticate header type is not Digest")
|
|
)
|
|
|
|
const (
|
|
// HTTP Basic Authentication
|
|
authTypeBasic = 1
|
|
// HTTP Digest Authentication
|
|
authTypeDigest = 2
|
|
// HTTP Cookie Authentication
|
|
authTypeCookie = 3
|
|
)
|
|
|
|
// AuthenticationService contains Authentication related functions.
|
|
//
|
|
// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api.html#authentication
|
|
type AuthenticationService struct {
|
|
client *Client
|
|
|
|
// Storage for authentication
|
|
// Username or name of cookie
|
|
name string
|
|
// Password or value of cookie
|
|
secret string
|
|
authType int
|
|
}
|
|
|
|
// SetBasicAuth sets basic parameters for HTTP Basic auth
|
|
func (s *AuthenticationService) SetBasicAuth(username, password string) {
|
|
s.name = username
|
|
s.secret = password
|
|
s.authType = authTypeBasic
|
|
}
|
|
|
|
// SetDigestAuth sets digest parameters for HTTP Digest auth.
|
|
func (s *AuthenticationService) SetDigestAuth(username, password string) {
|
|
s.name = username
|
|
s.secret = password
|
|
s.authType = authTypeDigest
|
|
}
|
|
|
|
// digestAuthHeader is called by gerrit.Client.Do in the event the server
|
|
// returns 401 Unauthorized and authType was set to authTypeDigest. The
|
|
// resulting string is used to set the Authorization header before retrying
|
|
// the request.
|
|
func (s *AuthenticationService) digestAuthHeader(response *http.Response) (string, error) {
|
|
authenticateHeader := response.Header.Get("WWW-Authenticate")
|
|
if authenticateHeader == "" {
|
|
return "", ErrWWWAuthenticateHeaderMissing
|
|
}
|
|
|
|
split := strings.SplitN(authenticateHeader, " ", 2)
|
|
if len(split) != 2 {
|
|
return "", ErrWWWAuthenticateHeaderInvalid
|
|
}
|
|
|
|
if split[0] != "Digest" {
|
|
return "", ErrWWWAuthenticateHeaderNotDigest
|
|
}
|
|
|
|
// Iterate over all the fields from the WWW-Authenticate header
|
|
// and create a map of keys and values.
|
|
authenticate := map[string]string{}
|
|
for _, value := range strings.Split(split[1], ",") {
|
|
kv := strings.SplitN(value, "=", 2)
|
|
if len(kv) != 2 {
|
|
continue
|
|
}
|
|
|
|
key := strings.Trim(strings.Trim(kv[0], " "), "\"")
|
|
value := strings.Trim(strings.Trim(kv[1], " "), "\"")
|
|
authenticate[key] = value
|
|
}
|
|
|
|
// Gerrit usually responds without providing the algorithm. According
|
|
// to RFC2617 if no algorithm is provided then the default is to use
|
|
// MD5. At the time this code was implemented Gerrit did not appear
|
|
// to support other algorithms or provide a means of changing the
|
|
// algorithm.
|
|
if value, ok := authenticate["algorithm"]; ok {
|
|
if value != "MD5" {
|
|
return "", fmt.Errorf(
|
|
"algorithm not implemented: %s", value)
|
|
}
|
|
}
|
|
|
|
realmHeader := authenticate["realm"]
|
|
qopHeader := authenticate["qop"]
|
|
nonceHeader := authenticate["nonce"]
|
|
|
|
// If the server does not inform us what the uri is supposed
|
|
// to be then use the last requests's uri instead.
|
|
if _, ok := authenticate["uri"]; !ok {
|
|
authenticate["uri"] = response.Request.URL.Path
|
|
}
|
|
|
|
uriHeader := authenticate["uri"]
|
|
|
|
// A1
|
|
h := md5.New() // nolint: gosec
|
|
A1 := fmt.Sprintf("%s:%s:%s", s.name, realmHeader, s.secret)
|
|
if _, err := io.WriteString(h, A1); err != nil {
|
|
return "", err
|
|
}
|
|
HA1 := fmt.Sprintf("%x", h.Sum(nil))
|
|
|
|
// A2
|
|
h = md5.New() // nolint: gosec
|
|
A2 := fmt.Sprintf("%s:%s", response.Request.Method, uriHeader)
|
|
if _, err := io.WriteString(h, A2); err != nil {
|
|
return "", err
|
|
}
|
|
HA2 := fmt.Sprintf("%x", h.Sum(nil))
|
|
|
|
k := make([]byte, 12)
|
|
for bytes := 0; bytes < len(k); {
|
|
n, err := rand.Read(k[bytes:])
|
|
if err != nil {
|
|
return "", fmt.Errorf("cnonce generation failed: %s", err)
|
|
}
|
|
bytes += n
|
|
}
|
|
cnonce := base64.StdEncoding.EncodeToString(k)
|
|
digest := md5.New() // nolint: gosec
|
|
if _, err := digest.Write([]byte(strings.Join([]string{HA1, nonceHeader, "00000001", cnonce, qopHeader, HA2}, ":"))); err != nil {
|
|
return "", err
|
|
}
|
|
responseField := fmt.Sprintf("%x", digest.Sum(nil))
|
|
|
|
return fmt.Sprintf(
|
|
`Digest username="%s", realm="%s", nonce="%s", uri="%s", cnonce="%s", nc=00000001, qop=%s, response="%s"`,
|
|
s.name, realmHeader, nonceHeader, uriHeader, cnonce, qopHeader, responseField), nil
|
|
}
|
|
|
|
// SetCookieAuth sets basic parameters for HTTP Cookie
|
|
func (s *AuthenticationService) SetCookieAuth(name, value string) {
|
|
s.name = name
|
|
s.secret = value
|
|
s.authType = authTypeCookie
|
|
}
|
|
|
|
// HasBasicAuth checks if the auth type is HTTP Basic auth
|
|
func (s *AuthenticationService) HasBasicAuth() bool {
|
|
return s.authType == authTypeBasic
|
|
}
|
|
|
|
// HasDigestAuth checks if the auth type is HTTP Digest based
|
|
func (s *AuthenticationService) HasDigestAuth() bool {
|
|
return s.authType == authTypeDigest
|
|
}
|
|
|
|
// HasCookieAuth checks if the auth type is HTTP Cookie based
|
|
func (s *AuthenticationService) HasCookieAuth() bool {
|
|
return s.authType == authTypeCookie
|
|
}
|
|
|
|
// HasAuth checks if an auth type is used
|
|
func (s *AuthenticationService) HasAuth() bool {
|
|
return s.authType > 0
|
|
}
|
|
|
|
// ResetAuth resets all former authentification settings
|
|
func (s *AuthenticationService) ResetAuth() {
|
|
s.name = ""
|
|
s.secret = ""
|
|
s.authType = 0
|
|
}
|