Manual authentication check Symfony 2 - symfony

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.

Related

Symfony - Edit User Entity without changing password

I'm running Symfony 3.4.14 and I developed my own User Bundle, I have a very bad experience with FOS then I don't want to use anymore. My goal is to let the admin users create/edit/remove users, I mean other users account.
I made :
the User entity
the Login form
the Registration form
.. and I'm stuck with the Update form. I want to let the admins edit a user without editing the password, but to give them the opportunity to do it if needed. Below is my EditUserAction in controller :
<?php
/**
* #Route("/admin/users/edit/{id}", requirements={"id" = "\d+"}, name="admin_users_edit")
* #Template("#Core/admin/users_edit.html.twig")
* #Security("has_role('ROLE_ADMIN')")
*/
public function EditUserAction($id, Request $request, UserPasswordEncoderInterface $passwordEncoder)
{
$user = $this->getDoctrine()->getRepository('CoreBundle:User')->findOneBy([ 'id'=>$id, 'deleted' => 0 ]);
if ( $user )
{
$old_password = $user->getPassword();
$form = $this->createForm(UserType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid())
{
// If admin changed the user password
if ( $user->getPlainPassword() )
{
$password = $passwordEncoder->encodePassword($user, $user->getPlainPassword());
$user->setPassword($password);
}
// If admin didn't change the user password, we persist the old one
else
{
$user->setPassword($old_password);
}
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($user);
$entityManager->flush();
}
return array('form' => $form->createView());
}
return $this->redirectToRoute('admin_users');
}
Case 1 (when admin choose to change the user password) works well, but the other case (when admin don't want to change the password) fails. They are no way to let the 2 password inputs empty. I can't get rid of this validation error in the debug toolbar :
Path: data.plainPassword
Message: This value should not be blank
In order to avoid this error, as you can see in my controller above, I try to keep the old one (may not be a best practice, I know).
In your form / entity (where you defined validations) you should write either your own constraint (documentation) or validation callback (documentation). In there you can check - If value is null, don't validate, if not null run your validations.
You need to have two separate methods for both.
public function changePasswordAction(Request $request)
{
// your code for changing password.
}
/**
* #Route("/admin/users/edit/{id}", requirements={"id" = "\d+"}, name="admin_users_edit")
* #Template("#Core/admin/users_edit.html.twig")
* #Security("has_role('ROLE_ADMIN')")
*/
public function editUserAction(User $user = null, Request $request, UserPasswordEncoderInterface $passwordEncoder)
{
//you can directly give your User Entity reference in parameters and you dont need to write an extra query to find user.
if ( $user === null){
//return user not found
}
else if($user->isDeleted() === false)
{
$form = $this->createForm(UserType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid())
{
//your code
}
// your code
}
return $this->redirectToRoute('admin_users');
}

Symfony 3: How to check LDAP inside Authenticator checkCredentials

I need to authenticate a user against an LDAP server with custom logic.
I'm implementing a custom authentication system in Symfony 3.3, and built a custom authenticator named LoginFormAuthenticator that extends AbstractFormLoginAuthenticator, as per:
http://symfony.com/doc/current/security/guard_authentication.html
I need to check the username against a User entity in the database, then, depending on the type of user, either auth against a bcrypt password stored in the database, or against an external LDAP server.
Inside of the checkCredentials method, I can validate the password stored in the database successfully with:
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator {
...
public function checkCredentials($credentials, UserInterface $user)
{
...
// check password if the user is database user
if ($user->getApp() == 'DB') {
if ($this->passwordEncoder->isPasswordValid($user, $password)) {
return true;
}
}
// check LDAP server if LDAP user
if ($this->getApp() == 'LDAP') {
if ($this->unknownLdapService->check($user, $password)
{
return true;
}
...
I'm not clear on the correct way to check the username and password against the LDAP server with native symfony functionality.
If I change my config to use form_login_ldap (and not my new authenticator), it does in fact successfully auth against LDAP, although where it makes the call is obfuscated to me.
What service or class I should be using to query LDAP in place of unknownLdapService above?
The solution I ended up using here was that I first injected the existing Symfony ldap service into the constructor of my method. The ldap service is configured in services.yml the same way the Symfony docs configure it for the form_login_ldap provider.
/**
* LoginFormAuthenticator constructor.
* #param FormFactoryInterface $formFactory
* #param EntityManager $em
* #param RouterInterface $router
* #param SecureUserPasswordEncoder $passwordEncoder
* #param Ldap $ldap
*/
public function __construct(..., Ldap $ldap, ... )
{
...
$this->ldap = $ldap;
}
Then inside of my checkCredentials method, I called the ldap bind method:
public function checkCredentials($credentials, $userInterface $user)
...
$password = $credentials['_password'];
$login_format = 'DOMAIN\%s'; // this is the expected format in my case
$login_username = sprintf($login_format, $user);
...
try {
// try to bind with the username and provided password
$this->ldap->bind($login_username, $password);
} catch (\Symfony\Component\Ldap\Exception\ConnectionException $e) {
//return false;
throw new CustomUserMessageAuthenticationException('The submitted LDAP password is invalid.');
};
return true;
};
This works, and if the ldap auth fails, it throws the appropriate exception.
You can could use your own LDAP service: you just need to call ldap_bind. (it can allows you to do more ldap checks or to mock it aswell)
You could alose use the Symfony provider: vendor/symfony/symfony/src/Symfony/Component/Security/Core/Authentication/Provider/LdapBindAuthenticationProvider.php
namespace Symfony\Component\Security\Core\Authentication\Provider;
class LdapBindAuthenticationProvider extends UserAuthenticationProvider
{
private $userProvider;
private $ldap;
private $dnString;
public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, $providerKey, LdapClientInterface $ldap, $dnString = '{username}', $hideUserNotFoundExceptions = true)
{
parent::__construct($userChecker, $providerKey, $hideUserNotFoundExceptions);
$this->userProvider = $userProvider;
$this->ldap = $ldap;
$this->dnString = $dnString;
}
/**
* {#inheritdoc}
*/
protected function retrieveUser($username, UsernamePasswordToken $token)
{
if ('NONE_PROVIDED' === $username) {
throw new UsernameNotFoundException('Username can not be null');
}
return $this->userProvider->loadUserByUsername($username);
}
/**
* {#inheritdoc}
*/
protected function checkAuthentication(UserInterface $user, UsernamePasswordToken $token)
{
$username = $token->getUsername();
$password = $token->getCredentials();
if ('' === $password) {
throw new BadCredentialsException('The presented password must not be empty.');
}
try {
$username = $this->ldap->escape($username, '', LDAP_ESCAPE_DN);
$dn = str_replace('{username}', $username, $this->dnString);
$this->ldap->bind($dn, $password);
} catch (ConnectionException $e) {
throw new BadCredentialsException('The presented password is invalid.');
}
}
}

symfony 3.1 registration email no FOSUser

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.

FOSUserBundle - PHPUnit - Mock a user

I am using Symfony with the FOSUserBundle and now I like to test some things like:
Doctrine lifecycle
Controller behind firewall
For those tests I need to be a specific user or at least in a user group.
How do I mock a user session so that ...
The lifecycle field like "createdAt" will use the logged in user
The Controller act like some mocked user is logged in
Example:
class FooTest extends ... {
function setUp() {
$user = $this->getMock('User', ['getId', 'getName']);
$someWhereGlobal->user = $user;
// after this you should be logged in as a mocked user
// all operations should run using this user.
}
}
You can do this with LiipFunctionalTestBundle. Once you have installed and configured the Bundle, creating and user and log in in tests is easy.
Create a fixture for your user
This creates a user which will be loaded during tests:
<?php
// Filename: DataFixtures/ORM/LoadUserData.php
namespace Acme\MyBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Acme\MyBundle\Entity\User;
class LoadUserData extends AbstractFixture implements FixtureInterface
{
public function load(ObjectManager $manager)
{
$user = new User();
$user
->setId(1)
->setName('foo bar')
->setEmail('foo#bar.com')
->setPassword('12341234')
->setAlgorithm('plaintext')
->setEnabled(true)
->setConfirmationToken(null)
;
$manager->persist($user);
$manager->flush();
// Create a reference for this user.
$this->addReference('user', $user);
}
}
If you want to use groups of users, you can see the official documentation.
Log in as this user in your test
As explained in LiipFunctionalTestBundle's documentation, here is how to load the user in the database and log in as this user:
/**
* Log in as the user defined in the Data Fixture.
*/
public function testWithUserLoggedIn()
{
$fixtures = $this->loadFixtures(array(
'Acme\MyBundle\DataFixtures\ORM\LoadUserData',
));
$repository = $fixtures->getReferenceRepository();
// Get the user from its reference.
$user = $repository->getReference('user')
// You can perform operations on this user.
// ...
// And perform functional tests:
// Create a new Client which will be logged in.
$this->loginAs($user, 'YOUR_FIREWALL_NAME');
$this->client = static::makeClient();
// The user is logged in: do whatever you want.
$path = '/';
$crawler = $this->client->request('GET', $path);
}
What I would do in this case is to create a CustomWebTestCase which extends the Symfony WebTestCase. In the class I would create a method which does the authentication for me.
Here is an example code:
namespace Company\MyBundle\Classes;
use Symfony\Bundle\FrameworkBundle\Client;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\BrowserKit\Cookie;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Security\Core\User\User;
abstract class CustomWebTestCase extends WebTestCase
{
/**
* #param array|null $roles
* #return \Symfony\Bundle\FrameworkBundle\Client
*/
protected static function createAuthenticatedClient(array $roles = null) {
// Assign default user roles if no roles have been passed.
if($roles == null) {
$role = new Role('ROLE_SUPER_ADMIN');
$roles = array($role);
} else {
$tmpRoles = array();
foreach($roles as $role)
{
$role = new Role($role, $role);
$tmpRoles[] = $role;
}
$roles = $tmpRoles;
}
$user = new User('test_super_admin', 'passwd', $roles);
return self::createAuthentication(static::createClient(), $user);
}
private static function createAuthentication(Client $client, User $user) {
// Read below regarding config_test.yml!
$session = $client->getContainer()->get('session');
// Authenticate
$firewall = 'user_area'; // This MUST MATCH the name in your security.firewalls.->user_area<-
$token = new UsernamePasswordToken($user, null, $firewall, $user->getRoles());
$session->set('_security_'.$firewall, serialize($token));
$session->save();
// Save authentication
$cookie = new Cookie($session->getName(), $session->getId());
$client->getCookieJar()->set($cookie);
return $client;
}
}
The code above will directly create a valid user session and will skip the firewall entirely. Therefore you can create whatever $user you want and it will still be valid. The important part of the code is located in the method createAuthentication. This is what does the authentication magic.
One more thing worth mentioning - make sure you have set framework.session.storage_id to session.storage.mock_file in your config_test.yml so that Symfony will automatically mock sessions instead of you having to deal with that in each test case:
framework:
session:
storage_id: session.storage.mock_file
Now in your test case you would simply extend MyWebTestCase and call the createAuthenticatedClient() method:
class MyTest extends CustomWebTestCase {
public function testSomething() {
//Create authoried and unauthorized clients.
$authenticatedClient = self::createAuthenticatedClient(array("ROLE_SUPER_ADMIN"));
$unauthorizedClient = self::createAuthenticatedClient(array("ROLE_INSUFFICIENT_PERMISSIONS"));
// Check if the page behaves properly when the user doesn't have necessary role(s).
$unauthorizedClient->request('GET', '/secured-page');
$response = $unauthorizedClient->getResponse();
$this->assertFalse($response->isSuccessful());
$this->assertEquals(403, $response->getStatusCode(), "This request should have failed!");
// Check if the page behaves properly when the user HAS the necessary role(s)
$authenticatedClient->request('GET', '/secured-page');
$response = $authenticatedClient->getResponse();
$this->assertTrue($response->isSuccessful());
$this->assertEquals(200, $response->getStatusCode(), "This request should be working!");
}
}
You can see an example in the Symfony official documentation as well.
You can easily do that with LiipFunctionalTestBundle which authorize you lot of shortcut for create Unit Test.
If already you have a form user for create or edit you can use this for your test unit workflow user in your application :
use the makeClient method for logging test
$credentials = array(
'username' => 'a valid username',
'password' => 'a valid password'
);
$client = static::makeClient($credentials);
use your form for test your creation
$crawler = $client->request('GET', '/profile');
$form = $crawler->selectButton('adding')->form();
$form['fos_user_profile_form[firstName]'] = 'Toto';
$form['fos_user_profile_form[lastName]'] = 'Tata';
$form['fos_user_profile_form[username]'] = 'dfgdgdgdgf';
$form['fos_user_profile_form[email]'] = 'testfgdf#grgreger.fr';
$form['fos_user_profile_form[current_password]'] = 'gfgfgdgpk5dfgddf';
testing "createdAt" with just call findOneBy in repository user like this
$user = $this->getObjectManager()
->getRepository('AcmeSecurityBundle:User')
->findOneBy(array('username' => 'testCreateUserUsername'));
$this->assertTrue($user->getCreatedAt() == now());

symfony2 form change password

I'm trying to make a modified form of an employee but I have a problem with the password field.
When I'm on the form if not insert anything in the password that is modified with a blank password. This is not correct and I want that if the user does nothing with the password does not get the password blank.
The problem here is that you're using one field to hold two different things: a plain password and its hash. So, instead, split them into $plainPassword and $password and update the $password field only if the $plainPassword is not empty:
if ($plainPassword = $user->getPlainPassword()) {
$encoder = $this->get('security.encoder_factory')->getEncoder($user);
$password = $encoder->encodePassword($plainPassword, $user->getSalt());
$user->setPassword($password);
}
Just do not update password field if it's not submitted(modified).
I think that's all you need
/**
* #ORM\Entity
**/
class User
{
// your setters/getters
public function setPassword($password)
{
if (null === $password) {
return;
}
$this->password = $password;
}
}

Resources