Skip to content

Commit

Permalink
BED-4726 added new ~eq predicate for filtering on strings
Browse files Browse the repository at this point in the history
  • Loading branch information
mvlipka committed Aug 22, 2024
1 parent 989ddae commit 083eac6
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 20 deletions.
10 changes: 9 additions & 1 deletion cmd/api/src/model/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@ const (
LessThanOrEquals FilterOperator = "lte"
Equals FilterOperator = "eq"
NotEquals FilterOperator = "neq"
ApproximatelyEquals FilterOperator = "~eq"

GreaterThanSymbol string = ">"
GreaterThanOrEqualsSymbol string = ">="
LessThanSymbol string = "<"
LessThanOrEqualsSymbol string = "<="
EqualsSymbol string = "="
NotEqualsSymbol string = "<>"
ApproximatelyEqualSymbol string = "ILIKE"

TrueString = "true"
FalseString = "false"
Expand Down Expand Up @@ -79,6 +81,9 @@ func ParseFilterOperator(raw string) (FilterOperator, error) {
case NotEquals:
return NotEquals, nil

case ApproximatelyEquals:
return ApproximatelyEquals, nil

default:
return "", fmt.Errorf("unknown query parameter filter predicate: %s", raw)
}
Expand Down Expand Up @@ -165,6 +170,9 @@ func (s QueryParameterFilterMap) BuildSQLFilter() (SQLFilter, error) {
predicate = EqualsSymbol
case NotEquals:
predicate = NotEqualsSymbol
case ApproximatelyEquals:
predicate = ApproximatelyEqualSymbol
filter.Value = fmt.Sprintf("%%%s%%", filter.Value)
default:
return SQLFilter{}, fmt.Errorf("invalid filter predicate specified")
}
Expand Down Expand Up @@ -344,6 +352,6 @@ func (s QueryParameterFilterParser) ParseQueryParameterFilters(request *http.Req

func NewQueryParameterFilterParser() QueryParameterFilterParser {
return QueryParameterFilterParser{
re: regexp.MustCompile(`([\w]+):([\w\d\--_]+)`),
re: regexp.MustCompile(`([~\w]+):([\w\d\--_]+)`),
}
}
27 changes: 18 additions & 9 deletions cmd/api/src/model/filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,19 +76,28 @@ func TestModel_BuildSQLFilter_Success(t *testing.T) {
IsStringData: false,
}

approximatelyEquals := model.QueryParameterFilter{
Name: "filtercolumn6",
Operator: model.ApproximatelyEquals,
Value: "testing value",
IsStringData: true,
}

expectedResults := map[string]model.SQLFilter{
"numericMin": {SQLString: fmt.Sprintf("%s > ?", numericMin.Name), Params: []any{numericMin.Value}},
"numericMax": {SQLString: fmt.Sprintf("%s < ?", numericMax.Name), Params: []any{numericMax.Value}},
"stringValue": {SQLString: fmt.Sprintf("%s = ?", stringValue.Name), Params: []any{stringValue.Value}},
"boolEquals": {SQLString: fmt.Sprintf("%s = ?", boolEquals.Name), Params: []any{boolEquals.Value}},
"boolNotEquals": {SQLString: fmt.Sprintf("%s <> ?", boolNotEquals.Name), Params: []any{boolNotEquals.Value}},
"numericMin": {SQLString: fmt.Sprintf("%s > ?", numericMin.Name), Params: []any{numericMin.Value}},
"numericMax": {SQLString: fmt.Sprintf("%s < ?", numericMax.Name), Params: []any{numericMax.Value}},
"stringValue": {SQLString: fmt.Sprintf("%s = ?", stringValue.Name), Params: []any{stringValue.Value}},
"boolEquals": {SQLString: fmt.Sprintf("%s = ?", boolEquals.Name), Params: []any{boolEquals.Value}},
"boolNotEquals": {SQLString: fmt.Sprintf("%s <> ?", boolNotEquals.Name), Params: []any{boolNotEquals.Value}},
"stringApproximatelyEquals": {SQLString: fmt.Sprintf("%s ILIKE ?", approximatelyEquals.Name), Params: []any{approximatelyEquals.Value}},
}

queryParameterFilterMap := model.QueryParameterFilterMap{
numericMax.Name: model.QueryParameterFilters{numericMin, numericMax},
stringValue.Name: model.QueryParameterFilters{stringValue},
boolEquals.Name: model.QueryParameterFilters{boolEquals},
boolNotEquals.Name: model.QueryParameterFilters{boolNotEquals},
numericMax.Name: model.QueryParameterFilters{numericMin, numericMax},
stringValue.Name: model.QueryParameterFilters{stringValue},
boolEquals.Name: model.QueryParameterFilters{boolEquals},
boolNotEquals.Name: model.QueryParameterFilters{boolNotEquals},
approximatelyEquals.Name: model.QueryParameterFilters{approximatelyEquals},
}

result, err := queryParameterFilterMap.BuildSQLFilter()
Expand Down
4 changes: 2 additions & 2 deletions cmd/api/src/model/saved_queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ func (s SavedQueries) IsSortable(column string) bool {
func (s SavedQueries) ValidFilters() map[string][]FilterOperator {
return map[string][]FilterOperator{
"user_id": {Equals, NotEquals},
"name": {Equals, NotEquals},
"name": {Equals, NotEquals, ApproximatelyEquals},
"query": {Equals, NotEquals},
"description": {Equals, NotEquals},
"description": {Equals, NotEquals, ApproximatelyEquals},
}
}

Expand Down
28 changes: 22 additions & 6 deletions cmd/api/src/model/saved_queries_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package model_test

import (
"github.com/stretchr/testify/assert"
"testing"

"github.com/specterops/bloodhound/src/model"
Expand All @@ -38,27 +39,42 @@ func TestSavedQueries_ValidFilters(t *testing.T) {
validFilters := savedQueries.ValidFilters()
require.Equal(t, 4, len(validFilters))

for _, column := range []string{"user_id", "name", "query", "description"} {
for _, column := range []string{"user_id", "query"} {
operators, ok := validFilters[column]
require.True(t, ok)
require.Equal(t, 2, len(operators))
assert.Equal(t, 2, len(operators))
}

for _, column := range []string{"name", "description"} {
operators, ok := validFilters[column]
require.True(t, ok)
assert.Equal(t, 3, len(operators))
}
}

func TestSavedQueries_GetValidFilterPredicatesAsStrings(t *testing.T) {
savedQueries := model.SavedQueries{}
for _, column := range []string{"user_id", "name", "query"} {
for _, column := range []string{"user_id", "query"} {
predicates, err := savedQueries.GetValidFilterPredicatesAsStrings(column)
require.Nil(t, err)
require.Equal(t, 2, len(predicates))
require.True(t, utils.Contains(predicates, string(model.Equals)))
require.True(t, utils.Contains(predicates, string(model.NotEquals)))
assert.True(t, utils.Contains(predicates, string(model.Equals)))
assert.True(t, utils.Contains(predicates, string(model.NotEquals)))
}

for _, column := range []string{"name", "description"} {
predicates, err := savedQueries.GetValidFilterPredicatesAsStrings(column)
require.Nil(t, err)
require.Equal(t, 3, len(predicates))
assert.True(t, utils.Contains(predicates, string(model.Equals)))
assert.True(t, utils.Contains(predicates, string(model.NotEquals)))
assert.True(t, utils.Contains(predicates, string(model.ApproximatelyEquals)))
}
}

func TestSavedQueries_IsString(t *testing.T) {
savedQueries := model.SavedQueries{}
for _, column := range []string{"name", "query"} {
for _, column := range []string{"name", "query", "description"} {
require.True(t, savedQueries.IsString(column))
}
}
2 changes: 1 addition & 1 deletion packages/go/openapi/doc/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -13784,7 +13784,7 @@
},
"api.params.predicate.filter.string": {
"type": "string",
"description": "Filter results by column string value. Valid filter predicates are `eq`, `neq`.\n"
"description": "Filter results by column string value. Valid filter predicates are `eq`, `neq`, `~eq`.\n"
},
"api.params.predicate.filter.integer": {
"type": "integer",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@

type: string
description: |
Filter results by column string value. Valid filter predicates are `eq`, `neq`.
Filter results by column string value. Valid filter predicates are `eq`, `neq`, `~eq`.

0 comments on commit 083eac6

Please sign in to comment.