| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153 |
- //go:build unit
- package middleware
- import (
- "net/http/httptest"
- "strings"
- "testing"
- "github.com/Wei-Shaw/sub2api/internal/config"
- "github.com/Wei-Shaw/sub2api/internal/pkg/ip"
- "github.com/Wei-Shaw/sub2api/internal/service"
- "github.com/gin-gonic/gin"
- "github.com/stretchr/testify/require"
- )
- func TestSessionBindingContextFollowsForwardedIPSwitch(t *testing.T) {
- gin.SetMode(gin.TestMode)
- for _, tc := range []struct {
- name string
- trustForwarded bool
- trustedProxies []string
- wantIP string
- }{
- {name: "enabled switch takes over raw headers", trustForwarded: true, wantIP: "1.2.3.4"},
- {name: "disabled switch ignores untrusted headers", trustForwarded: false, wantIP: "127.0.0.1"},
- {name: "disabled switch uses configured Gin proxy", trustForwarded: false, trustedProxies: []string{"127.0.0.1"}, wantIP: "1.2.3.4"},
- } {
- t.Run(tc.name, func(t *testing.T) {
- cfg := &config.Config{}
- cfg.SetTrustForwardedIPForAPIKeyACL(tc.trustForwarded)
- r := gin.New()
- require.NoError(t, r.SetTrustedProxies(tc.trustedProxies))
- r.Use(SessionBindingContext(cfg))
- r.GET("/t", func(c *gin.Context) {
- binding := service.SessionBindingFromContext(c.Request.Context())
- require.NotNil(t, binding)
- require.Equal(t, tc.wantIP, binding.IP)
- require.Equal(t, "test-agent", binding.UserAgent)
- require.Equal(t, tc.wantIP, SecurityClientIP(c))
- c.Status(200)
- })
- w := httptest.NewRecorder()
- req := httptest.NewRequest("GET", "/t", nil)
- req.RemoteAddr = "127.0.0.1:54321"
- req.Header.Set("X-Real-IP", "1.2.3.4")
- req.Header.Set("User-Agent", "test-agent")
- r.ServeHTTP(w, req)
- require.Equal(t, 200, w.Code)
- })
- }
- }
- func TestSessionBindingContextSnapshotsForwardedModeAndHeaders(t *testing.T) {
- gin.SetMode(gin.TestMode)
- cfg := &config.Config{}
- cfg.SetForwardedClientIPSettings(true, []string{"X-Initial-IP"})
- r := gin.New()
- require.NoError(t, r.SetTrustedProxies(nil))
- r.Use(SessionBindingContext(cfg))
- r.GET("/t", func(c *gin.Context) {
- binding := service.SessionBindingFromContext(c.Request.Context())
- require.NotNil(t, binding)
- require.Equal(t, "1.2.3.4", binding.IP)
- cfg.SetForwardedClientIPSettings(false, []string{"X-Changed-IP"})
- require.Equal(t, "1.2.3.4", ip.GetSecurityClientIP(c, false))
- c.Status(200)
- })
- w := httptest.NewRecorder()
- req := httptest.NewRequest("GET", "/t", nil)
- req.RemoteAddr = "9.9.9.9:12345"
- req.Header.Set("X-Initial-IP", "1.2.3.4")
- req.Header.Set("X-Changed-IP", "4.4.4.4")
- req.Header.Set("X-Real-IP", "8.8.8.8")
- r.ServeHTTP(w, req)
- require.Equal(t, 200, w.Code)
- runtimeSettings := cfg.ForwardedClientIPSettings()
- require.False(t, runtimeSettings.TrustForwardedIP)
- require.Equal(t, []string{"X-Changed-IP"}, runtimeSettings.Headers)
- }
- func TestSessionBindingContextBoundsPersistedUserAgent(t *testing.T) {
- cfg := &config.Config{}
- r := gin.New()
- r.Use(SessionBindingContext(cfg))
- r.GET("/t", func(c *gin.Context) {
- binding := service.SessionBindingFromContext(c.Request.Context())
- require.Len(t, binding.UserAgent, maxPersistentUserAgentBytes)
- require.Equal(t, binding.UserAgent, c.Request.UserAgent())
- c.Status(200)
- })
- w := httptest.NewRecorder()
- req := httptest.NewRequest("GET", "/t", nil)
- req.Header.Set("User-Agent", strings.Repeat("u", 2048))
- r.ServeHTTP(w, req)
- require.Equal(t, 200, w.Code)
- }
- // 未经过 SessionBindingContext 注入时(异常挂载顺序/单测直调),回退 trusted_proxies 链,
- // 等价于开关关闭时的历史行为。
- func TestSecurityClientIPFallsBackWithoutInjectedBinding(t *testing.T) {
- gin.SetMode(gin.TestMode)
- r := gin.New()
- require.NoError(t, r.SetTrustedProxies(nil))
- r.GET("/t", func(c *gin.Context) {
- c.String(200, SecurityClientIP(c))
- })
- w := httptest.NewRecorder()
- req := httptest.NewRequest("GET", "/t", nil)
- req.RemoteAddr = "9.9.9.9:12345"
- req.Header.Set("X-Real-IP", "1.2.3.4")
- r.ServeHTTP(w, req)
- require.Equal(t, 200, w.Code)
- require.Equal(t, "9.9.9.9", w.Body.String())
- }
- func TestRequestSessionBindingPrefersInjectedBinding(t *testing.T) {
- gin.SetMode(gin.TestMode)
- cfg := &config.Config{}
- cfg.SetTrustForwardedIPForAPIKeyACL(true)
- r := gin.New()
- require.NoError(t, r.SetTrustedProxies([]string{"127.0.0.1"}))
- r.Use(SessionBindingContext(cfg))
- r.GET("/t", func(c *gin.Context) {
- issued := &service.SessionBinding{IP: "1.2.3.4", UserAgent: "test-agent"}
- require.Equal(t, issued.Hash(), requestSessionBinding(c).Hash())
- c.Status(200)
- })
- w := httptest.NewRecorder()
- req := httptest.NewRequest("GET", "/t", nil)
- req.RemoteAddr = "127.0.0.1:54321"
- req.Header.Set("X-Real-IP", "1.2.3.4")
- req.Header.Set("User-Agent", "test-agent")
- r.ServeHTTP(w, req)
- require.Equal(t, 200, w.Code)
- }
|