119 lines · 3.8 KB · Go
RawDownload
package auth import (	"context"	"encoding/base64"	"net/http"	"strings" 	auth "github.com/abbot/go-http-auth"	"github.com/rclone/rclone/fs"	httplib "github.com/rclone/rclone/lib/http") // parseAuthorization parses the Authorization header into user, pass// it returns a boolean as to whether the parse was successfulfunc parseAuthorization(r *http.Request) (user, pass string, ok bool) {	authHeader := r.Header.Get("Authorization")	if authHeader != "" {		s := strings.SplitN(authHeader, " ", 2)		if len(s) == 2 && s[0] == "Basic" {			b, err := base64.StdEncoding.DecodeString(s[1])			if err == nil {				parts := strings.SplitN(string(b), ":", 2)				user = parts[0]				if len(parts) > 1 {					pass = parts[1]					ok = true				}			}		}	}	return} type contextUserType struct{} // ContextUserKey is a simple context key for storing the username of the requestvar ContextUserKey = &contextUserType{} type contextAuthType struct{} // ContextAuthKey is a simple context key for storing info returned by CustomAuthFnvar ContextAuthKey = &contextAuthType{} // LoggedBasicAuth extends BasicAuth to include access loggingtype LoggedBasicAuth struct {	auth.BasicAuth} // CheckAuth extends BasicAuth.CheckAuth to emit a log entry for unauthorised requestsfunc (a *LoggedBasicAuth) CheckAuth(r *http.Request) string {	username := a.BasicAuth.CheckAuth(r)	if username == "" {		user, _, _ := parseAuthorization(r)		fs.Infof(r.URL.Path, "%s: Unauthorized request from %s", r.RemoteAddr, user)	}	return username} // NewLoggedBasicAuthenticator instantiates a new instance of LoggedBasicAuthenticatorfunc NewLoggedBasicAuthenticator(realm string, secrets auth.SecretProvider) *LoggedBasicAuth {	return &LoggedBasicAuth{BasicAuth: auth.BasicAuth{Realm: realm, Secrets: secrets}}} // Helper to generate required interface for middlewarefunc basicAuth(authenticator *LoggedBasicAuth) httplib.Middleware {	return func(next http.Handler) http.Handler {		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {			if username := authenticator.CheckAuth(r); username == "" {				authenticator.RequireAuth(w, r)			} else {				r = r.WithContext(context.WithValue(r.Context(), ContextUserKey, username))				next.ServeHTTP(w, r)			}		})	}} // HtPasswdAuth instantiates middleware that authenticates against the passed htpasswd filefunc HtPasswdAuth(path, realm string) httplib.Middleware {	fs.Infof(nil, "Using %q as htpasswd storage", path)	secretProvider := auth.HtpasswdFileProvider(path)	authenticator := NewLoggedBasicAuthenticator(realm, secretProvider)	return basicAuth(authenticator)} // SingleAuth instantiates middleware that authenticates for a single userfunc SingleAuth(user, pass, realm, salt string) httplib.Middleware {	fs.Infof(nil, "Using --user %s --pass XXXX as authenticated user", user)	pass = string(auth.MD5Crypt([]byte(pass), []byte(salt), []byte("$1$")))	secretProvider := func(u, r string) string {		if user == u {			return pass		}		return ""	}	authenticator := NewLoggedBasicAuthenticator(realm, secretProvider)	return basicAuth(authenticator)} // CustomAuth instantiates middleware that authenticates using a custom functionfunc CustomAuth(fn CustomAuthFn, realm string) httplib.Middleware {	return func(next http.Handler) http.Handler {		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {			user, pass, ok := parseAuthorization(r)			if ok {				value, err := fn(user, pass)				if err != nil {					fs.Infof(r.URL.Path, "%s: Auth failed from %s: %v", r.RemoteAddr, user, err)					auth.NewBasicAuthenticator(realm, func(user, realm string) string { return "" }).RequireAuth(w, r) //Reuse BasicAuth error reporting					return				}				if value != nil {					r = r.WithContext(context.WithValue(r.Context(), ContextAuthKey, value))				}			}		})	}}