mirror of
https://github.com/zitadel/zitadel.git
synced 2025-03-01 00:07:22 +00:00
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
This commit is contained in:
parent
b104011418
commit
d1c03fd15c
@ -2504,6 +2504,7 @@ This is an empty request
|
|||||||
| label_policy_watermark | bool | - | |
|
| label_policy_watermark | bool | - | |
|
||||||
| custom_text | bool | - | |
|
| custom_text | bool | - | |
|
||||||
| privacy_policy | bool | - | |
|
| privacy_policy | bool | - | |
|
||||||
|
| metadata_user | bool | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -2689,6 +2690,7 @@ This is an empty request
|
|||||||
| label_policy_watermark | bool | - | |
|
| label_policy_watermark | bool | - | |
|
||||||
| custom_text | bool | - | |
|
| custom_text | bool | - | |
|
||||||
| privacy_policy | bool | - | |
|
| privacy_policy | bool | - | |
|
||||||
|
| metadata_user | bool | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -76,6 +76,7 @@ func setDefaultFeaturesRequestToDomain(req *admin_pb.SetDefaultFeaturesRequest)
|
|||||||
CustomDomain: req.CustomDomain,
|
CustomDomain: req.CustomDomain,
|
||||||
CustomText: req.CustomText,
|
CustomText: req.CustomText,
|
||||||
PrivacyPolicy: req.PrivacyPolicy,
|
PrivacyPolicy: req.PrivacyPolicy,
|
||||||
|
MetadataUser: req.MetadataUser,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,5 +99,6 @@ func setOrgFeaturesRequestToDomain(req *admin_pb.SetOrgFeaturesRequest) *domain.
|
|||||||
CustomDomain: req.CustomDomain,
|
CustomDomain: req.CustomDomain,
|
||||||
CustomText: req.CustomText,
|
CustomText: req.CustomText,
|
||||||
PrivacyPolicy: req.PrivacyPolicy,
|
PrivacyPolicy: req.PrivacyPolicy,
|
||||||
|
MetadataUser: req.MetadataUser,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,6 +103,8 @@ func (m *Metadata) processMetadata(event *es_models.Event) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return m.view.DeleteMetadata(event.AggregateID, data.Key, event)
|
return m.view.DeleteMetadata(event.AggregateID, data.Key, event)
|
||||||
|
case usr_model.UserMetadataRemovedAll:
|
||||||
|
return m.view.DeleteMetadataByAggregateID(event.AggregateID, event)
|
||||||
case usr_model.UserRemoved:
|
case usr_model.UserRemoved:
|
||||||
return m.view.DeleteMetadataByAggregateID(event.AggregateID, event)
|
return m.view.DeleteMetadataByAggregateID(event.AggregateID, event)
|
||||||
default:
|
default:
|
||||||
|
@ -163,6 +163,12 @@ func checkFeatures(features *features_view_model.FeaturesView, requiredFeatures
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if requiredFeature == domain.FeatureMetadataUser {
|
||||||
|
if !features.MetadataUser {
|
||||||
|
return MissingFeatureErr(requiredFeature)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
return MissingFeatureErr(requiredFeature)
|
return MissingFeatureErr(requiredFeature)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -28,6 +28,7 @@ type FeaturesWriteModel struct {
|
|||||||
CustomDomain bool
|
CustomDomain bool
|
||||||
CustomText bool
|
CustomText bool
|
||||||
PrivacyPolicy bool
|
PrivacyPolicy bool
|
||||||
|
MetadataUser bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wm *FeaturesWriteModel) Reduce() error {
|
func (wm *FeaturesWriteModel) Reduce() error {
|
||||||
@ -86,6 +87,9 @@ func (wm *FeaturesWriteModel) Reduce() error {
|
|||||||
if e.CustomText != nil {
|
if e.CustomText != nil {
|
||||||
wm.CustomText = *e.CustomText
|
wm.CustomText = *e.CustomText
|
||||||
}
|
}
|
||||||
|
if e.MetadataUser != nil {
|
||||||
|
wm.MetadataUser = *e.MetadataUser
|
||||||
|
}
|
||||||
case *features.FeaturesRemovedEvent:
|
case *features.FeaturesRemovedEvent:
|
||||||
wm.State = domain.FeaturesStateRemoved
|
wm.State = domain.FeaturesStateRemoved
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,7 @@ func (c *Commands) setDefaultFeatures(ctx context.Context, existingFeatures *IAM
|
|||||||
features.CustomDomain,
|
features.CustomDomain,
|
||||||
features.CustomText,
|
features.CustomText,
|
||||||
features.PrivacyPolicy,
|
features.PrivacyPolicy,
|
||||||
|
features.MetadataUser,
|
||||||
)
|
)
|
||||||
if !hasChanged {
|
if !hasChanged {
|
||||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged")
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged")
|
||||||
|
@ -68,7 +68,8 @@ func (wm *IAMFeaturesWriteModel) NewSetEvent(
|
|||||||
labelPolicyWatermark,
|
labelPolicyWatermark,
|
||||||
customDomain,
|
customDomain,
|
||||||
customText,
|
customText,
|
||||||
privacyPolicy bool,
|
privacyPolicy,
|
||||||
|
metadataUser bool,
|
||||||
) (*iam.FeaturesSetEvent, bool) {
|
) (*iam.FeaturesSetEvent, bool) {
|
||||||
|
|
||||||
changes := make([]features.FeaturesChanges, 0)
|
changes := make([]features.FeaturesChanges, 0)
|
||||||
@ -121,6 +122,9 @@ func (wm *IAMFeaturesWriteModel) NewSetEvent(
|
|||||||
if wm.PrivacyPolicy != privacyPolicy {
|
if wm.PrivacyPolicy != privacyPolicy {
|
||||||
changes = append(changes, features.ChangePrivacyPolicy(privacyPolicy))
|
changes = append(changes, features.ChangePrivacyPolicy(privacyPolicy))
|
||||||
}
|
}
|
||||||
|
if wm.MetadataUser != metadataUser {
|
||||||
|
changes = append(changes, features.ChangeMetadataUser(metadataUser))
|
||||||
|
}
|
||||||
if len(changes) == 0 {
|
if len(changes) == 0 {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,8 @@ func (wm *MetadataWriteModel) Reduce() error {
|
|||||||
wm.State = domain.MetadataStateActive
|
wm.State = domain.MetadataStateActive
|
||||||
case *metadata.RemovedEvent:
|
case *metadata.RemovedEvent:
|
||||||
wm.State = domain.MetadataStateRemoved
|
wm.State = domain.MetadataStateRemoved
|
||||||
|
case *metadata.RemovedAllEvent:
|
||||||
|
wm.State = domain.MetadataStateRemoved
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return wm.WriteModel.Reduce()
|
return wm.WriteModel.Reduce()
|
||||||
@ -43,6 +45,8 @@ func (wm *MetadataListWriteModel) Reduce() error {
|
|||||||
wm.metadataList[e.Key] = e.Value
|
wm.metadataList[e.Key] = e.Value
|
||||||
case *metadata.RemovedEvent:
|
case *metadata.RemovedEvent:
|
||||||
delete(wm.metadataList, e.Key)
|
delete(wm.metadataList, e.Key)
|
||||||
|
case *metadata.RemovedAllEvent:
|
||||||
|
wm.metadataList = make(map[string][]byte)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return wm.WriteModel.Reduce()
|
return wm.WriteModel.Reduce()
|
||||||
|
@ -42,6 +42,7 @@ func (c *Commands) SetOrgFeatures(ctx context.Context, resourceOwner string, fea
|
|||||||
features.CustomDomain,
|
features.CustomDomain,
|
||||||
features.CustomText,
|
features.CustomText,
|
||||||
features.PrivacyPolicy,
|
features.PrivacyPolicy,
|
||||||
|
features.MetadataUser,
|
||||||
)
|
)
|
||||||
if !hasChanged {
|
if !hasChanged {
|
||||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged")
|
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)
|
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
|
return events, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +75,8 @@ func (wm *OrgFeaturesWriteModel) NewSetEvent(
|
|||||||
labelPolicyWatermark,
|
labelPolicyWatermark,
|
||||||
customDomain,
|
customDomain,
|
||||||
customText,
|
customText,
|
||||||
privacyPolicy bool,
|
privacyPolicy,
|
||||||
|
metadataUser bool,
|
||||||
) (*org.FeaturesSetEvent, bool) {
|
) (*org.FeaturesSetEvent, bool) {
|
||||||
|
|
||||||
changes := make([]features.FeaturesChanges, 0)
|
changes := make([]features.FeaturesChanges, 0)
|
||||||
@ -131,6 +132,9 @@ func (wm *OrgFeaturesWriteModel) NewSetEvent(
|
|||||||
if wm.PrivacyPolicy != privacyPolicy {
|
if wm.PrivacyPolicy != privacyPolicy {
|
||||||
changes = append(changes, features.ChangePrivacyPolicy(privacyPolicy))
|
changes = append(changes, features.ChangePrivacyPolicy(privacyPolicy))
|
||||||
}
|
}
|
||||||
|
if wm.MetadataUser != metadataUser {
|
||||||
|
changes = append(changes, features.ChangeMetadataUser(metadataUser))
|
||||||
|
}
|
||||||
|
|
||||||
if len(changes) == 0 {
|
if len(changes) == 0 {
|
||||||
return nil, false
|
return nil, false
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/caos/zitadel/internal/repository/features"
|
"github.com/caos/zitadel/internal/repository/features"
|
||||||
"github.com/caos/zitadel/internal/repository/iam"
|
"github.com/caos/zitadel/internal/repository/iam"
|
||||||
"github.com/caos/zitadel/internal/repository/org"
|
"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"
|
||||||
"github.com/caos/zitadel/internal/static/mock"
|
"github.com/caos/zitadel/internal/static/mock"
|
||||||
)
|
)
|
||||||
@ -99,6 +100,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
|||||||
LabelPolicyPrivateLabel: false,
|
LabelPolicyPrivateLabel: false,
|
||||||
LabelPolicyWatermark: false,
|
LabelPolicyWatermark: false,
|
||||||
CustomDomain: false,
|
CustomDomain: false,
|
||||||
|
MetadataUser: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
@ -130,6 +132,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
|||||||
LabelPolicyPrivateLabel: false,
|
LabelPolicyPrivateLabel: false,
|
||||||
LabelPolicyWatermark: false,
|
LabelPolicyWatermark: false,
|
||||||
CustomDomain: false,
|
CustomDomain: false,
|
||||||
|
MetadataUser: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
@ -249,6 +252,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
[]*repository.Event{
|
[]*repository.Event{
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
@ -278,6 +282,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
|||||||
CustomDomain: false,
|
CustomDomain: false,
|
||||||
CustomText: false,
|
CustomText: false,
|
||||||
PrivacyPolicy: false,
|
PrivacyPolicy: false,
|
||||||
|
MetadataUser: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
@ -420,6 +425,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
[]*repository.Event{
|
[]*repository.Event{
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
@ -454,6 +460,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
|||||||
LabelPolicyPrivateLabel: false,
|
LabelPolicyPrivateLabel: false,
|
||||||
LabelPolicyWatermark: false,
|
LabelPolicyWatermark: false,
|
||||||
CustomDomain: false,
|
CustomDomain: false,
|
||||||
|
MetadataUser: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
@ -603,6 +610,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
[]*repository.Event{
|
[]*repository.Event{
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
@ -640,6 +648,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
|||||||
LabelPolicyPrivateLabel: false,
|
LabelPolicyPrivateLabel: false,
|
||||||
LabelPolicyWatermark: false,
|
LabelPolicyWatermark: false,
|
||||||
CustomDomain: false,
|
CustomDomain: false,
|
||||||
|
MetadataUser: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
@ -796,6 +805,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
[]*repository.Event{
|
[]*repository.Event{
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
@ -836,6 +846,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
|||||||
LabelPolicyPrivateLabel: false,
|
LabelPolicyPrivateLabel: false,
|
||||||
LabelPolicyWatermark: false,
|
LabelPolicyWatermark: false,
|
||||||
CustomDomain: false,
|
CustomDomain: false,
|
||||||
|
MetadataUser: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
@ -1044,6 +1055,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
[]*repository.Event{
|
[]*repository.Event{
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
@ -1095,6 +1107,171 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
|||||||
LabelPolicyPrivateLabel: false,
|
LabelPolicyPrivateLabel: false,
|
||||||
LabelPolicyWatermark: false,
|
LabelPolicyWatermark: false,
|
||||||
CustomDomain: 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{
|
res: res{
|
||||||
@ -1285,6 +1462,7 @@ func TestCommandSide_RemoveOrgFeatures(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
[]*repository.Event{
|
[]*repository.Event{
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
|
@ -149,6 +149,24 @@ func (c *Commands) BulkRemoveUserMetadata(ctx context.Context, userID, resourceO
|
|||||||
return writeModelToObjectDetails(&removeMetadata.WriteModel), nil
|
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) {
|
func (c *Commands) removeUserMetadata(ctx context.Context, userAgg *eventstore.Aggregate, metadataKey string) (pusher eventstore.EventPusher, err error) {
|
||||||
pusher = user.NewMetadataRemovedEvent(
|
pusher = user.NewMetadataRemovedEvent(
|
||||||
ctx,
|
ctx,
|
||||||
@ -175,3 +193,12 @@ func (c *Commands) getUserMetadataListModelByID(ctx context.Context, userID, res
|
|||||||
}
|
}
|
||||||
return userMetadataWriteModel, nil
|
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
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ package command
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/caos/zitadel/internal/eventstore"
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
|
"github.com/caos/zitadel/internal/repository/metadata"
|
||||||
"github.com/caos/zitadel/internal/repository/user"
|
"github.com/caos/zitadel/internal/repository/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,6 +29,8 @@ func (wm *UserMetadataWriteModel) AppendEvents(events ...eventstore.EventReader)
|
|||||||
wm.MetadataWriteModel.AppendEvents(&e.SetEvent)
|
wm.MetadataWriteModel.AppendEvents(&e.SetEvent)
|
||||||
case *user.MetadataRemovedEvent:
|
case *user.MetadataRemovedEvent:
|
||||||
wm.MetadataWriteModel.AppendEvents(&e.RemovedEvent)
|
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).
|
AggregateTypes(user.AggregateType).
|
||||||
EventTypes(
|
EventTypes(
|
||||||
user.MetadataSetType,
|
user.MetadataSetType,
|
||||||
user.MetadataRemovedType).
|
user.MetadataRemovedType,
|
||||||
|
user.MetadataRemovedAllType).
|
||||||
Builder()
|
Builder()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,6 +75,8 @@ func (wm *UserMetadataListWriteModel) AppendEvents(events ...eventstore.EventRea
|
|||||||
wm.MetadataListWriteModel.AppendEvents(&e.SetEvent)
|
wm.MetadataListWriteModel.AppendEvents(&e.SetEvent)
|
||||||
case *user.MetadataRemovedEvent:
|
case *user.MetadataRemovedEvent:
|
||||||
wm.MetadataListWriteModel.AppendEvents(&e.RemovedEvent)
|
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).
|
AggregateTypes(user.AggregateType).
|
||||||
EventTypes(
|
EventTypes(
|
||||||
user.MetadataSetType,
|
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()
|
Builder()
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,8 @@ const (
|
|||||||
FeatureCustomText = "custom_text"
|
FeatureCustomText = "custom_text"
|
||||||
FeatureCustomDomain = "custom_domain"
|
FeatureCustomDomain = "custom_domain"
|
||||||
FeaturePrivacyPolicy = "privacy_policy"
|
FeaturePrivacyPolicy = "privacy_policy"
|
||||||
|
FeatureMetadata = "metadata"
|
||||||
|
FeatureMetadataUser = FeatureMetadata + ".user"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Features struct {
|
type Features struct {
|
||||||
@ -45,6 +47,7 @@ type Features struct {
|
|||||||
CustomDomain bool
|
CustomDomain bool
|
||||||
CustomText bool
|
CustomText bool
|
||||||
PrivacyPolicy bool
|
PrivacyPolicy bool
|
||||||
|
MetadataUser bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type FeaturesState int32
|
type FeaturesState int32
|
||||||
|
@ -30,6 +30,7 @@ type FeaturesView struct {
|
|||||||
CustomDomain bool
|
CustomDomain bool
|
||||||
CustomText bool
|
CustomText bool
|
||||||
PrivacyPolicy bool
|
PrivacyPolicy bool
|
||||||
|
MetadataUser bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FeaturesView) FeatureList() []string {
|
func (f *FeaturesView) FeatureList() []string {
|
||||||
@ -70,6 +71,9 @@ func (f *FeaturesView) FeatureList() []string {
|
|||||||
if f.PrivacyPolicy {
|
if f.PrivacyPolicy {
|
||||||
list = append(list, domain.FeaturePrivacyPolicy)
|
list = append(list, domain.FeaturePrivacyPolicy)
|
||||||
}
|
}
|
||||||
|
if f.MetadataUser {
|
||||||
|
list = append(list, domain.FeatureMetadataUser)
|
||||||
|
}
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,7 @@ type FeaturesView struct {
|
|||||||
CustomDomain bool `json:"customDomain" gorm:"column:custom_domain"`
|
CustomDomain bool `json:"customDomain" gorm:"column:custom_domain"`
|
||||||
CustomText bool `json:"customText" gorm:"column:custom_text"`
|
CustomText bool `json:"customText" gorm:"column:custom_text"`
|
||||||
PrivacyPolicy bool `json:"privacyPolicy" gorm:"column:privacy_policy"`
|
PrivacyPolicy bool `json:"privacyPolicy" gorm:"column:privacy_policy"`
|
||||||
|
MetadataUser bool `json:"metadataUser" gorm:"column:metadata_user"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func FeaturesToModel(features *FeaturesView) *features_model.FeaturesView {
|
func FeaturesToModel(features *FeaturesView) *features_model.FeaturesView {
|
||||||
@ -70,6 +71,7 @@ func FeaturesToModel(features *FeaturesView) *features_model.FeaturesView {
|
|||||||
CustomDomain: features.CustomDomain,
|
CustomDomain: features.CustomDomain,
|
||||||
CustomText: features.CustomText,
|
CustomText: features.CustomText,
|
||||||
PrivacyPolicy: features.PrivacyPolicy,
|
PrivacyPolicy: features.PrivacyPolicy,
|
||||||
|
MetadataUser: features.MetadataUser,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,6 +103,8 @@ func (m *Metadata) processMetadata(event *es_models.Event) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return m.view.DeleteMetadata(event.AggregateID, data.Key, event)
|
return m.view.DeleteMetadata(event.AggregateID, data.Key, event)
|
||||||
|
case usr_model.UserMetadataRemovedAll:
|
||||||
|
return m.view.DeleteMetadataByAggregateID(event.AggregateID, event)
|
||||||
case usr_model.UserRemoved:
|
case usr_model.UserRemoved:
|
||||||
return m.view.DeleteMetadataByAggregateID(event.AggregateID, event)
|
return m.view.DeleteMetadataByAggregateID(event.AggregateID, event)
|
||||||
default:
|
default:
|
||||||
|
@ -37,6 +37,7 @@ type FeaturesSetEvent struct {
|
|||||||
CustomDomain *bool `json:"customDomain,omitempty"`
|
CustomDomain *bool `json:"customDomain,omitempty"`
|
||||||
CustomText *bool `json:"customText,omitempty"`
|
CustomText *bool `json:"customText,omitempty"`
|
||||||
PrivacyPolicy *bool `json:"privacyPolicy,omitempty"`
|
PrivacyPolicy *bool `json:"privacyPolicy,omitempty"`
|
||||||
|
MetadataUser *bool `json:"metadataUser,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *FeaturesSetEvent) Data() interface{} {
|
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) {
|
func FeaturesSetEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||||
e := &FeaturesSetEvent{
|
e := &FeaturesSetEvent{
|
||||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||||
|
@ -9,8 +9,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SetEventType = "metadata.set"
|
SetEventType = "metadata.set"
|
||||||
RemovedEventType = "metadata.removed"
|
RemovedEventType = "metadata.removed"
|
||||||
|
RemovedAllEventType = "metadata.removed.all"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SetEvent struct {
|
type SetEvent struct {
|
||||||
@ -90,3 +91,30 @@ func RemovedEventMapper(event *repository.Event) (eventstore.EventReader, error)
|
|||||||
|
|
||||||
return e, nil
|
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
|
||||||
|
}
|
||||||
|
@ -47,6 +47,7 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
|
|||||||
RegisterFilterEventMapper(UserUserNameChangedType, UsernameChangedEventMapper).
|
RegisterFilterEventMapper(UserUserNameChangedType, UsernameChangedEventMapper).
|
||||||
RegisterFilterEventMapper(MetadataSetType, MetadataSetEventMapper).
|
RegisterFilterEventMapper(MetadataSetType, MetadataSetEventMapper).
|
||||||
RegisterFilterEventMapper(MetadataRemovedType, MetadataRemovedEventMapper).
|
RegisterFilterEventMapper(MetadataRemovedType, MetadataRemovedEventMapper).
|
||||||
|
RegisterFilterEventMapper(MetadataRemovedAllType, MetadataRemovedAllEventMapper).
|
||||||
RegisterFilterEventMapper(HumanAddedType, HumanAddedEventMapper).
|
RegisterFilterEventMapper(HumanAddedType, HumanAddedEventMapper).
|
||||||
RegisterFilterEventMapper(HumanRegisteredType, HumanRegisteredEventMapper).
|
RegisterFilterEventMapper(HumanRegisteredType, HumanRegisteredEventMapper).
|
||||||
RegisterFilterEventMapper(HumanInitialCodeAddedType, HumanInitialCodeAddedEventMapper).
|
RegisterFilterEventMapper(HumanInitialCodeAddedType, HumanInitialCodeAddedEventMapper).
|
||||||
|
@ -9,8 +9,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MetadataSetType = userEventTypePrefix + metadata.SetEventType
|
MetadataSetType = userEventTypePrefix + metadata.SetEventType
|
||||||
MetadataRemovedType = userEventTypePrefix + metadata.RemovedEventType
|
MetadataRemovedType = userEventTypePrefix + metadata.RemovedEventType
|
||||||
|
MetadataRemovedAllType = userEventTypePrefix + metadata.RemovedAllEventType
|
||||||
)
|
)
|
||||||
|
|
||||||
type MetadataSetEvent struct {
|
type MetadataSetEvent struct {
|
||||||
@ -61,3 +62,27 @@ func MetadataRemovedEventMapper(event *repository.Event) (eventstore.EventReader
|
|||||||
|
|
||||||
return &MetadataRemovedEvent{RemovedEvent: *e.(*metadata.RemovedEvent)}, nil
|
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
|
||||||
|
}
|
||||||
|
@ -70,8 +70,9 @@ const (
|
|||||||
DomainClaimed models.EventType = "user.domain.claimed"
|
DomainClaimed models.EventType = "user.domain.claimed"
|
||||||
DomainClaimedSent models.EventType = "user.domain.claimed.sent"
|
DomainClaimedSent models.EventType = "user.domain.claimed.sent"
|
||||||
|
|
||||||
UserMetadataSet models.EventType = "user.metadata.set"
|
UserMetadataSet models.EventType = "user.metadata.set"
|
||||||
UserMetadataRemoved models.EventType = "user.metadata.removed"
|
UserMetadataRemoved models.EventType = "user.metadata.removed"
|
||||||
|
UserMetadataRemovedAll models.EventType = "user.metadata.removed.all"
|
||||||
)
|
)
|
||||||
|
|
||||||
// the following consts are for user(v2).human
|
// the following consts are for user(v2).human
|
||||||
|
4
migrations/cockroach/V1.60__user_meta_data_feature.sql
Normal file
4
migrations/cockroach/V1.60__user_meta_data_feature.sql
Normal file
@ -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;
|
@ -2612,6 +2612,7 @@ message SetDefaultFeaturesRequest {
|
|||||||
bool label_policy_watermark = 16;
|
bool label_policy_watermark = 16;
|
||||||
bool custom_text = 17;
|
bool custom_text = 17;
|
||||||
bool privacy_policy = 18;
|
bool privacy_policy = 18;
|
||||||
|
bool metadata_user = 19;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SetDefaultFeaturesResponse {
|
message SetDefaultFeaturesResponse {
|
||||||
@ -2647,6 +2648,7 @@ message SetOrgFeaturesRequest {
|
|||||||
bool label_policy_watermark = 17;
|
bool label_policy_watermark = 17;
|
||||||
bool custom_text = 18;
|
bool custom_text = 18;
|
||||||
bool privacy_policy = 19;
|
bool privacy_policy = 19;
|
||||||
|
bool metadata_user = 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SetOrgFeaturesResponse {
|
message SetOrgFeaturesResponse {
|
||||||
|
@ -26,6 +26,7 @@ message Features {
|
|||||||
bool label_policy_watermark = 15;
|
bool label_policy_watermark = 15;
|
||||||
bool custom_text = 16;
|
bool custom_text = 16;
|
||||||
bool privacy_policy = 17;
|
bool privacy_policy = 17;
|
||||||
|
bool metadata_user = 18;
|
||||||
}
|
}
|
||||||
|
|
||||||
message FeatureTier {
|
message FeatureTier {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user