diff --git a/api/scopes/scope.gen.go b/api/scopes/scope.gen.go index e1eff431df..580bbeb570 100644 --- a/api/scopes/scope.gen.go +++ b/api/scopes/scope.gen.go @@ -22,6 +22,7 @@ type Scope struct { CreatedTime time.Time `json:"created_time,omitempty"` UpdatedTime time.Time `json:"updated_time,omitempty"` Version uint32 `json:"version,omitempty"` + Type string `json:"type,omitempty"` } // Client is a client for this collection diff --git a/internal/gen/controller.swagger.json b/internal/gen/controller.swagger.json index 05970f2365..5d690e0c75 100644 --- a/internal/gen/controller.swagger.json +++ b/internal/gen/controller.swagger.json @@ -4686,6 +4686,10 @@ "format": "int64", "description": "The version can be used in subsequent write requests to ensure this\nresource has not changed and to fail the write if it has.\nOutput only.", "readOnly": true + }, + "type": { + "type": "string", + "title": "The type of the resource" } }, "title": "Scope contains all fields related to a Scope resource" diff --git a/internal/gen/controller/api/resources/scopes/scope.pb.go b/internal/gen/controller/api/resources/scopes/scope.pb.go index 7ada9e1214..7028d71429 100644 --- a/internal/gen/controller/api/resources/scopes/scope.pb.go +++ b/internal/gen/controller/api/resources/scopes/scope.pb.go @@ -145,6 +145,8 @@ type Scope struct { // resource has not changed and to fail the write if it has. // Output only. Version uint32 `protobuf:"varint,80,opt,name=version,proto3" json:"version,omitempty"` + // The type of the resource + Type string `protobuf:"bytes,90,opt,name=type,proto3" json:"type,omitempty"` } func (x *Scope) Reset() { @@ -235,6 +237,13 @@ func (x *Scope) GetVersion() uint32 { return 0 } +func (x *Scope) GetType() string { + if x != nil { + return x.Type + } + return "" +} + var File_controller_api_resources_scopes_v1_scope_proto protoreflect.FileDescriptor var file_controller_api_resources_scopes_v1_scope_proto_rawDesc = []byte{ @@ -259,7 +268,7 @@ var file_controller_api_resources_scopes_v1_scope_proto_rawDesc = []byte{ 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x0f, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, - 0x5f, 0x69, 0x64, 0x22, 0xbe, 0x03, 0x0a, 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x0e, 0x0a, + 0x5f, 0x69, 0x64, 0x22, 0xd2, 0x03, 0x0a, 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x12, 0x43, 0x0a, 0x05, 0x73, 0x63, 0x6f, @@ -287,13 +296,14 @@ var file_controller_api_resources_scopes_v1_scope_proto_rawDesc = []byte{ 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x50, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x53, 0x5a, 0x51, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, - 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, - 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, - 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x73, 0x63, 0x6f, 0x70, - 0x65, 0x73, 0x3b, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x5a, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x42, 0x53, 0x5a, 0x51, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, + 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, + 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, + 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x3b, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/internal/proto/local/controller/api/resources/scopes/v1/scope.proto b/internal/proto/local/controller/api/resources/scopes/v1/scope.proto index a1b247c679..d523dc3d64 100644 --- a/internal/proto/local/controller/api/resources/scopes/v1/scope.proto +++ b/internal/proto/local/controller/api/resources/scopes/v1/scope.proto @@ -61,4 +61,7 @@ message Scope { // resource has not changed and to fail the write if it has. // Output only. uint32 version = 80; + + // The type of the resource + string type = 90; } diff --git a/internal/servers/controller/handlers/accounts/account_service.go b/internal/servers/controller/handlers/accounts/account_service.go index fe5aac50f9..e33c08a248 100644 --- a/internal/servers/controller/handlers/accounts/account_service.go +++ b/internal/servers/controller/handlers/accounts/account_service.go @@ -419,7 +419,20 @@ func validateCreateRequest(req *pbs.CreateAccountRequest) error { } func validateUpdateRequest(req *pbs.UpdateAccountRequest) error { - return handlers.ValidateUpdateRequest(password.AccountPrefix, req, req.GetItem(), handlers.NoopValidatorFn) + return handlers.ValidateUpdateRequest(password.AccountPrefix, req, req.GetItem(), func() map[string]string { + badFields := map[string]string{} + switch auth.SubtypeFromId(req.GetId()) { + case auth.PasswordSubtype: + if req.GetItem().GetType() != "" && req.GetItem().GetType() != auth.PasswordSubtype.String() { + badFields["type"] = "Cannot modify resource type." + } + pwAttrs := &pb.PasswordAccountAttributes{} + if err := handlers.StructToProto(req.GetItem().GetAttributes(), pwAttrs); err != nil { + badFields["attributes"] = "Attribute fields do not match the expected format." + } + } + return badFields + }) } func validateDeleteRequest(req *pbs.DeleteAccountRequest) error { diff --git a/internal/servers/controller/handlers/accounts/account_service_test.go b/internal/servers/controller/handlers/accounts/account_service_test.go index 6c160b3a20..bd66edc39f 100644 --- a/internal/servers/controller/handlers/accounts/account_service_test.go +++ b/internal/servers/controller/handlers/accounts/account_service_test.go @@ -517,6 +517,7 @@ func TestUpdate(t *testing.T) { Item: &pb.Account{ Name: &wrapperspb.StringValue{Value: "new"}, Description: &wrapperspb.StringValue{Value: "desc"}, + Type: "password", }, }, res: &pbs.UpdateAccountResponse{ @@ -540,6 +541,7 @@ func TestUpdate(t *testing.T) { Item: &pb.Account{ Name: &wrapperspb.StringValue{Value: "new"}, Description: &wrapperspb.StringValue{Value: "desc"}, + Type: "password", }, }, res: &pbs.UpdateAccountResponse{ @@ -565,6 +567,19 @@ func TestUpdate(t *testing.T) { }, errCode: codes.InvalidArgument, }, + { + name: "Cant change type", + req: &pbs.UpdateAccountRequest{ + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"name"}, + }, + Item: &pb.Account{ + Name: &wrapperspb.StringValue{Value: ""}, + Type: "oidc", + }, + }, + errCode: codes.InvalidArgument, + }, { name: "No Paths in Mask", req: &pbs.UpdateAccountRequest{ diff --git a/internal/servers/controller/handlers/authmethods/authmethod_service.go b/internal/servers/controller/handlers/authmethods/authmethod_service.go index 10141b44b4..0e23c3acbe 100644 --- a/internal/servers/controller/handlers/authmethods/authmethod_service.go +++ b/internal/servers/controller/handlers/authmethods/authmethod_service.go @@ -362,8 +362,17 @@ func validateCreateRequest(req *pbs.CreateAuthMethodRequest) error { func validateUpdateRequest(req *pbs.UpdateAuthMethodRequest) error { 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." + switch auth.SubtypeFromId(req.GetId()) { + case auth.PasswordSubtype: + if req.GetItem().GetType() != "" && auth.SubtypeFromType(req.GetItem().GetType()) != auth.PasswordSubtype { + badFields["type"] = "Cannot modify resource type." + } + 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["id"] = "Incorrectly formatted identifier." } return badFields }) diff --git a/internal/servers/controller/handlers/authmethods/authmethod_service_test.go b/internal/servers/controller/handlers/authmethods/authmethod_service_test.go index 5cd6154ff7..36c8c42b01 100644 --- a/internal/servers/controller/handlers/authmethods/authmethod_service_test.go +++ b/internal/servers/controller/handlers/authmethods/authmethod_service_test.go @@ -529,6 +529,7 @@ func TestUpdate(t *testing.T) { Item: &pb.AuthMethod{ Name: &wrapperspb.StringValue{Value: "new"}, Description: &wrapperspb.StringValue{Value: "desc"}, + Type: "password", }, }, res: &pbs.UpdateAuthMethodResponse{ @@ -550,11 +551,12 @@ func TestUpdate(t *testing.T) { name: "Multiple Paths in single string", req: &pbs.UpdateAuthMethodRequest{ UpdateMask: &field_mask.FieldMask{ - Paths: []string{"name,description"}, + Paths: []string{"name,description,type"}, }, Item: &pb.AuthMethod{ Name: &wrapperspb.StringValue{Value: "new"}, Description: &wrapperspb.StringValue{Value: "desc"}, + Type: "password", }, }, res: &pbs.UpdateAuthMethodResponse{ @@ -604,6 +606,17 @@ func TestUpdate(t *testing.T) { }, errCode: codes.InvalidArgument, }, + { + name: "Cant change type", + req: &pbs.UpdateAuthMethodRequest{ + UpdateMask: &field_mask.FieldMask{Paths: []string{"name", "type"}}, + Item: &pb.AuthMethod{ + Name: &wrapperspb.StringValue{Value: "updated name"}, + Type: "oidc", + }, + }, + errCode: codes.InvalidArgument, + }, { name: "Unset Name", req: &pbs.UpdateAuthMethodRequest{ 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 8e5e42ef5a..c4f41b74b1 100644 --- a/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go +++ b/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/boundary/internal/servers/controller/handlers" "github.com/hashicorp/boundary/internal/types/action" "github.com/hashicorp/boundary/internal/types/resource" + "github.com/hashicorp/boundary/internal/types/scope" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/wrapperspb" @@ -314,6 +315,9 @@ func validateGetRequest(req *pbs.GetHostCatalogRequest) error { func validateCreateRequest(req *pbs.CreateHostCatalogRequest) error { return handlers.ValidateCreateRequest(req.GetItem(), func() map[string]string { badFields := map[string]string{} + if !handlers.ValidId(scope.Project.Prefix(), req.GetItem().GetScopeId()) { + badFields["scope_id"] = "This field must be a valid project scope id." + } switch host.SubtypeFromType(req.GetItem().GetType()) { case host.StaticSubtype: default: @@ -326,8 +330,11 @@ func validateCreateRequest(req *pbs.CreateHostCatalogRequest) error { func validateUpdateRequest(req *pbs.UpdateHostCatalogRequest) error { 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." + switch host.SubtypeFromId(req.GetId()) { + case host.StaticSubtype: + if req.GetItem().GetType() != "" && host.SubtypeFromType(req.GetItem().GetType()) != host.StaticSubtype { + badFields["type"] = "Cannot modify resource type." + } } return badFields }) @@ -339,6 +346,9 @@ func validateDeleteRequest(req *pbs.DeleteHostCatalogRequest) error { func validateListRequest(req *pbs.ListHostCatalogsRequest) error { badFields := map[string]string{} + if !handlers.ValidId(scope.Project.Prefix(), req.GetScopeId()) { + badFields["scope_id"] = "This field must be a valid project scope id." + } if len(badFields) > 0 { return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields) } diff --git a/internal/servers/controller/handlers/host_catalogs/host_catalog_service_test.go b/internal/servers/controller/handlers/host_catalogs/host_catalog_service_test.go index 0aed2594f7..6f66d0b443 100644 --- a/internal/servers/controller/handlers/host_catalogs/host_catalog_service_test.go +++ b/internal/servers/controller/handlers/host_catalogs/host_catalog_service_test.go @@ -198,6 +198,11 @@ func TestList(t *testing.T) { scopeId: scope.Project.Prefix() + "_DoesntExis", errCode: codes.PermissionDenied, }, + { + name: "Bad scope level", + scopeId: scope.Global.String(), + errCode: codes.InvalidArgument, + }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { @@ -318,6 +323,26 @@ func TestCreate(t *testing.T) { }, errCode: codes.OK, }, + { + name: "Cant create in org", + req: &pbs.CreateHostCatalogRequest{Item: &pb.HostCatalog{ + ScopeId: proj.GetParentId(), + Name: &wrappers.StringValue{Value: "name"}, + Description: &wrappers.StringValue{Value: "desc"}, + Type: "static", + }}, + errCode: codes.InvalidArgument, + }, + { + name: "Cant create in global", + req: &pbs.CreateHostCatalogRequest{Item: &pb.HostCatalog{ + ScopeId: scope.Global.String(), + Name: &wrappers.StringValue{Value: "name"}, + Description: &wrappers.StringValue{Value: "desc"}, + Type: "static", + }}, + errCode: codes.InvalidArgument, + }, { name: "Create with unknown type", req: &pbs.CreateHostCatalogRequest{Item: &pb.HostCatalog{ @@ -433,6 +458,7 @@ func TestUpdate(t *testing.T) { Item: &pb.HostCatalog{ Name: &wrappers.StringValue{Value: "new"}, Description: &wrappers.StringValue{Value: "desc"}, + Type: "static", }, }, res: &pbs.UpdateHostCatalogResponse{ @@ -457,6 +483,7 @@ func TestUpdate(t *testing.T) { Item: &pb.HostCatalog{ Name: &wrappers.StringValue{Value: "new"}, Description: &wrappers.StringValue{Value: "desc"}, + Type: "static", }, }, res: &pbs.UpdateHostCatalogResponse{ @@ -596,6 +623,19 @@ func TestUpdate(t *testing.T) { }, errCode: codes.OK, }, + { + name: "Cant change type", + req: &pbs.UpdateHostCatalogRequest{ + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"name", "type"}, + }, + Item: &pb.HostCatalog{ + Name: &wrappers.StringValue{Value: "updated name"}, + Type: "ec2", + }, + }, + errCode: codes.InvalidArgument, + }, { name: "Update a Non Existing HostCatalog", req: &pbs.UpdateHostCatalogRequest{ diff --git a/internal/servers/controller/handlers/host_sets/host_set_service.go b/internal/servers/controller/handlers/host_sets/host_set_service.go index 0538f0c683..75a678558d 100644 --- a/internal/servers/controller/handlers/host_sets/host_set_service.go +++ b/internal/servers/controller/handlers/host_sets/host_set_service.go @@ -430,8 +430,11 @@ func validateCreateRequest(req *pbs.CreateHostSetRequest) error { func validateUpdateRequest(req *pbs.UpdateHostSetRequest) error { return handlers.ValidateUpdateRequest(static.HostSetPrefix, 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." + switch host.SubtypeFromId(req.GetId()) { + case host.StaticSubtype: + if req.GetItem().GetType() != "" && req.GetItem().GetType() != host.StaticSubtype.String() { + badFields["type"] = "Cannot modify the resource type." + } } return badFields }) diff --git a/internal/servers/controller/handlers/host_sets/host_set_service_test.go b/internal/servers/controller/handlers/host_sets/host_set_service_test.go index 400c4bf429..72d44bed8d 100644 --- a/internal/servers/controller/handlers/host_sets/host_set_service_test.go +++ b/internal/servers/controller/handlers/host_sets/host_set_service_test.go @@ -462,11 +462,12 @@ func TestUpdate(t *testing.T) { name: "Update an Existing Host", req: &pbs.UpdateHostSetRequest{ UpdateMask: &field_mask.FieldMask{ - Paths: []string{"name", "description"}, + Paths: []string{"name", "description", "type"}, }, Item: &pb.HostSet{ Name: &wrappers.StringValue{Value: "new"}, Description: &wrappers.StringValue{Value: "desc"}, + Type: "static", }, }, res: &pbs.UpdateHostSetResponse{ @@ -487,11 +488,12 @@ func TestUpdate(t *testing.T) { name: "Multiple Paths in single string", req: &pbs.UpdateHostSetRequest{ UpdateMask: &field_mask.FieldMask{ - Paths: []string{"name,description"}, + Paths: []string{"name,description,type"}, }, Item: &pb.HostSet{ Name: &wrappers.StringValue{Value: "new"}, Description: &wrappers.StringValue{Value: "desc"}, + Type: "static", }, }, res: &pbs.UpdateHostSetResponse{ @@ -508,6 +510,20 @@ func TestUpdate(t *testing.T) { }, errCode: codes.OK, }, + { + name: "Cant modify type", + req: &pbs.UpdateHostSetRequest{ + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"name,type"}, + }, + Item: &pb.HostSet{ + Name: &wrappers.StringValue{Value: "updated name"}, + Description: &wrappers.StringValue{Value: "updated desc"}, + Type: "ec2", + }, + }, + errCode: codes.InvalidArgument, + }, { name: "No Update Mask", req: &pbs.UpdateHostSetRequest{ diff --git a/internal/servers/controller/handlers/hosts/host_service.go b/internal/servers/controller/handlers/hosts/host_service.go index d6151c7307..b456502863 100644 --- a/internal/servers/controller/handlers/hosts/host_service.go +++ b/internal/servers/controller/handlers/hosts/host_service.go @@ -362,13 +362,14 @@ func validateCreateRequest(req *pbs.CreateHostRequest) error { func validateUpdateRequest(req *pbs.UpdateHostRequest) error { return handlers.ValidateUpdateRequest(static.HostPrefix, req, req.GetItem(), func() map[string]string { badFields := map[string]string{} - ct := host.SubtypeFromId(req.GetId()) - if ct == host.UnknownSubtype { + switch host.SubtypeFromId(req.GetId()) { + case host.StaticSubtype: + if req.GetItem().GetType() != "" && req.GetItem().GetType() != host.StaticSubtype.String() { + badFields["type"] = "Cannot modify the resource type." + } + default: badFields["id"] = "Improperly formatted identifier used." } - if req.GetItem().GetType() != "" { - badFields["type"] = "This is a read only field and cannot be specified in an update request." - } return badFields }) } diff --git a/internal/servers/controller/handlers/hosts/host_service_test.go b/internal/servers/controller/handlers/hosts/host_service_test.go index dd34a8f09c..8851cac90c 100644 --- a/internal/servers/controller/handlers/hosts/host_service_test.go +++ b/internal/servers/controller/handlers/hosts/host_service_test.go @@ -464,11 +464,12 @@ func TestUpdate(t *testing.T) { name: "Update an Existing Host", req: &pbs.UpdateHostRequest{ UpdateMask: &field_mask.FieldMask{ - Paths: []string{"name", "description"}, + Paths: []string{"name", "description", "type"}, }, Item: &pb.Host{ Name: &wrappers.StringValue{Value: "new"}, Description: &wrappers.StringValue{Value: "desc"}, + Type: "static", }, }, res: &pbs.UpdateHostResponse{ @@ -491,11 +492,12 @@ func TestUpdate(t *testing.T) { name: "Multiple Paths in single string", req: &pbs.UpdateHostRequest{ UpdateMask: &field_mask.FieldMask{ - Paths: []string{"name,description"}, + Paths: []string{"name,description,type"}, }, Item: &pb.Host{ Name: &wrappers.StringValue{Value: "new"}, Description: &wrappers.StringValue{Value: "desc"}, + Type: "static", }, }, res: &pbs.UpdateHostResponse{ @@ -524,6 +526,19 @@ func TestUpdate(t *testing.T) { }, errCode: codes.InvalidArgument, }, + { + name: "No Update Mask", + req: &pbs.UpdateHostRequest{ + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"name,type"}, + }, + Item: &pb.Host{ + Name: &wrappers.StringValue{Value: "updated name"}, + Type: "ec2", + }, + }, + errCode: codes.InvalidArgument, + }, { name: "Empty Path", req: &pbs.UpdateHostRequest{ diff --git a/internal/servers/controller/handlers/scopes/scope_service.go b/internal/servers/controller/handlers/scopes/scope_service.go index 88eff42a0d..3f438ff161 100644 --- a/internal/servers/controller/handlers/scopes/scope_service.go +++ b/internal/servers/controller/handlers/scopes/scope_service.go @@ -324,6 +324,7 @@ func ToProto(in *iam.Scope) *pb.Scope { CreatedTime: in.GetCreateTime().GetTimestamp(), UpdatedTime: in.GetUpdateTime().GetTimestamp(), Version: in.GetVersion(), + Type: in.GetType(), } if in.GetDescription() != "" { out.Description = &wrapperspb.StringValue{Value: in.GetDescription()} @@ -365,7 +366,19 @@ func validateCreateRequest(req *pbs.CreateScopeRequest) error { badFields := map[string]string{} item := req.GetItem() if item.GetScopeId() == "" { - badFields["scope_id"] = "Missing value for scope_id" + badFields["scope_id"] = "Missing value for scope_id." + } + switch item.GetType() { + case scope.Global.String(): + badFields["type"] = "Cannot create a global scope." + case scope.Org.String(): + if !strings.EqualFold(scope.Global.String(), item.GetScopeId()) { + badFields["type"] = "Org scopes can only be created under the global scope." + } + case scope.Project.String(): + if !handlers.ValidId(scope.Org.Prefix(), item.GetScopeId()) { + badFields["type"] = "Project scopes can only be created under an org scope." + } } if item.GetId() != "" { badFields["id"] = "This is a read only field." @@ -394,10 +407,16 @@ func validateUpdateRequest(req *pbs.UpdateScopeRequest) error { if !handlers.ValidId(scope.Org.Prefix(), id) { badFields["id"] = "Invalidly formatted scope id." } + if req.GetItem().GetType() != "" && !strings.EqualFold(scope.Org.String(), req.GetItem().GetType()) { + badFields["type"] = "Cannot modify the resource type." + } case strings.HasPrefix(id, scope.Project.Prefix()): if !handlers.ValidId(scope.Project.Prefix(), id) { badFields["id"] = "Invalidly formatted scope id." } + if req.GetItem().GetType() != "" && !strings.EqualFold(scope.Project.String(), req.GetItem().GetType()) { + badFields["type"] = "Cannot modify the resource type." + } default: badFields["id"] = "Invalidly formatted scope id." } diff --git a/internal/servers/controller/handlers/scopes/scope_service_test.go b/internal/servers/controller/handlers/scopes/scope_service_test.go index 74d41edead..8d0086656c 100644 --- a/internal/servers/controller/handlers/scopes/scope_service_test.go +++ b/internal/servers/controller/handlers/scopes/scope_service_test.go @@ -69,6 +69,7 @@ func TestGet(t *testing.T) { CreatedTime: org.CreateTime.GetTimestamp(), UpdatedTime: org.UpdateTime.GetTimestamp(), Version: 2, + Type: scope.Org.String(), } pScope := &pb.Scope{ @@ -80,6 +81,7 @@ func TestGet(t *testing.T) { CreatedTime: proj.CreateTime.GetTimestamp(), UpdatedTime: proj.UpdateTime.GetTimestamp(), Version: 2, + Type: scope.Project.String(), } cases := []struct { @@ -223,6 +225,7 @@ func TestList(t *testing.T) { CreatedTime: o.GetCreateTime().GetTimestamp(), UpdatedTime: o.GetUpdateTime().GetTimestamp(), Version: 1, + Type: scope.Org.String(), }) } wantOrgs = append(wantOrgs, initialOrgs...) @@ -241,6 +244,7 @@ func TestList(t *testing.T) { CreatedTime: p.GetCreateTime().GetTimestamp(), UpdatedTime: p.GetUpdateTime().GetTimestamp(), Version: 1, + Type: scope.Project.String(), }) } scopes.SortScopes(wantProjects) @@ -393,22 +397,21 @@ func TestDelete_twice(t *testing.T) { func TestCreate(t *testing.T) { ctx := context.Background() - require := require.New(t) defaultOrg, defaultProj, repoFn := createDefaultScopesAndRepo(t) defaultProjCreated, err := ptypes.Timestamp(defaultProj.GetCreateTime().GetTimestamp()) - require.NoError(err, "Error converting proto to timestamp.") + require.NoError(t, err, "Error converting proto to timestamp.") toMerge := &pbs.CreateScopeRequest{} repo, err := repoFn() - require.NoError(err) + require.NoError(t, err) globalUser, err := iam.NewUser(scope.Global.String()) - require.NoError(err) + require.NoError(t, err) globalUser, err = repo.CreateUser(ctx, globalUser) - require.NoError(err) + require.NoError(t, err) orgUser, err := iam.NewUser(defaultOrg.GetPublicId()) - require.NoError(err) + require.NoError(t, err) orgUser, err = repo.CreateUser(ctx, orgUser) - require.NoError(err) + require.NoError(t, err) cases := []struct { name string @@ -435,6 +438,7 @@ func TestCreate(t *testing.T) { Name: &wrapperspb.StringValue{Value: "name"}, Description: &wrapperspb.StringValue{Value: "desc"}, Version: 1, + Type: scope.Project.String(), }, }, errCode: codes.OK, @@ -457,10 +461,79 @@ func TestCreate(t *testing.T) { Name: &wrapperspb.StringValue{Value: "name"}, Description: &wrapperspb.StringValue{Value: "desc"}, Version: 1, + Type: scope.Org.String(), + }, + }, + errCode: codes.OK, + }, + { + name: "Create a valid Project with type specified", + scopeId: defaultOrg.GetPublicId(), + req: &pbs.CreateScopeRequest{ + Item: &pb.Scope{ + ScopeId: defaultOrg.GetPublicId(), + Description: &wrapperspb.StringValue{Value: "desc"}, + Type: scope.Project.String(), + }, + }, + res: &pbs.CreateScopeResponse{ + Uri: "scopes/p_", + Item: &pb.Scope{ + ScopeId: defaultOrg.GetPublicId(), + Scope: &pb.ScopeInfo{Id: defaultOrg.GetPublicId(), Type: scope.Org.String()}, + Description: &wrapperspb.StringValue{Value: "desc"}, + Version: 1, + Type: scope.Project.String(), + }, + }, + errCode: codes.OK, + }, + { + name: "Create a valid Org with type specified", + scopeId: scope.Global.String(), + req: &pbs.CreateScopeRequest{ + Item: &pb.Scope{ + ScopeId: scope.Global.String(), + Description: &wrapperspb.StringValue{Value: "desc"}, + Type: scope.Org.String(), + }, + }, + res: &pbs.CreateScopeResponse{ + Uri: "scopes/o_", + Item: &pb.Scope{ + ScopeId: scope.Global.String(), + Scope: &pb.ScopeInfo{Id: scope.Global.String(), Type: scope.Global.String()}, + Description: &wrapperspb.StringValue{Value: "desc"}, + Version: 1, + Type: scope.Org.String(), }, }, errCode: codes.OK, }, + { + name: "Project with bad type specified", + scopeId: defaultOrg.GetPublicId(), + req: &pbs.CreateScopeRequest{ + Item: &pb.Scope{ + ScopeId: defaultOrg.GetPublicId(), + Description: &wrapperspb.StringValue{Value: "desc"}, + Type: scope.Org.String(), + }, + }, + errCode: codes.InvalidArgument, + }, + { + name: "Org with bad type specified", + scopeId: scope.Global.String(), + req: &pbs.CreateScopeRequest{ + Item: &pb.Scope{ + ScopeId: scope.Global.String(), + Description: &wrapperspb.StringValue{Value: "desc"}, + Type: scope.Project.String(), + }, + }, + errCode: codes.InvalidArgument, + }, { name: "Can't specify Id", scopeId: defaultOrg.GetPublicId(), @@ -492,6 +565,7 @@ func TestCreate(t *testing.T) { for _, tc := range cases { for _, withUserId := range []bool{false, true} { t.Run(fmt.Sprintf("%s-userid-%t", tc.name, withUserId), func(t *testing.T) { + assert, require := assert.New(t), require.New(t) var name string if tc.req != nil && tc.req.GetItem() != nil && tc.req.GetItem().GetName() != nil { name = tc.req.GetItem().GetName().GetValue() @@ -500,7 +574,6 @@ func TestCreate(t *testing.T) { tc.res.GetItem().GetName().Value = localName }() } - assert := assert.New(t) req := proto.Clone(toMerge).(*pbs.CreateScopeRequest) proto.Merge(req, tc.req) @@ -563,10 +636,9 @@ func TestCreate(t *testing.T) { } func TestUpdate(t *testing.T) { - require := require.New(t) org, proj, repoFn := createDefaultScopesAndRepo(t) tested, err := scopes.NewService(repoFn) - require.NoError(err, "Error when getting new project service.") + require.NoError(t, err, "Error when getting new project service.") var orgVersion uint32 = 2 var projVersion uint32 = 2 @@ -574,23 +646,23 @@ func TestUpdate(t *testing.T) { resetOrg := func() { orgVersion++ repo, err := repoFn() - require.NoError(err, "Couldn't get a new repo") + require.NoError(t, err, "Couldn't get a new repo") org, _, err = repo.UpdateScope(context.Background(), org, orgVersion, []string{"Name", "Description"}) - require.NoError(err, "Failed to reset the org") + require.NoError(t, err, "Failed to reset the org") orgVersion++ } resetProject := func() { projVersion++ repo, err := repoFn() - require.NoError(err, "Couldn't get a new repo") + require.NoError(t, err, "Couldn't get a new repo") proj, _, err = repo.UpdateScope(context.Background(), proj, projVersion, []string{"Name", "Description"}) - require.NoError(err, "Failed to reset the project") + require.NoError(t, err, "Failed to reset the project") projVersion++ } projCreated, err := ptypes.Timestamp(proj.GetCreateTime().GetTimestamp()) - require.NoError(err, "Error converting proto to timestamp") + require.NoError(t, err, "Error converting proto to timestamp") projToMerge := &pbs.UpdateScopeRequest{ Id: proj.GetPublicId(), } @@ -616,6 +688,7 @@ func TestUpdate(t *testing.T) { Item: &pb.Scope{ Name: &wrapperspb.StringValue{Value: "new"}, Description: &wrapperspb.StringValue{Value: "desc"}, + Type: scope.Project.String(), }, }, res: &pbs.UpdateScopeResponse{ @@ -626,6 +699,7 @@ func TestUpdate(t *testing.T) { Name: &wrapperspb.StringValue{Value: "new"}, Description: &wrapperspb.StringValue{Value: "desc"}, CreatedTime: proj.GetCreateTime().GetTimestamp(), + Type: scope.Project.String(), }, }, errCode: codes.OK, @@ -640,6 +714,7 @@ func TestUpdate(t *testing.T) { Item: &pb.Scope{ Name: &wrapperspb.StringValue{Value: "new"}, Description: &wrapperspb.StringValue{Value: "desc"}, + Type: scope.Org.String(), }, }, res: &pbs.UpdateScopeResponse{ @@ -650,6 +725,7 @@ func TestUpdate(t *testing.T) { Name: &wrapperspb.StringValue{Value: "new"}, Description: &wrapperspb.StringValue{Value: "desc"}, CreatedTime: org.GetCreateTime().GetTimestamp(), + Type: scope.Org.String(), }, }, errCode: codes.OK, @@ -674,6 +750,7 @@ func TestUpdate(t *testing.T) { Name: &wrapperspb.StringValue{Value: "new"}, Description: &wrapperspb.StringValue{Value: "desc"}, CreatedTime: proj.GetCreateTime().GetTimestamp(), + Type: scope.Project.String(), }, }, errCode: codes.OK, @@ -689,6 +766,20 @@ func TestUpdate(t *testing.T) { }, errCode: codes.InvalidArgument, }, + { + name: "Cant modify type", + scopeId: org.GetPublicId(), + req: &pbs.UpdateScopeRequest{ + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"name", "type"}, + }, + Item: &pb.Scope{ + Name: &wrapperspb.StringValue{Value: "updated name"}, + Type: scope.Org.String(), + }, + }, + errCode: codes.InvalidArgument, + }, { name: "No Paths in Mask", scopeId: org.GetPublicId(), @@ -731,6 +822,7 @@ func TestUpdate(t *testing.T) { Scope: &pb.ScopeInfo{Id: org.GetPublicId(), Type: scope.Org.String()}, Description: &wrapperspb.StringValue{Value: "defaultProj"}, CreatedTime: proj.GetCreateTime().GetTimestamp(), + Type: scope.Project.String(), }, }, errCode: codes.OK, @@ -753,6 +845,7 @@ func TestUpdate(t *testing.T) { Scope: &pb.ScopeInfo{Id: org.GetPublicId(), Type: scope.Org.String()}, Name: &wrappers.StringValue{Value: "defaultProj"}, CreatedTime: proj.GetCreateTime().GetTimestamp(), + Type: scope.Project.String(), }, }, errCode: codes.OK, @@ -777,6 +870,7 @@ func TestUpdate(t *testing.T) { Name: &wrapperspb.StringValue{Value: "updated"}, Description: &wrapperspb.StringValue{Value: "defaultProj"}, CreatedTime: proj.GetCreateTime().GetTimestamp(), + Type: scope.Project.String(), }, }, errCode: codes.OK, @@ -801,6 +895,7 @@ func TestUpdate(t *testing.T) { Name: &wrapperspb.StringValue{Value: "defaultProj"}, Description: &wrapperspb.StringValue{Value: "notignored"}, CreatedTime: proj.GetCreateTime().GetTimestamp(), + Type: scope.Project.String(), }, }, errCode: codes.OK, @@ -874,7 +969,7 @@ func TestUpdate(t *testing.T) { } tc.req.Item.Version = ver - assert := assert.New(t) + assert, require := assert.New(t), require.New(t) var req *pbs.UpdateScopeRequest switch tc.scopeId { case scope.Global.String():