2021-11-04 10:43:08 -07:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
|
// SPDX-License-Identifier: BUSL-1.1
|
2023-02-02 17:15:17 -08:00
|
|
|
|
2020-07-20 11:17:09 -07:00
|
|
|
package handlers
|
|
|
|
|
|
|
|
|
|
import (
|
2023-06-06 17:09:08 -04:00
|
|
|
"context"
|
2020-07-20 11:17:09 -07:00
|
|
|
"fmt"
|
|
|
|
|
"strings"
|
|
|
|
|
|
2021-04-16 12:06:03 -05:00
|
|
|
"github.com/hashicorp/boundary/internal/errors"
|
2020-07-20 11:17:09 -07:00
|
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
|
"google.golang.org/protobuf/reflect/protoreflect"
|
|
|
|
|
"google.golang.org/protobuf/types/descriptorpb"
|
|
|
|
|
|
2021-08-26 11:04:32 -04:00
|
|
|
pb "github.com/hashicorp/boundary/sdk/pbs/controller/protooptions"
|
2020-07-20 11:17:09 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type MaskManager map[string]string
|
|
|
|
|
|
2021-06-22 22:26:26 -04:00
|
|
|
type (
|
|
|
|
|
MaskDestination []protoreflect.ProtoMessage
|
|
|
|
|
MaskSource []protoreflect.ProtoMessage
|
|
|
|
|
)
|
|
|
|
|
|
2021-11-04 10:43:08 -07:00
|
|
|
// NewMaskManager returns a mask manager that can translate field masks into
|
|
|
|
|
// the first proto from all subsequent protos assuming they are both using the
|
|
|
|
|
// mask_mapping custom option. Error is returned if no mappings are
|
2020-08-14 14:08:12 -07:00
|
|
|
// found or if one of the passed protos has a mapping that doesn't reciprocate.
|
2023-06-06 17:09:08 -04:00
|
|
|
func NewMaskManager(ctx context.Context, dest MaskDestination, src MaskSource) (MaskManager, error) {
|
2021-04-16 12:06:03 -05:00
|
|
|
const op = "handlers.NewMaskManager"
|
2023-06-06 17:09:08 -04:00
|
|
|
srcToDest, err := mapFromProto(ctx, src)
|
2020-08-14 14:08:12 -07:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2023-06-06 17:09:08 -04:00
|
|
|
destToSrc, err := mapFromProto(ctx, dest)
|
2020-08-14 14:08:12 -07:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2020-07-20 11:17:09 -07:00
|
|
|
|
|
|
|
|
result := make(map[string]string)
|
|
|
|
|
for k, v := range srcToDest {
|
|
|
|
|
ov, ok := destToSrc[v]
|
|
|
|
|
if !ok || ov != k {
|
2023-06-06 17:09:08 -04:00
|
|
|
return nil, errors.New(ctx, errors.Encode, op, fmt.Sprintf("mapping src field %q maps to %q, dest %q maps to %q", k, v, v, ov))
|
2020-07-20 11:17:09 -07:00
|
|
|
}
|
|
|
|
|
result[k] = v
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Now check to make sure there aren't any dangling dest mappings.
|
|
|
|
|
for k, v := range destToSrc {
|
|
|
|
|
if ov, ok := srcToDest[v]; !ok || ov != k {
|
2023-06-06 17:09:08 -04:00
|
|
|
return nil, errors.New(ctx, errors.Encode, op, fmt.Sprintf("mapping src field %q maps to %q, dest %q maps to %q", k, v, v, ov))
|
2020-07-20 11:17:09 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(result) == 0 {
|
2023-06-06 17:09:08 -04:00
|
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "mask mapping generated is zero")
|
2020-07-20 11:17:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-06 17:09:08 -04:00
|
|
|
func mapFromProto(ctx context.Context, ps []protoreflect.ProtoMessage) (map[string]string, error) {
|
2021-04-16 12:06:03 -05:00
|
|
|
const op = "handlers.mapFromProto"
|
2020-07-20 11:17:09 -07:00
|
|
|
mapping := make(map[string]string)
|
2020-08-14 14:08:12 -07:00
|
|
|
for _, p := range ps {
|
|
|
|
|
m := p.ProtoReflect()
|
|
|
|
|
fields := m.Descriptor().Fields()
|
|
|
|
|
for i := 0; i < fields.Len(); i++ {
|
|
|
|
|
f := fields.Get(i)
|
|
|
|
|
opts := f.Options().(*descriptorpb.FieldOptions)
|
|
|
|
|
if nameMap := proto.GetExtension(opts, pb.E_MaskMapping).(*pb.MaskMapping); !proto.Equal(nameMap, &pb.MaskMapping{}) && nameMap != nil {
|
|
|
|
|
if _, ok := mapping[nameMap.GetThis()]; ok {
|
2023-06-06 17:09:08 -04:00
|
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("duplicate mapping from field %q with the mapping key %q", f.Name(), nameMap.GetThis()))
|
2020-08-14 14:08:12 -07:00
|
|
|
}
|
|
|
|
|
mapping[nameMap.GetThis()] = nameMap.GetThat()
|
|
|
|
|
}
|
2020-07-20 11:17:09 -07:00
|
|
|
}
|
|
|
|
|
}
|
2020-08-14 14:08:12 -07:00
|
|
|
return mapping, nil
|
2020-07-20 11:17:09 -07:00
|
|
|
}
|
|
|
|
|
|
2021-11-04 10:43:08 -07:00
|
|
|
// Translate takes a field mask's paths and returns paths translated for the
|
|
|
|
|
// destination's protobuf. If a translation doesn't exist for a specific path
|
|
|
|
|
// entry then nothing is returned for that specific path entry unless
|
|
|
|
|
// passedThroughPrefix is used and applies to that entry.
|
|
|
|
|
//
|
|
|
|
|
// passedThroughPrefix is helpful when the field mask's paths cannot be mapped
|
|
|
|
|
// through the mask mapper for some reason (we don't know the mapping at compile
|
|
|
|
|
// time, for example) but we still want certain paths to be passed on. If a
|
|
|
|
|
// path entry isn't mapped to anything by the MaskManager but does contain
|
|
|
|
|
// a prefix which matches a passedThroughPrefix it will be added unmodified
|
|
|
|
|
// to the returned value.
|
|
|
|
|
func (m MaskManager) Translate(paths []string, passedThroughPrefix ...string) []string {
|
2020-07-20 11:17:09 -07:00
|
|
|
var result []string
|
|
|
|
|
for _, v := range paths {
|
|
|
|
|
vSplit := strings.Split(v, ",")
|
|
|
|
|
for _, v := range vSplit {
|
2021-11-04 10:43:08 -07:00
|
|
|
candidate := strings.TrimSpace(v)
|
|
|
|
|
if ov, ok := m[candidate]; ok {
|
2020-07-20 11:17:09 -07:00
|
|
|
result = append(result, ov)
|
2021-11-04 10:43:08 -07:00
|
|
|
} else {
|
|
|
|
|
for _, pre := range passedThroughPrefix {
|
|
|
|
|
if strings.HasPrefix(candidate, pre) {
|
|
|
|
|
result = append(result, candidate)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-07-20 11:17:09 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
}
|
2020-09-01 15:05:46 -07:00
|
|
|
|
|
|
|
|
func MaskContains(paths []string, s string) bool {
|
|
|
|
|
for _, p := range paths {
|
|
|
|
|
if p == s {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
2023-02-06 08:20:12 -08:00
|
|
|
|
|
|
|
|
func MaskContainsPrefix(paths []string, s string) bool {
|
|
|
|
|
for _, p := range paths {
|
|
|
|
|
if strings.Contains(p, s) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|