diff --git a/cmd/setup/51.go b/cmd/setup/51.go new file mode 100644 index 0000000000..0ce3ede3bd --- /dev/null +++ b/cmd/setup/51.go @@ -0,0 +1,38 @@ +package setup + +import ( + "context" + "embed" + "fmt" + + "github.com/zitadel/logging" + + "github.com/zitadel/zitadel/internal/database" + "github.com/zitadel/zitadel/internal/eventstore" +) + +type InitPermittedOrgsFunction struct { + eventstoreClient *database.DB +} + +var ( + //go:embed 51/*.sql + permittedOrgsFunction embed.FS +) +var nc (mig *InitPermittedOrgsFunction) Execute(ctx context.Context, _ eventstore.Event) error { + statements, err := readStatements(permittedOrgsFunction, "51", "") + if err != nil { + return err + } + for _, stmt := range statements { + logging.WithFields("file", stmt.file, "migration", mig.String()).Info("execute statement") + if _, err := mig.eventstoreClient.ExecContext(ctx, stmt.query); err != nil { + return fmt.Errorf("%s %s: %w", mig.String(), stmt.file, err) + } + } + return nil +} + +func (*InitPermittedOrgsFunction) String() string { + return "51_init_permitted_orgs_function" +} diff --git a/cmd/setup/51/01-permitted_orgs_function.sql b/cmd/setup/51/01-permitted_orgs_function.sql new file mode 100644 index 0000000000..d7e0a910ff --- /dev/null +++ b/cmd/setup/51/01-permitted_orgs_function.sql @@ -0,0 +1,77 @@ +DROP FUNCTION IF EXISTS eventstore.permitted_orgs; + +CREATE OR REPLACE FUNCTION eventstore.permitted_orgs( + instanceId TEXT + , userId TEXT + , perm TEXT + , system_roles TEXT[] + , filter_orgs TEXT + + , org_ids OUT TEXT[] +) + LANGUAGE 'plpgsql' + STABLE +AS $$ +DECLARE + matched_roles TEXT[]; -- roles containing permission +BEGIN + SELECT array_agg(rp.role) INTO matched_roles + FROM eventstore.role_permissions rp + WHERE rp.instance_id = instanceId + AND rp.permission = perm; + + IF system_roles IS NOT NULL THEN + DECLARE + permission_found_in_system_roles bool; + BEGIN + SELECT result.role_found INTO permission_found_in_system_roles + FROM (SELECT matched_roles && system_roles AS role_found) AS result; + + IF permission_found_in_system_roles THEN + SELECT array_agg(o.org_id) INTO org_ids + FROM eventstore.instance_orgs o + WHERE o.instance_id = instanceId + AND CASE WHEN filter_orgs != '' + THEN o.org_id IN (filter_orgs) + ELSE TRUE END; + END IF; + END; + RETURN; + END IF; + + -- First try if the permission was granted thru an instance-level role + DECLARE + has_instance_permission bool; + BEGIN + SELECT true INTO has_instance_permission + FROM eventstore.instance_members im + WHERE im.role = ANY(matched_roles) + AND im.instance_id = instanceId + AND im.user_id = userId + LIMIT 1; + + IF has_instance_permission THEN + -- Return all organizations or only those in filter_orgs + SELECT array_agg(o.org_id) INTO org_ids + FROM eventstore.instance_orgs o + WHERE o.instance_id = instanceId + AND CASE WHEN filter_orgs != '' + THEN o.org_id IN (filter_orgs) + ELSE TRUE END; + RETURN; + END IF; + END; + + -- Return the organizations where permission were granted thru org-level roles + SELECT array_agg(org_id) INTO org_ids + FROM ( + SELECT DISTINCT om.org_id + FROM eventstore.org_members om + WHERE om.role = ANY(matched_roles) + AND om.instance_id = instanceID + AND om.user_id = userId + ); + RETURN; +END; +$$; + diff --git a/cmd/setup/config.go b/cmd/setup/config.go index 6706d219e6..9db79015ec 100644 --- a/cmd/setup/config.go +++ b/cmd/setup/config.go @@ -139,6 +139,7 @@ type Steps struct { s48Apps7SAMLConfigsLoginVersion *Apps7SAMLConfigsLoginVersion s49InitPermittedOrgsFunction *InitPermittedOrgsFunction s50IDPTemplate6UsePKCE *IDPTemplate6UsePKCE + s51InitPermittedOrgsFunction *InitPermittedOrgsFunction } func MustNewSteps(v *viper.Viper) *Steps { diff --git a/cmd/setup/setup.go b/cmd/setup/setup.go index b693df3022..f12d329c5d 100644 --- a/cmd/setup/setup.go +++ b/cmd/setup/setup.go @@ -177,6 +177,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string) steps.s48Apps7SAMLConfigsLoginVersion = &Apps7SAMLConfigsLoginVersion{dbClient: dbClient} steps.s49InitPermittedOrgsFunction = &InitPermittedOrgsFunction{eventstoreClient: dbClient} steps.s50IDPTemplate6UsePKCE = &IDPTemplate6UsePKCE{dbClient: dbClient} + steps.s51InitPermittedOrgsFunction = &InitPermittedOrgsFunction{eventstoreClient: dbClient} err = projection.Create(ctx, dbClient, eventstoreClient, config.Projections, nil, nil, nil) logging.OnError(err).Fatal("unable to start projections") @@ -216,6 +217,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string) steps.s47FillMembershipFields, steps.s49InitPermittedOrgsFunction, steps.s50IDPTemplate6UsePKCE, + steps.s51InitPermittedOrgsFunction, } { mustExecuteMigration(ctx, eventstoreClient, step, "migration failed") }