diff --git a/database/query/eval_history.sql b/database/query/eval_history.sql index 90a260bf92..382300d2f1 100644 --- a/database/query/eval_history.sql +++ b/database/query/eval_history.sql @@ -141,7 +141,7 @@ SELECT s.id::uuid AS evaluation_id, AND (sqlc.narg(prev)::timestamp without time zone IS NULL OR sqlc.narg(prev) < s.most_recent_evaluation) -- inclusion filters AND (sqlc.slice(entityTypes)::entities[] IS NULL OR entity_type::entities = ANY(sqlc.slice(entityTypes)::entities[])) - AND (sqlc.slice(entityNames)::text[] IS NULL OR ere.repository_id IS NULL OR r.repo_name = ANY(sqlc.slice(entityNames)::text[])) + AND (sqlc.slice(entityNames)::text[] IS NULL OR ere.repository_id IS NULL OR CONCAT(r.repo_owner, '/', r.repo_name) = ANY(sqlc.slice(entityNames)::text[])) AND (sqlc.slice(entityNames)::text[] IS NULL OR ere.pull_request_id IS NULL OR pr.pr_number::text = ANY(sqlc.slice(entityNames)::text[])) AND (sqlc.slice(entityNames)::text[] IS NULL OR ere.artifact_id IS NULL OR a.artifact_name = ANY(sqlc.slice(entityNames)::text[])) AND (sqlc.slice(profileNames)::text[] IS NULL OR p.name = ANY(sqlc.slice(profileNames)::text[])) @@ -150,7 +150,7 @@ SELECT s.id::uuid AS evaluation_id, AND (sqlc.slice(statuses)::eval_status_types[] IS NULL OR s.status = ANY(sqlc.slice(statuses)::eval_status_types[])) -- exclusion filters AND (sqlc.slice(notEntityTypes)::entities[] IS NULL OR entity_type::entities != ANY(sqlc.slice(notEntityTypes)::entities[])) - AND (sqlc.slice(notEntityNames)::text[] IS NULL OR ere.repository_id IS NULL OR r.repo_name != ANY(sqlc.slice(notEntityNames)::text[])) + AND (sqlc.slice(notEntityNames)::text[] IS NULL OR ere.repository_id IS NULL OR CONCAT(r.repo_owner, '/', r.repo_name) != ANY(sqlc.slice(notEntityNames)::text[])) AND (sqlc.slice(notEntityNames)::text[] IS NULL OR ere.pull_request_id IS NULL OR pr.pr_number::text != ANY(sqlc.slice(notEntityNames)::text[])) AND (sqlc.slice(notEntityNames)::text[] IS NULL OR ere.artifact_id IS NULL OR a.artifact_name != ANY(sqlc.slice(notEntityNames)::text[])) AND (sqlc.slice(notProfileNames)::text[] IS NULL OR p.name != ANY(sqlc.slice(notProfileNames)::text[])) diff --git a/internal/db/eval_history.sql.go b/internal/db/eval_history.sql.go index fedddf14ef..78ed55ea01 100644 --- a/internal/db/eval_history.sql.go +++ b/internal/db/eval_history.sql.go @@ -237,7 +237,7 @@ SELECT s.id::uuid AS evaluation_id, AND ($2::timestamp without time zone IS NULL OR $2 < s.most_recent_evaluation) -- inclusion filters AND ($3::entities[] IS NULL OR entity_type::entities = ANY($3::entities[])) - AND ($4::text[] IS NULL OR ere.repository_id IS NULL OR r.repo_name = ANY($4::text[])) + AND ($4::text[] IS NULL OR ere.repository_id IS NULL OR CONCAT(r.repo_owner, '/', r.repo_name) = ANY($4::text[])) AND ($4::text[] IS NULL OR ere.pull_request_id IS NULL OR pr.pr_number::text = ANY($4::text[])) AND ($4::text[] IS NULL OR ere.artifact_id IS NULL OR a.artifact_name = ANY($4::text[])) AND ($5::text[] IS NULL OR p.name = ANY($5::text[])) @@ -246,7 +246,7 @@ SELECT s.id::uuid AS evaluation_id, AND ($8::eval_status_types[] IS NULL OR s.status = ANY($8::eval_status_types[])) -- exclusion filters AND ($9::entities[] IS NULL OR entity_type::entities != ANY($9::entities[])) - AND ($10::text[] IS NULL OR ere.repository_id IS NULL OR r.repo_name != ANY($10::text[])) + AND ($10::text[] IS NULL OR ere.repository_id IS NULL OR CONCAT(r.repo_owner, '/', r.repo_name) != ANY($10::text[])) AND ($10::text[] IS NULL OR ere.pull_request_id IS NULL OR pr.pr_number::text != ANY($10::text[])) AND ($10::text[] IS NULL OR ere.artifact_id IS NULL OR a.artifact_name != ANY($10::text[])) AND ($11::text[] IS NULL OR p.name != ANY($11::text[])) diff --git a/internal/history/models.go b/internal/history/models.go index 052f4410c8..59409f8211 100644 --- a/internal/history/models.go +++ b/internal/history/models.go @@ -18,6 +18,7 @@ import ( "encoding/base64" "errors" "fmt" + "slices" "strconv" "strings" "time" @@ -44,6 +45,13 @@ var ( ErrInvalidIdentifier = errors.New("invalid identifier") ) +var ( + allowedEntityTypes = []string{"repository", "build_environment", "artifact", "pull_request"} + allowedEvaluationStatuses = []string{"success", "failure", "error", "skipped", "pending"} + allowedRemediationStatuses = []string{"success", "failure", "error", "skipped", "not_available", "pending"} + allowedAlertStatuses = []string{"on", "off", "error", "skipped", "not_available"} +) + // Direction enumerates the direction of the Cursor. type Direction string @@ -294,6 +302,9 @@ func (filter *listEvaluationFilter) AddEntityType(entityType string) error { } else { filter.includedEntityTypes = append(filter.includedEntityTypes, entityType) } + if !slices.Contains(allowedEntityTypes, entityType) { + return fmt.Errorf("%w: entity type", ErrInvalidIdentifier) + } // Prevent filtering for both inclusion and exclusion if len(filter.includedEntityTypes) > 0 && @@ -363,6 +374,9 @@ func (filter *listEvaluationFilter) AddStatus(status string) error { } else { filter.includedStatuses = append(filter.includedStatuses, status) } + if !slices.Contains(allowedEvaluationStatuses, status) { + return fmt.Errorf("%w: status", ErrInvalidIdentifier) + } // Prevent filtering for both inclusion and exclusion if len(filter.includedStatuses) > 0 && @@ -386,6 +400,9 @@ func (filter *listEvaluationFilter) AddRemediation(remediation string) error { } else { filter.includedRemediations = append(filter.includedRemediations, remediation) } + if !slices.Contains(allowedRemediationStatuses, remediation) { + return fmt.Errorf("%w: remediation", ErrInvalidIdentifier) + } // Prevent filtering for both inclusion and exclusion if len(filter.includedRemediations) > 0 && @@ -409,6 +426,9 @@ func (filter *listEvaluationFilter) AddAlert(alert string) error { } else { filter.includedAlerts = append(filter.includedAlerts, alert) } + if !slices.Contains(allowedAlertStatuses, alert) { + return fmt.Errorf("%w: alert", ErrInvalidIdentifier) + } // Prevent filtering for both inclusion and exclusion if len(filter.includedAlerts) > 0 && @@ -483,7 +503,6 @@ func WithEntityType(entityType string) FilterOpt { if !ok { return fmt.Errorf("%w: wrong filter type", ErrInvalidIdentifier) } - // TODO add validation on enumerated types return inner.AddEntityType(entityType) } } @@ -532,7 +551,6 @@ func WithStatus(status string) FilterOpt { if !ok { return fmt.Errorf("%w: wrong filter type", ErrInvalidIdentifier) } - // TODO add validation on enumerated types return inner.AddStatus(status) } } @@ -549,7 +567,6 @@ func WithRemediation(remediation string) FilterOpt { if !ok { return fmt.Errorf("%w: wrong filter type", ErrInvalidIdentifier) } - // TODO add validation on enumerated types return inner.AddRemediation(remediation) } } @@ -566,7 +583,6 @@ func WithAlert(alert string) FilterOpt { if !ok { return fmt.Errorf("%w: wrong filter type", ErrInvalidIdentifier) } - // TODO add validation on enumerated types return inner.AddAlert(alert) } } diff --git a/internal/history/models_test.go b/internal/history/models_test.go index ee1d1df9fc..8db37ab249 100644 --- a/internal/history/models_test.go +++ b/internal/history/models_test.go @@ -314,8 +314,8 @@ func TestListEvaluationFilter(t *testing.T) { filter: func(t *testing.T) (ListEvaluationFilter, error) { t.Helper() return NewListEvaluationFilter( - WithAlert("success"), - WithAlert("!failure"), + WithAlert("on"), + WithAlert("!off"), ) }, err: true, @@ -491,7 +491,7 @@ func TestFilterOptions(t *testing.T) { name: "bogus entity type", option: func(t *testing.T) FilterOpt { t.Helper() - return WithEntityType("!") + return WithEntityType("foo") }, filter: func(t *testing.T) Filter { t.Helper() @@ -665,7 +665,7 @@ func TestFilterOptions(t *testing.T) { name: "status in filter", option: func(t *testing.T) FilterOpt { t.Helper() - return WithStatus("repository") + return WithStatus("success") }, filter: func(t *testing.T) Filter { t.Helper() @@ -675,7 +675,7 @@ func TestFilterOptions(t *testing.T) { t.Helper() f := filter.(StatusFilter) require.NotNil(t, f.IncludedStatuses()) - require.Equal(t, []string{"repository"}, f.IncludedStatuses()) + require.Equal(t, []string{"success"}, f.IncludedStatuses()) require.Nil(t, f.ExcludedStatuses()) }, }, @@ -683,7 +683,7 @@ func TestFilterOptions(t *testing.T) { name: "status not in filter", option: func(t *testing.T) FilterOpt { t.Helper() - return WithStatus("!repository") + return WithStatus("!success") }, filter: func(t *testing.T) Filter { t.Helper() @@ -694,7 +694,7 @@ func TestFilterOptions(t *testing.T) { f := filter.(StatusFilter) require.Nil(t, f.IncludedStatuses()) require.NotNil(t, f.ExcludedStatuses()) - require.Equal(t, []string{"repository"}, f.ExcludedStatuses()) + require.Equal(t, []string{"success"}, f.ExcludedStatuses()) }, }, { @@ -713,7 +713,7 @@ func TestFilterOptions(t *testing.T) { name: "bogus status", option: func(t *testing.T) FilterOpt { t.Helper() - return WithStatus("!") + return WithStatus("foo") }, filter: func(t *testing.T) Filter { t.Helper() @@ -739,7 +739,7 @@ func TestFilterOptions(t *testing.T) { name: "remediation in filter", option: func(t *testing.T) FilterOpt { t.Helper() - return WithRemediation("repository") + return WithRemediation("success") }, filter: func(t *testing.T) Filter { t.Helper() @@ -749,7 +749,7 @@ func TestFilterOptions(t *testing.T) { t.Helper() f := filter.(RemediationFilter) require.NotNil(t, f.IncludedRemediations()) - require.Equal(t, []string{"repository"}, f.IncludedRemediations()) + require.Equal(t, []string{"success"}, f.IncludedRemediations()) require.Nil(t, f.ExcludedRemediations()) }, }, @@ -757,7 +757,7 @@ func TestFilterOptions(t *testing.T) { name: "remediation not in filter", option: func(t *testing.T) FilterOpt { t.Helper() - return WithRemediation("!repository") + return WithRemediation("!success") }, filter: func(t *testing.T) Filter { t.Helper() @@ -768,7 +768,7 @@ func TestFilterOptions(t *testing.T) { f := filter.(RemediationFilter) require.Nil(t, f.IncludedRemediations()) require.NotNil(t, f.ExcludedRemediations()) - require.Equal(t, []string{"repository"}, f.ExcludedRemediations()) + require.Equal(t, []string{"success"}, f.ExcludedRemediations()) }, }, { @@ -787,7 +787,7 @@ func TestFilterOptions(t *testing.T) { name: "bogus remediation", option: func(t *testing.T) FilterOpt { t.Helper() - return WithRemediation("!") + return WithRemediation("foo") }, filter: func(t *testing.T) Filter { t.Helper() @@ -813,7 +813,7 @@ func TestFilterOptions(t *testing.T) { name: "alert in filter", option: func(t *testing.T) FilterOpt { t.Helper() - return WithAlert("repository") + return WithAlert("on") }, filter: func(t *testing.T) Filter { t.Helper() @@ -823,7 +823,7 @@ func TestFilterOptions(t *testing.T) { t.Helper() f := filter.(AlertFilter) require.NotNil(t, f.IncludedAlerts()) - require.Equal(t, []string{"repository"}, f.IncludedAlerts()) + require.Equal(t, []string{"on"}, f.IncludedAlerts()) require.Nil(t, f.ExcludedAlerts()) }, }, @@ -831,7 +831,7 @@ func TestFilterOptions(t *testing.T) { name: "alert not in filter", option: func(t *testing.T) FilterOpt { t.Helper() - return WithAlert("!repository") + return WithAlert("!on") }, filter: func(t *testing.T) Filter { t.Helper() @@ -842,7 +842,7 @@ func TestFilterOptions(t *testing.T) { f := filter.(AlertFilter) require.Nil(t, f.IncludedAlerts()) require.NotNil(t, f.ExcludedAlerts()) - require.Equal(t, []string{"repository"}, f.ExcludedAlerts()) + require.Equal(t, []string{"on"}, f.ExcludedAlerts()) }, }, { @@ -861,7 +861,7 @@ func TestFilterOptions(t *testing.T) { name: "bogus alert", option: func(t *testing.T) FilterOpt { t.Helper() - return WithAlert("!") + return WithAlert("foo") }, filter: func(t *testing.T) Filter { t.Helper()