diff --git a/internal/servers/controller/handlers/accounts/account_service.go b/internal/servers/controller/handlers/accounts/account_service.go index b139f74bc2..1bb106a665 100644 --- a/internal/servers/controller/handlers/accounts/account_service.go +++ b/internal/servers/controller/handlers/accounts/account_service.go @@ -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)) -} diff --git a/internal/servers/controller/handlers/authmethods/authmethod_service.go b/internal/servers/controller/handlers/authmethods/authmethod_service.go index aa587c3e6b..ff16865a5e 100644 --- a/internal/servers/controller/handlers/authmethods/authmethod_service.go +++ b/internal/servers/controller/handlers/authmethods/authmethod_service.go @@ -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)) -} diff --git a/internal/servers/controller/handlers/authtokens/authtoken_service.go b/internal/servers/controller/handlers/authtokens/authtoken_service.go index 6cfc7e79df..72df3266d8 100644 --- a/internal/servers/controller/handlers/authtokens/authtoken_service.go +++ b/internal/servers/controller/handlers/authtokens/authtoken_service.go @@ -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)) -} diff --git a/internal/servers/controller/handlers/groups/group_service.go b/internal/servers/controller/handlers/groups/group_service.go index fb0c2d1cbc..077f21bde8 100644 --- a/internal/servers/controller/handlers/groups/group_service.go +++ b/internal/servers/controller/handlers/groups/group_service.go @@ -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)) -} diff --git a/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go b/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go index 7800a07937..b3e50b9d0e 100644 --- a/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go +++ b/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go @@ -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)) -} diff --git a/internal/servers/controller/handlers/hosts/host_service.go b/internal/servers/controller/handlers/hosts/host_service.go index 9b844e9995..b12b1b0d29 100644 --- a/internal/servers/controller/handlers/hosts/host_service.go +++ b/internal/servers/controller/handlers/hosts/host_service.go @@ -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)) -} diff --git a/internal/servers/controller/handlers/roles/role_service.go b/internal/servers/controller/handlers/roles/role_service.go index adc9f9b31d..97fc8e8003 100644 --- a/internal/servers/controller/handlers/roles/role_service.go +++ b/internal/servers/controller/handlers/roles/role_service.go @@ -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)) -} diff --git a/internal/servers/controller/handlers/users/user_service.go b/internal/servers/controller/handlers/users/user_service.go index 4864576318..53bd97fb0e 100644 --- a/internal/servers/controller/handlers/users/user_service.go +++ b/internal/servers/controller/handlers/users/user_service.go @@ -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)) -} diff --git a/internal/servers/controller/handlers/verifiers.go b/internal/servers/controller/handlers/verifiers.go new file mode 100644 index 0000000000..8a9b252b52 --- /dev/null +++ b/internal/servers/controller/handlers/verifiers.go @@ -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)) +} diff --git a/internal/servers/controller/handlers/verifiers_test.go b/internal/servers/controller/handlers/verifiers_test.go new file mode 100644 index 0000000000..24d9ae1eaf --- /dev/null +++ b/internal/servers/controller/handlers/verifiers_test.go @@ -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) + }) + } +}