1
votes

I have the Entities User (implements UserInterface which has a many to many relation to the Entity Role (implements RoleInterface).

My Project needs the ROLE_ADMIN after Login, otherwise it will return a AccessDenied Error. Furthermore I added a ROLE_ADMIN Role in the Role table in my database and created a User with a relation to this Role in my user_Roles (many to many) table.

But when I Login i get always the AccessDenied Error.

When I change my getRoles() method in User to return array('ROLE_ADMIN'); it works.

Any ideas?

Also I had to add this method in User, otherwise I couldn't add a User in my database.

  public function setUserRoles($userRoles)
    {
        if ( is_array($userRoles) ) {
            $this->userRoles = $userRoles ;
        } else {
            $this->userRoles->clear() ;
            $this->userRoles->add($userRoles) ;
        }
        return $this;
    }

User:

namespace Acme\AppBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\EquatableInterface;
/**
 * User
 *
 * @ORM\Table()
 * @ORM\Entity
 */
class User implements UserInterface, \Serializable
{   



    /**
     * @ORM\ManyToMany(targetEntity="Role", inversedBy="users")
     * @ORM\JoinTable(name="user_roles")
     *
     */
    private $userRoles;


    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

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


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

    /**
     * @ORM\Column(type="string", length=100)
     */
    private $password;

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

    /**
     * @ORM\Column(name="is_active", type="boolean")
     */
    private $isActive;

    public function __construct()
    {
        $this->isActive = true;
        $this->salt = md5(uniqid(null, true));
        $this->userRoles = new ArrayCollection();
    }

    /*
     * --Interface Methoden--
     */
    /**
     * @inheritDoc
     */
    public function getUsername()
    {
        return $this->username;
    }

    /**
     * @inheritDoc
     */
    public function getSalt()
    {
        return $this->salt;
    }

    /**
     * @inheritDoc
     */
    public function getPassword()
    {
        return $this->password;
    }

    /**
     * @inheritDoc
     */
    public function getRoles()
    {
        return $this->userRoles->toArray();

    }

    /**
     * @inheritDoc
     */
    public function eraseCredentials()
    {
    }

    /**
     * @see \Serializable::serialize()
     */
    public function serialize()
    {
        return serialize(array(
                $this->id,
        ));
    }

    /**
     * @see \Serializable::unserialize()
     */
    public function unserialize($serialized)
    {
        list (
                $this->id,
        ) = unserialize($serialized);
    }






    public function isEqualTo(UserInterface $user)
    {
        return $this->id === $user->getId();
    }

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

    /**
     * Set username
     *
     * @param string $username
     * @return User
     */
    public function setUsername($username)
    {
        $this->username = $username;

        return $this;
    }

    /**
     * Set salt
     *
     * @param string $salt
     * @return User
     */
    public function setSalt($salt)
    {
        $this->salt = $salt;

        return $this;
    }

    /**
     * Set password
     *
     * @param string $password
     * @return User
     */
    public function setPassword($password)
    {
        $this->password = $password;

        return $this;
    }



    /**
     * Set isActive
     *
     * @param boolean $isActive
     * @return User
     */
    public function setIsActive($isActive)
    {
        $this->isActive = $isActive;

        return $this;
    }

    /**
     * Get isActive
     *
     * @return boolean 
     */
    public function getIsActive()
    {
        return $this->isActive;
    }

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

        return $this;
    }

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



    public function setUserRoles($userRoles)
    {
        if ( is_array($userRoles) ) {
            $this->userRoles = $userRoles ;
        } else {
            $this->userRoles->clear() ;
            $this->userRoles->add($userRoles) ;
        }
        return $this;
    }

    /**
     * Add userRoles
     *
     * @param \Amce\AppBundle\Entity\Role $userRoles
     * @return User
     */
    public function addUserRole(\Acme\AppBundle\Entity\Role $userRoles)
    {
        $this->userRoles[] = $userRoles;

        return $this;
    }

    /**
     * Remove userRoles
     *
     * @param \Acme\AppBundle\Entity\Role $userRoles
     */
    public function removeUserRole(\Acme\AppBundle\Entity\Role $userRoles)
    {
        $this->userRoles->removeElement($userRoles);
    }

    /**
     * Get userRoles
     *
     * @return \Doctrine\Common\Collections\Collection 
     */
    public function getUserRoles()
    {
        return $this->userRoles;
    }
}

Role:

namespace Acme\AppBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Security\Core\Role\RoleInterface;
use Doctrine\ORM\Mapping as ORM;

/**
 * Role
 *
 * @ORM\Table()
 * @ORM\Entity
 */
class Role implements RoleInterface
{

    /**
     * @ORM\ManyToMany(targetEntity="User", mappedBy="userRoles")
     *
     */
    private $users;

    public function __construct()
    {
        $this->users = new ArrayCollection();
    }

    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

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


    /*
     * methods for RoleInterface
    */
    public function getRole()
    {
        $this->getName();
    }

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

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

        return $this;
    }

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

    /**
     * Add users
     *
     * @param \Acme\AppBundle\Entity\User $users
     * @return Role
     */
    public function addUser(\Acme\AppBundle\Entity\User $users)
    {
        $this->users[] = $users;

        return $this;
    }

    /**
     * Remove users
     *
     * @param \Acme\AppBundle\Entity\User $users
     */
    public function removeUser(\Acme\AppBundle\Entity\User $users)
    {
        $this->users->removeElement($users);
    }

    /**
     * Get users
     *
     * @return \Doctrine\Common\Collections\Collection 
     */
    public function getUsers()
    {
        return $this->users;
    }
}

Security.yml

    jms_security_extra:
    secure_all_services: false
    expressions: true

security:
    encoders:
        Acme\AppBundle\Entity\User: sha512
        Symfony\Component\Security\Core\User\User: plaintext


    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
        ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]

    providers:
        chain_providers:
            chain:
                providers: [main, in_memory]
        main:
            entity: { class: Acme\AppBundle\Entity\User, property: username }
        in_memory:
            memory:
                users:
                    user:  { password: userpass, roles: [ 'ROLE_USER' ] }
                    admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] }

    firewalls:

        secured_area:
            pattern:    .*
            form_login: ~
            logout: ~
            anonymous: ~
            #http_basic:
            #    realm: "Secured Demo Area"

    #access_control:
        #- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }

UserRepository:

namespace Acme\AppBundle\Entity;

use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\NoResultException;

class UserRepository extends EntityRepository implements UserProviderInterface
{
    public function loadUserByUsername($username)
    {
        $q = $this
            ->createQueryBuilder('u')
            ->select('u, r')
            ->leftJoin('u.userRoles', 'r')
            ->where('u.username = :username')
            ->setParameter('username', $username)
            ->getQuery();


        try {
            // The Query::getSingleResult() method throws an exception
            // if there is no record matching the criteria.
            $user = $q->getSingleResult();
        } catch (NoResultException $e) {
            $message = sprintf(
                'Unable to find an active admin AcmeUserBundle:User object identified by "%s".',
                $username
            );
            throw new UsernameNotFoundException($message, 0, $e);
        }

        return $user;
    }
...
}

Test:

public function testLoadUserByUsername()
{
    $users = $this->em
    ->getRepository('AcmeAppBundle:User')
    ->loadUserByUsername('admintest')
    ;
    $test = $users->getRoles();

    print $test[0];
    $this->assertCount(1, $users);
}
1
please remove the non role-related stuff from your entities from the question. leave only the methods - the question is quite long like this. - Nicolai Fröhlich
i changed a bit my entity based on your code.. i had problems with user roles.. :-) and now it's fixed.. btw you should use a dto and implement the advanced user interface outside the entity ... to decouple it since your security should not know about the entity. - L.S

1 Answers

4
votes

Please make sure your loadUserByUsername() in the UserRepository joins the according Roles properly on your User entity and User->getRoles() ( and Role->getRole() on your Role entity ) return the correct array/string.

You can test this in a TestController for instance by manually querying the UserRepository::loadUserByUsername() method and inspect if the Roles are present.

Next possibility would be a lazy loading issue. Try:

/**
 * @ORM\ManyToMany(targetEntity="Role", inversedBy="users", fetch="EAGER")
 * @ORM\JoinTable(name="user_roles")
 *
 */
private $userRoles;

The Documentation say:

To improve performances and avoid lazy loading of groups when retrieving a user from the custom entity provider.

If it's not the solution to your problem please comment, anyways it's a good practice to fetch user-group or user-role relationships with eager loading.

Remember to clear your cache after changing the fetch setting!

Tip:

Regarding your addRole method ... you most likely don't want to add Role entities multiple times to your User but only if they are not already added. Better write it like this:

public function addRole(RoleInterface $role)
{
   if (!$this->roles->contains($role)) {
       $this->roles->add($role);
   }

    return $this;
}