feature #12818 [2.7][SecurityBundle] Added a command to encode a password (saro0h)
This PR was merged into the 2.7 branch.
Discussion
----------
[2.7][SecurityBundle] Added a command to encode a password
| Q | A
| ------------- | ---
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | #11206
| License | MIT
| Doc PR | ~
- [x] Give some love to the UI (need your feedbacks)
- [x] Write tests
- [x] Write documentation
-------------------------------------------------------
The encoder got depend directly from the configuration of the user class in the security.yml.
So the user choose the user type, and, using the method `getEncoder($user)` of the `security.encoder_factory` service, I get the right encoder configured.
*Here is the output for `security:encode-password`*:
![capture d ecran 2015-01-01 a 19 48 07](https://cloud.githubusercontent.com/assets/667519/5593008/3af45686-91ef-11e4-8024-e66a2e000fbe.png)
Commits
-------
a7bd0fc
Added a command to encode a password
This commit is contained in:
commit
fa11c0637e
@ -0,0 +1,225 @@
|
||||
<?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\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
|
||||
/**
|
||||
* Encode a user's password.
|
||||
*
|
||||
* @author Sarah Khalil <mkhalil.sarah@gmail.com>
|
||||
*/
|
||||
class UserPasswordEncoderCommand extends ContainerAwareCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('security:encode-password')
|
||||
->setDescription('Encode a password.')
|
||||
->addArgument('password', InputArgument::OPTIONAL, 'Enter a password')
|
||||
->addArgument('user-class', InputArgument::OPTIONAL, 'Enter the user class configured to find the encoder you need.')
|
||||
->addArgument('salt', InputArgument::OPTIONAL, 'Enter the salt you want to use to encode your password.')
|
||||
->setHelp(<<<EOF
|
||||
|
||||
The <info>%command.name%</info> command allows to encode a password using encoders
|
||||
that are configured in the application configuration file, under the <comment>security.encoders</comment>.
|
||||
|
||||
For instance, if you have the following configuration for your application:
|
||||
<comment>
|
||||
security:
|
||||
encoders:
|
||||
Symfony\Component\Security\Core\User\User: plaintext
|
||||
AppBundle\Model\User: bcrypt
|
||||
</comment>
|
||||
|
||||
According to the response you will give to the question "<question>Provide your configured user class</question>" your
|
||||
password will be encoded the way it was configured.
|
||||
- If you answer "<comment>Symfony\Component\Security\Core\User\User</comment>", the password provided will be encoded
|
||||
with the <comment>plaintext</comment> encoder.
|
||||
- If you answer <comment>AppBundle\Model\User</comment>, the password provided will be encoded
|
||||
with the <comment>bcrypt</comment> encoder.
|
||||
|
||||
The command allows you to provide your own <comment>salt</comment>. If you don't provide any,
|
||||
the command will take care about that for you.
|
||||
|
||||
You can also use the non interactive way by typing the following command:
|
||||
<info>php %command.full_name% [password] [salt] [user-class]</info>
|
||||
|
||||
EOF
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->writeIntroduction($output);
|
||||
|
||||
$password = $input->getArgument('password');
|
||||
$salt = $input->getArgument('salt');
|
||||
$userClass = $input->getArgument('user-class');
|
||||
|
||||
$helper = $this->getHelper('question');
|
||||
|
||||
if (!$password) {
|
||||
$passwordQuestion = $this->createPasswordQuestion($input, $output);
|
||||
$password = $helper->ask($input, $output, $passwordQuestion);
|
||||
}
|
||||
|
||||
if (!$salt) {
|
||||
$saltQuestion = $this->createSaltQuestion($input, $output);
|
||||
$salt = $helper->ask($input, $output, $saltQuestion);
|
||||
}
|
||||
|
||||
$output->writeln("\n <comment>Encoders are configured by user type in the security.yml file.</comment>");
|
||||
|
||||
if (!$userClass) {
|
||||
$userClassQuestion = $this->createUserClassQuestion($input, $output);
|
||||
$userClass = $helper->ask($input, $output, $userClassQuestion);
|
||||
}
|
||||
|
||||
$encoder = $this->getContainer()->get('security.encoder_factory')->getEncoder($userClass);
|
||||
$encodedPassword = $encoder->encodePassword($password, $salt);
|
||||
|
||||
$this->writeResult($output);
|
||||
|
||||
$table = new Table($output);
|
||||
$table
|
||||
->setHeaders(array('Key', 'Value'))
|
||||
->addRow(array('Encoder used', get_class($encoder)))
|
||||
->addRow(array('Encoded password', $encodedPassword))
|
||||
;
|
||||
|
||||
$table->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the password question to ask the user for the password to be encoded.
|
||||
*
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
*
|
||||
* @return Question
|
||||
*/
|
||||
private function createPasswordQuestion(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$passwordQuestion = new Question("\n > <question>Type in your password to be encoded:</question> ");
|
||||
|
||||
$passwordQuestion->setValidator(function ($value) {
|
||||
if ('' === trim($value)) {
|
||||
throw new \Exception('The password must not be empty.');
|
||||
}
|
||||
|
||||
return $value;
|
||||
});
|
||||
$passwordQuestion->setHidden(true);
|
||||
$passwordQuestion->setMaxAttempts(20);
|
||||
|
||||
return $passwordQuestion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the question that asks for the salt to perform the encoding.
|
||||
* If there is no provided salt, a random one is automatically generated.
|
||||
*
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
*
|
||||
* @return Question
|
||||
*/
|
||||
private function createSaltQuestion(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$saltQuestion = new Question("\n > (Optional) <question>Provide a salt (press <enter> to generate one):</question> ");
|
||||
|
||||
$container = $this->getContainer();
|
||||
$saltQuestion->setValidator(function ($value) use ($output, $container) {
|
||||
if ('' === trim($value)) {
|
||||
$value = hash('sha512', $container->get('security.secure_random')->nextBytes(30));
|
||||
|
||||
$output->writeln("\n<comment>The salt has been generated: </comment>".$value);
|
||||
$output->writeln(sprintf("<comment>Make sure that your salt storage field fits this salt length: %s chars.</comment>\n", strlen($value)));
|
||||
}
|
||||
|
||||
return $value;
|
||||
});
|
||||
|
||||
return $saltQuestion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the question that asks for the configured user class.
|
||||
*
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
*
|
||||
* @return Question
|
||||
*/
|
||||
private function createUserClassQuestion(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$userClassQuestion = new Question(" > <question>Provide your configured user class:</question> ");
|
||||
$userClassQuestion->setAutocompleterValues(array('Symfony\Component\Security\Core\User\User'));
|
||||
|
||||
$userClassQuestion->setValidator(function ($value) use ($output) {
|
||||
if ('' === trim($value)) {
|
||||
$value = 'Symfony\Component\Security\Core\User\User';
|
||||
$output->writeln("<info>You did not provide any user class.</info> <comment>The user class used is: Symfony\Component\Security\Core\User\User</comment> \n");
|
||||
}
|
||||
|
||||
return $value;
|
||||
});
|
||||
|
||||
return $userClassQuestion;
|
||||
}
|
||||
|
||||
private function writeIntroduction(OutputInterface $output)
|
||||
{
|
||||
$output->writeln(array(
|
||||
'',
|
||||
$this->getHelperSet()->get('formatter')->formatBlock(
|
||||
'Symfony Password Encoder Utility',
|
||||
'bg=blue;fg=white',
|
||||
true
|
||||
),
|
||||
'',
|
||||
));
|
||||
|
||||
$output->writeln(array(
|
||||
'',
|
||||
'This command encodes any password you want according to the configuration you',
|
||||
'made in your configuration file containing the <comment>security.encoders</comment> key.',
|
||||
'',
|
||||
));
|
||||
}
|
||||
|
||||
private function writeResult(OutputInterface $output)
|
||||
{
|
||||
$output->writeln(array(
|
||||
'',
|
||||
$this->getHelperSet()->get('formatter')->formatBlock(
|
||||
'✔ Password encoding succeeded',
|
||||
'bg=green;fg=white',
|
||||
true
|
||||
),
|
||||
'',
|
||||
));
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
<?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\Tests\Functional;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Bundle\SecurityBundle\Command\UserPasswordEncoderCommand;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
/**
|
||||
* Tests UserPasswordEncoderCommand
|
||||
*
|
||||
* @author Sarah Khalil <mkhalil.sarah@gmail.com>
|
||||
*/
|
||||
class UserPasswordEncoderCommandTest extends WebTestCase
|
||||
{
|
||||
private $passwordEncoderCommandTester;
|
||||
|
||||
public function testEncodePasswordPasswordPlainText()
|
||||
{
|
||||
$this->passwordEncoderCommandTester->execute(array(
|
||||
'command' => 'security:encode-password',
|
||||
'password' => 'password',
|
||||
'user-class' => 'Symfony\Component\Security\Core\User\User',
|
||||
'salt' => 'AZERTYUIOPOfghjklytrertyuiolnbcxdfghjkytrfghjk',
|
||||
));
|
||||
$expected = file_get_contents(__DIR__.'/app/PasswordEncode/plaintext.txt');
|
||||
|
||||
$this->assertEquals($expected, $this->passwordEncoderCommandTester->getDisplay());
|
||||
}
|
||||
|
||||
public function testEncodePasswordBcrypt()
|
||||
{
|
||||
$this->passwordEncoderCommandTester->execute(array(
|
||||
'command' => 'security:encode-password',
|
||||
'password' => 'password',
|
||||
'user-class' => 'Custom\Class\Bcrypt\User',
|
||||
'salt' => 'AZERTYUIOPOfghjklytrertyuiolnbcxdfghjkytrfghjk',
|
||||
));
|
||||
$expected = file_get_contents(__DIR__.'/app/PasswordEncode/bcrypt.txt');
|
||||
|
||||
$this->assertEquals($expected, $this->passwordEncoderCommandTester->getDisplay());
|
||||
}
|
||||
|
||||
public function testEncodePasswordPbkdf2()
|
||||
{
|
||||
$this->passwordEncoderCommandTester->execute(array(
|
||||
'command' => 'security:encode-password',
|
||||
'password' => 'password',
|
||||
'user-class' => 'Custom\Class\Pbkdf2\User',
|
||||
'salt' => 'AZERTYUIOPOfghjklytrertyuiolnbcxdfghjkytrfghjk',
|
||||
));
|
||||
|
||||
$expected = file_get_contents(__DIR__.'/app/PasswordEncode/pbkdf2.txt');
|
||||
|
||||
$this->assertEquals($expected, $this->passwordEncoderCommandTester->getDisplay());
|
||||
}
|
||||
|
||||
public function testEncodePasswordNoConfigForGivenUserClass()
|
||||
{
|
||||
$this->setExpectedException('\RuntimeException', 'No encoder has been configured for account "Wrong/User/Class".');
|
||||
|
||||
$this->passwordEncoderCommandTester->execute(array(
|
||||
'command' => 'security:encode-password',
|
||||
'password' => 'password',
|
||||
'user-class' => 'Wrong/User/Class',
|
||||
'salt' => 'AZERTYUIOPOfghjklytrertyuiolnbcxdfghjkytrfghjk',
|
||||
));
|
||||
}
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$kernel = $this->createKernel(array('test_case' => 'PasswordEncode'));
|
||||
$kernel->boot();
|
||||
|
||||
$application = new Application($kernel);
|
||||
|
||||
$application->add(new UserPasswordEncoderCommand());
|
||||
$passwordEncoderCommand = $application->find('security:encode-password');
|
||||
|
||||
$this->passwordEncoderCommandTester = new CommandTester($passwordEncoderCommand);
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
$this->passwordEncoderCommandTester = null;
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
|
||||
|
||||
Symfony Password Encoder Utility
|
||||
|
||||
|
||||
|
||||
This command encodes any password you want according to the configuration you
|
||||
made in your configuration file containing the security.encoders key.
|
||||
|
||||
|
||||
Encoders are configured by user type in the security.yml file.
|
||||
|
||||
|
||||
✔ Password encoding succeeded
|
||||
|
||||
|
||||
+------------------+---------------------------------------------------------------+
|
||||
| Key | Value |
|
||||
+------------------+---------------------------------------------------------------+
|
||||
| Encoder used | Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder |
|
||||
| Encoded password | $2y$13$AZERTYUIOPOfghjklytreeBTRM4Wd.D3IW7dtnQ6xGA7z3fY8zg4. |
|
||||
+------------------+---------------------------------------------------------------+
|
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
return array(
|
||||
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
|
||||
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
|
||||
);
|
@ -0,0 +1,21 @@
|
||||
imports:
|
||||
- { resource: ./../config/framework.yml }
|
||||
|
||||
security:
|
||||
encoders:
|
||||
Symfony\Component\Security\Core\User\User: plaintext
|
||||
Custom\Class\Bcrypt\User: bcrypt
|
||||
Custom\Class\Pbkdf2\User: pbkdf2
|
||||
Custom\Class\Test\User: test
|
||||
|
||||
providers:
|
||||
in_memory:
|
||||
memory:
|
||||
users:
|
||||
user: { password: userpass, roles: [ 'ROLE_USER' ] }
|
||||
admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] }
|
||||
|
||||
firewalls:
|
||||
test:
|
||||
pattern: ^/
|
||||
security: false
|
@ -0,0 +1,22 @@
|
||||
|
||||
|
||||
Symfony Password Encoder Utility
|
||||
|
||||
|
||||
|
||||
This command encodes any password you want according to the configuration you
|
||||
made in your configuration file containing the security.encoders key.
|
||||
|
||||
|
||||
Encoders are configured by user type in the security.yml file.
|
||||
|
||||
|
||||
✔ Password encoding succeeded
|
||||
|
||||
|
||||
+------------------+---------------------------------------------------------------+
|
||||
| Key | Value |
|
||||
+------------------+---------------------------------------------------------------+
|
||||
| Encoder used | Symfony\Component\Security\Core\Encoder\Pbkdf2PasswordEncoder |
|
||||
| Encoded password | nvGk/kUwqj6PHzmqUqXxJA6GEhxD1TSJziV8P4ThqsEi4ZHF6yHp6g== |
|
||||
+------------------+---------------------------------------------------------------+
|
@ -0,0 +1,22 @@
|
||||
|
||||
|
||||
Symfony Password Encoder Utility
|
||||
|
||||
|
||||
|
||||
This command encodes any password you want according to the configuration you
|
||||
made in your configuration file containing the security.encoders key.
|
||||
|
||||
|
||||
Encoders are configured by user type in the security.yml file.
|
||||
|
||||
|
||||
✔ Password encoding succeeded
|
||||
|
||||
|
||||
+------------------+------------------------------------------------------------------+
|
||||
| Key | Value |
|
||||
+------------------+------------------------------------------------------------------+
|
||||
| Encoder used | Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder |
|
||||
| Encoded password | password{AZERTYUIOPOfghjklytrertyuiolnbcxdfghjkytrfghjk} |
|
||||
+------------------+------------------------------------------------------------------+
|
@ -23,7 +23,7 @@
|
||||
"require-dev": {
|
||||
"symfony/phpunit-bridge": "~2.7|~3.0.0",
|
||||
"symfony/browser-kit": "~2.4|~3.0.0",
|
||||
"symfony/console": "~2.3|~3.0.0",
|
||||
"symfony/console": "~2.5|~3.0.0",
|
||||
"symfony/css-selector": "~2.0,>=2.0.5|~3.0.0",
|
||||
"symfony/dependency-injection": "~2.3|~3.0.0",
|
||||
"symfony/dom-crawler": "~2.0,>=2.0.5|~3.0.0",
|
||||
@ -37,7 +37,8 @@
|
||||
"symfony/yaml": "~2.0,>=2.0.5|~3.0.0",
|
||||
"symfony/expression-language": "~2.6|~3.0.0",
|
||||
"doctrine/doctrine-bundle": "~1.2",
|
||||
"twig/twig": "~1.12"
|
||||
"twig/twig": "~1.12",
|
||||
"ircmaxell/password-compat": "~1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": { "Symfony\\Bundle\\SecurityBundle\\": "" }
|
||||
|
Reference in New Issue
Block a user