github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/lib/http/middleware.go (about)

     1  package http
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"fmt"
     7  	"net/http"
     8  	"strings"
     9  	"sync"
    10  
    11  	goauth "github.com/abbot/go-http-auth"
    12  	"github.com/rclone/rclone/fs"
    13  )
    14  
    15  // parseAuthorization parses the Authorization header into user, pass
    16  // it returns a boolean as to whether the parse was successful
    17  func parseAuthorization(r *http.Request) (user, pass string, ok bool) {
    18  	authHeader := r.Header.Get("Authorization")
    19  	if authHeader != "" {
    20  		s := strings.SplitN(authHeader, " ", 2)
    21  		if len(s) == 2 && s[0] == "Basic" {
    22  			b, err := base64.StdEncoding.DecodeString(s[1])
    23  			if err == nil {
    24  				parts := strings.SplitN(string(b), ":", 2)
    25  				user = parts[0]
    26  				if len(parts) > 1 {
    27  					pass = parts[1]
    28  					ok = true
    29  				}
    30  			}
    31  		}
    32  	}
    33  	return
    34  }
    35  
    36  // LoggedBasicAuth simply wraps the goauth.BasicAuth struct
    37  type LoggedBasicAuth struct {
    38  	goauth.BasicAuth
    39  }
    40  
    41  // CheckAuth extends BasicAuth.CheckAuth to emit a log entry for unauthorised requests
    42  func (a *LoggedBasicAuth) CheckAuth(r *http.Request) string {
    43  	username := a.BasicAuth.CheckAuth(r)
    44  	if username == "" {
    45  		user, _, _ := parseAuthorization(r)
    46  		fs.Infof(r.URL.Path, "%s: Unauthorized request from %s", r.RemoteAddr, user)
    47  	}
    48  	return username
    49  }
    50  
    51  // NewLoggedBasicAuthenticator instantiates a new instance of LoggedBasicAuthenticator
    52  func NewLoggedBasicAuthenticator(realm string, secrets goauth.SecretProvider) *LoggedBasicAuth {
    53  	return &LoggedBasicAuth{BasicAuth: goauth.BasicAuth{Realm: realm, Secrets: secrets}}
    54  }
    55  
    56  // Helper to generate required interface for middleware
    57  func basicAuth(authenticator *LoggedBasicAuth) func(next http.Handler) http.Handler {
    58  	return func(next http.Handler) http.Handler {
    59  		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    60  			// skip auth for unix socket
    61  			if IsUnixSocket(r) {
    62  				next.ServeHTTP(w, r)
    63  				return
    64  			}
    65  			// skip auth for CORS preflight
    66  			if r.Method == "OPTIONS" {
    67  				next.ServeHTTP(w, r)
    68  				return
    69  			}
    70  
    71  			username := authenticator.CheckAuth(r)
    72  			if username == "" {
    73  				authenticator.RequireAuth(w, r)
    74  				return
    75  			}
    76  			ctx := context.WithValue(r.Context(), ctxKeyUser, username)
    77  			next.ServeHTTP(w, r.WithContext(ctx))
    78  		})
    79  	}
    80  }
    81  
    82  // MiddlewareAuthCertificateUser instantiates middleware that extracts the authenticated user via client certificate common name
    83  func MiddlewareAuthCertificateUser() Middleware {
    84  	return func(next http.Handler) http.Handler {
    85  		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    86  			for _, cert := range r.TLS.PeerCertificates {
    87  				if cert.Subject.CommonName != "" {
    88  					r = r.WithContext(context.WithValue(r.Context(), ctxKeyUser, cert.Subject.CommonName))
    89  					next.ServeHTTP(w, r)
    90  					return
    91  				}
    92  			}
    93  			code := http.StatusUnauthorized
    94  			w.Header().Set("Content-Type", "text/plain")
    95  			http.Error(w, http.StatusText(code), code)
    96  		})
    97  	}
    98  }
    99  
   100  // MiddlewareAuthHtpasswd instantiates middleware that authenticates against the passed htpasswd file
   101  func MiddlewareAuthHtpasswd(path, realm string) Middleware {
   102  	fs.Infof(nil, "Using %q as htpasswd storage", path)
   103  	secretProvider := goauth.HtpasswdFileProvider(path)
   104  	authenticator := NewLoggedBasicAuthenticator(realm, secretProvider)
   105  	return basicAuth(authenticator)
   106  }
   107  
   108  // MiddlewareAuthBasic instantiates middleware that authenticates for a single user
   109  func MiddlewareAuthBasic(user, pass, realm, salt string) Middleware {
   110  	fs.Infof(nil, "Using --user %s --pass XXXX as authenticated user", user)
   111  	pass = string(goauth.MD5Crypt([]byte(pass), []byte(salt), []byte("$1$")))
   112  	secretProvider := func(u, r string) string {
   113  		if user == u {
   114  			return pass
   115  		}
   116  		return ""
   117  	}
   118  	authenticator := NewLoggedBasicAuthenticator(realm, secretProvider)
   119  	return basicAuth(authenticator)
   120  }
   121  
   122  // MiddlewareAuthCustom instantiates middleware that authenticates using a custom function
   123  func MiddlewareAuthCustom(fn CustomAuthFn, realm string, userFromContext bool) Middleware {
   124  	return func(next http.Handler) http.Handler {
   125  		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   126  			// skip auth for unix socket
   127  			if IsUnixSocket(r) {
   128  				next.ServeHTTP(w, r)
   129  				return
   130  			}
   131  			// skip auth for CORS preflight
   132  			if r.Method == "OPTIONS" {
   133  				next.ServeHTTP(w, r)
   134  				return
   135  			}
   136  
   137  			user, pass, ok := parseAuthorization(r)
   138  			if !ok && userFromContext {
   139  				user, ok = CtxGetUser(r.Context())
   140  			}
   141  
   142  			if !ok {
   143  				code := http.StatusUnauthorized
   144  				w.Header().Set("Content-Type", "text/plain")
   145  				w.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm=%q, charset="UTF-8"`, realm))
   146  				http.Error(w, http.StatusText(code), code)
   147  				return
   148  			}
   149  
   150  			value, err := fn(user, pass)
   151  			if err != nil {
   152  				fs.Infof(r.URL.Path, "%s: Auth failed from %s: %v", r.RemoteAddr, user, err)
   153  				goauth.NewBasicAuthenticator(realm, func(user, realm string) string { return "" }).RequireAuth(w, r) //Reuse BasicAuth error reporting
   154  				return
   155  			}
   156  
   157  			if value != nil {
   158  				r = r.WithContext(context.WithValue(r.Context(), ctxKeyAuth, value))
   159  			}
   160  
   161  			next.ServeHTTP(w, r)
   162  		})
   163  	}
   164  }
   165  
   166  var onlyOnceWarningAllowOrigin sync.Once
   167  
   168  // MiddlewareCORS instantiates middleware that handles basic CORS protections for rcd
   169  func MiddlewareCORS(allowOrigin string) Middleware {
   170  	onlyOnceWarningAllowOrigin.Do(func() {
   171  		if allowOrigin == "*" {
   172  			fs.Logf(nil, "Warning: Allow origin set to *. This can cause serious security problems.")
   173  		}
   174  	})
   175  
   176  	return func(next http.Handler) http.Handler {
   177  		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   178  			// skip cors for unix sockets
   179  			if IsUnixSocket(r) {
   180  				next.ServeHTTP(w, r)
   181  				return
   182  			}
   183  
   184  			if allowOrigin != "" {
   185  				w.Header().Add("Access-Control-Allow-Origin", allowOrigin)
   186  				w.Header().Add("Access-Control-Allow-Headers", "authorization, Content-Type")
   187  				w.Header().Add("Access-Control-Allow-Methods", "COPY, DELETE, GET, HEAD, LOCK, MKCOL, MOVE, OPTIONS, POST, PROPFIND, PROPPATCH, PUT, TRACE, UNLOCK")
   188  			}
   189  
   190  			next.ServeHTTP(w, r)
   191  		})
   192  	}
   193  }
   194  
   195  // MiddlewareStripPrefix instantiates middleware that removes the BaseURL from the path
   196  func MiddlewareStripPrefix(prefix string) Middleware {
   197  	return func(next http.Handler) http.Handler {
   198  		stripPrefixHandler := http.StripPrefix(prefix, next)
   199  		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   200  			// Allow OPTIONS on the root only
   201  			if r.URL.Path == "/" && r.Method == "OPTIONS" {
   202  				next.ServeHTTP(w, r)
   203  				return
   204  			}
   205  			stripPrefixHandler.ServeHTTP(w, r)
   206  		})
   207  	}
   208  }