diff --git a/Makefile b/Makefile index ad3082fdb..2711390b2 100644 --- a/Makefile +++ b/Makefile @@ -179,6 +179,8 @@ protobuild: @protoc-go-inject-tag -input=./internal/gen/controller/api/services/role_service.pb.go @protoc-go-inject-tag -input=./sdk/pbs/controller/api/resources/sessions/session.pb.go @protoc-go-inject-tag -input=./internal/gen/controller/api/services/session_service.pb.go + @protoc-go-inject-tag -input=./sdk/pbs/controller/api/resources/users/user.pb.go + @protoc-go-inject-tag -input=./internal/gen/controller/api/services/user_service.pb.go # these protos, services and openapi artifacts are purely for testing purposes @protoc-go-inject-tag -input=./internal/gen/testing/event/event.pb.go diff --git a/internal/gen/controller/api/services/user_service.pb.go b/internal/gen/controller/api/services/user_service.pb.go index 02e8a0532..bd8a91f30 100644 --- a/internal/gen/controller/api/services/user_service.pb.go +++ b/internal/gen/controller/api/services/user_service.pb.go @@ -29,7 +29,7 @@ type GetUserRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" class:"public"` // @gotags: `class:"public"` } func (x *GetUserRequest) Reset() { @@ -123,9 +123,9 @@ type ListUsersRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - ScopeId string `protobuf:"bytes,1,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty"` - Recursive bool `protobuf:"varint,20,opt,name=recursive,proto3" json:"recursive,omitempty"` - Filter string `protobuf:"bytes,30,opt,name=filter,proto3" json:"filter,omitempty"` + ScopeId string `protobuf:"bytes,1,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty" class:"public"` // @gotags: `class:"public"` + Recursive bool `protobuf:"varint,20,opt,name=recursive,proto3" json:"recursive,omitempty" class:"public"` // @gotags: `class:"public"` + Filter string `protobuf:"bytes,30,opt,name=filter,proto3" json:"filter,omitempty" class:"sensitive"` // @gotags: `class:"sensitive"` } func (x *ListUsersRequest) Reset() { @@ -280,7 +280,7 @@ type CreateUserResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Uri string `protobuf:"bytes,1,opt,name=uri,proto3" json:"uri,omitempty"` + Uri string `protobuf:"bytes,1,opt,name=uri,proto3" json:"uri,omitempty" class:"public"` // @gotags: `class:"public"` Item *users.User `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"` } @@ -335,7 +335,7 @@ type UpdateUserRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" class:"public"` // @gotags: `class:"public"` Item *users.User `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"` UpdateMask *fieldmaskpb.FieldMask `protobuf:"bytes,3,opt,name=update_mask,proto3" json:"update_mask,omitempty"` } @@ -445,7 +445,7 @@ type DeleteUserRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" class:"public"` // @gotags: `class:"public"` } func (x *DeleteUserRequest) Reset() { @@ -530,10 +530,10 @@ type AddUserAccountsRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" class:"public"` // @gotags: `class:"public"` // The version ensures the User hasn't changed since it was last retrieved and if it has the request will fail. - Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` - AccountIds []string `protobuf:"bytes,3,rep,name=account_ids,proto3" json:"account_ids,omitempty"` + Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty" class:"public"` // @gotags: `class:"public"` + AccountIds []string `protobuf:"bytes,3,rep,name=account_ids,proto3" json:"account_ids,omitempty" class:"public"` // @gotags: `class:"public"` } func (x *AddUserAccountsRequest) Reset() { @@ -641,10 +641,10 @@ type SetUserAccountsRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" class:"public"` // @gotags: `class:"public"` // The version ensures the User hasn't changed since it was last retrieved and if it has the request will fail. - Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` - AccountIds []string `protobuf:"bytes,3,rep,name=account_ids,proto3" json:"account_ids,omitempty"` + Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty" class:"public"` // @gotags: `class:"public"` + AccountIds []string `protobuf:"bytes,3,rep,name=account_ids,proto3" json:"account_ids,omitempty" class:"public"` // @gotags: `class:"public"` } func (x *SetUserAccountsRequest) Reset() { @@ -752,10 +752,10 @@ type RemoveUserAccountsRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" class:"public"` // @gotags: `class:"public"` // The version ensures the User hasn't changed since it was last retrieved and if it has the request will fail. - Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` - AccountIds []string `protobuf:"bytes,3,rep,name=account_ids,proto3" json:"account_ids,omitempty"` + Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty" class:"public"` // @gotags: `class:"public"` + AccountIds []string `protobuf:"bytes,3,rep,name=account_ids,proto3" json:"account_ids,omitempty" class:"public"` // @gotags: `class:"public"` } func (x *RemoveUserAccountsRequest) Reset() { diff --git a/internal/proto/controller/api/resources/users/v1/user.proto b/internal/proto/controller/api/resources/users/v1/user.proto index 12afb7cdc..794f2f0b7 100644 --- a/internal/proto/controller/api/resources/users/v1/user.proto +++ b/internal/proto/controller/api/resources/users/v1/user.proto @@ -11,61 +11,61 @@ import "controller/custom_options/v1/options.proto"; message Account { // Output only. The ID of the Account. - string id = 10; + string id = 10; // @gotags: `class:"public"` // Output only. The Scope containing the Account. - string scope_id = 20 [json_name = "scope_id"]; + string scope_id = 20 [json_name = "scope_id"]; // @gotags: `class:"public"` } // User contains all fields related to a User resource message User { // Output only. The ID of the User. - string id = 10; + string id = 10; // @gotags: `class:"public"` // The ID of the Scope this resource is in. - string scope_id = 20 [json_name = "scope_id"]; + string scope_id = 20 [json_name = "scope_id"]; // @gotags: `class:"public"` // Output only. Scope information for this resource. resources.scopes.v1.ScopeInfo scope = 30; // Optional name for identification purposes. - google.protobuf.StringValue name = 40 [(custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = { this: "name" that: "name" }]; + google.protobuf.StringValue name = 40 [(custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = { this: "name" that: "name" }]; // @gotags: `class:"sensitive"` // Optional user-set description for identification purposes. - google.protobuf.StringValue description = 50 [(custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = { this: "description" that: "description" }]; + google.protobuf.StringValue description = 50 [(custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = { this: "description" that: "description" }]; // @gotags: `class:"sensitive"` // Output only. The time this resource was created. - google.protobuf.Timestamp created_time = 60 [json_name = "created_time"]; + google.protobuf.Timestamp created_time = 60 [json_name = "created_time"]; // @gotags: `class:"public"` // Output only. The time this resource was last updated. - google.protobuf.Timestamp updated_time = 70 [json_name = "updated_time"]; + google.protobuf.Timestamp updated_time = 70 [json_name = "updated_time"]; // @gotags: `class:"public"` // Version is used in mutation requests, after the initial creation, to ensure this resource has not changed. // The mutation will fail if the version does not match the latest known good version. - uint32 version = 80; + uint32 version = 80; // @gotags: `class:"public"` // Output only. Contains the list of Account IDs linked to this User. - repeated string account_ids = 90 [json_name = "account_ids"]; + repeated string account_ids = 90 [json_name = "account_ids"]; // @gotags: `class:"public"` // Output only. The Accounts linked to this User. repeated Account accounts = 100; // Output only. The available actions on this resource for this user. - repeated string authorized_actions = 300 [json_name = "authorized_actions"]; + repeated string authorized_actions = 300 [json_name = "authorized_actions"]; // @gotags: `class:"public"` // Output only. login_name is a string that maps to the user's account "login // name" from the scope's primary auth method - string login_name = 110 [json_name = "login_name"]; + string login_name = 110 [json_name = "login_name"]; // @gotags: `class:"sensitive"` // Output only. full_name is a string that maps to the user's account name // from the scope's primary auth method - string full_name = 120 [json_name = "full_name"]; + string full_name = 120 [json_name = "full_name"]; // @gotags: `class:"sensitive"` // Output only. email is a string that maps to the user's account email from // the scope's primary auth method - string email = 130 [json_name = "email"]; + string email = 130 [json_name = "email"]; // @gotags: `class:"sensitive"` // Output only. primary_account_id is a string that maps to the user's account // public_id from the scope's primary auth method - string primary_account_id = 140 [json_name = "primary_account_id"]; + string primary_account_id = 140 [json_name = "primary_account_id"]; // @gotags: `class:"public"` } diff --git a/internal/proto/controller/api/services/v1/user_service.proto b/internal/proto/controller/api/services/v1/user_service.proto index a3b851137..f9f5c80eb 100644 --- a/internal/proto/controller/api/services/v1/user_service.proto +++ b/internal/proto/controller/api/services/v1/user_service.proto @@ -136,7 +136,7 @@ service UserService { } message GetUserRequest { - string id = 1; + string id = 1; // @gotags: `class:"public"` } message GetUserResponse { @@ -144,9 +144,9 @@ message GetUserResponse { } message ListUsersRequest { - string scope_id = 1; - bool recursive = 20 [json_name="recursive"]; - string filter = 30 [json_name="filter"]; + string scope_id = 1; // @gotags: `class:"public"` + bool recursive = 20 [json_name="recursive"]; // @gotags: `class:"public"` + string filter = 30 [json_name="filter"]; // @gotags: `class:"sensitive"` } message ListUsersResponse { @@ -158,12 +158,12 @@ message CreateUserRequest { } message CreateUserResponse { - string uri = 1; + string uri = 1; // @gotags: `class:"public"` resources.users.v1.User item = 2; } message UpdateUserRequest { - string id = 1; + string id = 1; // @gotags: `class:"public"` resources.users.v1.User item = 2; google.protobuf.FieldMask update_mask = 3 [json_name="update_mask"]; } @@ -173,16 +173,16 @@ message UpdateUserResponse { } message DeleteUserRequest { - string id = 1; + string id = 1; // @gotags: `class:"public"` } message DeleteUserResponse {} message AddUserAccountsRequest { - string id = 1; + string id = 1; // @gotags: `class:"public"` // The version ensures the User hasn't changed since it was last retrieved and if it has the request will fail. - uint32 version = 2; - repeated string account_ids = 3 [json_name="account_ids"]; + uint32 version = 2; // @gotags: `class:"public"` + repeated string account_ids = 3 [json_name="account_ids"]; // @gotags: `class:"public"` } message AddUserAccountsResponse { @@ -190,10 +190,10 @@ message AddUserAccountsResponse { } message SetUserAccountsRequest { - string id = 1; + string id = 1; // @gotags: `class:"public"` // The version ensures the User hasn't changed since it was last retrieved and if it has the request will fail. - uint32 version = 2; - repeated string account_ids = 3 [json_name="account_ids"]; + uint32 version = 2; // @gotags: `class:"public"` + repeated string account_ids = 3 [json_name="account_ids"]; // @gotags: `class:"public"` } message SetUserAccountsResponse { @@ -201,10 +201,10 @@ message SetUserAccountsResponse { } message RemoveUserAccountsRequest { - string id = 1; + string id = 1; // @gotags: `class:"public"` // The version ensures the User hasn't changed since it was last retrieved and if it has the request will fail. - uint32 version = 2; - repeated string account_ids = 3 [json_name="account_ids"]; + uint32 version = 2; // @gotags: `class:"public"` + repeated string account_ids = 3 [json_name="account_ids"]; // @gotags: `class:"public"` } message RemoveUserAccountsResponse { diff --git a/internal/tests/api/users/classification_test.go b/internal/tests/api/users/classification_test.go new file mode 100644 index 000000000..31f3ec5a1 --- /dev/null +++ b/internal/tests/api/users/classification_test.go @@ -0,0 +1,119 @@ +package users_test + +import ( + "context" + "encoding/json" + "testing" + "time" + + "github.com/hashicorp/boundary/sdk/pbs/controller/api" + "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes" + pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/users" + "github.com/hashicorp/boundary/sdk/wrapper" + "github.com/hashicorp/eventlogger" + "github.com/hashicorp/eventlogger/filters/encrypt" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +func TestUsers(t *testing.T) { + ctx := context.Background() + now := time.Now() + pbNow := timestamppb.Now() + wrapper := wrapper.TestWrapper(t) + testEncryptingFilter := api.NewEncryptFilter(t, wrapper) + testEncryptingFilter.FilterOperationOverrides = map[encrypt.DataClassification]encrypt.FilterOperation{ + // Use HMAC for sensitive fields for easy test comparisons + encrypt.SensitiveClassification: encrypt.HmacSha256Operation, + } + + tests := []struct { + name string + in *eventlogger.Event + want *eventlogger.Event + }{ + { + name: "role", + in: &eventlogger.Event{ + Type: "test", + CreatedAt: now, + Payload: &pb.User{ + Id: "id", + ScopeId: "scope-id", + Scope: &scopes.ScopeInfo{ + Id: "scope-id", + Type: "scope-type", + Name: "scope-name", + Description: "scope-description", + ParentScopeId: "scope-parent-scope-id", + }, + Name: &wrapperspb.StringValue{Value: "name"}, + Description: &wrapperspb.StringValue{Value: "description"}, + CreatedTime: pbNow, + UpdatedTime: pbNow, + Version: 0, + AccountIds: []string{"account-id"}, + Accounts: []*pb.Account{ + { + Id: "account-id", + ScopeId: "scope-id", + }, + }, + AuthorizedActions: []string{"action-1"}, + LoginName: "login-name", + FullName: "full-name", + Email: "email@test.test", + PrimaryAccountId: "primary-account-id", + }, + }, + want: &eventlogger.Event{ + Type: "test", + CreatedAt: now, + Payload: &pb.User{ + Id: "id", + ScopeId: "scope-id", + Scope: &scopes.ScopeInfo{ + Id: "scope-id", + Type: "scope-type", + Name: "scope-name", + Description: "scope-description", + ParentScopeId: "scope-parent-scope-id", + }, + Name: &wrapperspb.StringValue{Value: encrypt.TestHmacSha256(t, []byte("name"), wrapper, nil, nil)}, + Description: &wrapperspb.StringValue{Value: encrypt.TestHmacSha256(t, []byte("description"), wrapper, nil, nil)}, + CreatedTime: pbNow, + UpdatedTime: pbNow, + Version: 0, + AccountIds: []string{"account-id"}, + Accounts: []*pb.Account{ + { + Id: "account-id", + ScopeId: "scope-id", + }, + }, + AuthorizedActions: []string{"action-1"}, + LoginName: encrypt.TestHmacSha256(t, []byte("login-name"), wrapper, nil, nil), + FullName: encrypt.TestHmacSha256(t, []byte("full-name"), wrapper, nil, nil), + Email: encrypt.TestHmacSha256(t, []byte("email@test.test"), wrapper, nil, nil), + PrimaryAccountId: "primary-account-id", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + got, err := testEncryptingFilter.Process(ctx, tt.in) + require.NoError(err) + require.NotNil(got) + gotJSON, err := json.Marshal(got) + require.NoError(err) + + wantJSON, err := json.Marshal(tt.want) + require.NoError(err) + assert.JSONEq(string(wantJSON), string(gotJSON)) + }) + } +} diff --git a/sdk/pbs/controller/api/resources/users/user.pb.go b/sdk/pbs/controller/api/resources/users/user.pb.go index cac44e362..05a960d54 100644 --- a/sdk/pbs/controller/api/resources/users/user.pb.go +++ b/sdk/pbs/controller/api/resources/users/user.pb.go @@ -30,9 +30,9 @@ type Account struct { unknownFields protoimpl.UnknownFields // Output only. The ID of the Account. - Id string `protobuf:"bytes,10,opt,name=id,proto3" json:"id,omitempty"` + Id string `protobuf:"bytes,10,opt,name=id,proto3" json:"id,omitempty" class:"public"` // @gotags: `class:"public"` // Output only. The Scope containing the Account. - ScopeId string `protobuf:"bytes,20,opt,name=scope_id,proto3" json:"scope_id,omitempty"` + ScopeId string `protobuf:"bytes,20,opt,name=scope_id,proto3" json:"scope_id,omitempty" class:"public"` // @gotags: `class:"public"` } func (x *Account) Reset() { @@ -88,40 +88,40 @@ type User struct { unknownFields protoimpl.UnknownFields // Output only. The ID of the User. - Id string `protobuf:"bytes,10,opt,name=id,proto3" json:"id,omitempty"` + Id string `protobuf:"bytes,10,opt,name=id,proto3" json:"id,omitempty" class:"public"` // @gotags: `class:"public"` // The ID of the Scope this resource is in. - ScopeId string `protobuf:"bytes,20,opt,name=scope_id,proto3" json:"scope_id,omitempty"` + ScopeId string `protobuf:"bytes,20,opt,name=scope_id,proto3" json:"scope_id,omitempty" class:"public"` // @gotags: `class:"public"` // Output only. Scope information for this resource. Scope *scopes.ScopeInfo `protobuf:"bytes,30,opt,name=scope,proto3" json:"scope,omitempty"` // Optional name for identification purposes. - Name *wrapperspb.StringValue `protobuf:"bytes,40,opt,name=name,proto3" json:"name,omitempty"` + Name *wrapperspb.StringValue `protobuf:"bytes,40,opt,name=name,proto3" json:"name,omitempty" class:"sensitive"` // @gotags: `class:"sensitive"` // Optional user-set description for identification purposes. - Description *wrapperspb.StringValue `protobuf:"bytes,50,opt,name=description,proto3" json:"description,omitempty"` + Description *wrapperspb.StringValue `protobuf:"bytes,50,opt,name=description,proto3" json:"description,omitempty" class:"sensitive"` // @gotags: `class:"sensitive"` // Output only. The time this resource was created. - CreatedTime *timestamppb.Timestamp `protobuf:"bytes,60,opt,name=created_time,proto3" json:"created_time,omitempty"` + CreatedTime *timestamppb.Timestamp `protobuf:"bytes,60,opt,name=created_time,proto3" json:"created_time,omitempty" class:"public"` // @gotags: `class:"public"` // Output only. The time this resource was last updated. - UpdatedTime *timestamppb.Timestamp `protobuf:"bytes,70,opt,name=updated_time,proto3" json:"updated_time,omitempty"` + UpdatedTime *timestamppb.Timestamp `protobuf:"bytes,70,opt,name=updated_time,proto3" json:"updated_time,omitempty" class:"public"` // @gotags: `class:"public"` // Version is used in mutation requests, after the initial creation, to ensure this resource has not changed. // The mutation will fail if the version does not match the latest known good version. - Version uint32 `protobuf:"varint,80,opt,name=version,proto3" json:"version,omitempty"` + Version uint32 `protobuf:"varint,80,opt,name=version,proto3" json:"version,omitempty" class:"public"` // @gotags: `class:"public"` // Output only. Contains the list of Account IDs linked to this User. - AccountIds []string `protobuf:"bytes,90,rep,name=account_ids,proto3" json:"account_ids,omitempty"` + AccountIds []string `protobuf:"bytes,90,rep,name=account_ids,proto3" json:"account_ids,omitempty" class:"public"` // @gotags: `class:"public"` // Output only. The Accounts linked to this User. Accounts []*Account `protobuf:"bytes,100,rep,name=accounts,proto3" json:"accounts,omitempty"` // Output only. The available actions on this resource for this user. - AuthorizedActions []string `protobuf:"bytes,300,rep,name=authorized_actions,proto3" json:"authorized_actions,omitempty"` + AuthorizedActions []string `protobuf:"bytes,300,rep,name=authorized_actions,proto3" json:"authorized_actions,omitempty" class:"public"` // @gotags: `class:"public"` // Output only. login_name is a string that maps to the user's account "login // name" from the scope's primary auth method - LoginName string `protobuf:"bytes,110,opt,name=login_name,proto3" json:"login_name,omitempty"` + LoginName string `protobuf:"bytes,110,opt,name=login_name,proto3" json:"login_name,omitempty" class:"sensitive"` // @gotags: `class:"sensitive"` // Output only. full_name is a string that maps to the user's account name // from the scope's primary auth method - FullName string `protobuf:"bytes,120,opt,name=full_name,proto3" json:"full_name,omitempty"` + FullName string `protobuf:"bytes,120,opt,name=full_name,proto3" json:"full_name,omitempty" class:"sensitive"` // @gotags: `class:"sensitive"` // Output only. email is a string that maps to the user's account email from // the scope's primary auth method - Email string `protobuf:"bytes,130,opt,name=email,proto3" json:"email,omitempty"` + Email string `protobuf:"bytes,130,opt,name=email,proto3" json:"email,omitempty" class:"sensitive"` // @gotags: `class:"sensitive"` // Output only. primary_account_id is a string that maps to the user's account // public_id from the scope's primary auth method - PrimaryAccountId string `protobuf:"bytes,140,opt,name=primary_account_id,proto3" json:"primary_account_id,omitempty"` + PrimaryAccountId string `protobuf:"bytes,140,opt,name=primary_account_id,proto3" json:"primary_account_id,omitempty" class:"public"` // @gotags: `class:"public"` } func (x *User) Reset() {