lunes, 9 de junio de 2014

Implementando mi Módulo de Seguridad en SF2 Parte 2

Ya se han generado los CRUD de las entidades, sin embargo aun faltan algunas cosas para que funcione correctamente la autenticación de usuarios. En esta sección se personalizará el formulario correspondiente al usuario para agregar los campos correspondientes a su perfil, así como su contraseña.
Tambien se se configurará la entidad Usuarios para que proporciones los usuarios desde la base de datos.


Personalizando el form UsuariosType

Se necesitan agregar los campos correspondientes al perfil, por lo que primeramente es necesario crear for PerfilesType.
//src/Xanadu/SeguridadBundle/Form/PerfilesType.php
<?php

namespace Xanadu\SeguridadBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class PerfilesType extends AbstractType
{
    /**
    * @param FormBuilderInterface $builder
    * @param array $options
    */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('telefonoParticular')
            ->add('domicilio')

        ;
    }

    /**
    * @param OptionsResolverInterface $resolver
    */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Xanadu\SeguridadBundle\Entity\Perfiles'
        ));
    }

    /**
    * @return string
    */
    public function getName()
    {
        return 'xanadu_seguridadbundle_perfiles';
    }
}
El siguiente paso es modificar el formulario UsuariosType para agregar el perfil y modificar el campo de password.
<?php

use Xanadu\SeguridadBundle\Form\PerfilesType;
...

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('nombreUsuario')
            ->add('password', 'repeated', array(
                'type' => 'password',
                'invalid_message' => 'Las contraseñas no son iguales.',

                'required' => true,
                'first_options'  => array('label' => 'Password'),
                'second_options' => array('label' => 'Confirma Password'),
                'required' => false
            ))
            ->add('email')
            ->add('perfil', new PerfilesType())
            ->add('grupos')
            ->add('permisos')
        ;
    }
Como se puede obsevar, se ha incluido el formulario PerfilesType. Ahora es necesario modificar la acción createAction de el controlador UsuariosController para que al perfil se le asgine el usuario.
//...

      if ($form->isValid()) {
          $em = $this->getDoctrine()->getManager();
          //Se obtiene el usuario para asignarselo al perfil
          $form->getData()->getPerfil()->setUsuario($form->getData());
          $em->persist($entity);
          $em->flush();

          return $this->redirect($this->generateUrl('seguridad_usuarios_show', array('id' => $entity->getId())));
      }
Con esto ya se pueden dar de alta usuarios, sin embargo aun no se codifica la clave. Para ver el código de esta sección consulta la etiqueta git checkout seguridad1.2.

Implementando AdvancedUserInterface

Para continuar con la seguridad, se mecesitan agregar dos campos a la entidad Usuarios.
Campo Descripcion
salt Servirá para codificar el password
activo Indica si el Usuario está activo
Además es necesario que la entidad Usuarios implemente las interfaces AdvancedUserInterface y Serializable. Al implementar estas interfaces se necesitan agregar definir sus métodos, por lo que la entidad queda como se muestra a continuación.
<?php

namespace Xanadu\SeguridadBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\AdvancedUserInterface;

/**
* Usuarios
*
* @ORM\Table()
* @ORM\Entity
*/
class Usuarios implements AdvancedUserInterface, \Serializable
{
    ...

    /**
    *
    * @ORM\Column(name="Salt", type="string", length=255)
    */
    private $salt;

    /**
    *
    * @ORM\Column(name="Activo", type="boolean", length=255)
    */
    private $activo = true;



    //Inicio de funciones para seguridad

    public function serialize() {
        return serialize(array(
            $this->id
        ));
    }

    public function unserialize($serialized) {
        list (
            $this->id
        ) = unserialize($serialized);
    }

    public function eraseCredentials() {

    }

    public function getRoles() {

    }

    public function getSalt() {
        return $this->salt;
    }

    public function getUsername() {

    }

    public function isAccountNonExpired() {
        return true;
    }

    public function isAccountNonLocked() {
        return true;
    }

    public function isCredentialsNonExpired() {
        return true;
    }

    public function isEnabled() {
        return $this->activo;
    }

    public function getPassword() {
        return $this->password;
    }
    ...
}
Al implementar la interfaz AdavancedUserInterfaz se necesita el método getPassword(), sin embargo como ya tenemos un campo password vamos a tener el método duplicado, por lo que será necesario borrar el que se genero automáticamente al ejecutar el comando doctrine:generate:entities.
El código se puede consultar por medio del comando git checkout seguridad1.3.

Codificando el password

Para codificar el password, primero indicaremos que tipo de encriptación utilizaremos. Así que se procede a modificar al security.yml, bajo la clave encoders se agrega lo siguiente.
security:
    encoders:
        Xanadu\SeguridadBundle\Entity\Usuarios:
            algorithm: sha1
            encode_as_base64: false
            iterations: 1
    ...
Con esto se indica que se utilizará el algoritmo sha1 para cifrar el password. Como necesitara un valor para el campo salt para generar la contraseña, se modifica el constructor de la entidad Usuarios.
<?php

    /**
    * Constructor
    */
    public function __construct()
    {
        $this->grupos = new \Doctrine\Common\Collections\ArrayCollection();
        $this->permisos = new \Doctrine\Common\Collections\ArrayCollection();
        $this->salt = md5(uniqid(null, true));
    }
Lo que sigue a continuación es modificar el controlador UsuariosController, se agrega la instrucción $this->cifrarPassword($entity); en la acciones createAction y updateAction.
<?php
    ...
    public function createAction(Request $request)
    {
        ...

        if ($form->isValid()) {
            $em = $this->getDoctrine()->getManager();
            //Se asigna el usuario al perfil
            $entity->getPerfil()->setUsuario($entity);
            //Cifrando la contraseña
            $this->cifrarPassword($entity);
            ...
        }

        ....
    }

    ...
    public function updateAction(Request $request, $id)
    {
        ...

        if ($editForm->isValid()) {
            $this->cifrarPassword($entity, $entity->getPassword());
            ...
        }

        ...
    }
A continuación el código de la function cifrarPassword:
<?php
    ...

    private function cifrarPassword($usuario)
    {
        $password = $usuario->getPassword();
        if(!empty($password)) {
            $factory = $this->get('security.encoder_factory');
            $encoder = $factory->getEncoder($usuario);
            $password = $encoder->encodePassword($password, $usuario->getSalt());
            $usuario->setPassword($password);
        } else {
            $em = $this->getDoctrine()->getManager();
            $passwordActual = $em->getRepository("XanaduSeguridadBundle:Usuarios")->recuperarPassword($usuario);
            $usuario->setPassword($passwordActual);
        }
    }

    ...
Lo que hace esta función es recuperar el tipo de cifrado que se necesita para la clave, cuando el usuario es nuevo, siempre va a tener una clave a codificar, por lo que se ejecuta el código dentro de if(!empty($password). Sin embargo cuando se está actualizando un usuario, puede que el valor de la contraseña venga vacio, por lo que se necesita recuperar la contraseña actual del usuario.
Asi que para que esta función se ejecute correctamente, se creará el repositorio UsuariosRepository que contendra la función recuperarPassword. A continuación se muestra el código del repositorio.
<?php

namespace Xanadu\SeguridadBundle\Repository;

use Doctrine\ORM\EntityRepository;

/**
* UsuariosHasPermisosRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class UsuariosRepository extends EntityRepository
{
    public function recuperarPassword($usuario)
    {
        $dql = "SELECT u.password FROM XanaduSeguridadBundle:Usuarios u WHERE u.id = :usuario";
        $consulta = $this->getEntityManager()->createQuery($dql)
                ->setParameter("usuario", $usuario);

        return $consulta->getSingleScalarResult();
    }
}
Y tambien es necesario modificar la entidad para indicarle el repositorio.
<?php

...

/**
* Usuarios
*
* @ORM\Table()
* @ORM\Entity(repositoryClass="Xanadu\SeguridadBundle\Repository\UsuariosRepository")
*/

class Usuarios implements AdvancedUserInterface, \Serializable
{
    ...
}
Hasta este momento la contraseña ya se puede codificar usando sha1, el código se puede consultar con la instrucción git checkout seguridad1.4.

No hay comentarios.:

Publicar un comentario