Skip to content

Commit

Permalink
Merge branch '2.8' into 3.0
Browse files Browse the repository at this point in the history
* 2.8:
  Hash nonce when using as file name
  File System Security Issue in Custom Auth Article
  [Cookbook] Tweaking registration_form (e.g. bcrypt)
  • Loading branch information
xabbuh committed Feb 7, 2016
2 parents 266f7bf + 52f14f8 commit 811671d
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 16 deletions.
77 changes: 64 additions & 13 deletions cookbook/doctrine/registration_form.rst
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
.. index::
single: Doctrine; Simple Registration Form
single: Form; Simple Registration Form
single: Security; Simple Registration Form

How to Implement a simple Registration Form
How to Implement a Simple Registration Form
===========================================

Creating a registration form is pretty easy - it *really* means just creating
a form that will update some ``User`` model object (a Doctrine entity in this example)
and then save it.
a form that will update some ``User`` model object (a Doctrine entity in this
example) and then save it.

.. tip::

The popular `FOSUserBundle`_ provides a registration form, reset password form
and other user management functionality.
The popular `FOSUserBundle`_ provides a registration form, reset password
form and other user management functionality.

If you don't already have a ``User`` entity and a working login system,
first start with :doc:`/cookbook/security/entity_provider`.
Expand Down Expand Up @@ -61,27 +62,27 @@ With some validation added, your class may look something like this::
private $id;

/**
* @ORM\Column(type="string", length=255)
* @ORM\Column(type="string", length=255, unique=true)
* @Assert\NotBlank()
* @Assert\Email()
*/
private $email;

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

/**
* @Assert\NotBlank()
* @Assert\Length(max = 4096)
* @Assert\Length(max=4096)
*/
private $plainPassword;

/**
* The below length depends on the "algorithm" you use for encoding
* the password, but this works well with bcrypt
* the password, but this works well with bcrypt.
*
* @ORM\Column(type="string", length=64)
*/
Expand Down Expand Up @@ -124,6 +125,13 @@ With some validation added, your class may look something like this::
$this->password = $password;
}

public function getSalt()
{
// The bcrypt algorithm don't require a separate salt.
// You *may* need a real salt if you choose a different encoder.
return null;
}

// other methods, including security methods like getRoles()
}

Expand All @@ -146,8 +154,10 @@ example, see the :ref:`Entity Provider <security-crete-user-entity>` article.
only place where you don't need to worry about this is your login form,
since Symfony's Security component handles this for you.

Create a Form for the Model
---------------------------
.. _create-a-form-for-the-model:

Create a Form for the Entity
----------------------------

Next, create the form for the ``User`` entity::

Expand Down Expand Up @@ -195,8 +205,9 @@ There are just three fields: ``email``, ``username`` and ``plainPassword``
Handling the Form Submission
----------------------------

Next, you need a controller to handle the form. Start by creating a simple
controller for displaying the registration form::
Next, you need a controller to handle the form rendering and submission. If the
form is submitted, the controller performs the validation and saves the data
into the database::

// src/AppBundle/Controller/RegistrationController.php
namespace AppBundle\Controller;
Expand All @@ -222,6 +233,7 @@ controller for displaying the registration form::
// 2) handle the submit (will only happen on POST)
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {

// 3) Encode the password (you could also do this via Doctrine listener)
$password = $this->get('security.password_encoder')
->encodePassword($user, $user->getPlainPassword());
Expand All @@ -245,6 +257,45 @@ controller for displaying the registration form::
}
}

To define the algorithm used to encode the password in step 3 configure the
encoder in the security configuration:

.. configuration-block::

.. code-block:: yaml
# app/config/security.yml
security:
encoders:
AppBundle\Entity\User: bcrypt
.. code-block:: xml
<!-- app/config/security.xml -->
<?xml version="1.0" charset="UTF-8" ?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:srv="http://symfony.com/schema/dic/services"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<config>
<encoder class="AppBundle\Entity\User">bcrypt</encoder>
</config>
</srv:container>
.. code-block:: php
// app/config/security.php
$container->loadFromExtension('security', array(
'encoders' => array(
'AppBundle\Entity\User' => 'bcrypt',
),
));
In this case the recommended ``bcrypt`` algorithm is used. To learn more
about how to encode the users password have a look into the
:ref:`security chapter <book-security-encoding-user-password>`.

.. note::

If you decide to NOT use annotation routing (shown above), then you'll
Expand Down
9 changes: 6 additions & 3 deletions cookbook/security/custom_authentication_provider.rst
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ set an authenticated token in the token storage if successful.
{
$request = $event->getRequest();
$wsseRegex = '/UsernameToken Username="([^"]+)", PasswordDigest="([^"]+)", Nonce="([^"]+)", Created="([^"]+)"/';
$wsseRegex = '/UsernameToken Username="([^"]+)", PasswordDigest="([^"]+)", Nonce="([a-zA-Z0-9+/]+={0,2})", Created="([^"]+)"/';
if (!$request->headers->has('x-wsse') || 1 !== preg_match($wsseRegex, $request->headers->get('x-wsse'), $matches)) {
return;
}
Expand Down Expand Up @@ -260,14 +260,17 @@ the ``PasswordDigest`` header value matches with the user's password.
// Validate that the nonce is *not* used in the last 5 minutes
// if it has, this could be a replay attack
if (file_exists($this->cacheDir.'/'.$nonce) && file_get_contents($this->cacheDir.'/'.$nonce) + 300 > time()) {
if (
file_exists($this->cacheDir.'/'.md5($nonce))
&& file_get_contents($this->cacheDir.'/'.md5($nonce)) + 300 > time()
) {
throw new NonceExpiredException('Previously used nonce detected');
}
// If cache directory does not exist we create it
if (!is_dir($this->cacheDir)) {
mkdir($this->cacheDir, 0777, true);
}
file_put_contents($this->cacheDir.'/'.$nonce, time());
file_put_contents($this->cacheDir.'/'.md5($nonce), time());
// Validate Secret
$expected = base64_encode(sha1(base64_decode($nonce).$created.$secret, true));
Expand Down

0 comments on commit 811671d

Please sign in to comment.