Add unit tests for websocket Hub.checkOrigin function
Test coverage for CORS origin validation including: - No origin header (non-browser clients) - Same-origin requests with/without proxy headers - Wildcard and explicit allowed origins - Private network fallback (192.168.x, 10.x, localhost, .local, .lan) - Public IP/domain rejection - X-Forwarded-Proto/Host/Scheme header handling - WebSocket protocol normalization (ws->http, wss->https) Coverage: 37.3% → 48.6%
This commit is contained in:
parent
534d6c78a4
commit
8b3d8d3e86
1 changed files with 193 additions and 0 deletions
|
|
@ -2,6 +2,7 @@ package websocket
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -673,3 +674,195 @@ func TestMessage_Fields(t *testing.T) {
|
||||||
t.Errorf("Timestamp = %v, want 2024-01-01T00:00:00Z", msg.Timestamp)
|
t.Errorf("Timestamp = %v, want 2024-01-01T00:00:00Z", msg.Timestamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHub_CheckOrigin(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
origin string
|
||||||
|
host string
|
||||||
|
allowedOrigins []string
|
||||||
|
forwardedProto string
|
||||||
|
forwardedHost string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
// No origin header - always allowed for non-browser clients
|
||||||
|
{
|
||||||
|
name: "no origin header",
|
||||||
|
origin: "",
|
||||||
|
host: "localhost:8080",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Same-origin requests
|
||||||
|
{
|
||||||
|
name: "same origin http",
|
||||||
|
origin: "http://localhost:8080",
|
||||||
|
host: "localhost:8080",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "same origin with forwarded proto https",
|
||||||
|
origin: "https://example.com",
|
||||||
|
host: "example.com",
|
||||||
|
forwardedProto: "https",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "same origin with forwarded host",
|
||||||
|
origin: "http://proxy.example.com",
|
||||||
|
host: "backend:8080",
|
||||||
|
forwardedHost: "proxy.example.com",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Wildcard allowed origins
|
||||||
|
{
|
||||||
|
name: "wildcard allows any origin",
|
||||||
|
origin: "https://evil.com",
|
||||||
|
host: "localhost:8080",
|
||||||
|
allowedOrigins: []string{"*"},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Explicit allowed origins
|
||||||
|
{
|
||||||
|
name: "explicit allowed origin matches",
|
||||||
|
origin: "https://app.example.com",
|
||||||
|
host: "localhost:8080",
|
||||||
|
allowedOrigins: []string{"https://app.example.com"},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "explicit allowed origin no match",
|
||||||
|
origin: "https://other.example.com",
|
||||||
|
host: "localhost:8080",
|
||||||
|
allowedOrigins: []string{"https://app.example.com"},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple allowed origins - match second",
|
||||||
|
origin: "https://second.example.com",
|
||||||
|
host: "localhost:8080",
|
||||||
|
allowedOrigins: []string{"https://first.example.com", "https://second.example.com"},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Private network fallback (no allowed origins configured)
|
||||||
|
{
|
||||||
|
name: "private IP 192.168.x.x allowed when no origins configured",
|
||||||
|
origin: "http://192.168.1.100:3000",
|
||||||
|
host: "localhost:8080",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "private IP 10.x.x.x allowed when no origins configured",
|
||||||
|
origin: "http://10.0.0.50:3000",
|
||||||
|
host: "localhost:8080",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "localhost allowed when no origins configured",
|
||||||
|
origin: "http://localhost:3000",
|
||||||
|
host: "localhost:8080",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "127.0.0.1 allowed when no origins configured",
|
||||||
|
origin: "http://127.0.0.1:3000",
|
||||||
|
host: "localhost:8080",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: ".local domain allowed when no origins configured",
|
||||||
|
origin: "http://myserver.local:3000",
|
||||||
|
host: "localhost:8080",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: ".lan domain allowed when no origins configured",
|
||||||
|
origin: "http://myserver.lan:3000",
|
||||||
|
host: "localhost:8080",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "public IP rejected when no origins configured",
|
||||||
|
origin: "http://8.8.8.8:3000",
|
||||||
|
host: "localhost:8080",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "public domain rejected when no origins configured",
|
||||||
|
origin: "https://evil.example.com",
|
||||||
|
host: "localhost:8080",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// HTTPS origin stripping
|
||||||
|
{
|
||||||
|
name: "https origin with private IP",
|
||||||
|
origin: "https://192.168.1.50:443",
|
||||||
|
host: "localhost:8080",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Forwarded proto normalization
|
||||||
|
{
|
||||||
|
name: "wss forwarded proto normalized to https",
|
||||||
|
origin: "https://example.com",
|
||||||
|
host: "example.com",
|
||||||
|
forwardedProto: "wss",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ws forwarded proto normalized to http",
|
||||||
|
origin: "http://example.com",
|
||||||
|
host: "example.com",
|
||||||
|
forwardedProto: "ws",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
hub := NewHub(nil)
|
||||||
|
if len(tc.allowedOrigins) > 0 {
|
||||||
|
hub.SetAllowedOrigins(tc.allowedOrigins)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &http.Request{
|
||||||
|
Host: tc.host,
|
||||||
|
Header: make(http.Header),
|
||||||
|
}
|
||||||
|
if tc.origin != "" {
|
||||||
|
req.Header.Set("Origin", tc.origin)
|
||||||
|
}
|
||||||
|
if tc.forwardedProto != "" {
|
||||||
|
req.Header.Set("X-Forwarded-Proto", tc.forwardedProto)
|
||||||
|
}
|
||||||
|
if tc.forwardedHost != "" {
|
||||||
|
req.Header.Set("X-Forwarded-Host", tc.forwardedHost)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := hub.checkOrigin(req)
|
||||||
|
if result != tc.expected {
|
||||||
|
t.Errorf("checkOrigin() = %v, want %v", result, tc.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHub_CheckOrigin_XForwardedScheme(t *testing.T) {
|
||||||
|
hub := NewHub(nil)
|
||||||
|
|
||||||
|
req := &http.Request{
|
||||||
|
Host: "example.com",
|
||||||
|
Header: make(http.Header),
|
||||||
|
}
|
||||||
|
req.Header.Set("Origin", "https://example.com")
|
||||||
|
req.Header.Set("X-Forwarded-Scheme", "https")
|
||||||
|
|
||||||
|
result := hub.checkOrigin(req)
|
||||||
|
if !result {
|
||||||
|
t.Error("checkOrigin should allow same-origin with X-Forwarded-Scheme")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue