From c5b99274d7ccc118f9a946f4e6770fdb8581a9e2 Mon Sep 17 00:00:00 2001 From: Silvan Date: Mon, 28 Mar 2022 10:05:09 +0200 Subject: [PATCH] feat(cli): setup (#3267) * commander * commander * selber! * move to packages * fix(errors): implement Is interface * test: command * test: commands * add init steps * setup tenant * add default step yaml * possibility to set password * merge v2 into v2-commander * fix: rename iam command side to instance * fix: rename iam command side to instance * fix: rename iam command side to instance * fix: rename iam command side to instance * fix: search query builder can filter events in memory * fix: filters for add member * fix(setup): add `ExternalSecure` to config * chore: name iam to instance * fix: matching * remove unsued func * base url * base url * test(command): filter funcs * test: commands * fix: rename orgiampolicy to domain policy * start from init * commands * config * fix indexes and add constraints * fixes * fix: merge conflicts * fix: protos * fix: md files * setup * add deprecated org iam policy again * typo * fix search query * fix filter * Apply suggestions from code review * remove custom org from org setup * add todos for verification * change apps creation * simplify package structure * fix error * move preparation helper for tests * fix unique constraints * fix config mapping in setup * fix error handling in encryption_keys.go * fix projection config * fix query from old views to projection * fix setup of mgmt api * set iam project and fix instance projection * imports Co-authored-by: Livio Amstutz Co-authored-by: fabi --- cmd/admin/admin.go | 1 + cmd/admin/initialise/config.go | 20 +- cmd/admin/initialise/init.go | 33 +- .../sql/11_unique_constraints_table.sql | 3 +- cmd/admin/initialise/verify_database.go | 12 +- cmd/admin/initialise/verify_grant.go | 11 +- cmd/admin/initialise/verify_user.go | 11 +- cmd/admin/initialise/verify_zitadel.go | 2 +- .../setup/{01_projection_tables.go => 01.go} | 0 cmd/admin/setup/01_sql/adminapi.sql | 18 +- cmd/admin/setup/01_sql/auth.sql | 18 +- cmd/admin/setup/01_sql/authz.sql | 18 +- cmd/admin/setup/01_sql/notification.sql | 18 +- cmd/admin/setup/02.go | 22 + cmd/admin/setup/config.go | 47 ++- cmd/admin/setup/setup.go | 31 +- cmd/admin/setup/steps.yaml | 127 ++++++ cmd/admin/start/config.go | 66 +++ cmd/admin/start/encryption_keys.go | 2 +- cmd/admin/start/flags.go | 31 ++ cmd/admin/start/start.go | 80 +--- cmd/admin/start/start_from_init.go | 40 ++ cmd/defaults.yaml | 17 +- docs/docs/apis/proto/management.md | 2 +- internal/api/authz/context.go | 4 + internal/api/grpc/admin/domain_policy.go | 116 +++--- internal/api/grpc/management/org.go | 10 + internal/api/oidc/op.go | 2 +- .../eventsourcing/handler/handler.go | 6 + .../eventsourcing/handler/idp_providers.go | 16 +- .../repository/eventsourcing/handler/user.go | 8 +- .../handler/user_external_idps.go | 19 +- internal/command/command.go | 4 +- .../command/instance_custom_message_text.go | 4 +- .../instance_custom_message_text_model.go | 12 +- internal/command/instance_policy_domain.go | 2 +- .../command/instance_policy_domain_model.go | 12 +- .../command/instance_policy_domain_test.go | 20 +- internal/command/org.go | 16 +- internal/command/org_domain_test.go | 2 +- internal/command/org_policy_domain.go | 6 +- internal/command/org_policy_domain_model.go | 16 +- internal/command/org_policy_domain_test.go | 20 +- .../policy_password_complexity_model.go | 33 ++ internal/command/setup_step1.go | 329 --------------- internal/command/setup_step10.go | 38 -- internal/command/setup_step11.go | 40 -- internal/command/setup_step12.go | 56 --- internal/command/setup_step13.go | 33 -- internal/command/setup_step14.go | 48 --- internal/command/setup_step15.go | 33 -- internal/command/setup_step16.go | 40 -- internal/command/setup_step17.go | 36 -- internal/command/setup_step18.go | 36 -- internal/command/setup_step19.go | 212 ---------- internal/command/setup_step2.go | 42 -- internal/command/setup_step20.go | 26 -- internal/command/setup_step21.go | 105 ----- internal/command/setup_step3.go | 39 -- internal/command/setup_step4.go | 31 -- internal/command/setup_step5.go | 38 -- internal/command/setup_step6.go | 36 -- internal/command/setup_step7.go | 39 -- internal/command/setup_step8.go | 39 -- internal/command/setup_step9.go | 53 --- internal/command/user.go | 16 +- internal/command/user_human.go | 34 +- internal/command/user_human_init_model.go | 3 +- internal/command/user_human_model.go | 3 +- internal/command/user_human_otp.go | 2 +- internal/command/user_human_otp_test.go | 2 +- internal/command/user_human_test.go | 50 +-- internal/command/user_human_webauthn_model.go | 3 +- internal/command/user_machine.go | 6 +- internal/command/user_machine_test.go | 4 +- internal/command/user_model.go | 2 +- internal/command/user_test.go | 10 +- internal/command/v2/command.go | 36 ++ internal/command/v2/instance.go | 290 +++++++++++++ internal/command/v2/instance_domain_policy.go | 25 ++ .../command/v2/instance_email_template.go | 107 +++++ internal/command/v2/instance_label_policy.go | 45 ++ .../command/v2/instance_lockout_policy.go | 24 ++ internal/command/v2/instance_login_policy.go | 69 ++++ .../v2/instance_password_age_policy.go | 27 ++ .../v2/instance_password_complexity_policy.go | 33 ++ .../command/v2/instance_privacy_policy.go | 25 ++ internal/command/v2/org.go | 72 ++++ internal/command/v2/org_domain.go | 46 +++ internal/command/v2/org_domain_test.go | 133 ++++++ internal/command/v2/org_member.go | 65 +++ internal/command/v2/org_member_test.go | 249 +++++++++++ internal/command/v2/org_test.go | 57 +++ internal/command/v2/preparation/command.go | 80 ++++ .../command/v2/preparation/command_test.go | 176 ++++++++ internal/command/v2/preparation_test.go | 81 ++++ internal/command/v2/project.go | 75 ++++ internal/command/v2/project_app.go | 155 +++++++ internal/command/v2/project_app_test.go | 386 ++++++++++++++++++ internal/command/v2/project_test.go | 195 +++++++++ internal/command/v2/user.go | 40 ++ internal/command/v2/user_domain_policy.go | 50 +++ .../command/v2/user_domain_policy_test.go | 266 ++++++++++++ internal/command/v2/user_human.go | 97 +++++ internal/command/v2/user_human_test.go | 169 ++++++++ .../v2/user_password_complexity_policy.go | 50 +++ .../user_password_complexity_policy_test.go | 298 ++++++++++++++ internal/command/v2/user_test.go | 166 ++++++++ internal/config/hook/base64_to_bytes.go | 25 ++ internal/config/hook/tag_to_language.go | 26 ++ internal/database/database.go | 8 + internal/domain/application_oauth.go | 13 +- internal/domain/human_email.go | 9 +- internal/domain/org.go | 2 +- internal/domain/project.go | 6 + internal/errors/already_exists.go | 8 + internal/errors/caos_error.go | 18 +- internal/errors/deadline_exceeded.go | 8 + internal/errors/internal.go | 8 + internal/errors/invalid_argument.go | 8 + internal/errors/not_found.go | 8 + internal/errors/permission_denied.go | 8 + internal/errors/precondition_failed.go | 8 + internal/errors/unauthenticated.go | 8 + internal/errors/unavailable.go | 8 + internal/errors/unimplemented.go | 8 + internal/errors/unknown.go | 8 + internal/eventstore/aggregate.go | 18 + internal/eventstore/event.go | 9 + internal/eventstore/eventstore.go | 5 +- internal/eventstore/handler/crdb/init.go | 56 ++- internal/eventstore/repository/sql/crdb.go | 17 +- .../eventstore/repository/sql/crdb_test.go | 16 +- .../repository/sql/local_crdb_test.go | 4 +- .../repository/unique_constraint.go | 3 + internal/eventstore/search_query.go | 77 +++- internal/eventstore/search_query_test.go | 233 +++++++++++ internal/migration/command.go | 95 +++-- internal/migration/migration.go | 37 +- .../eventsourcing/handler/notify_user.go | 18 +- internal/query/instance_test.go | 54 +-- internal/query/projection/action.go | 2 +- internal/query/projection/app.go | 7 +- internal/query/projection/authn_key.go | 4 +- .../{org_iam_policy.go => domain_policy.go} | 26 +- ...m_policy_test.go => domain_policy_test.go} | 20 +- internal/query/projection/idp.go | 3 +- .../query/projection/idp_login_policy_link.go | 2 +- internal/query/projection/idp_user_link.go | 2 +- internal/query/projection/instance.go | 18 +- internal/query/projection/instance_member.go | 2 +- internal/query/projection/instance_test.go | 20 +- internal/query/projection/key.go | 1 + internal/query/projection/login_name.go | 30 +- internal/query/projection/login_name_test.go | 28 +- internal/query/projection/org.go | 4 +- internal/query/projection/org_member.go | 2 +- internal/query/projection/project.go | 2 +- internal/query/projection/project_grant.go | 4 +- .../query/projection/project_grant_member.go | 2 +- internal/query/projection/project_member.go | 2 +- internal/query/projection/projection.go | 5 +- internal/query/projection/user.go | 5 +- internal/query/projection/user_auth_method.go | 2 +- internal/query/projection/user_grant.go | 4 +- internal/query/projection/user_metadata.go | 2 +- .../projection/user_personal_access_token.go | 4 +- internal/repository/instance/eventstore.go | 4 +- internal/repository/instance/policy_domain.go | 32 +- internal/repository/org/eventstore.go | 6 +- internal/repository/org/policy_domain.go | 103 +++++ internal/repository/org/policy_org_domain.go | 103 ----- internal/setup/config.go | 64 --- internal/setup/setup.go | 42 -- proto/zitadel/management.proto | 2 +- 175 files changed, 5213 insertions(+), 2212 deletions(-) rename cmd/admin/setup/{01_projection_tables.go => 01.go} (100%) create mode 100644 cmd/admin/setup/02.go create mode 100644 cmd/admin/start/config.go create mode 100644 cmd/admin/start/flags.go create mode 100644 cmd/admin/start/start_from_init.go delete mode 100644 internal/command/setup_step1.go delete mode 100644 internal/command/setup_step10.go delete mode 100644 internal/command/setup_step11.go delete mode 100644 internal/command/setup_step12.go delete mode 100644 internal/command/setup_step13.go delete mode 100644 internal/command/setup_step14.go delete mode 100644 internal/command/setup_step15.go delete mode 100644 internal/command/setup_step16.go delete mode 100644 internal/command/setup_step17.go delete mode 100644 internal/command/setup_step18.go delete mode 100644 internal/command/setup_step19.go delete mode 100644 internal/command/setup_step2.go delete mode 100644 internal/command/setup_step20.go delete mode 100644 internal/command/setup_step21.go delete mode 100644 internal/command/setup_step3.go delete mode 100644 internal/command/setup_step4.go delete mode 100644 internal/command/setup_step5.go delete mode 100644 internal/command/setup_step6.go delete mode 100644 internal/command/setup_step7.go delete mode 100644 internal/command/setup_step8.go delete mode 100644 internal/command/setup_step9.go create mode 100644 internal/command/v2/command.go create mode 100644 internal/command/v2/instance.go create mode 100644 internal/command/v2/instance_domain_policy.go create mode 100644 internal/command/v2/instance_email_template.go create mode 100644 internal/command/v2/instance_label_policy.go create mode 100644 internal/command/v2/instance_lockout_policy.go create mode 100644 internal/command/v2/instance_login_policy.go create mode 100644 internal/command/v2/instance_password_age_policy.go create mode 100644 internal/command/v2/instance_password_complexity_policy.go create mode 100644 internal/command/v2/instance_privacy_policy.go create mode 100644 internal/command/v2/org.go create mode 100644 internal/command/v2/org_domain.go create mode 100644 internal/command/v2/org_domain_test.go create mode 100644 internal/command/v2/org_member.go create mode 100644 internal/command/v2/org_member_test.go create mode 100644 internal/command/v2/org_test.go create mode 100644 internal/command/v2/preparation/command.go create mode 100644 internal/command/v2/preparation/command_test.go create mode 100644 internal/command/v2/preparation_test.go create mode 100644 internal/command/v2/project.go create mode 100644 internal/command/v2/project_app.go create mode 100644 internal/command/v2/project_app_test.go create mode 100644 internal/command/v2/project_test.go create mode 100644 internal/command/v2/user.go create mode 100644 internal/command/v2/user_domain_policy.go create mode 100644 internal/command/v2/user_domain_policy_test.go create mode 100644 internal/command/v2/user_human.go create mode 100644 internal/command/v2/user_human_test.go create mode 100644 internal/command/v2/user_password_complexity_policy.go create mode 100644 internal/command/v2/user_password_complexity_policy_test.go create mode 100644 internal/command/v2/user_test.go create mode 100644 internal/config/hook/base64_to_bytes.go create mode 100644 internal/config/hook/tag_to_language.go rename internal/query/projection/{org_iam_policy.go => domain_policy.go} (85%) rename internal/query/projection/{org_iam_policy_test.go => domain_policy_test.go} (89%) create mode 100644 internal/repository/org/policy_domain.go delete mode 100644 internal/repository/org/policy_org_domain.go delete mode 100644 internal/setup/config.go delete mode 100644 internal/setup/setup.go diff --git a/cmd/admin/admin.go b/cmd/admin/admin.go index 07747dbd3a..f577df5d17 100644 --- a/cmd/admin/admin.go +++ b/cmd/admin/admin.go @@ -26,6 +26,7 @@ func New() *cobra.Command { initialise.New(), setup.New(), start.New(), + start.NewStartFromInit(), key.New(), ) diff --git a/cmd/admin/initialise/config.go b/cmd/admin/initialise/config.go index 0677fe4913..9cd260b0fd 100644 --- a/cmd/admin/initialise/config.go +++ b/cmd/admin/initialise/config.go @@ -1,13 +1,29 @@ package initialise -import "github.com/caos/zitadel/internal/database" +import ( + "github.com/caos/logging" + "github.com/caos/zitadel/internal/database" + "github.com/spf13/viper" +) type Config struct { Database database.Config AdminUser database.User + Log *logging.Config } -func adminConfig(config Config) database.Config { +func MustNewConfig(v *viper.Viper) *Config { + config := new(Config) + err := v.Unmarshal(config) + logging.OnError(err).Fatal("unable to read config") + + err = config.Log.SetLogger() + logging.OnError(err).Fatal("unable to set logger") + + return config +} + +func adminConfig(config *Config) database.Config { adminConfig := config.Database adminConfig.Username = config.AdminUser.Username adminConfig.Password = config.AdminUser.Password diff --git a/cmd/admin/initialise/init.go b/cmd/admin/initialise/init.go index a45e008912..268a5c4eb1 100644 --- a/cmd/admin/initialise/init.go +++ b/cmd/admin/initialise/init.go @@ -8,9 +8,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - //sql import - _ "github.com/lib/pq" - "github.com/caos/zitadel/internal/database" ) @@ -28,20 +25,10 @@ The user provided by flags needs priviledge to - see other users and create a new one if the user does not exist - grant all rights of the ZITADEL database to the user created if not yet set `, - RunE: func(cmd *cobra.Command, args []string) error { - config := Config{} - if err := viper.Unmarshal(&config); err != nil { - return err - } - if err := initialise(config, - VerifyUser(config.Database.User.Username, config.Database.User.Password), - VerifyDatabase(config.Database.Database), - VerifyGrant(config.Database.Database, config.Database.User.Username), - ); err != nil { - return err - } + Run: func(cmd *cobra.Command, args []string) { + config := MustNewConfig(viper.GetViper()) - return verifyZitadel(config.Database) + InitAll(config) }, } @@ -49,7 +36,19 @@ The user provided by flags needs priviledge to return cmd } -func initialise(config Config, steps ...func(*sql.DB) error) error { +func InitAll(config *Config) { + err := initialise(config, + VerifyUser(config.Database.Username, config.Database.Password), + VerifyDatabase(config.Database.Database), + VerifyGrant(config.Database.Database, config.Database.Username), + ) + logging.OnError(err).Fatal("unable to initialize the database") + + err = verifyZitadel(config.Database) + logging.OnError(err).Fatal("unable to initialize ZITADEL") +} + +func initialise(config *Config, steps ...func(*sql.DB) error) error { logging.Info("initialization started") db, err := database.Connect(adminConfig(config)) diff --git a/cmd/admin/initialise/sql/11_unique_constraints_table.sql b/cmd/admin/initialise/sql/11_unique_constraints_table.sql index 7f34148bc3..d7de6a25c6 100644 --- a/cmd/admin/initialise/sql/11_unique_constraints_table.sql +++ b/cmd/admin/initialise/sql/11_unique_constraints_table.sql @@ -1,5 +1,6 @@ CREATE TABLE eventstore.unique_constraints ( + instance_id TEXT, unique_type TEXT, unique_field TEXT, - PRIMARY KEY (unique_type, unique_field) + PRIMARY KEY (instance_id, unique_type, unique_field) ) diff --git a/cmd/admin/initialise/verify_database.go b/cmd/admin/initialise/verify_database.go index 178775149f..8111fa52c3 100644 --- a/cmd/admin/initialise/verify_database.go +++ b/cmd/admin/initialise/verify_database.go @@ -5,6 +5,7 @@ import ( _ "embed" "fmt" + "github.com/caos/logging" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -30,12 +31,11 @@ The user provided by flags needs priviledge to - see other users and create a new one if the user does not exist - grant all rights of the ZITADEL database to the user created if not yet set `, - RunE: func(cmd *cobra.Command, args []string) error { - config := Config{} - if err := viper.Unmarshal(&config); err != nil { - return err - } - return initialise(config, VerifyDatabase(config.Database.Database)) + Run: func(cmd *cobra.Command, args []string) { + config := MustNewConfig(viper.New()) + + err := initialise(config, VerifyDatabase(config.Database.Database)) + logging.OnError(err).Fatal("unable to initialize the database") }, } } diff --git a/cmd/admin/initialise/verify_grant.go b/cmd/admin/initialise/verify_grant.go index c00969c92f..d2d236d0a3 100644 --- a/cmd/admin/initialise/verify_grant.go +++ b/cmd/admin/initialise/verify_grant.go @@ -25,12 +25,11 @@ func newGrant() *cobra.Command { Prereqesits: - cockroachdb `, - RunE: func(cmd *cobra.Command, args []string) error { - config := Config{} - if err := viper.Unmarshal(&config); err != nil { - return err - } - return initialise(config, VerifyGrant(config.Database.Database, config.Database.User.Username)) + Run: func(cmd *cobra.Command, args []string) { + config := MustNewConfig(viper.New()) + + err := initialise(config, VerifyGrant(config.Database.Database, config.Database.User.Username)) + logging.OnError(err).Fatal("unable to set grant") }, } } diff --git a/cmd/admin/initialise/verify_user.go b/cmd/admin/initialise/verify_user.go index f57351cb50..5a0f4f31c9 100644 --- a/cmd/admin/initialise/verify_user.go +++ b/cmd/admin/initialise/verify_user.go @@ -29,12 +29,11 @@ The user provided by flags needs priviledge to - see other users and create a new one if the user does not exist - grant all rights of the ZITADEL database to the user created if not yet set `, - RunE: func(cmd *cobra.Command, args []string) error { - config := Config{} - if err := viper.Unmarshal(&config); err != nil { - return err - } - return initialise(config, VerifyUser(config.Database.User.Username, config.Database.User.Password)) + Run: func(cmd *cobra.Command, args []string) { + config := MustNewConfig(viper.New()) + + err := initialise(config, VerifyUser(config.Database.Username, config.Database.Password)) + logging.OnError(err).Fatal("unable to init user") }, } } diff --git a/cmd/admin/initialise/verify_zitadel.go b/cmd/admin/initialise/verify_zitadel.go index 261dbfd0ef..9a6a0bcabf 100644 --- a/cmd/admin/initialise/verify_zitadel.go +++ b/cmd/admin/initialise/verify_zitadel.go @@ -94,7 +94,7 @@ func VerifyZitadel(db *sql.DB) error { } func verifyZitadel(config database.Config) error { - logging.WithFields("database", config.Database).Info("verify database") + logging.WithFields("database", config.Database).Info("verify zitadel") db, err := database.Connect(config) if err != nil { return err diff --git a/cmd/admin/setup/01_projection_tables.go b/cmd/admin/setup/01.go similarity index 100% rename from cmd/admin/setup/01_projection_tables.go rename to cmd/admin/setup/01.go diff --git a/cmd/admin/setup/01_sql/adminapi.sql b/cmd/admin/setup/01_sql/adminapi.sql index 05d243210f..db5c3f37ba 100644 --- a/cmd/admin/setup/01_sql/adminapi.sql +++ b/cmd/admin/setup/01_sql/adminapi.sql @@ -3,27 +3,27 @@ CREATE SCHEMA adminapi; CREATE TABLE adminapi.locks ( locker_id TEXT, locked_until TIMESTAMPTZ(3), - projection_name TEXT, + view_name TEXT, - PRIMARY KEY (projection_name) + PRIMARY KEY (view_name) ); CREATE TABLE adminapi.current_sequences ( - projection_name TEXT, - aggregate_type TEXT, + view_name TEXT, current_sequence BIGINT, - timestamp TIMESTAMPTZ, + event_timestamp TIMESTAMPTZ, + last_successful_spooler_run TIMESTAMPTZ, - PRIMARY KEY (projection_name, aggregate_type) + PRIMARY KEY (view_name) ); CREATE TABLE adminapi.failed_events ( - projection_name TEXT, + view_name TEXT, failed_sequence BIGINT, failure_count SMALLINT, - error TEXT, + err_msg TEXT, - PRIMARY KEY (projection_name, failed_sequence) + PRIMARY KEY (view_name, failed_sequence) ); CREATE TABLE adminapi.styling ( diff --git a/cmd/admin/setup/01_sql/auth.sql b/cmd/admin/setup/01_sql/auth.sql index ec91cc3185..a96dc84ae1 100644 --- a/cmd/admin/setup/01_sql/auth.sql +++ b/cmd/admin/setup/01_sql/auth.sql @@ -3,27 +3,27 @@ CREATE SCHEMA auth; CREATE TABLE auth.locks ( locker_id TEXT, locked_until TIMESTAMPTZ(3), - projection_name TEXT, + view_name TEXT, - PRIMARY KEY (projection_name) + PRIMARY KEY (view_name) ); CREATE TABLE auth.current_sequences ( - projection_name TEXT, - aggregate_type TEXT, + view_name TEXT, current_sequence BIGINT, - timestamp TIMESTAMPTZ, + event_timestamp TIMESTAMPTZ, + last_successful_spooler_run TIMESTAMPTZ, - PRIMARY KEY (projection_name, aggregate_type) + PRIMARY KEY (view_name) ); CREATE TABLE auth.failed_events ( - projection_name TEXT, + view_name TEXT, failed_sequence BIGINT, failure_count SMALLINT, - error TEXT, + err_msg TEXT, - PRIMARY KEY (projection_name, failed_sequence) + PRIMARY KEY (view_name, failed_sequence) ); CREATE TABLE auth.users ( diff --git a/cmd/admin/setup/01_sql/authz.sql b/cmd/admin/setup/01_sql/authz.sql index 1e944b8007..78bb3c81d1 100644 --- a/cmd/admin/setup/01_sql/authz.sql +++ b/cmd/admin/setup/01_sql/authz.sql @@ -3,27 +3,27 @@ CREATE SCHEMA authz; CREATE TABLE authz.locks ( locker_id TEXT, locked_until TIMESTAMPTZ(3), - projection_name TEXT, + view_name TEXT, - PRIMARY KEY (projection_name) + PRIMARY KEY (view_name) ); CREATE TABLE authz.current_sequences ( - projection_name TEXT, - aggregate_type TEXT, + view_name TEXT, current_sequence BIGINT, - timestamp TIMESTAMPTZ, + event_timestamp TIMESTAMPTZ, + last_successful_spooler_run TIMESTAMPTZ, - PRIMARY KEY (projection_name, aggregate_type) + PRIMARY KEY (view_name) ); CREATE TABLE authz.failed_events ( - projection_name TEXT, + view_name TEXT, failed_sequence BIGINT, failure_count SMALLINT, - error TEXT, + err_msg TEXT, - PRIMARY KEY (projection_name, failed_sequence) + PRIMARY KEY (view_name, failed_sequence) ); CREATE TABLE authz.user_memberships ( diff --git a/cmd/admin/setup/01_sql/notification.sql b/cmd/admin/setup/01_sql/notification.sql index b9697179dd..6e3acf9b9e 100644 --- a/cmd/admin/setup/01_sql/notification.sql +++ b/cmd/admin/setup/01_sql/notification.sql @@ -3,27 +3,27 @@ CREATE SCHEMA notification; CREATE TABLE notification.locks ( locker_id TEXT, locked_until TIMESTAMPTZ(3), - projection_name TEXT, + view_name TEXT, - PRIMARY KEY (projection_name) + PRIMARY KEY (view_name) ); CREATE TABLE notification.current_sequences ( - projection_name TEXT, - aggregate_type TEXT, + view_name TEXT, current_sequence BIGINT, - timestamp TIMESTAMPTZ, + event_timestamp TIMESTAMPTZ, + last_successful_spooler_run TIMESTAMPTZ, - PRIMARY KEY (projection_name, aggregate_type) + PRIMARY KEY (view_name) ); CREATE TABLE notification.failed_events ( - projection_name TEXT, + view_name TEXT, failed_sequence BIGINT, failure_count SMALLINT, - error TEXT, + err_msg TEXT, - PRIMARY KEY (projection_name, failed_sequence) + PRIMARY KEY (view_name, failed_sequence) ); CREATE TABLE notification.notify_users ( diff --git a/cmd/admin/setup/02.go b/cmd/admin/setup/02.go new file mode 100644 index 0000000000..617ab05b7c --- /dev/null +++ b/cmd/admin/setup/02.go @@ -0,0 +1,22 @@ +package setup + +import ( + "context" + + command "github.com/caos/zitadel/internal/command/v2" +) + +type DefaultInstance struct { + cmd *command.Command + InstanceSetup command.InstanceSetup +} + +func (mig *DefaultInstance) Execute(ctx context.Context) error { + _, err := mig.cmd.SetUpInstance(ctx, &mig.InstanceSetup) + + return err +} + +func (mig *DefaultInstance) String() string { + return "02_default_instance" +} diff --git a/cmd/admin/setup/config.go b/cmd/admin/setup/config.go index 85878bc1ef..a28bd7d0cd 100644 --- a/cmd/admin/setup/config.go +++ b/cmd/admin/setup/config.go @@ -1,13 +1,58 @@ package setup import ( + "bytes" + + "github.com/caos/logging" + "github.com/mitchellh/mapstructure" + "github.com/spf13/viper" + + "github.com/caos/zitadel/internal/api/authz" + "github.com/caos/zitadel/internal/config/hook" + "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/database" ) type Config struct { - Database database.Config + Database database.Config + SystemDefaults systemdefaults.SystemDefaults + InternalAuthZ authz.Config + ExternalPort uint16 + ExternalDomain string + ExternalSecure bool + Log *logging.Config +} + +func MustNewConfig(v *viper.Viper) *Config { + config := new(Config) + err := v.Unmarshal(config) + logging.OnError(err).Fatal("unable to read config") + + err = config.Log.SetLogger() + logging.OnError(err).Fatal("unable to set logger") + + return config } type Steps struct { S1ProjectionTable *ProjectionTable + S2DefaultInstance *DefaultInstance +} + +func MustNewSteps(v *viper.Viper) *Steps { + v.SetConfigType("yaml") + err := v.ReadConfig(bytes.NewBuffer(defaultSteps)) + logging.OnError(err).Fatal("unable to read setup steps") + + steps := new(Steps) + err = v.Unmarshal(steps, + viper.DecodeHook(mapstructure.ComposeDecodeHookFunc( + hook.Base64ToBytesHookFunc(), + hook.TagToLanguageHookFunc(), + mapstructure.StringToTimeDurationHookFunc(), + mapstructure.StringToSliceHookFunc(","), + )), + ) + logging.OnError(err).Fatal("unable to read steps") + return steps } diff --git a/cmd/admin/setup/setup.go b/cmd/admin/setup/setup.go index 14494f2fb6..23ecd953b5 100644 --- a/cmd/admin/setup/setup.go +++ b/cmd/admin/setup/setup.go @@ -1,7 +1,6 @@ package setup import ( - "bytes" "context" _ "embed" @@ -9,6 +8,8 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + http_util "github.com/caos/zitadel/internal/api/http" + command "github.com/caos/zitadel/internal/command/v2" "github.com/caos/zitadel/internal/database" "github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/migration" @@ -27,32 +28,30 @@ func New() *cobra.Command { Requirements: - cockroachdb`, Run: func(cmd *cobra.Command, args []string) { - config := new(Config) - err := viper.Unmarshal(config) - logging.OnError(err).Fatal("unable to read config") + config := MustNewConfig(viper.GetViper()) + steps := MustNewSteps(viper.New()) - v := viper.New() - v.SetConfigType("yaml") - err = v.ReadConfig(bytes.NewBuffer(defaultSteps)) - logging.OnError(err).Fatal("unable to read setup steps") - - steps := new(Steps) - err = v.Unmarshal(steps) - logging.OnError(err).Fatal("unable to read steps") - - setup(config, steps) + Setup(config, steps) }, } } -func setup(config *Config, steps *Steps) { +func Setup(config *Config, steps *Steps) { dbClient, err := database.Connect(config.Database) logging.OnError(err).Fatal("unable to connect to database") eventstoreClient, err := eventstore.Start(dbClient) logging.OnError(err).Fatal("unable to start eventstore") + migration.RegisterMappers(eventstoreClient) + cmd := command.New(eventstoreClient, "localhost", config.SystemDefaults) + + steps.S2DefaultInstance.cmd = cmd steps.S1ProjectionTable = &ProjectionTable{dbClient: dbClient} + steps.S2DefaultInstance.InstanceSetup.Zitadel.IsDevMode = !config.ExternalSecure + steps.S2DefaultInstance.InstanceSetup.Zitadel.BaseURL = http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure) - migration.Migrate(context.Background(), eventstoreClient, steps.S1ProjectionTable) + ctx := context.Background() + migration.Migrate(ctx, eventstoreClient, steps.S1ProjectionTable) + migration.Migrate(ctx, eventstoreClient, steps.S2DefaultInstance) } diff --git a/cmd/admin/setup/steps.yaml b/cmd/admin/setup/steps.yaml index e69de29bb2..a7c44bf530 100644 --- a/cmd/admin/setup/steps.yaml +++ b/cmd/admin/setup/steps.yaml @@ -0,0 +1,127 @@ +S2DefaultInstance: + InstanceSetup: + Org: + Name: ZITADEL + Human: + UserName: zitadel-admin + FirstName: ZITADEL + LastName: Admin + NickName: + DisplayName: + Email: admin@zitadel.ch + PreferredLanguage: + Gender: + Phone: + Password: Password1! + PasswordComplexityPolicy: + MinLength: 8 + HasLowercase: true + HasUppercase: true + HasNumber: true + HasSymbol: true + PasswordAgePolicy: + ExpireWarnDays: 0 + MaxAgeDays: 0 + OrgIAMPolicy: + UserLoginMustBeDomain: true + LoginPolicy: + AllowUsernamePassword: true + AllowRegister: true + AllowExternalIDP: true + ForceMFA: false + HidePasswordReset: false + PasswordlessType: 1 #1: allowed 0: not allowed + PasswordCheckLifetime: 240h #10d + ExternalLoginCheckLifetime: 240h #10d + MfaInitSkipLifetime: 720h #30d + SecondFactorCheckLifetime: 18h + MultiFactorCheckLifetime: 12h + PrivacyPolicy: + TOSLink: https://docs.zitadel.ch/docs/legal/terms-of-service + PrivacyLink: https://docs.zitadel.ch/docs/legal/privacy-policy + HelpLink: '' + LockoutPolicy: + MaxAttempts: 0 + ShouldShowLockoutFailure: true + EmailTemplate: CjwhZG9jdHlwZSBodG1sPgo8aHRtbCB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgeG1sbnM6dj0idXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTp2bWwiIHhtbG5zOm89InVybjpzY2hlbWFzLW1pY3Jvc29mdC1jb206b2ZmaWNlOm9mZmljZSI+CjxoZWFkPgogIDx0aXRsZT4KCiAgPC90aXRsZT4KICA8IS0tW2lmICFtc29dPjwhLS0+CiAgPG1ldGEgaHR0cC1lcXVpdj0iWC1VQS1Db21wYXRpYmxlIiBjb250ZW50PSJJRT1lZGdlIj4KICA8IS0tPCFbZW5kaWZdLS0+CiAgPG1ldGEgaHR0cC1lcXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7IGNoYXJzZXQ9VVRGLTgiPgogIDxtZXRhIG5hbWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIGluaXRpYWwtc2NhbGU9MSI+CiAgPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KICAgICNvdXRsb29rIGEgeyBwYWRkaW5nOjA7IH0KICAgIGJvZHkgeyBtYXJnaW46MDtwYWRkaW5nOjA7LXdlYmtpdC10ZXh0LXNpemUtYWRqdXN0OjEwMCU7LW1zLXRleHQtc2l6ZS1hZGp1c3Q6MTAwJTsgfQogICAgdGFibGUsIHRkIHsgYm9yZGVyLWNvbGxhcHNlOmNvbGxhcHNlO21zby10YWJsZS1sc3BhY2U6MHB0O21zby10YWJsZS1yc3BhY2U6MHB0OyB9CiAgICBpbWcgeyBib3JkZXI6MDtoZWlnaHQ6YXV0bztsaW5lLWhlaWdodDoxMDAlOyBvdXRsaW5lOm5vbmU7dGV4dC1kZWNvcmF0aW9uOm5vbmU7LW1zLWludGVycG9sYXRpb24tbW9kZTpiaWN1YmljOyB9CiAgICBwIHsgZGlzcGxheTpibG9jazttYXJnaW46MTNweCAwOyB9CiAgPC9zdHlsZT4KICA8IS0tW2lmIG1zb10+CiAgPHhtbD4KICAgIDxvOk9mZmljZURvY3VtZW50U2V0dGluZ3M+CiAgICAgIDxvOkFsbG93UE5HLz4KICAgICAgPG86UGl4ZWxzUGVySW5jaD45NjwvbzpQaXhlbHNQZXJJbmNoPgogICAgPC9vOk9mZmljZURvY3VtZW50U2V0dGluZ3M+CiAgPC94bWw+CiAgPCFbZW5kaWZdLS0+CiAgPCEtLVtpZiBsdGUgbXNvIDExXT4KICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgogICAgLm1qLW91dGxvb2stZ3JvdXAtZml4IHsgd2lkdGg6MTAwJSAhaW1wb3J0YW50OyB9CiAgPC9zdHlsZT4KICA8IVtlbmRpZl0tLT4KCiAgPCEtLVtpZiAhbXNvXT48IS0tPgogIDxsaW5rIGhyZWY9Imh0dHBzOi8vZm9udHMuZ29vZ2xlYXBpcy5jb20vY3NzP2ZhbWlseT1VYnVudHU6MzAwLDQwMCw1MDAsNzAwIiByZWw9InN0eWxlc2hlZXQiIHR5cGU9InRleHQvY3NzIj4KICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgogICAgQGltcG9ydCB1cmwoaHR0cHM6Ly9mb250cy5nb29nbGVhcGlzLmNvbS9jc3M/ZmFtaWx5PVVidW50dTozMDAsNDAwLDUwMCw3MDApOwogIDwvc3R5bGU+CiAgPCEtLTwhW2VuZGlmXS0tPgoKCgogIDxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+CiAgICBAbWVkaWEgb25seSBzY3JlZW4gYW5kIChtaW4td2lkdGg6NDgwcHgpIHsKICAgICAgLm1qLWNvbHVtbi1wZXItMTAwIHsgd2lkdGg6MTAwJSAhaW1wb3J0YW50OyBtYXgtd2lkdGg6IDEwMCU7IH0KICAgICAgLm1qLWNvbHVtbi1wZXItNjAgeyB3aWR0aDo2MCUgIWltcG9ydGFudDsgbWF4LXdpZHRoOiA2MCU7IH0KICAgIH0KICA8L3N0eWxlPgoKCiAgPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCgoKICAgIEBtZWRpYSBvbmx5IHNjcmVlbiBhbmQgKG1heC13aWR0aDo0ODBweCkgewogICAgICB0YWJsZS5tai1mdWxsLXdpZHRoLW1vYmlsZSB7IHdpZHRoOiAxMDAlICFpbXBvcnRhbnQ7IH0KICAgICAgdGQubWotZnVsbC13aWR0aC1tb2JpbGUgeyB3aWR0aDogYXV0byAhaW1wb3J0YW50OyB9CiAgICB9CgogIDwvc3R5bGU+CiAgPHN0eWxlIHR5cGU9InRleHQvY3NzIj4uc2hhZG93IGEgewogICAgYm94LXNoYWRvdzogMHB4IDNweCAxcHggLTJweCByZ2JhKDAsIDAsIDAsIDAuMiksIDBweCAycHggMnB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTQpLCAwcHggMXB4IDVweCAwcHggcmdiYSgwLCAwLCAwLCAwLjEyKTsKICB9PC9zdHlsZT4KCiAge3tpZiAuRm9udFVSTH19CiAgPHN0eWxlPgogICAgQGZvbnQtZmFjZSB7CiAgICAgIGZvbnQtZmFtaWx5OiAne3suRm9udEZhbWlseX19JzsKICAgICAgZm9udC1zdHlsZTogbm9ybWFsOwogICAgICBmb250LWRpc3BsYXk6IHN3YXA7CiAgICAgIHNyYzogdXJsKHt7LkZvbnRVUkx9fSk7CiAgICB9CiAgPC9zdHlsZT4KICB7e2VuZH19Cgo8L2hlYWQ+Cjxib2R5IHN0eWxlPSJ3b3JkLXNwYWNpbmc6bm9ybWFsOyI+CgoKPGRpdgogICAgICAgIHN0eWxlPSIiCj4KCiAgPHRhYmxlCiAgICAgICAgICBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0iYmFja2dyb3VuZDp7ey5CYWNrZ3JvdW5kQ29sb3J9fTtiYWNrZ3JvdW5kLWNvbG9yOnt7LkJhY2tncm91bmRDb2xvcn19O3dpZHRoOjEwMCU7Ym9yZGVyLXJhZGl1czoxNnB4OyIKICA+CiAgICA8dGJvZHk+CiAgICA8dHI+CiAgICAgIDx0ZD4KCgogICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjx0YWJsZSBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgY2xhc3M9IiIgc3R5bGU9IndpZHRoOjgwMHB4OyIgd2lkdGg9IjgwMCIgPjx0cj48dGQgc3R5bGU9ImxpbmUtaGVpZ2h0OjBweDtmb250LXNpemU6MHB4O21zby1saW5lLWhlaWdodC1ydWxlOmV4YWN0bHk7Ij48IVtlbmRpZl0tLT4KCgogICAgICAgIDxkaXYgIHN0eWxlPSJtYXJnaW46MHB4IGF1dG87Ym9yZGVyLXJhZGl1czoxNnB4O21heC13aWR0aDo4MDBweDsiPgoKICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0id2lkdGg6MTAwJTtib3JkZXItcmFkaXVzOjE2cHg7IgogICAgICAgICAgPgogICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICA8dGQKICAgICAgICAgICAgICAgICAgICAgIHN0eWxlPSJkaXJlY3Rpb246bHRyO2ZvbnQtc2l6ZTowcHg7cGFkZGluZzoyMHB4IDA7cGFkZGluZy1sZWZ0OjA7dGV4dC1hbGlnbjpjZW50ZXI7IgogICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjx0YWJsZSByb2xlPSJwcmVzZW50YXRpb24iIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIj48dHI+PHRkIGNsYXNzPSIiIHdpZHRoPSI4MDBweCIgPjwhW2VuZGlmXS0tPgoKICAgICAgICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICAgICAgICBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0id2lkdGg6MTAwJTsiCiAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD4KCgogICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+PHRhYmxlIGFsaWduPSJjZW50ZXIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiBjbGFzcz0iIiBzdHlsZT0id2lkdGg6ODAwcHg7IiB3aWR0aD0iODAwIiA+PHRyPjx0ZCBzdHlsZT0ibGluZS1oZWlnaHQ6MHB4O2ZvbnQtc2l6ZTowcHg7bXNvLWxpbmUtaGVpZ2h0LXJ1bGU6ZXhhY3RseTsiPjwhW2VuZGlmXS0tPgoKCiAgICAgICAgICAgICAgICAgICAgICA8ZGl2ICBzdHlsZT0ibWFyZ2luOjBweCBhdXRvO21heC13aWR0aDo4MDBweDsiPgoKICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9IndpZHRoOjEwMCU7IgogICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHlsZT0iZGlyZWN0aW9uOmx0cjtmb250LXNpemU6MHB4O3BhZGRpbmc6MDt0ZXh0LWFsaWduOmNlbnRlcjsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjx0YWJsZSByb2xlPSJwcmVzZW50YXRpb24iIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIj48dHI+PHRkIGNsYXNzPSIiIHN0eWxlPSJ3aWR0aDo4MDBweDsiID48IVtlbmRpZl0tLT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGFzcz0ibWotY29sdW1uLXBlci0xMDAgbWotb3V0bG9vay1ncm91cC1maXgiIHN0eWxlPSJmb250LXNpemU6MDtsaW5lLWhlaWdodDowO3RleHQtYWxpZ246bGVmdDtkaXNwbGF5OmlubGluZS1ibG9jazt3aWR0aDoxMDAlO2RpcmVjdGlvbjpsdHI7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+PHRhYmxlIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iID48dHI+PHRkIHN0eWxlPSJ2ZXJ0aWNhbC1hbGlnbjp0b3A7d2lkdGg6ODAwcHg7IiA+PCFbZW5kaWZdLS0+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsYXNzPSJtai1jb2x1bW4tcGVyLTEwMCBtai1vdXRsb29rLWdyb3VwLWZpeCIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7dGV4dC1hbGlnbjpsZWZ0O2RpcmVjdGlvbjpsdHI7ZGlzcGxheTppbmxpbmUtYmxvY2s7dmVydGljYWwtYWxpZ246dG9wO3dpZHRoOjEwMCU7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgd2lkdGg9IjEwMCUiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCAgc3R5bGU9InZlcnRpY2FsLWFsaWduOnRvcDtwYWRkaW5nOjA7Ij4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9IiIgd2lkdGg9IjEwMCUiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsaWduPSJjZW50ZXIiIHN0eWxlPSJmb250LXNpemU6MHB4O3BhZGRpbmc6NTBweCAwIDMwcHggMDt3b3JkLWJyZWFrOmJyZWFrLXdvcmQ7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9ImJvcmRlci1jb2xsYXBzZTpjb2xsYXBzZTtib3JkZXItc3BhY2luZzowcHg7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgIHN0eWxlPSJ3aWR0aDoxODBweDsiPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxpbWcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGVpZ2h0PSJhdXRvIiBzcmM9Int7LkxvZ29VUkx9fSIgc3R5bGU9ImJvcmRlcjowO2JvcmRlci1yYWRpdXM6OHB4O2Rpc3BsYXk6YmxvY2s7b3V0bGluZTpub25lO3RleHQtZGVjb3JhdGlvbjpub25lO2hlaWdodDphdXRvO3dpZHRoOjEwMCU7Zm9udC1zaXplOjEzcHg7IiB3aWR0aD0iMTgwIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLz4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgoKCiAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KCgogICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjwvdGQ+PC90cj48dHI+PHRkIGNsYXNzPSIiIHdpZHRoPSI4MDBweCIgPjwhW2VuZGlmXS0tPgoKICAgICAgICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICAgICAgICBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0id2lkdGg6MTAwJTsiCiAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD4KCgogICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+PHRhYmxlIGFsaWduPSJjZW50ZXIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiBjbGFzcz0iIiBzdHlsZT0id2lkdGg6ODAwcHg7IiB3aWR0aD0iODAwIiA+PHRyPjx0ZCBzdHlsZT0ibGluZS1oZWlnaHQ6MHB4O2ZvbnQtc2l6ZTowcHg7bXNvLWxpbmUtaGVpZ2h0LXJ1bGU6ZXhhY3RseTsiPjwhW2VuZGlmXS0tPgoKCiAgICAgICAgICAgICAgICAgICAgICA8ZGl2ICBzdHlsZT0ibWFyZ2luOjBweCBhdXRvO21heC13aWR0aDo4MDBweDsiPgoKICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9IndpZHRoOjEwMCU7IgogICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHlsZT0iZGlyZWN0aW9uOmx0cjtmb250LXNpemU6MHB4O3BhZGRpbmc6MDt0ZXh0LWFsaWduOmNlbnRlcjsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjx0YWJsZSByb2xlPSJwcmVzZW50YXRpb24iIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIj48dHI+PHRkIGNsYXNzPSIiIHN0eWxlPSJ2ZXJ0aWNhbC1hbGlnbjp0b3A7d2lkdGg6NDgwcHg7IiA+PCFbZW5kaWZdLS0+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2xhc3M9Im1qLWNvbHVtbi1wZXItNjAgbWotb3V0bG9vay1ncm91cC1maXgiIHN0eWxlPSJmb250LXNpemU6MHB4O3RleHQtYWxpZ246bGVmdDtkaXJlY3Rpb246bHRyO2Rpc3BsYXk6aW5saW5lLWJsb2NrO3ZlcnRpY2FsLWFsaWduOnRvcDt3aWR0aDoxMDAlOyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHdpZHRoPSIxMDAlIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkICBzdHlsZT0idmVydGljYWwtYWxpZ246dG9wO3BhZGRpbmc6MDsiPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHN0eWxlPSIiIHdpZHRoPSIxMDAlIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsaWduPSJjZW50ZXIiIHN0eWxlPSJmb250LXNpemU6MHB4O3BhZGRpbmc6MTBweCAyNXB4O3dvcmQtYnJlYWs6YnJlYWstd29yZDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3R5bGU9ImZvbnQtZmFtaWx5Ont7LkZvbnRGYW1pbHl9fTtmb250LXNpemU6MjRweDtmb250LXdlaWdodDo1MDA7bGluZS1oZWlnaHQ6MTt0ZXh0LWFsaWduOmNlbnRlcjtjb2xvcjp7ey5Gb250Q29sb3J9fTsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPnt7LkdyZWV0aW5nfX08L2Rpdj4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzoxMHB4IDI1cHg7d29yZC1icmVhazpicmVhay13b3JkOyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHlsZT0iZm9udC1mYW1pbHk6e3suRm9udEZhbWlseX19O2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OmxpZ2h0O2xpbmUtaGVpZ2h0OjEuNTt0ZXh0LWFsaWduOmNlbnRlcjtjb2xvcjp7ey5Gb250Q29sb3J9fTsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPnt7LlRleHR9fTwvZGl2PgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgdmVydGljYWwtYWxpZ249Im1pZGRsZSIgY2xhc3M9InNoYWRvdyIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzoxMHB4IDI1cHg7d29yZC1icmVhazpicmVhay13b3JkOyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHN0eWxlPSJib3JkZXItY29sbGFwc2U6c2VwYXJhdGU7bGluZS1oZWlnaHQ6MTAwJTsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbGlnbj0iY2VudGVyIiBiZ2NvbG9yPSJ7ey5QcmltYXJ5Q29sb3J9fSIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0iYm9yZGVyOm5vbmU7Ym9yZGVyLXJhZGl1czo2cHg7Y3Vyc29yOmF1dG87bXNvLXBhZGRpbmctYWx0OjEwcHggMjVweDtiYWNrZ3JvdW5kOnt7LlByaW1hcnlDb2xvcn19OyIgdmFsaWduPSJtaWRkbGUiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8YQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaHJlZj0ie3suVVJMfX0iIHJlbD0ibm9vcGVuZXIgbm9yZWZlcnJlciIgc3R5bGU9ImRpc3BsYXk6aW5saW5lLWJsb2NrO2JhY2tncm91bmQ6e3suUHJpbWFyeUNvbG9yfX07Y29sb3I6I2ZmZmZmZjtmb250LWZhbWlseTpVYnVudHUsIEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7Zm9udC1zaXplOjE0cHg7Zm9udC13ZWlnaHQ6NTAwO2xpbmUtaGVpZ2h0OjEyMCU7bWFyZ2luOjA7dGV4dC1kZWNvcmF0aW9uOm5vbmU7dGV4dC10cmFuc2Zvcm06bm9uZTtwYWRkaW5nOjEwcHggMjVweDttc28tcGFkZGluZy1hbHQ6MHB4O2JvcmRlci1yYWRpdXM6NnB4OyIgdGFyZ2V0PSJfYmxhbmsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAge3suQnV0dG9uVGV4dH19CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9hPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7e2lmIC5JbmNsdWRlRm9vdGVyfX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzoxMHB4IDI1cHg7cGFkZGluZy10b3A6MjBweDtwYWRkaW5nLXJpZ2h0OjIwcHg7cGFkZGluZy1ib3R0b206MjBweDtwYWRkaW5nLWxlZnQ6MjBweDt3b3JkLWJyZWFrOmJyZWFrLXdvcmQ7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxwCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHlsZT0iYm9yZGVyLXRvcDpzb2xpZCAycHggI2RiZGJkYjtmb250LXNpemU6MXB4O21hcmdpbjowcHggYXV0bzt3aWR0aDoxMDAlOyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9wPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48dGFibGUgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHN0eWxlPSJib3JkZXItdG9wOnNvbGlkIDJweCAjZGJkYmRiO2ZvbnQtc2l6ZToxcHg7bWFyZ2luOjBweCBhdXRvO3dpZHRoOjQ0MHB4OyIgcm9sZT0icHJlc2VudGF0aW9uIiB3aWR0aD0iNDQwcHgiID48dHI+PHRkIHN0eWxlPSJoZWlnaHQ6MDtsaW5lLWhlaWdodDowOyI+ICZuYnNwOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+PC90cj48L3RhYmxlPjwhW2VuZGlmXS0tPgoKCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzoxNnB4O3dvcmQtYnJlYWs6YnJlYWstd29yZDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3R5bGU9ImZvbnQtZmFtaWx5Ont7LkZvbnRGYW1pbHl9fTtmb250LXNpemU6MTNweDtsaW5lLWhlaWdodDoxO3RleHQtYWxpZ246Y2VudGVyO2NvbG9yOnt7LkZvbnRDb2xvcn19OyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+e3suRm9vdGVyVGV4dH19PC9kaXY+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHt7ZW5kfX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgoKCiAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KCgogICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjwvdGQ+PC90cj48L3RhYmxlPjwhW2VuZGlmXS0tPgogICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICA8L2Rpdj4KCgogICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjwvdGQ+PC90cj48L3RhYmxlPjwhW2VuZGlmXS0tPgoKCiAgICAgIDwvdGQ+CiAgICA8L3RyPgogICAgPC90Ym9keT4KICA8L3RhYmxlPgoKPC9kaXY+Cgo8L2JvZHk+CjwvaHRtbD4KICA= + MessageTexts: + - MessageTextType: InitCode + Language: de + Title: Zitadel - User initialisieren + PreHeader: User initialisieren + Subject: User initialisieren + Greeting: Hallo {{.FirstName}} {{.LastName}}, + Text: Dieser Benutzer wurde soeben im Zitadel erstellt. Mit dem Benutzernamen <br><strong>{{.PreferredLoginName}}</strong><br> kannst du dich anmelden. Nutze den untenstehenden Button, um die Initialisierung abzuschliessen <br>(Code <strong>{{.Code}}</strong>).<br> Falls du dieses Mail nicht angefordert hast, kannst du es einfach ignorieren. + ButtonText: Initialisierung abschliessen + - MessageTextType: PasswordReset + Language: de + Title: Zitadel - Passwort zurücksetzen + PreHeader: Passwort zurücksetzen + Subject: Passwort zurücksetzen + Greeting: Hallo {{.FirstName}} {{.LastName}}, + Text: Wir haben eine Anfrage für das Zurücksetzen deines Passwortes bekommen. Du kannst den untenstehenden Button verwenden, um dein Passwort zurückzusetzen <br>(Code <strong>{{.Code}}</strong>).<br> Falls du dieses Mail nicht angefordert hast, kannst du es ignorieren. + ButtonText: Passwort zurücksetzen + - MessageTextType: VerifyEmail + Language: de + Title: Zitadel - Email verifizieren + PreHeader: Email verifizieren + Subject: Email verifizieren + Greeting: Hallo {{.FirstName}} {{.LastName}}, + Text: Eine neue E-Mail Adresse wurde hinzugefügt. Bitte verwende den untenstehenden Button um diese zu verifizieren <br>(Code <strong>{{.Code}}</strong>).<br> Falls du deine E-Mail Adresse nicht selber hinzugefügt hast, kannst du dieses E-Mail ignorieren. + ButtonText: Email verifizieren + - MessageTextType: VerifyPhone + Language: de + Title: Zitadel - Telefonnummer verifizieren + PreHeader: Telefonnummer verifizieren + Subject: Telefonnummer verifizieren + Greeting: Hallo {{.FirstName}} {{.LastName}}, + Text: Eine Telefonnummer wurde hinzugefügt. Bitte verifiziere diese in dem du folgenden Code eingibst (Code {{.Code}}) + ButtonText: Telefon verifizieren + - MessageTextType: DomainClaimed + Language: de + Title: Zitadel - Domain wurde beansprucht + PreHeader: Email / Username ändern + Subject: Domain wurde beansprucht + Greeting: Hallo {{.FirstName}} {{.LastName}}, + Text: Die Domain {{.Domain}} wurde von einer Organisation beansprucht. Dein derzeitiger User {{.Username}} ist nicht Teil dieser Organisation. Daher musst du beim nächsten Login eine neue Email hinterlegen. Für diesen Login haben wir dir einen temporären Usernamen ({{.TempUsername}}) erstellt. + ButtonText: Login + - MessageTextType: InitCode + Language: en + Title: Zitadel - Initialize User + PreHeader: Initialize User + Subject: Initialize User + Greeting: Hello {{.FirstName}} {{.LastName}}, + Text: This user was created in Zitadel. Use the username {{.PreferredLoginName}} to login. Please click the button below to finish the initialization process. (Code {{.Code}}) If you didn't ask for this mail, please ignore it. + ButtonText: Finish initialization + - MessageTextType: PasswordReset + Language: en + Title: Zitadel - Reset password + PreHeader: Reset password + Subject: Reset password + Greeting: Hello {{.FirstName}} {{.LastName}}, + Text: We received a password reset request. Please use the button below to reset your password. (Code {{.Code}}) If you didn't ask for this mail, please ignore it. + ButtonText: Reset password + - MessageTextType: VerifyEmail + Language: en + Title: Zitadel - Verify email + PreHeader: Verify email + Subject: Verify email + Greeting: Hello {{.FirstName}} {{.LastName}}, + Text: A new email has been added. Please use the button below to verify your mail. (Code {{.Code}}) If you din't add a new email, please ignore this email. + ButtonText: Verify email + - MessageTextType: VerifyPhone + Language: en + Title: Zitadel - Verify phone + PreHeader: Verify phone + Subject: Verify phone + Greeting: Hello {{.FirstName}} {{.LastName}}, + Text: A new phonenumber has been added. Please use the following code to verify it {{.Code}}. + ButtonText: Verify phone + - MessageTextType: DomainClaimed + Language: en + Title: Zitadel - Domain has been claimed + PreHeader: Change email / username + Subject: Domain has been claimed + Greeting: Hello {{.FirstName}} {{.LastName}}, + Text: The domain {{.Domain}} has been claimed by an organisation. Your current user {{.UserName}} is not part of this organisation. Therefore you'll have to change your email when you login. We have created a temporary username ({{.TempUsername}}) for this login. + ButtonText: Login diff --git a/cmd/admin/start/config.go b/cmd/admin/start/config.go new file mode 100644 index 0000000000..bd941bcbc5 --- /dev/null +++ b/cmd/admin/start/config.go @@ -0,0 +1,66 @@ +package start + +import ( + "github.com/caos/logging" + admin_es "github.com/caos/zitadel/internal/admin/repository/eventsourcing" + internal_authz "github.com/caos/zitadel/internal/api/authz" + "github.com/caos/zitadel/internal/api/http/middleware" + "github.com/caos/zitadel/internal/api/oidc" + "github.com/caos/zitadel/internal/api/ui/console" + "github.com/caos/zitadel/internal/api/ui/login" + auth_es "github.com/caos/zitadel/internal/auth/repository/eventsourcing" + "github.com/caos/zitadel/internal/authz" + "github.com/caos/zitadel/internal/config/systemdefaults" + "github.com/caos/zitadel/internal/crypto" + "github.com/caos/zitadel/internal/database" + "github.com/caos/zitadel/internal/notification" + "github.com/caos/zitadel/internal/query/projection" + static_config "github.com/caos/zitadel/internal/static/config" + "github.com/spf13/viper" +) + +type Config struct { + Log *logging.Config + Port uint16 + ExternalPort uint16 + ExternalDomain string + ExternalSecure bool + Database database.Config + Projections projection.Config + AuthZ authz.Config + Auth auth_es.Config + Admin admin_es.Config + UserAgentCookie *middleware.UserAgentCookieConfig + OIDC oidc.Config + Login login.Config + Console console.Config + Notification notification.Config + AssetStorage static_config.AssetStorageConfig + InternalAuthZ internal_authz.Config + SystemDefaults systemdefaults.SystemDefaults + EncryptionKeys *encryptionKeyConfig +} + +func MustNewConfig(v *viper.Viper) *Config { + config := new(Config) + + err := v.Unmarshal(config) + logging.OnError(err).Fatal("unable to read config") + + err = config.Log.SetLogger() + logging.OnError(err).Fatal("unable to set logger") + + return config +} + +type encryptionKeyConfig struct { + DomainVerification *crypto.KeyConfig + IDPConfig *crypto.KeyConfig + OIDC *crypto.KeyConfig + OTP *crypto.KeyConfig + SMS *crypto.KeyConfig + SMTP *crypto.KeyConfig + User *crypto.KeyConfig + CSRFCookieKeyID string + UserAgentCookieKeyID string +} diff --git a/cmd/admin/start/encryption_keys.go b/cmd/admin/start/encryption_keys.go index 433881589e..bb3f94aebb 100644 --- a/cmd/admin/start/encryption_keys.go +++ b/cmd/admin/start/encryption_keys.go @@ -35,7 +35,7 @@ type encryptionKeys struct { func ensureEncryptionKeys(keyConfig *encryptionKeyConfig, keyStorage crypto.KeyStorage) (*encryptionKeys, error) { keys, err := keyStorage.ReadKeys() if err != nil { - return nil, nil + return nil, err } if len(keys) == 0 { if err := createDefaultKeys(keyStorage); err != nil { diff --git a/cmd/admin/start/flags.go b/cmd/admin/start/flags.go new file mode 100644 index 0000000000..b09d462cb1 --- /dev/null +++ b/cmd/admin/start/flags.go @@ -0,0 +1,31 @@ +package start + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func startFlags(cmd *cobra.Command) { + bindUint16Flag(cmd, "port", "port to run ZITADEL on") + bindStringFlag(cmd, "externalDomain", "domain ZITADEL will be exposed on") + bindStringFlag(cmd, "externalPort", "port ZITADEL will be exposed on") + bindBoolFlag(cmd, "externalSecure", "if ZITADEL will be served on HTTPS") + + cmd.PersistentFlags().String(flagMasterKey, "", "masterkey for en/decryption keys") + +} + +func bindStringFlag(cmd *cobra.Command, name, description string) { + cmd.PersistentFlags().String(name, viper.GetString(name), description) + viper.BindPFlag(name, cmd.PersistentFlags().Lookup(name)) +} + +func bindUint16Flag(cmd *cobra.Command, name, description string) { + cmd.PersistentFlags().Uint16(name, uint16(viper.GetUint(name)), description) + viper.BindPFlag(name, cmd.PersistentFlags().Lookup(name)) +} + +func bindBoolFlag(cmd *cobra.Command, name, description string) { + cmd.PersistentFlags().Bool(name, viper.GetBool(name), description) + viper.BindPFlag(name, cmd.PersistentFlags().Lookup(name)) +} diff --git a/cmd/admin/start/start.go b/cmd/admin/start/start.go index 0532a6d797..f8f7acab05 100644 --- a/cmd/admin/start/start.go +++ b/cmd/admin/start/start.go @@ -15,7 +15,6 @@ import ( "github.com/caos/logging" "github.com/gorilla/mux" - "github.com/mitchellh/mapstructure" "github.com/spf13/cobra" "github.com/spf13/viper" "golang.org/x/net/http2" @@ -37,17 +36,13 @@ import ( "github.com/caos/zitadel/internal/authz" authz_repo "github.com/caos/zitadel/internal/authz/repository" "github.com/caos/zitadel/internal/command" - "github.com/caos/zitadel/internal/config/systemdefaults" - "github.com/caos/zitadel/internal/crypto" cryptoDB "github.com/caos/zitadel/internal/crypto/database" "github.com/caos/zitadel/internal/database" "github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/id" "github.com/caos/zitadel/internal/notification" "github.com/caos/zitadel/internal/query" - "github.com/caos/zitadel/internal/query/projection" "github.com/caos/zitadel/internal/static" - static_config "github.com/caos/zitadel/internal/static/config" "github.com/caos/zitadel/internal/webauthn" "github.com/caos/zitadel/openapi" ) @@ -64,82 +59,19 @@ func New() *cobra.Command { Requirements: - cockroachdb`, RunE: func(cmd *cobra.Command, args []string) error { - config := new(startConfig) - err := viper.Unmarshal(config, viper.DecodeHook(mapstructure.ComposeDecodeHookFunc( - mapstructure.StringToTimeDurationHookFunc(), - mapstructure.StringToSliceHookFunc(":"), - ))) - if err != nil { - return err - } - err = config.Log.SetLogger() - if err != nil { - return err - } - masterKey, _ := cmd.Flags().GetString("masterkey") + config := MustNewConfig(viper.GetViper()) + masterKey, _ := cmd.Flags().GetString(flagMasterKey) + return startZitadel(config, masterKey) }, } - bindUint16Flag(start, "port", "port to run ZITADEL on") - bindStringFlag(start, "externalDomain", "domain ZITADEL will be exposed on") - bindStringFlag(start, "externalPort", "port ZITADEL will be exposed on") - bindBoolFlag(start, "externalSecure", "if ZITADEL will be served on HTTPS") - start.PersistentFlags().String(flagMasterKey, "", "masterkey for en/decryption keys") + startFlags(start) return start } -func bindStringFlag(cmd *cobra.Command, name, description string) { - cmd.PersistentFlags().String(name, viper.GetString(name), description) - viper.BindPFlag(name, cmd.PersistentFlags().Lookup(name)) -} - -func bindUint16Flag(cmd *cobra.Command, name, description string) { - cmd.PersistentFlags().Uint16(name, uint16(viper.GetUint(name)), description) - viper.BindPFlag(name, cmd.PersistentFlags().Lookup(name)) -} - -func bindBoolFlag(cmd *cobra.Command, name, description string) { - cmd.PersistentFlags().Bool(name, viper.GetBool(name), description) - viper.BindPFlag(name, cmd.PersistentFlags().Lookup(name)) -} - -type startConfig struct { - Log *logging.Config - Port uint16 - ExternalPort uint16 - ExternalDomain string - ExternalSecure bool - Database database.Config - Projections projection.Config - AuthZ authz.Config - Auth auth_es.Config - Admin admin_es.Config - UserAgentCookie *middleware.UserAgentCookieConfig - OIDC oidc.Config - Login login.Config - Console console.Config - Notification notification.Config - AssetStorage static_config.AssetStorageConfig - InternalAuthZ internal_authz.Config - SystemDefaults systemdefaults.SystemDefaults - EncryptionKeys *encryptionKeyConfig -} - -type encryptionKeyConfig struct { - DomainVerification *crypto.KeyConfig - IDPConfig *crypto.KeyConfig - OIDC *crypto.KeyConfig - OTP *crypto.KeyConfig - SMS *crypto.KeyConfig - SMTP *crypto.KeyConfig - User *crypto.KeyConfig - CSRFCookieKeyID string - UserAgentCookieKeyID string -} - -func startZitadel(config *startConfig, masterKey string) error { +func startZitadel(config *Config, masterKey string) error { ctx := context.Background() keyChan := make(chan interface{}) @@ -197,7 +129,7 @@ func startZitadel(config *startConfig, masterKey string) error { return listen(ctx, router, config.Port) } -func startAPIs(ctx context.Context, router *mux.Router, commands *command.Commands, queries *query.Queries, eventstore *eventstore.Eventstore, dbClient *sql.DB, keyChan chan interface{}, config *startConfig, store static.Storage, authZRepo authz_repo.Repository, keys *encryptionKeys) error { +func startAPIs(ctx context.Context, router *mux.Router, commands *command.Commands, queries *query.Queries, eventstore *eventstore.Eventstore, dbClient *sql.DB, keyChan chan interface{}, config *Config, store static.Storage, authZRepo authz_repo.Repository, keys *encryptionKeys) error { repo := struct { authz_repo.Repository *query.Queries diff --git a/cmd/admin/start/start_from_init.go b/cmd/admin/start/start_from_init.go new file mode 100644 index 0000000000..273707ab2f --- /dev/null +++ b/cmd/admin/start/start_from_init.go @@ -0,0 +1,40 @@ +package start + +import ( + "github.com/caos/logging" + "github.com/caos/zitadel/cmd/admin/initialise" + "github.com/caos/zitadel/cmd/admin/setup" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func NewStartFromInit() *cobra.Command { + cmd := &cobra.Command{ + Use: "start-from-init", + Short: "cold starts zitadel", + Long: `cold starts ZITADEL. +First the minimum requirements to start ZITADEL are set up. +Second the initial events are created. +Last ZITADEL starts. + +Requirements: +- cockroachdb`, + Run: func(cmd *cobra.Command, args []string) { + initialise.InitAll(initialise.MustNewConfig(viper.GetViper())) + + setupConfig := setup.MustNewConfig(viper.GetViper()) + setupSteps := setup.MustNewSteps(viper.New()) + setup.Setup(setupConfig, setupSteps) + + startConfig := MustNewConfig(viper.GetViper()) + startMasterKey, _ := cmd.Flags().GetString(flagMasterKey) + + err := startZitadel(startConfig, startMasterKey) + logging.OnError(err).Fatal("unable to start zitadel") + }, + } + + startFlags(cmd) + + return cmd +} diff --git a/cmd/defaults.yaml b/cmd/defaults.yaml index ebcb750c77..6faf23e62f 100644 --- a/cmd/defaults.yaml +++ b/cmd/defaults.yaml @@ -35,15 +35,14 @@ AdminUser: Key: "" Projections: - Config: - RequeueEvery: 10s - RetryFailedAfter: 1s - MaxFailureCount: 5 - BulkLimit: 200 - MaxIterators: 1 - Customizations: - projects: - BulkLimit: 2000 + RequeueEvery: 10s + RetryFailedAfter: 1s + MaxFailureCount: 5 + BulkLimit: 200 + MaxIterators: 1 + Customizations: + projects: + BulkLimit: 2000 AuthZ: Repository: diff --git a/docs/docs/apis/proto/management.md b/docs/docs/apis/proto/management.md index b608867de4..a49e8fd689 100644 --- a/docs/docs/apis/proto/management.md +++ b/docs/docs/apis/proto/management.md @@ -4865,7 +4865,7 @@ This is an empty request | Field | Type | Description | Validation | | ----- | ---- | ----------- | ----------- | -| policy | zitadel.policy.v1.DomainPolicy | - | | +| policy | zitadel.policy.v1.OrgIAMPolicy | - | | diff --git a/internal/api/authz/context.go b/internal/api/authz/context.go index b219f49fcd..0570e3f546 100644 --- a/internal/api/authz/context.go +++ b/internal/api/authz/context.go @@ -121,6 +121,10 @@ func GetInstance(ctx context.Context) Instance { return instance } +func WithInstance(ctx context.Context, instance Instance) context.Context { + return context.WithValue(ctx, instanceKey, instance) +} + func GetRequestPermissionsFromCtx(ctx context.Context) []string { ctxPermission, _ := ctx.Value(requestPermissionsKey).([]string) return ctxPermission diff --git a/internal/api/grpc/admin/domain_policy.go b/internal/api/grpc/admin/domain_policy.go index d3c4d17516..ae98a69464 100644 --- a/internal/api/grpc/admin/domain_policy.go +++ b/internal/api/grpc/admin/domain_policy.go @@ -26,64 +26,6 @@ func (s *Server) GetCustomDomainPolicy(ctx context.Context, req *admin_pb.GetCus return &admin_pb.GetCustomDomainPolicyResponse{Policy: policy_grpc.DomainPolicyToPb(policy)}, nil } -func (s *Server) AddCustomOrgIAMPolicy(ctx context.Context, req *admin_pb.AddCustomOrgIAMPolicyRequest) (*admin_pb.AddCustomOrgIAMPolicyResponse, error) { - policy, err := s.command.AddOrgDomainPolicy(ctx, req.OrgId, domainPolicyToDomain(req.UserLoginMustBeDomain)) - if err != nil { - return nil, err - } - return &admin_pb.AddCustomOrgIAMPolicyResponse{ - Details: object.AddToDetailsPb( - policy.Sequence, - policy.ChangeDate, - policy.ResourceOwner, - ), - }, nil -} - -func (s *Server) UpdateOrgIAMPolicy(ctx context.Context, req *admin_pb.UpdateOrgIAMPolicyRequest) (*admin_pb.UpdateOrgIAMPolicyResponse, error) { - config, err := s.command.ChangeDefaultDomainPolicy(ctx, updateOrgIAMPolicyToDomain(req)) - if err != nil { - return nil, err - } - return &admin_pb.UpdateOrgIAMPolicyResponse{ - Details: object.ChangeToDetailsPb( - config.Sequence, - config.ChangeDate, - config.ResourceOwner, - ), - }, nil -} - -func (s *Server) UpdateCustomOrgIAMPolicy(ctx context.Context, req *admin_pb.UpdateCustomOrgIAMPolicyRequest) (*admin_pb.UpdateCustomOrgIAMPolicyResponse, error) { - config, err := s.command.ChangeOrgDomainPolicy(ctx, req.OrgId, updateCustomOrgIAMPolicyToDomain(req)) - if err != nil { - return nil, err - } - return &admin_pb.UpdateCustomOrgIAMPolicyResponse{ - Details: object.ChangeToDetailsPb( - config.Sequence, - config.ChangeDate, - config.ResourceOwner, - ), - }, nil -} - -func (s *Server) GetOrgIAMPolicy(ctx context.Context, _ *admin_pb.GetOrgIAMPolicyRequest) (*admin_pb.GetOrgIAMPolicyResponse, error) { - policy, err := s.query.DefaultDomainPolicy(ctx) - if err != nil { - return nil, err - } - return &admin_pb.GetOrgIAMPolicyResponse{Policy: policy_grpc.DomainPolicyToOrgIAMPb(policy)}, nil -} - -func (s *Server) GetCustomOrgIAMPolicy(ctx context.Context, req *admin_pb.GetCustomOrgIAMPolicyRequest) (*admin_pb.GetCustomOrgIAMPolicyResponse, error) { - policy, err := s.query.DomainPolicyByOrg(ctx, req.OrgId) - if err != nil { - return nil, err - } - return &admin_pb.GetCustomOrgIAMPolicyResponse{Policy: policy_grpc.DomainPolicyToOrgIAMPb(policy)}, nil -} - func (s *Server) AddCustomDomainPolicy(ctx context.Context, req *admin_pb.AddCustomDomainPolicyRequest) (*admin_pb.AddCustomDomainPolicyResponse, error) { policy, err := s.command.AddOrgDomainPolicy(ctx, req.OrgId, domainPolicyToDomain(req.UserLoginMustBeDomain)) if err != nil { @@ -158,6 +100,64 @@ func updateCustomDomainPolicyToDomain(req *admin_pb.UpdateCustomDomainPolicyRequ } } +func (s *Server) AddCustomOrgIAMPolicy(ctx context.Context, req *admin_pb.AddCustomOrgIAMPolicyRequest) (*admin_pb.AddCustomOrgIAMPolicyResponse, error) { + policy, err := s.command.AddOrgDomainPolicy(ctx, req.OrgId, domainPolicyToDomain(req.UserLoginMustBeDomain)) + if err != nil { + return nil, err + } + return &admin_pb.AddCustomOrgIAMPolicyResponse{ + Details: object.AddToDetailsPb( + policy.Sequence, + policy.ChangeDate, + policy.ResourceOwner, + ), + }, nil +} + +func (s *Server) UpdateOrgIAMPolicy(ctx context.Context, req *admin_pb.UpdateOrgIAMPolicyRequest) (*admin_pb.UpdateOrgIAMPolicyResponse, error) { + config, err := s.command.ChangeDefaultDomainPolicy(ctx, updateOrgIAMPolicyToDomain(req)) + if err != nil { + return nil, err + } + return &admin_pb.UpdateOrgIAMPolicyResponse{ + Details: object.ChangeToDetailsPb( + config.Sequence, + config.ChangeDate, + config.ResourceOwner, + ), + }, nil +} + +func (s *Server) UpdateCustomOrgIAMPolicy(ctx context.Context, req *admin_pb.UpdateCustomOrgIAMPolicyRequest) (*admin_pb.UpdateCustomOrgIAMPolicyResponse, error) { + config, err := s.command.ChangeOrgDomainPolicy(ctx, req.OrgId, updateCustomOrgIAMPolicyToDomain(req)) + if err != nil { + return nil, err + } + return &admin_pb.UpdateCustomOrgIAMPolicyResponse{ + Details: object.ChangeToDetailsPb( + config.Sequence, + config.ChangeDate, + config.ResourceOwner, + ), + }, nil +} + +func (s *Server) GetOrgIAMPolicy(ctx context.Context, _ *admin_pb.GetOrgIAMPolicyRequest) (*admin_pb.GetOrgIAMPolicyResponse, error) { + policy, err := s.query.DefaultDomainPolicy(ctx) + if err != nil { + return nil, err + } + return &admin_pb.GetOrgIAMPolicyResponse{Policy: policy_grpc.DomainPolicyToOrgIAMPb(policy)}, nil +} + +func (s *Server) GetCustomOrgIAMPolicy(ctx context.Context, req *admin_pb.GetCustomOrgIAMPolicyRequest) (*admin_pb.GetCustomOrgIAMPolicyResponse, error) { + policy, err := s.query.DomainPolicyByOrg(ctx, req.OrgId) + if err != nil { + return nil, err + } + return &admin_pb.GetCustomOrgIAMPolicyResponse{Policy: policy_grpc.DomainPolicyToOrgIAMPb(policy)}, nil +} + func updateOrgIAMPolicyToDomain(req *admin_pb.UpdateOrgIAMPolicyRequest) *domain.DomainPolicy { return &domain.DomainPolicy{ UserLoginMustBeDomain: req.UserLoginMustBeDomain, diff --git a/internal/api/grpc/management/org.go b/internal/api/grpc/management/org.go index 2a5340bf25..c81f4b8d57 100644 --- a/internal/api/grpc/management/org.go +++ b/internal/api/grpc/management/org.go @@ -112,6 +112,16 @@ func (s *Server) GetDomainPolicy(ctx context.Context, req *mgmt_pb.GetDomainPoli }, nil } +func (s *Server) GetOrgIAMPolicy(ctx context.Context, _ *mgmt_pb.GetOrgIAMPolicyRequest) (*mgmt_pb.GetOrgIAMPolicyResponse, error) { + policy, err := s.query.DomainPolicyByOrg(ctx, authz.GetCtxData(ctx).OrgID) + if err != nil { + return nil, err + } + return &mgmt_pb.GetOrgIAMPolicyResponse{ + Policy: policy_grpc.DomainPolicyToOrgIAMPb(policy), + }, nil +} + func (s *Server) ListOrgDomains(ctx context.Context, req *mgmt_pb.ListOrgDomainsRequest) (*mgmt_pb.ListOrgDomainsResponse, error) { queries, err := ListOrgDomainsRequestToModel(req) if err != nil { diff --git a/internal/api/oidc/op.go b/internal/api/oidc/op.go index c5153824cd..2169b48dc1 100644 --- a/internal/api/oidc/op.go +++ b/internal/api/oidc/op.go @@ -100,7 +100,7 @@ func NewProvider(ctx context.Context, config Config, issuer, defaultLogoutRedire options..., ) if err != nil { - return nil, caos_errs.ThrowInternal(err, "OIDC-DAtg3", "cannot create provider: %w") + return nil, caos_errs.ThrowInternal(err, "OIDC-DAtg3", "cannot create provider") } return provider, nil } diff --git a/internal/auth/repository/eventsourcing/handler/handler.go b/internal/auth/repository/eventsourcing/handler/handler.go index 26abb93e6d..895782e93b 100644 --- a/internal/auth/repository/eventsourcing/handler/handler.go +++ b/internal/auth/repository/eventsourcing/handler/handler.go @@ -1,8 +1,10 @@ package handler import ( + "context" "time" + "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/auth/repository/eventsourcing/view" sd "github.com/caos/zitadel/internal/config/systemdefaults" v1 "github.com/caos/zitadel/internal/eventstore/v1" @@ -69,3 +71,7 @@ func (h *handler) LockDuration() time.Duration { func (h *handler) QueryLimit() uint64 { return h.bulkLimit } + +func withInstanceID(ctx context.Context, instanceID string) context.Context { + return authz.WithInstance(ctx, authz.Instance{ID: instanceID}) +} diff --git a/internal/auth/repository/eventsourcing/handler/idp_providers.go b/internal/auth/repository/eventsourcing/handler/idp_providers.go index 734e8dcec5..6f2e5363a0 100644 --- a/internal/auth/repository/eventsourcing/handler/idp_providers.go +++ b/internal/auth/repository/eventsourcing/handler/idp_providers.go @@ -121,9 +121,9 @@ func (i *IDPProvider) processIdpProvider(event *es_models.Event) (err error) { } config := new(query2.IDP) if event.AggregateID == domain.IAMID { - config, err = i.getDefaultIDPConfig(context.TODO(), esConfig.IDPConfigID) + config, err = i.getDefaultIDPConfig(event.InstanceID, esConfig.IDPConfigID) } else { - config, err = i.getOrgIDPConfig(context.TODO(), event.AggregateID, esConfig.IDPConfigID) + config, err = i.getOrgIDPConfig(event.InstanceID, event.AggregateID, esConfig.IDPConfigID) } if err != nil { return err @@ -146,9 +146,9 @@ func (i *IDPProvider) processIdpProvider(event *es_models.Event) (err error) { func (i *IDPProvider) fillData(provider *iam_view_model.IDPProviderView) (err error) { var config *query2.IDP if provider.IDPProviderType == int32(iam_model.IDPProviderTypeSystem) { - config, err = i.getDefaultIDPConfig(context.Background(), provider.IDPConfigID) + config, err = i.getDefaultIDPConfig(provider.InstanceID, provider.IDPConfigID) } else { - config, err = i.getOrgIDPConfig(context.Background(), provider.AggregateID, provider.IDPConfigID) + config, err = i.getOrgIDPConfig(provider.InstanceID, provider.AggregateID, provider.IDPConfigID) } if err != nil { return err @@ -186,10 +186,10 @@ func (i *IDPProvider) OnSuccess() error { return spooler.HandleSuccess(i.view.UpdateIDPProviderSpoolerRunTimestamp) } -func (i *IDPProvider) getOrgIDPConfig(ctx context.Context, aggregateID, idpConfigID string) (*query2.IDP, error) { - return i.queries.IDPByIDAndResourceOwner(ctx, idpConfigID, aggregateID) +func (i *IDPProvider) getOrgIDPConfig(instanceID, aggregateID, idpConfigID string) (*query2.IDP, error) { + return i.queries.IDPByIDAndResourceOwner(withInstanceID(context.Background(), instanceID), idpConfigID, aggregateID) } -func (u *IDPProvider) getDefaultIDPConfig(ctx context.Context, idpConfigID string) (*query2.IDP, error) { - return u.queries.IDPByIDAndResourceOwner(ctx, idpConfigID, domain.IAMID) +func (u *IDPProvider) getDefaultIDPConfig(instanceID, idpConfigID string) (*query2.IDP, error) { + return u.queries.IDPByIDAndResourceOwner(withInstanceID(context.Background(), instanceID), idpConfigID, domain.IAMID) } diff --git a/internal/auth/repository/eventsourcing/handler/user.go b/internal/auth/repository/eventsourcing/handler/user.go index 5d3fcfae51..b4605ca2f0 100644 --- a/internal/auth/repository/eventsourcing/handler/user.go +++ b/internal/auth/repository/eventsourcing/handler/user.go @@ -187,9 +187,9 @@ func (u *User) ProcessOrg(event *es_models.Event) (err error) { switch event.Type { case org_es_model.OrgDomainVerified, org_es_model.OrgDomainRemoved, - es_models.EventType(org.OrgDomainPolicyAddedEventType), - es_models.EventType(org.OrgDomainPolicyChangedEventType), - es_models.EventType(org.OrgDomainPolicyRemovedEventType): + es_models.EventType(org.DomainPolicyAddedEventType), + es_models.EventType(org.DomainPolicyChangedEventType), + es_models.EventType(org.DomainPolicyRemovedEventType): return u.fillLoginNamesOnOrgUsers(event) case org_es_model.OrgDomainPrimarySet: return u.fillPreferredLoginNamesOnOrgUsers(event) @@ -268,7 +268,7 @@ func (u *User) loginNameInformation(ctx context.Context, orgID string) (userLogi return false, "", nil, err } if org.DomainPolicy == nil { - policy, err := u.queries.DefaultDomainPolicy(ctx) + policy, err := u.queries.DefaultDomainPolicy(withInstanceID(ctx, org.InstanceID)) if err != nil { return false, "", nil, err } diff --git a/internal/auth/repository/eventsourcing/handler/user_external_idps.go b/internal/auth/repository/eventsourcing/handler/user_external_idps.go index 4cf545da4e..be384259fe 100644 --- a/internal/auth/repository/eventsourcing/handler/user_external_idps.go +++ b/internal/auth/repository/eventsourcing/handler/user_external_idps.go @@ -4,10 +4,11 @@ import ( "context" "github.com/caos/logging" + "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/domain" caos_errs "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/v1" + v1 "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" @@ -137,9 +138,9 @@ func (i *ExternalIDP) processIdpConfig(event *es_models.Event) (err error) { return err } if event.AggregateType == iam_es_model.IAMAggregate { - config, err = i.getDefaultIDPConfig(context.Background(), configView.IDPConfigID) + config, err = i.getDefaultIDPConfig(event.InstanceID, configView.IDPConfigID) } else { - config, err = i.getOrgIDPConfig(context.Background(), event.AggregateID, configView.IDPConfigID) + config, err = i.getOrgIDPConfig(event.InstanceID, event.AggregateID, configView.IDPConfigID) } if err != nil { return err @@ -155,9 +156,9 @@ func (i *ExternalIDP) processIdpConfig(event *es_models.Event) (err error) { } func (i *ExternalIDP) fillData(externalIDP *usr_view_model.ExternalIDPView) error { - config, err := i.getOrgIDPConfig(context.Background(), externalIDP.ResourceOwner, externalIDP.IDPConfigID) + config, err := i.getOrgIDPConfig(externalIDP.InstanceID, externalIDP.ResourceOwner, externalIDP.IDPConfigID) if caos_errs.IsNotFound(err) { - config, err = i.getDefaultIDPConfig(context.Background(), externalIDP.IDPConfigID) + config, err = i.getDefaultIDPConfig(externalIDP.InstanceID, externalIDP.IDPConfigID) } if err != nil { return err @@ -179,10 +180,10 @@ func (i *ExternalIDP) OnSuccess() error { return spooler.HandleSuccess(i.view.UpdateExternalIDPSpoolerRunTimestamp) } -func (i *ExternalIDP) getOrgIDPConfig(ctx context.Context, aggregateID, idpConfigID string) (*query2.IDP, error) { - return i.queries.IDPByIDAndResourceOwner(ctx, idpConfigID, aggregateID) +func (i *ExternalIDP) getOrgIDPConfig(instanceID, aggregateID, idpConfigID string) (*query2.IDP, error) { + return i.queries.IDPByIDAndResourceOwner(withInstanceID(context.Background(), instanceID), idpConfigID, aggregateID) } -func (i *ExternalIDP) getDefaultIDPConfig(ctx context.Context, idpConfigID string) (*query2.IDP, error) { - return i.queries.IDPByIDAndResourceOwner(ctx, idpConfigID, domain.IAMID) +func (i *ExternalIDP) getDefaultIDPConfig(instanceID, idpConfigID string) (*query2.IDP, error) { + return i.queries.IDPByIDAndResourceOwner(withInstanceID(context.Background(), instanceID), idpConfigID, domain.IAMID) } diff --git a/internal/command/command.go b/internal/command/command.go index 91210b1374..d59a4b3f8c 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -13,7 +13,7 @@ import ( "github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/id" "github.com/caos/zitadel/internal/repository/action" - iam_repo "github.com/caos/zitadel/internal/repository/instance" + instance_repo "github.com/caos/zitadel/internal/repository/instance" "github.com/caos/zitadel/internal/repository/keypair" "github.com/caos/zitadel/internal/repository/org" proj_repo "github.com/caos/zitadel/internal/repository/project" @@ -82,7 +82,7 @@ func StartCommands(es *eventstore.Eventstore, domainVerificationAlg: domainVerificationEncryption, keyAlgorithm: oidcEncryption, } - iam_repo.RegisterEventMappers(repo.eventstore) + instance_repo.RegisterEventMappers(repo.eventstore) org.RegisterEventMappers(repo.eventstore) usr_repo.RegisterEventMappers(repo.eventstore) usr_grant_repo.RegisterEventMappers(repo.eventstore) diff --git a/internal/command/instance_custom_message_text.go b/internal/command/instance_custom_message_text.go index 7c5c0184c1..3e2406433a 100644 --- a/internal/command/instance_custom_message_text.go +++ b/internal/command/instance_custom_message_text.go @@ -27,7 +27,7 @@ func (c *Commands) SetDefaultMessageText(ctx context.Context, messageText *domai return writeModelToObjectDetails(&existingMessageText.WriteModel), nil } -func (c *Commands) setDefaultMessageText(ctx context.Context, instanceAgg *eventstore.Aggregate, msg *domain.CustomMessageText) ([]eventstore.Command, *InstanceCustomMessageTextReadModel, error) { +func (c *Commands) setDefaultMessageText(ctx context.Context, instanceAgg *eventstore.Aggregate, msg *domain.CustomMessageText) ([]eventstore.Command, *InstanceCustomMessageTextWriteModel, error) { if !msg.IsValid() { return nil, nil, caos_errs.ThrowInvalidArgument(nil, "INSTANCE-kd9fs", "Errors.CustomMessageText.Invalid") } @@ -113,7 +113,7 @@ func (c *Commands) RemoveInstanceMessageTexts(ctx context.Context, messageTextTy return writeModelToObjectDetails(&customText.WriteModel), nil } -func (c *Commands) defaultCustomMessageTextWriteModelByID(ctx context.Context, messageType string, lang language.Tag) (*InstanceCustomMessageTextReadModel, error) { +func (c *Commands) defaultCustomMessageTextWriteModelByID(ctx context.Context, messageType string, lang language.Tag) (*InstanceCustomMessageTextWriteModel, error) { writeModel := NewInstanceCustomMessageTextWriteModel(messageType, lang) err := c.eventstore.FilterToQueryReducer(ctx, writeModel) if err != nil { diff --git a/internal/command/instance_custom_message_text_model.go b/internal/command/instance_custom_message_text_model.go index c082ce72fa..42cbe24769 100644 --- a/internal/command/instance_custom_message_text_model.go +++ b/internal/command/instance_custom_message_text_model.go @@ -8,12 +8,12 @@ import ( "github.com/caos/zitadel/internal/repository/instance" ) -type InstanceCustomMessageTextReadModel struct { +type InstanceCustomMessageTextWriteModel struct { CustomMessageTextReadModel } -func NewInstanceCustomMessageTextWriteModel(messageTextType string, lang language.Tag) *InstanceCustomMessageTextReadModel { - return &InstanceCustomMessageTextReadModel{ +func NewInstanceCustomMessageTextWriteModel(messageTextType string, lang language.Tag) *InstanceCustomMessageTextWriteModel { + return &InstanceCustomMessageTextWriteModel{ CustomMessageTextReadModel{ WriteModel: eventstore.WriteModel{ AggregateID: domain.IAMID, @@ -25,7 +25,7 @@ func NewInstanceCustomMessageTextWriteModel(messageTextType string, lang languag } } -func (wm *InstanceCustomMessageTextReadModel) AppendEvents(events ...eventstore.Event) { +func (wm *InstanceCustomMessageTextWriteModel) AppendEvents(events ...eventstore.Event) { for _, event := range events { switch e := event.(type) { case *instance.CustomTextSetEvent: @@ -38,11 +38,11 @@ func (wm *InstanceCustomMessageTextReadModel) AppendEvents(events ...eventstore. } } -func (wm *InstanceCustomMessageTextReadModel) Reduce() error { +func (wm *InstanceCustomMessageTextWriteModel) Reduce() error { return wm.CustomMessageTextReadModel.Reduce() } -func (wm *InstanceCustomMessageTextReadModel) Query() *eventstore.SearchQueryBuilder { +func (wm *InstanceCustomMessageTextWriteModel) Query() *eventstore.SearchQueryBuilder { return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). ResourceOwner(wm.ResourceOwner). AddQuery(). diff --git a/internal/command/instance_policy_domain.go b/internal/command/instance_policy_domain.go index ff0ae0bd4c..dd111ea316 100644 --- a/internal/command/instance_policy_domain.go +++ b/internal/command/instance_policy_domain.go @@ -37,7 +37,7 @@ func (c *Commands) addDefaultDomainPolicy(ctx context.Context, instanceAgg *even if addedPolicy.State == domain.PolicyStateActive { return nil, caos_errs.ThrowAlreadyExists(nil, "INSTANCE-Lk0dS", "Errors.IAM.DomainPolicy.AlreadyExists") } - return iam_repo.NewInstnaceDomainPolicyAddedEvent(ctx, instanceAgg, policy.UserLoginMustBeDomain), nil + return iam_repo.NewDomainPolicyAddedEvent(ctx, instanceAgg, policy.UserLoginMustBeDomain), nil } func (c *Commands) ChangeDefaultDomainPolicy(ctx context.Context, policy *domain.DomainPolicy) (*domain.DomainPolicy, error) { diff --git a/internal/command/instance_policy_domain_model.go b/internal/command/instance_policy_domain_model.go index 6ce5ef0e55..83c05367b0 100644 --- a/internal/command/instance_policy_domain_model.go +++ b/internal/command/instance_policy_domain_model.go @@ -28,9 +28,9 @@ func NewInstanceDomainPolicyWriteModel() *InstanceDomainPolicyWriteModel { func (wm *InstanceDomainPolicyWriteModel) AppendEvents(events ...eventstore.Event) { for _, event := range events { switch e := event.(type) { - case *instance.InstanceDomainPolicyAddedEvent: + case *instance.DomainPolicyAddedEvent: wm.PolicyDomainWriteModel.AppendEvents(&e.DomainPolicyAddedEvent) - case *instance.InstanceDomainPolicyChangedEvent: + case *instance.DomainPolicyChangedEvent: wm.PolicyDomainWriteModel.AppendEvents(&e.DomainPolicyChangedEvent) } } @@ -47,15 +47,15 @@ func (wm *InstanceDomainPolicyWriteModel) Query() *eventstore.SearchQueryBuilder AggregateTypes(instance.AggregateType). AggregateIDs(wm.PolicyDomainWriteModel.AggregateID). EventTypes( - instance.InstanceDomainPolicyAddedEventType, - instance.InstanceDomainPolicyChangedEventType). + instance.DomainPolicyAddedEventType, + instance.DomainPolicyChangedEventType). Builder() } func (wm *InstanceDomainPolicyWriteModel) NewChangedEvent( ctx context.Context, aggregate *eventstore.Aggregate, - userLoginMustBeDomain bool) (*instance.InstanceDomainPolicyChangedEvent, bool) { + userLoginMustBeDomain bool) (*instance.DomainPolicyChangedEvent, bool) { changes := make([]policy.OrgPolicyChanges, 0) if wm.UserLoginMustBeDomain != userLoginMustBeDomain { changes = append(changes, policy.ChangeUserLoginMustBeDomain(userLoginMustBeDomain)) @@ -63,7 +63,7 @@ func (wm *InstanceDomainPolicyWriteModel) NewChangedEvent( if len(changes) == 0 { return nil, false } - changedEvent, err := instance.NewInstanceDomainPolicyChangedEvent(ctx, aggregate, changes) + changedEvent, err := instance.NewDomainPolicyChangedEvent(ctx, aggregate, changes) if err != nil { return nil, false } diff --git a/internal/command/instance_policy_domain_test.go b/internal/command/instance_policy_domain_test.go index be016776d3..81b0467861 100644 --- a/internal/command/instance_policy_domain_test.go +++ b/internal/command/instance_policy_domain_test.go @@ -33,13 +33,13 @@ func TestCommandSide_AddDefaultDomainPolicy(t *testing.T) { res res }{ { - name: "orgiam policy already existing, already exists error", + name: "domain policy already existing, already exists error", fields: fields{ eventstore: eventstoreExpect( t, expectFilter( eventFromEventPusher( - instance.NewInstnaceDomainPolicyAddedEvent(context.Background(), + instance.NewDomainPolicyAddedEvent(context.Background(), &instance.NewAggregate().Aggregate, true, ), @@ -66,7 +66,7 @@ func TestCommandSide_AddDefaultDomainPolicy(t *testing.T) { expectPush( []*repository.Event{ eventFromEventPusher( - instance.NewInstnaceDomainPolicyAddedEvent(context.Background(), + instance.NewDomainPolicyAddedEvent(context.Background(), &instance.NewAggregate().Aggregate, true, ), @@ -111,7 +111,7 @@ func TestCommandSide_AddDefaultDomainPolicy(t *testing.T) { } } -func TestCommandSide_ChangeDefaultOrgIAMPolicy(t *testing.T) { +func TestCommandSide_ChangeDefaultDomainPolicy(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore } @@ -130,7 +130,7 @@ func TestCommandSide_ChangeDefaultOrgIAMPolicy(t *testing.T) { res res }{ { - name: "orgiampolicy not existing, not found error", + name: "domain policy not existing, not found error", fields: fields{ eventstore: eventstoreExpect( t, @@ -154,7 +154,7 @@ func TestCommandSide_ChangeDefaultOrgIAMPolicy(t *testing.T) { t, expectFilter( eventFromEventPusher( - instance.NewInstnaceDomainPolicyAddedEvent(context.Background(), + instance.NewDomainPolicyAddedEvent(context.Background(), &instance.NewAggregate().Aggregate, true, ), @@ -179,7 +179,7 @@ func TestCommandSide_ChangeDefaultOrgIAMPolicy(t *testing.T) { t, expectFilter( eventFromEventPusher( - instance.NewInstnaceDomainPolicyAddedEvent(context.Background(), + instance.NewDomainPolicyAddedEvent(context.Background(), &instance.NewAggregate().Aggregate, true, ), @@ -188,7 +188,7 @@ func TestCommandSide_ChangeDefaultOrgIAMPolicy(t *testing.T) { expectPush( []*repository.Event{ eventFromEventPusher( - newDefaultOrgIAMPolicyChangedEvent(context.Background(), false), + newDefaultDomainPolicyChangedEvent(context.Background(), false), ), }, ), @@ -230,8 +230,8 @@ func TestCommandSide_ChangeDefaultOrgIAMPolicy(t *testing.T) { } } -func newDefaultOrgIAMPolicyChangedEvent(ctx context.Context, userLoginMustBeDomain bool) *instance.InstanceDomainPolicyChangedEvent { - event, _ := instance.NewInstanceDomainPolicyChangedEvent(ctx, +func newDefaultDomainPolicyChangedEvent(ctx context.Context, userLoginMustBeDomain bool) *instance.DomainPolicyChangedEvent { + event, _ := instance.NewDomainPolicyChangedEvent(ctx, &instance.NewAggregate().Aggregate, []policy.OrgPolicyChanges{ policy.ChangeUserLoginMustBeDomain(userLoginMustBeDomain), diff --git a/internal/command/org.go b/internal/command/org.go index 602d2e3a39..4c05a014f2 100644 --- a/internal/command/org.go +++ b/internal/command/org.go @@ -32,19 +32,20 @@ func (c *Commands) checkOrgExists(ctx context.Context, orgID string) error { return nil } -func (c *Commands) SetUpOrg(ctx context.Context, organisation *domain.Org, admin *domain.Human, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator, claimedUserIDs []string, selfregistered bool) (*domain.ObjectDetails, error) { - orgIAMPolicy, err := c.getDefaultDomainPolicy(ctx) +func (c *Commands) SetUpOrg(ctx context.Context, organisation *domain.Org, admin *domain.Human, initCodeGenerator, phoneCodeGenerator crypto.Generator, claimedUserIDs []string, selfregistered bool) (*domain.ObjectDetails, error) { + domainPolicy, err := c.getDefaultDomainPolicy(ctx) if err != nil { - return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-33M9f", "Errors.IAM.DomainPolicy.NotFound") + return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-33M9f", "Errors.Instance.DomainPolicy.NotFound") } pwPolicy, err := c.getDefaultPasswordComplexityPolicy(ctx) if err != nil { - return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-M5Fsd", "Errors.IAM.PasswordComplexity.NotFound") + return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-M5Fsd", "Errors.Instance.PasswordComplexity.NotFound") } - _, orgWriteModel, _, _, events, err := c.setUpOrg(ctx, organisation, admin, orgIAMPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator, claimedUserIDs, selfregistered) + _, orgWriteModel, _, _, events, err := c.setUpOrg(ctx, organisation, admin, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator, claimedUserIDs, selfregistered) if err != nil { return nil, err } + pushedEvents, err := c.eventstore.Push(ctx, events...) if err != nil { return nil, err @@ -201,7 +202,7 @@ func (c *Commands) setUpOrg( } func (c *Commands) addOrg(ctx context.Context, organisation *domain.Org, claimedUserIDs []string) (_ *eventstore.Aggregate, _ *OrgWriteModel, _ []eventstore.Command, err error) { - if organisation == nil || !organisation.IsValid() { + if !organisation.IsValid() { return nil, nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMM-deLSk", "Errors.Org.Invalid") } @@ -220,9 +221,8 @@ func (c *Commands) addOrg(ctx context.Context, organisation *domain.Org, claimed orgDomainEvents, err := c.addOrgDomain(ctx, orgAgg, NewOrgDomainWriteModel(orgAgg.ID, orgDomain.Domain), orgDomain, claimedUserIDs) if err != nil { return nil, nil, nil, err - } else { - events = append(events, orgDomainEvents...) } + events = append(events, orgDomainEvents...) } return orgAgg, addedOrg, events, nil } diff --git a/internal/command/org_domain_test.go b/internal/command/org_domain_test.go index dfd4676be2..907d005aa0 100644 --- a/internal/command/org_domain_test.go +++ b/internal/command/org_domain_test.go @@ -819,7 +819,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) { ), expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &org.NewAggregate("org2", "org2").Aggregate, false))), expectPush( diff --git a/internal/command/org_policy_domain.go b/internal/command/org_policy_domain.go index ebdc4c3450..f237c26041 100644 --- a/internal/command/org_policy_domain.go +++ b/internal/command/org_policy_domain.go @@ -39,7 +39,7 @@ func (c *Commands) addOrgDomainPolicy(ctx context.Context, orgAgg *eventstore.Ag if addedPolicy.State == domain.PolicyStateActive { return nil, caos_errs.ThrowAlreadyExists(nil, "ORG-1M8ds", "Errors.Org.DomainPolicy.AlreadyExists") } - return org.NewOrgDomainPolicyAddedEvent(ctx, orgAgg, policy.UserLoginMustBeDomain), nil + return org.NewDomainPolicyAddedEvent(ctx, orgAgg, policy.UserLoginMustBeDomain), nil } func (c *Commands) ChangeOrgDomainPolicy(ctx context.Context, resourceOwner string, policy *domain.DomainPolicy) (*domain.DomainPolicy, error) { @@ -80,11 +80,11 @@ func (c *Commands) RemoveOrgDomainPolicy(ctx context.Context, orgID string) erro return err } if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved { - return caos_errs.ThrowNotFound(nil, "ORG-Dvsh3", "Errors.Org.OrgIAMPolicy.NotFound") + return caos_errs.ThrowNotFound(nil, "ORG-Dvsh3", "Errors.Org.DomainPolicy.NotFound") } orgAgg := OrgAggregateFromWriteModel(&existingPolicy.PolicyDomainWriteModel.WriteModel) - _, err = c.eventstore.Push(ctx, org.NewOrgDomainPolicyRemovedEvent(ctx, orgAgg)) + _, err = c.eventstore.Push(ctx, org.NewDomainPolicyRemovedEvent(ctx, orgAgg)) return err } diff --git a/internal/command/org_policy_domain_model.go b/internal/command/org_policy_domain_model.go index 11c94e997e..dfc90f9336 100644 --- a/internal/command/org_policy_domain_model.go +++ b/internal/command/org_policy_domain_model.go @@ -27,11 +27,11 @@ func NewOrgDomainPolicyWriteModel(orgID string) *OrgDomainPolicyWriteModel { func (wm *OrgDomainPolicyWriteModel) AppendEvents(events ...eventstore.Event) { for _, event := range events { switch e := event.(type) { - case *org.OrgDomainPolicyAddedEvent: + case *org.DomainPolicyAddedEvent: wm.PolicyDomainWriteModel.AppendEvents(&e.DomainPolicyAddedEvent) - case *org.OrgDomainPolicyChangedEvent: + case *org.DomainPolicyChangedEvent: wm.PolicyDomainWriteModel.AppendEvents(&e.DomainPolicyChangedEvent) - case *org.OrgDomainPolicyRemovedEvent: + case *org.DomainPolicyRemovedEvent: wm.PolicyDomainWriteModel.AppendEvents(&e.DomainPolicyRemovedEvent) } } @@ -47,16 +47,16 @@ func (wm *OrgDomainPolicyWriteModel) Query() *eventstore.SearchQueryBuilder { AddQuery(). AggregateTypes(org.AggregateType). AggregateIDs(wm.PolicyDomainWriteModel.AggregateID). - EventTypes(org.OrgDomainPolicyAddedEventType, - org.OrgDomainPolicyChangedEventType, - org.OrgDomainPolicyRemovedEventType). + EventTypes(org.DomainPolicyAddedEventType, + org.DomainPolicyChangedEventType, + org.DomainPolicyRemovedEventType). Builder() } func (wm *OrgDomainPolicyWriteModel) NewChangedEvent( ctx context.Context, aggregate *eventstore.Aggregate, - userLoginMustBeDomain bool) (*org.OrgDomainPolicyChangedEvent, bool) { + userLoginMustBeDomain bool) (*org.DomainPolicyChangedEvent, bool) { changes := make([]policy.OrgPolicyChanges, 0) if wm.UserLoginMustBeDomain != userLoginMustBeDomain { changes = append(changes, policy.ChangeUserLoginMustBeDomain(userLoginMustBeDomain)) @@ -64,7 +64,7 @@ func (wm *OrgDomainPolicyWriteModel) NewChangedEvent( if len(changes) == 0 { return nil, false } - changedEvent, err := org.NewOrgDomainPolicyChangedEvent(ctx, aggregate, changes) + changedEvent, err := org.NewDomainPolicyChangedEvent(ctx, aggregate, changes) if err != nil { return nil, false } diff --git a/internal/command/org_policy_domain_test.go b/internal/command/org_policy_domain_test.go index c6c313dcbc..b61f4f4a5d 100644 --- a/internal/command/org_policy_domain_test.go +++ b/internal/command/org_policy_domain_test.go @@ -58,7 +58,7 @@ func TestCommandSide_AddDomainPolicy(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, true, ), @@ -86,7 +86,7 @@ func TestCommandSide_AddDomainPolicy(t *testing.T) { expectPush( []*repository.Event{ eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, true, ), @@ -194,7 +194,7 @@ func TestCommandSide_ChangeDomainPolicy(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, true, ), @@ -220,7 +220,7 @@ func TestCommandSide_ChangeDomainPolicy(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, true, ), @@ -229,7 +229,7 @@ func TestCommandSide_ChangeDomainPolicy(t *testing.T) { expectPush( []*repository.Event{ eventFromEventPusher( - newOrgIAMPolicyChangedEvent(context.Background(), "org1", false), + newDomainPolicyChangedEvent(context.Background(), "org1", false), ), }, ), @@ -272,7 +272,7 @@ func TestCommandSide_ChangeDomainPolicy(t *testing.T) { } } -func TestCommandSide_RemoveOrgIAMPolicy(t *testing.T) { +func TestCommandSide_RemoveDomainPolicy(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore } @@ -327,7 +327,7 @@ func TestCommandSide_RemoveOrgIAMPolicy(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, true, ), @@ -336,7 +336,7 @@ func TestCommandSide_RemoveOrgIAMPolicy(t *testing.T) { expectPush( []*repository.Event{ eventFromEventPusher( - org.NewOrgDomainPolicyRemovedEvent(context.Background(), + org.NewDomainPolicyRemovedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate), ), }, @@ -370,8 +370,8 @@ func TestCommandSide_RemoveOrgIAMPolicy(t *testing.T) { } } -func newOrgIAMPolicyChangedEvent(ctx context.Context, orgID string, userLoginMustBeDomain bool) *org.OrgDomainPolicyChangedEvent { - event, _ := org.NewOrgDomainPolicyChangedEvent(ctx, +func newDomainPolicyChangedEvent(ctx context.Context, orgID string, userLoginMustBeDomain bool) *org.DomainPolicyChangedEvent { + event, _ := org.NewDomainPolicyChangedEvent(ctx, &org.NewAggregate(orgID, orgID).Aggregate, []policy.OrgPolicyChanges{ policy.ChangeUserLoginMustBeDomain(userLoginMustBeDomain), diff --git a/internal/command/policy_password_complexity_model.go b/internal/command/policy_password_complexity_model.go index 1de3b2c7ab..046feaa185 100644 --- a/internal/command/policy_password_complexity_model.go +++ b/internal/command/policy_password_complexity_model.go @@ -1,11 +1,21 @@ package command import ( + "regexp" + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/repository/policy" ) +var ( + hasStringLowerCase = regexp.MustCompile(`[a-z]`).MatchString + hasStringUpperCase = regexp.MustCompile(`[A-Z]`).MatchString + hasNumber = regexp.MustCompile(`[0-9]`).MatchString + hasSymbol = regexp.MustCompile(`[^A-Za-z0-9]`).MatchString +) + type PasswordComplexityPolicyWriteModel struct { eventstore.WriteModel @@ -49,3 +59,26 @@ func (wm *PasswordComplexityPolicyWriteModel) Reduce() error { } return wm.WriteModel.Reduce() } + +func (wm *PasswordComplexityPolicyWriteModel) Validate(password string) error { + if wm.MinLength != 0 && uint64(len(password)) < wm.MinLength { + return errors.ThrowInvalidArgument(nil, "COMMA-HuJf6", "Errors.User.PasswordComplexityPolicy.MinLength") + } + + if wm.HasLowercase && !hasStringLowerCase(password) { + return errors.ThrowInvalidArgument(nil, "COMMA-co3Xw", "Errors.User.PasswordComplexityPolicy.HasLower") + } + + if wm.HasUppercase && !hasStringUpperCase(password) { + return errors.ThrowInvalidArgument(nil, "COMMA-VoaRj", "Errors.User.PasswordComplexityPolicy.HasUpper") + } + + if wm.HasNumber && !hasNumber(password) { + return errors.ThrowInvalidArgument(nil, "COMMA-ZBv4H", "Errors.User.PasswordComplexityPolicy.HasNumber") + } + + if wm.HasSymbol && !hasSymbol(password) { + return errors.ThrowInvalidArgument(nil, "COMMA-ZDLwA", "Errors.User.PasswordComplexityPolicy.HasSymbol") + } + return nil +} diff --git a/internal/command/setup_step1.go b/internal/command/setup_step1.go deleted file mode 100644 index 1570ab2d61..0000000000 --- a/internal/command/setup_step1.go +++ /dev/null @@ -1,329 +0,0 @@ -package command - -import ( - "context" - - "github.com/caos/zitadel/internal/crypto" - "github.com/caos/zitadel/internal/eventstore" - - "github.com/caos/zitadel/internal/eventstore/v1/models" - - "github.com/caos/logging" - - "github.com/caos/zitadel/internal/domain" - caos_errs "github.com/caos/zitadel/internal/errors" - iam_repo "github.com/caos/zitadel/internal/repository/instance" -) - -const ( - OIDCResponseTypeCode = "CODE" - OIDCResponseTypeIDToken = "ID_TOKEN" - OIDCResponseTypeToken = "ID_TOKEN TOKEN" - OIDCGrantTypeAuthorizationCode = "AUTHORIZATION_CODE" - OIDCGrantTypeImplicit = "IMPLICIT" - OIDCGrantTypeRefreshToken = "REFRESH_TOKEN" - OIDCApplicationTypeNative = "NATIVE" - OIDCApplicationTypeUserAgent = "USER_AGENT" - OIDCApplicationTypeWeb = "WEB" - AuthMethodTypeNone = "NONE" - AuthMethodTypeBasic = "BASIC" - AuthMethodTypePost = "POST" - AuthMethodTypePrivateKeyJWT = "PRIVATE_KEY_JWT" -) - -type Step1 struct { - GlobalOrg string - IAMProject string - DefaultLoginPolicy LoginPolicy - Orgs []Org -} - -func (s *Step1) Step() domain.Step { - return domain.Step1 -} - -func (s *Step1) execute(ctx context.Context, commandSide *Commands) error { - return commandSide.SetupStep1(ctx, s) -} - -type LoginPolicy struct { - AllowRegister bool - AllowUsernamePassword bool - AllowExternalIdp bool -} - -type User struct { - FirstName string - LastName string - UserName string - Email string - Password string -} - -type Org struct { - Name string - Domain string - OrgIamPolicy bool - Owner User - Projects []Project -} - -type Project struct { - Name string - Users []User - Members []string - OIDCApps []OIDCApp - APIs []API -} - -type OIDCApp struct { - Name string - RedirectUris []string - ResponseTypes []string - GrantTypes []string - ApplicationType string - AuthMethodType string - PostLogoutRedirectUris []string - DevMode bool -} - -type API struct { - Name string - AuthMethodType string -} - -func (c *Commands) SetupStep1(ctx context.Context, step1 *Step1) error { - var events []eventstore.Command - iamWriteModel := NewInstanceWriteModel() - iamAgg := InstanceAggregateFromWriteModel(&iamWriteModel.WriteModel) - //create default login policy - loginPolicyEvent, err := c.addDefaultLoginPolicy(ctx, iamAgg, NewInstanceLoginPolicyWriteModel(), - &domain.LoginPolicy{ - AllowUsernamePassword: step1.DefaultLoginPolicy.AllowUsernamePassword, - AllowRegister: step1.DefaultLoginPolicy.AllowRegister, - AllowExternalIDP: step1.DefaultLoginPolicy.AllowExternalIdp, - }) - if err != nil { - return err - } - events = append(events, loginPolicyEvent) - logging.Log("SETUP-sd2hj").Info("default login policy set up") - //create orgs - for _, organisation := range step1.Orgs { - orgIAMPolicy := &domain.DomainPolicy{UserLoginMustBeDomain: true} - if organisation.OrgIamPolicy { - orgIAMPolicy.UserLoginMustBeDomain = false - } - pwPolicy := &domain.PasswordComplexityPolicy{ - MinLength: 1, - } - orgAgg, _, humanWriteModel, _, setUpOrgEvents, err := c.setUpOrg(ctx, - &domain.Org{ - Name: organisation.Name, - Domains: []*domain.OrgDomain{{Domain: organisation.Domain}}, - }, - &domain.Human{ - Username: organisation.Owner.UserName, - Profile: &domain.Profile{ - FirstName: organisation.Owner.FirstName, - LastName: organisation.Owner.LastName, - }, - Password: &domain.Password{ - SecretString: organisation.Owner.Password, - }, - Email: &domain.Email{ - EmailAddress: organisation.Owner.Email, - IsEmailVerified: true, - }, - }, orgIAMPolicy, pwPolicy, - nil, //TODO: Code Generator missing! Should be setuped in step1 create iam - nil, //TODO: Code Generator missing! Should be setuped in step1 create iam - nil, false) - if err != nil { - return err - } - events = append(events, setUpOrgEvents...) - logging.LogWithFields("SETUP-Gdsfg", "id", orgAgg.ID, "name", organisation.Name).Info("org set up") - - if organisation.OrgIamPolicy { - orgIAMPolicyEvent, err := c.addOrgDomainPolicy(ctx, orgAgg, NewOrgDomainPolicyWriteModel(orgAgg.ID), orgIAMPolicy) - if err != nil { - return err - } - events = append(events, orgIAMPolicyEvent) - } - if organisation.Name == step1.GlobalOrg { - globalOrgEvent, err := c.setGlobalOrg(ctx, iamAgg, iamWriteModel, orgAgg.ID) - if err != nil { - return err - } - events = append(events, globalOrgEvent) - logging.Log("SETUP-BDn52").Info("global org set") - } - //projects - for _, proj := range organisation.Projects { - project := &domain.Project{Name: proj.Name} - projectEvents, projectWriteModel, err := c.addProject(ctx, project, orgAgg.ID, humanWriteModel.AggregateID) - if err != nil { - return err - } - events = append(events, projectEvents...) - if project.Name == step1.IAMProject { - iamProjectEvent, err := c.setIAMProject(ctx, iamAgg, iamWriteModel, projectWriteModel.AggregateID) - if err != nil { - return err - } - events = append(events, iamProjectEvent) - logging.Log("SETUP-Bdfs1").Info("IAM project set") - iamEvent, err := c.addInstanceMember(ctx, iamAgg, NewInstanceMemberWriteModel(humanWriteModel.AggregateID), domain.NewMember(iamAgg.ID, humanWriteModel.AggregateID, domain.RoleIAMOwner)) - if err != nil { - return err - } - events = append(events, iamEvent) - logging.Log("SETUP-BSf2h").Info("IAM owner set") - } - //create applications - for _, app := range proj.OIDCApps { - //TODO: Add Secret Generator - applicationEvents, err := setUpOIDCApplication(ctx, c, projectWriteModel, project, app, orgAgg.ID, nil) - if err != nil { - return err - } - events = append(events, applicationEvents...) - } - for _, app := range proj.APIs { - //TODO: Add Secret Generator - applicationEvents, err := setUpAPI(ctx, c, projectWriteModel, project, app, orgAgg.ID, nil) - if err != nil { - return err - } - events = append(events, applicationEvents...) - } - } - } - - events = append(events, iam_repo.NewSetupStepDoneEvent(ctx, iamAgg, domain.Step1)) - - _, err = c.eventstore.Push(ctx, events...) - if err != nil { - return caos_errs.ThrowPreconditionFailed(nil, "EVENT-Gr2hh", "Setup Step1 failed") - } - return nil -} - -func setUpOIDCApplication(ctx context.Context, r *Commands, projectWriteModel *ProjectWriteModel, project *domain.Project, oidcApp OIDCApp, resourceOwner string, appSecretGenerator crypto.Generator) ([]eventstore.Command, error) { - app := &domain.OIDCApp{ - ObjectRoot: models.ObjectRoot{ - AggregateID: projectWriteModel.AggregateID, - }, - AppName: oidcApp.Name, - RedirectUris: oidcApp.RedirectUris, - ResponseTypes: getOIDCResponseTypes(oidcApp.ResponseTypes), - GrantTypes: getOIDCGrantTypes(oidcApp.GrantTypes), - ApplicationType: getOIDCApplicationType(oidcApp.ApplicationType), - AuthMethodType: getOIDCAuthMethod(oidcApp.AuthMethodType), - DevMode: oidcApp.DevMode, - } - - projectAgg := ProjectAggregateFromWriteModel(&projectWriteModel.WriteModel) - events, _, err := r.addOIDCApplication(ctx, projectAgg, project, app, resourceOwner, appSecretGenerator) - if err != nil { - return nil, err - } - logging.LogWithFields("SETUP-Edgw4", "name", app.AppName, "clientID", app.ClientID).Info("application set up") - return events, nil -} - -func setUpAPI(ctx context.Context, r *Commands, projectWriteModel *ProjectWriteModel, project *domain.Project, apiApp API, resourceOwner string, appSecretGenerator crypto.Generator) ([]eventstore.Command, error) { - app := &domain.APIApp{ - ObjectRoot: models.ObjectRoot{ - AggregateID: projectWriteModel.AggregateID, - }, - AppName: apiApp.Name, - AuthMethodType: getAPIAuthMethod(apiApp.AuthMethodType), - } - - projectAgg := ProjectAggregateFromWriteModel(&projectWriteModel.WriteModel) - events, _, err := r.addAPIApplication(ctx, projectAgg, project, app, resourceOwner, appSecretGenerator) - if err != nil { - return nil, err - } - logging.LogWithFields("SETUP-Dbgsf", "name", app.AppName, "clientID", app.ClientID).Info("application set up") - return events, nil -} - -func getOIDCResponseTypes(responseTypes []string) []domain.OIDCResponseType { - types := make([]domain.OIDCResponseType, len(responseTypes)) - for i, t := range responseTypes { - types[i] = getOIDCResponseType(t) - } - return types -} - -func getOIDCResponseType(responseType string) domain.OIDCResponseType { - switch responseType { - case OIDCResponseTypeCode: - return domain.OIDCResponseTypeCode - case OIDCResponseTypeIDToken: - return domain.OIDCResponseTypeIDToken - case OIDCResponseTypeToken: - return domain.OIDCResponseTypeIDTokenToken - } - return domain.OIDCResponseTypeCode -} - -func getOIDCGrantTypes(grantTypes []string) []domain.OIDCGrantType { - types := make([]domain.OIDCGrantType, len(grantTypes)) - for i, t := range grantTypes { - types[i] = getOIDCGrantType(t) - } - return types -} - -func getOIDCGrantType(grantTypes string) domain.OIDCGrantType { - switch grantTypes { - case OIDCGrantTypeAuthorizationCode: - return domain.OIDCGrantTypeAuthorizationCode - case OIDCGrantTypeImplicit: - return domain.OIDCGrantTypeImplicit - case OIDCGrantTypeRefreshToken: - return domain.OIDCGrantTypeRefreshToken - } - return domain.OIDCGrantTypeAuthorizationCode -} - -func getOIDCApplicationType(appType string) domain.OIDCApplicationType { - switch appType { - case OIDCApplicationTypeNative: - return domain.OIDCApplicationTypeNative - case OIDCApplicationTypeUserAgent: - return domain.OIDCApplicationTypeUserAgent - case OIDCApplicationTypeWeb: - return domain.OIDCApplicationTypeWeb - } - return domain.OIDCApplicationTypeWeb -} - -func getOIDCAuthMethod(authMethod string) domain.OIDCAuthMethodType { - switch authMethod { - case AuthMethodTypeNone: - return domain.OIDCAuthMethodTypeNone - case AuthMethodTypeBasic: - return domain.OIDCAuthMethodTypeBasic - case AuthMethodTypePost: - return domain.OIDCAuthMethodTypePost - case AuthMethodTypePrivateKeyJWT: - return domain.OIDCAuthMethodTypePrivateKeyJWT - } - return domain.OIDCAuthMethodTypeBasic -} - -func getAPIAuthMethod(authMethod string) domain.APIAuthMethodType { - switch authMethod { - case AuthMethodTypeBasic: - return domain.APIAuthMethodTypeBasic - case AuthMethodTypePrivateKeyJWT: - return domain.APIAuthMethodTypePrivateKeyJWT - } - return domain.APIAuthMethodTypePrivateKeyJWT -} diff --git a/internal/command/setup_step10.go b/internal/command/setup_step10.go deleted file mode 100644 index 13293e233e..0000000000 --- a/internal/command/setup_step10.go +++ /dev/null @@ -1,38 +0,0 @@ -package command - -import ( - "context" - - "github.com/caos/logging" - - "github.com/caos/zitadel/internal/domain" - "github.com/caos/zitadel/internal/eventstore" -) - -type Step10 struct { - DefaultMailTemplate domain.MailTemplate -} - -func (s *Step10) Step() domain.Step { - return domain.Step10 -} - -func (s *Step10) execute(ctx context.Context, commandSide *Commands) error { - return commandSide.SetupStep10(ctx, s) -} - -func (c *Commands) SetupStep10(ctx context.Context, step *Step10) error { - fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) { - iamAgg := InstanceAggregateFromWriteModel(&iam.WriteModel) - mailTemplateEvent, err := c.addDefaultMailTemplate(ctx, iamAgg, NewInstanceMailTemplateWriteModel(), &step.DefaultMailTemplate) - if err != nil { - return nil, err - } - events := []eventstore.Command{ - mailTemplateEvent, - } - logging.Log("SETUP-3N9fs").Info("default mail template/text set up") - return events, nil - } - return c.setup(ctx, step, fn) -} diff --git a/internal/command/setup_step11.go b/internal/command/setup_step11.go deleted file mode 100644 index be6fb920b4..0000000000 --- a/internal/command/setup_step11.go +++ /dev/null @@ -1,40 +0,0 @@ -package command - -import ( - "context" - - "github.com/caos/logging" - "github.com/caos/zitadel/internal/domain" - "github.com/caos/zitadel/internal/eventstore" - iam_repo "github.com/caos/zitadel/internal/repository/instance" -) - -type Step11 struct { - MigrateV1EventstoreToV2 bool -} - -func (s *Step11) Step() domain.Step { - return domain.Step11 -} - -func (s *Step11) execute(ctx context.Context, commandSide *Commands) error { - return commandSide.SetupStep11(ctx, s) -} - -func (c *Commands) SetupStep11(ctx context.Context, step *Step11) error { - fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) { - iamAgg := InstanceAggregateFromWriteModel(&iam.WriteModel) - var uniqueContraintMigrations []*domain.UniqueConstraintMigration - if step.MigrateV1EventstoreToV2 { - uniqueConstraints := NewUniqueConstraintReadModel(ctx, c) - err := c.eventstore.FilterToQueryReducer(ctx, uniqueConstraints) - if err != nil { - return nil, err - } - uniqueContraintMigrations = uniqueConstraints.UniqueConstraints - } - logging.Log("SETUP-M9fsd").Info("migrate v1 eventstore to v2") - return []eventstore.Command{iam_repo.NewMigrateUniqueConstraintEvent(ctx, iamAgg, uniqueContraintMigrations)}, nil - } - return c.setup(ctx, step, fn) -} diff --git a/internal/command/setup_step12.go b/internal/command/setup_step12.go deleted file mode 100644 index c22c895792..0000000000 --- a/internal/command/setup_step12.go +++ /dev/null @@ -1,56 +0,0 @@ -package command - -import ( - "context" - "time" - - "github.com/caos/zitadel/internal/domain" - "github.com/caos/zitadel/internal/eventstore" -) - -type Step12 struct { - TierName string - TierDescription string - AuditLogRetention time.Duration - LoginPolicyFactors bool - LoginPolicyIDP bool - LoginPolicyPasswordless bool - LoginPolicyRegistration bool - LoginPolicyUsernameLogin bool - PasswordComplexityPolicy bool - LabelPolicy bool - CustomDomain bool -} - -func (s *Step12) Step() domain.Step { - return domain.Step12 -} - -func (s *Step12) execute(ctx context.Context, commandSide *Commands) error { - return commandSide.SetupStep12(ctx, s) -} - -func (c *Commands) SetupStep12(ctx context.Context, step *Step12) error { - fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) { - featuresWriteModel := NewInstanceFeaturesWriteModel() - featuresEvent, err := c.setDefaultFeatures(ctx, featuresWriteModel, &domain.Features{ - TierName: step.TierName, - TierDescription: step.TierDescription, - State: domain.FeaturesStateActive, - AuditLogRetention: step.AuditLogRetention, - LoginPolicyFactors: step.LoginPolicyFactors, - LoginPolicyIDP: step.LoginPolicyIDP, - LoginPolicyPasswordless: step.LoginPolicyPasswordless, - LoginPolicyRegistration: step.LoginPolicyRegistration, - LoginPolicyUsernameLogin: step.LoginPolicyUsernameLogin, - PasswordComplexityPolicy: step.PasswordComplexityPolicy, - LabelPolicyPrivateLabel: step.LabelPolicy, - CustomDomain: step.CustomDomain, - }) - if err != nil { - return nil, err - } - return []eventstore.Command{featuresEvent}, nil - } - return c.setup(ctx, step, fn) -} diff --git a/internal/command/setup_step13.go b/internal/command/setup_step13.go deleted file mode 100644 index 3958351bfb..0000000000 --- a/internal/command/setup_step13.go +++ /dev/null @@ -1,33 +0,0 @@ -package command - -import ( - "context" - - "github.com/caos/logging" - "github.com/caos/zitadel/internal/domain" - "github.com/caos/zitadel/internal/eventstore" -) - -type Step13 struct { - DefaultMailTemplate domain.MailTemplate -} - -func (s *Step13) Step() domain.Step { - return domain.Step13 -} - -func (s *Step13) execute(ctx context.Context, commandSide *Commands) error { - return commandSide.SetupStep13(ctx, s) -} - -func (c *Commands) SetupStep13(ctx context.Context, step *Step13) error { - fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) { - _, mailTemplateEvent, err := c.changeDefaultMailTemplate(ctx, &step.DefaultMailTemplate) - if err != nil { - return nil, err - } - logging.Log("SETUP-4insR").Info("default mail template/text set up") - return []eventstore.Command{mailTemplateEvent}, nil - } - return c.setup(ctx, step, fn) -} diff --git a/internal/command/setup_step14.go b/internal/command/setup_step14.go deleted file mode 100644 index ff93d84d28..0000000000 --- a/internal/command/setup_step14.go +++ /dev/null @@ -1,48 +0,0 @@ -package command - -import ( - "context" - - "github.com/caos/logging" - - "github.com/caos/zitadel/internal/domain" - "github.com/caos/zitadel/internal/eventstore" - iam_repo "github.com/caos/zitadel/internal/repository/instance" - org_repo "github.com/caos/zitadel/internal/repository/org" -) - -type Step14 struct { - ActivateExistingLabelPolicies bool -} - -func (s *Step14) Step() domain.Step { - return domain.Step14 -} - -func (s *Step14) execute(ctx context.Context, commandSide *Commands) error { - return commandSide.SetupStep14(ctx, s) -} - -func (c *Commands) SetupStep14(ctx context.Context, step *Step14) error { - fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) { - iamAgg := InstanceAggregateFromWriteModel(&iam.WriteModel) - var events []eventstore.Command - if step.ActivateExistingLabelPolicies { - existingPolicies := NewExistingLabelPoliciesReadModel(ctx) - err := c.eventstore.FilterToQueryReducer(ctx, existingPolicies) - if err != nil { - return nil, err - } - for _, aggID := range existingPolicies.aggregateIDs { - if iamAgg.ID == aggID { - events = append(events, iam_repo.NewLabelPolicyActivatedEvent(ctx, iamAgg)) - continue - } - events = append(events, org_repo.NewLabelPolicyActivatedEvent(ctx, &org_repo.NewAggregate(aggID, aggID).Aggregate)) - } - } - logging.Log("SETUP-M9fsd").Info("activate login policies") - return events, nil - } - return c.setup(ctx, step, fn) -} diff --git a/internal/command/setup_step15.go b/internal/command/setup_step15.go deleted file mode 100644 index 2c88ad6a1c..0000000000 --- a/internal/command/setup_step15.go +++ /dev/null @@ -1,33 +0,0 @@ -package command - -import ( - "context" - - "github.com/caos/logging" - "github.com/caos/zitadel/internal/domain" - "github.com/caos/zitadel/internal/eventstore" -) - -type Step15 struct { - DefaultMailTemplate domain.MailTemplate -} - -func (s *Step15) Step() domain.Step { - return domain.Step15 -} - -func (s *Step15) execute(ctx context.Context, commandSide *Commands) error { - return commandSide.SetupStep15(ctx, s) -} - -func (c *Commands) SetupStep15(ctx context.Context, step *Step15) error { - fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) { - _, mailTemplateEvent, err := c.changeDefaultMailTemplate(ctx, &step.DefaultMailTemplate) - if err != nil { - return nil, err - } - logging.Log("SETUP-2nfsd").Info("default mail template/text set up") - return []eventstore.Command{mailTemplateEvent}, nil - } - return c.setup(ctx, step, fn) -} diff --git a/internal/command/setup_step16.go b/internal/command/setup_step16.go deleted file mode 100644 index e6d6b3e567..0000000000 --- a/internal/command/setup_step16.go +++ /dev/null @@ -1,40 +0,0 @@ -package command - -import ( - "context" - - "github.com/caos/logging" - "github.com/caos/zitadel/internal/domain" - "github.com/caos/zitadel/internal/eventstore" -) - -type Step16 struct { - DefaultMessageTexts []domain.CustomMessageText -} - -func (s *Step16) Step() domain.Step { - return domain.Step16 -} - -func (s *Step16) execute(ctx context.Context, commandSide *Commands) error { - return commandSide.SetupStep16(ctx, s) -} - -func (c *Commands) SetupStep16(ctx context.Context, step *Step16) error { - fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) { - iamAgg := InstanceAggregateFromWriteModel(&iam.WriteModel) - events := make([]eventstore.Command, 0) - - for _, text := range step.DefaultMessageTexts { - mailEvents, _, err := c.setDefaultMessageText(ctx, iamAgg, &text) - if err != nil { - return nil, err - } - events = append(events, mailEvents...) - } - - logging.Log("SETUP-4k0LL").Info("default message text set up") - return events, nil - } - return c.setup(ctx, step, fn) -} diff --git a/internal/command/setup_step17.go b/internal/command/setup_step17.go deleted file mode 100644 index 45fb4120e3..0000000000 --- a/internal/command/setup_step17.go +++ /dev/null @@ -1,36 +0,0 @@ -package command - -import ( - "context" - - "github.com/caos/logging" - "github.com/caos/zitadel/internal/domain" - "github.com/caos/zitadel/internal/eventstore" -) - -type Step17 struct { - PrivacyPolicy domain.PrivacyPolicy -} - -func (s *Step17) Step() domain.Step { - return domain.Step17 -} - -func (s *Step17) execute(ctx context.Context, commandSide *Commands) error { - return commandSide.SetupStep17(ctx, s) -} - -func (c *Commands) SetupStep17(ctx context.Context, step *Step17) error { - fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) { - iamAgg := InstanceAggregateFromWriteModel(&iam.WriteModel) - addedPolicy := NewInstancePrivacyPolicyWriteModel() - events, err := c.addDefaultPrivacyPolicy(ctx, iamAgg, addedPolicy, &step.PrivacyPolicy) - if err != nil { - return nil, err - } - - logging.Log("SETUP-N9sq2").Info("default privacy policy set up") - return []eventstore.Command{events}, nil - } - return c.setup(ctx, step, fn) -} diff --git a/internal/command/setup_step18.go b/internal/command/setup_step18.go deleted file mode 100644 index aa5c8f725d..0000000000 --- a/internal/command/setup_step18.go +++ /dev/null @@ -1,36 +0,0 @@ -package command - -import ( - "context" - - "github.com/caos/logging" - "github.com/caos/zitadel/internal/domain" - "github.com/caos/zitadel/internal/eventstore" -) - -type Step18 struct { - LockoutPolicy domain.LockoutPolicy -} - -func (s *Step18) Step() domain.Step { - return domain.Step18 -} - -func (s *Step18) execute(ctx context.Context, commandSide *Commands) error { - return commandSide.SetupStep18(ctx, s) -} - -func (c *Commands) SetupStep18(ctx context.Context, step *Step18) error { - fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) { - iamAgg := InstanceAggregateFromWriteModel(&iam.WriteModel) - addedPolicy := NewInstanceLockoutPolicyWriteModel() - events, err := c.addDefaultLockoutPolicy(ctx, iamAgg, addedPolicy, &step.LockoutPolicy) - if err != nil { - return nil, err - } - - logging.Log("SETUP-3m99ds").Info("default lockout policy set up") - return []eventstore.Command{events}, nil - } - return c.setup(ctx, step, fn) -} diff --git a/internal/command/setup_step19.go b/internal/command/setup_step19.go deleted file mode 100644 index f5d5793861..0000000000 --- a/internal/command/setup_step19.go +++ /dev/null @@ -1,212 +0,0 @@ -package command - -import ( - "context" - "fmt" - "strings" - - "github.com/caos/logging" - "github.com/caos/zitadel/internal/domain" - "github.com/caos/zitadel/internal/eventstore" - "github.com/caos/zitadel/internal/repository/org" - "github.com/caos/zitadel/internal/repository/user" -) - -type Step19 struct{} - -func (s *Step19) Step() domain.Step { - return domain.Step19 -} - -func (s *Step19) execute(ctx context.Context, commandSide *Commands) error { - return commandSide.SetupStep19(ctx, s) -} - -func (c *Commands) SetupStep19(ctx context.Context, step *Step19) error { - fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) { - events := make([]eventstore.Command, 0) - orgs := newOrgsWithUsernameNotDomain() - if err := c.eventstore.FilterToQueryReducer(ctx, orgs); err != nil { - return nil, err - } - for orgID, usernameCheck := range orgs.orgs { - if !usernameCheck { - continue - } - users := newDomainClaimedUsernames(orgID) - if err := c.eventstore.FilterToQueryReducer(ctx, users); err != nil { - return nil, err - } - for userID, username := range users.users { - split := strings.Split(username, "@") - if len(split) != 2 { - continue - } - domainVerified := NewOrgDomainVerifiedWriteModel(split[1]) - if err := c.eventstore.FilterToQueryReducer(ctx, domainVerified); err != nil { - return nil, err - } - if domainVerified.Verified && domainVerified.ResourceOwner != orgID { - id, err := c.idGenerator.Next() - if err != nil { - return nil, err - } - events = append(events, user.NewDomainClaimedEvent( - ctx, - &user.NewAggregate(userID, orgID).Aggregate, - fmt.Sprintf("%s@temporary.%s", id, c.iamDomain), - username, - false)) - } - } - } - - if length := len(events); length > 0 { - logging.Log("SETUP-dFG2t").WithField("count", length).Info("domain claimed events created") - } - return events, nil - } - return c.setup(ctx, step, fn) -} - -func newOrgsWithUsernameNotDomain() *orgsWithUsernameNotDomain { - return &orgsWithUsernameNotDomain{ - orgEvents: make(map[string][]eventstore.Event), - orgs: make(map[string]bool), - } -} - -type orgsWithUsernameNotDomain struct { - eventstore.WriteModel - - orgEvents map[string][]eventstore.Event - orgs map[string]bool -} - -func (wm *orgsWithUsernameNotDomain) AppendEvents(events ...eventstore.Event) { - for _, event := range events { - switch e := event.(type) { - case *org.OrgAddedEvent: - wm.orgEvents[e.Aggregate().ID] = append(wm.orgEvents[e.Aggregate().ID], e) - case *org.OrgRemovedEvent: - delete(wm.orgEvents, e.Aggregate().ID) - case *org.OrgDomainPolicyAddedEvent: - wm.orgEvents[e.Aggregate().ID] = append(wm.orgEvents[e.Aggregate().ID], e) - case *org.OrgDomainPolicyChangedEvent: - if e.UserLoginMustBeDomain == nil { - continue - } - wm.orgEvents[e.Aggregate().ID] = append(wm.orgEvents[e.Aggregate().ID], e) - case *org.OrgDomainPolicyRemovedEvent: - delete(wm.orgEvents, e.Aggregate().ID) - } - } -} - -func (wm *orgsWithUsernameNotDomain) Reduce() error { - for _, events := range wm.orgEvents { - for _, event := range events { - switch e := event.(type) { - case *org.OrgDomainPolicyAddedEvent: - if !e.UserLoginMustBeDomain { - wm.orgs[e.Aggregate().ID] = true - } - case *org.OrgDomainPolicyChangedEvent: - if !*e.UserLoginMustBeDomain { - wm.orgs[e.Aggregate().ID] = true - } - delete(wm.orgs, e.Aggregate().ID) - } - } - } - return nil -} - -func (wm *orgsWithUsernameNotDomain) Query() *eventstore.SearchQueryBuilder { - return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). - AddQuery(). - AggregateTypes(org.AggregateType). - EventTypes( - org.OrgAddedEventType, - org.OrgRemovedEventType, - org.OrgDomainPolicyAddedEventType, - org.OrgDomainPolicyChangedEventType, - org.OrgDomainPolicyRemovedEventType). - Builder() -} - -func newDomainClaimedUsernames(orgID string) *domainClaimedUsernames { - return &domainClaimedUsernames{ - WriteModel: eventstore.WriteModel{ - ResourceOwner: orgID, - }, - userEvents: make(map[string][]eventstore.Event), - users: make(map[string]string), - } -} - -type domainClaimedUsernames struct { - eventstore.WriteModel - - userEvents map[string][]eventstore.Event - users map[string]string -} - -func (wm *domainClaimedUsernames) AppendEvents(events ...eventstore.Event) { - for _, event := range events { - switch e := event.(type) { - case *user.HumanAddedEvent: - if !strings.Contains(e.UserName, "@") { - continue - } - wm.userEvents[e.Aggregate().ID] = append(wm.userEvents[e.Aggregate().ID], e) - case *user.HumanRegisteredEvent: - if !strings.Contains(e.UserName, "@") { - continue - } - wm.userEvents[e.Aggregate().ID] = append(wm.userEvents[e.Aggregate().ID], e) - case *user.UsernameChangedEvent: - if !strings.Contains(e.UserName, "@") { - delete(wm.userEvents, e.Aggregate().ID) - continue - } - wm.userEvents[e.Aggregate().ID] = append(wm.userEvents[e.Aggregate().ID], e) - case *user.DomainClaimedEvent: - delete(wm.userEvents, e.Aggregate().ID) - case *user.UserRemovedEvent: - delete(wm.userEvents, e.Aggregate().ID) - } - } -} - -func (wm *domainClaimedUsernames) Reduce() error { - for _, events := range wm.userEvents { - for _, event := range events { - switch e := event.(type) { - case *user.HumanAddedEvent: - wm.users[e.Aggregate().ID] = e.UserName - case *user.HumanRegisteredEvent: - wm.users[e.Aggregate().ID] = e.UserName - case *user.UsernameChangedEvent: - wm.users[e.Aggregate().ID] = e.UserName - } - } - } - return nil -} - -func (wm *domainClaimedUsernames) Query() *eventstore.SearchQueryBuilder { - return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). - ResourceOwner(wm.ResourceOwner). - AddQuery(). - AggregateTypes(user.AggregateType). - EventTypes( - user.UserV1AddedType, - user.UserV1RegisteredType, - user.HumanAddedType, - user.HumanRegisteredType, - user.UserUserNameChangedType, - user.UserDomainClaimedType, - user.UserRemovedType). - Builder() -} diff --git a/internal/command/setup_step2.go b/internal/command/setup_step2.go deleted file mode 100644 index dff42e07c7..0000000000 --- a/internal/command/setup_step2.go +++ /dev/null @@ -1,42 +0,0 @@ -package command - -import ( - "context" - - "github.com/caos/logging" - - "github.com/caos/zitadel/internal/eventstore" - - "github.com/caos/zitadel/internal/domain" -) - -type Step2 struct { - DefaultPasswordComplexityPolicy domain.PasswordComplexityPolicy -} - -func (s *Step2) Step() domain.Step { - return domain.Step2 -} - -func (s *Step2) execute(ctx context.Context, commandSide *Commands) error { - return commandSide.SetupStep2(ctx, s) -} - -func (c *Commands) SetupStep2(ctx context.Context, step *Step2) error { - fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) { - iamAgg := InstanceAggregateFromWriteModel(&iam.WriteModel) - event, err := c.addDefaultPasswordComplexityPolicy(ctx, iamAgg, NewInstancePasswordComplexityPolicyWriteModel(), &domain.PasswordComplexityPolicy{ - MinLength: step.DefaultPasswordComplexityPolicy.MinLength, - HasLowercase: step.DefaultPasswordComplexityPolicy.HasLowercase, - HasUppercase: step.DefaultPasswordComplexityPolicy.HasUppercase, - HasNumber: step.DefaultPasswordComplexityPolicy.HasNumber, - HasSymbol: step.DefaultPasswordComplexityPolicy.HasSymbol, - }) - if err != nil { - return nil, err - } - logging.Log("SETUP-ADgd2").Info("default password complexity policy set up") - return []eventstore.Command{event}, nil - } - return c.setup(ctx, step, fn) -} diff --git a/internal/command/setup_step20.go b/internal/command/setup_step20.go deleted file mode 100644 index 9f02c3b063..0000000000 --- a/internal/command/setup_step20.go +++ /dev/null @@ -1,26 +0,0 @@ -package command - -import ( - "context" - - "github.com/caos/zitadel/internal/domain" - "github.com/caos/zitadel/internal/eventstore" -) - -type Step20 struct{} - -func (s *Step20) Step() domain.Step { - return domain.Step20 -} - -func (s *Step20) execute(ctx context.Context, commandSide *Commands) error { - return commandSide.SetupStep20(ctx, s) -} - -func (c *Commands) SetupStep20(ctx context.Context, step *Step20) error { - fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) { - err := c.eventstore.Step20(ctx, iam.Events[len(iam.Events)-1].Sequence()) - return nil, err - } - return c.setup(ctx, step, fn) -} diff --git a/internal/command/setup_step21.go b/internal/command/setup_step21.go deleted file mode 100644 index c05c2e8d70..0000000000 --- a/internal/command/setup_step21.go +++ /dev/null @@ -1,105 +0,0 @@ -package command - -import ( - "context" - - "github.com/caos/zitadel/internal/domain" - "github.com/caos/zitadel/internal/eventstore" - "github.com/caos/zitadel/internal/repository/org" - "github.com/caos/zitadel/internal/repository/user" -) - -type Step21 struct{} - -func (s *Step21) Step() domain.Step { - return domain.Step21 -} - -func (s *Step21) execute(ctx context.Context, commandSide *Commands) error { - return commandSide.SetupStep21(ctx, s) -} - -func (c *Commands) SetupStep21(ctx context.Context, step *Step21) error { - fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) { - events := make([]eventstore.Command, 0) - globalMembers := newGlobalOrgMemberWriteModel(iam.GlobalOrgID, domain.RoleOrgProjectCreator) - orgAgg := OrgAggregateFromWriteModel(&globalMembers.WriteModel) - if err := c.eventstore.FilterToQueryReducer(ctx, globalMembers); err != nil { - return nil, err - } - for userID, roles := range globalMembers.members { - for i, role := range roles { - if role == domain.RoleOrgProjectCreator { - roles[i] = domain.RoleSelfManagementGlobal - } - } - events = append(events, org.NewMemberChangedEvent(ctx, orgAgg, userID, roles...)) - } - return events, nil - } - return c.setup(ctx, step, fn) -} - -type globalOrgMembersWriteModel struct { - eventstore.WriteModel - - role string - members map[string][]string -} - -func newGlobalOrgMemberWriteModel(orgID, role string) *globalOrgMembersWriteModel { - return &globalOrgMembersWriteModel{ - WriteModel: eventstore.WriteModel{ - ResourceOwner: orgID, - AggregateID: orgID, - }, - role: role, - members: make(map[string][]string), - } -} - -func (wm *globalOrgMembersWriteModel) Reduce() error { - for _, event := range wm.Events { - switch e := event.(type) { - case *org.MemberAddedEvent: - for _, role := range e.Roles { - if wm.role == role { - wm.members[e.UserID] = e.Roles - break - } - } - case *org.MemberChangedEvent: - delete(wm.members, e.UserID) - for _, role := range e.Roles { - if wm.role == role { - wm.members[e.UserID] = e.Roles - break - } - } - case *org.MemberRemovedEvent: - delete(wm.members, e.UserID) - case *org.MemberCascadeRemovedEvent: - delete(wm.members, e.UserID) - case *user.UserRemovedEvent: - delete(wm.members, e.Aggregate().ID) - } - } - return wm.WriteModel.Reduce() -} - -func (wm *globalOrgMembersWriteModel) Query() *eventstore.SearchQueryBuilder { - return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). - AddQuery(). - AggregateTypes(org.AggregateType). - AggregateIDs(wm.AggregateID). - EventTypes( - org.MemberAddedEventType, - org.MemberChangedEventType, - org.MemberRemovedEventType, - org.MemberCascadeRemovedEventType, - ). - Or(). - AggregateTypes(user.AggregateType). - EventTypes(user.UserRemovedType). - Builder() -} diff --git a/internal/command/setup_step3.go b/internal/command/setup_step3.go deleted file mode 100644 index 1b723c5f01..0000000000 --- a/internal/command/setup_step3.go +++ /dev/null @@ -1,39 +0,0 @@ -package command - -import ( - "context" - - "github.com/caos/logging" - - "github.com/caos/zitadel/internal/eventstore" - - "github.com/caos/zitadel/internal/domain" -) - -type Step3 struct { - DefaultPasswordAgePolicy domain.PasswordAgePolicy -} - -func (s *Step3) Step() domain.Step { - return domain.Step3 -} - -func (s *Step3) execute(ctx context.Context, commandSide *Commands) error { - return commandSide.SetupStep3(ctx, s) -} - -func (c *Commands) SetupStep3(ctx context.Context, step *Step3) error { - fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) { - iamAgg := InstanceAggregateFromWriteModel(&iam.WriteModel) - event, err := c.addDefaultPasswordAgePolicy(ctx, iamAgg, NewInstancePasswordAgePolicyWriteModel(), &domain.PasswordAgePolicy{ - MaxAgeDays: step.DefaultPasswordAgePolicy.MaxAgeDays, - ExpireWarnDays: step.DefaultPasswordAgePolicy.ExpireWarnDays, - }) - if err != nil { - return nil, err - } - logging.Log("SETUP-DBqgq").Info("default password age policy set up") - return []eventstore.Command{event}, nil - } - return c.setup(ctx, step, fn) -} diff --git a/internal/command/setup_step4.go b/internal/command/setup_step4.go deleted file mode 100644 index 201fe13f74..0000000000 --- a/internal/command/setup_step4.go +++ /dev/null @@ -1,31 +0,0 @@ -package command - -import ( - "context" - - "github.com/caos/zitadel/internal/eventstore" - - "github.com/caos/zitadel/internal/domain" -) - -type Step4 struct { - DefaultPasswordLockoutPolicy domain.LockoutPolicy -} - -func (s *Step4) Step() domain.Step { - return domain.Step4 -} - -func (s *Step4) execute(ctx context.Context, commandSide *Commands) error { - return commandSide.SetupStep4(ctx, s) -} - -//This step should not be executed when a new instance is setup, because its not used anymore -//SetupStep4 is no op in favour of step 18. -//Password lockout policy is replaced by lockout policy -func (c *Commands) SetupStep4(ctx context.Context, step *Step4) error { - fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) { - return nil, nil - } - return c.setup(ctx, step, fn) -} diff --git a/internal/command/setup_step5.go b/internal/command/setup_step5.go deleted file mode 100644 index c6e9402192..0000000000 --- a/internal/command/setup_step5.go +++ /dev/null @@ -1,38 +0,0 @@ -package command - -import ( - "context" - - "github.com/caos/logging" - - "github.com/caos/zitadel/internal/eventstore" - - "github.com/caos/zitadel/internal/domain" -) - -type Step5 struct { - DefaultOrgIAMPolicy domain.DomainPolicy -} - -func (s *Step5) Step() domain.Step { - return domain.Step5 -} - -func (s *Step5) execute(ctx context.Context, commandSide *Commands) error { - return commandSide.SetupStep5(ctx, s) -} - -func (c *Commands) SetupStep5(ctx context.Context, step *Step5) error { - fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) { - iamAgg := InstanceAggregateFromWriteModel(&iam.WriteModel) - event, err := c.addDefaultDomainPolicy(ctx, iamAgg, NewInstanceDomainPolicyWriteModel(), &domain.DomainPolicy{ - UserLoginMustBeDomain: step.DefaultOrgIAMPolicy.UserLoginMustBeDomain, - }) - if err != nil { - return nil, err - } - logging.Log("SETUP-ADgd2").Info("default org iam policy set up") - return []eventstore.Command{event}, nil - } - return c.setup(ctx, step, fn) -} diff --git a/internal/command/setup_step6.go b/internal/command/setup_step6.go deleted file mode 100644 index 431169f386..0000000000 --- a/internal/command/setup_step6.go +++ /dev/null @@ -1,36 +0,0 @@ -package command - -import ( - "context" - - "github.com/caos/logging" - - "github.com/caos/zitadel/internal/eventstore" - - "github.com/caos/zitadel/internal/domain" -) - -type Step6 struct { - DefaultLabelPolicy domain.LabelPolicy -} - -func (s *Step6) Step() domain.Step { - return domain.Step6 -} - -func (s *Step6) execute(ctx context.Context, commandSide *Commands) error { - return commandSide.SetupStep6(ctx, s) -} - -func (c *Commands) SetupStep6(ctx context.Context, step *Step6) error { - fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) { - iamAgg := InstanceAggregateFromWriteModel(&iam.WriteModel) - event, err := c.addDefaultLabelPolicy(ctx, iamAgg, NewInstanceLabelPolicyWriteModel(), &step.DefaultLabelPolicy) - if err != nil { - return nil, err - } - logging.Log("SETUP-ADgd2").Info("default label policy set up") - return []eventstore.Command{event}, nil - } - return c.setup(ctx, step, fn) -} diff --git a/internal/command/setup_step7.go b/internal/command/setup_step7.go deleted file mode 100644 index 98205483c8..0000000000 --- a/internal/command/setup_step7.go +++ /dev/null @@ -1,39 +0,0 @@ -package command - -import ( - "context" - - "github.com/caos/logging" - "github.com/caos/zitadel/internal/eventstore" - - "github.com/caos/zitadel/internal/domain" -) - -type Step7 struct { - OTP bool -} - -func (s *Step7) Step() domain.Step { - return domain.Step7 -} - -func (s *Step7) execute(ctx context.Context, commandSide *Commands) error { - return commandSide.SetupStep7(ctx, s) -} - -func (c *Commands) SetupStep7(ctx context.Context, step *Step7) error { - fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) { - secondFactorModel := NewInstanceSecondFactorWriteModel(domain.SecondFactorTypeOTP) - iamAgg := InstanceAggregateFromWriteModel(&secondFactorModel.SecondFactorWriteModel.WriteModel) - if !step.OTP { - return []eventstore.Command{}, nil - } - event, err := c.addSecondFactorToDefaultLoginPolicy(ctx, iamAgg, secondFactorModel, domain.SecondFactorTypeOTP) - if err != nil { - return nil, err - } - logging.Log("SETUP-Dggsg").Info("added OTP to 2FA login policy") - return []eventstore.Command{event}, nil - } - return c.setup(ctx, step, fn) -} diff --git a/internal/command/setup_step8.go b/internal/command/setup_step8.go deleted file mode 100644 index 1eb0d69507..0000000000 --- a/internal/command/setup_step8.go +++ /dev/null @@ -1,39 +0,0 @@ -package command - -import ( - "context" - - "github.com/caos/logging" - "github.com/caos/zitadel/internal/eventstore" - - "github.com/caos/zitadel/internal/domain" -) - -type Step8 struct { - U2F bool -} - -func (s *Step8) Step() domain.Step { - return domain.Step8 -} - -func (s *Step8) execute(ctx context.Context, commandSide *Commands) error { - return commandSide.SetupStep8(ctx, s) -} - -func (c *Commands) SetupStep8(ctx context.Context, step *Step8) error { - fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) { - secondFactorModel := NewInstanceSecondFactorWriteModel(domain.SecondFactorTypeU2F) - iamAgg := InstanceAggregateFromWriteModel(&secondFactorModel.SecondFactorWriteModel.WriteModel) - if !step.U2F { - return []eventstore.Command{}, nil - } - event, err := c.addSecondFactorToDefaultLoginPolicy(ctx, iamAgg, secondFactorModel, domain.SecondFactorTypeU2F) - if err != nil { - return nil, err - } - logging.Log("SETUP-BDhne").Info("added U2F to 2FA login policy") - return []eventstore.Command{event}, nil - } - return c.setup(ctx, step, fn) -} diff --git a/internal/command/setup_step9.go b/internal/command/setup_step9.go deleted file mode 100644 index 3894f5c290..0000000000 --- a/internal/command/setup_step9.go +++ /dev/null @@ -1,53 +0,0 @@ -package command - -import ( - "context" - - "github.com/caos/logging" - "github.com/caos/zitadel/internal/eventstore" - - "github.com/caos/zitadel/internal/domain" -) - -type Step9 struct { - Passwordless bool -} - -func (s *Step9) Step() domain.Step { - return domain.Step9 -} - -func (s *Step9) execute(ctx context.Context, commandSide *Commands) error { - return commandSide.SetupStep9(ctx, s) -} - -func (c *Commands) SetupStep9(ctx context.Context, step *Step9) error { - fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) { - multiFactorModel := NewInstanceMultiFactorWriteModel(domain.MultiFactorTypeU2FWithPIN) - iamAgg := InstanceAggregateFromWriteModel(&multiFactorModel.MultiFactorWriteModel.WriteModel) - if !step.Passwordless { - return []eventstore.Command{}, nil - } - passwordlessEvent, err := setPasswordlessAllowedInPolicy(ctx, c, iamAgg) - if err != nil { - return nil, err - } - logging.Log("SETUP-AEG2t").Info("allowed passwordless in login policy") - multifactorEvent, err := c.addMultiFactorToDefaultLoginPolicy(ctx, iamAgg, multiFactorModel, domain.MultiFactorTypeU2FWithPIN) - if err != nil { - return nil, err - } - logging.Log("SETUP-ADfng").Info("added passwordless to MFA login policy") - return []eventstore.Command{passwordlessEvent, multifactorEvent}, nil - } - return c.setup(ctx, step, fn) -} - -func setPasswordlessAllowedInPolicy(ctx context.Context, c *Commands, iamAgg *eventstore.Aggregate) (eventstore.Command, error) { - policy, err := c.getDefaultLoginPolicy(ctx) - if err != nil { - return nil, err - } - policy.PasswordlessType = domain.PasswordlessTypeAllowed - return c.changeDefaultLoginPolicy(ctx, iamAgg, NewInstanceLoginPolicyWriteModel(), policy) -} diff --git a/internal/command/user.go b/internal/command/user.go index 44fc171e7b..7ff30cc2fd 100644 --- a/internal/command/user.go +++ b/internal/command/user.go @@ -36,18 +36,18 @@ func (c *Commands) ChangeUsername(ctx context.Context, orgID, userID, userName s return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6m9gs", "Errors.User.UsernameNotChanged") } - orgIAMPolicy, err := c.getOrgDomainPolicy(ctx, orgID) + domainPolicy, err := c.getOrgDomainPolicy(ctx, orgID) if err != nil { return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-38fnu", "Errors.Org.DomainPolicy.NotExisting") } - if err := CheckDomainPolicyForUserName(userName, orgIAMPolicy); err != nil { + if err := CheckDomainPolicyForUserName(userName, domainPolicy); err != nil { return nil, err } userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel) pushedEvents, err := c.eventstore.Push(ctx, - user.NewUsernameChangedEvent(ctx, userAgg, existingUser.UserName, userName, orgIAMPolicy.UserLoginMustBeDomain)) + user.NewUsernameChangedEvent(ctx, userAgg, existingUser.UserName, userName, domainPolicy.UserLoginMustBeDomain)) if err != nil { return nil, err } @@ -186,13 +186,13 @@ func (c *Commands) RemoveUser(ctx context.Context, userID, resourceOwner string, return nil, caos_errs.ThrowNotFound(nil, "COMMAND-m9od", "Errors.User.NotFound") } - orgIAMPolicy, err := c.getOrgDomainPolicy(ctx, existingUser.ResourceOwner) + domainPolicy, err := c.getOrgDomainPolicy(ctx, existingUser.ResourceOwner) if err != nil { - return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-3M9fs", "Errors.Org.OrgIAMPolicy.NotExisting") + return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-3M9fs", "Errors.Org.DomainPolicy.NotExisting") } var events []eventstore.Command userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel) - events = append(events, user.NewUserRemovedEvent(ctx, userAgg, existingUser.UserName, existingUser.IDPLinks, orgIAMPolicy.UserLoginMustBeDomain)) + events = append(events, user.NewUserRemovedEvent(ctx, userAgg, existingUser.UserName, existingUser.IDPLinks, domainPolicy.UserLoginMustBeDomain)) for _, grantID := range cascadingGrantIDs { removeEvent, _, err := c.removeUserGrant(ctx, grantID, "", true) @@ -320,7 +320,7 @@ func (c *Commands) userDomainClaimed(ctx context.Context, userID string) (events changedUserGrant := NewUserWriteModel(userID, existingUser.ResourceOwner) userAgg := UserAggregateFromWriteModel(&changedUserGrant.WriteModel) - orgIAMPolicy, err := c.getOrgDomainPolicy(ctx, existingUser.ResourceOwner) + domainPolicy, err := c.getOrgDomainPolicy(ctx, existingUser.ResourceOwner) if err != nil { return nil, nil, err } @@ -335,7 +335,7 @@ func (c *Commands) userDomainClaimed(ctx context.Context, userID string) (events userAgg, fmt.Sprintf("%s@temporary.%s", id, c.iamDomain), existingUser.UserName, - orgIAMPolicy.UserLoginMustBeDomain), + domainPolicy.UserLoginMustBeDomain), }, changedUserGrant, nil } diff --git a/internal/command/user_human.go b/internal/command/user_human.go index 0c9e6adb12..e4e810aecd 100644 --- a/internal/command/user_human.go +++ b/internal/command/user_human.go @@ -28,7 +28,7 @@ func (c *Commands) AddHuman(ctx context.Context, orgID string, human *domain.Hum if orgID == "" { return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-XYFk9", "Errors.ResourceOwnerMissing") } - orgIAMPolicy, err := c.getOrgDomainPolicy(ctx, orgID) + domainPolicy, err := c.getOrgDomainPolicy(ctx, orgID) if err != nil { return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-33M9f", "Errors.Org.DomainPolicy.NotFound") } @@ -36,7 +36,7 @@ func (c *Commands) AddHuman(ctx context.Context, orgID string, human *domain.Hum if err != nil { return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-M5Fsd", "Errors.Org.PasswordComplexityPolicy.NotFound") } - events, addedHuman, err := c.addHuman(ctx, orgID, human, orgIAMPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator) + events, addedHuman, err := c.addHuman(ctx, orgID, human, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator) if err != nil { return nil, err } @@ -57,7 +57,7 @@ func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain. if orgID == "" { return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5N8fs", "Errors.ResourceOwnerMissing") } - orgIAMPolicy, err := c.getOrgDomainPolicy(ctx, orgID) + domainPolicy, err := c.getOrgDomainPolicy(ctx, orgID) if err != nil { return nil, nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-2N9fs", "Errors.Org.DomainPolicy.NotFound") } @@ -65,7 +65,7 @@ func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain. if err != nil { return nil, nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-4N8gs", "Errors.Org.PasswordComplexityPolicy.NotFound") } - events, addedHuman, addedCode, code, err := c.importHuman(ctx, orgID, human, passwordless, orgIAMPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator, passwordlessCodeGenerator) + events, addedHuman, addedCode, code, err := c.importHuman(ctx, orgID, human, passwordless, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator, passwordlessCodeGenerator) if err != nil { return nil, nil, err } @@ -89,21 +89,21 @@ func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain. return writeModelToHuman(addedHuman), passwordlessCode, nil } -func (c *Commands) addHuman(ctx context.Context, orgID string, human *domain.Human, orgIAMPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) { +func (c *Commands) addHuman(ctx context.Context, orgID string, human *domain.Human, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) { if orgID == "" || !human.IsValid() { return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-67Ms8", "Errors.User.Invalid") } if human.Password != nil && human.SecretString != "" { human.ChangeRequired = true } - return c.createHuman(ctx, orgID, human, nil, false, false, orgIAMPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator) + return c.createHuman(ctx, orgID, human, nil, false, false, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator) } -func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.Human, passwordless bool, orgIAMPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator, passwordlessCodeGenerator crypto.Generator) (events []eventstore.Command, humanWriteModel *HumanWriteModel, passwordlessCodeWriteModel *HumanPasswordlessInitCodeWriteModel, code string, err error) { +func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.Human, passwordless bool, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator, passwordlessCodeGenerator crypto.Generator) (events []eventstore.Command, humanWriteModel *HumanWriteModel, passwordlessCodeWriteModel *HumanPasswordlessInitCodeWriteModel, code string, err error) { if orgID == "" || !human.IsValid() { return nil, nil, nil, "", caos_errs.ThrowInvalidArgument(nil, "COMMAND-00p2b", "Errors.User.Invalid") } - events, humanWriteModel, err = c.createHuman(ctx, orgID, human, nil, false, passwordless, orgIAMPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator) + events, humanWriteModel, err = c.createHuman(ctx, orgID, human, nil, false, passwordless, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator) if err != nil { return nil, nil, nil, "", err } @@ -122,7 +122,7 @@ func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domai if orgID == "" { return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-GEdf2", "Errors.ResourceOwnerMissing") } - orgIAMPolicy, err := c.getOrgDomainPolicy(ctx, orgID) + domainPolicy, err := c.getOrgDomainPolicy(ctx, orgID) if err != nil { return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-33M9f", "Errors.Org.DomainPolicy.NotFound") } @@ -137,7 +137,7 @@ func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domai if !loginPolicy.AllowRegister { return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-SAbr3", "Errors.Org.LoginPolicy.RegistrationNotAllowed") } - userEvents, registeredHuman, err := c.registerHuman(ctx, orgID, human, link, orgIAMPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator) + userEvents, registeredHuman, err := c.registerHuman(ctx, orgID, human, link, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator) if err != nil { return nil, err } @@ -171,7 +171,7 @@ func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domai return writeModelToHuman(registeredHuman), nil } -func (c *Commands) registerHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, orgIAMPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) { +func (c *Commands) registerHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) { if human != nil && human.Username == "" { human.Username = human.EmailAddress } @@ -181,14 +181,14 @@ func (c *Commands) registerHuman(ctx context.Context, orgID string, human *domai if human.Password != nil && human.SecretString != "" { human.ChangeRequired = false } - return c.createHuman(ctx, orgID, human, link, true, false, orgIAMPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator) + return c.createHuman(ctx, orgID, human, link, true, false, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator) } -func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, selfregister, passwordless bool, orgIAMPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) { - if err := human.CheckDomainPolicy(orgIAMPolicy); err != nil { +func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, selfregister, passwordless bool, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) { + if err := human.CheckDomainPolicy(domainPolicy); err != nil { return nil, nil, err } - if !orgIAMPolicy.UserLoginMustBeDomain { + if !domainPolicy.UserLoginMustBeDomain { usernameSplit := strings.Split(human.Username, "@") if len(usernameSplit) != 2 { return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Dfd21", "Errors.User.Invalid") @@ -219,9 +219,9 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain. var events []eventstore.Command if selfregister { - events = append(events, createRegisterHumanEvent(ctx, userAgg, human, orgIAMPolicy.UserLoginMustBeDomain)) + events = append(events, createRegisterHumanEvent(ctx, userAgg, human, domainPolicy.UserLoginMustBeDomain)) } else { - events = append(events, createAddHumanEvent(ctx, userAgg, human, orgIAMPolicy.UserLoginMustBeDomain)) + events = append(events, createAddHumanEvent(ctx, userAgg, human, domainPolicy.UserLoginMustBeDomain)) } if link != nil { diff --git a/internal/command/user_human_init_model.go b/internal/command/user_human_init_model.go index e757041643..00d6313a9b 100644 --- a/internal/command/user_human_init_model.go +++ b/internal/command/user_human_init_model.go @@ -67,7 +67,8 @@ func (wm *HumanInitCodeWriteModel) Query() *eventstore.SearchQueryBuilder { AddQuery(). AggregateTypes(user.AggregateType). AggregateIDs(wm.AggregateID). - EventTypes(user.UserV1AddedType, + EventTypes( + user.UserV1AddedType, user.HumanAddedType, user.UserV1RegisteredType, user.HumanRegisteredType, diff --git a/internal/command/user_human_model.go b/internal/command/user_human_model.go index 05ba4a771f..f52134aa80 100644 --- a/internal/command/user_human_model.go +++ b/internal/command/user_human_model.go @@ -109,7 +109,8 @@ func (wm *HumanWriteModel) Query() *eventstore.SearchQueryBuilder { AddQuery(). AggregateTypes(user.AggregateType). AggregateIDs(wm.AggregateID). - EventTypes(user.HumanAddedType, + EventTypes( + user.HumanAddedType, user.HumanRegisteredType, user.HumanInitialCodeAddedType, user.HumanInitializedCheckSucceededType, diff --git a/internal/command/user_human_otp.go b/internal/command/user_human_otp.go index a3782de63f..c38bbad409 100644 --- a/internal/command/user_human_otp.go +++ b/internal/command/user_human_otp.go @@ -28,7 +28,7 @@ func (c *Commands) AddHumanOTP(ctx context.Context, userID, resourceowner string orgPolicy, err := c.getOrgDomainPolicy(ctx, org.AggregateID) if err != nil { logging.Log("COMMAND-y5zv9").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get org policy for loginname") - return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-8ugTs", "Errors.Org.OrgIAMPolicy.NotFound") + return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-8ugTs", "Errors.Org.DomainPolicy.NotFound") } otpWriteModel, err := c.otpWriteModelByID(ctx, userID, resourceowner) if err != nil { diff --git a/internal/command/user_human_otp_test.go b/internal/command/user_human_otp_test.go index a7f4f95a5c..15dc2bb967 100644 --- a/internal/command/user_human_otp_test.go +++ b/internal/command/user_human_otp_test.go @@ -176,7 +176,7 @@ func TestCommandSide_AddHumanOTP(t *testing.T) { ), expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, true, ), diff --git a/internal/command/user_human_test.go b/internal/command/user_human_test.go index 0b9c6cd423..12e70429b6 100644 --- a/internal/command/user_human_test.go +++ b/internal/command/user_human_test.go @@ -102,7 +102,7 @@ func TestCommandSide_AddHuman(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, true, ), @@ -137,7 +137,7 @@ func TestCommandSide_AddHuman(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, true, ), @@ -178,7 +178,7 @@ func TestCommandSide_AddHuman(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, true, ), @@ -272,7 +272,7 @@ func TestCommandSide_AddHuman(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, true, ), @@ -359,7 +359,7 @@ func TestCommandSide_AddHuman(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, true, ), @@ -440,7 +440,7 @@ func TestCommandSide_AddHuman(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, true, ), @@ -539,7 +539,7 @@ func TestCommandSide_AddHuman(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, true, ), @@ -732,7 +732,7 @@ func TestCommandSide_ImportHuman(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, true, ), @@ -767,7 +767,7 @@ func TestCommandSide_ImportHuman(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, true, ), @@ -808,7 +808,7 @@ func TestCommandSide_ImportHuman(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, true, ), @@ -896,7 +896,7 @@ func TestCommandSide_ImportHuman(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, true, ), @@ -978,7 +978,7 @@ func TestCommandSide_ImportHuman(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, true, ), @@ -1082,7 +1082,7 @@ func TestCommandSide_ImportHuman(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, true, ), @@ -1190,7 +1190,7 @@ func TestCommandSide_ImportHuman(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, true, ), @@ -1294,7 +1294,7 @@ func TestCommandSide_ImportHuman(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, true, ), @@ -1495,7 +1495,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, true, ), @@ -1533,7 +1533,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, true, ), @@ -1579,7 +1579,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, true, ), @@ -1641,7 +1641,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, true, ), @@ -1703,7 +1703,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, false, ), @@ -1782,7 +1782,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, false, ), @@ -1919,7 +1919,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, true, ), @@ -2024,7 +2024,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &user.NewAggregate("org1", "org1").Aggregate, true, ), @@ -2123,7 +2123,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, true, ), @@ -2244,7 +2244,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, true, ), diff --git a/internal/command/user_human_webauthn_model.go b/internal/command/user_human_webauthn_model.go index d0ce4e86d0..7004e28973 100644 --- a/internal/command/user_human_webauthn_model.go +++ b/internal/command/user_human_webauthn_model.go @@ -317,8 +317,7 @@ type HumanU2FLoginReadModel struct { Challenge string AllowedCredentialIDs [][]byte UserVerification domain.UserVerificationRequirement - User - State domain.UserState + State domain.UserState } func NewHumanU2FLoginReadModel(userID, authReqID, resourceOwner string) *HumanU2FLoginReadModel { diff --git a/internal/command/user_machine.go b/internal/command/user_machine.go index 21460e02f5..d9e4d6738c 100644 --- a/internal/command/user_machine.go +++ b/internal/command/user_machine.go @@ -13,11 +13,11 @@ func (c *Commands) AddMachine(ctx context.Context, orgID string, machine *domain if !machine.IsValid() { return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-bm9Ds", "Errors.User.Invalid") } - orgIAMPolicy, err := c.getOrgDomainPolicy(ctx, orgID) + domainPolicy, err := c.getOrgDomainPolicy(ctx, orgID) if err != nil { return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-3M9fs", "Errors.Org.DomainPolicy.NotFound") } - if !orgIAMPolicy.UserLoginMustBeDomain { + if !domainPolicy.UserLoginMustBeDomain { return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M0ds", "Errors.User.Invalid") } userID, err := c.idGenerator.Next() @@ -33,7 +33,7 @@ func (c *Commands) AddMachine(ctx context.Context, orgID string, machine *domain machine.Username, machine.Name, machine.Description, - orgIAMPolicy.UserLoginMustBeDomain, + domainPolicy.UserLoginMustBeDomain, )) if err != nil { return nil, err diff --git a/internal/command/user_machine_test.go b/internal/command/user_machine_test.go index 36511c0f53..0c09bf6c25 100644 --- a/internal/command/user_machine_test.go +++ b/internal/command/user_machine_test.go @@ -83,7 +83,7 @@ func TestCommandSide_AddMachine(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, false, ), @@ -110,7 +110,7 @@ func TestCommandSide_AddMachine(t *testing.T) { t, expectFilter( eventFromEventPusher( - org.NewOrgDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, true, ), diff --git a/internal/command/user_model.go b/internal/command/user_model.go index bea85cfda4..14d0130df1 100644 --- a/internal/command/user_model.go +++ b/internal/command/user_model.go @@ -128,7 +128,7 @@ func UserAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregat func CheckDomainPolicyForUserName(userName string, policy *domain.DomainPolicy) error { if policy == nil { - return caos_errors.ThrowPreconditionFailed(nil, "COMMAND-3Mb9s", "Errors.Users.OrgIamPolicyNil") + return caos_errors.ThrowPreconditionFailed(nil, "COMMAND-3Mb9s", "Errors.Users.DomainPolicyNil") } if policy.UserLoginMustBeDomain && strings.Contains(userName, "@") { return caos_errors.ThrowPreconditionFailed(nil, "COMMAND-4M9vs", "Errors.User.EmailAsUsernameNotAllowed") diff --git a/internal/command/user_test.go b/internal/command/user_test.go index feb70424a5..913440ea26 100644 --- a/internal/command/user_test.go +++ b/internal/command/user_test.go @@ -202,7 +202,7 @@ func TestCommandSide_UsernameChange(t *testing.T) { expectFilter(), expectFilter( eventFromEventPusher( - instance.NewInstnaceDomainPolicyAddedEvent(context.Background(), + instance.NewDomainPolicyAddedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, true, ), @@ -244,7 +244,7 @@ func TestCommandSide_UsernameChange(t *testing.T) { expectFilter(), expectFilter( eventFromEventPusher( - instance.NewInstnaceDomainPolicyAddedEvent(context.Background(), + instance.NewDomainPolicyAddedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, true, ), @@ -1026,7 +1026,7 @@ func TestCommandSide_RemoveUser(t *testing.T) { expectFilter(), expectFilter( eventFromEventPusher( - instance.NewInstnaceDomainPolicyAddedEvent(context.Background(), + instance.NewDomainPolicyAddedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, true, ), @@ -1090,7 +1090,7 @@ func TestCommandSide_RemoveUser(t *testing.T) { expectFilter(), expectFilter( eventFromEventPusher( - instance.NewInstnaceDomainPolicyAddedEvent(context.Background(), + instance.NewDomainPolicyAddedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, true, ), @@ -1147,7 +1147,7 @@ func TestCommandSide_RemoveUser(t *testing.T) { expectFilter(), expectFilter( eventFromEventPusher( - instance.NewInstnaceDomainPolicyAddedEvent(context.Background(), + instance.NewDomainPolicyAddedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, true, ), diff --git a/internal/command/v2/command.go b/internal/command/v2/command.go new file mode 100644 index 0000000000..3fd4c66903 --- /dev/null +++ b/internal/command/v2/command.go @@ -0,0 +1,36 @@ +package command + +import ( + sd "github.com/caos/zitadel/internal/config/systemdefaults" + "github.com/caos/zitadel/internal/crypto" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/action" + iam_repo "github.com/caos/zitadel/internal/repository/instance" + "github.com/caos/zitadel/internal/repository/keypair" + "github.com/caos/zitadel/internal/repository/org" + proj_repo "github.com/caos/zitadel/internal/repository/project" + usr_repo "github.com/caos/zitadel/internal/repository/user" + usr_grant_repo "github.com/caos/zitadel/internal/repository/usergrant" +) + +type Command struct { + es *eventstore.Eventstore + userPasswordAlg crypto.HashAlgorithm + iamDomain string +} + +func New(es *eventstore.Eventstore, iamDomain string, defaults sd.SystemDefaults) *Command { + iam_repo.RegisterEventMappers(es) + org.RegisterEventMappers(es) + usr_repo.RegisterEventMappers(es) + usr_grant_repo.RegisterEventMappers(es) + proj_repo.RegisterEventMappers(es) + keypair.RegisterEventMappers(es) + action.RegisterEventMappers(es) + + return &Command{ + es: es, + iamDomain: iamDomain, + userPasswordAlg: crypto.NewBCrypt(defaults.SecretGenerators.PasswordSaltCost), + } +} diff --git a/internal/command/v2/instance.go b/internal/command/v2/instance.go new file mode 100644 index 0000000000..815aee041b --- /dev/null +++ b/internal/command/v2/instance.go @@ -0,0 +1,290 @@ +package command + +import ( + "context" + "time" + + "github.com/caos/zitadel/internal/api/authz" + "github.com/caos/zitadel/internal/api/ui/console" + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/id" + "github.com/caos/zitadel/internal/repository/instance" + "github.com/caos/zitadel/internal/repository/org" + "github.com/caos/zitadel/internal/repository/project" + "github.com/caos/zitadel/internal/repository/user" +) + +const ( + zitadelProjectName = "ZITADEL" + mgmtAppName = "Management-API" + adminAppName = "Admin-API" + authAppName = "Auth-API" + consoleAppName = "Console" + consoleRedirectPath = console.HandlerPrefix + "/auth/callback" + consolePostLogoutPath = console.HandlerPrefix + "/signedout" +) + +type InstanceSetup struct { + Org OrgSetup + Zitadel ZitadelConfig + PasswordComplexityPolicy struct { + MinLength uint64 + HasLowercase bool + HasUppercase bool + HasNumber bool + HasSymbol bool + } + PasswordAgePolicy struct { + ExpireWarnDays uint64 + MaxAgeDays uint64 + } + DomainPolicy struct { + UserLoginMustBeDomain bool + } + LoginPolicy struct { + AllowUsernamePassword bool + AllowRegister bool + AllowExternalIDP bool + ForceMFA bool + HidePasswordReset bool + PasswordlessType domain.PasswordlessType + PasswordCheckLifetime time.Duration + ExternalLoginCheckLifetime time.Duration + MfaInitSkipLifetime time.Duration + SecondFactorCheckLifetime time.Duration + MultiFactorCheckLifetime time.Duration + } + PrivacyPolicy struct { + TOSLink string + PrivacyLink string + HelpLink string + } + LockoutPolicy struct { + MaxAttempts uint64 + ShouldShowLockoutFailure bool + } + EmailTemplate []byte + MessageTexts []*domain.CustomMessageText +} + +type ZitadelConfig struct { + IsDevMode bool + BaseURL string + + projectID string + mgmtID string + mgmtClientID string + adminID string + adminClientID string + authID string + authClientID string + consoleID string + consoleClientID string +} + +func (s *InstanceSetup) generateIDs() (err error) { + s.Zitadel.projectID, err = id.SonyFlakeGenerator.Next() + if err != nil { + return err + } + + s.Zitadel.mgmtID, err = id.SonyFlakeGenerator.Next() + if err != nil { + return err + } + s.Zitadel.mgmtClientID, err = domain.NewClientID(id.SonyFlakeGenerator, zitadelProjectName) + if err != nil { + return err + } + + s.Zitadel.adminID, err = id.SonyFlakeGenerator.Next() + if err != nil { + return err + } + s.Zitadel.adminClientID, err = domain.NewClientID(id.SonyFlakeGenerator, zitadelProjectName) + if err != nil { + return err + } + + s.Zitadel.authID, err = id.SonyFlakeGenerator.Next() + if err != nil { + return err + } + s.Zitadel.authClientID, err = domain.NewClientID(id.SonyFlakeGenerator, zitadelProjectName) + if err != nil { + return err + } + + s.Zitadel.consoleID, err = id.SonyFlakeGenerator.Next() + if err != nil { + return err + } + s.Zitadel.consoleClientID, err = domain.NewClientID(id.SonyFlakeGenerator, zitadelProjectName) + if err != nil { + return err + } + return nil +} + +func (command *Command) SetUpInstance(ctx context.Context, setup *InstanceSetup) (*domain.ObjectDetails, error) { + // TODO + // instanceID, err := id.SonyFlakeGenerator.Next() + // if err != nil { + // return nil, err + // } + ctx = authz.SetCtxData(authz.WithInstance(ctx, authz.Instance{ID: "system"}), authz.CtxData{OrgID: domain.IAMID, ResourceOwner: domain.IAMID}) + + orgID, err := id.SonyFlakeGenerator.Next() + if err != nil { + return nil, err + } + + userID, err := id.SonyFlakeGenerator.Next() + if err != nil { + return nil, err + } + + if err = setup.generateIDs(); err != nil { + return nil, err + } + + setup.Org.Human.PasswordChangeRequired = true + + instanceAgg := instance.NewAggregate() + orgAgg := org.NewAggregate(orgID, orgID) + userAgg := user.NewAggregate(userID, orgID) + projectAgg := project.NewAggregate(setup.Zitadel.projectID, orgID) + + validations := []preparation.Validation{ + AddPasswordComplexityPolicy( + instanceAgg, + setup.PasswordComplexityPolicy.MinLength, + setup.PasswordComplexityPolicy.HasLowercase, + setup.PasswordComplexityPolicy.HasUppercase, + setup.PasswordComplexityPolicy.HasNumber, + setup.PasswordComplexityPolicy.HasSymbol, + ), + AddPasswordAgePolicy( + instanceAgg, + setup.PasswordAgePolicy.ExpireWarnDays, + setup.PasswordAgePolicy.MaxAgeDays, + ), + AddDefaultDomainPolicy( + instanceAgg, + setup.DomainPolicy.UserLoginMustBeDomain, + ), + AddDefaultLoginPolicy( + instanceAgg, + setup.LoginPolicy.AllowUsernamePassword, + setup.LoginPolicy.AllowRegister, + setup.LoginPolicy.AllowExternalIDP, + setup.LoginPolicy.ForceMFA, + setup.LoginPolicy.HidePasswordReset, + setup.LoginPolicy.PasswordlessType, + setup.LoginPolicy.PasswordCheckLifetime, + setup.LoginPolicy.ExternalLoginCheckLifetime, + setup.LoginPolicy.MfaInitSkipLifetime, + setup.LoginPolicy.SecondFactorCheckLifetime, + setup.LoginPolicy.MultiFactorCheckLifetime, + ), + AddSecondFactorToDefaultLoginPolicy(instanceAgg, domain.SecondFactorTypeOTP), + AddSecondFactorToDefaultLoginPolicy(instanceAgg, domain.SecondFactorTypeU2F), + AddMultiFactorToDefaultLoginPolicy(instanceAgg, domain.MultiFactorTypeU2FWithPIN), + + AddPrivacyPolicy(instanceAgg, setup.PrivacyPolicy.TOSLink, setup.PrivacyPolicy.PrivacyLink, setup.PrivacyPolicy.HelpLink), + AddDefaultLockoutPolicy(instanceAgg, setup.LockoutPolicy.MaxAttempts, setup.LockoutPolicy.ShouldShowLockoutFailure), + + AddEmailTemplate(instanceAgg, setup.EmailTemplate), + } + + for _, msg := range setup.MessageTexts { + validations = append(validations, SetInstanceCustomTexts(instanceAgg, msg)) + } + + validations = append(validations, + AddOrg(orgAgg, setup.Org.Name, command.iamDomain), + AddHumanCommand(userAgg, &setup.Org.Human, command.userPasswordAlg), + AddOrgMember(orgAgg, userID, domain.RoleOrgOwner), + + AddProject(projectAgg, zitadelProjectName, userID, false, false, false, domain.PrivateLabelingSettingUnspecified), + + SetIAMProject(instanceAgg, projectAgg.ID), + + AddAPIApp( + *projectAgg, + setup.Zitadel.mgmtID, + mgmtAppName, + setup.Zitadel.mgmtClientID, + nil, + domain.APIAuthMethodTypePrivateKeyJWT, + ), + + AddAPIApp( + *projectAgg, + setup.Zitadel.adminID, + adminAppName, + setup.Zitadel.adminClientID, + nil, + domain.APIAuthMethodTypePrivateKeyJWT, + ), + + AddAPIApp( + *projectAgg, + setup.Zitadel.authID, + authAppName, + setup.Zitadel.authClientID, + nil, + domain.APIAuthMethodTypePrivateKeyJWT, + ), + + AddOIDCApp( + *projectAgg, + domain.OIDCVersionV1, + setup.Zitadel.consoleID, + consoleAppName, + setup.Zitadel.consoleClientID, + nil, + []string{setup.Zitadel.BaseURL + consoleRedirectPath}, + []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, + []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, + domain.OIDCApplicationTypeUserAgent, + domain.OIDCAuthMethodTypeNone, + []string{setup.Zitadel.BaseURL + consolePostLogoutPath}, + setup.Zitadel.IsDevMode, + domain.OIDCTokenTypeBearer, + false, + false, + false, + 0, + nil, + ), + ) + + cmds, err := preparation.PrepareCommands(ctx, command.es.Filter, validations...) + if err != nil { + return nil, err + } + + events, err := command.es.Push(ctx, cmds...) + if err != nil { + return nil, err + } + return &domain.ObjectDetails{ + Sequence: events[len(events)-1].Sequence(), + EventDate: events[len(events)-1].CreationDate(), + ResourceOwner: orgID, + }, nil +} + +//SetIAMProject defines the commands to set the id of the IAM project onto the instance +func SetIAMProject(a *instance.Aggregate, projectID string) preparation.Validation { + return func() (preparation.CreateCommands, error) { + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + return []eventstore.Command{ + instance.NewIAMProjectSetEvent(ctx, &a.Aggregate, projectID), + }, nil + }, nil + } +} diff --git a/internal/command/v2/instance_domain_policy.go b/internal/command/v2/instance_domain_policy.go new file mode 100644 index 0000000000..0a6237c5fb --- /dev/null +++ b/internal/command/v2/instance_domain_policy.go @@ -0,0 +1,25 @@ +package command + +import ( + "context" + + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/instance" +) + +func AddDefaultDomainPolicy( + a *instance.Aggregate, + userLoginMustBeDomain bool, +) preparation.Validation { + return func() (preparation.CreateCommands, error) { + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + //TODO: check if already exists + return []eventstore.Command{ + instance.NewDomainPolicyAddedEvent(ctx, &a.Aggregate, + userLoginMustBeDomain, + ), + }, nil + }, nil + } +} diff --git a/internal/command/v2/instance_email_template.go b/internal/command/v2/instance_email_template.go new file mode 100644 index 0000000000..642bf4446e --- /dev/null +++ b/internal/command/v2/instance_email_template.go @@ -0,0 +1,107 @@ +package command + +import ( + "context" + + "golang.org/x/text/language" + + "github.com/caos/zitadel/internal/command" + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/instance" +) + +func AddEmailTemplate( + a *instance.Aggregate, + tempalte []byte, +) preparation.Validation { + return func() (preparation.CreateCommands, error) { + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + //TODO: check if already exists + return []eventstore.Command{ + instance.NewMailTemplateAddedEvent(ctx, &a.Aggregate, + tempalte, + ), + }, nil + }, nil + } +} + +func SetInstanceCustomTexts( + a *instance.Aggregate, + msg *domain.CustomMessageText, +) preparation.Validation { + return func() (preparation.CreateCommands, error) { + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + existing, err := existingInstanceCustomMessageText(ctx, filter, msg.MessageTextType, msg.Language) + if err != nil { + return nil, err + } + + cmds := make([]eventstore.Command, 0, 7) + if existing.Greeting != msg.Greeting { + if msg.Greeting != "" { + cmds = append(cmds, instance.NewCustomTextSetEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessageGreeting, msg.Greeting, msg.Language)) + } else { + cmds = append(cmds, instance.NewCustomTextRemovedEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessageGreeting, msg.Language)) + } + } + if existing.Subject != msg.Subject { + if msg.Subject != "" { + cmds = append(cmds, instance.NewCustomTextSetEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessageSubject, msg.Subject, msg.Language)) + } else { + cmds = append(cmds, instance.NewCustomTextRemovedEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessageSubject, msg.Language)) + } + } + if existing.Title != msg.Title { + if msg.Title != "" { + cmds = append(cmds, instance.NewCustomTextSetEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessageTitle, msg.Title, msg.Language)) + } else { + cmds = append(cmds, instance.NewCustomTextRemovedEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessageTitle, msg.Language)) + } + } + if existing.PreHeader != msg.PreHeader { + if msg.PreHeader != "" { + cmds = append(cmds, instance.NewCustomTextSetEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessagePreHeader, msg.PreHeader, msg.Language)) + } else { + cmds = append(cmds, instance.NewCustomTextRemovedEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessagePreHeader, msg.Language)) + } + } + if existing.Text != msg.Text { + if msg.Text != "" { + cmds = append(cmds, instance.NewCustomTextSetEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessageText, msg.Text, msg.Language)) + } else { + cmds = append(cmds, instance.NewCustomTextRemovedEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessageText, msg.Language)) + } + } + if existing.ButtonText != msg.ButtonText { + if msg.ButtonText != "" { + cmds = append(cmds, instance.NewCustomTextSetEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessageButtonText, msg.ButtonText, msg.Language)) + } else { + cmds = append(cmds, instance.NewCustomTextRemovedEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessageButtonText, msg.Language)) + } + } + if existing.FooterText != msg.FooterText { + if msg.FooterText != "" { + cmds = append(cmds, instance.NewCustomTextSetEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessageFooterText, msg.FooterText, msg.Language)) + } else { + cmds = append(cmds, instance.NewCustomTextRemovedEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessageFooterText, msg.Language)) + } + } + // TODO: what if no text changed? len(events) == 0 + return cmds, nil + }, nil + } +} + +func existingInstanceCustomMessageText(ctx context.Context, filter preparation.FilterToQueryReducer, textType string, lang language.Tag) (*command.InstanceCustomMessageTextWriteModel, error) { + writeModel := command.NewInstanceCustomMessageTextWriteModel(textType, lang) + events, err := filter(ctx, writeModel.Query()) + if err != nil { + return nil, err + } + writeModel.AppendEvents(events...) + writeModel.Reduce() + return writeModel, nil +} diff --git a/internal/command/v2/instance_label_policy.go b/internal/command/v2/instance_label_policy.go new file mode 100644 index 0000000000..3946a3408a --- /dev/null +++ b/internal/command/v2/instance_label_policy.go @@ -0,0 +1,45 @@ +package command + +import ( + "context" + + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/instance" +) + +func AddDefaultLabelPolicy( + a *instance.Aggregate, + primaryColor, + backgroundColor, + warnColor, + fontColor, + primaryColorDark, + backgroundColorDark, + warnColorDark, + fontColorDark string, + hideLoginNameSuffix, + errorMsgPopup, + disableWatermark bool, +) preparation.Validation { + return func() (preparation.CreateCommands, error) { + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + //TODO: check if already exists + return []eventstore.Command{ + instance.NewLabelPolicyAddedEvent(ctx, &a.Aggregate, + primaryColor, + backgroundColor, + warnColor, + fontColor, + primaryColorDark, + backgroundColorDark, + warnColorDark, + fontColorDark, + hideLoginNameSuffix, + errorMsgPopup, + disableWatermark, + ), + }, nil + }, nil + } +} diff --git a/internal/command/v2/instance_lockout_policy.go b/internal/command/v2/instance_lockout_policy.go new file mode 100644 index 0000000000..1860894cee --- /dev/null +++ b/internal/command/v2/instance_lockout_policy.go @@ -0,0 +1,24 @@ +package command + +import ( + "context" + + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/instance" +) + +func AddDefaultLockoutPolicy( + a *instance.Aggregate, + maxAttempts uint64, + showLockoutFailure bool, +) preparation.Validation { + return func() (preparation.CreateCommands, error) { + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + //TODO: check if already exists + return []eventstore.Command{ + instance.NewLockoutPolicyAddedEvent(ctx, &a.Aggregate, maxAttempts, showLockoutFailure), + }, nil + }, nil + } +} diff --git a/internal/command/v2/instance_login_policy.go b/internal/command/v2/instance_login_policy.go new file mode 100644 index 0000000000..9da82fac83 --- /dev/null +++ b/internal/command/v2/instance_login_policy.go @@ -0,0 +1,69 @@ +package command + +import ( + "context" + "time" + + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/instance" +) + +func AddDefaultLoginPolicy( + a *instance.Aggregate, + allowUsernamePassword bool, + allowRegister bool, + allowExternalIDP bool, + forceMFA bool, + hidePasswordReset bool, + passwordlessType domain.PasswordlessType, + passwordCheckLifetime time.Duration, + externalLoginCheckLifetime time.Duration, + mfaInitSkipLifetime time.Duration, + secondFactorCheckLifetime time.Duration, + multiFactorCheckLifetime time.Duration, +) preparation.Validation { + return func() (preparation.CreateCommands, error) { + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + //TODO: check if already exists + return []eventstore.Command{ + instance.NewLoginPolicyAddedEvent(ctx, &a.Aggregate, + allowUsernamePassword, + allowRegister, + allowExternalIDP, + forceMFA, + hidePasswordReset, + passwordlessType, + passwordCheckLifetime, + externalLoginCheckLifetime, + mfaInitSkipLifetime, + secondFactorCheckLifetime, + multiFactorCheckLifetime, + ), + }, nil + }, nil + } +} + +func AddSecondFactorToDefaultLoginPolicy(a *instance.Aggregate, factor domain.SecondFactorType) preparation.Validation { + return func() (preparation.CreateCommands, error) { + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + //TODO: check if already exists + return []eventstore.Command{ + instance.NewLoginPolicySecondFactorAddedEvent(ctx, &a.Aggregate, factor), + }, nil + }, nil + } +} + +func AddMultiFactorToDefaultLoginPolicy(a *instance.Aggregate, factor domain.MultiFactorType) preparation.Validation { + return func() (preparation.CreateCommands, error) { + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + //TODO: check if already exists + return []eventstore.Command{ + instance.NewLoginPolicyMultiFactorAddedEvent(ctx, &a.Aggregate, factor), + }, nil + }, nil + } +} diff --git a/internal/command/v2/instance_password_age_policy.go b/internal/command/v2/instance_password_age_policy.go new file mode 100644 index 0000000000..46fc711dcc --- /dev/null +++ b/internal/command/v2/instance_password_age_policy.go @@ -0,0 +1,27 @@ +package command + +import ( + "context" + + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/instance" +) + +func AddPasswordAgePolicy( + a *instance.Aggregate, + expireWarnDays, + maxAgeDays uint64, +) preparation.Validation { + return func() (preparation.CreateCommands, error) { + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + //TODO: check if already exists + return []eventstore.Command{ + instance.NewPasswordAgePolicyAddedEvent(ctx, &a.Aggregate, + expireWarnDays, + maxAgeDays, + ), + }, nil + }, nil + } +} diff --git a/internal/command/v2/instance_password_complexity_policy.go b/internal/command/v2/instance_password_complexity_policy.go new file mode 100644 index 0000000000..9ce04c2f36 --- /dev/null +++ b/internal/command/v2/instance_password_complexity_policy.go @@ -0,0 +1,33 @@ +package command + +import ( + "context" + + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/instance" +) + +func AddPasswordComplexityPolicy( + a *instance.Aggregate, + minLength uint64, + hasLowercase, + hasUppercase, + hasNumber, + hasSymbol bool, +) preparation.Validation { + return func() (preparation.CreateCommands, error) { + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + //TODO: check if already exists + return []eventstore.Command{ + instance.NewPasswordComplexityPolicyAddedEvent(ctx, &a.Aggregate, + minLength, + hasLowercase, + hasUppercase, + hasNumber, + hasSymbol, + ), + }, nil + }, nil + } +} diff --git a/internal/command/v2/instance_privacy_policy.go b/internal/command/v2/instance_privacy_policy.go new file mode 100644 index 0000000000..00a2b7f9c9 --- /dev/null +++ b/internal/command/v2/instance_privacy_policy.go @@ -0,0 +1,25 @@ +package command + +import ( + "context" + + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/instance" +) + +func AddPrivacyPolicy( + a *instance.Aggregate, + tosLink, + privacyLink, + helpLink string, +) preparation.Validation { + return func() (preparation.CreateCommands, error) { + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + //TODO: check if already exists + return []eventstore.Command{ + instance.NewPrivacyPolicyAddedEvent(ctx, &a.Aggregate, tosLink, privacyLink, helpLink), + }, nil + }, nil + } +} diff --git a/internal/command/v2/org.go b/internal/command/v2/org.go new file mode 100644 index 0000000000..66e8ad8994 --- /dev/null +++ b/internal/command/v2/org.go @@ -0,0 +1,72 @@ +package command + +import ( + "context" + "strings" + + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/id" + "github.com/caos/zitadel/internal/repository/org" + user_repo "github.com/caos/zitadel/internal/repository/user" +) + +type OrgSetup struct { + Name string + Human AddHuman +} + +func (command *Command) SetUpOrg(ctx context.Context, o *OrgSetup) (*domain.ObjectDetails, error) { + orgID, err := id.SonyFlakeGenerator.Next() + if err != nil { + return nil, err + } + + userID, err := id.SonyFlakeGenerator.Next() + if err != nil { + return nil, err + } + + orgAgg := org.NewAggregate(orgID, orgID) + userAgg := user_repo.NewAggregate(userID, orgID) + + cmds, err := preparation.PrepareCommands(ctx, command.es.Filter, + AddOrg(orgAgg, o.Name, command.iamDomain), + AddHumanCommand(userAgg, &o.Human, command.userPasswordAlg), + AddOrgMember(orgAgg, userID, domain.RoleOrgOwner), + ) + if err != nil { + return nil, err + } + + events, err := command.es.Push(ctx, cmds...) + if err != nil { + return nil, err + } + return &domain.ObjectDetails{ + Sequence: events[len(events)-1].Sequence(), + EventDate: events[len(events)-1].CreationDate(), + ResourceOwner: orgID, + }, nil +} + +//AddOrg defines the commands to create a new org, +// this includes the verified default domain +func AddOrg(a *org.Aggregate, name, iamDomain string) preparation.Validation { + return func() (preparation.CreateCommands, error) { + if name = strings.TrimSpace(name); name == "" { + return nil, errors.ThrowInvalidArgument(nil, "ORG-mruNY", "Errors.Invalid.Argument") + } + defaultDomain := domain.NewIAMDomainName(name, iamDomain) + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + return []eventstore.Command{ + org.NewOrgAddedEvent(ctx, &a.Aggregate, name), + org.NewDomainAddedEvent(ctx, &a.Aggregate, defaultDomain), + org.NewDomainVerifiedEvent(ctx, &a.Aggregate, defaultDomain), + org.NewDomainPrimarySetEvent(ctx, &a.Aggregate, defaultDomain), + }, nil + }, nil + } +} diff --git a/internal/command/v2/org_domain.go b/internal/command/v2/org_domain.go new file mode 100644 index 0000000000..9393a1097a --- /dev/null +++ b/internal/command/v2/org_domain.go @@ -0,0 +1,46 @@ +package command + +import ( + "context" + "strings" + + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/org" +) + +func AddOrgDomain(a *org.Aggregate, domain string) preparation.Validation { + return func() (preparation.CreateCommands, error) { + if domain = strings.TrimSpace(domain); domain == "" { + return nil, errors.ThrowInvalidArgument(nil, "ORG-r3h4J", "Errors.Invalid.Argument") + } + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + return []eventstore.Command{org.NewDomainAddedEvent(ctx, &a.Aggregate, domain)}, nil + }, nil + } +} + +func VerifyOrgDomain(a *org.Aggregate, domain string) preparation.Validation { + return func() (preparation.CreateCommands, error) { + if domain = strings.TrimSpace(domain); domain == "" { + return nil, errors.ThrowInvalidArgument(nil, "ORG-yqlVQ", "Errors.Invalid.Argument") + } + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + //TODO: check if already exists + return []eventstore.Command{org.NewDomainVerifiedEvent(ctx, &a.Aggregate, domain)}, nil + }, nil + } +} + +func SetPrimaryOrgDomain(a *org.Aggregate, domain string) preparation.Validation { + return func() (preparation.CreateCommands, error) { + if domain = strings.TrimSpace(domain); domain == "" { + return nil, errors.ThrowInvalidArgument(nil, "ORG-gmNqY", "Errors.Invalid.Argument") + } + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + //TODO: check if already exists and verified + return []eventstore.Command{org.NewDomainPrimarySetEvent(ctx, &a.Aggregate, domain)}, nil + }, nil + } +} diff --git a/internal/command/v2/org_domain_test.go b/internal/command/v2/org_domain_test.go new file mode 100644 index 0000000000..8a5ff01ae1 --- /dev/null +++ b/internal/command/v2/org_domain_test.go @@ -0,0 +1,133 @@ +package command + +import ( + "context" + "testing" + + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/org" +) + +func TestAddDomain(t *testing.T) { + type args struct { + a *org.Aggregate + domain string + } + + tests := []struct { + name string + args args + want Want + }{ + { + name: "invalid domain", + args: args{ + a: org.NewAggregate("test", "test"), + domain: "", + }, + want: Want{ + ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-r3h4J", "Errors.Invalid.Argument"), + }, + }, + { + name: "correct", + args: args{ + a: org.NewAggregate("test", "test"), + domain: "domain", + }, + want: Want{ + Commands: []eventstore.Command{ + org.NewDomainAddedEvent(context.Background(), &org.NewAggregate("test", "test").Aggregate, "domain"), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + AssertValidation(t, AddOrgDomain(tt.args.a, tt.args.domain), nil, tt.want) + }) + } +} + +func TestVerifyDomain(t *testing.T) { + type args struct { + a *org.Aggregate + domain string + } + + tests := []struct { + name string + args args + want Want + }{ + { + name: "invalid domain", + args: args{ + a: org.NewAggregate("test", "test"), + domain: "", + }, + want: Want{ + ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-yqlVQ", "Errors.Invalid.Argument"), + }, + }, + { + name: "correct", + args: args{ + a: org.NewAggregate("test", "test"), + domain: "domain", + }, + want: Want{ + Commands: []eventstore.Command{ + org.NewDomainVerifiedEvent(context.Background(), &org.NewAggregate("test", "test").Aggregate, "domain"), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + AssertValidation(t, VerifyOrgDomain(tt.args.a, tt.args.domain), nil, tt.want) + }) + } +} + +func TestSetDomainPrimary(t *testing.T) { + type args struct { + a *org.Aggregate + domain string + } + + tests := []struct { + name string + args args + want Want + }{ + { + name: "invalid domain", + args: args{ + a: org.NewAggregate("test", "test"), + domain: "", + }, + want: Want{ + ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-gmNqY", "Errors.Invalid.Argument"), + }, + }, + { + name: "correct", + args: args{ + a: org.NewAggregate("test", "test"), + domain: "domain", + }, + want: Want{ + Commands: []eventstore.Command{ + org.NewDomainPrimarySetEvent(context.Background(), &org.NewAggregate("test", "test").Aggregate, "domain"), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + AssertValidation(t, SetPrimaryOrgDomain(tt.args.a, tt.args.domain), nil, tt.want) + }) + } +} diff --git a/internal/command/v2/org_member.go b/internal/command/v2/org_member.go new file mode 100644 index 0000000000..f5182fb85e --- /dev/null +++ b/internal/command/v2/org_member.go @@ -0,0 +1,65 @@ +package command + +import ( + "context" + + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/org" +) + +func AddOrgMember(a *org.Aggregate, userID string, roles ...string) preparation.Validation { + return func() (preparation.CreateCommands, error) { + if userID == "" { + return nil, errors.ThrowInvalidArgument(nil, "ORG-4Mlfs", "Errors.Invalid.Argument") + } + // TODO: check roles + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + if exists, err := ExistsUser(ctx, filter, userID, a.ID); err != nil || !exists { + return nil, errors.ThrowNotFound(err, "ORG-GoXOn", "Errors.User.NotFound") + } + if isMember, err := IsOrgMember(ctx, filter, a.ID, userID); err != nil || isMember { + return nil, errors.ThrowAlreadyExists(err, "ORG-poWwe", "Errors.Org.Member.AlreadyExists") + } + return []eventstore.Command{org.NewMemberAddedEvent(ctx, &a.Aggregate, userID, roles...)}, nil + }, + nil + } +} + +func IsOrgMember(ctx context.Context, filter preparation.FilterToQueryReducer, orgID, userID string) (isMember bool, err error) { + events, err := filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + ResourceOwner(orgID). + OrderAsc(). + AddQuery(). + AggregateIDs(orgID). + AggregateTypes(org.AggregateType). + EventTypes( + org.MemberAddedEventType, + org.MemberRemovedEventType, + org.MemberCascadeRemovedEventType, + ).Builder()) + if err != nil { + return false, err + } + + for _, event := range events { + switch e := event.(type) { + case *org.MemberAddedEvent: + if e.UserID == userID { + isMember = true + } + case *org.MemberRemovedEvent: + if e.UserID == userID { + isMember = false + } + case *org.MemberCascadeRemovedEvent: + if e.UserID == userID { + isMember = false + } + } + } + + return isMember, nil +} diff --git a/internal/command/v2/org_member_test.go b/internal/command/v2/org_member_test.go new file mode 100644 index 0000000000..88665f628e --- /dev/null +++ b/internal/command/v2/org_member_test.go @@ -0,0 +1,249 @@ +package command + +import ( + "context" + "testing" + + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/org" + "github.com/caos/zitadel/internal/repository/user" +) + +func TestAddMember(t *testing.T) { + type args struct { + a *org.Aggregate + userID string + roles []string + filter preparation.FilterToQueryReducer + } + + ctx := context.Background() + agg := org.NewAggregate("test", "test") + + tests := []struct { + name string + args args + want Want + }{ + { + name: "no user id", + args: args{ + a: agg, + userID: "", + }, + want: Want{ + ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-4Mlfs", "Errors.Invalid.Argument"), + }, + }, + // { + // name: "TODO: invalid roles", + // args: args{ + // a: agg, + // userID: "", + // roles: []string{""}, + // }, + // want: preparation.Want{ + // ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-4Mlfs", "Errors.Invalid.Argument"), + // }, + // }, + { + name: "user not exists", + args: args{ + a: agg, + userID: "userID", + filter: NewMultiFilter(). + Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return nil, nil + }). + Filter(), + }, + want: Want{ + CreateErr: errors.ThrowNotFound(nil, "ORG-GoXOn", "Errors.User.NotFound"), + }, + }, + { + name: "already member", + args: args{ + a: agg, + userID: "userID", + filter: NewMultiFilter(). + Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + user.NewMachineAddedEvent( + ctx, + &user.NewAggregate("id", "ro").Aggregate, + "userName", + "name", + "description", + true, + ), + }, nil + }). + Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + org.NewMemberAddedEvent( + ctx, + &org.NewAggregate("id", "ro").Aggregate, + "userID", + ), + }, nil + }). + Filter(), + }, + want: Want{ + CreateErr: errors.ThrowAlreadyExists(nil, "ORG-poWwe", "Errors.Org.Member.AlreadyExists"), + }, + }, + { + name: "correct", + args: args{ + a: agg, + userID: "userID", + filter: NewMultiFilter(). + Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + user.NewMachineAddedEvent( + ctx, + &user.NewAggregate("id", "ro").Aggregate, + "userName", + "name", + "description", + true, + ), + }, nil + }). + Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return nil, nil + }). + Filter(), + }, + want: Want{ + Commands: []eventstore.Command{ + org.NewMemberAddedEvent(ctx, &agg.Aggregate, "userID"), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + AssertValidation(t, AddOrgMember(tt.args.a, tt.args.userID, tt.args.roles...), tt.args.filter, tt.want) + }) + } +} + +func TestIsMember(t *testing.T) { + type args struct { + filter preparation.FilterToQueryReducer + orgID string + userID string + } + tests := []struct { + name string + args args + wantExists bool + wantErr bool + }{ + { + name: "no events", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{}, nil + }, + orgID: "orgID", + userID: "userID", + }, + wantExists: false, + wantErr: false, + }, + { + name: "member added", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + org.NewMemberAddedEvent( + context.Background(), + &org.NewAggregate("orgID", "ro").Aggregate, + "userID", + ), + }, nil + }, + orgID: "orgID", + userID: "userID", + }, + wantExists: true, + wantErr: false, + }, + { + name: "member removed", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + org.NewMemberAddedEvent( + context.Background(), + &org.NewAggregate("orgID", "ro").Aggregate, + "userID", + ), + org.NewMemberRemovedEvent( + context.Background(), + &org.NewAggregate("orgID", "ro").Aggregate, + "userID", + ), + }, nil + }, + orgID: "orgID", + userID: "userID", + }, + wantExists: false, + wantErr: false, + }, + { + name: "member cascade removed", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + org.NewMemberAddedEvent( + context.Background(), + &org.NewAggregate("orgID", "ro").Aggregate, + "userID", + ), + org.NewMemberCascadeRemovedEvent( + context.Background(), + &org.NewAggregate("orgID", "ro").Aggregate, + "userID", + ), + }, nil + }, + orgID: "orgID", + userID: "userID", + }, + wantExists: false, + wantErr: false, + }, + { + name: "error durring filter", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return nil, errors.ThrowInternal(nil, "PROJE-Op26p", "Errors.Internal") + }, + orgID: "orgID", + userID: "userID", + }, + wantExists: false, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotExists, err := IsOrgMember(context.Background(), tt.args.filter, tt.args.orgID, tt.args.userID) + if (err != nil) != tt.wantErr { + t.Errorf("ExistsUser() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotExists != tt.wantExists { + t.Errorf("ExistsUser() = %v, want %v", gotExists, tt.wantExists) + } + }) + } +} diff --git a/internal/command/v2/org_test.go b/internal/command/v2/org_test.go new file mode 100644 index 0000000000..b4b5b64478 --- /dev/null +++ b/internal/command/v2/org_test.go @@ -0,0 +1,57 @@ +package command + +import ( + "context" + "testing" + + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/org" +) + +func TestAddOrg(t *testing.T) { + type args struct { + a *org.Aggregate + name string + } + + ctx := context.Background() + agg := org.NewAggregate("test", "test") + + tests := []struct { + name string + args args + want Want + }{ + { + name: "invalid domain", + args: args{ + a: agg, + name: "", + }, + want: Want{ + ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-mruNY", "Errors.Invalid.Argument"), + }, + }, + { + name: "correct", + args: args{ + a: agg, + name: "caos ag", + }, + want: Want{ + Commands: []eventstore.Command{ + org.NewOrgAddedEvent(ctx, &agg.Aggregate, "caos ag"), + org.NewDomainAddedEvent(ctx, &agg.Aggregate, "caos-ag.localhost"), + org.NewDomainVerifiedEvent(ctx, &agg.Aggregate, "caos-ag.localhost"), + org.NewDomainPrimarySetEvent(ctx, &agg.Aggregate, "caos-ag.localhost"), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + AssertValidation(t, AddOrg(tt.args.a, tt.args.name, "localhost"), nil, tt.want) + }) + } +} diff --git a/internal/command/v2/preparation/command.go b/internal/command/v2/preparation/command.go new file mode 100644 index 0000000000..7ec0570228 --- /dev/null +++ b/internal/command/v2/preparation/command.go @@ -0,0 +1,80 @@ +package preparation + +import ( + "context" + + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" +) + +// Validation of the input values of the command and if correct returns +// the function to create commands or if not valid an error +type Validation func() (CreateCommands, error) + +// CreateCommands builds the commands +// the filter param is an extended version of the eventstore filter method +// it filters for events including the commands on the current context +type CreateCommands func(context.Context, FilterToQueryReducer) ([]eventstore.Command, error) + +// FilterToQueryReducer is an abstraction of the eventstore method +type FilterToQueryReducer func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) + +var ( + //ErrNotExecutable is thrown if no command creator was created + ErrNotExecutable = errors.ThrowInvalidArgument(nil, "PREPA-pH70n", "Errors.Internal") +) + +// PrepareCommands checks the passed validations and if ok creates the commands +func PrepareCommands(ctx context.Context, filter FilterToQueryReducer, validations ...Validation) (cmds []eventstore.Command, err error) { + commanders, err := validate(validations) + if err != nil { + return nil, err + } + return create(ctx, filter, commanders) +} + +func validate(validations []Validation) ([]CreateCommands, error) { + creators := make([]CreateCommands, 0, len(validations)) + + for _, validate := range validations { + cmds, err := validate() + if err != nil { + return nil, err + } + creators = append(creators, cmds) + } + + if len(creators) == 0 { + return nil, ErrNotExecutable + } + return creators, nil +} + +func create(ctx context.Context, filter FilterToQueryReducer, commanders []CreateCommands) (cmds []eventstore.Command, err error) { + for _, command := range commanders { + cmd, err := command(ctx, transactionFilter(filter, cmds)) + if err != nil { + return nil, err + } + cmds = append(cmds, cmd...) + } + + return cmds, nil +} + +func transactionFilter(filter FilterToQueryReducer, commands []eventstore.Command) FilterToQueryReducer { + return func(ctx context.Context, query *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + events, err := filter(ctx, query) + if err != nil { + return nil, err + } + for _, command := range commands { + event := command.(eventstore.Event) + if !query.Matches(event, len(events)) { + continue + } + events = append(events, event) + } + return events, nil + } +} diff --git a/internal/command/v2/preparation/command_test.go b/internal/command/v2/preparation/command_test.go new file mode 100644 index 0000000000..ef87f3ab4e --- /dev/null +++ b/internal/command/v2/preparation/command_test.go @@ -0,0 +1,176 @@ +package preparation + +import ( + "context" + "errors" + "reflect" + "testing" + + "github.com/caos/zitadel/internal/eventstore" +) + +var errTest = errors.New("test") + +func Test_validate(t *testing.T) { + type args struct { + validations []Validation + } + type want struct { + len int + err error + } + tests := []struct { + name string + args args + want want + }{ + { + name: "no validations", + args: args{}, + want: want{ + err: ErrNotExecutable, + }, + }, + { + name: "error in validation", + args: args{ + validations: []Validation{ + func() (CreateCommands, error) { + return nil, errTest + }, + }, + }, + want: want{ + err: errTest, + }, + }, + { + name: "correct", + args: args{ + validations: []Validation{ + func() (CreateCommands, error) { + return func(_ context.Context, _ FilterToQueryReducer) ([]eventstore.Command, error) { + return nil, nil + }, nil + }, + }, + }, + want: want{ + len: 1, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := validate(tt.args.validations) + if !errors.Is(err, tt.want.err) { + t.Errorf("validate() error = %v, wantErr %v", err, tt.want.err) + return + } + if len(got) != tt.want.len { + t.Errorf("validate() len = %v, want %v", len(got), tt.want.len) + } + }) + } +} + +func Test_create(t *testing.T) { + type args struct { + commanders []CreateCommands + } + type want struct { + err error + len int + } + tests := []struct { + name string + args args + want want + }{ + { + name: "error in command", + want: want{ + err: errTest, + }, + args: args{ + commanders: []CreateCommands{ + func(_ context.Context, _ FilterToQueryReducer) ([]eventstore.Command, error) { + return nil, errTest + }, + }, + }, + }, + { + name: "no commands", + want: want{}, + args: args{ + commanders: []CreateCommands{ + func(_ context.Context, _ FilterToQueryReducer) ([]eventstore.Command, error) { + return nil, nil + }, + }, + }, + }, + { + name: "multiple commands", + want: want{ + len: 3, + }, + args: args{ + commanders: []CreateCommands{ + func(_ context.Context, _ FilterToQueryReducer) ([]eventstore.Command, error) { + return []eventstore.Command{new(testCommand), new(testCommand)}, nil + }, + func(_ context.Context, _ FilterToQueryReducer) ([]eventstore.Command, error) { + return []eventstore.Command{new(testCommand)}, nil + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotCmds, err := create(context.Background(), nil, tt.args.commanders) + if !errors.Is(err, tt.want.err) { + t.Errorf("create() error = %v, wantErr %v", err, tt.want.err) + return + } + if len(gotCmds) != tt.want.len { + t.Errorf("create() len = %d, want %d", len(gotCmds), tt.want.len) + } + }) + } +} + +func Test_transactionFilter(t *testing.T) { + type args struct { + filter FilterToQueryReducer + commands []eventstore.Command + } + tests := []struct { + name string + args args + want FilterToQueryReducer + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := transactionFilter(tt.args.filter, tt.args.commands); !reflect.DeepEqual(got, tt.want) { + t.Errorf("transactionFilter() = %v, want %v", got, tt.want) + } + }) + } +} + +type testCommand struct { + eventstore.BaseEvent +} + +func (c *testCommand) Data() interface{} { + return nil +} + +func (c *testCommand) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} diff --git a/internal/command/v2/preparation_test.go b/internal/command/v2/preparation_test.go new file mode 100644 index 0000000000..5860ec25e0 --- /dev/null +++ b/internal/command/v2/preparation_test.go @@ -0,0 +1,81 @@ +// this is a helper file for tests +package command + +import ( + "context" + "errors" + "reflect" + "testing" + + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/eventstore" +) + +//Want represents the expected values for each step +type Want struct { + ValidationErr error + CreateErr error + Commands []eventstore.Command +} + +//AssertValidation checks if the validation works as inteded +func AssertValidation(t *testing.T, validation preparation.Validation, filter preparation.FilterToQueryReducer, want Want) { + t.Helper() + + creates, err := validation() + if !errors.Is(err, want.ValidationErr) { + t.Errorf("wrong validation err = %v, want %v", err, want.ValidationErr) + return + } + if err != nil { + return + } + cmds, err := creates(context.Background(), filter) + if !errors.Is(err, want.CreateErr) { + t.Errorf("wrong create err = %v, want %v", err, want.CreateErr) + return + } + if err != nil { + return + } + + if len(cmds) != len(want.Commands) { + t.Errorf("wrong length of commands = %v, want %v", eventTypes(cmds), eventTypes(want.Commands)) + return + } + + for i, cmd := range want.Commands { + if !reflect.DeepEqual(cmd, cmds[i]) { + t.Errorf("unexpected command: = %v, want %v", cmds[i], cmd) + } + } +} + +func eventTypes(cmds []eventstore.Command) []eventstore.EventType { + types := make([]eventstore.EventType, len(cmds)) + for i, cmd := range cmds { + types[i] = cmd.Type() + } + return types +} + +type MultiFilter struct { + count int + filters []preparation.FilterToQueryReducer +} + +func NewMultiFilter() *MultiFilter { + return new(MultiFilter) +} + +func (mf *MultiFilter) Append(filter preparation.FilterToQueryReducer) *MultiFilter { + mf.filters = append(mf.filters, filter) + return mf +} + +func (mf *MultiFilter) Filter() preparation.FilterToQueryReducer { + return func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + mf.count++ + return mf.filters[mf.count-1](ctx, queryFactory) + } +} diff --git a/internal/command/v2/project.go b/internal/command/v2/project.go new file mode 100644 index 0000000000..2e95af474c --- /dev/null +++ b/internal/command/v2/project.go @@ -0,0 +1,75 @@ +package command + +import ( + "context" + "strings" + + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/project" +) + +func AddProject( + a *project.Aggregate, + name string, + owner string, + projectRoleAssertion bool, + projectRoleCheck bool, + hasProjectCheck bool, + privateLabelingSetting domain.PrivateLabelingSetting, +) preparation.Validation { + return func() (preparation.CreateCommands, error) { + if name = strings.TrimSpace(name); name == "" { + return nil, errors.ThrowInvalidArgument(nil, "PROJE-C01yo", "Errors.Invalid.Argument") + } + if !privateLabelingSetting.Valid() { + return nil, errors.ThrowInvalidArgument(nil, "PROJE-AO52V", "Errors.Invalid.Argument") + } + if owner == "" { + return nil, errors.ThrowPreconditionFailed(nil, "PROJE-hzxwo", "Errors.Invalid.Argument") + } + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + return []eventstore.Command{ + project.NewProjectAddedEvent(ctx, &a.Aggregate, + name, + projectRoleAssertion, + projectRoleCheck, + hasProjectCheck, + privateLabelingSetting, + ), + project.NewProjectMemberAddedEvent(ctx, &a.Aggregate, + owner, + domain.RoleProjectOwner), + }, nil + }, nil + } +} + +func ExistsProject(ctx context.Context, filter preparation.FilterToQueryReducer, projectID, resourceOwner string) (exists bool, err error) { + events, err := filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + ResourceOwner(resourceOwner). + OrderAsc(). + AddQuery(). + AggregateTypes(project.AggregateType). + AggregateIDs(projectID). + EventTypes( + project.ProjectAddedType, + project.ProjectRemovedType, + ).Builder()) + if err != nil { + return false, err + } + + for _, event := range events { + switch event.(type) { + case *project.ProjectAddedEvent: + exists = true + case *project.ProjectRemovedEvent: + exists = false + } + } + + return exists, nil +} diff --git a/internal/command/v2/project_app.go b/internal/command/v2/project_app.go new file mode 100644 index 0000000000..050485b3f5 --- /dev/null +++ b/internal/command/v2/project_app.go @@ -0,0 +1,155 @@ +package command + +import ( + "context" + "strings" + "time" + + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/crypto" + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/project" +) + +func AddOIDCApp( + a project.Aggregate, + version domain.OIDCVersion, + appID, + name, + clientID string, + clientSecret *crypto.CryptoValue, + redirectUris []string, + responseTypes []domain.OIDCResponseType, + grantTypes []domain.OIDCGrantType, + applicationType domain.OIDCApplicationType, + authMethodType domain.OIDCAuthMethodType, + postLogoutRedirectUris []string, + devMode bool, + accessTokenType domain.OIDCTokenType, + accessTokenRoleAssertion bool, + idTokenRoleAssertion bool, + idTokenUserinfoAssertion bool, + clockSkew time.Duration, + additionalOrigins []string, +) preparation.Validation { + return func() (preparation.CreateCommands, error) { + if appID == "" { + return nil, errors.ThrowInvalidArgument(nil, "PROJE-NnavI", "Errors.Invalid.Argument") + } + if name = strings.TrimSpace(name); name == "" { + return nil, errors.ThrowInvalidArgument(nil, "PROJE-Fef31", "Errors.Invalid.Argument") + } + if clientID == "" { + return nil, errors.ThrowInvalidArgument(nil, "PROJE-ghTsJ", "Errors.Invalid.Argument") + } + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + if exists, err := ExistsProject(ctx, filter, a.ID, a.ResourceOwner); !exists || err != nil { + return nil, errors.ThrowNotFound(err, "PROJE-5LQ0U", "Errors.Project.NotFound") + } + return []eventstore.Command{ + project.NewApplicationAddedEvent( + ctx, + &a.Aggregate, + appID, + name, + ), + project.NewOIDCConfigAddedEvent( + ctx, + &a.Aggregate, + version, + appID, + clientID, + clientSecret, + redirectUris, + responseTypes, + grantTypes, + applicationType, + authMethodType, + postLogoutRedirectUris, + devMode, + accessTokenType, + accessTokenRoleAssertion, + idTokenRoleAssertion, + idTokenUserinfoAssertion, + clockSkew, + additionalOrigins, + ), + }, nil + }, nil + } +} + +func AddAPIApp( + a project.Aggregate, + appID, + name, + clientID string, + clientSecret *crypto.CryptoValue, + authMethodType domain.APIAuthMethodType, +) preparation.Validation { + return func() (preparation.CreateCommands, error) { + if appID == "" { + return nil, errors.ThrowInvalidArgument(nil, "PROJE-XHsKt", "Errors.Invalid.Argument") + } + if name = strings.TrimSpace(name); name == "" { + return nil, errors.ThrowInvalidArgument(nil, "PROJE-F7g21", "Errors.Invalid.Argument") + } + if clientID == "" { + return nil, errors.ThrowInvalidArgument(nil, "PROJE-XXED5", "Errors.Invalid.Argument") + } + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + if exists, err := ExistsProject(ctx, filter, a.ID, a.ResourceOwner); !exists || err != nil { + return nil, errors.ThrowNotFound(err, "PROJE-Sf2gb", "Errors.Project.NotFound") + } + return []eventstore.Command{ + project.NewApplicationAddedEvent( + ctx, + &a.Aggregate, + appID, + name, + ), + project.NewAPIConfigAddedEvent( + ctx, + &a.Aggregate, + appID, + clientID, + clientSecret, + authMethodType, + ), + }, nil + }, nil + } +} + +func ExistsApp(ctx context.Context, filter preparation.FilterToQueryReducer, projectID, appID, resourceOwner string) (exists bool, err error) { + events, err := filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + ResourceOwner(resourceOwner). + OrderAsc(). + AddQuery(). + AggregateTypes(project.AggregateType). + AggregateIDs(projectID). + EventTypes( + project.ApplicationAddedType, + project.ApplicationRemovedType, + ).Builder()) + if err != nil { + return false, err + } + + for _, event := range events { + switch e := event.(type) { + case *project.ApplicationAddedEvent: + if e.AppID == appID { + exists = true + } + case *project.ApplicationRemovedEvent: + if e.AppID == appID { + exists = false + } + } + } + + return exists, nil +} diff --git a/internal/command/v2/project_app_test.go b/internal/command/v2/project_app_test.go new file mode 100644 index 0000000000..dff26631c4 --- /dev/null +++ b/internal/command/v2/project_app_test.go @@ -0,0 +1,386 @@ +package command + +import ( + "context" + "testing" + + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/project" +) + +func TestAddOIDCApp(t *testing.T) { + type args struct { + a *project.Aggregate + appID string + name string + clientID string + filter preparation.FilterToQueryReducer + } + + ctx := context.Background() + agg := project.NewAggregate("test", "test") + + tests := []struct { + name string + args args + want Want + }{ + { + name: "invalid appID", + args: args{ + a: agg, + appID: "", + name: "name", + clientID: "clientID", + }, + want: Want{ + ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-NnavI", "Errors.Invalid.Argument"), + }, + }, + { + name: "invalid name", + args: args{ + a: agg, + appID: "appID", + name: "", + clientID: "clientID", + }, + want: Want{ + ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-Fef31", "Errors.Invalid.Argument"), + }, + }, + { + name: "invalid clientID", + args: args{ + a: agg, + appID: "appID", + name: "name", + clientID: "", + }, + want: Want{ + ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-ghTsJ", "Errors.Invalid.Argument"), + }, + }, + { + name: "project not exists", + args: args{ + a: agg, + appID: "id", + name: "name", + clientID: "clientID", + filter: NewMultiFilter(). + Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return nil, nil + }). + Filter(), + }, + want: Want{ + CreateErr: errors.ThrowNotFound(nil, "PROJE-5LQ0U", "Errors.Project.NotFound"), + }, + }, + { + name: "correct", + args: args{ + a: agg, + appID: "appID", + name: "name", + clientID: "clientID", + filter: NewMultiFilter(). + Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + project.NewProjectAddedEvent( + ctx, + &agg.Aggregate, + "project", + false, + false, + false, + domain.PrivateLabelingSettingUnspecified, + ), + }, nil + }). + Filter(), + }, + want: Want{ + Commands: []eventstore.Command{ + project.NewApplicationAddedEvent(ctx, &agg.Aggregate, + "appID", + "name", + ), + project.NewOIDCConfigAddedEvent(ctx, &agg.Aggregate, + domain.OIDCVersionV1, + "appID", + "clientID", + nil, + nil, + nil, + nil, + domain.OIDCApplicationTypeWeb, + domain.OIDCAuthMethodTypeBasic, + nil, + false, + domain.OIDCTokenTypeBearer, + false, + false, + false, + 0, + nil, + ), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + AssertValidation(t, + AddOIDCApp(*tt.args.a, + domain.OIDCVersionV1, + tt.args.appID, + tt.args.name, + tt.args.clientID, + nil, + nil, + nil, + nil, + domain.OIDCApplicationTypeWeb, + domain.OIDCAuthMethodTypeBasic, + nil, + false, + domain.OIDCTokenTypeBearer, + false, + false, + false, + 0, + nil, + ), tt.args.filter, tt.want) + }) + } +} + +func TestAddAPIConfig(t *testing.T) { + type args struct { + a *project.Aggregate + appID string + name string + clientID string + filter preparation.FilterToQueryReducer + } + + ctx := context.Background() + agg := project.NewAggregate("test", "test") + + tests := []struct { + name string + args args + want Want + }{ + { + name: "invalid appID", + args: args{ + a: agg, + appID: "", + name: "name", + clientID: "clientID", + }, + want: Want{ + ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-XHsKt", "Errors.Invalid.Argument"), + }, + }, + { + name: "invalid name", + args: args{ + a: agg, + appID: "appID", + name: "", + clientID: "clientID", + }, + want: Want{ + ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-F7g21", "Errors.Invalid.Argument"), + }, + }, + { + name: "invalid clientID", + args: args{ + a: agg, + appID: "appID", + name: "name", + clientID: "", + }, + want: Want{ + ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-XXED5", "Errors.Invalid.Argument"), + }, + }, + { + name: "project not exists", + args: args{ + a: agg, + appID: "id", + name: "name", + clientID: "clientID", + filter: NewMultiFilter(). + Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return nil, nil + }). + Filter(), + }, + want: Want{ + CreateErr: errors.ThrowNotFound(nil, "PROJE-Sf2gb", "Errors.Project.NotFound"), + }, + }, + { + name: "correct", + args: args{ + a: agg, + appID: "appID", + name: "name", + clientID: "clientID", + filter: NewMultiFilter(). + Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + project.NewProjectAddedEvent( + ctx, + &agg.Aggregate, + "project", + false, + false, + false, + domain.PrivateLabelingSettingUnspecified, + ), + }, nil + }). + Filter(), + }, + want: Want{ + Commands: []eventstore.Command{ + project.NewApplicationAddedEvent( + ctx, + &agg.Aggregate, + "appID", + "name", + ), + project.NewAPIConfigAddedEvent(ctx, &agg.Aggregate, + "appID", + "clientID", + nil, + domain.APIAuthMethodTypeBasic, + ), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + AssertValidation(t, + AddAPIApp(*tt.args.a, + tt.args.appID, + tt.args.name, + tt.args.clientID, + nil, + domain.APIAuthMethodTypeBasic, + ), tt.args.filter, tt.want) + }) + } +} + +func TestExistsApp(t *testing.T) { + type args struct { + filter preparation.FilterToQueryReducer + appID string + projectID string + resourceOwner string + } + tests := []struct { + name string + args args + wantExists bool + wantErr bool + }{ + { + name: "no events", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{}, nil + }, + appID: "appID", + projectID: "projectID", + resourceOwner: "ro", + }, + wantExists: false, + wantErr: false, + }, + { + name: "app added", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + project.NewApplicationAddedEvent( + context.Background(), + &project.NewAggregate("id", "ro").Aggregate, + "appID", + "name", + ), + }, nil + }, + appID: "appID", + projectID: "projectID", + resourceOwner: "ro", + }, + wantExists: true, + wantErr: false, + }, + { + name: "app removed", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + project.NewApplicationAddedEvent( + context.Background(), + &project.NewAggregate("id", "ro").Aggregate, + "appID", + "name", + ), + project.NewApplicationRemovedEvent( + context.Background(), + &project.NewAggregate("id", "ro").Aggregate, + "appID", + "name", + ), + }, nil + }, + appID: "appID", + projectID: "projectID", + resourceOwner: "ro", + }, + wantExists: false, + wantErr: false, + }, + { + name: "error durring filter", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return nil, errors.ThrowInternal(nil, "PROJE-Op26p", "Errors.Internal") + }, + appID: "appID", + projectID: "projectID", + resourceOwner: "ro", + }, + wantExists: false, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotExists, err := ExistsApp(context.Background(), tt.args.filter, tt.args.projectID, tt.args.appID, tt.args.resourceOwner) + if (err != nil) != tt.wantErr { + t.Errorf("ExistsUser() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotExists != tt.wantExists { + t.Errorf("ExistsUser() = %v, want %v", gotExists, tt.wantExists) + } + }) + } +} diff --git a/internal/command/v2/project_test.go b/internal/command/v2/project_test.go new file mode 100644 index 0000000000..8e5fc81a8b --- /dev/null +++ b/internal/command/v2/project_test.go @@ -0,0 +1,195 @@ +package command + +import ( + "context" + "testing" + + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/project" +) + +func TestAddProject(t *testing.T) { + type args struct { + a *project.Aggregate + name string + owner string + privateLabelingSetting domain.PrivateLabelingSetting + } + + ctx := context.Background() + agg := project.NewAggregate("test", "test") + + tests := []struct { + name string + args args + want Want + }{ + { + name: "invalid name", + args: args{ + a: agg, + name: "", + owner: "owner", + privateLabelingSetting: domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy, + }, + want: Want{ + ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-C01yo", "Errors.Invalid.Argument"), + }, + }, + { + name: "invalid private labeling setting", + args: args{ + a: agg, + name: "name", + owner: "owner", + privateLabelingSetting: -1, + }, + want: Want{ + ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-AO52V", "Errors.Invalid.Argument"), + }, + }, + { + name: "invalid owner", + args: args{ + a: agg, + name: "name", + owner: "", + privateLabelingSetting: domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy, + }, + want: Want{ + ValidationErr: errors.ThrowPreconditionFailed(nil, "PROJE-hzxwo", "Errors.Invalid.Argument"), + }, + }, + { + name: "correct", + args: args{ + a: agg, + name: "ZITADEL", + owner: "CAOS AG", + privateLabelingSetting: domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy, + }, + want: Want{ + Commands: []eventstore.Command{ + project.NewProjectAddedEvent(ctx, &agg.Aggregate, + "ZITADEL", + false, + false, + false, + domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy, + ), + project.NewProjectMemberAddedEvent(ctx, &agg.Aggregate, + "CAOS AG", + domain.RoleProjectOwner), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + AssertValidation(t, AddProject(tt.args.a, tt.args.name, tt.args.owner, false, false, false, tt.args.privateLabelingSetting), nil, tt.want) + }) + } +} + +func TestExistsProject(t *testing.T) { + type args struct { + filter preparation.FilterToQueryReducer + id string + resourceOwner string + } + tests := []struct { + name string + args args + wantExists bool + wantErr bool + }{ + { + name: "no events", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{}, nil + }, + id: "id", + resourceOwner: "ro", + }, + wantExists: false, + wantErr: false, + }, + { + name: "project added", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + project.NewProjectAddedEvent( + context.Background(), + &project.NewAggregate("id", "ro").Aggregate, + "name", + false, + false, + false, + domain.PrivateLabelingSettingEnforceProjectResourceOwnerPolicy, + ), + }, nil + }, + id: "id", + resourceOwner: "ro", + }, + wantExists: true, + wantErr: false, + }, + { + name: "project removed", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + project.NewProjectAddedEvent( + context.Background(), + &project.NewAggregate("id", "ro").Aggregate, + "name", + false, + false, + false, + domain.PrivateLabelingSettingEnforceProjectResourceOwnerPolicy, + ), + project.NewProjectRemovedEvent( + context.Background(), + &project.NewAggregate("id", "ro").Aggregate, + "name", + ), + }, nil + }, + id: "id", + resourceOwner: "ro", + }, + wantExists: false, + wantErr: false, + }, + { + name: "error durring filter", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return nil, errors.ThrowInternal(nil, "PROJE-Op26p", "Errors.Internal") + }, + id: "id", + resourceOwner: "ro", + }, + wantExists: false, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotExists, err := ExistsProject(context.Background(), tt.args.filter, tt.args.id, tt.args.resourceOwner) + if (err != nil) != tt.wantErr { + t.Errorf("ExistsUser() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotExists != tt.wantExists { + t.Errorf("ExistsUser() = %v, want %v", gotExists, tt.wantExists) + } + }) + } +} diff --git a/internal/command/v2/user.go b/internal/command/v2/user.go new file mode 100644 index 0000000000..a24cca54fb --- /dev/null +++ b/internal/command/v2/user.go @@ -0,0 +1,40 @@ +package command + +import ( + "context" + + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/user" +) + +func ExistsUser(ctx context.Context, filter preparation.FilterToQueryReducer, id, resourceOwner string) (exists bool, err error) { + events, err := filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + ResourceOwner(resourceOwner). + OrderAsc(). + AddQuery(). + AggregateTypes(user.AggregateType). + AggregateIDs(id). + EventTypes( + user.HumanRegisteredType, + user.UserV1RegisteredType, + user.HumanAddedType, + user.UserV1AddedType, + user.MachineAddedEventType, + user.UserRemovedType, + ).Builder()) + if err != nil { + return false, err + } + + for _, event := range events { + switch event.(type) { + case *user.HumanRegisteredEvent, *user.HumanAddedEvent, *user.MachineAddedEvent: + exists = true + case *user.UserRemovedEvent: + exists = false + } + } + + return exists, nil +} diff --git a/internal/command/v2/user_domain_policy.go b/internal/command/v2/user_domain_policy.go new file mode 100644 index 0000000000..d93a059edf --- /dev/null +++ b/internal/command/v2/user_domain_policy.go @@ -0,0 +1,50 @@ +package command + +import ( + "context" + + "github.com/caos/zitadel/internal/api/authz" + "github.com/caos/zitadel/internal/command" + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/errors" +) + +func domainPolicyWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PolicyDomainWriteModel, error) { + wm, err := orgDomainPolicy(ctx, filter) + if err != nil || wm != nil && wm.State.Exists() { + return wm, err + } + wm, err = instanceDomainPolicy(ctx, filter) + if err != nil || wm != nil { + return wm, err + } + return nil, errors.ThrowInternal(nil, "USER-Ggk9n", "Errors.Internal") +} + +func orgDomainPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PolicyDomainWriteModel, error) { + policy := command.NewOrgDomainPolicyWriteModel(authz.GetCtxData(ctx).OrgID) + events, err := filter(ctx, policy.Query()) + if err != nil { + return nil, err + } + if len(events) == 0 { + return nil, nil + } + policy.AppendEvents(events...) + err = policy.Reduce() + return &policy.PolicyDomainWriteModel, err +} + +func instanceDomainPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PolicyDomainWriteModel, error) { + policy := command.NewInstanceDomainPolicyWriteModel() + events, err := filter(ctx, policy.Query()) + if err != nil { + return nil, err + } + if len(events) == 0 { + return nil, nil + } + policy.AppendEvents(events...) + err = policy.Reduce() + return &policy.PolicyDomainWriteModel, err +} diff --git a/internal/command/v2/user_domain_policy_test.go b/internal/command/v2/user_domain_policy_test.go new file mode 100644 index 0000000000..d3a75cb0c5 --- /dev/null +++ b/internal/command/v2/user_domain_policy_test.go @@ -0,0 +1,266 @@ +package command + +import ( + "context" + "reflect" + "testing" + + "github.com/caos/zitadel/internal/command" + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/instance" + "github.com/caos/zitadel/internal/repository/org" +) + +func Test_customDomainPolicy(t *testing.T) { + type args struct { + filter preparation.FilterToQueryReducer + } + tests := []struct { + name string + args args + want *command.PolicyDomainWriteModel + wantErr bool + }{ + { + name: "err from filter", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return nil, errors.ThrowInternal(nil, "USER-IgYlN", "Errors.Internal") + }, + }, + want: nil, + wantErr: true, + }, + { + name: "no events", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{}, nil + }, + }, + want: nil, + wantErr: false, + }, + { + name: "policy found", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + org.NewDomainPolicyAddedEvent( + context.Background(), + &org.NewAggregate("id", "ro").Aggregate, + true, + ), + }, nil + }, + }, + want: &command.PolicyDomainWriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: "id", + ResourceOwner: "ro", + Events: []eventstore.Event{}, + }, + UserLoginMustBeDomain: true, + State: domain.PolicyStateActive, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := orgDomainPolicy(context.Background(), tt.args.filter) + if (err != nil) != tt.wantErr { + t.Errorf("customDomainPolicy() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("customDomainPolicy() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_defaultDomainPolicy(t *testing.T) { + type args struct { + filter preparation.FilterToQueryReducer + } + tests := []struct { + name string + args args + want *command.PolicyDomainWriteModel + wantErr bool + }{ + { + name: "err from filter", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return nil, errors.ThrowInternal(nil, "USER-IgYlN", "Errors.Internal") + }, + }, + want: nil, + wantErr: true, + }, + { + name: "no events", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{}, nil + }, + }, + want: nil, + wantErr: false, + }, + { + name: "policy found", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + instance.NewDomainPolicyAddedEvent( + context.Background(), + &instance.NewAggregate().Aggregate, + true, + ), + }, nil + }, + }, + want: &command.PolicyDomainWriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: "IAM", + ResourceOwner: "IAM", + Events: []eventstore.Event{}, + }, + UserLoginMustBeDomain: true, + State: domain.PolicyStateActive, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := instanceDomainPolicy(context.Background(), tt.args.filter) + if (err != nil) != tt.wantErr { + t.Errorf("defaultDomainPolicy() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("defaultDomainPolicy() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_DomainPolicy(t *testing.T) { + type args struct { + filter preparation.FilterToQueryReducer + } + tests := []struct { + name string + args args + want *command.PolicyDomainWriteModel + wantErr bool + }{ + { + name: "err from filter custom", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return nil, errors.ThrowInternal(nil, "USER-IgYlN", "Errors.Internal") + }, + }, + want: nil, + wantErr: true, + }, + { + name: "custom found", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + org.NewDomainPolicyAddedEvent( + context.Background(), + &org.NewAggregate("id", "ro").Aggregate, + true, + ), + }, nil + }, + }, + want: &command.PolicyDomainWriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: "id", + ResourceOwner: "ro", + Events: []eventstore.Event{}, + }, + UserLoginMustBeDomain: true, + State: domain.PolicyStateActive, + }, + wantErr: false, + }, + { + name: "err from filter default", + args: args{ + filter: NewMultiFilter(). + Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return nil, nil + }). + Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return nil, errors.ThrowInternal(nil, "USER-6HnsD", "Errors.Internal") + }). + Filter(), + }, + want: nil, + wantErr: true, + }, + { + name: "default found", + args: args{ + filter: NewMultiFilter(). + Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return nil, nil + }). + Append(func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + instance.NewDomainPolicyAddedEvent( + context.Background(), + &instance.NewAggregate().Aggregate, + true, + ), + }, nil + }). + Filter(), + }, + want: &command.PolicyDomainWriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: "IAM", + ResourceOwner: "IAM", + Events: []eventstore.Event{}, + }, + UserLoginMustBeDomain: true, + State: domain.PolicyStateActive, + }, + wantErr: false, + }, + { + name: "no policy found", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return nil, nil + }, + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := domainPolicyWriteModel(context.Background(), tt.args.filter) + if (err != nil) != tt.wantErr { + t.Errorf("defaultDomainPolicy() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("defaultDomainPolicy() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/command/v2/user_human.go b/internal/command/v2/user_human.go new file mode 100644 index 0000000000..1907647ddc --- /dev/null +++ b/internal/command/v2/user_human.go @@ -0,0 +1,97 @@ +package command + +import ( + "context" + "strings" + + "golang.org/x/text/language" + + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/crypto" + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/user" +) + +type AddHuman struct { + // Username is required + Username string + // FirstName is required + FirstName string + // LastName is required + LastName string + // NickName is required + NickName string + // DisplayName is required + DisplayName string + // Email is required + Email string + // PreferredLang is required + PreferredLang language.Tag + // Gender is required + Gender domain.Gender + //TODO: can it also be verified? + Phone string + //Password is optional + Password string + //PasswordChangeRequired is used if the `Password`-field is set + PasswordChangeRequired bool +} + +func AddHumanCommand(a *user.Aggregate, human *AddHuman, passwordAlg crypto.HashAlgorithm) preparation.Validation { + return func() (preparation.CreateCommands, error) { + if !domain.EmailRegex.MatchString(human.Email) { + return nil, errors.ThrowInvalidArgument(nil, "USER-Ec7dM", "Errors.Invalid.Argument") + } + if human.FirstName = strings.TrimSpace(human.FirstName); human.FirstName == "" { + return nil, errors.ThrowInvalidArgument(nil, "USER-UCej2", "Errors.Invalid.Argument") + } + if human.LastName = strings.TrimSpace(human.LastName); human.LastName == "" { + return nil, errors.ThrowInvalidArgument(nil, "USER-DiAq8", "Errors.Invalid.Argument") + } + human.Phone = strings.TrimSpace(human.Phone) + + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + domainPolicy, err := domainPolicyWriteModel(ctx, filter) + if err != nil { + return nil, err + } + + cmd := user.NewHumanAddedEvent( + ctx, + &a.Aggregate, + human.Username, + human.FirstName, + human.LastName, + human.NickName, + human.DisplayName, + human.PreferredLang, + human.Gender, + human.Email, //TODO: pass if verified + domainPolicy.UserLoginMustBeDomain, + ) + if human.Phone != "" { + cmd.AddPhoneData(human.Phone) //TODO: pass if verified + } + if human.Password != "" { + passwordComplexity, err := passwordComplexityPolicyWriteModel(ctx, filter) + if err != nil { + return nil, err + } + + if err = passwordComplexity.Validate(human.Password); err != nil { + return nil, err + } + + secret, err := crypto.Hash([]byte(human.Password), passwordAlg) + if err != nil { + return nil, err + } + cmd.AddPasswordData(secret, human.PasswordChangeRequired) + } + + return []eventstore.Command{cmd}, nil + }, nil + } +} diff --git a/internal/command/v2/user_human_test.go b/internal/command/v2/user_human_test.go new file mode 100644 index 0000000000..ee82f3eae9 --- /dev/null +++ b/internal/command/v2/user_human_test.go @@ -0,0 +1,169 @@ +package command + +import ( + "context" + "testing" + + "github.com/golang/mock/gomock" + "golang.org/x/text/language" + + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/crypto" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/org" + "github.com/caos/zitadel/internal/repository/user" +) + +func TestAddHumanCommand(t *testing.T) { + type args struct { + a *user.Aggregate + human *AddHuman + passwordAlg crypto.HashAlgorithm + filter preparation.FilterToQueryReducer + } + agg := user.NewAggregate("id", "ro") + tests := []struct { + name string + args args + want Want + }{ + { + name: "invalid email", + args: args{ + a: agg, + human: &AddHuman{ + Email: "invalid", + }, + }, + want: Want{ + ValidationErr: errors.ThrowInvalidArgument(nil, "USER-Ec7dM", "Errors.Invalid.Argument"), + }, + }, + { + name: "invalid first name", + args: args{ + a: agg, + human: &AddHuman{ + Email: "support@zitadel.ch", + }, + }, + want: Want{ + ValidationErr: errors.ThrowInvalidArgument(nil, "USER-UCej2", "Errors.Invalid.Argument"), + }, + }, + { + name: "invalid last name", + args: args{ + a: agg, + human: &AddHuman{ + Email: "support@zitadel.ch", + FirstName: "hurst", + }, + }, + want: Want{ + ValidationErr: errors.ThrowInvalidArgument(nil, "USER-DiAq8", "Errors.Invalid.Argument"), + }, + }, + { + name: "invalid password", + args: args{ + a: agg, + human: &AddHuman{ + Email: "support@zitadel.ch", + FirstName: "gigi", + LastName: "giraffe", + Password: "short", + }, + filter: NewMultiFilter().Append( + func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + org.NewDomainPolicyAddedEvent( + context.Background(), + &org.NewAggregate("id", "ro").Aggregate, + true, + ), + }, nil + }). + Append( + func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + org.NewPasswordComplexityPolicyAddedEvent( + context.Background(), + &org.NewAggregate("id", "ro").Aggregate, + 8, + true, + true, + true, + true, + ), + }, nil + }). + Filter(), + }, + want: Want{ + CreateErr: errors.ThrowInvalidArgument(nil, "COMMA-HuJf6", "Errors.User.PasswordComplexityPolicy.MinLength"), + }, + }, + { + name: "correct", + args: args{ + a: agg, + human: &AddHuman{ + Email: "support@zitadel.ch", + FirstName: "gigi", + LastName: "giraffe", + Password: "", + }, + passwordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)), + filter: NewMultiFilter().Append( + func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + org.NewDomainPolicyAddedEvent( + context.Background(), + &org.NewAggregate("id", "ro").Aggregate, + true, + ), + }, nil + }). + Append( + func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + org.NewPasswordComplexityPolicyAddedEvent( + context.Background(), + &org.NewAggregate("id", "ro").Aggregate, + 2, + false, + false, + false, + false, + ), + }, nil + }). + Filter(), + }, + want: Want{ + Commands: []eventstore.Command{ + user.NewHumanAddedEvent( + context.Background(), + &agg.Aggregate, + "", + "gigi", + "giraffe", + "", + "", + language.Und, + 0, + "support@zitadel.ch", + true, + ), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + AssertValidation(t, AddHumanCommand(tt.args.a, tt.args.human, tt.args.passwordAlg), tt.args.filter, tt.want) + }) + } +} diff --git a/internal/command/v2/user_password_complexity_policy.go b/internal/command/v2/user_password_complexity_policy.go new file mode 100644 index 0000000000..85c037833e --- /dev/null +++ b/internal/command/v2/user_password_complexity_policy.go @@ -0,0 +1,50 @@ +package command + +import ( + "context" + + "github.com/caos/zitadel/internal/api/authz" + "github.com/caos/zitadel/internal/command" + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/errors" +) + +func passwordComplexityPolicyWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PasswordComplexityPolicyWriteModel, error) { + wm, err := customPasswordComplexityPolicy(ctx, filter) + if err != nil || wm != nil && wm.State.Exists() { + return wm, err + } + wm, err = defaultPasswordComplexityPolicy(ctx, filter) + if err != nil || wm != nil { + return wm, err + } + return nil, errors.ThrowInternal(nil, "USER-uQ96e", "Errors.Internal") +} + +func customPasswordComplexityPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PasswordComplexityPolicyWriteModel, error) { + policy := command.NewOrgPasswordComplexityPolicyWriteModel(authz.GetCtxData(ctx).OrgID) + events, err := filter(ctx, policy.Query()) + if err != nil { + return nil, err + } + if len(events) == 0 { + return nil, nil + } + policy.AppendEvents(events...) + err = policy.Reduce() + return &policy.PasswordComplexityPolicyWriteModel, err +} + +func defaultPasswordComplexityPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PasswordComplexityPolicyWriteModel, error) { + policy := command.NewInstancePasswordComplexityPolicyWriteModel() + events, err := filter(ctx, policy.Query()) + if err != nil { + return nil, err + } + if len(events) == 0 { + return nil, nil + } + policy.AppendEvents(events...) + err = policy.Reduce() + return &policy.PasswordComplexityPolicyWriteModel, err +} diff --git a/internal/command/v2/user_password_complexity_policy_test.go b/internal/command/v2/user_password_complexity_policy_test.go new file mode 100644 index 0000000000..d1dad302bf --- /dev/null +++ b/internal/command/v2/user_password_complexity_policy_test.go @@ -0,0 +1,298 @@ +package command + +import ( + "context" + "reflect" + "testing" + + "github.com/caos/zitadel/internal/command" + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/instance" + "github.com/caos/zitadel/internal/repository/org" +) + +func Test_customPasswordComplexityPolicy(t *testing.T) { + type args struct { + filter preparation.FilterToQueryReducer + } + tests := []struct { + name string + args args + want *command.PasswordComplexityPolicyWriteModel + wantErr bool + }{ + { + name: "err from filter", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return nil, errors.ThrowInternal(nil, "USER-IgYlN", "Errors.Internal") + }, + }, + want: nil, + wantErr: true, + }, + { + name: "no events", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{}, nil + }, + }, + want: nil, + wantErr: false, + }, + { + name: "policy found", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + org.NewPasswordComplexityPolicyAddedEvent( + context.Background(), + &org.NewAggregate("id", "ro").Aggregate, + 8, + true, + true, + true, + true, + ), + }, nil + }, + }, + want: &command.PasswordComplexityPolicyWriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: "id", + ResourceOwner: "ro", + Events: []eventstore.Event{}, + }, + MinLength: 8, + HasLowercase: true, + HasUppercase: true, + HasNumber: true, + HasSymbol: true, + State: domain.PolicyStateActive, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := customPasswordComplexityPolicy(context.Background(), tt.args.filter) + if (err != nil) != tt.wantErr { + t.Errorf("customPasswordComplexityPolicy() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("customPasswordComplexityPolicy() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_defaultPasswordComplexityPolicy(t *testing.T) { + type args struct { + filter preparation.FilterToQueryReducer + } + tests := []struct { + name string + args args + want *command.PasswordComplexityPolicyWriteModel + wantErr bool + }{ + { + name: "err from filter", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return nil, errors.ThrowInternal(nil, "USER-IgYlN", "Errors.Internal") + }, + }, + want: nil, + wantErr: true, + }, + { + name: "no events", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{}, nil + }, + }, + want: nil, + wantErr: false, + }, + { + name: "policy found", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + instance.NewPasswordComplexityPolicyAddedEvent( + context.Background(), + &instance.NewAggregate().Aggregate, + 8, + true, + true, + true, + true, + ), + }, nil + }, + }, + want: &command.PasswordComplexityPolicyWriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: "IAM", + ResourceOwner: "IAM", + Events: []eventstore.Event{}, + }, + MinLength: 8, + HasLowercase: true, + HasUppercase: true, + HasNumber: true, + HasSymbol: true, + State: domain.PolicyStateActive, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := defaultPasswordComplexityPolicy(context.Background(), tt.args.filter) + if (err != nil) != tt.wantErr { + t.Errorf("defaultPasswordComplexityPolicy() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("defaultPasswordComplexityPolicy() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_passwordComplexityPolicy(t *testing.T) { + type args struct { + filter preparation.FilterToQueryReducer + } + tests := []struct { + name string + args args + want *command.PasswordComplexityPolicyWriteModel + wantErr bool + }{ + { + name: "err from filter custom", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return nil, errors.ThrowInternal(nil, "USER-IgYlN", "Errors.Internal") + }, + }, + want: nil, + wantErr: true, + }, + { + name: "custom found", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + org.NewPasswordComplexityPolicyAddedEvent( + context.Background(), + &org.NewAggregate("id", "ro").Aggregate, + 8, + true, + true, + true, + true, + ), + }, nil + }, + }, + want: &command.PasswordComplexityPolicyWriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: "id", + ResourceOwner: "ro", + Events: []eventstore.Event{}, + }, + MinLength: 8, + HasLowercase: true, + HasUppercase: true, + HasNumber: true, + HasSymbol: true, + State: domain.PolicyStateActive, + }, + wantErr: false, + }, + { + name: "err from filter default", + args: args{ + filter: NewMultiFilter(). + Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return nil, nil + }). + Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return nil, errors.ThrowInternal(nil, "USER-6HnsD", "Errors.Internal") + }). + Filter(), + }, + want: nil, + wantErr: true, + }, + { + name: "default found", + args: args{ + filter: NewMultiFilter(). + Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return nil, nil + }). + Append(func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + instance.NewPasswordComplexityPolicyAddedEvent( + context.Background(), + &instance.NewAggregate().Aggregate, + 8, + true, + true, + true, + true, + ), + }, nil + }). + Filter(), + }, + want: &command.PasswordComplexityPolicyWriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: "IAM", + ResourceOwner: "IAM", + Events: []eventstore.Event{}, + }, + MinLength: 8, + HasLowercase: true, + HasUppercase: true, + HasNumber: true, + HasSymbol: true, + State: domain.PolicyStateActive, + }, + wantErr: false, + }, + { + name: "no policy found", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return nil, nil + }, + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := passwordComplexityPolicyWriteModel(context.Background(), tt.args.filter) + if (err != nil) != tt.wantErr { + t.Errorf("defaultPasswordComplexityPolicy() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("defaultPasswordComplexityPolicy() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/command/v2/user_test.go b/internal/command/v2/user_test.go new file mode 100644 index 0000000000..d2f20dcb12 --- /dev/null +++ b/internal/command/v2/user_test.go @@ -0,0 +1,166 @@ +package command + +import ( + "context" + "testing" + + "golang.org/x/text/language" + + "github.com/caos/zitadel/internal/command/v2/preparation" + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/user" +) + +func TestExistsUser(t *testing.T) { + type args struct { + filter preparation.FilterToQueryReducer + id string + resourceOwner string + } + tests := []struct { + name string + args args + wantExists bool + wantErr bool + }{ + { + name: "no events", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{}, nil + }, + id: "id", + resourceOwner: "ro", + }, + wantExists: false, + wantErr: false, + }, + { + name: "human registered", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + user.NewHumanRegisteredEvent( + context.Background(), + &user.NewAggregate("id", "ro").Aggregate, + "userName", + "firstName", + "lastName", + "nickName", + "displayName", + language.German, + domain.GenderFemale, + "support@zitadel.ch", + true, + ), + }, nil + }, + id: "id", + resourceOwner: "ro", + }, + wantExists: true, + wantErr: false, + }, + { + name: "human added", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + user.NewHumanAddedEvent( + context.Background(), + &user.NewAggregate("id", "ro").Aggregate, + "userName", + "firstName", + "lastName", + "nickName", + "displayName", + language.German, + domain.GenderFemale, + "support@zitadel.ch", + true, + ), + }, nil + }, + id: "id", + resourceOwner: "ro", + }, + wantExists: true, + wantErr: false, + }, + { + name: "machine added", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + user.NewMachineAddedEvent( + context.Background(), + &user.NewAggregate("id", "ro").Aggregate, + "userName", + "name", + "description", + true, + ), + }, nil + }, + id: "id", + resourceOwner: "ro", + }, + wantExists: true, + wantErr: false, + }, + { + name: "user removed", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + user.NewMachineAddedEvent( + context.Background(), + &user.NewAggregate("removed", "ro").Aggregate, + "userName", + "name", + "description", + true, + ), + user.NewUserRemovedEvent( + context.Background(), + &user.NewAggregate("removed", "ro").Aggregate, + "userName", + nil, + true, + ), + }, nil + }, + id: "id", + resourceOwner: "ro", + }, + wantExists: false, + wantErr: false, + }, + { + name: "error durring filter", + args: args{ + filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return nil, errors.ThrowInternal(nil, "USER-Drebn", "Errors.Internal") + }, + id: "id", + resourceOwner: "ro", + }, + wantExists: false, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotExists, err := ExistsUser(context.Background(), tt.args.filter, tt.args.id, tt.args.resourceOwner) + if (err != nil) != tt.wantErr { + t.Errorf("ExistsUser() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotExists != tt.wantExists { + t.Errorf("ExistsUser() = %v, want %v", gotExists, tt.wantExists) + } + }) + } +} diff --git a/internal/config/hook/base64_to_bytes.go b/internal/config/hook/base64_to_bytes.go new file mode 100644 index 0000000000..361a4b67ce --- /dev/null +++ b/internal/config/hook/base64_to_bytes.go @@ -0,0 +1,25 @@ +package hook + +import ( + "reflect" + + "github.com/mitchellh/mapstructure" +) + +func Base64ToBytesHookFunc() mapstructure.DecodeHookFuncType { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + + if t != reflect.TypeOf([]byte{}) { + return data, nil + } + + return []byte(data.(string)), nil + } +} diff --git a/internal/config/hook/tag_to_language.go b/internal/config/hook/tag_to_language.go new file mode 100644 index 0000000000..b8ac8c4f39 --- /dev/null +++ b/internal/config/hook/tag_to_language.go @@ -0,0 +1,26 @@ +package hook + +import ( + "reflect" + + "github.com/mitchellh/mapstructure" + "golang.org/x/text/language" +) + +func TagToLanguageHookFunc() mapstructure.DecodeHookFuncType { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + + if t != reflect.TypeOf(language.Tag{}) { + return data, nil + } + + return language.Parse(data.(string)) + } +} diff --git a/internal/database/database.go b/internal/database/database.go index 41dbff8106..162530a6fc 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -2,6 +2,10 @@ package database import ( "database/sql" + //sql import + _ "github.com/lib/pq" + + "github.com/caos/zitadel/internal/errors" ) func Connect(config Config) (*sql.DB, error) { @@ -14,5 +18,9 @@ func Connect(config Config) (*sql.DB, error) { client.SetConnMaxLifetime(config.MaxConnLifetime) client.SetConnMaxIdleTime(config.MaxConnIdleTime) + if err := client.Ping(); err != nil { + return nil, errors.ThrowPreconditionFailed(err, "DATAB-0pIWD", "Errors.Database.Connection.Failed") + } + return client, nil } diff --git a/internal/domain/application_oauth.go b/internal/domain/application_oauth.go index c75b4e3658..c2dfdc8f38 100644 --- a/internal/domain/application_oauth.go +++ b/internal/domain/application_oauth.go @@ -19,15 +19,24 @@ type oAuthApplication interface { //ClientID random_number@projectname (eg. 495894098234@zitadel) func SetNewClientID(a oAuthApplication, idGenerator id.Generator, project *Project) error { - rndID, err := idGenerator.Next() + clientID, err := NewClientID(idGenerator, project.Name) if err != nil { return err } - a.setClientID(fmt.Sprintf("%v@%v", rndID, strings.ReplaceAll(strings.ToLower(project.Name), " ", "_"))) + a.setClientID(clientID) return nil } +func NewClientID(idGenerator id.Generator, projectName string) (string, error) { + rndID, err := idGenerator.Next() + if err != nil { + return "", err + } + + return fmt.Sprintf("%s@%s", rndID, strings.ReplaceAll(strings.ToLower(projectName), " ", "_")), nil +} + func SetNewClientSecretIfNeeded(a oAuthApplication, generator crypto.Generator) (string, error) { if !a.requiresClientSecret() { return "", nil diff --git a/internal/domain/human_email.go b/internal/domain/human_email.go index 097e056666..5947a27600 100644 --- a/internal/domain/human_email.go +++ b/internal/domain/human_email.go @@ -1,13 +1,14 @@ package domain import ( - "github.com/caos/zitadel/internal/crypto" - es_models "github.com/caos/zitadel/internal/eventstore/v1/models" "regexp" "time" + + "github.com/caos/zitadel/internal/crypto" + es_models "github.com/caos/zitadel/internal/eventstore/v1/models" ) -var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") +var EmailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") type Email struct { es_models.ObjectRoot @@ -24,7 +25,7 @@ type EmailCode struct { } func (e *Email) IsValid() bool { - return e.EmailAddress != "" && emailRegex.MatchString(e.EmailAddress) + return e.EmailAddress != "" && EmailRegex.MatchString(e.EmailAddress) } func NewEmailCode(emailGenerator crypto.Generator) (*EmailCode, error) { diff --git a/internal/domain/org.go b/internal/domain/org.go index eafe2354ce..ba4a690a68 100644 --- a/internal/domain/org.go +++ b/internal/domain/org.go @@ -15,7 +15,7 @@ type Org struct { } func (o *Org) IsValid() bool { - return o.Name != "" + return o != nil && o.Name != "" } func (o *Org) AddIAMDomain(iamDomain string) { diff --git a/internal/domain/project.go b/internal/domain/project.go index 84fe9fafa1..8952a39c48 100644 --- a/internal/domain/project.go +++ b/internal/domain/project.go @@ -30,8 +30,14 @@ const ( PrivateLabelingSettingUnspecified PrivateLabelingSetting = iota PrivateLabelingSettingEnforceProjectResourceOwnerPolicy PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy + + privateLabelingSettingMax ) +func (s PrivateLabelingSetting) Valid() bool { + return s >= PrivateLabelingSettingUnspecified && s < privateLabelingSettingMax +} + func (o *Project) IsValid() bool { return o.Name != "" } diff --git a/internal/errors/already_exists.go b/internal/errors/already_exists.go index 57e1c3dea7..10ea010ec0 100644 --- a/internal/errors/already_exists.go +++ b/internal/errors/already_exists.go @@ -26,6 +26,14 @@ func ThrowAlreadyExistsf(parent error, id, format string, a ...interface{}) erro func (err *AlreadyExistsError) IsAlreadyExists() {} +func (err *AlreadyExistsError) Is(target error) bool { + t, ok := target.(*AlreadyExistsError) + if !ok { + return false + } + return err.CaosError.Is(t.CaosError) +} + func IsErrorAlreadyExists(err error) bool { _, ok := err.(AlreadyExists) return ok diff --git a/internal/errors/caos_error.go b/internal/errors/caos_error.go index 08a659875d..83d5a7ce4a 100644 --- a/internal/errors/caos_error.go +++ b/internal/errors/caos_error.go @@ -1,6 +1,7 @@ package errors import ( + "errors" "fmt" "reflect" ) @@ -53,8 +54,21 @@ func (err *CaosError) GetID() string { } func (err *CaosError) Is(target error) bool { - _, ok := target.(*CaosError) - return ok + t, ok := target.(*CaosError) + if !ok { + return false + } + if t.ID != "" && t.ID != err.ID { + return false + } + if t.Message != "" && t.Message != err.Message { + return false + } + if t.Parent != nil && !errors.Is(err.Parent, t.Parent) { + return false + } + + return true } func (err *CaosError) As(target interface{}) bool { diff --git a/internal/errors/deadline_exceeded.go b/internal/errors/deadline_exceeded.go index 715e19e3f2..fdfc7901ac 100644 --- a/internal/errors/deadline_exceeded.go +++ b/internal/errors/deadline_exceeded.go @@ -32,3 +32,11 @@ func IsDeadlineExceeded(err error) bool { _, ok := err.(DeadlineExceeded) return ok } + +func (err *DeadlineExceededError) Is(target error) bool { + t, ok := target.(*DeadlineExceededError) + if !ok { + return false + } + return err.CaosError.Is(t.CaosError) +} diff --git a/internal/errors/internal.go b/internal/errors/internal.go index eea3fb616e..ab42f9e093 100644 --- a/internal/errors/internal.go +++ b/internal/errors/internal.go @@ -32,3 +32,11 @@ func IsInternal(err error) bool { _, ok := err.(Internal) return ok } + +func (err *InternalError) Is(target error) bool { + t, ok := target.(*InternalError) + if !ok { + return false + } + return err.CaosError.Is(t.CaosError) +} diff --git a/internal/errors/invalid_argument.go b/internal/errors/invalid_argument.go index 0f9194248b..31cddeeaf4 100644 --- a/internal/errors/invalid_argument.go +++ b/internal/errors/invalid_argument.go @@ -30,3 +30,11 @@ func IsErrorInvalidArgument(err error) bool { _, ok := err.(InvalidArgument) return ok } + +func (err *InvalidArgumentError) Is(target error) bool { + t, ok := target.(*InvalidArgumentError) + if !ok { + return false + } + return err.CaosError.Is(t.CaosError) +} diff --git a/internal/errors/not_found.go b/internal/errors/not_found.go index f0859b7b94..fed445127a 100644 --- a/internal/errors/not_found.go +++ b/internal/errors/not_found.go @@ -25,3 +25,11 @@ func IsNotFound(err error) bool { _, ok := err.(NotFound) return ok } + +func (err *NotFoundError) Is(target error) bool { + t, ok := target.(*NotFoundError) + if !ok { + return false + } + return err.CaosError.Is(t.CaosError) +} diff --git a/internal/errors/permission_denied.go b/internal/errors/permission_denied.go index 18ac62a7b6..d12e04fbe4 100644 --- a/internal/errors/permission_denied.go +++ b/internal/errors/permission_denied.go @@ -32,3 +32,11 @@ func IsPermissionDenied(err error) bool { _, ok := err.(PermissionDenied) return ok } + +func (err *PermissionDeniedError) Is(target error) bool { + t, ok := target.(*PermissionDeniedError) + if !ok { + return false + } + return err.CaosError.Is(t.CaosError) +} diff --git a/internal/errors/precondition_failed.go b/internal/errors/precondition_failed.go index 89e0c0ee7c..999555996f 100644 --- a/internal/errors/precondition_failed.go +++ b/internal/errors/precondition_failed.go @@ -32,3 +32,11 @@ func IsPreconditionFailed(err error) bool { _, ok := err.(PreconditionFailed) return ok } + +func (err *PreconditionFailedError) Is(target error) bool { + t, ok := target.(*PreconditionFailedError) + if !ok { + return false + } + return err.CaosError.Is(t.CaosError) +} diff --git a/internal/errors/unauthenticated.go b/internal/errors/unauthenticated.go index 3f6ea204b0..dcb28075a4 100644 --- a/internal/errors/unauthenticated.go +++ b/internal/errors/unauthenticated.go @@ -32,3 +32,11 @@ func IsUnauthenticated(err error) bool { _, ok := err.(Unauthenticated) return ok } + +func (err *UnauthenticatedError) Is(target error) bool { + t, ok := target.(*UnauthenticatedError) + if !ok { + return false + } + return err.CaosError.Is(t.CaosError) +} diff --git a/internal/errors/unavailable.go b/internal/errors/unavailable.go index 0c0a09e46f..e6c34e692a 100644 --- a/internal/errors/unavailable.go +++ b/internal/errors/unavailable.go @@ -32,3 +32,11 @@ func IsUnavailable(err error) bool { _, ok := err.(Unavailable) return ok } + +func (err *UnavailableError) Is(target error) bool { + t, ok := target.(*UnavailableError) + if !ok { + return false + } + return err.CaosError.Is(t.CaosError) +} diff --git a/internal/errors/unimplemented.go b/internal/errors/unimplemented.go index e5fde78f88..71b4cd80b6 100644 --- a/internal/errors/unimplemented.go +++ b/internal/errors/unimplemented.go @@ -32,3 +32,11 @@ func IsUnimplemented(err error) bool { _, ok := err.(Unimplemented) return ok } + +func (err *UnimplementedError) Is(target error) bool { + t, ok := target.(*UnimplementedError) + if !ok { + return false + } + return err.CaosError.Is(t.CaosError) +} diff --git a/internal/errors/unknown.go b/internal/errors/unknown.go index fc1c62aa72..9e48385f59 100644 --- a/internal/errors/unknown.go +++ b/internal/errors/unknown.go @@ -32,3 +32,11 @@ func IsUnknown(err error) bool { _, ok := err.(Unknown) return ok } + +func (err *UnknownError) Is(target error) bool { + t, ok := target.(*UnknownError) + if !ok { + return false + } + return err.CaosError.Is(t.CaosError) +} diff --git a/internal/eventstore/aggregate.go b/internal/eventstore/aggregate.go index 60793fd361..6b061399d5 100644 --- a/internal/eventstore/aggregate.go +++ b/internal/eventstore/aggregate.go @@ -68,3 +68,21 @@ type Aggregate struct { //Version is the semver this aggregate represents Version Version `json:"-"` } + +func isAggreagteTypes(a Aggregate, types ...AggregateType) bool { + for _, typ := range types { + if a.Type == typ { + return true + } + } + return false +} + +func isAggregateIDs(a Aggregate, ids ...string) bool { + for _, id := range ids { + if a.ID == id { + return true + } + } + return false +} diff --git a/internal/eventstore/event.go b/internal/eventstore/event.go index 34da76a254..51dad061ed 100644 --- a/internal/eventstore/event.go +++ b/internal/eventstore/event.go @@ -45,3 +45,12 @@ type Event interface { //DataAsBytes returns the payload of the event. It represent the changed fields by the event DataAsBytes() []byte } + +func isEventTypes(event Event, types ...EventType) bool { + for _, typ := range types { + if event.Type() == typ { + return true + } + } + return false +} diff --git a/internal/eventstore/eventstore.go b/internal/eventstore/eventstore.go index d812a5f96b..a71dcdd7e4 100644 --- a/internal/eventstore/eventstore.go +++ b/internal/eventstore/eventstore.go @@ -90,19 +90,20 @@ func commandsToRepository(instanceID string, cmds []Command) (events []*reposito Data: data, } if len(cmd.UniqueConstraints()) > 0 { - constraints = append(constraints, uniqueConstraintsToRepository(cmd.UniqueConstraints())...) + constraints = append(constraints, uniqueConstraintsToRepository(instanceID, cmd.UniqueConstraints())...) } } return events, constraints, nil } -func uniqueConstraintsToRepository(constraints []*EventUniqueConstraint) (uniqueConstraints []*repository.UniqueConstraint) { +func uniqueConstraintsToRepository(instanceID string, constraints []*EventUniqueConstraint) (uniqueConstraints []*repository.UniqueConstraint) { uniqueConstraints = make([]*repository.UniqueConstraint, len(constraints)) for i, constraint := range constraints { uniqueConstraints[i] = &repository.UniqueConstraint{ UniqueType: constraint.UniqueType, UniqueField: constraint.UniqueField, + InstanceID: instanceID, Action: uniqueConstraintActionToRepository(constraint.Action), ErrorMessage: constraint.ErrorMessage, } diff --git a/internal/eventstore/handler/crdb/init.go b/internal/eventstore/handler/crdb/init.go index 9ee33311a8..c5c1de590d 100644 --- a/internal/eventstore/handler/crdb/init.go +++ b/internal/eventstore/handler/crdb/init.go @@ -15,17 +15,21 @@ import ( ) type Table struct { - columns []*Column - primaryKey PrimaryKey - indices []*Index + columns []*Column + primaryKey PrimaryKey + indices []*Index + constraints []*Constraint } -func NewTable(columns []*Column, key PrimaryKey, indices ...*Index) *Table { - return &Table{ +func NewTable(columns []*Column, key PrimaryKey, opts ...TableOption) *Table { + t := &Table{ columns: columns, primaryKey: key, - indices: indices, } + for _, opt := range opts { + opt(t) + } + return t } type SuffixedTable struct { @@ -33,17 +37,27 @@ type SuffixedTable struct { suffix string } -func NewSuffixedTable(columns []*Column, key PrimaryKey, suffix string, indices ...*Index) *SuffixedTable { +func NewSuffixedTable(columns []*Column, key PrimaryKey, suffix string, opts ...TableOption) *SuffixedTable { return &SuffixedTable{ - Table: Table{ - columns: columns, - primaryKey: key, - indices: indices, - }, + Table: *NewTable(columns, key, opts...), suffix: suffix, } } +type TableOption func(*Table) + +func WithIndex(index *Index) TableOption { + return func(table *Table) { + table.indices = append(table.indices, index) + } +} + +func WithConstraint(constraint *Constraint) TableOption { + return func(table *Table) { + table.constraints = append(table.constraints, constraint) + } +} + type Column struct { Name string Type ColumnType @@ -131,6 +145,19 @@ func Hash(bucketsCount uint16) indexOpts { } } +func NewConstraint(name string, columns []string) *Constraint { + i := &Constraint{ + Name: name, + Columns: columns, + } + return i +} + +type Constraint struct { + Name string + Columns []string +} + //Init implements handler.Init func (h *StatementHandler) Init(ctx context.Context, checks ...*handler.Check) error { for _, check := range checks { @@ -241,6 +268,9 @@ func createTableStatement(table *Table, tableName string, suffix string) string for _, index := range table.indices { stmt += fmt.Sprintf(", INDEX %s (%s)", index.Name, strings.Join(index.Columns, ",")) } + for _, constraint := range table.constraints { + stmt += fmt.Sprintf(", CONSTRAINT %s UNIQUE (%s)", constraint.Name, strings.Join(constraint.Columns, ",")) + } return stmt + ");" } @@ -276,7 +306,7 @@ func createColumnsStatement(cols []*Column, tableName string) string { if col.defaultValue != nil { column += " DEFAULT " + defaultValue(col.defaultValue) } - if col.deleteCascade != "" { + if len(col.deleteCascade) != 0 { column += fmt.Sprintf(" REFERENCES %s (%s) ON DELETE CASCADE", tableName, col.deleteCascade) } columns[i] = column diff --git a/internal/eventstore/repository/sql/crdb.go b/internal/eventstore/repository/sql/crdb.go index 1cdd6e3d2c..21db073ffd 100644 --- a/internal/eventstore/repository/sql/crdb.go +++ b/internal/eventstore/repository/sql/crdb.go @@ -80,15 +80,17 @@ const ( uniqueInsert = `INSERT INTO eventstore.unique_constraints ( unique_type, - unique_field + unique_field, + instance_id ) VALUES ( $1, - $2 + $2, + $3 )` uniqueDelete = `DELETE FROM eventstore.unique_constraints - WHERE unique_type = $1 and unique_field = $2` + WHERE unique_type = $1 and unique_field = $2 and instance_id = $3` ) type CRDB struct { @@ -159,8 +161,9 @@ func (db *CRDB) handleUniqueConstraints(ctx context.Context, tx *sql.Tx, uniqueC for _, uniqueConstraint := range uniqueConstraints { uniqueConstraint.UniqueField = strings.ToLower(uniqueConstraint.UniqueField) - if uniqueConstraint.Action == repository.UniqueConstraintAdd { - _, err := tx.ExecContext(ctx, uniqueInsert, uniqueConstraint.UniqueType, uniqueConstraint.UniqueField) + switch uniqueConstraint.Action { + case repository.UniqueConstraintAdd: + _, err := tx.ExecContext(ctx, uniqueInsert, uniqueConstraint.UniqueType, uniqueConstraint.UniqueField, uniqueConstraint.InstanceID) if err != nil { logging.WithFields( "unique_type", uniqueConstraint.UniqueType, @@ -172,8 +175,8 @@ func (db *CRDB) handleUniqueConstraints(ctx context.Context, tx *sql.Tx, uniqueC return caos_errs.ThrowInternal(err, "SQL-dM9ds", "unable to create unique constraint ") } - } else if uniqueConstraint.Action == repository.UniqueConstraintRemoved { - _, err := tx.ExecContext(ctx, uniqueDelete, uniqueConstraint.UniqueType, uniqueConstraint.UniqueField) + case repository.UniqueConstraintRemoved: + _, err := tx.ExecContext(ctx, uniqueDelete, uniqueConstraint.UniqueType, uniqueConstraint.UniqueField, uniqueConstraint.InstanceID) if err != nil { logging.WithFields( "unique_type", uniqueConstraint.UniqueType, diff --git a/internal/eventstore/repository/sql/crdb_test.go b/internal/eventstore/repository/sql/crdb_test.go index d4ffe9e175..12ba3255e7 100644 --- a/internal/eventstore/repository/sql/crdb_test.go +++ b/internal/eventstore/repository/sql/crdb_test.go @@ -267,11 +267,12 @@ func TestCRDB_columnName(t *testing.T) { func TestCRDB_Push_OneAggregate(t *testing.T) { type args struct { - ctx context.Context - events []*repository.Event - uniqueConstraints *repository.UniqueConstraint - uniqueDataType string - uniqueDataField string + ctx context.Context + events []*repository.Event + uniqueConstraints *repository.UniqueConstraint + uniqueDataType string + uniqueDataField string + uniqueDataInstanceID string } type eventsRes struct { pushedEventsCount int @@ -419,7 +420,7 @@ func TestCRDB_Push_OneAggregate(t *testing.T) { client: testCRDBClient, } if tt.args.uniqueDataType != "" && tt.args.uniqueDataField != "" { - err := fillUniqueData(tt.args.uniqueDataType, tt.args.uniqueDataField) + err := fillUniqueData(tt.args.uniqueDataType, tt.args.uniqueDataField, tt.args.uniqueDataInstanceID) if err != nil { t.Error("unable to prefill insert unique data: ", err) return @@ -440,7 +441,7 @@ func TestCRDB_Push_OneAggregate(t *testing.T) { t.Errorf("expected push count %d got %d", tt.res.eventsRes.pushedEventsCount, eventCount) } if tt.args.uniqueConstraints != nil { - countUniqueRow := testCRDBClient.QueryRow("SELECT COUNT(*) FROM eventstore.unique_constraints where unique_type = $1 AND unique_field = $2", tt.args.uniqueConstraints.UniqueType, tt.args.uniqueConstraints.UniqueField) + countUniqueRow := testCRDBClient.QueryRow("SELECT COUNT(*) FROM eventstore.unique_constraints where unique_type = $1 AND unique_field = $2 AND instance_id = $3", tt.args.uniqueConstraints.UniqueType, tt.args.uniqueConstraints.UniqueField, tt.args.uniqueConstraints.InstanceID) var uniqueCount int err := countUniqueRow.Scan(&uniqueCount) if err != nil { @@ -1117,6 +1118,7 @@ func generateRemoveUniqueConstraint(t *testing.T, table, uniqueField string) *re e := &repository.UniqueConstraint{ UniqueType: table, UniqueField: uniqueField, + InstanceID: "", Action: repository.UniqueConstraintRemoved, } diff --git a/internal/eventstore/repository/sql/local_crdb_test.go b/internal/eventstore/repository/sql/local_crdb_test.go index 799026f932..18291d572c 100644 --- a/internal/eventstore/repository/sql/local_crdb_test.go +++ b/internal/eventstore/repository/sql/local_crdb_test.go @@ -53,7 +53,7 @@ func initDB(db *sql.DB) error { return initialise.VerifyZitadel(db) } -func fillUniqueData(unique_type, field string) error { - _, err := testCRDBClient.Exec("INSERT INTO eventstore.unique_constraints (unique_type, unique_field) VALUES ($1, $2)", unique_type, field) +func fillUniqueData(unique_type, field, instanceID string) error { + _, err := testCRDBClient.Exec("INSERT INTO eventstore.unique_constraints (unique_type, unique_field, instance_id) VALUES ($1, $2, $3)", unique_type, field, instanceID) return err } diff --git a/internal/eventstore/repository/unique_constraint.go b/internal/eventstore/repository/unique_constraint.go index 573b5d4ed8..0a3643228f 100644 --- a/internal/eventstore/repository/unique_constraint.go +++ b/internal/eventstore/repository/unique_constraint.go @@ -8,6 +8,9 @@ type UniqueConstraint struct { //UniqueType is the type of the unique field UniqueType string + //InstanceID represents the instance + InstanceID string + //Action defines if unique constraint should be added or removed Action UniqueConstraintAction diff --git a/internal/eventstore/search_query.go b/internal/eventstore/search_query.go index fbf199fd72..cedf685485 100644 --- a/internal/eventstore/search_query.go +++ b/internal/eventstore/search_query.go @@ -50,22 +50,44 @@ func NewSearchQueryBuilder(columns Columns) *SearchQueryBuilder { } } +func (builder *SearchQueryBuilder) Matches(event Event, existingLen int) (matches bool) { + if builder.limit > 0 && uint64(existingLen) >= builder.limit { + return false + } + if builder.resourceOwner != "" && event.Aggregate().ResourceOwner != builder.resourceOwner { + return false + } + if event.Aggregate().InstanceID != "" && builder.instanceID != "" && event.Aggregate().InstanceID != builder.instanceID { + return false + } + + if len(builder.queries) == 0 { + return true + } + for _, query := range builder.queries { + if query.matches(event) { + return true + } + } + return false +} + //Columns defines which fields are set -func (factory *SearchQueryBuilder) Columns(columns Columns) *SearchQueryBuilder { - factory.columns = repository.Columns(columns) - return factory +func (builder *SearchQueryBuilder) Columns(columns Columns) *SearchQueryBuilder { + builder.columns = repository.Columns(columns) + return builder } //Limit defines how many events are returned maximally. -func (factory *SearchQueryBuilder) Limit(limit uint64) *SearchQueryBuilder { - factory.limit = limit - return factory +func (builder *SearchQueryBuilder) Limit(limit uint64) *SearchQueryBuilder { + builder.limit = limit + return builder } //ResourceOwner defines the resource owner (org) of the events -func (factory *SearchQueryBuilder) ResourceOwner(resourceOwner string) *SearchQueryBuilder { - factory.resourceOwner = resourceOwner - return factory +func (builder *SearchQueryBuilder) ResourceOwner(resourceOwner string) *SearchQueryBuilder { + builder.resourceOwner = resourceOwner + return builder } //InstanceID defines the instanceID (system) of the events @@ -75,25 +97,25 @@ func (factory *SearchQueryBuilder) InstanceID(instanceID string) *SearchQueryBui } //OrderDesc changes the sorting order of the returned events to descending -func (factory *SearchQueryBuilder) OrderDesc() *SearchQueryBuilder { - factory.desc = true - return factory +func (builder *SearchQueryBuilder) OrderDesc() *SearchQueryBuilder { + builder.desc = true + return builder } //OrderAsc changes the sorting order of the returned events to ascending -func (factory *SearchQueryBuilder) OrderAsc() *SearchQueryBuilder { - factory.desc = false - return factory +func (builder *SearchQueryBuilder) OrderAsc() *SearchQueryBuilder { + builder.desc = false + return builder } //AddQuery creates a new sub query. //All fields in the sub query are AND-connected in the storage request. //Multiple sub queries are OR-connected in the storage request. -func (factory *SearchQueryBuilder) AddQuery() *SearchQuery { +func (builder *SearchQueryBuilder) AddQuery() *SearchQuery { query := &SearchQuery{ - builder: factory, + builder: builder, } - factory.queries = append(factory.queries, query) + builder.queries = append(builder.queries, query) return query } @@ -145,6 +167,25 @@ func (query *SearchQuery) Builder() *SearchQueryBuilder { return query.builder } +func (query *SearchQuery) matches(event Event) bool { + if query.eventSequenceLess > 0 && event.Sequence() >= query.eventSequenceLess { + return false + } + if query.eventSequenceGreater > 0 && event.Sequence() <= query.eventSequenceGreater { + return false + } + if ok := isAggreagteTypes(event.Aggregate(), query.aggregateTypes...); len(query.aggregateTypes) > 0 && !ok { + return false + } + if ok := isAggregateIDs(event.Aggregate(), query.aggregateIDs...); len(query.aggregateIDs) > 0 && !ok { + return false + } + if ok := isEventTypes(event, query.eventTypes...); len(query.eventTypes) > 0 && !ok { + return false + } + return true +} + func (builder *SearchQueryBuilder) build(instanceID string) (*repository.SearchQuery, error) { if builder == nil || len(builder.queries) < 1 || diff --git a/internal/eventstore/search_query_test.go b/internal/eventstore/search_query_test.go index 0076a2469a..bd44b05e97 100644 --- a/internal/eventstore/search_query_test.go +++ b/internal/eventstore/search_query_test.go @@ -774,3 +774,236 @@ func assertFilters(t *testing.T, i int, want, got *repository.Filter) { t.Errorf("wrong value in filter %d : got: %v want: %v", i, got.Value, want.Value) } } + +func TestSearchQuery_matches(t *testing.T) { + type args struct { + event Event + } + tests := []struct { + name string + query *SearchQuery + event Event + want bool + }{ + { + name: "sequence too low", + query: NewSearchQueryBuilder(ColumnsEvent).AddQuery().SequenceLess(10), + event: &BaseEvent{ + sequence: 10, + }, + want: false, + }, + { + name: "sequence too high", + query: NewSearchQueryBuilder(ColumnsEvent).AddQuery().SequenceGreater(60), + event: &BaseEvent{ + sequence: 60, + }, + want: false, + }, + { + name: "wrong aggregate type", + query: NewSearchQueryBuilder(ColumnsEvent).AddQuery().AggregateTypes("searched"), + event: &BaseEvent{ + aggregate: Aggregate{ + Type: "found", + }, + }, + want: false, + }, + { + name: "wrong aggregate id", + query: NewSearchQueryBuilder(ColumnsEvent).AddQuery().AggregateIDs("1", "10", "100"), + event: &BaseEvent{ + aggregate: Aggregate{ + ID: "2", + }, + }, + want: false, + }, + { + name: "wrong event type", + query: NewSearchQueryBuilder(ColumnsEvent).AddQuery().EventTypes("event.searched.type"), + event: &BaseEvent{ + EventType: "event.actual.type", + }, + want: false, + }, + { + name: "matching", + query: NewSearchQueryBuilder(ColumnsEvent).AddQuery(). + SequenceLess(100).SequenceGreater(50).AggregateIDs("2").AggregateTypes("actual").EventTypes("event.actual.type"), + event: &BaseEvent{ + sequence: 55, + aggregate: Aggregate{ + ID: "2", + Type: "actual", + }, + EventType: "event.actual.type", + }, + want: true, + }, + { + name: "matching empty query", + query: NewSearchQueryBuilder(ColumnsEvent).AddQuery(), + event: &BaseEvent{ + sequence: 55, + aggregate: Aggregate{ + ID: "2", + Type: "actual", + }, + EventType: "event.actual.type", + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + query := &SearchQuery{ + aggregateTypes: tt.query.aggregateTypes, + aggregateIDs: tt.query.aggregateIDs, + eventSequenceGreater: tt.query.eventSequenceGreater, + eventSequenceLess: tt.query.eventSequenceLess, + eventTypes: tt.query.eventTypes, + eventData: tt.query.eventData, + } + if got := query.matches(tt.event); got != tt.want { + t.Errorf("SearchQuery.matches() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSearchQueryBuilder_Matches(t *testing.T) { + type args struct { + event Event + existingLen int + } + tests := []struct { + name string + builder *SearchQueryBuilder + args args + want bool + }{ + { + name: "limit exeeded", + builder: NewSearchQueryBuilder(ColumnsEvent).Limit(100), + args: args{ + event: &BaseEvent{}, + existingLen: 100, + }, + want: false, + }, + { + name: "wrong resource owner", + builder: NewSearchQueryBuilder(ColumnsEvent).ResourceOwner("query"), + args: args{ + event: &BaseEvent{ + aggregate: Aggregate{ + ResourceOwner: "ro", + }, + }, + existingLen: 0, + }, + want: false, + }, + { + name: "wrong instance", + builder: NewSearchQueryBuilder(ColumnsEvent).InstanceID("instance"), + args: args{ + event: &BaseEvent{ + aggregate: Aggregate{ + InstanceID: "different instance", + }, + }, + existingLen: 0, + }, + want: false, + }, + { + name: "query failed", + builder: NewSearchQueryBuilder(ColumnsEvent). + AddQuery(). + SequenceGreater(1000). + Builder(), + args: args{ + event: &BaseEvent{ + sequence: 999, + }, + existingLen: 0, + }, + want: false, + }, + { + name: "matching", + builder: NewSearchQueryBuilder(ColumnsEvent). + Limit(1000). + ResourceOwner("ro"). + InstanceID("instance"). + AddQuery(). + SequenceGreater(1000). + Builder(), + args: args{ + event: &BaseEvent{ + aggregate: Aggregate{ + ResourceOwner: "ro", + InstanceID: "instance", + }, + sequence: 1001, + }, + existingLen: 999, + }, + want: true, + }, + { + name: "matching builder resourceOwner and Instance", + builder: NewSearchQueryBuilder(ColumnsEvent), + args: args{ + event: &BaseEvent{ + aggregate: Aggregate{ + ResourceOwner: "ro", + InstanceID: "instance", + }, + sequence: 1001, + }, + existingLen: 999, + }, + want: true, + }, + { + name: "matching builder resourceOwner only", + builder: NewSearchQueryBuilder(ColumnsEvent), + args: args{ + event: &BaseEvent{ + aggregate: Aggregate{ + ResourceOwner: "ro", + }, + sequence: 1001, + }, + existingLen: 999, + }, + want: true, + }, + { + name: "matching builder instanceID only", + builder: NewSearchQueryBuilder(ColumnsEvent), + args: args{ + event: &BaseEvent{ + aggregate: Aggregate{ + InstanceID: "instance", + }, + sequence: 1001, + }, + existingLen: 999, + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.builder.Matches(tt.args.event, tt.args.existingLen); got != tt.want { + t.Errorf("SearchQueryBuilder.Matches() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/migration/command.go b/internal/migration/command.go index 84f73ca94a..4ebd85833c 100644 --- a/internal/migration/command.go +++ b/internal/migration/command.go @@ -1,66 +1,76 @@ package migration -import "github.com/caos/zitadel/internal/eventstore" +import ( + "context" + "encoding/json" + + "github.com/caos/zitadel/internal/api/authz" + "github.com/caos/zitadel/internal/api/service" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/eventstore/repository" +) //SetupStep is the command pushed on the eventstore type SetupStep struct { - typ eventstore.EventType - migration Migration - Name string `json:"name"` - Error error `json:"error,omitempty"` - done bool + eventstore.BaseEvent `json:"-"` + migration Migration + Name string `json:"name"` + Error error `json:"error,omitempty"` +} + +func (s *SetupStep) UnmarshalJSON(data []byte) error { + fields := struct { + Name string `json:"name,"` + Error *errors.CaosError `json:"error"` + }{} + if err := json.Unmarshal(data, &fields); err != nil { + return err + } + s.Name = fields.Name + s.Error = fields.Error + return nil } func setupStartedCmd(migration Migration) eventstore.Command { + ctx := authz.SetCtxData(service.WithService(context.Background(), "system"), authz.CtxData{UserID: "system", OrgID: "SYSTEM", ResourceOwner: "SYSTEM"}) return &SetupStep{ + BaseEvent: *eventstore.NewBaseEventForPush( + ctx, + eventstore.NewAggregate(ctx, aggregateID, aggregateType, "v1"), + startedType), migration: migration, - typ: startedType, Name: migration.String(), } } func setupDoneCmd(migration Migration, err error) eventstore.Command { + ctx := authz.SetCtxData(service.WithService(context.Background(), "system"), authz.CtxData{UserID: "system", OrgID: "SYSTEM", ResourceOwner: "SYSTEM"}) s := &SetupStep{ - typ: doneType, migration: migration, Name: migration.String(), + Error: err, } + typ := doneType if err != nil { - s.typ = failedType - s.Error = err + typ = failedType } + s.BaseEvent = *eventstore.NewBaseEventForPush( + ctx, + eventstore.NewAggregate(ctx, aggregateID, aggregateType, "v1"), + typ) + return s } -func (s *SetupStep) Aggregate() eventstore.Aggregate { - return eventstore.Aggregate{ - ID: aggregateID, - Type: aggregateType, - ResourceOwner: "SYSTEM", - Version: "v1", - } -} - -func (s *SetupStep) EditorService() string { - return "system" -} - -func (s *SetupStep) EditorUser() string { - return "system" -} - -func (s *SetupStep) Type() eventstore.EventType { - return s.typ -} - func (s *SetupStep) Data() interface{} { return s } func (s *SetupStep) UniqueConstraints() []*eventstore.EventUniqueConstraint { - switch s.typ { + switch s.Type() { case startedType: return []*eventstore.EventUniqueConstraint{ eventstore.NewAddEventUniqueConstraint("migration_started", s.migration.String(), "Errors.Step.Started.AlreadyExists"), @@ -75,3 +85,24 @@ func (s *SetupStep) UniqueConstraints() []*eventstore.EventUniqueConstraint { } } } + +func RegisterMappers(es *eventstore.Eventstore) { + es.RegisterFilterEventMapper(startedType, SetupMapper) + es.RegisterFilterEventMapper(doneType, SetupMapper) + es.RegisterFilterEventMapper(failedType, SetupMapper) +} + +func SetupMapper(event *repository.Event) (eventstore.Event, error) { + step := &SetupStep{ + BaseEvent: *eventstore.BaseEventFromRepo(event), + } + if len(event.Data) == 0 { + return step, nil + } + err := json.Unmarshal(event.Data, step) + if err != nil { + return nil, errors.ThrowInternal(err, "IAM-hYp7M", "unable to unmarshal step") + } + + return step, nil +} diff --git a/internal/migration/migration.go b/internal/migration/migration.go index 34c75965bb..2aca242be5 100644 --- a/internal/migration/migration.go +++ b/internal/migration/migration.go @@ -2,7 +2,6 @@ package migration import ( "context" - "encoding/json" "github.com/caos/logging" @@ -41,7 +40,7 @@ func Migrate(ctx context.Context, es *eventstore.Eventstore, migration Migration func shouldExec(ctx context.Context, es *eventstore.Eventstore, migration Migration) (should bool, err error) { events, err := es.Filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). - OrderDesc(). + OrderAsc(). AddQuery(). AggregateTypes(aggregateType). AggregateIDs(aggregateID). @@ -51,34 +50,24 @@ func shouldExec(ctx context.Context, es *eventstore.Eventstore, migration Migrat return false, err } - if len(events) == 0 { - return true, nil - } - if events[len(events)-1].Type() == startedType { - return false, nil - } - - for _, e := range events { - step := new(SetupStep) - - err = json.Unmarshal(e.DataAsBytes(), step) - if err != nil { - return false, err + var isStarted bool + for _, event := range events { + e, ok := event.(*SetupStep) + if !ok { + return false, errors.ThrowInternal(nil, "MIGRA-IJY3D", "Errors.Internal") } - if step.Name != migration.String() { + if e.Name != migration.String() { continue } - switch e.Type() { - case startedType, doneType: - //TODO: if started should we wait until done/failed? + switch event.Type() { + case startedType, failedType: + isStarted = !isStarted + case doneType: return false, nil - case failedType: - //TODO: how to allow retries? - logging.WithFields("migration", migration.String()).Error("failed before") - return false, errors.ThrowInternal(nil, "MIGRA-mjI2E", "migration failed before") } } - return true, nil + + return !isStarted, nil } diff --git a/internal/notification/repository/eventsourcing/handler/notify_user.go b/internal/notification/repository/eventsourcing/handler/notify_user.go index cc60707911..0e2d4f4f42 100644 --- a/internal/notification/repository/eventsourcing/handler/notify_user.go +++ b/internal/notification/repository/eventsourcing/handler/notify_user.go @@ -5,18 +5,18 @@ import ( "github.com/caos/logging" + "github.com/caos/zitadel/internal/api/authz" caos_errs "github.com/caos/zitadel/internal/errors" v1 "github.com/caos/zitadel/internal/eventstore/v1" - es_sdk "github.com/caos/zitadel/internal/eventstore/v1/sdk" - org_view "github.com/caos/zitadel/internal/org/repository/view" - query2 "github.com/caos/zitadel/internal/query" - "github.com/caos/zitadel/internal/repository/org" - es_models "github.com/caos/zitadel/internal/eventstore/v1/models" "github.com/caos/zitadel/internal/eventstore/v1/query" + es_sdk "github.com/caos/zitadel/internal/eventstore/v1/sdk" "github.com/caos/zitadel/internal/eventstore/v1/spooler" org_model "github.com/caos/zitadel/internal/org/model" org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" + org_view "github.com/caos/zitadel/internal/org/repository/view" + query2 "github.com/caos/zitadel/internal/query" + "github.com/caos/zitadel/internal/repository/org" es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" view_model "github.com/caos/zitadel/internal/user/repository/view/model" ) @@ -152,9 +152,9 @@ func (u *NotifyUser) ProcessOrg(event *es_models.Event) (err error) { switch event.Type { case org_es_model.OrgDomainVerified, org_es_model.OrgDomainRemoved, - es_models.EventType(org.OrgDomainPolicyAddedEventType), - es_models.EventType(org.OrgDomainPolicyChangedEventType), - es_models.EventType(org.OrgDomainPolicyRemovedEventType): + es_models.EventType(org.DomainPolicyAddedEventType), + es_models.EventType(org.DomainPolicyChangedEventType), + es_models.EventType(org.DomainPolicyRemovedEventType): return u.fillLoginNamesOnOrgUsers(event) case org_es_model.OrgDomainPrimarySet: return u.fillPreferredLoginNamesOnOrgUsers(event) @@ -251,7 +251,7 @@ func (u *NotifyUser) loginNameInformation(ctx context.Context, orgID string) (us return false, "", nil, err } if org.DomainPolicy == nil { - policy, err := u.queries.DefaultDomainPolicy(ctx) + policy, err := u.queries.DefaultDomainPolicy(authz.WithInstance(ctx, authz.Instance{ID: org.InstanceID})) if err != nil { return false, "", nil, err } diff --git a/internal/query/instance_test.go b/internal/query/instance_test.go index 79f25cd09a..31100dcb01 100644 --- a/internal/query/instance_test.go +++ b/internal/query/instance_test.go @@ -30,15 +30,15 @@ func Test_InstancePrepares(t *testing.T) { prepare: prepareIAMQuery, want: want{ sqlExpectations: mockQueries( - regexp.QuoteMeta(`SELECT projections.instance.id,`+ - ` projections.instance.change_date,`+ - ` projections.instance.sequence,`+ - ` projections.instance.global_org_id,`+ - ` projections.instance.iam_project_id,`+ - ` projections.instance.setup_started,`+ - ` projections.instance.setup_done,`+ - ` projections.instance.default_language`+ - ` FROM projections.instance`), + regexp.QuoteMeta(`SELECT projections.instances.id,`+ + ` projections.instances.change_date,`+ + ` projections.instances.sequence,`+ + ` projections.instances.global_org_id,`+ + ` projections.instances.iam_project_id,`+ + ` projections.instances.setup_started,`+ + ` projections.instances.setup_done,`+ + ` projections.instances.default_language`+ + ` FROM projections.instances`), nil, nil, ), @@ -56,15 +56,15 @@ func Test_InstancePrepares(t *testing.T) { prepare: prepareIAMQuery, want: want{ sqlExpectations: mockQuery( - regexp.QuoteMeta(`SELECT projections.instance.id,`+ - ` projections.instance.change_date,`+ - ` projections.instance.sequence,`+ - ` projections.instance.global_org_id,`+ - ` projections.instance.iam_project_id,`+ - ` projections.instance.setup_started,`+ - ` projections.instance.setup_done,`+ - ` projections.instance.default_language`+ - ` FROM projections.instance`), + regexp.QuoteMeta(`SELECT projections.instances.id,`+ + ` projections.instances.change_date,`+ + ` projections.instances.sequence,`+ + ` projections.instances.global_org_id,`+ + ` projections.instances.iam_project_id,`+ + ` projections.instances.setup_started,`+ + ` projections.instances.setup_done,`+ + ` projections.instances.default_language`+ + ` FROM projections.instances`), []string{ "id", "change_date", @@ -103,15 +103,15 @@ func Test_InstancePrepares(t *testing.T) { prepare: prepareIAMQuery, want: want{ sqlExpectations: mockQueryErr( - regexp.QuoteMeta(`SELECT projections.instance.id,`+ - ` projections.instance.change_date,`+ - ` projections.instance.sequence,`+ - ` projections.instance.global_org_id,`+ - ` projections.instance.iam_project_id,`+ - ` projections.instance.setup_started,`+ - ` projections.instance.setup_done,`+ - ` projections.instance.default_language`+ - ` FROM projections.instance`), + regexp.QuoteMeta(`SELECT projections.instances.id,`+ + ` projections.instances.change_date,`+ + ` projections.instances.sequence,`+ + ` projections.instances.global_org_id,`+ + ` projections.instances.iam_project_id,`+ + ` projections.instances.setup_started,`+ + ` projections.instances.setup_done,`+ + ` projections.instances.default_language`+ + ` FROM projections.instances`), sql.ErrConnDone, ), err: func(err error) (error, bool) { diff --git a/internal/query/projection/action.go b/internal/query/projection/action.go index 05e8cca3ab..351b6b446a 100644 --- a/internal/query/projection/action.go +++ b/internal/query/projection/action.go @@ -49,7 +49,7 @@ func NewActionProjection(ctx context.Context, config crdb.StatementHandlerConfig crdb.NewColumn(ActionAllowedToFailCol, crdb.ColumnTypeBool, crdb.Default(false)), }, crdb.NewPrimaryKey(ActionInstanceIDCol, ActionIDCol), - crdb.NewIndex("ro_idx", []string{ActionResourceOwnerCol}), + crdb.WithIndex(crdb.NewIndex("ro_idx", []string{ActionResourceOwnerCol})), ), ) p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/app.go b/internal/query/projection/app.go index cf8bb6f07e..61a2c4b6cd 100644 --- a/internal/query/projection/app.go +++ b/internal/query/projection/app.go @@ -75,7 +75,8 @@ func NewAppProjection(ctx context.Context, config crdb.StatementHandlerConfig) * crdb.NewColumn(AppColumnSequence, crdb.ColumnTypeInt64), }, crdb.NewPrimaryKey(AppColumnInstanceID, ActionIDCol), - crdb.NewIndex("project_id_idx", []string{AppColumnProjectID}), + crdb.WithIndex(crdb.NewIndex("project_id_idx", []string{AppColumnProjectID})), + crdb.WithConstraint(crdb.NewConstraint("id_unique", []string{AppColumnID})), ), crdb.NewSuffixedTable([]*crdb.Column{ crdb.NewColumn(AppAPIConfigColumnAppID, crdb.ColumnTypeText, crdb.DeleteCascade(AppColumnID)), @@ -85,7 +86,7 @@ func NewAppProjection(ctx context.Context, config crdb.StatementHandlerConfig) * }, crdb.NewPrimaryKey(AppAPIConfigColumnAppID), appAPITableSuffix, - crdb.NewIndex("client_id_idx", []string{AppAPIConfigColumnClientID}), + crdb.WithIndex(crdb.NewIndex("client_id_idx", []string{AppAPIConfigColumnClientID})), ), crdb.NewSuffixedTable([]*crdb.Column{ crdb.NewColumn(AppOIDCConfigColumnAppID, crdb.ColumnTypeText, crdb.DeleteCascade(AppColumnID)), @@ -108,7 +109,7 @@ func NewAppProjection(ctx context.Context, config crdb.StatementHandlerConfig) * }, crdb.NewPrimaryKey(AppOIDCConfigColumnAppID), appOIDCTableSuffix, - crdb.NewIndex("client_id_idx", []string{AppOIDCConfigColumnClientID}), + crdb.WithIndex(crdb.NewIndex("client_id_idx", []string{AppOIDCConfigColumnClientID})), ), ) p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/authn_key.go b/internal/query/projection/authn_key.go index 9864b0f4b7..7a32a118e8 100644 --- a/internal/query/projection/authn_key.go +++ b/internal/query/projection/authn_key.go @@ -53,8 +53,8 @@ func NewAuthNKeyProjection(ctx context.Context, config crdb.StatementHandlerConf crdb.NewColumn(AuthNKeyTypeCol, crdb.ColumnTypeEnum, crdb.Default(0)), }, crdb.NewPrimaryKey(AuthNKeyInstanceIDCol, AuthNKeyIDCol), - crdb.NewIndex("enabled_idx", []string{AuthNKeyEnabledCol}), - crdb.NewIndex("identifier_idx", []string{AuthNKeyIdentifierCol}), + crdb.WithIndex(crdb.NewIndex("enabled_idx", []string{AuthNKeyEnabledCol})), + crdb.WithIndex(crdb.NewIndex("identifier_idx", []string{AuthNKeyIdentifierCol})), ), ) p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/org_iam_policy.go b/internal/query/projection/domain_policy.go similarity index 85% rename from internal/query/projection/org_iam_policy.go rename to internal/query/projection/domain_policy.go index 32a98ce08e..8e1313b8ec 100644 --- a/internal/query/projection/org_iam_policy.go +++ b/internal/query/projection/domain_policy.go @@ -60,15 +60,15 @@ func (p *DomainPolicyProjection) reducers() []handler.AggregateReducer { Aggregate: org.AggregateType, EventRedusers: []handler.EventReducer{ { - Event: org.OrgDomainPolicyAddedEventType, + Event: org.DomainPolicyAddedEventType, Reduce: p.reduceAdded, }, { - Event: org.OrgDomainPolicyChangedEventType, + Event: org.DomainPolicyChangedEventType, Reduce: p.reduceChanged, }, { - Event: org.OrgDomainPolicyRemovedEventType, + Event: org.DomainPolicyRemovedEventType, Reduce: p.reduceRemoved, }, }, @@ -77,11 +77,11 @@ func (p *DomainPolicyProjection) reducers() []handler.AggregateReducer { Aggregate: instance.AggregateType, EventRedusers: []handler.EventReducer{ { - Event: instance.InstanceDomainPolicyAddedEventType, + Event: instance.DomainPolicyAddedEventType, Reduce: p.reduceAdded, }, { - Event: instance.InstanceDomainPolicyChangedEventType, + Event: instance.DomainPolicyChangedEventType, Reduce: p.reduceChanged, }, }, @@ -93,14 +93,14 @@ func (p *DomainPolicyProjection) reduceAdded(event eventstore.Event) (*handler.S var policyEvent policy.DomainPolicyAddedEvent var isDefault bool switch e := event.(type) { - case *org.OrgDomainPolicyAddedEvent: + case *org.DomainPolicyAddedEvent: policyEvent = e.DomainPolicyAddedEvent isDefault = false - case *instance.InstanceDomainPolicyAddedEvent: + case *instance.DomainPolicyAddedEvent: policyEvent = e.DomainPolicyAddedEvent isDefault = true default: - return nil, errors.ThrowInvalidArgumentf(nil, "PROJE-CSE7A", "reduce.wrong.event.type %v", []eventstore.EventType{org.OrgDomainPolicyAddedEventType, instance.InstanceDomainPolicyAddedEventType}) + return nil, errors.ThrowInvalidArgumentf(nil, "PROJE-CSE7A", "reduce.wrong.event.type %v", []eventstore.EventType{org.DomainPolicyAddedEventType, instance.DomainPolicyAddedEventType}) } return crdb.NewCreateStatement( &policyEvent, @@ -120,12 +120,12 @@ func (p *DomainPolicyProjection) reduceAdded(event eventstore.Event) (*handler.S func (p *DomainPolicyProjection) reduceChanged(event eventstore.Event) (*handler.Statement, error) { var policyEvent policy.DomainPolicyChangedEvent switch e := event.(type) { - case *org.OrgDomainPolicyChangedEvent: + case *org.DomainPolicyChangedEvent: policyEvent = e.DomainPolicyChangedEvent - case *instance.InstanceDomainPolicyChangedEvent: + case *instance.DomainPolicyChangedEvent: policyEvent = e.DomainPolicyChangedEvent default: - return nil, errors.ThrowInvalidArgumentf(nil, "PROJE-qgVug", "reduce.wrong.event.type %v", []eventstore.EventType{org.OrgDomainPolicyChangedEventType, instance.InstanceDomainPolicyChangedEventType}) + return nil, errors.ThrowInvalidArgumentf(nil, "PROJE-qgVug", "reduce.wrong.event.type %v", []eventstore.EventType{org.DomainPolicyChangedEventType, instance.DomainPolicyChangedEventType}) } cols := []handler.Column{ handler.NewCol(DomainPolicyChangeDateCol, policyEvent.CreationDate()), @@ -143,9 +143,9 @@ func (p *DomainPolicyProjection) reduceChanged(event eventstore.Event) (*handler } func (p *DomainPolicyProjection) reduceRemoved(event eventstore.Event) (*handler.Statement, error) { - policyEvent, ok := event.(*org.OrgDomainPolicyRemovedEvent) + policyEvent, ok := event.(*org.DomainPolicyRemovedEvent) if !ok { - return nil, errors.ThrowInvalidArgumentf(nil, "PROJE-JAENd", "reduce.wrong.event.type %s", org.OrgDomainPolicyRemovedEventType) + return nil, errors.ThrowInvalidArgumentf(nil, "PROJE-JAENd", "reduce.wrong.event.type %s", org.DomainPolicyRemovedEventType) } return crdb.NewDeleteStatement( policyEvent, diff --git a/internal/query/projection/org_iam_policy_test.go b/internal/query/projection/domain_policy_test.go similarity index 89% rename from internal/query/projection/org_iam_policy_test.go rename to internal/query/projection/domain_policy_test.go index 3b291e0e00..708624cd0f 100644 --- a/internal/query/projection/org_iam_policy_test.go +++ b/internal/query/projection/domain_policy_test.go @@ -26,12 +26,12 @@ func TestDomainPolicyProjection_reduces(t *testing.T) { name: "org.reduceAdded", args: args{ event: getEvent(testEvent( - repository.EventType(org.OrgDomainPolicyAddedEventType), + repository.EventType(org.DomainPolicyAddedEventType), org.AggregateType, []byte(`{ "userLoginMustBeDomain": true }`), - ), org.OrgDomainPolicyAddedEventMapper), + ), org.DomainPolicyAddedEventMapper), }, reduce: (&DomainPolicyProjection{}).reduceAdded, want: wantReduce{ @@ -64,12 +64,12 @@ func TestDomainPolicyProjection_reduces(t *testing.T) { reduce: (&DomainPolicyProjection{}).reduceChanged, args: args{ event: getEvent(testEvent( - repository.EventType(org.OrgDomainPolicyChangedEventType), + repository.EventType(org.DomainPolicyChangedEventType), org.AggregateType, []byte(`{ "userLoginMustBeDomain": true }`), - ), org.OrgDomainPolicyChangedEventMapper), + ), org.DomainPolicyChangedEventMapper), }, want: wantReduce{ aggregateType: eventstore.AggregateType("org"), @@ -96,10 +96,10 @@ func TestDomainPolicyProjection_reduces(t *testing.T) { reduce: (&DomainPolicyProjection{}).reduceRemoved, args: args{ event: getEvent(testEvent( - repository.EventType(org.OrgDomainPolicyRemovedEventType), + repository.EventType(org.DomainPolicyRemovedEventType), org.AggregateType, nil, - ), org.OrgDomainPolicyRemovedEventMapper), + ), org.DomainPolicyRemovedEventMapper), }, want: wantReduce{ aggregateType: eventstore.AggregateType("org"), @@ -123,12 +123,12 @@ func TestDomainPolicyProjection_reduces(t *testing.T) { reduce: (&DomainPolicyProjection{}).reduceAdded, args: args{ event: getEvent(testEvent( - repository.EventType(instance.InstanceDomainPolicyAddedEventType), + repository.EventType(instance.DomainPolicyAddedEventType), instance.AggregateType, []byte(`{ "userLoginMustBeDomain": true }`), - ), instance.InstanceDomainPolicyAddedEventMapper), + ), instance.DomainPolicyAddedEventMapper), }, want: wantReduce{ aggregateType: eventstore.AggregateType("instance"), @@ -160,12 +160,12 @@ func TestDomainPolicyProjection_reduces(t *testing.T) { reduce: (&DomainPolicyProjection{}).reduceChanged, args: args{ event: getEvent(testEvent( - repository.EventType(instance.InstanceDomainPolicyChangedEventType), + repository.EventType(instance.DomainPolicyChangedEventType), instance.AggregateType, []byte(`{ "userLoginMustBeDomain": true }`), - ), instance.InstanceDomainPolicyChangedEventMapper), + ), instance.DomainPolicyChangedEventMapper), }, want: wantReduce{ aggregateType: eventstore.AggregateType("instance"), diff --git a/internal/query/projection/idp.go b/internal/query/projection/idp.go index 6389712259..fa323d6ec9 100644 --- a/internal/query/projection/idp.go +++ b/internal/query/projection/idp.go @@ -77,7 +77,8 @@ func NewIDPProjection(ctx context.Context, config crdb.StatementHandlerConfig) * crdb.NewColumn(IDPTypeCol, crdb.ColumnTypeEnum), }, crdb.NewPrimaryKey(IDPInstanceIDCol, IDPIDCol), - crdb.NewIndex("ro_idx", []string{IDPResourceOwnerCol}), + crdb.WithIndex(crdb.NewIndex("ro_idx", []string{IDPResourceOwnerCol})), + crdb.WithConstraint(crdb.NewConstraint("id_unique", []string{IDPIDCol})), ), crdb.NewSuffixedTable([]*crdb.Column{ crdb.NewColumn(OIDCConfigIDPIDCol, crdb.ColumnTypeText, crdb.DeleteCascade(IDPIDCol)), diff --git a/internal/query/projection/idp_login_policy_link.go b/internal/query/projection/idp_login_policy_link.go index 380e4a35a5..f84535c84a 100644 --- a/internal/query/projection/idp_login_policy_link.go +++ b/internal/query/projection/idp_login_policy_link.go @@ -45,7 +45,7 @@ func NewIDPLoginPolicyLinkProjection(ctx context.Context, config crdb.StatementH crdb.NewColumn(IDPLoginPolicyLinkInstanceIDCol, crdb.ColumnTypeText), }, crdb.NewPrimaryKey(IDPLoginPolicyLinkInstanceIDCol, IDPLoginPolicyLinkAggregateIDCol, IDPLoginPolicyLinkIDPIDCol), - crdb.NewIndex("ro_idx", []string{IDPLoginPolicyLinkResourceOwnerCol}), + crdb.WithIndex(crdb.NewIndex("ro_idx", []string{IDPLoginPolicyLinkResourceOwnerCol})), ), ) p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/idp_user_link.go b/internal/query/projection/idp_user_link.go index d94efea949..4d25584a42 100644 --- a/internal/query/projection/idp_user_link.go +++ b/internal/query/projection/idp_user_link.go @@ -46,7 +46,7 @@ func NewIDPUserLinkProjection(ctx context.Context, config crdb.StatementHandlerC crdb.NewColumn(IDPUserLinkDisplayNameCol, crdb.ColumnTypeText), }, crdb.NewPrimaryKey(IDPUserLinkInstanceIDCol, IDPUserLinkIDPIDCol, IDPUserLinkExternalUserIDCol), - crdb.NewIndex("user_idx", []string{IDPUserLinkUserIDCol}), + crdb.WithIndex(crdb.NewIndex("user_idx", []string{IDPUserLinkUserIDCol})), ), ) p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/instance.go b/internal/query/projection/instance.go index ce33012dd8..fbed932852 100644 --- a/internal/query/projection/instance.go +++ b/internal/query/projection/instance.go @@ -11,7 +11,7 @@ import ( ) const ( - InstanceProjectionTable = "projections.instance" + InstanceProjectionTable = "projections.instances" InstanceColumnID = "id" InstanceColumnChangeDate = "change_date" @@ -23,12 +23,12 @@ const ( InstanceColumnDefaultLanguage = "default_language" ) -type IAMProjection struct { +type InstanceProjection struct { crdb.StatementHandler } -func NewIAMProjection(ctx context.Context, config crdb.StatementHandlerConfig) *IAMProjection { - p := new(IAMProjection) +func NewInstanceProjection(ctx context.Context, config crdb.StatementHandlerConfig) *InstanceProjection { + p := new(InstanceProjection) config.ProjectionName = InstanceProjectionTable config.Reducers = p.reducers() config.InitCheck = crdb.NewTableCheck( @@ -49,7 +49,7 @@ func NewIAMProjection(ctx context.Context, config crdb.StatementHandlerConfig) * return p } -func (p *IAMProjection) reducers() []handler.AggregateReducer { +func (p *InstanceProjection) reducers() []handler.AggregateReducer { return []handler.AggregateReducer{ { Aggregate: instance.AggregateType, @@ -79,7 +79,7 @@ func (p *IAMProjection) reducers() []handler.AggregateReducer { } } -func (p *IAMProjection) reduceGlobalOrgSet(event eventstore.Event) (*handler.Statement, error) { +func (p *InstanceProjection) reduceGlobalOrgSet(event eventstore.Event) (*handler.Statement, error) { e, ok := event.(*instance.GlobalOrgSetEvent) if !ok { return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-2n9f2", "reduce.wrong.event.type %s", instance.GlobalOrgSetEventType) @@ -95,7 +95,7 @@ func (p *IAMProjection) reduceGlobalOrgSet(event eventstore.Event) (*handler.Sta ), nil } -func (p *IAMProjection) reduceIAMProjectSet(event eventstore.Event) (*handler.Statement, error) { +func (p *InstanceProjection) reduceIAMProjectSet(event eventstore.Event) (*handler.Statement, error) { e, ok := event.(*instance.ProjectSetEvent) if !ok { return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-30o0e", "reduce.wrong.event.type %s", instance.ProjectSetEventType) @@ -111,7 +111,7 @@ func (p *IAMProjection) reduceIAMProjectSet(event eventstore.Event) (*handler.St ), nil } -func (p *IAMProjection) reduceDefaultLanguageSet(event eventstore.Event) (*handler.Statement, error) { +func (p *InstanceProjection) reduceDefaultLanguageSet(event eventstore.Event) (*handler.Statement, error) { e, ok := event.(*instance.DefaultLanguageSetEvent) if !ok { return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-30o0e", "reduce.wrong.event.type %s", instance.DefaultLanguageSetEventType) @@ -127,7 +127,7 @@ func (p *IAMProjection) reduceDefaultLanguageSet(event eventstore.Event) (*handl ), nil } -func (p *IAMProjection) reduceSetupEvent(event eventstore.Event) (*handler.Statement, error) { +func (p *InstanceProjection) reduceSetupEvent(event eventstore.Event) (*handler.Statement, error) { e, ok := event.(*instance.SetupStepEvent) if !ok { return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-d9nfw", "reduce.wrong.event.type %v", []eventstore.EventType{instance.SetupDoneEventType, instance.SetupStartedEventType}) diff --git a/internal/query/projection/instance_member.go b/internal/query/projection/instance_member.go index 15c41d2a4d..3cb42ca536 100644 --- a/internal/query/projection/instance_member.go +++ b/internal/query/projection/instance_member.go @@ -29,7 +29,7 @@ func NewInstanceMemberProjection(ctx context.Context, config crdb.StatementHandl crdb.NewTable( append(memberColumns, crdb.NewColumn(InstanceColumnID, crdb.ColumnTypeText)), crdb.NewPrimaryKey(MemberInstanceID, InstanceColumnID, MemberUserIDCol), - crdb.NewIndex("user_idx", []string{MemberUserIDCol}), + crdb.WithIndex(crdb.NewIndex("user_idx", []string{MemberUserIDCol})), ), ) diff --git a/internal/query/projection/instance_test.go b/internal/query/projection/instance_test.go index 041156237f..ecec5131c4 100644 --- a/internal/query/projection/instance_test.go +++ b/internal/query/projection/instance_test.go @@ -30,7 +30,7 @@ func TestInstanceProjection_reduces(t *testing.T) { []byte(`{"globalOrgId": "orgid"}`), ), instance.GlobalOrgSetMapper), }, - reduce: (&IAMProjection{}).reduceGlobalOrgSet, + reduce: (&InstanceProjection{}).reduceGlobalOrgSet, want: wantReduce{ projection: InstanceProjectionTable, aggregateType: eventstore.AggregateType("instance"), @@ -39,7 +39,7 @@ func TestInstanceProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPSERT INTO projections.instance (id, change_date, sequence, global_org_id) VALUES ($1, $2, $3, $4)", + expectedStmt: "UPSERT INTO projections.instances (id, change_date, sequence, global_org_id) VALUES ($1, $2, $3, $4)", expectedArgs: []interface{}{ "instance-id", anyArg{}, @@ -60,7 +60,7 @@ func TestInstanceProjection_reduces(t *testing.T) { []byte(`{"iamProjectId": "project-id"}`), ), instance.ProjectSetMapper), }, - reduce: (&IAMProjection{}).reduceIAMProjectSet, + reduce: (&InstanceProjection{}).reduceIAMProjectSet, want: wantReduce{ projection: InstanceProjectionTable, aggregateType: eventstore.AggregateType("instance"), @@ -69,7 +69,7 @@ func TestInstanceProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPSERT INTO projections.instance (id, change_date, sequence, iam_project_id) VALUES ($1, $2, $3, $4)", + expectedStmt: "UPSERT INTO projections.instances (id, change_date, sequence, iam_project_id) VALUES ($1, $2, $3, $4)", expectedArgs: []interface{}{ "instance-id", anyArg{}, @@ -90,7 +90,7 @@ func TestInstanceProjection_reduces(t *testing.T) { []byte(`{"language": "en"}`), ), instance.DefaultLanguageSetMapper), }, - reduce: (&IAMProjection{}).reduceDefaultLanguageSet, + reduce: (&InstanceProjection{}).reduceDefaultLanguageSet, want: wantReduce{ projection: InstanceProjectionTable, aggregateType: eventstore.AggregateType("instance"), @@ -99,7 +99,7 @@ func TestInstanceProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPSERT INTO projections.instance (id, change_date, sequence, default_language) VALUES ($1, $2, $3, $4)", + expectedStmt: "UPSERT INTO projections.instances (id, change_date, sequence, default_language) VALUES ($1, $2, $3, $4)", expectedArgs: []interface{}{ "instance-id", anyArg{}, @@ -120,7 +120,7 @@ func TestInstanceProjection_reduces(t *testing.T) { []byte(`{"Step": 1}`), ), instance.SetupStepMapper), }, - reduce: (&IAMProjection{}).reduceSetupEvent, + reduce: (&InstanceProjection{}).reduceSetupEvent, want: wantReduce{ projection: InstanceProjectionTable, aggregateType: eventstore.AggregateType("instance"), @@ -129,7 +129,7 @@ func TestInstanceProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPSERT INTO projections.instance (id, change_date, sequence, setup_started) VALUES ($1, $2, $3, $4)", + expectedStmt: "UPSERT INTO projections.instances (id, change_date, sequence, setup_started) VALUES ($1, $2, $3, $4)", expectedArgs: []interface{}{ "instance-id", anyArg{}, @@ -150,7 +150,7 @@ func TestInstanceProjection_reduces(t *testing.T) { []byte(`{"Step": 1}`), ), instance.SetupStepMapper), }, - reduce: (&IAMProjection{}).reduceSetupEvent, + reduce: (&InstanceProjection{}).reduceSetupEvent, want: wantReduce{ projection: InstanceProjectionTable, aggregateType: eventstore.AggregateType("instance"), @@ -159,7 +159,7 @@ func TestInstanceProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPSERT INTO projections.instance (id, change_date, sequence, setup_done) VALUES ($1, $2, $3, $4)", + expectedStmt: "UPSERT INTO projections.instances (id, change_date, sequence, setup_done) VALUES ($1, $2, $3, $4)", expectedArgs: []interface{}{ "instance-id", anyArg{}, diff --git a/internal/query/projection/key.go b/internal/query/projection/key.go index c9173825a1..7d1e6e7c89 100644 --- a/internal/query/projection/key.go +++ b/internal/query/projection/key.go @@ -59,6 +59,7 @@ func NewKeyProjection(ctx context.Context, config crdb.StatementHandlerConfig, k crdb.NewColumn(KeyColumnUse, crdb.ColumnTypeText, crdb.Default("")), }, crdb.NewPrimaryKey(KeyColumnInstanceID, KeyColumnID), + crdb.WithConstraint(crdb.NewConstraint("id_unique", []string{KeyColumnID})), ), crdb.NewSuffixedTable([]*crdb.Column{ crdb.NewColumn(KeyPrivateColumnID, crdb.ColumnTypeText, crdb.DeleteCascade(KeyColumnID)), diff --git a/internal/query/projection/login_name.go b/internal/query/projection/login_name.go index a83581d2bd..17d7704e75 100644 --- a/internal/query/projection/login_name.go +++ b/internal/query/projection/login_name.go @@ -103,7 +103,7 @@ func NewLoginNameProjection(ctx context.Context, config crdb.StatementHandlerCon }, crdb.NewPrimaryKey(LoginNameUserInstanceIDCol, LoginNameUserIDCol), loginNameUserSuffix, - crdb.NewIndex("ro_idx", []string{LoginNameUserResourceOwnerCol}), + crdb.WithIndex(crdb.NewIndex("ro_idx", []string{LoginNameUserResourceOwnerCol})), ), crdb.NewSuffixedTable([]*crdb.Column{ crdb.NewColumn(LoginNameDomainNameCol, crdb.ColumnTypeText), @@ -122,7 +122,7 @@ func NewLoginNameProjection(ctx context.Context, config crdb.StatementHandlerCon }, crdb.NewPrimaryKey(LoginNamePoliciesInstanceIDCol, LoginNamePoliciesResourceOwnerCol), loginNamePolicySuffix, - crdb.NewIndex("is_default_idx", []string{LoginNamePoliciesResourceOwnerCol, LoginNamePoliciesIsDefaultCol}), + crdb.WithIndex(crdb.NewIndex("is_default_idx", []string{LoginNamePoliciesResourceOwnerCol, LoginNamePoliciesIsDefaultCol})), ), ) p.StatementHandler = crdb.NewStatementHandler(ctx, config) @@ -175,15 +175,15 @@ func (p *LoginNameProjection) reducers() []handler.AggregateReducer { Aggregate: org.AggregateType, EventRedusers: []handler.EventReducer{ { - Event: org.OrgDomainPolicyAddedEventType, + Event: org.DomainPolicyAddedEventType, Reduce: p.reduceOrgIAMPolicyAdded, }, { - Event: org.OrgDomainPolicyChangedEventType, + Event: org.DomainPolicyChangedEventType, Reduce: p.reduceDomainPolicyChanged, }, { - Event: org.OrgDomainPolicyRemovedEventType, + Event: org.DomainPolicyRemovedEventType, Reduce: p.reduceDomainPolicyRemoved, }, { @@ -204,11 +204,11 @@ func (p *LoginNameProjection) reducers() []handler.AggregateReducer { Aggregate: instance.AggregateType, EventRedusers: []handler.EventReducer{ { - Event: instance.InstanceDomainPolicyAddedEventType, + Event: instance.DomainPolicyAddedEventType, Reduce: p.reduceOrgIAMPolicyAdded, }, { - Event: instance.InstanceDomainPolicyChangedEventType, + Event: instance.DomainPolicyChangedEventType, Reduce: p.reduceDomainPolicyChanged, }, }, @@ -300,14 +300,14 @@ func (p *LoginNameProjection) reduceOrgIAMPolicyAdded(event eventstore.Event) (* ) switch e := event.(type) { - case *org.OrgDomainPolicyAddedEvent: + case *org.DomainPolicyAddedEvent: policyEvent = &e.DomainPolicyAddedEvent isDefault = false - case *instance.InstanceDomainPolicyAddedEvent: + case *instance.DomainPolicyAddedEvent: policyEvent = &e.DomainPolicyAddedEvent isDefault = true default: - return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-yCV6S", "reduce.wrong.event.type %v", []eventstore.EventType{org.OrgDomainPolicyAddedEventType, instance.InstanceDomainPolicyAddedEventType}) + return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-yCV6S", "reduce.wrong.event.type %v", []eventstore.EventType{org.DomainPolicyAddedEventType, instance.DomainPolicyAddedEventType}) } return crdb.NewCreateStatement( @@ -326,12 +326,12 @@ func (p *LoginNameProjection) reduceDomainPolicyChanged(event eventstore.Event) var policyEvent *policy.DomainPolicyChangedEvent switch e := event.(type) { - case *org.OrgDomainPolicyChangedEvent: + case *org.DomainPolicyChangedEvent: policyEvent = &e.DomainPolicyChangedEvent - case *instance.InstanceDomainPolicyChangedEvent: + case *instance.DomainPolicyChangedEvent: policyEvent = &e.DomainPolicyChangedEvent default: - return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-ArFDd", "reduce.wrong.event.type %v", []eventstore.EventType{org.OrgDomainPolicyChangedEventType, instance.InstanceDomainPolicyChangedEventType}) + return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-ArFDd", "reduce.wrong.event.type %v", []eventstore.EventType{org.DomainPolicyChangedEventType, instance.DomainPolicyChangedEventType}) } if policyEvent.UserLoginMustBeDomain == nil { @@ -351,9 +351,9 @@ func (p *LoginNameProjection) reduceDomainPolicyChanged(event eventstore.Event) } func (p *LoginNameProjection) reduceDomainPolicyRemoved(event eventstore.Event) (*handler.Statement, error) { - e, ok := event.(*org.OrgDomainPolicyRemovedEvent) + e, ok := event.(*org.DomainPolicyRemovedEvent) if !ok { - return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-ysEeB", "reduce.wrong.event.type %s", org.OrgDomainPolicyRemovedEventType) + return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-ysEeB", "reduce.wrong.event.type %s", org.DomainPolicyRemovedEventType) } return crdb.NewDeleteStatement( diff --git a/internal/query/projection/login_name_test.go b/internal/query/projection/login_name_test.go index 38506eb043..acaa2c5cd2 100644 --- a/internal/query/projection/login_name_test.go +++ b/internal/query/projection/login_name_test.go @@ -209,12 +209,12 @@ func TestLoginNameProjection_reduces(t *testing.T) { name: "org.OrgDomainPolicyAddedEventType", args: args{ event: getEvent(testEvent( - repository.EventType(org.OrgDomainPolicyAddedEventType), + repository.EventType(org.DomainPolicyAddedEventType), user.AggregateType, []byte(`{ "userLoginMustBeDomain": true }`), - ), org.OrgDomainPolicyAddedEventMapper), + ), org.DomainPolicyAddedEventMapper), }, reduce: (&LoginNameProjection{}).reduceOrgIAMPolicyAdded, want: wantReduce{ @@ -241,12 +241,12 @@ func TestLoginNameProjection_reduces(t *testing.T) { name: "org.OrgDomainPolicyChangedEventType", args: args{ event: getEvent(testEvent( - repository.EventType(org.OrgDomainPolicyChangedEventType), + repository.EventType(org.DomainPolicyChangedEventType), user.AggregateType, []byte(`{ "userLoginMustBeDomain": false }`), - ), org.OrgDomainPolicyChangedEventMapper), + ), org.DomainPolicyChangedEventMapper), }, reduce: (&LoginNameProjection{}).reduceDomainPolicyChanged, want: wantReduce{ @@ -271,10 +271,10 @@ func TestLoginNameProjection_reduces(t *testing.T) { name: "org.OrgDomainPolicyChangedEventType no change", args: args{ event: getEvent(testEvent( - repository.EventType(org.OrgDomainPolicyChangedEventType), + repository.EventType(org.DomainPolicyChangedEventType), user.AggregateType, []byte(`{}`), - ), org.OrgDomainPolicyChangedEventMapper), + ), org.DomainPolicyChangedEventMapper), }, reduce: (&LoginNameProjection{}).reduceDomainPolicyChanged, want: wantReduce{ @@ -291,10 +291,10 @@ func TestLoginNameProjection_reduces(t *testing.T) { name: "org.OrgDomainPolicyRemovedEventType", args: args{ event: getEvent(testEvent( - repository.EventType(org.OrgDomainPolicyRemovedEventType), + repository.EventType(org.DomainPolicyRemovedEventType), user.AggregateType, []byte(`{}`), - ), org.OrgDomainPolicyRemovedEventMapper), + ), org.DomainPolicyRemovedEventMapper), }, reduce: (&LoginNameProjection{}).reduceDomainPolicyRemoved, want: wantReduce{ @@ -418,12 +418,12 @@ func TestLoginNameProjection_reduces(t *testing.T) { name: "iam.OrgDomainPolicyAddedEventType", args: args{ event: getEvent(testEvent( - repository.EventType(instance.InstanceDomainPolicyAddedEventType), + repository.EventType(instance.DomainPolicyAddedEventType), user.AggregateType, []byte(`{ "userLoginMustBeDomain": true }`), - ), instance.InstanceDomainPolicyAddedEventMapper), + ), instance.DomainPolicyAddedEventMapper), }, reduce: (&LoginNameProjection{}).reduceOrgIAMPolicyAdded, want: wantReduce{ @@ -450,12 +450,12 @@ func TestLoginNameProjection_reduces(t *testing.T) { name: "iam.OrgDomainPolicyChangedEventType", args: args{ event: getEvent(testEvent( - repository.EventType(instance.InstanceDomainPolicyChangedEventType), + repository.EventType(instance.DomainPolicyChangedEventType), user.AggregateType, []byte(`{ "userLoginMustBeDomain": false }`), - ), instance.InstanceDomainPolicyChangedEventMapper), + ), instance.DomainPolicyChangedEventMapper), }, reduce: (&LoginNameProjection{}).reduceDomainPolicyChanged, want: wantReduce{ @@ -480,10 +480,10 @@ func TestLoginNameProjection_reduces(t *testing.T) { name: "iam.OrgDomainPolicyChangedEventType no change", args: args{ event: getEvent(testEvent( - repository.EventType(instance.InstanceDomainPolicyChangedEventType), + repository.EventType(instance.DomainPolicyChangedEventType), user.AggregateType, []byte(`{}`), - ), instance.InstanceDomainPolicyChangedEventMapper), + ), instance.DomainPolicyChangedEventMapper), }, reduce: (&LoginNameProjection{}).reduceDomainPolicyChanged, want: wantReduce{ diff --git a/internal/query/projection/org.go b/internal/query/projection/org.go index 535ed357c1..8eb82e2fd7 100644 --- a/internal/query/projection/org.go +++ b/internal/query/projection/org.go @@ -46,8 +46,8 @@ func NewOrgProjection(ctx context.Context, config crdb.StatementHandlerConfig) * crdb.NewColumn(OrgColumnDomain, crdb.ColumnTypeText), }, crdb.NewPrimaryKey(OrgColumnInstanceID, OrgColumnID), - crdb.NewIndex("domain_idx", []string{OrgColumnDomain}), - crdb.NewIndex("name_idx", []string{OrgColumnName}), + crdb.WithIndex(crdb.NewIndex("domain_idx", []string{OrgColumnDomain})), + crdb.WithIndex(crdb.NewIndex("name_idx", []string{OrgColumnName})), ), ) p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/org_member.go b/internal/query/projection/org_member.go index 27a4764af9..fbec72efdc 100644 --- a/internal/query/projection/org_member.go +++ b/internal/query/projection/org_member.go @@ -28,7 +28,7 @@ func NewOrgMemberProjection(ctx context.Context, config crdb.StatementHandlerCon crdb.NewTable( append(memberColumns, crdb.NewColumn(OrgMemberOrgIDCol, crdb.ColumnTypeText)), crdb.NewPrimaryKey(MemberInstanceID, OrgMemberOrgIDCol, MemberUserIDCol), - crdb.NewIndex("user_idx", []string{MemberUserIDCol}), + crdb.WithIndex(crdb.NewIndex("user_idx", []string{MemberUserIDCol})), ), ) p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/project.go b/internal/query/projection/project.go index de97b35be4..b3d70b637d 100644 --- a/internal/query/projection/project.go +++ b/internal/query/projection/project.go @@ -54,7 +54,7 @@ func NewProjectProjection(ctx context.Context, config crdb.StatementHandlerConfi crdb.NewColumn(ProjectColumnCreator, crdb.ColumnTypeText), }, crdb.NewPrimaryKey(ProjectColumnInstanceID, ProjectColumnID), - crdb.NewIndex("ro_idx", []string{ProjectColumnResourceOwner}), + crdb.WithIndex(crdb.NewIndex("ro_idx", []string{ProjectColumnResourceOwner})), ), ) p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/project_grant.go b/internal/query/projection/project_grant.go index 2bcd202efe..901e5670a3 100644 --- a/internal/query/projection/project_grant.go +++ b/internal/query/projection/project_grant.go @@ -52,8 +52,8 @@ func NewProjectGrantProjection(ctx context.Context, config crdb.StatementHandler crdb.NewColumn(ProjectGrantColumnCreator, crdb.ColumnTypeText), }, crdb.NewPrimaryKey(ProjectGrantColumnInstanceID, ProjectGrantColumnGrantID), - crdb.NewIndex("ro_idx", []string{ProjectGrantColumnResourceOwner}), - crdb.NewIndex("granted_org_idx", []string{ProjectGrantColumnGrantedOrgID}), + crdb.WithIndex(crdb.NewIndex("ro_idx", []string{ProjectGrantColumnResourceOwner})), + crdb.WithIndex(crdb.NewIndex("granted_org_idx", []string{ProjectGrantColumnGrantedOrgID})), ), ) p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/project_grant_member.go b/internal/query/projection/project_grant_member.go index dec7f37066..1327f612de 100644 --- a/internal/query/projection/project_grant_member.go +++ b/internal/query/projection/project_grant_member.go @@ -34,7 +34,7 @@ func NewProjectGrantMemberProjection(ctx context.Context, config crdb.StatementH crdb.NewColumn(ProjectGrantMemberGrantIDCol, crdb.ColumnTypeText), ), crdb.NewPrimaryKey(MemberInstanceID, ProjectGrantMemberProjectIDCol, ProjectGrantMemberGrantIDCol, MemberUserIDCol), - crdb.NewIndex("user_idx", []string{MemberUserIDCol}), + crdb.WithIndex(crdb.NewIndex("user_idx", []string{MemberUserIDCol})), ), ) diff --git a/internal/query/projection/project_member.go b/internal/query/projection/project_member.go index 83431f018f..c8915c07ea 100644 --- a/internal/query/projection/project_member.go +++ b/internal/query/projection/project_member.go @@ -32,7 +32,7 @@ func NewProjectMemberProjection(ctx context.Context, config crdb.StatementHandle crdb.NewColumn(ProjectMemberProjectIDCol, crdb.ColumnTypeText), ), crdb.NewPrimaryKey(MemberInstanceID, ProjectMemberProjectIDCol, MemberUserIDCol), - crdb.NewIndex("user_idx", []string{MemberUserIDCol}), + crdb.WithIndex(crdb.NewIndex("user_idx", []string{MemberUserIDCol})), ), ) diff --git a/internal/query/projection/projection.go b/internal/query/projection/projection.go index 7c6945d298..bdcb96e614 100644 --- a/internal/query/projection/projection.go +++ b/internal/query/projection/projection.go @@ -3,7 +3,6 @@ package projection import ( "context" "database/sql" - "fmt" "time" "github.com/caos/zitadel/internal/crypto" @@ -35,7 +34,6 @@ func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, co BulkLimit: config.BulkLimit, } - now := time.Now() NewOrgProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["orgs"])) NewActionProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["actions"])) NewFlowProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["flows"])) @@ -69,14 +67,13 @@ func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, co NewUserGrantProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["user_grants"])) NewUserMetadataProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["user_metadata"])) NewUserAuthMethodProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["user_auth_method"])) - NewIAMProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["iam"])) + NewInstanceProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["instances"])) NewSecretGeneratorProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["secret_generators"])) NewSMTPConfigProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["smtp_configs"])) NewSMSConfigProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["sms_config"])) NewOIDCSettingsProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["oidc_settings"])) NewDebugNotificationProviderProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["debug_notification_provider"])) NewKeyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["keys"]), keyEncryptionAlgorithm, keyChan) - fmt.Println(time.Now().Sub(now)) return nil } diff --git a/internal/query/projection/user.go b/internal/query/projection/user.go index 342810603d..3c453974e9 100644 --- a/internal/query/projection/user.go +++ b/internal/query/projection/user.go @@ -75,8 +75,9 @@ func NewUserProjection(ctx context.Context, config crdb.StatementHandlerConfig) crdb.NewColumn(UserTypeCol, crdb.ColumnTypeEnum), }, crdb.NewPrimaryKey(UserInstanceIDCol, UserIDCol), - crdb.NewIndex("username_idx", []string{UserUsernameCol}), - crdb.NewIndex("ro_idx", []string{UserResourceOwnerCol}), + crdb.WithIndex(crdb.NewIndex("username_idx", []string{UserUsernameCol})), + crdb.WithIndex(crdb.NewIndex("ro_idx", []string{UserResourceOwnerCol})), + crdb.WithConstraint(crdb.NewConstraint("id_unique", []string{UserIDCol})), ), crdb.NewSuffixedTable([]*crdb.Column{ crdb.NewColumn(HumanUserIDCol, crdb.ColumnTypeText, crdb.DeleteCascade(UserIDCol)), diff --git a/internal/query/projection/user_auth_method.go b/internal/query/projection/user_auth_method.go index 95765a177f..bee4ff959d 100644 --- a/internal/query/projection/user_auth_method.go +++ b/internal/query/projection/user_auth_method.go @@ -48,7 +48,7 @@ func NewUserAuthMethodProjection(ctx context.Context, config crdb.StatementHandl crdb.NewColumn(UserAuthMethodNameCol, crdb.ColumnTypeText), }, crdb.NewPrimaryKey(UserAuthMethodInstanceIDCol, UserAuthMethodUserIDCol, UserAuthMethodTypeCol, UserAuthMethodTokenIDCol), - crdb.NewIndex("ro_idx", []string{UserAuthMethodResourceOwnerCol}), + crdb.WithIndex(crdb.NewIndex("ro_idx", []string{UserAuthMethodResourceOwnerCol})), ), ) p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/user_grant.go b/internal/query/projection/user_grant.go index 3feee4ca2a..5ffa607f37 100644 --- a/internal/query/projection/user_grant.go +++ b/internal/query/projection/user_grant.go @@ -54,8 +54,8 @@ func NewUserGrantProjection(ctx context.Context, config crdb.StatementHandlerCon crdb.NewColumn(UserGrantRoles, crdb.ColumnTypeTextArray, crdb.Nullable()), }, crdb.NewPrimaryKey(UserGrantInstanceID, UserGrantID), - crdb.NewIndex("user_idx", []string{UserGrantUserID}), - crdb.NewIndex("ro_idx", []string{UserGrantResourceOwner}), + crdb.WithIndex(crdb.NewIndex("user_idx", []string{UserGrantUserID})), + crdb.WithIndex(crdb.NewIndex("ro_idx", []string{UserGrantResourceOwner})), ), ) diff --git a/internal/query/projection/user_metadata.go b/internal/query/projection/user_metadata.go index 42a991663d..be750c85fc 100644 --- a/internal/query/projection/user_metadata.go +++ b/internal/query/projection/user_metadata.go @@ -43,7 +43,7 @@ func NewUserMetadataProjection(ctx context.Context, config crdb.StatementHandler crdb.NewColumn(UserMetadataColumnValue, crdb.ColumnTypeBytes, crdb.Nullable()), }, crdb.NewPrimaryKey(UserMetadataColumnInstanceID, UserMetadataColumnUserID), - crdb.NewIndex("ro_idx", []string{UserGrantResourceOwner}), + crdb.WithIndex(crdb.NewIndex("ro_idx", []string{UserGrantResourceOwner})), ), ) diff --git a/internal/query/projection/user_personal_access_token.go b/internal/query/projection/user_personal_access_token.go index a6f28d3a3a..5c06b8b022 100644 --- a/internal/query/projection/user_personal_access_token.go +++ b/internal/query/projection/user_personal_access_token.go @@ -47,8 +47,8 @@ func NewPersonalAccessTokenProjection(ctx context.Context, config crdb.Statement crdb.NewColumn(PersonalAccessTokenColumnScopes, crdb.ColumnTypeTextArray, crdb.Nullable()), }, crdb.NewPrimaryKey(PersonalAccessTokenColumnInstanceID, PersonalAccessTokenColumnID), - crdb.NewIndex("user_idx", []string{PersonalAccessTokenColumnUserID}), - crdb.NewIndex("ro_idx", []string{PersonalAccessTokenColumnResourceOwner}), + crdb.WithIndex(crdb.NewIndex("user_idx", []string{PersonalAccessTokenColumnUserID})), + crdb.WithIndex(crdb.NewIndex("ro_idx", []string{PersonalAccessTokenColumnResourceOwner})), ), ) diff --git a/internal/repository/instance/eventstore.go b/internal/repository/instance/eventstore.go index e7cd2cc740..5ac37b6814 100644 --- a/internal/repository/instance/eventstore.go +++ b/internal/repository/instance/eventstore.go @@ -47,8 +47,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) { RegisterFilterEventMapper(LabelPolicyAssetsRemovedEventType, LabelPolicyAssetsRemovedEventMapper). RegisterFilterEventMapper(LoginPolicyAddedEventType, LoginPolicyAddedEventMapper). RegisterFilterEventMapper(LoginPolicyChangedEventType, LoginPolicyChangedEventMapper). - RegisterFilterEventMapper(InstanceDomainPolicyAddedEventType, InstanceDomainPolicyAddedEventMapper). - RegisterFilterEventMapper(InstanceDomainPolicyChangedEventType, InstanceDomainPolicyChangedEventMapper). + RegisterFilterEventMapper(DomainPolicyAddedEventType, DomainPolicyAddedEventMapper). + RegisterFilterEventMapper(DomainPolicyChangedEventType, DomainPolicyChangedEventMapper). RegisterFilterEventMapper(PasswordAgePolicyAddedEventType, PasswordAgePolicyAddedEventMapper). RegisterFilterEventMapper(PasswordAgePolicyChangedEventType, PasswordAgePolicyChangedEventMapper). RegisterFilterEventMapper(PasswordComplexityPolicyAddedEventType, PasswordComplexityPolicyAddedEventMapper). diff --git a/internal/repository/instance/policy_domain.go b/internal/repository/instance/policy_domain.go index ca5732e8b9..a3f6abcc20 100644 --- a/internal/repository/instance/policy_domain.go +++ b/internal/repository/instance/policy_domain.go @@ -10,66 +10,66 @@ import ( ) var ( - InstanceDomainPolicyAddedEventType = instanceEventTypePrefix + policy.DomainPolicyAddedEventType - InstanceDomainPolicyChangedEventType = instanceEventTypePrefix + policy.DomainPolicyChangedEventType + DomainPolicyAddedEventType = instanceEventTypePrefix + policy.DomainPolicyAddedEventType + DomainPolicyChangedEventType = instanceEventTypePrefix + policy.DomainPolicyChangedEventType ) -type InstanceDomainPolicyAddedEvent struct { +type DomainPolicyAddedEvent struct { policy.DomainPolicyAddedEvent } -func NewInstnaceDomainPolicyAddedEvent( +func NewDomainPolicyAddedEvent( ctx context.Context, aggregate *eventstore.Aggregate, userLoginMustBeDomain bool, -) *InstanceDomainPolicyAddedEvent { - return &InstanceDomainPolicyAddedEvent{ +) *DomainPolicyAddedEvent { + return &DomainPolicyAddedEvent{ DomainPolicyAddedEvent: *policy.NewDomainPolicyAddedEvent( eventstore.NewBaseEventForPush( ctx, aggregate, - InstanceDomainPolicyAddedEventType), + DomainPolicyAddedEventType), userLoginMustBeDomain, ), } } -func InstanceDomainPolicyAddedEventMapper(event *repository.Event) (eventstore.Event, error) { +func DomainPolicyAddedEventMapper(event *repository.Event) (eventstore.Event, error) { e, err := policy.DomainPolicyAddedEventMapper(event) if err != nil { return nil, err } - return &InstanceDomainPolicyAddedEvent{DomainPolicyAddedEvent: *e.(*policy.DomainPolicyAddedEvent)}, nil + return &DomainPolicyAddedEvent{DomainPolicyAddedEvent: *e.(*policy.DomainPolicyAddedEvent)}, nil } -type InstanceDomainPolicyChangedEvent struct { +type DomainPolicyChangedEvent struct { policy.DomainPolicyChangedEvent } -func NewInstanceDomainPolicyChangedEvent( +func NewDomainPolicyChangedEvent( ctx context.Context, aggregate *eventstore.Aggregate, changes []policy.OrgPolicyChanges, -) (*InstanceDomainPolicyChangedEvent, error) { +) (*DomainPolicyChangedEvent, error) { changedEvent, err := policy.NewDomainPolicyChangedEvent( eventstore.NewBaseEventForPush( ctx, aggregate, - InstanceDomainPolicyChangedEventType), + DomainPolicyChangedEventType), changes, ) if err != nil { return nil, err } - return &InstanceDomainPolicyChangedEvent{DomainPolicyChangedEvent: *changedEvent}, nil + return &DomainPolicyChangedEvent{DomainPolicyChangedEvent: *changedEvent}, nil } -func InstanceDomainPolicyChangedEventMapper(event *repository.Event) (eventstore.Event, error) { +func DomainPolicyChangedEventMapper(event *repository.Event) (eventstore.Event, error) { e, err := policy.DomainPolicyChangedEventMapper(event) if err != nil { return nil, err } - return &InstanceDomainPolicyChangedEvent{DomainPolicyChangedEvent: *e.(*policy.DomainPolicyChangedEvent)}, nil + return &DomainPolicyChangedEvent{DomainPolicyChangedEvent: *e.(*policy.DomainPolicyChangedEvent)}, nil } diff --git a/internal/repository/org/eventstore.go b/internal/repository/org/eventstore.go index d0ac20ae4d..7aa9b43d46 100644 --- a/internal/repository/org/eventstore.go +++ b/internal/repository/org/eventstore.go @@ -44,9 +44,9 @@ func RegisterEventMappers(es *eventstore.Eventstore) { RegisterFilterEventMapper(LoginPolicyIDPProviderAddedEventType, IdentityProviderAddedEventMapper). RegisterFilterEventMapper(LoginPolicyIDPProviderRemovedEventType, IdentityProviderRemovedEventMapper). RegisterFilterEventMapper(LoginPolicyIDPProviderCascadeRemovedEventType, IdentityProviderCascadeRemovedEventMapper). - RegisterFilterEventMapper(OrgDomainPolicyAddedEventType, OrgDomainPolicyAddedEventMapper). - RegisterFilterEventMapper(OrgDomainPolicyChangedEventType, OrgDomainPolicyChangedEventMapper). - RegisterFilterEventMapper(OrgDomainPolicyRemovedEventType, OrgDomainPolicyRemovedEventMapper). + RegisterFilterEventMapper(DomainPolicyAddedEventType, DomainPolicyAddedEventMapper). + RegisterFilterEventMapper(DomainPolicyChangedEventType, DomainPolicyChangedEventMapper). + RegisterFilterEventMapper(DomainPolicyRemovedEventType, DomainPolicyRemovedEventMapper). RegisterFilterEventMapper(PasswordAgePolicyAddedEventType, PasswordAgePolicyAddedEventMapper). RegisterFilterEventMapper(PasswordAgePolicyChangedEventType, PasswordAgePolicyChangedEventMapper). RegisterFilterEventMapper(PasswordAgePolicyRemovedEventType, PasswordAgePolicyRemovedEventMapper). diff --git a/internal/repository/org/policy_domain.go b/internal/repository/org/policy_domain.go new file mode 100644 index 0000000000..9a805275b0 --- /dev/null +++ b/internal/repository/org/policy_domain.go @@ -0,0 +1,103 @@ +package org + +import ( + "context" + + "github.com/caos/zitadel/internal/eventstore" + + "github.com/caos/zitadel/internal/eventstore/repository" + "github.com/caos/zitadel/internal/repository/policy" +) + +var ( + DomainPolicyAddedEventType = orgEventTypePrefix + policy.DomainPolicyAddedEventType + DomainPolicyChangedEventType = orgEventTypePrefix + policy.DomainPolicyChangedEventType + DomainPolicyRemovedEventType = orgEventTypePrefix + policy.DomainPolicyRemovedEventType +) + +type DomainPolicyAddedEvent struct { + policy.DomainPolicyAddedEvent +} + +func NewDomainPolicyAddedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + userLoginMustBeDomain bool, +) *DomainPolicyAddedEvent { + return &DomainPolicyAddedEvent{ + DomainPolicyAddedEvent: *policy.NewDomainPolicyAddedEvent( + eventstore.NewBaseEventForPush( + ctx, + aggregate, + DomainPolicyAddedEventType), + userLoginMustBeDomain, + ), + } +} + +func DomainPolicyAddedEventMapper(event *repository.Event) (eventstore.Event, error) { + e, err := policy.DomainPolicyAddedEventMapper(event) + if err != nil { + return nil, err + } + + return &DomainPolicyAddedEvent{DomainPolicyAddedEvent: *e.(*policy.DomainPolicyAddedEvent)}, nil +} + +type DomainPolicyChangedEvent struct { + policy.DomainPolicyChangedEvent +} + +func NewDomainPolicyChangedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + changes []policy.OrgPolicyChanges, +) (*DomainPolicyChangedEvent, error) { + changedEvent, err := policy.NewDomainPolicyChangedEvent( + eventstore.NewBaseEventForPush( + ctx, + aggregate, + DomainPolicyChangedEventType), + changes, + ) + if err != nil { + return nil, err + } + return &DomainPolicyChangedEvent{DomainPolicyChangedEvent: *changedEvent}, nil +} + +func DomainPolicyChangedEventMapper(event *repository.Event) (eventstore.Event, error) { + e, err := policy.DomainPolicyChangedEventMapper(event) + if err != nil { + return nil, err + } + + return &DomainPolicyChangedEvent{DomainPolicyChangedEvent: *e.(*policy.DomainPolicyChangedEvent)}, nil +} + +type DomainPolicyRemovedEvent struct { + policy.DomainPolicyRemovedEvent +} + +func NewDomainPolicyRemovedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, +) *DomainPolicyRemovedEvent { + return &DomainPolicyRemovedEvent{ + DomainPolicyRemovedEvent: *policy.NewDomainPolicyRemovedEvent( + eventstore.NewBaseEventForPush( + ctx, + aggregate, + DomainPolicyRemovedEventType), + ), + } +} + +func DomainPolicyRemovedEventMapper(event *repository.Event) (eventstore.Event, error) { + e, err := policy.DomainPolicyRemovedEventMapper(event) + if err != nil { + return nil, err + } + + return &DomainPolicyRemovedEvent{DomainPolicyRemovedEvent: *e.(*policy.DomainPolicyRemovedEvent)}, nil +} diff --git a/internal/repository/org/policy_org_domain.go b/internal/repository/org/policy_org_domain.go deleted file mode 100644 index cdd249a3d6..0000000000 --- a/internal/repository/org/policy_org_domain.go +++ /dev/null @@ -1,103 +0,0 @@ -package org - -import ( - "context" - - "github.com/caos/zitadel/internal/eventstore" - - "github.com/caos/zitadel/internal/eventstore/repository" - "github.com/caos/zitadel/internal/repository/policy" -) - -var ( - OrgDomainPolicyAddedEventType = orgEventTypePrefix + policy.DomainPolicyAddedEventType - OrgDomainPolicyChangedEventType = orgEventTypePrefix + policy.DomainPolicyChangedEventType - OrgDomainPolicyRemovedEventType = orgEventTypePrefix + policy.DomainPolicyRemovedEventType -) - -type OrgDomainPolicyAddedEvent struct { - policy.DomainPolicyAddedEvent -} - -func NewOrgDomainPolicyAddedEvent( - ctx context.Context, - aggregate *eventstore.Aggregate, - userLoginMustBeDomain bool, -) *OrgDomainPolicyAddedEvent { - return &OrgDomainPolicyAddedEvent{ - DomainPolicyAddedEvent: *policy.NewDomainPolicyAddedEvent( - eventstore.NewBaseEventForPush( - ctx, - aggregate, - OrgDomainPolicyAddedEventType), - userLoginMustBeDomain, - ), - } -} - -func OrgDomainPolicyAddedEventMapper(event *repository.Event) (eventstore.Event, error) { - e, err := policy.DomainPolicyAddedEventMapper(event) - if err != nil { - return nil, err - } - - return &OrgDomainPolicyAddedEvent{DomainPolicyAddedEvent: *e.(*policy.DomainPolicyAddedEvent)}, nil -} - -type OrgDomainPolicyChangedEvent struct { - policy.DomainPolicyChangedEvent -} - -func NewOrgDomainPolicyChangedEvent( - ctx context.Context, - aggregate *eventstore.Aggregate, - changes []policy.OrgPolicyChanges, -) (*OrgDomainPolicyChangedEvent, error) { - changedEvent, err := policy.NewDomainPolicyChangedEvent( - eventstore.NewBaseEventForPush( - ctx, - aggregate, - OrgDomainPolicyChangedEventType), - changes, - ) - if err != nil { - return nil, err - } - return &OrgDomainPolicyChangedEvent{DomainPolicyChangedEvent: *changedEvent}, nil -} - -func OrgDomainPolicyChangedEventMapper(event *repository.Event) (eventstore.Event, error) { - e, err := policy.DomainPolicyChangedEventMapper(event) - if err != nil { - return nil, err - } - - return &OrgDomainPolicyChangedEvent{DomainPolicyChangedEvent: *e.(*policy.DomainPolicyChangedEvent)}, nil -} - -type OrgDomainPolicyRemovedEvent struct { - policy.DomainPolicyRemovedEvent -} - -func NewOrgDomainPolicyRemovedEvent( - ctx context.Context, - aggregate *eventstore.Aggregate, -) *OrgDomainPolicyRemovedEvent { - return &OrgDomainPolicyRemovedEvent{ - DomainPolicyRemovedEvent: *policy.NewDomainPolicyRemovedEvent( - eventstore.NewBaseEventForPush( - ctx, - aggregate, - OrgDomainPolicyRemovedEventType), - ), - } -} - -func OrgDomainPolicyRemovedEventMapper(event *repository.Event) (eventstore.Event, error) { - e, err := policy.DomainPolicyRemovedEventMapper(event) - if err != nil { - return nil, err - } - - return &OrgDomainPolicyRemovedEvent{DomainPolicyRemovedEvent: *e.(*policy.DomainPolicyRemovedEvent)}, nil -} diff --git a/internal/setup/config.go b/internal/setup/config.go deleted file mode 100644 index 83da2438fa..0000000000 --- a/internal/setup/config.go +++ /dev/null @@ -1,64 +0,0 @@ -package setup - -import ( - "github.com/caos/zitadel/internal/command" - "github.com/caos/zitadel/internal/domain" -) - -type IAMSetUp struct { - Step1 *command.Step1 - Step2 *command.Step2 - Step3 *command.Step3 - Step4 *command.Step4 - Step5 *command.Step5 - Step6 *command.Step6 - Step7 *command.Step7 - Step8 *command.Step8 - Step9 *command.Step9 - Step10 *command.Step10 - Step11 *command.Step11 - Step12 *command.Step12 - Step13 *command.Step13 - Step14 *command.Step14 - Step15 *command.Step15 - Step16 *command.Step16 - Step17 *command.Step17 - Step18 *command.Step18 - Step19 *command.Step19 - Step20 *command.Step20 - Step21 *command.Step21 -} - -func (setup *IAMSetUp) Steps(currentDone domain.Step) ([]command.Step, error) { - steps := make([]command.Step, 0) - - for _, step := range []command.Step{ - setup.Step1, - setup.Step2, - setup.Step3, - setup.Step4, - setup.Step5, - setup.Step6, - setup.Step7, - setup.Step8, - setup.Step9, - setup.Step10, - setup.Step11, - setup.Step12, - setup.Step13, - setup.Step14, - setup.Step15, - setup.Step16, - setup.Step17, - setup.Step18, - setup.Step19, - setup.Step20, - setup.Step21, - } { - if step.Step() <= currentDone { - continue - } - steps = append(steps, step) - } - return steps, nil -} diff --git a/internal/setup/setup.go b/internal/setup/setup.go deleted file mode 100644 index 3bf3c9c775..0000000000 --- a/internal/setup/setup.go +++ /dev/null @@ -1,42 +0,0 @@ -package setup - -import ( - "context" - - "github.com/caos/logging" - - "github.com/caos/zitadel/internal/command" - "github.com/caos/zitadel/internal/domain" - caos_errs "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/v1/models" -) - -func Execute(ctx context.Context, setUpConfig IAMSetUp, iamID string, commands *command.Commands) error { - logging.Log("SETUP-JAK2q").Info("starting setup") - - iam, err := commands.GetInstance(ctx) - if err != nil && !caos_errs.IsNotFound(err) { - return err - } - if iam != nil && (iam.SetUpDone == domain.StepCount-1 || iam.SetUpStarted != iam.SetUpDone) { - logging.Log("SETUP-VA2k1").Info("all steps done") - return nil - } - - if iam == nil { - iam = &domain.Instance{ObjectRoot: models.ObjectRoot{AggregateID: iamID}} - } - - steps, err := setUpConfig.Steps(iam.SetUpDone) - if err != nil || len(steps) == 0 { - return err - } - - err = commands.ExecuteSetupSteps(ctx, steps) - if err != nil { - return err - } - - logging.Log("SETUP-ds31h").Info("setup done") - return nil -} diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto index 415dcec397..22ea2b1f48 100644 --- a/proto/zitadel/management.proto +++ b/proto/zitadel/management.proto @@ -4387,7 +4387,7 @@ message GetFeaturesResponse { message GetOrgIAMPolicyRequest {} message GetOrgIAMPolicyResponse { - zitadel.policy.v1.DomainPolicy policy = 1; + zitadel.policy.v1.OrgIAMPolicy policy = 1; } message GetDomainPolicyRequest {}