| package rpc | 
 |  | 
 | import ( | 
 | 	"fmt" | 
 | 	"regexp" | 
 |  | 
 | 	"google.golang.org/grpc/codes" | 
 | 	"google.golang.org/grpc/status" | 
 | 	"google.golang.org/protobuf/proto" | 
 | 	"google.golang.org/protobuf/reflect/protoreflect" | 
 | 	"google.golang.org/protobuf/reflect/protoregistry" | 
 |  | 
 | 	epb "source.monogon.dev/metropolis/proto/ext" | 
 | ) | 
 |  | 
 | // methodInfo is the parsed information for a given RPC method, as configured by | 
 | // the metropolis.common.ext.authorization extension. | 
 | type methodInfo struct { | 
 | 	// unauthenticated is true if the method is defined as 'unauthenticated', ie. | 
 | 	// that all requests should be passed to the gRPC handler without any | 
 | 	// authentication or authorization performed. | 
 | 	unauthenticated bool | 
 | 	// need is a map of permissions that the caller needs to have in order to be | 
 | 	// allowed to call this method. If not empty, unauthenticated cannot be set to | 
 | 	// true. | 
 | 	need map[epb.Permission]bool | 
 | } | 
 |  | 
 | var ( | 
 | 	// reMethodName matches a /some.service/Method string from | 
 | 	// {Stream,Unary}ServerInfo.FullMethod. | 
 | 	reMethodName = regexp.MustCompile(`^/([^/]+)/([^/.]+)$`) | 
 | ) | 
 |  | 
 | // getMethodInfo returns the methodInfo for a given method name, as retrieved | 
 | // from grpc.{Stream,Unary}ServerInfo.FullMethod, or nil if the method could not | 
 | // be found. | 
 | // | 
 | // SECURITY: If the given method does not have any | 
 | // metropolis.common.ext.authorization annotations, a methodInfo which requires | 
 | // authorization but no permissions is returned, defaulting to a mildly secure | 
 | // default of a method that can be called by any authenticated user. | 
 | func getMethodInfo(methodName string) (*methodInfo, error) { | 
 | 	m := reMethodName.FindStringSubmatch(methodName) | 
 | 	if len(m) != 3 { | 
 | 		return nil, status.Errorf(codes.InvalidArgument, "invalid method name %q", methodName) | 
 | 	} | 
 | 	// Convert /foo.bar/Method to foo.bar.Method, which is used by the protoregistry. | 
 | 	methodName = fmt.Sprintf("%s.%s", m[1], m[2]) | 
 | 	desc, err := protoregistry.GlobalFiles.FindDescriptorByName(protoreflect.FullName(methodName)) | 
 | 	if err != nil { | 
 | 		return nil, status.Errorf(codes.InvalidArgument, "could not retrieve descriptor for method: %v", err) | 
 | 	} | 
 | 	method, ok := desc.(protoreflect.MethodDescriptor) | 
 | 	if !ok { | 
 | 		return nil, status.Error(codes.InvalidArgument, "querying method name did not yield a MethodDescriptor") | 
 | 	} | 
 |  | 
 | 	// Get authorization extension, defaults to no options set. | 
 | 	if !proto.HasExtension(method.Options(), epb.E_Authorization) { | 
 | 		return nil, status.Errorf(codes.Internal, "method does not provide Authorization extension, failing safe") | 
 | 	} | 
 | 	authz, ok := proto.GetExtension(method.Options(), epb.E_Authorization).(*epb.Authorization) | 
 | 	if !ok { | 
 | 		return nil, status.Errorf(codes.Internal, "method contains Authorization extension with wrong type, failing safe") | 
 | 	} | 
 | 	if authz == nil { | 
 | 		return nil, status.Errorf(codes.Internal, "method contains nil Authorization extension, failing safe") | 
 | 	} | 
 |  | 
 | 	// If unauthenticated connections are allowed, return immediately. | 
 | 	if authz.AllowUnauthenticated && len(authz.Need) == 0 { | 
 | 		return &methodInfo{ | 
 | 			unauthenticated: true, | 
 | 		}, nil | 
 | 	} | 
 |  | 
 | 	// Otherwise, return needed permissions. | 
 | 	res := &methodInfo{ | 
 | 		need: make(map[epb.Permission]bool), | 
 | 	} | 
 | 	for _, n := range authz.Need { | 
 | 		res.need[n] = true | 
 | 	} | 
 | 	return res, nil | 
 | } |