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

@@ -155,12 +155,14 @@ type AppleIDPTemplate struct {
}
type SAMLIDPTemplate struct {
IDPID string
Metadata []byte
Key *crypto.CryptoValue
Certificate []byte
Binding string
WithSignedRequest bool
IDPID string
Metadata []byte
Key *crypto.CryptoValue
Certificate []byte
Binding string
WithSignedRequest bool
NameIDFormat sql.Null[domain.SAMLNameIDFormat]
TransientMappingAttributeName string
}
var (
@@ -700,6 +702,14 @@ var (
name: projection.SAMLWithSignedRequestCol,
table: samlIdpTemplateTable,
}
SAMLNameIDFormatCol = Column{
name: projection.SAMLNameIDFormatCol,
table: samlIdpTemplateTable,
}
SAMLTransientMappingAttributeNameCol = Column{
name: projection.SAMLTransientMappingAttributeName,
table: samlIdpTemplateTable,
}
)
// IDPTemplateByID searches for the requested id
@@ -883,6 +893,8 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
SAMLCertificateCol.identifier(),
SAMLBindingCol.identifier(),
SAMLWithSignedRequestCol.identifier(),
SAMLNameIDFormatCol.identifier(),
SAMLTransientMappingAttributeNameCol.identifier(),
// ldap
LDAPIDCol.identifier(),
LDAPServersCol.identifier(),
@@ -997,6 +1009,8 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
var samlCertificate []byte
samlBinding := sql.NullString{}
samlWithSignedRequest := sql.NullBool{}
samlNameIDFormat := sql.Null[domain.SAMLNameIDFormat]{}
samlTransientMappingAttributeName := sql.NullString{}
ldapID := sql.NullString{}
ldapServers := database.TextArray[string]{}
@@ -1109,6 +1123,8 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
&samlCertificate,
&samlBinding,
&samlWithSignedRequest,
&samlNameIDFormat,
&samlTransientMappingAttributeName,
// ldap
&ldapID,
&ldapServers,
@@ -1237,12 +1253,14 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
}
if samlID.Valid {
idpTemplate.SAMLIDPTemplate = &SAMLIDPTemplate{
IDPID: samlID.String,
Metadata: samlMetadata,
Key: samlKey,
Certificate: samlCertificate,
Binding: samlBinding.String,
WithSignedRequest: samlWithSignedRequest.Bool,
IDPID: samlID.String,
Metadata: samlMetadata,
Key: samlKey,
Certificate: samlCertificate,
Binding: samlBinding.String,
WithSignedRequest: samlWithSignedRequest.Bool,
NameIDFormat: samlNameIDFormat,
TransientMappingAttributeName: samlTransientMappingAttributeName.String,
}
}
if ldapID.Valid {
@@ -1370,6 +1388,8 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
SAMLCertificateCol.identifier(),
SAMLBindingCol.identifier(),
SAMLWithSignedRequestCol.identifier(),
SAMLNameIDFormatCol.identifier(),
SAMLTransientMappingAttributeNameCol.identifier(),
// ldap
LDAPIDCol.identifier(),
LDAPServersCol.identifier(),
@@ -1489,6 +1509,8 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
var samlCertificate []byte
samlBinding := sql.NullString{}
samlWithSignedRequest := sql.NullBool{}
samlNameIDFormat := sql.Null[domain.SAMLNameIDFormat]{}
samlTransientMappingAttributeName := sql.NullString{}
ldapID := sql.NullString{}
ldapServers := database.TextArray[string]{}
@@ -1601,6 +1623,8 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
&samlCertificate,
&samlBinding,
&samlWithSignedRequest,
&samlNameIDFormat,
&samlTransientMappingAttributeName,
// ldap
&ldapID,
&ldapServers,
@@ -1728,12 +1752,14 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
}
if samlID.Valid {
idpTemplate.SAMLIDPTemplate = &SAMLIDPTemplate{
IDPID: samlID.String,
Metadata: samlMetadata,
Key: samlKey,
Certificate: samlCertificate,
Binding: samlBinding.String,
WithSignedRequest: samlWithSignedRequest.Bool,
IDPID: samlID.String,
Metadata: samlMetadata,
Key: samlKey,
Certificate: samlCertificate,
Binding: samlBinding.String,
WithSignedRequest: samlWithSignedRequest.Bool,
NameIDFormat: samlNameIDFormat,
TransientMappingAttributeName: samlTransientMappingAttributeName.String,
}
}
if ldapID.Valid {