feat: Privacy policy (#1957)

* feat: command side privacy policy

* feat: add privacy policy to api

* feat: add privacy policy query side

* fix: add privacy policy to mgmt api

* fix: add privacy policy to auth and base data

* feat: use privacyPolicy in login gui

* feat: use privacyPolicy in login gui

* feat: test org fatures

* feat: typos

* feat: tos in register
This commit is contained in:
Fabi
2021-07-05 10:36:51 +02:00
committed by GitHub
parent 91f1c88d4e
commit beb1c1604a
75 changed files with 3171 additions and 34 deletions

View File

@@ -27,6 +27,7 @@ type FeaturesWriteModel struct {
LabelPolicyWatermark bool
CustomDomain bool
CustomText bool
PrivacyPolicy bool
}
func (wm *FeaturesWriteModel) Reduce() error {

View File

@@ -120,6 +120,14 @@ func writeModelToPasswordLockoutPolicy(wm *PasswordLockoutPolicyWriteModel) *dom
}
}
func writeModelToPrivacyPolicy(wm *PrivacyPolicyWriteModel) *domain.PrivacyPolicy {
return &domain.PrivacyPolicy{
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
TOSLink: wm.TOSLink,
PrivacyLink: wm.PrivacyLink,
}
}
func writeModelToIDPConfig(wm *IDPConfigWriteModel) *domain.IDPConfig {
return &domain.IDPConfig{
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),

View File

@@ -50,6 +50,7 @@ func (c *Commands) setDefaultFeatures(ctx context.Context, existingFeatures *IAM
features.LabelPolicyWatermark,
features.CustomDomain,
features.CustomText,
features.PrivacyPolicy,
)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged")

View File

@@ -65,7 +65,8 @@ func (wm *IAMFeaturesWriteModel) NewSetEvent(
labelPolicyPrivateLabel,
labelPolicyWatermark,
customDomain,
customText bool,
customText,
privacyPolicy bool,
) (*iam.FeaturesSetEvent, bool) {
changes := make([]features.FeaturesChanges, 0)
@@ -115,7 +116,9 @@ func (wm *IAMFeaturesWriteModel) NewSetEvent(
if wm.CustomText != customText {
changes = append(changes, features.ChangeCustomText(customText))
}
if wm.PrivacyPolicy != privacyPolicy {
changes = append(changes, features.ChangePrivacyPolicy(privacyPolicy))
}
if len(changes) == 0 {
return nil, false
}

View File

@@ -0,0 +1,92 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
iam_repo "github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/telemetry/tracing"
)
func (c *Commands) getDefaultPrivacyPolicy(ctx context.Context) (*domain.PrivacyPolicy, error) {
policyWriteModel := NewIAMPrivacyPolicyWriteModel()
err := c.eventstore.FilterToQueryReducer(ctx, policyWriteModel)
if err != nil {
return nil, err
}
if !policyWriteModel.State.Exists() {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-559os", "Errors.IAM.OrgIAMPolicy.NotFound")
}
policy := writeModelToPrivacyPolicy(&policyWriteModel.PrivacyPolicyWriteModel)
policy.Default = true
return policy, nil
}
func (c *Commands) AddDefaultPrivacyPolicy(ctx context.Context, policy *domain.PrivacyPolicy) (*domain.PrivacyPolicy, error) {
addedPolicy := NewIAMPrivacyPolicyWriteModel()
iamAgg := IAMAggregateFromWriteModel(&addedPolicy.WriteModel)
events, err := c.addDefaultPrivacyPolicy(ctx, iamAgg, addedPolicy, policy)
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.PushEvents(ctx, events)
if err != nil {
return nil, err
}
err = AppendAndReduce(addedPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToPrivacyPolicy(&addedPolicy.PrivacyPolicyWriteModel), nil
}
func (c *Commands) addDefaultPrivacyPolicy(ctx context.Context, iamAgg *eventstore.Aggregate, addedPolicy *IAMPrivacyPolicyWriteModel, policy *domain.PrivacyPolicy) (eventstore.EventPusher, error) {
err := c.eventstore.FilterToQueryReducer(ctx, addedPolicy)
if err != nil {
return nil, err
}
if addedPolicy.State == domain.PolicyStateActive {
return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-M00rJ", "Errors.IAM.PrivacyPolicy.AlreadyExists")
}
return iam_repo.NewPrivacyPolicyAddedEvent(ctx, iamAgg, policy.TOSLink, policy.PrivacyLink), nil
}
func (c *Commands) ChangeDefaultPrivacyPolicy(ctx context.Context, policy *domain.PrivacyPolicy) (*domain.PrivacyPolicy, error) {
existingPolicy, err := c.defaultPrivacyPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-0oPew", "Errors.IAM.PasswordAgePolicy.NotFound")
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.PrivacyPolicyWriteModel.WriteModel)
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, iamAgg, policy.TOSLink, policy.PrivacyLink)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-4M9vs", "Errors.IAM.LabelPolicy.NotChanged")
}
pushedEvents, err := c.eventstore.PushEvents(ctx, changedEvent)
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToPrivacyPolicy(&existingPolicy.PrivacyPolicyWriteModel), nil
}
func (c *Commands) defaultPrivacyPolicyWriteModelByID(ctx context.Context) (policy *IAMPrivacyPolicyWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
writeModel := NewIAMPrivacyPolicyWriteModel()
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return writeModel, nil
}

View File

@@ -0,0 +1,73 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/policy"
)
type IAMPrivacyPolicyWriteModel struct {
PrivacyPolicyWriteModel
}
func NewIAMPrivacyPolicyWriteModel() *IAMPrivacyPolicyWriteModel {
return &IAMPrivacyPolicyWriteModel{
PrivacyPolicyWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: domain.IAMID,
ResourceOwner: domain.IAMID,
},
},
}
}
func (wm *IAMPrivacyPolicyWriteModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events {
switch e := event.(type) {
case *iam.PrivacyPolicyAddedEvent:
wm.PrivacyPolicyWriteModel.AppendEvents(&e.PrivacyPolicyAddedEvent)
case *iam.PrivacyPolicyChangedEvent:
wm.PrivacyPolicyWriteModel.AppendEvents(&e.PrivacyPolicyChangedEvent)
}
}
}
func (wm *IAMPrivacyPolicyWriteModel) Reduce() error {
return wm.PrivacyPolicyWriteModel.Reduce()
}
func (wm *IAMPrivacyPolicyWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, iam.AggregateType).
AggregateIDs(wm.PrivacyPolicyWriteModel.AggregateID).
ResourceOwner(wm.ResourceOwner).
EventTypes(
iam.PrivacyPolicyAddedEventType,
iam.PrivacyPolicyChangedEventType)
}
func (wm *IAMPrivacyPolicyWriteModel) NewChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
tosLink,
privacyLink string,
) (*iam.PrivacyPolicyChangedEvent, bool) {
changes := make([]policy.PrivacyPolicyChanges, 0)
if wm.TOSLink != tosLink {
changes = append(changes, policy.ChangeTOSLink(tosLink))
}
if wm.PrivacyLink != privacyLink {
changes = append(changes, policy.ChangePrivacyLink(privacyLink))
}
if len(changes) == 0 {
return nil, false
}
changedEvent, err := iam.NewPrivacyPolicyChangedEvent(ctx, aggregate, changes)
if err != nil {
return nil, false
}
return changedEvent, true
}

View File

@@ -0,0 +1,292 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/policy"
"github.com/stretchr/testify/assert"
"testing"
)
func TestCommandSide_AddDefaultPrivacyPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
policy *domain.PrivacyPolicy
}
type res struct {
want *domain.PrivacyPolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "privacy policy already existing, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewPrivacyPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"TOSLink",
"PrivacyLink",
),
),
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.PrivacyPolicy{
TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink",
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add policy,ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
iam.NewPrivacyPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"TOSLink",
"PrivacyLink",
),
),
},
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.PrivacyPolicy{
TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink",
},
},
res: res{
want: &domain.PrivacyPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink",
},
},
},
{
name: "add empty policy,ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
iam.NewPrivacyPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"",
"",
),
),
},
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.PrivacyPolicy{
TOSLink: "",
PrivacyLink: "",
},
},
res: res{
want: &domain.PrivacyPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
TOSLink: "",
PrivacyLink: "",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddDefaultPrivacyPolicy(tt.args.ctx, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_ChangeDefaultPrivacyPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
policy *domain.PrivacyPolicy
}
type res struct {
want *domain.PrivacyPolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "privacy policy not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
policy: &domain.PrivacyPolicy{
TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink",
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewPrivacyPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"TOSLink",
"PrivacyLink",
),
),
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.PrivacyPolicy{
TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink",
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewPrivacyPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"TOSLink",
"PrivacyLink",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newDefaultPrivacyPolicyChangedEvent(context.Background(),
"TOSLinkChanged",
"PrivacyLinkChanged",
),
),
},
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.PrivacyPolicy{
TOSLink: "TOSLinkChanged",
PrivacyLink: "PrivacyLinkChanged",
},
},
res: res{
want: &domain.PrivacyPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
TOSLink: "TOSLinkChanged",
PrivacyLink: "PrivacyLinkChanged",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeDefaultPrivacyPolicy(tt.args.ctx, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func newDefaultPrivacyPolicyChangedEvent(ctx context.Context, tosLink, privacyLink string) *iam.PrivacyPolicyChangedEvent {
event, _ := iam.NewPrivacyPolicyChangedEvent(ctx,
&iam.NewAggregate().Aggregate,
[]policy.PrivacyPolicyChanges{
policy.ChangeTOSLink(tosLink),
policy.ChangePrivacyLink(privacyLink),
},
)
return event
}

View File

@@ -41,3 +41,11 @@ func orgDomainWriteModelToOrgDomain(wm *OrgDomainWriteModel) *domain.OrgDomain {
ValidationCode: wm.ValidationCode,
}
}
func orgWriteModelToPrivacyPolicy(wm *OrgPrivacyPolicyWriteModel) *domain.PrivacyPolicy {
return &domain.PrivacyPolicy{
ObjectRoot: writeModelToObjectRoot(wm.PrivacyPolicyWriteModel.WriteModel),
TOSLink: wm.TOSLink,
PrivacyLink: wm.PrivacyLink,
}
}

View File

@@ -41,6 +41,7 @@ func (c *Commands) SetOrgFeatures(ctx context.Context, resourceOwner string, fea
features.LabelPolicyWatermark,
features.CustomDomain,
features.CustomText,
features.PrivacyPolicy,
)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged")
@@ -136,6 +137,15 @@ func (c *Commands) ensureOrgSettingsToFeatures(ctx context.Context, orgID string
events = append(events, removeCustomTextEvents...)
}
}
if !features.PrivacyPolicy {
removePrivacyPolicyEvent, err := c.removePrivacyPolicyIfExists(ctx, orgID)
if err != nil {
return nil, err
}
if removePrivacyPolicyEvent != nil {
events = append(events, removePrivacyPolicyEvent)
}
}
return events, nil
}

View File

@@ -72,7 +72,8 @@ func (wm *OrgFeaturesWriteModel) NewSetEvent(
labelPolicyPrivateLabel,
labelPolicyWatermark,
customDomain,
customText bool,
customText,
privacyPolicy bool,
) (*org.FeaturesSetEvent, bool) {
changes := make([]features.FeaturesChanges, 0)
@@ -125,6 +126,9 @@ func (wm *OrgFeaturesWriteModel) NewSetEvent(
if wm.CustomText != customText {
changes = append(changes, features.ChangeCustomText(customText))
}
if wm.PrivacyPolicy != privacyPolicy {
changes = append(changes, features.ChangePrivacyPolicy(privacyPolicy))
}
if len(changes) == 0 {
return nil, false

View File

@@ -239,6 +239,16 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
),
),
),
expectFilter(
eventFromEventPusher(
iam.NewPrivacyPolicyAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
"toslink",
"privacylink",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
@@ -267,6 +277,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
LabelPolicyWatermark: false,
CustomDomain: false,
CustomText: false,
PrivacyPolicy: false,
},
},
res: res{
@@ -399,6 +410,16 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
),
),
),
expectFilter(
eventFromEventPusher(
iam.NewPrivacyPolicyAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
"toslink",
"privacylink",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
@@ -572,6 +593,16 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
),
),
),
expectFilter(
eventFromEventPusher(
iam.NewPrivacyPolicyAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
"toslink",
"privacylink",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
@@ -755,6 +786,16 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
),
),
),
expectFilter(
eventFromEventPusher(
iam.NewPrivacyPolicyAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
"toslink",
"privacylink",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
@@ -993,6 +1034,16 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
),
),
),
expectFilter(
eventFromEventPusher(
org.NewPrivacyPolicyAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
"toslink",
"privacylink",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
@@ -1016,6 +1067,9 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
eventFromEventPusher(
org.NewCustomTextTemplateRemovedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, domain.InitCodeMessageType, language.English),
),
eventFromEventPusher(
org.NewPrivacyPolicyRemovedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate),
),
eventFromEventPusher(
newFeaturesSetEvent(context.Background(), "org1", "Test", domain.FeaturesStateActive, time.Hour),
),
@@ -1221,6 +1275,16 @@ func TestCommandSide_RemoveOrgFeatures(t *testing.T) {
),
),
),
expectFilter(
eventFromEventPusher(
iam.NewPrivacyPolicyAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
"toslink",
"privacylink",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(

View File

@@ -0,0 +1,135 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/repository/org"
)
func (c *Commands) getOrgPrivacyPolicy(ctx context.Context, orgID string) (*domain.PrivacyPolicy, error) {
policy, err := c.orgPrivacyPolicyWriteModelByID(ctx, orgID)
if err != nil {
return nil, err
}
if policy.State == domain.PolicyStateActive {
return orgWriteModelToPrivacyPolicy(policy), nil
}
return c.getDefaultPrivacyPolicy(ctx)
}
func (c *Commands) orgPrivacyPolicyWriteModelByID(ctx context.Context, orgID string) (*OrgPrivacyPolicyWriteModel, error) {
policy := NewOrgPrivacyPolicyWriteModel(orgID)
err := c.eventstore.FilterToQueryReducer(ctx, policy)
if err != nil {
return nil, err
}
return policy, nil
}
func (c *Commands) AddPrivacyPolicy(ctx context.Context, resourceOwner string, policy *domain.PrivacyPolicy) (*domain.PrivacyPolicy, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-MMk9fs", "Errors.ResourceOwnerMissing")
}
addedPolicy := NewOrgPrivacyPolicyWriteModel(resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, addedPolicy)
if err != nil {
return nil, err
}
if addedPolicy.State == domain.PolicyStateActive {
return nil, caos_errs.ThrowAlreadyExists(nil, "Org-0oLpd", "Errors.Org.PrivacyPolicy.AlreadyExists")
}
orgAgg := OrgAggregateFromWriteModel(&addedPolicy.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(
ctx,
org.NewPrivacyPolicyAddedEvent(
ctx,
orgAgg,
policy.TOSLink,
policy.PrivacyLink))
if err != nil {
return nil, err
}
err = AppendAndReduce(addedPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToPrivacyPolicy(&addedPolicy.PrivacyPolicyWriteModel), nil
}
func (c *Commands) ChangePrivacyPolicy(ctx context.Context, resourceOwner string, policy *domain.PrivacyPolicy) (*domain.PrivacyPolicy, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-22N89f", "Errors.ResourceOwnerMissing")
}
existingPolicy := NewOrgPrivacyPolicyWriteModel(resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "ORG-Ng8sf", "Errors.Org.PrivacyPolicy.NotFound")
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.PrivacyPolicyWriteModel.WriteModel)
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, orgAgg, policy.TOSLink, policy.PrivacyLink)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-4N9fs", "Errors.Org.PrivacyPolicy.NotChanged")
}
pushedEvents, err := c.eventstore.PushEvents(ctx, changedEvent)
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToPrivacyPolicy(&existingPolicy.PrivacyPolicyWriteModel), nil
}
func (c *Commands) RemovePrivacyPolicy(ctx context.Context, orgID string) (*domain.ObjectDetails, error) {
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-Nf9sf", "Errors.ResourceOwnerMissing")
}
existingPolicy := NewOrgPrivacyPolicyWriteModel(orgID)
event, err := c.removePrivacyPolicy(ctx, existingPolicy)
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.PushEvents(ctx, event)
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.PrivacyPolicyWriteModel.WriteModel), nil
}
func (c *Commands) removePrivacyPolicy(ctx context.Context, existingPolicy *OrgPrivacyPolicyWriteModel) (*org.PrivacyPolicyRemovedEvent, error) {
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "ORG-Ze9gs", "Errors.Org.PrivacyPolicy.NotFound")
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.WriteModel)
return org.NewPrivacyPolicyRemovedEvent(ctx, orgAgg), nil
}
func (c *Commands) removePrivacyPolicyIfExists(ctx context.Context, orgID string) (*org.PrivacyPolicyRemovedEvent, error) {
existingPolicy, err := c.orgPrivacyPolicyWriteModelByID(ctx, orgID)
if err != nil {
return nil, err
}
if existingPolicy.State != domain.PolicyStateActive {
return nil, nil
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.WriteModel)
return org.NewPrivacyPolicyRemovedEvent(ctx, orgAgg), nil
}

View File

@@ -0,0 +1,74 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/repository/policy"
)
type OrgPrivacyPolicyWriteModel struct {
PrivacyPolicyWriteModel
}
func NewOrgPrivacyPolicyWriteModel(orgID string) *OrgPrivacyPolicyWriteModel {
return &OrgPrivacyPolicyWriteModel{
PrivacyPolicyWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: orgID,
ResourceOwner: orgID,
},
},
}
}
func (wm *OrgPrivacyPolicyWriteModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events {
switch e := event.(type) {
case *org.PrivacyPolicyAddedEvent:
wm.PrivacyPolicyWriteModel.AppendEvents(&e.PrivacyPolicyAddedEvent)
case *org.PrivacyPolicyChangedEvent:
wm.PrivacyPolicyWriteModel.AppendEvents(&e.PrivacyPolicyChangedEvent)
case *org.PrivacyPolicyRemovedEvent:
wm.PrivacyPolicyWriteModel.AppendEvents(&e.PrivacyPolicyRemovedEvent)
}
}
}
func (wm *OrgPrivacyPolicyWriteModel) Reduce() error {
return wm.PrivacyPolicyWriteModel.Reduce()
}
func (wm *OrgPrivacyPolicyWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, org.AggregateType).
AggregateIDs(wm.PrivacyPolicyWriteModel.AggregateID).
ResourceOwner(wm.ResourceOwner).
EventTypes(org.PrivacyPolicyAddedEventType,
org.PrivacyPolicyChangedEventType,
org.PrivacyPolicyRemovedEventType)
}
func (wm *OrgPrivacyPolicyWriteModel) NewChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
tosLink,
privacyLink string,
) (*org.PrivacyPolicyChangedEvent, bool) {
changes := make([]policy.PrivacyPolicyChanges, 0)
if wm.TOSLink != tosLink {
changes = append(changes, policy.ChangeTOSLink(tosLink))
}
if wm.PrivacyLink != privacyLink {
changes = append(changes, policy.ChangePrivacyLink(privacyLink))
}
if len(changes) == 0 {
return nil, false
}
changedEvent, err := org.NewPrivacyPolicyChangedEvent(ctx, aggregate, changes)
if err != nil {
return nil, false
}
return changedEvent, true
}

View File

@@ -0,0 +1,479 @@
package command
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/repository/policy"
)
func TestCommandSide_AddPrivacyPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
policy *domain.PrivacyPolicy
}
type res struct {
want *domain.PrivacyPolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.PrivacyPolicy{
TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink",
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "policy already existing, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewPrivacyPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"TOSLink",
"PrivacyLink",
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PrivacyPolicy{
TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink",
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add policy,ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewPrivacyPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"TOSLink",
"PrivacyLink",
),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PrivacyPolicy{
TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink",
},
},
res: res{
want: &domain.PrivacyPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
ResourceOwner: "org1",
},
TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink",
},
},
},
{
name: "add policy empty links, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewPrivacyPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"",
"",
),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PrivacyPolicy{
TOSLink: "",
PrivacyLink: "",
},
},
res: res{
want: &domain.PrivacyPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
ResourceOwner: "org1",
},
TOSLink: "",
PrivacyLink: "",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddPrivacyPolicy(tt.args.ctx, tt.args.orgID, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_ChangePrivacyPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
policy *domain.PrivacyPolicy
}
type res struct {
want *domain.PrivacyPolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.PrivacyPolicy{
TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink",
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "policy not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PrivacyPolicy{
TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink",
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewPrivacyPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"TOSLink",
"PrivacyLink",
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PrivacyPolicy{
TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink",
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewPrivacyPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"TOSLink",
"PrivacyLink",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newPrivacyPolicyChangedEvent(context.Background(), "org1", "TOSLinkChange", "PrivacyLinkChange"),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PrivacyPolicy{
TOSLink: "TOSLinkChange",
PrivacyLink: "PrivacyLinkChange",
},
},
res: res{
want: &domain.PrivacyPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
ResourceOwner: "org1",
},
TOSLink: "TOSLinkChange",
PrivacyLink: "PrivacyLinkChange",
},
},
},
{
name: "change to empty links, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewPrivacyPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"TOSLink",
"PrivacyLink",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newPrivacyPolicyChangedEvent(context.Background(), "org1", "", ""),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PrivacyPolicy{
TOSLink: "",
PrivacyLink: "",
},
},
res: res{
want: &domain.PrivacyPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
ResourceOwner: "org1",
},
TOSLink: "",
PrivacyLink: "",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangePrivacyPolicy(tt.args.ctx, tt.args.orgID, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_RemovePrivacyPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "policy not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "remove, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewPrivacyPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"TOSLink",
"PrivacyLink",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewPrivacyPolicyRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.RemovePrivacyPolicy(tt.args.ctx, tt.args.orgID)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func newPrivacyPolicyChangedEvent(ctx context.Context, orgID string, tosLink, privacyLink string) *org.PrivacyPolicyChangedEvent {
event, _ := org.NewPrivacyPolicyChangedEvent(ctx,
&org.NewAggregate(orgID, orgID).Aggregate,
[]policy.PrivacyPolicyChanges{
policy.ChangeTOSLink(tosLink),
policy.ChangePrivacyLink(privacyLink),
},
)
return event
}

View File

@@ -0,0 +1,36 @@
package command
import (
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/policy"
)
type PrivacyPolicyWriteModel struct {
eventstore.WriteModel
TOSLink string
PrivacyLink string
State domain.PolicyState
}
func (wm *PrivacyPolicyWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *policy.PrivacyPolicyAddedEvent:
wm.TOSLink = e.TOSLink
wm.PrivacyLink = e.PrivacyLink
wm.State = domain.PolicyStateActive
case *policy.PrivacyPolicyChangedEvent:
if e.PrivacyLink != nil {
wm.PrivacyLink = *e.PrivacyLink
}
if e.TOSLink != nil {
wm.TOSLink = *e.TOSLink
}
case *policy.PrivacyPolicyRemovedEvent:
wm.State = domain.PolicyStateRemoved
}
}
return wm.WriteModel.Reduce()
}

View File

@@ -0,0 +1,35 @@
package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
)
type Step17 struct {
PrivacyPolicy domain.PrivacyPolicy
}
func (s *Step17) Step() domain.Step {
return domain.Step17
}
func (s *Step17) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep17(ctx, s)
}
func (c *Commands) SetupStep17(ctx context.Context, step *Step17) error {
fn := func(iam *IAMWriteModel) ([]eventstore.EventPusher, error) {
iamAgg := IAMAggregateFromWriteModel(&iam.WriteModel)
addedPolicy := NewIAMPrivacyPolicyWriteModel()
events, err := c.addDefaultPrivacyPolicy(ctx, iamAgg, addedPolicy, &step.PrivacyPolicy)
if err != nil {
return nil, err
}
logging.Log("SETUP-N9sq2").Info("default privacy policy set up")
return []eventstore.EventPusher{events}, nil
}
return c.setup(ctx, step, fn)
}