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_test.go

488 lines
12 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package handlers
import (
"errors"
"testing"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/users"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/fieldmaskpb"
"google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
)
func errorIncludesFields(t *testing.T, err error, wantFields []string) {
t.Helper()
var apiErr *ApiError
require.True(t, errors.As(err, &apiErr))
var gotFields []string
for _, d := range apiErr.Inner.GetDetails().GetRequestFields() {
gotFields = append(gotFields, d.GetName())
}
assert.ElementsMatch(t, gotFields, wantFields)
}
// Throughout this test we will use the User requests, but we could use any request
// message. User was picked arbitrarily.
func TestValidId(t *testing.T) {
assert.True(t, ValidId(Id("prefix_somerandomid"), "prefix"))
assert.True(t, ValidId(Id("prefix_somerandomid"), "notprefix", "prefix"))
assert.True(t, ValidId(Id("prefix_short"), "prefix"))
assert.True(t, ValidId(Id("prefix_thisisalongidentifierwhichstillworks"), "prefix"))
assert.False(t, ValidId(Id("prefixsomerandomid"), "prefix"))
assert.False(t, ValidId(Id("prefixsomerandomid"), "prefix", "alsobadprefix"))
assert.False(t, ValidId(Id("prefix_this has spaces"), "prefix"))
assert.False(t, ValidId(Id("prefix_includes-dash"), "prefix"))
assert.False(t, ValidId(Id("prefix_other@strange!characters"), "prefix"))
assert.False(t, ValidId(Id("prefix_short"), "short"))
}
func TestValidDescription(t *testing.T) {
assert.True(t, ValidDescription("foobar"))
assert.True(t, ValidDescription("this is\n a long description"))
assert.False(t, ValidDescription("foo\u200Bbar"))
}
func TestValidName(t *testing.T) {
assert.True(t, ValidName("foobar"))
assert.False(t, ValidName("this is\n a long description"))
assert.False(t, ValidName("foo\u200Bbar"))
}
func TestValidateGetRequest(t *testing.T) {
cases := []struct {
name string
prefix string
req GetRequest
valFn CustomValidatorFunc
badFields []string
}{
{
name: "noopvalidator no error",
prefix: "prefix",
req: &pbs.GetUserRequest{
Id: "prefix_someidentifier",
},
valFn: NoopValidatorFn,
},
{
name: "noopvalidator bad prefix",
prefix: "bad",
req: &pbs.GetUserRequest{
Id: "prefix_someidentifier",
},
valFn: NoopValidatorFn,
badFields: []string{"id"},
},
{
name: "custom field error",
prefix: "prefix",
req: &pbs.GetUserRequest{
Id: "prefix_someidentifier",
},
valFn: func() map[string]string {
return map[string]string{"test": "test"}
},
badFields: []string{"test"},
},
{
name: "both custom error and prefix",
prefix: "bad",
req: &pbs.GetUserRequest{
Id: "prefix_someidentifier",
},
valFn: func() map[string]string {
return map[string]string{"test": "test"}
},
badFields: []string{"id", "test"},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
err := ValidateGetRequest(tc.valFn, tc.req, tc.prefix)
if len(tc.badFields) == 0 {
if !assert.NoError(t, err) {
errorIncludesFields(t, err, []string{})
}
return
}
errorIncludesFields(t, err, tc.badFields)
})
}
}
func TestValidateDeleteRequest(t *testing.T) {
cases := []struct {
name string
prefix string
req DeleteRequest
valFn CustomValidatorFunc
badFields []string
}{
{
name: "noopvalidator no error",
prefix: "prefix",
req: &pbs.DeleteUserRequest{
Id: "prefix_someidentifier",
},
valFn: NoopValidatorFn,
},
{
name: "noopvalidator bad prefix",
prefix: "bad",
req: &pbs.DeleteUserRequest{
Id: "prefix_someidentifier",
},
valFn: NoopValidatorFn,
badFields: []string{"id"},
},
{
name: "custom field error",
prefix: "prefix",
req: &pbs.DeleteUserRequest{
Id: "prefix_someidentifier",
},
valFn: func() map[string]string {
return map[string]string{"test": "test"}
},
badFields: []string{"test"},
},
{
name: "both custom error and prefix",
prefix: "bad",
req: &pbs.DeleteUserRequest{
Id: "prefix_someidentifier",
},
valFn: func() map[string]string {
return map[string]string{"test": "test"}
},
badFields: []string{"id", "test"},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
err := ValidateDeleteRequest(tc.valFn, tc.req, tc.prefix)
if len(tc.badFields) == 0 {
if !assert.NoError(t, err) {
errorIncludesFields(t, err, []string{})
}
return
}
errorIncludesFields(t, err, tc.badFields)
})
}
}
func TestValidateCreateRequest(t *testing.T) {
cases := []struct {
name string
item ApiResource
valFn CustomValidatorFunc
badFields []string
}{
{
name: "valid",
item: &pb.User{},
valFn: NoopValidatorFn,
},
{
name: "disallow set id",
item: &pb.User{
Id: "anything",
},
valFn: NoopValidatorFn,
badFields: []string{"id"},
},
{
name: "disallow set created",
item: &pb.User{
CreatedTime: timestamppb.Now(),
},
valFn: NoopValidatorFn,
badFields: []string{"created_time"},
},
{
name: "disallow set updated",
item: &pb.User{
UpdatedTime: timestamppb.Now(),
},
valFn: NoopValidatorFn,
badFields: []string{"updated_time"},
},
{
name: "disallow set version",
item: &pb.User{
Version: 4,
},
valFn: NoopValidatorFn,
badFields: []string{"version"},
},
{
name: "custom validator error",
item: &pb.User{},
valFn: func() map[string]string {
return map[string]string{"test": "test"}
},
badFields: []string{"test"},
},
{
name: "disallow several fields",
item: &pb.User{
Id: "anything",
CreatedTime: timestamppb.Now(),
Version: 4,
},
valFn: func() map[string]string {
return map[string]string{"test": "test"}
},
badFields: []string{"id", "created_time", "version", "test"},
},
{
name: "good name",
item: &pb.User{
Name: wrapperspb.String("foobar"),
},
valFn: NoopValidatorFn,
},
{
name: "bad name",
item: &pb.User{
Name: wrapperspb.String("foo\u200Bbar"),
},
valFn: NoopValidatorFn,
badFields: []string{"name"},
},
{
name: "good description",
item: &pb.User{
Description: wrapperspb.String("foobar"),
},
valFn: NoopValidatorFn,
},
{
name: "bad description",
item: &pb.User{
Description: wrapperspb.String("foo\u200Bbar"),
},
valFn: NoopValidatorFn,
badFields: []string{"description"},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
err := ValidateCreateRequest(tc.item, tc.valFn)
if len(tc.badFields) == 0 {
if !assert.NoError(t, err) {
errorIncludesFields(t, err, []string{})
}
return
}
errorIncludesFields(t, err, tc.badFields)
})
}
}
func TestValidateUpdateRequest(t *testing.T) {
cases := []struct {
name string
prefix string
req UpdateRequest
item ApiResource
valFn CustomValidatorFunc
badFields []string
}{
{
name: "valid",
prefix: "prefix",
req: &pbs.UpdateUserRequest{
Id: "prefix_something",
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"anything"}},
},
item: &pb.User{
Version: 1,
},
valFn: NoopValidatorFn,
},
{
name: "missing mask",
prefix: "prefix",
req: &pbs.UpdateUserRequest{
Id: "prefix_something",
},
item: &pb.User{
Version: 1,
},
valFn: NoopValidatorFn,
badFields: []string{"update_mask"},
},
{
name: "bad id",
prefix: "prefix",
req: &pbs.UpdateUserRequest{
Id: "mismatched_prefix",
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"anything"}},
},
item: &pb.User{
Version: 1,
},
valFn: NoopValidatorFn,
badFields: []string{"id"},
},
{
name: "good name",
prefix: "prefix",
req: &pbs.UpdateUserRequest{
Id: "prefix_something",
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"anything"}},
},
item: &pb.User{
Version: 1,
Name: wrapperspb.String("foobar"),
},
valFn: NoopValidatorFn,
},
{
name: "bad name",
prefix: "prefix",
req: &pbs.UpdateUserRequest{
Id: "prefix_something",
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"anything"}},
},
item: &pb.User{
Version: 1,
Name: wrapperspb.String("foo\u200Bbar"),
},
valFn: NoopValidatorFn,
badFields: []string{"name"},
},
{
name: "good description",
prefix: "prefix",
req: &pbs.UpdateUserRequest{
Id: "prefix_something",
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"anything"}},
},
item: &pb.User{
Version: 1,
Description: wrapperspb.String("foobar"),
},
valFn: NoopValidatorFn,
},
{
name: "bad description",
prefix: "prefix",
req: &pbs.UpdateUserRequest{
Id: "prefix_something",
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"anything"}},
},
item: &pb.User{
Version: 1,
Description: wrapperspb.String("foo\u200Bbar"),
},
valFn: NoopValidatorFn,
badFields: []string{"description"},
},
{
name: "missing version",
prefix: "prefix",
req: &pbs.UpdateUserRequest{
Id: "prefix_something",
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"anything"}},
},
item: &pb.User{},
valFn: NoopValidatorFn,
badFields: []string{"version"},
},
{
name: "bad create time",
prefix: "prefix",
req: &pbs.UpdateUserRequest{
Id: "prefix_something",
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"anything"}},
},
item: &pb.User{
Version: 1,
CreatedTime: timestamppb.Now(),
},
valFn: NoopValidatorFn,
badFields: []string{"created_time"},
},
{
name: "bad updated time",
prefix: "prefix",
req: &pbs.UpdateUserRequest{
Id: "prefix_something",
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"anything"}},
},
item: &pb.User{
Version: 1,
UpdatedTime: timestamppb.Now(),
},
valFn: NoopValidatorFn,
badFields: []string{"updated_time"},
},
{
name: "bad defined id on item",
prefix: "prefix",
req: &pbs.UpdateUserRequest{
Id: "prefix_something",
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"anything"}},
},
item: &pb.User{
Id: "prefix_something",
Version: 1,
},
valFn: NoopValidatorFn,
badFields: []string{"id"},
},
{
name: "custom validator error",
prefix: "prefix",
req: &pbs.UpdateUserRequest{
Id: "prefix_something",
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"anything"}},
},
item: &pb.User{
Version: 1,
},
valFn: func() map[string]string {
return map[string]string{
"test": "test",
}
},
badFields: []string{"test"},
},
{
name: "multiple",
prefix: "prefix",
req: &pbs.UpdateUserRequest{},
item: &pb.User{},
valFn: NoopValidatorFn,
badFields: []string{"id", "update_mask", "version"},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
err := ValidateUpdateRequest(tc.req, tc.item, tc.valFn, tc.prefix)
if len(tc.badFields) == 0 {
if !assert.NoError(t, err) {
errorIncludesFields(t, err, []string{})
}
return
}
errorIncludesFields(t, err, tc.badFields)
})
}
}