14
14
use Symfony \Bundle \FrameworkBundle \Command \ContainerAwareCommand ;
15
15
use Symfony \Component \Console \Input \InputArgument ;
16
16
use Symfony \Component \Console \Input \InputInterface ;
17
+ use Symfony \Component \Console \Input \InputOption ;
17
18
use Symfony \Component \Console \Output \OutputInterface ;
18
19
use Symfony \Component \Console \Question \Question ;
19
- use Symfony \Component \Console \Helper \Table ;
20
+ use Symfony \Component \Console \Style \SymfonyStyle ;
21
+ use Symfony \Component \Security \Core \Encoder \BCryptPasswordEncoder ;
20
22
21
23
/**
22
24
* Encode a user's password.
@@ -32,35 +34,45 @@ protected function configure()
32
34
{
33
35
$ this
34
36
->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 . ' )
37
+ ->setDescription ('Encodes a password. ' )
38
+ ->addArgument ('password ' , InputArgument::OPTIONAL , 'The plain password to encode. ' )
39
+ ->addArgument ('user-class ' , InputArgument::OPTIONAL , 'The User entity class path associated with the encoder used to encode the password. ' , ' Symfony\Component\Security\Core\User\User ' )
40
+ ->addOption ( ' empty- salt ' , null , InputOption:: VALUE_NONE , 'Do not generate a salt or let the encoder generate one . ' )
39
41
->setHelp (<<<EOF
40
42
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
+ The <info>%command.name%</info> command encodes passwords according to your
44
+ security configuration. This command is mainly used to generate passwords for
45
+ the <comment>in_memory</comment> user provider type and for changing passwords
46
+ in the database while developing the application.
47
+
48
+ Suppose that you have the following security configuration in your application:
43
49
44
- For instance, if you have the following configuration for your application:
45
50
<comment>
46
- security:
47
- encoders:
48
- Symfony\Component\Security\Core\User\User: plaintext
49
- AppBundle\Model\User: bcrypt
51
+ # app/config/security.yml
52
+ security:
53
+ encoders:
54
+ Symfony\Component\Security\Core\User\User: plaintext
55
+ AppBundle\Entity\User: bcrypt
50
56
</comment>
51
57
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
+ If you execute the command non-interactively, the default Symfony User class
59
+ is used and a random salt is generated to encode the password:
60
+
61
+ <info>php %command.full_name% --no-interaction [password]</info>
62
+
63
+ Pass the full user class path as the second argument to encode passwords for
64
+ your own entities:
65
+
66
+ <info>php %command.full_name% --no-interaction [password] AppBundle\Entity\User</info>
58
67
59
- The command allows you to provide your own <comment> salt</comment>. If you don't provide any,
60
- the command will take care about that for you.
68
+ Executing the command interactively allows you to generate a random salt for
69
+ encoding the password:
61
70
62
- You can also use the non interactive way by typing the following command:
63
- <info>php %command.full_name% [password] [user-class] [salt]</info>
71
+ <info>php %command.full_name% [password] AppBundle\Entity\User</info>
72
+
73
+ In case your encoder doesn't require a salt, add the <comment>empty-salt</comment> option:
74
+
75
+ <info>php %command.full_name% --empty-salt [password] AppBundle\Entity\User</info>
64
76
65
77
EOF
66
78
)
@@ -72,154 +84,86 @@ protected function configure()
72
84
*/
73
85
protected function execute (InputInterface $ input , OutputInterface $ output )
74
86
{
75
- $ this ->writeIntroduction ($ output );
87
+ $ output = new SymfonyStyle ($ input , $ output );
88
+
89
+ $ input ->isInteractive () ? $ output ->title ('Symfony Password Encoder Utility ' ) : $ output ->newLine ();
76
90
77
91
$ password = $ input ->getArgument ('password ' );
78
- $ salt = $ input ->getArgument ('salt ' );
79
92
$ userClass = $ input ->getArgument ('user-class ' );
93
+ $ emptySalt = $ input ->getOption ('empty-salt ' );
80
94
81
- $ helper = $ this ->getHelper ('question ' );
95
+ $ encoder = $ this ->getContainer ()->get ('security.encoder_factory ' )->getEncoder ($ userClass );
96
+ $ bcryptWithoutEmptySalt = !$ emptySalt && $ encoder instanceof BCryptPasswordEncoder;
97
+
98
+ if ($ bcryptWithoutEmptySalt ) {
99
+ $ emptySalt = true ;
100
+ }
82
101
83
102
if (!$ password ) {
103
+ if (!$ input ->isInteractive ()) {
104
+ $ output ->error ('The password must not be empty. ' );
105
+
106
+ return 1 ;
107
+ }
84
108
$ passwordQuestion = $ this ->createPasswordQuestion ($ input , $ output );
85
- $ password = $ helper -> ask ( $ input , $ output , $ passwordQuestion );
109
+ $ password = $ output -> askQuestion ( $ passwordQuestion );
86
110
}
87
111
88
- if (! $ salt) {
89
- $ saltQuestion = $ this -> createSaltQuestion ( $ input , $ output );
90
- $ salt = $ helper -> ask ( $ input , $ output , $ saltQuestion );
91
- }
112
+ $ salt = null ;
113
+
114
+ if ( $ input -> isInteractive () && ! $ emptySalt ) {
115
+ $ emptySalt = true ;
92
116
93
- $ output ->writeln ( "\n <comment>Encoders are configured by user type in the security.yml file.</comment> " );
117
+ $ output ->note ( ' The command will take care of generating a salt for you. Be aware that some encoders advise to let them generate their own salt. If you \' re using one of those encoders, please answer \' no \' to the question below. ' . PHP_EOL . ' Provide the \' empty-salt \' option in order to let the encoder handle the generation itself. ' );
94
118
95
- if (!$ userClass ) {
96
- $ userClassQuestion = $ this ->createUserClassQuestion ($ input , $ output );
97
- $ userClass = $ helper ->ask ($ input , $ output , $ userClassQuestion );
119
+ if ($ output ->confirm ('Confirm salt generation ? ' )) {
120
+ $ salt = $ this ->generateSalt ();
121
+ $ emptySalt = false ;
122
+ }
123
+ } elseif (!$ emptySalt ) {
124
+ $ salt = $ this ->generateSalt ();
98
125
}
99
126
100
- $ encoder = $ this ->getContainer ()->get ('security.encoder_factory ' )->getEncoder ($ userClass );
101
127
$ encodedPassword = $ encoder ->encodePassword ($ password , $ salt );
102
128
103
- $ this ->writeResult ($ output );
129
+ $ rows = array (
130
+ array ('Encoder used ' , get_class ($ encoder )),
131
+ array ('Encoded password ' , $ encodedPassword ),
132
+ );
133
+ if (!$ emptySalt ) {
134
+ $ rows [] = array ('Generated salt ' , $ salt );
135
+ }
136
+ $ output ->table (array ('Key ' , 'Value ' ), $ rows );
104
137
105
- $ table = new Table ($ output );
106
- $ table
107
- ->setHeaders (array ('Key ' , 'Value ' ))
108
- ->addRow (array ('Encoder used ' , get_class ($ encoder )))
109
- ->addRow (array ('Encoded password ' , $ encodedPassword ))
110
- ;
138
+ if (!$ emptySalt ) {
139
+ $ output ->note (sprintf ('Make sure that your salt storage field fits the salt length: %s chars ' , strlen ($ salt )));
140
+ } elseif ($ bcryptWithoutEmptySalt ) {
141
+ $ output ->note ('Bcrypt encoder used: the encoder generated its own built-in salt. ' );
142
+ }
111
143
112
- $ table -> render ( );
144
+ $ output -> success ( ' Password encoding succeeded ' );
113
145
}
114
146
115
147
/**
116
148
* Create the password question to ask the user for the password to be encoded.
117
149
*
118
- * @param InputInterface $input
119
- * @param OutputInterface $output
120
- *
121
150
* @return Question
122
151
*/
123
- private function createPasswordQuestion (InputInterface $ input , OutputInterface $ output )
152
+ private function createPasswordQuestion ()
124
153
{
125
- $ passwordQuestion = new Question ("\n > <question> Type in your password to be encoded:</question> " );
154
+ $ passwordQuestion = new Question (' Type in your password to be encoded ' );
126
155
127
- $ passwordQuestion ->setValidator (function ($ value ) {
156
+ return $ passwordQuestion ->setValidator (function ($ value ) {
128
157
if ('' === trim ($ value )) {
129
158
throw new \Exception ('The password must not be empty. ' );
130
159
}
131
160
132
161
return $ value ;
133
- });
134
- $ passwordQuestion ->setHidden (true );
135
- $ passwordQuestion ->setMaxAttempts (20 );
136
-
137
- return $ passwordQuestion ;
138
- }
139
-
140
- /**
141
- * Create the question that asks for the salt to perform the encoding.
142
- * If there is no provided salt, a random one is automatically generated.
143
- *
144
- * @param InputInterface $input
145
- * @param OutputInterface $output
146
- *
147
- * @return Question
148
- */
149
- private function createSaltQuestion (InputInterface $ input , OutputInterface $ output )
150
- {
151
- $ saltQuestion = new Question ("\n > (Optional) <question>Provide a salt (press <enter> to generate one):</question> " );
152
-
153
- $ container = $ this ->getContainer ();
154
- $ saltQuestion ->setValidator (function ($ value ) use ($ output , $ container ) {
155
- if ('' === trim ($ value )) {
156
- $ value = base64_encode ($ container ->get ('security.secure_random ' )->nextBytes (30 ));
157
-
158
- $ output ->writeln ("\n<comment>The salt has been generated: </comment> " .$ value );
159
- $ output ->writeln (sprintf ("<comment>Make sure that your salt storage field fits this salt length: %s chars.</comment> \n" , strlen ($ value )));
160
- }
161
-
162
- return $ value ;
163
- });
164
-
165
- return $ saltQuestion ;
166
- }
167
-
168
- /**
169
- * Create the question that asks for the configured user class.
170
- *
171
- * @param InputInterface $input
172
- * @param OutputInterface $output
173
- *
174
- * @return Question
175
- */
176
- private function createUserClassQuestion (InputInterface $ input , OutputInterface $ output )
177
- {
178
- $ userClassQuestion = new Question (" > <question>Provide your configured user class:</question> " );
179
- $ userClassQuestion ->setAutocompleterValues (array ('Symfony\Component\Security\Core\User\User ' ));
180
-
181
- $ userClassQuestion ->setValidator (function ($ value ) use ($ output ) {
182
- if ('' === trim ($ value )) {
183
- $ value = 'Symfony\Component\Security\Core\User\User ' ;
184
- $ 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" );
185
- }
186
-
187
- return $ value ;
188
- });
189
-
190
- return $ userClassQuestion ;
191
- }
192
-
193
- private function writeIntroduction (OutputInterface $ output )
194
- {
195
- $ output ->writeln (array (
196
- '' ,
197
- $ this ->getHelperSet ()->get ('formatter ' )->formatBlock (
198
- 'Symfony Password Encoder Utility ' ,
199
- 'bg=blue;fg=white ' ,
200
- true
201
- ),
202
- '' ,
203
- ));
204
-
205
- $ output ->writeln (array (
206
- '' ,
207
- 'This command encodes any password you want according to the configuration you ' ,
208
- 'made in your configuration file containing the <comment>security.encoders</comment> key. ' ,
209
- '' ,
210
- ));
162
+ })->setHidden (true )->setMaxAttempts (20 );
211
163
}
212
164
213
- private function writeResult ( OutputInterface $ output )
165
+ private function generateSalt ( )
214
166
{
215
- $ output ->writeln (array (
216
- '' ,
217
- $ this ->getHelperSet ()->get ('formatter ' )->formatBlock (
218
- '✔ Password encoding succeeded ' ,
219
- 'bg=green;fg=white ' ,
220
- true
221
- ),
222
- '' ,
223
- ));
167
+ return base64_encode ($ this ->getContainer ()->get ('security.secure_random ' )->nextBytes (30 ));
224
168
}
225
169
}
0 commit comments