From f7f712c2f1a742c957f03f2727d8d49039e5db13 Mon Sep 17 00:00:00 2001 From: Simon Kirillov Date: Wed, 21 Aug 2024 12:23:23 +0200 Subject: [PATCH] Fix statistics calculation (#254) * Fix statistics calculation * Add test * Update chromedp dependency * Small refactoring --- go.mod | 6 +- go.sum | 7 ++ internal/db/statistics.go | 213 +++++++++++++-------------------- internal/db/statistics_test.go | 163 ++++++++++++++++++++++++- internal/report/console.go | 8 +- internal/report/html.go | 121 +++++++------------ internal/report/json.go | 4 +- 7 files changed, 301 insertions(+), 221 deletions(-) diff --git a/go.mod b/go.mod index 385c2e69..9f39c42c 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module github.com/wallarm/gotestwaf go 1.22 require ( - github.com/chromedp/cdproto v0.0.0-20240501202034-ef67d660e9fd - github.com/chromedp/chromedp v0.9.5 + github.com/chromedp/cdproto v0.0.0-20240810084448-b931b754e476 + github.com/chromedp/chromedp v0.10.0 github.com/clbanning/mxj v1.8.4 github.com/getkin/kin-openapi v0.114.0 github.com/go-echarts/go-echarts/v2 v2.2.5 @@ -55,7 +55,7 @@ require ( github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect - golang.org/x/sys v0.22.0 // indirect + golang.org/x/sys v0.24.0 // indirect golang.org/x/term v0.22.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index 1134be6c..c766da3e 100644 --- a/go.sum +++ b/go.sum @@ -42,8 +42,13 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= github.com/chromedp/cdproto v0.0.0-20240501202034-ef67d660e9fd h1:5/HXKq8EaAWVmnl6Hnyl4SVq7FF5990DBW6AuTrWtVw= github.com/chromedp/cdproto v0.0.0-20240501202034-ef67d660e9fd/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= +github.com/chromedp/cdproto v0.0.0-20240801214329-3f85d328b335/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= +github.com/chromedp/cdproto v0.0.0-20240810084448-b931b754e476 h1:VnjHsRXCRti7Av7E+j4DCha3kf68echfDzQ+wD11SBU= +github.com/chromedp/cdproto v0.0.0-20240810084448-b931b754e476/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= github.com/chromedp/chromedp v0.9.5 h1:viASzruPJOiThk7c5bueOUY91jGLJVximoEMGoH93rg= github.com/chromedp/chromedp v0.9.5/go.mod h1:D4I2qONslauw/C7INoCir1BJkSwBYMyZgx8X276z3+Y= +github.com/chromedp/chromedp v0.10.0 h1:bRclRYVpMm/UVD76+1HcRW9eV3l58rFfy7AdBvKab1E= +github.com/chromedp/chromedp v0.10.0/go.mod h1:ei/1ncZIqXX1YnAYDkxhD4gzBgavMEUu7JCKvztdomE= github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic= github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -410,6 +415,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= diff --git a/internal/db/statistics.go b/internal/db/statistics.go index 9b74318a..708255ff 100644 --- a/internal/db/statistics.go +++ b/internal/db/statistics.go @@ -12,63 +12,36 @@ type Statistics struct { TestCasesFingerprint string - TruePositiveTests struct { - SummaryTable []*SummaryTableRow - Blocked []*TestDetails - Bypasses []*TestDetails - Unresolved []*TestDetails - Failed []*FailedDetails - - AllRequestsNumber int - BlockedRequestsNumber int - BypassedRequestsNumber int - UnresolvedRequestsNumber int - FailedRequestsNumber int - ResolvedRequestsNumber int - - UnresolvedRequestsPercentage float64 - ResolvedBlockedRequestsPercentage float64 - ResolvedBypassedRequestsPercentage float64 - FailedRequestsPercentage float64 - } - - TrueNegativeTests struct { - SummaryTable []*SummaryTableRow - FalsePositive []*TestDetails - TruePositive []*TestDetails - Unresolved []*TestDetails - Failed []*FailedDetails - - AllRequestsNumber int - BlockedRequestsNumber int - BypassedRequestsNumber int - UnresolvedRequestsNumber int - FailedRequestsNumber int - ResolvedRequestsNumber int - - UnresolvedRequestsPercentage float64 - ResolvedFalseRequestsPercentage float64 - ResolvedTrueRequestsPercentage float64 - FailedRequestsPercentage float64 - } + TruePositiveTests TestsSummary + TrueNegativeTests TestsSummary Score struct { - ApiSec struct { - TruePositive float64 - TrueNegative float64 - Average float64 - } - - AppSec struct { - TruePositive float64 - TrueNegative float64 - Average float64 - } - + ApiSec Score + AppSec Score Average float64 } } +type TestsSummary struct { + SummaryTable []*SummaryTableRow + Blocked []*TestDetails + Bypasses []*TestDetails + Unresolved []*TestDetails + Failed []*FailedDetails + + AllRequestsNumber int + BlockedRequestsNumber int + BypassedRequestsNumber int + UnresolvedRequestsNumber int + FailedRequestsNumber int + ResolvedRequestsNumber int + + UnresolvedRequestsPercentage float64 + ResolvedBlockedRequestsPercentage float64 + ResolvedBypassedRequestsPercentage float64 + FailedRequestsPercentage float64 +} + type SummaryTableRow struct { TestSet string `json:"test_set" validate:"required,printascii,max=256"` TestCase string `json:"test_case" validate:"required,printascii,max=256"` @@ -101,6 +74,12 @@ type FailedDetails struct { Type string `json:"type" validate:"omitempty"` } +type Score struct { + TruePositive float64 + TrueNegative float64 + Average float64 +} + type Path struct { Method string `json:"method" validate:"required,printascii,max=32"` Path string `json:"path" validate:"required,printascii,max=1024"` @@ -233,35 +212,8 @@ func (db *DB) GetStatistics(ignoreUnresolved, nonBlockedAsPassed bool) *Statisti } } - // Number of all negative requests - s.TruePositiveTests.AllRequestsNumber = s.TruePositiveTests.BlockedRequestsNumber + - s.TruePositiveTests.BypassedRequestsNumber + - s.TruePositiveTests.UnresolvedRequestsNumber + - s.TruePositiveTests.FailedRequestsNumber - - // Number of negative resolved requests - s.TruePositiveTests.ResolvedRequestsNumber = s.TruePositiveTests.BlockedRequestsNumber + - s.TruePositiveTests.BypassedRequestsNumber - - // Number of all negative requests - s.TrueNegativeTests.AllRequestsNumber = s.TrueNegativeTests.BlockedRequestsNumber + - s.TrueNegativeTests.BypassedRequestsNumber + - s.TrueNegativeTests.UnresolvedRequestsNumber + - s.TrueNegativeTests.FailedRequestsNumber - - // Number of positive resolved requests - s.TrueNegativeTests.ResolvedRequestsNumber = s.TrueNegativeTests.BlockedRequestsNumber + - s.TrueNegativeTests.BypassedRequestsNumber - - s.TruePositiveTests.UnresolvedRequestsPercentage = CalculatePercentage(s.TruePositiveTests.UnresolvedRequestsNumber, s.TruePositiveTests.AllRequestsNumber) - s.TruePositiveTests.ResolvedBlockedRequestsPercentage = CalculatePercentage(s.TruePositiveTests.BlockedRequestsNumber, s.TruePositiveTests.ResolvedRequestsNumber) - s.TruePositiveTests.ResolvedBypassedRequestsPercentage = CalculatePercentage(s.TruePositiveTests.BypassedRequestsNumber, s.TruePositiveTests.ResolvedRequestsNumber) - s.TruePositiveTests.FailedRequestsPercentage = CalculatePercentage(s.TruePositiveTests.FailedRequestsNumber, s.TruePositiveTests.AllRequestsNumber) - - s.TrueNegativeTests.UnresolvedRequestsPercentage = CalculatePercentage(s.TrueNegativeTests.UnresolvedRequestsNumber, s.TrueNegativeTests.AllRequestsNumber) - s.TrueNegativeTests.ResolvedFalseRequestsPercentage = CalculatePercentage(s.TrueNegativeTests.BlockedRequestsNumber, s.TrueNegativeTests.ResolvedRequestsNumber) - s.TrueNegativeTests.ResolvedTrueRequestsPercentage = CalculatePercentage(s.TrueNegativeTests.BypassedRequestsNumber, s.TrueNegativeTests.ResolvedRequestsNumber) - s.TrueNegativeTests.FailedRequestsPercentage = CalculatePercentage(s.TrueNegativeTests.FailedRequestsNumber, s.TrueNegativeTests.AllRequestsNumber) + calculateTestsSummaryStat(&s.TruePositiveTests) + calculateTestsSummaryStat(&s.TrueNegativeTests) for _, blockedTest := range db.blockedTests { sort.Strings(blockedTest.AdditionalInfo) @@ -278,7 +230,7 @@ func (db *DB) GetStatistics(ignoreUnresolved, nonBlockedAsPassed bool) *Statisti } if isFalsePositiveTest(blockedTest.Set) { - s.TrueNegativeTests.FalsePositive = append(s.TrueNegativeTests.FalsePositive, testDetails) + s.TrueNegativeTests.Blocked = append(s.TrueNegativeTests.Blocked, testDetails) } else { s.TruePositiveTests.Blocked = append(s.TruePositiveTests.Blocked, testDetails) } @@ -299,7 +251,7 @@ func (db *DB) GetStatistics(ignoreUnresolved, nonBlockedAsPassed bool) *Statisti } if isFalsePositiveTest(passedTest.Set) { - s.TrueNegativeTests.TruePositive = append(s.TrueNegativeTests.TruePositive, testDetails) + s.TrueNegativeTests.Bypasses = append(s.TrueNegativeTests.Bypasses, testDetails) } else { s.TruePositiveTests.Bypasses = append(s.TruePositiveTests.Bypasses, testDetails) } @@ -321,7 +273,7 @@ func (db *DB) GetStatistics(ignoreUnresolved, nonBlockedAsPassed bool) *Statisti if ignoreUnresolved || nonBlockedAsPassed { if isFalsePositiveTest(unresolvedTest.Set) { - s.TrueNegativeTests.FalsePositive = append(s.TrueNegativeTests.FalsePositive, testDetails) + s.TrueNegativeTests.Blocked = append(s.TrueNegativeTests.Blocked, testDetails) } else { s.TruePositiveTests.Bypasses = append(s.TruePositiveTests.Bypasses, testDetails) } @@ -395,7 +347,7 @@ func (db *DB) GetStatistics(ignoreUnresolved, nonBlockedAsPassed bool) *Statisti var appSecTrueNegBypassNum int var appSecTrueNegNum int - for _, test := range s.TrueNegativeTests.TruePositive { + for _, test := range s.TrueNegativeTests.Bypasses { if isApiTest(test.TestSet) { apiSecTrueNegNum++ apiSecTrueNegBypassNum++ @@ -404,7 +356,7 @@ func (db *DB) GetStatistics(ignoreUnresolved, nonBlockedAsPassed bool) *Statisti appSecTrueNegBypassNum++ } } - for _, test := range s.TrueNegativeTests.FalsePositive { + for _, test := range s.TrueNegativeTests.Blocked { if isApiTest(test.TestSet) { apiSecTrueNegNum++ } else { @@ -412,75 +364,76 @@ func (db *DB) GetStatistics(ignoreUnresolved, nonBlockedAsPassed bool) *Statisti } } + calculateScorePercentage(&s.Score.ApiSec, apiSecTruePosBlockedNum, apiSecTruePosNum, apiSecTrueNegBypassNum, apiSecTrueNegNum) + calculateScorePercentage(&s.Score.AppSec, appSecTruePosBlockedNum, appSecTruePosNum, appSecTrueNegBypassNum, appSecTrueNegNum) + var divider int var sum float64 - s.Score.ApiSec.TruePositive = CalculatePercentage(apiSecTruePosBlockedNum, apiSecTruePosNum) - s.Score.ApiSec.TrueNegative = CalculatePercentage(apiSecTrueNegBypassNum, apiSecTrueNegNum) - - if apiSecTruePosNum != 0 { + if s.Score.ApiSec.Average != -1.0 { divider++ - sum += s.Score.ApiSec.TruePositive - } else { - s.Score.ApiSec.TruePositive = -1.0 + sum += s.Score.ApiSec.Average } - - if apiSecTrueNegNum != 0 { + if s.Score.AppSec.Average != -1.0 { divider++ - sum += s.Score.ApiSec.TrueNegative - } else { - s.Score.ApiSec.TrueNegative = -1.0 + sum += s.Score.AppSec.Average } if divider != 0 { - s.Score.ApiSec.Average = Round(sum / float64(divider)) + s.Score.Average = Round(sum / float64(divider)) } else { - s.Score.ApiSec.Average = -1.0 + s.Score.Average = -1.0 } - divider = 0 - sum = 0.0 + return s +} - s.Score.AppSec.TruePositive = CalculatePercentage(appSecTruePosBlockedNum, appSecTruePosNum) - s.Score.AppSec.TrueNegative = CalculatePercentage(appSecTrueNegBypassNum, appSecTrueNegNum) +func calculateTestsSummaryStat(s *TestsSummary) { + s.AllRequestsNumber = s.BlockedRequestsNumber + + s.BypassedRequestsNumber + + s.UnresolvedRequestsNumber + + s.FailedRequestsNumber - if appSecTruePosNum != 0 { - divider++ - sum += s.Score.AppSec.TruePositive - } else { - s.Score.AppSec.TruePositive = -1.0 - } + s.ResolvedRequestsNumber = s.BlockedRequestsNumber + + s.BypassedRequestsNumber - if appSecTrueNegNum != 0 { - divider++ - sum += s.Score.AppSec.TrueNegative - } else { - s.Score.AppSec.TrueNegative = -1.0 - } + s.UnresolvedRequestsPercentage = CalculatePercentage(s.UnresolvedRequestsNumber, s.AllRequestsNumber) + s.ResolvedBlockedRequestsPercentage = CalculatePercentage(s.BlockedRequestsNumber, s.ResolvedRequestsNumber) + s.ResolvedBypassedRequestsPercentage = CalculatePercentage(s.BypassedRequestsNumber, s.ResolvedRequestsNumber) + s.FailedRequestsPercentage = CalculatePercentage(s.FailedRequestsNumber, s.AllRequestsNumber) +} - if divider != 0 { - s.Score.AppSec.Average = Round(sum / float64(divider)) - } else { - s.Score.AppSec.Average = -1.0 - } +func calculateScorePercentage(s *Score, truePosBlockedNum, truePosNum, trueNegBypassNum, trueNegNum int) { + var ( + divider int + sum float64 + ) - divider = 0 - sum = 0.0 + s.TruePositive = CalculatePercentage(truePosBlockedNum, truePosNum) + s.TrueNegative = CalculatePercentage(trueNegBypassNum, trueNegNum) - if s.Score.ApiSec.Average != -1.0 { + if truePosNum != 0 { divider++ - sum += s.Score.ApiSec.Average + sum += s.TruePositive + } else { + s.TruePositive = -1.0 } - if s.Score.AppSec.Average != -1.0 { + + if trueNegNum != 0 { divider++ - sum += s.Score.AppSec.Average + sum += s.TrueNegative + } else { + s.TrueNegative = -1.0 } if divider != 0 { - s.Score.Average = Round(sum / float64(divider)) + // If all malicious request were passed then grade is 0. + if truePosBlockedNum == 0 { + s.Average = 0.0 + } else { + s.Average = Round(sum / float64(divider)) + } } else { - s.Score.Average = -1.0 + s.Average = -1.0 } - - return s } diff --git a/internal/db/statistics_test.go b/internal/db/statistics_test.go index a38f3800..fe3fdd14 100644 --- a/internal/db/statistics_test.go +++ b/internal/db/statistics_test.go @@ -182,8 +182,8 @@ func testPropertyOnlyPositiveNumberValues(db *DB, ignoreUnresolved, nonBlockedAs stat.TrueNegativeTests.FailedRequestsNumber < 0 || stat.TrueNegativeTests.ResolvedRequestsNumber < 0 || stat.TrueNegativeTests.UnresolvedRequestsPercentage < 0 || - stat.TrueNegativeTests.ResolvedFalseRequestsPercentage < 0 || - stat.TrueNegativeTests.ResolvedTrueRequestsPercentage < 0 || + stat.TrueNegativeTests.ResolvedBlockedRequestsPercentage < 0 || + stat.TrueNegativeTests.ResolvedBypassedRequestsPercentage < 0 || stat.TrueNegativeTests.FailedRequestsPercentage < 0 { return false } @@ -489,3 +489,162 @@ func BoolGenerator(b bool) gopter.Gen { return gopter.NewGenResult(b, gopter.NoShrinker) } } + +func TestStatisticsCalculation(t *testing.T) { + testCases := []struct { + apiSecTruePosBypassesNum int + apiSecTruePosBlockedNum int + + apiSecTrueNegBypassesNum int + apiSecTrueNegBlockedNum int + + appSecTruePosBypassesNum int + appSecTruePosBlockedNum int + + appSecTrueNegBypassesNum int + appSecTrueNegBlockedNum int + }{ + {0, 0, 0, 0, 0, 0, 0, 0}, + {rand.Int()%500 + 1, rand.Int()%500 + 1, rand.Int()%500 + 1, rand.Int()%500 + 1, rand.Int()%500 + 1, rand.Int()%500 + 1, rand.Int()%500 + 1, rand.Int()%500 + 1}, + {rand.Int()%500 + 1, 0, 0, 0, 0, 0, 0, 0}, + {0, rand.Int()%500 + 1, 0, 0, 0, 0, 0, 0}, + {rand.Int()%500 + 1, rand.Int()%500 + 1, 0, 0, 0, 0, 0, 0}, + {0, 0, rand.Int()%500 + 1, 0, 0, 0, 0, 0}, + {0, 0, 0, rand.Int()%500 + 1, 0, 0, 0, 0}, + {0, 0, rand.Int()%500 + 1, rand.Int()%500 + 1, 0, 0, 0, 0}, + {0, 0, 0, 0, rand.Int()%500 + 1, 0, 0, 0}, + {0, 0, 0, 0, 0, rand.Int()%500 + 1, 0, 0}, + {0, 0, 0, 0, rand.Int()%500 + 1, rand.Int()%500 + 1, 0, 0}, + {0, 0, 0, 0, 0, 0, rand.Int()%500 + 1, 0}, + {0, 0, 0, 0, 0, 0, 0, rand.Int()%500 + 1}, + {0, 0, 0, 0, 0, 0, rand.Int()%500 + 1, rand.Int()%500 + 1}, + } + + cases := []*Case{ + {Set: ""}, + {Set: "false"}, + {Set: "api"}, + {Set: "api-false"}, + } + + for _, tc := range testCases { + db, err := NewDB(cases) + if err != nil { + t.Fatal(err) + } + + for i := 0; i < tc.apiSecTruePosBypassesNum; i++ { + db.UpdatePassedTests(&Info{Set: "api"}) + } + for i := 0; i < tc.apiSecTruePosBlockedNum; i++ { + db.UpdateBlockedTests(&Info{Set: "api"}) + } + + for i := 0; i < tc.apiSecTrueNegBypassesNum; i++ { + db.UpdatePassedTests(&Info{Set: "api-false"}) + } + for i := 0; i < tc.apiSecTrueNegBlockedNum; i++ { + db.UpdateBlockedTests(&Info{Set: "api-false"}) + } + + for i := 0; i < tc.appSecTruePosBypassesNum; i++ { + db.UpdatePassedTests(&Info{}) + } + for i := 0; i < tc.appSecTruePosBlockedNum; i++ { + db.UpdateBlockedTests(&Info{}) + } + + for i := 0; i < tc.appSecTrueNegBypassesNum; i++ { + db.UpdatePassedTests(&Info{Set: "false"}) + } + for i := 0; i < tc.appSecTrueNegBlockedNum; i++ { + db.UpdateBlockedTests(&Info{Set: "false"}) + } + + stat := db.GetStatistics(false, false) + + sum := 0.0 + div := 0 + + apiSecTruePosNum := tc.apiSecTruePosBypassesNum + tc.apiSecTruePosBlockedNum + apiSecTruePosPercentage := CalculatePercentage(tc.apiSecTruePosBlockedNum, apiSecTruePosNum) + if apiSecTruePosNum == 0 { + apiSecTruePosPercentage = -1.0 + } else { + div++ + sum += apiSecTruePosPercentage + } + + apiSecTrueNegNum := tc.apiSecTrueNegBypassesNum + tc.apiSecTrueNegBlockedNum + apiSecTrueNegPercentage := CalculatePercentage(tc.apiSecTrueNegBypassesNum, apiSecTrueNegNum) + if apiSecTrueNegNum == 0 { + apiSecTrueNegPercentage = -1.0 + } else { + div++ + sum += apiSecTrueNegPercentage + } + + apiSecAverage := 0.0 + if div == 0 { + apiSecAverage = -1.0 + } else { + if tc.apiSecTruePosBlockedNum != 0 { + apiSecAverage = Round(sum / float64(div)) + } + } + + if stat.Score.ApiSec.TruePositive != apiSecTruePosPercentage { + t.Fatalf("ApiSec.TruePositive: want %#v, got %#v", apiSecTruePosPercentage, stat.Score.ApiSec.TruePositive) + } + + if stat.Score.ApiSec.TrueNegative != apiSecTrueNegPercentage { + t.Fatalf("ApiSec.TrueNegative: want %#v, got %#v", apiSecTrueNegPercentage, stat.Score.ApiSec.TrueNegative) + } + + if stat.Score.ApiSec.Average != apiSecAverage { + t.Fatalf("ApiSec.Average: want %#v, got %#v", apiSecAverage, stat.Score.ApiSec.Average) + } + + sum = 0.0 + div = 0 + + appSecTruePosNum := tc.appSecTruePosBypassesNum + tc.appSecTruePosBlockedNum + appSecTruePosPercentage := CalculatePercentage(tc.appSecTruePosBlockedNum, appSecTruePosNum) + if appSecTruePosNum == 0 { + appSecTruePosPercentage = -1.0 + } else { + div++ + sum += appSecTruePosPercentage + } + + appSecTrueNegNum := tc.appSecTrueNegBypassesNum + tc.appSecTrueNegBlockedNum + appSecTrueNegPercentage := CalculatePercentage(tc.appSecTrueNegBypassesNum, appSecTrueNegNum) + if appSecTrueNegNum == 0 { + appSecTrueNegPercentage = -1.0 + } else { + div++ + sum += appSecTrueNegPercentage + } + + appSecAverage := 0.0 + if div == 0 { + appSecAverage = -1.0 + } else { + if tc.appSecTruePosBlockedNum != 0 { + appSecAverage = Round(sum / float64(div)) + } + } + + if stat.Score.AppSec.TruePositive != appSecTruePosPercentage { + t.Fatalf("AppSec.TruePositive: want %#v, got %#v", appSecTruePosPercentage, stat.Score.AppSec.TruePositive) + } + + if stat.Score.AppSec.TrueNegative != appSecTrueNegPercentage { + t.Fatalf("AppSec.TrueNegative: want %#v, got %#v", appSecTrueNegPercentage, stat.Score.AppSec.TrueNegative) + } + + if stat.Score.AppSec.Average != appSecAverage { + t.Fatalf("AppSec.Average: want %#v, got %#v", appSecAverage, stat.Score.AppSec.Average) + } + } +} diff --git a/internal/report/console.go b/internal/report/console.go index 9579366c..7dfa740b 100644 --- a/internal/report/console.go +++ b/internal/report/console.go @@ -148,16 +148,16 @@ func printConsoleReportTable( footerPositiveTests := []string{ fmt.Sprintf("Date:\n%s", reportTime.Format("2006-01-02")), fmt.Sprintf("Project Name:\n%s", wafName), - fmt.Sprintf("True-Negative Score:\n%.2f%%", s.TrueNegativeTests.ResolvedTrueRequestsPercentage), + fmt.Sprintf("True-Negative Score:\n%.2f%%", s.TrueNegativeTests.ResolvedBypassedRequestsPercentage), fmt.Sprintf("Blocked (Resolved):\n%d/%d (%.2f%%)", s.TrueNegativeTests.BlockedRequestsNumber, s.TrueNegativeTests.ResolvedRequestsNumber, - s.TrueNegativeTests.ResolvedFalseRequestsPercentage, + s.TrueNegativeTests.ResolvedBlockedRequestsPercentage, ), fmt.Sprintf("Bypassed (Resolved):\n%d/%d (%.2f%%)", s.TrueNegativeTests.BypassedRequestsNumber, s.TrueNegativeTests.ResolvedRequestsNumber, - s.TrueNegativeTests.ResolvedTrueRequestsPercentage, + s.TrueNegativeTests.ResolvedBypassedRequestsPercentage, ), } if !ignoreUnresolved { @@ -284,7 +284,7 @@ func printConsoleReportJson( if len(s.TrueNegativeTests.SummaryTable) != 0 { report.TrueNegativeTests = &testsInfo{ - Score: s.TrueNegativeTests.ResolvedTrueRequestsPercentage, + Score: s.TrueNegativeTests.ResolvedBypassedRequestsPercentage, TotalSent: s.TrueNegativeTests.AllRequestsNumber, ResolvedTests: s.TrueNegativeTests.ResolvedRequestsNumber, BlockedTests: s.TrueNegativeTests.BlockedRequestsNumber, diff --git a/internal/report/html.go b/internal/report/html.go index 126fa433..035cf060 100644 --- a/internal/report/html.go +++ b/internal/report/html.go @@ -61,18 +61,18 @@ var ( } ) -func computeGrade(value float64, all int) *report.Grade { +func getGrade(grade float64, na bool) *report.Grade { g := &report.Grade{ Percentage: 0.0, Mark: naMark, CSSClassSuffix: "na", } - if all == 0 { + if na { return g } - g.Percentage = value / float64(all) + g.Percentage = grade if g.Percentage <= 1 { g.Percentage *= 100 } @@ -122,6 +122,14 @@ func computeGrade(value float64, all int) *report.Grade { return g } +func computeGrade(value float64, all int) *report.Grade { + if all == 0 { + return getGrade(0.0, true) + } + + return getGrade(value/float64(all), false) +} + // truncatePayload replaces the middle part of the payload if // it is longer than maxUntruncatedPayloadLength. func truncatePayload(payload string) string { @@ -162,90 +170,43 @@ func prepareHTMLFullReport( WallarmResult: wallarmResult, } - var apiSecNegBlockedNum int - var apiSecNegNum int - var appSecNegBlockedNum int - var appSecNegNum int - - for _, test := range s.TruePositiveTests.Blocked { - if isApiTest(test.TestSet) { - apiSecNegNum++ - apiSecNegBlockedNum++ - } else { - appSecNegNum++ - appSecNegBlockedNum++ - } - } - for _, test := range s.TruePositiveTests.Bypasses { - if isApiTest(test.TestSet) { - apiSecNegNum++ - } else { - appSecNegNum++ - } + if s.Score.ApiSec.TruePositive < 0 { + data.ApiSec.TruePositiveTestsGrade = getGrade(0.0, true) + } else { + data.ApiSec.TruePositiveTestsGrade = getGrade(s.Score.ApiSec.TruePositive, false) } - var apiSecPosBypassNum int - var apiSecPosNum int - var appSecPosBypassNum int - var appSecPosNum int - - for _, test := range s.TrueNegativeTests.TruePositive { - if isApiTest(test.TestSet) { - apiSecPosNum++ - apiSecPosBypassNum++ - } else { - appSecPosNum++ - appSecPosBypassNum++ - } - } - for _, test := range s.TrueNegativeTests.FalsePositive { - if isApiTest(test.TestSet) { - apiSecPosNum++ - } else { - appSecPosNum++ - } + if s.Score.ApiSec.TrueNegative < 0 { + data.ApiSec.TrueNegativeTestsGrade = getGrade(0.0, true) + } else { + data.ApiSec.TrueNegativeTestsGrade = getGrade(s.Score.ApiSec.TrueNegative, false) } - divider := 0 - data.ApiSec.TruePositiveTestsGrade = computeGrade(float64(apiSecNegBlockedNum), apiSecNegNum) - data.ApiSec.TrueNegativeTestsGrade = computeGrade(float64(apiSecPosBypassNum), apiSecPosNum) - if data.ApiSec.TruePositiveTestsGrade.Mark != naMark { - divider++ + if s.Score.ApiSec.Average < 0 { + data.ApiSec.Grade = getGrade(0.0, true) + } else { + data.ApiSec.Grade = getGrade(s.Score.ApiSec.Average, false) } - if data.ApiSec.TrueNegativeTestsGrade.Mark != naMark { - divider++ - } - data.ApiSec.Grade = computeGrade( - data.ApiSec.TruePositiveTestsGrade.Percentage+ - data.ApiSec.TrueNegativeTestsGrade.Percentage, - divider, - ) - divider = 0 - - data.AppSec.TruePositiveTestsGrade = computeGrade(float64(appSecNegBlockedNum), appSecNegNum) - data.AppSec.TrueNegativeTestsGrade = computeGrade(float64(appSecPosBypassNum), appSecPosNum) - if data.AppSec.TruePositiveTestsGrade.Mark != naMark { - divider++ - } - if data.AppSec.TrueNegativeTestsGrade.Mark != naMark { - divider++ + if s.Score.AppSec.TruePositive < 0 { + data.AppSec.TruePositiveTestsGrade = getGrade(0.0, true) + } else { + data.AppSec.TruePositiveTestsGrade = getGrade(s.Score.AppSec.TruePositive, false) } - data.AppSec.Grade = computeGrade( - data.AppSec.TruePositiveTestsGrade.Percentage+ - data.AppSec.TrueNegativeTestsGrade.Percentage, - divider, - ) - divider = 0 - if data.ApiSec.Grade.Mark != naMark { - divider++ + if s.Score.AppSec.TrueNegative < 0 { + data.AppSec.TrueNegativeTestsGrade = getGrade(0.0, true) + } else { + data.AppSec.TrueNegativeTestsGrade = getGrade(s.Score.AppSec.TrueNegative, false) } - if data.AppSec.Grade.Mark != naMark { - divider++ + + if s.Score.AppSec.Average < 0 { + data.AppSec.Grade = getGrade(0.0, true) + } else { + data.AppSec.Grade = getGrade(s.Score.AppSec.Average, false) } - data.Overall = computeGrade( - data.ApiSec.Grade.Percentage+data.AppSec.Grade.Percentage, divider) + + data.Overall = getGrade(s.Score.Average, false) apiIndicators, apiItems, appIndicators, appItems := generateChartData(s) @@ -362,7 +323,7 @@ func prepareHTMLFullReport( // map[payload]map[statusCode]*testDetails posBlocked := make(map[string]map[int]*report.TestDetails) - for _, d := range s.TrueNegativeTests.FalsePositive { + for _, d := range s.TrueNegativeTests.Blocked { payload := truncatePayload(d.Payload) if _, ok := posBlocked[payload]; !ok { @@ -384,7 +345,7 @@ func prepareHTMLFullReport( // map[payload]map[statusCode]*testDetails posBypassed := make(map[string]map[int]*report.TestDetails) - for _, d := range s.TrueNegativeTests.TruePositive { + for _, d := range s.TrueNegativeTests.Bypasses { payload := truncatePayload(d.Payload) if _, ok := posBypassed[payload]; !ok { @@ -441,7 +402,7 @@ func prepareHTMLFullReport( data.TruePositiveTests.UnresolvedRequestsNumber = s.TruePositiveTests.UnresolvedRequestsNumber data.TruePositiveTests.FailedRequestsNumber = s.TruePositiveTests.FailedRequestsNumber - data.TrueNegativeTests.Percentage = s.TrueNegativeTests.ResolvedTrueRequestsPercentage + data.TrueNegativeTests.Percentage = s.TrueNegativeTests.ResolvedBypassedRequestsPercentage data.TrueNegativeTests.TotalSent = s.TrueNegativeTests.AllRequestsNumber data.TrueNegativeTests.BlockedRequestsNumber = s.TrueNegativeTests.BlockedRequestsNumber data.TrueNegativeTests.BypassedRequestsNumber = s.TrueNegativeTests.BypassedRequestsNumber diff --git a/internal/report/json.go b/internal/report/json.go index 97928fc3..c3cefebb 100644 --- a/internal/report/json.go +++ b/internal/report/json.go @@ -126,7 +126,7 @@ func printFullReportToJson( if len(s.TrueNegativeTests.SummaryTable) != 0 { report.Summary.TrueNegativeTests = &testsInfo{ - Score: s.TrueNegativeTests.ResolvedTrueRequestsPercentage, + Score: s.TrueNegativeTests.ResolvedBypassedRequestsPercentage, TotalSent: s.TrueNegativeTests.AllRequestsNumber, ResolvedTests: s.TrueNegativeTests.ResolvedRequestsNumber, BlockedTests: s.TrueNegativeTests.BlockedRequestsNumber, @@ -197,7 +197,7 @@ func printFullReportToJson( report.TrueNegativeTestsPayloads = &testPayloads{} - for _, blocked := range s.TrueNegativeTests.FalsePositive { + for _, blocked := range s.TrueNegativeTests.Blocked { blockedDetails := &payloadDetails{ Payload: blocked.Payload, TestSet: blocked.TestSet,