1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16
17
18 package auth
19
20 import (
21 "crypto/rand"
22 "crypto/subtle"
23 "errors"
24 "fmt"
25 "net/http"
26 "os"
27 "regexp"
28 "strings"
29 "sync"
30
31 "perkeep.org/internal/httputil"
32 )
33
34
35 type Operation int
36
37 const (
38 OpUpload Operation = 1 << iota
39 OpStat
40 OpGet
41 OpEnumerate
42 OpRemove
43 OpSign
44
45
46
47 OpDiscovery
48 OpRead = OpEnumerate | OpStat | OpGet | OpDiscovery
49 OpRW = OpUpload | OpEnumerate | OpStat | OpGet
50 OpVivify = OpUpload | OpStat | OpGet | OpDiscovery
51 OpAll = OpUpload | OpEnumerate | OpStat | OpRemove | OpGet | OpSign | OpDiscovery
52 )
53
54 const OmitAuthToken = "OmitAuthToken"
55
56 var (
57
58
59
60 modes []AuthMode
61 )
62
63
64
65 type AuthMode interface {
66
67
68 AllowedAccess(req *http.Request) Operation
69
70
71 AddAuthHeader(req *http.Request)
72 }
73
74
75
76 type UnauthorizedSender interface {
77
78
79 SendUnauthorized(http.ResponseWriter, *http.Request) (handled bool)
80 }
81
82 func FromEnv() (AuthMode, error) {
83 return FromConfig(os.Getenv("CAMLI_AUTH"))
84 }
85
86
87
88 type AuthConfigParser func(arg string) (AuthMode, error)
89
90 var authConstructor = map[string]AuthConfigParser{
91 "none": newNoneAuth,
92 "localhost": newLocalhostAuth,
93 "userpass": newUserPassAuth,
94 "token": NewTokenAuth,
95 "devauth": newDevAuth,
96 "basic": newBasicAuth,
97 "tailscale": newTailscaleAuth,
98 }
99
100
101 func RegisterAuth(name string, ctor AuthConfigParser) {
102 if _, dup := authConstructor[name]; dup {
103 panic("Dup registration of auth mode " + name)
104 }
105 authConstructor[name] = ctor
106 }
107
108 func newNoneAuth(string) (AuthMode, error) {
109 return None{}, nil
110 }
111
112 func newLocalhostAuth(string) (AuthMode, error) {
113 return Localhost{}, nil
114 }
115
116 func newDevAuth(pw string) (AuthMode, error) {
117
118 vp := "vivi" + pw
119 return &DevAuth{
120 Password: pw,
121 VivifyPass: &vp,
122 }, nil
123 }
124
125 func newUserPassAuth(arg string) (AuthMode, error) {
126 pieces := strings.Split(arg, ":")
127 if len(pieces) < 2 {
128 return nil, fmt.Errorf("Wrong userpass auth string; needs to be \"user:password\"")
129 }
130 username := pieces[0]
131 password := pieces[1]
132 mode := &UserPass{Username: username, Password: password}
133 for _, opt := range pieces[2:] {
134 switch {
135 case opt == "+localhost":
136 mode.OrLocalhost = true
137 case strings.HasPrefix(opt, "vivify="):
138
139 vp := strings.Replace(opt, "vivify=", "", -1)
140 mode.VivifyPass = &vp
141 default:
142 return nil, fmt.Errorf("Unknown userpass option %q", opt)
143 }
144 }
145 return mode, nil
146 }
147
148 func newBasicAuth(arg string) (AuthMode, error) {
149 pieces := strings.Split(arg, ":")
150 if len(pieces) != 2 {
151 return nil, fmt.Errorf("invalid basic auth syntax. got %q, want \"username:password\"", arg)
152 }
153 return NewBasicAuth(pieces[0], pieces[1]), nil
154 }
155
156
157
158 func NewBasicAuth(username, password string) AuthMode {
159 return &UserPass{
160 Username: username,
161 Password: password,
162 }
163 }
164
165
166
167 func NewTokenAuth(token string) (AuthMode, error) {
168 return &tokenAuth{token: token}, nil
169 }
170
171
172 var ErrNoAuth = errors.New("auth: no configured authentication")
173
174
175
176
177
178
179
180 func FromConfig(authConfig string) (AuthMode, error) {
181 if authConfig == "" {
182 return nil, ErrNoAuth
183 }
184 pieces := strings.SplitN(authConfig, ":", 2)
185 if len(pieces) < 1 {
186 return nil, fmt.Errorf("Invalid auth string: %q", authConfig)
187 }
188 authType := pieces[0]
189
190 if fn, ok := authConstructor[authType]; ok {
191 arg := ""
192 if len(pieces) == 2 {
193 arg = pieces[1]
194 }
195 return fn(arg)
196 }
197 return nil, fmt.Errorf("Unknown auth type: %q", authType)
198 }
199
200
201
202 func SetMode(m AuthMode) {
203 modes = []AuthMode{m}
204 }
205
206
207
208 func AddMode(am AuthMode) {
209 modes = append(modes, am)
210 }
211
212 type tokenAuth struct {
213 token string
214 }
215
216 func (t *tokenAuth) AllowedAccess(r *http.Request) Operation {
217 if authTokenHeaderMatches(r) {
218 return OpAll
219 }
220 if websocketTokenMatches(r) {
221 return OpAll
222 }
223 return 0
224 }
225
226 func (t *tokenAuth) AddAuthHeader(r *http.Request) {
227 r.Header.Set("Authorization", "Token "+t.token)
228 }
229
230
231
232
233
234
235 type UserPass struct {
236 Username, Password string
237 OrLocalhost bool
238
239
240
241 VivifyPass *string
242 }
243
244 func (up *UserPass) AllowedAccess(req *http.Request) Operation {
245 user, pass, err := httputil.BasicAuth(req)
246 if err == nil {
247 if subtle.ConstantTimeCompare([]byte(user), []byte(up.Username)) == 1 {
248 if subtle.ConstantTimeCompare([]byte(pass), []byte(up.Password)) == 1 {
249 return OpAll
250 }
251 if up.VivifyPass != nil && subtle.ConstantTimeCompare([]byte(pass), []byte(*up.VivifyPass)) == 1 {
252 return OpVivify
253 }
254 }
255 }
256
257 if authTokenHeaderMatches(req) {
258 return OpAll
259 }
260 if websocketTokenMatches(req) {
261 return OpAll
262 }
263 if up.OrLocalhost && httputil.IsLocalhost(req) {
264 return OpAll
265 }
266
267 return 0
268 }
269
270 func (up *UserPass) AddAuthHeader(req *http.Request) {
271 req.SetBasicAuth(up.Username, up.Password)
272 }
273
274 type None struct{}
275
276 func (None) AllowedAccess(req *http.Request) Operation {
277 return OpAll
278 }
279
280 func (None) AddAuthHeader(req *http.Request) {
281
282 }
283
284 type Localhost struct {
285 None
286 }
287
288 func (Localhost) AllowedAccess(req *http.Request) (out Operation) {
289 if httputil.IsLocalhost(req) {
290 return OpAll
291 }
292 return 0
293 }
294
295
296
297 type DevAuth struct {
298 Password string
299
300 VivifyPass *string
301 }
302
303 func (da *DevAuth) AllowedAccess(req *http.Request) Operation {
304 _, pass, err := httputil.BasicAuth(req)
305 if err == nil {
306 if pass == da.Password {
307 return OpAll
308 }
309 if da.VivifyPass != nil && pass == *da.VivifyPass {
310 return OpVivify
311 }
312 }
313
314 if authTokenHeaderMatches(req) {
315 return OpAll
316 }
317 if websocketTokenMatches(req) {
318 return OpAll
319 }
320
321
322
323
324 if httputil.IsLocalhost(req) {
325 return OpAll
326 }
327
328 return 0
329 }
330
331 func (da *DevAuth) AddAuthHeader(req *http.Request) {
332 req.SetBasicAuth("", da.Password)
333 }
334
335 func IsLocalhost(req *http.Request) bool {
336 return httputil.IsLocalhost(req)
337 }
338
339
340
341
342 func AllowedWithAuth(am AuthMode, req *http.Request, op Operation) bool {
343 if op&OpUpload != 0 {
344
345 op = op | OpVivify
346 }
347 return am.AllowedAccess(req)&op == op
348 }
349
350
351
352 func Allowed(req *http.Request, op Operation) bool {
353 for _, m := range modes {
354 if AllowedWithAuth(m, req, op) {
355 return true
356 }
357 }
358 return false
359 }
360
361 var uiTokenPattern = regexp.MustCompile(`^Token ([a-zA-Z0-9]+)`)
362
363 func authTokenHeaderMatches(req *http.Request) bool {
364 authHeader := req.Header.Get("Authorization")
365 if authHeader == "" {
366 return false
367 }
368 matches := uiTokenPattern.FindStringSubmatch(authHeader)
369 if len(matches) != 2 {
370 return false
371 }
372 return matches[1] == Token()
373 }
374
375 func websocketTokenMatches(req *http.Request) bool {
376 return req.Method == "GET" &&
377 req.Header.Get("Upgrade") == "websocket" &&
378 req.FormValue("authtoken") == Token()
379 }
380
381 func TriedAuthorization(req *http.Request) bool {
382
383
384 return req.Header.Get("Authorization") != ""
385 }
386
387 func SendUnauthorized(rw http.ResponseWriter, req *http.Request) {
388 for _, m := range modes {
389 if us, ok := m.(UnauthorizedSender); ok {
390 if us.SendUnauthorized(rw, req) {
391 return
392 }
393 }
394 }
395 var realm string
396 hasDevAuth := func() (*DevAuth, bool) {
397 for _, m := range modes {
398 if devAuth, ok := m.(*DevAuth); ok {
399 return devAuth, ok
400 }
401 }
402 return nil, false
403 }
404 if devAuth, ok := hasDevAuth(); ok {
405 realm = "Any username, password is: " + devAuth.Password
406 }
407
408
409 rw.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", realm))
410 rw.WriteHeader(http.StatusUnauthorized)
411 fmt.Fprintf(rw, "<html><body><h1>Unauthorized</h1>")
412 }
413
414 type Handler struct {
415 http.Handler
416 }
417
418
419 func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
420 h.serveHTTPForOp(w, r, OpAll)
421 }
422
423
424 func (h Handler) serveHTTPForOp(w http.ResponseWriter, r *http.Request, op Operation) {
425 if Allowed(r, op) {
426 h.Handler.ServeHTTP(w, r)
427 } else {
428 SendUnauthorized(w, r)
429 }
430 }
431
432
433
434 func RequireAuth(h http.Handler, op Operation) http.Handler {
435 return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
436 if Allowed(req, op) {
437 h.ServeHTTP(rw, req)
438 } else {
439 SendUnauthorized(rw, req)
440 }
441 })
442 }
443
444 var (
445 processRand string
446 processRandOnce sync.Once
447 )
448
449
450
451
452 func Token() string {
453 processRandOnce.Do(genProcessRand)
454 return processRand
455 }
456
457
458
459 func DiscoveryToken() string {
460 if len(modes) == 0 {
461 return Token()
462 }
463 if _, ok := modes[0].(None); ok {
464 return OmitAuthToken
465 }
466 return Token()
467 }
468
469
470
471 func TokenOrNone(token string) (AuthMode, error) {
472 if token == OmitAuthToken {
473 return None{}, nil
474 }
475 return NewTokenAuth(token)
476 }
477
478 func genProcessRand() {
479 processRand = RandToken(20)
480 }
481
482
483
484 func RandToken(size int) string {
485 buf := make([]byte, size)
486 if n, err := rand.Read(buf); err != nil || n != len(buf) {
487 panic("failed to get random: " + err.Error())
488 }
489 return fmt.Sprintf("%x", buf)
490 }