From 6d282daa304c7357e2b2e2fef2e446d19be468e1 Mon Sep 17 00:00:00 2001 From: Igor V Belousov Date: Sat, 22 Jun 2019 16:03:39 +0300 Subject: [PATCH] symfony complete --- config/packages/security.yaml | 17 ++- config/packages/twig.yaml | 1 + src/Controller/IndexController.php | 17 +++ src/Controller/RegistrationController.php | 55 +++++++++ src/Controller/SecurityController.php | 33 ++++++ src/Controller/UserController.php | 101 ++++++++++++++++ src/Entity/User.php | 136 ++++++++++++++++++++++ src/Form/UserType.php | 28 +++++ src/Migrations/Version20190622050819.php | 35 ++++++ src/Repository/UserRepository.php | 50 ++++++++ src/Security/LoginFormAuthenticator.php | 103 ++++++++++++++++ templates/base.html.twig | 28 ++++- templates/index.html.twig | 17 +++ templates/registration/register.html.twig | 21 ++++ templates/security/login.html.twig | 40 +++++++ templates/user/_delete_form.html.twig | 5 + templates/user/_form.html.twig | 4 + templates/user/edit.html.twig | 17 +++ templates/user/index.html.twig | 48 ++++++++ templates/user/new.html.twig | 14 +++ templates/user/show.html.twig | 42 +++++++ 21 files changed, 808 insertions(+), 4 deletions(-) create mode 100644 src/Controller/IndexController.php create mode 100644 src/Controller/RegistrationController.php create mode 100644 src/Controller/SecurityController.php create mode 100644 src/Controller/UserController.php create mode 100644 src/Entity/User.php create mode 100644 src/Form/UserType.php create mode 100644 src/Migrations/Version20190622050819.php create mode 100644 src/Repository/UserRepository.php create mode 100644 src/Security/LoginFormAuthenticator.php create mode 100644 templates/index.html.twig create mode 100644 templates/registration/register.html.twig create mode 100644 templates/security/login.html.twig create mode 100644 templates/user/_delete_form.html.twig create mode 100644 templates/user/_form.html.twig create mode 100644 templates/user/edit.html.twig create mode 100644 templates/user/index.html.twig create mode 100644 templates/user/new.html.twig create mode 100644 templates/user/show.html.twig diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 4c496fb..7ff2843 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -1,14 +1,27 @@ security: + encoders: + App\Entity\User: + algorithm: argon2i + # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers providers: - in_memory: { memory: ~ } + # used to reload user from session & other features (e.g. switch_user) + app_user_provider: + entity: + class: App\Entity\User + property: username firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: anonymous: true + guard: + authenticators: + - App\Security\LoginFormAuthenticator + logout: + path: app_logout # activate different ways to authenticate # https://symfony.com/doc/current/security.html#firewalls-authentication @@ -18,5 +31,5 @@ security: # Easy way to control access for large sections of your site # Note: Only the *first* access control that matches will be used access_control: - # - { path: ^/admin, roles: ROLE_ADMIN } + - { path: ^/admin/*, roles: ROLE_ADMIN } # - { path: ^/profile, roles: ROLE_USER } diff --git a/config/packages/twig.yaml b/config/packages/twig.yaml index d1582a2..16fdd4b 100644 --- a/config/packages/twig.yaml +++ b/config/packages/twig.yaml @@ -2,3 +2,4 @@ twig: default_path: '%kernel.project_dir%/templates' debug: '%kernel.debug%' strict_variables: '%kernel.debug%' + form_themes: ['bootstrap_4_layout.html.twig'] diff --git a/src/Controller/IndexController.php b/src/Controller/IndexController.php new file mode 100644 index 0000000..b8b677d --- /dev/null +++ b/src/Controller/IndexController.php @@ -0,0 +1,17 @@ +render('index.html.twig'); + } +} diff --git a/src/Controller/RegistrationController.php b/src/Controller/RegistrationController.php new file mode 100644 index 0000000..9e36e49 --- /dev/null +++ b/src/Controller/RegistrationController.php @@ -0,0 +1,55 @@ +createForm(RegistrationFormType::class, $user); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + // encode the plain password + $user->setPassword( + $passwordEncoder->encodePassword( + $user, + $form->get('plainPassword')->getData() + ) + ); + + $user->setRoles(['ROLE_USER'])->setActive(false); + + $entityManager = $this->getDoctrine()->getManager(); + $entityManager->persist($user); + $entityManager->flush(); + + // do anything else you need here, like send an email + + return $guardHandler->authenticateUserAndHandleSuccess( + $user, + $request, + $authenticator, + 'main' // firewall name in security.yaml + ); + } + + return $this->render('registration/register.html.twig', [ + 'registrationForm' => $form->createView(), + ]); + } +} diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php new file mode 100644 index 0000000..7f28f35 --- /dev/null +++ b/src/Controller/SecurityController.php @@ -0,0 +1,33 @@ +getLastAuthenticationError(); + // last username entered by the user + $lastUsername = $authenticationUtils->getLastUsername(); + + return $this->render('security/login.html.twig', ['last_username' => $lastUsername, 'error' => $error]); + } + + /** + * @Route("/logout", name="app_logout", methods={"GET"}) + */ + public function logout() + { + // controller can be blank: it will never be executed! + throw new \Exception('Don\'t forget to activate logout in security.yaml'); + } +} diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php new file mode 100644 index 0000000..aeede18 --- /dev/null +++ b/src/Controller/UserController.php @@ -0,0 +1,101 @@ +render('user/index.html.twig', [ + 'users' => $userRepository->findAll(), + ]); + } + + /** + * @Route("/new", name="user_new", methods={"GET","POST"}) + */ + public function new(Request $request, UserPasswordEncoderInterface $passwordEncoder): Response + { + $user = new User(); + $form = $this->createForm(UserType::class, $user); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $entityManager = $this->getDoctrine()->getManager(); + $user->setPassword( + $passwordEncoder->encodePassword($user, $user->getPassword()) + ); + + $entityManager->persist($user); + $entityManager->flush(); + + return $this->redirectToRoute('user_index'); + } + + return $this->render('user/new.html.twig', [ + 'user' => $user, + 'form' => $form->createView(), + ]); + } + + /** + * @Route("/{id}", name="user_show", methods={"GET"}) + */ + public function show(User $user): Response + { + return $this->render('user/show.html.twig', [ + 'user' => $user, + ]); + } + + /** + * @Route("/{id}/edit", name="user_edit", methods={"GET","POST"}) + */ + public function edit(Request $request, User $user): Response + { + $form = $this->createForm(UserType::class, $user); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->getDoctrine()->getManager()->flush(); + + return $this->redirectToRoute('user_index', [ + 'id' => $user->getId(), + ]); + } + + return $this->render('user/edit.html.twig', [ + 'user' => $user, + 'form' => $form->createView(), + ]); + } + + /** + * @Route("/{id}", name="user_delete", methods={"DELETE"}) + */ + public function delete(Request $request, User $user): Response + { + if ($this->isCsrfTokenValid('delete'.$user->getId(), $request->request->get('_token'))) { + $entityManager = $this->getDoctrine()->getManager(); + $entityManager->remove($user); + $entityManager->flush(); + } + + return $this->redirectToRoute('user_index'); + } +} diff --git a/src/Entity/User.php b/src/Entity/User.php new file mode 100644 index 0000000..eb413e5 --- /dev/null +++ b/src/Entity/User.php @@ -0,0 +1,136 @@ +id; + } + + /** + * A visual identifier that represents this user. + * + * @see UserInterface + */ + public function getUsername(): string + { + return (string) $this->username; + } + + public function setUsername(string $username): self + { + $this->username = $username; + + return $this; + } + + /** + * @see UserInterface + */ + public function getRoles(): array + { + $roles = $this->roles; + // guarantee every user at least has ROLE_USER + $roles[] = 'ROLE_USER'; + + return array_unique($roles); + } + + public function setRoles(array $roles): self + { + $this->roles = $roles; + + return $this; + } + + /** + * @see UserInterface + */ + public function getPassword(): string + { + return (string) $this->password; + } + + public function setPassword(string $password): self + { + $this->password = $password; + + return $this; + } + + /** + * @see UserInterface + */ + public function getSalt() + { + // not needed when using the "bcrypt" algorithm in security.yaml + } + + /** + * @see UserInterface + */ + public function eraseCredentials() + { + // If you store any temporary, sensitive data on the user, clear it here + // $this->plainPassword = null; + } + + /** + * @return bool + */ + public function isActive(): bool + { + return (bool) $this->active; + } + + /** + * @param bool $active + * + * @return $this + */ + public function setActive(bool $active) + { + $this->active = $active; + + return $this; + } +} diff --git a/src/Form/UserType.php b/src/Form/UserType.php new file mode 100644 index 0000000..785a2a3 --- /dev/null +++ b/src/Form/UserType.php @@ -0,0 +1,28 @@ +add('username') + ->add('roles') + ->add('password') + ->add('active') + ; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => User::class, + ]); + } +} diff --git a/src/Migrations/Version20190622050819.php b/src/Migrations/Version20190622050819.php new file mode 100644 index 0000000..64d63c0 --- /dev/null +++ b/src/Migrations/Version20190622050819.php @@ -0,0 +1,35 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); + + $this->addSql('CREATE TABLE user (id INT AUTO_INCREMENT NOT NULL, username VARCHAR(180) NOT NULL, roles LONGTEXT NOT NULL COMMENT \'(DC2Type:simple_array)\', password VARCHAR(255) NOT NULL, active TINYINT(1) DEFAULT \'0\' NOT NULL, UNIQUE INDEX UNIQ_8D93D649F85E0677 (username), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB'); + } + + public function down(Schema $schema) : void + { + // this down() migration is auto-generated, please modify it to your needs + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); + + $this->addSql('DROP TABLE user'); + } +} diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php new file mode 100644 index 0000000..8e82202 --- /dev/null +++ b/src/Repository/UserRepository.php @@ -0,0 +1,50 @@ +createQueryBuilder('u') + ->andWhere('u.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('u.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?User + { + return $this->createQueryBuilder('u') + ->andWhere('u.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ +} diff --git a/src/Security/LoginFormAuthenticator.php b/src/Security/LoginFormAuthenticator.php new file mode 100644 index 0000000..b7f7924 --- /dev/null +++ b/src/Security/LoginFormAuthenticator.php @@ -0,0 +1,103 @@ +entityManager = $entityManager; + $this->urlGenerator = $urlGenerator; + $this->csrfTokenManager = $csrfTokenManager; + $this->passwordEncoder = $passwordEncoder; + } + + public function supports(Request $request) + { + return 'app_login' === $request->attributes->get('_route') + && $request->isMethod('POST'); + } + + public function getCredentials(Request $request) + { + $credentials = [ + 'username' => $request->request->get('username'), + 'password' => $request->request->get('password'), + 'csrf_token' => $request->request->get('_csrf_token'), + ]; + $request->getSession()->set( + Security::LAST_USERNAME, + $credentials['username'] + ); + + return $credentials; + } + + public function getUser($credentials, UserProviderInterface $userProvider) + { + $token = new CsrfToken('authenticate', $credentials['csrf_token']); + if (!$this->csrfTokenManager->isTokenValid($token)) { + throw new InvalidCsrfTokenException(); + } + + $user = $this->entityManager->getRepository(User::class)->findOneBy(['username' => $credentials['username']]); + + if (!$user) { + // fail authentication with a custom error + throw new CustomUserMessageAuthenticationException('Username could not be found.'); + } + + return $user; + } + + public function checkCredentials($credentials, UserInterface $user) + { + $check_password = $this->passwordEncoder->isPasswordValid($user, $credentials['password']); + if ($check_password && 1 === $user->getId() && !$user->isActive()) {//Если пользователь c id = 1, тогда делаем его админом + $user->setRoles(['ROLE_ADMIN', 'ROLE_USER'])->setActive(true); + $em = $this->entityManager; + $em->persist($user); + $em->flush(); + } + + return ($user->isActive() && $check_password) ? true : false; + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) + { + if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) { + return new RedirectResponse($targetPath); + } + + return new RedirectResponse($this->urlGenerator->generate('index_page')); + } + + protected function getLoginUrl() + { + return $this->urlGenerator->generate('app_login'); + } +} diff --git a/templates/base.html.twig b/templates/base.html.twig index 043f42d..643e45e 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -1,12 +1,36 @@ - + - {% block title %}Welcome!{% endblock %} + {% block title %}Welcome!{% endblock %}—Тест + + {% block stylesheets %}{% endblock %} +
+
+

+ + {{ block('title') }} — Тест +

+
+
{% block body %}{% endblock %} + + + + {% block javascripts %}{% endblock %} diff --git a/templates/index.html.twig b/templates/index.html.twig new file mode 100644 index 0000000..60dd0f5 --- /dev/null +++ b/templates/index.html.twig @@ -0,0 +1,17 @@ +{% extends "base.html.twig" %} +{% block title %} + Главная +{% endblock %} +{% block body %} +
+ {% if app.user == null %} + Войти + {% else %} + Выйти + {% endif %} +
+ {% if is_granted('ROLE_ADMIN') %} + Список пользователей + {% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/templates/registration/register.html.twig b/templates/registration/register.html.twig new file mode 100644 index 0000000..622189d --- /dev/null +++ b/templates/registration/register.html.twig @@ -0,0 +1,21 @@ +{% extends 'base.html.twig' %} + +{% block title %}Регистрация{% endblock %} + + +{% block body %} +
+ ← Вход +
+
+ {{ form_start(registrationForm) }} + + {{ form_row(registrationForm.username, {'label': 'Имя пользователя'}) }} + {{ form_row(registrationForm.plainPassword, {'label': 'Пароль'}) }} + + + {{ form_end(registrationForm) }} +
+
+
+{% endblock %} diff --git a/templates/security/login.html.twig b/templates/security/login.html.twig new file mode 100644 index 0000000..89758ff --- /dev/null +++ b/templates/security/login.html.twig @@ -0,0 +1,40 @@ +{% extends 'base.html.twig' %} + +{% block title %}Вход{% endblock %} + +{% block body %} +
+ ← Главная +
+
+
+ {% if error %} +
{{ error.messageKey|trans(error.messageData, 'security') }}
+ {% endif %} + +

Войдите

+
+ + +
+
+ + +
+ + + + + Регистрация +
+
+
+ +
+ +{% endblock %} diff --git a/templates/user/_delete_form.html.twig b/templates/user/_delete_form.html.twig new file mode 100644 index 0000000..133a5de --- /dev/null +++ b/templates/user/_delete_form.html.twig @@ -0,0 +1,5 @@ +
+ + + +
diff --git a/templates/user/_form.html.twig b/templates/user/_form.html.twig new file mode 100644 index 0000000..96b74f1 --- /dev/null +++ b/templates/user/_form.html.twig @@ -0,0 +1,4 @@ +{{ form_start(form) }} + {{ form_widget(form) }} + +{{ form_end(form) }} diff --git a/templates/user/edit.html.twig b/templates/user/edit.html.twig new file mode 100644 index 0000000..2239968 --- /dev/null +++ b/templates/user/edit.html.twig @@ -0,0 +1,17 @@ +{% extends 'base.html.twig' %} + +{% block title %}Edit User{% endblock %} + +{% block body %} +
+ ← к списку +
+
+ {{ include('user/_form.html.twig', {'button_label': 'Обновить'}) }} + + {{ include('user/_delete_form.html.twig') }} +
+
+
+ +{% endblock %} diff --git a/templates/user/index.html.twig b/templates/user/index.html.twig new file mode 100644 index 0000000..8299f85 --- /dev/null +++ b/templates/user/index.html.twig @@ -0,0 +1,48 @@ +{% extends 'base.html.twig' %} + +{% block title %}Список пользователей{% endblock %} + +{% block body %} +
+ + ← Главная + Добавить пользователя +
+ + + + + + + + + + + + + {% for user in users %} + + + + + + + + + {% else %} + + + + {% endfor %} + +
IdUsernameРолиХеш пароляАктивныйДействия
{{ user.id }}{{ user.username }} + {% for role in user.roles %} + {{ role }},
+ {% endfor %} +
{{ user.password }}
{{ user.active ? 'Да' : 'Нет' }} + Просмотр + Правка +
нет записей
+
+
+{% endblock %} diff --git a/templates/user/new.html.twig b/templates/user/new.html.twig new file mode 100644 index 0000000..8796c72 --- /dev/null +++ b/templates/user/new.html.twig @@ -0,0 +1,14 @@ +{% extends 'base.html.twig' %} + +{% block title %}New User{% endblock %} + +{% block body %} +
+ ← к списку +
+
+ {{ include('user/_form.html.twig') }} +
+
+
+{% endblock %} diff --git a/templates/user/show.html.twig b/templates/user/show.html.twig new file mode 100644 index 0000000..833b826 --- /dev/null +++ b/templates/user/show.html.twig @@ -0,0 +1,42 @@ +{% extends 'base.html.twig' %} + +{% block title %}Просмотр пользователя{% endblock %} + +{% block body %} +
+ ← к списку + + + + + + + + + + + + + + + + + + + + + + + +
Id{{ user.id }}
Username{{ user.username }}
Roles{% for role in user.roles %} + {{ role }},
+ {% endfor %}
Password{{ user.password }}
Active{{ user.active ? 'Yes' : 'No' }}
+
+
+ Правка + {{ include('user/_delete_form.html.twig') }} +
+
+ +
+{% endblock %}