Skip to content

Commit 105b4a9

Browse files
fix(validation): add input validation for port exposes and port mappings fields
1 parent 3b2e6e1 commit 105b4a9

10 files changed

Lines changed: 74 additions & 10 deletions

File tree

app/Livewire/Project/Application/General.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,8 @@ protected function rules(): array
153153
'staticImage' => 'required',
154154
'baseDirectory' => array_merge(['required'], array_slice(ValidationPatterns::directoryPathRules(), 1)),
155155
'publishDirectory' => ValidationPatterns::directoryPathRules(),
156-
'portsExposes' => 'required',
157-
'portsMappings' => 'nullable',
156+
'portsExposes' => ['required', 'string', 'regex:/^(\d+)(,\d+)*$/'],
157+
'portsMappings' => ValidationPatterns::portMappingRules(),
158158
'customNetworkAliases' => 'nullable',
159159
'dockerfile' => 'nullable',
160160
'dockerRegistryImageName' => 'nullable',
@@ -209,6 +209,8 @@ protected function messages(): array
209209
'staticImage.required' => 'The Static Image field is required.',
210210
'baseDirectory.required' => 'The Base Directory field is required.',
211211
'portsExposes.required' => 'The Exposed Ports field is required.',
212+
'portsExposes.regex' => 'Ports exposes must be a comma-separated list of port numbers (e.g. 3000,3001).',
213+
...ValidationPatterns::portMappingMessages(),
212214
'isStatic.required' => 'The Static setting is required.',
213215
'isStatic.boolean' => 'The Static setting must be true or false.',
214216
'isSpa.required' => 'The SPA setting is required.',
@@ -752,6 +754,12 @@ public function submit($showToaster = true)
752754
$this->authorize('update', $this->application);
753755

754756
$this->resetErrorBag();
757+
758+
$this->portsExposes = str($this->portsExposes)->replace(' ', '')->trim()->toString();
759+
if ($this->portsMappings) {
760+
$this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
761+
}
762+
755763
$this->validate();
756764

757765
$oldPortsExposes = $this->application->ports_exposes;

app/Livewire/Project/Database/Clickhouse/General.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ protected function rules(): array
7979
'clickhouseAdminUser' => 'required|string',
8080
'clickhouseAdminPassword' => 'required|string',
8181
'image' => 'required|string',
82-
'portsMappings' => 'nullable|string',
82+
'portsMappings' => ValidationPatterns::portMappingRules(),
8383
'isPublic' => 'nullable|boolean',
8484
'publicPort' => 'nullable|integer',
8585
'publicPortTimeout' => 'nullable|integer|min:1',
@@ -94,6 +94,7 @@ protected function messages(): array
9494
{
9595
return array_merge(
9696
ValidationPatterns::combinedMessages(),
97+
ValidationPatterns::portMappingMessages(),
9798
[
9899
'clickhouseAdminUser.required' => 'The Admin User field is required.',
99100
'clickhouseAdminUser.string' => 'The Admin User must be a string.',
@@ -207,6 +208,9 @@ public function submit()
207208
try {
208209
$this->authorize('update', $this->database);
209210

211+
if ($this->portsMappings) {
212+
$this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
213+
}
210214
if (str($this->publicPort)->isEmpty()) {
211215
$this->publicPort = null;
212216
}

app/Livewire/Project/Database/Dragonfly/General.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ protected function rules(): array
9090
'description' => ValidationPatterns::descriptionRules(),
9191
'dragonflyPassword' => 'required|string',
9292
'image' => 'required|string',
93-
'portsMappings' => 'nullable|string',
93+
'portsMappings' => ValidationPatterns::portMappingRules(),
9494
'isPublic' => 'nullable|boolean',
9595
'publicPort' => 'nullable|integer',
9696
'publicPortTimeout' => 'nullable|integer|min:1',
@@ -106,6 +106,7 @@ protected function messages(): array
106106
{
107107
return array_merge(
108108
ValidationPatterns::combinedMessages(),
109+
ValidationPatterns::portMappingMessages(),
109110
[
110111
'dragonflyPassword.required' => 'The Dragonfly Password field is required.',
111112
'dragonflyPassword.string' => 'The Dragonfly Password must be a string.',
@@ -217,6 +218,9 @@ public function submit()
217218
try {
218219
$this->authorize('update', $this->database);
219220

221+
if ($this->portsMappings) {
222+
$this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
223+
}
220224
if (str($this->publicPort)->isEmpty()) {
221225
$this->publicPort = null;
222226
}

app/Livewire/Project/Database/Keydb/General.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ protected function rules(): array
9393
'keydbConf' => 'nullable|string',
9494
'keydbPassword' => 'required|string',
9595
'image' => 'required|string',
96-
'portsMappings' => 'nullable|string',
96+
'portsMappings' => ValidationPatterns::portMappingRules(),
9797
'isPublic' => 'nullable|boolean',
9898
'publicPort' => 'nullable|integer',
9999
'publicPortTimeout' => 'nullable|integer|min:1',
@@ -111,6 +111,7 @@ protected function messages(): array
111111
{
112112
return array_merge(
113113
ValidationPatterns::combinedMessages(),
114+
ValidationPatterns::portMappingMessages(),
114115
[
115116
'keydbPassword.required' => 'The KeyDB Password field is required.',
116117
'keydbPassword.string' => 'The KeyDB Password must be a string.',
@@ -224,6 +225,9 @@ public function submit()
224225
try {
225226
$this->authorize('manageEnvironment', $this->database);
226227

228+
if ($this->portsMappings) {
229+
$this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
230+
}
227231
if (str($this->publicPort)->isEmpty()) {
228232
$this->publicPort = null;
229233
}

app/Livewire/Project/Database/Mariadb/General.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ protected function rules(): array
7878
'mariadbDatabase' => 'required',
7979
'mariadbConf' => 'nullable',
8080
'image' => 'required',
81-
'portsMappings' => 'nullable',
81+
'portsMappings' => ValidationPatterns::portMappingRules(),
8282
'isPublic' => 'nullable|boolean',
8383
'publicPort' => 'nullable|integer',
8484
'publicPortTimeout' => 'nullable|integer|min:1',
@@ -92,6 +92,7 @@ protected function messages(): array
9292
{
9393
return array_merge(
9494
ValidationPatterns::combinedMessages(),
95+
ValidationPatterns::portMappingMessages(),
9596
[
9697
'name.required' => 'The Name field is required.',
9798
'mariadbRootPassword.required' => 'The Root Password field is required.',
@@ -213,6 +214,9 @@ public function submit()
213214
try {
214215
$this->authorize('update', $this->database);
215216

217+
if ($this->portsMappings) {
218+
$this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
219+
}
216220
if (str($this->publicPort)->isEmpty()) {
217221
$this->publicPort = null;
218222
}

app/Livewire/Project/Database/Mongodb/General.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ protected function rules(): array
7777
'mongoInitdbRootPassword' => 'required',
7878
'mongoInitdbDatabase' => 'required',
7979
'image' => 'required',
80-
'portsMappings' => 'nullable',
80+
'portsMappings' => ValidationPatterns::portMappingRules(),
8181
'isPublic' => 'nullable|boolean',
8282
'publicPort' => 'nullable|integer',
8383
'publicPortTimeout' => 'nullable|integer|min:1',
@@ -92,6 +92,7 @@ protected function messages(): array
9292
{
9393
return array_merge(
9494
ValidationPatterns::combinedMessages(),
95+
ValidationPatterns::portMappingMessages(),
9596
[
9697
'name.required' => 'The Name field is required.',
9798
'mongoInitdbRootUsername.required' => 'The Root Username field is required.',
@@ -213,6 +214,9 @@ public function submit()
213214
try {
214215
$this->authorize('update', $this->database);
215216

217+
if ($this->portsMappings) {
218+
$this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
219+
}
216220
if (str($this->publicPort)->isEmpty()) {
217221
$this->publicPort = null;
218222
}

app/Livewire/Project/Database/Mysql/General.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ protected function rules(): array
8080
'mysqlDatabase' => 'required',
8181
'mysqlConf' => 'nullable',
8282
'image' => 'required',
83-
'portsMappings' => 'nullable',
83+
'portsMappings' => ValidationPatterns::portMappingRules(),
8484
'isPublic' => 'nullable|boolean',
8585
'publicPort' => 'nullable|integer',
8686
'publicPortTimeout' => 'nullable|integer|min:1',
@@ -95,6 +95,7 @@ protected function messages(): array
9595
{
9696
return array_merge(
9797
ValidationPatterns::combinedMessages(),
98+
ValidationPatterns::portMappingMessages(),
9899
[
99100
'name.required' => 'The Name field is required.',
100101
'mysqlRootPassword.required' => 'The Root Password field is required.',
@@ -220,6 +221,9 @@ public function submit()
220221
try {
221222
$this->authorize('update', $this->database);
222223

224+
if ($this->portsMappings) {
225+
$this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
226+
}
223227
if (str($this->publicPort)->isEmpty()) {
224228
$this->publicPort = null;
225229
}

app/Livewire/Project/Database/Postgresql/General.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ protected function rules(): array
9292
'postgresConf' => 'nullable',
9393
'initScripts' => 'nullable',
9494
'image' => 'required',
95-
'portsMappings' => 'nullable',
95+
'portsMappings' => ValidationPatterns::portMappingRules(),
9696
'isPublic' => 'nullable|boolean',
9797
'publicPort' => 'nullable|integer',
9898
'publicPortTimeout' => 'nullable|integer|min:1',
@@ -107,6 +107,7 @@ protected function messages(): array
107107
{
108108
return array_merge(
109109
ValidationPatterns::combinedMessages(),
110+
ValidationPatterns::portMappingMessages(),
110111
[
111112
'name.required' => 'The Name field is required.',
112113
'postgresUser.required' => 'The Postgres User field is required.',
@@ -456,6 +457,9 @@ public function submit()
456457
try {
457458
$this->authorize('update', $this->database);
458459

460+
if ($this->portsMappings) {
461+
$this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
462+
}
459463
if (str($this->publicPort)->isEmpty()) {
460464
$this->publicPort = null;
461465
}

app/Livewire/Project/Database/Redis/General.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ protected function rules(): array
7373
'description' => ValidationPatterns::descriptionRules(),
7474
'redisConf' => 'nullable',
7575
'image' => 'required',
76-
'portsMappings' => 'nullable',
76+
'portsMappings' => ValidationPatterns::portMappingRules(),
7777
'isPublic' => 'nullable|boolean',
7878
'publicPort' => 'nullable|integer',
7979
'publicPortTimeout' => 'nullable|integer|min:1',
@@ -89,6 +89,7 @@ protected function messages(): array
8989
{
9090
return array_merge(
9191
ValidationPatterns::combinedMessages(),
92+
ValidationPatterns::portMappingMessages(),
9293
[
9394
'name.required' => 'The Name field is required.',
9495
'image.required' => 'The Docker Image field is required.',
@@ -201,6 +202,9 @@ public function submit()
201202
try {
202203
$this->authorize('manageEnvironment', $this->database);
203204

205+
if ($this->portsMappings) {
206+
$this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
207+
}
204208
$this->syncData(true);
205209

206210
if (version_compare($this->redisVersion, '6.0', '>=')) {

app/Support/ValidationPatterns.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,12 @@ public static function volumeNameMessages(string $field = 'name'): array
194194
];
195195
}
196196

197+
/**
198+
* Pattern for port mappings (e.g. 3000:3000, 8080:80, 8000-8010:8000-8010)
199+
* Each entry requires host:container format, where each side can be a number or a range (number-number)
200+
*/
201+
public const PORT_MAPPINGS_PATTERN = '/^(\d+(-\d+)?:\d+(-\d+)?)(,\d+(-\d+)?:\d+(-\d+)?)*$/';
202+
197203
/**
198204
* Get validation rules for container name fields
199205
*/
@@ -202,6 +208,24 @@ public static function containerNameRules(int $maxLength = 255): array
202208
return ['string', 'max:'.$maxLength, 'regex:'.self::CONTAINER_NAME_PATTERN];
203209
}
204210

211+
/**
212+
* Get validation rules for port mapping fields
213+
*/
214+
public static function portMappingRules(): array
215+
{
216+
return ['nullable', 'string', 'regex:'.self::PORT_MAPPINGS_PATTERN];
217+
}
218+
219+
/**
220+
* Get validation messages for port mapping fields
221+
*/
222+
public static function portMappingMessages(string $field = 'portsMappings'): array
223+
{
224+
return [
225+
"{$field}.regex" => 'Port mappings must be a comma-separated list of port pairs or ranges (e.g. 3000:3000,8080:80,8000-8010:8000-8010).',
226+
];
227+
}
228+
205229
/**
206230
* Check if a string is a valid Docker container name.
207231
*/

0 commit comments

Comments
 (0)