Migrate from sfGuard to FOSUserBundle

🦖 This post was published in 2011 and is most likely outdated.

Passwords are not stored the same way by sfGuard (symfony 1) and FOSUserBundle (Symfony2). You will not be able to check with FOSUserBundle the password created with sfGuard for two reasons (if you keep default values):

  • hashing algorithm: sfGuard uses sha1 to hash passwords whereas FOSUserBundle uses sha512.
  • password and salt merging: sfGuard is doing $salt . $password whereas FOSUserBundle is doing $password . '{' . $salt . '}'.

I will show you how to change default in your new Symfony2 application to deal with passwords the way sfGuard do.

Hashing Algorithm

The bundle FOSAdvancedEncoderBundle allows you to use different encoders on a per-instance basis, whereas Symfony security component uses encoders on a per-class basis.

Install FOSAdvancedEncoderBundle following the instructions in the bundle documentation. Then, specify in config.yml the two encoders you want to use.

# app/config/config.yml
fos_advanced_encoder:
  encoders:
    # default values for FOSUserBundle (Symfony2)
    # see http://symfony.com/doc/current/reference/configuration/security.html
    default:
      algorithm: sha512
      iterations: 5000
      encode_as_base64: true
    # legacy encoder used by sfGuard (symfony 1)
    legacy:
      algorithm: sha1
      iterations: 1
      encode_as_base64: false

Then, you have to tell your application which encoder use for which instance of your entity User. For that, make User implement the interface EncoderAwareInterface and the function User::getEncoderName has to return the name of the encoder to use. For instance, you can add a field algorithm like this.

// src/Acme/SiteBundle/Entity/User.php
class User implements EncoderAwareInterface
{
  // ...

  /** @ORM\Column(type="string", length=255) */
  protected $algorithm = 'sha512';

  public function getEncoderName() {
    return $this->algorithm == 'sha1' ? 'legacy' : 'default';
  }
}

Password and salt merging

We are going to overwrite the method used to merge passwords. Create a new folder Security/ in your bundle and add the file MessageDigestPasswordEncoder.php:

namespace Acme\SiteBundle\Security;

use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder as BaseEncoder;

class MessageDigestPasswordEncoder extends BaseEncoder
{
    // overwritting
    protected function mergePasswordAndSalt($password, $salt)
    {
        return $salt . $password;
    }

    protected function demergePasswordAndSalt($mergedPasswordSalt)
    {
        throw new Exception("This method should not have been called: demerging password and salt impossible (they have been merged by concatenation)");
    }
}

Don’t forget to change Acme\SiteBundle according to your names. Then add to your config file this line telling your app that we want to use that class instead of the default one:

parameters:
    security.encoder.digest.class: Acme\SiteBundle\Security\MessageDigestPasswordEncoder

Don’t forget, here again, to change Acme\SiteBundle according to your names.

That’s it

You may now write your script reading the sfGuard users and writing FOSUserBundle users. It will probably look like that:

$em = $this->getContainer()->get('doctrine.orm.entity_manager');
$um = $this->getContainer()->get('fos_user.user_manager');

$mysqli = new \mysqli($dbHost, $dbUser, $dbPassword, $dbName, $dbPort);
$result = $mysqli->query('SELECT * FROM sf_guard_user');
$userSfGuard = $result->fetch_object();

while ($userSfGuard = $result->fetch_object()) {
    $userFOS = $um->createUser();

    $userFOS->setUsername($userSfGuard->nickname);
    $userFOS->setEmail($userSfGuard->username);
    $userFOS->setPassword($userSfGuard->password);
    $userFOS->setSalt($userSfGuard->salt);

    $em->persist($userFOS);
}
$em->flush();

If so, you will have to add the method setSalt() to your User entity:

public function setSalt($salt) {
    $this->salt = $salt;
}

Having some problems?

If you are having trouble, try the parameters for the encoder in this post (it’s in French, but just look for the Symfony2 config file).

Comments