-
Notifications
You must be signed in to change notification settings - Fork 0
/
auth.go
71 lines (61 loc) · 2.44 KB
/
auth.go
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
package api
import (
"context"
"errors"
"fmt"
"net/http"
"github.com/go-chi/render"
actx "go.hackfix.me/disco/app/context"
"go.hackfix.me/disco/db/models"
"go.hackfix.me/disco/web/server/types"
)
// authnUser authenticates the Disco user from the received TLS client
// certificate, and loads the User record in the request context given that the
// Subject Common Name matches an existing User name. For this to be reached,
// the resource needs to have been accessed with a valid client certificate,
// which is validated in the Go runtime, before reaching Disco HTTP endpoints.
//
// If this fails, a response with status 401 Unauthorized is returned. Otherwise
// the request is allowed to continue, and authorization to access individual
// resources is done later in each handler.
func authnUser(appCtx *actx.Context) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.TLS == nil || len(r.TLS.VerifiedChains) == 0 || len(r.TLS.VerifiedChains[0]) == 0 {
_ = render.Render(w, r, types.ErrUnauthorized("failed TLS authentication"))
return
}
subjectCN := r.TLS.VerifiedChains[0][0].Subject.CommonName
user := &models.User{Name: subjectCN}
if err := user.Load(appCtx.DB.NewContext(), appCtx.DB); err != nil {
appCtx.Logger.Warn(
"failed loading user with the received TLS client certificate",
"subjectCommonName", subjectCN, "error", err.Error())
_ = render.Render(w, r, types.ErrUnauthorized(
"failed loading user identified in the client TLS certificate"))
return
}
ctx := context.WithValue(r.Context(), types.ConnTLSUserKey, user)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
// authzUser checks whether the user is authorized to perform the given action
// on the given resource in the given namespace. An error is returned if
// authorization fails, or nil otherwise.
func authzUser(
req *http.Request, action models.Action, resource models.Resource,
namespace, target string,
) error {
user, ok := req.Context().Value(types.ConnTLSUserKey).(*models.User)
if !ok {
return errors.New("user object not found in the request context")
}
target = fmt.Sprintf("%s:%s:%s", namespace, resource, target)
if ok, err := user.Can(string(action), target); err != nil {
return err
} else if !ok {
return fmt.Errorf("user '%s' is not authorized to %s %s", user.Name, action, target)
}
return nil
}