-
Notifications
You must be signed in to change notification settings - Fork 23
/
middleware.go
134 lines (108 loc) · 3.39 KB
/
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
package auth
import (
"context"
"fmt"
"net/http"
"strings"
"github.com/aserto-dev/go-authorizer/pkg/aerr"
"github.com/aserto-dev/topaz/pkg/cc/config"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
"github.com/grpc-ecosystem/go-grpc-middleware/util/metautils"
"github.com/rs/zerolog"
"google.golang.org/grpc"
)
type APIKeyAuthMiddleware struct {
apiAuth map[string]string
// TODO: figure out what we want to do with the config.
cfg *config.AuthnConfig
logger *zerolog.Logger
}
func NewAPIKeyAuthMiddleware(
ctx context.Context,
// TODO: figure out what we want to do with the config.
cfg *config.AuthnConfig,
logger *zerolog.Logger,
) (*APIKeyAuthMiddleware, error) {
return &APIKeyAuthMiddleware{
apiAuth: cfg.APIKeys,
cfg: cfg,
logger: logger,
}, nil
}
func (a *APIKeyAuthMiddleware) Unary() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
newCtx, err := a.grpcAuthenticate(ctx)
if err != nil {
return nil, err
}
return handler(newCtx, req)
}
}
func (a *APIKeyAuthMiddleware) Stream() grpc.StreamServerInterceptor {
return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
ctx := stream.Context()
newCtx, err := a.grpcAuthenticate(ctx)
if err != nil {
return err
}
wrapped := grpc_middleware.WrapServerStream(stream)
wrapped.WrappedContext = newCtx
return handler(srv, wrapped)
}
}
func (a *APIKeyAuthMiddleware) Handler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
newCtx, err := a.authenticate(
r.Context(),
r.URL.Path,
httpAuthHeader(r),
)
if err != nil {
http.Error(w, fmt.Sprintf("%q", err.Error()), http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r.WithContext(newCtx))
})
}
func (a *APIKeyAuthMiddleware) authenticate(
ctx context.Context,
path, authHeader string,
) (context.Context, error) {
options := a.cfg.Options.ForPath(path)
if options.EnableAnonymous {
return ctx, nil
}
// if no API keys are defined or EnableAPIKey is not set, allow the request
if (len(a.cfg.APIKeys) == 0) || !options.EnableAPIKey {
return ctx, nil
}
basicAPIKey, err := parseAuthHeader(authHeader, "basic")
if err != nil {
a.logger.Trace().Err(err).Str("auth_header", authHeader).Msg("failed to parse basic auth header")
}
// allow the request if the API key is present in the config
if _, ok := a.cfg.APIKeys[basicAPIKey]; ok {
return ctx, nil
}
return ctx, aerr.ErrAuthenticationFailed
}
func (a *APIKeyAuthMiddleware) grpcAuthenticate(ctx context.Context) (context.Context, error) {
method, _ := grpc.Method(ctx)
return a.authenticate(ctx, method, grpcAuthHeader(ctx))
}
func grpcAuthHeader(ctx context.Context) string {
return metautils.ExtractIncoming(ctx).Get("Authorization")
}
func httpAuthHeader(r *http.Request) string {
return r.Header.Get("Authorization")
}
func parseAuthHeader(val, expectedScheme string) (string, error) {
splits := strings.SplitN(val, " ", 2)
if len(splits) < 2 {
return "", aerr.ErrAuthenticationFailed.Msg("Bad authorization string")
}
if !strings.EqualFold(splits[0], expectedScheme) {
return "", aerr.ErrAuthenticationFailed.Msgf("Request unauthenticated with expected scheme, expected: %s", expectedScheme)
}
return splits[1], nil
}