feat(saml): implementation of saml for ZITADEL v2 (#3618)

This commit is contained in:
Stefan Benz
2022-09-12 17:18:08 +01:00
committed by GitHub
parent 01a92ba5d9
commit 7a5f7f82cf
134 changed files with 5570 additions and 1293 deletions

View File

@@ -33,6 +33,7 @@ type App struct {
Name string
OIDCConfig *OIDCApp
SAMLConfig *SAMLApp
APIConfig *APIApp
}
@@ -56,6 +57,12 @@ type OIDCApp struct {
AllowedOrigins database.StringArray
}
type SAMLApp struct {
Metadata []byte
MetadataURL string
EntityID string
}
type APIApp struct {
ClientID string
AuthMethodType domain.APIAuthMethodType
@@ -116,6 +123,28 @@ var (
}
)
var (
appSAMLConfigsTable = table{
name: projection.AppSAMLTable,
}
AppSAMLConfigColumnAppID = Column{
name: projection.AppSAMLConfigColumnAppID,
table: appSAMLConfigsTable,
}
AppSAMLConfigColumnEntityID = Column{
name: projection.AppSAMLConfigColumnEntityID,
table: appSAMLConfigsTable,
}
AppSAMLConfigColumnMetadata = Column{
name: projection.AppSAMLConfigColumnMetadata,
table: appSAMLConfigsTable,
}
AppSAMLConfigColumnMetadataURL = Column{
name: projection.AppSAMLConfigColumnMetadataURL,
table: appSAMLConfigsTable,
}
)
var (
appAPIConfigsTable = table{
name: projection.AppAPITable,
@@ -225,6 +254,54 @@ func (q *Queries) AppByProjectAndAppID(ctx context.Context, shouldTriggerBulk bo
return scan(row)
}
func (q *Queries) AppByID(ctx context.Context, appID string) (*App, error) {
stmt, scan := prepareAppQuery()
query, args, err := stmt.Where(
sq.Eq{
AppColumnID.identifier(): appID,
AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
},
).ToSql()
if err != nil {
return nil, errors.ThrowInternal(err, "QUERY-immt9", "Errors.Query.SQLStatement")
}
row := q.client.QueryRowContext(ctx, query, args...)
return scan(row)
}
func (q *Queries) AppBySAMLEntityID(ctx context.Context, entityID string) (*App, error) {
stmt, scan := prepareAppQuery()
query, args, err := stmt.Where(
sq.Eq{
AppSAMLConfigColumnEntityID.identifier(): entityID,
},
).ToSql()
if err != nil {
return nil, errors.ThrowInternal(err, "QUERY-JgUop", "Errors.Query.SQLStatement")
}
row := q.client.QueryRowContext(ctx, query, args...)
return scan(row)
}
func (q *Queries) ProjectByClientID(ctx context.Context, appID string) (*Project, error) {
stmt, scan := prepareProjectByAppQuery()
query, args, err := stmt.Where(
sq.Or{
sq.Eq{AppOIDCConfigColumnClientID.identifier(): appID},
sq.Eq{AppAPIConfigColumnClientID.identifier(): appID},
sq.Eq{AppSAMLConfigColumnAppID.identifier(): appID},
},
).ToSql()
if err != nil {
return nil, errors.ThrowInternal(err, "QUERY-XhJi3", "Errors.Query.SQLStatement")
}
row := q.client.QueryRowContext(ctx, query, args...)
return scan(row)
}
func (q *Queries) ProjectIDFromOIDCClientID(ctx context.Context, appID string) (string, error) {
stmt, scan := prepareProjectIDByAppQuery()
query, args, err := stmt.Where(
@@ -249,6 +326,7 @@ func (q *Queries) ProjectIDFromClientID(ctx context.Context, appID string) (stri
sq.Or{
sq.Eq{AppOIDCConfigColumnClientID.identifier(): appID},
sq.Eq{AppAPIConfigColumnClientID.identifier(): appID},
sq.Eq{AppSAMLConfigColumnAppID.identifier(): appID},
},
},
).ToSql()
@@ -389,15 +467,22 @@ func prepareAppQuery() (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
AppOIDCConfigColumnIDTokenUserinfoAssertion.identifier(),
AppOIDCConfigColumnClockSkew.identifier(),
AppOIDCConfigColumnAdditionalOrigins.identifier(),
AppSAMLConfigColumnAppID.identifier(),
AppSAMLConfigColumnEntityID.identifier(),
AppSAMLConfigColumnMetadata.identifier(),
AppSAMLConfigColumnMetadataURL.identifier(),
).From(appsTable.identifier()).
LeftJoin(join(AppAPIConfigColumnAppID, AppColumnID)).
LeftJoin(join(AppOIDCConfigColumnAppID, AppColumnID)).
LeftJoin(join(AppSAMLConfigColumnAppID, AppColumnID)).
PlaceholderFormat(sq.Dollar), func(row *sql.Row) (*App, error) {
app := new(App)
var (
apiConfig = sqlAPIConfig{}
oidcConfig = sqlOIDCConfig{}
samlConfig = sqlSAMLConfig{}
)
err := row.Scan(
@@ -430,6 +515,11 @@ func prepareAppQuery() (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
&oidcConfig.iDTokenUserinfoAssertion,
&oidcConfig.clockSkew,
&oidcConfig.additionalOrigins,
&samlConfig.appID,
&samlConfig.entityID,
&samlConfig.metadata,
&samlConfig.metadataURL,
)
if err != nil {
@@ -441,6 +531,7 @@ func prepareAppQuery() (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
apiConfig.set(app)
oidcConfig.set(app)
samlConfig.set(app)
return app, nil
}
@@ -452,6 +543,7 @@ func prepareProjectIDByAppQuery() (sq.SelectBuilder, func(*sql.Row) (projectID s
).From(appsTable.identifier()).
LeftJoin(join(AppAPIConfigColumnAppID, AppColumnID)).
LeftJoin(join(AppOIDCConfigColumnAppID, AppColumnID)).
LeftJoin(join(AppSAMLConfigColumnAppID, AppColumnID)).
PlaceholderFormat(sq.Dollar), func(row *sql.Row) (projectID string, err error) {
err = row.Scan(
&projectID,
@@ -485,6 +577,7 @@ func prepareProjectByAppQuery() (sq.SelectBuilder, func(*sql.Row) (*Project, err
Join(join(AppColumnProjectID, ProjectColumnID)).
LeftJoin(join(AppAPIConfigColumnAppID, AppColumnID)).
LeftJoin(join(AppOIDCConfigColumnAppID, AppColumnID)).
LeftJoin(join(AppSAMLConfigColumnAppID, AppColumnID)).
PlaceholderFormat(sq.Dollar),
func(row *sql.Row) (*Project, error) {
p := new(Project)
@@ -542,10 +635,16 @@ func prepareAppsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Apps, error)) {
AppOIDCConfigColumnIDTokenUserinfoAssertion.identifier(),
AppOIDCConfigColumnClockSkew.identifier(),
AppOIDCConfigColumnAdditionalOrigins.identifier(),
AppSAMLConfigColumnAppID.identifier(),
AppSAMLConfigColumnEntityID.identifier(),
AppSAMLConfigColumnMetadata.identifier(),
AppSAMLConfigColumnMetadataURL.identifier(),
countColumn.identifier(),
).From(appsTable.identifier()).
LeftJoin(join(AppAPIConfigColumnAppID, AppColumnID)).
LeftJoin(join(AppOIDCConfigColumnAppID, AppColumnID)).
LeftJoin(join(AppSAMLConfigColumnAppID, AppColumnID)).
PlaceholderFormat(sq.Dollar), func(row *sql.Rows) (*Apps, error) {
apps := &Apps{Apps: []*App{}}
@@ -554,6 +653,7 @@ func prepareAppsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Apps, error)) {
var (
apiConfig = sqlAPIConfig{}
oidcConfig = sqlOIDCConfig{}
samlConfig = sqlSAMLConfig{}
)
err := row.Scan(
@@ -586,6 +686,12 @@ func prepareAppsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Apps, error)) {
&oidcConfig.iDTokenUserinfoAssertion,
&oidcConfig.clockSkew,
&oidcConfig.additionalOrigins,
&samlConfig.appID,
&samlConfig.entityID,
&samlConfig.metadata,
&samlConfig.metadataURL,
&apps.Count,
)
@@ -595,6 +701,7 @@ func prepareAppsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Apps, error)) {
apiConfig.set(app)
oidcConfig.set(app)
samlConfig.set(app)
apps.Apps = append(apps.Apps, app)
}
@@ -681,6 +788,24 @@ func (c sqlOIDCConfig) set(app *App) {
logging.LogWithFields("app", app.ID).OnError(err).Warn("unable to set allowed origins")
}
type sqlSAMLConfig struct {
appID sql.NullString
entityID sql.NullString
metadataURL sql.NullString
metadata []byte
}
func (c sqlSAMLConfig) set(app *App) {
if !c.appID.Valid {
return
}
app.SAMLConfig = &SAMLApp{
MetadataURL: c.metadataURL.String,
Metadata: c.metadata,
EntityID: c.entityID.String,
}
}
type sqlAPIConfig struct {
appID sql.NullString
clientID sql.NullString

View File

@@ -15,80 +15,93 @@ import (
)
var (
expectedAppQuery = regexp.QuoteMeta(`SELECT projections.apps2.id,` +
` projections.apps2.name,` +
` projections.apps2.project_id,` +
` projections.apps2.creation_date,` +
` projections.apps2.change_date,` +
` projections.apps2.resource_owner,` +
` projections.apps2.state,` +
` projections.apps2.sequence,` +
expectedAppQuery = regexp.QuoteMeta(`SELECT projections.apps3.id,` +
` projections.apps3.name,` +
` projections.apps3.project_id,` +
` projections.apps3.creation_date,` +
` projections.apps3.change_date,` +
` projections.apps3.resource_owner,` +
` projections.apps3.state,` +
` projections.apps3.sequence,` +
// api config
` projections.apps2_api_configs.app_id,` +
` projections.apps2_api_configs.client_id,` +
` projections.apps2_api_configs.auth_method,` +
` projections.apps3_api_configs.app_id,` +
` projections.apps3_api_configs.client_id,` +
` projections.apps3_api_configs.auth_method,` +
// oidc config
` projections.apps2_oidc_configs.app_id,` +
` projections.apps2_oidc_configs.version,` +
` projections.apps2_oidc_configs.client_id,` +
` projections.apps2_oidc_configs.redirect_uris,` +
` projections.apps2_oidc_configs.response_types,` +
` projections.apps2_oidc_configs.grant_types,` +
` projections.apps2_oidc_configs.application_type,` +
` projections.apps2_oidc_configs.auth_method_type,` +
` projections.apps2_oidc_configs.post_logout_redirect_uris,` +
` projections.apps2_oidc_configs.is_dev_mode,` +
` projections.apps2_oidc_configs.access_token_type,` +
` projections.apps2_oidc_configs.access_token_role_assertion,` +
` projections.apps2_oidc_configs.id_token_role_assertion,` +
` projections.apps2_oidc_configs.id_token_userinfo_assertion,` +
` projections.apps2_oidc_configs.clock_skew,` +
` projections.apps2_oidc_configs.additional_origins` +
` FROM projections.apps2` +
` LEFT JOIN projections.apps2_api_configs ON projections.apps2.id = projections.apps2_api_configs.app_id` +
` LEFT JOIN projections.apps2_oidc_configs ON projections.apps2.id = projections.apps2_oidc_configs.app_id`)
expectedAppsQuery = regexp.QuoteMeta(`SELECT projections.apps2.id,` +
` projections.apps2.name,` +
` projections.apps2.project_id,` +
` projections.apps2.creation_date,` +
` projections.apps2.change_date,` +
` projections.apps2.resource_owner,` +
` projections.apps2.state,` +
` projections.apps2.sequence,` +
` projections.apps3_oidc_configs.app_id,` +
` projections.apps3_oidc_configs.version,` +
` projections.apps3_oidc_configs.client_id,` +
` projections.apps3_oidc_configs.redirect_uris,` +
` projections.apps3_oidc_configs.response_types,` +
` projections.apps3_oidc_configs.grant_types,` +
` projections.apps3_oidc_configs.application_type,` +
` projections.apps3_oidc_configs.auth_method_type,` +
` projections.apps3_oidc_configs.post_logout_redirect_uris,` +
` projections.apps3_oidc_configs.is_dev_mode,` +
` projections.apps3_oidc_configs.access_token_type,` +
` projections.apps3_oidc_configs.access_token_role_assertion,` +
` projections.apps3_oidc_configs.id_token_role_assertion,` +
` projections.apps3_oidc_configs.id_token_userinfo_assertion,` +
` projections.apps3_oidc_configs.clock_skew,` +
` projections.apps3_oidc_configs.additional_origins,` +
//saml config
` projections.apps3_saml_configs.app_id,` +
` projections.apps3_saml_configs.entity_id,` +
` projections.apps3_saml_configs.metadata,` +
` projections.apps3_saml_configs.metadata_url` +
` FROM projections.apps3` +
` LEFT JOIN projections.apps3_api_configs ON projections.apps3.id = projections.apps3_api_configs.app_id` +
` LEFT JOIN projections.apps3_oidc_configs ON projections.apps3.id = projections.apps3_oidc_configs.app_id` +
` LEFT JOIN projections.apps3_saml_configs ON projections.apps3.id = projections.apps3_saml_configs.app_id`)
expectedAppsQuery = regexp.QuoteMeta(`SELECT projections.apps3.id,` +
` projections.apps3.name,` +
` projections.apps3.project_id,` +
` projections.apps3.creation_date,` +
` projections.apps3.change_date,` +
` projections.apps3.resource_owner,` +
` projections.apps3.state,` +
` projections.apps3.sequence,` +
// api config
` projections.apps2_api_configs.app_id,` +
` projections.apps2_api_configs.client_id,` +
` projections.apps2_api_configs.auth_method,` +
` projections.apps3_api_configs.app_id,` +
` projections.apps3_api_configs.client_id,` +
` projections.apps3_api_configs.auth_method,` +
// oidc config
` projections.apps2_oidc_configs.app_id,` +
` projections.apps2_oidc_configs.version,` +
` projections.apps2_oidc_configs.client_id,` +
` projections.apps2_oidc_configs.redirect_uris,` +
` projections.apps2_oidc_configs.response_types,` +
` projections.apps2_oidc_configs.grant_types,` +
` projections.apps2_oidc_configs.application_type,` +
` projections.apps2_oidc_configs.auth_method_type,` +
` projections.apps2_oidc_configs.post_logout_redirect_uris,` +
` projections.apps2_oidc_configs.is_dev_mode,` +
` projections.apps2_oidc_configs.access_token_type,` +
` projections.apps2_oidc_configs.access_token_role_assertion,` +
` projections.apps2_oidc_configs.id_token_role_assertion,` +
` projections.apps2_oidc_configs.id_token_userinfo_assertion,` +
` projections.apps2_oidc_configs.clock_skew,` +
` projections.apps2_oidc_configs.additional_origins,` +
` projections.apps3_oidc_configs.app_id,` +
` projections.apps3_oidc_configs.version,` +
` projections.apps3_oidc_configs.client_id,` +
` projections.apps3_oidc_configs.redirect_uris,` +
` projections.apps3_oidc_configs.response_types,` +
` projections.apps3_oidc_configs.grant_types,` +
` projections.apps3_oidc_configs.application_type,` +
` projections.apps3_oidc_configs.auth_method_type,` +
` projections.apps3_oidc_configs.post_logout_redirect_uris,` +
` projections.apps3_oidc_configs.is_dev_mode,` +
` projections.apps3_oidc_configs.access_token_type,` +
` projections.apps3_oidc_configs.access_token_role_assertion,` +
` projections.apps3_oidc_configs.id_token_role_assertion,` +
` projections.apps3_oidc_configs.id_token_userinfo_assertion,` +
` projections.apps3_oidc_configs.clock_skew,` +
` projections.apps3_oidc_configs.additional_origins,` +
//saml config
` projections.apps3_saml_configs.app_id,` +
` projections.apps3_saml_configs.entity_id,` +
` projections.apps3_saml_configs.metadata,` +
` projections.apps3_saml_configs.metadata_url,` +
` COUNT(*) OVER ()` +
` FROM projections.apps2` +
` LEFT JOIN projections.apps2_api_configs ON projections.apps2.id = projections.apps2_api_configs.app_id` +
` LEFT JOIN projections.apps2_oidc_configs ON projections.apps2.id = projections.apps2_oidc_configs.app_id`)
expectedAppIDsQuery = regexp.QuoteMeta(`SELECT projections.apps2_api_configs.client_id,` +
` projections.apps2_oidc_configs.client_id` +
` FROM projections.apps2` +
` LEFT JOIN projections.apps2_api_configs ON projections.apps2.id = projections.apps2_api_configs.app_id` +
` LEFT JOIN projections.apps2_oidc_configs ON projections.apps2.id = projections.apps2_oidc_configs.app_id`)
expectedProjectIDByAppQuery = regexp.QuoteMeta(`SELECT projections.apps2.project_id` +
` FROM projections.apps2` +
` LEFT JOIN projections.apps2_api_configs ON projections.apps2.id = projections.apps2_api_configs.app_id` +
` LEFT JOIN projections.apps2_oidc_configs ON projections.apps2.id = projections.apps2_oidc_configs.app_id`)
` FROM projections.apps3` +
` LEFT JOIN projections.apps3_api_configs ON projections.apps3.id = projections.apps3_api_configs.app_id` +
` LEFT JOIN projections.apps3_oidc_configs ON projections.apps3.id = projections.apps3_oidc_configs.app_id` +
` LEFT JOIN projections.apps3_saml_configs ON projections.apps3.id = projections.apps3_saml_configs.app_id`)
expectedAppIDsQuery = regexp.QuoteMeta(`SELECT projections.apps3_api_configs.client_id,` +
` projections.apps3_oidc_configs.client_id` +
` FROM projections.apps3` +
` LEFT JOIN projections.apps3_api_configs ON projections.apps3.id = projections.apps3_api_configs.app_id` +
` LEFT JOIN projections.apps3_oidc_configs ON projections.apps3.id = projections.apps3_oidc_configs.app_id`)
expectedProjectIDByAppQuery = regexp.QuoteMeta(`SELECT projections.apps3.project_id` +
` FROM projections.apps3` +
` LEFT JOIN projections.apps3_api_configs ON projections.apps3.id = projections.apps3_api_configs.app_id` +
` LEFT JOIN projections.apps3_oidc_configs ON projections.apps3.id = projections.apps3_oidc_configs.app_id` +
` LEFT JOIN projections.apps3_saml_configs ON projections.apps3.id = projections.apps3_saml_configs.app_id`)
expectedProjectByAppQuery = regexp.QuoteMeta(`SELECT projections.projects2.id,` +
` projections.projects2.creation_date,` +
` projections.projects2.change_date,` +
@@ -101,9 +114,10 @@ var (
` projections.projects2.has_project_check,` +
` projections.projects2.private_labeling_setting` +
` FROM projections.projects2` +
` JOIN projections.apps2 ON projections.projects2.id = projections.apps2.project_id` +
` LEFT JOIN projections.apps2_api_configs ON projections.apps2.id = projections.apps2_api_configs.app_id` +
` LEFT JOIN projections.apps2_oidc_configs ON projections.apps2.id = projections.apps2_oidc_configs.app_id`)
` JOIN projections.apps3 ON projections.projects2.id = projections.apps3.project_id` +
` LEFT JOIN projections.apps3_api_configs ON projections.apps3.id = projections.apps3_api_configs.app_id` +
` LEFT JOIN projections.apps3_oidc_configs ON projections.apps3.id = projections.apps3_oidc_configs.app_id` +
` LEFT JOIN projections.apps3_saml_configs ON projections.apps3.id = projections.apps3_saml_configs.app_id`)
appCols = database.StringArray{
"id",
@@ -135,6 +149,11 @@ var (
"id_token_userinfo_assertion",
"clock_skew",
"additional_origins",
//saml config
"app_id",
"entity_id",
"metadata",
"metadata_url",
}
appsCols = append(appCols, "count")
)
@@ -200,6 +219,11 @@ func Test_AppsPrepare(t *testing.T) {
nil,
nil,
nil,
// saml config
nil,
nil,
nil,
nil,
},
},
),
@@ -260,6 +284,11 @@ func Test_AppsPrepare(t *testing.T) {
nil,
nil,
nil,
// saml config
nil,
nil,
nil,
nil,
},
},
),
@@ -285,6 +314,75 @@ func Test_AppsPrepare(t *testing.T) {
},
},
},
}, {
name: "prepareAppsQuery saml app",
prepare: prepareAppsQuery,
want: want{
sqlExpectations: mockQueries(
expectedAppsQuery,
appsCols,
[][]driver.Value{
{
"app-id",
"app-name",
"project-id",
testNow,
testNow,
"ro",
domain.AppStateActive,
uint64(20211109),
// api config
nil,
nil,
nil,
// oidc config
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
// saml config
"app-id",
"https://test.com/saml/metadata",
[]byte("<?xml version=\"1.0\"?>\n<md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\"\n validUntil=\"2022-08-26T14:08:16Z\"\n cacheDuration=\"PT604800S\"\n entityID=\"https://test.com/saml/metadata\">\n <md:SPSSODescriptor AuthnRequestsSigned=\"false\" WantAssertionsSigned=\"false\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>\n <md:AssertionConsumerService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n Location=\"https://test.com/saml/acs\"\n index=\"1\" />\n \n </md:SPSSODescriptor>\n</md:EntityDescriptor>"),
"https://test.com/saml/metadata",
},
},
),
},
object: &Apps{
SearchResponse: SearchResponse{
Count: 1,
},
Apps: []*App{
{
ID: "app-id",
CreationDate: testNow,
ChangeDate: testNow,
ResourceOwner: "ro",
State: domain.AppStateActive,
Sequence: 20211109,
Name: "app-name",
ProjectID: "project-id",
SAMLConfig: &SAMLApp{
Metadata: []byte("<?xml version=\"1.0\"?>\n<md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\"\n validUntil=\"2022-08-26T14:08:16Z\"\n cacheDuration=\"PT604800S\"\n entityID=\"https://test.com/saml/metadata\">\n <md:SPSSODescriptor AuthnRequestsSigned=\"false\" WantAssertionsSigned=\"false\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>\n <md:AssertionConsumerService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n Location=\"https://test.com/saml/acs\"\n index=\"1\" />\n \n </md:SPSSODescriptor>\n</md:EntityDescriptor>"),
MetadataURL: "https://test.com/saml/metadata",
EntityID: "https://test.com/saml/metadata",
},
},
},
},
},
{
name: "prepareAppsQuery oidc app",
@@ -324,6 +422,11 @@ func Test_AppsPrepare(t *testing.T) {
true,
1 * time.Second,
database.StringArray{"additional.origin"},
// saml config
nil,
nil,
nil,
nil,
},
},
),
@@ -403,6 +506,11 @@ func Test_AppsPrepare(t *testing.T) {
true,
1 * time.Second,
database.StringArray{"additional.origin"},
// saml config
nil,
nil,
nil,
nil,
},
},
),
@@ -482,6 +590,11 @@ func Test_AppsPrepare(t *testing.T) {
true,
1 * time.Second,
database.StringArray{"additional.origin"},
// saml config
nil,
nil,
nil,
nil,
},
},
),
@@ -561,6 +674,11 @@ func Test_AppsPrepare(t *testing.T) {
true,
1 * time.Second,
database.StringArray{"additional.origin"},
// saml config
nil,
nil,
nil,
nil,
},
},
),
@@ -640,6 +758,11 @@ func Test_AppsPrepare(t *testing.T) {
true,
1 * time.Second,
database.StringArray{"additional.origin"},
// saml config
nil,
nil,
nil,
nil,
},
},
),
@@ -719,6 +842,11 @@ func Test_AppsPrepare(t *testing.T) {
true,
1 * time.Second,
database.StringArray{"additional.origin"},
// saml config
nil,
nil,
nil,
nil,
},
{
"api-app-id",
@@ -750,13 +878,54 @@ func Test_AppsPrepare(t *testing.T) {
nil,
nil,
nil,
// saml config
nil,
nil,
nil,
nil,
},
{
"saml-app-id",
"app-name",
"project-id",
testNow,
testNow,
"ro",
domain.AppStateActive,
uint64(20211109),
// api config
nil,
nil,
nil,
// oidc config
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
// saml config
"saml-app-id",
"https://test.com/saml/metadata",
[]byte("<?xml version=\"1.0\"?>\n<md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\"\n validUntil=\"2022-08-26T14:08:16Z\"\n cacheDuration=\"PT604800S\"\n entityID=\"https://test.com/saml/metadata\">\n <md:SPSSODescriptor AuthnRequestsSigned=\"false\" WantAssertionsSigned=\"false\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>\n <md:AssertionConsumerService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n Location=\"https://test.com/saml/acs\"\n index=\"1\" />\n \n </md:SPSSODescriptor>\n</md:EntityDescriptor>"),
"https://test.com/saml/metadata",
},
},
),
},
object: &Apps{
SearchResponse: SearchResponse{
Count: 2,
Count: 3,
},
Apps: []*App{
{
@@ -802,6 +971,21 @@ func Test_AppsPrepare(t *testing.T) {
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
},
},
{
ID: "saml-app-id",
CreationDate: testNow,
ChangeDate: testNow,
ResourceOwner: "ro",
State: domain.AppStateActive,
Sequence: 20211109,
Name: "app-name",
ProjectID: "project-id",
SAMLConfig: &SAMLApp{
Metadata: []byte("<?xml version=\"1.0\"?>\n<md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\"\n validUntil=\"2022-08-26T14:08:16Z\"\n cacheDuration=\"PT604800S\"\n entityID=\"https://test.com/saml/metadata\">\n <md:SPSSODescriptor AuthnRequestsSigned=\"false\" WantAssertionsSigned=\"false\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>\n <md:AssertionConsumerService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n Location=\"https://test.com/saml/acs\"\n index=\"1\" />\n \n </md:SPSSODescriptor>\n</md:EntityDescriptor>"),
MetadataURL: "https://test.com/saml/metadata",
EntityID: "https://test.com/saml/metadata",
},
},
},
},
},
@@ -896,6 +1080,11 @@ func Test_AppPrepare(t *testing.T) {
nil,
nil,
nil,
// saml config
nil,
nil,
nil,
nil,
},
),
},
@@ -948,6 +1137,11 @@ func Test_AppPrepare(t *testing.T) {
nil,
nil,
nil,
// saml config
nil,
nil,
nil,
nil,
},
},
),
@@ -1005,6 +1199,11 @@ func Test_AppPrepare(t *testing.T) {
true,
1 * time.Second,
database.StringArray{"additional.origin"},
// saml config
nil,
nil,
nil,
nil,
},
},
),
@@ -1038,6 +1237,68 @@ func Test_AppPrepare(t *testing.T) {
AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"},
},
},
}, {
name: "prepareAppQuery saml app",
prepare: prepareAppQuery,
want: want{
sqlExpectations: mockQueries(
expectedAppQuery,
appCols,
[][]driver.Value{
{
"app-id",
"app-name",
"project-id",
testNow,
testNow,
"ro",
domain.AppStateActive,
uint64(20211109),
// api config
nil,
nil,
nil,
// oidc config
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
// saml config
"app-id",
"https://test.com/saml/metadata",
[]byte("<?xml version=\"1.0\"?>\n<md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\"\n validUntil=\"2022-08-26T14:08:16Z\"\n cacheDuration=\"PT604800S\"\n entityID=\"https://test.com/saml/metadata\">\n <md:SPSSODescriptor AuthnRequestsSigned=\"false\" WantAssertionsSigned=\"false\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>\n <md:AssertionConsumerService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n Location=\"https://test.com/saml/acs\"\n index=\"1\" />\n \n </md:SPSSODescriptor>\n</md:EntityDescriptor>"),
"https://test.com/saml/metadata",
},
},
),
},
object: &App{
ID: "app-id",
CreationDate: testNow,
ChangeDate: testNow,
ResourceOwner: "ro",
State: domain.AppStateActive,
Sequence: 20211109,
Name: "app-name",
ProjectID: "project-id",
SAMLConfig: &SAMLApp{
Metadata: []byte("<?xml version=\"1.0\"?>\n<md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\"\n validUntil=\"2022-08-26T14:08:16Z\"\n cacheDuration=\"PT604800S\"\n entityID=\"https://test.com/saml/metadata\">\n <md:SPSSODescriptor AuthnRequestsSigned=\"false\" WantAssertionsSigned=\"false\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>\n <md:AssertionConsumerService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n Location=\"https://test.com/saml/acs\"\n index=\"1\" />\n \n </md:SPSSODescriptor>\n</md:EntityDescriptor>"),
MetadataURL: "https://test.com/saml/metadata",
EntityID: "https://test.com/saml/metadata",
},
},
},
{
name: "prepareAppQuery oidc app IsDevMode inactive",
@@ -1077,6 +1338,11 @@ func Test_AppPrepare(t *testing.T) {
true,
1 * time.Second,
database.StringArray{"additional.origin"},
// saml config
nil,
nil,
nil,
nil,
},
},
),
@@ -1149,6 +1415,11 @@ func Test_AppPrepare(t *testing.T) {
true,
1 * time.Second,
database.StringArray{"additional.origin"},
// saml config
nil,
nil,
nil,
nil,
},
},
),
@@ -1221,6 +1492,11 @@ func Test_AppPrepare(t *testing.T) {
true,
1 * time.Second,
database.StringArray{"additional.origin"},
// saml config
nil,
nil,
nil,
nil,
},
},
),
@@ -1293,6 +1569,11 @@ func Test_AppPrepare(t *testing.T) {
false,
1 * time.Second,
database.StringArray{"additional.origin"},
// saml config
nil,
nil,
nil,
nil,
},
},
),

View File

@@ -0,0 +1,156 @@
package query
import (
"context"
"database/sql"
"time"
sq "github.com/Masterminds/squirrel"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/query/projection"
)
type Certificate interface {
Key
Expiry() time.Time
Key() *crypto.CryptoValue
Certificate() []byte
}
type Certificates struct {
SearchResponse
Certificates []Certificate
}
type rsaCertificate struct {
key
expiry time.Time
privateKey *crypto.CryptoValue
certificate []byte
}
func (c *rsaCertificate) Expiry() time.Time {
return c.expiry
}
func (c *rsaCertificate) Key() *crypto.CryptoValue {
return c.privateKey
}
func (c *rsaCertificate) Certificate() []byte {
return c.certificate
}
var (
certificateTable = table{
name: projection.CertificateTable,
}
CertificateColID = Column{
name: projection.CertificateColumnID,
table: certificateTable,
}
CertificateColExpiry = Column{
name: projection.CertificateColumnExpiry,
table: certificateTable,
}
CertificateColCertificate = Column{
name: projection.CertificateColumnCertificate,
table: certificateTable,
}
)
func (q *Queries) ActiveCertificates(ctx context.Context, t time.Time, usage domain.KeyUsage) (*Certificates, error) {
query, scan := prepareCertificateQuery()
if t.IsZero() {
t = time.Now()
}
stmt, args, err := query.Where(
sq.And{
sq.Eq{
KeyColInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
KeyColUse.identifier(): usage,
},
sq.Gt{
CertificateColExpiry.identifier(): t,
},
sq.Gt{
KeyPrivateColExpiry.identifier(): t,
},
}).OrderBy(KeyPrivateColExpiry.identifier()).ToSql()
if err != nil {
return nil, errors.ThrowInternal(err, "QUERY-SDfkg", "Errors.Query.SQLStatement")
}
rows, err := q.client.QueryContext(ctx, stmt, args...)
if err != nil {
return nil, errors.ThrowInternal(err, "QUERY-Sgan4", "Errors.Internal")
}
keys, err := scan(rows)
if err != nil {
return nil, err
}
keys.LatestSequence, err = q.latestSequence(ctx, keyTable)
if !errors.IsNotFound(err) {
return keys, err
}
return keys, nil
}
func prepareCertificateQuery() (sq.SelectBuilder, func(*sql.Rows) (*Certificates, error)) {
return sq.Select(
KeyColID.identifier(),
KeyColCreationDate.identifier(),
KeyColChangeDate.identifier(),
KeyColSequence.identifier(),
KeyColResourceOwner.identifier(),
KeyColAlgorithm.identifier(),
KeyColUse.identifier(),
CertificateColExpiry.identifier(),
CertificateColCertificate.identifier(),
KeyPrivateColKey.identifier(),
countColumn.identifier(),
).From(keyTable.identifier()).
LeftJoin(join(CertificateColID, KeyColID)).
LeftJoin(join(KeyPrivateColID, KeyColID)).
PlaceholderFormat(sq.Dollar),
func(rows *sql.Rows) (*Certificates, error) {
certificates := make([]Certificate, 0)
var count uint64
for rows.Next() {
k := new(rsaCertificate)
err := rows.Scan(
&k.id,
&k.creationDate,
&k.changeDate,
&k.sequence,
&k.resourceOwner,
&k.algorithm,
&k.use,
&k.expiry,
&k.certificate,
&k.privateKey,
&count,
)
if err != nil {
return nil, err
}
certificates = append(certificates, k)
}
if err := rows.Close(); err != nil {
return nil, errors.ThrowInternal(err, "QUERY-rKd6k", "Errors.Query.CloseRows")
}
return &Certificates{
Certificates: certificates,
SearchResponse: SearchResponse{
Count: count,
},
}, nil
}
}

View File

@@ -0,0 +1,169 @@
package query
import (
"database/sql"
"database/sql/driver"
"errors"
"fmt"
"regexp"
"testing"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
errs "github.com/zitadel/zitadel/internal/errors"
)
func Test_CertificatePrepares(t *testing.T) {
type want struct {
sqlExpectations sqlExpectation
err checkErr
}
tests := []struct {
name string
prepare interface{}
want want
object interface{}
}{
{
name: "prepareCertificateQuery no result",
prepare: prepareCertificateQuery,
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(`SELECT projections.keys3.id,`+
` projections.keys3.creation_date,`+
` projections.keys3.change_date,`+
` projections.keys3.sequence,`+
` projections.keys3.resource_owner,`+
` projections.keys3.algorithm,`+
` projections.keys3.use,`+
` projections.keys3_certificate.expiry,`+
` projections.keys3_certificate.certificate,`+
` projections.keys3_private.key,`+
` COUNT(*) OVER ()`+
` FROM projections.keys3`+
` LEFT JOIN projections.keys3_certificate ON projections.keys3.id = projections.keys3_certificate.id`+
` LEFT JOIN projections.keys3_private ON projections.keys3.id = projections.keys3_private.id`),
nil,
nil,
),
err: func(err error) (error, bool) {
if !errs.IsNotFound(err) {
return fmt.Errorf("err should be zitadel.NotFoundError got: %w", err), false
}
return nil, true
},
},
object: &Certificates{Certificates: []Certificate{}},
},
{
name: "prepareCertificateQuery found",
prepare: prepareCertificateQuery,
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(`SELECT projections.keys3.id,`+
` projections.keys3.creation_date,`+
` projections.keys3.change_date,`+
` projections.keys3.sequence,`+
` projections.keys3.resource_owner,`+
` projections.keys3.algorithm,`+
` projections.keys3.use,`+
` projections.keys3_certificate.expiry,`+
` projections.keys3_certificate.certificate,`+
` projections.keys3_private.key,`+
` COUNT(*) OVER ()`+
` FROM projections.keys3`+
` LEFT JOIN projections.keys3_certificate ON projections.keys3.id = projections.keys3_certificate.id`+
` LEFT JOIN projections.keys3_private ON projections.keys3.id = projections.keys3_private.id`),
[]string{
"id",
"creation_date",
"change_date",
"sequence",
"resource_owner",
"algorithm",
"use",
"expiry",
"certificate",
"key",
"count",
},
[][]driver.Value{
{
"key-id",
testNow,
testNow,
uint64(20211109),
"ro",
"",
1,
testNow,
[]byte(`privateKey`),
[]byte(`{"Algorithm": "enc", "Crypted": "cHJpdmF0ZUtleQ==", "CryptoType": 0, "KeyID": "id"}`),
},
},
),
},
object: &Certificates{
SearchResponse: SearchResponse{
Count: 1,
},
Certificates: []Certificate{
&rsaCertificate{
key: key{
id: "key-id",
creationDate: testNow,
changeDate: testNow,
sequence: 20211109,
resourceOwner: "ro",
algorithm: "",
use: domain.KeyUsageSAMLMetadataSigning,
},
expiry: testNow,
certificate: []byte("privateKey"),
privateKey: &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("privateKey"),
},
},
},
},
},
{
name: "prepareCertificateQuery sql err",
prepare: prepareCertificateQuery,
want: want{
sqlExpectations: mockQueryErr(
regexp.QuoteMeta(`SELECT projections.keys3.id,`+
` projections.keys3.creation_date,`+
` projections.keys3.change_date,`+
` projections.keys3.sequence,`+
` projections.keys3.resource_owner,`+
` projections.keys3.algorithm,`+
` projections.keys3.use,`+
` projections.keys3_certificate.expiry,`+
` projections.keys3_certificate.certificate,`+
` projections.keys3_private.key,`+
` COUNT(*) OVER ()`+
` FROM projections.keys3`+
` LEFT JOIN projections.keys3_certificate ON projections.keys3.id = projections.keys3_certificate.id`+
` LEFT JOIN projections.keys3_private ON projections.keys3.id = projections.keys3_private.id`),
sql.ErrConnDone,
),
err: func(err error) (error, bool) {
if !errors.Is(err, sql.ErrConnDone) {
return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false
}
return nil, true
},
},
object: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assertPrepare(t, tt.prepare, tt.object, tt.want.sqlExpectations, tt.want.err)
})
}
}

View File

@@ -200,7 +200,10 @@ func (q *Queries) ActivePublicKeys(ctx context.Context, t time.Time) (*PublicKey
return nil, err
}
keys.LatestSequence, err = q.latestSequence(ctx, keyTable)
return keys, err
if !errors.IsNotFound(err) {
return keys, err
}
return keys, nil
}
func (q *Queries) ActivePrivateSigningKey(ctx context.Context, t time.Time) (*PrivateKeys, error) {

View File

@@ -31,18 +31,18 @@ func Test_KeyPrepares(t *testing.T) {
prepare: preparePublicKeysQuery,
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(`SELECT projections.keys2.id,`+
` projections.keys2.creation_date,`+
` projections.keys2.change_date,`+
` projections.keys2.sequence,`+
` projections.keys2.resource_owner,`+
` projections.keys2.algorithm,`+
` projections.keys2.use,`+
` projections.keys2_public.expiry,`+
` projections.keys2_public.key,`+
regexp.QuoteMeta(`SELECT projections.keys3.id,`+
` projections.keys3.creation_date,`+
` projections.keys3.change_date,`+
` projections.keys3.sequence,`+
` projections.keys3.resource_owner,`+
` projections.keys3.algorithm,`+
` projections.keys3.use,`+
` projections.keys3_public.expiry,`+
` projections.keys3_public.key,`+
` COUNT(*) OVER ()`+
` FROM projections.keys2`+
` LEFT JOIN projections.keys2_public ON projections.keys2.id = projections.keys2_public.id`),
` FROM projections.keys3`+
` LEFT JOIN projections.keys3_public ON projections.keys3.id = projections.keys3_public.id`),
nil,
nil,
),
@@ -60,18 +60,18 @@ func Test_KeyPrepares(t *testing.T) {
prepare: preparePublicKeysQuery,
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(`SELECT projections.keys2.id,`+
` projections.keys2.creation_date,`+
` projections.keys2.change_date,`+
` projections.keys2.sequence,`+
` projections.keys2.resource_owner,`+
` projections.keys2.algorithm,`+
` projections.keys2.use,`+
` projections.keys2_public.expiry,`+
` projections.keys2_public.key,`+
regexp.QuoteMeta(`SELECT projections.keys3.id,`+
` projections.keys3.creation_date,`+
` projections.keys3.change_date,`+
` projections.keys3.sequence,`+
` projections.keys3.resource_owner,`+
` projections.keys3.algorithm,`+
` projections.keys3.use,`+
` projections.keys3_public.expiry,`+
` projections.keys3_public.key,`+
` COUNT(*) OVER ()`+
` FROM projections.keys2`+
` LEFT JOIN projections.keys2_public ON projections.keys2.id = projections.keys2_public.id`),
` FROM projections.keys3`+
` LEFT JOIN projections.keys3_public ON projections.keys3.id = projections.keys3_public.id`),
[]string{
"id",
"creation_date",
@@ -128,18 +128,18 @@ func Test_KeyPrepares(t *testing.T) {
prepare: preparePublicKeysQuery,
want: want{
sqlExpectations: mockQueryErr(
regexp.QuoteMeta(`SELECT projections.keys2.id,`+
` projections.keys2.creation_date,`+
` projections.keys2.change_date,`+
` projections.keys2.sequence,`+
` projections.keys2.resource_owner,`+
` projections.keys2.algorithm,`+
` projections.keys2.use,`+
` projections.keys2_public.expiry,`+
` projections.keys2_public.key,`+
regexp.QuoteMeta(`SELECT projections.keys3.id,`+
` projections.keys3.creation_date,`+
` projections.keys3.change_date,`+
` projections.keys3.sequence,`+
` projections.keys3.resource_owner,`+
` projections.keys3.algorithm,`+
` projections.keys3.use,`+
` projections.keys3_public.expiry,`+
` projections.keys3_public.key,`+
` COUNT(*) OVER ()`+
` FROM projections.keys2`+
` LEFT JOIN projections.keys2_public ON projections.keys2.id = projections.keys2_public.id`),
` FROM projections.keys3`+
` LEFT JOIN projections.keys3_public ON projections.keys3.id = projections.keys3_public.id`),
sql.ErrConnDone,
),
err: func(err error) (error, bool) {
@@ -156,18 +156,18 @@ func Test_KeyPrepares(t *testing.T) {
prepare: preparePrivateKeysQuery,
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(`SELECT projections.keys2.id,`+
` projections.keys2.creation_date,`+
` projections.keys2.change_date,`+
` projections.keys2.sequence,`+
` projections.keys2.resource_owner,`+
` projections.keys2.algorithm,`+
` projections.keys2.use,`+
` projections.keys2_private.expiry,`+
` projections.keys2_private.key,`+
regexp.QuoteMeta(`SELECT projections.keys3.id,`+
` projections.keys3.creation_date,`+
` projections.keys3.change_date,`+
` projections.keys3.sequence,`+
` projections.keys3.resource_owner,`+
` projections.keys3.algorithm,`+
` projections.keys3.use,`+
` projections.keys3_private.expiry,`+
` projections.keys3_private.key,`+
` COUNT(*) OVER ()`+
` FROM projections.keys2`+
` LEFT JOIN projections.keys2_private ON projections.keys2.id = projections.keys2_private.id`),
` FROM projections.keys3`+
` LEFT JOIN projections.keys3_private ON projections.keys3.id = projections.keys3_private.id`),
nil,
nil,
),
@@ -185,18 +185,18 @@ func Test_KeyPrepares(t *testing.T) {
prepare: preparePrivateKeysQuery,
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(`SELECT projections.keys2.id,`+
` projections.keys2.creation_date,`+
` projections.keys2.change_date,`+
` projections.keys2.sequence,`+
` projections.keys2.resource_owner,`+
` projections.keys2.algorithm,`+
` projections.keys2.use,`+
` projections.keys2_private.expiry,`+
` projections.keys2_private.key,`+
regexp.QuoteMeta(`SELECT projections.keys3.id,`+
` projections.keys3.creation_date,`+
` projections.keys3.change_date,`+
` projections.keys3.sequence,`+
` projections.keys3.resource_owner,`+
` projections.keys3.algorithm,`+
` projections.keys3.use,`+
` projections.keys3_private.expiry,`+
` projections.keys3_private.key,`+
` COUNT(*) OVER ()`+
` FROM projections.keys2`+
` LEFT JOIN projections.keys2_private ON projections.keys2.id = projections.keys2_private.id`),
` FROM projections.keys3`+
` LEFT JOIN projections.keys3_private ON projections.keys3.id = projections.keys3_private.id`),
[]string{
"id",
"creation_date",
@@ -255,18 +255,18 @@ func Test_KeyPrepares(t *testing.T) {
prepare: preparePrivateKeysQuery,
want: want{
sqlExpectations: mockQueryErr(
regexp.QuoteMeta(`SELECT projections.keys2.id,`+
` projections.keys2.creation_date,`+
` projections.keys2.change_date,`+
` projections.keys2.sequence,`+
` projections.keys2.resource_owner,`+
` projections.keys2.algorithm,`+
` projections.keys2.use,`+
` projections.keys2_private.expiry,`+
` projections.keys2_private.key,`+
regexp.QuoteMeta(`SELECT projections.keys3.id,`+
` projections.keys3.creation_date,`+
` projections.keys3.change_date,`+
` projections.keys3.sequence,`+
` projections.keys3.resource_owner,`+
` projections.keys3.algorithm,`+
` projections.keys3.use,`+
` projections.keys3_private.expiry,`+
` projections.keys3_private.key,`+
` COUNT(*) OVER ()`+
` FROM projections.keys2`+
` LEFT JOIN projections.keys2_private ON projections.keys2.id = projections.keys2_private.id`),
` FROM projections.keys3`+
` LEFT JOIN projections.keys3_private ON projections.keys3.id = projections.keys3_private.id`),
sql.ErrConnDone,
),
err: func(err error) (error, bool) {

View File

@@ -13,9 +13,10 @@ import (
)
const (
AppProjectionTable = "projections.apps2"
AppProjectionTable = "projections.apps3"
AppAPITable = AppProjectionTable + "_" + appAPITableSuffix
AppOIDCTable = AppProjectionTable + "_" + appOIDCTableSuffix
AppSAMLTable = AppProjectionTable + "_" + appSAMLTableSuffix
AppColumnID = "id"
AppColumnName = "name"
@@ -53,6 +54,13 @@ const (
AppOIDCConfigColumnIDTokenUserinfoAssertion = "id_token_userinfo_assertion"
AppOIDCConfigColumnClockSkew = "clock_skew"
AppOIDCConfigColumnAdditionalOrigins = "additional_origins"
appSAMLTableSuffix = "saml_configs"
AppSAMLConfigColumnAppID = "app_id"
AppSAMLConfigColumnInstanceID = "instance_id"
AppSAMLConfigColumnEntityID = "entity_id"
AppSAMLConfigColumnMetadata = "metadata"
AppSAMLConfigColumnMetadataURL = "metadata_url"
)
type appProjection struct {
@@ -116,6 +124,18 @@ func newAppProjection(ctx context.Context, config crdb.StatementHandlerConfig) *
crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys("fk_oidc_ref_apps")),
crdb.WithIndex(crdb.NewIndex("oidc_client_id_idx", []string{AppOIDCConfigColumnClientID})),
),
crdb.NewSuffixedTable([]*crdb.Column{
crdb.NewColumn(AppSAMLConfigColumnAppID, crdb.ColumnTypeText),
crdb.NewColumn(AppSAMLConfigColumnInstanceID, crdb.ColumnTypeText),
crdb.NewColumn(AppSAMLConfigColumnEntityID, crdb.ColumnTypeText),
crdb.NewColumn(AppSAMLConfigColumnMetadata, crdb.ColumnTypeBytes),
crdb.NewColumn(AppSAMLConfigColumnMetadataURL, crdb.ColumnTypeText),
},
crdb.NewPrimaryKey(AppSAMLConfigColumnInstanceID, AppSAMLConfigColumnAppID),
appSAMLTableSuffix,
crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys("fk_saml_ref_apps")),
crdb.WithIndex(crdb.NewIndex("saml_entity_id_idx", []string{AppSAMLConfigColumnEntityID})),
),
)
p.StatementHandler = crdb.NewStatementHandler(ctx, config)
return p
@@ -174,6 +194,14 @@ func (p *appProjection) reducers() []handler.AggregateReducer {
Event: project.OIDCConfigSecretChangedType,
Reduce: p.reduceOIDCConfigSecretChanged,
},
{
Event: project.SAMLConfigAddedType,
Reduce: p.reduceSAMLConfigAdded,
},
{
Event: project.SAMLConfigChangedType,
Reduce: p.reduceSAMLConfigChanged,
},
},
},
}
@@ -535,3 +563,77 @@ func (p *appProjection) reduceOIDCConfigSecretChanged(event eventstore.Event) (*
),
), nil
}
func (p *appProjection) reduceSAMLConfigAdded(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*project.SAMLConfigAddedEvent)
if !ok {
return nil, errors.ThrowInvalidArgument(nil, "HANDL-GMHU1", "reduce.wrong.event.type")
}
return crdb.NewMultiStatement(
e,
crdb.AddCreateStatement(
[]handler.Column{
handler.NewCol(AppSAMLConfigColumnAppID, e.AppID),
handler.NewCol(AppSAMLConfigColumnInstanceID, e.Aggregate().InstanceID),
handler.NewCol(AppSAMLConfigColumnEntityID, e.EntityID),
handler.NewCol(AppSAMLConfigColumnMetadata, e.Metadata),
handler.NewCol(AppSAMLConfigColumnMetadataURL, e.MetadataURL),
},
crdb.WithTableSuffix(appSAMLTableSuffix),
),
crdb.AddUpdateStatement(
[]handler.Column{
handler.NewCol(AppColumnChangeDate, e.CreationDate()),
handler.NewCol(AppColumnSequence, e.Sequence()),
},
[]handler.Condition{
handler.NewCond(AppColumnID, e.AppID),
handler.NewCond(AppColumnInstanceID, e.Aggregate().InstanceID),
},
),
), nil
}
func (p *appProjection) reduceSAMLConfigChanged(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*project.SAMLConfigChangedEvent)
if !ok {
return nil, errors.ThrowInvalidArgument(nil, "HANDL-GMHU2", "reduce.wrong.event.type")
}
cols := make([]handler.Column, 0, 3)
if e.Metadata != nil {
cols = append(cols, handler.NewCol(AppSAMLConfigColumnMetadata, e.Metadata))
}
if e.MetadataURL != nil {
cols = append(cols, handler.NewCol(AppSAMLConfigColumnMetadataURL, *e.MetadataURL))
}
if e.EntityID != "" {
cols = append(cols, handler.NewCol(AppSAMLConfigColumnEntityID, e.EntityID))
}
if len(cols) == 0 {
return crdb.NewNoOpStatement(e), nil
}
return crdb.NewMultiStatement(
e,
crdb.AddUpdateStatement(
cols,
[]handler.Condition{
handler.NewCond(AppSAMLConfigColumnAppID, e.AppID),
handler.NewCond(AppSAMLConfigColumnInstanceID, e.Aggregate().InstanceID),
},
crdb.WithTableSuffix(appSAMLTableSuffix),
),
crdb.AddUpdateStatement(
[]handler.Column{
handler.NewCol(AppColumnChangeDate, e.CreationDate()),
handler.NewCol(AppColumnSequence, e.Sequence()),
},
[]handler.Condition{
handler.NewCond(AppColumnID, e.AppID),
handler.NewCond(AppColumnInstanceID, e.Aggregate().InstanceID),
},
),
), nil
}

View File

@@ -44,7 +44,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.apps2 (id, name, project_id, creation_date, change_date, resource_owner, instance_id, state, sequence) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedStmt: "INSERT INTO projections.apps3 (id, name, project_id, creation_date, change_date, resource_owner, instance_id, state, sequence) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedArgs: []interface{}{
"app-id",
"my-app",
@@ -82,7 +82,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.apps2 SET (name, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedStmt: "UPDATE projections.apps3 SET (name, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{
"my-app",
anyArg{},
@@ -115,7 +115,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.apps2 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedStmt: "UPDATE projections.apps3 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{
domain.AppStateInactive,
anyArg{},
@@ -148,7 +148,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.apps2 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedStmt: "UPDATE projections.apps3 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{
domain.AppStateActive,
anyArg{},
@@ -181,7 +181,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM projections.apps2 WHERE (id = $1) AND (instance_id = $2)",
expectedStmt: "DELETE FROM projections.apps3 WHERE (id = $1) AND (instance_id = $2)",
expectedArgs: []interface{}{
"app-id",
"instance-id",
@@ -209,7 +209,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM projections.apps2 WHERE (project_id = $1) AND (instance_id = $2)",
expectedStmt: "DELETE FROM projections.apps3 WHERE (project_id = $1) AND (instance_id = $2)",
expectedArgs: []interface{}{
"agg-id",
"instance-id",
@@ -242,7 +242,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.apps2_api_configs (app_id, instance_id, client_id, client_secret, auth_method) VALUES ($1, $2, $3, $4, $5)",
expectedStmt: "INSERT INTO projections.apps3_api_configs (app_id, instance_id, client_id, client_secret, auth_method) VALUES ($1, $2, $3, $4, $5)",
expectedArgs: []interface{}{
"app-id",
"instance-id",
@@ -252,7 +252,7 @@ func TestAppProjection_reduces(t *testing.T) {
},
},
{
expectedStmt: "UPDATE projections.apps2 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedStmt: "UPDATE projections.apps3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -287,7 +287,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.apps2_api_configs SET (client_secret, auth_method) = ($1, $2) WHERE (app_id = $3) AND (instance_id = $4)",
expectedStmt: "UPDATE projections.apps3_api_configs SET (client_secret, auth_method) = ($1, $2) WHERE (app_id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
anyArg{},
domain.APIAuthMethodTypePrivateKeyJWT,
@@ -296,7 +296,7 @@ func TestAppProjection_reduces(t *testing.T) {
},
},
{
expectedStmt: "UPDATE projections.apps2 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedStmt: "UPDATE projections.apps3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -351,7 +351,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.apps2_api_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)",
expectedStmt: "UPDATE projections.apps3_api_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{
anyArg{},
"app-id",
@@ -359,7 +359,7 @@ func TestAppProjection_reduces(t *testing.T) {
},
},
{
expectedStmt: "UPDATE projections.apps2 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedStmt: "UPDATE projections.apps3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -407,7 +407,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.apps2_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) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)",
expectedStmt: "INSERT INTO projections.apps3_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) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)",
expectedArgs: []interface{}{
"app-id",
"instance-id",
@@ -430,7 +430,7 @@ func TestAppProjection_reduces(t *testing.T) {
},
},
{
expectedStmt: "UPDATE projections.apps2 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedStmt: "UPDATE projections.apps3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -476,7 +476,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.apps2_oidc_configs SET (version, 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) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) WHERE (app_id = $15) AND (instance_id = $16)",
expectedStmt: "UPDATE projections.apps3_oidc_configs SET (version, 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) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) WHERE (app_id = $15) AND (instance_id = $16)",
expectedArgs: []interface{}{
domain.OIDCVersionV1,
database.StringArray{"redirect.one.ch", "redirect.two.ch"},
@@ -497,7 +497,7 @@ func TestAppProjection_reduces(t *testing.T) {
},
},
{
expectedStmt: "UPDATE projections.apps2 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedStmt: "UPDATE projections.apps3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -552,7 +552,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.apps2_oidc_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)",
expectedStmt: "UPDATE projections.apps3_oidc_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{
anyArg{},
"app-id",
@@ -560,7 +560,7 @@ func TestAppProjection_reduces(t *testing.T) {
},
},
{
expectedStmt: "UPDATE projections.apps2 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedStmt: "UPDATE projections.apps3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),

View File

@@ -13,9 +13,10 @@ import (
)
const (
KeyProjectionTable = "projections.keys2"
KeyProjectionTable = "projections.keys3"
KeyPrivateTable = KeyProjectionTable + "_" + privateKeyTableSuffix
KeyPublicTable = KeyProjectionTable + "_" + publicKeyTableSuffix
CertificateTable = KeyProjectionTable + "_" + certificateTableSuffix
KeyColumnID = "id"
KeyColumnCreationDate = "creation_date"
@@ -37,14 +38,21 @@ const (
KeyPublicColumnInstanceID = "instance_id"
KeyPublicColumnExpiry = "expiry"
KeyPublicColumnKey = "key"
certificateTableSuffix = "certificate"
CertificateColumnID = "id"
CertificateColumnInstanceID = "instance_id"
CertificateColumnExpiry = "expiry"
CertificateColumnCertificate = "certificate"
)
type keyProjection struct {
crdb.StatementHandler
encryptionAlgorithm crypto.EncryptionAlgorithm
encryptionAlgorithm crypto.EncryptionAlgorithm
certEncryptionAlgorithm crypto.EncryptionAlgorithm
}
func newKeyProjection(ctx context.Context, config crdb.StatementHandlerConfig, keyEncryptionAlgorithm crypto.EncryptionAlgorithm) *keyProjection {
func newKeyProjection(ctx context.Context, config crdb.StatementHandlerConfig, keyEncryptionAlgorithm crypto.EncryptionAlgorithm, certEncryptionAlgorithm crypto.EncryptionAlgorithm) *keyProjection {
p := new(keyProjection)
config.ProjectionName = KeyProjectionTable
config.Reducers = p.reducers()
@@ -82,8 +90,19 @@ func newKeyProjection(ctx context.Context, config crdb.StatementHandlerConfig, k
publicKeyTableSuffix,
crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys("fk_public_ref_keys")),
),
crdb.NewSuffixedTable([]*crdb.Column{
crdb.NewColumn(CertificateColumnID, crdb.ColumnTypeText),
crdb.NewColumn(CertificateColumnInstanceID, crdb.ColumnTypeText),
crdb.NewColumn(CertificateColumnExpiry, crdb.ColumnTypeTimestamp),
crdb.NewColumn(CertificateColumnCertificate, crdb.ColumnTypeBytes),
},
crdb.NewPrimaryKey(CertificateColumnInstanceID, CertificateColumnID),
certificateTableSuffix,
crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys("fk_certificate_ref_keys")),
),
)
p.encryptionAlgorithm = keyEncryptionAlgorithm
p.certEncryptionAlgorithm = certEncryptionAlgorithm
p.StatementHandler = crdb.NewStatementHandler(ctx, config)
return p
@@ -98,6 +117,10 @@ func (p *keyProjection) reducers() []handler.AggregateReducer {
Event: keypair.AddedEventType,
Reduce: p.reduceKeyPairAdded,
},
{
Event: keypair.AddedCertificateEventType,
Reduce: p.reduceCertificateAdded,
},
},
},
}
@@ -151,5 +174,34 @@ func (p *keyProjection) reduceKeyPairAdded(event eventstore.Event) (*handler.Sta
crdb.WithTableSuffix(publicKeyTableSuffix),
))
}
return crdb.NewMultiStatement(e, creates...), nil
}
func (p *keyProjection) reduceCertificateAdded(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*keypair.AddedCertificateEvent)
if !ok {
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-SAbr09", "reduce.wrong.event.type %s", keypair.AddedCertificateEventType)
}
if e.Certificate.Expiry.Before(time.Now()) {
return crdb.NewNoOpStatement(e), nil
}
certificate, err := crypto.Decrypt(e.Certificate.Key, p.certEncryptionAlgorithm)
if err != nil {
return nil, errors.ThrowInternal(err, "HANDL-Dajwig2f", "cannot decrypt certificate")
}
creates := []func(eventstore.Event) crdb.Exec{crdb.AddCreateStatement(
[]handler.Column{
handler.NewCol(CertificateColumnID, e.Aggregate().ID),
handler.NewCol(CertificateColumnInstanceID, e.Aggregate().InstanceID),
handler.NewCol(CertificateColumnExpiry, e.Certificate.Expiry),
handler.NewCol(CertificateColumnCertificate, certificate),
},
crdb.WithTableSuffix(certificateTableSuffix),
)}
return crdb.NewMultiStatement(e, creates...), nil
}

View File

@@ -1,6 +1,7 @@
package projection
import (
"fmt"
"testing"
"time"
@@ -31,7 +32,7 @@ func TestKeyProjection_reduces(t *testing.T) {
event: getEvent(testEvent(
repository.EventType(keypair.AddedEventType),
keypair.AggregateType,
keypairAddedEventData(time.Now().Add(time.Hour)),
keypairAddedEventData(domain.KeyUsageSigning, time.Now().Add(time.Hour)),
), keypair.AddedEventMapper),
},
reduce: (&keyProjection{encryptionAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t))}).reduceKeyPairAdded,
@@ -43,7 +44,7 @@ func TestKeyProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.keys2 (id, creation_date, change_date, resource_owner, instance_id, sequence, algorithm, use) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
expectedStmt: "INSERT INTO projections.keys3 (id, creation_date, change_date, resource_owner, instance_id, sequence, algorithm, use) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
expectedArgs: []interface{}{
"agg-id",
anyArg{},
@@ -56,7 +57,7 @@ func TestKeyProjection_reduces(t *testing.T) {
},
},
{
expectedStmt: "INSERT INTO projections.keys2_private (id, instance_id, expiry, key) VALUES ($1, $2, $3, $4)",
expectedStmt: "INSERT INTO projections.keys3_private (id, instance_id, expiry, key) VALUES ($1, $2, $3, $4)",
expectedArgs: []interface{}{
"agg-id",
"instance-id",
@@ -70,7 +71,7 @@ func TestKeyProjection_reduces(t *testing.T) {
},
},
{
expectedStmt: "INSERT INTO projections.keys2_public (id, instance_id, expiry, key) VALUES ($1, $2, $3, $4)",
expectedStmt: "INSERT INTO projections.keys3_public (id, instance_id, expiry, key) VALUES ($1, $2, $3, $4)",
expectedArgs: []interface{}{
"agg-id",
"instance-id",
@@ -88,7 +89,7 @@ func TestKeyProjection_reduces(t *testing.T) {
event: getEvent(testEvent(
repository.EventType(keypair.AddedEventType),
keypair.AggregateType,
keypairAddedEventData(time.Now().Add(-time.Hour)),
keypairAddedEventData(domain.KeyUsageSigning, time.Now().Add(-time.Hour)),
), keypair.AddedEventMapper),
},
reduce: (&keyProjection{}).reduceKeyPairAdded,
@@ -100,6 +101,36 @@ func TestKeyProjection_reduces(t *testing.T) {
executer: &testExecuter{},
},
},
{
name: "reduceCertificateAdded",
args: args{
event: getEvent(testEvent(
repository.EventType(keypair.AddedCertificateEventType),
keypair.AggregateType,
certificateAddedEventData(domain.KeyUsageSAMLMetadataSigning, time.Now().Add(time.Hour)),
), keypair.AddedCertificateEventMapper),
},
reduce: (&keyProjection{certEncryptionAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t))}).reduceCertificateAdded,
want: wantReduce{
projection: KeyProjectionTable,
aggregateType: eventstore.AggregateType("key_pair"),
sequence: 15,
previousSequence: 10,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.keys3_certificate (id, instance_id, expiry, certificate) VALUES ($1, $2, $3, $4)",
expectedArgs: []interface{}{
"agg-id",
"instance-id",
anyArg{},
[]byte("privateKey"),
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -116,6 +147,10 @@ func TestKeyProjection_reduces(t *testing.T) {
}
}
func keypairAddedEventData(t time.Time) []byte {
return []byte(`{"algorithm": "algorithm", "usage": 0, "privateKey": {"key": {"cryptoType": 0, "algorithm": "enc", "keyID": "id", "crypted": "cHJpdmF0ZUtleQ=="}, "expiry": "` + t.Format(time.RFC3339) + `"}, "publicKey": {"key": {"cryptoType": 0, "algorithm": "enc", "keyID": "id", "crypted": "cHVibGljS2V5"}, "expiry": "` + t.Format(time.RFC3339) + `"}}`)
func keypairAddedEventData(usage domain.KeyUsage, t time.Time) []byte {
return []byte(`{"algorithm": "algorithm", "usage": ` + fmt.Sprintf("%d", usage) + `, "privateKey": {"key": {"cryptoType": 0, "algorithm": "enc", "keyID": "id", "crypted": "cHJpdmF0ZUtleQ=="}, "expiry": "` + t.Format(time.RFC3339) + `"}, "publicKey": {"key": {"cryptoType": 0, "algorithm": "enc", "keyID": "id", "crypted": "cHVibGljS2V5"}, "expiry": "` + t.Format(time.RFC3339) + `"}}`)
}
func certificateAddedEventData(usage domain.KeyUsage, t time.Time) []byte {
return []byte(`{"algorithm": "algorithm", "usage": ` + fmt.Sprintf("%d", usage) + `, "certificate": {"key": {"cryptoType": 0, "algorithm": "enc", "keyID": "id", "crypted": "cHJpdmF0ZUtleQ=="}, "expiry": "` + t.Format(time.RFC3339) + `"}}`)
}

View File

@@ -62,7 +62,7 @@ var (
NotificationsProjection interface{}
)
func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, config Config, keyEncryptionAlgorithm crypto.EncryptionAlgorithm) error {
func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, config Config, keyEncryptionAlgorithm crypto.EncryptionAlgorithm, certEncryptionAlgorithm crypto.EncryptionAlgorithm) error {
projectionConfig = crdb.StatementHandlerConfig{
ProjectionHandlerConfig: handler.ProjectionHandlerConfig{
HandlerConfig: handler.HandlerConfig{
@@ -120,7 +120,7 @@ func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, co
SMSConfigProjection = newSMSConfigProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["sms_config"]))
OIDCSettingsProjection = newOIDCSettingsProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["oidc_settings"]))
DebugNotificationProviderProjection = newDebugNotificationProviderProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["debug_notification_provider"]))
KeyProjection = newKeyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["keys"]), keyEncryptionAlgorithm)
KeyProjection = newKeyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["keys"]), keyEncryptionAlgorithm, certEncryptionAlgorithm)
return nil
}

View File

@@ -4,14 +4,15 @@ import (
"context"
"database/sql"
"fmt"
sd "github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/domain"
"net/http"
"sync"
"github.com/rakyll/statik/fs"
"golang.org/x/text/language"
sd "github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/eventstore"
@@ -42,7 +43,7 @@ type Queries struct {
multifactors domain.MultifactorConfigs
}
func StartQueries(ctx context.Context, es *eventstore.Eventstore, sqlClient *sql.DB, projections projection.Config, defaults sd.SystemDefaults, idpConfigEncryption, otpEncryption, keyEncryptionAlgorithm crypto.EncryptionAlgorithm, zitadelRoles []authz.RoleMapping) (repo *Queries, err error) {
func StartQueries(ctx context.Context, es *eventstore.Eventstore, sqlClient *sql.DB, projections projection.Config, defaults sd.SystemDefaults, idpConfigEncryption, otpEncryption, keyEncryptionAlgorithm crypto.EncryptionAlgorithm, certEncryptionAlgorithm crypto.EncryptionAlgorithm, zitadelRoles []authz.RoleMapping) (repo *Queries, err error) {
statikLoginFS, err := fs.NewWithNamespace("login")
if err != nil {
return nil, fmt.Errorf("unable to start login statik dir")
@@ -79,7 +80,7 @@ func StartQueries(ctx context.Context, es *eventstore.Eventstore, sqlClient *sql
},
}
err = projection.Start(ctx, sqlClient, es, projections, keyEncryptionAlgorithm)
err = projection.Start(ctx, sqlClient, es, projections, keyEncryptionAlgorithm, certEncryptionAlgorithm)
if err != nil {
return nil, err
}