Skip to content

Commit

Permalink
Added tests 6.1.6, 6.1.7 (#74)
Browse files Browse the repository at this point in the history
  • Loading branch information
oxisto committed Oct 28, 2024
1 parent 71c7b9b commit 8e9a496
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -119,18 +119,48 @@ fun Csaf.Vulnerability.gatherProductGroupReferencesTo(ids: MutableCollection<Str
}

fun Csaf.ProductStatus?.gatherProductReferencesTo(ids: MutableCollection<String>) {
gatherAffectedProductReferencesTo(ids)
gatherNotAffectedProductReferencesTo(ids)
gatherFixedProductReferencesTo(ids)
gatherUnderInvestigationProductReferencesTo(ids)
gatherRecommendedProductReferencesTo(ids)
}

fun Csaf.ProductStatus?.gatherAffectedProductReferencesTo(ids: MutableCollection<String>) {
if (this == null) return

ids += first_affected
ids += first_fixed
ids += fixed
ids += known_affected
ids += known_not_affected
ids += last_affected
ids += recommended
}

fun Csaf.ProductStatus?.gatherNotAffectedProductReferencesTo(ids: MutableCollection<String>) {
if (this == null) return

ids += known_not_affected
}

fun Csaf.ProductStatus?.gatherFixedProductReferencesTo(ids: MutableCollection<String>) {
if (this == null) return

ids += first_fixed
ids += fixed
}

fun Csaf.ProductStatus?.gatherUnderInvestigationProductReferencesTo(
ids: MutableCollection<String>
) {
if (this == null) return

ids += under_investigation
}

fun Csaf.ProductStatus?.gatherRecommendedProductReferencesTo(ids: MutableCollection<String>) {
if (this == null) return

ids += recommended
}

fun List<*>?.gatherProductReferencesTo(ids: MutableCollection<String>) {
if (this == null) return

Expand Down Expand Up @@ -161,13 +191,13 @@ fun List<*>?.gatherProductGroupReferencesTo(ids: MutableCollection<String>) {
}
}

private operator fun <E> MutableCollection<E>.plusAssign(set: Collection<E>?) {
internal operator fun <E> MutableCollection<E>.plusAssign(set: Collection<E>?) {
if (set != null) {
this.addAll(set)
}
}

private operator fun <E> MutableCollection<E>.plusAssign(item: E?) {
internal operator fun <E> MutableCollection<E>.plusAssign(item: E?) {
if (item != null) {
this.add(item)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import io.github.csaf.sbom.validation.ValidationFailed
import io.github.csaf.sbom.validation.ValidationResult
import io.github.csaf.sbom.validation.ValidationSuccessful
import io.github.csaf.sbom.validation.merge
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive

/**
* Mandatory tests as defined in
Expand All @@ -34,6 +36,8 @@ var mandatoryTests =
Test613CircularDefinitionOfProductID,
Test614MissingDefinitionOfProductGroupID,
Test615MultipleDefinitionOfProductGroupID,
Test616ContradictingProductStatus,
Test617MultipleScoresWithSameVersionPerProduct,
)

/**
Expand Down Expand Up @@ -65,7 +69,7 @@ object Test611MissingDefinitionOfProductID : Test {
val definitions = doc.gatherProducts().map { it.product_id }
val references = doc.gatherProductReferences()

val notDefined = references.subtract(definitions)
val notDefined = references.subtract(definitions.toSet())

return if (notDefined.isEmpty()) {
ValidationSuccessful
Expand All @@ -85,7 +89,7 @@ object Test612MultipleDefinitionOfProductID : Test {
override fun test(doc: Csaf): ValidationResult {
val definitions = doc.gatherProducts().map { it.product_id }

val duplicates = definitions.groupingBy { it }.eachCount().filter { it.value > 1 }
val duplicates = definitions.duplicates()

return if (duplicates.isEmpty()) {
ValidationSuccessful
Expand All @@ -97,6 +101,10 @@ object Test612MultipleDefinitionOfProductID : Test {
}
}

private fun <T> List<T>.duplicates(): Map<T, Int> {
return groupingBy { it }.eachCount().filter { it.value > 1 }
}

/**
* Implementation of
* [Test 6.1.3](https://docs.oasis-open.org/csaf/csaf/v2.0/os/csaf-v2.0-os.html#613-circular-definition-of-product-id).
Expand All @@ -106,8 +114,8 @@ object Test613CircularDefinitionOfProductID : Test {
val circles = mutableSetOf<String>()

for (relationship in doc.product_tree?.relationships ?: listOf()) {
var definedId = relationship.full_product_name.product_id
var notAllowed =
val definedId = relationship.full_product_name.product_id
val notAllowed =
listOf(relationship.product_reference, relationship.relates_to_product_reference)
if (definedId in notAllowed) {
circles += definedId
Expand All @@ -133,7 +141,7 @@ object Test614MissingDefinitionOfProductGroupID : Test {
val definitions = doc.gatherProductGroups().map { it.group_id }
val references = doc.gatherProductGroupReferences()

val notDefined = references.subtract(definitions)
val notDefined = references.subtract(definitions.toSet())

return if (notDefined.isEmpty()) {
ValidationSuccessful
Expand All @@ -153,7 +161,7 @@ object Test615MultipleDefinitionOfProductGroupID : Test {
override fun test(doc: Csaf): ValidationResult {
val definitions = doc.gatherProductGroups().map { it.group_id }

val duplicates = definitions.groupingBy { it }.eachCount().filter { it.value > 1 }
val duplicates = definitions.duplicates()

return if (duplicates.isEmpty()) {
ValidationSuccessful
Expand All @@ -165,6 +173,93 @@ object Test615MultipleDefinitionOfProductGroupID : Test {
}
}

/**
* Implementation of
* [Test 6.1.6](https://docs.oasis-open.org/csaf/csaf/v2.0/os/csaf-v2.0-os.html#616-contradicting-product-status).
*/
object Test616ContradictingProductStatus : Test {
override fun test(doc: Csaf): ValidationResult {
val affected = mutableSetOf<String>()
val notAffected = mutableSetOf<String>()
val fixed = mutableSetOf<String>()
val underInvestigation = mutableSetOf<String>()

val contradicted = mutableSetOf<String>()

for (vulnerability in doc.vulnerabilities ?: listOf()) {
affected.clear()
notAffected.clear()
fixed.clear()
underInvestigation.clear()

vulnerability.product_status.gatherAffectedProductReferencesTo(affected)
vulnerability.product_status.gatherNotAffectedProductReferencesTo(notAffected)
vulnerability.product_status.gatherFixedProductReferencesTo(fixed)
vulnerability.product_status.gatherUnderInvestigationProductReferencesTo(
underInvestigation
)

contradicted += affected.intersect(notAffected)
contradicted += affected.intersect(fixed)
contradicted += affected.intersect(underInvestigation)
contradicted += notAffected.intersect(fixed)
contradicted += notAffected.intersect(underInvestigation)
contradicted += fixed.intersect(underInvestigation)
}

return if (contradicted.isEmpty()) {
ValidationSuccessful
} else {
ValidationFailed(
listOf(
"The following IDs have contradicting statuses: ${contradicted.joinToString(",")}"
)
)
}
}
}

/**
* Implementation of
* [Test 6.1.7](https://docs.oasis-open.org/csaf/csaf/v2.0/os/csaf-v2.0-os.html#617-multiple-scores-with-same-version-per-product).
*/
object Test617MultipleScoresWithSameVersionPerProduct : Test {
override fun test(doc: Csaf): ValidationResult {
val multiples = mutableSetOf<String>()

for (vulnerability in doc.vulnerabilities ?: listOf()) {
// Gather a map of product_id => list of cvss_version
val productScoreVersions = mutableMapOf<String, MutableList<String>>()
for (score in vulnerability.scores ?: listOf()) {
score.products.forEach {
val versions = productScoreVersions.computeIfAbsent(it) { mutableListOf() }
versions += score.cvss_v3?.version
versions += score.cvss_v2?.version
}
}

multiples +=
productScoreVersions
.filter {
// We need to look for potential duplicates in the versions
val versions = it.value
val duplicates = versions.duplicates()

duplicates.isNotEmpty()
}
.map { it.key }
}

return if (multiples.isEmpty()) {
ValidationSuccessful
} else {
ValidationFailed(
listOf("The following IDs have multiple scores: ${multiples.joinToString(",")}")
)
}
}
}

/**
* Implementation of
* [Test 6.2.1](https://docs.oasis-open.org/csaf/csaf/v2.0/os/csaf-v2.0-os.html#621-unused-definition-of-product-id).
Expand All @@ -174,11 +269,21 @@ object Test621UnusedDefinitionOfProductID : Test {
val definitions = doc.gatherProducts().map { it.product_id }
val references = doc.gatherProductReferences()

val notUsed = definitions.subtract(references)
val notUsed = definitions.subtract(references.toSet())
return if (notUsed.isEmpty()) {
ValidationSuccessful
} else {
ValidationFailed(listOf("The following IDs are not used: ${notUsed.joinToString(",")}"))
}
}
}

val JsonObject?.version: String?
get() {
val primitive = this?.get("version") as? JsonPrimitive
return if (primitive?.isString == true) {
primitive.content
} else {
null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,15 @@ fun goodProductTree(): Csaf.ProductTree =
name = "Linux 0.4",
product_id = "linux-0.4",
)
),
Csaf.Branche(
category = Csaf.Category3.vendor,
name = "Linux Vendor",
product =
Csaf.Product(
name = "Linux 0.5",
product_id = "linux-0.5",
)
)
)
)
Expand Down Expand Up @@ -235,13 +244,13 @@ fun goodVulnerabilities() =
product_status =
Csaf.ProductStatus(
first_affected = setOf("linux-0.1"),
first_fixed = setOf("linux-0.1", "linux-0.2"),
known_affected = setOf("linux-0.1", "linux-0.3"),
known_not_affected = setOf("linux-0.1", "linux-0.4"),
last_affected = setOf("linux-0.1", "linux-0.2"),
recommended = setOf("linux-0.1", "linux-0.3"),
fixed = setOf("linux-0.1", "linux-0.4"),
under_investigation = setOf("linux-0.1", "linux-0.3"),
first_fixed = setOf("linux-0.5"),
known_affected = setOf("linux-0.1"),
known_not_affected = setOf("linux-0.3"),
last_affected = setOf("linux-0.2"),
recommended = setOf("linux-0.5"),
fixed = setOf("linux-0.5"),
under_investigation = setOf("linux-0.4"),
),
remediations =
listOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ import kotlin.io.path.Path
import kotlin.io.path.readText
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive

/** The path to the test folder for the CSAF 2.0 tests. */
var testFolder: String = "../csaf/csaf_2.0/test/validator/data/"
Expand Down Expand Up @@ -106,6 +109,59 @@ class TestsTest {
)
}

@Test
fun test616() {
val test = Test616ContradictingProductStatus

// failing examples
assertValidationFailed(
"The following IDs have contradicting statuses: CSAFPID-9080700",
test.test(mandatoryTest("6-1-06-01"))
)
assertValidationFailed(
"The following IDs have contradicting statuses: CSAFPID-9080700",
test.test(mandatoryTest("6-1-06-02"))
)
assertValidationFailed(
"The following IDs have contradicting statuses: CSAFPID-9080700",
test.test(mandatoryTest("6-1-06-03"))
)
assertValidationFailed(
"The following IDs have contradicting statuses: CSAFPID-9080700,CSAFPID-9080701",
test.test(mandatoryTest("6-1-06-04"))
)
assertValidationFailed(
"The following IDs have contradicting statuses: CSAFPID-9080702,CSAFPID-9080700,CSAFPID-9080701",
test.test(mandatoryTest("6-1-06-05"))
)

// good examples
assertValidationSuccessful(test.test(goodCsaf(vulnerabilities = null)))
assertValidationSuccessful(test.test(mandatoryTest("6-1-06-11")))
assertValidationSuccessful(test.test(mandatoryTest("6-1-06-12")))
assertValidationSuccessful(test.test(mandatoryTest("6-1-06-13")))
assertValidationSuccessful(test.test(mandatoryTest("6-1-06-14")))
assertValidationSuccessful(test.test(mandatoryTest("6-1-06-15")))
}

@Test
fun test617() {
val test = Test617MultipleScoresWithSameVersionPerProduct

// failing examples
assertValidationFailed(
"The following IDs have multiple scores: CSAFPID-9080700",
test.test(mandatoryTest("6-1-07-01"))
)

assertValidationSuccessful(test.test(goodCsaf(vulnerabilities = null)))
assertValidationSuccessful(
test.test(goodCsaf(vulnerabilities = listOf(Csaf.Vulnerability(scores = null))))
)
assertValidationSuccessful(test.test(mandatoryTest("6-1-07-11")))
assertValidationSuccessful(test.test(mandatoryTest("6-1-07-12")))
}

@Test
fun test621() {
val test = Test621UnusedDefinitionOfProductID
Expand All @@ -120,6 +176,7 @@ class TestsTest {
fun testAllGood() {
val good = goodCsaf()
val tests = mandatoryTests + optionalTests + informativeTests

tests.forEach {
assertEquals(
ValidationSuccessful,
Expand All @@ -128,4 +185,13 @@ class TestsTest {
)
}
}

@OptIn(ExperimentalSerializationApi::class)
@Test
fun testVersion() {
@Suppress("USELESS_CAST") assertEquals(null, (null as? JsonObject).version)
assertEquals(null, JsonObject(content = mapOf()).version)
assertEquals(null, JsonObject(content = mapOf("version" to JsonPrimitive(null))).version)
assertEquals("3.0", JsonObject(content = mapOf("version" to JsonPrimitive("3.0"))).version)
}
}

0 comments on commit 8e9a496

Please sign in to comment.