Skip to content

Commit

Permalink
feat: Add experimental Python Poetry support (#5025)
Browse files Browse the repository at this point in the history
* Add experimental Python Poetry support. Part of #4995

* Fix some copy paste errors in comments

Co-authored-by: Ferdinand Niedermann <nerdinand@users.noreply.github.com>
  • Loading branch information
nerdinand and nerdinand committed Nov 19, 2022
1 parent aef859b commit 35fb02f
Show file tree
Hide file tree
Showing 12 changed files with 2,366 additions and 0 deletions.
23 changes: 23 additions & 0 deletions ant/src/main/java/org/owasp/dependencycheck/taskdefs/Check.java
Expand Up @@ -315,6 +315,10 @@ public class Check extends Update {
* Whether the pipfile analyzer should be enabled.
*/
private Boolean pipfileAnalyzerEnabled;
/**
* Whether the Poetry analyzer should be enabled.
*/
private Boolean poetryAnalyzerEnabled;
/**
* Sets the path for the mix_audit binary.
*/
Expand Down Expand Up @@ -932,6 +936,24 @@ public void setPipfileAnalyzerEnabled(Boolean pipfileAnalyzerEnabled) {
this.pipfileAnalyzerEnabled = pipfileAnalyzerEnabled;
}

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

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

/**
* Returns if the Bundle Audit Analyzer is enabled.
*
Expand Down Expand Up @@ -2019,6 +2041,7 @@ protected void populateSettings() throws BuildException {
getSettings().setBooleanIfNotNull(Settings.KEYS.ANALYZER_MAVEN_INSTALL_ENABLED, mavenInstallAnalyzerEnabled);
getSettings().setBooleanIfNotNull(Settings.KEYS.ANALYZER_PIP_ENABLED, pipAnalyzerEnabled);
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_CPANFILE_ENABLED, cpanfileAnalyzerEnabled);
getSettings().setBooleanIfNotNull(Settings.KEYS.ANALYZER_NODE_PACKAGE_ENABLED, nodeAnalyzerEnabled);
Expand Down
1 change: 1 addition & 0 deletions ant/src/site/markdown/configuration.md
Expand Up @@ -102,6 +102,7 @@ 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
composerAnalyzerEnabled | Sets whether the [experimental](../analyzers/index.html) PHP Composer Lock File Analyzer should be used. `enableExperimental` must be set to true. | true
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
Expand Down
2 changes: 2 additions & 0 deletions cli/src/main/java/org/owasp/dependencycheck/App.java
Expand Up @@ -516,6 +516,8 @@ protected void populateSettings(CliParser cli) throws InvalidSettingException {
!cli.hasDisableOption(CliParser.ARGUMENT.DISABLE_PIP, Settings.KEYS.ANALYZER_PIP_ENABLED));
settings.setBoolean(Settings.KEYS.ANALYZER_PIPFILE_ENABLED,
!cli.hasDisableOption(CliParser.ARGUMENT.DISABLE_PIPFILE, Settings.KEYS.ANALYZER_PIPFILE_ENABLED));
settings.setBoolean(Settings.KEYS.ANALYZER_POETRY_ENABLED,
!cli.hasDisableOption(CliParser.ARGUMENT.DISABLE_POETRY, Settings.KEYS.ANALYZER_POETRY_ENABLED));
settings.setBoolean(Settings.KEYS.ANALYZER_CMAKE_ENABLED,
!cli.hasDisableOption(CliParser.ARGUMENT.DISABLE_CMAKE, Settings.KEYS.ANALYZER_CMAKE_ENABLED));
settings.setBoolean(Settings.KEYS.ANALYZER_NUSPEC_ENABLED,
Expand Down
4 changes: 4 additions & 0 deletions cli/src/main/java/org/owasp/dependencycheck/CliParser.java
Expand Up @@ -1243,6 +1243,10 @@ public static class ARGUMENT {
* Disables the Pipfile Analyzer.
*/
public static final String DISABLE_PIPFILE = "disablePipfile";
/**
* Disables the Poetry Analyzer.
*/
public static final String DISABLE_POETRY = "disablePoetry";
/**
* Disables the Cmake Analyzer.
*/
Expand Down
@@ -0,0 +1,188 @@
/*
* This file is part of dependency-check-core.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) 2019 Nima Yahyazadeh. All Rights Reserved.
*/
package org.owasp.dependencycheck.analyzer;

import com.github.packageurl.MalformedPackageURLException;
import com.github.packageurl.PackageURL;
import com.github.packageurl.PackageURLBuilder;
import java.io.FileFilter;
import java.util.List;

import javax.annotation.concurrent.ThreadSafe;

import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
import org.owasp.dependencycheck.dependency.Confidence;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.dependency.EvidenceType;
import org.owasp.dependencycheck.exception.InitializationException;
import org.owasp.dependencycheck.utils.FileFilterBuilder;
import org.owasp.dependencycheck.utils.Settings;

import com.moandjiezana.toml.Toml;
import org.apache.commons.lang3.StringUtils;
import org.owasp.dependencycheck.data.nvd.ecosystem.Ecosystem;
import org.owasp.dependencycheck.dependency.naming.GenericIdentifier;
import org.owasp.dependencycheck.dependency.naming.Identifier;
import org.owasp.dependencycheck.dependency.naming.PurlIdentifier;
import org.owasp.dependencycheck.utils.Checksum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Poetry dependency analyzer.
*
* @author Ferdinand Niedermann
* @author Jeremy Long
*/
@ThreadSafe
@Experimental
public class PoetryAnalyzer extends AbstractFileTypeAnalyzer {

/**
* The logger.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(PoetryAnalyzer.class);

/**
* A descriptor for the type of dependencies processed or added by this
* analyzer.
*/
public static final String DEPENDENCY_ECOSYSTEM = Ecosystem.PYTHON;

/**
* Lock file name.
*/
private static final String POETRY_LOCK = "poetry.lock";

/**
* The file filter for poetry.lock
*/
private static final FileFilter POETRY_LOCK_FILTER = FileFilterBuilder.newInstance()
.addFilenames(POETRY_LOCK)
.build();

/**
* Returns the name of the Poetry Analyzer.
*
* @return the name of the analyzer
*/
@Override
public String getName() {
return "Poetry Analyzer";
}

/**
* Tell that we are used for information collection.
*
* @return INFORMATION_COLLECTION
*/
@Override
public AnalysisPhase getAnalysisPhase() {
return AnalysisPhase.INFORMATION_COLLECTION;
}

/**
* Returns the key name for the analyzers enabled setting.
*
* @return the key name for the analyzers enabled setting
*/
@Override
protected String getAnalyzerEnabledSettingKey() {
return Settings.KEYS.ANALYZER_POETRY_ENABLED;
}

/**
* Returns the FileFilter
*
* @return the FileFilter
*/
@Override
protected FileFilter getFileFilter() {
return POETRY_LOCK_FILTER;
}

/**
* No-op initializer implementation.
*
* @param engine a reference to the dependency-check engine
*
* @throws InitializationException never thrown
*/
@Override
protected void prepareFileTypeAnalyzer(Engine engine) throws InitializationException {
// Nothing to do here.
}

/**
* Analyzes poetry packages and adds evidence to the dependency.
*
* @param dependency the dependency being analyzed
* @param engine the engine being used to perform the scan
*
* @throws AnalysisException thrown if there is an unrecoverable error
* analyzing the dependency
*/
@Override
protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
LOGGER.debug("Checking file {}", dependency.getActualFilePath());

//do not report on the build file itself
engine.removeDependency(dependency);

final Toml result = new Toml().read(dependency.getActualFile());
final List<Toml> projectsLocks = result.getTables("package");
if (projectsLocks == null) {
return;
}
projectsLocks.forEach((project) -> {
final String name = project.getString("name");
final String version = project.getString("version");

LOGGER.debug(String.format("package, version: %s %s", name, version));

final Dependency d = new Dependency(dependency.getActualFile(), true);
d.setName(name);
d.setVersion(version);

try {
final PackageURL purl = PackageURLBuilder.aPackageURL()
.withType("pypi")
.withName(name)
.withVersion(version)
.build();
d.addSoftwareIdentifier(new PurlIdentifier(purl, Confidence.HIGHEST));
} catch (MalformedPackageURLException ex) {
LOGGER.debug("Unable to build package url for pypi", ex);
d.addSoftwareIdentifier(new GenericIdentifier("pypi:" + name + "@" + version, Confidence.HIGH));
}

d.setPackagePath(String.format("%s:%s", name, version));
d.setEcosystem(PythonDistributionAnalyzer.DEPENDENCY_ECOSYSTEM);
final String filePath = String.format("%s:%s/%s", dependency.getFilePath(), name, version);
d.setFilePath(filePath);
d.setSha1sum(Checksum.getSHA1Checksum(filePath));
d.setSha256sum(Checksum.getSHA256Checksum(filePath));
d.setMd5sum(Checksum.getMD5Checksum(filePath));
d.addEvidence(EvidenceType.PRODUCT, POETRY_LOCK, "product", name, Confidence.HIGHEST);
d.addEvidence(EvidenceType.VERSION, POETRY_LOCK, "version", version, Confidence.HIGHEST);
d.addEvidence(EvidenceType.VENDOR, POETRY_LOCK, "vendor", name, Confidence.HIGHEST);
engine.addDependency(d);
});
}
}
Expand Up @@ -21,6 +21,7 @@ org.owasp.dependencycheck.analyzer.PythonDistributionAnalyzer
org.owasp.dependencycheck.analyzer.PythonPackageAnalyzer
org.owasp.dependencycheck.analyzer.PipAnalyzer
org.owasp.dependencycheck.analyzer.PipfileAnalyzer
org.owasp.dependencycheck.analyzer.PoetryAnalyzer
org.owasp.dependencycheck.analyzer.AutoconfAnalyzer
org.owasp.dependencycheck.analyzer.OpenSSLAnalyzer
org.owasp.dependencycheck.analyzer.CMakeAnalyzer
Expand Down
1 change: 1 addition & 0 deletions core/src/main/resources/dependencycheck.properties
Expand Up @@ -132,6 +132,7 @@ analyzer.autoconf.enabled=true
analyzer.maveninstall.enabled=true
analyzer.pip.enabled=true
analyzer.pipfile.enabled=true
analyzer.poetry.enabled=true
analyzer.cmake.enabled=true
analyzer.assembly.enabled=true
analyzer.nuspec.enabled=true
Expand Down
@@ -0,0 +1,75 @@
/*
* This file is part of dependency-check-core.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) 2019 Nima Yahyazadeh. All Rights Reserved.
*/
package org.owasp.dependencycheck.analyzer;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.hamcrest.MatcherAssert.assertThat;

import java.io.File;

import org.junit.Before;
import org.junit.Test;
import org.owasp.dependencycheck.BaseTest;
import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
import org.owasp.dependencycheck.dependency.Dependency;

public class PoetryAnalyzerTest extends BaseTest {

private PoetryAnalyzer analyzer;
private Engine engine;

@Override
@Before
public void setUp() throws Exception {
super.setUp();
analyzer = new PoetryAnalyzer();
engine = new Engine(this.getSettings());
}

@Test
public void testName() {
assertEquals("Analyzer name wrong.", "Poetry Analyzer",
analyzer.getName());
}

@Test
public void testSupportsFiles() {
assertThat(analyzer.accept(new File("poetry.lock")), is(true));
}

@Test
public void testPoetryLock() throws AnalysisException {
final Dependency result = new Dependency(BaseTest.getResourceAsFile(this, "poetry.lock"));
analyzer.analyze(result, engine);
assertEquals(88, engine.getDependencies().length);
boolean found = false;
for (Dependency d : engine.getDependencies()) {
if ("urllib3".equals(d.getName())) {
found = true;
assertEquals("1.26.12", d.getVersion());
assertThat(d.getDisplayFileName(), equalTo("urllib3:1.26.12"));
assertEquals(PythonDistributionAnalyzer.DEPENDENCY_ECOSYSTEM, d.getEcosystem());
}
}
assertTrue("Expeced to find PyYAML", found);
}
}

0 comments on commit 35fb02f

Please sign in to comment.