- 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.
209 lines
6.6 KiB
Go
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
|
|
}
|