2021-04-20 04:35:45 +00:00
|
|
|
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 successful
|
|
|
|
func 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 request
|
|
|
|
var ContextUserKey = &contextUserType{}
|
|
|
|
|
|
|
|
type contextAuthType struct{}
|
|
|
|
|
|
|
|
// ContextAuthKey is a simple context key for storing info returned by CustomAuthFn
|
|
|
|
var ContextAuthKey = &contextAuthType{}
|
|
|
|
|
|
|
|
// LoggedBasicAuth extends BasicAuth to include access logging
|
|
|
|
type LoggedBasicAuth struct {
|
|
|
|
auth.BasicAuth
|
|
|
|
}
|
|
|
|
|
|
|
|
// CheckAuth extends BasicAuth.CheckAuth to emit a log entry for unauthorised requests
|
|
|
|
func (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 LoggedBasicAuthenticator
|
|
|
|
func NewLoggedBasicAuthenticator(realm string, secrets auth.SecretProvider) *LoggedBasicAuth {
|
|
|
|
return &LoggedBasicAuth{BasicAuth: auth.BasicAuth{Realm: realm, Secrets: secrets}}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Helper to generate required interface for middleware
|
|
|
|
func 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 file
|
|
|
|
func 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 user
|
2021-10-12 17:32:46 +00:00
|
|
|
func SingleAuth(user, pass, realm, salt string) httplib.Middleware {
|
2021-04-20 04:35:45 +00:00
|
|
|
fs.Infof(nil, "Using --user %s --pass XXXX as authenticated user", user)
|
2021-10-12 17:32:46 +00:00
|
|
|
pass = string(auth.MD5Crypt([]byte(pass), []byte(salt), []byte("$1$")))
|
2021-10-12 17:18:48 +00:00
|
|
|
secretProvider := func(u, r string) string {
|
|
|
|
if user == u {
|
2021-04-20 04:35:45 +00:00
|
|
|
return pass
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
authenticator := NewLoggedBasicAuthenticator(realm, secretProvider)
|
|
|
|
return basicAuth(authenticator)
|
|
|
|
}
|
|
|
|
|
|
|
|
// CustomAuth instantiates middleware that authenticates using a custom function
|
|
|
|
func 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))
|
|
|
|
}
|
2022-06-24 21:13:12 +00:00
|
|
|
next.ServeHTTP(w, r)
|
2021-04-20 04:35:45 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|