Symfony, wrong user logged by reset-password link - symfony

On my platform, the administrator create a user where the password is randomly generated and this automatically sends an email to this new user. The email contains a link that leads to the reset-password page (which will be a password creation page for the user because he does not know that he already has a password generated).
The problem is that when the user clicks on the email link and arrives on the change password page, he is logged in as admin and therefore has permissions that he should not have.
In fact, I want the email link to connect the new user to his account, I don't want him to be logged in as admin. I'm not sure how to do this.
I don't know much about tokens. I believe the Token is generated based on the session used (?).
Thank you in advance for your help.
Here is the code for creating a user :
/**
* #Route("/new", name="user_new", methods={"GET", "POST"})
* #throws TransportExceptionInterface
*/
public function new(Request $request, MailSender $mailSender,UserPasswordHasherInterface $passwordHasher): Response
{
// TODO CHECK IF USER ALREADY EXISTS BY EMAIL
$user = new User();
$form = $this
->createForm(UserType::class, $user)
->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// TODO GENERATE RANDOM PASSWORD
//$passwordHasher->hashPassword($user, $user->getPassword()));
$user->setPassword($passwordHasher->hashPassword($user, "password"));
$this->entityManager->persist($user);
$this->entityManager->flush();
try {
$resetToken = $mailSender->makeToken($user);
} catch (ResetPasswordExceptionInterface $e) {
return $this->redirectToRoute('user_new');
}
$mailInfos = array('template'=>"reset_password/email_activate.html.twig", 'subject'=>"Activer votre compte", 'email'=>$user->getEmail());
$mailSender->sendMail($resetToken, $mailInfos);
$mailSender->storeToken($resetToken);
return $this->redirectToRoute('user_index', [], Response::HTTP_SEE_OTHER);
}
return $this->renderForm('user/new.html.twig', [
'user' => $user,
'form' => $form,
]);
}

This is expected behaviour because:
multiple tabs/instances of the same browser will usually share the
same server-side session when interacting with the same domain.
means that you can´t be logged in with different users in different tabs per default.
And I don´t think that you would want this, just think of the downsides, do you really want to login again for every tab? This is very uncommon practice. Imagine you would open a stack-overflow question in a new tab and you would not be logged in there.
There are ways to achieve this though, but really re-think if thats your actual usecase, i don´t think so, you are just developing your feature and testing it, and in production a new user will not be already logged in as admin is my assumption.
So for testing your feature just use a private tab (that does usually not share the same server-side session )
if you want to learn more i found this pretty cool so-thread where users try to explain as best as possible
What are sessions? How do they work?

Related

FOSUserBundle, EventListener registration user

I'm working on the FOSUserBundle, on EventListener for RegistrationUser.
In this bundle, when I create a user, I use a method updateUser() (in Vendor...Model/UserManagerInterface). This method seems to be subject to an EventListener that triggers at least two actions. Registering the information entered in the database. And sending an email to the user to send them login credentials.
I found the method that sends the mail. By cons, I didn't find the one who makes the recording. I also didn't find where to set the two events.
First for all (and my personal information), I try to find these two points still unknown. If anyone could guide me?
Then, depending on what we decide with our client, I may proceed to a surcharge (which I still don't really know how to do), but I imagine that I would find a little better once my two strangers found
Thanks for your attention and help
This is the function wich handles the email confirmation on registrationSucces
FOS\UserBundle\EventListener\EmailConfirmationListener
public function onRegistrationSuccess(FormEvent $event)
{
/** #var $user \FOS\UserBundle\Model\UserInterface */
$user = $event->getForm()->getData();
$user->setEnabled(false);
if (null === $user->getConfirmationToken()) {
$user->setConfirmationToken($this->tokenGenerator->generateToken());
}
$this->mailer->sendConfirmationEmailMessage($user);
$this->session->set('fos_user_send_confirmation_email/email', $user->getEmail());
$url = $this->router->generate('fos_user_registration_check_email');
$event->setResponse(new RedirectResponse($url));
}
But I tell you that what you are trying to do is a bad practice. The recommended way is the following.
Step 1: Select one of the following events to listen(depending on when you want to catch the process)
/**
* The REGISTRATION_SUCCESS event occurs when the registration form is submitted successfully.
*
* This event allows you to set the response instead of using the default one.
*
* #Event("FOS\UserBundle\Event\FormEvent")
*/
const REGISTRATION_SUCCESS = 'fos_user.registration.success';
/**
* The REGISTRATION_COMPLETED event occurs after saving the user in the registration process.
*
* This event allows you to access the response which will be sent.
*
* #Event("FOS\UserBundle\Event\FilterUserResponseEvent")
*/
const REGISTRATION_COMPLETED = 'fos_user.registration.completed';
Step 2 Implement the Event Subscriber with a priority
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_SUCCESS => [
'onRegistrationSuccess', 100 //The priority is higher than the FOSuser so it will be called first
],
);
}
Step 3 Implement your function
public function onRegistrationSuccess(FormEvent $event)
{
//do your logic here
$event->stopPropagation();//the Fos User method shall never be called!!
$event->setResponse(new RedirectResponse($url));
}
You never should modify the third party libraries in this case the Event Dispatcher System is made for this to earlier process the event and if its needed stop the propagation and avoid the "re-processing" of the event.
Hope it helps!!!!

Symfony2 PHPunit testing functions which gets user object from token storage

I know this question is already asked but I can't get it to work and I can't see what I'm doing wrong.
I'm trying to write a test for a function which depends on a user logged in and gets user object from security.token_storage but unfortunately I can't get it work. For setting up the token my code is
$token = new UsernamePasswordToken(
$person,
$person->getPassword(),
'sso',
$person->getRoles()
);
$containerInterface->get('security.token_storage')->setToken($token);
$containerInterface->get('event_dispatcher')->dispatch(
AuthenticationEvents::AUTHENTICATION_SUCCESS,
new AuthenticationEvent($token)
);
where $person is user object and 'sso' is firewall name. When I run a test where I get user object from token_storage I get null.
If I understood correctly, you're missing the login event
$event = new InteractiveLoginEvent($request, $token);
$this->get("event_dispatcher")->dispatch("security.interactive_login", $event);
Also, but I'm not sure of, you don't need this snippet
$containerInterface->get('event_dispatcher')->dispatch(
AuthenticationEvents::AUTHENTICATION_SUCCESS,
new AuthenticationEvent($token)
);

Ignore Password when user updates profile with FOSUserBundle

I am using FOSUserBundle and I am trying to create a page that allows a user to update their user profile. The problem I am facing is that my form does not require that the user reenter their password if they don't want to change/update their password. So when a user submits the form with an empty password the database will be updated with an empty string, and the user will not be able to log in.
How can I get my form to ignore updating the password field if it is not set? Below is the code I am using.
$user = $this->get('security.context')->getToken()->getUser();
//user form has email and repeating password fields
$userForm = $this->createForm(new UserFormType(), $user);
if ($request->getMethod() == 'POST') {
$userForm->bindRequest($request);
if($userForm->isValid()){
//this will be be empty string in the database if the user does not enter a password
$user->setPlainPassword($userForm->getData()->getPassword());
$em->flush();
}
}
I have tried a few things such as the following, but this is still empty because the bindRequest sets the empty password to the user
if($userForm->getData()->getPassword())
$user->setPlainPassword($userForm->getData()->getPassword());
I have also tried, but this results in a similar situation and causes an unneeded query
if($userForm->getData()->getPassword())
$user->setPlainPassword($userForm->getData()->getPassword());
else
$user->setPlainPassword($user->getPlainPassword());
Are there any elegant ways to handle this use case?
The problem is that you bind a form to a User Object before controls upon password.
Let's analyze your snippet of code.
Do the following
$user = $this->get('security.context')->getToken()->getUser();
will load an existing user into a User Object. Now you "build" a form with that data and if receive a post, you'll take the posted data into the previous object
$userForm = $this->createForm(new UserFormType(), $user);
if ($request->getMethod() == 'POST') {
$userForm->bindRequest($request);
So, onto bindRequest you have alredy lost previous password into the object (obviously not into database yet) if that was leave empty. Every control from now on is useless.
A solution in that case is to manually verify value of form's field directly into $request object before binding it to the underlying object.
You can do this with this simple snippet of code
$postedValues = $request->request->get('formName');
Now you have to verify that password value is filled
if($postedValues['plainPassword']) { ... }
where plainPassword I suppose to be the name of the field we're interesting in.
If you find that this field contain a value (else branch) you haven't to do anything.
Otherwise you have to retrieve original password from User Object and set it into $request corrisponding value.
(update) Otherwise you may retrieve password from User Object but since that password is stored with an hased valued, you can't put it into the $request object because it will suffer from hashing again.
What you could do - i suppose - is an array_pop directly into $request object and put away the field that messes all the things up (plainPassword)
Now that you had done those things, you can bind posted data to underlying object.
Another solution (maybe better because you move some business logic away from controller) is to use prePersist hook, but is more conceptually advanced. If you want to explore that solution, you can read this about form events
I think you should reconsider if this is in fact a good use case. Should users be able to edit other users passwords? At our institution we do not allow even the highest level admin to perform this task.
If a user needs their password changed we let them handle that themselves. If they have forgotten their password we allow them to retrieve it via email. If they need assistance with adjusting their email we allow our admins to assist users then. But all password updating and creation is done soley by the user.
I think it is great that FOSUserBundle makes it so difficult to do otherwise but if you must DonCallisto seems to have a good solution.
<?php
class User
{
public function setPassword($password)
{
if (false == empty($password)) {
$this->password = $password;
}
}
}
This will only update the password on the user if it isn't empty.
I have found a simple hack to get rid of the "Enter a password" form error.
Manualy set a dummy plainPassword in the user entity. After form validation just reset it before you flush the entity.
<?php
public function updateAction(Request $request, $id)
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('AppBundle:User')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find Customer entity.');
}
$deleteForm = $this->createDeleteForm($id);
$editForm = $this->createEditForm($entity);
$postedValues = $request->request->get('appbundle_user');
/* HERE */ $entity->setPlainPassword('dummy'); // hack to avoid the "enter password" error
$editForm->handleRequest($request);
if ($editForm->isValid()) {
/* AND HERE */ $entity->setPlainPassword(''); // hack to avoid the "enter password" error
$em->flush();
return $this->redirect($this->generateUrl('customer_edit', array('id' => $id)));
}
return array(
'entity' => $entity,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
);
}

programmatically login symfony2 after registration

i try to login the user manually after the registration with this code:
http://www.michelsalib.com/2011/04/pragmatically-authenticate-the-user-in-symfony2/
Its not working with the fresh created user, but when i login an other existing user after a userregistation, everything works fine.
I thought the user object isn't complete but i tried to read the registrerd user again and it doesn't work too.
Has anyone an idea what the problem is?
Thank you very much
You have to create a new token and pass it to the security context.
// Create a new token
$token = new UsernamePasswordToken($user, $credentials, $providerKey, $user->getRoles());
// Retrieve the security context and set the token
$context = $this->container->get('security.context');
$context->setToken($token);

How do I hook into the Wordpress login system to stop some users programmatically?

I am working on a Wordpress based portal which integrates with a custom-made e-commerce.
The e-commerce serves also as a 'control panel': all the roles are set up there. Some users are recorded but 'inactive'; they shouldn't be able to log into Wordpress. For this reason I need to hook into the Wordpress login system.
If a user is, say, "bad_james", he cannot login, even if he has a valid WP login and PWD. The WP admin panel doesn't provide a a flag to block users.
Is there a way to implement a login filter?
Cheers,
Davide
You can either overload the wp_authenticate function (see the function in the code here: http://core.trac.wordpress.org/browser/trunk/wp-includes/pluggable.php) and return a WP_error if you don't want to allow the user to login.
Or better, use the filter authenticate and return null if you don't want the user to log in, e.g.
add_filter('authenticate', 'check_login', 10, 3);
function check_login($user, $username, $password) {
$user = get_userdatabylogin($username);
if( /* check to see if user is allowed */ ) {
return null;
}
return $user;
}
There were a few issues with mjangda answer so I'm posting a version that works with WordPress 3.2
The main issues were with the return statement. He should be returning a WP_User Object. The other issue was with the priority not being high enough.
add_filter('authenticate', 'check_login', 100, 3);
function check_login($user, $username, $password) {
// this filter is called on the log in page
// make sure we have a username before we move forward
if (!empty($username)) {
$user_data = $user->data;
if (/* check to see if user is allowed */) {
// stop login
return null;
}
else {
return $user;
}
}
return $user;
}
Might be an idea or code to borrow and implement: WordPress › External DB authentication « WordPress Plugins

Resources