symfony authentication autenticacion

autenticacion es el proceso por el que reconocemos si el que quiere ingresar a nuestro sitio es quien dice que es.
en esta etapa realizaremos lo que se conoce como logueo, o login.

vamos a usar authenfication de tipo Guard que aunque es mas trabajosa de implementar es mas flexible (a comparacion de sistemas built-in como form_login)

vamos a necesitar crear:

  • SecurityController.php : tendrá el loginAction
  • User.php : esta entidad implementará UserInterface
  • LoginFom.php : creará el formulario para el ingreso de username y password
  • LoginFormAuthenticator.php : aqui se realizará la obtencion de los datos de login y su validación.
  • login.html.twig


y también deberemos configurar:

  • services.yml : para registrar como servicio a LoginFormAuthenticator
  • security.yml : para indicar que la seguridad será manejada por el service ligada a LoginFormAuthenticator

class SecurityController extends Controller
{
/**
* @Route("/login", name="security_login")
*/
public function loginAction()
{
$authenticationUtils = $this->get('security.authentication_utils');
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
$form = $this->createForm(LoginForm::class, [
'_username' => $lastUsername,
]);
return $this->render(
'security/login.html.twig',
array(
'form' => $form->createView(),
'error' => $error,
)
);
}
}
**********************************************
/**
 * User
 *
 * @ORM\Table(name="user")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
 */
class User implements UserInterface
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column(type="string", unique=true)
     */
    private $email;

    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }
/**
* {@inheritDoc}
* @see \Symfony\Component\Security\Core\User\UserInterface::getRoles()
*/
public function getRoles() {
// TODO: Auto-generated method stub
return ['ROLE_USER'];
}



public function getUsername() {
// TODO: Auto-generated method stub
return $this->getEmail();
}


public function getEmail() {
return $this->email;
}
public function setEmail($email) {
$this->email = $email;
return $this;
}

}

notar que aun no estamos manejando un password por usuario
(la validación se hará luego contra una cadena cualquiera ,p.e: "123123")
el método getUsername, sólo devuelve el nombre de usuario para mostrar, en nuestro caso el nombre de usuario será el email.

**************
class LoginForm extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('_username')
->add('_password', PasswordType::class)
;
//_username for us, it's an email.
}
}
***************
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator {
private $formFactory;
private $em;
private $router;
public function __construct(FormFactoryInterface $formFactory, EntityManager $em, RouterInterface $router)
{
$this->formFactory = $formFactory;
$this->em = $em;
$this->router = $router;

}

public function getCredentials(Request $request) {
$isLoginSubmit = $request->getPathInfo() == '/login' && $request->isMethod('POST');
if (!$isLoginSubmit) {
// skip authentication
return;
}
$form = $this->formFactory->create(LoginForm::class);
$form->handleRequest($request);
$data = $form->getData();

$request->getSession()->set(
Security::LAST_USERNAME,
$data['_username']
);

return $data;
}
public function getUser($credentials, UserProviderInterface $userProvider) {
$username = $credentials['_username'];
return $this->em->getRepository('AppBundle:User')
->findOneBy(['email' => $username]);
}
public function checkCredentials($credentials, UserInterface $user) {
$password = $credentials['_password'];
if ($password == '123123') {
return true;
}
return false;
}
protected function getLoginUrl()
{
//nos determina a donde ir si el login falla
return $this->router->generate('security_login');
}
protected function getDefaultSuccessRedirectUrl()
{
//cuando el logueo es exitoso se regresará a la pagina desde la que se solicitó la authentication
//si no existiera esa pagina se retornorá por default a la home
return $this->router->generate('homepage');
}
}

esta clase será creada dentro de la nueva carpeta Security.
este authenticator se comunica con el SecurityController a travez de almacenar cosas en la session
por ejemplo guardando el ultimo username que falló en el logueo.

$request->getSession()->set(
Security::LAST_USERNAME,
$data['_username']
);

**********************************************

{# app/Resources/views/security/login.html.twig #} {# ... you will
probably extend your base template, like base.html.twig #}
{% extends 'base.html.twig' %}

{% block body %}
<div class="container">


<div class="row">
<div class="col-xs-12">
<h1>Login!</h1>
            {% if error %}
                <div class="alert alert-danger">
                    {{ error.messageKey|trans(error.messageData, 'security') }}
                </div>
            {% endif %}
{{ form_start(form) }}
                {{ form_row(form._username) }}
                {{ form_row(form._password) }}
<button type="submit" class="btn btn-success">Login <span class="fa fa-lock"></span></button>
            {{ form_end(form) }}


</form>
</div>
</div>
</div>
{% endblock %}

*********************************

services.yml

services:
     app.security.login_form_authenticator:
        class: AppBundle\Security\LoginFormAuthenticator
        autowire: true

**********************************

security.yml

security:

    providers:
        our_users:
            entity: { class: AppBundle\Entity\User, property: email }

    firewalls:
         main:
            guard:
                authenticators:
                    - app.security.login_form_authenticator


vamos ahora a hacer una verdadera validacion de password, entonces debemos agregar el campo en nuestra entidad User, password será el campo encodeado que se guardará en la tabla y plainPassword
el que guardara temporalmente el valor que se ingrese antes de encodearlo. Este campo no se persiste

/**
     * The encoded password
     *
     * @ORM\Column(type="string")
     */
    private $password;
 /**
     * campo non-persisted que se usará para crear el encoded password.
     *
     * @var string
     */
    private $plainPassword;


public function eraseCredentials() {
$this->plainPassword = null;
/*
* Symfony calls this after logging in,
* and it's just a minor  security measure
* to prevent the plain-text password from being accidentally saved anywhere.
*/

}
public function setPlainPassword($plainPassword) {
$this->plainPassword = $plainPassword;
// forces the object to look "dirty" to Doctrine. Avoids
// Doctrine *not* saving this entity, if only plainPassword changes
$this->password = null;
}
+++++++++++++

ahora vamos a implementar un listener que tendrá la tarea de disparar eventos para cuando se intente crear una entidad del tipo User (prePersist) o cuando se la intente actualizar (preUpdated)
este listener luego se lo registrará como servicio

lo crearemos dentro de una nueva carpeta llamada Doctrine:

class HashPasswordListener implements EventSubscriber {
/*
* Symfony comes with a built-in service that's really good at encoding passwords. 
* It's called security.password_encoder 
* and  its class is UserPasswordEncoder
*/
private $passwordEncoder;
public function __construct(UserPasswordEncoder $passwordEncoder)
{
$this->passwordEncoder = $passwordEncoder;
}
public function getSubscribedEvents() {
/* These are two event names that Doctrine makes available. 
* prePersist is called right before an entity is originally inserted.
* preUpdate is called right before an entity is updated. */
return ['prePersist', 'preUpdate'];
}
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();//obtenemos que entidad esta siendo salvada
/*
 * Now, if this is not an instanceof User, just return and do nothing
*/
if (!$entity instanceof User) {
return;
}
$this->encodePassword($entity);
}
public function preUpdate(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if (!$entity instanceof User) {
return;
}
$this->encodePassword($entity);
// necessary to force the update to see the change
$em = $args->getEntityManager();
$meta = $em->getClassMetadata(get_class($entity));
$em->getUnitOfWork()->recomputeSingleEntityChangeSet($meta, $entity);
}
private function encodePassword(User $entity)
{
if (!$entity->getPlainPassword()) {
return;
}
$encoded = $this->passwordEncoder->encodePassword(
$entity,
$entity->getPlainPassword()
);
$entity->setPassword($encoded);
}
}


en services.yml

services:
  app.doctrine.hash_password_listener:
        class: AppBundle\Doctrine\HashPasswordListener
        autowire: true
        tags:
            - { name: doctrine.event_subscriber }


en LoginFormAuthenticator.php

en el constructor agregamos un parametro extra para hacer referencia al encoder del password:
asi lo podemos usar para la validacion en checkCredentials

private $passwordEncoder;
public function __construct(FormFactoryInterface $formFactory, EntityManager $em, RouterInterface $router, UserPasswordEncoder $passwordEncoder)
{
$this->formFactory = $formFactory;
$this->em = $em;
$this->router = $router;
$this->passwordEncoder = $passwordEncoder;
}

public function checkCredentials($credentials, UserInterface $user) {
$password = $credentials['_password'];
if ($this->passwordEncoder->isPasswordValid($user, $password)) {
return true;
}
return false;
}


finalmente, en security.yml,
indicamos que encoder vamos a usar para encriptar los passwords

security:
encoders:
AppBundle\Entity\User: bcrypt

eclipse trabajar un proyecto php pdt

para facilitarte enormemente el trabajo en el desarrollo de un proyecto PHP,
hay que instalarle el plugin de extensiones PDT(PHP Development Tools)

Ir a Help -> Install New Software -> agregar la url: "http://p2.pdt-extensions.org" -> select PDT Extensions de la lista.



symfony formularios DateType



ahora vamos a agregar a nuestra entidad Employee el campo de su nacimiento

use Symfony\Component\Form\Extension\Core\Type\DateType;

/**
     * @ORM\Column(type="date",nullable=true)
     */
    private $birthday;

 /**
     * birthday
     * @return date
     */
    public function getBirthday(){
        return $this->birthday;
    }

    /**
     * birthday
     * @param date $birthday
     * @return Employee
     */
    public function setBirthday($birthday){
        $this->birthday = $birthday;
        return $this;
    }



ahora en el EmployeeType si le agregamos lo siguiente en el buildForm:

...

->add('birthday', DateType::class, [
    'widget' => 'single_text'
    ])


vamos a obtener un campo de texto con un formato de input dd/mm/yyyy que valida la fecha ingresada mientras se ingresa mas un datepicker desplegable.




symfony formularios validacion email

las reglas de validaciones de los campos se pueden hacer desde la entidad,
a traves de asserts,

use Symfony\Component\Validator\Constraints as Assert;

/**
     * @ORM\Column(type="string",nullable=true,length=100)
     * @Assert\Email()
     */
    private $email;

hará una validación de formato simple de lo que se ingrese. (que exista una @ y texto a ambos lados de ella) se muestra como tooltip.

Podemos cambiar el mensaje de direccion invalida (este mensaje se muestra como texto debajo del cuadro de input)

y checkMX en true, analiza el dns del servidor, si esto no estuviera podriamos ingresar jaja@cualquierfruta.com

/**
     * @ORM\Column(type="string",nullable=true,length=100)
     * @Assert\Email( message = "El email '{{ value }}' no es una dirección válida.",checkMX = true)
     */
    private $email;

symfony doctrine tipos de datos de mapeo

  • string: Type that maps a SQL VARCHAR to a PHP string.
  • integer: Type that maps a SQL INT to a PHP integer.
  • smallint: Type that maps a database SMALLINT to a PHP integer.
  • bigint: Type that maps a database BIGINT to a PHP string.
  • boolean: Type that maps a SQL boolean or equivalent (TINYINT) to a PHP boolean.
  • decimal: Type that maps a SQL DECIMAL to a PHP string.
  • date: Type that maps a SQL DATETIME to a PHP DateTime object.
  • time: Type that maps a SQL TIME to a PHP DateTime object.
  • datetime: Type that maps a SQL DATETIME/TIMESTAMP to a PHP DateTime object.
  • datetimetz: Type that maps a SQL DATETIME/TIMESTAMP to a PHP DateTime object with timezone.
  • text: Type that maps a SQL CLOB to a PHP string.
  • object: Type that maps a SQL CLOB to a PHP object using serialize() and unserialize()
  • array: Type that maps a SQL CLOB to a PHP array using serialize() and unserialize()
  • simple_array: Type that maps a SQL CLOB to a PHP array using implode() and explode(), with a comma as delimiter. IMPORTANT Only use this type if you are sure that your values cannot contain a ”,”.
  • json_array: Type that maps a SQL CLOB to a PHP array using json_encode() and json_decode()
  • float: Type that maps a SQL Float (Double Precision) to a PHP double. IMPORTANT: Works only with locale settings that use decimal points as separator.
  • guid: Type that maps a database GUID/UUID to a PHP string. Defaults to varchar but uses a specific type if the platform supports it.
  • blob: Type that maps a SQL BLOB to a PHP resource stream

All Date types assume that you are exclusively using the default timezone set by date_default_timezone_set() or by the php.ini configuration date.timezone. Working with different timezones will cause troubles and unexpected behavior.
If you need specific timezone handling you have to handle this in your domain, converting all the values back and forth from UTC. There is also a cookbook entry on working with datetimes that gives hints for implementing multi timezone applications

symfony formularios EntityType

supongamos que tenemos la entidad Employee y otra llamada Nacionality :D
un empleado tiene una nacionalidad y una misma nacionalidad la pueden tener muchos empleados
la relacion es de manytoone. (y se modela en Employee ya que es quien en la tabla tendrá el foreign_key)

la idea es que en el formulario de creacion de empleado, podamos seleccionar su nacionalidad de una lista de nacionalidades, esto se lografacilmente con EntityType, veamos:

use Symfony\Bridge\Doctrine\Form\Type\EntityType;
...

class EmployeeType extends AbstractType
{
 
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
..

)->add('nacionality', EntityType::class, [
    'class' => 'AppBundle:Nacionality',
    // use the Nacionality.name property as the visible option string
    'choice_label' => 'name',
   
    // used to render a select box, check boxes or radios
    // 'multiple' => true,
    // 'expanded' => true,
])

por default nos va a ordenar la lista por name alfabeticamente ASCendente.
que pasaría si quisiera cambiar el orden o filtrar la lista por alguna otra condicion?

usamos el atributo query_builder

use Doctrine\ORM\EntityRepository;

->add('nacionality', EntityType::class, [
    'class' => 'AppBundle:Nacionality',
    // use the User.username property as the visible option string
    'choice_label' => 'name',
    'query_builder' => function (EntityRepository $er) {
                return $er->createQueryBuilder('u')
            ->orderBy('u.name', 'DESC');
    },
    // used to render a select box, check boxes or radios
    // 'multiple' => true,
    // 'expanded' => true,
    ])

una mejor manera es crear la query en el repository correspondiente a la entity,
entonces en nuestro caso en NacionalityRepository, creamos:

public function createAlphabeticalQueryBuilder()
{
return $this->createQueryBuilder('nacionality')
->orderBy('nacionality.name', 'DESC');
}

entonces nuestro query_builder en el form type quedaría asi:

'query_builder' => function (EntityRepository $er) {
    return $er->createAlphabeticalQueryBuilder()
    ;
    }


si queremos que la primera opción de la lista sea un texto del tipo "Seleccione una nacionalidad"
agregamos estas lineas

->add('nacionality', EntityType::class, [
    'class' => 'AppBundle:Nacionality',
    // use the User.username property as the visible option string
    'choice_label' => 'name',
    'query_builder' => function (EntityRepository $er) {
    return $er->createQueryBuilder('u')
    ->orderBy('u.name', 'DESC');
    },
    'placeholder' => 'Seleccione la nacionalidad',
    'empty_data'  => null
    ]);

previamente tendremos que haber creado, la entidad Nacionality:

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Nacionality
 *
 * @ORM\Table(name="nacionality")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\NacionalityRepository")
 */
class Nacionality
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255, unique=true)
     */
    private $name;


    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set name
     *
     * @param string $name
     * @return Nacionality
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }
}

y en Employee generar la relacion:

/**
     * @ORM\ManyToOne(targetEntity="Nacionality")
     * @ORM\JoinColumn(nullable=true)
     */
    private $nacionality;

    /**
     * nacionality
     * @return Nacionality
     */
    public function getNacionality(){
        return $this->nacionality;
    }

    /**
     * nacionality
     * @param Nacionality $nacionality
     * @return Employee
     */
    public function setNacionality($nacionality){
        $this->nacionality = $nacionality;
        return $this;
    }

symfony ejecutar un up / down de un archivo de migration

php app/console doctrine:migration:execute YYYYMMDDHHMMSS --up



php app/console doctrine:migration:execute YYYYMMDDHHMMSS --down


tambien podemos saber el estado de las migraciones, cual fue la ultima, la previa etc

a través del siguiente comando:

php app/console doctrine:migrations:status

s  == Configuration

    >> Name:                                               Application Migrations
    >> Database Driver:                                    pdo_mysql
    >> Database Name:                                      test_56
    >> Configuration Source:                               manually configured
    >> Version Table Name:                                 migration_versions
    >> Version Column Name:                                version
    >> Migrations Namespace:                               Application\Migrations
    >> Migrations Directory:                               C:\Users\ibazan\workspace-zennovia-56\test\app/DoctrineMigrations
    >> Previous Version:                                   2017-04-28 15:57:13 (20170428155713)
    >> Current Version:                                    2017-04-28 16:01:26 (20170428160126)
    >> Next Version:                                       Already at latest version
    >> Latest Version:                                     2017-04-28 16:01:26 (20170428160126)
    >> Executed Migrations:                                9
    >> Executed Unavailable Migrations:                    0
    >> Available Migrations:                               9

    >> New Migrations:                                     0

linux ubuntu mint actualizar chrome

 desde una terminal: $ sudo apt update $ sudo apt install google-chrome-stable