diff --git a/cmd/api/src/database/graphschema.go b/cmd/api/src/database/graphschema.go index 6a3e69d0b92..d20d2b0692d 100644 --- a/cmd/api/src/database/graphschema.go +++ b/cmd/api/src/database/graphschema.go @@ -59,10 +59,11 @@ type OpenGraphSchema interface { GetEnvironments(ctx context.Context) ([]model.SchemaEnvironment, error) DeleteEnvironment(ctx context.Context, environmentId int32) error - CreateSchemaRelationshipFinding(ctx context.Context, extensionId int32, relationshipKindId int32, environmentId int32, name string, displayName string) (model.SchemaRelationshipFinding, error) - GetSchemaRelationshipFindingById(ctx context.Context, findingId int32) (model.SchemaRelationshipFinding, error) - GetSchemaRelationshipFindingByName(ctx context.Context, name string) (model.SchemaRelationshipFinding, error) - DeleteSchemaRelationshipFinding(ctx context.Context, findingId int32) error + CreateSchemaFinding(ctx context.Context, findingType model.SchemaFindingType, extensionId, kindId, environmentId int32, name, displayName string) (model.SchemaFinding, error) + GetSchemaFindingsBySchemaExtensionId(ctx context.Context, extensionId int32) ([]model.SchemaFinding, error) + GetSchemaFindingById(ctx context.Context, findingId int32) (model.SchemaFinding, error) + GetSchemaFindingByName(ctx context.Context, name string) (model.SchemaFinding, error) + DeleteSchemaFinding(ctx context.Context, findingId int32) error CreateRemediation(ctx context.Context, findingId int32, shortDescription string, longDescription string, shortRemediation string, longRemediation string) (model.Remediation, error) GetRemediationByFindingId(ctx context.Context, findingId int32) (model.Remediation, error) @@ -763,61 +764,61 @@ func (s *BloodhoundDB) DeleteEnvironment(ctx context.Context, environmentId int3 return nil } -// CreateSchemaRelationshipFinding - creates a new schema relationship finding. -func (s *BloodhoundDB) CreateSchemaRelationshipFinding(ctx context.Context, extensionId int32, relationshipKindId int32, environmentId int32, name string, displayName string) (model.SchemaRelationshipFinding, error) { - var finding model.SchemaRelationshipFinding +// CreateSchemaFinding - creates a new schema relationship finding. +func (s *BloodhoundDB) CreateSchemaFinding(ctx context.Context, findingType model.SchemaFindingType, extensionId, kindId, environmentId int32, name, displayName string) (model.SchemaFinding, error) { + var finding model.SchemaFinding if result := s.db.WithContext(ctx).Raw(fmt.Sprintf(` - INSERT INTO %s (schema_extension_id, relationship_kind_id, environment_id, name, display_name, created_at) - VALUES (?, ?, ?, ?, ?, NOW()) - RETURNING id, schema_extension_id, relationship_kind_id, environment_id, name, display_name, created_at`, + INSERT INTO %s (type, schema_extension_id, kind_id, environment_id, name, display_name, created_at) + VALUES (?, ?, ?, ?, ?, ?, NOW()) + RETURNING id, schema_extension_id, kind_id, environment_id, name, display_name, created_at`, finding.TableName()), - extensionId, relationshipKindId, environmentId, name, displayName).Scan(&finding); result.Error != nil { + findingType, extensionId, kindId, environmentId, name, displayName).Scan(&finding); result.Error != nil { if strings.Contains(result.Error.Error(), DuplicateKeyValueErrorString) { - return model.SchemaRelationshipFinding{}, fmt.Errorf("%w: %s", model.ErrDuplicateSchemaRelationshipFindingName, name) + return model.SchemaFinding{}, fmt.Errorf("%w: %s", model.ErrDuplicateSchemaFindingName, name) } - return model.SchemaRelationshipFinding{}, CheckError(result) + return model.SchemaFinding{}, CheckError(result) } return finding, nil } -// GetSchemaRelationshipFindingById - retrieves a schema relationship finding by id. -func (s *BloodhoundDB) GetSchemaRelationshipFindingById(ctx context.Context, findingId int32) (model.SchemaRelationshipFinding, error) { - var finding model.SchemaRelationshipFinding +// GetSchemaFindingById - retrieves a schema relationship finding by id. +func (s *BloodhoundDB) GetSchemaFindingById(ctx context.Context, findingId int32) (model.SchemaFinding, error) { + var finding model.SchemaFinding if result := s.db.WithContext(ctx).Raw(fmt.Sprintf(` - SELECT id, schema_extension_id, relationship_kind_id, environment_id, name, display_name, created_at + SELECT id, type, schema_extension_id, kind_id, environment_id, name, display_name, created_at FROM %s WHERE id = ?`, finding.TableName()), findingId).Scan(&finding); result.Error != nil { - return model.SchemaRelationshipFinding{}, CheckError(result) + return model.SchemaFinding{}, CheckError(result) } else if result.RowsAffected == 0 { - return model.SchemaRelationshipFinding{}, ErrNotFound + return model.SchemaFinding{}, ErrNotFound } return finding, nil } -// GetSchemaRelationshipFindingByName - retrieves a schema relationship finding by finding name. -func (s *BloodhoundDB) GetSchemaRelationshipFindingByName(ctx context.Context, name string) (model.SchemaRelationshipFinding, error) { - var finding model.SchemaRelationshipFinding +// GetSchemaFindingByName - retrieves a schema relationship finding by finding name. +func (s *BloodhoundDB) GetSchemaFindingByName(ctx context.Context, name string) (model.SchemaFinding, error) { + var finding model.SchemaFinding if result := s.db.WithContext(ctx).Raw(fmt.Sprintf(` - SELECT id, schema_extension_id, relationship_kind_id, environment_id, name, display_name, created_at + SELECT id, type, schema_extension_id, kind_id, environment_id, name, display_name, created_at FROM %s WHERE name = ?`, finding.TableName()), name).Scan(&finding); result.Error != nil { - return model.SchemaRelationshipFinding{}, CheckError(result) + return model.SchemaFinding{}, CheckError(result) } else if result.RowsAffected == 0 { - return model.SchemaRelationshipFinding{}, ErrNotFound + return model.SchemaFinding{}, ErrNotFound } return finding, nil } -// DeleteSchemaRelationshipFinding - deletes a schema relationship finding by id. -func (s *BloodhoundDB) DeleteSchemaRelationshipFinding(ctx context.Context, findingId int32) error { - var finding model.SchemaRelationshipFinding +// DeleteSchemaFinding - deletes a schema relationship finding by id. +func (s *BloodhoundDB) DeleteSchemaFinding(ctx context.Context, findingId int32) error { + var finding model.SchemaFinding if result := s.db.WithContext(ctx).Exec(fmt.Sprintf(`DELETE FROM %s WHERE id = ?`, finding.TableName()), findingId); result.Error != nil { return CheckError(result) @@ -828,12 +829,13 @@ func (s *BloodhoundDB) DeleteSchemaRelationshipFinding(ctx context.Context, find return nil } -// GetSchemaRelationshipFindingsBySchemaExtensionId - returns all findings by extension id. -func (s *BloodhoundDB) GetSchemaRelationshipFindingsBySchemaExtensionId(ctx context.Context, extensionId int32) ([]model.SchemaRelationshipFinding, error) { - var findings = make([]model.SchemaRelationshipFinding, 0) +// GetSchemaFindingsBySchemaExtensionId - returns all findings by extension id. +func (s *BloodhoundDB) GetSchemaFindingsBySchemaExtensionId(ctx context.Context, extensionId int32) ([]model.SchemaFinding, error) { + var findings []model.SchemaFinding + if result := s.db.WithContext(ctx).Raw(fmt.Sprintf(` - SELECT id, schema_extension_id, relationship_kind_id, environment_id, name, display_name, created_at - FROM %s WHERE schema_extension_id = ? ORDER BY id`, model.SchemaRelationshipFinding{}.TableName()), extensionId).Scan(&findings); result.Error != nil { + SELECT id, type, schema_extension_id, kind_id, environment_id, name, display_name, created_at + FROM %s WHERE schema_extension_id = ? ORDER BY id`, model.SchemaFinding{}.TableName()), extensionId).Scan(&findings); result.Error != nil { return findings, CheckError(result) } return findings, nil diff --git a/cmd/api/src/database/graphschema_integration_test.go b/cmd/api/src/database/graphschema_integration_test.go index 77622a54c1c..5da993a911f 100644 --- a/cmd/api/src/database/graphschema_integration_test.go +++ b/cmd/api/src/database/graphschema_integration_test.go @@ -4723,13 +4723,13 @@ func TestDatabase_Environments_CRUD(t *testing.T) { // may already contain existing records. These tests should be written to account for said data. func TestDatabase_Findings_CRUD(t *testing.T) { // Helper functions to assert on Findings - assertContainsFindings := func(t *testing.T, got []model.SchemaRelationshipFinding, expected ...model.SchemaRelationshipFinding) { + assertContainsFindings := func(t *testing.T, got []model.SchemaFinding, expected ...model.SchemaFinding) { t.Helper() for _, want := range expected { found := false for _, finding := range got { if finding.SchemaExtensionId == want.SchemaExtensionId && - finding.RelationshipKindId == want.RelationshipKindId && + finding.KindId == want.KindId && finding.EnvironmentId == want.EnvironmentId && finding.Name == want.Name && finding.DisplayName == want.DisplayName { @@ -4746,16 +4746,16 @@ func TestDatabase_Findings_CRUD(t *testing.T) { } } - assertContainsFinding := func(t *testing.T, got model.SchemaRelationshipFinding, expected ...model.SchemaRelationshipFinding) { + assertContainsFinding := func(t *testing.T, got model.SchemaFinding, expected ...model.SchemaFinding) { t.Helper() - assertContainsFindings(t, []model.SchemaRelationshipFinding{got}, expected...) + assertContainsFindings(t, []model.SchemaFinding{got}, expected...) } tests := []struct { name string assert func(t *testing.T, testSuite IntegrationTestSuite) }{ - // CreateSchemaRelationshipFinding + // CreateSchemaFinding { name: "Success: create a finding", assert: func(t *testing.T, testSuite IntegrationTestSuite) { @@ -4775,20 +4775,20 @@ func TestDatabase_Findings_CRUD(t *testing.T) { environment, err = testSuite.BHDatabase.CreateEnvironment(testSuite.Context, environment.SchemaExtensionId, environment.EnvironmentKindId, environment.SourceKindId) assert.NoError(t, err, "unexpected error occurred when creating environment") - finding := model.SchemaRelationshipFinding{ - SchemaExtensionId: extension.ID, - RelationshipKindId: 1, - EnvironmentId: environment.ID, - Name: "finding", - DisplayName: "display name", + finding := model.SchemaFinding{ + SchemaExtensionId: extension.ID, + KindId: 1, + EnvironmentId: environment.ID, + Name: "finding", + DisplayName: "display name", } // Create new finding - newFinding, err := testSuite.BHDatabase.CreateSchemaRelationshipFinding(testSuite.Context, finding.SchemaExtensionId, finding.RelationshipKindId, finding.EnvironmentId, finding.Name, finding.DisplayName) + newFinding, err := testSuite.BHDatabase.CreateSchemaFinding(testSuite.Context, model.SchemaFindingTypeRelationship, finding.SchemaExtensionId, finding.KindId, finding.EnvironmentId, finding.Name, finding.DisplayName) assert.NoError(t, err, "unexpected error occurred when creating finding") // Validate created finding is as expected - retrievedFinding, err := testSuite.BHDatabase.GetSchemaRelationshipFindingById(testSuite.Context, newFinding.ID) + retrievedFinding, err := testSuite.BHDatabase.GetSchemaFindingById(testSuite.Context, newFinding.ID) assert.NoError(t, err, "unexpected error occurred when retrieving finding") assertContainsFinding(t, retrievedFinding, finding) @@ -4814,22 +4814,22 @@ func TestDatabase_Findings_CRUD(t *testing.T) { environment, err = testSuite.BHDatabase.CreateEnvironment(testSuite.Context, environment.SchemaExtensionId, environment.EnvironmentKindId, environment.SourceKindId) assert.NoError(t, err, "unexpected error occurred when creating environment") - finding := model.SchemaRelationshipFinding{ - SchemaExtensionId: extension.ID, - RelationshipKindId: 1, - EnvironmentId: environment.ID, - Name: "finding", - DisplayName: "display name", + finding := model.SchemaFinding{ + SchemaExtensionId: extension.ID, + KindId: 1, + EnvironmentId: environment.ID, + Name: "finding", + DisplayName: "display name", } // Create new finding - newFinding, err := testSuite.BHDatabase.CreateSchemaRelationshipFinding(testSuite.Context, finding.SchemaExtensionId, finding.RelationshipKindId, finding.EnvironmentId, finding.Name, finding.DisplayName) + newFinding, err := testSuite.BHDatabase.CreateSchemaFinding(testSuite.Context, model.SchemaFindingTypeRelationship, finding.SchemaExtensionId, finding.KindId, finding.EnvironmentId, finding.Name, finding.DisplayName) assert.NoError(t, err, "unexpected error occurred when creating finding") // Create same finding again - _, err = testSuite.BHDatabase.CreateSchemaRelationshipFinding(testSuite.Context, newFinding.SchemaExtensionId, newFinding.RelationshipKindId, newFinding.EnvironmentId, newFinding.Name, newFinding.DisplayName) + _, err = testSuite.BHDatabase.CreateSchemaFinding(testSuite.Context, model.SchemaFindingTypeRelationship, newFinding.SchemaExtensionId, newFinding.KindId, newFinding.EnvironmentId, newFinding.Name, newFinding.DisplayName) // Assert error - assert.ErrorIs(t, err, model.ErrDuplicateSchemaRelationshipFindingName) + assert.ErrorIs(t, err, model.ErrDuplicateSchemaFindingName) }, }, // GetSchemaRelationshipFindingById @@ -4852,20 +4852,20 @@ func TestDatabase_Findings_CRUD(t *testing.T) { environment, err = testSuite.BHDatabase.CreateEnvironment(testSuite.Context, environment.SchemaExtensionId, environment.EnvironmentKindId, environment.SourceKindId) assert.NoError(t, err, "unexpected error occurred when creating environment") - finding := model.SchemaRelationshipFinding{ - SchemaExtensionId: extension.ID, - RelationshipKindId: 1, - EnvironmentId: environment.ID, - Name: "finding", - DisplayName: "display name", + finding := model.SchemaFinding{ + SchemaExtensionId: extension.ID, + KindId: 1, + EnvironmentId: environment.ID, + Name: "finding", + DisplayName: "display name", } // Create new finding - newFinding, err := testSuite.BHDatabase.CreateSchemaRelationshipFinding(testSuite.Context, finding.SchemaExtensionId, finding.RelationshipKindId, finding.EnvironmentId, finding.Name, finding.DisplayName) + newFinding, err := testSuite.BHDatabase.CreateSchemaFinding(testSuite.Context, model.SchemaFindingTypeRelationship, finding.SchemaExtensionId, finding.KindId, finding.EnvironmentId, finding.Name, finding.DisplayName) assert.NoError(t, err, "unexpected error occurred when creating finding") // Validate finding is as expected - retrievedFinding, err := testSuite.BHDatabase.GetSchemaRelationshipFindingById(testSuite.Context, newFinding.ID) + retrievedFinding, err := testSuite.BHDatabase.GetSchemaFindingById(testSuite.Context, newFinding.ID) assert.NoError(t, err, "unexpected error occurred when retrieving finding by id") assertContainsFinding(t, retrievedFinding, finding) @@ -4876,11 +4876,11 @@ func TestDatabase_Findings_CRUD(t *testing.T) { assert: func(t *testing.T, testSuite IntegrationTestSuite) { t.Helper() - _, err := testSuite.BHDatabase.GetSchemaRelationshipFindingById(testSuite.Context, int32(5000)) + _, err := testSuite.BHDatabase.GetSchemaFindingById(testSuite.Context, int32(5000)) require.ErrorIs(t, err, database.ErrNotFound) }, }, - // GetSchemaRelationshipFindingByName + // GetSchemaFindingByName { name: "Success: get finding by name", assert: func(t *testing.T, testSuite IntegrationTestSuite) { @@ -4900,20 +4900,20 @@ func TestDatabase_Findings_CRUD(t *testing.T) { environment, err = testSuite.BHDatabase.CreateEnvironment(testSuite.Context, environment.SchemaExtensionId, environment.EnvironmentKindId, environment.SourceKindId) assert.NoError(t, err, "unexpected error occurred when creating environment") - finding := model.SchemaRelationshipFinding{ - SchemaExtensionId: extension.ID, - RelationshipKindId: 1, - EnvironmentId: environment.ID, - Name: "finding", - DisplayName: "display name", + finding := model.SchemaFinding{ + SchemaExtensionId: extension.ID, + KindId: 1, + EnvironmentId: environment.ID, + Name: "finding", + DisplayName: "display name", } // Create new finding - newFinding, err := testSuite.BHDatabase.CreateSchemaRelationshipFinding(testSuite.Context, finding.SchemaExtensionId, finding.RelationshipKindId, finding.EnvironmentId, finding.Name, finding.DisplayName) + newFinding, err := testSuite.BHDatabase.CreateSchemaFinding(testSuite.Context, model.SchemaFindingTypeRelationship, finding.SchemaExtensionId, finding.KindId, finding.EnvironmentId, finding.Name, finding.DisplayName) assert.NoError(t, err, "unexpected error occurred when creating finding") // Validate finding is as expected - retrievedFinding, err := testSuite.BHDatabase.GetSchemaRelationshipFindingByName(testSuite.Context, newFinding.Name) + retrievedFinding, err := testSuite.BHDatabase.GetSchemaFindingByName(testSuite.Context, newFinding.Name) assert.NoError(t, err, "unexpected error occurred when retrieving finding by name") assertContainsFinding(t, retrievedFinding, finding) @@ -4924,11 +4924,11 @@ func TestDatabase_Findings_CRUD(t *testing.T) { assert: func(t *testing.T, testSuite IntegrationTestSuite) { t.Helper() - _, err := testSuite.BHDatabase.GetSchemaRelationshipFindingByName(testSuite.Context, "doesnotexist") + _, err := testSuite.BHDatabase.GetSchemaFindingByName(testSuite.Context, "doesnotexist") require.ErrorIs(t, err, database.ErrNotFound) }, }, - // DeleteSchemaRelationshipFinding + // DeleteSchemaFinding { name: "Success: finding deleted", assert: func(t *testing.T, testSuite IntegrationTestSuite) { @@ -4948,26 +4948,26 @@ func TestDatabase_Findings_CRUD(t *testing.T) { environment, err = testSuite.BHDatabase.CreateEnvironment(testSuite.Context, environment.SchemaExtensionId, environment.EnvironmentKindId, environment.SourceKindId) assert.NoError(t, err, "unexpected error occurred when creating environment") - finding := model.SchemaRelationshipFinding{ - SchemaExtensionId: extension.ID, - RelationshipKindId: 1, - EnvironmentId: environment.ID, - Name: "finding", - DisplayName: "display name", + finding := model.SchemaFinding{ + SchemaExtensionId: extension.ID, + KindId: 1, + EnvironmentId: environment.ID, + Name: "finding", + DisplayName: "display name", } // Create new finding - newFinding, err := testSuite.BHDatabase.CreateSchemaRelationshipFinding(testSuite.Context, finding.SchemaExtensionId, finding.RelationshipKindId, finding.EnvironmentId, finding.Name, finding.DisplayName) + newFinding, err := testSuite.BHDatabase.CreateSchemaFinding(testSuite.Context, model.SchemaFindingTypeRelationship, finding.SchemaExtensionId, finding.KindId, finding.EnvironmentId, finding.Name, finding.DisplayName) assert.NoError(t, err, "unexpected error occurred when creating finding") assertContainsFinding(t, newFinding, finding) // Delete Finding - err = testSuite.BHDatabase.DeleteSchemaRelationshipFinding(testSuite.Context, newFinding.ID) + err = testSuite.BHDatabase.DeleteSchemaFinding(testSuite.Context, newFinding.ID) assert.NoError(t, err, "unexpected error occurred when deleting finding") // Validate finding no longer exists - _, err = testSuite.BHDatabase.GetSchemaRelationshipFindingById(testSuite.Context, newFinding.ID) + _, err = testSuite.BHDatabase.GetSchemaFindingById(testSuite.Context, newFinding.ID) require.EqualError(t, err, database.ErrNotFound.Error()) }, }, @@ -4977,18 +4977,18 @@ func TestDatabase_Findings_CRUD(t *testing.T) { t.Helper() // Delete Finding - err := testSuite.BHDatabase.DeleteSchemaRelationshipFinding(testSuite.Context, int32(10000)) + err := testSuite.BHDatabase.DeleteSchemaFinding(testSuite.Context, int32(10000)) assert.EqualError(t, err, database.ErrNotFound.Error()) }, }, - // GetSchemaRelationshipFindingsBySchemaExtensionId + // GetSchemaFindingsBySchemaExtensionId { name: "Success: no findings found", assert: func(t *testing.T, testSuite IntegrationTestSuite) { t.Helper() // Get Findings - findings, err := testSuite.BHDatabase.GetSchemaRelationshipFindingsBySchemaExtensionId(testSuite.Context, int32(10000)) + findings, err := testSuite.BHDatabase.GetSchemaFindingsBySchemaExtensionId(testSuite.Context, int32(10000)) assert.NoError(t, err, "unexpected error occurred when retrieving findings by schema extension id") assert.Len(t, findings, 0, "findings were expected to be 0 when extension id was not found") @@ -5067,35 +5067,35 @@ func TestDatabase_Findings_CRUD(t *testing.T) { require.NoError(t, err, "unexpected error occurred when retrieving edge kind") // Assign Extension ID, Edge Kind, & Environment ID to Finding 1 - finding1 := model.SchemaRelationshipFinding{ - SchemaExtensionId: createdExtension.ID, - RelationshipKindId: edgeKind.ID, - EnvironmentId: createdEnvironment.ID, - Name: "Finding_1", - DisplayName: "Finding 1", + finding1 := model.SchemaFinding{ + SchemaExtensionId: createdExtension.ID, + KindId: edgeKind.ID, + EnvironmentId: createdEnvironment.ID, + Name: "Finding_1", + DisplayName: "Finding 1", } // Assign Extension ID, Edge Kind, & Environment ID to Finding 2 - finding2 := model.SchemaRelationshipFinding{ - SchemaExtensionId: createdExtension.ID, - RelationshipKindId: edgeKind.ID, - EnvironmentId: createdEnvironment.ID, - Name: "Finding_2", - DisplayName: "Finding 2", + finding2 := model.SchemaFinding{ + SchemaExtensionId: createdExtension.ID, + KindId: edgeKind.ID, + EnvironmentId: createdEnvironment.ID, + Name: "Finding_2", + DisplayName: "Finding 2", } // Create Finding 1 - _, err = testSuite.BHDatabase.CreateSchemaRelationshipFinding(testSuite.Context, + _, err = testSuite.BHDatabase.CreateSchemaFinding(testSuite.Context, model.SchemaFindingTypeRelationship, createdExtension.ID, edgeKind.ID, createdEnvironment.ID, finding1.Name, finding1.DisplayName) require.NoError(t, err, "unexpected error occurred when creating finding 1") // Create Finding 2 - _, err = testSuite.BHDatabase.CreateSchemaRelationshipFinding(testSuite.Context, + _, err = testSuite.BHDatabase.CreateSchemaFinding(testSuite.Context, model.SchemaFindingTypeRelationship, createdExtension.ID, edgeKind.ID, createdEnvironment.ID, finding2.Name, finding2.DisplayName) require.NoError(t, err, "unexpected error occurred when creating finding 2") // Get Findings by Extension ID - findings, err := testSuite.BHDatabase.GetSchemaRelationshipFindingsBySchemaExtensionId(testSuite.Context, createdExtension.ID) + findings, err := testSuite.BHDatabase.GetSchemaFindingsBySchemaExtensionId(testSuite.Context, createdExtension.ID) assert.NoError(t, err, "unexpected error occurred when getting findings by extension id") // Validate both findings exist on extension @@ -5166,16 +5166,16 @@ func TestDatabase_Remediations_CRUD(t *testing.T) { environment, err = testSuite.BHDatabase.CreateEnvironment(testSuite.Context, environment.SchemaExtensionId, environment.EnvironmentKindId, environment.SourceKindId) assert.NoError(t, err, "unexpected error occurred when creating environment") - finding := model.SchemaRelationshipFinding{ - SchemaExtensionId: extension.ID, - RelationshipKindId: 1, - EnvironmentId: environment.ID, - Name: "finding", - DisplayName: "display name", + finding := model.SchemaFinding{ + SchemaExtensionId: extension.ID, + KindId: 1, + EnvironmentId: environment.ID, + Name: "finding", + DisplayName: "display name", } // Create new finding - newFinding, err := testSuite.BHDatabase.CreateSchemaRelationshipFinding(testSuite.Context, finding.SchemaExtensionId, finding.RelationshipKindId, finding.EnvironmentId, finding.Name, finding.DisplayName) + newFinding, err := testSuite.BHDatabase.CreateSchemaFinding(testSuite.Context, model.SchemaFindingTypeRelationship, finding.SchemaExtensionId, finding.KindId, finding.EnvironmentId, finding.Name, finding.DisplayName) require.NoError(t, err, "unexpected error occurred when creating finding") remediation := model.Remediation{ @@ -5216,16 +5216,16 @@ func TestDatabase_Remediations_CRUD(t *testing.T) { environment, err = testSuite.BHDatabase.CreateEnvironment(testSuite.Context, environment.SchemaExtensionId, environment.EnvironmentKindId, environment.SourceKindId) assert.NoError(t, err, "unexpected error occurred when creating environment") - finding := model.SchemaRelationshipFinding{ - SchemaExtensionId: extension.ID, - RelationshipKindId: 1, - EnvironmentId: environment.ID, - Name: "finding", - DisplayName: "display name", + finding := model.SchemaFinding{ + SchemaExtensionId: extension.ID, + KindId: 1, + EnvironmentId: environment.ID, + Name: "finding", + DisplayName: "display name", } // Create new finding - newFinding, err := testSuite.BHDatabase.CreateSchemaRelationshipFinding(testSuite.Context, finding.SchemaExtensionId, finding.RelationshipKindId, finding.EnvironmentId, finding.Name, finding.DisplayName) + newFinding, err := testSuite.BHDatabase.CreateSchemaFinding(testSuite.Context, model.SchemaFindingTypeRelationship, finding.SchemaExtensionId, finding.KindId, finding.EnvironmentId, finding.Name, finding.DisplayName) require.NoError(t, err, "unexpected error occurred when creating finding") remediation := model.Remediation{ @@ -5265,16 +5265,16 @@ func TestDatabase_Remediations_CRUD(t *testing.T) { environment, err = testSuite.BHDatabase.CreateEnvironment(testSuite.Context, environment.SchemaExtensionId, environment.EnvironmentKindId, environment.SourceKindId) assert.NoError(t, err, "unexpected error occurred when creating environment") - finding := model.SchemaRelationshipFinding{ - SchemaExtensionId: extension.ID, - RelationshipKindId: 1, - EnvironmentId: environment.ID, - Name: "finding", - DisplayName: "display name", + finding := model.SchemaFinding{ + SchemaExtensionId: extension.ID, + KindId: 1, + EnvironmentId: environment.ID, + Name: "finding", + DisplayName: "display name", } // Create new finding - newFinding, err := testSuite.BHDatabase.CreateSchemaRelationshipFinding(testSuite.Context, finding.SchemaExtensionId, finding.RelationshipKindId, finding.EnvironmentId, finding.Name, finding.DisplayName) + newFinding, err := testSuite.BHDatabase.CreateSchemaFinding(testSuite.Context, model.SchemaFindingTypeRelationship, finding.SchemaExtensionId, finding.KindId, finding.EnvironmentId, finding.Name, finding.DisplayName) require.NoError(t, err, "unexpected error occurred when creating finding") remediation := model.Remediation{ @@ -5325,16 +5325,16 @@ func TestDatabase_Remediations_CRUD(t *testing.T) { environment, err = testSuite.BHDatabase.CreateEnvironment(testSuite.Context, environment.SchemaExtensionId, environment.EnvironmentKindId, environment.SourceKindId) assert.NoError(t, err, "unexpected error occurred when creating environment") - finding := model.SchemaRelationshipFinding{ - SchemaExtensionId: extension.ID, - RelationshipKindId: 1, - EnvironmentId: environment.ID, - Name: "finding", - DisplayName: "display name", + finding := model.SchemaFinding{ + SchemaExtensionId: extension.ID, + KindId: 1, + EnvironmentId: environment.ID, + Name: "finding", + DisplayName: "display name", } // Create new finding - newFinding, err := testSuite.BHDatabase.CreateSchemaRelationshipFinding(testSuite.Context, finding.SchemaExtensionId, finding.RelationshipKindId, finding.EnvironmentId, finding.Name, finding.DisplayName) + newFinding, err := testSuite.BHDatabase.CreateSchemaFinding(testSuite.Context, model.SchemaFindingTypeRelationship, finding.SchemaExtensionId, finding.KindId, finding.EnvironmentId, finding.Name, finding.DisplayName) require.NoError(t, err, "unexpected error occurred when creating finding") remediation := model.Remediation{ @@ -5385,16 +5385,16 @@ func TestDatabase_Remediations_CRUD(t *testing.T) { environment, err = testSuite.BHDatabase.CreateEnvironment(testSuite.Context, environment.SchemaExtensionId, environment.EnvironmentKindId, environment.SourceKindId) assert.NoError(t, err, "unexpected error occurred when creating environment") - finding := model.SchemaRelationshipFinding{ - SchemaExtensionId: extension.ID, - RelationshipKindId: 1, - EnvironmentId: environment.ID, - Name: "finding", - DisplayName: "display name", + finding := model.SchemaFinding{ + SchemaExtensionId: extension.ID, + KindId: 1, + EnvironmentId: environment.ID, + Name: "finding", + DisplayName: "display name", } // Create new finding - newFinding, err := testSuite.BHDatabase.CreateSchemaRelationshipFinding(testSuite.Context, finding.SchemaExtensionId, finding.RelationshipKindId, finding.EnvironmentId, finding.Name, finding.DisplayName) + newFinding, err := testSuite.BHDatabase.CreateSchemaFinding(testSuite.Context, model.SchemaFindingTypeRelationship, finding.SchemaExtensionId, finding.KindId, finding.EnvironmentId, finding.Name, finding.DisplayName) require.NoError(t, err, "unexpected error occurred when creating finding") remediation := model.Remediation{ @@ -5465,16 +5465,16 @@ func TestDatabase_Remediations_CRUD(t *testing.T) { environment, err = testSuite.BHDatabase.CreateEnvironment(testSuite.Context, environment.SchemaExtensionId, environment.EnvironmentKindId, environment.SourceKindId) assert.NoError(t, err, "unexpected error occurred when creating environment") - finding := model.SchemaRelationshipFinding{ - SchemaExtensionId: extension.ID, - RelationshipKindId: 1, - EnvironmentId: environment.ID, - Name: "finding", - DisplayName: "display name", + finding := model.SchemaFinding{ + SchemaExtensionId: extension.ID, + KindId: 1, + EnvironmentId: environment.ID, + Name: "finding", + DisplayName: "display name", } // Create new finding - newFinding, err := testSuite.BHDatabase.CreateSchemaRelationshipFinding(testSuite.Context, finding.SchemaExtensionId, finding.RelationshipKindId, finding.EnvironmentId, finding.Name, finding.DisplayName) + newFinding, err := testSuite.BHDatabase.CreateSchemaFinding(testSuite.Context, model.SchemaFindingTypeRelationship, finding.SchemaExtensionId, finding.KindId, finding.EnvironmentId, finding.Name, finding.DisplayName) require.NoError(t, err, "unexpected error occurred when creating finding") remediation := model.Remediation{ @@ -5758,7 +5758,7 @@ func TestDeleteSchemaExtension_CascadeDeletesAllDependents(t *testing.T) { environment, err := testSuite.BHDatabase.CreateEnvironment(testSuite.Context, extension.ID, dawgsEnvKind.ID, int32(sourceKind.ID)) require.NoError(t, err, "unexpected error occurred when creating environment") - relationshipFinding, err := testSuite.BHDatabase.CreateSchemaRelationshipFinding(testSuite.Context, extension.ID, edgeKind.ID, environment.ID, "CascadeTestFinding", "Cascade Test Finding") + relationshipFinding, err := testSuite.BHDatabase.CreateSchemaFinding(testSuite.Context, model.SchemaFindingTypeRelationship, extension.ID, edgeKind.ID, environment.ID, "CascadeTestFinding", "Cascade Test Finding") require.NoError(t, err, "unexpected error occurred when creating finding") _, err = testSuite.BHDatabase.CreateRemediation(testSuite.Context, relationshipFinding.ID, "Short desc", "Long desc", "Short remediation", "Long remediation") @@ -5786,7 +5786,7 @@ func TestDeleteSchemaExtension_CascadeDeletesAllDependents(t *testing.T) { _, err = testSuite.BHDatabase.GetEnvironmentById(testSuite.Context, environment.ID) assert.ErrorIs(t, err, database.ErrNotFound) - _, err = testSuite.BHDatabase.GetSchemaRelationshipFindingById(testSuite.Context, relationshipFinding.ID) + _, err = testSuite.BHDatabase.GetSchemaFindingById(testSuite.Context, relationshipFinding.ID) assert.ErrorIs(t, err, database.ErrNotFound) _, err = testSuite.BHDatabase.GetRemediationByFindingId(testSuite.Context, relationshipFinding.ID) diff --git a/cmd/api/src/database/migration/migrations/v8.8.0.sql b/cmd/api/src/database/migration/migrations/v8.8.0.sql new file mode 100644 index 00000000000..b77ffa6a8f1 --- /dev/null +++ b/cmd/api/src/database/migration/migrations/v8.8.0.sql @@ -0,0 +1,69 @@ +-- Copyright 2026 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 + +-- Drop list findings table, it was unused; +DROP TABLE IF EXISTS schema_list_findings; + +-- Create schema_findings table +CREATE TABLE IF NOT EXISTS schema_findings ( + id SERIAL, + type INTEGER NOT NULL, + schema_extension_id INTEGER NOT NULL REFERENCES schema_extensions(id) ON DELETE CASCADE, + environment_id INTEGER NOT NULL REFERENCES schema_environments(id) ON DELETE CASCADE, + kind_id INTEGER NOT NULL REFERENCES kind(id), + name TEXT NOT NULL, + display_name TEXT NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp, + PRIMARY KEY(id), + UNIQUE(name) +); +CREATE INDEX IF NOT EXISTS idx_schema_findings_extension_id ON schema_findings (schema_extension_id); +CREATE INDEX IF NOT EXISTS idx_schema_findings_environment_id ON schema_findings(environment_id); + +-- Populate schema_findings from old schema_relationship_findings +DO $$ +BEGIN + IF EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'schema_relationship_findings') THEN + INSERT INTO schema_findings (type, schema_extension_id, kind_id, environment_id, name, display_name, created_at) + SELECT 1, schema_extension_id, relationship_kind_id, environment_id, name, display_name, created_at + FROM schema_relationship_findings ON CONFLICT (name) DO NOTHING; + END IF; +END +$$; + +-- Update schema_remediations to reference schema_findings instead of schema_relationship_findings +ALTER TABLE schema_remediations DROP CONSTRAINT IF EXISTS schema_remediations_finding_id_fkey; +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE constraint_name = 'schema_remediations_schema_finding_id_fkey' + AND table_name = 'schema_remediations' + ) THEN + ALTER TABLE schema_remediations ADD CONSTRAINT schema_remediations_schema_finding_id_fkey + FOREIGN KEY (finding_id) REFERENCES schema_findings(id) ON DELETE CASCADE; +END IF; +END $$; + +-- Drop schema_relationship_findings +DROP TABLE IF EXISTS schema_relationship_findings; + +-- Add subtypes table to schema_findings +CREATE TABLE IF NOT EXISTS schema_findings_subtypes ( + schema_finding_id INTEGER NOT NULL REFERENCES schema_findings(id) ON DELETE CASCADE, + subtype TEXT NOT NULL, + PRIMARY KEY(schema_finding_id, subtype) +); diff --git a/cmd/api/src/database/mocks/db.go b/cmd/api/src/database/mocks/db.go index 8b233a1e308..96a121c3f93 100644 --- a/cmd/api/src/database/mocks/db.go +++ b/cmd/api/src/database/mocks/db.go @@ -600,19 +600,19 @@ func (mr *MockDatabaseMockRecorder) CreateSavedQueryPermissionsToUsers(ctx, quer return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSavedQueryPermissionsToUsers", reflect.TypeOf((*MockDatabase)(nil).CreateSavedQueryPermissionsToUsers), varargs...) } -// CreateSchemaRelationshipFinding mocks base method. -func (m *MockDatabase) CreateSchemaRelationshipFinding(ctx context.Context, extensionId, relationshipKindId, environmentId int32, name, displayName string) (model.SchemaRelationshipFinding, error) { +// CreateSchemaFinding mocks base method. +func (m *MockDatabase) CreateSchemaFinding(ctx context.Context, findingType model.SchemaFindingType, extensionId, kindId, environmentId int32, name, displayName string) (model.SchemaFinding, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateSchemaRelationshipFinding", ctx, extensionId, relationshipKindId, environmentId, name, displayName) - ret0, _ := ret[0].(model.SchemaRelationshipFinding) + ret := m.ctrl.Call(m, "CreateSchemaFinding", ctx, findingType, extensionId, kindId, environmentId, name, displayName) + ret0, _ := ret[0].(model.SchemaFinding) ret1, _ := ret[1].(error) return ret0, ret1 } -// CreateSchemaRelationshipFinding indicates an expected call of CreateSchemaRelationshipFinding. -func (mr *MockDatabaseMockRecorder) CreateSchemaRelationshipFinding(ctx, extensionId, relationshipKindId, environmentId, name, displayName any) *gomock.Call { +// CreateSchemaFinding indicates an expected call of CreateSchemaFinding. +func (mr *MockDatabaseMockRecorder) CreateSchemaFinding(ctx, findingType, extensionId, kindId, environmentId, name, displayName any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSchemaRelationshipFinding", reflect.TypeOf((*MockDatabase)(nil).CreateSchemaRelationshipFinding), ctx, extensionId, relationshipKindId, environmentId, name, displayName) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSchemaFinding", reflect.TypeOf((*MockDatabase)(nil).CreateSchemaFinding), ctx, findingType, extensionId, kindId, environmentId, name, displayName) } // CreateUser mocks base method. @@ -1029,18 +1029,18 @@ func (mr *MockDatabaseMockRecorder) DeleteSavedQueryPermissionsForUsers(ctx, que return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSavedQueryPermissionsForUsers", reflect.TypeOf((*MockDatabase)(nil).DeleteSavedQueryPermissionsForUsers), varargs...) } -// DeleteSchemaRelationshipFinding mocks base method. -func (m *MockDatabase) DeleteSchemaRelationshipFinding(ctx context.Context, findingId int32) error { +// DeleteSchemaFinding mocks base method. +func (m *MockDatabase) DeleteSchemaFinding(ctx context.Context, findingId int32) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteSchemaRelationshipFinding", ctx, findingId) + ret := m.ctrl.Call(m, "DeleteSchemaFinding", ctx, findingId) ret0, _ := ret[0].(error) return ret0 } -// DeleteSchemaRelationshipFinding indicates an expected call of DeleteSchemaRelationshipFinding. -func (mr *MockDatabaseMockRecorder) DeleteSchemaRelationshipFinding(ctx, findingId any) *gomock.Call { +// DeleteSchemaFinding indicates an expected call of DeleteSchemaFinding. +func (mr *MockDatabaseMockRecorder) DeleteSchemaFinding(ctx, findingId any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSchemaRelationshipFinding", reflect.TypeOf((*MockDatabase)(nil).DeleteSchemaRelationshipFinding), ctx, findingId) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSchemaFinding", reflect.TypeOf((*MockDatabase)(nil).DeleteSchemaFinding), ctx, findingId) } // DeleteSelectorNodesByNodeId mocks base method. @@ -2291,34 +2291,49 @@ func (mr *MockDatabaseMockRecorder) GetSavedQueryPermissions(ctx, queryID any) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSavedQueryPermissions", reflect.TypeOf((*MockDatabase)(nil).GetSavedQueryPermissions), ctx, queryID) } -// GetSchemaRelationshipFindingById mocks base method. -func (m *MockDatabase) GetSchemaRelationshipFindingById(ctx context.Context, findingId int32) (model.SchemaRelationshipFinding, error) { +// GetSchemaFindingById mocks base method. +func (m *MockDatabase) GetSchemaFindingById(ctx context.Context, findingId int32) (model.SchemaFinding, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetSchemaRelationshipFindingById", ctx, findingId) - ret0, _ := ret[0].(model.SchemaRelationshipFinding) + ret := m.ctrl.Call(m, "GetSchemaFindingById", ctx, findingId) + ret0, _ := ret[0].(model.SchemaFinding) ret1, _ := ret[1].(error) return ret0, ret1 } -// GetSchemaRelationshipFindingById indicates an expected call of GetSchemaRelationshipFindingById. -func (mr *MockDatabaseMockRecorder) GetSchemaRelationshipFindingById(ctx, findingId any) *gomock.Call { +// GetSchemaFindingById indicates an expected call of GetSchemaFindingById. +func (mr *MockDatabaseMockRecorder) GetSchemaFindingById(ctx, findingId any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSchemaRelationshipFindingById", reflect.TypeOf((*MockDatabase)(nil).GetSchemaRelationshipFindingById), ctx, findingId) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSchemaFindingById", reflect.TypeOf((*MockDatabase)(nil).GetSchemaFindingById), ctx, findingId) } -// GetSchemaRelationshipFindingByName mocks base method. -func (m *MockDatabase) GetSchemaRelationshipFindingByName(ctx context.Context, name string) (model.SchemaRelationshipFinding, error) { +// GetSchemaFindingByName mocks base method. +func (m *MockDatabase) GetSchemaFindingByName(ctx context.Context, name string) (model.SchemaFinding, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetSchemaRelationshipFindingByName", ctx, name) - ret0, _ := ret[0].(model.SchemaRelationshipFinding) + ret := m.ctrl.Call(m, "GetSchemaFindingByName", ctx, name) + ret0, _ := ret[0].(model.SchemaFinding) ret1, _ := ret[1].(error) return ret0, ret1 } -// GetSchemaRelationshipFindingByName indicates an expected call of GetSchemaRelationshipFindingByName. -func (mr *MockDatabaseMockRecorder) GetSchemaRelationshipFindingByName(ctx, name any) *gomock.Call { +// GetSchemaFindingByName indicates an expected call of GetSchemaFindingByName. +func (mr *MockDatabaseMockRecorder) GetSchemaFindingByName(ctx, name any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSchemaRelationshipFindingByName", reflect.TypeOf((*MockDatabase)(nil).GetSchemaRelationshipFindingByName), ctx, name) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSchemaFindingByName", reflect.TypeOf((*MockDatabase)(nil).GetSchemaFindingByName), ctx, name) +} + +// GetSchemaFindingsBySchemaExtensionId mocks base method. +func (m *MockDatabase) GetSchemaFindingsBySchemaExtensionId(ctx context.Context, extensionId int32) ([]model.SchemaFinding, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSchemaFindingsBySchemaExtensionId", ctx, extensionId) + ret0, _ := ret[0].([]model.SchemaFinding) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSchemaFindingsBySchemaExtensionId indicates an expected call of GetSchemaFindingsBySchemaExtensionId. +func (mr *MockDatabaseMockRecorder) GetSchemaFindingsBySchemaExtensionId(ctx, extensionId any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSchemaFindingsBySchemaExtensionId", reflect.TypeOf((*MockDatabase)(nil).GetSchemaFindingsBySchemaExtensionId), ctx, extensionId) } // GetScopeForSavedQuery mocks base method. diff --git a/cmd/api/src/database/upsert_schema_extension_integration_test.go b/cmd/api/src/database/upsert_schema_extension_integration_test.go index ca3cff540b3..8bf52963c0b 100644 --- a/cmd/api/src/database/upsert_schema_extension_integration_test.go +++ b/cmd/api/src/database/upsert_schema_extension_integration_test.go @@ -744,7 +744,7 @@ func TestBloodhoundDB_UpsertOpenGraphExtension(t *testing.T) { for _, finding := range existingFindings { var ( - schemaFinding model.SchemaRelationshipFinding + schemaFinding model.SchemaFinding ) schemaFinding, err = testSuite.BHDatabase.UpsertFinding(testSuite.Context, createdExtension.ID, @@ -830,7 +830,7 @@ func TestBloodhoundDB_UpsertOpenGraphExtension(t *testing.T) { for _, finding := range existingFindings { var ( - schemaFinding model.SchemaRelationshipFinding + schemaFinding model.SchemaFinding ) schemaFinding, err = testSuite.BHDatabase.UpsertFinding(testSuite.Context, createdExtension.ID, @@ -1075,7 +1075,7 @@ func getAndCompareGraphExtension(t *testing.T, testContext context.Context, db * dawgsPrincipalKind model.Kind dawgsFindingRelationshipKind model.Kind dawgsFindingEnvironmentKind model.Kind - gotSchemaRelationshipFinding []model.SchemaRelationshipFinding + gotSchemaRelationshipFinding []model.SchemaFinding gotRemediation model.Remediation findingEnvironment model.SchemaEnvironment ) @@ -1154,7 +1154,7 @@ func getAndCompareGraphExtension(t *testing.T, testContext context.Context, db * } // Test Findings - gotSchemaRelationshipFinding, err = db.GetSchemaRelationshipFindingsBySchemaExtensionId(testContext, gotGraphExtension.ID) + gotSchemaRelationshipFinding, err = db.GetSchemaFindingsBySchemaExtensionId(testContext, gotGraphExtension.ID) require.NoError(t, err) require.Equalf(t, len(want.RelationshipFindingsInput), len(gotSchemaRelationshipFinding), "mismatched number of findings") @@ -1163,7 +1163,7 @@ func getAndCompareGraphExtension(t *testing.T, testContext context.Context, db * require.Greater(t, finding.ID, int32(0)) require.Equalf(t, gotGraphExtension.ID, finding.SchemaExtensionId, "RelationshipFindingInput - graph schema extension id should be greater than 0") - dawgsFindingRelationshipKind, err = db.GetKindById(testContext, finding.RelationshipKindId) + dawgsFindingRelationshipKind, err = db.GetKindById(testContext, finding.KindId) require.NoError(t, err) require.Equalf(t, want.RelationshipFindingsInput[i].RelationshipKindName, dawgsFindingRelationshipKind.Name, "RelationshipFindingInput - relationship kind name mismatch") diff --git a/cmd/api/src/database/upsert_schema_finding.go b/cmd/api/src/database/upsert_schema_finding.go index 9bca51a1271..ea8b88650d6 100644 --- a/cmd/api/src/database/upsert_schema_finding.go +++ b/cmd/api/src/database/upsert_schema_finding.go @@ -25,32 +25,32 @@ import ( // UpsertFinding validates and upserts a finding. // If a finding with the same name exists, it will be deleted and re-created. -func (s *BloodhoundDB) UpsertFinding(ctx context.Context, extensionId int32, sourceKindName, relationshipKindName, environmentKind string, name, displayName string) (model.SchemaRelationshipFinding, error) { +func (s *BloodhoundDB) UpsertFinding(ctx context.Context, extensionId int32, sourceKindName, relationshipKindName, environmentKind string, name, displayName string) (model.SchemaFinding, error) { relationshipKindId, err := s.validateAndTranslateRelationshipKind(ctx, relationshipKindName) if err != nil { - return model.SchemaRelationshipFinding{}, err + return model.SchemaFinding{}, err } environmentKindId, err := s.validateAndTranslateEnvironmentKind(ctx, environmentKind) if err != nil { - return model.SchemaRelationshipFinding{}, err + return model.SchemaFinding{}, err } sourceKindId, err := s.validateAndTranslateSourceKind(ctx, sourceKindName) if err != nil { - return model.SchemaRelationshipFinding{}, err + return model.SchemaFinding{}, err } // The unique constraint on (environment_kind_id, source_kind_id) of the Schema Environment table ensures no // duplicate pairs exist, enabling this logic. environment, err := s.GetEnvironmentByKinds(ctx, environmentKindId, sourceKindId) if err != nil { - return model.SchemaRelationshipFinding{}, err + return model.SchemaFinding{}, err } finding, err := s.replaceFinding(ctx, extensionId, relationshipKindId, environment.ID, name, displayName) if err != nil { - return model.SchemaRelationshipFinding{}, err + return model.SchemaFinding{}, err } return finding, nil @@ -69,19 +69,20 @@ func (s *BloodhoundDB) validateAndTranslateRelationshipKind(ctx context.Context, // replaceFinding creates or updates a schema relationship finding. // If a finding with the given name exists, it deletes it first before creating the new one. -func (s *BloodhoundDB) replaceFinding(ctx context.Context, extensionId, relationshipKindId, environmentId int32, name, displayName string) (model.SchemaRelationshipFinding, error) { - if existing, err := s.GetSchemaRelationshipFindingByName(ctx, name); err != nil && !errors.Is(err, ErrNotFound) { - return model.SchemaRelationshipFinding{}, fmt.Errorf("error retrieving schema relationship finding: %w", err) +func (s *BloodhoundDB) replaceFinding(ctx context.Context, extensionId, kindId, environmentId int32, name, displayName string) (model.SchemaFinding, error) { + if existing, err := s.GetSchemaFindingByName(ctx, name); err != nil && !errors.Is(err, ErrNotFound) { + return model.SchemaFinding{}, fmt.Errorf("error retrieving schema relationship finding: %w", err) } else if err == nil { // Finding exists - delete it first - if err := s.DeleteSchemaRelationshipFinding(ctx, existing.ID); err != nil { - return model.SchemaRelationshipFinding{}, fmt.Errorf("error deleting schema relationship finding %d: %w", existing.ID, err) + if err := s.DeleteSchemaFinding(ctx, existing.ID); err != nil { + return model.SchemaFinding{}, fmt.Errorf("error deleting schema relationship finding %d: %w", existing.ID, err) } } - finding, err := s.CreateSchemaRelationshipFinding(ctx, extensionId, relationshipKindId, environmentId, name, displayName) + // Note: All schema findings uploaded via extensions are currently set to relationship findings. + finding, err := s.CreateSchemaFinding(ctx, model.SchemaFindingTypeRelationship, extensionId, kindId, environmentId, name, displayName) if err != nil { - return model.SchemaRelationshipFinding{}, fmt.Errorf("error creating schema relationship finding: %w", err) + return model.SchemaFinding{}, fmt.Errorf("error creating schema relationship finding: %w", err) } return finding, nil diff --git a/cmd/api/src/database/upsert_schema_finding_integration_test.go b/cmd/api/src/database/upsert_schema_finding_integration_test.go index 2a40ed2a23f..3a05d6dbae3 100644 --- a/cmd/api/src/database/upsert_schema_finding_integration_test.go +++ b/cmd/api/src/database/upsert_schema_finding_integration_test.go @@ -50,7 +50,7 @@ func TestBloodhoundDB_UpsertFinding(t *testing.T) { require.NoError(t, err) // Create finding - _, err = db.CreateSchemaRelationshipFinding(context.Background(), ext.ID, 1, env.ID, "Finding Name", "Finding Display Name") + _, err = db.CreateSchemaFinding(context.Background(), model.SchemaFindingTypeRelationship, ext.ID, 1, env.ID, "Finding Name", "Finding Display Name") require.NoError(t, err) return ext.ID @@ -66,7 +66,7 @@ func TestBloodhoundDB_UpsertFinding(t *testing.T) { assert: func(t *testing.T, db *database.BloodhoundDB, extensionId int32) { t.Helper() - finding, err := db.GetSchemaRelationshipFindingByName(context.Background(), "Finding Name") + finding, err := db.GetSchemaFindingByName(context.Background(), "Finding Name") require.NoError(t, err) assert.Equal(t, extensionId, finding.SchemaExtensionId) @@ -97,7 +97,7 @@ func TestBloodhoundDB_UpsertFinding(t *testing.T) { assert: func(t *testing.T, db *database.BloodhoundDB, extensionId int32) { t.Helper() - finding, err := db.GetSchemaRelationshipFindingByName(context.Background(), "Finding") + finding, err := db.GetSchemaFindingByName(context.Background(), "Finding") require.NoError(t, err) assert.Equal(t, extensionId, finding.SchemaExtensionId) @@ -114,7 +114,7 @@ func TestBloodhoundDB_UpsertFinding(t *testing.T) { extensionId := tt.setupData(t, testSuite.BHDatabase) - var findingResponse model.SchemaRelationshipFinding + var findingResponse model.SchemaFinding // Wrap the call in a transaction err := testSuite.BHDatabase.Transaction(context.Background(), func(tx *database.BloodhoundDB) error { finding, err := tx.UpsertFinding( diff --git a/cmd/api/src/database/upsert_schema_remediation_integration_test.go b/cmd/api/src/database/upsert_schema_remediation_integration_test.go index a566f7ceb62..2a44b1cc8c5 100644 --- a/cmd/api/src/database/upsert_schema_remediation_integration_test.go +++ b/cmd/api/src/database/upsert_schema_remediation_integration_test.go @@ -23,6 +23,7 @@ import ( "testing" "github.com/specterops/bloodhound/cmd/api/src/database" + "github.com/specterops/bloodhound/cmd/api/src/model" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -48,7 +49,7 @@ func TestBloodhoundDB_UpsertRemediation(t *testing.T) { env, err := db.CreateEnvironment(context.Background(), ext.ID, 1, 1) require.NoError(t, err) - finding, err := db.CreateSchemaRelationshipFinding(context.Background(), ext.ID, 1, env.ID, "Finding", "Finding Display Name") + finding, err := db.CreateSchemaFinding(context.Background(), model.SchemaFindingTypeRelationship, ext.ID, 1, env.ID, "Finding", "Finding Display Name") require.NoError(t, err) _, err = db.CreateRemediation(context.Background(), finding.ID, "short", "long", "short rem", "long rem") @@ -86,7 +87,7 @@ func TestBloodhoundDB_UpsertRemediation(t *testing.T) { require.NoError(t, err) // Create Finding but do not create Remediation - finding, err := db.CreateSchemaRelationshipFinding(context.Background(), ext.ID, 1, env.ID, "Finding2", "Finding 2 Display Name") + finding, err := db.CreateSchemaFinding(context.Background(), model.SchemaFindingTypeRelationship, ext.ID, 1, env.ID, "Finding2", "Finding 2 Display Name") require.NoError(t, err) return finding.ID diff --git a/cmd/api/src/model/graphschema.go b/cmd/api/src/model/graphschema.go index 42616e7fd56..babce2998f8 100644 --- a/cmd/api/src/model/graphschema.go +++ b/cmd/api/src/model/graphschema.go @@ -34,7 +34,7 @@ var ( ErrDuplicateGraphSchemaExtensionPropertyName = errors.New("duplicate graph schema extension property name") ErrDuplicateSchemaRelationshipKindName = errors.New("duplicate schema relationship kind name") ErrDuplicateSchemaEnvironment = errors.New("duplicate schema environment") - ErrDuplicateSchemaRelationshipFindingName = errors.New("duplicate schema relationship finding name") + ErrDuplicateSchemaFindingName = errors.New("duplicate schema finding name") ErrDuplicatePrincipalKind = errors.New("duplicate principal kind") ) @@ -45,23 +45,22 @@ var ( // ErrDuplicateGraphSchemaExtensionPropertyName // ErrDuplicateSchemaRelationshipKindName // ErrDuplicateSchemaEnvironment -// ErrDuplicateSchemaRelationshipFindingName +// ErrDuplicateSchemaFindingName // ErrDuplicatePrincipalKind func ErrIsGraphSchemaDuplicateError(err error) bool { - if err == nil { + switch { + case errors.Is(err, ErrDuplicateGraphSchemaExtensionName), + errors.Is(err, ErrDuplicateGraphSchemaExtensionNamespace), + errors.Is(err, ErrDuplicateSchemaNodeKindName), + errors.Is(err, ErrDuplicateGraphSchemaExtensionPropertyName), + errors.Is(err, ErrDuplicateSchemaRelationshipKindName), + errors.Is(err, ErrDuplicateSchemaEnvironment), + errors.Is(err, ErrDuplicateSchemaFindingName), + errors.Is(err, ErrDuplicatePrincipalKind): + return true + default: return false } - - var duplicateErrors = []error{ - ErrDuplicateGraphSchemaExtensionName, ErrDuplicateGraphSchemaExtensionNamespace, ErrDuplicateSchemaNodeKindName, - ErrDuplicateGraphSchemaExtensionPropertyName, ErrDuplicateSchemaRelationshipKindName, ErrDuplicateSchemaEnvironment, - ErrDuplicateSchemaRelationshipFindingName, ErrDuplicatePrincipalKind} - for _, e := range duplicateErrors { - if errors.Is(err, e) { - return true - } - } - return false } type GraphSchemaExtensions []GraphSchemaExtension @@ -167,19 +166,36 @@ func (SchemaEnvironment) TableName() string { return "schema_environments" } -// SchemaRelationshipFinding represents an individual finding (e.g., T0WriteOwner, T0ADCSESC1, T0DCSync) -type SchemaRelationshipFinding struct { - ID int32 - SchemaExtensionId int32 - RelationshipKindId int32 - EnvironmentId int32 - Name string - DisplayName string - CreatedAt time.Time +type SchemaFindingType int + +const ( + SchemaFindingTypeRelationship SchemaFindingType = 1 + SchemaFindingTypeList SchemaFindingType = 2 +) + +// SchemaFinding represents an individual finding (e.g., T0WriteOwner, T0ADCSESC1, T0DCSync) +type SchemaFinding struct { + ID int32 + Type SchemaFindingType + SchemaExtensionId int32 + EnvironmentId int32 + KindId int32 + Name string + DisplayName string + CreatedAt time.Time +} + +func (SchemaFinding) TableName() string { + return "schema_findings" +} + +type SchemaFindingsSubtype struct { + SchemaFindingId int32 + Subtype string } -func (SchemaRelationshipFinding) TableName() string { - return "schema_relationship_findings" +func (SchemaFindingsSubtype) TableName() string { + return "schema_findings_subtypes" } type Remediation struct {