Skip to content

Commit 02a727b

Browse files
authored
Unified the code which renders the table of vulnerabilities (#2835)
### What's done: - added `vulnerabilityTableComponent` which renders the table of vulnerabilities on `VulnerabilityCollection`, `UserProfile` and `Organization` pages. - reworked `vulnerabilityCollectionView`, deleted `renderVulnerabilityTableForProfileView` and `organizationVulnerabilitiesTab`. - added many visual improvements to the table of vulnerabilities. - added support for displaying only `APPROVED` and `AUTO_APPROVED` vulnerabilities depending on whether the user is logged in and his role. - fixed bug that prevented regular (non-admin) user from seeing another user's vulnerabilities. - fixed bug related to highlighting `Users` tab when switching to it on `UserProfile` page. It's part of #2643 Closes #2746
1 parent 369c90e commit 02a727b

14 files changed

Lines changed: 457 additions & 658 deletions

File tree

save-backend/src/main/kotlin/com/saveourtool/save/backend/controllers/vulnerability/VulnerabilityController.kt

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.saveourtool.save.backend.controllers.vulnerability
22

3+
import com.saveourtool.save.authservice.utils.username
34
import com.saveourtool.save.backend.security.VulnerabilityPermissionEvaluator
5+
import com.saveourtool.save.backend.service.LnkUserOrganizationService
46
import com.saveourtool.save.backend.service.vulnerability.VulnerabilityService
57
import com.saveourtool.save.backend.utils.hasRole
68
import com.saveourtool.save.configs.ApiSwaggerSupport
@@ -46,6 +48,7 @@ class VulnerabilityController(
4648
private val vulnerabilityService: VulnerabilityService,
4749
private val vulnerabilityPermissionEvaluator: VulnerabilityPermissionEvaluator,
4850
private val cosvService: CosvService,
51+
private val lnkUserOrganizationService: LnkUserOrganizationService,
4952
) {
5053
@PostMapping("/by-filter")
5154
@Operation(
@@ -83,13 +86,25 @@ class VulnerabilityController(
8386
filter: VulnerabilityFilter,
8487
authentication: Authentication?,
8588
) {
86-
if (
87-
// if user is not authenticated, he will have authentication = null and will not get other's submitted vulnerabilities
88-
(filter.statuses?.contains(VulnerabilityStatus.APPROVED) != true || filter.statuses?.contains(VulnerabilityStatus.AUTO_APPROVED) != true) &&
89-
authentication?.name != filter.authorName &&
90-
// only if user is NOT admin, if admin - everything is fine
91-
authentication?.hasRole(Role.SUPER_ADMIN) == false
92-
) {
89+
/* Everyone has permission to public vulnerabilities (that have status APPROVED or AUTO_APPROVED), also permission have:
90+
1) super admins to all vulnerabilities;
91+
2) users to their vulnerabilities;
92+
3) users to organization vulnerabilities in which they have ADMIN role or higher; */
93+
val isPublicVulnerabilities = filter.statuses?.isNotEmpty() == true &&
94+
filter.statuses?.all { it in listOf(VulnerabilityStatus.APPROVED, VulnerabilityStatus.AUTO_APPROVED) } == true
95+
96+
if (!isPublicVulnerabilities && authentication != null) {
97+
val isSuperAdmin = authentication.hasRole(Role.SUPER_ADMIN)
98+
val isOwner = filter.authorName?.let { it == authentication.username() } ?: false
99+
val roleInOrganization = filter.organizationName?.let { lnkUserOrganizationService.getGlobalRoleOrOrganizationRole(authentication, it) }
100+
val isAdminInOrganization = roleInOrganization?.isHigherOrEqualThan(Role.ADMIN) ?: false
101+
102+
val isHasAdditionalRights = isSuperAdmin || isOwner || isAdminInOrganization
103+
104+
if (!isHasAdditionalRights) {
105+
throw ResponseStatusException(HttpStatus.FORBIDDEN)
106+
}
107+
} else if (!isPublicVulnerabilities) {
93108
throw ResponseStatusException(HttpStatus.FORBIDDEN)
94109
}
95110
}

save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/entities/vulnerability/VulnerabilityStatus.kt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,36 @@ import kotlinx.serialization.Serializable
55

66
/**
77
* A value for vulnerability statuses.
8+
*
9+
* @property value
810
*/
911
@Serializable
1012
@JsExport
1113
@Suppress("IDENTIFIER_LENGTH")
12-
enum class VulnerabilityStatus {
14+
enum class VulnerabilityStatus(val value: String) {
1315
/**
1416
* Approve status
1517
*/
16-
APPROVED,
18+
APPROVED("Approved"),
1719

1820
/**
1921
* Automatically approved by batch-upload
2022
*/
21-
AUTO_APPROVED,
23+
AUTO_APPROVED("Auto-approved"),
2224

2325
/**
2426
* Created status
2527
*/
26-
CREATED,
28+
CREATED("Created"),
2729

2830
/**
2931
* Review status
3032
*/
31-
PENDING_REVIEW,
33+
PENDING_REVIEW("Pending review"),
3234

3335
/**
3436
* Reject status
3537
*/
36-
REJECTED,
38+
REJECTED("Rejected"),
3739
;
3840
}

save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/filters/VulnerabilityFilter.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ import kotlinx.serialization.Serializable
1717
*/
1818
@Serializable
1919
data class VulnerabilityFilter(
20-
val identifierPrefix: String,
21-
val statuses: List<VulnerabilityStatus>?,
20+
val identifierPrefix: String = "",
21+
val statuses: List<VulnerabilityStatus>? = null,
2222
val isOwner: Boolean = false,
2323
val tags: Set<String> = emptySet(),
2424
val minCriticality: Float = 0.0f,
@@ -28,6 +28,8 @@ data class VulnerabilityFilter(
2828
val organizationName: String? = null,
2929
) {
3030
companion object {
31-
val approved = VulnerabilityFilter("", statuses = listOf(VulnerabilityStatus.APPROVED, VulnerabilityStatus.AUTO_APPROVED))
31+
val empty = VulnerabilityFilter()
32+
val approved = VulnerabilityFilter(statuses = listOf(VulnerabilityStatus.APPROVED, VulnerabilityStatus.AUTO_APPROVED))
33+
val pendingApproval = VulnerabilityFilter(statuses = listOf(VulnerabilityStatus.CREATED, VulnerabilityStatus.PENDING_REVIEW))
3234
}
3335
}

save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/organizations/OrganizationVulnerabilitiesTab.kt

Lines changed: 0 additions & 188 deletions
This file was deleted.

save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/table/filters/VulnerabilitiesFiltersRow.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@file:Suppress("HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE", "FILE_NAME_MATCH_CLASS")
1+
@file:Suppress("FILE_NAME_MATCH_CLASS")
22

33
package com.saveourtool.save.frontend.components.basic.table.filters
44

@@ -60,7 +60,7 @@ val vulnerabilitiesFiltersRow: FC<VulnerabilitiesFiltersProps> = FC { props ->
6060
type = InputType.text
6161
className = ClassName("form-control")
6262
value = filter.identifierPrefix
63-
placeholder = "${"Name".t()}..."
63+
placeholder = "${"Identifier".t()}..."
6464
required = false
6565
onChange = { event ->
6666
setFilter { oldFilter ->
@@ -71,7 +71,7 @@ val vulnerabilitiesFiltersRow: FC<VulnerabilitiesFiltersProps> = FC { props ->
7171
}
7272

7373
div {
74-
className = ClassName("col-4 px-1")
74+
className = ClassName("d-flex col-4 px-1 justify-content-center")
7575
multiRangeSlider {
7676
min = 0.0f
7777
max = 10.0f
@@ -83,7 +83,7 @@ val vulnerabilitiesFiltersRow: FC<VulnerabilitiesFiltersProps> = FC { props ->
8383
ruler = false
8484
label = false
8585
style = jso {
86-
width = 16.4.rem
86+
width = 16.rem
8787
height = 0.rem
8888
border = "none".unsafeCast<Border>()
8989
boxShadow = "none".unsafeCast<BoxShadow>()

save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/tables/TableComponent.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Utilities for react-tables
33
*/
44

5-
@file:Suppress("FILE_NAME_MATCH_CLASS", "MatchingDeclarationName")
5+
@file:Suppress("FILE_NAME_MATCH_CLASS")
66

77
package com.saveourtool.save.frontend.components.tables
88

@@ -56,6 +56,8 @@ import kotlinx.coroutines.isActive
5656
import kotlinx.coroutines.launch
5757

5858
const val TABLE_HEADERS_LOCALE_NAMESPACE = "table-headers"
59+
const val INITIAL_TABLE_PAGE_SIZE = 10
60+
const val VULNERABILITIES_COLLECTION_TABLE_PAGE_SIZE = 7
5961

6062
private typealias TableHeaderBuilder<T> = (ChildrenBuilder, Table<T>, NavigateFunction) -> Unit
6163

@@ -124,7 +126,7 @@ external interface TableProps<D : Any> : Props {
124126
)
125127
fun <D : RowData, P : TableProps<D>> tableComponent(
126128
columns: (P) -> Array<out ColumnDef<D, *>>,
127-
initialPageSize: Int = 10,
129+
initialPageSize: Int = INITIAL_TABLE_PAGE_SIZE,
128130
useServerPaging: Boolean = false,
129131
isTransparentGrid: Boolean = false,
130132
@Suppress("EMPTY_BLOCK_STRUCTURE_ERROR") tableOptionsCustomizer: ChildrenBuilder.(TableOptions<D>) -> Unit = {},
@@ -261,7 +263,7 @@ fun <D : RowData, P : TableProps<D>> tableComponent(
261263
headerGroup.headers.map { header: Header<D, out Any?> ->
262264
val column = header.column
263265
th {
264-
this.className = className
266+
className = ClassName("m-0 font-weight-bold text-center text-nowrap")
265267
+renderHeader(header)
266268
if (column.getCanSort()) {
267269
style = style ?: jso()

0 commit comments

Comments
 (0)