184 lines
6.2 KiB
Go
184 lines
6.2 KiB
Go
package botfilter
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
func TestRequiredHeaderCreatesTemporaryBan(t *testing.T) {
|
|
cfg := CreateConfig()
|
|
cfg.RequireUserAgent = true
|
|
cfg.RequireAccept = true
|
|
cfg.RequireHost = true
|
|
nextCalls := 0
|
|
handler, err := New(context.Background(), http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
|
|
nextCalls++
|
|
rw.WriteHeader(http.StatusOK)
|
|
}), cfg, "test")
|
|
if err != nil {
|
|
t.Fatalf("New() error = %v", err)
|
|
}
|
|
|
|
first := httptest.NewRequest(http.MethodGet, "http://wiki.example/content/article", nil)
|
|
first.RemoteAddr = "203.0.113.11:54321"
|
|
first.Header.Set("Accept", "text/html")
|
|
first.Header.Del("User-Agent")
|
|
firstResponse := httptest.NewRecorder()
|
|
handler.ServeHTTP(firstResponse, first)
|
|
if firstResponse.Code != http.StatusForbidden {
|
|
t.Fatalf("first response status = %d, want %d", firstResponse.Code, http.StatusForbidden)
|
|
}
|
|
if firstResponse.Header().Get("Retry-After") == "" {
|
|
t.Fatal("first response did not include Retry-After")
|
|
}
|
|
|
|
second := httptest.NewRequest(http.MethodGet, "http://wiki.example/", nil)
|
|
second.RemoteAddr = "203.0.113.11:54321"
|
|
second.Header.Set("Accept", "text/html")
|
|
second.Header.Set("User-Agent", "Mozilla/5.0 Chrome/120.0 AppleWebKit/537.36 Safari/537.36")
|
|
secondResponse := httptest.NewRecorder()
|
|
handler.ServeHTTP(secondResponse, second)
|
|
if secondResponse.Code != http.StatusForbidden {
|
|
t.Fatalf("second response status = %d, want cached ban", secondResponse.Code)
|
|
}
|
|
if nextCalls != 0 {
|
|
t.Fatalf("next handler calls = %d, want 0", nextCalls)
|
|
}
|
|
}
|
|
|
|
func TestWhitelistBypassesFilter(t *testing.T) {
|
|
cfg := CreateConfig()
|
|
cfg.RequireUserAgent = true
|
|
cfg.WhitelistCIDRs = []string{"192.168.0.0/16"}
|
|
handler, err := New(context.Background(), http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
|
|
rw.WriteHeader(http.StatusNoContent)
|
|
}), cfg, "test")
|
|
if err != nil {
|
|
t.Fatalf("New() error = %v", err)
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://wiki.example/", nil)
|
|
request.RemoteAddr = "192.168.30.25:1234"
|
|
request.Header.Del("User-Agent")
|
|
response := httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
if response.Code != http.StatusNoContent {
|
|
t.Fatalf("response status = %d, want whitelist to reach next handler", response.Code)
|
|
}
|
|
}
|
|
|
|
func TestEncodedScanPathBansBeforeUpstream(t *testing.T) {
|
|
cfg := CreateConfig()
|
|
cfg.RequireUserAgent = false
|
|
cfg.RequireAccept = false
|
|
nextCalls := 0
|
|
handler, err := New(context.Background(), http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
|
|
nextCalls++
|
|
rw.WriteHeader(http.StatusOK)
|
|
}), cfg, "test")
|
|
if err != nil {
|
|
t.Fatalf("New() error = %v", err)
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "http://wiki.example/foo/..%2F.env", nil)
|
|
request.RemoteAddr = "198.51.100.22:1234"
|
|
request.Header.Set("User-Agent", "Mozilla/5.0 Chrome/120.0 AppleWebKit/537.36 Safari/537.36")
|
|
response := httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
if response.Code != http.StatusForbidden {
|
|
t.Fatalf("response status = %d, want %d", response.Code, http.StatusForbidden)
|
|
}
|
|
if nextCalls != 0 {
|
|
t.Fatalf("next handler calls = %d, want 0", nextCalls)
|
|
}
|
|
}
|
|
|
|
func Test404ScoreBansOnSubsequentRequest(t *testing.T) {
|
|
cfg := CreateConfig()
|
|
cfg.RequireUserAgent = false
|
|
cfg.RequireAccept = false
|
|
cfg.RandomArticlePatterns = nil
|
|
cfg.EmptyUserAgentScore = 0
|
|
cfg.MissingAcceptScore = 0
|
|
cfg.ScoreThreshold = 40
|
|
nextCalls := 0
|
|
handler, err := New(context.Background(), http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
|
|
nextCalls++
|
|
rw.WriteHeader(http.StatusNotFound)
|
|
}), cfg, "test")
|
|
if err != nil {
|
|
t.Fatalf("New() error = %v", err)
|
|
}
|
|
|
|
first := httptest.NewRequest(http.MethodGet, "http://wiki.example/not-found", nil)
|
|
first.RemoteAddr = "198.51.100.23:1234"
|
|
first.Header.Set("User-Agent", "Mozilla/5.0 Chrome/120.0 AppleWebKit/537.36 Safari/537.36")
|
|
first.Header.Set("Accept", "text/html")
|
|
firstResponse := httptest.NewRecorder()
|
|
handler.ServeHTTP(firstResponse, first)
|
|
if firstResponse.Code != http.StatusNotFound {
|
|
t.Fatalf("first response status = %d, want 404", firstResponse.Code)
|
|
}
|
|
|
|
second := httptest.NewRequest(http.MethodGet, "http://wiki.example/another-miss", nil)
|
|
second.RemoteAddr = "198.51.100.23:1234"
|
|
second.Header.Set("User-Agent", "Mozilla/5.0 Chrome/120.0 AppleWebKit/537.36 Safari/537.36")
|
|
second.Header.Set("Accept", "text/html")
|
|
secondResponse := httptest.NewRecorder()
|
|
handler.ServeHTTP(secondResponse, second)
|
|
if secondResponse.Code != http.StatusForbidden {
|
|
t.Fatalf("second response status = %d, want cached ban after two 404s", secondResponse.Code)
|
|
}
|
|
if nextCalls != 1 {
|
|
t.Fatalf("next handler calls = %d, want 1", nextCalls)
|
|
}
|
|
}
|
|
|
|
func TestTrustedProxyHeaderSeparatesClients(t *testing.T) {
|
|
cfg := CreateConfig()
|
|
cfg.RequireUserAgent = true
|
|
cfg.ClientIPHeader = "X-Forwarded-For"
|
|
cfg.TrustedProxyCIDRs = []string{"127.0.0.0/8"}
|
|
nextCalls := 0
|
|
handler, err := New(context.Background(), http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
|
|
nextCalls++
|
|
rw.WriteHeader(http.StatusNoContent)
|
|
}), cfg, "test")
|
|
if err != nil {
|
|
t.Fatalf("New() error = %v", err)
|
|
}
|
|
|
|
bad := httptest.NewRequest(http.MethodGet, "http://wiki.example/", nil)
|
|
bad.RemoteAddr = "127.0.0.1:1234"
|
|
bad.Header.Set("X-Forwarded-For", "198.51.100.61")
|
|
bad.Header.Del("User-Agent")
|
|
badResponse := httptest.NewRecorder()
|
|
handler.ServeHTTP(badResponse, bad)
|
|
if badResponse.Code != http.StatusForbidden {
|
|
t.Fatalf("bad client status = %d, want 403", badResponse.Code)
|
|
}
|
|
|
|
good := httptest.NewRequest(http.MethodGet, "http://wiki.example/", nil)
|
|
good.RemoteAddr = "127.0.0.1:1234"
|
|
good.Header.Set("X-Forwarded-For", "198.51.100.62")
|
|
good.Header.Set("User-Agent", "Mozilla/5.0 Chrome/120.0 AppleWebKit/537.36 Safari/537.36")
|
|
goodResponse := httptest.NewRecorder()
|
|
handler.ServeHTTP(goodResponse, good)
|
|
if goodResponse.Code != http.StatusNoContent {
|
|
t.Fatalf("good client status = %d, want 204", goodResponse.Code)
|
|
}
|
|
if nextCalls != 1 {
|
|
t.Fatalf("next handler calls = %d, want 1", nextCalls)
|
|
}
|
|
}
|
|
|
|
func TestCompileConfigRejectsInvalidCIDR(t *testing.T) {
|
|
cfg := CreateConfig()
|
|
cfg.WhitelistCIDRs = []string{"not-a-cidr"}
|
|
if _, err := compileConfig(cfg); err == nil {
|
|
t.Fatal("compileConfig() error = nil, want invalid CIDR error")
|
|
}
|
|
}
|