NewUserCommand.php 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. <?php
  2. /**
  3. * @package Grav\Plugin\Login
  4. *
  5. * @copyright Copyright (C) 2014 - 2017 RocketTheme, LLC. All rights reserved.
  6. * @license MIT License; see LICENSE file for details.
  7. */
  8. namespace Grav\Plugin\Console;
  9. use Grav\Common\Config\Config;
  10. use Grav\Console\ConsoleCommand;
  11. use Grav\Common\File\CompiledYamlFile;
  12. use Grav\Common\User\User;
  13. use Grav\Common\Grav;
  14. use Grav\Plugin\Login\Login;
  15. use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
  16. use Symfony\Component\Console\Input\InputOption;
  17. use Symfony\Component\Console\Helper\Helper;
  18. use Symfony\Component\Console\Question\ChoiceQuestion;
  19. use Symfony\Component\Console\Question\Question;
  20. /**
  21. * Class CleanCommand
  22. *
  23. * @package Grav\Console\Cli
  24. */
  25. class NewUserCommand extends ConsoleCommand
  26. {
  27. /** @var array */
  28. protected $options = [];
  29. /** @var Login */
  30. protected $login;
  31. /**
  32. * Configure the command
  33. */
  34. protected function configure()
  35. {
  36. $this
  37. ->setName('new-user')
  38. ->setAliases(['add-user', 'newuser'])
  39. ->addOption(
  40. 'user',
  41. 'u',
  42. InputOption::VALUE_REQUIRED,
  43. 'The username'
  44. )
  45. ->addOption(
  46. 'password',
  47. 'p',
  48. InputOption::VALUE_REQUIRED,
  49. "The password. Note that this option is not recommended because the password will be visible by users listing the processes. You should also make sure the password respects Grav's password policy."
  50. )
  51. ->addOption(
  52. 'email',
  53. 'e',
  54. InputOption::VALUE_REQUIRED,
  55. 'The user email'
  56. )
  57. ->addOption(
  58. 'permissions',
  59. 'P',
  60. InputOption::VALUE_REQUIRED,
  61. 'The user permissions. It can be either `a` for Admin access only, `s` for Site access only and `b` for both Admin and Site access.'
  62. )
  63. ->addOption(
  64. 'fullname',
  65. 'N',
  66. InputOption::VALUE_REQUIRED,
  67. 'The user full name.'
  68. )
  69. ->addOption(
  70. 'title',
  71. 't',
  72. InputOption::VALUE_REQUIRED,
  73. 'The title of the user. Usually used as a subtext. Example: Admin, Collaborator, Developer'
  74. )
  75. ->addOption(
  76. 'state',
  77. 's',
  78. InputOption::VALUE_REQUIRED,
  79. 'The state of the account. Can be either `enabled` or `disabled`. [default: "enabled"]'
  80. )
  81. ->setDescription('Creates a new user')
  82. ->setHelp('The <info>new-user</info> creates a new user file in user/accounts/ folder')
  83. ;
  84. }
  85. /**
  86. * @return int|null|void
  87. */
  88. protected function serve()
  89. {
  90. include __DIR__ . '/../vendor/autoload.php';
  91. $grav = Grav::instance();
  92. if (!isset($grav['login'])) {
  93. $grav['login'] = new Login($grav);
  94. }
  95. $this->login = $grav['login'];
  96. $this->options = [
  97. 'user' => $this->input->getOption('user'),
  98. 'password1' => $this->input->getOption('password'),
  99. 'email' => $this->input->getOption('email'),
  100. 'permissions' => $this->input->getOption('permissions'),
  101. 'fullname' => $this->input->getOption('fullname'),
  102. 'title' => $this->input->getOption('title'),
  103. 'state' => $this->input->getOption('state')
  104. ];
  105. $this->validateOptions();
  106. $helper = $this->getHelper('question');
  107. $data = [];
  108. $this->output->writeln('<green>Creating new user</green>');
  109. $this->output->writeln('');
  110. if (!$this->options['user']) {
  111. // Get username and validate
  112. $question = new Question('Enter a <yellow>username</yellow>: ', 'admin');
  113. $question->setValidator(function ($value) {
  114. $this->validate('user', $value);
  115. if (User::find($value, ['username'])->exists()) {
  116. throw new \RuntimeException('Username "' . $value . '" already exists, please pick another username');
  117. };
  118. return $value;
  119. });
  120. $data['username'] = $helper->ask($this->input, $this->output, $question);
  121. } else {
  122. $data['username'] = $this->options['user'];
  123. }
  124. if (!$this->options['password1']) {
  125. // Get password and validate
  126. $password = $this->askForPassword($helper, 'Enter a <yellow>password</yellow>: ', function ($password1) use ($helper) {
  127. $this->validate('password1', $password1);
  128. // Since input is hidden when prompting for passwords, the user is asked to repeat the password
  129. return $this->askForPassword($helper, 'Repeat the <yellow>password</yellow>: ', function ($password2) use ($password1) {
  130. return $this->validate('password2', $password2, $password1);
  131. });
  132. });
  133. $data['password'] = $password;
  134. } else {
  135. $data['password'] = $this->options['password1'];
  136. }
  137. if (!$this->options['email']) {
  138. // Get email and validate
  139. $question = new Question('Enter an <yellow>email</yellow>: ');
  140. $question->setValidator(function ($value) {
  141. return $this->validate('email', $value);
  142. });
  143. $data['email'] = $helper->ask($this->input, $this->output, $question);
  144. } else {
  145. $data['email'] = $this->options['email'];
  146. }
  147. if (!$this->options['permissions']) {
  148. // Choose permissions
  149. $question = new ChoiceQuestion(
  150. 'Please choose a set of <yellow>permissions</yellow>:',
  151. array('a' => 'Admin Access', 's' => 'Site Access', 'b' => 'Admin and Site Access'),
  152. 'a'
  153. );
  154. $question->setErrorMessage('Permissions %s is invalid.');
  155. $permissions_choice = $helper->ask($this->input, $this->output, $question);
  156. } else {
  157. $permissions_choice = $this->options['permissions'];
  158. }
  159. switch ($permissions_choice) {
  160. case 'a':
  161. $data['access']['admin'] = ['login' => true, 'super' => true];
  162. break;
  163. case 's':
  164. $data['access']['site'] = ['login' => true];
  165. break;
  166. case 'b':
  167. $data['access']['admin'] = ['login' => true, 'super' => true];
  168. $data['access']['site'] = ['login' => true];
  169. }
  170. if (!$this->options['fullname']) {
  171. // Get fullname
  172. $question = new Question('Enter a <yellow>fullname</yellow>: ');
  173. $question->setValidator(function ($value) {
  174. return $this->validate('fullname', $value);
  175. });
  176. $data['fullname'] = $helper->ask($this->input, $this->output, $question);
  177. } else {
  178. $data['fullname'] = $this->options['fullname'];
  179. }
  180. if (!$this->options['title'] && !count(array_filter($this->options))) {
  181. // Get title
  182. $question = new Question('Enter a <yellow>title</yellow>: ');
  183. $data['title'] = $helper->ask($this->input, $this->output, $question);
  184. } else {
  185. $data['title'] = $this->options['title'];
  186. }
  187. if (!$this->options['state'] && !count(array_filter($this->options))) {
  188. // Choose State
  189. $question = new ChoiceQuestion(
  190. 'Please choose the <yellow>state</yellow> for the account:',
  191. array('enabled' => 'Enabled', 'disabled' => 'Disabled'),
  192. 'enabled'
  193. );
  194. $question->setErrorMessage('State %s is invalid.');
  195. $data['state'] = $helper->ask($this->input, $this->output, $question);
  196. } else {
  197. $data['state'] = $this->options['state'] ?: 'enabled';
  198. }
  199. // Lowercase the username for the filename
  200. $username = mb_strtolower($data['username']);
  201. /** @var UniformResourceLocator $locator */
  202. $locator = Grav::instance()['locator'];
  203. // Create user object and save it
  204. $user = new User($data);
  205. $file = CompiledYamlFile::instance($locator->findResource('account://' . $username . YAML_EXT, true, true));
  206. $user->file($file);
  207. $user->save();
  208. $this->output->writeln('');
  209. $this->output->writeln('<green>Success!</green> User <cyan>' . $username . '</cyan> created.');
  210. }
  211. /**
  212. *
  213. */
  214. protected function validateOptions()
  215. {
  216. foreach (array_filter($this->options) as $type => $value) {
  217. $this->validate($type, $value);
  218. }
  219. }
  220. /**
  221. * @param string $type
  222. * @param mixed $value
  223. * @param string $extra
  224. *
  225. * @return string
  226. */
  227. protected function validate($type, $value, $extra = '')
  228. {
  229. return $this->login->validateField($type, $value, $extra);
  230. }
  231. /**
  232. * Get password and validate.
  233. *
  234. * @param Helper $helper
  235. * @param string $question
  236. * @param callable $validator
  237. *
  238. * @return string
  239. */
  240. protected function askForPassword(Helper $helper, $question, callable $validator)
  241. {
  242. $question = new Question($question);
  243. $question->setValidator($validator);
  244. $question->setHidden(true);
  245. $question->setHiddenFallback(true);
  246. return $helper->ask($this->input, $this->output, $question);
  247. }
  248. }