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 @@ - +
-Id | +Username | +Роли | +Хеш пароля | +Активный | +Действия | +
---|---|---|---|---|---|
{{ user.id }} | +{{ user.username }} | +
+ {% for role in user.roles %}
+ {{ role }}, + {% endfor %} + |
+ {{ user.password }} |
+ {{ user.active ? 'Да' : 'Нет' }} | ++ Просмотр + Правка + | +
нет записей | +
Id | +{{ user.id }} | +
---|---|
Username | +{{ user.username }} | +
Roles | +{% for role in user.roles %}
+ {{ role }}, + {% endfor %} |
+
Password | +{{ user.password }} | +
Active | +{{ user.active ? 'Yes' : 'No' }} | +