Skip to content

Commit e82c962

Browse files
committed
Added a command to encode a password
1 parent 6963887 commit e82c962

File tree

8 files changed

+416
-1
lines changed

8 files changed

+416
-1
lines changed
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\SecurityBundle\Command;
13+
14+
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
15+
use Symfony\Component\Console\Input\InputArgument;
16+
use Symfony\Component\Console\Input\InputInterface;
17+
use Symfony\Component\Console\Output\OutputInterface;
18+
use Symfony\Component\Console\Question\Question;
19+
use Symfony\Component\Console\Helper\Table;
20+
21+
/**
22+
* Encode a user's password.
23+
*
24+
* @author Sarah Khalil <mkhalil.sarah@gmail.com>
25+
*/
26+
class UserPasswordEncoderCommand extends ContainerAwareCommand
27+
{
28+
/**
29+
* {@inheritdoc}
30+
*/
31+
protected function configure()
32+
{
33+
$this
34+
->setName('security:encode-password')
35+
->setDescription('Encode a password.')
36+
->addArgument('password', InputArgument::OPTIONAL, 'Enter a password')
37+
->addArgument('user-class', InputArgument::OPTIONAL, 'Enter the user class configured to find the encoder you need.')
38+
->addArgument('salt', InputArgument::OPTIONAL, 'Enter the salt you want to use to encode your password.')
39+
->setHelp(<<<EOF
40+
41+
The <info>%command.name%</info> command allows to encode a password using encoders
42+
that are configured in the application configuration file, under the <comment>security.encoders</comment>.
43+
44+
For instance, if you have the following configuration for your application:
45+
<comment>
46+
security:
47+
encoders:
48+
Symfony\Component\Security\Core\User\User: plaintext
49+
AppBundle\Model\User: bcrypt
50+
</comment>
51+
52+
According to the response you will give to the question "<question>Provide your configured user class</question>" your
53+
password will be encoded the way it was configured.
54+
- If you answer "<comment>Symfony\Component\Security\Core\User\User</comment>", the password provided will be encoded
55+
with the <comment>plaintext</comment> encoder.
56+
- If you answer <comment>AppBundle\Model\User</comment>, the password provided will be encoded
57+
with the <comment>bcrypt</comment> encoder.
58+
59+
The command allows you to provide your own <comment>salt</comment>. If you don't provide any,
60+
the command will take about that for you.
61+
62+
You can also use the non interactive
63+
64+
EOF
65+
)
66+
;
67+
}
68+
69+
/**
70+
* {@inheritdoc}
71+
*/
72+
protected function execute(InputInterface $input, OutputInterface $output)
73+
{
74+
$this->writeIntroduction($output);
75+
76+
$password = $input->getArgument('password');
77+
$salt = $input->getArgument('salt');
78+
$userClass = $input->getArgument('user-class');
79+
80+
$helper = $this->getHelper('question');
81+
82+
if (!$password) {
83+
$passwordQuestion = $this->createPasswordQuestion($input, $output);
84+
$password = $helper->ask($input, $output, $passwordQuestion);
85+
}
86+
87+
if (!$salt) {
88+
$saltQuestion = $this->createSaltQuestion($input, $output);
89+
$salt = $helper->ask($input, $output, $saltQuestion);
90+
}
91+
92+
$output->writeln("\n <comment>Encoders are configured by user type in the security.yml file.</comment>");
93+
94+
if (!$userClass) {
95+
$userClassQuestion = $this->createUserClassQuestion($input, $output);
96+
$userClass = $helper->ask($input, $output, $userClassQuestion);
97+
}
98+
99+
$encoder = $this->getContainer()->get('security.encoder_factory')->getEncoder($userClass);
100+
$encodedPassword = $encoder->encodePassword($password, $salt);
101+
102+
$this->writeResult($output);
103+
104+
$table = new Table($output);
105+
$table
106+
->setHeaders(array('Key', 'Value'))
107+
->addRow(array('Encoder used', get_class($encoder)))
108+
->addRow(array('Encoded password', $encodedPassword))
109+
;
110+
111+
$table->render();
112+
}
113+
114+
/**
115+
* Create the password question to ask the user for the password to be encoded.
116+
*
117+
* @param InputInterface $input
118+
* @param OutputInterface $output
119+
*
120+
* @return Question
121+
*/
122+
private function createPasswordQuestion(InputInterface $input, OutputInterface $output)
123+
{
124+
$passwordQuestion = new Question("\n > <question>Type in your password to be encoded:</question> ");
125+
126+
$passwordQuestion->setValidator(function ($value) {
127+
if ('' === trim($value)) {
128+
throw new \Exception('The password must not be empty.');
129+
}
130+
131+
return $value;
132+
});
133+
$passwordQuestion->setHidden(true);
134+
$passwordQuestion->setMaxAttempts(20);
135+
136+
return $passwordQuestion;
137+
}
138+
139+
/**
140+
* Create the question that asks for the salt to perform the encoding.
141+
* If there is no provided salt, a random one is automatically generated.
142+
*
143+
* @param InputInterface $input
144+
* @param OutputInterface $output
145+
*
146+
* @return Question
147+
*/
148+
private function createSaltQuestion(InputInterface $input, OutputInterface $output)
149+
{
150+
$saltQuestion = new Question("\n > (Optional) <question>Provide a salt (press <enter> to generate one):</question> ");
151+
152+
$container = $this->getContainer();
153+
$saltQuestion->setValidator(function ($value) use ($output, $container) {
154+
if ('' === trim($value)) {
155+
$value = hash('sha512', $container->get('security.secure_random')->nextBytes(30));
156+
157+
$output->writeln("\n<comment>The salt has been generated: </comment>".$value);
158+
$output->writeln(sprintf("<comment>Make sure that your salt storage field fits this salt length: %s chars.</comment>\n", strlen($value)));
159+
}
160+
161+
return $value;
162+
});
163+
164+
return $saltQuestion;
165+
}
166+
167+
/**
168+
* Create the question that asks for the configured user class.
169+
*
170+
* @param InputInterface $input
171+
* @param OutputInterface $output
172+
*
173+
* @return Question
174+
*/
175+
private function createUserClassQuestion(InputInterface $input, OutputInterface $output)
176+
{
177+
$userClassQuestion = new Question(" > <question>Provide your configured user class:</question> ");
178+
$userClassQuestion->setAutocompleterValues(array('Symfony\Component\Security\Core\User\User'));
179+
180+
$userClassQuestion->setValidator(function ($value) use ($output) {
181+
if ('' === trim($value)) {
182+
$value = 'Symfony\Component\Security\Core\User\User';
183+
$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");
184+
}
185+
186+
return $value;
187+
});
188+
189+
return $userClassQuestion;
190+
}
191+
192+
private function writeIntroduction(OutputInterface $output)
193+
{
194+
$output->writeln(array(
195+
'',
196+
$this->getHelperSet()->get('formatter')->formatBlock(
197+
'Symfony Password Encoder Utility',
198+
'bg=blue;fg=white',
199+
true
200+
),
201+
'',
202+
));
203+
204+
$output->writeln(array(
205+
'',
206+
'This command encodes any password you want according to the configuration you',
207+
'made in your configuration file containing the <comment>security.encoders</comment> key.',
208+
'',
209+
));
210+
}
211+
212+
private function writeResult(OutputInterface $output)
213+
{
214+
$output->writeln(array(
215+
'',
216+
$this->getHelperSet()->get('formatter')->formatBlock(
217+
'✔ Password encoding succeeded',
218+
'bg=green;fg=white',
219+
true
220+
),
221+
'',
222+
));
223+
}
224+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\SecurityBundle\Tests\Functional;
13+
14+
use Symfony\Bundle\FrameworkBundle\Console\Application;
15+
use Symfony\Bundle\SecurityBundle\Command\UserPasswordEncoderCommand;
16+
use Symfony\Component\Console\Tester\CommandTester;
17+
18+
/**
19+
* Tests UserPasswordEncoderCommand
20+
*
21+
* @author Sarah Khalil <mkhalil.sarah@gmail.com>
22+
*/
23+
class UserPasswordEncoderCommandTest extends WebTestCase
24+
{
25+
private $passwordEncoderCommandTester;
26+
27+
public function testEncodePasswordPasswordPlainText()
28+
{
29+
$this->passwordEncoderCommandTester->execute(array(
30+
'command' => 'security:encode-password',
31+
'password' => 'password',
32+
'user-class' => 'Symfony\Component\Security\Core\User\User',
33+
'salt' => 'AZERTYUIOPOfghjklytrertyuiol,nbcxdfghjkytrfghjk',
34+
));
35+
$expected = file_get_contents(__DIR__.'/app/PasswordEncode/plaintext.txt');
36+
37+
$this->assertEquals($expected, $this->passwordEncoderCommandTester->getDisplay());
38+
}
39+
40+
public function testEncodePasswordBcrypt()
41+
{
42+
$this->passwordEncoderCommandTester->execute(array(
43+
'command' => 'security:encode-password',
44+
'password' => 'password',
45+
'user-class' => 'Custom\Class\Bcrypt\User',
46+
'salt' => 'AZERTYUIOPOfghjklytrertyuiol,nbcxdfghjkytrfghjk',
47+
));
48+
49+
$expected = file_get_contents(__DIR__.'/app/PasswordEncode/bcrypt.txt');
50+
51+
$this->assertEquals($expected, $this->passwordEncoderCommandTester->getDisplay());
52+
}
53+
54+
public function testEncodePasswordPbkdf2()
55+
{
56+
$this->passwordEncoderCommandTester->execute(array(
57+
'command' => 'security:encode-password',
58+
'password' => 'password',
59+
'user-class' => 'Custom\Class\Pbkdf2\User',
60+
'salt' => 'AZERTYUIOPOfghjklytrertyuiol,nbcxdfghjkytrfghjk',
61+
));
62+
63+
$expected = file_get_contents(__DIR__.'/app/PasswordEncode/pbkdf2.txt');
64+
65+
$this->assertEquals($expected, $this->passwordEncoderCommandTester->getDisplay());
66+
}
67+
68+
public function testEncodePasswordNoConfigForGivenUserClass()
69+
{
70+
$this->setExpectedException('\RuntimeException', 'No encoder has been configured for account "Wrong/User/Class".');
71+
72+
$this->passwordEncoderCommandTester->execute(array(
73+
'command' => 'security:encode-password',
74+
'password' => 'password',
75+
'user-class' => 'Wrong/User/Class',
76+
'salt' => 'AZERTYUIOPOfghjklytrertyuiol,nbcxdfghjkytrfghjk',
77+
));
78+
}
79+
80+
protected function setUp()
81+
{
82+
$kernel = $this->createKernel(array('test_case' => 'PasswordEncode'));
83+
$kernel->boot();
84+
85+
$application = new Application($kernel);
86+
87+
$application->add(new UserPasswordEncoderCommand());
88+
$passwordEncoderCommand = $application->find('security:encode-password');
89+
90+
$this->passwordEncoderCommandTester = new CommandTester($passwordEncoderCommand);
91+
}
92+
93+
protected function tearDown()
94+
{
95+
$this->passwordEncoderCommandTester = null;
96+
}
97+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
3+
Symfony Password Encoder Utility
4+
5+
6+
7+
This command encodes any password you want according to the configuration you
8+
made in your configuration file containing the security.encoders key.
9+
10+
11+
Encoders are configured by user type in the security.yml file.
12+
13+
14+
✔ Password encoding succeeded
15+
16+
17+
+------------------+---------------------------------------------------------------+
18+
| Key | Value |
19+
+------------------+---------------------------------------------------------------+
20+
| Encoder used | Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder |
21+
| Encoded password | $2y$13$OTnDSjPXTSjNSC7kX0foYu11IkBdne5gtTOGR.j2TtiBGETa4gEOy |
22+
+------------------+---------------------------------------------------------------+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
3+
return array(
4+
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
5+
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
6+
);
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
imports:
2+
- { resource: ./../config/framework.yml }
3+
4+
security:
5+
encoders:
6+
Symfony\Component\Security\Core\User\User: plaintext
7+
Custom\Class\Bcrypt\User: bcrypt
8+
Custom\Class\Pbkdf2\User: pbkdf2
9+
Custom\Class\Test\User: test
10+
11+
providers:
12+
in_memory:
13+
memory:
14+
users:
15+
user: { password: userpass, roles: [ 'ROLE_USER' ] }
16+
admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] }
17+
18+
firewalls:
19+
test:
20+
pattern: ^/
21+
security: false
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
3+
Symfony Password Encoder Utility
4+
5+
6+
7+
This command encodes any password you want according to the configuration you
8+
made in your configuration file containing the security.encoders key.
9+
10+
11+
Encoders are configured by user type in the security.yml file.
12+
13+
14+
✔ Password encoding succeeded
15+
16+
17+
+------------------+---------------------------------------------------------------+
18+
| Key | Value |
19+
+------------------+---------------------------------------------------------------+
20+
| Encoder used | Symfony\Component\Security\Core\Encoder\Pbkdf2PasswordEncoder |
21+
| Encoded password | c0lWMUreTNITAMdsvVJZgyAvpr42876VAsmipN98OEaH29oJOpYe+Q== |
22+
+------------------+---------------------------------------------------------------+

0 commit comments

Comments
 (0)