Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add new data structures for an abstract representation of projects/findings #75

Merged
merged 11 commits into from
Aug 1, 2023
Merged
1 change: 1 addition & 0 deletions querying/datasource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package querying
20 changes: 20 additions & 0 deletions querying/ecosystems.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package querying

type FindingEcosystemType uint8

const (
FindingEcosystemApt FindingEcosystemType = iota
tarkatronic marked this conversation as resolved.
Show resolved Hide resolved
FindingEcosystemCSharp
FindingEcosystemDart
FindingEcosystemErlang
FindingEcosystemGHA // GitHub Actions
FindingEcosystemGo
FindingEcosystemJava
FindingEcosystemJS
FindingEcosystemPHP
FindingEcosystemPython
FindingEcosystemRPM
FindingEcosystemRuby
FindingEcosystemRust
FindingEcosystemSwift
)
20 changes: 20 additions & 0 deletions querying/finding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package querying

import "sync"

type FindingIdentifierType uint8
type FindingIdentifierMap map[FindingIdentifierType]string

const (
FindingIdentifierCVE FindingIdentifierType = iota
FindingIdentifierGHSA
)

type Finding struct {
Identifiers FindingIdentifierMap
Ecosystem FindingEcosystemType
Severity FindingSeverityType
Description string
PackageName string
mu sync.Mutex
}
89 changes: 89 additions & 0 deletions querying/project.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package querying

import (
"regexp"
"strings"
"sync"

"golang.org/x/exp/maps"
)

type ProjectCollection struct {
Projects []*Project
mu sync.Mutex
}

type Project struct {
Name string
Findings []*Finding
Links map[string]string
mu sync.Mutex
}

func NewProject(name string) *Project {
return &Project{
Name: name,
Findings: []*Finding{},
Links: map[string]string{},
}
}

func NewProjectCollection() *ProjectCollection {
return &ProjectCollection{
Projects: []*Project{},
}
}

func normalizeProjectName(name string) string {
unacceptableChars := regexp.MustCompile(`[^\p{L}\p{N} \-\_]+`)
developerDemetri marked this conversation as resolved.
Show resolved Hide resolved
replacer := strings.NewReplacer(
" ", "_",
"-", "_",
)
return replacer.Replace(
unacceptableChars.ReplaceAllString(
strings.ToLower(name), "",
),
)
}

func (c *ProjectCollection) GetProject(name string) *Project {
c.mu.Lock()
defer c.mu.Unlock()
name = normalizeProjectName(name)
for _, proj := range c.Projects {
if normalizeProjectName(proj.Name) == name {
return proj
}
}
// If we make it past the loop, no existing project was found with this name
newProj := NewProject(name)
c.Projects = append(c.Projects, newProj)
return newProj
}

func (p *Project) GetFinding(identifiers FindingIdentifierMap) *Finding {
var result *Finding
p.mu.Lock()
defer p.mu.Unlock()
for _, finding := range p.Findings {
for idType, id := range finding.Identifiers {
val, ok := identifiers[idType]
if ok && val == id {
result = finding
break
}
}
}
if result == nil {
result = &Finding{
Identifiers: identifiers,
}
p.Findings = append(p.Findings, result)
} else {
result.mu.Lock()
defer result.mu.Unlock()
maps.Copy(result.Identifiers, identifiers)
}
return result
}
70 changes: 70 additions & 0 deletions querying/project_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package querying_test

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/underdog-tech/vulnbot/querying"
)

func TestGetProjectAddsToCollection(t *testing.T) {
projects := querying.NewProjectCollection()
assert.Len(t, projects.Projects, 0)
proj := projects.GetProject("Improbability Drive")
assert.Len(t, projects.Projects, 1)
assert.Equal(t, proj, projects.Projects[0])
}

func TestProjectNameIsNormalized(t *testing.T) {
projects := querying.NewProjectCollection()
proj := projects.GetProject("Heart-of-Gold: Improbability Drive!")
assert.Equal(t, "heart_of_gold_improbability_drive", proj.Name)
}

func TestProjectsAreNotDuplicated(t *testing.T) {
projects := querying.NewProjectCollection()
proj1 := projects.GetProject("Improbability Drive")
proj2 := projects.GetProject("Improbability Drive")
assert.Len(t, projects.Projects, 1)
assert.Equal(t, proj1, proj2)
}

func TestGetFindingAddsToProject(t *testing.T) {
project := querying.NewProject("Improbability Drive")
assert.Len(t, project.Findings, 0)
finding := project.GetFinding(
querying.FindingIdentifierMap{
querying.FindingIdentifierCVE: "CVE-42",
},
)
assert.Len(t, project.Findings, 1)
assert.Equal(t, finding, project.Findings[0])
}

func TestFindingsAreNotDuplicated(t *testing.T) {
project := querying.NewProject("Improbability Drive")
identifiers := querying.FindingIdentifierMap{
querying.FindingIdentifierCVE: "CVE-42",
}
finding1 := project.GetFinding(identifiers)
finding2 := project.GetFinding(identifiers)
assert.Len(t, project.Findings, 1)
assert.Equal(t, finding1, finding2)
}

func TestFindingIdentifiersAreMerged(t *testing.T) {
project := querying.NewProject("Improbability Drive")
id_single := querying.FindingIdentifierMap{
querying.FindingIdentifierCVE: "CVE-42",
}
id_multi := querying.FindingIdentifierMap{
querying.FindingIdentifierCVE: "CVE-42",
querying.FindingIdentifierGHSA: "GHSA-4242",
}
finding1 := project.GetFinding(id_single)
finding2 := project.GetFinding(id_multi)
assert.Len(t, project.Findings, 1)
assert.Equal(t, finding1, finding2)
assert.Equal(t, finding1.Identifiers, id_multi)
assert.Equal(t, project.Findings[0].Identifiers, id_multi)
}
12 changes: 12 additions & 0 deletions querying/severities.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package querying

type FindingSeverityType uint8

const (
FindingSeverityCritical FindingSeverityType = iota
FindingSeverityHigh
FindingSeverityModerate
FindingSeverityLow
FindingSeverityInfo
FindingSeverityUndefined
)