Implements a master toggle for security features across config, CLI, and application logic. This allows granular control over whether the advanced security middleware and protections are active globally.
193 lines
4.6 KiB
Go
193 lines
4.6 KiB
Go
package server
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"warpbox/lib/boxstore"
|
|
"warpbox/lib/models"
|
|
)
|
|
|
|
func (app *App) requireAPI(ctx *gin.Context) bool {
|
|
if app.config.APIEnabled {
|
|
return true
|
|
}
|
|
ctx.JSON(http.StatusForbidden, gin.H{"error": "API access is disabled"})
|
|
return false
|
|
}
|
|
|
|
func (app *App) requireGuestUploads(ctx *gin.Context) bool {
|
|
if app.config.GuestUploadsEnabled {
|
|
return true
|
|
}
|
|
ctx.JSON(http.StatusForbidden, gin.H{"error": "Guest uploads are disabled"})
|
|
return false
|
|
}
|
|
|
|
func (app *App) validateCreateBoxRequest(request *models.CreateBoxRequest) error {
|
|
if request == nil {
|
|
return nil
|
|
}
|
|
if !app.retentionAllowed(request.RetentionKey) {
|
|
return fmt.Errorf("Retention option is not allowed")
|
|
}
|
|
if !app.config.ZipDownloadsEnabled {
|
|
allowZip := false
|
|
request.AllowZip = &allowZip
|
|
}
|
|
if strings.TrimSpace(request.RetentionKey) == boxstore.OneTimeDownloadRetentionKey && !app.config.OneTimeDownloadsEnabled {
|
|
return fmt.Errorf("One-time downloads are disabled")
|
|
}
|
|
|
|
totalSize := int64(0)
|
|
for _, file := range request.Files {
|
|
if err := app.validateFileSize(file.Size); err != nil {
|
|
return err
|
|
}
|
|
totalSize += file.Size
|
|
}
|
|
return app.validateBoxSize(totalSize)
|
|
}
|
|
|
|
func (app *App) validateIncomingFile(boxID string, size int64) error {
|
|
if err := app.validateFileSize(size); err != nil {
|
|
return err
|
|
}
|
|
if app.config.GlobalMaxBoxSizeBytes <= 0 {
|
|
return nil
|
|
}
|
|
|
|
files, err := boxstore.ListFiles(boxID)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
totalSize := size
|
|
for _, file := range files {
|
|
totalSize += file.Size
|
|
}
|
|
return app.validateBoxSize(totalSize)
|
|
}
|
|
|
|
func (app *App) validateManifestFileUpload(boxID string, fileID string, size int64) error {
|
|
if err := app.validateFileSize(size); err != nil {
|
|
return err
|
|
}
|
|
|
|
manifest, err := boxstore.ReadManifest(boxID)
|
|
if err != nil {
|
|
return app.validateIncomingFile(boxID, size)
|
|
}
|
|
if boxstore.IsExpired(manifest) {
|
|
_ = boxstore.DeleteBox(boxID)
|
|
return fmt.Errorf("Box expired")
|
|
}
|
|
if app.config.GlobalMaxBoxSizeBytes <= 0 {
|
|
return nil
|
|
}
|
|
totalSize := int64(0)
|
|
found := false
|
|
for _, file := range manifest.Files {
|
|
if file.ID == fileID {
|
|
totalSize += size
|
|
found = true
|
|
continue
|
|
}
|
|
totalSize += file.Size
|
|
}
|
|
if !found {
|
|
totalSize += size
|
|
}
|
|
return app.validateBoxSize(totalSize)
|
|
}
|
|
|
|
func (app *App) validateFileSize(size int64) error {
|
|
if size < 0 {
|
|
return fmt.Errorf("File size cannot be negative")
|
|
}
|
|
if app.config.GlobalMaxFileSizeBytes > 0 && size > app.config.GlobalMaxFileSizeBytes {
|
|
return fmt.Errorf("File exceeds the global max file size")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (app *App) validateBoxSize(size int64) error {
|
|
if size < 0 {
|
|
return fmt.Errorf("Box size cannot be negative")
|
|
}
|
|
if app.config.GlobalMaxBoxSizeBytes > 0 && size > app.config.GlobalMaxBoxSizeBytes {
|
|
return fmt.Errorf("Box exceeds the global max box size")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (app *App) rejectExpiredManifestBox(boxID string) error {
|
|
manifest, err := boxstore.ReadManifest(boxID)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
if !boxstore.IsExpired(manifest) {
|
|
return nil
|
|
}
|
|
_ = boxstore.DeleteBox(boxID)
|
|
return fmt.Errorf("Box expired")
|
|
}
|
|
|
|
func (app *App) limitRequestBody(ctx *gin.Context) {
|
|
limit := app.maxRequestBodyBytes()
|
|
if limit <= 0 {
|
|
return
|
|
}
|
|
ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, limit)
|
|
}
|
|
|
|
func (app *App) maxRequestBodyBytes() int64 {
|
|
limit := app.config.GlobalMaxBoxSizeBytes
|
|
if limit <= 0 || app.config.GlobalMaxFileSizeBytes > limit {
|
|
limit = app.config.GlobalMaxFileSizeBytes
|
|
}
|
|
if limit <= 0 {
|
|
return 0
|
|
}
|
|
return limit + 10*1024*1024
|
|
}
|
|
|
|
func (app *App) enforceUploadRateLimit(ctx *gin.Context, size int64) bool {
|
|
if !app.securityFeaturesEnabled() || app.securityGuard == nil {
|
|
return true
|
|
}
|
|
ip := app.clientIP(ctx)
|
|
if app.securityGuard.IsWhitelisted(ip) || app.securityGuard.IsAdminWhitelisted(ip) {
|
|
return true
|
|
}
|
|
allowed, requestCount, totalBytes := app.securityGuard.AllowUpload(
|
|
ip,
|
|
size,
|
|
app.config.SecurityUploadWindowSeconds,
|
|
app.config.SecurityUploadMaxRequests,
|
|
app.config.SecurityUploadMaxBytes,
|
|
)
|
|
if allowed {
|
|
return true
|
|
}
|
|
|
|
app.logActivity("security.upload_limit", "high", "Upload rate limit exceeded", ctx, map[string]string{
|
|
"requests": strconv.Itoa(requestCount),
|
|
"bytes": strconv.FormatInt(totalBytes, 10),
|
|
})
|
|
app.createAlert(
|
|
"Upload rate limit triggered",
|
|
"medium",
|
|
"security",
|
|
"430",
|
|
"security.upload.rate_limit",
|
|
"Per-IP upload rate limit blocked request.",
|
|
map[string]string{"ip": ip, "requests": strconv.Itoa(requestCount)},
|
|
)
|
|
ctx.JSON(http.StatusTooManyRequests, gin.H{"error": "Too many uploads from this IP. Try again later."})
|
|
return false
|
|
}
|