-
Notifications
You must be signed in to change notification settings - Fork 57
/
server_interceptor.go
110 lines (97 loc) · 3.92 KB
/
server_interceptor.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
package l402
import (
"bytes"
"context"
"fmt"
"net/http"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
// ServerInterceptor is a gRPC server interceptor that extracts the token ID
// from the request context if an L402 is present.
type ServerInterceptor struct{}
// UnaryInterceptor is an unary gRPC server interceptor that inspects incoming
// calls for authentication tokens. If an L402 authentication token is found in
// the request, its token ID is extracted and treated as client ID. The
// extracted ID is then attached to the request context in a format that is easy
// to extract by request handlers.
func (i *ServerInterceptor) UnaryInterceptor(ctx context.Context,
req interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (
resp interface{}, err error) {
// Try getting the authentication header embedded in the context meta
// data and parse it. We ignore all errors that happen and just forward
// the request to the handler if anything fails. Incoming calls with
// invalid metadata will therefore just be treated as non-identified or
// non-authenticated.
token, err := tokenFromContext(ctx)
if err != nil {
log.Debugf("No token extracted, error was: %v", err)
return handler(ctx, req)
}
// We got a token, create a new context that wraps its value and
// continue the call chain by invoking the handler.
idCtx := AddToContext(ctx, KeyTokenID, *token)
return handler(idCtx, req)
}
// wrappedStream is a thin wrapper around the grpc.ServerStream that allows us
// to overwrite the context of the stream.
type wrappedStream struct {
grpc.ServerStream
WrappedContext context.Context
}
// Context returns the context for this stream.
func (w *wrappedStream) Context() context.Context {
return w.WrappedContext
}
// StreamInterceptor is an stream gRPC server interceptor that inspects incoming
// streams for authentication tokens. If an L402 authentication token is found
// in the initial stream establishment request, its token ID is extracted and
// treated as client ID. The extracted ID is then attached to the request
// context in a format that is easy to extract by request handlers.
func (i *ServerInterceptor) StreamInterceptor(srv interface{},
ss grpc.ServerStream, _ *grpc.StreamServerInfo,
handler grpc.StreamHandler) error {
// Try getting the authentication header embedded in the context meta
// data and parse it. We ignore all errors that happen and just forward
// the request to the handler if anything fails. Incoming calls with
// invalid metadata will therefore just be treated as non-identified or
// non-authenticated.
ctx := ss.Context()
token, err := tokenFromContext(ctx)
if err != nil {
log.Debugf("No token extracted, error was: %v", err)
return handler(srv, ss)
}
// We got a token, create a new context that wraps its value and
// continue the call chain by invoking the handler. We can't directly
// modify the server stream so we have to wrap it.
idCtx := AddToContext(ctx, KeyTokenID, *token)
wrappedStream := &wrappedStream{ss, idCtx}
return handler(srv, wrappedStream)
}
// tokenFromContext tries to extract the L402 from a context.
func tokenFromContext(ctx context.Context) (*TokenID, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, fmt.Errorf("context contains no metadata")
}
header := &http.Header{
HeaderAuthorization: md.Get(HeaderAuthorization),
}
log.Debugf("Auth header present in request: %s",
md.Get(HeaderAuthorization))
macaroon, _, err := FromHeader(header)
if err != nil {
return nil, fmt.Errorf("auth header extraction failed: %v", err)
}
// If there is an L402, decode and add it to the context.
identifier, err := DecodeIdentifier(bytes.NewBuffer(macaroon.Id()))
if err != nil {
return nil, fmt.Errorf("token ID decoding failed: %v", err)
}
var clientID TokenID
copy(clientID[:], identifier.TokenID[:])
log.Debugf("Decoded client/token ID %s from auth header",
clientID.String())
return &clientID, nil
}