1414use  Symfony \Bundle \FrameworkBundle \Command \ContainerAwareCommand ;
1515use  Symfony \Component \Console \Input \InputArgument ;
1616use  Symfony \Component \Console \Input \InputInterface ;
17+ use  Symfony \Component \Console \Input \InputOption ;
1718use  Symfony \Component \Console \Output \OutputInterface ;
1819use  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 ;
2022
2123/** 
2224 * Encode a user's password. 
@@ -32,35 +34,45 @@ protected function configure()
3234 {
3335 $ this 
3436 ->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- saltnull , InputOption:: VALUE_NONE , 'Do not generate a salt or let the encoder generate one . )
3941 ->setHelp (<<<EOF 
4042
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: 
4349
44- For instance, if you have the following configuration for your application: 
4550<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 
5056</comment> 
5157
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> 
5867
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: 
6170
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> 
6476
6577EOF 
6678 )
@@ -72,154 +84,86 @@ protected function configure()
7284 */ 
7385 protected  function  execute (InputInterface $ inputOutputInterface $ output
7486 {
75-  $ this writeIntroduction ($ output
87+  $ outputnew  SymfonyStyle ($ input$ output
88+ 
89+  $ inputisInteractive () ? $ outputtitle ('Symfony Password Encoder Utility ' ) : $ outputnewLine ();
7690
7791 $ password$ inputgetArgument ('password ' );
78-  $ salt$ inputgetArgument ('salt ' );
7992 $ userClass$ inputgetArgument ('user-class ' );
93+  $ emptySalt$ inputgetOption ('empty-salt ' );
8094
81-  $ helper$ this getHelper ('question ' );
95+  $ encoder$ this getContainer ()->get ('security.encoder_factory ' )->getEncoder ($ userClass
96+  $ bcryptWithoutEmptySalt$ emptySalt$ encoderinstanceof  BCryptPasswordEncoder;
97+ 
98+  if  ($ bcryptWithoutEmptySalt
99+  $ emptySalttrue ;
100+  }
82101
83102 if  (!$ password
103+  if  (!$ inputisInteractive ()) {
104+  $ outputerror ('The password must not be empty. ' );
105+ 
106+  return  1 ;
107+  }
84108 $ passwordQuestion$ this createPasswordQuestion ($ input$ output
85-  $ password$ helper -> ask ( $ input ,  $ output ,  $ passwordQuestion
109+  $ password$ output -> askQuestion ( $ passwordQuestion
86110 }
87111
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 ; 
92116
93-  $ outputwriteln ( "\n  <comment>Encoders are configured by user type in  the security.yml file.</comment> " 
117+    $ outputnote ( ' 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. ' 
94118
95-  if  (!$ userClass
96-  $ userClassQuestion$ this createUserClassQuestion ($ input$ output
97-  $ userClass$ helperask ($ input$ output$ userClassQuestion
119+  if  ($ outputconfirm ('Confirm salt generation ? ' )) {
120+  $ salt$ this generateSalt ();
121+  $ emptySaltfalse ;
122+  }
123+  } elseif  (!$ emptySalt
124+  $ salt$ this generateSalt ();
98125 }
99126
100-  $ encoder$ this getContainer ()->get ('security.encoder_factory ' )->getEncoder ($ userClass
101127 $ encodedPassword$ encoderencodePassword ($ password$ salt
102128
103-  $ this writeResult ($ output
129+  $ rowsarray (
130+  array ('Encoder used ' , get_class ($ encoder
131+  array ('Encoded password ' , $ encodedPassword
132+  );
133+  if  (!$ emptySalt
134+  $ rowsarray ('Generated salt ' , $ salt
135+  }
136+  $ outputtable (array ('Key ' , 'Value ' ), $ rows
104137
105-  $ tablenew  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+  $ outputnote (sprintf ('Make sure that your salt storage field fits the salt length: %s chars ' , strlen ($ salt
140+  } elseif  ($ bcryptWithoutEmptySalt
141+  $ outputnote ('Bcrypt encoder used: the encoder generated its own built-in salt. ' );
142+  }
111143
112-  $ table -> render ( );
144+  $ output -> success ( ' Password encoding succeeded ' 
113145 }
114146
115147 /** 
116148 * Create the password question to ask the user for the password to be encoded. 
117149 * 
118-  * @param InputInterface $input 
119-  * @param OutputInterface $output 
120-  * 
121150 * @return Question 
122151 */ 
123-  private  function  createPasswordQuestion (InputInterface   $ input ,  OutputInterface   $ output 
152+  private  function  createPasswordQuestion ()
124153 {
125-  $ passwordQuestionnew  Question ("\n  > <question> Type in your password to be encoded:</question>  " 
154+  $ passwordQuestionnew  Question (' Type in your password to be encoded ' 
126155
127-  $ passwordQuestionsetValidator (function  ($ value
156+  return   $ passwordQuestionsetValidator (function  ($ value
128157 if  (''  === trim ($ value
129158 throw  new  \Exception ('The password must not be empty. ' );
130159 }
131160
132161 return  $ value
133-  });
134-  $ passwordQuestionsetHidden (true );
135-  $ passwordQuestionsetMaxAttempts (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 $ inputOutputInterface $ output
150-  {
151-  $ saltQuestionnew  Question ("\n > (Optional) <question>Provide a salt (press <enter> to generate one):</question>  " );
152- 
153-  $ container$ this getContainer ();
154-  $ saltQuestionsetValidator (function  ($ valueuse  ($ output$ container
155-  if  (''  === trim ($ value
156-  $ valuebase64_encode ($ containerget ('security.secure_random ' )->nextBytes (30 ));
157- 
158-  $ outputwriteln ("\n<comment>The salt has been generated: </comment> " .$ value
159-  $ outputwriteln (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 $ inputOutputInterface $ output
177-  {
178-  $ userClassQuestionnew  Question (" > <question>Provide your configured user class:</question>  " );
179-  $ userClassQuestionsetAutocompleterValues (array ('Symfony\Component\Security\Core\User\User ' ));
180- 
181-  $ userClassQuestionsetValidator (function  ($ valueuse  ($ output
182-  if  (''  === trim ($ value
183-  $ value'Symfony\Component\Security\Core\User\User ' ;
184-  $ outputwriteln ("<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-  $ outputwriteln (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-  $ outputwriteln (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 );
211163 }
212164
213-  private  function  writeResult ( OutputInterface   $ output 
165+  private  function  generateSalt ( )
214166 {
215-  $ outputwriteln (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 ));
224168 }
225169}
0 commit comments