File: basic.go

package info (click to toggle)
rclone 1.60.1%2Bdfsg-4
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 34,832 kB
  • sloc: sh: 957; xml: 857; python: 655; javascript: 612; makefile: 269; ansic: 101; php: 74
file content (120 lines) | stat: -rw-r--r-- 3,901 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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
func 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 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))
				}
				next.ServeHTTP(w, r)
			}
		})
	}
}