zitadel/internal/command/system_model.go
Livio Spring 95481c2e0b
feat: allow system config changes (#3876)
* feat: run repeatable setup steps

* feat: react to system config changes

* renaming
2022-07-20 11:20:49 +02:00

185 lines
6.9 KiB
Go

package command
import (
"strings"
"github.com/zitadel/zitadel/internal/command/preparation"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/project"
)
type SystemConfigWriteModel struct {
eventstore.WriteModel
Instances map[string]*systemConfigChangesInstanceModel
externalDomain string
externalPort uint16
externalSecure bool
newExternalDomain string
newExternalPort uint16
newExternalSecure bool
}
type systemConfigChangesInstanceModel struct {
Domains []string
GeneratedDomain string
ProjectID string
ConsoleAppID string
RedirectUris []string
PostLogoutRedirectUris []string
}
func NewSystemConfigWriteModel(externalDomain, newExternalDomain string, externalPort, newExternalPort uint16, externalSecure, newExternalSecure bool) *SystemConfigWriteModel {
return &SystemConfigWriteModel{
WriteModel: eventstore.WriteModel{},
Instances: make(map[string]*systemConfigChangesInstanceModel),
externalDomain: externalDomain,
externalPort: externalPort,
externalSecure: externalSecure,
newExternalDomain: newExternalDomain,
newExternalPort: newExternalPort,
newExternalSecure: newExternalSecure,
}
}
func (wm *SystemConfigWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *instance.InstanceAddedEvent:
wm.Instances[e.Aggregate().InstanceID] = &systemConfigChangesInstanceModel{}
case *instance.InstanceRemovedEvent:
delete(wm.Instances, e.Aggregate().InstanceID)
case *instance.DomainAddedEvent:
if !e.Generated && e.Domain != wm.externalDomain && e.Domain != wm.newExternalDomain {
continue
}
if e.Generated && !strings.HasSuffix(e.Domain, wm.externalDomain) && !strings.HasSuffix(e.Domain, wm.newExternalDomain) {
continue
}
wm.Instances[e.Aggregate().InstanceID].Domains = append(wm.Instances[e.Aggregate().InstanceID].Domains, e.Domain)
case *instance.DomainRemovedEvent:
domains := wm.Instances[e.Aggregate().InstanceID].Domains
for i, domain := range domains {
if domain == e.Domain {
domains[i] = domains[len(domains)-1]
domains[len(domains)-1] = ""
wm.Instances[e.Aggregate().InstanceID].Domains = domains[:len(domains)-1]
break
}
}
case *instance.ProjectSetEvent:
wm.Instances[e.Aggregate().InstanceID].ProjectID = e.ProjectID
case *instance.ConsoleSetEvent:
wm.Instances[e.Aggregate().InstanceID].ConsoleAppID = e.AppID
case *project.OIDCConfigAddedEvent:
if wm.Instances[e.Aggregate().InstanceID].ConsoleAppID != e.AppID {
continue
}
wm.Instances[e.Aggregate().InstanceID].RedirectUris = e.RedirectUris
wm.Instances[e.Aggregate().InstanceID].PostLogoutRedirectUris = e.PostLogoutRedirectUris
case *project.OIDCConfigChangedEvent:
if wm.Instances[e.Aggregate().InstanceID].ConsoleAppID != e.AppID {
continue
}
if e.RedirectUris != nil {
wm.Instances[e.Aggregate().InstanceID].RedirectUris = *e.RedirectUris
}
if e.PostLogoutRedirectUris != nil {
wm.Instances[e.Aggregate().InstanceID].PostLogoutRedirectUris = *e.PostLogoutRedirectUris
}
}
}
return nil
}
func (wm *SystemConfigWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
AddQuery().
AggregateTypes(instance.AggregateType).
EventTypes(
instance.InstanceAddedEventType,
instance.InstanceRemovedEventType,
instance.InstanceDomainAddedEventType,
instance.InstanceDomainRemovedEventType,
instance.ProjectSetEventType,
instance.ConsoleSetEventType,
).
Or().
AggregateTypes(project.AggregateType).
EventTypes(
project.OIDCConfigAddedType,
project.OIDCConfigChangedType,
).
Builder()
}
type SystemConfigChangesValidation struct {
ProjectID string
ConsoleAppID string
Validations []preparation.Validation
InstanceID string
}
func (wm *SystemConfigWriteModel) NewChangedEvents(commands *Commands) map[string]*SystemConfigChangesValidation {
var newCustomDomainExists, isInstanceOfCustomDomain bool
var instanceOfCustomDomain string
cmds := make(map[string]*SystemConfigChangesValidation)
for i, inst := range wm.Instances {
cmds[i] = &SystemConfigChangesValidation{
InstanceID: i,
ProjectID: inst.ProjectID,
ConsoleAppID: inst.ConsoleAppID,
}
//check each instance separately for changes (using the generated domain) and check if there's an existing custom domain
newCustomDomainExists, isInstanceOfCustomDomain = wm.changeConfig(cmds[i], inst, commands)
if isInstanceOfCustomDomain || newCustomDomainExists {
instanceOfCustomDomain = i
}
}
//handle the custom domain at last
if newCustomDomainExists {
//if the domain itself already exists, then only check if the uris of the console app exist as well
wm.changeURIs(cmds[instanceOfCustomDomain], wm.Instances[instanceOfCustomDomain], commands, wm.newExternalDomain)
return cmds
}
//otherwise the add instance domain will take care of the uris
cmds[instanceOfCustomDomain].Validations = append(cmds[instanceOfCustomDomain].Validations, commands.addInstanceDomain(instance.NewAggregate(instanceOfCustomDomain), wm.newExternalDomain, false))
return cmds
}
func (wm *SystemConfigWriteModel) changeConfig(validation *SystemConfigChangesValidation, inst *systemConfigChangesInstanceModel, commands *Commands) (newCustomDomainExists, isInstanceOfCustomDomain bool) {
var newGeneratedDomain string
var newGeneratedDomainExists bool
for _, domain := range inst.Domains {
if domain == wm.newExternalDomain {
newCustomDomainExists = true
continue
}
if domain != wm.newExternalDomain && strings.HasSuffix(domain, wm.newExternalDomain) {
newGeneratedDomainExists = true
}
if !newCustomDomainExists && domain == wm.externalDomain {
isInstanceOfCustomDomain = true
}
if domain != wm.externalDomain && strings.HasSuffix(domain, wm.externalDomain) {
newGeneratedDomain = strings.TrimSuffix(domain, wm.externalDomain) + wm.newExternalDomain
}
}
if newGeneratedDomainExists {
//if the domain itself already exists, then only check if the uris of the console app exist as well
wm.changeURIs(validation, inst, commands, newGeneratedDomain)
return newCustomDomainExists, isInstanceOfCustomDomain
}
//otherwise the add instance domain will take care of the uris
validation.Validations = append(validation.Validations, commands.addInstanceDomain(instance.NewAggregate(validation.InstanceID), newGeneratedDomain, true))
return newCustomDomainExists, isInstanceOfCustomDomain
}
func (wm *SystemConfigWriteModel) changeURIs(validation *SystemConfigChangesValidation, inst *systemConfigChangesInstanceModel, commands *Commands, domain string) {
if commands.checkUpdateConsoleRedirectURIs(domain, inst.RedirectUris, inst.PostLogoutRedirectUris) {
return
}
validation.Validations = append(validation.Validations, commands.prepareUpdateConsoleRedirectURIs(domain))
}