diff --git a/advisor/src/funTest/kotlin/OsvFunTest.kt b/advisor/src/funTest/kotlin/OsvFunTest.kt index 9ae4e00fb63fe..5e28afc6bae24 100644 --- a/advisor/src/funTest/kotlin/OsvFunTest.kt +++ b/advisor/src/funTest/kotlin/OsvFunTest.kt @@ -26,10 +26,10 @@ import io.kotest.matchers.shouldBe import java.time.Instant import org.ossreviewtoolkit.advisor.advisors.Osv +import org.ossreviewtoolkit.advisor.advisors.OsvConfiguration import org.ossreviewtoolkit.model.AdvisorResult import org.ossreviewtoolkit.model.Identifier import org.ossreviewtoolkit.model.Package -import org.ossreviewtoolkit.model.config.OsvConfiguration import org.ossreviewtoolkit.model.readValue import org.ossreviewtoolkit.model.utils.toPurl import org.ossreviewtoolkit.utils.test.getAssetFile diff --git a/advisor/src/main/kotlin/AdviceProviderFactory.kt b/advisor/src/main/kotlin/AdviceProviderFactory.kt index 62093ff3f05be..8793a207363d7 100644 --- a/advisor/src/main/kotlin/AdviceProviderFactory.kt +++ b/advisor/src/main/kotlin/AdviceProviderFactory.kt @@ -21,48 +21,15 @@ package org.ossreviewtoolkit.advisor import java.util.ServiceLoader -import org.ossreviewtoolkit.model.config.AdvisorConfiguration -import org.ossreviewtoolkit.utils.common.Options -import org.ossreviewtoolkit.utils.common.Plugin -import org.ossreviewtoolkit.utils.ort.ORT_CONFIG_FILENAME -import org.ossreviewtoolkit.utils.ort.ortConfigDirectory +import org.ossreviewtoolkit.utils.common.TypedConfigurablePluginFactory /** - * A common interface for use with [ServiceLoader] that all [AbstractAdviceProviderFactory] classes need to - * implement. + * A common abstract class for use with [ServiceLoader] that all [AdviceProviderFactory] classes need to implement. */ -interface AdviceProviderFactory : Plugin { +abstract class AdviceProviderFactory(override val type: String) : + TypedConfigurablePluginFactory { /** - * Create an [AdviceProvider] using the specified [config]. - */ - fun create(config: AdvisorConfiguration): AdviceProvider -} - -/** - * A generic factory class for an [AdviceProvider]. - */ -abstract class AbstractAdviceProviderFactory( - override val type: String -) : AdviceProviderFactory { - abstract override fun create(config: AdvisorConfiguration): T - - /** - * For providers that require configuration, return the typed configuration dedicated to provider [T] or throw if it - * does not exist. - */ - protected fun AdvisorConfiguration.forProvider(select: AdvisorConfiguration.() -> T?): T = - requireNotNull(select()) { - "No configuration for '$type' found in '${ortConfigDirectory.resolve(ORT_CONFIG_FILENAME)}'." - } - - /** - * Return a map with options for the [AdviceProvider] managed by this factory or an empty map if no options are - * available. - */ - protected fun AdvisorConfiguration.providerOptions(): Options = options.orEmpty()[type].orEmpty() - - /** - * Return the provider's name here to allow Clikt to display something meaningful when listing the advisors which + * Return the provider's type here to allow Clikt to display something meaningful when listing the advisors which * are enabled by default via their factories. */ override fun toString() = type diff --git a/advisor/src/main/kotlin/Advisor.kt b/advisor/src/main/kotlin/Advisor.kt index 5a99ee3ed37e1..1f97f1dd2c4e5 100644 --- a/advisor/src/main/kotlin/Advisor.kt +++ b/advisor/src/main/kotlin/Advisor.kt @@ -42,14 +42,14 @@ import org.ossreviewtoolkit.utils.ort.Environment * [OrtResult]. */ class Advisor( - private val providerFactories: List, + private val providerFactories: List>, private val config: AdvisorConfiguration ) { companion object { /** * All [advice provider factories][AdviceProviderFactory] available in the classpath, associated by their names. */ - val ALL by lazy { Plugin.getAll() } + val ALL by lazy { Plugin.getAll>() } } /** @@ -83,7 +83,10 @@ class Advisor( if (packages.isEmpty()) { logger.info { "There are no packages to give advice for." } } else { - val providers = providerFactories.map { it.create(config) } + val providers = providerFactories.map { + val providerConfig = config.config?.get(it.type) + it.create(providerConfig?.options.orEmpty(), providerConfig?.secrets.orEmpty()) + } providers.map { provider -> async { diff --git a/advisor/src/main/kotlin/advisors/GitHubDefects.kt b/advisor/src/main/kotlin/advisors/GitHubDefects.kt index ee717324731db..63b79b4f19654 100644 --- a/advisor/src/main/kotlin/advisors/GitHubDefects.kt +++ b/advisor/src/main/kotlin/advisors/GitHubDefects.kt @@ -33,8 +33,8 @@ import kotlinx.coroutines.withContext import org.apache.logging.log4j.kotlin.logger -import org.ossreviewtoolkit.advisor.AbstractAdviceProviderFactory import org.ossreviewtoolkit.advisor.AdviceProvider +import org.ossreviewtoolkit.advisor.AdviceProviderFactory import org.ossreviewtoolkit.clients.github.DateTime import org.ossreviewtoolkit.clients.github.GitHubService import org.ossreviewtoolkit.clients.github.Paging @@ -50,9 +50,9 @@ import org.ossreviewtoolkit.model.Defect import org.ossreviewtoolkit.model.Issue import org.ossreviewtoolkit.model.Package import org.ossreviewtoolkit.model.Severity -import org.ossreviewtoolkit.model.config.AdvisorConfiguration -import org.ossreviewtoolkit.model.config.GitHubDefectsConfiguration +import org.ossreviewtoolkit.model.config.PluginConfiguration import org.ossreviewtoolkit.model.createAndLogIssue +import org.ossreviewtoolkit.utils.common.Options import org.ossreviewtoolkit.utils.common.collectMessages import org.ossreviewtoolkit.utils.common.enumSetOf import org.ossreviewtoolkit.utils.ort.filterVersionNames @@ -79,6 +79,23 @@ import org.ossreviewtoolkit.utils.ort.showStackTrace * * For these reasons, this advisor is more a reference implementation for ORT's defects model and not necessarily * suitable for production usage. + * + * This [AdviceProvider] offers the following configuration options: + * + * #### [Options][PluginConfiguration.options] + * + * * **`endpointUrl`:** The URL of the GraphQL endpoint to be accessed by the service. If undefined, default is the + * endpoint of the official GitHub GraphQL API. + * * **`labelFilter`:** A list with labels to be used for filtering GitHub issues. See + * [GitHubDefectsConfiguration.labelFilter] for details. + * * **`maxNumberOfIssuesPerRepository`:** The maximum number of defects that are retrieved from a single repository. + * See [GitHubDefectsConfiguration.maxNumberOfIssuesPerRepository] for details. + * * **`parallelRequests`:** Determines the number of requests to the GitHub GraphQL API that are executed in parallel. + * See [GitHubDefectsConfiguration.parallelRequests] for details. + * + * #### [Secrets][PluginConfiguration.secrets] + * + * * **`token`:** The access token to authenticate against the GitHub GraphQL endpoint. */ class GitHubDefects(name: String, config: GitHubDefectsConfiguration) : AdviceProvider(name) { companion object { @@ -89,8 +106,18 @@ class GitHubDefects(name: String, config: GitHubDefectsConfiguration) : AdvicePr const val DEFAULT_PARALLEL_REQUESTS = 4 } - class Factory : AbstractAdviceProviderFactory("GitHubDefects") { - override fun create(config: AdvisorConfiguration) = GitHubDefects(type, config.forProvider { gitHubDefects }) + class Factory : AdviceProviderFactory("GitHubDefects") { + override fun create(config: GitHubDefectsConfiguration) = GitHubDefects(type, config) + + override fun parseConfig(options: Options, secrets: Options) = + GitHubDefectsConfiguration( + token = secrets["token"], + endpointUrl = options["endpointUrl"], + labelFilter = options["labelFilter"]?.split(",")?.map { it.trim() } + ?: listOf("!duplicate", "!enhancement", "!invalid", "!question", "*"), + maxNumberOfIssuesPerRepository = options["maxNumberOfIssuesPerRepository"]?.toInt(), + parallelRequests = options["parallelRequests"]?.toInt() + ) } /** diff --git a/advisor/src/main/kotlin/advisors/GitHubDefectsConfiguration.kt b/advisor/src/main/kotlin/advisors/GitHubDefectsConfiguration.kt new file mode 100644 index 0000000000000..b85268896202d --- /dev/null +++ b/advisor/src/main/kotlin/advisors/GitHubDefectsConfiguration.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2021 The ORT Project Authors (see ) + * + * 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 + * + * https://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.advisors + +/** + * The configuration for the GitHub Defects advisor. + */ +data class GitHubDefectsConfiguration( + /** + * The access token to authenticate against the GitHub GraphQL endpoint. + */ + val token: String? = null, + + /** + * The URL of the GraphQL endpoint to be accessed by the service. If undefined, default is the endpoint of the + * official GitHub GraphQL API. + */ + val endpointUrl: String? = null, + + /** + * A list with labels to be used for filtering GitHub issues. With GitHub's data model for issues, it is not + * possible to determine whether a specific issue is actually a defect or something else, e.g. a feature request. + * Via this property, it is possible to limit the issues retrieved by the GitHub defects advisor by filtering for + * specific label values. The filtering works as follows: + * - Each string in this list refers to a label to be matched. The strings are processed in order. + * - If for an issue a label with the name of the current string is found, the issue is included into the result + * set. + * - If the current string starts with one of the characters '-' or '!', it defines an exclusion. So, if an issue + * contains a label named like the current string with the first character removed, this issue is not added to + * the result set, and filtering stops here. (The ordered processing resolves conflicting filters, as the first + * match wins.) + * - Label name matches are case-insensitive. + * - Wildcards are supported; a "*" matches arbitrary characters. + * - If the end of the list is reached and no match was found, the issue is not added to the result set. In order + * to have all issues included for which no specific exclusion was found, a wildcard match "*" can be added at + * the end. + * Per default, some of GitHub's default labels are excluded that typically indicate that an issue is not a defect + * (see https://docs.github.com/en/issues/using-labels-and-milestones-to-track-work/managing-labels#about-default-labels) + */ + val labelFilter: List = listOf("!duplicate", "!enhancement", "!invalid", "!question", "*"), + + /** + * The maximum number of defects that are retrieved from a single repository. If a repository contains more + * issues, only this number is returned (the newest ones). Popular libraries hosted on GitHub can really have a + * large number of issues; therefore, it makes sense to restrict the result set produced by this advisor. + */ + val maxNumberOfIssuesPerRepository: Int? = null, + + /** + * Determines the number of requests to the GitHub GraphQL API that are executed in parallel. Rather than querying + * each repository one after the other, fetching the data of multiple repositories concurrently can reduce the + * execution times for this advisor implementation. If unspecified, a default value for parallel executions as + * defined in the _GitHubDefects_ class is used. + */ + val parallelRequests: Int? = null +) diff --git a/advisor/src/main/kotlin/advisors/NexusIq.kt b/advisor/src/main/kotlin/advisors/NexusIq.kt index cf680dd21ac6a..efe41246406f9 100644 --- a/advisor/src/main/kotlin/advisors/NexusIq.kt +++ b/advisor/src/main/kotlin/advisors/NexusIq.kt @@ -25,8 +25,8 @@ import java.time.Instant import org.apache.logging.log4j.kotlin.logger -import org.ossreviewtoolkit.advisor.AbstractAdviceProviderFactory import org.ossreviewtoolkit.advisor.AdviceProvider +import org.ossreviewtoolkit.advisor.AdviceProviderFactory import org.ossreviewtoolkit.clients.nexusiq.NexusIqService import org.ossreviewtoolkit.clients.nexusiq.NexusIqService.Component import org.ossreviewtoolkit.clients.nexusiq.NexusIqService.ComponentDetails @@ -41,11 +41,11 @@ import org.ossreviewtoolkit.model.Issue import org.ossreviewtoolkit.model.Package import org.ossreviewtoolkit.model.Vulnerability import org.ossreviewtoolkit.model.VulnerabilityReference -import org.ossreviewtoolkit.model.config.AdvisorConfiguration -import org.ossreviewtoolkit.model.config.NexusIqConfiguration +import org.ossreviewtoolkit.model.config.PluginConfiguration import org.ossreviewtoolkit.model.utils.PurlType import org.ossreviewtoolkit.model.utils.getPurlType import org.ossreviewtoolkit.model.utils.toPurl +import org.ossreviewtoolkit.utils.common.Options import org.ossreviewtoolkit.utils.common.collectMessages import org.ossreviewtoolkit.utils.common.enumSetOf import org.ossreviewtoolkit.utils.ort.OkHttpClientHelper @@ -63,10 +63,35 @@ private val READ_TIMEOUT = Duration.ofSeconds(60) /** * A wrapper for [Nexus IQ Server](https://help.sonatype.com/iqserver) security vulnerability data. + * + * This [AdviceProvider] offers the following configuration options: + * + * #### [Options][PluginConfiguration.options] + * + * * **`serverUrl`:** The URL to use for REST API requests against the server. + * * **`browseUrl`:** The URL to use as a base for browsing vulnerability details. Defaults to the server URL. + * + * #### [Secrets][PluginConfiguration.secrets] + * + * * **`username`:** The username to use for authentication. + * * **`password`:** The password to use for authentication. + * + * If not both `username` and `password` are provided, authentication is disabled. */ class NexusIq(name: String, private val config: NexusIqConfiguration) : AdviceProvider(name) { - class Factory : AbstractAdviceProviderFactory("NexusIQ") { - override fun create(config: AdvisorConfiguration) = NexusIq(type, config.forProvider { nexusIq }) + class Factory : AdviceProviderFactory("NexusIQ") { + override fun create(config: NexusIqConfiguration) = NexusIq(type, config) + + override fun parseConfig(options: Options, secrets: Options): NexusIqConfiguration { + val serverUrl = options.getValue("serverUrl") + + return NexusIqConfiguration( + serverUrl = serverUrl, + browseUrl = options["browseUrl"] ?: serverUrl, + username = secrets["username"], + password = secrets["password"] + ) + } } override val details: AdvisorDetails = AdvisorDetails(providerName, enumSetOf(AdvisorCapability.VULNERABILITIES)) diff --git a/advisor/src/main/kotlin/advisors/NexusIqConfiguration.kt b/advisor/src/main/kotlin/advisors/NexusIqConfiguration.kt new file mode 100644 index 0000000000000..08f6a733a8b56 --- /dev/null +++ b/advisor/src/main/kotlin/advisors/NexusIqConfiguration.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2020 The ORT Project Authors (see ) + * + * 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 + * + * https://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.advisors + +/** + * The configuration for Nexus IQ as a security vulnerability provider. + */ +data class NexusIqConfiguration( + /** + * The URL to use for REST API requests against the server. + */ + val serverUrl: String, + + /** + * A URL to use as a base for browsing vulnerability details. Defaults to the server URL. + */ + val browseUrl: String = serverUrl, + + /** + * The username to use for authentication. If not both [username] and [password] are provided, authentication is + * disabled. + */ + val username: String? = null, + + /** + * The password to use for authentication. If not both [username] and [password] are provided, authentication is + * disabled. + */ + val password: String? = null +) diff --git a/advisor/src/main/kotlin/advisors/OssIndex.kt b/advisor/src/main/kotlin/advisors/OssIndex.kt index 5ea37a11c3293..ea5578c6dcacd 100644 --- a/advisor/src/main/kotlin/advisors/OssIndex.kt +++ b/advisor/src/main/kotlin/advisors/OssIndex.kt @@ -24,8 +24,8 @@ import java.time.Instant import org.apache.logging.log4j.kotlin.logger -import org.ossreviewtoolkit.advisor.AbstractAdviceProviderFactory import org.ossreviewtoolkit.advisor.AdviceProvider +import org.ossreviewtoolkit.advisor.AdviceProviderFactory import org.ossreviewtoolkit.clients.ossindex.OssIndexService import org.ossreviewtoolkit.clients.ossindex.OssIndexService.ComponentReport import org.ossreviewtoolkit.clients.ossindex.OssIndexService.ComponentReportRequest @@ -37,15 +37,29 @@ import org.ossreviewtoolkit.model.Issue import org.ossreviewtoolkit.model.Package import org.ossreviewtoolkit.model.Vulnerability import org.ossreviewtoolkit.model.VulnerabilityReference -import org.ossreviewtoolkit.model.config.AdvisorConfiguration -import org.ossreviewtoolkit.model.config.OssIndexConfiguration +import org.ossreviewtoolkit.model.config.PluginConfiguration import org.ossreviewtoolkit.model.utils.toPurl +import org.ossreviewtoolkit.utils.common.Options import org.ossreviewtoolkit.utils.common.collectMessages import org.ossreviewtoolkit.utils.common.enumSetOf import org.ossreviewtoolkit.utils.ort.OkHttpClientHelper /** * The number of packages to request from Sonatype OSS Index in one request. + * + * This [AdviceProvider] offers the following configuration options: + * + * #### [Options][PluginConfiguration.options] + * + * * **`serverUrl`:** The base URL of the OSS Index REST API. If undefined, the default base URL for the REST API of the + * public OSS Index service is used. + * + * #### [Secrets][PluginConfiguration.secrets] + * + * * **`username`:** The username to use for authentication. + * * **`password`:** The password to use for authentication. + * + * If not both `username` and `password` are provided, authentication is disabled. */ private const val BULK_REQUEST_SIZE = 128 @@ -53,9 +67,15 @@ private const val BULK_REQUEST_SIZE = 128 * A wrapper for Sonatype's [OSS Index](https://ossindex.sonatype.org/) security vulnerability data. */ class OssIndex(name: String, config: OssIndexConfiguration) : AdviceProvider(name) { - class Factory : AbstractAdviceProviderFactory("OssIndex") { - override fun create(config: AdvisorConfiguration) = - OssIndex(type, config.forProvider { ossIndex ?: OssIndexConfiguration() }) + class Factory : AdviceProviderFactory("OssIndex") { + override fun create(config: OssIndexConfiguration) = OssIndex(type, config) + + override fun parseConfig(options: Options, secrets: Options) = + OssIndexConfiguration( + serverUrl = options["serverUrl"], + username = secrets["username"], + password = secrets["password"] + ) } override val details = AdvisorDetails(providerName, enumSetOf(AdvisorCapability.VULNERABILITIES)) diff --git a/advisor/src/main/kotlin/advisors/OssIndexConfiguration.kt b/advisor/src/main/kotlin/advisors/OssIndexConfiguration.kt new file mode 100644 index 0000000000000..baeef9c48142d --- /dev/null +++ b/advisor/src/main/kotlin/advisors/OssIndexConfiguration.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 The ORT Project Authors (see ) + * + * 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 + * + * https://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.advisors + +/** + * The configuration for the OSS Index provider. + */ +data class OssIndexConfiguration( + /** + * The base URL of the OSS Index REST API. If undefined, default base URL for the REST API of the public OSS Index + * service. + */ + val serverUrl: String? = null, + + /** + * The username to use for authentication. If not both [username] and [password] are provided, authentication is + * disabled. + */ + val username: String? = null, + + /** + * The password to use for authentication. If not both [username] and [password] are provided, authentication is + * disabled. + */ + val password: String? = null +) diff --git a/advisor/src/main/kotlin/advisors/Osv.kt b/advisor/src/main/kotlin/advisors/Osv.kt index 684d47fcde739..7e12d61519d90 100644 --- a/advisor/src/main/kotlin/advisors/Osv.kt +++ b/advisor/src/main/kotlin/advisors/Osv.kt @@ -26,8 +26,8 @@ import kotlinx.serialization.json.contentOrNull import org.apache.logging.log4j.kotlin.logger -import org.ossreviewtoolkit.advisor.AbstractAdviceProviderFactory import org.ossreviewtoolkit.advisor.AdviceProvider +import org.ossreviewtoolkit.advisor.AdviceProviderFactory import org.ossreviewtoolkit.clients.osv.Ecosystem import org.ossreviewtoolkit.clients.osv.OsvService import org.ossreviewtoolkit.clients.osv.Severity @@ -40,8 +40,8 @@ import org.ossreviewtoolkit.model.AdvisorSummary import org.ossreviewtoolkit.model.Identifier import org.ossreviewtoolkit.model.Package import org.ossreviewtoolkit.model.VulnerabilityReference -import org.ossreviewtoolkit.model.config.AdvisorConfiguration -import org.ossreviewtoolkit.model.config.OsvConfiguration +import org.ossreviewtoolkit.model.config.PluginConfiguration +import org.ossreviewtoolkit.utils.common.Options import org.ossreviewtoolkit.utils.common.collectMessages import org.ossreviewtoolkit.utils.common.enumSetOf import org.ossreviewtoolkit.utils.common.toUri @@ -51,12 +51,20 @@ import us.springett.cvss.Cvss /** * An advice provider that obtains vulnerability information from Open Source Vulnerabilities (https://osv.dev/). + * + * This [AdviceProvider] offers the following configuration options: + * + * #### [Options][PluginConfiguration.options] + * + * * **`serverUrl`:** The base URL of the OSV REST API. If undefined, default is the production endpoint of the official + * OSV.dev API. */ class Osv(name: String, config: OsvConfiguration) : AdviceProvider(name) { - class Factory : AbstractAdviceProviderFactory("OSV") { - override fun create(config: AdvisorConfiguration) = - // OSV does not require any dedicated configuration to be present. - Osv(type, config.forProvider { osv ?: OsvConfiguration() }) + class Factory : AdviceProviderFactory("OSV") { + override fun create(config: OsvConfiguration) = Osv(type, config) + + override fun parseConfig(options: Options, secrets: Options) = + OsvConfiguration(serverUrl = options["serverUrl"]) } override val details: AdvisorDetails = AdvisorDetails(providerName, enumSetOf(AdvisorCapability.VULNERABILITIES)) diff --git a/advisor/src/main/kotlin/advisors/OsvConfiguration.kt b/advisor/src/main/kotlin/advisors/OsvConfiguration.kt new file mode 100644 index 0000000000000..ebeb1697d55be --- /dev/null +++ b/advisor/src/main/kotlin/advisors/OsvConfiguration.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 The ORT Project Authors (see ) + * + * 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 + * + * https://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.advisors + +/** + * The configuration for the Google OSV vulnerability provider. + */ +data class OsvConfiguration( + /** + * The base URL of the OSV REST API. If undefined, default is the production endpoint of the official OSV.dev API. + */ + val serverUrl: String? = null +) diff --git a/advisor/src/main/kotlin/advisors/VulnerableCode.kt b/advisor/src/main/kotlin/advisors/VulnerableCode.kt index 03ecfe440a04d..63d51205753ff 100644 --- a/advisor/src/main/kotlin/advisors/VulnerableCode.kt +++ b/advisor/src/main/kotlin/advisors/VulnerableCode.kt @@ -22,8 +22,8 @@ package org.ossreviewtoolkit.advisor.advisors import java.net.URI import java.time.Instant -import org.ossreviewtoolkit.advisor.AbstractAdviceProviderFactory import org.ossreviewtoolkit.advisor.AdviceProvider +import org.ossreviewtoolkit.advisor.AdviceProviderFactory import org.ossreviewtoolkit.clients.vulnerablecode.VulnerableCodeService import org.ossreviewtoolkit.clients.vulnerablecode.VulnerableCodeService.PackagesWrapper import org.ossreviewtoolkit.model.AdvisorCapability @@ -35,10 +35,10 @@ import org.ossreviewtoolkit.model.Package import org.ossreviewtoolkit.model.Severity import org.ossreviewtoolkit.model.Vulnerability import org.ossreviewtoolkit.model.VulnerabilityReference -import org.ossreviewtoolkit.model.config.AdvisorConfiguration -import org.ossreviewtoolkit.model.config.VulnerableCodeConfiguration +import org.ossreviewtoolkit.model.config.PluginConfiguration import org.ossreviewtoolkit.model.createAndLogIssue import org.ossreviewtoolkit.model.utils.toPurl +import org.ossreviewtoolkit.utils.common.Options import org.ossreviewtoolkit.utils.common.collectMessages import org.ossreviewtoolkit.utils.common.enumSetOf import org.ossreviewtoolkit.utils.ort.OkHttpClientHelper @@ -52,10 +52,27 @@ private const val BULK_REQUEST_SIZE = 100 /** * An [AdviceProvider] implementation that obtains security vulnerability information from a * [VulnerableCode][https://github.com/nexB/vulnerablecode] instance. + * + * This [AdviceProvider] offers the following configuration options: + * + * #### [Options][PluginConfiguration.options] + * + * * **`serverUrl`:** The base URL of the VulnerableCode REST API. By default, the public VulnerableCode instance is + * used. + * + * #### [Secrets][PluginConfiguration.secrets] + * + * * **`apiKey`:** The optional API key to use. */ class VulnerableCode(name: String, config: VulnerableCodeConfiguration) : AdviceProvider(name) { - class Factory : AbstractAdviceProviderFactory("VulnerableCode") { - override fun create(config: AdvisorConfiguration) = VulnerableCode(type, config.forProvider { vulnerableCode }) + class Factory : AdviceProviderFactory("VulnerableCode") { + override fun create(config: VulnerableCodeConfiguration) = VulnerableCode(type, config) + + override fun parseConfig(options: Options, secrets: Options) = + VulnerableCodeConfiguration( + serverUrl = options["serverUrl"], + apiKey = secrets["apiKey"] + ) } /** diff --git a/advisor/src/main/kotlin/advisors/VulnerableCodeConfiguration.kt b/advisor/src/main/kotlin/advisors/VulnerableCodeConfiguration.kt new file mode 100644 index 0000000000000..c4b00f29d2ee1 --- /dev/null +++ b/advisor/src/main/kotlin/advisors/VulnerableCodeConfiguration.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2021 The ORT Project Authors (see ) + * + * 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 + * + * https://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.advisors + +/** + * The configuration for VulnerableCode as security vulnerability provider. + */ +data class VulnerableCodeConfiguration( + /** + * The base URL of the VulnerableCode REST API. By default, the public VulnerableCode instance is used. + */ + val serverUrl: String? = null, + + /** + * The optional API key to use. + */ + val apiKey: String? = null +) diff --git a/advisor/src/test/kotlin/AbstractAdviceProviderFactoryTest.kt b/advisor/src/test/kotlin/AbstractAdviceProviderFactoryTest.kt deleted file mode 100644 index 2e6f5a367d280..0000000000000 --- a/advisor/src/test/kotlin/AbstractAdviceProviderFactoryTest.kt +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2021 The ORT Project Authors (see ) - * - * 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 - * - * https://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 io.kotest.assertions.throwables.shouldThrow -import io.kotest.core.spec.style.WordSpec -import io.kotest.matchers.maps.beEmpty -import io.kotest.matchers.should -import io.kotest.matchers.shouldBe -import io.kotest.matchers.string.shouldContain - -import org.ossreviewtoolkit.advisor.advisors.VulnerableCode -import org.ossreviewtoolkit.model.config.AdvisorConfiguration -import org.ossreviewtoolkit.model.config.VulnerableCodeConfiguration - -class AbstractAdviceProviderFactoryTest : WordSpec({ - "forProvider" should { - "return the configuration for the selected provider" { - val advisorConfig = AdvisorConfiguration(vulnerableCode = VULNERABLE_CODE_CONFIG) - - val factory = object : AbstractAdviceProviderFactory(PROVIDER_NAME) { - override fun create(config: AdvisorConfiguration): AdviceProvider { - config.forProvider { vulnerableCode } shouldBe VULNERABLE_CODE_CONFIG - return VulnerableCode(type, VULNERABLE_CODE_CONFIG) - } - } - - factory.create(advisorConfig) - } - - "throw an exception if no configuration for the selected provider is defined" { - val factory = object : AbstractAdviceProviderFactory(PROVIDER_NAME) { - override fun create(config: AdvisorConfiguration): AdviceProvider { - val exception = shouldThrow { - config.forProvider { vulnerableCode } - } - - exception.message shouldContain PROVIDER_NAME - - return VulnerableCode(type, VULNERABLE_CODE_CONFIG) - } - } - - factory.create(AdvisorConfiguration()) - } - } - - "providerOptions" should { - "return the specific options for the selected provider" { - val providerOptions = mapOf("foo" to "bar") - val advisorConfig = AdvisorConfiguration(options = mapOf(PROVIDER_NAME to providerOptions)) - - val factory = object : AbstractAdviceProviderFactory(PROVIDER_NAME) { - override fun create(config: AdvisorConfiguration): AdviceProvider { - config.providerOptions() shouldBe providerOptions - - return VulnerableCode(type, VULNERABLE_CODE_CONFIG) - } - } - - factory.create(advisorConfig) - } - - "return an empty map if no options for the selected provider are available" { - val options = mapOf("anotherProvider" to mapOf("someOption" to "someValue")) - val advisorConfig = AdvisorConfiguration(options = options) - - val factory = object : AbstractAdviceProviderFactory(PROVIDER_NAME) { - override fun create(config: AdvisorConfiguration): AdviceProvider { - config.providerOptions() should beEmpty() - - return VulnerableCode(type, VULNERABLE_CODE_CONFIG) - } - } - - factory.create(advisorConfig) - } - - "return an empty map if no options are defined at all" { - val factory = object : AbstractAdviceProviderFactory(PROVIDER_NAME) { - override fun create(config: AdvisorConfiguration): AdviceProvider { - config.providerOptions() should beEmpty() - - return VulnerableCode(type, VULNERABLE_CODE_CONFIG) - } - } - - factory.create(AdvisorConfiguration()) - } - } -}) - -private const val PROVIDER_NAME = "testAdviceProvider" - -private val VULNERABLE_CODE_CONFIG = VulnerableCodeConfiguration("https://example.org/vc", "") diff --git a/advisor/src/test/kotlin/AdvisorTest.kt b/advisor/src/test/kotlin/AdvisorTest.kt index 34fde3d89e496..5b433a987f8ae 100644 --- a/advisor/src/test/kotlin/AdvisorTest.kt +++ b/advisor/src/test/kotlin/AdvisorTest.kt @@ -117,8 +117,8 @@ private fun createAdvisor(providers: List): Advisor { val advisorConfig = AdvisorConfiguration() val factories = providers.map { provider -> - val factory = mockk() - every { factory.create(advisorConfig) } returns provider + val factory = mockk>() + every { factory.create(emptyMap(), emptyMap()) } returns provider factory } diff --git a/advisor/src/test/kotlin/advisors/GitHubDefectsTest.kt b/advisor/src/test/kotlin/advisors/GitHubDefectsTest.kt index c901e2e8c3d45..733f289dfafe5 100644 --- a/advisor/src/test/kotlin/advisors/GitHubDefectsTest.kt +++ b/advisor/src/test/kotlin/advisors/GitHubDefectsTest.kt @@ -58,8 +58,6 @@ import org.ossreviewtoolkit.model.Package import org.ossreviewtoolkit.model.Severity import org.ossreviewtoolkit.model.VcsInfo import org.ossreviewtoolkit.model.VcsType -import org.ossreviewtoolkit.model.config.AdvisorConfiguration -import org.ossreviewtoolkit.model.config.GitHubDefectsConfiguration import org.ossreviewtoolkit.utils.common.enumSetOf class GitHubDefectsTest : WordSpec({ @@ -431,10 +429,9 @@ private fun createAdvisor( val githubConfig = GitHubDefectsConfiguration(token = GITHUB_TOKEN, endpointUrl = url) .run { labelFilter?.let { copy(labelFilter = it) } ?: this } .run { maxDefectsCount?.let { copy(maxNumberOfIssuesPerRepository = it) } ?: this } - val advisorConfig = AdvisorConfiguration(gitHubDefects = githubConfig) val factory = GitHubDefects.Factory() - return factory.create(advisorConfig) + return factory.create(githubConfig) } /** diff --git a/advisor/src/test/kotlin/advisors/OssIndexTest.kt b/advisor/src/test/kotlin/advisors/OssIndexTest.kt index 2b16f331518e4..6ba5dd61ee4f5 100644 --- a/advisor/src/test/kotlin/advisors/OssIndexTest.kt +++ b/advisor/src/test/kotlin/advisors/OssIndexTest.kt @@ -45,7 +45,6 @@ import org.ossreviewtoolkit.model.Package import org.ossreviewtoolkit.model.Severity import org.ossreviewtoolkit.model.Vulnerability import org.ossreviewtoolkit.model.VulnerabilityReference -import org.ossreviewtoolkit.model.config.OssIndexConfiguration import org.ossreviewtoolkit.model.utils.toPurl import org.ossreviewtoolkit.utils.common.enumSetOf import org.ossreviewtoolkit.utils.test.shouldNotBeNull diff --git a/advisor/src/test/kotlin/advisors/VulnerableCodeTest.kt b/advisor/src/test/kotlin/advisors/VulnerableCodeTest.kt index 4311daa4fb7e7..9d1f7a5771304 100644 --- a/advisor/src/test/kotlin/advisors/VulnerableCodeTest.kt +++ b/advisor/src/test/kotlin/advisors/VulnerableCodeTest.kt @@ -48,7 +48,6 @@ import org.ossreviewtoolkit.model.Package import org.ossreviewtoolkit.model.Severity import org.ossreviewtoolkit.model.Vulnerability import org.ossreviewtoolkit.model.VulnerabilityReference -import org.ossreviewtoolkit.model.config.VulnerableCodeConfiguration import org.ossreviewtoolkit.model.readValue import org.ossreviewtoolkit.model.utils.toPurl import org.ossreviewtoolkit.utils.common.enumSetOf diff --git a/cli/src/funTest/assets/semver4j-ort-result.yml b/cli/src/funTest/assets/semver4j-ort-result.yml index 74fef7629711c..417280f0ab30d 100644 --- a/cli/src/funTest/assets/semver4j-ort-result.yml +++ b/cli/src/funTest/assets/semver4j-ort-result.yml @@ -373,10 +373,13 @@ advisor: GOPATH: "/go" tool_versions: {} config: - nexus_iq: - server_url: "https://oss-review-toolkit.org" - browse_url: "https://oss-review-toolkit.org" - username: "user" + config: + NexusIQ: + options: + server_url: "https://oss-review-toolkit.org" + browse_url: "https://oss-review-toolkit.org" + secrets: + username: "user" results: advisor_results: Maven:junit:junit:4.12: diff --git a/integrations/schemas/ort-configuration-schema.json b/integrations/schemas/ort-configuration-schema.json index 4d79bb5479326..b6931b32fed61 100644 --- a/integrations/schemas/ort-configuration-schema.json +++ b/integrations/schemas/ort-configuration-schema.json @@ -51,133 +51,17 @@ "type": "object", "additionalProperties": false, "properties": { - "nexusIq": { - "$ref": "#/definitions/NexusIq" - }, - "ossIndex": { - "$ref": "#/definitions/OssIndex" - }, - "vulnerableCode": { - "$ref": "#/definitions/VulnerableCode" - }, - "gitHubDefects": { - "$ref": "#/definitions/GitHubDefects" - }, - "osv": { - "$ref": "#/definitions/Osv" - }, - "options": { - "$ref": "#/definitions/AdvisorOptions" - } - } - }, - "GitHubDefects": { - "type": "object", - "additionalProperties": false, - "properties": { - "token": { - "type": "string" - }, - "labelFilter": { - "type": "array", - "items": { - "type": "string" - } - }, - "maxNumberOfIssuesPerRepository": { - "type": "integer" - }, - "parallelRequests": { - "type": "integer" - } - }, - "required": [ - "labelFilter" - ] - }, - "NexusIq": { - "type": "object", - "additionalProperties": false, - "properties": { - "serverUrl": { - "type": "string", - "format": "uri" - }, - "browseUrl": { - "type": "string", - "format": "uri" - }, - "username": { - "type": "string" - }, - "password": { - "type": "string" - } - }, - "required": [ - "serverUrl" - ] - }, - "OssIndex": { - "type": "object", - "additionalProperties": false, - "properties": { - "serverUrl": { - "type": "string", - "format": "uri" - }, - "username": { - "type": "string" - }, - "password": { - "type": "string" + "config": { + "$ref": "#/definitions/AdvisorConfig" } } }, - "AdvisorOptions": { + "AdvisorConfig": { "type": "object", "additionalProperties": { "type": "object" } }, - "CustomAdvisor": { - "type": "object", - "additionalProperties": false, - "properties": { - "apiKey": { - "type": "string" - } - }, - "required": [ - "apiKey" - ] - }, - "Osv": { - "type": "object", - "additionalProperties": false, - "properties": { - "serverUrl": { - "type": "string", - "format": "uri" - } - } - }, - "VulnerableCode": { - "type": "object", - "additionalProperties": false, - "properties": { - "serverUrl": { - "type": "string", - "format": "uri" - }, - "apiKey": { - "type": "string" - } - }, - "required": [ - "serverUrl" - ] - }, "Analyzer": { "$ref": "https://raw.githubusercontent.com/oss-review-toolkit/ort/main/integrations/schemas/analyzer-configuration-schema.json" }, diff --git a/model/src/main/kotlin/config/AdvisorConfiguration.kt b/model/src/main/kotlin/config/AdvisorConfiguration.kt index d9634c6fb412c..482ec1cf003a3 100644 --- a/model/src/main/kotlin/config/AdvisorConfiguration.kt +++ b/model/src/main/kotlin/config/AdvisorConfiguration.kt @@ -20,161 +20,17 @@ package org.ossreviewtoolkit.model.config import com.fasterxml.jackson.annotation.JsonInclude -import com.fasterxml.jackson.annotation.JsonProperty -import org.ossreviewtoolkit.utils.common.Options +import org.ossreviewtoolkit.utils.common.Plugin /** * The base configuration model of the advisor. */ @JsonInclude(JsonInclude.Include.NON_NULL) data class AdvisorConfiguration( - val gitHubDefects: GitHubDefectsConfiguration? = null, - val nexusIq: NexusIqConfiguration? = null, - val ossIndex: OssIndexConfiguration? = null, - val osv: OsvConfiguration? = null, - val vulnerableCode: VulnerableCodeConfiguration? = null, - - /** - * A map with generic options for advice providers using the provider name as key. While the advice providers - * shipped with ORT can access their configuration in a type-safe way via the other properties in this class, - * this map offers a way for external advisor plugins to query configuration information. - */ - @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) - val options: Map? = null -) - -/** - * The configuration for the GitHub Defects advisor. - */ -data class GitHubDefectsConfiguration( - /** - * The access token to authenticate against the GitHub GraphQL endpoint. - */ - @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) - val token: String? = null, - - /** - * The URL of the GraphQL endpoint to be accessed by the service. If undefined, default is the endpoint of the - * official GitHub GraphQL API. - */ - @JsonInclude(JsonInclude.Include.NON_DEFAULT) - val endpointUrl: String? = null, - - /** - * A list with labels to be used for filtering GitHub issues. With GitHub's data model for issues, it is not - * possible to determine whether a specific issue is actually a defect or something else, e.g. a feature request. - * Via this property, it is possible to limit the issues retrieved by the GitHub defects advisor by filtering for - * specific label values. The filtering works as follows: - * - Each string in this list refers to a label to be matched. The strings are processed in order. - * - If for an issue a label with the name of the current string is found, the issue is included into the result - * set. - * - If the current string starts with one of the characters '-' or '!', it defines an exclusion. So, if an issue - * contains a label named like the current string with the first character removed, this issue is not added to - * the result set, and filtering stops here. (The ordered processing resolves conflicting filters, as the first - * match wins.) - * - Label name matches are case-insensitive. - * - Wildcards are supported; a "*" matches arbitrary characters. - * - If the end of the list is reached and no match was found, the issue is not added to the result set. In order - * to have all issues included for which no specific exclusion was found, a wildcard match "*" can be added at - * the end. - * Per default, some of GitHub's default labels are excluded that typically indicate that an issue is not a defect - * (see https://docs.github.com/en/issues/using-labels-and-milestones-to-track-work/managing-labels#about-default-labels) - */ - @JsonInclude(JsonInclude.Include.NON_DEFAULT) - val labelFilter: List = listOf("!duplicate", "!enhancement", "!invalid", "!question", "*"), - - /** - * The maximum number of defects that are retrieved from a single repository. If a repository contains more - * issues, only this number is returned (the newest ones). Popular libraries hosted on GitHub can really have a - * large number of issues; therefore, it makes sense to restrict the result set produced by this advisor. - */ - val maxNumberOfIssuesPerRepository: Int? = null, - - /** - * Determines the number of requests to the GitHub GraphQL API that are executed in parallel. Rather than querying - * each repository one after the other, fetching the data of multiple repositories concurrently can reduce the - * execution times for this advisor implementation. If unspecified, a default value for parallel executions as - * defined in the _GitHubDefects_ class is used. - */ - val parallelRequests: Int? = null -) - -/** - * The configuration for Nexus IQ as a security vulnerability provider. - */ -data class NexusIqConfiguration( - /** - * The URL to use for REST API requests against the server. - */ - val serverUrl: String, - - /** - * A URL to use as a base for browsing vulnerability details. Defaults to the server URL. - */ - @JsonInclude(JsonInclude.Include.NON_DEFAULT) - val browseUrl: String = serverUrl, - - /** - * The username to use for authentication. If not both [username] and [password] are provided, authentication is - * disabled. - */ - val username: String? = null, - - /** - * The password to use for authentication. If not both [username] and [password] are provided, authentication is - * disabled. - */ - @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) - val password: String? = null -) - -/** - * The configuration for the OSS Index provider. - */ -data class OssIndexConfiguration( - /** - * The base URL of the OSS Index REST API. If undefined, default base URL for the REST API of the public OSS Index - * service. - */ - val serverUrl: String? = null, - - /** - * The username to use for authentication. If not both [username] and [password] are provided, authentication is - * disabled. - */ - val username: String? = null, - - /** - * The password to use for authentication. If not both [username] and [password] are provided, authentication is - * disabled. - */ - @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) - val password: String? = null -) - -/** - * The configuration for the Google OSV vulnerability provider. - */ -data class OsvConfiguration( - /** - * The base URL of the OSV REST API. If undefined, default is the production endpoint of the official OSV.dev API. - */ - val serverUrl: String? = null -) - -/** - * The configuration for VulnerableCode as security vulnerability provider. - */ -data class VulnerableCodeConfiguration( - /** - * The base URL of the VulnerableCode REST API. By default, the public VulnerableCode instance is used. - */ - val serverUrl: String? = null, - /** - * The optional API key to use. + * A map with [configuration][PluginConfiguration] for advice providers using the [provider type][Plugin.type] as + * key. */ - @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) - val apiKey: String? = null + val config: Map? = null ) diff --git a/model/src/main/resources/reference.yml b/model/src/main/resources/reference.yml index 899ba0c2a3052..4d0bdd258fe62 100644 --- a/model/src/main/resources/reference.yml +++ b/model/src/main/resources/reference.yml @@ -114,34 +114,40 @@ ort: lockfileName: "lockfile.lock" advisor: - nexusIq: - serverUrl: 'https://rest-api-url-of-your-nexus-iq-server' - browseUrl: 'https://web-browsing-url-of-your-nexus-iq-server' - username: username - password: password - - ossIndex: - username: username - password: password + config: + GitHubDefects: + options: + endpointUrl: 'https://api.github.com/graphql' + labelFilter: '!duplicate, !enhancement, !invalid, !question, !documentation, *' + maxNumberOfIssuesPerRepository: 50 + parallelRequests: 5 + secrets: + token: githubAccessToken - vulnerableCode: - serverUrl: 'http://localhost:8000' - apiKey: 0123456789012345678901234567890123456789 + NexusIQ: + options: + serverUrl: 'https://rest-api-url-of-your-nexus-iq-server' + browseUrl: 'https://web-browsing-url-of-your-nexus-iq-server' + secrets: + username: username + password: password - gitHubDefects: - token: githubAccessToken - labelFilter: ['!duplicate', '!enhancement', '!invalid', '!question', '!documentation', '*'] - maxNumberOfIssuesPerRepository: 50 - parallelRequests: 5 + OssIndex: + options: + serverUrl: 'https://ossindex.sonatype.org/' + secrets: + username: username + password: password - osv: - serverUrl: 'https://api.osv.dev' + OSV: + options: + serverUrl: 'https://api.osv.dev' - options: - # A map of maps from advice provider names to provider-specific key-value pairs. This map can be used to provide - # configuration information for external advisor plugins. - CustomAdvisor: - apiKey: '' + VulnerableCode: + options: + serverUrl: 'http://localhost:8000' + secrets: + apiKey: 0123456789012345678901234567890123456789 downloader: allowMovingRevisions: true diff --git a/model/src/test/kotlin/config/AdvisorConfigurationTest.kt b/model/src/test/kotlin/config/AdvisorConfigurationTest.kt deleted file mode 100644 index d50515111d0e4..0000000000000 --- a/model/src/test/kotlin/config/AdvisorConfigurationTest.kt +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2020 The ORT Project Authors (see ) - * - * 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 - * - * https://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.model.config - -import io.kotest.core.spec.style.WordSpec -import io.kotest.matchers.nulls.shouldBeNull -import io.kotest.matchers.nulls.shouldNotBeNull -import io.kotest.matchers.shouldBe - -import java.io.File - -import org.ossreviewtoolkit.model.fromYaml -import org.ossreviewtoolkit.model.toYaml - -class AdvisorConfigurationTest : WordSpec({ - "Generic advisor options" should { - "not be serialized as they might contain sensitive information" { - rereadAdvisorConfig(loadAdvisorConfig()).options.shouldBeNull() - } - } - - "GitHubDefectsConfiguration" should { - "support a serialization round-trip via an ObjectMapper" { - val originalConfig = loadAdvisorConfig() - val rereadConfig = rereadAdvisorConfig(originalConfig) - - val expectedGHConfig = originalConfig.gitHubDefects.shouldNotBeNull() - val actualGHConfig = rereadConfig.gitHubDefects.shouldNotBeNull() - - actualGHConfig.endpointUrl shouldBe expectedGHConfig.endpointUrl - actualGHConfig.labelFilter shouldBe expectedGHConfig.labelFilter - actualGHConfig.maxNumberOfIssuesPerRepository shouldBe expectedGHConfig.maxNumberOfIssuesPerRepository - actualGHConfig.parallelRequests shouldBe expectedGHConfig.parallelRequests - } - - "not serialize credentials" { - val rereadConfig = rereadAdvisorConfig(loadAdvisorConfig()).gitHubDefects.shouldNotBeNull() - - rereadConfig.token.shouldBeNull() - } - } - - "NexusIqConfiguration" should { - "support a serialization round-trip via an ObjectMapper" { - val originalConfig = loadAdvisorConfig() - val rereadConfig = rereadAdvisorConfig(originalConfig) - - val expectedNexusIqConfig = originalConfig.nexusIq.shouldNotBeNull() - val actualNexusIqConfiguration = rereadConfig.nexusIq.shouldNotBeNull() - - actualNexusIqConfiguration.serverUrl shouldBe expectedNexusIqConfig.serverUrl - actualNexusIqConfiguration.username shouldBe expectedNexusIqConfig.username - } - - "not serialize credentials" { - val rereadConfig = rereadAdvisorConfig(loadAdvisorConfig()).nexusIq.shouldNotBeNull() - - rereadConfig.password.shouldBeNull() - } - } - - "VulnerableCodeConfiguration" should { - "support a serialization round-trip via an ObjectMapper" { - val originalConfig = loadAdvisorConfig() - val rereadConfig = rereadAdvisorConfig(originalConfig) - - val expectedVCConfig = originalConfig.vulnerableCode.shouldNotBeNull() - val actualVCConfig = rereadConfig.vulnerableCode.shouldNotBeNull() - - actualVCConfig.serverUrl shouldBe expectedVCConfig.serverUrl - } - - "not serialize credentials" { - val rereadConfig = rereadAdvisorConfig(loadAdvisorConfig()).vulnerableCode.shouldNotBeNull() - - rereadConfig.apiKey.shouldBeNull() - } - } -}) - -/** - * Load the ORT reference configuration and extract the advisor configuration. - */ -private fun loadAdvisorConfig(): AdvisorConfiguration = - OrtConfiguration.load(file = File("src/main/resources/$REFERENCE_CONFIG_FILENAME")).advisor - -/** - * Perform a serialization round-trip of the given advisor [config] and return the result. This is used to check - * whether serialization and deserialization of advisor configurations work as expected. - */ -private fun rereadAdvisorConfig(config: AdvisorConfiguration): AdvisorConfiguration = config.toYaml().fromYaml() diff --git a/model/src/test/kotlin/config/OrtConfigurationTest.kt b/model/src/test/kotlin/config/OrtConfigurationTest.kt index 0da0b82bdca92..38a0bdec84f37 100644 --- a/model/src/test/kotlin/config/OrtConfigurationTest.kt +++ b/model/src/test/kotlin/config/OrtConfigurationTest.kt @@ -125,38 +125,58 @@ class OrtConfigurationTest : WordSpec({ } with(ortConfig.advisor) { - nexusIq shouldNotBeNull { - serverUrl shouldBe "https://rest-api-url-of-your-nexus-iq-server" - browseUrl shouldBe "https://web-browsing-url-of-your-nexus-iq-server" - username shouldBe "username" - password shouldBe "password" - } + config shouldNotBeNull { + get("GitHubDefects") shouldNotBeNull { + options shouldContainExactly mapOf( + "endpointUrl" to "https://api.github.com/graphql", + "labelFilter" to "!duplicate, !enhancement, !invalid, !question, !documentation, *", + "maxNumberOfIssuesPerRepository" to "50", + "parallelRequests" to "5" + ) - vulnerableCode shouldNotBeNull { - serverUrl shouldBe "http://localhost:8000" - apiKey shouldBe "0123456789012345678901234567890123456789" - } + secrets shouldContainExactly mapOf( + "token" to "githubAccessToken" + ) + } - gitHubDefects shouldNotBeNull { - token shouldBe "githubAccessToken" - labelFilter shouldContainExactlyInAnyOrder listOf( - "!duplicate", - "!enhancement", - "!invalid", - "!question", - "!documentation", - "*" - ) - maxNumberOfIssuesPerRepository shouldBe 50 - parallelRequests shouldBe 5 - } + get("NexusIQ") shouldNotBeNull { + options shouldContainExactly mapOf( + "serverUrl" to "https://rest-api-url-of-your-nexus-iq-server", + "browseUrl" to "https://web-browsing-url-of-your-nexus-iq-server" + ) - osv shouldNotBeNull { - serverUrl shouldBe "https://api.osv.dev" - } + secrets shouldContainExactly mapOf( + "username" to "username", + "password" to "password" + ) + } - options shouldNotBeNull { - this["CustomAdvisor"]?.get("apiKey") shouldBe "" + get("OssIndex") shouldNotBeNull { + options shouldContainExactly mapOf( + "serverUrl" to "https://ossindex.sonatype.org/" + ) + + secrets shouldContainExactly mapOf( + "username" to "username", + "password" to "password" + ) + } + + get("OSV") shouldNotBeNull { + options shouldContainExactly mapOf( + "serverUrl" to "https://api.osv.dev" + ) + } + + get("VulnerableCode") shouldNotBeNull { + options shouldContainExactly mapOf( + "serverUrl" to "http://localhost:8000" + ) + + secrets shouldContainExactly mapOf( + "apiKey" to "0123456789012345678901234567890123456789" + ) + } } } diff --git a/plugins/reporters/evaluated-model/src/funTest/assets/reporter-test-input.yml b/plugins/reporters/evaluated-model/src/funTest/assets/reporter-test-input.yml index 0ec46d5b5f487..5be1022ff5bb9 100644 --- a/plugins/reporters/evaluated-model/src/funTest/assets/reporter-test-input.yml +++ b/plugins/reporters/evaluated-model/src/funTest/assets/reporter-test-input.yml @@ -689,12 +689,14 @@ advisor: variables: {} tool_versions: {} config: - nexus_iq: - server_url: "https://rest-api-url-of-your-nexus-iq-server" - browse_url: "https://web-browsing-url-of-your-nexus-iq-server" - username: "username" - vulnerable_code: - server_url: "http://localhost:8000" + config: + NexusIQ: + options: + server_url: "https://rest-api-url-of-your-nexus-iq-server" + browse_url: "https://web-browsing-url-of-your-nexus-iq-server" + Vulnerable_code: + options: + server_url: "http://localhost:8000" results: advisor_results: Maven:org.apache.commons:commons-text:1.1: diff --git a/plugins/reporters/opossum/src/funTest/assets/reporter-test-input.yml b/plugins/reporters/opossum/src/funTest/assets/reporter-test-input.yml index 718f04d76c598..586ba842c7b9b 100644 --- a/plugins/reporters/opossum/src/funTest/assets/reporter-test-input.yml +++ b/plugins/reporters/opossum/src/funTest/assets/reporter-test-input.yml @@ -685,12 +685,14 @@ advisor: variables: {} tool_versions: {} config: - nexus_iq: - server_url: "https://rest-api-url-of-your-nexus-iq-server" - browse_url: "https://web-browsing-url-of-your-nexus-iq-server" - username: "username" - vulnerable_code: - server_url: "http://localhost:8000" + config: + NexusIQ: + options: + server_url: "https://rest-api-url-of-your-nexus-iq-server" + browse_url: "https://web-browsing-url-of-your-nexus-iq-server" + Vulnerable_code: + options: + server_url: "http://localhost:8000" results: advisor_results: Maven:org.apache.commons:commons-text:1.1: diff --git a/plugins/reporters/static-html/src/funTest/assets/reporter-test-input.yml b/plugins/reporters/static-html/src/funTest/assets/reporter-test-input.yml index ed21db0b2c3c6..e61ced2a3ef74 100644 --- a/plugins/reporters/static-html/src/funTest/assets/reporter-test-input.yml +++ b/plugins/reporters/static-html/src/funTest/assets/reporter-test-input.yml @@ -685,12 +685,14 @@ advisor: variables: {} tool_versions: {} config: - nexus_iq: - server_url: "https://rest-api-url-of-your-nexus-iq-server" - browse_url: "https://web-browsing-url-of-your-nexus-iq-server" - username: "username" - vulnerable_code: - server_url: "http://localhost:8000" + config: + NexusIQ: + options: + server_url: "https://rest-api-url-of-your-nexus-iq-server" + browse_url: "https://web-browsing-url-of-your-nexus-iq-server" + Vulnerable_code: + options: + server_url: "http://localhost:8000" results: advisor_results: Maven:org.apache.commons:commons-text:1.1: