-
-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Fixing Security Entity Provider tutorial #2765
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -167,21 +167,6 @@ interface forces the class to implement the five following methods: | |
|
||
For more details on each of these, see :class:`Symfony\\Component\\Security\\Core\\User\\UserInterface`. | ||
|
||
.. code-block:: php | ||
|
||
// src/Acme/UserBundle/Entity/User.php | ||
|
||
namespace Acme\UserBundle\Entity; | ||
|
||
use Symfony\Component\Security\Core\User\EquatableInterface; | ||
|
||
// ... | ||
|
||
public function isEqualTo(UserInterface $user) | ||
{ | ||
return $this->id === $user->getId(); | ||
} | ||
|
||
.. note:: | ||
|
||
The :phpclass:`Serializable` interface and its ``serialize`` and ``unserialize`` | ||
|
@@ -191,24 +176,32 @@ For more details on each of these, see :class:`Symfony\\Component\\Security\\Cor | |
because the :method:`Symfony\\Bridge\\Doctrine\\Security\\User\\EntityUserProvider::refreshUser` | ||
method reloads the user on each request by using the ``id``. | ||
|
||
Below is an export of my ``User`` table from MySQL. For details on how to | ||
create user records and encode their password, see :ref:`book-security-encoding-user-password`. | ||
.. tip:: | ||
|
||
To generate missing setters and getters for your ``User`` entity, you | ||
can use ``php app/console doctrine:generate:entities Acme/UserBundle/Entity/User``. | ||
For more details, see Doctrine's :ref:`book-doctrine-generating-getters-and-setters`. | ||
|
||
Below is an export of my ``User`` table from MySQL with user `admin` | ||
and password `admin`. For details on how to create user records and | ||
encode their password, see :ref:`book-security-encoding-user-password`. | ||
|
||
.. code-block:: bash | ||
|
||
$ mysql> select * from user; | ||
+----+----------+----------------------------------+------------------------------------------+--------------------+-----------+ | ||
| id | username | salt | password | email | is_active | | ||
+----+----------+----------------------------------+------------------------------------------+--------------------+-----------+ | ||
| 1 | hhamon | 7308e59b97f6957fb42d66f894793079 | 09610f61637408828a35d7debee5b38a8350eebe | [email protected] | 1 | | ||
| 2 | jsmith | ce617a6cca9126bf4036ca0c02e82dee | 8390105917f3a3d533815250ed7c64b4594d7ebf | [email protected] | 1 | | ||
| 3 | maxime | cd01749bb995dc658fa56ed45458d807 | 9764731e5f7fb944de5fd8efad4949b995b72a3c | [email protected] | 0 | | ||
| 4 | donald | 6683c2bfd90c0426088402930cadd0f8 | 5c3bcec385f59edcc04490d1db95fdb8673bf612 | [email protected] | 1 | | ||
+----+----------+----------------------------------+------------------------------------------+--------------------+-----------+ | ||
4 rows in set (0.00 sec) | ||
|
||
The database now contains four users with different usernames, emails and | ||
statuses. The next part will focus on how to authenticate one of these users | ||
$ mysql> select * from acme_users; | ||
+----+----------+------+------------------------------------------+--------------------+-----------+ | ||
| id | username | salt | password | email | is_active | | ||
+----+----------+------+------------------------------------------+--------------------+-----------+ | ||
| 1 | admin | | d033e22ae348aeb5660fc2140aec35850c4da997 | [email protected] | 1 | | ||
+----+----------+------+------------------------------------------+--------------------+-----------+ | ||
|
||
.. tip:: | ||
|
||
To generate database table from your ``User`` entity, you can run | ||
``php app/console doctrine:schema:update --force``. | ||
For mor details, see Doctrine's :ref:`book-doctrine-creating-the-database-tables-schema`. | ||
|
||
The next part will focus on how to authenticate one of these users | ||
thanks to the Doctrine entity user provider and a couple of lines of | ||
configuration. | ||
|
||
|
@@ -323,9 +316,8 @@ entity user provider to load User entity objects from the database by using | |
the ``username`` unique field. In other words, this tells Symfony how to | ||
fetch the user from the database before checking the password validity. | ||
|
||
This code and configuration works but it's not enough to secure the application | ||
for **active** users. As of now, you can still authenticate with ``maxime``. The | ||
next section explains how to forbid non active users. | ||
This code is not enough to secure the application for **active** users. | ||
The next section explains how to forbid non active users. | ||
|
||
Forbid non Active Users | ||
----------------------- | ||
|
@@ -355,10 +347,10 @@ For this example, the first three methods will return ``true`` whereas the | |
// src/Acme/UserBundle/Entity/User.php | ||
namespace Acme\UserBundle\Entity; | ||
|
||
// ... | ||
use Doctrine\ORM\Mapping as ORM; | ||
use Symfony\Component\Security\Core\User\AdvancedUserInterface; | ||
|
||
class User implements AdvancedUserInterface | ||
class User implements AdvancedUserInterface, \Serializable | ||
{ | ||
// ... | ||
|
||
|
@@ -383,10 +375,8 @@ For this example, the first three methods will return ``true`` whereas the | |
} | ||
} | ||
|
||
If you try to authenticate as ``maxime``, the access is now forbidden as this | ||
user does not have an enabled account. The next session will focus on how | ||
to write a custom entity provider to authenticate a user with his username | ||
or his email address. | ||
The next session will focus on how to write a custom entity provider | ||
to authenticate a user with his username or his email address. | ||
|
||
Authenticating Someone with a Custom Entity Provider | ||
---------------------------------------------------- | ||
|
@@ -428,8 +418,7 @@ The code below shows the implementation of the | |
->where('u.username = :username OR u.email = :email') | ||
->setParameter('username', $username) | ||
->setParameter('email', $username) | ||
->getQuery() | ||
; | ||
->getQuery(); | ||
|
||
try { | ||
// The Query::getSingleResult() method throws an exception | ||
|
@@ -537,10 +526,11 @@ about in this section. | |
authenticated at all. | ||
|
||
In this example, the ``AcmeUserBundle:User`` entity class defines a | ||
many-to-many relationship with a ``AcmeUserBundle:Group`` entity class. A user | ||
can be related to several groups and a group can be composed of one or | ||
more users. As a group is also a role, the previous ``getRoles()`` method now | ||
returns the list of related groups:: | ||
many-to-many relationship with a ``AcmeUserBundle:Role`` entity class. | ||
A user can be related to several roles and a role can be composed of | ||
one or more users. The previous ``getRoles()`` method now returns | ||
the list of related roles. | ||
Notice that methods ``__construcotor()`` and ``getRoles()`` had changed:: | ||
|
||
// src/Acme/UserBundle/Entity/User.php | ||
namespace Acme\UserBundle\Entity; | ||
|
@@ -550,63 +540,46 @@ returns the list of related groups:: | |
|
||
class User implements AdvancedUserInterface, \Serializable | ||
{ | ||
//... | ||
|
||
/** | ||
* @ORM\ManyToMany(targetEntity="Group", inversedBy="users") | ||
* @ORM\ManyToMany(targetEntity="Role", inversedBy="users") | ||
* | ||
*/ | ||
private $groups; | ||
private $roles; | ||
|
||
public function __construct() | ||
{ | ||
$this->groups = new ArrayCollection(); | ||
$this->roles = new ArrayCollection(); | ||
} | ||
|
||
// ... | ||
|
||
public function getRoles() | ||
{ | ||
return $this->groups->toArray(); | ||
} | ||
|
||
/** | ||
* @see \Serializable::serialize() | ||
*/ | ||
public function serialize() | ||
{ | ||
return serialize(array( | ||
$this->id, | ||
)); | ||
return $this->roles->toArray(); | ||
} | ||
|
||
// ... | ||
|
||
/** | ||
* @see \Serializable::unserialize() | ||
*/ | ||
public function unserialize($serialized) | ||
{ | ||
list ( | ||
$this->id, | ||
) = unserialize($serialized); | ||
} | ||
} | ||
|
||
The ``AcmeUserBundle:Group`` entity class defines three table fields (``id``, | ||
The ``AcmeUserBundle:Role`` entity class defines three table fields (``id``, | ||
``name`` and ``role``). The unique ``role`` field contains the role name used by | ||
the Symfony security layer to secure parts of the application. The most | ||
important thing to notice is that the ``AcmeUserBundle:Group`` entity class | ||
important thing to notice is that the ``AcmeUserBundle:Role`` entity class | ||
extends the :class:`Symfony\\Component\\Security\\Core\\Role\\Role`:: | ||
|
||
// src/Acme/Bundle/UserBundle/Entity/Group.php | ||
// src/Acme/Bundle/UserBundle/Entity/Role.php | ||
namespace Acme\UserBundle\Entity; | ||
|
||
use Symfony\Component\Security\Core\Role\Role; | ||
use Symfony\Component\Security\Core\Role\RoleInterface; | ||
use Doctrine\Common\Collections\ArrayCollection; | ||
use Doctrine\ORM\Mapping as ORM; | ||
|
||
/** | ||
* @ORM\Table(name="acme_groups") | ||
* @ORM\Table(name="acme_roles") | ||
* @ORM\Entity() | ||
*/ | ||
class Group extends Role | ||
class Role implements RoleInterface | ||
{ | ||
/** | ||
* @ORM\Column(name="id", type="integer") | ||
|
@@ -626,7 +599,7 @@ extends the :class:`Symfony\\Component\\Security\\Core\\Role\\Role`:: | |
private $role; | ||
|
||
/** | ||
* @ORM\ManyToMany(targetEntity="User", mappedBy="groups") | ||
* @ORM\ManyToMany(targetEntity="User", mappedBy="roles") | ||
*/ | ||
private $users; | ||
|
||
|
@@ -635,21 +608,27 @@ extends the :class:`Symfony\\Component\\Security\\Core\\Role\\Role`:: | |
$this->users = new ArrayCollection(); | ||
} | ||
|
||
// ... getters and setters for each property | ||
|
||
/** | ||
* @see RoleInterface | ||
*/ | ||
public function getRole() | ||
{ | ||
return $this->role; | ||
} | ||
|
||
// ... getters and setters for each property | ||
} | ||
|
||
To improve performances and avoid lazy loading of groups when retrieving a user | ||
from the custom entity provider, the best solution is to join the groups | ||
.. tip:: | ||
|
||
To generate missing setters and getters for your ``Role`` entity, you | ||
can use ``php app/console doctrine:generate:entities Acme/UserBundle/Entity/User``. | ||
For more details, see Doctrine's :ref:`book-doctrine-generating-getters-and-setters`. | ||
|
||
To improve performances and avoid lazy loading of roles when retrieving a user | ||
from the custom entity provider, the best solution is to join the roles | ||
relationship in the ``UserRepository::loadUserByUsername()`` method. This will | ||
fetch the user and his associated roles / groups with a single query:: | ||
fetch the user and his associated roles with a single query:: | ||
|
||
// src/Acme/UserBundle/Entity/UserRepository.php | ||
namespace Acme\UserBundle\Entity; | ||
|
@@ -662,8 +641,8 @@ fetch the user and his associated roles / groups with a single query:: | |
{ | ||
$q = $this | ||
->createQueryBuilder('u') | ||
->select('u, g') | ||
->leftJoin('u.groups', 'g') | ||
->select('u, r') | ||
->leftJoin('u.roles', 'r') | ||
->where('u.username = :username OR u.email = :email') | ||
->setParameter('username', $username) | ||
->setParameter('email', $username) | ||
|
@@ -675,6 +654,29 @@ fetch the user and his associated roles / groups with a single query:: | |
// ... | ||
} | ||
|
||
The ``QueryBuilder::leftJoin()`` method joins and fetches related groups from | ||
The ``QueryBuilder::leftJoin()`` method joins and fetches related roles from | ||
the ``AcmeUserBundle:User`` model class when a user is retrieved with his email | ||
address or username. | ||
|
||
To re-generate all database tables, you can run ``php app/console doctrine:schema:update --force``. | ||
This will also create additional table ``user_role`` what holds | ||
relations between users and roles. | ||
For mor details, see Doctrine's :ref:`book-doctrine-creating-the-database-tables-schema`. | ||
|
||
Below is an export of my ``Roles`` and ``user_role`` tables from MySQL: | ||
|
||
.. code-block:: bash | ||
|
||
$ mysql> select * from acme_users; | ||
+----+-------+------------+ | ||
| id | name | role | | ||
+----+-------+------------+ | ||
| 1 | admin | ROLE_ADMIN | | ||
+----+-------+------------+ | ||
|
||
mysql> select * from user_role; | ||
+---------+---------+ | ||
| user_id | role_id | | ||
+---------+---------+ | ||
| 1 | 1 | | ||
+---------+---------+ |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like the first person view here, I think "Below is an export of the
user
table containing aadmin
user with passwordadmin
." (I'm not quite sure whether or not literals should be used here)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i've just iterated on what was already there. as you can see, i did not change this text just improved it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know. I'm sorry if I offended you. I was just wondering if you could change this as well. Otherwise, I can open a new pull request on it after this one is merged. Would be fine with either way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:) no hard feelings. honestly at this stage i would prefer to get this merged as its taking quite some time. i might do this change later today if this will still not be merged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gondo @weaverryan is the only man who merges stuff, it can take a couple weeks before it get merged