From c3373a6f719523485ab4b7caca96a1b0a4fc83e3 Mon Sep 17 00:00:00 2001
From: zhangzengfei <zhangzengfei@smartai.com>
Date: 星期一, 27 五月 2024 11:09:22 +0800
Subject: [PATCH] debug

---
 pkg/auth/digest.go |  289 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 289 insertions(+), 0 deletions(-)

diff --git a/pkg/auth/digest.go b/pkg/auth/digest.go
new file mode 100644
index 0000000..ce0b2ba
--- /dev/null
+++ b/pkg/auth/digest.go
@@ -0,0 +1,289 @@
+package auth
+
+import (
+	"context"
+	"crypto/subtle"
+	"fmt"
+	"net/http"
+	"net/url"
+	"sort"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+)
+
+type digestClient struct {
+	nc       uint64
+	lastSeen int64
+}
+
+// DigestAuth is an authenticator implementation for 'Digest' HTTP Authentication scheme (RFC 7616).
+//
+// Note: this implementation was written following now deprecated RFC
+// 2617, and supports only MD5 algorithm.
+//
+// TODO: Add support for SHA-256 and SHA-512/256 algorithms.
+type DigestAuth struct {
+	Realm            string
+	Opaque           string
+	Secrets          SecretProvider
+	PlainTextSecrets bool
+	IgnoreNonceCount bool
+	// Headers used by authenticator. Set to ProxyHeaders to use with
+	// proxy server. When nil, NormalHeaders are used.
+	Headers *Headers
+
+	/*
+	   Approximate size of Client's Cache. When actual number of
+	   tracked client nonces exceeds
+	   ClientCacheSize+ClientCacheTolerance, ClientCacheTolerance*2
+	   older entries are purged.
+	*/
+	ClientCacheSize      int
+	ClientCacheTolerance int
+
+	clients map[string]*digestClient
+	mutex   sync.RWMutex
+}
+
+// check that DigestAuth implements AuthenticatorInterface
+var _ = (AuthenticatorInterface)((*DigestAuth)(nil))
+
+type digestCacheEntry struct {
+	nonce    string
+	lastSeen int64
+}
+
+type digestCache []digestCacheEntry
+
+func (c digestCache) Less(i, j int) bool {
+	return c[i].lastSeen < c[j].lastSeen
+}
+
+func (c digestCache) Len() int {
+	return len(c)
+}
+
+func (c digestCache) Swap(i, j int) {
+	c[i], c[j] = c[j], c[i]
+}
+
+// Purge removes count oldest entries from DigestAuth.clients
+func (da *DigestAuth) Purge(count int) {
+	da.mutex.Lock()
+	da.purgeLocked(count)
+	da.mutex.Unlock()
+}
+
+func (da *DigestAuth) purgeLocked(count int) {
+	entries := make([]digestCacheEntry, 0, len(da.clients))
+	for nonce, client := range da.clients {
+		entries = append(entries, digestCacheEntry{nonce, client.lastSeen})
+	}
+	cache := digestCache(entries)
+	sort.Sort(cache)
+	for _, client := range cache[:count] {
+		delete(da.clients, client.nonce)
+	}
+}
+
+// RequireAuth is an http.HandlerFunc which initiates the
+// authentication process (or requires reauthentication).
+func (da *DigestAuth) RequireAuth(w http.ResponseWriter, r *http.Request) {
+	da.mutex.RLock()
+	clientsLen := len(da.clients)
+	da.mutex.RUnlock()
+
+	if clientsLen > da.ClientCacheSize+da.ClientCacheTolerance {
+		da.Purge(da.ClientCacheTolerance * 2)
+	}
+	nonce := RandomKey()
+
+	da.mutex.Lock()
+	da.clients[nonce] = &digestClient{nc: 0, lastSeen: time.Now().UnixNano()}
+	da.mutex.Unlock()
+
+	da.mutex.RLock()
+	w.Header().Set(contentType, da.Headers.V().UnauthContentType)
+	w.Header().Set(da.Headers.V().Authenticate,
+		fmt.Sprintf(`Digest realm="%s", nonce="%s", opaque="%s", algorithm=MD5, qop="auth"`,
+			da.Realm, nonce, da.Opaque))
+	w.WriteHeader(da.Headers.V().UnauthCode)
+	w.Write([]byte(da.Headers.V().UnauthResponse))
+	da.mutex.RUnlock()
+}
+
+// DigestAuthParams parses 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(authorization string) map[string]string {
+	s := strings.SplitN(authorization, " ", 2)
+	if len(s) != 2 || s[0] != "Digest" {
+		return nil
+	}
+	return ParsePairs(s[1])
+}
+
+// CheckAuth checks whether the request contains valid authentication
+// data. Returns a pair of username, authinfo, where username is the
+// name of the authenticated user or an empty string and authinfo is
+// the contents for the optional Authentication-Info response header.
+func (da *DigestAuth) CheckAuth(r *http.Request) (username string, authinfo *string) {
+	da.mutex.RLock()
+	defer da.mutex.RUnlock()
+	username = ""
+	authinfo = nil
+
+	auth := DigestAuthParams(r.Header.Get(da.Headers.V().Authorization))
+	if auth == nil {
+		return "", nil
+	}
+	// RFC2617 Section 3.2.1 specifies that unset value of algorithm in
+	// WWW-Authenticate Response header should be treated as
+	// "MD5". According to section 3.2.2 the "algorithm" value in
+	// subsequent Request Authorization header must be set to whatever
+	// was supplied in the WWW-Authenticate Response header. This
+	// implementation always returns an algorithm in WWW-Authenticate
+	// header, however there seems to be broken clients in the wild
+	// which do not set the algorithm. Assume the unset algorithm in
+	// Authorization header to be equal to MD5.
+	if _, ok := auth["algorithm"]; !ok {
+		auth["algorithm"] = "MD5"
+	}
+	if da.Opaque != auth["opaque"] || auth["algorithm"] != "MD5" || auth["qop"] != "auth" {
+		return "", nil
+	}
+
+	// Check if the requested URI matches auth header
+	if r.RequestURI != auth["uri"] {
+		// We allow auth["uri"] to be a full path prefix of request-uri
+		// for some reason lost in history, which is probably wrong, but
+		// used to be like that for quite some time
+		// (https://tools.ietf.org/html/rfc2617#section-3.2.2 explicitly
+		// says that auth["uri"] is the request-uri).
+		//
+		// TODO: make an option to allow only strict checking.
+		switch u, err := url.Parse(auth["uri"]); {
+		case err != nil:
+			return "", nil
+		case r.URL == nil:
+			return "", nil
+		case len(u.Path) > len(r.URL.Path):
+			return "", nil
+		case !strings.HasPrefix(r.URL.Path, u.Path):
+			return "", nil
+		}
+	}
+
+	HA1 := da.Secrets(auth["username"], da.Realm)
+
+	if da.PlainTextSecrets {
+		HA1 = H(auth["username"] + ":" + da.Realm + ":" + HA1)
+	}
+	HA2 := H(r.Method + ":" + auth["uri"])
+	KD := H(strings.Join([]string{HA1, auth["nonce"], auth["nc"], auth["cnonce"], auth["qop"], HA2}, ":"))
+
+	if subtle.ConstantTimeCompare([]byte(KD), []byte(auth["response"])) != 1 {
+		return "", nil
+	}
+
+	// At this point crypto checks are completed and validated.
+	// Now check if the session is valid.
+
+	nc, err := strconv.ParseUint(auth["nc"], 16, 64)
+	if err != nil {
+		return "", nil
+	}
+
+	client, ok := da.clients[auth["nonce"]]
+	if !ok {
+		return "", nil
+	}
+	if client.nc != 0 && client.nc >= nc && !da.IgnoreNonceCount {
+		return "", nil
+	}
+	client.nc = nc
+	client.lastSeen = time.Now().UnixNano()
+
+	respHA2 := H(":" + auth["uri"])
+	rspauth := H(strings.Join([]string{HA1, auth["nonce"], auth["nc"], auth["cnonce"], auth["qop"], respHA2}, ":"))
+
+	info := fmt.Sprintf(`qop="auth", rspauth="%s", cnonce="%s", nc="%s"`, rspauth, auth["cnonce"], auth["nc"])
+	return auth["username"], &info
+}
+
+// Default values for ClientCacheSize and ClientCacheTolerance for DigestAuth
+const (
+	DefaultClientCacheSize      = 1000
+	DefaultClientCacheTolerance = 100
+)
+
+// Wrap returns an http.HandlerFunc wraps AuthenticatedHandlerFunc
+// with this DigestAuth authentication checks. Once the request
+// contains valid credentials, it calls wrapped
+// AuthenticatedHandlerFunc.
+//
+// Deprecated: new code should use NewContext instead.
+func (da *DigestAuth) Wrap(wrapped AuthenticatedHandlerFunc) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		if username, authinfo := da.CheckAuth(r); username == "" {
+			da.RequireAuth(w, r)
+		} else {
+			ar := &AuthenticatedRequest{Request: *r, Username: username}
+			if authinfo != nil {
+				w.Header().Set(da.Headers.V().AuthInfo, *authinfo)
+			}
+			wrapped(w, ar)
+		}
+	}
+}
+
+// JustCheck returns a new http.HandlerFunc, which requires
+// DigestAuth to successfully authenticate a user before calling
+// wrapped http.HandlerFunc.
+//
+// Authenticated Username is passed as an extra
+// X-Authenticated-Username header to the wrapped HandlerFunc.
+func (da *DigestAuth) JustCheck(wrapped http.HandlerFunc) http.HandlerFunc {
+	return da.Wrap(func(w http.ResponseWriter, ar *AuthenticatedRequest) {
+		ar.Header.Set(AuthUsernameHeader, ar.Username)
+		wrapped(w, &ar.Request)
+	})
+}
+
+// NewContext returns a context carrying authentication information for the request.
+func (da *DigestAuth) NewContext(ctx context.Context, r *http.Request) context.Context {
+	username, authinfo := da.CheckAuth(r)
+	da.mutex.Lock()
+	defer da.mutex.Unlock()
+	info := &Info{Username: username, ResponseHeaders: make(http.Header)}
+	if username != "" {
+		info.Authenticated = true
+		info.ResponseHeaders.Set(da.Headers.V().AuthInfo, *authinfo)
+	} else {
+		// return back digest WWW-Authenticate header
+		if len(da.clients) > da.ClientCacheSize+da.ClientCacheTolerance {
+			da.purgeLocked(da.ClientCacheTolerance * 2)
+		}
+		nonce := RandomKey()
+		da.clients[nonce] = &digestClient{nc: 0, lastSeen: time.Now().UnixNano()}
+		info.ResponseHeaders.Set(da.Headers.V().Authenticate,
+			fmt.Sprintf(`Digest realm="%s", nonce="%s", opaque="%s", algorithm=MD5, qop="auth"`,
+				da.Realm, nonce, da.Opaque))
+	}
+	return context.WithValue(ctx, infoKey, info)
+}
+
+// NewDigestAuthenticator generates a new DigestAuth object
+func NewDigestAuthenticator(realm string, secrets SecretProvider) *DigestAuth {
+	da := &DigestAuth{
+		Opaque:               RandomKey(),
+		Realm:                realm,
+		Secrets:              secrets,
+		PlainTextSecrets:     true,
+		ClientCacheSize:      DefaultClientCacheSize,
+		ClientCacheTolerance: DefaultClientCacheTolerance,
+		clients:              map[string]*digestClient{}}
+	return da
+}

--
Gitblit v1.8.0