1

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);
}
ChrisS
  • 736
  • 2
  • 8
  • 21
  • 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 May 25 '13 at 17:59
  • 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 Sep 11 '16 at 23:34

1 Answers1

4

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;
}
Nicolai Fröhlich
  • 51,330
  • 11
  • 126
  • 130
  • Thanks for your answer. I tried fetch="Eager" but there is still the "Token does not have the required roles. " error Message . And in the app_dev.php Profiler the user is authenticated but Roles : [null] – ChrisS May 25 '13 at 17:48
  • updated my answer - please provide your UserRepository::loadUserByUsername method .. maybe it's something with the QueryBuilder. will update again then. – Nicolai Fröhlich May 25 '13 at 17:57
  • i think i have your solution ... please review my updated answer! added a tip aswell. – Nicolai Fröhlich May 25 '13 at 18:10
  • Hi, I checked my loadByUsername() method in my Repository and provided my repository at the end of my first post. I could'nt find any mistakes. I'm trying to test my loadByUsername() now. – ChrisS May 26 '13 at 09:25
  • Couldn't find my mistake. When I test my loadUserByUsername() method and I do something like this after the loadUserByUsername method: $test = $users->getRoles(); print $test[0] ,then nothing appears. There is no array input. print $test[0]; – ChrisS May 26 '13 at 13:17
  • Or has it something to do with the implements RoleInterface? In the Managing Roles in the Database they use the Entity Groups extends Role ? http://symfony.com/doc/master/cookbook/security/entity_provider.html#managing-roles-in-the-database – ChrisS May 26 '13 at 15:45
  • omg, what a fail from my side. I found the mistake. my getRole() method in Role didn't return the role. Thanks for your effort. :) – ChrisS May 26 '13 at 16:25
  • Okay so this was more or less related to the correct loading of your roles as my answer suggested - you might want to accept the answer though as it might be helpful for others :) added the hint to getRoles and getRole method returning correct values in my answer. – Nicolai Fröhlich May 26 '13 at 18:12