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:
Stefan Benz
2023-09-29 11:26:14 +02:00
committed by GitHub
parent 2e99d0fe1b
commit 15fd3045e0
82 changed files with 6301 additions and 245 deletions

View File

@@ -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)
}