From e93b89a98282af54bca0e973f211d8c0c85f045d Mon Sep 17 00:00:00 2001 From: Korbinian Singhammer Date: Fri, 12 Mar 2021 11:14:39 -0500 Subject: [PATCH 1/4] AdvisorCommand: Store available advisors in map Use a map to access an advisor directly by its name. Signed-off-by: Korbinian Singhammer --- cli/src/main/kotlin/commands/AdvisorCommand.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cli/src/main/kotlin/commands/AdvisorCommand.kt b/cli/src/main/kotlin/commands/AdvisorCommand.kt index 2a4cc5755dfbf..04f5b655bed37 100644 --- a/cli/src/main/kotlin/commands/AdvisorCommand.kt +++ b/cli/src/main/kotlin/commands/AdvisorCommand.kt @@ -43,6 +43,9 @@ import org.ossreviewtoolkit.utils.expandTilde import org.ossreviewtoolkit.utils.safeMkdirs class AdvisorCommand : CliktCommand(name = "advise", help = "Check dependencies for security vulnerabilities.") { + private val allAdvisorsByName = Advisor.ALL.associateBy { it.advisorName } + .toSortedMap(String.CASE_INSENSITIVE_ORDER) + private val input by option( "--ort-file", "-i", help = "An ORT result file with an analyzer result to use." @@ -76,10 +79,10 @@ class AdvisorCommand : CliktCommand(name = "advise", help = "Check dependencies private val advisorFactory by option( "--advisor", "-a", - help = "The advisor to use, one of ${Advisor.ALL}" + help = "The advisor to use, one of ${allAdvisorsByName.keys}" ).convert { advisorName -> - Advisor.ALL.find { it.advisorName.equals(advisorName, ignoreCase = true) } - ?: throw BadParameterValue("Advisor '$advisorName' is not one of ${Advisor.ALL}") + allAdvisorsByName[advisorName] + ?: throw BadParameterValue("Advisor '$advisorName' is not one of ${allAdvisorsByName.keys}") }.required() private val skipExcluded by option( From 81e60e17d6ad1aadb696045d680d78adab0344e3 Mon Sep 17 00:00:00 2001 From: Korbinian Singhammer Date: Fri, 12 Mar 2021 12:09:16 -0500 Subject: [PATCH 2/4] VulnerabilityProvider: Add class that will represent each advisor Signed-off-by: Korbinian Singhammer --- .../src/main/kotlin/VulnerabilityProvider.kt | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 advisor/src/main/kotlin/VulnerabilityProvider.kt diff --git a/advisor/src/main/kotlin/VulnerabilityProvider.kt b/advisor/src/main/kotlin/VulnerabilityProvider.kt new file mode 100644 index 0000000000000..3a719e685a8d8 --- /dev/null +++ b/advisor/src/main/kotlin/VulnerabilityProvider.kt @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2020-2021 Bosch.IO GmbH + * Copyright (C) 2021 HERE Europe B.V. + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ + +package org.ossreviewtoolkit.advisor + +import java.time.Instant + +import org.ossreviewtoolkit.model.AdvisorDetails +import org.ossreviewtoolkit.model.AdvisorResult +import org.ossreviewtoolkit.model.AdvisorSummary +import org.ossreviewtoolkit.model.Package +import org.ossreviewtoolkit.model.createAndLogIssue +import org.ossreviewtoolkit.utils.collectMessagesAsString +import org.ossreviewtoolkit.utils.showStackTrace + +/** + * An abstract class that represents a service that can retrieve vulnerability information + * for a list of given [Package]s. + */ +abstract class VulnerabilityProvider(val providerName: String) { + /** + * For a given list of [Package]s, retrieve vulnerability information and return a map + * that associates each package with a list of [AdvisorResult]s. Needs to be implemented + * by child classes. + */ + protected abstract suspend fun retrievePackageVulnerabilities( + packages: List + ): Map> + + /** + * A generic method that creates a failed [AdvisorResult] for [Package]s if there was an issue + * during the retrieval of vulnerability information. + */ + protected fun createFailedResults( + startTime: Instant, + packages: List, + t: Throwable + ): Map> { + val endTime = Instant.now() + + t.showStackTrace() + + val failedResults = listOf( + AdvisorResult( + vulnerabilities = emptyList(), + advisor = AdvisorDetails(providerName), + summary = AdvisorSummary( + startTime = startTime, + endTime = endTime, + issues = listOf( + createAndLogIssue( + source = providerName, + message = "Failed to retrieve security vulnerabilities from $providerName: " + + t.collectMessagesAsString() + ) + ) + ) + ) + ) + + return packages.associateWith { failedResults } + } +} From d32c08fca16d6441b6b8818f5c345dde12e4e403 Mon Sep 17 00:00:00 2001 From: Korbinian Singhammer Date: Fri, 12 Mar 2021 12:16:13 -0500 Subject: [PATCH 3/4] advisor: Rename AdvisorFactory to VulnerabilityProviderFactory In the following commit, vulnerability providers, such as NexusIq, are going to implement the VulnerabilitProvider class instead of the Advisor class. Thus, rename the factory to match the name. Signed-off-by: Korbinian Singhammer --- advisor/src/main/kotlin/Advisor.kt | 2 +- ...actory.kt => VulnerabilityProviderFactory.kt} | 16 ++++++++++++---- advisor/src/main/kotlin/advisors/NexusIq.kt | 4 ++-- .../src/main/kotlin/advisors/VulnerableCode.kt | 4 ++-- 4 files changed, 17 insertions(+), 9 deletions(-) rename advisor/src/main/kotlin/{AdvisorFactory.kt => VulnerabilityProviderFactory.kt} (70%) diff --git a/advisor/src/main/kotlin/Advisor.kt b/advisor/src/main/kotlin/Advisor.kt index 5fa231175542a..c67adea29f796 100644 --- a/advisor/src/main/kotlin/Advisor.kt +++ b/advisor/src/main/kotlin/Advisor.kt @@ -45,7 +45,7 @@ import org.ossreviewtoolkit.utils.showStackTrace */ abstract class Advisor(val advisorName: String, protected val config: AdvisorConfiguration) { companion object { - private val LOADER = ServiceLoader.load(AdvisorFactory::class.java)!! + private val LOADER = ServiceLoader.load(VulnerabilityProviderFactory::class.java)!! /** * The list of all available advisors in the classpath. diff --git a/advisor/src/main/kotlin/AdvisorFactory.kt b/advisor/src/main/kotlin/VulnerabilityProviderFactory.kt similarity index 70% rename from advisor/src/main/kotlin/AdvisorFactory.kt rename to advisor/src/main/kotlin/VulnerabilityProviderFactory.kt index dae6ed57650ad..2a38942b4da41 100644 --- a/advisor/src/main/kotlin/AdvisorFactory.kt +++ b/advisor/src/main/kotlin/VulnerabilityProviderFactory.kt @@ -22,11 +22,14 @@ package org.ossreviewtoolkit.advisor import java.util.ServiceLoader import org.ossreviewtoolkit.model.config.AdvisorConfiguration +import org.ossreviewtoolkit.utils.ORT_CONFIG_FILENAME +import org.ossreviewtoolkit.utils.ortConfigDirectory /** - * A common interface for use with [ServiceLoader] that all [AbstractAdvisorFactory] classes need to implement. + * A common interface for use with [ServiceLoader] that all [AbstractVulnerabilityProviderFactory] + * classes need to implement. */ -interface AdvisorFactory { +interface VulnerabilityProviderFactory { /** * The name to use to refer to the advisor. */ @@ -41,11 +44,16 @@ interface AdvisorFactory { /** * A generic factory class for an [Advisor]. */ -abstract class AbstractAdvisorFactory( +abstract class AbstractVulnerabilityProviderFactory( override val advisorName: String -) : AdvisorFactory { +) : VulnerabilityProviderFactory { abstract override fun create(config: AdvisorConfiguration): T + protected fun getProviderConfiguration(config: AdvisorConfiguration, select: (AdvisorConfiguration) -> T?): T = + select(config) ?: throw IllegalArgumentException( + "No $advisorName advisor configuration found in ${ortConfigDirectory.resolve(ORT_CONFIG_FILENAME)}" + ) + /** * Return the advisor's name here to allow Clikt to display something meaningful when listing the scanners * which are enabled by default via their factories. diff --git a/advisor/src/main/kotlin/advisors/NexusIq.kt b/advisor/src/main/kotlin/advisors/NexusIq.kt index d78098767472f..378d7baeff845 100644 --- a/advisor/src/main/kotlin/advisors/NexusIq.kt +++ b/advisor/src/main/kotlin/advisors/NexusIq.kt @@ -23,7 +23,7 @@ import java.io.IOException import java.net.URI import java.time.Instant -import org.ossreviewtoolkit.advisor.AbstractAdvisorFactory +import org.ossreviewtoolkit.advisor.AbstractVulnerabilityProviderFactory import org.ossreviewtoolkit.advisor.Advisor import org.ossreviewtoolkit.clients.nexusiq.NexusIqService import org.ossreviewtoolkit.model.AdvisorDetails @@ -54,7 +54,7 @@ class NexusIq( name: String, config: AdvisorConfiguration ) : Advisor(name, config) { - class Factory : AbstractAdvisorFactory("NexusIQ") { + class Factory : AbstractVulnerabilityProviderFactory("NexusIQ") { override fun create(config: AdvisorConfiguration) = NexusIq(advisorName, config) } diff --git a/advisor/src/main/kotlin/advisors/VulnerableCode.kt b/advisor/src/main/kotlin/advisors/VulnerableCode.kt index 70f012ead84c7..80f94e99ceaae 100644 --- a/advisor/src/main/kotlin/advisors/VulnerableCode.kt +++ b/advisor/src/main/kotlin/advisors/VulnerableCode.kt @@ -27,7 +27,7 @@ import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope -import org.ossreviewtoolkit.advisor.AbstractAdvisorFactory +import org.ossreviewtoolkit.advisor.AbstractVulnerabilityProviderFactory import org.ossreviewtoolkit.advisor.Advisor import org.ossreviewtoolkit.clients.vulnerablecode.VulnerableCodeService import org.ossreviewtoolkit.model.AdvisorDetails @@ -46,7 +46,7 @@ import org.ossreviewtoolkit.utils.ortConfigDirectory * [VulnerableCode][https://github.com/nexB/vulnerablecode] instance. */ class VulnerableCode(name: String, config: AdvisorConfiguration) : Advisor(name, config) { - class Factory : AbstractAdvisorFactory("VulnerableCode") { + class Factory : AbstractVulnerabilityProviderFactory("VulnerableCode") { override fun create(config: AdvisorConfiguration) = VulnerableCode(advisorName, config) } From b93553bf23c8625de8d6dd069a15cebf40fc4bf6 Mon Sep 17 00:00:00 2001 From: Korbinian Singhammer Date: Fri, 12 Mar 2021 12:49:44 -0500 Subject: [PATCH 4/4] advisor: Redesign the Advisor class Let vulnerabilty providers, e.g. NexusIq, implement the VulnerabilityProvider class. Move responsibility to create providers to the Advisor class, which can then call the provider. This is a preparation to let the Advisor handle multiple providers. Signed-off-by: Korbinian Singhammer --- advisor/src/main/kotlin/Advisor.kt | 51 +++---------------- .../src/main/kotlin/VulnerabilityProvider.kt | 2 +- .../kotlin/VulnerabilityProviderFactory.kt | 20 ++++---- advisor/src/main/kotlin/advisors/NexusIq.kt | 20 +++----- .../main/kotlin/advisors/VulnerableCode.kt | 21 ++++---- .../kotlin/advisors/VulnerableCodeTest.kt | 45 +++++++++------- .../main/kotlin/commands/AdvisorCommand.kt | 18 ++++--- 7 files changed, 71 insertions(+), 106 deletions(-) diff --git a/advisor/src/main/kotlin/Advisor.kt b/advisor/src/main/kotlin/Advisor.kt index c67adea29f796..2574d5a25efe3 100644 --- a/advisor/src/main/kotlin/Advisor.kt +++ b/advisor/src/main/kotlin/Advisor.kt @@ -26,29 +26,22 @@ import java.util.ServiceLoader import kotlinx.coroutines.runBlocking -import org.ossreviewtoolkit.model.AdvisorDetails import org.ossreviewtoolkit.model.AdvisorRecord -import org.ossreviewtoolkit.model.AdvisorResult import org.ossreviewtoolkit.model.AdvisorRun -import org.ossreviewtoolkit.model.AdvisorSummary import org.ossreviewtoolkit.model.OrtResult -import org.ossreviewtoolkit.model.Package import org.ossreviewtoolkit.model.config.AdvisorConfiguration -import org.ossreviewtoolkit.model.createAndLogIssue import org.ossreviewtoolkit.model.readValue import org.ossreviewtoolkit.utils.Environment -import org.ossreviewtoolkit.utils.collectMessagesAsString -import org.ossreviewtoolkit.utils.showStackTrace /** - * The class to retrieve security advisories. + * The class to manage [VulnerabilityProvider]s that retrieve security advisories. */ -abstract class Advisor(val advisorName: String, protected val config: AdvisorConfiguration) { +class Advisor(private val providerFactory: VulnerabilityProviderFactory, private val config: AdvisorConfiguration) { companion object { private val LOADER = ServiceLoader.load(VulnerabilityProviderFactory::class.java)!! /** - * The list of all available advisors in the classpath. + * The list of all available [VulnerabilityProvider]s in the classpath. */ val ALL by lazy { LOADER.iterator().asSequence().toList() } } @@ -69,8 +62,10 @@ abstract class Advisor(val advisorName: String, protected val config: AdvisorCon "The provided ORT result file '${ortResultFile.canonicalPath}' does not contain an analyzer result." } + val provider = providerFactory.create(config) + val packages = ortResult.getPackages(skipExcluded).map { it.pkg } - val results = runBlocking { retrievePackageVulnerabilities(packages) } + val results = runBlocking { provider.retrievePackageVulnerabilities(packages) } .mapKeysTo(sortedMapOf()) { (pkg, _) -> pkg.id } val advisorRecord = AdvisorRecord(results) @@ -80,38 +75,4 @@ abstract class Advisor(val advisorName: String, protected val config: AdvisorCon val advisorRun = AdvisorRun(startTime, endTime, Environment(), config, advisorRecord) return ortResult.copy(advisor = advisorRun) } - - protected abstract suspend fun retrievePackageVulnerabilities( - packages: List - ): Map> - - protected fun createFailedResults( - startTime: Instant, - packages: List, - t: Throwable - ): Map> { - val endTime = Instant.now() - - t.showStackTrace() - - val failedResults = listOf( - AdvisorResult( - vulnerabilities = emptyList(), - advisor = AdvisorDetails(advisorName), - summary = AdvisorSummary( - startTime = startTime, - endTime = endTime, - issues = listOf( - createAndLogIssue( - source = advisorName, - message = "Failed to retrieve security vulnerabilities from $advisorName: " + - t.collectMessagesAsString() - ) - ) - ) - ) - ) - - return packages.associateWith { failedResults } - } } diff --git a/advisor/src/main/kotlin/VulnerabilityProvider.kt b/advisor/src/main/kotlin/VulnerabilityProvider.kt index 3a719e685a8d8..c14c4eb933f6e 100644 --- a/advisor/src/main/kotlin/VulnerabilityProvider.kt +++ b/advisor/src/main/kotlin/VulnerabilityProvider.kt @@ -40,7 +40,7 @@ abstract class VulnerabilityProvider(val providerName: String) { * that associates each package with a list of [AdvisorResult]s. Needs to be implemented * by child classes. */ - protected abstract suspend fun retrievePackageVulnerabilities( + abstract suspend fun retrievePackageVulnerabilities( packages: List ): Map> diff --git a/advisor/src/main/kotlin/VulnerabilityProviderFactory.kt b/advisor/src/main/kotlin/VulnerabilityProviderFactory.kt index 2a38942b4da41..257b0e13cf628 100644 --- a/advisor/src/main/kotlin/VulnerabilityProviderFactory.kt +++ b/advisor/src/main/kotlin/VulnerabilityProviderFactory.kt @@ -31,32 +31,32 @@ import org.ossreviewtoolkit.utils.ortConfigDirectory */ interface VulnerabilityProviderFactory { /** - * The name to use to refer to the advisor. + * The name to use to refer to the provider. */ - val advisorName: String + val providerName: String /** - * Create an [Advisor] using the specified [config]. + * Create a [VulnerabilityProvider] using the specified [config]. */ - fun create(config: AdvisorConfiguration): Advisor + fun create(config: AdvisorConfiguration): VulnerabilityProvider } /** - * A generic factory class for an [Advisor]. + * A generic factory class for a [VulnerabilityProvider]. */ -abstract class AbstractVulnerabilityProviderFactory( - override val advisorName: String +abstract class AbstractVulnerabilityProviderFactory( + override val providerName: String ) : VulnerabilityProviderFactory { abstract override fun create(config: AdvisorConfiguration): T protected fun getProviderConfiguration(config: AdvisorConfiguration, select: (AdvisorConfiguration) -> T?): T = select(config) ?: throw IllegalArgumentException( - "No $advisorName advisor configuration found in ${ortConfigDirectory.resolve(ORT_CONFIG_FILENAME)}" + "No configuration for $providerName found in ${ortConfigDirectory.resolve(ORT_CONFIG_FILENAME)}" ) /** - * Return the advisor's name here to allow Clikt to display something meaningful when listing the scanners + * Return the provider's name here to allow Clikt to display something meaningful when listing the scanners * which are enabled by default via their factories. */ - override fun toString() = advisorName + override fun toString() = providerName } diff --git a/advisor/src/main/kotlin/advisors/NexusIq.kt b/advisor/src/main/kotlin/advisors/NexusIq.kt index 378d7baeff845..f6762becd84fa 100644 --- a/advisor/src/main/kotlin/advisors/NexusIq.kt +++ b/advisor/src/main/kotlin/advisors/NexusIq.kt @@ -24,7 +24,7 @@ import java.net.URI import java.time.Instant import org.ossreviewtoolkit.advisor.AbstractVulnerabilityProviderFactory -import org.ossreviewtoolkit.advisor.Advisor +import org.ossreviewtoolkit.advisor.VulnerabilityProvider import org.ossreviewtoolkit.clients.nexusiq.NexusIqService import org.ossreviewtoolkit.model.AdvisorDetails import org.ossreviewtoolkit.model.AdvisorResult @@ -32,13 +32,12 @@ import org.ossreviewtoolkit.model.AdvisorSummary import org.ossreviewtoolkit.model.Package import org.ossreviewtoolkit.model.Vulnerability import org.ossreviewtoolkit.model.config.AdvisorConfiguration +import org.ossreviewtoolkit.model.config.NexusIqConfiguration import org.ossreviewtoolkit.model.utils.PurlType import org.ossreviewtoolkit.model.utils.getPurlType import org.ossreviewtoolkit.model.utils.toPurl -import org.ossreviewtoolkit.utils.ORT_CONFIG_FILENAME import org.ossreviewtoolkit.utils.OkHttpClientHelper import org.ossreviewtoolkit.utils.log -import org.ossreviewtoolkit.utils.ortConfigDirectory import retrofit2.HttpException @@ -50,19 +49,12 @@ private const val REQUEST_CHUNK_SIZE = 100 /** * A wrapper for [Nexus IQ Server](https://help.sonatype.com/iqserver) security vulnerability data. */ -class NexusIq( - name: String, - config: AdvisorConfiguration -) : Advisor(name, config) { +class NexusIq(name: String, private val nexusIqConfig: NexusIqConfiguration) : VulnerabilityProvider(name) { class Factory : AbstractVulnerabilityProviderFactory("NexusIQ") { - override fun create(config: AdvisorConfiguration) = NexusIq(advisorName, config) + override fun create(config: AdvisorConfiguration) = + NexusIq(providerName, getProviderConfiguration(config) { it.nexusIq }) } - private val nexusIqConfig = config.nexusIq - ?: throw IllegalArgumentException( - "No $advisorName advisor configuration found in ${ortConfigDirectory.resolve(ORT_CONFIG_FILENAME)}" - ) - private val service by lazy { NexusIqService.create( nexusIqConfig.serverUrl, @@ -108,7 +100,7 @@ class NexusIq( pkg to listOf( AdvisorResult( details.securityData.securityIssues.map { it.toVulnerability() }, - AdvisorDetails(advisorName), + AdvisorDetails(providerName), AdvisorSummary(startTime, endTime) ) ) diff --git a/advisor/src/main/kotlin/advisors/VulnerableCode.kt b/advisor/src/main/kotlin/advisors/VulnerableCode.kt index 80f94e99ceaae..c62c60f0ca4b7 100644 --- a/advisor/src/main/kotlin/advisors/VulnerableCode.kt +++ b/advisor/src/main/kotlin/advisors/VulnerableCode.kt @@ -28,7 +28,7 @@ import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope import org.ossreviewtoolkit.advisor.AbstractVulnerabilityProviderFactory -import org.ossreviewtoolkit.advisor.Advisor +import org.ossreviewtoolkit.advisor.VulnerabilityProvider import org.ossreviewtoolkit.clients.vulnerablecode.VulnerableCodeService import org.ossreviewtoolkit.model.AdvisorDetails import org.ossreviewtoolkit.model.AdvisorResult @@ -36,18 +36,21 @@ import org.ossreviewtoolkit.model.AdvisorSummary import org.ossreviewtoolkit.model.Package import org.ossreviewtoolkit.model.Vulnerability import org.ossreviewtoolkit.model.config.AdvisorConfiguration +import org.ossreviewtoolkit.model.config.VulnerableCodeConfiguration import org.ossreviewtoolkit.model.utils.toPurl -import org.ossreviewtoolkit.utils.ORT_CONFIG_FILENAME import org.ossreviewtoolkit.utils.OkHttpClientHelper -import org.ossreviewtoolkit.utils.ortConfigDirectory /** - * An [Advisor] implementation that obtains security vulnerability information from a + * A [VulnerabilityProvider] implementation that obtains security vulnerability information from a * [VulnerableCode][https://github.com/nexB/vulnerablecode] instance. */ -class VulnerableCode(name: String, config: AdvisorConfiguration) : Advisor(name, config) { +class VulnerableCode( + name: String, + private val vulnerableCodeConfiguration: VulnerableCodeConfiguration +) : VulnerabilityProvider(name) { class Factory : AbstractVulnerabilityProviderFactory("VulnerableCode") { - override fun create(config: AdvisorConfiguration) = VulnerableCode(advisorName, config) + override fun create(config: AdvisorConfiguration) = + VulnerableCode(providerName, getProviderConfiguration(config) { it.vulnerableCode }) } companion object { @@ -73,10 +76,6 @@ class VulnerableCode(name: String, config: AdvisorConfiguration) : Advisor(name, } private val service by lazy { - val vulnerableCodeConfiguration = config.vulnerableCode - ?: throw IllegalArgumentException( - "No $advisorName advisor configuration found in ${ortConfigDirectory.resolve(ORT_CONFIG_FILENAME)}" - ) VulnerableCodeService.create(vulnerableCodeConfiguration.serverUrl, OkHttpClientHelper.buildClient()) } @@ -103,7 +102,7 @@ class VulnerableCode(name: String, config: AdvisorConfiguration) : Advisor(name, pkg to listOf( AdvisorResult( vulnerabilities, - AdvisorDetails(advisorName), + AdvisorDetails(providerName), AdvisorSummary(startTime, endTime) ) ) diff --git a/advisor/src/test/kotlin/advisors/VulnerableCodeTest.kt b/advisor/src/test/kotlin/advisors/VulnerableCodeTest.kt index 2be3afe342178..0abb725881f9f 100644 --- a/advisor/src/test/kotlin/advisors/VulnerableCodeTest.kt +++ b/advisor/src/test/kotlin/advisors/VulnerableCodeTest.kt @@ -36,18 +36,21 @@ import io.kotest.matchers.collections.beEmpty import io.kotest.matchers.collections.containExactly import io.kotest.matchers.collections.containExactlyInAnyOrder import io.kotest.matchers.collections.shouldHaveSize -import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.maps.shouldNotBeEmpty import io.kotest.matchers.should import io.kotest.matchers.shouldBe import java.io.File import java.net.URI +import kotlinx.coroutines.runBlocking + import org.ossreviewtoolkit.model.Identifier +import org.ossreviewtoolkit.model.OrtResult import org.ossreviewtoolkit.model.Severity import org.ossreviewtoolkit.model.Vulnerability -import org.ossreviewtoolkit.model.config.AdvisorConfiguration import org.ossreviewtoolkit.model.config.VulnerableCodeConfiguration +import org.ossreviewtoolkit.model.readValue import org.ossreviewtoolkit.model.utils.toPurl import org.ossreviewtoolkit.utils.test.shouldNotBeNull @@ -71,7 +74,7 @@ class VulnerableCodeTest : WordSpec({ wiremock.resetAll() } - "VulnerabilityCode" should { + "VulnerableCode" should { "return vulnerability information" { stubFor( post(urlPathEqualTo("/api/packages/bulk_search")) @@ -87,10 +90,12 @@ class VulnerableCodeTest : WordSpec({ stubVulnerability("v2", "CVE-2009-1382", 11.0f) stubVulnerability("v3", "CVE-2019-CoV19", 77.0f) - val advisor = createAdvisor(wiremock) - val result = advisor.retrieveVulnerabilityInformation(resultFile()).advisor?.results?.advisorResults + val vulnerableCode = createVulnerableCode(wiremock) + val packagesToAdvise = resultFile().readValue().getPackages(false).map { it.pkg } + + val result = vulnerableCode.retrievePackageVulnerabilities(packagesToAdvise).mapKeys { it.key.id } - result.shouldNotBeNull() + result.shouldNotBeEmpty() result.keys should containExactlyInAnyOrder(idLang, idStruts) val langResults = result.getValue(idLang) @@ -141,8 +146,10 @@ class VulnerableCodeTest : WordSpec({ ) ) - val advisor = createAdvisor(wiremock) - advisor.retrieveVulnerabilityInformation(resultFile()).advisor?.results?.advisorResults shouldNotBeNull { + val vulnerableCode = createVulnerableCode(wiremock) + val packagesToAdvise = resultFile().readValue().getPackages(false).map { it.pkg } + + vulnerableCode.retrievePackageVulnerabilities(packagesToAdvise).mapKeys { it.key.id } shouldNotBeNull { val strutsResults = getValue(idStruts) val expStrutsVulnerabilities = listOf( Vulnerability( @@ -211,7 +218,7 @@ private val idHamcrest = Identifier("Maven:org.hamcrest:hamcrest-core:1.3") private val packageIdentifiers = listOf(idJUnit, idLang, idText, idStruts, idHamcrest) /** - * The list of packages referenced by the test result. These packages should be requested by the advisor. + * The list of packages referenced by the test result. These packages should be requested by the vulnerability provider. */ private val packages = packageIdentifiers.map { it.toPurl() } @@ -227,12 +234,16 @@ private val packagesRequestJson = generateListRequest(packages, "packages") private val vulnerabilityDetailsTemplate = File(TEST_FILES_ROOT).resolve(VULNERABILITY_TEMPLATE).readText() /** - * Run a test with the VulnerabilityCode advisor against the given [test server][wiremock] and expect the + * Run a test with the VulnerabilityCode provider against the given [test server][wiremock] and expect the * operation to fail. In this case, for all packages a result with an error issue should have been created. */ private fun expectErrorResult(wiremock: WireMockServer) { - val advisor = createAdvisor(wiremock) - val result = advisor.retrieveVulnerabilityInformation(resultFile()).advisor?.results?.advisorResults + val vulnerableCode = createVulnerableCode(wiremock) + val packagesToAdvise = resultFile().readValue().getPackages(false).map { it.pkg } + + val result = runBlocking { + vulnerableCode.retrievePackageVulnerabilities(packagesToAdvise).mapKeys { it.key.id } + } result shouldNotBeNull { keys should containExactly(packageIdentifiers) @@ -250,17 +261,17 @@ private fun expectErrorResult(wiremock: WireMockServer) { } /** - * Create a configuration for the [VulnerableCode] advisor that points to the local [wireMockServer]. + * Create a configuration for the [VulnerableCode] vulnerability provider that points to the local [wireMockServer]. */ -private fun createConfig(wireMockServer: WireMockServer): AdvisorConfiguration { +private fun createConfig(wireMockServer: WireMockServer): VulnerableCodeConfiguration { val url = "http://localhost:${wireMockServer.port()}" - return AdvisorConfiguration(vulnerableCode = VulnerableCodeConfiguration(url)) + return VulnerableCodeConfiguration(url) } /** - * Create a test advisor instance that communicates with the local [wireMockServer]. + * Create a test instance of [VulnerableCode] that communicates with the local [wireMockServer]. */ -private fun createAdvisor(wireMockServer: WireMockServer): VulnerableCode = +private fun createVulnerableCode(wireMockServer: WireMockServer): VulnerableCode = VulnerableCode(ADVISOR_NAME, createConfig(wireMockServer)) /** diff --git a/cli/src/main/kotlin/commands/AdvisorCommand.kt b/cli/src/main/kotlin/commands/AdvisorCommand.kt index 04f5b655bed37..c0d12ab0a6ff5 100644 --- a/cli/src/main/kotlin/commands/AdvisorCommand.kt +++ b/cli/src/main/kotlin/commands/AdvisorCommand.kt @@ -43,7 +43,7 @@ import org.ossreviewtoolkit.utils.expandTilde import org.ossreviewtoolkit.utils.safeMkdirs class AdvisorCommand : CliktCommand(name = "advise", help = "Check dependencies for security vulnerabilities.") { - private val allAdvisorsByName = Advisor.ALL.associateBy { it.advisorName } + private val allVulnerabilityProvidersByName = Advisor.ALL.associateBy { it.providerName } .toSortedMap(String.CASE_INSENSITIVE_ORDER) private val input by option( @@ -77,12 +77,14 @@ class AdvisorCommand : CliktCommand(name = "advise", help = "Check dependencies private val globalOptionsForSubcommands by requireObject() - private val advisorFactory by option( + private val providerFactory by option( "--advisor", "-a", - help = "The advisor to use, one of ${allAdvisorsByName.keys}" - ).convert { advisorName -> - allAdvisorsByName[advisorName] - ?: throw BadParameterValue("Advisor '$advisorName' is not one of ${allAdvisorsByName.keys}") + help = "The advisor to use, one of ${allVulnerabilityProvidersByName.keys}" + ).convert { name -> + allVulnerabilityProvidersByName[name] + ?: throw BadParameterValue( + "Advisor '$name' is not one of ${allVulnerabilityProvidersByName.keys}" + ) }.required() private val skipExcluded by option( @@ -102,9 +104,9 @@ class AdvisorCommand : CliktCommand(name = "advise", help = "Check dependencies } } - val advisor = advisorFactory.create(globalOptionsForSubcommands.config.advisor) + val advisor = Advisor(providerFactory, globalOptionsForSubcommands.config.advisor) - println("Using advisor '${advisor.advisorName}'.") + println("Using advisor '${providerFactory.providerName}'.") val ortResult = advisor.retrieveVulnerabilityInformation(input, skipExcluded).mergeLabels(labels)