From 38c0641734bb0741d62705fc13ceec2c58eea4ea Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 19 Aug 2025 12:14:48 +0200 Subject: [PATCH] feat(validation): centralize validation patterns for names and descriptions - Introduced `ValidationPatterns` class to standardize validation rules and messages for name and description fields across the application. - Updated various components and models to utilize the new validation patterns, ensuring consistent sanitization and validation logic. - Replaced the `HasSafeNameAttribute` trait with `HasSafeStringAttribute` to enhance attribute handling and maintain consistency in name sanitization. - Enhanced the `CleanupNames` command to align with the new validation rules, allowing for a broader range of valid characters in names. --- app/Console/Commands/CleanupNames.php | 7 +- .../Controllers/Api/ProjectController.php | 31 ++----- app/Livewire/Project/AddEmpty.php | 21 +++-- app/Livewire/Project/CloneMe.php | 19 ++-- app/Livewire/Project/Edit.php | 21 +++-- app/Livewire/Project/EnvironmentEdit.php | 21 +++-- app/Livewire/Project/Show.php | 21 +++-- app/Models/Application.php | 4 +- app/Models/Environment.php | 11 +-- app/Models/LocalPersistentVolume.php | 6 +- app/Models/PrivateKey.php | 4 +- app/Models/Project.php | 4 +- app/Models/S3Storage.php | 4 +- app/Models/ScheduledTask.php | 4 +- app/Models/Server.php | 4 +- app/Models/Service.php | 4 +- app/Models/StandaloneClickhouse.php | 4 +- app/Models/StandaloneDocker.php | 4 +- app/Models/StandaloneDragonfly.php | 4 +- app/Models/StandaloneKeydb.php | 4 +- app/Models/StandaloneMariadb.php | 4 +- app/Models/StandaloneMongodb.php | 4 +- app/Models/StandaloneMysql.php | 4 +- app/Models/StandalonePostgresql.php | 4 +- app/Models/StandaloneRedis.php | 4 +- app/Models/Tag.php | 12 +-- app/Models/Team.php | 4 +- app/Support/ValidationPatterns.php | 93 +++++++++++++++++++ app/Traits/HasSafeNameAttribute.php | 14 --- app/Traits/HasSafeStringAttribute.php | 25 +++++ 30 files changed, 238 insertions(+), 132 deletions(-) create mode 100644 app/Support/ValidationPatterns.php delete mode 100644 app/Traits/HasSafeNameAttribute.php create mode 100644 app/Traits/HasSafeStringAttribute.php diff --git a/app/Console/Commands/CleanupNames.php b/app/Console/Commands/CleanupNames.php index 3ff0ff848..2992e32b9 100644 --- a/app/Console/Commands/CleanupNames.php +++ b/app/Console/Commands/CleanupNames.php @@ -20,6 +20,7 @@ use App\Models\StandalonePostgresql; use App\Models\StandaloneRedis; use App\Models\Tag; use App\Models\Team; +use App\Support\ValidationPatterns; use Illuminate\Console\Command; use Illuminate\Support\Facades\Log; @@ -31,7 +32,7 @@ class CleanupNames extends Command {--backup : Create database backup before changes} {--force : Skip confirmation prompt}'; - protected $description = 'Sanitize name fields by removing invalid characters (keeping only letters, numbers, spaces, dashes, underscores, dots)'; + protected $description = 'Sanitize name fields by removing invalid characters (keeping only letters, numbers, spaces, dashes, underscores, dots, slashes, colons, parentheses)'; protected array $modelsToClean = [ 'Project' => Project::class, @@ -148,7 +149,9 @@ class CleanupNames extends Command protected function sanitizeName(string $name): string { // Remove all characters that don't match the allowed pattern - $sanitized = preg_replace('/[^a-zA-Z0-9\s\-_.]+/', '', $name); + // Use the shared ValidationPatterns to ensure consistency + $allowedPattern = str_replace(['/', '^', '$'], '', ValidationPatterns::NAME_PATTERN); + $sanitized = preg_replace('/[^'.$allowedPattern.']+/', '', $name); // Clean up excessive whitespace but preserve other allowed characters $sanitized = preg_replace('/\s+/', ' ', $sanitized); diff --git a/app/Http/Controllers/Api/ProjectController.php b/app/Http/Controllers/Api/ProjectController.php index 05ceefed4..e688b8980 100644 --- a/app/Http/Controllers/Api/ProjectController.php +++ b/app/Http/Controllers/Api/ProjectController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; use App\Models\Project; +use App\Support\ValidationPatterns; use Illuminate\Http\Request; use Illuminate\Support\Facades\Validator; use OpenApi\Attributes as OA; @@ -229,14 +230,9 @@ class ProjectController extends Controller return $return; } $validator = Validator::make($request->all(), [ - 'name' => ['required', 'string', 'max:255', 'regex:/^[a-zA-Z0-9\s\-_.]+$/'], - 'description' => 'string|nullable', - ], [ - 'name.regex' => 'The project name may only contain letters, numbers, spaces, dashes, underscores, and dots.', - 'name.required' => 'The project name is required.', - 'name.min' => 'The project name must be at least 3 characters.', - 'name.max' => 'The project name may not be greater than 255 characters.', - ]); + 'name' => ValidationPatterns::nameRules(), + 'description' => ValidationPatterns::descriptionRules(), + ], ValidationPatterns::combinedMessages()); $extraFields = array_diff(array_keys($request->all()), $allowedFields); if ($validator->fails() || ! empty($extraFields)) { @@ -344,13 +340,9 @@ class ProjectController extends Controller return $return; } $validator = Validator::make($request->all(), [ - 'name' => ['nullable', 'string', 'max:255', 'regex:/^[a-zA-Z0-9\s\-_.]+$/'], - 'description' => 'string|nullable', - ], [ - 'name.regex' => 'The project name may only contain letters, numbers, spaces, dashes, underscores, and dots.', - 'name.min' => 'The project name must be at least 3 characters.', - 'name.max' => 'The project name may not be greater than 255 characters.', - ]); + 'name' => ValidationPatterns::nameRules(required: false), + 'description' => ValidationPatterns::descriptionRules(), + ], ValidationPatterns::combinedMessages()); $extraFields = array_diff(array_keys($request->all()), $allowedFields); if ($validator->fails() || ! empty($extraFields)) { @@ -590,13 +582,8 @@ class ProjectController extends Controller return $return; } $validator = Validator::make($request->all(), [ - 'name' => ['required', 'string', 'max:255', 'regex:/^[a-zA-Z0-9\s\-_.]+$/'], - ], [ - 'name.regex' => 'The environment name may only contain letters, numbers, spaces, dashes, underscores, and dots.', - 'name.required' => 'The environment name is required.', - 'name.min' => 'The environment name must be at least 3 characters.', - 'name.max' => 'The environment name may not be greater than 255 characters.', - ]); + 'name' => ValidationPatterns::nameRules(), + ], ValidationPatterns::nameMessages()); $extraFields = array_diff(array_keys($request->all()), $allowedFields); if ($validator->fails() || ! empty($extraFields)) { diff --git a/app/Livewire/Project/AddEmpty.php b/app/Livewire/Project/AddEmpty.php index 36b1edd15..751b4945b 100644 --- a/app/Livewire/Project/AddEmpty.php +++ b/app/Livewire/Project/AddEmpty.php @@ -3,23 +3,28 @@ namespace App\Livewire\Project; use App\Models\Project; -use Livewire\Attributes\Validate; +use App\Support\ValidationPatterns; use Livewire\Component; use Visus\Cuid2\Cuid2; class AddEmpty extends Component { - #[Validate(['required', 'string', 'min:3', 'max:255', 'regex:/^[a-zA-Z0-9\s\-_.]+$/'])] public string $name; - #[Validate(['nullable', 'string'])] public string $description = ''; - protected $messages = [ - 'name.regex' => 'The name may only contain letters, numbers, spaces, dashes, underscores, and dots.', - 'name.min' => 'The name must be at least 3 characters.', - 'name.max' => 'The name may not be greater than 255 characters.', - ]; + protected function rules(): array + { + return [ + 'name' => ValidationPatterns::nameRules(), + 'description' => ValidationPatterns::descriptionRules(), + ]; + } + + protected function messages(): array + { + return ValidationPatterns::combinedMessages(); + } public function submit() { diff --git a/app/Livewire/Project/CloneMe.php b/app/Livewire/Project/CloneMe.php index e69e55df6..3c8c9843d 100644 --- a/app/Livewire/Project/CloneMe.php +++ b/app/Livewire/Project/CloneMe.php @@ -11,6 +11,7 @@ use App\Jobs\VolumeCloneJob; use App\Models\Environment; use App\Models\Project; use App\Models\Server; +use App\Support\ValidationPatterns; use Livewire\Component; use Visus\Cuid2\Cuid2; @@ -42,14 +43,14 @@ class CloneMe extends Component public bool $cloneVolumeData = false; - protected $messages = [ - 'selectedServer' => 'Please select a server.', - 'selectedDestination' => 'Please select a server & destination.', - 'newName.required' => 'Please enter a name for the new project or environment.', - 'newName.regex' => 'The name may only contain letters, numbers, spaces, dashes, underscores, and dots.', - 'newName.min' => 'The name must be at least 3 characters.', - 'newName.max' => 'The name may not be greater than 255 characters.', - ]; + protected function messages(): array + { + return array_merge([ + 'selectedServer' => 'Please select a server.', + 'selectedDestination' => 'Please select a server & destination.', + 'newName.required' => 'Please enter a name for the new project or environment.', + ], ValidationPatterns::nameMessages()); + } public function mount($project_uuid) { @@ -93,7 +94,7 @@ class CloneMe extends Component try { $this->validate([ 'selectedDestination' => 'required', - 'newName' => ['required', 'string', 'min:3', 'max:255', 'regex:/^[a-zA-Z0-9\s\-_.]+$/'], + 'newName' => ValidationPatterns::nameRules(), ]); if ($type === 'project') { $foundProject = Project::where('name', $this->newName)->first(); diff --git a/app/Livewire/Project/Edit.php b/app/Livewire/Project/Edit.php index 800dc31e1..a2d73eb5f 100644 --- a/app/Livewire/Project/Edit.php +++ b/app/Livewire/Project/Edit.php @@ -3,24 +3,29 @@ namespace App\Livewire\Project; use App\Models\Project; -use Livewire\Attributes\Validate; +use App\Support\ValidationPatterns; use Livewire\Component; class Edit extends Component { public Project $project; - #[Validate(['required', 'string', 'min:3', 'max:255', 'regex:/^[a-zA-Z0-9\s\-_.]+$/'])] public string $name; - #[Validate(['nullable', 'string', 'max:255'])] public ?string $description = null; - protected $messages = [ - 'name.regex' => 'The name may only contain letters, numbers, spaces, dashes, underscores, and dots.', - 'name.min' => 'The name must be at least 3 characters.', - 'name.max' => 'The name may not be greater than 255 characters.', - ]; + protected function rules(): array + { + return [ + 'name' => ValidationPatterns::nameRules(), + 'description' => ValidationPatterns::descriptionRules(), + ]; + } + + protected function messages(): array + { + return ValidationPatterns::combinedMessages(); + } public function mount(string $project_uuid) { diff --git a/app/Livewire/Project/EnvironmentEdit.php b/app/Livewire/Project/EnvironmentEdit.php index 021f499ab..d57be2cc8 100644 --- a/app/Livewire/Project/EnvironmentEdit.php +++ b/app/Livewire/Project/EnvironmentEdit.php @@ -4,8 +4,8 @@ namespace App\Livewire\Project; use App\Models\Application; use App\Models\Project; +use App\Support\ValidationPatterns; use Livewire\Attributes\Locked; -use Livewire\Attributes\Validate; use Livewire\Component; class EnvironmentEdit extends Component @@ -17,17 +17,22 @@ class EnvironmentEdit extends Component #[Locked] public $environment; - #[Validate(['required', 'string', 'min:3', 'max:255', 'regex:/^[a-zA-Z0-9\s\-_.]+$/'])] public string $name; - #[Validate(['nullable', 'string', 'max:255'])] public ?string $description = null; - protected $messages = [ - 'name.regex' => 'The environment name may only contain letters, numbers, spaces, dashes, underscores, and dots.', - 'name.min' => 'The environment name must be at least 3 characters.', - 'name.max' => 'The environment name may not be greater than 255 characters.', - ]; + protected function rules(): array + { + return [ + 'name' => ValidationPatterns::nameRules(), + 'description' => ValidationPatterns::descriptionRules(), + ]; + } + + protected function messages(): array + { + return ValidationPatterns::combinedMessages(); + } public function mount(string $project_uuid, string $environment_uuid) { diff --git a/app/Livewire/Project/Show.php b/app/Livewire/Project/Show.php index e24558530..7e828d14c 100644 --- a/app/Livewire/Project/Show.php +++ b/app/Livewire/Project/Show.php @@ -4,7 +4,7 @@ namespace App\Livewire\Project; use App\Models\Environment; use App\Models\Project; -use Livewire\Attributes\Validate; +use App\Support\ValidationPatterns; use Livewire\Component; use Visus\Cuid2\Cuid2; @@ -12,17 +12,22 @@ class Show extends Component { public Project $project; - #[Validate(['required', 'string', 'min:3', 'max:255', 'regex:/^[a-zA-Z0-9\s\-_.]+$/'])] public string $name; - #[Validate(['nullable', 'string'])] public ?string $description = null; - protected $messages = [ - 'name.regex' => 'The environment name may only contain letters, numbers, spaces, dashes, underscores, and dots.', - 'name.min' => 'The environment name must be at least 3 characters.', - 'name.max' => 'The environment name may not be greater than 255 characters.', - ]; + protected function rules(): array + { + return [ + 'name' => ValidationPatterns::nameRules(), + 'description' => ValidationPatterns::descriptionRules(), + ]; + } + + protected function messages(): array + { + return ValidationPatterns::combinedMessages(); + } public function mount(string $project_uuid) { diff --git a/app/Models/Application.php b/app/Models/Application.php index cbbe8d83c..1971488c7 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -5,7 +5,7 @@ namespace App\Models; use App\Enums\ApplicationDeploymentStatus; use App\Services\ConfigurationGenerator; use App\Traits\HasConfiguration; -use App\Traits\HasSafeNameAttribute; +use App\Traits\HasSafeStringAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -110,7 +110,7 @@ use Visus\Cuid2\Cuid2; class Application extends BaseModel { - use HasConfiguration, HasFactory, HasSafeNameAttribute, SoftDeletes; + use HasConfiguration, HasFactory, HasSafeStringAttribute, SoftDeletes; private static $parserVersion = '5'; diff --git a/app/Models/Environment.php b/app/Models/Environment.php index 659ec74e4..437be7d87 100644 --- a/app/Models/Environment.php +++ b/app/Models/Environment.php @@ -2,8 +2,7 @@ namespace App\Models; -use App\Traits\HasSafeNameAttribute; -use Illuminate\Database\Eloquent\Casts\Attribute; +use App\Traits\HasSafeStringAttribute; use OpenApi\Attributes as OA; #[OA\Schema( @@ -20,7 +19,7 @@ use OpenApi\Attributes as OA; )] class Environment extends BaseModel { - use HasSafeNameAttribute; + use HasSafeStringAttribute; protected $guarded = []; @@ -122,10 +121,8 @@ class Environment extends BaseModel return $this->hasMany(Service::class); } - protected function name(): Attribute + protected function customizeName($value) { - return Attribute::make( - set: fn (string $value) => str($value)->lower()->trim()->replace('/', '-')->toString(), - ); + return str($value)->lower()->trim()->replace('/', '-')->toString(); } } diff --git a/app/Models/LocalPersistentVolume.php b/app/Models/LocalPersistentVolume.php index b5dfd9663..00dc15fea 100644 --- a/app/Models/LocalPersistentVolume.php +++ b/app/Models/LocalPersistentVolume.php @@ -24,11 +24,9 @@ class LocalPersistentVolume extends Model return $this->morphTo('resource'); } - protected function name(): Attribute + protected function customizeName($value) { - return Attribute::make( - set: fn (string $value) => str($value)->trim()->value, - ); + return str($value)->trim()->value; } protected function mountPath(): Attribute diff --git a/app/Models/PrivateKey.php b/app/Models/PrivateKey.php index 128fac7af..f70f32bc4 100644 --- a/app/Models/PrivateKey.php +++ b/app/Models/PrivateKey.php @@ -2,7 +2,7 @@ namespace App\Models; -use App\Traits\HasSafeNameAttribute; +use App\Traits\HasSafeStringAttribute; use DanHarrin\LivewireRateLimiting\WithRateLimiting; use Illuminate\Support\Facades\Storage; use Illuminate\Validation\ValidationException; @@ -28,7 +28,7 @@ use phpseclib3\Crypt\PublicKeyLoader; )] class PrivateKey extends BaseModel { - use HasSafeNameAttribute, WithRateLimiting; + use HasSafeStringAttribute, WithRateLimiting; protected $fillable = [ 'name', diff --git a/app/Models/Project.php b/app/Models/Project.php index a4e92fd51..1c46042e3 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -2,7 +2,7 @@ namespace App\Models; -use App\Traits\HasSafeNameAttribute; +use App\Traits\HasSafeStringAttribute; use OpenApi\Attributes as OA; use Visus\Cuid2\Cuid2; @@ -24,7 +24,7 @@ use Visus\Cuid2\Cuid2; )] class Project extends BaseModel { - use HasSafeNameAttribute; + use HasSafeStringAttribute; protected $guarded = []; diff --git a/app/Models/S3Storage.php b/app/Models/S3Storage.php index 86676f3d1..de27bbca6 100644 --- a/app/Models/S3Storage.php +++ b/app/Models/S3Storage.php @@ -2,14 +2,14 @@ namespace App\Models; -use App\Traits\HasSafeNameAttribute; +use App\Traits\HasSafeStringAttribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Support\Facades\Storage; class S3Storage extends BaseModel { - use HasFactory, HasSafeNameAttribute; + use HasFactory, HasSafeStringAttribute; protected $guarded = []; diff --git a/app/Models/ScheduledTask.php b/app/Models/ScheduledTask.php index 10b9c184e..06903ffb6 100644 --- a/app/Models/ScheduledTask.php +++ b/app/Models/ScheduledTask.php @@ -2,13 +2,13 @@ namespace App\Models; -use App\Traits\HasSafeNameAttribute; +use App\Traits\HasSafeStringAttribute; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; class ScheduledTask extends BaseModel { - use HasSafeNameAttribute; + use HasSafeStringAttribute; protected $guarded = []; diff --git a/app/Models/Server.php b/app/Models/Server.php index 553defb0c..0f92bd390 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -13,7 +13,7 @@ use App\Jobs\RegenerateSslCertJob; use App\Notifications\Server\Reachable; use App\Notifications\Server\Unreachable; use App\Services\ConfigurationRepository; -use App\Traits\HasSafeNameAttribute; +use App\Traits\HasSafeStringAttribute; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -165,7 +165,7 @@ class Server extends BaseModel protected $guarded = []; - use HasSafeNameAttribute; + use HasSafeStringAttribute; public function type() { diff --git a/app/Models/Service.php b/app/Models/Service.php index 033c9ee42..43cb32d85 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -3,7 +3,7 @@ namespace App\Models; use App\Enums\ProcessStatus; -use App\Traits\HasSafeNameAttribute; +use App\Traits\HasSafeStringAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -41,7 +41,7 @@ use Visus\Cuid2\Cuid2; )] class Service extends BaseModel { - use HasFactory, HasSafeNameAttribute, SoftDeletes; + use HasFactory, HasSafeStringAttribute, SoftDeletes; private static $parserVersion = '5'; diff --git a/app/Models/StandaloneClickhouse.php b/app/Models/StandaloneClickhouse.php index 5e1b200eb..60a750a99 100644 --- a/app/Models/StandaloneClickhouse.php +++ b/app/Models/StandaloneClickhouse.php @@ -2,14 +2,14 @@ namespace App\Models; -use App\Traits\HasSafeNameAttribute; +use App\Traits\HasSafeStringAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; class StandaloneClickhouse extends BaseModel { - use HasFactory, HasSafeNameAttribute, SoftDeletes; + use HasFactory, HasSafeStringAttribute, SoftDeletes; protected $guarded = []; diff --git a/app/Models/StandaloneDocker.php b/app/Models/StandaloneDocker.php index 141c0cf12..aeb99d34a 100644 --- a/app/Models/StandaloneDocker.php +++ b/app/Models/StandaloneDocker.php @@ -2,11 +2,11 @@ namespace App\Models; -use App\Traits\HasSafeNameAttribute; +use App\Traits\HasSafeStringAttribute; class StandaloneDocker extends BaseModel { - use HasSafeNameAttribute; + use HasSafeStringAttribute; protected $guarded = []; diff --git a/app/Models/StandaloneDragonfly.php b/app/Models/StandaloneDragonfly.php index aa861ae25..673851713 100644 --- a/app/Models/StandaloneDragonfly.php +++ b/app/Models/StandaloneDragonfly.php @@ -2,14 +2,14 @@ namespace App\Models; -use App\Traits\HasSafeNameAttribute; +use App\Traits\HasSafeStringAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; class StandaloneDragonfly extends BaseModel { - use HasFactory, HasSafeNameAttribute, SoftDeletes; + use HasFactory, HasSafeStringAttribute, SoftDeletes; protected $guarded = []; diff --git a/app/Models/StandaloneKeydb.php b/app/Models/StandaloneKeydb.php index 6b5700ea6..e6562193b 100644 --- a/app/Models/StandaloneKeydb.php +++ b/app/Models/StandaloneKeydb.php @@ -2,14 +2,14 @@ namespace App\Models; -use App\Traits\HasSafeNameAttribute; +use App\Traits\HasSafeStringAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; class StandaloneKeydb extends BaseModel { - use HasFactory, HasSafeNameAttribute, SoftDeletes; + use HasFactory, HasSafeStringAttribute, SoftDeletes; protected $guarded = []; diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php index c6ad7dd51..1aa9d63c1 100644 --- a/app/Models/StandaloneMariadb.php +++ b/app/Models/StandaloneMariadb.php @@ -2,7 +2,7 @@ namespace App\Models; -use App\Traits\HasSafeNameAttribute; +use App\Traits\HasSafeStringAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\MorphTo; @@ -10,7 +10,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; class StandaloneMariadb extends BaseModel { - use HasFactory, HasSafeNameAttribute, SoftDeletes; + use HasFactory, HasSafeStringAttribute, SoftDeletes; protected $guarded = []; diff --git a/app/Models/StandaloneMongodb.php b/app/Models/StandaloneMongodb.php index 95bdad5d1..299ea75b2 100644 --- a/app/Models/StandaloneMongodb.php +++ b/app/Models/StandaloneMongodb.php @@ -2,14 +2,14 @@ namespace App\Models; -use App\Traits\HasSafeNameAttribute; +use App\Traits\HasSafeStringAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; class StandaloneMongodb extends BaseModel { - use HasFactory, HasSafeNameAttribute, SoftDeletes; + use HasFactory, HasSafeStringAttribute, SoftDeletes; protected $guarded = []; diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php index 7138ef5bb..f376c7644 100644 --- a/app/Models/StandaloneMysql.php +++ b/app/Models/StandaloneMysql.php @@ -2,14 +2,14 @@ namespace App\Models; -use App\Traits\HasSafeNameAttribute; +use App\Traits\HasSafeStringAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; class StandaloneMysql extends BaseModel { - use HasFactory, HasSafeNameAttribute, SoftDeletes; + use HasFactory, HasSafeStringAttribute, SoftDeletes; protected $guarded = []; diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php index 870a6ade8..0bca2f4a7 100644 --- a/app/Models/StandalonePostgresql.php +++ b/app/Models/StandalonePostgresql.php @@ -2,14 +2,14 @@ namespace App\Models; -use App\Traits\HasSafeNameAttribute; +use App\Traits\HasSafeStringAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; class StandalonePostgresql extends BaseModel { - use HasFactory, HasSafeNameAttribute, SoftDeletes; + use HasFactory, HasSafeStringAttribute, SoftDeletes; protected $guarded = []; diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php index 35de6602d..6a44ee714 100644 --- a/app/Models/StandaloneRedis.php +++ b/app/Models/StandaloneRedis.php @@ -2,14 +2,14 @@ namespace App\Models; -use App\Traits\HasSafeNameAttribute; +use App\Traits\HasSafeStringAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; class StandaloneRedis extends BaseModel { - use HasFactory, HasSafeNameAttribute, SoftDeletes; + use HasFactory, HasSafeStringAttribute, SoftDeletes; protected $guarded = []; diff --git a/app/Models/Tag.php b/app/Models/Tag.php index 8b8793673..3594d1072 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -2,21 +2,17 @@ namespace App\Models; -use App\Traits\HasSafeNameAttribute; -use Illuminate\Database\Eloquent\Casts\Attribute; +use App\Traits\HasSafeStringAttribute; class Tag extends BaseModel { - use HasSafeNameAttribute; + use HasSafeStringAttribute; protected $guarded = []; - public function name(): Attribute + protected function customizeName($value) { - return Attribute::make( - get: fn ($value) => strtolower($value), - set: fn ($value) => strtolower($value) - ); + return strtolower($value); } public static function ownedByCurrentTeam() diff --git a/app/Models/Team.php b/app/Models/Team.php index d5358df74..81638e31c 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -8,7 +8,7 @@ use App\Notifications\Channels\SendsEmail; use App\Notifications\Channels\SendsPushover; use App\Notifications\Channels\SendsSlack; use App\Traits\HasNotificationSettings; -use App\Traits\HasSafeNameAttribute; +use App\Traits\HasSafeStringAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; @@ -37,7 +37,7 @@ use OpenApi\Attributes as OA; class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, SendsSlack { - use HasNotificationSettings, HasSafeNameAttribute, Notifiable; + use HasNotificationSettings, HasSafeStringAttribute, Notifiable; protected $guarded = []; diff --git a/app/Support/ValidationPatterns.php b/app/Support/ValidationPatterns.php new file mode 100644 index 000000000..965142558 --- /dev/null +++ b/app/Support/ValidationPatterns.php @@ -0,0 +1,93 @@ + 'The name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().', + 'name.min' => 'The name must be at least :min characters.', + 'name.max' => 'The name may not be greater than :max characters.', + ]; + } + + /** + * Get validation messages for description fields + */ + public static function descriptionMessages(): array + { + return [ + 'description.regex' => 'The description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.', + 'description.max' => 'The description may not be greater than :max characters.', + ]; + } + + /** + * Get combined validation messages for both name and description fields + */ + public static function combinedMessages(): array + { + return array_merge(self::nameMessages(), self::descriptionMessages()); + } +} diff --git a/app/Traits/HasSafeNameAttribute.php b/app/Traits/HasSafeNameAttribute.php deleted file mode 100644 index e6c089f70..000000000 --- a/app/Traits/HasSafeNameAttribute.php +++ /dev/null @@ -1,14 +0,0 @@ -attributes['name'] = strip_tags($value); - } -} diff --git a/app/Traits/HasSafeStringAttribute.php b/app/Traits/HasSafeStringAttribute.php new file mode 100644 index 000000000..8a5d2ce77 --- /dev/null +++ b/app/Traits/HasSafeStringAttribute.php @@ -0,0 +1,25 @@ +attributes['name'] = $this->customizeName($sanitized); + } + + protected function customizeName($value) + { + return $value; // Default: no customization + } + + public function setDescriptionAttribute($value) + { + $this->attributes['description'] = strip_tags($value); + } +}