Configurando la seguridad

10/17

Lectura

Implementando el control de accesos

A partir de este momento debemos distinguir los diferentes tipos de usuario que manejamos, de modo de mostrarle a cada uno s贸lo lo que le corresponde.

Esto responde a dos necesidades complementarias:

  1. Mantener la seguridad de la informaci贸n, es decir, evitar que alguien vea algo que no debe.
  2. Dar una experiencia acorde a cada usuario.

Para lograr estos objetivos necesitamos, ante todo, saber qui茅n es el visitante.

Esto se consigue mediante un sistema de login.

Desde ya que podr铆amos implementar uno propio pero鈥 驴para qu茅 esforzarnos si alguien ya lo ha hecho por nosotros? 馃槂

Crear una clase User

El primer paso en la implementaci贸n de un sistema de login es contar con una clase que maneje los usuarios: la clase User.

Arranquemos cre谩ndola con este comando:

php bin/console make:user

``En nuestro caso usaremos el campousernamecomo identificador y almacenaremos esta informaci贸n en nuestra base de datos a trav茅s de Doctrine.

Al finalizar este comando tendremos cambios al modelo de datos, con lo cual, deberemos crear y ejecutar una nueva migraci贸n:

php bin/console make:migration
php bin/console doctrine:migrations:migrate

Y listo! Tenemos nuestra clase User completa 馃槂

C贸mo crear un sistema de login

La seguridad (o mejor dicho, el control de accesos) en Symfony se maneja a trav茅s del archivo de configuraci贸n: config/packages/security.yaml.

Hay mucho que puede tocarse dentro de este archivo para personalizar el comportamiento de la aplicaci贸n, sin embargo, para efectos de este curso estaremos bien con los valores por defecto.

Comencemos pues con lo que nos toca hacer:

Paso 1: crear el formulario de login

Usa el comando php bin/console make:auth

  1. Elige la opci贸n 1 (Login form authenticator)
  2. Selecciona los valores por defecto hasta el final

Todos estos comandos crear谩n el c贸digo necesario para armar el formulario de login (Php y Twig) y procesar los datos enviados por el usuario y tambi茅n har谩n las modificaciones necesarias en el archivo config/packages/security.yaml.

Vamos a ver qu茅 logramos con todo esto.

Apunta tu navegador a http://homestead.test/login.

Si todo est谩 en su lugar te encontrar谩s con una pantalla como esta:

Nada mal, 驴cierto? Bueno鈥 si quieres ponerla m谩s bonita, 隆adelante! S贸lo debes meterle mano al templatetemplates/security/login.html.twig

Bueno, hasta aqu铆 todo fue magia鈥 es hora de trabajar un poco 馃槂

Si vuelves un poco atr谩s notar谩s que te qued贸 tarea para el hogar luego del comando make:auth

Finish the redirect "TODO" in the App\Security\LoginFormAuthenticator::onAuthenticationSuccess() method

Este m茅todo ser谩 invocado en forma autom谩tica cuando el login sea exitoso, es hora de decidir qu茅 se deber谩 hacer.

Te podr谩s imaginar que cada usuario deber谩 ser redireccionado a alguna p谩gina de bienvenida especial seg煤n su rol.

Abre el archivo src/Security/LoginFormAuthenticator.phpen tu editor favorito y busca el m茅todoonAuthenticationSuccess():

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
        if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
            return new RedirectResponse($targetPath);
        }

        // For example : return new RedirectResponse($this->urlGenerator->generate('some_route'));
        throw new \Exception('TODO: provide a valid redirect inside '.__FILE__);
    }

Y agrega estas l铆neas:

if ( in_array( 'ROLE_ADMIN', $token->getUser()->getRoles() ) ) {

   return new RedirectResponse($this->urlGenerator->generate('company'));
}

Antes del throw new \Exception.

De esta forma, cuando ingrese un usuario de tipo administrador ser谩 redireccionado al listado de empresas.

A medida que avancemos iremos agregando aqu铆 las redirecciones propias de los otros tipos de usuario.

Creando nuestro usuario administrador

Para crear nuestro usuario administrador tendremos que utilizar algunos trucos (lamentablemente aqu铆 no contamos con un comando m谩gico que haga todo).

Comienza por generar la contrase帽a escribiendo:

php bin/console security:encode-password admin

Copia el resultado que aparece en "Encoded password":

Y ejecuta el siguiente comando:

php bin/console doctrine:query:sql "INSERT INTO user (username, roles, password) VALUES ('admin', '[\"ROLE_ADMIN\"]', '\$argon2id\$v=19\$m=65536,t=4,p=1\$zHd8/9QmaFCkXITvBXNcKg\$kLZXWpuUrRH0FOhMaGbX4aZildau7gMym4PUHTTK44I');"

Con 茅l estamos enviando un comando a la base de datos a trav茅s de Doctrine.

Es importante notar que los caracteres $han sido escapados usando \$

Y ahora s铆, puedes realizar un login con las credenciales admin:admin 馃槂

Una vez pasado el login te encontrar谩s con el listado de empresas, desde all铆 podr谩s crear una nueva empresa usando los formularios definidos en la clase 6.

隆Genial!

Ahora es el momento de que un representante de la empresa ingrese y comience a cargar sus ofertas.

Lo que debemos hacer es generar un nuevo usuario para la empresa.

驴Pero c贸mo se vincular谩 nuestro usuario con la empresa? Es claro que alg煤n cambio tendremos que hacer a nuestro modelo de datos.

Recordemos que el modelo original se ve铆a as铆:

Screenshot_2020-06-09 Clase 7 - Seguridad(2).png

Pero si miramos nuestra base de datos o nuestro directorio src/Entitynotaremos que hay una clase que no est谩 (Applicant) y una que sobra.

Ninguna de estas circunstancias deber铆a llamar mucho la atenci贸n:

  1. A煤n no hemos llegado a desarrollar la parte del postulante.
  2. Al momento de comenzar nuestro an谩lisis no hab铆amos tenido en cuenta los usuarios.

驴De qu茅 forma se relacionan los usuarios con las otras entidades?

Para comenzar una empresa debe tener un usuario que la represente. La parte del postulante la veremos cuando llegue el momento.

Lo que debemos hacer pues es agregar una propiedad a nuestra clase.

Para ello usaremos un comando que ya vimos:

php bin/console make:entity Company

Nota como el comando tiene la inteligencia suficiente para darse cuenta de que no debe crear una nueva clase si no_ modificar_ la previamente existente.

De modo que lo que debes contestar es lo siguiente:

 New property name (press <return> to stop adding fields):
 > owner

 Field type (enter ? to see all types) [string]:
 > relation

 What class should this entity be related to?:
 > User

 Relation type? [ManyToOne, OneToMany, ManyToMany, OneToOne]:
 > OneToOne

 Is the Company.owner property allowed to be null (nullable)? (yes/no) [yes]:
 > no

 Do you want to add a new property to User so that you can access/update Company objects from it - e.g. $user->getCompany()? (yes/no) [no]:
 > no


隆Perfecto! Si abres el archivo src/Entity/Company notar谩s que ha aparecido una nueva propiedad:

/**
* @ORM\OneToOne(targetEntity="App\Entity\User", cascade={"persist", "remove"})
* @ORM\JoinColumn(nullable=false)
*/
private $owner;

Claro que eso solo no afecta a la base de datos鈥

Para tener el modelo completo debes ejecutar:

php bin/console make:migration

Verificar el contenido desrc/Migrations/Version20200420172310.php

Y ejecutar php bin/console doctrine:migrations:migrate

Ahora debemos hacer algunas adaptaciones al c贸digo que ya ten铆amos.

Comencemos por algo sencillo: agreguemos un link para que el administrador pueda crear una nueva empresa desde el listado:

Para crear ese link vamos a utilizar una funci贸n de Twig (Lo que se conoce como un helper) de modo de no hardcodear(ver nota) URLs.

Abre el archivo templates/company/index.htmly agrega

<p><a href="{{ url('company_new') }}">Nueva empresa</a></p>

En la l铆nea 23.

Esta funci贸n (url) utiliza una t茅cnica conocida como ruteo inverso: se genera una URL a partir del nombre de una ruta.

F铆jate en src/Controller/CompanyController.php como es la definici贸n del m茅todo index:

/**
* @Route("/company", name="company")
*/
public function index(EntityManagerInterface $entityManager)

El atributo name del annotation se utiliza como par谩metro para el helper url para generar un link cuyo atributo href es http://homestead.test:8000/company/new

Ahora entonces si te diriges a[http://homestead.test:8000/company](http://homestead.test:8000/company)y das click en el link ver谩s:

Algo falta 驴no? 驴C贸mo se especifica el owner?

De hecho, si intentas crear una empresa usando este formulario te encontrar谩s con:

Tenemos que modificar ese formulario鈥 no hay problema: Symfony al rescate 馃槂

Primero eliminemos el formulario existente y luego usemos php bin/console make:form para re-construir nuestro CompanyType(No olvides agregar el bot贸n de submit).

Y ahora, volvemos atr谩s al formulario, refrescamos la p谩gina y nos encontramos con:

驴Qu茅 sucedi贸? Twig est谩 tratando de generar HTML a partir de la definici贸n del formulario:

public function buildForm(FormBuilderInterface $builder, array $options)
{
   $builder
       ->add('name')
       ->add('email')
       ->add('owner')
       ->add('submit', SubmitType::class, [
           'label' => 'Guardar',
       ])
   ;
}

Cada uno de los campos se genera a partir de la definici贸n del widget (Objeto HTML) que le corresponde.

Si no aclaramos nada, Symfony lo adivina en base al tipo de datos del campo.

En el caso de owner, se trata de una entidad relacionada.

Symfony asocia este tipo de campos a una visualizaci贸n en forma de desplegable:

Y para generar este desplegable es necesario saber c贸mo transformar un objeto de clase User en un string (el que se mostrar谩 como opci贸n seleccionable).

驴C贸mo solucionarlo? Simple: hay que definir un m茅todo llamado __toStringen la clase User.

Ahora, como vemos, necesitamos crear primero los usuarios de cada empresa, para ello necesitaremos crear nuevos formularios.

Ve谩moslo en la pr贸xima clase.

Nota

Si no tienes muy claro el concepto de hardcoding te recomiendo el curso de 鈥淏uenas Pr谩cticas para la escritura de c贸digo鈥

Aportes 12

Preguntas 1

Ordenar por:

驴Quieres ver m谩s aportes, preguntas y respuestas de la comunidad? Crea una cuenta o inicia sesi贸n.

La verdad es que faltan ejemplos de c贸digo y comentarios, est谩 siendo un poco flojo en comparaci贸n con otros鈥

para convertir al usuario en string basta con colocar esto en la entidad user:

public function __toString()
    {
        return $this->username;
    }

Est谩 costando hacer este curso鈥 no porque sea dif铆cil sino por la falta de informaci贸n, en ning煤n momento dijo c贸mo se ten铆a que definir el m茅todo __toString, menos mal en los aportes si est谩

Para los que hayan usado

php bin/console make:crud

en la lecci贸n anterior, os recuerdo que en el archivo src/Security/LoginFormAuthenticator.php hay que cambiar en la redirecci贸n del rol administrador 鈥榗ompany鈥 por 鈥榗ompany_index鈥.

Estoy obteniendo este error

La verdad que muy malo el sistema y el curso en si. Decepcionante

Si tienen errores em el CompanyType (el formulario) puede ser que les falte la clase SubmiteType

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

Saludos, tuve un incoveniente en mi base de datos PostgreSQL, debido a que ya existe una tabla llamada user que gurda los roles, para ser espeficos se debe usa public.user.

$ php bin/console doctrine:query:sql "INSERT INTO public.user (username, roles, password ) VALUES ('admin', '["ROLE_ADMIN"]', '$argon2id$v=19$m=65536,t=4,p=1$5mXk+47BWJ4EEGBZU40htw$fNotSJsmOXfxn2LhIXkQlFtFvQydcYwL1Ej0nILbdLs' );"

驴En teor铆a el formulario lo crea el comando de consola?

al final en src/Entity/User dentro de la clase User

public function __toString()
    {
        return (string) $this->username;
    }

as铆 me funciona

Me esta tomando tiempo, pero ah铆 voy siguiendo el proyecto.
Muy importante los aportes de los compa帽eros.

Vamos funcionando, haciendo correcciones pero ah铆 vamos. Extra帽o los v铆deos.