mirror of
https://github.com/photoprism/photoprism.git
synced 2026-07-17 16:49:04 +00:00
174 lines
5.7 KiB
Go
174 lines
5.7 KiB
Go
package server
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/acme/autocert"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/photoprism/photoprism/internal/config"
|
|
"github.com/photoprism/photoprism/pkg/http/header"
|
|
)
|
|
|
|
// newProxyTestRouter creates a test router with trusted proxy settings applied.
|
|
func newProxyTestRouter(conf *config.Config) *gin.Engine {
|
|
r := gin.New()
|
|
configureTrustedProxySettings(r, conf)
|
|
|
|
r.GET("/ip", func(c *gin.Context) {
|
|
c.String(http.StatusOK, header.ClientIP(c))
|
|
})
|
|
|
|
return r
|
|
}
|
|
|
|
// requestClientIP performs a test request and returns the resolved client IP.
|
|
func requestClientIP(t *testing.T, router *gin.Engine, remoteAddr, forwardedFor string) string {
|
|
t.Helper()
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/ip", nil)
|
|
req.RemoteAddr = remoteAddr
|
|
|
|
if forwardedFor != "" {
|
|
req.Header.Set(header.XForwardedFor, forwardedFor)
|
|
}
|
|
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
|
|
return w.Body.String()
|
|
}
|
|
|
|
func TestConfigureTrustedProxySettings(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
t.Run("UsesForwardedIPForTrustedProxy", func(t *testing.T) {
|
|
conf := config.NewConfig(config.CliTestContext())
|
|
conf.Options().TrustedProxies = []string{header.CidrDockerInternal}
|
|
conf.Options().ProxyClientHeaders = []string{header.XForwardedFor}
|
|
|
|
router := newProxyTestRouter(conf)
|
|
ip := requestClientIP(t, router, "172.16.5.10:12345", "203.0.113.9")
|
|
|
|
assert.Equal(t, "203.0.113.9", ip)
|
|
})
|
|
t.Run("DisablesProxyTrustWhenNoTrustedProxiesConfigured", func(t *testing.T) {
|
|
conf := config.NewConfig(config.CliTestContext())
|
|
conf.Options().TrustedProxies = nil
|
|
conf.Options().ProxyClientHeaders = []string{header.XForwardedFor}
|
|
|
|
router := newProxyTestRouter(conf)
|
|
ip := requestClientIP(t, router, "198.51.100.10:12345", "10.0.0.123")
|
|
|
|
assert.Equal(t, "198.51.100.10", ip)
|
|
})
|
|
t.Run("FallsBackToDirectIPWhenTrustedProxyIsInvalid", func(t *testing.T) {
|
|
conf := config.NewConfig(config.CliTestContext())
|
|
conf.Options().TrustedProxies = []string{"invalid"}
|
|
conf.Options().ProxyClientHeaders = []string{header.XForwardedFor}
|
|
|
|
router := newProxyTestRouter(conf)
|
|
ip := requestClientIP(t, router, "198.51.100.11:12345", "10.0.0.124")
|
|
|
|
assert.Equal(t, "198.51.100.11", ip)
|
|
})
|
|
}
|
|
|
|
func TestNewHTTPServer(t *testing.T) {
|
|
t.Run("UsesConfiguredValues", func(t *testing.T) {
|
|
conf := config.NewConfig(config.CliTestContext())
|
|
conf.Options().HttpHeaderTimeout = 15 * time.Second
|
|
conf.Options().HttpHeaderBytes = 2048
|
|
conf.Options().HttpIdleTimeout = 2 * time.Minute
|
|
|
|
server := newHTTPServer(http.NewServeMux(), conf)
|
|
|
|
assert.Equal(t, 15*time.Second, server.ReadHeaderTimeout)
|
|
assert.Equal(t, 0*time.Second, server.ReadTimeout)
|
|
assert.Equal(t, 0*time.Second, server.WriteTimeout)
|
|
assert.Equal(t, 2*time.Minute, server.IdleTimeout)
|
|
assert.Equal(t, 2048, server.MaxHeaderBytes)
|
|
})
|
|
t.Run("UsesDefaultsWhenConfigIsNil", func(t *testing.T) {
|
|
server := newHTTPServer(http.NewServeMux(), nil)
|
|
|
|
assert.Equal(t, config.DefaultHttpHeaderTimeout, server.ReadHeaderTimeout)
|
|
assert.Equal(t, 0*time.Second, server.ReadTimeout)
|
|
assert.Equal(t, 0*time.Second, server.WriteTimeout)
|
|
assert.Equal(t, config.DefaultHttpIdleTimeout, server.IdleTimeout)
|
|
assert.Equal(t, config.DefaultHttpHeaderBytes, server.MaxHeaderBytes)
|
|
})
|
|
}
|
|
|
|
func TestCanonicalRedirectTarget(t *testing.T) {
|
|
t.Run("UsesConfiguredSiteHostInsteadOfRequestHost", func(t *testing.T) {
|
|
conf := config.NewConfig(config.CliTestContext())
|
|
conf.Options().SiteUrl = "https://photos.example.com:7443/library/"
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "http://evil.example.test/library/login?next=%2Flibrary", nil)
|
|
req.Host = "evil.example.test"
|
|
|
|
target := canonicalRedirectTarget(req, conf)
|
|
|
|
assert.Equal(t, "https://photos.example.com:7443/library/login?next=%2Flibrary", target)
|
|
})
|
|
t.Run("FallsBackToRequestHostWithoutConfig", func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "http://localhost:2342/library", nil)
|
|
req.Host = "localhost:2342"
|
|
|
|
target := canonicalRedirectTarget(req, nil)
|
|
|
|
assert.Equal(t, "https://localhost:2342/library", target)
|
|
})
|
|
}
|
|
|
|
func TestAutoTLSHTTPHandler(t *testing.T) {
|
|
t.Run("UsesCanonicalHostForFallbackRedirect", func(t *testing.T) {
|
|
conf := config.NewConfig(config.CliTestContext())
|
|
conf.Options().SiteUrl = "https://photos.example.com:7443/library/"
|
|
|
|
handler := (&autocert.Manager{
|
|
HostPolicy: autocert.HostWhitelist(conf.SiteDomain()),
|
|
}).HTTPHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
redirect(w, r, conf)
|
|
}))
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "http://evil.example.test/library/login?next=%2Flibrary", nil)
|
|
req.Host = "evil.example.test"
|
|
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, httpsRedirect, w.Code)
|
|
assert.Equal(t, "https://photos.example.com:7443/library/login?next=%2Flibrary", w.Header().Get(header.Location))
|
|
})
|
|
t.Run("HandlesAcmeChallengeWithoutFallbackRedirect", func(t *testing.T) {
|
|
conf := config.NewConfig(config.CliTestContext())
|
|
conf.Options().SiteUrl = "https://photos.example.com/"
|
|
|
|
fallbackCalled := false
|
|
handler := (&autocert.Manager{
|
|
HostPolicy: autocert.HostWhitelist(conf.SiteDomain()),
|
|
}).HTTPHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
fallbackCalled = true
|
|
redirect(w, r, conf)
|
|
}))
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "http://photos.example.com/.well-known/acme-challenge/test-token", nil)
|
|
req.Host = conf.SiteDomain()
|
|
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, req)
|
|
|
|
assert.False(t, fallbackCalled)
|
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
|
assert.Empty(t, w.Header().Get(header.Location))
|
|
})
|
|
}
|