diff --git a/api/go.mod b/api/go.mod index b028830a6..2df5e966c 100644 --- a/api/go.mod +++ b/api/go.mod @@ -3,7 +3,7 @@ module github.com/ZupIT/horusec-platform/api go 1.17 require ( - github.com/ZupIT/horusec-devkit v1.0.23 + github.com/ZupIT/horusec-devkit v1.0.24 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 github.com/go-chi/chi v4.1.2+incompatible github.com/go-chi/cors v1.2.0 @@ -64,6 +64,6 @@ require ( google.golang.org/protobuf v1.27.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect - gorm.io/driver/postgres v1.2.3 // indirect - gorm.io/gorm v1.22.5 // indirect + gorm.io/driver/postgres v1.3.1 // indirect + gorm.io/gorm v1.23.2 // indirect ) diff --git a/api/go.sum b/api/go.sum index cc019537f..44cb8f068 100644 --- a/api/go.sum +++ b/api/go.sum @@ -44,8 +44,8 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/ZupIT/horusec-devkit v1.0.23 h1:CBL5ya45zLMXYYgmdAtShAm3VC1F7KQGiRaIU3WGTow= -github.com/ZupIT/horusec-devkit v1.0.23/go.mod h1:01lg6tLZkqwJE/Nn8Prnq7bFjq9Agf4zwbuV47sxMno= +github.com/ZupIT/horusec-devkit v1.0.24 h1:GGW6LyyvVvmN2+2/miPjOZ6E6BhE9k7Tw0rVcS+CaFM= +github.com/ZupIT/horusec-devkit v1.0.24/go.mod h1:l1vuCb/lxyGZ8vIgW3EO5CS6aJUukGprD9UB6FwU16w= github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs= github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -244,14 +244,12 @@ github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01C github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.9.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgtype v1.9.1 h1:MJc2s0MFS8C3ok1wQTdQxWuXQcB6+HwAm5x1CzW7mf0= github.com/jackc/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.14.0/go.mod h1:jT3ibf/A0ZVCp89rtCIN0zCJxcE74ypROmHEZYsG/j8= github.com/jackc/pgx/v4 v4.14.1 h1:71oo1KAGI6mXhLiTMn6iDFcp3e7+zon/capWjl2OEFU= github.com/jackc/pgx/v4 v4.14.1/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= @@ -260,7 +258,6 @@ github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas= github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -744,11 +741,11 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/postgres v1.2.3 h1:f4t0TmNMy9gh3TU2PX+EppoA6YsgFnyq8Ojtddb42To= -gorm.io/driver/postgres v1.2.3/go.mod h1:pJV6RgYQPG47aM1f0QeOzFH9HxQc8JcmAgjRCgS0wjs= -gorm.io/gorm v1.22.3/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= -gorm.io/gorm v1.22.5 h1:lYREBgc02Be/5lSCTuysZZDb6ffL2qrat6fg9CFbvXU= -gorm.io/gorm v1.22.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/driver/postgres v1.3.1 h1:Pyv+gg1Gq1IgsLYytj/S2k7ebII3CzEdpqQkPOdH24g= +gorm.io/driver/postgres v1.3.1/go.mod h1:WwvWOuR9unCLpGWCL6Y3JOeBWvbKi6JLhayiVclSZZU= +gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.23.2 h1:xmq9QRMWL8HTJyhAUBXy8FqIIQCYESeKfJL4DoGKiWQ= +gorm.io/gorm v1.23.2/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/api/internal/controllers/analysis/analysis.go b/api/internal/controllers/analysis/analysis.go index 7971867ae..72d9de706 100644 --- a/api/internal/controllers/analysis/analysis.go +++ b/api/internal/controllers/analysis/analysis.go @@ -15,10 +15,9 @@ package analysis import ( - "errors" "time" - "github.com/ZupIT/horusec-devkit/pkg/services/database/response" + "github.com/ZupIT/horusec-devkit/pkg/entities/vulnerability" "github.com/google/uuid" @@ -66,23 +65,15 @@ func (c *Controller) GetAnalysis(analysisID uuid.UUID) (*analysis.Analysis, erro return res.GetData().(*analysis.Analysis), nil } -// nolint func (c *Controller) SaveAnalysis(analysisEntity *analysis.Analysis) (uuid.UUID, error) { analysisEntity, err := c.createRepositoryIfNotExists(analysisEntity) if err != nil { return uuid.Nil, err } - - //TODO: REMOVE treatCompatibility IN v2.10.0 VERSION - if err := c.treatCompatibility(analysisEntity); err != nil { - return uuid.Nil, err - } - - analysisDecorated, err := c.decorateAnalysisEntityAndSaveOnDatabase(analysisEntity) + analysisDecorated, err := c.saveNewAnalysisInDatabase(analysisEntity) if err != nil { return uuid.Nil, err } - if err := c.publishInBroker(analysisDecorated.ID); err != nil { return uuid.Nil, err } @@ -105,10 +96,19 @@ func (c *Controller) createRepositoryIfNotExists(analysisEntity *analysis.Analys return analysisEntity, nil } -func (c *Controller) decorateAnalysisEntityAndSaveOnDatabase( - analysisEntity *analysis.Analysis) (*analysis.Analysis, error) { - analysisDecorated := c.decoratorAnalysisToSave(analysisEntity) - return analysisDecorated, c.createNewAnalysis(analysisDecorated) +func (c *Controller) saveNewAnalysisInDatabase(newAnalysis *analysis.Analysis) (*analysis.Analysis, error) { + //TODO: REMOVE treatHashCompatibility IN v2.10.0 VERSION + if err := c.treatHashCompatibility(newAnalysis); err != nil { + return nil, err + } + + analysisDecorated := c.decoratorAnalysisToSave(newAnalysis) + + if err := c.repoAnalysis.CreateFullAnalysis(analysisDecorated); err != nil { + return nil, err + } + + return analysisDecorated, nil } func (c *Controller) decoratorAnalysisToSave(analysisEntity *analysis.Analysis) *analysis.Analysis { @@ -128,10 +128,6 @@ func (c *Controller) decoratorAnalysisToSave(analysisEntity *analysis.Analysis) return newAnalysis } -func (c *Controller) createNewAnalysis(newAnalysis *analysis.Analysis) error { - return c.repoAnalysis.CreateFullAnalysis(newAnalysis) -} - func (c *Controller) extractBaseOfTheAnalysis(analysisEntity *analysis.Analysis) *analysis.Analysis { return &analysis.Analysis{ ID: analysisEntity.ID, @@ -168,88 +164,60 @@ func (c *Controller) publishInBroker(analysisID uuid.UUID) error { } // TODO:REMOVE ALL BELOW AFTER v2.10.0 -// treatCompatibility checks if the field Analysis.AnalysisVulnerabilities[i].DeprecatedHashes exists +// treatHashCompatibility checks if the field Analysis.AnalysisVulnerabilities[i].DeprecatedHashes exists // and if so, find them on database and updates it with the correct field Analysis.AnalysisVulnerabilities[i].VulnHash. // this is only a temporary fix to maintain compatibility between versions, // it will be deleted when v2.10.0 is released -// nolint -func (c *Controller) treatCompatibility(analysisEntity *analysis.Analysis) error { +// nolint:funlen,gocyclo // funlen and gocyclo is not necessary in this method deprecated +func (c *Controller) treatHashCompatibility(analysisEntity *analysis.Analysis) error { if !c.existsDeprecatedHashesSlice(analysisEntity.AnalysisVulnerabilities) { return nil } - deprecatedHashes := make([]string, 0) - - for i := range analysisEntity.AnalysisVulnerabilities { - deprecatedHashes = append( - deprecatedHashes, - analysisEntity.AnalysisVulnerabilities[i].Vulnerability.DeprecatedHashes...) - } - - if err := c.saveUpdates(deprecatedHashes, analysisEntity); err != nil { + deprecatedHashes := []string{} + for index := range analysisEntity.AnalysisVulnerabilities { + manyToMany := analysisEntity.AnalysisVulnerabilities[index] + deprecatedHashes = append(deprecatedHashes, manyToMany.Vulnerability.DeprecatedHashes...) + } + mapHashToVulnerabilityID, err := c.getMapHashToVulnerabilityID(deprecatedHashes, analysisEntity) + if err != nil || len(mapHashToVulnerabilityID) == 0 { + // When the database cannot find data, it means that the user is using the new CLI version, + // but Horusec-Platform is new and has not yet received any analysis for this repository. + if err == enums.ErrorNotFoundRecords { + return nil + } return err } - - return nil + return c.repoAnalysis.SaveTreatCompatibility(mapHashToVulnerabilityID, analysisEntity) } -func (c *Controller) saveUpdates(hashSlice []string, analysisEntity *analysis.Analysis) error { - res := c.repoAnalysis.FindVulnerabilitiesByHashSliceInRepository(hashSlice, analysisEntity.RepositoryID) - if res.GetError() != nil { - return res.GetError() - } - mapHashToID, err := c.parseResIds(res) - if err != nil { - return err +// existsDeprecatedHashesSlice will get all vulnerabilities from database with deprecated hashes +// nolint:funlen // funlen is not necessary in this method deprecated +func (c *Controller) getMapHashToVulnerabilityID(deprecatedHashes []string, + analysisEntity *analysis.Analysis) (map[string]uuid.UUID, error) { + mapHashToVulnerabilityID := make(map[string]uuid.UUID, 0) + res := c.repoAnalysis.FindAllVulnerabilitiesByHashesAndRepository(deprecatedHashes, analysisEntity.RepositoryID) + if err := res.GetError(); err != nil { + return map[string]uuid.UUID{}, err } - query, values := c.mountUpdateQuery(analysisEntity, mapHashToID) - - if err := c.repoAnalysis.RawQuery(query, values); err != nil { - return err + if res.GetData() == nil { + return map[string]uuid.UUID{}, nil } - return nil -} - -// mountUpdateQuery iterates over rawAnalysis.AnalysisVulnerabilities and -// checks if some vuln.Vulnerability.DeprecatedHashes is present on -// mapHashToId then creates and update statement to update the -// deprecated Hash value to the new one (that is present in rawAnalysis.Vulnerability.VulnHash field) -func (c *Controller) mountUpdateQuery( - rawAnalysis *analysis.Analysis, mapHashToID map[string]uuid.UUID, -) (string, []string) { - query := "" - values := make([]string, 0) - for i := range rawAnalysis.AnalysisVulnerabilities { - vuln := rawAnalysis.AnalysisVulnerabilities[i] - for _, hash := range vuln.Vulnerability.DeprecatedHashes { - if mapHashToID[hash] != uuid.Nil { - query += "UPDATE vulnerabilities SET vuln_hash =? where vulnerability_id = ? ;\n" - values = append(values, vuln.Vulnerability.VulnHash, mapHashToID[hash].String()) - } - } + vulnerabilities := *res.GetData().(*[]vulnerability.Vulnerability) + for index := range vulnerabilities { + vuln := vulnerabilities[index] + mapHashToVulnerabilityID[vuln.VulnHash] = vuln.VulnerabilityID } - return query, values + return mapHashToVulnerabilityID, nil } // existsDeprecatedHashesSlice checks if []analysis.AnalysisVulnerabilities.Vulnerability // has a field called DeprecatedHashes -func (c *Controller) existsDeprecatedHashesSlice(vulns []analysis.AnalysisVulnerabilities) bool { - if len(vulns) > 0 { - if vulns[0].Vulnerability.DeprecatedHashes != nil { +func (c *Controller) existsDeprecatedHashesSlice(sliceManyToMany []analysis.AnalysisVulnerabilities) bool { + for index := range sliceManyToMany { + manyToMany := sliceManyToMany[index] + if len(manyToMany.Vulnerability.DeprecatedHashes) > 0 { return true } } return false } - -// parseResIds makes a map[hash] id that already exists on database for further manipulation -func (c *Controller) parseResIds(res response.IResponse) (map[string]uuid.UUID, error) { - if res.GetData() == nil { - return nil, errors.New("nil response.GetData") - } - mapIds := res.GetData().(*[]map[string]interface{}) - mapIDHash := make(map[string]uuid.UUID, len(*mapIds)) - for _, id := range *mapIds { - mapIDHash[id["vuln_hash"].(string)] = uuid.MustParse(id["vulnerability_id"].(string)) - } - return mapIDHash, nil -} diff --git a/api/internal/controllers/analysis/analysis_test.go b/api/internal/controllers/analysis/analysis_test.go index d5c55ad93..96f377bdc 100644 --- a/api/internal/controllers/analysis/analysis_test.go +++ b/api/internal/controllers/analysis/analysis_test.go @@ -558,14 +558,14 @@ func TestController_SaveAnalysis(t *testing.T) { appConfigMock := &appConfiguration.Mock{} repoRepositoryMock := &repository.Mock{} repoAnalysisMock := &repoAnalysis.Mock{} - repoAnalysisMock.On("RawQuery").Return(nil) - repoAnalysisMock.On("FindVulnerabilitiesByHashSliceInRepository").Return(response.NewResponse( + repoAnalysisMock.On("SaveTreatCompatibility").Return(nil) + repoAnalysisMock.On("FindAllVulnerabilitiesByHashesAndRepository").Return(response.NewResponse( 1, nil, - &[]map[string]interface{}{ + &[]vulnerability.Vulnerability{ { - "vulnerability_id": "47a7807b-ca70-41b1-8ff2-432a5d1752fd", - "vuln_hash": "oldHash", + VulnerabilityID: uuid.New(), + VulnHash: "oldHash", }, }, )) @@ -625,20 +625,20 @@ func TestController_SaveAnalysis(t *testing.T) { assert.NoError(t, err) assert.NotEqual(t, res, uuid.Nil) }) - t.Run("Should save analysis with error when FindVulnerabilitiesByHashSliceInRepository fails", func(t *testing.T) { + t.Run("Should save analysis with error when FindAllVulnerabilitiesByHashesAndRepository fails", func(t *testing.T) { brokerMock := &broker.Mock{} brokerMock.On("Publish").Return(nil) appConfigMock := &appConfiguration.Mock{} repoRepositoryMock := &repository.Mock{} repoAnalysisMock := &repoAnalysis.Mock{} - repoAnalysisMock.On("RawQuery").Return(nil) - repoAnalysisMock.On("FindVulnerabilitiesByHashSliceInRepository").Return(response.NewResponse( + repoAnalysisMock.On("SaveTreatCompatibility").Return(nil) + repoAnalysisMock.On("FindAllVulnerabilitiesByHashesAndRepository").Return(response.NewResponse( 1, errors.New("error"), - &[]map[string]interface{}{ + &[]vulnerability.Vulnerability{ { - "vulnerability_id": "47a7807b-ca70-41b1-8ff2-432a5d1752fd", - "vuln_hash": "oldHash", + VulnerabilityID: uuid.New(), + VulnHash: "oldHash", }, }, )) @@ -698,20 +698,88 @@ func TestController_SaveAnalysis(t *testing.T) { assert.Error(t, err) assert.Equal(t, res, uuid.Nil) }) + t.Run("Should save analysis with not found records when FindAllVulnerabilitiesByHashesAndRepository fails", func(t *testing.T) { + brokerMock := &broker.Mock{} + brokerMock.On("Publish").Return(nil) + appConfigMock := &appConfiguration.Mock{} + repoRepositoryMock := &repository.Mock{} + repoAnalysisMock := &repoAnalysis.Mock{} + repoAnalysisMock.On("SaveTreatCompatibility").Return(nil) + repoAnalysisMock.On("FindAllVulnerabilitiesByHashesAndRepository").Return(response.NewResponse( + 0, + enums.ErrorNotFoundRecords, + &[]vulnerability.Vulnerability{}, + )) + repoAnalysisMock.On("CreateFullAnalysisResponse").Return(nil) + repoAnalysisMock.On("CreateFullAnalysisArguments").Return(func(any *analysis.Analysis) {}) + repoAnalysisMock.On("FindAnalysisByID").Return(response.NewResponse(0, nil, &analysis.Analysis{ + ID: uuid.New(), + Status: analysisEnum.Success, + Errors: "", + CreatedAt: time.Now(), + FinishedAt: time.Now(), + })) + controller := NewAnalysisController( + brokerMock, + appConfigMock, + repoRepositoryMock, + repoAnalysisMock, + ) + res, err := controller.SaveAnalysis(&analysis.Analysis{ + ID: uuid.New(), + WorkspaceID: uuid.New(), + WorkspaceName: uuid.NewString(), + RepositoryID: uuid.New(), + RepositoryName: uuid.NewString(), + Status: analysisEnum.Success, + Errors: "", + CreatedAt: time.Now(), + FinishedAt: time.Now(), + AnalysisVulnerabilities: []analysis.AnalysisVulnerabilities{ + { + VulnerabilityID: uuid.New(), + AnalysisID: uuid.New(), + CreatedAt: time.Now(), + Vulnerability: vulnerability.Vulnerability{ + VulnerabilityID: uuid.New(), + Line: "1", + Column: "1", + Confidence: confidence.High, + File: "/deployments/cert.pem", + Code: "-----BEGIN CERTIFICATE-----", + Details: "Asymmetric Private Key \n Found SSH and/or x.509 Cerficates among the files of your project, make sure you want this kind of information inside your Git repo, since it can be missused by someone with access to any kind of copy. For more information checkout the CWE-312 (https://cwe.mitre.org/data/definitions/312.html) advisory.", + SecurityTool: "Wrong security tool", + Language: languages.Leaks, + Severity: severities.Critical, + VulnHash: "1234567890", + Type: vulnerabilityEnum.Vulnerability, + CommitAuthor: "Wilian Gabriel", + CommitEmail: "wilian.silva@zup.com.br", + CommitHash: "9876543210", + CommitMessage: "Initial Commit", + CommitDate: "2021-03-31T10:58:42Z", + DeprecatedHashes: []string{"oldHash", "oldHash1"}, + }, + }, + }, + }) + assert.NoError(t, err) + assert.NotEqual(t, res, uuid.Nil) + }) t.Run("Should save analysis with error when RawQuery fails", func(t *testing.T) { brokerMock := &broker.Mock{} brokerMock.On("Publish").Return(nil) appConfigMock := &appConfiguration.Mock{} repoRepositoryMock := &repository.Mock{} repoAnalysisMock := &repoAnalysis.Mock{} - repoAnalysisMock.On("RawQuery").Return(errors.New("some error")) - repoAnalysisMock.On("FindVulnerabilitiesByHashSliceInRepository").Return(response.NewResponse( + repoAnalysisMock.On("SaveTreatCompatibility").Return(errors.New("some error")) + repoAnalysisMock.On("FindAllVulnerabilitiesByHashesAndRepository").Return(response.NewResponse( 1, nil, - &[]map[string]interface{}{ + &[]vulnerability.Vulnerability{ { - "vulnerability_id": "47a7807b-ca70-41b1-8ff2-432a5d1752fd", - "vuln_hash": "oldHash", + VulnerabilityID: uuid.New(), + VulnHash: "oldHash", }, }, )) @@ -771,14 +839,14 @@ func TestController_SaveAnalysis(t *testing.T) { assert.Error(t, err) assert.Equal(t, res, uuid.Nil) }) - t.Run("Should save analysis with error when nil response.GetData on FindVulnerabilitiesByHashSliceInRepository", func(t *testing.T) { + t.Run("Should save analysis with error when nil response.GetData on FindAllVulnerabilitiesByHashesAndRepository", func(t *testing.T) { brokerMock := &broker.Mock{} brokerMock.On("Publish").Return(nil) appConfigMock := &appConfiguration.Mock{} repoRepositoryMock := &repository.Mock{} repoAnalysisMock := &repoAnalysis.Mock{} - repoAnalysisMock.On("RawQuery").Return(errors.New("some error")) - repoAnalysisMock.On("FindVulnerabilitiesByHashSliceInRepository").Return(response.NewResponse( + repoAnalysisMock.On("SaveTreatCompatibility").Return(errors.New("some error")) + repoAnalysisMock.On("FindAllVulnerabilitiesByHashesAndRepository").Return(response.NewResponse( 1, nil, nil, @@ -836,7 +904,7 @@ func TestController_SaveAnalysis(t *testing.T) { }, }, }) - assert.Error(t, err) - assert.Equal(t, res, uuid.Nil) + assert.NoError(t, err) + assert.NotEqual(t, res, uuid.Nil) }) } diff --git a/api/internal/repositories/analysis/analysis.go b/api/internal/repositories/analysis/analysis.go index 575300a6a..8134b3ced 100644 --- a/api/internal/repositories/analysis/analysis.go +++ b/api/internal/repositories/analysis/analysis.go @@ -32,8 +32,8 @@ import ( type IAnalysis interface { FindAnalysisByID(analysisID uuid.UUID) response.IResponse CreateFullAnalysis(newAnalysis *analysis.Analysis) error - FindVulnerabilitiesByHashSliceInRepository(vulnHash []string, repositoryID uuid.UUID) response.IResponse - RawQuery(string string, values ...interface{}) error + FindAllVulnerabilitiesByHashesAndRepository(hashes []string, repositoryID uuid.UUID) response.IResponse + SaveTreatCompatibility(mapHashToVulnerabilityID map[string]uuid.UUID, newAnalysis *analysis.Analysis) error } type Analysis struct { @@ -172,23 +172,40 @@ func (a *Analysis) findVulnerabilityByHashInRepository(vulnHash string, reposito return a.databaseRead.Raw(query, map[string]interface{}{}, vulnHash, repositoryID) } -func (a *Analysis) FindVulnerabilitiesByHashSliceInRepository(vulnHash []string, +func (a *Analysis) FindAllVulnerabilitiesByHashesAndRepository(hashes []string, repositoryID uuid.UUID) response.IResponse { query := ` - SELECT DISTINCT vulnerabilities.vulnerability_id as vulnerability_id, - vulnerabilities.vuln_hash as vuln_hash + SELECT DISTINCT vulnerabilities.vulnerability_id as vulnerability_id, vulnerabilities.vuln_hash as vuln_hash FROM vulnerabilities INNER JOIN analysis_vulnerabilities ON vulnerabilities.vulnerability_id = analysis_vulnerabilities.vulnerability_id INNER JOIN analysis ON analysis_vulnerabilities.analysis_id = analysis.analysis_id WHERE vulnerabilities.vuln_hash IN ? AND analysis.repository_id = ? ` - object := make([]map[string]interface{}, 0) - return a.databaseRead.Raw(query, &object, vulnHash, repositoryID) + return a.databaseRead.Raw(query, &[]vulnerability.Vulnerability{}, hashes, repositoryID) } -// Deprecated: RawQuery starts a transaction and try to execute the raw query into database. -// is not recommended using this and the method will not be available after cli v2.10.0 -func (a *Analysis) RawQuery(rawQuery string, values ...interface{}) error { +// Deprecated: SaveTreatCompatibility starts a transaction and try to execute the raw query into database. +// is not recommended using this and the method will not be available after cli v2.10.0. +// The mapHashToVulnerabilityID param is the deprecated hash on the key and +// its value is vulnerabilityID founded in database the newAnalysis param is required to compare +// if exists deprecated hash from analysis and to update in database. +// nolint:funlen // funlen is not necessary in deprecated hash +func (a *Analysis) SaveTreatCompatibility(mapHashToVulnerabilityID map[string]uuid.UUID, + newAnalysis *analysis.Analysis) error { + rawQuery := "" + values := []string{} + for indexNN := range newAnalysis.AnalysisVulnerabilities { + manyToMany := newAnalysis.AnalysisVulnerabilities[indexNN] + for _, deprecatedHash := range manyToMany.Vulnerability.DeprecatedHashes { + if mapHashToVulnerabilityID[deprecatedHash] != uuid.Nil { + rawQuery += "UPDATE vulnerabilities SET vuln_hash = ? where vulnerability_id = ? ;\n" + values = append(values, manyToMany.Vulnerability.VulnHash, mapHashToVulnerabilityID[deprecatedHash].String()) + } + } + } + if rawQuery == "" { + return nil + } return a.databaseWrite.Exec(rawQuery, values) } diff --git a/api/internal/repositories/analysis/analysis_mock.go b/api/internal/repositories/analysis/analysis_mock.go index a3aaa6836..0d2f49a0d 100644 --- a/api/internal/repositories/analysis/analysis_mock.go +++ b/api/internal/repositories/analysis/analysis_mock.go @@ -27,11 +27,6 @@ type Mock struct { mock.Mock } -func (m *Mock) RawQuery(_ string, _ ...interface{}) error { - args := m.MethodCalled("RawQuery") - return utilsMock.ReturnNilOrError(args, 0) -} - func (m *Mock) FindAnalysisByID(_ uuid.UUID) response.IResponse { args := m.MethodCalled("FindAnalysisByID") return args.Get(0).(response.IResponse) @@ -41,7 +36,12 @@ func (m *Mock) CreateFullAnalysis(analysisArgument *analysis.Analysis) error { args := m.MethodCalled("CreateFullAnalysisResponse") return utilsMock.ReturnNilOrError(args, 0) } -func (m *Mock) FindVulnerabilitiesByHashSliceInRepository(vulnHash []string, repositoryID uuid.UUID) response.IResponse { - args := m.MethodCalled("FindVulnerabilitiesByHashSliceInRepository") +func (m *Mock) FindAllVulnerabilitiesByHashesAndRepository(_ []string, _ uuid.UUID) response.IResponse { + args := m.MethodCalled("FindAllVulnerabilitiesByHashesAndRepository") return args.Get(0).(response.IResponse) } + +func (m *Mock) SaveTreatCompatibility(_ map[string]uuid.UUID, _ *analysis.Analysis) error { + args := m.MethodCalled("SaveTreatCompatibility") + return utilsMock.ReturnNilOrError(args, 0) +} diff --git a/api/internal/repositories/analysis/analysis_test.go b/api/internal/repositories/analysis/analysis_test.go index 8fe455e76..23c55c5a1 100644 --- a/api/internal/repositories/analysis/analysis_test.go +++ b/api/internal/repositories/analysis/analysis_test.go @@ -438,14 +438,14 @@ func TestAnalysis_CreateFullAnalysis(t *testing.T) { err := NewRepositoriesAnalysis(connectionMock).CreateFullAnalysis(data) assert.Error(t, err) }) - t.Run("Should run FindVulnerabilitiesByHashSliceInRepository", func(t *testing.T) { + t.Run("Should run FindAllVulnerabilitiesByHashesAndRepository", func(t *testing.T) { mockRead := &database.Mock{} connectionMock := &database.Connection{ Write: nil, Read: mockRead, } mockRead.On("Raw").Return(response.NewResponse(1, nil, nil)) - res := NewRepositoriesAnalysis(connectionMock).FindVulnerabilitiesByHashSliceInRepository([]string{"something"}, uuid.New()) + res := NewRepositoriesAnalysis(connectionMock).FindAllVulnerabilitiesByHashesAndRepository([]string{"something"}, uuid.New()) assert.NoError(t, res.GetError()) }) t.Run("Should run Exec", func(t *testing.T) { @@ -455,7 +455,95 @@ func TestAnalysis_CreateFullAnalysis(t *testing.T) { Read: nil, } mockWrite.On("Exec").Return(nil) - err := NewRepositoriesAnalysis(connectionMock).RawQuery("something") + err := NewRepositoriesAnalysis(connectionMock).SaveTreatCompatibility(map[string]uuid.UUID{"something": uuid.New()}, &analysis.Analysis{}) + assert.NoError(t, err) + }) + t.Run("Should run SaveTreatCompatibility with success", func(t *testing.T) { + mockWrite := &database.Mock{} + connectionMock := &database.Connection{ + Write: mockWrite, + Read: nil, + } + mockWrite.On("Exec").Return(nil) + vulnerabilityID := uuid.New() + mapHashes := map[string]uuid.UUID{ + "oldHash": uuid.New(), + "oldHash1": uuid.New(), + } + newAnalysis := &analysis.Analysis{ + AnalysisVulnerabilities: []analysis.AnalysisVulnerabilities{ + { + VulnerabilityID: vulnerabilityID, + AnalysisID: uuid.New(), + CreatedAt: time.Now(), + Vulnerability: vulnerability.Vulnerability{ + VulnerabilityID: vulnerabilityID, + Line: "1", + Column: "1", + Confidence: confidence.High, + File: "/deployments/cert.pem", + Code: "-----BEGIN CERTIFICATE-----", + Details: "Asymmetric Private Key \n Found SSH and/or x.509 Cerficates among the files of your project, make sure you want this kind of information inside your Git repo, since it can be missused by someone with access to any kind of copy. For more information checkout the CWE-312 (https://cwe.mitre.org/data/definitions/312.html) advisory.", + SecurityTool: "Wrong security tool", + Language: languages.Leaks, + Severity: severities.Critical, + VulnHash: "1234567890", + Type: vulnerabilityEnum.Vulnerability, + CommitAuthor: "Wilian Gabriel", + CommitEmail: "wilian.silva@zup.com.br", + CommitHash: "9876543210", + CommitMessage: "Initial Commit", + CommitDate: "2021-03-31T10:58:42Z", + DeprecatedHashes: []string{"oldHash", "oldHash1"}, + }, + }, + }, + } + err := NewRepositoriesAnalysis(connectionMock).SaveTreatCompatibility(mapHashes, newAnalysis) + assert.NoError(t, err) + }) + + t.Run("Should run SaveTreatCompatibility and return not error but not run any query", func(t *testing.T) { + mockWrite := &database.Mock{} + connectionMock := &database.Connection{ + Write: mockWrite, + Read: nil, + } + mockWrite.On("Exec").Return(nil) + vulnerabilityID := uuid.New() + mapHashes := map[string]uuid.UUID{ + "other-hash-not-exists": uuid.New(), + } + newAnalysis := &analysis.Analysis{ + AnalysisVulnerabilities: []analysis.AnalysisVulnerabilities{ + { + VulnerabilityID: vulnerabilityID, + AnalysisID: uuid.New(), + CreatedAt: time.Now(), + Vulnerability: vulnerability.Vulnerability{ + VulnerabilityID: vulnerabilityID, + Line: "1", + Column: "1", + Confidence: confidence.High, + File: "/deployments/cert.pem", + Code: "-----BEGIN CERTIFICATE-----", + Details: "Asymmetric Private Key \n Found SSH and/or x.509 Cerficates among the files of your project, make sure you want this kind of information inside your Git repo, since it can be missused by someone with access to any kind of copy. For more information checkout the CWE-312 (https://cwe.mitre.org/data/definitions/312.html) advisory.", + SecurityTool: "Wrong security tool", + Language: languages.Leaks, + Severity: severities.Critical, + VulnHash: "1234567890", + Type: vulnerabilityEnum.Vulnerability, + CommitAuthor: "Wilian Gabriel", + CommitEmail: "wilian.silva@zup.com.br", + CommitHash: "9876543210", + CommitMessage: "Initial Commit", + CommitDate: "2021-03-31T10:58:42Z", + DeprecatedHashes: []string{"oldHash", "oldHash1"}, + }, + }, + }, + } + err := NewRepositoriesAnalysis(connectionMock).SaveTreatCompatibility(mapHashes, newAnalysis) assert.NoError(t, err) }) }