Skip to content

Commit ebad7c8

Browse files
authored
Added automatically added tags about affected ecosystems (#2853)
### What's done: - added logic for automatically added tags about affected ecosystems during vulnerabilities uploading. - changed tag length range from [3, 15] to [2, 15].
1 parent 73b0c13 commit ebad7c8

8 files changed

Lines changed: 90 additions & 24 deletions

File tree

save-backend/src/main/kotlin/com/saveourtool/save/backend/service/BackendForCosvService.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.saveourtool.save.backend.security.OrganizationPermissionEvaluator
55
import com.saveourtool.save.backend.security.UserPermissionEvaluator
66
import com.saveourtool.save.entities.Organization
77
import com.saveourtool.save.entities.User
8+
import com.saveourtool.save.entities.cosv.LnkVulnerabilityMetadataTag
89
import com.saveourtool.save.permission.Permission
910
import org.springframework.security.core.Authentication
1011
import org.springframework.stereotype.Service
@@ -19,6 +20,7 @@ class BackendForCosvService(
1920
private val userDetailsService: UserDetailsService,
2021
private val userPermissionEvaluator: UserPermissionEvaluator,
2122
private val organizationPermissionEvaluator: OrganizationPermissionEvaluator,
23+
private val tagService: TagService,
2224
configProperties: ConfigProperties,
2325
) : IBackendService {
2426
override val workingDir: Path = configProperties.workingDir
@@ -41,4 +43,9 @@ class BackendForCosvService(
4143
override fun saveUser(user: User): User = userDetailsService.saveUser(user)
4244

4345
override fun saveOrganization(organization: Organization) = organizationService.updateOrganization(organization)
46+
47+
override fun addVulnerabilityTags(
48+
identifier: String,
49+
tagName: Set<String>
50+
): List<LnkVulnerabilityMetadataTag>? = tagService.addVulnerabilityTags(identifier, tagName)
4451
}

save-backend/src/main/kotlin/com/saveourtool/save/backend/service/TagService.kt

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import com.saveourtool.save.cosv.repository.LnkVulnerabilityMetadataTagRepositor
55
import com.saveourtool.save.cosv.repository.VulnerabilityMetadataRepository
66
import com.saveourtool.save.entities.Tag
77
import com.saveourtool.save.entities.cosv.LnkVulnerabilityMetadataTag
8+
import com.saveourtool.save.utils.error
89
import com.saveourtool.save.utils.getLogger
9-
import com.saveourtool.save.utils.info
1010
import com.saveourtool.save.utils.orNotFound
1111
import com.saveourtool.save.validation.TAG_ERROR_MESSAGE
1212
import com.saveourtool.save.validation.isValidTag
@@ -37,8 +37,6 @@ class TagService(
3737
*/
3838
@Transactional
3939
fun addVulnerabilityTag(identifier: String, tagName: String): LnkVulnerabilityMetadataTag {
40-
log.info { "Trying to add $tagName to $identifier vulnerability" }
41-
4240
if (!tagName.isValidTag()) {
4341
throw ResponseStatusException(HttpStatus.CONFLICT, TAG_ERROR_MESSAGE)
4442
}
@@ -52,6 +50,32 @@ class TagService(
5250
)
5351
}
5452

53+
/**
54+
* @param identifier [com.saveourtool.save.entities.cosv.VulnerabilityMetadata.identifier]
55+
* @param tagNames tags to add
56+
* @return new [LnkVulnerabilityMetadataTag]
57+
*/
58+
@Transactional
59+
fun addVulnerabilityTags(identifier: String, tagNames: Set<String>): List<LnkVulnerabilityMetadataTag>? {
60+
if (tagNames.any { !it.isValidTag() }) {
61+
log.error { TAG_ERROR_MESSAGE }
62+
return null
63+
}
64+
65+
val metadata = vulnerabilityMetadataRepository.findByIdentifier(identifier) ?: run {
66+
log.error { "Could not find metadata for vulnerability $identifier" }
67+
return null
68+
}
69+
70+
val links = tagNames.map {
71+
tagRepository.findByName(it) ?: tagRepository.save(Tag(it))
72+
}.map {
73+
LnkVulnerabilityMetadataTag(metadata, it)
74+
}
75+
76+
return lnkVulnerabilityMetadataTagRepository.saveAll(links)
77+
}
78+
5579
/**
5680
* @param identifier [com.saveourtool.save.entities.cosv.VulnerabilityMetadata.identifier]
5781
* @param tagName tag to delete

save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/validation/ValidationErrorMessages.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const val CVE_NAME_ERROR_MESSAGE = "CVE identifier is invalid"
4343
/**
4444
* Error message that is shown when tag is invalid.
4545
*/
46-
const val TAG_ERROR_MESSAGE = "Tag length should be in [3, 15] range, no commas are allowed."
46+
const val TAG_ERROR_MESSAGE = "Tag length should be in [2, 15] range, no commas are allowed."
4747

4848
/**
4949
* Error message that is shown when severity score vector is invalid.

save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/validation/ValidationUtils.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const val NAMING_MAX_LENGTH = 22
1313
private val namingAllowedSpecialSymbols = setOf('-', '_', '.')
1414

1515
@Suppress("MagicNumber")
16-
private val tagLengthRange = 3..15
16+
private val tagLengthRange = 2..15
1717

1818
/**
1919
* Check if name is valid.

save-cosv/src/main/kotlin/com/saveourtool/save/backend/service/IBackendService.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package com.saveourtool.save.backend.service
44

55
import com.saveourtool.save.entities.Organization
66
import com.saveourtool.save.entities.User
7+
import com.saveourtool.save.entities.cosv.LnkVulnerabilityMetadataTag
78
import com.saveourtool.save.info.UserPermissions
89
import com.saveourtool.save.permission.Permission
910
import org.jetbrains.annotations.Blocking
@@ -63,4 +64,14 @@ interface IBackendService {
6364
organizationName: String,
6465
permission: Permission,
6566
): Boolean
67+
68+
/**
69+
* @param identifier [com.saveourtool.save.entities.cosv.VulnerabilityMetadata.identifier]
70+
* @param tagName tag to add
71+
* @return new [LnkVulnerabilityMetadataTag]
72+
*/
73+
fun addVulnerabilityTags(
74+
identifier: String,
75+
tagName: Set<String>
76+
): List<LnkVulnerabilityMetadataTag>?
6677
}

save-cosv/src/main/kotlin/com/saveourtool/save/cosv/controllers/RawCosvFileController.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ class RawCosvFileController(
278278
backendService.getOrganizationByName(organizationName) to backendService.getUserByName(authentication.name)
279279
}
280280
.flatMap { (organization, user) ->
281-
cosvService.process(ids, user, organization)
281+
cosvService.processAndAddTagsAndUpdateRating(ids, user, organization)
282282
}
283283
.subscribeOn(Schedulers.boundedElastic())
284284
.subscribe()
@@ -287,7 +287,7 @@ class RawCosvFileController(
287287
/**
288288
* @param organizationName
289289
* @param authentication
290-
* @return statistics [RawCosvFileStatisticDto] with counts of all, uploaded, processing, failed raw cosv files in [organizationName]
290+
* @return statistics [RawCosvFileStatisticsDto] with counts of all, uploaded, processing, failed raw cosv files in [organizationName]
291291
*/
292292
@RequiresAuthorizationSourceHeader
293293
@GetMapping("/statistics")

save-cosv/src/main/kotlin/com/saveourtool/save/cosv/service/CosvService.kt

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import reactor.core.publisher.Mono
2222
import reactor.core.scheduler.Schedulers
2323
import reactor.kotlin.core.publisher.switchIfEmpty
2424
import reactor.kotlin.core.publisher.toFlux
25-
import reactor.kotlin.extra.math.sumAll
2625

2726
import java.nio.ByteBuffer
2827
import javax.annotation.PostConstruct
@@ -31,6 +30,8 @@ import kotlin.time.Duration.Companion.minutes
3130
import kotlin.time.Duration.Companion.seconds
3231
import kotlinx.serialization.serializer
3332

33+
typealias VulnerabilityMetadataDtoList = List<VulnerabilityMetadataDto>
34+
3435
/**
3536
* Service for vulnerabilities
3637
*/
@@ -46,6 +47,8 @@ class CosvService(
4647
private val lnkVulnerabilityMetadataTagRepository: LnkVulnerabilityMetadataTagRepository,
4748
private val cosvGeneratedIdRepository: CosvGeneratedIdRepository,
4849
) {
50+
private val scheduler = Schedulers.boundedElastic()
51+
4952
/**
5053
* Init method to restore all raw cosv files with `in progress` state to process
5154
*/
@@ -71,7 +74,7 @@ class CosvService(
7174
"Storage ${RawCosvFileStorage::class.simpleName} and repository ${CosvRepository::class.simpleName} are not initialized in $initMaxTime"
7275
}
7376
}
74-
.subscribeOn(Schedulers.boundedElastic())
77+
.subscribeOn(scheduler)
7578
.subscribe()
7679
}
7780

@@ -90,7 +93,11 @@ class CosvService(
9093
.flatMap {
9194
doProcess(it.requiredId(), user, organization)
9295
}
93-
.updateRating(user, organization)
96+
.collectList()
97+
.map { it.flatten() }
98+
.blockingMap {
99+
vulnerabilityRatingService.addRatingForBulkUpload(user, organization, it.size)
100+
}
94101
}
95102
}
96103
.thenJust(Unit)
@@ -102,12 +109,14 @@ class CosvService(
102109
fun generateIdentifier(): String = cosvGeneratedIdRepository.saveAndFlush(CosvGeneratedId()).getIdentifier()
103110

104111
/**
112+
* Method to process all raw cosv files, add ecosystem tags to new vulnerabilities, update user rating
113+
*
105114
* @param rawCosvFileIds
106115
* @param user
107116
* @param organization
108117
* @return empty [Mono]
109118
*/
110-
fun process(
119+
fun processAndAddTagsAndUpdateRating(
111120
rawCosvFileIds: Collection<Long>,
112121
user: User,
113122
organization: Organization,
@@ -118,7 +127,30 @@ class CosvService(
118127
doProcess(rawCosvFileId, user, organization)
119128
}
120129
}
121-
.updateRating(user, organization)
130+
.collectList()
131+
.map { it.flatten() }
132+
.flatMap { metadataList ->
133+
Mono.just(metadataList.size).doOnSuccess {
134+
metadataList.toFlux().flatMap { vulnerabilityMetadataDto ->
135+
getVulnerabilityExt(vulnerabilityMetadataDto.identifier).mapNotNull { vulnerabilityExt ->
136+
vulnerabilityExt.cosv.affected?.mapNotNull { it.`package`?.ecosystem }?.toSet()?.let {
137+
vulnerabilityExt.metadataDto.identifier to it
138+
}
139+
}
140+
}
141+
.collectList()
142+
.blockingMap { identifierToTagsList ->
143+
identifierToTagsList.forEach { (identifier, tags) ->
144+
backendService.addVulnerabilityTags(identifier, tags)
145+
}
146+
}
147+
.subscribeOn(scheduler)
148+
.subscribe()
149+
}
150+
}
151+
.blockingMap {
152+
vulnerabilityRatingService.addRatingForBulkUpload(user, organization, it)
153+
}
122154
.map {
123155
log.debug {
124156
"Finished processing raw COSV files $rawCosvFileIds"
@@ -144,7 +176,7 @@ class CosvService(
144176
rawCosvFileId: Long,
145177
user: User,
146178
organization: Organization,
147-
): Mono<Int> = rawCosvFileStorage.downloadById(rawCosvFileId)
179+
): Mono<VulnerabilityMetadataDtoList> = rawCosvFileStorage.downloadById(rawCosvFileId)
148180
.collectToInputStream()
149181
.flatMap { inputStream ->
150182
val errorMessage by lazy {
@@ -170,22 +202,14 @@ class CosvService(
170202
.flatMap {
171203
rawCosvFileStorage.deleteById(rawCosvFileId)
172204
}
173-
.thenReturn(metadataList.size)
205+
.thenReturn(metadataList)
174206
}
175207
.onErrorResume { error ->
176208
log.error(error) { errorMessage }
177-
Mono.just(0)
209+
Mono.just(emptyList())
178210
}
179211
}
180212

181-
private fun Flux<Int>.updateRating(
182-
user: User,
183-
organization: Organization,
184-
): Mono<Unit> = sumAll()
185-
.blockingMap {
186-
vulnerabilityRatingService.addRatingForBulkUpload(user, organization, it)
187-
}
188-
189213
/**
190214
* @param cosvId
191215
* @param updater

save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/vuln/VulnerabilityTagsComponent.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ val vulnerabilityTagsComponent: FC<VulnerabilityTagsComponentProps> = FC { props
4646
className = ClassName("form-control custom-input $validityClassName")
4747
value = newTag
4848
placeholder = "Add a new tag...".t()
49-
title = "Tag should not have commas, length should be more than 2 and less than 16.".t()
49+
title = "Tag should not have commas, length should be more than 1 and less than 16.".t()
5050
asDynamic()["data-toggle"] = "tooltip"
5151
asDynamic()["data-placement"] = "top"
5252
onChange = { event -> setNewTag(event.target.value) }

0 commit comments

Comments
 (0)