From a7d172a9372c8d6a65af86f212e3b57048c7c1d5 Mon Sep 17 00:00:00 2001 From: "R. Eric Wheeler" Date: Mon, 13 Feb 2017 09:56:18 -0800 Subject: [PATCH] WIP: Saving Local Branch --- app/Kernel.php | 68 +++++++++-- app/views/base.html.twig | 4 +- app/views/form_errors.html.twig | 22 ++++ app/views/login.html.twig | 38 +++++++ app/views/reset_password_token.html.twig | 39 +++++++ app/views/rsvp_form.html.twig | 1 + bin/console.php | 50 +++++++++ cli-config.php | 21 ---- composer.json | 3 +- doctrine.php | 30 ----- html/index.php | 12 +- .../App/Controller/DefaultController.php | 77 +++++++------ .../App/Controller/RsvpController.php | 23 +++- src/Sikofitt/App/Entity/User.php | 81 +++++++++---- src/Sikofitt/App/Form/ResetPasswordType.php | 83 ++++++++++++++ src/Sikofitt/App/Form/UserLoginType.php | 79 +++++++++++++ .../Middleware/AuthenticatorMiddleware.php | 33 ++++++ .../App/Provider/DoctrineConsoleProvider.php | 106 ++++++++++++++++++ .../App/Repository/UserRepository.php | 33 +++++- .../App/Traits/EntityManagerTrait.php | 38 +++++++ src/Sikofitt/App/Traits/FlashTrait.php | 104 ++++++++++++++++- src/Sikofitt/Security/MySqlUserProvider.php | 100 +++++++++++++++++ src/Sikofitt/Security/MysqlAuthenticator.php | 45 +++++++- 23 files changed, 953 insertions(+), 137 deletions(-) create mode 100644 app/views/form_errors.html.twig create mode 100644 app/views/login.html.twig create mode 100644 app/views/reset_password_token.html.twig create mode 100755 bin/console.php delete mode 100644 cli-config.php delete mode 100644 doctrine.php create mode 100644 src/Sikofitt/App/Form/ResetPasswordType.php create mode 100644 src/Sikofitt/App/Form/UserLoginType.php create mode 100644 src/Sikofitt/App/Middleware/AuthenticatorMiddleware.php create mode 100644 src/Sikofitt/App/Provider/DoctrineConsoleProvider.php create mode 100644 src/Sikofitt/App/Traits/EntityManagerTrait.php create mode 100644 src/Sikofitt/Security/MySqlUserProvider.php diff --git a/app/Kernel.php b/app/Kernel.php index 9ad4035..42bcb9d 100644 --- a/app/Kernel.php +++ b/app/Kernel.php @@ -30,7 +30,11 @@ use Monolog\{ Handler\StreamHandler, Logger }; +use Sikofitt\App\Controller\DefaultController; +use Sikofitt\App\Entity\User; +use Sikofitt\App\Traits\EntityManagerTrait; use Sikofitt\App\Traits\FlashTrait; +use Sikofitt\Security\MySqlUserProvider; use Silex\Application; use Silex\Application\{ FormTrait, @@ -72,6 +76,7 @@ use Symfony\Component\Translation\Translator; */ class Kernel extends Application { + use EntityManagerTrait; use FlashTrait; use FormTrait; use MonologTrait; @@ -81,6 +86,7 @@ class Kernel extends Application use TwigTrait; use UrlGeneratorTrait; + /** * Kernel constructor. * @@ -95,13 +101,19 @@ class Kernel extends Application if (true === $debug) { $this->setDebug(); } + $this->setUpProviders(); $this->setUpDatabase(); $this->setUpView(); $this->setUpLogger(); $this->setUpMailer(); } + public function setUpRoutes(\Kernel $app) + { + $app->match('/login', DefaultController::class.'::loginAction') + ->method('GET|POST'); + } /** * @param array $values * @@ -266,8 +278,6 @@ class Kernel extends Application * Closure supports \Twig_Environment and Silex\Application as a second * parameter, but we never use Silex\Application so we leave it out. */ - $r = new \Symfony\Component\HttpFoundation\RequestStack(); - $this->extend('twig', function (\Twig_Environment $twig) { $twig->addGlobal('session', $this['session']); $twig->addExtension(new TranslationExtension(new Translator('en'))); @@ -281,19 +291,57 @@ class Kernel extends Application */ protected function setUpProviders() { + /*$this['app.mysql_authenticator'] = function($app) { + return new Sikofitt\Security\MysqlAuthenticator($app['security.encoder_factory'], $app->getEntityManager()); + }; + $this['security.firewalls'] = array( + + 'login' => [ + 'pattern' => '^/login$', + 'anonymous' => true, + + + ], + 'secured' => [ + 'pattern' => '^/rsvp$', + 'guard' => [ + 'authenticators' => [ + 'app.mysql_authenticator', + ], + 'form' => [ + 'login_path' => '/login', + 'check_path' => '/login', + ] + + ], + + 'users' => $this['users'] = function() { + return new MySqlUserProvider($this['orm.em']); + }, + ], + + + // configure where your users come from. Hardcode them, or load them from somewhere + // http://silex.sensiolabs.org/doc/providers/security.html#defining-a-custom-user-provider + + // 'anonymous' => true + + );*/ + $this['protected_pages'] = function() { + return [ + 'gallery', + 'rsvp/update' + ]; + }; + $this ->register(new CsrfServiceProvider()) ->register(new FormServiceProvider()) - ->register(new SecurityServiceProvider(), [ - 'security.firewalls' => [ - 'admin' => [ - 'pattern' => '^/admin', - 'http' => true, - ], - ], - ]) + //->register(new SecurityServiceProvider()) ; + + $this->extend('form.extensions', function ($extensions) { return $extensions; }); diff --git a/app/views/base.html.twig b/app/views/base.html.twig index 3656cc3..fa3ed10 100644 --- a/app/views/base.html.twig +++ b/app/views/base.html.twig @@ -1,4 +1,6 @@ - +{% if form is defined %} +{% form_theme form with [_self, 'form_errors.html.twig'] %} +{% endif %} diff --git a/app/views/form_errors.html.twig b/app/views/form_errors.html.twig new file mode 100644 index 0000000..d6c198d --- /dev/null +++ b/app/views/form_errors.html.twig @@ -0,0 +1,22 @@ + +{# form_errors.html.twig #} +{% block form_errors %} + {% spaceless %} + {% if errors|length > 0 %} + {% if compound %} +
+ + {% for error in errors %} +

{{ error.message }}

+ {% endfor %} +
+ {% else %} +
+ + {% set error = errors|first %} +

{{ error.message }}

+
+ {% endif %} + {% endif %} + {% endspaceless %} +{% endblock form_errors %} \ No newline at end of file diff --git a/app/views/login.html.twig b/app/views/login.html.twig new file mode 100644 index 0000000..8ef82ca --- /dev/null +++ b/app/views/login.html.twig @@ -0,0 +1,38 @@ +{% extends 'base.html.twig' %} + +{% block body %} + + {{ form_start(form) }} +
+ + Login +
+ {{ form_errors(form.email_username) }} + {{ form_errors(form.password) }} +
+ +
+ {{ form_label(form.email_username) }} +
+ + + {{ form_widget(form.email_username) }} +
+
+
+ {{ form_label(form.password) }} + +
+ + {{ form_widget(form.password) }} +
+
+
+ {{ form_row(form.submit) }} +
+
+ {{ form_rest(form) }} +
+
+ {{ form_end(form) }} +{% endblock %} \ No newline at end of file diff --git a/app/views/reset_password_token.html.twig b/app/views/reset_password_token.html.twig new file mode 100644 index 0000000..16da54f --- /dev/null +++ b/app/views/reset_password_token.html.twig @@ -0,0 +1,39 @@ +{% extends 'base.html.twig' %} + +{% block body %} + + {% if token.valid == false %} +

+ Sorry your token ({{ token.value }}) is invalid. +

+

+ Please see {{ url('rsvp_password_reset') }}. +

+{% else %} + {{ form_start(form) }} +
+ Choose a new password . +
+ {{ form_label(form.password.children.first) }} +
+ + {{ form_errors(form.password.children.first) }} + {{ form_widget(form.password.children.first) }} +
+ + {{ form_label(form.password.children.second) }} +
+ {{ form_errors(form.password.children.second) }} + {{ form_widget(form.password.children.second) }} +
+
+ {{ form_row(form.submit) }} +
+ +
+ {{ form_rest(form) }} +
+ + {{ form_end(form) }} + {% endif %} +{% endblock %} \ No newline at end of file diff --git a/app/views/rsvp_form.html.twig b/app/views/rsvp_form.html.twig index 6a86881..229e8bf 100644 --- a/app/views/rsvp_form.html.twig +++ b/app/views/rsvp_form.html.twig @@ -2,6 +2,7 @@ {% block body %} + {{ dump(app.session.get('user')) }} {{ form_start(form) }}
RSVP! diff --git a/bin/console.php b/bin/console.php new file mode 100755 index 0000000..9de444c --- /dev/null +++ b/bin/console.php @@ -0,0 +1,50 @@ +#!/usr/bin/env php +. + */ + +use Doctrine\ORM\Tools\Console\Command\ClearCache\EntityRegionCommand; +use Doctrine\ORM\Tools\Console\Command\MappingDescribeCommand; +use Knp\Provider\ConsoleServiceProvider; +use PhpCsFixer\Console\Command\DescribeCommand; +use PhpCsFixer\Console\Command\SelfUpdateCommand; +use Sikofitt\App\Provider\DoctrineConsoleProvider; +use Symfony\Component\Console\Application; +use Symfony\Component\Yaml\Command\LintCommand; + +$loader = require __DIR__.'/../vendor/autoload.php'; +$app = new \Kernel($loader, true); +$consoleConfig = [ + 'console.name' => 'Doughnut Wedding', + 'console.version' => '0.0.2', + 'console.project_directory' => __DIR__.'/..', +]; + +$app + ->register(new ConsoleServiceProvider(), $consoleConfig) + ->register(new DoctrineConsoleProvider()); +/** + * @var Application $console + */ +$console = $app['console']; +$console->add(new Symfony\Bridge\Twig\Command\LintCommand()); +$console->add(new Symfony\Bridge\Twig\Command\DebugCommand()); +$console->add(new PhpCsFixer\Console\Command\FixCommand()); +$console->add(new Symfony\Component\Yaml\Command\LintCommand()); +$app['console']->run(); diff --git a/cli-config.php b/cli-config.php deleted file mode 100644 index 3022f6d..0000000 --- a/cli-config.php +++ /dev/null @@ -1,21 +0,0 @@ - new ConnectionHelper($em->getConnection()), - 'em' => new EntityManagerHelper($em), - -]); - -return $helperSet; diff --git a/composer.json b/composer.json index f20602e..75f11e0 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "doughnutwedding.com website", "type": "project", "require": { - "php":">=7.0", + "php": ">=7.0", "bramus/monolog-colored-line-formatter": "~2.0", "container-interop/container-interop": "^1.1", "dflydev/doctrine-orm-service-provider": "^2.0", @@ -15,6 +15,7 @@ "google/recaptcha": "^1.1", "ircmaxell/random-lib": "^1.2", "ircmaxell/security-lib": "^1.1", + "knplabs/console-service-provider": "^2.0", "monolog/monolog": "^1.22", "paragonie/cookie": "^3.1", "paragonie/csp-builder": "^2.0", diff --git a/doctrine.php b/doctrine.php deleted file mode 100644 index f5e6fad..0000000 --- a/doctrine.php +++ /dev/null @@ -1,30 +0,0 @@ - [ - 'annotation_autoloaders' => ['class_exists'], - 'connection' => [ - 'driver' => 'pdo_mysql', - 'user' => 'doughnut', - 'password' => 'doughnut', - 'dbname' => 'doughnut', - 'host' => 'mysql', - ], - 'metadata_mapping' => [ - [ - 'type' => ManagerBuilder::METADATA_MAPPING_ANNOTATION, - 'path' => [__DIR__ . '/src/Sikofitt/App/Entity'], - ], - ], - ], -]; - -$managerBuilder = new ManagerBuilder([ManagerBuilder::RELATIONAL_MANAGER_KEY => 'default']); -$managerBuilder->loadSettings($settings); -return $managerBuilder->getManager('entityManager'); - - - diff --git a/html/index.php b/html/index.php index 4ace820..fefff3c 100644 --- a/html/index.php +++ b/html/index.php @@ -28,20 +28,21 @@ $loader = require __DIR__.'/../vendor/autoload.php'; $app = new Kernel($loader, true); // Controllers // Default -$app->get('/', DefaultController::class.'::indexAction') - ->bind('index'); +$app->setUpRoutes($app); + //$app->match('/login', DefaultController::class.'loginAction') // ->bind('login'); // RSVP Actions + $app->match('/rsvp', RsvpController::class.'::indexAction') ->method('GET|POST') ->bind('rsvp'); $app->match('/rsvp/reset', RsvpController::class.'::resetAction') ->method('GET|POST') ->bind('rsvp_password_reset'); -$app->get('/rsvp/reset/{token}', RsvpController::class.'::tokenAction') - ->bind('rsvp_token'); - +$app->match('/rsvp/reset/{token}', RsvpController::class.'::tokenAction') + ->bind('rsvp_token') + ->method('GET|POST'); //->before(new MysqlAuthenticatorMiddleware()); $app->match('/rsvp/edit', RsvpController::class.'::editAction') ->method('GET|POST') @@ -52,4 +53,5 @@ $app->before(new CspMiddleware(), \Kernel::EARLY_EVENT); $app->before(new HeaderMiddleware(), \Kernel::EARLY_EVENT); // Run the app + $app->run(); diff --git a/src/Sikofitt/App/Controller/DefaultController.php b/src/Sikofitt/App/Controller/DefaultController.php index 09a4044..716e536 100644 --- a/src/Sikofitt/App/Controller/DefaultController.php +++ b/src/Sikofitt/App/Controller/DefaultController.php @@ -20,12 +20,10 @@ namespace Sikofitt\App\Controller; -use Sikofitt\App\Form\RsvpType; -use Symfony\Component\Form\Extension\Csrf\CsrfExtension; -use Symfony\Component\Form\Extension\HttpFoundation\Type\FormTypeHttpFoundationExtension; -use Symfony\Component\Form\Forms; +use Sikofitt\App\Entity\User; +use Sikofitt\App\Form\UserLoginType; +use Symfony\Component\Form\FormError; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\Security\Csrf\CsrfTokenManager; class DefaultController { @@ -34,42 +32,47 @@ class DefaultController return $app->render('index.html.twig', ['request' => $request]); } - public function rsvpAction() + public function loginAction(Request $request, \Kernel $app) { - /* $app = $this->app; - $rsvp = new Rsvp(); - $rsvp - ->setGuests(2) - ->setCreated(new \DateTime('now')) - ->setUpdated(new \DateTime('now')); + if ($app->session()->has('user')) { + //return $app->redirect($app->url('rsvp_edit')); + } + $loginForm = $app->getFormFactory()->create(UserLoginType::class); + if ($request->isMethod('POST')) { + $loginForm->handleRequest($request); + if ($loginForm->isValid() && $loginForm->isSubmitted()) { + $user = $app->getEntityManager()->getRepository(User::class)->findByEmail($loginForm->get('email_username')->getData()); + if (null !== $user && true === password_verify($loginForm->get('password')->getData(), $user[0]->getPassword())) { + $userSession = [ + 'firstName' => $user[0]->getFirstName(), + 'lastName' => $user[0]->getLastName(), + 'fullName' => sprintf('%s %s', $user[0]->getFirstName(), $user[0]->getLastName()), + 'familySide' => $user[0]->getFamilySide(), + 'email' => $user[0]->getEmail(), + 'family' => $user[0]->getFamily(), + 'created' => $user[0]->getCreated()->format('U'), + 'updated' => $user[0]->getUpdated()->format('U'), + 'guests' => $user[0]->getRsvp()->getGuests(), + ]; + $app->getSession()->set('user', $userSession); + $app->redirect($app->url('rsvp')); + } else { + $error = new FormError('Your password or email is incorrect.'); + $error->setOrigin($loginForm); + $loginForm->get('password')->addError($error); - $user = new User(); - $user->setFirstName('Eric') - ->setLastName('Wheeler') - ->setFamily(true) - ->setEmail('sikofitt@gmail.com') - ->setCreated(new \DateTime('now')) - ->setUpdated(new \DateTime('now')) - ->setFamilySide(User::ERIC_SIDE) - ->setRsvp($rsvp); + return $app->render('login.html.twig', ['form' => $loginForm->createView()]); + } + } + } + return $app->render('login.html.twig', ['form' => $loginForm->createView()]); + } - $app['em']->persist($user); - $app['em']->flush(); */ - $bytes = \ParagonIE_Sodium_Compat::randombytes_buf(22); + public function logoutAction(Request $request, \Kernel $app) + { + $app->session()->remove('user'); - $password = new Password(new ScryptPassword()); - // dump($password->hash('password')); - $blake = \ParagonIE_Sodium_Compat::crypto_generichash($bytes); - $blake2b = \ParagonIE_Sodium_Core_BLAKE2b::bin2hex($blake); - - $formFactory = Forms::createFormFactoryBuilder() - ->addTypeExtension(new FormTypeHttpFoundationExtension()) - ->addExtension(new CsrfExtension(new CsrfTokenManager())) - ->getFormFactory(); - $form = $formFactory->create(RsvpType::class); - // dump($form->createView()); - return 'hello'; - //return $this->container->get('view')->render('RsvpForm.html.twig', ['form' => $form->createView()]); + return $app->render('logout.html.twig'); } } diff --git a/src/Sikofitt/App/Controller/RsvpController.php b/src/Sikofitt/App/Controller/RsvpController.php index 5c4b5e6..9f196f9 100644 --- a/src/Sikofitt/App/Controller/RsvpController.php +++ b/src/Sikofitt/App/Controller/RsvpController.php @@ -22,7 +22,7 @@ namespace Sikofitt\App\Controller; use Doctrine\ORM\EntityManager; use Sikofitt\{ - App\Entity\Rsvp, App\Entity\User, App\Form\ResetType, App\Form\RsvpType, App\Repository\RsvpRepository, App\Repository\UserRepository + App\Entity\Rsvp, App\Entity\User, App\Form\ResetPasswordType, App\Form\ResetType, App\Form\RsvpType, App\Repository\RsvpRepository, App\Repository\UserRepository }; use Symfony\Component\Form\FormFactory; @@ -144,6 +144,7 @@ class RsvpController ]); }*/ $app->addInfo('Message', 'message 2'); + return $app->render( 'reset_password.html.twig', [ @@ -156,5 +157,25 @@ class RsvpController public function tokenAction(Request $request, \Kernel $app, string $token = null) { + $user = $app['orm.em']->getRepository('Sikofitt\App\Entity\User')->getUserByToken($token); + + if (null === $user) { + return $app->render('reset_password_token.html.twig', ['token' => ['valid' => false, 'value' => $token]]); + } else { + $passwordForm = $app->getFormFactory()->create(ResetPasswordType::class); + if ($request->isMethod('POST')) { + $passwordForm->handleRequest($request); + if ($passwordForm->isValid() && $passwordForm->isSubmitted()) { + $hash = $app->encodePassword($user, $passwordForm->get('password')->getData()); + $user->setPassword($hash); + $app['orm.em']->getRepository('Sikofitt\App\Entity\User')->updatePassword($user); + $app->addSuccess('Successfully changed your password!'); + + return $app->redirect($app->url('rsvp')); + } + } + + return $app->render('reset_password_token.html.twig', ['token' => ['valid' => true, 'value' => $token], 'form' => $passwordForm->createView(), 'user' => $user]); + } } } diff --git a/src/Sikofitt/App/Entity/User.php b/src/Sikofitt/App/Entity/User.php index b6f58ed..2de796b 100644 --- a/src/Sikofitt/App/Entity/User.php +++ b/src/Sikofitt/App/Entity/User.php @@ -21,7 +21,7 @@ namespace Sikofitt\App\Entity; use Doctrine\ORM\Mapping as ORM; -use Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder; +use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Validator\Constraints as Assert; /** @@ -30,7 +30,7 @@ use Symfony\Component\Validator\Constraints as Assert; * @ORM\Entity(repositoryClass="Sikofitt\App\Repository\UserRepository") * @ORM\Table(name="users") */ -class User +class User implements UserInterface { const KATRINA_SIDE = 'Katrina'; @@ -92,9 +92,15 @@ class User /** * @var string - * @ORM\Column(name="token", type="string", length=255, nullable=true) + * @ORM\Column(name="reset_token", type="string", length=255, nullable=true) */ - private $token; + private $resetToken; + + /** + * @var string + * @ORM\Column(name="user_token", type="string", length=255, nullable=true) + */ + private $userToken; /** * @var int @@ -177,6 +183,16 @@ class User return $this->lastName; } + /** + * @return string + */ + public function getUsername() + { + $email = explode('@', $this->email); + + return $email[0]; + } + /** * Set family. * @@ -256,10 +272,7 @@ class User */ public function setPassword($password) { - $encoder = new BCryptPasswordEncoder(14); - - $salt = bin2hex(random_bytes(16)); - $this->password = $encoder->encodePassword($password, $salt); + $this->password = $password; return $this; } @@ -275,13 +288,9 @@ class User /** * @return string */ - public function getPlainPassword(): string + public function getPlainPassword() { - if (null === $this->plainPassword) { - return ''; - } else { - return $this->plainPassword; - } + return $this->plainPassword; } /** @@ -297,13 +306,13 @@ class User } /** - * @param string $token + * @param string $userToken * * @return $this */ - public function setToken($token) + public function setUserToken($userToken) { - $this->token = $token; + $this->userToken = $userToken; return $this; } @@ -311,9 +320,29 @@ class User /** * @return string */ - public function getToken() + public function getUserToken() { - return $this->token; + return $this->userToken; + } + + /** + * @param string $resetToken + * + * @return $this + */ + public function setResetToken($resetToken) + { + $this->resetToken = $resetToken; + + return $this; + } + + /** + * @return string + */ + public function getResetToken() + { + return $this->resetToken; } /** @@ -387,4 +416,18 @@ class User { return $this->rsvp; } + + public function getRoles() + { + return ['ROLE_USER']; + } + + public function eraseCredentials() + { + } + + public function getSalt() + { + return null; + } } diff --git a/src/Sikofitt/App/Form/ResetPasswordType.php b/src/Sikofitt/App/Form/ResetPasswordType.php new file mode 100644 index 0000000..66d24e0 --- /dev/null +++ b/src/Sikofitt/App/Form/ResetPasswordType.php @@ -0,0 +1,83 @@ +. + */ + +namespace Sikofitt\App\Form; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\PasswordType; +use Symfony\Component\Form\Extension\Core\Type\RepeatedType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Validator\Constraints\Length; +use Symfony\Component\Validator\Constraints\NotBlank; +use Symfony\Component\Validator\Constraints\NotNull; + +/** + * Class ResetPasswordType. + */ +class ResetPasswordType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('password', RepeatedType::class, [ + 'type' => PasswordType::class, + 'invalid_message' => 'Passwords do not match.', + 'invalid_message_parameters' => [ + 'class' => 'uk-text-danger', + ], + 'required' => true, + 'options' => [ + 'always_empty' => false, + 'attr' => [ + 'class' => 'uk-input uk-form-large uk-padding-small uk-box-shadow-hover-small', + ], + 'label_attr' => [ + 'class' => 'uk-form-label', + ], + 'constraints' => [ + new NotBlank(), + new NotNull(), + ], + ], + 'first_options' => [ + 'label' => 'New password', + 'constraints' => [ + new Length(['min' => 8, 'minMessage' => 'Password must be at least 8 characters.']), + ], + ], + 'second_options' => [ + 'label' => 'Repeat your password', + ], + ]) + ->add('submit', SubmitType::class, [ + 'attr' => [ + 'class' => 'uk-button uk-button-primary uk-button-large', + ], + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefault('attr', ['class' => 'uk-form uk-form-horizontal']); + } +} diff --git a/src/Sikofitt/App/Form/UserLoginType.php b/src/Sikofitt/App/Form/UserLoginType.php new file mode 100644 index 0000000..fbf85af --- /dev/null +++ b/src/Sikofitt/App/Form/UserLoginType.php @@ -0,0 +1,79 @@ +. + */ + +namespace Sikofitt\App\Form; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\PasswordType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Validator\Constraints\Email; +use Symfony\Component\Validator\Constraints\NotBlank; + +class UserLoginType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('email_username', TextType::class, [ + 'attr' => [ + 'class' => 'uk-input uk-form-large uk-padding-small uk-box-shadow-hover-small', + 'placeholder' => 'Email address', + ], + 'label' => 'Email address', + 'label_attr' => [ + 'class' => 'uk-form-label uk-text-primary', + ], + 'constraints' => [ + new NotBlank(), + new Email([ + 'strict' => true, + 'checkMX' => true, + 'checkHost' => true, + 'message' => 'Invalid email address.', + ]), + ], + ])->add('password', PasswordType::class, [ + 'attr' => [ + 'class' => 'uk-input uk-form-large uk-padding-small uk-box-shadow-hover-small', + 'placeholder' => 'Password', + ], + 'label' => 'Password', + 'label_attr' => [ + 'class' => 'uk-form-label uk-text-primary', + ], + 'constraints' => [ + new NotBlank(), + ], + ])->add('submit', SubmitType::class, [ + 'attr' => [ + 'class' => 'uk-button uk-button-primary', + ], + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefault('attr', ['class' => 'uk-form uk-form-horizontal']); + } +} diff --git a/src/Sikofitt/App/Middleware/AuthenticatorMiddleware.php b/src/Sikofitt/App/Middleware/AuthenticatorMiddleware.php new file mode 100644 index 0000000..bcd45dd --- /dev/null +++ b/src/Sikofitt/App/Middleware/AuthenticatorMiddleware.php @@ -0,0 +1,33 @@ +. + */ + +namespace Sikofitt\App\Middleware; + +use Symfony\Component\HttpFoundation\Request; + +class AuthenticatorMiddleware +{ + public function __invoke(Request $request, \Kernel $app) + { + if ($app->session()->has('user')) { + return true; + } + } +} diff --git a/src/Sikofitt/App/Provider/DoctrineConsoleProvider.php b/src/Sikofitt/App/Provider/DoctrineConsoleProvider.php new file mode 100644 index 0000000..43a55db --- /dev/null +++ b/src/Sikofitt/App/Provider/DoctrineConsoleProvider.php @@ -0,0 +1,106 @@ +. + */ + +namespace Sikofitt\App\Provider; + + +use Doctrine\DBAL\Tools\Console\Command\{ + ImportCommand, + ReservedWordsCommand, + RunSqlCommand +}; +use Doctrine\ORM\Tools\Console\Command\{ + ClearCache\EntityRegionCommand, ClearCache\MetadataCommand, + ClearCache\QueryCommand, ClearCache\ResultCommand, + ConvertDoctrine1SchemaCommand, ConvertMappingCommand, + EnsureProductionSettingsCommand, GenerateEntitiesCommand, + GenerateProxiesCommand, GenerateRepositoriesCommand, InfoCommand, + MappingDescribeCommand, RunDqlCommand, SchemaTool\CreateCommand, + SchemaTool\DropCommand, SchemaTool\UpdateCommand, ValidateSchemaCommand +}; +use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper; +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Symfony\Component\Console\Helper\HelperSet; + +/** + * Class DoctrineConsoleProvider. + */ +class DoctrineConsoleProvider implements ServiceProviderInterface +{ + /** + * Registers services on the given container. + * + * This method should only be used to configure services and parameters. + * It should not get services. + * + * @param Container $pimple A container instance + */ + public function register(Container $pimple) + { + if (false === isset($pimple['console'])) { + throw new \LogicException('You must enable the Knp\Provider\ConsoleServiceProvider service provider to be able to use the DoctrineConsoleProvider.'); + } + if (false === isset($pimple['db.options'])) { + throw new \LogicException('You must enable the DoctrineServiceProvider to use the DoctrineConsoleProvider.'); + } + if(false === isset($pimple['orm.em'])) { + throw new \LogicException('You must enable the Dflydev\Provider\DoctrineOrm\DoctrineOrmServiceProvider to use the DoctrineConsoleProvider.'); + } + + $console = $pimple['console']; + $console->setHelperSet(new HelperSet(array( + 'em' => new EntityManagerHelper($pimple['orm.em']) + ))); + + $updateCommand = new UpdateCommand(); + $updateCommand->setName('orm:schema:update'); + $schemaValidateCommand = (new ValidateSchemaCommand()) + ->setName('orm:schema:validate') + ->setAliases(['orm:validate']); + $schemaDropCommand = (new DropCommand()) + ->setName('orm:schema:drop'); + $schemaCreateCommand = (new CreateCommand()) + ->setName('orm:schema:create'); + + $console->addCommands([ + new ConvertDoctrine1SchemaCommand(), + new ConvertMappingCommand(), + new EnsureProductionSettingsCommand(), + new EntityRegionCommand(), + new GenerateEntitiesCommand(), + new GenerateProxiesCommand(), + new GenerateRepositoriesCommand(), + new ImportCommand(), + new InfoCommand(), + new MappingDescribeCommand(), + new MetadataCommand(), + new QueryCommand(), + new RunDqlCommand(), + new RunSqlCommand(), + new ReservedWordsCommand(), + new ResultCommand(), + $schemaCreateCommand, + $schemaDropCommand, + $schemaValidateCommand, + ]); + } + +} diff --git a/src/Sikofitt/App/Repository/UserRepository.php b/src/Sikofitt/App/Repository/UserRepository.php index 7304c13..66375bd 100644 --- a/src/Sikofitt/App/Repository/UserRepository.php +++ b/src/Sikofitt/App/Repository/UserRepository.php @@ -23,6 +23,7 @@ namespace Sikofitt\App\Repository; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query; use Sikofitt\App\Entity\User; +use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Validator\Constraints\Email; use Symfony\Component\Validator\Validation; @@ -33,6 +34,17 @@ use Symfony\Component\Validator\Validation; */ class UserRepository extends EntityRepository { + public function findByEmail(string $email) + { + return $this->findBy(['email' => $email]); + /*return $this->createQueryBuilder('u') + ->select('u') + ->where('u.email = :email') + ->setParameter('email', $email) + ->getQuery() + ->getOneOrNullResult(Query::HYDRATE_OBJECT);*/ + } + public function getKatrinaCount() { return $this->createQueryBuilder('u') @@ -78,9 +90,21 @@ class UserRepository extends EntityRepository ->getOneOrNullResult(Query::HYDRATE_SINGLE_SCALAR); } + public function getUserByToken(string $token) + { + return $this->createQueryBuilder('u') + ->select(['u']) + ->where('u.token = :token') + ->setParameter('token', $token) + ->getQuery() + ->getOneOrNullResult(); + } + public function setResetToken(string $email) { - $token = bin2hex(random_bytes(22)); + $bytes = \ParagonIE_Sodium_Compat::randombytes_buf(22); + $blake = \ParagonIE_Sodium_Compat::crypto_generichash($bytes); + $token = \ParagonIE_Sodium_Core_BLAKE2b::bin2hex($blake); return (bool) $this->createQueryBuilder('u') ->update() @@ -91,4 +115,11 @@ class UserRepository extends EntityRepository ->getQuery() ->getOneOrNullResult(Query::HYDRATE_SINGLE_SCALAR); } + + public function updatePassword(UserInterface $user) + { + $user->setToken(null); + $this->getEntityManager()->persist($user); + $this->getEntityManager()->flush(); + } } diff --git a/src/Sikofitt/App/Traits/EntityManagerTrait.php b/src/Sikofitt/App/Traits/EntityManagerTrait.php new file mode 100644 index 0000000..8148ee5 --- /dev/null +++ b/src/Sikofitt/App/Traits/EntityManagerTrait.php @@ -0,0 +1,38 @@ +. + */ + +namespace Sikofitt\App\Traits; + +use Doctrine\ORM\EntityManager; + +trait EntityManagerTrait +{ + /** + * @return null|EntityManager + */ + public function getEntityManager() + { + if (false === isset($this['orm.em']) || false === isset($this['db.options'])) { + return null; + } + + return $this['orm.em']; + } +} diff --git a/src/Sikofitt/App/Traits/FlashTrait.php b/src/Sikofitt/App/Traits/FlashTrait.php index 5145e48..532fa68 100644 --- a/src/Sikofitt/App/Traits/FlashTrait.php +++ b/src/Sikofitt/App/Traits/FlashTrait.php @@ -20,6 +20,9 @@ namespace Sikofitt\App\Traits; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; +use Symfony\Component\HttpFoundation\Session\Session; + /** * Trait FlashTrait. * @@ -30,12 +33,12 @@ trait FlashTrait /** * @param \string[] ...$messages * - * @return $this + * @return $this|null */ public function addInfo(string ...$messages) { if (false === isset($this['session'])) { - return; + return null; } foreach ($messages as $message) { @@ -53,7 +56,7 @@ trait FlashTrait public function addError(string ...$messages) { if (false === isset($this['session'])) { - return; + return null; } foreach ($messages as $message) { @@ -71,7 +74,7 @@ trait FlashTrait public function addSuccess(string ...$messages) { if (false === isset($this['session'])) { - return; + return null; } foreach ($messages as $message) { @@ -84,7 +87,7 @@ trait FlashTrait public function addWarning(string ...$messages) { if (false === isset($this['session'])) { - return; + return null; } foreach ($messages as $message) { @@ -93,4 +96,95 @@ trait FlashTrait return $this; } + + /** + * @return array|null + */ + public function peekAll() + { + if (false === isset($this['session'])) { + return null; + } + + return $this['session']->getFlashBag()->peekAll(); + } + + /** + * @param string $name + * @param array $default + * + * @return array|null + */ + public function peekFlash(string $name, array $default = []) + { + if (false === isset($this['session'])) { + return null; + } + + if (true === $this['session']->getFlashBag()->has($name)) { + return $this['session']->getFlashBag()->peek($name, $default); + } + + return []; + } + + public function clearFlashes() + { + $this['session']->getFlashBag()->clear(); + } + + /** + * @param string $name + * @param array $default + * + * @return array + */ + public function getFlash(string $name, array $default = []) + { + if (false === isset($this['session'])) { + return null; + } + + if (true === $this['session']->getFlashBag()->has($name)) { + return $this['session']->getFlashBag()->get($name, $default); + } + + return []; + } + + /** + * @return FlashBag + */ + public function getFlashBag() + { + if (false === isset($this['session'])) { + return null; + } + + return $this['session']->getFlashBag(); + } + + /** + * @return Session + */ + public function session() + { + if (false === isset($this['session'])) { + if (class_exists(Session::class)) { + return new Session(); + } else { + return null; + } + } + + return $this['session']; + } + + /** + * @return Session + */ + public function getSession() + { + return $this->session(); + } } diff --git a/src/Sikofitt/Security/MySqlUserProvider.php b/src/Sikofitt/Security/MySqlUserProvider.php new file mode 100644 index 0000000..7a6de24 --- /dev/null +++ b/src/Sikofitt/Security/MySqlUserProvider.php @@ -0,0 +1,100 @@ +. + */ + +namespace Sikofitt\Security; + +use Doctrine\ORM\EntityManager; +use Sikofitt\App\Entity\User; +use Symfony\Component\Security\Core\Exception\UnsupportedUserException; +use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; + +class MySqlUserProvider implements UserProviderInterface +{ + private $entityManager; + + public function __construct(EntityManager $entityManager) + { + $this->entityManager = $entityManager; + } + + /** + * Loads the user for the given username. + * + * This method must throw UsernameNotFoundException if the user is not + * found. + * + * @param string $username The username + * + * @throws UsernameNotFoundException if the user is not found + * + * @return UserInterface + */ + public function loadUserByUsername($username) + { + if (null === $username) { + $username = ''; + } + $userArray = $this->entityManager->getRepository(User::class)->findByEmail($username); + $user = new User(); + $user->setCreated($userArray['created']) + ->setUpdated($userArray['updated']) + ->setFamily($userArray['family']) + ->setFamilySide($userArray['familySide']) + ->setFirstName($userArray['firstname']) + ->setLastName($userArray['lastname']) + ->setRsvp($userArray['rsvp']) + ->setPassword($userArray['password']) + ->setEmail($userArray['email']); + + return $user; + } + + /** + * Refreshes the user for the account interface. + * + * It is up to the implementation to decide if the user data should be + * totally reloaded (e.g. from the database), or if the UserInterface + * object can just be merged into some internal array of users / identity + * map. + * + * @param UserInterface $user + * + * @throws UnsupportedUserException if the account is not supported + * + * @return UserInterface + */ + public function refreshUser(UserInterface $user) + { + } + + /** + * Whether this provider supports the given user class. + * + * @param string $class + * + * @return bool + */ + public function supportsClass($class) + { + return get_class($class) === self::class; + } +} diff --git a/src/Sikofitt/Security/MysqlAuthenticator.php b/src/Sikofitt/Security/MysqlAuthenticator.php index f4cf3c7..8d47ba7 100644 --- a/src/Sikofitt/Security/MysqlAuthenticator.php +++ b/src/Sikofitt/Security/MysqlAuthenticator.php @@ -20,9 +20,12 @@ namespace Sikofitt\Security; +use Doctrine\ORM\EntityManager; +use Sikofitt\App\Entity\User; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; @@ -30,6 +33,15 @@ use Symfony\Component\Security\Guard\AbstractGuardAuthenticator; class MysqlAuthenticator extends AbstractGuardAuthenticator { + private $encoderFactory; + private $entityManager; + + public function __construct(EncoderFactoryInterface $encoderFactory, EntityManager $entityManager) + { + $this->encoderFactory = $encoderFactory; + $this->entityManager = $entityManager; + } + /** * Returns a response that directs the user to authenticate. * @@ -88,7 +100,17 @@ class MysqlAuthenticator extends AbstractGuardAuthenticator */ public function getCredentials(Request $request) { - // TODO: Implement getCredentials() method. + $password = $request->request->get('password'); + $email = $request->request->get('email'); + + if (isset($user[0])) { + $user = $user[0]; + } + + return [ + 'email' => $email, + 'password' => $password, + ]; } /** @@ -108,7 +130,11 @@ class MysqlAuthenticator extends AbstractGuardAuthenticator */ public function getUser($credentials, UserProviderInterface $userProvider) { - // TODO: Implement getUser() method. + if (null === $userProvider->loadUserByUsername($credentials['email'])) { + return new User(); + } else { + return $userProvider->loadUserByUsername($credentials['email']); + } } /** @@ -129,7 +155,14 @@ class MysqlAuthenticator extends AbstractGuardAuthenticator */ public function checkCredentials($credentials, UserInterface $user) { - // TODO: Implement checkCredentials() method. + $encoder = $this->encoderFactory->getEncoder($user); + + return $encoder + ->isPasswordValid( + $user->getPassword(), + $credentials['password'], + null + ); } /** @@ -151,7 +184,7 @@ class MysqlAuthenticator extends AbstractGuardAuthenticator Request $request, AuthenticationException $exception ) { - // TODO: Implement onAuthenticationFailure() method. + return new Response($exception->getMessage(), 403); } /** @@ -174,7 +207,7 @@ class MysqlAuthenticator extends AbstractGuardAuthenticator TokenInterface $token, $providerKey ) { - // TODO: Implement onAuthenticationSuccess() method. + $request->getSession()->set('user', $token.':'.$providerKey); } /** @@ -193,6 +226,6 @@ class MysqlAuthenticator extends AbstractGuardAuthenticator */ public function supportsRememberMe() { - // TODO: Implement supportsRememberMe() method. + return false; } }