diff --git a/db/v-2/tables/cosv-metadata.xml b/db/v-2/tables/cosv-metadata.xml
index 84975e52c6..c4a5e6a751 100644
--- a/db/v-2/tables/cosv-metadata.xml
+++ b/db/v-2/tables/cosv-metadata.xml
@@ -44,4 +44,15 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/controllers/vulnerability/VulnerabilityController.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/controllers/vulnerability/VulnerabilityController.kt
index 9e9676a8f6..f123798f0b 100644
--- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/controllers/vulnerability/VulnerabilityController.kt
+++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/controllers/vulnerability/VulnerabilityController.kt
@@ -6,6 +6,7 @@ import com.saveourtool.save.backend.service.vulnerability.VulnerabilityService
import com.saveourtool.save.backend.utils.hasRole
import com.saveourtool.save.configs.ApiSwaggerSupport
import com.saveourtool.save.configs.RequiresAuthorizationSourceHeader
+import com.saveourtool.save.cosv.service.CosvService
import com.saveourtool.save.domain.Role
import com.saveourtool.save.entities.vulnerability.VulnerabilityDateDto
import com.saveourtool.save.entities.vulnerability.VulnerabilityDto
@@ -45,6 +46,7 @@ class VulnerabilityController(
private val vulnerabilityService: VulnerabilityService,
private val vulnerabilityPermissionEvaluator: VulnerabilityPermissionEvaluator,
private val tagService: TagService,
+ private val cosvService: CosvService,
) {
@PostMapping("/by-filter")
@Operation(
@@ -171,6 +173,9 @@ class VulnerabilityController(
"Vulnerability Identifier should either be empty or start with one of prefixes: ${VulnerabilityDto.vulnerabilityPrefixes}"
}
.blockingMap { vulnerabilityService.save(vulnerabilityDto, authentication) }
+ .flatMap { vulnerability ->
+ cosvService.generateAndSave(vulnerability.toDtoWithDescription().copy(userInfo = UserInfo(authentication.name)))
+ }
.map { ResponseEntity.ok("Vulnerability was successfully saved") }
@PostMapping("/update")
diff --git a/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/utils/CosvSchemaUtils.kt b/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/utils/CosvSchemaUtils.kt
index 3e675f0a0a..b5748ddc28 100644
--- a/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/utils/CosvSchemaUtils.kt
+++ b/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/utils/CosvSchemaUtils.kt
@@ -9,6 +9,8 @@ import com.saveourtool.save.entities.vulnerability.VulnerabilityDateType
import com.saveourtool.save.entities.vulnerability.VulnerabilityLanguage
import com.saveourtool.save.info.UserInfo
+import com.saveourtool.osv4k.Credit
+import com.saveourtool.osv4k.CreditType
import com.saveourtool.osv4k.OsvSchema as CosvSchema
import com.saveourtool.osv4k.TimeLineEntry
import com.saveourtool.osv4k.TimeLineEntryType
@@ -27,6 +29,19 @@ fun CosvSchema<*, *, *, *>.getSaveContributes(): List = credits
?.map { UserInfo(it) }
.orEmpty()
+/**
+ * @return list of [Credit]
+ */
+fun List.asCredits(): List = map {
+ Credit(
+ name = it.name,
+ contact = listOf(
+ SAVEOURTOOL_PROFILE_PREFIX + it.name
+ ),
+ type = CreditType.REPORTER,
+ )
+}
+
/**
* @return timeline as [List] of [VulnerabilityDateDto]
*/
diff --git a/save-cosv/src/main/kotlin/com/saveourtool/save/cosv/repository/CosvRepository.kt b/save-cosv/src/main/kotlin/com/saveourtool/save/cosv/repository/CosvRepository.kt
index c169c009dc..36e40ac044 100644
--- a/save-cosv/src/main/kotlin/com/saveourtool/save/cosv/repository/CosvRepository.kt
+++ b/save-cosv/src/main/kotlin/com/saveourtool/save/cosv/repository/CosvRepository.kt
@@ -32,7 +32,7 @@ interface CosvRepository {
entry: CosvSchema,
serializer: CosvSchemaKSerializer,
user: User,
- organization: Organization,
+ organization: Organization?,
): Mono
/**
diff --git a/save-cosv/src/main/kotlin/com/saveourtool/save/cosv/repository/CosvRepositoryInStorage.kt b/save-cosv/src/main/kotlin/com/saveourtool/save/cosv/repository/CosvRepositoryInStorage.kt
index 13e8cba12f..781ec737f8 100644
--- a/save-cosv/src/main/kotlin/com/saveourtool/save/cosv/repository/CosvRepositoryInStorage.kt
+++ b/save-cosv/src/main/kotlin/com/saveourtool/save/cosv/repository/CosvRepositoryInStorage.kt
@@ -42,7 +42,7 @@ class CosvRepositoryInStorage(
entry: CosvSchema,
serializer: CosvSchemaKSerializer,
user: User,
- organization: Organization
+ organization: Organization?,
): Mono = saveMetadata(entry, user, organization).flatMap { metadata ->
cosvStorage.upload(
metadata.toStorageKey(),
@@ -53,7 +53,7 @@ class CosvRepositoryInStorage(
private fun saveMetadata(
entry: CosvSchema<*, *, *, *>,
user: User,
- organization: Organization,
+ organization: Organization?,
): Mono = blockingToMono {
val metadata = cosvMetadataRepository.findByCosvId(entry.id)
?.let { existedMetadata ->
@@ -74,12 +74,14 @@ class CosvRepositoryInStorage(
"already existed in save uploaded by another userId=${existedMetadata.user.requiredId()}",
)
}
- if (existedMetadata.organization?.requiredId() != organization.requiredId()) {
- throw ResponseStatusException(
- HttpStatus.FORBIDDEN,
- "${errorPrefix()} to organizationId=${organization.requiredId()}: " +
- "already existed in save in another organizationId=${existedMetadata.organization?.requiredId()}",
- )
+ existedMetadata.organization?.run {
+ if (requiredId() != organization?.requiredId()) {
+ throw ResponseStatusException(
+ HttpStatus.FORBIDDEN,
+ "${errorPrefix()} to organizationId=${requiredId()}: " +
+ "already existed in save in another organizationId=${existedMetadata.organization?.requiredId()}",
+ )
+ }
}
existedMetadata.updateBy(entry)
}
@@ -118,7 +120,7 @@ class CosvRepositoryInStorage(
companion object {
private fun CosvSchema<*, *, *, *>.toMetadata(
user: User,
- organization: Organization,
+ organization: Organization?,
) = CosvMetadata(
cosvId = id,
summary = summary ?: "Summary not provided",
diff --git a/save-cosv/src/main/kotlin/com/saveourtool/save/cosv/service/CosvService.kt b/save-cosv/src/main/kotlin/com/saveourtool/save/cosv/service/CosvService.kt
index 9493bfd176..459dcdcca0 100644
--- a/save-cosv/src/main/kotlin/com/saveourtool/save/cosv/service/CosvService.kt
+++ b/save-cosv/src/main/kotlin/com/saveourtool/save/cosv/service/CosvService.kt
@@ -3,14 +3,16 @@ package com.saveourtool.save.cosv.service
import com.saveourtool.save.backend.service.IBackendService
import com.saveourtool.save.cosv.processor.CosvProcessor
import com.saveourtool.save.cosv.repository.CosvRepository
+import com.saveourtool.save.cosv.repository.CosvSchema
import com.saveourtool.save.cosv.utils.toJsonArrayOrSingle
import com.saveourtool.save.entities.Organization
import com.saveourtool.save.entities.User
+import com.saveourtool.save.entities.cosv.CosvMetadataDto
import com.saveourtool.save.entities.cosv.RawCosvExt
import com.saveourtool.save.entities.vulnerability.*
import com.saveourtool.save.utils.*
-import com.saveourtool.osv4k.RawOsvSchema
+import com.saveourtool.osv4k.*
import org.springframework.security.core.Authentication
import org.springframework.stereotype.Service
import reactor.core.publisher.Flux
@@ -23,6 +25,8 @@ import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.*
import kotlinx.serialization.serializer
+private typealias ManualCosvSchema = CosvSchema
+
/**
* Service for vulnerabilities
*/
@@ -133,4 +137,48 @@ class CosvService(
fun findExtByCosvId(
cosvId: String,
): Mono = cosvRepository.findLatestRawExt(cosvId)
+
+ /**
+ * Generates COSV from [VulnerabilityDto] and saves it
+ *
+ * @param vulnerabilityDto as a source for COSV
+ * @return [CosvMetadataDto] saved metadata
+ */
+ fun generateAndSave(
+ vulnerabilityDto: VulnerabilityDto,
+ ): Mono = blockingToMono {
+ val user = backendService.getUserByName(vulnerabilityDto.userInfo.name)
+ val organization = vulnerabilityDto.organization?.let { backendService.getOrganizationByName(it.name) }
+ user to organization
+ }.flatMap { (user, organization) ->
+ val osv = ManualCosvSchema(
+ id = vulnerabilityDto.identifier,
+ published = vulnerabilityDto.creationDateTime ?: getCurrentLocalDateTime(),
+ modified = vulnerabilityDto.lastUpdatedDateTime ?: getCurrentLocalDateTime(),
+ severity = listOf(
+ Severity(
+ type = SeverityType.CVSS_V3,
+ score = "N/A",
+ scoreNum = vulnerabilityDto.progress.toString(),
+ )
+ ),
+ summary = vulnerabilityDto.shortDescription,
+ details = vulnerabilityDto.description,
+ references = vulnerabilityDto.relatedLink?.let { relatedLink ->
+ listOf(
+ Reference(
+ type = ReferenceType.WEB,
+ url = relatedLink,
+ )
+ )
+ },
+ credits = vulnerabilityDto.participants.asCredits().takeUnless { it.isEmpty() },
+ )
+ cosvRepository.save(
+ entry = osv,
+ serializer = serializer(),
+ user = user,
+ organization = organization,
+ )
+ }
}