From b3232d5f7216f725470d849e98a773f2b898a7c9 Mon Sep 17 00:00:00 2001 From: Felipe Zipitria Date: Sun, 22 Sep 2024 22:02:10 -0300 Subject: [PATCH] feat: add factories for creating new objects Signed-off-by: Felipe Zipitria --- experimental/corpus/types.go | 5 + internal/quantitative/factories.go | 27 ++++ internal/quantitative/leipzig/payload.go | 15 +- internal/quantitative/leipzig/payload_test.go | 129 ++---------------- internal/quantitative/local_engine.go | 4 +- internal/quantitative/runner.go | 44 +++--- internal/quantitative/runner_test.go | 9 +- 7 files changed, 90 insertions(+), 143 deletions(-) create mode 100644 internal/quantitative/factories.go diff --git a/experimental/corpus/types.go b/experimental/corpus/types.go index bc3e194..b63fc78 100644 --- a/experimental/corpus/types.go +++ b/experimental/corpus/types.go @@ -25,6 +25,7 @@ type Type string const ( Leipzig Type = "leipzig" + NoType Type = "none" ) func (t *Type) String() string { @@ -112,6 +113,10 @@ type Iterator interface { type Payload interface { // LineNumber returns the payload given a line from the Corpus Iterator LineNumber() int + // SetLineNumber sets the line number of the payload + SetLineNumber(line int) // Content returns the payload given a line from the Corpus Iterator Content() string + // SetContent sets the content of the payload + SetContent(content string) } diff --git a/internal/quantitative/factories.go b/internal/quantitative/factories.go new file mode 100644 index 0000000..17b546b --- /dev/null +++ b/internal/quantitative/factories.go @@ -0,0 +1,27 @@ +package quantitative + +import ( + "fmt" + "github.com/coreruleset/go-ftw/experimental/corpus" + "github.com/coreruleset/go-ftw/internal/quantitative/leipzig" +) + +// CorpusFactory creates a new corpus +func CorpusFactory(t corpus.Type) (corpus.Corpus, error) { + switch t { + case corpus.Leipzig: + return leipzig.NewLeipzigCorpus(), nil + default: + return nil, fmt.Errorf("unsupported corpus type: %s", t) + } +} + +// PayloadFactory creates a new Payload based on the corpus.Type +func PayloadFactory(t corpus.Type) (corpus.Payload, error) { + switch t { + case corpus.Leipzig: + return &leipzig.Payload{}, nil + default: + return nil, fmt.Errorf("unsupported corpus type: %s", t) + } +} diff --git a/internal/quantitative/leipzig/payload.go b/internal/quantitative/leipzig/payload.go index 9d60e2f..62a71d1 100644 --- a/internal/quantitative/leipzig/payload.go +++ b/internal/quantitative/leipzig/payload.go @@ -4,6 +4,7 @@ package leipzig import ( + "github.com/coreruleset/go-ftw/experimental/corpus" "strconv" "strings" ) @@ -14,8 +15,8 @@ type Payload struct { payload string } -// NewPayload returns a new Payload -func NewPayload(line string) *Payload { +// NewPayload returns a new Payload from a line in the corpus. +func NewPayload(line string) corpus.Payload { split := strings.Split(line, "\t") // convert to int num, err := strconv.Atoi(split[0]) @@ -35,7 +36,17 @@ func (p *Payload) LineNumber() int { return p.line } +// SetLineNumber sets the line number of the payload +func (p *Payload) SetLineNumber(line int) { + p.line = line +} + // Content returns the payload given a line from the Corpus Iterator func (p *Payload) Content() string { return p.payload } + +// SetContent sets the content of the payload +func (p *Payload) SetContent(content string) { + p.payload = content +} diff --git a/internal/quantitative/leipzig/payload_test.go b/internal/quantitative/leipzig/payload_test.go index 52df43f..b4dc292 100644 --- a/internal/quantitative/leipzig/payload_test.go +++ b/internal/quantitative/leipzig/payload_test.go @@ -4,7 +4,6 @@ package leipzig import ( - "reflect" "testing" "github.com/stretchr/testify/suite" @@ -19,120 +18,20 @@ func TestPayloadTestSuite(t *testing.T) { } func (s *payloadTestSuite) TestNewPayload() { - type args struct { - line string - } - tests := []struct { - name string - args args - want *Payload - }{ - { - name: "TestNewPayload", - args: args{ - line: "1\t$156,834 for The Pathway to Excellence in Practice program through Neighborhood Place of Puna.", - }, - want: &Payload{ - line: 1, - payload: "$156,834 for The Pathway to Excellence in Practice program through Neighborhood Place of Puna.", - }, - }, - { - name: "TestAdditional", - args: args{ - line: "2000\tThis is an additional payload", - }, - want: &Payload{ - line: 2000, - payload: "This is an additional payload", - }, - }, - } - for _, tt := range tests { - s.Run(tt.name, func() { - if got := NewPayload(tt.args.line); !reflect.DeepEqual(got, tt.want) { - s.Require().Equal(got, tt.want) - } - }) - } + line := "1\t$156,834 for The Pathway to Excellence in Practice program through Neighborhood Place of Puna." + p := NewPayload(line) + s.Require().Equal(1, p.LineNumber()) + s.Require().Equal("$156,834 for The Pathway to Excellence in Practice program through Neighborhood Place of Puna.", p.Content()) + line2 := "2000\tThis is an additional payload" + p2 := NewPayload(line2) + s.Require().Equal(2000, p2.LineNumber()) + s.Require().Equal("This is an additional payload", p2.Content()) } -func (s *payloadTestSuite) TestPayload_Content() { - type fields struct { - line int - payload string - } - tests := []struct { - name string - fields fields - want string - }{ - { - name: "TestContent", - fields: fields{ - line: 1, - payload: "$156,834 for The Pathway to Excellence in Practice program through Neighborhood Place of Puna.", - }, - want: "$156,834 for The Pathway to Excellence in Practice program through Neighborhood Place of Puna.", - }, - { - name: "TestContent2", - fields: fields{ - line: 2000, - payload: "This is another test payload", - }, - want: "This is another test payload", - }, - } - for _, tt := range tests { - s.Run(tt.name, func() { - p := &Payload{ - line: tt.fields.line, - payload: tt.fields.payload, - } - if got := p.Content(); got != tt.want { - s.Require().Equal(got, tt.want) - } - }) - } -} - -func (s *payloadTestSuite) TestPayload_LineNumber() { - type fields struct { - line int - payload string - } - tests := []struct { - name string - fields fields - want int - }{ - { - name: "TestLineNumber", - fields: fields{ - line: 1, - payload: "This is a test payload", - }, - want: 1, - }, - { - name: "TestLineNumber2", - fields: fields{ - line: 2000, - payload: "This is another test payload", - }, - want: 2000, - }, - } - for _, tt := range tests { - s.Run(tt.name, func() { - p := &Payload{ - line: tt.fields.line, - payload: tt.fields.payload, - } - if got := p.LineNumber(); got != tt.want { - s.Require().Equal(got, tt.want) - } - }) - } +func (s *payloadTestSuite) TestPayloadSetters() { + p := &Payload{} + p.SetLineNumber(1) + s.Require().Equal(1, p.LineNumber()) + p.SetContent("test") + s.Require().Equal("test", p.Content()) } diff --git a/internal/quantitative/local_engine.go b/internal/quantitative/local_engine.go index 3b8bfd9..03849db 100644 --- a/internal/quantitative/local_engine.go +++ b/internal/quantitative/local_engine.go @@ -135,7 +135,7 @@ func crsWAF(prefix string, paranoiaLevel int) coraza.WAF { vars := map[string]interface{}{ "ParanoiaLevel": paranoiaLevel, } - log.Debug().Msgf("Using paranoia level: %d\n", paranoiaLevel) + log.Debug().Msgf("Using paranoia level: %d", paranoiaLevel) // set up configuration from template configTmpl, err := template.New("crs-config").Parse(testingConfigTmpl) if err != nil { @@ -163,7 +163,7 @@ func obtainStatusCodeFromInterruptionOrDefault(it *types.Interruption, defaultSt if it.Action == "deny" { statusCode := it.Status if statusCode == 0 { - statusCode = 403 + statusCode = http.StatusForbidden } return statusCode diff --git a/internal/quantitative/runner.go b/internal/quantitative/runner.go index 1d4c6bb..58c9d65 100644 --- a/internal/quantitative/runner.go +++ b/internal/quantitative/runner.go @@ -10,7 +10,6 @@ import ( "github.com/rs/zerolog/log" "github.com/coreruleset/go-ftw/experimental/corpus" - "github.com/coreruleset/go-ftw/internal/quantitative/leipzig" "github.com/coreruleset/go-ftw/output" ) @@ -42,19 +41,9 @@ type Params struct { CorpusSource string } -// NewCorpus creates a new corpus -func NewCorpus(corpusType corpus.Type) corpus.Corpus { - switch corpusType { - case corpus.Leipzig: - return leipzig.NewLeipzigCorpus() - default: - log.Fatal().Msgf("Unknown corpus implementation: %s", corpusType) - return nil - } -} - // RunQuantitativeTests runs all quantitative tests func RunQuantitativeTests(params Params, out *output.Output) error { + var lc corpus.File out.Println(":hourglass: Running quantitative tests") log.Trace().Msgf("Lines: %d", params.Lines) @@ -70,14 +59,20 @@ func RunQuantitativeTests(params Params, out *output.Output) error { startTime := time.Now() // create a new corpusRunner - corpusRunner := NewCorpus(params.Corpus). + corpusRunner, err := CorpusFactory(params.Corpus) + if err != nil { + return err + } + corpusRunner = corpusRunner. WithSize(params.CorpusSize). WithYear(params.CorpusYear). WithSource(params.CorpusSource). WithLanguage(params.CorpusLang) - // download the corpusRunner file - lc := corpusRunner.FetchCorpusFile() + // download the corpusRunner file if no payload is provided + if params.Payload == "" { + lc = corpusRunner.FetchCorpusFile() + } // create the results stats := NewQuantitativeStats() @@ -85,17 +80,22 @@ func RunQuantitativeTests(params Params, out *output.Output) error { runner := engine.Create(params.Directory, params.ParanoiaLevel) // Are we using the corpus at all? + // TODO: this could be moved to a generic "file" iterator (instead of "corpus), with a Factory method if params.Payload != "" { log.Trace().Msgf("Payload received from cmdline: %s", params.Payload) + p, err := PayloadFactory(params.Corpus) + if err != nil { + return err + } // CrsCall with payload - doEngineCall(runner, params.Payload, params.Rule, stats) + doEngineCall(runner, p, params.Rule, stats) } else { // iterate over the corpus log.Trace().Msgf("Iterating over corpus") for iter := corpusRunner.GetIterator(lc); iter.HasNext(); { - p := iter.Next() + payload := iter.Next() stats.incrementRun() - payload := p.Content() - log.Trace().Msgf("Line: %s", payload) + content := payload.Content() + log.Trace().Msgf("Line: %s", content) // check if we are looking for a specific payload line # if needSpecificPayload(params.Number, stats.Count()) { continue @@ -130,8 +130,8 @@ func wantSpecificRuleResults(specific int, rule int) bool { } // doEngineCall -func doEngineCall(engine LocalEngine, payload string, specificRule int, stats *QuantitativeRunStats) { - status, matchedRules := engine.CrsCall(payload) +func doEngineCall(engine LocalEngine, payload corpus.Payload, specificRule int, stats *QuantitativeRunStats) { + status, matchedRules := engine.CrsCall(payload.Content()) log.Trace().Msgf("Status: %d", status) log.Trace().Msgf("Rules: %v", matchedRules) if status == http.StatusForbidden { @@ -144,7 +144,7 @@ func doEngineCall(engine LocalEngine, payload string, specificRule int, stats *Q continue } stats.addFalsePositive(rule) - log.Debug().Msgf("**> rule %d => %s", rule, data) + log.Debug().Msgf("**> rule %d with payload %d => %s", rule, payload.LineNumber(), data) } } } diff --git a/internal/quantitative/runner_test.go b/internal/quantitative/runner_test.go index 275f7cb..0478bf5 100644 --- a/internal/quantitative/runner_test.go +++ b/internal/quantitative/runner_test.go @@ -60,10 +60,15 @@ func (s *runnerTestSuite) TeardownTest() { s.Require().NoError(err) } -func (s *runnerTestSuite) TestNewCorpus() { - s.c = NewCorpus(corpus.Leipzig) +func (s *runnerTestSuite) TestCorpusFactory() { + var err error + s.c, err = CorpusFactory(corpus.Leipzig) + s.Require().NoError(err) s.Require().NotNil(s.c) s.Require().Equal(s.c.URL(), "https://downloads.wortschatz-leipzig.de/corpora") + + s.c, err = CorpusFactory(corpus.NoType) + s.Require().Error(err) } func (s *runnerTestSuite) TestRunQuantitativeTests() {