Skip to content

Commit

Permalink
Vulnerability tags (#2370)
Browse files Browse the repository at this point in the history
* Vulnerability tags

### What's done:
 * Added tags column in vulnerability table
 * Added field to Vulnerability entity
 * Added field to VulnerabilityDto
 * Supported tags displaying
 * Supported adding tags to existed vulnerabilities
 * Fixed buttons with icons to be vertically aligned
  • Loading branch information
sanyavertolet authored Jul 25, 2023
1 parent a9d1934 commit 5bbecd5
Show file tree
Hide file tree
Showing 10 changed files with 449 additions and 124 deletions.
6 changes: 6 additions & 0 deletions db/v-2/tables/vulnerability.xml
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,10 @@
</dropColumn>
</changeSet>

<changeSet id="vulnerability-11" author="sanyavertolet" context="dev or prod">
<addColumn tableName="vulnerability">
<column name="tags" type="varchar(256)" defaultValue=""/>
</addColumn>
</changeSet>

</databaseChangeLog>
Original file line number Diff line number Diff line change
Expand Up @@ -389,4 +389,44 @@ class VulnerabilityController(
.flatMap { blockingToMono { vulnerabilityService.deleteUser(userName, vulnerabilityName) } }
.switchIfEmptyToNotFound { "Could not find user." }
.map { StringResponse.ok("Successfully deleted user from vulnerability.") }

@PostMapping("/save-tag")
@Operation(
method = "POST",
summary = "Save a new tag.",
description = "Save a new tag.",
)
@PreAuthorize("permitAll()")
@ApiResponse(responseCode = "200", description = "Successfully added a new tag to vulnerability")
@ApiResponse(responseCode = "403", description = "Not enough permissions for managing vulnerability")
@ApiResponse(responseCode = "404", description = "Requested vulnerability is not found")
fun saveTag(
@RequestParam vulnerabilityName: String,
@RequestParam tag: String,
authentication: Authentication
) = vulnerabilityName.toMono()
.filter { vulnerabilityPermissionEvaluator.hasPermission(authentication, vulnerabilityName, Permission.WRITE) }
.switchIfEmptyToResponseException(HttpStatus.FORBIDDEN) { "Not enough permission for managing $vulnerabilityName." }
.flatMap { blockingToMono { vulnerabilityService.addTag(vulnerabilityName, tag) } }
.map { StringResponse.ok("Successfully added tag $tag to vulnerability $vulnerabilityName.") }

@DeleteMapping("/delete-tag")
@Operation(
method = "DELETE",
summary = "Delete a tag.",
description = "Delete a tag.",
)
@PreAuthorize("permitAll()")
@ApiResponse(responseCode = "200", description = "Successfully deleted a tag in vulnerability")
@ApiResponse(responseCode = "403", description = "Not enough permissions for managing vulnerability")
@ApiResponse(responseCode = "404", description = "Requested vulnerability is not found")
fun deleteTag(
@RequestParam vulnerabilityName: String,
@RequestParam tag: String,
authentication: Authentication
) = vulnerabilityName.toMono()
.filter { vulnerabilityPermissionEvaluator.hasPermission(authentication, vulnerabilityName, Permission.DELETE) }
.switchIfEmptyToResponseException(HttpStatus.FORBIDDEN) { "Not enough permission for managing $vulnerabilityName." }
.flatMap { blockingToMono { vulnerabilityService.deleteTag(vulnerabilityName, tag) } }
.map { StringResponse.ok("Successfully deleted tag $tag in vulnerability $vulnerabilityName.") }
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.saveourtool.save.entities.vulnerability.VulnerabilityProjectDto
import com.saveourtool.save.entities.vulnerability.VulnerabilityStatus
import com.saveourtool.save.filters.VulnerabilityFilter
import com.saveourtool.save.info.UserInfo
import com.saveourtool.save.utils.DATABASE_DELIMITER
import com.saveourtool.save.utils.getByIdOrNotFound
import com.saveourtool.save.utils.orNotFound

Expand Down Expand Up @@ -48,7 +49,10 @@ class VulnerabilityService(
* @param status status of vulnerability
* @return vulnerability by name
*/
fun findByVulnerabilityIdentifierAndStatus(vulnerabilityIdentifier: String, status: VulnerabilityStatus) = vulnerabilityRepository.findByVulnerabilityIdentifierAndStatus(
fun findByVulnerabilityIdentifierAndStatus(
vulnerabilityIdentifier: String,
status: VulnerabilityStatus,
) = vulnerabilityRepository.findByVulnerabilityIdentifierAndStatus(
vulnerabilityIdentifier,
status
)
Expand Down Expand Up @@ -124,10 +128,16 @@ class VulnerabilityService(
}
} ?: cb.and()

val tagsPredicate = tags.map { tag ->
cb.like(root.get("tags"), tag)
}
.let { cb.and(*it.toTypedArray()) }

cb.and(
namePredicate,
cb.equal(root.get<Vulnerability>("status"), status),
ownerPredicate,
tagsPredicate,
cb.equal(root.get<Vulnerability>("status"), status),
)
}
}
Expand Down Expand Up @@ -155,8 +165,14 @@ class VulnerabilityService(
cb.like(root.get("name"), "%$prefixName%")
}

val tagsPredicate = tags.map { tag ->
cb.like(root.get("tags"), tag)
}
.let { cb.and(*it.toTypedArray()) }

cb.and(
namePredicate,
tagsPredicate,
cb.equal(root.get<Vulnerability>("status"), status),
)
}
Expand Down Expand Up @@ -213,6 +229,7 @@ class VulnerabilityService(
status = VulnerabilityStatus.CREATED,
userId = user.requiredId(),
organization = organizationNew,
tags = vulnerabilityDto.tags.joinToString(DATABASE_DELIMITER),
)
val vulnerabilityNew = vulnerabilityRepository.saveAndFlush(vulnerability)
val newName = "SOTV-${LocalDateTime.now().year}-${vulnerabilityNew.id}"
Expand Down Expand Up @@ -300,7 +317,8 @@ class VulnerabilityService(
userRepository.save(user)

vulnerabilityDto.organization?.name?.let { organizationName ->
val organizationNew = organizationRepository.findByName(organizationName).orNotFound { "Not found organization by name = $organizationName" }
val organizationNew = organizationRepository.findByName(organizationName)
.orNotFound { "Not found organization by name = $organizationName" }
organizationNew.apply { rating += VULNERABILITY_ORGANIZATION_RATING }
organizationRepository.save(organizationNew)
}
Expand Down Expand Up @@ -420,7 +438,10 @@ class VulnerabilityService(
* @return updated [Vulnerability]
*/
@Transactional
fun deleteProject(name: String, vulnerabilityName: String) = vulnerabilityProjectRepository.deleteByNameAndVulnerabilityName(name, vulnerabilityName)
fun deleteProject(
name: String,
vulnerabilityName: String,
) = vulnerabilityProjectRepository.deleteByNameAndVulnerabilityName(name, vulnerabilityName)

/**
* @param dateDto [VulnerabilityDateDto] that corresponds with [VulnerabilityDate] that should be deleted
Expand All @@ -434,6 +455,36 @@ class VulnerabilityService(
.orNotFound { "Could not find date for ${dateDto.vulnerabilityName} of type ${dateDto.type} (${dateDto.date})." }
.let { vulnerabilityDateRepository.delete(it) }

/**
* @param vulnerabilityName [Vulnerability.name]
* @param tag tag to add
* @return updated [Vulnerability]
*/
@Transactional
fun addTag(vulnerabilityName: String, tag: String): Vulnerability {
val vulnerability = vulnerabilityRepository.findByName(vulnerabilityName).orNotFound {
"Could not find vulnerability $vulnerabilityName"
}.apply {
tags = getTags().plus(tag).sorted().joinToString(DATABASE_DELIMITER)
}
return vulnerabilityRepository.save(vulnerability)
}

/**
* @param vulnerabilityName [Vulnerability.name]
* @param tag tag to delete
* @return updated [Vulnerability]
*/
@Transactional
fun deleteTag(vulnerabilityName: String, tag: String): Vulnerability {
val vulnerability = vulnerabilityRepository.findByName(vulnerabilityName).orNotFound {
"Could not find vulnerability $vulnerabilityName"
}.apply {
tags = getTags().minus(tag).sorted().joinToString(DATABASE_DELIMITER)
}
return vulnerabilityRepository.save(vulnerability)
}

companion object {
private const val VULNERABILITY_ORGANIZATION_RATING = 10
private const val VULNERABILITY_OWNER_RATING = 10
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import kotlinx.serialization.Serializable
* @property status
* @property creationDateTime [LocalDateTime] of creation
* @property lastUpdatedDateTime [LocalDateTime] of last updating
* @property tags
*/
@Serializable
data class VulnerabilityDto(
Expand All @@ -39,6 +40,7 @@ data class VulnerabilityDto(
val status: VulnerabilityStatus,
val creationDateTime: LocalDateTime? = null,
val lastUpdatedDateTime: LocalDateTime? = null,
val tags: Set<String> = emptySet(),
) {
/**
* @return map where key is LocalDateTime and value is a label of LocalDateTime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import kotlinx.serialization.Serializable
* @property prefixName
* @property status
* @property isOwner
* @property tags
*/
@Serializable
data class VulnerabilityFilter(
val prefixName: String,
val status: VulnerabilityStatus = VulnerabilityStatus.APPROVED,
val isOwner: Boolean = false,
val tags: List<String> = emptyList(),
) {
companion object {
val approved = VulnerabilityFilter("")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.saveourtool.save.entities.vulnerability.VulnerabilityLanguage
import com.saveourtool.save.entities.vulnerability.VulnerabilityStatus
import com.saveourtool.save.info.UserInfo
import com.saveourtool.save.spring.entity.BaseEntityWithDateAndDto
import com.saveourtool.save.utils.DATABASE_DELIMITER

import com.fasterxml.jackson.annotation.JsonIgnore

Expand All @@ -25,6 +26,7 @@ import kotlinx.datetime.toKotlinLocalDateTime
* @property language
* @property organization
* @property status
* @property tags
**/
@Entity
@Suppress("LongParameterList")
Expand Down Expand Up @@ -63,7 +65,13 @@ class Vulnerability(
@JoinColumn(name = "organization_id")
var organization: Organization?,

var tags: String,
) : BaseEntityWithDateAndDto<VulnerabilityDto>() {
/**
* @return list of [tags] as list of strings
*/
fun getTags() = tags.split(DATABASE_DELIMITER).filter { it.isNotBlank() }.toSet()

/**
* @return a vulnerability dto
*/
Expand All @@ -83,6 +91,7 @@ class Vulnerability(
status = status,
creationDateTime = createDate?.toKotlinLocalDateTime(),
lastUpdatedDateTime = updateDate?.toKotlinLocalDateTime(),
tags = getTags(),
)

/**
Expand All @@ -104,5 +113,6 @@ class Vulnerability(
status = status,
creationDateTime = createDate?.toKotlinLocalDateTime(),
lastUpdatedDateTime = updateDate?.toKotlinLocalDateTime(),
tags = getTags(),
)
}
Loading

0 comments on commit 5bbecd5

Please sign in to comment.