package command import ( "context" "slices" "time" "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/project" ) type OIDCApplicationWriteModel struct { eventstore.WriteModel AppID string AppName string ClientID string HashedSecret string ClientSecretString string RedirectUris []string ResponseTypes []domain.OIDCResponseType GrantTypes []domain.OIDCGrantType ApplicationType domain.OIDCApplicationType AuthMethodType domain.OIDCAuthMethodType PostLogoutRedirectUris []string OIDCVersion domain.OIDCVersion Compliance *domain.Compliance DevMode bool AccessTokenType domain.OIDCTokenType AccessTokenRoleAssertion bool IDTokenRoleAssertion bool IDTokenUserinfoAssertion bool ClockSkew time.Duration State domain.AppState AdditionalOrigins []string SkipNativeAppSuccessPage bool BackChannelLogoutURI string LoginVersion domain.LoginVersion LoginBaseURI string oidc bool } func NewOIDCApplicationWriteModelWithAppID(projectID, appID, resourceOwner string) *OIDCApplicationWriteModel { return &OIDCApplicationWriteModel{ WriteModel: eventstore.WriteModel{ AggregateID: projectID, ResourceOwner: resourceOwner, }, AppID: appID, } } func NewOIDCApplicationWriteModel(projectID, resourceOwner string) *OIDCApplicationWriteModel { return &OIDCApplicationWriteModel{ WriteModel: eventstore.WriteModel{ AggregateID: projectID, ResourceOwner: resourceOwner, }, } } func (wm *OIDCApplicationWriteModel) AppendEvents(events ...eventstore.Event) { for _, event := range events { switch e := event.(type) { case *project.ApplicationAddedEvent: if e.AppID != wm.AppID { continue } wm.WriteModel.AppendEvents(e) case *project.ApplicationChangedEvent: if e.AppID != wm.AppID { continue } wm.WriteModel.AppendEvents(e) case *project.ApplicationDeactivatedEvent: if e.AppID != wm.AppID { continue } wm.WriteModel.AppendEvents(e) case *project.ApplicationReactivatedEvent: if e.AppID != wm.AppID { continue } wm.WriteModel.AppendEvents(e) case *project.ApplicationRemovedEvent: if e.AppID != wm.AppID { continue } wm.WriteModel.AppendEvents(e) case *project.OIDCConfigAddedEvent: if e.AppID != wm.AppID { continue } wm.WriteModel.AppendEvents(e) case *project.OIDCConfigChangedEvent: if e.AppID != wm.AppID { continue } wm.WriteModel.AppendEvents(e) case *project.OIDCConfigSecretChangedEvent: if e.AppID != wm.AppID { continue } wm.WriteModel.AppendEvents(e) case *project.OIDCConfigSecretHashUpdatedEvent: if e.AppID != wm.AppID { continue } wm.WriteModel.AppendEvents(e) case *project.ProjectRemovedEvent: wm.WriteModel.AppendEvents(e) } } } func (wm *OIDCApplicationWriteModel) Reduce() error { for _, event := range wm.Events { switch e := event.(type) { case *project.ApplicationAddedEvent: wm.AppName = e.Name wm.State = domain.AppStateActive case *project.ApplicationChangedEvent: wm.AppName = e.Name case *project.ApplicationDeactivatedEvent: if wm.State == domain.AppStateRemoved { continue } wm.State = domain.AppStateInactive case *project.ApplicationReactivatedEvent: if wm.State == domain.AppStateRemoved { continue } wm.State = domain.AppStateActive case *project.ApplicationRemovedEvent: wm.State = domain.AppStateRemoved case *project.OIDCConfigAddedEvent: wm.appendAddOIDCEvent(e) case *project.OIDCConfigChangedEvent: wm.appendChangeOIDCEvent(e) case *project.OIDCConfigSecretChangedEvent: wm.HashedSecret = crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret) case *project.OIDCConfigSecretHashUpdatedEvent: wm.HashedSecret = e.HashedSecret case *project.ProjectRemovedEvent: wm.State = domain.AppStateRemoved } } return wm.WriteModel.Reduce() } func (wm *OIDCApplicationWriteModel) appendAddOIDCEvent(e *project.OIDCConfigAddedEvent) { wm.oidc = true wm.ClientID = e.ClientID wm.HashedSecret = crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret) wm.RedirectUris = e.RedirectUris wm.ResponseTypes = e.ResponseTypes wm.GrantTypes = e.GrantTypes wm.ApplicationType = e.ApplicationType wm.AuthMethodType = e.AuthMethodType wm.PostLogoutRedirectUris = e.PostLogoutRedirectUris wm.OIDCVersion = e.Version wm.DevMode = e.DevMode wm.AccessTokenType = e.AccessTokenType wm.AccessTokenRoleAssertion = e.AccessTokenRoleAssertion wm.IDTokenRoleAssertion = e.IDTokenRoleAssertion wm.IDTokenUserinfoAssertion = e.IDTokenUserinfoAssertion wm.ClockSkew = e.ClockSkew wm.AdditionalOrigins = e.AdditionalOrigins wm.SkipNativeAppSuccessPage = e.SkipNativeAppSuccessPage wm.BackChannelLogoutURI = e.BackChannelLogoutURI wm.LoginVersion = e.LoginVersion wm.LoginBaseURI = e.LoginBaseURI } func (wm *OIDCApplicationWriteModel) appendChangeOIDCEvent(e *project.OIDCConfigChangedEvent) { if e.RedirectUris != nil { wm.RedirectUris = *e.RedirectUris } if e.ResponseTypes != nil { wm.ResponseTypes = *e.ResponseTypes } if e.GrantTypes != nil { wm.GrantTypes = *e.GrantTypes } if e.ApplicationType != nil { wm.ApplicationType = *e.ApplicationType } if e.AuthMethodType != nil { wm.AuthMethodType = *e.AuthMethodType } if e.PostLogoutRedirectUris != nil { wm.PostLogoutRedirectUris = *e.PostLogoutRedirectUris } if e.Version != nil { wm.OIDCVersion = *e.Version } if e.DevMode != nil { wm.DevMode = *e.DevMode } if e.AccessTokenType != nil { wm.AccessTokenType = *e.AccessTokenType } if e.AccessTokenRoleAssertion != nil { wm.AccessTokenRoleAssertion = *e.AccessTokenRoleAssertion } if e.IDTokenRoleAssertion != nil { wm.IDTokenRoleAssertion = *e.IDTokenRoleAssertion } if e.IDTokenUserinfoAssertion != nil { wm.IDTokenUserinfoAssertion = *e.IDTokenUserinfoAssertion } if e.ClockSkew != nil { wm.ClockSkew = *e.ClockSkew } if e.AdditionalOrigins != nil { wm.AdditionalOrigins = *e.AdditionalOrigins } if e.SkipNativeAppSuccessPage != nil { wm.SkipNativeAppSuccessPage = *e.SkipNativeAppSuccessPage } if e.BackChannelLogoutURI != nil { wm.BackChannelLogoutURI = *e.BackChannelLogoutURI } if e.LoginVersion != nil { wm.LoginVersion = *e.LoginVersion } if e.LoginBaseURI != nil { wm.LoginBaseURI = *e.LoginBaseURI } } func (wm *OIDCApplicationWriteModel) Query() *eventstore.SearchQueryBuilder { return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). ResourceOwner(wm.ResourceOwner). AddQuery(). AggregateTypes(project.AggregateType). AggregateIDs(wm.AggregateID). EventTypes( project.ApplicationAddedType, project.ApplicationChangedType, project.ApplicationDeactivatedType, project.ApplicationReactivatedType, project.ApplicationRemovedType, project.OIDCConfigAddedType, project.OIDCConfigChangedType, project.OIDCConfigSecretChangedType, project.OIDCConfigSecretHashUpdatedType, project.ProjectRemovedType, ).Builder() } func (wm *OIDCApplicationWriteModel) NewChangedEvent( ctx context.Context, aggregate *eventstore.Aggregate, appID string, redirectURIS, postLogoutRedirectURIs []string, responseTypes []domain.OIDCResponseType, grantTypes []domain.OIDCGrantType, appType *domain.OIDCApplicationType, authMethodType *domain.OIDCAuthMethodType, oidcVersion *domain.OIDCVersion, accessTokenType *domain.OIDCTokenType, devMode, accessTokenRoleAssertion, idTokenRoleAssertion, idTokenUserinfoAssertion *bool, clockSkew *time.Duration, additionalOrigins []string, skipNativeAppSuccessPage *bool, backChannelLogoutURI *string, loginVersion *domain.LoginVersion, loginBaseURI *string, ) (*project.OIDCConfigChangedEvent, bool, error) { changes := make([]project.OIDCConfigChanges, 0) var err error if redirectURIS != nil && !slices.Equal(wm.RedirectUris, redirectURIS) { changes = append(changes, project.ChangeRedirectURIs(redirectURIS)) } if responseTypes != nil && !slices.Equal(wm.ResponseTypes, responseTypes) { changes = append(changes, project.ChangeResponseTypes(responseTypes)) } if grantTypes != nil && !slices.Equal(wm.GrantTypes, grantTypes) { changes = append(changes, project.ChangeGrantTypes(grantTypes)) } if appType != nil && wm.ApplicationType != *appType { changes = append(changes, project.ChangeApplicationType(*appType)) } if authMethodType != nil && wm.AuthMethodType != *authMethodType { changes = append(changes, project.ChangeAuthMethodType(*authMethodType)) } if postLogoutRedirectURIs != nil && !slices.Equal(wm.PostLogoutRedirectUris, postLogoutRedirectURIs) { changes = append(changes, project.ChangePostLogoutRedirectURIs(postLogoutRedirectURIs)) } if oidcVersion != nil && wm.OIDCVersion != *oidcVersion { changes = append(changes, project.ChangeVersion(*oidcVersion)) } if devMode != nil && wm.DevMode != *devMode { changes = append(changes, project.ChangeDevMode(*devMode)) } if accessTokenType != nil && wm.AccessTokenType != *accessTokenType { changes = append(changes, project.ChangeAccessTokenType(*accessTokenType)) } if accessTokenRoleAssertion != nil && wm.AccessTokenRoleAssertion != *accessTokenRoleAssertion { changes = append(changes, project.ChangeAccessTokenRoleAssertion(*accessTokenRoleAssertion)) } if idTokenRoleAssertion != nil && wm.IDTokenRoleAssertion != *idTokenRoleAssertion { changes = append(changes, project.ChangeIDTokenRoleAssertion(*idTokenRoleAssertion)) } if idTokenUserinfoAssertion != nil && wm.IDTokenUserinfoAssertion != *idTokenUserinfoAssertion { changes = append(changes, project.ChangeIDTokenUserinfoAssertion(*idTokenUserinfoAssertion)) } if clockSkew != nil && wm.ClockSkew != *clockSkew { changes = append(changes, project.ChangeClockSkew(*clockSkew)) } if additionalOrigins != nil && !slices.Equal(wm.AdditionalOrigins, additionalOrigins) { changes = append(changes, project.ChangeAdditionalOrigins(additionalOrigins)) } if skipNativeAppSuccessPage != nil && wm.SkipNativeAppSuccessPage != *skipNativeAppSuccessPage { changes = append(changes, project.ChangeSkipNativeAppSuccessPage(*skipNativeAppSuccessPage)) } if backChannelLogoutURI != nil && wm.BackChannelLogoutURI != *backChannelLogoutURI { changes = append(changes, project.ChangeBackChannelLogoutURI(*backChannelLogoutURI)) } if loginVersion != nil && wm.LoginVersion != *loginVersion { changes = append(changes, project.ChangeOIDCLoginVersion(*loginVersion)) } if loginBaseURI != nil && wm.LoginBaseURI != *loginBaseURI { changes = append(changes, project.ChangeOIDCLoginBaseURI(*loginBaseURI)) } if len(changes) == 0 { return nil, false, nil } changeEvent, err := project.NewOIDCConfigChangedEvent(ctx, aggregate, appID, changes) if err != nil { return nil, false, err } return changeEvent, true, nil } func (wm *OIDCApplicationWriteModel) IsOIDC() bool { return wm.oidc }