2026-04-06 15:48:37 +05:30
|
|
|
// Copyright IBM Corp. 2020, 2026
|
2024-02-13 09:17:41 -08:00
|
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
|
|
|
|
|
|
package alias
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
|
|
|
|
"strings"
|
|
|
|
|
"sync"
|
|
|
|
|
|
2026-10-12 10:33:41 -07:00
|
|
|
"repo.anhonesthost.net/CyberCoveLLC/threshold/internal/errors"
|
|
|
|
|
"repo.anhonesthost.net/CyberCoveLLC/threshold/sdk/pbs/controller/protooptions"
|
2024-02-13 09:17:41 -08:00
|
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
|
"google.golang.org/protobuf/reflect/protoreflect"
|
|
|
|
|
"google.golang.org/protobuf/reflect/protoregistry"
|
|
|
|
|
"google.golang.org/protobuf/types/descriptorpb"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
|
// register all proto fields that are aliasable.
|
|
|
|
|
protoregistry.GlobalTypes.RangeMessages(func(m protoreflect.MessageType) bool {
|
|
|
|
|
registerAliasableFields(m.Descriptor())
|
|
|
|
|
return true
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type registrationInfo struct {
|
|
|
|
|
unaliasableWithFields []string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// globalAliasableRegistry is a map of proto field's FullName to registrationInfo.
|
|
|
|
|
var globalAliasableRegistry sync.Map
|
|
|
|
|
|
|
|
|
|
func registerAliasableFields(d protoreflect.MessageDescriptor) {
|
|
|
|
|
fields := d.Fields()
|
|
|
|
|
for i := 0; i < fields.Len(); i++ {
|
|
|
|
|
f := fields.Get(i)
|
|
|
|
|
name := f.FullName()
|
|
|
|
|
opts, ok := f.Options().(*descriptorpb.FieldOptions)
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
aliasableInfo := proto.GetExtension(opts, protooptions.E_Aliasable).(*protooptions.AliasInfo)
|
|
|
|
|
if aliasableInfo != nil {
|
|
|
|
|
ri := ®istrationInfo{unaliasableWithFields: aliasableInfo.GetUnlessSet().GetFields()}
|
|
|
|
|
globalAliasableRegistry.Store(name, ri)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// aliasLookup is an interface for looking up an alias by its value.
|
|
|
|
|
type aliasLookup interface {
|
|
|
|
|
lookupAliasByValue(ctx context.Context, value string) (*Alias, error)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ResolveRequestIds transforms the request by replacing aliasable field values
|
|
|
|
|
// with their corresponding destination ids. A field is aliasable depending on
|
|
|
|
|
// if and how the custom proto option "custom_options.v1.aliasable" is set on
|
|
|
|
|
// it. When the option is set on a field, it can be marked as always aliasable,
|
|
|
|
|
// in which case the field will always be checked for an alias value on the field
|
|
|
|
|
// or it can be marked as aliasable unless other specified fields on the same
|
|
|
|
|
// message is set.
|
|
|
|
|
// If no alias is found or the alias has no destination id, an error is returned.
|
|
|
|
|
func ResolveRequestIds(ctx context.Context, req proto.Message, lookup aliasLookup) (context.Context, error) {
|
|
|
|
|
const op = "alias.ResolveRequestIds"
|
|
|
|
|
r := req.ProtoReflect()
|
|
|
|
|
fields := r.Descriptor().Fields()
|
|
|
|
|
|
|
|
|
|
nextField:
|
|
|
|
|
for i := 0; i < fields.Len(); i++ {
|
|
|
|
|
f := fields.Get(i)
|
|
|
|
|
if f.Kind() != protoreflect.StringKind {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
aiVal, ok := globalAliasableRegistry.Load(f.FullName())
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
ai, ok := aiVal.(*registrationInfo)
|
|
|
|
|
if !ok {
|
|
|
|
|
return ctx, errors.New(ctx, errors.Internal, op, fmt.Sprintf("unable to cast aliasable info for field %q", f.FullName()))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
v := r.Get(f).String()
|
|
|
|
|
if !maybeAlias(v) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, fieldName := range ai.unaliasableWithFields {
|
|
|
|
|
if r.Has(fields.ByName(protoreflect.Name(fieldName))) {
|
|
|
|
|
continue nextField
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
a, err := lookup.lookupAliasByValue(ctx, v)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return ctx, err
|
|
|
|
|
}
|
|
|
|
|
if a == nil {
|
2024-03-13 14:25:33 -07:00
|
|
|
return ctx, errors.New(ctx, errors.NotFound, op, fmt.Sprintf("resource alias not found with value %q", v), errors.WithoutEvent())
|
2024-02-13 09:17:41 -08:00
|
|
|
}
|
|
|
|
|
if a.DestinationId == "" {
|
2024-03-13 14:25:33 -07:00
|
|
|
return ctx, errors.New(ctx, errors.NotFound, op, fmt.Sprintf("resource not found for alias value %q", v), errors.WithoutEvent())
|
2024-02-13 09:17:41 -08:00
|
|
|
}
|
|
|
|
|
r.Set(f, protoreflect.ValueOfString(a.DestinationId))
|
|
|
|
|
ctx = setCtxAliasInfo(ctx, a)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ctx, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// maybeAlias returns true if the input string is a candidate for being an alias.
|
|
|
|
|
func maybeAlias(s string) bool {
|
|
|
|
|
return !strings.Contains(s, "_") &&
|
|
|
|
|
len(s) > 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type key int
|
|
|
|
|
|
|
|
|
|
var aliasCtxKey key
|
|
|
|
|
|
|
|
|
|
func setCtxAliasInfo(ctx context.Context, a *Alias) context.Context {
|
|
|
|
|
return context.WithValue(ctx, aliasCtxKey, a)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FromContext returns the Alias from the context, if it exists. It will only
|
|
|
|
|
// exist if the original request contained an alias in a field marked as
|
|
|
|
|
// aliasable in the proto definition, and the alias was successfully resolved.
|
|
|
|
|
func FromContext(ctx context.Context) *Alias {
|
|
|
|
|
v, ok := ctx.Value(aliasCtxKey).(*Alias)
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return v
|
|
|
|
|
}
|