-
Notifications
You must be signed in to change notification settings - Fork 3.6k
/
authentication_middleware.go
185 lines (153 loc) · 4.99 KB
/
authentication_middleware.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
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
package http
import (
"context"
"errors"
"fmt"
"net/http"
"time"
"github.com/influxdata/httprouter"
platform "github.com/influxdata/influxdb/v2"
platcontext "github.com/influxdata/influxdb/v2/context"
"github.com/influxdata/influxdb/v2/jsonweb"
errors2 "github.com/influxdata/influxdb/v2/kit/platform/errors"
"github.com/influxdata/influxdb/v2/session"
"github.com/opentracing/opentracing-go"
"go.uber.org/zap"
)
// AuthenticationHandler is a middleware for authenticating incoming requests.
type AuthenticationHandler struct {
errors2.HTTPErrorHandler
log *zap.Logger
AuthorizationService platform.AuthorizationService
SessionService platform.SessionService
UserService platform.UserService
TokenParser *jsonweb.TokenParser
SessionRenewDisabled bool
// This is only really used for it's lookup method the specific http
// handler used to register routes does not matter.
noAuthRouter *httprouter.Router
Handler http.Handler
}
// NewAuthenticationHandler creates an authentication handler.
func NewAuthenticationHandler(log *zap.Logger, h errors2.HTTPErrorHandler) *AuthenticationHandler {
return &AuthenticationHandler{
log: log,
HTTPErrorHandler: h,
Handler: http.NotFoundHandler(),
TokenParser: jsonweb.NewTokenParser(jsonweb.EmptyKeyStore),
noAuthRouter: httprouter.New(),
}
}
// RegisterNoAuthRoute excludes routes from needing authentication.
func (h *AuthenticationHandler) RegisterNoAuthRoute(method, path string) {
// the handler specified here does not matter.
h.noAuthRouter.HandlerFunc(method, path, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
}
const (
tokenAuthScheme = "token"
sessionAuthScheme = "session"
)
// ProbeAuthScheme probes the http request for the requests for token or cookie session.
func ProbeAuthScheme(r *http.Request) (string, error) {
_, tokenErr := GetToken(r)
_, sessErr := session.DecodeCookieSession(r.Context(), r)
if tokenErr != nil && sessErr != nil {
return "", fmt.Errorf("token required")
}
if tokenErr == nil {
return tokenAuthScheme, nil
}
return sessionAuthScheme, nil
}
func (h *AuthenticationHandler) unauthorized(ctx context.Context, w http.ResponseWriter, err error) {
h.log.Info("Unauthorized", zap.Error(err))
UnauthorizedError(ctx, h, w)
}
// ServeHTTP extracts the session or token from the http request and places the resulting authorizer on the request context.
func (h *AuthenticationHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if handler, _, _ := h.noAuthRouter.Lookup(r.Method, r.URL.Path); handler != nil {
h.Handler.ServeHTTP(w, r)
return
}
ctx := r.Context()
scheme, err := ProbeAuthScheme(r)
if err != nil {
h.unauthorized(ctx, w, err)
return
}
var auth platform.Authorizer
switch scheme {
case tokenAuthScheme:
auth, err = h.extractAuthorization(ctx, r)
case sessionAuthScheme:
auth, err = h.extractSession(ctx, r)
default:
// TODO: this error will be nil if it gets here, this should be remedied with some
// sentinel error I'm thinking
err = errors.New("invalid auth scheme")
}
if err != nil {
h.unauthorized(ctx, w, err)
return
}
// jwt based auth is permission based rather than identity based
// and therefor has no associated user. if the user ID is invalid
// disregard the user active check
if auth.GetUserID().Valid() {
if err = h.isUserActive(ctx, auth); err != nil {
InactiveUserError(ctx, h, w)
return
}
}
ctx = platcontext.SetAuthorizer(ctx, auth)
if span := opentracing.SpanFromContext(ctx); span != nil {
span.SetTag("user_id", auth.GetUserID().String())
}
h.Handler.ServeHTTP(w, r.WithContext(ctx))
}
func (h *AuthenticationHandler) isUserActive(ctx context.Context, auth platform.Authorizer) error {
u, err := h.UserService.FindUserByID(ctx, auth.GetUserID())
if err != nil {
return err
}
if u.Status != "inactive" {
return nil
}
return &errors2.Error{Code: errors2.EForbidden, Msg: "User is inactive"}
}
func (h *AuthenticationHandler) extractAuthorization(ctx context.Context, r *http.Request) (platform.Authorizer, error) {
t, err := GetToken(r)
if err != nil {
return nil, err
}
token, err := h.TokenParser.Parse(t)
if err == nil {
return token, nil
}
// if the error returned signifies ths token is
// not a well formed JWT then use it as a lookup
// key for its associated authorization
// otherwise return the error
if !jsonweb.IsMalformedError(err) {
return nil, err
}
return h.AuthorizationService.FindAuthorizationByToken(ctx, t)
}
func (h *AuthenticationHandler) extractSession(ctx context.Context, r *http.Request) (*platform.Session, error) {
k, err := session.DecodeCookieSession(ctx, r)
if err != nil {
return nil, err
}
s, err := h.SessionService.FindSession(ctx, k)
if err != nil {
return nil, err
}
if !h.SessionRenewDisabled {
// if the session is not expired, renew the session
err = h.SessionService.RenewSession(ctx, s, time.Now().Add(platform.RenewSessionTime))
if err != nil {
return nil, err
}
}
return s, err
}