PageRenderTime 41ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/user/plugins/login/cli/NewUserCommand.php

https://gitlab.com/asun89/socianovation-web
PHP | 311 lines | 259 code | 22 blank | 30 comment | 16 complexity | a586630fd6b9cfc133a28701cf90333d MD5 | raw file
  1. <?php
  2. namespace Grav\Plugin\Console;
  3. use Grav\Console\ConsoleCommand;
  4. use Grav\Common\File\CompiledYamlFile;
  5. use Grav\Common\User\User;
  6. use Symfony\Component\Console\Input\InputOption;
  7. use Symfony\Component\Console\Helper\Helper;
  8. use Symfony\Component\Console\Question\ChoiceQuestion;
  9. use Symfony\Component\Console\Question\Question;
  10. /**
  11. * Class CleanCommand
  12. *
  13. * @package Grav\Console\Cli
  14. */
  15. class NewUserCommand extends ConsoleCommand
  16. {
  17. /**
  18. * @var array
  19. */
  20. protected $options = [];
  21. /**
  22. * Configure the command
  23. */
  24. protected function configure()
  25. {
  26. $this
  27. ->setName('new-user')
  28. ->setAliases(['add-user', 'newuser'])
  29. ->addOption(
  30. 'user',
  31. 'u',
  32. InputOption::VALUE_REQUIRED,
  33. 'The username'
  34. )
  35. ->addOption(
  36. 'password',
  37. 'p',
  38. InputOption::VALUE_REQUIRED,
  39. "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."
  40. )
  41. ->addOption(
  42. 'email',
  43. 'e',
  44. InputOption::VALUE_REQUIRED,
  45. 'The user email'
  46. )
  47. ->addOption(
  48. 'permissions',
  49. 'P',
  50. InputOption::VALUE_REQUIRED,
  51. '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.'
  52. )
  53. ->addOption(
  54. 'fullname',
  55. 'N',
  56. InputOption::VALUE_REQUIRED,
  57. 'The user full name.'
  58. )
  59. ->addOption(
  60. 'title',
  61. 't',
  62. InputOption::VALUE_REQUIRED,
  63. 'The title of the user. Usually used as a subtext. Example: Admin, Collaborator, Developer'
  64. )
  65. ->addOption(
  66. 'state',
  67. 's',
  68. InputOption::VALUE_REQUIRED,
  69. 'The state of the account. Can be either `enabled` or `disabled`. [default: "enabled"]'
  70. )
  71. ->setDescription('Creates a new user')
  72. ->setHelp('The <info>new-user</info> creates a new user file in user/accounts/ folder')
  73. ;
  74. }
  75. /**
  76. * @return int|null|void
  77. */
  78. protected function serve()
  79. {
  80. $this->options = [
  81. 'user' => $this->input->getOption('user'),
  82. 'password1' => $this->input->getOption('password'),
  83. 'email' => $this->input->getOption('email'),
  84. 'permissions' => $this->input->getOption('permissions'),
  85. 'fullname' => $this->input->getOption('fullname'),
  86. 'title' => $this->input->getOption('title'),
  87. 'state' => $this->input->getOption('state')
  88. ];
  89. $this->validateOptions();
  90. $helper = $this->getHelper('question');
  91. $data = [];
  92. $this->output->writeln('<green>Creating new user</green>');
  93. $this->output->writeln('');
  94. if (!$this->options['user']) {
  95. // Get username and validate
  96. $question = new Question('Enter a <yellow>username</yellow>: ', 'admin');
  97. $question->setValidator(function ($value) {
  98. return $this->validate('user', $value);
  99. });
  100. $username = $helper->ask($this->input, $this->output, $question);
  101. } else {
  102. $username = $this->options['user'];
  103. }
  104. if (!$this->options['password1']) {
  105. // Get password and validate
  106. $password = $this->askForPassword($helper, 'Enter a <yellow>password</yellow>: ', function ($password1) use ($helper) {
  107. $this->validate('password1', $password1);
  108. // Since input is hidden when prompting for passwords, the user is asked to repeat the password
  109. return $this->askForPassword($helper, 'Repeat the <yellow>password</yellow>: ', function ($password2) use ($password1) {
  110. return $this->validate('password2', $password2, $password1);
  111. });
  112. });
  113. $data['password'] = $password;
  114. } else {
  115. $data['password'] = $this->options['password1'];
  116. }
  117. if (!$this->options['email']) {
  118. // Get email and validate
  119. $question = new Question('Enter an <yellow>email</yellow>: ');
  120. $question->setValidator(function ($value) {
  121. return $this->validate('email', $value);
  122. });
  123. $data['email'] = $helper->ask($this->input, $this->output, $question);
  124. } else {
  125. $data['email'] = $this->options['email'];
  126. }
  127. if (!$this->options['permissions']) {
  128. // Choose permissions
  129. $question = new ChoiceQuestion(
  130. 'Please choose a set of <yellow>permissions</yellow>:',
  131. array('a' => 'Admin Access', 's' => 'Site Access', 'b' => 'Admin and Site Access'),
  132. 'a'
  133. );
  134. $question->setErrorMessage('Permissions %s is invalid.');
  135. $permissions_choice = $helper->ask($this->input, $this->output, $question);
  136. } else {
  137. $permissions_choice = $this->options['permissions'];
  138. }
  139. switch ($permissions_choice) {
  140. case 'a':
  141. $data['access']['admin'] = ['login' => true, 'super' => true];
  142. break;
  143. case 's':
  144. $data['access']['site'] = ['login' => true];
  145. break;
  146. case 'b':
  147. $data['access']['admin'] = ['login' => true, 'super' => true];
  148. $data['access']['site'] = ['login' => true];
  149. }
  150. if (!$this->options['fullname']) {
  151. // Get fullname
  152. $question = new Question('Enter a <yellow>fullname</yellow>: ');
  153. $question->setValidator(function ($value) {
  154. return $this->validate('fullname', $value);
  155. });
  156. $data['fullname'] = $helper->ask($this->input, $this->output, $question);
  157. } else {
  158. $data['fullname'] = $this->options['fullname'];
  159. }
  160. if (!$this->options['title'] && !count(array_filter($this->options))) {
  161. // Get title
  162. $question = new Question('Enter a <yellow>title</yellow>: ');
  163. $data['title'] = $helper->ask($this->input, $this->output, $question);
  164. } else {
  165. $data['title'] = $this->options['title'];
  166. }
  167. if (!$this->options['state'] && !count(array_filter($this->options))) {
  168. // Choose State
  169. $question = new ChoiceQuestion(
  170. 'Please choose the <yellow>state</yellow> for the account:',
  171. array('enabled' => 'Enabled', 'disabled' => 'Disabled'),
  172. 'enabled'
  173. );
  174. $question->setErrorMessage('State %s is invalid.');
  175. $data['state'] = $helper->ask($this->input, $this->output, $question);
  176. } else {
  177. $data['state'] = $this->options['state'] ?: 'enabled';
  178. }
  179. // Lowercase the username for the filename
  180. $username = strtolower($username);
  181. // Create user object and save it
  182. $user = new User($data);
  183. $file = CompiledYamlFile::instance(self::getGrav()['locator']->findResource('user://accounts/' . $username . YAML_EXT, true, true));
  184. $user->file($file);
  185. $user->save();
  186. $this->output->writeln('');
  187. $this->output->writeln('<green>Success!</green> User <cyan>' . $username . '</cyan> created.');
  188. }
  189. /**
  190. *
  191. */
  192. protected function validateOptions()
  193. {
  194. foreach (array_filter($this->options) as $type => $value) {
  195. $this->validate($type, $value);
  196. }
  197. }
  198. /**
  199. * @param $type
  200. * @param $value
  201. * @param string $extra
  202. *
  203. * @return mixed
  204. */
  205. protected function validate($type, $value, $extra = '')
  206. {
  207. switch ($type) {
  208. case 'user':
  209. if (!preg_match('/^[a-z0-9_-]{3,16}$/', $value)) {
  210. throw new \RuntimeException('Username should be between 3 and 16 characters, including lowercase letters, numbers, underscores, and hyphens. Uppercase letters, spaces, and special characters are not allowed');
  211. }
  212. if (file_exists(self::getGrav()['locator']->findResource('user://accounts/' . $value . YAML_EXT))) {
  213. throw new \RuntimeException('Username "' . $value . '" already exists, please pick another username');
  214. }
  215. break;
  216. case 'password1':
  217. if (!preg_match('/(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}/', $value)) {
  218. throw new \RuntimeException('Password must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters');
  219. }
  220. break;
  221. case 'password2':
  222. if (strcmp($value, $extra)) {
  223. throw new \RuntimeException('Passwords did not match.');
  224. }
  225. break;
  226. case 'email':
  227. if (!preg_match('/^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/', $value)) {
  228. throw new \RuntimeException('Not a valid email address');
  229. }
  230. break;
  231. case 'permissions':
  232. if (!in_array($value, ['a', 's', 'b'])) {
  233. throw new \RuntimeException('Permissions ' . $value . ' are invalid.');
  234. }
  235. break;
  236. case 'fullname':
  237. if ($value === null || trim($value) == '') {
  238. throw new \RuntimeException('Fullname cannot be empty');
  239. }
  240. break;
  241. case 'state':
  242. if ($value !== 'enabled' && $value !== 'disabled') {
  243. throw new \RuntimeException('State is not valid');
  244. }
  245. break;
  246. }
  247. return $value;
  248. }
  249. /**
  250. * Get password and validate.
  251. *
  252. * @param Helper $helper
  253. * @param string $question
  254. * @param callable $validator
  255. *
  256. * @return string
  257. */
  258. protected function askForPassword(Helper $helper, $question, callable $validator)
  259. {
  260. $question = new Question($question);
  261. $question->setValidator($validator);
  262. $question->setHidden(true);
  263. $question->setHiddenFallback(true);
  264. return $helper->ask($this->input, $this->output, $question);
  265. }
  266. }