feat: implement UpdateZitadelProvider in ManagementService (#12384)

# Which Problems Are Solved

This PR adds support for updating ZITADEL identity provider (IDP)
templates.

# How the Problems Are Solved

- Implements `UpdateZitadelProvider` in the Management gRPC server plus
converter
- Introduces an org-scoped `ZitadelIDPChanged` event and registers its
event mapper.
- Adds command-side support to update org Zitadel providers and appends
the new event to the org write model.
- Extends the IDP template projection reducer to accept org change
events.

# Additional Changes
N/A

# Additional Context
Closes https://github.com/zitadel/zitadel/issues/11922
Follow up for PRs:

- https://github.com/zitadel/zitadel/pull/12018
- https://github.com/zitadel/zitadel/pull/12020
- https://github.com/zitadel/zitadel/pull/12055
- https://github.com/zitadel/zitadel/pull/12056
- https://github.com/zitadel/zitadel/pull/12371
- https://github.com/zitadel/zitadel/pull/12378

---------

Co-authored-by: Copilot Autofix powered by AI <[email protected]>
Co-authored-by: Livio Spring <[email protected]>
This commit is contained in:
Gayathri Vijayan
2026-07-07 04:32:18 +00:00
committed by GitHub
co-authored by Copilot Autofix powered by AI Livio Spring
parent bc9333aef9
commit 942607e0e9
11 changed files with 1128 additions and 19 deletions
+7 -1
View File
@@ -704,6 +704,10 @@ func (p *idpTemplateProjection) Reducers() []handler.AggregateReducer {
Event: org.ZitadelIDPAddedEventType,
Reduce: p.reduceZitadelIDPAdded,
},
{
Event: org.ZitadelIDPChangedEventType,
Reduce: p.reduceZitadelIDPChanged,
},
{
Event: org.IDPConfigRemovedEventType,
Reduce: p.reduceIDPConfigRemoved,
@@ -2640,8 +2644,10 @@ func (p *idpTemplateProjection) reduceZitadelIDPChanged(event eventstore.Event)
switch e := event.(type) {
case *instance.ZitadelIDPChangedEvent:
idpEvent = e.ZitadelIDPChangedEvent
case *org.ZitadelIDPChangedEvent:
idpEvent = e.ZitadelIDPChangedEvent
default:
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-qjMUfi", "reduce.wrong.event.type %v", []eventstore.EventType{instance.ZitadelIDPChangedEventType})
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-qjMUfi", "reduce.wrong.event.type %v", []eventstore.EventType{instance.ZitadelIDPChangedEventType, org.ZitadelIDPChangedEventType})
}
ops := make([]func(eventstore.Event) handler.Exec, 0, 2)
@@ -4981,6 +4981,179 @@ func TestIDPTemplateProjection_reducesZitadel(t *testing.T) {
},
},
},
{
name: "org reduceZitadelIDPChanged minimal",
args: args{
event: getEvent(
testEvent(
org.ZitadelIDPChangedEventType,
org.AggregateType,
[]byte(`{
"id": "idp-id",
"isCreationAllowed": true,
"clientId": "id"
}`),
), eventstore.GenericEventMapper[org.ZitadelIDPChangedEvent]),
},
reduce: (&idpTemplateProjection{}).reduceZitadelIDPChanged,
want: wantReduce{
aggregateType: eventstore.AggregateType("org"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: idpTemplateUpdateMinimalStmt,
expectedArgs: []interface{}{
true,
anyArg{},
uint64(15),
"idp-id",
"instance-id",
},
},
{
expectedStmt: "UPDATE projections.idp_templates6_zitadel SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{
"id",
"idp-id",
"instance-id",
},
},
},
},
},
},
{
name: "org reduceZitadelIDPChanged",
args: args{
event: getEvent(
testEvent(
org.ZitadelIDPChangedEventType,
org.AggregateType,
[]byte(`{
"id": "idp-id",
"name": "name",
"issuer": "issuer",
"clientId": "client_id",
"clientSecret": {
"cryptoType": 0,
"algorithm": "RSA-265",
"keyId": "key-id"
},
"scopes": ["profile"],
"instanceRolesInfo": [{
"organizationId": "org3",
"organizationDomain": "org3.com"
}],
"isCreationAllowed": true,
"isLinkingAllowed": true,
"isAutoCreation": true,
"isAutoUpdate": true,
"autoLinkingOption": 1
}`),
), eventstore.GenericEventMapper[org.ZitadelIDPChangedEvent]),
},
reduce: (&idpTemplateProjection{}).reduceZitadelIDPChanged,
want: wantReduce{
aggregateType: eventstore.AggregateType("org"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: idpTemplateUpdateStmt,
expectedArgs: []interface{}{
"name",
true,
true,
true,
true,
domain.AutoLinkingOptionUsername,
anyArg{},
uint64(15),
"idp-id",
"instance-id",
},
},
{
expectedStmt: "UPDATE projections.idp_templates6_zitadel SET (client_id, client_secret, issuer, scopes, instance_roles_info) = ($1, $2, $3, $4, $5) WHERE (idp_id = $6) AND (instance_id = $7)",
expectedArgs: []interface{}{
"client_id",
anyArg{},
"issuer",
database.TextArray[string]{"profile"},
[]byte(`[{"organizationId":"org3","organizationDomain":"org3.com"}]`),
"idp-id",
"instance-id",
},
},
},
},
},
},
{
name: "org reduceZitadelIDPChanged - unset scopes and instance roles info",
args: args{
event: getEvent(
testEvent(
org.ZitadelIDPChangedEventType,
org.AggregateType,
[]byte(`{
"id": "idp-id",
"name": "name",
"issuer": "issuer",
"clientId": "client_id",
"clientSecret": {
"cryptoType": 0,
"algorithm": "RSA-265",
"keyId": "key-id"
},
"scopes": [],
"instanceRolesInfo": [],
"isCreationAllowed": true,
"isLinkingAllowed": true,
"isAutoCreation": true,
"isAutoUpdate": true,
"autoLinkingOption": 1
}`),
), eventstore.GenericEventMapper[org.ZitadelIDPChangedEvent]),
},
reduce: (&idpTemplateProjection{}).reduceZitadelIDPChanged,
want: wantReduce{
aggregateType: eventstore.AggregateType("org"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: idpTemplateUpdateStmt,
expectedArgs: []interface{}{
"name",
true,
true,
true,
true,
domain.AutoLinkingOptionUsername,
anyArg{},
uint64(15),
"idp-id",
"instance-id",
},
},
{
expectedStmt: "UPDATE projections.idp_templates6_zitadel SET (client_id, client_secret, issuer, scopes, instance_roles_info) = ($1, $2, $3, $4, $5) WHERE (idp_id = $6) AND (instance_id = $7)",
expectedArgs: []interface{}{
"client_id",
anyArg{},
"issuer",
database.TextArray[string]{},
[]byte(`[]`),
"idp-id",
"instance-id",
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {