diff --git a/.mockery.yml b/.mockery.yml index 5574500a57..12d04ac118 100644 --- a/.mockery.yml +++ b/.mockery.yml @@ -116,3 +116,7 @@ packages: AnalyzeInterface: AnonymizeInterface: EntityInterface: + github.com/nucleuscloud/neosync/backend/internal/userdata: + interfaces: + Interface: + EntityEnforcer: diff --git a/backend/gen/go/db/mock_Querier.go b/backend/gen/go/db/mock_Querier.go index bda36d5885..be934d1e4d 100644 --- a/backend/gen/go/db/mock_Querier.go +++ b/backend/gen/go/db/mock_Querier.go @@ -1386,6 +1386,65 @@ func (_c *MockQuerier_GetAccountIdFromJobId_Call) RunAndReturn(run func(context. return _c } +// GetAccountIds provides a mock function with given fields: ctx, db +func (_m *MockQuerier) GetAccountIds(ctx context.Context, db DBTX) ([]pgtype.UUID, error) { + ret := _m.Called(ctx, db) + + if len(ret) == 0 { + panic("no return value specified for GetAccountIds") + } + + var r0 []pgtype.UUID + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, DBTX) ([]pgtype.UUID, error)); ok { + return rf(ctx, db) + } + if rf, ok := ret.Get(0).(func(context.Context, DBTX) []pgtype.UUID); ok { + r0 = rf(ctx, db) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]pgtype.UUID) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, DBTX) error); ok { + r1 = rf(ctx, db) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockQuerier_GetAccountIds_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAccountIds' +type MockQuerier_GetAccountIds_Call struct { + *mock.Call +} + +// GetAccountIds is a helper method to define mock.On call +// - ctx context.Context +// - db DBTX +func (_e *MockQuerier_Expecter) GetAccountIds(ctx interface{}, db interface{}) *MockQuerier_GetAccountIds_Call { + return &MockQuerier_GetAccountIds_Call{Call: _e.mock.On("GetAccountIds", ctx, db)} +} + +func (_c *MockQuerier_GetAccountIds_Call) Run(run func(ctx context.Context, db DBTX)) *MockQuerier_GetAccountIds_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(DBTX)) + }) + return _c +} + +func (_c *MockQuerier_GetAccountIds_Call) Return(_a0 []pgtype.UUID, _a1 error) *MockQuerier_GetAccountIds_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockQuerier_GetAccountIds_Call) RunAndReturn(run func(context.Context, DBTX) ([]pgtype.UUID, error)) *MockQuerier_GetAccountIds_Call { + _c.Call.Return(run) + return _c +} + // GetAccountInvite provides a mock function with given fields: ctx, db, id func (_m *MockQuerier) GetAccountInvite(ctx context.Context, db DBTX, id pgtype.UUID) (NeosyncApiAccountInvite, error) { ret := _m.Called(ctx, db, id) @@ -1620,6 +1679,66 @@ func (_c *MockQuerier_GetAccountUserAssociation_Call) RunAndReturn(run func(cont return _c } +// GetAccountUsers provides a mock function with given fields: ctx, db, accountid +func (_m *MockQuerier) GetAccountUsers(ctx context.Context, db DBTX, accountid pgtype.UUID) ([]pgtype.UUID, error) { + ret := _m.Called(ctx, db, accountid) + + if len(ret) == 0 { + panic("no return value specified for GetAccountUsers") + } + + var r0 []pgtype.UUID + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, DBTX, pgtype.UUID) ([]pgtype.UUID, error)); ok { + return rf(ctx, db, accountid) + } + if rf, ok := ret.Get(0).(func(context.Context, DBTX, pgtype.UUID) []pgtype.UUID); ok { + r0 = rf(ctx, db, accountid) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]pgtype.UUID) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, DBTX, pgtype.UUID) error); ok { + r1 = rf(ctx, db, accountid) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockQuerier_GetAccountUsers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAccountUsers' +type MockQuerier_GetAccountUsers_Call struct { + *mock.Call +} + +// GetAccountUsers is a helper method to define mock.On call +// - ctx context.Context +// - db DBTX +// - accountid pgtype.UUID +func (_e *MockQuerier_Expecter) GetAccountUsers(ctx interface{}, db interface{}, accountid interface{}) *MockQuerier_GetAccountUsers_Call { + return &MockQuerier_GetAccountUsers_Call{Call: _e.mock.On("GetAccountUsers", ctx, db, accountid)} +} + +func (_c *MockQuerier_GetAccountUsers_Call) Run(run func(ctx context.Context, db DBTX, accountid pgtype.UUID)) *MockQuerier_GetAccountUsers_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(DBTX), args[2].(pgtype.UUID)) + }) + return _c +} + +func (_c *MockQuerier_GetAccountUsers_Call) Return(_a0 []pgtype.UUID, _a1 error) *MockQuerier_GetAccountUsers_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockQuerier_GetAccountUsers_Call) RunAndReturn(run func(context.Context, DBTX, pgtype.UUID) ([]pgtype.UUID, error)) *MockQuerier_GetAccountUsers_Call { + _c.Call.Return(run) + return _c +} + // GetAccountsByUser provides a mock function with given fields: ctx, db, id func (_m *MockQuerier) GetAccountsByUser(ctx context.Context, db DBTX, id pgtype.UUID) ([]NeosyncApiAccount, error) { ret := _m.Called(ctx, db, id) diff --git a/backend/gen/go/db/models.go b/backend/gen/go/db/models.go index b73fe4fba9..134efe77d7 100644 --- a/backend/gen/go/db/models.go +++ b/backend/gen/go/db/models.go @@ -54,6 +54,18 @@ type NeosyncApiAccountUserAssociation struct { UpdatedAt pgtype.Timestamp } +type NeosyncApiCasbinRule struct { + PType string + V0 string + V1 string + V2 string + V3 string + V4 string + V5 string + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz +} + type NeosyncApiConnection struct { ID pgtype.UUID CreatedAt pgtype.Timestamp diff --git a/backend/gen/go/db/querier.go b/backend/gen/go/db/querier.go index b975903ff5..1b24ddd4b8 100644 --- a/backend/gen/go/db/querier.go +++ b/backend/gen/go/db/querier.go @@ -36,10 +36,12 @@ type Querier interface { GetAccountApiKeyByKeyValue(ctx context.Context, db DBTX, keyValue string) (NeosyncApiAccountApiKey, error) GetAccountApiKeys(ctx context.Context, db DBTX, accountid pgtype.UUID) ([]NeosyncApiAccountApiKey, error) GetAccountIdFromJobId(ctx context.Context, db DBTX, id pgtype.UUID) (pgtype.UUID, error) + GetAccountIds(ctx context.Context, db DBTX) ([]pgtype.UUID, error) GetAccountInvite(ctx context.Context, db DBTX, id pgtype.UUID) (NeosyncApiAccountInvite, error) GetAccountInviteByToken(ctx context.Context, db DBTX, token string) (NeosyncApiAccountInvite, error) GetAccountOnboardingConfig(ctx context.Context, db DBTX, id pgtype.UUID) (*pg_models.AccountOnboardingConfig, error) GetAccountUserAssociation(ctx context.Context, db DBTX, arg GetAccountUserAssociationParams) (NeosyncApiAccountUserAssociation, error) + GetAccountUsers(ctx context.Context, db DBTX, accountid pgtype.UUID) ([]pgtype.UUID, error) GetAccountsByUser(ctx context.Context, db DBTX, id pgtype.UUID) ([]NeosyncApiAccount, error) GetActiveAccountInvites(ctx context.Context, db DBTX, accountid pgtype.UUID) ([]NeosyncApiAccountInvite, error) GetActiveJobHooks(ctx context.Context, db DBTX, jobID pgtype.UUID) ([]NeosyncApiJobHook, error) diff --git a/backend/gen/go/db/users.sql.go b/backend/gen/go/db/users.sql.go index 9e2ec0f783..d29593a1b6 100644 --- a/backend/gen/go/db/users.sql.go +++ b/backend/gen/go/db/users.sql.go @@ -248,6 +248,30 @@ func (q *Queries) GetAccount(ctx context.Context, db DBTX, id pgtype.UUID) (Neos return i, err } +const getAccountIds = `-- name: GetAccountIds :many +SELECT id FROM neosync_api.accounts +` + +func (q *Queries) GetAccountIds(ctx context.Context, db DBTX) ([]pgtype.UUID, error) { + rows, err := db.Query(ctx, getAccountIds) + if err != nil { + return nil, err + } + defer rows.Close() + var items []pgtype.UUID + for rows.Next() { + var id pgtype.UUID + if err := rows.Scan(&id); err != nil { + return nil, err + } + items = append(items, id) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const getAccountInvite = `-- name: GetAccountInvite :one SELECT id, account_id, sender_user_id, email, token, accepted, created_at, updated_at, expires_at FROM neosync_api.account_invites WHERE id = $1 @@ -330,6 +354,34 @@ func (q *Queries) GetAccountUserAssociation(ctx context.Context, db DBTX, arg Ge return i, err } +const getAccountUsers = `-- name: GetAccountUsers :many +SELECT u.id +FROM neosync_api.users u +INNER JOIN neosync_api.account_user_associations aua ON aua.user_id = u.id +INNER JOIN neosync_api.accounts a ON a.id = aua.account_id +WHERE a.id = $1 AND u.user_type = 0 +` + +func (q *Queries) GetAccountUsers(ctx context.Context, db DBTX, accountid pgtype.UUID) ([]pgtype.UUID, error) { + rows, err := db.Query(ctx, getAccountUsers, accountid) + if err != nil { + return nil, err + } + defer rows.Close() + var items []pgtype.UUID + for rows.Next() { + var id pgtype.UUID + if err := rows.Scan(&id); err != nil { + return nil, err + } + items = append(items, id) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const getAccountsByUser = `-- name: GetAccountsByUser :many SELECT a.id, a.created_at, a.updated_at, a.account_type, a.account_slug, a.temporal_config, a.onboarding_config, a.max_allowed_records, a.stripe_customer_id FROM neosync_api.accounts a @@ -612,9 +664,10 @@ func (q *Queries) GetUserByProviderSub(ctx context.Context, db DBTX, providerSub } const getUserIdentitiesByTeamAccount = `-- name: GetUserIdentitiesByTeamAccount :many -SELECT aipa.id, aipa.user_id, aipa.provider_sub, aipa.created_at, aipa.updated_at FROM neosync_api.user_identity_provider_associations aipa -JOIN neosync_api.account_user_associations aua ON aua.user_id = aipa.user_id -JOIN neosync_api.accounts a ON a.id = aua.account_id +SELECT aipa.id, aipa.user_id, aipa.provider_sub, aipa.created_at, aipa.updated_at +FROM neosync_api.user_identity_provider_associations aipa +INNER JOIN neosync_api.account_user_associations aua ON aua.user_id = aipa.user_id +INNER JOIN neosync_api.accounts a ON a.id = aua.account_id WHERE aua.account_id = $1 AND a.account_type = 1 ` diff --git a/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect/mock_UserAccountServiceClient.go b/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect/mock_UserAccountServiceClient.go index 4615e5bae0..54efc19f97 100644 --- a/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect/mock_UserAccountServiceClient.go +++ b/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect/mock_UserAccountServiceClient.go @@ -1440,6 +1440,65 @@ func (_c *MockUserAccountServiceClient_SetUser_Call) RunAndReturn(run func(conte return _c } +// SetUserRole provides a mock function with given fields: _a0, _a1 +func (_m *MockUserAccountServiceClient) SetUserRole(_a0 context.Context, _a1 *connect.Request[mgmtv1alpha1.SetUserRoleRequest]) (*connect.Response[mgmtv1alpha1.SetUserRoleResponse], error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for SetUserRole") + } + + var r0 *connect.Response[mgmtv1alpha1.SetUserRoleResponse] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[mgmtv1alpha1.SetUserRoleRequest]) (*connect.Response[mgmtv1alpha1.SetUserRoleResponse], error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[mgmtv1alpha1.SetUserRoleRequest]) *connect.Response[mgmtv1alpha1.SetUserRoleResponse]); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*connect.Response[mgmtv1alpha1.SetUserRoleResponse]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *connect.Request[mgmtv1alpha1.SetUserRoleRequest]) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockUserAccountServiceClient_SetUserRole_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetUserRole' +type MockUserAccountServiceClient_SetUserRole_Call struct { + *mock.Call +} + +// SetUserRole is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *connect.Request[mgmtv1alpha1.SetUserRoleRequest] +func (_e *MockUserAccountServiceClient_Expecter) SetUserRole(_a0 interface{}, _a1 interface{}) *MockUserAccountServiceClient_SetUserRole_Call { + return &MockUserAccountServiceClient_SetUserRole_Call{Call: _e.mock.On("SetUserRole", _a0, _a1)} +} + +func (_c *MockUserAccountServiceClient_SetUserRole_Call) Run(run func(_a0 context.Context, _a1 *connect.Request[mgmtv1alpha1.SetUserRoleRequest])) *MockUserAccountServiceClient_SetUserRole_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*connect.Request[mgmtv1alpha1.SetUserRoleRequest])) + }) + return _c +} + +func (_c *MockUserAccountServiceClient_SetUserRole_Call) Return(_a0 *connect.Response[mgmtv1alpha1.SetUserRoleResponse], _a1 error) *MockUserAccountServiceClient_SetUserRole_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockUserAccountServiceClient_SetUserRole_Call) RunAndReturn(run func(context.Context, *connect.Request[mgmtv1alpha1.SetUserRoleRequest]) (*connect.Response[mgmtv1alpha1.SetUserRoleResponse], error)) *MockUserAccountServiceClient_SetUserRole_Call { + _c.Call.Return(run) + return _c +} + // NewMockUserAccountServiceClient creates a new instance of MockUserAccountServiceClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewMockUserAccountServiceClient(t interface { diff --git a/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect/user_account.connect.go b/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect/user_account.connect.go index 8efd566915..10ac38ccdd 100644 --- a/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect/user_account.connect.go +++ b/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect/user_account.connect.go @@ -105,6 +105,9 @@ const ( // UserAccountServiceSetBillingMeterEventProcedure is the fully-qualified name of the // UserAccountService's SetBillingMeterEvent RPC. UserAccountServiceSetBillingMeterEventProcedure = "/mgmt.v1alpha1.UserAccountService/SetBillingMeterEvent" + // UserAccountServiceSetUserRoleProcedure is the fully-qualified name of the UserAccountService's + // SetUserRole RPC. + UserAccountServiceSetUserRoleProcedure = "/mgmt.v1alpha1.UserAccountService/SetUserRole" ) // These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. @@ -134,6 +137,7 @@ var ( userAccountServiceGetAccountBillingPortalSessionMethodDescriptor = userAccountServiceServiceDescriptor.Methods().ByName("GetAccountBillingPortalSession") userAccountServiceGetBillingAccountsMethodDescriptor = userAccountServiceServiceDescriptor.Methods().ByName("GetBillingAccounts") userAccountServiceSetBillingMeterEventMethodDescriptor = userAccountServiceServiceDescriptor.Methods().ByName("SetBillingMeterEvent") + userAccountServiceSetUserRoleMethodDescriptor = userAccountServiceServiceDescriptor.Methods().ByName("SetUserRole") ) // UserAccountServiceClient is a client for the mgmt.v1alpha1.UserAccountService service. @@ -170,6 +174,8 @@ type UserAccountServiceClient interface { GetBillingAccounts(context.Context, *connect.Request[v1alpha1.GetBillingAccountsRequest]) (*connect.Response[v1alpha1.GetBillingAccountsResponse], error) // Sends a new metered event to the billing system SetBillingMeterEvent(context.Context, *connect.Request[v1alpha1.SetBillingMeterEventRequest]) (*connect.Response[v1alpha1.SetBillingMeterEventResponse], error) + // Sets the users role + SetUserRole(context.Context, *connect.Request[v1alpha1.SetUserRoleRequest]) (*connect.Response[v1alpha1.SetUserRoleResponse], error) } // NewUserAccountServiceClient constructs a client for the mgmt.v1alpha1.UserAccountService service. @@ -330,6 +336,12 @@ func NewUserAccountServiceClient(httpClient connect.HTTPClient, baseURL string, connect.WithSchema(userAccountServiceSetBillingMeterEventMethodDescriptor), connect.WithClientOptions(opts...), ), + setUserRole: connect.NewClient[v1alpha1.SetUserRoleRequest, v1alpha1.SetUserRoleResponse]( + httpClient, + baseURL+UserAccountServiceSetUserRoleProcedure, + connect.WithSchema(userAccountServiceSetUserRoleMethodDescriptor), + connect.WithClientOptions(opts...), + ), } } @@ -359,6 +371,7 @@ type userAccountServiceClient struct { getAccountBillingPortalSession *connect.Client[v1alpha1.GetAccountBillingPortalSessionRequest, v1alpha1.GetAccountBillingPortalSessionResponse] getBillingAccounts *connect.Client[v1alpha1.GetBillingAccountsRequest, v1alpha1.GetBillingAccountsResponse] setBillingMeterEvent *connect.Client[v1alpha1.SetBillingMeterEventRequest, v1alpha1.SetBillingMeterEventResponse] + setUserRole *connect.Client[v1alpha1.SetUserRoleRequest, v1alpha1.SetUserRoleResponse] } // GetUser calls mgmt.v1alpha1.UserAccountService.GetUser. @@ -483,6 +496,11 @@ func (c *userAccountServiceClient) SetBillingMeterEvent(ctx context.Context, req return c.setBillingMeterEvent.CallUnary(ctx, req) } +// SetUserRole calls mgmt.v1alpha1.UserAccountService.SetUserRole. +func (c *userAccountServiceClient) SetUserRole(ctx context.Context, req *connect.Request[v1alpha1.SetUserRoleRequest]) (*connect.Response[v1alpha1.SetUserRoleResponse], error) { + return c.setUserRole.CallUnary(ctx, req) +} + // UserAccountServiceHandler is an implementation of the mgmt.v1alpha1.UserAccountService service. type UserAccountServiceHandler interface { GetUser(context.Context, *connect.Request[v1alpha1.GetUserRequest]) (*connect.Response[v1alpha1.GetUserResponse], error) @@ -517,6 +535,8 @@ type UserAccountServiceHandler interface { GetBillingAccounts(context.Context, *connect.Request[v1alpha1.GetBillingAccountsRequest]) (*connect.Response[v1alpha1.GetBillingAccountsResponse], error) // Sends a new metered event to the billing system SetBillingMeterEvent(context.Context, *connect.Request[v1alpha1.SetBillingMeterEventRequest]) (*connect.Response[v1alpha1.SetBillingMeterEventResponse], error) + // Sets the users role + SetUserRole(context.Context, *connect.Request[v1alpha1.SetUserRoleRequest]) (*connect.Response[v1alpha1.SetUserRoleResponse], error) } // NewUserAccountServiceHandler builds an HTTP handler from the service implementation. It returns @@ -673,6 +693,12 @@ func NewUserAccountServiceHandler(svc UserAccountServiceHandler, opts ...connect connect.WithSchema(userAccountServiceSetBillingMeterEventMethodDescriptor), connect.WithHandlerOptions(opts...), ) + userAccountServiceSetUserRoleHandler := connect.NewUnaryHandler( + UserAccountServiceSetUserRoleProcedure, + svc.SetUserRole, + connect.WithSchema(userAccountServiceSetUserRoleMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) return "/mgmt.v1alpha1.UserAccountService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case UserAccountServiceGetUserProcedure: @@ -723,6 +749,8 @@ func NewUserAccountServiceHandler(svc UserAccountServiceHandler, opts ...connect userAccountServiceGetBillingAccountsHandler.ServeHTTP(w, r) case UserAccountServiceSetBillingMeterEventProcedure: userAccountServiceSetBillingMeterEventHandler.ServeHTTP(w, r) + case UserAccountServiceSetUserRoleProcedure: + userAccountServiceSetUserRoleHandler.ServeHTTP(w, r) default: http.NotFound(w, r) } @@ -827,3 +855,7 @@ func (UnimplementedUserAccountServiceHandler) GetBillingAccounts(context.Context func (UnimplementedUserAccountServiceHandler) SetBillingMeterEvent(context.Context, *connect.Request[v1alpha1.SetBillingMeterEventRequest]) (*connect.Response[v1alpha1.SetBillingMeterEventResponse], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgmt.v1alpha1.UserAccountService.SetBillingMeterEvent is not implemented")) } + +func (UnimplementedUserAccountServiceHandler) SetUserRole(context.Context, *connect.Request[v1alpha1.SetUserRoleRequest]) (*connect.Response[v1alpha1.SetUserRoleResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgmt.v1alpha1.UserAccountService.SetUserRole is not implemented")) +} diff --git a/backend/gen/go/protos/mgmt/v1alpha1/user_account.pb.go b/backend/gen/go/protos/mgmt/v1alpha1/user_account.pb.go index cc6d22a5ac..88d98eaef8 100644 --- a/backend/gen/go/protos/mgmt/v1alpha1/user_account.pb.go +++ b/backend/gen/go/protos/mgmt/v1alpha1/user_account.pb.go @@ -198,6 +198,66 @@ func (AccountStatus) EnumDescriptor() ([]byte, []int) { return file_mgmt_v1alpha1_user_account_proto_rawDescGZIP(), []int{2} } +type AccountRole int32 + +const ( + // Default value, this should not be used, but will default to ACCOUNT_ROLE_JOB_VIEWER + AccountRole_ACCOUNT_ROLE_UNSPECIFIED AccountRole = 0 + // Admin, can do anything in the account. + AccountRole_ACCOUNT_ROLE_ADMIN AccountRole = 1 + // Can view, edit jobs and connections + AccountRole_ACCOUNT_ROLE_JOB_DEVELOPER AccountRole = 2 + // Can view + AccountRole_ACCOUNT_ROLE_JOB_VIEWER AccountRole = 3 + // Can view and execute + AccountRole_ACCOUNT_ROLE_JOB_EXECUTOR AccountRole = 4 +) + +// Enum value maps for AccountRole. +var ( + AccountRole_name = map[int32]string{ + 0: "ACCOUNT_ROLE_UNSPECIFIED", + 1: "ACCOUNT_ROLE_ADMIN", + 2: "ACCOUNT_ROLE_JOB_DEVELOPER", + 3: "ACCOUNT_ROLE_JOB_VIEWER", + 4: "ACCOUNT_ROLE_JOB_EXECUTOR", + } + AccountRole_value = map[string]int32{ + "ACCOUNT_ROLE_UNSPECIFIED": 0, + "ACCOUNT_ROLE_ADMIN": 1, + "ACCOUNT_ROLE_JOB_DEVELOPER": 2, + "ACCOUNT_ROLE_JOB_VIEWER": 3, + "ACCOUNT_ROLE_JOB_EXECUTOR": 4, + } +) + +func (x AccountRole) Enum() *AccountRole { + p := new(AccountRole) + *p = x + return p +} + +func (x AccountRole) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (AccountRole) Descriptor() protoreflect.EnumDescriptor { + return file_mgmt_v1alpha1_user_account_proto_enumTypes[3].Descriptor() +} + +func (AccountRole) Type() protoreflect.EnumType { + return &file_mgmt_v1alpha1_user_account_proto_enumTypes[3] +} + +func (x AccountRole) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use AccountRole.Descriptor instead. +func (AccountRole) EnumDescriptor() ([]byte, []int) { + return file_mgmt_v1alpha1_user_account_proto_rawDescGZIP(), []int{3} +} + type GetUserRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2913,6 +2973,106 @@ func (*SetBillingMeterEventResponse) Descriptor() ([]byte, []int) { return file_mgmt_v1alpha1_user_account_proto_rawDescGZIP(), []int{52} } +type SetUserRoleRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The account id to apply this role to + AccountId string `protobuf:"bytes,1,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"` + // The user that this will be applied to + UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + // The role that this user will obtain + Role AccountRole `protobuf:"varint,3,opt,name=role,proto3,enum=mgmt.v1alpha1.AccountRole" json:"role,omitempty"` +} + +func (x *SetUserRoleRequest) Reset() { + *x = SetUserRoleRequest{} + mi := &file_mgmt_v1alpha1_user_account_proto_msgTypes[53] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetUserRoleRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetUserRoleRequest) ProtoMessage() {} + +func (x *SetUserRoleRequest) ProtoReflect() protoreflect.Message { + mi := &file_mgmt_v1alpha1_user_account_proto_msgTypes[53] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetUserRoleRequest.ProtoReflect.Descriptor instead. +func (*SetUserRoleRequest) Descriptor() ([]byte, []int) { + return file_mgmt_v1alpha1_user_account_proto_rawDescGZIP(), []int{53} +} + +func (x *SetUserRoleRequest) GetAccountId() string { + if x != nil { + return x.AccountId + } + return "" +} + +func (x *SetUserRoleRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *SetUserRoleRequest) GetRole() AccountRole { + if x != nil { + return x.Role + } + return AccountRole_ACCOUNT_ROLE_UNSPECIFIED +} + +type SetUserRoleResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *SetUserRoleResponse) Reset() { + *x = SetUserRoleResponse{} + mi := &file_mgmt_v1alpha1_user_account_proto_msgTypes[54] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetUserRoleResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetUserRoleResponse) ProtoMessage() {} + +func (x *SetUserRoleResponse) ProtoReflect() protoreflect.Message { + mi := &file_mgmt_v1alpha1_user_account_proto_msgTypes[54] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetUserRoleResponse.ProtoReflect.Descriptor instead. +func (*SetUserRoleResponse) Descriptor() ([]byte, []int) { + return file_mgmt_v1alpha1_user_account_proto_rawDescGZIP(), []int{54} +} + var File_mgmt_v1alpha1_user_account_proto protoreflect.FileDescriptor var file_mgmt_v1alpha1_user_account_proto_rawDesc = []byte{ @@ -3278,235 +3438,262 @@ var file_mgmt_v1alpha1_user_account_proto_rawDesc = []byte{ 0x6d, 0x70, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x1e, 0x0a, 0x1c, 0x53, 0x65, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x4d, 0x65, 0x74, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x2a, 0x92, 0x01, 0x0a, 0x0f, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x1d, 0x55, 0x53, 0x45, 0x52, 0x5f, - 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, - 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1e, 0x0a, 0x1a, 0x55, 0x53, - 0x45, 0x52, 0x5f, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, - 0x50, 0x45, 0x52, 0x53, 0x4f, 0x4e, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x55, 0x53, - 0x45, 0x52, 0x5f, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, - 0x54, 0x45, 0x41, 0x4d, 0x10, 0x02, 0x12, 0x20, 0x0a, 0x1c, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x41, - 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, 0x4e, 0x54, 0x45, - 0x52, 0x50, 0x52, 0x49, 0x53, 0x45, 0x10, 0x03, 0x2a, 0xa9, 0x01, 0x0a, 0x0d, 0x42, 0x69, 0x6c, - 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1e, 0x0a, 0x1a, 0x42, 0x49, - 0x4c, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, - 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x42, 0x49, - 0x4c, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x41, 0x43, 0x54, - 0x49, 0x56, 0x45, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x42, 0x49, 0x4c, 0x4c, 0x49, 0x4e, 0x47, - 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x45, 0x44, 0x10, - 0x02, 0x12, 0x1f, 0x0a, 0x1b, 0x42, 0x49, 0x4c, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x53, 0x54, 0x41, - 0x54, 0x55, 0x53, 0x5f, 0x54, 0x52, 0x49, 0x41, 0x4c, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, - 0x10, 0x03, 0x12, 0x20, 0x0a, 0x1c, 0x42, 0x49, 0x4c, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x53, 0x54, - 0x41, 0x54, 0x55, 0x53, 0x5f, 0x54, 0x52, 0x49, 0x41, 0x4c, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, - 0x45, 0x44, 0x10, 0x04, 0x2a, 0x8c, 0x02, 0x0a, 0x0d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x25, 0x0a, 0x21, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, - 0x54, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, - 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x28, 0x0a, - 0x24, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, - 0x45, 0x58, 0x43, 0x45, 0x45, 0x44, 0x53, 0x5f, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x45, 0x44, 0x5f, - 0x4c, 0x49, 0x4d, 0x49, 0x54, 0x10, 0x01, 0x12, 0x2a, 0x0a, 0x26, 0x41, 0x43, 0x43, 0x4f, 0x55, - 0x4e, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, - 0x54, 0x45, 0x44, 0x5f, 0x45, 0x58, 0x43, 0x45, 0x45, 0x44, 0x53, 0x5f, 0x4c, 0x49, 0x4d, 0x49, - 0x54, 0x10, 0x02, 0x12, 0x2b, 0x0a, 0x27, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x53, - 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x49, 0x4e, - 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x45, 0x44, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x10, 0x03, - 0x12, 0x27, 0x0a, 0x23, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x54, - 0x55, 0x53, 0x5f, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x54, 0x52, 0x49, 0x41, 0x4c, - 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x10, 0x04, 0x12, 0x28, 0x0a, 0x24, 0x41, 0x43, 0x43, - 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x41, 0x43, 0x43, 0x4f, - 0x55, 0x4e, 0x54, 0x5f, 0x54, 0x52, 0x49, 0x41, 0x4c, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x45, - 0x44, 0x10, 0x05, 0x32, 0xa0, 0x16, 0x0a, 0x12, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x07, 0x47, 0x65, - 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1d, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x07, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, + 0x6e, 0x73, 0x65, 0x22, 0x90, 0x01, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, + 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0a, 0x61, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, + 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x09, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x06, + 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x6f, 0x6c, 0x65, + 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x22, 0x15, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x92, 0x01, + 0x0a, 0x0f, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x21, 0x0a, 0x1d, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, + 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, + 0x45, 0x44, 0x10, 0x00, 0x12, 0x1e, 0x0a, 0x1a, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x41, 0x43, 0x43, + 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x45, 0x52, 0x53, 0x4f, 0x4e, + 0x41, 0x4c, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x41, 0x43, 0x43, + 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x54, 0x45, 0x41, 0x4d, 0x10, 0x02, + 0x12, 0x20, 0x0a, 0x1c, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, 0x4e, 0x54, 0x45, 0x52, 0x50, 0x52, 0x49, 0x53, 0x45, + 0x10, 0x03, 0x2a, 0xa9, 0x01, 0x0a, 0x0d, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x1e, 0x0a, 0x1a, 0x42, 0x49, 0x4c, 0x4c, 0x49, 0x4e, 0x47, 0x5f, + 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, + 0x45, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x42, 0x49, 0x4c, 0x4c, 0x49, 0x4e, 0x47, 0x5f, + 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x10, 0x01, 0x12, + 0x1a, 0x0a, 0x16, 0x42, 0x49, 0x4c, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, + 0x53, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x45, 0x44, 0x10, 0x02, 0x12, 0x1f, 0x0a, 0x1b, 0x42, + 0x49, 0x4c, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x54, 0x52, + 0x49, 0x41, 0x4c, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x10, 0x03, 0x12, 0x20, 0x0a, 0x1c, + 0x42, 0x49, 0x4c, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x54, + 0x52, 0x49, 0x41, 0x4c, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x45, 0x44, 0x10, 0x04, 0x2a, 0x8c, + 0x02, 0x0a, 0x0d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x25, 0x0a, 0x21, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x54, + 0x55, 0x53, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, + 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x28, 0x0a, 0x24, 0x41, 0x43, 0x43, 0x4f, 0x55, + 0x4e, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x58, 0x43, 0x45, 0x45, 0x44, + 0x53, 0x5f, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x45, 0x44, 0x5f, 0x4c, 0x49, 0x4d, 0x49, 0x54, 0x10, + 0x01, 0x12, 0x2a, 0x0a, 0x26, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x53, 0x54, 0x41, + 0x54, 0x55, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x45, 0x58, + 0x43, 0x45, 0x45, 0x44, 0x53, 0x5f, 0x4c, 0x49, 0x4d, 0x49, 0x54, 0x10, 0x02, 0x12, 0x2b, 0x0a, + 0x27, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, + 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x49, 0x4e, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, + 0x45, 0x44, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x10, 0x03, 0x12, 0x27, 0x0a, 0x23, 0x41, 0x43, + 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x41, 0x43, 0x43, + 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x54, 0x52, 0x49, 0x41, 0x4c, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, + 0x45, 0x10, 0x04, 0x12, 0x28, 0x0a, 0x24, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x53, + 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x54, 0x52, + 0x49, 0x41, 0x4c, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x45, 0x44, 0x10, 0x05, 0x2a, 0x9f, 0x01, + 0x0a, 0x0b, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x1c, 0x0a, + 0x18, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x52, 0x4f, 0x4c, 0x45, 0x5f, 0x55, 0x4e, + 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x41, + 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x52, 0x4f, 0x4c, 0x45, 0x5f, 0x41, 0x44, 0x4d, 0x49, + 0x4e, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x52, + 0x4f, 0x4c, 0x45, 0x5f, 0x4a, 0x4f, 0x42, 0x5f, 0x44, 0x45, 0x56, 0x45, 0x4c, 0x4f, 0x50, 0x45, + 0x52, 0x10, 0x02, 0x12, 0x1b, 0x0a, 0x17, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x52, + 0x4f, 0x4c, 0x45, 0x5f, 0x4a, 0x4f, 0x42, 0x5f, 0x56, 0x49, 0x45, 0x57, 0x45, 0x52, 0x10, 0x03, + 0x12, 0x1d, 0x0a, 0x19, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x52, 0x4f, 0x4c, 0x45, + 0x5f, 0x4a, 0x4f, 0x42, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x4f, 0x52, 0x10, 0x04, 0x32, + 0xf8, 0x16, 0x0a, 0x12, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1d, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, - 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x62, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x25, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x6d, - 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, - 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6b, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x50, 0x65, 0x72, - 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x2e, 0x6d, + 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x07, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1d, 0x2e, + 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, + 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, - 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, - 0x61, 0x6c, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x12, 0x89, 0x01, 0x0a, 0x1c, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x50, - 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x54, 0x6f, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x62, + 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x73, 0x12, 0x25, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, + 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x12, 0x6b, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, + 0x6c, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x65, 0x72, 0x73, + 0x6f, 0x6e, 0x61, 0x6c, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x41, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x89, 0x01, 0x0a, 0x1c, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x50, 0x65, 0x72, 0x73, 0x6f, + 0x6e, 0x61, 0x6c, 0x54, 0x6f, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x32, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, + 0x54, 0x6f, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x54, 0x6f, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, - 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x54, 0x6f, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, - 0x68, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x27, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x61, 0x6d, 0x41, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, - 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x62, 0x0a, 0x0f, 0x49, 0x73, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x6e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x2e, 0x6d, - 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x49, 0x73, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x6e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x31, 0x2e, 0x49, 0x73, 0x55, 0x73, 0x65, 0x72, 0x49, 0x6e, 0x41, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7d, 0x0a, - 0x18, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, - 0x72, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x2e, 0x6d, 0x67, 0x6d, 0x74, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x6d, 0x67, 0x6d, 0x74, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7d, 0x0a, 0x18, - 0x53, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, - 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x74, 0x0a, 0x15, 0x47, - 0x65, 0x74, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x65, 0x6d, - 0x62, 0x65, 0x72, 0x73, 0x12, 0x2b, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x2c, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x7a, 0x0a, 0x17, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x65, 0x61, 0x6d, 0x41, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2d, 0x2e, 0x6d, - 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x6d, - 0x6f, 0x76, 0x65, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x65, - 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x6d, 0x67, - 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, - 0x76, 0x65, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x65, 0x6d, - 0x62, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7a, 0x0a, - 0x17, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x54, 0x6f, 0x54, 0x65, 0x61, - 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2d, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x55, - 0x73, 0x65, 0x72, 0x54, 0x6f, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x68, 0x0a, 0x11, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x27, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x6d, 0x67, 0x6d, 0x74, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x62, 0x0a, 0x0f, 0x49, 0x73, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x6e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x49, 0x73, 0x55, 0x73, 0x65, 0x72, 0x49, + 0x6e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x26, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x49, 0x73, 0x55, 0x73, 0x65, 0x72, 0x49, 0x6e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7d, 0x0a, 0x18, 0x47, 0x65, 0x74, + 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7d, 0x0a, 0x18, 0x53, 0x65, 0x74, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, + 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, + 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x74, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x54, 0x65, + 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, + 0x12, 0x2b, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4d, + 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, + 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, + 0x74, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x65, 0x6d, 0x62, + 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7a, 0x0a, + 0x17, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2d, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, + 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x55, 0x73, - 0x65, 0x72, 0x54, 0x6f, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x74, 0x0a, 0x15, 0x47, 0x65, 0x74, - 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, - 0x65, 0x73, 0x12, 0x2b, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x2c, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, - 0x47, 0x65, 0x74, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, - 0x76, 0x69, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, - 0x7a, 0x0a, 0x17, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x12, 0x2d, 0x2e, 0x6d, 0x67, 0x6d, - 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, - 0x65, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x76, 0x69, - 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x6d, 0x67, 0x6d, 0x74, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, - 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7a, 0x0a, 0x17, 0x41, - 0x63, 0x63, 0x65, 0x70, 0x74, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x65, + 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7a, 0x0a, 0x17, 0x49, 0x6e, 0x76, + 0x69, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x54, 0x6f, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2d, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x54, + 0x6f, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x54, 0x6f, + 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x74, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x54, 0x65, 0x61, 0x6d, + 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x73, 0x12, 0x2b, + 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, + 0x65, 0x74, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x76, + 0x69, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x6d, 0x67, + 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, + 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7a, 0x0a, 0x17, 0x52, + 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x12, 0x2d, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x54, 0x65, 0x61, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x54, 0x65, 0x61, 0x6d, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x74, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x53, 0x79, - 0x73, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x2a, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, - 0x47, 0x65, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x6d, 0x67, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7a, 0x0a, 0x17, 0x41, 0x63, 0x63, 0x65, 0x70, + 0x74, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x76, 0x69, + 0x74, 0x65, 0x12, 0x2d, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x2e, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x12, 0x74, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, + 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x83, 0x01, - 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x6e, 0x62, 0x6f, - 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x30, 0x2e, 0x6d, - 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, - 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, - 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, - 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, - 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, - 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x12, 0x83, 0x01, 0x0a, 0x1a, 0x53, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x12, 0x30, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x6e, 0x62, - 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4f, - 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x68, 0x0a, 0x10, 0x47, 0x65, 0x74, - 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x26, 0x2e, - 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, - 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, - 0x90, 0x02, 0x01, 0x12, 0x74, 0x0a, 0x14, 0x49, 0x73, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x2a, 0x2e, 0x6d, 0x67, - 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x49, 0x73, 0x41, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, + 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x83, 0x01, 0x0a, 0x1a, 0x47, 0x65, + 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, + 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x30, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x6d, 0x67, 0x6d, + 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x83, 0x01, 0x0a, 0x1a, 0x53, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x6e, + 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x30, + 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, + 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, + 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x31, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x53, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x6e, 0x62, 0x6f, 0x61, + 0x72, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x68, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x26, 0x2e, 0x6d, 0x67, 0x6d, 0x74, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x27, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, + 0x74, 0x0a, 0x14, 0x49, 0x73, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x2a, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x49, 0x73, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x95, 0x01, 0x0a, 0x20, 0x47, 0x65, - 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x43, - 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x36, - 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, - 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, - 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, - 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x8f, 0x01, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x53, 0x65, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, - 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x53, 0x65, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x6d, 0x67, 0x6d, + 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x49, 0x73, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x95, 0x01, 0x0a, 0x20, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x65, 0x63, 0x6b, + 0x6f, 0x75, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x36, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x72, 0x74, - 0x61, 0x6c, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x12, 0x6e, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, - 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x28, 0x2e, 0x6d, 0x67, 0x6d, 0x74, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x69, 0x6c, - 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, - 0x90, 0x02, 0x01, 0x12, 0x71, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, - 0x67, 0x4d, 0x65, 0x74, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2a, 0x2e, 0x6d, 0x67, - 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x42, - 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x4d, 0x65, 0x74, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x69, 0x6c, + 0x6c, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x8f, 0x01, + 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x69, 0x6c, 0x6c, + 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x34, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, + 0x6e, 0x67, 0x50, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x53, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x6e, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x28, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, + 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x29, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, + 0x71, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x4d, 0x65, 0x74, + 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2a, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, - 0x6e, 0x67, 0x4d, 0x65, 0x74, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0xcc, 0x01, 0x0a, 0x11, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, - 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, 0x10, 0x55, 0x73, - 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, - 0x5a, 0x50, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6e, 0x75, 0x63, - 0x6c, 0x65, 0x75, 0x73, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6e, 0x65, 0x6f, 0x73, 0x79, 0x6e, - 0x63, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, - 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x6d, 0x67, 0x6d, 0x74, 0x2f, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x6d, 0x67, 0x6d, 0x74, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x31, 0xa2, 0x02, 0x03, 0x4d, 0x58, 0x58, 0xaa, 0x02, 0x0d, 0x4d, 0x67, 0x6d, 0x74, 0x2e, - 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, 0x02, 0x0d, 0x4d, 0x67, 0x6d, 0x74, 0x5c, - 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, 0x19, 0x4d, 0x67, 0x6d, 0x74, 0x5c, - 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0e, 0x4d, 0x67, 0x6d, 0x74, 0x3a, 0x3a, 0x56, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6e, 0x67, 0x4d, 0x65, 0x74, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x4d, 0x65, + 0x74, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x12, 0x56, 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x6f, 0x6c, + 0x65, 0x12, 0x21, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x6f, 0x6c, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0xcc, 0x01, 0x0a, 0x11, 0x63, + 0x6f, 0x6d, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x42, 0x10, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x50, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x6e, 0x75, 0x63, 0x6c, 0x65, 0x75, 0x73, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6e, 0x65, + 0x6f, 0x73, 0x79, 0x6e, 0x63, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x67, 0x65, + 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x6d, 0x67, 0x6d, 0x74, + 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x6d, 0x67, 0x6d, 0x74, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x4d, 0x58, 0x58, 0xaa, 0x02, 0x0d, 0x4d, + 0x67, 0x6d, 0x74, 0x2e, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, 0x02, 0x0d, 0x4d, + 0x67, 0x6d, 0x74, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, 0x19, 0x4d, + 0x67, 0x6d, 0x74, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0e, 0x4d, 0x67, 0x6d, 0x74, 0x3a, + 0x3a, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( @@ -3521,141 +3708,147 @@ func file_mgmt_v1alpha1_user_account_proto_rawDescGZIP() []byte { return file_mgmt_v1alpha1_user_account_proto_rawDescData } -var file_mgmt_v1alpha1_user_account_proto_enumTypes = make([]protoimpl.EnumInfo, 3) -var file_mgmt_v1alpha1_user_account_proto_msgTypes = make([]protoimpl.MessageInfo, 53) +var file_mgmt_v1alpha1_user_account_proto_enumTypes = make([]protoimpl.EnumInfo, 4) +var file_mgmt_v1alpha1_user_account_proto_msgTypes = make([]protoimpl.MessageInfo, 55) var file_mgmt_v1alpha1_user_account_proto_goTypes = []any{ (UserAccountType)(0), // 0: mgmt.v1alpha1.UserAccountType (BillingStatus)(0), // 1: mgmt.v1alpha1.BillingStatus (AccountStatus)(0), // 2: mgmt.v1alpha1.AccountStatus - (*GetUserRequest)(nil), // 3: mgmt.v1alpha1.GetUserRequest - (*GetUserResponse)(nil), // 4: mgmt.v1alpha1.GetUserResponse - (*SetUserRequest)(nil), // 5: mgmt.v1alpha1.SetUserRequest - (*SetUserResponse)(nil), // 6: mgmt.v1alpha1.SetUserResponse - (*GetUserAccountsRequest)(nil), // 7: mgmt.v1alpha1.GetUserAccountsRequest - (*GetUserAccountsResponse)(nil), // 8: mgmt.v1alpha1.GetUserAccountsResponse - (*UserAccount)(nil), // 9: mgmt.v1alpha1.UserAccount - (*ConvertPersonalToTeamAccountRequest)(nil), // 10: mgmt.v1alpha1.ConvertPersonalToTeamAccountRequest - (*ConvertPersonalToTeamAccountResponse)(nil), // 11: mgmt.v1alpha1.ConvertPersonalToTeamAccountResponse - (*SetPersonalAccountRequest)(nil), // 12: mgmt.v1alpha1.SetPersonalAccountRequest - (*SetPersonalAccountResponse)(nil), // 13: mgmt.v1alpha1.SetPersonalAccountResponse - (*IsUserInAccountRequest)(nil), // 14: mgmt.v1alpha1.IsUserInAccountRequest - (*IsUserInAccountResponse)(nil), // 15: mgmt.v1alpha1.IsUserInAccountResponse - (*GetAccountTemporalConfigRequest)(nil), // 16: mgmt.v1alpha1.GetAccountTemporalConfigRequest - (*GetAccountTemporalConfigResponse)(nil), // 17: mgmt.v1alpha1.GetAccountTemporalConfigResponse - (*SetAccountTemporalConfigRequest)(nil), // 18: mgmt.v1alpha1.SetAccountTemporalConfigRequest - (*SetAccountTemporalConfigResponse)(nil), // 19: mgmt.v1alpha1.SetAccountTemporalConfigResponse - (*AccountTemporalConfig)(nil), // 20: mgmt.v1alpha1.AccountTemporalConfig - (*CreateTeamAccountRequest)(nil), // 21: mgmt.v1alpha1.CreateTeamAccountRequest - (*CreateTeamAccountResponse)(nil), // 22: mgmt.v1alpha1.CreateTeamAccountResponse - (*AccountUser)(nil), // 23: mgmt.v1alpha1.AccountUser - (*GetTeamAccountMembersRequest)(nil), // 24: mgmt.v1alpha1.GetTeamAccountMembersRequest - (*GetTeamAccountMembersResponse)(nil), // 25: mgmt.v1alpha1.GetTeamAccountMembersResponse - (*RemoveTeamAccountMemberRequest)(nil), // 26: mgmt.v1alpha1.RemoveTeamAccountMemberRequest - (*RemoveTeamAccountMemberResponse)(nil), // 27: mgmt.v1alpha1.RemoveTeamAccountMemberResponse - (*InviteUserToTeamAccountRequest)(nil), // 28: mgmt.v1alpha1.InviteUserToTeamAccountRequest - (*AccountInvite)(nil), // 29: mgmt.v1alpha1.AccountInvite - (*InviteUserToTeamAccountResponse)(nil), // 30: mgmt.v1alpha1.InviteUserToTeamAccountResponse - (*GetTeamAccountInvitesRequest)(nil), // 31: mgmt.v1alpha1.GetTeamAccountInvitesRequest - (*GetTeamAccountInvitesResponse)(nil), // 32: mgmt.v1alpha1.GetTeamAccountInvitesResponse - (*RemoveTeamAccountInviteRequest)(nil), // 33: mgmt.v1alpha1.RemoveTeamAccountInviteRequest - (*RemoveTeamAccountInviteResponse)(nil), // 34: mgmt.v1alpha1.RemoveTeamAccountInviteResponse - (*AcceptTeamAccountInviteRequest)(nil), // 35: mgmt.v1alpha1.AcceptTeamAccountInviteRequest - (*AcceptTeamAccountInviteResponse)(nil), // 36: mgmt.v1alpha1.AcceptTeamAccountInviteResponse - (*GetSystemInformationRequest)(nil), // 37: mgmt.v1alpha1.GetSystemInformationRequest - (*GetSystemInformationResponse)(nil), // 38: mgmt.v1alpha1.GetSystemInformationResponse - (*GetAccountOnboardingConfigRequest)(nil), // 39: mgmt.v1alpha1.GetAccountOnboardingConfigRequest - (*GetAccountOnboardingConfigResponse)(nil), // 40: mgmt.v1alpha1.GetAccountOnboardingConfigResponse - (*SetAccountOnboardingConfigRequest)(nil), // 41: mgmt.v1alpha1.SetAccountOnboardingConfigRequest - (*SetAccountOnboardingConfigResponse)(nil), // 42: mgmt.v1alpha1.SetAccountOnboardingConfigResponse - (*AccountOnboardingConfig)(nil), // 43: mgmt.v1alpha1.AccountOnboardingConfig - (*GetAccountStatusRequest)(nil), // 44: mgmt.v1alpha1.GetAccountStatusRequest - (*GetAccountStatusResponse)(nil), // 45: mgmt.v1alpha1.GetAccountStatusResponse - (*IsAccountStatusValidRequest)(nil), // 46: mgmt.v1alpha1.IsAccountStatusValidRequest - (*IsAccountStatusValidResponse)(nil), // 47: mgmt.v1alpha1.IsAccountStatusValidResponse - (*GetAccountBillingCheckoutSessionRequest)(nil), // 48: mgmt.v1alpha1.GetAccountBillingCheckoutSessionRequest - (*GetAccountBillingCheckoutSessionResponse)(nil), // 49: mgmt.v1alpha1.GetAccountBillingCheckoutSessionResponse - (*GetAccountBillingPortalSessionRequest)(nil), // 50: mgmt.v1alpha1.GetAccountBillingPortalSessionRequest - (*GetAccountBillingPortalSessionResponse)(nil), // 51: mgmt.v1alpha1.GetAccountBillingPortalSessionResponse - (*GetBillingAccountsRequest)(nil), // 52: mgmt.v1alpha1.GetBillingAccountsRequest - (*GetBillingAccountsResponse)(nil), // 53: mgmt.v1alpha1.GetBillingAccountsResponse - (*SetBillingMeterEventRequest)(nil), // 54: mgmt.v1alpha1.SetBillingMeterEventRequest - (*SetBillingMeterEventResponse)(nil), // 55: mgmt.v1alpha1.SetBillingMeterEventResponse - (*timestamppb.Timestamp)(nil), // 56: google.protobuf.Timestamp + (AccountRole)(0), // 3: mgmt.v1alpha1.AccountRole + (*GetUserRequest)(nil), // 4: mgmt.v1alpha1.GetUserRequest + (*GetUserResponse)(nil), // 5: mgmt.v1alpha1.GetUserResponse + (*SetUserRequest)(nil), // 6: mgmt.v1alpha1.SetUserRequest + (*SetUserResponse)(nil), // 7: mgmt.v1alpha1.SetUserResponse + (*GetUserAccountsRequest)(nil), // 8: mgmt.v1alpha1.GetUserAccountsRequest + (*GetUserAccountsResponse)(nil), // 9: mgmt.v1alpha1.GetUserAccountsResponse + (*UserAccount)(nil), // 10: mgmt.v1alpha1.UserAccount + (*ConvertPersonalToTeamAccountRequest)(nil), // 11: mgmt.v1alpha1.ConvertPersonalToTeamAccountRequest + (*ConvertPersonalToTeamAccountResponse)(nil), // 12: mgmt.v1alpha1.ConvertPersonalToTeamAccountResponse + (*SetPersonalAccountRequest)(nil), // 13: mgmt.v1alpha1.SetPersonalAccountRequest + (*SetPersonalAccountResponse)(nil), // 14: mgmt.v1alpha1.SetPersonalAccountResponse + (*IsUserInAccountRequest)(nil), // 15: mgmt.v1alpha1.IsUserInAccountRequest + (*IsUserInAccountResponse)(nil), // 16: mgmt.v1alpha1.IsUserInAccountResponse + (*GetAccountTemporalConfigRequest)(nil), // 17: mgmt.v1alpha1.GetAccountTemporalConfigRequest + (*GetAccountTemporalConfigResponse)(nil), // 18: mgmt.v1alpha1.GetAccountTemporalConfigResponse + (*SetAccountTemporalConfigRequest)(nil), // 19: mgmt.v1alpha1.SetAccountTemporalConfigRequest + (*SetAccountTemporalConfigResponse)(nil), // 20: mgmt.v1alpha1.SetAccountTemporalConfigResponse + (*AccountTemporalConfig)(nil), // 21: mgmt.v1alpha1.AccountTemporalConfig + (*CreateTeamAccountRequest)(nil), // 22: mgmt.v1alpha1.CreateTeamAccountRequest + (*CreateTeamAccountResponse)(nil), // 23: mgmt.v1alpha1.CreateTeamAccountResponse + (*AccountUser)(nil), // 24: mgmt.v1alpha1.AccountUser + (*GetTeamAccountMembersRequest)(nil), // 25: mgmt.v1alpha1.GetTeamAccountMembersRequest + (*GetTeamAccountMembersResponse)(nil), // 26: mgmt.v1alpha1.GetTeamAccountMembersResponse + (*RemoveTeamAccountMemberRequest)(nil), // 27: mgmt.v1alpha1.RemoveTeamAccountMemberRequest + (*RemoveTeamAccountMemberResponse)(nil), // 28: mgmt.v1alpha1.RemoveTeamAccountMemberResponse + (*InviteUserToTeamAccountRequest)(nil), // 29: mgmt.v1alpha1.InviteUserToTeamAccountRequest + (*AccountInvite)(nil), // 30: mgmt.v1alpha1.AccountInvite + (*InviteUserToTeamAccountResponse)(nil), // 31: mgmt.v1alpha1.InviteUserToTeamAccountResponse + (*GetTeamAccountInvitesRequest)(nil), // 32: mgmt.v1alpha1.GetTeamAccountInvitesRequest + (*GetTeamAccountInvitesResponse)(nil), // 33: mgmt.v1alpha1.GetTeamAccountInvitesResponse + (*RemoveTeamAccountInviteRequest)(nil), // 34: mgmt.v1alpha1.RemoveTeamAccountInviteRequest + (*RemoveTeamAccountInviteResponse)(nil), // 35: mgmt.v1alpha1.RemoveTeamAccountInviteResponse + (*AcceptTeamAccountInviteRequest)(nil), // 36: mgmt.v1alpha1.AcceptTeamAccountInviteRequest + (*AcceptTeamAccountInviteResponse)(nil), // 37: mgmt.v1alpha1.AcceptTeamAccountInviteResponse + (*GetSystemInformationRequest)(nil), // 38: mgmt.v1alpha1.GetSystemInformationRequest + (*GetSystemInformationResponse)(nil), // 39: mgmt.v1alpha1.GetSystemInformationResponse + (*GetAccountOnboardingConfigRequest)(nil), // 40: mgmt.v1alpha1.GetAccountOnboardingConfigRequest + (*GetAccountOnboardingConfigResponse)(nil), // 41: mgmt.v1alpha1.GetAccountOnboardingConfigResponse + (*SetAccountOnboardingConfigRequest)(nil), // 42: mgmt.v1alpha1.SetAccountOnboardingConfigRequest + (*SetAccountOnboardingConfigResponse)(nil), // 43: mgmt.v1alpha1.SetAccountOnboardingConfigResponse + (*AccountOnboardingConfig)(nil), // 44: mgmt.v1alpha1.AccountOnboardingConfig + (*GetAccountStatusRequest)(nil), // 45: mgmt.v1alpha1.GetAccountStatusRequest + (*GetAccountStatusResponse)(nil), // 46: mgmt.v1alpha1.GetAccountStatusResponse + (*IsAccountStatusValidRequest)(nil), // 47: mgmt.v1alpha1.IsAccountStatusValidRequest + (*IsAccountStatusValidResponse)(nil), // 48: mgmt.v1alpha1.IsAccountStatusValidResponse + (*GetAccountBillingCheckoutSessionRequest)(nil), // 49: mgmt.v1alpha1.GetAccountBillingCheckoutSessionRequest + (*GetAccountBillingCheckoutSessionResponse)(nil), // 50: mgmt.v1alpha1.GetAccountBillingCheckoutSessionResponse + (*GetAccountBillingPortalSessionRequest)(nil), // 51: mgmt.v1alpha1.GetAccountBillingPortalSessionRequest + (*GetAccountBillingPortalSessionResponse)(nil), // 52: mgmt.v1alpha1.GetAccountBillingPortalSessionResponse + (*GetBillingAccountsRequest)(nil), // 53: mgmt.v1alpha1.GetBillingAccountsRequest + (*GetBillingAccountsResponse)(nil), // 54: mgmt.v1alpha1.GetBillingAccountsResponse + (*SetBillingMeterEventRequest)(nil), // 55: mgmt.v1alpha1.SetBillingMeterEventRequest + (*SetBillingMeterEventResponse)(nil), // 56: mgmt.v1alpha1.SetBillingMeterEventResponse + (*SetUserRoleRequest)(nil), // 57: mgmt.v1alpha1.SetUserRoleRequest + (*SetUserRoleResponse)(nil), // 58: mgmt.v1alpha1.SetUserRoleResponse + (*timestamppb.Timestamp)(nil), // 59: google.protobuf.Timestamp } var file_mgmt_v1alpha1_user_account_proto_depIdxs = []int32{ - 9, // 0: mgmt.v1alpha1.GetUserAccountsResponse.accounts:type_name -> mgmt.v1alpha1.UserAccount + 10, // 0: mgmt.v1alpha1.GetUserAccountsResponse.accounts:type_name -> mgmt.v1alpha1.UserAccount 0, // 1: mgmt.v1alpha1.UserAccount.type:type_name -> mgmt.v1alpha1.UserAccountType - 20, // 2: mgmt.v1alpha1.GetAccountTemporalConfigResponse.config:type_name -> mgmt.v1alpha1.AccountTemporalConfig - 20, // 3: mgmt.v1alpha1.SetAccountTemporalConfigRequest.config:type_name -> mgmt.v1alpha1.AccountTemporalConfig - 20, // 4: mgmt.v1alpha1.SetAccountTemporalConfigResponse.config:type_name -> mgmt.v1alpha1.AccountTemporalConfig - 23, // 5: mgmt.v1alpha1.GetTeamAccountMembersResponse.users:type_name -> mgmt.v1alpha1.AccountUser - 56, // 6: mgmt.v1alpha1.AccountInvite.created_at:type_name -> google.protobuf.Timestamp - 56, // 7: mgmt.v1alpha1.AccountInvite.updated_at:type_name -> google.protobuf.Timestamp - 56, // 8: mgmt.v1alpha1.AccountInvite.expires_at:type_name -> google.protobuf.Timestamp - 29, // 9: mgmt.v1alpha1.InviteUserToTeamAccountResponse.invite:type_name -> mgmt.v1alpha1.AccountInvite - 29, // 10: mgmt.v1alpha1.GetTeamAccountInvitesResponse.invites:type_name -> mgmt.v1alpha1.AccountInvite - 9, // 11: mgmt.v1alpha1.AcceptTeamAccountInviteResponse.account:type_name -> mgmt.v1alpha1.UserAccount - 56, // 12: mgmt.v1alpha1.GetSystemInformationResponse.build_date:type_name -> google.protobuf.Timestamp - 43, // 13: mgmt.v1alpha1.GetAccountOnboardingConfigResponse.config:type_name -> mgmt.v1alpha1.AccountOnboardingConfig - 43, // 14: mgmt.v1alpha1.SetAccountOnboardingConfigRequest.config:type_name -> mgmt.v1alpha1.AccountOnboardingConfig - 43, // 15: mgmt.v1alpha1.SetAccountOnboardingConfigResponse.config:type_name -> mgmt.v1alpha1.AccountOnboardingConfig + 21, // 2: mgmt.v1alpha1.GetAccountTemporalConfigResponse.config:type_name -> mgmt.v1alpha1.AccountTemporalConfig + 21, // 3: mgmt.v1alpha1.SetAccountTemporalConfigRequest.config:type_name -> mgmt.v1alpha1.AccountTemporalConfig + 21, // 4: mgmt.v1alpha1.SetAccountTemporalConfigResponse.config:type_name -> mgmt.v1alpha1.AccountTemporalConfig + 24, // 5: mgmt.v1alpha1.GetTeamAccountMembersResponse.users:type_name -> mgmt.v1alpha1.AccountUser + 59, // 6: mgmt.v1alpha1.AccountInvite.created_at:type_name -> google.protobuf.Timestamp + 59, // 7: mgmt.v1alpha1.AccountInvite.updated_at:type_name -> google.protobuf.Timestamp + 59, // 8: mgmt.v1alpha1.AccountInvite.expires_at:type_name -> google.protobuf.Timestamp + 30, // 9: mgmt.v1alpha1.InviteUserToTeamAccountResponse.invite:type_name -> mgmt.v1alpha1.AccountInvite + 30, // 10: mgmt.v1alpha1.GetTeamAccountInvitesResponse.invites:type_name -> mgmt.v1alpha1.AccountInvite + 10, // 11: mgmt.v1alpha1.AcceptTeamAccountInviteResponse.account:type_name -> mgmt.v1alpha1.UserAccount + 59, // 12: mgmt.v1alpha1.GetSystemInformationResponse.build_date:type_name -> google.protobuf.Timestamp + 44, // 13: mgmt.v1alpha1.GetAccountOnboardingConfigResponse.config:type_name -> mgmt.v1alpha1.AccountOnboardingConfig + 44, // 14: mgmt.v1alpha1.SetAccountOnboardingConfigRequest.config:type_name -> mgmt.v1alpha1.AccountOnboardingConfig + 44, // 15: mgmt.v1alpha1.SetAccountOnboardingConfigResponse.config:type_name -> mgmt.v1alpha1.AccountOnboardingConfig 1, // 16: mgmt.v1alpha1.GetAccountStatusResponse.subscription_status:type_name -> mgmt.v1alpha1.BillingStatus 2, // 17: mgmt.v1alpha1.IsAccountStatusValidResponse.account_status:type_name -> mgmt.v1alpha1.AccountStatus - 56, // 18: mgmt.v1alpha1.IsAccountStatusValidResponse.trial_expires_at:type_name -> google.protobuf.Timestamp - 9, // 19: mgmt.v1alpha1.GetBillingAccountsResponse.accounts:type_name -> mgmt.v1alpha1.UserAccount - 3, // 20: mgmt.v1alpha1.UserAccountService.GetUser:input_type -> mgmt.v1alpha1.GetUserRequest - 5, // 21: mgmt.v1alpha1.UserAccountService.SetUser:input_type -> mgmt.v1alpha1.SetUserRequest - 7, // 22: mgmt.v1alpha1.UserAccountService.GetUserAccounts:input_type -> mgmt.v1alpha1.GetUserAccountsRequest - 12, // 23: mgmt.v1alpha1.UserAccountService.SetPersonalAccount:input_type -> mgmt.v1alpha1.SetPersonalAccountRequest - 10, // 24: mgmt.v1alpha1.UserAccountService.ConvertPersonalToTeamAccount:input_type -> mgmt.v1alpha1.ConvertPersonalToTeamAccountRequest - 21, // 25: mgmt.v1alpha1.UserAccountService.CreateTeamAccount:input_type -> mgmt.v1alpha1.CreateTeamAccountRequest - 14, // 26: mgmt.v1alpha1.UserAccountService.IsUserInAccount:input_type -> mgmt.v1alpha1.IsUserInAccountRequest - 16, // 27: mgmt.v1alpha1.UserAccountService.GetAccountTemporalConfig:input_type -> mgmt.v1alpha1.GetAccountTemporalConfigRequest - 18, // 28: mgmt.v1alpha1.UserAccountService.SetAccountTemporalConfig:input_type -> mgmt.v1alpha1.SetAccountTemporalConfigRequest - 24, // 29: mgmt.v1alpha1.UserAccountService.GetTeamAccountMembers:input_type -> mgmt.v1alpha1.GetTeamAccountMembersRequest - 26, // 30: mgmt.v1alpha1.UserAccountService.RemoveTeamAccountMember:input_type -> mgmt.v1alpha1.RemoveTeamAccountMemberRequest - 28, // 31: mgmt.v1alpha1.UserAccountService.InviteUserToTeamAccount:input_type -> mgmt.v1alpha1.InviteUserToTeamAccountRequest - 31, // 32: mgmt.v1alpha1.UserAccountService.GetTeamAccountInvites:input_type -> mgmt.v1alpha1.GetTeamAccountInvitesRequest - 33, // 33: mgmt.v1alpha1.UserAccountService.RemoveTeamAccountInvite:input_type -> mgmt.v1alpha1.RemoveTeamAccountInviteRequest - 35, // 34: mgmt.v1alpha1.UserAccountService.AcceptTeamAccountInvite:input_type -> mgmt.v1alpha1.AcceptTeamAccountInviteRequest - 37, // 35: mgmt.v1alpha1.UserAccountService.GetSystemInformation:input_type -> mgmt.v1alpha1.GetSystemInformationRequest - 39, // 36: mgmt.v1alpha1.UserAccountService.GetAccountOnboardingConfig:input_type -> mgmt.v1alpha1.GetAccountOnboardingConfigRequest - 41, // 37: mgmt.v1alpha1.UserAccountService.SetAccountOnboardingConfig:input_type -> mgmt.v1alpha1.SetAccountOnboardingConfigRequest - 44, // 38: mgmt.v1alpha1.UserAccountService.GetAccountStatus:input_type -> mgmt.v1alpha1.GetAccountStatusRequest - 46, // 39: mgmt.v1alpha1.UserAccountService.IsAccountStatusValid:input_type -> mgmt.v1alpha1.IsAccountStatusValidRequest - 48, // 40: mgmt.v1alpha1.UserAccountService.GetAccountBillingCheckoutSession:input_type -> mgmt.v1alpha1.GetAccountBillingCheckoutSessionRequest - 50, // 41: mgmt.v1alpha1.UserAccountService.GetAccountBillingPortalSession:input_type -> mgmt.v1alpha1.GetAccountBillingPortalSessionRequest - 52, // 42: mgmt.v1alpha1.UserAccountService.GetBillingAccounts:input_type -> mgmt.v1alpha1.GetBillingAccountsRequest - 54, // 43: mgmt.v1alpha1.UserAccountService.SetBillingMeterEvent:input_type -> mgmt.v1alpha1.SetBillingMeterEventRequest - 4, // 44: mgmt.v1alpha1.UserAccountService.GetUser:output_type -> mgmt.v1alpha1.GetUserResponse - 6, // 45: mgmt.v1alpha1.UserAccountService.SetUser:output_type -> mgmt.v1alpha1.SetUserResponse - 8, // 46: mgmt.v1alpha1.UserAccountService.GetUserAccounts:output_type -> mgmt.v1alpha1.GetUserAccountsResponse - 13, // 47: mgmt.v1alpha1.UserAccountService.SetPersonalAccount:output_type -> mgmt.v1alpha1.SetPersonalAccountResponse - 11, // 48: mgmt.v1alpha1.UserAccountService.ConvertPersonalToTeamAccount:output_type -> mgmt.v1alpha1.ConvertPersonalToTeamAccountResponse - 22, // 49: mgmt.v1alpha1.UserAccountService.CreateTeamAccount:output_type -> mgmt.v1alpha1.CreateTeamAccountResponse - 15, // 50: mgmt.v1alpha1.UserAccountService.IsUserInAccount:output_type -> mgmt.v1alpha1.IsUserInAccountResponse - 17, // 51: mgmt.v1alpha1.UserAccountService.GetAccountTemporalConfig:output_type -> mgmt.v1alpha1.GetAccountTemporalConfigResponse - 19, // 52: mgmt.v1alpha1.UserAccountService.SetAccountTemporalConfig:output_type -> mgmt.v1alpha1.SetAccountTemporalConfigResponse - 25, // 53: mgmt.v1alpha1.UserAccountService.GetTeamAccountMembers:output_type -> mgmt.v1alpha1.GetTeamAccountMembersResponse - 27, // 54: mgmt.v1alpha1.UserAccountService.RemoveTeamAccountMember:output_type -> mgmt.v1alpha1.RemoveTeamAccountMemberResponse - 30, // 55: mgmt.v1alpha1.UserAccountService.InviteUserToTeamAccount:output_type -> mgmt.v1alpha1.InviteUserToTeamAccountResponse - 32, // 56: mgmt.v1alpha1.UserAccountService.GetTeamAccountInvites:output_type -> mgmt.v1alpha1.GetTeamAccountInvitesResponse - 34, // 57: mgmt.v1alpha1.UserAccountService.RemoveTeamAccountInvite:output_type -> mgmt.v1alpha1.RemoveTeamAccountInviteResponse - 36, // 58: mgmt.v1alpha1.UserAccountService.AcceptTeamAccountInvite:output_type -> mgmt.v1alpha1.AcceptTeamAccountInviteResponse - 38, // 59: mgmt.v1alpha1.UserAccountService.GetSystemInformation:output_type -> mgmt.v1alpha1.GetSystemInformationResponse - 40, // 60: mgmt.v1alpha1.UserAccountService.GetAccountOnboardingConfig:output_type -> mgmt.v1alpha1.GetAccountOnboardingConfigResponse - 42, // 61: mgmt.v1alpha1.UserAccountService.SetAccountOnboardingConfig:output_type -> mgmt.v1alpha1.SetAccountOnboardingConfigResponse - 45, // 62: mgmt.v1alpha1.UserAccountService.GetAccountStatus:output_type -> mgmt.v1alpha1.GetAccountStatusResponse - 47, // 63: mgmt.v1alpha1.UserAccountService.IsAccountStatusValid:output_type -> mgmt.v1alpha1.IsAccountStatusValidResponse - 49, // 64: mgmt.v1alpha1.UserAccountService.GetAccountBillingCheckoutSession:output_type -> mgmt.v1alpha1.GetAccountBillingCheckoutSessionResponse - 51, // 65: mgmt.v1alpha1.UserAccountService.GetAccountBillingPortalSession:output_type -> mgmt.v1alpha1.GetAccountBillingPortalSessionResponse - 53, // 66: mgmt.v1alpha1.UserAccountService.GetBillingAccounts:output_type -> mgmt.v1alpha1.GetBillingAccountsResponse - 55, // 67: mgmt.v1alpha1.UserAccountService.SetBillingMeterEvent:output_type -> mgmt.v1alpha1.SetBillingMeterEventResponse - 44, // [44:68] is the sub-list for method output_type - 20, // [20:44] is the sub-list for method input_type - 20, // [20:20] is the sub-list for extension type_name - 20, // [20:20] is the sub-list for extension extendee - 0, // [0:20] is the sub-list for field type_name + 59, // 18: mgmt.v1alpha1.IsAccountStatusValidResponse.trial_expires_at:type_name -> google.protobuf.Timestamp + 10, // 19: mgmt.v1alpha1.GetBillingAccountsResponse.accounts:type_name -> mgmt.v1alpha1.UserAccount + 3, // 20: mgmt.v1alpha1.SetUserRoleRequest.role:type_name -> mgmt.v1alpha1.AccountRole + 4, // 21: mgmt.v1alpha1.UserAccountService.GetUser:input_type -> mgmt.v1alpha1.GetUserRequest + 6, // 22: mgmt.v1alpha1.UserAccountService.SetUser:input_type -> mgmt.v1alpha1.SetUserRequest + 8, // 23: mgmt.v1alpha1.UserAccountService.GetUserAccounts:input_type -> mgmt.v1alpha1.GetUserAccountsRequest + 13, // 24: mgmt.v1alpha1.UserAccountService.SetPersonalAccount:input_type -> mgmt.v1alpha1.SetPersonalAccountRequest + 11, // 25: mgmt.v1alpha1.UserAccountService.ConvertPersonalToTeamAccount:input_type -> mgmt.v1alpha1.ConvertPersonalToTeamAccountRequest + 22, // 26: mgmt.v1alpha1.UserAccountService.CreateTeamAccount:input_type -> mgmt.v1alpha1.CreateTeamAccountRequest + 15, // 27: mgmt.v1alpha1.UserAccountService.IsUserInAccount:input_type -> mgmt.v1alpha1.IsUserInAccountRequest + 17, // 28: mgmt.v1alpha1.UserAccountService.GetAccountTemporalConfig:input_type -> mgmt.v1alpha1.GetAccountTemporalConfigRequest + 19, // 29: mgmt.v1alpha1.UserAccountService.SetAccountTemporalConfig:input_type -> mgmt.v1alpha1.SetAccountTemporalConfigRequest + 25, // 30: mgmt.v1alpha1.UserAccountService.GetTeamAccountMembers:input_type -> mgmt.v1alpha1.GetTeamAccountMembersRequest + 27, // 31: mgmt.v1alpha1.UserAccountService.RemoveTeamAccountMember:input_type -> mgmt.v1alpha1.RemoveTeamAccountMemberRequest + 29, // 32: mgmt.v1alpha1.UserAccountService.InviteUserToTeamAccount:input_type -> mgmt.v1alpha1.InviteUserToTeamAccountRequest + 32, // 33: mgmt.v1alpha1.UserAccountService.GetTeamAccountInvites:input_type -> mgmt.v1alpha1.GetTeamAccountInvitesRequest + 34, // 34: mgmt.v1alpha1.UserAccountService.RemoveTeamAccountInvite:input_type -> mgmt.v1alpha1.RemoveTeamAccountInviteRequest + 36, // 35: mgmt.v1alpha1.UserAccountService.AcceptTeamAccountInvite:input_type -> mgmt.v1alpha1.AcceptTeamAccountInviteRequest + 38, // 36: mgmt.v1alpha1.UserAccountService.GetSystemInformation:input_type -> mgmt.v1alpha1.GetSystemInformationRequest + 40, // 37: mgmt.v1alpha1.UserAccountService.GetAccountOnboardingConfig:input_type -> mgmt.v1alpha1.GetAccountOnboardingConfigRequest + 42, // 38: mgmt.v1alpha1.UserAccountService.SetAccountOnboardingConfig:input_type -> mgmt.v1alpha1.SetAccountOnboardingConfigRequest + 45, // 39: mgmt.v1alpha1.UserAccountService.GetAccountStatus:input_type -> mgmt.v1alpha1.GetAccountStatusRequest + 47, // 40: mgmt.v1alpha1.UserAccountService.IsAccountStatusValid:input_type -> mgmt.v1alpha1.IsAccountStatusValidRequest + 49, // 41: mgmt.v1alpha1.UserAccountService.GetAccountBillingCheckoutSession:input_type -> mgmt.v1alpha1.GetAccountBillingCheckoutSessionRequest + 51, // 42: mgmt.v1alpha1.UserAccountService.GetAccountBillingPortalSession:input_type -> mgmt.v1alpha1.GetAccountBillingPortalSessionRequest + 53, // 43: mgmt.v1alpha1.UserAccountService.GetBillingAccounts:input_type -> mgmt.v1alpha1.GetBillingAccountsRequest + 55, // 44: mgmt.v1alpha1.UserAccountService.SetBillingMeterEvent:input_type -> mgmt.v1alpha1.SetBillingMeterEventRequest + 57, // 45: mgmt.v1alpha1.UserAccountService.SetUserRole:input_type -> mgmt.v1alpha1.SetUserRoleRequest + 5, // 46: mgmt.v1alpha1.UserAccountService.GetUser:output_type -> mgmt.v1alpha1.GetUserResponse + 7, // 47: mgmt.v1alpha1.UserAccountService.SetUser:output_type -> mgmt.v1alpha1.SetUserResponse + 9, // 48: mgmt.v1alpha1.UserAccountService.GetUserAccounts:output_type -> mgmt.v1alpha1.GetUserAccountsResponse + 14, // 49: mgmt.v1alpha1.UserAccountService.SetPersonalAccount:output_type -> mgmt.v1alpha1.SetPersonalAccountResponse + 12, // 50: mgmt.v1alpha1.UserAccountService.ConvertPersonalToTeamAccount:output_type -> mgmt.v1alpha1.ConvertPersonalToTeamAccountResponse + 23, // 51: mgmt.v1alpha1.UserAccountService.CreateTeamAccount:output_type -> mgmt.v1alpha1.CreateTeamAccountResponse + 16, // 52: mgmt.v1alpha1.UserAccountService.IsUserInAccount:output_type -> mgmt.v1alpha1.IsUserInAccountResponse + 18, // 53: mgmt.v1alpha1.UserAccountService.GetAccountTemporalConfig:output_type -> mgmt.v1alpha1.GetAccountTemporalConfigResponse + 20, // 54: mgmt.v1alpha1.UserAccountService.SetAccountTemporalConfig:output_type -> mgmt.v1alpha1.SetAccountTemporalConfigResponse + 26, // 55: mgmt.v1alpha1.UserAccountService.GetTeamAccountMembers:output_type -> mgmt.v1alpha1.GetTeamAccountMembersResponse + 28, // 56: mgmt.v1alpha1.UserAccountService.RemoveTeamAccountMember:output_type -> mgmt.v1alpha1.RemoveTeamAccountMemberResponse + 31, // 57: mgmt.v1alpha1.UserAccountService.InviteUserToTeamAccount:output_type -> mgmt.v1alpha1.InviteUserToTeamAccountResponse + 33, // 58: mgmt.v1alpha1.UserAccountService.GetTeamAccountInvites:output_type -> mgmt.v1alpha1.GetTeamAccountInvitesResponse + 35, // 59: mgmt.v1alpha1.UserAccountService.RemoveTeamAccountInvite:output_type -> mgmt.v1alpha1.RemoveTeamAccountInviteResponse + 37, // 60: mgmt.v1alpha1.UserAccountService.AcceptTeamAccountInvite:output_type -> mgmt.v1alpha1.AcceptTeamAccountInviteResponse + 39, // 61: mgmt.v1alpha1.UserAccountService.GetSystemInformation:output_type -> mgmt.v1alpha1.GetSystemInformationResponse + 41, // 62: mgmt.v1alpha1.UserAccountService.GetAccountOnboardingConfig:output_type -> mgmt.v1alpha1.GetAccountOnboardingConfigResponse + 43, // 63: mgmt.v1alpha1.UserAccountService.SetAccountOnboardingConfig:output_type -> mgmt.v1alpha1.SetAccountOnboardingConfigResponse + 46, // 64: mgmt.v1alpha1.UserAccountService.GetAccountStatus:output_type -> mgmt.v1alpha1.GetAccountStatusResponse + 48, // 65: mgmt.v1alpha1.UserAccountService.IsAccountStatusValid:output_type -> mgmt.v1alpha1.IsAccountStatusValidResponse + 50, // 66: mgmt.v1alpha1.UserAccountService.GetAccountBillingCheckoutSession:output_type -> mgmt.v1alpha1.GetAccountBillingCheckoutSessionResponse + 52, // 67: mgmt.v1alpha1.UserAccountService.GetAccountBillingPortalSession:output_type -> mgmt.v1alpha1.GetAccountBillingPortalSessionResponse + 54, // 68: mgmt.v1alpha1.UserAccountService.GetBillingAccounts:output_type -> mgmt.v1alpha1.GetBillingAccountsResponse + 56, // 69: mgmt.v1alpha1.UserAccountService.SetBillingMeterEvent:output_type -> mgmt.v1alpha1.SetBillingMeterEventResponse + 58, // 70: mgmt.v1alpha1.UserAccountService.SetUserRole:output_type -> mgmt.v1alpha1.SetUserRoleResponse + 46, // [46:71] is the sub-list for method output_type + 21, // [21:46] is the sub-list for method input_type + 21, // [21:21] is the sub-list for extension type_name + 21, // [21:21] is the sub-list for extension extendee + 0, // [0:21] is the sub-list for field type_name } func init() { file_mgmt_v1alpha1_user_account_proto_init() } @@ -3675,8 +3868,8 @@ func file_mgmt_v1alpha1_user_account_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_mgmt_v1alpha1_user_account_proto_rawDesc, - NumEnums: 3, - NumMessages: 53, + NumEnums: 4, + NumMessages: 55, NumExtensions: 0, NumServices: 1, }, diff --git a/backend/gen/go/protos/mgmt/v1alpha1/user_account.pb.json.go b/backend/gen/go/protos/mgmt/v1alpha1/user_account.pb.json.go index 392e55a0c0..d20e7714d1 100644 --- a/backend/gen/go/protos/mgmt/v1alpha1/user_account.pb.json.go +++ b/backend/gen/go/protos/mgmt/v1alpha1/user_account.pb.json.go @@ -536,3 +536,23 @@ func (msg *SetBillingMeterEventResponse) MarshalJSON() ([]byte, error) { func (msg *SetBillingMeterEventResponse) UnmarshalJSON(b []byte) error { return protojson.UnmarshalOptions{}.Unmarshal(b, msg) } + +// MarshalJSON implements json.Marshaler +func (msg *SetUserRoleRequest) MarshalJSON() ([]byte, error) { + return protojson.MarshalOptions{}.Marshal(msg) +} + +// UnmarshalJSON implements json.Unmarshaler +func (msg *SetUserRoleRequest) UnmarshalJSON(b []byte) error { + return protojson.UnmarshalOptions{}.Unmarshal(b, msg) +} + +// MarshalJSON implements json.Marshaler +func (msg *SetUserRoleResponse) MarshalJSON() ([]byte, error) { + return protojson.MarshalOptions{}.Marshal(msg) +} + +// UnmarshalJSON implements json.Unmarshaler +func (msg *SetUserRoleResponse) UnmarshalJSON(b []byte) error { + return protojson.UnmarshalOptions{}.Unmarshal(b, msg) +} diff --git a/backend/internal/cmds/mgmt/serve/connect/cmd.go b/backend/internal/cmds/mgmt/serve/connect/cmd.go index d5ccbedb51..551678b2b1 100644 --- a/backend/internal/cmds/mgmt/serve/connect/cmd.go +++ b/backend/internal/cmds/mgmt/serve/connect/cmd.go @@ -17,6 +17,8 @@ import ( "connectrpc.com/otelconnect" "github.com/auth0/go-jwt-middleware/v2/validator" "github.com/go-logr/logr" + "github.com/jackc/pgx/v5/stdlib" + db_queries "github.com/nucleuscloud/neosync/backend/gen/go/db" "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect" connectionmanager "github.com/nucleuscloud/neosync/internal/connection-manager" "github.com/nucleuscloud/neosync/internal/connectrpc/validate" @@ -43,9 +45,12 @@ import ( bookend_logging_interceptor "github.com/nucleuscloud/neosync/backend/internal/connect/interceptors/bookend" logger_interceptor "github.com/nucleuscloud/neosync/backend/internal/connect/interceptors/logger" jobhooks "github.com/nucleuscloud/neosync/backend/internal/ee/hooks/jobs" + "github.com/nucleuscloud/neosync/backend/internal/ee/rbac" + "github.com/nucleuscloud/neosync/backend/internal/ee/rbac/enforcer" neosync_gcp "github.com/nucleuscloud/neosync/backend/internal/gcp" "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" "github.com/nucleuscloud/neosync/backend/internal/temporal/clientmanager" + "github.com/nucleuscloud/neosync/backend/internal/userdata" neosynclogger "github.com/nucleuscloud/neosync/backend/pkg/logger" "github.com/nucleuscloud/neosync/backend/pkg/mongoconnect" mssql_queries "github.com/nucleuscloud/neosync/backend/pkg/mssql-querier" @@ -122,6 +127,11 @@ func serve(ctx context.Context) error { } slogger.Debug(fmt.Sprintf("neosync cloud enabled: %t", ncloudlicense.IsValid())) + cascadelicense := license.NewCascadeLicense( + ncloudlicense, + eelicense, + ) + mux := http.NewServeMux() services := []string{ @@ -159,15 +169,18 @@ func serve(ctx context.Context) error { return err } - db, err := neosyncdb.NewFromConfig(dbconfig) + pool, err := neosyncdb.NewPool(dbconfig) if err != nil { return err } + querier := db_queries.New() + db := neosyncdb.New(pool, querier) + if viper.GetBool("DB_AUTO_MIGRATE") { schemaDir := viper.GetString("DB_SCHEMA_DIR") if schemaDir == "" { - return errors.New("must provide DB_SCHEMA_DIR env var to run auto db migrations") + return errors.New("must provide DB_SCHEMA_DIR env var to run auto db migrationssss") } dbMigConfig, err := getDbMigrationConfig() if err != nil { @@ -180,8 +193,34 @@ func serve(ctx context.Context) error { schemaDir, slogger, ); err != nil { - return fmt.Errorf("unable to complete database migrations: %w", err) + return fmt.Errorf("unable to complete database migrationss: %w", err) + } + } + + var rbacclient rbac.Interface + if cascadelicense.IsValid() { + slogger.Debug("rbac is enabled") + stddb := stdlib.OpenDBFromPool(pool) + + rbacenforcer, err := enforcer.NewActiveEnforcer(ctx, stddb, "neosync_api.casbin_rule") + if err != nil { + return err + } + rbacenforcer.EnableAutoSave(true) + err = rbacenforcer.LoadPolicy() + if err != nil { + return fmt.Errorf("unable to load rbac policies: %w", err) + } + rbacdb := rbac.NewRbacDb(querier, db.Db) + enforcedClient := rbac.New(rbacenforcer) + err = enforcedClient.InitPolicies(ctx, rbacdb, slogger) + if err != nil { + return fmt.Errorf("unable to initialize rbac policies: %w", err) } + rbacclient = enforcedClient + } else { + slogger.Debug("rbac is disabled") + rbacclient = rbac.NewAllowAllClient() } stdInterceptors := []connect.Interceptor{} @@ -436,7 +475,7 @@ func serve(ctx context.Context) error { IsAuthEnabled: isAuthEnabled, IsNeosyncCloud: ncloudlicense.IsValid(), DefaultMaxAllowedRecords: getDefaultMaxAllowedRecords(), - }, db, temporalConfigProvider, authclient, authadminclient, billingClient) + }, db, temporalConfigProvider, authclient, authadminclient, billingClient, rbacclient) api.Handle( mgmtv1alpha1connect.NewUserAccountServiceHandler( useraccountService, @@ -446,10 +485,11 @@ func serve(ctx context.Context) error { connect.WithRecover(recoverHandler), ), ) + userdataclient := userdata.NewClient(useraccountService, rbacclient) apiKeyService := v1alpha1_apikeyservice.New(&v1alpha1_apikeyservice.Config{ IsAuthEnabled: isAuthEnabled, - }, db, useraccountService) + }, db, userdataclient) api.Handle( mgmtv1alpha1connect.NewApiKeyServiceHandler( apiKeyService, @@ -473,10 +513,11 @@ func serve(ctx context.Context) error { sql_manager.WithConnectionManagerOpts(connectionmanager.WithCloseOnRelease()), ) mongoconnector := mongoconnect.NewConnector() + connectionService := v1alpha1_connectionservice.New( &v1alpha1_connectionservice.Config{}, db, - useraccountService, + userdataclient, mongoconnector, awsManager, sqlmanager, @@ -499,7 +540,7 @@ func serve(ctx context.Context) error { jobhookService := jobhooks.New( db, - useraccountService, + userdataclient, jobhookOpts..., ) @@ -518,9 +559,9 @@ func serve(ctx context.Context) error { db, tfwfmgr, connectionService, - useraccountService, sqlmanager, jobhookService, + userdataclient, ) api.Handle( mgmtv1alpha1connect.NewJobServiceHandler( @@ -558,7 +599,7 @@ func serve(ctx context.Context) error { transformerService := v1alpha1_transformerservice.New(&v1alpha1_transformerservice.Config{ IsPresidioEnabled: ncloudlicense.IsValid(), IsNeosyncCloud: ncloudlicense.IsValid(), - }, db, useraccountService, presEntityClient) + }, db, presEntityClient, userdataclient) api.Handle( mgmtv1alpha1connect.NewTransformersServiceHandler( transformerService, @@ -574,7 +615,7 @@ func serve(ctx context.Context) error { PresidioDefaultLanguage: getPresidioDefaultLanguage(), IsAuthEnabled: isAuthEnabled, IsNeosyncCloud: ncloudlicense.IsValid(), - }, anonymizerMeter, useraccountService, presAnalyzeClient, presAnonClient, db) + }, anonymizerMeter, userdataclient, useraccountService, presAnalyzeClient, presAnonClient, db) api.Handle( mgmtv1alpha1connect.NewAnonymizationServiceHandler( anonymizationService, @@ -588,7 +629,6 @@ func serve(ctx context.Context) error { gcpmanager := neosync_gcp.NewManager() connectionDataService := v1alpha1_connectiondataservice.New( &v1alpha1_connectiondataservice.Config{}, - useraccountService, connectionService, jobService, awsManager, @@ -612,7 +652,7 @@ func serve(ctx context.Context) error { if shouldEnableMetricsService() { metricsService := v1alpha1_metricsservice.New( &v1alpha1_metricsservice.Config{}, - useraccountService, + userdataclient, jobService, promv1.NewAPI(promclient), ) diff --git a/backend/internal/ee/hooks/jobs/service.go b/backend/internal/ee/hooks/jobs/service.go index 1082ec7e6b..6dcd64d097 100644 --- a/backend/internal/ee/hooks/jobs/service.go +++ b/backend/internal/ee/hooks/jobs/service.go @@ -12,14 +12,16 @@ import ( "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect" logger_interceptor "github.com/nucleuscloud/neosync/backend/internal/connect/interceptors/logger" "github.com/nucleuscloud/neosync/backend/internal/dtomaps" + "github.com/nucleuscloud/neosync/backend/internal/ee/rbac" nucleuserrors "github.com/nucleuscloud/neosync/backend/internal/errors" "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" + "github.com/nucleuscloud/neosync/backend/internal/userdata" ) type Service struct { - cfg *config - db *neosyncdb.NeosyncDb - useraccountService mgmtv1alpha1connect.UserAccountServiceClient + cfg *config + db *neosyncdb.NeosyncDb + userdataclient userdata.Interface } var _ Interface = (*Service)(nil) @@ -49,7 +51,7 @@ type Option func(*config) func New( db *neosyncdb.NeosyncDb, - useraccountservice mgmtv1alpha1connect.UserAccountServiceClient, + userdataclient userdata.Interface, opts ...Option, ) *Service { cfg := &config{} @@ -57,7 +59,7 @@ func New( opt(cfg) } - return &Service{cfg: cfg, db: db, useraccountService: useraccountservice} + return &Service{cfg: cfg, db: db, userdataclient: userdataclient} } func (s *Service) GetJobHooks( @@ -71,7 +73,7 @@ func (s *Service) GetJobHooks( logger := logger_interceptor.GetLoggerFromContextOrDefault(ctx) logger = logger.With("jobId", req.GetJobId()) - verifyResp, err := s.verifyUserHasJob(ctx, req.GetJobId()) + verifyResp, err := s.verifyUserHasJob(ctx, req.GetJobId(), rbac.JobAction_View) if err != nil { return nil, err } @@ -114,7 +116,7 @@ func (s *Service) GetJobHook( return nil, nucleuserrors.NewNotFound("unable to find job hook by id") } - verifyResp, err := s.verifyUserHasJob(ctx, neosyncdb.UUIDString(hook.JobID)) + verifyResp, err := s.verifyUserHasJob(ctx, neosyncdb.UUIDString(hook.JobID), rbac.JobAction_View) if err != nil { return nil, err } @@ -156,7 +158,7 @@ func (s *Service) DeleteJobHook( return &mgmtv1alpha1.DeleteJobHookResponse{}, nil } - verifyResp, err := s.verifyUserHasJob(ctx, neosyncdb.UUIDString(hook.JobID)) + verifyResp, err := s.verifyUserHasJob(ctx, neosyncdb.UUIDString(hook.JobID), rbac.JobAction_Delete) if err != nil { return nil, err } @@ -187,7 +189,7 @@ func (s *Service) IsJobHookNameAvailable( if err != nil { return nil, err } - verifyResp, err := s.verifyUserHasJob(ctx, req.GetJobId()) + verifyResp, err := s.verifyUserHasJob(ctx, req.GetJobId(), rbac.JobAction_View) if err != nil { return nil, err } @@ -221,7 +223,7 @@ func (s *Service) CreateJobHook( if err != nil { return nil, err } - verifyResp, err := s.verifyUserHasJob(ctx, req.GetJobId()) + verifyResp, err := s.verifyUserHasJob(ctx, req.GetJobId(), rbac.JobAction_Create) if err != nil { return nil, err } @@ -230,11 +232,6 @@ func (s *Service) CreateJobHook( "jobId", neosyncdb.UUIDString(verifyResp.JobUuid), ) - useruuid, err := s.getUserUuid(ctx) - if err != nil { - return nil, err - } - hookReq := req.GetHook() logger.Debug(fmt.Sprintf("attempting to create new job hook %q", hookReq.GetName())) @@ -267,8 +264,8 @@ func (s *Service) CreateJobHook( JobID: jobuuid, Enabled: hookReq.GetEnabled(), Priority: priority, - CreatedByUserID: *useruuid, - UpdatedByUserID: *useruuid, + CreatedByUserID: verifyResp.UserUuid, + UpdatedByUserID: verifyResp.UserUuid, Config: config, }) if err != nil { @@ -295,10 +292,6 @@ func (s *Service) UpdateJobHook( logger := logger_interceptor.GetLoggerFromContextOrDefault(ctx) logger = logger.With("hookId", req.GetId()) - useruuid, err := s.getUserUuid(ctx) - if err != nil { - return nil, err - } jobuuid, err := neosyncdb.ToUuid(getResp.GetHook().GetJobId()) if err != nil { return nil, err @@ -332,13 +325,22 @@ func (s *Service) UpdateJobHook( return nil, err } + user, err := s.userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + _, err = s.verifyUserHasJob(ctx, neosyncdb.UUIDString(jobuuid), rbac.JobAction_Edit) + if err != nil { + return nil, err + } + updatedhook, err := s.db.Q.UpdateJobHook(ctx, s.db.Db, db_queries.UpdateJobHookParams{ Name: req.GetName(), Description: req.GetDescription(), Config: config, Enabled: req.GetEnabled(), Priority: priority, - UpdatedByUserID: *useruuid, + UpdatedByUserID: user.PgId(), ID: hookuuid, }) if err != nil { @@ -370,10 +372,11 @@ func (s *Service) SetJobHookEnabled( return &mgmtv1alpha1.SetJobHookEnabledResponse{Hook: getResp.GetHook()}, nil } - useruuid, err := s.getUserUuid(ctx) + verifyResp, err := s.verifyUserHasJob(ctx, getResp.GetHook().GetJobId(), rbac.JobAction_Edit) if err != nil { return nil, err } + hookuuid, err := neosyncdb.ToUuid(getResp.GetHook().GetId()) if err != nil { return nil, err @@ -382,7 +385,7 @@ func (s *Service) SetJobHookEnabled( logger.Debug(fmt.Sprintf("attempting to update job hook enabled status from %v to %v", getResp.GetHook().GetEnabled(), req.GetEnabled())) updatedHook, err := s.db.Q.SetJobHookEnabled(ctx, s.db.Db, db_queries.SetJobHookEnabledParams{ Enabled: req.GetEnabled(), - UpdatedByUserID: *useruuid, + UpdatedByUserID: verifyResp.UserUuid, ID: hookuuid, }) if err != nil { @@ -408,7 +411,7 @@ func (s *Service) GetActiveJobHooksByTiming( logger := logger_interceptor.GetLoggerFromContextOrDefault(ctx) logger = logger.With("jobId", req.GetJobId()) - verifyResp, err := s.verifyUserHasJob(ctx, req.GetJobId()) + verifyResp, err := s.verifyUserHasJob(ctx, req.GetJobId(), rbac.JobAction_View) if err != nil { return nil, err } @@ -456,9 +459,10 @@ func (s *Service) GetActiveJobHooksByTiming( type verifyUserJobResponse struct { JobUuid pgtype.UUID AccountUuid pgtype.UUID + UserUuid pgtype.UUID } -func (s *Service) verifyUserHasJob(ctx context.Context, jobId string) (*verifyUserJobResponse, error) { +func (s *Service) verifyUserHasJob(ctx context.Context, jobId string, permission rbac.JobAction) (*verifyUserJobResponse, error) { jobuuid, err := neosyncdb.ToUuid(jobId) if err != nil { return nil, err @@ -470,13 +474,20 @@ func (s *Service) verifyUserHasJob(ctx context.Context, jobId string) (*verifyUs } else if err != nil && neosyncdb.IsNoRows(err) { return nil, nucleuserrors.NewNotFound("unable to find job id") } - _, err = s.verifyUserInAccount(ctx, neosyncdb.UUIDString(accountUuid)) + + user, err := s.userdataclient.GetUser(ctx) if err != nil { return nil, err } + + if err := user.EnforceJob(ctx, userdata.NewDbDomainEntity(accountUuid, jobuuid), permission); err != nil { + return nil, err + } + return &verifyUserJobResponse{ JobUuid: jobuuid, AccountUuid: accountUuid, + UserUuid: user.PgId(), }, nil } diff --git a/backend/internal/ee/hooks/jobs/user-account.go b/backend/internal/ee/hooks/jobs/user-account.go deleted file mode 100644 index 95686ec33e..0000000000 --- a/backend/internal/ee/hooks/jobs/user-account.go +++ /dev/null @@ -1,59 +0,0 @@ -package jobhooks - -import ( - "context" - - "connectrpc.com/connect" - "github.com/jackc/pgx/v5/pgtype" - mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" - "github.com/nucleuscloud/neosync/backend/internal/apikey" - auth_apikey "github.com/nucleuscloud/neosync/backend/internal/auth/apikey" - nucleuserrors "github.com/nucleuscloud/neosync/backend/internal/errors" - "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" -) - -func (s *Service) verifyUserInAccount( - ctx context.Context, - accountId string, -) (*pgtype.UUID, error) { - accountUuid, err := neosyncdb.ToUuid(accountId) - if err != nil { - return nil, err - } - - if isWorkerApiKey(ctx) { - return &accountUuid, nil - } - - resp, err := s.useraccountService.IsUserInAccount(ctx, connect.NewRequest(&mgmtv1alpha1.IsUserInAccountRequest{AccountId: accountId})) - if err != nil { - return nil, err - } - if !resp.Msg.Ok { - return nil, nucleuserrors.NewForbidden("user in not in requested account") - } - - return &accountUuid, nil -} - -func isWorkerApiKey(ctx context.Context) bool { - data, err := auth_apikey.GetTokenDataFromCtx(ctx) - if err != nil { - return false - } - return data.ApiKeyType == apikey.WorkerApiKey -} - -func (s *Service) getUserUuid( - ctx context.Context, -) (*pgtype.UUID, error) { - user, err := s.useraccountService.GetUser(ctx, connect.NewRequest(&mgmtv1alpha1.GetUserRequest{})) - if err != nil { - return nil, err - } - userUuid, err := neosyncdb.ToUuid(user.Msg.UserId) - if err != nil { - return nil, err - } - return &userUuid, nil -} diff --git a/backend/internal/ee/rbac/actions.go b/backend/internal/ee/rbac/actions.go new file mode 100644 index 0000000000..0a92793b39 --- /dev/null +++ b/backend/internal/ee/rbac/actions.go @@ -0,0 +1,41 @@ +package rbac + +type AccountAction string + +const ( + AccountAction_Create AccountAction = "create" + AccountAction_Delete AccountAction = "delete" + AccountAction_View AccountAction = "view" + AccountAction_Edit AccountAction = "edit" +) + +func (a AccountAction) String() string { + return string(a) +} + +type ConnectionAction string + +const ( + ConnectionAction_Create ConnectionAction = "create" + ConnectionAction_Delete ConnectionAction = "delete" + ConnectionAction_View ConnectionAction = "view" + ConnectionAction_Edit ConnectionAction = "edit" +) + +func (c ConnectionAction) String() string { + return string(c) +} + +type JobAction string + +const ( + JobAction_Create JobAction = "create" + JobAction_Delete JobAction = "delete" + JobAction_Execute JobAction = "execute" + JobAction_View JobAction = "view" + JobAction_Edit JobAction = "edit" +) + +func (a JobAction) String() string { + return string(a) +} diff --git a/backend/internal/ee/rbac/allow_all_client.go b/backend/internal/ee/rbac/allow_all_client.go new file mode 100644 index 0000000000..9f7e9e4c24 --- /dev/null +++ b/backend/internal/ee/rbac/allow_all_client.go @@ -0,0 +1,57 @@ +package rbac + +import ( + "context" + "log/slog" + + mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" +) + +type AllowAllClient struct { +} + +var _ Interface = (*AllowAllClient)(nil) + +func (a *AllowAllClient) Job(ctx context.Context, user, account, job EntityString, action JobAction) (bool, error) { + return true, nil +} + +func (a *AllowAllClient) Connection(ctx context.Context, user, account, connection EntityString, action ConnectionAction) (bool, error) { + return true, nil +} + +func (a *AllowAllClient) Account(ctx context.Context, user, account EntityString, action AccountAction) (bool, error) { + return true, nil +} + +func (a *AllowAllClient) EnforceJob(ctx context.Context, user, account, job EntityString, action JobAction) error { + return nil +} + +func (a *AllowAllClient) EnforceConnection(ctx context.Context, user, account, connection EntityString, action ConnectionAction) error { + return nil +} + +func (a *AllowAllClient) EnforceAccount(ctx context.Context, user, account EntityString, action AccountAction) error { + return nil +} + +func (a *AllowAllClient) SetAccountRole(ctx context.Context, user, account EntityString, role mgmtv1alpha1.AccountRole) error { + return nil +} + +func (a *AllowAllClient) RemoveAccountRole(ctx context.Context, user, account EntityString, role mgmtv1alpha1.AccountRole) error { + return nil +} + +func (a *AllowAllClient) RemoveAccountUser(ctx context.Context, user, account EntityString) error { + return nil +} + +func (a *AllowAllClient) SetupNewAccount(ctx context.Context, accountId string, logger *slog.Logger) error { + return nil +} + +func NewAllowAllClient() *AllowAllClient { + return &AllowAllClient{} +} diff --git a/backend/internal/ee/rbac/client.go b/backend/internal/ee/rbac/client.go new file mode 100644 index 0000000000..5ada51418f --- /dev/null +++ b/backend/internal/ee/rbac/client.go @@ -0,0 +1,21 @@ +package rbac + +import "github.com/casbin/casbin/v2" + +type Rbac struct { + e casbin.IEnforcer +} + +// Combines RBAC interface that handles entity enforcement and role management +type Interface interface { + EntityEnforcer + RoleAdmin +} + +var _ Interface = (*Rbac)(nil) + +func New( + e casbin.IEnforcer, +) *Rbac { + return &Rbac{e: e} +} diff --git a/backend/internal/ee/rbac/db.go b/backend/internal/ee/rbac/db.go new file mode 100644 index 0000000000..7065fd2e7f --- /dev/null +++ b/backend/internal/ee/rbac/db.go @@ -0,0 +1,39 @@ +package rbac + +import ( + "context" + + db_queries "github.com/nucleuscloud/neosync/backend/gen/go/db" + "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" +) + +type RbacDb struct { + q db_queries.Querier + dbtx db_queries.DBTX +} + +var _ Db = (*RbacDb)(nil) + +func NewRbacDb(q db_queries.Querier, dbtx db_queries.DBTX) *RbacDb { + return &RbacDb{q: q, dbtx: dbtx} +} + +func (r *RbacDb) GetAccountIds(ctx context.Context) ([]string, error) { + resp, err := r.q.GetAccountIds(ctx, r.dbtx) + if err != nil { + return nil, err + } + return neosyncdb.UUIDStrings(resp), nil +} + +func (r *RbacDb) GetAccountUsers(ctx context.Context, accountId string) ([]string, error) { + accountUuid, err := neosyncdb.ToUuid(accountId) + if err != nil { + return nil, err + } + resp, err := r.q.GetAccountUsers(ctx, r.dbtx, accountUuid) + if err != nil { + return nil, err + } + return neosyncdb.UUIDStrings(resp), nil +} diff --git a/backend/internal/ee/rbac/enforcer/enforcer.go b/backend/internal/ee/rbac/enforcer/enforcer.go new file mode 100644 index 0000000000..5d4b2d9874 --- /dev/null +++ b/backend/internal/ee/rbac/enforcer/enforcer.go @@ -0,0 +1,53 @@ +package enforcer + +import ( + "context" + "database/sql" + "fmt" + + sqladapter "github.com/Blank-Xu/sql-adapter" + "github.com/casbin/casbin/v2" + "github.com/casbin/casbin/v2/model" + "github.com/casbin/casbin/v2/persist" +) + +// The default casbin enforcer with a SQL-enabled backend +func NewActiveEnforcer( + ctx context.Context, + db *sql.DB, + casbinTableName string, +) (casbin.IEnforcer, error) { + adapter, err := newSqlAdapter(ctx, db, casbinTableName) + if err != nil { + return nil, err + } + return newEnforcer(adapter) +} + +func newEnforcer( + adapter persist.Adapter, +) (casbin.IEnforcer, error) { + m, err := model.NewModelFromString(neosyncRbacModel) + if err != nil { + return nil, fmt.Errorf("unable to initialize casbin model from string: %w", err) + } + + enforcer, err := casbin.NewSyncedEnforcer(m, adapter) + if err != nil { + return nil, fmt.Errorf("unable to initialize casbin synced cached enforcer: %w", err) + } + enforcer.EnableAutoSave(true) // seems to do this automatically but it doesn't hurt + return enforcer, nil +} + +func newSqlAdapter( + ctx context.Context, + db *sql.DB, + tableName string, +) (persist.Adapter, error) { + adapter, err := sqladapter.NewAdapterWithContext(ctx, db, "postgres", tableName) + if err != nil { + return nil, fmt.Errorf("unable to create casbin sql adapter: %w", err) + } + return adapter, nil +} diff --git a/backend/internal/ee/rbac/enforcer/model.go b/backend/internal/ee/rbac/enforcer/model.go new file mode 100644 index 0000000000..09120bd9fd --- /dev/null +++ b/backend/internal/ee/rbac/enforcer/model.go @@ -0,0 +1,6 @@ +package enforcer + +import _ "embed" + +//go:embed rbac_model.conf +var neosyncRbacModel string diff --git a/backend/internal/ee/rbac/enforcer/rbac_model.conf b/backend/internal/ee/rbac/enforcer/rbac_model.conf new file mode 100644 index 0000000000..487455d245 --- /dev/null +++ b/backend/internal/ee/rbac/enforcer/rbac_model.conf @@ -0,0 +1,14 @@ +[request_definition] +r = sub, dom, obj, act + +[policy_definition] +p = sub, dom, obj, act + +[role_definition] +g = _, _, _ + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act) diff --git a/backend/internal/ee/rbac/entity.go b/backend/internal/ee/rbac/entity.go new file mode 100644 index 0000000000..78ba3a804d --- /dev/null +++ b/backend/internal/ee/rbac/entity.go @@ -0,0 +1,53 @@ +package rbac + +import ( + "fmt" + + "github.com/jackc/pgx/v5/pgtype" + "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" +) + +const ( + Wildcard = "*" +) + +var ( + JobWildcard = NewEntity("jobs", Wildcard) + ConnectionWildcard = NewEntity("connections", Wildcard) +) + +type Entity struct { + prefix string + value string +} + +type EntityString interface { + String() string +} + +func NewEntity(prefix, value string) *Entity { + return &Entity{prefix: prefix, value: value} +} + +func (e *Entity) String() string { + return fmt.Sprintf("%s/%s", e.prefix, e.value) +} + +func NewAccountIdEntity(value string) *Entity { + return NewEntity("accounts", value) +} + +func NewJobIdEntity(value string) *Entity { + return NewEntity("jobs", value) +} + +func NewUserIdEntity(value string) *Entity { + return NewEntity("users", value) +} +func NewPgUserIdEntity(value pgtype.UUID) *Entity { + return NewUserIdEntity(neosyncdb.UUIDString(value)) +} + +func NewConnectionIdEntity(value string) *Entity { + return NewEntity("connections", value) +} diff --git a/backend/internal/ee/rbac/policy.go b/backend/internal/ee/rbac/policy.go new file mode 100644 index 0000000000..a176f8a1ec --- /dev/null +++ b/backend/internal/ee/rbac/policy.go @@ -0,0 +1,372 @@ +package rbac + +import ( + "context" + "fmt" + "log/slog" + + "github.com/casbin/casbin/v2" + mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" + nucleuserrors "github.com/nucleuscloud/neosync/backend/internal/errors" + "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" +) + +// Interface used by rbac engine to make necessary calls to the database +type Db interface { + GetAccountIds(ctx context.Context) ([]string, error) + GetAccountUsers(ctx context.Context, accountId string) ([]string, error) +} + +// Interface that handles enforcing entity level policies +type EntityEnforcer interface { + Job(ctx context.Context, user EntityString, account EntityString, job EntityString, action JobAction) (bool, error) + EnforceJob(ctx context.Context, user EntityString, account EntityString, job EntityString, action JobAction) error + Connection(ctx context.Context, user EntityString, account EntityString, connection EntityString, action ConnectionAction) (bool, error) + EnforceConnection(ctx context.Context, user EntityString, account EntityString, connection EntityString, action ConnectionAction) error + Account(ctx context.Context, user EntityString, account EntityString, action AccountAction) (bool, error) + EnforceAccount(ctx context.Context, user EntityString, account EntityString, action AccountAction) error +} + +// Interface that handles setting and removing roles for users +type RoleAdmin interface { + SetAccountRole(ctx context.Context, user EntityString, account EntityString, role mgmtv1alpha1.AccountRole) error + RemoveAccountRole(ctx context.Context, user EntityString, account EntityString, role mgmtv1alpha1.AccountRole) error + RemoveAccountUser(ctx context.Context, user EntityString, account EntityString) error + SetupNewAccount(ctx context.Context, accountId string, logger *slog.Logger) error +} + +// Initialize default policies for existing accounts at startup +func (r *Rbac) InitPolicies( + ctx context.Context, + db Db, + logger *slog.Logger, +) error { + accountIds, err := db.GetAccountIds(ctx) + if err != nil { + return fmt.Errorf("unable to retrieve account ids during casbin policy init: %w", err) + } + err = setupAccountPolicies(r.e, accountIds, logger) + if err != nil { + return err + } + + err = setupUserAssignments(ctx, db, r.e, accountIds, logger) + if err != nil { + return err + } + + return nil +} + +func setupAccountPolicies(enforcer casbin.IEnforcer, accountIds []string, logger *slog.Logger) error { + logger.Debug(fmt.Sprintf("found %d account ids to associate with rbac policies", len(accountIds))) + + policyRules := [][]string{} + for _, accountId := range accountIds { + accountRules := getAccountPolicyRules(accountId) + policyRules = append( + policyRules, + accountRules..., + ) + } + + if len(policyRules) > 0 { + logger.Debug(fmt.Sprintf("adding %d policy rules to rbac engine", len(policyRules))) + for _, policy := range policyRules { + err := setPolicy(enforcer, policy) + if err != nil { + return err + } + } + } + return nil +} + +// For the given accounts, assign users to the account admin role if the account does not currently have any role assignments +func setupUserAssignments(ctx context.Context, db Db, enforcer casbin.IEnforcer, accountIds []string, logger *slog.Logger) error { + policiesByDomain, err := getGroupingPoliciesByDomain(enforcer) + if err != nil { + return err + } + + groupedRules := [][]string{} + for _, accountId := range accountIds { + _, ok := policiesByDomain[NewAccountIdEntity(accountId).String()] + if ok { + continue + } + + // get users in account + // assign them all account admin role for the account + users, err := db.GetAccountUsers(ctx, accountId) + if err != nil && !neosyncdb.IsNoRows(err) { + return err + } else if err != nil && neosyncdb.IsNoRows(err) { + logger.Debug(fmt.Sprintf("no users found for account %s, skipping", accountId)) + continue + } + logger.Debug(fmt.Sprintf("found %d users for account %s, assigning all account admin role", len(users), accountId)) + for _, user := range users { + groupedRules = append(groupedRules, []string{ + NewUserIdEntity(user).String(), + Role_AccountAdmin.String(), + NewAccountIdEntity(accountId).String(), + }) + } + } + if len(groupedRules) > 0 { + logger.Debug(fmt.Sprintf("adding %d grouping policies to rbac engine", len(groupedRules))) + _, err := enforcer.AddNamedGroupingPolicies("g", groupedRules) + if err != nil { + return err + } + } + return nil +} + +func getGroupingPoliciesByDomain(enforcer casbin.IEnforcer) (map[string][][]string, error) { + // Get all grouping policies + allPolicies, err := enforcer.GetNamedGroupingPolicy("g") + if err != nil { + return nil, fmt.Errorf("unable to get grouping policies: %w", err) + } + + // Create a map to store policies by domain + policiesByDomain := make(map[string][][]string) + + // Group policies by domain (domain is the third element, index 2) + for _, policy := range allPolicies { + domain := policy[2] + policiesByDomain[domain] = append(policiesByDomain[domain], policy) + } + + return policiesByDomain, nil +} + +func (r *Rbac) SetupNewAccount( + ctx context.Context, + accountId string, + logger *slog.Logger, +) error { + accountRules := getAccountPolicyRules(accountId) + if len(accountRules) > 0 { + logger.Debug(fmt.Sprintf("adding %d policy rules to rbac engine for account %s", len(accountRules), accountId)) + for _, policy := range accountRules { + err := setPolicy(r.e, policy) + if err != nil { + return err + } + } + } + return nil +} + +func getAccountPolicyRules(accountId string) [][]string { + accountKey := NewAccountIdEntity(accountId).String() + return [][]string{ + { + Role_AccountAdmin.String(), + accountKey, + Wildcard, // any resource in the account + Wildcard, // all actions in the account + }, + { + Role_JobDeveloper.String(), + accountKey, + JobWildcard.String(), // all jobs in the account + Wildcard, // all job actions + }, + { + Role_JobDeveloper.String(), + accountKey, + ConnectionWildcard.String(), // all connections in the account + Wildcard, // all connection actions + }, + { + Role_JobDeveloper.String(), + accountKey, + accountKey, + AccountAction_View.String(), // job developer can view the account + }, + { + Role_JobViewer.String(), + accountKey, + JobWildcard.String(), + JobAction_View.String(), // job viewer can view all jobs in the account + }, + { + Role_JobViewer.String(), + accountKey, + JobWildcard.String(), + JobAction_Execute.String(), // job viewer can execute all jobs in the account + }, + { + Role_JobViewer.String(), + accountKey, + ConnectionWildcard.String(), + ConnectionAction_View.String(), // job viewer can view all connections in the account + }, + { + Role_JobViewer.String(), + accountKey, + accountKey, + AccountAction_View.String(), // job viewer can view the account + }, + } +} + +// For the given user and account, removes all existing roles and replaces them with the new role +func (r *Rbac) SetAccountRole( + ctx context.Context, + user EntityString, + account EntityString, + role mgmtv1alpha1.AccountRole, +) error { + roleName, err := toRoleName(role) + if err != nil { + return err + } + + _, err = r.e.DeleteRolesForUserInDomain(user.String(), account.String()) + if err != nil { + return fmt.Errorf("unable to delete roles for user in domain: %w", err) + } + + _, err = r.e.AddRoleForUserInDomain(user.String(), roleName, account.String()) + return err +} + +// For the given user and account, removes the given role +func (r *Rbac) RemoveAccountRole( + ctx context.Context, + user EntityString, + account EntityString, + role mgmtv1alpha1.AccountRole, +) error { + roleName, err := toRoleName(role) + if err != nil { + return err + } + _, err = r.e.DeleteRoleForUserInDomain(user.String(), roleName, account.String()) + return err +} + +// For the given user and account, removes all roles for the user +func (r *Rbac) RemoveAccountUser( + ctx context.Context, + user EntityString, + account EntityString, +) error { + _, err := r.e.DeleteRolesForUserInDomain(user.String(), account.String()) + return err +} + +func (r *Rbac) Job( + ctx context.Context, + user EntityString, + account EntityString, + job EntityString, + action JobAction, +) (bool, error) { + return r.e.Enforce(user.String(), account.String(), job.String(), action.String()) +} + +func (r *Rbac) EnforceJob( + ctx context.Context, + user EntityString, + account EntityString, + job EntityString, + action JobAction, +) error { + ok, err := r.Job(ctx, user, account, job, action) + if err != nil { + return err + } + if !ok { + return nucleuserrors.NewForbidden(fmt.Sprintf("user does not have permission to %s job", action)) + } + return nil +} + +func (r *Rbac) Connection( + ctx context.Context, + user EntityString, + account EntityString, + connection EntityString, + action ConnectionAction, +) (bool, error) { + return r.e.Enforce(user.String(), account.String(), connection.String(), action.String()) +} + +func (r *Rbac) EnforceConnection( + ctx context.Context, + user EntityString, + account EntityString, + connection EntityString, + action ConnectionAction, +) error { + ok, err := r.Connection(ctx, user, account, connection, action) + if err != nil { + return err + } + if !ok { + return nucleuserrors.NewForbidden(fmt.Sprintf("user does not have permission to %s connection", action)) + } + return nil +} + +func (r *Rbac) Account( + ctx context.Context, + user EntityString, + account EntityString, + action AccountAction, +) (bool, error) { + return r.e.Enforce(user.String(), account.String(), account.String(), action.String()) +} + +func (r *Rbac) EnforceAccount( + ctx context.Context, + user EntityString, + account EntityString, + action AccountAction, +) error { + ok, err := r.Account(ctx, user, account, action) + if err != nil { + return err + } + if !ok { + return nucleuserrors.NewForbidden(fmt.Sprintf("user does not have permission to %s account", action)) + } + return nil +} + +func toRoleName(role mgmtv1alpha1.AccountRole) (string, error) { + switch role { + case mgmtv1alpha1.AccountRole_ACCOUNT_ROLE_ADMIN: + return Role_AccountAdmin.String(), nil + case mgmtv1alpha1.AccountRole_ACCOUNT_ROLE_JOB_DEVELOPER: + return Role_JobDeveloper.String(), nil + case mgmtv1alpha1.AccountRole_ACCOUNT_ROLE_JOB_VIEWER: + return Role_JobViewer.String(), nil + default: + return "", fmt.Errorf("account role provided has not be mapped to a casbin role name: %d", role) + } +} + +func setPolicy(e casbin.IEnforcer, policy []string) error { + // AddPoliciesEx is what should be uesd here but is resulting in duplicates (and errors with unique constraint) + // AddPolicies handles the unique constraint but fails if even one policy already exists.. + + // This logic here seems to handle what I want it to do instead strangely... + ok, err := e.HasPolicy(policy) + if err != nil { + return fmt.Errorf("unable to check if policy exists: %w", err) + } + if !ok { + _, err = e.AddPolicy(policy) // always resolves to true even if it was not added, may be adapter dependent + if err != nil { + return fmt.Errorf("unable to add policy: %w", err) + } + } + return nil +} diff --git a/backend/internal/ee/rbac/rbac_integration_test.go b/backend/internal/ee/rbac/rbac_integration_test.go new file mode 100644 index 0000000000..16b918de7e --- /dev/null +++ b/backend/internal/ee/rbac/rbac_integration_test.go @@ -0,0 +1,232 @@ +package rbac + +import ( + "context" + "testing" + + "github.com/google/uuid" + "github.com/jackc/pgx/v5/stdlib" + mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" + "github.com/nucleuscloud/neosync/backend/internal/ee/rbac/enforcer" + neomigrate "github.com/nucleuscloud/neosync/internal/migrate" + "github.com/nucleuscloud/neosync/internal/testutil" + testcontainers_postgres "github.com/nucleuscloud/neosync/internal/testutil/testcontainers/postgres" + "github.com/stretchr/testify/require" +) + +func TestRbac(t *testing.T) { + t.Parallel() + ok := testutil.ShouldRunIntegrationTest() + if !ok { + return + } + + ctx := context.Background() + + container, err := testcontainers_postgres.NewPostgresTestContainer(ctx) + require.NoError(t, err) + + err = neomigrate.Up(ctx, container.URL, "../../../sql/postgresql/schema", testutil.GetTestLogger(t)) + require.NoError(t, err) + + rbacenforcer, err := enforcer.NewActiveEnforcer(ctx, stdlib.OpenDBFromPool(container.DB), "neosync_api.casbin_rule") + require.NoError(t, err) + + rbacenforcer.EnableAutoSave(true) + err = rbacenforcer.LoadPolicy() + require.NoError(t, err) + + rbacclient := New(rbacenforcer) + + t.Run("account_admin", func(t *testing.T) { + t.Parallel() + + accountId := uuid.NewString() + userId := uuid.NewString() + err := rbacclient.SetupNewAccount(ctx, accountId, testutil.GetTestLogger(t)) + require.NoError(t, err) + + // Set user as account admin + err = rbacclient.SetAccountRole(ctx, NewUserIdEntity(userId), NewAccountIdEntity(accountId), mgmtv1alpha1.AccountRole_ACCOUNT_ROLE_ADMIN) + require.NoError(t, err) + + // Test Account permissions + err = rbacclient.EnforceAccount(ctx, NewUserIdEntity(userId), NewAccountIdEntity(accountId), AccountAction_View) + require.NoError(t, err) + + err = rbacclient.EnforceAccount(ctx, NewUserIdEntity(userId), NewAccountIdEntity(accountId), AccountAction_Edit) + require.NoError(t, err) + + // Test Job permissions + jobId := uuid.NewString() + err = rbacclient.EnforceJob(ctx, NewUserIdEntity(userId), NewAccountIdEntity(accountId), NewJobIdEntity(jobId), JobAction_Create) + require.NoError(t, err) + + // Test Connection permissions + connectionId := uuid.NewString() + err = rbacclient.EnforceConnection(ctx, NewUserIdEntity(userId), NewAccountIdEntity(accountId), NewConnectionIdEntity(connectionId), ConnectionAction_Create) + require.NoError(t, err) + }) + + t.Run("job_developer", func(t *testing.T) { + t.Parallel() + + accountId := uuid.NewString() + userId := uuid.NewString() + err := rbacclient.SetupNewAccount(ctx, accountId, testutil.GetTestLogger(t)) + require.NoError(t, err) + + // Set user as job developer + err = rbacclient.SetAccountRole(ctx, NewUserIdEntity(userId), NewAccountIdEntity(accountId), mgmtv1alpha1.AccountRole_ACCOUNT_ROLE_JOB_DEVELOPER) + require.NoError(t, err) + + // Test Account permissions (should only have view) + err = rbacclient.EnforceAccount(ctx, NewUserIdEntity(userId), NewAccountIdEntity(accountId), AccountAction_View) + require.NoError(t, err) + + err = rbacclient.EnforceAccount(ctx, NewUserIdEntity(userId), NewAccountIdEntity(accountId), AccountAction_Edit) + require.Error(t, err) + require.ErrorContains(t, err, "user does not have permission to edit account") + + // Test Job permissions (should have all) + jobId := uuid.NewString() + err = rbacclient.EnforceJob(ctx, NewUserIdEntity(userId), NewAccountIdEntity(accountId), NewJobIdEntity(jobId), JobAction_Create) + require.NoError(t, err) + }) + + t.Run("job_viewer", func(t *testing.T) { + t.Parallel() + + accountId := uuid.NewString() + userId := uuid.NewString() + err := rbacclient.SetupNewAccount(ctx, accountId, testutil.GetTestLogger(t)) + require.NoError(t, err) + + // Set user as job viewer + err = rbacclient.SetAccountRole(ctx, NewUserIdEntity(userId), NewAccountIdEntity(accountId), mgmtv1alpha1.AccountRole_ACCOUNT_ROLE_JOB_VIEWER) + require.NoError(t, err) + + // Test Account permissions (should only have view) + err = rbacclient.EnforceAccount(ctx, NewUserIdEntity(userId), NewAccountIdEntity(accountId), AccountAction_View) + require.NoError(t, err) + + // Test Job permissions (should only have view and execute) + jobId := uuid.NewString() + err = rbacclient.EnforceJob(ctx, NewUserIdEntity(userId), NewAccountIdEntity(accountId), NewJobIdEntity(jobId), JobAction_View) + require.NoError(t, err) + + err = rbacclient.EnforceJob(ctx, NewUserIdEntity(userId), NewAccountIdEntity(accountId), NewJobIdEntity(jobId), JobAction_Execute) + require.NoError(t, err) + + err = rbacclient.EnforceJob(ctx, NewUserIdEntity(userId), NewAccountIdEntity(accountId), NewJobIdEntity(jobId), JobAction_Create) + require.Error(t, err) + require.ErrorContains(t, err, "user does not have permission to create job") + }) + + t.Run("role_changes", func(t *testing.T) { + t.Parallel() + + accountId := uuid.NewString() + userId := uuid.NewString() + err := rbacclient.SetupNewAccount(ctx, accountId, testutil.GetTestLogger(t)) + require.NoError(t, err) + + // Set and remove roles + err = rbacclient.SetAccountRole(ctx, NewUserIdEntity(userId), NewAccountIdEntity(accountId), mgmtv1alpha1.AccountRole_ACCOUNT_ROLE_ADMIN) + require.NoError(t, err) + + err = rbacclient.RemoveAccountRole(ctx, NewUserIdEntity(userId), NewAccountIdEntity(accountId), mgmtv1alpha1.AccountRole_ACCOUNT_ROLE_ADMIN) + require.NoError(t, err) + + // Verify permissions are removed + err = rbacclient.EnforceAccount(ctx, NewUserIdEntity(userId), NewAccountIdEntity(accountId), AccountAction_View) + require.Error(t, err) + require.ErrorContains(t, err, "user does not have permission to view account") + }) + + t.Run("cross_account_access", func(t *testing.T) { + t.Parallel() + + // Setup first account and user + account1Id := uuid.NewString() + user1Id := uuid.NewString() + err := rbacclient.SetupNewAccount(ctx, account1Id, testutil.GetTestLogger(t)) + require.NoError(t, err) + err = rbacclient.SetAccountRole(ctx, NewUserIdEntity(user1Id), NewAccountIdEntity(account1Id), mgmtv1alpha1.AccountRole_ACCOUNT_ROLE_ADMIN) + require.NoError(t, err) + + // Setup second account and user + account2Id := uuid.NewString() + user2Id := uuid.NewString() + err = rbacclient.SetupNewAccount(ctx, account2Id, testutil.GetTestLogger(t)) + require.NoError(t, err) + err = rbacclient.SetAccountRole(ctx, NewUserIdEntity(user2Id), NewAccountIdEntity(account2Id), mgmtv1alpha1.AccountRole_ACCOUNT_ROLE_ADMIN) + require.NoError(t, err) + + // Verify user1 cannot access account2's resources + err = rbacclient.EnforceAccount(ctx, NewUserIdEntity(user1Id), NewAccountIdEntity(account2Id), AccountAction_View) + require.Error(t, err) + require.ErrorContains(t, err, "user does not have permission to view account") + + // Verify user1 cannot access jobs in account2 + jobId := uuid.NewString() + err = rbacclient.EnforceJob(ctx, NewUserIdEntity(user1Id), NewAccountIdEntity(account2Id), NewJobIdEntity(jobId), JobAction_View) + require.Error(t, err) + require.ErrorContains(t, err, "user does not have permission to view job") + + // Verify user1 cannot access connections in account2 + connectionId := uuid.NewString() + err = rbacclient.EnforceConnection(ctx, NewUserIdEntity(user1Id), NewAccountIdEntity(account2Id), NewConnectionIdEntity(connectionId), ConnectionAction_View) + require.Error(t, err) + require.ErrorContains(t, err, "user does not have permission to view connection") + }) + + t.Run("mixed_roles_same_account", func(t *testing.T) { + t.Parallel() + + accountId := uuid.NewString() + adminUserId := uuid.NewString() + viewerUserId := uuid.NewString() + err := rbacclient.SetupNewAccount(ctx, accountId, testutil.GetTestLogger(t)) + require.NoError(t, err) + + // Set up admin user + err = rbacclient.SetAccountRole(ctx, NewUserIdEntity(adminUserId), NewAccountIdEntity(accountId), mgmtv1alpha1.AccountRole_ACCOUNT_ROLE_ADMIN) + require.NoError(t, err) + + // Set up job viewer user + err = rbacclient.SetAccountRole(ctx, NewUserIdEntity(viewerUserId), NewAccountIdEntity(accountId), mgmtv1alpha1.AccountRole_ACCOUNT_ROLE_JOB_VIEWER) + require.NoError(t, err) + + jobId := uuid.NewString() + connectionId := uuid.NewString() + + // Verify admin has full permissions + err = rbacclient.EnforceJob(ctx, NewUserIdEntity(adminUserId), NewAccountIdEntity(accountId), NewJobIdEntity(jobId), JobAction_Create) + require.NoError(t, err) + err = rbacclient.EnforceJob(ctx, NewUserIdEntity(adminUserId), NewAccountIdEntity(accountId), NewJobIdEntity(jobId), JobAction_Delete) + require.NoError(t, err) + err = rbacclient.EnforceConnection(ctx, NewUserIdEntity(adminUserId), NewAccountIdEntity(accountId), NewConnectionIdEntity(connectionId), ConnectionAction_Create) + require.NoError(t, err) + + // Verify job viewer can only view and execute jobs + err = rbacclient.EnforceJob(ctx, NewUserIdEntity(viewerUserId), NewAccountIdEntity(accountId), NewJobIdEntity(jobId), JobAction_View) + require.NoError(t, err) + err = rbacclient.EnforceJob(ctx, NewUserIdEntity(viewerUserId), NewAccountIdEntity(accountId), NewJobIdEntity(jobId), JobAction_Execute) + require.NoError(t, err) + + // Verify job viewer cannot perform other actions + err = rbacclient.EnforceJob(ctx, NewUserIdEntity(viewerUserId), NewAccountIdEntity(accountId), NewJobIdEntity(jobId), JobAction_Create) + require.Error(t, err) + require.ErrorContains(t, err, "user does not have permission to create job") + + err = rbacclient.EnforceJob(ctx, NewUserIdEntity(viewerUserId), NewAccountIdEntity(accountId), NewJobIdEntity(jobId), JobAction_Delete) + require.Error(t, err) + require.ErrorContains(t, err, "user does not have permission to delete job") + + err = rbacclient.EnforceJob(ctx, NewUserIdEntity(viewerUserId), NewAccountIdEntity(accountId), NewJobIdEntity(jobId), JobAction_Edit) + require.Error(t, err) + require.ErrorContains(t, err, "user does not have permission to edit job") + }) + +} diff --git a/backend/internal/ee/rbac/roles.go b/backend/internal/ee/rbac/roles.go new file mode 100644 index 0000000000..cd23445bc0 --- /dev/null +++ b/backend/internal/ee/rbac/roles.go @@ -0,0 +1,13 @@ +package rbac + +type Role string + +const ( + Role_AccountAdmin Role = "account_admin" + Role_JobDeveloper Role = "job_developer" + Role_JobViewer Role = "job_viewer" +) + +func (r Role) String() string { + return string(r) +} diff --git a/backend/internal/neosyncdb/db.go b/backend/internal/neosyncdb/db.go index 3a2a13ce63..01023cc74c 100644 --- a/backend/internal/neosyncdb/db.go +++ b/backend/internal/neosyncdb/db.go @@ -58,7 +58,7 @@ func New(db DBTX, q db_queries.Querier) *NeosyncDb { } } -func NewFromConfig(config *ConnectConfig) (*NeosyncDb, error) { +func NewPool(config *ConnectConfig) (*pgxpool.Pool, error) { pgxconfig, err := pgxpool.ParseConfig(GetDbUrl(config)) if err != nil { return nil, fmt.Errorf("unable to parse pgxpool config: %w", err) @@ -72,6 +72,14 @@ func NewFromConfig(config *ConnectConfig) (*NeosyncDb, error) { if err != nil { return nil, fmt.Errorf("uanble to initialize pgx pool from configuration: %w", err) } + return pool, nil +} + +func NewFromConfig(config *ConnectConfig) (*NeosyncDb, error) { + pool, err := NewPool(config) + if err != nil { + return nil, err + } return New(pool, nil), nil } diff --git a/backend/internal/userdata/client.go b/backend/internal/userdata/client.go new file mode 100644 index 0000000000..0e12867368 --- /dev/null +++ b/backend/internal/userdata/client.go @@ -0,0 +1,68 @@ +package userdata + +import ( + "context" + "fmt" + + "connectrpc.com/connect" + mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" + auth_apikey "github.com/nucleuscloud/neosync/backend/internal/auth/apikey" + "github.com/nucleuscloud/neosync/backend/internal/ee/rbac" + "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" +) + +type UserServiceClient interface { + GetUser(ctx context.Context, req *connect.Request[mgmtv1alpha1.GetUserRequest]) (*connect.Response[mgmtv1alpha1.GetUserResponse], error) + IsUserInAccount(ctx context.Context, req *connect.Request[mgmtv1alpha1.IsUserInAccountRequest]) (*connect.Response[mgmtv1alpha1.IsUserInAccountResponse], error) +} + +type Client struct { + userServiceClient UserServiceClient + enforcer rbac.EntityEnforcer +} + +type Interface interface { + GetUser(ctx context.Context) (*User, error) +} + +type GetUserResponse struct { + User *User +} + +func NewClient( + userServiceClient UserServiceClient, + enforcer rbac.EntityEnforcer, +) *Client { + return &Client{ + userServiceClient: userServiceClient, + enforcer: enforcer, + } +} + +func (c *Client) GetUser(ctx context.Context) (*User, error) { + resp, err := c.userServiceClient.GetUser(ctx, connect.NewRequest(&mgmtv1alpha1.GetUserRequest{})) + if err != nil { + return nil, fmt.Errorf("unable to get user: %w", err) + } + pguuid, err := neosyncdb.ToUuid(resp.Msg.GetUserId()) + if err != nil { + return nil, fmt.Errorf("unable to parse user id: %w", err) + } + + apiKeyData, _ := auth_apikey.GetTokenDataFromCtx(ctx) + + user := &User{ + id: pguuid, + apiKeyData: apiKeyData, + userAccountServiceClient: c.userServiceClient, + } + user.EntityEnforcer = &UserEntityEnforcer{ + enforcer: c.enforcer, + user: rbac.NewUserIdEntity(resp.Msg.GetUserId()), + enforceAccountAccess: func(ctx context.Context, accountId string) error { + return EnforceAccountAccess(ctx, user, accountId) + }, + } + + return user, nil +} diff --git a/backend/internal/userdata/entity.go b/backend/internal/userdata/entity.go new file mode 100644 index 0000000000..461ff7716e --- /dev/null +++ b/backend/internal/userdata/entity.go @@ -0,0 +1,71 @@ +package userdata + +import ( + "github.com/jackc/pgx/v5/pgtype" + "github.com/nucleuscloud/neosync/backend/internal/ee/rbac" + "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" +) + +// Domain entity interface that mimics the domain model of the mgmt service +type DomainEntity interface { + Identifier + GetAccountId() string +} + +type DomainEntityImpl struct { + id string + accountId string + isWild bool +} + +type Identifier interface { + GetId() string +} + +func (j *DomainEntityImpl) GetId() string { + return j.id +} +func (j *DomainEntityImpl) GetAccountId() string { + return j.accountId +} + +// Used for things like mgmtv1alpha1.Job, mgmtv1alpha1.Connection, etc +func NewDomainEntity(accountId, id string) DomainEntity { + return &DomainEntityImpl{ + id: id, + accountId: accountId, + } +} + +// Used for things like mgmtv1alpha1.Job, mgmtv1alpha1.Connection, etc +// But for checking wildcard or account-level access +func NewWildcardDomainEntity(accountId string) DomainEntity { + return &DomainEntityImpl{ + id: rbac.Wildcard, + accountId: accountId, + isWild: true, + } +} + +// Helper function that can be used when dealing with the DB entities instead of the domain entities +func NewDbDomainEntity(accountId, id pgtype.UUID) DomainEntity { + return &DomainEntityImpl{ + id: neosyncdb.UUIDString(id), + accountId: neosyncdb.UUIDString(accountId), + } +} + +type IdentifierImpl struct { + id string +} + +// Helper function that creates just an identitier. Generally used when working with the account object +func NewIdentifier(id string) Identifier { + return &IdentifierImpl{ + id: id, + } +} + +func (i *IdentifierImpl) GetId() string { + return i.id +} diff --git a/backend/internal/userdata/entity_enforcer.go b/backend/internal/userdata/entity_enforcer.go new file mode 100644 index 0000000000..827f0d2f6e --- /dev/null +++ b/backend/internal/userdata/entity_enforcer.go @@ -0,0 +1,46 @@ +package userdata + +import ( + "context" + + "github.com/nucleuscloud/neosync/backend/internal/ee/rbac" +) + +type UserEntityEnforcer struct { + enforcer rbac.EntityEnforcer + user rbac.EntityString + enforceAccountAccess func(ctx context.Context, accountId string) error +} + +var _ EntityEnforcer = (*UserEntityEnforcer)(nil) + +// Higher level entity enforcer that slightly abstracts away the lower level rbac interface +// The intention here is to be able to use objects that are closer to the mgmt domain model +// rather than the lower level rbac model +// see the entity.go file for functions that help with this +type EntityEnforcer interface { + EnforceJob(ctx context.Context, job DomainEntity, action rbac.JobAction) error + EnforceConnection(ctx context.Context, connection DomainEntity, action rbac.ConnectionAction) error + EnforceAccount(ctx context.Context, account Identifier, action rbac.AccountAction) error +} + +func (u *UserEntityEnforcer) EnforceJob(ctx context.Context, job DomainEntity, action rbac.JobAction) error { + if err := u.enforceAccountAccess(ctx, job.GetAccountId()); err != nil { + return err + } + return u.enforcer.EnforceJob(ctx, u.user, rbac.NewAccountIdEntity(job.GetAccountId()), rbac.NewJobIdEntity(job.GetId()), action) +} + +func (u *UserEntityEnforcer) EnforceConnection(ctx context.Context, connection DomainEntity, action rbac.ConnectionAction) error { + if err := u.enforceAccountAccess(ctx, connection.GetAccountId()); err != nil { + return err + } + return u.enforcer.EnforceConnection(ctx, u.user, rbac.NewAccountIdEntity(connection.GetAccountId()), rbac.NewConnectionIdEntity(connection.GetId()), action) +} + +func (u *UserEntityEnforcer) EnforceAccount(ctx context.Context, account Identifier, action rbac.AccountAction) error { + if err := u.enforceAccountAccess(ctx, account.GetId()); err != nil { + return err + } + return u.enforcer.EnforceAccount(ctx, u.user, rbac.NewAccountIdEntity(account.GetId()), action) +} diff --git a/backend/internal/userdata/mock_EntityEnforcer.go b/backend/internal/userdata/mock_EntityEnforcer.go new file mode 100644 index 0000000000..0bc5b90143 --- /dev/null +++ b/backend/internal/userdata/mock_EntityEnforcer.go @@ -0,0 +1,181 @@ +// Code generated by mockery. DO NOT EDIT. + +package userdata + +import ( + context "context" + + rbac "github.com/nucleuscloud/neosync/backend/internal/ee/rbac" + mock "github.com/stretchr/testify/mock" +) + +// MockEntityEnforcer is an autogenerated mock type for the EntityEnforcer type +type MockEntityEnforcer struct { + mock.Mock +} + +type MockEntityEnforcer_Expecter struct { + mock *mock.Mock +} + +func (_m *MockEntityEnforcer) EXPECT() *MockEntityEnforcer_Expecter { + return &MockEntityEnforcer_Expecter{mock: &_m.Mock} +} + +// EnforceAccount provides a mock function with given fields: ctx, account, action +func (_m *MockEntityEnforcer) EnforceAccount(ctx context.Context, account Identifier, action rbac.AccountAction) error { + ret := _m.Called(ctx, account, action) + + if len(ret) == 0 { + panic("no return value specified for EnforceAccount") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, Identifier, rbac.AccountAction) error); ok { + r0 = rf(ctx, account, action) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockEntityEnforcer_EnforceAccount_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EnforceAccount' +type MockEntityEnforcer_EnforceAccount_Call struct { + *mock.Call +} + +// EnforceAccount is a helper method to define mock.On call +// - ctx context.Context +// - account Identifier +// - action rbac.AccountAction +func (_e *MockEntityEnforcer_Expecter) EnforceAccount(ctx interface{}, account interface{}, action interface{}) *MockEntityEnforcer_EnforceAccount_Call { + return &MockEntityEnforcer_EnforceAccount_Call{Call: _e.mock.On("EnforceAccount", ctx, account, action)} +} + +func (_c *MockEntityEnforcer_EnforceAccount_Call) Run(run func(ctx context.Context, account Identifier, action rbac.AccountAction)) *MockEntityEnforcer_EnforceAccount_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(Identifier), args[2].(rbac.AccountAction)) + }) + return _c +} + +func (_c *MockEntityEnforcer_EnforceAccount_Call) Return(_a0 error) *MockEntityEnforcer_EnforceAccount_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockEntityEnforcer_EnforceAccount_Call) RunAndReturn(run func(context.Context, Identifier, rbac.AccountAction) error) *MockEntityEnforcer_EnforceAccount_Call { + _c.Call.Return(run) + return _c +} + +// EnforceConnection provides a mock function with given fields: ctx, connection, action +func (_m *MockEntityEnforcer) EnforceConnection(ctx context.Context, connection DomainEntity, action rbac.ConnectionAction) error { + ret := _m.Called(ctx, connection, action) + + if len(ret) == 0 { + panic("no return value specified for EnforceConnection") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, DomainEntity, rbac.ConnectionAction) error); ok { + r0 = rf(ctx, connection, action) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockEntityEnforcer_EnforceConnection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EnforceConnection' +type MockEntityEnforcer_EnforceConnection_Call struct { + *mock.Call +} + +// EnforceConnection is a helper method to define mock.On call +// - ctx context.Context +// - connection DomainEntity +// - action rbac.ConnectionAction +func (_e *MockEntityEnforcer_Expecter) EnforceConnection(ctx interface{}, connection interface{}, action interface{}) *MockEntityEnforcer_EnforceConnection_Call { + return &MockEntityEnforcer_EnforceConnection_Call{Call: _e.mock.On("EnforceConnection", ctx, connection, action)} +} + +func (_c *MockEntityEnforcer_EnforceConnection_Call) Run(run func(ctx context.Context, connection DomainEntity, action rbac.ConnectionAction)) *MockEntityEnforcer_EnforceConnection_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(DomainEntity), args[2].(rbac.ConnectionAction)) + }) + return _c +} + +func (_c *MockEntityEnforcer_EnforceConnection_Call) Return(_a0 error) *MockEntityEnforcer_EnforceConnection_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockEntityEnforcer_EnforceConnection_Call) RunAndReturn(run func(context.Context, DomainEntity, rbac.ConnectionAction) error) *MockEntityEnforcer_EnforceConnection_Call { + _c.Call.Return(run) + return _c +} + +// EnforceJob provides a mock function with given fields: ctx, job, action +func (_m *MockEntityEnforcer) EnforceJob(ctx context.Context, job DomainEntity, action rbac.JobAction) error { + ret := _m.Called(ctx, job, action) + + if len(ret) == 0 { + panic("no return value specified for EnforceJob") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, DomainEntity, rbac.JobAction) error); ok { + r0 = rf(ctx, job, action) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockEntityEnforcer_EnforceJob_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EnforceJob' +type MockEntityEnforcer_EnforceJob_Call struct { + *mock.Call +} + +// EnforceJob is a helper method to define mock.On call +// - ctx context.Context +// - job DomainEntity +// - action rbac.JobAction +func (_e *MockEntityEnforcer_Expecter) EnforceJob(ctx interface{}, job interface{}, action interface{}) *MockEntityEnforcer_EnforceJob_Call { + return &MockEntityEnforcer_EnforceJob_Call{Call: _e.mock.On("EnforceJob", ctx, job, action)} +} + +func (_c *MockEntityEnforcer_EnforceJob_Call) Run(run func(ctx context.Context, job DomainEntity, action rbac.JobAction)) *MockEntityEnforcer_EnforceJob_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(DomainEntity), args[2].(rbac.JobAction)) + }) + return _c +} + +func (_c *MockEntityEnforcer_EnforceJob_Call) Return(_a0 error) *MockEntityEnforcer_EnforceJob_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockEntityEnforcer_EnforceJob_Call) RunAndReturn(run func(context.Context, DomainEntity, rbac.JobAction) error) *MockEntityEnforcer_EnforceJob_Call { + _c.Call.Return(run) + return _c +} + +// NewMockEntityEnforcer creates a new instance of MockEntityEnforcer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockEntityEnforcer(t interface { + mock.TestingT + Cleanup(func()) +}) *MockEntityEnforcer { + mock := &MockEntityEnforcer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/backend/internal/userdata/mock_Interface.go b/backend/internal/userdata/mock_Interface.go new file mode 100644 index 0000000000..85af7fb3c0 --- /dev/null +++ b/backend/internal/userdata/mock_Interface.go @@ -0,0 +1,94 @@ +// Code generated by mockery. DO NOT EDIT. + +package userdata + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// MockInterface is an autogenerated mock type for the Interface type +type MockInterface struct { + mock.Mock +} + +type MockInterface_Expecter struct { + mock *mock.Mock +} + +func (_m *MockInterface) EXPECT() *MockInterface_Expecter { + return &MockInterface_Expecter{mock: &_m.Mock} +} + +// GetUser provides a mock function with given fields: ctx +func (_m *MockInterface) GetUser(ctx context.Context) (*User, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetUser") + } + + var r0 *User + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*User, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *User); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*User) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockInterface_GetUser_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetUser' +type MockInterface_GetUser_Call struct { + *mock.Call +} + +// GetUser is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockInterface_Expecter) GetUser(ctx interface{}) *MockInterface_GetUser_Call { + return &MockInterface_GetUser_Call{Call: _e.mock.On("GetUser", ctx)} +} + +func (_c *MockInterface_GetUser_Call) Run(run func(ctx context.Context)) *MockInterface_GetUser_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockInterface_GetUser_Call) Return(_a0 *User, _a1 error) *MockInterface_GetUser_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockInterface_GetUser_Call) RunAndReturn(run func(context.Context) (*User, error)) *MockInterface_GetUser_Call { + _c.Call.Return(run) + return _c +} + +// NewMockInterface creates a new instance of MockInterface. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockInterface(t interface { + mock.TestingT + Cleanup(func()) +}) *MockInterface { + mock := &MockInterface{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/backend/internal/userdata/user.go b/backend/internal/userdata/user.go new file mode 100644 index 0000000000..70fc1d157e --- /dev/null +++ b/backend/internal/userdata/user.go @@ -0,0 +1,66 @@ +package userdata + +import ( + "context" + "fmt" + + "connectrpc.com/connect" + "github.com/jackc/pgx/v5/pgtype" + mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" + "github.com/nucleuscloud/neosync/backend/internal/apikey" + auth_apikey "github.com/nucleuscloud/neosync/backend/internal/auth/apikey" + nucleuserrors "github.com/nucleuscloud/neosync/backend/internal/errors" + "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" +) + +type UserAccountServiceClient interface { + IsUserInAccount(ctx context.Context, req *connect.Request[mgmtv1alpha1.IsUserInAccountRequest]) (*connect.Response[mgmtv1alpha1.IsUserInAccountResponse], error) +} + +type User struct { + id pgtype.UUID + + apiKeyData *auth_apikey.TokenContextData // Optional because we may not have an api key + + userAccountServiceClient UserAccountServiceClient + + EntityEnforcer +} + +func (u *User) Id() string { + return neosyncdb.UUIDString(u.id) +} +func (u *User) PgId() pgtype.UUID { + return u.id +} + +func (u *User) IsWorkerApiKey() bool { + return u.apiKeyData != nil && u.apiKeyData.ApiKeyType == apikey.WorkerApiKey +} + +func (u *User) IsApiKey() bool { + return u.apiKeyData != nil +} + +func EnforceAccountAccess(ctx context.Context, user *User, accountId string) error { + if user.IsApiKey() { + if user.IsWorkerApiKey() { + return nil + } + // We first want to check to make sure the api key is valid and that it says it's in the account + // However, we still want to make a DB request to ensure the DB still says it's in the account + if user.apiKeyData.ApiKey == nil || neosyncdb.UUIDString(user.apiKeyData.ApiKey.AccountID) != accountId { + return nucleuserrors.NewForbidden("api key is not valid for account") + } + } + + // Note: because we are calling to the user account service here, the ctx must still contain the user data + inAccountResp, err := user.userAccountServiceClient.IsUserInAccount(ctx, connect.NewRequest(&mgmtv1alpha1.IsUserInAccountRequest{AccountId: accountId})) + if err != nil { + return fmt.Errorf("unable to check if user is in account: %w", err) + } + if !inAccountResp.Msg.GetOk() { + return nucleuserrors.NewForbidden("user is not in account") + } + return nil +} diff --git a/backend/pkg/integration-test/integration-test.go b/backend/pkg/integration-test/integration-test.go index 3970a92ca8..da3106f13b 100644 --- a/backend/pkg/integration-test/integration-test.go +++ b/backend/pkg/integration-test/integration-test.go @@ -2,11 +2,13 @@ package integrationtests_test import ( "context" + "fmt" "net/http" "net/http/httptest" "testing" "connectrpc.com/connect" + "github.com/jackc/pgx/v5/stdlib" db_queries "github.com/nucleuscloud/neosync/backend/gen/go/db" mysql_queries "github.com/nucleuscloud/neosync/backend/gen/go/db/dbschemas/mysql" pg_queries "github.com/nucleuscloud/neosync/backend/gen/go/db/dbschemas/postgresql" @@ -18,9 +20,12 @@ import ( "github.com/nucleuscloud/neosync/backend/internal/authmgmt" auth_interceptor "github.com/nucleuscloud/neosync/backend/internal/connect/interceptors/auth" jobhooks "github.com/nucleuscloud/neosync/backend/internal/ee/hooks/jobs" + "github.com/nucleuscloud/neosync/backend/internal/ee/rbac" + "github.com/nucleuscloud/neosync/backend/internal/ee/rbac/enforcer" neosync_gcp "github.com/nucleuscloud/neosync/backend/internal/gcp" "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" clientmanager "github.com/nucleuscloud/neosync/backend/internal/temporal/clientmanager" + "github.com/nucleuscloud/neosync/backend/internal/userdata" "github.com/nucleuscloud/neosync/backend/internal/utils" "github.com/nucleuscloud/neosync/backend/pkg/mongoconnect" "github.com/nucleuscloud/neosync/backend/pkg/sqlconnect" @@ -150,6 +155,7 @@ func (s *NeosyncApiTestClient) Setup(ctx context.Context, t testing.TB) error { if err != nil { return err } + s.Pgcontainer = pgcontainer s.NeosyncQuerier = db_queries.New() s.systemQuerier = pg_queries.New() @@ -168,6 +174,24 @@ func (s *NeosyncApiTestClient) Setup(ctx context.Context, t testing.TB) error { }, } + err = s.InitializeTest(ctx, t) + if err != nil { + return err + } + + permissiveRbacClient := rbac.NewAllowAllClient() + + rbacenforcer, err := enforcer.NewActiveEnforcer(ctx, stdlib.OpenDBFromPool(pgcontainer.DB), "neosync_api.casbin_rule") + if err != nil { + return fmt.Errorf("unable to create rbac enforcer: %w", err) + } + rbacenforcer.EnableAutoSave(true) + err = rbacenforcer.LoadPolicy() + if err != nil { + return fmt.Errorf("unable to load rbac policies: %w", err) + } + enforcedRbacClient := rbac.New(rbacenforcer) + maxAllowed := int64(10000) unauthdUserService := v1alpha1_useraccountservice.New( &v1alpha1_useraccountservice.Config{IsAuthEnabled: false, IsNeosyncCloud: false, DefaultMaxAllowedRecords: &maxAllowed}, @@ -175,8 +199,10 @@ func (s *NeosyncApiTestClient) Setup(ctx context.Context, t testing.TB) error { s.Mocks.TemporalConfigProvider, s.Mocks.Authclient, s.Mocks.Authmanagerclient, - nil, + nil, // billing client + permissiveRbacClient, // rbac client ) + unauthdUserClient := userdata.NewClient(unauthdUserService, permissiveRbacClient) authdUserService := v1alpha1_useraccountservice.New( &v1alpha1_useraccountservice.Config{IsAuthEnabled: true, IsNeosyncCloud: false}, @@ -184,15 +210,18 @@ func (s *NeosyncApiTestClient) Setup(ctx context.Context, t testing.TB) error { s.Mocks.TemporalConfigProvider, s.Mocks.Authclient, s.Mocks.Authmanagerclient, - nil, + nil, // billing client + enforcedRbacClient, // rbac client ) sqlmanagerclient := NewTestSqlManagerClient() + authdUserDataClient := userdata.NewClient(authdUserService, enforcedRbacClient) + authdConnectionService := v1alpha1_connectionservice.New( &v1alpha1_connectionservice.Config{}, neosyncdb.New(pgcontainer.DB, db_queries.New()), - authdUserService, + authdUserDataClient, mongoconnect.NewConnector(), awsmanager.New(), sqlmanagerclient, @@ -206,10 +235,13 @@ func (s *NeosyncApiTestClient) Setup(ctx context.Context, t testing.TB) error { s.Mocks.Authclient, s.Mocks.Authmanagerclient, s.Mocks.Billingclient, + enforcedRbacClient, // rbac client ) + neoCloudUserDataClient := userdata.NewClient(neoCloudAuthdUserService, enforcedRbacClient) neoCloudAuthdAnonymizeService := v1alpha_anonymizationservice.New( &v1alpha_anonymizationservice.Config{IsAuthEnabled: true, IsNeosyncCloud: true, IsPresidioEnabled: false}, - nil, + nil, // meter + neoCloudUserDataClient, neoCloudAuthdUserService, s.Mocks.Presidio.Analyzer, s.Mocks.Presidio.Anonymizer, @@ -219,7 +251,7 @@ func (s *NeosyncApiTestClient) Setup(ctx context.Context, t testing.TB) error { neoCloudConnectionService := v1alpha1_connectionservice.New( &v1alpha1_connectionservice.Config{}, neosyncdb.New(pgcontainer.DB, db_queries.New()), - neoCloudAuthdUserService, + neoCloudUserDataClient, mongoconnect.NewConnector(), awsmanager.New(), sqlmanagerclient, @@ -227,7 +259,7 @@ func (s *NeosyncApiTestClient) Setup(ctx context.Context, t testing.TB) error { ) neoCloudJobHookService := jobhooks.New( neosyncdb.New(pgcontainer.DB, db_queries.New()), - neoCloudAuthdUserService, + neoCloudUserDataClient, jobhooks.WithEnabled(), ) neoCloudJobService := v1alpha1_jobservice.New( @@ -235,9 +267,9 @@ func (s *NeosyncApiTestClient) Setup(ctx context.Context, t testing.TB) error { neosyncdb.New(pgcontainer.DB, db_queries.New()), s.Mocks.TemporalClientManager, neoCloudConnectionService, - neoCloudAuthdUserService, sqlmanagerclient, neoCloudJobHookService, + neoCloudUserDataClient, ) unauthdTransformersService := v1alpha1_transformersservice.New( @@ -246,23 +278,23 @@ func (s *NeosyncApiTestClient) Setup(ctx context.Context, t testing.TB) error { IsNeosyncCloud: false, }, neosyncdb.New(pgcontainer.DB, db_queries.New()), - unauthdUserService, s.Mocks.Presidio.Entities, + unauthdUserClient, ) unauthdConnectionsService := v1alpha1_connectionservice.New( &v1alpha1_connectionservice.Config{}, neosyncdb.New(pgcontainer.DB, db_queries.New()), - unauthdUserService, + unauthdUserClient, mongoconnect.NewConnector(), awsmanager.New(), sqlmanagerclient, &sqlconnect.SqlOpenConnector{}, ) - jobhookService := jobhooks.New( + unAuthdjobhookService := jobhooks.New( neosyncdb.New(pgcontainer.DB, db_queries.New()), - unauthdUserService, + unauthdUserClient, ) unauthdJobsService := v1alpha1_jobservice.New( @@ -270,14 +302,13 @@ func (s *NeosyncApiTestClient) Setup(ctx context.Context, t testing.TB) error { neosyncdb.New(pgcontainer.DB, db_queries.New()), s.Mocks.TemporalClientManager, unauthdConnectionsService, - unauthdUserService, sqlmanagerclient, - jobhookService, + unAuthdjobhookService, + unauthdUserClient, ) unauthdConnectionDataService := v1alpha1_connectiondataservice.New( &v1alpha1_connectiondataservice.Config{}, - unauthdUserService, unauthdConnectionsService, unauthdJobsService, awsmanager.New(), @@ -294,7 +325,8 @@ func (s *NeosyncApiTestClient) Setup(ctx context.Context, t testing.TB) error { unauthdAnonymizationService := v1alpha_anonymizationservice.New( &v1alpha_anonymizationservice.Config{IsPresidioEnabled: false}, - nil, + nil, // meter + unauthdUserClient, unauthdUserService, presAnalyzeClient, presAnonClient, neosyncdb.New(pgcontainer.DB, db_queries.New()), @@ -392,11 +424,6 @@ func (s *NeosyncApiTestClient) Setup(ctx context.Context, t testing.TB) error { httpsrv: s.httpsrv, basepath: "/ncauth", } - - err = s.InitializeTest(ctx, t) - if err != nil { - return err - } return nil } diff --git a/backend/protos/mgmt/v1alpha1/user_account.proto b/backend/protos/mgmt/v1alpha1/user_account.proto index 8ce64f9b4d..7f18dd5c93 100644 --- a/backend/protos/mgmt/v1alpha1/user_account.proto +++ b/backend/protos/mgmt/v1alpha1/user_account.proto @@ -308,6 +308,30 @@ message SetBillingMeterEventRequest { message SetBillingMeterEventResponse {} +message SetUserRoleRequest { + // The account id to apply this role to + string account_id = 1 [(buf.validate.field).string.uuid = true]; + // The user that this will be applied to + string user_id = 2 [(buf.validate.field).string.uuid = true]; + // The role that this user will obtain + AccountRole role = 3; +} + +enum AccountRole { + // Default value, this should not be used, but will default to ACCOUNT_ROLE_JOB_VIEWER + ACCOUNT_ROLE_UNSPECIFIED = 0; + // Admin, can do anything in the account. + ACCOUNT_ROLE_ADMIN = 1; + // Can view, edit jobs and connections + ACCOUNT_ROLE_JOB_DEVELOPER = 2; + // Can view + ACCOUNT_ROLE_JOB_VIEWER = 3; + // Can view and execute + ACCOUNT_ROLE_JOB_EXECUTOR = 4; +} + +message SetUserRoleResponse {} + service UserAccountService { rpc GetUser(GetUserRequest) returns (GetUserResponse) {} rpc SetUser(SetUserRequest) returns (SetUserResponse) {} @@ -363,4 +387,7 @@ service UserAccountService { // Sends a new metered event to the billing system rpc SetBillingMeterEvent(SetBillingMeterEventRequest) returns (SetBillingMeterEventResponse) {} + + // Sets the users role + rpc SetUserRole(SetUserRoleRequest) returns (SetUserRoleResponse) {} } diff --git a/backend/services/mgmt/v1alpha1/anonymization-service/anonymization.go b/backend/services/mgmt/v1alpha1/anonymization-service/anonymization.go index 5f46f5a2f9..3765c362c0 100644 --- a/backend/services/mgmt/v1alpha1/anonymization-service/anonymization.go +++ b/backend/services/mgmt/v1alpha1/anonymization-service/anonymization.go @@ -12,6 +12,7 @@ import ( "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect" nucleuserrors "github.com/nucleuscloud/neosync/backend/internal/errors" "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" + "github.com/nucleuscloud/neosync/backend/internal/userdata" "github.com/nucleuscloud/neosync/backend/pkg/metrics" jsonanonymizer "github.com/nucleuscloud/neosync/internal/json-anonymizer" "go.opentelemetry.io/otel/attribute" @@ -35,12 +36,21 @@ func (s *Service) AnonymizeMany( ) } - accountUuid, err := s.verifyUserInAccount(ctx, req.Msg.AccountId) + user, err := s.userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + err = userdata.EnforceAccountAccess(ctx, user, req.Msg.GetAccountId()) + if err != nil { + return nil, err + } + + accountUuid, err := neosyncdb.ToUuid(req.Msg.GetAccountId()) if err != nil { return nil, err } - account, err := s.db.Q.GetAccount(ctx, s.db.Db, *accountUuid) + account, err := s.db.Q.GetAccount(ctx, s.db.Db, accountUuid) if err != nil { return nil, err } @@ -52,7 +62,7 @@ func (s *Service) AnonymizeMany( requestedCount := uint64(len(req.Msg.InputData)) resp, err := s.useraccountService.IsAccountStatusValid(ctx, connect.NewRequest(&mgmtv1alpha1.IsAccountStatusValidRequest{ - AccountId: neosyncdb.UUIDString(*accountUuid), + AccountId: req.Msg.GetAccountId(), RequestedRecordCount: &requestedCount, })) if err != nil { @@ -76,7 +86,7 @@ func (s *Service) AnonymizeMany( var outputErrorCounter, outputCounter metric.Int64Counter var labels []attribute.KeyValue if s.meter != nil { - labels = getMetricLabels(ctx, "anonymizeMany", neosyncdb.UUIDString(*accountUuid)) + labels = getMetricLabels(ctx, "anonymizeMany", req.Msg.GetAccountId()) counter, err := s.meter.Int64Counter(inputMetricStr) if err != nil { return nil, err @@ -126,12 +136,21 @@ func (s *Service) AnonymizeSingle( ctx context.Context, req *connect.Request[mgmtv1alpha1.AnonymizeSingleRequest], ) (*connect.Response[mgmtv1alpha1.AnonymizeSingleResponse], error) { - accountUuid, err := s.verifyUserInAccount(ctx, req.Msg.AccountId) + user, err := s.userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + err = userdata.EnforceAccountAccess(ctx, user, req.Msg.GetAccountId()) + if err != nil { + return nil, err + } + + accountUuid, err := neosyncdb.ToUuid(req.Msg.GetAccountId()) if err != nil { return nil, err } - account, err := s.db.Q.GetAccount(ctx, s.db.Db, *accountUuid) + account, err := s.db.Q.GetAccount(ctx, s.db.Db, accountUuid) if err != nil { return nil, err } @@ -151,7 +170,7 @@ func (s *Service) AnonymizeSingle( requestedCount := uint64(len(req.Msg.InputData)) resp, err := s.useraccountService.IsAccountStatusValid(ctx, connect.NewRequest(&mgmtv1alpha1.IsAccountStatusValidRequest{ - AccountId: neosyncdb.UUIDString(*accountUuid), + AccountId: req.Msg.GetAccountId(), RequestedRecordCount: &requestedCount, })) if err != nil { @@ -174,7 +193,7 @@ func (s *Service) AnonymizeSingle( var outputCounter, outputErrorCounter metric.Int64Counter var labels []attribute.KeyValue if s.meter != nil { - labels = getMetricLabels(ctx, "anonymizeSingle", neosyncdb.UUIDString(*accountUuid)) + labels = getMetricLabels(ctx, "anonymizeSingle", req.Msg.GetAccountId()) counter, err := s.meter.Int64Counter(inputMetricStr) if err != nil { return nil, err diff --git a/backend/services/mgmt/v1alpha1/anonymization-service/service.go b/backend/services/mgmt/v1alpha1/anonymization-service/service.go index 61648d91aa..adf2aca39c 100644 --- a/backend/services/mgmt/v1alpha1/anonymization-service/service.go +++ b/backend/services/mgmt/v1alpha1/anonymization-service/service.go @@ -3,6 +3,7 @@ package v1alpha_anonymizationservice import ( "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect" "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" + "github.com/nucleuscloud/neosync/backend/internal/userdata" presidioapi "github.com/nucleuscloud/neosync/internal/ee/presidio" "go.opentelemetry.io/otel/metric" ) @@ -10,6 +11,7 @@ import ( type Service struct { cfg *Config meter metric.Meter // optional + userdataclient userdata.Interface useraccountService mgmtv1alpha1connect.UserAccountServiceClient analyze presidioapi.AnalyzeInterface anonymize presidioapi.AnonymizeInterface @@ -26,6 +28,7 @@ type Config struct { func New( cfg *Config, meter metric.Meter, + userdataclient userdata.Interface, useraccountService mgmtv1alpha1connect.UserAccountServiceClient, analyzeclient presidioapi.AnalyzeInterface, anonymizeclient presidioapi.AnonymizeInterface, @@ -34,6 +37,7 @@ func New( return &Service{ cfg: cfg, meter: meter, + userdataclient: userdataclient, useraccountService: useraccountService, analyze: analyzeclient, anonymize: anonymizeclient, diff --git a/backend/services/mgmt/v1alpha1/anonymization-service/user-account.go b/backend/services/mgmt/v1alpha1/anonymization-service/user-account.go deleted file mode 100644 index 49cf33902d..0000000000 --- a/backend/services/mgmt/v1alpha1/anonymization-service/user-account.go +++ /dev/null @@ -1,45 +0,0 @@ -package v1alpha_anonymizationservice - -import ( - "context" - - "connectrpc.com/connect" - "github.com/jackc/pgx/v5/pgtype" - mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" - "github.com/nucleuscloud/neosync/backend/internal/apikey" - auth_apikey "github.com/nucleuscloud/neosync/backend/internal/auth/apikey" - nucleuserrors "github.com/nucleuscloud/neosync/backend/internal/errors" - "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" -) - -func (s *Service) verifyUserInAccount( - ctx context.Context, - accountId string, -) (*pgtype.UUID, error) { - accountUuid, err := neosyncdb.ToUuid(accountId) - if err != nil { - return nil, err - } - - if isWorkerApiKey(ctx) { - return &accountUuid, nil - } - - resp, err := s.useraccountService.IsUserInAccount(ctx, connect.NewRequest(&mgmtv1alpha1.IsUserInAccountRequest{AccountId: accountId})) - if err != nil { - return nil, err - } - if !resp.Msg.Ok { - return nil, nucleuserrors.NewForbidden("user in not in requested account") - } - - return &accountUuid, nil -} - -func isWorkerApiKey(ctx context.Context) bool { - data, err := auth_apikey.GetTokenDataFromCtx(ctx) - if err != nil { - return false - } - return data.ApiKeyType == apikey.WorkerApiKey -} diff --git a/backend/services/mgmt/v1alpha1/api-key-service/api-keys.go b/backend/services/mgmt/v1alpha1/api-key-service/api-keys.go index 5f287ee1c9..03611c3a01 100644 --- a/backend/services/mgmt/v1alpha1/api-key-service/api-keys.go +++ b/backend/services/mgmt/v1alpha1/api-key-service/api-keys.go @@ -8,8 +8,10 @@ import ( mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" "github.com/nucleuscloud/neosync/backend/internal/apikey" "github.com/nucleuscloud/neosync/backend/internal/dtomaps" + "github.com/nucleuscloud/neosync/backend/internal/ee/rbac" nucleuserrors "github.com/nucleuscloud/neosync/backend/internal/errors" "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" + "github.com/nucleuscloud/neosync/backend/internal/userdata" pkg_utils "github.com/nucleuscloud/neosync/backend/pkg/utils" ) @@ -17,12 +19,21 @@ func (s *Service) GetAccountApiKeys( ctx context.Context, req *connect.Request[mgmtv1alpha1.GetAccountApiKeysRequest], ) (*connect.Response[mgmtv1alpha1.GetAccountApiKeysResponse], error) { - accountUuid, err := s.verifyUserInAccount(ctx, req.Msg.AccountId) + user, err := s.userdataclient.GetUser(ctx) if err != nil { return nil, err } - apiKeys, err := s.db.Q.GetAccountApiKeys(ctx, s.db.Db, *accountUuid) + if err := user.EnforceAccount(ctx, userdata.NewIdentifier(req.Msg.GetAccountId()), rbac.AccountAction_View); err != nil { + return nil, err + } + + accountUuid, err := neosyncdb.ToUuid(req.Msg.GetAccountId()) + if err != nil { + return nil, err + } + + apiKeys, err := s.db.Q.GetAccountApiKeys(ctx, s.db.Db, accountUuid) if err != nil { return nil, err } @@ -42,7 +53,7 @@ func (s *Service) GetAccountApiKey( ctx context.Context, req *connect.Request[mgmtv1alpha1.GetAccountApiKeyRequest], ) (*connect.Response[mgmtv1alpha1.GetAccountApiKeyResponse], error) { - apiKeyUuid, err := neosyncdb.ToUuid(req.Msg.Id) + apiKeyUuid, err := neosyncdb.ToUuid(req.Msg.GetId()) if err != nil { return nil, err } @@ -54,10 +65,13 @@ func (s *Service) GetAccountApiKey( return nil, nucleuserrors.NewNotFound("unable to find api key") } - _, err = s.verifyUserInAccount(ctx, neosyncdb.UUIDString(apiKey.AccountID)) + user, err := s.userdataclient.GetUser(ctx) if err != nil { return nil, err } + if err := user.EnforceAccount(ctx, userdata.NewIdentifier(neosyncdb.UUIDString(apiKey.AccountID)), rbac.AccountAction_View); err != nil { + return nil, err + } return connect.NewResponse(&mgmtv1alpha1.GetAccountApiKeyResponse{ ApiKey: dtomaps.ToAccountApiKeyDto(&apiKey, nil), @@ -68,16 +82,25 @@ func (s *Service) CreateAccountApiKey( ctx context.Context, req *connect.Request[mgmtv1alpha1.CreateAccountApiKeyRequest], ) (*connect.Response[mgmtv1alpha1.CreateAccountApiKeyResponse], error) { - accountUuid, err := s.verifyUserInAccount(ctx, req.Msg.AccountId) + user, err := s.userdataclient.GetUser(ctx) if err != nil { return nil, err } - userUuid, err := s.getUserUuid(ctx) + + if user.IsApiKey() { + return nil, nucleuserrors.NewUnauthorized("api key user cannot create api keys") + } + + if err := user.EnforceAccount(ctx, userdata.NewIdentifier(req.Msg.GetAccountId()), rbac.AccountAction_Edit); err != nil { + return nil, err + } + + accountUuid, err := neosyncdb.ToUuid(req.Msg.GetAccountId()) if err != nil { return nil, err } - expiresAt, err := neosyncdb.ToTimestamp(req.Msg.ExpiresAt.AsTime()) + expiresAt, err := neosyncdb.ToTimestamp(req.Msg.GetExpiresAt().AsTime()) if err != nil { return nil, err } @@ -90,8 +113,8 @@ func (s *Service) CreateAccountApiKey( newApiKey, err := s.db.CreateAccountApikey(ctx, &neosyncdb.CreateAccountApiKeyRequest{ KeyName: req.Msg.Name, KeyValue: hashedKeyValue, - AccountUuid: *accountUuid, - CreatedByUserUuid: *userUuid, + AccountUuid: accountUuid, + CreatedByUserUuid: user.PgId(), ExpiresAt: expiresAt, }) if err != nil { @@ -106,7 +129,7 @@ func (s *Service) RegenerateAccountApiKey( ctx context.Context, req *connect.Request[mgmtv1alpha1.RegenerateAccountApiKeyRequest], ) (*connect.Response[mgmtv1alpha1.RegenerateAccountApiKeyResponse], error) { - apiKeyUuid, err := neosyncdb.ToUuid(req.Msg.Id) + apiKeyUuid, err := neosyncdb.ToUuid(req.Msg.GetId()) if err != nil { return nil, err } @@ -118,26 +141,31 @@ func (s *Service) RegenerateAccountApiKey( return nil, nucleuserrors.NewNotFound("account api key not found") } - _, err = s.verifyUserInAccount(ctx, neosyncdb.UUIDString(apiKey.AccountID)) + user, err := s.userdataclient.GetUser(ctx) if err != nil { return nil, err } - userUuid, err := s.getUserUuid(ctx) - if err != nil { + + if user.IsApiKey() { + return nil, nucleuserrors.NewUnauthorized("api key user cannot regenerate api keys") + } + + if err := user.EnforceAccount(ctx, userdata.NewIdentifier(neosyncdb.UUIDString(apiKey.AccountID)), rbac.AccountAction_Edit); err != nil { return nil, err } + clearKeyValue := apikey.NewV1AccountKey() hashedKeyValue := pkg_utils.ToSha256( clearKeyValue, ) - expiresAt, err := neosyncdb.ToTimestamp(req.Msg.ExpiresAt.AsTime()) + expiresAt, err := neosyncdb.ToTimestamp(req.Msg.GetExpiresAt().AsTime()) if err != nil { return nil, err } updatedApiKey, err := s.db.Q.UpdateAccountApiKeyValue(ctx, s.db.Db, db_queries.UpdateAccountApiKeyValueParams{ KeyValue: hashedKeyValue, ExpiresAt: expiresAt, - UpdatedByID: *userUuid, + UpdatedByID: user.PgId(), ID: apiKeyUuid, }) if err != nil { @@ -152,10 +180,11 @@ func (s *Service) DeleteAccountApiKey( ctx context.Context, req *connect.Request[mgmtv1alpha1.DeleteAccountApiKeyRequest], ) (*connect.Response[mgmtv1alpha1.DeleteAccountApiKeyResponse], error) { - apiKeyUuid, err := neosyncdb.ToUuid(req.Msg.Id) + apiKeyUuid, err := neosyncdb.ToUuid(req.Msg.GetId()) if err != nil { return nil, err } + apiKey, err := s.db.Q.GetAccountApiKeyById(ctx, s.db.Db, apiKeyUuid) if err != nil && !neosyncdb.IsNoRows(err) { return nil, err @@ -163,10 +192,16 @@ func (s *Service) DeleteAccountApiKey( return connect.NewResponse(&mgmtv1alpha1.DeleteAccountApiKeyResponse{}), nil } - _, err = s.verifyUserInAccount(ctx, neosyncdb.UUIDString(apiKey.AccountID)) + user, err := s.userdataclient.GetUser(ctx) if err != nil { return nil, err } + if user.IsApiKey() { + return nil, nucleuserrors.NewUnauthorized("api key user cannot delete api keys") + } + if err := user.EnforceAccount(ctx, userdata.NewIdentifier(neosyncdb.UUIDString(apiKey.AccountID)), rbac.AccountAction_Edit); err != nil { + return nil, err + } err = s.db.Q.RemoveAccountApiKey(ctx, s.db.Db, apiKeyUuid) if err != nil && !neosyncdb.IsNoRows(err) { diff --git a/backend/services/mgmt/v1alpha1/api-key-service/api-keys_test.go b/backend/services/mgmt/v1alpha1/api-key-service/api-keys_test.go index 87ab6007da..a6fc4e0961 100644 --- a/backend/services/mgmt/v1alpha1/api-key-service/api-keys_test.go +++ b/backend/services/mgmt/v1alpha1/api-key-service/api-keys_test.go @@ -2,6 +2,7 @@ package v1alpha1_apikeyservice import ( "context" + "errors" "testing" "time" @@ -11,8 +12,8 @@ import ( "github.com/jackc/pgx/v5/pgtype" db_queries "github.com/nucleuscloud/neosync/backend/gen/go/db" mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" - "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect" "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" + "github.com/nucleuscloud/neosync/backend/internal/userdata" pgxmock "github.com/nucleuscloud/neosync/internal/mocks/github.com/jackc/pgx/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -22,9 +23,9 @@ import ( func Test_Service_GetAccountApiKeys(t *testing.T) { mockDbtx := neosyncdb.NewMockDBTX(t) mockQuerier := db_queries.NewMockQuerier(t) - mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) + mockUserService := userdata.NewMockInterface(t) - svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) rawData := []db_queries.NeosyncApiAccountApiKey{ { @@ -52,7 +53,7 @@ func Test_Service_GetAccountApiKeys(t *testing.T) { } mockQuerier.On("GetAccountApiKeys", mock.Anything, mock.Anything, mock.Anything). Return(rawData, nil) - mockIsUserInAccount(mockUserAccountService, true) + mockIsUserInAccount(t, mockUserService, true) resp, err := svc.GetAccountApiKeys(context.Background(), connect.NewRequest(&mgmtv1alpha1.GetAccountApiKeysRequest{ AccountId: uuid.NewString(), @@ -76,11 +77,11 @@ func Test_Service_GetAccountApiKeys(t *testing.T) { func Test_Service_GetAccountApiKeys_ForbiddenAccount(t *testing.T) { mockDbtx := neosyncdb.NewMockDBTX(t) mockQuerier := db_queries.NewMockQuerier(t) - mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) + mockUserService := userdata.NewMockInterface(t) - svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) - mockIsUserInAccount(mockUserAccountService, false) + mockIsUserInAccount(t, mockUserService, false) resp, err := svc.GetAccountApiKeys(context.Background(), connect.NewRequest(&mgmtv1alpha1.GetAccountApiKeysRequest{ AccountId: uuid.NewString(), @@ -92,9 +93,9 @@ func Test_Service_GetAccountApiKeys_ForbiddenAccount(t *testing.T) { func Test_Service_GetAccountApiKey_Found(t *testing.T) { mockDbtx := neosyncdb.NewMockDBTX(t) mockQuerier := db_queries.NewMockQuerier(t) - mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) + mockUserService := userdata.NewMockInterface(t) - svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) rawData := db_queries.NeosyncApiAccountApiKey{ ID: newPgUuid(t), @@ -109,7 +110,7 @@ func Test_Service_GetAccountApiKey_Found(t *testing.T) { } mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). Return(rawData, nil) - mockIsUserInAccount(mockUserAccountService, true) + mockIsUserInAccount(t, mockUserService, true) resp, err := svc.GetAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.GetAccountApiKeyRequest{ Id: uuid.NewString(), @@ -123,9 +124,9 @@ func Test_Service_GetAccountApiKey_Found(t *testing.T) { func Test_Service_GetAccountApiKey_NotFound(t *testing.T) { mockDbtx := neosyncdb.NewMockDBTX(t) mockQuerier := db_queries.NewMockQuerier(t) - mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) + mockUserService := userdata.NewMockInterface(t) - svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). Return(db_queries.NeosyncApiAccountApiKey{}, pgx.ErrNoRows) @@ -140,9 +141,9 @@ func Test_Service_GetAccountApiKey_NotFound(t *testing.T) { func Test_Service_GetAccountApiKey_Found_ForbiddenAccount(t *testing.T) { mockDbtx := neosyncdb.NewMockDBTX(t) mockQuerier := db_queries.NewMockQuerier(t) - mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) + mockUserService := userdata.NewMockInterface(t) - svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) rawData := db_queries.NeosyncApiAccountApiKey{ ID: newPgUuid(t), @@ -157,7 +158,7 @@ func Test_Service_GetAccountApiKey_Found_ForbiddenAccount(t *testing.T) { } mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). Return(rawData, nil) - mockIsUserInAccount(mockUserAccountService, false) + mockIsUserInAccount(t, mockUserService, false) resp, err := svc.GetAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.GetAccountApiKeyRequest{ Id: uuid.NewString(), @@ -170,12 +171,12 @@ func Test_Service_CreateAccountApiKey(t *testing.T) { mockDbtx := neosyncdb.NewMockDBTX(t) mockQuerier := db_queries.NewMockQuerier(t) mockTx := pgxmock.NewMockTx(t) - mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) + mockUserService := userdata.NewMockInterface(t) - svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) + + mockIsUserInAccount(t, mockUserService, true) - mockIsUserInAccount(mockUserAccountService, true) - mockUserAccountCalls(mockUserAccountService, true, uuid.NewString()) mockDbtx.On("Begin", mock.Anything).Return(mockTx, nil) mockTx.On("Commit", mock.Anything).Return(nil) mockTx.On("Rollback", mock.Anything).Return(nil) @@ -214,12 +215,12 @@ func Test_Service_CreateAccountApiKey(t *testing.T) { func Test_Service_RegenerateAccountApiKey(t *testing.T) { mockDbtx := neosyncdb.NewMockDBTX(t) mockQuerier := db_queries.NewMockQuerier(t) - mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) + mockUserService := userdata.NewMockInterface(t) + + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) - svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) + mockIsUserInAccount(t, mockUserService, true) - mockIsUserInAccount(mockUserAccountService, true) - mockUserAccountCalls(mockUserAccountService, true, uuid.NewString()) rawData := db_queries.NeosyncApiAccountApiKey{ ID: newPgUuid(t), AccountID: newPgUuid(t), @@ -249,11 +250,11 @@ func Test_Service_RegenerateAccountApiKey(t *testing.T) { func Test_Service_RegenerateAccountApiKey_ForbiddenAccount(t *testing.T) { mockDbtx := neosyncdb.NewMockDBTX(t) mockQuerier := db_queries.NewMockQuerier(t) - mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) + mockUserService := userdata.NewMockInterface(t) - svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) - mockIsUserInAccount(mockUserAccountService, false) + mockIsUserInAccount(t, mockUserService, false) rawData := db_queries.NeosyncApiAccountApiKey{ ID: newPgUuid(t), AccountID: newPgUuid(t), @@ -279,9 +280,9 @@ func Test_Service_RegenerateAccountApiKey_ForbiddenAccount(t *testing.T) { func Test_Service_RegenerateAccountApiKey_NotFound(t *testing.T) { mockDbtx := neosyncdb.NewMockDBTX(t) mockQuerier := db_queries.NewMockQuerier(t) - mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) + mockUserService := userdata.NewMockInterface(t) - svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). Return(db_queries.NeosyncApiAccountApiKey{}, pgx.ErrNoRows) @@ -297,11 +298,11 @@ func Test_Service_RegenerateAccountApiKey_NotFound(t *testing.T) { func Test_Service_CreateAccountApiKey_ForbiddenAccount(t *testing.T) { mockDbtx := neosyncdb.NewMockDBTX(t) mockQuerier := db_queries.NewMockQuerier(t) - mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) + mockUserService := userdata.NewMockInterface(t) - svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) - mockIsUserInAccount(mockUserAccountService, false) + mockIsUserInAccount(t, mockUserService, false) resp, err := svc.CreateAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.CreateAccountApiKeyRequest{ AccountId: uuid.NewString(), @@ -315,9 +316,9 @@ func Test_Service_CreateAccountApiKey_ForbiddenAccount(t *testing.T) { func Test_Service_DeleteAccountApiKey_Existing(t *testing.T) { mockDbtx := neosyncdb.NewMockDBTX(t) mockQuerier := db_queries.NewMockQuerier(t) - mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) + mockUserService := userdata.NewMockInterface(t) - svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) rawData := db_queries.NeosyncApiAccountApiKey{ ID: newPgUuid(t), @@ -332,7 +333,7 @@ func Test_Service_DeleteAccountApiKey_Existing(t *testing.T) { } mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). Return(rawData, nil) - mockIsUserInAccount(mockUserAccountService, true) + mockIsUserInAccount(t, mockUserService, true) mockQuerier.On("RemoveAccountApiKey", mock.Anything, mock.Anything, mock.Anything).Return(nil) resp, err := svc.DeleteAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.DeleteAccountApiKeyRequest{ @@ -345,9 +346,9 @@ func Test_Service_DeleteAccountApiKey_Existing(t *testing.T) { func Test_Service_DeleteAccountApiKey_Existing_ForbiddenAccount(t *testing.T) { mockDbtx := neosyncdb.NewMockDBTX(t) mockQuerier := db_queries.NewMockQuerier(t) - mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) + mockUserService := userdata.NewMockInterface(t) - svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) rawData := db_queries.NeosyncApiAccountApiKey{ ID: newPgUuid(t), @@ -362,7 +363,7 @@ func Test_Service_DeleteAccountApiKey_Existing_ForbiddenAccount(t *testing.T) { } mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). Return(rawData, nil) - mockIsUserInAccount(mockUserAccountService, false) + mockIsUserInAccount(t, mockUserService, false) resp, err := svc.DeleteAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.DeleteAccountApiKeyRequest{ Id: uuid.NewString(), @@ -374,9 +375,9 @@ func Test_Service_DeleteAccountApiKey_Existing_ForbiddenAccount(t *testing.T) { func Test_Service_DeleteAccountApiKey_NotFound(t *testing.T) { mockDbtx := neosyncdb.NewMockDBTX(t) mockQuerier := db_queries.NewMockQuerier(t) - mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) + mockUserService := userdata.NewMockInterface(t) - svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). Return(db_queries.NeosyncApiAccountApiKey{}, pgx.ErrNoRows) @@ -391,9 +392,9 @@ func Test_Service_DeleteAccountApiKey_NotFound(t *testing.T) { func Test_Service_DeleteAccountApiKey_Existing_DeleteRace(t *testing.T) { mockDbtx := neosyncdb.NewMockDBTX(t) mockQuerier := db_queries.NewMockQuerier(t) - mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) + mockUserService := userdata.NewMockInterface(t) - svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) rawData := db_queries.NeosyncApiAccountApiKey{ ID: newPgUuid(t), @@ -408,7 +409,7 @@ func Test_Service_DeleteAccountApiKey_Existing_DeleteRace(t *testing.T) { } mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). Return(rawData, nil) - mockIsUserInAccount(mockUserAccountService, true) + mockIsUserInAccount(t, mockUserService, true) mockQuerier.On("RemoveAccountApiKey", mock.Anything, mock.Anything, mock.Anything).Return(pgx.ErrNoRows) resp, err := svc.DeleteAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.DeleteAccountApiKeyRequest{ @@ -426,19 +427,14 @@ func newPgUuid(t *testing.T) pgtype.UUID { return val } -func mockIsUserInAccount(userAccountServiceMock *mgmtv1alpha1connect.MockUserAccountServiceClient, isInAccount bool) { - userAccountServiceMock.On("IsUserInAccount", mock.Anything, mock.Anything).Return(connect.NewResponse(&mgmtv1alpha1.IsUserInAccountResponse{ - Ok: isInAccount, - }), nil) -} - -func mockUserAccountCalls( - userAccountServiceMock *mgmtv1alpha1connect.MockUserAccountServiceClient, - isInAccount bool, - userId string, -) { - mockIsUserInAccount(userAccountServiceMock, isInAccount) - userAccountServiceMock.On("GetUser", mock.Anything, mock.Anything).Return(connect.NewResponse(&mgmtv1alpha1.GetUserResponse{ - UserId: userId, - }), nil) +func mockIsUserInAccount(t testing.TB, userServiceMock *userdata.MockInterface, isInAccount bool) { + mockEntityEnforcer := userdata.NewMockEntityEnforcer(t) + if isInAccount { + mockEntityEnforcer.On("EnforceAccount", mock.Anything, mock.Anything, mock.Anything).Once().Return(nil) + } else { + mockEntityEnforcer.On("EnforceAccount", mock.Anything, mock.Anything, mock.Anything).Once().Return(errors.New("test: not in account")) + } + userServiceMock.On("GetUser", mock.Anything).Once().Return(&userdata.User{ + EntityEnforcer: mockEntityEnforcer, + }, nil) } diff --git a/backend/services/mgmt/v1alpha1/api-key-service/service.go b/backend/services/mgmt/v1alpha1/api-key-service/service.go index d6e0aaff9c..25c0b35b96 100644 --- a/backend/services/mgmt/v1alpha1/api-key-service/service.go +++ b/backend/services/mgmt/v1alpha1/api-key-service/service.go @@ -1,14 +1,14 @@ package v1alpha1_apikeyservice import ( - "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect" "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" + "github.com/nucleuscloud/neosync/backend/internal/userdata" ) type Service struct { - cfg *Config - db *neosyncdb.NeosyncDb - useraccountService mgmtv1alpha1connect.UserAccountServiceClient + cfg *Config + db *neosyncdb.NeosyncDb + userdataclient userdata.Interface } type Config struct { @@ -18,7 +18,7 @@ type Config struct { func New( cfg *Config, db *neosyncdb.NeosyncDb, - useraccountService mgmtv1alpha1connect.UserAccountServiceClient, + userdataclient userdata.Interface, ) *Service { - return &Service{cfg: cfg, db: db, useraccountService: useraccountService} + return &Service{cfg: cfg, db: db, userdataclient: userdataclient} } diff --git a/backend/services/mgmt/v1alpha1/api-key-service/user-account.go b/backend/services/mgmt/v1alpha1/api-key-service/user-account.go deleted file mode 100644 index f0445e8dbc..0000000000 --- a/backend/services/mgmt/v1alpha1/api-key-service/user-account.go +++ /dev/null @@ -1,44 +0,0 @@ -package v1alpha1_apikeyservice - -import ( - "context" - - "connectrpc.com/connect" - "github.com/jackc/pgx/v5/pgtype" - mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" - nucleuserrors "github.com/nucleuscloud/neosync/backend/internal/errors" - "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" -) - -func (s *Service) verifyUserInAccount( - ctx context.Context, - accountId string, -) (*pgtype.UUID, error) { - resp, err := s.useraccountService.IsUserInAccount(ctx, connect.NewRequest(&mgmtv1alpha1.IsUserInAccountRequest{AccountId: accountId})) - if err != nil { - return nil, err - } - if !resp.Msg.Ok { - return nil, nucleuserrors.NewForbidden("user in not in requested account") - } - - accountUuid, err := neosyncdb.ToUuid(accountId) - if err != nil { - return nil, err - } - return &accountUuid, nil -} - -func (s *Service) getUserUuid( - ctx context.Context, -) (*pgtype.UUID, error) { - user, err := s.useraccountService.GetUser(ctx, connect.NewRequest(&mgmtv1alpha1.GetUserRequest{})) - if err != nil { - return nil, err - } - userUuid, err := neosyncdb.ToUuid(user.Msg.UserId) - if err != nil { - return nil, err - } - return &userUuid, nil -} diff --git a/backend/services/mgmt/v1alpha1/connection-data-service/connection-data.go b/backend/services/mgmt/v1alpha1/connection-data-service/connection-data.go index 852678b096..6a272d2cf7 100644 --- a/backend/services/mgmt/v1alpha1/connection-data-service/connection-data.go +++ b/backend/services/mgmt/v1alpha1/connection-data-service/connection-data.go @@ -75,10 +75,6 @@ func (s *Service) GetConnectionDataStream( return err } connection := connResp.Msg.Connection - _, err = s.verifyUserInAccount(ctx, connection.AccountId) - if err != nil { - return err - } connectionTimeout := uint32(5) @@ -483,10 +479,6 @@ func (s *Service) GetConnectionSchema( return nil, err } connection := connResp.Msg.Connection - _, err = s.verifyUserInAccount(ctx, connection.AccountId) - if err != nil { - return nil, err - } switch config := connection.ConnectionConfig.Config.(type) { case *mgmtv1alpha1.ConnectionConfig_MysqlConfig, *mgmtv1alpha1.ConnectionConfig_PgConfig, *mgmtv1alpha1.ConnectionConfig_MssqlConfig: @@ -753,11 +745,6 @@ func (s *Service) GetConnectionForeignConstraints( return nil, err } - _, err = s.verifyUserInAccount(ctx, connection.Msg.Connection.AccountId) - if err != nil { - return nil, err - } - schemaResp, err := s.getConnectionSchema(ctx, connection.Msg.Connection, &schemaOpts{}) if err != nil { return nil, err @@ -816,11 +803,6 @@ func (s *Service) GetConnectionPrimaryConstraints( return nil, err } - _, err = s.verifyUserInAccount(ctx, connection.Msg.Connection.AccountId) - if err != nil { - return nil, err - } - schemaResp, err := s.getConnectionSchema(ctx, connection.Msg.Connection, &schemaOpts{}) if err != nil { return nil, err @@ -870,11 +852,6 @@ func (s *Service) GetConnectionInitStatements( return nil, err } - _, err = s.verifyUserInAccount(ctx, connection.Msg.Connection.AccountId) - if err != nil { - return nil, err - } - schemaResp, err := s.getConnectionSchema(ctx, connection.Msg.Connection, &schemaOpts{}) if err != nil { return nil, err @@ -1213,11 +1190,6 @@ func (s *Service) GetConnectionUniqueConstraints( return nil, err } - _, err = s.verifyUserInAccount(ctx, connection.Msg.Connection.AccountId) - if err != nil { - return nil, err - } - schemaResp, err := s.getConnectionSchema(ctx, connection.Msg.Connection, &schemaOpts{}) if err != nil { return nil, err @@ -1277,10 +1249,6 @@ func (s *Service) GetAiGeneratedData( return nil, err } aiconnection := aiconnectionResp.Msg.GetConnection() - _, err = s.verifyUserInAccount(ctx, aiconnection.GetAccountId()) - if err != nil { - return nil, err - } dbconnectionResp, err := s.connectionService.GetConnection(ctx, connect.NewRequest(&mgmtv1alpha1.GetConnectionRequest{ Id: req.Msg.GetDataConnectionId(), @@ -1372,11 +1340,6 @@ func (s *Service) GetConnectionTableConstraints( return nil, err } - _, err = s.verifyUserInAccount(ctx, connection.Msg.Connection.AccountId) - if err != nil { - return nil, err - } - schemaResp, err := s.getConnectionSchema(ctx, connection.Msg.Connection, &schemaOpts{}) if err != nil { return nil, err @@ -1458,11 +1421,6 @@ func (s *Service) GetTableRowCount( return nil, err } - _, err = s.verifyUserInAccount(ctx, connection.Msg.Connection.AccountId) - if err != nil { - return nil, err - } - switch connection.Msg.GetConnection().GetConnectionConfig().Config.(type) { case *mgmtv1alpha1.ConnectionConfig_PgConfig, *mgmtv1alpha1.ConnectionConfig_MysqlConfig, *mgmtv1alpha1.ConnectionConfig_MssqlConfig: db, err := s.sqlmanager.NewSqlConnection(ctx, connectionmanager.NewUniqueSession(), connection.Msg.GetConnection(), logger) diff --git a/backend/services/mgmt/v1alpha1/connection-data-service/connection-data_test.go b/backend/services/mgmt/v1alpha1/connection-data-service/connection-data_test.go index b58b1c3165..fc6b15f9e1 100644 --- a/backend/services/mgmt/v1alpha1/connection-data-service/connection-data_test.go +++ b/backend/services/mgmt/v1alpha1/connection-data-service/connection-data_test.go @@ -60,7 +60,6 @@ func Test_GetConnectionSchema_AwsS3(t *testing.T) { mockKey := "workflows/7c54e1ce-3924-477c-bfa8-ab8bd36cfee2-2023-12-21T22:02:35Z/activities/public.regions/data/228.txt.gz" path := fmt.Sprintf("workflows/%s/activities/", mockJobRunId) connection := getConnectionMock(mockAccountId, mockConnectionName, mockConnectionId, AwsS3Mock) - mockIsUserInAccount(m.UserAccountServiceMock, true) m.ConnectionServiceMock.On("GetConnection", mock.Anything, mock.Anything).Return(connect.NewResponse(&mgmtv1alpha1.GetConnectionResponse{ Connection: connection, }), nil) @@ -138,7 +137,6 @@ func Test_GetConnectionSchema_Postgres(t *testing.T) { m.DbMock.On("Close").Return(nil) connection := getConnectionMock(mockAccountId, mockConnectionName, mockConnectionId, PostgresMock) - mockIsUserInAccount(m.UserAccountServiceMock, true) m.ConnectionServiceMock.On("GetConnection", mock.Anything, mock.Anything).Return(connect.NewResponse(&mgmtv1alpha1.GetConnectionResponse{ Connection: connection, }), nil) @@ -187,7 +185,6 @@ func Test_GetConnectionSchema_Mysql(t *testing.T) { } connection := getConnectionMock(mockAccountId, mockConnectionName, mockConnectionId, MysqlMock) - mockIsUserInAccount(m.UserAccountServiceMock, true) m.ConnectionServiceMock.On("GetConnection", mock.Anything, mock.Anything).Return(connect.NewResponse(&mgmtv1alpha1.GetConnectionResponse{ Connection: connection, }), nil) @@ -217,7 +214,6 @@ func Test_GetConnectionSchema_NoRows(t *testing.T) { defer m.SqlDbMock.Close() connection := getConnectionMock(mockAccountId, mockConnectionName, mockConnectionId, MysqlMock) - mockIsUserInAccount(m.UserAccountServiceMock, true) m.ConnectionServiceMock.On("GetConnection", mock.Anything, mock.Anything).Return(connect.NewResponse(&mgmtv1alpha1.GetConnectionResponse{ Connection: connection, }), nil) @@ -244,7 +240,6 @@ func Test_GetConnectionSchema_Error(t *testing.T) { defer m.SqlDbMock.Close() connection := getConnectionMock(mockAccountId, mockConnectionName, mockConnectionId, MysqlMock) - mockIsUserInAccount(m.UserAccountServiceMock, true) m.ConnectionServiceMock.On("GetConnection", mock.Anything, mock.Anything).Return(connect.NewResponse(&mgmtv1alpha1.GetConnectionResponse{ Connection: connection, }), nil) @@ -270,7 +265,6 @@ func Test_GetConnectionForeignConstraints_Mysql(t *testing.T) { defer m.SqlDbMock.Close() connection := getConnectionMock(mockAccountId, mockConnectionName, mockConnectionId, MysqlMock) - mockIsUserInAccount(m.UserAccountServiceMock, true) m.ConnectionServiceMock.On("GetConnection", mock.Anything, mock.Anything).Return(connect.NewResponse(&mgmtv1alpha1.GetConnectionResponse{ Connection: connection, }), nil) @@ -321,7 +315,6 @@ func Test_GetConnectionForeignConstraints_Postgres(t *testing.T) { ) m.DbMock.On("Close").Return(nil) connection := getConnectionMock(mockAccountId, mockConnectionName, mockConnectionId, PostgresMock) - mockIsUserInAccount(m.UserAccountServiceMock, true) m.ConnectionServiceMock.On("GetConnection", mock.Anything, mock.Anything).Return(connect.NewResponse(&mgmtv1alpha1.GetConnectionResponse{ Connection: connection, }), nil) @@ -366,7 +359,6 @@ func Test_GetConnectionPrimaryConstraints_Mysql(t *testing.T) { defer m.SqlDbMock.Close() connection := getConnectionMock(mockAccountId, mockConnectionName, mockConnectionId, MysqlMock) - mockIsUserInAccount(m.UserAccountServiceMock, true) m.ConnectionServiceMock.On("GetConnection", mock.Anything, mock.Anything).Return(connect.NewResponse(&mgmtv1alpha1.GetConnectionResponse{ Connection: connection, }), nil) @@ -409,7 +401,6 @@ func Test_GetConnectionPrimaryConstraints_Postgres(t *testing.T) { defer m.SqlDbMock.Close() connection := getConnectionMock(mockAccountId, mockConnectionName, mockConnectionId, PostgresMock) - mockIsUserInAccount(m.UserAccountServiceMock, true) m.ConnectionServiceMock.On("GetConnection", mock.Anything, mock.Anything).Return(connect.NewResponse(&mgmtv1alpha1.GetConnectionResponse{ Connection: connection, }), nil) @@ -453,7 +444,6 @@ func Test_GetConnectionInitStatements_Mysql_Create(t *testing.T) { defer m.SqlDbMock.Close() connection := getConnectionMock(mockAccountId, mockConnectionName, mockConnectionId, MysqlMock) - mockIsUserInAccount(m.UserAccountServiceMock, true) m.ConnectionServiceMock.On("GetConnection", mock.Anything, mock.Anything).Return(connect.NewResponse(&mgmtv1alpha1.GetConnectionResponse{ Connection: connection, }), nil) @@ -506,7 +496,6 @@ func Test_GetConnectionInitStatements_Mysql_Truncate(t *testing.T) { defer m.SqlDbMock.Close() connection := getConnectionMock(mockAccountId, mockConnectionName, mockConnectionId, MysqlMock) - mockIsUserInAccount(m.UserAccountServiceMock, true) m.ConnectionServiceMock.On("GetConnection", mock.Anything, mock.Anything).Return(connect.NewResponse(&mgmtv1alpha1.GetConnectionResponse{ Connection: connection, }), nil) @@ -553,7 +542,6 @@ func Test_GetConnectionInitStatements_Postgres_Create(t *testing.T) { ) m.DbMock.On("Close").Return(nil) connection := getConnectionMock(mockAccountId, mockConnectionName, mockConnectionId, PostgresMock) - mockIsUserInAccount(m.UserAccountServiceMock, true) m.ConnectionServiceMock.On("GetConnection", mock.Anything, mock.Anything).Return(connect.NewResponse(&mgmtv1alpha1.GetConnectionResponse{ Connection: connection, }), nil) @@ -607,7 +595,6 @@ func Test_GetConnectionInitStatements_Postgres_Truncate(t *testing.T) { ) m.DbMock.On("Close").Return(nil) connection := getConnectionMock(mockAccountId, mockConnectionName, mockConnectionId, PostgresMock) - mockIsUserInAccount(m.UserAccountServiceMock, true) m.ConnectionServiceMock.On("GetConnection", mock.Anything, mock.Anything).Return(connect.NewResponse(&mgmtv1alpha1.GetConnectionResponse{ Connection: connection, }), nil) @@ -644,29 +631,27 @@ func Test_GetConnectionInitStatements_Postgres_Truncate(t *testing.T) { } type serviceMocks struct { - Service *Service - DbtxMock *neosyncdb.MockDBTX - QuerierMock *db_queries.MockQuerier - UserAccountServiceMock *mgmtv1alpha1connect.MockUserAccountServiceClient - ConnectionServiceMock *mgmtv1alpha1connect.MockConnectionServiceClient - JobServiceMock *mgmtv1alpha1connect.MockJobServiceHandler - SqlMock sqlmock.Sqlmock - SqlDbMock *sql.DB - SqlDbContainerMock *sqlconnect.MockSqlDbContainer - PgQueierMock *pg_queries.MockQuerier - MysqlQueierMock *mysql_queries.MockQuerier - SqlConnectorMock *sqlconnect.MockSqlConnector - AwsManagerMock *awsmanager.MockNeosyncAwsManagerClient - MongoConnectorMock *mongoconnect.MockInterface - SqlManagerMock *sqlmanager.MockSqlManagerClient - DbMock *sqlmanager.MockSqlDatabase - GcpManagerMock *neosync_gcp.MockManagerInterface + Service *Service + DbtxMock *neosyncdb.MockDBTX + QuerierMock *db_queries.MockQuerier + ConnectionServiceMock *mgmtv1alpha1connect.MockConnectionServiceClient + JobServiceMock *mgmtv1alpha1connect.MockJobServiceHandler + SqlMock sqlmock.Sqlmock + SqlDbMock *sql.DB + SqlDbContainerMock *sqlconnect.MockSqlDbContainer + PgQueierMock *pg_queries.MockQuerier + MysqlQueierMock *mysql_queries.MockQuerier + SqlConnectorMock *sqlconnect.MockSqlConnector + AwsManagerMock *awsmanager.MockNeosyncAwsManagerClient + MongoConnectorMock *mongoconnect.MockInterface + SqlManagerMock *sqlmanager.MockSqlManagerClient + DbMock *sqlmanager.MockSqlDatabase + GcpManagerMock *neosync_gcp.MockManagerInterface } func createServiceMock(t *testing.T) *serviceMocks { mockDbtx := neosyncdb.NewMockDBTX(t) mockQuerier := db_queries.NewMockQuerier(t) - mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) mockConnectionService := mgmtv1alpha1connect.NewMockConnectionServiceClient(t) mockJobService := mgmtv1alpha1connect.NewMockJobServiceHandler(t) mockPgquerier := pg_queries.NewMockQuerier(t) @@ -685,7 +670,6 @@ func createServiceMock(t *testing.T) *serviceMocks { service := New( &Config{}, - mockUserAccountService, mockConnectionService, mockJobService, mockAwsManager, @@ -698,32 +682,25 @@ func createServiceMock(t *testing.T) *serviceMocks { ) return &serviceMocks{ - Service: service, - DbtxMock: mockDbtx, - QuerierMock: mockQuerier, - UserAccountServiceMock: mockUserAccountService, - ConnectionServiceMock: mockConnectionService, - JobServiceMock: mockJobService, - SqlMock: sqlMock, - SqlDbMock: sqlDbMock, - SqlDbContainerMock: sqlconnect.NewMockSqlDbContainer(t), - PgQueierMock: mockPgquerier, - MysqlQueierMock: mockMysqlquerier, - SqlConnectorMock: mockSqlConnector, - AwsManagerMock: mockAwsManager, - MongoConnectorMock: mockMongoConnector, - SqlManagerMock: mockSqlManager, - DbMock: mockSqlDb, - GcpManagerMock: mockGcpManager, + Service: service, + DbtxMock: mockDbtx, + QuerierMock: mockQuerier, + ConnectionServiceMock: mockConnectionService, + JobServiceMock: mockJobService, + SqlMock: sqlMock, + SqlDbMock: sqlDbMock, + SqlDbContainerMock: sqlconnect.NewMockSqlDbContainer(t), + PgQueierMock: mockPgquerier, + MysqlQueierMock: mockMysqlquerier, + SqlConnectorMock: mockSqlConnector, + AwsManagerMock: mockAwsManager, + MongoConnectorMock: mockMongoConnector, + SqlManagerMock: mockSqlManager, + DbMock: mockSqlDb, + GcpManagerMock: mockGcpManager, } } -func mockIsUserInAccount(userAccountServiceMock *mgmtv1alpha1connect.MockUserAccountServiceClient, isInAccount bool) { //nolint - userAccountServiceMock.On("IsUserInAccount", mock.Anything, mock.Anything).Return(connect.NewResponse(&mgmtv1alpha1.IsUserInAccountResponse{ - Ok: isInAccount, - }), nil) -} - //nolint:all func getConnectionMock(accountId, name string, id string, connType ConnTypeMock) *mgmtv1alpha1.Connection { timestamp := timestamppb.New(time.Now()) @@ -909,7 +886,6 @@ func Test_GetConnectionUniqueConstraints_Mysql(t *testing.T) { defer m.SqlDbMock.Close() connection := getConnectionMock(mockAccountId, mockConnectionName, mockConnectionId, MysqlMock) - mockIsUserInAccount(m.UserAccountServiceMock, true) m.ConnectionServiceMock.On("GetConnection", mock.Anything, mock.Anything).Return(connect.NewResponse(&mgmtv1alpha1.GetConnectionResponse{ Connection: connection, }), nil) @@ -954,7 +930,6 @@ func Test_GetConnectionUniqueConstraints_Postgres(t *testing.T) { defer m.SqlDbMock.Close() connection := getConnectionMock(mockAccountId, mockConnectionName, mockConnectionId, PostgresMock) - mockIsUserInAccount(m.UserAccountServiceMock, true) m.ConnectionServiceMock.On("GetConnection", mock.Anything, mock.Anything).Return(connect.NewResponse(&mgmtv1alpha1.GetConnectionResponse{ Connection: connection, }), nil) diff --git a/backend/services/mgmt/v1alpha1/connection-data-service/service.go b/backend/services/mgmt/v1alpha1/connection-data-service/service.go index a7f225638c..bc7ca36713 100644 --- a/backend/services/mgmt/v1alpha1/connection-data-service/service.go +++ b/backend/services/mgmt/v1alpha1/connection-data-service/service.go @@ -12,10 +12,9 @@ import ( ) type Service struct { - cfg *Config - useraccountService mgmtv1alpha1connect.UserAccountServiceClient - connectionService mgmtv1alpha1connect.ConnectionServiceClient - jobService mgmtv1alpha1connect.JobServiceHandler + cfg *Config + connectionService mgmtv1alpha1connect.ConnectionServiceClient + jobService mgmtv1alpha1connect.JobServiceHandler awsManager awsmanager.NeosyncAwsManagerClient @@ -33,7 +32,6 @@ type Config struct { func New( cfg *Config, - useraccountService mgmtv1alpha1connect.UserAccountServiceClient, connectionService mgmtv1alpha1connect.ConnectionServiceClient, jobService mgmtv1alpha1connect.JobServiceHandler, @@ -47,16 +45,15 @@ func New( gcpmanager neosync_gcp.ManagerInterface, ) *Service { return &Service{ - cfg: cfg, - useraccountService: useraccountService, - connectionService: connectionService, - jobService: jobService, - awsManager: awsManager, - sqlConnector: sqlConnector, - pgquerier: pgquerier, - mysqlquerier: mysqlquerier, - sqlmanager: sqlmanager, - mongoconnector: mongoconnector, - gcpmanager: gcpmanager, + cfg: cfg, + connectionService: connectionService, + jobService: jobService, + awsManager: awsManager, + sqlConnector: sqlConnector, + pgquerier: pgquerier, + mysqlquerier: mysqlquerier, + sqlmanager: sqlmanager, + mongoconnector: mongoconnector, + gcpmanager: gcpmanager, } } diff --git a/backend/services/mgmt/v1alpha1/connection-data-service/user-account.go b/backend/services/mgmt/v1alpha1/connection-data-service/user-account.go deleted file mode 100644 index 5ade83d05a..0000000000 --- a/backend/services/mgmt/v1alpha1/connection-data-service/user-account.go +++ /dev/null @@ -1,43 +0,0 @@ -package v1alpha1_connectiondataservice - -import ( - "context" - - "connectrpc.com/connect" - "github.com/jackc/pgx/v5/pgtype" - mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" - "github.com/nucleuscloud/neosync/backend/internal/apikey" - auth_apikey "github.com/nucleuscloud/neosync/backend/internal/auth/apikey" - nucleuserrors "github.com/nucleuscloud/neosync/backend/internal/errors" - "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" -) - -func (s *Service) verifyUserInAccount( - ctx context.Context, - accountId string, -) (*pgtype.UUID, error) { //nolint:unparam - accountUuid, err := neosyncdb.ToUuid(accountId) - if err != nil { - return nil, err - } - if isWorkerApiKey(ctx) { - return &accountUuid, nil - } - resp, err := s.useraccountService.IsUserInAccount(ctx, connect.NewRequest(&mgmtv1alpha1.IsUserInAccountRequest{AccountId: accountId})) - if err != nil { - return nil, err - } - if !resp.Msg.Ok { - return nil, nucleuserrors.NewForbidden("user in not in requested account") - } - - return &accountUuid, nil -} - -func isWorkerApiKey(ctx context.Context) bool { - data, err := auth_apikey.GetTokenDataFromCtx(ctx) - if err != nil { - return false - } - return data.ApiKeyType == apikey.WorkerApiKey -} diff --git a/backend/services/mgmt/v1alpha1/connection-service/connection.go b/backend/services/mgmt/v1alpha1/connection-service/connection.go index c3180332e7..1ff26c4033 100644 --- a/backend/services/mgmt/v1alpha1/connection-service/connection.go +++ b/backend/services/mgmt/v1alpha1/connection-service/connection.go @@ -16,8 +16,10 @@ import ( mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" logger_interceptor "github.com/nucleuscloud/neosync/backend/internal/connect/interceptors/logger" "github.com/nucleuscloud/neosync/backend/internal/dtomaps" + "github.com/nucleuscloud/neosync/backend/internal/ee/rbac" nucleuserrors "github.com/nucleuscloud/neosync/backend/internal/errors" "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" + "github.com/nucleuscloud/neosync/backend/internal/userdata" dbconnectconfig "github.com/nucleuscloud/neosync/backend/pkg/dbconnect-config" "github.com/nucleuscloud/neosync/backend/pkg/sqlconnect" pg_models "github.com/nucleuscloud/neosync/backend/sql/postgresql/models" @@ -237,13 +239,20 @@ func (s *Service) IsConnectionNameAvailable( ctx context.Context, req *connect.Request[mgmtv1alpha1.IsConnectionNameAvailableRequest], ) (*connect.Response[mgmtv1alpha1.IsConnectionNameAvailableResponse], error) { - accountUuid, err := s.verifyUserInAccount(ctx, req.Msg.AccountId) + user, err := s.userclient.GetUser(ctx) if err != nil { return nil, err } + accountUuid, err := neosyncdb.ToUuid(req.Msg.GetAccountId()) + if err != nil { + return nil, err + } + if err := user.EnforceConnection(ctx, userdata.NewWildcardDomainEntity(req.Msg.GetAccountId()), rbac.ConnectionAction_View); err != nil { + return nil, err + } count, err := s.db.Q.IsConnectionNameAvailable(ctx, s.db.Db, db_queries.IsConnectionNameAvailableParams{ - AccountId: *accountUuid, + AccountId: accountUuid, ConnectionName: req.Msg.ConnectionName, }) if err != nil { @@ -259,12 +268,19 @@ func (s *Service) GetConnections( ctx context.Context, req *connect.Request[mgmtv1alpha1.GetConnectionsRequest], ) (*connect.Response[mgmtv1alpha1.GetConnectionsResponse], error) { - accountUuid, err := s.verifyUserInAccount(ctx, req.Msg.AccountId) + user, err := s.userclient.GetUser(ctx) + if err != nil { + return nil, err + } + if err := user.EnforceConnection(ctx, userdata.NewWildcardDomainEntity(req.Msg.GetAccountId()), rbac.ConnectionAction_View); err != nil { + return nil, err + } + accountUuid, err := neosyncdb.ToUuid(req.Msg.GetAccountId()) if err != nil { return nil, err } - connections, err := s.db.Q.GetConnectionsByAccount(ctx, s.db.Db, *accountUuid) + connections, err := s.db.Q.GetConnectionsByAccount(ctx, s.db.Db, accountUuid) if err != nil { return nil, err } @@ -300,14 +316,22 @@ func (s *Service) GetConnection( return nil, nucleuserrors.NewNotFound("unable to find connection by id") } - _, err = s.verifyUserInAccount(ctx, neosyncdb.UUIDString(connection.AccountID)) + dto, err := dtomaps.ToConnectionDto(&connection) if err != nil { return nil, err } - dto, err := dtomaps.ToConnectionDto(&connection) + + user, err := s.userclient.GetUser(ctx) if err != nil { return nil, err } + if err := user.EnforceConnection(ctx, userdata.NewDbDomainEntity(connection.AccountID, connection.ID), rbac.ConnectionAction_View); err != nil { + return nil, err + } + + if err := user.EnforceConnection(ctx, dto, rbac.ConnectionAction_View); err != nil { + return nil, err + } return connect.NewResponse(&mgmtv1alpha1.GetConnectionResponse{ Connection: dto, }), nil @@ -322,22 +346,24 @@ func (s *Service) CreateConnection( return nil, err } - accountUuid, err := s.verifyUserInAccount(ctx, req.Msg.AccountId) + user, err := s.userclient.GetUser(ctx) if err != nil { return nil, err } - - userUuid, err := s.getUserUuid(ctx) + accountUuid, err := neosyncdb.ToUuid(req.Msg.GetAccountId()) if err != nil { return nil, err } + if err := user.EnforceConnection(ctx, userdata.NewWildcardDomainEntity(req.Msg.GetAccountId()), rbac.ConnectionAction_Create); err != nil { + return nil, err + } connection, err := s.db.Q.CreateConnection(ctx, s.db.Db, db_queries.CreateConnectionParams{ - AccountID: *accountUuid, + AccountID: accountUuid, Name: req.Msg.Name, ConnectionConfig: cc, - CreatedByID: *userUuid, - UpdatedByID: *userUuid, + CreatedByID: user.PgId(), + UpdatedByID: user.PgId(), }) if err != nil { return nil, err @@ -366,13 +392,12 @@ func (s *Service) UpdateConnection( return nil, nucleuserrors.NewNotFound("unable to find connection by id") } - _, err = s.verifyUserInAccount(ctx, neosyncdb.UUIDString(connection.AccountID)) + user, err := s.userclient.GetUser(ctx) if err != nil { return nil, err } - userUuid, err := s.getUserUuid(ctx) - if err != nil { + if err := user.EnforceConnection(ctx, userdata.NewDbDomainEntity(connection.AccountID, connection.ID), rbac.ConnectionAction_Edit); err != nil { return nil, err } @@ -384,7 +409,7 @@ func (s *Service) UpdateConnection( connection, err = s.db.Q.UpdateConnection(ctx, s.db.Db, db_queries.UpdateConnectionParams{ ID: connection.ID, ConnectionConfig: cc, - UpdatedByID: *userUuid, + UpdatedByID: user.PgId(), Name: req.Msg.Name, }) if err != nil { @@ -415,11 +440,15 @@ func (s *Service) DeleteConnection( return connect.NewResponse(&mgmtv1alpha1.DeleteConnectionResponse{}), nil } - _, err = s.verifyUserInAccount(ctx, neosyncdb.UUIDString(connection.AccountID)) + user, err := s.userclient.GetUser(ctx) if err != nil { return nil, err } + if err := user.EnforceConnection(ctx, userdata.NewDbDomainEntity(connection.AccountID, connection.ID), rbac.ConnectionAction_Delete); err != nil { + return nil, err + } + err = s.db.Q.RemoveConnectionById(ctx, s.db.Db, connection.ID) if err != nil { return nil, err diff --git a/backend/services/mgmt/v1alpha1/connection-service/service.go b/backend/services/mgmt/v1alpha1/connection-service/service.go index 46dfd9170a..c731536196 100644 --- a/backend/services/mgmt/v1alpha1/connection-service/service.go +++ b/backend/services/mgmt/v1alpha1/connection-service/service.go @@ -1,8 +1,8 @@ package v1alpha1_connectionservice import ( - "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect" "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" + "github.com/nucleuscloud/neosync/backend/internal/userdata" "github.com/nucleuscloud/neosync/backend/pkg/mongoconnect" "github.com/nucleuscloud/neosync/backend/pkg/sqlconnect" sql_manager "github.com/nucleuscloud/neosync/backend/pkg/sqlmanager" @@ -10,13 +10,13 @@ import ( ) type Service struct { - cfg *Config - db *neosyncdb.NeosyncDb - useraccountService mgmtv1alpha1connect.UserAccountServiceClient - sqlConnector sqlconnect.SqlConnector - sqlmanager sql_manager.SqlManagerClient - mongoconnector mongoconnect.Interface - awsManager awsmanager.NeosyncAwsManagerClient + cfg *Config + db *neosyncdb.NeosyncDb + userclient userdata.Interface + sqlConnector sqlconnect.SqlConnector + sqlmanager sql_manager.SqlManagerClient + mongoconnector mongoconnect.Interface + awsManager awsmanager.NeosyncAwsManagerClient } type Config struct { @@ -25,19 +25,19 @@ type Config struct { func New( cfg *Config, db *neosyncdb.NeosyncDb, - useraccountService mgmtv1alpha1connect.UserAccountServiceClient, + userclient userdata.Interface, mongoconnector mongoconnect.Interface, awsManager awsmanager.NeosyncAwsManagerClient, sqlmanager sql_manager.SqlManagerClient, sqlconnector sqlconnect.SqlConnector, ) *Service { return &Service{ - cfg: cfg, - db: db, - useraccountService: useraccountService, - sqlmanager: sqlmanager, - mongoconnector: mongoconnector, - awsManager: awsManager, - sqlConnector: sqlconnector, + cfg: cfg, + db: db, + userclient: userclient, + sqlmanager: sqlmanager, + mongoconnector: mongoconnector, + awsManager: awsManager, + sqlConnector: sqlconnector, } } diff --git a/backend/services/mgmt/v1alpha1/connection-service/user-account.go b/backend/services/mgmt/v1alpha1/connection-service/user-account.go deleted file mode 100644 index 14637f6e05..0000000000 --- a/backend/services/mgmt/v1alpha1/connection-service/user-account.go +++ /dev/null @@ -1,59 +0,0 @@ -package v1alpha1_connectionservice - -import ( - "context" - - "connectrpc.com/connect" - "github.com/jackc/pgx/v5/pgtype" - mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" - "github.com/nucleuscloud/neosync/backend/internal/apikey" - auth_apikey "github.com/nucleuscloud/neosync/backend/internal/auth/apikey" - nucleuserrors "github.com/nucleuscloud/neosync/backend/internal/errors" - "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" -) - -func (s *Service) verifyUserInAccount( - ctx context.Context, - accountId string, -) (*pgtype.UUID, error) { - accountUuid, err := neosyncdb.ToUuid(accountId) - if err != nil { - return nil, err - } - - if isWorkerApiKey(ctx) { - return &accountUuid, nil - } - - resp, err := s.useraccountService.IsUserInAccount(ctx, connect.NewRequest(&mgmtv1alpha1.IsUserInAccountRequest{AccountId: accountId})) - if err != nil { - return nil, err - } - if !resp.Msg.Ok { - return nil, nucleuserrors.NewForbidden("user in not in requested account") - } - - return &accountUuid, nil -} - -func (s *Service) getUserUuid( - ctx context.Context, -) (*pgtype.UUID, error) { - user, err := s.useraccountService.GetUser(ctx, connect.NewRequest(&mgmtv1alpha1.GetUserRequest{})) - if err != nil { - return nil, err - } - userUuid, err := neosyncdb.ToUuid(user.Msg.UserId) - if err != nil { - return nil, err - } - return &userUuid, nil -} - -func isWorkerApiKey(ctx context.Context) bool { - data, err := auth_apikey.GetTokenDataFromCtx(ctx) - if err != nil { - return false - } - return data.ApiKeyType == apikey.WorkerApiKey -} diff --git a/backend/services/mgmt/v1alpha1/integration_tests/integration_test.go b/backend/services/mgmt/v1alpha1/integration_tests/integration_test.go index fdde39e529..1fa360193b 100644 --- a/backend/services/mgmt/v1alpha1/integration_tests/integration_test.go +++ b/backend/services/mgmt/v1alpha1/integration_tests/integration_test.go @@ -20,7 +20,7 @@ func (s *IntegrationTestSuite) SetupSuite() { s.ctx = context.Background() api, err := tcneosyncapi.NewNeosyncApiTestClient(s.ctx, s.T()) if err != nil { - panic(err) + s.T().Fatalf("unable to create neosync api test client: %v", err) } s.NeosyncApiTestClient = *api } @@ -29,21 +29,21 @@ func (s *IntegrationTestSuite) SetupSuite() { func (s *IntegrationTestSuite) SetupTest() { err := s.InitializeTest(s.ctx, s.T()) if err != nil { - panic(err) + s.T().Fatalf("unable to initialize test: %v", err) } } func (s *IntegrationTestSuite) TearDownTest() { err := s.CleanupTest(s.ctx) if err != nil { - panic(err) + s.T().Fatalf("unable to cleanup test: %v", err) } } func (s *IntegrationTestSuite) TearDownSuite() { err := s.TearDown(s.ctx) if err != nil { - panic(err) + s.T().Fatalf("unable to cleanup test: %v", err) } } diff --git a/backend/services/mgmt/v1alpha1/job-service/jobs.go b/backend/services/mgmt/v1alpha1/job-service/jobs.go index 200a3c2bd3..08c51a6618 100644 --- a/backend/services/mgmt/v1alpha1/job-service/jobs.go +++ b/backend/services/mgmt/v1alpha1/job-service/jobs.go @@ -14,8 +14,10 @@ import ( mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" logger_interceptor "github.com/nucleuscloud/neosync/backend/internal/connect/interceptors/logger" "github.com/nucleuscloud/neosync/backend/internal/dtomaps" + "github.com/nucleuscloud/neosync/backend/internal/ee/rbac" nucleuserrors "github.com/nucleuscloud/neosync/backend/internal/errors" "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" + "github.com/nucleuscloud/neosync/backend/internal/userdata" "github.com/nucleuscloud/neosync/backend/internal/utils" sqlmanager_shared "github.com/nucleuscloud/neosync/backend/pkg/sqlmanager/shared" tabledependency "github.com/nucleuscloud/neosync/backend/pkg/table-dependency" @@ -37,12 +39,21 @@ func (s *Service) GetJobs( ) (*connect.Response[mgmtv1alpha1.GetJobsResponse], error) { logger := logger_interceptor.GetLoggerFromContextOrDefault(ctx) - accountUuid, err := s.verifyUserInAccount(ctx, req.Msg.GetAccountId()) + user, err := s.userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + err = user.EnforceJob(ctx, userdata.NewWildcardDomainEntity(req.Msg.GetAccountId()), rbac.JobAction_View) if err != nil { return nil, err } - jobs, err := s.db.Q.GetJobsByAccount(ctx, s.db.Db, *accountUuid) + accountUuid, err := neosyncdb.ToUuid(req.Msg.GetAccountId()) + if err != nil { + return nil, err + } + + jobs, err := s.db.Q.GetJobsByAccount(ctx, s.db.Db, accountUuid) if err != nil && !neosyncdb.IsNoRows(err) { return nil, fmt.Errorf("unable to get jobs by account: %w", err) } else if err != nil && neosyncdb.IsNoRows(err) { @@ -99,7 +110,11 @@ func (s *Service) GetJob( ctx context.Context, req *connect.Request[mgmtv1alpha1.GetJobRequest], ) (*connect.Response[mgmtv1alpha1.GetJobResponse], error) { - jobUuid, err := neosyncdb.ToUuid(req.Msg.Id) + user, err := s.userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + jobUuid, err := neosyncdb.ToUuid(req.Msg.GetId()) if err != nil { return nil, err } @@ -132,8 +147,7 @@ func (s *Service) GetJob( return nil, err } - _, err = s.verifyUserInAccount(ctx, neosyncdb.UUIDString(job.AccountID)) - if err != nil { + if err := user.EnforceJob(ctx, userdata.NewDbDomainEntity(job.AccountID, job.ID), rbac.JobAction_View); err != nil { return nil, err } @@ -147,24 +161,13 @@ func (s *Service) GetJobStatus( req *connect.Request[mgmtv1alpha1.GetJobStatusRequest], ) (*connect.Response[mgmtv1alpha1.GetJobStatusResponse], error) { logger := logger_interceptor.GetLoggerFromContextOrDefault(ctx) - logger = logger.With("jobId", req.Msg.JobId) - jobUuid, err := neosyncdb.ToUuid(req.Msg.JobId) - if err != nil { - return nil, err - } - job, err := s.db.Q.GetJobById(ctx, s.db.Db, jobUuid) - if err != nil && !neosyncdb.IsNoRows(err) { - return nil, fmt.Errorf("unable to get job by id: %w", err) - } else if err != nil && neosyncdb.IsNoRows(err) { - return nil, nucleuserrors.NewNotFound("job with that id does not exist") - } - - _, err = s.verifyUserInAccount(ctx, neosyncdb.UUIDString(job.AccountID)) + logger = logger.With("jobId", req.Msg.GetJobId()) + jobResp, err := s.GetJob(ctx, connect.NewRequest(&mgmtv1alpha1.GetJobRequest{Id: req.Msg.GetJobId()})) if err != nil { return nil, err } - schedule, err := s.temporalmgr.DescribeSchedule(ctx, neosyncdb.UUIDString(job.AccountID), neosyncdb.UUIDString(job.ID), logger) + schedule, err := s.temporalmgr.DescribeSchedule(ctx, jobResp.Msg.GetJob().GetAccountId(), jobResp.Msg.GetJob().GetId(), logger) if err != nil { return nil, fmt.Errorf("unable to describe temporal schedule when retrieving job status: %w", err) } @@ -180,11 +183,21 @@ func (s *Service) GetJobStatuses( ) (*connect.Response[mgmtv1alpha1.GetJobStatusesResponse], error) { logger := logger_interceptor.GetLoggerFromContextOrDefault(ctx) - accountUuid, err := s.verifyUserInAccount(ctx, req.Msg.GetAccountId()) + user, err := s.userdataclient.GetUser(ctx) if err != nil { return nil, err } - jobs, err := s.db.Q.GetJobsByAccount(ctx, s.db.Db, *accountUuid) + err = user.EnforceJob(ctx, userdata.NewWildcardDomainEntity(req.Msg.GetAccountId()), rbac.JobAction_View) + if err != nil { + return nil, err + } + + accountUuid, err := neosyncdb.ToUuid(req.Msg.GetAccountId()) + if err != nil { + return nil, err + } + + jobs, err := s.db.Q.GetJobsByAccount(ctx, s.db.Db, accountUuid) if err != nil && !neosyncdb.IsNoRows(err) { return nil, fmt.Errorf("unable to get jobs by account: %w", err) } else if err != nil && neosyncdb.IsNoRows(err) { @@ -229,23 +242,13 @@ func (s *Service) GetJobRecentRuns( ) (*connect.Response[mgmtv1alpha1.GetJobRecentRunsResponse], error) { logger := logger_interceptor.GetLoggerFromContextOrDefault(ctx) logger = logger.With("jobId", req.Msg.JobId) - jobUuid, err := neosyncdb.ToUuid(req.Msg.JobId) - if err != nil { - return nil, err - } - job, err := s.db.Q.GetJobById(ctx, s.db.Db, jobUuid) - if err != nil && !neosyncdb.IsNoRows(err) { - return nil, fmt.Errorf("unable to get job by id: %w", err) - } else if err != nil && neosyncdb.IsNoRows(err) { - return nil, nucleuserrors.NewNotFound("job with that id does not exist") - } - _, err = s.verifyUserInAccount(ctx, neosyncdb.UUIDString(job.AccountID)) + jobResp, err := s.GetJob(ctx, connect.NewRequest(&mgmtv1alpha1.GetJobRequest{Id: req.Msg.GetJobId()})) if err != nil { return nil, err } - schedule, err := s.temporalmgr.DescribeSchedule(ctx, neosyncdb.UUIDString(job.AccountID), neosyncdb.UUIDString(job.ID), logger) + schedule, err := s.temporalmgr.DescribeSchedule(ctx, jobResp.Msg.GetJob().GetAccountId(), jobResp.Msg.GetJob().GetId(), logger) if err != nil { return nil, fmt.Errorf("unable to describe temporal schedule when retrieving job recent runs: %w", err) } @@ -260,24 +263,14 @@ func (s *Service) GetJobNextRuns( req *connect.Request[mgmtv1alpha1.GetJobNextRunsRequest], ) (*connect.Response[mgmtv1alpha1.GetJobNextRunsResponse], error) { logger := logger_interceptor.GetLoggerFromContextOrDefault(ctx) - logger = logger.With("jobId", req.Msg.JobId) - jobUuid, err := neosyncdb.ToUuid(req.Msg.JobId) - if err != nil { - return nil, err - } - job, err := s.db.Q.GetJobById(ctx, s.db.Db, jobUuid) - if err != nil && !neosyncdb.IsNoRows(err) { - return nil, fmt.Errorf("unable to get job by id: %w", err) - } else if err != nil && neosyncdb.IsNoRows(err) { - return nil, nucleuserrors.NewNotFound("job with that id does not exist") - } + logger = logger.With("jobId", req.Msg.GetJobId()) - _, err = s.verifyUserInAccount(ctx, neosyncdb.UUIDString(job.AccountID)) + jobResp, err := s.GetJob(ctx, connect.NewRequest(&mgmtv1alpha1.GetJobRequest{Id: req.Msg.GetJobId()})) if err != nil { return nil, err } - schedule, err := s.temporalmgr.DescribeSchedule(ctx, neosyncdb.UUIDString(job.AccountID), neosyncdb.UUIDString(job.ID), logger) + schedule, err := s.temporalmgr.DescribeSchedule(ctx, jobResp.Msg.GetJob().GetAccountId(), jobResp.Msg.GetJob().GetId(), logger) if err != nil { return nil, fmt.Errorf("unable to describe temporal schedule when retrieving job next runs: %w", err) } @@ -287,7 +280,7 @@ func (s *Service) GetJobNextRuns( }), nil } -type Destination struct { +type destination struct { ConnectionId pgtype.UUID Options *pg_models.JobDestinationOptions } @@ -299,18 +292,22 @@ func (s *Service) CreateJob( logger := logger_interceptor.GetLoggerFromContextOrDefault(ctx) logger = logger.With("jobName", req.Msg.JobName, "accountId", req.Msg.AccountId) - accountUuid, err := s.verifyUserInAccount(ctx, req.Msg.AccountId) + user, err := s.userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + err = user.EnforceJob(ctx, userdata.NewWildcardDomainEntity(req.Msg.GetAccountId()), rbac.JobAction_Create) if err != nil { return nil, err } - userUuid, err := s.getUserUuid(ctx) + accountUuid, err := neosyncdb.ToUuid(req.Msg.GetAccountId()) if err != nil { return nil, err } connectionUuids := []pgtype.UUID{} connectionIds := []string{} - destinations := []*Destination{} + destinations := []*destination{} for _, dest := range req.Msg.Destinations { destUuid, err := neosyncdb.ToUuid(dest.ConnectionId) if err != nil { @@ -321,14 +318,14 @@ func (s *Service) CreateJob( if err != nil { return nil, err } - destinations = append(destinations, &Destination{ConnectionId: destUuid, Options: options}) + destinations = append(destinations, &destination{ConnectionId: destUuid, Options: options}) connectionIds = append(connectionIds, dest.ConnectionId) connectionUuids = append(connectionUuids, destUuid) } logger.Debug("verifying connections") count, err := s.db.Q.AreConnectionsInAccount(ctx, s.db.Db, db_queries.AreConnectionsInAccountParams{ - AccountId: *accountUuid, + AccountId: accountUuid, ConnectionIds: connectionUuids, }) if err != nil { @@ -451,14 +448,14 @@ func (s *Service) CreateJob( cj, err := s.db.CreateJob(ctx, &db_queries.CreateJobParams{ Name: req.Msg.JobName, - AccountID: *accountUuid, + AccountID: accountUuid, Status: int16(mgmtv1alpha1.JobStatus_JOB_STATUS_ENABLED), CronSchedule: cronText, ConnectionOptions: connectionOptions, Mappings: mappings, VirtualForeignKeys: virtualForeignKeys, - CreatedByID: *userUuid, - UpdatedByID: *userUuid, + CreatedByID: user.PgId(), + UpdatedByID: user.PgId(), WorkflowOptions: workflowOptions, SyncOptions: activitySyncOptions, }, connDestParams) @@ -552,7 +549,11 @@ func (s *Service) DeleteJob( return connect.NewResponse(&mgmtv1alpha1.DeleteJobResponse{}), nil } - _, err = s.verifyUserInAccount(ctx, neosyncdb.UUIDString(job.AccountID)) + user, err := s.userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + err = user.EnforceJob(ctx, userdata.NewDbDomainEntity(job.AccountID, job.ID), rbac.JobAction_Delete) if err != nil { return nil, err } @@ -593,19 +594,22 @@ func (s *Service) CreateJobDestinationConnections( if err != nil { return nil, err } - accountUuid, err := s.verifyUserInAccount(ctx, job.Msg.Job.AccountId) + user, err := s.userdataclient.GetUser(ctx) if err != nil { return nil, err } - userUuid, err := s.getUserUuid(ctx) + err = user.EnforceJob(ctx, job.Msg.GetJob(), rbac.JobAction_Create) + if err != nil { + return nil, err + } + accountUuid, err := neosyncdb.ToUuid(job.Msg.GetJob().GetAccountId()) if err != nil { return nil, err } - logger = logger.With("userId", userUuid) connectionIds := []string{} connectionUuids := []pgtype.UUID{} - destinations := []*Destination{} + destinations := []*destination{} for _, dest := range req.Msg.Destinations { destUuid, err := neosyncdb.ToUuid(dest.ConnectionId) if err != nil { @@ -616,7 +620,7 @@ func (s *Service) CreateJobDestinationConnections( if err != nil { return nil, err } - destinations = append(destinations, &Destination{ConnectionId: destUuid, Options: options}) + destinations = append(destinations, &destination{ConnectionId: destUuid, Options: options}) connectionIds = append(connectionIds, dest.ConnectionId) connectionUuids = append(connectionUuids, destUuid) } @@ -625,7 +629,7 @@ func (s *Service) CreateJobDestinationConnections( return nil, nucleuserrors.NewBadRequest("connections ids are not unique") } - isInSameAccount, err := verifyConnectionsInAccount(ctx, s.db, connectionUuids, *accountUuid) + isInSameAccount, err := verifyConnectionsInAccount(ctx, s.db, connectionUuids, accountUuid) if err != nil { return nil, err } @@ -668,23 +672,20 @@ func (s *Service) UpdateJobSchedule( logger := logger_interceptor.GetLoggerFromContextOrDefault(ctx) logger = logger.With("jobId", req.Msg.Id) logger.Debug("updating job schedule") - jobUuid, err := neosyncdb.ToUuid(req.Msg.Id) + + jobResp, err := s.GetJob(ctx, connect.NewRequest(&mgmtv1alpha1.GetJobRequest{ + Id: req.Msg.Id, + })) if err != nil { return nil, err } - job, err := s.db.Q.GetJobById(ctx, s.db.Db, jobUuid) - if err != nil && !neosyncdb.IsNoRows(err) { - return nil, fmt.Errorf("unable to get job by id: %w", err) - } else if err != nil && neosyncdb.IsNoRows(err) { - return nil, nucleuserrors.NewNotFound("job with that id does not exist") - } + job := jobResp.Msg.GetJob() - _, err = s.verifyUserInAccount(ctx, neosyncdb.UUIDString(job.AccountID)) + user, err := s.userdataclient.GetUser(ctx) if err != nil { return nil, err } - - userUuid, err := s.getUserUuid(ctx) + err = user.EnforceJob(ctx, job, rbac.JobAction_Edit) if err != nil { return nil, err } @@ -699,11 +700,16 @@ func (s *Service) UpdateJobSchedule( return nil, err } + jobUuid, err := neosyncdb.ToUuid(job.GetId()) + if err != nil { + return nil, err + } + if err := s.db.WithTx(ctx, nil, func(dbtx neosyncdb.BaseDBTX) error { _, err = s.db.Q.UpdateJobSchedule(ctx, dbtx, db_queries.UpdateJobScheduleParams{ - ID: job.ID, + ID: jobUuid, CronSchedule: cronText, - UpdatedByID: *userUuid, + UpdatedByID: user.PgId(), }) if err != nil { return err @@ -715,8 +721,8 @@ func (s *Service) UpdateJobSchedule( // update temporal scheduled job err = s.temporalmgr.UpdateSchedule( ctx, - neosyncdb.UUIDString(job.AccountID), - neosyncdb.UUIDString(job.ID), + job.GetAccountId(), + job.GetId(), &temporalclient.ScheduleUpdateOptions{ DoUpdate: func(schedule temporalclient.ScheduleUpdateInput) (*temporalclient.ScheduleUpdate, error) { schedule.Description.Schedule.Spec = spec @@ -753,18 +759,19 @@ func (s *Service) PauseJob( ) (*connect.Response[mgmtv1alpha1.PauseJobResponse], error) { logger := logger_interceptor.GetLoggerFromContextOrDefault(ctx) logger = logger.With("jobId", req.Msg.Id) - jobUuid, err := neosyncdb.ToUuid(req.Msg.Id) + jobResp, err := s.GetJob(ctx, connect.NewRequest(&mgmtv1alpha1.GetJobRequest{ + Id: req.Msg.Id, + })) if err != nil { return nil, err } - job, err := s.db.Q.GetJobById(ctx, s.db.Db, jobUuid) - if err != nil && !neosyncdb.IsNoRows(err) { - return nil, fmt.Errorf("unable to get job by id: %w", err) - } else if err != nil && neosyncdb.IsNoRows(err) { - return nil, nucleuserrors.NewNotFound("job with that id does not exist") - } + job := jobResp.Msg.GetJob() - _, err = s.verifyUserInAccount(ctx, neosyncdb.UUIDString(job.AccountID)) + user, err := s.userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + err = user.EnforceJob(ctx, job, rbac.JobAction_Edit) if err != nil { return nil, err } @@ -773,8 +780,8 @@ func (s *Service) PauseJob( logger.Debug("pausing job") err = s.temporalmgr.PauseSchedule( ctx, - neosyncdb.UUIDString(job.AccountID), - neosyncdb.UUIDString(job.ID), + job.GetAccountId(), + job.GetId(), &temporalclient.SchedulePauseOptions{Note: req.Msg.GetNote()}, logger, ) @@ -785,8 +792,8 @@ func (s *Service) PauseJob( logger.Debug("unpausing job") err = s.temporalmgr.UnpauseSchedule( ctx, - neosyncdb.UUIDString(job.AccountID), - neosyncdb.UUIDString(job.ID), + job.GetAccountId(), + job.GetId(), &temporalclient.ScheduleUnpauseOptions{Note: req.Msg.GetNote()}, logger, ) @@ -814,23 +821,20 @@ func (s *Service) UpdateJobSourceConnection( logger := logger_interceptor.GetLoggerFromContextOrDefault(ctx) logger = logger.With("jobId", req.Msg.Id) logger.Debug("updating job source connection and mappings") - jobUuid, err := neosyncdb.ToUuid(req.Msg.Id) + + jobResp, err := s.GetJob(ctx, connect.NewRequest(&mgmtv1alpha1.GetJobRequest{ + Id: req.Msg.Id, + })) if err != nil { return nil, err } + job := jobResp.Msg.GetJob() - job, err := s.db.Q.GetJobById(ctx, s.db.Db, jobUuid) - if err != nil && !neosyncdb.IsNoRows(err) { - return nil, fmt.Errorf("unable to get job by id: %w", err) - } else if err != nil && neosyncdb.IsNoRows(err) { - return nil, nucleuserrors.NewNotFound("job with that id does not exist") - } - - _, err = s.verifyUserInAccount(ctx, neosyncdb.UUIDString(job.AccountID)) + user, err := s.userdataclient.GetUser(ctx) if err != nil { return nil, err } - userUuid, err := s.getUserUuid(ctx) + err = user.EnforceJob(ctx, job, rbac.JobAction_Edit) if err != nil { return nil, err } @@ -862,7 +866,7 @@ func (s *Service) UpdateJobSourceConnection( } // verifies that the account has access to that connection id - if err := s.verifyConnectionInAccount(ctx, connectionIdToVerify, neosyncdb.UUIDString(job.AccountID)); err != nil { + if err := s.verifyConnectionInAccount(ctx, connectionIdToVerify, job.GetAccountId()); err != nil { return nil, err } @@ -952,21 +956,26 @@ func (s *Service) UpdateJobSourceConnection( vfkKeys[key] = struct{}{} } + jobUuid, err := neosyncdb.ToUuid(job.GetId()) + if err != nil { + return nil, err + } + if err := s.db.WithTx(ctx, nil, func(dbtx neosyncdb.BaseDBTX) error { _, err = s.db.Q.UpdateJobSource(ctx, dbtx, db_queries.UpdateJobSourceParams{ - ID: job.ID, + ID: jobUuid, ConnectionOptions: connectionOptions, - UpdatedByID: *userUuid, + UpdatedByID: user.PgId(), }) if err != nil { return fmt.Errorf("unable to update job source: %w", err) } _, err = s.db.Q.UpdateJobMappings(ctx, dbtx, db_queries.UpdateJobMappingsParams{ - ID: job.ID, + ID: jobUuid, Mappings: mappings, - UpdatedByID: *userUuid, + UpdatedByID: user.PgId(), }) if err != nil { return fmt.Errorf("unable to update job mappings: %w", err) @@ -974,8 +983,8 @@ func (s *Service) UpdateJobSourceConnection( args := db_queries.UpdateJobVirtualForeignKeysParams{ VirtualForeignKeys: virtualForeignKeys, - UpdatedByID: *userUuid, - ID: job.ID, + UpdatedByID: user.PgId(), + ID: jobUuid, } _, err = s.db.Q.UpdateJobVirtualForeignKeys(ctx, dbtx, args) if err != nil { @@ -1006,36 +1015,37 @@ func (s *Service) SetJobSourceSqlConnectionSubsets( logger := logger_interceptor.GetLoggerFromContextOrDefault(ctx) logger = logger.With("jobId", req.Msg.Id) logger.Debug("updating job source sql connection subsets") - jobUuid, err := neosyncdb.ToUuid(req.Msg.Id) + + jobResp, err := s.GetJob(ctx, connect.NewRequest(&mgmtv1alpha1.GetJobRequest{ + Id: req.Msg.Id, + })) if err != nil { return nil, err } - job, err := s.db.Q.GetJobById(ctx, s.db.Db, jobUuid) - if err != nil && !neosyncdb.IsNoRows(err) { - return nil, fmt.Errorf("unable to get job by id: %w", err) - } else if err != nil && neosyncdb.IsNoRows(err) { - return nil, nucleuserrors.NewNotFound("job with that id does not exist") + job := jobResp.Msg.GetJob() + jobUuid, err := neosyncdb.ToUuid(job.GetId()) + if err != nil { + return nil, err } - - _, err = s.verifyUserInAccount(ctx, neosyncdb.UUIDString(job.AccountID)) + user, err := s.userdataclient.GetUser(ctx) if err != nil { return nil, err } - userUuid, err := s.getUserUuid(ctx) + err = user.EnforceJob(ctx, job, rbac.JobAction_Edit) if err != nil { return nil, err } var connectionId *string - if job.ConnectionOptions != nil { - if job.ConnectionOptions.MysqlOptions != nil { - connectionId = &job.ConnectionOptions.MysqlOptions.ConnectionId - } else if job.ConnectionOptions.PostgresOptions != nil { - connectionId = &job.ConnectionOptions.PostgresOptions.ConnectionId - } else if job.ConnectionOptions.DynamoDBOptions != nil { - connectionId = &job.ConnectionOptions.DynamoDBOptions.ConnectionId - } else if job.ConnectionOptions.MssqlOptions != nil { - connectionId = &job.ConnectionOptions.MssqlOptions.ConnectionId + if job.GetSource().GetOptions() != nil { + if job.GetSource().GetOptions().GetMysql() != nil { + connectionId = &job.GetSource().GetOptions().GetMysql().ConnectionId + } else if job.GetSource().GetOptions().GetPostgres() != nil { + connectionId = &job.GetSource().GetOptions().GetPostgres().ConnectionId + } else if job.GetSource().GetOptions().GetDynamodb() != nil { + connectionId = &job.GetSource().GetOptions().GetDynamodb().ConnectionId + } else if job.GetSource().GetOptions().GetMssql() != nil { + connectionId = &job.GetSource().GetOptions().GetMssql().ConnectionId } else { return nil, nucleuserrors.NewBadRequest("only jobs with a valid source connection id may be subset") } @@ -1062,7 +1072,7 @@ func (s *Service) SetJobSourceSqlConnectionSubsets( jobUuid, req.Msg.Schemas, req.Msg.SubsetByForeignKeyConstraints, - *userUuid, + user.PgId(), ); err != nil { return nil, err } @@ -1084,37 +1094,37 @@ func (s *Service) UpdateJobDestinationConnection( req *connect.Request[mgmtv1alpha1.UpdateJobDestinationConnectionRequest], ) (*connect.Response[mgmtv1alpha1.UpdateJobDestinationConnectionResponse], error) { logger := logger_interceptor.GetLoggerFromContextOrDefault(ctx) - logger = logger.With("jobId", req.Msg.JobId, "connectionId", req.Msg.ConnectionId) + logger = logger.With("jobId", req.Msg.GetJobId(), "connectionId", req.Msg.GetConnectionId()) - jobUuid, err := neosyncdb.ToUuid(req.Msg.JobId) + jobUuid, err := neosyncdb.ToUuid(req.Msg.GetJobId()) if err != nil { return nil, err } - destinationUuid, err := neosyncdb.ToUuid(req.Msg.DestinationId) + destinationUuid, err := neosyncdb.ToUuid(req.Msg.GetDestinationId()) if err != nil { return nil, err } - job, err := s.GetJob(ctx, connect.NewRequest(&mgmtv1alpha1.GetJobRequest{ + jobResp, err := s.GetJob(ctx, connect.NewRequest(&mgmtv1alpha1.GetJobRequest{ Id: req.Msg.JobId, })) if err != nil { return nil, err } - _, err = s.verifyUserInAccount(ctx, job.Msg.Job.AccountId) + job := jobResp.Msg.GetJob() + user, err := s.userdataclient.GetUser(ctx) if err != nil { return nil, err } - userUuid, err := s.getUserUuid(ctx) + err = user.EnforceJob(ctx, job, rbac.JobAction_Edit) if err != nil { return nil, err } - logger = logger.With("userId", userUuid) - connectionUuid, err := neosyncdb.ToUuid(req.Msg.ConnectionId) + connectionUuid, err := neosyncdb.ToUuid(req.Msg.GetConnectionId()) if err != nil { return nil, err } - if err := s.verifyConnectionInAccount(ctx, req.Msg.ConnectionId, job.Msg.Job.AccountId); err != nil { + if err := s.verifyConnectionInAccount(ctx, req.Msg.GetConnectionId(), job.GetAccountId()); err != nil { return nil, err } options := &pg_models.JobDestinationOptions{} @@ -1146,7 +1156,7 @@ func (s *Service) UpdateJobDestinationConnection( } updatedJob, err := s.GetJob(ctx, connect.NewRequest(&mgmtv1alpha1.GetJobRequest{ - Id: job.Msg.Job.Id, + Id: job.GetId(), })) if err != nil { return nil, err @@ -1162,9 +1172,9 @@ func (s *Service) DeleteJobDestinationConnection( req *connect.Request[mgmtv1alpha1.DeleteJobDestinationConnectionRequest], ) (*connect.Response[mgmtv1alpha1.DeleteJobDestinationConnectionResponse], error) { logger := logger_interceptor.GetLoggerFromContextOrDefault(ctx) - logger = logger.With("destinationId", req.Msg.DestinationId) + logger = logger.With("destinationId", req.Msg.GetDestinationId()) - destinationUuid, err := neosyncdb.ToUuid(req.Msg.DestinationId) + destinationUuid, err := neosyncdb.ToUuid(req.Msg.GetDestinationId()) if err != nil { return nil, err } @@ -1176,27 +1186,24 @@ func (s *Service) DeleteJobDestinationConnection( return connect.NewResponse(&mgmtv1alpha1.DeleteJobDestinationConnectionResponse{}), nil } - job, err := s.db.Q.GetJobById(ctx, s.db.Db, destination.JobID) - if err != nil && !neosyncdb.IsNoRows(err) { - return nil, fmt.Errorf("unable to get job by id: %w", err) - } else if err != nil && neosyncdb.IsNoRows(err) { - return nil, nucleuserrors.NewNotFound("job with that id does not exist") - } + jobId := neosyncdb.UUIDString(destination.JobID) - _, err = s.verifyUserInAccount(ctx, neosyncdb.UUIDString(job.AccountID)) + jobResp, err := s.GetJob(ctx, connect.NewRequest(&mgmtv1alpha1.GetJobRequest{ + Id: jobId, + })) if err != nil { return nil, err } - userUuid, err := s.getUserUuid(ctx) + job := jobResp.Msg.GetJob() + + logger = logger.With("jobId", job.GetId()) + + user, err := s.userdataclient.GetUser(ctx) if err != nil { return nil, err } - logger = logger.With("userId", userUuid, "jobId", job.ID) - - if err := s.verifyConnectionInAccount( - ctx, - neosyncdb.UUIDString(destination.ConnectionID), - neosyncdb.UUIDString(job.AccountID)); err != nil { + err = user.EnforceJob(ctx, job, rbac.JobAction_Edit) + if err != nil { return nil, err } @@ -1215,13 +1222,22 @@ func (s *Service) IsJobNameAvailable( ctx context.Context, req *connect.Request[mgmtv1alpha1.IsJobNameAvailableRequest], ) (*connect.Response[mgmtv1alpha1.IsJobNameAvailableResponse], error) { - accountUuid, err := s.verifyUserInAccount(ctx, req.Msg.AccountId) + user, err := s.userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + + if err := user.EnforceJob(ctx, userdata.NewWildcardDomainEntity(req.Msg.GetAccountId()), rbac.JobAction_View); err != nil { + return nil, err + } + + accountUuid, err := neosyncdb.ToUuid(req.Msg.GetAccountId()) if err != nil { return nil, err } count, err := s.db.Q.IsJobNameAvailable(ctx, s.db.Db, db_queries.IsJobNameAvailableParams{ - AccountId: *accountUuid, + AccountId: accountUuid, JobName: req.Msg.Name, }) if err != nil { @@ -1287,7 +1303,7 @@ func verifyConnectionIdsUnique(connectionIds []string) bool { return true } -func verifyConnectionsAreCompatible(ctx context.Context, db *neosyncdb.NeosyncDb, sourceConnId pgtype.UUID, destinations []*Destination) (bool, error) { +func verifyConnectionsAreCompatible(ctx context.Context, db *neosyncdb.NeosyncDb, sourceConnId pgtype.UUID, destinations []*destination) (bool, error) { var sourceConnection db_queries.NeosyncApiConnection dests := make([]db_queries.NeosyncApiConnection, len(destinations)) group := new(errgroup.Group) @@ -1360,7 +1376,12 @@ func (s *Service) SetJobWorkflowOptions( if err != nil { return nil, err } - _, err = s.verifyUserInAccount(ctx, job.Msg.Job.AccountId) + + user, err := s.userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + err = user.EnforceJob(ctx, job.Msg.GetJob(), rbac.JobAction_Edit) if err != nil { return nil, err } @@ -1374,16 +1395,13 @@ func (s *Service) SetJobWorkflowOptions( if err != nil { return nil, err } - userUuid, err := s.getUserUuid(ctx) - if err != nil { - return nil, err - } + // update temporal scheduled job if err := s.db.WithTx(ctx, nil, func(dbtx neosyncdb.BaseDBTX) error { _, err = s.db.Q.SetJobWorkflowOptions(ctx, dbtx, db_queries.SetJobWorkflowOptionsParams{ ID: jobUuid, WorkflowOptions: wfOptions, - UpdatedByID: *userUuid, + UpdatedByID: user.PgId(), }) if err != nil { return fmt.Errorf("unable to set job workflow options: %w", err) @@ -1437,13 +1455,17 @@ func (s *Service) SetJobSyncOptions( ctx context.Context, req *connect.Request[mgmtv1alpha1.SetJobSyncOptionsRequest], ) (*connect.Response[mgmtv1alpha1.SetJobSyncOptionsResponse], error) { - job, err := s.GetJob(ctx, connect.NewRequest(&mgmtv1alpha1.GetJobRequest{ + jobResp, err := s.GetJob(ctx, connect.NewRequest(&mgmtv1alpha1.GetJobRequest{ Id: req.Msg.Id, })) if err != nil { return nil, err } - _, err = s.verifyUserInAccount(ctx, job.Msg.Job.AccountId) + user, err := s.userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + err = user.EnforceJob(ctx, jobResp.Msg.GetJob(), rbac.JobAction_Edit) if err != nil { return nil, err } @@ -1457,22 +1479,18 @@ func (s *Service) SetJobSyncOptions( if err != nil { return nil, err } - userUuid, err := s.getUserUuid(ctx) - if err != nil { - return nil, err - } _, err = s.db.Q.SetJobSyncOptions(ctx, s.db.Db, db_queries.SetJobSyncOptionsParams{ ID: jobUuid, SyncOptions: syncOptions, - UpdatedByID: *userUuid, + UpdatedByID: user.PgId(), }) if err != nil { return nil, err } updatedJob, err := s.GetJob(ctx, connect.NewRequest(&mgmtv1alpha1.GetJobRequest{ - Id: req.Msg.Id, + Id: req.Msg.GetId(), })) if err != nil { return nil, err @@ -1485,15 +1503,10 @@ func (s *Service) ValidateJobMappings( req *connect.Request[mgmtv1alpha1.ValidateJobMappingsRequest], ) (*connect.Response[mgmtv1alpha1.ValidateJobMappingsResponse], error) { logger := logger_interceptor.GetLoggerFromContextOrDefault(ctx) - logger = logger.With("accountId", req.Msg.AccountId) - - _, err := s.verifyUserInAccount(ctx, req.Msg.AccountId) - if err != nil { - return nil, err - } + logger = logger.With("accountId", req.Msg.GetAccountId()) connection, err := s.connectionService.GetConnection(ctx, connect.NewRequest(&mgmtv1alpha1.GetConnectionRequest{ - Id: req.Msg.ConnectionId, + Id: req.Msg.GetConnectionId(), })) if err != nil { return nil, err diff --git a/backend/services/mgmt/v1alpha1/job-service/runs.go b/backend/services/mgmt/v1alpha1/job-service/runs.go index 40460b200e..4375d557b4 100644 --- a/backend/services/mgmt/v1alpha1/job-service/runs.go +++ b/backend/services/mgmt/v1alpha1/job-service/runs.go @@ -12,14 +12,15 @@ import ( "time" "connectrpc.com/connect" - "github.com/jackc/pgx/v5/pgtype" db_queries "github.com/nucleuscloud/neosync/backend/gen/go/db" mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" logger_interceptor "github.com/nucleuscloud/neosync/backend/internal/connect/interceptors/logger" "github.com/nucleuscloud/neosync/backend/internal/dtomaps" + "github.com/nucleuscloud/neosync/backend/internal/ee/rbac" nucleuserrors "github.com/nucleuscloud/neosync/backend/internal/errors" "github.com/nucleuscloud/neosync/backend/internal/loki" "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" + "github.com/nucleuscloud/neosync/backend/internal/userdata" "go.temporal.io/api/enums/v1" temporalclient "go.temporal.io/sdk/client" "go.temporal.io/sdk/converter" @@ -51,6 +52,7 @@ func (s *Service) GetJobRuns( if err != nil { return nil, err } + accountId = neosyncdb.UUIDString(job.AccountID) jobIds = append(jobIds, id.JobId) case *mgmtv1alpha1.GetJobRunsRequest_AccountId: @@ -71,10 +73,13 @@ func (s *Service) GetJobRuns( return nil, fmt.Errorf("must provide jobId or accountId") } - _, err := s.verifyUserInAccount(ctx, accountId) + user, err := s.userdataclient.GetUser(ctx) if err != nil { return nil, err } + if err := user.EnforceJob(ctx, userdata.NewWildcardDomainEntity(accountId), rbac.JobAction_View); err != nil { + return nil, err + } workflows, err := s.temporalmgr.GetWorkflowExecutionsByScheduleIds(ctx, accountId, jobIds, logger) if err != nil { @@ -97,12 +102,22 @@ func (s *Service) GetJobRun( ) (*connect.Response[mgmtv1alpha1.GetJobRunResponse], error) { logger := logger_interceptor.GetLoggerFromContextOrDefault(ctx) logger = logger.With("jobRunId", req.Msg.JobRunId) + res, err := s.temporalmgr.DescribeWorklowExecution(ctx, req.Msg.GetAccountId(), req.Msg.GetJobRunId(), logger) if err != nil { return nil, err } dto := dtomaps.ToJobRunDto(logger, res) + + user, err := s.userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + if err := user.EnforceJob(ctx, userdata.NewDomainEntity(req.Msg.GetAccountId(), dto.GetJobId()), rbac.JobAction_View); err != nil { + return nil, err + } + return connect.NewResponse(&mgmtv1alpha1.GetJobRunResponse{ JobRun: dto, }), nil @@ -114,13 +129,24 @@ func (s *Service) GetJobRunEvents( ) (*connect.Response[mgmtv1alpha1.GetJobRunEventsResponse], error) { logger := logger_interceptor.GetLoggerFromContextOrDefault(ctx) logger = logger.With("accountId", req.Msg.GetAccountId(), "jobRunId", req.Msg.GetJobRunId()) + + jrResp, err := s.GetJobRun(ctx, connect.NewRequest(&mgmtv1alpha1.GetJobRunRequest{ + AccountId: req.Msg.GetAccountId(), + JobRunId: req.Msg.GetJobRunId(), + })) + if err != nil { + return nil, err + } + + jobRun := jrResp.Msg.GetJobRun() + isRunComplete := false activityOrder := []int64{} activityMap := map[int64]*mgmtv1alpha1.JobRunEvent{} iter, err := s.temporalmgr.GetWorkflowHistory( ctx, req.Msg.GetAccountId(), - req.Msg.GetJobRunId(), + jobRun.GetId(), logger, ) if err != nil { @@ -228,26 +254,27 @@ func (s *Service) CreateJobRun( req *connect.Request[mgmtv1alpha1.CreateJobRunRequest], ) (*connect.Response[mgmtv1alpha1.CreateJobRunResponse], error) { logger := logger_interceptor.GetLoggerFromContextOrDefault(ctx) - logger = logger.With("jobId", req.Msg.JobId) - jobUuid, err := neosyncdb.ToUuid(req.Msg.JobId) + logger = logger.With("jobId", req.Msg.GetJobId()) + jobResp, err := s.GetJob(ctx, connect.NewRequest(&mgmtv1alpha1.GetJobRequest{ + Id: req.Msg.GetJobId(), + })) if err != nil { return nil, err } - job, err := s.db.Q.GetJobById(ctx, s.db.Db, jobUuid) + job := jobResp.Msg.GetJob() + user, err := s.userdataclient.GetUser(ctx) if err != nil { return nil, err } - accountId := neosyncdb.UUIDString(job.AccountID) - _, err = s.verifyUserInAccount(ctx, accountId) - if err != nil { + if err := user.EnforceJob(ctx, userdata.NewDomainEntity(job.GetAccountId(), job.GetId()), rbac.JobAction_Execute); err != nil { return nil, err } logger.Debug("creating job run by triggering temporal schedule") err = s.temporalmgr.TriggerSchedule( ctx, - neosyncdb.UUIDString(job.AccountID), - neosyncdb.UUIDString(job.ID), + job.GetAccountId(), + job.GetId(), &temporalclient.ScheduleTriggerOptions{}, logger, ) @@ -267,7 +294,24 @@ func (s *Service) CancelJobRun( "accountId", req.Msg.GetAccountId(), "jobRunId", req.Msg.GetJobRunId(), ) - err := s.temporalmgr.CancelWorkflow(ctx, req.Msg.GetAccountId(), req.Msg.GetJobRunId(), logger) + + jobRunResp, err := s.GetJobRun(ctx, connect.NewRequest(&mgmtv1alpha1.GetJobRunRequest{ + AccountId: req.Msg.GetAccountId(), + JobRunId: req.Msg.GetJobRunId(), + })) + if err != nil { + return nil, err + } + jobRun := jobRunResp.Msg.GetJobRun() + user, err := s.userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + if err := user.EnforceJob(ctx, userdata.NewDomainEntity(req.Msg.GetAccountId(), jobRun.GetJobId()), rbac.JobAction_Execute); err != nil { + return nil, err + } + + err = s.temporalmgr.CancelWorkflow(ctx, req.Msg.GetAccountId(), req.Msg.GetJobRunId(), logger) if err != nil { return nil, fmt.Errorf("unable to cancel job run: %w", err) } @@ -283,7 +327,23 @@ func (s *Service) TerminateJobRun( "accountId", req.Msg.GetAccountId(), "jobRunId", req.Msg.GetJobRunId(), ) - err := s.temporalmgr.TerminateWorkflow(ctx, req.Msg.GetAccountId(), req.Msg.GetJobRunId(), logger) + jobRunResp, err := s.GetJobRun(ctx, connect.NewRequest(&mgmtv1alpha1.GetJobRunRequest{ + AccountId: req.Msg.GetAccountId(), + JobRunId: req.Msg.GetJobRunId(), + })) + if err != nil { + return nil, err + } + jobRun := jobRunResp.Msg.GetJobRun() + user, err := s.userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + if err := user.EnforceJob(ctx, userdata.NewDomainEntity(req.Msg.GetAccountId(), jobRun.GetJobId()), rbac.JobAction_Execute); err != nil { + return nil, err + } + + err = s.temporalmgr.TerminateWorkflow(ctx, req.Msg.GetAccountId(), req.Msg.GetJobRunId(), logger) if err != nil { return nil, fmt.Errorf("unable to terminate job run: %w", err) } @@ -299,7 +359,23 @@ func (s *Service) DeleteJobRun( "accountId", req.Msg.GetAccountId(), "jobRunId", req.Msg.GetJobRunId(), ) - err := s.temporalmgr.DeleteWorkflowExecution(ctx, req.Msg.GetAccountId(), req.Msg.GetJobRunId(), logger) + jobRunResp, err := s.GetJobRun(ctx, connect.NewRequest(&mgmtv1alpha1.GetJobRunRequest{ + AccountId: req.Msg.GetAccountId(), + JobRunId: req.Msg.GetJobRunId(), + })) + if err != nil { + return nil, err + } + jobRun := jobRunResp.Msg.GetJobRun() + user, err := s.userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + if err := user.EnforceJob(ctx, userdata.NewDomainEntity(req.Msg.GetAccountId(), jobRun.GetJobId()), rbac.JobAction_Delete); err != nil { + return nil, err + } + + err = s.temporalmgr.DeleteWorkflowExecution(ctx, req.Msg.GetAccountId(), req.Msg.GetJobRunId(), logger) if err != nil { return nil, fmt.Errorf("unable to delete job run: %w", err) } @@ -318,12 +394,28 @@ func (s *Service) GetJobRunLogsStream( stream *connect.ServerStream[mgmtv1alpha1.GetJobRunLogsStreamResponse], ) error { logger := logger_interceptor.GetLoggerFromContextOrDefault(ctx) - logger = logger.With("jobRunId", req.Msg.JobRunId) + logger = logger.With("jobRunId", req.Msg.GetJobRunId()) if s.cfg.RunLogConfig == nil || !s.cfg.RunLogConfig.IsEnabled || s.cfg.RunLogConfig.RunLogType == nil { return nucleuserrors.NewNotImplemented("job run logs streaming is not enabled. please configure or contact system administrator to enable logs.") } + jobRunResp, err := s.GetJobRun(ctx, connect.NewRequest(&mgmtv1alpha1.GetJobRunRequest{ + AccountId: req.Msg.GetAccountId(), + JobRunId: req.Msg.GetJobRunId(), + })) + if err != nil { + return err + } + jobRun := jobRunResp.Msg.GetJobRun() + user, err := s.userdataclient.GetUser(ctx) + if err != nil { + return err + } + if err := user.EnforceJob(ctx, userdata.NewDomainEntity(req.Msg.GetAccountId(), jobRun.GetJobId()), rbac.JobAction_View); err != nil { + return err + } + switch *s.cfg.RunLogConfig.RunLogType { case KubePodRunLogType: return s.streamK8sWorkerPodLogs(ctx, req, stream, logger) @@ -543,7 +635,16 @@ func (s *Service) GetRunContext( req *connect.Request[mgmtv1alpha1.GetRunContextRequest], ) (*connect.Response[mgmtv1alpha1.GetRunContextResponse], error) { id := req.Msg.GetId() - accountUuid, err := s.verifyUserInAccount(ctx, id.GetAccountId()) + + user, err := s.userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + if err := user.EnforceJob(ctx, userdata.NewWildcardDomainEntity(id.GetAccountId()), rbac.JobAction_View); err != nil { + return nil, err + } + + accountUuid, err := neosyncdb.ToUuid(id.GetAccountId()) if err != nil { return nil, err } @@ -551,7 +652,7 @@ func (s *Service) GetRunContext( runContext, err := s.db.Q.GetRunContextByKey(ctx, s.db.Db, db_queries.GetRunContextByKeyParams{ WorkflowId: id.GetJobRunId(), ExternalId: id.GetExternalId(), - AccountId: *accountUuid, + AccountId: accountUuid, }) if err != nil && !neosyncdb.IsNoRows(err) { return nil, fmt.Errorf("unable to retrieve run context by key: %w", err) @@ -569,36 +670,31 @@ func (s *Service) SetRunContext( req *connect.Request[mgmtv1alpha1.SetRunContextRequest], ) (*connect.Response[mgmtv1alpha1.SetRunContextResponse], error) { id := req.Msg.GetId() - accountUuid, err := s.verifyUserInAccount(ctx, id.GetAccountId()) + + user, err := s.userdataclient.GetUser(ctx) if err != nil { return nil, err } - if s.cfg.IsNeosyncCloud && !isWorkerApiKey(ctx) { + if err := user.EnforceJob(ctx, userdata.NewWildcardDomainEntity(id.GetAccountId()), rbac.JobAction_Edit); err != nil { + return nil, err + } + + if s.cfg.IsNeosyncCloud && !user.IsWorkerApiKey() { return nil, nucleuserrors.NewUnauthenticated("must provide valid authentication credentials for this endpoint") } - var userId *pgtype.UUID - if isWorkerApiKey(ctx) { - uid, err := neosyncdb.ToUuid("00000000-0000-0000-0000-000000000000") - if err != nil { - return nil, err - } - userId = &uid - } else { - userUuid, err := s.getUserUuid(ctx) - if err != nil { - return nil, err - } - userId = userUuid + accountUuid, err := neosyncdb.ToUuid(id.GetAccountId()) + if err != nil { + return nil, err } err = s.db.Q.SetRunContext(ctx, s.db.Db, db_queries.SetRunContextParams{ WorkflowID: id.GetJobRunId(), ExternalID: id.GetExternalId(), - AccountID: *accountUuid, + AccountID: accountUuid, Value: req.Msg.GetValue(), - CreatedByID: *userId, - UpdatedByID: *userId, + CreatedByID: user.PgId(), + UpdatedByID: user.PgId(), }) if err != nil { return nil, fmt.Errorf("unable to set run context: %w", err) @@ -614,36 +710,30 @@ func (s *Service) SetRunContexts( for stream.Receive() { req := stream.Msg() id := req.GetId() - accountUuid, err := s.verifyUserInAccount(ctx, id.GetAccountId()) + user, err := s.userdataclient.GetUser(ctx) if err != nil { return nil, err } - if s.cfg.IsNeosyncCloud && !isWorkerApiKey(ctx) { + if err := user.EnforceJob(ctx, userdata.NewWildcardDomainEntity(id.GetAccountId()), rbac.JobAction_Edit); err != nil { + return nil, err + } + + if s.cfg.IsNeosyncCloud && !user.IsWorkerApiKey() { return nil, nucleuserrors.NewUnauthenticated("must provide valid authentication credentials for this endpoint") } - var userId *pgtype.UUID - if isWorkerApiKey(ctx) { - uid, err := neosyncdb.ToUuid("00000000-0000-0000-0000-000000000000") - if err != nil { - return nil, err - } - userId = &uid - } else { - userUuid, err := s.getUserUuid(ctx) - if err != nil { - return nil, err - } - userId = userUuid + accountUuid, err := neosyncdb.ToUuid(id.GetAccountId()) + if err != nil { + return nil, err } err = s.db.Q.SetRunContext(ctx, s.db.Db, db_queries.SetRunContextParams{ WorkflowID: id.GetJobRunId(), ExternalID: id.GetExternalId(), - AccountID: *accountUuid, + AccountID: accountUuid, Value: req.GetValue(), - CreatedByID: *userId, - UpdatedByID: *userId, + CreatedByID: user.PgId(), + UpdatedByID: user.PgId(), }) if err != nil { return nil, fmt.Errorf("unable to set run context: %w", err) diff --git a/backend/services/mgmt/v1alpha1/job-service/service.go b/backend/services/mgmt/v1alpha1/job-service/service.go index c54d1140e4..6b2e3fe330 100644 --- a/backend/services/mgmt/v1alpha1/job-service/service.go +++ b/backend/services/mgmt/v1alpha1/job-service/service.go @@ -5,15 +5,16 @@ import ( jobhooks "github.com/nucleuscloud/neosync/backend/internal/ee/hooks/jobs" "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" clientmanager "github.com/nucleuscloud/neosync/backend/internal/temporal/clientmanager" + "github.com/nucleuscloud/neosync/backend/internal/userdata" sql_manager "github.com/nucleuscloud/neosync/backend/pkg/sqlmanager" ) type Service struct { - cfg *Config - db *neosyncdb.NeosyncDb - connectionService mgmtv1alpha1connect.ConnectionServiceClient - useraccountService mgmtv1alpha1connect.UserAccountServiceClient - sqlmanager sql_manager.SqlManagerClient + cfg *Config + db *neosyncdb.NeosyncDb + connectionService mgmtv1alpha1connect.ConnectionServiceClient + userdataclient userdata.Interface + sqlmanager sql_manager.SqlManagerClient temporalmgr clientmanager.Interface @@ -59,17 +60,17 @@ func New( db *neosyncdb.NeosyncDb, temporalWfManager clientmanager.Interface, connectionService mgmtv1alpha1connect.ConnectionServiceClient, - useraccountService mgmtv1alpha1connect.UserAccountServiceClient, sqlmanager sql_manager.SqlManagerClient, jobhookService jobhooks.Interface, + userdataclient userdata.Interface, ) *Service { return &Service{ - cfg: cfg, - db: db, - temporalmgr: temporalWfManager, - connectionService: connectionService, - useraccountService: useraccountService, - sqlmanager: sqlmanager, - hookService: jobhookService, + cfg: cfg, + db: db, + temporalmgr: temporalWfManager, + connectionService: connectionService, + sqlmanager: sqlmanager, + hookService: jobhookService, + userdataclient: userdataclient, } } diff --git a/backend/services/mgmt/v1alpha1/job-service/user-account.go b/backend/services/mgmt/v1alpha1/job-service/user-account.go deleted file mode 100644 index 1a461c0729..0000000000 --- a/backend/services/mgmt/v1alpha1/job-service/user-account.go +++ /dev/null @@ -1,59 +0,0 @@ -package v1alpha1_jobservice - -import ( - "context" - - "connectrpc.com/connect" - "github.com/jackc/pgx/v5/pgtype" - mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" - "github.com/nucleuscloud/neosync/backend/internal/apikey" - auth_apikey "github.com/nucleuscloud/neosync/backend/internal/auth/apikey" - nucleuserrors "github.com/nucleuscloud/neosync/backend/internal/errors" - "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" -) - -func (s *Service) verifyUserInAccount( - ctx context.Context, - accountId string, -) (*pgtype.UUID, error) { - accountUuid, err := neosyncdb.ToUuid(accountId) - if err != nil { - return nil, err - } - - if isWorkerApiKey(ctx) { - return &accountUuid, nil - } - - resp, err := s.useraccountService.IsUserInAccount(ctx, connect.NewRequest(&mgmtv1alpha1.IsUserInAccountRequest{AccountId: accountId})) - if err != nil { - return nil, err - } - if !resp.Msg.Ok { - return nil, nucleuserrors.NewForbidden("user in not in requested account") - } - - return &accountUuid, nil -} - -func isWorkerApiKey(ctx context.Context) bool { - data, err := auth_apikey.GetTokenDataFromCtx(ctx) - if err != nil { - return false - } - return data.ApiKeyType == apikey.WorkerApiKey -} - -func (s *Service) getUserUuid( - ctx context.Context, -) (*pgtype.UUID, error) { - user, err := s.useraccountService.GetUser(ctx, connect.NewRequest(&mgmtv1alpha1.GetUserRequest{})) - if err != nil { - return nil, err - } - userUuid, err := neosyncdb.ToUuid(user.Msg.UserId) - if err != nil { - return nil, err - } - return &userUuid, nil -} diff --git a/backend/services/mgmt/v1alpha1/metrics-service/metrics.go b/backend/services/mgmt/v1alpha1/metrics-service/metrics.go index 4a2cb9f741..10672b01c3 100644 --- a/backend/services/mgmt/v1alpha1/metrics-service/metrics.go +++ b/backend/services/mgmt/v1alpha1/metrics-service/metrics.go @@ -9,7 +9,9 @@ import ( "connectrpc.com/connect" mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" logger_interceptor "github.com/nucleuscloud/neosync/backend/internal/connect/interceptors/logger" + "github.com/nucleuscloud/neosync/backend/internal/ee/rbac" nucleuserrors "github.com/nucleuscloud/neosync/backend/internal/errors" + "github.com/nucleuscloud/neosync/backend/internal/userdata" "github.com/nucleuscloud/neosync/backend/pkg/metrics" ) @@ -49,7 +51,11 @@ func (s *Service) GetDailyMetricCount( switch identifier := req.Msg.Identifier.(type) { case *mgmtv1alpha1.GetDailyMetricCountRequest_AccountId: - if _, err := s.verifyUserInAccount(ctx, identifier.AccountId); err != nil { + user, err := s.userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + if err := user.EnforceAccount(ctx, userdata.NewIdentifier(identifier.AccountId), rbac.AccountAction_View); err != nil { return nil, err } queryLabels = append(queryLabels, metrics.NewEqLabel(metrics.AccountIdLabel, identifier.AccountId)) @@ -130,7 +136,11 @@ func (s *Service) GetMetricCount( switch identifier := req.Msg.Identifier.(type) { case *mgmtv1alpha1.GetMetricCountRequest_AccountId: - if _, err := s.verifyUserInAccount(ctx, identifier.AccountId); err != nil { + user, err := s.userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + if err := user.EnforceAccount(ctx, userdata.NewIdentifier(identifier.AccountId), rbac.AccountAction_View); err != nil { return nil, err } queryLabels = append(queryLabels, metrics.NewEqLabel(metrics.AccountIdLabel, identifier.AccountId)) diff --git a/backend/services/mgmt/v1alpha1/metrics-service/metrics_test.go b/backend/services/mgmt/v1alpha1/metrics-service/metrics_test.go index 7f23d0ab3d..af8c12e01a 100644 --- a/backend/services/mgmt/v1alpha1/metrics-service/metrics_test.go +++ b/backend/services/mgmt/v1alpha1/metrics-service/metrics_test.go @@ -2,12 +2,14 @@ package v1alpha1_metricsservice import ( "context" + "errors" "testing" "time" "connectrpc.com/connect" mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect" + "github.com/nucleuscloud/neosync/backend/internal/userdata" promapiv1mock "github.com/nucleuscloud/neosync/internal/mocks/github.com/prometheus/client_golang/api/prometheus/v1" "github.com/prometheus/common/model" "github.com/stretchr/testify/assert" @@ -50,7 +52,7 @@ var ( func Test_GetMetricCount_Empty_Matrix(t *testing.T) { m := createServiceMock(t, &Config{}) - mockIsUserInAccount(m.UserAccountServiceMock, true) + mockIsUserInAccount(t, m.UserServiceMock, true) ctx := context.Background() @@ -89,7 +91,7 @@ func Test_GetMetricCount_InvalidIdentifier(t *testing.T) { func Test_GetMetricCount_AccountId(t *testing.T) { m := createServiceMock(t, &Config{}) - mockIsUserInAccount(m.UserAccountServiceMock, true) + mockIsUserInAccount(t, m.UserServiceMock, true) ctx := context.Background() @@ -283,39 +285,45 @@ func Test_GetMetricCount_No_Metric(t *testing.T) { } type serviceMocks struct { - Service *Service - UserAccountServiceMock *mgmtv1alpha1connect.MockUserAccountServiceClient - JobServiceMock *mgmtv1alpha1connect.MockJobServiceHandler - PromApiMock *promapiv1mock.MockAPI + Service *Service + UserServiceMock *userdata.MockInterface + JobServiceMock *mgmtv1alpha1connect.MockJobServiceHandler + PromApiMock *promapiv1mock.MockAPI } func createServiceMock(t testing.TB, config *Config) *serviceMocks { t.Helper() - mockUserAccService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) + mockUserService := userdata.NewMockInterface(t) mockJobService := mgmtv1alpha1connect.NewMockJobServiceHandler(t) mockPromApi := promapiv1mock.NewMockAPI(t) - service := New(config, mockUserAccService, mockJobService, mockPromApi) + service := New(config, mockUserService, mockJobService, mockPromApi) return &serviceMocks{ - Service: service, - UserAccountServiceMock: mockUserAccService, - JobServiceMock: mockJobService, - PromApiMock: mockPromApi, + Service: service, + UserServiceMock: mockUserService, + JobServiceMock: mockJobService, + PromApiMock: mockPromApi, } } //nolint:unparam -func mockIsUserInAccount(userAccountServiceMock *mgmtv1alpha1connect.MockUserAccountServiceClient, isInAccount bool) { - userAccountServiceMock.On("IsUserInAccount", mock.Anything, mock.Anything).Return(connect.NewResponse(&mgmtv1alpha1.IsUserInAccountResponse{ - Ok: isInAccount, - }), nil) +func mockIsUserInAccount(t testing.TB, userServiceMock *userdata.MockInterface, isInAccount bool) { + mockEntityEnforcer := userdata.NewMockEntityEnforcer(t) + if isInAccount { + mockEntityEnforcer.On("EnforceAccount", mock.Anything, mock.Anything, mock.Anything).Once().Return(nil) + } else { + mockEntityEnforcer.On("EnforceAccount", mock.Anything, mock.Anything, mock.Anything).Once().Return(errors.New("test: not in account")) + } + userServiceMock.On("GetUser", mock.Anything).Once().Return(&userdata.User{ + EntityEnforcer: mockEntityEnforcer, + }, nil) } func Test_GetDailyMetricCount_Empty_Matrix(t *testing.T) { m := createServiceMock(t, &Config{}) - mockIsUserInAccount(m.UserAccountServiceMock, true) + mockIsUserInAccount(t, m.UserServiceMock, true) ctx := context.Background() @@ -356,7 +364,7 @@ func Test_GetDailyMetricCount_InvalidIdentifier(t *testing.T) { func Test_GetDailyMetricCount_AccountId(t *testing.T) { m := createServiceMock(t, &Config{}) - mockIsUserInAccount(m.UserAccountServiceMock, true) + mockIsUserInAccount(t, m.UserServiceMock, true) ctx := context.Background() @@ -448,7 +456,7 @@ func Test_GetDailyMetricCount_RunId(t *testing.T) { func Test_GetDailyMetricCount_MultipleDays(t *testing.T) { m := createServiceMock(t, &Config{}) - mockIsUserInAccount(m.UserAccountServiceMock, true) + mockIsUserInAccount(t, m.UserServiceMock, true) ctx := context.Background() @@ -486,7 +494,7 @@ func Test_GetDailyMetricCount_MultipleDays(t *testing.T) { func Test_GetDailyMetricCount_MultipleDays_Ordering(t *testing.T) { m := createServiceMock(t, &Config{}) - mockIsUserInAccount(m.UserAccountServiceMock, true) + mockIsUserInAccount(t, m.UserServiceMock, true) ctx := context.Background() diff --git a/backend/services/mgmt/v1alpha1/metrics-service/service.go b/backend/services/mgmt/v1alpha1/metrics-service/service.go index 536f57f5b4..1d4cb208a9 100644 --- a/backend/services/mgmt/v1alpha1/metrics-service/service.go +++ b/backend/services/mgmt/v1alpha1/metrics-service/service.go @@ -2,15 +2,16 @@ package v1alpha1_metricsservice import ( "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect" + "github.com/nucleuscloud/neosync/backend/internal/userdata" promv1 "github.com/prometheus/client_golang/api/prometheus/v1" ) type Service struct { cfg *Config - useraccountservice mgmtv1alpha1connect.UserAccountServiceClient - jobservice mgmtv1alpha1connect.JobServiceHandler - prometheusclient promv1.API + userdataclient userdata.Interface + jobservice mgmtv1alpha1connect.JobServiceHandler + prometheusclient promv1.API } type Config struct { @@ -19,14 +20,14 @@ type Config struct { func New( cfg *Config, - useraccountservice mgmtv1alpha1connect.UserAccountServiceClient, + userdataclient userdata.Interface, jobservice mgmtv1alpha1connect.JobServiceHandler, promclient promv1.API, ) *Service { return &Service{ - cfg: cfg, - useraccountservice: useraccountservice, - jobservice: jobservice, - prometheusclient: promclient, + cfg: cfg, + userdataclient: userdataclient, + jobservice: jobservice, + prometheusclient: promclient, } } diff --git a/backend/services/mgmt/v1alpha1/metrics-service/user-account.go b/backend/services/mgmt/v1alpha1/metrics-service/user-account.go deleted file mode 100644 index fabdc23114..0000000000 --- a/backend/services/mgmt/v1alpha1/metrics-service/user-account.go +++ /dev/null @@ -1,60 +0,0 @@ -package v1alpha1_metricsservice - -import ( - "context" - - "connectrpc.com/connect" - "github.com/jackc/pgx/v5/pgtype" - mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" - "github.com/nucleuscloud/neosync/backend/internal/apikey" - auth_apikey "github.com/nucleuscloud/neosync/backend/internal/auth/apikey" - nucleuserrors "github.com/nucleuscloud/neosync/backend/internal/errors" - "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" -) - -//nolint:unparam -func (s *Service) verifyUserInAccount( - ctx context.Context, - accountId string, -) (*pgtype.UUID, error) { - accountUuid, err := neosyncdb.ToUuid(accountId) - if err != nil { - return nil, err - } - - if isWorkerApiKey(ctx) { - return &accountUuid, nil - } - - resp, err := s.useraccountservice.IsUserInAccount(ctx, connect.NewRequest(&mgmtv1alpha1.IsUserInAccountRequest{AccountId: accountId})) - if err != nil { - return nil, err - } - if !resp.Msg.Ok { - return nil, nucleuserrors.NewForbidden("user in not in requested account") - } - - return &accountUuid, nil -} - -func isWorkerApiKey(ctx context.Context) bool { - data, err := auth_apikey.GetTokenDataFromCtx(ctx) - if err != nil { - return false - } - return data.ApiKeyType == apikey.WorkerApiKey -} - -// func (s *Service) getUserUuid( -// ctx context.Context, -// ) (*pgtype.UUID, error) { -// user, err := s.useraccountservice.GetUser(ctx, connect.NewRequest(&mgmtv1alpha1.GetUserRequest{})) -// if err != nil { -// return nil, err -// } -// userUuid, err := neosyncdb.ToUuid(user.Msg.UserId) -// if err != nil { -// return nil, err -// } -// return &userUuid, nil -// } diff --git a/backend/services/mgmt/v1alpha1/transformers-service/entities.go b/backend/services/mgmt/v1alpha1/transformers-service/entities.go index 8d17982b27..41f665d110 100644 --- a/backend/services/mgmt/v1alpha1/transformers-service/entities.go +++ b/backend/services/mgmt/v1alpha1/transformers-service/entities.go @@ -8,7 +8,9 @@ import ( "connectrpc.com/connect" mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect" + "github.com/nucleuscloud/neosync/backend/internal/ee/rbac" nucleuserrors "github.com/nucleuscloud/neosync/backend/internal/errors" + "github.com/nucleuscloud/neosync/backend/internal/userdata" presidioapi "github.com/nucleuscloud/neosync/internal/ee/presidio" ) @@ -26,7 +28,11 @@ func (s *Service) GetTransformPiiEntities( if s.entityclient == nil { return nil, nucleuserrors.NewInternalError("entity service is enabled but client was nil.") } - _, err := s.verifyUserInAccount(ctx, req.Msg.GetAccountId()) + user, err := s.userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + err = user.EnforceJob(ctx, userdata.NewWildcardDomainEntity(req.Msg.GetAccountId()), rbac.JobAction_View) if err != nil { return nil, err } diff --git a/backend/services/mgmt/v1alpha1/transformers-service/service.go b/backend/services/mgmt/v1alpha1/transformers-service/service.go index 56e3aa144c..37dfaf266a 100644 --- a/backend/services/mgmt/v1alpha1/transformers-service/service.go +++ b/backend/services/mgmt/v1alpha1/transformers-service/service.go @@ -1,16 +1,16 @@ package v1alpha1_transformersservice import ( - "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect" "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" + "github.com/nucleuscloud/neosync/backend/internal/userdata" presidioapi "github.com/nucleuscloud/neosync/internal/ee/presidio" ) type Service struct { - cfg *Config - db *neosyncdb.NeosyncDb - useraccountService mgmtv1alpha1connect.UserAccountServiceClient - entityclient presidioapi.EntityInterface + cfg *Config + db *neosyncdb.NeosyncDb + entityclient presidioapi.EntityInterface + userdataclient userdata.Interface } type Config struct { @@ -21,13 +21,13 @@ type Config struct { func New( cfg *Config, db *neosyncdb.NeosyncDb, - useraccountService mgmtv1alpha1connect.UserAccountServiceClient, recognizerclient presidioapi.EntityInterface, + userdataclient userdata.Interface, ) *Service { return &Service{ - cfg: cfg, - db: db, - useraccountService: useraccountService, - entityclient: recognizerclient, + cfg: cfg, + db: db, + entityclient: recognizerclient, + userdataclient: userdataclient, } } diff --git a/backend/services/mgmt/v1alpha1/transformers-service/user-account.go b/backend/services/mgmt/v1alpha1/transformers-service/user-account.go deleted file mode 100644 index 7b37ec17c9..0000000000 --- a/backend/services/mgmt/v1alpha1/transformers-service/user-account.go +++ /dev/null @@ -1,59 +0,0 @@ -package v1alpha1_transformersservice - -import ( - "context" - - "connectrpc.com/connect" - "github.com/jackc/pgx/v5/pgtype" - mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" - "github.com/nucleuscloud/neosync/backend/internal/apikey" - auth_apikey "github.com/nucleuscloud/neosync/backend/internal/auth/apikey" - nucleuserrors "github.com/nucleuscloud/neosync/backend/internal/errors" - "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" -) - -func (s *Service) verifyUserInAccount( - ctx context.Context, - accountId string, -) (*pgtype.UUID, error) { - accountUuid, err := neosyncdb.ToUuid(accountId) - if err != nil { - return nil, err - } - - if isWorkerApiKey(ctx) { - return &accountUuid, nil - } - - resp, err := s.useraccountService.IsUserInAccount(ctx, connect.NewRequest(&mgmtv1alpha1.IsUserInAccountRequest{AccountId: accountId})) - if err != nil { - return nil, err - } - if !resp.Msg.Ok { - return nil, nucleuserrors.NewForbidden("user in not in requested account") - } - - return &accountUuid, nil -} - -func (s *Service) getUserUuid( - ctx context.Context, -) (*pgtype.UUID, error) { - user, err := s.useraccountService.GetUser(ctx, connect.NewRequest(&mgmtv1alpha1.GetUserRequest{})) - if err != nil { - return nil, err - } - userUuid, err := neosyncdb.ToUuid(user.Msg.UserId) - if err != nil { - return nil, err - } - return &userUuid, nil -} - -func isWorkerApiKey(ctx context.Context) bool { - data, err := auth_apikey.GetTokenDataFromCtx(ctx) - if err != nil { - return false - } - return data.ApiKeyType == apikey.WorkerApiKey -} diff --git a/backend/services/mgmt/v1alpha1/transformers-service/userdefined_transformers.go b/backend/services/mgmt/v1alpha1/transformers-service/userdefined_transformers.go index 395e51094a..9159df007b 100644 --- a/backend/services/mgmt/v1alpha1/transformers-service/userdefined_transformers.go +++ b/backend/services/mgmt/v1alpha1/transformers-service/userdefined_transformers.go @@ -11,8 +11,10 @@ import ( mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" logger_interceptor "github.com/nucleuscloud/neosync/backend/internal/connect/interceptors/logger" "github.com/nucleuscloud/neosync/backend/internal/dtomaps" + "github.com/nucleuscloud/neosync/backend/internal/ee/rbac" nucleuserrors "github.com/nucleuscloud/neosync/backend/internal/errors" "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" + "github.com/nucleuscloud/neosync/backend/internal/userdata" pg_models "github.com/nucleuscloud/neosync/backend/sql/postgresql/models" ) @@ -20,12 +22,20 @@ func (s *Service) GetUserDefinedTransformers( ctx context.Context, req *connect.Request[mgmtv1alpha1.GetUserDefinedTransformersRequest], ) (*connect.Response[mgmtv1alpha1.GetUserDefinedTransformersResponse], error) { - accountUuid, err := s.verifyUserInAccount(ctx, req.Msg.AccountId) + user, err := s.userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + err = user.EnforceJob(ctx, userdata.NewWildcardDomainEntity(req.Msg.GetAccountId()), rbac.JobAction_View) + if err != nil { + return nil, err + } + accountUuid, err := neosyncdb.ToUuid(req.Msg.GetAccountId()) if err != nil { return nil, err } - transformers, err := s.db.Q.GetUserDefinedTransformersByAccount(ctx, s.db.Db, *accountUuid) + transformers, err := s.db.Q.GetUserDefinedTransformersByAccount(ctx, s.db.Db, accountUuid) if err != nil { return nil, err } @@ -49,7 +59,7 @@ func (s *Service) GetUserDefinedTransformerById( ctx context.Context, req *connect.Request[mgmtv1alpha1.GetUserDefinedTransformerByIdRequest], ) (*connect.Response[mgmtv1alpha1.GetUserDefinedTransformerByIdResponse], error) { - tId, err := neosyncdb.ToUuid(req.Msg.TransformerId) + tId, err := neosyncdb.ToUuid(req.Msg.GetTransformerId()) if err != nil { return nil, err } @@ -61,14 +71,18 @@ func (s *Service) GetUserDefinedTransformerById( return nil, nucleuserrors.NewNotFound("unable to find transformer by id") } - _, err = s.verifyUserInAccount(ctx, neosyncdb.UUIDString(transformer.AccountID)) + dto, err := dtomaps.ToUserDefinedTransformerDto(&transformer, s.getSystemTransformerSourceMap()) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to map user defined transformer %s with source %d: %w", neosyncdb.UUIDString(transformer.ID), transformer.Source, err) } - dto, err := dtomaps.ToUserDefinedTransformerDto(&transformer, s.getSystemTransformerSourceMap()) + user, err := s.userdataclient.GetUser(ctx) if err != nil { - return nil, fmt.Errorf("failed to map user defined transformer %s with source %d: %w", neosyncdb.UUIDString(transformer.ID), transformer.Source, err) + return nil, err + } + err = user.EnforceJob(ctx, userdata.NewWildcardDomainEntity(dto.GetAccountId()), rbac.JobAction_View) + if err != nil { + return nil, err } return connect.NewResponse(&mgmtv1alpha1.GetUserDefinedTransformerByIdResponse{ @@ -77,24 +91,27 @@ func (s *Service) GetUserDefinedTransformerById( } func (s *Service) CreateUserDefinedTransformer(ctx context.Context, req *connect.Request[mgmtv1alpha1.CreateUserDefinedTransformerRequest]) (*connect.Response[mgmtv1alpha1.CreateUserDefinedTransformerResponse], error) { - accountUuid, err := s.verifyUserInAccount(ctx, req.Msg.AccountId) + user, err := s.userdataclient.GetUser(ctx) if err != nil { return nil, err } - - userUuid, err := s.getUserUuid(ctx) + err = user.EnforceJob(ctx, userdata.NewWildcardDomainEntity(req.Msg.GetAccountId()), rbac.JobAction_Edit) + if err != nil { + return nil, err + } + accountUuid, err := neosyncdb.ToUuid(req.Msg.GetAccountId()) if err != nil { return nil, err } UserDefinedTransformer := &db_queries.CreateUserDefinedTransformerParams{ - AccountID: *accountUuid, + AccountID: accountUuid, Name: req.Msg.Name, Description: req.Msg.Description, TransformerConfig: &pg_models.TransformerConfig{}, Source: int32(req.Msg.Source), - CreatedByID: *userUuid, - UpdatedByID: *userUuid, + CreatedByID: user.PgId(), + UpdatedByID: user.PgId(), } err = UserDefinedTransformer.TransformerConfig.FromTransformerConfigDto(req.Msg.TransformerConfig) @@ -119,9 +136,9 @@ func (s *Service) CreateUserDefinedTransformer(ctx context.Context, req *connect func (s *Service) DeleteUserDefinedTransformer(ctx context.Context, req *connect.Request[mgmtv1alpha1.DeleteUserDefinedTransformerRequest]) (*connect.Response[mgmtv1alpha1.DeleteUserDefinedTransformerResponse], error) { logger := logger_interceptor.GetLoggerFromContextOrDefault(ctx) - logger = logger.With("transformerId", req.Msg.TransformerId) + logger = logger.With("transformerId", req.Msg.GetTransformerId()) - tId, err := neosyncdb.ToUuid(req.Msg.TransformerId) + tId, err := neosyncdb.ToUuid(req.Msg.GetTransformerId()) if err != nil { return nil, err } @@ -133,7 +150,11 @@ func (s *Service) DeleteUserDefinedTransformer(ctx context.Context, req *connect return connect.NewResponse(&mgmtv1alpha1.DeleteUserDefinedTransformerResponse{}), nil } - _, err = s.verifyUserInAccount(ctx, neosyncdb.UUIDString(transformer.AccountID)) + user, err := s.userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + err = user.EnforceJob(ctx, userdata.NewWildcardDomainEntity(neosyncdb.UUIDString(transformer.AccountID)), rbac.JobAction_Delete) if err != nil { return nil, err } @@ -160,12 +181,11 @@ func (s *Service) UpdateUserDefinedTransformer(ctx context.Context, req *connect return nil, nucleuserrors.NewNotFound("unable to find transformer by id") } - _, err = s.verifyUserInAccount(ctx, neosyncdb.UUIDString(transformer.AccountID)) + user, err := s.userdataclient.GetUser(ctx) if err != nil { return nil, err } - - userUuid, err := s.getUserUuid(ctx) + err = user.EnforceJob(ctx, userdata.NewWildcardDomainEntity(neosyncdb.UUIDString(transformer.AccountID)), rbac.JobAction_Edit) if err != nil { return nil, err } @@ -174,11 +194,11 @@ func (s *Service) UpdateUserDefinedTransformer(ctx context.Context, req *connect Name: req.Msg.Name, Description: req.Msg.Description, TransformerConfig: &pg_models.TransformerConfig{}, - UpdatedByID: *userUuid, + UpdatedByID: user.PgId(), ID: tUuid, } // todo: must verify that this updated config is valid for the configured source - err = updateParams.TransformerConfig.FromTransformerConfigDto(req.Msg.TransformerConfig) + err = updateParams.TransformerConfig.FromTransformerConfigDto(req.Msg.GetTransformerConfig()) if err != nil { return nil, err } @@ -199,13 +219,21 @@ func (s *Service) UpdateUserDefinedTransformer(ctx context.Context, req *connect } func (s *Service) IsTransformerNameAvailable(ctx context.Context, req *connect.Request[mgmtv1alpha1.IsTransformerNameAvailableRequest]) (*connect.Response[mgmtv1alpha1.IsTransformerNameAvailableResponse], error) { - accountUuid, err := s.verifyUserInAccount(ctx, req.Msg.AccountId) + user, err := s.userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + err = user.EnforceJob(ctx, userdata.NewWildcardDomainEntity(req.Msg.GetAccountId()), rbac.JobAction_View) + if err != nil { + return nil, err + } + accountUuid, err := neosyncdb.ToUuid(req.Msg.GetAccountId()) if err != nil { return nil, err } count, err := s.db.Q.IsTransformerNameAvailable(ctx, s.db.Db, db_queries.IsTransformerNameAvailableParams{ - AccountId: *accountUuid, + AccountId: accountUuid, TransformerName: req.Msg.TransformerName, }) if err != nil { diff --git a/backend/services/mgmt/v1alpha1/user-account-service/account-onboarding.go b/backend/services/mgmt/v1alpha1/user-account-service/account-onboarding.go index 2d59472c34..36d7720e94 100644 --- a/backend/services/mgmt/v1alpha1/user-account-service/account-onboarding.go +++ b/backend/services/mgmt/v1alpha1/user-account-service/account-onboarding.go @@ -6,6 +6,9 @@ import ( "connectrpc.com/connect" db_queries "github.com/nucleuscloud/neosync/backend/gen/go/db" mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" + "github.com/nucleuscloud/neosync/backend/internal/ee/rbac" + "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" + "github.com/nucleuscloud/neosync/backend/internal/userdata" pg_models "github.com/nucleuscloud/neosync/backend/sql/postgresql/models" ) @@ -13,12 +16,22 @@ func (s *Service) GetAccountOnboardingConfig( ctx context.Context, req *connect.Request[mgmtv1alpha1.GetAccountOnboardingConfigRequest], ) (*connect.Response[mgmtv1alpha1.GetAccountOnboardingConfigResponse], error) { - accountId, err := s.verifyUserInAccount(ctx, req.Msg.GetAccountId()) + userdataclient := userdata.NewClient(s, s.rbacClient) + user, err := userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + err = user.EnforceAccount(ctx, userdata.NewIdentifier(req.Msg.GetAccountId()), rbac.AccountAction_View) + if err != nil { + return nil, err + } + + accountUuid, err := neosyncdb.ToUuid(req.Msg.GetAccountId()) if err != nil { return nil, err } - oc, err := s.db.Q.GetAccountOnboardingConfig(ctx, s.db.Db, *accountId) + oc, err := s.db.Q.GetAccountOnboardingConfig(ctx, s.db.Db, accountUuid) if err != nil { return nil, err } @@ -32,7 +45,17 @@ func (s *Service) SetAccountOnboardingConfig( ctx context.Context, req *connect.Request[mgmtv1alpha1.SetAccountOnboardingConfigRequest], ) (*connect.Response[mgmtv1alpha1.SetAccountOnboardingConfigResponse], error) { - accountId, err := s.verifyUserInAccount(ctx, req.Msg.GetAccountId()) + userdataclient := userdata.NewClient(s, s.rbacClient) + user, err := userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + err = user.EnforceAccount(ctx, userdata.NewIdentifier(req.Msg.GetAccountId()), rbac.AccountAction_Edit) + if err != nil { + return nil, err + } + + accountUuid, err := neosyncdb.ToUuid(req.Msg.GetAccountId()) if err != nil { return nil, err } @@ -47,7 +70,7 @@ func (s *Service) SetAccountOnboardingConfig( account, err := s.db.Q.UpdateAccountOnboardingConfig(ctx, s.db.Db, db_queries.UpdateAccountOnboardingConfigParams{ OnboardingConfig: onboardingConfigModel, - AccountId: *accountId, + AccountId: accountUuid, }) if err != nil { return nil, err diff --git a/backend/services/mgmt/v1alpha1/user-account-service/account-temporal-config.go b/backend/services/mgmt/v1alpha1/user-account-service/account-temporal-config.go index b2206d9d4e..8c50a81179 100644 --- a/backend/services/mgmt/v1alpha1/user-account-service/account-temporal-config.go +++ b/backend/services/mgmt/v1alpha1/user-account-service/account-temporal-config.go @@ -6,7 +6,10 @@ import ( "connectrpc.com/connect" db_queries "github.com/nucleuscloud/neosync/backend/gen/go/db" mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" + "github.com/nucleuscloud/neosync/backend/internal/ee/rbac" nucleuserrors "github.com/nucleuscloud/neosync/backend/internal/errors" + "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" + "github.com/nucleuscloud/neosync/backend/internal/userdata" pg_models "github.com/nucleuscloud/neosync/backend/sql/postgresql/models" ) @@ -17,7 +20,12 @@ func (s *Service) GetAccountTemporalConfig( if s.cfg.IsNeosyncCloud { return nil, nucleuserrors.NewNotImplemented("not enabled in Neosync Cloud") } - _, err := s.verifyUserInAccount(ctx, req.Msg.GetAccountId()) + userdataclient := userdata.NewClient(s, s.rbacClient) + user, err := userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + err = user.EnforceAccount(ctx, userdata.NewIdentifier(req.Msg.GetAccountId()), rbac.AccountAction_View) if err != nil { return nil, err } @@ -39,7 +47,18 @@ func (s *Service) SetAccountTemporalConfig( if s.cfg.IsNeosyncCloud { return nil, nucleuserrors.NewNotImplemented("not enabled in Neosync Cloud") } - accountUuid, err := s.verifyUserInAccount(ctx, req.Msg.GetAccountId()) + userdataclient := userdata.NewClient(s, s.rbacClient) + user, err := userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + + err = user.EnforceAccount(ctx, userdata.NewIdentifier(req.Msg.GetAccountId()), rbac.AccountAction_Edit) + if err != nil { + return nil, err + } + + accountUuid, err := neosyncdb.ToUuid(req.Msg.GetAccountId()) if err != nil { return nil, err } @@ -54,7 +73,7 @@ func (s *Service) SetAccountTemporalConfig( _, err = s.db.Q.UpdateTemporalConfigByAccount(ctx, s.db.Db, db_queries.UpdateTemporalConfigByAccountParams{ TemporalConfig: tc, - AccountId: *accountUuid, + AccountId: accountUuid, }) if err != nil { return nil, err diff --git a/backend/services/mgmt/v1alpha1/user-account-service/billing.go b/backend/services/mgmt/v1alpha1/user-account-service/billing.go index 122bcf3465..0fe63dd5f2 100644 --- a/backend/services/mgmt/v1alpha1/user-account-service/billing.go +++ b/backend/services/mgmt/v1alpha1/user-account-service/billing.go @@ -14,8 +14,10 @@ import ( "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect" logger_interceptor "github.com/nucleuscloud/neosync/backend/internal/connect/interceptors/logger" "github.com/nucleuscloud/neosync/backend/internal/dtomaps" + "github.com/nucleuscloud/neosync/backend/internal/ee/rbac" nucleuserrors "github.com/nucleuscloud/neosync/backend/internal/errors" "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" + "github.com/nucleuscloud/neosync/backend/internal/userdata" "github.com/nucleuscloud/neosync/internal/billing" "github.com/stripe/stripe-go/v79" "google.golang.org/protobuf/types/known/timestamppb" @@ -32,16 +34,27 @@ func (s *Service) GetAccountStatus( ) (*connect.Response[mgmtv1alpha1.GetAccountStatusResponse], error) { logger := logger_interceptor.GetLoggerFromContextOrDefault(ctx) - accountId, err := s.verifyUserInAccount(ctx, req.Msg.GetAccountId()) + userdataclient := userdata.NewClient(s, s.rbacClient) + user, err := userdataclient.GetUser(ctx) if err != nil { return nil, err } - logger = logger.With("accountId", accountId) + err = user.EnforceAccount(ctx, userdata.NewIdentifier(req.Msg.GetAccountId()), rbac.AccountAction_View) + if err != nil { + return nil, err + } + + accountUuid, err := neosyncdb.ToUuid(req.Msg.GetAccountId()) + if err != nil { + return nil, err + } + + logger = logger.With("accountId", req.Msg.GetAccountId()) if !s.cfg.IsNeosyncCloud || s.billingclient == nil { return connect.NewResponse(&mgmtv1alpha1.GetAccountStatusResponse{}), nil } - account, err := s.db.Q.GetAccount(ctx, s.db.Db, *accountId) + account, err := s.db.Q.GetAccount(ctx, s.db.Db, accountUuid) if err != nil { return nil, fmt.Errorf("unable to retrieve account: %w", err) } @@ -212,12 +225,21 @@ func (s *Service) GetAccountBillingCheckoutSession( return nil, nucleuserrors.NewNotImplemented(fmt.Sprintf("%s is not implemented", strings.TrimPrefix(mgmtv1alpha1connect.UserAccountServiceGetAccountBillingCheckoutSessionProcedure, "/"))) } logger = logger.With("accountId", req.Msg.GetAccountId()) - accountId, err := s.verifyUserInAccount(ctx, req.Msg.GetAccountId()) + userdataclient := userdata.NewClient(s, s.rbacClient) + user, err := userdataclient.GetUser(ctx) if err != nil { return nil, err } - user, err := s.GetUser(ctx, connect.NewRequest(&mgmtv1alpha1.GetUserRequest{})) + accountUuid, err := neosyncdb.ToUuid(req.Msg.GetAccountId()) + if err != nil { + return nil, err + } + + err = user.EnforceAccount(ctx, userdata.NewIdentifier(req.Msg.GetAccountId()), rbac.AccountAction_Edit) + if err != nil { + return nil, err + } if err != nil { return nil, err } @@ -225,8 +247,8 @@ func (s *Service) GetAccountBillingCheckoutSession( // retrieve the account, creates a customer id if one doesn't already exist account, err := s.db.UpsertStripeCustomerId( ctx, - *accountId, - s.getCreateStripeAccountFunction(user.Msg.GetUserId(), logger), + accountUuid, + s.getCreateStripeAccountFunction(user.Id(), logger), logger, ) if err != nil { @@ -236,7 +258,7 @@ func (s *Service) GetAccountBillingCheckoutSession( return nil, errors.New("stripe customer id does not exist on account after creation attempt") } - session, err := s.generateCheckoutSession(account.StripeCustomerID.String, account.AccountSlug, user.Msg.GetUserId(), logger) + session, err := s.generateCheckoutSession(account.StripeCustomerID.String, account.AccountSlug, user.Id(), logger) if err != nil { return nil, fmt.Errorf("unable to generate billing checkout session: %w", err) } @@ -253,12 +275,23 @@ func (s *Service) GetAccountBillingPortalSession( if !s.cfg.IsNeosyncCloud || s.billingclient == nil { return nil, nucleuserrors.NewNotImplemented(fmt.Sprintf("%s is not implemented", strings.TrimPrefix(mgmtv1alpha1connect.UserAccountServiceGetAccountBillingPortalSessionProcedure, "/"))) } - accountId, err := s.verifyUserInAccount(ctx, req.Msg.GetAccountId()) + userdataclient := userdata.NewClient(s, s.rbacClient) + user, err := userdataclient.GetUser(ctx) if err != nil { return nil, err } - account, err := s.db.Q.GetAccount(ctx, s.db.Db, *accountId) + err = user.EnforceAccount(ctx, userdata.NewIdentifier(req.Msg.GetAccountId()), rbac.AccountAction_Edit) + if err != nil { + return nil, err + } + + accountUuid, err := neosyncdb.ToUuid(req.Msg.GetAccountId()) + if err != nil { + return nil, err + } + + account, err := s.db.Q.GetAccount(ctx, s.db.Db, accountUuid) if err != nil { return nil, err } @@ -279,7 +312,12 @@ func (s *Service) GetBillingAccounts( ctx context.Context, req *connect.Request[mgmtv1alpha1.GetBillingAccountsRequest], ) (*connect.Response[mgmtv1alpha1.GetBillingAccountsResponse], error) { - if s.cfg.IsNeosyncCloud && !isWorkerApiKey(ctx) { + userdataclient := userdata.NewClient(s, s.rbacClient) + user, err := userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + if s.cfg.IsNeosyncCloud && !user.IsWorkerApiKey() { return nil, nucleuserrors.NewUnauthorized("must provide valid authentication credentials for this endpoint") } @@ -312,7 +350,12 @@ func (s *Service) SetBillingMeterEvent( if s.billingclient == nil { return nil, nucleuserrors.NewUnauthorized("billing is not currently enabled") } - if s.cfg.IsNeosyncCloud && !isWorkerApiKey(ctx) { + userdataclient := userdata.NewClient(s, s.rbacClient) + user, err := userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + if s.cfg.IsNeosyncCloud && !user.IsWorkerApiKey() { return nil, nucleuserrors.NewUnauthorized("must provide valid authentication credentials for this endpoint") } diff --git a/backend/services/mgmt/v1alpha1/user-account-service/service.go b/backend/services/mgmt/v1alpha1/user-account-service/service.go index ebc06d107f..e401ea2d67 100644 --- a/backend/services/mgmt/v1alpha1/user-account-service/service.go +++ b/backend/services/mgmt/v1alpha1/user-account-service/service.go @@ -3,6 +3,7 @@ package v1alpha1_useraccountservice import ( auth_client "github.com/nucleuscloud/neosync/backend/internal/auth/client" "github.com/nucleuscloud/neosync/backend/internal/authmgmt" + "github.com/nucleuscloud/neosync/backend/internal/ee/rbac" "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" "github.com/nucleuscloud/neosync/backend/internal/temporal/clientmanager" "github.com/nucleuscloud/neosync/internal/billing" @@ -15,6 +16,7 @@ type Service struct { authclient auth_client.Interface authadminclient authmgmt.Interface billingclient billing.Interface + rbacClient rbac.Interface } type Config struct { @@ -30,6 +32,7 @@ func New( authclient auth_client.Interface, authadminclient authmgmt.Interface, billingclient billing.Interface, + rbacClient rbac.Interface, ) *Service { return &Service{ cfg: cfg, @@ -38,5 +41,6 @@ func New( authclient: authclient, authadminclient: authadminclient, billingclient: billingclient, + rbacClient: rbacClient, } } diff --git a/backend/services/mgmt/v1alpha1/user-account-service/users.go b/backend/services/mgmt/v1alpha1/user-account-service/users.go index 18016b4132..1dff668945 100644 --- a/backend/services/mgmt/v1alpha1/user-account-service/users.go +++ b/backend/services/mgmt/v1alpha1/user-account-service/users.go @@ -17,8 +17,10 @@ import ( "github.com/nucleuscloud/neosync/backend/internal/auth/tokenctx" logger_interceptor "github.com/nucleuscloud/neosync/backend/internal/connect/interceptors/logger" "github.com/nucleuscloud/neosync/backend/internal/dtomaps" + "github.com/nucleuscloud/neosync/backend/internal/ee/rbac" nucleuserrors "github.com/nucleuscloud/neosync/backend/internal/errors" "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" + "github.com/nucleuscloud/neosync/backend/internal/userdata" "github.com/nucleuscloud/neosync/backend/internal/version" "github.com/nucleuscloud/neosync/internal/billing" "github.com/stripe/stripe-go/v79" @@ -63,6 +65,10 @@ func (s *Service) GetUser( return connect.NewResponse(&mgmtv1alpha1.GetUserResponse{ UserId: neosyncdb.UUIDString(tokenctxResp.ApiKeyContextData.ApiKey.UserID), }), nil + } else if tokenctxResp.ApiKeyContextData.ApiKeyType == apikey.WorkerApiKey { + return connect.NewResponse(&mgmtv1alpha1.GetUserResponse{ + UserId: "00000000-0000-0000-0000-000000000000", + }), nil } return nil, nucleuserrors.NewUnauthenticated(fmt.Sprintf("invalid api key type when calling GetUser: %s", tokenctxResp.ApiKeyContextData.ApiKeyType)) } else if tokenctxResp.JwtContextData != nil { @@ -136,7 +142,7 @@ func (s *Service) GetUserAccounts( if err != nil { return nil, err } - userId, err := neosyncdb.ToUuid(user.Msg.UserId) + userId, err := neosyncdb.ToUuid(user.Msg.GetUserId()) if err != nil { return nil, err } @@ -194,6 +200,28 @@ func (s *Service) ConvertPersonalToTeamAccount( break } } + } else { + personalAccountUuid, err := neosyncdb.ToUuid(personalAccountId) + if err != nil { + return nil, err + } + count, err := s.db.Q.IsUserInAccount(ctx, s.db.Db, db_queries.IsUserInAccountParams{ + AccountId: personalAccountUuid, + UserId: userId, + }) + if err != nil { + return nil, err + } + if count == 0 { + return nil, nucleuserrors.NewNotFound("user is not in the provided account") + } + account, err := s.db.Q.GetAccount(ctx, s.db.Db, personalAccountUuid) + if err != nil { + return nil, err + } + if account.AccountType != int16(neosyncdb.AccountType_Personal) { + return nil, nucleuserrors.NewNotFound("account is not a personal account") + } } personalAccountUuid, err := neosyncdb.ToUuid(personalAccountId) @@ -245,7 +273,7 @@ func (s *Service) SetPersonalAccount( return nil, err } - userId, err := neosyncdb.ToUuid(user.Msg.UserId) + userId, err := neosyncdb.ToUuid(user.Msg.GetUserId()) if err != nil { return nil, err } @@ -255,6 +283,19 @@ func (s *Service) SetPersonalAccount( return nil, err } + logger := logger_interceptor.GetLoggerFromContextOrDefault(ctx) + logger = logger.With("accountId", neosyncdb.UUIDString(account.ID), "userId", user.Msg.GetUserId()) + + if err := s.rbacClient.SetupNewAccount(ctx, neosyncdb.UUIDString(account.ID), logger); err != nil { + // note: if this fails the account is kind of in a broken state... + return nil, fmt.Errorf("unable to setup new account, please reach out to support for further assistance: %w", err) + } + + if err := s.rbacClient.SetAccountRole(ctx, rbac.NewUserIdEntity(user.Msg.GetUserId()), rbac.NewAccountIdEntity(neosyncdb.UUIDString(account.ID)), mgmtv1alpha1.AccountRole_ACCOUNT_ROLE_ADMIN); err != nil { + // note: if this fails the account is kind of in a broken state... + return nil, fmt.Errorf("unable to set account role for user, please reach out to support for further assistance: %w", err) + } + return connect.NewResponse(&mgmtv1alpha1.SetPersonalAccountResponse{ AccountId: neosyncdb.UUIDString(account.ID), }), nil @@ -327,6 +368,8 @@ func (s *Service) CreateTeamAccount( return nil, err } + logger = logger.With("accountId", neosyncdb.UUIDString(account.ID)) + var checkoutSessionUrl *string if s.cfg.IsNeosyncCloud && !account.StripeCustomerID.Valid && s.billingclient != nil { account, err = s.db.UpsertStripeCustomerId( @@ -346,6 +389,16 @@ func (s *Service) CreateTeamAccount( checkoutSessionUrl = &session.URL } + if err := s.rbacClient.SetupNewAccount(ctx, neosyncdb.UUIDString(account.ID), logger); err != nil { + // note: if this fails the account is kind of in a broken state... + return nil, fmt.Errorf("unable to setup new account, please reach out to support for further assistance: %w", err) + } + + if err := s.rbacClient.SetAccountRole(ctx, rbac.NewUserIdEntity(user.Msg.GetUserId()), rbac.NewAccountIdEntity(neosyncdb.UUIDString(account.ID)), mgmtv1alpha1.AccountRole_ACCOUNT_ROLE_ADMIN); err != nil { + // note: if this fails the account is kind of in a broken state... + return nil, fmt.Errorf("unable to set account role for user, please reach out to support for further assistance: %w", err) + } + return connect.NewResponse(&mgmtv1alpha1.CreateTeamAccountResponse{ AccountId: neosyncdb.UUIDString(account.ID), CheckoutSessionUrl: checkoutSessionUrl, @@ -401,16 +454,26 @@ func (s *Service) GetTeamAccountMembers( req *connect.Request[mgmtv1alpha1.GetTeamAccountMembersRequest], ) (*connect.Response[mgmtv1alpha1.GetTeamAccountMembersResponse], error) { logger := logger_interceptor.GetLoggerFromContextOrDefault(ctx) - accountId, err := s.verifyUserInAccount(ctx, req.Msg.AccountId) + + userdataclient := userdata.NewClient(s, s.rbacClient) + user, err := userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + if err := user.EnforceAccount(ctx, userdata.NewIdentifier(req.Msg.GetAccountId()), rbac.AccountAction_View); err != nil { + return nil, err + } + + accountUuid, err := neosyncdb.ToUuid(req.Msg.AccountId) if err != nil { return nil, err } - if err := s.verifyTeamAccount(ctx, *accountId); err != nil { + if err := s.verifyTeamAccount(ctx, accountUuid); err != nil { return nil, err } - userIdentities, err := s.db.Q.GetUserIdentitiesByTeamAccount(ctx, s.db.Db, *accountId) + userIdentities, err := s.db.Q.GetUserIdentitiesByTeamAccount(ctx, s.db.Db, accountUuid) if err != nil { return nil, err } @@ -455,11 +518,21 @@ func (s *Service) RemoveTeamAccountMember( ctx context.Context, req *connect.Request[mgmtv1alpha1.RemoveTeamAccountMemberRequest], ) (*connect.Response[mgmtv1alpha1.RemoveTeamAccountMemberResponse], error) { - accountId, err := s.verifyUserInAccount(ctx, req.Msg.AccountId) + userdataclient := userdata.NewClient(s, s.rbacClient) + user, err := userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + if err := user.EnforceAccount(ctx, userdata.NewIdentifier(req.Msg.GetAccountId()), rbac.AccountAction_Edit); err != nil { + return nil, err + } + + accountUuid, err := neosyncdb.ToUuid(req.Msg.GetAccountId()) if err != nil { return nil, err } - if err := s.verifyTeamAccount(ctx, *accountId); err != nil { + + if err := s.verifyTeamAccount(ctx, accountUuid); err != nil { return nil, err } memberUserId, err := neosyncdb.ToUuid(req.Msg.UserId) @@ -467,11 +540,15 @@ func (s *Service) RemoveTeamAccountMember( return nil, err } err = s.db.Q.RemoveAccountUser(ctx, s.db.Db, db_queries.RemoveAccountUserParams{ - AccountId: *accountId, + AccountId: accountUuid, UserId: memberUserId, }) if err != nil && !neosyncdb.IsNoRows(err) { - return nil, err + return nil, fmt.Errorf("unable to remove account user from db: %w", err) + } + + if err := s.rbacClient.RemoveAccountUser(ctx, rbac.NewPgUserIdEntity(memberUserId), rbac.NewAccountIdEntity(neosyncdb.UUIDString(accountUuid))); err != nil { + return nil, fmt.Errorf("unable to remove account user from rbac engine: %w", err) } return connect.NewResponse(&mgmtv1alpha1.RemoveTeamAccountMemberResponse{}), nil @@ -481,21 +558,21 @@ func (s *Service) InviteUserToTeamAccount( ctx context.Context, req *connect.Request[mgmtv1alpha1.InviteUserToTeamAccountRequest], ) (*connect.Response[mgmtv1alpha1.InviteUserToTeamAccountResponse], error) { - user, err := s.GetUser(ctx, connect.NewRequest(&mgmtv1alpha1.GetUserRequest{})) + userdataclient := userdata.NewClient(s, s.rbacClient) + user, err := userdataclient.GetUser(ctx) if err != nil { return nil, err } - userId, err := neosyncdb.ToUuid(user.Msg.UserId) - if err != nil { + if err := user.EnforceAccount(ctx, userdata.NewIdentifier(req.Msg.GetAccountId()), rbac.AccountAction_Edit); err != nil { return nil, err } - accountId, err := s.verifyUserInAccount(ctx, req.Msg.AccountId) + accountUuid, err := neosyncdb.ToUuid(req.Msg.GetAccountId()) if err != nil { return nil, err } - if err := s.verifyTeamAccount(ctx, *accountId); err != nil { + if err := s.verifyTeamAccount(ctx, accountUuid); err != nil { return nil, err } @@ -505,7 +582,9 @@ func (s *Service) InviteUserToTeamAccount( return nil, err } - invite, err := s.db.CreateTeamAccountInvite(ctx, *accountId, userId, req.Msg.Email, expiresAt) + // todo: this method will need the intended role for the user + + invite, err := s.db.CreateTeamAccountInvite(ctx, accountUuid, user.PgId(), req.Msg.GetEmail(), expiresAt) if err != nil { return nil, err } @@ -519,16 +598,25 @@ func (s *Service) GetTeamAccountInvites( ctx context.Context, req *connect.Request[mgmtv1alpha1.GetTeamAccountInvitesRequest], ) (*connect.Response[mgmtv1alpha1.GetTeamAccountInvitesResponse], error) { - accountId, err := s.verifyUserInAccount(ctx, req.Msg.AccountId) + userdataclient := userdata.NewClient(s, s.rbacClient) + user, err := userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + if err := user.EnforceAccount(ctx, userdata.NewIdentifier(req.Msg.GetAccountId()), rbac.AccountAction_View); err != nil { + return nil, err + } + + accountUuid, err := neosyncdb.ToUuid(req.Msg.GetAccountId()) if err != nil { return nil, err } - if err := s.verifyTeamAccount(ctx, *accountId); err != nil { + if err := s.verifyTeamAccount(ctx, accountUuid); err != nil { return nil, err } - invites, err := s.db.Q.GetActiveAccountInvites(ctx, s.db.Db, *accountId) + invites, err := s.db.Q.GetActiveAccountInvites(ctx, s.db.Db, accountUuid) if err != nil && !neosyncdb.IsNoRows(err) { return nil, nucleuserrors.New(err) } else if err != nil && neosyncdb.IsNoRows(err) { @@ -551,7 +639,7 @@ func (s *Service) RemoveTeamAccountInvite( ctx context.Context, req *connect.Request[mgmtv1alpha1.RemoveTeamAccountInviteRequest], ) (*connect.Response[mgmtv1alpha1.RemoveTeamAccountInviteResponse], error) { - inviteId, err := neosyncdb.ToUuid(req.Msg.Id) + inviteId, err := neosyncdb.ToUuid(req.Msg.GetId()) if err != nil { return nil, err } @@ -561,20 +649,23 @@ func (s *Service) RemoveTeamAccountInvite( } else if err != nil && neosyncdb.IsNoRows(err) { return connect.NewResponse(&mgmtv1alpha1.RemoveTeamAccountInviteResponse{}), nil } - accountId, err := s.verifyUserInAccount(ctx, neosyncdb.UUIDString(invite.AccountID)) + + userdataclient := userdata.NewClient(s, s.rbacClient) + user, err := userdataclient.GetUser(ctx) if err != nil { return nil, err } + if err := user.EnforceAccount(ctx, userdata.NewIdentifier(neosyncdb.UUIDString(invite.AccountID)), rbac.AccountAction_Edit); err != nil { + return nil, err + } - if err := s.verifyTeamAccount(ctx, *accountId); err != nil { + if err := s.verifyTeamAccount(ctx, invite.AccountID); err != nil { return nil, err } err = s.db.Q.RemoveAccountInvite(ctx, s.db.Db, inviteId) if err != nil && !neosyncdb.IsNoRows(err) { return nil, nucleuserrors.New(err) - } else if err != nil && neosyncdb.IsNoRows(err) { - return connect.NewResponse(&mgmtv1alpha1.RemoveTeamAccountInviteResponse{}), nil } return connect.NewResponse(&mgmtv1alpha1.RemoveTeamAccountInviteResponse{}), nil @@ -624,6 +715,11 @@ func (s *Service) AcceptTeamAccountInvite( return nil, err } + // todo: this should be updated to set the intended role based on what was configured in the invite + if err := s.rbacClient.SetAccountRole(ctx, rbac.NewUserIdEntity(user.Msg.GetUserId()), rbac.NewAccountIdEntity(neosyncdb.UUIDString(accountId)), mgmtv1alpha1.AccountRole_ACCOUNT_ROLE_ADMIN); err != nil { + return nil, fmt.Errorf("unable to set account role for user, please reach out to support for further assistance: %w", err) + } + if err := s.verifyTeamAccount(ctx, accountId); err != nil { return nil, err } @@ -638,46 +734,58 @@ func (s *Service) AcceptTeamAccountInvite( }), nil } -func (s *Service) verifyTeamAccount(ctx context.Context, accountId pgtype.UUID) error { - account, err := s.db.Q.GetAccount(ctx, s.db.Db, accountId) +func (s *Service) SetUserRole( + ctx context.Context, + req *connect.Request[mgmtv1alpha1.SetUserRoleRequest], +) (*connect.Response[mgmtv1alpha1.SetUserRoleResponse], error) { + userdataclient := userdata.NewClient(s, s.rbacClient) + user, err := userdataclient.GetUser(ctx) if err != nil { - return err + return nil, err } - if account.AccountType != 1 { - return nucleuserrors.NewForbidden("account is not a team account") + + if err := user.EnforceAccount(ctx, userdata.NewIdentifier(req.Msg.GetAccountId()), rbac.AccountAction_Edit); err != nil { + return nil, err } - return nil -} -func isWorkerApiKey(ctx context.Context) bool { - data, err := auth_apikey.GetTokenDataFromCtx(ctx) + + accountUuid, err := neosyncdb.ToUuid(req.Msg.GetAccountId()) if err != nil { - return false + return nil, err } - return data.ApiKeyType == apikey.WorkerApiKey -} -func (s *Service) verifyUserInAccount( - ctx context.Context, - accountId string, -) (*pgtype.UUID, error) { - accountUuid, err := neosyncdb.ToUuid(accountId) + requestingUserUuid, err := neosyncdb.ToUuid(req.Msg.GetUserId()) if err != nil { return nil, err } - if isWorkerApiKey(ctx) { - return &accountUuid, nil + count, err := s.db.Q.IsUserInAccount(ctx, s.db.Db, db_queries.IsUserInAccountParams{ + AccountId: accountUuid, + UserId: requestingUserUuid, + }) + if err != nil { + return nil, err + } + if count == 0 { + return nil, nucleuserrors.NewBadRequest("provided user id is not in account") } - resp, err := s.IsUserInAccount(ctx, connect.NewRequest(&mgmtv1alpha1.IsUserInAccountRequest{AccountId: accountId})) + err = s.rbacClient.SetAccountRole(ctx, rbac.NewPgUserIdEntity(requestingUserUuid), rbac.NewAccountIdEntity(req.Msg.GetAccountId()), req.Msg.GetRole()) if err != nil { return nil, err } - if !resp.Msg.Ok { - return nil, nucleuserrors.NewForbidden("user in not in requested account") - } - return &accountUuid, nil + return connect.NewResponse(&mgmtv1alpha1.SetUserRoleResponse{}), nil +} + +func (s *Service) verifyTeamAccount(ctx context.Context, accountId pgtype.UUID) error { + account, err := s.db.Q.GetAccount(ctx, s.db.Db, accountId) + if err != nil { + return err + } + if account.AccountType != int16(neosyncdb.AccountType_Team) && account.AccountType != int16(neosyncdb.AccountType_Enterprise) { + return nucleuserrors.NewForbidden("account is not a team account") + } + return nil } func (s *Service) GetSystemInformation(ctx context.Context, req *connect.Request[mgmtv1alpha1.GetSystemInformationRequest]) (*connect.Response[mgmtv1alpha1.GetSystemInformationResponse], error) { diff --git a/backend/sql/postgresql/queries/users.sql b/backend/sql/postgresql/queries/users.sql index 5ac3c03265..534d4cfcdb 100644 --- a/backend/sql/postgresql/queries/users.sql +++ b/backend/sql/postgresql/queries/users.sql @@ -28,11 +28,19 @@ INSERT INTO neosync_api.users ( RETURNING *; -- name: GetUserIdentitiesByTeamAccount :many -SELECT aipa.* FROM neosync_api.user_identity_provider_associations aipa -JOIN neosync_api.account_user_associations aua ON aua.user_id = aipa.user_id -JOIN neosync_api.accounts a ON a.id = aua.account_id +SELECT aipa.* +FROM neosync_api.user_identity_provider_associations aipa +INNER JOIN neosync_api.account_user_associations aua ON aua.user_id = aipa.user_id +INNER JOIN neosync_api.accounts a ON a.id = aua.account_id WHERE aua.account_id = sqlc.arg('accountId') AND a.account_type = 1; +-- name: GetAccountUsers :many +SELECT u.id +FROM neosync_api.users u +INNER JOIN neosync_api.account_user_associations aua ON aua.user_id = u.id +INNER JOIN neosync_api.accounts a ON a.id = aua.account_id +WHERE a.id = sqlc.arg('accountId') AND u.user_type = 0; + -- name: GetUserIdentityByUserId :one SELECT aipa.* FROM neosync_api.user_identity_provider_associations aipa WHERE aipa.user_id = $1; @@ -226,3 +234,6 @@ SET account_slug = sqlc.arg('teamName'), max_allowed_records = NULL WHERE id = sqlc.arg('accountId') RETURNING *; + +-- name: GetAccountIds :many +SELECT id FROM neosync_api.accounts; diff --git a/backend/sql/postgresql/schema/20241210211459_add-rbac-casbin.down.sql b/backend/sql/postgresql/schema/20241210211459_add-rbac-casbin.down.sql new file mode 100644 index 0000000000..88cd884ff0 --- /dev/null +++ b/backend/sql/postgresql/schema/20241210211459_add-rbac-casbin.down.sql @@ -0,0 +1,2 @@ +DROP INDEX IF EXISTS neosync_api.idx_casbin_rule; +DROP TABLE IF EXISTS neosync_api.casbin_rule; diff --git a/backend/sql/postgresql/schema/20241210211459_add-rbac-casbin.up.sql b/backend/sql/postgresql/schema/20241210211459_add-rbac-casbin.up.sql new file mode 100644 index 0000000000..5fce06e7e4 --- /dev/null +++ b/backend/sql/postgresql/schema/20241210211459_add-rbac-casbin.up.sql @@ -0,0 +1,19 @@ +CREATE TABLE IF NOT EXISTS neosync_api.casbin_rule( + p_type VARCHAR(32) DEFAULT '' NOT NULL, + v0 VARCHAR(255) DEFAULT '' NOT NULL, + v1 VARCHAR(255) DEFAULT '' NOT NULL, + v2 VARCHAR(255) DEFAULT '' NOT NULL, + v3 VARCHAR(255) DEFAULT '' NOT NULL, + v4 VARCHAR(255) DEFAULT '' NOT NULL, + v5 VARCHAR(255) DEFAULT '' NOT NULL, + created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP +); +CREATE INDEX IF NOT EXISTS "idx_neosync_api_casbin_rule" ON neosync_api.casbin_rule (p_type, v0, v1); +CREATE UNIQUE INDEX IF NOT EXISTS "idx_neosync_api_casbin_rule_unique_policy" +ON neosync_api.casbin_rule (p_type, v0, v1, v2, v3, v4, v5); + +CREATE TRIGGER update_neosync_api_casbin_rule_updated_at +BEFORE UPDATE ON neosync_api.casbin_rule +FOR EACH ROW +EXECUTE FUNCTION update_updated_at_column(); diff --git a/docs/openapi/mgmt/v1alpha1/user_account.openapi.yaml b/docs/openapi/mgmt/v1alpha1/user_account.openapi.yaml index 2a10cfaa8b..21aa9b37a7 100644 --- a/docs/openapi/mgmt/v1alpha1/user_account.openapi.yaml +++ b/docs/openapi/mgmt/v1alpha1/user_account.openapi.yaml @@ -851,8 +851,53 @@ paths: application/json: schema: $ref: '#/components/schemas/mgmt.v1alpha1.SetBillingMeterEventResponse' + /mgmt.v1alpha1.UserAccountService/SetUserRole: + post: + tags: + - mgmt.v1alpha1.UserAccountService + summary: SetUserRole + description: Sets the users role + operationId: mgmt.v1alpha1.UserAccountService.SetUserRole + parameters: + - name: Connect-Protocol-Version + in: header + required: true + schema: + $ref: '#/components/schemas/connect-protocol-version' + - name: Connect-Timeout-Ms + in: header + schema: + $ref: '#/components/schemas/connect-timeout-header' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/mgmt.v1alpha1.SetUserRoleRequest' + required: true + responses: + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/mgmt.v1alpha1.SetUserRoleResponse' components: schemas: + mgmt.v1alpha1.AccountRole: + type: string + title: AccountRole + enum: + - ACCOUNT_ROLE_UNSPECIFIED + - ACCOUNT_ROLE_ADMIN + - ACCOUNT_ROLE_JOB_DEVELOPER + - ACCOUNT_ROLE_JOB_VIEWER + - ACCOUNT_ROLE_JOB_EXECUTOR mgmt.v1alpha1.AccountStatus: type: string title: AccountStatus @@ -1701,6 +1746,30 @@ components: title: user_id title: SetUserResponse additionalProperties: false + mgmt.v1alpha1.SetUserRoleRequest: + type: object + properties: + accountId: + type: string + title: account_id + format: uuid + description: The account id to apply this role to + userId: + type: string + title: user_id + format: uuid + description: The user that this will be applied to + role: + allOf: + - title: role + description: The role that this user will obtain + - $ref: '#/components/schemas/mgmt.v1alpha1.AccountRole' + title: SetUserRoleRequest + additionalProperties: false + mgmt.v1alpha1.SetUserRoleResponse: + type: object + title: SetUserRoleResponse + additionalProperties: false mgmt.v1alpha1.UserAccount: type: object properties: diff --git a/docs/openapi/neosync.mgmt.v1alpha1.yaml b/docs/openapi/neosync.mgmt.v1alpha1.yaml index 089a5fb018..59dba30864 100644 --- a/docs/openapi/neosync.mgmt.v1alpha1.yaml +++ b/docs/openapi/neosync.mgmt.v1alpha1.yaml @@ -4020,6 +4020,43 @@ paths: schema: $ref: '#/components/schemas/connect.error' security: [] + /mgmt.v1alpha1.UserAccountService/SetUserRole: + post: + tags: + - mgmt.v1alpha1.UserAccountService + summary: SetUserRole + description: Sets the users role + operationId: mgmt.v1alpha1.UserAccountService.SetUserRole + parameters: + - name: Connect-Protocol-Version + in: header + required: true + schema: + $ref: '#/components/schemas/connect-protocol-version' + - name: Connect-Timeout-Ms + in: header + schema: + $ref: '#/components/schemas/connect-timeout-header' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/mgmt.v1alpha1.SetUserRoleRequest' + required: true + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/mgmt.v1alpha1.SetUserRoleResponse' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + security: [] components: schemas: mgmt.v1alpha1.GenerateEmailType: @@ -12256,6 +12293,15 @@ components: title: valid title: ValidateUserRegexCodeResponse additionalProperties: false + mgmt.v1alpha1.AccountRole: + type: string + title: AccountRole + enum: + - ACCOUNT_ROLE_UNSPECIFIED + - ACCOUNT_ROLE_ADMIN + - ACCOUNT_ROLE_JOB_DEVELOPER + - ACCOUNT_ROLE_JOB_VIEWER + - ACCOUNT_ROLE_JOB_EXECUTOR mgmt.v1alpha1.AccountStatus: type: string title: AccountStatus @@ -13028,6 +13074,30 @@ components: title: user_id title: SetUserResponse additionalProperties: false + mgmt.v1alpha1.SetUserRoleRequest: + type: object + properties: + accountId: + type: string + title: account_id + format: uuid + description: The account id to apply this role to + userId: + type: string + title: user_id + format: uuid + description: The user that this will be applied to + role: + allOf: + - title: role + description: The role that this user will obtain + - $ref: '#/components/schemas/mgmt.v1alpha1.AccountRole' + title: SetUserRoleRequest + additionalProperties: false + mgmt.v1alpha1.SetUserRoleResponse: + type: object + title: SetUserRoleResponse + additionalProperties: false mgmt.v1alpha1.UserAccount: type: object properties: diff --git a/docs/protos/mgmt/v1alpha1/user_account.proto.mdx b/docs/protos/mgmt/v1alpha1/user_account.proto.mdx index 275a203157..393fd9277d 100644 --- a/docs/protos/mgmt/v1alpha1/user_account.proto.mdx +++ b/docs/protos/mgmt/v1alpha1/user_account.proto.mdx @@ -225,23 +225,35 @@ _**package** mgmt.v1alpha1_ +### `SetUserRoleRequest` + + + +### `SetUserRoleResponse` + + + ### `UserAccount` - + --- ## Enums +### `AccountRole` + + + ### `AccountStatus` - + ### `BillingStatus` - + ### `UserAccountType` - + --- ## Services @@ -348,6 +360,10 @@ _**package** mgmt.v1alpha1_ +#### `SetUserRole` + + + --- diff --git a/docs/protos/proto_docs.json b/docs/protos/proto_docs.json index 09fbb1a405..ece92cc186 100644 --- a/docs/protos/proto_docs.json +++ b/docs/protos/proto_docs.json @@ -15690,6 +15690,39 @@ "hasMessages": true, "hasServices": true, "enums": [ + { + "name": "AccountRole", + "longName": "AccountRole", + "fullName": "mgmt.v1alpha1.AccountRole", + "description": "", + "values": [ + { + "name": "ACCOUNT_ROLE_UNSPECIFIED", + "number": "0", + "description": "Default value, this should not be used, but will default to ACCOUNT_ROLE_JOB_VIEWER" + }, + { + "name": "ACCOUNT_ROLE_ADMIN", + "number": "1", + "description": "Admin, can do anything in the account." + }, + { + "name": "ACCOUNT_ROLE_JOB_DEVELOPER", + "number": "2", + "description": "Can view, edit jobs and connections" + }, + { + "name": "ACCOUNT_ROLE_JOB_VIEWER", + "number": "3", + "description": "Can view" + }, + { + "name": "ACCOUNT_ROLE_JOB_EXECUTOR", + "number": "4", + "description": "Can view and execute" + } + ] + }, { "name": "AccountStatus", "longName": "AccountStatus", @@ -17440,6 +17473,65 @@ } ] }, + { + "name": "SetUserRoleRequest", + "longName": "SetUserRoleRequest", + "fullName": "mgmt.v1alpha1.SetUserRoleRequest", + "description": "", + "hasExtensions": false, + "hasFields": true, + "hasOneofs": false, + "extensions": [], + "fields": [ + { + "name": "account_id", + "description": "The account id to apply this role to", + "label": "", + "type": "string", + "longType": "string", + "fullType": "string", + "ismap": false, + "isoneof": false, + "oneofdecl": "", + "defaultValue": "" + }, + { + "name": "user_id", + "description": "The user that this will be applied to", + "label": "", + "type": "string", + "longType": "string", + "fullType": "string", + "ismap": false, + "isoneof": false, + "oneofdecl": "", + "defaultValue": "" + }, + { + "name": "role", + "description": "The role that this user will obtain", + "label": "", + "type": "AccountRole", + "longType": "AccountRole", + "fullType": "mgmt.v1alpha1.AccountRole", + "ismap": false, + "isoneof": false, + "oneofdecl": "", + "defaultValue": "" + } + ] + }, + { + "name": "SetUserRoleResponse", + "longName": "SetUserRoleResponse", + "fullName": "mgmt.v1alpha1.SetUserRoleResponse", + "description": "", + "hasExtensions": false, + "hasFields": false, + "hasOneofs": false, + "extensions": [], + "fields": [] + }, { "name": "UserAccount", "longName": "UserAccount", @@ -17807,6 +17899,18 @@ "responseLongType": "SetBillingMeterEventResponse", "responseFullType": "mgmt.v1alpha1.SetBillingMeterEventResponse", "responseStreaming": false + }, + { + "name": "SetUserRole", + "description": "Sets the users role", + "requestType": "SetUserRoleRequest", + "requestLongType": "SetUserRoleRequest", + "requestFullType": "mgmt.v1alpha1.SetUserRoleRequest", + "requestStreaming": false, + "responseType": "SetUserRoleResponse", + "responseLongType": "SetUserRoleResponse", + "responseFullType": "mgmt.v1alpha1.SetUserRoleResponse", + "responseStreaming": false } ] } diff --git a/frontend/packages/sdk/src/client/mgmt/v1alpha1/user_account-UserAccountService_connectquery.ts b/frontend/packages/sdk/src/client/mgmt/v1alpha1/user_account-UserAccountService_connectquery.ts index 8e3ef4775b..501ef5d663 100644 --- a/frontend/packages/sdk/src/client/mgmt/v1alpha1/user_account-UserAccountService_connectquery.ts +++ b/frontend/packages/sdk/src/client/mgmt/v1alpha1/user_account-UserAccountService_connectquery.ts @@ -4,7 +4,7 @@ // @ts-nocheck import { MethodIdempotency, MethodKind } from "@bufbuild/protobuf"; -import { AcceptTeamAccountInviteRequest, AcceptTeamAccountInviteResponse, ConvertPersonalToTeamAccountRequest, ConvertPersonalToTeamAccountResponse, CreateTeamAccountRequest, CreateTeamAccountResponse, GetAccountBillingCheckoutSessionRequest, GetAccountBillingCheckoutSessionResponse, GetAccountBillingPortalSessionRequest, GetAccountBillingPortalSessionResponse, GetAccountOnboardingConfigRequest, GetAccountOnboardingConfigResponse, GetAccountStatusRequest, GetAccountStatusResponse, GetAccountTemporalConfigRequest, GetAccountTemporalConfigResponse, GetBillingAccountsRequest, GetBillingAccountsResponse, GetSystemInformationRequest, GetSystemInformationResponse, GetTeamAccountInvitesRequest, GetTeamAccountInvitesResponse, GetTeamAccountMembersRequest, GetTeamAccountMembersResponse, GetUserAccountsRequest, GetUserAccountsResponse, GetUserRequest, GetUserResponse, InviteUserToTeamAccountRequest, InviteUserToTeamAccountResponse, IsAccountStatusValidRequest, IsAccountStatusValidResponse, IsUserInAccountRequest, IsUserInAccountResponse, RemoveTeamAccountInviteRequest, RemoveTeamAccountInviteResponse, RemoveTeamAccountMemberRequest, RemoveTeamAccountMemberResponse, SetAccountOnboardingConfigRequest, SetAccountOnboardingConfigResponse, SetAccountTemporalConfigRequest, SetAccountTemporalConfigResponse, SetBillingMeterEventRequest, SetBillingMeterEventResponse, SetPersonalAccountRequest, SetPersonalAccountResponse, SetUserRequest, SetUserResponse } from "./user_account_pb.js"; +import { AcceptTeamAccountInviteRequest, AcceptTeamAccountInviteResponse, ConvertPersonalToTeamAccountRequest, ConvertPersonalToTeamAccountResponse, CreateTeamAccountRequest, CreateTeamAccountResponse, GetAccountBillingCheckoutSessionRequest, GetAccountBillingCheckoutSessionResponse, GetAccountBillingPortalSessionRequest, GetAccountBillingPortalSessionResponse, GetAccountOnboardingConfigRequest, GetAccountOnboardingConfigResponse, GetAccountStatusRequest, GetAccountStatusResponse, GetAccountTemporalConfigRequest, GetAccountTemporalConfigResponse, GetBillingAccountsRequest, GetBillingAccountsResponse, GetSystemInformationRequest, GetSystemInformationResponse, GetTeamAccountInvitesRequest, GetTeamAccountInvitesResponse, GetTeamAccountMembersRequest, GetTeamAccountMembersResponse, GetUserAccountsRequest, GetUserAccountsResponse, GetUserRequest, GetUserResponse, InviteUserToTeamAccountRequest, InviteUserToTeamAccountResponse, IsAccountStatusValidRequest, IsAccountStatusValidResponse, IsUserInAccountRequest, IsUserInAccountResponse, RemoveTeamAccountInviteRequest, RemoveTeamAccountInviteResponse, RemoveTeamAccountMemberRequest, RemoveTeamAccountMemberResponse, SetAccountOnboardingConfigRequest, SetAccountOnboardingConfigResponse, SetAccountTemporalConfigRequest, SetAccountTemporalConfigResponse, SetBillingMeterEventRequest, SetBillingMeterEventResponse, SetPersonalAccountRequest, SetPersonalAccountResponse, SetUserRequest, SetUserResponse, SetUserRoleRequest, SetUserRoleResponse } from "./user_account_pb.js"; /** * @generated from rpc mgmt.v1alpha1.UserAccountService.GetUser @@ -361,3 +361,19 @@ export const setBillingMeterEvent = { typeName: "mgmt.v1alpha1.UserAccountService" } } as const; + +/** + * Sets the users role + * + * @generated from rpc mgmt.v1alpha1.UserAccountService.SetUserRole + */ +export const setUserRole = { + localName: "setUserRole", + name: "SetUserRole", + kind: MethodKind.Unary, + I: SetUserRoleRequest, + O: SetUserRoleResponse, + service: { + typeName: "mgmt.v1alpha1.UserAccountService" + } +} as const; diff --git a/frontend/packages/sdk/src/client/mgmt/v1alpha1/user_account_connect.ts b/frontend/packages/sdk/src/client/mgmt/v1alpha1/user_account_connect.ts index ecb51ea4f1..8ec3eb5d90 100644 --- a/frontend/packages/sdk/src/client/mgmt/v1alpha1/user_account_connect.ts +++ b/frontend/packages/sdk/src/client/mgmt/v1alpha1/user_account_connect.ts @@ -3,7 +3,7 @@ /* eslint-disable */ // @ts-nocheck -import { AcceptTeamAccountInviteRequest, AcceptTeamAccountInviteResponse, ConvertPersonalToTeamAccountRequest, ConvertPersonalToTeamAccountResponse, CreateTeamAccountRequest, CreateTeamAccountResponse, GetAccountBillingCheckoutSessionRequest, GetAccountBillingCheckoutSessionResponse, GetAccountBillingPortalSessionRequest, GetAccountBillingPortalSessionResponse, GetAccountOnboardingConfigRequest, GetAccountOnboardingConfigResponse, GetAccountStatusRequest, GetAccountStatusResponse, GetAccountTemporalConfigRequest, GetAccountTemporalConfigResponse, GetBillingAccountsRequest, GetBillingAccountsResponse, GetSystemInformationRequest, GetSystemInformationResponse, GetTeamAccountInvitesRequest, GetTeamAccountInvitesResponse, GetTeamAccountMembersRequest, GetTeamAccountMembersResponse, GetUserAccountsRequest, GetUserAccountsResponse, GetUserRequest, GetUserResponse, InviteUserToTeamAccountRequest, InviteUserToTeamAccountResponse, IsAccountStatusValidRequest, IsAccountStatusValidResponse, IsUserInAccountRequest, IsUserInAccountResponse, RemoveTeamAccountInviteRequest, RemoveTeamAccountInviteResponse, RemoveTeamAccountMemberRequest, RemoveTeamAccountMemberResponse, SetAccountOnboardingConfigRequest, SetAccountOnboardingConfigResponse, SetAccountTemporalConfigRequest, SetAccountTemporalConfigResponse, SetBillingMeterEventRequest, SetBillingMeterEventResponse, SetPersonalAccountRequest, SetPersonalAccountResponse, SetUserRequest, SetUserResponse } from "./user_account_pb.js"; +import { AcceptTeamAccountInviteRequest, AcceptTeamAccountInviteResponse, ConvertPersonalToTeamAccountRequest, ConvertPersonalToTeamAccountResponse, CreateTeamAccountRequest, CreateTeamAccountResponse, GetAccountBillingCheckoutSessionRequest, GetAccountBillingCheckoutSessionResponse, GetAccountBillingPortalSessionRequest, GetAccountBillingPortalSessionResponse, GetAccountOnboardingConfigRequest, GetAccountOnboardingConfigResponse, GetAccountStatusRequest, GetAccountStatusResponse, GetAccountTemporalConfigRequest, GetAccountTemporalConfigResponse, GetBillingAccountsRequest, GetBillingAccountsResponse, GetSystemInformationRequest, GetSystemInformationResponse, GetTeamAccountInvitesRequest, GetTeamAccountInvitesResponse, GetTeamAccountMembersRequest, GetTeamAccountMembersResponse, GetUserAccountsRequest, GetUserAccountsResponse, GetUserRequest, GetUserResponse, InviteUserToTeamAccountRequest, InviteUserToTeamAccountResponse, IsAccountStatusValidRequest, IsAccountStatusValidResponse, IsUserInAccountRequest, IsUserInAccountResponse, RemoveTeamAccountInviteRequest, RemoveTeamAccountInviteResponse, RemoveTeamAccountMemberRequest, RemoveTeamAccountMemberResponse, SetAccountOnboardingConfigRequest, SetAccountOnboardingConfigResponse, SetAccountTemporalConfigRequest, SetAccountTemporalConfigResponse, SetBillingMeterEventRequest, SetBillingMeterEventResponse, SetPersonalAccountRequest, SetPersonalAccountResponse, SetUserRequest, SetUserResponse, SetUserRoleRequest, SetUserRoleResponse } from "./user_account_pb.js"; import { MethodIdempotency, MethodKind } from "@bufbuild/protobuf"; /** @@ -248,6 +248,17 @@ export const UserAccountService = { O: SetBillingMeterEventResponse, kind: MethodKind.Unary, }, + /** + * Sets the users role + * + * @generated from rpc mgmt.v1alpha1.UserAccountService.SetUserRole + */ + setUserRole: { + name: "SetUserRole", + I: SetUserRoleRequest, + O: SetUserRoleResponse, + kind: MethodKind.Unary, + }, } } as const; diff --git a/frontend/packages/sdk/src/client/mgmt/v1alpha1/user_account_pb.ts b/frontend/packages/sdk/src/client/mgmt/v1alpha1/user_account_pb.ts index 7e381ff0b0..bc232ea774 100644 --- a/frontend/packages/sdk/src/client/mgmt/v1alpha1/user_account_pb.ts +++ b/frontend/packages/sdk/src/client/mgmt/v1alpha1/user_account_pb.ts @@ -141,6 +141,54 @@ proto3.util.setEnumType(AccountStatus, "mgmt.v1alpha1.AccountStatus", [ { no: 5, name: "ACCOUNT_STATUS_ACCOUNT_TRIAL_EXPIRED" }, ]); +/** + * @generated from enum mgmt.v1alpha1.AccountRole + */ +export enum AccountRole { + /** + * Default value, this should not be used, but will default to ACCOUNT_ROLE_JOB_VIEWER + * + * @generated from enum value: ACCOUNT_ROLE_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * Admin, can do anything in the account. + * + * @generated from enum value: ACCOUNT_ROLE_ADMIN = 1; + */ + ADMIN = 1, + + /** + * Can view, edit jobs and connections + * + * @generated from enum value: ACCOUNT_ROLE_JOB_DEVELOPER = 2; + */ + JOB_DEVELOPER = 2, + + /** + * Can view + * + * @generated from enum value: ACCOUNT_ROLE_JOB_VIEWER = 3; + */ + JOB_VIEWER = 3, + + /** + * Can view and execute + * + * @generated from enum value: ACCOUNT_ROLE_JOB_EXECUTOR = 4; + */ + JOB_EXECUTOR = 4, +} +// Retrieve enum metadata with: proto3.getEnumType(AccountRole) +proto3.util.setEnumType(AccountRole, "mgmt.v1alpha1.AccountRole", [ + { no: 0, name: "ACCOUNT_ROLE_UNSPECIFIED" }, + { no: 1, name: "ACCOUNT_ROLE_ADMIN" }, + { no: 2, name: "ACCOUNT_ROLE_JOB_DEVELOPER" }, + { no: 3, name: "ACCOUNT_ROLE_JOB_VIEWER" }, + { no: 4, name: "ACCOUNT_ROLE_JOB_EXECUTOR" }, +]); + /** * @generated from message mgmt.v1alpha1.GetUserRequest */ @@ -2403,3 +2451,89 @@ export class SetBillingMeterEventResponse extends Message { + /** + * The account id to apply this role to + * + * @generated from field: string account_id = 1; + */ + accountId = ""; + + /** + * The user that this will be applied to + * + * @generated from field: string user_id = 2; + */ + userId = ""; + + /** + * The role that this user will obtain + * + * @generated from field: mgmt.v1alpha1.AccountRole role = 3; + */ + role = AccountRole.UNSPECIFIED; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "mgmt.v1alpha1.SetUserRoleRequest"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "account_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "user_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 3, name: "role", kind: "enum", T: proto3.getEnumType(AccountRole) }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): SetUserRoleRequest { + return new SetUserRoleRequest().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): SetUserRoleRequest { + return new SetUserRoleRequest().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): SetUserRoleRequest { + return new SetUserRoleRequest().fromJsonString(jsonString, options); + } + + static equals(a: SetUserRoleRequest | PlainMessage | undefined, b: SetUserRoleRequest | PlainMessage | undefined): boolean { + return proto3.util.equals(SetUserRoleRequest, a, b); + } +} + +/** + * @generated from message mgmt.v1alpha1.SetUserRoleResponse + */ +export class SetUserRoleResponse extends Message { + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "mgmt.v1alpha1.SetUserRoleResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): SetUserRoleResponse { + return new SetUserRoleResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): SetUserRoleResponse { + return new SetUserRoleResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): SetUserRoleResponse { + return new SetUserRoleResponse().fromJsonString(jsonString, options); + } + + static equals(a: SetUserRoleResponse | PlainMessage | undefined, b: SetUserRoleResponse | PlainMessage | undefined): boolean { + return proto3.util.equals(SetUserRoleResponse, a, b); + } +} + diff --git a/go.mod b/go.mod index c197796649..feac9768e3 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( connectrpc.com/otelconnect v0.7.1 github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai v0.7.1 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 + github.com/Blank-Xu/sql-adapter v1.1.1 github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/Jeffail/gabs/v2 v2.7.0 github.com/Jeffail/shutdown v1.0.0 @@ -25,6 +26,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 github.com/aws/smithy-go v1.22.1 github.com/bufbuild/protovalidate-go v0.7.3 + github.com/casbin/casbin/v2 v2.102.0 github.com/cenkalti/backoff/v4 v4.3.0 github.com/charmbracelet/bubbles v0.20.0 github.com/charmbracelet/bubbletea v1.2.4 @@ -176,8 +178,10 @@ require ( github.com/aymerick/douceur v0.2.0 // indirect github.com/benhoyt/goawk v1.25.0 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/bmatcuk/doublestar/v4 v4.7.1 // indirect github.com/bufbuild/protocompile v0.8.0 // indirect github.com/bwmarrin/snowflake v0.3.0 // indirect + github.com/casbin/govaluate v1.2.0 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charmbracelet/x/ansi v0.4.5 // indirect @@ -208,7 +212,7 @@ require ( github.com/eapache/go-resiliency v1.5.0 // indirect github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect github.com/eapache/queue v1.1.0 // indirect - github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 // indirect + github.com/edsrzf/mmap-go v1.0.0 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/envoyproxy/go-control-plane v0.13.1 // indirect github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect diff --git a/go.sum b/go.sum index 96ccc7953d..b60de0f197 100644 --- a/go.sum +++ b/go.sum @@ -747,6 +747,8 @@ github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0/go.mod h1:Vt9s github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.3 h1:6LyjnnaLpcOKK0fbYisI+mb8CE7iNe7i89nMNQxFxs8= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.3/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/Blank-Xu/sql-adapter v1.1.1 h1:+g7QXU9sl/qT6Po97teMpf3GjAO0X9aFaqgSePXvYko= +github.com/Blank-Xu/sql-adapter v1.1.1/go.mod h1:o2g8EZhZ3TudnYEGDkoU+3jCTCgDgx1o/Ig5ajKkaLY= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ClickHouse/ch-go v0.61.5 h1:zwR8QbYI0tsMiEcze/uIMK+Tz1D3XZXLdNrlaOpeEI4= @@ -977,6 +979,9 @@ github.com/bits-and-blooms/bitset v1.4.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edY github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk= github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= +github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0CXv75Q= +github.com/bmatcuk/doublestar/v4 v4.7.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bobg/gcsobj v0.1.2/go.mod h1:vS49EQ1A1Ib8FgrL58C8xXYZyOCR2TgzAdopy6/ipa8= @@ -1008,6 +1013,11 @@ github.com/bwmarrin/discordgo v0.27.1 h1:ib9AIc/dom1E/fSIulrBwnez0CToJE113ZGt4Ho github.com/bwmarrin/discordgo v0.27.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= +github.com/casbin/casbin/v2 v2.100.0/go.mod h1:LO7YPez4dX3LgoTCqSQAleQDo0S0BeZBDxYnPUl95Ng= +github.com/casbin/casbin/v2 v2.102.0 h1:weq9iSThUSL21SH3VrwoKa2DgRsaYMfjRNX/yOU3Foo= +github.com/casbin/casbin/v2 v2.102.0/go.mod h1:LO7YPez4dX3LgoTCqSQAleQDo0S0BeZBDxYnPUl95Ng= +github.com/casbin/govaluate v1.2.0 h1:wXCXFmqyY+1RwiKfYo3jMKyrtZmOL3kHwaqDyCPOYak= +github.com/casbin/govaluate v1.2.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -1154,8 +1164,8 @@ github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQNCyp70xik= github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE= -github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 h1:aaQcKT9WumO6JEJcRyTqFVq4XUZiUcKR2/GI31TOcz8= -github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/proto v1.10.0 h1:pDGyFRVV5RvV+nkBK9iy3q67FBy9Xa7vwrOTE+g5aGw= diff --git a/python/src/neosync/mgmt/v1alpha1/user_account_pb2.py b/python/src/neosync/mgmt/v1alpha1/user_account_pb2.py index 8b162f29c9..ba8817d4dc 100644 --- a/python/src/neosync/mgmt/v1alpha1/user_account_pb2.py +++ b/python/src/neosync/mgmt/v1alpha1/user_account_pb2.py @@ -26,7 +26,7 @@ from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n mgmt/v1alpha1/user_account.proto\x12\rmgmt.v1alpha1\x1a\x1b\x62uf/validate/validate.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\x10\n\x0eGetUserRequest\"*\n\x0fGetUserResponse\x12\x17\n\x07user_id\x18\x01 \x01(\tR\x06userId\"\x10\n\x0eSetUserRequest\"*\n\x0fSetUserResponse\x12\x17\n\x07user_id\x18\x01 \x01(\tR\x06userId\"\x18\n\x16GetUserAccountsRequest\"Q\n\x17GetUserAccountsResponse\x12\x36\n\x08\x61\x63\x63ounts\x18\x01 \x03(\x0b\x32\x1a.mgmt.v1alpha1.UserAccountR\x08\x61\x63\x63ounts\"\x9a\x01\n\x0bUserAccount\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n\x04name\x18\x02 \x01(\tR\x04name\x12\x32\n\x04type\x18\x03 \x01(\x0e\x32\x1e.mgmt.v1alpha1.UserAccountTypeR\x04type\x12\x33\n\x16has_stripe_customer_id\x18\x04 \x01(\x08R\x13hasStripeCustomerId\"\x91\x01\n#ConvertPersonalToTeamAccountRequest\x12-\n\x04name\x18\x01 \x01(\tB\x19\xbaH\x16r\x14\x32\x12^[a-z0-9-]{3,100}$R\x04name\x12,\n\naccount_id\x18\x02 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01H\x00R\taccountId\x88\x01\x01\x42\r\n\x0b_account_id\"\xcc\x01\n$ConvertPersonalToTeamAccountResponse\x12\x1d\n\naccount_id\x18\x01 \x01(\tR\taccountId\x12\x35\n\x14\x63heckout_session_url\x18\x02 \x01(\tH\x00R\x12\x63heckoutSessionUrl\x88\x01\x01\x12\x35\n\x17new_personal_account_id\x18\x03 \x01(\tR\x14newPersonalAccountIdB\x17\n\x15_checkout_session_url\"\x1b\n\x19SetPersonalAccountRequest\";\n\x1aSetPersonalAccountResponse\x12\x1d\n\naccount_id\x18\x01 \x01(\tR\taccountId\"A\n\x16IsUserInAccountRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\")\n\x17IsUserInAccountResponse\x12\x0e\n\x02ok\x18\x01 \x01(\x08R\x02ok\"J\n\x1fGetAccountTemporalConfigRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\"`\n GetAccountTemporalConfigResponse\x12<\n\x06\x63onfig\x18\x01 \x01(\x0b\x32$.mgmt.v1alpha1.AccountTemporalConfigR\x06\x63onfig\"\x88\x01\n\x1fSetAccountTemporalConfigRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\x12<\n\x06\x63onfig\x18\x02 \x01(\x0b\x32$.mgmt.v1alpha1.AccountTemporalConfigR\x06\x63onfig\"`\n SetAccountTemporalConfigResponse\x12<\n\x06\x63onfig\x18\x01 \x01(\x0b\x32$.mgmt.v1alpha1.AccountTemporalConfigR\x06\x63onfig\"\x91\x01\n\x15\x41\x63\x63ountTemporalConfig\x12\x19\n\x03url\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01R\x03url\x12%\n\tnamespace\x18\x02 \x01(\tB\x07\xbaH\x04r\x02\x10\x01R\tnamespace\x12\x36\n\x13sync_job_queue_name\x18\x03 \x01(\tB\x07\xbaH\x04r\x02\x10\x01R\x10syncJobQueueName\"I\n\x18\x43reateTeamAccountRequest\x12-\n\x04name\x18\x01 \x01(\tB\x19\xbaH\x16r\x14\x32\x12^[a-z0-9-]{3,100}$R\x04name\"\x8a\x01\n\x19\x43reateTeamAccountResponse\x12\x1d\n\naccount_id\x18\x01 \x01(\tR\taccountId\x12\x35\n\x14\x63heckout_session_url\x18\x02 \x01(\tH\x00R\x12\x63heckoutSessionUrl\x88\x01\x01\x42\x17\n\x15_checkout_session_url\"]\n\x0b\x41\x63\x63ountUser\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n\x04name\x18\x02 \x01(\tR\x04name\x12\x14\n\x05image\x18\x03 \x01(\tR\x05image\x12\x14\n\x05\x65mail\x18\x04 \x01(\tR\x05\x65mail\"G\n\x1cGetTeamAccountMembersRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\"Q\n\x1dGetTeamAccountMembersResponse\x12\x30\n\x05users\x18\x01 \x03(\x0b\x32\x1a.mgmt.v1alpha1.AccountUserR\x05users\"l\n\x1eRemoveTeamAccountMemberRequest\x12!\n\x07user_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\x06userId\x12\'\n\naccount_id\x18\x02 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\"!\n\x1fRemoveTeamAccountMemberResponse\"h\n\x1eInviteUserToTeamAccountRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\x12\x1d\n\x05\x65mail\x18\x02 \x01(\tB\x07\xbaH\x04r\x02\x10\x01R\x05\x65mail\"\xdd\x02\n\rAccountInvite\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\x1d\n\naccount_id\x18\x02 \x01(\tR\taccountId\x12$\n\x0esender_user_id\x18\x03 \x01(\tR\x0csenderUserId\x12\x14\n\x05\x65mail\x18\x04 \x01(\tR\x05\x65mail\x12\x14\n\x05token\x18\x05 \x01(\tR\x05token\x12\x1a\n\x08\x61\x63\x63\x65pted\x18\x06 \x01(\x08R\x08\x61\x63\x63\x65pted\x12\x39\n\ncreated_at\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tcreatedAt\x12\x39\n\nupdated_at\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tupdatedAt\x12\x39\n\nexpires_at\x18\t \x01(\x0b\x32\x1a.google.protobuf.TimestampR\texpiresAt\"W\n\x1fInviteUserToTeamAccountResponse\x12\x34\n\x06invite\x18\x01 \x01(\x0b\x32\x1c.mgmt.v1alpha1.AccountInviteR\x06invite\"G\n\x1cGetTeamAccountInvitesRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\"W\n\x1dGetTeamAccountInvitesResponse\x12\x36\n\x07invites\x18\x01 \x03(\x0b\x32\x1c.mgmt.v1alpha1.AccountInviteR\x07invites\":\n\x1eRemoveTeamAccountInviteRequest\x12\x18\n\x02id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\x02id\"!\n\x1fRemoveTeamAccountInviteResponse\"?\n\x1e\x41\x63\x63\x65ptTeamAccountInviteRequest\x12\x1d\n\x05token\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01R\x05token\"W\n\x1f\x41\x63\x63\x65ptTeamAccountInviteResponse\x12\x34\n\x07\x61\x63\x63ount\x18\x01 \x01(\x0b\x32\x1a.mgmt.v1alpha1.UserAccountR\x07\x61\x63\x63ount\"\x1d\n\x1bGetSystemInformationRequest\"\xc3\x01\n\x1cGetSystemInformationResponse\x12\x18\n\x07version\x18\x01 \x01(\tR\x07version\x12\x16\n\x06\x63ommit\x18\x02 \x01(\tR\x06\x63ommit\x12\x1a\n\x08\x63ompiler\x18\x03 \x01(\tR\x08\x63ompiler\x12\x1a\n\x08platform\x18\x04 \x01(\tR\x08platform\x12\x39\n\nbuild_date\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tbuildDate\"L\n!GetAccountOnboardingConfigRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\"d\n\"GetAccountOnboardingConfigResponse\x12>\n\x06\x63onfig\x18\x01 \x01(\x0b\x32&.mgmt.v1alpha1.AccountOnboardingConfigR\x06\x63onfig\"\x8c\x01\n!SetAccountOnboardingConfigRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\x12>\n\x06\x63onfig\x18\x02 \x01(\x0b\x32&.mgmt.v1alpha1.AccountOnboardingConfigR\x06\x63onfig\"d\n\"SetAccountOnboardingConfigResponse\x12>\n\x06\x63onfig\x18\x01 \x01(\x0b\x32&.mgmt.v1alpha1.AccountOnboardingConfigR\x06\x63onfig\"\xbb\x02\n\x17\x41\x63\x63ountOnboardingConfig\x12\x41\n\x1dhas_created_source_connection\x18\x01 \x01(\x08R\x1ahasCreatedSourceConnection\x12K\n\"has_created_destination_connection\x18\x02 \x01(\x08R\x1fhasCreatedDestinationConnection\x12&\n\x0fhas_created_job\x18\x03 \x01(\x08R\rhasCreatedJob\x12.\n\x13has_invited_members\x18\x04 \x01(\x08R\x11hasInvitedMembers\x12\x38\n\x18has_completed_onboarding\x18\x05 \x01(\x08R\x16hasCompletedOnboarding\"B\n\x17GetAccountStatusRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\"\xe5\x01\n\x18GetAccountStatusResponse\x12*\n\x11used_record_count\x18\x01 \x01(\x04R\x0fusedRecordCount\x12\x35\n\x14\x61llowed_record_count\x18\x02 \x01(\x04H\x00R\x12\x61llowedRecordCount\x88\x01\x01\x12M\n\x13subscription_status\x18\x03 \x01(\x0e\x32\x1c.mgmt.v1alpha1.BillingStatusR\x12subscriptionStatusB\x17\n\x15_allowed_record_count\"\x9c\x01\n\x1bIsAccountStatusValidRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\x12\x39\n\x16requested_record_count\x18\x02 \x01(\x04H\x00R\x14requestedRecordCount\x88\x01\x01\x42\x19\n\x17_requested_record_count\"\xa3\x03\n\x1cIsAccountStatusValidResponse\x12\x19\n\x08is_valid\x18\x01 \x01(\x08R\x07isValid\x12\x1b\n\x06reason\x18\x02 \x01(\tH\x00R\x06reason\x88\x01\x01\x12\x1f\n\x0bshould_poll\x18\x03 \x01(\x08R\nshouldPoll\x12*\n\x11used_record_count\x18\x04 \x01(\x04R\x0fusedRecordCount\x12\x35\n\x14\x61llowed_record_count\x18\x05 \x01(\x04H\x01R\x12\x61llowedRecordCount\x88\x01\x01\x12\x43\n\x0e\x61\x63\x63ount_status\x18\x06 \x01(\x0e\x32\x1c.mgmt.v1alpha1.AccountStatusR\raccountStatus\x12I\n\x10trial_expires_at\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x02R\x0etrialExpiresAt\x88\x01\x01\x42\t\n\x07_reasonB\x17\n\x15_allowed_record_countB\x13\n\x11_trial_expires_at\"R\n\'GetAccountBillingCheckoutSessionRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\"\\\n(GetAccountBillingCheckoutSessionResponse\x12\x30\n\x14\x63heckout_session_url\x18\x01 \x01(\tR\x12\x63heckoutSessionUrl\"P\n%GetAccountBillingPortalSessionRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\"V\n&GetAccountBillingPortalSessionResponse\x12,\n\x12portal_session_url\x18\x01 \x01(\tR\x10portalSessionUrl\"<\n\x19GetBillingAccountsRequest\x12\x1f\n\x0b\x61\x63\x63ount_ids\x18\x01 \x03(\tR\naccountIds\"T\n\x1aGetBillingAccountsResponse\x12\x36\n\x08\x61\x63\x63ounts\x18\x01 \x03(\x0b\x32\x1a.mgmt.v1alpha1.UserAccountR\x08\x61\x63\x63ounts\"\xe2\x01\n\x1bSetBillingMeterEventRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\x12&\n\nevent_name\x18\x02 \x01(\tB\x07\xbaH\x04r\x02\x10\x01R\teventName\x12\x1d\n\x05value\x18\x03 \x01(\tB\x07\xbaH\x04r\x02\x10\x01R\x05value\x12\"\n\x08\x65vent_id\x18\x04 \x01(\tB\x07\xbaH\x04r\x02\x10\x01R\x07\x65ventId\x12!\n\ttimestamp\x18\x05 \x01(\x04H\x00R\ttimestamp\x88\x01\x01\x42\x0c\n\n_timestamp\"\x1e\n\x1cSetBillingMeterEventResponse*\x92\x01\n\x0fUserAccountType\x12!\n\x1dUSER_ACCOUNT_TYPE_UNSPECIFIED\x10\x00\x12\x1e\n\x1aUSER_ACCOUNT_TYPE_PERSONAL\x10\x01\x12\x1a\n\x16USER_ACCOUNT_TYPE_TEAM\x10\x02\x12 \n\x1cUSER_ACCOUNT_TYPE_ENTERPRISE\x10\x03*\xa9\x01\n\rBillingStatus\x12\x1e\n\x1a\x42ILLING_STATUS_UNSPECIFIED\x10\x00\x12\x19\n\x15\x42ILLING_STATUS_ACTIVE\x10\x01\x12\x1a\n\x16\x42ILLING_STATUS_EXPIRED\x10\x02\x12\x1f\n\x1b\x42ILLING_STATUS_TRIAL_ACTIVE\x10\x03\x12 \n\x1c\x42ILLING_STATUS_TRIAL_EXPIRED\x10\x04*\x8c\x02\n\rAccountStatus\x12%\n!ACCOUNT_STATUS_REASON_UNSPECIFIED\x10\x00\x12(\n$ACCOUNT_STATUS_EXCEEDS_ALLOWED_LIMIT\x10\x01\x12*\n&ACCOUNT_STATUS_REQUESTED_EXCEEDS_LIMIT\x10\x02\x12+\n\'ACCOUNT_STATUS_ACCOUNT_IN_EXPIRED_STATE\x10\x03\x12\'\n#ACCOUNT_STATUS_ACCOUNT_TRIAL_ACTIVE\x10\x04\x12(\n$ACCOUNT_STATUS_ACCOUNT_TRIAL_EXPIRED\x10\x05\x32\xa0\x16\n\x12UserAccountService\x12J\n\x07GetUser\x12\x1d.mgmt.v1alpha1.GetUserRequest\x1a\x1e.mgmt.v1alpha1.GetUserResponse\"\x00\x12J\n\x07SetUser\x12\x1d.mgmt.v1alpha1.SetUserRequest\x1a\x1e.mgmt.v1alpha1.SetUserResponse\"\x00\x12\x62\n\x0fGetUserAccounts\x12%.mgmt.v1alpha1.GetUserAccountsRequest\x1a&.mgmt.v1alpha1.GetUserAccountsResponse\"\x00\x12k\n\x12SetPersonalAccount\x12(.mgmt.v1alpha1.SetPersonalAccountRequest\x1a).mgmt.v1alpha1.SetPersonalAccountResponse\"\x00\x12\x89\x01\n\x1c\x43onvertPersonalToTeamAccount\x12\x32.mgmt.v1alpha1.ConvertPersonalToTeamAccountRequest\x1a\x33.mgmt.v1alpha1.ConvertPersonalToTeamAccountResponse\"\x00\x12h\n\x11\x43reateTeamAccount\x12\'.mgmt.v1alpha1.CreateTeamAccountRequest\x1a(.mgmt.v1alpha1.CreateTeamAccountResponse\"\x00\x12\x62\n\x0fIsUserInAccount\x12%.mgmt.v1alpha1.IsUserInAccountRequest\x1a&.mgmt.v1alpha1.IsUserInAccountResponse\"\x00\x12}\n\x18GetAccountTemporalConfig\x12..mgmt.v1alpha1.GetAccountTemporalConfigRequest\x1a/.mgmt.v1alpha1.GetAccountTemporalConfigResponse\"\x00\x12}\n\x18SetAccountTemporalConfig\x12..mgmt.v1alpha1.SetAccountTemporalConfigRequest\x1a/.mgmt.v1alpha1.SetAccountTemporalConfigResponse\"\x00\x12t\n\x15GetTeamAccountMembers\x12+.mgmt.v1alpha1.GetTeamAccountMembersRequest\x1a,.mgmt.v1alpha1.GetTeamAccountMembersResponse\"\x00\x12z\n\x17RemoveTeamAccountMember\x12-.mgmt.v1alpha1.RemoveTeamAccountMemberRequest\x1a..mgmt.v1alpha1.RemoveTeamAccountMemberResponse\"\x00\x12z\n\x17InviteUserToTeamAccount\x12-.mgmt.v1alpha1.InviteUserToTeamAccountRequest\x1a..mgmt.v1alpha1.InviteUserToTeamAccountResponse\"\x00\x12t\n\x15GetTeamAccountInvites\x12+.mgmt.v1alpha1.GetTeamAccountInvitesRequest\x1a,.mgmt.v1alpha1.GetTeamAccountInvitesResponse\"\x00\x12z\n\x17RemoveTeamAccountInvite\x12-.mgmt.v1alpha1.RemoveTeamAccountInviteRequest\x1a..mgmt.v1alpha1.RemoveTeamAccountInviteResponse\"\x00\x12z\n\x17\x41\x63\x63\x65ptTeamAccountInvite\x12-.mgmt.v1alpha1.AcceptTeamAccountInviteRequest\x1a..mgmt.v1alpha1.AcceptTeamAccountInviteResponse\"\x00\x12t\n\x14GetSystemInformation\x12*.mgmt.v1alpha1.GetSystemInformationRequest\x1a+.mgmt.v1alpha1.GetSystemInformationResponse\"\x03\x90\x02\x01\x12\x83\x01\n\x1aGetAccountOnboardingConfig\x12\x30.mgmt.v1alpha1.GetAccountOnboardingConfigRequest\x1a\x31.mgmt.v1alpha1.GetAccountOnboardingConfigResponse\"\x00\x12\x83\x01\n\x1aSetAccountOnboardingConfig\x12\x30.mgmt.v1alpha1.SetAccountOnboardingConfigRequest\x1a\x31.mgmt.v1alpha1.SetAccountOnboardingConfigResponse\"\x00\x12h\n\x10GetAccountStatus\x12&.mgmt.v1alpha1.GetAccountStatusRequest\x1a\'.mgmt.v1alpha1.GetAccountStatusResponse\"\x03\x90\x02\x01\x12t\n\x14IsAccountStatusValid\x12*.mgmt.v1alpha1.IsAccountStatusValidRequest\x1a+.mgmt.v1alpha1.IsAccountStatusValidResponse\"\x03\x90\x02\x01\x12\x95\x01\n GetAccountBillingCheckoutSession\x12\x36.mgmt.v1alpha1.GetAccountBillingCheckoutSessionRequest\x1a\x37.mgmt.v1alpha1.GetAccountBillingCheckoutSessionResponse\"\x00\x12\x8f\x01\n\x1eGetAccountBillingPortalSession\x12\x34.mgmt.v1alpha1.GetAccountBillingPortalSessionRequest\x1a\x35.mgmt.v1alpha1.GetAccountBillingPortalSessionResponse\"\x00\x12n\n\x12GetBillingAccounts\x12(.mgmt.v1alpha1.GetBillingAccountsRequest\x1a).mgmt.v1alpha1.GetBillingAccountsResponse\"\x03\x90\x02\x01\x12q\n\x14SetBillingMeterEvent\x12*.mgmt.v1alpha1.SetBillingMeterEventRequest\x1a+.mgmt.v1alpha1.SetBillingMeterEventResponse\"\x00\x42\xcc\x01\n\x11\x63om.mgmt.v1alpha1B\x10UserAccountProtoP\x01ZPgithub.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1;mgmtv1alpha1\xa2\x02\x03MXX\xaa\x02\rMgmt.V1alpha1\xca\x02\rMgmt\\V1alpha1\xe2\x02\x19Mgmt\\V1alpha1\\GPBMetadata\xea\x02\x0eMgmt::V1alpha1b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n mgmt/v1alpha1/user_account.proto\x12\rmgmt.v1alpha1\x1a\x1b\x62uf/validate/validate.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\x10\n\x0eGetUserRequest\"*\n\x0fGetUserResponse\x12\x17\n\x07user_id\x18\x01 \x01(\tR\x06userId\"\x10\n\x0eSetUserRequest\"*\n\x0fSetUserResponse\x12\x17\n\x07user_id\x18\x01 \x01(\tR\x06userId\"\x18\n\x16GetUserAccountsRequest\"Q\n\x17GetUserAccountsResponse\x12\x36\n\x08\x61\x63\x63ounts\x18\x01 \x03(\x0b\x32\x1a.mgmt.v1alpha1.UserAccountR\x08\x61\x63\x63ounts\"\x9a\x01\n\x0bUserAccount\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n\x04name\x18\x02 \x01(\tR\x04name\x12\x32\n\x04type\x18\x03 \x01(\x0e\x32\x1e.mgmt.v1alpha1.UserAccountTypeR\x04type\x12\x33\n\x16has_stripe_customer_id\x18\x04 \x01(\x08R\x13hasStripeCustomerId\"\x91\x01\n#ConvertPersonalToTeamAccountRequest\x12-\n\x04name\x18\x01 \x01(\tB\x19\xbaH\x16r\x14\x32\x12^[a-z0-9-]{3,100}$R\x04name\x12,\n\naccount_id\x18\x02 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01H\x00R\taccountId\x88\x01\x01\x42\r\n\x0b_account_id\"\xcc\x01\n$ConvertPersonalToTeamAccountResponse\x12\x1d\n\naccount_id\x18\x01 \x01(\tR\taccountId\x12\x35\n\x14\x63heckout_session_url\x18\x02 \x01(\tH\x00R\x12\x63heckoutSessionUrl\x88\x01\x01\x12\x35\n\x17new_personal_account_id\x18\x03 \x01(\tR\x14newPersonalAccountIdB\x17\n\x15_checkout_session_url\"\x1b\n\x19SetPersonalAccountRequest\";\n\x1aSetPersonalAccountResponse\x12\x1d\n\naccount_id\x18\x01 \x01(\tR\taccountId\"A\n\x16IsUserInAccountRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\")\n\x17IsUserInAccountResponse\x12\x0e\n\x02ok\x18\x01 \x01(\x08R\x02ok\"J\n\x1fGetAccountTemporalConfigRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\"`\n GetAccountTemporalConfigResponse\x12<\n\x06\x63onfig\x18\x01 \x01(\x0b\x32$.mgmt.v1alpha1.AccountTemporalConfigR\x06\x63onfig\"\x88\x01\n\x1fSetAccountTemporalConfigRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\x12<\n\x06\x63onfig\x18\x02 \x01(\x0b\x32$.mgmt.v1alpha1.AccountTemporalConfigR\x06\x63onfig\"`\n SetAccountTemporalConfigResponse\x12<\n\x06\x63onfig\x18\x01 \x01(\x0b\x32$.mgmt.v1alpha1.AccountTemporalConfigR\x06\x63onfig\"\x91\x01\n\x15\x41\x63\x63ountTemporalConfig\x12\x19\n\x03url\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01R\x03url\x12%\n\tnamespace\x18\x02 \x01(\tB\x07\xbaH\x04r\x02\x10\x01R\tnamespace\x12\x36\n\x13sync_job_queue_name\x18\x03 \x01(\tB\x07\xbaH\x04r\x02\x10\x01R\x10syncJobQueueName\"I\n\x18\x43reateTeamAccountRequest\x12-\n\x04name\x18\x01 \x01(\tB\x19\xbaH\x16r\x14\x32\x12^[a-z0-9-]{3,100}$R\x04name\"\x8a\x01\n\x19\x43reateTeamAccountResponse\x12\x1d\n\naccount_id\x18\x01 \x01(\tR\taccountId\x12\x35\n\x14\x63heckout_session_url\x18\x02 \x01(\tH\x00R\x12\x63heckoutSessionUrl\x88\x01\x01\x42\x17\n\x15_checkout_session_url\"]\n\x0b\x41\x63\x63ountUser\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n\x04name\x18\x02 \x01(\tR\x04name\x12\x14\n\x05image\x18\x03 \x01(\tR\x05image\x12\x14\n\x05\x65mail\x18\x04 \x01(\tR\x05\x65mail\"G\n\x1cGetTeamAccountMembersRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\"Q\n\x1dGetTeamAccountMembersResponse\x12\x30\n\x05users\x18\x01 \x03(\x0b\x32\x1a.mgmt.v1alpha1.AccountUserR\x05users\"l\n\x1eRemoveTeamAccountMemberRequest\x12!\n\x07user_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\x06userId\x12\'\n\naccount_id\x18\x02 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\"!\n\x1fRemoveTeamAccountMemberResponse\"h\n\x1eInviteUserToTeamAccountRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\x12\x1d\n\x05\x65mail\x18\x02 \x01(\tB\x07\xbaH\x04r\x02\x10\x01R\x05\x65mail\"\xdd\x02\n\rAccountInvite\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\x1d\n\naccount_id\x18\x02 \x01(\tR\taccountId\x12$\n\x0esender_user_id\x18\x03 \x01(\tR\x0csenderUserId\x12\x14\n\x05\x65mail\x18\x04 \x01(\tR\x05\x65mail\x12\x14\n\x05token\x18\x05 \x01(\tR\x05token\x12\x1a\n\x08\x61\x63\x63\x65pted\x18\x06 \x01(\x08R\x08\x61\x63\x63\x65pted\x12\x39\n\ncreated_at\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tcreatedAt\x12\x39\n\nupdated_at\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tupdatedAt\x12\x39\n\nexpires_at\x18\t \x01(\x0b\x32\x1a.google.protobuf.TimestampR\texpiresAt\"W\n\x1fInviteUserToTeamAccountResponse\x12\x34\n\x06invite\x18\x01 \x01(\x0b\x32\x1c.mgmt.v1alpha1.AccountInviteR\x06invite\"G\n\x1cGetTeamAccountInvitesRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\"W\n\x1dGetTeamAccountInvitesResponse\x12\x36\n\x07invites\x18\x01 \x03(\x0b\x32\x1c.mgmt.v1alpha1.AccountInviteR\x07invites\":\n\x1eRemoveTeamAccountInviteRequest\x12\x18\n\x02id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\x02id\"!\n\x1fRemoveTeamAccountInviteResponse\"?\n\x1e\x41\x63\x63\x65ptTeamAccountInviteRequest\x12\x1d\n\x05token\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01R\x05token\"W\n\x1f\x41\x63\x63\x65ptTeamAccountInviteResponse\x12\x34\n\x07\x61\x63\x63ount\x18\x01 \x01(\x0b\x32\x1a.mgmt.v1alpha1.UserAccountR\x07\x61\x63\x63ount\"\x1d\n\x1bGetSystemInformationRequest\"\xc3\x01\n\x1cGetSystemInformationResponse\x12\x18\n\x07version\x18\x01 \x01(\tR\x07version\x12\x16\n\x06\x63ommit\x18\x02 \x01(\tR\x06\x63ommit\x12\x1a\n\x08\x63ompiler\x18\x03 \x01(\tR\x08\x63ompiler\x12\x1a\n\x08platform\x18\x04 \x01(\tR\x08platform\x12\x39\n\nbuild_date\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tbuildDate\"L\n!GetAccountOnboardingConfigRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\"d\n\"GetAccountOnboardingConfigResponse\x12>\n\x06\x63onfig\x18\x01 \x01(\x0b\x32&.mgmt.v1alpha1.AccountOnboardingConfigR\x06\x63onfig\"\x8c\x01\n!SetAccountOnboardingConfigRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\x12>\n\x06\x63onfig\x18\x02 \x01(\x0b\x32&.mgmt.v1alpha1.AccountOnboardingConfigR\x06\x63onfig\"d\n\"SetAccountOnboardingConfigResponse\x12>\n\x06\x63onfig\x18\x01 \x01(\x0b\x32&.mgmt.v1alpha1.AccountOnboardingConfigR\x06\x63onfig\"\xbb\x02\n\x17\x41\x63\x63ountOnboardingConfig\x12\x41\n\x1dhas_created_source_connection\x18\x01 \x01(\x08R\x1ahasCreatedSourceConnection\x12K\n\"has_created_destination_connection\x18\x02 \x01(\x08R\x1fhasCreatedDestinationConnection\x12&\n\x0fhas_created_job\x18\x03 \x01(\x08R\rhasCreatedJob\x12.\n\x13has_invited_members\x18\x04 \x01(\x08R\x11hasInvitedMembers\x12\x38\n\x18has_completed_onboarding\x18\x05 \x01(\x08R\x16hasCompletedOnboarding\"B\n\x17GetAccountStatusRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\"\xe5\x01\n\x18GetAccountStatusResponse\x12*\n\x11used_record_count\x18\x01 \x01(\x04R\x0fusedRecordCount\x12\x35\n\x14\x61llowed_record_count\x18\x02 \x01(\x04H\x00R\x12\x61llowedRecordCount\x88\x01\x01\x12M\n\x13subscription_status\x18\x03 \x01(\x0e\x32\x1c.mgmt.v1alpha1.BillingStatusR\x12subscriptionStatusB\x17\n\x15_allowed_record_count\"\x9c\x01\n\x1bIsAccountStatusValidRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\x12\x39\n\x16requested_record_count\x18\x02 \x01(\x04H\x00R\x14requestedRecordCount\x88\x01\x01\x42\x19\n\x17_requested_record_count\"\xa3\x03\n\x1cIsAccountStatusValidResponse\x12\x19\n\x08is_valid\x18\x01 \x01(\x08R\x07isValid\x12\x1b\n\x06reason\x18\x02 \x01(\tH\x00R\x06reason\x88\x01\x01\x12\x1f\n\x0bshould_poll\x18\x03 \x01(\x08R\nshouldPoll\x12*\n\x11used_record_count\x18\x04 \x01(\x04R\x0fusedRecordCount\x12\x35\n\x14\x61llowed_record_count\x18\x05 \x01(\x04H\x01R\x12\x61llowedRecordCount\x88\x01\x01\x12\x43\n\x0e\x61\x63\x63ount_status\x18\x06 \x01(\x0e\x32\x1c.mgmt.v1alpha1.AccountStatusR\raccountStatus\x12I\n\x10trial_expires_at\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x02R\x0etrialExpiresAt\x88\x01\x01\x42\t\n\x07_reasonB\x17\n\x15_allowed_record_countB\x13\n\x11_trial_expires_at\"R\n\'GetAccountBillingCheckoutSessionRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\"\\\n(GetAccountBillingCheckoutSessionResponse\x12\x30\n\x14\x63heckout_session_url\x18\x01 \x01(\tR\x12\x63heckoutSessionUrl\"P\n%GetAccountBillingPortalSessionRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\"V\n&GetAccountBillingPortalSessionResponse\x12,\n\x12portal_session_url\x18\x01 \x01(\tR\x10portalSessionUrl\"<\n\x19GetBillingAccountsRequest\x12\x1f\n\x0b\x61\x63\x63ount_ids\x18\x01 \x03(\tR\naccountIds\"T\n\x1aGetBillingAccountsResponse\x12\x36\n\x08\x61\x63\x63ounts\x18\x01 \x03(\x0b\x32\x1a.mgmt.v1alpha1.UserAccountR\x08\x61\x63\x63ounts\"\xe2\x01\n\x1bSetBillingMeterEventRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\x12&\n\nevent_name\x18\x02 \x01(\tB\x07\xbaH\x04r\x02\x10\x01R\teventName\x12\x1d\n\x05value\x18\x03 \x01(\tB\x07\xbaH\x04r\x02\x10\x01R\x05value\x12\"\n\x08\x65vent_id\x18\x04 \x01(\tB\x07\xbaH\x04r\x02\x10\x01R\x07\x65ventId\x12!\n\ttimestamp\x18\x05 \x01(\x04H\x00R\ttimestamp\x88\x01\x01\x42\x0c\n\n_timestamp\"\x1e\n\x1cSetBillingMeterEventResponse\"\x90\x01\n\x12SetUserRoleRequest\x12\'\n\naccount_id\x18\x01 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\taccountId\x12!\n\x07user_id\x18\x02 \x01(\tB\x08\xbaH\x05r\x03\xb0\x01\x01R\x06userId\x12.\n\x04role\x18\x03 \x01(\x0e\x32\x1a.mgmt.v1alpha1.AccountRoleR\x04role\"\x15\n\x13SetUserRoleResponse*\x92\x01\n\x0fUserAccountType\x12!\n\x1dUSER_ACCOUNT_TYPE_UNSPECIFIED\x10\x00\x12\x1e\n\x1aUSER_ACCOUNT_TYPE_PERSONAL\x10\x01\x12\x1a\n\x16USER_ACCOUNT_TYPE_TEAM\x10\x02\x12 \n\x1cUSER_ACCOUNT_TYPE_ENTERPRISE\x10\x03*\xa9\x01\n\rBillingStatus\x12\x1e\n\x1a\x42ILLING_STATUS_UNSPECIFIED\x10\x00\x12\x19\n\x15\x42ILLING_STATUS_ACTIVE\x10\x01\x12\x1a\n\x16\x42ILLING_STATUS_EXPIRED\x10\x02\x12\x1f\n\x1b\x42ILLING_STATUS_TRIAL_ACTIVE\x10\x03\x12 \n\x1c\x42ILLING_STATUS_TRIAL_EXPIRED\x10\x04*\x8c\x02\n\rAccountStatus\x12%\n!ACCOUNT_STATUS_REASON_UNSPECIFIED\x10\x00\x12(\n$ACCOUNT_STATUS_EXCEEDS_ALLOWED_LIMIT\x10\x01\x12*\n&ACCOUNT_STATUS_REQUESTED_EXCEEDS_LIMIT\x10\x02\x12+\n\'ACCOUNT_STATUS_ACCOUNT_IN_EXPIRED_STATE\x10\x03\x12\'\n#ACCOUNT_STATUS_ACCOUNT_TRIAL_ACTIVE\x10\x04\x12(\n$ACCOUNT_STATUS_ACCOUNT_TRIAL_EXPIRED\x10\x05*\x9f\x01\n\x0b\x41\x63\x63ountRole\x12\x1c\n\x18\x41\x43\x43OUNT_ROLE_UNSPECIFIED\x10\x00\x12\x16\n\x12\x41\x43\x43OUNT_ROLE_ADMIN\x10\x01\x12\x1e\n\x1a\x41\x43\x43OUNT_ROLE_JOB_DEVELOPER\x10\x02\x12\x1b\n\x17\x41\x43\x43OUNT_ROLE_JOB_VIEWER\x10\x03\x12\x1d\n\x19\x41\x43\x43OUNT_ROLE_JOB_EXECUTOR\x10\x04\x32\xf8\x16\n\x12UserAccountService\x12J\n\x07GetUser\x12\x1d.mgmt.v1alpha1.GetUserRequest\x1a\x1e.mgmt.v1alpha1.GetUserResponse\"\x00\x12J\n\x07SetUser\x12\x1d.mgmt.v1alpha1.SetUserRequest\x1a\x1e.mgmt.v1alpha1.SetUserResponse\"\x00\x12\x62\n\x0fGetUserAccounts\x12%.mgmt.v1alpha1.GetUserAccountsRequest\x1a&.mgmt.v1alpha1.GetUserAccountsResponse\"\x00\x12k\n\x12SetPersonalAccount\x12(.mgmt.v1alpha1.SetPersonalAccountRequest\x1a).mgmt.v1alpha1.SetPersonalAccountResponse\"\x00\x12\x89\x01\n\x1c\x43onvertPersonalToTeamAccount\x12\x32.mgmt.v1alpha1.ConvertPersonalToTeamAccountRequest\x1a\x33.mgmt.v1alpha1.ConvertPersonalToTeamAccountResponse\"\x00\x12h\n\x11\x43reateTeamAccount\x12\'.mgmt.v1alpha1.CreateTeamAccountRequest\x1a(.mgmt.v1alpha1.CreateTeamAccountResponse\"\x00\x12\x62\n\x0fIsUserInAccount\x12%.mgmt.v1alpha1.IsUserInAccountRequest\x1a&.mgmt.v1alpha1.IsUserInAccountResponse\"\x00\x12}\n\x18GetAccountTemporalConfig\x12..mgmt.v1alpha1.GetAccountTemporalConfigRequest\x1a/.mgmt.v1alpha1.GetAccountTemporalConfigResponse\"\x00\x12}\n\x18SetAccountTemporalConfig\x12..mgmt.v1alpha1.SetAccountTemporalConfigRequest\x1a/.mgmt.v1alpha1.SetAccountTemporalConfigResponse\"\x00\x12t\n\x15GetTeamAccountMembers\x12+.mgmt.v1alpha1.GetTeamAccountMembersRequest\x1a,.mgmt.v1alpha1.GetTeamAccountMembersResponse\"\x00\x12z\n\x17RemoveTeamAccountMember\x12-.mgmt.v1alpha1.RemoveTeamAccountMemberRequest\x1a..mgmt.v1alpha1.RemoveTeamAccountMemberResponse\"\x00\x12z\n\x17InviteUserToTeamAccount\x12-.mgmt.v1alpha1.InviteUserToTeamAccountRequest\x1a..mgmt.v1alpha1.InviteUserToTeamAccountResponse\"\x00\x12t\n\x15GetTeamAccountInvites\x12+.mgmt.v1alpha1.GetTeamAccountInvitesRequest\x1a,.mgmt.v1alpha1.GetTeamAccountInvitesResponse\"\x00\x12z\n\x17RemoveTeamAccountInvite\x12-.mgmt.v1alpha1.RemoveTeamAccountInviteRequest\x1a..mgmt.v1alpha1.RemoveTeamAccountInviteResponse\"\x00\x12z\n\x17\x41\x63\x63\x65ptTeamAccountInvite\x12-.mgmt.v1alpha1.AcceptTeamAccountInviteRequest\x1a..mgmt.v1alpha1.AcceptTeamAccountInviteResponse\"\x00\x12t\n\x14GetSystemInformation\x12*.mgmt.v1alpha1.GetSystemInformationRequest\x1a+.mgmt.v1alpha1.GetSystemInformationResponse\"\x03\x90\x02\x01\x12\x83\x01\n\x1aGetAccountOnboardingConfig\x12\x30.mgmt.v1alpha1.GetAccountOnboardingConfigRequest\x1a\x31.mgmt.v1alpha1.GetAccountOnboardingConfigResponse\"\x00\x12\x83\x01\n\x1aSetAccountOnboardingConfig\x12\x30.mgmt.v1alpha1.SetAccountOnboardingConfigRequest\x1a\x31.mgmt.v1alpha1.SetAccountOnboardingConfigResponse\"\x00\x12h\n\x10GetAccountStatus\x12&.mgmt.v1alpha1.GetAccountStatusRequest\x1a\'.mgmt.v1alpha1.GetAccountStatusResponse\"\x03\x90\x02\x01\x12t\n\x14IsAccountStatusValid\x12*.mgmt.v1alpha1.IsAccountStatusValidRequest\x1a+.mgmt.v1alpha1.IsAccountStatusValidResponse\"\x03\x90\x02\x01\x12\x95\x01\n GetAccountBillingCheckoutSession\x12\x36.mgmt.v1alpha1.GetAccountBillingCheckoutSessionRequest\x1a\x37.mgmt.v1alpha1.GetAccountBillingCheckoutSessionResponse\"\x00\x12\x8f\x01\n\x1eGetAccountBillingPortalSession\x12\x34.mgmt.v1alpha1.GetAccountBillingPortalSessionRequest\x1a\x35.mgmt.v1alpha1.GetAccountBillingPortalSessionResponse\"\x00\x12n\n\x12GetBillingAccounts\x12(.mgmt.v1alpha1.GetBillingAccountsRequest\x1a).mgmt.v1alpha1.GetBillingAccountsResponse\"\x03\x90\x02\x01\x12q\n\x14SetBillingMeterEvent\x12*.mgmt.v1alpha1.SetBillingMeterEventRequest\x1a+.mgmt.v1alpha1.SetBillingMeterEventResponse\"\x00\x12V\n\x0bSetUserRole\x12!.mgmt.v1alpha1.SetUserRoleRequest\x1a\".mgmt.v1alpha1.SetUserRoleResponse\"\x00\x42\xcc\x01\n\x11\x63om.mgmt.v1alpha1B\x10UserAccountProtoP\x01ZPgithub.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1;mgmtv1alpha1\xa2\x02\x03MXX\xaa\x02\rMgmt.V1alpha1\xca\x02\rMgmt\\V1alpha1\xe2\x02\x19Mgmt\\V1alpha1\\GPBMetadata\xea\x02\x0eMgmt::V1alpha1b\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -88,6 +88,10 @@ _globals['_SETBILLINGMETEREVENTREQUEST'].fields_by_name['value']._serialized_options = b'\272H\004r\002\020\001' _globals['_SETBILLINGMETEREVENTREQUEST'].fields_by_name['event_id']._loaded_options = None _globals['_SETBILLINGMETEREVENTREQUEST'].fields_by_name['event_id']._serialized_options = b'\272H\004r\002\020\001' + _globals['_SETUSERROLEREQUEST'].fields_by_name['account_id']._loaded_options = None + _globals['_SETUSERROLEREQUEST'].fields_by_name['account_id']._serialized_options = b'\272H\005r\003\260\001\001' + _globals['_SETUSERROLEREQUEST'].fields_by_name['user_id']._loaded_options = None + _globals['_SETUSERROLEREQUEST'].fields_by_name['user_id']._serialized_options = b'\272H\005r\003\260\001\001' _globals['_USERACCOUNTSERVICE'].methods_by_name['GetSystemInformation']._loaded_options = None _globals['_USERACCOUNTSERVICE'].methods_by_name['GetSystemInformation']._serialized_options = b'\220\002\001' _globals['_USERACCOUNTSERVICE'].methods_by_name['GetAccountStatus']._loaded_options = None @@ -96,12 +100,14 @@ _globals['_USERACCOUNTSERVICE'].methods_by_name['IsAccountStatusValid']._serialized_options = b'\220\002\001' _globals['_USERACCOUNTSERVICE'].methods_by_name['GetBillingAccounts']._loaded_options = None _globals['_USERACCOUNTSERVICE'].methods_by_name['GetBillingAccounts']._serialized_options = b'\220\002\001' - _globals['_USERACCOUNTTYPE']._serialized_start=5798 - _globals['_USERACCOUNTTYPE']._serialized_end=5944 - _globals['_BILLINGSTATUS']._serialized_start=5947 - _globals['_BILLINGSTATUS']._serialized_end=6116 - _globals['_ACCOUNTSTATUS']._serialized_start=6119 - _globals['_ACCOUNTSTATUS']._serialized_end=6387 + _globals['_USERACCOUNTTYPE']._serialized_start=5968 + _globals['_USERACCOUNTTYPE']._serialized_end=6114 + _globals['_BILLINGSTATUS']._serialized_start=6117 + _globals['_BILLINGSTATUS']._serialized_end=6286 + _globals['_ACCOUNTSTATUS']._serialized_start=6289 + _globals['_ACCOUNTSTATUS']._serialized_end=6557 + _globals['_ACCOUNTROLE']._serialized_start=6560 + _globals['_ACCOUNTROLE']._serialized_end=6719 _globals['_GETUSERREQUEST']._serialized_start=113 _globals['_GETUSERREQUEST']._serialized_end=129 _globals['_GETUSERRESPONSE']._serialized_start=131 @@ -208,6 +214,10 @@ _globals['_SETBILLINGMETEREVENTREQUEST']._serialized_end=5763 _globals['_SETBILLINGMETEREVENTRESPONSE']._serialized_start=5765 _globals['_SETBILLINGMETEREVENTRESPONSE']._serialized_end=5795 - _globals['_USERACCOUNTSERVICE']._serialized_start=6390 - _globals['_USERACCOUNTSERVICE']._serialized_end=9238 + _globals['_SETUSERROLEREQUEST']._serialized_start=5798 + _globals['_SETUSERROLEREQUEST']._serialized_end=5942 + _globals['_SETUSERROLERESPONSE']._serialized_start=5944 + _globals['_SETUSERROLERESPONSE']._serialized_end=5965 + _globals['_USERACCOUNTSERVICE']._serialized_start=6722 + _globals['_USERACCOUNTSERVICE']._serialized_end=9658 # @@protoc_insertion_point(module_scope) diff --git a/python/src/neosync/mgmt/v1alpha1/user_account_pb2.pyi b/python/src/neosync/mgmt/v1alpha1/user_account_pb2.pyi index 4dfc1cb96f..2543c2a14a 100644 --- a/python/src/neosync/mgmt/v1alpha1/user_account_pb2.pyi +++ b/python/src/neosync/mgmt/v1alpha1/user_account_pb2.pyi @@ -31,6 +31,14 @@ class AccountStatus(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): ACCOUNT_STATUS_ACCOUNT_IN_EXPIRED_STATE: _ClassVar[AccountStatus] ACCOUNT_STATUS_ACCOUNT_TRIAL_ACTIVE: _ClassVar[AccountStatus] ACCOUNT_STATUS_ACCOUNT_TRIAL_EXPIRED: _ClassVar[AccountStatus] + +class AccountRole(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + ACCOUNT_ROLE_UNSPECIFIED: _ClassVar[AccountRole] + ACCOUNT_ROLE_ADMIN: _ClassVar[AccountRole] + ACCOUNT_ROLE_JOB_DEVELOPER: _ClassVar[AccountRole] + ACCOUNT_ROLE_JOB_VIEWER: _ClassVar[AccountRole] + ACCOUNT_ROLE_JOB_EXECUTOR: _ClassVar[AccountRole] USER_ACCOUNT_TYPE_UNSPECIFIED: UserAccountType USER_ACCOUNT_TYPE_PERSONAL: UserAccountType USER_ACCOUNT_TYPE_TEAM: UserAccountType @@ -46,6 +54,11 @@ ACCOUNT_STATUS_REQUESTED_EXCEEDS_LIMIT: AccountStatus ACCOUNT_STATUS_ACCOUNT_IN_EXPIRED_STATE: AccountStatus ACCOUNT_STATUS_ACCOUNT_TRIAL_ACTIVE: AccountStatus ACCOUNT_STATUS_ACCOUNT_TRIAL_EXPIRED: AccountStatus +ACCOUNT_ROLE_UNSPECIFIED: AccountRole +ACCOUNT_ROLE_ADMIN: AccountRole +ACCOUNT_ROLE_JOB_DEVELOPER: AccountRole +ACCOUNT_ROLE_JOB_VIEWER: AccountRole +ACCOUNT_ROLE_JOB_EXECUTOR: AccountRole class GetUserRequest(_message.Message): __slots__ = () @@ -438,3 +451,17 @@ class SetBillingMeterEventRequest(_message.Message): class SetBillingMeterEventResponse(_message.Message): __slots__ = () def __init__(self) -> None: ... + +class SetUserRoleRequest(_message.Message): + __slots__ = ("account_id", "user_id", "role") + ACCOUNT_ID_FIELD_NUMBER: _ClassVar[int] + USER_ID_FIELD_NUMBER: _ClassVar[int] + ROLE_FIELD_NUMBER: _ClassVar[int] + account_id: str + user_id: str + role: AccountRole + def __init__(self, account_id: _Optional[str] = ..., user_id: _Optional[str] = ..., role: _Optional[_Union[AccountRole, str]] = ...) -> None: ... + +class SetUserRoleResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... diff --git a/python/src/neosync/mgmt/v1alpha1/user_account_pb2_grpc.py b/python/src/neosync/mgmt/v1alpha1/user_account_pb2_grpc.py index 27ca5c04cd..fca0d3a589 100644 --- a/python/src/neosync/mgmt/v1alpha1/user_account_pb2_grpc.py +++ b/python/src/neosync/mgmt/v1alpha1/user_account_pb2_grpc.py @@ -134,6 +134,11 @@ def __init__(self, channel): request_serializer=mgmt_dot_v1alpha1_dot_user__account__pb2.SetBillingMeterEventRequest.SerializeToString, response_deserializer=mgmt_dot_v1alpha1_dot_user__account__pb2.SetBillingMeterEventResponse.FromString, _registered_method=True) + self.SetUserRole = channel.unary_unary( + '/mgmt.v1alpha1.UserAccountService/SetUserRole', + request_serializer=mgmt_dot_v1alpha1_dot_user__account__pb2.SetUserRoleRequest.SerializeToString, + response_deserializer=mgmt_dot_v1alpha1_dot_user__account__pb2.SetUserRoleResponse.FromString, + _registered_method=True) class UserAccountServiceServicer(object): @@ -291,6 +296,13 @@ def SetBillingMeterEvent(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def SetUserRole(self, request, context): + """Sets the users role + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def add_UserAccountServiceServicer_to_server(servicer, server): rpc_method_handlers = { @@ -414,6 +426,11 @@ def add_UserAccountServiceServicer_to_server(servicer, server): request_deserializer=mgmt_dot_v1alpha1_dot_user__account__pb2.SetBillingMeterEventRequest.FromString, response_serializer=mgmt_dot_v1alpha1_dot_user__account__pb2.SetBillingMeterEventResponse.SerializeToString, ), + 'SetUserRole': grpc.unary_unary_rpc_method_handler( + servicer.SetUserRole, + request_deserializer=mgmt_dot_v1alpha1_dot_user__account__pb2.SetUserRoleRequest.FromString, + response_serializer=mgmt_dot_v1alpha1_dot_user__account__pb2.SetUserRoleResponse.SerializeToString, + ), } generic_handler = grpc.method_handlers_generic_handler( 'mgmt.v1alpha1.UserAccountService', rpc_method_handlers) @@ -1072,3 +1089,30 @@ def SetBillingMeterEvent(request, timeout, metadata, _registered_method=True) + + @staticmethod + def SetUserRole(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/mgmt.v1alpha1.UserAccountService/SetUserRole', + mgmt_dot_v1alpha1_dot_user__account__pb2.SetUserRoleRequest.SerializeToString, + mgmt_dot_v1alpha1_dot_user__account__pb2.SetUserRoleResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True)