I use FOSUserBundle for my User Authentication
I have a controller, let's call it adminController which is reserved for User granted User::ADMIN_ROLE
Everything works fine but I have an error when I try to write my functional Test
Inside my AdminControllerTest I have a method that try to test a page that need User::ADMIN_ROLE
My testAdminAccess() method
public function testAdminAccess()
{
$session = $this->client->getContainer()->get('session');
// the firewall context defaults to the firewall name
= 'main';
$user = $this->getUserByUsername('admin#yopmail.com');
$token = new UsernamePasswordToken($user, null, $firewallContext, $user->getRoles());
$session->set('_security_'.$firewallContext, serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$this->client->getCookieJar()->set($cookie);
$this->client->followRedirects();
$crawler = $this->client->request(
'GET',
'http://localhost/admin'
);
dump($crawler);
}
I'm always redirected to my login page
How can I keep the session to access some page that's protected by a specific Role?
What I'm already tried:
http://kristiankaa.dk/symfony-authentication-controller-testing
How to log in User in Session within a Functional Test in Symfony 2.3?
https://symfony-docs-zh-cn.readthedocs.io/cookbook/testing/simulating_authentication.html
https://symfony.com/doc/2.6/cookbook/testing/simulating_authentication.html
How to programmatically login/authenticate a user?
I'm using Symfony version 3.4
The best is to login the user normally submitting the username and password like a standard user would do, I use a function like this (adapt your paths):
/**
* Log the test user for the connected tests.
*/
public function login(string $username = null, string $password = null): KernelBrowser
{
$client = static::createClient();
// Login page
$client->request('GET', '/en/login/');
$this->assertTrue($kernelBrowser->getResponse()->isOk());
// Auth
$token = $client->getContainer()->get('security.csrf.token_manager')->getToken('authenticate');
$client->request('POST', '/login_check', [
'_csrf_token' => $token,
'_username' => $username ?? 'test',
'_password' => $password ?? 'test',
'_remember_me' => 'on',
]);
$this->assertTrue($client->getResponse()->isRedirect());
$client->followRedirect();
return $client;
}
Related
Good morning.
Tldr: I think that, I need for Symfony Test Client something similiar to js xhr settings: withcredentals:true.
Symfony 3.1. I have action in rest api controller:
/**
* #Rest\GET("/get-stuff")
* #Rest\View
* #Security("is_granted('IS_AUTHENTICATED_FULLY')")
*/
public function GetStuffAction($userId)
{
// get userId from session
$userId = $this->getUser()->getId();
$em = $this->getDoctrine()->getManager();
$Stuff = $em->getRepository('MyApiBundle:Stuff')->findBy(['user' => $userId]);
return $this->viewGroup($Stuff, ['stuff']);
}
And it does work correct.
Now I want Test, so I have :
class TestCase extends WebTestCase
{
public function testIndex()
{
$this->client = self::createClient();
$this->storage = new MockFileSessionStorage(__dir__.'/../../../../app/cache/test/sessions');
$this->session = new Session($this->storage);
$this->logIn($this->getUser(), new Response());
$this->client->request('GET', '/get-stuff');
$content = $this->client->getResponse()->getContent();
$this->assertEquals({"message":"Full authentication is required to access this resource.","code":0},$content);
}
public function logIn(User $user, Response $response)
{
$this->session->start();
$this->cookie = new Cookie('MOCKSESSID', $this->storage->getId());
$this->cookieJar = new CookieJar();
$this->cookieJar->set($this->cookie);
$this->token = new UsernamePasswordToken($user, 'user', 'main', $user->getRoles());
$this->session->set('_security_main', serialize($this->token));
$this->getSecurityManager()->loginUser(
$this->container->getParameter('fos_user.firewall_name'),
$user,
$response
);
$this->session->save();
}
}
And test for it gives me message: "Full authentication is required to access this resource". After method logIn() I can read session data which are connected with logged user.
How can I make to be logged during this part to not receive message about authentication?:
$this->client->request('GET', '/get-stuff');
$content = $this->client->getResponse()->getContent();
More details:
1. My test class extends WebTestCase.
2. My user is overwritten for FosUserBundle.
I suppose that is something like in javascript:
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
but I don't know what.
Thank you in advance for helping solve my problem!
Ok, I was good way to do this:
public function logIn(User $user) {
$session = $this->session;
$firewallContext = 'secured_area';
$token = new UsernamePasswordToken($user, 'user', 'main', $user->getRoles());
$session->set('_security_'.$firewallContext, serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$this->client->getCookieJar()->set($cookie);
}
Nice to meet you.
I'm developping with Symfony 3.1.3 and I'm using the security system offered by the framework, no FOSUser neither Guard.
I have in my controller the typical function login:
public function loginAction(Request $request)
{
// This works
$authenticationUtils = $this->get('security.authentication_utils');
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render(
'security/loginForm_old.html.twig',
array(
// last username entered by the user
'last_username' => $lastUsername,
'error' => $error,
)
);
}
And I want to check if the user has activated his account. In the entity User I have the isActive attribute set to false by default and only with the link into the registration email is setted to true.
I have been searching for this issue without results and I'm sure this is something very common, everybody wants to check if the user's email is a good one.
Thanks.
Lets assume that you have an RegistrationController.php class where you store all code that manage about user's registration.
Create a function which sends email to user after registration:
public function sendConfirmationEmailMessage(User $user)
{
$confirmationToken = $user->getConfirmationToken();
$username = $user->getUsername();
$subject = 'Account activation';
$email = $user->getEmail();
$renderedTemplate = $this->templating->render('AppBundle:Emails:registration.html.twig', array(
'username' => $username,
'confirmationToken' => $confirmationToken
));
$message = \Swift_Message::newInstance()
->setSubject($subject)
->setFrom(MAILER_FROM)
->setReplyTo(MAILER_FROM)
->setTo($email)
->setBody($renderedTemplate, "text/html");
$this->mailer->send($message);
}
Create a route associated with function which takes an generated token as argument, then search user by that token and activate if user exist:
/**
* #Route("user/activate/{token}")
*/
public function confirmAction(Request $request, $token)
{
$em = $this->getDoctrine()->getManager();
$repository = $em->getRepository('AppBundle:User');
$user = $repository->findUserByConfirmationToken($token);
if (!$user)
{
throw $this->createNotFoundException('We couldn\'t find an account for that confirmation token');
}
$user->setConfirmationToken(null);
$user->setEnabled(true);
$em->persist($user);
$em->flush();
return $this->redirectToRoute('user_registration_confirmed');
}
Then when you have a function which actually registers the user you call the sendConfirmationEmailMessage as shown below:
public function registerAction(Request $request)
{
/* All the logic goes here: form validation, creating new user */
/* $user is created user */
sendConfirmationEmailMessage($user);
}
Anyway if isActive() function return false Symfony security system will prevent you from login. Your User entity should implement UserInterface.
In my Symfony2 project i'm using HwiOAuthbundle to login users with Salesforce users, and in my user provider i get the information about the user: email, nickname, password,... etc. And i wanna know if how can i get the role of the authenticated user directly from the response.
this my user provider code :
class UserProvider extends OAuthUserProvider {
public function loadUserByOAuthUserResponse(UserResponseInterface $response)
{
$username = $response->getUsername();
$email = $response->getEmail();
$nickname = $response->getNickname();
$realname = $response->getRealName();
//set data in session
$this->session->set('email', $email);
$this->session->set('nickname', $nickname);
$this->session->set('realname', $realname);
$result = $this->doctrine->getManager()->getRepository('EnvivioUserBundle:User')->findOneBy(array(
'username' => $username,
));
if (!count($result)) {
$user = new User();
$user->setUsername($username);
$user->setRealname($realname);
$user->setNickname($nickname);
$user->setEmail($email);
//Set some wild random pass since its irrelevant, this is Google login
$factory = $this->container->get('security.encoder_factory');
$encoder = $factory->getEncoder($user);
$password = $encoder->encodePassword(md5(uniqid()), $user->getSalt());
$user->setPassword($password);
$em = $this->doctrine->getManager();
$em->persist($user);
$em->flush();
} else {
$user = $result; /* return User */
}
//set id
$this->session->set('id', $user->getId());
return $this->loadUserByUsername($response->getUsername());
}
id don't know if i can get the roles frol $response like $response->getRoles()
usually it works as:
$token = new UsernamePasswordToken($user, null, $providerKey, $user->getRoles());
$this->container->get('security.context')->setToken($token);
so after you get username from a response, you get/create new user then you get get his roles as $user->getRoles();
do you have at security.yml imilar as
security:
providers:
main:
entity: { class: MyApp\UserBundle\Entity\User, property: username }
fos_userbundle:
id: fos_user.user_provider.username
if you use it with FosUserBundle
UPDATE:
I was little bit confused about "roles", and seems you are mixing notion "role" from symfony and "role" from external site. Unfortunately it won't work "from the box".
First you can look at UserResponseInterface https://github.com/hwi/HWIOAuthBundle/blob/master/OAuth/Response/UserResponseInterface.php and see that there is no any field associated with roles.
So I suppose you need make own custom solution by looking to "Salesforce" api and check is it possible to pass user role. Then if it is possible to pass you will need to extend Hwi to get passed roles from response
I'm using FOSUserBundle together with NnmMultiUserBundle.
When a user register, using my form_handler, the User entity is created, and should automatically login this new user.
I haven't been able to make that automatic login works.
My Controller:
public function registerAction()
{
$discriminator = $this->container->get('nmn_user_discriminator');
$discriminator->setClass('Travelyo\UserBundle\Entity\UserFrontend');
$form = $discriminator->getRegistrationForm();
//$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, array(User::ROLE_CUSTOMER));
if ($process) {
$user = $form->getData();
if ($confirmationEnabled) {
$this->container->get('session')->set('fos_user_send_confirmation_email/email', $user->getEmail());
$route = 'fos_frontend_registration_check_email';
} else {
$this->authenticateUser($user);
$route = 'fos_frontend_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('TravelyoUserBundle:Registration:user_frontend.form.html.' . $this->getEngine(), array('form' => $form->createView(),));
}
Athenticate method:
protected function authenticateUser(UserInterface $user)
{
try {
$this->container->get('fos_user.user_checker')->checkPostAuth($user);
} catch (AccountStatusException $e) {
// Don't authenticate locked, disabled or expired users
return;
}
$providerKey = $this->container->getParameter('fos_user.firewall_name');
$token = new UsernamePasswordToken($user, null, $providerKey, $user->getRoles());
$this->container->get('security.context')->setToken($token);
}
After calling athenticate the controller redirects to ConfirmedAction, where it tries to get the "logged in" user:
public function confirmedAction()
{
$user = $this->container->get('security.context')->getToken()->getUser();
if (!is_object($user) || !$user instanceof UserInterface) {
throw new AccessDeniedException('This user does not have access to this section.');
}
return $this->container->get('templating')->renderResponse('FOSUserBundle:Registration:confirmed.html.'.$this->getEngine(), array(
'user' => $user,
));
}
Error message:
Fatal error: Call to a member function getUser() on a non-object in /Data/Web/Local/travelyo/vendor/bundles/FOS/UserBundle/Controller/RegistrationController.php on line 115
If I then try to login using the login form it works fine (meaning that the creation of the user is working). But I can't understand why this token cannot be retrieved from security.context.
Security context is (as described in symfony 2 doc) not shared across firewall.
My registration form was in the "public firewall" => /register.
When authenticating the user (right after the user is registered), the token is saved for the public security.context.
I was then redirected to my actual "account" firewall => /account, and no token was found in the security.context as the token had never been defined for that firewall.
In order to solve it I simply moved my registration process under the same firewall, and it now works like a charm.
But I would still assume it should be possible to set a token for another security.context, if anyone know how it might be useful.
Thanks!
I'm working on a Symfony 2 application where the user must select a profile during the login process.
Users may have multiples profiles to work with and they only know their own profiles. So first, I need to prompt for username and password, if those are correct, I should not login the user, I need to prompt for profile witch user will use during the session.
So, I show a form with a username and password field, and send it using an Ajax request, that request responds with the profile list if username and password are correct or an error code otherwise. Finally the user logs into the system using username, password and profile.
The problem is that I don't know how to check if authentication data is correct (using all my authentication managers, users providers, etc) to accomplish this intermediate step (prompts for profile) without in fact logging the user.
Can anyone help me with this?
A problem with #Jordon's code is that it will not work with hashing algorithms that generate different hashes for the same password (such as bcrypt that stories internally its parameters, both the number of iterations and the salt). It is more correct to use isPasswordValid of the Encoder for comparing passwords.
Here is the improved code that works fine with bcrypt:
$username = trim($this->getRequest()->query->get('username'));
$password = trim($this->getRequest()->query->get('password'));
$em = $this->get('doctrine')->getManager();
$query = $em->createQuery("SELECT u FROM \Some\Bundle\Entity\User u WHERE u.username = :username");
$query->setParameter('username', $username);
$user = $query->getOneOrNullResult();
if ($user) {
// Get the encoder for the users password
$encoder_service = $this->get('security.encoder_factory');
$encoder = $encoder_service->getEncoder($user);
// Note the difference
if ($encoder->isPasswordValid($user->getPassword(), $password, $user->getSalt())) {
// Get profile list
} else {
// Password bad
}
} else {
// Username bad
}
You could do something like this to retrieve the user and manually test the password -
$username = trim($this->getRequest()->query->get('username'));
$password = trim($this->getRequest()->query->get('password'));
$em = $this->get('doctrine')->getEntityManager();
$query = $em->createQuery("SELECT u FROM \Some\Bundle\Entity\User u WHERE u.username = :username");
$query->setParameter('username', $username);
$user = $query->getOneOrNullResult();
if ($user) {
// Get the encoder for the users password
$encoder_service = $this->get('security.encoder_factory');
$encoder = $encoder_service->getEncoder($user);
$encoded_pass = $encoder->encodePassword($password, $user->getSalt());
if ($user->getPassword() == $encoded_pass) {
// Get profile list
} else {
// Password bad
}
} else {
// Username bad
}
Once you've got your profile back from the client, you can perform the login manually in the AJAX server controller easily enough too -
// Get the security firewall name, login
$providerKey = $this->container->getParameter('fos_user.firewall_name');
$token = new UsernamePasswordToken($user, $password, $providerKey, $user->getRoles());
$this->get("security.context")->setToken($token);
// Fire the login event
$event = new InteractiveLoginEvent($this->getRequest(), $token);
$this->get("event_dispatcher")->dispatch("security.interactive_login", $event);
Might need a few use lines -
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
I used the code from #Jordon and #Potor Polak to wrap the logic in a standalone service that used the current access token to validate the password. Maybe some needs this:
services.yml:
app.validator.manual_password:
class: AppBundle\Service\ManualPasswordValidator
arguments:
- '#security.token_storage'
- '#security.encoder_factory'
ManualPasswordValidator.php:
<?php
namespace AppBundle\Service;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Encoder\EncoderFactory;
/**
* Class ManualPasswordValidator
*
* #package AppBundle\Service
*/
class ManualPasswordValidator
{
/**
* #var EncoderFactory
*/
protected $encoderFactory;
/**
* #var TokenStorage
*/
protected $tokenStorage;
/**
* ManualPasswordValidator constructor.
*
* #param EncoderFactory $encoderFactory
* #param TokenStorage $tokenStorage
*/
public function __construct(TokenStorage $tokenStorage, EncoderFactory $encoderFactory)
{
$this->encoderFactory = $encoderFactory;
$this->tokenStorage = $tokenStorage;
}
/**
* #param $password
* #return bool
*/
public function passwordIsValidForCurrentUser($password)
{
$token = $this->tokenStorage->getToken();
if ($token) {
$user = $token->getUser();
if ($user) {
$encoder = $this->encoderFactory->getEncoder($user);
if ($encoder->isPasswordValid($user->getPassword(), $password, $user->getSalt())) {
return true;
}
}
}
return false;
}
}
After this you can inject the ManualPasswordValidator wherever you want and use it like:
$password = $request->get('password');
$passwordIsValid = $this->manualPasswordValidator->passwordIsValidForCurrentUser($password);
The only way I could authenticate my users on a controller is by making a subrequest and then redirecting. Here is my code, I'm using silex but you can easily adapt it to symfony2:
$subRequest = Request::create($app['url_generator']->generate('login_check'), 'POST', array('_username' => $email, '_password' => $password, $request->cookies->all(), array(), $request->server->all());
$response = $app->handle($subRequest, HttpKernelInterface::MASTER_REQUEST, false);
return $app->redirect($app['url_generator']->generate('curriculos.editar'));
In Symfony 4, the usage of the UserPasswordEncoderInterface is recommended in Controllers. Simply add a UserPasswordEncoderInterface as a parameter to the function in which you want to check the password and then add the code below.
public function changePasswordAction($old, $new, UserPasswordEncoderInterface $enc) {
// Fetch logged in user object, can also be done differently.
$auth_checker = $this->get('security.authorization_checker');
$token = $this->get('security.token_storage')->getToken();
$user = $token->getUser();
// Check for valid password
$valid = $encoder->isPasswordValid($user, $old);
// Do something, e.g. change the Password
if($valid)
$user->setPassword($encoder->encodePassword($user, $new));
}
Symfony 5.4
Password validation can be done using UserPasswordHasherInterface
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
class AuthenticaitonServices
{
public function __construct(UserPasswordHasherInterface $hasher)
{
$this->hasher = $hasher;
}
public function validate($request)
{
$form = [
"username" => $request->request->get("_username"),
"password" => $request->request->get("_password")
];
if(!$this->hasher->isPasswordValid($user, $form['password']))
{
// Incorrect Password
} else {
// Correct Password
}
isPasswordValid returns a bool response
If anyone checking solution for password validation in Symfony 5.4.
Above code is for validating password posted from a login form.
Hope this is helpful.