diff --git a/app/bootstrap.php.d/70-user.php b/app/bootstrap.php.d/70-user.php new file mode 100644 index 0000000..32b9642 --- /dev/null +++ b/app/bootstrap.php.d/70-user.php @@ -0,0 +1,41 @@ +register( + new SecurityServiceProvider(), + [ + 'security.firewalls' => [ + 'default' => [ + 'pattern' => '^/user.*$', + 'anonymous' => false, + 'form' => [ + 'login_path' => '/login', + 'check_path' => 'login_check', + ], + 'logout' => [ + 'logout_path' => '/logout' + ], + 'users' => $app->share(function() use ($app) { + return $app['user.provider']; + }), + ], + ], + 'security.access_rules' => [ + ['^/user.*$', 'ROLE_USER'], + ] + ] +); diff --git a/app/console b/app/console index 9a2e551..6eed2fc 100755 --- a/app/console +++ b/app/console @@ -4,11 +4,13 @@ use Gist\Command\CreateCommand; use Gist\Command\UpdateCommand; use Gist\Command\StatsCommand; +use Gist\Command\UserCreateCommand; $app = require __DIR__.'/bootstrap.php'; $app['console']->add(new CreateCommand()); $app['console']->add(new UpdateCommand()); $app['console']->add(new StatsCommand()); +$app['console']->add(new UserCreateCommand()); $app['console']->run(); diff --git a/composer.json b/composer.json index ff724f6..2f25fac 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,8 @@ "symfony/security-csrf": "~2.6", "knplabs/console-service-provider": "~1.0", "propel/propel": "~2.0@dev", - "guzzlehttp/guzzle": "~6.0" + "guzzlehttp/guzzle": "~6.0", + "symfony/security": "^2.7" }, "autoload": { "psr-0": { diff --git a/src/Gist/Command/UserCreateCommand.php b/src/Gist/Command/UserCreateCommand.php new file mode 100644 index 0000000..a3dc742 --- /dev/null +++ b/src/Gist/Command/UserCreateCommand.php @@ -0,0 +1,45 @@ +setName('user:create') + ->setDescription('Create a user') + ->setHelp(""); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $helper = $this->getHelper('question'); + $userProvider = $this->getSilexApplication()['user.provider']; + + $username = ''; + $password = ''; + + while (trim($username) === '') { + $question = new Question('Username: ', ''); + $username = $helper->ask($input, $output, $question); + + if ($userProvider->userExists($username)) { + $output->writeln('This username is already used.'); + $username = ''; + } + } + + while (trim($password) === '') { + $question = new Question('Password: ', ''); + $password = $helper->ask($input, $output, $question); + } + + $userProvider->registerUser($username, $password); + } +} diff --git a/src/Gist/Model/User.php b/src/Gist/Model/User.php new file mode 100644 index 0000000..7fd1cc5 --- /dev/null +++ b/src/Gist/Model/User.php @@ -0,0 +1,14 @@ +setPassword(null); + } +} diff --git a/src/Gist/Model/UserQuery.php b/src/Gist/Model/UserQuery.php new file mode 100644 index 0000000..e299a08 --- /dev/null +++ b/src/Gist/Model/UserQuery.php @@ -0,0 +1,20 @@ + - + + + + +
+ + + + + + + + + +
diff --git a/src/Gist/Service/SaltGenerator.php b/src/Gist/Service/SaltGenerator.php new file mode 100644 index 0000000..95a7405 --- /dev/null +++ b/src/Gist/Service/SaltGenerator.php @@ -0,0 +1,29 @@ + + */ +class SaltGenerator +{ + public function generate($length = 64) + { + if (!is_numeric($length)) { + throw new InvalidArgumentException('Paramter length must be a valid integer.'); + } + + if (function_exists('openssl_random_pseudo_bytes')) { + return substr(base64_encode(openssl_random_pseudo_bytes($length)), 0, $length); + } + + if (function_exists('mcrypt_create_iv')) { + return substr(base64_encode(mcrypt_create_iv($length, MCRYPT_DEV_URANDOM)), 0, $length); + } + + throw new RuntimeException('You must enable openssl or mcrypt modules.'); + } +} diff --git a/src/Gist/Service/UserProvider.php b/src/Gist/Service/UserProvider.php new file mode 100644 index 0000000..60fc181 --- /dev/null +++ b/src/Gist/Service/UserProvider.php @@ -0,0 +1,112 @@ + + */ +class UserProvider implements UserProviderInterface +{ + protected $encoder; + + protected $saltGenerator; + + public function __construct(MessageDigestPasswordEncoder $encoder, SaltGenerator $saltGenerator) + { + $this->encoder = $encoder; + $this->saltGenerator = $saltGenerator; + } + + public function setEncoder(MessageDigestPasswordEncoder $encoder) + { + $this->encoder = $encoder; + + return $this; + } + + public function getEncoder() + { + return $this->encoder; + } + + public function setSaltGenerator(SaltGenerator $saltGenerator) + { + $this->saltGenerator = $saltGenerator; + + return $this; + } + + public function getSaltGenerator() + { + return $this->saltGenerator; + } + + public function userExists($username) + { + return UserQuery::create() + ->filterByUsername($username) + ->count() > 0; + } + + public function registerUser($username, $password) + { + $user = new User(); + + $salt = $this->saltGenerator->generate(64); + + $user + ->setUsername($username) + ->setRoles('ROLE_USER') + ->setSalt($salt); + + $user + ->setPassword($this->encoder->encodePassword($user, $password)) + ->save(); + + return $user; + } + + public function updateUserPassword(User $user, $password) + { + $user + ->setPassword($this->encoder->encodePassword($password)) + ->save(); + + return $user; + } + + public function loadUserByUsername($username) + { + $user = UserQuery::create()->findOneByUsername($username); + + if (null === $user) { + throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username)); + } + + return $user; + } + + public function refreshUser(UserInterface $user) + { + if (!$user instanceof User) { + throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user))); + } + + return $this->loadUserByUsername($user->getUsername()); + } + + public function supportsClass($class) + { + return $class === 'Gist\\Model\\User'; + } +}