mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 03:37:34 +00:00
feat: add SAML as identity provider (#6454)
* feat: first implementation for saml sp * fix: add command side instance and org for saml provider * fix: add query side instance and org for saml provider * fix: request handling in event and retrieval of finished intent * fix: add review changes and integration tests * fix: add integration tests for saml idp * fix: correct unit tests with review changes * fix: add saml session unit test * fix: add saml session unit test * fix: add saml session unit test * fix: changes from review * fix: changes from review * fix: proto build error * fix: proto build error * fix: proto build error * fix: proto require metadata oneof * fix: login with saml provider * fix: integration test for saml assertion * lint client.go * fix json tag * fix: linting * fix import * fix: linting * fix saml idp query * fix: linting * lint: try all issues * revert linting config * fix: add regenerate endpoints * fix: translations * fix mk.yaml * ignore acs path for user agent cookie * fix: add AuthFromProvider test for saml * fix: integration test for saml retrieve information --------- Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
@@ -24,6 +24,8 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/ldap"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/oauth"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/oidc"
|
||||
saml2 "github.com/zitadel/zitadel/internal/idp/providers/saml"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/saml/requesttracker"
|
||||
"github.com/zitadel/zitadel/internal/repository/idp"
|
||||
"github.com/zitadel/zitadel/internal/repository/idpconfig"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
@@ -1721,6 +1723,153 @@ func (wm *AppleIDPWriteModel) GetProviderOptions() idp.Options {
|
||||
return wm.Options
|
||||
}
|
||||
|
||||
type SAMLIDPWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
Name string
|
||||
ID string
|
||||
Metadata []byte
|
||||
Key *crypto.CryptoValue
|
||||
Certificate []byte
|
||||
Binding string
|
||||
WithSignedRequest bool
|
||||
idp.Options
|
||||
|
||||
State domain.IDPState
|
||||
}
|
||||
|
||||
func (wm *SAMLIDPWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *idp.SAMLIDPAddedEvent:
|
||||
wm.reduceAddedEvent(e)
|
||||
case *idp.SAMLIDPChangedEvent:
|
||||
wm.reduceChangedEvent(e)
|
||||
case *idp.RemovedEvent:
|
||||
wm.State = domain.IDPStateRemoved
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *SAMLIDPWriteModel) reduceAddedEvent(e *idp.SAMLIDPAddedEvent) {
|
||||
wm.Name = e.Name
|
||||
wm.Metadata = e.Metadata
|
||||
wm.Key = e.Key
|
||||
wm.Certificate = e.Certificate
|
||||
wm.Binding = e.Binding
|
||||
wm.WithSignedRequest = e.WithSignedRequest
|
||||
wm.Options = e.Options
|
||||
wm.State = domain.IDPStateActive
|
||||
}
|
||||
|
||||
func (wm *SAMLIDPWriteModel) reduceChangedEvent(e *idp.SAMLIDPChangedEvent) {
|
||||
if e.Key != nil {
|
||||
wm.Key = e.Key
|
||||
}
|
||||
if e.Certificate != nil {
|
||||
wm.Certificate = e.Certificate
|
||||
}
|
||||
if e.Name != nil {
|
||||
wm.Name = *e.Name
|
||||
}
|
||||
if e.Metadata != nil {
|
||||
wm.Metadata = e.Metadata
|
||||
}
|
||||
if e.Binding != nil {
|
||||
wm.Binding = *e.Binding
|
||||
}
|
||||
if e.WithSignedRequest != nil {
|
||||
wm.WithSignedRequest = *e.WithSignedRequest
|
||||
}
|
||||
wm.Options.ReduceChanges(e.OptionChanges)
|
||||
}
|
||||
|
||||
func (wm *SAMLIDPWriteModel) NewChanges(
|
||||
name string,
|
||||
metadata,
|
||||
key,
|
||||
certificate []byte,
|
||||
secretCrypto crypto.Crypto,
|
||||
binding string,
|
||||
withSignedRequest bool,
|
||||
options idp.Options,
|
||||
) ([]idp.SAMLIDPChanges, error) {
|
||||
changes := make([]idp.SAMLIDPChanges, 0)
|
||||
if key != nil {
|
||||
keyEnc, err := crypto.Crypt(key, secretCrypto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
changes = append(changes, idp.ChangeSAMLKey(keyEnc))
|
||||
}
|
||||
if certificate != nil {
|
||||
changes = append(changes, idp.ChangeSAMLCertificate(certificate))
|
||||
}
|
||||
if wm.Name != name {
|
||||
changes = append(changes, idp.ChangeSAMLName(name))
|
||||
}
|
||||
if !reflect.DeepEqual(wm.Metadata, metadata) {
|
||||
changes = append(changes, idp.ChangeSAMLMetadata(metadata))
|
||||
}
|
||||
if wm.Binding != binding {
|
||||
changes = append(changes, idp.ChangeSAMLBinding(binding))
|
||||
}
|
||||
if wm.WithSignedRequest != withSignedRequest {
|
||||
changes = append(changes, idp.ChangeSAMLWithSignedRequest(withSignedRequest))
|
||||
}
|
||||
opts := wm.Options.Changes(options)
|
||||
if !opts.IsZero() {
|
||||
changes = append(changes, idp.ChangeSAMLOptions(opts))
|
||||
}
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
func (wm *SAMLIDPWriteModel) ToProvider(callbackURL string, idpAlg crypto.EncryptionAlgorithm, getRequest requesttracker.GetRequest, addRequest requesttracker.AddRequest) (providers.Provider, error) {
|
||||
key, err := crypto.Decrypt(wm.Key, idpAlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opts := make([]saml2.ProviderOpts, 0, 7)
|
||||
if wm.IsCreationAllowed {
|
||||
opts = append(opts, saml2.WithCreationAllowed())
|
||||
}
|
||||
if wm.IsLinkingAllowed {
|
||||
opts = append(opts, saml2.WithLinkingAllowed())
|
||||
}
|
||||
if wm.IsAutoCreation {
|
||||
opts = append(opts, saml2.WithAutoCreation())
|
||||
}
|
||||
if wm.IsAutoUpdate {
|
||||
opts = append(opts, saml2.WithAutoUpdate())
|
||||
}
|
||||
if wm.WithSignedRequest {
|
||||
opts = append(opts, saml2.WithSignedRequest())
|
||||
}
|
||||
if wm.Binding != "" {
|
||||
opts = append(opts, saml2.WithBinding(wm.Binding))
|
||||
}
|
||||
opts = append(opts, saml2.WithCustomRequestTracker(
|
||||
requesttracker.New(
|
||||
addRequest,
|
||||
getRequest,
|
||||
),
|
||||
))
|
||||
return saml2.New(
|
||||
wm.Name,
|
||||
callbackURL,
|
||||
wm.Metadata,
|
||||
wm.Certificate,
|
||||
key,
|
||||
opts...,
|
||||
)
|
||||
}
|
||||
|
||||
func (wm *SAMLIDPWriteModel) GetProviderOptions() idp.Options {
|
||||
return wm.Options
|
||||
}
|
||||
|
||||
type IDPRemoveWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
@@ -1753,6 +1902,8 @@ func (wm *IDPRemoveWriteModel) Reduce() error {
|
||||
wm.reduceAdded(e.ID)
|
||||
case *idp.AppleIDPAddedEvent:
|
||||
wm.reduceAdded(e.ID)
|
||||
case *idp.SAMLIDPAddedEvent:
|
||||
wm.reduceAdded(e.ID)
|
||||
case *idp.RemovedEvent:
|
||||
wm.reduceRemoved(e.ID)
|
||||
case *idpconfig.IDPConfigAddedEvent:
|
||||
@@ -1839,6 +1990,10 @@ func (wm *IDPTypeWriteModel) Reduce() error {
|
||||
wm.reduceAdded(e.ID, domain.IDPTypeApple, e.Aggregate())
|
||||
case *org.AppleIDPAddedEvent:
|
||||
wm.reduceAdded(e.ID, domain.IDPTypeApple, e.Aggregate())
|
||||
case *instance.SAMLIDPAddedEvent:
|
||||
wm.reduceAdded(e.ID, domain.IDPTypeSAML, e.Aggregate())
|
||||
case *org.SAMLIDPAddedEvent:
|
||||
wm.reduceAdded(e.ID, domain.IDPTypeSAML, e.Aggregate())
|
||||
case *instance.OIDCIDPMigratedAzureADEvent:
|
||||
wm.reduceChanged(e.ID, domain.IDPTypeAzureAD)
|
||||
case *org.OIDCIDPMigratedAzureADEvent:
|
||||
@@ -1915,6 +2070,7 @@ func (wm *IDPTypeWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
instance.GoogleIDPAddedEventType,
|
||||
instance.LDAPIDPAddedEventType,
|
||||
instance.AppleIDPAddedEventType,
|
||||
instance.SAMLIDPAddedEventType,
|
||||
instance.OIDCIDPMigratedAzureADEventType,
|
||||
instance.OIDCIDPMigratedGoogleEventType,
|
||||
instance.IDPRemovedEventType,
|
||||
@@ -1934,6 +2090,7 @@ func (wm *IDPTypeWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
org.GoogleIDPAddedEventType,
|
||||
org.LDAPIDPAddedEventType,
|
||||
org.AppleIDPAddedEventType,
|
||||
org.SAMLIDPAddedEventType,
|
||||
org.OIDCIDPMigratedAzureADEventType,
|
||||
org.OIDCIDPMigratedGoogleEventType,
|
||||
org.IDPRemovedEventType,
|
||||
@@ -1962,8 +2119,15 @@ type IDP interface {
|
||||
GetProviderOptions() idp.Options
|
||||
}
|
||||
|
||||
type SAMLIDP interface {
|
||||
eventstore.QueryReducer
|
||||
ToProvider(string, crypto.EncryptionAlgorithm, requesttracker.GetRequest, requesttracker.AddRequest) (providers.Provider, error)
|
||||
GetProviderOptions() idp.Options
|
||||
}
|
||||
|
||||
type AllIDPWriteModel struct {
|
||||
model IDP
|
||||
model IDP
|
||||
samlModel SAMLIDP
|
||||
|
||||
ID string
|
||||
IDPType domain.IDPType
|
||||
@@ -2003,6 +2167,8 @@ func NewAllIDPWriteModel(resourceOwner string, instanceBool bool, id string, idp
|
||||
writeModel.model = NewGoogleInstanceIDPWriteModel(resourceOwner, id)
|
||||
case domain.IDPTypeApple:
|
||||
writeModel.model = NewAppleInstanceIDPWriteModel(resourceOwner, id)
|
||||
case domain.IDPTypeSAML:
|
||||
writeModel.samlModel = NewSAMLInstanceIDPWriteModel(resourceOwner, id)
|
||||
case domain.IDPTypeUnspecified:
|
||||
fallthrough
|
||||
default:
|
||||
@@ -2032,6 +2198,8 @@ func NewAllIDPWriteModel(resourceOwner string, instanceBool bool, id string, idp
|
||||
writeModel.model = NewGoogleOrgIDPWriteModel(resourceOwner, id)
|
||||
case domain.IDPTypeApple:
|
||||
writeModel.model = NewAppleOrgIDPWriteModel(resourceOwner, id)
|
||||
case domain.IDPTypeSAML:
|
||||
writeModel.samlModel = NewSAMLOrgIDPWriteModel(resourceOwner, id)
|
||||
case domain.IDPTypeUnspecified:
|
||||
fallthrough
|
||||
default:
|
||||
@@ -2042,21 +2210,44 @@ func NewAllIDPWriteModel(resourceOwner string, instanceBool bool, id string, idp
|
||||
}
|
||||
|
||||
func (wm *AllIDPWriteModel) Reduce() error {
|
||||
return wm.model.Reduce()
|
||||
if wm.model != nil {
|
||||
return wm.model.Reduce()
|
||||
}
|
||||
return wm.samlModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *AllIDPWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return wm.model.Query()
|
||||
if wm.model != nil {
|
||||
return wm.model.Query()
|
||||
}
|
||||
return wm.samlModel.Query()
|
||||
}
|
||||
|
||||
func (wm *AllIDPWriteModel) AppendEvents(events ...eventstore.Event) {
|
||||
wm.model.AppendEvents(events...)
|
||||
if wm.model != nil {
|
||||
wm.model.AppendEvents(events...)
|
||||
return
|
||||
}
|
||||
wm.samlModel.AppendEvents(events...)
|
||||
}
|
||||
|
||||
func (wm *AllIDPWriteModel) ToProvider(callbackURL string, idpAlg crypto.EncryptionAlgorithm) (providers.Provider, error) {
|
||||
if wm.model == nil {
|
||||
return nil, errors.ThrowInternal(nil, "COMMAND-afvf0gc9sa", "ErrorsIDPConfig.NotExisting")
|
||||
}
|
||||
return wm.model.ToProvider(callbackURL, idpAlg)
|
||||
}
|
||||
|
||||
func (wm *AllIDPWriteModel) GetProviderOptions() idp.Options {
|
||||
return wm.model.GetProviderOptions()
|
||||
if wm.model != nil {
|
||||
return wm.model.GetProviderOptions()
|
||||
}
|
||||
return wm.samlModel.GetProviderOptions()
|
||||
}
|
||||
|
||||
func (wm *AllIDPWriteModel) ToSAMLProvider(callbackURL string, idpAlg crypto.EncryptionAlgorithm, getRequest requesttracker.GetRequest, addRequest requesttracker.AddRequest) (providers.Provider, error) {
|
||||
if wm.samlModel == nil {
|
||||
return nil, errors.ThrowInternal(nil, "COMMAND-csi30hdscv", "ErrorsIDPConfig.NotExisting")
|
||||
}
|
||||
return wm.samlModel.ToProvider(callbackURL, idpAlg, getRequest, addRequest)
|
||||
}
|
||||
|
Reference in New Issue
Block a user