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

Gradle create fix pull request #478

Merged
merged 50 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
664ef75
starting gradle package handler & adding to commonpackagehandler
eranturgeman Sep 6, 2023
0be931e
Merge branch 'dev' of https://github.com/jfrog/frogbot into gradle-cr…
eranturgeman Sep 6, 2023
e642da3
added up to row detection and started the fixers file
eranturgeman Sep 7, 2023
9f6d3f7
Merge branch 'dev' of https://github.com/jfrog/frogbot into gradle-cr…
eranturgeman Sep 7, 2023
c2993aa
basic framework completed
eranturgeman Sep 10, 2023
7a5357a
Merge branch 'dev' of https://github.com/jfrog/frogbot into gradle-cr…
eranturgeman Sep 11, 2023
5a04949
fixed some cases. start adding cases
eranturgeman Sep 11, 2023
c2ab98d
merging vulnerable rows detection and row's type detection
eranturgeman Sep 11, 2023
f34fccb
before deleting old fixBuildFile and related functions
eranturgeman Sep 12, 2023
379a046
stable version: fixing all Groovy direct dependencies
eranturgeman Sep 12, 2023
9458852
fixed some issues and improved code - groovy ready except notes
eranturgeman Sep 12, 2023
6ebe6d3
Merge branch 'dev' of https://github.com/jfrog/frogbot into gradle-cr…
eranturgeman Sep 13, 2023
9b652de
minor improvements
eranturgeman Sep 13, 2023
ea1b646
added tests for create-fix with gradle (groovy)
eranturgeman Sep 13, 2023
f6f440e
Merge branch 'dev' into gradle-create-fix-pull-request
eranturgeman Sep 13, 2023
36e81fb
supports kotlin
eranturgeman Sep 13, 2023
814e2b4
Merge remote-tracking branch 'eran-fork/gradle-create-fix-pull-reques…
eranturgeman Sep 13, 2023
a3a3aa6
minor fix
eranturgeman Sep 13, 2023
8fc2bb7
Merge branch 'dev' of https://github.com/jfrog/frogbot into gradle-cr…
eranturgeman Sep 14, 2023
8ebf0f8
added tests and improved logging
eranturgeman Sep 14, 2023
3e160df
simplified the flow. delete all redundant and fix tests
eranturgeman Sep 15, 2023
3a28d3f
fixed all flow and tests
eranturgeman Sep 18, 2023
5f3e453
.
eranturgeman Sep 18, 2023
16566a7
Merge branch 'dev' into gradle-create-fix-pull-request
eranturgeman Sep 18, 2023
47a5d0e
Merge branch 'dev' of https://github.com/jfrog/frogbot into gradle-cr…
eranturgeman Sep 18, 2023
bae7447
.
eranturgeman Sep 18, 2023
c5c8404
Merge remote-tracking branch 'eran-fork/gradle-create-fix-pull-reques…
eranturgeman Sep 18, 2023
9155fad
fixing issues
eranturgeman Sep 18, 2023
28d7424
Merge branch 'dev' of https://github.com/jfrog/frogbot into gradle-cr…
eranturgeman Sep 19, 2023
a50a6ed
Merge branch 'dev' into gradle-create-fix-pull-request
eranturgeman Sep 19, 2023
d4a3643
Merge branch 'dev' of https://github.com/jfrog/frogbot into gradle-cr…
eranturgeman Sep 19, 2023
5ae7443
Merge branch 'gradle-create-fix-pull-request' of https://github.com/e…
eranturgeman Sep 19, 2023
9e30d4b
fixing breaks after pull
eranturgeman Sep 19, 2023
31ab71c
adding patch for latest release
eranturgeman Sep 19, 2023
a4a58f2
.
eranturgeman Sep 19, 2023
12fab53
.
eranturgeman Sep 19, 2023
dd8fe0a
optimized flow and fixed tests
eranturgeman Sep 26, 2023
cab8168
Merge branch 'dev' of https://github.com/jfrog/frogbot into gradle-cr…
eranturgeman Sep 26, 2023
7639560
all old commented- before delete
eranturgeman Sep 26, 2023
3762450
old flow deleted
eranturgeman Sep 26, 2023
7d8595e
Merge branch 'dev' of https://github.com/jfrog/frogbot into gradle-cr…
eranturgeman Sep 26, 2023
36ec474
minor fix
eranturgeman Sep 26, 2023
9569ab4
minor fix
eranturgeman Sep 26, 2023
171e193
before deleting old fix flow with precompiled regexps
eranturgeman Sep 26, 2023
def29f7
fixed pr issues
eranturgeman Sep 27, 2023
dc92966
Merge branch 'dev' of https://github.com/jfrog/frogbot into gradle-cr…
eranturgeman Sep 27, 2023
c17eba6
minor fix
eranturgeman Sep 27, 2023
435895c
minor fix
eranturgeman Sep 27, 2023
7aadf8f
fixed pr issues, fixed tests and fixed readme
eranturgeman Sep 27, 2023
bb415b2
fixed pr issues
eranturgeman Sep 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packagehandlers/commonpackagehandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ func GetCompatiblePackageHandler(vulnDetails *utils.VulnerabilityDetails, detail
handler = &MavenPackageHandler{depsRepo: details.DepsRepo, ServerDetails: details.ServerDetails}
case coreutils.Nuget:
handler = &NugetPackageHandler{}
case coreutils.Gradle:
handler = &GradlePackageHandler{}
default:
handler = &UnsupportedPackageHandler{}
}
Expand Down
164 changes: 164 additions & 0 deletions packagehandlers/gradlepackagehandler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package packagehandlers

import (
"fmt"
"github.com/jfrog/frogbot/utils"
"github.com/jfrog/jfrog-client-go/utils/errorutils"
"io/fs"
"os"
"path/filepath"
"regexp"
"strings"
)

const (
groovyBuildFileSuffix = "build.gradle"
kotlinBuildFileSuffix = "build.gradle.kts"
apostrophes = "[\\\"|\\']"
directMapRegexpEntry = "\\s*%s\\s*[:|=]\\s*"
directStringWithVersionFormat = "%s:%s:%s"
)

var directMapWithVersionRegexp string

func init() {
// Initializing a regexp pattern for map dependencies
// Example: group: "junit", name: "junit", version: "1.0.0" | group = "junit", name = "junit", version = "1.0.0"
groupEntry := getMapRegexpEntry("group")
nameEntry := getMapRegexpEntry("name")
versionEntry := getMapRegexpEntry("version")
directMapWithVersionRegexp = groupEntry + "," + nameEntry + "," + versionEntry
}
eranturgeman marked this conversation as resolved.
Show resolved Hide resolved

type GradlePackageHandler struct {
CommonPackageHandler
}

func (gph *GradlePackageHandler) UpdateDependency(vulnDetails *utils.VulnerabilityDetails) error {
if vulnDetails.IsDirectDependency {
return gph.updateDirectDependency(vulnDetails)
}

return &utils.ErrUnsupportedFix{
PackageName: vulnDetails.ImpactedDependencyName,
FixedVersion: vulnDetails.SuggestedFixedVersion,
ErrorType: utils.IndirectDependencyFixNotSupported,
}
}

func (gph *GradlePackageHandler) updateDirectDependency(vulnDetails *utils.VulnerabilityDetails) (err error) {
if !isVersionSupportedForFix(vulnDetails.ImpactedDependencyVersion) {
return &utils.ErrUnsupportedFix{
PackageName: vulnDetails.ImpactedDependencyName,
FixedVersion: vulnDetails.SuggestedFixedVersion,
ErrorType: utils.UnsupportedForFixVulnerableVersion,
}
}

eranturgeman marked this conversation as resolved.
Show resolved Hide resolved
// A gradle project may contain several descriptor files in several sub-modules. Each vulnerability may be found in each of the descriptor files.
// Therefore we iterate over every descriptor file for each vulnerability and try to find and fix it.
descriptorFilesPaths, err := getDescriptorFilesPaths()
if err != nil {
return
}

for _, descriptorFilePath := range descriptorFilesPaths {
err = fixVulnerabilityIfExists(descriptorFilePath, vulnDetails)
if err != nil {
return
}
}
return
}

// Checks if the impacted version is currently supported for fix
func isVersionSupportedForFix(impactedVersion string) bool {
if strings.Contains(impactedVersion, "+") ||
(strings.Contains(impactedVersion, "[") || strings.Contains(impactedVersion, "(")) ||
strings.Contains(impactedVersion, "latest.release") {
return false
}
return true
}

// Collects all descriptor files absolute paths
func getDescriptorFilesPaths() (descriptorFilesPaths []string, err error) {
err = filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return fmt.Errorf("error has occured when trying to access or traverse the files system: %s", err.Error())
}

if d.Type().IsRegular() && (strings.HasSuffix(path, groovyBuildFileSuffix) || strings.HasSuffix(path, kotlinBuildFileSuffix)) {
eranturgeman marked this conversation as resolved.
Show resolved Hide resolved
absFilePath, innerErr := filepath.Abs(path)
if innerErr != nil {
return fmt.Errorf("couldn't retrieve file's absolute path for './%s':%s", path, innerErr.Error())
}
descriptorFilesPaths = append(descriptorFilesPaths, absFilePath)
}
return nil
})
return
}

// Fixes all direct occurrences (string/map) of the given vulnerability in the given descriptor file if vulnerability occurs
func fixVulnerabilityIfExists(descriptorFilePath string, vulnDetails *utils.VulnerabilityDetails) (err error) {
byteFileContent, err := os.ReadFile(descriptorFilePath)
if err != nil {
err = fmt.Errorf("couldn't read file '%s': %s", descriptorFilePath, err.Error())
return
}
fileContent := string(byteFileContent)

depGroup, depName, err := getVulnerabilityGroupAndName(vulnDetails.ImpactedDependencyName)
if err != nil {
return
}

// Fixing all vulnerable rows in string format
directStringVulnerableRow := fmt.Sprintf(directStringWithVersionFormat, depGroup, depName, vulnDetails.ImpactedDependencyVersion)
directStringFixedRow := fmt.Sprintf(directStringWithVersionFormat, depGroup, depName, vulnDetails.SuggestedFixedVersion)
fileContent = strings.ReplaceAll(fileContent, directStringVulnerableRow, directStringFixedRow)

// Fixing all vulnerable rows in a map format
mapRegexpForVulnerability := fmt.Sprintf(directMapWithVersionRegexp, depGroup, depName, vulnDetails.ImpactedDependencyVersion)
regexpCompiler := regexp.MustCompile(mapRegexpForVulnerability)
if rowsMatches := regexpCompiler.FindAllString(fileContent, -1); rowsMatches != nil {
for _, entry := range rowsMatches {
fixedRow := strings.Replace(entry, vulnDetails.ImpactedDependencyVersion, vulnDetails.SuggestedFixedVersion, 1)
fileContent = strings.ReplaceAll(fileContent, entry, fixedRow)
}
}

err = writeUpdatedBuildFile(descriptorFilePath, fileContent)
eranturgeman marked this conversation as resolved.
Show resolved Hide resolved
return
}

// Returns separated 'group' and 'name' for a given vulnerability name
func getVulnerabilityGroupAndName(impactedDependencyName string) (depGroup string, depName string, err error) {
seperatedImpactedDepName := strings.Split(impactedDependencyName, ":")
if len(seperatedImpactedDepName) != 2 {
err = errorutils.CheckErrorf("unable to parse impacted dependency name '%s'", impactedDependencyName)
eranturgeman marked this conversation as resolved.
Show resolved Hide resolved
return
}
return seperatedImpactedDepName[0], seperatedImpactedDepName[1], err
}

func getMapRegexpEntry(mapEntry string) string {
return fmt.Sprintf(directMapRegexpEntry, mapEntry) + apostrophes + "%s" + apostrophes
}

// Writes the updated content of the descriptor's file into the file
func writeUpdatedBuildFile(filePath string, fileContent string) (err error) {
fileInfo, err := os.Stat(filePath)
if err != nil {
err = fmt.Errorf("couldn't get file info for file '%s': %s", filePath, err.Error())
return
}
filePerm := fileInfo.Mode()

err = os.WriteFile(filePath, []byte(fileContent), filePerm)
eranturgeman marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
err = fmt.Errorf("couldn't write fixes to file '%s': %q", filePath, err)
}
return
}
162 changes: 162 additions & 0 deletions packagehandlers/packagehandlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import (
"fmt"
testdatautils "github.com/jfrog/build-info-go/build/testdata"
biutils "github.com/jfrog/build-info-go/utils"
fileutils "github.com/jfrog/build-info-go/utils"
"github.com/jfrog/frogbot/utils"
"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
"github.com/jfrog/jfrog-cli-core/v2/xray/formats"
"github.com/stretchr/testify/assert"
"math"
"os"
"path/filepath"
"regexp"
Expand Down Expand Up @@ -218,6 +220,50 @@ func TestUpdateDependency(t *testing.T) {
fixSupported: true,
},
},

// Gradle test cases
{
{
vulnDetails: &utils.VulnerabilityDetails{
SuggestedFixedVersion: "4.13.1",
IsDirectDependency: false,
VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Gradle, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "commons-collections:commons-collections", ImpactedDependencyVersion: "3.2"}},
},
fixSupported: false,
},
{ // Unsupported fix: dynamic version
vulnDetails: &utils.VulnerabilityDetails{
SuggestedFixedVersion: "3.2.2",
IsDirectDependency: true,
VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Gradle, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "commons-collections:commons-collections", ImpactedDependencyVersion: "3.+"}},
},
fixSupported: false,
},
{ // Unsupported fix: latest version
vulnDetails: &utils.VulnerabilityDetails{
SuggestedFixedVersion: "3.2.2",
IsDirectDependency: true,
VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Gradle, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "commons-collections:commons-collections", ImpactedDependencyVersion: "latest.release"}},
},
fixSupported: false,
},
{ // Unsupported fix: range version
vulnDetails: &utils.VulnerabilityDetails{
SuggestedFixedVersion: "3.2.2",
IsDirectDependency: true,
VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Gradle, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "commons-collections:commons-collections", ImpactedDependencyVersion: "[3.0, 3.5.6)"}},
},
fixSupported: false,
},
{
vulnDetails: &utils.VulnerabilityDetails{
SuggestedFixedVersion: "4.13.1",
IsDirectDependency: true,
VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Gradle, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "junit:junit", ImpactedDependencyVersion: "4.7"}},
},
fixSupported: true,
},
},
}

for _, testBatch := range testCases {
Expand Down Expand Up @@ -568,6 +614,8 @@ func uniquePackageManagerChecks(t *testing.T, test dependencyFixTest) {
case coreutils.Go:
packageDescriptor := extraArgs[0]
assertFixVersionInPackageDescriptor(t, test, packageDescriptor)
case coreutils.Gradle:
checkVulnVersionFixInBuildFile(t, test)
default:
}
}
Expand Down Expand Up @@ -598,3 +646,117 @@ func TestGetFixedPackage(t *testing.T) {
assert.Equal(t, test.expectedOutput, fixedPackageArgs)
}
}

func checkVulnVersionFixInBuildFile(t *testing.T, testcase dependencyFixTest) {
depGroup, depName, err := getVulnerabilityGroupAndName(testcase.vulnDetails.ImpactedDependencyName)
assert.NoError(t, err)

stringPatternForVulnerability := fmt.Sprintf(directStringWithVersionFormat, depGroup, depName, testcase.vulnDetails.ImpactedDependencyVersion)
mapRegexpForVulnerability := fmt.Sprintf(directMapWithVersionRegexp, depGroup, depName, testcase.vulnDetails.ImpactedDependencyVersion)
mapRegexpCompiler := regexp.MustCompile(mapRegexpForVulnerability)

descriptorFilesPaths, err := getDescriptorFilesPaths()
eranturgeman marked this conversation as resolved.
Show resolved Hide resolved
assert.NoError(t, err)

for _, filePath := range descriptorFilesPaths {
fileContent, readErr := os.ReadFile(filePath)
assert.NoError(t, readErr)
eranturgeman marked this conversation as resolved.
Show resolved Hide resolved

// Checking there is no unfixed rows in a string format
assert.NotContains(t, fileContent, stringPatternForVulnerability)

// Checking there is no unfixed rows in a map format
rowsMatches := mapRegexpCompiler.FindAllString(string(fileContent), -1)
assert.Empty(t, rowsMatches)
}
}

func TestGradleGetDescriptorFilesPaths(t *testing.T) {
currDir, err := os.Getwd()
assert.NoError(t, err)
tmpDir, err := os.MkdirTemp("", "")
assert.NoError(t, err)
assert.NoError(t, biutils.CopyDir(filepath.Join("..", "testdata", "projects", "gradle"), tmpDir, true, nil))
assert.NoError(t, os.Chdir(tmpDir))
defer func() {
assert.NoError(t, os.Chdir(currDir))
}()
finalPath, err := os.Getwd()
assert.NoError(t, err)

expectedResults := []string{filepath.Join(finalPath, "build.gradle"), filepath.Join(finalPath, "innerProjectForTest", "build.gradle.kts")}

buildFilesPaths, err := getDescriptorFilesPaths()
assert.NoError(t, err)
assert.ElementsMatch(t, expectedResults, buildFilesPaths)
}

func TestGradleFixVulnerabilityIfExists(t *testing.T) {
currDir, err := os.Getwd()
assert.NoError(t, err)

tmpDir, err := os.MkdirTemp("", "")
assert.NoError(t, err)
assert.NoError(t, biutils.CopyDir(filepath.Join("..", "testdata", "projects", "gradle", "innerProjectForTest"), tmpDir, true, nil))
assert.NoError(t, os.Chdir(tmpDir))
defer func() {
assert.NoError(t, os.Chdir(currDir))
}()

buildFiles, err := getDescriptorFilesPaths()
assert.NoError(t, err)

err = fixVulnerabilityIfExists(buildFiles[0], &utils.VulnerabilityDetails{
SuggestedFixedVersion: "4.13.1",
IsDirectDependency: true,
VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Gradle, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "junit:junit", ImpactedDependencyVersion: "4.7"}}})

assert.NoError(t, err)

finalPath, err := os.Getwd()
assert.NoError(t, err)

expectedFileContent, err := fileutils.ReadNLines(filepath.Join(finalPath, "fixedBuildGradleKtsForCompare.txt"), math.MaxInt)
assert.NoError(t, err)

fixedFileContent, err := fileutils.ReadNLines(filepath.Join(finalPath, "build.gradle.kts"), math.MaxInt)
assert.NoError(t, err)

assert.ElementsMatch(t, expectedFileContent, fixedFileContent)
}

func TestGradleIsVersionSupportedForFix(t *testing.T) {
var testcases = []struct {
impactedVersion string
expectedResult bool
}{
{
impactedVersion: "10.+",
expectedResult: false,
},
{
impactedVersion: "[10.3, 11.0)",
expectedResult: false,
},
{
impactedVersion: "(10.4.2, 11.7.8)",
expectedResult: false,
},
{
impactedVersion: "latest.release",
expectedResult: false,
},
{
impactedVersion: "5.5",
expectedResult: true,
},
{
impactedVersion: "9.0.13-beta",
expectedResult: true,
},
}

for _, testcase := range testcases {
assert.Equal(t, testcase.expectedResult, isVersionSupportedForFix(testcase.impactedVersion))
}
}
Loading
Loading