mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
## Problem description During tests we found concurrency issues in the relational table projections. Tests got flaky if a table (e.g. organization domains) had too many foreign keys to tables which were projected by another handler. ## Resolution The projections of the relational tables got merged into one handler to enforce sequential reduction of events. ## Additional info - closes https://github.com/zitadel/zitadel/issues/11703 - closes https://github.com/zitadel/zitadel/issues/10810 --------- Co-authored-by: Marco Ardizzone <[email protected]>
238 lines
9.2 KiB
Go
238 lines
9.2 KiB
Go
package projection
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"slices"
|
|
|
|
"github.com/muhlemmer/gu"
|
|
|
|
"github.com/zitadel/zitadel/backend/v3/domain"
|
|
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
|
v3_sql "github.com/zitadel/zitadel/backend/v3/storage/database/dialect/sql"
|
|
"github.com/zitadel/zitadel/backend/v3/storage/database/repository"
|
|
"github.com/zitadel/zitadel/internal/eventstore"
|
|
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
|
|
"github.com/zitadel/zitadel/internal/repository/idp"
|
|
"github.com/zitadel/zitadel/internal/repository/instance"
|
|
"github.com/zitadel/zitadel/internal/repository/org"
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
)
|
|
|
|
func (p *relationalTablesProjection) reduceLDAPIDPAdded(event eventstore.Event) (*handler.Statement, error) {
|
|
var orgId *string
|
|
var idpEvent idp.LDAPIDPAddedEvent
|
|
switch e := event.(type) {
|
|
case *org.LDAPIDPAddedEvent:
|
|
idpEvent = e.LDAPIDPAddedEvent
|
|
orgId = &idpEvent.Aggregate().ResourceOwner
|
|
case *instance.LDAPIDPAddedEvent:
|
|
idpEvent = e.LDAPIDPAddedEvent
|
|
default:
|
|
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-9s02m1", "reduce.wrong.event.type %v", []eventstore.EventType{org.LDAPIDPAddedEventType, instance.LDAPIDPAddedEventType})
|
|
}
|
|
|
|
ldap := domain.LDAP{
|
|
Servers: idpEvent.Servers,
|
|
StartTLS: idpEvent.StartTLS,
|
|
BaseDN: idpEvent.BaseDN,
|
|
BindDN: idpEvent.BindDN,
|
|
BindPassword: idpEvent.BindPassword,
|
|
UserBase: idpEvent.UserBase,
|
|
UserObjectClasses: idpEvent.UserObjectClasses,
|
|
UserFilters: idpEvent.UserFilters,
|
|
Timeout: idpEvent.Timeout,
|
|
LDAPAttributes: domain.LDAPAttributes{
|
|
IDAttribute: idpEvent.IDAttribute,
|
|
FirstNameAttribute: idpEvent.FirstNameAttribute,
|
|
LastNameAttribute: idpEvent.LastNameAttribute,
|
|
DisplayNameAttribute: idpEvent.DisplayNameAttribute,
|
|
NickNameAttribute: idpEvent.NickNameAttribute,
|
|
PreferredUsernameAttribute: idpEvent.PreferredUsernameAttribute,
|
|
EmailAttribute: idpEvent.EmailAttribute,
|
|
EmailVerifiedAttribute: idpEvent.EmailVerifiedAttribute,
|
|
PhoneAttribute: idpEvent.PhoneAttribute,
|
|
PhoneVerifiedAttribute: idpEvent.PhoneVerifiedAttribute,
|
|
PreferredLanguageAttribute: idpEvent.PreferredLanguageAttribute,
|
|
AvatarURLAttribute: idpEvent.AvatarURLAttribute,
|
|
ProfileAttribute: idpEvent.ProfileAttribute,
|
|
},
|
|
}
|
|
|
|
payloadJSON, err := json.Marshal(ldap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, _ string) error {
|
|
tx, ok := ex.(*sql.Tx)
|
|
if !ok {
|
|
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-XCJ8w", "reduce.wrong.db.pool %T", ex)
|
|
}
|
|
|
|
repo := repository.IDProviderRepository()
|
|
return repo.Create(ctx, v3_sql.SQLTx(tx), &domain.IdentityProvider{
|
|
InstanceID: idpEvent.Aggregate().InstanceID,
|
|
OrgID: orgId,
|
|
ID: idpEvent.ID,
|
|
State: domain.IDPStateActive,
|
|
Name: idpEvent.Name,
|
|
Type: gu.Ptr(domain.IDPTypeLDAP),
|
|
AllowCreation: idpEvent.IsCreationAllowed,
|
|
AllowAutoCreation: idpEvent.IsAutoCreation,
|
|
AllowAutoUpdate: idpEvent.IsAutoUpdate,
|
|
AllowLinking: idpEvent.IsLinkingAllowed,
|
|
AutoLinkingField: mapAutoLinkingField(idpEvent.AutoLinkingOption),
|
|
Payload: payloadJSON,
|
|
CreatedAt: event.CreatedAt(),
|
|
UpdatedAt: event.CreatedAt(),
|
|
})
|
|
}), nil
|
|
}
|
|
|
|
func (p *relationalTablesProjection) reduceLDAPIDPChanged(event eventstore.Event) (*handler.Statement, error) {
|
|
var orgId *string
|
|
var idpEvent idp.LDAPIDPChangedEvent
|
|
switch e := event.(type) {
|
|
case *org.LDAPIDPChangedEvent:
|
|
idpEvent = e.LDAPIDPChangedEvent
|
|
orgId = &idpEvent.Aggregate().ResourceOwner
|
|
case *instance.LDAPIDPChangedEvent:
|
|
idpEvent = e.LDAPIDPChangedEvent
|
|
default:
|
|
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-p1582ks", "reduce.wrong.event.type %v", []eventstore.EventType{org.LDAPIDPChangedEventType, instance.LDAPIDPChangedEventType})
|
|
}
|
|
|
|
return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, _ string) error {
|
|
tx, ok := ex.(*sql.Tx)
|
|
if !ok {
|
|
return zerrors.ThrowInternal(nil, "HANDL-HX6ed", "unable to cast to tx executer")
|
|
}
|
|
idpRepo := repository.IDProviderRepository()
|
|
ldap, err := idpRepo.GetLDAP(ctx, v3_sql.SQLTx(tx), database.WithCondition(idpScopedCondition(idpRepo, idpEvent.Agg.InstanceID, idpEvent.ID, orgId)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
changes := p.reduceIDPChangedTemplateColumns(idpRepo, idpEvent.Name, idpEvent.OptionChanges)
|
|
|
|
payload := &ldap.LDAP
|
|
payloadChanged := p.reduceLDAPIDPChangedColumns(payload, &idpEvent)
|
|
if payloadChanged {
|
|
payloadJSON, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
changes = append(changes, idpRepo.SetPayload(string(payloadJSON)))
|
|
}
|
|
|
|
changes = append(changes, idpRepo.SetUpdatedAt(gu.Ptr(event.CreatedAt())))
|
|
_, err = idpRepo.Update(ctx, v3_sql.SQLTx(tx), idpScopedCondition(idpRepo, idpEvent.Aggregate().InstanceID, idpEvent.ID, orgId), changes...)
|
|
return err
|
|
|
|
}), nil
|
|
}
|
|
|
|
//nolint:gocognit
|
|
func (p *relationalTablesProjection) reduceLDAPIDPChangedColumns(payload *domain.LDAP, idpEvent *idp.LDAPIDPChangedEvent) bool {
|
|
payloadChanged := false
|
|
if payload == nil || idpEvent == nil {
|
|
return payloadChanged
|
|
}
|
|
|
|
if idpEvent.Servers != nil && !slices.Equal(idpEvent.Servers, payload.Servers) {
|
|
payloadChanged = true
|
|
payload.Servers = idpEvent.Servers
|
|
}
|
|
if idpEvent.StartTLS != nil && *idpEvent.StartTLS != payload.StartTLS {
|
|
payloadChanged = true
|
|
payload.StartTLS = *idpEvent.StartTLS
|
|
}
|
|
if idpEvent.BaseDN != nil && *idpEvent.BaseDN != payload.BaseDN {
|
|
payloadChanged = true
|
|
payload.BaseDN = *idpEvent.BaseDN
|
|
}
|
|
if idpEvent.BindDN != nil && *idpEvent.BindDN != payload.BindDN {
|
|
payloadChanged = true
|
|
payload.BindDN = *idpEvent.BindDN
|
|
}
|
|
if idpEvent.BindPassword != nil {
|
|
payloadChanged = true
|
|
payload.BindPassword = idpEvent.BindPassword
|
|
}
|
|
if idpEvent.UserBase != nil && *idpEvent.UserBase != payload.UserBase {
|
|
payloadChanged = true
|
|
payload.UserBase = *idpEvent.UserBase
|
|
}
|
|
if idpEvent.UserObjectClasses != nil && !slices.Equal(idpEvent.UserObjectClasses, payload.UserObjectClasses) {
|
|
payloadChanged = true
|
|
payload.UserObjectClasses = idpEvent.UserObjectClasses
|
|
}
|
|
if idpEvent.UserFilters != nil && !slices.Equal(idpEvent.UserFilters, payload.UserFilters) {
|
|
payloadChanged = true
|
|
payload.UserFilters = idpEvent.UserFilters
|
|
}
|
|
if idpEvent.Timeout != nil && *idpEvent.Timeout != payload.Timeout {
|
|
payloadChanged = true
|
|
payload.Timeout = *idpEvent.Timeout
|
|
}
|
|
if idpEvent.RootCA != nil && !slices.Equal(idpEvent.RootCA, payload.RootCA) {
|
|
payloadChanged = true
|
|
payload.RootCA = idpEvent.RootCA
|
|
}
|
|
if idpEvent.IDAttribute != nil && *idpEvent.IDAttribute != payload.IDAttribute {
|
|
payloadChanged = true
|
|
payload.IDAttribute = *idpEvent.IDAttribute
|
|
}
|
|
if idpEvent.FirstNameAttribute != nil && *idpEvent.FirstNameAttribute != payload.FirstNameAttribute {
|
|
payloadChanged = true
|
|
payload.FirstNameAttribute = *idpEvent.FirstNameAttribute
|
|
}
|
|
if idpEvent.LastNameAttribute != nil && *idpEvent.LastNameAttribute != payload.LastNameAttribute {
|
|
payloadChanged = true
|
|
payload.LastNameAttribute = *idpEvent.LastNameAttribute
|
|
}
|
|
if idpEvent.DisplayNameAttribute != nil && *idpEvent.DisplayNameAttribute != payload.DisplayNameAttribute {
|
|
payloadChanged = true
|
|
payload.DisplayNameAttribute = *idpEvent.DisplayNameAttribute
|
|
}
|
|
if idpEvent.NickNameAttribute != nil && *idpEvent.NickNameAttribute != payload.NickNameAttribute {
|
|
payloadChanged = true
|
|
payload.NickNameAttribute = *idpEvent.NickNameAttribute
|
|
}
|
|
if idpEvent.PreferredUsernameAttribute != nil && *idpEvent.PreferredUsernameAttribute != payload.PreferredUsernameAttribute {
|
|
payloadChanged = true
|
|
payload.PreferredUsernameAttribute = *idpEvent.PreferredUsernameAttribute
|
|
}
|
|
if idpEvent.EmailAttribute != nil && *idpEvent.EmailAttribute != payload.EmailAttribute {
|
|
payloadChanged = true
|
|
payload.EmailAttribute = *idpEvent.EmailAttribute
|
|
}
|
|
if idpEvent.EmailVerifiedAttribute != nil && *idpEvent.EmailVerifiedAttribute != payload.EmailVerifiedAttribute {
|
|
payloadChanged = true
|
|
payload.EmailVerifiedAttribute = *idpEvent.EmailVerifiedAttribute
|
|
}
|
|
if idpEvent.PhoneAttribute != nil && *idpEvent.PhoneAttribute != payload.PhoneAttribute {
|
|
payloadChanged = true
|
|
payload.PhoneAttribute = *idpEvent.PhoneAttribute
|
|
}
|
|
if idpEvent.PhoneVerifiedAttribute != nil && *idpEvent.PhoneVerifiedAttribute != payload.PhoneVerifiedAttribute {
|
|
payloadChanged = true
|
|
payload.PhoneVerifiedAttribute = *idpEvent.PhoneVerifiedAttribute
|
|
}
|
|
if idpEvent.PreferredLanguageAttribute != nil && *idpEvent.PreferredLanguageAttribute != payload.PreferredLanguageAttribute {
|
|
payloadChanged = true
|
|
payload.PreferredLanguageAttribute = *idpEvent.PreferredLanguageAttribute
|
|
}
|
|
if idpEvent.AvatarURLAttribute != nil && *idpEvent.AvatarURLAttribute != payload.AvatarURLAttribute {
|
|
payloadChanged = true
|
|
payload.AvatarURLAttribute = *idpEvent.AvatarURLAttribute
|
|
}
|
|
if idpEvent.ProfileAttribute != nil && *idpEvent.ProfileAttribute != payload.ProfileAttribute {
|
|
payloadChanged = true
|
|
payload.ProfileAttribute = *idpEvent.ProfileAttribute
|
|
}
|
|
return payloadChanged
|
|
}
|