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, + ) + } }