mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-09-27 05:50:09 +00:00
299 lines
7.4 KiB
Go
299 lines
7.4 KiB
Go
// GoToSocial
|
|
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package middleware_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
|
"github.com/superseriousbusiness/gotosocial/internal/db/bundb"
|
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
|
"github.com/superseriousbusiness/gotosocial/internal/headerfilter"
|
|
"github.com/superseriousbusiness/gotosocial/internal/id"
|
|
"github.com/superseriousbusiness/gotosocial/internal/middleware"
|
|
"github.com/superseriousbusiness/gotosocial/internal/state"
|
|
"github.com/superseriousbusiness/gotosocial/testrig"
|
|
)
|
|
|
|
func TestHeaderFilter(t *testing.T) {
|
|
testrig.InitTestLog()
|
|
testrig.InitTestConfig()
|
|
|
|
for _, test := range []struct {
|
|
mode string
|
|
allow []filter
|
|
block []filter
|
|
input http.Header
|
|
expect bool
|
|
}{
|
|
{
|
|
// Allow mode with expected 200 OK.
|
|
mode: config.RequestHeaderFilterModeAllow,
|
|
allow: []filter{
|
|
{"User-Agent", ".*Firefox.*"},
|
|
},
|
|
block: []filter{},
|
|
input: http.Header{
|
|
"User-Agent": []string{"Firefox v169.42; Extra Tracking Info"},
|
|
},
|
|
expect: true,
|
|
},
|
|
{
|
|
// Allow mode with expected 403 Forbidden.
|
|
mode: config.RequestHeaderFilterModeAllow,
|
|
allow: []filter{
|
|
{"User-Agent", ".*Firefox.*"},
|
|
},
|
|
block: []filter{},
|
|
input: http.Header{
|
|
"User-Agent": []string{"Chromium v169.42; Extra Tracking Info"},
|
|
},
|
|
expect: false,
|
|
},
|
|
{
|
|
// Allow mode with too long header value expecting 403 Forbidden.
|
|
mode: config.RequestHeaderFilterModeAllow,
|
|
allow: []filter{
|
|
{"User-Agent", ".*"},
|
|
},
|
|
block: []filter{},
|
|
input: http.Header{
|
|
"User-Agent": []string{func() string {
|
|
var buf strings.Builder
|
|
for i := 0; i < headerfilter.MaxHeaderValue+1; i++ {
|
|
buf.WriteByte(' ')
|
|
}
|
|
return buf.String()
|
|
}()},
|
|
},
|
|
expect: false,
|
|
},
|
|
{
|
|
// Allow mode with explicit block expecting 403 Forbidden.
|
|
mode: config.RequestHeaderFilterModeAllow,
|
|
allow: []filter{
|
|
{"User-Agent", ".*Firefox.*"},
|
|
},
|
|
block: []filter{
|
|
{"User-Agent", ".*Firefox v169\\.42.*"},
|
|
},
|
|
input: http.Header{
|
|
"User-Agent": []string{"Firefox v169.42; Extra Tracking Info"},
|
|
},
|
|
expect: false,
|
|
},
|
|
{
|
|
// Block mode with an expected 403 Forbidden.
|
|
mode: config.RequestHeaderFilterModeBlock,
|
|
allow: []filter{},
|
|
block: []filter{
|
|
{"User-Agent", ".*Firefox.*"},
|
|
},
|
|
input: http.Header{
|
|
"User-Agent": []string{"Firefox v169.42; Extra Tracking Info"},
|
|
},
|
|
expect: false,
|
|
},
|
|
{
|
|
// Block mode with an expected 200 OK.
|
|
mode: config.RequestHeaderFilterModeBlock,
|
|
allow: []filter{},
|
|
block: []filter{
|
|
{"User-Agent", ".*Firefox.*"},
|
|
},
|
|
input: http.Header{
|
|
"User-Agent": []string{"Chromium v169.42; Extra Tracking Info"},
|
|
},
|
|
expect: true,
|
|
},
|
|
{
|
|
// Block mode with too long header value expecting 403 Forbidden.
|
|
mode: config.RequestHeaderFilterModeBlock,
|
|
allow: []filter{},
|
|
block: []filter{
|
|
{"User-Agent", "none"},
|
|
},
|
|
input: http.Header{
|
|
"User-Agent": []string{func() string {
|
|
var buf strings.Builder
|
|
for i := 0; i < headerfilter.MaxHeaderValue+1; i++ {
|
|
buf.WriteByte(' ')
|
|
}
|
|
return buf.String()
|
|
}()},
|
|
},
|
|
expect: false,
|
|
},
|
|
{
|
|
// Block mode with explicit allow expecting 200 OK.
|
|
mode: config.RequestHeaderFilterModeBlock,
|
|
allow: []filter{
|
|
{"User-Agent", ".*Firefox.*"},
|
|
},
|
|
block: []filter{
|
|
{"User-Agent", ".*Firefox v169\\.42.*"},
|
|
},
|
|
input: http.Header{
|
|
"User-Agent": []string{"Firefox v169.42; Extra Tracking Info"},
|
|
},
|
|
expect: true,
|
|
},
|
|
{
|
|
// Disabled mode with an expected 200 OK.
|
|
mode: config.RequestHeaderFilterModeDisabled,
|
|
allow: []filter{
|
|
{"Key1", "only-this"},
|
|
{"Key2", "only-this"},
|
|
{"Key3", "only-this"},
|
|
},
|
|
block: []filter{
|
|
{"Key1", "Value"},
|
|
{"Key2", "Value"},
|
|
{"Key3", "Value"},
|
|
},
|
|
input: http.Header{
|
|
"Key1": []string{"Value"},
|
|
"Key2": []string{"Value"},
|
|
"Key3": []string{"Value"},
|
|
},
|
|
expect: true,
|
|
},
|
|
} {
|
|
// Generate a unique name for this test case.
|
|
name := fmt.Sprintf("%s allow=%v block=%v => expect=%v",
|
|
test.mode,
|
|
test.allow,
|
|
test.block,
|
|
test.expect,
|
|
)
|
|
|
|
// Update header filter mode to test case.
|
|
config.SetAdvancedHeaderFilterMode(test.mode)
|
|
|
|
// Run this particular test case.
|
|
ok := t.Run(name, func(t *testing.T) {
|
|
testHeaderFilter(t,
|
|
test.allow,
|
|
test.block,
|
|
test.input,
|
|
test.expect,
|
|
)
|
|
})
|
|
|
|
if !ok {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func testHeaderFilter(t *testing.T, allow, block []filter, input http.Header, expect bool) {
|
|
var err error
|
|
|
|
// Create test context with cancel.
|
|
ctx := context.Background()
|
|
ctx, cncl := context.WithCancel(ctx)
|
|
defer cncl()
|
|
|
|
// Initialize caches.
|
|
var state state.State
|
|
state.Caches.Init()
|
|
|
|
// Create new database instance with test config.
|
|
state.DB, err = bundb.NewBunDBService(ctx, &state)
|
|
if err != nil {
|
|
t.Fatalf("error opening database: %v", err)
|
|
}
|
|
|
|
// Insert all allow filters into DB.
|
|
for _, filter := range allow {
|
|
filter := >smodel.HeaderFilter{
|
|
ID: id.NewULID(),
|
|
Header: filter.header,
|
|
Regex: filter.regex,
|
|
AuthorID: "admin-id",
|
|
Author: nil,
|
|
}
|
|
|
|
if err := state.DB.PutAllowHeaderFilter(ctx, filter); err != nil {
|
|
t.Fatalf("error inserting allow filter into database: %v", err)
|
|
}
|
|
}
|
|
|
|
// Insert all block filters into DB.
|
|
for _, filter := range block {
|
|
filter := >smodel.HeaderFilter{
|
|
ID: id.NewULID(),
|
|
Header: filter.header,
|
|
Regex: filter.regex,
|
|
AuthorID: "admin-id",
|
|
Author: nil,
|
|
}
|
|
|
|
if err := state.DB.PutBlockHeaderFilter(ctx, filter); err != nil {
|
|
t.Fatalf("error inserting block filter into database: %v", err)
|
|
}
|
|
}
|
|
|
|
// Gin test http engine
|
|
// (used for ctx init).
|
|
e := gin.New()
|
|
|
|
// Create new filter middleware to test against.
|
|
middleware := middleware.HeaderFilter(&state)
|
|
e.Use(middleware)
|
|
|
|
// Set the empty gin handler (always returns okay).
|
|
e.Handle("GET", "/", func(ctx *gin.Context) { ctx.Status(200) })
|
|
|
|
// Prepare a gin test context.
|
|
r := httptest.NewRequest("GET", "/", nil)
|
|
rw := httptest.NewRecorder()
|
|
|
|
// Set input headers.
|
|
r.Header = input
|
|
|
|
// Pass req through
|
|
// engine handler.
|
|
e.ServeHTTP(rw, r)
|
|
|
|
// Get http result.
|
|
res := rw.Result()
|
|
|
|
switch {
|
|
case expect && res.StatusCode != http.StatusOK:
|
|
t.Errorf("unexpected response (should allow): %s", res.Status)
|
|
|
|
case !expect && res.StatusCode != http.StatusForbidden:
|
|
t.Errorf("unexpected response (should block): %s", res.Status)
|
|
}
|
|
}
|
|
|
|
type filter struct {
|
|
header string
|
|
regex string
|
|
}
|
|
|
|
func (hf *filter) String() string {
|
|
return fmt.Sprintf("%s=%q", hf.header, hf.regex)
|
|
}
|