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) reduceSAMLIDPAdded(event eventstore.Event) (*handler.Statement, error) { var orgId *string var idpEvent idp.SAMLIDPAddedEvent switch e := event.(type) { case *org.SAMLIDPAddedEvent: idpEvent = e.SAMLIDPAddedEvent orgId = &idpEvent.Aggregate().ResourceOwner case *instance.SAMLIDPAddedEvent: idpEvent = e.SAMLIDPAddedEvent default: return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Ys02m1", "reduce.wrong.event.type %v", []eventstore.EventType{org.SAMLIDPAddedEventType, instance.SAMLIDPAddedEventType}) } saml := domain.SAML{ Metadata: idpEvent.Metadata, Key: idpEvent.Key, Certificate: idpEvent.Certificate, Binding: idpEvent.Binding, WithSignedRequest: idpEvent.WithSignedRequest, NameIDFormat: idpEvent.NameIDFormat, TransientMappingAttributeName: idpEvent.TransientMappingAttributeName, SignatureAlgorithm: idpEvent.SignatureAlgorithm, } payloadJSON, err := json.Marshal(saml) 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-ksJ3N", "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.IDPTypeSAML), 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) reduceSAMLIDPChanged(event eventstore.Event) (*handler.Statement, error) { var orgId *string var idpEvent idp.SAMLIDPChangedEvent switch e := event.(type) { case *org.SAMLIDPChangedEvent: idpEvent = e.SAMLIDPChangedEvent orgId = &idpEvent.Aggregate().ResourceOwner case *instance.SAMLIDPChangedEvent: idpEvent = e.SAMLIDPChangedEvent default: return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Y7c0fii4ad", "reduce.wrong.event.type %v", []eventstore.EventType{org.SAMLIDPChangedEventType, instance.SAMLIDPChangedEventType}) } 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() saml, err := idpRepo.GetSAML(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 := &saml.SAML payloadChanged := p.reduceSAMLIDPChangedColumns(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 } func (p *relationalTablesProjection) reduceSAMLIDPChangedColumns(payload *domain.SAML, idpEvent *idp.SAMLIDPChangedEvent) bool { payloadChanged := false if payload == nil || idpEvent == nil { return payloadChanged } if idpEvent.Metadata != nil && !slices.Equal(idpEvent.Metadata, payload.Metadata) { payloadChanged = true payload.Metadata = idpEvent.Metadata } if idpEvent.Key != nil { payloadChanged = true payload.Key = idpEvent.Key } if idpEvent.Certificate != nil && !slices.Equal(idpEvent.Certificate, payload.Certificate) { payloadChanged = true payload.Certificate = idpEvent.Certificate } if idpEvent.Binding != nil && *idpEvent.Binding != payload.Binding { payloadChanged = true payload.Binding = *idpEvent.Binding } if idpEvent.WithSignedRequest != nil && *idpEvent.WithSignedRequest != payload.WithSignedRequest { payloadChanged = true payload.WithSignedRequest = *idpEvent.WithSignedRequest } if idpEvent.NameIDFormat != nil && (payload.NameIDFormat == nil || *idpEvent.NameIDFormat != *payload.NameIDFormat) { payloadChanged = true payload.NameIDFormat = idpEvent.NameIDFormat } if idpEvent.TransientMappingAttributeName != nil && *idpEvent.TransientMappingAttributeName != payload.TransientMappingAttributeName { payloadChanged = true payload.TransientMappingAttributeName = *idpEvent.TransientMappingAttributeName } if idpEvent.FederatedLogoutEnabled != nil && *idpEvent.FederatedLogoutEnabled != payload.FederatedLogoutEnabled { payloadChanged = true payload.FederatedLogoutEnabled = *idpEvent.FederatedLogoutEnabled } if idpEvent.SignatureAlgorithm != nil && *idpEvent.SignatureAlgorithm != payload.SignatureAlgorithm { payloadChanged = true payload.SignatureAlgorithm = *idpEvent.SignatureAlgorithm } return payloadChanged }