diff --git a/e2e/config/host.docker.internal/zitadel.yaml b/e2e/config/host.docker.internal/zitadel.yaml index 88080e1de1..6808269531 100644 --- a/e2e/config/host.docker.internal/zitadel.yaml +++ b/e2e/config/host.docker.internal/zitadel.yaml @@ -9,19 +9,27 @@ Telemetry: ExternalDomain: host.docker.internal ExternalSecure: false +TLS: + Enabled: false + Database: cockroach: # This makes the e2e config reusable with an out-of-docker zitadel process and an /etc/hosts entry Host: host.docker.internal -TLS: - Enabled: false - FirstInstance: Org: Human: PasswordChangeRequired: false +DefaultInstance: + Org: + Human: + PasswordChangeRequired: false + Password: "Password1!" + LoginPolicy: + MfaInitSkipLifetime: "0" + LogStore: Access: Database: @@ -50,10 +58,6 @@ Projections: Telemetry: RequeueEvery: 1s -DefaultInstance: - LoginPolicy: - MfaInitSkipLifetime: "0" - SystemAPIUsers: - cypress: KeyData: "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF6aStGRlNKTDdmNXl3NEtUd3pnTQpQMzRlUEd5Y20vTStrVDBNN1Y0Q2d4NVYzRWFESXZUUUtUTGZCYUVCNDV6YjlMdGpJWHpEdzByWFJvUzJoTzZ0CmgrQ1lRQ3ozS0N2aDA5QzBJenhaaUIySVMzSC9hVCs1Qng5RUZZK3ZuQWtaamNjYnlHNVlOUnZtdE9sbnZJZUkKSDdxWjB0RXdrUGZGNUdFWk5QSlB0bXkzVUdWN2lvZmRWUVMxeFJqNzMrYU13NXJ2SDREOElkeWlBQzNWZWtJYgpwdDBWajBTVVgzRHdLdG9nMzM3QnpUaVBrM2FYUkYwc2JGaFFvcWRKUkk4TnFnWmpDd2pxOXlmSTV0eXhZc3duCitKR3pIR2RIdlczaWRPRGxtd0V0NUsycGFzaVJJV0syT0dmcSt3MEVjbHRRSGFidXFFUGdabG1oQ2tSZE5maXgKQndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==" diff --git a/e2e/config/localhost/zitadel.yaml b/e2e/config/localhost/zitadel.yaml index ebc4d6e1c1..ad9d0692d3 100644 --- a/e2e/config/localhost/zitadel.yaml +++ b/e2e/config/localhost/zitadel.yaml @@ -10,19 +10,27 @@ Telemetry: ExternalDomain: localhost ExternalSecure: false +TLS: + Enabled: false + Database: cockroach: # This makes the e2e config reusable with an out-of-docker zitadel process and an /etc/hosts entry Host: host.docker.internal -TLS: - Enabled: false - FirstInstance: Org: Human: PasswordChangeRequired: false +DefaultInstance: + Org: + Human: + PasswordChangeRequired: false + Password: "Password1!" + LoginPolicy: + MfaInitSkipLifetime: "0" + LogStore: Access: Database: @@ -51,10 +59,6 @@ Projections: Telemetry: RequeueEvery: 30s -DefaultInstance: - LoginPolicy: - MfaInitSkipLifetime: "0" - SystemAPIUsers: - cypress: KeyData: "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF6aStGRlNKTDdmNXl3NEtUd3pnTQpQMzRlUEd5Y20vTStrVDBNN1Y0Q2d4NVYzRWFESXZUUUtUTGZCYUVCNDV6YjlMdGpJWHpEdzByWFJvUzJoTzZ0CmgrQ1lRQ3ozS0N2aDA5QzBJenhaaUIySVMzSC9hVCs1Qng5RUZZK3ZuQWtaamNjYnlHNVlOUnZtdE9sbnZJZUkKSDdxWjB0RXdrUGZGNUdFWk5QSlB0bXkzVUdWN2lvZmRWUVMxeFJqNzMrYU13NXJ2SDREOElkeWlBQzNWZWtJYgpwdDBWajBTVVgzRHdLdG9nMzM3QnpUaVBrM2FYUkYwc2JGaFFvcWRKUkk4TnFnWmpDd2pxOXlmSTV0eXhZc3duCitKR3pIR2RIdlczaWRPRGxtd0V0NUsycGFzaVJJV0syT0dmcSt3MEVjbHRRSGFidXFFUGdabG1oQ2tSZE5maXgKQndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==" diff --git a/internal/command/milestone.go b/internal/command/milestone.go index 70f119459b..f01ec6d158 100644 --- a/internal/command/milestone.go +++ b/internal/command/milestone.go @@ -17,6 +17,6 @@ func (c *Commands) MilestonePushed( if err != nil { return err } - _, err = c.eventstore.Push(ctx, milestone.NewPushedEvent(ctx, milestone.NewAggregate(ctx, id), msType, endpoints, primaryDomain)) + _, err = c.eventstore.Push(ctx, milestone.NewPushedEvent(ctx, milestone.NewAggregate(ctx, id), msType, endpoints, primaryDomain, c.externalDomain)) return err } diff --git a/internal/eventstore/handler/crdb/handler_stmt.go b/internal/eventstore/handler/crdb/handler_stmt.go index 02cbf70b71..60c703f8f4 100644 --- a/internal/eventstore/handler/crdb/handler_stmt.go +++ b/internal/eventstore/handler/crdb/handler_stmt.go @@ -137,9 +137,9 @@ func (h *StatementHandler) SearchQuery(ctx context.Context, instanceIDs []string } queryBuilder. AddQuery(). + AggregateTypes(aggregateType). SequenceGreater(seq). - InstanceID(instanceID). - AggregateTypes(aggregateType) + InstanceID(instanceID) } } diff --git a/internal/eventstore/handler/crdb/statement.go b/internal/eventstore/handler/crdb/statement.go index 0f2747ae6c..8f50b7d7a0 100644 --- a/internal/eventstore/handler/crdb/statement.go +++ b/internal/eventstore/handler/crdb/statement.go @@ -236,12 +236,6 @@ func AddDeleteStatement(conditions []handler.Condition, opts ...execOption) func } } -func AddCopyStatement(conflict, from, to []handler.Column, conditions []handler.Condition, opts ...execOption) func(eventstore.Event) Exec { - return func(event eventstore.Event) Exec { - return NewCopyStatement(event, conflict, from, to, conditions, opts...).Execute - } -} - func NewArrayAppendCol(column string, value interface{}) handler.Column { return handler.Column{ Name: column, @@ -286,42 +280,31 @@ func NewCopyCol(column, from string) handler.Column { } } -func NewIsNullCond(column string) handler.Condition { - return handler.Condition{ - Name: column, - ParameterOpt: func(string) string { - return " IS NULL" - }, - } -} - -func NewNotEqualCond(column, value string) handler.Condition { - return handler.Condition{ - Name: column, - Value: value, - ParameterOpt: func(param string) string { - return fmt.Sprintf(" != %s", param) - }, - } -} - func NewLessThanCond(column string, value interface{}) handler.Condition { - return handler.Condition{ - Name: column, - Value: value, - ParameterOpt: func(placeholder string) string { - return " < " + placeholder - }, + return func(param string) (string, interface{}) { + return column + " < " + param, value } } -func NewContainsCond(column string, value interface{}) handler.Condition { - return handler.Condition{ - Name: column, - Value: value, - ParameterOpt: func(placeholder string) string { - return fmt.Sprintf(" @> ARRAY[%s]", placeholder) - }, +func NewIsNullCond(column string) handler.Condition { + return func(param string) (string, interface{}) { + return column + " IS NULL", nil + } +} + +// NewTextArrayContainsCond returns a handler.Condition that checks if the column that stores an array of text contains the given value +func NewTextArrayContainsCond(column string, value string) handler.Condition { + return func(param string) (string, interface{}) { + return column + " @> " + param, database.StringArray{value} + } +} + +// Not is a function instead of a method, so that calling it is well readable +// For example conditions := []handler.Condition{ Not(NewTextArrayContainsCond())} +func Not(condition handler.Condition) handler.Condition { + return func(param string) (string, interface{}) { + cond, value := condition(param) + return "NOT ( " + cond + " )", value } } @@ -330,7 +313,7 @@ func NewContainsCond(column string, value interface{}) handler.Condition { // if the value of a col is empty the data will be copied from the selected row // if the value of a col is not empty the data will be set by the static value // conds represent the conditions for the selection subquery -func NewCopyStatement(event eventstore.Event, conflictCols, from, to []handler.Column, conds []handler.Condition, opts ...execOption) *handler.Statement { +func NewCopyStatement(event eventstore.Event, conflictCols, from, to, whereEqual []handler.Column, opts ...execOption) *handler.Statement { columnNames := make([]string, len(to)) selectColumns := make([]string, len(from)) updateColumns := make([]string, len(columnNames)) @@ -350,8 +333,8 @@ func NewCopyStatement(event eventstore.Event, conflictCols, from, to []handler.C } - wheres := make([]string, len(conds)) - for i, cond := range conds { + wheres := make([]string, len(whereEqual)) + for i, cond := range whereEqual { argCounter++ wheres[i] = "copy_table." + cond.Name + " = $" + strconv.Itoa(argCounter) args = append(args, cond.Value) @@ -370,7 +353,7 @@ func NewCopyStatement(event eventstore.Event, conflictCols, from, to []handler.C config.err = handler.ErrNoValues } - if len(conds) == 0 { + if len(whereEqual) == 0 { config.err = handler.ErrNoCondition } @@ -424,21 +407,16 @@ func columnsToQuery(cols []handler.Column) (names []string, parameters []string, return names, parameters, values[:parameterIndex] } -func conditionsToWhere(cols []handler.Condition, paramOffset int) (wheres []string, values []interface{}) { - wheres = make([]string, len(cols)) - values = make([]interface{}, 0, len(cols)) - - for i, col := range cols { - param := "$" + strconv.Itoa(i+1+paramOffset) - wheres[i] = "(" + col.Name + " = " + param + ")" - if col.ParameterOpt != nil { - wheres[i] = "(" + col.Name + col.ParameterOpt(param) + ")" - } - if col.Value != nil { - values = append(values, col.Value) +func conditionsToWhere(conditions []handler.Condition, paramOffset int) (wheres []string, values []interface{}) { + wheres = make([]string, len(conditions)) + values = make([]interface{}, 0, len(conditions)) + for i, conditionFunc := range conditions { + condition, value := conditionFunc("$" + strconv.Itoa(i+1+paramOffset)) + wheres[i] = fmt.Sprintf("(%s)", condition) + if value != nil { + values = append(values, value) } } - return wheres, values } diff --git a/internal/eventstore/handler/handler_projection.go b/internal/eventstore/handler/handler_projection.go index d7aed5ef3b..c954cf1bc5 100644 --- a/internal/eventstore/handler/handler_projection.go +++ b/internal/eventstore/handler/handler_projection.go @@ -118,10 +118,6 @@ func (h *ProjectionHandler) Trigger(ctx context.Context, instances ...string) er if len(instances) > 0 { ids = instances } - return h.processEvents(ctx, ids...) -} - -func (h *ProjectionHandler) processEvents(ctx context.Context, ids ...string) error { for { events, hasLimitExceeded, err := h.FetchEvents(ctx, ids...) if err != nil { diff --git a/internal/eventstore/handler/statement.go b/internal/eventstore/handler/statement.go index ae7bec92e1..d61a59bbf1 100644 --- a/internal/eventstore/handler/statement.go +++ b/internal/eventstore/handler/statement.go @@ -4,6 +4,7 @@ import ( "database/sql" "encoding/json" "errors" + "fmt" "github.com/zitadel/logging" @@ -62,11 +63,10 @@ func NewJSONCol(name string, value interface{}) Column { return NewCol(name, marshalled) } -type Condition Column +type Condition func(param string) (string, interface{}) func NewCond(name string, value interface{}) Condition { - return Condition{ - Name: name, - Value: value, + return func(param string) (string, interface{}) { + return fmt.Sprintf("%s = %s", name, param), value } } diff --git a/internal/notification/handlers/telemetry_pusher.go b/internal/notification/handlers/telemetry_pusher.go index fa3f34e86d..514477f1b6 100644 --- a/internal/notification/handlers/telemetry_pusher.go +++ b/internal/notification/handlers/telemetry_pusher.go @@ -20,6 +20,7 @@ import ( "github.com/zitadel/zitadel/internal/notification/types" "github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/query/projection" + "github.com/zitadel/zitadel/internal/repository/milestone" "github.com/zitadel/zitadel/internal/repository/pseudo" ) @@ -132,6 +133,13 @@ func (t *telemetryPusher) pushMilestones(event eventstore.Event) (*handler.State func (t *telemetryPusher) pushMilestone(ctx context.Context, event *pseudo.ScheduledEvent, ms *query.Milestone) error { ctx = authz.WithInstanceID(ctx, ms.InstanceID) + alreadyHandled, err := t.queries.IsAlreadyHandled(ctx, event, map[string]interface{}{"type": ms.Type}, milestone.AggregateType, milestone.PushedEventType) + if err != nil { + return err + } + if alreadyHandled { + return nil + } for _, endpoint := range t.endpoints { if err := types.SendJSON( ctx, diff --git a/internal/query/projection/label_policy.go b/internal/query/projection/label_policy.go index b8a84d9221..7235919605 100644 --- a/internal/query/projection/label_policy.go +++ b/internal/query/projection/label_policy.go @@ -402,10 +402,10 @@ func (p *labelPolicyProjection) reduceActivated(event eventstore.Event) (*handle handler.NewCol(LabelPolicyDarkLogoURLCol, nil), handler.NewCol(LabelPolicyDarkIconURLCol, nil), }, - []handler.Condition{ - handler.NewCond(LabelPolicyIDCol, event.Aggregate().ID), - handler.NewCond(LabelPolicyStateCol, domain.LabelPolicyStatePreview), - handler.NewCond(LabelPolicyInstanceIDCol, event.Aggregate().InstanceID), + []handler.Column{ + handler.NewCol(LabelPolicyIDCol, event.Aggregate().ID), + handler.NewCol(LabelPolicyStateCol, domain.LabelPolicyStatePreview), + handler.NewCol(LabelPolicyInstanceIDCol, event.Aggregate().InstanceID), }), nil } diff --git a/internal/query/projection/milestones.go b/internal/query/projection/milestones.go index 802aac6f17..f1a665bbaf 100644 --- a/internal/query/projection/milestones.go +++ b/internal/query/projection/milestones.go @@ -248,17 +248,17 @@ func (p *milestoneProjection) reduceUserTokenAdded(event eventstore.Event) (*han } // We ignore authentications without app, for example JWT profile or PAT if e.ApplicationID != "" { - crdb.AddUpdateStatement( + statements = append(statements, crdb.AddUpdateStatement( []handler.Column{ handler.NewCol(MilestoneColumnReachedDate, event.CreationDate()), }, []handler.Condition{ handler.NewCond(MilestoneColumnInstanceID, event.Aggregate().InstanceID), handler.NewCond(MilestoneColumnType, milestone.AuthenticationSucceededOnApplication), - crdb.NewContainsCond(MilestoneColumnIgnoreClientIDs, e.ApplicationID), + crdb.Not(crdb.NewTextArrayContainsCond(MilestoneColumnIgnoreClientIDs, e.ApplicationID)), crdb.NewIsNullCond(MilestoneColumnReachedDate), }, - ) + )) } return crdb.NewMultiStatement(e, statements...), nil } diff --git a/internal/query/projection/projection.go b/internal/query/projection/projection.go index d6d31e93f3..085875b3aa 100644 --- a/internal/query/projection/projection.go +++ b/internal/query/projection/projection.go @@ -194,7 +194,7 @@ func applyCustomConfig(config crdb.StatementHandlerConfig, customConfig CustomCo // as setup and start currently create them individually, we make sure we get the right one // will be refactored when changing to new id based projections // -// NotificationsProjection is not added here, because it does not statement based / has no proprietary projection table +// Event handlers NotificationsProjection, NotificationsQuotaProjection and NotificationsProjection are not added here, because the do not statement based / have no proprietary projection table func newProjectionsList() { projections = []projection{ OrgProjection, diff --git a/internal/repository/milestone/events.go b/internal/repository/milestone/events.go index a6f45961dd..d709e89093 100644 --- a/internal/repository/milestone/events.go +++ b/internal/repository/milestone/events.go @@ -14,6 +14,7 @@ const ( type PushedEvent struct { *eventstore.BaseEvent `json:"-"` MilestoneType Type `json:"type"` + ExternalDomain string `json:"externalDomain"` PrimaryDomain string `json:"primaryDomain"` Endpoints []string `json:"endpoints"` } @@ -35,7 +36,7 @@ func NewPushedEvent( aggregate *Aggregate, msType Type, endpoints []string, - primaryDomain string, + externalDomain, primaryDomain string, ) *PushedEvent { return &PushedEvent{ BaseEvent: eventstore.NewBaseEventForPush( @@ -43,8 +44,9 @@ func NewPushedEvent( &aggregate.Aggregate, PushedEventType, ), - MilestoneType: msType, - Endpoints: endpoints, - PrimaryDomain: primaryDomain, + MilestoneType: msType, + Endpoints: endpoints, + ExternalDomain: externalDomain, + PrimaryDomain: primaryDomain, } }