-
Notifications
You must be signed in to change notification settings - Fork 14
/
callback.go
174 lines (155 loc) · 8.83 KB
/
callback.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
package core
import (
"context"
"fmt"
"net/http"
"github.com/anz-bank/pkg/log"
"github.com/anz-bank/sysl-go/jwtauth"
"github.com/anz-bank/sysl-go/common"
"github.com/anz-bank/sysl-go/config"
"github.com/anz-bank/sysl-go/core/authrules"
"github.com/go-chi/chi"
"google.golang.org/grpc"
)
// RestGenCallback is used by `sysl-go` to call hand-crafted code.
type RestGenCallback interface {
// AddMiddleware allows hand-crafted code to add middleware to the router
AddMiddleware(ctx context.Context, r chi.Router)
// BasePath allows hand-crafted code to set the base path for the Router
BasePath() string
// Config returns a structure representing the server config
// This is returned from the status endpoint
Config() interface{}
// MapError maps an error to an HTTPError in instances where custom error mapping is required. Return nil to perform default error mapping; defined as:
// 1. CustomError.HTTPError if the original error is a CustomError, otherwise
// 2. common.MapError
MapError(ctx context.Context, err error) *common.HTTPError
// DownstreamTimeoutContext add the desired timeout duration to the context for downstreams
// A separate service timeout (usually greater than the downstream) should also be in
// place to automatically respond to callers
DownstreamTimeoutContext(ctx context.Context) (context.Context, context.CancelFunc)
}
// GrpcGenCallback is currently a subset of RestGenCallback so is defined separately for convenience.
type GrpcGenCallback interface {
DownstreamTimeoutContext(ctx context.Context) (context.Context, context.CancelFunc)
}
// Hooks can be used to customise the behaviour of an autogenerated sysl-go service.
type Hooks struct {
MapError func(ctx context.Context, err error) *common.HTTPError
// AdditionalGrpcDialOptions can be used to append to the default grpc.DialOption configuration used by
// an autogenerated service when it calls grpc.Dial when using a grpc.Client to connect to a gRPC server.
// If given, AdditionalGrpcDialOptions will be appended to the list of default options created by
// DefaultGrpcDialOptions(CommonGRPCDownstreamData).
//
// Use AdditionalGrpcDialOptions if you need both default and custom options. Be careful that you do
// not specify any options that clash with the default options.
//
// If you need to completely override the default options, use OverrideGrpcDialOptions.
// It is an error to set both AdditionalGrpcDialOptions and OverrideGrpcDialOptions.
AdditionalGrpcDialOptions []grpc.DialOption
// OverrideGrpcDialOptions can be used to override the default grpc.DialOption configuration used by an
// an autogenerated service when it calls grpc.Dial when using a grpc.Client to connect to a gRPC server.
//
// The serviceName parameter will be filled with the name of the target service that we
// are about to call grpc.Dial to connect to -- a function implementing this hook can use the
// serviceName to customise different dial options for different targets.
//
// Prefer to use AdditionalGrpcDialOptions instead of OverrideGrpcDialOptions if you only need
// to append to the default grpc.DialOption configuration instead of overriding it completely.
//
// It is an error to set both AdditionalGrpcDialOptions and OverrideGrpcDialOptions.
OverrideGrpcDialOptions func(serviceName string, cfg *config.CommonGRPCDownstreamData) ([]grpc.DialOption, error)
// AdditionalGrpcServerOptions can be used to append to the default grpc.ServerOption configuration used by
// an autogenerated service when it creates a gRPC server. If given, AdditionalGrpcServerOptions will be
// appended to the list of default options created by DefaultGrpcServerOptions(context.Context, CommonServerConfig).
//
// Use AdditionalGrpcServerOptions if you need both default and custom options. Be careful that you do
// not specify any options that clash with the default options.
//
// If you need to completely override the default options, use OverrideGrpcServerOptions.
// It is an error to set both AdditionalGrpcServerOptions and OverrideGrpcServerOptions.
AdditionalGrpcServerOptions []grpc.ServerOption
// OverrideGrpcServerOptions can be used to override the default grpc.ServerOption configuration used by an
// autogenerated service when it creates a gRPC server.
//
// Prefer to use AdditionalGrpcServerOptions instead of OverrideGrpcServerOptions if you only need
// to append to the default grpc.ServerOption configuration instead of overriding it completely.
//
// It is an error to set both AdditionalGrpcServerOptions and OverrideGrpcServerOptions.
OverrideGrpcServerOptions func(ctx context.Context, grpcPublicServerConfig *config.CommonServerConfig) ([]grpc.ServerOption, error)
// OverrideMakeJWTClaimsBasedAuthorizationRule can be used to customise how authorization rule
// expressions are evaluated and used to decide if JWT claims are authorised. By default, if this
// hook is nil, then authrules.MakeDefaultJWTClaimsBasedAuthorizationRule is used.
OverrideMakeJWTClaimsBasedAuthorizationRule func(authorizationRuleExpression string) (authrules.JWTClaimsBasedAuthorizationRule, error)
}
func ResolveGrpcDialOptions(serviceName string, h *Hooks, grpcDownstreamConfig *config.CommonGRPCDownstreamData) ([]grpc.DialOption, error) {
switch {
case len(h.AdditionalGrpcDialOptions) > 0 && h.OverrideGrpcDialOptions != nil:
return nil, fmt.Errorf("Hooks.AdditionalGrpcDialOptions and Hooks.OverrideGrpcDialOptions cannot both be set")
case h.OverrideGrpcDialOptions != nil:
return h.OverrideGrpcDialOptions(serviceName, grpcDownstreamConfig)
default:
opts, err := config.DefaultGrpcDialOptions(grpcDownstreamConfig)
if err != nil {
return nil, err
}
opts = append(opts, h.AdditionalGrpcDialOptions...)
return opts, nil
}
}
func ResolveGrpcServerOptions(ctx context.Context, h *Hooks, grpcPublicServerConfig *config.CommonServerConfig) ([]grpc.ServerOption, error) {
switch {
case len(h.AdditionalGrpcServerOptions) > 0 && h.OverrideGrpcServerOptions != nil:
return nil, fmt.Errorf("Hooks.AdditionalGrpcServerOptions and Hooks.OverrideGrpcServerOptions cannot both be set")
case h.OverrideGrpcServerOptions != nil:
return h.OverrideGrpcServerOptions(ctx, grpcPublicServerConfig)
default:
opts, err := DefaultGrpcServerOptions(ctx, grpcPublicServerConfig)
if err != nil {
return nil, err
}
opts = append(opts, h.AdditionalGrpcServerOptions...)
return opts, nil
}
}
func ResolveGRPCAuthorizationRule(ctx context.Context, cfg *config.DefaultConfig, h *Hooks, endpointName string, authRuleExpression string) (authrules.Rule, error) {
return resolveAuthorizationRule(ctx, cfg, h, endpointName, authRuleExpression, authrules.MakeGRPCJWTAuthorizationRule)
}
func ResolveRESTAuthorizationRule(ctx context.Context, cfg *config.DefaultConfig, h *Hooks, endpointName string, authRuleExpression string) (authrules.Rule, error) {
return resolveAuthorizationRule(ctx, cfg, h, endpointName, authRuleExpression, authrules.MakeRESTJWTAuthorizationRule)
}
func resolveAuthorizationRule(ctx context.Context, cfg *config.DefaultConfig, h *Hooks, endpointName string, authRuleExpression string, ruleFactory func(authRule authrules.JWTClaimsBasedAuthorizationRule, authenticator jwtauth.Authenticator) (authrules.Rule, error)) (authrules.Rule, error) {
if cfg.Development != nil && cfg.Development.DisableAllAuthorizationRules {
// pkg logger API doesnt support warn.
log.Infof(ctx, "warning: development.disableAllAuthorizationRules is set, all authorization rules are disabled, this is insecure and should not be used in production.")
return authrules.InsecureAlwaysGrantAccess, nil
}
var claimsBasedAuthRuleFactory func(authorizationRuleExpression string) (authrules.JWTClaimsBasedAuthorizationRule, error)
switch {
case h.OverrideMakeJWTClaimsBasedAuthorizationRule != nil:
claimsBasedAuthRuleFactory = h.OverrideMakeJWTClaimsBasedAuthorizationRule
default:
claimsBasedAuthRuleFactory = authrules.MakeDefaultJWTClaimsBasedAuthorizationRule
}
claimsBasedAuthRule, err := claimsBasedAuthRuleFactory(authRuleExpression)
if err != nil {
return nil, err
}
// TODO(fletcher) inject custom http client instrumented with monitoring
httpClient, err := config.DefaultHTTPClient(nil)
if err != nil {
return nil, err
}
httpClientFactory := func(_ string) *http.Client {
return httpClient
}
// Note: this will start a new jwtauth.Authenticator with its own cache & threads running for each of our service's endpoints, we usually want a shared one.
if cfg == nil || cfg.Library.Authentication == nil || cfg.Library.Authentication.JWTAuth == nil {
return nil, fmt.Errorf("method/endpoint %s requires a JWT-based authorization rule, but there is no config for library.authentication.jwtauth", endpointName)
}
authenticator, err := jwtauth.AuthFromConfig(ctx, cfg.Library.Authentication.JWTAuth, httpClientFactory)
if err != nil {
return nil, err
}
return ruleFactory(claimsBasedAuthRule, authenticator)
}