Skip to content
This repository has been archived by the owner on Nov 6, 2024. It is now read-only.

[BCE-26147] Add SAST findings to JetBrains #99

Merged
merged 1 commit into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion src/main/kotlin/com/bridgecrew/CheckovResult.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,25 @@ data class CheckovResult(
val guideline: String = "\"No Guide\")",
val code_block: List<List<Any>>,
var check_type: String,
val fixed_definition: String = ""
val fixed_definition: String = "",
val cwe: ArrayList<String> = ArrayList(),
val owasp: String = "",
val metadata: Metadata? = null
)

data class Metadata(
val code_locations: List<DataFlow>?,
val taint_mode: List<DataFlow>?
)

data class DataFlow(
val path: String,
val start: CodePosition,
val end: CodePosition,
val code_block: String
)

data class CodePosition(
val row: Int,
val column: Int
)
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.bridgecrew.results

import com.bridgecrew.Metadata

class WeaknessCheckovResult(
checkType: CheckType,
filePath: String,
Expand All @@ -13,7 +15,10 @@ class WeaknessCheckovResult(
fileLineRange: List<Int>,
fixDefinition: String?,
codeBlock: List<List<Any>>,
val checkName: String) :
val checkName: String,
val cwe: List<String>,
val owasp: String,
val metadata: Metadata?) :
BaseCheckovResult(
category = Category.WEAKNESSES,
checkType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ class CheckovResultsListUtils {
checkovResult.checkType, checkovResult.filePath,
checkovResult.resource, checkovResult.name, checkovResult.id, checkovResult.severity, checkovResult.description,
checkovResult.guideline, checkovResult.absoluteFilePath, fileLineRange, checkovResult.fixDefinition,
codeBlock, (checkovResult as SecretsCheckovResult).checkName
codeBlock, (checkovResult as WeaknessCheckovResult).checkName, checkovResult.cwe, checkovResult.owasp, checkovResult.metadata
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ class ResultsCacheService(val project: Project) {
val weaknessCheckovResult = WeaknessCheckovResult(checkType, filePath,
resource, name, result.check_id, severity, description,
result.guideline, fileAbsPath, result.file_line_range, result.fixed_definition,
result.code_block, result.check_name)
result.code_block, result.check_name, result.cwe, result.owasp, result.metadata)
checkovResults.add(weaknessCheckovResult)
continue
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ class CheckovToolWindowManagerPanel(val project: Project) : SimpleToolWindowPane
private fun createIconForLineErrors(firstRow: Int, results: List<BaseCheckovResult>, markup: MarkupModel, document: Document) {
val rowInFile = if(firstRow > 0) firstRow - 1 else firstRow
val rangeHighlighter: RangeHighlighter = markup.addLineHighlighter(rowInFile, HighlighterLayer.ERROR, null)
val bubbleLocation = if(rowInFile >= document.lineCount) document.getLineStartOffset(rowInFile) else document.getLineStartOffset(rowInFile + 1)
val bubbleLocation = if(rowInFile >= document.lineCount - 1) document.getLineStartOffset(rowInFile) else document.getLineStartOffset(rowInFile + 1)
val gutterIconRenderer = CheckovGutterErrorIcon(project, results, bubbleLocation, markup, rowInFile)
rangeHighlighter.gutterIconRenderer = gutterIconRenderer
rangeHighlighter.putUserData(key, true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class CheckovErrorRightPanel(val project: Project, var result: BaseCheckovResult
Category.VULNERABILITIES -> VulnerabilitiesExtraInfoPanel(result as VulnerabilityCheckovResult)
Category.SECRETS -> SecretsExtraInfoPanel(result as SecretsCheckovResult)
Category.LICENSES -> LicenseExtraInfoPanel(result as LicenseCheckovResult)
Category.WEAKNESSES -> WeaknessExtraInfoPanel(result as WeaknessCheckovResult)
Category.WEAKNESSES -> WeaknessExtraInfoPanel(result as WeaknessCheckovResult, project)
}
return ScrollPaneFactory.createScrollPane(
extraInfoPanel,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,45 @@
package com.bridgecrew.ui.rightPanel.dictionaryDetails

import com.bridgecrew.DataFlow
import com.bridgecrew.results.WeaknessCheckovResult
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.OpenFileDescriptor
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.ui.JBColor
import com.intellij.util.ui.JBUI
import java.awt.Cursor
import java.awt.Dimension
import java.awt.Font
import java.awt.GridBagConstraints
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.io.File
import javax.swing.JLabel

class WeaknessDictionaryPanel(result: WeaknessCheckovResult): DictionaryExtraInfoPanel() {
data class DataFlowDictionary(
val fileName: String,
val path: String,
val codeBlock: String,
val column: Int,
val row: Int,
)

class WeaknessDictionaryPanel(private val result: WeaknessCheckovResult, private val project: Project) : DictionaryExtraInfoPanel() {
override var fieldsMap: MutableMap<String, Any?> = mutableMapOf(
"Description" to result.description,
"Code" to extractCode(result)
"Code" to extractCode(result),
"CWE(s)" to result.cwe.joinToString(", "),
"OWASP Top 10" to result.owasp,
"Data flow" to extractDataFlow(result)
)

init {
addCustomPolicyGuidelinesIfNeeded(result)
createDictionaryLayout()
createDataFlowLayout()
}

private fun extractCode(result: WeaknessCheckovResult): Any {
Expand All @@ -19,4 +49,90 @@ class WeaknessDictionaryPanel(result: WeaknessCheckovResult): DictionaryExtraInf
""
}
}

private fun extractDataFlow(result: WeaknessCheckovResult): String? {
val dataFlow = result.metadata?.code_locations ?: result.metadata?.taint_mode
if (dataFlow !== null) {
return this.calculateDataFlow(dataFlow);
}
return null
}

private fun calculateDataFlow(dataFlowList: List<DataFlow>): String {
val filesCount = dataFlowList.map { it.path }.distinct().count()
val stepsCount = dataFlowList.count()
return "$stepsCount steps in $filesCount file(s)"
}


private fun getDataFlowDictionary(result: WeaknessCheckovResult): Array<DataFlowDictionary>? {
val dataFlow = result.metadata?.code_locations ?: result.metadata?.taint_mode

if (dataFlow !== null) {
return dataFlow.map {
val line = it.start.row.toString() + if (it.start.row != it.end.row) "-${it.end.row}" else ""
DataFlowDictionary("${File(it.path).name}: $line", it.path, it.code_block, it.start.column, it.start.row)
}.toTypedArray()
}
return null
}

private fun openFileAtLine(project: Project, absPath: String, line: Int, column: Int) {
val virtualFile = LocalFileSystem.getInstance().findFileByPath(absPath)
if (virtualFile != null) {
val editor: Editor? = FileEditorManager.getInstance(project).openTextEditor(OpenFileDescriptor(project, virtualFile, line, column), true)
editor?.caretModel?.moveToLogicalPosition(LogicalPosition(line, column))
}
}


private fun createDataFlowLayout() {
val dataArray = this.getDataFlowDictionary(this.result) ?: return
val dictionaryFont = Font("SF Pro Text", Font.BOLD, 12)

val maxKeyWidth = dataArray.maxOfOrNull {
getFontMetrics(dictionaryFont).stringWidth(it.fileName)
} ?: 0

val keyConstraints = GridBagConstraints().apply {
weightx = 0.0
fill = GridBagConstraints.VERTICAL
anchor = GridBagConstraints.LINE_START
insets = JBUI.insets(1, 0, 1, 15)
}

val valueConstraints = GridBagConstraints().apply {
weightx = 1.0
fill = GridBagConstraints.HORIZONTAL
gridwidth = GridBagConstraints.REMAINDER
insets = JBUI.insetsBottom(10)
}

val boldFont = Font(dictionaryFont.name, Font.BOLD, dictionaryFont.size)

for (item in dataArray) {
val valueAsString = if (item.codeBlock.isEmpty()) "---" else item.codeBlock.trim()
// Create a clickable JLabel for the key
val keyLabel = JLabel("<html><a href=''>${item.fileName}</a></html>")
keyLabel.font = boldFont
keyLabel.preferredSize = Dimension(maxKeyWidth + 50, keyLabel.preferredSize.height)
keyLabel.cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
keyLabel.toolTipText = item.fileName
keyLabel.setOpaque(true); // Make the JLabel opaque to show the background color
keyLabel.setBackground(JBColor.PanelBackground);
// Add mouse listener to the key label
keyLabel.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
openFileAtLine(project, item.path, item.row, item.column)
}
})

add(keyLabel, keyConstraints)
val valueLabel = JLabel(valueAsString)
valueLabel.font = dictionaryFont.deriveFont(Font.PLAIN)
valueLabel.toolTipText = valueAsString
add(valueLabel, valueConstraints)
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ package com.bridgecrew.ui.rightPanel.extraInfoPanel

import com.bridgecrew.results.WeaknessCheckovResult
import com.bridgecrew.ui.rightPanel.dictionaryDetails.WeaknessDictionaryPanel
import com.intellij.openapi.project.Project

class WeaknessExtraInfoPanel(result: WeaknessCheckovResult) : CheckovExtraInfoPanel(result) {
class WeaknessExtraInfoPanel(result: WeaknessCheckovResult, project: Project) : CheckovExtraInfoPanel(result) {

init {
initLayout()
add(WeaknessDictionaryPanel(result))
add(WeaknessDictionaryPanel(result, project))
addCodeDiffPanel()
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.bridgecrew.services.fixtures
package com.bridgecrew.fixtures

import com.bridgecrew.CheckovResult

Expand All @@ -19,11 +19,11 @@ fun createDefaultResults(): CheckovResult {
guideline = "",
code_block = listOf(listOf(1.0, "class MyBadImplementation extends java.security.MessageDigest {], [2.0 ], [3.0, }")),
check_type = "sast_java",
fixed_definition = ""
fixed_definition = "",
)
}

fun createSastCheckovResultResults(): CheckovResult {
fun createSastCheckovResult(): CheckovResult {
return CheckovResult(
check_id = "CKV3_SAST_13",
bc_check_id = "",
Expand All @@ -40,6 +40,7 @@ fun createSastCheckovResultResults(): CheckovResult {
guideline = "",
code_block = listOf(listOf(1.0, "class MyBadImplementation extends java.security.MessageDigest {], [2.0 ], [3.0, }")),
check_type = "sast_java",
cwe = arrayListOf("CWE-327: Use of a Broken or Risky Cryptographic Algorithm"),
fixed_definition = ""
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.bridgecrew.fixtures

import com.bridgecrew.results.WeaknessCheckovResult
import com.google.gson.Gson



const val metadataCodeCollectionJson = """
"metadata": {
"code_locations": [{
"path": "/Users/sast-core/tests_policies/src/python/EncryptionKeySize2.py",
"start": {
"row": 13,
"column": 0
},
"end": {
"row": 13,
"column": 66
},
"code_block": "cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key"
}, {
"path": "/Users/sast-core/tests_policies/src/python/EncryptionKeySize.py",
"start": {
"row": 14,
"column": 73
},
"end": {
"row": 15,
"column": 86
},
"code_block": "key_size=size"
}]
},
"""

const val metadataTaintModeJson = """
"metadata": {
"taint_mode": [{
"path": "/Users/sast-core/tests_policies/src/python/EncryptionKeySize.py",
"start": {
"row": 13,
"column": 0
},
"end": {
"row": 13,
"column": 66
},
"code_block": "cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key"
}, {
"path": "/Users/sast-core/tests_policies/src/python/EncryptionKeySize.py",
"start": {
"row": 14,
"column": 73
},
"end": {
"row": 15,
"column": 86
},
"code_block": "key_size=size"
}]
},
"""

const val metadataEmptyJson = """
"metadata": {
},
"""

const val metadataAbsent = ""



fun createWeaknessCheckovResult(metadata: String): WeaknessCheckovResult {
val resultJson = """
{
"checkName": "Unsafe custom MessageDigest is implemented",
"cwe": ["CWE-327: Use of a Broken or Risky Cryptographic Algorithm"],
"owasp": "TBD",
$metadata
"category": "WEAKNESSES",
"checkType": "SAST",
"filePath": "/features/sast/BrokenCryptographicAlgorithm/fail.java",
"resource": "",
"name": "Unsafe custom MessageDigest is implemented (1 - 2)",
"id": "CKV3_SAST_13",
"severity": "MEDIUM",
"guideline": "https://docs.paloaltonetworks.com/prisma/prisma-cloud/prisma-cloud-code-security-policy-reference/sast-policies/java-policies/sast-policy-13",
"absoluteFilePath": "/Users/user/testing-resources/features/sast/BrokenCryptographicAlgorithm/fail.java",
"fileLineRange": [1, 2],
"codeBlock": [
[1.0, "class MyBadImplementation extends java.security.MessageDigest {\n"],
[2.0, "}\n"]
],
"codeDiffFirstLine": 1
}
""".trimIndent()

val gson = Gson()
val userObject: WeaknessCheckovResult = gson.fromJson(resultJson, WeaknessCheckovResult::class.java)

return userObject
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.bridgecrew.services

import com.bridgecrew.fixtures.createSastCheckovResult
import com.bridgecrew.results.Category
import com.bridgecrew.results.CheckType
import com.bridgecrew.services.fixtures.*
import com.bridgecrew.fixtures.*
import com.intellij.mock.MockProject
import com.intellij.openapi.util.Disposer
import org.jetbrains.annotations.SystemIndependent
Expand All @@ -26,7 +27,7 @@ class ResultsCacheServiceTest {
fun `setCheckovResultsFromResultsList should set WeaknessCheckovResult`() {
val resultsCacheService = ResultsCacheService(project)

val checkovResult = createSastCheckovResultResults()
val checkovResult = createSastCheckovResult()
resultsCacheService.setCheckovResultsFromResultsList(listOf(checkovResult));

assertEquals(resultsCacheService.checkovResults.size, 1)
Expand Down
Loading
Loading