Symfony 4 - Good practice to remove your own user account while connected - symfony

I would like my users to be able to delete their own user account. I made a SecurityController where there is my 3 functions login, logout and deleteUser. When I delete the current user in database this error appears :
You cannot refresh a user from the EntityUserProvider that does not contain an identifier. The user object has to be serialized with its own identifier mapped by Doctrine.
When I delete another user, it works correctly because he's not connected.
Do I have to serialize the User and pass it through a Service, logout the user then remove it in a Service? Or can I clear the PHP session in the controller but I don't know how to do it with symfony4 I think it changed since version 4.
<?php
namespace App\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
use App\Entity\User;
use App\Form\UserType;
class SecurityController extends Controller
{
/**
* #Route("/createAdmin", name="create_admin")
*/
public function createAdminUser(Request $request, UserPasswordEncoderInterface $passwordEncoder)
{
$usersRepo = $this->getDoctrine()->getRepository(User::class);
$uCount = $usersRepo->countAllUsers();
if ($uCount == 0)
{
$user = new User();
$form = $this->createForm(UserType::class, $user, array(
'is_fresh_install' => true,
));
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// Encode the password
$password = $passwordEncoder->encodePassword($user, $user->getPlainPassword());
$user->setPassword($password);
// save the User
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
// Do what you want here before redirecting the user
return $this->redirectToRoute('login');
}
return $this->render('security/register_admin_user.html.twig', array(
'form' => $form->createView(),
));
} else {
if ($this->getUser())
{
return $this->redirectToRoute('user_account');
} else {
return $this->redirectToRoute('login');
}
}
}
/**
* #Route("/login", name="login")
*/
public function login(Request $request, AuthenticationUtils $authUtils)
{
$usersRepo = $this->getDoctrine()->getRepository(User::class);
$uCount = $usersRepo->countAllUsers();
if ($uCount == 0)
{
return $this->redirectToRoute('create_admin');
} else {
$error = $authUtils->getLastAuthenticationError();
$lastUsername = $authUtils->getLastUsername();
return $this->render('security/login.html.twig', array(
'last_username' => $lastUsername,
'error' => $error,
));
}
}
/**
* #Route("/logout", name="logout")
*/
public function logout()
{
}
/**
* #Route("/delete_user/{id}", name="delete_user")
*/
public function deleteUser($id)
{
$em = $this->getDoctrine()->getManager();
$usrRepo = $em->getRepository(User::class);
$user = $usrRepo->find($id);
$em->remove($user);
$em->flush();
return $this->redirectToRoute('user_registration');
}
}

SOLUTION :
You have to clear the Session before deleting user entry in DB with this method:
<?php
use Symfony\Component\HttpFoundation\Session\Session;
// In your deleteUser function...
$currentUserId = $this->getUser()->getId();
if ($currentUserId == $id)
{
$session = $this->get('session');
$session = new Session();
$session->invalidate();
}
I don't know why $this->get('session')->invalidate(); doesn't work directly... if someone knows :)

I find it also a good solution to do this what is an accepted answer but you can also simply redirect the user to the logout route.
This has worked for me without any problem in Symfony 4.4

I think you have to serialize the User and pass it through a service.Just check with the previous versions,you will find out the problem.

Related

How to modify user Information after requiring his password with Symfony 4

I would like to create a page that allows the user to modify his personal infos.
I want him to enter his current password to modify any information.
I created a form based on the connected user when the form is submitted and valid, I want to check if the password is valid using the function isPasswordValid() of my passwordEncoder..
My problem is that when this function is called with $user as parameter, it always returns false. I found where this problem comes from, it's because the $user used as parameter as been modified when the form has been submitted. I've tried declaring another variable to stock my Initial User ($dbUser for example) and using another to instance the form but when I dump the
$dbUser it has been modified and I don't know why...
The variable shouldn't be changed after the submit because it's never used... I can't find what I doing wrong...
/**
* #Route("/mes-infos", name="account_infos")
*/
public function showMyInfos(Request $request, UserRepository $userRepo)
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
$user = $this->getUser();
// $dbUser = $userRepo->findOneBy(['id' => 13]);
$form = $this->createForm(UserModificationType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$enteredPassword = $request->request->get('user_modification')['plainPassword'];
$passwordEncoder = $this->passwordEncoder;
$manager = $this->getDoctrine()->getManager();
if ($passwordEncoder->isPasswordValid($user, $enteredPassword)) {
dd('it works!!!!!');
// $manager->persist($user);
// $manager->flush();
} else {
dd('It\'s not!!!!');
}
}
return $this->render('account/myaccount-infos.html.twig', [
'form' => $form->createView(),
]);
}
The better solution is to use a constraint.
Symfony already implements a UserPasswordConstraint.
You can add it in your entity directly. Be careful to declare a group for this constraint. If you don't do it, your use case "user update" will works fine, but the use case "user creation" will now failed.
namespace App\Entity;
use Symfony\Component\Security\Core\Validator\Constraints as SecurityAssert;
class User
{
//...
/**
* #SecurityAssert\UserPassword(
* message = "Wrong value for your current password",
* groups = {"update"}
* )
*/
protected $password;
//...
}
In your form, specified the validation groups by updating (or adding) the configureOptions method:
//App\Form\UserModificationType
//...
class UserModificationType {
//...
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
// ...
'validation_groups' => ['update'],
]);
}
}
Then your $form->isValid() will automatically test the password. So, you can removed all lines in the "if condition".
/**
* #Route("/mes-infos", name="account_infos")
*/
public function showMyInfos(Request $request, UserRepository $userRepo)
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
$user = $this->getUser();
$form = $this->createForm(UserModificationType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
dd('Password is good! it works!!!!!');
}
return $this->render('account/myaccount-infos.html.twig', [
'form' => $form->createView()
]);
}
But I think it is a better practice to use a Model like in the documentation when you update your user entity.

Overriding FOSUserBundle loginAction() to redirect to custom routes

I am working on a Symfony 3.3.8 project with FOSUserBundle. I have created two registration and two profile pages for student and provider respectively. Now I am trying to override the FOSUserBundle's loginAction() method, where I am checking the logged-in user for it's role. If the role is ROLE_STUDENT I am trying to redirect the user to student profile page after successful login and if the role is ROLE_PROVIDER I want the user to be redirected to the provider profile page after successful login. Here is my overridden SecurityController with loginAction():
<?php
namespace Epita\HousingBundle\Controller;
use FOS\UserBundle\Controller\SecurityController as BaseController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
class SecurityController extends BaseController {
/**
*
*/
public function loginAction(Request $request) {
/** #var $session \Symfony\Component\HttpFoundation\Session\Session */
$session = $request->getSession();
$key = '_security.main.target_path';
$securityContext = $this->container->get('security.authorization_checker');
if ($securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
$user = $this->getUser();
}
if ($this->container->get('session')->has($key))
{
if ($this->get('security.authorization_checker')->isGranted('ROLE_STUDENT')) {
return $this->redirectToRoute('student_profile');
} else if ($this->get('security.authorization_checker')->isGranted('ROLE_PROVIDER')) {
return $this->redirectToRoute('provider_profile');
}
}
else{
return $this->redirectToRoute('student_profile');
}
if (class_exists('\Symfony\Component\Security\Core\Security')) {
$authErrorKey = Security::AUTHENTICATION_ERROR;
$lastUsernameKey = Security::LAST_USERNAME;
} else {
// BC for SF < 2.6
$authErrorKey = SecurityContextInterface::AUTHENTICATION_ERROR;
$lastUsernameKey = SecurityContextInterface::LAST_USERNAME;
}
// get the error if any (works with forward and redirect -- see below)
if ($request->attributes->has($authErrorKey)) {
$error = $request->attributes->get($authErrorKey);
} elseif (null !== $session && $session->has($authErrorKey)) {
$error = $session->get($authErrorKey);
$session->remove($authErrorKey);
} else {
$error = null;
}
if (!$error instanceof AuthenticationException) {
$error = null; // The value does not come from the security component.
}
// last username entered by the user
$lastUsername = (null === $session) ? '' : $session->get($lastUsernameKey);
if ($this->has('security.csrf.token_manager')) {
$csrfToken = $this->get('security.csrf.token_manager')->getToken('authenticate')->getValue();
} else {
// BC for SF < 2.4
$csrfToken = $this->has('form.csrf_provider')
? $this->get('form.csrf_provider')->generateCsrfToken('authenticate')
: null;
}
return $this->renderLogin(array(
'last_username' => $lastUsername,
'error' => $error,
'csrf_token' => $csrfToken,
));
}
/**
*
* #param array $data
*
* #return \Symfony\Component\HttpFoundation\Response
*/
protected function renderLogin(array $data)
{
$template = sprintf('EpitaHousingBundle:Security:login.html.twig');
return $this->container->get('templating')->renderResponse($template, $data);
}
public function checkAction()
{
throw new \RuntimeException('You must configure the check path to be handled by the firewall using form_login in your security firewall configuration.');
}
public function logoutAction()
{
throw new \RuntimeException('You must activate the logout in your security firewall configuration.');
}
}
But this solution does not work for me. My guess is I have to play around with the session. Since I am new to this can anyone help me?
Thanks in advance. Let me know if you need more details.
Here is some code that might help you.
1 - This is the Event/LoginSuccessHandler, your logic is in this class
<?php
namespace AppBundle\Event;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Router;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
class LoginSuccessHandler implements AuthenticationSuccessHandlerInterface
{
protected $router;
protected $security;
public function __construct(Router $router, AuthorizationChecker $security)
{
$this->router = $router;
$this->security = $security;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
$url = 'homepage';
if ($this->security->isGranted('ROLE_STUDENT')) {
$url = 'student_route';
}
if ($this->security->isGranted('ROLE_PROVIDER')) {
$url = 'provider_route';
}
$response = new RedirectResponse($this->router->generate($url));
return $response;
}
}
2 - We set up the config that listens the login event in your services.yml
login_success_handler:
class: AppBundle\Event\LoginSuccessHandler
arguments:
- "#router"
- "#security.authorization_checker"
tags:
- { name: 'monolog.logger', channel: 'security' }
3 - That should be it. Try and tell me if something is wrong

How to load 'isSubmitted()-method' (defined in service controller) into the main controller?

I've put a registration form in a method so, that I can use it in different places.
My service registration controller looks like this:
public function loadRegisterForm()
{
$user = new User();
$form = $this->createForm(RegistrationType::class, $user);
$form->handleRequest($this->request);
$errors = "";
if ($form->isSubmitted())
{
if ($form->isValid())
{
$password = $this->get('security.password_encoder')
->encodePassword($user, $user->getPlainPassword());
$user->setPassword($password);
$user->setIsActive(1);
$user->setLastname('none');
$user->setCountry('none');
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
}
else
{
$errors = $this->get('validator')->validate($form);
}
}
$parametersArray['form'] = $form;
$parametersArray['errors'] = $errors;
return $parametersArray;
}
services.yml looks like this:
register_form_service:
class: ImmoBundle\Controller\Security\RegistrationController
calls:
- [setContainer, ["#service_container"]]
And the main controller where I load the service controller:
private function indexAction()
{
/**
* Load register form
*/
$registerForm = $this->get('register_form_service');
$registerParameters = $registerForm->loadRegisterForm();
$registerParameters['form']->handleRequest($request);
return $this->render(
'ImmoBundle::Pages/mainPage.html.twig',
array(
'register_form' => $registerParameters['form']->createView(),
'errors' => $registerParameters['errors'],
)
);
}
The form itself is well rendered, so there is no problem. However nothing happens if I try to submit the form. I know that I should add the following line to the main controller
if ($registerParameters['form']->isSubmitted())
{
// add to db
}
But is there any way to do it only in a service controller?
You do not need a service definition to inject the container into your controller. If the controller extends Symfony\Bundle\FrameworkBundle\Controller\Controller all services are accesible via ->get(). Next to that, $form->isValid() already checks whether the form is submitted.
Why is your action private? It should be public, and it need to get the Request object as it's first parameter:
public function indexAction(Request $request)
{
$user = new User();
$form = $this->createForm(RegistrationType::class, $user);
$form->handleRequest($request);
if ($form->isValid()) {
// Do something here
}
}
See http://symfony.com/doc/current/book/forms.html#handling-form-submissions

Symfony2 FOSUser Login After clear cache

I have a problem with my FosUser login in this days i try to render the controller of my login html page in navbar and the login work well..
Now i put my site on the server, i clear the cache and after login the page refreshes without log in the site..
This is the controller
namespace FOS\UserBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
class SecurityController extends Controller
{
public function loginAction(Request $request)
{
/** #var $session \Symfony\Component\HttpFoundation\Session\Session */
$session = $request->getSession();
// get the error if any (works with forward and redirect -- see below)
if ($request->attributes->has(SecurityContextInterface::AUTHENTICATION_ERROR)) {
$error = $request->attributes->get(SecurityContextInterface::AUTHENTICATION_ERROR);
} elseif (null !== $session && $session->has(SecurityContextInterface::AUTHENTICATION_ERROR)) {
$error = $session->get(SecurityContextInterface::AUTHENTICATION_ERROR);
$session->remove(SecurityContextInterface::AUTHENTICATION_ERROR);
} else {
$error = null;
}
if (!$error instanceof AuthenticationException) {
$error = null; // The value does not come from the security component.
}
// last username entered by the user
$lastUsername = (null === $session) ? '' : $session->get(SecurityContextInterface::LAST_USERNAME);
$csrfToken = $this->has('form.csrf_provider')
? $this->get('form.csrf_provider')->generateCsrfToken('authenticate')
: null;
return $this->renderLogin(array(
'last_username' => $lastUsername,
'error' => $error,
'csrf_token' => $csrfToken,
));
}
/**
* Renders the login template with the given parameters. Overwrite this function in
* an extended controller to provide additional data for the login template.
*
* #param array $data
*
* #return \Symfony\Component\HttpFoundation\Response
*/
protected function renderLogin(array $data)
{
return $this->render('FOSUserBundle:Security:login.html.twig', $data);
}
public function checkAction()
{
throw new \RuntimeException('You must configure the check path to be handled by the firewall using form_login in your security firewall configuration.');
}
public function logoutAction()
{
throw new \RuntimeException('You must activate the logout in your security firewall configuration.');
}
}
How can i do??
Did you do a:
app/console cache:clear --env=prod
or a:
app/console cache:clear
You should use the first one for your production environment
https://reformatcode.com/browse

How to add a collection when register a new user using Symfony 2.6 FosUser 1.3

I'm using FosUserBundle 1.3 and Symfo 2.6.
I need to add addresses (collection type) when I register a new User. Is it possible ?
Address entity has the owner side so when I want to register (create) the user, it returns me an error (contraint integrity userID cannot be null).
I see that I can do something like that into the Controller before the flush :
if($entity->getAddresses() !== null){
foreach($entity->getAddresses() as $address){
$address->setUser($entity);
}
}
Is it best practice ?
Now, where can I do that with Fos ? I think that here seems to be the right place but I'm not sure and can I override it ?
RegistrationFormHandler
...
public function process($confirmation = false)
{
$user = $this->createUser();
$this->form->setData($user);
if ('POST' === $this->request->getMethod()) {
$this->form->bind($this->request);
if ($this->form->isValid()) {
// I WOULD LIKE TO COPY MY CODE HERE
//
$this->onSuccess($user, $confirmation);
return true;
}
}
return false;
}
...
This will work since 2.0 version.
I think you should register you own EventListener that will listen to FOSUserEvents::REGISTRATION_SUCCESS or FOSUserEvents::REGISTRATION_COMPLETED and then you can add there your custom logic without controller overriding. Something like:
RegistrationListener class:
namespace AppBundle\EventListener;
use FOS\UserBundle\Event\FilterUserResponseEvent;
use FOS\UserBundle\FOSUserEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class RegistrationListener implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_COMPLETED => 'completed'
);
}
public function completed(FilterUserResponseEvent $event)
{
$user = $event->getUser();
// add your custom logic here
}
}
and services.yml:
app_listener.registration:
class: AppBundle\EventListener\RegistrationListener
tags:
- { name: kernel.event_subscriber }
Remember to add proper dependencies to your listener class if needed.
For 1.3 version
You need to create a child bundle whose parent is FOSUserBundle and ovveride: RegistraionController::registerAction in your case:
<?php
namespace Acme\UserBundle\Controller;
use Symfony\Component\HttpFoundation\RedirectResponse;
use FOS\UserBundle\Controller\RegistrationController as BaseController;
class RegistrationController extends BaseController
{
public function registerAction()
{
$form = $this->container->get('fos_user.registration.form');
$formHandler = $this->container->get('fos_user.registration.form.handler');
$confirmationEnabled = $this->container->getParameter('fos_user.registration.confirmation.enabled');
$process = $formHandler->process($confirmationEnabled);
if ($process) {
$user = $form->getData();
// you custom logic
if ($confirmationEnabled) {
$this->container->get('session')->set('fos_user_send_confirmation_email/email', $user->getEmail());
$route = 'fos_user_registration_check_email';
} else {
$this->authenticateUser($user);
$route = 'fos_user_registration_confirmed';
}
$this->setFlash('fos_user_success', 'registration.flash.user_created');
$url = $this->container->get('router')->generate($route);
return new RedirectResponse($url);
}
return $this->container->get('templating')->renderResponse('FOSUserBundle:Registration:register.html.'.$this->getEngine(), array(
'form' => $form->createView(),
));
}
}

Resources