2023-02-15 09:14:59 +01:00
|
|
|
package query
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"database/sql"
|
2023-12-08 16:30:55 +02:00
|
|
|
"errors"
|
2023-02-15 09:14:59 +01:00
|
|
|
"time"
|
|
|
|
|
|
|
|
sq "github.com/Masterminds/squirrel"
|
2023-10-19 12:19:10 +02:00
|
|
|
"github.com/zitadel/logging"
|
|
|
|
|
2023-02-15 09:14:59 +01:00
|
|
|
"github.com/zitadel/zitadel/internal/api/authz"
|
|
|
|
"github.com/zitadel/zitadel/internal/crypto"
|
2023-02-21 18:18:28 +01:00
|
|
|
"github.com/zitadel/zitadel/internal/database"
|
2023-02-15 09:14:59 +01:00
|
|
|
"github.com/zitadel/zitadel/internal/domain"
|
2023-10-19 12:19:10 +02:00
|
|
|
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
|
2023-02-15 09:14:59 +01:00
|
|
|
"github.com/zitadel/zitadel/internal/query/projection"
|
|
|
|
"github.com/zitadel/zitadel/internal/repository/idp"
|
|
|
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
2023-12-08 16:30:55 +02:00
|
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
2023-02-15 09:14:59 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type IDPTemplate struct {
|
|
|
|
CreationDate time.Time
|
|
|
|
ChangeDate time.Time
|
|
|
|
Sequence uint64
|
|
|
|
ResourceOwner string
|
|
|
|
ID string
|
|
|
|
State domain.IDPState
|
|
|
|
Name string
|
|
|
|
Type domain.IDPType
|
|
|
|
OwnerType domain.IdentityProviderType
|
|
|
|
IsCreationAllowed bool
|
|
|
|
IsLinkingAllowed bool
|
|
|
|
IsAutoCreation bool
|
|
|
|
IsAutoUpdate bool
|
2024-04-10 17:46:30 +02:00
|
|
|
AutoLinking domain.AutoLinkingOption
|
2023-02-24 15:16:06 +01:00
|
|
|
*OAuthIDPTemplate
|
2023-02-27 16:32:18 +01:00
|
|
|
*OIDCIDPTemplate
|
|
|
|
*JWTIDPTemplate
|
2023-03-15 07:48:37 +01:00
|
|
|
*AzureADIDPTemplate
|
2023-03-08 11:17:28 +01:00
|
|
|
*GitHubIDPTemplate
|
|
|
|
*GitHubEnterpriseIDPTemplate
|
2023-03-13 17:34:29 +01:00
|
|
|
*GitLabIDPTemplate
|
|
|
|
*GitLabSelfHostedIDPTemplate
|
2023-02-21 18:18:28 +01:00
|
|
|
*GoogleIDPTemplate
|
2023-02-15 09:14:59 +01:00
|
|
|
*LDAPIDPTemplate
|
2023-08-31 08:39:16 +02:00
|
|
|
*AppleIDPTemplate
|
2023-09-29 11:26:14 +02:00
|
|
|
*SAMLIDPTemplate
|
2023-02-15 09:14:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
type IDPTemplates struct {
|
|
|
|
SearchResponse
|
|
|
|
Templates []*IDPTemplate
|
|
|
|
}
|
|
|
|
|
2023-02-24 15:16:06 +01:00
|
|
|
type OAuthIDPTemplate struct {
|
|
|
|
IDPID string
|
|
|
|
ClientID string
|
|
|
|
ClientSecret *crypto.CryptoValue
|
|
|
|
AuthorizationEndpoint string
|
|
|
|
TokenEndpoint string
|
|
|
|
UserEndpoint string
|
2023-10-19 12:19:10 +02:00
|
|
|
Scopes database.TextArray[string]
|
2023-03-03 11:38:49 +01:00
|
|
|
IDAttribute string
|
2025-02-26 13:20:47 +01:00
|
|
|
UsePKCE bool
|
2023-02-24 15:16:06 +01:00
|
|
|
}
|
|
|
|
|
2023-02-27 16:32:18 +01:00
|
|
|
type OIDCIDPTemplate struct {
|
2023-03-16 16:47:22 +01:00
|
|
|
IDPID string
|
|
|
|
ClientID string
|
|
|
|
ClientSecret *crypto.CryptoValue
|
|
|
|
Issuer string
|
2023-10-19 12:19:10 +02:00
|
|
|
Scopes database.TextArray[string]
|
2023-03-16 16:47:22 +01:00
|
|
|
IsIDTokenMapping bool
|
2025-02-26 13:20:47 +01:00
|
|
|
UsePKCE bool
|
2023-02-27 16:32:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
type JWTIDPTemplate struct {
|
|
|
|
IDPID string
|
|
|
|
Issuer string
|
|
|
|
KeysEndpoint string
|
|
|
|
HeaderName string
|
|
|
|
Endpoint string
|
|
|
|
}
|
|
|
|
|
2023-03-15 07:48:37 +01:00
|
|
|
type AzureADIDPTemplate struct {
|
|
|
|
IDPID string
|
|
|
|
ClientID string
|
|
|
|
ClientSecret *crypto.CryptoValue
|
2023-10-19 12:19:10 +02:00
|
|
|
Scopes database.TextArray[string]
|
2023-03-15 07:48:37 +01:00
|
|
|
Tenant string
|
|
|
|
IsEmailVerified bool
|
|
|
|
}
|
|
|
|
|
2023-03-08 11:17:28 +01:00
|
|
|
type GitHubIDPTemplate struct {
|
|
|
|
IDPID string
|
|
|
|
ClientID string
|
|
|
|
ClientSecret *crypto.CryptoValue
|
2023-10-19 12:19:10 +02:00
|
|
|
Scopes database.TextArray[string]
|
2023-03-08 11:17:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
type GitHubEnterpriseIDPTemplate struct {
|
|
|
|
IDPID string
|
|
|
|
ClientID string
|
|
|
|
ClientSecret *crypto.CryptoValue
|
|
|
|
AuthorizationEndpoint string
|
|
|
|
TokenEndpoint string
|
|
|
|
UserEndpoint string
|
2023-10-19 12:19:10 +02:00
|
|
|
Scopes database.TextArray[string]
|
2023-03-08 11:17:28 +01:00
|
|
|
}
|
|
|
|
|
2023-03-13 17:34:29 +01:00
|
|
|
type GitLabIDPTemplate struct {
|
|
|
|
IDPID string
|
|
|
|
ClientID string
|
|
|
|
ClientSecret *crypto.CryptoValue
|
2023-10-19 12:19:10 +02:00
|
|
|
Scopes database.TextArray[string]
|
2023-03-13 17:34:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
type GitLabSelfHostedIDPTemplate struct {
|
|
|
|
IDPID string
|
|
|
|
Issuer string
|
|
|
|
ClientID string
|
|
|
|
ClientSecret *crypto.CryptoValue
|
2023-10-19 12:19:10 +02:00
|
|
|
Scopes database.TextArray[string]
|
2023-03-13 17:34:29 +01:00
|
|
|
}
|
|
|
|
|
2023-02-21 18:18:28 +01:00
|
|
|
type GoogleIDPTemplate struct {
|
|
|
|
IDPID string
|
|
|
|
ClientID string
|
|
|
|
ClientSecret *crypto.CryptoValue
|
2023-10-19 12:19:10 +02:00
|
|
|
Scopes database.TextArray[string]
|
2023-02-21 18:18:28 +01:00
|
|
|
}
|
|
|
|
|
2023-02-15 09:14:59 +01:00
|
|
|
type LDAPIDPTemplate struct {
|
2023-03-24 16:18:56 +01:00
|
|
|
IDPID string
|
|
|
|
Servers []string
|
|
|
|
StartTLS bool
|
|
|
|
BaseDN string
|
|
|
|
BindDN string
|
|
|
|
BindPassword *crypto.CryptoValue
|
|
|
|
UserBase string
|
|
|
|
UserObjectClasses []string
|
|
|
|
UserFilters []string
|
|
|
|
Timeout time.Duration
|
2025-02-18 10:06:50 +00:00
|
|
|
RootCA []byte
|
2023-02-15 09:14:59 +01:00
|
|
|
idp.LDAPAttributes
|
|
|
|
}
|
|
|
|
|
2023-08-31 08:39:16 +02:00
|
|
|
type AppleIDPTemplate struct {
|
|
|
|
IDPID string
|
|
|
|
ClientID string
|
|
|
|
TeamID string
|
|
|
|
KeyID string
|
|
|
|
PrivateKey *crypto.CryptoValue
|
2023-10-19 12:19:10 +02:00
|
|
|
Scopes database.TextArray[string]
|
2023-08-31 08:39:16 +02:00
|
|
|
}
|
|
|
|
|
2023-09-29 11:26:14 +02:00
|
|
|
type SAMLIDPTemplate struct {
|
2024-05-23 07:04:07 +02:00
|
|
|
IDPID string
|
|
|
|
Metadata []byte
|
|
|
|
Key *crypto.CryptoValue
|
|
|
|
Certificate []byte
|
|
|
|
Binding string
|
|
|
|
WithSignedRequest bool
|
|
|
|
NameIDFormat sql.Null[domain.SAMLNameIDFormat]
|
|
|
|
TransientMappingAttributeName string
|
feat: federated logout for SAML IdPs (#9931)
# Which Problems Are Solved
Currently if a user signs in using an IdP, once they sign out of
Zitadel, the corresponding IdP session is not terminated. This can be
the desired behavior. In some cases, e.g. when using a shared computer
it results in a potential security risk, since a follower user might be
able to sign in as the previous using the still open IdP session.
# How the Problems Are Solved
- Admins can enabled a federated logout option on SAML IdPs through the
Admin and Management APIs.
- During the termination of a login V1 session using OIDC end_session
endpoint, Zitadel will check if an IdP was used to authenticate that
session.
- In case there was a SAML IdP used with Federated Logout enabled, it
will intercept the logout process, store the information into the shared
cache and redirect to the federated logout endpoint in the V1 login.
- The V1 login federated logout endpoint checks every request on an
existing cache entry. On success it will create a SAML logout request
for the used IdP and either redirect or POST to the configured SLO
endpoint. The cache entry is updated with a `redirected` state.
- A SLO endpoint is added to the `/idp` handlers, which will handle the
SAML logout responses. At the moment it will check again for an existing
federated logout entry (with state `redirected`) in the cache. On
success, the user is redirected to the initially provided
`post_logout_redirect_uri` from the end_session request.
# Additional Changes
None
# Additional Context
- This PR merges the https://github.com/zitadel/zitadel/pull/9841 and
https://github.com/zitadel/zitadel/pull/9854 to main, additionally
updating the docs on Entra ID SAML.
- closes #9228
- backport to 3.x
---------
Co-authored-by: Silvan <27845747+adlerhurst@users.noreply.github.com>
Co-authored-by: Zach Hirschtritt <zachary.hirschtritt@klaviyo.com>
2025-05-23 13:52:25 +02:00
|
|
|
FederatedLogoutEnabled bool
|
2023-09-29 11:26:14 +02:00
|
|
|
}
|
|
|
|
|
2023-02-15 09:14:59 +01:00
|
|
|
var (
|
|
|
|
idpTemplateTable = table{
|
|
|
|
name: projection.IDPTemplateTable,
|
|
|
|
instanceIDCol: projection.IDPTemplateInstanceIDCol,
|
|
|
|
}
|
|
|
|
IDPTemplateIDCol = Column{
|
|
|
|
name: projection.IDPTemplateIDCol,
|
|
|
|
table: idpTemplateTable,
|
|
|
|
}
|
|
|
|
IDPTemplateCreationDateCol = Column{
|
|
|
|
name: projection.IDPTemplateCreationDateCol,
|
|
|
|
table: idpTemplateTable,
|
|
|
|
}
|
|
|
|
IDPTemplateChangeDateCol = Column{
|
|
|
|
name: projection.IDPTemplateChangeDateCol,
|
|
|
|
table: idpTemplateTable,
|
|
|
|
}
|
|
|
|
IDPTemplateSequenceCol = Column{
|
|
|
|
name: projection.IDPTemplateSequenceCol,
|
|
|
|
table: idpTemplateTable,
|
|
|
|
}
|
|
|
|
IDPTemplateResourceOwnerCol = Column{
|
|
|
|
name: projection.IDPTemplateResourceOwnerCol,
|
|
|
|
table: idpTemplateTable,
|
|
|
|
}
|
|
|
|
IDPTemplateInstanceIDCol = Column{
|
|
|
|
name: projection.IDPTemplateInstanceIDCol,
|
|
|
|
table: idpTemplateTable,
|
|
|
|
}
|
|
|
|
IDPTemplateStateCol = Column{
|
|
|
|
name: projection.IDPTemplateStateCol,
|
|
|
|
table: idpTemplateTable,
|
|
|
|
}
|
|
|
|
IDPTemplateNameCol = Column{
|
|
|
|
name: projection.IDPTemplateNameCol,
|
|
|
|
table: idpTemplateTable,
|
|
|
|
}
|
|
|
|
IDPTemplateOwnerTypeCol = Column{
|
|
|
|
name: projection.IDPOwnerTypeCol,
|
|
|
|
table: idpTemplateTable,
|
|
|
|
}
|
|
|
|
IDPTemplateTypeCol = Column{
|
|
|
|
name: projection.IDPTemplateTypeCol,
|
|
|
|
table: idpTemplateTable,
|
|
|
|
}
|
|
|
|
IDPTemplateOwnerRemovedCol = Column{
|
|
|
|
name: projection.IDPTemplateOwnerRemovedCol,
|
|
|
|
table: idpTemplateTable,
|
|
|
|
}
|
|
|
|
IDPTemplateIsCreationAllowedCol = Column{
|
|
|
|
name: projection.IDPTemplateIsCreationAllowedCol,
|
|
|
|
table: idpTemplateTable,
|
|
|
|
}
|
|
|
|
IDPTemplateIsLinkingAllowedCol = Column{
|
|
|
|
name: projection.IDPTemplateIsLinkingAllowedCol,
|
|
|
|
table: idpTemplateTable,
|
|
|
|
}
|
|
|
|
IDPTemplateIsAutoCreationCol = Column{
|
|
|
|
name: projection.IDPTemplateIsAutoCreationCol,
|
|
|
|
table: idpTemplateTable,
|
|
|
|
}
|
|
|
|
IDPTemplateIsAutoUpdateCol = Column{
|
|
|
|
name: projection.IDPTemplateIsAutoUpdateCol,
|
|
|
|
table: idpTemplateTable,
|
|
|
|
}
|
2024-04-10 17:46:30 +02:00
|
|
|
IDPTemplateAutoLinkingCol = Column{
|
|
|
|
name: projection.IDPTemplateAutoLinkingCol,
|
|
|
|
table: idpTemplateTable,
|
|
|
|
}
|
2023-02-15 09:14:59 +01:00
|
|
|
)
|
|
|
|
|
2023-02-24 15:16:06 +01:00
|
|
|
var (
|
|
|
|
oauthIdpTemplateTable = table{
|
|
|
|
name: projection.IDPTemplateOAuthTable,
|
|
|
|
instanceIDCol: projection.OAuthInstanceIDCol,
|
|
|
|
}
|
|
|
|
OAuthIDCol = Column{
|
|
|
|
name: projection.OAuthIDCol,
|
|
|
|
table: oauthIdpTemplateTable,
|
|
|
|
}
|
|
|
|
OAuthInstanceIDCol = Column{
|
|
|
|
name: projection.OAuthInstanceIDCol,
|
|
|
|
table: oauthIdpTemplateTable,
|
|
|
|
}
|
|
|
|
OAuthClientIDCol = Column{
|
|
|
|
name: projection.OAuthClientIDCol,
|
|
|
|
table: oauthIdpTemplateTable,
|
|
|
|
}
|
|
|
|
OAuthClientSecretCol = Column{
|
|
|
|
name: projection.OAuthClientSecretCol,
|
|
|
|
table: oauthIdpTemplateTable,
|
|
|
|
}
|
|
|
|
OAuthAuthorizationEndpointCol = Column{
|
|
|
|
name: projection.OAuthAuthorizationEndpointCol,
|
|
|
|
table: oauthIdpTemplateTable,
|
|
|
|
}
|
|
|
|
OAuthTokenEndpointCol = Column{
|
|
|
|
name: projection.OAuthTokenEndpointCol,
|
|
|
|
table: oauthIdpTemplateTable,
|
|
|
|
}
|
|
|
|
OAuthUserEndpointCol = Column{
|
|
|
|
name: projection.OAuthUserEndpointCol,
|
|
|
|
table: oauthIdpTemplateTable,
|
|
|
|
}
|
|
|
|
OAuthScopesCol = Column{
|
|
|
|
name: projection.OAuthScopesCol,
|
|
|
|
table: oauthIdpTemplateTable,
|
|
|
|
}
|
2023-03-03 11:38:49 +01:00
|
|
|
OAuthIDAttributeCol = Column{
|
|
|
|
name: projection.OAuthIDAttributeCol,
|
|
|
|
table: oauthIdpTemplateTable,
|
|
|
|
}
|
2025-02-26 13:20:47 +01:00
|
|
|
OAuthUsePKCECol = Column{
|
|
|
|
name: projection.OAuthUsePKCECol,
|
|
|
|
table: oauthIdpTemplateTable,
|
|
|
|
}
|
2023-02-24 15:16:06 +01:00
|
|
|
)
|
|
|
|
|
2023-02-27 16:32:18 +01:00
|
|
|
var (
|
|
|
|
oidcIdpTemplateTable = table{
|
|
|
|
name: projection.IDPTemplateOIDCTable,
|
|
|
|
instanceIDCol: projection.OIDCInstanceIDCol,
|
|
|
|
}
|
|
|
|
OIDCIDCol = Column{
|
|
|
|
name: projection.OIDCIDCol,
|
|
|
|
table: oidcIdpTemplateTable,
|
|
|
|
}
|
|
|
|
OIDCInstanceIDCol = Column{
|
|
|
|
name: projection.OIDCInstanceIDCol,
|
|
|
|
table: oidcIdpTemplateTable,
|
|
|
|
}
|
|
|
|
OIDCIssuerCol = Column{
|
|
|
|
name: projection.OIDCIssuerCol,
|
|
|
|
table: oidcIdpTemplateTable,
|
|
|
|
}
|
|
|
|
OIDCClientIDCol = Column{
|
|
|
|
name: projection.OIDCClientIDCol,
|
|
|
|
table: oidcIdpTemplateTable,
|
|
|
|
}
|
|
|
|
OIDCClientSecretCol = Column{
|
|
|
|
name: projection.OIDCClientSecretCol,
|
|
|
|
table: oidcIdpTemplateTable,
|
|
|
|
}
|
|
|
|
OIDCScopesCol = Column{
|
|
|
|
name: projection.OIDCScopesCol,
|
|
|
|
table: oidcIdpTemplateTable,
|
|
|
|
}
|
2023-03-16 16:47:22 +01:00
|
|
|
OIDCIDTokenMappingCol = Column{
|
|
|
|
name: projection.OIDCIDTokenMappingCol,
|
|
|
|
table: oidcIdpTemplateTable,
|
|
|
|
}
|
2025-02-26 13:20:47 +01:00
|
|
|
OIDCUsePKCECol = Column{
|
|
|
|
name: projection.OIDCUsePKCECol,
|
|
|
|
table: oidcIdpTemplateTable,
|
|
|
|
}
|
2023-02-27 16:32:18 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
jwtIdpTemplateTable = table{
|
|
|
|
name: projection.IDPTemplateJWTTable,
|
|
|
|
instanceIDCol: projection.JWTInstanceIDCol,
|
|
|
|
}
|
|
|
|
JWTIDCol = Column{
|
|
|
|
name: projection.JWTIDCol,
|
|
|
|
table: jwtIdpTemplateTable,
|
|
|
|
}
|
|
|
|
JWTInstanceIDCol = Column{
|
|
|
|
name: projection.JWTInstanceIDCol,
|
|
|
|
table: jwtIdpTemplateTable,
|
|
|
|
}
|
|
|
|
JWTIssuerCol = Column{
|
|
|
|
name: projection.JWTIssuerCol,
|
|
|
|
table: jwtIdpTemplateTable,
|
|
|
|
}
|
|
|
|
JWTEndpointCol = Column{
|
|
|
|
name: projection.JWTEndpointCol,
|
|
|
|
table: jwtIdpTemplateTable,
|
|
|
|
}
|
|
|
|
JWTKeysEndpointCol = Column{
|
|
|
|
name: projection.JWTKeysEndpointCol,
|
|
|
|
table: jwtIdpTemplateTable,
|
|
|
|
}
|
|
|
|
JWTHeaderNameCol = Column{
|
|
|
|
name: projection.JWTHeaderNameCol,
|
|
|
|
table: jwtIdpTemplateTable,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2023-03-15 07:48:37 +01:00
|
|
|
var (
|
|
|
|
azureadIdpTemplateTable = table{
|
|
|
|
name: projection.IDPTemplateAzureADTable,
|
|
|
|
instanceIDCol: projection.AzureADInstanceIDCol,
|
|
|
|
}
|
|
|
|
AzureADIDCol = Column{
|
|
|
|
name: projection.AzureADIDCol,
|
|
|
|
table: azureadIdpTemplateTable,
|
|
|
|
}
|
|
|
|
AzureADInstanceIDCol = Column{
|
|
|
|
name: projection.AzureADInstanceIDCol,
|
|
|
|
table: azureadIdpTemplateTable,
|
|
|
|
}
|
|
|
|
AzureADClientIDCol = Column{
|
|
|
|
name: projection.AzureADClientIDCol,
|
|
|
|
table: azureadIdpTemplateTable,
|
|
|
|
}
|
|
|
|
AzureADClientSecretCol = Column{
|
|
|
|
name: projection.AzureADClientSecretCol,
|
|
|
|
table: azureadIdpTemplateTable,
|
|
|
|
}
|
|
|
|
AzureADScopesCol = Column{
|
|
|
|
name: projection.AzureADScopesCol,
|
|
|
|
table: azureadIdpTemplateTable,
|
|
|
|
}
|
|
|
|
AzureADTenantCol = Column{
|
|
|
|
name: projection.AzureADTenantCol,
|
|
|
|
table: azureadIdpTemplateTable,
|
|
|
|
}
|
|
|
|
AzureADIsEmailVerified = Column{
|
|
|
|
name: projection.AzureADIsEmailVerified,
|
|
|
|
table: azureadIdpTemplateTable,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2023-03-08 11:17:28 +01:00
|
|
|
var (
|
|
|
|
githubIdpTemplateTable = table{
|
|
|
|
name: projection.IDPTemplateGitHubTable,
|
|
|
|
instanceIDCol: projection.GitHubInstanceIDCol,
|
|
|
|
}
|
|
|
|
GitHubIDCol = Column{
|
|
|
|
name: projection.GitHubIDCol,
|
|
|
|
table: githubIdpTemplateTable,
|
|
|
|
}
|
|
|
|
GitHubInstanceIDCol = Column{
|
|
|
|
name: projection.GitHubInstanceIDCol,
|
|
|
|
table: githubIdpTemplateTable,
|
|
|
|
}
|
|
|
|
GitHubClientIDCol = Column{
|
|
|
|
name: projection.GitHubClientIDCol,
|
|
|
|
table: githubIdpTemplateTable,
|
|
|
|
}
|
|
|
|
GitHubClientSecretCol = Column{
|
|
|
|
name: projection.GitHubClientSecretCol,
|
|
|
|
table: githubIdpTemplateTable,
|
|
|
|
}
|
|
|
|
GitHubScopesCol = Column{
|
|
|
|
name: projection.GitHubScopesCol,
|
|
|
|
table: githubIdpTemplateTable,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
githubEnterpriseIdpTemplateTable = table{
|
|
|
|
name: projection.IDPTemplateGitHubEnterpriseTable,
|
|
|
|
instanceIDCol: projection.GitHubEnterpriseInstanceIDCol,
|
|
|
|
}
|
|
|
|
GitHubEnterpriseIDCol = Column{
|
|
|
|
name: projection.GitHubEnterpriseIDCol,
|
|
|
|
table: githubEnterpriseIdpTemplateTable,
|
|
|
|
}
|
|
|
|
GitHubEnterpriseInstanceIDCol = Column{
|
|
|
|
name: projection.GitHubEnterpriseInstanceIDCol,
|
|
|
|
table: githubEnterpriseIdpTemplateTable,
|
|
|
|
}
|
|
|
|
GitHubEnterpriseClientIDCol = Column{
|
|
|
|
name: projection.GitHubEnterpriseClientIDCol,
|
|
|
|
table: githubEnterpriseIdpTemplateTable,
|
|
|
|
}
|
|
|
|
GitHubEnterpriseClientSecretCol = Column{
|
|
|
|
name: projection.GitHubEnterpriseClientSecretCol,
|
|
|
|
table: githubEnterpriseIdpTemplateTable,
|
|
|
|
}
|
|
|
|
GitHubEnterpriseAuthorizationEndpointCol = Column{
|
|
|
|
name: projection.GitHubEnterpriseAuthorizationEndpointCol,
|
|
|
|
table: githubEnterpriseIdpTemplateTable,
|
|
|
|
}
|
|
|
|
GitHubEnterpriseTokenEndpointCol = Column{
|
|
|
|
name: projection.GitHubEnterpriseTokenEndpointCol,
|
|
|
|
table: githubEnterpriseIdpTemplateTable,
|
|
|
|
}
|
|
|
|
GitHubEnterpriseUserEndpointCol = Column{
|
|
|
|
name: projection.GitHubEnterpriseUserEndpointCol,
|
|
|
|
table: githubEnterpriseIdpTemplateTable,
|
|
|
|
}
|
|
|
|
GitHubEnterpriseScopesCol = Column{
|
|
|
|
name: projection.GitHubEnterpriseScopesCol,
|
|
|
|
table: githubEnterpriseIdpTemplateTable,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2023-03-13 17:34:29 +01:00
|
|
|
var (
|
|
|
|
gitlabIdpTemplateTable = table{
|
|
|
|
name: projection.IDPTemplateGitLabTable,
|
|
|
|
instanceIDCol: projection.GitLabInstanceIDCol,
|
|
|
|
}
|
|
|
|
GitLabIDCol = Column{
|
|
|
|
name: projection.GitLabIDCol,
|
|
|
|
table: gitlabIdpTemplateTable,
|
|
|
|
}
|
|
|
|
GitLabInstanceIDCol = Column{
|
|
|
|
name: projection.GitLabInstanceIDCol,
|
|
|
|
table: gitlabIdpTemplateTable,
|
|
|
|
}
|
|
|
|
GitLabClientIDCol = Column{
|
|
|
|
name: projection.GitLabClientIDCol,
|
|
|
|
table: gitlabIdpTemplateTable,
|
|
|
|
}
|
|
|
|
GitLabClientSecretCol = Column{
|
|
|
|
name: projection.GitLabClientSecretCol,
|
|
|
|
table: gitlabIdpTemplateTable,
|
|
|
|
}
|
|
|
|
GitLabScopesCol = Column{
|
|
|
|
name: projection.GitLabScopesCol,
|
|
|
|
table: gitlabIdpTemplateTable,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
gitlabSelfHostedIdpTemplateTable = table{
|
|
|
|
name: projection.IDPTemplateGitLabSelfHostedTable,
|
|
|
|
instanceIDCol: projection.GitLabSelfHostedInstanceIDCol,
|
|
|
|
}
|
|
|
|
GitLabSelfHostedIDCol = Column{
|
|
|
|
name: projection.GitLabSelfHostedIDCol,
|
|
|
|
table: gitlabSelfHostedIdpTemplateTable,
|
|
|
|
}
|
|
|
|
GitLabSelfHostedInstanceIDCol = Column{
|
|
|
|
name: projection.GitLabSelfHostedInstanceIDCol,
|
|
|
|
table: gitlabSelfHostedIdpTemplateTable,
|
|
|
|
}
|
|
|
|
GitLabSelfHostedIssuerCol = Column{
|
|
|
|
name: projection.GitLabSelfHostedIssuerCol,
|
|
|
|
table: gitlabSelfHostedIdpTemplateTable,
|
|
|
|
}
|
|
|
|
GitLabSelfHostedClientIDCol = Column{
|
|
|
|
name: projection.GitLabSelfHostedClientIDCol,
|
|
|
|
table: gitlabSelfHostedIdpTemplateTable,
|
|
|
|
}
|
|
|
|
GitLabSelfHostedClientSecretCol = Column{
|
|
|
|
name: projection.GitLabSelfHostedClientSecretCol,
|
|
|
|
table: gitlabSelfHostedIdpTemplateTable,
|
|
|
|
}
|
|
|
|
GitLabSelfHostedScopesCol = Column{
|
|
|
|
name: projection.GitLabSelfHostedScopesCol,
|
|
|
|
table: gitlabSelfHostedIdpTemplateTable,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2023-02-21 18:18:28 +01:00
|
|
|
var (
|
|
|
|
googleIdpTemplateTable = table{
|
|
|
|
name: projection.IDPTemplateGoogleTable,
|
|
|
|
instanceIDCol: projection.GoogleInstanceIDCol,
|
|
|
|
}
|
|
|
|
GoogleIDCol = Column{
|
|
|
|
name: projection.GoogleIDCol,
|
|
|
|
table: googleIdpTemplateTable,
|
|
|
|
}
|
|
|
|
GoogleInstanceIDCol = Column{
|
|
|
|
name: projection.GoogleInstanceIDCol,
|
|
|
|
table: googleIdpTemplateTable,
|
|
|
|
}
|
|
|
|
GoogleClientIDCol = Column{
|
|
|
|
name: projection.GoogleClientIDCol,
|
|
|
|
table: googleIdpTemplateTable,
|
|
|
|
}
|
|
|
|
GoogleClientSecretCol = Column{
|
|
|
|
name: projection.GoogleClientSecretCol,
|
|
|
|
table: googleIdpTemplateTable,
|
|
|
|
}
|
|
|
|
GoogleScopesCol = Column{
|
|
|
|
name: projection.GoogleScopesCol,
|
|
|
|
table: googleIdpTemplateTable,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2023-02-15 09:14:59 +01:00
|
|
|
var (
|
|
|
|
ldapIdpTemplateTable = table{
|
|
|
|
name: projection.IDPTemplateLDAPTable,
|
|
|
|
instanceIDCol: projection.IDPTemplateInstanceIDCol,
|
|
|
|
}
|
|
|
|
LDAPIDCol = Column{
|
|
|
|
name: projection.LDAPIDCol,
|
|
|
|
table: ldapIdpTemplateTable,
|
|
|
|
}
|
|
|
|
LDAPInstanceIDCol = Column{
|
|
|
|
name: projection.LDAPInstanceIDCol,
|
|
|
|
table: ldapIdpTemplateTable,
|
|
|
|
}
|
2023-03-24 16:18:56 +01:00
|
|
|
LDAPServersCol = Column{
|
|
|
|
name: projection.LDAPServersCol,
|
2023-02-15 09:14:59 +01:00
|
|
|
table: ldapIdpTemplateTable,
|
|
|
|
}
|
2023-03-24 16:18:56 +01:00
|
|
|
LDAPStartTLSCol = Column{
|
|
|
|
name: projection.LDAPStartTLSCol,
|
2023-02-15 09:14:59 +01:00
|
|
|
table: ldapIdpTemplateTable,
|
|
|
|
}
|
|
|
|
LDAPBaseDNCol = Column{
|
|
|
|
name: projection.LDAPBaseDNCol,
|
|
|
|
table: ldapIdpTemplateTable,
|
|
|
|
}
|
2023-03-24 16:18:56 +01:00
|
|
|
LDAPBindDNCol = Column{
|
|
|
|
name: projection.LDAPBindDNCol,
|
|
|
|
table: ldapIdpTemplateTable,
|
|
|
|
}
|
|
|
|
LDAPBindPasswordCol = Column{
|
|
|
|
name: projection.LDAPBindPasswordCol,
|
|
|
|
table: ldapIdpTemplateTable,
|
|
|
|
}
|
|
|
|
LDAPUserBaseCol = Column{
|
|
|
|
name: projection.LDAPUserBaseCol,
|
2023-02-15 09:14:59 +01:00
|
|
|
table: ldapIdpTemplateTable,
|
|
|
|
}
|
2023-03-24 16:18:56 +01:00
|
|
|
LDAPUserObjectClassesCol = Column{
|
|
|
|
name: projection.LDAPUserObjectClassesCol,
|
2023-02-15 09:14:59 +01:00
|
|
|
table: ldapIdpTemplateTable,
|
|
|
|
}
|
2023-03-24 16:18:56 +01:00
|
|
|
LDAPUserFiltersCol = Column{
|
|
|
|
name: projection.LDAPUserFiltersCol,
|
2023-02-15 09:14:59 +01:00
|
|
|
table: ldapIdpTemplateTable,
|
|
|
|
}
|
2023-03-24 16:18:56 +01:00
|
|
|
LDAPTimeoutCol = Column{
|
|
|
|
name: projection.LDAPTimeoutCol,
|
2023-02-15 09:14:59 +01:00
|
|
|
table: ldapIdpTemplateTable,
|
|
|
|
}
|
2025-02-18 10:06:50 +00:00
|
|
|
LDAPRootCACol = Column{
|
|
|
|
name: projection.LDAPRootCACol,
|
|
|
|
table: ldapIdpTemplateTable,
|
|
|
|
}
|
2023-02-15 09:14:59 +01:00
|
|
|
LDAPIDAttributeCol = Column{
|
|
|
|
name: projection.LDAPIDAttributeCol,
|
|
|
|
table: ldapIdpTemplateTable,
|
|
|
|
}
|
|
|
|
LDAPFirstNameAttributeCol = Column{
|
|
|
|
name: projection.LDAPFirstNameAttributeCol,
|
|
|
|
table: ldapIdpTemplateTable,
|
|
|
|
}
|
|
|
|
LDAPLastNameAttributeCol = Column{
|
|
|
|
name: projection.LDAPLastNameAttributeCol,
|
|
|
|
table: ldapIdpTemplateTable,
|
|
|
|
}
|
|
|
|
LDAPDisplayNameAttributeCol = Column{
|
|
|
|
name: projection.LDAPDisplayNameAttributeCol,
|
|
|
|
table: ldapIdpTemplateTable,
|
|
|
|
}
|
|
|
|
LDAPNickNameAttributeCol = Column{
|
|
|
|
name: projection.LDAPNickNameAttributeCol,
|
|
|
|
table: ldapIdpTemplateTable,
|
|
|
|
}
|
|
|
|
LDAPPreferredUsernameAttributeCol = Column{
|
|
|
|
name: projection.LDAPPreferredUsernameAttributeCol,
|
|
|
|
table: ldapIdpTemplateTable,
|
|
|
|
}
|
|
|
|
LDAPEmailAttributeCol = Column{
|
|
|
|
name: projection.LDAPEmailAttributeCol,
|
|
|
|
table: ldapIdpTemplateTable,
|
|
|
|
}
|
|
|
|
LDAPEmailVerifiedAttributeCol = Column{
|
|
|
|
name: projection.LDAPEmailVerifiedAttributeCol,
|
|
|
|
table: ldapIdpTemplateTable,
|
|
|
|
}
|
|
|
|
LDAPPhoneAttributeCol = Column{
|
|
|
|
name: projection.LDAPPhoneAttributeCol,
|
|
|
|
table: ldapIdpTemplateTable,
|
|
|
|
}
|
|
|
|
LDAPPhoneVerifiedAttributeCol = Column{
|
|
|
|
name: projection.LDAPPhoneVerifiedAttributeCol,
|
|
|
|
table: ldapIdpTemplateTable,
|
|
|
|
}
|
|
|
|
LDAPPreferredLanguageAttributeCol = Column{
|
|
|
|
name: projection.LDAPPreferredLanguageAttributeCol,
|
|
|
|
table: ldapIdpTemplateTable,
|
|
|
|
}
|
|
|
|
LDAPAvatarURLAttributeCol = Column{
|
|
|
|
name: projection.LDAPAvatarURLAttributeCol,
|
|
|
|
table: ldapIdpTemplateTable,
|
|
|
|
}
|
|
|
|
LDAPProfileAttributeCol = Column{
|
|
|
|
name: projection.LDAPProfileAttributeCol,
|
|
|
|
table: ldapIdpTemplateTable,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2023-08-31 08:39:16 +02:00
|
|
|
var (
|
|
|
|
appleIdpTemplateTable = table{
|
|
|
|
name: projection.IDPTemplateAppleTable,
|
|
|
|
instanceIDCol: projection.AppleInstanceIDCol,
|
|
|
|
}
|
|
|
|
AppleIDCol = Column{
|
|
|
|
name: projection.AppleIDCol,
|
|
|
|
table: appleIdpTemplateTable,
|
|
|
|
}
|
|
|
|
AppleInstanceIDCol = Column{
|
|
|
|
name: projection.AppleInstanceIDCol,
|
|
|
|
table: appleIdpTemplateTable,
|
|
|
|
}
|
|
|
|
AppleClientIDCol = Column{
|
|
|
|
name: projection.AppleClientIDCol,
|
|
|
|
table: appleIdpTemplateTable,
|
|
|
|
}
|
|
|
|
AppleTeamIDCol = Column{
|
|
|
|
name: projection.AppleTeamIDCol,
|
|
|
|
table: appleIdpTemplateTable,
|
|
|
|
}
|
|
|
|
AppleKeyIDCol = Column{
|
|
|
|
name: projection.AppleKeyIDCol,
|
|
|
|
table: appleIdpTemplateTable,
|
|
|
|
}
|
|
|
|
ApplePrivateKeyCol = Column{
|
|
|
|
name: projection.ApplePrivateKeyCol,
|
|
|
|
table: appleIdpTemplateTable,
|
|
|
|
}
|
|
|
|
AppleScopesCol = Column{
|
|
|
|
name: projection.AppleScopesCol,
|
|
|
|
table: appleIdpTemplateTable,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2023-09-29 11:26:14 +02:00
|
|
|
var (
|
|
|
|
samlIdpTemplateTable = table{
|
|
|
|
name: projection.IDPTemplateSAMLTable,
|
|
|
|
instanceIDCol: projection.IDPTemplateInstanceIDCol,
|
|
|
|
}
|
|
|
|
SAMLIDCol = Column{
|
|
|
|
name: projection.SAMLIDCol,
|
|
|
|
table: samlIdpTemplateTable,
|
|
|
|
}
|
|
|
|
SAMLInstanceCol = Column{
|
|
|
|
name: projection.SAMLInstanceIDCol,
|
|
|
|
table: samlIdpTemplateTable,
|
|
|
|
}
|
|
|
|
SAMLMetadataCol = Column{
|
|
|
|
name: projection.SAMLMetadataCol,
|
|
|
|
table: samlIdpTemplateTable,
|
|
|
|
}
|
|
|
|
SAMLKeyCol = Column{
|
|
|
|
name: projection.SAMLKeyCol,
|
|
|
|
table: samlIdpTemplateTable,
|
|
|
|
}
|
|
|
|
SAMLCertificateCol = Column{
|
|
|
|
name: projection.SAMLCertificateCol,
|
|
|
|
table: samlIdpTemplateTable,
|
|
|
|
}
|
|
|
|
SAMLBindingCol = Column{
|
|
|
|
name: projection.SAMLBindingCol,
|
|
|
|
table: samlIdpTemplateTable,
|
|
|
|
}
|
|
|
|
SAMLWithSignedRequestCol = Column{
|
|
|
|
name: projection.SAMLWithSignedRequestCol,
|
|
|
|
table: samlIdpTemplateTable,
|
|
|
|
}
|
2024-05-23 07:04:07 +02:00
|
|
|
SAMLNameIDFormatCol = Column{
|
|
|
|
name: projection.SAMLNameIDFormatCol,
|
|
|
|
table: samlIdpTemplateTable,
|
|
|
|
}
|
|
|
|
SAMLTransientMappingAttributeNameCol = Column{
|
|
|
|
name: projection.SAMLTransientMappingAttributeName,
|
|
|
|
table: samlIdpTemplateTable,
|
|
|
|
}
|
feat: federated logout for SAML IdPs (#9931)
# Which Problems Are Solved
Currently if a user signs in using an IdP, once they sign out of
Zitadel, the corresponding IdP session is not terminated. This can be
the desired behavior. In some cases, e.g. when using a shared computer
it results in a potential security risk, since a follower user might be
able to sign in as the previous using the still open IdP session.
# How the Problems Are Solved
- Admins can enabled a federated logout option on SAML IdPs through the
Admin and Management APIs.
- During the termination of a login V1 session using OIDC end_session
endpoint, Zitadel will check if an IdP was used to authenticate that
session.
- In case there was a SAML IdP used with Federated Logout enabled, it
will intercept the logout process, store the information into the shared
cache and redirect to the federated logout endpoint in the V1 login.
- The V1 login federated logout endpoint checks every request on an
existing cache entry. On success it will create a SAML logout request
for the used IdP and either redirect or POST to the configured SLO
endpoint. The cache entry is updated with a `redirected` state.
- A SLO endpoint is added to the `/idp` handlers, which will handle the
SAML logout responses. At the moment it will check again for an existing
federated logout entry (with state `redirected`) in the cache. On
success, the user is redirected to the initially provided
`post_logout_redirect_uri` from the end_session request.
# Additional Changes
None
# Additional Context
- This PR merges the https://github.com/zitadel/zitadel/pull/9841 and
https://github.com/zitadel/zitadel/pull/9854 to main, additionally
updating the docs on Entra ID SAML.
- closes #9228
- backport to 3.x
---------
Co-authored-by: Silvan <27845747+adlerhurst@users.noreply.github.com>
Co-authored-by: Zach Hirschtritt <zachary.hirschtritt@klaviyo.com>
2025-05-23 13:52:25 +02:00
|
|
|
SAMLFederatedLogoutEnabledCol = Column{
|
|
|
|
name: projection.SAMLFederatedLogoutEnabled,
|
|
|
|
table: samlIdpTemplateTable,
|
|
|
|
}
|
2023-09-29 11:26:14 +02:00
|
|
|
)
|
|
|
|
|
2024-08-14 20:18:29 +02:00
|
|
|
// IDPTemplateByID searches for the requested id with permission check if necessary
|
|
|
|
func (q *Queries) IDPTemplateByID(ctx context.Context, shouldTriggerBulk bool, id string, withOwnerRemoved bool, permissionCheck domain.PermissionCheck, queries ...SearchQuery) (template *IDPTemplate, err error) {
|
|
|
|
idp, err := q.idpTemplateByID(ctx, shouldTriggerBulk, id, withOwnerRemoved, queries...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if permissionCheck != nil {
|
|
|
|
switch idp.OwnerType {
|
|
|
|
case domain.IdentityProviderTypeSystem:
|
|
|
|
if err := permissionCheck(ctx, domain.PermissionIDPRead, idp.ResourceOwner, idp.ID); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
case domain.IdentityProviderTypeOrg:
|
|
|
|
if err := permissionCheck(ctx, domain.PermissionOrgIDPRead, idp.ResourceOwner, idp.ID); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return idp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// idpTemplateByID searches for the requested id
|
|
|
|
func (q *Queries) idpTemplateByID(ctx context.Context, shouldTriggerBulk bool, id string, withOwnerRemoved bool, queries ...SearchQuery) (template *IDPTemplate, err error) {
|
2023-02-15 09:14:59 +01:00
|
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
|
|
|
|
if shouldTriggerBulk {
|
2023-10-26 17:07:56 +02:00
|
|
|
_, traceSpan := tracing.NewNamedSpan(ctx, "TriggerIDPTemplateProjection")
|
2023-10-19 12:19:10 +02:00
|
|
|
ctx, err = projection.IDPTemplateProjection.Trigger(ctx, handler.WithAwaitRunning())
|
|
|
|
logging.OnError(err).Debug("unable to trigger")
|
2023-10-26 17:07:56 +02:00
|
|
|
traceSpan.EndWithError(err)
|
2023-02-15 09:14:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
eq := sq.Eq{
|
|
|
|
IDPTemplateIDCol.identifier(): id,
|
|
|
|
IDPTemplateInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(),
|
|
|
|
}
|
|
|
|
if !withOwnerRemoved {
|
|
|
|
eq[IDPTemplateOwnerRemovedCol.identifier()] = false
|
|
|
|
}
|
2025-04-02 16:53:06 +02:00
|
|
|
query, scan := prepareIDPTemplateByIDQuery()
|
2023-02-28 21:20:58 +01:00
|
|
|
for _, q := range queries {
|
|
|
|
query = q.toQuery(query)
|
2023-02-15 09:14:59 +01:00
|
|
|
}
|
2023-02-28 21:20:58 +01:00
|
|
|
stmt, args, err := query.Where(eq).ToSql()
|
2023-02-15 09:14:59 +01:00
|
|
|
if err != nil {
|
2023-12-08 16:30:55 +02:00
|
|
|
return nil, zerrors.ThrowInternal(err, "QUERY-SFefg", "Errors.Query.SQLStatement")
|
2023-02-15 09:14:59 +01:00
|
|
|
}
|
|
|
|
|
2023-08-22 12:49:22 +02:00
|
|
|
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
|
|
|
template, err = scan(row)
|
|
|
|
return err
|
|
|
|
}, stmt, args...)
|
|
|
|
return template, err
|
2023-02-15 09:14:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// IDPTemplates searches idp templates matching the query
|
|
|
|
func (q *Queries) IDPTemplates(ctx context.Context, queries *IDPTemplateSearchQueries, withOwnerRemoved bool) (idps *IDPTemplates, err error) {
|
|
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
|
2025-04-02 16:53:06 +02:00
|
|
|
query, scan := prepareIDPTemplatesQuery()
|
2023-02-15 09:14:59 +01:00
|
|
|
eq := sq.Eq{
|
|
|
|
IDPTemplateInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(),
|
|
|
|
}
|
|
|
|
if !withOwnerRemoved {
|
|
|
|
eq[IDPTemplateOwnerRemovedCol.identifier()] = false
|
|
|
|
}
|
|
|
|
stmt, args, err := queries.toQuery(query).Where(eq).ToSql()
|
|
|
|
if err != nil {
|
2023-12-08 16:30:55 +02:00
|
|
|
return nil, zerrors.ThrowInvalidArgument(err, "QUERY-SAF34", "Errors.Query.InvalidRequest")
|
2023-02-15 09:14:59 +01:00
|
|
|
}
|
|
|
|
|
2023-08-22 12:49:22 +02:00
|
|
|
err = q.client.QueryContext(ctx, func(rows *sql.Rows) error {
|
|
|
|
idps, err = scan(rows)
|
|
|
|
return err
|
|
|
|
}, stmt, args...)
|
2023-02-15 09:14:59 +01:00
|
|
|
if err != nil {
|
2023-12-08 16:30:55 +02:00
|
|
|
return nil, zerrors.ThrowInternal(err, "QUERY-BDFrq", "Errors.Internal")
|
2023-02-15 09:14:59 +01:00
|
|
|
}
|
2023-10-19 12:19:10 +02:00
|
|
|
idps.State, err = q.latestState(ctx, idpTemplateTable)
|
2023-02-15 09:14:59 +01:00
|
|
|
return idps, err
|
|
|
|
}
|
|
|
|
|
|
|
|
type IDPTemplateSearchQueries struct {
|
|
|
|
SearchRequest
|
|
|
|
Queries []SearchQuery
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewIDPTemplateIDSearchQuery(id string) (SearchQuery, error) {
|
|
|
|
return NewTextQuery(IDPTemplateIDCol, id, TextEquals)
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewIDPTemplateOwnerTypeSearchQuery(ownerType domain.IdentityProviderType) (SearchQuery, error) {
|
|
|
|
return NewNumberQuery(IDPTemplateOwnerTypeCol, ownerType, NumberEquals)
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewIDPTemplateNameSearchQuery(method TextComparison, value string) (SearchQuery, error) {
|
|
|
|
return NewTextQuery(IDPTemplateNameCol, value, method)
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewIDPTemplateResourceOwnerSearchQuery(value string) (SearchQuery, error) {
|
|
|
|
return NewTextQuery(IDPTemplateResourceOwnerCol, value, TextEquals)
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewIDPTemplateResourceOwnerListSearchQuery(ids ...string) (SearchQuery, error) {
|
|
|
|
list := make([]interface{}, len(ids))
|
|
|
|
for i, value := range ids {
|
|
|
|
list[i] = value
|
|
|
|
}
|
|
|
|
return NewListQuery(IDPTemplateResourceOwnerCol, list, ListIn)
|
|
|
|
}
|
|
|
|
|
2024-12-18 17:19:05 +01:00
|
|
|
func NewIDPTemplateIsCreationAllowedSearchQuery(value bool) (SearchQuery, error) {
|
|
|
|
return NewBoolQuery(IDPTemplateIsCreationAllowedCol, value)
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewIDPTemplateIsLinkingAllowedSearchQuery(value bool) (SearchQuery, error) {
|
|
|
|
return NewBoolQuery(IDPTemplateIsLinkingAllowedCol, value)
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewIDPTemplateIsAutoCreationSearchQuery(value bool) (SearchQuery, error) {
|
|
|
|
return NewBoolQuery(IDPTemplateIsAutoCreationCol, value)
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewIDPTemplateAutoLinkingSearchQuery(value int, method NumberComparison) (SearchQuery, error) {
|
|
|
|
return NewNumberQuery(IDPTemplateAutoLinkingCol, value, method)
|
|
|
|
}
|
|
|
|
|
2023-02-15 09:14:59 +01:00
|
|
|
func (q *IDPTemplateSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
|
|
|
query = q.SearchRequest.toQuery(query)
|
|
|
|
for _, q := range q.Queries {
|
|
|
|
query = q.toQuery(query)
|
|
|
|
}
|
|
|
|
return query
|
|
|
|
}
|
|
|
|
|
2025-04-02 16:53:06 +02:00
|
|
|
func prepareIDPTemplateByIDQuery() (sq.SelectBuilder, func(*sql.Row) (*IDPTemplate, error)) {
|
2023-02-15 09:14:59 +01:00
|
|
|
return sq.Select(
|
|
|
|
IDPTemplateIDCol.identifier(),
|
|
|
|
IDPTemplateResourceOwnerCol.identifier(),
|
|
|
|
IDPTemplateCreationDateCol.identifier(),
|
|
|
|
IDPTemplateChangeDateCol.identifier(),
|
|
|
|
IDPTemplateSequenceCol.identifier(),
|
|
|
|
IDPTemplateStateCol.identifier(),
|
|
|
|
IDPTemplateNameCol.identifier(),
|
|
|
|
IDPTemplateTypeCol.identifier(),
|
|
|
|
IDPTemplateOwnerTypeCol.identifier(),
|
|
|
|
IDPTemplateIsCreationAllowedCol.identifier(),
|
|
|
|
IDPTemplateIsLinkingAllowedCol.identifier(),
|
|
|
|
IDPTemplateIsAutoCreationCol.identifier(),
|
|
|
|
IDPTemplateIsAutoUpdateCol.identifier(),
|
2024-04-10 17:46:30 +02:00
|
|
|
IDPTemplateAutoLinkingCol.identifier(),
|
2023-02-24 15:16:06 +01:00
|
|
|
// oauth
|
|
|
|
OAuthIDCol.identifier(),
|
|
|
|
OAuthClientIDCol.identifier(),
|
|
|
|
OAuthClientSecretCol.identifier(),
|
|
|
|
OAuthAuthorizationEndpointCol.identifier(),
|
|
|
|
OAuthTokenEndpointCol.identifier(),
|
|
|
|
OAuthUserEndpointCol.identifier(),
|
|
|
|
OAuthScopesCol.identifier(),
|
2023-03-03 11:38:49 +01:00
|
|
|
OAuthIDAttributeCol.identifier(),
|
2025-02-26 13:20:47 +01:00
|
|
|
OAuthUsePKCECol.identifier(),
|
2023-02-27 16:32:18 +01:00
|
|
|
// oidc
|
|
|
|
OIDCIDCol.identifier(),
|
|
|
|
OIDCIssuerCol.identifier(),
|
|
|
|
OIDCClientIDCol.identifier(),
|
|
|
|
OIDCClientSecretCol.identifier(),
|
|
|
|
OIDCScopesCol.identifier(),
|
2023-03-16 16:47:22 +01:00
|
|
|
OIDCIDTokenMappingCol.identifier(),
|
2025-02-26 13:20:47 +01:00
|
|
|
OIDCUsePKCECol.identifier(),
|
2023-02-27 16:32:18 +01:00
|
|
|
// jwt
|
|
|
|
JWTIDCol.identifier(),
|
|
|
|
JWTIssuerCol.identifier(),
|
|
|
|
JWTEndpointCol.identifier(),
|
|
|
|
JWTKeysEndpointCol.identifier(),
|
|
|
|
JWTHeaderNameCol.identifier(),
|
2023-03-15 07:48:37 +01:00
|
|
|
// azure
|
|
|
|
AzureADIDCol.identifier(),
|
|
|
|
AzureADClientIDCol.identifier(),
|
|
|
|
AzureADClientSecretCol.identifier(),
|
|
|
|
AzureADScopesCol.identifier(),
|
|
|
|
AzureADTenantCol.identifier(),
|
|
|
|
AzureADIsEmailVerified.identifier(),
|
2023-03-08 11:17:28 +01:00
|
|
|
// github
|
|
|
|
GitHubIDCol.identifier(),
|
|
|
|
GitHubClientIDCol.identifier(),
|
|
|
|
GitHubClientSecretCol.identifier(),
|
|
|
|
GitHubScopesCol.identifier(),
|
|
|
|
// github enterprise
|
|
|
|
GitHubEnterpriseIDCol.identifier(),
|
|
|
|
GitHubEnterpriseClientIDCol.identifier(),
|
|
|
|
GitHubEnterpriseClientSecretCol.identifier(),
|
|
|
|
GitHubEnterpriseAuthorizationEndpointCol.identifier(),
|
|
|
|
GitHubEnterpriseTokenEndpointCol.identifier(),
|
|
|
|
GitHubEnterpriseUserEndpointCol.identifier(),
|
|
|
|
GitHubEnterpriseScopesCol.identifier(),
|
2023-03-13 17:34:29 +01:00
|
|
|
// gitlab
|
|
|
|
GitLabIDCol.identifier(),
|
|
|
|
GitLabClientIDCol.identifier(),
|
|
|
|
GitLabClientSecretCol.identifier(),
|
|
|
|
GitLabScopesCol.identifier(),
|
|
|
|
// gitlab self hosted
|
|
|
|
GitLabSelfHostedIDCol.identifier(),
|
|
|
|
GitLabSelfHostedIssuerCol.identifier(),
|
|
|
|
GitLabSelfHostedClientIDCol.identifier(),
|
|
|
|
GitLabSelfHostedClientSecretCol.identifier(),
|
|
|
|
GitLabSelfHostedScopesCol.identifier(),
|
2023-02-24 15:16:06 +01:00
|
|
|
// google
|
2023-02-21 18:18:28 +01:00
|
|
|
GoogleIDCol.identifier(),
|
|
|
|
GoogleClientIDCol.identifier(),
|
|
|
|
GoogleClientSecretCol.identifier(),
|
|
|
|
GoogleScopesCol.identifier(),
|
2023-09-29 11:26:14 +02:00
|
|
|
// saml
|
|
|
|
SAMLIDCol.identifier(),
|
|
|
|
SAMLMetadataCol.identifier(),
|
|
|
|
SAMLKeyCol.identifier(),
|
|
|
|
SAMLCertificateCol.identifier(),
|
|
|
|
SAMLBindingCol.identifier(),
|
|
|
|
SAMLWithSignedRequestCol.identifier(),
|
2024-05-23 07:04:07 +02:00
|
|
|
SAMLNameIDFormatCol.identifier(),
|
|
|
|
SAMLTransientMappingAttributeNameCol.identifier(),
|
feat: federated logout for SAML IdPs (#9931)
# Which Problems Are Solved
Currently if a user signs in using an IdP, once they sign out of
Zitadel, the corresponding IdP session is not terminated. This can be
the desired behavior. In some cases, e.g. when using a shared computer
it results in a potential security risk, since a follower user might be
able to sign in as the previous using the still open IdP session.
# How the Problems Are Solved
- Admins can enabled a federated logout option on SAML IdPs through the
Admin and Management APIs.
- During the termination of a login V1 session using OIDC end_session
endpoint, Zitadel will check if an IdP was used to authenticate that
session.
- In case there was a SAML IdP used with Federated Logout enabled, it
will intercept the logout process, store the information into the shared
cache and redirect to the federated logout endpoint in the V1 login.
- The V1 login federated logout endpoint checks every request on an
existing cache entry. On success it will create a SAML logout request
for the used IdP and either redirect or POST to the configured SLO
endpoint. The cache entry is updated with a `redirected` state.
- A SLO endpoint is added to the `/idp` handlers, which will handle the
SAML logout responses. At the moment it will check again for an existing
federated logout entry (with state `redirected`) in the cache. On
success, the user is redirected to the initially provided
`post_logout_redirect_uri` from the end_session request.
# Additional Changes
None
# Additional Context
- This PR merges the https://github.com/zitadel/zitadel/pull/9841 and
https://github.com/zitadel/zitadel/pull/9854 to main, additionally
updating the docs on Entra ID SAML.
- closes #9228
- backport to 3.x
---------
Co-authored-by: Silvan <27845747+adlerhurst@users.noreply.github.com>
Co-authored-by: Zach Hirschtritt <zachary.hirschtritt@klaviyo.com>
2025-05-23 13:52:25 +02:00
|
|
|
SAMLFederatedLogoutEnabledCol.identifier(),
|
2023-02-24 15:16:06 +01:00
|
|
|
// ldap
|
2023-02-15 09:14:59 +01:00
|
|
|
LDAPIDCol.identifier(),
|
2023-03-24 16:18:56 +01:00
|
|
|
LDAPServersCol.identifier(),
|
|
|
|
LDAPStartTLSCol.identifier(),
|
2023-02-15 09:14:59 +01:00
|
|
|
LDAPBaseDNCol.identifier(),
|
2023-03-24 16:18:56 +01:00
|
|
|
LDAPBindDNCol.identifier(),
|
|
|
|
LDAPBindPasswordCol.identifier(),
|
|
|
|
LDAPUserBaseCol.identifier(),
|
|
|
|
LDAPUserObjectClassesCol.identifier(),
|
|
|
|
LDAPUserFiltersCol.identifier(),
|
|
|
|
LDAPTimeoutCol.identifier(),
|
2025-02-18 10:06:50 +00:00
|
|
|
LDAPRootCACol.identifier(),
|
2023-02-15 09:14:59 +01:00
|
|
|
LDAPIDAttributeCol.identifier(),
|
|
|
|
LDAPFirstNameAttributeCol.identifier(),
|
|
|
|
LDAPLastNameAttributeCol.identifier(),
|
|
|
|
LDAPDisplayNameAttributeCol.identifier(),
|
|
|
|
LDAPNickNameAttributeCol.identifier(),
|
|
|
|
LDAPPreferredUsernameAttributeCol.identifier(),
|
|
|
|
LDAPEmailAttributeCol.identifier(),
|
|
|
|
LDAPEmailVerifiedAttributeCol.identifier(),
|
|
|
|
LDAPPhoneAttributeCol.identifier(),
|
|
|
|
LDAPPhoneVerifiedAttributeCol.identifier(),
|
|
|
|
LDAPPreferredLanguageAttributeCol.identifier(),
|
|
|
|
LDAPAvatarURLAttributeCol.identifier(),
|
|
|
|
LDAPProfileAttributeCol.identifier(),
|
2023-08-31 08:39:16 +02:00
|
|
|
// apple
|
|
|
|
AppleIDCol.identifier(),
|
|
|
|
AppleClientIDCol.identifier(),
|
|
|
|
AppleTeamIDCol.identifier(),
|
|
|
|
AppleKeyIDCol.identifier(),
|
|
|
|
ApplePrivateKeyCol.identifier(),
|
|
|
|
AppleScopesCol.identifier(),
|
2023-02-15 09:14:59 +01:00
|
|
|
).From(idpTemplateTable.identifier()).
|
2023-02-24 15:16:06 +01:00
|
|
|
LeftJoin(join(OAuthIDCol, IDPTemplateIDCol)).
|
2023-02-27 16:32:18 +01:00
|
|
|
LeftJoin(join(OIDCIDCol, IDPTemplateIDCol)).
|
|
|
|
LeftJoin(join(JWTIDCol, IDPTemplateIDCol)).
|
2023-03-15 07:48:37 +01:00
|
|
|
LeftJoin(join(AzureADIDCol, IDPTemplateIDCol)).
|
2023-03-08 11:17:28 +01:00
|
|
|
LeftJoin(join(GitHubIDCol, IDPTemplateIDCol)).
|
|
|
|
LeftJoin(join(GitHubEnterpriseIDCol, IDPTemplateIDCol)).
|
2023-03-13 17:34:29 +01:00
|
|
|
LeftJoin(join(GitLabIDCol, IDPTemplateIDCol)).
|
|
|
|
LeftJoin(join(GitLabSelfHostedIDCol, IDPTemplateIDCol)).
|
2023-02-21 18:18:28 +01:00
|
|
|
LeftJoin(join(GoogleIDCol, IDPTemplateIDCol)).
|
2023-09-29 11:26:14 +02:00
|
|
|
LeftJoin(join(SAMLIDCol, IDPTemplateIDCol)).
|
2023-08-31 08:39:16 +02:00
|
|
|
LeftJoin(join(LDAPIDCol, IDPTemplateIDCol)).
|
2025-04-02 16:53:06 +02:00
|
|
|
LeftJoin(join(AppleIDCol, IDPTemplateIDCol)).
|
2023-02-15 09:14:59 +01:00
|
|
|
PlaceholderFormat(sq.Dollar),
|
|
|
|
func(row *sql.Row) (*IDPTemplate, error) {
|
|
|
|
idpTemplate := new(IDPTemplate)
|
|
|
|
|
2023-02-21 18:18:28 +01:00
|
|
|
name := sql.NullString{}
|
|
|
|
|
2023-02-24 15:16:06 +01:00
|
|
|
oauthID := sql.NullString{}
|
|
|
|
oauthClientID := sql.NullString{}
|
|
|
|
oauthClientSecret := new(crypto.CryptoValue)
|
|
|
|
oauthAuthorizationEndpoint := sql.NullString{}
|
|
|
|
oauthTokenEndpoint := sql.NullString{}
|
|
|
|
oauthUserEndpoint := sql.NullString{}
|
2023-10-19 12:19:10 +02:00
|
|
|
oauthScopes := database.TextArray[string]{}
|
2023-03-03 11:38:49 +01:00
|
|
|
oauthIDAttribute := sql.NullString{}
|
2025-02-26 13:20:47 +01:00
|
|
|
oauthUserPKCE := sql.NullBool{}
|
2023-02-24 15:16:06 +01:00
|
|
|
|
2023-02-27 16:32:18 +01:00
|
|
|
oidcID := sql.NullString{}
|
|
|
|
oidcIssuer := sql.NullString{}
|
|
|
|
oidcClientID := sql.NullString{}
|
|
|
|
oidcClientSecret := new(crypto.CryptoValue)
|
2023-10-19 12:19:10 +02:00
|
|
|
oidcScopes := database.TextArray[string]{}
|
2023-03-16 16:47:22 +01:00
|
|
|
oidcIDTokenMapping := sql.NullBool{}
|
2025-02-26 13:20:47 +01:00
|
|
|
oidcUserPKCE := sql.NullBool{}
|
2023-02-27 16:32:18 +01:00
|
|
|
|
|
|
|
jwtID := sql.NullString{}
|
|
|
|
jwtIssuer := sql.NullString{}
|
|
|
|
jwtEndpoint := sql.NullString{}
|
|
|
|
jwtKeysEndpoint := sql.NullString{}
|
|
|
|
jwtHeaderName := sql.NullString{}
|
|
|
|
|
2023-03-15 07:48:37 +01:00
|
|
|
azureadID := sql.NullString{}
|
|
|
|
azureadClientID := sql.NullString{}
|
|
|
|
azureadClientSecret := new(crypto.CryptoValue)
|
2023-10-19 12:19:10 +02:00
|
|
|
azureadScopes := database.TextArray[string]{}
|
2023-03-15 07:48:37 +01:00
|
|
|
azureadTenant := sql.NullString{}
|
|
|
|
azureadIsEmailVerified := sql.NullBool{}
|
|
|
|
|
2023-03-08 11:17:28 +01:00
|
|
|
githubID := sql.NullString{}
|
|
|
|
githubClientID := sql.NullString{}
|
|
|
|
githubClientSecret := new(crypto.CryptoValue)
|
2023-10-19 12:19:10 +02:00
|
|
|
githubScopes := database.TextArray[string]{}
|
2023-03-08 11:17:28 +01:00
|
|
|
|
|
|
|
githubEnterpriseID := sql.NullString{}
|
|
|
|
githubEnterpriseClientID := sql.NullString{}
|
|
|
|
githubEnterpriseClientSecret := new(crypto.CryptoValue)
|
|
|
|
githubEnterpriseAuthorizationEndpoint := sql.NullString{}
|
|
|
|
githubEnterpriseTokenEndpoint := sql.NullString{}
|
|
|
|
githubEnterpriseUserEndpoint := sql.NullString{}
|
2023-10-19 12:19:10 +02:00
|
|
|
githubEnterpriseScopes := database.TextArray[string]{}
|
2023-03-08 11:17:28 +01:00
|
|
|
|
2023-03-13 17:34:29 +01:00
|
|
|
gitlabID := sql.NullString{}
|
|
|
|
gitlabClientID := sql.NullString{}
|
|
|
|
gitlabClientSecret := new(crypto.CryptoValue)
|
2023-10-19 12:19:10 +02:00
|
|
|
gitlabScopes := database.TextArray[string]{}
|
2023-03-13 17:34:29 +01:00
|
|
|
|
|
|
|
gitlabSelfHostedID := sql.NullString{}
|
|
|
|
gitlabSelfHostedIssuer := sql.NullString{}
|
|
|
|
gitlabSelfHostedClientID := sql.NullString{}
|
|
|
|
gitlabSelfHostedClientSecret := new(crypto.CryptoValue)
|
2023-10-19 12:19:10 +02:00
|
|
|
gitlabSelfHostedScopes := database.TextArray[string]{}
|
2023-03-13 17:34:29 +01:00
|
|
|
|
2023-02-21 18:18:28 +01:00
|
|
|
googleID := sql.NullString{}
|
|
|
|
googleClientID := sql.NullString{}
|
|
|
|
googleClientSecret := new(crypto.CryptoValue)
|
2023-10-19 12:19:10 +02:00
|
|
|
googleScopes := database.TextArray[string]{}
|
2023-02-21 18:18:28 +01:00
|
|
|
|
2023-09-29 11:26:14 +02:00
|
|
|
samlID := sql.NullString{}
|
|
|
|
var samlMetadata []byte
|
|
|
|
samlKey := new(crypto.CryptoValue)
|
|
|
|
var samlCertificate []byte
|
|
|
|
samlBinding := sql.NullString{}
|
|
|
|
samlWithSignedRequest := sql.NullBool{}
|
2024-05-23 07:04:07 +02:00
|
|
|
samlNameIDFormat := sql.Null[domain.SAMLNameIDFormat]{}
|
|
|
|
samlTransientMappingAttributeName := sql.NullString{}
|
feat: federated logout for SAML IdPs (#9931)
# Which Problems Are Solved
Currently if a user signs in using an IdP, once they sign out of
Zitadel, the corresponding IdP session is not terminated. This can be
the desired behavior. In some cases, e.g. when using a shared computer
it results in a potential security risk, since a follower user might be
able to sign in as the previous using the still open IdP session.
# How the Problems Are Solved
- Admins can enabled a federated logout option on SAML IdPs through the
Admin and Management APIs.
- During the termination of a login V1 session using OIDC end_session
endpoint, Zitadel will check if an IdP was used to authenticate that
session.
- In case there was a SAML IdP used with Federated Logout enabled, it
will intercept the logout process, store the information into the shared
cache and redirect to the federated logout endpoint in the V1 login.
- The V1 login federated logout endpoint checks every request on an
existing cache entry. On success it will create a SAML logout request
for the used IdP and either redirect or POST to the configured SLO
endpoint. The cache entry is updated with a `redirected` state.
- A SLO endpoint is added to the `/idp` handlers, which will handle the
SAML logout responses. At the moment it will check again for an existing
federated logout entry (with state `redirected`) in the cache. On
success, the user is redirected to the initially provided
`post_logout_redirect_uri` from the end_session request.
# Additional Changes
None
# Additional Context
- This PR merges the https://github.com/zitadel/zitadel/pull/9841 and
https://github.com/zitadel/zitadel/pull/9854 to main, additionally
updating the docs on Entra ID SAML.
- closes #9228
- backport to 3.x
---------
Co-authored-by: Silvan <27845747+adlerhurst@users.noreply.github.com>
Co-authored-by: Zach Hirschtritt <zachary.hirschtritt@klaviyo.com>
2025-05-23 13:52:25 +02:00
|
|
|
samlFederatedLogoutEnabled := sql.NullBool{}
|
2023-09-29 11:26:14 +02:00
|
|
|
|
2023-02-15 09:14:59 +01:00
|
|
|
ldapID := sql.NullString{}
|
2023-10-19 12:19:10 +02:00
|
|
|
ldapServers := database.TextArray[string]{}
|
2023-03-24 16:18:56 +01:00
|
|
|
ldapStartTls := sql.NullBool{}
|
2023-02-15 09:14:59 +01:00
|
|
|
ldapBaseDN := sql.NullString{}
|
2023-03-24 16:18:56 +01:00
|
|
|
ldapBindDN := sql.NullString{}
|
|
|
|
ldapBindPassword := new(crypto.CryptoValue)
|
|
|
|
ldapUserBase := sql.NullString{}
|
2023-10-19 12:19:10 +02:00
|
|
|
ldapUserObjectClasses := database.TextArray[string]{}
|
|
|
|
ldapUserFilters := database.TextArray[string]{}
|
2023-03-24 16:18:56 +01:00
|
|
|
ldapTimeout := sql.NullInt64{}
|
2025-02-18 10:06:50 +00:00
|
|
|
var ldapRootCA []byte
|
2023-02-15 09:14:59 +01:00
|
|
|
ldapIDAttribute := sql.NullString{}
|
|
|
|
ldapFirstNameAttribute := sql.NullString{}
|
|
|
|
ldapLastNameAttribute := sql.NullString{}
|
|
|
|
ldapDisplayNameAttribute := sql.NullString{}
|
|
|
|
ldapNickNameAttribute := sql.NullString{}
|
|
|
|
ldapPreferredUsernameAttribute := sql.NullString{}
|
|
|
|
ldapEmailAttribute := sql.NullString{}
|
|
|
|
ldapEmailVerifiedAttribute := sql.NullString{}
|
|
|
|
ldapPhoneAttribute := sql.NullString{}
|
|
|
|
ldapPhoneVerifiedAttribute := sql.NullString{}
|
|
|
|
ldapPreferredLanguageAttribute := sql.NullString{}
|
|
|
|
ldapAvatarURLAttribute := sql.NullString{}
|
|
|
|
ldapProfileAttribute := sql.NullString{}
|
|
|
|
|
2023-08-31 08:39:16 +02:00
|
|
|
appleID := sql.NullString{}
|
|
|
|
appleClientID := sql.NullString{}
|
|
|
|
appleTeamID := sql.NullString{}
|
|
|
|
appleKeyID := sql.NullString{}
|
|
|
|
applePrivateKey := new(crypto.CryptoValue)
|
2023-10-19 12:19:10 +02:00
|
|
|
appleScopes := database.TextArray[string]{}
|
2023-08-31 08:39:16 +02:00
|
|
|
|
2023-02-15 09:14:59 +01:00
|
|
|
err := row.Scan(
|
|
|
|
&idpTemplate.ID,
|
|
|
|
&idpTemplate.ResourceOwner,
|
|
|
|
&idpTemplate.CreationDate,
|
|
|
|
&idpTemplate.ChangeDate,
|
|
|
|
&idpTemplate.Sequence,
|
|
|
|
&idpTemplate.State,
|
2023-02-21 18:18:28 +01:00
|
|
|
&name,
|
2023-02-15 09:14:59 +01:00
|
|
|
&idpTemplate.Type,
|
|
|
|
&idpTemplate.OwnerType,
|
|
|
|
&idpTemplate.IsCreationAllowed,
|
|
|
|
&idpTemplate.IsLinkingAllowed,
|
|
|
|
&idpTemplate.IsAutoCreation,
|
|
|
|
&idpTemplate.IsAutoUpdate,
|
2024-04-10 17:46:30 +02:00
|
|
|
&idpTemplate.AutoLinking,
|
2023-02-24 15:16:06 +01:00
|
|
|
// oauth
|
|
|
|
&oauthID,
|
|
|
|
&oauthClientID,
|
|
|
|
&oauthClientSecret,
|
|
|
|
&oauthAuthorizationEndpoint,
|
|
|
|
&oauthTokenEndpoint,
|
|
|
|
&oauthUserEndpoint,
|
|
|
|
&oauthScopes,
|
2023-03-03 11:38:49 +01:00
|
|
|
&oauthIDAttribute,
|
2025-02-26 13:20:47 +01:00
|
|
|
&oauthUserPKCE,
|
2023-02-27 16:32:18 +01:00
|
|
|
// oidc
|
|
|
|
&oidcID,
|
|
|
|
&oidcIssuer,
|
|
|
|
&oidcClientID,
|
|
|
|
&oidcClientSecret,
|
|
|
|
&oidcScopes,
|
2023-03-16 16:47:22 +01:00
|
|
|
&oidcIDTokenMapping,
|
2025-02-26 13:20:47 +01:00
|
|
|
&oidcUserPKCE,
|
2023-02-27 16:32:18 +01:00
|
|
|
// jwt
|
|
|
|
&jwtID,
|
|
|
|
&jwtIssuer,
|
|
|
|
&jwtEndpoint,
|
|
|
|
&jwtKeysEndpoint,
|
|
|
|
&jwtHeaderName,
|
2023-03-15 07:48:37 +01:00
|
|
|
// azure
|
|
|
|
&azureadID,
|
|
|
|
&azureadClientID,
|
|
|
|
&azureadClientSecret,
|
|
|
|
&azureadScopes,
|
|
|
|
&azureadTenant,
|
|
|
|
&azureadIsEmailVerified,
|
2023-03-08 11:17:28 +01:00
|
|
|
// github
|
|
|
|
&githubID,
|
|
|
|
&githubClientID,
|
|
|
|
&githubClientSecret,
|
|
|
|
&githubScopes,
|
|
|
|
// github enterprise
|
|
|
|
&githubEnterpriseID,
|
|
|
|
&githubEnterpriseClientID,
|
|
|
|
&githubEnterpriseClientSecret,
|
|
|
|
&githubEnterpriseAuthorizationEndpoint,
|
|
|
|
&githubEnterpriseTokenEndpoint,
|
|
|
|
&githubEnterpriseUserEndpoint,
|
|
|
|
&githubEnterpriseScopes,
|
2023-03-13 17:34:29 +01:00
|
|
|
// gitlab
|
|
|
|
&gitlabID,
|
|
|
|
&gitlabClientID,
|
|
|
|
&gitlabClientSecret,
|
|
|
|
&gitlabScopes,
|
|
|
|
// gitlab self hosted
|
|
|
|
&gitlabSelfHostedID,
|
|
|
|
&gitlabSelfHostedIssuer,
|
|
|
|
&gitlabSelfHostedClientID,
|
|
|
|
&gitlabSelfHostedClientSecret,
|
|
|
|
&gitlabSelfHostedScopes,
|
2023-02-24 15:16:06 +01:00
|
|
|
// google
|
2023-02-21 18:18:28 +01:00
|
|
|
&googleID,
|
|
|
|
&googleClientID,
|
|
|
|
&googleClientSecret,
|
|
|
|
&googleScopes,
|
2023-09-29 11:26:14 +02:00
|
|
|
// saml
|
|
|
|
&samlID,
|
|
|
|
&samlMetadata,
|
|
|
|
&samlKey,
|
|
|
|
&samlCertificate,
|
|
|
|
&samlBinding,
|
|
|
|
&samlWithSignedRequest,
|
2024-05-23 07:04:07 +02:00
|
|
|
&samlNameIDFormat,
|
|
|
|
&samlTransientMappingAttributeName,
|
feat: federated logout for SAML IdPs (#9931)
# Which Problems Are Solved
Currently if a user signs in using an IdP, once they sign out of
Zitadel, the corresponding IdP session is not terminated. This can be
the desired behavior. In some cases, e.g. when using a shared computer
it results in a potential security risk, since a follower user might be
able to sign in as the previous using the still open IdP session.
# How the Problems Are Solved
- Admins can enabled a federated logout option on SAML IdPs through the
Admin and Management APIs.
- During the termination of a login V1 session using OIDC end_session
endpoint, Zitadel will check if an IdP was used to authenticate that
session.
- In case there was a SAML IdP used with Federated Logout enabled, it
will intercept the logout process, store the information into the shared
cache and redirect to the federated logout endpoint in the V1 login.
- The V1 login federated logout endpoint checks every request on an
existing cache entry. On success it will create a SAML logout request
for the used IdP and either redirect or POST to the configured SLO
endpoint. The cache entry is updated with a `redirected` state.
- A SLO endpoint is added to the `/idp` handlers, which will handle the
SAML logout responses. At the moment it will check again for an existing
federated logout entry (with state `redirected`) in the cache. On
success, the user is redirected to the initially provided
`post_logout_redirect_uri` from the end_session request.
# Additional Changes
None
# Additional Context
- This PR merges the https://github.com/zitadel/zitadel/pull/9841 and
https://github.com/zitadel/zitadel/pull/9854 to main, additionally
updating the docs on Entra ID SAML.
- closes #9228
- backport to 3.x
---------
Co-authored-by: Silvan <27845747+adlerhurst@users.noreply.github.com>
Co-authored-by: Zach Hirschtritt <zachary.hirschtritt@klaviyo.com>
2025-05-23 13:52:25 +02:00
|
|
|
&samlFederatedLogoutEnabled,
|
2023-02-24 15:16:06 +01:00
|
|
|
// ldap
|
2023-02-15 09:14:59 +01:00
|
|
|
&ldapID,
|
2023-03-24 16:18:56 +01:00
|
|
|
&ldapServers,
|
|
|
|
&ldapStartTls,
|
2023-02-15 09:14:59 +01:00
|
|
|
&ldapBaseDN,
|
2023-03-24 16:18:56 +01:00
|
|
|
&ldapBindDN,
|
|
|
|
&ldapBindPassword,
|
|
|
|
&ldapUserBase,
|
|
|
|
&ldapUserObjectClasses,
|
|
|
|
&ldapUserFilters,
|
|
|
|
&ldapTimeout,
|
2025-02-18 10:06:50 +00:00
|
|
|
&ldapRootCA,
|
2023-02-15 09:14:59 +01:00
|
|
|
&ldapIDAttribute,
|
|
|
|
&ldapFirstNameAttribute,
|
|
|
|
&ldapLastNameAttribute,
|
|
|
|
&ldapDisplayNameAttribute,
|
|
|
|
&ldapNickNameAttribute,
|
|
|
|
&ldapPreferredUsernameAttribute,
|
|
|
|
&ldapEmailAttribute,
|
|
|
|
&ldapEmailVerifiedAttribute,
|
|
|
|
&ldapPhoneAttribute,
|
|
|
|
&ldapPhoneVerifiedAttribute,
|
|
|
|
&ldapPreferredLanguageAttribute,
|
|
|
|
&ldapAvatarURLAttribute,
|
|
|
|
&ldapProfileAttribute,
|
2023-08-31 08:39:16 +02:00
|
|
|
// apple
|
|
|
|
&appleID,
|
|
|
|
&appleClientID,
|
|
|
|
&appleTeamID,
|
|
|
|
&appleKeyID,
|
|
|
|
&applePrivateKey,
|
|
|
|
&appleScopes,
|
2023-02-15 09:14:59 +01:00
|
|
|
)
|
|
|
|
if err != nil {
|
2023-12-08 16:30:55 +02:00
|
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
|
|
return nil, zerrors.ThrowNotFound(err, "QUERY-SAFrt", "Errors.IDPConfig.NotExisting")
|
2023-02-15 09:14:59 +01:00
|
|
|
}
|
2023-12-08 16:30:55 +02:00
|
|
|
return nil, zerrors.ThrowInternal(err, "QUERY-ADG42", "Errors.Internal")
|
2023-02-15 09:14:59 +01:00
|
|
|
}
|
|
|
|
|
2023-02-21 18:18:28 +01:00
|
|
|
idpTemplate.Name = name.String
|
|
|
|
|
2023-02-24 15:16:06 +01:00
|
|
|
if oauthID.Valid {
|
|
|
|
idpTemplate.OAuthIDPTemplate = &OAuthIDPTemplate{
|
|
|
|
IDPID: oauthID.String,
|
|
|
|
ClientID: oauthClientID.String,
|
|
|
|
ClientSecret: oauthClientSecret,
|
|
|
|
AuthorizationEndpoint: oauthAuthorizationEndpoint.String,
|
|
|
|
TokenEndpoint: oauthTokenEndpoint.String,
|
|
|
|
UserEndpoint: oauthUserEndpoint.String,
|
|
|
|
Scopes: oauthScopes,
|
2023-03-03 11:38:49 +01:00
|
|
|
IDAttribute: oauthIDAttribute.String,
|
2025-02-26 13:20:47 +01:00
|
|
|
UsePKCE: oauthUserPKCE.Bool,
|
2023-02-24 15:16:06 +01:00
|
|
|
}
|
|
|
|
}
|
2023-02-27 16:32:18 +01:00
|
|
|
if oidcID.Valid {
|
|
|
|
idpTemplate.OIDCIDPTemplate = &OIDCIDPTemplate{
|
2023-03-16 16:47:22 +01:00
|
|
|
IDPID: oidcID.String,
|
|
|
|
ClientID: oidcClientID.String,
|
|
|
|
ClientSecret: oidcClientSecret,
|
|
|
|
Issuer: oidcIssuer.String,
|
|
|
|
Scopes: oidcScopes,
|
|
|
|
IsIDTokenMapping: oidcIDTokenMapping.Bool,
|
2025-02-26 13:20:47 +01:00
|
|
|
UsePKCE: oidcUserPKCE.Bool,
|
2023-02-27 16:32:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if jwtID.Valid {
|
|
|
|
idpTemplate.JWTIDPTemplate = &JWTIDPTemplate{
|
|
|
|
IDPID: jwtID.String,
|
|
|
|
Issuer: jwtIssuer.String,
|
|
|
|
KeysEndpoint: jwtKeysEndpoint.String,
|
|
|
|
HeaderName: jwtHeaderName.String,
|
|
|
|
Endpoint: jwtEndpoint.String,
|
|
|
|
}
|
|
|
|
}
|
2023-03-15 07:48:37 +01:00
|
|
|
if azureadID.Valid {
|
|
|
|
idpTemplate.AzureADIDPTemplate = &AzureADIDPTemplate{
|
|
|
|
IDPID: azureadID.String,
|
|
|
|
ClientID: azureadClientID.String,
|
|
|
|
ClientSecret: azureadClientSecret,
|
|
|
|
Scopes: azureadScopes,
|
|
|
|
Tenant: azureadTenant.String,
|
|
|
|
IsEmailVerified: azureadIsEmailVerified.Bool,
|
|
|
|
}
|
|
|
|
}
|
2023-03-08 11:17:28 +01:00
|
|
|
if githubID.Valid {
|
|
|
|
idpTemplate.GitHubIDPTemplate = &GitHubIDPTemplate{
|
|
|
|
IDPID: githubID.String,
|
|
|
|
ClientID: githubClientID.String,
|
|
|
|
ClientSecret: githubClientSecret,
|
|
|
|
Scopes: githubScopes,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if githubEnterpriseID.Valid {
|
|
|
|
idpTemplate.GitHubEnterpriseIDPTemplate = &GitHubEnterpriseIDPTemplate{
|
|
|
|
IDPID: githubEnterpriseID.String,
|
|
|
|
ClientID: githubEnterpriseClientID.String,
|
|
|
|
ClientSecret: githubEnterpriseClientSecret,
|
|
|
|
AuthorizationEndpoint: githubEnterpriseAuthorizationEndpoint.String,
|
|
|
|
TokenEndpoint: githubEnterpriseTokenEndpoint.String,
|
|
|
|
UserEndpoint: githubEnterpriseUserEndpoint.String,
|
|
|
|
Scopes: githubEnterpriseScopes,
|
|
|
|
}
|
|
|
|
}
|
2023-03-13 17:34:29 +01:00
|
|
|
if gitlabID.Valid {
|
|
|
|
idpTemplate.GitLabIDPTemplate = &GitLabIDPTemplate{
|
|
|
|
IDPID: gitlabID.String,
|
|
|
|
ClientID: gitlabClientID.String,
|
|
|
|
ClientSecret: gitlabClientSecret,
|
|
|
|
Scopes: gitlabScopes,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if gitlabSelfHostedID.Valid {
|
|
|
|
idpTemplate.GitLabSelfHostedIDPTemplate = &GitLabSelfHostedIDPTemplate{
|
|
|
|
IDPID: gitlabSelfHostedID.String,
|
|
|
|
Issuer: gitlabSelfHostedIssuer.String,
|
|
|
|
ClientID: gitlabSelfHostedClientID.String,
|
|
|
|
ClientSecret: gitlabSelfHostedClientSecret,
|
|
|
|
Scopes: gitlabSelfHostedScopes,
|
|
|
|
}
|
|
|
|
}
|
2023-02-21 18:18:28 +01:00
|
|
|
if googleID.Valid {
|
|
|
|
idpTemplate.GoogleIDPTemplate = &GoogleIDPTemplate{
|
|
|
|
IDPID: googleID.String,
|
|
|
|
ClientID: googleClientID.String,
|
|
|
|
ClientSecret: googleClientSecret,
|
|
|
|
Scopes: googleScopes,
|
|
|
|
}
|
2023-02-24 15:16:06 +01:00
|
|
|
}
|
2023-09-29 11:26:14 +02:00
|
|
|
if samlID.Valid {
|
|
|
|
idpTemplate.SAMLIDPTemplate = &SAMLIDPTemplate{
|
2024-05-23 07:04:07 +02:00
|
|
|
IDPID: samlID.String,
|
|
|
|
Metadata: samlMetadata,
|
|
|
|
Key: samlKey,
|
|
|
|
Certificate: samlCertificate,
|
|
|
|
Binding: samlBinding.String,
|
|
|
|
WithSignedRequest: samlWithSignedRequest.Bool,
|
|
|
|
NameIDFormat: samlNameIDFormat,
|
|
|
|
TransientMappingAttributeName: samlTransientMappingAttributeName.String,
|
feat: federated logout for SAML IdPs (#9931)
# Which Problems Are Solved
Currently if a user signs in using an IdP, once they sign out of
Zitadel, the corresponding IdP session is not terminated. This can be
the desired behavior. In some cases, e.g. when using a shared computer
it results in a potential security risk, since a follower user might be
able to sign in as the previous using the still open IdP session.
# How the Problems Are Solved
- Admins can enabled a federated logout option on SAML IdPs through the
Admin and Management APIs.
- During the termination of a login V1 session using OIDC end_session
endpoint, Zitadel will check if an IdP was used to authenticate that
session.
- In case there was a SAML IdP used with Federated Logout enabled, it
will intercept the logout process, store the information into the shared
cache and redirect to the federated logout endpoint in the V1 login.
- The V1 login federated logout endpoint checks every request on an
existing cache entry. On success it will create a SAML logout request
for the used IdP and either redirect or POST to the configured SLO
endpoint. The cache entry is updated with a `redirected` state.
- A SLO endpoint is added to the `/idp` handlers, which will handle the
SAML logout responses. At the moment it will check again for an existing
federated logout entry (with state `redirected`) in the cache. On
success, the user is redirected to the initially provided
`post_logout_redirect_uri` from the end_session request.
# Additional Changes
None
# Additional Context
- This PR merges the https://github.com/zitadel/zitadel/pull/9841 and
https://github.com/zitadel/zitadel/pull/9854 to main, additionally
updating the docs on Entra ID SAML.
- closes #9228
- backport to 3.x
---------
Co-authored-by: Silvan <27845747+adlerhurst@users.noreply.github.com>
Co-authored-by: Zach Hirschtritt <zachary.hirschtritt@klaviyo.com>
2025-05-23 13:52:25 +02:00
|
|
|
FederatedLogoutEnabled: samlFederatedLogoutEnabled.Bool,
|
2023-09-29 11:26:14 +02:00
|
|
|
}
|
|
|
|
}
|
2023-02-24 15:16:06 +01:00
|
|
|
if ldapID.Valid {
|
2023-02-15 09:14:59 +01:00
|
|
|
idpTemplate.LDAPIDPTemplate = &LDAPIDPTemplate{
|
2023-03-24 16:18:56 +01:00
|
|
|
IDPID: ldapID.String,
|
|
|
|
Servers: ldapServers,
|
|
|
|
StartTLS: ldapStartTls.Bool,
|
|
|
|
BaseDN: ldapBaseDN.String,
|
|
|
|
BindDN: ldapBindDN.String,
|
|
|
|
BindPassword: ldapBindPassword,
|
|
|
|
UserBase: ldapUserBase.String,
|
|
|
|
UserObjectClasses: ldapUserObjectClasses,
|
|
|
|
UserFilters: ldapUserFilters,
|
|
|
|
Timeout: time.Duration(ldapTimeout.Int64),
|
2025-02-18 10:06:50 +00:00
|
|
|
RootCA: ldapRootCA,
|
2023-02-15 09:14:59 +01:00
|
|
|
LDAPAttributes: idp.LDAPAttributes{
|
|
|
|
IDAttribute: ldapIDAttribute.String,
|
|
|
|
FirstNameAttribute: ldapFirstNameAttribute.String,
|
|
|
|
LastNameAttribute: ldapLastNameAttribute.String,
|
|
|
|
DisplayNameAttribute: ldapDisplayNameAttribute.String,
|
|
|
|
NickNameAttribute: ldapNickNameAttribute.String,
|
|
|
|
PreferredUsernameAttribute: ldapPreferredUsernameAttribute.String,
|
|
|
|
EmailAttribute: ldapEmailAttribute.String,
|
|
|
|
EmailVerifiedAttribute: ldapEmailVerifiedAttribute.String,
|
|
|
|
PhoneAttribute: ldapPhoneAttribute.String,
|
|
|
|
PhoneVerifiedAttribute: ldapPhoneVerifiedAttribute.String,
|
|
|
|
PreferredLanguageAttribute: ldapPreferredLanguageAttribute.String,
|
|
|
|
AvatarURLAttribute: ldapAvatarURLAttribute.String,
|
|
|
|
ProfileAttribute: ldapProfileAttribute.String,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2023-08-31 08:39:16 +02:00
|
|
|
if appleID.Valid {
|
|
|
|
idpTemplate.AppleIDPTemplate = &AppleIDPTemplate{
|
|
|
|
IDPID: appleID.String,
|
|
|
|
ClientID: appleClientID.String,
|
|
|
|
TeamID: appleTeamID.String,
|
|
|
|
KeyID: appleKeyID.String,
|
|
|
|
PrivateKey: applePrivateKey,
|
|
|
|
Scopes: appleScopes,
|
|
|
|
}
|
|
|
|
}
|
2023-02-15 09:14:59 +01:00
|
|
|
|
|
|
|
return idpTemplate, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-02 16:53:06 +02:00
|
|
|
//nolint:gocognit
|
|
|
|
func prepareIDPTemplatesQuery() (sq.SelectBuilder, func(*sql.Rows) (*IDPTemplates, error)) {
|
2023-02-15 09:14:59 +01:00
|
|
|
return sq.Select(
|
|
|
|
IDPTemplateIDCol.identifier(),
|
|
|
|
IDPTemplateResourceOwnerCol.identifier(),
|
|
|
|
IDPTemplateCreationDateCol.identifier(),
|
|
|
|
IDPTemplateChangeDateCol.identifier(),
|
|
|
|
IDPTemplateSequenceCol.identifier(),
|
|
|
|
IDPTemplateStateCol.identifier(),
|
|
|
|
IDPTemplateNameCol.identifier(),
|
|
|
|
IDPTemplateTypeCol.identifier(),
|
|
|
|
IDPTemplateOwnerTypeCol.identifier(),
|
|
|
|
IDPTemplateIsCreationAllowedCol.identifier(),
|
|
|
|
IDPTemplateIsLinkingAllowedCol.identifier(),
|
|
|
|
IDPTemplateIsAutoCreationCol.identifier(),
|
|
|
|
IDPTemplateIsAutoUpdateCol.identifier(),
|
2024-04-10 17:46:30 +02:00
|
|
|
IDPTemplateAutoLinkingCol.identifier(),
|
2023-02-24 15:16:06 +01:00
|
|
|
// oauth
|
|
|
|
OAuthIDCol.identifier(),
|
|
|
|
OAuthClientIDCol.identifier(),
|
|
|
|
OAuthClientSecretCol.identifier(),
|
|
|
|
OAuthAuthorizationEndpointCol.identifier(),
|
|
|
|
OAuthTokenEndpointCol.identifier(),
|
|
|
|
OAuthUserEndpointCol.identifier(),
|
|
|
|
OAuthScopesCol.identifier(),
|
2023-03-03 11:38:49 +01:00
|
|
|
OAuthIDAttributeCol.identifier(),
|
2025-02-26 13:20:47 +01:00
|
|
|
OAuthUsePKCECol.identifier(),
|
2023-02-27 16:32:18 +01:00
|
|
|
// oidc
|
|
|
|
OIDCIDCol.identifier(),
|
|
|
|
OIDCIssuerCol.identifier(),
|
|
|
|
OIDCClientIDCol.identifier(),
|
|
|
|
OIDCClientSecretCol.identifier(),
|
|
|
|
OIDCScopesCol.identifier(),
|
2023-03-16 16:47:22 +01:00
|
|
|
OIDCIDTokenMappingCol.identifier(),
|
2025-02-26 13:20:47 +01:00
|
|
|
OIDCUsePKCECol.identifier(),
|
2023-02-27 16:32:18 +01:00
|
|
|
// jwt
|
|
|
|
JWTIDCol.identifier(),
|
|
|
|
JWTIssuerCol.identifier(),
|
|
|
|
JWTEndpointCol.identifier(),
|
|
|
|
JWTKeysEndpointCol.identifier(),
|
|
|
|
JWTHeaderNameCol.identifier(),
|
2023-03-15 07:48:37 +01:00
|
|
|
// azure
|
|
|
|
AzureADIDCol.identifier(),
|
|
|
|
AzureADClientIDCol.identifier(),
|
|
|
|
AzureADClientSecretCol.identifier(),
|
|
|
|
AzureADScopesCol.identifier(),
|
|
|
|
AzureADTenantCol.identifier(),
|
|
|
|
AzureADIsEmailVerified.identifier(),
|
2023-03-08 11:17:28 +01:00
|
|
|
// github
|
|
|
|
GitHubIDCol.identifier(),
|
|
|
|
GitHubClientIDCol.identifier(),
|
|
|
|
GitHubClientSecretCol.identifier(),
|
|
|
|
GitHubScopesCol.identifier(),
|
|
|
|
// github enterprise
|
|
|
|
GitHubEnterpriseIDCol.identifier(),
|
|
|
|
GitHubEnterpriseClientIDCol.identifier(),
|
|
|
|
GitHubEnterpriseClientSecretCol.identifier(),
|
|
|
|
GitHubEnterpriseAuthorizationEndpointCol.identifier(),
|
|
|
|
GitHubEnterpriseTokenEndpointCol.identifier(),
|
|
|
|
GitHubEnterpriseUserEndpointCol.identifier(),
|
|
|
|
GitHubEnterpriseScopesCol.identifier(),
|
2023-03-13 17:34:29 +01:00
|
|
|
// gitlab
|
|
|
|
GitLabIDCol.identifier(),
|
|
|
|
GitLabClientIDCol.identifier(),
|
|
|
|
GitLabClientSecretCol.identifier(),
|
|
|
|
GitLabScopesCol.identifier(),
|
|
|
|
// gitlab self hosted
|
|
|
|
GitLabSelfHostedIDCol.identifier(),
|
|
|
|
GitLabSelfHostedIssuerCol.identifier(),
|
|
|
|
GitLabSelfHostedClientIDCol.identifier(),
|
|
|
|
GitLabSelfHostedClientSecretCol.identifier(),
|
|
|
|
GitLabSelfHostedScopesCol.identifier(),
|
2023-02-24 15:16:06 +01:00
|
|
|
// google
|
2023-02-21 18:18:28 +01:00
|
|
|
GoogleIDCol.identifier(),
|
|
|
|
GoogleClientIDCol.identifier(),
|
|
|
|
GoogleClientSecretCol.identifier(),
|
|
|
|
GoogleScopesCol.identifier(),
|
2023-09-29 11:26:14 +02:00
|
|
|
// saml
|
|
|
|
SAMLIDCol.identifier(),
|
|
|
|
SAMLMetadataCol.identifier(),
|
|
|
|
SAMLKeyCol.identifier(),
|
|
|
|
SAMLCertificateCol.identifier(),
|
|
|
|
SAMLBindingCol.identifier(),
|
|
|
|
SAMLWithSignedRequestCol.identifier(),
|
2024-05-23 07:04:07 +02:00
|
|
|
SAMLNameIDFormatCol.identifier(),
|
|
|
|
SAMLTransientMappingAttributeNameCol.identifier(),
|
feat: federated logout for SAML IdPs (#9931)
# Which Problems Are Solved
Currently if a user signs in using an IdP, once they sign out of
Zitadel, the corresponding IdP session is not terminated. This can be
the desired behavior. In some cases, e.g. when using a shared computer
it results in a potential security risk, since a follower user might be
able to sign in as the previous using the still open IdP session.
# How the Problems Are Solved
- Admins can enabled a federated logout option on SAML IdPs through the
Admin and Management APIs.
- During the termination of a login V1 session using OIDC end_session
endpoint, Zitadel will check if an IdP was used to authenticate that
session.
- In case there was a SAML IdP used with Federated Logout enabled, it
will intercept the logout process, store the information into the shared
cache and redirect to the federated logout endpoint in the V1 login.
- The V1 login federated logout endpoint checks every request on an
existing cache entry. On success it will create a SAML logout request
for the used IdP and either redirect or POST to the configured SLO
endpoint. The cache entry is updated with a `redirected` state.
- A SLO endpoint is added to the `/idp` handlers, which will handle the
SAML logout responses. At the moment it will check again for an existing
federated logout entry (with state `redirected`) in the cache. On
success, the user is redirected to the initially provided
`post_logout_redirect_uri` from the end_session request.
# Additional Changes
None
# Additional Context
- This PR merges the https://github.com/zitadel/zitadel/pull/9841 and
https://github.com/zitadel/zitadel/pull/9854 to main, additionally
updating the docs on Entra ID SAML.
- closes #9228
- backport to 3.x
---------
Co-authored-by: Silvan <27845747+adlerhurst@users.noreply.github.com>
Co-authored-by: Zach Hirschtritt <zachary.hirschtritt@klaviyo.com>
2025-05-23 13:52:25 +02:00
|
|
|
SAMLFederatedLogoutEnabledCol.identifier(),
|
2023-02-24 15:16:06 +01:00
|
|
|
// ldap
|
2023-02-15 09:14:59 +01:00
|
|
|
LDAPIDCol.identifier(),
|
2023-03-24 16:18:56 +01:00
|
|
|
LDAPServersCol.identifier(),
|
|
|
|
LDAPStartTLSCol.identifier(),
|
2023-02-15 09:14:59 +01:00
|
|
|
LDAPBaseDNCol.identifier(),
|
2023-03-24 16:18:56 +01:00
|
|
|
LDAPBindDNCol.identifier(),
|
|
|
|
LDAPBindPasswordCol.identifier(),
|
|
|
|
LDAPUserBaseCol.identifier(),
|
|
|
|
LDAPUserObjectClassesCol.identifier(),
|
|
|
|
LDAPUserFiltersCol.identifier(),
|
|
|
|
LDAPTimeoutCol.identifier(),
|
2025-02-18 10:06:50 +00:00
|
|
|
LDAPRootCACol.identifier(),
|
2023-02-15 09:14:59 +01:00
|
|
|
LDAPIDAttributeCol.identifier(),
|
|
|
|
LDAPFirstNameAttributeCol.identifier(),
|
|
|
|
LDAPLastNameAttributeCol.identifier(),
|
|
|
|
LDAPDisplayNameAttributeCol.identifier(),
|
|
|
|
LDAPNickNameAttributeCol.identifier(),
|
|
|
|
LDAPPreferredUsernameAttributeCol.identifier(),
|
|
|
|
LDAPEmailAttributeCol.identifier(),
|
|
|
|
LDAPEmailVerifiedAttributeCol.identifier(),
|
|
|
|
LDAPPhoneAttributeCol.identifier(),
|
|
|
|
LDAPPhoneVerifiedAttributeCol.identifier(),
|
|
|
|
LDAPPreferredLanguageAttributeCol.identifier(),
|
|
|
|
LDAPAvatarURLAttributeCol.identifier(),
|
|
|
|
LDAPProfileAttributeCol.identifier(),
|
2023-08-31 08:39:16 +02:00
|
|
|
// apple
|
|
|
|
AppleIDCol.identifier(),
|
|
|
|
AppleClientIDCol.identifier(),
|
|
|
|
AppleTeamIDCol.identifier(),
|
|
|
|
AppleKeyIDCol.identifier(),
|
|
|
|
ApplePrivateKeyCol.identifier(),
|
|
|
|
AppleScopesCol.identifier(),
|
|
|
|
// count
|
2023-02-15 09:14:59 +01:00
|
|
|
countColumn.identifier(),
|
|
|
|
).From(idpTemplateTable.identifier()).
|
2023-02-24 15:16:06 +01:00
|
|
|
LeftJoin(join(OAuthIDCol, IDPTemplateIDCol)).
|
2023-02-27 16:32:18 +01:00
|
|
|
LeftJoin(join(OIDCIDCol, IDPTemplateIDCol)).
|
|
|
|
LeftJoin(join(JWTIDCol, IDPTemplateIDCol)).
|
2023-03-15 07:48:37 +01:00
|
|
|
LeftJoin(join(AzureADIDCol, IDPTemplateIDCol)).
|
2023-03-08 11:17:28 +01:00
|
|
|
LeftJoin(join(GitHubIDCol, IDPTemplateIDCol)).
|
|
|
|
LeftJoin(join(GitHubEnterpriseIDCol, IDPTemplateIDCol)).
|
2023-03-13 17:34:29 +01:00
|
|
|
LeftJoin(join(GitLabIDCol, IDPTemplateIDCol)).
|
|
|
|
LeftJoin(join(GitLabSelfHostedIDCol, IDPTemplateIDCol)).
|
2023-02-21 18:18:28 +01:00
|
|
|
LeftJoin(join(GoogleIDCol, IDPTemplateIDCol)).
|
2023-09-29 11:26:14 +02:00
|
|
|
LeftJoin(join(SAMLIDCol, IDPTemplateIDCol)).
|
2023-08-31 08:39:16 +02:00
|
|
|
LeftJoin(join(LDAPIDCol, IDPTemplateIDCol)).
|
2025-04-02 16:53:06 +02:00
|
|
|
LeftJoin(join(AppleIDCol, IDPTemplateIDCol)).
|
2023-02-15 09:14:59 +01:00
|
|
|
PlaceholderFormat(sq.Dollar),
|
|
|
|
func(rows *sql.Rows) (*IDPTemplates, error) {
|
|
|
|
templates := make([]*IDPTemplate, 0)
|
|
|
|
var count uint64
|
|
|
|
for rows.Next() {
|
|
|
|
idpTemplate := new(IDPTemplate)
|
|
|
|
|
2023-02-21 18:18:28 +01:00
|
|
|
name := sql.NullString{}
|
|
|
|
|
2023-02-24 15:16:06 +01:00
|
|
|
oauthID := sql.NullString{}
|
|
|
|
oauthClientID := sql.NullString{}
|
|
|
|
oauthClientSecret := new(crypto.CryptoValue)
|
|
|
|
oauthAuthorizationEndpoint := sql.NullString{}
|
|
|
|
oauthTokenEndpoint := sql.NullString{}
|
|
|
|
oauthUserEndpoint := sql.NullString{}
|
2023-10-19 12:19:10 +02:00
|
|
|
oauthScopes := database.TextArray[string]{}
|
2023-03-03 11:38:49 +01:00
|
|
|
oauthIDAttribute := sql.NullString{}
|
2025-02-26 13:20:47 +01:00
|
|
|
oauthUserPKCE := sql.NullBool{}
|
2023-02-24 15:16:06 +01:00
|
|
|
|
2023-02-27 16:32:18 +01:00
|
|
|
oidcID := sql.NullString{}
|
|
|
|
oidcIssuer := sql.NullString{}
|
|
|
|
oidcClientID := sql.NullString{}
|
|
|
|
oidcClientSecret := new(crypto.CryptoValue)
|
2023-10-19 12:19:10 +02:00
|
|
|
oidcScopes := database.TextArray[string]{}
|
2023-03-16 16:47:22 +01:00
|
|
|
oidcIDTokenMapping := sql.NullBool{}
|
2025-02-26 13:20:47 +01:00
|
|
|
oidcUserPKCE := sql.NullBool{}
|
2023-02-27 16:32:18 +01:00
|
|
|
|
|
|
|
jwtID := sql.NullString{}
|
|
|
|
jwtIssuer := sql.NullString{}
|
|
|
|
jwtEndpoint := sql.NullString{}
|
|
|
|
jwtKeysEndpoint := sql.NullString{}
|
|
|
|
jwtHeaderName := sql.NullString{}
|
|
|
|
|
2023-03-15 07:48:37 +01:00
|
|
|
azureadID := sql.NullString{}
|
|
|
|
azureadClientID := sql.NullString{}
|
|
|
|
azureadClientSecret := new(crypto.CryptoValue)
|
2023-10-19 12:19:10 +02:00
|
|
|
azureadScopes := database.TextArray[string]{}
|
2023-03-15 07:48:37 +01:00
|
|
|
azureadTenant := sql.NullString{}
|
|
|
|
azureadIsEmailVerified := sql.NullBool{}
|
|
|
|
|
2023-03-08 11:17:28 +01:00
|
|
|
githubID := sql.NullString{}
|
|
|
|
githubClientID := sql.NullString{}
|
|
|
|
githubClientSecret := new(crypto.CryptoValue)
|
2023-10-19 12:19:10 +02:00
|
|
|
githubScopes := database.TextArray[string]{}
|
2023-03-08 11:17:28 +01:00
|
|
|
|
|
|
|
githubEnterpriseID := sql.NullString{}
|
|
|
|
githubEnterpriseClientID := sql.NullString{}
|
|
|
|
githubEnterpriseClientSecret := new(crypto.CryptoValue)
|
|
|
|
githubEnterpriseAuthorizationEndpoint := sql.NullString{}
|
|
|
|
githubEnterpriseTokenEndpoint := sql.NullString{}
|
|
|
|
githubEnterpriseUserEndpoint := sql.NullString{}
|
2023-10-19 12:19:10 +02:00
|
|
|
githubEnterpriseScopes := database.TextArray[string]{}
|
2023-03-08 11:17:28 +01:00
|
|
|
|
2023-03-13 17:34:29 +01:00
|
|
|
gitlabID := sql.NullString{}
|
|
|
|
gitlabClientID := sql.NullString{}
|
|
|
|
gitlabClientSecret := new(crypto.CryptoValue)
|
2023-10-19 12:19:10 +02:00
|
|
|
gitlabScopes := database.TextArray[string]{}
|
2023-03-13 17:34:29 +01:00
|
|
|
|
|
|
|
gitlabSelfHostedID := sql.NullString{}
|
|
|
|
gitlabSelfHostedIssuer := sql.NullString{}
|
|
|
|
gitlabSelfHostedClientID := sql.NullString{}
|
|
|
|
gitlabSelfHostedClientSecret := new(crypto.CryptoValue)
|
2023-10-19 12:19:10 +02:00
|
|
|
gitlabSelfHostedScopes := database.TextArray[string]{}
|
2023-03-13 17:34:29 +01:00
|
|
|
|
2023-02-21 18:18:28 +01:00
|
|
|
googleID := sql.NullString{}
|
|
|
|
googleClientID := sql.NullString{}
|
|
|
|
googleClientSecret := new(crypto.CryptoValue)
|
2023-10-19 12:19:10 +02:00
|
|
|
googleScopes := database.TextArray[string]{}
|
2023-02-21 18:18:28 +01:00
|
|
|
|
2023-09-29 11:26:14 +02:00
|
|
|
samlID := sql.NullString{}
|
|
|
|
var samlMetadata []byte
|
|
|
|
samlKey := new(crypto.CryptoValue)
|
|
|
|
var samlCertificate []byte
|
|
|
|
samlBinding := sql.NullString{}
|
|
|
|
samlWithSignedRequest := sql.NullBool{}
|
2024-05-23 07:04:07 +02:00
|
|
|
samlNameIDFormat := sql.Null[domain.SAMLNameIDFormat]{}
|
|
|
|
samlTransientMappingAttributeName := sql.NullString{}
|
feat: federated logout for SAML IdPs (#9931)
# Which Problems Are Solved
Currently if a user signs in using an IdP, once they sign out of
Zitadel, the corresponding IdP session is not terminated. This can be
the desired behavior. In some cases, e.g. when using a shared computer
it results in a potential security risk, since a follower user might be
able to sign in as the previous using the still open IdP session.
# How the Problems Are Solved
- Admins can enabled a federated logout option on SAML IdPs through the
Admin and Management APIs.
- During the termination of a login V1 session using OIDC end_session
endpoint, Zitadel will check if an IdP was used to authenticate that
session.
- In case there was a SAML IdP used with Federated Logout enabled, it
will intercept the logout process, store the information into the shared
cache and redirect to the federated logout endpoint in the V1 login.
- The V1 login federated logout endpoint checks every request on an
existing cache entry. On success it will create a SAML logout request
for the used IdP and either redirect or POST to the configured SLO
endpoint. The cache entry is updated with a `redirected` state.
- A SLO endpoint is added to the `/idp` handlers, which will handle the
SAML logout responses. At the moment it will check again for an existing
federated logout entry (with state `redirected`) in the cache. On
success, the user is redirected to the initially provided
`post_logout_redirect_uri` from the end_session request.
# Additional Changes
None
# Additional Context
- This PR merges the https://github.com/zitadel/zitadel/pull/9841 and
https://github.com/zitadel/zitadel/pull/9854 to main, additionally
updating the docs on Entra ID SAML.
- closes #9228
- backport to 3.x
---------
Co-authored-by: Silvan <27845747+adlerhurst@users.noreply.github.com>
Co-authored-by: Zach Hirschtritt <zachary.hirschtritt@klaviyo.com>
2025-05-23 13:52:25 +02:00
|
|
|
samlFederatedLogoutEnabled := sql.NullBool{}
|
2023-09-29 11:26:14 +02:00
|
|
|
|
2023-02-15 09:14:59 +01:00
|
|
|
ldapID := sql.NullString{}
|
2023-10-19 12:19:10 +02:00
|
|
|
ldapServers := database.TextArray[string]{}
|
2023-03-24 16:18:56 +01:00
|
|
|
ldapStartTls := sql.NullBool{}
|
2023-02-15 09:14:59 +01:00
|
|
|
ldapBaseDN := sql.NullString{}
|
2023-03-24 16:18:56 +01:00
|
|
|
ldapBindDN := sql.NullString{}
|
|
|
|
ldapBindPassword := new(crypto.CryptoValue)
|
|
|
|
ldapUserBase := sql.NullString{}
|
2023-10-19 12:19:10 +02:00
|
|
|
ldapUserObjectClasses := database.TextArray[string]{}
|
|
|
|
ldapUserFilters := database.TextArray[string]{}
|
2023-03-24 16:18:56 +01:00
|
|
|
ldapTimeout := sql.NullInt64{}
|
2025-02-18 10:06:50 +00:00
|
|
|
var ldapRootCA []byte
|
2023-02-15 09:14:59 +01:00
|
|
|
ldapIDAttribute := sql.NullString{}
|
|
|
|
ldapFirstNameAttribute := sql.NullString{}
|
|
|
|
ldapLastNameAttribute := sql.NullString{}
|
|
|
|
ldapDisplayNameAttribute := sql.NullString{}
|
|
|
|
ldapNickNameAttribute := sql.NullString{}
|
|
|
|
ldapPreferredUsernameAttribute := sql.NullString{}
|
|
|
|
ldapEmailAttribute := sql.NullString{}
|
|
|
|
ldapEmailVerifiedAttribute := sql.NullString{}
|
|
|
|
ldapPhoneAttribute := sql.NullString{}
|
|
|
|
ldapPhoneVerifiedAttribute := sql.NullString{}
|
|
|
|
ldapPreferredLanguageAttribute := sql.NullString{}
|
|
|
|
ldapAvatarURLAttribute := sql.NullString{}
|
|
|
|
ldapProfileAttribute := sql.NullString{}
|
|
|
|
|
2023-08-31 08:39:16 +02:00
|
|
|
appleID := sql.NullString{}
|
|
|
|
appleClientID := sql.NullString{}
|
|
|
|
appleTeamID := sql.NullString{}
|
|
|
|
appleKeyID := sql.NullString{}
|
|
|
|
applePrivateKey := new(crypto.CryptoValue)
|
2023-10-19 12:19:10 +02:00
|
|
|
appleScopes := database.TextArray[string]{}
|
2023-08-31 08:39:16 +02:00
|
|
|
|
2023-02-15 09:14:59 +01:00
|
|
|
err := rows.Scan(
|
|
|
|
&idpTemplate.ID,
|
|
|
|
&idpTemplate.ResourceOwner,
|
|
|
|
&idpTemplate.CreationDate,
|
|
|
|
&idpTemplate.ChangeDate,
|
|
|
|
&idpTemplate.Sequence,
|
|
|
|
&idpTemplate.State,
|
2023-02-21 18:18:28 +01:00
|
|
|
&name,
|
2023-02-15 09:14:59 +01:00
|
|
|
&idpTemplate.Type,
|
|
|
|
&idpTemplate.OwnerType,
|
|
|
|
&idpTemplate.IsCreationAllowed,
|
|
|
|
&idpTemplate.IsLinkingAllowed,
|
|
|
|
&idpTemplate.IsAutoCreation,
|
|
|
|
&idpTemplate.IsAutoUpdate,
|
2024-04-10 17:46:30 +02:00
|
|
|
&idpTemplate.AutoLinking,
|
2023-02-24 15:16:06 +01:00
|
|
|
// oauth
|
|
|
|
&oauthID,
|
|
|
|
&oauthClientID,
|
|
|
|
&oauthClientSecret,
|
|
|
|
&oauthAuthorizationEndpoint,
|
|
|
|
&oauthTokenEndpoint,
|
|
|
|
&oauthUserEndpoint,
|
|
|
|
&oauthScopes,
|
2023-03-03 11:38:49 +01:00
|
|
|
&oauthIDAttribute,
|
2025-02-26 13:20:47 +01:00
|
|
|
&oauthUserPKCE,
|
2023-02-27 16:32:18 +01:00
|
|
|
// oidc
|
|
|
|
&oidcID,
|
|
|
|
&oidcIssuer,
|
|
|
|
&oidcClientID,
|
|
|
|
&oidcClientSecret,
|
|
|
|
&oidcScopes,
|
2023-03-16 16:47:22 +01:00
|
|
|
&oidcIDTokenMapping,
|
2025-02-26 13:20:47 +01:00
|
|
|
&oidcUserPKCE,
|
2023-02-27 16:32:18 +01:00
|
|
|
// jwt
|
|
|
|
&jwtID,
|
|
|
|
&jwtIssuer,
|
|
|
|
&jwtEndpoint,
|
|
|
|
&jwtKeysEndpoint,
|
|
|
|
&jwtHeaderName,
|
2023-03-15 07:48:37 +01:00
|
|
|
// azure
|
|
|
|
&azureadID,
|
|
|
|
&azureadClientID,
|
|
|
|
&azureadClientSecret,
|
|
|
|
&azureadScopes,
|
|
|
|
&azureadTenant,
|
|
|
|
&azureadIsEmailVerified,
|
2023-03-08 11:17:28 +01:00
|
|
|
// github
|
|
|
|
&githubID,
|
|
|
|
&githubClientID,
|
|
|
|
&githubClientSecret,
|
|
|
|
&githubScopes,
|
|
|
|
// github enterprise
|
|
|
|
&githubEnterpriseID,
|
|
|
|
&githubEnterpriseClientID,
|
|
|
|
&githubEnterpriseClientSecret,
|
|
|
|
&githubEnterpriseAuthorizationEndpoint,
|
|
|
|
&githubEnterpriseTokenEndpoint,
|
|
|
|
&githubEnterpriseUserEndpoint,
|
|
|
|
&githubEnterpriseScopes,
|
2023-03-13 17:34:29 +01:00
|
|
|
// gitlab
|
|
|
|
&gitlabID,
|
|
|
|
&gitlabClientID,
|
|
|
|
&gitlabClientSecret,
|
|
|
|
&gitlabScopes,
|
|
|
|
// gitlab self hosted
|
|
|
|
&gitlabSelfHostedID,
|
|
|
|
&gitlabSelfHostedIssuer,
|
|
|
|
&gitlabSelfHostedClientID,
|
|
|
|
&gitlabSelfHostedClientSecret,
|
|
|
|
&gitlabSelfHostedScopes,
|
2023-02-24 15:16:06 +01:00
|
|
|
// google
|
2023-02-21 18:18:28 +01:00
|
|
|
&googleID,
|
|
|
|
&googleClientID,
|
|
|
|
&googleClientSecret,
|
|
|
|
&googleScopes,
|
2023-09-29 11:26:14 +02:00
|
|
|
// saml
|
|
|
|
&samlID,
|
|
|
|
&samlMetadata,
|
|
|
|
&samlKey,
|
|
|
|
&samlCertificate,
|
|
|
|
&samlBinding,
|
|
|
|
&samlWithSignedRequest,
|
2024-05-23 07:04:07 +02:00
|
|
|
&samlNameIDFormat,
|
|
|
|
&samlTransientMappingAttributeName,
|
feat: federated logout for SAML IdPs (#9931)
# Which Problems Are Solved
Currently if a user signs in using an IdP, once they sign out of
Zitadel, the corresponding IdP session is not terminated. This can be
the desired behavior. In some cases, e.g. when using a shared computer
it results in a potential security risk, since a follower user might be
able to sign in as the previous using the still open IdP session.
# How the Problems Are Solved
- Admins can enabled a federated logout option on SAML IdPs through the
Admin and Management APIs.
- During the termination of a login V1 session using OIDC end_session
endpoint, Zitadel will check if an IdP was used to authenticate that
session.
- In case there was a SAML IdP used with Federated Logout enabled, it
will intercept the logout process, store the information into the shared
cache and redirect to the federated logout endpoint in the V1 login.
- The V1 login federated logout endpoint checks every request on an
existing cache entry. On success it will create a SAML logout request
for the used IdP and either redirect or POST to the configured SLO
endpoint. The cache entry is updated with a `redirected` state.
- A SLO endpoint is added to the `/idp` handlers, which will handle the
SAML logout responses. At the moment it will check again for an existing
federated logout entry (with state `redirected`) in the cache. On
success, the user is redirected to the initially provided
`post_logout_redirect_uri` from the end_session request.
# Additional Changes
None
# Additional Context
- This PR merges the https://github.com/zitadel/zitadel/pull/9841 and
https://github.com/zitadel/zitadel/pull/9854 to main, additionally
updating the docs on Entra ID SAML.
- closes #9228
- backport to 3.x
---------
Co-authored-by: Silvan <27845747+adlerhurst@users.noreply.github.com>
Co-authored-by: Zach Hirschtritt <zachary.hirschtritt@klaviyo.com>
2025-05-23 13:52:25 +02:00
|
|
|
&samlFederatedLogoutEnabled,
|
2023-02-24 15:16:06 +01:00
|
|
|
// ldap
|
2023-02-15 09:14:59 +01:00
|
|
|
&ldapID,
|
2023-03-24 16:18:56 +01:00
|
|
|
&ldapServers,
|
|
|
|
&ldapStartTls,
|
2023-02-15 09:14:59 +01:00
|
|
|
&ldapBaseDN,
|
2023-03-24 16:18:56 +01:00
|
|
|
&ldapBindDN,
|
|
|
|
&ldapBindPassword,
|
|
|
|
&ldapUserBase,
|
|
|
|
&ldapUserObjectClasses,
|
|
|
|
&ldapUserFilters,
|
|
|
|
&ldapTimeout,
|
2025-02-18 10:06:50 +00:00
|
|
|
&ldapRootCA,
|
2023-02-15 09:14:59 +01:00
|
|
|
&ldapIDAttribute,
|
|
|
|
&ldapFirstNameAttribute,
|
|
|
|
&ldapLastNameAttribute,
|
|
|
|
&ldapDisplayNameAttribute,
|
|
|
|
&ldapNickNameAttribute,
|
|
|
|
&ldapPreferredUsernameAttribute,
|
|
|
|
&ldapEmailAttribute,
|
|
|
|
&ldapEmailVerifiedAttribute,
|
|
|
|
&ldapPhoneAttribute,
|
|
|
|
&ldapPhoneVerifiedAttribute,
|
|
|
|
&ldapPreferredLanguageAttribute,
|
|
|
|
&ldapAvatarURLAttribute,
|
|
|
|
&ldapProfileAttribute,
|
2023-08-31 08:39:16 +02:00
|
|
|
// apple
|
|
|
|
&appleID,
|
|
|
|
&appleClientID,
|
|
|
|
&appleTeamID,
|
|
|
|
&appleKeyID,
|
|
|
|
&applePrivateKey,
|
|
|
|
&appleScopes,
|
2023-02-15 09:14:59 +01:00
|
|
|
&count,
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-02-21 18:18:28 +01:00
|
|
|
idpTemplate.Name = name.String
|
|
|
|
|
2023-02-24 15:16:06 +01:00
|
|
|
if oauthID.Valid {
|
|
|
|
idpTemplate.OAuthIDPTemplate = &OAuthIDPTemplate{
|
|
|
|
IDPID: oauthID.String,
|
|
|
|
ClientID: oauthClientID.String,
|
|
|
|
ClientSecret: oauthClientSecret,
|
|
|
|
AuthorizationEndpoint: oauthAuthorizationEndpoint.String,
|
|
|
|
TokenEndpoint: oauthTokenEndpoint.String,
|
|
|
|
UserEndpoint: oauthUserEndpoint.String,
|
|
|
|
Scopes: oauthScopes,
|
2023-03-03 11:38:49 +01:00
|
|
|
IDAttribute: oauthIDAttribute.String,
|
2025-02-26 13:20:47 +01:00
|
|
|
UsePKCE: oauthUserPKCE.Bool,
|
2023-02-24 15:16:06 +01:00
|
|
|
}
|
|
|
|
}
|
2023-02-27 16:32:18 +01:00
|
|
|
if oidcID.Valid {
|
|
|
|
idpTemplate.OIDCIDPTemplate = &OIDCIDPTemplate{
|
2023-03-16 16:47:22 +01:00
|
|
|
IDPID: oidcID.String,
|
|
|
|
ClientID: oidcClientID.String,
|
|
|
|
ClientSecret: oidcClientSecret,
|
|
|
|
Issuer: oidcIssuer.String,
|
|
|
|
Scopes: oidcScopes,
|
|
|
|
IsIDTokenMapping: oidcIDTokenMapping.Bool,
|
2025-02-26 13:20:47 +01:00
|
|
|
UsePKCE: oidcUserPKCE.Bool,
|
2023-02-27 16:32:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if jwtID.Valid {
|
|
|
|
idpTemplate.JWTIDPTemplate = &JWTIDPTemplate{
|
|
|
|
IDPID: jwtID.String,
|
|
|
|
Issuer: jwtIssuer.String,
|
|
|
|
KeysEndpoint: jwtKeysEndpoint.String,
|
|
|
|
HeaderName: jwtHeaderName.String,
|
|
|
|
Endpoint: jwtEndpoint.String,
|
|
|
|
}
|
|
|
|
}
|
2023-03-15 07:48:37 +01:00
|
|
|
if azureadID.Valid {
|
|
|
|
idpTemplate.AzureADIDPTemplate = &AzureADIDPTemplate{
|
|
|
|
IDPID: azureadID.String,
|
|
|
|
ClientID: azureadClientID.String,
|
|
|
|
ClientSecret: azureadClientSecret,
|
|
|
|
Scopes: azureadScopes,
|
|
|
|
Tenant: azureadTenant.String,
|
|
|
|
IsEmailVerified: azureadIsEmailVerified.Bool,
|
|
|
|
}
|
|
|
|
}
|
2023-03-08 11:17:28 +01:00
|
|
|
if githubID.Valid {
|
|
|
|
idpTemplate.GitHubIDPTemplate = &GitHubIDPTemplate{
|
|
|
|
IDPID: githubID.String,
|
|
|
|
ClientID: githubClientID.String,
|
|
|
|
ClientSecret: githubClientSecret,
|
|
|
|
Scopes: githubScopes,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if githubEnterpriseID.Valid {
|
|
|
|
idpTemplate.GitHubEnterpriseIDPTemplate = &GitHubEnterpriseIDPTemplate{
|
|
|
|
IDPID: githubEnterpriseID.String,
|
|
|
|
ClientID: githubEnterpriseClientID.String,
|
|
|
|
ClientSecret: githubEnterpriseClientSecret,
|
|
|
|
AuthorizationEndpoint: githubEnterpriseAuthorizationEndpoint.String,
|
|
|
|
TokenEndpoint: githubEnterpriseTokenEndpoint.String,
|
|
|
|
UserEndpoint: githubEnterpriseUserEndpoint.String,
|
|
|
|
Scopes: githubEnterpriseScopes,
|
|
|
|
}
|
|
|
|
}
|
2023-03-13 17:34:29 +01:00
|
|
|
if gitlabID.Valid {
|
|
|
|
idpTemplate.GitLabIDPTemplate = &GitLabIDPTemplate{
|
|
|
|
IDPID: gitlabID.String,
|
|
|
|
ClientID: gitlabClientID.String,
|
|
|
|
ClientSecret: gitlabClientSecret,
|
|
|
|
Scopes: gitlabScopes,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if gitlabSelfHostedID.Valid {
|
|
|
|
idpTemplate.GitLabSelfHostedIDPTemplate = &GitLabSelfHostedIDPTemplate{
|
|
|
|
IDPID: gitlabSelfHostedID.String,
|
|
|
|
Issuer: gitlabSelfHostedIssuer.String,
|
|
|
|
ClientID: gitlabSelfHostedClientID.String,
|
|
|
|
ClientSecret: gitlabSelfHostedClientSecret,
|
|
|
|
Scopes: gitlabSelfHostedScopes,
|
|
|
|
}
|
|
|
|
}
|
2023-02-21 18:18:28 +01:00
|
|
|
if googleID.Valid {
|
|
|
|
idpTemplate.GoogleIDPTemplate = &GoogleIDPTemplate{
|
|
|
|
IDPID: googleID.String,
|
|
|
|
ClientID: googleClientID.String,
|
|
|
|
ClientSecret: googleClientSecret,
|
|
|
|
Scopes: googleScopes,
|
|
|
|
}
|
2023-02-24 15:16:06 +01:00
|
|
|
}
|
2023-09-29 11:26:14 +02:00
|
|
|
if samlID.Valid {
|
|
|
|
idpTemplate.SAMLIDPTemplate = &SAMLIDPTemplate{
|
2024-05-23 07:04:07 +02:00
|
|
|
IDPID: samlID.String,
|
|
|
|
Metadata: samlMetadata,
|
|
|
|
Key: samlKey,
|
|
|
|
Certificate: samlCertificate,
|
|
|
|
Binding: samlBinding.String,
|
|
|
|
WithSignedRequest: samlWithSignedRequest.Bool,
|
|
|
|
NameIDFormat: samlNameIDFormat,
|
|
|
|
TransientMappingAttributeName: samlTransientMappingAttributeName.String,
|
feat: federated logout for SAML IdPs (#9931)
# Which Problems Are Solved
Currently if a user signs in using an IdP, once they sign out of
Zitadel, the corresponding IdP session is not terminated. This can be
the desired behavior. In some cases, e.g. when using a shared computer
it results in a potential security risk, since a follower user might be
able to sign in as the previous using the still open IdP session.
# How the Problems Are Solved
- Admins can enabled a federated logout option on SAML IdPs through the
Admin and Management APIs.
- During the termination of a login V1 session using OIDC end_session
endpoint, Zitadel will check if an IdP was used to authenticate that
session.
- In case there was a SAML IdP used with Federated Logout enabled, it
will intercept the logout process, store the information into the shared
cache and redirect to the federated logout endpoint in the V1 login.
- The V1 login federated logout endpoint checks every request on an
existing cache entry. On success it will create a SAML logout request
for the used IdP and either redirect or POST to the configured SLO
endpoint. The cache entry is updated with a `redirected` state.
- A SLO endpoint is added to the `/idp` handlers, which will handle the
SAML logout responses. At the moment it will check again for an existing
federated logout entry (with state `redirected`) in the cache. On
success, the user is redirected to the initially provided
`post_logout_redirect_uri` from the end_session request.
# Additional Changes
None
# Additional Context
- This PR merges the https://github.com/zitadel/zitadel/pull/9841 and
https://github.com/zitadel/zitadel/pull/9854 to main, additionally
updating the docs on Entra ID SAML.
- closes #9228
- backport to 3.x
---------
Co-authored-by: Silvan <27845747+adlerhurst@users.noreply.github.com>
Co-authored-by: Zach Hirschtritt <zachary.hirschtritt@klaviyo.com>
2025-05-23 13:52:25 +02:00
|
|
|
FederatedLogoutEnabled: samlFederatedLogoutEnabled.Bool,
|
2023-09-29 11:26:14 +02:00
|
|
|
}
|
|
|
|
}
|
2023-02-24 15:16:06 +01:00
|
|
|
if ldapID.Valid {
|
2023-02-15 09:14:59 +01:00
|
|
|
idpTemplate.LDAPIDPTemplate = &LDAPIDPTemplate{
|
2023-03-24 16:18:56 +01:00
|
|
|
IDPID: ldapID.String,
|
|
|
|
Servers: ldapServers,
|
|
|
|
StartTLS: ldapStartTls.Bool,
|
|
|
|
BaseDN: ldapBaseDN.String,
|
|
|
|
BindDN: ldapBindDN.String,
|
|
|
|
BindPassword: ldapBindPassword,
|
|
|
|
UserBase: ldapUserBase.String,
|
|
|
|
UserObjectClasses: ldapUserObjectClasses,
|
|
|
|
UserFilters: ldapUserFilters,
|
|
|
|
Timeout: time.Duration(ldapTimeout.Int64),
|
2025-02-18 10:06:50 +00:00
|
|
|
RootCA: ldapRootCA,
|
2023-02-15 09:14:59 +01:00
|
|
|
LDAPAttributes: idp.LDAPAttributes{
|
|
|
|
IDAttribute: ldapIDAttribute.String,
|
|
|
|
FirstNameAttribute: ldapFirstNameAttribute.String,
|
|
|
|
LastNameAttribute: ldapLastNameAttribute.String,
|
|
|
|
DisplayNameAttribute: ldapDisplayNameAttribute.String,
|
|
|
|
NickNameAttribute: ldapNickNameAttribute.String,
|
|
|
|
PreferredUsernameAttribute: ldapPreferredUsernameAttribute.String,
|
|
|
|
EmailAttribute: ldapEmailAttribute.String,
|
|
|
|
EmailVerifiedAttribute: ldapEmailVerifiedAttribute.String,
|
|
|
|
PhoneAttribute: ldapPhoneAttribute.String,
|
|
|
|
PhoneVerifiedAttribute: ldapPhoneVerifiedAttribute.String,
|
|
|
|
PreferredLanguageAttribute: ldapPreferredLanguageAttribute.String,
|
|
|
|
AvatarURLAttribute: ldapAvatarURLAttribute.String,
|
|
|
|
ProfileAttribute: ldapProfileAttribute.String,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2023-08-31 08:39:16 +02:00
|
|
|
if appleID.Valid {
|
|
|
|
idpTemplate.AppleIDPTemplate = &AppleIDPTemplate{
|
|
|
|
IDPID: appleID.String,
|
|
|
|
ClientID: appleClientID.String,
|
|
|
|
TeamID: appleTeamID.String,
|
|
|
|
KeyID: appleKeyID.String,
|
|
|
|
PrivateKey: applePrivateKey,
|
|
|
|
Scopes: appleScopes,
|
|
|
|
}
|
|
|
|
}
|
2023-02-15 09:14:59 +01:00
|
|
|
templates = append(templates, idpTemplate)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := rows.Close(); err != nil {
|
2023-12-08 16:30:55 +02:00
|
|
|
return nil, zerrors.ThrowInternal(err, "QUERY-SAGrt", "Errors.Query.CloseRows")
|
2023-02-15 09:14:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return &IDPTemplates{
|
|
|
|
Templates: templates,
|
|
|
|
SearchResponse: SearchResponse{
|
|
|
|
Count: count,
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
}
|