mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:47:32 +00:00
feat: mfa policy (#913)
* feat: add mfa to login policy * feat: add mfa to login policy * feat: add mfa to login policy * feat: add mfa to login policy * feat: add mfa to login policy on org * feat: add mfa to login policy on org * feat: append events on policy views * feat: iam login policy mfa definition * feat: login policies on orgs * feat: configured mfas in login process * feat: configured mfas in login process * Update internal/ui/login/static/i18n/en.yaml Co-authored-by: Livio Amstutz <livio.a@gmail.com> * fix: rename software and hardware mfas * fix: pr requests * fix user mfa * fix: test * fix: oidc version * fix: oidc version * fix: proto gen Co-authored-by: Livio Amstutz <livio.a@gmail.com> Co-authored-by: Max Peintner <max@caos.ch>
This commit is contained in:
@@ -13,6 +13,9 @@ type LoginPolicy struct {
|
||||
AllowRegister bool
|
||||
AllowExternalIdp bool
|
||||
IDPProviders []*IDPProvider
|
||||
ForceMFA bool
|
||||
SecondFactors []SecondFactorType
|
||||
MultiFactors []MultiFactorType
|
||||
}
|
||||
|
||||
type IDPProvider struct {
|
||||
@@ -35,6 +38,21 @@ const (
|
||||
IDPProviderTypeOrg
|
||||
)
|
||||
|
||||
type SecondFactorType int32
|
||||
|
||||
const (
|
||||
SecondFactorTypeUnspecified SecondFactorType = iota
|
||||
SecondFactorTypeOTP
|
||||
SecondFactorTypeU2F
|
||||
)
|
||||
|
||||
type MultiFactorType int32
|
||||
|
||||
const (
|
||||
MultiFactorTypeUnspecified MultiFactorType = iota
|
||||
MultiFactorTypeU2FWithPIN
|
||||
)
|
||||
|
||||
func (p *LoginPolicy) IsValid() bool {
|
||||
return p.ObjectRoot.AggregateID != ""
|
||||
}
|
||||
@@ -51,3 +69,21 @@ func (p *LoginPolicy) GetIdpProvider(id string) (int, *IDPProvider) {
|
||||
}
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
func (p *LoginPolicy) GetSecondFactor(mfaType SecondFactorType) (int, SecondFactorType) {
|
||||
for i, m := range p.SecondFactors {
|
||||
if m == mfaType {
|
||||
return i, m
|
||||
}
|
||||
}
|
||||
return -1, 0
|
||||
}
|
||||
|
||||
func (p *LoginPolicy) GetMultiFactor(mfaType MultiFactorType) (int, MultiFactorType) {
|
||||
for i, m := range p.MultiFactors {
|
||||
if m == mfaType {
|
||||
return i, m
|
||||
}
|
||||
}
|
||||
return -1, 0
|
||||
}
|
||||
|
@@ -10,6 +10,9 @@ type LoginPolicyView struct {
|
||||
AllowUsernamePassword bool
|
||||
AllowRegister bool
|
||||
AllowExternalIDP bool
|
||||
ForceMFA bool
|
||||
SecondFactors []SecondFactorType
|
||||
MultiFactors []MultiFactorType
|
||||
Default bool
|
||||
|
||||
CreationDate time.Time
|
||||
@@ -46,3 +49,17 @@ type LoginPolicySearchResponse struct {
|
||||
Sequence uint64
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
func (p *LoginPolicyView) HasSecondFactors() bool {
|
||||
if p.SecondFactors == nil || len(p.SecondFactors) == 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *LoginPolicyView) HasMultiFactors() bool {
|
||||
if p.MultiFactors == nil || len(p.MultiFactors) == 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
47
internal/iam/model/mfa_view.go
Normal file
47
internal/iam/model/mfa_view.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/model"
|
||||
)
|
||||
|
||||
type SecondFactorsSearchRequest struct {
|
||||
Queries []*MFASearchQuery
|
||||
}
|
||||
|
||||
type MultiFactorsSearchRequest struct {
|
||||
Offset uint64
|
||||
Limit uint64
|
||||
Asc bool
|
||||
Queries []*MFASearchQuery
|
||||
}
|
||||
|
||||
type MFASearchQuery struct {
|
||||
Key MFASearchKey
|
||||
Method model.SearchMethod
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
type MFASearchKey int32
|
||||
|
||||
const (
|
||||
MFASearchKeyUnspecified MFASearchKey = iota
|
||||
MFASearchKeyAggregateID
|
||||
)
|
||||
|
||||
type SecondFactorsSearchResponse struct {
|
||||
TotalResult uint64
|
||||
Result []SecondFactorType
|
||||
}
|
||||
|
||||
type MultiFactorsSearchResponse struct {
|
||||
TotalResult uint64
|
||||
Result []MultiFactorType
|
||||
}
|
||||
|
||||
func (r *SecondFactorsSearchRequest) AppendAggregateIDQuery(aggregateID string) {
|
||||
r.Queries = append(r.Queries, &MFASearchQuery{Key: MFASearchKeyAggregateID, Method: model.SearchMethodEquals, Value: aggregateID})
|
||||
}
|
||||
|
||||
func (r *MultiFactorsSearchRequest) AppendAggregateIDQuery(aggregateID string) {
|
||||
r.Queries = append(r.Queries, &MFASearchQuery{Key: MFASearchKeyAggregateID, Method: model.SearchMethodEquals, Value: aggregateID})
|
||||
}
|
@@ -9,6 +9,7 @@ import (
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
es_int "github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
es_sdk "github.com/caos/zitadel/internal/eventstore/sdk"
|
||||
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||
"github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
||||
@@ -64,6 +65,14 @@ func (es *IAMEventstore) IAMByID(ctx context.Context, id string) (_ *iam_model.I
|
||||
return model.IAMToModel(iam), nil
|
||||
}
|
||||
|
||||
func (es *IAMEventstore) IAMEventsByID(ctx context.Context, id string, sequence uint64) ([]*es_models.Event, error) {
|
||||
query, err := IAMByIDQuery(id, sequence)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return es.FilterEvents(ctx, query)
|
||||
}
|
||||
|
||||
func (es *IAMEventstore) StartSetup(ctx context.Context, iamID string, step iam_model.Step) (*iam_model.IAM, error) {
|
||||
iam, err := es.IAMByID(ctx, iamID)
|
||||
if err != nil && !caos_errs.IsNotFound(err) {
|
||||
@@ -484,7 +493,7 @@ func (es *IAMEventstore) ChangeLabelPolicy(ctx context.Context, policy *iam_mode
|
||||
|
||||
func (es *IAMEventstore) PrepareAddLoginPolicy(ctx context.Context, policy *iam_model.LoginPolicy) (*model.IAM, *models.Aggregate, error) {
|
||||
if policy == nil || !policy.IsValid() {
|
||||
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Lso02", "Errors.IAM.LoginPolicyInvalid")
|
||||
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-3mP0s", "Errors.IAM.LoginPolicyInvalid")
|
||||
}
|
||||
iam, err := es.IAMByID(ctx, policy.AggregateID)
|
||||
if err != nil {
|
||||
@@ -516,7 +525,7 @@ func (es *IAMEventstore) AddLoginPolicy(ctx context.Context, policy *iam_model.L
|
||||
|
||||
func (es *IAMEventstore) ChangeLoginPolicy(ctx context.Context, policy *iam_model.LoginPolicy) (*iam_model.LoginPolicy, error) {
|
||||
if policy == nil || !policy.IsValid() {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Lso02", "Errors.IAM.LoginPolicyInvalid")
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-3M0so", "Errors.IAM.LoginPolicyInvalid")
|
||||
}
|
||||
iam, err := es.IAMByID(ctx, policy.AggregateID)
|
||||
if err != nil {
|
||||
@@ -537,7 +546,7 @@ func (es *IAMEventstore) ChangeLoginPolicy(ctx context.Context, policy *iam_mode
|
||||
|
||||
func (es *IAMEventstore) AddIDPProviderToLoginPolicy(ctx context.Context, provider *iam_model.IDPProvider) (*iam_model.IDPProvider, error) {
|
||||
if provider == nil || !provider.IsValid() {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Lso02", "Errors.IdpProviderInvalid")
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-bMS8i", "Errors.IdpProviderInvalid")
|
||||
}
|
||||
iam, err := es.IAMByID(ctx, provider.AggregateID)
|
||||
if err != nil {
|
||||
@@ -593,9 +602,107 @@ func (es *IAMEventstore) RemoveIDPProviderFromLoginPolicy(ctx context.Context, p
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *IAMEventstore) AddSecondFactorToLoginPolicy(ctx context.Context, aggregateID string, mfa iam_model.SecondFactorType) (iam_model.SecondFactorType, error) {
|
||||
if mfa == iam_model.SecondFactorTypeUnspecified {
|
||||
return 0, caos_errs.ThrowPreconditionFailed(nil, "EVENT-1M8Js", "Errors.IAM.LoginPolicy.MFA.Unspecified")
|
||||
}
|
||||
iam, err := es.IAMByID(ctx, aggregateID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, m := iam.DefaultLoginPolicy.GetSecondFactor(mfa); m != 0 {
|
||||
return 0, caos_errs.ThrowAlreadyExists(nil, "EVENT-4Rk09", "Errors.IAM.LoginPolicy.MFA.AlreadyExists")
|
||||
}
|
||||
repoIam := model.IAMFromModel(iam)
|
||||
repoMFA := model.SecondFactorFromModel(mfa)
|
||||
|
||||
addAggregate := LoginPolicySecondFactorAddedAggregate(es.Eventstore.AggregateCreator(), repoIam, repoMFA)
|
||||
err = es_sdk.Push(ctx, es.PushAggregates, repoIam.AppendEvents, addAggregate)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
es.iamCache.cacheIAM(repoIam)
|
||||
if _, m := model.GetMFA(repoIam.DefaultLoginPolicy.SecondFactors, int32(mfa)); m != 0 {
|
||||
return iam_model.SecondFactorType(m), nil
|
||||
}
|
||||
return 0, caos_errs.ThrowInternal(nil, "EVENT-5N9so", "Errors.Internal")
|
||||
}
|
||||
|
||||
func (es *IAMEventstore) RemoveSecondFactorFromLoginPolicy(ctx context.Context, aggregateID string, mfa iam_model.SecondFactorType) error {
|
||||
if mfa == iam_model.SecondFactorTypeUnspecified {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-4gJ9s", "Errors.IAM.LoginPolicy.MFA.Unspecified")
|
||||
}
|
||||
iam, err := es.IAMByID(ctx, aggregateID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, m := iam.DefaultLoginPolicy.GetSecondFactor(mfa); m == 0 {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-gBm9s", "Errors.IAM.LoginPolicy.MFA.NotExisting")
|
||||
}
|
||||
repoIam := model.IAMFromModel(iam)
|
||||
repoMFA := model.SecondFactorFromModel(mfa)
|
||||
|
||||
removeAgg := LoginPolicySecondFactorRemovedAggregate(es.Eventstore.AggregateCreator(), repoIam, repoMFA)
|
||||
err = es_sdk.Push(ctx, es.PushAggregates, repoIam.AppendEvents, removeAgg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
es.iamCache.cacheIAM(repoIam)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *IAMEventstore) AddMultiFactorToLoginPolicy(ctx context.Context, aggregateID string, mfa iam_model.MultiFactorType) (iam_model.MultiFactorType, error) {
|
||||
if mfa == iam_model.MultiFactorTypeUnspecified {
|
||||
return 0, caos_errs.ThrowPreconditionFailed(nil, "EVENT-2Dh7J", "Errors.IAM.LoginPolicy.MFA.Unspecified")
|
||||
}
|
||||
iam, err := es.IAMByID(ctx, aggregateID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, m := iam.DefaultLoginPolicy.GetMultiFactor(mfa); m != 0 {
|
||||
return 0, caos_errs.ThrowAlreadyExists(nil, "EVENT-4Rk09", "Errors.IAM.LoginPolicy.MFA.AlreadyExists")
|
||||
}
|
||||
repoIam := model.IAMFromModel(iam)
|
||||
repoMFA := model.MultiFactorFromModel(mfa)
|
||||
|
||||
addAggregate := LoginPolicyMultiFactorAddedAggregate(es.Eventstore.AggregateCreator(), repoIam, repoMFA)
|
||||
err = es_sdk.Push(ctx, es.PushAggregates, repoIam.AppendEvents, addAggregate)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
es.iamCache.cacheIAM(repoIam)
|
||||
if _, m := model.GetMFA(repoIam.DefaultLoginPolicy.MultiFactors, int32(mfa)); m != 0 {
|
||||
return iam_model.MultiFactorType(m), nil
|
||||
}
|
||||
return 0, caos_errs.ThrowInternal(nil, "EVENT-5N9so", "Errors.Internal")
|
||||
}
|
||||
|
||||
func (es *IAMEventstore) RemoveMultiFactorFromLoginPolicy(ctx context.Context, aggregateID string, mfa iam_model.MultiFactorType) error {
|
||||
if mfa == iam_model.MultiFactorTypeUnspecified {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-4gJ9s", "Errors.IAM.LoginPolicy.MFA.Unspecified")
|
||||
}
|
||||
iam, err := es.IAMByID(ctx, aggregateID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, m := iam.DefaultLoginPolicy.GetMultiFactor(mfa); m == 0 {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-gBm9s", "Errors.IAM.LoginPolicy.MFA.NotExisting")
|
||||
}
|
||||
repoIam := model.IAMFromModel(iam)
|
||||
repoMFA := model.MultiFactorFromModel(mfa)
|
||||
|
||||
removeAgg := LoginPolicyMultiFactorRemovedAggregate(es.Eventstore.AggregateCreator(), repoIam, repoMFA)
|
||||
err = es_sdk.Push(ctx, es.PushAggregates, repoIam.AppendEvents, removeAgg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
es.iamCache.cacheIAM(repoIam)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *IAMEventstore) PrepareAddPasswordComplexityPolicy(ctx context.Context, policy *iam_model.PasswordComplexityPolicy) (*model.IAM, *models.Aggregate, error) {
|
||||
if policy == nil {
|
||||
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Lso02", "Errors.IAM.PasswordComplexityPolicy.Empty")
|
||||
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Ks8Fs", "Errors.IAM.PasswordComplexityPolicy.Empty")
|
||||
}
|
||||
if err := policy.IsValid(); err != nil {
|
||||
return nil, nil, err
|
||||
@@ -764,7 +871,7 @@ func (es *IAMEventstore) GetOrgIAMPolicy(ctx context.Context, iamID string) (*ia
|
||||
return nil, err
|
||||
}
|
||||
if existingIAM.DefaultOrgIAMPolicy == nil {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "EVENT-2Fj8s", "Errors.IAM.OrgIAM.NotExisting")
|
||||
return nil, caos_errs.ThrowNotFound(nil, "EVENT-2Fj8s", "Errors.IAM.OrgIAMPolicy.NotExisting")
|
||||
}
|
||||
return existingIAM.DefaultOrgIAMPolicy, nil
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ package eventsourcing
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
model2 "github.com/caos/zitadel/internal/iam/model"
|
||||
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/id"
|
||||
@@ -123,6 +124,25 @@ func GetMockManipulateIAMWithLoginPolicy(ctrl *gomock.Controller) *IAMEventstore
|
||||
return GetMockedEventstore(ctrl, mockEs)
|
||||
}
|
||||
|
||||
func GetMockManipulateIAMWithLoginPolicyWithMFAs(ctrl *gomock.Controller) *IAMEventstore {
|
||||
policyData, _ := json.Marshal(model.LoginPolicy{AllowRegister: true, AllowUsernamePassword: true, AllowExternalIdp: true})
|
||||
idpProviderData, _ := json.Marshal(model.IDPProvider{IDPConfigID: "IDPConfigID", Type: 1})
|
||||
secondFactor, _ := json.Marshal(model.MFA{MfaType: int32(model2.SecondFactorTypeOTP)})
|
||||
multiFactor, _ := json.Marshal(model.MFA{MfaType: int32(model2.MultiFactorTypeU2FWithPIN)})
|
||||
events := []*es_models.Event{
|
||||
{AggregateID: "AggregateID", Sequence: 1, Type: model.IAMSetupStarted},
|
||||
{AggregateID: "AggregateID", Sequence: 1, Type: model.LoginPolicyAdded, Data: policyData},
|
||||
{AggregateID: "AggregateID", Sequence: 1, Type: model.LoginPolicyIDPProviderAdded, Data: idpProviderData},
|
||||
{AggregateID: "AggregateID", Sequence: 1, Type: model.LoginPolicySecondFactorAdded, Data: secondFactor},
|
||||
{AggregateID: "AggregateID", Sequence: 1, Type: model.LoginPolicyMultiFactorAdded, Data: multiFactor},
|
||||
}
|
||||
mockEs := mock.NewMockEventstore(ctrl)
|
||||
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||
return GetMockedEventstore(ctrl, mockEs)
|
||||
}
|
||||
|
||||
func GetMockManipulateIAMWithPasswodComplexityPolicy(ctrl *gomock.Controller) *IAMEventstore {
|
||||
policyData, _ := json.Marshal(model.PasswordComplexityPolicy{MinLength: 10})
|
||||
events := []*es_models.Event{
|
||||
|
@@ -1657,6 +1657,340 @@ func TestRemoveIdpProviderFromLoginPolicy(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddSecondFactorToLoginPolicy(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
type args struct {
|
||||
es *IAMEventstore
|
||||
ctx context.Context
|
||||
aggreageID string
|
||||
mfa iam_model.SecondFactorType
|
||||
}
|
||||
type res struct {
|
||||
result iam_model.SecondFactorType
|
||||
wantErr bool
|
||||
errFunc func(err error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "add second factor to login policy, ok",
|
||||
args: args{
|
||||
es: GetMockManipulateIAMWithLoginPolicy(ctrl),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
aggreageID: "AggregateID",
|
||||
mfa: iam_model.SecondFactorTypeOTP,
|
||||
},
|
||||
res: res{
|
||||
result: iam_model.SecondFactorTypeOTP,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add second factor to login policy, already existing",
|
||||
args: args{
|
||||
es: GetMockManipulateIAMWithLoginPolicyWithMFAs(ctrl),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
aggreageID: "AggregateID",
|
||||
mfa: iam_model.SecondFactorTypeOTP,
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsErrorAlreadyExists,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid mfa",
|
||||
args: args{
|
||||
es: GetMockManipulateIAM(ctrl),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
mfa: iam_model.SecondFactorTypeUnspecified,
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing iam not found",
|
||||
args: args{
|
||||
es: GetMockManipulateIAMNotExisting(ctrl),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
aggreageID: "Test",
|
||||
mfa: iam_model.SecondFactorTypeOTP,
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := tt.args.es.AddSecondFactorToLoginPolicy(tt.args.ctx, tt.args.aggreageID, tt.args.mfa)
|
||||
if (tt.res.wantErr && !tt.res.errFunc(err)) || (err != nil && !tt.res.wantErr) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
return
|
||||
}
|
||||
if tt.res.wantErr && tt.res.errFunc(err) {
|
||||
return
|
||||
}
|
||||
if result != tt.res.result {
|
||||
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.res.result, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveSecondFactorFromLoginPolicy(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
type args struct {
|
||||
es *IAMEventstore
|
||||
ctx context.Context
|
||||
aggregateID string
|
||||
mfa iam_model.SecondFactorType
|
||||
}
|
||||
type res struct {
|
||||
wantErr bool
|
||||
errFunc func(err error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "remove second factor from login policy, ok",
|
||||
args: args{
|
||||
es: GetMockManipulateIAMWithLoginPolicyWithMFAs(ctrl),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
aggregateID: "AggregateID",
|
||||
mfa: iam_model.SecondFactorTypeOTP,
|
||||
},
|
||||
res: res{},
|
||||
},
|
||||
{
|
||||
name: "remove second factor to login policy, not existing",
|
||||
args: args{
|
||||
es: GetMockManipulateIAMWithLoginPolicy(ctrl),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
aggregateID: "AggregateID",
|
||||
mfa: iam_model.SecondFactorTypeOTP,
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid provider",
|
||||
args: args{
|
||||
es: GetMockManipulateIAM(ctrl),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
aggregateID: "AggregateID",
|
||||
mfa: iam_model.SecondFactorTypeUnspecified,
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing iam not found",
|
||||
args: args{
|
||||
es: GetMockManipulateIAMNotExisting(ctrl),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
aggregateID: "Test",
|
||||
mfa: iam_model.SecondFactorTypeOTP,
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.args.es.RemoveSecondFactorFromLoginPolicy(tt.args.ctx, tt.args.aggregateID, tt.args.mfa)
|
||||
|
||||
if !tt.res.wantErr && err != nil {
|
||||
t.Errorf("should not get err: %v ", err)
|
||||
}
|
||||
if tt.res.wantErr && !tt.res.errFunc(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddMultiFactorToLoginPolicy(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
type args struct {
|
||||
es *IAMEventstore
|
||||
ctx context.Context
|
||||
aggreageID string
|
||||
mfa iam_model.MultiFactorType
|
||||
}
|
||||
type res struct {
|
||||
result iam_model.MultiFactorType
|
||||
wantErr bool
|
||||
errFunc func(err error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "add second factor to login policy, ok",
|
||||
args: args{
|
||||
es: GetMockManipulateIAMWithLoginPolicy(ctrl),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
aggreageID: "AggregateID",
|
||||
mfa: iam_model.MultiFactorTypeU2FWithPIN,
|
||||
},
|
||||
res: res{
|
||||
result: iam_model.MultiFactorTypeU2FWithPIN,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add second factor to login policy, already existing",
|
||||
args: args{
|
||||
es: GetMockManipulateIAMWithLoginPolicyWithMFAs(ctrl),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
aggreageID: "AggregateID",
|
||||
mfa: iam_model.MultiFactorTypeU2FWithPIN,
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsErrorAlreadyExists,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid mfa",
|
||||
args: args{
|
||||
es: GetMockManipulateIAM(ctrl),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
mfa: iam_model.MultiFactorTypeUnspecified,
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing iam not found",
|
||||
args: args{
|
||||
es: GetMockManipulateIAMNotExisting(ctrl),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
aggreageID: "Test",
|
||||
mfa: iam_model.MultiFactorTypeU2FWithPIN,
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := tt.args.es.AddMultiFactorToLoginPolicy(tt.args.ctx, tt.args.aggreageID, tt.args.mfa)
|
||||
if (tt.res.wantErr && !tt.res.errFunc(err)) || (err != nil && !tt.res.wantErr) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
return
|
||||
}
|
||||
if tt.res.wantErr && tt.res.errFunc(err) {
|
||||
return
|
||||
}
|
||||
if result != tt.res.result {
|
||||
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.res.result, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveMultiFactorFromLoginPolicy(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
type args struct {
|
||||
es *IAMEventstore
|
||||
ctx context.Context
|
||||
aggregateID string
|
||||
mfa iam_model.MultiFactorType
|
||||
}
|
||||
type res struct {
|
||||
wantErr bool
|
||||
errFunc func(err error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "remove second factor from login policy, ok",
|
||||
args: args{
|
||||
es: GetMockManipulateIAMWithLoginPolicyWithMFAs(ctrl),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
aggregateID: "AggregateID",
|
||||
mfa: iam_model.MultiFactorTypeU2FWithPIN,
|
||||
},
|
||||
res: res{},
|
||||
},
|
||||
{
|
||||
name: "remove second factor to login policy, not existing",
|
||||
args: args{
|
||||
es: GetMockManipulateIAMWithLoginPolicy(ctrl),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
aggregateID: "AggregateID",
|
||||
mfa: iam_model.MultiFactorTypeU2FWithPIN,
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid provider",
|
||||
args: args{
|
||||
es: GetMockManipulateIAM(ctrl),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
aggregateID: "AggregateID",
|
||||
mfa: iam_model.MultiFactorTypeUnspecified,
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing iam not found",
|
||||
args: args{
|
||||
es: GetMockManipulateIAMNotExisting(ctrl),
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
aggregateID: "Test",
|
||||
mfa: iam_model.MultiFactorTypeU2FWithPIN,
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.args.es.RemoveMultiFactorFromLoginPolicy(tt.args.ctx, tt.args.aggregateID, tt.args.mfa)
|
||||
|
||||
if !tt.res.wantErr && err != nil {
|
||||
t.Errorf("should not get err: %v ", err)
|
||||
}
|
||||
if tt.res.wantErr && !tt.res.errFunc(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddLabelPolicy(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
type args struct {
|
||||
|
@@ -346,6 +346,70 @@ func LoginPolicyIDPProviderRemovedAggregate(ctx context.Context, aggCreator *es_
|
||||
return agg.AppendEvent(model.LoginPolicyIDPProviderRemoved, provider)
|
||||
}
|
||||
|
||||
func LoginPolicySecondFactorAddedAggregate(aggCreator *es_models.AggregateCreator, existing *model.IAM, mfa *model.MFA) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
if mfa == nil {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-4Gm9s", "Errors.Internal")
|
||||
}
|
||||
agg, err := IAMAggregate(ctx, aggCreator, existing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
validationQuery := es_models.NewSearchQuery().
|
||||
AggregateTypeFilter(model.IAMAggregate).
|
||||
AggregateIDFilter(existing.AggregateID)
|
||||
|
||||
validation := checkExistingLoginPolicySecondFactorValidation(mfa.MfaType)
|
||||
agg.SetPrecondition(validationQuery, validation)
|
||||
return agg.AppendEvent(model.LoginPolicySecondFactorAdded, mfa)
|
||||
}
|
||||
}
|
||||
|
||||
func LoginPolicySecondFactorRemovedAggregate(aggCreator *es_models.AggregateCreator, existing *model.IAM, mfa *model.MFA) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
if mfa == nil || existing == nil {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-5Bm9s", "Errors.Internal")
|
||||
}
|
||||
agg, err := IAMAggregate(ctx, aggCreator, existing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return agg.AppendEvent(model.LoginPolicySecondFactorRemoved, mfa)
|
||||
}
|
||||
}
|
||||
|
||||
func LoginPolicyMultiFactorAddedAggregate(aggCreator *es_models.AggregateCreator, existing *model.IAM, mfa *model.MFA) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
if mfa == nil {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-4Gm9s", "Errors.Internal")
|
||||
}
|
||||
agg, err := IAMAggregate(ctx, aggCreator, existing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
validationQuery := es_models.NewSearchQuery().
|
||||
AggregateTypeFilter(model.IAMAggregate).
|
||||
AggregateIDFilter(existing.AggregateID)
|
||||
|
||||
validation := checkExistingLoginPolicyMultiFactorValidation(mfa.MfaType)
|
||||
agg.SetPrecondition(validationQuery, validation)
|
||||
return agg.AppendEvent(model.LoginPolicyMultiFactorAdded, mfa)
|
||||
}
|
||||
}
|
||||
|
||||
func LoginPolicyMultiFactorRemovedAggregate(aggCreator *es_models.AggregateCreator, existing *model.IAM, mfa *model.MFA) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
if mfa == nil || existing == nil {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-6Mso9", "Errors.Internal")
|
||||
}
|
||||
agg, err := IAMAggregate(ctx, aggCreator, existing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return agg.AppendEvent(model.LoginPolicyMultiFactorRemoved, mfa)
|
||||
}
|
||||
}
|
||||
|
||||
func PasswordComplexityPolicyAddedAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, existing *model.IAM, policy *model.PasswordComplexityPolicy) (*es_models.Aggregate, error) {
|
||||
if policy == nil {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-Smla8", "Errors.Internal")
|
||||
@@ -613,3 +677,77 @@ func checkExistingLoginPolicyIDPProviderValidation(idpConfigID string) func(...*
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func checkExistingLoginPolicySecondFactorValidation(mfaType int32) func(...*es_models.Event) error {
|
||||
return func(events ...*es_models.Event) error {
|
||||
mfas := make([]int32, 0)
|
||||
for _, event := range events {
|
||||
switch event.Type {
|
||||
case model.LoginPolicySecondFactorAdded:
|
||||
idp := new(model.MFA)
|
||||
err := idp.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mfas = append(mfas, idp.MfaType)
|
||||
case model.LoginPolicySecondFactorRemoved:
|
||||
mfa := new(model.MFA)
|
||||
err := mfa.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := len(mfas) - 1; i >= 0; i-- {
|
||||
if mfas[i] == mfa.MfaType {
|
||||
mfas[i] = mfas[len(mfas)-1]
|
||||
mfas[len(mfas)-1] = 0
|
||||
mfas = mfas[:len(mfas)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, m := range mfas {
|
||||
if m == mfaType {
|
||||
return errors.ThrowPreconditionFailed(nil, "EVENT-3vmHd", "Errors.IAM.LoginPolicy.MFA.AlreadyExisting")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func checkExistingLoginPolicyMultiFactorValidation(mfaType int32) func(...*es_models.Event) error {
|
||||
return func(events ...*es_models.Event) error {
|
||||
mfas := make([]int32, 0)
|
||||
for _, event := range events {
|
||||
switch event.Type {
|
||||
case model.LoginPolicyMultiFactorAdded:
|
||||
idp := new(model.MFA)
|
||||
err := idp.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mfas = append(mfas, idp.MfaType)
|
||||
case model.LoginPolicyMultiFactorRemoved:
|
||||
mfa := new(model.MFA)
|
||||
err := mfa.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := len(mfas) - 1; i >= 0; i-- {
|
||||
if mfas[i] == mfa.MfaType {
|
||||
mfas[i] = mfas[len(mfas)-1]
|
||||
mfas[len(mfas)-1] = 0
|
||||
mfas = mfas[:len(mfas)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, m := range mfas {
|
||||
if m == mfaType {
|
||||
return errors.ThrowPreconditionFailed(nil, "EVENT-6Hsj89", "Errors.IAM.LoginPolicy.MFA.AlreadyExisting")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@@ -1468,6 +1468,360 @@ func TestLoginPolicyIdpProviderRemovedAggregate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoginPolicySecondFactorAddedAggregate(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
existingIAM *model.IAM
|
||||
newMFA *model.MFA
|
||||
aggCreator *models.AggregateCreator
|
||||
}
|
||||
type res struct {
|
||||
eventLen int
|
||||
eventTypes []models.EventType
|
||||
wantErr bool
|
||||
errFunc func(err error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "add second factor to login policy",
|
||||
args: args{
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
existingIAM: &model.IAM{
|
||||
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
|
||||
IAMProjectID: "IAMProjectID",
|
||||
DefaultLoginPolicy: &model.LoginPolicy{
|
||||
AllowUsernamePassword: true,
|
||||
}},
|
||||
newMFA: &model.MFA{
|
||||
MfaType: int32(iam_model.SecondFactorTypeOTP),
|
||||
},
|
||||
aggCreator: models.NewAggregateCreator("Test"),
|
||||
},
|
||||
res: res{
|
||||
eventLen: 1,
|
||||
eventTypes: []models.EventType{model.LoginPolicySecondFactorAdded},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing iam nil",
|
||||
args: args{
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
existingIAM: nil,
|
||||
aggCreator: models.NewAggregateCreator("Test"),
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mfa config nil",
|
||||
args: args{
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
existingIAM: &model.IAM{ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}, IAMProjectID: "IAMProjectID"},
|
||||
newMFA: nil,
|
||||
aggCreator: models.NewAggregateCreator("Test"),
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
agg, err := LoginPolicySecondFactorAddedAggregate(tt.args.aggCreator, tt.args.existingIAM, tt.args.newMFA)(tt.args.ctx)
|
||||
|
||||
if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen {
|
||||
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
|
||||
}
|
||||
for i := 0; i < tt.res.eventLen; i++ {
|
||||
if !tt.res.wantErr && agg.Events[i].Type != tt.res.eventTypes[i] {
|
||||
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], agg.Events[i].Type.String())
|
||||
}
|
||||
if !tt.res.wantErr && agg.Events[i].Data == nil {
|
||||
t.Errorf("should have data in event")
|
||||
}
|
||||
}
|
||||
|
||||
if tt.res.wantErr && !tt.res.errFunc(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoginPolicySecondFactorRemovedAggregate(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
existingIAM *model.IAM
|
||||
mfa *model.MFA
|
||||
aggCreator *models.AggregateCreator
|
||||
}
|
||||
type res struct {
|
||||
eventLen int
|
||||
eventTypes []models.EventType
|
||||
wantErr bool
|
||||
errFunc func(err error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "remove second factor to login policy",
|
||||
args: args{
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
existingIAM: &model.IAM{
|
||||
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
|
||||
IAMProjectID: "IAMProjectID",
|
||||
DefaultLoginPolicy: &model.LoginPolicy{
|
||||
AllowUsernamePassword: true,
|
||||
SecondFactors: []int32{
|
||||
int32(iam_model.SecondFactorTypeOTP),
|
||||
},
|
||||
}},
|
||||
mfa: &model.MFA{
|
||||
MfaType: int32(iam_model.SecondFactorTypeOTP),
|
||||
},
|
||||
aggCreator: models.NewAggregateCreator("Test"),
|
||||
},
|
||||
res: res{
|
||||
eventLen: 1,
|
||||
eventTypes: []models.EventType{model.LoginPolicySecondFactorRemoved},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing iam nil",
|
||||
args: args{
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
existingIAM: nil,
|
||||
aggCreator: models.NewAggregateCreator("Test"),
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "idp config config nil",
|
||||
args: args{
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
existingIAM: &model.IAM{ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}, IAMProjectID: "IAMProjectID"},
|
||||
mfa: nil,
|
||||
aggCreator: models.NewAggregateCreator("Test"),
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
agg, err := LoginPolicySecondFactorRemovedAggregate(tt.args.aggCreator, tt.args.existingIAM, tt.args.mfa)(tt.args.ctx)
|
||||
|
||||
if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen {
|
||||
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
|
||||
}
|
||||
for i := 0; i < tt.res.eventLen; i++ {
|
||||
if !tt.res.wantErr && agg.Events[i].Type != tt.res.eventTypes[i] {
|
||||
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], agg.Events[i].Type.String())
|
||||
}
|
||||
if !tt.res.wantErr && agg.Events[i].Data == nil {
|
||||
t.Errorf("should have data in event")
|
||||
}
|
||||
}
|
||||
|
||||
if tt.res.wantErr && !tt.res.errFunc(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoginPolicyMultiFactorAddedAggregate(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
existingIAM *model.IAM
|
||||
newMFA *model.MFA
|
||||
aggCreator *models.AggregateCreator
|
||||
}
|
||||
type res struct {
|
||||
eventLen int
|
||||
eventTypes []models.EventType
|
||||
wantErr bool
|
||||
errFunc func(err error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "add mfa to login policy",
|
||||
args: args{
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
existingIAM: &model.IAM{
|
||||
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
|
||||
IAMProjectID: "IAMProjectID",
|
||||
DefaultLoginPolicy: &model.LoginPolicy{
|
||||
AllowUsernamePassword: true,
|
||||
}},
|
||||
newMFA: &model.MFA{
|
||||
MfaType: int32(iam_model.MultiFactorTypeU2FWithPIN),
|
||||
},
|
||||
aggCreator: models.NewAggregateCreator("Test"),
|
||||
},
|
||||
res: res{
|
||||
eventLen: 1,
|
||||
eventTypes: []models.EventType{model.LoginPolicyMultiFactorAdded},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing iam nil",
|
||||
args: args{
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
existingIAM: nil,
|
||||
aggCreator: models.NewAggregateCreator("Test"),
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mfa config nil",
|
||||
args: args{
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
existingIAM: &model.IAM{ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}, IAMProjectID: "IAMProjectID"},
|
||||
newMFA: nil,
|
||||
aggCreator: models.NewAggregateCreator("Test"),
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
agg, err := LoginPolicyMultiFactorAddedAggregate(tt.args.aggCreator, tt.args.existingIAM, tt.args.newMFA)(tt.args.ctx)
|
||||
|
||||
if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen {
|
||||
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
|
||||
}
|
||||
for i := 0; i < tt.res.eventLen; i++ {
|
||||
if !tt.res.wantErr && agg.Events[i].Type != tt.res.eventTypes[i] {
|
||||
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], agg.Events[i].Type.String())
|
||||
}
|
||||
if !tt.res.wantErr && agg.Events[i].Data == nil {
|
||||
t.Errorf("should have data in event")
|
||||
}
|
||||
}
|
||||
|
||||
if tt.res.wantErr && !tt.res.errFunc(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoginPolicyMultiFactorRemovedAggregate(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
existingIAM *model.IAM
|
||||
mfa *model.MFA
|
||||
aggCreator *models.AggregateCreator
|
||||
}
|
||||
type res struct {
|
||||
eventLen int
|
||||
eventTypes []models.EventType
|
||||
wantErr bool
|
||||
errFunc func(err error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "remove mfa to login policy",
|
||||
args: args{
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
existingIAM: &model.IAM{
|
||||
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
|
||||
IAMProjectID: "IAMProjectID",
|
||||
DefaultLoginPolicy: &model.LoginPolicy{
|
||||
AllowUsernamePassword: true,
|
||||
SecondFactors: []int32{
|
||||
int32(iam_model.SecondFactorTypeOTP),
|
||||
},
|
||||
}},
|
||||
mfa: &model.MFA{
|
||||
MfaType: int32(iam_model.SecondFactorTypeOTP),
|
||||
},
|
||||
aggCreator: models.NewAggregateCreator("Test"),
|
||||
},
|
||||
res: res{
|
||||
eventLen: 1,
|
||||
eventTypes: []models.EventType{model.LoginPolicyMultiFactorRemoved},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing iam nil",
|
||||
args: args{
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
existingIAM: nil,
|
||||
aggCreator: models.NewAggregateCreator("Test"),
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "idp config config nil",
|
||||
args: args{
|
||||
ctx: authz.NewMockContext("orgID", "userID"),
|
||||
existingIAM: &model.IAM{ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}, IAMProjectID: "IAMProjectID"},
|
||||
mfa: nil,
|
||||
aggCreator: models.NewAggregateCreator("Test"),
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
agg, err := LoginPolicyMultiFactorRemovedAggregate(tt.args.aggCreator, tt.args.existingIAM, tt.args.mfa)(tt.args.ctx)
|
||||
|
||||
if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen {
|
||||
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
|
||||
}
|
||||
for i := 0; i < tt.res.eventLen; i++ {
|
||||
if !tt.res.wantErr && agg.Events[i].Type != tt.res.eventTypes[i] {
|
||||
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], agg.Events[i].Type.String())
|
||||
}
|
||||
if !tt.res.wantErr && agg.Events[i].Data == nil {
|
||||
t.Errorf("should have data in event")
|
||||
}
|
||||
}
|
||||
|
||||
if tt.res.wantErr && !tt.res.errFunc(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPasswordComplexityPolicyAddedAggregate(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
|
@@ -168,6 +168,14 @@ func (i *IAM) AppendEvent(event *es_models.Event) (err error) {
|
||||
return i.appendAddIDPProviderToLoginPolicyEvent(event)
|
||||
case LoginPolicyIDPProviderRemoved:
|
||||
return i.appendRemoveIDPProviderFromLoginPolicyEvent(event)
|
||||
case LoginPolicySecondFactorAdded:
|
||||
return i.appendAddSecondFactorToLoginPolicyEvent(event)
|
||||
case LoginPolicySecondFactorRemoved:
|
||||
return i.appendRemoveSecondFactorFromLoginPolicyEvent(event)
|
||||
case LoginPolicyMultiFactorAdded:
|
||||
return i.appendAddMultiFactorToLoginPolicyEvent(event)
|
||||
case LoginPolicyMultiFactorRemoved:
|
||||
return i.appendRemoveMultiFactorFromLoginPolicyEvent(event)
|
||||
case LabelPolicyAdded:
|
||||
return i.appendAddLabelPolicyEvent(event)
|
||||
case LabelPolicyChanged:
|
||||
|
@@ -14,7 +14,10 @@ type LoginPolicy struct {
|
||||
AllowUsernamePassword bool `json:"allowUsernamePassword"`
|
||||
AllowRegister bool `json:"allowRegister"`
|
||||
AllowExternalIdp bool `json:"allowExternalIdp"`
|
||||
ForceMFA bool `json:"forceMfa"`
|
||||
IDPProviders []*IDPProvider `json:"-"`
|
||||
SecondFactors []int32 `json:"-"`
|
||||
MultiFactors []int32 `json:"-"`
|
||||
}
|
||||
|
||||
type IDPProvider struct {
|
||||
@@ -27,6 +30,10 @@ type IDPProviderID struct {
|
||||
IDPConfigID string `json:"idpConfigId"`
|
||||
}
|
||||
|
||||
type MFA struct {
|
||||
MfaType int32 `json:"mfaType"`
|
||||
}
|
||||
|
||||
func GetIDPProvider(providers []*IDPProvider, id string) (int, *IDPProvider) {
|
||||
for i, p := range providers {
|
||||
if p.IDPConfigID == id {
|
||||
@@ -36,8 +43,18 @@ func GetIDPProvider(providers []*IDPProvider, id string) (int, *IDPProvider) {
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
func GetMFA(mfas []int32, mfaType int32) (int, int32) {
|
||||
for i, m := range mfas {
|
||||
if m == mfaType {
|
||||
return i, m
|
||||
}
|
||||
}
|
||||
return -1, 0
|
||||
}
|
||||
func LoginPolicyToModel(policy *LoginPolicy) *iam_model.LoginPolicy {
|
||||
idps := IDPProvidersToModel(policy.IDPProviders)
|
||||
secondFactors := SecondFactorsToModel(policy.SecondFactors)
|
||||
multiFactors := MultiFactorsToModel(policy.MultiFactors)
|
||||
return &iam_model.LoginPolicy{
|
||||
ObjectRoot: policy.ObjectRoot,
|
||||
State: iam_model.PolicyState(policy.State),
|
||||
@@ -45,11 +62,16 @@ func LoginPolicyToModel(policy *LoginPolicy) *iam_model.LoginPolicy {
|
||||
AllowRegister: policy.AllowRegister,
|
||||
AllowExternalIdp: policy.AllowExternalIdp,
|
||||
IDPProviders: idps,
|
||||
ForceMFA: policy.ForceMFA,
|
||||
SecondFactors: secondFactors,
|
||||
MultiFactors: multiFactors,
|
||||
}
|
||||
}
|
||||
|
||||
func LoginPolicyFromModel(policy *iam_model.LoginPolicy) *LoginPolicy {
|
||||
idps := IDOProvidersFromModel(policy.IDPProviders)
|
||||
secondFactors := SecondFactorsFromModel(policy.SecondFactors)
|
||||
multiFactors := MultiFactorsFromModel(policy.MultiFactors)
|
||||
return &LoginPolicy{
|
||||
ObjectRoot: policy.ObjectRoot,
|
||||
State: int32(policy.State),
|
||||
@@ -57,6 +79,9 @@ func LoginPolicyFromModel(policy *iam_model.LoginPolicy) *LoginPolicy {
|
||||
AllowRegister: policy.AllowRegister,
|
||||
AllowExternalIdp: policy.AllowExternalIdp,
|
||||
IDPProviders: idps,
|
||||
ForceMFA: policy.ForceMFA,
|
||||
SecondFactors: secondFactors,
|
||||
MultiFactors: multiFactors,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +117,46 @@ func IDPProviderFromModel(provider *iam_model.IDPProvider) *IDPProvider {
|
||||
}
|
||||
}
|
||||
|
||||
func SecondFactorsFromModel(mfas []iam_model.SecondFactorType) []int32 {
|
||||
convertedMFAs := make([]int32, len(mfas))
|
||||
for i, mfa := range mfas {
|
||||
convertedMFAs[i] = int32(mfa)
|
||||
}
|
||||
return convertedMFAs
|
||||
}
|
||||
|
||||
func SecondFactorFromModel(mfa iam_model.SecondFactorType) *MFA {
|
||||
return &MFA{MfaType: int32(mfa)}
|
||||
}
|
||||
|
||||
func SecondFactorsToModel(mfas []int32) []iam_model.SecondFactorType {
|
||||
convertedMFAs := make([]iam_model.SecondFactorType, len(mfas))
|
||||
for i, mfa := range mfas {
|
||||
convertedMFAs[i] = iam_model.SecondFactorType(mfa)
|
||||
}
|
||||
return convertedMFAs
|
||||
}
|
||||
|
||||
func MultiFactorsFromModel(mfas []iam_model.MultiFactorType) []int32 {
|
||||
convertedMFAs := make([]int32, len(mfas))
|
||||
for i, mfa := range mfas {
|
||||
convertedMFAs[i] = int32(mfa)
|
||||
}
|
||||
return convertedMFAs
|
||||
}
|
||||
|
||||
func MultiFactorFromModel(mfa iam_model.MultiFactorType) *MFA {
|
||||
return &MFA{MfaType: int32(mfa)}
|
||||
}
|
||||
|
||||
func MultiFactorsToModel(mfas []int32) []iam_model.MultiFactorType {
|
||||
convertedMFAs := make([]iam_model.MultiFactorType, len(mfas))
|
||||
for i, mfa := range mfas {
|
||||
convertedMFAs[i] = iam_model.MultiFactorType(mfa)
|
||||
}
|
||||
return convertedMFAs
|
||||
}
|
||||
|
||||
func (p *LoginPolicy) Changes(changed *LoginPolicy) map[string]interface{} {
|
||||
changes := make(map[string]interface{}, 2)
|
||||
|
||||
@@ -104,7 +169,9 @@ func (p *LoginPolicy) Changes(changed *LoginPolicy) map[string]interface{} {
|
||||
if changed.AllowExternalIdp != p.AllowExternalIdp {
|
||||
changes["allowExternalIdp"] = changed.AllowExternalIdp
|
||||
}
|
||||
|
||||
if changed.ForceMFA != p.ForceMFA {
|
||||
changes["forceMFA"] = changed.ForceMFA
|
||||
}
|
||||
return changes
|
||||
}
|
||||
|
||||
@@ -143,6 +210,57 @@ func (iam *IAM) appendRemoveIDPProviderFromLoginPolicyEvent(event *es_models.Eve
|
||||
iam.DefaultLoginPolicy.IDPProviders[i] = iam.DefaultLoginPolicy.IDPProviders[len(iam.DefaultLoginPolicy.IDPProviders)-1]
|
||||
iam.DefaultLoginPolicy.IDPProviders[len(iam.DefaultLoginPolicy.IDPProviders)-1] = nil
|
||||
iam.DefaultLoginPolicy.IDPProviders = iam.DefaultLoginPolicy.IDPProviders[:len(iam.DefaultLoginPolicy.IDPProviders)-1]
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (iam *IAM) appendAddSecondFactorToLoginPolicyEvent(event *es_models.Event) error {
|
||||
mfa := new(MFA)
|
||||
err := mfa.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
iam.DefaultLoginPolicy.SecondFactors = append(iam.DefaultLoginPolicy.SecondFactors, mfa.MfaType)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (iam *IAM) appendRemoveSecondFactorFromLoginPolicyEvent(event *es_models.Event) error {
|
||||
mfa := new(MFA)
|
||||
err := mfa.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if i, m := GetMFA(iam.DefaultLoginPolicy.SecondFactors, mfa.MfaType); m != 0 {
|
||||
iam.DefaultLoginPolicy.SecondFactors[i] = iam.DefaultLoginPolicy.SecondFactors[len(iam.DefaultLoginPolicy.SecondFactors)-1]
|
||||
iam.DefaultLoginPolicy.SecondFactors[len(iam.DefaultLoginPolicy.SecondFactors)-1] = 0
|
||||
iam.DefaultLoginPolicy.SecondFactors = iam.DefaultLoginPolicy.SecondFactors[:len(iam.DefaultLoginPolicy.SecondFactors)-1]
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (iam *IAM) appendAddMultiFactorToLoginPolicyEvent(event *es_models.Event) error {
|
||||
mfa := new(MFA)
|
||||
err := mfa.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
iam.DefaultLoginPolicy.MultiFactors = append(iam.DefaultLoginPolicy.MultiFactors, mfa.MfaType)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (iam *IAM) appendRemoveMultiFactorFromLoginPolicyEvent(event *es_models.Event) error {
|
||||
mfa := new(MFA)
|
||||
err := mfa.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if i, m := GetMFA(iam.DefaultLoginPolicy.MultiFactors, mfa.MfaType); m != 0 {
|
||||
iam.DefaultLoginPolicy.MultiFactors[i] = iam.DefaultLoginPolicy.MultiFactors[len(iam.DefaultLoginPolicy.MultiFactors)-1]
|
||||
iam.DefaultLoginPolicy.MultiFactors[len(iam.DefaultLoginPolicy.MultiFactors)-1] = 0
|
||||
iam.DefaultLoginPolicy.MultiFactors = iam.DefaultLoginPolicy.MultiFactors[:len(iam.DefaultLoginPolicy.MultiFactors)-1]
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -162,3 +280,11 @@ func (p *IDPProvider) SetData(event *es_models.Event) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MFA) SetData(event *es_models.Event) error {
|
||||
err := json.Unmarshal(event.Data, m)
|
||||
if err != nil {
|
||||
return errors.ThrowInternal(err, "EVENT-4G9os", "unable to unmarshal data")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -23,18 +23,18 @@ func TestLoginPolicyChanges(t *testing.T) {
|
||||
{
|
||||
name: "loginpolicy all attributes change",
|
||||
args: args{
|
||||
existing: &LoginPolicy{AllowUsernamePassword: false, AllowRegister: false, AllowExternalIdp: false},
|
||||
new: &LoginPolicy{AllowUsernamePassword: true, AllowRegister: true, AllowExternalIdp: true},
|
||||
existing: &LoginPolicy{AllowUsernamePassword: false, AllowRegister: false, AllowExternalIdp: false, ForceMFA: false},
|
||||
new: &LoginPolicy{AllowUsernamePassword: true, AllowRegister: true, AllowExternalIdp: true, ForceMFA: true},
|
||||
},
|
||||
res: res{
|
||||
changesLen: 3,
|
||||
changesLen: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no changes",
|
||||
args: args{
|
||||
existing: &LoginPolicy{AllowUsernamePassword: false, AllowRegister: false, AllowExternalIdp: false},
|
||||
new: &LoginPolicy{AllowUsernamePassword: false, AllowRegister: false, AllowExternalIdp: false},
|
||||
existing: &LoginPolicy{AllowUsernamePassword: false, AllowRegister: false, AllowExternalIdp: false, ForceMFA: false},
|
||||
new: &LoginPolicy{AllowUsernamePassword: false, AllowRegister: false, AllowExternalIdp: false, ForceMFA: false},
|
||||
},
|
||||
res: res{
|
||||
changesLen: 0,
|
||||
@@ -66,10 +66,10 @@ func TestAppendAddLoginPolicyEvent(t *testing.T) {
|
||||
name: "append add login policy event",
|
||||
args: args{
|
||||
iam: new(IAM),
|
||||
policy: &LoginPolicy{AllowUsernamePassword: true, AllowRegister: true, AllowExternalIdp: true},
|
||||
policy: &LoginPolicy{AllowUsernamePassword: true, AllowRegister: true, AllowExternalIdp: true, ForceMFA: true},
|
||||
event: new(es_models.Event),
|
||||
},
|
||||
result: &IAM{DefaultLoginPolicy: &LoginPolicy{AllowUsernamePassword: true, AllowRegister: true, AllowExternalIdp: true}},
|
||||
result: &IAM{DefaultLoginPolicy: &LoginPolicy{AllowUsernamePassword: true, AllowRegister: true, AllowExternalIdp: true, ForceMFA: true}},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@@ -88,6 +88,9 @@ func TestAppendAddLoginPolicyEvent(t *testing.T) {
|
||||
if tt.result.DefaultLoginPolicy.AllowExternalIdp != tt.args.iam.DefaultLoginPolicy.AllowExternalIdp {
|
||||
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.DefaultLoginPolicy.AllowExternalIdp, tt.args.iam.DefaultLoginPolicy.AllowExternalIdp)
|
||||
}
|
||||
if tt.result.DefaultLoginPolicy.ForceMFA != tt.args.iam.DefaultLoginPolicy.ForceMFA {
|
||||
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.DefaultLoginPolicy.ForceMFA, tt.args.iam.DefaultLoginPolicy.ForceMFA)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -110,14 +113,16 @@ func TestAppendChangeLoginPolicyEvent(t *testing.T) {
|
||||
AllowExternalIdp: false,
|
||||
AllowRegister: false,
|
||||
AllowUsernamePassword: false,
|
||||
ForceMFA: false,
|
||||
}},
|
||||
policy: &LoginPolicy{AllowUsernamePassword: true, AllowRegister: true, AllowExternalIdp: true},
|
||||
policy: &LoginPolicy{AllowUsernamePassword: true, AllowRegister: true, AllowExternalIdp: true, ForceMFA: true},
|
||||
event: &es_models.Event{},
|
||||
},
|
||||
result: &IAM{DefaultLoginPolicy: &LoginPolicy{
|
||||
AllowExternalIdp: true,
|
||||
AllowRegister: true,
|
||||
AllowUsernamePassword: true,
|
||||
ForceMFA: true,
|
||||
}},
|
||||
},
|
||||
}
|
||||
@@ -137,6 +142,9 @@ func TestAppendChangeLoginPolicyEvent(t *testing.T) {
|
||||
if tt.result.DefaultLoginPolicy.AllowExternalIdp != tt.args.iam.DefaultLoginPolicy.AllowExternalIdp {
|
||||
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.DefaultLoginPolicy.AllowExternalIdp, tt.args.iam.DefaultLoginPolicy.AllowExternalIdp)
|
||||
}
|
||||
if tt.result.DefaultLoginPolicy.ForceMFA != tt.args.iam.DefaultLoginPolicy.ForceMFA {
|
||||
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.DefaultLoginPolicy.ForceMFA, tt.args.iam.DefaultLoginPolicy.ForceMFA)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -197,7 +205,7 @@ func TestAppendAddIdpToPolicyEvent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveAddIdpToPolicyEvent(t *testing.T) {
|
||||
func TestRemoveIdpToPolicyEvent(t *testing.T) {
|
||||
type args struct {
|
||||
iam *IAM
|
||||
provider *IDPProvider
|
||||
@@ -251,3 +259,171 @@ func TestRemoveAddIdpToPolicyEvent(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendAddSecondFactorToPolicyEvent(t *testing.T) {
|
||||
type args struct {
|
||||
iam *IAM
|
||||
mfa *MFA
|
||||
event *es_models.Event
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
result *IAM
|
||||
}{
|
||||
{
|
||||
name: "append add second factor to login policy event",
|
||||
args: args{
|
||||
iam: &IAM{DefaultLoginPolicy: &LoginPolicy{AllowExternalIdp: true, AllowRegister: true, AllowUsernamePassword: true}},
|
||||
mfa: &MFA{MfaType: int32(model.SecondFactorTypeOTP)},
|
||||
event: &es_models.Event{},
|
||||
},
|
||||
result: &IAM{DefaultLoginPolicy: &LoginPolicy{
|
||||
SecondFactors: []int32{
|
||||
int32(model.SecondFactorTypeOTP),
|
||||
}}},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.args.mfa != nil {
|
||||
data, _ := json.Marshal(tt.args.mfa)
|
||||
tt.args.event.Data = data
|
||||
}
|
||||
tt.args.iam.appendAddSecondFactorToLoginPolicyEvent(tt.args.event)
|
||||
if len(tt.result.DefaultLoginPolicy.SecondFactors) != len(tt.args.iam.DefaultLoginPolicy.SecondFactors) {
|
||||
t.Errorf("got wrong second factors len: expected: %v, actual: %v ", len(tt.result.DefaultLoginPolicy.SecondFactors), len(tt.args.iam.DefaultLoginPolicy.SecondFactors))
|
||||
}
|
||||
if tt.result.DefaultLoginPolicy.SecondFactors[0] != tt.args.mfa.MfaType {
|
||||
t.Errorf("got wrong second factor: expected: %v, actual: %v ", tt.result.DefaultLoginPolicy.SecondFactors[0], tt.args.mfa)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveSecondFactorToPolicyEvent(t *testing.T) {
|
||||
type args struct {
|
||||
iam *IAM
|
||||
mfa *MFA
|
||||
event *es_models.Event
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
result *IAM
|
||||
}{
|
||||
{
|
||||
name: "append remove second factor to login policy event",
|
||||
args: args{
|
||||
iam: &IAM{
|
||||
DefaultLoginPolicy: &LoginPolicy{
|
||||
SecondFactors: []int32{
|
||||
int32(model.SecondFactorTypeOTP),
|
||||
}}},
|
||||
mfa: &MFA{MfaType: int32(model.SecondFactorTypeOTP)},
|
||||
event: &es_models.Event{},
|
||||
},
|
||||
result: &IAM{DefaultLoginPolicy: &LoginPolicy{
|
||||
AllowExternalIdp: true,
|
||||
AllowRegister: true,
|
||||
AllowUsernamePassword: true,
|
||||
SecondFactors: []int32{}}},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.args.mfa != nil {
|
||||
data, _ := json.Marshal(tt.args.mfa)
|
||||
tt.args.event.Data = data
|
||||
}
|
||||
tt.args.iam.appendRemoveSecondFactorFromLoginPolicyEvent(tt.args.event)
|
||||
if len(tt.result.DefaultLoginPolicy.SecondFactors) != len(tt.args.iam.DefaultLoginPolicy.SecondFactors) {
|
||||
t.Errorf("got wrong second factor len: expected: %v, actual: %v ", len(tt.result.DefaultLoginPolicy.SecondFactors), len(tt.args.iam.DefaultLoginPolicy.SecondFactors))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendAddMultiFactorToPolicyEvent(t *testing.T) {
|
||||
type args struct {
|
||||
iam *IAM
|
||||
mfa *MFA
|
||||
event *es_models.Event
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
result *IAM
|
||||
}{
|
||||
{
|
||||
name: "append add mfa to login policy event",
|
||||
args: args{
|
||||
iam: &IAM{DefaultLoginPolicy: &LoginPolicy{AllowExternalIdp: true, AllowRegister: true, AllowUsernamePassword: true}},
|
||||
mfa: &MFA{MfaType: int32(model.MultiFactorTypeU2FWithPIN)},
|
||||
event: &es_models.Event{},
|
||||
},
|
||||
result: &IAM{DefaultLoginPolicy: &LoginPolicy{
|
||||
MultiFactors: []int32{
|
||||
int32(model.MultiFactorTypeU2FWithPIN),
|
||||
}}},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.args.mfa != nil {
|
||||
data, _ := json.Marshal(tt.args.mfa)
|
||||
tt.args.event.Data = data
|
||||
}
|
||||
tt.args.iam.appendAddMultiFactorToLoginPolicyEvent(tt.args.event)
|
||||
if len(tt.result.DefaultLoginPolicy.MultiFactors) != len(tt.args.iam.DefaultLoginPolicy.MultiFactors) {
|
||||
t.Errorf("got wrong mfas len: expected: %v, actual: %v ", len(tt.result.DefaultLoginPolicy.MultiFactors), len(tt.args.iam.DefaultLoginPolicy.MultiFactors))
|
||||
}
|
||||
if tt.result.DefaultLoginPolicy.MultiFactors[0] != tt.args.mfa.MfaType {
|
||||
t.Errorf("got wrong mfa: expected: %v, actual: %v ", tt.result.DefaultLoginPolicy.MultiFactors[0], tt.args.mfa)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveMultiFactorToPolicyEvent(t *testing.T) {
|
||||
type args struct {
|
||||
iam *IAM
|
||||
mfa *MFA
|
||||
event *es_models.Event
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
result *IAM
|
||||
}{
|
||||
{
|
||||
name: "append remove mfa to login policy event",
|
||||
args: args{
|
||||
iam: &IAM{
|
||||
DefaultLoginPolicy: &LoginPolicy{
|
||||
MultiFactors: []int32{
|
||||
int32(model.MultiFactorTypeU2FWithPIN),
|
||||
}}},
|
||||
mfa: &MFA{MfaType: int32(model.MultiFactorTypeU2FWithPIN)},
|
||||
event: &es_models.Event{},
|
||||
},
|
||||
result: &IAM{DefaultLoginPolicy: &LoginPolicy{
|
||||
AllowExternalIdp: true,
|
||||
AllowRegister: true,
|
||||
AllowUsernamePassword: true,
|
||||
MultiFactors: []int32{}}},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.args.mfa != nil {
|
||||
data, _ := json.Marshal(tt.args.mfa)
|
||||
tt.args.event.Data = data
|
||||
}
|
||||
tt.args.iam.appendRemoveMultiFactorFromLoginPolicyEvent(tt.args.event)
|
||||
if len(tt.result.DefaultLoginPolicy.MultiFactors) != len(tt.args.iam.DefaultLoginPolicy.MultiFactors) {
|
||||
t.Errorf("got wrong mfa len: expected: %v, actual: %v ", len(tt.result.DefaultLoginPolicy.MultiFactors), len(tt.args.iam.DefaultLoginPolicy.MultiFactors))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -30,8 +30,13 @@ const (
|
||||
LoginPolicyIDPProviderAdded models.EventType = "iam.policy.login.idpprovider.added"
|
||||
LoginPolicyIDPProviderRemoved models.EventType = "iam.policy.login.idpprovider.removed"
|
||||
LoginPolicyIDPProviderCascadeRemoved models.EventType = "iam.policy.login.idpprovider.cascade.removed"
|
||||
LabelPolicyAdded models.EventType = "iam.policy.label.added"
|
||||
LabelPolicyChanged models.EventType = "iam.policy.label.changed"
|
||||
LoginPolicySecondFactorAdded models.EventType = "iam.policy.login.secondfactor.added"
|
||||
LoginPolicySecondFactorRemoved models.EventType = "iam.policy.login.secondfactor.removed"
|
||||
LoginPolicyMultiFactorAdded models.EventType = "iam.policy.login.multifactor.added"
|
||||
LoginPolicyMultiFactorRemoved models.EventType = "iam.policy.login.multifactor.removed"
|
||||
|
||||
LabelPolicyAdded models.EventType = "iam.policy.label.added"
|
||||
LabelPolicyChanged models.EventType = "iam.policy.label.changed"
|
||||
|
||||
PasswordComplexityPolicyAdded models.EventType = "iam.policy.password.complexity.added"
|
||||
PasswordComplexityPolicyChanged models.EventType = "iam.policy.password.complexity.changed"
|
||||
|
@@ -3,6 +3,7 @@ package model
|
||||
import (
|
||||
"encoding/json"
|
||||
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||
"github.com/lib/pq"
|
||||
"time"
|
||||
|
||||
es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
||||
@@ -23,10 +24,13 @@ type LoginPolicyView struct {
|
||||
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
|
||||
State int32 `json:"-" gorm:"column:login_policy_state"`
|
||||
|
||||
AllowRegister bool `json:"allowRegister" gorm:"column:allow_register"`
|
||||
AllowUsernamePassword bool `json:"allowUsernamePassword" gorm:"column:allow_username_password"`
|
||||
AllowExternalIDP bool `json:"allowExternalIdp" gorm:"column:allow_external_idp"`
|
||||
Default bool `json:"-" gorm:"-"`
|
||||
AllowRegister bool `json:"allowRegister" gorm:"column:allow_register"`
|
||||
AllowUsernamePassword bool `json:"allowUsernamePassword" gorm:"column:allow_username_password"`
|
||||
AllowExternalIDP bool `json:"allowExternalIdp" gorm:"column:allow_external_idp"`
|
||||
ForceMFA bool `json:"forceMFA" gorm:"column:force_mfa"`
|
||||
SecondFactors pq.Int64Array `json:"-" gorm:"column:second_factors"`
|
||||
MultiFactors pq.Int64Array `json:"-" gorm:"column:multi_factors"`
|
||||
Default bool `json:"-" gorm:"-"`
|
||||
|
||||
Sequence uint64 `json:"-" gorm:"column:sequence"`
|
||||
}
|
||||
@@ -40,10 +44,29 @@ func LoginPolicyViewFromModel(policy *model.LoginPolicyView) *LoginPolicyView {
|
||||
AllowRegister: policy.AllowRegister,
|
||||
AllowExternalIDP: policy.AllowExternalIDP,
|
||||
AllowUsernamePassword: policy.AllowUsernamePassword,
|
||||
ForceMFA: policy.ForceMFA,
|
||||
SecondFactors: secondFactorsFromModel(policy.SecondFactors),
|
||||
MultiFactors: multiFactorsFromModel(policy.MultiFactors),
|
||||
Default: policy.Default,
|
||||
}
|
||||
}
|
||||
|
||||
func secondFactorsFromModel(mfas []model.SecondFactorType) []int64 {
|
||||
convertedMFAs := make([]int64, len(mfas))
|
||||
for i, m := range mfas {
|
||||
convertedMFAs[i] = int64(m)
|
||||
}
|
||||
return convertedMFAs
|
||||
}
|
||||
|
||||
func multiFactorsFromModel(mfas []model.MultiFactorType) []int64 {
|
||||
convertedMFAs := make([]int64, len(mfas))
|
||||
for i, m := range mfas {
|
||||
convertedMFAs[i] = int64(m)
|
||||
}
|
||||
return convertedMFAs
|
||||
}
|
||||
|
||||
func LoginPolicyViewToModel(policy *LoginPolicyView) *model.LoginPolicyView {
|
||||
return &model.LoginPolicyView{
|
||||
AggregateID: policy.AggregateID,
|
||||
@@ -53,20 +76,57 @@ func LoginPolicyViewToModel(policy *LoginPolicyView) *model.LoginPolicyView {
|
||||
AllowRegister: policy.AllowRegister,
|
||||
AllowExternalIDP: policy.AllowExternalIDP,
|
||||
AllowUsernamePassword: policy.AllowUsernamePassword,
|
||||
ForceMFA: policy.ForceMFA,
|
||||
SecondFactors: secondFactorsToModel(policy.SecondFactors),
|
||||
MultiFactors: multiFactorsToToModel(policy.MultiFactors),
|
||||
Default: policy.Default,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *LoginPolicyView) AppendEvent(event *models.Event) (err error) {
|
||||
i.Sequence = event.Sequence
|
||||
i.ChangeDate = event.CreationDate
|
||||
func secondFactorsToModel(mfas []int64) []model.SecondFactorType {
|
||||
convertedMFAs := make([]model.SecondFactorType, len(mfas))
|
||||
for i, m := range mfas {
|
||||
convertedMFAs[i] = model.SecondFactorType(m)
|
||||
}
|
||||
return convertedMFAs
|
||||
}
|
||||
|
||||
func multiFactorsToToModel(mfas []int64) []model.MultiFactorType {
|
||||
convertedMFAs := make([]model.MultiFactorType, len(mfas))
|
||||
for i, m := range mfas {
|
||||
convertedMFAs[i] = model.MultiFactorType(m)
|
||||
}
|
||||
return convertedMFAs
|
||||
}
|
||||
|
||||
func (p *LoginPolicyView) AppendEvent(event *models.Event) (err error) {
|
||||
p.Sequence = event.Sequence
|
||||
p.ChangeDate = event.CreationDate
|
||||
switch event.Type {
|
||||
case es_model.LoginPolicyAdded, org_es_model.LoginPolicyAdded:
|
||||
i.setRootData(event)
|
||||
i.CreationDate = event.CreationDate
|
||||
err = i.SetData(event)
|
||||
p.setRootData(event)
|
||||
p.CreationDate = event.CreationDate
|
||||
err = p.SetData(event)
|
||||
case es_model.LoginPolicyChanged, org_es_model.LoginPolicyChanged:
|
||||
err = i.SetData(event)
|
||||
err = p.SetData(event)
|
||||
case es_model.LoginPolicySecondFactorAdded, org_es_model.LoginPolicySecondFactorAdded:
|
||||
mfa := new(es_model.MFA)
|
||||
err := mfa.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.SecondFactors = append(p.SecondFactors, int64(mfa.MfaType))
|
||||
case es_model.LoginPolicySecondFactorRemoved, org_es_model.LoginPolicySecondFactorRemoved:
|
||||
err = p.removeSecondFactor(event)
|
||||
case es_model.LoginPolicyMultiFactorAdded, org_es_model.LoginPolicyMultiFactorAdded:
|
||||
mfa := new(es_model.MFA)
|
||||
err := mfa.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.MultiFactors = append(p.MultiFactors, int64(mfa.MfaType))
|
||||
case es_model.LoginPolicyMultiFactorRemoved, org_es_model.LoginPolicyMultiFactorRemoved:
|
||||
err = p.removeMultiFactor(event)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -82,3 +142,37 @@ func (r *LoginPolicyView) SetData(event *models.Event) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *LoginPolicyView) removeSecondFactor(event *models.Event) error {
|
||||
mfa := new(es_model.MFA)
|
||||
err := mfa.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := len(p.SecondFactors) - 1; i >= 0; i-- {
|
||||
if p.SecondFactors[i] == int64(mfa.MfaType) {
|
||||
copy(p.SecondFactors[i:], p.SecondFactors[i+1:])
|
||||
p.SecondFactors[len(p.SecondFactors)-1] = 0
|
||||
p.SecondFactors = p.SecondFactors[:len(p.SecondFactors)-1]
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *LoginPolicyView) removeMultiFactor(event *models.Event) error {
|
||||
mfa := new(es_model.MFA)
|
||||
err := mfa.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := len(p.MultiFactors) - 1; i >= 0; i-- {
|
||||
if p.MultiFactors[i] == int64(mfa.MfaType) {
|
||||
copy(p.MultiFactors[i:], p.MultiFactors[i+1:])
|
||||
p.MultiFactors[len(p.MultiFactors)-1] = 0
|
||||
p.MultiFactors = p.MultiFactors[:len(p.MultiFactors)-1]
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user