I am writing a method to request a password reset in symfony
I get the email of the user from the posted value
$data = $form->getData();
$email = $data['email'];
It seems that I get the good email value and I can verify it by a
dump($email);
that returns
"firstname.name#domain.ext"
Then I try to fetch a user in the database with
$user = $this->getDoctrine()->getRepository(User::class)->findOneBy(["email" => $email]);
but the result is null.
After that I try 2 different ways and both of them work perfectly i.e. give a valid user.
1- I replace
$email = $data['email'];
with
$email = "firstname.name#domain.ext";
2-
I change the line that fetches the user with:
$user = $this->getDoctrine()->getRepository(User::class)->findOneBy(["email" => "firstname.name#domain.ext"]);
Obviously the trouble comes from the way the value is passed to the findOneBy method, not because the user is not in the database.
I would like to know what I should do to use the value I got from the Post ?
Here is my controller
/**
* #Route("/passforgotten", name="app_forgotten_password", methods="GET|POST")
*/
public function askResetPass(
Request $request,
UserPasswordEncoderInterface $encoder,
ManagerRegistry $managerRegistry,
\Swift_Mailer $mailer,
TokenGeneratorInterface $tokenGenerator
): Response {
$defaultData = ['message' => 'Type your message here'];
$form = $this->createFormBuilder($defaultData)
->add('email', EmailType::class)
->add('send', SubmitType::class)
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
$email = $data['email'];
//alternative that works of course with a real email
//$email="firstname.name#domain.ext";
dump($email);
$user = $this->getDoctrine()->getRepository(User::class)->findOneBy(["email" => $email]);
dump($email);
dump($user);
if ($user === null) {
$this->addFlash('danger', 'Email Inconnu, recommence !');
return $this->redirectToRoute('app_register');
}
$token = $tokenGenerator->generateToken();
$manager = $managerRegistry->getManager();
try {
$user->setResetPasswordToken($token);
$manager->flush();
} catch (\Exception $e) {
$this->addFlash('warning', $e->getMessage());
return $this->redirectToRoute('home');
}
//this has not been tested yet
$url = $this->generateUrl('security/ask_reset_password', array('token' => $token), UrlGeneratorInterface::ABSOLUTE_URL);
$message = (new \Swift_Message('Rénitialisation du mot de pass'))
->setFrom(array('symfony#domain.ext'))
->setTo($user->getEmail())
->setBody('hello ask for reset pass!'
);
$mailer->send($message);
$this->addFlash('notice', 'Mail correctement envoyé !');
//this is not finished
return $this->redirectToRoute('a_route');
}
return $this->render('security/ask_reset_password.html.twig', [
'form' => $form->createView()
]);
}
Sorry but I was passing a misspelled email a letter l just before a b was missing and I used the form memorization of it each time. In fact it works in every case.
I don't find the reason why my subsequent kernel.response event listener won't modify my response here. Do you encountered this problem too before, or is there a misprint into the lines ?
Context: users will have a 10-items visit history attached to them, under Symfony3.
public function onResponse(FilterResponseEvent $event)
{
$request = $event->getRequest();
if(!$event->isMasterRequest() || !preg_match('#^dm_.*#', $request->get('_route'))){
return;
}
$history = $request->cookies->get('history');
if(!$history)$history = "[]";
$history = json_decode($history, true);
$addon = [
'route' => $request->get('_route'),
'route_params' => $request->attributes->get('_route_params'),
'url' => $request->getRequestUri(),
'date' => new \Datetime
];
$history[] = $addon;
$history = array_slice(
$history,
-10
);
$cookie = new Cookie('history', json_encode($history), time() + 3600);
$response = $event->getResponse();
$response->headers->setCookie($cookie);
}
I added some var_dump()s, to check that event was caught (and it was). I also tested to modify headers elsewhere, and it works fine. So, just here, I don't see why it doesn't.
I am new in testing.I want to test my function.I have successfully installed phpUnit. I check many tutorials on internet.But I could not get the proper information regarding testing. Here is the my function code:
public function loginAction(Request $request)
{
$session = $this->getRequest()->getSession();
if( $session->get('userId') && $session->get('userId') != '' && $session->get('type') == '2')
{
//if user is login then it will be redirect to login page
return $this->redirect($this->generateUrl('registrarGeneral_dashboard'));
}
$em = $this->getDoctrine()->getEntityManager();
$repository = $em->getRepository('DRPAdminBundle:User');
if ($request->getMethod() == 'POST')
{
$session->clear();
$userName = $request->get('username');
$password = md5($request->get('password'));
//find email, password type and status of User
$user = $repository->findOneBy(array('username' => $userName, 'password' => $password,'type'=>2,'status'=>1 ));
$userEmail = $repository->findOneBy(array('email' => $userName, 'password' => $password,'type'=>2,'status'=>1 ));
if ($user)
{
//set session of User login
$session->set('userId', $user->getId());
$session->set('type', 2);
$session->set('nameRegistrar', $user->getFirstName());
$session->set('pictureRegistrar', $user->getPicture());
//echo "<pre>";print_r($session->get('picture'));die;
return $this->redirect($this->generateUrl('registrarGeneral_dashboard'));
}
if ($userEmail)
{
$session->set('type', 2);
$session->set('userId', $userEmail->getId());
$session->set('nameRegistrar', $userEmail->getFirstName());
$session->set('pictureRegistrar', $userEmail->getPicture());
//echo "<pre>";print_r($session->get('picture'));die;
return $this->redirect($this->generateUrl('registrarGeneral_dashboard'));
}
else
{
return $this->render('DRPRegistrarGeneralBundle:Pages:login.html.twig', array('name' => 'Invalid Email/Password'));
}
}
return $this->render('DRPRegistrarGeneralBundle:Pages:login.html.twig');
}
how to test this function? Please help
I don't know what you want to test but here is an exemple of what you can do to test user fonctionnalities :
public function testUserPageDown()
{
$client = static::createClient();
$client->request('GET', '/user/login');
$this->assertTrue($client->getResponse()->isSuccessful());
$client->request('GET', '/user/register');
$this->assertTrue($client->getResponse()->isSuccessful());
}
public function testUserFirewall()
{
$client = static::createClient();
//Trying go to user routes without being logged
$client->request('GET', '/user/profile');
$this->assertTrue($client->getResponse()->isRedirect());
$client->request('GET', '/user/profile/edit');
$this->assertTrue($client->getResponse()->isRedirect());
$client->request('GET', '/user/profile/editpassword');
$this->assertTrue($client->getResponse()->isRedirect());
}
public function testUserFormRegister()
{
$client = static::createClient();
$crawler = $client->request('GET', '/user/register');
$buttonCrawlerNode = $crawler->selectButton('submit_user_register');
$form = $buttonCrawlerNode->form();
$testForm = array(
'wineot_databundle_user[username]' => 'test',
'wineot_databundle_user[firstname]' => 'test',
'wineot_databundle_user[lastname]' => 'test',
'wineot_databundle_user[mail]' => 'test#mail.fr',
'wineot_databundle_user[plain_password][first]' => 'blabla321',
'wineot_databundle_user[plain_password][second]' => 'blabla321'
);
$response = $client->getResponse();
$client->submit($form, $testForm);
//If the submit is true that mean that the register is ok
$this->assertTrue($response->isSuccessful());
}
I hope that will help you do undestand how to test.
Im calling this script over ajax:
$userRepository = $this->getDoctrine( )->getRepository( 'DonePunctisBundle:User' );
$user = $userRepository->findOneByUsername( array( 'username' => $fbuser['email'] ) );
$this->_authenticateUser($user);
protected function _authenticateUser( User $user ) {
$token = new UsernamePasswordToken($user, null, 'main', $user->getRoles() );
$this->get( 'security.context' )->setToken( $token );
}
No errors, but the user do not gets login. What i need to do?
Just pass user password in UsernamePasswordToken.
new UsernamePasswordToken($user, $user->getPassword(), 'main', $user->getRoles());
Could somebody explain how you can manually create a remember me cookie in a controller?
I want the users to stay logged in after they pressed the "register"
button, without having to login with their credentials afterwards.
I've tried to create a cookie manually but i'm guessing the cookie
value is incorrect, and therefor the "remember me" functionality
doesn't work.
A cookie with the correct name gets set. I've checked that.
The remember me functionality works as expected when using the normal
login procedure with the user's credentials.
security.yml
security.yml remember me
security:
firewalls:
main:
remember_me:
lifetime: 86400
domain: ~
path: /
key: myKey
This is what I have now, even though the cookie is set, it doesn't work.
$um = $this->get('fos_user.user_manager');
$member = $um->createUser();
… Form stuff with bindRequest etc.
$um->updatePassword($member);
$um->updateUser($member);
$providerKey = $this->container->getParameter('fos_user.firewall_name');
$securityKey = 'myKey';
$token = new RememberMeToken($member, $providerKey, $securityKey,
$member->getRoles());
$this->container->get('security.context')->setToken($token);
$redirectResponse = new RedirectResponse($url);
$redirectResponse->headers->setCookie(
new \Symfony\Component\HttpFoundation\Cookie(
'REMEMBERME',
base64_encode(implode(':', array($member->getUsername(),
$member->getPassword()))),
time() + 60*60*24
)
);
return $redirectResponse;
Update:
I've also tried working with the
PersistentTokenBasedRememberMeServices class with reflection but it does not work. a cookie gets set but it's not working
$token = $this->container->get('security.context')->getToken();
$providerKey = $this->container->getParameter('fos_user.firewall_name');
$securityKey = 'myKey';
$persistenService = new
PersistentTokenBasedRememberMeServices(array($um), $providerKey,
$securityKey, array('path' => '/', 'name' => 'REMEMBERME', 'domain' =>
null, 'secure' => false, 'httponly' => true,
'lifetime' => 86400));
$persistenService->setTokenProvider(new InMemoryTokenProvider());
$method = new \ReflectionMethod('Symfony\Component\Security\Http\RememberMe\PersistentTokenBasedRememberMeServices',
'onLoginSuccess');
$method->setAccessible(true);
$method->invoke($persistenService, $request, $redirectResponse, $token);
I'm using Symfony v2.0.5 and FOSUserBundle 1.0
UPDATE 2:
I've tried a 3rd way. The same as above but without reflection:
$token = $this->container->get('security.context')->getToken();
$providerKey = $this->container->getParameter('fos_user.firewall_name');
$securityKey = 'myKey';
$persistenService = new PersistentTokenBasedRememberMeServices(array($um), $providerKey, $securityKey, array('path' => '/', 'name' => 'REMEMBERME', 'domain' => null, 'secure' => false, 'httponly' => true, 'lifetime' => 31536000, 'always_remember_me' => true, 'remember_me_parameter' => '_remember_me'));
$persistenService->setTokenProvider(new InMemoryTokenProvider());
$persistenService->loginSuccess($request, $redirectResponse, $token);
Here is how I did it. I'm not using the FOSUserBundle and I'm using Doctrine Entity User Provider, but it should be trivial to adjust to your needs. Here is a general solution:
// after registration and persisting the user object to DB, I'm logging the user in automatically
$token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
// but you can also get the token directly, if you're user is already logged in
$token = $this->container->get('security.context')->getToken();
// write cookie for persistent session storing
$providerKey = 'main'; // defined in security.yml
$securityKey = 'MySecret'; // defined in security.yml
$userProvider = new EntityUserProvider($this->getDoctrine()->getEntityManager(), 'MyCompany\MyBundle\Entity\User', 'username');
$rememberMeService = new TokenBasedRememberMeServices(array($userProvider), $securityKey, $providerKey, array(
'path' => '/',
'name' => 'MyRememberMeCookie',
'domain' => null,
'secure' => false,
'httponly' => true,
'lifetime' => 1209600, // 14 days
'always_remember_me' => true,
'remember_me_parameter' => '_remember_me')
);
$response = new Response();
$rememberMeService->loginSuccess($request, $response, $token);
// further modify the response
// ........
return $response;
Just remember you have to set always_remember_me option to true (like I did in the code above) or have it in your $_POST parameters somehow, otherwise method isRememberMeRequested of AbstractRememberMeServices will return false and the cookie won't be stored.
You were pretty close to the correct solution though :) What you did wrong (in the 3rd attempt) is that you've changed the order of parameters here:
$persistenService = new PersistentTokenBasedRememberMeServices(array($um), $providerKey, $securityKey, array('path' => '/', 'name' => 'REMEMBERME', 'domain' => null, 'secure' => false, 'httponly' => true, 'lifetime' => 31536000, 'always_remember_me' => true, 'remember_me_parameter' => '_remember_me'));
Take a look at __construct() in AbstractRememberMeServices.php. You should pass a $securityKey as 2nd argument and $providerKey as 3rd argument, not the other way around like you did by mistake ;)
What I don't know yet, is how to get parameters from security.yml directly in the controller not to duplicate it. By using $this->container->getParameter() I can get parameters stored under parameters key in config.yml, but not the ones places higher in the configuration tree. Any thoughts on this?
If you are setting the rememberme cookie directly, you have to use the following format:
base64_encode(<classname>:base64_encode(<username>):<expiry-timestamp>:<hash>)
where the hash will be:
sha256(<classname> . <username> . <expiry-timestamp> . <password> . <key>)
the key is the key you have entered in your security(.xml/.yml) in the remember_me section.
This is taken from processAutoLoginCookie() method in the Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeService.php file.
This is all done by the generateCookieValue() method in the same class.
However, I would not recommend on using doing it this way directly, but try to see if you can call the TokenBasedRememberMeService::onLoginSuccess() method, which sets this cookie for you to make the code more robust and portable.
For me the easiest solution was extend a BaseTokenBasedRememberMeServices and let it handle
namespace AppBundke\Security\Http;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\Security\Http\RememberMe\TokenBasedRememberMeServices as BaseTokenBasedRememberMeServices;
class TokenBasedRememberMeServices extends BaseTokenBasedRememberMeServices
{
protected $options_new = array('name' => 'REMEMBERME', 'domain' => null, 'path' => '/');
public function __construct($userProvider, $secret, $providerKey, array $options = array(), LoggerInterface $logger = null)
{
return parent::__construct(array($userProvider), $secret, $providerKey, array_merge($this->options_new, $options));
}
public function generateCookie($user, $username, $expires, $password)
{
$cookie = new Cookie(
$this->options['name'],
parent::generateCookieValue(get_class($user), $username, $expires, $password),
$expires,
$this->options['path'],
$this->options['domain'],
$this->options['secure'],
$this->options['httponly']
);
return $cookie;
}
}
and in controller;
$user = $this->getUser();
$providerKey = $this->getParameter('fos_user.firewall_name');
$secret = $this->getParameter('secret');
$cookie_life_time = $this->getParameter('cookie_life_time');
$remember_me_service = new TokenBasedRememberMeServices($user, $secret, $providerKey );
$remember_me_cookie = $remember_me_service->generateCookie($user, $user->getUsername(),(time() + $cookie_life_time), $user->getPassword());
then response set cookie to $remember_me_cookie
I hope its works with you 2.
I had the same issue when I tried to set REMEMBERME cookie an User after a connection by token, using Guard Authentication.
In this situation I had no Response object to be able to use $response->headers->setCookie() and needs to use setcookie().
And in this situation, create a RedirectResponse is not appropriate.
This needs to be refactored but I post the raw procedural on which I based my service
$expires = time() + 2628000;
$hash = hash_hmac(
'sha256',
get_class($user).$user->getUsername().$expires.$user->getPassword(), 'secret in parameters.yml'
);
$value = base64_encode(implode(':', [get_class($user), base64_encode($user->getUsername()), $expires, $hash]));
setcookie(
'REMEMBERME',
$value,
$expires,
'/',
'host',
'ssl boolean',
true
);