I have two roles in my application, for example ROLE_USER and ROLE_SUPERUSER. Users are stored in the database using Doctrine. Users with the ROLE_USER role are based on a simple User class. (Getters and setters have been removed for readability.)
Acme\MyBundle\Entity\User.php
namespace Acme\MyBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity
* #ORM\Entity(repositoryClass="Acme\MyBundle\Entity\UserRepository")
* #ORM\Table("users")
* #UniqueEntity(
* fields={"email"},
* message="email already used"
* )
*/
class User implements UserInterface, \Serializable
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255, unique=true)
* #Assert\NotBlank()
* #Assert\Email()
*/
protected $email;
/**
* #ORM\Column(type="string", length=32)
*/
private $salt;
/**
* #ORM\Column(type="string", length=4096)
*/
private $password;
/**
* #ORM\Column(name="is_active", type="boolean")
*/
private $isActive;
public function __construct()
{
$this->isActive = true;
$this->salt = md5(uniqid(null, true));
}
/**
* #inheritDoc
*/
public function getRoles()
{
return array('ROLE_USER','ROLE_SUPERUSER');
}
}
I have a SuperUser class that extends User to provides more fields that only users with ROLE_SUPERUSER would need.
Acme\MyBundle\Entity\SuperUser.php
namespace Acme\MyBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="super_users")
*/
class SuperUser extends User
{
/**
* #var integer
*/
protected $id;
/**
* #var string
*/
protected $email;
/**
* #ORM\Column(type="string", length=32)
*/
proteced $avatar;
/**
* #ORM\Column(type="string", length=50, unique=true)
*/
proteced $username;
}
In my controllers I'm using $user = $this -> getUser(); to get the current user, but this returns an instance of the User class and I cannot access the SuperUser properties or methods, even if the user has the role ROLE_SUPERUSER.
For example, I would like to be able to use the following code.
if ($this->get('security.context')->isGranted('ROLE_SUPERUSER')) {
$avatar = $this->getUser()-> avatar;
}
Is there anyway to be able to do that? I would say there's something to do with Doctrine relationships, but I don't really know what to change.
By the way, as you can see I don't have an username field in my standard User class, it's only present in SuperUser. Does this may cause any problems since the authentication is based on username?
I think my problem hasn't been clearly exposed, but this might be due to my current code, which is wrong.
I don't have two user tables. I want only one User class (with one users table). The authentication is operated only on this class with email and password.
I have another class SuperUser```that provides extra fields to users that have the ROLE_SUPERUSER`` role, but the superusers are users and have an entry in the users table. I just want to create a left join on the concerned rows, that's why I used inheritance. (Maybe there's a better way to do it.)
If I want to get all the emails, I can query the users table. If I want to get all the usernames, since only superusers have one, I can query the superusers table.
I would go for two entities with a OneToOne relationship (No extends)
/** #Entity **/
class SuperUser
{
// ...
/**
* #ORM\OneToOne(targetEntity="User", mappedBy="superUser")
**/
private $user;
// ...
}
/** #Entity **/
class User
{
// ...
/**
* #ORM\OneToOne(targetEntity="SuperUser", inversedBy="user")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
**/
private $superUser;
// ...
}
Otherwise take a look at Inheritance Mapping
You should have two user providers then, and use one master provider:
security:
providers:
master_provider:
chain:
providers: [user, super_user]
user:
entity: { class: Acme\MyBundle\Entity\User, property: username }
super_user:
entity: { class: Acme\MyBundle\Entity\SuperUser, property: username }
firewalls:
main:
provider: master_provider
http://symfony.com/doc/current/cookbook/security/multiple_user_providers.html
Related
I want to create a custom authentication system by separating passwords from user entity . each user can have more than a password and the latest one is used , and when user try to updated his password i want to prevent him to use an old password , as like as it is described in the link bellow .
Please i need your help and thank you .
https://filebin.net/wa6jrfy0t0xcqru7/Screenshot_from_2019-04-14_00-46-20.jpg?t=n9vnajox
For me is not a custom authentication system, you just need to change the body of the method getPassword. you will find below an example.
password.php
<?php
namespace your\name\space;
use Doctrine\ORM\Mapping as ORM;
use DateTime;
class Password
{
/**
* #ORM\ManyToOne(targetEntity="your\name\space\User", inversedBy="passwords")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
*
* #var User
*/
private $user;
/**
* #ORM\Column(name="inserted_at", type="datetime")
*
* #var DateTime
*/
private $insertedAt;
}
User.php
<?php
namespace your\name\space;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
class User implements UserInterface
{
/**
* #ORM\OneToMany(targetEntity="your\name\space\Password", mappedBy="user")
* #ORM\OrderBy(value={"insertedAt" = "DESC"})
*
* #var Collection
*/
private $passwords;
/**
* User constructor.
*/
public function __construct()
{
$this->passwords = new ArrayCollection();
}
/**
* {#inheritDoc}
*/
public function getPassword()
{
if ($this->passwords->isEmpty()) {
return null;
}
return $this->passwords->first();
}
}
I have two class, User and PersonalData. We need relation OnetoOne with Doctrine in Symfony2. In my code I tried this relations, but in MySQL doesnt appear the foreign key.
My code:
namespace TFC\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* User
*/
/** #ORM\Entity */
class User
{
/**
* #Id #Column(type="integer") #GeneratedValue
*/
private $id;
/**
* #var string
*/
private $email;
}
use Doctrine\ORM\Mapping as ORM;
/**
* PersonalData
*/
/** #ORM\Entity */
class PersonalData
{
/** #Id #OneToOne(targetEntity="User") */
private $userId;
/**
* #var string
*/
private $firstName;
}
You have to prefix with #ORM all your annotations like this:
#ORM\Id
#ORM\Column(type="integer")
#ORM\GeneratedValue(strategy="AUTO")
This "Unidirectional association" needs to be placed on User entity (because I think you want to load that informations from the User object...) and you don't need to care about the foreign key because doctrine create it automatically.
So place a $personalData property on User Entity and apply there the association, then delete $userId from PersonalData and add the $id property like did in User.
Even if the documentation claims that is not necessary have you tried something like:
#JoinColumn(name="userId", referencedColumnName="id")
When you generate the entity do you get any message?
Whats the best practice to check if entity fields exist before persisting it.
Here's the example
Entity
class Pile{
/**
* #var \ABC\CoreBundle\Entity\Record
*
* #ORM\OneToMany(targetEntity="Record")
*
*/
private $records;
/**
* #var \CSC\CoreBundle\Entity\Project
*
* #ORM\ManyToOne(targetEntity="Project")
*
*/
private $project;
/**
* #var string
*
* #ORM\Column(name="Block", type="string", length=255)
*/
private $block;
/**
* #var string
*
* #ORM\Column(name="Type", type="string", length=255)
*/
private $type;
}
class Record{
/**
* #var \CSC\CoreBundle\Entity\Pile
*
* #ORM\ManyToOne(targetEntity="Pile")
*
*/
private $records;
}
There are two controllers that handle the CRUD of Pile and Records.
To create Pile there must not be any duplicate fields [project, block, type]
In Record Controllers I could create Pile together with Record.
Here's the problem where and when do I check the db if a similar Pile entity is created?
Whats the Best Practice?
Copy and paste the query checker in both controller?
Can I use $form->valid() to perform any check in PileType class?
Must I use a service and have both controller to call the service?
In entity life-cycle use pre-insert?
Thanks
Therefore, the fields must be unique?
If so, then it is very simple: UniqueEntity
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
...
/**
* #ORM\Entity
* #UniqueEntity(
* fields={"project", "block", "type"}
* )
*/
class Pile{
/**
* #var \ABC\CoreBundle\Entity\Record
*
* #ORM\OneToMany(targetEntity="Record")
*
*/
private $records;
/**
* #var \CSC\CoreBundle\Entity\Project
*
* #ORM\ManyToOne(targetEntity="Project")
*
*/
private $project;
/**
* #var string
*
* #ORM\Column(name="Block", type="string", length=255, unique=true)
*/
private $block;
/**
* #var string
*
* #ORM\Column(name="Type", type="string", length=255, unique=true)
*/
private $type;
}
You can use a custom validation constraint in your form, so that $form->isValid() will do the check.
Follow this documentation entry on How to create a Custom Validation Constraint to create the custom validator and then inject doctrine into it to do the check.
UPDATE: Well, I didn't know there was an UniqueEntity Constraint already included in Symfony.
To inject doctrine do the following:
services:
validator.unique.unique_pile:
class: ABC\CoreBundle\Validator\Constraints\UniquePileValidator
arguments: [#doctrine.orm.entity_manager]
tags:
- { name: validator.constraint_validator, alias: unique_pile }
The validator class might then look like this:
// src/ABC/CoreBundle/Validator/Constraints/UniquePileValidator.php
namespace ABC\CoreBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class UniquePileValidator extends ConstraintValidator
{
protected $em;
function __construct($em) {
$this->em = $em;
}
public function validate($value, Constraint $constraint)
{
$repo = $this->em->getRepository('ABC\CoreBundle\Entity\Record');
$duplicate_project = $repo->findByProject($value);
$duplicate_block = $repo->findByBlock($value);
$duplicate_type = $repo->findByType($value);
if ($duplicate_project || $duplicate_block || $duplicate_type) {
$this->context->addViolation(
$constraint->message,
array('%string%' => $value)
);
}
}
}
And to be complete, the constraint class:
// src/ABC/CoreBundle/Validator/Constraints/ContainsAlphanumeric.php
namespace ABC\CoreBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class ContainsAlphanumeric extends Constraint
{
public $message = 'This Pile already exists!';
public function validatedBy()
{
return 'unique_pile';
}
}
Should be nearly copy/pasteable...
I trying to create CRUD panel from FOSUserBundle but i have some troubles. I mean that i created User entity for FOS and made crud panel for this entity. Now when i trying to add new user i have error like below
Neither the property "expiresAt" nor one of the methods "getExpiresAt()", "isExpiresAt()", "hasExpiresAt()", "_get()" or "_call()" exist and have public access in class "Bn\UserBundle\Entity\User".
It's my first project so please understand when i will ask for simple function, some suggestion ? What is wrong ?
<?php
namespace Bn\UserBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* User
*
* #ORM\Table(name="fos_user")
* #ORM\Entity
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* Get expiresAt
*
* #return \DateTime
*/
public function getExpiresAt()
{
return $this->expiresAt;
}
/**
* Get credentials_expire_at
*
* #return \DateTime
*/
public function getCredentialsExpireAt()
{
return $this->credentialsExpireAt;
}
public function __construct()
{
parent::__construct();
// your own logic
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
}
Now is working but i don't know why i must declare again function for getter.
I believe this means you need to add public accessors setExpiresAt() and getExpiresAt() to your User entity.
You need only add getExpiresAt to your User.php class. FOSUserBundle\User doesn't have getter for this field, but Sensio generator creates views for all fields.
public function getExpiresAt()
{
return $this->expiresAt;
}
In my web application, I want my user's to be able to create roles and add users to them dynamically. The only thing I imagine, is to edit the security.yml every time, but this can't be the best solution, can it? It would be very nice, if there is something like a User Provider for roles, so I can define one which loads the roles from a database (doctrine).
Thanks for your help, hice3000.
Then, you should want to add a role Entity to your model Hice.
You have to know that Symfony2 provides support for dynamic roles too. You have a getRoles() method in the Symfony2 User spec in the API Doc, that your User entity should implement, that forces him to return Roles. These roles must either implement the role interface that specifies a getRole() method that returns, most usually, the role name itself.
You can then add the newly created role directly to your user role list that the getRoles() user method will then return.
Here is an example using annotations :
First role class
/**
* Role class
*
* #ORM\Entity()
*/
class Role implements RoleInterface, \Serializable
{
/**
* #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;
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="userRoles")
*/
private $users;
public function __construct()
{
$this->users = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getRole()
{
return $this->name;
}
}
And the User class
/**
* User
*
* #ORM\Entity()
*/
class User implements UserInterface, \Serializable
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="username", type="string", length=255)
*/
private $username;
/**
* #ORM\ManyToMany(targetEntity="Role", inversedBy="users")
* #ORM\JoinTable(name="user_roles")
*/
private $userRoles;
public function __construct()
{
$this->userRoles = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getRoles()
{
return $this->userRoles->toArray();
}
I've skipped imports and methods to simplify the approach.
EDIT : There is something to know about serialization too. As Sharom commented on github, you musn't serialize users in roles neither roles in users. Just read his post and I think you'll understand :)