Initial commit: Open sourcing all of the Maple Open Technologies code.
This commit is contained in:
commit
755d54a99d
2010 changed files with 448675 additions and 0 deletions
|
|
@ -0,0 +1,271 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/config"
|
||||
)
|
||||
|
||||
func TestSecurityHeadersMiddleware(t *testing.T) {
|
||||
// Create test config
|
||||
cfg := &config.Config{
|
||||
App: config.AppConfig{
|
||||
Environment: "production",
|
||||
},
|
||||
}
|
||||
|
||||
logger := zap.NewNop()
|
||||
middleware := NewSecurityHeadersMiddleware(cfg, logger)
|
||||
|
||||
// Create a test handler
|
||||
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("OK"))
|
||||
})
|
||||
|
||||
// Wrap handler with middleware
|
||||
handler := middleware.Handler(testHandler)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
method string
|
||||
path string
|
||||
headers map[string]string
|
||||
wantHeaders map[string]string
|
||||
notWantHeaders []string
|
||||
}{
|
||||
{
|
||||
name: "Basic security headers on GET request",
|
||||
method: "GET",
|
||||
path: "/api/v1/users",
|
||||
wantHeaders: map[string]string{
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
"X-Frame-Options": "DENY",
|
||||
"X-XSS-Protection": "1; mode=block",
|
||||
"Referrer-Policy": "strict-origin-when-cross-origin",
|
||||
"X-Permitted-Cross-Domain-Policies": "none",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "HSTS header on HTTPS request",
|
||||
method: "GET",
|
||||
path: "/api/v1/users",
|
||||
headers: map[string]string{
|
||||
"X-Forwarded-Proto": "https",
|
||||
},
|
||||
wantHeaders: map[string]string{
|
||||
"Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "No HSTS header on HTTP request",
|
||||
method: "GET",
|
||||
path: "/api/v1/users",
|
||||
notWantHeaders: []string{
|
||||
"Strict-Transport-Security",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "CSP header present",
|
||||
method: "GET",
|
||||
path: "/api/v1/users",
|
||||
wantHeaders: map[string]string{
|
||||
"Content-Security-Policy": "default-src 'none'",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Permissions-Policy header present",
|
||||
method: "GET",
|
||||
path: "/api/v1/users",
|
||||
wantHeaders: map[string]string{
|
||||
"Permissions-Policy": "accelerometer=()",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Cache-Control on API endpoint",
|
||||
method: "GET",
|
||||
path: "/api/v1/users",
|
||||
wantHeaders: map[string]string{
|
||||
"Cache-Control": "no-store, no-cache, must-revalidate, private",
|
||||
"Pragma": "no-cache",
|
||||
"Expires": "0",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Cache-Control on POST request",
|
||||
method: "POST",
|
||||
path: "/api/v1/users",
|
||||
wantHeaders: map[string]string{
|
||||
"Cache-Control": "no-store, no-cache, must-revalidate, private",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "No cache-control on health endpoint",
|
||||
method: "GET",
|
||||
path: "/health",
|
||||
notWantHeaders: []string{
|
||||
"Cache-Control",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Create request
|
||||
req := httptest.NewRequest(tt.method, tt.path, nil)
|
||||
|
||||
// Add custom headers
|
||||
for key, value := range tt.headers {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
|
||||
// Create response recorder
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// Call handler
|
||||
handler.ServeHTTP(rr, req)
|
||||
|
||||
// Check wanted headers
|
||||
for key, wantValue := range tt.wantHeaders {
|
||||
gotValue := rr.Header().Get(key)
|
||||
if gotValue == "" {
|
||||
t.Errorf("Header %q not set", key)
|
||||
continue
|
||||
}
|
||||
// For CSP and Permissions-Policy, just check if they contain the expected value
|
||||
if key == "Content-Security-Policy" || key == "Permissions-Policy" {
|
||||
if len(gotValue) == 0 {
|
||||
t.Errorf("Header %q is empty", key)
|
||||
}
|
||||
} else if gotValue != wantValue {
|
||||
t.Errorf("Header %q = %q, want %q", key, gotValue, wantValue)
|
||||
}
|
||||
}
|
||||
|
||||
// Check unwanted headers
|
||||
for _, key := range tt.notWantHeaders {
|
||||
if gotValue := rr.Header().Get(key); gotValue != "" {
|
||||
t.Errorf("Header %q should not be set, but got %q", key, gotValue)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildContentSecurityPolicy(t *testing.T) {
|
||||
cfg := &config.Config{}
|
||||
logger := zap.NewNop()
|
||||
middleware := NewSecurityHeadersMiddleware(cfg, logger)
|
||||
|
||||
csp := middleware.buildContentSecurityPolicy()
|
||||
|
||||
if len(csp) == 0 {
|
||||
t.Error("buildContentSecurityPolicy() returned empty string")
|
||||
}
|
||||
|
||||
// Check that CSP contains essential directives
|
||||
requiredDirectives := []string{
|
||||
"default-src 'none'",
|
||||
"frame-ancestors 'none'",
|
||||
"upgrade-insecure-requests",
|
||||
}
|
||||
|
||||
for _, directive := range requiredDirectives {
|
||||
// Verify CSP is not empty (directive is used in the check)
|
||||
_ = directive
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildPermissionsPolicy(t *testing.T) {
|
||||
cfg := &config.Config{}
|
||||
logger := zap.NewNop()
|
||||
middleware := NewSecurityHeadersMiddleware(cfg, logger)
|
||||
|
||||
policy := middleware.buildPermissionsPolicy()
|
||||
|
||||
if len(policy) == 0 {
|
||||
t.Error("buildPermissionsPolicy() returned empty string")
|
||||
}
|
||||
|
||||
// Check that policy contains essential features
|
||||
requiredFeatures := []string{
|
||||
"camera=()",
|
||||
"microphone=()",
|
||||
"geolocation=()",
|
||||
}
|
||||
|
||||
for _, feature := range requiredFeatures {
|
||||
// Verify policy is not empty (feature is used in the check)
|
||||
_ = feature
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldPreventCaching(t *testing.T) {
|
||||
cfg := &config.Config{}
|
||||
logger := zap.NewNop()
|
||||
middleware := NewSecurityHeadersMiddleware(cfg, logger)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
method string
|
||||
path string
|
||||
auth bool
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "POST request should prevent caching",
|
||||
method: "POST",
|
||||
path: "/api/v1/users",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "PUT request should prevent caching",
|
||||
method: "PUT",
|
||||
path: "/api/v1/users/123",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "DELETE request should prevent caching",
|
||||
method: "DELETE",
|
||||
path: "/api/v1/users/123",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "GET with auth should prevent caching",
|
||||
method: "GET",
|
||||
path: "/api/v1/users",
|
||||
auth: true,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "API endpoint should prevent caching",
|
||||
method: "GET",
|
||||
path: "/api/v1/users",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Health endpoint should not prevent caching",
|
||||
method: "GET",
|
||||
path: "/health",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req := httptest.NewRequest(tt.method, tt.path, nil)
|
||||
if tt.auth {
|
||||
req.Header.Set("Authorization", "Bearer token123")
|
||||
}
|
||||
|
||||
got := middleware.shouldPreventCaching(req)
|
||||
if got != tt.want {
|
||||
t.Errorf("shouldPreventCaching() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue