Los formularios de Symfony 1.4

2.4. Seguridad de los validadores

Por defecto, los formularios sólo son válidos si todos los campos rellenados por el usuario disponen de un validador. De esta forma, Symfony se asegura que todos los campos tienen establecidas reglas de validación y también se asegura que no sea posible incluir información en campos que no están definidos en el formulario original.

Para comprender mejor la seguridad de los formularios, se muestra el ejemplo del listado 2-6 en el que se utiliza un objeto de tipo usuario.

Listado 2-6 - La clase Usuario

class Usuario
{
  protected
    $nombre = '',
    $es_administrador = false;

  public function setCampos($campos)
  {
    if (isset($campos['nombre']))
    {
      $this->nombre = $campos['nombre'];
    }

    if (isset($campos['es_administrador']))
    {
      $this->es_administrador = $campos['es_administrador'];
    }
  }

  // ...
}

El objeto Usuario incluye dos propiedades, el nombre del usuario (nombre) y un valor booleano que indica si el usuario es de tipo administrador (es_administrador). El método setCampos() actualiza el valor de estas dos propiedades. El listado 2-7 muestra el formulario relacionado con la clase Usuario y que permite modificar solamente la propiedad nombre.

Listado 2-7 - Formulario Usuario

class UsuarioForm extends sfForm
{
  public function configure()
  {
    $this->setWidgets(array('nombre' => new sfWidgetFormInputString()));
    $this->widgetSchema->setNameFormat('usuario[%s]');

    $this->setValidators(array('nombre' => new sfValidatorString()));
  }
}

El listado 2-8 muestra el código de un módulo llamado usuario que utiliza el formulario anterior para permitir al usuario cambiar el valor del campo nombre.

Listado 2-8 - Código del módulo usuario

class usuarioActions extends sfActions
{
  public function executeIndex($request)
  {
    $this->formulario = new UsuarioForm();

    if ($request->isMethod('post'))
    {
      $this->formulario->bind($request->getParameter('usuario'));
      if ($this->formulario->isValid())
      {
        $usuario = // obtener el objeto del usuario

        $usuario->setCampos($this->formulario->getValues());

        $this->redirect('...');
      }
    }
  }
}

Si no se utiliza ninguna protección, el código de la aplicación es vulnerable ya que el usuario podría enviar el formulario con los campos nombre y es_administrador rellenos. Modificar el código HTML de los formularios para enviar cualquier información es muy sencillo gracias a herramientas como Firebug. Además, como el campo es_administrador no tiene asociado ningún validador, su valor siempre es válido. Por lo tanto, sea cual sea su valor, el método setCampos() no sólo actualiza la propiedad nombre sino que también modifica la propiedad es_administrador.

Si se prueba el código anterior con un formulario que incluya los campos nombre y es_administrador, se produce un error global y se muestra el mensaje de error "Extra field es_administrador, tal y como se observa en la figura 2-8. El error se produce porque algunos de los campos enviados no tienen ningún validador asociado, ya que el campo es_administrador no está definido en el formulario UsuarioForm.

Error producido porque no se ha definido un validador

Figura 2.8 Error producido porque no se ha definido un validador

Todos los validadores utilizados hasta el momento generan errores asociados con sus propios campos. Por lo tanto, ¿de dónde proviene este error global? Cuando se emplea el método setValidators(), Symfony crea un objeto de tipo sfValidatorSchema que a su vez define una colección de validadores. Invocar el método setValidators() es equivalente al siguiente código:

$this->setValidatorSchema(new sfValidatorSchema(array(
  'email'   => new sfValidatorEmail(),
  'asunto'  => new sfValidatorChoice(array('choices' => array_keys(self::$asuntos))),
  'mensaje' => new sfValidatorString(array('min_length' => 4)),
)));

El objeto sfValidatorSchema incluye dos reglas de validación activadas por defecto para proteger la colección de validadores. Estas reglas se pueden configurar con las opciones allow_extra_fields y filter_extra_fields.

La opción allow_extra_fields, que vale false por defecto, comprueba si todos los campos enviados disponen de un validador. En caso negativo, genera el error global "Extra field .", como sucedía en el ejemplo anterior. Cuando se desarrolla la aplicación esta opción es muy útil porque avisa a los programadores sobre todos los campos que todavía no disponen de un validador.

Volviendo al formulario de contacto, vamos a modificar sus reglas de validación y vamos a establecer que el campo nombre es obligatorio. Como el valor por defecto de la opción required es true, se puede establecer el validador de nombre simplemente como:

$validadorNombre = new sfValidatorString();

Como el validador no establece ni la opción min_length ni max_length, no tiene efecto sobre el texto introducido. De hecho, se podría reemplazar por un validador vacío:

$validadorNombre = new sfValidatorPass();

En el ejemplo anterior, en vez de utilizar un validador vacío, se puede eliminar por completo el validador, pero la protección por defecto del formulario impide eliminar el validador. El listado 2-9 muestra cómo deshabilitar la protección por defecto mediante la opción allow_extra_fields.

Listado 2-9 - Deshabilitando la protección allow_extra_fields

class ContactoForm extends sfForm
{
  public function configure()
  {
    // ...

    $this->setValidators(array(
      'email'   => new sfValidatorEmail(),
      'asunto'  => new sfValidatorChoice(array('choices' => array_keys(self::$asuntos))),
      'mensaje' => new sfValidatorString(array('min_length' => 4)),
    ));

    $this->validatorSchema->setOption('allow_extra_fields', true);
  }
}

Con el código anterior, ya es posible validar el formulario como se muestra en la figura 2-9.

Validando el formulario con la opción allow_extra_fields activada

Figura 2.9 Validando el formulario con la opción allow_extra_fields activada

Si observas con atención la imagen anterior, verás que aunque el formulario es válido, el valor del campo nombre está vacío en la página de agradecimiento, independientemente del valor que se haya escrito el usuario en ese campo. De hecho, el valor enviado no existe ni siquiera en el array que se obtiene mediante $this->formulario->getValues(). Deshabilitar la opción allow_extra_fields evita que se muestre el error debido a la falta del validador, pero como la otra opción llamada filter_extra_fields vale true por defecto, se filtran todos los campos que no tienen validador, por lo que sus valores no se encuentran entre los valores válidos. El listado 2-10 muestra cómo modificar este comportamiento.

Listado 2-10 - Deshabilitando la protección de filter_extra_fields

class ContactoForm extends sfForm
{
  public function configure()
  {
    // ...

    $this->setValidators(array(
      'email'   => new sfValidatorEmail(),
      'asunto'  => new sfValidatorChoice(array('choices' => array_keys(self::$asuntos))),
      'mensaje' => new sfValidatorString(array('min_length' => 4)),
    ));

    $this->validatorSchema->setOption('allow_extra_fields', true);
    $this->validatorSchema->setOption('filter_extra_fields', false);
  }
}

Ahora ya es posible validar el formulario y obtener el valor introducido por el usuario para mostrarlo en la página de agradecimiento.

En el capítulo 4 se muestra cómo utilizar estas protecciones para serializar de forma segura los objetos Propel a partir de los valores del formulario.