fix: handle org features downgrades (#1578)

* features

* features

* features

* fix json tags

* add features handler to auth

* mocks for tests

* add setup step

* fixes

* add featurelist to auth api

* fx proto merge

* remove policies

* factors

* handle auth factors

* test org features

* cleanup
This commit is contained in:
Livio Amstutz 2021-04-12 17:03:09 +02:00 committed by GitHub
parent 0e1e7bb382
commit 75d4b33281
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 932 additions and 60 deletions

View File

@ -2,12 +2,17 @@ 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/repository/org"
)
func (c *Commands) SetOrgFeatures(ctx context.Context, resourceOwner string, features *domain.Features) (*domain.ObjectDetails, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Features-G5tg", "Errors.ResourceOwnerMissing")
}
existingFeatures := NewOrgFeaturesWriteModel(resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, existingFeatures)
if err != nil {
@ -33,7 +38,13 @@ func (c *Commands) SetOrgFeatures(ctx context.Context, resourceOwner string, fea
return nil, caos_errs.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged")
}
pushedEvents, err := c.eventstore.PushEvents(ctx, setEvent)
events, err := c.ensureOrgSettingsToFeatures(ctx, resourceOwner, features)
if err != nil {
return nil, err
}
events = append(events, setEvent)
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
if err != nil {
return nil, err
}
@ -45,6 +56,9 @@ func (c *Commands) SetOrgFeatures(ctx context.Context, resourceOwner string, fea
}
func (c *Commands) RemoveOrgFeatures(ctx context.Context, orgID string) (*domain.ObjectDetails, error) {
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Features-G5tg", "Errors.ResourceOwnerMissing")
}
existingFeatures := NewOrgFeaturesWriteModel(orgID)
err := c.eventstore.FilterToQueryReducer(ctx, existingFeatures)
if err != nil {
@ -53,9 +67,19 @@ func (c *Commands) RemoveOrgFeatures(ctx context.Context, orgID string) (*domain
if existingFeatures.State == domain.FeaturesStateUnspecified || existingFeatures.State == domain.FeaturesStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "Features-Bg32G", "Errors.Features.NotFound")
}
removedEvent := org.NewFeaturesRemovedEvent(ctx, OrgAggregateFromWriteModel(&existingFeatures.FeaturesWriteModel.WriteModel))
orgAgg := OrgAggregateFromWriteModel(&existingFeatures.FeaturesWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewFeaturesRemovedEvent(ctx, orgAgg))
features, err := c.getDefaultFeatures(ctx)
if err != nil {
return nil, err
}
events, err := c.ensureOrgSettingsToFeatures(ctx, orgID, features)
if err != nil {
return nil, err
}
events = append(events, removedEvent)
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
if err != nil {
return nil, err
}
@ -65,3 +89,130 @@ func (c *Commands) RemoveOrgFeatures(ctx context.Context, orgID string) (*domain
}
return writeModelToObjectDetails(&existingFeatures.WriteModel), nil
}
func (c *Commands) ensureOrgSettingsToFeatures(ctx context.Context, orgID string, features *domain.Features) ([]eventstore.EventPusher, error) {
events, err := c.setAllowedLoginPolicy(ctx, orgID, features)
if err != nil {
return nil, err
}
if !features.PasswordComplexityPolicy {
removePasswordComplexityEvent, err := c.removePasswordComplexityPolicyIfExists(ctx, orgID)
if err != nil {
return nil, err
}
if removePasswordComplexityEvent != nil {
events = append(events, removePasswordComplexityEvent)
}
}
if !features.LabelPolicy {
removeLabelPolicyEvent, err := c.removeLabelPolicyIfExists(ctx, orgID)
if err != nil {
return nil, err
}
if removeLabelPolicyEvent != nil {
events = append(events, removeLabelPolicyEvent)
}
}
return events, nil
}
func (c *Commands) setAllowedLoginPolicy(ctx context.Context, orgID string, features *domain.Features) ([]eventstore.EventPusher, error) {
events := make([]eventstore.EventPusher, 0)
existingPolicy, err := c.orgLoginPolicyWriteModelByID(ctx, orgID)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, nil
}
defaultPolicy, err := c.getDefaultLoginPolicy(ctx)
if err != nil {
return nil, err
}
policy := *existingPolicy
if !features.LoginPolicyFactors {
if defaultPolicy.ForceMFA != existingPolicy.ForceMFA {
policy.ForceMFA = defaultPolicy.ForceMFA
}
authFactorsEvents, err := c.setDefaultAuthFactorsInCustomLoginPolicy(ctx, orgID)
if err != nil {
return nil, err
}
events = append(events, authFactorsEvents...)
}
if !features.LoginPolicyIDP {
if defaultPolicy.AllowExternalIDP != existingPolicy.AllowExternalIDP {
policy.AllowExternalIDP = defaultPolicy.AllowExternalIDP
}
//TODO: handle idps
}
if !features.LoginPolicyRegistration && defaultPolicy.AllowRegister != existingPolicy.AllowRegister {
policy.AllowRegister = defaultPolicy.AllowRegister
}
if !features.LoginPolicyPasswordless && defaultPolicy.PasswordlessType != existingPolicy.PasswordlessType {
policy.PasswordlessType = defaultPolicy.PasswordlessType
}
if !features.LoginPolicyUsernameLogin && defaultPolicy.AllowUsernamePassword != existingPolicy.AllowUserNamePassword {
policy.AllowUserNamePassword = defaultPolicy.AllowUsernamePassword
}
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, OrgAggregateFromWriteModel(&existingPolicy.WriteModel), policy.AllowUserNamePassword, policy.AllowRegister, policy.AllowExternalIDP, policy.ForceMFA, policy.PasswordlessType)
if hasChanged {
events = append(events, changedEvent)
}
return events, nil
}
func (c *Commands) setDefaultAuthFactorsInCustomLoginPolicy(ctx context.Context, orgID string) ([]eventstore.EventPusher, error) {
orgAuthFactors, err := c.orgLoginPolicyAuthFactorsWriteModel(ctx, orgID)
if err != nil {
return nil, err
}
events := make([]eventstore.EventPusher, 0)
for factor, state := range orgAuthFactors.SecondFactors {
if state.IAM == state.Org {
continue
}
secondFactorWriteModel := orgAuthFactors.ToSecondFactorWriteModel(factor)
if state.IAM == domain.FactorStateActive {
event, err := c.addSecondFactorToLoginPolicy(ctx, secondFactorWriteModel, factor)
if err != nil {
return nil, err
}
if event != nil {
events = append(events, event)
}
continue
}
event, err := c.removeSecondFactorFromLoginPolicy(ctx, secondFactorWriteModel, factor)
if err != nil {
return nil, err
}
if event != nil {
events = append(events, event)
}
}
for factor, state := range orgAuthFactors.MultiFactors {
if state.IAM == state.Org {
continue
}
multiFactorWriteModel := orgAuthFactors.ToMultiFactorWriteModel(factor)
if state.IAM == domain.FactorStateActive {
event, err := c.addMultiFactorToLoginPolicy(ctx, multiFactorWriteModel, factor)
if err != nil {
return nil, err
}
if event != nil {
events = append(events, event)
}
continue
}
event, err := c.removeMultiFactorFromLoginPolicy(ctx, multiFactorWriteModel, factor)
if err != nil {
return nil, err
}
if event != nil {
events = append(events, event)
}
}
return events, nil
}

View File

@ -0,0 +1,514 @@
package command
import (
"context"
"testing"
"time"
"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/repository/features"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/org"
)
func TestCommandSide_SetOrgFeatures(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
resourceOwner string
features *domain.Features
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "resourceowner missing, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
features: &domain.Features{
TierName: "Test",
State: domain.FeaturesStateActive,
AuditLogRetention: time.Hour,
LoginPolicyFactors: false,
LoginPolicyIDP: false,
LoginPolicyPasswordless: false,
LoginPolicyRegistration: false,
LoginPolicyUsernameLogin: false,
PasswordComplexityPolicy: false,
LabelPolicy: false,
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "no change, error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
newFeaturesSetEvent(context.Background(), "org1", "Test", domain.FeaturesStateActive, time.Hour),
),
),
),
},
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,
PasswordComplexityPolicy: false,
LabelPolicy: false,
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "set with default policies, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectFilter(
eventFromEventPusher(
iam.NewLoginPolicyAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
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",
false,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newFeaturesSetEvent(context.Background(), "org1", "Test", domain.FeaturesStateActive, time.Hour),
),
},
),
),
},
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,
PasswordComplexityPolicy: false,
LabelPolicy: false,
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
name: "set with custom policies, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
//NewOrgFeaturesWriteModel
expectFilter(),
//begin ensureOrgSettingsToFeatures
//begin setAllowedLoginPolicy
//orgLoginPolicyWriteModelByID
expectFilter(
eventFromEventPusher(
iam.NewLoginPolicyAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
),
),
eventFromEventPusher(
org.NewLoginPolicyAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
),
),
),
//getDefaultLoginPolicy
expectFilter(
eventFromEventPusher(
iam.NewLoginPolicyAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
),
),
),
//begin setDefaultAuthFactorsInCustomLoginPolicy
//orgLoginPolicyAuthFactorsWriteModel
expectFilter(
eventFromEventPusher(
iam.NewLoginPolicySecondFactorAddedEvent(context.Background(), &iam.NewAggregate().Aggregate, domain.SecondFactorTypeU2F),
),
eventFromEventPusher(
iam.NewLoginPolicyMultiFactorAddedEvent(context.Background(), &iam.NewAggregate().Aggregate, domain.MultiFactorTypeU2FWithPIN),
),
eventFromEventPusher(
org.NewLoginPolicySecondFactorAddedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, domain.SecondFactorTypeOTP),
),
),
//addSecondFactorToLoginPolicy
expectFilter(),
//removeSecondFactorFromLoginPolicy
expectFilter(),
//addMultiFactorToLoginPolicy
expectFilter(),
//end setDefaultAuthFactorsInCustomLoginPolicy
//orgPasswordComplexityPolicyWriteModelByID
expectFilter(
eventFromEventPusher(
iam.NewPasswordComplexityPolicyAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
8,
false,
false,
false,
false,
),
),
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
7,
false,
false,
false,
false,
),
),
),
//orgLabelPolicyWriteModelByID
expectFilter(
eventFromEventPusher(
iam.NewLabelPolicyAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
"primary",
"secondary",
false,
),
),
eventFromEventPusher(
org.NewLabelPolicyAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
"custom",
"secondary",
false,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewLoginPolicySecondFactorAddedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, domain.SecondFactorTypeU2F),
),
eventFromEventPusher(
org.NewLoginPolicySecondFactorRemovedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, domain.SecondFactorTypeOTP),
),
eventFromEventPusher(
org.NewLoginPolicyMultiFactorAddedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, domain.MultiFactorTypeU2FWithPIN),
),
eventFromEventPusher(
newLoginPolicyChangedEvent(context.Background(), "org1", true, true, true, true, domain.PasswordlessTypeAllowed),
),
eventFromEventPusher(
org.NewPasswordComplexityPolicyRemovedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate),
),
eventFromEventPusher(
org.NewLabelPolicyRemovedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate),
),
eventFromEventPusher(
newFeaturesSetEvent(context.Background(), "org1", "Test", domain.FeaturesStateActive, time.Hour),
),
},
),
),
},
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,
PasswordComplexityPolicy: false,
LabelPolicy: false,
},
},
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.SetOrgFeatures(tt.args.ctx, tt.args.resourceOwner, tt.args.features)
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_RemoveOrgFeatures(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
resourceOwner string
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "resourceowner missing, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "no features set, error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "remove with default policies, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
newFeaturesSetEvent(context.Background(), "org1", "Test", domain.FeaturesStateActive, time.Hour)),
),
expectFilter(
eventFromEventPusher(
newIAMFeaturesSetEvent(context.Background(), "Default", domain.FeaturesStateActive, time.Hour)),
),
expectFilter(
eventFromEventPusher(
iam.NewLoginPolicyAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
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",
false,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewFeaturesRemovedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate),
),
},
),
),
},
args: args{
ctx: context.Background(),
resourceOwner: "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.RemoveOrgFeatures(tt.args.ctx, tt.args.resourceOwner)
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 newIAMFeaturesSetEvent(ctx context.Context, tierName string, state domain.FeaturesState, auditLog time.Duration) *iam.FeaturesSetEvent {
event, _ := iam.NewFeaturesSetEvent(
ctx,
&iam.NewAggregate().Aggregate,
[]features.FeaturesChanges{
features.ChangeTierName(tierName),
features.ChangeState(state),
features.ChangeAuditLogRetention(auditLog),
},
)
return event
}
func newFeaturesSetEvent(ctx context.Context, orgID string, tierName string, state domain.FeaturesState, auditLog time.Duration) *org.FeaturesSetEvent {
event, _ := org.NewFeaturesSetEvent(
ctx,
&org.NewAggregate(orgID, orgID).Aggregate,
[]features.FeaturesChanges{
features.ChangeTierName(tierName),
features.ChangeState(state),
features.ChangeAuditLogRetention(auditLog),
},
)
return event
}

View File

@ -142,22 +142,9 @@ func (c *Commands) RemoveIDPConfig(ctx context.Context, idpID, orgID string, cas
if err != nil {
return nil, err
}
if existingIDP.State == domain.IDPConfigStateRemoved || existingIDP.State == domain.IDPConfigStateUnspecified {
return nil, caos_errs.ThrowNotFound(nil, "Org-Yx9vd", "Errors.Org.IDPConfig.NotExisting")
}
if existingIDP.State != domain.IDPConfigStateInactive {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-5Mo0d", "Errors.Org.IDPConfig.NotInactive")
}
orgAgg := OrgAggregateFromWriteModel(&existingIDP.WriteModel)
events := []eventstore.EventPusher{
org_repo.NewIDPConfigRemovedEvent(ctx, orgAgg, idpID, existingIDP.Name),
}
if cascadeRemoveProvider {
removeIDPEvents := c.removeIDPProviderFromLoginPolicy(ctx, orgAgg, idpID, true, cascadeExternalIDPs...)
events = append(events, removeIDPEvents...)
events, err := c.removeIDPConfig(ctx, existingIDP, cascadeRemoveProvider, cascadeExternalIDPs...)
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
if err != nil {
@ -170,6 +157,26 @@ func (c *Commands) RemoveIDPConfig(ctx context.Context, idpID, orgID string, cas
return writeModelToObjectDetails(&existingIDP.IDPConfigWriteModel.WriteModel), nil
}
func (c *Commands) removeIDPConfig(ctx context.Context, existingIDP *OrgIDPConfigWriteModel, cascadeRemoveProvider bool, cascadeExternalIDPs ...*domain.ExternalIDP) ([]eventstore.EventPusher, error) {
if existingIDP.State == domain.IDPConfigStateRemoved || existingIDP.State == domain.IDPConfigStateUnspecified {
return nil, caos_errs.ThrowNotFound(nil, "Org-Yx9vd", "Errors.Org.IDPConfig.NotExisting")
}
if existingIDP.State != domain.IDPConfigStateInactive {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-5Mo0d", "Errors.Org.IDPConfig.NotInactive")
}
orgAgg := OrgAggregateFromWriteModel(&existingIDP.WriteModel)
events := []eventstore.EventPusher{
org_repo.NewIDPConfigRemovedEvent(ctx, orgAgg, existingIDP.AggregateID, existingIDP.Name),
}
if cascadeRemoveProvider {
removeIDPEvents := c.removeIDPProviderFromLoginPolicy(ctx, orgAgg, existingIDP.AggregateID, true, cascadeExternalIDPs...)
events = append(events, removeIDPEvents...)
}
return events, nil
}
func (c *Commands) getOrgIDPConfigByID(ctx context.Context, idpID, orgID string) (*domain.IDPConfig, error) {
config, err := c.orgIDPConfigWriteModelByID(ctx, idpID, orgID)
if err != nil {

View File

@ -74,15 +74,11 @@ func (c *Commands) RemoveLabelPolicy(ctx context.Context, orgID string) (*domain
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-Mf9sf", "Errors.ResourceOwnerMissing")
}
existingPolicy := NewOrgLabelPolicyWriteModel(orgID)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
removeEvent, err := c.removeLabelPolicy(ctx, existingPolicy)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "Org-3M9df", "Errors.Org.LabelPolicy.NotFound")
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewLabelPolicyRemovedEvent(ctx, orgAgg))
pushedEvents, err := c.eventstore.PushEvents(ctx, removeEvent)
if err != nil {
return nil, err
}
@ -92,3 +88,36 @@ func (c *Commands) RemoveLabelPolicy(ctx context.Context, orgID string) (*domain
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) removeLabelPolicy(ctx context.Context, existingPolicy *OrgLabelPolicyWriteModel) (*org.LabelPolicyRemovedEvent, 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-3M9df", "Errors.Org.LabelPolicy.NotFound")
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.WriteModel)
return org.NewLabelPolicyRemovedEvent(ctx, orgAgg), nil
}
func (c *Commands) removeLabelPolicyIfExists(ctx context.Context, orgID string) (*org.LabelPolicyRemovedEvent, error) {
existingPolicy, err := c.orgLabelPolicyWriteModelByID(ctx, orgID)
if err != nil {
return nil, err
}
if existingPolicy.State != domain.PolicyStateActive {
return nil, nil
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.WriteModel)
return org.NewLabelPolicyRemovedEvent(ctx, orgAgg), nil
}
func (c *Commands) orgLabelPolicyWriteModelByID(ctx context.Context, orgID string) (*OrgLabelPolicyWriteModel, error) {
policy := NewOrgLabelPolicyWriteModel(orgID)
err := c.eventstore.FilterToQueryReducer(ctx, policy)
if err != nil {
return nil, err
}
return policy, nil
}

View File

@ -11,6 +11,7 @@ import (
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/telemetry/tracing"
)
func (c *Commands) AddLoginPolicy(ctx context.Context, resourceOwner string, policy *domain.LoginPolicy) (*domain.LoginPolicy, error) {
@ -238,18 +239,12 @@ func (c *Commands) AddSecondFactorToLoginPolicy(ctx context.Context, secondFacto
return domain.SecondFactorTypeUnspecified, nil, caos_errs.ThrowInvalidArgument(nil, "Org-5m9fs", "Errors.Org.LoginPolicy.MFA.Unspecified")
}
secondFactorModel := NewOrgSecondFactorWriteModel(orgID, secondFactor)
err := c.eventstore.FilterToQueryReducer(ctx, secondFactorModel)
addedEvent, err := c.addSecondFactorToLoginPolicy(ctx, secondFactorModel, secondFactor)
if err != nil {
return domain.SecondFactorTypeUnspecified, nil, err
}
if secondFactorModel.State == domain.FactorStateActive {
return domain.SecondFactorTypeUnspecified, nil, caos_errs.ThrowAlreadyExists(nil, "Org-2B0ps", "Errors.Org.LoginPolicy.MFA.AlreadyExists")
}
orgAgg := OrgAggregateFromWriteModel(&secondFactorModel.SecondFactorWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewLoginPolicySecondFactorAddedEvent(ctx, orgAgg, secondFactor))
pushedEvents, err := c.eventstore.PushEvents(ctx, addedEvent)
if err != nil {
return domain.SecondFactorTypeUnspecified, nil, err
}
@ -261,6 +256,20 @@ func (c *Commands) AddSecondFactorToLoginPolicy(ctx context.Context, secondFacto
return secondFactorModel.MFAType, writeModelToObjectDetails(&secondFactorModel.WriteModel), nil
}
func (c *Commands) addSecondFactorToLoginPolicy(ctx context.Context, secondFactorModel *OrgSecondFactorWriteModel, secondFactor domain.SecondFactorType) (*org.LoginPolicySecondFactorAddedEvent, error) {
err := c.eventstore.FilterToQueryReducer(ctx, secondFactorModel)
if err != nil {
return nil, err
}
if secondFactorModel.State == domain.FactorStateActive {
return nil, caos_errs.ThrowAlreadyExists(nil, "Org-2B0ps", "Errors.Org.LoginPolicy.MFA.AlreadyExists")
}
orgAgg := OrgAggregateFromWriteModel(&secondFactorModel.SecondFactorWriteModel.WriteModel)
return org.NewLoginPolicySecondFactorAddedEvent(ctx, orgAgg, secondFactor), nil
}
func (c *Commands) RemoveSecondFactorFromLoginPolicy(ctx context.Context, secondFactor domain.SecondFactorType, orgID string) (*domain.ObjectDetails, error) {
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-fM0gs", "Errors.ResourceOwnerMissing")
@ -269,16 +278,12 @@ func (c *Commands) RemoveSecondFactorFromLoginPolicy(ctx context.Context, second
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-55n8s", "Errors.Org.LoginPolicy.MFA.Unspecified")
}
secondFactorModel := NewOrgSecondFactorWriteModel(orgID, secondFactor)
err := c.eventstore.FilterToQueryReducer(ctx, secondFactorModel)
removedEvent, err := c.removeSecondFactorFromLoginPolicy(ctx, secondFactorModel, secondFactor)
if err != nil {
return nil, err
}
if secondFactorModel.State == domain.FactorStateUnspecified || secondFactorModel.State == domain.FactorStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "Org-3M9od", "Errors.Org.LoginPolicy.MFA.NotExisting")
}
orgAgg := OrgAggregateFromWriteModel(&secondFactorModel.SecondFactorWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewLoginPolicySecondFactorRemovedEvent(ctx, orgAgg, secondFactor))
pushedEvents, err := c.eventstore.PushEvents(ctx, removedEvent)
if err != nil {
return nil, err
}
@ -289,6 +294,18 @@ func (c *Commands) RemoveSecondFactorFromLoginPolicy(ctx context.Context, second
return writeModelToObjectDetails(&secondFactorModel.WriteModel), nil
}
func (c *Commands) removeSecondFactorFromLoginPolicy(ctx context.Context, secondFactorModel *OrgSecondFactorWriteModel, secondFactor domain.SecondFactorType) (*org.LoginPolicySecondFactorRemovedEvent, error) {
err := c.eventstore.FilterToQueryReducer(ctx, secondFactorModel)
if err != nil {
return nil, err
}
if secondFactorModel.State == domain.FactorStateUnspecified || secondFactorModel.State == domain.FactorStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "Org-3M9od", "Errors.Org.LoginPolicy.MFA.NotExisting")
}
orgAgg := OrgAggregateFromWriteModel(&secondFactorModel.SecondFactorWriteModel.WriteModel)
return org.NewLoginPolicySecondFactorRemovedEvent(ctx, orgAgg, secondFactor), nil
}
func (c *Commands) AddMultiFactorToLoginPolicy(ctx context.Context, multiFactor domain.MultiFactorType, orgID string) (domain.MultiFactorType, *domain.ObjectDetails, error) {
if orgID == "" {
return domain.MultiFactorTypeUnspecified, nil, caos_errs.ThrowInvalidArgument(nil, "Org-M0fsf", "Errors.ResourceOwnerMissing")
@ -297,17 +314,12 @@ func (c *Commands) AddMultiFactorToLoginPolicy(ctx context.Context, multiFactor
return domain.MultiFactorTypeUnspecified, nil, caos_errs.ThrowInvalidArgument(nil, "Org-5m9fs", "Errors.Org.LoginPolicy.MFA.Unspecified")
}
multiFactorModel := NewOrgMultiFactorWriteModel(orgID, multiFactor)
err := c.eventstore.FilterToQueryReducer(ctx, multiFactorModel)
addedEvent, err := c.addMultiFactorToLoginPolicy(ctx, multiFactorModel, multiFactor)
if err != nil {
return domain.MultiFactorTypeUnspecified, nil, err
}
if multiFactorModel.State == domain.FactorStateActive {
return domain.MultiFactorTypeUnspecified, nil, caos_errs.ThrowAlreadyExists(nil, "Org-3M9od", "Errors.Org.LoginPolicy.MFA.AlreadyExists")
}
orgAgg := OrgAggregateFromWriteModel(&multiFactorModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewLoginPolicyMultiFactorAddedEvent(ctx, orgAgg, multiFactor))
pushedEvents, err := c.eventstore.PushEvents(ctx, addedEvent)
if err != nil {
return domain.MultiFactorTypeUnspecified, nil, err
}
@ -318,6 +330,19 @@ func (c *Commands) AddMultiFactorToLoginPolicy(ctx context.Context, multiFactor
return multiFactorModel.MultiFactorWriteModel.MFAType, writeModelToObjectDetails(&multiFactorModel.WriteModel), nil
}
func (c *Commands) addMultiFactorToLoginPolicy(ctx context.Context, multiFactorModel *OrgMultiFactorWriteModel, multiFactor domain.MultiFactorType) (*org.LoginPolicyMultiFactorAddedEvent, error) {
err := c.eventstore.FilterToQueryReducer(ctx, multiFactorModel)
if err != nil {
return nil, err
}
if multiFactorModel.State == domain.FactorStateActive {
return nil, caos_errs.ThrowAlreadyExists(nil, "Org-3M9od", "Errors.Org.LoginPolicy.MFA.AlreadyExists")
}
orgAgg := OrgAggregateFromWriteModel(&multiFactorModel.WriteModel)
return org.NewLoginPolicyMultiFactorAddedEvent(ctx, orgAgg, multiFactor), nil
}
func (c *Commands) RemoveMultiFactorFromLoginPolicy(ctx context.Context, multiFactor domain.MultiFactorType, orgID string) (*domain.ObjectDetails, error) {
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-M0fsf", "Errors.ResourceOwnerMissing")
@ -326,16 +351,12 @@ func (c *Commands) RemoveMultiFactorFromLoginPolicy(ctx context.Context, multiFa
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-5m9fs", "Errors.Org.LoginPolicy.MFA.Unspecified")
}
multiFactorModel := NewOrgMultiFactorWriteModel(orgID, multiFactor)
err := c.eventstore.FilterToQueryReducer(ctx, multiFactorModel)
removedEvent, err := c.removeMultiFactorFromLoginPolicy(ctx, multiFactorModel, multiFactor)
if err != nil {
return nil, err
}
if multiFactorModel.State == domain.FactorStateUnspecified || multiFactorModel.State == domain.FactorStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "Org-3M9df", "Errors.Org.LoginPolicy.MFA.NotExisting")
}
orgAgg := OrgAggregateFromWriteModel(&multiFactorModel.MultiFactorWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewLoginPolicyMultiFactorRemovedEvent(ctx, orgAgg, multiFactor))
pushedEvents, err := c.eventstore.PushEvents(ctx, removedEvent)
if err != nil {
return nil, err
}
@ -345,3 +366,28 @@ func (c *Commands) RemoveMultiFactorFromLoginPolicy(ctx context.Context, multiFa
}
return writeModelToObjectDetails(&multiFactorModel.WriteModel), nil
}
func (c *Commands) removeMultiFactorFromLoginPolicy(ctx context.Context, multiFactorModel *OrgMultiFactorWriteModel, multiFactor domain.MultiFactorType) (*org.LoginPolicyMultiFactorRemovedEvent, error) {
err := c.eventstore.FilterToQueryReducer(ctx, multiFactorModel)
if err != nil {
return nil, err
}
if multiFactorModel.State == domain.FactorStateUnspecified || multiFactorModel.State == domain.FactorStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "Org-3M9df", "Errors.Org.LoginPolicy.MFA.NotExisting")
}
orgAgg := OrgAggregateFromWriteModel(&multiFactorModel.MultiFactorWriteModel.WriteModel)
return org.NewLoginPolicyMultiFactorRemovedEvent(ctx, orgAgg, multiFactor), nil
}
func (c *Commands) orgLoginPolicyAuthFactorsWriteModel(ctx context.Context, orgID string) (_ *OrgAuthFactorsAllowedWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
writeModel := NewOrgAuthFactorsAllowedWriteModel(orgID)
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return writeModel, nil
}

View File

@ -3,6 +3,7 @@ package command
import (
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/org"
)
@ -93,3 +94,99 @@ func (wm *OrgMultiFactorWriteModel) Query() *eventstore.SearchQueryBuilder {
org.LoginPolicyMultiFactorAddedEventType,
org.LoginPolicyMultiFactorRemovedEventType)
}
func NewOrgAuthFactorsAllowedWriteModel(orgID string) *OrgAuthFactorsAllowedWriteModel {
return &OrgAuthFactorsAllowedWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: orgID,
ResourceOwner: orgID,
},
SecondFactors: map[domain.SecondFactorType]*factorState{},
MultiFactors: map[domain.MultiFactorType]*factorState{},
}
}
type OrgAuthFactorsAllowedWriteModel struct {
eventstore.WriteModel
SecondFactors map[domain.SecondFactorType]*factorState
MultiFactors map[domain.MultiFactorType]*factorState
}
type factorState struct {
IAM domain.FactorState
Org domain.FactorState
}
func (wm *OrgAuthFactorsAllowedWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *iam.LoginPolicySecondFactorAddedEvent:
wm.ensureSecondFactor(e.MFAType)
wm.SecondFactors[e.MFAType].IAM = domain.FactorStateActive
case *iam.LoginPolicySecondFactorRemovedEvent:
wm.ensureSecondFactor(e.MFAType)
wm.SecondFactors[e.MFAType].IAM = domain.FactorStateRemoved
case *org.LoginPolicySecondFactorAddedEvent:
wm.ensureSecondFactor(e.MFAType)
wm.SecondFactors[e.MFAType].Org = domain.FactorStateActive
case *org.LoginPolicySecondFactorRemovedEvent:
wm.ensureSecondFactor(e.MFAType)
wm.SecondFactors[e.MFAType].Org = domain.FactorStateRemoved
case *iam.LoginPolicyMultiFactorAddedEvent:
wm.ensureMultiFactor(e.MFAType)
wm.MultiFactors[e.MFAType].IAM = domain.FactorStateActive
case *iam.LoginPolicyMultiFactorRemovedEvent:
wm.ensureMultiFactor(e.MFAType)
wm.MultiFactors[e.MFAType].IAM = domain.FactorStateRemoved
case *org.LoginPolicyMultiFactorAddedEvent:
wm.ensureMultiFactor(e.MFAType)
wm.MultiFactors[e.MFAType].Org = domain.FactorStateActive
case *org.LoginPolicyMultiFactorRemovedEvent:
wm.ensureMultiFactor(e.MFAType)
wm.MultiFactors[e.MFAType].Org = domain.FactorStateRemoved
}
}
return wm.WriteModel.Reduce()
}
func (wm *OrgAuthFactorsAllowedWriteModel) ensureSecondFactor(secondFactor domain.SecondFactorType) {
_, ok := wm.SecondFactors[secondFactor]
if !ok {
wm.SecondFactors[secondFactor] = &factorState{}
}
}
func (wm *OrgAuthFactorsAllowedWriteModel) ensureMultiFactor(multiFactor domain.MultiFactorType) {
_, ok := wm.MultiFactors[multiFactor]
if !ok {
wm.MultiFactors[multiFactor] = &factorState{}
}
}
func (wm *OrgAuthFactorsAllowedWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, iam.AggregateType, org.AggregateType).
AggregateIDs(domain.IAMID, wm.WriteModel.AggregateID).
EventTypes(
iam.LoginPolicySecondFactorAddedEventType,
iam.LoginPolicySecondFactorRemovedEventType,
iam.LoginPolicyMultiFactorAddedEventType,
iam.LoginPolicyMultiFactorRemovedEventType,
org.LoginPolicySecondFactorAddedEventType,
org.LoginPolicySecondFactorRemovedEventType,
org.LoginPolicyMultiFactorAddedEventType,
org.LoginPolicyMultiFactorRemovedEventType)
}
func (wm *OrgAuthFactorsAllowedWriteModel) ToSecondFactorWriteModel(factor domain.SecondFactorType) *OrgSecondFactorWriteModel {
orgSecondFactorWriteModel := NewOrgSecondFactorWriteModel(wm.AggregateID, factor)
orgSecondFactorWriteModel.ProcessedSequence = wm.ProcessedSequence
orgSecondFactorWriteModel.State = wm.SecondFactors[factor].Org
return orgSecondFactorWriteModel
}
func (wm *OrgAuthFactorsAllowedWriteModel) ToMultiFactorWriteModel(factor domain.MultiFactorType) *OrgMultiFactorWriteModel {
orgMultiFactorWriteModel := NewOrgMultiFactorWriteModel(wm.AggregateID, factor)
orgMultiFactorWriteModel.ProcessedSequence = wm.ProcessedSequence
orgMultiFactorWriteModel.State = wm.MultiFactors[factor].Org
return orgMultiFactorWriteModel
}

View File

@ -9,8 +9,7 @@ import (
)
func (c *Commands) getOrgPasswordComplexityPolicy(ctx context.Context, orgID string) (*domain.PasswordComplexityPolicy, error) {
policy := NewOrgPasswordComplexityPolicyWriteModel(orgID)
err := c.eventstore.FilterToQueryReducer(ctx, policy)
policy, err := c.orgPasswordComplexityPolicyWriteModelByID(ctx, orgID)
if err != nil {
return nil, err
}
@ -20,6 +19,15 @@ func (c *Commands) getOrgPasswordComplexityPolicy(ctx context.Context, orgID str
return c.getDefaultPasswordComplexityPolicy(ctx)
}
func (c *Commands) orgPasswordComplexityPolicyWriteModelByID(ctx context.Context, orgID string) (*OrgPasswordComplexityPolicyWriteModel, error) {
policy := NewOrgPasswordComplexityPolicyWriteModel(orgID)
err := c.eventstore.FilterToQueryReducer(ctx, policy)
if err != nil {
return nil, err
}
return policy, nil
}
func (c *Commands) AddPasswordComplexityPolicy(ctx context.Context, resourceOwner string, policy *domain.PasswordComplexityPolicy) (*domain.PasswordComplexityPolicy, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-7ufEs", "Errors.ResourceOwnerMissing")
@ -96,15 +104,11 @@ func (c *Commands) RemovePasswordComplexityPolicy(ctx context.Context, orgID str
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-J8fsf", "Errors.ResourceOwnerMissing")
}
existingPolicy := NewOrgPasswordComplexityPolicyWriteModel(orgID)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
event, err := c.removePasswordComplexityPolicy(ctx, existingPolicy)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "ORG-ADgs2", "Errors.Org.PasswordComplexityPolicy.NotFound")
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewPasswordComplexityPolicyRemovedEvent(ctx, orgAgg))
pushedEvents, err := c.eventstore.PushEvents(ctx, event)
if err != nil {
return nil, err
}
@ -114,3 +118,27 @@ func (c *Commands) RemovePasswordComplexityPolicy(ctx context.Context, orgID str
}
return writeModelToObjectDetails(&existingPolicy.PasswordComplexityPolicyWriteModel.WriteModel), nil
}
func (c *Commands) removePasswordComplexityPolicy(ctx context.Context, existingPolicy *OrgPasswordComplexityPolicyWriteModel) (*org.PasswordComplexityPolicyRemovedEvent, 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-ADgs2", "Errors.Org.PasswordComplexityPolicy.NotFound")
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.WriteModel)
return org.NewPasswordComplexityPolicyRemovedEvent(ctx, orgAgg), nil
}
func (c *Commands) removePasswordComplexityPolicyIfExists(ctx context.Context, orgID string) (*org.PasswordComplexityPolicyRemovedEvent, error) {
existingPolicy, err := c.orgPasswordComplexityPolicyWriteModelByID(ctx, orgID)
if err != nil {
return nil, err
}
if existingPolicy.State != domain.PolicyStateActive {
return nil, nil
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.WriteModel)
return org.NewPasswordComplexityPolicyRemovedEvent(ctx, orgAgg), nil
}