mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-04 05:12:16 +00:00
feat: native app links for passkeys (#12580)
# Which Problems Are Solved - Native iOS/Android passkeys need OS trust files on the ZITADEL domain; without them, Associated Domains / App Links verification fails. - Operators could not configure iOS Team ID + Bundle ID or Android package name + SHA-256 fingerprints on OIDC apps. - `/.well-known/apple-app-site-association` and `/.well-known/assetlinks.json` were not served from application config. # How the Problems Are Solved - Add iOS/Android app-link fields on OIDC app create/update (Application API v2 + Management), with validation. - Persist and project those fields; query active app-link configs instance-wide. - Serve AASA (`webcredentials`) and Digital Asset Links (`get_login_creds`) from well-known paths, with configurable `Cache-Control` and fingerprint normalization at serve time. - Console UI to edit the fields, with links to the well-known endpoints. - Operator docs for configuration, endpoints, caching, and verification. # Additional Changes - Document on API fields that well-known responses may be HTTP-cached and platform verifiers may delay propagation. - Runtime config: `WellKnown.AppLinksCacheControlMaxAge` (default `5m`; `0` → `no-store`). # Additional Context - Closes #12497 - Implemented and reviewed as stack: - #12531 API contract - #12532 storage wiring - #12536 well-known endpoints - #12537 console - #12547 docs --------- Co-authored-by: Cursor <[email protected]>
This commit is contained in:
+111
-59
@@ -43,27 +43,31 @@ type App struct {
|
||||
}
|
||||
|
||||
type OIDCApp struct {
|
||||
RedirectURIs database.TextArray[string]
|
||||
ResponseTypes database.NumberArray[domain.OIDCResponseType]
|
||||
GrantTypes database.NumberArray[domain.OIDCGrantType]
|
||||
AppType domain.OIDCApplicationType
|
||||
ClientID string
|
||||
AuthMethodType domain.OIDCAuthMethodType
|
||||
PostLogoutRedirectURIs database.TextArray[string]
|
||||
Version domain.OIDCVersion
|
||||
ComplianceProblems database.TextArray[string]
|
||||
IsDevMode bool
|
||||
AccessTokenType domain.OIDCTokenType
|
||||
AssertAccessTokenRole bool
|
||||
AssertIDTokenRole bool
|
||||
AssertIDTokenUserinfo bool
|
||||
ClockSkew time.Duration
|
||||
AdditionalOrigins database.TextArray[string]
|
||||
AllowedOrigins database.TextArray[string]
|
||||
SkipNativeAppSuccessPage bool
|
||||
BackChannelLogoutURI string
|
||||
LoginVersion domain.LoginVersion
|
||||
LoginBaseURI *string
|
||||
RedirectURIs database.TextArray[string]
|
||||
ResponseTypes database.NumberArray[domain.OIDCResponseType]
|
||||
GrantTypes database.NumberArray[domain.OIDCGrantType]
|
||||
AppType domain.OIDCApplicationType
|
||||
ClientID string
|
||||
AuthMethodType domain.OIDCAuthMethodType
|
||||
PostLogoutRedirectURIs database.TextArray[string]
|
||||
Version domain.OIDCVersion
|
||||
ComplianceProblems database.TextArray[string]
|
||||
IsDevMode bool
|
||||
AccessTokenType domain.OIDCTokenType
|
||||
AssertAccessTokenRole bool
|
||||
AssertIDTokenRole bool
|
||||
AssertIDTokenUserinfo bool
|
||||
ClockSkew time.Duration
|
||||
AdditionalOrigins database.TextArray[string]
|
||||
AllowedOrigins database.TextArray[string]
|
||||
SkipNativeAppSuccessPage bool
|
||||
BackChannelLogoutURI string
|
||||
LoginVersion domain.LoginVersion
|
||||
LoginBaseURI *string
|
||||
IOSTeamID string
|
||||
IOSBundleID string
|
||||
AndroidPackageName string
|
||||
AndroidSHA256CertFingerprints database.TextArray[string]
|
||||
}
|
||||
|
||||
type SAMLApp struct {
|
||||
@@ -278,6 +282,22 @@ var (
|
||||
name: projection.AppOIDCConfigColumnLoginBaseURI,
|
||||
table: appOIDCConfigsTable,
|
||||
}
|
||||
AppOIDCConfigColumnIOSTeamID = Column{
|
||||
name: projection.AppOIDCConfigColumnIOSTeamID,
|
||||
table: appOIDCConfigsTable,
|
||||
}
|
||||
AppOIDCConfigColumnIOSBundleID = Column{
|
||||
name: projection.AppOIDCConfigColumnIOSBundleID,
|
||||
table: appOIDCConfigsTable,
|
||||
}
|
||||
AppOIDCConfigColumnAndroidPackageName = Column{
|
||||
name: projection.AppOIDCConfigColumnAndroidPackageName,
|
||||
table: appOIDCConfigsTable,
|
||||
}
|
||||
AppOIDCConfigColumnAndroidSHA256CertFingerprints = Column{
|
||||
name: projection.AppOIDCConfigColumnAndroidSHA256CertFingerprints,
|
||||
table: appOIDCConfigsTable,
|
||||
}
|
||||
)
|
||||
|
||||
func (q *Queries) AppByProjectAndAppID(ctx context.Context, shouldTriggerBulk bool, projectID, appID string) (app *App, err error) {
|
||||
@@ -725,6 +745,10 @@ func prepareAppQuery(activeOnly bool) (sq.SelectBuilder, func(*sql.Row) (*App, e
|
||||
AppOIDCConfigColumnBackChannelLogoutURI.identifier(),
|
||||
AppOIDCConfigColumnLoginVersion.identifier(),
|
||||
AppOIDCConfigColumnLoginBaseURI.identifier(),
|
||||
AppOIDCConfigColumnIOSTeamID.identifier(),
|
||||
AppOIDCConfigColumnIOSBundleID.identifier(),
|
||||
AppOIDCConfigColumnAndroidPackageName.identifier(),
|
||||
AppOIDCConfigColumnAndroidSHA256CertFingerprints.identifier(),
|
||||
|
||||
AppSAMLConfigColumnAppID.identifier(),
|
||||
AppSAMLConfigColumnEntityID.identifier(),
|
||||
@@ -794,6 +818,10 @@ func scanApp(row *sql.Row) (*App, error) {
|
||||
&oidcConfig.backChannelLogoutURI,
|
||||
&oidcConfig.loginVersion,
|
||||
&oidcConfig.loginBaseURI,
|
||||
&oidcConfig.iosTeamID,
|
||||
&oidcConfig.iosBundleID,
|
||||
&oidcConfig.androidPackageName,
|
||||
&oidcConfig.androidSHA256CertFingerprints,
|
||||
|
||||
&samlConfig.appID,
|
||||
&samlConfig.entityID,
|
||||
@@ -848,6 +876,10 @@ func prepareOIDCAppQuery() (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
|
||||
AppOIDCConfigColumnBackChannelLogoutURI.identifier(),
|
||||
AppOIDCConfigColumnLoginVersion.identifier(),
|
||||
AppOIDCConfigColumnLoginBaseURI.identifier(),
|
||||
AppOIDCConfigColumnIOSTeamID.identifier(),
|
||||
AppOIDCConfigColumnIOSBundleID.identifier(),
|
||||
AppOIDCConfigColumnAndroidPackageName.identifier(),
|
||||
AppOIDCConfigColumnAndroidSHA256CertFingerprints.identifier(),
|
||||
).From(appsTable.identifier()).
|
||||
Join(join(AppOIDCConfigColumnAppID, AppColumnID)).
|
||||
PlaceholderFormat(sq.Dollar), func(row *sql.Row) (*App, error) {
|
||||
@@ -887,6 +919,10 @@ func prepareOIDCAppQuery() (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
|
||||
&oidcConfig.backChannelLogoutURI,
|
||||
&oidcConfig.loginVersion,
|
||||
&oidcConfig.loginBaseURI,
|
||||
&oidcConfig.iosTeamID,
|
||||
&oidcConfig.iosBundleID,
|
||||
&oidcConfig.androidPackageName,
|
||||
&oidcConfig.androidSHA256CertFingerprints,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
@@ -1004,6 +1040,10 @@ func prepareAppsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Apps, error)) {
|
||||
AppOIDCConfigColumnBackChannelLogoutURI.identifier(),
|
||||
AppOIDCConfigColumnLoginVersion.identifier(),
|
||||
AppOIDCConfigColumnLoginBaseURI.identifier(),
|
||||
AppOIDCConfigColumnIOSTeamID.identifier(),
|
||||
AppOIDCConfigColumnIOSBundleID.identifier(),
|
||||
AppOIDCConfigColumnAndroidPackageName.identifier(),
|
||||
AppOIDCConfigColumnAndroidSHA256CertFingerprints.identifier(),
|
||||
|
||||
AppSAMLConfigColumnAppID.identifier(),
|
||||
AppSAMLConfigColumnEntityID.identifier(),
|
||||
@@ -1061,6 +1101,10 @@ func prepareAppsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Apps, error)) {
|
||||
&oidcConfig.backChannelLogoutURI,
|
||||
&oidcConfig.loginVersion,
|
||||
&oidcConfig.loginBaseURI,
|
||||
&oidcConfig.iosTeamID,
|
||||
&oidcConfig.iosBundleID,
|
||||
&oidcConfig.androidPackageName,
|
||||
&oidcConfig.androidSHA256CertFingerprints,
|
||||
|
||||
&samlConfig.appID,
|
||||
&samlConfig.entityID,
|
||||
@@ -1148,26 +1192,30 @@ func prepareLoginVersionBySAMLAppID() (sq.SelectBuilder, func(*sql.Row) (domain.
|
||||
}
|
||||
|
||||
type sqlOIDCConfig struct {
|
||||
appID sql.NullString
|
||||
version sql.NullInt32
|
||||
clientID sql.NullString
|
||||
redirectUris database.TextArray[string]
|
||||
applicationType sql.NullInt16
|
||||
authMethodType sql.NullInt16
|
||||
postLogoutRedirectUris database.TextArray[string]
|
||||
devMode sql.NullBool
|
||||
accessTokenType sql.NullInt16
|
||||
accessTokenRoleAssertion sql.NullBool
|
||||
iDTokenRoleAssertion sql.NullBool
|
||||
iDTokenUserinfoAssertion sql.NullBool
|
||||
clockSkew sql.NullInt64
|
||||
additionalOrigins database.TextArray[string]
|
||||
responseTypes database.NumberArray[domain.OIDCResponseType]
|
||||
grantTypes database.NumberArray[domain.OIDCGrantType]
|
||||
skipNativeAppSuccessPage sql.NullBool
|
||||
backChannelLogoutURI sql.NullString
|
||||
loginVersion sql.NullInt16
|
||||
loginBaseURI sql.NullString
|
||||
appID sql.NullString
|
||||
version sql.NullInt32
|
||||
clientID sql.NullString
|
||||
redirectUris database.TextArray[string]
|
||||
applicationType sql.NullInt16
|
||||
authMethodType sql.NullInt16
|
||||
postLogoutRedirectUris database.TextArray[string]
|
||||
devMode sql.NullBool
|
||||
accessTokenType sql.NullInt16
|
||||
accessTokenRoleAssertion sql.NullBool
|
||||
iDTokenRoleAssertion sql.NullBool
|
||||
iDTokenUserinfoAssertion sql.NullBool
|
||||
clockSkew sql.NullInt64
|
||||
additionalOrigins database.TextArray[string]
|
||||
responseTypes database.NumberArray[domain.OIDCResponseType]
|
||||
grantTypes database.NumberArray[domain.OIDCGrantType]
|
||||
skipNativeAppSuccessPage sql.NullBool
|
||||
backChannelLogoutURI sql.NullString
|
||||
loginVersion sql.NullInt16
|
||||
loginBaseURI sql.NullString
|
||||
iosTeamID sql.NullString
|
||||
iosBundleID sql.NullString
|
||||
androidPackageName sql.NullString
|
||||
androidSHA256CertFingerprints database.TextArray[string]
|
||||
}
|
||||
|
||||
func (c sqlOIDCConfig) set(app *App) {
|
||||
@@ -1175,24 +1223,28 @@ func (c sqlOIDCConfig) set(app *App) {
|
||||
return
|
||||
}
|
||||
app.OIDCConfig = &OIDCApp{
|
||||
Version: domain.OIDCVersion(c.version.Int32),
|
||||
ClientID: c.clientID.String,
|
||||
RedirectURIs: c.redirectUris,
|
||||
AppType: domain.OIDCApplicationType(c.applicationType.Int16),
|
||||
AuthMethodType: domain.OIDCAuthMethodType(c.authMethodType.Int16),
|
||||
PostLogoutRedirectURIs: c.postLogoutRedirectUris,
|
||||
IsDevMode: c.devMode.Bool,
|
||||
AccessTokenType: domain.OIDCTokenType(c.accessTokenType.Int16),
|
||||
AssertAccessTokenRole: c.accessTokenRoleAssertion.Bool,
|
||||
AssertIDTokenRole: c.iDTokenRoleAssertion.Bool,
|
||||
AssertIDTokenUserinfo: c.iDTokenUserinfoAssertion.Bool,
|
||||
ClockSkew: time.Duration(c.clockSkew.Int64),
|
||||
AdditionalOrigins: c.additionalOrigins,
|
||||
ResponseTypes: c.responseTypes,
|
||||
GrantTypes: c.grantTypes,
|
||||
SkipNativeAppSuccessPage: c.skipNativeAppSuccessPage.Bool,
|
||||
BackChannelLogoutURI: c.backChannelLogoutURI.String,
|
||||
LoginVersion: domain.LoginVersion(c.loginVersion.Int16),
|
||||
Version: domain.OIDCVersion(c.version.Int32),
|
||||
ClientID: c.clientID.String,
|
||||
RedirectURIs: c.redirectUris,
|
||||
AppType: domain.OIDCApplicationType(c.applicationType.Int16),
|
||||
AuthMethodType: domain.OIDCAuthMethodType(c.authMethodType.Int16),
|
||||
PostLogoutRedirectURIs: c.postLogoutRedirectUris,
|
||||
IsDevMode: c.devMode.Bool,
|
||||
AccessTokenType: domain.OIDCTokenType(c.accessTokenType.Int16),
|
||||
AssertAccessTokenRole: c.accessTokenRoleAssertion.Bool,
|
||||
AssertIDTokenRole: c.iDTokenRoleAssertion.Bool,
|
||||
AssertIDTokenUserinfo: c.iDTokenUserinfoAssertion.Bool,
|
||||
ClockSkew: time.Duration(c.clockSkew.Int64),
|
||||
AdditionalOrigins: c.additionalOrigins,
|
||||
ResponseTypes: c.responseTypes,
|
||||
GrantTypes: c.grantTypes,
|
||||
SkipNativeAppSuccessPage: c.skipNativeAppSuccessPage.Bool,
|
||||
BackChannelLogoutURI: c.backChannelLogoutURI.String,
|
||||
LoginVersion: domain.LoginVersion(c.loginVersion.Int16),
|
||||
IOSTeamID: c.iosTeamID.String,
|
||||
IOSBundleID: c.iosBundleID.String,
|
||||
AndroidPackageName: c.androidPackageName.String,
|
||||
AndroidSHA256CertFingerprints: c.androidSHA256CertFingerprints,
|
||||
}
|
||||
if c.loginBaseURI.Valid {
|
||||
app.OIDCConfig.LoginBaseURI = &c.loginBaseURI.String
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"strings"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
// OIDCAppLinkConfig holds iOS/Android association fields for one active OIDC app.
|
||||
type OIDCAppLinkConfig struct {
|
||||
AppID string
|
||||
IOSTeamID string
|
||||
IOSBundleID string
|
||||
AndroidPackageName string
|
||||
AndroidSHA256CertFingerprints []string
|
||||
}
|
||||
|
||||
// SearchOIDCAppLinkConfigs returns association fields for active OIDC apps in the
|
||||
// current instance that have at least one iOS or Android app-link field set.
|
||||
func (q *Queries) SearchOIDCAppLinkConfigs(ctx context.Context) (configs []*OIDCAppLinkConfig, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
query, scan := prepareOIDCAppLinkConfigsQuery()
|
||||
iosTeamIDCol := AppOIDCConfigColumnIOSTeamID.identifier()
|
||||
iosBundleIDCol := AppOIDCConfigColumnIOSBundleID.identifier()
|
||||
androidPackageCol := AppOIDCConfigColumnAndroidPackageName.identifier()
|
||||
stmt, args, err := query.Where(sq.And{
|
||||
sq.Eq{
|
||||
AppOIDCConfigColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
AppColumnState.identifier(): domain.AppStateActive,
|
||||
},
|
||||
sq.Or{
|
||||
sq.Expr("COALESCE(" + iosTeamIDCol + ", '') <> ''"),
|
||||
sq.Expr("COALESCE(" + iosBundleIDCol + ", '') <> ''"),
|
||||
sq.Expr("COALESCE(" + androidPackageCol + ", '') <> ''"),
|
||||
},
|
||||
}).OrderBy(AppOIDCConfigColumnAppID.identifier()).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInvalidArgument(err, "QUERY-ApLn1", "Errors.Query.InvalidRequest")
|
||||
}
|
||||
|
||||
err = q.client.QueryContext(ctx, func(rows *sql.Rows) error {
|
||||
configs, err = scan(rows)
|
||||
return err
|
||||
}, stmt, args...)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-ApLn2", "Errors.Internal")
|
||||
}
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
func prepareOIDCAppLinkConfigsQuery() (sq.SelectBuilder, func(*sql.Rows) ([]*OIDCAppLinkConfig, error)) {
|
||||
return sq.Select(
|
||||
AppOIDCConfigColumnAppID.identifier(),
|
||||
AppOIDCConfigColumnIOSTeamID.identifier(),
|
||||
AppOIDCConfigColumnIOSBundleID.identifier(),
|
||||
AppOIDCConfigColumnAndroidPackageName.identifier(),
|
||||
AppOIDCConfigColumnAndroidSHA256CertFingerprints.identifier(),
|
||||
).From(appOIDCConfigsTable.identifier()).
|
||||
Join(join(AppColumnID, AppOIDCConfigColumnAppID)).
|
||||
PlaceholderFormat(sq.Dollar),
|
||||
func(rows *sql.Rows) ([]*OIDCAppLinkConfig, error) {
|
||||
configs := make([]*OIDCAppLinkConfig, 0)
|
||||
for rows.Next() {
|
||||
var (
|
||||
appID sql.NullString
|
||||
iosTeamID sql.NullString
|
||||
iosBundleID sql.NullString
|
||||
androidPackage sql.NullString
|
||||
fingerprints database.TextArray[string]
|
||||
)
|
||||
if err := rows.Scan(
|
||||
&appID,
|
||||
&iosTeamID,
|
||||
&iosBundleID,
|
||||
&androidPackage,
|
||||
&fingerprints,
|
||||
); err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-ApLn3", "Errors.Internal")
|
||||
}
|
||||
configs = append(configs, &OIDCAppLinkConfig{
|
||||
AppID: appID.String,
|
||||
IOSTeamID: strings.TrimSpace(iosTeamID.String),
|
||||
IOSBundleID: strings.TrimSpace(iosBundleID.String),
|
||||
AndroidPackageName: strings.TrimSpace(androidPackage.String),
|
||||
AndroidSHA256CertFingerprints: fingerprints,
|
||||
})
|
||||
}
|
||||
return configs, nil
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,10 @@ var (
|
||||
` projections.apps7_oidc_configs.back_channel_logout_uri,` +
|
||||
` projections.apps7_oidc_configs.login_version,` +
|
||||
` projections.apps7_oidc_configs.login_base_uri,` +
|
||||
` projections.apps7_oidc_configs.ios_team_id,` +
|
||||
` projections.apps7_oidc_configs.ios_bundle_id,` +
|
||||
` projections.apps7_oidc_configs.android_package_name,` +
|
||||
` projections.apps7_oidc_configs.android_sha256_cert_fingerprints,` +
|
||||
//saml config
|
||||
` projections.apps7_saml_configs.app_id,` +
|
||||
` projections.apps7_saml_configs.entity_id,` +
|
||||
@@ -99,6 +103,10 @@ var (
|
||||
` projections.apps7_oidc_configs.back_channel_logout_uri,` +
|
||||
` projections.apps7_oidc_configs.login_version,` +
|
||||
` projections.apps7_oidc_configs.login_base_uri,` +
|
||||
` projections.apps7_oidc_configs.ios_team_id,` +
|
||||
` projections.apps7_oidc_configs.ios_bundle_id,` +
|
||||
` projections.apps7_oidc_configs.android_package_name,` +
|
||||
` projections.apps7_oidc_configs.android_sha256_cert_fingerprints,` +
|
||||
//saml config
|
||||
` projections.apps7_saml_configs.app_id,` +
|
||||
` projections.apps7_saml_configs.entity_id,` +
|
||||
@@ -172,6 +180,10 @@ var (
|
||||
"back_channel_logout_uri",
|
||||
"login_version",
|
||||
"login_base_uri",
|
||||
"ios_team_id",
|
||||
"ios_bundle_id",
|
||||
"android_package_name",
|
||||
"android_sha256_cert_fingerprints",
|
||||
//saml config
|
||||
"app_id",
|
||||
"entity_id",
|
||||
@@ -248,6 +260,10 @@ func Test_AppsPrepare(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// saml config
|
||||
nil,
|
||||
nil,
|
||||
@@ -319,6 +335,10 @@ func Test_AppsPrepare(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// saml config
|
||||
nil,
|
||||
nil,
|
||||
@@ -393,6 +413,10 @@ func Test_AppsPrepare(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// saml config
|
||||
"app-id",
|
||||
"https://test.com/saml/metadata",
|
||||
@@ -469,6 +493,10 @@ func Test_AppsPrepare(t *testing.T) {
|
||||
"back.channel.logout.ch",
|
||||
domain.LoginVersionUnspecified,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// saml config
|
||||
nil,
|
||||
nil,
|
||||
@@ -563,6 +591,10 @@ func Test_AppsPrepare(t *testing.T) {
|
||||
"back.channel.logout.ch",
|
||||
domain.LoginVersionUnspecified,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// saml config
|
||||
nil,
|
||||
nil,
|
||||
@@ -657,6 +689,10 @@ func Test_AppsPrepare(t *testing.T) {
|
||||
"back.channel.logout.ch",
|
||||
domain.LoginVersionUnspecified,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// saml config
|
||||
nil,
|
||||
nil,
|
||||
@@ -751,6 +787,10 @@ func Test_AppsPrepare(t *testing.T) {
|
||||
"back.channel.logout.ch",
|
||||
domain.LoginVersionUnspecified,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// saml config
|
||||
nil,
|
||||
nil,
|
||||
@@ -845,6 +885,10 @@ func Test_AppsPrepare(t *testing.T) {
|
||||
"back.channel.logout.ch",
|
||||
domain.LoginVersionUnspecified,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// saml config
|
||||
nil,
|
||||
nil,
|
||||
@@ -939,6 +983,10 @@ func Test_AppsPrepare(t *testing.T) {
|
||||
"back.channel.logout.ch",
|
||||
domain.LoginVersionUnspecified,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// saml config
|
||||
nil,
|
||||
nil,
|
||||
@@ -1033,6 +1081,10 @@ func Test_AppsPrepare(t *testing.T) {
|
||||
"back.channel.logout.ch",
|
||||
domain.LoginVersion2,
|
||||
"https://login.ch/",
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// saml config
|
||||
nil,
|
||||
nil,
|
||||
@@ -1075,6 +1127,10 @@ func Test_AppsPrepare(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// saml config
|
||||
nil,
|
||||
nil,
|
||||
@@ -1117,6 +1173,10 @@ func Test_AppsPrepare(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// saml config
|
||||
"saml-app-id",
|
||||
"https://test.com/saml/metadata",
|
||||
@@ -1302,6 +1362,10 @@ func Test_AppPrepare(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// saml config
|
||||
nil,
|
||||
nil,
|
||||
@@ -1367,6 +1431,10 @@ func Test_AppPrepare(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// saml config
|
||||
nil,
|
||||
nil,
|
||||
@@ -1437,6 +1505,10 @@ func Test_AppPrepare(t *testing.T) {
|
||||
"back.channel.logout.ch",
|
||||
domain.LoginVersionUnspecified,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// saml config
|
||||
nil,
|
||||
nil,
|
||||
@@ -1526,6 +1598,10 @@ func Test_AppPrepare(t *testing.T) {
|
||||
"back.channel.logout.ch",
|
||||
domain.LoginVersionUnspecified,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// saml config
|
||||
nil,
|
||||
nil,
|
||||
@@ -1615,6 +1691,10 @@ func Test_AppPrepare(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// saml config
|
||||
"app-id",
|
||||
"https://test.com/saml/metadata",
|
||||
@@ -1688,6 +1768,10 @@ func Test_AppPrepare(t *testing.T) {
|
||||
"back.channel.logout.ch",
|
||||
domain.LoginVersionUnspecified,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// saml config
|
||||
nil,
|
||||
nil,
|
||||
@@ -1777,6 +1861,10 @@ func Test_AppPrepare(t *testing.T) {
|
||||
"back.channel.logout.ch",
|
||||
domain.LoginVersionUnspecified,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// saml config
|
||||
nil,
|
||||
nil,
|
||||
@@ -1866,6 +1954,10 @@ func Test_AppPrepare(t *testing.T) {
|
||||
"back.channel.logout.ch",
|
||||
domain.LoginVersionUnspecified,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// saml config
|
||||
nil,
|
||||
nil,
|
||||
@@ -1955,6 +2047,10 @@ func Test_AppPrepare(t *testing.T) {
|
||||
"back.channel.logout.ch",
|
||||
domain.LoginVersionUnspecified,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// saml config
|
||||
nil,
|
||||
nil,
|
||||
|
||||
@@ -38,30 +38,34 @@ const (
|
||||
AppAPIConfigColumnClientSecret = "client_secret"
|
||||
AppAPIConfigColumnAuthMethod = "auth_method"
|
||||
|
||||
appOIDCTableSuffix = "oidc_configs"
|
||||
AppOIDCConfigColumnAppID = "app_id"
|
||||
AppOIDCConfigColumnInstanceID = "instance_id"
|
||||
AppOIDCConfigColumnVersion = "version"
|
||||
AppOIDCConfigColumnClientID = "client_id"
|
||||
AppOIDCConfigColumnClientSecret = "client_secret"
|
||||
AppOIDCConfigColumnRedirectUris = "redirect_uris"
|
||||
AppOIDCConfigColumnResponseTypes = "response_types"
|
||||
AppOIDCConfigColumnGrantTypes = "grant_types"
|
||||
AppOIDCConfigColumnApplicationType = "application_type"
|
||||
AppOIDCConfigColumnAuthMethodType = "auth_method_type"
|
||||
AppOIDCConfigColumnPostLogoutRedirectUris = "post_logout_redirect_uris"
|
||||
AppOIDCConfigColumnDevMode = "is_dev_mode"
|
||||
AppOIDCConfigColumnAccessTokenType = "access_token_type"
|
||||
AppOIDCConfigColumnAccessTokenRoleAssertion = "access_token_role_assertion"
|
||||
AppOIDCConfigColumnIDTokenRoleAssertion = "id_token_role_assertion"
|
||||
AppOIDCConfigColumnIDTokenUserinfoAssertion = "id_token_userinfo_assertion"
|
||||
AppOIDCConfigColumnClockSkew = "clock_skew"
|
||||
AppOIDCConfigColumnAdditionalOrigins = "additional_origins"
|
||||
AppOIDCConfigColumnSkipNativeAppSuccessPage = "skip_native_app_success_page"
|
||||
AppOIDCConfigColumnBackChannelLogoutURI = "back_channel_logout_uri"
|
||||
AppOIDCConfigColumnLoginVersion = "login_version"
|
||||
AppOIDCConfigColumnLoginBaseURI = "login_base_uri"
|
||||
AppOIDCConfigColumnRegistrationToken = "registration_token"
|
||||
appOIDCTableSuffix = "oidc_configs"
|
||||
AppOIDCConfigColumnAppID = "app_id"
|
||||
AppOIDCConfigColumnInstanceID = "instance_id"
|
||||
AppOIDCConfigColumnVersion = "version"
|
||||
AppOIDCConfigColumnClientID = "client_id"
|
||||
AppOIDCConfigColumnClientSecret = "client_secret"
|
||||
AppOIDCConfigColumnRedirectUris = "redirect_uris"
|
||||
AppOIDCConfigColumnResponseTypes = "response_types"
|
||||
AppOIDCConfigColumnGrantTypes = "grant_types"
|
||||
AppOIDCConfigColumnApplicationType = "application_type"
|
||||
AppOIDCConfigColumnAuthMethodType = "auth_method_type"
|
||||
AppOIDCConfigColumnPostLogoutRedirectUris = "post_logout_redirect_uris"
|
||||
AppOIDCConfigColumnDevMode = "is_dev_mode"
|
||||
AppOIDCConfigColumnAccessTokenType = "access_token_type"
|
||||
AppOIDCConfigColumnAccessTokenRoleAssertion = "access_token_role_assertion"
|
||||
AppOIDCConfigColumnIDTokenRoleAssertion = "id_token_role_assertion"
|
||||
AppOIDCConfigColumnIDTokenUserinfoAssertion = "id_token_userinfo_assertion"
|
||||
AppOIDCConfigColumnClockSkew = "clock_skew"
|
||||
AppOIDCConfigColumnAdditionalOrigins = "additional_origins"
|
||||
AppOIDCConfigColumnSkipNativeAppSuccessPage = "skip_native_app_success_page"
|
||||
AppOIDCConfigColumnBackChannelLogoutURI = "back_channel_logout_uri"
|
||||
AppOIDCConfigColumnLoginVersion = "login_version"
|
||||
AppOIDCConfigColumnLoginBaseURI = "login_base_uri"
|
||||
AppOIDCConfigColumnIOSTeamID = "ios_team_id"
|
||||
AppOIDCConfigColumnIOSBundleID = "ios_bundle_id"
|
||||
AppOIDCConfigColumnAndroidPackageName = "android_package_name"
|
||||
AppOIDCConfigColumnAndroidSHA256CertFingerprints = "android_sha256_cert_fingerprints"
|
||||
AppOIDCConfigColumnRegistrationToken = "registration_token"
|
||||
|
||||
appSAMLTableSuffix = "saml_configs"
|
||||
AppSAMLConfigColumnAppID = "app_id"
|
||||
@@ -134,6 +138,10 @@ func (*appProjection) Init() *old_handler.Check {
|
||||
handler.NewColumn(AppOIDCConfigColumnBackChannelLogoutURI, handler.ColumnTypeText, handler.Nullable()),
|
||||
handler.NewColumn(AppOIDCConfigColumnLoginVersion, handler.ColumnTypeEnum, handler.Nullable()),
|
||||
handler.NewColumn(AppOIDCConfigColumnLoginBaseURI, handler.ColumnTypeText, handler.Nullable()),
|
||||
handler.NewColumn(AppOIDCConfigColumnIOSTeamID, handler.ColumnTypeText, handler.Nullable()),
|
||||
handler.NewColumn(AppOIDCConfigColumnIOSBundleID, handler.ColumnTypeText, handler.Nullable()),
|
||||
handler.NewColumn(AppOIDCConfigColumnAndroidPackageName, handler.ColumnTypeText, handler.Nullable()),
|
||||
handler.NewColumn(AppOIDCConfigColumnAndroidSHA256CertFingerprints, handler.ColumnTypeTextArray, handler.Nullable()),
|
||||
handler.NewColumn(AppOIDCConfigColumnRegistrationToken, handler.ColumnTypeText, handler.Nullable()),
|
||||
},
|
||||
handler.NewPrimaryKey(AppOIDCConfigColumnInstanceID, AppOIDCConfigColumnAppID),
|
||||
@@ -519,6 +527,10 @@ func (p *appProjection) reduceOIDCConfigAdded(event eventstore.Event) (*handler.
|
||||
handler.NewCol(AppOIDCConfigColumnBackChannelLogoutURI, e.BackChannelLogoutURI),
|
||||
handler.NewCol(AppOIDCConfigColumnLoginVersion, e.LoginVersion),
|
||||
handler.NewCol(AppOIDCConfigColumnLoginBaseURI, e.LoginBaseURI),
|
||||
handler.NewCol(AppOIDCConfigColumnIOSTeamID, e.IOSTeamID),
|
||||
handler.NewCol(AppOIDCConfigColumnIOSBundleID, e.IOSBundleID),
|
||||
handler.NewCol(AppOIDCConfigColumnAndroidPackageName, e.AndroidPackageName),
|
||||
handler.NewCol(AppOIDCConfigColumnAndroidSHA256CertFingerprints, database.TextArray[string](e.AndroidSHA256CertFingerprints)),
|
||||
},
|
||||
handler.WithTableSuffix(appOIDCTableSuffix),
|
||||
),
|
||||
@@ -596,6 +608,18 @@ func (p *appProjection) reduceOIDCConfigChanged(event eventstore.Event) (*handle
|
||||
if e.LoginBaseURI != nil {
|
||||
cols = append(cols, handler.NewCol(AppOIDCConfigColumnLoginBaseURI, *e.LoginBaseURI))
|
||||
}
|
||||
if e.IOSTeamID != nil {
|
||||
cols = append(cols, handler.NewCol(AppOIDCConfigColumnIOSTeamID, *e.IOSTeamID))
|
||||
}
|
||||
if e.IOSBundleID != nil {
|
||||
cols = append(cols, handler.NewCol(AppOIDCConfigColumnIOSBundleID, *e.IOSBundleID))
|
||||
}
|
||||
if e.AndroidPackageName != nil {
|
||||
cols = append(cols, handler.NewCol(AppOIDCConfigColumnAndroidPackageName, *e.AndroidPackageName))
|
||||
}
|
||||
if e.AndroidSHA256CertFingerprints != nil {
|
||||
cols = append(cols, handler.NewCol(AppOIDCConfigColumnAndroidSHA256CertFingerprints, database.TextArray[string](*e.AndroidSHA256CertFingerprints)))
|
||||
}
|
||||
|
||||
if len(cols) == 0 {
|
||||
return handler.NewNoOpStatement(e), nil
|
||||
|
||||
@@ -572,7 +572,7 @@ func TestAppProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.apps7_oidc_configs (app_id, instance_id, version, client_id, client_secret, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page, back_channel_logout_uri, login_version, login_base_uri) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22)",
|
||||
expectedStmt: "INSERT INTO projections.apps7_oidc_configs (app_id, instance_id, version, client_id, client_secret, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page, back_channel_logout_uri, login_version, login_base_uri, ios_team_id, ios_bundle_id, android_package_name, android_sha256_cert_fingerprints) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26)",
|
||||
expectedArgs: []interface{}{
|
||||
"app-id",
|
||||
"instance-id",
|
||||
@@ -596,6 +596,10 @@ func TestAppProjection_reduces(t *testing.T) {
|
||||
"back.channel.one.ch",
|
||||
domain.LoginVersion2,
|
||||
"https://login.ch/",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
database.TextArray[string](nil),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -650,7 +654,7 @@ func TestAppProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.apps7_oidc_configs (app_id, instance_id, version, client_id, client_secret, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page, back_channel_logout_uri, login_version, login_base_uri) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22)",
|
||||
expectedStmt: "INSERT INTO projections.apps7_oidc_configs (app_id, instance_id, version, client_id, client_secret, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page, back_channel_logout_uri, login_version, login_base_uri, ios_team_id, ios_bundle_id, android_package_name, android_sha256_cert_fingerprints) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26)",
|
||||
expectedArgs: []interface{}{
|
||||
"app-id",
|
||||
"instance-id",
|
||||
@@ -674,6 +678,10 @@ func TestAppProjection_reduces(t *testing.T) {
|
||||
"back.channel.one.ch",
|
||||
domain.LoginVersion2,
|
||||
"https://login.ch/",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
database.TextArray[string](nil),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -761,6 +769,52 @@ func TestAppProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "project reduceOIDCConfigChanged app links",
|
||||
args: args{
|
||||
event: getEvent(
|
||||
testEvent(
|
||||
project.OIDCConfigChangedType,
|
||||
project.AggregateType,
|
||||
[]byte(`{
|
||||
"appId": "app-id",
|
||||
"iosTeamId": "TEAMID",
|
||||
"iosBundleId": "com.example.app",
|
||||
"androidPackageName": "com.example.app",
|
||||
"androidSha256CertFingerprints": ["AA:BB:CC"]
|
||||
}`),
|
||||
), project.OIDCConfigChangedEventMapper),
|
||||
},
|
||||
reduce: (&appProjection{}).reduceOIDCConfigChanged,
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("project"),
|
||||
sequence: 15,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.apps7_oidc_configs SET (ios_team_id, ios_bundle_id, android_package_name, android_sha256_cert_fingerprints) = ($1, $2, $3, $4) WHERE (app_id = $5) AND (instance_id = $6)",
|
||||
expectedArgs: []interface{}{
|
||||
"TEAMID",
|
||||
"com.example.app",
|
||||
"com.example.app",
|
||||
database.TextArray[string]{"AA:BB:CC"},
|
||||
"app-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
"app-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "project reduceOIDCConfigChanged noop",
|
||||
args: args{
|
||||
|
||||
Reference in New Issue
Block a user