I have the following app flow:
Add role action:
public function addroleAction($role) {
$securityContext = $this->get('security.context');
$em = $this->getDoctrine()->getManager();
$user = $this->get('security.context')->getToken()->getUser();
$userId = $user->getId();
$userObj = $em->getRepository('ProjectEntityBundle:User')->find($userId);
switch ($role) {
case 6://student
$userObj->addRole('ROLE_STUDENT');
$em->flush();
$securityContext->getToken()->setUser($userObj);
break;
}
return $this->redirect($this->generateUrl('check_role'));
}
Check role action:
public function checkroleAction() {
$securityContext = $this->get('security.context');
if ($securityContext->isGranted('ROLE_STUDENT')) {
return $this->redirect($this->generateUrl('student_profile'));
} else {
return $this->render('ProjectEduBundle:User:selectrole.html.twig');
}
}
The first action adds role to the database and redirect is made to checkroleAction.
The if condition is not met(returns false).
I guess this happens because security context is not refreshed after the database operation of adding role is done.
How do I solve this issue?
You can set the user on the token again...
$this->get('security.context')->getToken()->setUser($userObject);
Add that at the end of your addroleAction()
Related
I am trying to query our database to see if users can log in based on whether the organisation they belong to have logins enabled.
LoginController.php
protected function redirectTo()
{
$user = Auth::user()->id;
$userOrg = UserOrganization::where('user_id', $user)->first();
$org = Organization::where('id', $userOrg->org_id)->first();
if ($org->allow_org_login == 0) {
return '/login';
} else {
if(Auth::user()->has_changed_temp_password == false)
{
DB::table('users')->where('id', $user)->update(['last_login' => Carbon::now()]);
DB::table('users')->where('id', $user)->increment('total_logins');
return '/user/password/change';
} else {
DB::table('users')->where('id', $user)->update(['last_login' => Carbon::now()]);
DB::table('users')->where('id', $user)->increment('total_logins');
return '/overview';
}
}
}
trying to log in as a user belonging to an organisation with allow_org_login = 0 should redirect to the '/login' page, but instead it either logs the user in or prompts for a password change for a new user.
What am I doing wrong?
Edit: Debug contents of $org (allow_org_login on the bottom line)
since there is many to many relation between user and organization.
i suppose this relation is defined in User & Organization as in documentation:
https://laravel.com/docs/7.x/eloquent-relationships#many-to-many
considering that:
user may have more than an organization, and if any of the organization allowed log_in the user should login to your system
$user = Auth::user();
$userOranization=$user->organizations()->get();
$allowUserToLogin=false;
if($userOranization->where('allow_org_login',1)->first()!=null)
$allowUserToLogin=true;
and then:
if ($allowUserToLogin == 0) {
return '/login';
} else { ....
for redirectTo() method it will only fire when we using POST method for login.
inside you redirectTo() method your check condition and then you return '/login';
which it will redirectTo login page. but this time you already login then on login it will check if user login then it redirectTo url that we config on LoginController and protected $redirectTo; it will not call redirectTo() method. cuz this time we use redirect using GET method not POST.
if you want to put validate on redirectTo() method you can try below code:
protected function redirectTo()
{
$user = Auth::user()->id;
$userOrg = UserOrganization::where('user_id', $user)->first();
$org = Organization::where('id', $userOrg->org_id)->first();
if ($org->allow_org_login == 0) {
Auth::logout(); // logout user before redirect
return '/login';
} else {
if(Auth::user()->has_changed_temp_password == false)
{
// depend on you choice need to logout or not
DB::table('users')->where('id', $user)->update(['last_login' => Carbon::now()]);
DB::table('users')->where('id', $user)->increment('total_logins');
return '/user/password/change';
} else {
// depend on you choice need to logout or not
DB::table('users')->where('id', $user)->update(['last_login' => Carbon::now()]);
DB::table('users')->where('id', $user)->increment('total_logins');
return '/overview';
}
}
}
but for my option i will create new middleware for handle this.
After a user enters his credential and tries to login and after the user is found, we have a siterole table that will be checked, if the role that the user selected is found in the database "where userID=request and roleType = request" then the login is successful otherwise it fails due to choosing the wrong user role.
The code is simple:
$findrole = $request->role;
$user_id = Auth::user()->id;
$userrole = DB::table('siterole')->where('role_id' ,'=',$findrole)->where('user_id' ,'=', $user_id)->get();
if(!empty($userrole)) {
make it login
}
else{
redirect it with a fail login
}
By failed login I mean no session should be set, where I tried this code was in
vendor\laravel\framework\src\Illuminate\Foundation\Auth\AuthenticatesUsers.php
BUT when the "role_id" is not found for that "user_Id", the user is logged in and redirected to the wrong page!
Edit the function Im putting my code in is this :
public function login(Request $request)
{
$this->validateLogin($request);
$throttles = $this->isUsingThrottlesLoginsTrait();
if ($throttles && $lockedOut = $this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
$credentials = $this->getCredentials($request);
if (Auth::guard($this->getGuard())->attempt($credentials, $request->has('remember'))) {
//MYCODE GOES BETWEEN THESE LINES
if its not empty return the below code
return $this->handleUserWasAuthenticated($request, $throttles);
}
if ($throttles && ! $lockedOut) {
$this->incrementLoginAttempts($request);
}
//if its empty return to this section
return $this->sendFailedLoginResponse($request);
}
Auth::user()->id returns the user id only when you are authenticated. In line 2 of your example code, when you are creating the $user_id variable you are not authenticated yet so it will always be null. You'll need to get the user_id another way.
Found the solution, so where i was putting my condition is where laravel already returned a login = true, so i cant do anything.
that attemp() is actually attempting the login which is located in :
vendor\laravel\framework\src\Illuminate\Auth\SessionGuard.php
now in attemp function we dont have access to our request but we can pass the User type i call it (role) in function getCredentials which is located in :
vendor\laravel\framework\src\Illuminate\Foundation\Auth\AuthenticatesUsers.php
Step 1:
protected function getCredentials(Request $request)
{
//sending role as credential too
// my input name was role
return $request->only($this->loginUsername(), 'password','role');
}
Now since we passed it in attemp() , its the 2nd array of our credentials BUT we have to unset it from the main credentials because laravel will create a where clause for each key in array :
Step 2
public function attempt(array $credentials = [], $remember = false, $login = true)
{
//get the user roll to check if the user has the same role
//else kill him #Stormspirit
$user_role = $credentials['role'];
//as laravel make the where clause for every field we unset it from the array
unset($credentials['role']);
$this->fireAttemptEvent($credentials, $remember, $login);
$this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
// If an implementation of UserInterface was returned, we'll ask the provider
// to validate the user against the given credentials, and if they are in
// fact valid we'll log the users into the application and return true.
if ($this->hasValidCredentials($user, $credentials)) {
//user credential was valid check the role part
$userrole_finder = DB::table('siterole')->where('role_type',$user_role)->where('user_id',$user->id)->get();
if($userrole_finder==[]) {
$login = false;
return false;
}
if ($login) {
$this->login($user, $remember);
}
return true;
}
All set! dont forget to add use DB; check your user role table and if it was empty make the login false and return false that would do the rest and u will see laravel's invalid credential error.
You can implement this for user type I just called it role.you can also put the user type in a session in handleUserWasAuthenticated function in AuthenticatesUsers.php , exact location described above
protected function handleUserWasAuthenticated(Request $request, $throttles)
{
session(['user_role' => $request->role]);
if ($throttles) {
$this->clearLoginAttempts($request);
}
if (method_exists($this, 'authenticated')) {
return $this->authenticated($request, Auth::guard($this->getGuard())->user());
}
return redirect()->intended($this->redirectPath());
}
I have written a basic login script and now need to update the data stored in the auth component and then save it to the database, this is what i have so far;
public function login()
{
if ($this->request->is('post')) {
$user = $this->Auth->identify();
if ($user) {
$this->Auth->setUser($user);
$this->Auth->user()->last_activity = date("Y-m-d");
$this->Users->save($this->Auth->user());
return $this->redirect($this->Auth->redirectUrl());
}
$this->Flash->error(__('Email or password is incorrect, please try again.'));
}
}
I've tried a few different variations but can't get any to work. Any ideas?
Updating data in cakephp3 is slightly different than cakephp2, Try something like this:
public function login()
{
if ($this->request->is('post')) {
$user = $this->Auth->identify();
if ($user) {
$this->Auth->setUser($user);
$userData = $this->Users->get($user['id']);
$userData->last_activity = date("Y-m-d");
if($this->Users->save($userData)){
$user['last_activity'] = $userData->last_activity; // to update auth component
}
// echo $this->Auth->user('last_activity');
return $this->redirect($this->Auth->redirectUrl());
}
$this->Flash->error(__('Email or password is incorrect, please try again.'));
}
}
Another way of updating record in cakephp3 is:
$query = $this->Users->query();
$query->update()
->set(['last_activity ' => date('Y-m-d')])
->where(['id' => $user['id']])
->execute();
But I don't recommend this one as callbacks are not fired.
In Cake3, you can take advantage of the afterIdentify event.
In AppController::initialize, add a listener for the event:
\Cake\Event\EventManager::instance()->on('Auth.afterIdentify', [$this, 'afterIdentify']);
Add AppController::afterIdentify function to handle the event:
public function afterIdentify(CakeEvent $cakeEvent, $data, $auth) {
$users_table = TableRegistry::get('Users');
$user = $users_table->get($data['id']);
$user->last_activity = new Cake\I18n\FrozenTime();
// If you ever need to do password rehashing, here's where it goes
if ($this->Auth->authenticationProvider()->needsPasswordRehash()) {
$user->password = $this->request->data('password');
}
$users_table->save($user);
}
Now, the data returned by the Auth->user() call should always be up-to-date without any extra effort on your part.
I am using sonatadmin for a symfony 2 project. Sometimes admin user may accidently delete his own account. how to prevent admin user to delete his own account? Thanks!
To prevent admin to delete his own account you need define your own CRUDController for sonata user by following ADVANCED CONFIGURATION
admin: # Admin Classes
user:
class: Sonata\UserBundle\Admin\Entity\UserAdmin
controller: YourUserBundle:CRUD
translation: SonataUserBundle
and then in your controller override batchActionDelete() & deleteAction() functions in these functions check if request contains admin object/id then restrict here.
public function deleteAction($id)
{
$id = $this->get('request')->get($this->admin->getIdParameter());
$object = $this->admin->getObject($id);
if (!$object) {
throw new NotFoundHttpException(sprintf('unable to find the object with id : %s', $id));
}
$userid = $this->getUser()->getId() // get id of logged in user
if($userid == $id){
$this->addFlash(
'sonata_flash_error',
'Error you cannot delete your own account'
);
return $this->redirectTo($object);
}
// other code from base class
}
Same logic for batchActionDelete() function
I am using SonataUserBundle along with FOSUserBundle and I ended up with the following solution.
config.yml:
parameters:
sonata.user.admin.user.controller: AppBundle:CRUD\CRUD
AppBundle\Controller\CRUD\CRUDController:
<?php
namespace AppBundle\Controller\CRUD;
use Sonata\AdminBundle\Controller\CRUDController as Controller;
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
class CRUDController extends Controller
{
public function deleteAction($id)
{
$request = $this->getRequest();
$id = $request->get($this->admin->getIdParameter());
$object = $this->admin->getObject($id);
if (!$object) {
throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
}
$currentUserId = $this->getUser()->getId(); // ID of the current user
if ($currentUserId == $id) {
$this->addFlash(
'sonata_flash_error',
'You cannot delete your own account.'
);
return $this->redirectTo($object);
}
return parent::deleteAction($id);
}
public function batchActionDelete(ProxyQueryInterface $query)
{
$request = $this->getRequest();
$currentUserId = $this->getUser()->getId(); // ID of the current user
$selectedUsers = $query->execute();
foreach ($selectedUsers as $selectedUser) {
if ($selectedUser->getId() == $currentUserId) {
$this->addFlash(
'sonata_flash_error',
'You cannot delete your own account.'
);
return new RedirectResponse(
$this->admin->generateUrl('list', array('filter' => $this->admin->getFilterParameters()))
);
}
}
return parent::batchActionDelete($query);
}
}
References:
M Khalid Junaid's answer
https://sonata-project.org/bundles/admin/3-x/doc/cookbook/recipe_custom_action.html (Sonata documentation)
https://sonata-project.org/bundles/user/2-0/doc/reference/advanced_configuration.html
(advanced configuration)
I am new with Symfony and FOSUserBundle, I have a relation One To One between User and Stream, when a user want to register he check if he is a streamer or not. If he is a streamer I want to create a new stream in relation with the user so here is the registerAction from FOSUserBundle that I'm overriding :
$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);
if ($process) {
$user = $form->getData();
/*This is what I added*/
if($user->getIsStreamer()){
$em = $this->getDoctrine()->getManager();
$stream = new Stream();
$stream->setUser($user);
$em->persist($stream);
$em->flush();
}
/********/
$authUser = false;
if ($confirmationEnabled) {
$this->container->get('session')->set('fos_user_send_confirmation_email/email', $user->getEmail());
$route = 'fos_user_registration_check_email';
} else {
$authUser = true;
$route = 'fos_user_registration_confirmed';
}
$this->setFlash('fos_user_success', 'registration.flash.user_created');
$url = $this->container->get('router')->generate($route);
$response = new RedirectResponse($url);
if ($authUser) {
$this->authenticateUser($user, $response);
}
return $response;
}
But nothing happened, what did I missed ?
You are missing the part that add the Stream to the user (and not only set the user of the stream).
First, be sure your association contains cascade={"persist"} to avoid need of manually persist the Stream :
/**
* #ORM\OneToOne(targetEntity="Stream", cascade={"persist"},
*/
protected $stream;
Then, change your setStream to make it calling $stream->setUser automatically :
public function setStream(Stream $stream = null)
{
$this->stream = $stream;
$stream->setUser($this); // Setter calling
return $this;
}
Now you can replace the logic of your condition like this :
if($user->getIsStreamer()){
$stream = new Stream();
$user->setStream($stream);
}
The association should be correctly stored when calling $em->flush at the end of your method.
In the case of FOSUB registration it should be :
$this->get('fos_user.user_manager')->updateUser($user);.
It's done in the confirmAction where your user is redirected in success.