add project

This commit is contained in:
GuilhermeStrice
2025-07-09 19:24:12 +01:00
parent 8d7c2d4b04
commit 1108bf3ef6
17 changed files with 4752 additions and 0 deletions

438
Models/InputValidation.cs Normal file
View File

@ -0,0 +1,438 @@
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
namespace DatabaseSnapshotsService.Models
{
public static class InputValidation
{
// Validation patterns
private static readonly Regex ValidFileNamePattern = new(@"^[a-zA-Z0-9._-]+$", RegexOptions.Compiled);
private static readonly Regex ValidPathPattern = new(@"^[a-zA-Z0-9/._-]+$", RegexOptions.Compiled);
private static readonly Regex ValidNamePattern = new(@"^[a-zA-Z0-9\s._-]{1,100}$", RegexOptions.Compiled);
private static readonly Regex ValidDescriptionPattern = new(@"^[a-zA-Z0-9\s.,!?@#$%^&*()_+-=:;'""<>/\\|`~]{0,500}$", RegexOptions.Compiled);
private static readonly Regex ValidTableNamePattern = new(@"^[a-zA-Z][a-zA-Z0-9_]*$", RegexOptions.Compiled);
private static readonly Regex ValidOperationPattern = new(@"^(insert|update|delete|truncate|query)$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex ValidEventTypePattern = new(@"^(binlog|status|error|info)$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
// Dangerous patterns to detect
private static readonly Regex[] DangerousPatterns = {
new(@"\.\./", RegexOptions.Compiled), // Path traversal
new(@"\.\.\\", RegexOptions.Compiled), // Windows path traversal
new(@"[<>""'&]", RegexOptions.Compiled), // HTML/XML injection
new(@"(union|select|insert|update|delete|drop|create|alter|exec|execute|script|javascript|vbscript|onload|onerror)", RegexOptions.IgnoreCase | RegexOptions.Compiled), // SQL/script injection
new(@"(\x00|\x01|\x02|\x03|\x04|\x05|\x06|\x07|\x08|\x0B|\x0C|\x0E|\x0F|\x10|\x11|\x12|\x13|\x14|\x15|\x16|\x17|\x18|\x19|\x1A|\x1B|\x1C|\x1D|\x1E|\x1F)", RegexOptions.Compiled), // Control characters
};
public static class SnapshotValidation
{
public static ValidationResult ValidateSnapshotName(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return new ValidationResult("Snapshot name cannot be empty");
}
if (name.Length > 100)
{
return new ValidationResult("Snapshot name cannot exceed 100 characters");
}
if (!ValidNamePattern.IsMatch(name))
{
return new ValidationResult("Snapshot name contains invalid characters. Use only letters, numbers, spaces, dots, underscores, and hyphens");
}
if (ContainsDangerousPatterns(name))
{
return new ValidationResult("Snapshot name contains potentially dangerous content");
}
return ValidationResult.Success!;
}
public static ValidationResult ValidateSnapshotDescription(string? description)
{
if (string.IsNullOrWhiteSpace(description))
{
return ValidationResult.Success!; // Description is optional
}
if (description.Length > 500)
{
return new ValidationResult("Snapshot description cannot exceed 500 characters");
}
if (!ValidDescriptionPattern.IsMatch(description))
{
return new ValidationResult("Snapshot description contains invalid characters");
}
if (ContainsDangerousPatterns(description))
{
return new ValidationResult("Snapshot description contains potentially dangerous content");
}
return ValidationResult.Success!;
}
public static ValidationResult ValidateSnapshotId(int id)
{
if (id <= 0)
{
return new ValidationResult("Snapshot ID must be a positive integer");
}
if (id > int.MaxValue)
{
return new ValidationResult("Snapshot ID is too large");
}
return ValidationResult.Success!;
}
public static ValidationResult ValidateSnapshotType(string type)
{
if (string.IsNullOrWhiteSpace(type))
{
return new ValidationResult("Snapshot type cannot be empty");
}
var validTypes = new[] { "full", "incremental", "trading", "user" };
if (!validTypes.Contains(type.ToLowerInvariant()))
{
return new ValidationResult($"Invalid snapshot type. Must be one of: {string.Join(", ", validTypes)}");
}
return ValidationResult.Success!;
}
}
public static class EventValidation
{
public static ValidationResult ValidateTableName(string tableName)
{
if (string.IsNullOrWhiteSpace(tableName))
{
return new ValidationResult("Table name cannot be empty");
}
if (tableName.Length > 64)
{
return new ValidationResult("Table name cannot exceed 64 characters");
}
if (!ValidTableNamePattern.IsMatch(tableName))
{
return new ValidationResult("Table name contains invalid characters. Use only letters, numbers, and underscores, starting with a letter");
}
if (ContainsDangerousPatterns(tableName))
{
return new ValidationResult("Table name contains potentially dangerous content");
}
return ValidationResult.Success!;
}
public static ValidationResult ValidateOperation(string operation)
{
if (string.IsNullOrWhiteSpace(operation))
{
return new ValidationResult("Operation cannot be empty");
}
if (!ValidOperationPattern.IsMatch(operation))
{
return new ValidationResult("Invalid operation. Must be one of: insert, update, delete, truncate, query");
}
return ValidationResult.Success!;
}
public static ValidationResult ValidateEventType(string eventType)
{
if (string.IsNullOrWhiteSpace(eventType))
{
return new ValidationResult("Event type cannot be empty");
}
if (!ValidEventTypePattern.IsMatch(eventType))
{
return new ValidationResult("Invalid event type. Must be one of: binlog, status, error, info");
}
return ValidationResult.Success!;
}
public static ValidationResult ValidateEventData(string data)
{
if (string.IsNullOrWhiteSpace(data))
{
return new ValidationResult("Event data cannot be empty");
}
if (data.Length > 10000) // 10KB limit
{
return new ValidationResult("Event data cannot exceed 10KB");
}
if (ContainsDangerousPatterns(data))
{
return new ValidationResult("Event data contains potentially dangerous content");
}
return ValidationResult.Success!;
}
public static ValidationResult ValidateLimit(int limit)
{
if (limit <= 0)
{
return new ValidationResult("Limit must be a positive integer");
}
if (limit > 10000)
{
return new ValidationResult("Limit cannot exceed 10000");
}
return ValidationResult.Success!;
}
public static ValidationResult ValidateTimestamp(long timestamp)
{
if (timestamp < 0)
{
return new ValidationResult("Timestamp cannot be negative");
}
var maxTimestamp = DateTimeOffset.MaxValue.ToUnixTimeSeconds();
if (timestamp > maxTimestamp)
{
return new ValidationResult("Timestamp is too far in the future");
}
return ValidationResult.Success!;
}
}
public static class RecoveryValidation
{
public static ValidationResult ValidateRecoveryPointName(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return new ValidationResult("Recovery point name cannot be empty");
}
if (name.Length > 100)
{
return new ValidationResult("Recovery point name cannot exceed 100 characters");
}
if (!ValidNamePattern.IsMatch(name))
{
return new ValidationResult("Recovery point name contains invalid characters. Use only letters, numbers, spaces, dots, underscores, and hyphens");
}
if (ContainsDangerousPatterns(name))
{
return new ValidationResult("Recovery point name contains potentially dangerous content");
}
return ValidationResult.Success!;
}
public static ValidationResult ValidateRecoveryPointDescription(string? description)
{
if (string.IsNullOrWhiteSpace(description))
{
return ValidationResult.Success!; // Description is optional
}
if (description.Length > 500)
{
return new ValidationResult("Recovery point description cannot exceed 500 characters");
}
if (!ValidDescriptionPattern.IsMatch(description))
{
return new ValidationResult("Recovery point description contains invalid characters");
}
if (ContainsDangerousPatterns(description))
{
return new ValidationResult("Recovery point description contains potentially dangerous content");
}
return ValidationResult.Success!;
}
}
public static class FileValidation
{
public static ValidationResult ValidateFilePath(string filePath)
{
if (string.IsNullOrWhiteSpace(filePath))
{
return new ValidationResult("File path cannot be empty");
}
if (filePath.Length > 260) // Windows path limit
{
return new ValidationResult("File path cannot exceed 260 characters");
}
if (!ValidPathPattern.IsMatch(filePath))
{
return new ValidationResult("File path contains invalid characters");
}
if (ContainsDangerousPatterns(filePath))
{
return new ValidationResult("File path contains potentially dangerous content");
}
// Check for path traversal attempts
var normalizedPath = Path.GetFullPath(filePath);
if (normalizedPath.Contains("..") || normalizedPath.Contains("~"))
{
return new ValidationResult("File path contains invalid path traversal characters");
}
return ValidationResult.Success!;
}
public static ValidationResult ValidateFileName(string fileName)
{
if (string.IsNullOrWhiteSpace(fileName))
{
return new ValidationResult("File name cannot be empty");
}
if (fileName.Length > 255)
{
return new ValidationResult("File name cannot exceed 255 characters");
}
if (!ValidFileNamePattern.IsMatch(fileName))
{
return new ValidationResult("File name contains invalid characters. Use only letters, numbers, dots, underscores, and hyphens");
}
if (ContainsDangerousPatterns(fileName))
{
return new ValidationResult("File name contains potentially dangerous content");
}
// Check for reserved Windows filenames
var reservedNames = new[]
{
"CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"
};
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName).ToUpperInvariant();
if (reservedNames.Contains(fileNameWithoutExtension))
{
return new ValidationResult("File name is a reserved system name");
}
return ValidationResult.Success!;
}
}
public static class DataValidation
{
public static ValidationResult ValidateDataContains(string dataContains)
{
if (string.IsNullOrWhiteSpace(dataContains))
{
return new ValidationResult("Data contains filter cannot be empty");
}
if (dataContains.Length > 1000)
{
return new ValidationResult("Data contains filter cannot exceed 1000 characters");
}
if (ContainsDangerousPatterns(dataContains))
{
return new ValidationResult("Data contains filter contains potentially dangerous content");
}
return ValidationResult.Success!;
}
public static ValidationResult ValidateOutputFile(string outputFile)
{
if (string.IsNullOrWhiteSpace(outputFile))
{
return new ValidationResult("Output file path cannot be empty");
}
return FileValidation.ValidateFilePath(outputFile);
}
}
private static bool ContainsDangerousPatterns(string input)
{
return DangerousPatterns.Any(pattern => pattern.IsMatch(input));
}
public static string SanitizeString(string input)
{
if (string.IsNullOrWhiteSpace(input))
{
return string.Empty;
}
// Remove control characters
var sanitized = Regex.Replace(input, @"[\x00-\x1F\x7F]", "");
// Trim whitespace
sanitized = sanitized.Trim();
return sanitized;
}
public static string SanitizeFileName(string fileName)
{
if (string.IsNullOrWhiteSpace(fileName))
{
return string.Empty;
}
// Remove invalid characters for file names
var sanitized = Regex.Replace(fileName, @"[<>:""/\\|?*\x00-\x1F]", "");
// Trim and limit length
sanitized = sanitized.Trim();
if (sanitized.Length > 255)
{
sanitized = sanitized.Substring(0, 255);
}
return sanitized;
}
public static string SanitizePath(string path)
{
if (string.IsNullOrWhiteSpace(path))
{
return string.Empty;
}
// Remove path traversal attempts
var sanitized = path.Replace("..", "").Replace("~", "");
// Remove invalid characters
sanitized = Regex.Replace(sanitized, @"[<>""|?\x00-\x1F]", "");
// Normalize path separators
sanitized = sanitized.Replace('\\', '/');
return sanitized;
}
}
}