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

feat: Add experimental Python Poetry support #5025

Merged
merged 3 commits into from Nov 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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);
}
}