How to delete session for specific user in cakePHP3? - php

What I need to achieve is to have the possiblility for administrator to remove session of given user to force relogin. This can come in handy when for example user permissions changes. How to bind session and user together so other user can access that data?
I am using database session store so removing record from the database would result in force relogin. Also authentication is based on Auth component.
Here is some of my related config:
$this->loadComponent('Auth', [
'loginAction' => [
'controller' => 'Auth',
'action' => 'login'
],
'loginRedirect' => "/home",
'logoutRedirect' => [
'controller' => 'Auth',
'action' => 'login'
],
'authError' => "You have no permissions to access resource ${url}. Contact system administrator.",
'storage' => 'Session',
'authenticate' => [
'Form' => [
'userModel' => 'User',
'finder' => 'user',
'fields' => ['username' => 'name', 'password' => 'password']
]
],
'authorize' => ["Controller"]
]);
And session storage:
'Session' => [
'defaults' => 'database',
]
Below I have marked where would i place database update code. Unfortunetly after login execution, session gets "revalidated" so the ID changes. All in all, changes made in login action are not visible after redirect.
Login action:
public function login()
{
$form = new LoginForm();
if ($this->request->is('post')) {
if ($form->validate($this->request->data)) {
$user = $this->Auth->identify();
if ($user) {
$this->Auth->setUser($user);
// here would be good place to update the database
return $this->redirect($this->Auth->redirectUrl());
} else {
$this->Flash->error("Invalid security credentials provided");
}
} else {
$this->Flash->error("Invalid login form");
}
}
$this->set('loginForm', $form);
}

#ndm Thanks for valuable comments and pinpoints.
Thank to #ndm's comments I have found (most probably) viable solution. I had to inject some code into session data save process. To do that, I have used custom SessionHandler and PHP's Cloasure in role of callback.
class UserSession extends DatabaseSession
{
public $callback;
public function write($id, $data)
{
$result = parent::write($id, $data);
if ($this->callback) {
call_user_func($this->callback,$id);
}
return $result;
}
}
and the login action
$user = $this->Auth->identify();
if ($user) {
$this->Auth->setUser($user);
/** #var UserTable $model */
$model = $this->loadModel("User");
$handler = $this->Session->engine();
/** #var UserSession $handler */
$handler->callback = function ($id) use ($model, $user) {
$model->updateSession($id, $user['id']);
};
return $this->redirect($this->Auth->redirectUrl());
}
This way, the session row in the DB is updated AFTER session data is flushed into the DB.
Now I can query all sessions for given user, and delete them if needed forcing relogin for that user. Callback is called only once upon user login, whitch was as well my goal in matter of optimalization and avoidance of UPDATE query db during every http request (because that would be the solution as well, but I wanted to avoid that)

Related

After logging in, the redirect does not work in CakePHP

I have a cakephp website. After login the user should be redirected to a new page, this is working on local. But on dev site it is giving issues .
When the user clicks on submit the URL of the next page gets appended to the current URL and the page is not redirecting.
This is the login function from Userscontroller
public function login()
{
if ($this->request->is('post')) {
$user = $this->Auth->identify();
if ($user) {
$this->Auth->setUser($user);
return $this->redirect(['controller'=>'Recipes','action' => 'index']);
//return $this->redirect($this->Auth->redirectUrl());
}
$this->Flash->error('Your username or password is incorrect.');
}
}
This is the initialize from AppController
public function initialize()
{
parent::initialize();
$this->loadComponent('RequestHandler', [
'enableBeforeRedirect' => false,
]);
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'email',
'password' => 'password'
]
]
],
'loginAction' => [
'controller' => 'Users',
'action' => 'login'
],
'storage' => 'Session',
// If unauthorized, return them to page they were just on
'unauthorizedRedirect' => $this->referer()
]);
// $this->Auth->setConfig('authorize', ['Controller']);
// Allow the display action so our PagesController
// continues to work. Also enable the read only actions.
/* $this->Auth->allow(['display', 'view', 'index','register','forgotPassword']);*/
/*
* Enable the following component for recommended CakePHP security settings.
* see https://book.cakephp.org/3.0/en/controllers/components/security.html
*/
//$this->loadComponent('Security');
}
I have also attached a screenshot of whats happening
The issue is solved. it seems the issues occurred because of cache as it is working on other computer . i cleared the cache it is working now.

Cakephp 3.6.14: redirect after action deny

I am trying to deny some actions for non-admin users in my controllers. So in the controllers I am using this code:
public $components = array('Auth');
public function beforeFilter(Event $event) {
parent::beforeFilter($event);
if($this->Auth->user('role_id')==1 or $this->Auth->user('role_id')==2){ //role: 1 admin, 2 project manager
$this->set('is_admin', true);
}
else
{
$this->Auth->deny(['index','delete']);
$this->set('is_admin', false);
}
$this->set('my_id', $this->Auth->user('id'));
}
So now anytime a user that is not admin or project manager tries to perform index or delete actions, is redirected to the "Method Not Allowed" error page. But I would like to return to the previous page with a message: "You are not authorized to perform this action".
I tried to set 'unauthorizedRedirect' => $this->referer() in the AppController:
$this->loadComponent('Auth', [
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'email',
'password' => 'password'
]
]
],
'loginAction' => [
'controller' => 'Users',
'action' => 'login'
],
'unauthorizedRedirect' => $this->referer()
]);
But didn't work. The only way I managed to achieve that is by using this code in the beforeFilter function of the controller:
if(!($this->Auth->user('role_id')==1 && !$this->Auth->user('role_id')==2 && ($this->request->action === 'index' || $this->request->action === 'delete')){
$this->Flash->error(__('You are not authorized to perform this action'));
return $this->redirect(['controller' => 'Users', 'action' => 'index']);
}
But it doesn't seem the proper way to do this in all the controllers that I want to deny some actions. Is there another way?
Cakephp provides isAuthorized function for the same. You can take advantage of it.
Just define isAuthorized in your App controller or in separate controllers(if you want to put separate conditions for each controllers.)
public function isAuthorized($user)
{
$roleArray = [1, 2]; // your role ids array
if ( !in_array($user['role_id'], $roleArray) && in_array($this->request->getParam('action'), ['index', 'delete'])) { // put your conditions here
return false;
}
return true;
}
Cakephp -> Authentication and Authorization -> Authorization (who’s allowed to access what)

"Auth->identify()" always returns false - CakePHP 3.5

For some reason I can't login to accounts I registered. More info below.
Functions from UsersController.php
public function login() {
if ($this->request->is('post')) {
$auth = $this->Auth->identify(); // Returns false
debug($this->request->getData()); // Return email & with unhashed password
debug($auth);
if ($auth) {
$this->Auth->setUser($auth);
$this->redirect($this->Auth->redirectUrl());
} else {
$this->Flash->error('E-mail or password is wrong.');
}
}
}
public function register() {
$user = $this->Users->newEntity();
$this->set('user', $user);
$this->loadModel('Groups');
$group = $this->Groups->newEntity();
$this->set('group', $user);
if ($this->request->is('post')) {
// Check if passwords matches
$pass = $this->request->getData('password');
$con_pass = $this->request->getData('password_confirm');
if ($pass !== $con_pass) {
return $this->Flash->error('Passwords don\'t match');
}
// Patch entities
$group = $this->Groups->patchEntity($group, $this->request->getData());
$user = $this->Users->patchEntity($user, $this->request->getData());
// Make group and user
if (empty($group->errors()) && empty($user->errors())) {
// Group
if (!$this->Groups->save($group)) {
return $this->Flash->error('Something went wrong');
}
// User
$user->group_id = $group->id;
if ($this->Users->save($user)) {
$this->Flash->success('Welkom ' . $user->name . '!');
// return $this->redirect(['action' => 'register']);
} else {
return $this->Flash->error('something went wrong2');
}
}
}
}
Auth component in AppController:
$this->loadComponent('Auth', [
'userModel' => 'Users',
'loginAction' => [
'controller' => 'Users',
'action' => 'login'
],
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'email',
'password' => 'password'
]
]
],
//'authError' => false,
'storage' => 'Session'
]);
Login form:
<?= $this->Form->create('User'); ?>
<?= $this->Form->email('email', ['placeholder' => 'E-mail', 'maxlength' => '42', 'label' => false]) ?>
<?= $this->Form->password('password', ['type' => 'password', 'placeholder' => 'Wachtwoord', 'maxlength' => '32', 'label' => false]) ?>
<?= $this->Form->submit('Login', ['class' => 'button']) ?>
<?= $this->Form->end(); ?>
User entity:
class User extends Entity {
protected $_accessible = [
'group_id' => true,
'name' => true,
'email' => true,
'password' => true,
'profile_img_url' => true,
'pass_reset_time' => true,
'creation_date' => true,
'modified_date' => true
];
protected function _setPassword($password) {
return (new DefaultPasswordHasher)->hash($password);
}
protected $_hidden = [
'password'
];
}
The user gets saved correctly in the database with a hashed password.
When I try to login $this->Auth->identify(); always returns false.
I've tried to / Things to know:
I'm trying to login with email and password.
Table name in db is users
Renew salt (And create a new account and login with that account)
Password column length is 255.
Checked Auth docs
Checked Blog tutorial
Checked a lot of questions related to this on Stack and other websites but nothing has fixed my problem yet.
Users get stored correctly. But as soon as I try to login, it won't let me.
I tried to login without the password hasher function and with an unhashed password, Also didn't work.
Checked in different browsers & deleted cache.
Thanks!
There doesn't seem to be any obvious errors, except for a missing emptiness check in the _setPassword() method that would prevent an empty $password from being hashed. You should do something similar to what is shown in the docs:
if (strlen($password) > 0) {
return (new DefaultPasswordHasher)->hash($password);
}
See Cookbook > Controllers > Components > Authentication > Hashing Passwords
Also the FormHelper::create() method also doesn't take a string, it only doesn't error out there for backwards compatibility reasons IIRC. If you don't have a valid context to pass, then don't pass any value at all.
That being said, you'll have to do more debugging on your own. Start with manually validating the hashed password stored in the database using the DefaultPasswordHasher::validate() method to ensure that the correct value has been hashed.
Then go set some breakpoints in the authentication code flow to figure where things may go wrong, check:
FormAuthenticate::authenticate()
FormAuthenticate::_checkFields()
BaseAuthenticate::_findUser()
BaseAuthenticate::_query()
whether the correct request data is being read, whether the query conditions are built as expected, whether and what value is being returned for password verification, etc...
Alright, I wasted my whole morning and afternoon.
I thought my password column length was 255 but it was actually 32. I checked the length of the wrong column like 4 times apparently.
Thanks for the help #ndm.

Custom Authorisation in CakePHP 3

I have an intranet app running on IIS, using CakePHP 3. From IIS I am able to access the server var $_SERVER['AUTH_USER'] and I want to use this variable to authenticate users.
I have created a users table in my database with a username field that I want to match to AUTH_USER. I have created a custom Auth component like so:
namespace App\Auth;
use Cake\Auth\BaseAuthenticate;
use Cake\Network\Request;
use Cake\Network\Response;
use Cake\ORM\TableRegistry;
class AuthuserAuthenticate extends BaseAuthenticate
{
public function authenticate(Request $request, Response $response) {
$username = str_replace('DOMAIN\\', '', $_SERVER['AUTH_USER']);
$users = TableRegistry::get('Users');
$user = $users->find()->where(['username' => $username])->first();
if ($user) {
return $user;
} else {
$user = $this->Users->newEntity();
$user->username = $username;
if ($this->Users->save($user)) {
return $user;
} else {
return false;
}
}
}
And in the AppController initialize() I have tried to load Auth with the custom component.
$this->loadComponent('Auth', [
'authenticate' => [
'Basic' => [
'fields' => ['username' => 'username'],
'userModel' => 'Users'
],
],
'loginAction' => [
'controller' => 'Pages',
'action' => 'display'
],
'storage' => 'Memory',
'unauthorizedRedirect' => false
]);
$this->Auth->config('authenticate', 'Authuser');
At this point I just get redirected no matter what page I try to go on, I'm not really sure if it's failing to authenticate or something else is the problem.
I have tried adding this to AppController as a test:
public function isAuthorized($user)
{
return true;
}
But I am unable to access any pages with this code in place. Can anyone let me know what I'm doing wrong?
Thanks,
Kez
Your auth component is not implementing the authorize method.
public function authorize($user, Request $request) {
// return true if authorized
// return false if not authorized
}
Secondly, isAuthorized is called when using the ControllerAuthorize component. If you want to use controller authentication, you should use ControllerAuthorize insted.
$this->loadComponent('Auth', [
'authenticate' => 'Controller'
]);
Also: You are configuring the BasicAuthenticate component, then immediately overwriting the config.

Setting which pages can be accessed in CakePHP

I've put a considerable amount of digging into this but I haven't been able to figure out what the best method would be.
I have an employee management system where everyone who logs in is either an "employee", a "supervisor" or a "manager".
At the moment, I display different versions of pages just fine depending on what their rank is. However, regular "employees" can still get to pages they shouldn't if they manually enter the URL. According to CakePHP's documentation, it says all pages are restricted by default, and you have to grant access to each one. But I haven't granted access and it seems all the pages are accessible.
What is the best method for page access?
Thanks!
Edit: Here is the configuration of the AppController:
public $components = array(
'DebugKit.Toolbar',
'Session',
'Auth' => array(
'authenticate' => array(
'Form' => array(
'userModel' => 'Employee'
)
),
'loginAction' => array(
'controller' => 'employees',
'action' => 'login',
//'plugin' => 'users'
),
'loginRedirect' => array('controller' => 'employees', 'action' => 'dashboard'),
'logoutRedirect' => array('controller' => 'employees', 'action' => 'login'),
'authError' => 'You must be logged in to see that.'
)
);
And then there is the isAuthorized() method which always is set to return false:
public function isAuthorized($user = null) {
// Any registered user can access public functions
/*if (empty($this->request->params['admin'])) {
return true;
}*/
// Only admins can access admin functions
/*if (isset($this->request->params['admin'])) {
return (bool)($user['role'] === 'admin');
}*/
// Default deny
return false;
}
Crete two tables in database
resources (id, controller, action) (This will contain controller names and action names.)
permission (roll_id, resource_id)
In isAuthorized() function
If roll is admin, then return true.
Else Check the following.
using $this->request->controller Get current controller name.
using $this->request->action Get current action name.
Get resource_id from resources table for current controller and action.
Check record in permission table for resource_id and roll_id.
If record exist then return true.
Else at the end it is returning false by default.
Your code is missing this in the AppController.php
class AppController extends Controller {
public function isAuthorized($user) {
return true;
}
public function initialize() {
parent::initialize();
$this->loadComponent('RequestHandler');
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'authenticate' => [
'Form' => ['fields' => ['username' => 'email']]
],
'authorize' => ['Controller'],
]);
//some other code here
}

Categories