Skip to content

Commit

Permalink
Added editing criticality with calculator (#2740)
Browse files Browse the repository at this point in the history
### What's done:
- added logic for editing criticality with calculator.
- added display of already selected metrics when using calculator for proposing or editing vulnerability.
- fixed case when after editing vulnerability`s fields and then canceling the changes made, these changes were still displayed.
- fixed behavior in some cases when vulnerability hasn't any severities, or has only CVSS_V2 severity, or has many CVSS_V2/CVSS_V3 severities at the same time.
- fixed that calculator wasn't in center of screen.
- fixed unnecessary multiple checking current user`s rights for editing vulnerability.

Closes #2726
  • Loading branch information
DrAlexD authored Oct 19, 2023
1 parent e43d5a3 commit cbb22c7
Show file tree
Hide file tree
Showing 11 changed files with 162 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.saveourtool.save.utils.shorten
import com.saveourtool.save.utils.warn

import com.saveourtool.osv4k.Severity
import com.saveourtool.osv4k.SeverityType
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
Expand Down Expand Up @@ -91,7 +92,7 @@ class VulnerabilityMetadataService(
identifier = id,
summary = summary ?: details?.take(SUMMARY_LENGTH_FROM_DETAILS)?.let { "$it ..." } ?: "Summary not provided",
details = details ?: "Details not provided",
severityNum = severity?.firstOrNull()?.let { getScore(it) } ?: 0f,
severityNum = getSeverityNumber(),
modified = modified.toJavaLocalDateTime(),
submitted = getCurrentLocalDateTime().toJavaLocalDateTime(),
user = user,
Expand Down Expand Up @@ -153,12 +154,19 @@ class VulnerabilityMetadataService(
): VulnerabilityMetadata = apply {
summary = entry.summary?.shorten(SUMMARY_LENGTH) ?: entry.details?.take(SUMMARY_LENGTH_FROM_DETAILS)?.let { "$it$ELLIPSIS" } ?: "Summary not provided"
details = entry.details ?: "Details not provided"
severityNum = entry.severity?.firstOrNull()?.let { getScore(it) } ?: 0f
severityNum = entry.getSeverityNumber()
modified = entry.modified.toJavaLocalDateTime()
latestCosvFile = cosvFile
status = isAutoApprove.toVulnerabilityStatus()
}

private fun CosvSchema<*, *, *, *>.getSeverityNumber() =
severity?.find { it.type == SeverityType.CVSS_V3 }?.let {
getScore(it)
} ?: severity?.find { it.type == SeverityType.CVSS_V2 }?.let {
getScore(it)
} ?: 0f

private fun Boolean.toVulnerabilityStatus() = if (this) VulnerabilityStatus.AUTO_APPROVED else VulnerabilityStatus.PENDING_REVIEW
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,24 +101,6 @@ fun ChildrenBuilder.displayModal(
buttonBuilder: ChildrenBuilder.() -> Unit,
) = doCreateDisplayModal(false, isOpen, title, message, modalStyle, onCloseButtonPressed, buttonBuilder)

/**
* @see displayModal
*/
@Suppress(
"LongParameterList",
"TOO_MANY_PARAMETERS",
"KDOC_WITHOUT_PARAM_TAG",
"KDOC_WITHOUT_RETURN_TAG"
)
fun ChildrenBuilder.displayModalWithPreTag(
isOpen: Boolean,
title: String,
message: String,
modalStyle: Styles = defaultModalStyle,
onCloseButtonPressed: (() -> Unit)? = null,
buttonBuilder: ChildrenBuilder.() -> Unit,
) = doCreateDisplayModal(true, isOpen, title, message, modalStyle, onCloseButtonPressed, buttonBuilder)

/**
* Universal function to create modals with click condition styles inside react modals
*
Expand Down Expand Up @@ -181,39 +163,6 @@ fun ChildrenBuilder.displayModal(
displayModal(opener.isOpen(), title, message, modalStyle, opener.closeWindowAction(), buttonBuilder)
}

/**
* Universal function to create modals for confirmation.
*
* @param windowOpenness
* @param title title of the modal that will be shown in top-left corner
* @param message main text that will be shown in the center of modal
* @param modalStyle [Styles] that will be applied to react modal
* @param successAction lambda for success action
*/
fun ChildrenBuilder.displayConfirmationModal(
windowOpenness: WindowOpenness,
title: String,
message: String,
modalStyle: Styles = mediumTransparentModalStyle,
successAction: () -> Unit,
) {
displayModal(
isOpen = windowOpenness.isOpen(),
title = title,
message = message,
modalStyle = modalStyle,
onCloseButtonPressed = windowOpenness.closeWindowAction()
) {
buttonBuilder("Ok") {
successAction()
windowOpenness.closeWindow()
}
buttonBuilder("Cancel", "secondary") {
windowOpenness.closeWindow()
}
}
}

/**
* Universal function to create modals for confirmation.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,19 @@ val largeTransparentModalStyle = Styles(
overlay = defaultOverlayProperties,
)

val calculatorModalStyle = Styles(
content = json(
"top" to "5%",
"left" to "5%",
"right" to "16%",
"bottom" to "2%",
"overflow" to "hide",
"backgroundColor" to "transparent",
"border" to "1px solid rgba(255, 255, 255, 0.01)"
).unsafeCast<CSSProperties>(),
overlay = defaultOverlayProperties,
)

val loaderModalStyle = Styles(
content = json(
"top" to "25%",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package com.saveourtool.save.frontend.components.views.vuln

import com.saveourtool.save.cvsscalculator.CvssVersion
import com.saveourtool.save.cvsscalculator.calculateBaseScore
import com.saveourtool.save.cvsscalculator.parsingVectorToMap
import com.saveourtool.save.cvsscalculator.v3.CvssVectorV3
import com.saveourtool.save.entities.OrganizationDto
import com.saveourtool.save.frontend.components.basic.addUserComponent
Expand All @@ -12,8 +13,8 @@ import com.saveourtool.save.frontend.components.basic.renderAvatar
import com.saveourtool.save.frontend.components.inputform.InputTypes
import com.saveourtool.save.frontend.components.inputform.inputTextFormOptional
import com.saveourtool.save.frontend.components.modal.MAX_Z_INDEX
import com.saveourtool.save.frontend.components.modal.calculatorModalStyle
import com.saveourtool.save.frontend.components.modal.displayModal
import com.saveourtool.save.frontend.components.modal.largeTransparentModalStyle
import com.saveourtool.save.frontend.components.views.vuln.component.cvssBaseScoreCalculator
import com.saveourtool.save.frontend.components.views.vuln.component.uploadCosvButton
import com.saveourtool.save.frontend.externals.fontawesome.*
Expand Down Expand Up @@ -132,6 +133,9 @@ val createVulnerabilityView: VFC = VFC {
"Base Score Calculator".t(),
bodyBuilder = {
cvssBaseScoreCalculator {
baseMetricsOfVulnerability = cosv.severity?.let { severityList ->
severityList.singleOrNull { it.type == SeverityType.CVSS_V3 }?.let { CvssVectorV3(it.score.parsingVectorToMap()).baseMetrics }
}
onCloseButton = { cvssCalculatorWindowOpenness.closeWindow() }
onCloseButtonPassed = { baseMetrics ->
val cvssVector = CvssVectorV3(CvssVersion.CVSS_V3_1, baseMetrics)
Expand All @@ -140,7 +144,7 @@ val createVulnerabilityView: VFC = VFC {
}
}
},
modalStyle = largeTransparentModalStyle,
modalStyle = calculatorModalStyle,
onCloseButtonPressed = cvssCalculatorWindowOpenness.closeWindowAction(),
customWidth = jso { width = 45.rem }
) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,25 @@

package com.saveourtool.save.frontend.components.views.vuln

import com.saveourtool.save.cvsscalculator.CvssVersion
import com.saveourtool.save.cvsscalculator.parsingVectorToMap
import com.saveourtool.save.cvsscalculator.v3.CvssVectorV3
import com.saveourtool.save.entities.cosv.VulnerabilityExt
import com.saveourtool.save.entities.vulnerability.VulnerabilityDto
import com.saveourtool.save.frontend.components.modal.calculatorModalStyle
import com.saveourtool.save.frontend.components.modal.displayModal
import com.saveourtool.save.frontend.components.views.vuln.component.cvssBaseScoreCalculator
import com.saveourtool.save.frontend.externals.i18next.useTranslation
import com.saveourtool.save.frontend.externals.progressbar.progressBar
import com.saveourtool.save.frontend.themes.Colors
import com.saveourtool.save.frontend.utils.buttonBuilder
import com.saveourtool.save.frontend.utils.useWindowOpenness

import com.saveourtool.osv4k.Severity
import com.saveourtool.osv4k.SeverityType
import js.core.jso
import react.FC
import react.Props
import react.StateSetter
import react.dom.html.ReactHTML.a
import react.dom.html.ReactHTML.div
import react.dom.html.ReactHTML.h4
Expand All @@ -24,6 +35,67 @@ private const val MAX_VALUE = 10.0f
val vulnerabilityBadge: FC<VulnerabilityBadgeProps> = FC { props ->
val (t) = useTranslation("vulnerability")
val severityNum = props.vulnerability.metadataDto.severityNum
val cvssCalculatorWindowOpenness = useWindowOpenness()

@Suppress("EMPTY_BLOCK_STRUCTURE_ERROR")
displayModal(
cvssCalculatorWindowOpenness.isOpen(),
"Base Score Calculator".t(),
bodyBuilder = {
cvssBaseScoreCalculator {
baseMetricsOfVulnerability = props.vulnerability.cosv.severity?.let { severityList ->
severityList.singleOrNull { it.type == SeverityType.CVSS_V3 }?.let { CvssVectorV3(it.score.parsingVectorToMap()).baseMetrics }
}

onCloseButton = { cvssCalculatorWindowOpenness.closeWindow() }

onCloseButtonPassed = { baseMetrics ->
props.vulnerability.cosv.severity?.let { currentSeverityList ->
val severityV2Count = currentSeverityList.count { it.type == SeverityType.CVSS_V2 }
val severityList = currentSeverityList.toMutableList()

if (severityV2Count > 1) {
severityList.removeAll { it.type == SeverityType.CVSS_V2 }
}

val cvssVector = CvssVectorV3(CvssVersion.CVSS_V3_1, baseMetrics)
severityList.removeAll { it.type == SeverityType.CVSS_V3 }
severityList.add(
Severity(
type = SeverityType.CVSS_V3,
score = cvssVector.scoreVectorString(),
scoreNum = cvssVector.calculateBaseScore().toString(),
)
)

props.setVulnerability {
it?.copy(
cosv = props.vulnerability.cosv.copy(severity = severityList.toList()),
metadataDto = props.vulnerability.metadataDto.copy(severityNum = cvssVector.calculateBaseScore())
)
}
} ?: run {
val cvssVector = CvssVectorV3(CvssVersion.CVSS_V3_1, baseMetrics)
val severityList = listOf(Severity(
type = SeverityType.CVSS_V3,
score = cvssVector.scoreVectorString(),
scoreNum = cvssVector.calculateBaseScore().toString(),
))

props.setVulnerability {
it?.copy(
cosv = props.vulnerability.cosv.copy(severity = severityList),
metadataDto = props.vulnerability.metadataDto.copy(severityNum = cvssVector.calculateBaseScore())
)
}
}
}
}
},
modalStyle = calculatorModalStyle,
onCloseButtonPressed = cvssCalculatorWindowOpenness.closeWindowAction(),
customWidth = jso { width = 45.rem }
) { }

val (color, criticalityLabel) = when (severityNum) {
in 0f..FOR_GREEN -> Colors.SUCCESS.value to "Low"
Expand Down Expand Up @@ -83,6 +155,17 @@ val vulnerabilityBadge: FC<VulnerabilityBadgeProps> = FC { props ->
}
}
}
div {
className = ClassName("col-2 ml-4")
div {
className = ClassName("row align-items-start justify-content-end")
if (!props.isEditDisabled) {
buttonBuilder("Edit".t(), style = "primary", isOutline = false) {
cvssCalculatorWindowOpenness.openWindow()
}
}
}
}
}
}
}
Expand All @@ -93,7 +176,17 @@ val vulnerabilityBadge: FC<VulnerabilityBadgeProps> = FC { props ->
*/
external interface VulnerabilityBadgeProps : Props {
/**
* [VulnerabilityDto] to display
* Displayed vulnerability
*/
var vulnerability: VulnerabilityExt

/**
* Vulnerability setter
*/
var setVulnerability: StateSetter<VulnerabilityExt?>

/**
* If the edit button was not yet pressed
*/
var isEditDisabled: Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import kotlinx.datetime.TimeZone
@Suppress("EMPTY_BLOCK_STRUCTURE_ERROR", "MAGIC_NUMBER")
val vulnerabilityGeneralInfoProps: FC<VulnerabilityGeneralInfoProps> = FC { props ->
val (t) = useTranslation("vulnerability")
val (cachedVulnerability, setCachedVulnerability) = useState<VulnerabilityExt>()

with(props.vulnerability) {
div {
Expand All @@ -53,9 +54,8 @@ val vulnerabilityGeneralInfoProps: FC<VulnerabilityGeneralInfoProps> = FC { prop
}
}
if (props.isEditDisabled) {
// only Super Users and owners of unapproved vuln. can edit it
if (props.userInfo?.isSuperAdmin() == true ||
(props.userInfo?.name == metadataDto.user.name && props.vulnerability.metadataDto.status != VulnerabilityStatus.APPROVED)) {
// only Super Users or owners/participants of unapproved vulnerability can edit it
if (hasRightsToEdit(props.currentUserInfo, this@with)) {
buttonBuilder(
labelBuilder = {
p {
Expand All @@ -69,6 +69,7 @@ val vulnerabilityGeneralInfoProps: FC<VulnerabilityGeneralInfoProps> = FC { prop
},
isOutline = true, classes = "text-xs text-left ml-auto"
) {
setCachedVulnerability(props.vulnerability.copy())
props.setIsEditDisabled(false)
}
}
Expand All @@ -78,7 +79,7 @@ val vulnerabilityGeneralInfoProps: FC<VulnerabilityGeneralInfoProps> = FC { prop
props.setIsEditDisabled(true)
}
buttonBuilder(faTimesCircle, null, isOutline = true) {
props.setVulnerability(props.vulnerability)
props.setVulnerability(cachedVulnerability)
props.setIsEditDisabled(true)
}
}
Expand Down Expand Up @@ -188,17 +189,16 @@ val vulnerabilityGeneralInfoProps: FC<VulnerabilityGeneralInfoProps> = FC { prop

// ================= tags ===================

if (props.canEditVulnerability || props.vulnerability.metadataDto.tags.isNotEmpty()) {
if (!props.isEditDisabled || props.vulnerability.metadataDto.tags.isNotEmpty()) {
hr { }
h6 {
className = ClassName("font-weight-bold text-primary-blue mb-4")
+"Tags".t()
}
div {
vulnerabilityTagsComponent {
this.userInfo = props.userInfo
this.currentUserInfo = props.currentUserInfo
this.vulnerability = props.vulnerability
this.canEditVulnerability = props.canEditVulnerability
this.isEditDisabled = props.isEditDisabled
this.setVulnerabilityExt = props.setVulnerability
}
Expand Down Expand Up @@ -316,15 +316,10 @@ external interface VulnerabilityGeneralInfoProps : Props {
*/
var fetchVulnerability: () -> Unit

/**
* FLag that defines if current user can edit vulnerability or not
*/
var canEditVulnerability: Boolean

/**
* Currently logged-in user or null
*/
var userInfo: UserInfo?
var currentUserInfo: UserInfo?

/**
* If the edit button was not yet pressed
Expand All @@ -346,3 +341,12 @@ external interface VulnerabilityGeneralInfoProps : Props {
*/
var setSelectedMenu: StateSetter<VulnerabilityTab>
}

/**
* @param currentUserInfo
* @param vulnerability
* @return if user can press edit button
*/
private fun hasRightsToEdit(currentUserInfo: UserInfo?, vulnerability: VulnerabilityExt): Boolean =
currentUserInfo?.isSuperAdmin() == true || ((currentUserInfo?.name == vulnerability.metadataDto.user.name ||
currentUserInfo?.name in vulnerability.getAllParticipants().map { it.name }) && vulnerability.metadataDto.status != VulnerabilityStatus.APPROVED)
Loading

0 comments on commit cbb22c7

Please sign in to comment.