|
4 | 4 | package corazawaf |
5 | 5 |
|
6 | 6 | import ( |
| 7 | + "fmt" |
7 | 8 | "testing" |
8 | 9 |
|
9 | 10 | "github.com/corazawaf/coraza/v3/experimental/plugins/macro" |
| 11 | + "github.com/corazawaf/coraza/v3/types" |
10 | 12 | ) |
11 | 13 |
|
12 | 14 | func newTestRule(id int) *Rule { |
@@ -85,3 +87,93 @@ func TestRuleGroupDeleteByID(t *testing.T) { |
85 | 87 | t.Fatal("Unexpected remaining rule in the rulegroup") |
86 | 88 | } |
87 | 89 | } |
| 90 | + |
| 91 | +// RuleFilterWrapper provides a flexible way to define rule filtering logic for tests. |
| 92 | +type RuleFilterWrapper struct { |
| 93 | + shouldIgnore func(rule types.RuleMetadata) bool |
| 94 | +} |
| 95 | + |
| 96 | +func (fw *RuleFilterWrapper) ShouldIgnore(rule types.RuleMetadata) bool { |
| 97 | + if fw.shouldIgnore == nil { |
| 98 | + return false // Default behavior: don't ignore if no function is provided |
| 99 | + } |
| 100 | + return fw.shouldIgnore(rule) |
| 101 | +} |
| 102 | + |
| 103 | +// TestRuleFilterInteraction confirms filter is checked first in Eval loop for all phases. |
| 104 | +func TestRuleFilterInteraction(t *testing.T) { |
| 105 | + // --- Define Rule (Phase 0 to run in all phases) --- |
| 106 | + rule := NewRule() |
| 107 | + rule.ID_ = 1 |
| 108 | + rule.Phase_ = 0 // Phase 0: Always evaluate |
| 109 | + rule.operator = nil // No operator means it always matches |
| 110 | + if err := rule.AddAction("deny", &dummyDenyAction{}); err != nil { |
| 111 | + t.Fatalf("Setup: Failed to add deny action: %v", err) |
| 112 | + } |
| 113 | + |
| 114 | + // --- Phases to Test --- |
| 115 | + phasesToTest := []types.RulePhase{ |
| 116 | + types.PhaseRequestHeaders, |
| 117 | + types.PhaseRequestBody, |
| 118 | + types.PhaseResponseHeaders, |
| 119 | + types.PhaseResponseBody, |
| 120 | + types.PhaseLogging, |
| 121 | + } |
| 122 | + |
| 123 | + // --- Filter Actions --- |
| 124 | + filterActions := []struct { |
| 125 | + name string |
| 126 | + filterShouldIgnore bool |
| 127 | + expectInterruption bool // Expect interruption only if filter *allows* the deny rule |
| 128 | + }{ |
| 129 | + { |
| 130 | + name: "Rule Filtered", |
| 131 | + filterShouldIgnore: true, |
| 132 | + expectInterruption: false, |
| 133 | + }, |
| 134 | + { |
| 135 | + name: "Rule Allowed", |
| 136 | + filterShouldIgnore: false, |
| 137 | + expectInterruption: true, |
| 138 | + }, |
| 139 | + } |
| 140 | + |
| 141 | + // --- Iterate through Phases --- |
| 142 | + for _, currentPhase := range phasesToTest { |
| 143 | + phaseTestName := fmt.Sprintf("Phase_%d", currentPhase) |
| 144 | + |
| 145 | + t.Run(phaseTestName, func(t *testing.T) { |
| 146 | + // --- Iterate through Filter Actions --- |
| 147 | + for _, fa := range filterActions { |
| 148 | + filterActionTestName := fa.name |
| 149 | + |
| 150 | + t.Run(filterActionTestName, func(t *testing.T) { |
| 151 | + waf := NewWAF() |
| 152 | + if err := waf.Rules.Add(rule); err != nil { |
| 153 | + t.Fatalf("Setup: Failed to add rule for %s/%s: %v", phaseTestName, filterActionTestName, err) |
| 154 | + } |
| 155 | + tx := waf.NewTransaction() |
| 156 | + |
| 157 | + var filterCalled bool |
| 158 | + testFilter := &RuleFilterWrapper{ |
| 159 | + shouldIgnore: func(r types.RuleMetadata) bool { |
| 160 | + filterCalled = true |
| 161 | + return fa.filterShouldIgnore |
| 162 | + }, |
| 163 | + } |
| 164 | + tx.UseRuleFilter(testFilter) |
| 165 | + |
| 166 | + interrupted := waf.Rules.Eval(currentPhase, tx) |
| 167 | + if interrupted != fa.expectInterruption { |
| 168 | + t.Fatalf("[%s/%s] ShouldFilter is '%t', expecting interruption '%t', but Eval returned '%t'", |
| 169 | + phaseTestName, filterActionTestName, fa.filterShouldIgnore, fa.expectInterruption, interrupted, |
| 170 | + ) |
| 171 | + } |
| 172 | + if !filterCalled { |
| 173 | + t.Fatalf("[%s/%s] ShouldIgnore was *not* called", phaseTestName, filterActionTestName) |
| 174 | + } |
| 175 | + }) |
| 176 | + } |
| 177 | + }) |
| 178 | + } |
| 179 | +} |
0 commit comments