feat(saml): allow setting nameid-format and alternative mapping for transient format (#7979)

# Which Problems Are Solved

ZITADEL currently always uses
`urn:oasis:names:tc:SAML:2.0:nameid-format:persistent` in SAML requests,
relying on the IdP to respect that flag and always return a peristent
nameid in order to be able to map the external user with an existing
user (idp link) in ZITADEL.
In case the IdP however returns a
`urn:oasis:names:tc:SAML:2.0:nameid-format:transient` (transient)
nameid, the attribute will differ between each request and it will not
be possible to match existing users.

# How the Problems Are Solved

This PR adds the following two options on SAML IdP:
- **nameIDFormat**: allows to set the nameid-format used in the SAML
Request
- **transientMappingAttributeName**: allows to set an attribute name,
which will be used instead of the nameid itself in case the returned
nameid-format is transient

# Additional Changes

To reduce impact on current installations, the `idp_templates6_saml`
table is altered with the two added columns by a setup job. New
installations will automatically get the table with the two columns
directly.
All idp unit tests are updated to use `expectEventstore` instead of the
deprecated `eventstoreExpect`.

# Additional Context

Closes #7483
Closes #7743

---------

Co-authored-by: peintnermax <max@caos.ch>
Co-authored-by: Stefan Benz <46600784+stebenz@users.noreply.github.com>
This commit is contained in:
Livio Spring
2024-05-23 07:04:07 +02:00
committed by GitHub
parent 12be21a3ff
commit e57a9b57c8
58 changed files with 1306 additions and 720 deletions

View File

@@ -1726,13 +1726,15 @@ func (wm *AppleIDPWriteModel) GetProviderOptions() idp.Options {
type SAMLIDPWriteModel struct {
eventstore.WriteModel
Name string
ID string
Metadata []byte
Key *crypto.CryptoValue
Certificate []byte
Binding string
WithSignedRequest bool
Name string
ID string
Metadata []byte
Key *crypto.CryptoValue
Certificate []byte
Binding string
WithSignedRequest bool
NameIDFormat *domain.SAMLNameIDFormat
TransientMappingAttributeName string
idp.Options
State domain.IDPState
@@ -1759,6 +1761,8 @@ func (wm *SAMLIDPWriteModel) reduceAddedEvent(e *idp.SAMLIDPAddedEvent) {
wm.Certificate = e.Certificate
wm.Binding = e.Binding
wm.WithSignedRequest = e.WithSignedRequest
wm.NameIDFormat = e.NameIDFormat
wm.TransientMappingAttributeName = e.TransientMappingAttributeName
wm.Options = e.Options
wm.State = domain.IDPStateActive
}
@@ -1782,6 +1786,12 @@ func (wm *SAMLIDPWriteModel) reduceChangedEvent(e *idp.SAMLIDPChangedEvent) {
if e.WithSignedRequest != nil {
wm.WithSignedRequest = *e.WithSignedRequest
}
if e.NameIDFormat != nil {
wm.NameIDFormat = e.NameIDFormat
}
if e.TransientMappingAttributeName != nil {
wm.TransientMappingAttributeName = *e.TransientMappingAttributeName
}
wm.Options.ReduceChanges(e.OptionChanges)
}
@@ -1793,6 +1803,8 @@ func (wm *SAMLIDPWriteModel) NewChanges(
secretCrypto crypto.EncryptionAlgorithm,
binding string,
withSignedRequest bool,
nameIDFormat *domain.SAMLNameIDFormat,
transientMappingAttributeName string,
options idp.Options,
) ([]idp.SAMLIDPChanges, error) {
changes := make([]idp.SAMLIDPChanges, 0)
@@ -1818,6 +1830,12 @@ func (wm *SAMLIDPWriteModel) NewChanges(
if wm.WithSignedRequest != withSignedRequest {
changes = append(changes, idp.ChangeSAMLWithSignedRequest(withSignedRequest))
}
if wm.NameIDFormat != nameIDFormat {
changes = append(changes, idp.ChangeSAMLNameIDFormat(nameIDFormat))
}
if wm.TransientMappingAttributeName != transientMappingAttributeName {
changes = append(changes, idp.ChangeSAMLTransientMappingAttributeName(transientMappingAttributeName))
}
opts := wm.Options.Changes(options)
if !opts.IsZero() {
changes = append(changes, idp.ChangeSAMLOptions(opts))
@@ -1850,6 +1868,12 @@ func (wm *SAMLIDPWriteModel) ToProvider(callbackURL string, idpAlg crypto.Encryp
if wm.Binding != "" {
opts = append(opts, saml2.WithBinding(wm.Binding))
}
if wm.NameIDFormat != nil {
opts = append(opts, saml2.WithNameIDFormat(*wm.NameIDFormat))
}
if wm.TransientMappingAttributeName != "" {
opts = append(opts, saml2.WithTransientMappingAttributeName(wm.TransientMappingAttributeName))
}
opts = append(opts, saml2.WithCustomRequestTracker(
requesttracker.New(
addRequest,