1
votes

I have an app that manages all of its users inside one entity (USER), implementing the UserInterface, as recommended. Linked to it, I got the CUSTOMER and the EMPLOYEE entities: each one storing specific infos about the user.

You may ask why I'm doing like this: an EMPLOYEE can be a CUSTOMER in many WORKPLACES and may work in many WORKPLACES. In order to avoid repetition, the common info got centralized in USER and the attribute Roles in USER is always blank in db.

The goal here is to load my roles based on specific data stored in another entity (WORKPLACE) when an EMPLOYEE logs in.

My great question may live in the UserRepository: when symfony calls the repository looking for the user entered on the login form, doctrine delivers it with no roles. I put my roles and return the User to the Authenticator (the vanilla one, I havent touched it yet). After the end of every requery, Symfony checks for the user but then doctrine loads the ROLE_CLIENT (in other words - no privileges).

What I already tried and failed

The entities

class User implements UserInterface
{
    /**
     * @ORM\Id()
     */
    private $cpf;

    /**
     * @ORM\Column
     * 
     */
    private $email;

    /**
     * @ORM\Column
     */
    private $nome;

    /**
     * @var string
     *
     * @ORM\Column
     */
    private $telefone;

    /**
     * @var \DateTime|null
     *
     * @ORM\Column
     */
    private $nascimento;

     /**
     * @var \DateTime|null
     *
     * @ORM\Column
     */
    private $ultimoLogin;

    /**
     * @var string|null
     *
     * @ORM\Column
     */
    private $endereco;

    /**
     * @var string|null
     *
     * @ORM\Column
     */
    private $cidade;

    /**
     * @var string|null
     *
     * @ORM\Column
     */
    private $uf;

    /**
     * @var int|null
     *
     * @ORM\Column
     */
    private $cep;

    /**
     * @ORM\Column(name="Roles", type="json")
     */
    private $roles = [];

    /**
     * @var string The hashed password
     * @ORM\Column(name="password", type="string")
     */
    private $password;

//vanilla getters and setters

Workplace entity

The booleans store the privileges i want to get

class LocalTrabalho
{
    /**
     * @var Configuracao
     * 
     * @ORM\ManyToOne(targetEntity=Configuracao::class, inversedBy="localTrabalho")
     * @ORM\JoinColumn(name="CNPJ", referencedColumnName="CNPJ", nullable=false)
     * @ORM\Id
     * the company unique code
     */
    private $cnpj;

    /**
     * @var Funcionario
     * 
     * @ORM\ManyToOne(targetEntity=Funcionario::class, inversedBy="localTrabalho")
     * @ORM\JoinColumn(name="CPF_Funcionario", referencedColumnName="CPF", nullable=false)
     * @ORM\Id
     * the user-employee unique code
     */
    private $cpfFuncionario;

    /**
     * @var bool
     * 
     * @ORM\Column
     * is this employee is active?
     */
    private $ativo = 1;

    /**
     * @var bool
     * 
     * @ORM\Column
     */
    private $privilegioCaixa = 0;

    /**
     * @var bool
     * 
     * @ORM\Column
     */
    private $privilegioPrestador = 1;

    /**
     * @var bool
     * 
     * @ORM\Column
     */
    private $privilegioRecepcao = 0;

    /**
     * @var bool
     * 
     * @ORM\Column
     */
    private $privilegioAdministracao = 0;

Employee Entity

class Funcionario
{
    /**
     * @var int
     *
     * @ORM\Column
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="NONE")
     */
    private $cpf;

    /**
     * @var string|null
     *
     * @ORM\Column
     */
    private $ctps;

    /**
     * @var string|null
     *
     * @ORM\Column
     */
    private $foto;

An example USER tuple from db

# CodPFis, email, password, Roles, Nome, Telefone, Nascimento, Ultimo_login, Endereco, Cidade, UF, CEP
'89038252099', 'sophiejenniferteresinhaoliveira__sophiejenniferteresinhaoliveira@grupomozue.com.br', '$argon2id$v=19$m=65536,t=4,p=1$WEn7b64I9744kRJICEpaLA$jcYLDvh2bZsZPakMDGsncpbfIZwR6lN0QcgJOOSerK0', NULL, 'João da Silva', '', NULL, NULL, NULL, NULL, NULL, NULL

My last resource is asking here, after some long and wasted work hours, what should I do (or what i did wrong)?

I've seen some other threads asking similar questions, but they're outdated (im using the version 5): Dynamic roles in symfony Symfony 2 - Loading roles from database Symfony2 - Dynamic Role Management How to update roles in security token in Symfony 4 without re-logging in Symfony User Logout After Role Change

PS: Someone tried this approach (https://github.com/symfony/symfony/issues/12025#issuecomment-562004005) and got success in symfony5?

1
The roles column is not a requirement of UserInterface, only the getter is. What I'm getting at is that you can crawl those relations and build the Roles array in the getRoles function in the entity. I can't give a more detailed explanation since you didn't tell exactly how User and Funcionario are 'linked', you could do it directly if they are modelled as relations or you might need some collaborators... - msg
Too much detail in your question. Perhaps you could try to reduce it to the actual problem. I think you are saying that you are initially adding the roles and things work? But then "After the end of every requery, Symfony checks for the user but then doctrine loads the ROLE_CLIENT (in other words - no privileges)." Don't know what that means at all. - Cerad
@Cerad To sum up, when a employee logs in, I intend to build its roles based on what company he "chose" to access (company's id is sent in the auth form). If that employee has no active jobs in any company (or no rules), he's granted as a client. I can get the manipulated user with the needed roles inside my FormLoginAuthenticator, but for the next requests, i get no clue how to keep doing this manipulation because symfony asks doctrine for a fresh copy of the user from db and get no rules - Igor Pereira
@msg User is where I keep credentials and general info about the user. If this user works somewhere, the brazilian laws require some more attributes to be stored. The link between these entities is the cpf - so in the db the funcionario is a "child table" of user. I will check out about the relations you said - Igor Pereira
That is where the user guard authenticators come in. Check the security section in the docs. Basically you have complete control of how the user and their roles are loaded. - Cerad

1 Answers

0
votes

Well, turns out that the best solution for my case was manipulate the User Repository. Doctrine uses them to operate the db so I just coded my rules inside and let the framework do the rest.

The algorithm is something like the shown below:

  • Find the user using the provided $id
  • If he/she works somewhere, load the ROLE constants into the User entity
  • If he/she does not have a job, load the profile as a common user
public function find($id, $lockmode=null, $lockversion = null){
        $us = $this->createQueryBuilder('u')
        ->andWhere('u.cpf = :cpf')
        ->setParameter('cpf', $id['cpf'])
        ->getQuery()->getOneOrNullResult();        
        $lt = $this->_em->createQuery(
            'SELECT t
            FROM App\Entity\LocalTrabalho t
            WHERE t.cpfFuncionario = :cpf'
        )
        ->setParameters(['cpf' => $us->getCpf()])
        ->getResult();

        if (count($lt) > 0){
            $regras = ['ROLE_FUNCIONARIO'];
            if ($lt[0]->getPrivilegioCaixa()) $regras[] = 'ROLE_CAIXA';
            if ($lt[0]->getPrivilegioPrestador()) $regras[] = 'ROLE_PRESTADOR';
            if ($lt[0]->getPrivilegioRecepcao()) $regras[] = 'ROLE_RECEPCAO';
            
            if ($lt[0]->getPrivilegioAdministracao())
            {
                $regras = ['ROLE_ADMIN'];
            }
            $us->setRoles($regras);
        }
        return $us;
}