Skip to content

Commit

Permalink
moved the secure random class from JMSSecurityExtraBundle to Symfony (c…
Browse files Browse the repository at this point in the history
…loses symfony#3595)
  • Loading branch information
fabpot committed Jul 5, 2012
1 parent 73d3efb commit 93e4eaf
Show file tree
Hide file tree
Showing 22 changed files with 673 additions and 37 deletions.
68 changes: 68 additions & 0 deletions src/Symfony/Bridge/Doctrine/Security/DoctrineSeedProvider.php
@@ -0,0 +1,68 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bridge\Doctrine\Security;

use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Connection;
use Symfony\Component\Security\Core\Util\SeedProviderInterface;

/**
* Doctrine Seed Provider.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Fabien Potencier <fabien@symfony.com>
*/
class DoctrineSeedProvider implements SeedProviderInterface
{
private $con;
private $seedTableName;

/**
* Constructor.
*
* @param Connection $con
* @param string $tableName
*/
public function __construct(Connection $con, $tableName)
{
$this->con = $con;
$this->seedTableName = $tableName;
}

/**
* {@inheritdoc}
*/
public function loadSeed()
{
$stmt = $this->con->executeQuery("SELECT seed, updated_at FROM {$this->seedTableName}");

if (false === $seed = $stmt->fetchColumn(0)) {
throw new \RuntimeException('You need to initialize the generator by running the console command "init:prng".');
}

$seedLastUpdatedAt = new \DateTime($stmt->fetchColumn(1));

return array($seed, $seedLastUpdatedAt);
}

/**
* {@inheritdoc}
*/
public function updateSeed($seed)
{
$params = array(':seed' => $seed, ':updatedAt' => new \DateTime());
$types = array(':updatedAt' => Type::DATETIME);
if (!$this->con->executeUpdate("UPDATE {$this->seedTableName} SET seed = :seed, updated_at = :updatedAt", $params, $types)) {
$this->con->executeUpdate("INSERT INTO {$this->seedTableName} VALUES (:seed, :updatedAt)", $params, $types);
}
}
}
@@ -0,0 +1,33 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bridge\Doctrine\Security\EventListener;

use Symfony\Bridge\Doctrine\Security\PrngSchema;
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;

/**
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class PrngSchemaListener
{
private $schema;

public function __construct(PrngSchema $schema)
{
$this->schema = $schema;
}

public function postGenerateSchema(GenerateSchemaEventArgs $args)
{
$this->schema->addToSchema($args->getSchema());
}
}
43 changes: 43 additions & 0 deletions src/Symfony/Bridge/Doctrine/Security/PrngSchema.php
@@ -0,0 +1,43 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bridge\Doctrine\Security;

use Doctrine\DBAL\Schema\Schema;

/**
* The DBAL schema that will be used if you choose the database-based seed provider.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
final class PrngSchema extends Schema
{
public function __construct($tableName)
{
parent::__construct();

$table = $this->createTable($tableName);
$table->addColumn('seed', 'string', array(
'length' => 88,
'not_null' => true,
));
$table->addColumn('updated_at', 'datetime', array(
'not_null' => true,
));
}

public function addToSchema(Schema $schema)
{
foreach ($this->getTables() as $table) {
$schema->_addTable($table);
}
}
}
@@ -0,0 +1,48 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bridge\Doctrine\Tests\Security;

use Symfony\Bridge\Doctrine\Security\DoctrineSeedProvider;
use Symfony\Bridge\Doctrine\Security\PrngSchema;
use Symfony\Component\Security\Core\Util\Prng;
use Symfony\Component\Security\Tests\Core\Util\PrngTest;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Connection;

class DoctrineSeedProviderTest extends PrngTest
{
public function getPrngs()
{
$con = DriverManager::getConnection(array(
'driver' => 'pdo_sqlite',
'memory' => true
));

$schema = new PrngSchema('seed_table');
foreach ($schema->toSql($con->getDatabasePlatform()) as $sql) {
$con->executeQuery($sql);
}
$con->executeQuery("INSERT INTO seed_table VALUES (:seed, :updatedAt)", array(
':seed' => base64_encode(hash('sha512', uniqid(mt_rand(), true), true)),
':updatedAt' => date('Y-m-d H:i:s'),
));

// no-openssl with database seed provider
$prng = new Prng(new DoctrineSeedProvider($con, 'seed_table'));
$this->disableOpenSsl($prng);

$prngs = parent::getPrngs();
$prngs[] = array($prng);

return $prngs;
}
}
57 changes: 57 additions & 0 deletions src/Symfony/Bundle/SecurityBundle/Command/InitPrngCommand.php
@@ -0,0 +1,57 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\SecurityBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;

/**
* Initializes a custom PRNG seed provider.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class InitPrngCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('init:prng');
->addArgument('phrase', InputArgument::REQUIRED, 'A random string');
->setDescription('Initialize a custom PRNG seed provider')
->setHelp(<<<EOF
The <info>%command.name%</info> command initializes a custom PRNG seed provider:
<info>php %command.full_name% ABCDE...</info>
The argument should be a random string, whatever comes to your mind right now.
You do not need to remember it, it does not need to be cryptic, or long, and it
will not be stored in a decipherable way. One restriction however, you should
not let this be generated in an automated fashion.
EOF
)
;
}

protected function execute(InputInterface $input, OutputInterface $output)
{
if (!$this->getContainer()->has('security.prng_seed_provider')) {
throw new \RuntimeException('No seed provider has been configured under path "secure.prng".');
}

$this->getContainer()->get('security.prng_seed_provider')->updateSeed(base64_encode(hash('sha512', $input->getArgument('phrase'), true)));

$output->writeln('The CSPRNG has been initialized successfully.');
}
}
Expand Up @@ -87,6 +87,7 @@ private function addAclSection(ArrayNodeDefinition $rootNode)
{
$rootNode
->children()
->scalarNode('prng_seed_provider')->defaultNull()->end()
->arrayNode('acl')
->children()
->scalarNode('connection')
Expand Down
Expand Up @@ -88,6 +88,10 @@ public function load(array $configs, ContainerBuilder $container)
$this->aclLoad($config['acl'], $container);
}

if (null !== $config['prng_seed_provider']) {
$container->setAlias('security.prng_seed_provider', $config['prng_seed_provider']);
}

// add some required classes for compilation
$this->addClassesToCompile(array(
'Symfony\\Component\\Security\\Http\\Firewall',
Expand Down
Expand Up @@ -137,5 +137,12 @@
<argument type="service" id="security.context" />
<argument type="service" id="security.encoder_factory" />
</service>

<!-- Pseudorandom Number Generator -->
<service id="security.prng" class="Symfony\Component\Security\Core\Util\Prng">
<tag name="monolog.logger" channel="security" />
<argument type="service" id="security.prng_seed_provider" on-invalid="ignore" />
<argument type="service" id="logger" on-invalid="ignore" />
</service>
</services>
</container>
Expand Up @@ -45,6 +45,7 @@
class="%security.authentication.rememberme.services.persistent.class%"
parent="security.authentication.rememberme.services.abstract"
abstract="true">
<call method="setPrng"><argument type="service" id="security.prng" /></call>
</service>

<service id="security.authentication.rememberme.services.simplehash"
Expand Down
Expand Up @@ -2,6 +2,7 @@

$container->loadFromExtension('security', array(
'acl' => array(),
'prng_seed_provider' => 'custom_seed_provider',
'encoders' => array(
'JMS\FooBundle\Entity\User1' => 'plaintext',
'JMS\FooBundle\Entity\User2' => array(
Expand Down
Expand Up @@ -5,7 +5,7 @@
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>
<config prng-seed-provider="custom_seed_provider">
<acl />

<encoder class="JMS\FooBundle\Entity\User1" algorithm="plaintext" />
Expand Down
@@ -1,5 +1,6 @@
security:
acl: ~
prng_seed_provider: custom_seed_provider
encoders:
JMS\FooBundle\Entity\User1: plaintext
JMS\FooBundle\Entity\User2:
Expand Down
Expand Up @@ -164,6 +164,13 @@ public function testCustomAclProvider()
$this->assertEquals('foo', (string) $container->getAlias('security.acl.provider'));
}

public function testSeedProvider()
{
$container = $this->getContainer('container1');

$this->assertEquals('custom_seed_provider', (string) $container->getAlias('security.prng_seed_provider'));
}

protected function getContainer($file)
{
$container = new ContainerBuilder();
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/Security/CHANGELOG.md
Expand Up @@ -4,6 +4,7 @@ CHANGELOG
2.1.0
-----

* added secure random number generator
* [BC BREAK] The signature of ExceptionListener has changed
* changed the HttpUtils constructor signature to take a UrlGenerator and a UrlMatcher instead of a Router
* EncoderFactoryInterface::getEncoder() can now also take a class name as an argument
Expand Down
Expand Up @@ -11,6 +11,8 @@

namespace Symfony\Component\Security\Core\Encoder;

use Symfony\Component\Security\Core\Util\String;

/**
* BasePasswordEncoder is the base class for all password encoders.
*
Expand Down Expand Up @@ -77,15 +79,6 @@ protected function mergePasswordAndSalt($password, $salt)
*/
protected function comparePasswords($password1, $password2)
{
if (strlen($password1) !== strlen($password2)) {
return false;
}

$result = 0;
for ($i = 0; $i < strlen($password1); $i++) {
$result |= ord($password1[$i]) ^ ord($password2[$i]);
}

return 0 === $result;
return String::equals($password1, $password2);
}
}

0 comments on commit 93e4eaf

Please sign in to comment.