diff --git a/cmd/api/src/api/v2/saved_queries.go b/cmd/api/src/api/v2/saved_queries.go index cba5d1e4db..5a42c820c5 100644 --- a/cmd/api/src/api/v2/saved_queries.go +++ b/cmd/api/src/api/v2/saved_queries.go @@ -229,16 +229,30 @@ func (s Resources) DeleteSavedQuery(response http.ResponseWriter, request *http. api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusNotFound, "query does not exist", request), response) } else if err != nil { api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusInternalServerError, api.ErrorResponseDetailsInternalServerError, request), response) - } else if !savedQueryBelongsToUser { - api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusBadRequest, "invalid saved_query_id supplied", request), response) - } else if err := s.DB.DeleteSavedQuery(request.Context(), savedQueryID); errors.Is(err, database.ErrNotFound) { - // This is an edge case and can only occur if the database has a concurrent operation that deletes the saved query - // after the check at s.DB.SavedQueryBelongsToUser but before getting here. - // Still, adding in the same check for good measure. - api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusNotFound, "query does not exist", request), response) - } else if err != nil { - api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusInternalServerError, api.ErrorResponseDetailsInternalServerError, request), response) } else { - response.WriteHeader(http.StatusNoContent) + if !savedQueryBelongsToUser { + if _, isAdmin := user.Roles.FindByName(auth.RoleAdministrator); !isAdmin { + api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusForbidden, "User does not have permission to delete this query", request), response) + return + } else if isPublicQuery, err := s.DB.IsSavedQueryPublic(request.Context(), int64(savedQueryID)); err != nil { + api.HandleDatabaseError(request, response, err) + return + } else if !isPublicQuery { + api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusForbidden, "User does not have permission to delete this query", request), response) + return + } + } + + if err := s.DB.DeleteSavedQuery(request.Context(), savedQueryID); errors.Is(err, database.ErrNotFound) { + // This is an edge case and can only occur if the database has a concurrent operation that deletes the saved query + // after the check at s.DB.SavedQueryBelongsToUser but before getting here. + // Still, adding in the same check for good measure. + api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusNotFound, "query does not exist", request), response) + } else if err != nil { + api.WriteErrorResponse(request.Context(), api.BuildErrorResponse(http.StatusInternalServerError, api.ErrorResponseDetailsInternalServerError, request), response) + } else { + response.WriteHeader(http.StatusNoContent) + } + } } diff --git a/cmd/api/src/api/v2/saved_queries_test.go b/cmd/api/src/api/v2/saved_queries_test.go index f8123c5184..3d69dabdae 100644 --- a/cmd/api/src/api/v2/saved_queries_test.go +++ b/cmd/api/src/api/v2/saved_queries_test.go @@ -1236,7 +1236,7 @@ func TestResources_DeleteSavedQuery_DBError(t *testing.T) { require.Equal(t, http.StatusInternalServerError, response.Code) } -func TestResources_DeleteSavedQuery_QueryDoesNotBelongToUser(t *testing.T) { +func TestResources_DeleteSavedQuery_UserNotAdmin(t *testing.T) { var ( mockCtrl = gomock.NewController(t) mockDB = mocks.NewMockDatabase(mockCtrl) @@ -1262,8 +1262,69 @@ func TestResources_DeleteSavedQuery_QueryDoesNotBelongToUser(t *testing.T) { handler := http.HandlerFunc(resources.DeleteSavedQuery) handler.ServeHTTP(response, req) - require.Equal(t, http.StatusBadRequest, response.Code) - require.Contains(t, response.Body.String(), api.URIPathVariableSavedQueryID) + assert.Equal(t, http.StatusForbidden, response.Code) + assert.Contains(t, response.Body.String(), "User does not have permission to delete this query") +} + +func TestResources_DeleteSavedQuery_IsPublicSavedQueryDBError(t *testing.T) { + var ( + mockCtrl = gomock.NewController(t) + mockDB = mocks.NewMockDatabase(mockCtrl) + resources = v2.Resources{DB: mockDB} + ) + defer mockCtrl.Finish() + + userId, err := uuid2.NewV4() + require.Nil(t, err) + + endpoint := "/api/v2/saved-queries/%s" + savedQueryId := "1" + + mockDB.EXPECT().SavedQueryBelongsToUser(gomock.Any(), gomock.Any(), gomock.Any()).Return(false, nil) + mockDB.EXPECT().IsSavedQueryPublic(gomock.Any(), gomock.Any()).Return(false, fmt.Errorf("error")) + + req, err := http.NewRequestWithContext(createContextWithAdminOwnerId(userId), "DELETE", fmt.Sprintf(endpoint, savedQueryId), nil) + require.Nil(t, err) + + req.Header.Set(headers.ContentType.String(), mediatypes.ApplicationJson.String()) + req = mux.SetURLVars(req, map[string]string{api.URIPathVariableSavedQueryID: savedQueryId}) + + response := httptest.NewRecorder() + handler := http.HandlerFunc(resources.DeleteSavedQuery) + + handler.ServeHTTP(response, req) + assert.Equal(t, http.StatusInternalServerError, response.Code) +} + +func TestResources_DeleteSavedQuery_NotPublicQueryAndUserIsAdmin(t *testing.T) { + var ( + mockCtrl = gomock.NewController(t) + mockDB = mocks.NewMockDatabase(mockCtrl) + resources = v2.Resources{DB: mockDB} + ) + defer mockCtrl.Finish() + + userId, err := uuid2.NewV4() + require.Nil(t, err) + + endpoint := "/api/v2/saved-queries/%s" + savedQueryId := "1" + + mockDB.EXPECT().SavedQueryBelongsToUser(gomock.Any(), gomock.Any(), gomock.Any()).Return(false, nil) + mockDB.EXPECT().IsSavedQueryPublic(gomock.Any(), gomock.Any()).Return(false, nil) + + req, err := http.NewRequestWithContext(createContextWithAdminOwnerId(userId), "DELETE", fmt.Sprintf(endpoint, savedQueryId), nil) + require.Nil(t, err) + + req.Header.Set(headers.ContentType.String(), mediatypes.ApplicationJson.String()) + req = mux.SetURLVars(req, map[string]string{api.URIPathVariableSavedQueryID: savedQueryId}) + + response := httptest.NewRecorder() + handler := http.HandlerFunc(resources.DeleteSavedQuery) + + handler.ServeHTTP(response, req) + assert.Equal(t, http.StatusForbidden, response.Code) + assert.Contains(t, response.Body.String(), "User does not have permission to delete this query") } func TestResources_DeleteSavedQuery_RecordNotFound(t *testing.T) { @@ -1358,6 +1419,37 @@ func TestResources_DeleteSavedQuery_DeleteError(t *testing.T) { require.Contains(t, response.Body.String(), api.ErrorResponseDetailsInternalServerError) } +func TestResources_DeleteSavedQuery_PublicQueryAndUserIsAdmin(t *testing.T) { + var ( + mockCtrl = gomock.NewController(t) + mockDB = mocks.NewMockDatabase(mockCtrl) + resources = v2.Resources{DB: mockDB} + ) + defer mockCtrl.Finish() + + userId, err := uuid2.NewV4() + require.Nil(t, err) + + endpoint := "/api/v2/saved-queries/%s" + savedQueryId := "1" + + mockDB.EXPECT().SavedQueryBelongsToUser(gomock.Any(), gomock.Any(), gomock.Any()).Return(false, nil) + mockDB.EXPECT().IsSavedQueryPublic(gomock.Any(), gomock.Any()).Return(true, nil) + mockDB.EXPECT().DeleteSavedQuery(gomock.Any(), gomock.Any()).Return(nil) + + req, err := http.NewRequestWithContext(createContextWithAdminOwnerId(userId), "DELETE", fmt.Sprintf(endpoint, savedQueryId), nil) + require.Nil(t, err) + + req.Header.Set(headers.ContentType.String(), mediatypes.ApplicationJson.String()) + req = mux.SetURLVars(req, map[string]string{api.URIPathVariableSavedQueryID: savedQueryId}) + + response := httptest.NewRecorder() + handler := http.HandlerFunc(resources.DeleteSavedQuery) + + handler.ServeHTTP(response, req) + require.Equal(t, http.StatusNoContent, response.Code) +} + func TestResources_DeleteSavedQuery(t *testing.T) { var ( mockCtrl = gomock.NewController(t) diff --git a/cmd/api/src/queries/graph.go b/cmd/api/src/queries/graph.go index d6a6acf3f9..f26b2a8634 100644 --- a/cmd/api/src/queries/graph.go +++ b/cmd/api/src/queries/graph.go @@ -154,9 +154,9 @@ type GraphQuery struct { Cache cache.Cache SlowQueryThreshold int64 // Threshold in milliseconds DisableCypherComplexityLimit bool - EnableCypherMutations bool - cypherEmitter format.Emitter - strippedCypherEmitter format.Emitter + EnableCypherMutations bool + cypherEmitter format.Emitter + strippedCypherEmitter format.Emitter } func NewGraphQuery(graphDB graph.Database, cache cache.Cache, cfg config.Configuration) *GraphQuery { diff --git a/cmd/api/src/test/fixtures/fixtures/expected_ingest.go b/cmd/api/src/test/fixtures/fixtures/expected_ingest.go index c1df9db800..123cf4f5f4 100644 --- a/cmd/api/src/test/fixtures/fixtures/expected_ingest.go +++ b/cmd/api/src/test/fixtures/fixtures/expected_ingest.go @@ -18,6 +18,7 @@ package fixtures import ( "bytes" + "github.com/specterops/bloodhound/cypher/models/cypher/format" "github.com/specterops/bloodhound/cypher/models/cypher" diff --git a/cmd/ui/src/utils.ts b/cmd/ui/src/utils.ts index fdb6579f39..6c93c0caeb 100644 --- a/cmd/ui/src/utils.ts +++ b/cmd/ui/src/utils.ts @@ -25,7 +25,7 @@ import { isLink, isNode } from 'src/ducks/graph/utils'; import { Glyph } from 'src/rendering/programs/node.glyphs'; import { store } from 'src/store'; -const IGNORE_401_LOGOUT = ['/api/v2/login', '/api/v2/logout', '/api/v2/features'] +const IGNORE_401_LOGOUT = ['/api/v2/login', '/api/v2/logout', '/api/v2/features']; export const getDatesInRange = (startDate: Date, endDate: Date) => { const date = new Date(startDate.getTime()); diff --git a/packages/go/cypher/analyzer/analyzer.go b/packages/go/cypher/analyzer/analyzer.go index e95889694d..91154ef38f 100644 --- a/packages/go/cypher/analyzer/analyzer.go +++ b/packages/go/cypher/analyzer/analyzer.go @@ -18,6 +18,7 @@ package analyzer import ( "errors" + "github.com/specterops/bloodhound/cypher/models/cypher" "github.com/specterops/bloodhound/dawgs/graph" ) diff --git a/packages/go/cypher/analyzer/measure.go b/packages/go/cypher/analyzer/measure.go index aa897484f1..e9c5c4903b 100644 --- a/packages/go/cypher/analyzer/measure.go +++ b/packages/go/cypher/analyzer/measure.go @@ -18,6 +18,7 @@ package analyzer import ( "fmt" + "github.com/specterops/bloodhound/cypher/models/cypher" "github.com/specterops/bloodhound/dawgs/graph" diff --git a/packages/go/cypher/frontend/comparison.go b/packages/go/cypher/frontend/comparison.go index 1bbb934883..f5218d67e4 100644 --- a/packages/go/cypher/frontend/comparison.go +++ b/packages/go/cypher/frontend/comparison.go @@ -18,6 +18,7 @@ package frontend import ( "fmt" + "github.com/specterops/bloodhound/cypher/models/cypher" "github.com/antlr4-go/antlr/v4" diff --git a/packages/go/cypher/frontend/expression.go b/packages/go/cypher/frontend/expression.go index dccc37ff3d..e021b987dd 100644 --- a/packages/go/cypher/frontend/expression.go +++ b/packages/go/cypher/frontend/expression.go @@ -17,9 +17,10 @@ package frontend import ( - "github.com/specterops/bloodhound/cypher/models/cypher" "strings" + "github.com/specterops/bloodhound/cypher/models/cypher" + "github.com/antlr4-go/antlr/v4" "github.com/specterops/bloodhound/cypher/parser" "github.com/specterops/bloodhound/dawgs/graph" diff --git a/packages/go/cypher/frontend/literal.go b/packages/go/cypher/frontend/literal.go index 57989a47df..9567b8a38e 100644 --- a/packages/go/cypher/frontend/literal.go +++ b/packages/go/cypher/frontend/literal.go @@ -18,9 +18,10 @@ package frontend import ( "fmt" - "github.com/specterops/bloodhound/cypher/models/cypher" "strconv" + "github.com/specterops/bloodhound/cypher/models/cypher" + "github.com/specterops/bloodhound/cypher/parser" ) diff --git a/packages/go/cypher/frontend/parse.go b/packages/go/cypher/frontend/parse.go index c5663fc4ea..9d554b6bf2 100644 --- a/packages/go/cypher/frontend/parse.go +++ b/packages/go/cypher/frontend/parse.go @@ -19,9 +19,10 @@ package frontend import ( "bytes" "errors" - "github.com/specterops/bloodhound/cypher/models/cypher" "strings" + "github.com/specterops/bloodhound/cypher/models/cypher" + "github.com/antlr4-go/antlr/v4" "github.com/specterops/bloodhound/cypher/parser" ) diff --git a/packages/go/cypher/frontend/pattern.go b/packages/go/cypher/frontend/pattern.go index b48e8a9c92..403c6e570f 100644 --- a/packages/go/cypher/frontend/pattern.go +++ b/packages/go/cypher/frontend/pattern.go @@ -18,9 +18,10 @@ package frontend import ( "fmt" - "github.com/specterops/bloodhound/cypher/models/cypher" "strconv" + "github.com/specterops/bloodhound/cypher/models/cypher" + "github.com/antlr4-go/antlr/v4" "github.com/specterops/bloodhound/cypher/parser" "github.com/specterops/bloodhound/dawgs/graph" diff --git a/packages/go/cypher/frontend/query.go b/packages/go/cypher/frontend/query.go index bf3acfd3c9..04699b8386 100644 --- a/packages/go/cypher/frontend/query.go +++ b/packages/go/cypher/frontend/query.go @@ -18,9 +18,10 @@ package frontend import ( "fmt" - "github.com/specterops/bloodhound/cypher/models/cypher" "strconv" + "github.com/specterops/bloodhound/cypher/models/cypher" + "github.com/specterops/bloodhound/cypher/parser" "github.com/specterops/bloodhound/dawgs/graph" ) diff --git a/packages/go/cypher/models/cypher/copy_test.go b/packages/go/cypher/models/cypher/copy_test.go index 1184345140..01427bcc3f 100644 --- a/packages/go/cypher/models/cypher/copy_test.go +++ b/packages/go/cypher/models/cypher/copy_test.go @@ -17,9 +17,10 @@ package cypher_test import ( - model2 "github.com/specterops/bloodhound/cypher/models/cypher" "testing" + model2 "github.com/specterops/bloodhound/cypher/models/cypher" + "github.com/specterops/bloodhound/dawgs/graph" "github.com/stretchr/testify/require" ) diff --git a/packages/go/cypher/models/cypher/format/format.go b/packages/go/cypher/models/cypher/format/format.go index 18211cad52..7eaa3ab061 100644 --- a/packages/go/cypher/models/cypher/format/format.go +++ b/packages/go/cypher/models/cypher/format/format.go @@ -18,11 +18,12 @@ package format import ( "fmt" - "github.com/specterops/bloodhound/cypher/models/cypher" "io" "strconv" "strings" + "github.com/specterops/bloodhound/cypher/models/cypher" + "github.com/specterops/bloodhound/dawgs/graph" ) @@ -844,7 +845,6 @@ func (s Emitter) formatRemove(output io.Writer, remove *cypher.Remove) error { } } - if removeItem.KindMatcher != nil { if err := s.WriteExpression(output, removeItem.KindMatcher.Reference); err != nil { return err diff --git a/packages/go/cypher/models/cypher/format/format_test.go b/packages/go/cypher/models/cypher/format/format_test.go index c1bcc45927..007acc8e05 100644 --- a/packages/go/cypher/models/cypher/format/format_test.go +++ b/packages/go/cypher/models/cypher/format/format_test.go @@ -18,9 +18,10 @@ package format_test import ( "bytes" - "github.com/specterops/bloodhound/cypher/models/cypher/format" "testing" + "github.com/specterops/bloodhound/cypher/models/cypher/format" + "github.com/specterops/bloodhound/cypher/frontend" "github.com/stretchr/testify/require" diff --git a/packages/go/cypher/models/cypher/walk_test.go b/packages/go/cypher/models/cypher/walk_test.go index 324c2aa290..518c25c0c3 100644 --- a/packages/go/cypher/models/cypher/walk_test.go +++ b/packages/go/cypher/models/cypher/walk_test.go @@ -17,9 +17,10 @@ package cypher_test import ( - model2 "github.com/specterops/bloodhound/cypher/models/cypher" "testing" + model2 "github.com/specterops/bloodhound/cypher/models/cypher" + "github.com/specterops/bloodhound/cypher/frontend" "github.com/specterops/bloodhound/cypher/test" "github.com/stretchr/testify/require" diff --git a/packages/go/cypher/models/pgsql/format/format_test.go b/packages/go/cypher/models/pgsql/format/format_test.go index 4a180b2b07..c83f4b856c 100644 --- a/packages/go/cypher/models/pgsql/format/format_test.go +++ b/packages/go/cypher/models/pgsql/format/format_test.go @@ -18,11 +18,12 @@ package format_test import ( "fmt" + "testing" + "github.com/specterops/bloodhound/cypher/models" "github.com/specterops/bloodhound/cypher/models/pgsql" "github.com/specterops/bloodhound/cypher/models/pgsql/format" "github.com/stretchr/testify/require" - "testing" ) func mustAsLiteral(value any) pgsql.Literal { diff --git a/packages/go/cypher/models/pgsql/identifiers.go b/packages/go/cypher/models/pgsql/identifiers.go index 55573c6206..4e19ab092a 100644 --- a/packages/go/cypher/models/pgsql/identifiers.go +++ b/packages/go/cypher/models/pgsql/identifiers.go @@ -17,9 +17,10 @@ package pgsql import ( - "github.com/specterops/bloodhound/cypher/models" "slices" "strings" + + "github.com/specterops/bloodhound/cypher/models" ) const ( diff --git a/packages/go/cypher/models/pgsql/identifiers_test.go b/packages/go/cypher/models/pgsql/identifiers_test.go index 6f5488eeda..b55b7b1ceb 100644 --- a/packages/go/cypher/models/pgsql/identifiers_test.go +++ b/packages/go/cypher/models/pgsql/identifiers_test.go @@ -1,8 +1,25 @@ +// Copyright 2024 Specter Ops, Inc. +// +// Licensed under the Apache License, Version 2.0 +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + package pgsql import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestIdentifierSet_CombinedKey(t *testing.T) { diff --git a/packages/go/cypher/models/pgsql/model.go b/packages/go/cypher/models/pgsql/model.go index dc0a505cfd..1dd40c2223 100644 --- a/packages/go/cypher/models/pgsql/model.go +++ b/packages/go/cypher/models/pgsql/model.go @@ -17,9 +17,10 @@ package pgsql import ( - "github.com/specterops/bloodhound/dawgs/graph" "strings" + "github.com/specterops/bloodhound/dawgs/graph" + "github.com/specterops/bloodhound/cypher/models" ) diff --git a/packages/go/cypher/models/pgsql/nodes.go b/packages/go/cypher/models/pgsql/nodes.go index 94fe786c13..8da4991e47 100644 --- a/packages/go/cypher/models/pgsql/nodes.go +++ b/packages/go/cypher/models/pgsql/nodes.go @@ -59,7 +59,6 @@ type MergeAction interface { AsMergeAction() MergeAction } - // SetExpression is an expression that represents a query body expression. type SetExpression interface { Expression diff --git a/packages/go/cypher/models/pgsql/pgtypes.go b/packages/go/cypher/models/pgsql/pgtypes.go index 4eb4d49570..cf3a9a1448 100644 --- a/packages/go/cypher/models/pgsql/pgtypes.go +++ b/packages/go/cypher/models/pgsql/pgtypes.go @@ -19,8 +19,9 @@ package pgsql import ( "errors" "fmt" - "github.com/specterops/bloodhound/dawgs/graph" "time" + + "github.com/specterops/bloodhound/dawgs/graph" ) var ( diff --git a/packages/go/cypher/models/pgsql/test/testcase.go b/packages/go/cypher/models/pgsql/test/testcase.go index 6ea8a0546f..afb4ab6095 100644 --- a/packages/go/cypher/models/pgsql/test/testcase.go +++ b/packages/go/cypher/models/pgsql/test/testcase.go @@ -1,21 +1,38 @@ +// Copyright 2024 Specter Ops, Inc. +// +// Licensed under the Apache License, Version 2.0 +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + package test import ( - "cuelang.org/go/pkg/regexp" "embed" "encoding/json" "fmt" + "io" + "io/fs" + "path/filepath" + "strings" + "testing" + + "cuelang.org/go/pkg/regexp" "github.com/specterops/bloodhound/cypher/frontend" "github.com/specterops/bloodhound/cypher/models/cypher" "github.com/specterops/bloodhound/cypher/models/pgsql" "github.com/specterops/bloodhound/cypher/models/pgsql/translate" "github.com/specterops/bloodhound/cypher/models/walk" "github.com/stretchr/testify/require" - "io" - "io/fs" - "path/filepath" - "strings" - "testing" ) //go:embed translation_cases/* diff --git a/packages/go/cypher/models/pgsql/translate/constraints.go b/packages/go/cypher/models/pgsql/translate/constraints.go index 40432097c7..7945c53213 100644 --- a/packages/go/cypher/models/pgsql/translate/constraints.go +++ b/packages/go/cypher/models/pgsql/translate/constraints.go @@ -18,6 +18,7 @@ package translate import ( "fmt" + "github.com/specterops/bloodhound/cypher/models/pgsql" "github.com/specterops/bloodhound/dawgs/graph" ) diff --git a/packages/go/cypher/models/pgsql/translate/delete.go b/packages/go/cypher/models/pgsql/translate/delete.go index 291885e31f..daf7532a96 100644 --- a/packages/go/cypher/models/pgsql/translate/delete.go +++ b/packages/go/cypher/models/pgsql/translate/delete.go @@ -18,6 +18,7 @@ package translate import ( "fmt" + "github.com/specterops/bloodhound/cypher/models" cypher "github.com/specterops/bloodhound/cypher/models/cypher" "github.com/specterops/bloodhound/cypher/models/pgsql" diff --git a/packages/go/cypher/models/pgsql/translate/expression.go b/packages/go/cypher/models/pgsql/translate/expression.go index 554debe0bc..447127bd61 100644 --- a/packages/go/cypher/models/pgsql/translate/expression.go +++ b/packages/go/cypher/models/pgsql/translate/expression.go @@ -18,6 +18,7 @@ package translate import ( "fmt" + "github.com/specterops/bloodhound/cypher/models/pgsql" "github.com/specterops/bloodhound/cypher/models/walk" ) @@ -285,7 +286,6 @@ func applyBinaryExpressionTypeHints(expression *pgsql.BinaryExpression) error { } } - type Builder struct { stack []pgsql.Expression } @@ -558,7 +558,7 @@ func (s *ExpressionTreeTranslator) PopPushBinaryExpression(operator pgsql.Operat return fmt.Errorf("expected string but found %T as right operand for operator %s", typedROperand.Value, operator) } else { newExpression.Operator = pgsql.OperatorLike - newExpression.ROperand = pgsql.NewLiteral("%" + stringValue + "%", rOperandDataType) + newExpression.ROperand = pgsql.NewLiteral("%"+stringValue+"%", rOperandDataType) } case *pgsql.BinaryExpression: @@ -614,7 +614,7 @@ func (s *ExpressionTreeTranslator) PopPushBinaryExpression(operator pgsql.Operat return fmt.Errorf("expected string but found %T as right operand for operator %s", typedROperand.Value, operator) } else { newExpression.Operator = pgsql.OperatorLike - newExpression.ROperand = pgsql.NewLiteral(stringValue + "%", rOperandDataType) + newExpression.ROperand = pgsql.NewLiteral(stringValue+"%", rOperandDataType) } case *pgsql.BinaryExpression: @@ -667,7 +667,7 @@ func (s *ExpressionTreeTranslator) PopPushBinaryExpression(operator pgsql.Operat return fmt.Errorf("expected string but found %T as right operand for operator %s", typedROperand.Value, operator) } else { newExpression.Operator = pgsql.OperatorLike - newExpression.ROperand = pgsql.NewLiteral("%" + stringValue, rOperandDataType) + newExpression.ROperand = pgsql.NewLiteral("%"+stringValue, rOperandDataType) } case *pgsql.BinaryExpression: diff --git a/packages/go/cypher/models/pgsql/translate/format.go b/packages/go/cypher/models/pgsql/translate/format.go index 48cadc775c..148f54f21c 100644 --- a/packages/go/cypher/models/pgsql/translate/format.go +++ b/packages/go/cypher/models/pgsql/translate/format.go @@ -1,7 +1,24 @@ +// Copyright 2024 Specter Ops, Inc. +// +// Licensed under the Apache License, Version 2.0 +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + package translate import ( "bytes" + "github.com/specterops/bloodhound/cypher/models/cypher" cypherFormat "github.com/specterops/bloodhound/cypher/models/cypher/format" "github.com/specterops/bloodhound/cypher/models/pgsql" diff --git a/packages/go/cypher/models/pgsql/translate/model.go b/packages/go/cypher/models/pgsql/translate/model.go index 13b101b4a9..142c7c1178 100644 --- a/packages/go/cypher/models/pgsql/translate/model.go +++ b/packages/go/cypher/models/pgsql/translate/model.go @@ -18,6 +18,7 @@ package translate import ( "fmt" + "github.com/specterops/bloodhound/cypher/models" cypher "github.com/specterops/bloodhound/cypher/models/cypher" "github.com/specterops/bloodhound/cypher/models/pgsql" diff --git a/packages/go/cypher/models/pgsql/translate/node.go b/packages/go/cypher/models/pgsql/translate/node.go index 443b3c87f3..dd0864677d 100644 --- a/packages/go/cypher/models/pgsql/translate/node.go +++ b/packages/go/cypher/models/pgsql/translate/node.go @@ -18,10 +18,11 @@ package translate import ( "fmt" + "strings" + "github.com/specterops/bloodhound/cypher/models" cypher "github.com/specterops/bloodhound/cypher/models/cypher" "github.com/specterops/bloodhound/cypher/models/pgsql" - "strings" ) func (s *Translator) translateNodePattern(scope *Scope, nodePattern *cypher.NodePattern, part *PatternPart) error { diff --git a/packages/go/cypher/models/pgsql/translate/predicate.go b/packages/go/cypher/models/pgsql/translate/predicate.go index 709c3a5f8f..ff72366e2c 100644 --- a/packages/go/cypher/models/pgsql/translate/predicate.go +++ b/packages/go/cypher/models/pgsql/translate/predicate.go @@ -18,6 +18,7 @@ package translate import ( "fmt" + "github.com/specterops/bloodhound/cypher/models" "github.com/specterops/bloodhound/cypher/models/pgsql" "github.com/specterops/bloodhound/dawgs/graph" diff --git a/packages/go/cypher/models/pgsql/translate/projection.go b/packages/go/cypher/models/pgsql/translate/projection.go index 4dbb2c947b..6bf21f4920 100644 --- a/packages/go/cypher/models/pgsql/translate/projection.go +++ b/packages/go/cypher/models/pgsql/translate/projection.go @@ -18,6 +18,7 @@ package translate import ( "fmt" + "github.com/specterops/bloodhound/cypher/models" "github.com/specterops/bloodhound/cypher/models/pgsql" ) diff --git a/packages/go/cypher/models/pgsql/translate/relationship.go b/packages/go/cypher/models/pgsql/translate/relationship.go index 597022e75a..f0fc2a441a 100644 --- a/packages/go/cypher/models/pgsql/translate/relationship.go +++ b/packages/go/cypher/models/pgsql/translate/relationship.go @@ -17,10 +17,11 @@ package translate import ( + "strings" + "github.com/specterops/bloodhound/cypher/models" cypher "github.com/specterops/bloodhound/cypher/models/cypher" "github.com/specterops/bloodhound/cypher/models/pgsql" - "strings" ) func (s *Translator) translateRelationshipPattern(scope *Scope, relationshipPattern *cypher.RelationshipPattern, part *PatternPart) error { diff --git a/packages/go/cypher/models/pgsql/translate/renamer.go b/packages/go/cypher/models/pgsql/translate/renamer.go index 3988934e31..bbd3bb48fe 100644 --- a/packages/go/cypher/models/pgsql/translate/renamer.go +++ b/packages/go/cypher/models/pgsql/translate/renamer.go @@ -51,14 +51,14 @@ func rewriteCompositeTypeFieldReference(scopeIdentifier pgsql.Identifier, compos // is wrapped in a parenthetical: // // // a.properties with scopeIdentifier 's0' becomes -> (s0.a).properties -// pgsql.CompoundExpression{ -// &pgsql.Parenthetical{ -// Expression: pgsql.CompoundIdentifier{scopeIdentifier, fieldReference.Root()}, -// }, // -// fieldReference[1:], -// } +// pgsql.CompoundExpression{ +// &pgsql.Parenthetical{ +// Expression: pgsql.CompoundIdentifier{scopeIdentifier, fieldReference.Root()}, +// }, // +// fieldReference[1:], +// } type IdentifierRewriter struct { walk.HierarchicalVisitor[pgsql.SyntaxNode] diff --git a/packages/go/cypher/models/pgsql/translate/tracking.go b/packages/go/cypher/models/pgsql/translate/tracking.go index 60b93b863b..9c381210fa 100644 --- a/packages/go/cypher/models/pgsql/translate/tracking.go +++ b/packages/go/cypher/models/pgsql/translate/tracking.go @@ -18,9 +18,10 @@ package translate import ( "fmt" + "strconv" + "github.com/specterops/bloodhound/cypher/models" "github.com/specterops/bloodhound/cypher/models/pgsql" - "strconv" ) type IdentifierGenerator map[pgsql.DataType]int diff --git a/packages/go/cypher/models/pgsql/translate/translation.go b/packages/go/cypher/models/pgsql/translate/translation.go index cead07d416..c1367dd86b 100644 --- a/packages/go/cypher/models/pgsql/translate/translation.go +++ b/packages/go/cypher/models/pgsql/translate/translation.go @@ -18,10 +18,11 @@ package translate import ( "fmt" + "strings" + "github.com/specterops/bloodhound/cypher/models" cypher "github.com/specterops/bloodhound/cypher/models/cypher" "github.com/specterops/bloodhound/cypher/models/pgsql" - "strings" ) func translateCypherAssignmentOperator(operator cypher.AssignmentOperator) (pgsql.Operator, error) { diff --git a/packages/go/cypher/models/pgsql/translate/translator.go b/packages/go/cypher/models/pgsql/translate/translator.go index 0c71da1530..b590a730d2 100644 --- a/packages/go/cypher/models/pgsql/translate/translator.go +++ b/packages/go/cypher/models/pgsql/translate/translator.go @@ -18,12 +18,13 @@ package translate import ( "fmt" + "strings" + "github.com/specterops/bloodhound/cypher/models" "github.com/specterops/bloodhound/cypher/models/cypher" "github.com/specterops/bloodhound/cypher/models/pgsql" "github.com/specterops/bloodhound/cypher/models/walk" "github.com/specterops/bloodhound/dawgs/graph" - "strings" ) type State int diff --git a/packages/go/cypher/models/pgsql/translate/translator_test.go b/packages/go/cypher/models/pgsql/translate/translator_test.go index 15f9ddb023..af5fd15e1a 100644 --- a/packages/go/cypher/models/pgsql/translate/translator_test.go +++ b/packages/go/cypher/models/pgsql/translate/translator_test.go @@ -18,10 +18,11 @@ package translate_test import ( "fmt" + "testing" + "github.com/specterops/bloodhound/cypher/models/pgsql" "github.com/specterops/bloodhound/cypher/models/pgsql/test" "github.com/specterops/bloodhound/dawgs/graph" - "testing" ) var ( diff --git a/packages/go/cypher/models/pgsql/translate/traversal.go b/packages/go/cypher/models/pgsql/translate/traversal.go index e3ea7a17fe..2524f2f15d 100644 --- a/packages/go/cypher/models/pgsql/translate/traversal.go +++ b/packages/go/cypher/models/pgsql/translate/traversal.go @@ -18,6 +18,7 @@ package translate import ( "fmt" + "github.com/specterops/bloodhound/cypher/models" "github.com/specterops/bloodhound/cypher/models/pgsql" "github.com/specterops/bloodhound/dawgs/graph" diff --git a/packages/go/cypher/models/pgsql/translate/update.go b/packages/go/cypher/models/pgsql/translate/update.go index d2ed781d2c..7566bf65d2 100644 --- a/packages/go/cypher/models/pgsql/translate/update.go +++ b/packages/go/cypher/models/pgsql/translate/update.go @@ -18,6 +18,7 @@ package translate import ( "fmt" + "github.com/specterops/bloodhound/cypher/models" "github.com/specterops/bloodhound/cypher/models/pgsql" ) diff --git a/packages/go/cypher/models/pgsql/type.go b/packages/go/cypher/models/pgsql/type.go index 9749f537bf..4954d46d3c 100644 --- a/packages/go/cypher/models/pgsql/type.go +++ b/packages/go/cypher/models/pgsql/type.go @@ -20,9 +20,10 @@ import ( "bytes" "encoding/json" + "reflect" + "github.com/jackc/pgtype" "github.com/specterops/bloodhound/dawgs/graph" - "reflect" ) func ValueToJSONB(value any) (pgtype.JSONB, error) { diff --git a/packages/go/cypher/models/pgsql/visualization/puml.go b/packages/go/cypher/models/pgsql/visualization/puml.go index dc1485b36e..9db4896af7 100644 --- a/packages/go/cypher/models/pgsql/visualization/puml.go +++ b/packages/go/cypher/models/pgsql/visualization/puml.go @@ -18,10 +18,11 @@ package visualization import ( "fmt" - "github.com/specterops/bloodhound/cypher/models/pgsql" "io" "os" "strings" + + "github.com/specterops/bloodhound/cypher/models/pgsql" ) func WriteStrings(writer io.Writer, strings ...string) error { diff --git a/packages/go/cypher/models/pgsql/visualization/visualizer.go b/packages/go/cypher/models/pgsql/visualization/visualizer.go index 1c83d65fdc..1997726948 100644 --- a/packages/go/cypher/models/pgsql/visualization/visualizer.go +++ b/packages/go/cypher/models/pgsql/visualization/visualizer.go @@ -18,10 +18,11 @@ package visualization import ( "fmt" - "github.com/specterops/bloodhound/cypher/models/walk" "strconv" "strings" + "github.com/specterops/bloodhound/cypher/models/walk" + "github.com/specterops/bloodhound/cypher/models/pgsql" "github.com/specterops/bloodhound/cypher/models/pgsql/format" ) diff --git a/packages/go/cypher/models/pgsql/visualization/visualizer_test.go b/packages/go/cypher/models/pgsql/visualization/visualizer_test.go index 1e0080a836..07687ac256 100644 --- a/packages/go/cypher/models/pgsql/visualization/visualizer_test.go +++ b/packages/go/cypher/models/pgsql/visualization/visualizer_test.go @@ -18,9 +18,10 @@ package visualization import ( "bytes" - "github.com/specterops/bloodhound/cypher/models/pgsql/test" "testing" + "github.com/specterops/bloodhound/cypher/models/pgsql/test" + "github.com/specterops/bloodhound/cypher/frontend" "github.com/specterops/bloodhound/cypher/models/pgsql/translate" "github.com/stretchr/testify/require" diff --git a/packages/go/cypher/models/walk/walk.go b/packages/go/cypher/models/walk/walk.go index e13aabc5a9..c2f9a75176 100644 --- a/packages/go/cypher/models/walk/walk.go +++ b/packages/go/cypher/models/walk/walk.go @@ -19,6 +19,7 @@ package walk import ( "errors" "fmt" + "github.com/specterops/bloodhound/cypher/models/cypher" "github.com/specterops/bloodhound/cypher/models/pgsql" ) diff --git a/packages/go/cypher/models/walk/walk_cypher.go b/packages/go/cypher/models/walk/walk_cypher.go index 0254499e19..ee7d0a581a 100644 --- a/packages/go/cypher/models/walk/walk_cypher.go +++ b/packages/go/cypher/models/walk/walk_cypher.go @@ -18,6 +18,7 @@ package walk import ( "fmt" + "github.com/specterops/bloodhound/cypher/models/cypher" "github.com/specterops/bloodhound/slicesext" diff --git a/packages/go/cypher/models/walk/walk_pgsql.go b/packages/go/cypher/models/walk/walk_pgsql.go index df9ef8b0d5..65df9c7cce 100644 --- a/packages/go/cypher/models/walk/walk_pgsql.go +++ b/packages/go/cypher/models/walk/walk_pgsql.go @@ -18,6 +18,7 @@ package walk import ( "fmt" + "github.com/specterops/bloodhound/cypher/models/pgsql" "github.com/specterops/bloodhound/slicesext" ) diff --git a/packages/go/cypher/models/walk/walk_test.go b/packages/go/cypher/models/walk/walk_test.go index cda26c094b..e00d6073fd 100644 --- a/packages/go/cypher/models/walk/walk_test.go +++ b/packages/go/cypher/models/walk/walk_test.go @@ -17,9 +17,10 @@ package walk_test import ( + "testing" + "github.com/specterops/bloodhound/cypher/models/cypher" "github.com/specterops/bloodhound/cypher/models/walk" - "testing" "github.com/specterops/bloodhound/cypher/frontend" "github.com/specterops/bloodhound/cypher/test" diff --git a/packages/go/cypher/test/test.go b/packages/go/cypher/test/test.go index e8f41fc762..ee899e5129 100644 --- a/packages/go/cypher/test/test.go +++ b/packages/go/cypher/test/test.go @@ -20,12 +20,13 @@ import ( "bytes" "embed" "encoding/json" - "github.com/specterops/bloodhound/cypher/models/cypher" - format2 "github.com/specterops/bloodhound/cypher/models/cypher/format" "io" "regexp" "testing" + "github.com/specterops/bloodhound/cypher/models/cypher" + format2 "github.com/specterops/bloodhound/cypher/models/cypher/format" + "github.com/specterops/bloodhound/cypher/frontend" "github.com/stretchr/testify/require" ) diff --git a/packages/go/dawgs/drivers/pg/pg_integration_test.go b/packages/go/dawgs/drivers/pg/pg_integration_test.go index 79e0bda1b2..9b1eee906d 100644 --- a/packages/go/dawgs/drivers/pg/pg_integration_test.go +++ b/packages/go/dawgs/drivers/pg/pg_integration_test.go @@ -21,6 +21,8 @@ package pg_test import ( "context" + "testing" + "github.com/specterops/bloodhound/dawgs" "github.com/specterops/bloodhound/dawgs/drivers/pg" "github.com/specterops/bloodhound/dawgs/graph" @@ -28,7 +30,6 @@ import ( "github.com/specterops/bloodhound/graphschema/ad" "github.com/specterops/bloodhound/src/test/integration/utils" "github.com/stretchr/testify/require" - "testing" ) const murderDB = ` diff --git a/packages/go/dawgs/drivers/pg/query.go b/packages/go/dawgs/drivers/pg/query.go index e615081626..423d28a24e 100644 --- a/packages/go/dawgs/drivers/pg/query.go +++ b/packages/go/dawgs/drivers/pg/query.go @@ -18,6 +18,7 @@ package pg import ( "context" + "github.com/specterops/bloodhound/cypher/models/pgsql/translate" "github.com/specterops/bloodhound/dawgs/graph" "github.com/specterops/bloodhound/dawgs/query" @@ -63,7 +64,6 @@ func (s *liveQuery) Query(delegate func(results graph.Result) error, finalCriter } } - func (s *liveQuery) QueryAllShortestPaths(delegate func(results graph.Result) error, finalCriteria ...graph.Criteria) error { for _, criteria := range finalCriteria { s.queryBuilder.Apply(criteria) diff --git a/packages/go/dawgs/drivers/pg/transaction.go b/packages/go/dawgs/drivers/pg/transaction.go index 9876f47f2f..af35861636 100644 --- a/packages/go/dawgs/drivers/pg/transaction.go +++ b/packages/go/dawgs/drivers/pg/transaction.go @@ -19,6 +19,7 @@ package pg import ( "context" "fmt" + "github.com/specterops/bloodhound/cypher/models/pgsql" "github.com/specterops/bloodhound/cypher/models/pgsql/translate" diff --git a/packages/go/dawgs/drivers/pg/util.go b/packages/go/dawgs/drivers/pg/util.go index 77dd10a34e..e3e75581ad 100644 --- a/packages/go/dawgs/drivers/pg/util.go +++ b/packages/go/dawgs/drivers/pg/util.go @@ -1,3 +1,19 @@ +// Copyright 2024 Specter Ops, Inc. +// +// Licensed under the Apache License, Version 2.0 +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + package pg import "github.com/specterops/bloodhound/dawgs/graph" diff --git a/packages/go/dawgs/query/builder.go b/packages/go/dawgs/query/builder.go index f32b689667..29a7286f89 100644 --- a/packages/go/dawgs/query/builder.go +++ b/packages/go/dawgs/query/builder.go @@ -32,8 +32,8 @@ type Cache struct { } type Builder struct { - regularQuery *cypher.RegularQuery - cache *Cache + regularQuery *cypher.RegularQuery + cache *Cache } func NewBuilder(cache *Cache) *Builder { diff --git a/packages/go/dawgs/query/neo4j/neo4j.go b/packages/go/dawgs/query/neo4j/neo4j.go index aa2cc8eec4..31540b14db 100644 --- a/packages/go/dawgs/query/neo4j/neo4j.go +++ b/packages/go/dawgs/query/neo4j/neo4j.go @@ -20,6 +20,7 @@ import ( "bytes" "errors" "fmt" + cypherBackend "github.com/specterops/bloodhound/cypher/models/cypher/format" "github.com/specterops/bloodhound/cypher/models/cypher" diff --git a/packages/go/openapi/doc/openapi.json b/packages/go/openapi/doc/openapi.json index 8d892b6096..e30335bdf5 100644 --- a/packages/go/openapi/doc/openapi.json +++ b/packages/go/openapi/doc/openapi.json @@ -4225,6 +4225,13 @@ "schema": { "$ref": "#/components/schemas/api.params.predicate.filter.string" } + }, + { + "name": "scope", + "in": "query", + "schema": { + "$ref": "#/components/schemas/api.params.predicate.filter.contains" + } } ], "responses": {