-
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[2.7][SecurityBundle] Added a command to encode a password #12818
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMHO this should be an class variable not "hidden" & hardcoded one... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you mean? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With your approach even if you extend this class you need to replace whole method (copy&paste) to replace default class value... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @stloyd I can't understand what you're meaning! This method has been made private because there are really few chances someone wants to override it IMO. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @saro0h Would it be possible to load the configuration and propose a choice amongst the configure user classes? That would be much more easier for end users (typing a fully-qualified class name without a typo is hard on the CLI -- at least for me.) |
||
$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"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about a default value? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't because, there is no way to access the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think he suggests to set There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that is what I mean. Since you configure |
||
} | ||
|
||
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 | ||
), | ||
'', | ||
)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. | | ||
+------------------+---------------------------------------------------------------+ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<?php | ||
|
||
return array( | ||
new Symfony\Bundle\SecurityBundle\SecurityBundle(), | ||
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), | ||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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== | | ||
+------------------+---------------------------------------------------------------+ |
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.
we should allow to pass the class name as an argument for a non-interactive usage.
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.
Sure, let me do that