Using the FOSUserbundle, I want to achieve the following:
1) User submits a POST with "username" and "password" parameters. Password is in plaintext.
2) In a controller, I parse the parameters as usual:
/**
* #Route("/api/token",name="api_token")
*/
public function tokenAction(Request $request) {
$username = $request->get('username');
$password = $request->get('password');
// ...
}
3) Finally, if the credentials match, I return something.
Note that I want to do this WITHOUT modifying the session, i.e. without actually authenticating the user and setting a token. I only want to check if the credentials match.
UPDATE
The accepted answer works, but only under the assumption that you have an active session. If you want to solve the case where you simply expose a REST layer or the like (which was my usecase), you can do the following, assuming your usernames are unique:
/**
* #Route("/api/token",name="api_token", options={"expose"=true})
*/
public function getTokenAction(Request $request) {
$username = $request->get('username');
$password = $request->get('password');
$user = $this->getDoctrine()
->getRepository('YourUserClass')
->findOneBy(["username" => $username]);
$encoder = $this->get('security.encoder_factory')->getEncoder($user);
$isValidPassword = $encoder->isPasswordValid($user->getPassword(),
$password,
$user->getSalt());
if ($isValidPassword) {
// Handle success
} else {
// Handle error
}
}
You should use an authentication service to do this, writing all code in controller is not a best practice. Anyway, to your answer, you can use this:
/**
* #Route("/api/token",name="api_token")
*/
public function tokenAction(Request $request) {
$username = $request->get('username');
$password = $request->get('password');
// fetch your user using user name
$user = ...
//If your controller is extended from Symfony\Bundle\FrameworkBundle\Controller\Controller
$encoder = $this->get('security.encoder_factory')->getEncoder($user);
//If you are extending from other ContainerAware Controller you may have to do
//$this->container->get('security.encoder_factory')->getEncoder($user)
//Check your user
$isValidPassword = $encoder->isPasswordValid(
$user->getPassword(),
$password,
$user->getSalt()
);
if ($isValidPassword) {
//.... do your valid stuff
}else{
//... Do your in valid stuff
}
}
Related
I want to get a user by their username and password using Doctrine (findOneBy() method), on Symfony4.
security:
encoders:
App\Entity\User:
algorithm: argon2i
$username= $request->request->get('username');
$password = $request->request->get('password');
$user= $this->em->getRepository(User::class)->findOneBy([
'username'=>$username,
'passsword'=>$password
]);
return new Response($user);
The correct way to do this is to fetch the user by their username, and then verify the password using Symfony's password encoder.
E.g.:
public function __construct(UserPasswordEncoderInterface $encoder) {
$this->encoder = $encoder;
}
public function yourAction(Request $request) {
$username = $request->request->get('username');
$password = $request->request->get('password');
$user = $this->em->getRepository(User::class)->findOneBy(['username'=>$username]);
if ($user === null) {
// user not found
// throw exception or return error or however you handle it
}
if (! $this->encoder->isPasswordValid($user, $password)) {
// invalid password
// throw exception, or return error, or however you handle it
}
//...
}
Remember that you would need to add the use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; statement at the top of your script, or you'll get a "class not found" error.
technically, you can find a user if you know the username and the passwort (hash).
However, since the hash usually is salted with a random salt, you can't find a user by username and plain text password.
The clean approach here is to fetch the user by username and check the password (via password_verify or the symfony version (or see yivi's answer)).
I think to pass multiple criteria, you have to use the "findBy" method
$username= $request->request->get('username');
$password = $request->request->get('password');
$user= $this->em->getRepository(User::class)->findBy(
[
'username' => $username,
'password'=> $password
],
[],
1
);
return new Response($user);
You can find detail explanation of doctrine methods here
You could create a new method inside your repository (App/Repository/UserRepository.php)
where you can give username and password too and send back the result.
public function getUserByUsernameAndPassword(string $username, string $password)
{
$qb = $this->createQueryBuilder('u')
->andWhere('u.username = :username')
->andWhere('u.password LIKE :password')
->setParameter('username', $username )
->setParameter('password', $password )
->getQuery();
return $qb->getResult();
}
We are working in a login form, using simfony and a REST Webservice.
We have been searching in this link (http://symfony.com/doc/current/security/custom_provider.html)
The goal is login in with the form and the REST web service, updating my session data, like, name, doc, email, etc.
And with this data allow or deny the access to some pages or functions.
When we submit the form, we donĀ“t know how to use the data returned by the webservice, also if there are response or not.
This is our code:
SecurityController.php
<?php
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class SecurityController extends Controller {
// public function loginAction(AuthenticationUtils $authenticationUtils) {
public function loginAction(Request $request, AuthenticationUtils $authenticationUtils) {
// $authenticationUtils = $this->get('security.authentication_utils');
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('AppBundle:Security:login.html.twig', array('last_username' => $lastUsername, 'error' => $error));
// return $this->render('AppBundle:Security:login.html.twig');
}
public function loginCheckAction() {
$ca = $this->get('webservice_user_provider');
print_r($ca);
exit;
}
}
Login.html.twig-----
<form class="form-signin" action="{{ path('app_user_login_check') }}" method="POST">
Security.yml-----------------------
webservice:
id: webservice_user_provider
Archivo services.yml----------------------------
webservice_user_provider:
class: AppBundle\Security\User\WebserviceUserProvider
WebserviceUserProvider.php-----------------------------
<?php
// src/AppBundle/Security/User/WebserviceUserProvider.php
namespace AppBundle\Security\User;
use AppBundle\Security\User\WebserviceUser;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Unirest;
class WebserviceUserProvider implements UserProviderInterface {
protected $user;
public function __contsruct(UserInterface $user) {
$this->user = $user;
}
public function loadUserByUsername($username) {
// make a call to your webservice here
print_r("Estoy en el controlador de usuario");
exit;
$headers = array('Accept' => 'application/json');
$password = $this->request->get('password');
$query = array('user' => $username, 'password' => _password);
$userData = Unirest\Request::post('http://127.0.0.10:8888/login', $headers, $query);
// pretend it returns an array on success, false if there is no user
if ($userData) {
$datos = $userData->raw_body;
// print_r($userData);
// print_r($userData->body);
// print_r($userData->raw_body);
$username = $datos['ldap']['document'];
$password = $datos['ldap']['document'];
$salt = $datos['ldap']['document'];
$roles = $datos['ldap']['document'];
$doc = $datos['ldap']['document'];
$full_name = $datos['ldap']['document'];
$userLdap = $datos['ldap']['document'];
$userEpersonal = $datos['ldap']['document'];
$mail = $datos['ldap']['document'];
$position = $datos['ldap']['document'];
return new WebserviceUser($username, $password, $salt, $roles, $documento, $full_name, $userLdap, $userEpersonal, $mail, $position);
}
throw new UsernameNotFoundException(
sprintf('Username "%s" does not exist.', $username)
);
}
public function refreshUser(UserInterface $user) {
if (!$user instanceof WebserviceUser) {
throw new UnsupportedUserException(
sprintf('Instances of "%s" are not supported.', get_class($user))
);
}
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class) {
return WebserviceUser::class === $class;
}
}
I will give you a general overview, for implementation details you may want to ask another question.
A REST web service should be stateless, i.e. (to simplify a bit) should have no session. To implement ACL you may have different strategies.
The easiest one is to perform authentication on each request. You may use http authentication, or you can use an API key as many webservices do. Your webservice will always authenticate the user as the first step in each request.
A slightly more secure strategy is to have authentication return you a temporary token. I.e. first you request the login action with whatever authentication system you choose (you can even have more than one) and you get back a randomly generated token associated with your user. In the next requests you include this token and the system know who you are.
I'm trying to create a user with the Zizaco/confide library, the user data comes from Facebook and not from a form.
I try this :
$user_profile = Facebook::api('/me','GET');
$new_user = new User;
$new_user->username = $user_profile['name'];
$new_user->password = Hash::make('secret');
$new_user->email = $user_profile['email'];
$new_user->confirmation_code = '456456';
$new_user->confirmed = true;
$new_user->save();
but it doesn't save the user. Any help ?
I found the problem, the confide library sets some default rules to create the user, you need to pass this rules to save a user:
public static $rules = array(
'username' => 'required|alpha_dash|unique:users',
'email' => 'required|email|unique:users',
'password' => 'required|between:4,11|confirmed',
'password_confirmation' => 'between:4,11',
);
with this example it works:
$user = new User();
$user->username = 'asdasd';
$user->email = 'angorusadf#gmail.com';
$user->password = '12312312';
$user->password_confirmation = '12312312';
$user->save();
Mabe the method should give you some information when you don't pass the rules.
Maybe it's a pretty late answer, but I'm posting this for future reference because I was myself looking for an answer to the same question, How to create a user programatically in zizaco/confide? or more generally, how to bypass zizaco/confide's requirement for setting a password when saving a user for the first time?
well, the answer is pretty simple, thanks to the new architecture which Zizaco implemented in the 4.* branch, it's now possible to register a new user validator class see more at the package's readme in short all you need in this very case though, is just to extend the current User validator and override validatePassword() to make it accept empty passwords for new users.
Below is an example implementation:
In routes.php
// we need to register our validator so that it gets used instead of the default one
// Register custom Validator for ConfideUsers
App::bind('confide.user_validator', 'MyUserValidator');
In app/models/MyUserValidator.php (that's basically a copy of the function in the class, simply just added a check whether this is a old user or not (if the user has an ID then this is an update operation) if this is a new user, the method always returns true!
/**
* Class MyUserValidator
* Custom Validation for Confide User
*/
class MyUserValidator extends \Zizaco\Confide\UserValidator //implements \Zizaco\Confide\UserValidatorInterface
{
/**
* Validates the password and password_confirmation of the given
* user
* #param ConfideUserInterface $user
* #return boolean True if password is valid
*/
public function validatePassword(\Zizaco\Confide\ConfideUserInterface $user)
{
$hash = App::make('hash');
if($user->getOriginal('password') != $user->password && $user->getOriginal('id')) {
if ($user->password == $user->password_confirmation) {
// Hashes password and unset password_confirmation field
$user->password = $hash->make($user->password);
unset($user->password_confirmation);
return true;
} else {
$this->attachErrorMsg(
$user,
'validation.confirmed::confide.alerts.wrong_confirmation',
'password_confirmation'
);
return false;
}
}
unset($user->password_confirmation);
return true;
}
}
I'm new to Silex, I used this tutorial to set up Doctrine ORM.
But now when I'm trying to log in, i got "Bad credentials" error.
It happens when I use the default "login_check" controller.
If I use a custom one, it works but I don't know how to redirect the user to the page he was looking for. (I tried whith $request->headers->get('referer') in my login controller but it's empty.)
Here's my custom login_check contorller :
$app->post('/login-check-perso', function(Request $request) use ($app){
$route = $request->request->filter('route');
$password = $request->get('_password');
$username = $request->get('_email');
$userProvider = new \Lib\Provider\UserProvider($app);
$user = null;
try {
$user = $userProvider->loadUserByUsername($username);
}
catch (UsernameNotFoundException $e)
{
}
$encoder = $app['security.encoder_factory']->getEncoder($user);
// compute the encoded password
$encodedPassword = $encoder->encodePassword($password, $user->getSalt());
// compare passwords
if ($user->getPassword() == $encodedPassword)
{
// set security token into security
$token = new UsernamePasswordToken($user, $password, 'yourProviderKeyHere', array('ROLE_ADMIN'));
$app['security']->setToken($token);
// redirect or give response here
} else {
// error feedback
echo "wrong password";
die();
}
// replace url by the one the user requested while he wasn't logged in
return $app->redirect('/web/index_dev.php/admin/');
})->bind('login_check_perso');
So if someone can explain how to use the default "login_check", or explain to me how can I redirect user to the page he was trying to visit while not logged, it'll be great.
Thanks
EDIT:
I think the "Bad Credentials" is caused by a wrong encoders setting, I used this
to configure mine :
$app['security.encoder.digest'] = $app->share(function ($app) {
// use the sha1 algorithm
// don't base64 encode the password
// use only 1 iteration
return new MessageDigestPasswordEncoder('sha1', false, 1);
});
$app['security.encoder_factory'] = $app->share(function ($app) {
return new EncoderFactory(
array(
'Symfony\Component\Security\Core\User\UserInterface' => $app['security.encoder.digest'],
'Entity\User' => $app['security.encoder.digest'],
)
);
});
Is that correct ?
You can extend the DefaultAuthenticationSuccessHandler, which will be called after a successfull login, I doing it like this:
// overwrite the default authentication success handler
$app['security.authentication.success_handler.general'] = $app->share(function () use ($app) {
return new CustomAuthenticationSuccessHandler($app['security.http_utils'], array(), $app);
});
Create a CustomAuthenticationSuccessHandler:
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Silex\Application;
class CustomAuthenticationSuccessHandler extends DefaultAuthenticationSuccessHandler
{
protected $app = null;
/**
* Constructor
*
* #param HttpUtils $httpUtils
* #param array $options
* #param unknown $database
*/
public function __construct(HttpUtils $httpUtils, array $options, Application $app)
{
parent::__construct($httpUtils, $options);
$this->app = $app;
}
/**
* (non-PHPdoc)
* #see \Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler::onAuthenticationSuccess()
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
$user = $token->getUser();
$data = array(
'last_login' => date('Y-m-d H:i:s')
);
// save the last login of the user
$this->app['account']->updateUser($user->getUsername(), $data);
return $this->httpUtils->createRedirectResponse($request, $this->determineTargetUrl($request));
}
}
I'm using onAuthenticationSuccess() to save the users last loging datetime. You can use the createRedirectResponse() to redirect the user to the starting point you need.
I am using Sonata admin bundle for my application all works well,In my application i have users and admin,admin can add/edit/delete the users when i am trying to update a user there is a problem the password data is overrided from user table. i have overrided the preUpdate method of admin controller ,I got $object which has an instance of user entity manager so if user leaves to update password and saves data the password is lost.
public function preUpdate($object)
{
$Password = $object->getUserPassword();
if (!empty($Password)) { /* i check here if user has enter password then update it goes well*/
$salt = md5(time());
$encoderservice = $this->getConfigurationPool()->getContainer()->get('security.encoder_factory');
$User = new User();
$encoder = $encoderservice->getEncoder($User);
$encoded_pass = $encoder->encodePassword($Password, $salt);
$object->setUserSalt($salt)->setUserPassword($encoded_pass);
} else { /* here i try to set the old password if user not enters the new password but fails */
$object->setUserPassword($object->getUserPassword());
}
}
When i try to set $object->setUserPassword($object->getUserPassword()); it gets null and updates the password as null its not getting the edit data i have tried to get the repository (below) again to get the password but no luck its getting the same
$DM = $this->getConfigurationPool()->getContainer()->get('Doctrine')->getManager()->getRepository("...")->find(id here);
Is there a way i can access the original data of current entity in entity manager
You can access the original data by getting doctrine's Unit of Work.As from docs
You can get direct access to the Unit of Work by calling
EntityManager#getUnitOfWork(). This will return the UnitOfWork
instance the EntityManager is currently using.An array containing the
original data of entity
Grab the password from Unit of Work and use in your setter method
public function preUpdate($object)
{
$DM = $this->getConfigurationPool()->getContainer()->get('Doctrine')->getManager();
$uow = $DM->getUnitOfWork();
$OriginalEntityData = $uow->getOriginalEntityData( $object );
$Password = $object->getUserPassword();
if (!empty($Password)) { /* i check here if user has enter password then update it goes well*/
$salt = md5(time());
$encoderservice = $this->getConfigurationPool()->getContainer()->get('security.encoder_factory');
$User = new User();
$encoder = $encoderservice->getEncoder($User);
$encoded_pass = $encoder->encodePassword($Password, $salt);
$object->setUserSalt($salt)->setUserPassword($encoded_pass);
} else { /* here i try to set the old password if user not enters the new password but fails */
$object->setUserPassword($OriginalEntityData['Password']);/* your property name for password field */
}
}
Hope it works fine
Direct access to a Unit of Work
Reset entity in entity manage, example for onFlush event
/**
* #param OnFlushEventArgs $args
*
* #throws \Doctrine\ORM\ORMException
* #throws \Doctrine\ORM\OptimisticLockException
*/
public function onFlush(OnFlushEventArgs $args)
{
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityUpdates() as $keyEntity => $entity) {
if ($entity instanceof Bill) {
$em->refresh($entity);
$this->createPdfs($entity);
}
}
}
$this->getConfigurationPool()
->getContainer()
->get('Doctrine')
->getRepository("...")
->find(id here);
So leave out the getManager() part;