You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/internal/daemon/controller/handlers/verifiers.go

211 lines
5.5 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package handlers
import (
"regexp"
"strings"
"unicode"
"github.com/golang/protobuf/ptypes/timestamp"
"github.com/golang/protobuf/ptypes/wrappers"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
"google.golang.org/genproto/protobuf/field_mask"
)
type CustomValidatorFunc func() map[string]string
// A boundary resource identifier
type Id string
var NoopValidatorFn CustomValidatorFunc = func() map[string]string { return nil }
type ApiResource interface {
GetId() string
GetScope() *scopes.ScopeInfo
GetName() *wrappers.StringValue
GetDescription() *wrappers.StringValue
GetCreatedTime() *timestamp.Timestamp
GetUpdatedTime() *timestamp.Timestamp
GetVersion() uint32
}
func ValidateCreateRequest(i ApiResource, fn CustomValidatorFunc) error {
badFields := map[string]string{}
if i.GetId() != "" {
badFields["id"] = "This is a read only field."
}
if i.GetName() != nil {
trimmed := strings.TrimSpace(i.GetName().GetValue())
switch {
case trimmed == "":
badFields["name"] = "Cannot set empty string as name"
case !ValidName(trimmed):
badFields["name"] = "Name contains unprintable characters"
default:
i.GetName().Value = trimmed
}
}
if i.GetDescription() != nil {
trimmed := strings.TrimSpace(i.GetDescription().GetValue())
switch {
case trimmed == "":
badFields["description"] = "Cannot set empty string as description"
case !ValidDescription(trimmed):
badFields["description"] = "Description contains unprintable characters"
default:
i.GetDescription().Value = trimmed
}
}
if i.GetCreatedTime() != nil {
badFields["created_time"] = "This is a read only field."
}
if i.GetUpdatedTime() != nil {
badFields["updated_time"] = "This is a read only field."
}
if i.GetVersion() != 0 {
badFields["version"] = "Cannot specify this field in a create request."
}
for k, v := range fn() {
badFields[k] = v
}
if len(badFields) > 0 {
return InvalidArgumentErrorf("Error in provided request.", badFields)
}
return nil
}
type UpdateRequest interface {
GetId() string
GetUpdateMask() *field_mask.FieldMask
}
func ValidateUpdateRequest(r UpdateRequest, i ApiResource, fn CustomValidatorFunc, prefix ...string) error {
badFields := map[string]string{}
if !ValidId(Id(r.GetId()), prefix...) {
badFields["id"] = "Improperly formatted path identifier."
}
if r.GetUpdateMask() == nil {
badFields["update_mask"] = "UpdateMask not provided but is required to update this resource."
}
if i == nil {
// It is legitimate for no item to be specified in an update request as it indicates all fields provided in
// the mask will be marked as unset.
return nil
}
if i.GetName() != nil {
trimmed := strings.TrimSpace(i.GetName().GetValue())
switch {
case trimmed == "":
badFields["name"] = "Cannot set empty string as name"
case !ValidName(trimmed):
badFields["name"] = "Name contains unprintable characters"
default:
i.GetName().Value = trimmed
}
}
if i.GetDescription() != nil {
trimmed := strings.TrimSpace(i.GetDescription().GetValue())
switch {
case trimmed == "":
badFields["description"] = "Cannot set empty string as description"
case !ValidDescription(trimmed):
badFields["description"] = "Description contains unprintable characters"
default:
i.GetDescription().Value = trimmed
}
}
if i.GetVersion() == 0 {
badFields["version"] = "Existing resource version is required for an update."
}
if i.GetId() != "" {
badFields["id"] = "This is a read only field and cannot be specified in an update request."
}
if i.GetCreatedTime() != nil {
badFields["created_time"] = "This is a read only field and cannot be specified in an update request."
}
if i.GetUpdatedTime() != nil {
badFields["updated_time"] = "This is a read only field and cannot be specified in an update request."
}
for k, v := range fn() {
badFields[k] = v
}
if len(badFields) > 0 {
return InvalidArgumentErrorf("Error in provided request.", badFields)
}
return nil
}
type GetRequest interface {
GetId() string
}
func ValidateGetRequest(fn CustomValidatorFunc, r GetRequest, prefix ...string) error {
badFields := map[string]string{}
if !ValidId(Id(r.GetId()), prefix...) {
badFields["id"] = "Invalid formatted identifier."
}
for k, v := range fn() {
badFields[k] = v
}
if len(badFields) > 0 {
return InvalidArgumentErrorf("Error in provided request.", badFields)
}
return nil
}
type DeleteRequest interface {
GetId() string
}
func ValidateDeleteRequest(fn CustomValidatorFunc, r DeleteRequest, prefix ...string) error {
badFields := map[string]string{}
if !ValidId(Id(r.GetId()), prefix...) {
badFields["id"] = "Incorrectly formatted identifier."
}
for k, v := range fn() {
badFields[k] = v
}
if len(badFields) > 0 {
return InvalidArgumentErrorf("Error in provided request.", badFields)
}
return nil
}
var reInvalidID = regexp.MustCompile("[^A-Za-z0-9]")
func ValidId(i Id, prefixes ...string) bool {
id := string(i)
for _, prefix := range prefixes {
prefix = prefix + "_"
if !strings.HasPrefix(id, prefix) {
continue
}
id = strings.TrimPrefix(id, prefix)
if !reInvalidID.MatchString(id) {
return true
}
}
return false
}
func ValidDescription(in string) bool {
idx := strings.IndexFunc(in, func(c rune) bool {
return !(unicode.IsPrint(c) || unicode.IsSpace(c))
})
return idx == -1
}
func ValidName(in string) bool {
idx := strings.IndexFunc(in, func(c rune) bool {
return !unicode.IsPrint(c)
})
return idx == -1
}