Request Validation logic moved into a helper (#296)

* Move all the validation logic into a helper tool.

* Adding tests for verifier helpers.
pull/290/head
Todd Knight 6 years ago committed by GitHub
parent bc32272ca7
commit e423b6589e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4,8 +4,6 @@ import (
"context"
"errors"
"fmt"
"regexp"
"strings"
"github.com/hashicorp/boundary/internal/auth"
"github.com/hashicorp/boundary/internal/auth/password"
@ -21,7 +19,6 @@ import (
var (
maskManager handlers.MaskManager
reInvalidID = regexp.MustCompile("[^A-Za-z0-9]")
)
func init() {
@ -67,7 +64,7 @@ func (s Service) ListAccounts(ctx context.Context, req *pbs.ListAccountsRequest)
return &pbs.ListAccountsResponse{Items: ul}, nil
}
// GetAccounts implements the interface pbs.AccountServiceServer.
// GetAccount implements the interface pbs.AccountServiceServer.
func (s Service) GetAccount(ctx context.Context, req *pbs.GetAccountRequest) (*pbs.GetAccountResponse, error) {
authResults := auth.Verify(ctx)
if authResults.Error != nil {
@ -353,76 +350,58 @@ func toProto(in *password.Account) (*pb.Account, error) {
// * All required parameters are set
// * There are no conflicting parameters provided
func validateGetRequest(req *pbs.GetAccountRequest) error {
badFields := map[string]string{}
if !validId(req.GetId(), password.AccountPrefix+"_") {
badFields["id"] = "Invalid formatted identifier."
}
if !validId(req.GetAuthMethodId(), password.AuthMethodPrefix+"_") {
badFields["auth_method_id"] = "Invalid formatted identifier."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields)
}
return nil
return handlers.ValidateGetRequest(password.AccountPrefix, req, func() map[string]string {
badFields := map[string]string{}
if !handlers.ValidId(password.AuthMethodPrefix, req.GetAuthMethodId()) {
badFields["auth_method_id"] = "Invalid formatted identifier."
}
return badFields
})
}
func validateCreateRequest(req *pbs.CreateAccountRequest) error {
badFields := map[string]string{}
if !validId(req.GetAuthMethodId(), password.AuthMethodPrefix+"_") {
badFields["auth_method_id"] = "Invalid formatted identifier."
}
item := req.GetItem()
if item.GetVersion() != 0 {
badFields["version"] = "Cannot specify this field in a create request."
}
if item.GetId() != "" {
badFields["id"] = "This is a read only field."
}
if item.GetAuthMethodId() != "" {
badFields["auth_method_id"] = "This is a read only field."
}
if item.GetCreatedTime() != nil {
badFields["created_time"] = "This is a read only field."
}
if item.GetUpdatedTime() != nil {
badFields["updated_time"] = "This is a read only field."
}
switch auth.SubtypeFromId(req.GetAuthMethodId()) {
case auth.PasswordSubtype:
if item.GetType() != "" && item.GetType() != auth.PasswordSubtype.String() {
badFields["type"] = "Doesn't match the parent resource's type."
return handlers.ValidateCreateRequest(req.GetItem(), func() map[string]string {
badFields := map[string]string{}
if req.GetItem().GetAuthMethodId() != "" {
badFields["auth_method_id"] = "This is a read only field."
}
pwAttrs := &pb.PasswordAccountAttributes{}
if err := handlers.StructToProto(item.GetAttributes(), pwAttrs); err != nil {
badFields["attributes"] = "Attribute fields do not match the expected format."
switch auth.SubtypeFromId(req.GetAuthMethodId()) {
case auth.PasswordSubtype:
if req.GetItem().GetType() != "" && req.GetItem().GetType() != auth.PasswordSubtype.String() {
badFields["type"] = "Doesn't match the parent resource's type."
}
pwAttrs := &pb.PasswordAccountAttributes{}
if err := handlers.StructToProto(req.GetItem().GetAttributes(), pwAttrs); err != nil {
badFields["attributes"] = "Attribute fields do not match the expected format."
}
if pwAttrs.GetLoginName() == "" {
badFields["login_name"] = "This is a required field for this type."
}
}
if pwAttrs.GetLoginName() == "" {
badFields["login_name"] = "This is a required field for this type."
}
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Argument errors found in the request.", badFields)
}
return nil
return badFields
})
}
func validateUpdateRequest(req *pbs.UpdateAccountRequest) error {
return handlers.ValidateUpdateRequest(password.AccountPrefix, req, req.GetItem(), func() map[string]string {
badFields := map[string]string{}
return badFields
})
}
func validateDeleteRequest(req *pbs.DeleteAccountRequest) error {
badFields := map[string]string{}
if !validId(req.GetAuthMethodId(), password.AuthMethodPrefix+"_") {
badFields["auth_method_id"] = "Invalid formatted identifier."
}
if !validId(req.GetId(), password.AccountPrefix+"_") {
badFields["id"] = "Incorrectly formatted identifier."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
}
return nil
return handlers.ValidateDeleteRequest(password.AccountPrefix, req, func() map[string]string {
badFields := map[string]string{}
if !handlers.ValidId(password.AuthMethodPrefix, req.GetAuthMethodId()) {
badFields["auth_method_id"] = "Invalid formatted identifier."
}
return badFields
})
}
func validateListRequest(req *pbs.ListAccountsRequest) error {
badFields := map[string]string{}
if !validId(req.GetAuthMethodId(), password.AuthMethodPrefix+"_") {
if !handlers.ValidId(password.AuthMethodPrefix, req.GetAuthMethodId()) {
badFields["auth_method_id"] = "Invalid formatted identifier."
}
if len(badFields) > 0 {
@ -433,10 +412,10 @@ func validateListRequest(req *pbs.ListAccountsRequest) error {
func validateChangePasswordRequest(req *pbs.ChangePasswordRequest) error {
badFields := map[string]string{}
if !validId(req.GetId(), password.AccountPrefix+"_") {
if !handlers.ValidId(password.AccountPrefix, req.GetId()) {
badFields["id"] = "Improperly formatted identifier."
}
if !validId(req.GetAuthMethodId(), password.AuthMethodPrefix+"_") {
if !handlers.ValidId(password.AuthMethodPrefix, req.GetAuthMethodId()) {
badFields["auth_method_id"] = "Invalid formatted identifier."
}
if req.GetVersion() == 0 {
@ -456,10 +435,10 @@ func validateChangePasswordRequest(req *pbs.ChangePasswordRequest) error {
func validateSetPasswordRequest(req *pbs.SetPasswordRequest) error {
badFields := map[string]string{}
if !validId(req.GetId(), password.AccountPrefix+"_") {
if !handlers.ValidId(password.AccountPrefix, req.GetId()) {
badFields["id"] = "Improperly formatted identifier."
}
if !validId(req.GetAuthMethodId(), password.AuthMethodPrefix+"_") {
if !handlers.ValidId(password.AuthMethodPrefix, req.GetAuthMethodId()) {
badFields["auth_method_id"] = "Invalid formatted identifier."
}
if req.GetVersion() == 0 {
@ -470,54 +449,3 @@ func validateSetPasswordRequest(req *pbs.SetPasswordRequest) error {
}
return nil
}
func validateUpdateRequest(req *pbs.UpdateAccountRequest) error {
badFields := map[string]string{}
if !validId(req.GetId(), password.AccountPrefix+"_") {
badFields["id"] = "Improperly formatted identifier."
}
if !validId(req.GetAuthMethodId(), password.AuthMethodPrefix+"_") {
badFields["auth_method_id"] = "Improperly formatted identifier."
}
if req.GetUpdateMask() == nil {
badFields["update_mask"] = "UpdateMask not provided but is required to update this resource."
}
item := req.GetItem()
if item == 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 item.GetVersion() == 0 {
badFields["version"] = "Existing resource version is required for an update."
}
if item.GetId() != "" {
badFields["id"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetAuthMethodId() != "" {
badFields["auth_method_id"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetCreatedTime() != nil {
badFields["created_time"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetUpdatedTime() != nil {
badFields["updated_time"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetType() != "" {
badFields["type"] = "This is a read only field and cannot be specified in an update request."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
}
return nil
}
func validId(id, prefix string) bool {
if !strings.HasPrefix(id, prefix) {
return false
}
id = strings.TrimPrefix(id, prefix)
return !reInvalidID.Match([]byte(id))
}

@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"regexp"
"strings"
"github.com/hashicorp/boundary/internal/auth"
"github.com/hashicorp/boundary/internal/auth/password"
@ -65,7 +64,7 @@ func (s Service) ListAuthMethods(ctx context.Context, req *pbs.ListAuthMethodsRe
return &pbs.ListAuthMethodsResponse{Items: ul}, nil
}
// GetAuthMethods implements the interface pbs.AuthMethodServiceServer.
// GetAuthMethod implements the interface pbs.AuthMethodServiceServer.
func (s Service) GetAuthMethod(ctx context.Context, req *pbs.GetAuthMethodRequest) (*pbs.GetAuthMethodResponse, error) {
authResults := auth.Verify(ctx)
if authResults.Error != nil {
@ -286,106 +285,43 @@ func toProto(in *password.AuthMethod) (*pb.AuthMethod, error) {
// * All required parameters are set
// * There are no conflicting parameters provided
func validateGetRequest(req *pbs.GetAuthMethodRequest) error {
badFields := map[string]string{}
if !validId(req.GetId(), password.AuthMethodPrefix+"_") {
badFields["id"] = "Invalid formatted id."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields)
}
return nil
}
func validateListRequest(req *pbs.ListAuthMethodsRequest) error {
badFields := map[string]string{}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields)
}
return nil
return handlers.ValidateGetRequest(password.AuthMethodPrefix, req, handlers.NoopValidatorFn)
}
func validateCreateRequest(req *pbs.CreateAuthMethodRequest) error {
badFields := map[string]string{}
item := req.GetItem()
if item.GetId() != "" {
badFields["id"] = "This is a read only field."
}
if item.GetCreatedTime() != nil {
badFields["created_time"] = "This is a read only field."
}
if item.GetUpdatedTime() != nil {
badFields["updated_time"] = "This is a read only field."
}
if item.GetVersion() != 0 {
badFields["version"] = "Cannot specify this field in a create request."
}
switch auth.SubtypeFromType(item.GetType()) {
case auth.PasswordSubtype:
pwAttrs := &pb.PasswordAuthMethodAttributes{}
if err := handlers.StructToProto(item.GetAttributes(), pwAttrs); err != nil {
badFields["attributes"] = "Attribute fields do not match the expected format."
return handlers.ValidateCreateRequest(req.GetItem(), func() map[string]string {
badFields := map[string]string{}
switch auth.SubtypeFromType(req.GetItem().GetType()) {
case auth.PasswordSubtype:
pwAttrs := &pb.PasswordAuthMethodAttributes{}
if err := handlers.StructToProto(req.GetItem().GetAttributes(), pwAttrs); err != nil {
badFields["attributes"] = "Attribute fields do not match the expected format."
}
default:
badFields["type"] = fmt.Sprintf("This is a required field and must be %q.", auth.PasswordSubtype.String())
}
default:
badFields["type"] = fmt.Sprintf("This is a required field and must be %q.", auth.PasswordSubtype.String())
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Argument errors found in the request.", badFields)
}
return nil
return badFields
})
}
func validateUpdateRequest(req *pbs.UpdateAuthMethodRequest) error {
badFields := map[string]string{}
if !validId(req.GetId(), password.AuthMethodPrefix+"_") {
badFields["id"] = "Improperly formatted identifier."
}
if req.GetUpdateMask() == nil {
badFields["update_mask"] = "UpdateMask not provided but is required to update this resource."
}
item := req.GetItem()
if item == 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 item.GetVersion() == 0 {
badFields["version"] = "Existing resource version is required for an update."
}
if item.GetId() != "" {
badFields["id"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetCreatedTime() != nil {
badFields["created_time"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetUpdatedTime() != nil {
badFields["updated_time"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetType() != "" {
badFields["type"] = "This is a read only field and cannot be specified in an update request."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
}
return nil
return handlers.ValidateUpdateRequest(password.AuthMethodPrefix, req, req.GetItem(), func() map[string]string {
badFields := map[string]string{}
if req.GetItem().GetType() != "" {
badFields["type"] = "This is a read only field and cannot be specified in an update request."
}
return badFields
})
}
func validateDeleteRequest(req *pbs.DeleteAuthMethodRequest) error {
return handlers.ValidateDeleteRequest(password.AuthMethodPrefix, req, handlers.NoopValidatorFn)
}
func validateListRequest(_ *pbs.ListAuthMethodsRequest) error {
badFields := map[string]string{}
if !validId(req.GetId(), password.AuthMethodPrefix+"_") {
badFields["id"] = "Incorrectly formatted identifier."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields)
}
return nil
}
func validId(id, prefix string) bool {
if !strings.HasPrefix(id, prefix) {
return false
}
id = strings.TrimPrefix(id, prefix)
return !reInvalidID.Match([]byte(id))
}

@ -4,8 +4,6 @@ import (
"context"
"errors"
"fmt"
"regexp"
"strings"
"github.com/hashicorp/boundary/internal/auth"
"github.com/hashicorp/boundary/internal/authtoken"
@ -19,10 +17,6 @@ import (
const orgIdFieldName = "org_id"
var (
reInvalidID = regexp.MustCompile("[^A-Za-z0-9]")
)
// Service handles request as described by the pbs.AuthTokenServiceServer interface.
type Service struct {
repoFn func() (*authtoken.Repository, error)
@ -57,7 +51,7 @@ func (s Service) ListAuthTokens(ctx context.Context, req *pbs.ListAuthTokensRequ
return &pbs.ListAuthTokensResponse{Items: ul}, nil
}
// GetAuthTokens implements the interface pbs.AuthTokenServiceServer.
// GetAuthToken implements the interface pbs.AuthTokenServiceServer.
func (s Service) GetAuthToken(ctx context.Context, req *pbs.GetAuthTokenRequest) (*pbs.GetAuthTokenResponse, error) {
authResults := auth.Verify(ctx)
if authResults.Error != nil {
@ -159,39 +153,17 @@ func toProto(in *authtoken.AuthToken) *pb.AuthToken {
// * All required parameters are set
// * There are no conflicting parameters provided
func validateGetRequest(req *pbs.GetAuthTokenRequest) error {
badFields := map[string]string{}
if !validId(req.GetId(), authtoken.AuthTokenPrefix+"_") {
badFields["id"] = "Invalid formatted user id."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields)
}
return nil
return handlers.ValidateGetRequest(authtoken.AuthTokenPrefix, req, handlers.NoopValidatorFn)
}
func validateDeleteRequest(req *pbs.DeleteAuthTokenRequest) error {
badFields := map[string]string{}
if !validId(req.GetId(), authtoken.AuthTokenPrefix+"_") {
badFields["id"] = "Incorrectly formatted identifier."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
}
return nil
return handlers.ValidateDeleteRequest(authtoken.AuthTokenPrefix, req, handlers.NoopValidatorFn)
}
func validateListRequest(req *pbs.ListAuthTokensRequest) error {
func validateListRequest(_ *pbs.ListAuthTokensRequest) error {
badFields := map[string]string{}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields)
}
return nil
}
func validId(id, prefix string) bool {
if !strings.HasPrefix(id, prefix) {
return false
}
id = strings.TrimPrefix(id, prefix)
return !reInvalidID.Match([]byte(id))
}

@ -4,8 +4,6 @@ import (
"context"
"errors"
"fmt"
"regexp"
"strings"
"github.com/hashicorp/boundary/internal/auth"
"github.com/hashicorp/boundary/internal/db"
@ -21,7 +19,6 @@ import (
var (
maskManager handlers.MaskManager
reInvalidID = regexp.MustCompile("[^A-Za-z0-9]")
)
func init() {
@ -373,80 +370,19 @@ func toProto(in *iam.Group, members []*iam.GroupMember) *pb.Group {
// * All required parameters are set
// * There are no conflicting parameters provided
func validateGetRequest(req *pbs.GetGroupRequest) error {
badFields := map[string]string{}
if !validId(req.GetId(), iam.GroupPrefix+"_") {
badFields["id"] = "Invalid formatted group id."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields)
}
return nil
return handlers.ValidateGetRequest(iam.GroupPrefix, req, handlers.NoopValidatorFn)
}
func validateCreateRequest(req *pbs.CreateGroupRequest) error {
badFields := map[string]string{}
item := req.GetItem()
if item.GetId() != "" {
badFields["id"] = "This is a read only field."
}
if item.GetCreatedTime() != nil {
badFields["created_time"] = "This is a read only field."
}
if item.GetUpdatedTime() != nil {
badFields["updated_time"] = "This is a read only field."
}
if item.GetVersion() != 0 {
badFields["version"] = "Cannot specify this field in a create request."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Argument errors found in the request.", badFields)
}
return nil
return handlers.ValidateCreateRequest(req.GetItem(), handlers.NoopValidatorFn)
}
func validateUpdateRequest(req *pbs.UpdateGroupRequest) error {
badFields := map[string]string{}
if !validId(req.GetId(), iam.GroupPrefix+"_") {
badFields["group_id"] = "Improperly formatted path identifier."
}
if req.GetUpdateMask() == nil {
badFields["update_mask"] = "UpdateMask not provided but is required to update a group."
}
item := req.GetItem()
if item == 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 item.GetVersion() == 0 {
badFields["version"] = "Existing resource version is required for an update."
}
if item.GetId() != "" {
badFields["id"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetCreatedTime() != nil {
badFields["created_time"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetUpdatedTime() != nil {
badFields["updated_time"] = "This is a read only field and cannot be specified in an update request."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
}
return nil
return handlers.ValidateUpdateRequest(iam.GroupPrefix, req, req.GetItem(), handlers.NoopValidatorFn)
}
func validateDeleteRequest(req *pbs.DeleteGroupRequest) error {
badFields := map[string]string{}
if !validId(req.GetId(), iam.GroupPrefix+"_") {
badFields["id"] = "Incorrectly formatted identifier."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
}
return nil
return handlers.ValidateDeleteRequest(iam.GroupPrefix, req, handlers.NoopValidatorFn)
}
func validateListRequest(req *pbs.ListGroupsRequest) error {
@ -459,7 +395,7 @@ func validateListRequest(req *pbs.ListGroupsRequest) error {
func validateAddGroupMembersRequest(req *pbs.AddGroupMembersRequest) error {
badFields := map[string]string{}
if !validId(req.GetId(), iam.GroupPrefix+"_") {
if !handlers.ValidId(iam.GroupPrefix, req.GetId()) {
badFields["id"] = "Incorrectly formatted identifier."
}
if req.GetVersion() == 0 {
@ -481,7 +417,7 @@ func validateAddGroupMembersRequest(req *pbs.AddGroupMembersRequest) error {
func validateSetGroupMembersRequest(req *pbs.SetGroupMembersRequest) error {
badFields := map[string]string{}
if !validId(req.GetId(), iam.GroupPrefix+"_") {
if !handlers.ValidId(iam.GroupPrefix, req.GetId()) {
badFields["id"] = "Incorrectly formatted identifier."
}
if req.GetVersion() == 0 {
@ -500,7 +436,7 @@ func validateSetGroupMembersRequest(req *pbs.SetGroupMembersRequest) error {
func validateRemoveGroupMembersRequest(req *pbs.RemoveGroupMembersRequest) error {
badFields := map[string]string{}
if !validId(req.GetId(), iam.GroupPrefix+"_") {
if !handlers.ValidId(iam.GroupPrefix, req.GetId()) {
badFields["id"] = "Incorrectly formatted identifier."
}
if req.GetVersion() == 0 {
@ -514,11 +450,3 @@ func validateRemoveGroupMembersRequest(req *pbs.RemoveGroupMembersRequest) error
}
return nil
}
func validId(id, prefix string) bool {
if !strings.HasPrefix(id, prefix) {
return false
}
id = strings.TrimPrefix(id, prefix)
return !reInvalidID.Match([]byte(id))
}

@ -3,8 +3,6 @@ package host_catalogs
import (
"context"
"fmt"
"regexp"
"strings"
"github.com/hashicorp/boundary/internal/auth"
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/hosts"
@ -21,7 +19,6 @@ import (
var (
maskManager handlers.MaskManager
reInvalidID = regexp.MustCompile("[^A-Za-z0-9]")
)
func init() {
@ -260,110 +257,43 @@ func toProto(in *static.HostCatalog) *pb.HostCatalog {
// * The type asserted by the ID and/or field is known
// * If relevant, the type derived from the id prefix matches what is claimed by the type field
func validateGetRequest(req *pbs.GetHostCatalogRequest) error {
badFields := map[string]string{}
if !validId(req.GetId(), static.HostCatalogPrefix+"_") {
badFields["id"] = "Invalid formatted identifier."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Invalid arguments provided.", badFields)
}
return nil
}
func validateListRequest(req *pbs.ListHostCatalogsRequest) error {
badFields := map[string]string{}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields)
}
return nil
return handlers.ValidateGetRequest(static.HostCatalogPrefix, req, handlers.NoopValidatorFn)
}
func validateCreateRequest(req *pbs.CreateHostCatalogRequest) error {
badFields := map[string]string{}
item := req.GetItem()
if item == nil {
badFields["item"] = "This field is required."
}
if item.GetVersion() != 0 {
badFields["version"] = "Cannot specify this field in a create request."
}
switch host.SubtypeFromType(item.GetType()) {
case host.StaticSubtype:
shcAttrs := &pb.StaticHostCatalogDetails{}
if err := handlers.StructToProto(item.GetAttributes(), shcAttrs); err != nil {
badFields["attributes"] = "Attribute fields do not match the expected format."
return handlers.ValidateCreateRequest(req.GetItem(), func() map[string]string {
badFields := map[string]string{}
switch host.SubtypeFromType(req.GetItem().GetType()) {
case host.StaticSubtype:
shcAttrs := &pb.StaticHostCatalogDetails{}
if err := handlers.StructToProto(req.GetItem().GetAttributes(), shcAttrs); err != nil {
badFields["attributes"] = "Attribute fields do not match the expected format."
}
default:
badFields["type"] = fmt.Sprintf("This is a required field and must be %q.", host.StaticSubtype.String())
}
default:
badFields["type"] = fmt.Sprintf("This is a required field and must be %q.", host.StaticSubtype.String())
}
if item.GetId() != "" {
badFields["id"] = "This field is read only."
}
if item.GetCreatedTime() != nil {
badFields["created_time"] = "This field is read only."
}
if item.GetUpdatedTime() != nil {
badFields["updated_time"] = "This field is read only."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Invalid arguments provided.", badFields)
}
return nil
return badFields
})
}
func validateUpdateRequest(req *pbs.UpdateHostCatalogRequest) error {
badFields := map[string]string{}
if !validId(req.GetId(), static.HostCatalogPrefix+"_") {
badFields["id"] = "The field is incorrectly formatted."
}
if req.GetUpdateMask() == nil {
badFields["update_mask"] = "This field is required."
}
item := req.GetItem()
if item == 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 item.GetVersion() == 0 {
badFields["version"] = "Existing resource version is required for an update."
}
if item.GetType() != "" {
badFields["type"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetId() != "" {
badFields["id"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetCreatedTime() != nil {
badFields["created_time"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetUpdatedTime() != nil {
badFields["updated_time"] = "This is a read only field and cannot be specified in an update request."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
}
return nil
return handlers.ValidateUpdateRequest(static.HostCatalogPrefix, req, req.GetItem(), func() map[string]string {
badFields := map[string]string{}
if req.GetItem().GetType() != "" {
badFields["type"] = "This is a read only field and cannot be specified in an update request."
}
return badFields
})
}
func validateDeleteRequest(req *pbs.DeleteHostCatalogRequest) error {
return handlers.ValidateDeleteRequest(static.HostCatalogPrefix, req, handlers.NoopValidatorFn)
}
func validateListRequest(req *pbs.ListHostCatalogsRequest) error {
badFields := map[string]string{}
if !validId(req.GetId(), static.HostCatalogPrefix+"_") {
badFields["id"] = "The field is incorrectly formatted."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Invalid arguments provided.", badFields)
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields)
}
return nil
}
func validId(id, prefix string) bool {
if !strings.HasPrefix(id, prefix) {
return false
}
id = strings.TrimPrefix(id, prefix)
return !reInvalidID.Match([]byte(id))
}

@ -4,7 +4,6 @@ import (
"context"
"fmt"
"regexp"
"strings"
"github.com/hashicorp/boundary/internal/auth"
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/hosts"
@ -295,116 +294,71 @@ func toProto(in *static.Host, members []*static.HostSetMember) (*pb.Host, error)
// * The type asserted by the ID and/or field is known
// * If relevant, the type derived from the id prefix matches what is claimed by the type field
func validateGetRequest(req *pbs.GetHostRequest) error {
badFields := map[string]string{}
if !validId(req.GetHostCatalogId(), static.HostCatalogPrefix+"_") {
badFields["host_catalog_id"] = "The field is incorrectly formatted."
}
if !validId(req.GetId(), static.HostPrefix+"_") {
badFields["id"] = "Invalid formatted identifier."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Invalid arguments provided.", badFields)
}
return nil
return handlers.ValidateGetRequest(static.HostPrefix, req, func() map[string]string {
badFields := map[string]string{}
if !handlers.ValidId(static.HostCatalogPrefix, req.GetHostCatalogId()) {
badFields["host_catalog_id"] = "The field is incorrectly formatted."
}
return badFields
})
}
func validateCreateRequest(req *pbs.CreateHostRequest) error {
badFields := map[string]string{}
if !validId(req.GetHostCatalogId(), static.HostCatalogPrefix+"_") {
badFields["host_catalog_id"] = "The field is incorrectly formatted."
}
item := req.GetItem()
if item == nil {
return handlers.InvalidArgumentErrorf("Invalid arguments provided.", map[string]string{"item": "this field is required."})
}
ha := &pb.StaticHostAttributes{}
if err := handlers.StructToProto(item.GetAttributes(), ha); err != nil {
badFields["attributes"] = "Incorrectly formatted attribute for this type."
}
if ha.GetAddress() == nil {
badFields["attributes.address"] = "This field is required."
}
switch host.SubtypeFromId(req.GetHostCatalogId()) {
case host.StaticSubtype:
if item.GetType() != "" && item.GetType() != host.StaticSubtype.String() {
badFields["type"] = "Doesn't match the parent resource's type."
return handlers.ValidateCreateRequest(req.GetItem(), func() map[string]string {
badFields := map[string]string{}
if !handlers.ValidId(static.HostCatalogPrefix, req.GetHostCatalogId()) {
badFields["host_catalog_id"] = "The field is incorrectly formatted."
}
attrs := &pb.StaticHostAttributes{}
if err := handlers.StructToProto(item.GetAttributes(), attrs); err != nil {
badFields["attributes"] = "Attribute fields do not match the expected format."
if req.GetItem().GetHostCatalogId() != "" {
badFields["host_catalog_id"] = "This field is read only."
}
if attrs.GetAddress() == nil {
badFields["attributes.address"] = "This is a required field for this type."
switch host.SubtypeFromId(req.GetHostCatalogId()) {
case host.StaticSubtype:
if req.GetItem().GetType() != "" && req.GetItem().GetType() != host.StaticSubtype.String() {
badFields["type"] = "Doesn't match the parent resource's type."
}
attrs := &pb.StaticHostAttributes{}
if err := handlers.StructToProto(req.GetItem().GetAttributes(), attrs); err != nil {
badFields["attributes"] = "Attribute fields do not match the expected format."
}
if attrs.GetAddress() == nil {
badFields["attributes.address"] = "This is a required field for this type."
}
}
}
if item.GetId() != "" {
badFields["id"] = "This field is read only."
}
if item.GetHostCatalogId() != "" {
badFields["host_catalog_id"] = "This field is read only."
}
if item.GetCreatedTime() != nil {
badFields["created_time"] = "This field is read only."
}
if item.GetUpdatedTime() != nil {
badFields["updated_time"] = "This field is read only."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Invalid arguments provided.", badFields)
}
return nil
return badFields
})
}
func validateUpdateRequest(req *pbs.UpdateHostRequest) error {
badFields := map[string]string{}
if !validId(req.GetId(), static.HostPrefix+"_") {
badFields["id"] = "The field is incorrectly formatted."
}
if !validId(req.GetHostCatalogId(), static.HostCatalogPrefix+"_") {
badFields["host_catalog_id"] = "The field is incorrectly formatted."
}
if req.GetUpdateMask() == nil {
badFields["update_mask"] = "This field is required."
}
item := req.GetItem()
if item == 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 item.GetVersion() == 0 {
badFields["version"] = "Existing resource version is required for an update."
}
if item.GetType() != "" {
badFields["type"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetHostCatalogId() != "" {
badFields["host_catalog_id"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetId() != "" {
badFields["id"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetCreatedTime() != nil {
badFields["created_time"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetUpdatedTime() != nil {
badFields["updated_time"] = "This is a read only field and cannot be specified in an update request."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
}
return handlers.ValidateUpdateRequest(static.HostPrefix, req, req.GetItem(), func() map[string]string {
badFields := map[string]string{}
if !handlers.ValidId(static.HostCatalogPrefix, req.GetHostCatalogId()) {
badFields["host_catalog_id"] = "The field is incorrectly formatted."
}
if req.GetItem().GetHostCatalogId() != "" {
badFields["host_catalog_id"] = "This is a read only field and cannot be specified in an update request."
}
if req.GetItem().GetType() != "" {
badFields["type"] = "This is a read only field and cannot be specified in an update request."
}
return badFields
})
}
return nil
func validateDeleteRequest(req *pbs.DeleteHostRequest) error {
return handlers.ValidateDeleteRequest(static.HostPrefix, req, func() map[string]string {
badFields := map[string]string{}
if !handlers.ValidId(static.HostCatalogPrefix, req.GetHostCatalogId()) {
badFields["host_catalog_id"] = "The field is incorrectly formatted."
}
return badFields
})
}
func validateListRequest(req *pbs.ListHostsRequest) error {
badFields := map[string]string{}
if !validId(req.GetHostCatalogId(), static.HostCatalogPrefix+"_") {
if !handlers.ValidId(static.HostCatalogPrefix, req.GetHostCatalogId()) {
badFields["host_catalog_id"] = "The field is incorrectly formatted."
}
if len(badFields) > 0 {
@ -412,25 +366,3 @@ func validateListRequest(req *pbs.ListHostsRequest) error {
}
return nil
}
func validateDeleteRequest(req *pbs.DeleteHostRequest) error {
badFields := map[string]string{}
if !validId(req.GetHostCatalogId(), static.HostCatalogPrefix+"_") {
badFields["host_catalog_id"] = "The field is incorrectly formatted."
}
if !validId(req.GetId(), static.HostPrefix+"_") {
badFields["id"] = "The field is incorrectly formatted."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Invalid arguments provided.", badFields)
}
return nil
}
func validId(id, prefix string) bool {
if !strings.HasPrefix(id, prefix) {
return false
}
id = strings.TrimPrefix(id, prefix)
return !reInvalidID.Match([]byte(id))
}

@ -4,8 +4,6 @@ import (
"context"
"errors"
"fmt"
"regexp"
"strings"
"github.com/hashicorp/boundary/internal/auth"
"github.com/hashicorp/boundary/internal/db"
@ -16,6 +14,7 @@ import (
"github.com/hashicorp/boundary/internal/iam/store"
"github.com/hashicorp/boundary/internal/perms"
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
"github.com/hashicorp/boundary/internal/types/scope"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/wrapperspb"
@ -23,7 +22,6 @@ import (
var (
maskManager handlers.MaskManager
reInvalidID = regexp.MustCompile("[^A-Za-z0-9]")
)
func init() {
@ -528,105 +526,54 @@ func toProto(in *iam.Role, principals []iam.PrincipalRole, grants []*iam.RoleGra
// * All required parameters are set
// * There are no conflicting parameters provided
func validateGetRequest(req *pbs.GetRoleRequest) error {
badFields := map[string]string{}
if !validId(req.GetId(), iam.RolePrefix+"_") {
badFields["id"] = "Invalid formatted role id."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields)
}
return nil
return handlers.ValidateGetRequest(iam.RolePrefix, req, handlers.NoopValidatorFn)
}
func validateCreateRequest(req *pbs.CreateRoleRequest, scope *scopes.ScopeInfo) error {
badFields := map[string]string{}
item := req.GetItem()
if item.GetId() != "" {
badFields["id"] = "This is a read only field."
}
if item.GetCreatedTime() != nil {
badFields["created_time"] = "This is a read only field."
}
if item.GetUpdatedTime() != nil {
badFields["updated_time"] = "This is a read only field."
}
if item.GetGrantScopeId() != nil && scope.GetType() == "project" {
if item.GetGrantScopeId().Value != scope.GetId() {
badFields["grant_scope_id"] = "Must be empty or set to the project_id when the scope type is project."
func validateCreateRequest(req *pbs.CreateRoleRequest, s *scopes.ScopeInfo) error {
return handlers.ValidateCreateRequest(req.GetItem(), func() map[string]string {
badFields := map[string]string{}
item := req.GetItem()
if item.GetGrantScopeId() != nil && s.GetType() == scope.Project.String() {
if item.GetGrantScopeId().Value != s.GetId() {
badFields["grant_scope_id"] = "Must be empty or set to the project_id when the scope type is project."
}
}
}
if item.GetPrincipals() != nil {
badFields["principals"] = "This is a read only field."
}
if item.GetGrants() != nil {
badFields["grant_strings"] = "This is a read only field."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Argument errors found in the request.", badFields)
}
return nil
if item.GetPrincipals() != nil {
badFields["principals"] = "This is a read only field."
}
if item.GetGrants() != nil {
badFields["grant_strings"] = "This is a read only field."
}
return badFields
})
}
func validateUpdateRequest(req *pbs.UpdateRoleRequest, scope *scopes.ScopeInfo) error {
badFields := map[string]string{}
if !validId(req.GetId(), iam.RolePrefix+"_") {
badFields["role_id"] = "Improperly formatted path identifier."
}
if req.GetUpdateMask() == nil {
badFields["update_mask"] = "UpdateMask not provided but is required to update a role."
}
item := req.GetItem()
if item == 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 item.GetVersion() == 0 {
badFields["version"] = "Existing resource version is required for an update."
}
if item.GetId() != "" {
badFields["id"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetCreatedTime() != nil {
badFields["created_time"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetUpdatedTime() != nil {
badFields["updated_time"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetPrincipalIds() != nil {
badFields["principal_ids"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetPrincipals() != nil {
badFields["principals"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetGrants() != nil {
badFields["grants"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetGrantStrings() != nil {
badFields["grant_strings"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetGrantScopeId() != nil && scope.GetType() == "project" {
if item.GetGrantScopeId().Value != scope.GetId() {
badFields["grant_scope_id"] = "Must be empty or set to the project_id when the scope type is project."
func validateUpdateRequest(req *pbs.UpdateRoleRequest, s *scopes.ScopeInfo) error {
return handlers.ValidateUpdateRequest(iam.RolePrefix, req, req.GetItem(), func() map[string]string {
badFields := map[string]string{}
if req.GetItem().GetPrincipalIds() != nil {
badFields["principal_ids"] = "This is a read only field and cannot be specified in an update request."
}
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
}
return nil
if req.GetItem().GetPrincipals() != nil {
badFields["principals"] = "This is a read only field and cannot be specified in an update request."
}
if req.GetItem().GetGrants() != nil {
badFields["grants"] = "This is a read only field and cannot be specified in an update request."
}
if req.GetItem().GetGrantStrings() != nil {
badFields["grant_strings"] = "This is a read only field and cannot be specified in an update request."
}
if req.GetItem().GetGrantScopeId() != nil && s.GetType() == scope.Project.String() {
if req.GetItem().GetGrantScopeId().Value != s.GetId() {
badFields["grant_scope_id"] = "Must be empty or set to the project_id when the scope type is project."
}
}
return badFields
})
}
func validateDeleteRequest(req *pbs.DeleteRoleRequest) error {
badFields := map[string]string{}
if !validId(req.GetId(), iam.RolePrefix+"_") {
badFields["id"] = "Incorrectly formatted identifier."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
}
return nil
return handlers.ValidateDeleteRequest(iam.RolePrefix, req, handlers.NoopValidatorFn)
}
func validateListRequest(req *pbs.ListRolesRequest) error {
@ -639,7 +586,7 @@ func validateListRequest(req *pbs.ListRolesRequest) error {
func validateAddRolePrincipalsRequest(req *pbs.AddRolePrincipalsRequest) error {
badFields := map[string]string{}
if !validId(req.GetRoleId(), iam.RolePrefix+"_") {
if !handlers.ValidId(iam.RolePrefix, req.GetRoleId()) {
badFields["id"] = "Incorrectly formatted identifier."
}
if req.GetVersion() == 0 {
@ -661,7 +608,7 @@ func validateAddRolePrincipalsRequest(req *pbs.AddRolePrincipalsRequest) error {
func validateSetRolePrincipalsRequest(req *pbs.SetRolePrincipalsRequest) error {
badFields := map[string]string{}
if !validId(req.GetRoleId(), iam.RolePrefix+"_") {
if !handlers.ValidId(iam.RolePrefix, req.GetRoleId()) {
badFields["id"] = "Incorrectly formatted identifier."
}
if req.GetVersion() == 0 {
@ -680,7 +627,7 @@ func validateSetRolePrincipalsRequest(req *pbs.SetRolePrincipalsRequest) error {
func validateRemoveRolePrincipalsRequest(req *pbs.RemoveRolePrincipalsRequest) error {
badFields := map[string]string{}
if !validId(req.GetRoleId(), iam.RolePrefix+"_") {
if !handlers.ValidId(iam.RolePrefix, req.GetRoleId()) {
badFields["id"] = "Incorrectly formatted identifier."
}
if req.GetVersion() == 0 {
@ -697,7 +644,7 @@ func validateRemoveRolePrincipalsRequest(req *pbs.RemoveRolePrincipalsRequest) e
func validateAddRoleGrantsRequest(req *pbs.AddRoleGrantsRequest) error {
badFields := map[string]string{}
if !validId(req.GetRoleId(), iam.RolePrefix+"_") {
if !handlers.ValidId(iam.RolePrefix, req.GetRoleId()) {
badFields["id"] = "Incorrectly formatted identifier."
}
if req.GetVersion() == 0 {
@ -714,7 +661,7 @@ func validateAddRoleGrantsRequest(req *pbs.AddRoleGrantsRequest) error {
func validateSetRoleGrantsRequest(req *pbs.SetRoleGrantsRequest) error {
badFields := map[string]string{}
if !validId(req.GetRoleId(), iam.RolePrefix+"_") {
if !handlers.ValidId(iam.RolePrefix, req.GetRoleId()) {
badFields["id"] = "Incorrectly formatted identifier."
}
if req.GetVersion() == 0 {
@ -728,7 +675,7 @@ func validateSetRoleGrantsRequest(req *pbs.SetRoleGrantsRequest) error {
func validateRemoveRoleGrantsRequest(req *pbs.RemoveRoleGrantsRequest) error {
badFields := map[string]string{}
if !validId(req.GetRoleId(), iam.RolePrefix+"_") {
if !handlers.ValidId(iam.RolePrefix, req.GetRoleId()) {
badFields["id"] = "Incorrectly formatted identifier."
}
if req.GetVersion() == 0 {
@ -742,11 +689,3 @@ func validateRemoveRoleGrantsRequest(req *pbs.RemoveRoleGrantsRequest) error {
}
return nil
}
func validId(id, prefix string) bool {
if !strings.HasPrefix(id, prefix) {
return false
}
id = strings.TrimPrefix(id, prefix)
return !reInvalidID.Match([]byte(id))
}

@ -4,8 +4,6 @@ import (
"context"
"errors"
"fmt"
"regexp"
"strings"
"github.com/hashicorp/boundary/internal/auth"
"github.com/hashicorp/boundary/internal/db"
@ -21,7 +19,6 @@ import (
var (
maskManager handlers.MaskManager
reInvalidID = regexp.MustCompile("[^A-Za-z0-9]")
)
func init() {
@ -261,80 +258,19 @@ func toProto(in *iam.User) *pb.User {
// * All required parameters are set
// * There are no conflicting parameters provided
func validateGetRequest(req *pbs.GetUserRequest) error {
badFields := map[string]string{}
if !validId(req.GetId(), iam.UserPrefix+"_") {
badFields["id"] = "Invalid formatted user id."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields)
}
return nil
return handlers.ValidateGetRequest(iam.UserPrefix, req, handlers.NoopValidatorFn)
}
func validateCreateRequest(req *pbs.CreateUserRequest) error {
badFields := map[string]string{}
item := req.GetItem()
if item.GetId() != "" {
badFields["id"] = "This is a read only field."
}
if item.GetCreatedTime() != nil {
badFields["created_time"] = "This is a read only field."
}
if item.GetUpdatedTime() != nil {
badFields["updated_time"] = "This is a read only field."
}
if item.GetVersion() != 0 {
badFields["version"] = "Cannot specify this field in a create request."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Argument errors found in the request.", badFields)
}
return nil
return handlers.ValidateCreateRequest(req.GetItem(), handlers.NoopValidatorFn)
}
func validateUpdateRequest(req *pbs.UpdateUserRequest) error {
badFields := map[string]string{}
if !validId(req.GetId(), iam.UserPrefix+"_") {
badFields["user_id"] = "Improperly formatted path identifier."
}
if req.GetUpdateMask() == nil {
badFields["update_mask"] = "UpdateMask not provided but is required to update a user."
}
item := req.GetItem()
if item == 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 item.GetVersion() == 0 {
badFields["version"] = "Existing resource version is required for an update."
}
if item.GetId() != "" {
badFields["id"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetCreatedTime() != nil {
badFields["created_time"] = "This is a read only field and cannot be specified in an update request."
}
if item.GetUpdatedTime() != nil {
badFields["updated_time"] = "This is a read only field and cannot be specified in an update request."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
}
return nil
return handlers.ValidateUpdateRequest(iam.UserPrefix, req, req.GetItem(), handlers.NoopValidatorFn)
}
func validateDeleteRequest(req *pbs.DeleteUserRequest) error {
badFields := map[string]string{}
if !validId(req.GetId(), iam.UserPrefix+"_") {
badFields["id"] = "Incorrectly formatted identifier."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
}
return nil
return handlers.ValidateDeleteRequest(iam.UserPrefix, req, handlers.NoopValidatorFn)
}
func validateListRequest(req *pbs.ListUsersRequest) error {
@ -344,11 +280,3 @@ func validateListRequest(req *pbs.ListUsersRequest) error {
}
return nil
}
func validId(id, prefix string) bool {
if !strings.HasPrefix(id, prefix) {
return false
}
id = strings.TrimPrefix(id, prefix)
return !reInvalidID.Match([]byte(id))
}

@ -0,0 +1,137 @@
package handlers
import (
"regexp"
"strings"
"github.com/golang/protobuf/ptypes/timestamp"
"github.com/golang/protobuf/ptypes/wrappers"
"github.com/hashicorp/boundary/internal/gen/controller/api/resources/scopes"
"google.golang.org/genproto/protobuf/field_mask"
)
type CustomValidatorFunc func() map[string]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.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("Argument errors found in the request.", badFields)
}
return nil
}
type UpdateRequest interface {
GetId() string
GetUpdateMask() *field_mask.FieldMask
}
func ValidateUpdateRequest(prefix string, r UpdateRequest, i ApiResource, fn CustomValidatorFunc) error {
badFields := map[string]string{}
if !ValidId(prefix, r.GetId()) {
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.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("Errors in provided fields.", badFields)
}
return nil
}
type GetRequest interface {
GetId() string
}
func ValidateGetRequest(prefix string, r GetRequest, fn CustomValidatorFunc) error {
badFields := map[string]string{}
if !ValidId(prefix, r.GetId()) {
badFields["id"] = "Invalid formatted group id."
}
for k, v := range fn() {
badFields[k] = v
}
if len(badFields) > 0 {
return InvalidArgumentErrorf("Improperly formatted identifier.", badFields)
}
return nil
}
type DeleteRequest interface {
GetId() string
}
func ValidateDeleteRequest(prefix string, r DeleteRequest, fn CustomValidatorFunc) error {
badFields := map[string]string{}
if !ValidId(prefix, r.GetId()) {
badFields["id"] = "Incorrectly formatted identifier."
}
for k, v := range fn() {
badFields[k] = v
}
if len(badFields) > 0 {
return InvalidArgumentErrorf("Errors in provided fields.", badFields)
}
return nil
}
var reInvalidID = regexp.MustCompile("[^A-Za-z0-9]")
func ValidId(prefix, id string) bool {
prefix = prefix + "_"
if !strings.HasPrefix(id, prefix) {
return false
}
id = strings.TrimPrefix(id, prefix)
return !reInvalidID.Match([]byte(id))
}

@ -0,0 +1,389 @@
package handlers
import (
"testing"
pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/users"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/fieldmaskpb"
"google.golang.org/protobuf/types/known/timestamppb"
)
func errorIncludesFields(t *testing.T, err error, wantFields []string) {
t.Helper()
s, ok := status.FromError(err)
require.True(t, ok)
require.Len(t, s.Details(), 1)
rd, ok := s.Details()[0].(*errdetails.BadRequest)
require.True(t, ok)
var gotFields []string
for _, d := range rd.FieldViolations {
gotFields = append(gotFields, d.Field)
}
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("prefix", "prefix_somerandomid"))
assert.True(t, ValidId("prefix", "prefix_short"))
assert.True(t, ValidId("prefix", "prefix_thisisalongidentifierwhichstillworks"))
assert.False(t, ValidId("prefix", "prefixsomerandomid"))
assert.False(t, ValidId("prefix", "prefix_this has spaces"))
assert.False(t, ValidId("prefix", "prefix_includes-dash"))
assert.False(t, ValidId("prefix", "prefix_other@strange!characters"))
assert.False(t, ValidId("short", "prefix_short"))
}
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.prefix, tc.req, tc.valFn)
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.prefix, tc.req, tc.valFn)
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"},
},
}
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: "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.prefix, tc.req, tc.item, tc.valFn)
if len(tc.badFields) == 0 {
if !assert.NoError(t, err) {
errorIncludesFields(t, err, []string{})
}
return
}
errorIncludesFields(t, err, tc.badFields)
})
}
}
Loading…
Cancel
Save