From d1c03fd15c4d107d0a787070834660c0a1396fb1 Mon Sep 17 00:00:00 2001 From: Fabi <38692350+fgerschwiler@users.noreply.github.com> Date: Thu, 12 Aug 2021 16:10:01 +0200 Subject: [PATCH] fix: add user metadata to the features (#2179) * fix: add user metadata to the features * fix: remove user metadata * fix: add test * fix: add test --- docs/docs/apis/proto/admin.md | 2 + internal/api/grpc/admin/features.go | 2 + .../eventsourcing/handler/metadata.go | 2 + .../eventstore/token_verifier.go | 6 + internal/command/features_model.go | 4 + internal/command/iam_features.go | 1 + internal/command/iam_features_model.go | 6 +- internal/command/metadata_model.go | 4 + internal/command/org_features.go | 10 + internal/command/org_features_model.go | 6 +- internal/command/org_features_test.go | 178 ++++++++++++++++++ internal/command/user_metadata.go | 27 +++ internal/command/user_metadata_model.go | 73 ++++++- internal/domain/features.go | 3 + internal/features/model/features_view.go | 4 + .../repository/view/model/features.go | 2 + .../eventsourcing/handler/metadata.go | 2 + internal/repository/features/features.go | 6 + internal/repository/metadata/metadata.go | 32 +++- internal/repository/user/eventstore.go | 1 + internal/repository/user/metadata.go | 29 ++- .../repository/eventsourcing/model/types.go | 5 +- .../V1.60__user_meta_data_feature.sql | 4 + proto/zitadel/admin.proto | 2 + proto/zitadel/features.proto | 1 + 25 files changed, 402 insertions(+), 10 deletions(-) create mode 100644 migrations/cockroach/V1.60__user_meta_data_feature.sql diff --git a/docs/docs/apis/proto/admin.md b/docs/docs/apis/proto/admin.md index b604eeed57..1397afb0c8 100644 --- a/docs/docs/apis/proto/admin.md +++ b/docs/docs/apis/proto/admin.md @@ -2504,6 +2504,7 @@ This is an empty request | label_policy_watermark | bool | - | | | custom_text | bool | - | | | privacy_policy | bool | - | | +| metadata_user | bool | - | | @@ -2689,6 +2690,7 @@ This is an empty request | label_policy_watermark | bool | - | | | custom_text | bool | - | | | privacy_policy | bool | - | | +| metadata_user | bool | - | | diff --git a/internal/api/grpc/admin/features.go b/internal/api/grpc/admin/features.go index 8e3be2a635..55ba275d24 100644 --- a/internal/api/grpc/admin/features.go +++ b/internal/api/grpc/admin/features.go @@ -76,6 +76,7 @@ func setDefaultFeaturesRequestToDomain(req *admin_pb.SetDefaultFeaturesRequest) CustomDomain: req.CustomDomain, CustomText: req.CustomText, PrivacyPolicy: req.PrivacyPolicy, + MetadataUser: req.MetadataUser, } } @@ -98,5 +99,6 @@ func setOrgFeaturesRequestToDomain(req *admin_pb.SetOrgFeaturesRequest) *domain. CustomDomain: req.CustomDomain, CustomText: req.CustomText, PrivacyPolicy: req.PrivacyPolicy, + MetadataUser: req.MetadataUser, } } diff --git a/internal/auth/repository/eventsourcing/handler/metadata.go b/internal/auth/repository/eventsourcing/handler/metadata.go index 7b6510d200..3f951b51a8 100644 --- a/internal/auth/repository/eventsourcing/handler/metadata.go +++ b/internal/auth/repository/eventsourcing/handler/metadata.go @@ -103,6 +103,8 @@ func (m *Metadata) processMetadata(event *es_models.Event) (err error) { return err } return m.view.DeleteMetadata(event.AggregateID, data.Key, event) + case usr_model.UserMetadataRemovedAll: + return m.view.DeleteMetadataByAggregateID(event.AggregateID, event) case usr_model.UserRemoved: return m.view.DeleteMetadataByAggregateID(event.AggregateID, event) default: diff --git a/internal/authz/repository/eventsourcing/eventstore/token_verifier.go b/internal/authz/repository/eventsourcing/eventstore/token_verifier.go index 569edce584..06d042297b 100644 --- a/internal/authz/repository/eventsourcing/eventstore/token_verifier.go +++ b/internal/authz/repository/eventsourcing/eventstore/token_verifier.go @@ -163,6 +163,12 @@ func checkFeatures(features *features_view_model.FeaturesView, requiredFeatures } continue } + if requiredFeature == domain.FeatureMetadataUser { + if !features.MetadataUser { + return MissingFeatureErr(requiredFeature) + } + continue + } return MissingFeatureErr(requiredFeature) } return nil diff --git a/internal/command/features_model.go b/internal/command/features_model.go index 5878b6faad..82df49d78d 100644 --- a/internal/command/features_model.go +++ b/internal/command/features_model.go @@ -28,6 +28,7 @@ type FeaturesWriteModel struct { CustomDomain bool CustomText bool PrivacyPolicy bool + MetadataUser bool } func (wm *FeaturesWriteModel) Reduce() error { @@ -86,6 +87,9 @@ func (wm *FeaturesWriteModel) Reduce() error { if e.CustomText != nil { wm.CustomText = *e.CustomText } + if e.MetadataUser != nil { + wm.MetadataUser = *e.MetadataUser + } case *features.FeaturesRemovedEvent: wm.State = domain.FeaturesStateRemoved } diff --git a/internal/command/iam_features.go b/internal/command/iam_features.go index afdb3fee6e..fcfdc3bbf7 100644 --- a/internal/command/iam_features.go +++ b/internal/command/iam_features.go @@ -51,6 +51,7 @@ func (c *Commands) setDefaultFeatures(ctx context.Context, existingFeatures *IAM features.CustomDomain, features.CustomText, features.PrivacyPolicy, + features.MetadataUser, ) if !hasChanged { return nil, caos_errs.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged") diff --git a/internal/command/iam_features_model.go b/internal/command/iam_features_model.go index 7b79638248..38718c86b6 100644 --- a/internal/command/iam_features_model.go +++ b/internal/command/iam_features_model.go @@ -68,7 +68,8 @@ func (wm *IAMFeaturesWriteModel) NewSetEvent( labelPolicyWatermark, customDomain, customText, - privacyPolicy bool, + privacyPolicy, + metadataUser bool, ) (*iam.FeaturesSetEvent, bool) { changes := make([]features.FeaturesChanges, 0) @@ -121,6 +122,9 @@ func (wm *IAMFeaturesWriteModel) NewSetEvent( if wm.PrivacyPolicy != privacyPolicy { changes = append(changes, features.ChangePrivacyPolicy(privacyPolicy)) } + if wm.MetadataUser != metadataUser { + changes = append(changes, features.ChangeMetadataUser(metadataUser)) + } if len(changes) == 0 { return nil, false } diff --git a/internal/command/metadata_model.go b/internal/command/metadata_model.go index 260d763770..f811b6ed42 100644 --- a/internal/command/metadata_model.go +++ b/internal/command/metadata_model.go @@ -25,6 +25,8 @@ func (wm *MetadataWriteModel) Reduce() error { wm.State = domain.MetadataStateActive case *metadata.RemovedEvent: wm.State = domain.MetadataStateRemoved + case *metadata.RemovedAllEvent: + wm.State = domain.MetadataStateRemoved } } return wm.WriteModel.Reduce() @@ -43,6 +45,8 @@ func (wm *MetadataListWriteModel) Reduce() error { wm.metadataList[e.Key] = e.Value case *metadata.RemovedEvent: delete(wm.metadataList, e.Key) + case *metadata.RemovedAllEvent: + wm.metadataList = make(map[string][]byte) } } return wm.WriteModel.Reduce() diff --git a/internal/command/org_features.go b/internal/command/org_features.go index 9f57e9d7f5..ba26026359 100644 --- a/internal/command/org_features.go +++ b/internal/command/org_features.go @@ -42,6 +42,7 @@ func (c *Commands) SetOrgFeatures(ctx context.Context, resourceOwner string, fea features.CustomDomain, features.CustomText, features.PrivacyPolicy, + features.MetadataUser, ) if !hasChanged { return nil, caos_errs.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged") @@ -146,6 +147,15 @@ func (c *Commands) ensureOrgSettingsToFeatures(ctx context.Context, orgID string events = append(events, removePrivacyPolicyEvent) } } + if !features.MetadataUser { + removeOrgUserMetadatas, err := c.removeUserMetadataFromOrg(ctx, orgID) + if err != nil { + return nil, err + } + if len(removeOrgUserMetadatas) > 0 { + events = append(events, removeOrgUserMetadatas...) + } + } return events, nil } diff --git a/internal/command/org_features_model.go b/internal/command/org_features_model.go index b72ff1f889..0bda8172cd 100644 --- a/internal/command/org_features_model.go +++ b/internal/command/org_features_model.go @@ -75,7 +75,8 @@ func (wm *OrgFeaturesWriteModel) NewSetEvent( labelPolicyWatermark, customDomain, customText, - privacyPolicy bool, + privacyPolicy, + metadataUser bool, ) (*org.FeaturesSetEvent, bool) { changes := make([]features.FeaturesChanges, 0) @@ -131,6 +132,9 @@ func (wm *OrgFeaturesWriteModel) NewSetEvent( if wm.PrivacyPolicy != privacyPolicy { changes = append(changes, features.ChangePrivacyPolicy(privacyPolicy)) } + if wm.MetadataUser != metadataUser { + changes = append(changes, features.ChangeMetadataUser(metadataUser)) + } if len(changes) == 0 { return nil, false diff --git a/internal/command/org_features_test.go b/internal/command/org_features_test.go index 86f845bdbe..53f004b293 100644 --- a/internal/command/org_features_test.go +++ b/internal/command/org_features_test.go @@ -16,6 +16,7 @@ import ( "github.com/caos/zitadel/internal/repository/features" "github.com/caos/zitadel/internal/repository/iam" "github.com/caos/zitadel/internal/repository/org" + "github.com/caos/zitadel/internal/repository/user" "github.com/caos/zitadel/internal/static" "github.com/caos/zitadel/internal/static/mock" ) @@ -99,6 +100,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) { LabelPolicyPrivateLabel: false, LabelPolicyWatermark: false, CustomDomain: false, + MetadataUser: false, }, }, res: res{ @@ -130,6 +132,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) { LabelPolicyPrivateLabel: false, LabelPolicyWatermark: false, CustomDomain: false, + MetadataUser: false, }, }, res: res{ @@ -249,6 +252,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) { ), ), ), + expectFilter(), expectPush( []*repository.Event{ eventFromEventPusher( @@ -278,6 +282,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) { CustomDomain: false, CustomText: false, PrivacyPolicy: false, + MetadataUser: false, }, }, res: res{ @@ -420,6 +425,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) { ), ), ), + expectFilter(), expectPush( []*repository.Event{ eventFromEventPusher( @@ -454,6 +460,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) { LabelPolicyPrivateLabel: false, LabelPolicyWatermark: false, CustomDomain: false, + MetadataUser: false, }, }, res: res{ @@ -603,6 +610,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) { ), ), ), + expectFilter(), expectPush( []*repository.Event{ eventFromEventPusher( @@ -640,6 +648,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) { LabelPolicyPrivateLabel: false, LabelPolicyWatermark: false, CustomDomain: false, + MetadataUser: false, }, }, res: res{ @@ -796,6 +805,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) { ), ), ), + expectFilter(), expectPush( []*repository.Event{ eventFromEventPusher( @@ -836,6 +846,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) { LabelPolicyPrivateLabel: false, LabelPolicyWatermark: false, CustomDomain: false, + MetadataUser: false, }, }, res: res{ @@ -1044,6 +1055,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) { ), ), ), + expectFilter(), expectPush( []*repository.Event{ eventFromEventPusher( @@ -1095,6 +1107,171 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) { LabelPolicyPrivateLabel: false, LabelPolicyWatermark: false, CustomDomain: false, + MetadataUser: false, + }, + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "org1", + }, + }, + }, + { + name: "set with default policies, usermetadata, ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + org.NewOrgAddedEvent( + context.Background(), + &org.NewAggregate("org1", "org1").Aggregate, + "org1", + ), + ), + ), + expectFilter(), + expectFilter( + eventFromEventPusher( + iam.NewLoginPolicyAddedEvent( + context.Background(), + &iam.NewAggregate().Aggregate, + false, + false, + false, + false, + false, + domain.PasswordlessTypeAllowed, + ), + ), + ), + expectFilter( + eventFromEventPusher( + iam.NewPasswordComplexityPolicyAddedEvent( + context.Background(), + &iam.NewAggregate().Aggregate, + 8, + false, + false, + false, + false, + ), + ), + ), + expectFilter( + eventFromEventPusher( + iam.NewLabelPolicyAddedEvent( + context.Background(), + &iam.NewAggregate().Aggregate, + "primary", + "secondary", + "warn", + "font", + "primary-dark", + "secondary-dark", + "warn-dark", + "font-dark", + false, + false, + false, + ), + ), + ), + expectFilter( + eventFromEventPusher( + org.NewOrgAddedEvent( + context.Background(), + &org.NewAggregate("org1", "org1").Aggregate, + "org1", + ), + ), + eventFromEventPusher( + org.NewDomainAddedEvent( + context.Background(), + &org.NewAggregate("org1", "org1").Aggregate, + "org1.iam-domain", + ), + ), + eventFromEventPusher( + org.NewDomainVerifiedEvent( + context.Background(), + &org.NewAggregate("org1", "org1").Aggregate, + "org1.iam-domain", + ), + ), + eventFromEventPusher( + org.NewDomainPrimarySetEvent( + context.Background(), + &org.NewAggregate("org1", "org1").Aggregate, + "org1.iam-domain", + ), + ), + ), + expectFilter( + eventFromEventPusher( + iam.NewCustomTextSetEvent( + context.Background(), + &iam.NewAggregate().Aggregate, + domain.InitCodeMessageType, + domain.MessageSubject, + "text", + language.English, + ), + ), + ), + expectFilter( + eventFromEventPusher( + iam.NewPrivacyPolicyAddedEvent( + context.Background(), + &iam.NewAggregate().Aggregate, + "toslink", + "privacylink", + ), + ), + ), + expectFilter( + eventFromEventPusher( + user.NewMetadataSetEvent( + context.Background(), + &user.NewAggregate("user1", "org1").Aggregate, + "key", + []byte("value"), + ), + ), + ), + expectPush( + []*repository.Event{ + eventFromEventPusher( + user.NewMetadataRemovedAllEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate), + ), + eventFromEventPusher( + newFeaturesSetEvent(context.Background(), "org1", "Test", domain.FeaturesStateActive, time.Hour), + ), + }, + ), + ), + iamDomain: "iam-domain", + }, + args: args{ + ctx: context.Background(), + resourceOwner: "org1", + features: &domain.Features{ + TierName: "Test", + State: domain.FeaturesStateActive, + AuditLogRetention: time.Hour, + LoginPolicyFactors: false, + LoginPolicyIDP: false, + LoginPolicyPasswordless: false, + LoginPolicyRegistration: false, + LoginPolicyUsernameLogin: false, + LoginPolicyPasswordReset: false, + PasswordComplexityPolicy: false, + LabelPolicyPrivateLabel: false, + LabelPolicyWatermark: false, + CustomDomain: false, + CustomText: false, + PrivacyPolicy: false, + MetadataUser: false, }, }, res: res{ @@ -1285,6 +1462,7 @@ func TestCommandSide_RemoveOrgFeatures(t *testing.T) { ), ), ), + expectFilter(), expectPush( []*repository.Event{ eventFromEventPusher( diff --git a/internal/command/user_metadata.go b/internal/command/user_metadata.go index 7f1fc4aaec..d86b88c367 100644 --- a/internal/command/user_metadata.go +++ b/internal/command/user_metadata.go @@ -149,6 +149,24 @@ func (c *Commands) BulkRemoveUserMetadata(ctx context.Context, userID, resourceO return writeModelToObjectDetails(&removeMetadata.WriteModel), nil } +func (c *Commands) removeUserMetadataFromOrg(ctx context.Context, resourceOwner string) ([]eventstore.EventPusher, error) { + existingUserMetadata, err := c.getUserMetadataByOrgListModelByID(ctx, resourceOwner) + if err != nil { + return nil, err + } + if len(existingUserMetadata.UserMetadata) == 0 { + return nil, nil + } + events := make([]eventstore.EventPusher, 0) + for key, value := range existingUserMetadata.UserMetadata { + if len(value) == 0 { + continue + } + events = append(events, user.NewMetadataRemovedAllEvent(ctx, &user.NewAggregate(key, resourceOwner).Aggregate)) + } + return events, nil +} + func (c *Commands) removeUserMetadata(ctx context.Context, userAgg *eventstore.Aggregate, metadataKey string) (pusher eventstore.EventPusher, err error) { pusher = user.NewMetadataRemovedEvent( ctx, @@ -175,3 +193,12 @@ func (c *Commands) getUserMetadataListModelByID(ctx context.Context, userID, res } return userMetadataWriteModel, nil } + +func (c *Commands) getUserMetadataByOrgListModelByID(ctx context.Context, resourceOwner string) (*UserMetadataByOrgListWriteModel, error) { + userMetadataWriteModel := NewUserMetadataByOrgListWriteModel(resourceOwner) + err := c.eventstore.FilterToQueryReducer(ctx, userMetadataWriteModel) + if err != nil { + return nil, err + } + return userMetadataWriteModel, nil +} diff --git a/internal/command/user_metadata_model.go b/internal/command/user_metadata_model.go index 4aed0e6422..756a4f01cf 100644 --- a/internal/command/user_metadata_model.go +++ b/internal/command/user_metadata_model.go @@ -2,6 +2,7 @@ package command import ( "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/metadata" "github.com/caos/zitadel/internal/repository/user" ) @@ -28,6 +29,8 @@ func (wm *UserMetadataWriteModel) AppendEvents(events ...eventstore.EventReader) wm.MetadataWriteModel.AppendEvents(&e.SetEvent) case *user.MetadataRemovedEvent: wm.MetadataWriteModel.AppendEvents(&e.RemovedEvent) + case *user.MetadataRemovedAllEvent: + wm.MetadataWriteModel.AppendEvents(&e.RemovedAllEvent) } } } @@ -44,7 +47,8 @@ func (wm *UserMetadataWriteModel) Query() *eventstore.SearchQueryBuilder { AggregateTypes(user.AggregateType). EventTypes( user.MetadataSetType, - user.MetadataRemovedType). + user.MetadataRemovedType, + user.MetadataRemovedAllType). Builder() } @@ -71,6 +75,8 @@ func (wm *UserMetadataListWriteModel) AppendEvents(events ...eventstore.EventRea wm.MetadataListWriteModel.AppendEvents(&e.SetEvent) case *user.MetadataRemovedEvent: wm.MetadataListWriteModel.AppendEvents(&e.RemovedEvent) + case *user.MetadataRemovedAllEvent: + wm.MetadataListWriteModel.AppendEvents(&e.RemovedAllEvent) } } } @@ -87,6 +93,69 @@ func (wm *UserMetadataListWriteModel) Query() *eventstore.SearchQueryBuilder { AggregateTypes(user.AggregateType). EventTypes( user.MetadataSetType, - user.MetadataRemovedType). + user.MetadataRemovedType, + user.MetadataRemovedAllType). + Builder() +} + +type UserMetadataByOrgListWriteModel struct { + eventstore.WriteModel + resourceOwner string + UserMetadata map[string]map[string][]byte +} + +func NewUserMetadataByOrgListWriteModel(resourceOwner string) *UserMetadataByOrgListWriteModel { + return &UserMetadataByOrgListWriteModel{ + resourceOwner: resourceOwner, + UserMetadata: make(map[string]map[string][]byte), + } +} + +func (wm *UserMetadataByOrgListWriteModel) AppendEvents(events ...eventstore.EventReader) { + for _, event := range events { + switch e := event.(type) { + case *user.MetadataSetEvent: + wm.WriteModel.AppendEvents(&e.SetEvent) + case *user.MetadataRemovedEvent: + wm.WriteModel.AppendEvents(&e.RemovedEvent) + case *user.MetadataRemovedAllEvent: + wm.WriteModel.AppendEvents(&e.RemovedAllEvent) + } + } +} + +func (wm *UserMetadataByOrgListWriteModel) Reduce() error { + for _, event := range wm.Events { + switch e := event.(type) { + case *metadata.SetEvent: + if val, ok := wm.UserMetadata[e.Aggregate().ID]; ok { + val[e.Key] = e.Value + } else { + wm.UserMetadata[e.Aggregate().ID] = map[string][]byte{ + e.Key: e.Value, + } + } + case *metadata.RemovedEvent: + if val, ok := wm.UserMetadata[e.Aggregate().ID]; ok { + delete(val, e.Key) + } + case *metadata.RemovedAllEvent: + if _, ok := wm.UserMetadata[e.Aggregate().ID]; ok { + delete(wm.UserMetadata, e.Aggregate().ID) + } + } + } + return wm.WriteModel.Reduce() +} + +func (wm *UserMetadataByOrgListWriteModel) Query() *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + ResourceOwner(wm.ResourceOwner). + AddQuery(). + AggregateTypes(user.AggregateType). + EventTypes( + user.MetadataSetType, + user.MetadataRemovedType, + user.MetadataRemovedAllType). Builder() } diff --git a/internal/domain/features.go b/internal/domain/features.go index b07f35dfe6..1ca4eb71e9 100644 --- a/internal/domain/features.go +++ b/internal/domain/features.go @@ -21,6 +21,8 @@ const ( FeatureCustomText = "custom_text" FeatureCustomDomain = "custom_domain" FeaturePrivacyPolicy = "privacy_policy" + FeatureMetadata = "metadata" + FeatureMetadataUser = FeatureMetadata + ".user" ) type Features struct { @@ -45,6 +47,7 @@ type Features struct { CustomDomain bool CustomText bool PrivacyPolicy bool + MetadataUser bool } type FeaturesState int32 diff --git a/internal/features/model/features_view.go b/internal/features/model/features_view.go index 4cf7519a2b..cd0f2f1917 100644 --- a/internal/features/model/features_view.go +++ b/internal/features/model/features_view.go @@ -30,6 +30,7 @@ type FeaturesView struct { CustomDomain bool CustomText bool PrivacyPolicy bool + MetadataUser bool } func (f *FeaturesView) FeatureList() []string { @@ -70,6 +71,9 @@ func (f *FeaturesView) FeatureList() []string { if f.PrivacyPolicy { list = append(list, domain.FeaturePrivacyPolicy) } + if f.MetadataUser { + list = append(list, domain.FeatureMetadataUser) + } return list } diff --git a/internal/features/repository/view/model/features.go b/internal/features/repository/view/model/features.go index b8fe4819ff..e1779d0046 100644 --- a/internal/features/repository/view/model/features.go +++ b/internal/features/repository/view/model/features.go @@ -44,6 +44,7 @@ type FeaturesView struct { CustomDomain bool `json:"customDomain" gorm:"column:custom_domain"` CustomText bool `json:"customText" gorm:"column:custom_text"` PrivacyPolicy bool `json:"privacyPolicy" gorm:"column:privacy_policy"` + MetadataUser bool `json:"metadataUser" gorm:"column:metadata_user"` } func FeaturesToModel(features *FeaturesView) *features_model.FeaturesView { @@ -70,6 +71,7 @@ func FeaturesToModel(features *FeaturesView) *features_model.FeaturesView { CustomDomain: features.CustomDomain, CustomText: features.CustomText, PrivacyPolicy: features.PrivacyPolicy, + MetadataUser: features.MetadataUser, } } diff --git a/internal/management/repository/eventsourcing/handler/metadata.go b/internal/management/repository/eventsourcing/handler/metadata.go index 1d2b7f86b5..8c4fcdcaaf 100644 --- a/internal/management/repository/eventsourcing/handler/metadata.go +++ b/internal/management/repository/eventsourcing/handler/metadata.go @@ -103,6 +103,8 @@ func (m *Metadata) processMetadata(event *es_models.Event) (err error) { return err } return m.view.DeleteMetadata(event.AggregateID, data.Key, event) + case usr_model.UserMetadataRemovedAll: + return m.view.DeleteMetadataByAggregateID(event.AggregateID, event) case usr_model.UserRemoved: return m.view.DeleteMetadataByAggregateID(event.AggregateID, event) default: diff --git a/internal/repository/features/features.go b/internal/repository/features/features.go index fa89f0ac75..32dd8f3d79 100644 --- a/internal/repository/features/features.go +++ b/internal/repository/features/features.go @@ -37,6 +37,7 @@ type FeaturesSetEvent struct { CustomDomain *bool `json:"customDomain,omitempty"` CustomText *bool `json:"customText,omitempty"` PrivacyPolicy *bool `json:"privacyPolicy,omitempty"` + MetadataUser *bool `json:"metadataUser,omitempty"` } func (e *FeaturesSetEvent) Data() interface{} { @@ -167,6 +168,11 @@ func ChangePrivacyPolicy(privacyPolicy bool) func(event *FeaturesSetEvent) { } } +func ChangeMetadataUser(metadataUser bool) func(event *FeaturesSetEvent) { + return func(e *FeaturesSetEvent) { + e.MetadataUser = &metadataUser + } +} func FeaturesSetEventMapper(event *repository.Event) (eventstore.EventReader, error) { e := &FeaturesSetEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), diff --git a/internal/repository/metadata/metadata.go b/internal/repository/metadata/metadata.go index 3cb8c8d3a9..c19311da60 100644 --- a/internal/repository/metadata/metadata.go +++ b/internal/repository/metadata/metadata.go @@ -9,8 +9,9 @@ import ( ) const ( - SetEventType = "metadata.set" - RemovedEventType = "metadata.removed" + SetEventType = "metadata.set" + RemovedEventType = "metadata.removed" + RemovedAllEventType = "metadata.removed.all" ) type SetEvent struct { @@ -90,3 +91,30 @@ func RemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) return e, nil } + +type RemovedAllEvent struct { + eventstore.BaseEvent `json:"-"` +} + +func (e *RemovedAllEvent) Data() interface{} { + return nil +} + +func (e *RemovedAllEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + +func NewRemovedAllEvent( + base *eventstore.BaseEvent, +) *RemovedAllEvent { + + return &RemovedAllEvent{ + BaseEvent: *base, + } +} + +func RemovedAllEventMapper(event *repository.Event) (eventstore.EventReader, error) { + return &RemovedAllEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(event), + }, nil +} diff --git a/internal/repository/user/eventstore.go b/internal/repository/user/eventstore.go index fe82878095..0eb3d1eed8 100644 --- a/internal/repository/user/eventstore.go +++ b/internal/repository/user/eventstore.go @@ -47,6 +47,7 @@ func RegisterEventMappers(es *eventstore.Eventstore) { RegisterFilterEventMapper(UserUserNameChangedType, UsernameChangedEventMapper). RegisterFilterEventMapper(MetadataSetType, MetadataSetEventMapper). RegisterFilterEventMapper(MetadataRemovedType, MetadataRemovedEventMapper). + RegisterFilterEventMapper(MetadataRemovedAllType, MetadataRemovedAllEventMapper). RegisterFilterEventMapper(HumanAddedType, HumanAddedEventMapper). RegisterFilterEventMapper(HumanRegisteredType, HumanRegisteredEventMapper). RegisterFilterEventMapper(HumanInitialCodeAddedType, HumanInitialCodeAddedEventMapper). diff --git a/internal/repository/user/metadata.go b/internal/repository/user/metadata.go index 337dbe03e3..66a8a918ae 100644 --- a/internal/repository/user/metadata.go +++ b/internal/repository/user/metadata.go @@ -9,8 +9,9 @@ import ( ) const ( - MetadataSetType = userEventTypePrefix + metadata.SetEventType - MetadataRemovedType = userEventTypePrefix + metadata.RemovedEventType + MetadataSetType = userEventTypePrefix + metadata.SetEventType + MetadataRemovedType = userEventTypePrefix + metadata.RemovedEventType + MetadataRemovedAllType = userEventTypePrefix + metadata.RemovedAllEventType ) type MetadataSetEvent struct { @@ -61,3 +62,27 @@ func MetadataRemovedEventMapper(event *repository.Event) (eventstore.EventReader return &MetadataRemovedEvent{RemovedEvent: *e.(*metadata.RemovedEvent)}, nil } + +type MetadataRemovedAllEvent struct { + metadata.RemovedAllEvent +} + +func NewMetadataRemovedAllEvent(ctx context.Context, aggregate *eventstore.Aggregate) *MetadataRemovedAllEvent { + return &MetadataRemovedAllEvent{ + RemovedAllEvent: *metadata.NewRemovedAllEvent( + eventstore.NewBaseEventForPush( + ctx, + aggregate, + MetadataRemovedAllType), + ), + } +} + +func MetadataRemovedAllEventMapper(event *repository.Event) (eventstore.EventReader, error) { + e, err := metadata.RemovedAllEventMapper(event) + if err != nil { + return nil, err + } + + return &MetadataRemovedAllEvent{RemovedAllEvent: *e.(*metadata.RemovedAllEvent)}, nil +} diff --git a/internal/user/repository/eventsourcing/model/types.go b/internal/user/repository/eventsourcing/model/types.go index bb171f351b..87b06cd5a5 100644 --- a/internal/user/repository/eventsourcing/model/types.go +++ b/internal/user/repository/eventsourcing/model/types.go @@ -70,8 +70,9 @@ const ( DomainClaimed models.EventType = "user.domain.claimed" DomainClaimedSent models.EventType = "user.domain.claimed.sent" - UserMetadataSet models.EventType = "user.metadata.set" - UserMetadataRemoved models.EventType = "user.metadata.removed" + UserMetadataSet models.EventType = "user.metadata.set" + UserMetadataRemoved models.EventType = "user.metadata.removed" + UserMetadataRemovedAll models.EventType = "user.metadata.removed.all" ) // the following consts are for user(v2).human diff --git a/migrations/cockroach/V1.60__user_meta_data_feature.sql b/migrations/cockroach/V1.60__user_meta_data_feature.sql new file mode 100644 index 0000000000..30190b5baa --- /dev/null +++ b/migrations/cockroach/V1.60__user_meta_data_feature.sql @@ -0,0 +1,4 @@ +ALTER TABLE adminapi.features ADD COLUMN metadata_user BOOLEAN; +ALTER TABLE auth.features ADD COLUMN metadata_user BOOLEAN; +ALTER TABLE authz.features ADD COLUMN metadata_user BOOLEAN; +ALTER TABLE management.features ADD COLUMN metadata_user BOOLEAN; \ No newline at end of file diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto index 9cd9185b5c..3143979def 100644 --- a/proto/zitadel/admin.proto +++ b/proto/zitadel/admin.proto @@ -2612,6 +2612,7 @@ message SetDefaultFeaturesRequest { bool label_policy_watermark = 16; bool custom_text = 17; bool privacy_policy = 18; + bool metadata_user = 19; } message SetDefaultFeaturesResponse { @@ -2647,6 +2648,7 @@ message SetOrgFeaturesRequest { bool label_policy_watermark = 17; bool custom_text = 18; bool privacy_policy = 19; + bool metadata_user = 20; } message SetOrgFeaturesResponse { diff --git a/proto/zitadel/features.proto b/proto/zitadel/features.proto index 93e1937917..589193b068 100644 --- a/proto/zitadel/features.proto +++ b/proto/zitadel/features.proto @@ -26,6 +26,7 @@ message Features { bool label_policy_watermark = 15; bool custom_text = 16; bool privacy_policy = 17; + bool metadata_user = 18; } message FeatureTier {