lightning-terminal/rpcmiddleware/interface.go

476 lines
17 KiB
Go
Raw Normal View History

package rpcmiddleware
import (
"context"
"fmt"
"reflect"
"github.com/lightningnetwork/lnd/lnrpc"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
)
var (
// ErrNotSupported is returned if a specific method is called that is
// not supported by the RPC middleware interceptor checker.
ErrNotSupported = fmt.Errorf("method not supported")
// errorType is the reflection type of the error interface.
errorType = reflect.TypeOf((*error)(nil)).Elem()
// ctxType is the reflection type of the context.Context interface.
ctxType = reflect.TypeOf((*context.Context)(nil)).Elem()
// protoMessageType is the reflection type of the proto.Message
// interface.
protoMessageType = reflect.TypeOf((*proto.Message)(nil)).Elem()
// passThroughMessageHandler is a messageHandler that does not modify
// the message and just passes it through.
passThroughMessageHandler messageHandler = func(context.Context,
proto.Message) (proto.Message, error) {
return nil, nil
}
// PassThroughErrorHandler is an ErrorHandler that does not modify an
// error and instead just passes it through.
PassThroughErrorHandler ErrorHandler = func(context.Context, error) (
error, error) {
return nil, nil
}
// messageDenyHandler disallows the given message.
messageDenyHandler messageHandler = func(context.Context,
proto.Message) (proto.Message, error) {
return nil, ErrNotSupported
}
)
// RequestInterceptor is a type that can intercept an RPC request.
type RequestInterceptor interface {
// Name returns the name of the interceptor.
Name() string
// ReadOnly returns true if this interceptor should be registered in
// read-only mode. In read-only mode no custom caveat name can be
// specified.
ReadOnly() bool
// CustomCaveatName returns the name of the custom caveat that is
// expected to be handled by this interceptor. Cannot be specified in
// read-only mode.
CustomCaveatName() string
// Intercept processes an RPC middleware interception request and
// returns the interception result which either accepts or rejects the
// intercepted message.
Intercept(context.Context,
*lnrpc.RPCMiddlewareRequest) (*lnrpc.RPCMiddlewareResponse,
error)
}
// messageHandler is a function type for a generic gRPC message handler that
// can pass through the message (=return nil, nil), replace the message with a
// new message of the same type (=return non-nil message, nil error) or abort
// the call by returning a non-nil error. If the message is a request, then
// returning a non-nil error will reject the request.
type messageHandler func(context.Context, proto.Message) (proto.Message, error)
// ErrorHandler is a function type for a generic gRPC error handler. It can
// pass through the error unchanged (=return nil, nil), replace the error with
// a different one (=return non-nil error, nil error) or abort by returning a
// non-nil error.
type ErrorHandler func(ctx context.Context, respErr error) (error, error)
// RoundTripChecker is a type that represents a basic request/response round
// trip checker.
type RoundTripChecker interface {
// HandlesRequest returns true if the checker accepts protobuf request
// messages of the given type. This is mainly a safety feature to make
// sure a round trip checker is implemented correctly.
HandlesRequest(protoreflect.MessageType) bool
// HandlesResponse returns true if the checker can handle protobuf
// response messages of the given type. This is mainly a safety feature
// to make sure a round trip checker is implemented correctly.
HandlesResponse(protoreflect.MessageType) bool
// HandleRequest is called for each incoming gRPC request message of the
// type declared to be accepted by HandlesRequest. The handler can
// accept the request as is (=return nil, nil), replace the request with
// a new message of the same type (=return non-nil message, nil) or
// refuse (=return non-nil error with rejection reason) an incoming
// request.
HandleRequest(context.Context, proto.Message) (proto.Message, error)
// HandleResponse is called for each outgoing gRPC response message of
// the type declared to be handled by HandlesResponse. The handler can
// pass through the response (=return nil, nil), replace the response
// with a new message of the same type (=return non-nil message, nil
// error) or abort the call by returning a non-nil error.
HandleResponse(context.Context, proto.Message) (proto.Message, error)
// HandleErrorResponse is called for any error response.
// The handler can pass through the error (=return nil, nil), replace
// the response error with a new one (=return non-nil error, nil) or
// abort by returning a non nil error (=return nil, non-nil error).
HandleErrorResponse(context.Context, error) (error, error)
}
// DefaultChecker is the default implementation of a round trip checker.
type DefaultChecker struct {
requestType protoreflect.MessageType
responseType protoreflect.MessageType
requestHandler messageHandler
responseHandler messageHandler
errorHandler ErrorHandler
}
// A compile-time check to ensure that DefaultChecker implements
// RoundTripChecker.
var _ RoundTripChecker = (*DefaultChecker)(nil)
// HandlesRequest returns true if the checker accepts protobuf request messages
// of the given type. This is mainly a safety feature to make sure a round trip
// checker is implemented correctly.
func (r *DefaultChecker) HandlesRequest(t protoreflect.MessageType) bool {
return t == r.requestType
}
// HandlesResponse returns true if the checker can handle protobuf response
// messages of the given type. This is mainly a safety feature to make sure a
// round trip checker is implemented correctly.
func (r *DefaultChecker) HandlesResponse(t protoreflect.MessageType) bool {
return t == r.responseType
}
// HandleRequest is called for each incoming gRPC request message of the type
// declared to be accepted by HandlesRequest. The handler can accept the request
// as is (=return nil, nil), replace the request with a new message of the same
// type (=return non-nil message, nil) or refuse (=return non-nil error with
// rejection reason) an incoming request.
func (r *DefaultChecker) HandleRequest(ctx context.Context,
req proto.Message) (proto.Message, error) {
return r.requestHandler(ctx, req)
}
// HandleResponse is called for each outgoing gRPC response message of the type
// declared to be handled by HandlesResponse. The handler can pass through the
// response (=return nil, nil), replace the response with a new message of the
// same type (=return non-nil message, nil error) or abort the call by returning
// a non-nil error.
func (r *DefaultChecker) HandleResponse(ctx context.Context,
resp proto.Message) (proto.Message, error) {
return r.responseHandler(ctx, resp)
}
// HandleErrorResponse is called for any error response.
// The handler can pass through the error (=return nil, nil), replace
// the response error with a new one (=return non-nil error, nil) or
// abort by returning a non nil error (=return nil, non-nil error).
func (r *DefaultChecker) HandleErrorResponse(ctx context.Context,
respErr error) (error, error) {
return r.errorHandler(ctx, respErr)
}
// NewPassThrough returns a round trip checker that allows the incoming request
// and passes through the response unmodified.
func NewPassThrough(requestSample proto.Message,
responseSample proto.Message) *DefaultChecker {
return &DefaultChecker{
requestType: requestSample.ProtoReflect().Type(),
responseType: responseSample.ProtoReflect().Type(),
requestHandler: passThroughMessageHandler,
responseHandler: passThroughMessageHandler,
errorHandler: PassThroughErrorHandler,
}
}
// NewRequestChecker returns a round trip checker that inspects the incoming
// request and passes through the response unmodified.
func NewRequestChecker(requestSample proto.Message,
responseSample proto.Message,
typedRequestHandler interface{}) *DefaultChecker {
return &DefaultChecker{
requestType: requestSample.ProtoReflect().Type(),
responseType: responseSample.ProtoReflect().Type(),
requestHandler: newReflectionRequestCheckHandler(
requestSample, typedRequestHandler,
),
responseHandler: passThroughMessageHandler,
errorHandler: PassThroughErrorHandler,
}
}
// NewRequestDenier returns a round trip checker that denies the given requests.
func NewRequestDenier(requestSample proto.Message,
responseSample proto.Message) *DefaultChecker {
return &DefaultChecker{
requestType: requestSample.ProtoReflect().Type(),
responseType: responseSample.ProtoReflect().Type(),
requestHandler: messageDenyHandler,
responseHandler: messageDenyHandler,
errorHandler: PassThroughErrorHandler,
}
}
// NewRequestRewriter returns a round trip checker that inspects and potentially
// modifies the incoming request and passes through the response unmodified.
func NewRequestRewriter(requestSample proto.Message,
responseSample proto.Message,
typedRequestHandler interface{}) *DefaultChecker {
return &DefaultChecker{
requestType: requestSample.ProtoReflect().Type(),
responseType: responseSample.ProtoReflect().Type(),
requestHandler: newReflectionMessageHandler(
requestSample, typedRequestHandler,
),
responseHandler: passThroughMessageHandler,
errorHandler: PassThroughErrorHandler,
}
}
// NewResponseRewriter returns a round trip checker that allows the incoming
// request and inspects and potentially modifies the response.
func NewResponseRewriter(requestSample proto.Message,
responseSample proto.Message, typedResponseHandler interface{},
errorHandler ErrorHandler) *DefaultChecker {
return &DefaultChecker{
requestType: requestSample.ProtoReflect().Type(),
responseType: responseSample.ProtoReflect().Type(),
requestHandler: passThroughMessageHandler,
responseHandler: newReflectionMessageHandler(
responseSample, typedResponseHandler,
),
errorHandler: errorHandler,
}
}
// NewResponseEmptier returns a round trip checker that allows the incoming
// request and replaces the response with an empty one.
func NewResponseEmptier[reqT, respT proto.Message]() *DefaultChecker {
req := *new(reqT)
resp := *new(respT)
return &DefaultChecker{
requestType: req.ProtoReflect().Type(),
responseType: resp.ProtoReflect().Type(),
requestHandler: passThroughMessageHandler,
responseHandler: newReflectionMessageHandler(
resp, func(context.Context, respT) (proto.Message,
error) {
return *new(respT), nil
},
),
errorHandler: PassThroughErrorHandler,
}
}
// NewFullChecker returns a round trip checker that both inspects the incoming
// request and response and potentially modifies the response.
func NewFullChecker(requestSample proto.Message,
responseSample proto.Message, typedRequestHandler interface{},
typedResponseHandler interface{},
errorHandler ErrorHandler) *DefaultChecker {
return &DefaultChecker{
requestType: requestSample.ProtoReflect().Type(),
responseType: responseSample.ProtoReflect().Type(),
requestHandler: newReflectionRequestCheckHandler(
requestSample, typedRequestHandler,
),
responseHandler: newReflectionMessageHandler(
responseSample, typedResponseHandler,
),
errorHandler: errorHandler,
}
}
// NewFullRewriter returns a round trip checker that both inspects the incoming
// request and response and potentially modifies the both the request and
// response.
func NewFullRewriter(requestSample proto.Message,
responseSample proto.Message, typedRequestHandler interface{},
typedResponseHandler interface{},
errHandler ErrorHandler) *DefaultChecker {
return &DefaultChecker{
requestType: requestSample.ProtoReflect().Type(),
responseType: responseSample.ProtoReflect().Type(),
requestHandler: newReflectionMessageHandler(
requestSample, typedRequestHandler,
),
responseHandler: newReflectionMessageHandler(
responseSample, typedResponseHandler,
),
errorHandler: errHandler,
}
}
// newReflectionRequestCheckHandler returns a request handler that adapts the
// generic proto.Message capable request handler into one that is type specific
// for the given request message sample message. This requires reflection and
// cannot be implemented with Generics.
func newReflectionRequestCheckHandler(requestSample proto.Message,
typedHandler interface{}) messageHandler {
requestType := reflect.TypeOf(requestSample)
requestProtoType := requestSample.ProtoReflect().Type()
handlerValue := reflect.ValueOf(typedHandler)
err := validateRequestCheckHandler(handlerValue.Type(), requestType)
if err != nil {
// This is covered by unit tests and shouldn't happen in the
// first place, as this would be an implementation error.
panic(err)
}
return func(ctx context.Context, req proto.Message) (proto.Message,
error) {
if req.ProtoReflect().Type() != requestProtoType {
return nil, fmt.Errorf("request handler called for "+
"unsupported type %v (expected %v)",
req.ProtoReflect().Type(), requestProtoType)
}
// We made sure this call would succeed when creating the
// handler.
resp := handlerValue.Call([]reflect.Value{
reflect.ValueOf(ctx),
reflect.ValueOf(req),
})
// We also made sure the types returned from the function would
// be compatible with what we expect.
var err error
if resp[0].Interface() != nil {
err = resp[0].Interface().(error)
}
return nil, err
}
}
// newReflectionMessageHandler returns a message handler that adapts the generic
// proto.Message capable message handler into one that is type specific for the
// given sample message. This requires reflection and cannot be implemented with
// Generics.
func newReflectionMessageHandler(messageSample proto.Message,
typedHandler interface{}) messageHandler {
messageType := reflect.TypeOf(messageSample)
messageProtoType := messageSample.ProtoReflect().Type()
handlerValue := reflect.ValueOf(typedHandler)
err := validateMessageHandler(handlerValue.Type(), messageType)
if err != nil {
// This is covered by unit tests and shouldn't happen in the
// first place, as this would be an implementation error.
panic(err)
}
return func(ctx context.Context, req proto.Message) (proto.Message,
error) {
if req.ProtoReflect().Type() != messageProtoType {
return nil, fmt.Errorf("message handler called for "+
"unsupported type %v (expected %v)",
req.ProtoReflect().Type(), messageProtoType)
}
// We made sure this call would succeed when creating the
// handler.
resp := handlerValue.Call([]reflect.Value{
reflect.ValueOf(ctx),
reflect.ValueOf(req),
})
// We also made sure the types returned from the function would
// be compatible with what we expect.
var (
replacementMessage proto.Message
err error
)
if resp[0].Interface() != nil {
replacementMessage = resp[0].Interface().(proto.Message)
}
if resp[1].Interface() != nil {
err = resp[1].Interface().(error)
}
return replacementMessage, err
}
}
// validateRequestCheckHandler makes sure that the given request handler
// function has the correct number and types of parameters and return values.
func validateRequestCheckHandler(typedHandlerType reflect.Type,
requestType reflect.Type) error {
if typedHandlerType.Kind() != reflect.Func {
return fmt.Errorf("request handler must be a function")
}
if typedHandlerType.NumIn() != 2 || typedHandlerType.NumOut() != 1 {
return fmt.Errorf("request handler must have exactly two " +
"parameter and one return value")
}
if !typedHandlerType.In(0).ConvertibleTo(ctxType) {
return fmt.Errorf("request handler must have first parameter " +
"with a sub type of context.Context")
}
if !typedHandlerType.In(1).ConvertibleTo(requestType) {
return fmt.Errorf("request handler must have second " +
"parameter with a sub type of proto.Message")
}
if typedHandlerType.Out(0) != errorType {
return fmt.Errorf("request handler must return exactly one " +
"error value")
}
return nil
}
// validateMessageHandler makes sure that the given message handler function
// has the correct number and types of parameters and return values.
func validateMessageHandler(typedHandlerType reflect.Type,
messageType reflect.Type) error {
if typedHandlerType.Kind() != reflect.Func {
return fmt.Errorf("message handler must be a function")
}
if typedHandlerType.NumIn() != 2 || typedHandlerType.NumOut() != 2 {
return fmt.Errorf("message handler must have exactly two " +
"parameter and two return values")
}
if !typedHandlerType.In(0).ConvertibleTo(ctxType) {
return fmt.Errorf("request handler must have first parameter " +
"with a sub type of context.Context")
}
if !typedHandlerType.In(1).ConvertibleTo(messageType) {
return fmt.Errorf("message handler must have second " +
"parameter with a sub type of proto.Message")
}
outType0 := typedHandlerType.Out(0)
pmt := protoMessageType
if outType0 != pmt ||
typedHandlerType.Out(1) != errorType {
return fmt.Errorf("message handler must return exactly two " +
"values, proto.Message and error")
}
return nil
}