github.com/jfrog/frogbot@v1.1.1-0.20231221090046-821a26f50338/packagehandlers/packagehandlers_test.go (about) 1 package packagehandlers 2 3 import ( 4 "fmt" 5 "github.com/jfrog/build-info-go/tests" 6 biutils "github.com/jfrog/build-info-go/utils" 7 "github.com/jfrog/frogbot/utils" 8 "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" 9 "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/sca/java" 10 "github.com/jfrog/jfrog-cli-core/v2/xray/formats" 11 "github.com/jfrog/jfrog-client-go/utils/io/fileutils" 12 "github.com/stretchr/testify/assert" 13 "os" 14 "path/filepath" 15 "regexp" 16 "strconv" 17 "strings" 18 "testing" 19 ) 20 21 type dependencyFixTest struct { 22 vulnDetails *utils.VulnerabilityDetails 23 scanDetails *utils.ScanDetails 24 fixSupported bool 25 specificTechVersion string 26 uniqueChecksExtraArgs []string 27 testDirName string 28 } 29 30 const ( 31 requirementsFile = "oslo.config>=1.12.1,<1.13\noslo.utils<5.0,>=4.0.0\nparamiko==2.7.2\npasslib<=1.7.4\nprance>=0.9.0\nprompt-toolkit~=1.0.15\npyinotify>0.9.6\nPyJWT>1.7.1\nurllib3 > 1.1.9, < 1.5.*" 32 GoPackageDescriptor = "go.mod" 33 ) 34 35 type pipPackageRegexTest struct { 36 packageName string 37 expectedRequirement string 38 } 39 40 func TestUpdateDependency(t *testing.T) { 41 testCases := [][]dependencyFixTest{ 42 // Go test cases 43 { 44 { 45 vulnDetails: &utils.VulnerabilityDetails{ 46 SuggestedFixedVersion: "0.0.0-20201216223049-8b5274cf687f", 47 IsDirectDependency: false, 48 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Go, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "golang.org/x/crypto"}}, 49 }, 50 fixSupported: true, 51 uniqueChecksExtraArgs: []string{GoPackageDescriptor}, 52 }, 53 { 54 vulnDetails: &utils.VulnerabilityDetails{ 55 SuggestedFixedVersion: "1.7.7", 56 IsDirectDependency: true, 57 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Go, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "github.com/gin-gonic/gin"}}, 58 }, 59 fixSupported: true, 60 uniqueChecksExtraArgs: []string{GoPackageDescriptor}, 61 }, 62 { 63 vulnDetails: &utils.VulnerabilityDetails{ 64 SuggestedFixedVersion: "1.3.0", 65 IsDirectDependency: true, 66 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Go, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "github.com/google/uuid"}}, 67 }, 68 fixSupported: true, 69 uniqueChecksExtraArgs: []string{GoPackageDescriptor}, 70 }, 71 }, 72 73 // Python test cases (includes pip, pipenv, poetry) 74 { 75 { 76 vulnDetails: &utils.VulnerabilityDetails{ 77 SuggestedFixedVersion: "1.25.9", 78 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Pip, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "urllib3"}}, 79 }, 80 scanDetails: &utils.ScanDetails{Project: &utils.Project{PipRequirementsFile: "requirements.txt"}}, 81 fixSupported: false, 82 }, 83 { 84 vulnDetails: &utils.VulnerabilityDetails{ 85 SuggestedFixedVersion: "1.25.9", 86 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Poetry, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "urllib3"}}, 87 }, 88 scanDetails: &utils.ScanDetails{Project: &utils.Project{PipRequirementsFile: "pyproejct.toml"}}, 89 fixSupported: false, 90 }, 91 { 92 vulnDetails: &utils.VulnerabilityDetails{ 93 SuggestedFixedVersion: "1.25.9", 94 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Pipenv, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "urllib3"}}, 95 }, 96 scanDetails: &utils.ScanDetails{Project: &utils.Project{PipRequirementsFile: "Pipfile"}}, 97 fixSupported: false, 98 }, 99 { 100 vulnDetails: &utils.VulnerabilityDetails{ 101 SuggestedFixedVersion: "2.4.0", 102 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Pip, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "pyjwt"}}, 103 IsDirectDependency: true, 104 }, 105 scanDetails: &utils.ScanDetails{Project: &utils.Project{PipRequirementsFile: "requirements.txt"}}, 106 fixSupported: true, 107 }, 108 { 109 vulnDetails: &utils.VulnerabilityDetails{ 110 SuggestedFixedVersion: "2.4.0", 111 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Pip, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "Pyjwt"}}, 112 IsDirectDependency: true}, 113 scanDetails: &utils.ScanDetails{Project: &utils.Project{PipRequirementsFile: "requirements.txt"}}, 114 fixSupported: true, 115 }, 116 { 117 vulnDetails: &utils.VulnerabilityDetails{ 118 SuggestedFixedVersion: "2.4.0", 119 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Pip, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "pyjwt"}}, 120 IsDirectDependency: true}, 121 scanDetails: &utils.ScanDetails{Project: &utils.Project{PipRequirementsFile: "setup.py"}}, 122 fixSupported: true, 123 }, 124 { 125 vulnDetails: &utils.VulnerabilityDetails{ 126 SuggestedFixedVersion: "2.4.0", 127 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Poetry, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "pyjwt"}}, 128 IsDirectDependency: true}, 129 scanDetails: &utils.ScanDetails{Project: &utils.Project{PipRequirementsFile: "pyproject.toml"}}, 130 fixSupported: true, 131 }, 132 }, 133 134 // Npm test cases 135 { 136 { 137 vulnDetails: &utils.VulnerabilityDetails{ 138 SuggestedFixedVersion: "0.8.4", 139 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Npm, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "mpath"}}, 140 }, 141 fixSupported: false, 142 }, 143 { 144 vulnDetails: &utils.VulnerabilityDetails{ 145 SuggestedFixedVersion: "3.0.2", 146 IsDirectDependency: true, 147 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Npm, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "minimatch"}}, 148 }, 149 fixSupported: true, 150 }, 151 }, 152 153 // Yarn test cases 154 { 155 { 156 // This test case directs to non-existing directory. It only checks if the dependency update is blocked if the vulnerable dependency is not a direct dependency 157 vulnDetails: &utils.VulnerabilityDetails{ 158 SuggestedFixedVersion: "1.2.6", 159 IsDirectDependency: false, 160 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Yarn, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "minimist"}}, 161 }, 162 fixSupported: false, 163 }, 164 { 165 vulnDetails: &utils.VulnerabilityDetails{ 166 SuggestedFixedVersion: "1.2.6", 167 IsDirectDependency: true, 168 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Yarn, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "minimist"}}, 169 }, 170 fixSupported: true, 171 specificTechVersion: "1", 172 }, 173 { 174 vulnDetails: &utils.VulnerabilityDetails{ 175 SuggestedFixedVersion: "1.2.6", 176 IsDirectDependency: true, 177 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Yarn, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "minimist"}}, 178 }, 179 fixSupported: true, 180 specificTechVersion: "2", 181 }, 182 }, 183 184 // Maven test cases 185 { 186 { 187 vulnDetails: &utils.VulnerabilityDetails{ 188 SuggestedFixedVersion: "2.7", 189 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Maven, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "commons-io:commons-io"}}, 190 IsDirectDependency: true}, 191 scanDetails: &utils.ScanDetails{Project: &utils.Project{DepsRepo: ""}, ServerDetails: nil}, 192 fixSupported: true, 193 }, 194 { 195 vulnDetails: &utils.VulnerabilityDetails{ 196 SuggestedFixedVersion: "4.3.20", 197 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Maven, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "org.springframework:spring-core"}}, 198 IsDirectDependency: false}, 199 scanDetails: &utils.ScanDetails{Project: &utils.Project{DepsRepo: ""}, ServerDetails: nil}, 200 fixSupported: false, 201 }, 202 }, 203 204 // NuGet test cases 205 { 206 { 207 // This test case directs to non-existing directory. It only checks if the dependency update is blocked if the vulnerable dependency is not a direct dependency 208 vulnDetails: &utils.VulnerabilityDetails{ 209 SuggestedFixedVersion: "1.1.1", 210 IsDirectDependency: false, 211 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Nuget, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "snappier", ImpactedDependencyVersion: "1.1.0"}}, 212 }, 213 fixSupported: false, 214 testDirName: "dotnet", 215 }, 216 { 217 vulnDetails: &utils.VulnerabilityDetails{ 218 SuggestedFixedVersion: "1.1.1", 219 IsDirectDependency: true, 220 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Nuget, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "snappier", ImpactedDependencyVersion: "1.1.0"}}, 221 }, 222 fixSupported: true, 223 testDirName: "dotnet", 224 }, 225 }, 226 227 // Gradle test cases 228 { 229 { 230 vulnDetails: &utils.VulnerabilityDetails{ 231 SuggestedFixedVersion: "4.13.1", 232 IsDirectDependency: false, 233 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Gradle, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "commons-collections:commons-collections", ImpactedDependencyVersion: "3.2"}}, 234 }, 235 fixSupported: false, 236 }, 237 { // Unsupported fix: dynamic version 238 vulnDetails: &utils.VulnerabilityDetails{ 239 SuggestedFixedVersion: "3.2.2", 240 IsDirectDependency: true, 241 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Gradle, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "commons-collections:commons-collections", ImpactedDependencyVersion: "3.+"}}, 242 }, 243 fixSupported: false, 244 }, 245 { // Unsupported fix: latest version 246 vulnDetails: &utils.VulnerabilityDetails{ 247 SuggestedFixedVersion: "3.2.2", 248 IsDirectDependency: true, 249 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Gradle, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "commons-collections:commons-collections", ImpactedDependencyVersion: "latest.release"}}, 250 }, 251 fixSupported: false, 252 }, 253 { // Unsupported fix: range version 254 vulnDetails: &utils.VulnerabilityDetails{ 255 SuggestedFixedVersion: "3.2.2", 256 IsDirectDependency: true, 257 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Gradle, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "commons-collections:commons-collections", ImpactedDependencyVersion: "[3.0, 3.5.6)"}}, 258 }, 259 fixSupported: false, 260 }, 261 { 262 vulnDetails: &utils.VulnerabilityDetails{ 263 SuggestedFixedVersion: "4.13.1", 264 IsDirectDependency: true, 265 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Gradle, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "junit:junit", ImpactedDependencyVersion: "4.7"}}, 266 }, 267 fixSupported: true, 268 }, 269 }, 270 } 271 272 for _, testBatch := range testCases { 273 for _, test := range testBatch { 274 packageHandler := GetCompatiblePackageHandler(test.vulnDetails, test.scanDetails) 275 t.Run(fmt.Sprintf("%s:%s direct:%s", test.vulnDetails.Technology.String()+test.specificTechVersion, test.vulnDetails.ImpactedDependencyName, strconv.FormatBool(test.vulnDetails.IsDirectDependency)), 276 func(t *testing.T) { 277 testDataDir := getTestDataDir(t, test.vulnDetails.IsDirectDependency) 278 testDirName := test.vulnDetails.Technology.String() 279 if test.testDirName != "" { 280 testDirName = test.testDirName 281 } 282 cleanup := createTempDirAndChdir(t, testDataDir, testDirName+test.specificTechVersion) 283 defer cleanup() 284 err := packageHandler.UpdateDependency(test.vulnDetails) 285 if test.fixSupported { 286 assert.NoError(t, err) 287 uniquePackageManagerChecks(t, test) 288 } else { 289 assert.Error(t, err) 290 assert.IsType(t, &utils.ErrUnsupportedFix{}, err, "Expected unsupported fix error") 291 } 292 }) 293 } 294 } 295 296 } 297 298 func TestPipPackageRegex(t *testing.T) { 299 var pipPackagesRegexTests = []pipPackageRegexTest{ 300 {"oslo.config", "oslo.config>=1.12.1,<1.13"}, 301 {"oslo.utils", "oslo.utils<5.0,>=4.0.0"}, 302 {"paramiko", "paramiko==2.7.2"}, 303 {"passlib", "passlib<=1.7.4"}, 304 {"PassLib", "passlib<=1.7.4"}, 305 {"prance", "prance>=0.9.0"}, 306 {"prompt-toolkit", "prompt-toolkit~=1.0.15"}, 307 {"pyinotify", "pyinotify>0.9.6"}, 308 {"pyjwt", "pyjwt>1.7.1"}, 309 {"PyJWT", "pyjwt>1.7.1"}, 310 {"urllib3", "urllib3 > 1.1.9, < 1.5.*"}, 311 } 312 for _, pack := range pipPackagesRegexTests { 313 re := regexp.MustCompile(PythonPackageRegexPrefix + "(" + pack.packageName + "|" + strings.ToLower(pack.packageName) + ")" + PythonPackageRegexSuffix) 314 found := re.FindString(requirementsFile) 315 assert.Equal(t, pack.expectedRequirement, strings.ToLower(found)) 316 } 317 } 318 319 // Maven utils functions 320 func TestGetDependenciesFromPomXmlSingleDependency(t *testing.T) { 321 testCases := []string{`<dependency> 322 <groupId>org.apache.commons</groupId> 323 <artifactId>commons-email</artifactId> 324 <version>1.1</version> 325 <scope>compile</scope> 326 </dependency>`, 327 `<dependency> 328 <groupId> org.apache.commons</groupId> 329 <artifactId>commons-email </artifactId> 330 <version> 1.1 </version> 331 <scope> compile </scope> 332 </dependency>`, 333 } 334 335 for _, testCase := range testCases { 336 result, err := getMavenDependencies([]byte(testCase)) 337 assert.NoError(t, err) 338 339 assert.Len(t, result, 1) 340 assert.Equal(t, "org.apache.commons", result[0].GroupId) 341 assert.Equal(t, "commons-email", result[0].ArtifactId) 342 assert.Equal(t, "1.1", result[0].Version) 343 } 344 } 345 346 func TestGetDependenciesFromPomXmlMultiDependency(t *testing.T) { 347 testCases := []string{` 348 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 349 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd"> 350 <dependencies> 351 <dependency> 352 <groupId>org.apache.commons</groupId> 353 <artifactId>commons-email</artifactId> 354 <version>1.1</version> 355 <scope>compile</scope> 356 </dependency> 357 <dependency> 358 <groupId>org.codehaus.plexus</groupId> 359 <artifactId>plexus-utils</artifactId> 360 <version>1.5.1</version> 361 </dependency> 362 </dependencies> 363 </project>`, 364 } 365 366 for _, testCase := range testCases { 367 result, err := getMavenDependencies([]byte(testCase)) 368 assert.NoError(t, err) 369 370 assert.Len(t, result, 2) 371 assert.Equal(t, "org.apache.commons", result[0].GroupId) 372 assert.Equal(t, "commons-email", result[0].ArtifactId) 373 assert.Equal(t, "1.1", result[0].Version) 374 375 assert.Equal(t, "org.codehaus.plexus", result[1].GroupId) 376 assert.Equal(t, "plexus-utils", result[1].ArtifactId) 377 assert.Equal(t, "1.5.1", result[1].Version) 378 } 379 } 380 381 func TestGetPluginsFromPomXml(t *testing.T) { 382 testCase := 383 `<project> 384 <build> 385 <plugins> 386 <plugin> 387 <groupId>org.apache.maven.plugins</groupId> 388 <artifactId>maven-source-plugin</artifactId> 389 </plugin> 390 <plugin> 391 <groupId>com.github.spotbugs</groupId> 392 <artifactId>spotbugs-maven-plugin</artifactId> 393 <version>4.5.3.0</version> 394 <configuration> 395 <excludeFilterFile>spotbugs-security-exclude.xml</excludeFilterFile> 396 <plugins> 397 <plugin> 398 <groupId>com.h3xstream.findsecbugs</groupId> 399 <artifactId>findsecbugs-plugin</artifactId> 400 <version>1.12.0</version> 401 </plugin> 402 </plugins> 403 </configuration> 404 </plugin> 405 <plugin> 406 <groupId>org.apache.maven.plugins</groupId> 407 <artifactId>maven-surefire-plugin</artifactId> 408 <version>2.22.1</version> 409 <configuration> 410 <systemPropertyVariables> 411 <!--This will disable JenkinsRule timeout--> 412 <maven.surefire.debug>true</maven.surefire.debug> 413 </systemPropertyVariables> 414 <excludes> 415 <exclude>**/InjectedTest.java</exclude> 416 <exclude>**/*ITest.java</exclude> 417 </excludes> 418 </configuration> 419 </plugin> 420 </plugins> 421 </build> 422 </project> 423 ` 424 plugins, err := getMavenDependencies([]byte(testCase)) 425 assert.NoError(t, err) 426 assert.Equal(t, "org.apache.maven.plugins", plugins[0].GroupId) 427 assert.Equal(t, "maven-source-plugin", plugins[0].ArtifactId) 428 assert.Equal(t, "com.github.spotbugs", plugins[1].GroupId) 429 assert.Equal(t, "spotbugs-maven-plugin", plugins[1].ArtifactId) 430 assert.Equal(t, "4.5.3.0", plugins[1].Version) 431 assert.Equal(t, "com.h3xstream.findsecbugs", plugins[2].GroupId) 432 assert.Equal(t, "findsecbugs-plugin", plugins[2].ArtifactId) 433 assert.Equal(t, "1.12.0", plugins[2].Version) 434 assert.Equal(t, "org.apache.maven.plugins", plugins[3].GroupId) 435 assert.Equal(t, "maven-surefire-plugin", plugins[3].ArtifactId) 436 assert.Equal(t, "2.22.1", plugins[3].Version) 437 } 438 439 func TestGetDependenciesFromDependencyManagement(t *testing.T) { 440 testCase := ` 441 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 442 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd"> 443 <dependencyManagement> 444 <dependencies> 445 <dependency> 446 <groupId>io.jenkins.tools.bom</groupId> 447 <artifactId>bom-2.346.x</artifactId> 448 <version>1607.va_c1576527071</version> 449 <scope>import</scope> 450 <type>pom</type> 451 </dependency> 452 <dependency> 453 <groupId>com.fasterxml.jackson.core</groupId> 454 <artifactId>jackson-core</artifactId> 455 <version>2.13.4</version> 456 </dependency> 457 <dependency> 458 <groupId>com.fasterxml.jackson.core</groupId> 459 <artifactId>jackson-databind</artifactId> 460 <version>2.13.4.2</version> 461 </dependency> 462 <dependency> 463 <groupId>com.fasterxml.jackson.core</groupId> 464 <artifactId>jackson-annotations</artifactId> 465 <version>2.13.4</version> 466 </dependency> 467 <dependency> 468 <groupId>org.apache.httpcomponents</groupId> 469 <artifactId>httpcore</artifactId> 470 <version>4.4.15</version> 471 </dependency> 472 <dependency> 473 <groupId>org.jenkins-ci.plugins.workflow</groupId> 474 <artifactId>workflow-durable-task-step</artifactId> 475 <version>1190.vc93d7d457042</version> 476 <scope>test</scope> 477 </dependency> 478 </dependencies> 479 </dependencyManagement> 480 </project> 481 ` 482 dependencies, err := getMavenDependencies([]byte(testCase)) 483 assert.NoError(t, err) 484 assert.Len(t, dependencies, 6) 485 for _, dependency := range dependencies { 486 assert.True(t, dependency.foundInDependencyManagement) 487 } 488 } 489 490 func TestGetProjectPoms(t *testing.T) { 491 mvnHandler := &MavenPackageHandler{MavenDepTreeManager: java.NewMavenDepTreeManager(&java.DepTreeParams{}, java.Projects, false)} 492 currDir, err := os.Getwd() 493 assert.NoError(t, err) 494 tmpDir, err := os.MkdirTemp("", "") 495 assert.NoError(t, err) 496 assert.NoError(t, biutils.CopyDir(filepath.Join("..", "testdata", "projects", "maven"), tmpDir, true, nil)) 497 assert.NoError(t, os.Chdir(tmpDir)) 498 defer func() { 499 assert.NoError(t, os.Chdir(currDir)) 500 }() 501 assert.NoError(t, mvnHandler.getProjectPoms()) 502 assert.Len(t, mvnHandler.pomPaths, 2) 503 } 504 505 // General Utils functions 506 func TestFixVersionInfo_UpdateFixVersionIfMax(t *testing.T) { 507 type testCase struct { 508 fixVersionInfo utils.VulnerabilityDetails 509 newFixVersion string 510 expectedOutput string 511 } 512 513 testCases := []testCase{ 514 {fixVersionInfo: utils.VulnerabilityDetails{SuggestedFixedVersion: "1.2.3", IsDirectDependency: true}, newFixVersion: "1.2.4", expectedOutput: "1.2.4"}, 515 {fixVersionInfo: utils.VulnerabilityDetails{SuggestedFixedVersion: "1.2.3", IsDirectDependency: true}, newFixVersion: "1.0.4", expectedOutput: "1.2.3"}, 516 } 517 518 for _, tc := range testCases { 519 t.Run(tc.expectedOutput, func(t *testing.T) { 520 tc.fixVersionInfo.UpdateFixVersionIfMax(tc.newFixVersion) 521 assert.Equal(t, tc.expectedOutput, tc.fixVersionInfo.SuggestedFixedVersion) 522 }) 523 } 524 } 525 526 func TestUpdatePackageVersion(t *testing.T) { 527 testProjectPath := filepath.Join("..", "testdata", "packagehandlers") 528 currDir, err := os.Getwd() 529 assert.NoError(t, err) 530 tmpDir, err := os.MkdirTemp("", "") 531 assert.NoError(t, err) 532 assert.NoError(t, biutils.CopyDir(testProjectPath, tmpDir, true, nil)) 533 assert.NoError(t, os.Chdir(tmpDir)) 534 defer func() { 535 assert.NoError(t, os.Chdir(currDir)) 536 }() 537 testCases := []struct { 538 impactedPackage string 539 fixedVersion string 540 foundInDependencyManagement bool 541 }{ 542 {impactedPackage: "org.jfrog.filespecs:file-specs-java", fixedVersion: "1.1.2"}, 543 {impactedPackage: "com.fasterxml.jackson.core:jackson-core", fixedVersion: "2.15.0", foundInDependencyManagement: true}, 544 {impactedPackage: "org.apache.httpcomponents:httpcore", fixedVersion: "4.4.16", foundInDependencyManagement: true}, 545 } 546 mvnHandler := &MavenPackageHandler{MavenDepTreeManager: &java.MavenDepTreeManager{}} 547 for _, test := range testCases { 548 assert.NoError(t, mvnHandler.updatePackageVersion(test.impactedPackage, test.fixedVersion, test.foundInDependencyManagement)) 549 } 550 modifiedPom, err := os.ReadFile("pom.xml") 551 assert.NoError(t, err) 552 for _, test := range testCases { 553 assert.Contains(t, fmt.Sprintf("<version>%s</version>", string(modifiedPom)), test.fixedVersion) 554 } 555 556 // Test non-existing version error 557 assert.ErrorContains(t, 558 mvnHandler.updatePackageVersion("org.apache.httpcomponents:httpcore", "non.existing.version", true), 559 fmt.Sprintf(MavenVersionNotAvailableErrorFormat, "non.existing.version")) 560 } 561 562 func TestUpdatePropertiesVersion(t *testing.T) { 563 testProjectPath := filepath.Join("..", "testdata", "packagehandlers") 564 currDir, err := os.Getwd() 565 assert.NoError(t, err) 566 tmpDir, err := os.MkdirTemp("", "") 567 assert.NoError(t, err) 568 assert.NoError(t, biutils.CopyDir(testProjectPath, tmpDir, true, nil)) 569 assert.NoError(t, os.Chdir(tmpDir)) 570 defer func() { 571 assert.NoError(t, os.Chdir(currDir)) 572 }() 573 mvnHandler := &MavenPackageHandler{MavenDepTreeManager: &java.MavenDepTreeManager{}} 574 assert.NoError(t, mvnHandler.updateProperties(&pomDependencyDetails{properties: []string{"buildinfo.version"}}, "2.39.9")) 575 modifiedPom, err := os.ReadFile("pom.xml") 576 assert.NoError(t, err) 577 assert.Contains(t, string(modifiedPom), "2.39.9") 578 } 579 580 func getTestDataDir(t *testing.T, directDependency bool) string { 581 var projectDir string 582 if directDependency { 583 projectDir = "projects" 584 } else { 585 projectDir = "indirect-projects" 586 } 587 testdataDir, err := filepath.Abs(filepath.Join("..", "testdata", projectDir)) 588 assert.NoError(t, err) 589 return testdataDir 590 } 591 592 func createTempDirAndChdir(t *testing.T, testdataDir string, tech string) func() { 593 // Create temp technology project 594 projectPath := filepath.Join(testdataDir, tech) 595 tmpProjectPath, cleanup := tests.CreateTestProject(t, projectPath) 596 currDir, err := os.Getwd() 597 assert.NoError(t, err) 598 assert.NoError(t, os.Chdir(tmpProjectPath)) 599 if tech == "go" { 600 err = removeTxtSuffix("go.mod.txt") 601 assert.NoError(t, err) 602 err = removeTxtSuffix("go.sum.txt") 603 assert.NoError(t, err) 604 err = removeTxtSuffix("main.go.txt") 605 assert.NoError(t, err) 606 } 607 return func() { 608 cleanup() 609 assert.NoError(t, os.Chdir(currDir)) 610 } 611 } 612 613 func removeTxtSuffix(txtFileName string) error { 614 // go.sum.txt >> go.sum 615 return fileutils.MoveFile(txtFileName, strings.TrimSuffix(txtFileName, ".txt")) 616 } 617 618 func assertFixVersionInPackageDescriptor(t *testing.T, test dependencyFixTest, packageDescriptor string) { 619 file, err := os.ReadFile(packageDescriptor) 620 assert.NoError(t, err) 621 622 assert.Contains(t, string(file), test.vulnDetails.SuggestedFixedVersion) 623 // Verify that case-sensitive packages in python are lowered 624 assert.Contains(t, string(file), strings.ToLower(test.vulnDetails.ImpactedDependencyName)) 625 626 } 627 628 // This function is intended to add unique checks for specific package managers 629 func uniquePackageManagerChecks(t *testing.T, test dependencyFixTest) { 630 technology := test.vulnDetails.Technology 631 extraArgs := test.uniqueChecksExtraArgs 632 switch technology { 633 case coreutils.Go: 634 packageDescriptor := extraArgs[0] 635 assertFixVersionInPackageDescriptor(t, test, packageDescriptor) 636 case coreutils.Gradle: 637 descriptorFilesPaths, err := getDescriptorFilesPaths() 638 assert.NoError(t, err) 639 assert.Equal(t, len(descriptorFilesPaths), 2, "incorrect number of descriptor files found") 640 for _, packageDescriptor := range descriptorFilesPaths { 641 assertFixVersionInPackageDescriptor(t, test, packageDescriptor) 642 } 643 default: 644 } 645 } 646 647 func TestNugetFixVulnerabilityIfExists(t *testing.T) { 648 var testcases = []struct { 649 vulnerabilityDetails *utils.VulnerabilityDetails 650 }{ 651 // Basic check 652 { 653 vulnerabilityDetails: &utils.VulnerabilityDetails{ 654 SuggestedFixedVersion: "1.1.1", 655 IsDirectDependency: true, 656 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Nuget, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "snappier", ImpactedDependencyVersion: "1.1.0"}}}, 657 }, 658 // This testcase checks a fix with a vulnerability that has '.' in the dependency's group and name + more complex version, including letters, to check that the regexp captures them correctly 659 { 660 vulnerabilityDetails: &utils.VulnerabilityDetails{ 661 SuggestedFixedVersion: "7.0.11", 662 IsDirectDependency: true, 663 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Nuget, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "Microsoft.Bcl.AsyncInterfaces", ImpactedDependencyVersion: "8.0.0-rc.1.23419.4"}}}, 664 }, 665 } 666 testRootDir, err := os.Getwd() 667 assert.NoError(t, err) 668 669 tmpDir, err := os.MkdirTemp("", "") 670 assert.NoError(t, err) 671 assert.NoError(t, biutils.CopyDir(filepath.Join("..", "testdata", "projects", "dotnet"), tmpDir, true, nil)) 672 assert.NoError(t, os.Chdir(tmpDir)) 673 defer func() { 674 assert.NoError(t, os.Chdir(testRootDir)) 675 }() 676 677 assetFiles, err := getAssetsFilesPaths() 678 assert.NoError(t, err) 679 testedAssetFile := assetFiles[0] 680 681 nph := &NugetPackageHandler{} 682 683 for _, testcase := range testcases { 684 vulnRegexpCompiler := getVulnerabilityRegexCompiler(testcase.vulnerabilityDetails.ImpactedDependencyName, testcase.vulnerabilityDetails.ImpactedDependencyVersion) 685 var isFileChanged bool 686 isFileChanged, err = nph.fixVulnerabilityIfExists(testcase.vulnerabilityDetails, testedAssetFile, vulnRegexpCompiler, tmpDir) 687 assert.NoError(t, err) 688 assert.True(t, isFileChanged) 689 } 690 691 var fixedFileContent []byte 692 fixedFileContent, err = os.ReadFile(testedAssetFile) 693 fixedFileContentString := string(fixedFileContent) 694 695 assert.NoError(t, err) 696 assert.NotContains(t, fixedFileContentString, "<PackageReference Include=\"snappier\" Version=\"1.1.0\" />") 697 assert.Contains(t, fixedFileContentString, "<PackageReference Include=\"snappier\" Version=\"1.1.1\" />") 698 assert.NotContains(t, fixedFileContentString, "<PackageReference Include=\"Microsoft.Bcl.AsyncInterfaces\" Version=\"8.0.0-rc.1.23419.4\" />") 699 assert.Contains(t, fixedFileContentString, "<PackageReference Include=\"Microsoft.Bcl.AsyncInterfaces\" Version=\"7.0.11\" />") 700 } 701 702 func TestGetFixedPackage(t *testing.T) { 703 var testcases = []struct { 704 impactedPackage string 705 versionOperator string 706 suggestedFixedVersion string 707 expectedOutput []string 708 }{ 709 { 710 impactedPackage: "snappier", 711 versionOperator: " -v ", 712 suggestedFixedVersion: "1.1.1", 713 expectedOutput: []string{"snappier", "-v", "1.1.1"}, 714 }, 715 { 716 impactedPackage: "json", 717 versionOperator: "@", 718 suggestedFixedVersion: "10.0.0", 719 expectedOutput: []string{"json@10.0.0"}, 720 }, 721 } 722 723 for _, test := range testcases { 724 fixedPackageArgs := getFixedPackage(test.impactedPackage, test.versionOperator, test.suggestedFixedVersion) 725 assert.Equal(t, test.expectedOutput, fixedPackageArgs) 726 } 727 } 728 729 func TestGradleGetDescriptorFilesPaths(t *testing.T) { 730 currDir, err := os.Getwd() 731 assert.NoError(t, err) 732 tmpDir, err := os.MkdirTemp("", "") 733 assert.NoError(t, err) 734 assert.NoError(t, biutils.CopyDir(filepath.Join("..", "testdata", "projects", "gradle"), tmpDir, true, nil)) 735 assert.NoError(t, os.Chdir(tmpDir)) 736 defer func() { 737 assert.NoError(t, os.Chdir(currDir)) 738 }() 739 finalPath, err := os.Getwd() 740 assert.NoError(t, err) 741 742 expectedResults := []string{filepath.Join(finalPath, groovyDescriptorFileSuffix), filepath.Join(finalPath, "innerProjectForTest", kotlinDescriptorFileSuffix)} 743 744 buildFilesPaths, err := getDescriptorFilesPaths() 745 assert.NoError(t, err) 746 assert.ElementsMatch(t, expectedResults, buildFilesPaths) 747 } 748 749 func TestGradleFixVulnerabilityIfExists(t *testing.T) { 750 var testcases = []struct { 751 vulnerabilityDetails *utils.VulnerabilityDetails 752 }{ 753 // Basic check 754 { 755 vulnerabilityDetails: &utils.VulnerabilityDetails{ 756 SuggestedFixedVersion: "4.13.1", 757 IsDirectDependency: true, 758 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Gradle, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "junit:junit", ImpactedDependencyVersion: "4.7"}}}, 759 }, 760 // This testcase checks a fix with a vulnerability that has '.' in the dependency's group and name + more complex version, including letters, to check that the regexp captures them correctly 761 { 762 vulnerabilityDetails: &utils.VulnerabilityDetails{ 763 SuggestedFixedVersion: "1.9.9", 764 IsDirectDependency: true, 765 VulnerabilityOrViolationRow: formats.VulnerabilityOrViolationRow{Technology: coreutils.Gradle, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "my.group:my.dot.name", ImpactedDependencyVersion: "1.0.0-beta.test"}}}, 766 }, 767 } 768 769 currDir, err := os.Getwd() 770 assert.NoError(t, err) 771 772 tmpDir, err := os.MkdirTemp("", "") 773 assert.NoError(t, err) 774 assert.NoError(t, biutils.CopyDir(filepath.Join("..", "testdata", "projects", "gradle"), tmpDir, true, nil)) 775 assert.NoError(t, os.Chdir(tmpDir)) 776 defer func() { 777 assert.NoError(t, os.Chdir(currDir)) 778 }() 779 780 descriptorFiles, err := getDescriptorFilesPaths() 781 assert.NoError(t, err) 782 783 gph := GradlePackageHandler{} 784 785 for _, descriptorFile := range descriptorFiles { 786 for _, testcase := range testcases { 787 var isFileChanged bool 788 isFileChanged, err = gph.fixVulnerabilityIfExists(descriptorFile, testcase.vulnerabilityDetails) 789 assert.NoError(t, err) 790 assert.True(t, isFileChanged) 791 } 792 compareFixedFileToComparisonFile(t, descriptorFile) 793 } 794 795 } 796 797 func compareFixedFileToComparisonFile(t *testing.T, descriptorFileAbsPath string) { 798 var compareFilePath string 799 if strings.HasSuffix(descriptorFileAbsPath, groovyDescriptorFileSuffix) { 800 curDirPath := strings.TrimSuffix(descriptorFileAbsPath, groovyDescriptorFileSuffix) 801 compareFilePath = filepath.Join(curDirPath, "fixedBuildGradleForCompare.txt") 802 } else { 803 curDirPath := strings.TrimSuffix(descriptorFileAbsPath, kotlinDescriptorFileSuffix) 804 compareFilePath = filepath.Join(curDirPath, "fixedBuildGradleKtsForCompare.txt") 805 } 806 807 expectedFileContent, err := os.ReadFile(descriptorFileAbsPath) 808 assert.NoError(t, err) 809 810 fixedFileContent, err := os.ReadFile(compareFilePath) 811 assert.NoError(t, err) 812 813 assert.ElementsMatch(t, expectedFileContent, fixedFileContent) 814 } 815 816 func TestGradleIsVersionSupportedForFix(t *testing.T) { 817 var testcases = []struct { 818 impactedVersion string 819 expectedResult bool 820 }{ 821 { 822 impactedVersion: "10.+", 823 expectedResult: false, 824 }, 825 { 826 impactedVersion: "[10.3, 11.0)", 827 expectedResult: false, 828 }, 829 { 830 impactedVersion: "(10.4.2, 11.7.8)", 831 expectedResult: false, 832 }, 833 { 834 impactedVersion: "latest.release", 835 expectedResult: false, 836 }, 837 { 838 impactedVersion: "5.5", 839 expectedResult: true, 840 }, 841 { 842 impactedVersion: "9.0.13-beta", 843 expectedResult: true, 844 }, 845 } 846 847 for _, testcase := range testcases { 848 assert.Equal(t, testcase.expectedResult, isVersionSupportedForFix(testcase.impactedVersion)) 849 } 850 }