diff --git a/Makefile b/Makefile
index c16cea52..934d4d7b 100644
--- a/Makefile
+++ b/Makefile
@@ -23,7 +23,7 @@ RM = rm -rf
GOSEC = /opt/homebrew/bin/gosec
# Targets
-.phony: all prep run_tests clean tidy install uninstall gosec
+.phony: all prep run_tests clean tidy install uninstall gosec gv
default: all
@@ -60,6 +60,11 @@ uninstall:
gosec:
$(GOSEC) ./...
+gv: out/tmp/diagram.png
+
+out/tmp/diagram.png: out/tmp/diagram.gv
+ dot -Tpng $< -o $@
+
bin/raa_calc: cmd/raa/main.go
$(GO) build $(GOFLAGS) -o $@ $<
diff --git a/internal/threagile/root.go b/internal/threagile/root.go
index 513fa5ad..3005ce40 100644
--- a/internal/threagile/root.go
+++ b/internal/threagile/root.go
@@ -33,8 +33,8 @@ Aliases:
Examples:
{{.Example}}{{end}}{{if .HasAvailableSubCommands}}
-Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
- {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasHelpSubCommands}}
+Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Title "help"))}}
+ {{rpad .Title .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasHelpSubCommands}}
Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}
diff --git a/pkg/common/config.go b/pkg/common/config.go
index 4ad139e3..9b15f31c 100644
--- a/pkg/common/config.go
+++ b/pkg/common/config.go
@@ -39,6 +39,8 @@ type Config struct {
RiskRulesPlugins []string
SkipRiskRules string
ExecuteModelMacro string
+ HideColumns []string
+ GroupByColumns []string
ServerMode bool
DiagramDPI int
@@ -81,6 +83,8 @@ func (c *Config) Defaults(buildTimestamp string) *Config {
TemplateFilename: TemplateFilename,
RAAPlugin: RAAPluginName,
RiskRulesPlugins: make([]string, 0),
+ HideColumns: make([]string, 0),
+ GroupByColumns: make([]string, 0),
SkipRiskRules: "",
ExecuteModelMacro: "",
ServerMode: false,
@@ -259,6 +263,12 @@ func (c *Config) Merge(config Config, values map[string]any) {
case strings.ToLower("RiskRulesPlugins"):
c.RiskRulesPlugins = config.RiskRulesPlugins
+ case strings.ToLower("HideColumns"):
+ c.HideColumns = append(c.HideColumns, config.HideColumns...)
+
+ case strings.ToLower("GroupByColumns"):
+ c.GroupByColumns = append(c.GroupByColumns, config.GroupByColumns...)
+
case strings.ToLower("SkipRiskRules"):
c.SkipRiskRules = config.SkipRiskRules
diff --git a/pkg/report/excel-column.go b/pkg/report/excel-column.go
new file mode 100644
index 00000000..d71bf692
--- /dev/null
+++ b/pkg/report/excel-column.go
@@ -0,0 +1,65 @@
+package report
+
+import (
+ "github.com/xuri/excelize/v2"
+ "strings"
+)
+
+type ExcelColumns map[string]ExcelColumn
+
+func (what *ExcelColumns) GetColumns() ExcelColumns {
+ *what = map[string]ExcelColumn{
+ "A": {Title: "Severity", Width: 12},
+ "B": {Title: "Likelihood", Width: 15},
+ "C": {Title: "Impact", Width: 15},
+ "D": {Title: "STRIDE", Width: 22},
+ "E": {Title: "Function", Width: 16},
+ "F": {Title: "CWE", Width: 12},
+ "G": {Title: "Risk Category", Width: 50},
+ "H": {Title: "Technical Asset", Width: 50},
+ "I": {Title: "Communication Link", Width: 50},
+ "J": {Title: "RAA %", Width: 10},
+ "K": {Title: "Identified Risk", Width: 75},
+ "L": {Title: "Action", Width: 45},
+ "M": {Title: "Mitigation", Width: 75},
+ "N": {Title: "Check", Width: 40},
+ "O": {Title: "ID", Width: 10},
+ "P": {Title: "Status", Width: 18},
+ "Q": {Title: "Justification", Width: 80},
+ "R": {Title: "Date", Width: 18},
+ "S": {Title: "Checked by", Width: 20},
+ "T": {Title: "Ticket", Width: 20},
+ }
+
+ return *what
+}
+
+func (what *ExcelColumns) FindColumnNameByTitle(title string) string {
+ for column, excelColumn := range *what {
+ if strings.EqualFold(excelColumn.Title, title) {
+ return column
+ }
+ }
+
+ return ""
+}
+
+func (what *ExcelColumns) FindColumnIndexByTitle(title string) int {
+ for column, excelColumn := range *what {
+ if strings.EqualFold(excelColumn.Title, title) {
+ columnNumber, columnNumberError := excelize.ColumnNameToNumber(column)
+ if columnNumberError != nil {
+ return -1
+ }
+
+ return columnNumber - 1
+ }
+ }
+
+ return -1
+}
+
+type ExcelColumn struct {
+ Title string
+ Width float64
+}
diff --git a/pkg/report/excel-style.go b/pkg/report/excel-style.go
new file mode 100644
index 00000000..7f36d6e0
--- /dev/null
+++ b/pkg/report/excel-style.go
@@ -0,0 +1,507 @@
+package report
+
+import (
+ "fmt"
+ "github.com/threagile/threagile/pkg/security/types"
+ "github.com/xuri/excelize/v2"
+ "strings"
+)
+
+type ExcelStyles struct {
+ severityCriticalBold int
+ severityCriticalCenter int
+ severityHighBold int
+ severityHighCenter int
+ severityElevatedBold int
+ severityElevatedCenter int
+ severityMediumBold int
+ severityMediumCenter int
+ severityLowBold int
+ severityLowCenter int
+ redCenter int
+ greenCenter int
+ blueCenter int
+ yellowCenter int
+ orangeCenter int
+ grayCenter int
+ blackLeft int
+ blackLeftBold int
+ blackCenter int
+ blackRight int
+ blackSmall int
+ graySmall int
+ blackBold int
+ mitigation int
+ headCenter int
+ headCenterBoldItalic int
+ headCenterBold int
+}
+
+type styleCreator struct {
+ ExcelFile *excelize.File
+ Error error
+}
+
+func (what *styleCreator) Init(excel *excelize.File) *styleCreator {
+ what.ExcelFile = excel
+ return what
+}
+
+func (what *styleCreator) NewStyle(style *excelize.Style) int {
+ if what.Error != nil {
+ return 0
+ }
+
+ var styleID int
+ styleID, what.Error = what.ExcelFile.NewStyle(style)
+
+ return styleID
+}
+
+func (what *ExcelStyles) Init(excel *excelize.File) (*ExcelStyles, error) {
+ if excel == nil {
+ return what, fmt.Errorf("no excel file provided to create styles")
+ }
+
+ creator := new(styleCreator).Init(excel)
+
+ what.severityCriticalBold = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ Horizontal: "center",
+ ShrinkToFit: true,
+ WrapText: false,
+ },
+ Font: &excelize.Font{
+ Color: rgbHexColorCriticalRisk(),
+ Size: 12,
+ Bold: true,
+ },
+ })
+
+ what.severityCriticalCenter = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ Horizontal: "center",
+ ShrinkToFit: true,
+ WrapText: false,
+ },
+ Font: &excelize.Font{
+ Color: rgbHexColorCriticalRisk(),
+ Size: 12,
+ },
+ })
+
+ what.severityHighBold = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ Horizontal: "center",
+ ShrinkToFit: true,
+ WrapText: false,
+ },
+ Font: &excelize.Font{
+ Color: rgbHexColorHighRisk(),
+ Size: 12,
+ Bold: true,
+ },
+ })
+
+ what.severityHighCenter = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ Horizontal: "center",
+ ShrinkToFit: true,
+ WrapText: false,
+ },
+ Font: &excelize.Font{
+ Color: rgbHexColorHighRisk(),
+ Size: 12,
+ },
+ })
+
+ what.severityElevatedBold = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ Horizontal: "center",
+ ShrinkToFit: true,
+ WrapText: false,
+ },
+ Font: &excelize.Font{
+ Color: rgbHexColorElevatedRisk(),
+ Size: 12,
+ Bold: true,
+ },
+ })
+
+ what.severityElevatedCenter = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ Horizontal: "center",
+ ShrinkToFit: true,
+ WrapText: false,
+ },
+ Font: &excelize.Font{
+ Color: rgbHexColorElevatedRisk(),
+ Size: 12,
+ },
+ })
+
+ what.severityMediumBold = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ Horizontal: "center",
+ ShrinkToFit: true,
+ WrapText: false,
+ },
+ Font: &excelize.Font{
+ Color: rgbHexColorMediumRisk(),
+ Size: 12,
+ Bold: true,
+ },
+ })
+
+ what.severityMediumCenter = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ Horizontal: "center",
+ ShrinkToFit: true,
+ WrapText: false,
+ },
+ Font: &excelize.Font{
+ Color: rgbHexColorMediumRisk(),
+ Size: 12,
+ },
+ })
+
+ what.severityLowBold = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ Horizontal: "center",
+ ShrinkToFit: true,
+ WrapText: false,
+ },
+ Font: &excelize.Font{
+ Color: rgbHexColorLowRisk(),
+ Size: 12,
+ Bold: true,
+ },
+ })
+
+ what.severityLowCenter = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ Horizontal: "center",
+ ShrinkToFit: true,
+ WrapText: false,
+ },
+ Font: &excelize.Font{
+ Color: rgbHexColorLowRisk(),
+ Size: 12,
+ },
+ })
+
+ what.redCenter = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ Horizontal: "center",
+ ShrinkToFit: true,
+ WrapText: false,
+ },
+ Font: &excelize.Font{
+ Color: rgbHexColorLowRisk(),
+ Size: 12,
+ },
+ })
+
+ what.greenCenter = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ Horizontal: "center",
+ ShrinkToFit: true,
+ WrapText: false,
+ },
+ Font: &excelize.Font{
+ Color: rgbHexColorRiskStatusMitigated(),
+ Size: 12,
+ },
+ })
+
+ what.blueCenter = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ Horizontal: "center",
+ ShrinkToFit: true,
+ WrapText: false,
+ },
+ Font: &excelize.Font{
+ Color: rgbHexColorRiskStatusInProgress(),
+ Size: 12,
+ },
+ })
+
+ what.yellowCenter = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ Horizontal: "center",
+ ShrinkToFit: true,
+ WrapText: false,
+ },
+ Font: &excelize.Font{
+ Color: rgbHexColorRiskStatusAccepted(),
+ Size: 12,
+ },
+ })
+
+ what.orangeCenter = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ Horizontal: "center",
+ ShrinkToFit: true,
+ WrapText: false,
+ },
+ Font: &excelize.Font{
+ Color: rgbHexColorRiskStatusInDiscussion(),
+ Size: 12,
+ },
+ })
+
+ what.grayCenter = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ Horizontal: "center",
+ ShrinkToFit: true,
+ WrapText: false,
+ },
+ Font: &excelize.Font{
+ Color: rgbHexColorRiskStatusFalsePositive(),
+ Size: 12,
+ },
+ })
+
+ what.blackLeft = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ Horizontal: "left",
+ ShrinkToFit: true,
+ WrapText: false,
+ },
+ Font: &excelize.Font{
+ Color: "#000000",
+ Size: 12,
+ },
+ })
+
+ what.blackCenter = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ Horizontal: "center",
+ ShrinkToFit: true,
+ WrapText: false,
+ },
+ Font: &excelize.Font{
+ Color: "#000000",
+ Size: 12,
+ },
+ })
+
+ what.blackRight = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ Horizontal: "right",
+ ShrinkToFit: true,
+ WrapText: false,
+ },
+ Font: &excelize.Font{
+ Color: "#000000",
+ Size: 12,
+ },
+ })
+
+ what.blackSmall = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ ShrinkToFit: true,
+ },
+ Font: &excelize.Font{
+ Color: "#000000",
+ Size: 10,
+ },
+ })
+
+ what.graySmall = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ ShrinkToFit: true,
+ },
+ Font: &excelize.Font{
+ Color: rgbHexColorOutOfScope(),
+ Size: 10,
+ },
+ })
+
+ what.blackBold = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ Horizontal: "center",
+ ShrinkToFit: true,
+ WrapText: false,
+ },
+ Font: &excelize.Font{
+ Color: "#000000",
+ Size: 12,
+ Bold: true,
+ },
+ })
+
+ what.blackLeftBold = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ Horizontal: "left",
+ ShrinkToFit: true,
+ WrapText: false,
+ },
+ Font: &excelize.Font{
+ Color: "#000000",
+ Size: 12,
+ Bold: true,
+ },
+ })
+
+ what.mitigation = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ ShrinkToFit: true,
+ },
+ Font: &excelize.Font{
+ Color: rgbHexColorRiskStatusMitigated(),
+ Size: 10,
+ },
+ })
+
+ what.headCenterBoldItalic = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ Horizontal: "center",
+ ShrinkToFit: true,
+ WrapText: false,
+ },
+ Font: &excelize.Font{
+ Color: "#000000",
+ Bold: true,
+ Italic: false,
+ Size: 14,
+ },
+ Fill: excelize.Fill{
+ Type: "pattern",
+ Color: []string{"#eeeeee"},
+ Pattern: 1,
+ },
+ })
+
+ what.headCenterBold = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ Horizontal: "center",
+ ShrinkToFit: true,
+ WrapText: false,
+ },
+ Font: &excelize.Font{
+ Color: "#000000",
+ Size: 14,
+ Bold: true,
+ },
+ Fill: excelize.Fill{
+ Type: "pattern",
+ Color: []string{"#eeeeee"},
+ Pattern: 1,
+ },
+ })
+
+ what.headCenter = creator.NewStyle(&excelize.Style{
+ Alignment: &excelize.Alignment{
+ Horizontal: "center",
+ ShrinkToFit: true,
+ WrapText: false,
+ },
+ Font: &excelize.Font{
+ Color: "#000000",
+ Size: 14,
+ },
+ Fill: excelize.Fill{
+ Type: "pattern",
+ Color: []string{"#eeeeee"},
+ Pattern: 1,
+ },
+ })
+
+ return what, creator.Error
+}
+
+func (what *ExcelStyles) Get(column string, status types.RiskStatus, severity types.RiskSeverity) int {
+ switch strings.ToUpper(column) {
+ case "A", "B", "C", "D", "E", "F":
+ if !status.IsStillAtRisk() {
+ return what.blackCenter
+ }
+
+ switch severity {
+ case types.CriticalSeverity:
+ return what.severityCriticalCenter
+
+ case types.HighSeverity:
+ return what.severityHighCenter
+
+ case types.ElevatedSeverity:
+ return what.severityElevatedCenter
+
+ case types.MediumSeverity:
+ return what.severityMediumCenter
+
+ case types.LowSeverity:
+ return what.severityLowCenter
+ }
+
+ case "G", "H", "I":
+ if !status.IsStillAtRisk() {
+ return what.blackBold
+ }
+
+ switch severity {
+ case types.CriticalSeverity:
+ return what.severityCriticalBold
+
+ case types.HighSeverity:
+ return what.severityHighBold
+
+ case types.ElevatedSeverity:
+ return what.severityElevatedBold
+
+ case types.MediumSeverity:
+ return what.severityMediumBold
+
+ case types.LowSeverity:
+ return what.severityLowBold
+ }
+
+ case "J":
+ return what.blackRight
+
+ case "K":
+ return what.blackSmall
+
+ case "L", "M", "N":
+ return what.mitigation
+
+ case "O":
+ return what.graySmall
+
+ case "P":
+ switch status {
+ case types.Unchecked:
+ return what.redCenter
+
+ case types.Mitigated:
+ return what.greenCenter
+
+ case types.InProgress:
+ return what.blueCenter
+
+ case types.Accepted:
+ return what.yellowCenter
+
+ case types.InDiscussion:
+ return what.orangeCenter
+
+ case types.FalsePositive:
+ return what.grayCenter
+
+ default:
+ return what.blackCenter
+ }
+
+ case "Q":
+ return what.blackSmall
+
+ case "R", "S":
+ return what.blackCenter
+
+ case "T":
+ return what.blackLeft
+ }
+
+ return what.blackRight
+}
diff --git a/pkg/report/excel.go b/pkg/report/excel.go
index d1010af8..7580b1be 100644
--- a/pkg/report/excel.go
+++ b/pkg/report/excel.go
@@ -2,19 +2,22 @@ package report
import (
"fmt"
+ "github.com/shopspring/decimal"
+ "github.com/threagile/threagile/pkg/common"
+ "github.com/threagile/threagile/pkg/security/types"
+ "github.com/xuri/excelize/v2"
"sort"
"strconv"
"strings"
-
- "github.com/threagile/threagile/pkg/security/types"
- "github.com/xuri/excelize/v2"
+ "unicode/utf8"
)
-func WriteRisksExcelToFile(parsedModel *types.ParsedModel, filename string) error {
- excelRow := 0
+func WriteRisksExcelToFile(parsedModel *types.ParsedModel, filename string, config *common.Config) error {
+ columns := new(ExcelColumns).GetColumns()
excel := excelize.NewFile()
sheetName := parsedModel.Title
- err := excel.SetDocProps(&excelize.DocProperties{
+
+ setDocPropsError := excel.SetDocProps(&excelize.DocProperties{
Category: "Threat Model Risks Summary",
ContentStatus: "Final",
Creator: parsedModel.Author.Name,
@@ -28,20 +31,28 @@ func WriteRisksExcelToFile(parsedModel *types.ParsedModel, filename string) erro
Language: "en-US",
Version: "1.0.0",
})
- if err != nil {
- return fmt.Errorf("unable to set doc properties: %w", err)
+ if setDocPropsError != nil {
+ return fmt.Errorf("failed to set doc properties: %w", setDocPropsError)
+ }
+
+ sheetIndex, newSheetError := excel.NewSheet(sheetName)
+ if newSheetError != nil {
+ return fmt.Errorf("failed to add sheet: %w", newSheetError)
+ }
+
+ deleteSheetError := excel.DeleteSheet("Sheet1")
+ if deleteSheetError != nil {
+ return fmt.Errorf("failed to delete sheet: %w", deleteSheetError)
}
- sheetIndex, _ := excel.NewSheet(sheetName)
- _ = excel.DeleteSheet("Sheet1")
orientation := "landscape"
- size := 9
- err = excel.SetPageLayout(sheetName, &excelize.PageLayoutOptions{Orientation: &orientation, Size: &size}) // A4
- if err != nil {
- return fmt.Errorf("unable to set page layout: %w", err)
+ size := 9 // A4
+ setPageLayoutError := excel.SetPageLayout(sheetName, &excelize.PageLayoutOptions{Orientation: &orientation, Size: &size})
+ if setPageLayoutError != nil {
+ return fmt.Errorf("unable to set page layout: %w", setPageLayoutError)
}
- err = excel.SetHeaderFooter(sheetName, &excelize.HeaderFooterOptions{
+ setHeaderFooterError := excel.SetHeaderFooter(sheetName, &excelize.HeaderFooterOptions{
DifferentFirst: false,
DifferentOddEven: false,
OddHeader: "&R&P",
@@ -50,635 +61,174 @@ func WriteRisksExcelToFile(parsedModel *types.ParsedModel, filename string) erro
EvenFooter: "&L&D&R&T",
FirstHeader: `&Threat Model &"-,` + parsedModel.Title + `"Bold&"-,Regular"Risks Summary+000A&D`,
})
- if err != nil {
- return fmt.Errorf("unable to set header/footer: %w", err)
- }
-
- err = setCellValue(excel, sheetName, []setCellValueCommand{
- {"A1", "Severity"},
- {"B1", "Likelihood"},
- {"C1", "Impact"},
- {"D1", "STRIDE"},
- {"E1", "Function"},
- {"F1", "CWE"},
- {"G1", "Risk category"},
- {"H1", "Technical Asset"},
- {"I1", "Communication Link"},
- {"J1", "RAA %"},
- {"K1", "Identified Risk"},
- {"L1", "Action"},
- {"M1", "Mitigation"},
- {"N1", "Check"},
- {"O1", "ID"},
- {"P1", "Status"},
- {"Q1", "Justification"},
- {"R1", "Date"},
- {"S1", "Checked by"},
- {"T1", "Ticket"},
- })
- if err != nil {
- return fmt.Errorf("unable to set cell value: %w", err)
- }
-
- err = setColumnWidth(excel, sheetName, []setColumnWidthCommand{
- {"A", 12},
- {"B", 15},
- {"C", 15},
- {"D", 22},
- {"E", 16},
- {"F", 12},
- {"G", 50},
- {"H", 50},
- {"I", 50},
- {"J", 10},
- {"K", 75},
- {"L", 45},
- {"M", 75},
- {"N", 50},
- {"O", 10},
- {"P", 18},
- {"Q", 75},
- {"R", 18},
- {"S", 20},
- {"T", 20},
- })
- if err != nil {
- return fmt.Errorf("unable to set column width: %w", err)
+ if setHeaderFooterError != nil {
+ return fmt.Errorf("unable to set header/footer: %w", setHeaderFooterError)
}
- cellStyles, err := createCellStyles(excel)
- if err != nil {
- return fmt.Errorf("unable to create cell styles: %w", err)
+ // set header row
+ for columnLetter, column := range columns {
+ setCellValueError := excel.SetCellValue(sheetName, columnLetter+"1", column.Title)
+ if setCellValueError != nil {
+ return fmt.Errorf("unable to set cell value: %w", setCellValueError)
+ }
}
- excelRow++ // as we have a header line
+ cellStyles, createCellStylesError := new(ExcelStyles).Init(excel)
+ if createCellStylesError != nil {
+ return fmt.Errorf("unable to create cell styles: %w", createCellStylesError)
+ }
+
+ // get sorted risks
+ riskItems := make([]RiskItem, 0)
for _, category := range types.SortedRiskCategories(parsedModel) {
risks := types.SortedRisksOfCategory(parsedModel, category)
for _, risk := range risks {
- excelRow++
techAsset := parsedModel.TechnicalAssets[risk.MostRelevantTechnicalAssetId]
commLink := parsedModel.CommunicationLinks[risk.MostRelevantCommunicationLinkId]
- riskTrackingStatus := risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel)
- // content
- err := setCellValue(excel, sheetName, []setCellValueCommand{
- {"A" + strconv.Itoa(excelRow), risk.Severity.Title()},
- {"B" + strconv.Itoa(excelRow), risk.ExploitationLikelihood.Title()},
- {"C" + strconv.Itoa(excelRow), risk.ExploitationImpact.Title()},
- {"D" + strconv.Itoa(excelRow), category.STRIDE.Title()},
- {"E" + strconv.Itoa(excelRow), category.Function.Title()},
- {"F" + strconv.Itoa(excelRow), "CWE-" + strconv.Itoa(category.CWE)},
- {"G" + strconv.Itoa(excelRow), category.Title},
- {"H" + strconv.Itoa(excelRow), techAsset.Title},
- {"I" + strconv.Itoa(excelRow), commLink.Title},
- {"K" + strconv.Itoa(excelRow), removeFormattingTags(risk.Title)},
- {"L" + strconv.Itoa(excelRow), category.Action},
- {"M" + strconv.Itoa(excelRow), category.Mitigation},
- {"N" + strconv.Itoa(excelRow), category.Check},
- {"O" + strconv.Itoa(excelRow), risk.SyntheticId},
- {"P" + strconv.Itoa(excelRow), riskTrackingStatus.Title()},
- })
- if err != nil {
- return err
- }
- err = excel.SetCellFloat(sheetName, "J"+strconv.Itoa(excelRow), techAsset.RAA, 0, 32)
- if err != nil {
- return fmt.Errorf("unable to set cell float: %w", err)
- }
+ date := ""
riskTracking := risk.GetRiskTracking(parsedModel)
- err = excel.SetCellValue(sheetName, "Q"+strconv.Itoa(excelRow), riskTracking.Justification)
- if err != nil {
- return fmt.Errorf("unable to set cell value: %w", err)
- }
if !riskTracking.Date.IsZero() {
- err = excel.SetCellValue(sheetName, "R"+strconv.Itoa(excelRow), riskTracking.Date.Format("2006-01-02"))
- if err != nil {
- return fmt.Errorf("unable to set cell value: %w", err)
- }
- }
- err = excel.SetCellValue(sheetName, "S"+strconv.Itoa(excelRow), riskTracking.CheckedBy)
- if err != nil {
- return fmt.Errorf("unable to set cell value: %w", err)
- }
- err = excel.SetCellValue(sheetName, "T"+strconv.Itoa(excelRow), riskTracking.Ticket)
- if err != nil {
- return fmt.Errorf("unable to set cell value: %w", err)
+ date = riskTracking.Date.Format("2006-01-02")
}
- // styles
- leftCellsStyle, rightCellStyles := fromSeverityToExcelStyle(riskTrackingStatus, risk.Severity, cellStyles)
- err = setCellStyle(excel, sheetName, []setCellStyleCommand{
- {"A" + strconv.Itoa(excelRow), "F" + strconv.Itoa(excelRow), leftCellsStyle},
- {"G" + strconv.Itoa(excelRow), "I" + strconv.Itoa(excelRow), rightCellStyles},
- {"J" + strconv.Itoa(excelRow), "J" + strconv.Itoa(excelRow), cellStyles.blackRight},
- {"K" + strconv.Itoa(excelRow), "K" + strconv.Itoa(excelRow), cellStyles.blackSmall},
- {"L" + strconv.Itoa(excelRow), "L" + strconv.Itoa(excelRow), cellStyles.mitigation},
- {"M" + strconv.Itoa(excelRow), "M" + strconv.Itoa(excelRow), cellStyles.mitigation},
- {"N" + strconv.Itoa(excelRow), "N" + strconv.Itoa(excelRow), cellStyles.mitigation},
- {"O" + strconv.Itoa(excelRow), "O" + strconv.Itoa(excelRow), cellStyles.graySmall},
- {"P" + strconv.Itoa(excelRow), "P" + strconv.Itoa(excelRow), fromRiskTrackingToExcelStyle(riskTrackingStatus, cellStyles)},
- {"Q" + strconv.Itoa(excelRow), "Q" + strconv.Itoa(excelRow), cellStyles.blackSmall},
- {"R" + strconv.Itoa(excelRow), "R" + strconv.Itoa(excelRow), cellStyles.blackCenter},
- {"S" + strconv.Itoa(excelRow), "S" + strconv.Itoa(excelRow), cellStyles.blackCenter},
- {"T" + strconv.Itoa(excelRow), "T" + strconv.Itoa(excelRow), cellStyles.blackLeft},
+ riskTrackingStatus := risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel)
+
+ riskItems = append(riskItems, RiskItem{
+ Columns: []string{
+ risk.Severity.Title(),
+ risk.ExploitationLikelihood.Title(),
+ risk.ExploitationImpact.Title(),
+ category.STRIDE.Title(),
+ category.Function.Title(),
+ "CWE-" + strconv.Itoa(category.CWE),
+ category.Title,
+ techAsset.Title,
+ commLink.Title,
+ decimal.NewFromFloat(techAsset.RAA).StringFixed(0),
+ removeFormattingTags(risk.Title),
+ category.Action,
+ category.Mitigation,
+ category.Check,
+ risk.SyntheticId,
+ riskTrackingStatus.Title(),
+ riskTracking.Justification,
+ date,
+ riskTracking.CheckedBy,
+ riskTracking.Ticket,
+ },
+ Status: riskTrackingStatus,
+ Severity: risk.Severity,
})
- if err != nil {
- return fmt.Errorf("unable to set cell style: %w", err)
- }
}
}
- err = excel.SetCellStyle(sheetName, "A1", "T1", cellStyles.headCenterBoldItalic)
- if err != nil {
- return fmt.Errorf("unable to set cell style: %w", err)
- }
-
- excel.SetActiveSheet(sheetIndex)
- err = excel.SaveAs(filename)
- if err != nil {
- return fmt.Errorf("unable to save excel file: %w", err)
+ // group risks
+ groupedRisk, groupedRiskError := new(RiskGroup).Make(riskItems, columns, config.GroupByColumns)
+ if groupedRiskError != nil {
+ return fmt.Errorf("failed to group risks: %w", groupedRiskError)
}
- return nil
-}
-type cellStyles struct {
- severityCriticalBold int
- severityCriticalCenter int
- severityHighBold int
- severityHighCenter int
- severityElevatedBold int
- severityElevatedCenter int
- severityMediumBold int
- severityMediumCenter int
- severityLowBold int
- severityLowCenter int
- redCenter int
- greenCenter int
- blueCenter int
- yellowCenter int
- orangeCenter int
- grayCenter int
- blackLeft int
- blackLeftBold int
- blackCenter int
- blackRight int
- blackSmall int
- graySmall int
- blackBold int
- mitigation int
- headCenter int
- headCenterBoldItalic int
- headCenterBold int
-}
+ _ = groupedRisk
-func createCellStyles(excel *excelize.File) (*cellStyles, error) {
- styleSeverityCriticalBold, err := excel.NewStyle(&excelize.Style{
- Font: &excelize.Font{
- Color: rgbHexColorCriticalRisk(),
- Size: 12,
- Bold: true,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
- }
- styleSeverityCriticalCenter, err := excel.NewStyle(&excelize.Style{
- Alignment: &excelize.Alignment{
- Horizontal: "center",
- ShrinkToFit: true,
- WrapText: false,
- },
- Font: &excelize.Font{
- Color: rgbHexColorCriticalRisk(),
- Size: 12,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
- }
- styleSeverityHighBold, err := excel.NewStyle(&excelize.Style{
- Font: &excelize.Font{
- Color: rgbHexColorHighRisk(),
- Size: 12,
- Bold: true,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
- }
- styleSeverityHighCenter, err := excel.NewStyle(&excelize.Style{
- Alignment: &excelize.Alignment{
- Horizontal: "center",
- ShrinkToFit: true,
- WrapText: false,
- },
- Font: &excelize.Font{
- Color: rgbHexColorHighRisk(),
- Size: 12,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
- }
- styleSeverityElevatedBold, err := excel.NewStyle(&excelize.Style{
- Font: &excelize.Font{
- Color: rgbHexColorElevatedRisk(),
- Size: 12,
- Bold: true,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
- }
- styleSeverityElevatedCenter, err := excel.NewStyle(&excelize.Style{
- Alignment: &excelize.Alignment{
- Horizontal: "center",
- ShrinkToFit: true,
- WrapText: false,
- },
- Font: &excelize.Font{
- Color: rgbHexColorElevatedRisk(),
- Size: 12,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
- }
- styleSeverityMediumBold, err := excel.NewStyle(&excelize.Style{
- Font: &excelize.Font{
- Color: rgbHexColorMediumRisk(),
- Size: 12,
- Bold: true,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
- }
- styleSeverityMediumCenter, err := excel.NewStyle(&excelize.Style{
- Alignment: &excelize.Alignment{
- Horizontal: "center",
- ShrinkToFit: true,
- WrapText: false,
- },
- Font: &excelize.Font{
- Color: rgbHexColorMediumRisk(),
- Size: 12,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
- }
- styleSeverityLowBold, err := excel.NewStyle(&excelize.Style{
- Font: &excelize.Font{
- Color: rgbHexColorLowRisk(),
- Size: 12,
- Bold: true,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
- }
- styleSeverityLowCenter, err := excel.NewStyle(&excelize.Style{
- Alignment: &excelize.Alignment{
- Horizontal: "center",
- ShrinkToFit: true,
- WrapText: false,
- },
- Font: &excelize.Font{
- Color: rgbHexColorLowRisk(),
- Size: 12,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
- }
- styleRedCenter, err := excel.NewStyle(&excelize.Style{
- Alignment: &excelize.Alignment{
- Horizontal: "center",
- ShrinkToFit: true,
- WrapText: false,
- },
- Font: &excelize.Font{
- Color: rgbHexColorLowRisk(),
- Size: 12,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
- }
- styleGreenCenter, err := excel.NewStyle(&excelize.Style{
- Alignment: &excelize.Alignment{
- Horizontal: "center",
- ShrinkToFit: true,
- WrapText: false,
- },
- Font: &excelize.Font{
- Color: rgbHexColorRiskStatusMitigated(),
- Size: 12,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
- }
- styleBlueCenter, err := excel.NewStyle(&excelize.Style{
- Alignment: &excelize.Alignment{
- Horizontal: "center",
- ShrinkToFit: true,
- WrapText: false,
- },
- Font: &excelize.Font{
- Color: rgbHexColorRiskStatusInProgress(),
- Size: 12,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
- }
- styleYellowCenter, err := excel.NewStyle(&excelize.Style{
- Alignment: &excelize.Alignment{
- Horizontal: "center",
- ShrinkToFit: true,
- WrapText: false,
- },
- Font: &excelize.Font{
- Color: rgbHexColorRiskStatusAccepted(),
- Size: 12,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
- }
- styleOrangeCenter, err := excel.NewStyle(&excelize.Style{
- Alignment: &excelize.Alignment{
- Horizontal: "center",
- ShrinkToFit: true,
- WrapText: false,
- },
- Font: &excelize.Font{
- Color: rgbHexColorRiskStatusInDiscussion(),
- Size: 12,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
- }
- styleGrayCenter, err := excel.NewStyle(&excelize.Style{
- Alignment: &excelize.Alignment{
- Horizontal: "center",
- ShrinkToFit: true,
- WrapText: false,
- },
- Font: &excelize.Font{
- Color: rgbHexColorRiskStatusFalsePositive(),
- Size: 12,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
- }
- styleBlackLeft, err := excel.NewStyle(&excelize.Style{
- Alignment: &excelize.Alignment{
- Horizontal: "left",
- ShrinkToFit: true,
- WrapText: false,
- },
- Font: &excelize.Font{
- Color: "#000000",
- Size: 12,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
- }
- styleBlackCenter, err := excel.NewStyle(&excelize.Style{
- Alignment: &excelize.Alignment{
- Horizontal: "center",
- ShrinkToFit: true,
- WrapText: false,
- },
- Font: &excelize.Font{
- Color: "#000000",
- Size: 12,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
- }
- styleBlackRight, err := excel.NewStyle(&excelize.Style{
- Alignment: &excelize.Alignment{
- Horizontal: "right",
- ShrinkToFit: true,
- WrapText: false,
- },
- Font: &excelize.Font{
- Color: "#000000",
- Size: 12,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
- }
- styleBlackSmall, err := excel.NewStyle(&excelize.Style{
- Font: &excelize.Font{
- Color: "#000000",
- Size: 10,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
+ // write data
+ writeError := groupedRisk.Write(excel, sheetName, cellStyles)
+ if writeError != nil {
+ return fmt.Errorf("failed to write data: %w", writeError)
}
- styleGraySmall, err := excel.NewStyle(&excelize.Style{
- Font: &excelize.Font{
- Color: rgbHexColorOutOfScope(),
- Size: 10,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
- }
- styleBlackBold, err := excel.NewStyle(&excelize.Style{
- Alignment: &excelize.Alignment{
- Horizontal: "right",
- ShrinkToFit: true,
- WrapText: false,
- },
- Font: &excelize.Font{
- Color: "#000000",
- Size: 12,
- Bold: true,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
- }
- styleBlackLeftBold, err := excel.NewStyle(&excelize.Style{
- Alignment: &excelize.Alignment{
- Horizontal: "left",
- ShrinkToFit: true,
- WrapText: false,
- },
- Font: &excelize.Font{
- Color: "#000000",
- Size: 12,
- Bold: true,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
+
+ // set header style
+ setCellStyleError := excel.SetCellStyle(sheetName, "A1", "T1", cellStyles.headCenterBoldItalic)
+ if setCellStyleError != nil {
+ return fmt.Errorf("unable to set cell style: %w", setCellStyleError)
}
- styleMitigation, err := excel.NewStyle(&excelize.Style{
- Font: &excelize.Font{
- Color: rgbHexColorRiskStatusMitigated(),
- Size: 10,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
- }
- styleHeadCenterBoldItalic, err := excel.NewStyle(&excelize.Style{
- Alignment: &excelize.Alignment{
- Horizontal: "center",
- ShrinkToFit: true,
- WrapText: false,
- },
- Font: &excelize.Font{
- Color: "#000000",
- Bold: true,
- Italic: false,
- Size: 14,
- },
- Fill: excelize.Fill{
- Type: "pattern",
- Color: []string{"#eeeeee"},
- Pattern: 1,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
- }
- styleHeadCenterBold, err := excel.NewStyle(&excelize.Style{
- Alignment: &excelize.Alignment{
- Horizontal: "center",
- ShrinkToFit: true,
- WrapText: false,
- },
- Font: &excelize.Font{
- Color: "#000000",
- Size: 14,
- Bold: true,
- },
- Fill: excelize.Fill{
- Type: "pattern",
- Color: []string{"#eeeeee"},
- Pattern: 1,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to create style: %w", err)
- }
-
- styleHeadCenter, err := excel.NewStyle(&excelize.Style{
- Alignment: &excelize.Alignment{
- Horizontal: "center",
- ShrinkToFit: true,
- WrapText: false,
- },
- Font: &excelize.Font{
- Color: "#000000",
- Size: 14,
- },
- Fill: excelize.Fill{
- Type: "pattern",
- Color: []string{"#eeeeee"},
- Pattern: 1,
- },
- })
- if err != nil {
- return nil, fmt.Errorf("unable to set cell style: %w", err)
- }
-
- return &cellStyles{
- headCenter: styleHeadCenter,
- headCenterBoldItalic: styleHeadCenterBoldItalic,
- headCenterBold: styleHeadCenterBold,
- severityCriticalBold: styleSeverityCriticalBold,
- severityCriticalCenter: styleSeverityCriticalCenter,
- severityHighBold: styleSeverityHighBold,
- severityHighCenter: styleSeverityHighCenter,
- severityElevatedBold: styleSeverityElevatedBold,
- severityElevatedCenter: styleSeverityElevatedCenter,
- severityMediumBold: styleSeverityMediumBold,
- severityMediumCenter: styleSeverityMediumCenter,
- severityLowBold: styleSeverityLowBold,
- severityLowCenter: styleSeverityLowCenter,
- redCenter: styleRedCenter,
- greenCenter: styleGreenCenter,
- blueCenter: styleBlueCenter,
- yellowCenter: styleYellowCenter,
- orangeCenter: styleOrangeCenter,
- grayCenter: styleGrayCenter,
- blackLeft: styleBlackLeft,
- blackLeftBold: styleBlackLeftBold,
- blackCenter: styleBlackCenter,
- blackRight: styleBlackRight,
- blackSmall: styleBlackSmall,
- graySmall: styleGraySmall,
- blackBold: styleBlackBold,
- mitigation: styleMitigation,
- }, nil
-}
-func fromRiskTrackingToExcelStyle(riskTrackingStatus types.RiskStatus, cellStyles *cellStyles) int {
- switch riskTrackingStatus {
- case types.Unchecked:
- return cellStyles.redCenter
- case types.Mitigated:
- return cellStyles.greenCenter
- case types.InProgress:
- return cellStyles.blueCenter
- case types.Accepted:
- return cellStyles.yellowCenter
- case types.InDiscussion:
- return cellStyles.orangeCenter
- case types.FalsePositive:
- return cellStyles.grayCenter
- default:
- return cellStyles.blackCenter
+ // fix column width
+ cols, colsError := excel.GetCols(sheetName)
+ if colsError == nil {
+ for colIndex, col := range cols {
+ name, columnNumberToNameError := excelize.ColumnNumberToName(colIndex + 1)
+ if columnNumberToNameError != nil {
+ return columnNumberToNameError
+ }
+
+ var largestWidth float64 = 0
+ for rowIndex, rowCell := range col {
+ cellWidth := float64(utf8.RuneCountInString(rowCell) + 1) // + 1 for margin
+
+ cellName, coordinateError := excelize.CoordinatesToCellName(colIndex+1, rowIndex+1)
+ if coordinateError == nil {
+ style, styleError := excel.GetCellStyle(sheetName, cellName)
+ if styleError == nil {
+ styleDetails, detailsError := excel.GetStyle(style)
+ if detailsError == nil {
+ if styleDetails.Font != nil && styleDetails.Font.Size > 0 {
+ cellWidth *= styleDetails.Font.Size / 14.
+ }
+ }
+ }
+ }
+
+ if cellWidth > largestWidth {
+ largestWidth = cellWidth
+ }
+ }
+
+ var minWidth float64
+ for columnLetter := range columns {
+ if strings.EqualFold(columnLetter, name) {
+ minWidth = columns[columnLetter].Width
+ }
+ }
+
+ if largestWidth < 100 {
+ minWidth = largestWidth
+ }
+
+ if minWidth < 8 {
+ minWidth = 8
+ }
+
+ setColWidthError := excel.SetColWidth(sheetName, name, name, minWidth)
+ if setColWidthError != nil {
+ return setColWidthError
+ }
+ }
}
-}
-func fromSeverityToExcelStyle(riskTrackingStatus types.RiskStatus, severity types.RiskSeverity, cellStyles *cellStyles) (int, int) {
-
- if riskTrackingStatus.IsStillAtRisk() {
- switch severity {
- case types.CriticalSeverity:
- return cellStyles.severityCriticalCenter, cellStyles.severityCriticalBold
- case types.HighSeverity:
- return cellStyles.severityHighCenter, cellStyles.severityHighBold
- case types.ElevatedSeverity:
- return cellStyles.severityElevatedCenter, cellStyles.severityElevatedBold
- case types.MediumSeverity:
- return cellStyles.severityMediumCenter, cellStyles.severityMediumBold
- case types.LowSeverity:
- return cellStyles.severityLowCenter, cellStyles.severityLowBold
+ // hide some columns
+ for columnLetter, column := range columns {
+ for _, hiddenColumn := range config.HideColumns {
+ if strings.EqualFold(hiddenColumn, column.Title) {
+ hideColumnError := excel.SetColVisible(sheetName, columnLetter, false)
+ if hideColumnError != nil {
+ return fmt.Errorf("unable to hide column: %w", hideColumnError)
+ }
+ }
}
}
- return cellStyles.blackCenter, cellStyles.blackBold
-}
-type setCellStyleCommand struct {
- hCell string
- vCell string
- Style int
-}
+ // freeze header
+ freezeError := excel.SetPanes(sheetName, &excelize.Panes{
+ Freeze: true,
+ Split: false,
+ XSplit: 0,
+ YSplit: 1,
+ TopLeftCell: "A2",
+ ActivePane: "bottomLeft",
+ })
+ if freezeError != nil {
+ return fmt.Errorf("unable to freeze header: %w", freezeError)
+ }
-func setCellStyle(excel *excelize.File, sheetName string, commands []setCellStyleCommand) error {
- for _, command := range commands {
- err := excel.SetCellStyle(sheetName, command.hCell, command.vCell, command.Style)
- if err != nil {
- return fmt.Errorf("unable to set cell style: %w", err)
- }
+ excel.SetActiveSheet(sheetIndex)
+
+ // save file
+ saveAsError := excel.SaveAs(filename)
+ if saveAsError != nil {
+ return fmt.Errorf("unable to save excel file: %w", saveAsError)
}
+
return nil
}
@@ -733,10 +283,13 @@ func WriteTagsExcelToFile(parsedModel *types.ParsedModel, filename string) error
sortedTagsAvailable := parsedModel.TagsActuallyUsed()
sort.Strings(sortedTagsAvailable)
- axis := ""
for i, tag := range sortedTagsAvailable {
- axis = determineColumnLetter(i)
- err = excel.SetCellValue(sheetName, axis+"1", tag)
+ cellName, coordinatesToCellNameError := excelize.CoordinatesToCellName(i+2, 1)
+ if coordinatesToCellNameError != nil {
+ return fmt.Errorf("failed to get cell coordinates from [%d, %d]: %w", i+2, 1, coordinatesToCellNameError)
+ }
+
+ err = excel.SetCellValue(sheetName, cellName, tag)
if err != nil {
return err
}
@@ -747,46 +300,47 @@ func WriteTagsExcelToFile(parsedModel *types.ParsedModel, filename string) error
return err
}
+ lastColumn, _ := excelize.ColumnNumberToName(len(sortedTagsAvailable) + 2)
if len(sortedTagsAvailable) > 0 {
- err = excel.SetColWidth(sheetName, "B", axis, 35)
+ err = excel.SetColWidth(sheetName, "B", lastColumn, 35)
}
if err != nil {
return err
}
- cellStyles, err := createCellStyles(excel)
- if err != nil {
- return err
+ cellStyles, createCellStylesError := new(ExcelStyles).Init(excel)
+ if createCellStylesError != nil {
+ return fmt.Errorf("unable to create cell styles: %w", createCellStylesError)
}
excelRow++ // as we have a header line
if len(sortedTagsAvailable) > 0 {
for _, techAsset := range sortedTechnicalAssetsByTitle(parsedModel) {
- err := writeRow(excel, &excelRow, sheetName, axis, cellStyles.blackLeftBold, cellStyles.blackCenter, sortedTagsAvailable, techAsset.Title, techAsset.Tags)
+ err := writeRow(excel, &excelRow, sheetName, lastColumn, cellStyles.blackLeftBold, cellStyles.blackCenter, sortedTagsAvailable, techAsset.Title, techAsset.Tags)
if err != nil {
return fmt.Errorf("unable to write row: %w", err)
}
for _, commLink := range techAsset.CommunicationLinksSorted() {
- err := writeRow(excel, &excelRow, sheetName, axis, cellStyles.blackLeftBold, cellStyles.blackCenter, sortedTagsAvailable, commLink.Title, commLink.Tags)
+ err := writeRow(excel, &excelRow, sheetName, lastColumn, cellStyles.blackLeftBold, cellStyles.blackCenter, sortedTagsAvailable, commLink.Title, commLink.Tags)
if err != nil {
return fmt.Errorf("unable to write row: %w", err)
}
}
}
for _, dataAsset := range sortedDataAssetsByTitle(parsedModel) {
- err := writeRow(excel, &excelRow, sheetName, axis, cellStyles.blackLeftBold, cellStyles.blackCenter, sortedTagsAvailable, dataAsset.Title, dataAsset.Tags)
+ err := writeRow(excel, &excelRow, sheetName, lastColumn, cellStyles.blackLeftBold, cellStyles.blackCenter, sortedTagsAvailable, dataAsset.Title, dataAsset.Tags)
if err != nil {
return fmt.Errorf("unable to write row: %w", err)
}
}
for _, trustBoundary := range sortedTrustBoundariesByTitle(parsedModel) {
- err := writeRow(excel, &excelRow, sheetName, axis, cellStyles.blackLeftBold, cellStyles.blackCenter, sortedTagsAvailable, trustBoundary.Title, trustBoundary.Tags)
+ err := writeRow(excel, &excelRow, sheetName, lastColumn, cellStyles.blackLeftBold, cellStyles.blackCenter, sortedTagsAvailable, trustBoundary.Title, trustBoundary.Tags)
if err != nil {
return fmt.Errorf("unable to write row: %w", err)
}
}
for _, sharedRuntime := range sortedSharedRuntimesByTitle(parsedModel) {
- err := writeRow(excel, &excelRow, sheetName, axis, cellStyles.blackLeftBold, cellStyles.blackCenter, sortedTagsAvailable, sharedRuntime.Title, sharedRuntime.Tags)
+ err := writeRow(excel, &excelRow, sheetName, lastColumn, cellStyles.blackLeftBold, cellStyles.blackCenter, sortedTagsAvailable, sharedRuntime.Title, sharedRuntime.Tags)
if err != nil {
return fmt.Errorf("unable to write row: %w", err)
}
@@ -795,7 +349,7 @@ func WriteTagsExcelToFile(parsedModel *types.ParsedModel, filename string) error
err = excel.SetCellStyle(sheetName, "A1", "A1", cellStyles.headCenterBold)
if len(sortedTagsAvailable) > 0 {
- err = excel.SetCellStyle(sheetName, "B1", axis+"1", cellStyles.headCenter)
+ err = excel.SetCellStyle(sheetName, "B1", lastColumn+"1", cellStyles.headCenter)
}
if err != nil {
return fmt.Errorf("unable to set cell style: %w", err)
@@ -830,72 +384,57 @@ func sortedDataAssetsByTitle(parsedModel *types.ParsedModel) []types.DataAsset {
func writeRow(excel *excelize.File, excelRow *int, sheetName string, axis string, styleBlackLeftBold int, styleBlackCenter int,
sortedTags []string, assetTitle string, tagsUsed []string) error {
*excelRow++
- err := excel.SetCellValue(sheetName, "A"+strconv.Itoa(*excelRow), assetTitle)
+
+ firstCellName, firstCoordinatesToCellNameError := excelize.CoordinatesToCellName(1, *excelRow)
+ if firstCoordinatesToCellNameError != nil {
+ return fmt.Errorf("failed to get cell coordinates from [%d, %d]: %w", 1, *excelRow, firstCoordinatesToCellNameError)
+ }
+
+ err := excel.SetCellValue(sheetName, firstCellName, assetTitle)
if err != nil {
return fmt.Errorf("unable to write row: %w", err)
}
+
for i, tag := range sortedTags {
if contains(tagsUsed, tag) {
- err = excel.SetCellValue(sheetName, determineColumnLetter(i)+strconv.Itoa(*excelRow), "X")
+ cellName, coordinatesToCellNameError := excelize.CoordinatesToCellName(i+2, *excelRow)
+ if coordinatesToCellNameError != nil {
+ return fmt.Errorf("failed to get cell coordinates from [%d, %d]: %w", i+2, *excelRow, coordinatesToCellNameError)
+ }
+
+ err = excel.SetCellValue(sheetName, cellName, "X")
if err != nil {
return fmt.Errorf("unable to write row: %w", err)
}
}
}
- err = excel.SetCellStyle(sheetName, "A"+strconv.Itoa(*excelRow), "A"+strconv.Itoa(*excelRow), styleBlackLeftBold)
+
+ err = excel.SetCellStyle(sheetName, firstCellName, firstCellName, styleBlackLeftBold)
if err != nil {
return fmt.Errorf("unable to write row: %w", err)
}
- err = excel.SetCellStyle(sheetName, "B"+strconv.Itoa(*excelRow), axis+strconv.Itoa(*excelRow), styleBlackCenter)
+
+ secondCellName, secondCoordinatesToCellNameError := excelize.CoordinatesToCellName(2, *excelRow)
+ if secondCoordinatesToCellNameError != nil {
+ return fmt.Errorf("failed to get cell coordinates from [%d, %d]: %w", 2, *excelRow, secondCoordinatesToCellNameError)
+ }
+
+ lastCellName, lastCoordinatesToCellNameError := excelize.CoordinatesToCellName(len(sortedTags)+2, *excelRow)
+ if lastCoordinatesToCellNameError != nil {
+ return fmt.Errorf("failed to get cell coordinates from [%d, %d]: %w", len(sortedTags)+2, *excelRow, lastCoordinatesToCellNameError)
+ }
+
+ err = excel.SetCellStyle(sheetName, secondCellName, lastCellName, styleBlackCenter)
if err != nil {
return fmt.Errorf("unable to write row: %w", err)
}
- return nil
-}
-func determineColumnLetter(i int) string {
- alphabet := []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}
- // can only have 700 columns in Excel that way, but that should be more than usable anyway ;)... otherwise think about your model...
- i++
- if i < 26 {
- return alphabet[i]
- }
- return alphabet[(i/26)-1] + alphabet[i%26]
+ return nil
}
-func removeFormattingTags(content string) interface{} {
+func removeFormattingTags(content string) string {
result := strings.ReplaceAll(strings.ReplaceAll(content, "", ""), "", "")
result = strings.ReplaceAll(strings.ReplaceAll(result, "", ""), "", "")
result = strings.ReplaceAll(strings.ReplaceAll(result, "", ""), "", "")
return result
}
-
-type setCellValueCommand struct {
- cell string
- value interface{}
-}
-
-func setCellValue(excel *excelize.File, sheetName string, cmds []setCellValueCommand) error {
- for _, cmd := range cmds {
- err := excel.SetCellValue(sheetName, cmd.cell, cmd.value)
- if err != nil {
- return err
- }
- }
- return nil
-}
-
-type setColumnWidthCommand struct {
- column string
- width float64
-}
-
-func setColumnWidth(excel *excelize.File, sheetName string, cmds []setColumnWidthCommand) error {
- for _, cmd := range cmds {
- err := excel.SetColWidth(sheetName, cmd.column, cmd.column, cmd.width)
- if err != nil {
- return err
- }
- }
- return nil
-}
diff --git a/pkg/report/generate.go b/pkg/report/generate.go
index 53b89633..433833c4 100644
--- a/pkg/report/generate.go
+++ b/pkg/report/generate.go
@@ -68,7 +68,7 @@ func Generate(config *common.Config, readResult *model.ReadResult, commands *Gen
}
err = GenerateDataFlowDiagramGraphvizImage(dotFile, config.OutputFolder,
- config.TempFolder, config.DataFlowDiagramFilenamePNG, progressReporter)
+ config.TempFolder, config.DataFlowDiagramFilenamePNG, progressReporter, config.KeepDiagramSourceFiles)
if err != nil {
progressReporter.Warn(err)
}
@@ -125,7 +125,7 @@ func Generate(config *common.Config, readResult *model.ReadResult, commands *Gen
// risks Excel
if commands.RisksExcel {
progressReporter.Info("Writing risks excel")
- err := WriteRisksExcelToFile(readResult.ParsedModel, filepath.Join(config.OutputFolder, config.ExcelRisksFilename))
+ err := WriteRisksExcelToFile(readResult.ParsedModel, filepath.Join(config.OutputFolder, config.ExcelRisksFilename), config)
if err != nil {
return err
}
diff --git a/pkg/report/graphviz.go b/pkg/report/graphviz.go
index 0912d75b..a9a63532 100644
--- a/pkg/report/graphviz.go
+++ b/pkg/report/graphviz.go
@@ -394,20 +394,24 @@ func determineArrowColor(cl types.CommunicationLink, parsedModel *types.ParsedMo
}
func GenerateDataFlowDiagramGraphvizImage(dotFile *os.File, targetDir string,
- tempFolder, dataFlowDiagramFilenamePNG string, progressReporter progressReporter) error {
+ tempFolder, dataFlowDiagramFilenamePNG string, progressReporter progressReporter, keepGraphVizDataFile bool) error {
progressReporter.Info("Rendering data flow diagram input")
// tmp files
tmpFileDOT, err := os.CreateTemp(tempFolder, "diagram-*-.gv")
if err != nil {
return fmt.Errorf("error creating temp file: %v", err)
}
- defer func() { _ = os.Remove(tmpFileDOT.Name()) }()
+ if !keepGraphVizDataFile {
+ defer func() { _ = os.Remove(tmpFileDOT.Name()) }()
+ }
tmpFilePNG, err := os.CreateTemp(tempFolder, "diagram-*-.png")
if err != nil {
return fmt.Errorf("error creating temp file: %v", err)
}
- defer func() { _ = os.Remove(tmpFilePNG.Name()) }()
+ if !keepGraphVizDataFile {
+ defer func() { _ = os.Remove(tmpFilePNG.Name()) }()
+ }
// copy into tmp file as input
inputDOT, err := os.ReadFile(dotFile.Name())
diff --git a/pkg/report/report.go b/pkg/report/report.go
index 99a3d341..17ee85c5 100644
--- a/pkg/report/report.go
+++ b/pkg/report/report.go
@@ -163,9 +163,9 @@ func (r *pdfReporter) parseBackgroundTemplate(templateFilename string) {
checkErr(err)
file, err := os.CreateTemp("", "background-*-.r.pdf")
checkErr(err)
- defer os.Remove(file.Name())
+ defer os.Remove(file.Title())
backgroundBytes := imageBox.MustBytes("background.r.pdf")
- err = os.WriteFile(file.Name(), backgroundBytes, 0644)
+ err = os.WriteFile(file.Title(), backgroundBytes, 0644)
checkErr(err)
*/
r.coverTemplateId = gofpdi.ImportPage(r.pdf, templateFilename, 1, "/MediaBox")
@@ -4352,15 +4352,15 @@ func (r *pdfReporter) embedDataFlowDiagram(diagramFilenamePNG string, tempFolder
// now rotate left by 90 degrees
rotatedFile, err := os.CreateTemp(tempFolder, "diagram-*-.png")
checkErr(err)
- defer os.Remove(rotatedFile.Name())
+ defer os.Remove(rotatedFile.Title())
dstImage := image.NewRGBA(image.Rect(0, 0, srcDimensions.Dy(), srcDimensions.Dx()))
err = graphics.Rotate(dstImage, srcImage, &graphics.RotateOptions{-1 * math.Pi / 2.0})
checkErr(err)
- newImage, _ := os.Create(rotatedFile.Name())
+ newImage, _ := os.Create(rotatedFile.Title())
defer newImage.Close()
err = png.Encode(newImage, dstImage)
checkErr(err)
- diagramFilenamePNG = rotatedFile.Name()
+ diagramFilenamePNG = rotatedFile.Title()
}
} else {
r.pdf.AddPage()
@@ -4452,15 +4452,15 @@ func (r *pdfReporter) embedDataRiskMapping(diagramFilenamePNG string, tempFolder
// now rotate left by 90 degrees
rotatedFile, err := os.CreateTemp(tempFolder, "diagram-*-.png")
checkErr(err)
- defer os.Remove(rotatedFile.Name())
+ defer os.Remove(rotatedFile.Title())
dstImage := image.NewRGBA(image.Rect(0, 0, srcDimensions.Dy(), srcDimensions.Dx()))
err = graphics.Rotate(dstImage, srcImage, &graphics.RotateOptions{-1 * math.Pi / 2.0})
checkErr(err)
- newImage, _ := os.Create(rotatedFile.Name())
+ newImage, _ := os.Create(rotatedFile.Title())
defer newImage.Close()
err = png.Encode(newImage, dstImage)
checkErr(err)
- diagramFilenamePNG = rotatedFile.Name()
+ diagramFilenamePNG = rotatedFile.Title()
}
} else {
r.pdf.AddPage()
diff --git a/pkg/report/risk-group.go b/pkg/report/risk-group.go
new file mode 100644
index 00000000..efec002c
--- /dev/null
+++ b/pkg/report/risk-group.go
@@ -0,0 +1,127 @@
+package report
+
+import (
+ "fmt"
+ "github.com/mpvl/unique"
+ "github.com/xuri/excelize/v2"
+ "sort"
+)
+
+type RiskGroup struct {
+ Groups map[string]*RiskGroup
+ Items []RiskItem
+}
+
+func (what *RiskGroup) Init(riskItems []RiskItem) *RiskGroup {
+ *what = RiskGroup{
+ Groups: make(map[string]*RiskGroup),
+ Items: riskItems,
+ }
+
+ return what
+}
+
+func (what *RiskGroup) SortedGroups() []string {
+ groups := make([]string, 0)
+ for group := range what.Groups {
+ groups = append(groups, group)
+ }
+
+ sort.Strings(groups)
+ unique.Strings(&groups)
+
+ return groups
+}
+
+func (what *RiskGroup) Make(riskItems []RiskItem, columns ExcelColumns, groupBy []string) (*RiskGroup, error) {
+ what.Init(riskItems)
+
+ if len(groupBy) == 0 {
+ return what, nil
+ }
+
+ groupName, groupBy := groupBy[0], groupBy[1:]
+ column := columns.FindColumnIndexByTitle(groupName)
+ if column < 0 {
+ return what, fmt.Errorf("unable to find column %q", groupName)
+ }
+
+ values := what.uniqueValues(column)
+ for _, value := range values {
+ subItems := make([]RiskItem, 0)
+ for _, item := range what.Items {
+ if item.Columns[column] == value {
+ subItems = append(subItems, item)
+ }
+ }
+
+ group, groupError := new(RiskGroup).Make(subItems, columns, groupBy)
+ if groupError != nil {
+ return what, fmt.Errorf("unable to create group: %w", groupError)
+ }
+
+ what.Groups[value] = group
+ }
+
+ return what, nil
+}
+
+func (what *RiskGroup) Write(excel *excelize.File, sheetName string, cellStyles *ExcelStyles) error {
+ if len(what.Groups) == 0 {
+ _, writeError := what.writeGroup(excel, sheetName, cellStyles, 0)
+ return writeError
+ }
+
+ var writeError error
+ excelRow := 0
+ for _, group := range what.SortedGroups() {
+ excelRow, writeError = what.Groups[group].writeGroup(excel, sheetName, cellStyles, excelRow)
+ if writeError != nil {
+ return writeError
+ }
+ }
+
+ return writeError
+}
+
+func (what *RiskGroup) writeGroup(excel *excelize.File, sheetName string, cellStyles *ExcelStyles, excelRow int) (int, error) {
+ for _, risk := range what.Items {
+ excelRow++
+
+ for columnIndex, column := range risk.Columns {
+ cellName, coordinatesToCellNameError := excelize.CoordinatesToCellName(columnIndex+1, excelRow+1)
+ if coordinatesToCellNameError != nil {
+ return excelRow, fmt.Errorf("failed to get cell coordinates from [%d, %d]: %w", columnIndex+1, excelRow+1, coordinatesToCellNameError)
+ }
+
+ setCellValueError := excel.SetCellValue(sheetName, cellName, column)
+ if setCellValueError != nil {
+ return excelRow, setCellValueError
+ }
+
+ columnName, columnNameError := excelize.ColumnNumberToName(columnIndex + 1)
+ if columnNameError != nil {
+ return excelRow, fmt.Errorf("failed to get cell coordinates from column [%d]: %w", columnIndex+1, columnNameError)
+ }
+
+ setCellStyleError := excel.SetCellStyle(sheetName, cellName, cellName, cellStyles.Get(columnName, risk.Status, risk.Severity))
+ if setCellStyleError != nil {
+ return excelRow, fmt.Errorf("failed to set cell style: %w", setCellStyleError)
+ }
+ }
+ }
+
+ return excelRow, nil
+}
+
+func (what *RiskGroup) uniqueValues(column int) []string {
+ values := make([]string, 0)
+ for _, risk := range what.Items {
+ values = append(values, risk.Columns[column])
+ }
+
+ sort.Strings(values)
+ unique.Strings(&values)
+
+ return values
+}
diff --git a/pkg/report/risk-item.go b/pkg/report/risk-item.go
new file mode 100644
index 00000000..5a12efbb
--- /dev/null
+++ b/pkg/report/risk-item.go
@@ -0,0 +1,9 @@
+package report
+
+import "github.com/threagile/threagile/pkg/security/types"
+
+type RiskItem struct {
+ Columns []string
+ Status types.RiskStatus
+ Severity types.RiskSeverity
+}
diff --git a/pkg/security/types/risk_status.go b/pkg/security/types/risk_status.go
index 3da71f0a..bdddd221 100644
--- a/pkg/security/types/risk_status.go
+++ b/pkg/security/types/risk_status.go
@@ -62,7 +62,7 @@ func (what RiskStatus) Explain() string {
}
func (what RiskStatus) Title() string {
- return [...]string{"Unchecked", "in Discussion", "Accepted", "in Progress", "Mitigated", "False Positive"}[what]
+ return [...]string{"Unchecked", "In Discussion", "Accepted", "In Progress", "Mitigated", "False Positive"}[what]
}
func (what RiskStatus) IsStillAtRisk() bool {
diff --git a/pkg/server/zip.go b/pkg/server/zip.go
index 431d4cc9..78d7b731 100644
--- a/pkg/server/zip.go
+++ b/pkg/server/zip.go
@@ -111,7 +111,7 @@ func addFileToZip(zipWriter *zip.Writer, filename string) error {
// Using FileInfoHeader() above only uses the basename of the file. If we want
// to preserve the folder structure we can overwrite this with the full path.
- //header.Name = filename
+ //header.Title = filename
// Change to deflate to gain better compression
// see http://golang.org/pkg/archive/zip/#pkg-constants