Skip to content

Commit ef43429

Browse files
author
Carlos Garcia
committed
- Ahora CodeModel permite usar lower() y upper() en los campos fieldCode y fieldDescription.
- Mejorados los tests unitarios de CodeModel.
1 parent 5c070f8 commit ef43429

File tree

2 files changed

+183
-1
lines changed

2 files changed

+183
-1
lines changed

Core/Model/CodeModel.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,9 @@ public static function setLimit(int $newLimit): void
270270
}
271271

272272
/**
273-
* Valida que un nombre de campo sea seguro para usar en consultas SQL.
273+
* Valída que un nombre de campo sea seguro para usar en consultas SQL.
274274
* Solo permite letras, números, guiones bajos y puntos (para campos con alias de tabla).
275+
* También permite el uso de las funciones lower() y upper().
275276
*
276277
* @param string $fieldName
277278
*
@@ -284,6 +285,11 @@ protected static function isValidFieldName(string $fieldName): bool
284285
return true;
285286
}
286287

288+
// permite lower() y upper() con un campo válido dentro
289+
if (preg_match('/^(lower|upper)\(([a-zA-Z0-9_\.]+)\)$/i', $fieldName, $matches)) {
290+
return true;
291+
}
292+
287293
// permite letras, números, guiones bajos y puntos (para tabla.campo)
288294
return preg_match('/^[a-zA-Z0-9_\.]+$/', $fieldName) === 1;
289295
}

Test/Core/Model/CodeModelTest.php

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,182 @@ public function testAllWithNonExistentTable(): void
241241
$this->assertEquals('------', $result[0]->description);
242242
}
243243

244+
public function testFieldNameValidation(): void
245+
{
246+
// Crear almacenes de prueba con códigos válidos (1-4 caracteres) en mayúsculas y nombres con mayúsculas/minúsculas
247+
$almacen1 = $this->getRandomWarehouse();
248+
$almacen1->codalmacen = 'TST1';
249+
$almacen1->nombre = 'Almacen Principal';
250+
$this->assertTrue($almacen1->save());
251+
252+
$almacen2 = $this->getRandomWarehouse();
253+
$almacen2->codalmacen = 'TST2';
254+
$almacen2->nombre = 'Almacen Secundario';
255+
$this->assertTrue($almacen2->save());
256+
257+
// Test con campos normales - el código debe estar en mayúsculas como se guardó
258+
$result = CodeModel::all('almacenes', 'codalmacen', 'nombre', false);
259+
$this->assertIsArray($result);
260+
$found = false;
261+
foreach ($result as $item) {
262+
if ($item->code === 'TST1') {
263+
$this->assertEquals('Almacen Principal', $item->description);
264+
$found = true;
265+
break;
266+
}
267+
}
268+
$this->assertTrue($found, 'No se encontró el almacén TST1');
269+
270+
// Test con lower() en fieldCode - el código debe estar en minúsculas
271+
$result = CodeModel::all('almacenes', 'lower(codalmacen)', 'nombre', false);
272+
$this->assertIsArray($result);
273+
$found = false;
274+
foreach ($result as $item) {
275+
if ($item->code === 'tst1') {
276+
$this->assertEquals('Almacen Principal', $item->description);
277+
// Verificar que NO está en mayúsculas
278+
$this->assertNotEquals('TST1', $item->code);
279+
$found = true;
280+
break;
281+
}
282+
}
283+
$this->assertTrue($found, 'No se encontró el código en minúsculas tst1');
284+
285+
// Test con upper() en fieldCode - el código debe estar en mayúsculas
286+
$result = CodeModel::all('almacenes', 'upper(codalmacen)', 'nombre', false);
287+
$this->assertIsArray($result);
288+
$found = false;
289+
foreach ($result as $item) {
290+
if ($item->code === 'TST1') {
291+
$this->assertEquals('Almacen Principal', $item->description);
292+
$found = true;
293+
break;
294+
}
295+
}
296+
$this->assertTrue($found, 'No se encontró el código en mayúsculas TST1');
297+
298+
// Test con LOWER() en mayúsculas (case-insensitive) - debe funcionar igual
299+
$result = CodeModel::all('almacenes', 'LOWER(codalmacen)', 'nombre', false);
300+
$this->assertIsArray($result);
301+
$found = false;
302+
foreach ($result as $item) {
303+
if ($item->code === 'tst2') {
304+
$this->assertEquals('Almacen Secundario', $item->description);
305+
$found = true;
306+
break;
307+
}
308+
}
309+
$this->assertTrue($found, 'LOWER() no funcionó correctamente');
310+
311+
// Test con lower() en fieldDescription - la descripción debe estar en minúsculas
312+
$result = CodeModel::all('almacenes', 'codalmacen', 'lower(nombre)', false);
313+
$this->assertIsArray($result);
314+
$found = false;
315+
foreach ($result as $item) {
316+
if ($item->code === 'TST1') {
317+
$this->assertEquals('almacen principal', $item->description);
318+
// Verificar que NO tiene mayúsculas
319+
$this->assertNotEquals('Almacen Principal', $item->description);
320+
$found = true;
321+
break;
322+
}
323+
}
324+
$this->assertTrue($found, 'No se encontró la descripción en minúsculas');
325+
326+
// Test con upper() en fieldDescription - la descripción debe estar en mayúsculas
327+
$result = CodeModel::all('almacenes', 'codalmacen', 'upper(nombre)', false);
328+
$this->assertIsArray($result);
329+
$found = false;
330+
foreach ($result as $item) {
331+
if ($item->code === 'TST2') {
332+
$this->assertEquals('ALMACEN SECUNDARIO', $item->description);
333+
// Verificar que NO tiene minúsculas
334+
$this->assertNotEquals('Almacen Secundario', $item->description);
335+
$found = true;
336+
break;
337+
}
338+
}
339+
$this->assertTrue($found, 'No se encontró la descripción en mayúsculas');
340+
341+
// Test con lower() en ambos campos
342+
$result = CodeModel::all('almacenes', 'lower(codalmacen)', 'lower(nombre)', false);
343+
$this->assertIsArray($result);
344+
$found = false;
345+
foreach ($result as $item) {
346+
if ($item->code === 'tst1') {
347+
$this->assertEquals('almacen principal', $item->description);
348+
$found = true;
349+
break;
350+
}
351+
}
352+
$this->assertTrue($found, 'No funcionó lower() en ambos campos');
353+
354+
// Test con upper() en ambos campos
355+
$result = CodeModel::all('almacenes', 'upper(codalmacen)', 'upper(nombre)', false);
356+
$this->assertIsArray($result);
357+
$found = false;
358+
foreach ($result as $item) {
359+
if ($item->code === 'TST2') {
360+
$this->assertEquals('ALMACEN SECUNDARIO', $item->description);
361+
$found = true;
362+
break;
363+
}
364+
}
365+
$this->assertTrue($found, 'No funcionó upper() en ambos campos');
366+
367+
// Test con campo de tabla con punto
368+
$result = CodeModel::all('almacenes', 'almacenes.codalmacen', 'almacenes.nombre', false);
369+
$this->assertIsArray($result);
370+
$found = false;
371+
foreach ($result as $item) {
372+
if ($item->code === 'TST1') {
373+
$found = true;
374+
break;
375+
}
376+
}
377+
$this->assertTrue($found, 'No funcionó con tabla.campo');
378+
379+
// Test con lower() y tabla.campo
380+
$result = CodeModel::all('almacenes', 'lower(almacenes.codalmacen)', 'nombre', false);
381+
$this->assertIsArray($result);
382+
$found = false;
383+
foreach ($result as $item) {
384+
if ($item->code === 'tst1') {
385+
$found = true;
386+
break;
387+
}
388+
}
389+
$this->assertTrue($found, 'No funcionó lower() con tabla.campo');
390+
391+
// Test con función no permitida concat() - debe fallar
392+
$result = CodeModel::all('almacenes', 'concat(codalmacen, nombre)', 'nombre', true);
393+
$this->assertIsArray($result);
394+
$this->assertCount(1, $result); // Solo debe retornar el elemento vacío
395+
$this->assertNull($result[0]->code);
396+
397+
// Test con función no permitida substring() - debe fallar
398+
$result = CodeModel::all('almacenes', 'codalmacen', 'substring(nombre, 1, 10)', true);
399+
$this->assertIsArray($result);
400+
$this->assertCount(1, $result); // Solo debe retornar el elemento vacío
401+
$this->assertNull($result[0]->code);
402+
403+
// Test con intento de SQL injection - debe fallar
404+
$result = CodeModel::all('almacenes', 'codalmacen; DROP TABLE almacenes--', 'nombre', true);
405+
$this->assertIsArray($result);
406+
$this->assertCount(1, $result); // Solo debe retornar el elemento vacío
407+
$this->assertNull($result[0]->code);
408+
409+
// Test con caracteres especiales no permitidos - debe fallar
410+
$result = CodeModel::all('almacenes', 'codalmacen OR 1=1', 'nombre', true);
411+
$this->assertIsArray($result);
412+
$this->assertCount(1, $result); // Solo debe retornar el elemento vacío
413+
$this->assertNull($result[0]->code);
414+
415+
// Limpiar datos de prueba
416+
$this->assertTrue($almacen1->delete());
417+
$this->assertTrue($almacen2->delete());
418+
}
419+
244420
protected function tearDown(): void
245421
{
246422
$this->logErrors();

0 commit comments

Comments
 (0)