Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add experimental Python Poetry support (#5025)
* 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
Showing
12 changed files
with
2,366 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
188 changes: 188 additions & 0 deletions
188
core/src/main/java/org/owasp/dependencycheck/analyzer/PoetryAnalyzer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
75 changes: 75 additions & 0 deletions
75
core/src/test/java/org/owasp/dependencycheck/analyzer/PoetryAnalyzerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
Oops, something went wrong.