diff --git a/go.sum b/go.sum index 7e54c9f28f..717321d282 100644 --- a/go.sum +++ b/go.sum @@ -155,6 +155,7 @@ github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= @@ -308,6 +309,7 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -442,6 +444,7 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -522,6 +525,7 @@ github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= @@ -565,6 +569,7 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= +github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw= github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -574,6 +579,7 @@ github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/manifoldco/promptui v0.7.0 h1:3l11YT8tm9MnwGFQ4kETwkzpAwY2Jt9lCrumCUW4+z4= github.com/manifoldco/promptui v0.7.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= @@ -603,6 +609,7 @@ github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1f github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -615,6 +622,7 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= @@ -759,6 +767,7 @@ github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTd github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v0.0.7 h1:FfTH+vuMXOas8jmfb5/M7dzEYx7LpcLb7a0LPe34uOU= github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -907,6 +916,7 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1108,12 +1118,14 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201030143252-cf7a54d06671/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201103235415-b653051172e4 h1:Qe0EMgvVYb6tmJhJHljCj3gS96hvSTkGNaIzp/ivq10= golang.org/x/tools v0.0.0-20201103235415-b653051172e4/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= diff --git a/internal/admin/repository/eventsourcing/eventstore/iam.go b/internal/admin/repository/eventsourcing/eventstore/iam.go index 98878b6099..9524c808aa 100644 --- a/internal/admin/repository/eventsourcing/eventstore/iam.go +++ b/internal/admin/repository/eventsourcing/eventstore/iam.go @@ -2,6 +2,7 @@ package eventstore import ( "context" + "github.com/caos/zitadel/internal/user/repository/view/model" "strings" caos_errs "github.com/caos/zitadel/internal/errors" @@ -111,6 +112,39 @@ func (repo *IAMRepository) RemoveIDPConfig(ctx context.Context, idpConfigID stri return es_sdk.PushAggregates(ctx, repo.Eventstore.PushAggregates, nil, aggregates...) } +func (repo *IAMRepository) IDPProvidersByIDPConfigID(ctx context.Context, idpConfigID string) ([]*iam_model.IDPProviderView, error) { + providers, err := repo.View.IDPProvidersByIdpConfigID(idpConfigID) + if err != nil { + return nil, err + } + return iam_es_model.IDPProviderViewsToModel(providers), nil +} + +func (repo *IAMRepository) ExternalIDPsByIDPConfigID(ctx context.Context, idpConfigID string) ([]*usr_model.ExternalIDPView, error) { + externalIDPs, err := repo.View.ExternalIDPsByIDPConfigID(idpConfigID) + if err != nil { + return nil, err + } + return model.ExternalIDPViewsToModel(externalIDPs), nil +} + +func (repo *IAMRepository) ExternalIDPsByIDPConfigIDFromDefaultPolicy(ctx context.Context, idpConfigID string) ([]*usr_model.ExternalIDPView, error) { + policies, err := repo.View.AllDefaultLoginPolicies() + if err != nil { + return nil, err + } + resourceOwners := make([]string, len(policies)) + for i, policy := range policies { + resourceOwners[i] = policy.AggregateID + } + + externalIDPs, err := repo.View.ExternalIDPsByIDPConfigIDAndResourceOwners(idpConfigID, resourceOwners) + if err != nil { + return nil, err + } + return model.ExternalIDPViewsToModel(externalIDPs), nil +} + func (repo *IAMRepository) SearchIDPConfigs(ctx context.Context, request *iam_model.IDPConfigSearchRequest) (*iam_model.IDPConfigSearchResponse, error) { request.EnsureLimit(repo.SearchLimit) sequence, err := repo.View.GetLatestIDPConfigSequence() diff --git a/internal/admin/repository/eventsourcing/handler/login_policy.go b/internal/admin/repository/eventsourcing/handler/login_policy.go index aaf1f5b197..90c6d15aba 100644 --- a/internal/admin/repository/eventsourcing/handler/login_policy.go +++ b/internal/admin/repository/eventsourcing/handler/login_policy.go @@ -1,14 +1,19 @@ package handler import ( + "context" "github.com/caos/logging" + caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore/models" es_models "github.com/caos/zitadel/internal/eventstore/models" "github.com/caos/zitadel/internal/eventstore/query" "github.com/caos/zitadel/internal/eventstore/spooler" - "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" + "github.com/caos/zitadel/internal/iam/repository/eventsourcing" + iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" iam_model "github.com/caos/zitadel/internal/iam/repository/view/model" + model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" + "github.com/caos/zitadel/internal/v2/domain" ) const ( @@ -44,7 +49,7 @@ func (p *LoginPolicy) ViewModel() string { } func (p *LoginPolicy) AggregateTypes() []models.AggregateType { - return []models.AggregateType{model.IAMAggregate} + return []models.AggregateType{iam_es_model.IAMAggregate, model.OrgAggregate} } func (p *LoginPolicy) EventQuery() (*models.SearchQuery, error) { @@ -67,7 +72,7 @@ func (p *LoginPolicy) CurrentSequence() (uint64, error) { func (p *LoginPolicy) Reduce(event *models.Event) (err error) { switch event.AggregateType { - case model.IAMAggregate: + case model.OrgAggregate, iam_es_model.IAMAggregate: err = p.processLoginPolicy(event) } return err @@ -76,8 +81,31 @@ func (p *LoginPolicy) Reduce(event *models.Event) (err error) { func (p *LoginPolicy) processLoginPolicy(event *models.Event) (err error) { policy := new(iam_model.LoginPolicyView) switch event.Type { - case model.LoginPolicyAdded: + case model.OrgAdded: + policy, err = p.getDefaultLoginPolicy() + if err != nil { + return err + } + policy.AggregateID = event.AggregateID + policy.Default = true + case iam_es_model.LoginPolicyAdded, model.LoginPolicyAdded: err = policy.AppendEvent(event) + case iam_es_model.LoginPolicyChanged, + iam_es_model.LoginPolicySecondFactorAdded, + iam_es_model.LoginPolicySecondFactorRemoved, + iam_es_model.LoginPolicyMultiFactorAdded, + iam_es_model.LoginPolicyMultiFactorRemoved: + policies, err := p.view.AllDefaultLoginPolicies() + if err != nil { + return err + } + for _, policy := range policies { + err = policy.AppendEvent(event) + if err != nil { + return err + } + } + return p.view.PutLoginPolicies(policies, event) case model.LoginPolicyChanged, model.LoginPolicySecondFactorAdded, model.LoginPolicySecondFactorRemoved, @@ -88,6 +116,13 @@ func (p *LoginPolicy) processLoginPolicy(event *models.Event) (err error) { return err } err = policy.AppendEvent(event) + case model.LoginPolicyRemoved: + policy, err = p.getDefaultLoginPolicy() + if err != nil { + return err + } + policy.AggregateID = event.AggregateID + policy.Default = true default: return p.view.ProcessedLoginPolicySequence(event) } @@ -105,3 +140,33 @@ func (p *LoginPolicy) OnError(event *models.Event, err error) error { func (p *LoginPolicy) OnSuccess() error { return spooler.HandleSuccess(p.view.UpdateLoginPolicySpoolerRunTimestamp) } + +func (p *LoginPolicy) getDefaultLoginPolicy() (*iam_model.LoginPolicyView, error) { + policy, policyErr := p.view.LoginPolicyByAggregateID(domain.IAMID) + if policyErr != nil && !caos_errs.IsNotFound(policyErr) { + return nil, policyErr + } + if policy == nil { + policy = &iam_model.LoginPolicyView{} + } + events, err := p.getIAMEvents(policy.Sequence) + if err != nil { + return policy, policyErr + } + policyCopy := *policy + for _, event := range events { + if err := policyCopy.AppendEvent(event); err != nil { + return policy, nil + } + } + return &policyCopy, nil +} + +func (p *LoginPolicy) getIAMEvents(sequence uint64) ([]*models.Event, error) { + query, err := eventsourcing.IAMByIDQuery(domain.IAMID, sequence) + if err != nil { + return nil, err + } + + return p.es.FilterEvents(context.Background(), query) +} diff --git a/internal/admin/repository/eventsourcing/view/external_idps.go b/internal/admin/repository/eventsourcing/view/external_idps.go index 6b9a239b23..f211f1006b 100644 --- a/internal/admin/repository/eventsourcing/view/external_idps.go +++ b/internal/admin/repository/eventsourcing/view/external_idps.go @@ -25,6 +25,10 @@ func (v *View) ExternalIDPsByIDPConfigID(idpConfigID string) ([]*model.ExternalI return view.ExternalIDPsByIDPConfigID(v.Db, externalIDPTable, idpConfigID) } +func (v *View) ExternalIDPsByIDPConfigIDAndResourceOwners(idpConfigID string, resourceOwners []string) ([]*model.ExternalIDPView, error) { + return view.ExternalIDPsByIDPConfigIDAndResourceOwners(v.Db, externalIDPTable, idpConfigID, resourceOwners) +} + func (v *View) ExternalIDPsByUserID(userID string) ([]*model.ExternalIDPView, error) { return view.ExternalIDPsByUserID(v.Db, externalIDPTable, userID) } diff --git a/internal/admin/repository/eventsourcing/view/login_policies.go b/internal/admin/repository/eventsourcing/view/login_policies.go index 8785e8df24..f4eb8914b8 100644 --- a/internal/admin/repository/eventsourcing/view/login_policies.go +++ b/internal/admin/repository/eventsourcing/view/login_policies.go @@ -12,6 +12,10 @@ const ( loginPolicyTable = "adminapi.login_policies" ) +func (v *View) AllDefaultLoginPolicies() ([]*model.LoginPolicyView, error) { + return view.GetDefaultLoginPolicies(v.Db, loginPolicyTable) +} + func (v *View) LoginPolicyByAggregateID(aggregateID string) (*model.LoginPolicyView, error) { return view.GetLoginPolicyByAggregateID(v.Db, loginPolicyTable, aggregateID) } @@ -24,6 +28,14 @@ func (v *View) PutLoginPolicy(policy *model.LoginPolicyView, event *models.Event return v.ProcessedLoginPolicySequence(event) } +func (v *View) PutLoginPolicies(policies []*model.LoginPolicyView, event *models.Event) error { + err := view.PutLoginPolicies(v.Db, loginPolicyTable, policies...) + if err != nil { + return err + } + return v.ProcessedLoginPolicySequence(event) +} + func (v *View) DeleteLoginPolicy(aggregateID string, event *models.Event) error { err := view.DeleteLoginPolicy(v.Db, loginPolicyTable, aggregateID) if err != nil && !errors.IsNotFound(err) { diff --git a/internal/admin/repository/iam.go b/internal/admin/repository/iam.go index b2ca0c6fa6..51e96b1b69 100644 --- a/internal/admin/repository/iam.go +++ b/internal/admin/repository/iam.go @@ -2,6 +2,7 @@ package repository import ( "context" + usr_model "github.com/caos/zitadel/internal/user/model" iam_model "github.com/caos/zitadel/internal/iam/model" ) @@ -18,6 +19,10 @@ type IAMRepository interface { SearchDefaultSecondFactors(ctx context.Context) (*iam_model.SecondFactorsSearchResponse, error) SearchDefaultMultiFactors(ctx context.Context) (*iam_model.MultiFactorsSearchResponse, error) + IDPProvidersByIDPConfigID(ctx context.Context, idpConfigID string) ([]*iam_model.IDPProviderView, error) + ExternalIDPsByIDPConfigID(ctx context.Context, idpConfigID string) ([]*usr_model.ExternalIDPView, error) + ExternalIDPsByIDPConfigIDFromDefaultPolicy(ctx context.Context, idpConfigID string) ([]*usr_model.ExternalIDPView, error) + GetDefaultLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error) GetDefaultMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error) diff --git a/internal/api/grpc/admin/idp_config.go b/internal/api/grpc/admin/idp_config.go index f949385627..c17510804b 100644 --- a/internal/api/grpc/admin/idp_config.go +++ b/internal/api/grpc/admin/idp_config.go @@ -42,7 +42,15 @@ func (s *Server) ReactivateIdpConfig(ctx context.Context, id *admin.IdpID) (*emp } func (s *Server) RemoveIdpConfig(ctx context.Context, id *admin.IdpID) (*empty.Empty, error) { - err := s.command.RemoveDefaultIDPConfig(ctx, id.Id) + idpProviders, err := s.iam.IDPProvidersByIDPConfigID(ctx, id.Id) + if err != nil { + return &empty.Empty{}, err + } + externalIDPs, err := s.iam.ExternalIDPsByIDPConfigID(ctx, id.Id) + if err != nil { + return &empty.Empty{}, err + } + err = s.command.RemoveDefaultIDPConfig(ctx, id.Id, idpProviderViewsToDomain(idpProviders), externalIDPViewsToDomain(externalIDPs)...) return &empty.Empty{}, err } diff --git a/internal/api/grpc/admin/idp_config_converter.go b/internal/api/grpc/admin/idp_config_converter.go index f14d20a42d..6db7939554 100644 --- a/internal/api/grpc/admin/idp_config_converter.go +++ b/internal/api/grpc/admin/idp_config_converter.go @@ -2,6 +2,7 @@ package admin import ( "github.com/caos/logging" + "github.com/caos/zitadel/internal/eventstore/models" iam_model "github.com/caos/zitadel/internal/iam/model" "github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/pkg/grpc/admin" @@ -257,3 +258,26 @@ func idpConfigStylingTypeToDomain(stylingType admin.IdpStylingType) domain.IDPCo return domain.IDPConfigStylingTypeUnspecified } } + +func idpConfigTypeToDomain(idpType iam_model.IDPProviderType) domain.IdentityProviderType { + switch idpType { + case iam_model.IDPProviderTypeOrg: + return domain.IdentityProviderTypeOrg + default: + return domain.IdentityProviderTypeSystem + } +} + +func idpProviderViewsToDomain(idps []*iam_model.IDPProviderView) []*domain.IDPProvider { + idpProvider := make([]*domain.IDPProvider, len(idps)) + for i, idp := range idps { + idpProvider[i] = &domain.IDPProvider{ + ObjectRoot: models.ObjectRoot{ + AggregateID: idp.AggregateID, + }, + IDPConfigID: idp.IDPConfigID, + Type: idpConfigTypeToDomain(idp.IDPProviderType), + } + } + return idpProvider +} diff --git a/internal/api/grpc/admin/login_policy.go b/internal/api/grpc/admin/login_policy.go index 2155e2e0ec..badf1e2410 100644 --- a/internal/api/grpc/admin/login_policy.go +++ b/internal/api/grpc/admin/login_policy.go @@ -39,7 +39,11 @@ func (s *Server) AddIdpProviderToDefaultLoginPolicy(ctx context.Context, provide } func (s *Server) RemoveIdpProviderFromDefaultLoginPolicy(ctx context.Context, provider *admin.IdpProviderID) (*empty.Empty, error) { - err := s.command.RemoveIDPProviderFromDefaultLoginPolicy(ctx, idpProviderToDomain(provider)) + externalIDPs, err := s.iam.ExternalIDPsByIDPConfigIDFromDefaultPolicy(ctx, provider.IdpConfigId) + if err != nil { + return &empty.Empty{}, err + } + err = s.command.RemoveIDPProviderFromDefaultLoginPolicy(ctx, idpProviderToDomain(provider), externalIDPViewsToDomain(externalIDPs)...) return &empty.Empty{}, err } diff --git a/internal/api/grpc/admin/user_converter.go b/internal/api/grpc/admin/user_converter.go index 29479a585d..3a964161a9 100644 --- a/internal/api/grpc/admin/user_converter.go +++ b/internal/api/grpc/admin/user_converter.go @@ -2,6 +2,7 @@ package admin import ( "github.com/caos/logging" + "github.com/caos/zitadel/internal/eventstore/models" usr_model "github.com/caos/zitadel/internal/user/model" "github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/pkg/grpc/admin" @@ -194,3 +195,19 @@ func humanFromModel(user *usr_model.Human) *admin.HumanResponse { } return human } + +func externalIDPViewsToDomain(idps []*usr_model.ExternalIDPView) []*domain.ExternalIDP { + externalIDPs := make([]*domain.ExternalIDP, len(idps)) + for i, idp := range idps { + externalIDPs[i] = &domain.ExternalIDP{ + ObjectRoot: models.ObjectRoot{ + AggregateID: idp.UserID, + ResourceOwner: idp.ResourceOwner, + }, + IDPConfigID: idp.IDPConfigID, + ExternalUserID: idp.ExternalUserID, + DisplayName: idp.UserDisplayName, + } + } + return externalIDPs +} diff --git a/internal/api/grpc/management/idp_config.go b/internal/api/grpc/management/idp_config.go index 57b83d5596..bba451f8ac 100644 --- a/internal/api/grpc/management/idp_config.go +++ b/internal/api/grpc/management/idp_config.go @@ -44,7 +44,15 @@ func (s *Server) ReactivateIdpConfig(ctx context.Context, id *management.IdpID) } func (s *Server) RemoveIdpConfig(ctx context.Context, id *management.IdpID) (*empty.Empty, error) { - err := s.command.RemoveIDPConfig(ctx, id.Id, authz.GetCtxData(ctx).OrgID) + externalIdps, err := s.user.ExternalIDPsByIDPConfigIDAndResourceOwner(ctx, id.Id, authz.GetCtxData(ctx).OrgID) + if err != nil { + return &empty.Empty{}, err + } + providers, err := s.org.GetIDPProvidersByIDPConfigID(ctx, authz.GetCtxData(ctx).OrgID, id.Id) + if err != nil { + return &empty.Empty{}, err + } + err = s.command.RemoveIDPConfig(ctx, id.Id, authz.GetCtxData(ctx).OrgID, len(providers) > 0, externalIDPViewsToDomain(externalIdps)...) return &empty.Empty{}, err } diff --git a/internal/api/grpc/management/idp_config_converter.go b/internal/api/grpc/management/idp_config_converter.go index 4741673424..d344c38629 100644 --- a/internal/api/grpc/management/idp_config_converter.go +++ b/internal/api/grpc/management/idp_config_converter.go @@ -2,6 +2,7 @@ package management import ( "context" + "github.com/caos/zitadel/internal/user/model" "github.com/caos/logging" "github.com/caos/zitadel/internal/api/authz" @@ -328,3 +329,19 @@ func idpProviderTypeStringToModel(providerType string) (iam_model.IDPProviderTyp return 0, caos_errors.ThrowPreconditionFailed(nil, "MGMT-6is9f", "Errors.Org.IDP.InvalidSearchQuery") } } + +func externalIDPViewsToDomain(idps []*model.ExternalIDPView) []*domain.ExternalIDP { + externalIDPs := make([]*domain.ExternalIDP, len(idps)) + for i, idp := range idps { + externalIDPs[i] = &domain.ExternalIDP{ + ObjectRoot: models.ObjectRoot{ + AggregateID: idp.UserID, + ResourceOwner: idp.ResourceOwner, + }, + IDPConfigID: idp.IDPConfigID, + ExternalUserID: idp.ExternalUserID, + DisplayName: idp.UserDisplayName, + } + } + return externalIDPs +} diff --git a/internal/api/grpc/management/login_policy.go b/internal/api/grpc/management/login_policy.go index a03e0c5ce8..df9c7ffc12 100644 --- a/internal/api/grpc/management/login_policy.go +++ b/internal/api/grpc/management/login_policy.go @@ -63,7 +63,11 @@ func (s *Server) AddIdpProviderToLoginPolicy(ctx context.Context, provider *mana } func (s *Server) RemoveIdpProviderFromLoginPolicy(ctx context.Context, provider *management.IdpProviderID) (*empty.Empty, error) { - err := s.command.RemoveIDPProviderFromLoginPolicy(ctx, idpProviderIDToDomain(ctx, provider)) + externalIDPs, err := s.user.ExternalIDPsByIDPConfigIDAndResourceOwner(ctx, provider.IdpConfigId, authz.GetCtxData(ctx).OrgID) + if err != nil { + return &empty.Empty{}, err + } + err = s.command.RemoveIDPProviderFromLoginPolicy(ctx, idpProviderIDToDomain(ctx, provider), externalIDPViewsToDomain(externalIDPs)...) return &empty.Empty{}, err } diff --git a/internal/api/grpc/management/project.go b/internal/api/grpc/management/project.go index afa3ab1ef8..9287beb484 100644 --- a/internal/api/grpc/management/project.go +++ b/internal/api/grpc/management/project.go @@ -101,7 +101,15 @@ func (s *Server) ChangeProjectRole(ctx context.Context, in *management.ProjectRo } func (s *Server) RemoveProjectRole(ctx context.Context, in *management.ProjectRoleRemove) (*empty.Empty, error) { - err := s.command.RemoveProjectRole(ctx, in.Id, in.Key, authz.GetCtxData(ctx).OrgID) + userGrants, err := s.usergrant.UserGrantsByProjectIDAndRoleKey(ctx, in.Id, in.Key) + if err != nil { + return &empty.Empty{}, err + } + projectGrants, err := s.project.ProjectGrantsByProjectIDAndRoleKey(ctx, in.Id, in.Key) + if err != nil { + return &empty.Empty{}, err + } + err = s.command.RemoveProjectRole(ctx, in.Id, in.Key, authz.GetCtxData(ctx).OrgID, projectGrantsToIDs(projectGrants), userGrantsToIDs(userGrants)...) return &empty.Empty{}, err } diff --git a/internal/api/grpc/management/project_grant.go b/internal/api/grpc/management/project_grant.go index 5c89a3b84c..44fe000035 100644 --- a/internal/api/grpc/management/project_grant.go +++ b/internal/api/grpc/management/project_grant.go @@ -36,7 +36,11 @@ func (s *Server) CreateProjectGrant(ctx context.Context, in *management.ProjectG return projectGrantFromDomain(grant), nil } func (s *Server) UpdateProjectGrant(ctx context.Context, in *management.ProjectGrantUpdate) (*management.ProjectGrant, error) { - grant, err := s.command.ChangeProjectGrant(ctx, projectGrantUpdateToDomain(in), authz.GetCtxData(ctx).OrgID) + userGrants, err := s.usergrant.UserGrantsByProjectAndGrantID(ctx, in.ProjectId, in.Id) + if err != nil { + return nil, err + } + grant, err := s.command.ChangeProjectGrant(ctx, projectGrantUpdateToDomain(in), authz.GetCtxData(ctx).OrgID, userGrantsToIDs(userGrants)...) if err != nil { return nil, err } diff --git a/internal/api/grpc/management/project_grant_converter.go b/internal/api/grpc/management/project_grant_converter.go index a1c85ceed3..359ec8f114 100644 --- a/internal/api/grpc/management/project_grant_converter.go +++ b/internal/api/grpc/management/project_grant_converter.go @@ -180,3 +180,11 @@ func projectGrantStateFromProjectStateModel(state proj_model.ProjectState) manag return management.ProjectGrantState_PROJECTGRANTSTATE_UNSPECIFIED } } + +func projectGrantsToIDs(projectGrants []*proj_model.ProjectGrantView) []string { + converted := make([]string, len(projectGrants)) + for i, grant := range projectGrants { + converted[i] = grant.GrantID + } + return converted +} diff --git a/internal/api/grpc/management/user.go b/internal/api/grpc/management/user.go index e10419df85..fc74160e6c 100644 --- a/internal/api/grpc/management/user.go +++ b/internal/api/grpc/management/user.go @@ -88,7 +88,11 @@ func (s *Server) UnlockUser(ctx context.Context, in *management.UserID) (*empty. } func (s *Server) DeleteUser(ctx context.Context, in *management.UserID) (*empty.Empty, error) { - err := s.command.RemoveUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID) + grants, err := s.usergrant.UserGrantsByUserID(ctx, in.Id) + if err != nil { + return &empty.Empty{}, err + } + err = s.command.RemoveUser(ctx, in.Id, authz.GetCtxData(ctx).OrgID, userGrantsToIDs(grants)...) return &empty.Empty{}, err } diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index 3a3aba5092..c8d0110e21 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -696,13 +696,6 @@ func (repo *AuthRequestRepo) mfaSkippedOrSetUp(user *user_model.UserView) bool { func (repo *AuthRequestRepo) getLoginPolicy(ctx context.Context, orgID string) (*iam_model.LoginPolicyView, error) { policy, err := repo.View.LoginPolicyByAggregateID(orgID) - if errors.IsNotFound(err) { - policy, err = repo.View.LoginPolicyByAggregateID(repo.IAMID) - if err != nil { - return nil, err - } - policy.Default = true - } if err != nil { return nil, err } diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go index f6085c1ebf..c73ad4fe4d 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go @@ -3,6 +3,7 @@ package eventstore import ( "context" "encoding/json" + "github.com/caos/zitadel/internal/v2/domain" "testing" "time" @@ -213,14 +214,14 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { MultiFactorCheckLifeTime time.Duration } type args struct { - request *model.AuthRequest + request *domain.AuthRequest checkLoggedIn bool } tests := []struct { name string fields fields args args - want []model.NextStep + want []domain.NextStep wantErr func(error) bool }{ { @@ -233,8 +234,8 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { { "prompt none and checkLoggedIn false, callback step", fields{}, - args{&model.AuthRequest{Prompt: model.PromptNone}, false}, - []model.NextStep{&model.RedirectToCallbackStep{}}, + args{&domain.AuthRequest{Prompt: domain.PromptNone}, false}, + []domain.NextStep{&domain.RedirectToCallbackStep{}}, nil, }, { @@ -242,8 +243,8 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { fields{ userSessionViewProvider: &mockViewNoUserSession{}, }, - args{&model.AuthRequest{}, false}, - []model.NextStep{&model.LoginStep{}}, + args{&domain.AuthRequest{}, false}, + []domain.NextStep{&domain.LoginStep{}}, nil, }, { @@ -251,8 +252,8 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { fields{ userSessionViewProvider: &mockViewNoUserSession{}, }, - args{&model.AuthRequest{LinkingUsers: []*model.ExternalUser{{IDPConfigID: "IDPConfigID", ExternalUserID: "ExternalUserID"}}}, false}, - []model.NextStep{&model.ExternalNotFoundOptionStep{}}, + args{&domain.AuthRequest{LinkingUsers: []*domain.ExternalUser{{IDPConfigID: "IDPConfigID", ExternalUserID: "ExternalUserID"}}}, false}, + []domain.NextStep{&domain.ExternalNotFoundOptionStep{}}, nil, }, { @@ -260,7 +261,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { fields{ userSessionViewProvider: &mockViewErrUserSession{}, }, - args{&model.AuthRequest{Prompt: model.PromptSelectAccount}, false}, + args{&domain.AuthRequest{Prompt: domain.PromptSelectAccount}, false}, nil, errors.IsInternal, }, @@ -283,11 +284,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { }, userEventProvider: &mockEventUser{}, }, - args{&model.AuthRequest{Prompt: model.PromptSelectAccount}, false}, - []model.NextStep{ - &model.LoginStep{}, - &model.SelectUserStep{ - Users: []model.UserSelection{ + args{&domain.AuthRequest{Prompt: domain.PromptSelectAccount}, false}, + []domain.NextStep{ + &domain.LoginStep{}, + &domain.SelectUserStep{ + Users: []domain.UserSelection{ { UserID: "id1", LoginName: "loginname1", @@ -321,11 +322,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { }, userEventProvider: &mockEventUser{}, }, - args{&model.AuthRequest{Prompt: model.PromptSelectAccount, RequestedOrgID: "orgID1"}, false}, - []model.NextStep{ - &model.LoginStep{}, - &model.SelectUserStep{ - Users: []model.UserSelection{ + args{&domain.AuthRequest{Prompt: domain.PromptSelectAccount, RequestedOrgID: "orgID1"}, false}, + []domain.NextStep{ + &domain.LoginStep{}, + &domain.SelectUserStep{ + Users: []domain.UserSelection{ { UserID: "id1", LoginName: "loginname1", @@ -348,11 +349,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { }, userEventProvider: &mockEventUser{}, }, - args{&model.AuthRequest{Prompt: model.PromptSelectAccount}, false}, - []model.NextStep{ - &model.LoginStep{}, - &model.SelectUserStep{ - Users: []model.UserSelection{}, + args{&domain.AuthRequest{Prompt: domain.PromptSelectAccount}, false}, + []domain.NextStep{ + &domain.LoginStep{}, + &domain.SelectUserStep{ + Users: []domain.UserSelection{}, }}, nil, }, @@ -362,7 +363,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userViewProvider: &mockViewNoUser{}, userEventProvider: &mockEventUser{}, }, - args{&model.AuthRequest{UserID: "UserID"}, false}, + args{&domain.AuthRequest{UserID: "UserID"}, false}, nil, errors.IsNotFound, }, @@ -378,7 +379,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { }, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, }, - args{&model.AuthRequest{UserID: "UserID"}, false}, + args{&domain.AuthRequest{UserID: "UserID"}, false}, nil, errors.IsPreconditionFailed, }, @@ -394,7 +395,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { }, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, }, - args{&model.AuthRequest{UserID: "UserID"}, false}, + args{&domain.AuthRequest{UserID: "UserID"}, false}, nil, errors.IsPreconditionFailed, }, @@ -405,7 +406,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewErrOrg{}, }, - args{&model.AuthRequest{UserID: "UserID"}, false}, + args{&domain.AuthRequest{UserID: "UserID"}, false}, nil, errors.IsInternal, }, @@ -416,7 +417,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateInactive}, }, - args{&model.AuthRequest{UserID: "UserID"}, false}, + args{&domain.AuthRequest{UserID: "UserID"}, false}, nil, errors.IsPreconditionFailed, }, @@ -430,8 +431,8 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, }, - args{&model.AuthRequest{UserID: "UserID", LoginPolicy: &iam_model.LoginPolicyView{}}, false}, - []model.NextStep{&model.PasswordStep{}}, + args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{}}, false}, + []domain.NextStep{&domain.PasswordStep{}}, nil, }, { @@ -442,7 +443,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, }, - args{&model.AuthRequest{UserID: "UserID"}, false}, + args{&domain.AuthRequest{UserID: "UserID"}, false}, nil, errors.IsInternal, }, @@ -457,8 +458,8 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, }, - args{&model.AuthRequest{UserID: "UserID"}, false}, - []model.NextStep{&model.InitUserStep{ + args{&domain.AuthRequest{UserID: "UserID"}, false}, + []domain.NextStep{&domain.InitUserStep{ PasswordSet: true, }}, nil, @@ -475,8 +476,8 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, MultiFactorCheckLifeTime: 10 * time.Hour, }, - args{&model.AuthRequest{UserID: "UserID", LoginPolicy: &iam_model.LoginPolicyView{PasswordlessType: iam_model.PasswordlessTypeAllowed}}, false}, - []model.NextStep{&model.PasswordlessStep{}}, + args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{PasswordlessType: domain.PasswordlessTypeAllowed}}, false}, + []domain.NextStep{&domain.PasswordlessStep{}}, nil, }, { @@ -497,14 +498,14 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, MultiFactorCheckLifeTime: 10 * time.Hour, }, - args{&model.AuthRequest{ + args{&domain.AuthRequest{ UserID: "UserID", - LoginPolicy: &iam_model.LoginPolicyView{ - PasswordlessType: iam_model.PasswordlessTypeAllowed, - MultiFactors: []iam_model.MultiFactorType{iam_model.MultiFactorTypeU2FWithPIN}, + LoginPolicy: &domain.LoginPolicy{ + PasswordlessType: domain.PasswordlessTypeAllowed, + MultiFactors: []domain.MultiFactorType{domain.MultiFactorTypeU2FWithPIN}, }, }, false}, - []model.NextStep{&model.VerifyEMailStep{}}, + []domain.NextStep{&domain.VerifyEMailStep{}}, nil, }, { @@ -515,8 +516,8 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, }, - args{&model.AuthRequest{UserID: "UserID", LoginPolicy: &iam_model.LoginPolicyView{}}, false}, - []model.NextStep{&model.InitPasswordStep{}}, + args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{}}, false}, + []domain.NextStep{&domain.InitPasswordStep{}}, nil, }, { @@ -533,8 +534,8 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, SecondFactorCheckLifeTime: 18 * time.Hour, }, - args{&model.AuthRequest{UserID: "UserID", SelectedIDPConfigID: "IDPConfigID"}, false}, - []model.NextStep{&model.ExternalLoginStep{SelectedIDPConfigID: "IDPConfigID"}}, + args{&domain.AuthRequest{UserID: "UserID", SelectedIDPConfigID: "IDPConfigID"}, false}, + []domain.NextStep{&domain.ExternalLoginStep{SelectedIDPConfigID: "IDPConfigID"}}, nil, }, { @@ -558,14 +559,14 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { SecondFactorCheckLifeTime: 18 * time.Hour, }, args{ - &model.AuthRequest{ + &domain.AuthRequest{ UserID: "UserID", SelectedIDPConfigID: "IDPConfigID", - Request: &model.AuthRequestOIDC{}, - LoginPolicy: &iam_model.LoginPolicyView{}, + Request: &domain.AuthRequestOIDC{}, + LoginPolicy: &domain.LoginPolicy{}, }, false}, - []model.NextStep{&model.RedirectToCallbackStep{}}, + []domain.NextStep{&domain.RedirectToCallbackStep{}}, nil, }, { @@ -579,8 +580,8 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, PasswordCheckLifeTime: 10 * 24 * time.Hour, }, - args{&model.AuthRequest{UserID: "UserID", LoginPolicy: &iam_model.LoginPolicyView{}}, false}, - []model.NextStep{&model.PasswordStep{}}, + args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{}}, false}, + []domain.NextStep{&domain.PasswordStep{}}, nil, }, { @@ -602,13 +603,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { ExternalLoginCheckLifeTime: 10 * 24 * time.Hour, }, args{ - &model.AuthRequest{ + &domain.AuthRequest{ UserID: "UserID", SelectedIDPConfigID: "IDPConfigID", - Request: &model.AuthRequestOIDC{}, - LoginPolicy: &iam_model.LoginPolicyView{}, + Request: &domain.AuthRequestOIDC{}, + LoginPolicy: &domain.LoginPolicy{}, }, false}, - []model.NextStep{&model.RedirectToCallbackStep{}}, + []domain.NextStep{&domain.RedirectToCallbackStep{}}, nil, }, { @@ -629,14 +630,14 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { SecondFactorCheckLifeTime: 18 * time.Hour, }, args{ - &model.AuthRequest{ + &domain.AuthRequest{ UserID: "UserID", - LoginPolicy: &iam_model.LoginPolicyView{ - SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP}, + LoginPolicy: &domain.LoginPolicy{ + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, }, }, false}, - []model.NextStep{&model.MFAVerificationStep{ - MFAProviders: []model.MFAType{model.MFATypeOTP}, + []domain.NextStep{&domain.MFAVerificationStep{ + MFAProviders: []domain.MFAType{domain.MFATypeOTP}, }}, nil, }, @@ -657,14 +658,14 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { SecondFactorCheckLifeTime: 18 * time.Hour, }, args{ - &model.AuthRequest{ + &domain.AuthRequest{ UserID: "UserID", - LoginPolicy: &iam_model.LoginPolicyView{ - SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP}, + LoginPolicy: &domain.LoginPolicy{ + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, }, }, false}, - []model.NextStep{&model.MFAVerificationStep{ - MFAProviders: []model.MFAType{model.MFATypeOTP}, + []domain.NextStep{&domain.MFAVerificationStep{ + MFAProviders: []domain.MFAType{domain.MFATypeOTP}, }}, nil, }, @@ -687,15 +688,15 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { SecondFactorCheckLifeTime: 18 * time.Hour, }, args{ - &model.AuthRequest{ + &domain.AuthRequest{ UserID: "UserID", SelectedIDPConfigID: "IDPConfigID", - LoginPolicy: &iam_model.LoginPolicyView{ - SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP}, + LoginPolicy: &domain.LoginPolicy{ + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, }, }, false}, - []model.NextStep{&model.MFAVerificationStep{ - MFAProviders: []model.MFAType{model.MFATypeOTP}, + []domain.NextStep{&domain.MFAVerificationStep{ + MFAProviders: []domain.MFAType{domain.MFATypeOTP}, }}, nil, }, @@ -718,13 +719,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { SecondFactorCheckLifeTime: 18 * time.Hour, }, args{ - &model.AuthRequest{ + &domain.AuthRequest{ UserID: "UserID", - LoginPolicy: &iam_model.LoginPolicyView{ - SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP}, + LoginPolicy: &domain.LoginPolicy{ + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, }, }, false}, - []model.NextStep{&model.ChangePasswordStep{}}, + []domain.NextStep{&domain.ChangePasswordStep{}}, nil, }, { @@ -743,13 +744,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { PasswordCheckLifeTime: 10 * 24 * time.Hour, SecondFactorCheckLifeTime: 18 * time.Hour, }, - args{&model.AuthRequest{ + args{&domain.AuthRequest{ UserID: "UserID", - LoginPolicy: &iam_model.LoginPolicyView{ - SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP}, + LoginPolicy: &domain.LoginPolicy{ + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, }, }, false}, - []model.NextStep{&model.VerifyEMailStep{}}, + []domain.NextStep{&domain.VerifyEMailStep{}}, nil, }, { @@ -769,13 +770,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { PasswordCheckLifeTime: 10 * 24 * time.Hour, SecondFactorCheckLifeTime: 18 * time.Hour, }, - args{&model.AuthRequest{ + args{&domain.AuthRequest{ UserID: "UserID", - LoginPolicy: &iam_model.LoginPolicyView{ - SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP}, + LoginPolicy: &domain.LoginPolicy{ + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, }, }, false}, - []model.NextStep{&model.ChangePasswordStep{}, &model.VerifyEMailStep{}}, + []domain.NextStep{&domain.ChangePasswordStep{}, &domain.VerifyEMailStep{}}, nil, }, { @@ -796,14 +797,14 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { PasswordCheckLifeTime: 10 * 24 * time.Hour, SecondFactorCheckLifeTime: 18 * time.Hour, }, - args{&model.AuthRequest{ + args{&domain.AuthRequest{ UserID: "UserID", - Request: &model.AuthRequestOIDC{}, - LoginPolicy: &iam_model.LoginPolicyView{ - SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP}, + Request: &domain.AuthRequestOIDC{}, + LoginPolicy: &domain.LoginPolicy{ + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, }, }, false}, - []model.NextStep{&model.RedirectToCallbackStep{}}, + []domain.NextStep{&domain.RedirectToCallbackStep{}}, nil, }, { @@ -824,15 +825,15 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { PasswordCheckLifeTime: 10 * 24 * time.Hour, SecondFactorCheckLifeTime: 18 * time.Hour, }, - args{&model.AuthRequest{ + args{&domain.AuthRequest{ UserID: "UserID", - Prompt: model.PromptNone, - Request: &model.AuthRequestOIDC{}, - LoginPolicy: &iam_model.LoginPolicyView{ - SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP}, + Prompt: domain.PromptNone, + Request: &domain.AuthRequestOIDC{}, + LoginPolicy: &domain.LoginPolicy{ + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, }, }, true}, - []model.NextStep{&model.RedirectToCallbackStep{}}, + []domain.NextStep{&domain.RedirectToCallbackStep{}}, nil, }, { @@ -856,15 +857,15 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { PasswordCheckLifeTime: 10 * 24 * time.Hour, SecondFactorCheckLifeTime: 18 * time.Hour, }, - args{&model.AuthRequest{ + args{&domain.AuthRequest{ UserID: "UserID", - Prompt: model.PromptNone, - Request: &model.AuthRequestOIDC{}, - LoginPolicy: &iam_model.LoginPolicyView{ - SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP}, + Prompt: domain.PromptNone, + Request: &domain.AuthRequestOIDC{}, + LoginPolicy: &domain.LoginPolicy{ + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, }, }, true}, - []model.NextStep{&model.GrantRequiredStep{}}, + []domain.NextStep{&domain.GrantRequiredStep{}}, nil, }, { @@ -888,15 +889,15 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { PasswordCheckLifeTime: 10 * 24 * time.Hour, SecondFactorCheckLifeTime: 18 * time.Hour, }, - args{&model.AuthRequest{ + args{&domain.AuthRequest{ UserID: "UserID", - Prompt: model.PromptNone, - Request: &model.AuthRequestOIDC{}, - LoginPolicy: &iam_model.LoginPolicyView{ - SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP}, + Prompt: domain.PromptNone, + Request: &domain.AuthRequestOIDC{}, + LoginPolicy: &domain.LoginPolicy{ + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, }, }, true}, - []model.NextStep{&model.RedirectToCallbackStep{}}, + []domain.NextStep{&domain.RedirectToCallbackStep{}}, nil, }, { @@ -915,13 +916,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { SecondFactorCheckLifeTime: 18 * time.Hour, }, args{ - &model.AuthRequest{ + &domain.AuthRequest{ UserID: "UserID", - LoginPolicy: &iam_model.LoginPolicyView{}, + LoginPolicy: &domain.LoginPolicy{}, SelectedIDPConfigID: "IDPConfigID", - LinkingUsers: []*model.ExternalUser{{IDPConfigID: "IDPConfigID", ExternalUserID: "UserID", DisplayName: "DisplayName"}}, + LinkingUsers: []*domain.ExternalUser{{IDPConfigID: "IDPConfigID", ExternalUserID: "UserID", DisplayName: "DisplayName"}}, }, false}, - []model.NextStep{&model.PasswordStep{}}, + []domain.NextStep{&domain.PasswordStep{}}, nil, }, { @@ -942,15 +943,15 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { PasswordCheckLifeTime: 10 * 24 * time.Hour, }, args{ - &model.AuthRequest{ + &domain.AuthRequest{ UserID: "UserID", SelectedIDPConfigID: "IDPConfigID", - LinkingUsers: []*model.ExternalUser{{IDPConfigID: "IDPConfigID", ExternalUserID: "UserID", DisplayName: "DisplayName"}}, - LoginPolicy: &iam_model.LoginPolicyView{ - SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP}, + LinkingUsers: []*domain.ExternalUser{{IDPConfigID: "IDPConfigID", ExternalUserID: "UserID", DisplayName: "DisplayName"}}, + LoginPolicy: &domain.LoginPolicy{ + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, }, }, false}, - []model.NextStep{&model.LinkUsersStep{}}, + []domain.NextStep{&domain.LinkUsersStep{}}, nil, }, } @@ -990,7 +991,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { } type args struct { userSession *user_model.UserSessionView - request *model.AuthRequest + request *domain.AuthRequest user *user_model.UserView policy *iam_model.LoginPolicyView } @@ -998,7 +999,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { name string fields fields args args - want model.NextStep + want domain.NextStep wantChecked bool errFunc func(err error) bool }{ @@ -1006,7 +1007,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { // "required, prompt and false", //TODO: enable when LevelsOfAssurance is checked // fields{}, // args{ - // request: &model.AuthRequest{PossibleLOAs: []model.LevelOfAssurance{}}, + // request: &domain.AuthRequest{PossibleLOAs: []model.LevelOfAssurance{}}, // user: &user_model.UserView{ // OTPState: user_model.MFAStateReady, // }, @@ -1019,8 +1020,8 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { MFAInitSkippedLifeTime: 30 * 24 * time.Hour, }, args{ - request: &model.AuthRequest{ - LoginPolicy: &iam_model.LoginPolicyView{ + request: &domain.AuthRequest{ + LoginPolicy: &domain.LoginPolicy{ ForceMFA: true, }, }, @@ -1040,8 +1041,8 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { MFAInitSkippedLifeTime: 30 * 24 * time.Hour, }, args{ - request: &model.AuthRequest{ - LoginPolicy: &iam_model.LoginPolicyView{}, + request: &domain.AuthRequest{ + LoginPolicy: &domain.LoginPolicy{}, }, user: &user_model.UserView{ HumanView: &user_model.HumanView{ @@ -1059,9 +1060,9 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { MFAInitSkippedLifeTime: 30 * 24 * time.Hour, }, args{ - request: &model.AuthRequest{ - LoginPolicy: &iam_model.LoginPolicyView{ - SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP}, + request: &domain.AuthRequest{ + LoginPolicy: &domain.LoginPolicy{ + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, }, }, user: &user_model.UserView{ @@ -1070,9 +1071,9 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { }, }, }, - &model.MFAPromptStep{ - MFAProviders: []model.MFAType{ - model.MFATypeOTP, + &domain.MFAPromptStep{ + MFAProviders: []domain.MFAType{ + domain.MFATypeOTP, }, }, false, @@ -1084,10 +1085,10 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { MFAInitSkippedLifeTime: 30 * 24 * time.Hour, }, args{ - request: &model.AuthRequest{ - LoginPolicy: &iam_model.LoginPolicyView{ + request: &domain.AuthRequest{ + LoginPolicy: &domain.LoginPolicy{ ForceMFA: true, - SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, }, }, user: &user_model.UserView{ @@ -1096,10 +1097,10 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { }, }, }, - &model.MFAPromptStep{ + &domain.MFAPromptStep{ Required: true, - MFAProviders: []model.MFAType{ - model.MFATypeOTP, + MFAProviders: []domain.MFAType{ + domain.MFATypeOTP, }, }, false, @@ -1111,8 +1112,8 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { MFAInitSkippedLifeTime: 30 * 24 * time.Hour, }, args{ - request: &model.AuthRequest{ - LoginPolicy: &iam_model.LoginPolicyView{}, + request: &domain.AuthRequest{ + LoginPolicy: &domain.LoginPolicy{}, }, user: &user_model.UserView{ HumanView: &user_model.HumanView{ @@ -1131,9 +1132,9 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { SecondFactorCheckLifeTime: 18 * time.Hour, }, args{ - request: &model.AuthRequest{ - LoginPolicy: &iam_model.LoginPolicyView{ - SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP}, + request: &domain.AuthRequest{ + LoginPolicy: &domain.LoginPolicy{ + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, }, }, user: &user_model.UserView{ @@ -1154,9 +1155,9 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { SecondFactorCheckLifeTime: 18 * time.Hour, }, args{ - request: &model.AuthRequest{ - LoginPolicy: &iam_model.LoginPolicyView{ - SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP}, + request: &domain.AuthRequest{ + LoginPolicy: &domain.LoginPolicy{ + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, }, }, user: &user_model.UserView{ @@ -1168,8 +1169,8 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { userSession: &user_model.UserSessionView{}, }, - &model.MFAVerificationStep{ - MFAProviders: []model.MFAType{model.MFATypeOTP}, + &domain.MFAVerificationStep{ + MFAProviders: []domain.MFAType{domain.MFATypeOTP}, }, false, nil, diff --git a/internal/auth/repository/eventsourcing/handler/login_policy.go b/internal/auth/repository/eventsourcing/handler/login_policy.go index be964781c3..ac354a9568 100644 --- a/internal/auth/repository/eventsourcing/handler/login_policy.go +++ b/internal/auth/repository/eventsourcing/handler/login_policy.go @@ -1,9 +1,13 @@ package handler import ( + "context" "github.com/caos/logging" + caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/iam/repository/eventsourcing" iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" + "github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/internal/eventstore/models" es_models "github.com/caos/zitadel/internal/eventstore/models" @@ -78,20 +82,48 @@ func (p *LoginPolicy) Reduce(event *models.Event) (err error) { func (p *LoginPolicy) processLoginPolicy(event *models.Event) (err error) { policy := new(iam_model.LoginPolicyView) switch event.Type { + case model.OrgAdded: + policy, err = p.getDefaultLoginPolicy() + if err != nil { + return err + } + policy.AggregateID = event.AggregateID + policy.Default = true case iam_es_model.LoginPolicyAdded, model.LoginPolicyAdded: err = policy.AppendEvent(event) - case iam_es_model.LoginPolicyChanged, model.LoginPolicyChanged, - iam_es_model.LoginPolicySecondFactorAdded, model.LoginPolicySecondFactorAdded, - iam_es_model.LoginPolicySecondFactorRemoved, model.LoginPolicySecondFactorRemoved, - iam_es_model.LoginPolicyMultiFactorAdded, model.LoginPolicyMultiFactorAdded, - iam_es_model.LoginPolicyMultiFactorRemoved, model.LoginPolicyMultiFactorRemoved: + case iam_es_model.LoginPolicyChanged, + iam_es_model.LoginPolicySecondFactorAdded, + iam_es_model.LoginPolicySecondFactorRemoved, + iam_es_model.LoginPolicyMultiFactorAdded, + iam_es_model.LoginPolicyMultiFactorRemoved: + policies, err := p.view.AllDefaultLoginPolicies() + if err != nil { + return err + } + for _, policy := range policies { + err = policy.AppendEvent(event) + if err != nil { + return err + } + } + return p.view.PutLoginPolicies(policies, event) + case model.LoginPolicyChanged, + model.LoginPolicySecondFactorAdded, + model.LoginPolicySecondFactorRemoved, + model.LoginPolicyMultiFactorAdded, + model.LoginPolicyMultiFactorRemoved: policy, err = p.view.LoginPolicyByAggregateID(event.AggregateID) if err != nil { return err } err = policy.AppendEvent(event) case model.LoginPolicyRemoved: - return p.view.DeleteLoginPolicy(event.AggregateID, event) + policy, err = p.getDefaultLoginPolicy() + if err != nil { + return err + } + policy.AggregateID = event.AggregateID + policy.Default = true default: return p.view.ProcessedLoginPolicySequence(event) } @@ -109,3 +141,33 @@ func (p *LoginPolicy) OnError(event *models.Event, err error) error { func (p *LoginPolicy) OnSuccess() error { return spooler.HandleSuccess(p.view.UpdateLoginPolicySpoolerRunTimestamp) } + +func (p *LoginPolicy) getDefaultLoginPolicy() (*iam_model.LoginPolicyView, error) { + policy, policyErr := p.view.LoginPolicyByAggregateID(domain.IAMID) + if policyErr != nil && !caos_errs.IsNotFound(policyErr) { + return nil, policyErr + } + if policy == nil { + policy = &iam_model.LoginPolicyView{} + } + events, err := p.getIAMEvents(policy.Sequence) + if err != nil { + return policy, policyErr + } + policyCopy := *policy + for _, event := range events { + if err := policyCopy.AppendEvent(event); err != nil { + return policy, nil + } + } + return &policyCopy, nil +} + +func (p *LoginPolicy) getIAMEvents(sequence uint64) ([]*models.Event, error) { + query, err := eventsourcing.IAMByIDQuery(domain.IAMID, sequence) + if err != nil { + return nil, err + } + + return p.es.FilterEvents(context.Background(), query) +} diff --git a/internal/auth/repository/eventsourcing/view/login_policies.go b/internal/auth/repository/eventsourcing/view/login_policies.go index 73bf483582..a0533b99d1 100644 --- a/internal/auth/repository/eventsourcing/view/login_policies.go +++ b/internal/auth/repository/eventsourcing/view/login_policies.go @@ -16,6 +16,10 @@ func (v *View) LoginPolicyByAggregateID(aggregateID string) (*model.LoginPolicyV return view.GetLoginPolicyByAggregateID(v.Db, loginPolicyTable, aggregateID) } +func (v *View) AllDefaultLoginPolicies() ([]*model.LoginPolicyView, error) { + return view.GetDefaultLoginPolicies(v.Db, loginPolicyTable) +} + func (v *View) PutLoginPolicy(policy *model.LoginPolicyView, event *models.Event) error { err := view.PutLoginPolicy(v.Db, loginPolicyTable, policy) if err != nil { @@ -24,6 +28,14 @@ func (v *View) PutLoginPolicy(policy *model.LoginPolicyView, event *models.Event return v.ProcessedLoginPolicySequence(event) } +func (v *View) PutLoginPolicies(policies []*model.LoginPolicyView, event *models.Event) error { + err := view.PutLoginPolicies(v.Db, loginPolicyTable, policies...) + if err != nil { + return err + } + return v.ProcessedLoginPolicySequence(event) +} + func (v *View) DeleteLoginPolicy(aggregateID string, event *models.Event) error { err := view.DeleteLoginPolicy(v.Db, loginPolicyTable, aggregateID) if err != nil && !errors.IsNotFound(err) { diff --git a/internal/auth_request/repository/mock/repository.mock.go b/internal/auth_request/repository/mock/repository.mock.go index 95433e4610..b4296ae16b 100644 --- a/internal/auth_request/repository/mock/repository.mock.go +++ b/internal/auth_request/repository/mock/repository.mock.go @@ -6,7 +6,7 @@ package mock import ( context "context" - model "github.com/caos/zitadel/internal/auth_request/model" + domain "github.com/caos/zitadel/internal/v2/domain" gomock "github.com/golang/mock/gomock" reflect "reflect" ) @@ -49,10 +49,10 @@ func (mr *MockAuthRequestCacheMockRecorder) DeleteAuthRequest(arg0, arg1 interfa } // GetAuthRequestByCode mocks base method -func (m *MockAuthRequestCache) GetAuthRequestByCode(arg0 context.Context, arg1 string) (*model.AuthRequest, error) { +func (m *MockAuthRequestCache) GetAuthRequestByCode(arg0 context.Context, arg1 string) (*domain.AuthRequest, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAuthRequestByCode", arg0, arg1) - ret0, _ := ret[0].(*model.AuthRequest) + ret0, _ := ret[0].(*domain.AuthRequest) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -64,10 +64,10 @@ func (mr *MockAuthRequestCacheMockRecorder) GetAuthRequestByCode(arg0, arg1 inte } // GetAuthRequestByID mocks base method -func (m *MockAuthRequestCache) GetAuthRequestByID(arg0 context.Context, arg1 string) (*model.AuthRequest, error) { +func (m *MockAuthRequestCache) GetAuthRequestByID(arg0 context.Context, arg1 string) (*domain.AuthRequest, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAuthRequestByID", arg0, arg1) - ret0, _ := ret[0].(*model.AuthRequest) + ret0, _ := ret[0].(*domain.AuthRequest) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -93,7 +93,7 @@ func (mr *MockAuthRequestCacheMockRecorder) Health(arg0 interface{}) *gomock.Cal } // SaveAuthRequest mocks base method -func (m *MockAuthRequestCache) SaveAuthRequest(arg0 context.Context, arg1 *model.AuthRequest) error { +func (m *MockAuthRequestCache) SaveAuthRequest(arg0 context.Context, arg1 *domain.AuthRequest) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SaveAuthRequest", arg0, arg1) ret0, _ := ret[0].(error) diff --git a/internal/eventstore/spooler/spooler_test.go b/internal/eventstore/spooler/spooler_test.go index b685af2675..54ef72fb8d 100644 --- a/internal/eventstore/spooler/spooler_test.go +++ b/internal/eventstore/spooler/spooler_test.go @@ -3,6 +3,7 @@ package spooler import ( "context" "fmt" + es_v2 "github.com/caos/zitadel/internal/eventstore/v2" "testing" "time" @@ -105,6 +106,9 @@ func (es *eventstoreStub) PushAggregates(ctx context.Context, in ...*models.Aggr func (es *eventstoreStub) LatestSequence(ctx context.Context, in *models.SearchQueryFactory) (uint64, error) { return 0, nil } +func (es *eventstoreStub) V2() *es_v2.Eventstore { + return nil +} func TestSpooler_process(t *testing.T) { type fields struct { diff --git a/internal/eventstore/v2/eventstore_test.go b/internal/eventstore/v2/eventstore_test.go index 81a2de1c2a..9a6c2fd271 100644 --- a/internal/eventstore/v2/eventstore_test.go +++ b/internal/eventstore/v2/eventstore_test.go @@ -63,7 +63,7 @@ func (e *testEvent) Data() interface{} { return e.data() } -func (e *testEvent) UniqueConstraint() []*EventUniqueConstraint { +func (e *testEvent) UniqueConstraints() []*EventUniqueConstraint { return nil } @@ -1358,24 +1358,24 @@ func TestEventstore_mapEvents(t *testing.T) { args args res res }{ - { - name: "no mapper", - args: args{ - events: []*repository.Event{ - { - Type: "no.mapper.found", - }, - }, - }, - fields: fields{ - eventMapper: map[EventType]func(*repository.Event) (EventReader, error){}, - }, - res: res{ - //TODO: as long as not all events are implemented in v2 eventstore doesn't return an error - // afterwards it will return an error on un - wantErr: true, - }, - }, + //{ + // name: "no mapper", + // args: args{ + // events: []*repository.Event{ + // { + // Type: "no.mapper.found", + // }, + // }, + // }, + // fields: fields{ + // eventMapper: map[EventType]func(*repository.Event) (EventReader, error){}, + // }, + // res: res{ + // //TODO: as long as not all events are implemented in v2 eventstore doesn't return an error + // // afterwards it will return an error on un + // wantErr: true, + // }, + //}, { name: "mapping failed", args: args{ diff --git a/internal/eventstore/v2/local_crdb_test.go b/internal/eventstore/v2/local_crdb_test.go index ceaec04b4b..10eaf17f5a 100644 --- a/internal/eventstore/v2/local_crdb_test.go +++ b/internal/eventstore/v2/local_crdb_test.go @@ -10,6 +10,7 @@ import ( "strconv" "strings" "testing" + "time" "github.com/caos/logging" "github.com/cockroachdb/cockroach-go/v2/testserver" @@ -49,12 +50,19 @@ func executeMigrations() error { return err } sort.Sort(files) + if err = setPasswordNULL(); err != nil { + return err + } + if err = createFlywayHistory(); err != nil { + return err + } for _, file := range files { - migration, err := ioutil.ReadFile(string(file)) + migrationData, err := ioutil.ReadFile(file) if err != nil { return err } - transactionInMigration := strings.Contains(string(migration), "BEGIN;") + migration := os.ExpandEnv(string(migrationData)) + transactionInMigration := strings.Contains(migration, "BEGIN;") exec := testCRDBClient.Exec var tx *sql.Tx if !transactionInMigration { @@ -64,18 +72,43 @@ func executeMigrations() error { } exec = tx.Exec } - if _, err = exec(string(migration)); err != nil { + if _, err = exec(migration); err != nil { return fmt.Errorf("exec file: %v || err: %w", file, err) } + duration := 1 * time.Second if !transactionInMigration { if err = tx.Commit(); err != nil { return fmt.Errorf("commit file: %v || err: %w", file, err) } + duration = 0 + } + time.Sleep(duration) + } + return nil +} + +func setPasswordNULL() error { + passwordNames := []string{ + "eventstorepassword", + "managementpassword", + "adminapipassword", + "authpassword", + "notificationpassword", + "authzpassword", + } + for _, name := range passwordNames { + if err := os.Setenv(name, "NULL"); err != nil { + return err } } return nil } +func createFlywayHistory() error { + _, err := testCRDBClient.Exec("CREATE TABLE defaultdb.flyway_schema_history(id TEXT, PRIMARY KEY(id));") + return err +} + type migrationPaths []string type version struct { diff --git a/internal/eventstore/v2/repository/sql/crdb_test.go b/internal/eventstore/v2/repository/sql/crdb_test.go index 3544677079..bccdca3fef 100644 --- a/internal/eventstore/v2/repository/sql/crdb_test.go +++ b/internal/eventstore/v2/repository/sql/crdb_test.go @@ -638,33 +638,6 @@ func TestCRDB_Push_Parallel(t *testing.T) { }, }, }, - { - name: "clients push same aggregates", - args: args{ - events: [][]*repository.Event{ - { - generateEventWithData(t, "210", []byte(`{ "transaction": 1 }`)), - generateEventWithData(t, "210", []byte(`{ "transaction": 1.1 }`)), - }, - { - generateEventWithData(t, "210", []byte(`{ "transaction": 2 }`)), - generateEventWithData(t, "210", []byte(`{ "transaction": 2.1 }`)), - }, - { - generateEventWithData(t, "210", []byte(`{ "transaction": 3 }`)), - generateEventWithData(t, "210", []byte(`{ "transaction": 30.1 }`)), - }, - }, - }, - res: res{ - errCount: 2, - eventsRes: eventsRes{ - aggIDs: []string{"210"}, - pushedEventsCount: 2, - aggTypes: []repository.AggregateType{repository.AggregateType(t.Name())}, - }, - }, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/eventstore/v2/repository/sql/local_crdb_test.go b/internal/eventstore/v2/repository/sql/local_crdb_test.go index 1a956eb747..3d0988cc0f 100644 --- a/internal/eventstore/v2/repository/sql/local_crdb_test.go +++ b/internal/eventstore/v2/repository/sql/local_crdb_test.go @@ -10,6 +10,7 @@ import ( "strconv" "strings" "testing" + "time" "github.com/caos/logging" "github.com/cockroachdb/cockroach-go/v2/testserver" @@ -53,12 +54,19 @@ func executeMigrations() error { return err } sort.Sort(files) + if err = setPasswordNULL(); err != nil { + return err + } + if err = createFlywayHistory(); err != nil { + return err + } for _, file := range files { - migration, err := ioutil.ReadFile(string(file)) + migrationData, err := ioutil.ReadFile(file) if err != nil { return err } - transactionInMigration := strings.Contains(string(migration), "BEGIN;") + migration := os.ExpandEnv(string(migrationData)) + transactionInMigration := strings.Contains(migration, "BEGIN;") exec := testCRDBClient.Exec var tx *sql.Tx if !transactionInMigration { @@ -68,18 +76,43 @@ func executeMigrations() error { } exec = tx.Exec } - if _, err = exec(string(migration)); err != nil { + if _, err = exec(migration); err != nil { return fmt.Errorf("exec file: %v || err: %w", file, err) } + duration := 1 * time.Second if !transactionInMigration { if err = tx.Commit(); err != nil { return fmt.Errorf("commit file: %v || err: %w", file, err) } + duration = 0 + } + time.Sleep(duration) + } + return nil +} + +func setPasswordNULL() error { + passwordNames := []string{ + "eventstorepassword", + "managementpassword", + "adminapipassword", + "authpassword", + "notificationpassword", + "authzpassword", + } + for _, name := range passwordNames { + if err := os.Setenv(name, "NULL"); err != nil { + return err } } return nil } +func createFlywayHistory() error { + _, err := testCRDBClient.Exec("CREATE TABLE defaultdb.flyway_schema_history(id TEXT, PRIMARY KEY(id));") + return err +} + 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) return err diff --git a/internal/eventstore/v2/search_query.go b/internal/eventstore/v2/search_query.go index 634b2ccccc..0ff1166cbb 100644 --- a/internal/eventstore/v2/search_query.go +++ b/internal/eventstore/v2/search_query.go @@ -137,7 +137,7 @@ func (factory *SearchQueryBuilder) eventTypeFilter() *repository.Filter { return nil } if len(factory.eventTypes) == 1 { - return repository.NewFilter(repository.FieldEventType, factory.eventTypes[0], repository.OperationEquals) + return repository.NewFilter(repository.FieldEventType, repository.EventType(factory.eventTypes[0]), repository.OperationEquals) } eventTypes := make([]repository.EventType, len(factory.eventTypes)) for i, eventType := range factory.eventTypes { @@ -148,7 +148,7 @@ func (factory *SearchQueryBuilder) eventTypeFilter() *repository.Filter { func (factory *SearchQueryBuilder) aggregateTypeFilter() *repository.Filter { if len(factory.aggregateTypes) == 1 { - return repository.NewFilter(repository.FieldAggregateType, factory.aggregateTypes[0], repository.OperationEquals) + return repository.NewFilter(repository.FieldAggregateType, repository.AggregateType(factory.aggregateTypes[0]), repository.OperationEquals) } aggregateTypes := make([]repository.AggregateType, len(factory.aggregateTypes)) for i, aggregateType := range factory.aggregateTypes { diff --git a/internal/eventstore/v2/search_query_test.go b/internal/eventstore/v2/search_query_test.go index 6d001fdfcc..8a2acbc805 100644 --- a/internal/eventstore/v2/search_query_test.go +++ b/internal/eventstore/v2/search_query_test.go @@ -231,7 +231,7 @@ func TestSearchQueryFactoryBuild(t *testing.T) { Desc: false, Limit: 0, Filters: []*repository.Filter{ - repository.NewFilter(repository.FieldAggregateType, AggregateType("user"), repository.OperationEquals), + repository.NewFilter(repository.FieldAggregateType, repository.AggregateType("user"), repository.OperationEquals), }, }, }, @@ -250,7 +250,7 @@ func TestSearchQueryFactoryBuild(t *testing.T) { Desc: false, Limit: 0, Filters: []*repository.Filter{ - repository.NewFilter(repository.FieldAggregateType, []AggregateType{"user", "org"}, repository.OperationIn), + repository.NewFilter(repository.FieldAggregateType, []repository.AggregateType{"user", "org"}, repository.OperationIn), }, }, }, @@ -273,7 +273,7 @@ func TestSearchQueryFactoryBuild(t *testing.T) { Desc: true, Limit: 5, Filters: []*repository.Filter{ - repository.NewFilter(repository.FieldAggregateType, AggregateType("user"), repository.OperationEquals), + repository.NewFilter(repository.FieldAggregateType, repository.AggregateType("user"), repository.OperationEquals), repository.NewFilter(repository.FieldSequence, uint64(100), repository.OperationLess), }, }, @@ -297,7 +297,7 @@ func TestSearchQueryFactoryBuild(t *testing.T) { Desc: false, Limit: 5, Filters: []*repository.Filter{ - repository.NewFilter(repository.FieldAggregateType, AggregateType("user"), repository.OperationEquals), + repository.NewFilter(repository.FieldAggregateType, repository.AggregateType("user"), repository.OperationEquals), repository.NewFilter(repository.FieldSequence, uint64(100), repository.OperationGreater), }, }, @@ -322,7 +322,7 @@ func TestSearchQueryFactoryBuild(t *testing.T) { Desc: true, Limit: 5, Filters: []*repository.Filter{ - repository.NewFilter(repository.FieldAggregateType, AggregateType("user"), repository.OperationEquals), + repository.NewFilter(repository.FieldAggregateType, repository.AggregateType("user"), repository.OperationEquals), repository.NewFilter(repository.FieldSequence, uint64(100), repository.OperationLess), }, }, @@ -344,7 +344,7 @@ func TestSearchQueryFactoryBuild(t *testing.T) { Desc: false, Limit: 0, Filters: []*repository.Filter{ - repository.NewFilter(repository.FieldAggregateType, AggregateType("user"), repository.OperationEquals), + repository.NewFilter(repository.FieldAggregateType, repository.AggregateType("user"), repository.OperationEquals), repository.NewFilter(repository.FieldAggregateID, "1234", repository.OperationEquals), }, }, @@ -366,7 +366,7 @@ func TestSearchQueryFactoryBuild(t *testing.T) { Desc: false, Limit: 0, Filters: []*repository.Filter{ - repository.NewFilter(repository.FieldAggregateType, AggregateType("user"), repository.OperationEquals), + repository.NewFilter(repository.FieldAggregateType, repository.AggregateType("user"), repository.OperationEquals), repository.NewFilter(repository.FieldAggregateID, []string{"1234", "0815"}, repository.OperationIn), }, }, @@ -388,7 +388,7 @@ func TestSearchQueryFactoryBuild(t *testing.T) { Desc: false, Limit: 0, Filters: []*repository.Filter{ - repository.NewFilter(repository.FieldAggregateType, AggregateType("user"), repository.OperationEquals), + repository.NewFilter(repository.FieldAggregateType, repository.AggregateType("user"), repository.OperationEquals), repository.NewFilter(repository.FieldSequence, uint64(8), repository.OperationGreater), }, }, @@ -410,8 +410,8 @@ func TestSearchQueryFactoryBuild(t *testing.T) { Desc: false, Limit: 0, Filters: []*repository.Filter{ - repository.NewFilter(repository.FieldAggregateType, AggregateType("user"), repository.OperationEquals), - repository.NewFilter(repository.FieldEventType, EventType("user.created"), repository.OperationEquals), + repository.NewFilter(repository.FieldAggregateType, repository.AggregateType("user"), repository.OperationEquals), + repository.NewFilter(repository.FieldEventType, repository.EventType("user.created"), repository.OperationEquals), }, }, }, @@ -432,8 +432,8 @@ func TestSearchQueryFactoryBuild(t *testing.T) { Desc: false, Limit: 0, Filters: []*repository.Filter{ - repository.NewFilter(repository.FieldAggregateType, AggregateType("user"), repository.OperationEquals), - repository.NewFilter(repository.FieldEventType, []EventType{"user.created", "user.changed"}, repository.OperationIn), + repository.NewFilter(repository.FieldAggregateType, repository.AggregateType("user"), repository.OperationEquals), + repository.NewFilter(repository.FieldEventType, []repository.EventType{"user.created", "user.changed"}, repository.OperationIn), }, }, }, @@ -454,7 +454,7 @@ func TestSearchQueryFactoryBuild(t *testing.T) { Desc: false, Limit: 0, Filters: []*repository.Filter{ - repository.NewFilter(repository.FieldAggregateType, AggregateType("user"), repository.OperationEquals), + repository.NewFilter(repository.FieldAggregateType, repository.AggregateType("user"), repository.OperationEquals), repository.NewFilter(repository.FieldResourceOwner, "hodor", repository.OperationEquals), }, }, diff --git a/internal/iam/model/login_policy_view.go b/internal/iam/model/login_policy_view.go index b28a3f62e2..488e6e7204 100644 --- a/internal/iam/model/login_policy_view.go +++ b/internal/iam/model/login_policy_view.go @@ -36,6 +36,7 @@ type LoginPolicySearchKey int32 const ( LoginPolicySearchKeyUnspecified LoginPolicySearchKey = iota LoginPolicySearchKeyAggregateID + LoginPolicySearchKeyDefault ) type LoginPolicySearchQuery struct { diff --git a/internal/iam/repository/eventsourcing/eventstore_test.go b/internal/iam/repository/eventsourcing/eventstore_test.go index 3054044ac0..e6c304ef62 100644 --- a/internal/iam/repository/eventsourcing/eventstore_test.go +++ b/internal/iam/repository/eventsourcing/eventstore_test.go @@ -150,79 +150,6 @@ func TestIamByID(t *testing.T) { // } //} -func TestSetUpDone(t *testing.T) { - ctrl := gomock.NewController(t) - type args struct { - es *IAMEventstore - ctx context.Context - iamID string - step iam_model.Step - } - type res struct { - iam *iam_model.IAM - errFunc func(err error) bool - } - tests := []struct { - name string - args args - res res - }{ - { - name: "setup done iam, ok", - args: args{ - es: GetMockManipulateIAM(ctrl), - ctx: authz.NewMockContext("orgID", "userID"), - iamID: "iamID", - step: iam_model.Step1, - }, - res: res{ - iam: &iam_model.IAM{ObjectRoot: es_models.ObjectRoot{AggregateID: "iamID", Sequence: 1}, SetUpStarted: iam_model.Step1, SetUpDone: iam_model.Step1}, - }, - }, - { - name: "setup iam no id", - args: args{ - es: GetMockManipulateIAM(ctrl), - ctx: authz.NewMockContext("orgID", "userID"), - step: iam_model.Step1, - }, - res: res{ - errFunc: caos_errs.IsPreconditionFailed, - }, - }, - { - name: "iam not found", - args: args{ - es: GetMockManipulateIAMNotExisting(ctrl), - ctx: authz.NewMockContext("orgID", "userID"), - iamID: "iamID", - step: iam_model.Step1, - }, - res: res{ - errFunc: caos_errs.IsNotFound, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result, err := tt.args.es.SetupDone(tt.args.ctx, tt.args.iamID, tt.args.step) - if (tt.res.errFunc != nil && !tt.res.errFunc(err)) || (err != nil && tt.res.errFunc == nil) { - t.Errorf("got wrong err: %v ", err) - return - } - if tt.res.errFunc != nil && tt.res.errFunc(err) { - return - } - if result.AggregateID == "" { - t.Errorf("result has no id") - } - if result.SetUpDone != tt.res.iam.SetUpDone { - t.Errorf("got wrong result SetUpDone: expected: %v, actual: %v ", tt.res.iam.SetUpDone, result.SetUpDone) - } - }) - } -} - func TestSetGlobalOrg(t *testing.T) { ctrl := gomock.NewController(t) type args struct { diff --git a/internal/iam/repository/view/login_policy_view.go b/internal/iam/repository/view/login_policy_view.go index dbcb30afea..0563f196a8 100644 --- a/internal/iam/repository/view/login_policy_view.go +++ b/internal/iam/repository/view/login_policy_view.go @@ -9,6 +9,19 @@ import ( "github.com/jinzhu/gorm" ) +func GetDefaultLoginPolicies(db *gorm.DB, table string) ([]*model.LoginPolicyView, error) { + loginPolicies := make([]*model.LoginPolicyView, 0) + queries := []*iam_model.LoginPolicySearchQuery{ + {Key: iam_model.LoginPolicySearchKeyDefault, Value: true, Method: global_model.SearchMethodEquals}, + } + query := repository.PrepareSearchQuery(table, model.LoginPolicySearchRequest{Queries: queries}) + _, err := query(db, &loginPolicies) + if err != nil { + return nil, err + } + return loginPolicies, nil +} + func GetLoginPolicyByAggregateID(db *gorm.DB, table, aggregateID string) (*model.LoginPolicyView, error) { policy := new(model.LoginPolicyView) aggregateIDQuery := &model.LoginPolicySearchQuery{Key: iam_model.LoginPolicySearchKeyAggregateID, Value: aggregateID, Method: global_model.SearchMethodEquals} @@ -25,6 +38,15 @@ func PutLoginPolicy(db *gorm.DB, table string, policy *model.LoginPolicyView) er return save(db, policy) } +func PutLoginPolicies(db *gorm.DB, table string, policies ...*model.LoginPolicyView) error { + save := repository.PrepareBulkSave(table) + u := make([]interface{}, len(policies)) + for i, user := range policies { + u[i] = user + } + return save(db, u...) +} + func DeleteLoginPolicy(db *gorm.DB, table, aggregateID string) error { delete := repository.PrepareDeleteByKey(table, model.LoginPolicySearchKey(iam_model.LoginPolicySearchKeyAggregateID), aggregateID) diff --git a/internal/iam/repository/view/model/login_policy.go b/internal/iam/repository/view/model/login_policy.go index 7bdc919a87..9291e8faad 100644 --- a/internal/iam/repository/view/model/login_policy.go +++ b/internal/iam/repository/view/model/login_policy.go @@ -16,6 +16,7 @@ import ( const ( LoginPolicyKeyAggregateID = "aggregate_id" + LoginPolicyKeyDefault = "default_policy" ) type LoginPolicyView struct { @@ -31,7 +32,7 @@ type LoginPolicyView struct { PasswordlessType int32 `json:"passwordlessType" gorm:"column:passwordless_type"` SecondFactors pq.Int64Array `json:"-" gorm:"column:second_factors"` MultiFactors pq.Int64Array `json:"-" gorm:"column:multi_factors"` - Default bool `json:"-" gorm:"-"` + Default bool `json:"-" gorm:"column:default_policy"` Sequence uint64 `json:"-" gorm:"column:sequence"` } @@ -106,10 +107,16 @@ func (p *LoginPolicyView) AppendEvent(event *models.Event) (err error) { p.Sequence = event.Sequence p.ChangeDate = event.CreationDate switch event.Type { - case es_model.LoginPolicyAdded, org_es_model.LoginPolicyAdded: + case es_model.LoginPolicyAdded: + p.setRootData(event) + p.CreationDate = event.CreationDate + p.Default = true + err = p.SetData(event) + case org_es_model.LoginPolicyAdded: p.setRootData(event) p.CreationDate = event.CreationDate err = p.SetData(event) + p.Default = false case es_model.LoginPolicyChanged, org_es_model.LoginPolicyChanged: err = p.SetData(event) case es_model.LoginPolicySecondFactorAdded, org_es_model.LoginPolicySecondFactorAdded: @@ -118,7 +125,10 @@ func (p *LoginPolicyView) AppendEvent(event *models.Event) (err error) { if err != nil { return err } - p.SecondFactors = append(p.SecondFactors, int64(mfa.MFAType)) + if !existsMFA(p.SecondFactors, int64(mfa.MFAType)) { + p.SecondFactors = append(p.SecondFactors, int64(mfa.MFAType)) + } + case es_model.LoginPolicySecondFactorRemoved, org_es_model.LoginPolicySecondFactorRemoved: err = p.removeSecondFactor(event) case es_model.LoginPolicyMultiFactorAdded, org_es_model.LoginPolicyMultiFactorAdded: @@ -127,7 +137,9 @@ func (p *LoginPolicyView) AppendEvent(event *models.Event) (err error) { if err != nil { return err } - p.MultiFactors = append(p.MultiFactors, int64(mfa.MFAType)) + if !existsMFA(p.MultiFactors, int64(mfa.MFAType)) { + p.MultiFactors = append(p.MultiFactors, int64(mfa.MFAType)) + } case es_model.LoginPolicyMultiFactorRemoved, org_es_model.LoginPolicyMultiFactorRemoved: err = p.removeMultiFactor(event) } @@ -179,3 +191,12 @@ func (p *LoginPolicyView) removeMultiFactor(event *models.Event) error { } return nil } + +func existsMFA(mfas []int64, mfaType int64) bool { + for _, m := range mfas { + if m == mfaType { + return true + } + } + return false +} diff --git a/internal/iam/repository/view/model/login_policy_query.go b/internal/iam/repository/view/model/login_policy_query.go index dde3431842..5a4c8fff88 100644 --- a/internal/iam/repository/view/model/login_policy_query.go +++ b/internal/iam/repository/view/model/login_policy_query.go @@ -53,6 +53,8 @@ func (key LoginPolicySearchKey) ToColumnName() string { switch iam_model.LoginPolicySearchKey(key) { case iam_model.LoginPolicySearchKeyAggregateID: return LoginPolicyKeyAggregateID + case iam_model.LoginPolicySearchKeyDefault: + return LoginPolicyKeyDefault default: return "" } diff --git a/internal/management/repository/eventsourcing/eventstore/org.go b/internal/management/repository/eventsourcing/eventstore/org.go index c236e55358..de173878fd 100644 --- a/internal/management/repository/eventsourcing/eventstore/org.go +++ b/internal/management/repository/eventsourcing/eventstore/org.go @@ -2,6 +2,7 @@ package eventstore import ( "context" + "github.com/caos/zitadel/internal/v2/domain" "strings" iam_es "github.com/caos/zitadel/internal/iam/repository/eventsourcing" @@ -361,15 +362,23 @@ func (repo *OrgRepository) GetLoginPolicy(ctx context.Context) (*iam_model.Login return iam_es_model.LoginPolicyViewToModel(policy), nil } +func (repo *OrgRepository) GetIDPProvidersByIDPConfigID(ctx context.Context, aggregateID, idpConfigID string) ([]*iam_model.IDPProviderView, error) { + idpProviders, err := repo.View.IDPProvidersByIdpConfigID(aggregateID, idpConfigID) + if err != nil { + return nil, err + } + return iam_view_model.IDPProviderViewsToModel(idpProviders), err +} + func (repo *OrgRepository) GetDefaultLoginPolicy(ctx context.Context) (*iam_model.LoginPolicyView, error) { - policy, viewErr := repo.View.LoginPolicyByAggregateID(repo.SystemDefaults.IamID) + policy, viewErr := repo.View.LoginPolicyByAggregateID(domain.IAMID) if viewErr != nil && !errors.IsNotFound(viewErr) { return nil, viewErr } if errors.IsNotFound(viewErr) { policy = new(iam_es_model.LoginPolicyView) } - events, esErr := repo.IAMEventstore.IAMEventsByID(ctx, repo.SystemDefaults.IamID, policy.Sequence) + events, esErr := repo.IAMEventstore.IAMEventsByID(ctx, domain.IAMID, policy.Sequence) if errors.IsNotFound(viewErr) && len(events) == 0 { return nil, errors.ThrowNotFound(nil, "EVENT-cmO9s", "Errors.IAM.LoginPolicy.NotFound") } @@ -405,11 +414,12 @@ func (repo *OrgRepository) RemoveLoginPolicy(ctx context.Context) error { } func (repo *OrgRepository) SearchIDPProviders(ctx context.Context, request *iam_model.IDPProviderSearchRequest) (*iam_model.IDPProviderSearchResponse, error) { - _, err := repo.View.LoginPolicyByAggregateID(authz.GetCtxData(ctx).OrgID) + policy, err := repo.View.LoginPolicyByAggregateID(authz.GetCtxData(ctx).OrgID) if err != nil { - if errors.IsNotFound(err) { - request.AppendAggregateIDQuery(repo.SystemDefaults.IamID) - } + return nil, err + } + if policy.Default { + request.AppendAggregateIDQuery(domain.IAMID) } else { request.AppendAggregateIDQuery(authz.GetCtxData(ctx).OrgID) } diff --git a/internal/management/repository/eventsourcing/eventstore/project.go b/internal/management/repository/eventsourcing/eventstore/project.go index a014a8e7ec..3c7eb5773e 100644 --- a/internal/management/repository/eventsourcing/eventstore/project.go +++ b/internal/management/repository/eventsourcing/eventstore/project.go @@ -280,6 +280,14 @@ func (repo *ProjectRepo) ProjectGrantByID(ctx context.Context, grantID string) ( return model.ProjectGrantToModel(grant), nil } +func (repo *ProjectRepo) ProjectGrantsByProjectIDAndRoleKey(ctx context.Context, projectID, roleKey string) ([]*proj_model.ProjectGrantView, error) { + grants, err := repo.View.ProjectGrantsByProjectIDAndRoleKey(projectID, roleKey) + if err != nil { + return nil, err + } + return model.ProjectGrantsToModel(grants), nil +} + func (repo *ProjectRepo) SearchProjectGrants(ctx context.Context, request *proj_model.ProjectGrantViewSearchRequest) (*proj_model.ProjectGrantViewSearchResponse, error) { request.EnsureLimit(repo.SearchLimit) sequence, sequenceErr := repo.View.GetLatestProjectGrantSequence() diff --git a/internal/management/repository/eventsourcing/eventstore/user.go b/internal/management/repository/eventsourcing/eventstore/user.go index 9368470587..0cf81890bf 100644 --- a/internal/management/repository/eventsourcing/eventstore/user.go +++ b/internal/management/repository/eventsourcing/eventstore/user.go @@ -78,6 +78,10 @@ func (repo *UserRepo) SearchUsers(ctx context.Context, request *usr_model.UserSe return result, nil } +func (repo *UserRepo) UserIDsByDomain(ctx context.Context, domain string) ([]string, error) { + return repo.View.UserIDsByDomain(domain) +} + func (repo *UserRepo) UserChanges(ctx context.Context, id string, lastSequence uint64, limit uint64, sortAscending bool) (*usr_model.UserChanges, error) { changes, err := repo.UserEvents.UserChanges(ctx, id, lastSequence, limit, sortAscending) if err != nil { @@ -164,6 +168,22 @@ func (repo *UserRepo) SearchExternalIDPs(ctx context.Context, request *usr_model return result, nil } +func (repo *UserRepo) ExternalIDPsByIDPConfigID(ctx context.Context, idpConfigID string) ([]*usr_model.ExternalIDPView, error) { + externalIDPs, err := repo.View.ExternalIDPsByIDPConfigID(idpConfigID) + if err != nil { + return nil, err + } + return model.ExternalIDPViewsToModel(externalIDPs), nil +} + +func (repo *UserRepo) ExternalIDPsByIDPConfigIDAndResourceOwner(ctx context.Context, idpConfigID, resourceOwner string) ([]*usr_model.ExternalIDPView, error) { + externalIDPs, err := repo.View.ExternalIDPsByIDPConfigIDAndResourceOwner(idpConfigID, resourceOwner) + if err != nil { + return nil, err + } + return model.ExternalIDPViewsToModel(externalIDPs), nil +} + func (repo *UserRepo) GetMachineKey(ctx context.Context, userID, keyID string) (*usr_model.MachineKeyView, error) { key, err := repo.View.MachineKeyByIDs(userID, keyID) if err != nil { diff --git a/internal/management/repository/eventsourcing/eventstore/user_grant.go b/internal/management/repository/eventsourcing/eventstore/user_grant.go index 878385be54..0642587167 100644 --- a/internal/management/repository/eventsourcing/eventstore/user_grant.go +++ b/internal/management/repository/eventsourcing/eventstore/user_grant.go @@ -35,6 +35,22 @@ func (repo *UserGrantRepo) UserGrantsByProjectID(ctx context.Context, projectID return model.UserGrantsToModel(grants), nil } +func (repo *UserGrantRepo) UserGrantsByProjectIDAndRoleKey(ctx context.Context, projectID, roleKey string) ([]*grant_model.UserGrantView, error) { + grants, err := repo.View.UserGrantsByProjectIDAndRoleKey(projectID, roleKey) + if err != nil { + return nil, err + } + return model.UserGrantsToModel(grants), nil +} + +func (repo *UserGrantRepo) UserGrantsByProjectAndGrantID(ctx context.Context, projectID, grantID string) ([]*grant_model.UserGrantView, error) { + grants, err := repo.View.UserGrantsByProjectAndGrantID(projectID, grantID) + if err != nil { + return nil, err + } + return model.UserGrantsToModel(grants), nil +} + func (repo *UserGrantRepo) UserGrantsByUserID(ctx context.Context, userID string) ([]*grant_model.UserGrantView, error) { grants, err := repo.View.UserGrantsByUserID(userID) if err != nil { diff --git a/internal/management/repository/eventsourcing/handler/login_policy.go b/internal/management/repository/eventsourcing/handler/login_policy.go index c5a1eae408..ed1d42f654 100644 --- a/internal/management/repository/eventsourcing/handler/login_policy.go +++ b/internal/management/repository/eventsourcing/handler/login_policy.go @@ -1,15 +1,19 @@ package handler import ( + "context" "github.com/caos/logging" + caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore/models" es_models "github.com/caos/zitadel/internal/eventstore/models" "github.com/caos/zitadel/internal/eventstore/query" "github.com/caos/zitadel/internal/eventstore/spooler" + "github.com/caos/zitadel/internal/iam/repository/eventsourcing" iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" iam_model "github.com/caos/zitadel/internal/iam/repository/view/model" "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" + "github.com/caos/zitadel/internal/v2/domain" ) const ( @@ -77,20 +81,48 @@ func (m *LoginPolicy) Reduce(event *models.Event) (err error) { func (m *LoginPolicy) processLoginPolicy(event *models.Event) (err error) { policy := new(iam_model.LoginPolicyView) switch event.Type { + case model.OrgAdded: + policy, err = m.getDefaultLoginPolicy() + if err != nil { + return err + } + policy.AggregateID = event.AggregateID + policy.Default = true case iam_es_model.LoginPolicyAdded, model.LoginPolicyAdded: err = policy.AppendEvent(event) - case iam_es_model.LoginPolicyChanged, model.LoginPolicyChanged, - iam_es_model.LoginPolicySecondFactorAdded, model.LoginPolicySecondFactorAdded, - iam_es_model.LoginPolicySecondFactorRemoved, model.LoginPolicySecondFactorRemoved, - iam_es_model.LoginPolicyMultiFactorAdded, model.LoginPolicyMultiFactorAdded, - iam_es_model.LoginPolicyMultiFactorRemoved, model.LoginPolicyMultiFactorRemoved: + case iam_es_model.LoginPolicyChanged, + iam_es_model.LoginPolicySecondFactorAdded, + iam_es_model.LoginPolicySecondFactorRemoved, + iam_es_model.LoginPolicyMultiFactorAdded, + iam_es_model.LoginPolicyMultiFactorRemoved: + policies, err := m.view.AllDefaultLoginPolicies() + if err != nil { + return err + } + for _, policy := range policies { + err = policy.AppendEvent(event) + if err != nil { + return err + } + } + return m.view.PutLoginPolicies(policies, event) + case model.LoginPolicyChanged, + model.LoginPolicySecondFactorAdded, + model.LoginPolicySecondFactorRemoved, + model.LoginPolicyMultiFactorAdded, + model.LoginPolicyMultiFactorRemoved: policy, err = m.view.LoginPolicyByAggregateID(event.AggregateID) if err != nil { return err } err = policy.AppendEvent(event) case model.LoginPolicyRemoved: - return m.view.DeleteLoginPolicy(event.AggregateID, event) + policy, err = m.getDefaultLoginPolicy() + if err != nil { + return err + } + policy.AggregateID = event.AggregateID + policy.Default = true default: return m.view.ProcessedLoginPolicySequence(event) } @@ -108,3 +140,33 @@ func (m *LoginPolicy) OnError(event *models.Event, err error) error { func (m *LoginPolicy) OnSuccess() error { return spooler.HandleSuccess(m.view.UpdateLoginPolicySpoolerRunTimestamp) } + +func (p *LoginPolicy) getDefaultLoginPolicy() (*iam_model.LoginPolicyView, error) { + policy, policyErr := p.view.LoginPolicyByAggregateID(domain.IAMID) + if policyErr != nil && !caos_errs.IsNotFound(policyErr) { + return nil, policyErr + } + if policy == nil { + policy = &iam_model.LoginPolicyView{} + } + events, err := p.getIAMEvents(policy.Sequence) + if err != nil { + return policy, policyErr + } + policyCopy := *policy + for _, event := range events { + if err := policyCopy.AppendEvent(event); err != nil { + return policy, nil + } + } + return &policyCopy, nil +} + +func (p *LoginPolicy) getIAMEvents(sequence uint64) ([]*models.Event, error) { + query, err := eventsourcing.IAMByIDQuery(domain.IAMID, sequence) + if err != nil { + return nil, err + } + + return p.es.FilterEvents(context.Background(), query) +} diff --git a/internal/management/repository/eventsourcing/view/external_idps.go b/internal/management/repository/eventsourcing/view/external_idps.go index 24fe60c26e..74c1f48f47 100644 --- a/internal/management/repository/eventsourcing/view/external_idps.go +++ b/internal/management/repository/eventsourcing/view/external_idps.go @@ -25,6 +25,9 @@ func (v *View) ExternalIDPsByIDPConfigID(idpConfigID string) ([]*model.ExternalI return view.ExternalIDPsByIDPConfigID(v.Db, externalIDPTable, idpConfigID) } +func (v *View) ExternalIDPsByIDPConfigIDAndResourceOwner(idpConfigID, resourceOwner string) ([]*model.ExternalIDPView, error) { + return view.ExternalIDPsByIDPConfigIDAndResourceOwner(v.Db, externalIDPTable, idpConfigID, resourceOwner) +} func (v *View) ExternalIDPsByUserID(userID string) ([]*model.ExternalIDPView, error) { return view.ExternalIDPsByUserID(v.Db, externalIDPTable, userID) } diff --git a/internal/management/repository/eventsourcing/view/login_policies.go b/internal/management/repository/eventsourcing/view/login_policies.go index 1788a622b3..6ce2ee91bb 100644 --- a/internal/management/repository/eventsourcing/view/login_policies.go +++ b/internal/management/repository/eventsourcing/view/login_policies.go @@ -12,6 +12,10 @@ const ( loginPolicyTable = "management.login_policies" ) +func (v *View) AllDefaultLoginPolicies() ([]*model.LoginPolicyView, error) { + return view.GetDefaultLoginPolicies(v.Db, loginPolicyTable) +} + func (v *View) LoginPolicyByAggregateID(aggregateID string) (*model.LoginPolicyView, error) { return view.GetLoginPolicyByAggregateID(v.Db, loginPolicyTable, aggregateID) } @@ -24,6 +28,14 @@ func (v *View) PutLoginPolicy(policy *model.LoginPolicyView, event *models.Event return v.ProcessedLoginPolicySequence(event) } +func (v *View) PutLoginPolicies(policies []*model.LoginPolicyView, event *models.Event) error { + err := view.PutLoginPolicies(v.Db, loginPolicyTable, policies...) + if err != nil { + return err + } + return v.ProcessedLoginPolicySequence(event) +} + func (v *View) DeleteLoginPolicy(aggregateID string, event *models.Event) error { err := view.DeleteLoginPolicy(v.Db, loginPolicyTable, aggregateID) if err != nil && !errors.IsNotFound(err) { diff --git a/internal/management/repository/eventsourcing/view/user_grant.go b/internal/management/repository/eventsourcing/view/user_grant.go index 1e2f6d587f..6a34a8ee78 100644 --- a/internal/management/repository/eventsourcing/view/user_grant.go +++ b/internal/management/repository/eventsourcing/view/user_grant.go @@ -29,6 +29,10 @@ func (v *View) UserGrantsByProjectID(projectID string) ([]*model.UserGrantView, return view.UserGrantsByProjectID(v.Db, userGrantTable, projectID) } +func (v *View) UserGrantsByProjectAndGrantID(projectID, grantID string) ([]*model.UserGrantView, error) { + return view.UserGrantsByProjectAndGrantID(v.Db, userGrantTable, projectID, grantID) +} + func (v *View) UserGrantsByOrgID(orgID string) ([]*model.UserGrantView, error) { return view.UserGrantsByOrgID(v.Db, userGrantTable, orgID) } diff --git a/internal/management/repository/org.go b/internal/management/repository/org.go index 1292eed15a..955b96d213 100644 --- a/internal/management/repository/org.go +++ b/internal/management/repository/org.go @@ -27,6 +27,7 @@ type OrgRepository interface { GetLoginPolicy(ctx context.Context) (*iam_model.LoginPolicyView, error) GetDefaultLoginPolicy(ctx context.Context) (*iam_model.LoginPolicyView, error) SearchIDPProviders(ctx context.Context, request *iam_model.IDPProviderSearchRequest) (*iam_model.IDPProviderSearchResponse, error) + GetIDPProvidersByIDPConfigID(ctx context.Context, aggregateID, idpConfigID string) ([]*iam_model.IDPProviderView, error) SearchSecondFactors(ctx context.Context) (*iam_model.SecondFactorsSearchResponse, error) SearchMultiFactors(ctx context.Context) (*iam_model.MultiFactorsSearchResponse, error) diff --git a/internal/management/repository/project.go b/internal/management/repository/project.go index 153e133701..8960006f23 100644 --- a/internal/management/repository/project.go +++ b/internal/management/repository/project.go @@ -9,6 +9,8 @@ import ( type ProjectRepository interface { ProjectByID(ctx context.Context, id string) (*model.ProjectView, error) SearchProjects(ctx context.Context, request *model.ProjectViewSearchRequest) (*model.ProjectViewSearchResponse, error) + + ProjectGrantsByProjectIDAndRoleKey(ctx context.Context, projectID, roleKey string) ([]*model.ProjectGrantView, error) SearchProjectGrants(ctx context.Context, request *model.ProjectGrantViewSearchRequest) (*model.ProjectGrantViewSearchResponse, error) SearchGrantedProjects(ctx context.Context, request *model.ProjectGrantViewSearchRequest) (*model.ProjectGrantViewSearchResponse, error) ProjectGrantViewByID(ctx context.Context, grantID string) (*model.ProjectGrantView, error) diff --git a/internal/management/repository/user.go b/internal/management/repository/user.go index b545ecb6d1..976e0097a0 100644 --- a/internal/management/repository/user.go +++ b/internal/management/repository/user.go @@ -9,6 +9,7 @@ import ( type UserRepository interface { UserByID(ctx context.Context, id string) (*model.UserView, error) SearchUsers(ctx context.Context, request *model.UserSearchRequest) (*model.UserSearchResponse, error) + UserIDsByDomain(ctx context.Context, domain string) ([]string, error) GetUserByLoginNameGlobal(ctx context.Context, email string) (*model.UserView, error) IsUserUnique(ctx context.Context, userName, email string) (bool, error) @@ -22,6 +23,8 @@ type UserRepository interface { GetPasswordless(ctx context.Context, userID string) ([]*model.WebAuthNToken, error) SearchExternalIDPs(ctx context.Context, request *model.ExternalIDPSearchRequest) (*model.ExternalIDPSearchResponse, error) + ExternalIDPsByIDPConfigID(ctx context.Context, idpConfigID string) ([]*model.ExternalIDPView, error) + ExternalIDPsByIDPConfigIDAndResourceOwner(ctx context.Context, idpConfigID, resourceOwner string) ([]*model.ExternalIDPView, error) SearchMachineKeys(ctx context.Context, request *model.MachineKeySearchRequest) (*model.MachineKeySearchResponse, error) GetMachineKey(ctx context.Context, userID, keyID string) (*model.MachineKeyView, error) diff --git a/internal/management/repository/user_grant.go b/internal/management/repository/user_grant.go index 00714568a1..2225c7e575 100644 --- a/internal/management/repository/user_grant.go +++ b/internal/management/repository/user_grant.go @@ -9,5 +9,7 @@ type UserGrantRepository interface { UserGrantByID(ctx context.Context, grantID string) (*model.UserGrantView, error) SearchUserGrants(ctx context.Context, request *model.UserGrantSearchRequest) (*model.UserGrantSearchResponse, error) UserGrantsByProjectID(ctx context.Context, projectID string) ([]*model.UserGrantView, error) + UserGrantsByProjectAndGrantID(ctx context.Context, projectID, grantID string) ([]*model.UserGrantView, error) + UserGrantsByProjectIDAndRoleKey(ctx context.Context, projectID, roleKey string) ([]*model.UserGrantView, error) UserGrantsByUserID(ctx context.Context, userID string) ([]*model.UserGrantView, error) } diff --git a/internal/static/i18n/de.yaml b/internal/static/i18n/de.yaml index 07b935bca4..69ebb2f088 100644 --- a/internal/static/i18n/de.yaml +++ b/internal/static/i18n/de.yaml @@ -269,6 +269,7 @@ Errors: NotActive: Benutzer Berechtigung ist nicht aktiv NotInactive: Benutzer Berechtigung ist nicht deaktiviert NoPermissionForProject: Benutzer hat keine Rechte auf diesem Projekt + RoleKeyNotFound: Rolle konnte nicht gefunden werden IDPConfig: AlreadyExists: IDP Konfiguration mit diesem Name existiert bereits Changes: diff --git a/internal/static/i18n/en.yaml b/internal/static/i18n/en.yaml index eb99429d13..266c21bb6c 100644 --- a/internal/static/i18n/en.yaml +++ b/internal/static/i18n/en.yaml @@ -265,6 +265,7 @@ Errors: NotActive: User grant is not active NotInactive: User grant is not deactivated NoPermissionForProject: User has no permissions on this project + RoleKeyNotFound: Role not found IDPConfig: AlreadyExists: IDP Configuration with this name already exists Changes: diff --git a/internal/user/repository/view/external_idp_view.go b/internal/user/repository/view/external_idp_view.go index ce01d3d459..d3669c59b2 100644 --- a/internal/user/repository/view/external_idp_view.go +++ b/internal/user/repository/view/external_idp_view.go @@ -70,6 +70,44 @@ func ExternalIDPsByIDPConfigID(db *gorm.DB, table, idpConfigID string) ([]*model return externalIDPs, err } +func ExternalIDPsByIDPConfigIDAndResourceOwner(db *gorm.DB, table, idpConfigID, resourceOwner string) ([]*model.ExternalIDPView, error) { + externalIDPs := make([]*model.ExternalIDPView, 0) + idpConfigIDQuery := &usr_model.ExternalIDPSearchQuery{ + Key: usr_model.ExternalIDPSearchKeyIdpConfigID, + Method: global_model.SearchMethodEquals, + Value: idpConfigID, + } + orgIDQuery := &usr_model.ExternalIDPSearchQuery{ + Key: usr_model.ExternalIDPSearchKeyResourceOwner, + Method: global_model.SearchMethodEquals, + Value: resourceOwner, + } + query := repository.PrepareSearchQuery(table, model.ExternalIDPSearchRequest{ + Queries: []*usr_model.ExternalIDPSearchQuery{orgIDQuery, idpConfigIDQuery}, + }) + _, err := query(db, &externalIDPs) + return externalIDPs, err +} + +func ExternalIDPsByIDPConfigIDAndResourceOwners(db *gorm.DB, table, idpConfigID string, resourceOwners []string) ([]*model.ExternalIDPView, error) { + externalIDPs := make([]*model.ExternalIDPView, 0) + idpConfigIDQuery := &usr_model.ExternalIDPSearchQuery{ + Key: usr_model.ExternalIDPSearchKeyIdpConfigID, + Method: global_model.SearchMethodEquals, + Value: idpConfigID, + } + orgIDQuery := &usr_model.ExternalIDPSearchQuery{ + Key: usr_model.ExternalIDPSearchKeyResourceOwner, + Method: global_model.SearchMethodIsOneOf, + Value: resourceOwners, + } + query := repository.PrepareSearchQuery(table, model.ExternalIDPSearchRequest{ + Queries: []*usr_model.ExternalIDPSearchQuery{orgIDQuery, idpConfigIDQuery}, + }) + _, err := query(db, &externalIDPs) + return externalIDPs, err +} + func ExternalIDPsByUserID(db *gorm.DB, table, userID string) ([]*model.ExternalIDPView, error) { externalIDPs := make([]*model.ExternalIDPView, 0) orgIDQuery := &usr_model.ExternalIDPSearchQuery{ diff --git a/internal/usergrant/repository/view/user_grant_view.go b/internal/usergrant/repository/view/user_grant_view.go index aa36907b69..ed31215237 100644 --- a/internal/usergrant/repository/view/user_grant_view.go +++ b/internal/usergrant/repository/view/user_grant_view.go @@ -109,6 +109,20 @@ func UserGrantsByProjectAndUserID(db *gorm.DB, table, projectID, userID string) return users, nil } +func UserGrantsByProjectAndGrantID(db *gorm.DB, table, projectID, grantID string) ([]*model.UserGrantView, error) { + users := make([]*model.UserGrantView, 0) + queries := []*grant_model.UserGrantSearchQuery{ + {Key: grant_model.UserGrantSearchKeyProjectID, Value: projectID, Method: global_model.SearchMethodEquals}, + {Key: grant_model.UserGrantSearchKeyGrantID, Value: grantID, Method: global_model.SearchMethodEquals}, + } + query := repository.PrepareSearchQuery(table, model.UserGrantSearchRequest{Queries: queries}) + _, err := query(db, &users) + if err != nil { + return nil, err + } + return users, nil +} + func UserGrantsByProjectIDAndRole(db *gorm.DB, table, projectID, roleKey string) ([]*model.UserGrantView, error) { users := make([]*model.UserGrantView, 0) queries := []*grant_model.UserGrantSearchQuery{ diff --git a/internal/v2/command/iam_idp_config.go b/internal/v2/command/iam_idp_config.go index 11326d7d30..404ed3f072 100644 --- a/internal/v2/command/iam_idp_config.go +++ b/internal/v2/command/iam_idp_config.go @@ -2,6 +2,7 @@ package command import ( "context" + "github.com/caos/zitadel/internal/eventstore/v2" caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/telemetry/tracing" @@ -21,7 +22,6 @@ func (r *CommandSide) AddDefaultIDPConfig(ctx context.Context, config *domain.ID if err != nil { return nil, err } - //TODO: check name unique on aggregate addedConfig := NewIAMIDPConfigWriteModel(idpConfigID) clientSecret, err := crypto.Crypt([]byte(config.OIDCConfig.ClientSecretString), r.idpConfigSecretCrypto) @@ -108,7 +108,7 @@ func (r *CommandSide) ReactivateDefaultIDPConfig(ctx context.Context, idpID stri return r.eventstore.PushAggregate(ctx, existingIDP, iamAgg) } -func (r *CommandSide) RemoveDefaultIDPConfig(ctx context.Context, idpID string) error { +func (r *CommandSide) RemoveDefaultIDPConfig(ctx context.Context, idpID string, idpProviders []*domain.IDPProvider, externalIDPs ...*domain.ExternalIDP) error { existingIDP, err := r.iamIDPConfigWriteModelByID(ctx, idpID) if err != nil { return err @@ -116,10 +116,24 @@ func (r *CommandSide) RemoveDefaultIDPConfig(ctx context.Context, idpID string) if existingIDP.State == domain.IDPConfigStateRemoved || existingIDP.State == domain.IDPConfigStateUnspecified { return caos_errs.ThrowNotFound(nil, "IAM-4M0xy", "Errors.IAM.IDPConfig.NotExisting") } + + aggregates := make([]eventstore.Aggregater, 0) iamAgg := IAMAggregateFromWriteModel(&existingIDP.WriteModel) iamAgg.PushEvents(iam_repo.NewIDPConfigRemovedEvent(ctx, existingIDP.ResourceOwner, idpID, existingIDP.Name)) - return r.eventstore.PushAggregate(ctx, existingIDP, iamAgg) + userAggregates := make([]eventstore.Aggregater, 0) + for _, idpProvider := range idpProviders { + if idpProvider.AggregateID == domain.IAMID { + userAggregates = r.removeIDPProviderFromDefaultLoginPolicy(ctx, iamAgg, idpProvider, true, externalIDPs...) + } + orgAgg := OrgAggregateFromWriteModel(&NewOrgIdentityProviderWriteModel(idpProvider.AggregateID, idpID).WriteModel) + r.removeIDPProviderFromLoginPolicy(ctx, orgAgg, idpID, true) + } + + aggregates = append(aggregates, iamAgg) + aggregates = append(aggregates, userAggregates...) + _, err = r.eventstore.PushAggregates(ctx, aggregates...) + return err } func (r *CommandSide) iamIDPConfigWriteModelByID(ctx context.Context, idpID string) (policy *IAMIDPConfigWriteModel, err error) { diff --git a/internal/v2/command/iam_policy_login.go b/internal/v2/command/iam_policy_login.go index 4efe343394..a43b6d93c0 100644 --- a/internal/v2/command/iam_policy_login.go +++ b/internal/v2/command/iam_policy_login.go @@ -2,6 +2,8 @@ package command import ( "context" + "github.com/caos/logging" + "github.com/caos/zitadel/internal/eventstore/v2" caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/telemetry/tracing" @@ -101,7 +103,7 @@ func (r *CommandSide) AddIDPProviderToDefaultLoginPolicy(ctx context.Context, id return writeModelToIDPProvider(&idpModel.IdentityProviderWriteModel), nil } -func (r *CommandSide) RemoveIDPProviderFromDefaultLoginPolicy(ctx context.Context, idpProvider *domain.IDPProvider) error { +func (r *CommandSide) RemoveIDPProviderFromDefaultLoginPolicy(ctx context.Context, idpProvider *domain.IDPProvider, cascadeExternalIDPs ...*domain.ExternalIDP) error { idpModel := NewIAMIdentityProviderWriteModel(idpProvider.IDPConfigID) err := r.eventstore.FilterToQueryReducer(ctx, idpModel) if err != nil { @@ -110,10 +112,35 @@ func (r *CommandSide) RemoveIDPProviderFromDefaultLoginPolicy(ctx context.Contex if idpModel.State == domain.IdentityProviderStateUnspecified || idpModel.State == domain.IdentityProviderStateRemoved { return caos_errs.ThrowNotFound(nil, "IAM-39fjs", "Errors.IAM.LoginPolicy.IDP.NotExisting") } + + aggregates := make([]eventstore.Aggregater, 0) iamAgg := IAMAggregateFromWriteModel(&idpModel.IdentityProviderWriteModel.WriteModel) iamAgg.PushEvents(iam_repo.NewIdentityProviderRemovedEvent(ctx, idpProvider.IDPConfigID)) - return r.eventstore.PushAggregate(ctx, idpModel, iamAgg) + userAggregates := r.removeIDPProviderFromDefaultLoginPolicy(ctx, iamAgg, idpProvider, false, cascadeExternalIDPs...) + aggregates = append(aggregates, iamAgg) + aggregates = append(aggregates, userAggregates...) + _, err = r.eventstore.PushAggregates(ctx, aggregates...) + return err +} + +func (r *CommandSide) removeIDPProviderFromDefaultLoginPolicy(ctx context.Context, iamAgg *iam_repo.Aggregate, idpProvider *domain.IDPProvider, cascade bool, cascadeExternalIDPs ...*domain.ExternalIDP) []eventstore.Aggregater { + if cascade { + iamAgg.PushEvents(iam_repo.NewIdentityProviderCascadeRemovedEvent(ctx, idpProvider.IDPConfigID)) + return nil + } + iamAgg.PushEvents(iam_repo.NewIdentityProviderRemovedEvent(ctx, idpProvider.IDPConfigID)) + + userAggregates := make([]eventstore.Aggregater, 0) + for _, idp := range cascadeExternalIDPs { + userAgg, _, err := r.removeHumanExternalIDP(ctx, idp, true) + if err != nil { + logging.LogWithFields("COMMAND-4nfsf", "userid", idp.AggregateID, "idp-id", idp.IDPConfigID).WithError(err).Warn("could not cascade remove externalidp in remove provider from policy") + continue + } + userAggregates = append(userAggregates, userAgg) + } + return userAggregates } func (r *CommandSide) AddSecondFactorToDefaultLoginPolicy(ctx context.Context, secondFactor domain.SecondFactorType) (domain.SecondFactorType, error) { @@ -128,7 +155,7 @@ func (r *CommandSide) AddSecondFactorToDefaultLoginPolicy(ctx context.Context, s return domain.SecondFactorTypeUnspecified, err } - return domain.SecondFactorType(secondFactorModel.MFAType), nil + return secondFactorModel.MFAType, nil } func (r *CommandSide) addSecondFactorToDefaultLoginPolicy(ctx context.Context, iamAgg *iam_repo.Aggregate, secondFactorModel *IAMSecondFactorWriteModel, secondFactor domain.SecondFactorType) error { diff --git a/internal/v2/command/org.go b/internal/v2/command/org.go index 27b73938d9..71bfb57e4a 100644 --- a/internal/v2/command/org.go +++ b/internal/v2/command/org.go @@ -2,6 +2,7 @@ package command import ( "context" + "github.com/caos/zitadel/internal/eventstore/v2" caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/v2/domain" @@ -32,17 +33,19 @@ func (r *CommandSide) checkOrgExists(ctx context.Context, orgID string) error { } func (r *CommandSide) SetUpOrg(ctx context.Context, organisation *domain.Org, admin *domain.Human) error { - orgAgg, userAgg, orgMemberAgg, err := r.setUpOrg(ctx, organisation, admin) + orgAgg, userAgg, orgMemberAgg, claimedUsers, err := r.setUpOrg(ctx, organisation, admin) if err != nil { return err } - - _, err = r.eventstore.PushAggregates(ctx, orgAgg, userAgg, orgMemberAgg) + aggregates := make([]eventstore.Aggregater, 0) + aggregates = append(aggregates, orgAgg, userAgg, orgMemberAgg) + aggregates = append(aggregates, claimedUsers...) + _, err = r.eventstore.PushAggregates(ctx, aggregates...) return err } func (r *CommandSide) AddOrg(ctx context.Context, name, userID, resourceOwner string) (*domain.Org, error) { - orgAgg, addedOrg, err := r.addOrg(ctx, &domain.Org{Name: name}) + orgAgg, addedOrg, claimedUsers, err := r.addOrg(ctx, &domain.Org{Name: name}) if err != nil { return nil, err } @@ -56,7 +59,15 @@ func (r *CommandSide) AddOrg(ctx context.Context, name, userID, resourceOwner st if err != nil { return nil, err } - err = r.eventstore.PushAggregate(ctx, addedOrg, orgAgg) + aggregates := make([]eventstore.Aggregater, 0) + aggregates = append(aggregates, orgAgg) + aggregates = append(aggregates, claimedUsers...) + resEvents, err := r.eventstore.PushAggregates(ctx, aggregates...) + if err != nil { + return nil, err + } + addedOrg.AppendEvents(resEvents...) + err = addedOrg.Reduce() if err != nil { return nil, err } @@ -97,46 +108,50 @@ func (r *CommandSide) ReactivateOrg(ctx context.Context, orgID string) error { return r.eventstore.PushAggregate(ctx, orgWriteModel, orgAgg) } -func (r *CommandSide) setUpOrg(ctx context.Context, organisation *domain.Org, admin *domain.Human) (*org.Aggregate, *user.Aggregate, *org.Aggregate, error) { - orgAgg, _, err := r.addOrg(ctx, organisation) +func (r *CommandSide) setUpOrg(ctx context.Context, organisation *domain.Org, admin *domain.Human) (*org.Aggregate, *user.Aggregate, *org.Aggregate, []eventstore.Aggregater, error) { + orgAgg, _, claimedUserAggregates, err := r.addOrg(ctx, organisation) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } userAgg, _, err := r.addHuman(ctx, orgAgg.ID(), admin) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } addedMember := NewOrgMemberWriteModel(orgAgg.ID(), userAgg.ID()) orgMemberAgg := OrgAggregateFromWriteModel(&addedMember.WriteModel) err = r.addOrgMember(ctx, orgMemberAgg, addedMember, domain.NewMember(orgMemberAgg.ID(), userAgg.ID(), domain.RoleOrgOwner)) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } - return orgAgg, userAgg, orgMemberAgg, nil + return orgAgg, userAgg, orgMemberAgg, claimedUserAggregates, nil } -func (r *CommandSide) addOrg(ctx context.Context, organisation *domain.Org) (_ *org.Aggregate, _ *OrgWriteModel, err error) { +func (r *CommandSide) addOrg(ctx context.Context, organisation *domain.Org, claimedUserIDs ...string) (_ *org.Aggregate, _ *OrgWriteModel, _ []eventstore.Aggregater, err error) { if organisation == nil || !organisation.IsValid() { - return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMM-deLSk", "Errors.Org.Invalid") + return nil, nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMM-deLSk", "Errors.Org.Invalid") } organisation.AggregateID, err = r.idGenerator.Next() if err != nil { - return nil, nil, caos_errs.ThrowInternal(err, "COMMA-OwciI", "Errors.Internal") + return nil, nil, nil, caos_errs.ThrowInternal(err, "COMMA-OwciI", "Errors.Internal") } organisation.AddIAMDomain(r.iamDomain) addedOrg := NewOrgWriteModel(organisation.AggregateID) orgAgg := OrgAggregateFromWriteModel(&addedOrg.WriteModel) orgAgg.PushEvents(org.NewOrgAddedEvent(ctx, organisation.Name)) + claimedUserAggregates := make([]eventstore.Aggregater, 0) for _, orgDomain := range organisation.Domains { - if err := r.addOrgDomain(ctx, orgAgg, NewOrgDomainWriteModel(orgAgg.ID(), orgDomain.Domain), orgDomain); err != nil { - return nil, nil, err + aggregates, err := r.addOrgDomain(ctx, orgAgg, NewOrgDomainWriteModel(orgAgg.ID(), orgDomain.Domain), orgDomain, claimedUserIDs...) + if err != nil { + return nil, nil, nil, err + } else { + claimedUserAggregates = append(claimedUserAggregates, aggregates...) } } - return orgAgg, addedOrg, nil + return orgAgg, addedOrg, claimedUserAggregates, nil } func (r *CommandSide) getOrgWriteModelByID(ctx context.Context, orgID string) (*OrgWriteModel, error) { diff --git a/internal/v2/command/org_domain.go b/internal/v2/command/org_domain.go index 8fda17066c..370e51d923 100644 --- a/internal/v2/command/org_domain.go +++ b/internal/v2/command/org_domain.go @@ -2,6 +2,7 @@ package command import ( "context" + "github.com/caos/zitadel/internal/eventstore/v2" "github.com/caos/logging" @@ -15,14 +16,27 @@ import ( func (r *CommandSide) AddOrgDomain(ctx context.Context, orgDomain *domain.OrgDomain) (*domain.OrgDomain, error) { domainWriteModel := NewOrgDomainWriteModel(orgDomain.AggregateID, orgDomain.Domain) orgAgg := OrgAggregateFromWriteModel(&domainWriteModel.WriteModel) - err := r.addOrgDomain(ctx, orgAgg, domainWriteModel, orgDomain) + userAggregates, err := r.addOrgDomain(ctx, orgAgg, domainWriteModel, orgDomain) if err != nil { return nil, err } - err = r.eventstore.PushAggregate(ctx, domainWriteModel, orgAgg) + if len(userAggregates) == 0 { + err = r.eventstore.PushAggregate(ctx, domainWriteModel, orgAgg) + if err != nil { + return nil, err + } + return orgDomainWriteModelToOrgDomain(domainWriteModel), nil + } + + aggregates := make([]eventstore.Aggregater, 0) + aggregates = append(aggregates, orgAgg) + aggregates = append(aggregates, userAggregates...) + resultEvents, err := r.eventstore.PushAggregates(ctx, aggregates...) if err != nil { return nil, err } + domainWriteModel.AppendEvents(resultEvents...) + domainWriteModel.Reduce() return orgDomainWriteModelToOrgDomain(domainWriteModel), nil } @@ -63,7 +77,7 @@ func (r *CommandSide) GenerateOrgDomainValidation(ctx context.Context, orgDomain return token, url, nil } -func (r *CommandSide) ValidateOrgDomain(ctx context.Context, orgDomain *domain.OrgDomain) error { +func (r *CommandSide) ValidateOrgDomain(ctx context.Context, orgDomain *domain.OrgDomain, claimedUserIDs ...string) error { if orgDomain == nil || !orgDomain.IsValid() { return caos_errs.ThrowPreconditionFailed(nil, "ORG-R24hb", "Errors.Org.InvalidDomain") } @@ -89,8 +103,20 @@ func (r *CommandSide) ValidateOrgDomain(ctx context.Context, orgDomain *domain.O err = r.domainVerificationValidator(domainWriteModel.Domain, validationCode, validationCode, checkType) orgAgg := OrgAggregateFromWriteModel(&domainWriteModel.WriteModel) if err == nil { + aggregates := make([]eventstore.Aggregater, 0) orgAgg.PushEvents(org.NewDomainVerifiedEvent(ctx, orgDomain.Domain)) - return r.eventstore.PushAggregate(ctx, domainWriteModel, orgAgg) + aggregates = append(aggregates, orgAgg) + + for _, userID := range claimedUserIDs { + userAgg, _, err := r.userDomainClaimed(ctx, userID) + if err != nil { + logging.LogWithFields("COMMAND-5m8fs", "userid", userID).WithError(err).Warn("could not claim user") + continue + } + aggregates = append(aggregates, userAgg) + } + _, err = r.eventstore.PushAggregates(ctx, aggregates...) + return err } orgAgg.PushEvents(org.NewDomainVerificationFailedEvent(ctx, orgDomain.Domain)) err = r.eventstore.PushAggregate(ctx, domainWriteModel, orgAgg) @@ -136,24 +162,33 @@ func (r *CommandSide) RemoveOrgDomain(ctx context.Context, orgDomain *domain.Org return r.eventstore.PushAggregate(ctx, domainWriteModel, orgAgg) } -func (r *CommandSide) addOrgDomain(ctx context.Context, orgAgg *org.Aggregate, addedDomain *OrgDomainWriteModel, orgDomain *domain.OrgDomain) error { +func (r *CommandSide) addOrgDomain(ctx context.Context, orgAgg *org.Aggregate, addedDomain *OrgDomainWriteModel, orgDomain *domain.OrgDomain, claimedUserIDs ...string) ([]eventstore.Aggregater, error) { err := r.eventstore.FilterToQueryReducer(ctx, addedDomain) if err != nil { - return err + return nil, err } if addedDomain.State == domain.OrgDomainStateActive { - return caos_errs.ThrowAlreadyExists(nil, "COMMA-Bd2jj", "Errors.Org.Domain.AlreadyExists") + return nil, caos_errs.ThrowAlreadyExists(nil, "COMMA-Bd2jj", "Errors.Org.Domain.AlreadyExists") } + orgAgg.PushEvents(org.NewDomainAddedEvent(ctx, orgDomain.Domain)) + + userAggregates := make([]eventstore.Aggregater, 0) if orgDomain.Verified { - //TODO: uniqueness verified domain - //TODO: users with verified domain -> domain claimed orgAgg.PushEvents(org.NewDomainVerifiedEvent(ctx, orgDomain.Domain)) + for _, userID := range claimedUserIDs { + userAgg, _, err := r.userDomainClaimed(ctx, userID) + if err != nil { + logging.LogWithFields("COMMAND-nn8Jf", "userid", userID).WithError(err).Warn("could not claim user") + continue + } + userAggregates = append(userAggregates, userAgg) + } } if orgDomain.Primary { orgAgg.PushEvents(org.NewDomainPrimarySetEvent(ctx, orgDomain.Domain)) } - return nil + return userAggregates, nil } func (r *CommandSide) getOrgDomainWriteModel(ctx context.Context, orgID, domain string) (*OrgDomainWriteModel, error) { diff --git a/internal/v2/command/org_idp_config.go b/internal/v2/command/org_idp_config.go index 119b4c925c..6e19ab913f 100644 --- a/internal/v2/command/org_idp_config.go +++ b/internal/v2/command/org_idp_config.go @@ -2,6 +2,7 @@ package command import ( "context" + "github.com/caos/zitadel/internal/eventstore/v2" caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/telemetry/tracing" @@ -21,7 +22,6 @@ func (r *CommandSide) AddIDPConfig(ctx context.Context, config *domain.IDPConfig if err != nil { return nil, err } - //TODO: check name unique on aggregate addedConfig := NewOrgIDPConfigWriteModel(idpConfigID, config.AggregateID) clientSecret, err := crypto.Crypt([]byte(config.OIDCConfig.ClientSecretString), r.idpConfigSecretCrypto) @@ -109,7 +109,7 @@ func (r *CommandSide) ReactivateIDPConfig(ctx context.Context, idpID, orgID stri return r.eventstore.PushAggregate(ctx, existingIDP, orgAgg) } -func (r *CommandSide) RemoveIDPConfig(ctx context.Context, idpID, orgID string) error { +func (r *CommandSide) RemoveIDPConfig(ctx context.Context, idpID, orgID string, cascadeRemoveProvider bool, cascadeExternalIDPs ...*domain.ExternalIDP) error { existingIDP, err := r.orgIDPConfigWriteModelByID(ctx, idpID, orgID) if err != nil { return err @@ -121,10 +121,20 @@ func (r *CommandSide) RemoveIDPConfig(ctx context.Context, idpID, orgID string) if existingIDP.State != domain.IDPConfigStateInactive { return caos_errs.ThrowPreconditionFailed(nil, "Org-5Mo0d", "Errors.Org.IDPConfig.NotInactive") } + + aggregates := make([]eventstore.Aggregater, 0) orgAgg := OrgAggregateFromWriteModel(&existingIDP.WriteModel) orgAgg.PushEvents(org_repo.NewIDPConfigRemovedEvent(ctx, existingIDP.ResourceOwner, idpID, existingIDP.Name)) - return r.eventstore.PushAggregate(ctx, existingIDP, orgAgg) + userAggregates := make([]eventstore.Aggregater, 0) + if cascadeRemoveProvider { + userAggregates = r.removeIDPProviderFromLoginPolicy(ctx, orgAgg, idpID, true, cascadeExternalIDPs...) + } + aggregates = append(aggregates, orgAgg) + aggregates = append(aggregates, userAggregates...) + + _, err = r.eventstore.PushAggregates(ctx, aggregates...) + return err } func (r *CommandSide) orgIDPConfigWriteModelByID(ctx context.Context, idpID, orgID string) (policy *OrgIDPConfigWriteModel, err error) { diff --git a/internal/v2/command/org_policy_login.go b/internal/v2/command/org_policy_login.go index 2bb8e47696..7188b6129d 100644 --- a/internal/v2/command/org_policy_login.go +++ b/internal/v2/command/org_policy_login.go @@ -2,6 +2,8 @@ package command import ( "context" + "github.com/caos/logging" + "github.com/caos/zitadel/internal/eventstore/v2" caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/v2/domain" @@ -38,7 +40,7 @@ func (r *CommandSide) ChangeLoginPolicy(ctx context.Context, policy *domain.Logi if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved { return nil, caos_errs.ThrowNotFound(nil, "Org-M0sif", "Errors.Org.LoginPolicy.NotFound") } - changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIDP, policy.ForceMFA, domain.PasswordlessType(policy.PasswordlessType)) + changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, policy.AllowUsernamePassword, policy.AllowRegister, policy.AllowExternalIDP, policy.ForceMFA, policy.PasswordlessType) if !hasChanged { return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-5M9vdd", "Errors.Org.LoginPolicy.NotChanged") } @@ -89,7 +91,7 @@ func (r *CommandSide) AddIDPProviderToLoginPolicy(ctx context.Context, idpProvid return writeModelToIDPProvider(&idpModel.IdentityProviderWriteModel), nil } -func (r *CommandSide) RemoveIDPProviderFromLoginPolicy(ctx context.Context, idpProvider *domain.IDPProvider) error { +func (r *CommandSide) RemoveIDPProviderFromLoginPolicy(ctx context.Context, idpProvider *domain.IDPProvider, cascadeExternalIDPs ...*domain.ExternalIDP) error { idpModel := NewOrgIdentityProviderWriteModel(idpProvider.AggregateID, idpProvider.IDPConfigID) err := r.eventstore.FilterToQueryReducer(ctx, idpModel) if err != nil { @@ -98,10 +100,36 @@ func (r *CommandSide) RemoveIDPProviderFromLoginPolicy(ctx context.Context, idpP if idpModel.State == domain.IdentityProviderStateUnspecified || idpModel.State == domain.IdentityProviderStateRemoved { return caos_errs.ThrowNotFound(nil, "Org-39fjs", "Errors.Org.LoginPolicy.IDP.NotExisting") } - orgAgg := OrgAggregateFromWriteModel(&idpModel.IdentityProviderWriteModel.WriteModel) - orgAgg.PushEvents(org.NewIdentityProviderRemovedEvent(ctx, idpProvider.IDPConfigID)) - return r.eventstore.PushAggregate(ctx, idpModel, orgAgg) + aggregates := make([]eventstore.Aggregater, 0) + orgAgg := OrgAggregateFromWriteModel(&idpModel.IdentityProviderWriteModel.WriteModel) + userAggregates := r.removeIDPProviderFromLoginPolicy(ctx, orgAgg, idpProvider.IDPConfigID, false, cascadeExternalIDPs...) + + aggregates = append(aggregates, orgAgg) + aggregates = append(aggregates, userAggregates...) + + _, err = r.eventstore.PushAggregates(ctx, aggregates...) + return err +} + +func (r *CommandSide) removeIDPProviderFromLoginPolicy(ctx context.Context, orgAgg *org.Aggregate, idpConfigID string, cascade bool, cascadeExternalIDPs ...*domain.ExternalIDP) []eventstore.Aggregater { + if cascade { + orgAgg.PushEvents(org.NewIdentityProviderCascadeRemovedEvent(ctx, idpConfigID)) + + } else { + orgAgg.PushEvents(org.NewIdentityProviderRemovedEvent(ctx, idpConfigID)) + } + + userAggregates := make([]eventstore.Aggregater, 0) + for _, idp := range cascadeExternalIDPs { + userAgg, _, err := r.removeHumanExternalIDP(ctx, idp, true) + if err != nil { + logging.LogWithFields("COMMAND-n8RRf", "userid", idp.AggregateID, "idpconfigid", idp.IDPConfigID).WithError(err).Warn("could not cascade remove external idp") + continue + } + userAggregates = append(userAggregates, userAgg) + } + return userAggregates } func (r *CommandSide) AddSecondFactorToLoginPolicy(ctx context.Context, secondFactor domain.SecondFactorType, orgID string) (domain.SecondFactorType, error) { diff --git a/internal/v2/command/project.go b/internal/v2/command/project.go index cf072b0ae3..45d1668ed8 100644 --- a/internal/v2/command/project.go +++ b/internal/v2/command/project.go @@ -2,6 +2,7 @@ package command import ( "context" + "github.com/caos/logging" "github.com/caos/zitadel/internal/eventstore/v2" caos_errs "github.com/caos/zitadel/internal/errors" @@ -145,7 +146,7 @@ func (r *CommandSide) ReactivateProject(ctx context.Context, projectID string, r return r.eventstore.PushAggregate(ctx, existingProject, projectAgg) } -func (r *CommandSide) RemoveProject(ctx context.Context, projectID, resourceOwner string, cascadingGrantIDs ...string) error { +func (r *CommandSide) RemoveProject(ctx context.Context, projectID, resourceOwner string, cascadingUserGrantIDs ...string) error { if projectID == "" || resourceOwner == "" { return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-66hM9", "Errors.Project.ProjectIDMissing") } @@ -163,10 +164,11 @@ func (r *CommandSide) RemoveProject(ctx context.Context, projectID, resourceOwne projectAgg.PushEvents(project.NewProjectRemovedEvent(ctx, existingProject.Name, existingProject.ResourceOwner)) aggregates = append(aggregates, projectAgg) - for _, grantID := range cascadingGrantIDs { + for _, grantID := range cascadingUserGrantIDs { grantAgg, _, err := r.removeUserGrant(ctx, grantID, "", true) if err != nil { - return err + logging.LogWithFields("COMMAND-b8Djf", "usergrantid", grantID).WithError(err).Warn("could not cascade remove user grant") + continue } aggregates = append(aggregates, grantAgg) } diff --git a/internal/v2/command/project_grant.go b/internal/v2/command/project_grant.go index d11dafdf8c..8d5d0d3ac7 100644 --- a/internal/v2/command/project_grant.go +++ b/internal/v2/command/project_grant.go @@ -2,10 +2,13 @@ package command import ( "context" + "github.com/caos/logging" caos_errs "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore/v2" "github.com/caos/zitadel/internal/telemetry/tracing" "github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/internal/v2/repository/project" + "github.com/caos/zitadel/internal/v2/repository/usergrant" "reflect" ) @@ -41,7 +44,7 @@ func (r *CommandSide) AddProjectGrant(ctx context.Context, grant *domain.Project return projectGrantWriteModelToProjectGrant(addedGrant), nil } -func (r *CommandSide) ChangeProjectGrant(ctx context.Context, grant *domain.ProjectGrant, resourceOwner string) (_ *domain.ProjectGrant, err error) { +func (r *CommandSide) ChangeProjectGrant(ctx context.Context, grant *domain.ProjectGrant, resourceOwner string, cascadeUserGrantIDs ...string) (_ *domain.ProjectGrant, err error) { if grant.GrantID == "" { return nil, caos_errs.ThrowPreconditionFailed(nil, "PROJECT-1j83s", "Errors.IDMissing") } @@ -58,16 +61,75 @@ func (r *CommandSide) ChangeProjectGrant(ctx context.Context, grant *domain.Proj if reflect.DeepEqual(existingGrant.RoleKeys, grant.RoleKeys) { return nil, caos_errs.ThrowPreconditionFailed(nil, "PROJECT-0o0pL", "Errors.NoChangesFoundc") } + projectAgg.PushEvents(project.NewGrantChangedEvent(ctx, grant.GrantID, grant.RoleKeys)) - //TODO: Change UserGrants (if role removed should be removed from user grant) - err = r.eventstore.PushAggregate(ctx, existingGrant, projectAgg) + + removedRoles := domain.GetRemovedRoles(existingGrant.RoleKeys, grant.RoleKeys) + if len(removedRoles) == 0 { + err = r.eventstore.PushAggregate(ctx, existingGrant, projectAgg) + if err != nil { + return nil, err + } + return projectGrantWriteModelToProjectGrant(existingGrant), nil + } + + aggregates := make([]eventstore.Aggregater, 0) + aggregates = append(aggregates, projectAgg) + for _, userGrantID := range cascadeUserGrantIDs { + grantAgg, _, err := r.removeRoleFromUserGrant(ctx, userGrantID, removedRoles, true) + if err != nil { + continue + } + aggregates = append(aggregates, grantAgg) + } + resultEvents, err := r.eventstore.PushAggregates(ctx, aggregates...) + if err != nil { + return nil, err + } + existingGrant.AppendEvents(resultEvents...) + err = existingGrant.Reduce() if err != nil { return nil, err } - return projectGrantWriteModelToProjectGrant(existingGrant), nil } +func (r *CommandSide) removeRoleFromProjectGrant(ctx context.Context, projectAgg *project.Aggregate, projectID, projectGrantID, roleKey string, cascade bool) (_ *ProjectGrantWriteModel, err error) { + existingProjectGrant, err := r.projectGrantWriteModelByID(ctx, projectID, projectGrantID, "") + if err != nil { + return nil, err + } + if existingProjectGrant.State == domain.ProjectGrantStateUnspecified || existingProjectGrant.State == domain.ProjectGrantStateRemoved { + return nil, caos_errs.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.Project.Grant.NotFound") + } + keyExists := false + for i, key := range existingProjectGrant.RoleKeys { + if key == roleKey { + keyExists = true + copy(existingProjectGrant.RoleKeys[i:], existingProjectGrant.RoleKeys[i+1:]) + existingProjectGrant.RoleKeys[len(existingProjectGrant.RoleKeys)-1] = "" + existingProjectGrant.RoleKeys = existingProjectGrant.RoleKeys[:len(existingProjectGrant.RoleKeys)-1] + continue + } + } + if !keyExists { + return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-5m8g9", "Errors.Project.Grant.RoleKeyNotFound") + } + changedProjectGrant := NewProjectGrantWriteModel(projectGrantID, projectID, existingProjectGrant.ResourceOwner) + + if !cascade { + projectAgg.PushEvents( + project.NewGrantChangedEvent(ctx, projectGrantID, existingProjectGrant.RoleKeys), + ) + } else { + projectAgg.PushEvents( + usergrant.NewUserGrantCascadeChangedEvent(ctx, existingProjectGrant.RoleKeys), + ) + } + + return changedProjectGrant, nil +} + func (r *CommandSide) DeactivateProjectGrant(ctx context.Context, projectID, grantID, resourceOwner string) (err error) { if grantID == "" || projectID == "" { return caos_errs.ThrowPreconditionFailed(nil, "PROJECT-p0s4V", "Errors.IDMissing") @@ -109,7 +171,7 @@ func (r *CommandSide) ReactivateProjectGrant(ctx context.Context, projectID, gra return r.eventstore.PushAggregate(ctx, existingGrant, projectAgg) } -func (r *CommandSide) RemoveProjectGrant(ctx context.Context, projectID, grantID, resourceOwner string) (err error) { +func (r *CommandSide) RemoveProjectGrant(ctx context.Context, projectID, grantID, resourceOwner string, cascadeUserGrantIDs ...string) (err error) { if grantID == "" || projectID == "" { return caos_errs.ThrowPreconditionFailed(nil, "PROJECT-1m9fJ", "Errors.IDMissing") } @@ -121,10 +183,21 @@ func (r *CommandSide) RemoveProjectGrant(ctx context.Context, projectID, grantID if err != nil { return err } + aggregates := make([]eventstore.Aggregater, 0) projectAgg := ProjectAggregateFromWriteModel(&existingGrant.WriteModel) projectAgg.PushEvents(project.NewGrantRemovedEvent(ctx, grantID, existingGrant.GrantedOrgID, projectID)) - //TODO: Cascade Remove usergrants - return r.eventstore.PushAggregate(ctx, existingGrant, projectAgg) + aggregates = append(aggregates, projectAgg) + + for _, userGrantID := range cascadeUserGrantIDs { + grantAgg, _, err := r.removeUserGrant(ctx, userGrantID, "", true) + if err != nil { + logging.LogWithFields("COMMAND-3m8sG", "usergrantid", grantID).WithError(err).Warn("could not cascade remove user grant") + continue + } + aggregates = append(aggregates, grantAgg) + } + _, err = r.eventstore.PushAggregates(ctx, aggregates...) + return err } func (r *CommandSide) projectGrantWriteModelByID(ctx context.Context, grantID, projectID, resourceOwner string) (member *ProjectGrantWriteModel, err error) { diff --git a/internal/v2/command/project_grant_model.go b/internal/v2/command/project_grant_model.go index bab4efa7d5..3222bd001f 100644 --- a/internal/v2/command/project_grant_model.go +++ b/internal/v2/command/project_grant_model.go @@ -90,15 +90,18 @@ func (wm *ProjectGrantWriteModel) Reduce() error { } func (wm *ProjectGrantWriteModel) Query() *eventstore.SearchQueryBuilder { - return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, project.AggregateType). + query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, project.AggregateType). AggregateIDs(wm.AggregateID). - ResourceOwner(wm.ResourceOwner) - //EventTypes( - // project.GrantAddedType, - // project.GrantChangedType, - // project.GrantCascadeChangedType, - // project.GrantDeactivatedType, - // project.GrantReactivatedType, - // project.GrantRemovedType, - // project.ProjectRemovedType) + EventTypes( + project.GrantAddedType, + project.GrantChangedType, + project.GrantCascadeChangedType, + project.GrantDeactivatedType, + project.GrantReactivatedType, + project.GrantRemovedType, + project.ProjectRemovedType) + if wm.ResourceOwner != "" { + query.ResourceOwner(wm.ResourceOwner) + } + return query } diff --git a/internal/v2/command/project_role.go b/internal/v2/command/project_role.go index ce48b6cb87..1568feea40 100644 --- a/internal/v2/command/project_role.go +++ b/internal/v2/command/project_role.go @@ -2,7 +2,9 @@ package command import ( "context" + "github.com/caos/logging" caos_errs "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore/v2" "github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/internal/v2/repository/project" ) @@ -92,7 +94,7 @@ func (r *CommandSide) ChangeProjectRole(ctx context.Context, projectRole *domain return roleWriteModelToRole(existingRole), nil } -func (r *CommandSide) RemoveProjectRole(ctx context.Context, projectID, key, resourceOwner string) (err error) { +func (r *CommandSide) RemoveProjectRole(ctx context.Context, projectID, key, resourceOwner string, cascadingProjectGrantIds []string, cascadeUserGrantIDs ...string) (err error) { if projectID == "" || key == "" { return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4m9vS", "Errors.Project.Role.Invalid") } @@ -103,12 +105,29 @@ func (r *CommandSide) RemoveProjectRole(ctx context.Context, projectID, key, res if existingRole.State == domain.ProjectRoleStateUnspecified || existingRole.State == domain.ProjectRoleStateRemoved { return caos_errs.ThrowNotFound(nil, "COMMAND-m9vMf", "Errors.Project.Role.NotExisting") } - + aggregates := make([]eventstore.Aggregater, 0) projectAgg := ProjectAggregateFromWriteModel(&existingRole.WriteModel) projectAgg.PushEvents(project.NewRoleRemovedEvent(ctx, key, projectID, existingRole.ResourceOwner)) - //TODO: Update UserGrants (remove roles if on usergrants) + for _, projectGrantID := range cascadingProjectGrantIds { + _, err = r.removeRoleFromProjectGrant(ctx, projectAgg, projectID, projectGrantID, key, true) + if err != nil { + logging.LogWithFields("COMMAND-6n77g", "projectgrantid", projectGrantID).WithError(err).Warn("could not cascade remove role from project grant") + continue + } + } + aggregates = append(aggregates, projectAgg) - return r.eventstore.PushAggregate(ctx, existingRole, projectAgg) + for _, grantID := range cascadeUserGrantIDs { + grantAgg, _, err := r.removeRoleFromUserGrant(ctx, grantID, []string{key}, true) + if err != nil { + logging.LogWithFields("COMMAND-mK0of", "usergrantid", grantID).WithError(err).Warn("could not cascade remove role on user grant") + continue + } + aggregates = append(aggregates, grantAgg) + } + + _, err = r.eventstore.PushAggregates(ctx, aggregates...) + return err } func (r *CommandSide) getProjectRoleWriteModelByID(ctx context.Context, key, projectID, resourceOwner string) (*ProjectRoleWriteModel, error) { diff --git a/internal/v2/command/setup_step1.go b/internal/v2/command/setup_step1.go index bd0b1eb942..aff1001220 100644 --- a/internal/v2/command/setup_step1.go +++ b/internal/v2/command/setup_step1.go @@ -100,7 +100,7 @@ func (r *CommandSide) SetupStep1(ctx context.Context, step1 *Step1) error { //create orgs aggregates := make([]eventstore.Aggregater, 0) for _, organisation := range step1.Orgs { - orgAgg, userAgg, orgMemberAgg, err := r.setUpOrg(ctx, + orgAgg, userAgg, orgMemberAgg, claimedUsers, err := r.setUpOrg(ctx, &domain.Org{ Name: organisation.Name, Domains: []*domain.OrgDomain{{Domain: organisation.Domain}}, @@ -131,6 +131,7 @@ func (r *CommandSide) SetupStep1(ctx context.Context, step1 *Step1) error { } } aggregates = append(aggregates, orgAgg, userAgg, orgMemberAgg) + aggregates = append(aggregates, claimedUsers...) if organisation.Name == step1.GlobalOrg { err = r.setGlobalOrg(ctx, iamAgg, iamWriteModel, orgAgg.ID()) if err != nil { diff --git a/internal/v2/command/user.go b/internal/v2/command/user.go index f02e0fb604..8e7a9bda42 100644 --- a/internal/v2/command/user.go +++ b/internal/v2/command/user.go @@ -2,8 +2,11 @@ package command import ( "context" + "fmt" + "github.com/caos/logging" auth_req_model "github.com/caos/zitadel/internal/auth_request/model" "github.com/caos/zitadel/internal/eventstore/models" + "github.com/caos/zitadel/internal/eventstore/v2" "strings" "time" @@ -121,7 +124,7 @@ func (r *CommandSide) UnlockUser(ctx context.Context, userID, resourceOwner stri return r.eventstore.PushAggregate(ctx, existingUser, userAgg) } -func (r *CommandSide) RemoveUser(ctx context.Context, userID, resourceOwner string) error { +func (r *CommandSide) RemoveUser(ctx context.Context, userID, resourceOwner string, cascadingGrantIDs ...string) error { if userID == "" { return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M0ds", "Errors.User.UserIDMissing") } @@ -136,11 +139,22 @@ func (r *CommandSide) RemoveUser(ctx context.Context, userID, resourceOwner stri if err != nil { return err } + aggregates := make([]eventstore.Aggregater, 0) userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel) userAgg.PushEvents(user.NewUserRemovedEvent(ctx, existingUser.ResourceOwner, existingUser.UserName, orgIAMPolicy.UserLoginMustBeDomain)) - //TODO: remove user grants + aggregates = append(aggregates, userAgg) - return r.eventstore.PushAggregate(ctx, existingUser, userAgg) + for _, grantID := range cascadingGrantIDs { + grantAgg, _, err := r.removeUserGrant(ctx, grantID, "", true) + if err != nil { + logging.LogWithFields("COMMAND-5m9oL", "usergrantid", grantID).WithError(err).Warn("could not cascade remove role on user grant") + continue + } + aggregates = append(aggregates, grantAgg) + } + + _, err = r.eventstore.PushAggregates(ctx, aggregates...) + return err } func (r *CommandSide) CreateUserToken(ctx context.Context, orgID, agentID, clientID, userID string, audience, scopes []string, lifetime time.Duration) (*domain.Token, error) { @@ -193,6 +207,25 @@ func (r *CommandSide) CreateUserToken(ctx context.Context, orgID, agentID, clien }, nil } +func (r *CommandSide) userDomainClaimed(ctx context.Context, userID string) (_ *user.Aggregate, _ *UserWriteModel, err error) { + existingUser, err := r.userWriteModelByID(ctx, userID, "") + if err != nil { + return nil, nil, err + } + if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted { + return nil, nil, caos_errs.ThrowNotFound(nil, "COMMAND-ii9K0", "Errors.User.NotFound") + } + changedUserGrant := NewUserWriteModel(userID, existingUser.ResourceOwner) + userAgg := UserAggregateFromWriteModel(&changedUserGrant.WriteModel) + + id, err := r.idGenerator.Next() + if err != nil { + return nil, nil, err + } + userAgg.PushEvents(user.NewDomainClaimedEvent(ctx, fmt.Sprintf("%s@temporary.%s", id, r.iamDomain))) + return userAgg, changedUserGrant, nil +} + func (r *CommandSide) UserDomainClaimedSent(ctx context.Context, orgID, userID string) (err error) { existingUser, err := r.userWriteModelByID(ctx, userID, orgID) if err != nil { diff --git a/internal/v2/command/user_grant.go b/internal/v2/command/user_grant.go index 9c460c52c7..160389873d 100644 --- a/internal/v2/command/user_grant.go +++ b/internal/v2/command/user_grant.go @@ -108,6 +108,45 @@ func (r *CommandSide) changeUserGrant(ctx context.Context, userGrant *domain.Use return userGrantAgg, changedUserGrant, nil } +func (r *CommandSide) removeRoleFromUserGrant(ctx context.Context, userGrantID string, roleKeys []string, cascade bool) (_ *usergrant.Aggregate, _ *UserGrantWriteModel, err error) { + existingUserGrant, err := r.userGrantWriteModelByID(ctx, userGrantID, "") + if err != nil { + return nil, nil, err + } + if existingUserGrant.State == domain.UserGrantStateUnspecified || existingUserGrant.State == domain.UserGrantStateRemoved { + return nil, nil, caos_errs.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.UserGrant.NotFound") + } + keyExists := false + for i, key := range existingUserGrant.RoleKeys { + for _, roleKey := range roleKeys { + if key == roleKey { + keyExists = true + copy(existingUserGrant.RoleKeys[i:], existingUserGrant.RoleKeys[i+1:]) + existingUserGrant.RoleKeys[len(existingUserGrant.RoleKeys)-1] = "" + existingUserGrant.RoleKeys = existingUserGrant.RoleKeys[:len(existingUserGrant.RoleKeys)-1] + continue + } + } + } + if !keyExists { + return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-5m8g9", "Errors.UserGrant.RoleKeyNotFound") + } + changedUserGrant := NewUserGrantWriteModel(userGrantID, "") + userGrantAgg := UserGrantAggregateFromWriteModel(&changedUserGrant.WriteModel) + + if !cascade { + userGrantAgg.PushEvents( + usergrant.NewUserGrantChangedEvent(ctx, existingUserGrant.RoleKeys), + ) + } else { + userGrantAgg.PushEvents( + usergrant.NewUserGrantCascadeChangedEvent(ctx, existingUserGrant.RoleKeys), + ) + } + + return userGrantAgg, changedUserGrant, nil +} + func (r *CommandSide) DeactivateUserGrant(ctx context.Context, grantID, resourceOwner string) (err error) { if grantID == "" || resourceOwner == "" { return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-M0dsf", "Errors.UserGrant.IDMissing") @@ -197,10 +236,13 @@ func (r *CommandSide) removeUserGrant(ctx context.Context, grantID, resourceOwne if err != nil { return nil, nil, err } - err = checkExplicitProjectPermission(ctx, existingUserGrant.ProjectGrantID, existingUserGrant.ProjectID) - if err != nil { - return nil, nil, err + if !cascade { + err = checkExplicitProjectPermission(ctx, existingUserGrant.ProjectGrantID, existingUserGrant.ProjectID) + if err != nil { + return nil, nil, err + } } + if existingUserGrant.State == domain.UserGrantStateUnspecified || existingUserGrant.State == domain.UserGrantStateRemoved { return nil, nil, caos_errs.ThrowNotFound(nil, "COMMAND-1My0t", "Errors.UserGrant.NotFound") } diff --git a/internal/v2/command/user_human_externalidp.go b/internal/v2/command/user_human_externalidp.go index 95e8c5c75b..7edbcc5863 100644 --- a/internal/v2/command/user_human_externalidp.go +++ b/internal/v2/command/user_human_externalidp.go @@ -10,7 +10,7 @@ import ( ) func (r *CommandSide) BulkAddedHumanExternalIDP(ctx context.Context, userID, resourceOwner string, externalIDPs []*domain.ExternalIDP) error { - if len(externalIDPs) == 0 { + if externalIDPs == nil || len(externalIDPs) == 0 { return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Ek9s", "Errors.User.ExternalIDP.MinimumExternalIDPNeeded") } aggregates := make([]eventstore.Aggregater, len(externalIDPs)) @@ -38,20 +38,24 @@ func (r *CommandSide) addHumanExternalIDP(ctx context.Context, userAgg *user.Agg } func (r *CommandSide) RemoveHumanExternalIDP(ctx context.Context, externalIDP *domain.ExternalIDP) error { - return r.removeHumanExternalIDP(ctx, externalIDP, false) + userAgg, writemodel, err := r.removeHumanExternalIDP(ctx, externalIDP, false) + if err != nil { + return err + } + return r.eventstore.PushAggregate(ctx, writemodel, userAgg) } -func (r *CommandSide) removeHumanExternalIDP(ctx context.Context, externalIDP *domain.ExternalIDP, cascade bool) error { +func (r *CommandSide) removeHumanExternalIDP(ctx context.Context, externalIDP *domain.ExternalIDP, cascade bool) (*user.Aggregate, *HumanExternalIDPWriteModel, error) { if externalIDP.IsValid() { - return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M9ds", "Errors.IDMissing") + return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M9ds", "Errors.IDMissing") } existingExternalIDP, err := r.externalIDPWriteModelByID(ctx, externalIDP.AggregateID, externalIDP.IDPConfigID, externalIDP.ExternalUserID, externalIDP.ResourceOwner) if err != nil { - return err + return nil, nil, err } if existingExternalIDP.State == domain.ExternalIDPStateUnspecified || existingExternalIDP.State == domain.ExternalIDPStateRemoved { - return caos_errs.ThrowNotFound(nil, "COMMAND-1M9xR", "Errors.User.ExternalIDP.NotFound") + return nil, nil, caos_errs.ThrowNotFound(nil, "COMMAND-1M9xR", "Errors.User.ExternalIDP.NotFound") } userAgg := UserAggregateFromWriteModel(&existingExternalIDP.WriteModel) if !cascade { @@ -63,7 +67,7 @@ func (r *CommandSide) removeHumanExternalIDP(ctx context.Context, externalIDP *d user.NewHumanExternalIDPCascadeRemovedEvent(ctx, externalIDP.IDPConfigID, externalIDP.ExternalUserID), ) } - return r.eventstore.PushAggregate(ctx, existingExternalIDP, userAgg) + return userAgg, existingExternalIDP, nil } func (r *CommandSide) HumanExternalLoginChecked(ctx context.Context, orgID, userID string, authRequest *domain.AuthRequest) (err error) { diff --git a/internal/v2/domain/project_grant.go b/internal/v2/domain/project_grant.go index a5c8cb6e6f..e5b61126e5 100644 --- a/internal/v2/domain/project_grant.go +++ b/internal/v2/domain/project_grant.go @@ -28,3 +28,22 @@ const ( func (p *ProjectGrant) IsValid() bool { return p.GrantedOrgID != "" } + +func GetRemovedRoles(existingRoles, newRoles []string) []string { + removed := make([]string, 0) + for _, role := range existingRoles { + if !containsKey(newRoles, role) { + removed = append(removed, role) + } + } + return removed +} + +func containsKey(roles []string, key string) bool { + for _, role := range roles { + if role == key { + return true + } + } + return false +} diff --git a/internal/v2/repository/iam/eventstore.go b/internal/v2/repository/iam/eventstore.go index 1994adb22d..8a73336c54 100644 --- a/internal/v2/repository/iam/eventstore.go +++ b/internal/v2/repository/iam/eventstore.go @@ -29,5 +29,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) { RegisterFilterEventMapper(IDPConfigDeactivatedEventType, IDPConfigDeactivatedEventMapper). RegisterFilterEventMapper(IDPConfigReactivatedEventType, IDPConfigReactivatedEventMapper). RegisterFilterEventMapper(IDPOIDCConfigAddedEventType, IDPOIDCConfigAddedEventMapper). - RegisterFilterEventMapper(IDPOIDCConfigChangedEventType, IDPOIDCConfigChangedEventMapper) + RegisterFilterEventMapper(IDPOIDCConfigChangedEventType, IDPOIDCConfigChangedEventMapper). + RegisterFilterEventMapper(LoginPolicyIDPProviderAddedEventType, IdentityProviderAddedEventMapper). + RegisterFilterEventMapper(LoginPolicyIDPProviderRemovedEventType, IdentityProviderRemovedEventMapper). + RegisterFilterEventMapper(LoginPolicyIDPProviderCascadeRemovedEventType, IdentityProviderCascadeRemovedEventMapper) } diff --git a/internal/v2/repository/iam/policy_login_identity_provider.go b/internal/v2/repository/iam/policy_login_identity_provider.go index 68004e539f..44c115fd42 100644 --- a/internal/v2/repository/iam/policy_login_identity_provider.go +++ b/internal/v2/repository/iam/policy_login_identity_provider.go @@ -9,8 +9,9 @@ import ( ) var ( - LoginPolicyIDPProviderAddedEventType = iamEventTypePrefix + policy.LoginPolicyIDPProviderAddedType - LoginPolicyIDPProviderRemovedEventType = iamEventTypePrefix + policy.LoginPolicyIDPProviderRemovedType + LoginPolicyIDPProviderAddedEventType = iamEventTypePrefix + policy.LoginPolicyIDPProviderAddedType + LoginPolicyIDPProviderRemovedEventType = iamEventTypePrefix + policy.LoginPolicyIDPProviderRemovedType + LoginPolicyIDPProviderCascadeRemovedEventType = iamEventTypePrefix + policy.LoginPolicyIDPProviderCascadeRemovedType ) type IdentityProviderAddedEvent struct { @@ -67,3 +68,29 @@ func IdentityProviderRemovedEventMapper(event *repository.Event) (eventstore.Eve IdentityProviderRemovedEvent: *e.(*policy.IdentityProviderRemovedEvent), }, nil } + +type IdentityProviderCascadeRemovedEvent struct { + policy.IdentityProviderCascadeRemovedEvent +} + +func NewIdentityProviderCascadeRemovedEvent( + ctx context.Context, + idpConfigID string, +) *IdentityProviderCascadeRemovedEvent { + return &IdentityProviderCascadeRemovedEvent{ + IdentityProviderCascadeRemovedEvent: *policy.NewIdentityProviderCascadeRemovedEvent( + eventstore.NewBaseEventForPush(ctx, LoginPolicyIDPProviderCascadeRemovedEventType), + idpConfigID), + } +} + +func IdentityProviderCascadeRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) { + e, err := policy.IdentityProviderCascadeRemovedEventMapper(event) + if err != nil { + return nil, err + } + + return &IdentityProviderCascadeRemovedEvent{ + IdentityProviderCascadeRemovedEvent: *e.(*policy.IdentityProviderCascadeRemovedEvent), + }, nil +} diff --git a/internal/v2/repository/org/domain.go b/internal/v2/repository/org/domain.go index 6bbf4a589b..4c92c33eed 100644 --- a/internal/v2/repository/org/domain.go +++ b/internal/v2/repository/org/domain.go @@ -12,6 +12,7 @@ import ( ) const ( + uniqueOrgDomain = "org_domain" domainEventPrefix = orgEventTypePrefix + "domain." OrgDomainAddedEventType = domainEventPrefix + "added" OrgDomainVerificationAddedEventType = domainEventPrefix + "verification.added" @@ -21,6 +22,19 @@ const ( OrgDomainRemovedEventType = domainEventPrefix + "removed" ) +func NewAddOrgDomainUniqueConstraint(orgDomain string) *eventstore.EventUniqueConstraint { + return eventstore.NewAddEventUniqueConstraint( + uniqueOrgDomain, + orgDomain, + "Errors.Org.Domain.AlreadyExists") +} + +func NewRemoveOrgDomainUniqueConstraint(orgDomain string) *eventstore.EventUniqueConstraint { + return eventstore.NewRemoveEventUniqueConstraint( + uniqueOrgDomain, + orgDomain) +} + type DomainAddedEvent struct { eventstore.BaseEvent `json:"-"` @@ -70,7 +84,7 @@ func (e *DomainVerificationAddedEvent) Data() interface{} { } func (e *DomainVerificationAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { - return nil + return []*eventstore.EventUniqueConstraint{NewAddOrgDomainUniqueConstraint(e.Domain)} } func NewDomainVerificationAddedEvent( @@ -212,7 +226,8 @@ func DomainPrimarySetEventMapper(event *repository.Event) (eventstore.EventReade type DomainRemovedEvent struct { eventstore.BaseEvent `json:"-"` - Domain string `json:"domain,omitempty"` + Domain string `json:"domain,omitempty"` + isVerified bool } func (e *DomainRemovedEvent) Data() interface{} { @@ -220,7 +235,10 @@ func (e *DomainRemovedEvent) Data() interface{} { } func (e *DomainRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { - return nil + if !e.isVerified { + return nil + } + return []*eventstore.EventUniqueConstraint{NewRemoveOrgDomainUniqueConstraint(e.Domain)} } func NewDomainRemovedEvent(ctx context.Context, domain string) *DomainRemovedEvent { diff --git a/internal/v2/repository/org/eventstore.go b/internal/v2/repository/org/eventstore.go index d8a676d581..9659bfb19f 100644 --- a/internal/v2/repository/org/eventstore.go +++ b/internal/v2/repository/org/eventstore.go @@ -31,6 +31,7 @@ func RegisterEventMappers(es *eventstore.Eventstore) { RegisterFilterEventMapper(LoginPolicyMultiFactorRemovedEventType, MultiFactorRemovedEventEventMapper). RegisterFilterEventMapper(LoginPolicyIDPProviderAddedEventType, IdentityProviderAddedEventMapper). RegisterFilterEventMapper(LoginPolicyIDPProviderRemovedEventType, IdentityProviderRemovedEventMapper). + RegisterFilterEventMapper(LoginPolicyIDPProviderCascadeRemovedEventType, IdentityProviderCascadeRemovedEventMapper). RegisterFilterEventMapper(OrgIAMPolicyAddedEventType, OrgIAMPolicyAddedEventMapper). RegisterFilterEventMapper(OrgIAMPolicyChangedEventType, OrgIAMPolicyChangedEventMapper). RegisterFilterEventMapper(OrgIAMPolicyRemovedEventType, OrgIAMPolicyRemovedEventMapper). diff --git a/internal/v2/repository/org/org.go b/internal/v2/repository/org/org.go index 3ef4221cee..ee6a4f18ce 100644 --- a/internal/v2/repository/org/org.go +++ b/internal/v2/repository/org/org.go @@ -18,12 +18,6 @@ const ( OrgRemovedEventType = orgEventTypePrefix + "removed" ) -type OrgnameUniqueConstraint struct { - uniqueType string - orgName string - action eventstore.UniqueConstraintAction -} - func NewAddOrgNameUniqueConstraint(orgName string) *eventstore.EventUniqueConstraint { return eventstore.NewAddEventUniqueConstraint( uniqueOrgname, diff --git a/internal/v2/repository/org/policy_login_identity_provider.go b/internal/v2/repository/org/policy_login_identity_provider.go index ea15506feb..7112566693 100644 --- a/internal/v2/repository/org/policy_login_identity_provider.go +++ b/internal/v2/repository/org/policy_login_identity_provider.go @@ -9,8 +9,9 @@ import ( ) var ( - LoginPolicyIDPProviderAddedEventType = orgEventTypePrefix + policy.LoginPolicyIDPProviderAddedType - LoginPolicyIDPProviderRemovedEventType = orgEventTypePrefix + policy.LoginPolicyIDPProviderRemovedType + LoginPolicyIDPProviderAddedEventType = orgEventTypePrefix + policy.LoginPolicyIDPProviderAddedType + LoginPolicyIDPProviderRemovedEventType = orgEventTypePrefix + policy.LoginPolicyIDPProviderRemovedType + LoginPolicyIDPProviderCascadeRemovedEventType = orgEventTypePrefix + policy.LoginPolicyIDPProviderCascadeRemovedType ) type IdentityProviderAddedEvent struct { @@ -67,3 +68,29 @@ func IdentityProviderRemovedEventMapper(event *repository.Event) (eventstore.Eve IdentityProviderRemovedEvent: *e.(*policy.IdentityProviderRemovedEvent), }, nil } + +type IdentityProviderCascadeRemovedEvent struct { + policy.IdentityProviderCascadeRemovedEvent +} + +func NewIdentityProviderCascadeRemovedEvent( + ctx context.Context, + idpConfigID string, +) *IdentityProviderCascadeRemovedEvent { + return &IdentityProviderCascadeRemovedEvent{ + IdentityProviderCascadeRemovedEvent: *policy.NewIdentityProviderCascadeRemovedEvent( + eventstore.NewBaseEventForPush(ctx, LoginPolicyIDPProviderRemovedEventType), + idpConfigID), + } +} + +func IdentityProviderCascadeRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) { + e, err := policy.IdentityProviderCascadeRemovedEventMapper(event) + if err != nil { + return nil, err + } + + return &IdentityProviderCascadeRemovedEvent{ + IdentityProviderCascadeRemovedEvent: *e.(*policy.IdentityProviderCascadeRemovedEvent), + }, nil +} diff --git a/internal/v2/repository/policy/policy_login_identity_provider.go b/internal/v2/repository/policy/policy_login_identity_provider.go index e780316eaa..2b923bdf2c 100644 --- a/internal/v2/repository/policy/policy_login_identity_provider.go +++ b/internal/v2/repository/policy/policy_login_identity_provider.go @@ -9,9 +9,10 @@ import ( ) const ( - loginPolicyIDPProviderPrevix = loginPolicyPrefix + "idpprovider." - LoginPolicyIDPProviderAddedType = loginPolicyIDPProviderPrevix + "added" - LoginPolicyIDPProviderRemovedType = loginPolicyIDPProviderPrevix + "removed" + loginPolicyIDPProviderPrevix = loginPolicyPrefix + "idpprovider." + LoginPolicyIDPProviderAddedType = loginPolicyIDPProviderPrevix + "added" + LoginPolicyIDPProviderRemovedType = loginPolicyIDPProviderPrevix + "removed" + LoginPolicyIDPProviderCascadeRemovedType = loginPolicyIDPProviderPrevix + "cascade.removed" ) type IdentityProviderAddedEvent struct { @@ -91,3 +92,40 @@ func IdentityProviderRemovedEventMapper(event *repository.Event) (eventstore.Eve return e, nil } + +type IdentityProviderCascadeRemovedEvent struct { + eventstore.BaseEvent + + IDPConfigID string `json:"idpConfigId"` +} + +func (e *IdentityProviderCascadeRemovedEvent) Data() interface{} { + return e +} + +func (e *IdentityProviderCascadeRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + +func NewIdentityProviderCascadeRemovedEvent( + base *eventstore.BaseEvent, + idpConfigID string, +) *IdentityProviderCascadeRemovedEvent { + return &IdentityProviderCascadeRemovedEvent{ + BaseEvent: *base, + IDPConfigID: idpConfigID, + } +} + +func IdentityProviderCascadeRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) { + e := &IdentityProviderCascadeRemovedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(event), + } + + err := json.Unmarshal(event.Data, e) + if err != nil { + return nil, errors.ThrowInternal(err, "PROVI-7M9fs", "Errors.Internal") + } + + return e, nil +} diff --git a/migrations/cockroach/V1.27__unique_tables.sql b/migrations/cockroach/V1.29__unique_tables.sql similarity index 100% rename from migrations/cockroach/V1.27__unique_tables.sql rename to migrations/cockroach/V1.29__unique_tables.sql diff --git a/migrations/cockroach/V1.30__policies.sql b/migrations/cockroach/V1.30__policies.sql new file mode 100644 index 0000000000..36b161d424 --- /dev/null +++ b/migrations/cockroach/V1.30__policies.sql @@ -0,0 +1,3 @@ +ALTER TABLE management.login_policies ADD COLUMN default_policy BOOLEAN; +ALTER TABLE adminapi.login_policies ADD COLUMN default_policy BOOLEAN; +ALTER TABLE auth.login_policies ADD COLUMN default_policy BOOLEAN; diff --git a/migrations/cockroach/migrate_local.go b/migrations/cockroach/migrate_local.go index 97554514b7..786e47a253 100644 --- a/migrations/cockroach/migrate_local.go +++ b/migrations/cockroach/migrate_local.go @@ -2,4 +2,4 @@ package migrations -//go:generate flyway -url=jdbc:postgresql://localhost:26257/defaultdb -user=root -password= -locations=filesystem:./ migrate +//go:generate flyway -url=jdbc:postgresql://localhost:26257/defaultdb -user=root -password= -locations=filesystem:./ -placeholders.eventstorepassword=NULL -placeholders.managementpassword=NULL -placeholders.adminapipassword=NULL -placeholders.authpassword=NULL -placeholders.notificationpassword=NULL -placeholders.authzpassword=NULL migrate