Skip to content

Commit

Permalink
added password reset functionality
Browse files Browse the repository at this point in the history
update AdminUser entity
  • Loading branch information
twin-elements committed Mar 22, 2022
1 parent 585bf92 commit d7fe608
Show file tree
Hide file tree
Showing 33 changed files with 822 additions and 265 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ imports:
- { resource: '@TwinElementsAdminBundle/Resources/config/security.yaml' }
```

in `/config/packages/twin_elements_admin.yaml` add
```
imports:
- { resource: "@TwinElementsAdminBundle/config/config.yaml" }
twin_elements_admin: ~
```

#### How create a new Roles?

1.Create class implements `TwinElements\AdminBundle\Role\RoleGroupInterface`
Expand Down
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
"require": {
"php": "^7.1.3",
"symfony/framework-bundle": "^4.4 || ^5.4",
"twin-elements/form-extensions": "^v1.4",
"twin-elements/flashes": "^1.0"
"twin-elements/form-extensions": "^v1.5",
"twin-elements/flashes": "^1.0",
"symfonycasts/reset-password-bundle": "^1.13"
},
"autoload": {
"psr-4": {
Expand Down
106 changes: 84 additions & 22 deletions src/Controller/AdminUserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,73 @@

namespace TwinElements\AdminBundle\Controller;

use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use TwinElements\AdminBundle\Entity\AdminUser;
use TwinElements\AdminBundle\Form\AdminUserPasswordType;
use TwinElements\AdminBundle\Form\AdminUserType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use TwinElements\AdminBundle\Model\CrudControllerTrait;
use TwinElements\AdminBundle\Repository\AdminUserRepository;
use TwinElements\AdminBundle\Role\AdminUserRole;
use TwinElements\AdminBundle\Service\AdminTranslator;
use TwinElements\Component\Flashes\Flashes;

/**
* @Route("user")
*/
class AdminUserController extends AbstractController
{

use CrudControllerTrait;

/**
* @Route("/", name="user_index", methods={"GET"})
*/
public function indexAction(AdminTranslator $translator)
public function indexAction(
AdminTranslator $translator,
AdminUserRepository $userRepository
)
{
$em = $this->getDoctrine()->getManager();

$adminUsers = $em->getRepository(AdminUser::class)->findAll();

$this->breadcrumbs->setItems([
$translator->translate('admin.user.users') => null
]);

return $this->render('@TwinElementsAdmin/adminuser/index.html.twig', array(
'adminUsers' => $adminUsers,
'adminUsers' => $userRepository->findAll(),
));
}

/**
* @Route("/new", name="user_new", methods={"GET", "POST"})
*/
public function newAction(Request $request, UserPasswordEncoderInterface $passwordEncoder, AdminTranslator $translator)
public function newAction(
Request $request,
UserPasswordHasherInterface $userPasswordHasher,
AdminTranslator $translator,
ManagerRegistry $managerRegistry
)
{
$this->denyAccessUnlessGranted(AdminUserRole::ROLE_ADMIN);

$adminUser = new AdminUser();
$form = $this->createForm(AdminUserType::class, $adminUser);
$form = $this->createForm(AdminUserType::class, $adminUser, [
'enable_password_input' => true
]);
$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {
$em = $managerRegistry->getManager();

$password = $passwordEncoder->encodePassword($adminUser, $adminUser->getPassword());
$adminUser->setPassword($password);
$adminUser->setPassword(
$userPasswordHasher->hashPassword(
$adminUser,
$form->get('plainPassword')->getData()
)
);

$em = $this->getDoctrine()->getManager();
$em->persist($adminUser);
$em->flush();
$this->flashes->successMessage($translator->translate('admin.user.user_has_been_added'));
Expand All @@ -78,25 +92,20 @@ public function newAction(Request $request, UserPasswordEncoderInterface $passwo
* @Route("/{id}/edit", name="user_edit", methods={"GET", "POST"})
*
*/
public function editAction(Request $request, AdminUser $adminUser, UserPasswordEncoderInterface $passwordEncoder)
public function editAction(
Request $request,
AdminUser $adminUser,
ManagerRegistry $managerRegistry)
{
$this->denyAccessUnlessGranted(AdminUserRole::ROLE_ADMIN);

$oldPassword = $adminUser->getPassword();

$deleteForm = $this->createDeleteForm($adminUser);
$editForm = $this->createForm(AdminUserType::class, $adminUser);
$editForm->handleRequest($request);

if ($editForm->isSubmitted() && $editForm->isValid()) {
if (null === $editForm->getData()->getPassword()) {
$adminUser->setPassword($oldPassword);
} else {
$password = $passwordEncoder->encodePassword($adminUser, $adminUser->getPassword());
$adminUser->setPassword($password);
}

$this->getDoctrine()->getManager()->flush();
$managerRegistry->getManager()->flush();
$this->flashes->successMessage($this->adminTranslator->translate('admin.success_operation'));

return $this->redirectToRoute('user_edit', array('id' => $adminUser->getId()));
Expand All @@ -112,7 +121,60 @@ public function editAction(Request $request, AdminUser $adminUser, UserPasswordE
'form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
}

/**
* @Route("/{id}/change-password", name="user_change_password", methods={"GET", "POST"})
*/
public function changePassword(
int $id,
AdminUserRepository $userRepository,
Request $request,
ManagerRegistry $managerRegistry,
Flashes $flashes,
UserPasswordHasherInterface $userPasswordHasher
)
{
/**
* @var AdminUser $user
*/
$user = $userRepository->find($id);
$form = $this->createForm(AdminUserPasswordType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
try {
if (!$userPasswordHasher->isPasswordValid($user, $form->get('oldPassword')->getData())) {
throw new \Exception($this->adminTranslator->translate('admin.the_password_is_incorrect'));
}

$user->setPassword(
$userPasswordHasher->hashPassword(
$user,
$form->get('plainPassword')->getData()
)
);

$managerRegistry->getManager()->flush();
$flashes->successMessage($this->adminTranslator->translate('admin.password_has_been_changed'));

} catch (\Exception $exception) {
$this->flashes->errorMessage($exception->getMessage());
}

return $this->redirectToRoute('user_change_password', [
'id' => $user->getId()
]);
}

$this->breadcrumbs->setItems([
'admin.users' => $this->generateUrl('user_index'),
$user->getUsername() => null
]);

return $this->render('@TwinElementsAdmin/adminuser/change-password.html.twig', [
'form' => $form->createView(),
'adminUser' => $user
]);
}

/**
Expand Down
178 changes: 178 additions & 0 deletions src/Controller/ResetPasswordController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
<?php

namespace TwinElements\AdminBundle\Controller;

use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Translation\TranslatorInterface;
use SymfonyCasts\Bundle\ResetPassword\Controller\ResetPasswordControllerTrait;
use SymfonyCasts\Bundle\ResetPassword\Exception\ResetPasswordExceptionInterface;
use SymfonyCasts\Bundle\ResetPassword\ResetPasswordHelperInterface;
use TwinElements\AdminBundle\Entity\AdminUser;
use TwinElements\AdminBundle\Entity\ResetPasswordEmail;
use TwinElements\AdminBundle\Form\ChangePasswordFormType;
use TwinElements\AdminBundle\Form\ResetPasswordRequestFormType;
use TwinElements\AdminBundle\Repository\AdminUserRepository;
use TwinElements\Component\Message\MessageBuilder;

/**
* @Route("/reset-admin-password")
*/
class ResetPasswordController extends AbstractController
{
use ResetPasswordControllerTrait;

private ResetPasswordHelperInterface $resetPasswordHelper;
private EntityManagerInterface $entityManager;
private MessageBuilder $messageBuilder;
private TranslatorInterface $translator;
private AdminUserRepository $userRepository;

public function __construct(
ResetPasswordHelperInterface $resetPasswordHelper,
EntityManagerInterface $entityManager,
MessageBuilder $messageBuilder,
TranslatorInterface $translator,
AdminUserRepository $userRepository
)
{
$this->resetPasswordHelper = $resetPasswordHelper;
$this->entityManager = $entityManager;
$this->messageBuilder = $messageBuilder;
$this->translator = $translator;
$this->userRepository = $userRepository;
}

/**
* Display & process form to request a password reset.
* @Route("", name="admin_forgot_password_request")
*/
public function request(Request $request, MailerInterface $mailer): Response
{
$form = $this->createForm(ResetPasswordRequestFormType::class);
$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {
return $this->processSendingPasswordResetEmail(
$form->get('email')->getData(),
$mailer
);
}

return $this->render('@TwinElementsAdmin/reset_password/request.html.twig', [
'requestForm' => $form->createView(),
]);
}

/**
* Confirmation page after a user has requested a password reset.
* @Route("/check-email", name="admin_check_email")
*/
public function checkEmail(): Response
{
if (null === ($resetToken = $this->getTokenObjectFromSession())) {
$resetToken = $this->resetPasswordHelper->generateFakeResetToken();
}

return $this->render('@TwinElementsAdmin/reset_password/check_email.html.twig', [
'resetToken' => $resetToken,
]);
}

/**
* Validates and process the reset URL that the user clicked in their email.
* @Route("/reset/{token}", name="admin_reset_password")
*/
public function reset(Request $request, UserPasswordHasherInterface $userPasswordHasher, string $token = null): Response
{
if ($token) {
$this->storeTokenInSession($token);

return $this->redirectToRoute('admin_reset_password');
}

$token = $this->getTokenFromSession();
if (null === $token) {
throw $this->createNotFoundException('No reset password token found in the URL or in the session.');
}

try {
$user = $this->resetPasswordHelper->validateTokenAndFetchUser($token);
} catch (ResetPasswordExceptionInterface $e) {
$this->addFlash('reset_password_error', sprintf(
'There was a problem validating your reset request - %s',
$e->getReason()
));

return $this->redirectToRoute('admin_forgot_password_request');
}

$form = $this->createForm(ChangePasswordFormType::class);
$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {
$this->resetPasswordHelper->removeResetRequest($token);

$encodedPassword = $userPasswordHasher->hashPassword(
$user,
$form->get('plainPassword')->getData()
);

$user->setPassword($encodedPassword);
$this->entityManager->flush();

// The session is cleaned up after the password has been changed.
$this->cleanSessionAfterReset();

return $this->redirectToRoute('security_login');
}

return $this->render('@TwinElementsAdmin/reset_password/reset.html.twig', [
'resetForm' => $form->createView(),
]);
}

private function processSendingPasswordResetEmail(
string $emailFormData,
MailerInterface $mailer
): RedirectResponse
{
/**
* @var AdminUser $user
*/
$user = $this->userRepository->findOneBy([
'email' => $emailFormData,
]);

if (!$user || !$user->isEnable()) {
return $this->redirectToRoute('admin_check_email');
}

try {
$resetToken = $this->resetPasswordHelper->generateResetToken($user);
} catch (ResetPasswordExceptionInterface $e) {
$this->addFlash('reset_password_error', sprintf(
'There was a problem handling your password reset request - %s',
$e->getReason()
));

return $this->redirectToRoute('admin_check_email');
}

$this->messageBuilder->setSubject($this->translator->trans('admin.forgot_password_form.subject', [], 'messages'));
$this->messageBuilder->addTo($user->getEmail());
$email = $this->messageBuilder->getMessage((new ResetPasswordEmail($user->getEmail(), $resetToken)), '@TwinElementsAdmin/reset_password/email.html.twig');

$mailer->send($email);

$this->setTokenObjectInSession($resetToken);

return $this->redirectToRoute('admin_check_email');
}
}
Loading

0 comments on commit d7fe608

Please sign in to comment.