Files
Charon/backend/internal/api/handlers/certificate_handler_security_test.go
GitHub Actions 2c1cf5f0ac feat: Implement SSL Provider selection feature with tests and documentation
- Added functionality to select SSL Provider (Auto, Let's Encrypt, ZeroSSL) in the Caddy Manager.
- Updated the ApplyConfig method to handle different SSL provider settings and staging flags.
- Created unit tests for various SSL provider scenarios, ensuring correct behavior and backward compatibility.
- Enhanced frontend System Settings page to include SSL Provider dropdown with appropriate options and descriptions.
- Updated documentation to reflect new SSL Provider feature and its usage.
- Added QA report detailing testing outcomes and security verification for the SSL Provider implementation.
2025-12-06 20:59:34 +00:00

209 lines
6.6 KiB
Go

package handlers
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"github.com/Wikid82/charon/backend/internal/models"
"github.com/Wikid82/charon/backend/internal/services"
)
// TestCertificateHandler_Delete_RequiresAuth tests that delete requires authentication
func TestCertificateHandler_Delete_RequiresAuth(t *testing.T) {
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
if err != nil {
t.Fatalf("failed to open db: %v", err)
}
if err := db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}); err != nil {
t.Fatalf("failed to migrate: %v", err)
}
gin.SetMode(gin.TestMode)
r := gin.New()
// Add a middleware that rejects all unauthenticated requests
r.Use(func(c *gin.Context) {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
})
svc := services.NewCertificateService("/tmp", db)
h := NewCertificateHandler(svc, nil, nil)
r.DELETE("/api/certificates/:id", h.Delete)
req := httptest.NewRequest(http.MethodDelete, "/api/certificates/1", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusUnauthorized {
t.Fatalf("expected 401 Unauthorized without auth, got %d", w.Code)
}
}
// TestCertificateHandler_List_RequiresAuth tests that list requires authentication
func TestCertificateHandler_List_RequiresAuth(t *testing.T) {
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
if err != nil {
t.Fatalf("failed to open db: %v", err)
}
if err := db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}); err != nil {
t.Fatalf("failed to migrate: %v", err)
}
gin.SetMode(gin.TestMode)
r := gin.New()
// Add a middleware that rejects all unauthenticated requests
r.Use(func(c *gin.Context) {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
})
svc := services.NewCertificateService("/tmp", db)
h := NewCertificateHandler(svc, nil, nil)
r.GET("/api/certificates", h.List)
req := httptest.NewRequest(http.MethodGet, "/api/certificates", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusUnauthorized {
t.Fatalf("expected 401 Unauthorized without auth, got %d", w.Code)
}
}
// TestCertificateHandler_Upload_RequiresAuth tests that upload requires authentication
func TestCertificateHandler_Upload_RequiresAuth(t *testing.T) {
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
if err != nil {
t.Fatalf("failed to open db: %v", err)
}
if err := db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}); err != nil {
t.Fatalf("failed to migrate: %v", err)
}
gin.SetMode(gin.TestMode)
r := gin.New()
// Add a middleware that rejects all unauthenticated requests
r.Use(func(c *gin.Context) {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
})
svc := services.NewCertificateService("/tmp", db)
h := NewCertificateHandler(svc, nil, nil)
r.POST("/api/certificates", h.Upload)
req := httptest.NewRequest(http.MethodPost, "/api/certificates", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusUnauthorized {
t.Fatalf("expected 401 Unauthorized without auth, got %d", w.Code)
}
}
// TestCertificateHandler_Delete_DiskSpaceCheck tests the disk space check before backup
func TestCertificateHandler_Delete_DiskSpaceCheck(t *testing.T) {
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
if err != nil {
t.Fatalf("failed to open db: %v", err)
}
if err := db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}); err != nil {
t.Fatalf("failed to migrate: %v", err)
}
// Create a certificate
cert := models.SSLCertificate{
UUID: "test-cert",
Name: "test",
Provider: "custom",
Domains: "test.com",
}
if err := db.Create(&cert).Error; err != nil {
t.Fatalf("failed to create cert: %v", err)
}
gin.SetMode(gin.TestMode)
r := gin.New()
r.Use(mockAuthMiddleware())
svc := services.NewCertificateService("/tmp", db)
// Mock backup service that reports low disk space
mockBackup := &mockBackupService{
availableSpaceFunc: func() (int64, error) {
return 50 * 1024 * 1024, nil // 50MB (less than 100MB required)
},
}
h := NewCertificateHandler(svc, mockBackup, nil)
r.DELETE("/api/certificates/:id", h.Delete)
req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/api/certificates/%d", cert.ID), nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusInsufficientStorage {
t.Fatalf("expected 507 Insufficient Storage with low disk space, got %d", w.Code)
}
}
// TestCertificateHandler_Delete_NotificationRateLimiting tests rate limiting
func TestCertificateHandler_Delete_NotificationRateLimiting(t *testing.T) {
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
if err != nil {
t.Fatalf("failed to open db: %v", err)
}
if err := db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}); err != nil {
t.Fatalf("failed to migrate: %v", err)
}
// Create certificates
cert1 := models.SSLCertificate{UUID: "test-1", Name: "test1", Provider: "custom", Domains: "test1.com"}
cert2 := models.SSLCertificate{UUID: "test-2", Name: "test2", Provider: "custom", Domains: "test2.com"}
if err := db.Create(&cert1).Error; err != nil {
t.Fatalf("failed to create cert1: %v", err)
}
if err := db.Create(&cert2).Error; err != nil {
t.Fatalf("failed to create cert2: %v", err)
}
gin.SetMode(gin.TestMode)
r := gin.New()
r.Use(mockAuthMiddleware())
svc := services.NewCertificateService("/tmp", db)
mockBackup := &mockBackupService{
createFunc: func() (string, error) {
return "backup.zip", nil
},
}
h := NewCertificateHandler(svc, mockBackup, nil)
r.DELETE("/api/certificates/:id", h.Delete)
// Delete first cert
req1 := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/api/certificates/%d", cert1.ID), nil)
w1 := httptest.NewRecorder()
r.ServeHTTP(w1, req1)
if w1.Code != http.StatusOK {
t.Fatalf("first delete failed: got %d", w1.Code)
}
// Delete second cert (different ID, should not be rate limited)
req2 := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/api/certificates/%d", cert2.ID), nil)
w2 := httptest.NewRecorder()
r.ServeHTTP(w2, req2)
if w2.Code != http.StatusOK {
t.Fatalf("second delete failed: got %d", w2.Code)
}
// The test passes if both deletions succeed
// Rate limiting is per-certificate ID, so different certs should not interfere
}