All Downloads are FREE. Search and download functionalities are using the official Maven repository.

toolkit.evaluator.36.0.0.source-code.PackageRule.kt Maven / Gradle / Ivy

Go to download

Part of the OSS Review Toolkit (ORT), a suite to automate software compliance checks.

There is a newer version: 40.0.1
Show newest version
/*
 * Copyright (C) 2017 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.evaluator

import org.ossreviewtoolkit.model.CuratedPackage
import org.ossreviewtoolkit.model.Identifier
import org.ossreviewtoolkit.model.LicenseSource
import org.ossreviewtoolkit.model.Package
import org.ossreviewtoolkit.model.Project
import org.ossreviewtoolkit.model.Severity
import org.ossreviewtoolkit.model.config.Excludes
import org.ossreviewtoolkit.model.licenses.LicenseView
import org.ossreviewtoolkit.model.licenses.ResolvedLicense
import org.ossreviewtoolkit.model.licenses.ResolvedLicenseInfo
import org.ossreviewtoolkit.model.vulnerabilities.Cvss2Rating
import org.ossreviewtoolkit.model.vulnerabilities.Cvss3Rating
import org.ossreviewtoolkit.model.vulnerabilities.Cvss4Rating
import org.ossreviewtoolkit.model.vulnerabilities.Vulnerability
import org.ossreviewtoolkit.model.vulnerabilities.VulnerabilityReference
import org.ossreviewtoolkit.utils.spdx.SpdxExpression
import org.ossreviewtoolkit.utils.spdx.SpdxLicenseReferenceExpression

/**
 * A [Rule] to check a single [Package].
 */
open class PackageRule(
    ruleSet: RuleSet,
    name: String,

    /**
     * The [CuratedPackage] to check.
     */
    val pkg: CuratedPackage,

    /**
     * The resolved license info for the [Package].
     */
    val resolvedLicenseInfo: ResolvedLicenseInfo
) : Rule(ruleSet, name) {
    private val licenseRules = mutableListOf()

    @Suppress("UNUSED") // This is intended to be used by rule implementations.
    val uncuratedPkg: Package by lazy {
        @Suppress("UnsafeCallOnNullableType")
        ruleSet.ortResult.getUncuratedPackageOrProject(pkg.metadata.id)!!
    }

    override val description = "Evaluating rule '$name' for package '${pkg.metadata.id.toCoordinates()}'."

    override fun issueSource() = "$name - ${pkg.metadata.id.toCoordinates()}"

    override fun runInternal() {
        licenseRules.forEach { it.evaluate() }
    }

    /**
     * A [RuleMatcher] that checks whether any vulnerability was found for the [package][pkg].
     */
    fun hasVulnerability(): RuleMatcher {
        return object : RuleMatcher {
            override val description = "hasVulnerability()"

            override fun matches(): Boolean {
                val run = ruleSet.ortResult.advisor ?: return false
                return run.getVulnerabilities(pkg.metadata.id).isNotEmpty()
            }
        }
    }

    /**
     * A [RuleMatcher] that checks whether any vulnerability for the [package][pkg] has a
     * [reference][Vulnerability.references] with a [score][VulnerabilityReference.score] that equals or is
     * greater than [scoreThreshold] according to the [scoringSystem]. If the reference provides no score but a
     * [severity][VulnerabilityReference.severity], the threshold is mapped to a qualitative rating for comparison.
     */
    fun hasVulnerability(scoreThreshold: Float, scoringSystem: String) =
        object : RuleMatcher {
            override val description = "hasVulnerability($scoreThreshold, $scoringSystem)"

            override fun matches(): Boolean {
                val run = ruleSet.ortResult.advisor ?: return false
                val matchingSystems = run.getVulnerabilities(pkg.metadata.id).asSequence()
                    .filter { !ruleSet.resolutionProvider.isResolved(it) }
                    .flatMap { it.references }
                    .filter { it.scoringSystem == scoringSystem }

                val scores = matchingSystems.mapNotNull { it.score }
                if (scores.any()) return scores.any { it >= scoreThreshold }

                // Fall back to a more coarse comparison of qualitative severity ratings if no scores are available.
                val severityThreshold = VulnerabilityReference.getQualitativeRating(scoringSystem, scoreThreshold)
                    ?: return false

                val severities = matchingSystems
                    .mapNotNull { it.severity }
                    .mapNotNull { severity ->
                        val system = scoringSystem.uppercase()
                        when {
                            Cvss2Rating.PREFIXES.any { system.startsWith(it) } -> enumValueOf(severity)
                            Cvss3Rating.PREFIXES.any { system.startsWith(it) } -> enumValueOf(severity)
                            Cvss4Rating.PREFIXES.any { system.startsWith(it) } -> enumValueOf(severity)
                            else -> null
                        }
                    }

                return severities.any { it.ordinal >= severityThreshold.ordinal }
            }
        }

    /**
     * A [RuleMatcher] that checks if the [package][pkg] has any concluded, declared, or detected license.
     */
    fun hasLicense() =
        object : RuleMatcher {
            override val description = "hasLicense()"

            override fun matches() = resolvedLicenseInfo.licenses.any { it.license.isPresent() }
        }

    /**
     * A [RuleMatcher] that checks if the [package][pkg] is [excluded][Excludes].
     */
    fun isExcluded() =
        object : RuleMatcher {
            override val description = "isExcluded()"

            override fun matches() = ruleSet.ortResult.isExcluded(pkg.metadata.id)
        }

    /**
     * A [RuleMatcher] that checks if the [identifier][Package.id] of the [package][pkg] belongs to one of the provided
     * organization [names][Identifier.isFromOrg].
     */
    fun isFromOrg(vararg names: String) =
        object : RuleMatcher {
            override val description = "isFromOrg(${names.joinToString()})"

            override fun matches() = pkg.metadata.id.isFromOrg(*names)
        }

    /**
     * A [RuleMatcher] that checks whether the [package][pkg] is metadata only.
     */
    fun isMetadataOnly() =
        object : RuleMatcher {
            override val description = "isMetadataOnly()"

            override fun matches() = pkg.metadata.isMetadataOnly
        }

    /**
     * A [RuleMatcher] that checks if the [package][pkg] was created from a [Project].
     */
    fun isProject() =
        object : RuleMatcher {
            override val description = "isProject()"

            override fun matches() = ruleSet.ortResult.isProject(pkg.metadata.id)
        }

    /**
     * A [RuleMatcher] that checks if the [identifier type][Identifier.type] of the [package][pkg] equals [type].
     */
    fun isType(type: String) =
        object : RuleMatcher {
            override val description = "isType($type)"

            override fun matches() = pkg.metadata.id.type == type
        }

    /**
     * A DSL function to configure a [LicenseRule] and add it to this rule.
     */
    fun licenseRule(name: String, licenseView: LicenseView, block: LicenseRule.() -> Unit) {
        resolvedLicenseInfo.filter(licenseView, filterSources = true)
            .applyChoices(ruleSet.ortResult.getPackageLicenseChoices(pkg.metadata.id), licenseView)
            .applyChoices(ruleSet.ortResult.getRepositoryLicenseChoices(), licenseView).forEach { resolvedLicense ->
                resolvedLicense.sources.forEach { licenseSource ->
                    licenseRules += LicenseRule(name, resolvedLicense, licenseSource).apply(block)
                }
            }
    }

    fun issue(severity: Severity, message: String, howToFix: String) =
        issue(severity, pkg.metadata.id, null, null, message, howToFix)

    /**
     * Add a [hint][Severity.HINT] to the list of [violations].
     */
    fun hint(message: String, howToFix: String) = hint(pkg.metadata.id, null, null, message, howToFix)

    /**
     * Add a [warning][Severity.WARNING] to the list of [violations].
     */
    fun warning(message: String, howToFix: String) = warning(pkg.metadata.id, null, null, message, howToFix)

    /**
     * Add an [error][Severity.ERROR] to the list of [violations].
     */
    fun error(message: String, howToFix: String) = error(pkg.metadata.id, null, null, message, howToFix)

    /**
     * A [Rule] to check a single license of the [package][pkg].
     */
    inner class LicenseRule(
        name: String,

        /**
         * The [ResolvedLicense].
         */
        val resolvedLicense: ResolvedLicense,

        /**
         * The source of the license.
         */
        val licenseSource: LicenseSource
    ) : Rule(ruleSet, name) {
        /**
         * A shortcut for the [license][ResolvedLicense.license] in [resolvedLicense].
         */
        val license = resolvedLicense.license

        /**
         * A helper function to access [PackageRule.pkg] in extension functions for [LicenseRule], required because the
         * properties of the outer class [PackageRule] cannot be accessed from an extension function.
         */
        fun pkg() = pkg

        override val description = "\tEvaluating license rule '$name' for $licenseSource license " +
            "'${resolvedLicense.license}'."

        override fun issueSource() =
            "$name - ${pkg.metadata.id.toCoordinates()} - ${resolvedLicense.license} ($licenseSource)"

        /**
         * A [RuleMatcher] that checks if a [detected][LicenseSource.DETECTED] license is
         * [excluded][ResolvedLicense.isDetectedExcluded].
         */
        fun isExcluded() =
            object : RuleMatcher {
                override val description = "isDetectedExcluded($license)"

                override fun matches() = licenseSource == LicenseSource.DETECTED && resolvedLicense.isDetectedExcluded
            }

        /**
         * A [RuleMatcher] that checks if the [license] is a valid SPDX license.
         */
        fun isSpdxLicense() =
            object : RuleMatcher {
                override val description = "isSpdxLicense($license)"

                override fun matches() =
                    when (license) {
                        !is SpdxLicenseReferenceExpression ->
                            license.isValid(SpdxExpression.Strictness.ALLOW_DEPRECATED)
                        else -> false
                    }
            }

        fun issue(severity: Severity, message: String, howToFix: String) =
            issue(severity, pkg.metadata.id, license, licenseSource, message, howToFix)

        /**
         * Add a [hint][Severity.HINT] to the list of [violations].
         */
        fun hint(message: String, howToFix: String) = hint(pkg.metadata.id, license, licenseSource, message, howToFix)

        /**
         * Add a [warning][Severity.WARNING] to the list of [violations].
         */
        fun warning(message: String, howToFix: String) =
            warning(pkg.metadata.id, license, licenseSource, message, howToFix)

        /**
         * Add an [error][Severity.ERROR] to the list of [violations].
         */
        fun error(message: String, howToFix: String) = error(pkg.metadata.id, license, licenseSource, message, howToFix)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy