diff --git a/internal/admin/repository/eventsourcing/eventstore/iam.go b/internal/admin/repository/eventsourcing/eventstore/iam.go index 417fb0d0d8..c2bcc7996f 100644 --- a/internal/admin/repository/eventsourcing/eventstore/iam.go +++ b/internal/admin/repository/eventsourcing/eventstore/iam.go @@ -17,6 +17,7 @@ import ( "github.com/caos/zitadel/internal/eventstore/v1/models" "github.com/caos/zitadel/internal/i18n" iam_view "github.com/caos/zitadel/internal/iam/repository/view" + "github.com/caos/zitadel/internal/query" "github.com/caos/zitadel/internal/user/repository/view/model" caos_errs "github.com/caos/zitadel/internal/errors" @@ -32,6 +33,7 @@ import ( ) type IAMRepository struct { + Query *query.Queries Eventstore v1.Eventstore SearchLimit uint64 View *admin_view.View @@ -116,23 +118,6 @@ func (repo *IAMRepository) ExternalIDPsByIDPConfigID(ctx context.Context, idpCon return model.ExternalIDPViewsToModel(externalIDPs), nil } -func (repo *IAMRepository) ExternalIDPsByIDPConfigIDFromDefaultPolicy(ctx context.Context, idpConfigID string) ([]*usr_model.ExternalIDPView, error) { - policies, err := repo.View.AllDefaultLoginPolicies() - if err != nil { - return nil, err - } - resourceOwners := make([]string, len(policies)) - for i, policy := range policies { - resourceOwners[i] = policy.AggregateID - } - - externalIDPs, err := repo.View.ExternalIDPsByIDPConfigIDAndResourceOwners(idpConfigID, resourceOwners) - if err != nil { - return nil, err - } - return model.ExternalIDPViewsToModel(externalIDPs), nil -} - func (repo *IAMRepository) SearchIDPConfigs(ctx context.Context, request *iam_model.IDPConfigSearchRequest) (*iam_model.IDPConfigSearchResponse, error) { err := request.EnsureLimit(repo.SearchLimit) if err != nil { @@ -157,32 +142,6 @@ func (repo *IAMRepository) SearchIDPConfigs(ctx context.Context, request *iam_mo return result, nil } -func (repo *IAMRepository) GetDefaultLoginPolicy(ctx context.Context) (*iam_model.LoginPolicyView, error) { - policy, viewErr := repo.View.LoginPolicyByAggregateID(repo.SystemDefaults.IamID) - if viewErr != nil && !caos_errs.IsNotFound(viewErr) { - return nil, viewErr - } - if caos_errs.IsNotFound(viewErr) { - policy = new(iam_es_model.LoginPolicyView) - } - - events, esErr := repo.getIAMEvents(ctx, policy.Sequence) - if caos_errs.IsNotFound(viewErr) && len(events) == 0 { - return nil, caos_errs.ThrowNotFound(nil, "EVENT-cmO9s", "Errors.IAM.LoginPolicy.NotFound") - } - if esErr != nil { - logging.Log("EVENT-2Mi8s").WithError(esErr).Debug("error retrieving new events") - return iam_es_model.LoginPolicyViewToModel(policy), nil - } - policyCopy := *policy - for _, event := range events { - if err := policyCopy.AppendEvent(event); err != nil { - return iam_es_model.LoginPolicyViewToModel(policy), nil - } - } - return iam_es_model.LoginPolicyViewToModel(policy), nil -} - func (repo *IAMRepository) SearchDefaultIDPProviders(ctx context.Context, request *iam_model.IDPProviderSearchRequest) (*iam_model.IDPProviderSearchResponse, error) { err := request.EnsureLimit(repo.SearchLimit) if err != nil { @@ -208,28 +167,6 @@ func (repo *IAMRepository) SearchDefaultIDPProviders(ctx context.Context, reques return result, nil } -func (repo *IAMRepository) SearchDefaultSecondFactors(ctx context.Context) (*iam_model.SecondFactorsSearchResponse, error) { - policy, err := repo.GetDefaultLoginPolicy(ctx) - if err != nil { - return nil, err - } - return &iam_model.SecondFactorsSearchResponse{ - TotalResult: uint64(len(policy.SecondFactors)), - Result: policy.SecondFactors, - }, nil -} - -func (repo *IAMRepository) SearchDefaultMultiFactors(ctx context.Context) (*iam_model.MultiFactorsSearchResponse, error) { - policy, err := repo.GetDefaultLoginPolicy(ctx) - if err != nil { - return nil, err - } - return &iam_model.MultiFactorsSearchResponse{ - TotalResult: uint64(len(policy.MultiFactors)), - Result: policy.MultiFactors, - }, nil -} - func (repo *IAMRepository) GetDefaultPasswordComplexityPolicy(ctx context.Context) (*iam_model.PasswordComplexityPolicyView, error) { policy, viewErr := repo.View.PasswordComplexityPolicyByAggregateID(repo.SystemDefaults.IamID) if viewErr != nil && !caos_errs.IsNotFound(viewErr) { @@ -377,7 +314,6 @@ func (repo *IAMRepository) SearchIAMMembersx(ctx context.Context, request *iam_m } if err == nil { result.Sequence = sequence.CurrentSequence - result.Timestamp = result.Timestamp } return result, nil } diff --git a/internal/admin/repository/eventsourcing/handler/handler.go b/internal/admin/repository/eventsourcing/handler/handler.go index 6d21bb66b3..0a504fe511 100644 --- a/internal/admin/repository/eventsourcing/handler/handler.go +++ b/internal/admin/repository/eventsourcing/handler/handler.go @@ -39,8 +39,6 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es handler{view, bulkLimit, configs.cycleDuration("IDPConfig"), errorCount, es}), newLabelPolicy( handler{view, bulkLimit, configs.cycleDuration("LabelPolicy"), errorCount, es}), - newLoginPolicy( - handler{view, bulkLimit, configs.cycleDuration("LoginPolicy"), errorCount, es}), newIDPProvider( handler{view, bulkLimit, configs.cycleDuration("IDPProvider"), errorCount, es}, defaults), diff --git a/internal/admin/repository/eventsourcing/handler/login_policy.go b/internal/admin/repository/eventsourcing/handler/login_policy.go deleted file mode 100644 index 0c1b818be8..0000000000 --- a/internal/admin/repository/eventsourcing/handler/login_policy.go +++ /dev/null @@ -1,177 +0,0 @@ -package handler - -import ( - "context" - - "github.com/caos/logging" - - "github.com/caos/zitadel/internal/domain" - caos_errs "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/v1" - es_models "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/eventstore/v1/query" - "github.com/caos/zitadel/internal/eventstore/v1/spooler" - "github.com/caos/zitadel/internal/iam/repository/eventsourcing" - iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" - iam_model "github.com/caos/zitadel/internal/iam/repository/view/model" - "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" -) - -const ( - loginPolicyTable = "adminapi.login_policies" -) - -type LoginPolicy struct { - handler - subscription *v1.Subscription -} - -func newLoginPolicy(handler handler) *LoginPolicy { - h := &LoginPolicy{ - handler: handler, - } - - h.subscribe() - - return h -} - -func (p *LoginPolicy) subscribe() { - p.subscription = p.es.Subscribe(p.AggregateTypes()...) - go func() { - for event := range p.subscription.Events { - query.ReduceEvent(p, event) - } - }() -} - -func (p *LoginPolicy) ViewModel() string { - return loginPolicyTable -} - -func (p *LoginPolicy) Subscription() *v1.Subscription { - return p.subscription -} - -func (p *LoginPolicy) AggregateTypes() []es_models.AggregateType { - return []es_models.AggregateType{iam_es_model.IAMAggregate, model.OrgAggregate} -} - -func (p *LoginPolicy) EventQuery() (*es_models.SearchQuery, error) { - sequence, err := p.view.GetLatestLoginPolicySequence() - if err != nil { - return nil, err - } - return es_models.NewSearchQuery(). - AggregateTypeFilter(p.AggregateTypes()...). - LatestSequenceFilter(sequence.CurrentSequence), nil -} - -func (p *LoginPolicy) CurrentSequence() (uint64, error) { - sequence, err := p.view.GetLatestLoginPolicySequence() - if err != nil { - return 0, err - } - return sequence.CurrentSequence, nil -} - -func (p *LoginPolicy) Reduce(event *es_models.Event) (err error) { - switch event.AggregateType { - case model.OrgAggregate, iam_es_model.IAMAggregate: - err = p.processLoginPolicy(event) - } - return err -} - -func (p *LoginPolicy) processLoginPolicy(event *es_models.Event) (err error) { - policy := new(iam_model.LoginPolicyView) - switch event.Type { - case model.OrgAdded: - policy, err = p.getDefaultLoginPolicy() - if err != nil { - return err - } - policy.AggregateID = event.AggregateID - policy.Default = true - case iam_es_model.LoginPolicyAdded, model.LoginPolicyAdded: - err = policy.AppendEvent(event) - case iam_es_model.LoginPolicyChanged, - iam_es_model.LoginPolicySecondFactorAdded, - iam_es_model.LoginPolicySecondFactorRemoved, - iam_es_model.LoginPolicyMultiFactorAdded, - iam_es_model.LoginPolicyMultiFactorRemoved: - policies, err := p.view.AllDefaultLoginPolicies() - if err != nil { - return err - } - for _, policy := range policies { - err = policy.AppendEvent(event) - if err != nil { - return err - } - } - return p.view.PutLoginPolicies(policies, event) - case model.LoginPolicyChanged, - model.LoginPolicySecondFactorAdded, - model.LoginPolicySecondFactorRemoved, - model.LoginPolicyMultiFactorAdded, - model.LoginPolicyMultiFactorRemoved: - policy, err = p.view.LoginPolicyByAggregateID(event.AggregateID) - if err != nil { - return err - } - err = policy.AppendEvent(event) - case model.LoginPolicyRemoved: - policy, err = p.getDefaultLoginPolicy() - if err != nil { - return err - } - policy.AggregateID = event.AggregateID - policy.Default = true - default: - return p.view.ProcessedLoginPolicySequence(event) - } - if err != nil { - return err - } - return p.view.PutLoginPolicy(policy, event) -} - -func (p *LoginPolicy) OnError(event *es_models.Event, err error) error { - logging.LogWithFields("SPOOL-Wj8sf", "id", event.AggregateID).WithError(err).Warn("something went wrong in login policy handler") - return spooler.HandleError(event, err, p.view.GetLatestLoginPolicyFailedEvent, p.view.ProcessedLoginPolicyFailedEvent, p.view.ProcessedLoginPolicySequence, p.errorCountUntilSkip) -} - -func (p *LoginPolicy) OnSuccess() error { - return spooler.HandleSuccess(p.view.UpdateLoginPolicySpoolerRunTimestamp) -} - -func (p *LoginPolicy) getDefaultLoginPolicy() (*iam_model.LoginPolicyView, error) { - policy, policyErr := p.view.LoginPolicyByAggregateID(domain.IAMID) - if policyErr != nil && !caos_errs.IsNotFound(policyErr) { - return nil, policyErr - } - if policy == nil { - policy = &iam_model.LoginPolicyView{} - } - events, err := p.getIAMEvents(policy.Sequence) - if err != nil { - return policy, policyErr - } - policyCopy := *policy - for _, event := range events { - if err := policyCopy.AppendEvent(event); err != nil { - return policy, nil - } - } - return &policyCopy, nil -} - -func (p *LoginPolicy) getIAMEvents(sequence uint64) ([]*es_models.Event, error) { - query, err := eventsourcing.IAMByIDQuery(domain.IAMID, sequence) - if err != nil { - return nil, err - } - - return p.es.FilterEvents(context.Background(), query) -} diff --git a/internal/admin/repository/eventsourcing/view/login_policies.go b/internal/admin/repository/eventsourcing/view/login_policies.go deleted file mode 100644 index 257bbddf3e..0000000000 --- a/internal/admin/repository/eventsourcing/view/login_policies.go +++ /dev/null @@ -1,65 +0,0 @@ -package view - -import ( - "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/iam/repository/view" - "github.com/caos/zitadel/internal/iam/repository/view/model" - global_view "github.com/caos/zitadel/internal/view/repository" -) - -const ( - loginPolicyTable = "adminapi.login_policies" -) - -func (v *View) AllDefaultLoginPolicies() ([]*model.LoginPolicyView, error) { - return view.GetDefaultLoginPolicies(v.Db, loginPolicyTable) -} - -func (v *View) LoginPolicyByAggregateID(aggregateID string) (*model.LoginPolicyView, error) { - return view.GetLoginPolicyByAggregateID(v.Db, loginPolicyTable, aggregateID) -} - -func (v *View) PutLoginPolicy(policy *model.LoginPolicyView, event *models.Event) error { - err := view.PutLoginPolicy(v.Db, loginPolicyTable, policy) - if err != nil { - return err - } - return v.ProcessedLoginPolicySequence(event) -} - -func (v *View) PutLoginPolicies(policies []*model.LoginPolicyView, event *models.Event) error { - err := view.PutLoginPolicies(v.Db, loginPolicyTable, policies...) - if err != nil { - return err - } - return v.ProcessedLoginPolicySequence(event) -} - -func (v *View) DeleteLoginPolicy(aggregateID string, event *models.Event) error { - err := view.DeleteLoginPolicy(v.Db, loginPolicyTable, aggregateID) - if err != nil && !errors.IsNotFound(err) { - return err - } - return v.ProcessedLoginPolicySequence(event) -} - -func (v *View) GetLatestLoginPolicySequence() (*global_view.CurrentSequence, error) { - return v.latestSequence(loginPolicyTable) -} - -func (v *View) ProcessedLoginPolicySequence(event *models.Event) error { - return v.saveCurrentSequence(loginPolicyTable, event) -} - -func (v *View) UpdateLoginPolicySpoolerRunTimestamp() error { - return v.updateSpoolerRunSequence(loginPolicyTable) -} - -func (v *View) GetLatestLoginPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { - return v.latestFailedEvent(loginPolicyTable, sequence) -} - -func (v *View) ProcessedLoginPolicyFailedEvent(failedEvent *global_view.FailedEvent) error { - return v.saveFailedEvent(failedEvent) -} diff --git a/internal/admin/repository/iam.go b/internal/admin/repository/iam.go index 22f1ed4b13..fb85d25b52 100644 --- a/internal/admin/repository/iam.go +++ b/internal/admin/repository/iam.go @@ -20,14 +20,10 @@ type IAMRepository interface { SearchIDPConfigs(ctx context.Context, request *iam_model.IDPConfigSearchRequest) (*iam_model.IDPConfigSearchResponse, error) - GetDefaultLoginPolicy(ctx context.Context) (*iam_model.LoginPolicyView, error) SearchDefaultIDPProviders(ctx context.Context, request *iam_model.IDPProviderSearchRequest) (*iam_model.IDPProviderSearchResponse, error) - SearchDefaultSecondFactors(ctx context.Context) (*iam_model.SecondFactorsSearchResponse, error) - SearchDefaultMultiFactors(ctx context.Context) (*iam_model.MultiFactorsSearchResponse, error) IDPProvidersByIDPConfigID(ctx context.Context, idpConfigID string) ([]*iam_model.IDPProviderView, error) ExternalIDPsByIDPConfigID(ctx context.Context, idpConfigID string) ([]*usr_model.ExternalIDPView, error) - ExternalIDPsByIDPConfigIDFromDefaultPolicy(ctx context.Context, idpConfigID string) ([]*usr_model.ExternalIDPView, error) GetDefaultLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error) GetDefaultPreviewLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error) diff --git a/internal/api/grpc/admin/login_policy.go b/internal/api/grpc/admin/login_policy.go index 816b2e3218..9d731ce789 100644 --- a/internal/api/grpc/admin/login_policy.go +++ b/internal/api/grpc/admin/login_policy.go @@ -2,19 +2,18 @@ package admin import ( "context" + "github.com/caos/zitadel/internal/api/grpc/user" - "time" "github.com/caos/zitadel/internal/api/grpc/idp" "github.com/caos/zitadel/internal/api/grpc/object" - "github.com/caos/zitadel/internal/api/grpc/policy" policy_grpc "github.com/caos/zitadel/internal/api/grpc/policy" "github.com/caos/zitadel/internal/domain" admin_pb "github.com/caos/zitadel/pkg/grpc/admin" ) func (s *Server) GetLoginPolicy(ctx context.Context, _ *admin_pb.GetLoginPolicyRequest) (*admin_pb.GetLoginPolicyResponse, error) { - policy, err := s.iam.GetDefaultLoginPolicy(ctx) + policy, err := s.query.DefaultLoginPolicy(ctx) if err != nil { return nil, err } @@ -75,19 +74,18 @@ func (s *Server) RemoveIDPFromLoginPolicy(ctx context.Context, req *admin_pb.Rem } func (s *Server) ListLoginPolicySecondFactors(ctx context.Context, req *admin_pb.ListLoginPolicySecondFactorsRequest) (*admin_pb.ListLoginPolicySecondFactorsResponse, error) { - result, err := s.iam.SearchDefaultSecondFactors(ctx) + result, err := s.query.DefaultSecondFactors(ctx) if err != nil { return nil, err } return &admin_pb.ListLoginPolicySecondFactorsResponse{ - //TODO: missing values from res - Details: object.ToListDetails(result.TotalResult, 0, time.Time{}), - Result: policy.ModelSecondFactorTypesToPb(result.Result), + Details: object.ToListDetails(result.Count, result.Sequence, result.Timestamp), + Result: policy_grpc.ModelSecondFactorTypesToPb(result.Factors), }, nil } func (s *Server) AddSecondFactorToLoginPolicy(ctx context.Context, req *admin_pb.AddSecondFactorToLoginPolicyRequest) (*admin_pb.AddSecondFactorToLoginPolicyResponse, error) { - _, objectDetails, err := s.command.AddSecondFactorToDefaultLoginPolicy(ctx, policy.SecondFactorTypeToDomain(req.Type)) + _, objectDetails, err := s.command.AddSecondFactorToDefaultLoginPolicy(ctx, policy_grpc.SecondFactorTypeToDomain(req.Type)) if err != nil { return nil, err } @@ -97,7 +95,7 @@ func (s *Server) AddSecondFactorToLoginPolicy(ctx context.Context, req *admin_pb } func (s *Server) RemoveSecondFactorFromLoginPolicy(ctx context.Context, req *admin_pb.RemoveSecondFactorFromLoginPolicyRequest) (*admin_pb.RemoveSecondFactorFromLoginPolicyResponse, error) { - objectDetails, err := s.command.RemoveSecondFactorFromDefaultLoginPolicy(ctx, policy.SecondFactorTypeToDomain(req.Type)) + objectDetails, err := s.command.RemoveSecondFactorFromDefaultLoginPolicy(ctx, policy_grpc.SecondFactorTypeToDomain(req.Type)) if err != nil { return nil, err } @@ -107,14 +105,13 @@ func (s *Server) RemoveSecondFactorFromLoginPolicy(ctx context.Context, req *adm } func (s *Server) ListLoginPolicyMultiFactors(ctx context.Context, req *admin_pb.ListLoginPolicyMultiFactorsRequest) (*admin_pb.ListLoginPolicyMultiFactorsResponse, error) { - res, err := s.iam.SearchDefaultMultiFactors(ctx) + res, err := s.query.DefaultMultiFactors(ctx) if err != nil { return nil, err } return &admin_pb.ListLoginPolicyMultiFactorsResponse{ - //TODO: additional values - Details: object.ToListDetails(res.TotalResult, 0, time.Time{}), - Result: policy.ModelMultiFactorTypesToPb(res.Result), + Details: object.ToListDetails(res.Count, res.Sequence, res.Timestamp), + Result: policy_grpc.ModelMultiFactorTypesToPb(res.Factors), }, nil } @@ -129,7 +126,7 @@ func (s *Server) AddMultiFactorToLoginPolicy(ctx context.Context, req *admin_pb. } func (s *Server) RemoveMultiFactorFromLoginPolicy(ctx context.Context, req *admin_pb.RemoveMultiFactorFromLoginPolicyRequest) (*admin_pb.RemoveMultiFactorFromLoginPolicyResponse, error) { - objectDetails, err := s.command.RemoveMultiFactorFromDefaultLoginPolicy(ctx, policy.MultiFactorTypeToDomain(req.Type)) + objectDetails, err := s.command.RemoveMultiFactorFromDefaultLoginPolicy(ctx, policy_grpc.MultiFactorTypeToDomain(req.Type)) if err != nil { return nil, err } diff --git a/internal/api/grpc/management/policy_login.go b/internal/api/grpc/management/policy_login.go index 7d0fa766f8..76b54ea001 100644 --- a/internal/api/grpc/management/policy_login.go +++ b/internal/api/grpc/management/policy_login.go @@ -2,27 +2,27 @@ package management import ( "context" + "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/grpc/idp" "github.com/caos/zitadel/internal/api/grpc/object" policy_grpc "github.com/caos/zitadel/internal/api/grpc/policy" "github.com/caos/zitadel/internal/api/grpc/user" "github.com/caos/zitadel/internal/domain" - "time" mgmt_pb "github.com/caos/zitadel/pkg/grpc/management" ) func (s *Server) GetLoginPolicy(ctx context.Context, req *mgmt_pb.GetLoginPolicyRequest) (*mgmt_pb.GetLoginPolicyResponse, error) { - policy, err := s.org.GetLoginPolicy(ctx) + policy, err := s.query.LoginPolicyByID(ctx, "") if err != nil { return nil, err } - return &mgmt_pb.GetLoginPolicyResponse{Policy: policy_grpc.ModelLoginPolicyToPb(policy), IsDefault: policy.Default}, nil + return &mgmt_pb.GetLoginPolicyResponse{Policy: policy_grpc.ModelLoginPolicyToPb(policy), IsDefault: policy.IsDefault}, nil } func (s *Server) GetDefaultLoginPolicy(ctx context.Context, req *mgmt_pb.GetDefaultLoginPolicyRequest) (*mgmt_pb.GetDefaultLoginPolicyResponse, error) { - policy, err := s.org.GetDefaultLoginPolicy(ctx) + policy, err := s.query.DefaultLoginPolicy(ctx) if err != nil { return nil, err } @@ -107,14 +107,13 @@ func (s *Server) RemoveIDPFromLoginPolicy(ctx context.Context, req *mgmt_pb.Remo } func (s *Server) ListLoginPolicySecondFactors(ctx context.Context, req *mgmt_pb.ListLoginPolicySecondFactorsRequest) (*mgmt_pb.ListLoginPolicySecondFactorsResponse, error) { - result, err := s.org.SearchSecondFactors(ctx) + result, err := s.query.SecondFactorsByID(ctx, authz.GetCtxData(ctx).OrgID) if err != nil { return nil, err } return &mgmt_pb.ListLoginPolicySecondFactorsResponse{ - //TODO: missing values from res - Details: object.ToListDetails(result.TotalResult, 0, time.Time{}), - Result: policy_grpc.ModelSecondFactorTypesToPb(result.Result), + Details: object.ToListDetails(result.Count, result.Sequence, result.Timestamp), + Result: policy_grpc.ModelSecondFactorTypesToPb(result.Factors), }, nil } @@ -139,14 +138,13 @@ func (s *Server) RemoveSecondFactorFromLoginPolicy(ctx context.Context, req *mgm } func (s *Server) ListLoginPolicyMultiFactors(ctx context.Context, req *mgmt_pb.ListLoginPolicyMultiFactorsRequest) (*mgmt_pb.ListLoginPolicyMultiFactorsResponse, error) { - res, err := s.org.SearchMultiFactors(ctx) + res, err := s.query.MultiFactorsByID(ctx, authz.GetCtxData(ctx).OrgID) if err != nil { return nil, err } return &mgmt_pb.ListLoginPolicyMultiFactorsResponse{ - //TODO: additional values - Details: object.ToListDetails(res.TotalResult, 0, time.Time{}), - Result: policy_grpc.ModelMultiFactorTypesToPb(res.Result), + Details: object.ToListDetails(res.Count, res.Sequence, res.Timestamp), + Result: policy_grpc.ModelMultiFactorTypesToPb(res.Factors), }, nil } diff --git a/internal/api/grpc/policy/login_policy.go b/internal/api/grpc/policy/login_policy.go index 48c57ffe1c..f40a090dba 100644 --- a/internal/api/grpc/policy/login_policy.go +++ b/internal/api/grpc/policy/login_policy.go @@ -2,19 +2,27 @@ package policy import ( "github.com/caos/zitadel/internal/domain" - "github.com/caos/zitadel/internal/iam/model" + "github.com/caos/zitadel/internal/query" + "github.com/caos/zitadel/pkg/grpc/object" policy_pb "github.com/caos/zitadel/pkg/grpc/policy" + timestamp_pb "google.golang.org/protobuf/types/known/timestamppb" ) -func ModelLoginPolicyToPb(policy *model.LoginPolicyView) *policy_pb.LoginPolicy { +func ModelLoginPolicyToPb(policy *query.LoginPolicy) *policy_pb.LoginPolicy { return &policy_pb.LoginPolicy{ - IsDefault: policy.Default, + IsDefault: policy.IsDefault, AllowUsernamePassword: policy.AllowUsernamePassword, AllowRegister: policy.AllowRegister, - AllowExternalIdp: policy.AllowExternalIDP, + AllowExternalIdp: policy.AllowExternalIDPs, ForceMfa: policy.ForceMFA, PasswordlessType: ModelPasswordlessTypeToPb(policy.PasswordlessType), HidePasswordReset: policy.HidePasswordReset, + Details: &object.ObjectDetails{ + Sequence: policy.Sequence, + CreationDate: timestamp_pb.New(policy.CreationDate), + ChangeDate: timestamp_pb.New(policy.ChangeDate), + ResourceOwner: policy.OrgID, + }, } } @@ -29,11 +37,11 @@ func PasswordlessTypeToDomain(passwordlessType policy_pb.PasswordlessType) domai } } -func ModelPasswordlessTypeToPb(passwordlessType model.PasswordlessType) policy_pb.PasswordlessType { +func ModelPasswordlessTypeToPb(passwordlessType domain.PasswordlessType) policy_pb.PasswordlessType { switch passwordlessType { - case model.PasswordlessTypeAllowed: + case domain.PasswordlessTypeAllowed: return policy_pb.PasswordlessType_PASSWORDLESS_TYPE_ALLOWED - case model.PasswordlessTypeNotAllowed: + case domain.PasswordlessTypeNotAllowed: return policy_pb.PasswordlessType_PASSWORDLESS_TYPE_NOT_ALLOWED default: return policy_pb.PasswordlessType_PASSWORDLESS_TYPE_NOT_ALLOWED diff --git a/internal/api/grpc/policy/second_factor.go b/internal/api/grpc/policy/second_factor.go index 830456e89f..0214cb2f36 100644 --- a/internal/api/grpc/policy/second_factor.go +++ b/internal/api/grpc/policy/second_factor.go @@ -2,7 +2,6 @@ package policy import ( "github.com/caos/zitadel/internal/domain" - "github.com/caos/zitadel/internal/iam/model" policy_pb "github.com/caos/zitadel/pkg/grpc/policy" ) @@ -17,7 +16,7 @@ func SecondFactorTypeToDomain(secondFactorType policy_pb.SecondFactorType) domai } } -func ModelSecondFactorTypesToPb(types []model.SecondFactorType) []policy_pb.SecondFactorType { +func ModelSecondFactorTypesToPb(types []domain.SecondFactorType) []policy_pb.SecondFactorType { t := make([]policy_pb.SecondFactorType, len(types)) for i, typ := range types { t[i] = ModelSecondFactorTypeToPb(typ) @@ -25,18 +24,18 @@ func ModelSecondFactorTypesToPb(types []model.SecondFactorType) []policy_pb.Seco return t } -func ModelSecondFactorTypeToPb(secondFactorType model.SecondFactorType) policy_pb.SecondFactorType { +func ModelSecondFactorTypeToPb(secondFactorType domain.SecondFactorType) policy_pb.SecondFactorType { switch secondFactorType { - case model.SecondFactorTypeOTP: + case domain.SecondFactorTypeOTP: return policy_pb.SecondFactorType_SECOND_FACTOR_TYPE_OTP - case model.SecondFactorTypeU2F: + case domain.SecondFactorTypeU2F: return policy_pb.SecondFactorType_SECOND_FACTOR_TYPE_U2F default: return policy_pb.SecondFactorType_SECOND_FACTOR_TYPE_UNSPECIFIED } } -func ModelMultiFactorTypesToPb(types []model.MultiFactorType) []policy_pb.MultiFactorType { +func ModelMultiFactorTypesToPb(types []domain.MultiFactorType) []policy_pb.MultiFactorType { t := make([]policy_pb.MultiFactorType, len(types)) for i, typ := range types { t[i] = ModelMultiFactorTypeToPb(typ) @@ -44,9 +43,9 @@ func ModelMultiFactorTypesToPb(types []model.MultiFactorType) []policy_pb.MultiF return t } -func ModelMultiFactorTypeToPb(typ model.MultiFactorType) policy_pb.MultiFactorType { +func ModelMultiFactorTypeToPb(typ domain.MultiFactorType) policy_pb.MultiFactorType { switch typ { - case model.MultiFactorTypeU2FWithPIN: + case domain.MultiFactorTypeU2FWithPIN: return policy_pb.MultiFactorType_MULTI_FACTOR_TYPE_U2F_WITH_VERIFICATION default: return policy_pb.MultiFactorType_MULTI_FACTOR_TYPE_UNSPECIFIED diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index 0429c394b1..d65ff01527 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -67,7 +67,7 @@ type userViewProvider interface { } type loginPolicyViewProvider interface { - LoginPolicyByAggregateID(string) (*iam_view_model.LoginPolicyView, error) + LoginPolicyByID(context.Context, string) (*query.LoginPolicy, error) } type lockoutPolicyViewProvider interface { @@ -474,21 +474,21 @@ func (repo *AuthRequestRepo) getAuthRequest(ctx context.Context, id, userAgentID return request, nil } -func (repo *AuthRequestRepo) getLoginPolicyAndIDPProviders(ctx context.Context, orgID string) (*domain.LoginPolicy, []*domain.IDPProvider, error) { - policy, err := repo.getLoginPolicy(ctx, orgID) +func (repo *AuthRequestRepo) getLoginPolicyAndIDPProviders(ctx context.Context, orgID string) (*query.LoginPolicy, []*domain.IDPProvider, error) { + policy, err := repo.LoginPolicyViewProvider.LoginPolicyByID(ctx, orgID) if err != nil { return nil, nil, err } - if !policy.AllowExternalIDP { - return policy.ToLoginPolicyDomain(), nil, nil + if !policy.AllowExternalIDPs { + return policy, nil, nil } - idpProviders, err := getLoginPolicyIDPProviders(repo.IDPProviderViewProvider, repo.IAMID, orgID, policy.Default) + idpProviders, err := getLoginPolicyIDPProviders(repo.IDPProviderViewProvider, repo.IAMID, orgID, policy.IsDefault) if err != nil { return nil, nil, err } providers := iam_model.IdpProviderViewsToDomain(idpProviders) - return policy.ToLoginPolicyDomain(), providers, nil + return policy, providers, nil } func (repo *AuthRequestRepo) fillPolicies(ctx context.Context, request *domain.AuthRequest) error { @@ -504,7 +504,7 @@ func (repo *AuthRequestRepo) fillPolicies(ctx context.Context, request *domain.A if err != nil { return err } - request.LoginPolicy = loginPolicy + request.LoginPolicy = queryLoginPolicyToDomain(loginPolicy) if idpProviders != nil { request.AllowedExternalIDPs = idpProviders } @@ -580,7 +580,7 @@ func (repo AuthRequestRepo) checkLoginPolicyWithResourceOwner(ctx context.Contex if err != nil { return err } - if len(request.LinkingUsers) != 0 && !loginPolicy.AllowExternalIDP { + if len(request.LinkingUsers) != 0 && !loginPolicy.AllowExternalIDPs { return errors.ThrowInvalidArgument(nil, "LOGIN-s9sio", "Errors.User.NotAllowedToLink") } if len(request.LinkingUsers) != 0 { @@ -589,11 +589,32 @@ func (repo AuthRequestRepo) checkLoginPolicyWithResourceOwner(ctx context.Contex return errors.ThrowInvalidArgument(nil, "LOGIN-Dj89o", "Errors.User.NotAllowedToLink") } } - request.LoginPolicy = loginPolicy + request.LoginPolicy = queryLoginPolicyToDomain(loginPolicy) request.AllowedExternalIDPs = idpProviders return nil } +func queryLoginPolicyToDomain(policy *query.LoginPolicy) *domain.LoginPolicy { + return &domain.LoginPolicy{ + ObjectRoot: es_models.ObjectRoot{ + AggregateID: policy.OrgID, + Sequence: policy.Sequence, + ResourceOwner: policy.OrgID, + CreationDate: policy.CreationDate, + ChangeDate: policy.ChangeDate, + }, + Default: policy.IsDefault, + AllowUsernamePassword: policy.AllowUsernamePassword, + AllowRegister: policy.AllowRegister, + AllowExternalIDP: policy.AllowExternalIDPs, + ForceMFA: policy.ForceMFA, + SecondFactors: policy.SecondFactors, + MultiFactors: policy.MultiFactors, + PasswordlessType: policy.PasswordlessType, + HidePasswordReset: policy.HidePasswordReset, + } +} + func (repo *AuthRequestRepo) checkSelectedExternalIDP(request *domain.AuthRequest, idpConfigID string) error { for _, externalIDP := range request.AllowedExternalIDPs { if externalIDP.IDPConfigID == idpConfigID { @@ -838,14 +859,6 @@ func (repo *AuthRequestRepo) mfaSkippedOrSetUp(user *user_model.UserView) bool { return checkVerificationTime(user.MFAInitSkipped, repo.MFAInitSkippedLifeTime) } -func (repo *AuthRequestRepo) getLoginPolicy(ctx context.Context, orgID string) (*iam_model.LoginPolicyView, error) { - policy, err := repo.View.LoginPolicyByAggregateID(orgID) - if err != nil { - return nil, err - } - return iam_view_model.LoginPolicyViewToModel(policy), err -} - func (repo *AuthRequestRepo) getPrivacyPolicy(ctx context.Context, orgID string) (*domain.PrivacyPolicy, error) { policy, err := repo.View.PrivacyPolicyByAggregateID(orgID) if errors.IsNotFound(err) { diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go index 4c0291308d..803c28bbbe 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go @@ -15,7 +15,6 @@ import ( "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/errors" es_models "github.com/caos/zitadel/internal/eventstore/v1/models" - iam_model "github.com/caos/zitadel/internal/iam/model" iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model" proj_view_model "github.com/caos/zitadel/internal/project/repository/view/model" "github.com/caos/zitadel/internal/query" @@ -144,10 +143,10 @@ type mockViewUser struct { } type mockLoginPolicy struct { - policy *iam_view_model.LoginPolicyView + policy *query.LoginPolicy } -func (m *mockLoginPolicy) LoginPolicyByAggregateID(id string) (*iam_view_model.LoginPolicyView, error) { +func (m *mockLoginPolicy) LoginPolicyByID(ctx context.Context, id string) (*query.LoginPolicy, error) { return m.policy, nil } @@ -711,7 +710,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userGrantProvider: &mockUserGrants{}, projectProvider: &mockProject{}, loginPolicyProvider: &mockLoginPolicy{ - policy: &iam_view_model.LoginPolicyView{}, + policy: &query.LoginPolicy{}, }, lockoutPolicyProvider: &mockLockoutPolicy{ policy: &iam_view_model.LockoutPolicyView{ @@ -1308,7 +1307,6 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { userSession *user_model.UserSessionView request *domain.AuthRequest user *user_model.UserView - policy *iam_model.LoginPolicyView } tests := []struct { name string diff --git a/internal/auth/repository/eventsourcing/handler/handler.go b/internal/auth/repository/eventsourcing/handler/handler.go index 6a54aa7b85..05b65805bf 100644 --- a/internal/auth/repository/eventsourcing/handler/handler.go +++ b/internal/auth/repository/eventsourcing/handler/handler.go @@ -50,8 +50,6 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es systemDefaults.IamID), newAuthNKeys( handler{view, bulkLimit, configs.cycleDuration("MachineKey"), errorCount, es}), - newLoginPolicy( - handler{view, bulkLimit, configs.cycleDuration("LoginPolicy"), errorCount, es}), newIDPConfig( handler{view, bulkLimit, configs.cycleDuration("IDPConfig"), errorCount, es}), newIDPProvider( diff --git a/internal/auth/repository/eventsourcing/handler/login_policy.go b/internal/auth/repository/eventsourcing/handler/login_policy.go deleted file mode 100644 index 34f2aa9765..0000000000 --- a/internal/auth/repository/eventsourcing/handler/login_policy.go +++ /dev/null @@ -1,176 +0,0 @@ -package handler - -import ( - "context" - "github.com/caos/logging" - "github.com/caos/zitadel/internal/domain" - caos_errs "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/v1" - "github.com/caos/zitadel/internal/iam/repository/eventsourcing" - iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" - - es_models "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/eventstore/v1/query" - "github.com/caos/zitadel/internal/eventstore/v1/spooler" - iam_model "github.com/caos/zitadel/internal/iam/repository/view/model" - "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" -) - -const ( - loginPolicyTable = "auth.login_policies" -) - -type LoginPolicy struct { - handler - subscription *v1.Subscription -} - -func newLoginPolicy(handler handler) *LoginPolicy { - h := &LoginPolicy{ - handler: handler, - } - - h.subscribe() - - return h -} - -func (p *LoginPolicy) subscribe() { - p.subscription = p.es.Subscribe(p.AggregateTypes()...) - go func() { - for event := range p.subscription.Events { - query.ReduceEvent(p, event) - } - }() -} - -func (p *LoginPolicy) ViewModel() string { - return loginPolicyTable -} - -func (p *LoginPolicy) Subscription() *v1.Subscription { - return p.subscription -} - -func (_ *LoginPolicy) AggregateTypes() []es_models.AggregateType { - return []es_models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate} -} - -func (p *LoginPolicy) CurrentSequence() (uint64, error) { - sequence, err := p.view.GetLatestLoginPolicySequence() - if err != nil { - return 0, err - } - return sequence.CurrentSequence, nil -} - -func (p *LoginPolicy) EventQuery() (*es_models.SearchQuery, error) { - sequence, err := p.view.GetLatestLoginPolicySequence() - if err != nil { - return nil, err - } - return es_models.NewSearchQuery(). - AggregateTypeFilter(p.AggregateTypes()...). - LatestSequenceFilter(sequence.CurrentSequence), nil -} - -func (p *LoginPolicy) Reduce(event *es_models.Event) (err error) { - switch event.AggregateType { - case model.OrgAggregate, iam_es_model.IAMAggregate: - err = p.processLoginPolicy(event) - } - return err -} - -func (p *LoginPolicy) processLoginPolicy(event *es_models.Event) (err error) { - policy := new(iam_model.LoginPolicyView) - switch event.Type { - case model.OrgAdded: - policy, err = p.getDefaultLoginPolicy() - if err != nil { - return err - } - policy.AggregateID = event.AggregateID - policy.Default = true - case iam_es_model.LoginPolicyAdded, model.LoginPolicyAdded: - err = policy.AppendEvent(event) - case iam_es_model.LoginPolicyChanged, - iam_es_model.LoginPolicySecondFactorAdded, - iam_es_model.LoginPolicySecondFactorRemoved, - iam_es_model.LoginPolicyMultiFactorAdded, - iam_es_model.LoginPolicyMultiFactorRemoved: - policies, err := p.view.AllDefaultLoginPolicies() - if err != nil { - return err - } - for _, policy := range policies { - err = policy.AppendEvent(event) - if err != nil { - return err - } - } - return p.view.PutLoginPolicies(policies, event) - case model.LoginPolicyChanged, - model.LoginPolicySecondFactorAdded, - model.LoginPolicySecondFactorRemoved, - model.LoginPolicyMultiFactorAdded, - model.LoginPolicyMultiFactorRemoved: - policy, err = p.view.LoginPolicyByAggregateID(event.AggregateID) - if err != nil { - return err - } - err = policy.AppendEvent(event) - case model.LoginPolicyRemoved: - policy, err = p.getDefaultLoginPolicy() - if err != nil { - return err - } - policy.AggregateID = event.AggregateID - policy.Default = true - default: - return p.view.ProcessedLoginPolicySequence(event) - } - if err != nil { - return err - } - return p.view.PutLoginPolicy(policy, event) -} - -func (p *LoginPolicy) OnError(event *es_models.Event, err error) error { - logging.LogWithFields("SPOOL-5id9s", "id", event.AggregateID).WithError(err).Warn("something went wrong in login policy handler") - return spooler.HandleError(event, err, p.view.GetLatestLoginPolicyFailedEvent, p.view.ProcessedLoginPolicyFailedEvent, p.view.ProcessedLoginPolicySequence, p.errorCountUntilSkip) -} - -func (p *LoginPolicy) OnSuccess() error { - return spooler.HandleSuccess(p.view.UpdateLoginPolicySpoolerRunTimestamp) -} - -func (p *LoginPolicy) getDefaultLoginPolicy() (*iam_model.LoginPolicyView, error) { - policy, policyErr := p.view.LoginPolicyByAggregateID(domain.IAMID) - if policyErr != nil && !caos_errs.IsNotFound(policyErr) { - return nil, policyErr - } - if policy == nil { - policy = &iam_model.LoginPolicyView{} - } - events, err := p.getIAMEvents(policy.Sequence) - if err != nil { - return policy, policyErr - } - policyCopy := *policy - for _, event := range events { - if err := policyCopy.AppendEvent(event); err != nil { - return policy, nil - } - } - return &policyCopy, nil -} - -func (p *LoginPolicy) getIAMEvents(sequence uint64) ([]*es_models.Event, error) { - query, err := eventsourcing.IAMByIDQuery(domain.IAMID, sequence) - if err != nil { - return nil, err - } - - return p.es.FilterEvents(context.Background(), query) -} diff --git a/internal/auth/repository/eventsourcing/repository.go b/internal/auth/repository/eventsourcing/repository.go index 13f0f7fd36..978fc75511 100644 --- a/internal/auth/repository/eventsourcing/repository.go +++ b/internal/auth/repository/eventsourcing/repository.go @@ -108,7 +108,7 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, co UserCommandProvider: command, UserEventProvider: &userRepo, IDPProviderViewProvider: view, - LoginPolicyViewProvider: view, + LoginPolicyViewProvider: queries, LockoutPolicyViewProvider: view, UserGrantProvider: view, ProjectProvider: view, diff --git a/internal/auth/repository/eventsourcing/view/login_policies.go b/internal/auth/repository/eventsourcing/view/login_policies.go deleted file mode 100644 index bc9caf903e..0000000000 --- a/internal/auth/repository/eventsourcing/view/login_policies.go +++ /dev/null @@ -1,65 +0,0 @@ -package view - -import ( - "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/iam/repository/view" - "github.com/caos/zitadel/internal/iam/repository/view/model" - global_view "github.com/caos/zitadel/internal/view/repository" -) - -const ( - loginPolicyTable = "auth.login_policies" -) - -func (v *View) LoginPolicyByAggregateID(aggregateID string) (*model.LoginPolicyView, error) { - return view.GetLoginPolicyByAggregateID(v.Db, loginPolicyTable, aggregateID) -} - -func (v *View) AllDefaultLoginPolicies() ([]*model.LoginPolicyView, error) { - return view.GetDefaultLoginPolicies(v.Db, loginPolicyTable) -} - -func (v *View) PutLoginPolicy(policy *model.LoginPolicyView, event *models.Event) error { - err := view.PutLoginPolicy(v.Db, loginPolicyTable, policy) - if err != nil { - return err - } - return v.ProcessedLoginPolicySequence(event) -} - -func (v *View) PutLoginPolicies(policies []*model.LoginPolicyView, event *models.Event) error { - err := view.PutLoginPolicies(v.Db, loginPolicyTable, policies...) - if err != nil { - return err - } - return v.ProcessedLoginPolicySequence(event) -} - -func (v *View) DeleteLoginPolicy(aggregateID string, event *models.Event) error { - err := view.DeleteLoginPolicy(v.Db, loginPolicyTable, aggregateID) - if err != nil && !errors.IsNotFound(err) { - return err - } - return v.ProcessedLoginPolicySequence(event) -} - -func (v *View) GetLatestLoginPolicySequence() (*global_view.CurrentSequence, error) { - return v.latestSequence(loginPolicyTable) -} - -func (v *View) ProcessedLoginPolicySequence(event *models.Event) error { - return v.saveCurrentSequence(loginPolicyTable, event) -} - -func (v *View) UpdateLoginPolicySpoolerRunTimestamp() error { - return v.updateSpoolerRunSequence(loginPolicyTable) -} - -func (v *View) GetLatestLoginPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { - return v.latestFailedEvent(loginPolicyTable, sequence) -} - -func (v *View) ProcessedLoginPolicyFailedEvent(failedEvent *global_view.FailedEvent) error { - return v.saveFailedEvent(failedEvent) -} diff --git a/internal/i18n/i18n.go b/internal/i18n/i18n.go index c2888eb879..0ed99dd652 100644 --- a/internal/i18n/i18n.go +++ b/internal/i18n/i18n.go @@ -199,7 +199,7 @@ func localize(localizer *i18n.Localizer, id string, args map[string]interface{}) TemplateData: args, }) if err != nil { - logging.Log("I18N-MsF5sx").WithError(err).Warnf("missing translation") + logging.LogWithFields("I18N-MsF5sx", "id", id, "args", args).WithError(err).Warnf("missing translation") return id } return s diff --git a/internal/iam/model/login_policy.go b/internal/iam/model/login_policy.go index 75e218ec92..3d6a56ab24 100644 --- a/internal/iam/model/login_policy.go +++ b/internal/iam/model/login_policy.go @@ -1,6 +1,7 @@ package model import ( + "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/eventstore/v1/models" ) @@ -14,8 +15,8 @@ type LoginPolicy struct { AllowExternalIdp bool IDPProviders []*IDPProvider ForceMFA bool - SecondFactors []SecondFactorType - MultiFactors []MultiFactorType + SecondFactors []domain.SecondFactorType + MultiFactors []domain.MultiFactorType PasswordlessType PasswordlessType } @@ -39,14 +40,6 @@ const ( IDPProviderTypeOrg ) -type SecondFactorType int32 - -const ( - SecondFactorTypeUnspecified SecondFactorType = iota - SecondFactorTypeOTP - SecondFactorTypeU2F -) - type MultiFactorType int32 const ( @@ -78,7 +71,7 @@ func (p *LoginPolicy) GetIdpProvider(id string) (int, *IDPProvider) { return -1, nil } -func (p *LoginPolicy) GetSecondFactor(mfaType SecondFactorType) (int, SecondFactorType) { +func (p *LoginPolicy) GetSecondFactor(mfaType domain.SecondFactorType) (int, domain.SecondFactorType) { for i, m := range p.SecondFactors { if m == mfaType { return i, m @@ -87,7 +80,7 @@ func (p *LoginPolicy) GetSecondFactor(mfaType SecondFactorType) (int, SecondFact return -1, 0 } -func (p *LoginPolicy) GetMultiFactor(mfaType MultiFactorType) (int, MultiFactorType) { +func (p *LoginPolicy) GetMultiFactor(mfaType domain.MultiFactorType) (int, domain.MultiFactorType) { for i, m := range p.MultiFactors { if m == mfaType { return i, m diff --git a/internal/iam/model/login_policy_view.go b/internal/iam/model/login_policy_view.go index 2c1a998ea0..4e4594a76d 100644 --- a/internal/iam/model/login_policy_view.go +++ b/internal/iam/model/login_policy_view.go @@ -15,8 +15,8 @@ type LoginPolicyView struct { ForceMFA bool HidePasswordReset bool PasswordlessType PasswordlessType - SecondFactors []SecondFactorType - MultiFactors []MultiFactorType + SecondFactors []domain.SecondFactorType + MultiFactors []domain.MultiFactorType Default bool CreationDate time.Time @@ -100,24 +100,24 @@ func passwordLessTypeToDomain(passwordless PasswordlessType) domain.Passwordless } } -func secondFactorsToDomain(types []SecondFactorType) []domain.SecondFactorType { +func secondFactorsToDomain(types []domain.SecondFactorType) []domain.SecondFactorType { secondfactors := make([]domain.SecondFactorType, len(types)) for i, secondfactorType := range types { switch secondfactorType { - case SecondFactorTypeU2F: + case domain.SecondFactorTypeU2F: secondfactors[i] = domain.SecondFactorTypeU2F - case SecondFactorTypeOTP: + case domain.SecondFactorTypeOTP: secondfactors[i] = domain.SecondFactorTypeOTP } } return secondfactors } -func multiFactorsToDomain(types []MultiFactorType) []domain.MultiFactorType { +func multiFactorsToDomain(types []domain.MultiFactorType) []domain.MultiFactorType { multifactors := make([]domain.MultiFactorType, len(types)) for i, multifactorType := range types { switch multifactorType { - case MultiFactorTypeU2FWithPIN: + case domain.MultiFactorTypeU2FWithPIN: multifactors[i] = domain.MultiFactorTypeU2FWithPIN } } diff --git a/internal/iam/model/mfa_view.go b/internal/iam/model/mfa_view.go index ca8e453449..e0c88e9509 100644 --- a/internal/iam/model/mfa_view.go +++ b/internal/iam/model/mfa_view.go @@ -30,12 +30,12 @@ const ( type SecondFactorsSearchResponse struct { TotalResult uint64 - Result []SecondFactorType + Result []domain.SecondFactorType } type MultiFactorsSearchResponse struct { TotalResult uint64 - Result []MultiFactorType + Result []domain.MultiFactorType } func (r *SecondFactorsSearchRequest) AppendAggregateIDQuery(aggregateID string) { diff --git a/internal/iam/repository/eventsourcing/model/login_policy.go b/internal/iam/repository/eventsourcing/model/login_policy.go index b257ff6aea..68d091596b 100644 --- a/internal/iam/repository/eventsourcing/model/login_policy.go +++ b/internal/iam/repository/eventsourcing/model/login_policy.go @@ -3,6 +3,7 @@ package model import ( "encoding/json" + "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/errors" es_models "github.com/caos/zitadel/internal/eventstore/v1/models" iam_model "github.com/caos/zitadel/internal/iam/model" @@ -70,24 +71,6 @@ func LoginPolicyToModel(policy *LoginPolicy) *iam_model.LoginPolicy { } } -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), - AllowUsernamePassword: policy.AllowUsernamePassword, - AllowRegister: policy.AllowRegister, - AllowExternalIdp: policy.AllowExternalIdp, - IDPProviders: idps, - ForceMFA: policy.ForceMFA, - SecondFactors: secondFactors, - MultiFactors: multiFactors, - PasswordlessType: int32(policy.PasswordlessType), - } -} - func IDPProvidersToModel(members []*IDPProvider) []*iam_model.IDPProvider { convertedProviders := make([]*iam_model.IDPProvider, len(members)) for i, m := range members { @@ -120,7 +103,7 @@ func IDPProviderFromModel(provider *iam_model.IDPProvider) *IDPProvider { } } -func SecondFactorsFromModel(mfas []iam_model.SecondFactorType) []int32 { +func SecondFactorsFromModel(mfas []domain.SecondFactorType) []int32 { convertedMFAs := make([]int32, len(mfas)) for i, mfa := range mfas { convertedMFAs[i] = int32(mfa) @@ -128,14 +111,14 @@ func SecondFactorsFromModel(mfas []iam_model.SecondFactorType) []int32 { return convertedMFAs } -func SecondFactorFromModel(mfa iam_model.SecondFactorType) *MFA { +func SecondFactorFromModel(mfa domain.SecondFactorType) *MFA { return &MFA{MFAType: int32(mfa)} } -func SecondFactorsToModel(mfas []int32) []iam_model.SecondFactorType { - convertedMFAs := make([]iam_model.SecondFactorType, len(mfas)) +func SecondFactorsToModel(mfas []int32) []domain.SecondFactorType { + convertedMFAs := make([]domain.SecondFactorType, len(mfas)) for i, mfa := range mfas { - convertedMFAs[i] = iam_model.SecondFactorType(mfa) + convertedMFAs[i] = domain.SecondFactorType(mfa) } return convertedMFAs } @@ -152,10 +135,10 @@ 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)) +func MultiFactorsToModel(mfas []int32) []domain.MultiFactorType { + convertedMFAs := make([]domain.MultiFactorType, len(mfas)) for i, mfa := range mfas { - convertedMFAs[i] = iam_model.MultiFactorType(mfa) + convertedMFAs[i] = domain.MultiFactorType(mfa) } return convertedMFAs } diff --git a/internal/iam/repository/eventsourcing/model/login_policy_test.go b/internal/iam/repository/eventsourcing/model/login_policy_test.go index 04f26a2f72..fefaf1cd84 100644 --- a/internal/iam/repository/eventsourcing/model/login_policy_test.go +++ b/internal/iam/repository/eventsourcing/model/login_policy_test.go @@ -2,9 +2,11 @@ package model import ( "encoding/json" + "testing" + + "github.com/caos/zitadel/internal/domain" es_models "github.com/caos/zitadel/internal/eventstore/v1/models" "github.com/caos/zitadel/internal/iam/model" - "testing" ) func TestLoginPolicyChanges(t *testing.T) { @@ -275,12 +277,12 @@ func TestAppendAddSecondFactorToPolicyEvent(t *testing.T) { 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)}, + mfa: &MFA{MFAType: int32(domain.SecondFactorTypeOTP)}, event: &es_models.Event{}, }, result: &IAM{DefaultLoginPolicy: &LoginPolicy{ SecondFactors: []int32{ - int32(model.SecondFactorTypeOTP), + int32(domain.SecondFactorTypeOTP), }}}, }, } @@ -318,9 +320,9 @@ func TestRemoveSecondFactorToPolicyEvent(t *testing.T) { iam: &IAM{ DefaultLoginPolicy: &LoginPolicy{ SecondFactors: []int32{ - int32(model.SecondFactorTypeOTP), + int32(domain.SecondFactorTypeOTP), }}}, - mfa: &MFA{MFAType: int32(model.SecondFactorTypeOTP)}, + mfa: &MFA{MFAType: int32(domain.SecondFactorTypeOTP)}, event: &es_models.Event{}, }, result: &IAM{DefaultLoginPolicy: &LoginPolicy{ diff --git a/internal/iam/repository/view/login_policy_view.go b/internal/iam/repository/view/login_policy_view.go deleted file mode 100644 index b1535a6567..0000000000 --- a/internal/iam/repository/view/login_policy_view.go +++ /dev/null @@ -1,54 +0,0 @@ -package view - -import ( - "github.com/caos/zitadel/internal/domain" - caos_errs "github.com/caos/zitadel/internal/errors" - iam_model "github.com/caos/zitadel/internal/iam/model" - "github.com/caos/zitadel/internal/iam/repository/view/model" - "github.com/caos/zitadel/internal/view/repository" - "github.com/jinzhu/gorm" -) - -func GetDefaultLoginPolicies(db *gorm.DB, table string) ([]*model.LoginPolicyView, error) { - loginPolicies := make([]*model.LoginPolicyView, 0) - queries := []*iam_model.LoginPolicySearchQuery{ - {Key: iam_model.LoginPolicySearchKeyDefault, Value: true, Method: domain.SearchMethodEquals}, - } - query := repository.PrepareSearchQuery(table, model.LoginPolicySearchRequest{Queries: queries}) - _, err := query(db, &loginPolicies) - if err != nil { - return nil, err - } - return loginPolicies, nil -} - -func GetLoginPolicyByAggregateID(db *gorm.DB, table, aggregateID string) (*model.LoginPolicyView, error) { - policy := new(model.LoginPolicyView) - aggregateIDQuery := &model.LoginPolicySearchQuery{Key: iam_model.LoginPolicySearchKeyAggregateID, Value: aggregateID, Method: domain.SearchMethodEquals} - query := repository.PrepareGetByQuery(table, aggregateIDQuery) - err := query(db, policy) - if caos_errs.IsNotFound(err) { - return nil, caos_errs.ThrowNotFound(nil, "VIEW-Lso0cs", "Errors.IAM.LoginPolicy.NotExisting") - } - return policy, err -} - -func PutLoginPolicy(db *gorm.DB, table string, policy *model.LoginPolicyView) error { - save := repository.PrepareSave(table) - return save(db, policy) -} - -func PutLoginPolicies(db *gorm.DB, table string, policies ...*model.LoginPolicyView) error { - save := repository.PrepareBulkSave(table) - u := make([]interface{}, len(policies)) - for i, user := range policies { - u[i] = user - } - return save(db, u...) -} - -func DeleteLoginPolicy(db *gorm.DB, table, aggregateID string) error { - delete := repository.PrepareDeleteByKey(table, model.LoginPolicySearchKey(iam_model.LoginPolicySearchKeyAggregateID), aggregateID) - - return delete(db) -} diff --git a/internal/iam/repository/view/model/login_policy.go b/internal/iam/repository/view/model/login_policy.go deleted file mode 100644 index 3fdff7904d..0000000000 --- a/internal/iam/repository/view/model/login_policy.go +++ /dev/null @@ -1,208 +0,0 @@ -package model - -import ( - "encoding/json" - "time" - - "github.com/lib/pq" - - org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" - - es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" - - "github.com/caos/logging" - - caos_errs "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/iam/model" -) - -const ( - LoginPolicyKeyAggregateID = "aggregate_id" - LoginPolicyKeyDefault = "default_policy" -) - -type LoginPolicyView struct { - AggregateID string `json:"-" gorm:"column:aggregate_id;primary_key"` - CreationDate time.Time `json:"-" gorm:"column:creation_date"` - 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"` - ForceMFA bool `json:"forceMFA" gorm:"column:force_mfa"` - HidePasswordReset bool `json:"hidePasswordReset" gorm:"column:hide_password_reset"` - PasswordlessType int32 `json:"passwordlessType" gorm:"column:passwordless_type"` - SecondFactors pq.Int64Array `json:"-" gorm:"column:second_factors"` - MultiFactors pq.Int64Array `json:"-" gorm:"column:multi_factors"` - Default bool `json:"-" gorm:"column:default_policy"` - - Sequence uint64 `json:"-" gorm:"column:sequence"` -} - -func LoginPolicyViewFromModel(policy *model.LoginPolicyView) *LoginPolicyView { - return &LoginPolicyView{ - AggregateID: policy.AggregateID, - Sequence: policy.Sequence, - CreationDate: policy.CreationDate, - ChangeDate: policy.ChangeDate, - AllowRegister: policy.AllowRegister, - AllowExternalIDP: policy.AllowExternalIDP, - AllowUsernamePassword: policy.AllowUsernamePassword, - ForceMFA: policy.ForceMFA, - HidePasswordReset: policy.HidePasswordReset, - PasswordlessType: int32(policy.PasswordlessType), - 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, - Sequence: policy.Sequence, - CreationDate: policy.CreationDate, - ChangeDate: policy.ChangeDate, - AllowRegister: policy.AllowRegister, - AllowExternalIDP: policy.AllowExternalIDP, - AllowUsernamePassword: policy.AllowUsernamePassword, - ForceMFA: policy.ForceMFA, - HidePasswordReset: policy.HidePasswordReset, - PasswordlessType: model.PasswordlessType(policy.PasswordlessType), - SecondFactors: secondFactorsToModel(policy.SecondFactors), - MultiFactors: multiFactorsToToModel(policy.MultiFactors), - Default: policy.Default, - } -} - -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: - p.setRootData(event) - p.CreationDate = event.CreationDate - p.Default = true - err = p.SetData(event) - case org_es_model.LoginPolicyAdded: - p.setRootData(event) - p.CreationDate = event.CreationDate - err = p.SetData(event) - p.Default = false - case es_model.LoginPolicyChanged, org_es_model.LoginPolicyChanged: - 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 - } - if !existsMFA(p.SecondFactors, int64(mfa.MFAType)) { - 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 - } - if !existsMFA(p.MultiFactors, int64(mfa.MFAType)) { - p.MultiFactors = append(p.MultiFactors, int64(mfa.MFAType)) - } - case es_model.LoginPolicyMultiFactorRemoved, org_es_model.LoginPolicyMultiFactorRemoved: - err = p.removeMultiFactor(event) - } - return err -} - -func (r *LoginPolicyView) setRootData(event *models.Event) { - r.AggregateID = event.AggregateID -} - -func (r *LoginPolicyView) SetData(event *models.Event) error { - if err := json.Unmarshal(event.Data, r); err != nil { - logging.Log("EVEN-Kn7ds").WithError(err).Error("could not unmarshal event data") - return caos_errs.ThrowInternal(err, "MODEL-Hs8uf", "Could not unmarshal data") - } - 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 -} - -func existsMFA(mfas []int64, mfaType int64) bool { - for _, m := range mfas { - if m == mfaType { - return true - } - } - return false -} diff --git a/internal/iam/repository/view/model/login_policy_query.go b/internal/iam/repository/view/model/login_policy_query.go deleted file mode 100644 index 662284fa51..0000000000 --- a/internal/iam/repository/view/model/login_policy_query.go +++ /dev/null @@ -1,61 +0,0 @@ -package model - -import ( - "github.com/caos/zitadel/internal/domain" - iam_model "github.com/caos/zitadel/internal/iam/model" - "github.com/caos/zitadel/internal/view/repository" -) - -type LoginPolicySearchRequest iam_model.LoginPolicySearchRequest -type LoginPolicySearchQuery iam_model.LoginPolicySearchQuery -type LoginPolicySearchKey iam_model.LoginPolicySearchKey - -func (req LoginPolicySearchRequest) GetLimit() uint64 { - return req.Limit -} - -func (req LoginPolicySearchRequest) GetOffset() uint64 { - return req.Offset -} - -func (req LoginPolicySearchRequest) GetSortingColumn() repository.ColumnKey { - if req.SortingColumn == iam_model.LoginPolicySearchKeyUnspecified { - return nil - } - return LoginPolicySearchKey(req.SortingColumn) -} - -func (req LoginPolicySearchRequest) GetAsc() bool { - return req.Asc -} - -func (req LoginPolicySearchRequest) GetQueries() []repository.SearchQuery { - result := make([]repository.SearchQuery, len(req.Queries)) - for i, q := range req.Queries { - result[i] = LoginPolicySearchQuery{Key: q.Key, Value: q.Value, Method: q.Method} - } - return result -} - -func (req LoginPolicySearchQuery) GetKey() repository.ColumnKey { - return LoginPolicySearchKey(req.Key) -} - -func (req LoginPolicySearchQuery) GetMethod() domain.SearchMethod { - return req.Method -} - -func (req LoginPolicySearchQuery) GetValue() interface{} { - return req.Value -} - -func (key LoginPolicySearchKey) ToColumnName() string { - switch iam_model.LoginPolicySearchKey(key) { - case iam_model.LoginPolicySearchKeyAggregateID: - return LoginPolicyKeyAggregateID - case iam_model.LoginPolicySearchKeyDefault: - return LoginPolicyKeyDefault - default: - return "" - } -} diff --git a/internal/management/repository/eventsourcing/eventstore/org.go b/internal/management/repository/eventsourcing/eventstore/org.go index 6677f670f1..c30d672c4a 100644 --- a/internal/management/repository/eventsourcing/eventstore/org.go +++ b/internal/management/repository/eventsourcing/eventstore/org.go @@ -31,12 +31,14 @@ import ( org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" org_view "github.com/caos/zitadel/internal/org/repository/view" "github.com/caos/zitadel/internal/org/repository/view/model" + "github.com/caos/zitadel/internal/query" usr_model "github.com/caos/zitadel/internal/user/model" "github.com/caos/zitadel/internal/user/repository/view" usr_es_model "github.com/caos/zitadel/internal/user/repository/view/model" ) type OrgRepository struct { + Query *query.Queries SearchLimit uint64 Eventstore v1.Eventstore View *mgmt_view.View @@ -242,31 +244,6 @@ func (repo *OrgRepository) getDefaultLabelPolicy(ctx context.Context, state doma return iam_view_model.LabelPolicyViewToModel(policy), nil } -func (repo *OrgRepository) GetLoginPolicy(ctx context.Context) (*iam_model.LoginPolicyView, error) { - policy, viewErr := repo.View.LoginPolicyByAggregateID(authz.GetCtxData(ctx).OrgID) - if viewErr != nil && !errors.IsNotFound(viewErr) { - return nil, viewErr - } - if errors.IsNotFound(viewErr) { - policy = new(iam_view_model.LoginPolicyView) - } - events, esErr := repo.getOrgEvents(ctx, repo.SystemDefaults.IamID, policy.Sequence) - if errors.IsNotFound(viewErr) && len(events) == 0 { - return repo.GetDefaultLoginPolicy(ctx) - } - if esErr != nil { - logging.Log("EVENT-38iTr").WithError(esErr).Debug("error retrieving new events") - return iam_view_model.LoginPolicyViewToModel(policy), nil - } - policyCopy := *policy - for _, event := range events { - if err := policyCopy.AppendEvent(event); err != nil { - return iam_view_model.LoginPolicyViewToModel(policy), nil - } - } - return iam_view_model.LoginPolicyViewToModel(policy), nil -} - func (repo *OrgRepository) GetIDPProvidersByIDPConfigID(ctx context.Context, aggregateID, idpConfigID string) ([]*iam_model.IDPProviderView, error) { idpProviders, err := repo.View.IDPProvidersByIdpConfigID(aggregateID, idpConfigID) if err != nil { @@ -275,38 +252,12 @@ func (repo *OrgRepository) GetIDPProvidersByIDPConfigID(ctx context.Context, agg return iam_view_model.IDPProviderViewsToModel(idpProviders), err } -func (repo *OrgRepository) GetDefaultLoginPolicy(ctx context.Context) (*iam_model.LoginPolicyView, error) { - policy, viewErr := repo.View.LoginPolicyByAggregateID(domain.IAMID) - if viewErr != nil && !errors.IsNotFound(viewErr) { - return nil, viewErr - } - if errors.IsNotFound(viewErr) { - policy = new(iam_view_model.LoginPolicyView) - } - events, esErr := repo.getIAMEvents(ctx, policy.Sequence) - if errors.IsNotFound(viewErr) && len(events) == 0 { - return nil, errors.ThrowNotFound(nil, "EVENT-cmO9s", "Errors.IAM.LoginPolicy.NotFound") - } - if esErr != nil { - logging.Log("EVENT-28uLp").WithError(esErr).Debug("error retrieving new events") - return iam_view_model.LoginPolicyViewToModel(policy), nil - } - policyCopy := *policy - for _, event := range events { - if err := policyCopy.AppendEvent(event); err != nil { - return iam_view_model.LoginPolicyViewToModel(policy), nil - } - } - policy.Default = true - return iam_view_model.LoginPolicyViewToModel(policy), nil -} - func (repo *OrgRepository) SearchIDPProviders(ctx context.Context, request *iam_model.IDPProviderSearchRequest) (*iam_model.IDPProviderSearchResponse, error) { - policy, err := repo.View.LoginPolicyByAggregateID(authz.GetCtxData(ctx).OrgID) + policy, err := repo.Query.LoginPolicyByID(ctx, authz.GetCtxData(ctx).OrgID) if err != nil { return nil, err } - if policy.Default { + if policy.IsDefault { request.AppendAggregateIDQuery(domain.IAMID) } else { request.AppendAggregateIDQuery(authz.GetCtxData(ctx).OrgID) @@ -334,28 +285,6 @@ func (repo *OrgRepository) SearchIDPProviders(ctx context.Context, request *iam_ return result, nil } -func (repo *OrgRepository) SearchSecondFactors(ctx context.Context) (*iam_model.SecondFactorsSearchResponse, error) { - policy, err := repo.GetLoginPolicy(ctx) - if err != nil { - return nil, err - } - return &iam_model.SecondFactorsSearchResponse{ - TotalResult: uint64(len(policy.SecondFactors)), - Result: policy.SecondFactors, - }, nil -} - -func (repo *OrgRepository) SearchMultiFactors(ctx context.Context) (*iam_model.MultiFactorsSearchResponse, error) { - policy, err := repo.GetLoginPolicy(ctx) - if err != nil { - return nil, err - } - return &iam_model.MultiFactorsSearchResponse{ - TotalResult: uint64(len(policy.MultiFactors)), - Result: policy.MultiFactors, - }, nil -} - func (repo *OrgRepository) GetPasswordComplexityPolicy(ctx context.Context) (*iam_model.PasswordComplexityPolicyView, error) { policy, viewErr := repo.View.PasswordComplexityPolicyByAggregateID(authz.GetCtxData(ctx).OrgID) if viewErr != nil && !errors.IsNotFound(viewErr) { diff --git a/internal/management/repository/eventsourcing/handler/handler.go b/internal/management/repository/eventsourcing/handler/handler.go index 27723e6e24..7c5a595f55 100644 --- a/internal/management/repository/eventsourcing/handler/handler.go +++ b/internal/management/repository/eventsourcing/handler/handler.go @@ -48,8 +48,6 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es handler{view, bulkLimit, configs.cycleDuration("MachineKeys"), errorCount, es}), newIDPConfig( handler{view, bulkLimit, configs.cycleDuration("IDPConfig"), errorCount, es}), - newLoginPolicy( - handler{view, bulkLimit, configs.cycleDuration("LoginPolicy"), errorCount, es}), newLabelPolicy( handler{view, bulkLimit, configs.cycleDuration("LabelPolicy"), errorCount, es}, staticStorage), diff --git a/internal/management/repository/eventsourcing/handler/login_policy.go b/internal/management/repository/eventsourcing/handler/login_policy.go deleted file mode 100644 index ca5a68845d..0000000000 --- a/internal/management/repository/eventsourcing/handler/login_policy.go +++ /dev/null @@ -1,175 +0,0 @@ -package handler - -import ( - "context" - "github.com/caos/logging" - "github.com/caos/zitadel/internal/domain" - caos_errs "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/v1" - es_models "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/eventstore/v1/query" - "github.com/caos/zitadel/internal/eventstore/v1/spooler" - "github.com/caos/zitadel/internal/iam/repository/eventsourcing" - iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" - iam_model "github.com/caos/zitadel/internal/iam/repository/view/model" - "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" -) - -const ( - loginPolicyTable = "management.login_policies" -) - -type LoginPolicy struct { - handler - subscription *v1.Subscription -} - -func newLoginPolicy(handler handler) *LoginPolicy { - h := &LoginPolicy{ - handler: handler, - } - - h.subscribe() - - return h -} - -func (m *LoginPolicy) subscribe() { - m.subscription = m.es.Subscribe(m.AggregateTypes()...) - go func() { - for event := range m.subscription.Events { - query.ReduceEvent(m, event) - } - }() -} - -func (m *LoginPolicy) ViewModel() string { - return loginPolicyTable -} - -func (p *LoginPolicy) Subscription() *v1.Subscription { - return p.subscription -} - -func (_ *LoginPolicy) AggregateTypes() []es_models.AggregateType { - return []es_models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate} -} - -func (m *LoginPolicy) CurrentSequence() (uint64, error) { - sequence, err := m.view.GetLatestLoginPolicySequence() - if err != nil { - return 0, err - } - return sequence.CurrentSequence, nil -} - -func (m *LoginPolicy) EventQuery() (*es_models.SearchQuery, error) { - sequence, err := m.view.GetLatestLoginPolicySequence() - if err != nil { - return nil, err - } - return es_models.NewSearchQuery(). - AggregateTypeFilter(m.AggregateTypes()...). - LatestSequenceFilter(sequence.CurrentSequence), nil -} - -func (m *LoginPolicy) Reduce(event *es_models.Event) (err error) { - switch event.AggregateType { - case model.OrgAggregate, iam_es_model.IAMAggregate: - err = m.processLoginPolicy(event) - } - return err -} - -func (m *LoginPolicy) processLoginPolicy(event *es_models.Event) (err error) { - policy := new(iam_model.LoginPolicyView) - switch event.Type { - case model.OrgAdded: - policy, err = m.getDefaultLoginPolicy() - if err != nil { - return err - } - policy.AggregateID = event.AggregateID - policy.Default = true - case iam_es_model.LoginPolicyAdded, model.LoginPolicyAdded: - err = policy.AppendEvent(event) - case iam_es_model.LoginPolicyChanged, - iam_es_model.LoginPolicySecondFactorAdded, - iam_es_model.LoginPolicySecondFactorRemoved, - iam_es_model.LoginPolicyMultiFactorAdded, - iam_es_model.LoginPolicyMultiFactorRemoved: - policies, err := m.view.AllDefaultLoginPolicies() - if err != nil { - return err - } - for _, policy := range policies { - err = policy.AppendEvent(event) - if err != nil { - return err - } - } - return m.view.PutLoginPolicies(policies, event) - case model.LoginPolicyChanged, - model.LoginPolicySecondFactorAdded, - model.LoginPolicySecondFactorRemoved, - model.LoginPolicyMultiFactorAdded, - model.LoginPolicyMultiFactorRemoved: - policy, err = m.view.LoginPolicyByAggregateID(event.AggregateID) - if err != nil { - return err - } - err = policy.AppendEvent(event) - case model.LoginPolicyRemoved: - policy, err = m.getDefaultLoginPolicy() - if err != nil { - return err - } - policy.AggregateID = event.AggregateID - policy.Default = true - default: - return m.view.ProcessedLoginPolicySequence(event) - } - if err != nil { - return err - } - return m.view.PutLoginPolicy(policy, event) -} - -func (m *LoginPolicy) OnError(event *es_models.Event, err error) error { - logging.LogWithFields("SPOOL-92n8F", "id", event.AggregateID).WithError(err).Warn("something went wrong in login policy handler") - return spooler.HandleError(event, err, m.view.GetLatestLoginPolicyFailedEvent, m.view.ProcessedLoginPolicyFailedEvent, m.view.ProcessedLoginPolicySequence, m.errorCountUntilSkip) -} - -func (m *LoginPolicy) OnSuccess() error { - return spooler.HandleSuccess(m.view.UpdateLoginPolicySpoolerRunTimestamp) -} - -func (p *LoginPolicy) getDefaultLoginPolicy() (*iam_model.LoginPolicyView, error) { - policy, policyErr := p.view.LoginPolicyByAggregateID(domain.IAMID) - if policyErr != nil && !caos_errs.IsNotFound(policyErr) { - return nil, policyErr - } - if policy == nil { - policy = &iam_model.LoginPolicyView{} - } - events, err := p.getIAMEvents(policy.Sequence) - if err != nil { - return policy, policyErr - } - policyCopy := *policy - for _, event := range events { - if err := policyCopy.AppendEvent(event); err != nil { - return policy, nil - } - } - return &policyCopy, nil -} - -func (p *LoginPolicy) getIAMEvents(sequence uint64) ([]*es_models.Event, error) { - query, err := eventsourcing.IAMByIDQuery(domain.IAMID, sequence) - if err != nil { - return nil, err - } - - return p.es.FilterEvents(context.Background(), query) -} diff --git a/internal/management/repository/eventsourcing/view/login_policies.go b/internal/management/repository/eventsourcing/view/login_policies.go deleted file mode 100644 index 18647af083..0000000000 --- a/internal/management/repository/eventsourcing/view/login_policies.go +++ /dev/null @@ -1,65 +0,0 @@ -package view - -import ( - "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/iam/repository/view" - "github.com/caos/zitadel/internal/iam/repository/view/model" - global_view "github.com/caos/zitadel/internal/view/repository" -) - -const ( - loginPolicyTable = "management.login_policies" -) - -func (v *View) AllDefaultLoginPolicies() ([]*model.LoginPolicyView, error) { - return view.GetDefaultLoginPolicies(v.Db, loginPolicyTable) -} - -func (v *View) LoginPolicyByAggregateID(aggregateID string) (*model.LoginPolicyView, error) { - return view.GetLoginPolicyByAggregateID(v.Db, loginPolicyTable, aggregateID) -} - -func (v *View) PutLoginPolicy(policy *model.LoginPolicyView, event *models.Event) error { - err := view.PutLoginPolicy(v.Db, loginPolicyTable, policy) - if err != nil { - return err - } - return v.ProcessedLoginPolicySequence(event) -} - -func (v *View) PutLoginPolicies(policies []*model.LoginPolicyView, event *models.Event) error { - err := view.PutLoginPolicies(v.Db, loginPolicyTable, policies...) - if err != nil { - return err - } - return v.ProcessedLoginPolicySequence(event) -} - -func (v *View) DeleteLoginPolicy(aggregateID string, event *models.Event) error { - err := view.DeleteLoginPolicy(v.Db, loginPolicyTable, aggregateID) - if err != nil && !errors.IsNotFound(err) { - return err - } - return v.ProcessedLoginPolicySequence(event) -} - -func (v *View) GetLatestLoginPolicySequence() (*global_view.CurrentSequence, error) { - return v.latestSequence(loginPolicyTable) -} - -func (v *View) ProcessedLoginPolicySequence(event *models.Event) error { - return v.saveCurrentSequence(loginPolicyTable, event) -} - -func (v *View) UpdateLoginPolicySpoolerRunTimestamp() error { - return v.updateSpoolerRunSequence(loginPolicyTable) -} - -func (v *View) GetLatestLoginPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { - return v.latestFailedEvent(loginPolicyTable, sequence) -} - -func (v *View) ProcessedLoginPolicyFailedEvent(failedEvent *global_view.FailedEvent) error { - return v.saveFailedEvent(failedEvent) -} diff --git a/internal/management/repository/org.go b/internal/management/repository/org.go index e6231bf0d6..5fa937dabb 100644 --- a/internal/management/repository/org.go +++ b/internal/management/repository/org.go @@ -25,12 +25,8 @@ type OrgRepository interface { GetMyOrgIamPolicy(ctx context.Context) (*iam_model.OrgIAMPolicyView, error) - GetLoginPolicy(ctx context.Context) (*iam_model.LoginPolicyView, error) - GetDefaultLoginPolicy(ctx context.Context) (*iam_model.LoginPolicyView, error) SearchIDPProviders(ctx context.Context, request *iam_model.IDPProviderSearchRequest) (*iam_model.IDPProviderSearchResponse, error) GetIDPProvidersByIDPConfigID(ctx context.Context, aggregateID, idpConfigID string) ([]*iam_model.IDPProviderView, error) - SearchSecondFactors(ctx context.Context) (*iam_model.SecondFactorsSearchResponse, error) - SearchMultiFactors(ctx context.Context) (*iam_model.MultiFactorsSearchResponse, error) GetPasswordComplexityPolicy(ctx context.Context) (*iam_model.PasswordComplexityPolicyView, error) GetDefaultPasswordComplexityPolicy(ctx context.Context) (*iam_model.PasswordComplexityPolicyView, error) diff --git a/internal/org/repository/eventsourcing/model/login_policy_test.go b/internal/org/repository/eventsourcing/model/login_policy_test.go index 978cc57e04..b43611f80e 100644 --- a/internal/org/repository/eventsourcing/model/login_policy_test.go +++ b/internal/org/repository/eventsourcing/model/login_policy_test.go @@ -2,10 +2,12 @@ package model import ( "encoding/json" + "testing" + + "github.com/caos/zitadel/internal/domain" es_models "github.com/caos/zitadel/internal/eventstore/v1/models" iam_model "github.com/caos/zitadel/internal/iam/model" iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" - "testing" ) func TestAppendAddLoginPolicyEvent(t *testing.T) { @@ -224,7 +226,7 @@ func TestAppendAddSecondFactorToPolicyEvent(t *testing.T) { name: "append add second factor to login policy event", args: args{ org: &Org{LoginPolicy: &iam_es_model.LoginPolicy{AllowExternalIdp: true, AllowRegister: true, AllowUsernamePassword: true}}, - mfa: &iam_es_model.MFA{MFAType: int32(iam_model.SecondFactorTypeOTP)}, + mfa: &iam_es_model.MFA{MFAType: int32(domain.SecondFactorTypeOTP)}, event: &es_models.Event{}, }, result: &Org{LoginPolicy: &iam_es_model.LoginPolicy{ @@ -232,7 +234,7 @@ func TestAppendAddSecondFactorToPolicyEvent(t *testing.T) { AllowRegister: true, AllowUsernamePassword: true, SecondFactors: []int32{ - int32(iam_model.SecondFactorTypeOTP), + int32(domain.SecondFactorTypeOTP), }}}, }, } @@ -273,9 +275,9 @@ func TestRemoveSecondFactorFromPolicyEvent(t *testing.T) { AllowRegister: true, AllowUsernamePassword: true, SecondFactors: []int32{ - int32(iam_model.SecondFactorTypeOTP), + int32(domain.SecondFactorTypeOTP), }}}, - mfa: &iam_es_model.MFA{MFAType: int32(iam_model.SecondFactorTypeOTP)}, + mfa: &iam_es_model.MFA{MFAType: int32(domain.SecondFactorTypeOTP)}, event: &es_models.Event{}, }, result: &Org{LoginPolicy: &iam_es_model.LoginPolicy{ diff --git a/internal/query/policy_login.go b/internal/query/policy_login.go new file mode 100644 index 0000000000..6c6d26ae71 --- /dev/null +++ b/internal/query/policy_login.go @@ -0,0 +1,295 @@ +package query + +import ( + "context" + "database/sql" + errs "errors" + "time" + + sq "github.com/Masterminds/squirrel" + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/query/projection" + "github.com/lib/pq" +) + +type LoginPolicy struct { + OrgID string + CreationDate time.Time + ChangeDate time.Time + Sequence uint64 + AllowRegister bool + AllowUsernamePassword bool + AllowExternalIDPs bool + ForceMFA bool + SecondFactors []domain.SecondFactorType + MultiFactors []domain.MultiFactorType + PasswordlessType domain.PasswordlessType + IsDefault bool + HidePasswordReset bool +} + +type SecondFactors struct { + SearchResponse + Factors []domain.SecondFactorType +} + +type MultiFactors struct { + SearchResponse + Factors []domain.MultiFactorType +} + +var ( + loginPolicyTable = table{ + name: projection.LoginPolicyTable, + } + LoginPolicyColumnOrgID = Column{ + name: projection.LoginPolicyIDCol, + } + LoginPolicyColumnCreationDate = Column{ + name: projection.LoginPolicyCreationDateCol, + } + LoginPolicyColumnChangeDate = Column{ + name: projection.LoginPolicyChangeDateCol, + } + LoginPolicyColumnSequence = Column{ + name: projection.LoginPolicySequenceCol, + } + LoginPolicyColumnAllowRegister = Column{ + name: projection.LoginPolicyAllowRegisterCol, + } + LoginPolicyColumnAllowUsernamePassword = Column{ + name: projection.LoginPolicyAllowUsernamePasswordCol, + } + LoginPolicyColumnAllowExternalIDPs = Column{ + name: projection.LoginPolicyAllowExternalIDPsCol, + } + LoginPolicyColumnForceMFA = Column{ + name: projection.LoginPolicyForceMFACol, + } + LoginPolicyColumnSecondFactors = Column{ + name: projection.LoginPolicy2FAsCol, + } + LoginPolicyColumnMultiFactors = Column{ + name: projection.LoginPolicyMFAsCol, + } + LoginPolicyColumnPasswordlessType = Column{ + name: projection.LoginPolicyPasswordlessTypeCol, + } + LoginPolicyColumnIsDefault = Column{ + name: projection.LoginPolicyIsDefaultCol, + } + LoginPolicyColumnHidePasswordReset = Column{ + name: projection.LoginPolicyHidePWResetCol, + } +) + +func (q *Queries) LoginPolicyByID(ctx context.Context, orgID string) (*LoginPolicy, error) { + query, scan := prepareLoginPolicyQuery() + stmt, args, err := query.Where( + sq.Or{ + sq.Eq{ + LoginPolicyColumnOrgID.identifier(): orgID, + }, + sq.Eq{ + LoginPolicyColumnOrgID.identifier(): domain.IAMID, + }, + }). + OrderBy(LoginPolicyColumnIsDefault.identifier()). + Limit(1).ToSql() + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-scVHo", "Errors.Query.SQLStatement") + } + + row := q.client.QueryRowContext(ctx, stmt, args...) + return scan(row) +} + +func (q *Queries) DefaultLoginPolicy(ctx context.Context) (*LoginPolicy, error) { + query, scan := prepareLoginPolicyQuery() + stmt, args, err := query.Where(sq.Eq{ + LoginPolicyColumnOrgID.identifier(): domain.IAMID, + }).OrderBy(LoginPolicyColumnIsDefault.identifier()).ToSql() + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-t4TBK", "Errors.Query.SQLStatement") + } + + row := q.client.QueryRowContext(ctx, stmt, args...) + return scan(row) +} + +func (q *Queries) SecondFactorsByID(ctx context.Context, orgID string) (*SecondFactors, error) { + query, scan := prepareLoginPolicy2FAsQuery() + stmt, args, err := query.Where( + sq.Or{ + sq.Eq{ + LoginPolicyColumnOrgID.identifier(): orgID, + }, + sq.Eq{ + LoginPolicyColumnOrgID.identifier(): domain.IAMID, + }, + }). + OrderBy(LoginPolicyColumnIsDefault.identifier()). + Limit(1).ToSql() + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-scVHo", "Errors.Query.SQLStatement") + } + + row := q.client.QueryRowContext(ctx, stmt, args...) + return scan(row) +} + +func (q *Queries) DefaultSecondFactors(ctx context.Context) (*SecondFactors, error) { + query, scan := prepareLoginPolicy2FAsQuery() + stmt, args, err := query.Where(sq.Eq{ + LoginPolicyColumnOrgID.identifier(): domain.IAMID, + }).OrderBy(LoginPolicyColumnIsDefault.identifier()).ToSql() + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-CZ2Nv", "Errors.Query.SQLStatement") + } + + row := q.client.QueryRowContext(ctx, stmt, args...) + return scan(row) +} + +func (q *Queries) MultiFactorsByID(ctx context.Context, orgID string) (*MultiFactors, error) { + query, scan := prepareLoginPolicyMFAsQuery() + stmt, args, err := query.Where( + sq.Or{ + sq.Eq{ + LoginPolicyColumnOrgID.identifier(): orgID, + }, + sq.Eq{ + LoginPolicyColumnOrgID.identifier(): domain.IAMID, + }, + }). + OrderBy(LoginPolicyColumnIsDefault.identifier()). + Limit(1).ToSql() + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-B4o7h", "Errors.Query.SQLStatement") + } + + row := q.client.QueryRowContext(ctx, stmt, args...) + return scan(row) +} + +func (q *Queries) DefaultMultiFactors(ctx context.Context) (*MultiFactors, error) { + query, scan := prepareLoginPolicyMFAsQuery() + stmt, args, err := query.Where(sq.Eq{ + LoginPolicyColumnOrgID.identifier(): domain.IAMID, + }).OrderBy(LoginPolicyColumnIsDefault.identifier()).ToSql() + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-WxYjr", "Errors.Query.SQLStatement") + } + + row := q.client.QueryRowContext(ctx, stmt, args...) + return scan(row) +} + +func prepareLoginPolicyQuery() (sq.SelectBuilder, func(*sql.Row) (*LoginPolicy, error)) { + return sq.Select( + LoginPolicyColumnOrgID.identifier(), + LoginPolicyColumnCreationDate.identifier(), + LoginPolicyColumnChangeDate.identifier(), + LoginPolicyColumnSequence.identifier(), + LoginPolicyColumnAllowRegister.identifier(), + LoginPolicyColumnAllowUsernamePassword.identifier(), + LoginPolicyColumnAllowExternalIDPs.identifier(), + LoginPolicyColumnForceMFA.identifier(), + LoginPolicyColumnSecondFactors.identifier(), + LoginPolicyColumnMultiFactors.identifier(), + LoginPolicyColumnPasswordlessType.identifier(), + LoginPolicyColumnIsDefault.identifier(), + LoginPolicyColumnHidePasswordReset.identifier(), + ).From(loginPolicyTable.identifier()).PlaceholderFormat(sq.Dollar), + func(row *sql.Row) (*LoginPolicy, error) { + p := new(LoginPolicy) + secondFactors := pq.Int32Array{} + multiFactors := pq.Int32Array{} + err := row.Scan( + &p.OrgID, + &p.CreationDate, + &p.ChangeDate, + &p.Sequence, + &p.AllowRegister, + &p.AllowUsernamePassword, + &p.AllowExternalIDPs, + &p.ForceMFA, + &secondFactors, + &multiFactors, + &p.PasswordlessType, + &p.IsDefault, + &p.HidePasswordReset, + ) + if err != nil { + if errs.Is(err, sql.ErrNoRows) { + return nil, errors.ThrowNotFound(err, "QUERY-QsUBJ", "Errors.LoginPolicy.NotFound") + } + return nil, errors.ThrowInternal(err, "QUERY-YcC53", "Errors.Internal") + } + + p.MultiFactors = make([]domain.MultiFactorType, len(multiFactors)) + for i, mfa := range multiFactors { + p.MultiFactors[i] = domain.MultiFactorType(mfa) + } + p.SecondFactors = make([]domain.SecondFactorType, len(secondFactors)) + for i, mfa := range secondFactors { + p.SecondFactors[i] = domain.SecondFactorType(mfa) + } + return p, nil + } +} + +func prepareLoginPolicy2FAsQuery() (sq.SelectBuilder, func(*sql.Row) (*SecondFactors, error)) { + return sq.Select( + LoginPolicyColumnSequence.identifier(), + LoginPolicyColumnSecondFactors.identifier(), + ).From(loginPolicyTable.identifier()).PlaceholderFormat(sq.Dollar), + func(row *sql.Row) (*SecondFactors, error) { + p := new(SecondFactors) + secondFactors := pq.Int32Array{} + err := row.Scan( + &p.Sequence, + &secondFactors, + ) + if err != nil { + if errs.Is(err, sql.ErrNoRows) { + return nil, errors.ThrowNotFound(err, "QUERY-yPqIZ", "Errors.LoginPolicy.NotFound") + } + return nil, errors.ThrowInternal(err, "QUERY-Mr6H3", "Errors.Internal") + } + + p.Factors = make([]domain.SecondFactorType, len(secondFactors)) + for i, mfa := range secondFactors { + p.Factors[i] = domain.SecondFactorType(mfa) + } + return p, nil + } +} + +func prepareLoginPolicyMFAsQuery() (sq.SelectBuilder, func(*sql.Row) (*MultiFactors, error)) { + return sq.Select( + LoginPolicyColumnSequence.identifier(), + LoginPolicyColumnMultiFactors.identifier(), + ).From(loginPolicyTable.identifier()).PlaceholderFormat(sq.Dollar), + func(row *sql.Row) (*MultiFactors, error) { + p := new(MultiFactors) + multiFactors := pq.Int32Array{} + err := row.Scan( + &p.Sequence, + &multiFactors, + ) + if err != nil { + if errs.Is(err, sql.ErrNoRows) { + return nil, errors.ThrowNotFound(err, "QUERY-yPqIZ", "Errors.LoginPolicy.NotFound") + } + return nil, errors.ThrowInternal(err, "QUERY-Mr6H3", "Errors.Internal") + } + + p.Factors = make([]domain.MultiFactorType, len(multiFactors)) + for i, mfa := range multiFactors { + p.Factors[i] = domain.MultiFactorType(mfa) + } + return p, nil + } +} diff --git a/internal/query/projection/login_policy.go b/internal/query/projection/login_policy.go new file mode 100644 index 0000000000..0c41b021e6 --- /dev/null +++ b/internal/query/projection/login_policy.go @@ -0,0 +1,301 @@ +package projection + +import ( + "context" + + "github.com/caos/logging" + + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/eventstore/handler" + "github.com/caos/zitadel/internal/eventstore/handler/crdb" + "github.com/caos/zitadel/internal/repository/iam" + "github.com/caos/zitadel/internal/repository/org" + "github.com/caos/zitadel/internal/repository/policy" +) + +type LoginPolicyProjection struct { + crdb.StatementHandler +} + +const ( + LoginPolicyTable = "zitadel.projections.login_policies" +) + +func NewLoginPolicyProjection(ctx context.Context, config crdb.StatementHandlerConfig) *LoginPolicyProjection { + p := &LoginPolicyProjection{} + config.ProjectionName = LoginPolicyTable + config.Reducers = p.reducers() + p.StatementHandler = crdb.NewStatementHandler(ctx, config) + return p +} + +func (p *LoginPolicyProjection) reducers() []handler.AggregateReducer { + return []handler.AggregateReducer{ + { + Aggregate: org.AggregateType, + EventRedusers: []handler.EventReducer{ + { + Event: org.LoginPolicyAddedEventType, + Reduce: p.reduceLoginPolicyAdded, + }, + { + Event: org.LoginPolicyChangedEventType, + Reduce: p.reduceLoginPolicyChanged, + }, + { + Event: org.LoginPolicyMultiFactorAddedEventType, + Reduce: p.reduceMFAAdded, + }, + { + Event: org.LoginPolicyMultiFactorRemovedEventType, + Reduce: p.reduceMFARemoved, + }, + { + Event: org.LoginPolicyRemovedEventType, + Reduce: p.reduceLoginPolicyRemoved, + }, + { + Event: org.LoginPolicySecondFactorAddedEventType, + Reduce: p.reduce2FAAdded, + }, + { + Event: org.LoginPolicySecondFactorRemovedEventType, + Reduce: p.reduce2FARemoved, + }, + }, + }, + { + Aggregate: iam.AggregateType, + EventRedusers: []handler.EventReducer{ + { + Event: iam.LoginPolicyAddedEventType, + Reduce: p.reduceLoginPolicyAdded, + }, + { + Event: iam.LoginPolicyChangedEventType, + Reduce: p.reduceLoginPolicyChanged, + }, + { + Event: iam.LoginPolicyMultiFactorAddedEventType, + Reduce: p.reduceMFAAdded, + }, + { + Event: iam.LoginPolicyMultiFactorRemovedEventType, + Reduce: p.reduceMFARemoved, + }, + { + Event: iam.LoginPolicySecondFactorAddedEventType, + Reduce: p.reduce2FAAdded, + }, + { + Event: iam.LoginPolicySecondFactorRemovedEventType, + Reduce: p.reduce2FARemoved, + }, + }, + }, + } +} + +const ( + LoginPolicyIDCol = "aggregate_id" + LoginPolicyCreationDateCol = "creation_date" + LoginPolicyChangeDateCol = "change_date" + LoginPolicySequenceCol = "sequence" + LoginPolicyAllowRegisterCol = "allow_register" + LoginPolicyAllowUsernamePasswordCol = "allow_username_password" + LoginPolicyAllowExternalIDPsCol = "allow_external_idps" + LoginPolicyForceMFACol = "force_mfa" + LoginPolicy2FAsCol = "second_factors" + LoginPolicyMFAsCol = "multi_factors" + LoginPolicyPasswordlessTypeCol = "passwordless_type" + LoginPolicyIsDefaultCol = "is_default" + LoginPolicyHidePWResetCol = "hide_password_reset" +) + +func (p *LoginPolicyProjection) reduceLoginPolicyAdded(event eventstore.EventReader) (*handler.Statement, error) { + var policyEvent policy.LoginPolicyAddedEvent + var isDefault bool + switch e := event.(type) { + case *iam.LoginPolicyAddedEvent: + policyEvent = e.LoginPolicyAddedEvent + isDefault = true + case *org.LoginPolicyAddedEvent: + policyEvent = e.LoginPolicyAddedEvent + isDefault = false + default: + logging.LogWithFields("HANDL-IW6So", "seq", event.Sequence(), "expectedTypes", []eventstore.EventType{org.LoginPolicyAddedEventType, iam.LoginPolicyAddedEventType}).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-pYPxS", "reduce.wrong.event.type") + } + + return crdb.NewCreateStatement(&policyEvent, []handler.Column{ + handler.NewCol(LoginPolicyIDCol, policyEvent.Aggregate().ID), + handler.NewCol(LoginPolicyCreationDateCol, policyEvent.CreationDate()), + handler.NewCol(LoginPolicyChangeDateCol, policyEvent.CreationDate()), + handler.NewCol(LoginPolicySequenceCol, policyEvent.Sequence()), + handler.NewCol(LoginPolicyAllowRegisterCol, policyEvent.AllowRegister), + handler.NewCol(LoginPolicyAllowUsernamePasswordCol, policyEvent.AllowUserNamePassword), + handler.NewCol(LoginPolicyAllowExternalIDPsCol, policyEvent.AllowExternalIDP), + handler.NewCol(LoginPolicyForceMFACol, policyEvent.ForceMFA), + handler.NewCol(LoginPolicyPasswordlessTypeCol, policyEvent.PasswordlessType), + handler.NewCol(LoginPolicyIsDefaultCol, isDefault), + handler.NewCol(LoginPolicyHidePWResetCol, policyEvent.HidePasswordReset), + }), nil +} + +func (p *LoginPolicyProjection) reduceLoginPolicyChanged(event eventstore.EventReader) (*handler.Statement, error) { + var policyEvent policy.LoginPolicyChangedEvent + switch e := event.(type) { + case *iam.LoginPolicyChangedEvent: + policyEvent = e.LoginPolicyChangedEvent + case *org.LoginPolicyChangedEvent: + policyEvent = e.LoginPolicyChangedEvent + default: + logging.LogWithFields("HANDL-NIvFo", "seq", event.Sequence(), "expectedTypes", []eventstore.EventType{org.LoginPolicyChangedEventType, iam.LoginPolicyChangedEventType}).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-BpaO6", "reduce.wrong.event.type") + } + + cols := []handler.Column{ + handler.NewCol(LoginPolicyChangeDateCol, policyEvent.CreationDate()), + handler.NewCol(LoginPolicySequenceCol, policyEvent.Sequence()), + } + if policyEvent.AllowRegister != nil { + cols = append(cols, handler.NewCol(LoginPolicyAllowRegisterCol, *policyEvent.AllowRegister)) + } + if policyEvent.AllowUserNamePassword != nil { + cols = append(cols, handler.NewCol(LoginPolicyAllowUsernamePasswordCol, *policyEvent.AllowUserNamePassword)) + } + if policyEvent.AllowExternalIDP != nil { + cols = append(cols, handler.NewCol(LoginPolicyAllowExternalIDPsCol, *policyEvent.AllowExternalIDP)) + } + if policyEvent.ForceMFA != nil { + cols = append(cols, handler.NewCol(LoginPolicyForceMFACol, *policyEvent.ForceMFA)) + } + if policyEvent.PasswordlessType != nil { + cols = append(cols, handler.NewCol(LoginPolicyPasswordlessTypeCol, *policyEvent.PasswordlessType)) + } + if policyEvent.HidePasswordReset != nil { + cols = append(cols, handler.NewCol(LoginPolicyHidePWResetCol, *policyEvent.HidePasswordReset)) + } + return crdb.NewUpdateStatement( + &policyEvent, + cols, + []handler.Condition{ + handler.NewCond(LoginPolicyIDCol, policyEvent.Aggregate().ID), + }, + ), nil +} + +func (p *LoginPolicyProjection) reduceMFAAdded(event eventstore.EventReader) (*handler.Statement, error) { + var policyEvent policy.MultiFactorAddedEvent + switch e := event.(type) { + case *iam.LoginPolicyMultiFactorAddedEvent: + policyEvent = e.MultiFactorAddedEvent + case *org.LoginPolicyMultiFactorAddedEvent: + policyEvent = e.MultiFactorAddedEvent + default: + logging.LogWithFields("HANDL-fYAHO", "seq", event.Sequence(), "expectedTypes", []eventstore.EventType{org.LoginPolicyMultiFactorAddedEventType, iam.LoginPolicyMultiFactorAddedEventType}).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-WMhAV", "reduce.wrong.event.type") + } + + return crdb.NewUpdateStatement( + &policyEvent, + []handler.Column{ + handler.NewCol(LoginPolicyChangeDateCol, policyEvent.CreationDate()), + handler.NewCol(LoginPolicySequenceCol, policyEvent.Sequence()), + crdb.NewArrayAppendCol(LoginPolicyMFAsCol, policyEvent.MFAType), + }, + []handler.Condition{ + handler.NewCond(LoginPolicyIDCol, policyEvent.Aggregate().ID), + }, + ), nil +} + +func (p *LoginPolicyProjection) reduceMFARemoved(event eventstore.EventReader) (*handler.Statement, error) { + var policyEvent policy.MultiFactorRemovedEvent + switch e := event.(type) { + case *iam.LoginPolicyMultiFactorRemovedEvent: + policyEvent = e.MultiFactorRemovedEvent + case *org.LoginPolicyMultiFactorRemovedEvent: + policyEvent = e.MultiFactorRemovedEvent + default: + logging.LogWithFields("HANDL-vtC31", "seq", event.Sequence(), "expectedTypes", []eventstore.EventType{org.LoginPolicyMultiFactorRemovedEventType, iam.LoginPolicyMultiFactorRemovedEventType}).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-czU7n", "reduce.wrong.event.type") + } + + return crdb.NewUpdateStatement( + &policyEvent, + []handler.Column{ + handler.NewCol(LoginPolicyChangeDateCol, policyEvent.CreationDate()), + handler.NewCol(LoginPolicySequenceCol, policyEvent.Sequence()), + crdb.NewArrayRemoveCol(LoginPolicyMFAsCol, policyEvent.MFAType), + }, + []handler.Condition{ + handler.NewCond(LoginPolicyIDCol, policyEvent.Aggregate().ID), + }, + ), nil +} + +func (p *LoginPolicyProjection) reduceLoginPolicyRemoved(event eventstore.EventReader) (*handler.Statement, error) { + e, ok := event.(*org.LoginPolicyRemovedEvent) + if !ok { + logging.LogWithFields("HANDL-gF5q6", "seq", event.Sequence(), "expectedType", org.LoginPolicyRemovedEventType).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-oRSvD", "reduce.wrong.event.type") + } + return crdb.NewDeleteStatement( + e, + []handler.Condition{ + handler.NewCond(LoginPolicyIDCol, e.Aggregate().ID), + }, + ), nil +} + +func (p *LoginPolicyProjection) reduce2FAAdded(event eventstore.EventReader) (*handler.Statement, error) { + var policyEvent policy.SecondFactorAddedEvent + switch e := event.(type) { + case *iam.LoginPolicySecondFactorAddedEvent: + policyEvent = e.SecondFactorAddedEvent + case *org.LoginPolicySecondFactorAddedEvent: + policyEvent = e.SecondFactorAddedEvent + default: + logging.LogWithFields("HANDL-dwadQ", "seq", event.Sequence(), "expectedTypes", []eventstore.EventType{org.LoginPolicySecondFactorAddedEventType, iam.LoginPolicySecondFactorAddedEventType}).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-agB2E", "reduce.wrong.event.type") + } + + return crdb.NewUpdateStatement( + &policyEvent, + []handler.Column{ + handler.NewCol(LoginPolicyChangeDateCol, policyEvent.CreationDate()), + handler.NewCol(LoginPolicySequenceCol, policyEvent.Sequence()), + crdb.NewArrayAppendCol(LoginPolicy2FAsCol, policyEvent.MFAType), + }, + []handler.Condition{ + handler.NewCond(LoginPolicyIDCol, policyEvent.Aggregate().ID), + }, + ), nil +} + +func (p *LoginPolicyProjection) reduce2FARemoved(event eventstore.EventReader) (*handler.Statement, error) { + var policyEvent policy.SecondFactorRemovedEvent + switch e := event.(type) { + case *iam.LoginPolicySecondFactorRemovedEvent: + policyEvent = e.SecondFactorRemovedEvent + case *org.LoginPolicySecondFactorRemovedEvent: + policyEvent = e.SecondFactorRemovedEvent + default: + logging.LogWithFields("HANDL-2IE8Y", "seq", event.Sequence(), "expectedTypes", []eventstore.EventType{org.LoginPolicySecondFactorRemovedEventType, iam.LoginPolicySecondFactorRemovedEventType}).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-KYJvA", "reduce.wrong.event.type") + } + + return crdb.NewUpdateStatement( + &policyEvent, + []handler.Column{ + handler.NewCol(LoginPolicyChangeDateCol, policyEvent.CreationDate()), + handler.NewCol(LoginPolicySequenceCol, policyEvent.Sequence()), + crdb.NewArrayRemoveCol(LoginPolicy2FAsCol, policyEvent.MFAType), + }, + []handler.Condition{ + handler.NewCond(LoginPolicyIDCol, policyEvent.Aggregate().ID), + }, + ), nil +} diff --git a/internal/query/projection/login_policy_test.go b/internal/query/projection/login_policy_test.go new file mode 100644 index 0000000000..2bf54675d7 --- /dev/null +++ b/internal/query/projection/login_policy_test.go @@ -0,0 +1,494 @@ +package projection + +import ( + "testing" + + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/eventstore/handler" + "github.com/caos/zitadel/internal/eventstore/repository" + "github.com/caos/zitadel/internal/repository/iam" + "github.com/caos/zitadel/internal/repository/org" +) + +func TestLoginPolicyProjection_reduces(t *testing.T) { + type args struct { + event func(t *testing.T) eventstore.EventReader + } + tests := []struct { + name string + args args + reduce func(event eventstore.EventReader) (*handler.Statement, error) + want wantReduce + }{ + { + name: "org.reduceLoginPolicyAdded", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.LoginPolicyAddedEventType), + org.AggregateType, + []byte(`{ + "allowUsernamePassword": true, + "allowRegister": true, + "allowExternalIdp": false, + "forceMFA": false, + "hidePasswordReset": true, + "passwordlessType": 1 +}`), + ), org.LoginPolicyAddedEventMapper), + }, + reduce: (&LoginPolicyProjection{}).reduceLoginPolicyAdded, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: LoginPolicyTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "INSERT INTO zitadel.projections.login_policies (aggregate_id, creation_date, change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, is_default, hide_password_reset) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", + expectedArgs: []interface{}{ + "agg-id", + anyArg{}, + anyArg{}, + uint64(15), + true, + true, + false, + false, + domain.PasswordlessTypeAllowed, + false, + true, + }, + }, + }, + }, + }, + }, + { + name: "org.reduceLoginPolicyChanged", + reduce: (&LoginPolicyProjection{}).reduceLoginPolicyChanged, + args: args{ + event: getEvent(testEvent( + repository.EventType(org.LoginPolicyChangedEventType), + org.AggregateType, + []byte(`{ + "allowUsernamePassword": true, + "allowRegister": true, + "allowExternalIdp": true, + "forceMFA": true, + "hidePasswordReset": true, + "passwordlessType": 1 +}`), + ), org.LoginPolicyChangedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: LoginPolicyTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE zitadel.projections.login_policies SET (change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, hide_password_reset) = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (aggregate_id = $9)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + true, + true, + true, + true, + domain.PasswordlessTypeAllowed, + true, + "agg-id", + }, + }, + }, + }, + }, + }, + { + name: "org.reduceMFAAdded", + reduce: (&LoginPolicyProjection{}).reduceMFAAdded, + args: args{ + event: getEvent(testEvent( + repository.EventType(org.LoginPolicyMultiFactorAddedEventType), + org.AggregateType, + []byte(`{ + "mfaType": 1 +}`), + ), org.MultiFactorAddedEventEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: LoginPolicyTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE zitadel.projections.login_policies SET (change_date, sequence, multi_factors) = ($1, $2, array_append(multi_factors, $3)) WHERE (aggregate_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + domain.MultiFactorTypeU2FWithPIN, + "agg-id", + }, + }, + }, + }, + }, + }, + { + name: "org.reduceMFARemoved", + reduce: (&LoginPolicyProjection{}).reduceMFARemoved, + args: args{ + event: getEvent(testEvent( + repository.EventType(org.LoginPolicyMultiFactorRemovedEventType), + org.AggregateType, + []byte(`{ + "mfaType": 1 + }`), + ), org.MultiFactorRemovedEventEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: LoginPolicyTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE zitadel.projections.login_policies SET (change_date, sequence, multi_factors) = ($1, $2, array_remove(multi_factors, $3)) WHERE (aggregate_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + domain.MultiFactorTypeU2FWithPIN, + "agg-id", + }, + }, + }, + }, + }, + }, + { + name: "org.reduceLoginPolicyRemoved", + reduce: (&LoginPolicyProjection{}).reduceLoginPolicyRemoved, + args: args{ + event: getEvent(testEvent( + repository.EventType(org.LoginPolicyRemovedEventType), + org.AggregateType, + nil, + ), org.LoginPolicyRemovedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: LoginPolicyTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.login_policies WHERE (aggregate_id = $1)", + expectedArgs: []interface{}{ + "agg-id", + }, + }, + }, + }, + }, + }, + { + name: "org.reduce2FAAdded", + reduce: (&LoginPolicyProjection{}).reduce2FAAdded, + args: args{ + event: getEvent(testEvent( + repository.EventType(org.LoginPolicySecondFactorAddedEventType), + org.AggregateType, + []byte(`{ + "mfaType": 2 + }`), + ), org.SecondFactorAddedEventEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: LoginPolicyTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE zitadel.projections.login_policies SET (change_date, sequence, second_factors) = ($1, $2, array_append(second_factors, $3)) WHERE (aggregate_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + domain.SecondFactorTypeU2F, + "agg-id", + }, + }, + }, + }, + }, + }, + { + name: "org.reduce2FARemoved", + reduce: (&LoginPolicyProjection{}).reduce2FARemoved, + args: args{ + event: getEvent(testEvent( + repository.EventType(org.LoginPolicySecondFactorRemovedEventType), + org.AggregateType, + []byte(`{ + "mfaType": 2 + }`), + ), org.SecondFactorRemovedEventEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: LoginPolicyTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE zitadel.projections.login_policies SET (change_date, sequence, second_factors) = ($1, $2, array_remove(second_factors, $3)) WHERE (aggregate_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + domain.SecondFactorTypeU2F, + "agg-id", + }, + }, + }, + }, + }, + }, + { + name: "iam.reduceLoginPolicyAdded", + reduce: (&LoginPolicyProjection{}).reduceLoginPolicyAdded, + args: args{ + event: getEvent(testEvent( + repository.EventType(iam.LoginPolicyAddedEventType), + iam.AggregateType, + []byte(`{ + "allowUsernamePassword": true, + "allowRegister": true, + "allowExternalIdp": false, + "forceMFA": false, + "hidePasswordReset": true, + "passwordlessType": 1 + }`), + ), iam.LoginPolicyAddedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("iam"), + sequence: 15, + previousSequence: 10, + projection: LoginPolicyTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "INSERT INTO zitadel.projections.login_policies (aggregate_id, creation_date, change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, is_default, hide_password_reset) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", + expectedArgs: []interface{}{ + "agg-id", + anyArg{}, + anyArg{}, + uint64(15), + true, + true, + false, + false, + domain.PasswordlessTypeAllowed, + true, + true, + }, + }, + }, + }, + }, + }, + { + name: "iam.reduceLoginPolicyChanged", + reduce: (&LoginPolicyProjection{}).reduceLoginPolicyChanged, + args: args{ + event: getEvent(testEvent( + repository.EventType(iam.LoginPolicyChangedEventType), + iam.AggregateType, + []byte(`{ + "allowUsernamePassword": true, + "allowRegister": true, + "allowExternalIdp": true, + "forceMFA": true, + "hidePasswordReset": true, + "passwordlessType": 1 + }`), + ), iam.LoginPolicyChangedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("iam"), + sequence: 15, + previousSequence: 10, + projection: LoginPolicyTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE zitadel.projections.login_policies SET (change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, hide_password_reset) = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (aggregate_id = $9)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + true, + true, + true, + true, + domain.PasswordlessTypeAllowed, + true, + "agg-id", + }, + }, + }, + }, + }, + }, + { + name: "iam.reduceMFAAdded", + reduce: (&LoginPolicyProjection{}).reduceMFAAdded, + args: args{ + event: getEvent(testEvent( + repository.EventType(iam.LoginPolicyMultiFactorAddedEventType), + iam.AggregateType, + []byte(`{ + "mfaType": 1 + }`), + ), iam.MultiFactorAddedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("iam"), + sequence: 15, + previousSequence: 10, + projection: LoginPolicyTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE zitadel.projections.login_policies SET (change_date, sequence, multi_factors) = ($1, $2, array_append(multi_factors, $3)) WHERE (aggregate_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + domain.MultiFactorTypeU2FWithPIN, + "agg-id", + }, + }, + }, + }, + }, + }, + { + name: "iam.reduceMFARemoved", + reduce: (&LoginPolicyProjection{}).reduceMFARemoved, + args: args{ + event: getEvent(testEvent( + repository.EventType(iam.LoginPolicyMultiFactorRemovedEventType), + iam.AggregateType, + []byte(`{ + "mfaType": 1 + }`), + ), iam.MultiFactorRemovedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("iam"), + sequence: 15, + previousSequence: 10, + projection: LoginPolicyTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE zitadel.projections.login_policies SET (change_date, sequence, multi_factors) = ($1, $2, array_remove(multi_factors, $3)) WHERE (aggregate_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + domain.MultiFactorTypeU2FWithPIN, + "agg-id", + }, + }, + }, + }, + }, + }, + { + name: "iam.reduce2FAAdded", + reduce: (&LoginPolicyProjection{}).reduce2FAAdded, + args: args{ + event: getEvent(testEvent( + repository.EventType(iam.LoginPolicySecondFactorAddedEventType), + iam.AggregateType, + []byte(`{ + "mfaType": 2 + }`), + ), iam.SecondFactorAddedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("iam"), + sequence: 15, + previousSequence: 10, + projection: LoginPolicyTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE zitadel.projections.login_policies SET (change_date, sequence, second_factors) = ($1, $2, array_append(second_factors, $3)) WHERE (aggregate_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + domain.SecondFactorTypeU2F, + "agg-id", + }, + }, + }, + }, + }, + }, + { + name: "iam.reduce2FARemoved", + reduce: (&LoginPolicyProjection{}).reduce2FARemoved, + args: args{ + event: getEvent(testEvent( + repository.EventType(iam.LoginPolicySecondFactorRemovedEventType), + iam.AggregateType, + []byte(`{ + "mfaType": 2 + }`), + ), iam.SecondFactorRemovedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("iam"), + sequence: 15, + previousSequence: 10, + projection: LoginPolicyTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE zitadel.projections.login_policies SET (change_date, sequence, second_factors) = ($1, $2, array_remove(second_factors, $3)) WHERE (aggregate_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + domain.SecondFactorTypeU2F, + "agg-id", + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + event := baseEvent(t) + got, err := tt.reduce(event) + if _, ok := err.(errors.InvalidArgument); !ok { + t.Errorf("no wrong event mapping: %v, got: %v", err, got) + } + + event = tt.args.event(t) + got, err = tt.reduce(event) + assertReduce(t, got, err, tt.want) + }) + } +} diff --git a/internal/query/projection/projection.go b/internal/query/projection/projection.go index bf1f071404..9ba258c2a1 100644 --- a/internal/query/projection/projection.go +++ b/internal/query/projection/projection.go @@ -41,6 +41,7 @@ func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, co NewProjectRoleProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["project_roles"])) // owner.NewOrgOwnerProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["org_owners"])) NewOrgDomainProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["org_domains"])) + NewLoginPolicyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["login_policies"])) return nil } diff --git a/internal/repository/policy/login.go b/internal/repository/policy/login.go index 8c7037bb2c..016a256906 100644 --- a/internal/repository/policy/login.go +++ b/internal/repository/policy/login.go @@ -79,9 +79,6 @@ type LoginPolicyChangedEvent struct { PasswordlessType *domain.PasswordlessType `json:"passwordlessType,omitempty"` } -type LoginPolicyEventData struct { -} - func (e *LoginPolicyChangedEvent) Data() interface{} { return e } diff --git a/migrations/cockroach/V1.78__login_policies_projection.sql b/migrations/cockroach/V1.78__login_policies_projection.sql new file mode 100644 index 0000000000..d8cf482dc1 --- /dev/null +++ b/migrations/cockroach/V1.78__login_policies_projection.sql @@ -0,0 +1,20 @@ +CREATE TABLE zitadel.projections.login_policies ( + aggregate_id TEXT, + + creation_date TIMESTAMPTZ, + change_date TIMESTAMPTZ, + sequence BIGINT, + + allow_register BOOLEAN, + allow_username_password BOOLEAN, + allow_external_idps BOOLEAN, + + force_mfa BOOLEAN, + second_factors SMALLINT ARRAY, + multi_factors SMALLINT ARRAY, + passwordless_type SMALLINT, + is_default BOOLEAN, + hide_password_reset BOOLEAN, + + PRIMARY KEY (aggregate_id) +); \ No newline at end of file diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto index 374a462e69..0894f13c3f 100644 --- a/proto/zitadel/admin.proto +++ b/proto/zitadel/admin.proto @@ -2899,6 +2899,7 @@ message GetCustomOrgIAMPolicyRequest { message GetCustomOrgIAMPolicyResponse { zitadel.policy.v1.OrgIAMPolicy policy = 1; + //deprecated: is_default is also defined in zitadel.policy.v1.OrgIAMPolicy bool is_default = 2; } diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto index ea917092d4..794cb2d7ed 100644 --- a/proto/zitadel/management.proto +++ b/proto/zitadel/management.proto @@ -4221,6 +4221,7 @@ message GetLoginPolicyRequest {} message GetLoginPolicyResponse { zitadel.policy.v1.LoginPolicy policy = 1; + //deprecated: is_default is also defined in zitadel.policy.v1.LoginPolicy bool is_default = 2; } @@ -4337,6 +4338,7 @@ message GetPasswordComplexityPolicyRequest {} message GetPasswordComplexityPolicyResponse { zitadel.policy.v1.PasswordComplexityPolicy policy = 1; + //deprecated: is_default is also defined in zitadel.policy.v1.PasswordComplexityPolicy bool is_default = 2; } @@ -4383,6 +4385,7 @@ message GetPasswordAgePolicyRequest {} message GetPasswordAgePolicyResponse { zitadel.policy.v1.PasswordAgePolicy policy = 1; + //deprecated: is_default is also defined in zitadel.policy.v1.PasswordAgePolicy bool is_default = 2; } @@ -4423,6 +4426,7 @@ message GetLockoutPolicyRequest {} message GetLockoutPolicyResponse { zitadel.policy.v1.LockoutPolicy policy = 1; + //deprecated: is_default is also defined in zitadel.policy.v1.LockoutPolicy bool is_default = 2; } @@ -4500,6 +4504,7 @@ message GetLabelPolicyRequest {} message GetLabelPolicyResponse { zitadel.policy.v1.LabelPolicy policy = 1; + //deprecated: is_default is also defined in zitadel.policy.v1.LabelPolicy bool is_default = 2; } @@ -4508,6 +4513,7 @@ message GetPreviewLabelPolicyRequest {} message GetPreviewLabelPolicyResponse { zitadel.policy.v1.LabelPolicy policy = 1; + //deprecated: is_default is also defined in zitadel.policy.v1.LabelPolicy bool is_default = 2; }