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';
+ }
+}