PoetryAnalyzer.java
- /*
- * 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 java.io.File;
- import org.owasp.dependencycheck.data.nvd.ecosystem.Ecosystem;
- import org.owasp.dependencycheck.dependency.naming.GenericIdentifier;
- 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";
- /**
- * Poetry project file.
- */
- private static final String PYPROJECT_TOML = "pyproject.toml";
- /**
- * The file filter for poetry.lock
- */
- private static final FileFilter POETRY_LOCK_FILTER = FileFilterBuilder.newInstance()
- .addFilenames(POETRY_LOCK, PYPROJECT_TOML)
- .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());
- if (PYPROJECT_TOML.equals(dependency.getActualFile().getName())) {
- if (result.getTable("tool.poetry") == null) {
- LOGGER.debug("skipping {} as it does not contain `tool.poetry`", dependency.getDisplayFileName());
- return;
- }
- final File parentPath = dependency.getActualFile().getParentFile();
- ensureLock(parentPath);
- //exit as we can't analyze pyproject.toml - insufficient version information
- return;
- }
- 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);
- });
- }
- private void ensureLock(File parent) throws AnalysisException {
- final File lock = new File(parent, POETRY_LOCK);
- final File requirements = new File(parent, "requirements.txt");
- final boolean found = lock.isFile() || requirements.isFile();
- //do not throw an error if the pyproject.toml is in the node_modules (#5464).
- if (!found && !parent.toString().contains("node_modules")) {
- throw new AnalysisException("Python `pyproject.toml` found and there "
- + "is not a `poetry.lock` or `requirements.txt` - analysis will be incomplete");
- }
- }
- }