From 3077484674501d924314b3db29139d60ba92ad34 Mon Sep 17 00:00:00 2001 From: Joerg Reichelt Date: Thu, 18 Jul 2024 11:39:55 -0700 Subject: [PATCH] added generation of explanations to script engine --- go.mod | 30 +-- go.sum | 29 +++ pkg/risks/script/common/any-value.go | 157 +++++++++------ pkg/risks/script/common/array-value.go | 98 +++++---- pkg/risks/script/common/bool-value.go | 57 ++++-- pkg/risks/script/common/built-in.go | 16 +- pkg/risks/script/common/cast.go | 108 +++++----- pkg/risks/script/common/compare.go | 132 ++++++------- pkg/risks/script/common/decimal-value.go | 57 ++++-- pkg/risks/script/common/event.go | 153 -------------- pkg/risks/script/common/history.go | 32 --- pkg/risks/script/common/path.go | 68 ------- pkg/risks/script/common/property.go | 106 +++++----- pkg/risks/script/common/scope.go | 92 ++++----- pkg/risks/script/common/stack.go | 16 ++ pkg/risks/script/common/string-value.go | 71 +++++-- pkg/risks/script/common/value.go | 9 +- pkg/risks/script/common/values.go | 10 +- pkg/risks/script/event/contain.go | 37 ++++ pkg/risks/script/event/contain_test.go | 53 +++++ pkg/risks/script/event/equal.go | 37 ++++ pkg/risks/script/event/equal_test.go | 53 +++++ pkg/risks/script/event/event.go | 7 + pkg/risks/script/event/explain.go | 27 +++ pkg/risks/script/event/explain_test.go | 19 ++ pkg/risks/script/event/false.go | 35 ++++ pkg/risks/script/event/greater-or-equal.go | 37 ++++ pkg/risks/script/event/greater.go | 37 ++++ pkg/risks/script/event/history.go | 12 ++ pkg/risks/script/event/less-or-equal.go | 37 ++++ pkg/risks/script/event/less.go | 37 ++++ pkg/risks/script/event/not-equal.go | 37 ++++ pkg/risks/script/event/path.go | 25 +++ pkg/risks/script/event/test-value.go | 45 +++++ pkg/risks/script/event/text.go | 186 ++++++++++++++++++ pkg/risks/script/event/texter.go | 9 + pkg/risks/script/event/true.go | 35 ++++ pkg/risks/script/event/value-event.go | 25 +++ pkg/risks/script/event/value.go | 9 + .../script/expressions/all-expression.go | 124 +++++------- .../script/expressions/and-expression.go | 14 +- .../script/expressions/any-expression.go | 123 +++++------- .../script/expressions/array-expression.go | 3 +- ...ns-expression.go => contain-expression.go} | 51 +++-- .../script/expressions/count-expression.go | 89 +++++---- .../script/expressions/equal-expression.go | 9 +- .../expressions/equal-expression_test.go | 70 +++++++ .../equal-or-greater-expression.go | 13 +- .../expressions/equal-or-less-expression.go | 13 +- .../script/expressions/expression-list.go | 5 +- .../script/expressions/false-expression.go | 12 +- .../script/expressions/greater-expression.go | 9 +- .../script/expressions/less-expression.go | 9 +- .../expressions/not-equal-expression.go | 9 +- pkg/risks/script/expressions/or-expression.go | 10 +- .../script/expressions/value-expression.go | 83 ++++---- pkg/risks/script/property/blank.go | 23 --- pkg/risks/script/property/equal.go | 28 --- pkg/risks/script/property/false.go | 29 --- pkg/risks/script/property/greater-or-equal.go | 28 --- pkg/risks/script/property/greater.go | 28 --- pkg/risks/script/property/item-with-path.go | 6 - pkg/risks/script/property/item.go | 7 - pkg/risks/script/property/less-or-equal.go | 28 --- pkg/risks/script/property/less.go | 28 --- pkg/risks/script/property/not-equal.go | 28 --- pkg/risks/script/property/texter.go | 5 - pkg/risks/script/property/true.go | 25 --- pkg/risks/script/property/value.go | 63 ------ pkg/risks/script/script.go | 64 +++--- pkg/risks/script/statements/if-statement.go | 9 +- pkg/risks/script/statements/loop-statement.go | 16 +- .../script/statements/method-statement.go | 7 +- .../script/statements/return-statement.go | 6 +- pkg/risks/scripts/accidental-secret-leak.yaml | 6 +- .../scripts/accidental_secret_leak_test.go | 6 +- 76 files changed, 1775 insertions(+), 1351 deletions(-) delete mode 100644 pkg/risks/script/common/event.go delete mode 100644 pkg/risks/script/common/history.go delete mode 100644 pkg/risks/script/common/path.go create mode 100644 pkg/risks/script/common/stack.go create mode 100644 pkg/risks/script/event/contain.go create mode 100644 pkg/risks/script/event/contain_test.go create mode 100644 pkg/risks/script/event/equal.go create mode 100644 pkg/risks/script/event/equal_test.go create mode 100644 pkg/risks/script/event/event.go create mode 100644 pkg/risks/script/event/explain.go create mode 100644 pkg/risks/script/event/explain_test.go create mode 100644 pkg/risks/script/event/false.go create mode 100644 pkg/risks/script/event/greater-or-equal.go create mode 100644 pkg/risks/script/event/greater.go create mode 100644 pkg/risks/script/event/history.go create mode 100644 pkg/risks/script/event/less-or-equal.go create mode 100644 pkg/risks/script/event/less.go create mode 100644 pkg/risks/script/event/not-equal.go create mode 100644 pkg/risks/script/event/path.go create mode 100644 pkg/risks/script/event/test-value.go create mode 100644 pkg/risks/script/event/text.go create mode 100644 pkg/risks/script/event/texter.go create mode 100644 pkg/risks/script/event/true.go create mode 100644 pkg/risks/script/event/value-event.go create mode 100644 pkg/risks/script/event/value.go rename pkg/risks/script/expressions/{contains-expression.go => contain-expression.go} (59%) create mode 100644 pkg/risks/script/expressions/equal-expression_test.go delete mode 100644 pkg/risks/script/property/blank.go delete mode 100644 pkg/risks/script/property/equal.go delete mode 100644 pkg/risks/script/property/false.go delete mode 100644 pkg/risks/script/property/greater-or-equal.go delete mode 100644 pkg/risks/script/property/greater.go delete mode 100644 pkg/risks/script/property/item-with-path.go delete mode 100644 pkg/risks/script/property/item.go delete mode 100644 pkg/risks/script/property/less-or-equal.go delete mode 100644 pkg/risks/script/property/less.go delete mode 100644 pkg/risks/script/property/not-equal.go delete mode 100644 pkg/risks/script/property/texter.go delete mode 100644 pkg/risks/script/property/true.go delete mode 100644 pkg/risks/script/property/value.go diff --git a/go.mod b/go.mod index 7af4c47e..83ba2f8d 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ module github.com/threagile/threagile -go 1.20 +go 1.22 require ( github.com/chzyer/readline v1.5.1 - github.com/gin-gonic/gin v1.9.1 + github.com/gin-gonic/gin v1.10.0 github.com/google/uuid v1.6.0 github.com/jung-kurt/gofpdf v1.16.2 github.com/mattn/go-shellwords v1.0.12 @@ -13,19 +13,19 @@ require ( github.com/spf13/pflag v1.0.5 github.com/wcharczuk/go-chart v2.0.1+incompatible github.com/xuri/excelize/v2 v2.8.1 - golang.org/x/crypto v0.22.0 + golang.org/x/crypto v0.25.0 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/bytedance/sonic/loader v0.1.1 // indirect - github.com/cloudwego/base64x v0.1.3 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-json v0.10.3 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -38,28 +38,28 @@ require ( github.com/richardlehane/mscfb v1.0.4 // indirect github.com/richardlehane/msoleps v1.0.3 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - golang.org/x/sys v0.19.0 // indirect + golang.org/x/sys v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect ) require ( github.com/akedrou/textdiff v0.0.0-20230423230343-2ebdcebdccc1 github.com/blend/go-sdk v1.20220411.3 // indirect - github.com/bytedance/sonic v1.11.5 // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/go-playground/validator/v10 v10.19.0 // indirect + github.com/bytedance/sonic v1.11.9 // indirect + github.com/gabriel-vasile/mimetype v1.4.4 // indirect + github.com/go-playground/validator/v10 v10.22.0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect - github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/pelletier/go-toml/v2 v2.2.1 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/phpdave11/gofpdi v1.0.13 // indirect - github.com/spf13/cobra v1.8.0 + github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 github.com/ugorji/go/codec v1.2.12 // indirect github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d // indirect github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 // indirect - golang.org/x/arch v0.7.0 // indirect + golang.org/x/arch v0.8.0 // indirect golang.org/x/image v0.18.0 // indirect - golang.org/x/net v0.24.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/net v0.27.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/go.sum b/go.sum index 9eea0cb7..79123704 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,8 @@ github.com/blend/go-sdk v1.20220411.3/go.mod h1:7lnH8fTi6U4i1fArEXRyOIY2E1X4MALg github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bytedance/sonic v1.11.5 h1:G00FYjjqll5iQ1PYXynbg/hyzqBqavH8Mo9/oTopd9k= github.com/bytedance/sonic v1.11.5/go.mod h1:X2PC2giUdj/Cv2lliWFLk6c/DUQok5rViJSemeB0wDw= +github.com/bytedance/sonic v1.11.9 h1:LFHENlIY/SLzDWverzdOvgMztTxcfcF+cqNsz9pK5zg= +github.com/bytedance/sonic v1.11.9/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic/loader v0.1.0/go.mod h1:UmRT+IRTGKz/DAkzcEGzyVqQFJ7H9BqwBO3pm9H/+HY= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= @@ -16,18 +18,25 @@ github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/cloudwego/base64x v0.1.3 h1:b5J/l8xolB7dyDTTmhJP2oTs5LdrjyrUFuNxdfq5hAg= github.com/cloudwego/base64x v0.1.3/go.mod h1:1+1K5BUHIQzyapgpF7LwvOGAEDicKtt1umPV+aN8pi8= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I= +github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= @@ -35,8 +44,12 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= +github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -54,6 +67,8 @@ github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/Ym github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= @@ -72,6 +87,8 @@ github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de h1:D5x39vF5KCwKQaw+OC9 github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de/go.mod h1:kJun4WP5gFuHZgRjZUWWuH1DTxCtxbHDOIJsudS8jzY= github.com/pelletier/go-toml/v2 v2.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtosvtEhg= github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/phpdave11/gofpdi v1.0.13 h1:o61duiW8M9sMlkVXWlvP92sZJtGKENvW3VExs6dZukQ= github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= @@ -91,6 +108,8 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -121,23 +140,33 @@ github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7/go.mod h1:WwHg+CVyzlv/TX9 golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/risks/script/common/any-value.go b/pkg/risks/script/common/any-value.go index a2c29222..e4287939 100644 --- a/pkg/risks/script/common/any-value.go +++ b/pkg/risks/script/common/any-value.go @@ -2,100 +2,116 @@ package common import ( "fmt" + "github.com/shopspring/decimal" + "github.com/threagile/threagile/pkg/risks/script/event" + "gopkg.in/yaml.v3" ) type AnyValue struct { - value any - name Path - event *Event + value any + path event.Path + history event.History +} + +func (what AnyValue) PlainValue() any { + switch castValue := what.value.(type) { + case Value: + return castValue.PlainValue() + } + + return what.value } func (what AnyValue) Value() any { return what.value } -func (what AnyValue) Name() Path { - return what.name +func (what AnyValue) Path() event.Path { + return what.path } -func (what AnyValue) SetName(name ...string) { - what.name.SetPath(name...) +func (what AnyValue) ValueText() event.Text { + yamlText, yamlError := yaml.Marshal(what.value) + if yamlError != nil { + return new(event.Text).Append(fmt.Sprintf("%v", what.value)) + } + + return SomeStringValue(string(yamlText), nil).ValueText() } -func (what AnyValue) Event() *Event { - return what.event +func (what AnyValue) History() event.History { + return what.history } -func (what AnyValue) PlainValue() any { - switch castValue := what.value.(type) { - case Value: - return castValue.PlainValue() +func (what AnyValue) Text() event.Text { + if len(what.path) > 0 { + return new(event.Text).Append(what.path.String()) } - return what.value + return what.ValueText() } -func (what AnyValue) Text() []string { - switch castValue := what.value.(type) { - case []any: - text := make([]string, 0) - for _, item := range castValue { - text = append(text, fmt.Sprintf(" - %v", item)) - } +func (what AnyValue) Description() event.Text { + if len(what.path) == 0 { + return what.History().Text() + } - return text + text := what.ValueText() + switch len(text) { + case 0: + return new(event.Text).Append(what.path.String() + " is (empty)") - case map[string]any: - text := make([]string, 0) - for name, item := range castValue { - text = append(text, fmt.Sprintf(" %v: %v", name, item)) - } + case 1: + return new(event.Text).Append(fmt.Sprintf("%v is %v", what.path.String(), text[0].Line)) - return text + default: + return new(event.Text).Append(fmt.Sprintf("%v is:", what.path.String()), text...) } - - return []string{fmt.Sprintf("%v", what.PlainValue())} } func NilValue() Value { return &AnyValue{} } -func SomeValue(anyValue any, event *Event) Value { +func SomeValue(anyValue any, stack Stack, events ...event.Event) Value { + return SomeValueWithPath(anyValue, nil, stack, events...) +} + +func SomeValueWithPath(anyValue any, path event.Path, stack Stack, events ...event.Event) Value { switch castValue := anyValue.(type) { case string: - return SomeStringValue(castValue, event) + return SomeStringValueWithPath(castValue, path, stack, events...) case bool: - return SomeBoolValue(castValue, event) + return SomeBoolValueWithPath(castValue, path, stack, events...) case decimal.Decimal: - return SomeDecimalValue(castValue, event) + return SomeDecimalValueWithPath(castValue, path, stack, events...) case int: - return SomeDecimalValue(decimal.NewFromInt(int64(castValue)), event) + return SomeDecimalValueWithPath(decimal.NewFromInt(int64(castValue)), path, stack, events...) case int8: - return SomeDecimalValue(decimal.NewFromInt(int64(castValue)), event) + return SomeDecimalValueWithPath(decimal.NewFromInt(int64(castValue)), path, stack, events...) case int16: - return SomeDecimalValue(decimal.NewFromInt(int64(castValue)), event) + return SomeDecimalValueWithPath(decimal.NewFromInt(int64(castValue)), path, stack, events...) case int32: - return SomeDecimalValue(decimal.NewFromInt(int64(castValue)), event) + return SomeDecimalValueWithPath(decimal.NewFromInt(int64(castValue)), path, stack, events...) case int64: - return SomeDecimalValue(decimal.NewFromInt(castValue), event) + return SomeDecimalValueWithPath(decimal.NewFromInt(castValue), path, stack, events...) case float32: - return SomeDecimalValue(decimal.NewFromFloat32(castValue), event) + return SomeDecimalValueWithPath(decimal.NewFromFloat32(castValue), path, stack, events...) case float64: - return SomeDecimalValue(decimal.NewFromFloat(castValue), event) + return SomeDecimalValueWithPath(decimal.NewFromFloat(castValue), path, stack, events...) case []Value: - return SomeArrayValue(castValue, event) + return SomeArrayValueWithPath(castValue, path, stack, events...) case []any: array := make([]Value, 0) @@ -105,36 +121,51 @@ func SomeValue(anyValue any, event *Event) Value { array = append(array, castItem) default: - array = append(array, SomeValue(castItem, nil)) + array = append(array, SomeValueWithPath(castItem, path, stack, events...)) } } - return SomeArrayValue(array, event) + return SomeArrayValueWithPath(array, path, stack, events...) - case Value: - return castValue - } + case *AnyValue: + return &AnyValue{ + value: castValue.Value(), + path: path, + history: stack.History(append(castValue.History(), events...)...), + } - return &AnyValue{ - value: anyValue, - event: event, - } -} + case *ArrayValue: + return &ArrayValue{ + value: castValue.ArrayValue(), + path: path, + history: stack.History(append(castValue.History(), events...)...), + } -func AddValueHistory(anyValue Value, history []*Event) Value { - if anyValue == nil { - if len(history) == 0 { - return nil + case *BoolValue: + return &BoolValue{ + value: castValue.BoolValue(), + path: path, + history: stack.History(append(castValue.History(), events...)...), } - return SomeValue(nil, NewEvent(NewValueProperty(nil), EmptyPath()).AddHistory(history)) - } + case *DecimalValue: + return &DecimalValue{ + value: castValue.DecimalValue(), + path: path, + history: stack.History(append(castValue.History(), events...)...), + } - event := anyValue.Event() - if event == nil { - path := anyValue.Name() - event = NewEvent(NewValueProperty(anyValue), &path).AddHistory(history) + case *StringValue: + return &StringValue{ + value: castValue.StringValue(), + path: path, + history: stack.History(append(castValue.History(), events...)...), + } } - return SomeValue(anyValue.PlainValue(), event) + return &AnyValue{ + value: anyValue, + path: path, + history: stack.History(events...), + } } diff --git a/pkg/risks/script/common/array-value.go b/pkg/risks/script/common/array-value.go index 36ccc05d..43800410 100644 --- a/pkg/risks/script/common/array-value.go +++ b/pkg/risks/script/common/array-value.go @@ -2,32 +2,14 @@ package common import ( "fmt" + + "github.com/threagile/threagile/pkg/risks/script/event" ) type ArrayValue struct { - value []Value - name Path - event *Event -} - -func (what ArrayValue) Value() any { - return what.value -} - -func (what ArrayValue) Name() Path { - return what.name -} - -func (what ArrayValue) SetName(name ...string) { - what.name.SetPath(name...) -} - -func (what ArrayValue) Event() *Event { - return what.event -} - -func (what ArrayValue) ArrayValue() []Value { - return what.value + value []Value + path event.Path + history event.History } func (what ArrayValue) PlainValue() any { @@ -39,36 +21,78 @@ func (what ArrayValue) PlainValue() any { return values } -func (what ArrayValue) Text() []string { - text := make([]string, 0) +func (what ArrayValue) Value() any { + return what.value +} + +func (what ArrayValue) Path() event.Path { + return what.path +} + +func (what ArrayValue) ValueText() event.Text { + if len(what.value) == 0 { + return new(event.Text).Append("(empty)") + } + + text := make(event.Text, 0) for _, item := range what.value { itemText := item.Text() switch len(itemText) { case 0: + text = new(event.Text).Append(" - (empty)") case 1: - text = append(text, " - "+itemText[0]) + text = text.Append(" - " + itemText[0].Line) default: - text = append(text, " - ") - - for _, line := range itemText { - text = append(text, " "+line) - } + text = new(event.Text).Append(" - :", itemText...) } } return text } +func (what ArrayValue) History() event.History { + return what.history +} + +func (what ArrayValue) Text() event.Text { + if len(what.path) > 0 { + return new(event.Text).Append(what.path.String()) + } + + return what.ValueText() +} + +func (what ArrayValue) Description() event.Text { + if len(what.path) == 0 { + return what.History().Text() + } + + if len(what.value) == 0 { + return new(event.Text).Append(what.path.String() + " is (empty)") + } + + return append(new(event.Text).Append(what.path.String()+" is:"), what.ValueText()...) +} + +func (what ArrayValue) ArrayValue() []Value { + return what.value +} + func EmptyArrayValue() *ArrayValue { return &ArrayValue{} } -func SomeArrayValue(value []Value, event *Event) *ArrayValue { +func SomeArrayValue(value []Value, stack Stack, events ...event.Event) *ArrayValue { + return SomeArrayValueWithPath(value, nil, stack, events...) +} + +func SomeArrayValueWithPath(value []Value, path event.Path, stack Stack, events ...event.Event) *ArrayValue { return &ArrayValue{ - value: value, - event: event, + value: value, + path: path, + history: stack.History(events...), } } @@ -89,8 +113,8 @@ func ToArrayValue(value Value) (*ArrayValue, error) { } return &ArrayValue{ - value: arrayValue, - name: value.Name(), - event: value.Event(), + value: arrayValue, + path: value.Path(), + history: value.History(), }, nil } diff --git a/pkg/risks/script/common/bool-value.go b/pkg/risks/script/common/bool-value.go index c7637c46..a0be568e 100644 --- a/pkg/risks/script/common/bool-value.go +++ b/pkg/risks/script/common/bool-value.go @@ -2,36 +2,50 @@ package common import ( "fmt" + + "github.com/threagile/threagile/pkg/risks/script/event" ) type BoolValue struct { - value bool - name Path - event *Event + value bool + path event.Path + history event.History +} + +func (what BoolValue) PlainValue() any { + return what.value } func (what BoolValue) Value() any { return what.value } -func (what BoolValue) Name() Path { - return what.name +func (what BoolValue) Path() event.Path { + return what.path } -func (what BoolValue) SetName(name ...string) { - what.name.SetPath(name...) +func (what BoolValue) ValueText() event.Text { + return new(event.Text).Append(fmt.Sprintf("%v", what.value)) } -func (what BoolValue) Event() *Event { - return what.event +func (what BoolValue) History() event.History { + return what.history } -func (what BoolValue) PlainValue() any { - return what.value +func (what BoolValue) Text() event.Text { + if len(what.path) > 0 { + return new(event.Text).Append(what.path.String()) + } + + return what.ValueText() } -func (what BoolValue) Text() []string { - return []string{fmt.Sprintf("%v", what.value)} +func (what BoolValue) Description() event.Text { + if len(what.path) == 0 { + return what.History().Text() + } + + return new(event.Text).Append(fmt.Sprintf("%v is %v", what.path.String(), what.value)) } func (what BoolValue) BoolValue() bool { @@ -42,10 +56,15 @@ func EmptyBoolValue() *BoolValue { return &BoolValue{} } -func SomeBoolValue(value bool, event *Event) *BoolValue { +func SomeBoolValue(value bool, stack Stack, events ...event.Event) *BoolValue { + return SomeBoolValueWithPath(value, nil, stack, events...) +} + +func SomeBoolValueWithPath(value bool, path event.Path, stack Stack, events ...event.Event) *BoolValue { return &BoolValue{ - value: value, - event: event, + value: value, + path: path, + history: stack.History(events...), } } @@ -58,8 +77,8 @@ func ToBoolValue(value Value) (*BoolValue, error) { } return &BoolValue{ - value: castValue, - name: value.Name(), - event: value.Event(), + value: castValue, + path: value.Path(), + history: value.History(), }, conversionError } diff --git a/pkg/risks/script/common/built-in.go b/pkg/risks/script/common/built-in.go index 2b053660..14cb36de 100644 --- a/pkg/risks/script/common/built-in.go +++ b/pkg/risks/script/common/built-in.go @@ -2,7 +2,9 @@ package common import ( "fmt" + "github.com/shopspring/decimal" + "github.com/threagile/threagile/pkg/risks/script/event" "github.com/threagile/threagile/pkg/types" ) @@ -16,38 +18,38 @@ var ( } ) -type builtInFunc func(parameters []Value) (Value, error) +type builtInFunc func(parameters []Value, stack Stack, events ...event.Event) (Value, error) func IsBuiltIn(builtInName string) bool { _, ok := callers[builtInName] return ok } -func CallBuiltIn(builtInName string, parameters ...Value) (Value, error) { +func CallBuiltIn(builtInName string, stack Stack, events []event.Event, parameters ...Value) (Value, error) { caller, ok := callers[builtInName] if !ok { return nil, fmt.Errorf("unknown built-in %v", builtInName) } - return caller(parameters) + return caller(parameters, stack, events...) } -func calculateSeverityFunc(parameters []Value) (Value, error) { +func calculateSeverityFunc(parameters []Value, stack Stack, events ...event.Event) (Value, error) { if len(parameters) != 2 { return nil, fmt.Errorf("failed to calculate severity: expected 2 parameters, got %d", len(parameters)) } - likelihoodValue, likelihoodError := toLikelihood(parameters[0]) + likelihoodValue, likelihoodError := toLikelihood(parameters[0], stack, events...) if likelihoodError != nil { return nil, fmt.Errorf("failed to calculate severity: %w", likelihoodError) } likelihoodDecimal := likelihoodValue.Value().(decimal.Decimal).IntPart() - impactValue, impactError := toImpact(parameters[1]) + impactValue, impactError := toImpact(parameters[1], stack, events...) if impactError != nil { return nil, fmt.Errorf("failed to calculate severity: %w", impactError) } impactDecimal := impactValue.Value().(decimal.Decimal).IntPart() - return SomeStringValue(types.CalculateSeverity(types.RiskExploitationLikelihood(likelihoodDecimal), types.RiskExploitationImpact(impactDecimal)).String(), nil), nil + return SomeStringValue(types.CalculateSeverity(types.RiskExploitationLikelihood(likelihoodDecimal), types.RiskExploitationImpact(impactDecimal)).String(), stack), nil } diff --git a/pkg/risks/script/common/cast.go b/pkg/risks/script/common/cast.go index 6291b0fb..48ab61d6 100644 --- a/pkg/risks/script/common/cast.go +++ b/pkg/risks/script/common/cast.go @@ -2,7 +2,9 @@ package common import ( "fmt" + "github.com/shopspring/decimal" + "github.com/threagile/threagile/pkg/risks/script/event" "github.com/threagile/threagile/pkg/types" ) @@ -38,9 +40,9 @@ var ( } ) -type castFunc func(value Value) (Value, error) +type castFunc func(value Value, stack Stack, events ...event.Event) (Value, error) -func CastValue(value Value, castType string) (Value, error) { +func CastValue(value Value, stack Stack, castType string) (Value, error) { if value == nil { return NilValue(), nil } @@ -50,10 +52,10 @@ func CastValue(value Value, castType string) (Value, error) { return nil, fmt.Errorf("unknown cast type %v", castType) } - return caster(value) + return caster(value, stack, value.History()...) } -func toConfidentiality(value Value) (Value, error) { +func toConfidentiality(value Value, stack Stack, events ...event.Event) (Value, error) { switch castValue := value.Value().(type) { case string: converted, conversionError := types.Confidentiality(0).Find(castValue) @@ -61,23 +63,23 @@ func toConfidentiality(value Value) (Value, error) { return nil, conversionError } - return SomeDecimalValue(decimal.NewFromInt(int64(converted)), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(int64(converted)), stack, events...), nil case int: - return SomeDecimalValue(decimal.NewFromInt(int64(castValue)), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(int64(castValue)), stack, events...), nil case int64: - return SomeDecimalValue(decimal.NewFromInt(castValue), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(castValue), stack, events...), nil case Value: - return toConfidentiality(castValue) + return toConfidentiality(castValue, stack, events...) default: return nil, fmt.Errorf("toConfidentiality: unexpected type %T", value) } } -func toCriticality(value Value) (Value, error) { +func toCriticality(value Value, stack Stack, events ...event.Event) (Value, error) { switch castValue := value.Value().(type) { case string: converted, conversionError := types.Criticality(0).Find(castValue) @@ -85,23 +87,23 @@ func toCriticality(value Value) (Value, error) { return nil, conversionError } - return SomeDecimalValue(decimal.NewFromInt(int64(converted)), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(int64(converted)), stack, events...), nil case int: - return SomeDecimalValue(decimal.NewFromInt(int64(castValue)), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(int64(castValue)), stack, events...), nil case int64: - return SomeDecimalValue(decimal.NewFromInt(castValue), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(castValue), stack, events...), nil case Value: - return toCriticality(castValue) + return toCriticality(castValue, stack, events...) default: return nil, fmt.Errorf("toConfidentiality: unexpected type %T", value) } } -func toAuthentication(value Value) (Value, error) { +func toAuthentication(value Value, stack Stack, events ...event.Event) (Value, error) { switch castValue := value.Value().(type) { case string: converted, conversionError := types.Authentication(0).Find(castValue) @@ -109,23 +111,23 @@ func toAuthentication(value Value) (Value, error) { return nil, conversionError } - return SomeDecimalValue(decimal.NewFromInt(int64(converted)), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(int64(converted)), stack, events...), nil case int: - return SomeDecimalValue(decimal.NewFromInt(int64(castValue)), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(int64(castValue)), stack, events...), nil case int64: - return SomeDecimalValue(decimal.NewFromInt(castValue), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(castValue), stack, events...), nil case Value: - return toAuthentication(castValue) + return toAuthentication(castValue, stack, events...) default: return nil, fmt.Errorf("toConfidentiality: unexpected type %T", value) } } -func toAuthorization(value Value) (Value, error) { +func toAuthorization(value Value, stack Stack, events ...event.Event) (Value, error) { switch castValue := value.Value().(type) { case string: converted, conversionError := types.Authorization(0).Find(castValue) @@ -133,23 +135,23 @@ func toAuthorization(value Value) (Value, error) { return nil, conversionError } - return SomeDecimalValue(decimal.NewFromInt(int64(converted)), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(int64(converted)), stack, events...), nil case int: - return SomeDecimalValue(decimal.NewFromInt(int64(castValue)), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(int64(castValue)), stack, events...), nil case int64: - return SomeDecimalValue(decimal.NewFromInt(castValue), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(castValue), stack, events...), nil case Value: - return toAuthorization(castValue) + return toAuthorization(castValue, stack, events...) default: return nil, fmt.Errorf("toConfidentiality: unexpected type %T", value) } } -func toProbability(value Value) (Value, error) { +func toProbability(value Value, stack Stack, events ...event.Event) (Value, error) { switch castValue := value.Value().(type) { case string: converted, conversionError := types.DataBreachProbability(0).Find(castValue) @@ -157,23 +159,23 @@ func toProbability(value Value) (Value, error) { return nil, conversionError } - return SomeDecimalValue(decimal.NewFromInt(int64(converted)), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(int64(converted)), stack, events...), nil case int: - return SomeDecimalValue(decimal.NewFromInt(int64(castValue)), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(int64(castValue)), stack, events...), nil case int64: - return SomeDecimalValue(decimal.NewFromInt(castValue), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(castValue), stack, events...), nil case Value: - return toProbability(castValue) + return toProbability(castValue, stack, events...) default: return nil, fmt.Errorf("toConfidentiality: unexpected type %T", value) } } -func toEncryption(value Value) (Value, error) { +func toEncryption(value Value, stack Stack, events ...event.Event) (Value, error) { switch castValue := value.Value().(type) { case string: converted, conversionError := types.EncryptionStyle(0).Find(castValue) @@ -181,23 +183,23 @@ func toEncryption(value Value) (Value, error) { return nil, conversionError } - return SomeDecimalValue(decimal.NewFromInt(int64(converted)), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(int64(converted)), stack, events...), nil case int: - return SomeDecimalValue(decimal.NewFromInt(int64(castValue)), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(int64(castValue)), stack, events...), nil case int64: - return SomeDecimalValue(decimal.NewFromInt(castValue), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(castValue), stack, events...), nil case Value: - return toEncryption(castValue) + return toEncryption(castValue, stack, events...) default: return nil, fmt.Errorf("toConfidentiality: unexpected type %T", value) } } -func toQuantity(value Value) (Value, error) { +func toQuantity(value Value, stack Stack, events ...event.Event) (Value, error) { switch castValue := value.Value().(type) { case string: converted, conversionError := types.Quantity(0).Find(castValue) @@ -205,23 +207,23 @@ func toQuantity(value Value) (Value, error) { return nil, conversionError } - return SomeDecimalValue(decimal.NewFromInt(int64(converted)), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(int64(converted)), stack, events...), nil case int: - return SomeDecimalValue(decimal.NewFromInt(int64(castValue)), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(int64(castValue)), stack, events...), nil case int64: - return SomeDecimalValue(decimal.NewFromInt(castValue), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(castValue), stack, events...), nil case Value: - return toQuantity(castValue) + return toQuantity(castValue, stack, events...) default: return nil, fmt.Errorf("toConfidentiality: unexpected type %T", value) } } -func toImpact(value Value) (Value, error) { +func toImpact(value Value, stack Stack, events ...event.Event) (Value, error) { switch castValue := value.Value().(type) { case string: converted, conversionError := types.RiskExploitationImpact(0).Find(castValue) @@ -229,23 +231,23 @@ func toImpact(value Value) (Value, error) { return nil, conversionError } - return SomeDecimalValue(decimal.NewFromInt(int64(converted)), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(int64(converted)), stack, events...), nil case int: - return SomeDecimalValue(decimal.NewFromInt(int64(castValue)), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(int64(castValue)), stack, events...), nil case int64: - return SomeDecimalValue(decimal.NewFromInt(castValue), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(castValue), stack, events...), nil case Value: - return toImpact(castValue) + return toImpact(castValue, stack, events...) default: return nil, fmt.Errorf("toConfidentiality: unexpected type %T", value) } } -func toLikelihood(value Value) (Value, error) { +func toLikelihood(value Value, stack Stack, events ...event.Event) (Value, error) { switch castValue := value.Value().(type) { case string: converted, conversionError := types.RiskExploitationLikelihood(0).Find(castValue) @@ -253,23 +255,23 @@ func toLikelihood(value Value) (Value, error) { return nil, conversionError } - return SomeDecimalValue(decimal.NewFromInt(int64(converted)), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(int64(converted)), stack, events...), nil case int: - return SomeDecimalValue(decimal.NewFromInt(int64(castValue)), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(int64(castValue)), stack, events...), nil case int64: - return SomeDecimalValue(decimal.NewFromInt(castValue), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(castValue), stack, events...), nil case Value: - return toLikelihood(castValue) + return toLikelihood(castValue, stack, events...) default: return nil, fmt.Errorf("toConfidentiality: unexpected type %T", value) } } -func toSize(value Value) (Value, error) { +func toSize(value Value, stack Stack, events ...event.Event) (Value, error) { switch castValue := value.Value().(type) { case string: converted, conversionError := types.TechnicalAssetSize(0).Find(castValue) @@ -277,16 +279,16 @@ func toSize(value Value) (Value, error) { return nil, conversionError } - return SomeDecimalValue(decimal.NewFromInt(int64(converted)), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(int64(converted)), stack, events...), nil case int: - return SomeDecimalValue(decimal.NewFromInt(int64(castValue)), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(int64(castValue)), stack, events...), nil case int64: - return SomeDecimalValue(decimal.NewFromInt(castValue), value.Event()), nil + return SomeDecimalValue(decimal.NewFromInt(castValue), stack, events...), nil case Value: - return toSize(castValue) + return toSize(castValue, stack, events...) default: return nil, fmt.Errorf("toConfidentiality: unexpected type %T", value) diff --git a/pkg/risks/script/common/compare.go b/pkg/risks/script/common/compare.go index 35ccae55..caae8edf 100644 --- a/pkg/risks/script/common/compare.go +++ b/pkg/risks/script/common/compare.go @@ -2,203 +2,181 @@ package common import ( "fmt" - "github.com/threagile/threagile/pkg/risks/script/property" + + "github.com/threagile/threagile/pkg/risks/script/event" ) -func Compare(first Value, second Value, as string) (*Event, error) { - firstValue := first - secondValue := second +func Compare(firstValue Value, secondValue Value, as string, stack Stack) (event.Event, error) { + castFirstValue := firstValue + castSecondValue := secondValue if len(as) > 0 { var castError error - firstValue, castError = CastValue(firstValue, as) + castFirstValue, castError = CastValue(firstValue, stack, as) if castError != nil { return nil, fmt.Errorf("failed to cast value to %q: %w", as, castError) } - secondValue, castError = CastValue(secondValue, as) + castSecondValue, castError = CastValue(secondValue, stack, as) if castError != nil { return nil, fmt.Errorf("failed to cast value to %q: %w", as, castError) } } - return compare(firstValue, secondValue) + return compare(castFirstValue, firstValue, castSecondValue, secondValue) } -func compare(firstValue Value, secondValue Value) (*Event, error) { - switch first := firstValue.(type) { +func compare(castFirstValue Value, origFirstValue Value, castSecondValue Value, origSecondValue Value) (event.Event, error) { + switch first := castFirstValue.(type) { case *ArrayValue: - second, conversionError := ToArrayValue(secondValue) + second, conversionError := ToArrayValue(castSecondValue) if conversionError != nil { return nil, conversionError } - return compareArrays(first, second) + return compareArrays(first, origFirstValue, second, origSecondValue) case *BoolValue: - second, conversionError := ToBoolValue(secondValue) + second, conversionError := ToBoolValue(castSecondValue) if conversionError != nil { return nil, conversionError } if first.BoolValue() == second.BoolValue() { - return NewEventFrom(NewEqualProperty(second), first, second), nil + return event.NewEqual(origFirstValue, origSecondValue), nil } - return NewEventFrom(NewNotEqualProperty(second), first, second), nil + return event.NewNotEqual(origFirstValue, origSecondValue), nil case *DecimalValue: - second, conversionError := ToDecimalValue(secondValue) + second, conversionError := ToDecimalValue(castSecondValue) if conversionError != nil { return nil, conversionError } value := first.DecimalValue().Cmp(second.DecimalValue()) - prop := NewEqualProperty(secondValue) if value < 0 { - prop = NewLessProperty(secondValue) + return event.NewLess(origFirstValue, origSecondValue), nil } else if value > 0 { - prop = NewGreaterProperty(secondValue) + return event.NewGreater(origFirstValue, origSecondValue), nil } - return NewEventFrom(prop, first, second), nil + return event.NewEqual(origFirstValue, origSecondValue), nil case *StringValue: - second, conversionError := ToStringValue(secondValue) + second, conversionError := ToStringValue(castSecondValue) if conversionError != nil { return nil, conversionError } if first.StringValue() == second.StringValue() { - return NewEventFrom(NewEqualProperty(second), first, second), nil + return event.NewEqual(origFirstValue, origSecondValue), nil } - return NewEventFrom(NewNotEqualProperty(second), first, second), nil + return event.NewNotEqual(origFirstValue, origSecondValue), nil case *AnyValue: - if firstValue.Value() == nil { - return compare(nil, secondValue) + if castFirstValue.Value() == nil { + return compare(nil, origFirstValue, castSecondValue, origSecondValue) } case nil: - switch second := secondValue.(type) { + switch second := castSecondValue.(type) { case *ArrayValue: if len(second.ArrayValue()) == 0 { - return NewEventFrom(NewEqualProperty(second), first, second), nil + return event.NewEqual(origFirstValue, origSecondValue), nil } - return NewEventFrom(NewNotEqualProperty(second), first, second), nil + return event.NewNotEqual(origFirstValue, origSecondValue), nil case *BoolValue: if !second.BoolValue() { - return NewEventFrom(NewEqualProperty(second), first, second), nil + return event.NewEqual(origFirstValue, origSecondValue), nil } - return NewEventFrom(NewNotEqualProperty(second), first, second), nil + return event.NewNotEqual(origFirstValue, origSecondValue), nil case *DecimalValue: if second.DecimalValue().IsZero() { - return NewEventFrom(NewEqualProperty(second), first, second), nil + return event.NewEqual(origFirstValue, origSecondValue), nil } - return NewEventFrom(NewNotEqualProperty(second), first, second), nil + return event.NewNotEqual(origFirstValue, origSecondValue), nil case *StringValue: if len(second.StringValue()) == 0 { - return NewEventFrom(NewEqualProperty(second), first, second), nil + return event.NewEqual(origFirstValue, origSecondValue), nil } - return NewEventFrom(NewNotEqualProperty(second), first, second), nil + return event.NewNotEqual(origFirstValue, origSecondValue), nil case *AnyValue: if second.Value() == nil { - return NewEvent(NewEqualProperty(nil), nil), nil + return event.NewEqual(origFirstValue, origSecondValue), nil } case nil: - return NewEvent(NewEqualProperty(nil), nil), nil + return event.NewEqual(origFirstValue, origSecondValue), nil } } - return nil, fmt.Errorf("can't compare %T to %T", firstValue, secondValue) + return nil, fmt.Errorf("can't compare %T to %T", origFirstValue, origSecondValue) } -func compareArrays(firstValue *ArrayValue, secondValue *ArrayValue) (*Event, error) { +func compareArrays(firstValue *ArrayValue, origFirstValue Value, secondValue *ArrayValue, origSecondValue Value) (event.Event, error) { if len(firstValue.ArrayValue()) != len(secondValue.ArrayValue()) { - return NewEventFrom(NewNotEqualProperty(firstValue), firstValue, secondValue), nil + return event.NewNotEqual(origFirstValue, origSecondValue), nil } for index, first := range firstValue.ArrayValue() { second := secondValue.ArrayValue()[index] - event, compareError := compare(first, second) + result, compareError := compare(first, first, second, second) if compareError != nil { - return event, compareError + return result, compareError } - if !IsSame(event.Property) { - return NewEventFrom(NewNotEqualProperty(firstValue), firstValue, secondValue), nil + if !IsSame(result) { + return event.NewNotEqual(origFirstValue, origSecondValue), nil } } - return NewEventFrom(NewEqualProperty(firstValue), firstValue, secondValue), nil + return event.NewEqual(origFirstValue, origSecondValue), nil } -func IsSame(value *Property) bool { - if value == nil { +func IsSame(e event.Event) bool { + if e == nil { return false } - switch value.Property.(type) { - case *property.Equal: + switch e.(type) { + case *event.Equal: return true } return false } -func IsGreater(value *Property) bool { - if value == nil { +func IsGreater(e event.Event) bool { + if e == nil { return false } - switch value.Property.(type) { - case *property.Greater: + switch e.(type) { + case *event.Greater: return true } return false } -func IsLess(value *Property) bool { - if value == nil { +func IsLess(e event.Event) bool { + if e == nil { return false } - switch value.Property.(type) { - case *property.Less: + switch e.(type) { + case *event.Less: return true } return false } - -func ToString(value any) (*StringValue, error) { - switch castValue := value.(type) { - case string: - return SomeStringValue(castValue, nil), nil - - case *StringValue: - return castValue, nil - - case *AnyValue: - aString, valueError := ToString(castValue.Value()) - stringValue := SomeStringValue(aString.StringValue(), castValue.Event()) - if valueError != nil { - return stringValue, valueError - } - - return stringValue, nil - - default: - return EmptyStringValue(), fmt.Errorf("expected string, got %T", value) - } -} diff --git a/pkg/risks/script/common/decimal-value.go b/pkg/risks/script/common/decimal-value.go index b04ed52c..93392ebe 100644 --- a/pkg/risks/script/common/decimal-value.go +++ b/pkg/risks/script/common/decimal-value.go @@ -2,37 +2,51 @@ package common import ( "fmt" + "github.com/shopspring/decimal" + "github.com/threagile/threagile/pkg/risks/script/event" ) type DecimalValue struct { - value decimal.Decimal - name Path - event *Event + value decimal.Decimal + path event.Path + history event.History +} + +func (what DecimalValue) PlainValue() any { + return what.value } func (what DecimalValue) Value() any { return what.value } -func (what DecimalValue) Name() Path { - return what.name +func (what DecimalValue) Path() event.Path { + return what.path } -func (what DecimalValue) SetName(name ...string) { - what.name.SetPath(name...) +func (what DecimalValue) ValueText() event.Text { + return new(event.Text).Append(what.value.String()) } -func (what DecimalValue) Event() *Event { - return what.event +func (what DecimalValue) History() event.History { + return what.history } -func (what DecimalValue) PlainValue() any { - return what.value +func (what DecimalValue) Text() event.Text { + if len(what.path) > 0 { + return new(event.Text).Append(what.path.String()) + } + + return what.ValueText() } -func (what DecimalValue) Text() []string { - return []string{what.value.String()} +func (what DecimalValue) Description() event.Text { + if len(what.path) == 0 { + return what.History().Text() + } + + return new(event.Text).Append(fmt.Sprintf("%v is %v", what.path.String(), what.value.String())) } func (what DecimalValue) DecimalValue() decimal.Decimal { @@ -45,10 +59,15 @@ func EmptyDecimalValue() *DecimalValue { } } -func SomeDecimalValue(value decimal.Decimal, event *Event) *DecimalValue { +func SomeDecimalValue(value decimal.Decimal, stack Stack, events ...event.Event) *DecimalValue { + return SomeDecimalValueWithPath(value, nil, stack, events...) +} + +func SomeDecimalValueWithPath(value decimal.Decimal, path event.Path, stack Stack, events ...event.Event) *DecimalValue { return &DecimalValue{ - value: value, - event: event, + value: value, + path: path, + history: stack.History(events...), } } @@ -61,8 +80,8 @@ func ToDecimalValue(value Value) (*DecimalValue, error) { } return &DecimalValue{ - value: castValue, - name: value.Name(), - event: value.Event(), + value: castValue, + path: value.Path(), + history: value.History(), }, conversionError } diff --git a/pkg/risks/script/common/event.go b/pkg/risks/script/common/event.go deleted file mode 100644 index 0ab04bee..00000000 --- a/pkg/risks/script/common/event.go +++ /dev/null @@ -1,153 +0,0 @@ -package common - -import ( - "strings" -) - -type Event struct { - Origin *Path - Property *Property - Events []*Event -} - -func NewEvent(property *Property, path *Path) *Event { - return &Event{ - Property: property, - Origin: path, - } -} - -func NewEventFrom(property *Property, firstValue Value, value ...Value) *Event { - var path *Path - var events []*Event - if firstValue != nil && firstValue.Event() != nil { - path = firstValue.Event().Path().Copy() - events = firstValue.Event().Events - } - - event := &Event{ - Property: property, - Origin: path, - Events: events, - } - - event.From(value...) - - return event -} - -func EmptyEvent() *Event { - return &Event{ - Property: NewBlankProperty(), - } -} - -func (what *Event) From(values ...Value) *Event { - if what == nil { - return what - } - - for _, value := range values { - if value.Event() != nil { - what.Events = append(what.Events, value.Event()) - } - } - return what -} - -func (what *Event) AddHistory(history []*Event) *Event { - if what == nil { - return what - } - - what.Events = append(what.Events, history...) - - return what -} - -func (what *Event) Path() *Path { - if what == nil { - return nil - } - - return what.Origin -} - -func (what *Event) SetPath(path *Path) *Event { - if what == nil { - return what - } - - what.Origin = path - return what -} - -func (what *Event) AddPathParent(path ...string) *Event { - if what == nil { - return what - } - - what.Origin.AddPathParent(path...) - return what -} - -func (what *Event) AddPathLeaf(path ...string) *Event { - if what == nil { - return what - } - - what.Origin.AddPathLeaf(path...) - return what -} - -func (what *Event) String() string { - if what == nil { - return "" - } - - return strings.Join(what.Indented(0), "\n") -} - -func (what *Event) Indented(level int) []string { - if what == nil { - return []string{} - } - - propertyText := what.Property.Text() - - lines := make([]string, 0) - text := what.Origin.String() + " is " - if len(propertyText) <= 1 { - text += strings.Join(propertyText, " ") - - if len(what.Events) > 0 { - text += " because" - } - - if len(text) > 0 { - lines = append(lines, what.indent(level, text)) - } - } else { - for _, line := range propertyText { - lines = append(lines, what.indent(level+1, line)) - } - - if len(what.Events) > 0 { - lines = append(lines, what.indent(level, " because")) - } - } - - for _, event := range what.Events { - lines = append(lines, event.Indented(level+1)...) - } - - return lines -} - -func (what *Event) indent(level int, text string) string { - if what == nil { - return "" - } - - return strings.Repeat(" ", level) + text -} diff --git a/pkg/risks/script/common/history.go b/pkg/risks/script/common/history.go deleted file mode 100644 index a4ef6934..00000000 --- a/pkg/risks/script/common/history.go +++ /dev/null @@ -1,32 +0,0 @@ -package common - -import ( - "strings" -) - -type History []*Event - -func NewHistory(item *Event) History { - return new(History).New(item) -} - -func (what History) New(item *Event) History { - if item != nil { - return append(History{item}, what...) - } - - return what -} - -func (what History) String() string { - return strings.Join(what.Indented(0), "\n") -} - -func (what History) Indented(level int) []string { - lines := make([]string, 0) - for _, item := range what { - lines = append(lines, item.Indented(level)...) - } - - return lines -} diff --git a/pkg/risks/script/common/path.go b/pkg/risks/script/common/path.go deleted file mode 100644 index 742263fe..00000000 --- a/pkg/risks/script/common/path.go +++ /dev/null @@ -1,68 +0,0 @@ -package common - -import ( - "fmt" - "strings" -) - -type Path struct { - Path []string -} - -func NewPath(path ...string) *Path { - return new(Path).SetPath(path...) -} - -func EmptyPath() *Path { - return new(Path) -} - -func (what *Path) Copy() *Path { - if what == nil { - return what - } - - return &Path{ - Path: what.Path[:], - } -} - -func (what *Path) SetPath(path ...string) *Path { - if what == nil { - return what - } - - what.Path = path - return what -} - -func (what *Path) AddPathParent(path ...string) *Path { - if what == nil { - return what - } - - what.Path = append(path, what.Path...) - return what -} - -func (what *Path) AddPathLeaf(path ...string) *Path { - if what == nil { - return what - } - - what.Path = append(what.Path, path...) - return what -} - -func (what *Path) String() string { - if what == nil { - return "" - } - - path := make([]string, 0) - for _, item := range what.Path { - path = append([]string{fmt.Sprintf("%v", item)}, path...) - } - - return strings.Join(path, " of ") -} diff --git a/pkg/risks/script/common/property.go b/pkg/risks/script/common/property.go index f7401251..4d2d3ecd 100644 --- a/pkg/risks/script/common/property.go +++ b/pkg/risks/script/common/property.go @@ -1,93 +1,90 @@ package common -import ( - "fmt" - "github.com/threagile/threagile/pkg/risks/script/property" -) - -type Property struct { - Property property.Item - Path *Path +/* +type Event struct { + Event property.Event + Path Path } -func NewBlankProperty() *Property { - return &Property{ - Property: property.NewBlank(), - } -} - -func NewEqualProperty(value Value) *Property { - var path *Path - if value.Event() != nil { - path = value.Event().Path().Copy() +func NewEqualProperty(value Value) *Event { + var path Path + if value.EventX() != nil { + path = *value.EventX().Path().Copy() } - return &Property{ - Property: property.NewEqual(), + return &Event{ + Event: property.NewEqual(), Path: path, } } -func NewFalseProperty() *Property { - return &Property{ - Property: property.NewFalse(), +func NewFalseProperty() *Event { + return &Event{ + Event: property.NewFalse(), } } -func NewGreaterProperty(value Value) *Property { - var path *Path - if value.Event() != nil { - path = value.Event().Path().Copy() +func NewGreaterProperty(value Value) *Event { + var path Path + if value.EventX() != nil { + path = *value.EventX().Path().Copy() } - return &Property{ - Property: property.NewGreater(), + return &Event{ + Event: property.NewGreater(), Path: path, } } -func NewLessProperty(value Value) *Property { - var path *Path - if value.Event() != nil { - path = value.Event().Path().Copy() +func NewLessProperty(value Value) *Event { + var path Path + if value.EventX() != nil { + path = *value.EventX().Path().Copy() } - return &Property{ - Property: property.NewLess(), + return &Event{ + Event: property.NewLess(), Path: path, } } -func NewNotEqualProperty(value Value) *Property { - var path *Path - if value.Event() != nil { - path = value.Event().Path().Copy() +func NewNotEqualProperty(value Value) *Event { + var path Path + if value.EventX() != nil { + path = *value.EventX().Path().Copy() } - return &Property{ - Property: property.NewNotEqual(), + return &Event{ + Event: property.NewNotEqual(), Path: path, } } -func NewTrueProperty() *Property { - return &Property{ - Property: property.NewTrue(), +func NewTrueProperty() *Event { + return &Event{ + Event: property.NewTrue(), } } -func NewValueProperty(value any) *Property { - return &Property{ - Property: property.NewValue(value), +func NewValueProperty(value any) *Event { + return &Event{ + Event: property.NewValue(value), } } -func (what *Property) Text() []string { +func (what *Event) ValueText() []string { if what == nil { return []string{} } - propertyText := what.Property.Text() + originalPropertyText := what.Event.ValueText() + propertyText := make([]string, 0) + for _, text := range originalPropertyText { + if len(text) > 0 { + propertyText = append(propertyText, text) + } + } + switch len(propertyText) { case 0: // blank return []string{} @@ -104,7 +101,7 @@ func (what *Property) Text() []string { } } -func (what *Property) SetPath(path *Path) *Property { +func (what *Event) SetPath(path Path) *Event { if what == nil { return what } @@ -113,20 +110,21 @@ func (what *Property) SetPath(path *Path) *Property { return what } -func (what *Property) AddPathParent(path ...string) *Property { +func (what *Event) AddPathParent(path ...string) *Event { if what == nil { return what } - what.Path.AddPathParent(path...) + what.Path.AddParent(path...) return what } -func (what *Property) AddPathLeaf(path ...string) *Property { +func (what *Event) AddPathLeaf(path ...string) *Event { if what == nil { return what } - what.Path.AddPathLeaf(path...) + what.Path.AddLeaf(path...) return what } +*/ diff --git a/pkg/risks/script/common/scope.go b/pkg/risks/script/common/scope.go index e929dd0c..b6d70da9 100644 --- a/pkg/risks/script/common/scope.go +++ b/pkg/risks/script/common/scope.go @@ -1,9 +1,11 @@ package common import ( + "strings" + + "github.com/threagile/threagile/pkg/risks/script/event" "github.com/threagile/threagile/pkg/types" "gopkg.in/yaml.v3" - "strings" ) type Scope struct { @@ -16,7 +18,7 @@ type Scope struct { Methods map[string]Statement Deferred []Statement Explain ExplainStatement - CallStack History + CallStack Stack HasReturned bool item Value returnValue Value @@ -81,12 +83,19 @@ func (what *Scope) Defer(statement Statement) { what.Deferred = append(what.Deferred, statement) } -func (what *Scope) PushCall(event *Event) History { - what.CallStack = what.CallStack.New(event) +func (what *Scope) Events() []event.Event { + return what.CallStack.History() +} + +func (what *Scope) Stack() Stack { return what.CallStack } -func (what *Scope) PopCall() { +func (what *Scope) PushHistory(event ...event.Event) { + what.CallStack = append(what.CallStack, event) +} + +func (what *Scope) PopHistory() { if len(what.CallStack) > 0 { what.CallStack = what.CallStack[:len(what.CallStack)-1] } @@ -106,39 +115,40 @@ func (what *Scope) Get(name string) (Value, bool) { switch strings.ToLower(path[0]) { // value name starts with `$model`: refers to `what.Model` case "$model": - value, ok := what.get(path[1:], what.Model, NewPath("threat model", strings.Join(path[1:], "."))) + value, ok := what.get(path[1:], what.Model, event.Path{"threat model", "'" + strings.Join(path[1:], ".") + "'"}, nil) if ok { return value, true } // value name starts with `$risk`: refers to `what.Risk` case "$risk": - value, ok := what.get(path[1:], what.Risk, NewPath("risk category", strings.Join(path[1:], "."))) + value, ok := what.get(path[1:], what.Risk, event.Path{"risk category", "'" + strings.Join(path[1:], ".") + "'"}, nil) if ok { return value, true } } + + return nil, false } // value name starts with a dot: refers to `what.item` if len(path[0]) == 0 { - if len(path[1:]) > 0 { - if what.item == nil { - return nil, false - } - - switch castValue := what.item.Value().(type) { - case map[string]any: - value, ok := what.get(path[1:], castValue, what.item.Event().Path().Copy().AddPathLeaf(strings.Join(path[1:], "."))) - if ok { - return value, true - } - } + if len(path[1:]) == 0 { + return what.item, true + } + if what.item == nil { return nil, false } - return what.item, true + switch castValue := what.item.Value().(type) { + case map[string]any: + return what.get(path[1:], castValue, append(what.item.Path(), "'"+strings.Join(path[1:], ".")+"'"), Stack{what.item.History()}) + + default: + } + + return SomeValueWithPath(nil, append(what.item.Path(), "'"+strings.Join(path[1:], ".")+"'"), Stack{what.item.History()}), false } // value name starts with something else: refers to `what.Vars` @@ -157,45 +167,13 @@ func (what *Scope) Get(name string) (Value, bool) { value, isMap := variable.Value().(map[string]any) if isMap { - return what.get(path[1:], value, variable.Event().Path().Copy().AddPathLeaf(strings.Join(path[1:], "."))) + return what.get(path[1:], value, append(variable.Path(), "'"+strings.Join(path[1:], ".")+"'"), Stack{variable.History()}) } // value name does not resolve return nil, false } -func (what *Scope) GetHistory() []*Event { - history := make([]*Event, 0) - for _, event := range what.CallStack { - newHistory := what.getHistory(event) - if newHistory != nil { - history = append(history, newHistory...) - } - } - - return history -} - -func (what *Scope) getHistory(event *Event) []*Event { - history := make([]*Event, 0) - if event == nil { - return history - } - - if event.Origin != nil && len(event.Origin.Path) > 0 { - return append(history, event) - } - - for _, subEvent := range event.Events { - newHistory := what.getHistory(subEvent) - if newHistory != nil { - history = append(history, newHistory...) - } - } - - return history -} - func (what *Scope) GetItem() Value { return what.item } @@ -219,7 +197,7 @@ func (what *Scope) GetReturnValue() Value { return what.returnValue } -func (what *Scope) get(path []string, item map[string]any, valuePath *Path) (Value, bool) { +func (what *Scope) get(path []string, item map[string]any, valuePath event.Path, stack Stack) (Value, bool) { if len(path) == 0 { return nil, false } @@ -230,7 +208,7 @@ func (what *Scope) get(path []string, item map[string]any, valuePath *Path) (Val field, ok := item[strings.ToLower(path[0])] if !ok { - return SomeValue(nil, NewEvent(NewValueProperty(nil), valuePath)), false + return SomeValueWithPath(nil, valuePath, stack), false } if len(path) == 1 { @@ -239,13 +217,13 @@ func (what *Scope) get(path []string, item map[string]any, valuePath *Path) (Val return castField, true default: - return SomeValue(castField, NewEvent(NewValueProperty(castField), valuePath)), true + return SomeValueWithPath(castField, valuePath, stack), true } } value, isMap := field.(map[string]any) if isMap { - return what.get(path[1:], value, valuePath) + return what.get(path[1:], value, valuePath, stack) } return nil, false diff --git a/pkg/risks/script/common/stack.go b/pkg/risks/script/common/stack.go new file mode 100644 index 00000000..281e3414 --- /dev/null +++ b/pkg/risks/script/common/stack.go @@ -0,0 +1,16 @@ +package common + +import ( + "github.com/threagile/threagile/pkg/risks/script/event" +) + +type Stack []event.History + +func (what Stack) History(events ...event.Event) event.History { + history := events + for _, frame := range what { + history = append(history, frame...) + } + + return history +} diff --git a/pkg/risks/script/common/string-value.go b/pkg/risks/script/common/string-value.go index 1c820c0b..9cdc504d 100644 --- a/pkg/risks/script/common/string-value.go +++ b/pkg/risks/script/common/string-value.go @@ -2,36 +2,64 @@ package common import ( "fmt" + + "github.com/threagile/threagile/pkg/risks/script/event" ) type StringValue struct { - value string - name Path - event *Event + value string + path event.Path + history event.History +} + +func (what StringValue) PlainValue() any { + return what.value } func (what StringValue) Value() any { return what.value } -func (what StringValue) Name() Path { - return what.name +func (what StringValue) Path() event.Path { + return what.path } -func (what StringValue) SetName(name ...string) { - what.name.SetPath(name...) +func (what StringValue) ValueText() event.Text { + text := make(event.Text, 0) + for _, item := range event.GetLines(what.value) { + text = text.Append(item) + } + + return text } -func (what StringValue) Event() *Event { - return what.event +func (what StringValue) History() event.History { + return what.history } -func (what StringValue) PlainValue() any { - return what.value +func (what StringValue) Text() event.Text { + if len(what.path) > 0 { + return new(event.Text).Append(what.path.String()) + } + + return what.ValueText() } -func (what StringValue) Text() []string { - return []string{what.value} +func (what StringValue) Description() event.Text { + if len(what.path) == 0 { + return what.History().Text() + } + + if len(what.value) == 0 { + return new(event.Text).Append(what.path.String() + " is (empty)") + } + + lines := event.GetLines(what.value) + if len(lines) == 1 { + return new(event.Text).Append(fmt.Sprintf("%v is %q", what.path.String(), lines[0])) + } + + return append(new(event.Text).Append(what.path.String()+" is:"), what.ValueText()...) } func (what StringValue) StringValue() string { @@ -42,10 +70,15 @@ func EmptyStringValue() *StringValue { return &StringValue{} } -func SomeStringValue(value string, event *Event) *StringValue { +func SomeStringValue(value string, stack Stack, events ...event.Event) *StringValue { + return SomeStringValueWithPath(value, nil, stack, events...) +} + +func SomeStringValueWithPath(value string, path event.Path, stack Stack, events ...event.Event) *StringValue { return &StringValue{ - value: value, - event: event, + value: value, + path: path, + history: stack.History(events...), } } @@ -58,8 +91,8 @@ func ToStringValue(value Value) (*StringValue, error) { } return &StringValue{ - value: castValue, - name: value.Name(), - event: value.Event(), + value: castValue, + path: value.Path(), + history: value.History(), }, conversionError } diff --git a/pkg/risks/script/common/value.go b/pkg/risks/script/common/value.go index 9cf317c2..3f2bc57a 100644 --- a/pkg/risks/script/common/value.go +++ b/pkg/risks/script/common/value.go @@ -1,10 +1,11 @@ package common +import ( + "github.com/threagile/threagile/pkg/risks/script/event" +) + type Value interface { + event.Value PlainValue() any Value() any - Name() Path - SetName(name ...string) - Event() *Event - Text() []string } diff --git a/pkg/risks/script/common/values.go b/pkg/risks/script/common/values.go index d17fed05..5971c894 100644 --- a/pkg/risks/script/common/values.go +++ b/pkg/risks/script/common/values.go @@ -9,19 +9,19 @@ func (what Values) Copy() (Values, error) { for name, value := range what { switch castValue := value.(type) { case *AnyValue: - values[name] = SomeValue(castValue.Value(), castValue.Event()) + values[name] = SomeValueWithPath(castValue.Value(), castValue.Path(), nil, castValue.History()...) case *ArrayValue: - values[name] = SomeArrayValue(castValue.ArrayValue(), castValue.Event()) + values[name] = SomeArrayValueWithPath(castValue.ArrayValue(), castValue.Path(), nil, castValue.History()...) case *BoolValue: - values[name] = SomeBoolValue(castValue.BoolValue(), castValue.Event()) + values[name] = SomeBoolValueWithPath(castValue.BoolValue(), castValue.Path(), nil, castValue.History()...) case *DecimalValue: - values[name] = SomeDecimalValue(castValue.DecimalValue(), castValue.Event()) + values[name] = SomeDecimalValueWithPath(castValue.DecimalValue(), castValue.Path(), nil, castValue.History()...) case *StringValue: - values[name] = SomeStringValue(castValue.StringValue(), castValue.Event()) + values[name] = SomeStringValueWithPath(castValue.StringValue(), castValue.Path(), nil, castValue.History()...) default: return nil, fmt.Errorf("can't copy value of type %T", value) diff --git a/pkg/risks/script/event/contain.go b/pkg/risks/script/event/contain.go new file mode 100644 index 00000000..faf6741b --- /dev/null +++ b/pkg/risks/script/event/contain.go @@ -0,0 +1,37 @@ +package event + +type Contain struct { + negated bool + set Value + item Value + history History +} + +func NewContain(set Value, item Value, history ...Event) *Contain { + return &Contain{ + set: set, + item: item, + history: history, + } +} + +func (what *Contain) Negate() Event { + what.negated = !what.negated + return what +} + +func (what *Contain) Text() Text { + if len(what.set.Path()) == 0 && len(what.item.Path()) == 0 { + return append(what.history, append(what.set.History(), what.item.History()...)...).Text() + } + + if what.negated { + return makeTwoValueText(what.set, "does not contain", what.item) + } + + return makeTwoValueText(what.set, "contains", what.item) +} + +func (what *Contain) History() History { + return what.history +} diff --git a/pkg/risks/script/event/contain_test.go b/pkg/risks/script/event/contain_test.go new file mode 100644 index 00000000..a95f7ff4 --- /dev/null +++ b/pkg/risks/script/event/contain_test.go @@ -0,0 +1,53 @@ +package event + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestContainText(t *testing.T) { + assert.Equal(t, + "", + NewContain( + NewTestValue("value1", nil), + NewTestValue("value2", nil), + ).Text().String()) + + assert.Equal(t, + "value-path-1 contains value2", + NewContain( + NewTestValue("value1", NewPath("value-path-1")), + NewTestValue("value2", nil), + ).Text().String()) + + assert.Equal(t, + "value-path-1 contains value-path-2", + NewContain( + NewTestValue("value1", NewPath("value-path-1")), + NewTestValue("value2", NewPath("value-path-2")), + ).Text().String()) +} + +func TestContainNegate(t *testing.T) { + assert.Equal(t, + "", + NewContain( + NewTestValue("value1", nil), + NewTestValue("value2", nil), + ).Negate().Text().String()) + + assert.Equal(t, + "value-path-1 does not contain value2", + NewContain( + NewTestValue("value1", NewPath("value-path-1")), + NewTestValue("value2", nil), + ).Negate().Text().String()) + + assert.Equal(t, + "value-path-1 does not contain value-path-2", + NewContain( + NewTestValue("value1", NewPath("value-path-1")), + NewTestValue("value2", NewPath("value-path-2")), + ).Negate().Text().String()) +} diff --git a/pkg/risks/script/event/equal.go b/pkg/risks/script/event/equal.go new file mode 100644 index 00000000..3fc495ca --- /dev/null +++ b/pkg/risks/script/event/equal.go @@ -0,0 +1,37 @@ +package event + +type Equal struct { + negated bool + item1 Value + item2 Value + history History +} + +func NewEqual(item1 Value, item2 Value, history ...Event) *Equal { + return &Equal{ + item1: item1, + item2: item2, + history: history, + } +} + +func (what *Equal) Negate() Event { + what.negated = !what.negated + return what +} + +func (what *Equal) Text() Text { + if len(what.item1.Path()) == 0 && len(what.item2.Path()) == 0 { + return append(what.history, append(what.item1.History(), what.item2.History()...)...).Text() + } + + if what.negated { + return makeTwoValueText(what.item1, "is not equal to", what.item2) + } + + return makeTwoValueText(what.item1, "is equal to", what.item2) +} + +func (what *Equal) History() History { + return what.history +} diff --git a/pkg/risks/script/event/equal_test.go b/pkg/risks/script/event/equal_test.go new file mode 100644 index 00000000..63987af9 --- /dev/null +++ b/pkg/risks/script/event/equal_test.go @@ -0,0 +1,53 @@ +package event + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEqualText(t *testing.T) { + assert.Equal(t, + "", + NewEqual( + NewTestValue("value1", nil), + NewTestValue("value2", nil), + ).Text().String()) + + assert.Equal(t, + "value-path-1 is equal to value2", + NewEqual( + NewTestValue("value1", NewPath("value-path-1")), + NewTestValue("value2", nil), + ).Text().String()) + + assert.Equal(t, + "value-path-1 is equal to value-path-2", + NewEqual( + NewTestValue("value1", NewPath("value-path-1")), + NewTestValue("value2", NewPath("value-path-2")), + ).Text().String()) +} + +func TestEqualNegate(t *testing.T) { + assert.Equal(t, + "", + NewEqual( + NewTestValue("value1", nil), + NewTestValue("value2", nil), + ).Negate().Text().String()) + + assert.Equal(t, + "value-path-1 is not equal to value2", + NewEqual( + NewTestValue("value1", NewPath("value-path-1")), + NewTestValue("value2", nil), + ).Negate().Text().String()) + + assert.Equal(t, + "value-path-1 is not equal to value-path-2", + NewEqual( + NewTestValue("value1", NewPath("value-path-1")), + NewTestValue("value2", NewPath("value-path-2")), + ).Negate().Text().String()) +} diff --git a/pkg/risks/script/event/event.go b/pkg/risks/script/event/event.go new file mode 100644 index 00000000..e9f97ff3 --- /dev/null +++ b/pkg/risks/script/event/event.go @@ -0,0 +1,7 @@ +package event + +type Event interface { + Negate() Event + Text() Text + History() History +} diff --git a/pkg/risks/script/event/explain.go b/pkg/risks/script/event/explain.go new file mode 100644 index 00000000..d74e2ef2 --- /dev/null +++ b/pkg/risks/script/event/explain.go @@ -0,0 +1,27 @@ +package event + +type Explain struct { + negated bool + text string + history History +} + +func NewExplain(text string, history ...Event) *Explain { + return &Explain{ + text: text, + history: history, + } +} + +func (what *Explain) Negate() Event { + what.negated = !what.negated + return what +} + +func (what *Explain) Text() Text { + return new(Text).Append(what.text) +} + +func (what *Explain) History() History { + return what.history +} diff --git a/pkg/risks/script/event/explain_test.go b/pkg/risks/script/event/explain_test.go new file mode 100644 index 00000000..af907e47 --- /dev/null +++ b/pkg/risks/script/event/explain_test.go @@ -0,0 +1,19 @@ +package event + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestExplainText(t *testing.T) { + assert.Equal(t, + "some text", + NewExplain("some text").Text().String()) +} + +func TestExplainNegate(t *testing.T) { + assert.Equal(t, + "some text", + NewExplain("some text").Negate().Text().String()) +} diff --git a/pkg/risks/script/event/false.go b/pkg/risks/script/event/false.go new file mode 100644 index 00000000..c0be5c01 --- /dev/null +++ b/pkg/risks/script/event/false.go @@ -0,0 +1,35 @@ +package event + +type False struct { + negated bool + item Value + history History +} + +func NewFalse(item Value, history ...Event) *False { + return &False{ + item: item, + history: history, + } +} + +func (what *False) Negate() Event { + what.negated = !what.negated + return what +} + +func (what *False) Text() Text { + if len(what.item.Path()) == 0 { + return append(what.history, what.item.History()...).Text() + } + + if what.negated { + return makeOneValueText(what.item, "is true") + } + + return makeOneValueText(what.item, "is false") +} + +func (what *False) History() History { + return what.history +} diff --git a/pkg/risks/script/event/greater-or-equal.go b/pkg/risks/script/event/greater-or-equal.go new file mode 100644 index 00000000..a021b0be --- /dev/null +++ b/pkg/risks/script/event/greater-or-equal.go @@ -0,0 +1,37 @@ +package event + +type GreaterOrEqual struct { + negated bool + item1 Value + item2 Value + history History +} + +func NewGreaterOrEqual(item1 Value, item2 Value, history ...Event) *GreaterOrEqual { + return &GreaterOrEqual{ + item1: item1, + item2: item2, + history: history, + } +} + +func (what *GreaterOrEqual) Negate() Event { + what.negated = !what.negated + return what +} + +func (what *GreaterOrEqual) Text() Text { + if len(what.item1.Path()) == 0 && len(what.item2.Path()) == 0 { + return append(what.history, append(what.item1.History(), what.item2.History()...)...).Text() + } + + if what.negated { + return makeTwoValueText(what.item1, "is less than", what.item2) + } + + return makeTwoValueText(what.item1, "is greater than or equal to", what.item2) +} + +func (what *GreaterOrEqual) History() History { + return what.history +} diff --git a/pkg/risks/script/event/greater.go b/pkg/risks/script/event/greater.go new file mode 100644 index 00000000..007274b4 --- /dev/null +++ b/pkg/risks/script/event/greater.go @@ -0,0 +1,37 @@ +package event + +type Greater struct { + negated bool + item1 Value + item2 Value + history History +} + +func NewGreater(item1 Value, item2 Value, history ...Event) *Greater { + return &Greater{ + item1: item1, + item2: item2, + history: history, + } +} + +func (what *Greater) Negate() Event { + what.negated = !what.negated + return what +} + +func (what *Greater) Text() Text { + if len(what.item1.Path()) == 0 && len(what.item2.Path()) == 0 { + return append(what.history, append(what.item1.History(), what.item2.History()...)...).Text() + } + + if what.negated { + return makeTwoValueText(what.item1, "is less than or equal to", what.item2) + } + + return makeTwoValueText(what.item1, "is greater than", what.item2) +} + +func (what *Greater) History() History { + return what.history +} diff --git a/pkg/risks/script/event/history.go b/pkg/risks/script/event/history.go new file mode 100644 index 00000000..ddc6a5ad --- /dev/null +++ b/pkg/risks/script/event/history.go @@ -0,0 +1,12 @@ +package event + +type History []Event + +func (what History) Text() Text { + lines := make(Text, 0) + for _, item := range what { + lines = append(lines, item.Text()...) + } + + return lines +} diff --git a/pkg/risks/script/event/less-or-equal.go b/pkg/risks/script/event/less-or-equal.go new file mode 100644 index 00000000..4f8bd165 --- /dev/null +++ b/pkg/risks/script/event/less-or-equal.go @@ -0,0 +1,37 @@ +package event + +type LessOrEqual struct { + negated bool + item1 Value + item2 Value + history History +} + +func NewLessOrEqual(item1 Value, item2 Value, history ...Event) *LessOrEqual { + return &LessOrEqual{ + item1: item1, + item2: item2, + history: history, + } +} + +func (what *LessOrEqual) Negate() Event { + what.negated = !what.negated + return what +} + +func (what *LessOrEqual) Text() Text { + if len(what.item1.Path()) == 0 && len(what.item2.Path()) == 0 { + return append(what.history, append(what.item1.History(), what.item2.History()...)...).Text() + } + + if what.negated { + return makeTwoValueText(what.item1, "is greater than", what.item2) + } + + return makeTwoValueText(what.item1, "is less than or equal to", what.item2) +} + +func (what *LessOrEqual) History() History { + return what.history +} diff --git a/pkg/risks/script/event/less.go b/pkg/risks/script/event/less.go new file mode 100644 index 00000000..ab8e9bf8 --- /dev/null +++ b/pkg/risks/script/event/less.go @@ -0,0 +1,37 @@ +package event + +type Less struct { + negated bool + item1 Value + item2 Value + history History +} + +func NewLess(item1 Value, item2 Value, history ...Event) *Less { + return &Less{ + item1: item1, + item2: item2, + history: history, + } +} + +func (what *Less) Negate() Event { + what.negated = !what.negated + return what +} + +func (what *Less) Text() Text { + if len(what.item1.Path()) == 0 && len(what.item2.Path()) == 0 { + return append(what.history, append(what.item1.History(), what.item2.History()...)...).Text() + } + + if what.negated { + return makeTwoValueText(what.item1, "is greater than or equal to", what.item2) + } + + return makeTwoValueText(what.item1, "is less than", what.item2) +} + +func (what *Less) History() History { + return what.history +} diff --git a/pkg/risks/script/event/not-equal.go b/pkg/risks/script/event/not-equal.go new file mode 100644 index 00000000..b25ed1a7 --- /dev/null +++ b/pkg/risks/script/event/not-equal.go @@ -0,0 +1,37 @@ +package event + +type NotEqual struct { + negated bool + item1 Value + item2 Value + history History +} + +func NewNotEqual(item1 Value, item2 Value, history ...Event) *NotEqual { + return &NotEqual{ + item1: item1, + item2: item2, + history: history, + } +} + +func (what *NotEqual) Negate() Event { + what.negated = !what.negated + return what +} + +func (what *NotEqual) Text() Text { + if len(what.item1.Path()) == 0 && len(what.item2.Path()) == 0 { + return append(what.history, append(what.item1.History(), what.item2.History()...)...).Text() + } + + if what.negated { + return makeTwoValueText(what.item1, "is equal to", what.item2) + } + + return makeTwoValueText(what.item1, "is not equal to", what.item2) +} + +func (what *NotEqual) History() History { + return what.history +} diff --git a/pkg/risks/script/event/path.go b/pkg/risks/script/event/path.go new file mode 100644 index 00000000..6292e4b9 --- /dev/null +++ b/pkg/risks/script/event/path.go @@ -0,0 +1,25 @@ +package event + +import ( + "fmt" + "strings" +) + +type Path []string + +func NewPath(path ...string) Path { + return path[:] +} + +func (what Path) String() string { + if what == nil { + return "" + } + + path := make([]string, 0) + for _, item := range what { + path = append([]string{fmt.Sprintf("%v", item)}, path...) + } + + return strings.Join(path, " of ") +} diff --git a/pkg/risks/script/event/test-value.go b/pkg/risks/script/event/test-value.go new file mode 100644 index 00000000..cbab64ef --- /dev/null +++ b/pkg/risks/script/event/test-value.go @@ -0,0 +1,45 @@ +package event + +import "fmt" + +type TestValue struct { + value any + path Path + history History +} + +func NewTestValue(value any, path Path, history ...Event) *TestValue { + return &TestValue{ + value: value, + path: path, + history: history, + } +} + +func (what TestValue) Value() any { + return what.value +} + +func (what TestValue) Path() Path { + return what.path +} + +func (what TestValue) ValueText() Text { + return new(Text).Append(fmt.Sprintf("%v", what.value)) +} + +func (what TestValue) History() History { + return what.history +} + +func (what TestValue) Text() Text { + if len(what.path) > 0 { + return new(Text).Append(what.path.String()) + } + + return what.ValueText() +} + +func (what TestValue) Description() Text { + return new(Text).Append(fmt.Sprintf("%v is %v", what.path.String(), what.value)) +} diff --git a/pkg/risks/script/event/text.go b/pkg/risks/script/event/text.go new file mode 100644 index 00000000..cb412f4a --- /dev/null +++ b/pkg/risks/script/event/text.go @@ -0,0 +1,186 @@ +package event + +import ( + "strings" +) + +type Text []TextItem + +type TextItem struct { + Line string + SubText Text +} + +func (what Text) Append(line string, items ...TextItem) Text { + return append(what, TextItem{Line: line, SubText: items}) +} + +func (what Text) Lines() []string { + text := make([]string, 0) + for _, item := range what { + line := item.Line + + switch len(item.SubText) { + case 0: + text = append(text, line) + + default: + text = append(text, line+" because") + text = append(text, indent(item.SubText.Lines())...) + } + } + + return text +} + +func (what Text) Indent(lines []string) []string { + return indent(lines) +} + +func (what Text) String() string { + return strings.Join(what.Lines(), "\n") +} + +func makeOneValueText(value Value, verbText string) Text { + if value == nil { + return new(Text).Append("nil " + verbText) + } + + text := value.Text() + switch len(text) { + case 0: + return new(Text).Append("(empty) "+verbText, value.History().Text()...) + + case 1: + return new(Text).Append(text[0].Line+" "+verbText, value.History().Text()...) + + default: + return text.Append(verbText, value.History().Text()...) + } +} + +func makeTwoValueText(value1 Value, verbText string, value2 Value) Text { + switch value1.(type) { + case nil: + switch value2.(type) { + case nil: + return new(Text).Append("nil " + verbText + " nil") + + default: + text2 := value2.Text() + switch len(text2) { + case 0: + text2 = new(Text).Append("nil "+verbText+" (empty)", value2.History().Text()...) + + case 1: + text2 = new(Text).Append("nil "+verbText+" "+text2[0].Line, value2.History().Text()...) + + default: + text2 = append(new(Text).Append("nil "+verbText), text2...) + text2[len(text2)-1].SubText = value2.History().Text() + } + + return text2 + } + } + + switch value2.(type) { + case nil: + text1 := value1.Text() + switch len(text1) { + case 0: + text1 = new(Text).Append("(empty) "+verbText+" nil", value1.History().Text()...) + + case 1: + text1 = new(Text).Append(text1[0].Line+" "+verbText+" nil", value1.History().Text()...) + + default: + return text1.Append(verbText+" nil", value1.History().Text()...) + } + + return text1 + } + + text1 := value1.Text() + text2 := value2.Text() + switch len(text1) { + case 0: + switch len(text2) { + case 0: + text2 = new(Text).Append("(empty) "+verbText+" (empty)", append(value1.History().Text(), value2.History().Text()...)...) + + case 1: + text2 = new(Text).Append("(empty) "+verbText+" "+text2[0].Line, append(value1.History().Text(), value2.History().Text()...)...) + + default: + text2 = append(new(Text).Append("(empty) "+verbText), text2...) + text2[len(text2)-1].SubText = append(value1.History().Text(), value2.History().Text()...) + } + + return text2 + + case 1: + switch len(text2) { + case 0: + text2 = new(Text).Append(text1[0].Line+" "+verbText+" (empty)", append(value1.History().Text(), value2.History().Text()...)...) + + case 1: + text2 = new(Text).Append(text1[0].Line+" "+verbText+" "+text2[0].Line, append(value1.History().Text(), value2.History().Text()...)...) + + default: + text2 = append(new(Text).Append(text1[0].Line+" "+verbText), text2...) + text2[len(text2)-1].SubText = append(value1.History().Text(), value2.History().Text()...) + } + + return text2 + + default: + switch len(text2) { + case 0: + text2 = text1.Append(verbText+" (empty)", append(value1.History().Text(), value2.History().Text()...)...) + + case 1: + text2 = text1.Append(verbText+" "+text2[0].Line, append(value1.History().Text(), value2.History().Text()...)...) + + default: + text2 = append(text1.Append(verbText), text2...) + text2[len(text2)-1].SubText = append(value1.History().Text(), value2.History().Text()...) + } + + return text2 + } +} + +func GetLines(text string) []string { + value := strings.ReplaceAll(text, "\r\n", "\n") + value = strings.ReplaceAll(value, "\n\r", "\n") + value = strings.ReplaceAll(value, "\r", "\n") + + return strings.Split(value, "\n") +} + +func IndentAndConcatenate(text []string) []string { + return indent(makeList(text)) +} + +func indent(text []string) []string { + newText := make([]string, 0) + for _, line := range text { + newText = append(newText, INDENT+line) + } + + return newText +} + +func makeList(text []string) []string { + newText := make([]string, 0) + for n, line := range text { + if n < len(text)-1 { + line += ", and" + } + + newText = append(newText, line) + } + + return newText +} diff --git a/pkg/risks/script/event/texter.go b/pkg/risks/script/event/texter.go new file mode 100644 index 00000000..28b7ccea --- /dev/null +++ b/pkg/risks/script/event/texter.go @@ -0,0 +1,9 @@ +package event + +const ( + INDENT = " " +) + +type Texter interface { + Text() Text +} diff --git a/pkg/risks/script/event/true.go b/pkg/risks/script/event/true.go new file mode 100644 index 00000000..75478c82 --- /dev/null +++ b/pkg/risks/script/event/true.go @@ -0,0 +1,35 @@ +package event + +type True struct { + negated bool + item Value + history History +} + +func NewTrue(item Value, history ...Event) *True { + return &True{ + item: item, + history: history, + } +} + +func (what *True) Negate() Event { + what.negated = !what.negated + return what +} + +func (what *True) Text() Text { + if len(what.item.Path()) == 0 { + return append(what.history, what.item.History()...).Text() + } + + if what.negated { + return makeOneValueText(what.item, "is false") + } + + return makeOneValueText(what.item, "is true") +} + +func (what *True) History() History { + return what.history +} diff --git a/pkg/risks/script/event/value-event.go b/pkg/risks/script/event/value-event.go new file mode 100644 index 00000000..8fdef1f4 --- /dev/null +++ b/pkg/risks/script/event/value-event.go @@ -0,0 +1,25 @@ +package event + +type ValueEvent struct { + negated bool + item Value +} + +func NewValueEvent(item Value) *ValueEvent { + return &ValueEvent{ + item: item, + } +} + +func (what *ValueEvent) Negate() Event { + what.negated = !what.negated + return what +} + +func (what *ValueEvent) Text() Text { + return what.item.Description() +} + +func (what *ValueEvent) History() History { + return nil +} diff --git a/pkg/risks/script/event/value.go b/pkg/risks/script/event/value.go new file mode 100644 index 00000000..b93f4abf --- /dev/null +++ b/pkg/risks/script/event/value.go @@ -0,0 +1,9 @@ +package event + +type Value interface { + Path() Path // origin path + ValueText() Text // text representation of value + History() History // events resulting in this value + Text() Text // origin path if present, value text otherwise + Description() Text // description, including origin path and value +} diff --git a/pkg/risks/script/expressions/all-expression.go b/pkg/risks/script/expressions/all-expression.go index ada35f49..4a6009e0 100644 --- a/pkg/risks/script/expressions/all-expression.go +++ b/pkg/risks/script/expressions/all-expression.go @@ -2,8 +2,10 @@ package expressions import ( "fmt" + "github.com/shopspring/decimal" "github.com/threagile/threagile/pkg/risks/script/common" + "github.com/threagile/threagile/pkg/risks/script/event" ) type AllExpression struct { @@ -88,108 +90,86 @@ func (what *AllExpression) EvalBool(scope *common.Scope) (*common.BoolValue, str } func (what *AllExpression) evalBool(scope *common.Scope, inValue common.Value) (*common.BoolValue, string, error) { - oldItem := scope.PopItem() - defer scope.SetItem(oldItem) + if what.expression == nil { + return common.SomeBoolValue(true, scope.Stack()), "", nil + } switch castValue := inValue.Value().(type) { case []any: - if what.expression == nil { - return common.SomeBoolValue(true, nil), "", nil - } - - values := make([]common.Value, 0) + events := make([]event.Event, 0) for index, item := range castValue { - if len(what.index) > 0 { - scope.Set(what.index, common.SomeDecimalValue(decimal.NewFromInt(int64(index)), nil)) - } - - itemValue := scope.SetItem(common.SomeValue(item, inValue.Event())) - if len(what.item) > 0 { - scope.Set(what.item, itemValue) - } - - value, errorLiteral, expressionError := what.expression.EvalBool(scope) - if expressionError != nil { - return common.EmptyBoolValue(), errorLiteral, fmt.Errorf("error evaluating expression #%v of all-expression: %w", index+1, expressionError) + indexValue := common.SomeDecimalValue(decimal.NewFromInt(int64(index)), scope.Stack()) + itemValue := common.SomeValueWithPath(item, inValue.Path(), scope.Stack(), event.NewValueEvent(inValue)) + isDone, value, errorLiteral, expressionError := what.evalBoolOnce(scope, indexValue, itemValue, fmt.Sprintf("#%v", index)) + if isDone { + return common.SomeBoolValue(false, scope.Stack(), event.NewValueEvent(value)), errorLiteral, expressionError } - if !value.BoolValue() { - return common.SomeBoolValue(false, value.Event()), "", nil - } - - values = append(values, value) + events = append(events, event.NewValueEvent(value)) } - return common.SomeBoolValue(true, common.NewEvent(common.NewTrueProperty(), inValue.Event().Origin).From(values...)), "", nil + return common.SomeBoolValue(true, scope.Stack(), events...), "", nil case []common.Value: - if what.expression == nil { - return common.SomeBoolValue(true, nil), "", nil - } - - values := make([]common.Value, 0) + events := make([]event.Event, 0) for index, item := range castValue { - if len(what.index) > 0 { - scope.Set(what.index, common.SomeDecimalValue(decimal.NewFromInt(int64(index)), nil)) - } - - itemValue := scope.SetItem(common.SomeValue(item.Value(), inValue.Event())) - if len(what.item) > 0 { - scope.Set(what.item, itemValue) - } - - value, errorLiteral, expressionError := what.expression.EvalBool(scope) - if expressionError != nil { - return common.EmptyBoolValue(), errorLiteral, fmt.Errorf("error evaluating expression #%v of all-expression: %w", index+1, expressionError) - } - - if !value.BoolValue() { - return common.SomeBoolValue(false, value.Event()), "", nil + indexValue := common.SomeDecimalValue(decimal.NewFromInt(int64(index)), scope.Stack()) + itemValue := common.SomeValueWithPath(item, inValue.Path(), scope.Stack(), event.NewValueEvent(inValue)) + isDone, value, errorLiteral, expressionError := what.evalBoolOnce(scope, indexValue, itemValue, fmt.Sprintf("#%v", index)) + if isDone { + return common.SomeBoolValue(false, scope.Stack(), event.NewValueEvent(value)), errorLiteral, expressionError } - values = append(values, value) + events = append(events, event.NewValueEvent(value)) } - return common.SomeBoolValue(true, common.NewEvent(common.NewTrueProperty(), inValue.Event().Origin).From(values...)), "", nil + return common.SomeBoolValue(true, scope.Stack(), events...), "", nil case map[string]any: - if what.expression == nil { - return common.SomeBoolValue(true, nil), "", nil - } - - values := make([]common.Value, 0) + events := make([]event.Event, 0) for name, item := range castValue { - if len(what.index) > 0 { - scope.Set(what.index, common.SomeStringValue(name, nil)) - } - - itemValue := scope.SetItem(common.SomeValue(item, inValue.Event())) - if len(what.item) > 0 { - scope.Set(what.item, itemValue) + indexValue := common.SomeStringValue(name, scope.Stack()) + itemValue := common.SomeValueWithPath(item, inValue.Path(), scope.Stack(), event.NewValueEvent(inValue)) + isDone, value, errorLiteral, expressionError := what.evalBoolOnce(scope, indexValue, itemValue, fmt.Sprintf("%q", name)) + if isDone { + return common.SomeBoolValue(false, scope.Stack(), event.NewValueEvent(value)), errorLiteral, expressionError } - value, errorLiteral, expressionError := what.expression.EvalBool(scope) - if expressionError != nil { - return common.EmptyBoolValue(), errorLiteral, fmt.Errorf("error evaluating expression %q of all-expression: %w", name, expressionError) - } - - if !value.BoolValue() { - return common.SomeBoolValue(false, value.Event()), "", nil - } - - values = append(values, value) + events = append(events, event.NewValueEvent(value)) } - return common.SomeBoolValue(true, common.NewEvent(common.NewTrueProperty(), inValue.Event().Origin).From(values...)), "", nil + return common.SomeBoolValue(true, scope.Stack(), events...), "", nil case common.Value: - return what.evalBool(scope, common.SomeValue(castValue.Value(), inValue.Event())) + return what.evalBool(scope, castValue) default: return common.EmptyBoolValue(), what.Literal(), fmt.Errorf("failed to eval all-expression: expected iterable type, got %T", inValue) } } +func (what *AllExpression) evalBoolOnce(scope *common.Scope, indexValue common.Value, itemValue common.Value, index string) (bool, common.Value, string, error) { + if len(what.index) > 0 { + scope.Set(what.index, indexValue) + } + + scope.SetItem(itemValue) + if len(what.item) > 0 { + scope.Set(what.item, itemValue) + } + + value, errorLiteral, expressionError := what.expression.EvalBool(scope) + if expressionError != nil { + return true, value, errorLiteral, fmt.Errorf("error evaluating expression %v of all-expression: %w", index, expressionError) + } + + if !value.BoolValue() { + return true, value, "", nil + } + + return false, value, "", nil +} + func (what *AllExpression) EvalAny(scope *common.Scope) (common.Value, string, error) { return what.EvalBool(scope) } diff --git a/pkg/risks/script/expressions/and-expression.go b/pkg/risks/script/expressions/and-expression.go index d7c68cc6..a5a620b4 100644 --- a/pkg/risks/script/expressions/and-expression.go +++ b/pkg/risks/script/expressions/and-expression.go @@ -2,7 +2,9 @@ package expressions import ( "fmt" + "github.com/threagile/threagile/pkg/risks/script/common" + "github.com/threagile/threagile/pkg/risks/script/event" ) type AndExpression struct { @@ -44,7 +46,7 @@ func (what *AndExpression) ParseAny(script any) (common.Expression, any, error) } func (what *AndExpression) EvalBool(scope *common.Scope) (*common.BoolValue, string, error) { - values := make([]common.Value, 0) + events := make([]event.Event, 0) for index, expression := range what.expressions { value, errorLiteral, evalError := expression.EvalBool(scope) if evalError != nil { @@ -52,13 +54,17 @@ func (what *AndExpression) EvalBool(scope *common.Scope) (*common.BoolValue, str } if !value.BoolValue() { - return common.SomeBoolValue(false, value.Event()), "", nil + return common.SomeBoolValue(false, scope.Stack(), event.NewValueEvent(value)), "", nil } - values = append(values, value) + if len(value.Path()) == 0 { + events = append(events, value.History()...) + } else { + events = append(events, event.NewValueEvent(value)) + } } - return common.SomeBoolValue(true, common.NewEvent(common.NewTrueProperty(), common.EmptyPath()).From(values...)), "", nil + return common.SomeBoolValue(true, scope.Stack(), events...), "", nil } func (what *AndExpression) EvalAny(scope *common.Scope) (common.Value, string, error) { diff --git a/pkg/risks/script/expressions/any-expression.go b/pkg/risks/script/expressions/any-expression.go index f574077b..4d6b3da4 100644 --- a/pkg/risks/script/expressions/any-expression.go +++ b/pkg/risks/script/expressions/any-expression.go @@ -2,8 +2,10 @@ package expressions import ( "fmt" + "github.com/shopspring/decimal" "github.com/threagile/threagile/pkg/risks/script/common" + "github.com/threagile/threagile/pkg/risks/script/event" ) type AnyExpression struct { @@ -88,105 +90,86 @@ func (what *AnyExpression) EvalBool(scope *common.Scope) (*common.BoolValue, str } func (what *AnyExpression) evalBool(scope *common.Scope, inValue common.Value) (*common.BoolValue, string, error) { + if what.expression == nil { + return common.SomeBoolValue(true, scope.Stack()), "", nil + } + switch castValue := inValue.Value().(type) { case []any: - if what.expression == nil { - return common.SomeBoolValue(true, nil), "", nil - } - - values := make([]common.Value, 0) + events := make([]event.Event, 0) for index, item := range castValue { - if len(what.index) > 0 { - scope.Set(what.index, common.SomeDecimalValue(decimal.NewFromInt(int64(index)), nil)) + indexValue := common.SomeDecimalValue(decimal.NewFromInt(int64(index)), scope.Stack()) + itemValue := common.SomeValueWithPath(item, inValue.Path(), scope.Stack(), event.NewValueEvent(inValue)) + isDone, value, errorLiteral, expressionError := what.evalBoolOnce(scope, indexValue, itemValue, fmt.Sprintf("#%v", index)) + if isDone { + return common.SomeBoolValue(true, scope.Stack(), event.NewValueEvent(value)), errorLiteral, expressionError } - itemValue := scope.SetItem(common.SomeValue(item, inValue.Event())) - if len(what.item) > 0 { - scope.Set(what.item, itemValue) - } - - value, errorLiteral, expressionError := what.expression.EvalBool(scope) - if expressionError != nil { - return common.EmptyBoolValue(), errorLiteral, fmt.Errorf("error evaluating expression #%v of any-expression: %w", index+1, expressionError) - } - - if value.BoolValue() { - return common.SomeBoolValue(true, value.Event()), "", nil - } - - values = append(values, value) + events = append(events, event.NewValueEvent(value)) } - return common.SomeBoolValue(false, common.NewEvent(common.NewFalseProperty(), inValue.Event().Origin).From(values...)), "", nil + return common.SomeBoolValue(false, scope.Stack(), events...), "", nil case []common.Value: - if what.expression == nil { - return common.SomeBoolValue(true, nil), "", nil - } - - values := make([]common.Value, 0) + events := make([]event.Event, 0) for index, item := range castValue { - if len(what.index) > 0 { - scope.Set(what.index, common.SomeDecimalValue(decimal.NewFromInt(int64(index)), nil)) - } - - itemValue := scope.SetItem(common.SomeValue(item.Value(), inValue.Event())) - if len(what.item) > 0 { - scope.Set(what.item, itemValue) - } - - value, errorLiteral, expressionError := what.expression.EvalBool(scope) - if expressionError != nil { - return common.EmptyBoolValue(), errorLiteral, fmt.Errorf("error evaluating expression #%v of any-expression: %w", index+1, expressionError) - } - - if value.BoolValue() { - return common.SomeBoolValue(true, value.Event()), "", nil + indexValue := common.SomeDecimalValue(decimal.NewFromInt(int64(index)), scope.Stack()) + itemValue := common.SomeValueWithPath(item, inValue.Path(), scope.Stack(), event.NewValueEvent(inValue)) + isDone, value, errorLiteral, expressionError := what.evalBoolOnce(scope, indexValue, itemValue, fmt.Sprintf("#%v", index)) + if isDone { + return common.SomeBoolValue(true, scope.Stack(), event.NewValueEvent(value)), errorLiteral, expressionError } - values = append(values, value) + events = append(events, event.NewValueEvent(value)) } - return common.SomeBoolValue(false, common.NewEvent(common.NewFalseProperty(), inValue.Event().Origin).From(values...)), "", nil + return common.SomeBoolValue(false, scope.Stack(), events...), "", nil case map[string]any: - if what.expression == nil { - return common.SomeBoolValue(true, nil), "", nil - } - - values := make([]common.Value, 0) + events := make([]event.Event, 0) for name, item := range castValue { - if len(what.index) > 0 { - scope.Set(what.index, common.SomeStringValue(name, nil)) + indexValue := common.SomeStringValue(name, scope.Stack()) + itemValue := common.SomeValueWithPath(item, inValue.Path(), scope.Stack(), event.NewValueEvent(inValue)) + isDone, value, errorLiteral, expressionError := what.evalBoolOnce(scope, indexValue, itemValue, fmt.Sprintf("%q", name)) + if isDone { + return common.SomeBoolValue(true, scope.Stack(), event.NewValueEvent(value)), errorLiteral, expressionError } - itemValue := scope.SetItem(common.SomeValue(item, inValue.Event())) - if len(what.item) > 0 { - scope.Set(what.item, itemValue) - } - - value, errorLiteral, expressionError := what.expression.EvalBool(scope) - if expressionError != nil { - return common.EmptyBoolValue(), errorLiteral, fmt.Errorf("error evaluating expression %q of any-expression: %w", name, expressionError) - } - - if value.BoolValue() { - return common.SomeBoolValue(false, value.Event()), "", nil - } - - values = append(values, value) + events = append(events, event.NewValueEvent(value)) } - return common.SomeBoolValue(true, common.NewEvent(common.NewFalseProperty(), inValue.Event().Origin).From(values...)), "", nil + return common.SomeBoolValue(false, scope.Stack(), events...), "", nil case common.Value: - return what.evalBool(scope, common.SomeValue(castValue.Value(), inValue.Event())) + return what.evalBool(scope, castValue) default: return common.EmptyBoolValue(), what.Literal(), fmt.Errorf("failed to eval any-expression: expected iterable type, got %T", inValue) } } +func (what *AnyExpression) evalBoolOnce(scope *common.Scope, indexValue common.Value, itemValue common.Value, index string) (bool, common.Value, string, error) { + if len(what.index) > 0 { + scope.Set(what.index, indexValue) + } + + scope.SetItem(itemValue) + if len(what.item) > 0 { + scope.Set(what.item, itemValue) + } + + value, errorLiteral, expressionError := what.expression.EvalBool(scope) + if expressionError != nil { + return true, value, errorLiteral, fmt.Errorf("error evaluating expression %v of any-expression: %w", index, expressionError) + } + + if value.BoolValue() { + return true, value, "", nil + } + + return false, value, "", nil +} + func (what *AnyExpression) EvalAny(scope *common.Scope) (common.Value, string, error) { return what.EvalBool(scope) } diff --git a/pkg/risks/script/expressions/array-expression.go b/pkg/risks/script/expressions/array-expression.go index 92007b34..128f49ed 100644 --- a/pkg/risks/script/expressions/array-expression.go +++ b/pkg/risks/script/expressions/array-expression.go @@ -2,6 +2,7 @@ package expressions import ( "fmt" + "github.com/threagile/threagile/pkg/risks/script/common" ) @@ -45,7 +46,7 @@ func (what *ArrayExpression) EvalArray(scope *common.Scope) (*common.ArrayValue, values = append(values, value) } - return common.SomeArrayValue(values, common.NewEvent(common.NewValueProperty(values), common.NewPath("array value")).From(values...)), "", nil + return common.SomeArrayValue(values, scope.Stack()), "", nil } func (what *ArrayExpression) EvalAny(scope *common.Scope) (common.Value, string, error) { diff --git a/pkg/risks/script/expressions/contains-expression.go b/pkg/risks/script/expressions/contain-expression.go similarity index 59% rename from pkg/risks/script/expressions/contains-expression.go rename to pkg/risks/script/expressions/contain-expression.go index 7ec5f23d..97e6608a 100644 --- a/pkg/risks/script/expressions/contains-expression.go +++ b/pkg/risks/script/expressions/contain-expression.go @@ -2,17 +2,19 @@ package expressions import ( "fmt" + "github.com/threagile/threagile/pkg/risks/script/common" + "github.com/threagile/threagile/pkg/risks/script/event" ) -type ContainsExpression struct { +type ContainExpression struct { literal string item common.ValueExpression in common.ValueExpression as string } -func (what *ContainsExpression) ParseBool(script any) (common.BoolExpression, any, error) { +func (what *ContainExpression) ParseBool(script any) (common.BoolExpression, any, error) { what.literal = common.ToLiteral(script) switch script.(type) { @@ -55,11 +57,11 @@ func (what *ContainsExpression) ParseBool(script any) (common.BoolExpression, an return what, nil, nil } -func (what *ContainsExpression) ParseAny(script any) (common.Expression, any, error) { +func (what *ContainExpression) ParseAny(script any) (common.Expression, any, error) { return what.ParseBool(script) } -func (what *ContainsExpression) EvalBool(scope *common.Scope) (*common.BoolValue, string, error) { +func (what *ContainExpression) EvalBool(scope *common.Scope) (*common.BoolValue, string, error) { item, errorItemLiteral, itemError := what.item.EvalAny(scope) if itemError != nil { return common.EmptyBoolValue(), errorItemLiteral, itemError @@ -73,62 +75,71 @@ func (what *ContainsExpression) EvalBool(scope *common.Scope) (*common.BoolValue return what.evalBool(scope, item, inValue) } -func (what *ContainsExpression) evalBool(scope *common.Scope, item common.Value, inValue common.Value) (*common.BoolValue, string, error) { +func (what *ContainExpression) evalBool(scope *common.Scope, item common.Value, inValue common.Value) (*common.BoolValue, string, error) { switch castValue := inValue.Value().(type) { case []any: + events := make([]event.Event, 0) for index, value := range castValue { - compareValue, compareError := common.Compare(item, common.SomeValue(value, nil), what.as) + compareEvent, compareError := common.Compare(item, common.SomeValue(value, scope.Stack()), what.as, scope.Stack()) if compareError != nil { return common.EmptyBoolValue(), what.Literal(), fmt.Errorf("failed to eval contains-expression: can't compare value to item #%v: %w", index+1, compareError) } - if common.IsSame(compareValue.Property) { - return common.SomeBoolValue(true, compareValue), "", nil + if common.IsSame(compareEvent) { + return common.SomeBoolValue(true, scope.Stack(), event.NewContain(inValue, item, compareEvent)), "", nil } + + events = append(events, compareEvent) } - return common.SomeBoolValue(false, nil), "", nil + return common.SomeBoolValue(false, scope.Stack(), events...), "", nil case []common.Value: + events := make([]event.Event, 0) for index, value := range castValue { - compareValue, compareError := common.Compare(item, common.SomeValue(value.Value(), value.Event()), what.as) + compareEvent, compareError := common.Compare(item, value, what.as, scope.Stack()) if compareError != nil { return common.EmptyBoolValue(), what.Literal(), fmt.Errorf("failed to eval contains-expression: can't compare value to item #%v: %w", index+1, compareError) } - if common.IsSame(compareValue.Property) { - return common.SomeBoolValue(true, compareValue), "", nil + if common.IsSame(compareEvent) { + return common.SomeBoolValue(true, scope.Stack(), event.NewContain(inValue, item, compareEvent)), "", nil } + + events = append(events, compareEvent) } - return common.SomeBoolValue(false, nil), "", nil + return common.SomeBoolValue(false, scope.Stack(), events...), "", nil case map[string]any: + events := make([]event.Event, 0) for name, value := range castValue { - compareValue, compareError := common.Compare(item, common.SomeValue(value, nil), what.as) + compareEvent, compareError := common.Compare(item, common.SomeValue(value, scope.Stack()), what.as, scope.Stack()) if compareError != nil { return common.EmptyBoolValue(), what.Literal(), fmt.Errorf("failed to eval contains-expression: can't compare value to item %q: %w", name, compareError) } - if common.IsSame(compareValue.Property) { - return common.SomeBoolValue(true, compareValue), "", nil + if common.IsSame(compareEvent) { + return common.SomeBoolValue(true, scope.Stack(), event.NewContain(inValue, item, compareEvent)), "", nil } + + events = append(events, compareEvent) } - return common.SomeBoolValue(false, nil), "", nil + return common.SomeBoolValue(false, scope.Stack(), events...), "", nil case common.Value: - return what.evalBool(scope, item, common.SomeValue(castValue.Value(), inValue.Event())) + return what.evalBool(scope, item, castValue) default: return common.EmptyBoolValue(), "", fmt.Errorf("failed to eval contains-expression: expected iterable type, got %T", inValue) } } -func (what *ContainsExpression) EvalAny(scope *common.Scope) (common.Value, string, error) { +func (what *ContainExpression) EvalAny(scope *common.Scope) (common.Value, string, error) { return what.EvalBool(scope) } -func (what *ContainsExpression) Literal() string { +func (what *ContainExpression) Literal() string { return what.literal } diff --git a/pkg/risks/script/expressions/count-expression.go b/pkg/risks/script/expressions/count-expression.go index fc04934b..8bc2d7b1 100644 --- a/pkg/risks/script/expressions/count-expression.go +++ b/pkg/risks/script/expressions/count-expression.go @@ -2,8 +2,10 @@ package expressions import ( "fmt" + "github.com/shopspring/decimal" "github.com/threagile/threagile/pkg/risks/script/common" + "github.com/threagile/threagile/pkg/risks/script/event" ) type CountExpression struct { @@ -91,102 +93,99 @@ func (what *CountExpression) evalDecimal(scope *common.Scope, inValue common.Val switch castValue := inValue.Value().(type) { case []any: if what.expression == nil { - return common.SomeDecimalValue(decimal.NewFromInt(int64(len(castValue))), nil), "", nil + return common.SomeDecimalValue(decimal.NewFromInt(int64(len(castValue))), scope.Stack()), "", nil } + events := make([]event.Event, 0) var count int64 = 0 - values := make([]common.Value, 0) for index, item := range castValue { - if len(what.index) > 0 { - scope.Set(what.index, common.SomeDecimalValue(decimal.NewFromInt(int64(index)), nil)) - } - - itemValue := scope.SetItem(common.SomeValue(item, inValue.Event())) - if len(what.item) > 0 { - scope.Set(what.item, itemValue) - } - - value, errorLiteral, expressionError := what.expression.EvalBool(scope) + indexValue := common.SomeDecimalValue(decimal.NewFromInt(int64(index)), scope.Stack()) + itemValue := common.SomeValueWithPath(item, inValue.Path(), scope.Stack(), event.NewValueEvent(inValue)) + value, errorLiteral, expressionError := what.evalDecimalOnce(scope, indexValue, itemValue, fmt.Sprintf("#%v", index)) if expressionError != nil { return common.EmptyDecimalValue(), errorLiteral, fmt.Errorf("error evaluating expression #%v of all-expression: %w", index+1, expressionError) } if value.BoolValue() { - values = append(values, value) + events = append(events, event.NewValueEvent(value)) count++ } } - return common.SomeDecimalValue(decimal.NewFromInt(count), inValue.Event().From(values...)), "", nil + return common.SomeDecimalValue(decimal.NewFromInt(count), scope.Stack(), events...), "", nil case []common.Value: if what.expression == nil { - return common.SomeDecimalValue(decimal.NewFromInt(int64(len(castValue))), nil), "", nil + return common.SomeDecimalValue(decimal.NewFromInt(int64(len(castValue))), scope.Stack()), "", nil } + events := make([]event.Event, 0) var count int64 = 0 - values := make([]common.Value, 0) for index, item := range castValue { - if len(what.index) > 0 { - scope.Set(what.index, common.SomeDecimalValue(decimal.NewFromInt(int64(index)), nil)) - } - - itemValue := scope.SetItem(common.SomeValue(item.Value(), inValue.Event())) - if len(what.item) > 0 { - scope.Set(what.item, itemValue) - } - - value, errorLiteral, expressionError := what.expression.EvalBool(scope) + indexValue := common.SomeDecimalValue(decimal.NewFromInt(int64(index)), scope.Stack()) + itemValue := common.SomeValueWithPath(item, inValue.Path(), scope.Stack(), event.NewValueEvent(inValue)) + value, errorLiteral, expressionError := what.evalDecimalOnce(scope, indexValue, itemValue, fmt.Sprintf("#%v", index)) if expressionError != nil { - return common.EmptyDecimalValue(), errorLiteral, fmt.Errorf("error evaluating expression #%v of all-expression: %w", index+1, expressionError) + return common.EmptyDecimalValue(), errorLiteral, expressionError } if value.BoolValue() { - values = append(values, value) + events = append(events, event.NewValueEvent(value)) count++ } } - return common.SomeDecimalValue(decimal.NewFromInt(count), inValue.Event().From(values...)), "", nil + return common.SomeDecimalValue(decimal.NewFromInt(count), scope.Stack(), events...), "", nil case map[string]any: if what.expression == nil { - return common.SomeDecimalValue(decimal.NewFromInt(int64(len(castValue))), nil), "", nil + return common.SomeDecimalValue(decimal.NewFromInt(int64(len(castValue))), scope.Stack()), "", nil } + events := make([]event.Event, 0) var count int64 = 0 - values := make([]common.Value, 0) for name, item := range castValue { - if len(what.index) > 0 { - scope.Set(what.index, common.SomeStringValue(name, nil)) - } - - itemValue := scope.SetItem(common.SomeValue(item, inValue.Event())) - if len(what.item) > 0 { - scope.Set(what.item, itemValue) - } - - value, errorLiteral, expressionError := what.expression.EvalBool(scope) + indexValue := common.SomeStringValue(name, scope.Stack()) + itemValue := common.SomeValueWithPath(item, inValue.Path(), scope.Stack(), event.NewValueEvent(inValue)) + value, errorLiteral, expressionError := what.evalDecimalOnce(scope, indexValue, itemValue, fmt.Sprintf("%q", name)) if expressionError != nil { - return common.EmptyDecimalValue(), errorLiteral, fmt.Errorf("error evaluating expression %q of all-expression: %w", name, expressionError) + return common.EmptyDecimalValue(), errorLiteral, expressionError } if value.BoolValue() { - values = append(values, value) + events = append(events, event.NewValueEvent(value)) count++ } } - return common.SomeDecimalValue(decimal.NewFromInt(count), inValue.Event().From(values...)), "", nil + return common.SomeDecimalValue(decimal.NewFromInt(count), scope.Stack(), events...), "", nil case common.Value: - return what.evalDecimal(scope, common.SomeValue(castValue.Value(), inValue.Event())) + return what.evalDecimal(scope, castValue) default: return common.EmptyDecimalValue(), what.Literal(), fmt.Errorf("failed to eval all-expression: expected iterable type, got %T", inValue) } } +func (what *CountExpression) evalDecimalOnce(scope *common.Scope, indexValue common.Value, itemValue common.Value, index string) (*common.BoolValue, string, error) { + if len(what.index) > 0 { + scope.Set(what.index, indexValue) + } + + scope.SetItem(itemValue) + if len(what.item) > 0 { + scope.Set(what.item, itemValue) + } + + value, errorLiteral, expressionError := what.expression.EvalBool(scope) + if expressionError != nil { + return value, errorLiteral, fmt.Errorf("error evaluating expression %v of all-expression: %w", index, expressionError) + } + + return value, "", nil +} + func (what *CountExpression) EvalAny(scope *common.Scope) (common.Value, string, error) { return what.EvalDecimal(scope) } diff --git a/pkg/risks/script/expressions/equal-expression.go b/pkg/risks/script/expressions/equal-expression.go index 8f62de5b..d7b8de5b 100644 --- a/pkg/risks/script/expressions/equal-expression.go +++ b/pkg/risks/script/expressions/equal-expression.go @@ -2,6 +2,7 @@ package expressions import ( "fmt" + "github.com/threagile/threagile/pkg/risks/script/common" ) @@ -70,16 +71,16 @@ func (what *EqualExpression) EvalBool(scope *common.Scope) (*common.BoolValue, s return common.EmptyBoolValue(), errorInLiteral, evalError } - compareValue, compareError := common.Compare(first, second, what.as) + compareValue, compareError := common.Compare(first, second, what.as, scope.Stack()) if compareError != nil { return common.EmptyBoolValue(), what.Literal(), fmt.Errorf("failed to compare equal-expression: %w", compareError) } - if common.IsSame(compareValue.Property) { - return common.SomeBoolValue(true, compareValue), "", nil + if common.IsSame(compareValue) { + return common.SomeBoolValue(true, scope.Stack(), compareValue), "", nil } - return common.SomeBoolValue(false, compareValue), "", nil + return common.SomeBoolValue(false, scope.Stack(), compareValue), "", nil } func (what *EqualExpression) EvalAny(scope *common.Scope) (common.Value, string, error) { diff --git a/pkg/risks/script/expressions/equal-expression_test.go b/pkg/risks/script/expressions/equal-expression_test.go new file mode 100644 index 00000000..f8ed9c43 --- /dev/null +++ b/pkg/risks/script/expressions/equal-expression_test.go @@ -0,0 +1,70 @@ +package expressions + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/threagile/threagile/pkg/risks/script/common" +) + +func TestParseEqualExpression(t *testing.T) { + expression, errorScript, parseError := new(EqualExpression).ParseBool(map[string]interface{}{ + "first": "value1", + "second": "value2", + "as": "", + }) + + assert.Equal(t, nil, parseError) + assert.Equal(t, nil, errorScript) + assert.IsType(t, &EqualExpression{}, expression) +} + +func TestParseErrorEqualExpression(t *testing.T) { + expression, errorScript, parseError := new(EqualExpression).ParseBool(map[string]interface{}{ + "first0": "value1", + "second": "value2", + }) + + assert.NotEqual(t, nil, parseError) + assert.Equal(t, "failed to parse equal-expression: unexpected keyword \"first0\"", parseError.Error()) + assert.Equal(t, map[string]interface{}{"first0": "value1", "second": "value2"}, errorScript) + assert.IsType(t, nil, expression) +} + +func TestEvalTrueEqualExpression(t *testing.T) { + expression, errorScript, parseError := new(EqualExpression).ParseBool(map[string]interface{}{ + "first": "value1", + "second": "value1", + }) + + assert.Equal(t, nil, parseError) + assert.Equal(t, nil, errorScript) + assert.IsType(t, &EqualExpression{}, expression) + + result, errorLiteral, evalError := expression.EvalBool(new(common.Scope)) + + assert.Equal(t, nil, evalError) + assert.Equal(t, "", errorLiteral) + assert.IsType(t, &common.BoolValue{}, result) + + assert.Equal(t, true, result.BoolValue()) +} + +func TestEvalFalseEqualExpression(t *testing.T) { + expression, errorScript, parseError := new(EqualExpression).ParseBool(map[string]interface{}{ + "first": "value1", + "second": "value2", + }) + + assert.Equal(t, nil, parseError) + assert.Equal(t, nil, errorScript) + assert.IsType(t, &EqualExpression{}, expression) + + result, errorLiteral, evalError := expression.EvalBool(new(common.Scope)) + + assert.Equal(t, nil, evalError) + assert.Equal(t, "", errorLiteral) + assert.IsType(t, &common.BoolValue{}, result) + + assert.Equal(t, false, result.BoolValue()) +} diff --git a/pkg/risks/script/expressions/equal-or-greater-expression.go b/pkg/risks/script/expressions/equal-or-greater-expression.go index 8cda001f..179b339f 100644 --- a/pkg/risks/script/expressions/equal-or-greater-expression.go +++ b/pkg/risks/script/expressions/equal-or-greater-expression.go @@ -2,6 +2,7 @@ package expressions import ( "fmt" + "github.com/threagile/threagile/pkg/risks/script/common" ) @@ -70,20 +71,20 @@ func (what *EqualOrGreaterExpression) EvalBool(scope *common.Scope) (*common.Boo return common.EmptyBoolValue(), errorInLiteral, evalError } - compareValue, compareError := common.Compare(first, second, what.as) + compareValue, compareError := common.Compare(first, second, what.as, scope.Stack()) if compareError != nil { return common.EmptyBoolValue(), what.Literal(), fmt.Errorf("failed to compare equal-expression: %w", compareError) } - if common.IsSame(compareValue.Property) { - return common.SomeBoolValue(true, compareValue), "", nil + if common.IsSame(compareValue) { + return common.SomeBoolValue(true, scope.Stack(), compareValue), "", nil } - if common.IsGreater(compareValue.Property) { - return common.SomeBoolValue(true, compareValue), "", nil + if common.IsGreater(compareValue) { + return common.SomeBoolValue(true, scope.Stack(), compareValue), "", nil } - return common.SomeBoolValue(false, compareValue), "", nil + return common.SomeBoolValue(false, scope.Stack(), compareValue), "", nil } func (what *EqualOrGreaterExpression) EvalAny(scope *common.Scope) (common.Value, string, error) { diff --git a/pkg/risks/script/expressions/equal-or-less-expression.go b/pkg/risks/script/expressions/equal-or-less-expression.go index c03d7450..346654b8 100644 --- a/pkg/risks/script/expressions/equal-or-less-expression.go +++ b/pkg/risks/script/expressions/equal-or-less-expression.go @@ -2,6 +2,7 @@ package expressions import ( "fmt" + "github.com/threagile/threagile/pkg/risks/script/common" ) @@ -70,20 +71,20 @@ func (what *EqualOrLessExpression) EvalBool(scope *common.Scope) (*common.BoolVa return common.EmptyBoolValue(), errorInLiteral, evalError } - compareValue, compareError := common.Compare(first, second, what.as) + compareValue, compareError := common.Compare(first, second, what.as, scope.Stack()) if compareError != nil { return common.EmptyBoolValue(), what.Literal(), fmt.Errorf("failed to compare equal-expression: %w", compareError) } - if common.IsSame(compareValue.Property) { - return common.SomeBoolValue(true, compareValue), "", nil + if common.IsSame(compareValue) { + return common.SomeBoolValue(true, scope.Stack(), compareValue), "", nil } - if common.IsLess(compareValue.Property) { - return common.SomeBoolValue(true, compareValue), "", nil + if common.IsLess(compareValue) { + return common.SomeBoolValue(true, scope.Stack(), compareValue), "", nil } - return common.SomeBoolValue(false, compareValue), "", nil + return common.SomeBoolValue(false, scope.Stack(), compareValue), "", nil } func (what *EqualOrLessExpression) EvalAny(scope *common.Scope) (common.Value, string, error) { diff --git a/pkg/risks/script/expressions/expression-list.go b/pkg/risks/script/expressions/expression-list.go index 6c65b34b..f064bf11 100644 --- a/pkg/risks/script/expressions/expression-list.go +++ b/pkg/risks/script/expressions/expression-list.go @@ -2,6 +2,7 @@ package expressions import ( "fmt" + "github.com/threagile/threagile/pkg/risks/script/common" ) @@ -23,7 +24,7 @@ func (what *ExpressionList) ParseExpression(script map[string]any) (common.Expre return new(AndExpression).ParseBool(value) case common.Contains: - return new(ContainsExpression).ParseBool(value) + return new(ContainExpression).ParseBool(value) case common.Count: return new(CountExpression).ParseDecimal(value) @@ -143,7 +144,7 @@ func (what *ExpressionList) EvalAny(scope *common.Scope) (common.Value, string, values = append(values, value) } - return common.SomeArrayValue(values, common.NewEvent(common.NewValueProperty(values), common.NewPath("array value")).From(values...)), "", nil + return common.SomeArrayValue(values, scope.Stack()), "", nil } } diff --git a/pkg/risks/script/expressions/false-expression.go b/pkg/risks/script/expressions/false-expression.go index 6994d817..75ae774c 100644 --- a/pkg/risks/script/expressions/false-expression.go +++ b/pkg/risks/script/expressions/false-expression.go @@ -2,7 +2,9 @@ package expressions import ( "fmt" + "github.com/threagile/threagile/pkg/risks/script/common" + "github.com/threagile/threagile/pkg/risks/script/event" ) type FalseExpression struct { @@ -39,7 +41,15 @@ func (what *FalseExpression) EvalBool(scope *common.Scope) (*common.BoolValue, s return common.EmptyBoolValue(), errorLiteral, fmt.Errorf("%q: error evaluating false-expression: %w", what.literal, evalError) } - return common.SomeBoolValue(!value.BoolValue(), value.Event()), "", nil + history := value.History() + if len(history) == 1 { + theEvent := history[0] + theEvent.Negate() + + return common.SomeBoolValueWithPath(!value.BoolValue(), value.Path(), scope.Stack(), theEvent), "", nil + } + + return common.SomeBoolValue(!value.BoolValue(), scope.Stack(), event.NewValueEvent(value)), "", nil } func (what *FalseExpression) EvalAny(scope *common.Scope) (common.Value, string, error) { diff --git a/pkg/risks/script/expressions/greater-expression.go b/pkg/risks/script/expressions/greater-expression.go index 27a189af..b6959c72 100644 --- a/pkg/risks/script/expressions/greater-expression.go +++ b/pkg/risks/script/expressions/greater-expression.go @@ -2,6 +2,7 @@ package expressions import ( "fmt" + "github.com/threagile/threagile/pkg/risks/script/common" ) @@ -80,16 +81,16 @@ func (what *GreaterExpression) EvalBool(scope *common.Scope) (*common.BoolValue, asString = as.StringValue() } - compareValue, compareError := common.Compare(first, second, asString) + compareValue, compareError := common.Compare(first, second, asString, scope.Stack()) if compareError != nil { return common.EmptyBoolValue(), what.Literal(), fmt.Errorf("failed to compare equal-expression: %w", compareError) } - if common.IsGreater(compareValue.Property) { - return common.SomeBoolValue(true, compareValue), "", nil + if common.IsGreater(compareValue) { + return common.SomeBoolValue(true, scope.Stack(), compareValue), "", nil } - return common.SomeBoolValue(false, compareValue), "", nil + return common.SomeBoolValue(false, scope.Stack(), compareValue), "", nil } func (what *GreaterExpression) EvalAny(scope *common.Scope) (common.Value, string, error) { diff --git a/pkg/risks/script/expressions/less-expression.go b/pkg/risks/script/expressions/less-expression.go index 3d557252..c34b7c7e 100644 --- a/pkg/risks/script/expressions/less-expression.go +++ b/pkg/risks/script/expressions/less-expression.go @@ -2,6 +2,7 @@ package expressions import ( "fmt" + "github.com/threagile/threagile/pkg/risks/script/common" ) @@ -70,16 +71,16 @@ func (what *LessExpression) EvalBool(scope *common.Scope) (*common.BoolValue, st return common.EmptyBoolValue(), errorInLiteral, evalError } - compareValue, compareError := common.Compare(first, second, what.as) + compareValue, compareError := common.Compare(first, second, what.as, scope.Stack()) if compareError != nil { return common.EmptyBoolValue(), what.Literal(), fmt.Errorf("failed to compare equal-expression: %w", compareError) } - if common.IsLess(compareValue.Property) { - return common.SomeBoolValue(true, compareValue), "", nil + if common.IsLess(compareValue) { + return common.SomeBoolValue(true, scope.Stack(), compareValue), "", nil } - return common.SomeBoolValue(false, compareValue), "", nil + return common.SomeBoolValue(false, scope.Stack(), compareValue), "", nil } func (what *LessExpression) EvalAny(scope *common.Scope) (common.Value, string, error) { diff --git a/pkg/risks/script/expressions/not-equal-expression.go b/pkg/risks/script/expressions/not-equal-expression.go index 70de7c97..207d14dc 100644 --- a/pkg/risks/script/expressions/not-equal-expression.go +++ b/pkg/risks/script/expressions/not-equal-expression.go @@ -2,6 +2,7 @@ package expressions import ( "fmt" + "github.com/threagile/threagile/pkg/risks/script/common" ) @@ -70,16 +71,16 @@ func (what *NotEqualExpression) EvalBool(scope *common.Scope) (*common.BoolValue return common.EmptyBoolValue(), errorInLiteral, evalError } - compareValue, compareError := common.Compare(first, second, what.as) + compareValue, compareError := common.Compare(first, second, what.as, scope.Stack()) if compareError != nil { return common.EmptyBoolValue(), what.Literal(), fmt.Errorf("failed to compare not-equal-expression: %w", compareError) } - if !common.IsSame(compareValue.Property) { - return common.SomeBoolValue(true, compareValue), "", nil + if !common.IsSame(compareValue) { + return common.SomeBoolValue(true, scope.Stack(), compareValue), "", nil } - return common.SomeBoolValue(false, compareValue), "", nil + return common.SomeBoolValue(false, scope.Stack(), compareValue), "", nil } func (what *NotEqualExpression) EvalAny(scope *common.Scope) (common.Value, string, error) { diff --git a/pkg/risks/script/expressions/or-expression.go b/pkg/risks/script/expressions/or-expression.go index 70f72119..b6fc98fa 100644 --- a/pkg/risks/script/expressions/or-expression.go +++ b/pkg/risks/script/expressions/or-expression.go @@ -2,7 +2,9 @@ package expressions import ( "fmt" + "github.com/threagile/threagile/pkg/risks/script/common" + "github.com/threagile/threagile/pkg/risks/script/event" ) type OrExpression struct { @@ -44,7 +46,7 @@ func (what *OrExpression) ParseAny(script any) (common.Expression, any, error) { } func (what *OrExpression) EvalBool(scope *common.Scope) (*common.BoolValue, string, error) { - values := make([]common.Value, 0) + events := make([]event.Event, 0) for index, expression := range what.expressions { value, errorLiteral, evalError := expression.EvalBool(scope) if evalError != nil { @@ -52,13 +54,13 @@ func (what *OrExpression) EvalBool(scope *common.Scope) (*common.BoolValue, stri } if value.BoolValue() { - return common.SomeBoolValue(true, value.Event()), "", nil + return value, "", nil } - values = append(values, value) + events = append(events, event.NewValueEvent(value)) } - return common.SomeBoolValue(false, common.NewEvent(common.NewFalseProperty(), common.EmptyPath()).From(values...)), "", nil + return common.SomeBoolValue(false, scope.Stack(), events...), "", nil } func (what *OrExpression) EvalAny(scope *common.Scope) (common.Value, string, error) { diff --git a/pkg/risks/script/expressions/value-expression.go b/pkg/risks/script/expressions/value-expression.go index adeb7f53..5f2af68a 100644 --- a/pkg/risks/script/expressions/value-expression.go +++ b/pkg/risks/script/expressions/value-expression.go @@ -2,11 +2,13 @@ package expressions import ( "fmt" - "github.com/shopspring/decimal" - "github.com/threagile/threagile/pkg/risks/script/common" "regexp" "strconv" "strings" + + "github.com/shopspring/decimal" + "github.com/threagile/threagile/pkg/risks/script/common" + "github.com/threagile/threagile/pkg/risks/script/event" ) type ValueExpression struct { @@ -57,23 +59,23 @@ func (what *ValueExpression) ParseAny(script any) (common.Expression, any, error } func (what *ValueExpression) EvalArray(scope *common.Scope) (*common.ArrayValue, string, error) { - return what.evalArray(scope, common.SomeValue(what.value, nil)) + return what.evalArray(scope, common.SomeValue(what.value, scope.Stack())) } func (what *ValueExpression) EvalBool(scope *common.Scope) (*common.BoolValue, string, error) { - return what.evalBool(scope, common.SomeValue(what.value, nil)) + return what.evalBool(scope, common.SomeValue(what.value, scope.Stack())) } func (what *ValueExpression) EvalDecimal(scope *common.Scope) (*common.DecimalValue, string, error) { - return what.evalDecimal(scope, common.SomeValue(what.value, nil)) + return what.evalDecimal(scope, common.SomeValue(what.value, scope.Stack())) } func (what *ValueExpression) EvalString(scope *common.Scope) (*common.StringValue, string, error) { - return what.evalString(scope, common.SomeValue(what.value, nil)) + return what.evalString(scope, common.SomeValue(what.value, scope.Stack())) } func (what *ValueExpression) EvalAny(scope *common.Scope) (common.Value, string, error) { - return what.evalAny(scope, common.SomeValue(what.value, nil)) + return what.evalAny(scope, common.SomeValue(what.value, scope.Stack())) } func (what *ValueExpression) evalArray(scope *common.Scope, anyValue common.Value) (*common.ArrayValue, string, error) { @@ -98,7 +100,7 @@ func (what *ValueExpression) evalArray(scope *common.Scope, anyValue common.Valu array = append(array, value) } - return common.SomeArrayValue(array, nil), "", nil + return common.SomeArrayValue(array, scope.Stack()), "", nil case nil: return common.EmptyArrayValue(), "", nil @@ -117,7 +119,7 @@ func (what *ValueExpression) evalBool(scope *common.Scope, anyValue common.Value return castValue, "", nil case nil: - return common.SomeBoolValue(false, nil), "", nil + return common.SomeBoolValue(false, scope.Stack()), "", nil default: return common.EmptyBoolValue(), what.Literal(), fmt.Errorf("failed to eval value-expression: value type %T is not a bool", what.value) @@ -133,7 +135,7 @@ func (what *ValueExpression) evalDecimal(scope *common.Scope, anyValue common.Va return castValue, "", nil case nil: - return common.SomeDecimalValue(decimal.Zero, nil), "", nil + return common.SomeDecimalValue(decimal.Zero, scope.Stack()), "", nil default: return common.EmptyDecimalValue(), what.Literal(), fmt.Errorf("failed to eval value-expression: value type %T is not a decimal", what.value) @@ -146,7 +148,7 @@ func (what *ValueExpression) evalString(scope *common.Scope, anyValue common.Val return what.stringToString(scope, castValue) case nil: - return common.SomeStringValue("", nil), "", nil + return common.SomeStringValue("", scope.Stack()), "", nil default: return common.EmptyStringValue(), what.Literal(), fmt.Errorf("failed to eval value-expression: value type %T is not a string", what.value) @@ -168,7 +170,7 @@ func (what *ValueExpression) evalAny(scope *common.Scope, anyValue common.Value) return what.evalArray(scope, castValue) case nil: - return common.SomeValue(nil, nil), "", nil + return common.NilValue(), "", nil default: return nil, what.Literal(), fmt.Errorf("failed to eval value-expression: value type is %T", what.value) @@ -182,13 +184,13 @@ func (what *ValueExpression) stringToBool(scope *common.Scope, valueString *comm } if value == nil { - return common.SomeBoolValue(false, valueString.Event()), "", nil + return common.SomeBoolValueWithPath(false, valueString.Path(), scope.Stack(), valueString.History()...), "", nil } switch castValue := value.Value().(type) { case string: // string literal if len(castValue) == 0 { - return common.SomeBoolValue(false, value.Event()), "", nil + return common.SomeBoolValueWithPath(false, value.Path(), scope.Stack(), value.History()...), "", nil } boolValue, parseError := strconv.ParseBool(castValue) @@ -196,13 +198,13 @@ func (what *ValueExpression) stringToBool(scope *common.Scope, valueString *comm return common.EmptyBoolValue(), what.Literal(), fmt.Errorf("failed to parse value-expression: %w", parseError) } - return common.SomeBoolValue(boolValue, value.Event()), "", nil + return common.SomeBoolValueWithPath(boolValue, value.Path(), scope.Stack(), value.History()...), "", nil case bool: // bool value - return common.SomeBoolValue(castValue, value.Event()), "", nil + return common.SomeBoolValueWithPath(castValue, value.Path(), scope.Stack(), value.History()...), "", nil case nil: // empty value - return common.SomeBoolValue(false, value.Event()), "", nil + return common.SomeBoolValueWithPath(false, value.Path(), scope.Stack(), value.History()...), "", nil case common.Value: return what.evalBool(scope, castValue) @@ -220,7 +222,7 @@ func (what *ValueExpression) stringToDecimal(scope *common.Scope, valueString *c switch castValue := value.Value().(type) { case decimal.Decimal: - return common.SomeDecimalValue(castValue, value.Event()), "", nil + return common.SomeDecimalValue(castValue, scope.Stack(), event.NewValueEvent(value)), "", nil case string: decimalValue, parseError := decimal.NewFromString(castValue) @@ -228,10 +230,10 @@ func (what *ValueExpression) stringToDecimal(scope *common.Scope, valueString *c return common.EmptyDecimalValue(), what.Literal(), fmt.Errorf("failed to parse value-expression: %w", parseError) } - return common.SomeDecimalValue(decimalValue, value.Event()), "", nil + return common.SomeDecimalValue(decimalValue, scope.Stack(), event.NewValueEvent(value)), "", nil case nil: - return common.SomeDecimalValue(decimal.Zero, value.Event()), "", nil + return common.SomeDecimalValue(decimal.Zero, scope.Stack(), event.NewValueEvent(value)), "", nil default: return common.EmptyDecimalValue(), what.Literal(), fmt.Errorf("expected value-expression to eval to a decimal instead of %T", value) @@ -246,7 +248,7 @@ func (what *ValueExpression) stringToString(scope *common.Scope, valueString *co switch castValue := value.Value().(type) { case string: - return common.SomeStringValue(castValue, value.Event()), "", nil + return common.SomeStringValue(castValue, scope.Stack(), event.NewValueEvent(value)), "", nil case *common.StringValue: return castValue, "", nil @@ -280,15 +282,19 @@ func (what *ValueExpression) evalStringReference(scope *common.Scope, ref *commo if regexp.MustCompile(`^` + funcRe + `$`).MatchString(resolvedValue.StringValue()) { genericValue, genericErrorLiteral, genericEvalError := what.resolveMethodCall(scope, funcRe, resolvedValue) - return common.SomeValue(genericValue, ref.Event()), genericErrorLiteral, genericEvalError + if genericEvalError != nil { + return common.NilValue(), genericErrorLiteral, genericEvalError + } + + return common.SomeValue(genericValue, scope.Stack(), event.NewValueEvent(genericValue)), "", nil } - return common.SomeValue(resolvedValue, ref.Event()), "", nil + return resolvedValue, "", nil } func (what *ValueExpression) resolveStringValues(scope *common.Scope, reString string, value *common.StringValue) *common.StringValue { replacements := 0 - values := make([]common.Value, 0) + events := value.History() text := regexp.MustCompile(reString).ReplaceAllStringFunc(value.StringValue(), func(name string) string { cleanName := name[1 : len(name)-1] item, ok := scope.Get(strings.ToLower(cleanName)) @@ -299,14 +305,15 @@ func (what *ValueExpression) resolveStringValues(scope *common.Scope, reString s switch castItem := item.Value().(type) { case string: replacements++ + events = append(events, event.NewValueEvent(item)) return castItem case common.Value: - values = append(values, castItem) stringValue := castItem.PlainValue() switch castStringValue := stringValue.(type) { case string: replacements++ + events = append(events, event.NewValueEvent(item)) return castStringValue default: @@ -319,29 +326,35 @@ func (what *ValueExpression) resolveStringValues(scope *common.Scope, reString s }) if replacements > 0 { - return what.resolveStringValues(scope, reString, common.SomeStringValue(text, value.Event().From(values...))) + return what.resolveStringValues(scope, reString, common.SomeStringValue(text, scope.Stack(), events...)) } - return common.SomeStringValue(text, value.Event()) + return common.SomeStringValue(text, scope.Stack(), events...) } func (what *ValueExpression) resolveMethodCalls(scope *common.Scope, reString string, value *common.StringValue) (*common.StringValue, string, error) { replacements := 0 - values := make([]common.Value, 0) + events := make([]event.Event, 0) text := regexp.MustCompile(reString).ReplaceAllStringFunc(value.StringValue(), func(name string) string { - returnValue, _, callError := what.resolveMethodCall(scope, reString, common.SomeStringValue(name, value.Event())) + returnValue, _, callError := what.resolveMethodCall(scope, reString, common.SomeStringValue(name, scope.Stack(), event.NewValueEvent(value))) if callError != nil { return name } if returnValue != nil { - stringValue, valueError := common.ToString(returnValue) + stringValue, valueError := common.ToStringValue(returnValue) if valueError != nil { return name } replacements++ - values = append(values, stringValue) + + if len(returnValue.Path()) == 0 { + events = append(events, returnValue.History()...) + } else { + events = append(events, event.NewValueEvent(stringValue)) + } + return stringValue.StringValue() } @@ -349,10 +362,10 @@ func (what *ValueExpression) resolveMethodCalls(scope *common.Scope, reString st }) if replacements == 0 { - return common.SomeStringValue(text, value.Event().From(values...)), "", nil + return value, "", nil } - return what.resolveMethodCalls(scope, reString, common.SomeStringValue(text, value.Event().From(values...))) + return what.resolveMethodCalls(scope, reString, common.SomeStringValue(text, scope.Stack(), append(value.History(), events...)...)) } func (what *ValueExpression) resolveMethodCall(scope *common.Scope, reString string, value *common.StringValue) (common.Value, string, error) { @@ -368,7 +381,7 @@ func (what *ValueExpression) resolveMethodCall(scope *common.Scope, reString str if len(strings.TrimSpace(match[2])) > 0 { // todo: better arg parsing for '{var1}, {var2}' for _, arg := range strings.Split(match[2], ",") { - val, errorLiteral, evalError := what.evalStringReference(scope, common.SomeStringValue(strings.TrimSpace(arg), value.Event())) + val, errorLiteral, evalError := what.evalStringReference(scope, common.SomeStringValue(strings.TrimSpace(arg), scope.Stack(), event.NewValueEvent(value))) if evalError != nil { return nil, errorLiteral, fmt.Errorf("failed to eval method parameter: %w", evalError) } @@ -394,7 +407,7 @@ func (what *ValueExpression) resolveMethodCall(scope *common.Scope, reString str } if common.IsBuiltIn(name) { - callValue, callError := common.CallBuiltIn(name, args...) + callValue, callError := common.CallBuiltIn(name, scope.Stack(), value.History(), args...) if callError != nil { return common.NilValue(), what.Literal(), fmt.Errorf("failed to call %q: %w", name, callError) } diff --git a/pkg/risks/script/property/blank.go b/pkg/risks/script/property/blank.go deleted file mode 100644 index 8a999765..00000000 --- a/pkg/risks/script/property/blank.go +++ /dev/null @@ -1,23 +0,0 @@ -package property - -/* - this is a property for a value that should not be printed as part of the history, such as structured values -*/ - -type Blank struct { -} - -func NewBlank() *Blank { - return new(Blank) -} - -func (what *Blank) Negate() { -} - -func (what *Blank) Negated() bool { - return false -} - -func (what *Blank) Text() []string { - return []string{} -} diff --git a/pkg/risks/script/property/equal.go b/pkg/risks/script/property/equal.go deleted file mode 100644 index 984399c1..00000000 --- a/pkg/risks/script/property/equal.go +++ /dev/null @@ -1,28 +0,0 @@ -package property - -type Equal struct { - negated bool -} - -func NewEqual() *Equal { - return new(Equal) -} - -func (what *Equal) Path() { -} - -func (what *Equal) Negate() { - what.negated = !what.negated -} - -func (what *Equal) Negated() bool { - return what.negated -} - -func (what *Equal) Text() []string { - if what.negated { - return []string{"not equal to"} - } - - return []string{"equal to"} -} diff --git a/pkg/risks/script/property/false.go b/pkg/risks/script/property/false.go deleted file mode 100644 index 40e95945..00000000 --- a/pkg/risks/script/property/false.go +++ /dev/null @@ -1,29 +0,0 @@ -package property - -type False struct { - negated bool -} - -func NewFalse() *False { - return new(False) -} - -func (what *False) Value() any { - return what.negated -} - -func (what *False) Negate() { - what.negated = !what.negated -} - -func (what *False) Negated() bool { - return what.negated -} - -func (what *False) Text() []string { - if what.negated { - return []string{"true"} - } - - return []string{"false"} -} diff --git a/pkg/risks/script/property/greater-or-equal.go b/pkg/risks/script/property/greater-or-equal.go deleted file mode 100644 index 07b306dd..00000000 --- a/pkg/risks/script/property/greater-or-equal.go +++ /dev/null @@ -1,28 +0,0 @@ -package property - -type GreaterOrEqual struct { - negated bool -} - -func NewGreaterOrEqual() *GreaterOrEqual { - return new(GreaterOrEqual) -} - -func (what *GreaterOrEqual) Path() { -} - -func (what *GreaterOrEqual) Negate() { - what.negated = !what.negated -} - -func (what *GreaterOrEqual) Negated() bool { - return what.negated -} - -func (what *GreaterOrEqual) Text() []string { - if what.negated { - return []string{"less than"} - } - - return []string{"greater than or equal to"} -} diff --git a/pkg/risks/script/property/greater.go b/pkg/risks/script/property/greater.go deleted file mode 100644 index c3ec51c0..00000000 --- a/pkg/risks/script/property/greater.go +++ /dev/null @@ -1,28 +0,0 @@ -package property - -type Greater struct { - negated bool -} - -func NewGreater() *Greater { - return new(Greater) -} - -func (what *Greater) Path() { -} - -func (what *Greater) Negate() { - what.negated = !what.negated -} - -func (what *Greater) Negated() bool { - return what.negated -} - -func (what *Greater) Text() []string { - if what.negated { - return []string{"less than or equal to"} - } - - return []string{"greater than"} -} diff --git a/pkg/risks/script/property/item-with-path.go b/pkg/risks/script/property/item-with-path.go deleted file mode 100644 index 86a38181..00000000 --- a/pkg/risks/script/property/item-with-path.go +++ /dev/null @@ -1,6 +0,0 @@ -package property - -type ItemWithPath interface { - Item - Path() -} diff --git a/pkg/risks/script/property/item.go b/pkg/risks/script/property/item.go deleted file mode 100644 index b3a0c825..00000000 --- a/pkg/risks/script/property/item.go +++ /dev/null @@ -1,7 +0,0 @@ -package property - -type Item interface { - Negate() - Negated() bool - Text() []string -} diff --git a/pkg/risks/script/property/less-or-equal.go b/pkg/risks/script/property/less-or-equal.go deleted file mode 100644 index 55da4cde..00000000 --- a/pkg/risks/script/property/less-or-equal.go +++ /dev/null @@ -1,28 +0,0 @@ -package property - -type LessOrEqual struct { - negated bool -} - -func NewLessOrEqual() *LessOrEqual { - return new(LessOrEqual) -} - -func (what *LessOrEqual) Path() { -} - -func (what *LessOrEqual) Negate() { - what.negated = !what.negated -} - -func (what *LessOrEqual) Negated() bool { - return what.negated -} - -func (what *LessOrEqual) Text() []string { - if what.negated { - return []string{"greater than"} - } - - return []string{"less than or equal to"} -} diff --git a/pkg/risks/script/property/less.go b/pkg/risks/script/property/less.go deleted file mode 100644 index 6a96609f..00000000 --- a/pkg/risks/script/property/less.go +++ /dev/null @@ -1,28 +0,0 @@ -package property - -type Less struct { - negated bool -} - -func NewLess() *Less { - return new(Less) -} - -func (what *Less) Path() { -} - -func (what *Less) Negate() { - what.negated = !what.negated -} - -func (what *Less) Negated() bool { - return what.negated -} - -func (what *Less) Text() []string { - if what.negated { - return []string{"greater than or equal to"} - } - - return []string{"less than"} -} diff --git a/pkg/risks/script/property/not-equal.go b/pkg/risks/script/property/not-equal.go deleted file mode 100644 index 8a90d9bc..00000000 --- a/pkg/risks/script/property/not-equal.go +++ /dev/null @@ -1,28 +0,0 @@ -package property - -type NotEqual struct { - negated bool -} - -func NewNotEqual() *NotEqual { - return new(NotEqual) -} - -func (what *NotEqual) Path() { -} - -func (what *NotEqual) Negate() { - what.negated = !what.negated -} - -func (what *NotEqual) Negated() bool { - return what.negated -} - -func (what *NotEqual) Text() []string { - if what.negated { - return []string{"equal to"} - } - - return []string{"not equal to"} -} diff --git a/pkg/risks/script/property/texter.go b/pkg/risks/script/property/texter.go deleted file mode 100644 index 3266b7b1..00000000 --- a/pkg/risks/script/property/texter.go +++ /dev/null @@ -1,5 +0,0 @@ -package property - -type Texter interface { - Text() []string -} diff --git a/pkg/risks/script/property/true.go b/pkg/risks/script/property/true.go deleted file mode 100644 index d98394b8..00000000 --- a/pkg/risks/script/property/true.go +++ /dev/null @@ -1,25 +0,0 @@ -package property - -type True struct { - negated bool -} - -func NewTrue() *True { - return new(True) -} - -func (what *True) Negate() { - what.negated = !what.negated -} - -func (what *True) Negated() bool { - return what.negated -} - -func (what *True) Text() []string { - if what.negated { - return []string{"false"} - } - - return []string{"true"} -} diff --git a/pkg/risks/script/property/value.go b/pkg/risks/script/property/value.go deleted file mode 100644 index 2ebe18e3..00000000 --- a/pkg/risks/script/property/value.go +++ /dev/null @@ -1,63 +0,0 @@ -package property - -import ( - "fmt" -) - -type Value struct { - value any - negated bool -} - -func NewValue(value any) *Value { - return &Value{value: value} -} - -func (what *Value) Negate() { - what.negated = !what.negated -} - -func (what *Value) Negated() bool { - return what.negated -} - -func (what *Value) Text() []string { - text := make([]string, 0) - switch castValue := what.value.(type) { - case Texter: - if what.negated { - text = append(text, "not") - } - - for _, value := range castValue.Text() { - text = append(text, fmt.Sprintf(" %v", value)) - } - - case []any: - if what.negated { - text = append(text, "not") - } - - for _, item := range castValue { - text = append(text, fmt.Sprintf(" - %v", item)) - } - - case map[string]any: - if what.negated { - text = append(text, "not") - } - - for name, item := range castValue { - text = append(text, fmt.Sprintf(" %v: %v", name, item)) - } - - default: - if what.negated { - text = append(text, fmt.Sprintf("not %v", castValue)) - } else { - text = append(text, fmt.Sprintf("%v", castValue)) - } - } - - return text -} diff --git a/pkg/risks/script/script.go b/pkg/risks/script/script.go index 8b3db980..084fe377 100644 --- a/pkg/risks/script/script.go +++ b/pkg/risks/script/script.go @@ -2,13 +2,15 @@ package script import ( "fmt" + "strings" + "github.com/threagile/threagile/pkg/input" "github.com/threagile/threagile/pkg/risks/script/common" + "github.com/threagile/threagile/pkg/risks/script/event" "github.com/threagile/threagile/pkg/risks/script/expressions" "github.com/threagile/threagile/pkg/risks/script/statements" "github.com/threagile/threagile/pkg/types" "gopkg.in/yaml.v3" - "strings" ) type Script struct { @@ -156,7 +158,7 @@ func (what *Script) ParseScript(script map[string]any) (*Script, error) { } func (what *Script) GenerateRisks(scope *common.Scope) ([]*types.Risk, string, error) { - value, valueOk := what.getItem(scope.Model, "technical_assets") + value, valueOk := what.getItem(scope.Model, `technical_assets`) if !valueOk { return nil, "", fmt.Errorf("no technical assets in scope") } @@ -168,7 +170,7 @@ func (what *Script) GenerateRisks(scope *common.Scope) ([]*types.Risk, string, e risks := make([]*types.Risk, 0) for techAssetName, techAsset := range techAssets { - techAssetValue := common.SomeValue(techAsset, common.NewEvent(common.NewValueProperty(techAsset), common.NewPath(fmt.Sprintf("technical asset '%v'", techAssetName)))) + techAssetValue := common.SomeValueWithPath(techAsset, event.NewPath(fmt.Sprintf("technical asset '%v'", techAssetName)), scope.Stack()) isMatch, errorMatchLiteral, matchError := what.matchRisk(scope, techAssetValue) if matchError != nil { return nil, errorMatchLiteral, matchError @@ -178,7 +180,7 @@ func (what *Script) GenerateRisks(scope *common.Scope) ([]*types.Risk, string, e continue } - risk, errorRiskLiteral, riskError := what.generateRisk(scope, techAssetName, techAssetValue, isMatch.Event()) + risk, errorRiskLiteral, riskError := what.generateRisk(scope, techAssetName, techAssetValue, isMatch.History()) if riskError != nil { return nil, errorRiskLiteral, riskError } @@ -232,7 +234,7 @@ func (what *Script) matchRisk(outerScope *common.Scope, techAsset common.Value) return common.EmptyBoolValue(), "", nil } -func (what *Script) generateRisk(outerScope *common.Scope, techAssetName string, techAsset common.Value, isMatchEvent *common.Event) (*types.Risk, string, error) { +func (what *Script) generateRisk(outerScope *common.Scope, techAssetName string, techAsset common.Value, isMatchEvent event.History) (*types.Risk, string, error) { if what.data == nil { return nil, "", fmt.Errorf("no data template") } @@ -296,20 +298,11 @@ func (what *Script) generateRisk(outerScope *common.Scope, techAssetName string, continue } - text := fmt.Sprintf("'%v' is '%v'", title, newValue.PlainValue()) - explanation := what.Explain([]*common.Event{newValue.Event()}) - if len(explanation) > 0 { - ratingExplanation = append(ratingExplanation, text+" because") - for n, line := range explanation { - if n < len(explanation)-1 { - ratingExplanation = append(ratingExplanation, line+", and") - } else { - ratingExplanation = append(ratingExplanation, line) - } - } - } else { - ratingExplanation = append(ratingExplanation, text) - } + newValue = common.SomeValueWithPath(newValue, event.NewPath(title), nil) + description := newValue.Description().String() + history := newValue.History() + _, _ = description, history + ratingExplanation = append(ratingExplanation, what.explain(newValue.Description().String(), newValue.History())...) } riskData, marshalError := yaml.Marshal(riskMap) @@ -325,25 +318,10 @@ func (what *Script) generateRisk(outerScope *common.Scope, techAssetName string, risk.CategoryId, _ = what.getItemString(scope.Risk, "id") - riskExplanation := make([]string, 0) text := fmt.Sprintf("Risk '%v' has been flagged for technical asset '%v'", scope.Category.Title, techAssetName) - var explanation []string - if isMatchEvent != nil { - explanation = what.Explain(isMatchEvent.Events) - if len(explanation) > 0 { - riskExplanation = append(riskExplanation, text+" because") - for n, line := range explanation { - if n < len(explanation)-1 { - riskExplanation = append(riskExplanation, line+", and") - } else { - riskExplanation = append(riskExplanation, line) - } - } - } else { - riskExplanation = append(riskExplanation, text) - } - } + riskExplanation := make([]string, 0) + riskExplanation = append(riskExplanation, what.explain(text, isMatchEvent)...) risk.RiskExplanation = riskExplanation risk.RatingExplanation = ratingExplanation @@ -351,13 +329,17 @@ func (what *Script) generateRisk(outerScope *common.Scope, techAssetName string, return &risk, "", nil } -func (what *Script) Explain(history []*common.Event) []string { - text := make([]string, 0) - for _, event := range history { - text = append(text, event.Indented(0)...) +func (what *Script) explain(text string, history event.History) []string { + explanation := make([]string, 0) + if len(history) > 0 { + explanation = append(explanation, event.IndentAndConcatenate(history.Text().Lines())...) + } + + if len(explanation) == 0 { + return append([]string{text}, explanation...) } - return text + return append([]string{text + " because"}, explanation...) } func (what *Script) getRiskID(outerScope *common.Scope, techAsset common.Value, risk *types.Risk) (string, string, error) { diff --git a/pkg/risks/script/statements/if-statement.go b/pkg/risks/script/statements/if-statement.go index 4aa0ba61..eb6a4b42 100644 --- a/pkg/risks/script/statements/if-statement.go +++ b/pkg/risks/script/statements/if-statement.go @@ -2,6 +2,7 @@ package statements import ( "fmt" + "github.com/threagile/threagile/pkg/risks/script/common" "github.com/threagile/threagile/pkg/risks/script/expressions" ) @@ -95,15 +96,15 @@ func (what *IfStatement) Run(scope *common.Scope) (string, error) { if value.BoolValue() { if what.yesPath != nil { - scope.PushCall(common.NewEventFrom(common.NewTrueProperty(), value)) - defer scope.PopCall() + scope.PushHistory(value.History()...) + defer scope.PopHistory() return what.yesPath.Run(scope) } } else { if what.noPath != nil { - scope.PushCall(common.NewEventFrom(common.NewFalseProperty(), value)) - defer scope.PopCall() + scope.PushHistory(value.History()...) + defer scope.PopHistory() return what.noPath.Run(scope) } diff --git a/pkg/risks/script/statements/loop-statement.go b/pkg/risks/script/statements/loop-statement.go index 5bb1c9e8..e62aa235 100644 --- a/pkg/risks/script/statements/loop-statement.go +++ b/pkg/risks/script/statements/loop-statement.go @@ -2,8 +2,10 @@ package statements import ( "fmt" + "github.com/shopspring/decimal" "github.com/threagile/threagile/pkg/risks/script/common" + "github.com/threagile/threagile/pkg/risks/script/event" "github.com/threagile/threagile/pkg/risks/script/expressions" ) @@ -91,10 +93,10 @@ func (what *LoopStatement) run(scope *common.Scope, value common.Value) (string, } if len(what.index) > 0 { - scope.Set(what.index, common.SomeDecimalValue(decimal.NewFromInt(int64(index)), nil)) + scope.Set(what.index, common.SomeDecimalValue(decimal.NewFromInt(int64(index)), scope.Stack())) } - itemValue := scope.SetItem(common.SomeValue(item, value.Event())) + itemValue := scope.SetItem(common.SomeValue(item, scope.Stack(), event.NewValueEvent(value))) if len(what.item) > 0 { scope.Set(what.item, itemValue) } @@ -112,10 +114,10 @@ func (what *LoopStatement) run(scope *common.Scope, value common.Value) (string, } if len(what.index) > 0 { - scope.Set(what.index, common.SomeDecimalValue(decimal.NewFromInt(int64(index)), nil)) + scope.Set(what.index, common.SomeDecimalValue(decimal.NewFromInt(int64(index)), scope.Stack())) } - itemValue := scope.SetItem(common.SomeValue(item.Value(), value.Event())) + itemValue := scope.SetItem(common.SomeValue(item.Value(), scope.Stack(), event.NewValueEvent(value))) if len(what.item) > 0 { scope.Set(what.item, itemValue) } @@ -133,10 +135,10 @@ func (what *LoopStatement) run(scope *common.Scope, value common.Value) (string, } if len(what.index) > 0 { - scope.Set(what.index, common.SomeStringValue(name, nil)) + scope.Set(what.index, common.SomeStringValue(name, scope.Stack())) } - itemValue := scope.SetItem(common.SomeValue(item, value.Event())) + itemValue := scope.SetItem(common.SomeValue(item, scope.Stack(), event.NewValueEvent(value))) if len(what.item) > 0 { scope.Set(what.item, itemValue) } @@ -148,7 +150,7 @@ func (what *LoopStatement) run(scope *common.Scope, value common.Value) (string, } case common.Value: - return what.run(scope, common.SomeValue(castValue.Value(), value.Event())) + return what.run(scope, common.SomeValue(castValue.Value(), nil, value.History()...)) default: return what.Literal(), fmt.Errorf("failed to run loop-statement: expected iterable type, got %T", value) diff --git a/pkg/risks/script/statements/method-statement.go b/pkg/risks/script/statements/method-statement.go index 775af972..ae7ee4dc 100644 --- a/pkg/risks/script/statements/method-statement.go +++ b/pkg/risks/script/statements/method-statement.go @@ -2,7 +2,9 @@ package statements import ( "fmt" + "github.com/threagile/threagile/pkg/risks/script/common" + "github.com/threagile/threagile/pkg/risks/script/event" ) type MethodStatement struct { @@ -93,11 +95,12 @@ func (what *MethodStatement) runDeferred(scope *common.Scope) { if scope.Explain != nil { scope.HasReturned = false - scope.CallStack = make(common.History, 0) + scope.CallStack = make([]event.History, 0) + explanation := scope.Explain.Eval(scope) returnValue := scope.GetReturnValue() if returnValue != nil { - returnValue = common.SomeValue(returnValue.PlainValue(), returnValue.Event()) + returnValue = common.SomeValueWithPath(returnValue.PlainValue(), returnValue.Path(), scope.Stack(), event.NewExplain(explanation)) scope.SetReturnValue(returnValue) } } diff --git a/pkg/risks/script/statements/return-statement.go b/pkg/risks/script/statements/return-statement.go index fc3b06de..dae5e331 100644 --- a/pkg/risks/script/statements/return-statement.go +++ b/pkg/risks/script/statements/return-statement.go @@ -2,6 +2,7 @@ package statements import ( "fmt" + "github.com/threagile/threagile/pkg/risks/script/common" "github.com/threagile/threagile/pkg/risks/script/expressions" ) @@ -38,7 +39,10 @@ func (what *ReturnStatement) Run(scope *common.Scope) (string, error) { return errorLiteral, evalError } - scope.SetReturnValue(common.AddValueHistory(value, scope.GetHistory())) + if value != nil { + scope.SetReturnValue(value) + } + scope.HasReturned = true return "", nil diff --git a/pkg/risks/scripts/accidental-secret-leak.yaml b/pkg/risks/scripts/accidental-secret-leak.yaml index af650aa2..65c590b4 100644 --- a/pkg/risks/scripts/accidental-secret-leak.yaml +++ b/pkg/risks/scripts/accidental-secret-leak.yaml @@ -65,16 +65,16 @@ risk: parameters: - tech_asset do: - - defer: - - explain: "it is constructed based on technical asset '{tech_asset.id}'" - if: contains: item: git in: "{tech_asset.tags}" then: + - explain: "technical asset '{tech_asset.id}' has tag 'git'" - return: "Accidental Secret Leak(Git) risk at {tech_asset.title}: Git Leak Prevention" else: + - explain: "technical asset '{tech_asset.id}' does not have tag 'git'" - return: "Accidental Secret Leak risk at {tech_asset.title}" @@ -132,7 +132,7 @@ risk: # - explain: "the highest {type} value of the technical asset or any data asset processed by it is '{value}'" - assign: - value: "{tech_asset.{type}}" - - explain: "{type} value of the technical asset is '{value}'" + - explain: "'{type}' of technical asset '{tech_asset.id}' is '{value}'" - loop: in: "{tech_asset.data_assets_processed}" item: data_id diff --git a/pkg/risks/scripts/accidental_secret_leak_test.go b/pkg/risks/scripts/accidental_secret_leak_test.go index 0eb05b03..e560fd59 100644 --- a/pkg/risks/scripts/accidental_secret_leak_test.go +++ b/pkg/risks/scripts/accidental_secret_leak_test.go @@ -66,7 +66,7 @@ func TestAccidentalSecretLeakRuleGenerateRisksTechAssetNotContainSecretsNotRisks // "ta1": { // Technologies: types.TechnologyList{ // { -// Name: "git repository", +// Path: "git repository", // Attributes: map[string]bool{ // types.MayContainSecrets: true, // }, @@ -91,7 +91,7 @@ func TestAccidentalSecretLeakRuleGenerateRisksTechAssetNotContainSecretsNotRisks // "ta1": { // Technologies: types.TechnologyList{ // { -// Name: "git repository", +// Path: "git repository", // Attributes: map[string]bool{ // types.MayContainSecrets: true, // }, @@ -115,7 +115,7 @@ func TestAccidentalSecretLeakRuleGenerateRisksTechAssetNotContainSecretsNotRisks // "ta1": { // Technologies: types.TechnologyList{ // { -// Name: "git repository", +// Path: "git repository", // Attributes: map[string]bool{ // types.MayContainSecrets: true, // },