I have to get the user's plain password for LDAP authentification and then retrieve LDAP user informations in the Active Directory with Symfony2.
/**
* #Route("/infos-profil/{id}", name="infos_profil")
* #Template()
*/
public function infosProfilAction($id)
{
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('MyUserBundle:LdapUser')->find($id); // Find User Entity
if (!$user) {
throw $this->createNotFoundException('Unable to find LdapUser entity.');
}
$login = $user->getUsername(); // Login
$pass = $user->getPlainPassword(); // Password
$ds = ldap_connect("12.34.56.789"); // Domain connexion
if ($ds) {
$r = ldap_bind($ds, $login, $pass); // LDAP User connexion
if ($r) {
$filter = "(&(objectClass=user)(samaccountname=".$login.")(cn=*))";
$sr=ldap_search($ds, "ou=DOMAIN, ou=Test, ou=Users, dc=ats, dc=lan", $filter);
$info = ldap_get_entries($ds, $sr); // Retrieve user's Active Direcory informations
}
}
return array(
'user' => $user,
'info' => $info,
}
But it doesn't work, $pass is empty. When I put the plain password manually in the ldap_bind() function it works perfectly, I just have to get the plain password ! ...
Is it possible ?
It isn't possible to retrieve plain password from database for obvious security reasons.
For your problem, you should create a custom Authentication Provider, following this tutorial : https://symfony.com/doc/4.4/security/custom_authentication_provider.html
This way, your provider will get the plain password from the login form and you will be able to send it to your LDAP server.
You don't need the user's password to retrieve information about them from active directory. Once they are authenticated simply look them up via LDAP using their username, with either an anonymous connection or failing that, a known privileged account.
Related
I am running a Symfony 2.8 based web app using FOSUserBundle to manage users. Creating new users with a web form is absolutely no problem.
Now I would like to add a feature to create new users with a REST api. Of course submitting username, password, email, etc. to a controller is no big deal:
public function registerAction(Request $request) {
$requestJson = json_decode($request->getContent(), true);
$username = $requestJson[JSON_KEY_USERNAME];
$email = $requestJson[JSON_KEY_MAIL];
$password = $requestJson[JSON_KEY_PASSWORD];
...
$this->registerUser($username, $email, $password);
...
}
private function registerUser($username, $email, $password, $locale, $timezone, $currency) {
$userManager = $this->get('fos_user.user_manager');
$emailExist = $userManager->findUserByEmail($email);
$userNameExists = $userManager->findUserByUsername($username);
if ($emailExist || $userNameExists)
return false;
$user = $userManager->createUser();
$user->setUsername($username);
$user->setEmail($email);
$user->setPlainPassword($password);
...
$user->setLocked(0);
$user->setEnabled(0);
$userManager->updateUser($user);
return true;
}
However, this performs no validation at all. If for example the username is empty an NotNullConstraintViolationException is thrown when persisting the user object.
Of course I could manually re-implement the same validation process which is used by the RegistrationForm (username not empty, not taken, no invalid characters, password min length, e-mail format, etc.) and pass back the same error messages but this would mean to reinvent the wheel.
Is it somehow possible to run the exact same validation which is used by the RegistrationForm?
Symfony validator can work independently. In a controller you can use validator service like this:
$violations = $this->get('validator')->validate($user, null, ['your_groups_here']);
// Or inject Symfony\Component\Validator\Validator\ValidatorInterface into a service.
It will return a ConstraintViolationListInterface, you can loop trough this object.
You can check FOSUserBundle validation groups here: https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/config/validation.xml
I am using symfony 3.3 with fos_userbundle and fr3d_ldapbundle to authentichate my users trough LDAP.
The login works correctly if a try to use the standard login form generated.
But what I need to do is a manual(programmatically) login.
What is the best way to do it with fr3d_ldapbundle?
Sorry guys, I give you more details:
I tried to follow this guide: https://ourcodeworld.com/articles/read/459/how-to-authenticate-login-manually-an-user-in-a-controller-with-or-without-fosuserbundle-on-symfony-3
If I try to use the fos_user.user_manager the login works correctly, but using the fr3d_ldap.ldap_manager it doesn't work. (the isPasswordValid function return me "Username or Password not valid")
The user is retrieved correctly from LDAP server, but the "password" field is empty if I print the $user object. Using the standard login form the authentication works correctly and the username is stored in my fos user bundle table with the password field empty. Could be this my problem?
Also the $salt is empty.
This is my code of LoginAction:
public function loginAction(Request $request)
{
// This data is most likely to be retrieven from the Request object (from Form)
// But to make it easy to understand ...
$_username = "user";
$_password = "password";
// Retrieve the security encoder of symfony
$factory = $this->get('security.encoder_factory');
/// Start retrieve user
// Let's retrieve the user by its username:
/*
// If you are using FOSUserBundle:
$user_manager = $this->get('fos_user.user_manager');
$user = $user_manager->findUserByUsername($_username);
//Or by yourself
$user = $this->getDoctrine()->getManager()->getRepository("ApiBundle:User")
->findOneBy(array('username' => $_username));
*/
//Using fr3d/ldap-bundle
$user_manager = $this->get('fr3d_ldap.ldap_manager');
$user = $user_manager->findUserByUsername($_username);
//print_r($user);die();
/// End Retrieve user
// Check if the user exists !
if(!$user){
return new Response(
'Username doesnt exists',
Response::HTTP_UNAUTHORIZED,
array('Content-type' => 'application/json')
);
}
/// Start verification
$encoder = $factory->getEncoder($user);
$salt = $user->getSalt();
if(!$encoder->isPasswordValid($user->getPassword(), $_password, $salt)) {
return new Response(
'Username or Password not valid.',
Response::HTTP_UNAUTHORIZED,
array('Content-type' => 'application/json')
);
}
/// End Verification
// The password matches ! then proceed to set the user in session
//Handle getting or creating the user entity likely with a posted form
// The third parameter "main" can change according to the name of your firewall in security.yml
$token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
$this->get('security.token_storage')->setToken($token);
// If the firewall name is not main, then the set value would be instead:
// $this->get('session')->set('_security_XXXFIREWALLNAMEXXX', serialize($token));
$this->get('session')->set('_security_main', serialize($token));
// Fire the login event manually
$event = new InteractiveLoginEvent($request, $token);
$this->get("event_dispatcher")->dispatch("security.interactive_login", $event);
/*
* Now the user is authenticated !!!!
* Do what you need to do now, like render a view, redirect to route etc.
*/
return new Response(
'Welcome '. $user->getUsername(),
Response::HTTP_OK,
array('Content-type' => 'application/json')
);
}
Someone is able to help me?
Thank you.
#Jon Doe, you certainly cannot get the password information on user object while doing ldap authentication.
LDAP uses bind function which takes username and password information, tries to authenticate and return success or failure.
While using FR3DLdapBundle, this should be done inside Authentication Provider. Check LdapAuthenticationProvider.php file for following code.
if (!$this->ldapManager->bind($currentUser, $presentedPassword)) {
throw new BadCredentialsException('The credentials were changed from another session.');
}
In your controller - LoginAction you shouldn't be doing any authentication.
Just check for any authentication error and check for any access specific role if you need to have role based access as following example.
// get the login error if there is one
$error = $this->get('security.authentication_utils')->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $this->get('security.authentication_utils')->getLastUsername();
//if you need to check for the user role.
$roleGranted = $this->get('security.authorization_checker')->isGranted('ROLE_ADMIN');
I created an action that handles redirection to respected areas depending on user's type and ROLE (trainee, company or university let's say). If user is not logged in, it redirects to homepage (anonymous area), and if logged in - to their profile pages. I use it in homepage and many other cases, for example, as sign up and login success redirection.
public function authorizationAction(Request $request)
{
$user = $this->getUser();
$authorizationChecker = $this->get('security.authorization_checker');
$request->cookies->remove('action');
if ($user) {
if ($user->getType() == 'company' && $authorizationChecker->isGranted('ROLE_COMPANY_GUEST')) {
/** #var Company $company */
$company = $user->getCompany();
if ($user->getState() == 'active' && $company->getState() == 'active') {
$response = $this->redirectToRoute('company');
} else {
$response = $this->redirectToRoute('company_profile');
}
} elseif ($user->getType() == 'university' && $authorizationChecker->isGranted('ROLE_UNIVERSITY_GUEST')) {
/** #var University $university */
$university = $user->getUniversity();
if ($user->getState() == 'active' && $university->getState() == 'active') {
$response = $this->redirectToRoute('university');
} else {
$response = $this->redirectToRoute('university_profile');
}
} elseif ($user->getType() == 'trainee' && $authorizationChecker->isGranted('ROLE_TRAINEE')) {
/** #var Trainee $trainee */
$trainee = $user->getTrainee();
if ($user->getState() == 'active' && $trainee->getState() == 'active') {
$response = $this->redirectToRoute('trainee');
} else {
$response = $this->redirectToRoute('trainee_profile');
}
} else {
$response = $this->redirectToRoute('homepage');
}
} else {
$response = $this->redirectToRoute('homepage');
}
return $response;
}
I have seen some examples recommending using symfony events (kernel.request) to handle it minimizing controller code. But in this case I will not be able to use this action as sign up and login success path.
I am not using FOS, because of lack of user customization. I prefer handling User my self.
Is my approach wrong and something to be worried about?
Some things that I am concerned:
Redirection count:
For example. I am logged in user and I go to homepage and am redirected to my action where I am checked whether I am logged in or not and depending on user type I am redirected to respected page.
public function indexAction(Request $request)
{
if ($this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) {
return $this->redirectToRoute('authorization');
}
// ...
}
Slowing website:
In the future I will be using this action in more pages and website will definatelly slow down everytime executing same code on each page.
I am not using FOS, because of lack of user customization. I prefer handling User my self.
Blockquote
You really shouldn't reinvent the wheel here but even if you want to your assessment that extending FOS is hard is wrong, take a look at this gist how many of us Symfony Devs extend FOS to enable social login via HWIOauthBundle. Extending it for other purposes is equally trivial.
Update Since OP's Comments Below
....
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
use AppBundle\Form\Type\LoginType;
....
....
$login_form = $this->createForm(LoginType::class);
$login_form->handleRequest($request);
if (!$login_form->isValid()){
//throw error
}
$email = strip_tags($login_form->get('email')->getData());
$password = $login_form->get('password')->getData();
// Call the User Manager
$userManager = $this->get('fos_user.user_manager');
$user = $userManager->findUserByUsernameOrEmail($email);
if(!$user){
//throw error : User With Submitted Credentials Does Not Exist
}
$user_password = $user->getPassword();
$encoded_password = $this->get('security.password_encoder')->encodePassword($user, $password);
if($encoded_password != $user_password){
//throw error : Wrong Password, Please Check Your Details & Re-submit
}
// Successful query of username/email and password now we log in user
// Create new token
$token = new UsernamePasswordToken($user, $user->getPassword(), 'main', $user->getRoles());
// Login New User
$tokenStorage = $this->get('security.token_storage');
$tokenStorage->setToken($token);
// User now logged in
$user_id = $user->getId();
....
Redirection count:
In your case each redirection would cause at least 1 database query to verify user session, so yes many them could lead to a bad use and server experience. If you use FOSUserBundle with Doctrine's Second Level Cache, you can avoid this query everytime you call $this->getUser();
Slowing website:
This very small bit of code with hardly be you bottleneck at scale but for arguments sake let's assume it is. I would solve this problem by introducing client side sessions. A framework like Angular or my personal favorite Ember would allow you to store user sessions on the client so as not to even bother going back to your server all the time for such a menial task as checking authentication or roles. I would still advise you to keep some server side code for those cheeky users who want to poke holes in your code.
The Access Control section in the Symfony documentation might offer easier solutions to restrict access. In my time using Symfony I have always been able to use it for redirection and access control.
I try to integrate the bundle samlspbundle on a project running with fosuserbundle.
I actually received information from my idp which send me the saml with the email address of the user.
What i'm trying to do is load the user from my table fosuser and then authenticate it.
this is the method i am in my model SamlToUser :
private function loadUserByTargetedID($targetedID)
{
$repository = $this->container->get('doctrine')->getManager()->getRepository('MCCAppBDDBundle:User');
$user = $repository->findOneBy(
array('email' => $targetedID)
);
if ($user) {
$userManager = $this->container->get('fos_user.user_manager');
$url = $this->container->get('router')->generate('homepage');
$response = new RedirectResponse($url);
$this->container->get('fos_user.security.login_manager')->loginUser(
$this->container->getParameter('fos_user.firewall_name'),
$user,
null
);
$userManager->updateUser($user);
return $user;
}
throw new \Symfony\Component\Security\Core\Exception\UsernameNotFoundException();
}
After that i have this error : PHP Warning: session_regenerate_id(): Cannot regenerate session id - headers already sent
I'm not sure is the right thing to do.
If you need other detail, i can give you.
Thanks to help.
We are currently using Symfony 2 and FOS/UserBundle for user authentication.
I want to check if a given username/password combination is valid without logging in. This is because another person is currently logged in but for example needs to do a specific action which needs to be done by someone with a higher clearance.
Basically I want another user to do a different controller action besides the person that is currently logged.
If there's a better way of doing this please let me know
How can validate username and password from controller #696
public function validUser($username, $password){
$user = new Users(); //entity
$factory = $this->get('security.encoder_factory');
$encoder = $factory->getEncoder($user);
$bool = $encoder->isPasswordValid($user->getPassword(),$password,$user->getSalt());
}
Symfony 5.4
Password validation can be done using UserPasswordHasherInterface
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
class AuthenticaitonServices
{
public function __construct(UserPasswordHasherInterface $passwordHasher)
{
$this->hasher = $passwordHasher;
}
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
Bundles are not longer available in newer Symfony versions. Above code is for validating password posted from a login form
Hope this is helpful.