Skip to content

Commit

Permalink
feat: PHP Composer Analyzer Scans packages-dev by default (#7114)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremylong authored Oct 30, 2024
1 parent 910570d commit 08d7657
Show file tree
Hide file tree
Showing 13 changed files with 106 additions and 32 deletions.
23 changes: 23 additions & 0 deletions ant/src/main/java/org/owasp/dependencycheck/taskdefs/Check.java
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,10 @@ public class Check extends Update {
* Whether or not the PHP Composer Analyzer is enabled.
*/
private Boolean composerAnalyzerEnabled;
/**
* Whether or not the PHP Composer Analyzer will skip "packages-dev".
*/
private Boolean composerAnalyzerSkipDev;
/**
* Whether or not the Perl CPAN File Analyzer is enabled.
*/
Expand Down Expand Up @@ -943,6 +947,24 @@ public Boolean isComposerAnalyzerEnabled() {
public void setComposerAnalyzerEnabled(Boolean composerAnalyzerEnabled) {
this.composerAnalyzerEnabled = composerAnalyzerEnabled;
}

/**
* Get the value of composerAnalyzerSkipDev.
*
* @return the value of composerAnalyzerSkipDev
*/
public Boolean isComposerAnalyzerSkipDev() {
return composerAnalyzerSkipDev;
}

/**
* Set the value of composerAnalyzerSkipDev.
*
* @param composerAnalyzerSkipDev new value of composerAnalyzerSkipDev
*/
public void setComposerAnalyzerSkipDev(Boolean composerAnalyzerSkipDev) {
this.composerAnalyzerSkipDev = composerAnalyzerSkipDev;
}

/**
* Get the value of cpanfileAnalyzerEnabled.
Expand Down Expand Up @@ -2183,6 +2205,7 @@ protected void populateSettings() throws BuildException {
getSettings().setBooleanIfNotNull(Settings.KEYS.ANALYZER_PIPFILE_ENABLED, pipfileAnalyzerEnabled);
getSettings().setBooleanIfNotNull(Settings.KEYS.ANALYZER_POETRY_ENABLED, poetryAnalyzerEnabled);
getSettings().setBooleanIfNotNull(Settings.KEYS.ANALYZER_COMPOSER_LOCK_ENABLED, composerAnalyzerEnabled);
getSettings().setBooleanIfNotNull(Settings.KEYS.ANALYZER_COMPOSER_LOCK_SKIP_DEV, composerAnalyzerSkipDev);
getSettings().setBooleanIfNotNull(Settings.KEYS.ANALYZER_CPANFILE_ENABLED, cpanfileAnalyzerEnabled);
getSettings().setBooleanIfNotNull(Settings.KEYS.ANALYZER_NODE_PACKAGE_ENABLED, nodeAnalyzerEnabled);
getSettings().setBooleanIfNotNull(Settings.KEYS.ANALYZER_NODE_PACKAGE_SKIPDEV, nodePackageSkipDevDependencies);
Expand Down
1 change: 1 addition & 0 deletions ant/src/site/markdown/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ pipAnalyzerEnabled | Sets whether the [experimental](../analyze
pipfileAnalyzerEnabled | Sets whether the [experimental](../analyzers/index.html) Pipfile Analyzer should be used. `enableExperimental` must be set to true. | true
poetryAnalyzerEnabled | Sets whether the [experimental](../analyzers/index.html) Poetry Analyzer should be used. `enableExperimental` must be set to true. | true
composerAnalyzerEnabled | Sets whether the [experimental](../analyzers/index.html) PHP Composer Lock File Analyzer should be used. `enableExperimental` must be set to true. | true
composerAnalyzerSkipDev | Sets whether the [experimental](../analyzers/index.html) PHP Composer Lock File Analyzer should skip "packages-dev" | false
cpanfileAnalyzerEnabled | Sets whether the [experimental](../analyzers/index.html) Perl CPAN File Analyzer should be used. `enableExperimental` must be set to true. | true
nodeAnalyzerEnabled | Sets whether the [retired](../analyzers/index.html) Node.js Analyzer should be used. | true
nodeAuditAnalyzerEnabled | Sets whether the Node Audit Analyzer should be used. This analyzer requires an internet connection. | true
Expand Down
2 changes: 2 additions & 0 deletions cli/src/main/java/org/owasp/dependencycheck/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,8 @@ protected void populateSettings(CliParser cli) throws InvalidSettingException {
!cli.isDisabled(CliParser.ARGUMENT.DISABLE_OPENSSL, Settings.KEYS.ANALYZER_OPENSSL_ENABLED));
settings.setBoolean(Settings.KEYS.ANALYZER_COMPOSER_LOCK_ENABLED,
!cli.isDisabled(CliParser.ARGUMENT.DISABLE_COMPOSER, Settings.KEYS.ANALYZER_COMPOSER_LOCK_ENABLED));
settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_COMPOSER_LOCK_SKIP_DEV,
cli.hasOption(CliParser.ARGUMENT.COMPOSER_LOCK_SKIP_DEV));
settings.setBoolean(Settings.KEYS.ANALYZER_CPANFILE_ENABLED,
!cli.isDisabled(CliParser.ARGUMENT.DISABLE_CPAN, Settings.KEYS.ANALYZER_CPANFILE_ENABLED));
settings.setBoolean(Settings.KEYS.ANALYZER_GOLANG_DEP_ENABLED,
Expand Down
5 changes: 5 additions & 0 deletions cli/src/main/java/org/owasp/dependencycheck/CliParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,7 @@ private void addAdvancedOptions(final Options options) {
.addOption(newOption(ARGUMENT.DISABLE_PIP, "Disable the pip Analyzer."))
.addOption(newOption(ARGUMENT.DISABLE_PIPFILE, "Disable the Pipfile Analyzer."))
.addOption(newOption(ARGUMENT.DISABLE_COMPOSER, "Disable the PHP Composer Analyzer."))
.addOption(newOption(ARGUMENT.COMPOSER_LOCK_SKIP_DEV, "Configures the PHP Composer Analyzer to skip packages-dev"))
.addOption(newOption(ARGUMENT.DISABLE_CPAN, "Disable the Perl CPAN file Analyzer."))
.addOption(newOption(ARGUMENT.DISABLE_POETRY, "Disable the Poetry Analyzer."))
.addOption(newOption(ARGUMENT.DISABLE_GOLANG_MOD, "Disable the Golang Mod Analyzer."))
Expand Down Expand Up @@ -1249,6 +1250,10 @@ public static class ARGUMENT {
* Disables the PHP Composer Analyzer.
*/
public static final String DISABLE_COMPOSER = "disableComposer";
/**
* Whether the PHP Composer Analyzer skips dev packages.
*/
public static final String COMPOSER_LOCK_SKIP_DEV = "composerSkipDev";
/**
* Disables the Perl CPAN File Analyzer.
*/
Expand Down
1 change: 1 addition & 0 deletions cli/src/site/markdown/arguments.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ Advanced Options
| | \-\-zipExtensions | \<strings\> | A comma-separated list of additional file extensions to be treated like a ZIP file, the contents will be extracted and analyzed. | &nbsp; |
| | \-\-disableJar | | Sets whether the Jar Analyzer will be disabled. | &nbsp; |
| | \-\-disableComposer | | Sets whether the [experimental](../analyzers/index.html) PHP Composer Lock File Analyzer will be disabled. | &nbsp; |
| | \-\-composerSkipDev | | Sets whether the [experimental](../analyzers/index.html) PHP Composer Lock File Analyzer should skip "packages-dev". | &nbsp; |
| | \-\-disableCpan | | Sets whether the [experimental](../analyzers/index.html) Perl CPAN File Analyzer will be disabled. | &nbsp; |
| | \-\-disableDart | | Sets whether the [experimental](../analyzers/index.html) Dart Analyzer will be disabled. | &nbsp; |
| | \-\-disableOssIndex | | Sets whether the [OSS Index Analyzer](../analyzers/oss-index-analyzer.html) will be disabled. This analyzer requires an internet connection. | &nbsp; |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ protected void prepareFileTypeAnalyzer(Engine engine) throws InitializationExcep
protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
engine.removeDependency(dependency);
try (FileInputStream fis = new FileInputStream(dependency.getActualFile())) {
final ComposerLockParser clp = new ComposerLockParser(fis);
final boolean skipdev = getSettings().getBoolean(Settings.KEYS.ANALYZER_COMPOSER_LOCK_SKIP_DEV, false);
final ComposerLockParser clp = new ComposerLockParser(fis, skipdev);
LOGGER.debug("Checking composer.lock file {}", dependency.getActualFilePath());
clp.process();
clp.getDependencies().stream().map((dep) -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
import javax.annotation.concurrent.NotThreadSafe;

/**
* Parses a Composer.lock file from an input stream. In a separate class so it can hopefully be injected.
* Parses a Composer.lock file from an input stream. In a separate class so it
* can hopefully be injected.
*
* @author colezlaw
*/
Expand All @@ -43,12 +44,14 @@ public class ComposerLockParser {
* The JsonReader for parsing JSON
*/
private final JsonReader jsonReader;

/**
* The List of ComposerDependencies found
*/
private final List<ComposerDependency> composerDependencies;

/**
* Whether to skip dev dependencies.
*/
private final boolean skipDev;
/**
* The LOGGER
*/
Expand All @@ -59,10 +62,11 @@ public class ComposerLockParser {
*
* @param inputStream the InputStream to parse
*/
public ComposerLockParser(InputStream inputStream) {
public ComposerLockParser(InputStream inputStream, boolean skipDev) {
LOGGER.debug("Creating a ComposerLockParser");
this.jsonReader = Json.createReader(inputStream);
this.composerDependencies = new ArrayList<>();
this.skipDev = skipDev;
}

/**
Expand All @@ -76,26 +80,14 @@ public void process() {
LOGGER.debug("Found packages");
final JsonArray packages = composer.getJsonArray("packages");
for (JsonObject pkg : packages.getValuesAs(JsonObject.class)) {
if (pkg.containsKey("name")) {
final String groupName = pkg.getString("name");
if (groupName.indexOf('/') >= 0 && groupName.indexOf('/') <= groupName.length() - 1) {
if (pkg.containsKey("version")) {
final String group = groupName.substring(0, groupName.indexOf('/'));
final String project = groupName.substring(groupName.indexOf('/') + 1);
String version = pkg.getString("version");
// Some version numbers begin with v - which doesn't end up matching CPE's
if (version.startsWith("v")) {
version = version.substring(1);
}
LOGGER.debug("Got package {}/{}/{}", group, project, version);
composerDependencies.add(new ComposerDependency(group, project, version));
} else {
LOGGER.debug("Group/package {} does not have a version", groupName);
}
} else {
LOGGER.debug("Got a dependency with no name");
}
}
processPackageEntry(pkg);
}
}
if (composer.containsKey("packages-dev") && !skipDev) {
LOGGER.debug("Found packages-dev");
final JsonArray devPackages = composer.getJsonArray("packages-dev");
for (JsonObject pkg : devPackages.getValuesAs(JsonObject.class)) {
processPackageEntry(pkg);
}
}
} catch (JsonParsingException jsonpe) {
Expand All @@ -109,6 +101,29 @@ public void process() {
}
}

protected void processPackageEntry(JsonObject pkg) {
if (pkg.containsKey("name")) {
final String groupName = pkg.getString("name");
if (groupName.indexOf('/') >= 0 && groupName.indexOf('/') <= groupName.length() - 1) {
if (pkg.containsKey("version")) {
final String group = groupName.substring(0, groupName.indexOf('/'));
final String project = groupName.substring(groupName.indexOf('/') + 1);
String version = pkg.getString("version");
// Some version numbers begin with v - which doesn't end up matching CPE's
if (version.startsWith("v")) {
version = version.substring(1);
}
LOGGER.debug("Got package {}/{}/{}", group, project, version);
composerDependencies.add(new ComposerDependency(group, project, version));
} else {
LOGGER.debug("Group/package {} does not have a version", groupName);
}
} else {
LOGGER.debug("Got a dependency with no name");
}
}
}

/**
* Gets the list of dependencies.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,30 +43,42 @@ public void setUp() throws Exception {

@Test
public void testValidComposerLock() {
ComposerLockParser clp = new ComposerLockParser(inputStream);
ComposerLockParser clp = new ComposerLockParser(inputStream, false);
clp.process();
assertEquals(30, clp.getDependencies().size());
assertTrue(clp.getDependencies().contains(new ComposerDependency("symfony", "translation", "2.7.3")));
assertTrue(clp.getDependencies().contains(new ComposerDependency("vlucas", "phpdotenv", "1.1.1")));
}


@Test
public void testComposerLockSkipDev() {
ComposerLockParser clp = new ComposerLockParser(inputStream, true);
clp.process();
assertEquals(29, clp.getDependencies().size());
assertTrue(clp.getDependencies().contains(new ComposerDependency("symfony", "translation", "2.7.3")));
//vlucas/phpdotenv is in packages-dev
assertFalse(clp.getDependencies().contains(new ComposerDependency("vlucas", "phpdotenv", "1.1.1")));
}

@Test(expected = ComposerException.class)
public void testNotJSON() throws Exception {
String input = "NOT VALID JSON";
ComposerLockParser clp = new ComposerLockParser(new ByteArrayInputStream(input.getBytes(Charset.defaultCharset())));
ComposerLockParser clp = new ComposerLockParser(new ByteArrayInputStream(input.getBytes(Charset.defaultCharset())), false);
clp.process();
}

@Test(expected = ComposerException.class)
public void testNotComposer() throws Exception {
String input = "[\"ham\",\"eggs\"]";
ComposerLockParser clp = new ComposerLockParser(new ByteArrayInputStream(input.getBytes(Charset.defaultCharset())));
ComposerLockParser clp = new ComposerLockParser(new ByteArrayInputStream(input.getBytes(Charset.defaultCharset())), false);
clp.process();
}

@Test(expected = ComposerException.class)
public void testNotPackagesArray() throws Exception {
String input = "{\"packages\":\"eleventy\"}";
ComposerLockParser clp = new ComposerLockParser(new ByteArrayInputStream(input.getBytes(Charset.defaultCharset())));
ComposerLockParser clp = new ComposerLockParser(new ByteArrayInputStream(input.getBytes(Charset.defaultCharset())), false);
clp.process();
}
}
5 changes: 3 additions & 2 deletions core/src/test/resources/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,11 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma
*/
@Parameter(property = "composerAnalyzerEnabled")
private Boolean composerAnalyzerEnabled;
/**
* Sets whether or not the PHP Composer Lock File Analyzer will scan "packages-dev".
*/
@Parameter(property = "composerAnalyzerSkipDev")
private boolean composerAnalyzerSkipDev;
/**
* Whether or not the Perl CPAN File Analyzer is enabled.
*/
Expand Down Expand Up @@ -2282,6 +2287,7 @@ protected void populateSettings() {
settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_PIPFILE_ENABLED, pipfileAnalyzerEnabled);
settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_POETRY_ENABLED, poetryAnalyzerEnabled);
settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_COMPOSER_LOCK_ENABLED, composerAnalyzerEnabled);
settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_COMPOSER_LOCK_SKIP_DEV, composerAnalyzerSkipDev);
settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_CPANFILE_ENABLED, cpanfileAnalyzerEnabled);
settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_NODE_PACKAGE_ENABLED, nodeAnalyzerEnabled);
settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_NODE_AUDIT_ENABLED, nodeAuditAnalyzerEnabled);
Expand Down
3 changes: 2 additions & 1 deletion maven/src/site/markdown/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,9 @@ cmakeAnalyzerEnabled | Sets whether the [experimental](../analyze
autoconfAnalyzerEnabled | Sets whether the [experimental](../analyzers/index.html) autoconf Analyzer should be used. `enableExperimental` must be set to true. | true
pipAnalyzerEnabled | Sets whether the [experimental](../analyzers/index.html) pip Analyzer should be used. `enableExperimental` must be set to true. | true
pipfileAnalyzerEnabled | Sets whether the [experimental](../analyzers/index.html) Pipfile Analyzer should be used. `enableExperimental` must be set to true. | true
poetryAnalyzerEnabled | Sets whether the [experimental](../analyzers/index.html) Poetry Analyzer should be used. `enableExperimental` must be set to true. | true
poetryAnalyzerEnabled | Sets whether the [experimental](../analyzers/index.html) Poetry Analyzer should be used. `enableExperimental` must be set to true. | true
composerAnalyzerEnabled | Sets whether the [experimental](../analyzers/index.html) PHP Composer Lock File Analyzer should be used. `enableExperimental` must be set to true. | true
composerAnalyzerSkipDev | Sets whether the [experimental](../analyzers/index.html) PHP Composer Lock File Analyzer should skip "packages-dev" | false
cpanfileAnalyzerEnabled | Sets whether the [experimental](../analyzers/index.html) Perl CPAN File Analyzer should be used. `enableExperimental` must be set to true. | true
yarnAuditAnalyzerEnabled | Sets whether the Yarn Audit Analyzer should be used. This analyzer requires yarn and an internet connection. Use `nodeAuditSkipDevDependencies` to skip dev dependencies. | true
pnpmAuditAnalyzerEnabled | Sets whether the Pnpm Audit Analyzer should be used. This analyzer requires pnpm and an internet connection. Use `nodeAuditSkipDevDependencies` to skip dev dependencies. | true
Expand Down
Loading

0 comments on commit 08d7657

Please sign in to comment.