CakePHP Audit Log Plugin user authentication - cakephp

I'm trying to implement the Audit Trail plugin - https://github.com/robwilkerson/CakePHP-Audit-Log-Plugin
It all works great, however i can't get the user authentication working by following the instructions i get the following error -
Fatal error: Call to undefined method CakeErrorController::currentUser()
I have followed the instructions by adding
protected function currentUser() {
$user = $this->Auth->user();
return $user[$this->Auth->userModel]; # Return the complete user array
}
and adding
public function beforeFilter() {
...
if( !empty( $this->data ) && empty( $this->data[$this->Auth->userModel] ) ) {
$this->data[$this->Auth->userModel] = $this->currentUser();
}
}
to my appController, has anyone implemented this before or recognise the error?

For Cakephp 2.4 you have to do some changes in order to work with the Auth component:
In the AppModel:
public function currentUser() {
$userId = AuthComponent::user('id');
//Get the information of the user
$currentUser = $this->importModel('User')->find('first', array(
'conditions'=>array('User.id'=>$userId),
));
//Return all the User
return $currentUser['User'];
}
And now in your AppController:
The true is you don't need to do anything else in your controller, it's just to prevent some problem. So, OPTIONAL:
if( !empty( $this->request->data ) && empty( $this->request->data[$this->Auth->userModel] ) ) {
$user['User']['id'] = $this->Auth->user('id');
$this->request->data[$this->Auth->userModel] = $user;
}
It works for me.

Don't add the currentUser() function to your AppController, it has to be in your AppModel. Here's what my currentUser() function looks like using CakePHP 2.3:
public function currentUser() {
return array('id' => AuthComponent::user('id'));
}

Related

CakePHP 3.0 build a permission menu using Cells

I would like to create a menu in new CakePHP 3.0 and I found that using cells might be a good way. So let's say I created UserMenuCell
class UserMenuCell extends Cell {
protected $_validCellOptions = [];
public function display() {
$menu = [];
$menu[] = $this ->menu( __('Dashboard'), array( 'controller' => 'Users', 'action' => 'dashboard' ), 'fa-dashboard', [] );
if( $this -> Auth -> isAuthorized(null, ??? ))
$menu[] = $this ->menu( __('Barcodes'), array( 'controller' => 'Barcodes', 'action' => 'index' ), 'fa-table', [] );
$this -> set ( 'menu', $menu );
}
private function menu( $title, $url = [], $icon, $submenu = [] ) {
return ['title' => $title, 'url' => $url, 'icon' => $icon, 'submenu' => $submenu]; }
}
But I want to display Barcodes item only when current user is authorized to manage barcodes. How can I do it? I can't even access $this -> Auth to get current user.
In my cell's template is everything OK. I just need to create this nested array for menu.
According to Cookbook, the session is available from within Cells.
class UsermenuCell extends Cell
{
public function display()
{
var_dump($this->request->session()->read('Auth'));
}
}
Like this you could read the needed informations in your cell display function.
and if you pass the session variable?
<?= $this->cell('userMenu', $this->Session->read('Auth')); ?>
I think the problem is solved:
I can make controller to have static method for example static public function _isAuthorized($user, $request) which would handle the authorization logic (so every controller controls only its own permissions).
And then I can just call from anywhere for example PostsController::_isAuthorized($user, ['action' => 'add']). This should solve all problems I guess.
Also good point is to pass $this -> Auth -> user() into view, so it can be used in Cells (through parameter).
src/Controller/AppController.php
public function beforeFilter(Event $event) {
$this -> set('user', $this -> Auth -> user());
}
src/View/Cell/MenuCell.php
use App\Controller\PostsController; // Don't forget to use namespace of your Controller
class MenuCell extends Cell {
public function display($user) {
$menu = [];
if (PostsController::_isAuthorized($user, ['action' => 'add'])) // In that method you must handle authorization
$menu[] = ['title' => 'Add post', 'url' => array('controller' => 'Posts', 'action' => 'add')];
$this -> set ('menu', $menu); // Handle this in Template/Cell/Menu/display.ctp
}
}
src/Template/Cell/Menu/display.ctp - just to show how to render menu
<ul>
<?php foreach($menu as $item) {
echo '<li>' . $this -> Html -> link ($item['title'], $item['url']);
} ?>
</ul>
src/Template/Layout/default.ctp - render menu in main layout
<?= $this -> cell('Menu', array($user)) /* This is the user passed from beforeFilter */ ?>
Then you can play with isAuthorized methods. For example you can edit your AppController. Always when CakePHP calls isAuthorized function it will be redirected to YourNameController::_isAuthorized() static method (if exists).
src/Controller/AppController.php
public function isAuthorized( $user ) {
$childClass = get_called_class();
if(method_exists($childClass, '_isAuthorized'))
return $childClass::_isAuthorized($user, $this -> request);
return static::_isAuthorized($user, $request);
}
static public function _isAuthorized($user, $request)
{
if ($user['role'] == 'admin')
return true;
return false; // By default deny any unwanted access
}
This is an example of your controller. You can specify only static _isAuthorized($user, $request) method, because for purposes of CakePHP default behavior it will be called from AppController::isAuthorized (see code above).
src/Controller/PostController.php
static public function _isAuthorized($user, $request)
{
$action = ($request instanceof Cake\Network\Request) ? $request -> action : $request['action'];
if($action == 'add' && $user['role'] == 'CanAddPosts')
return true;
return parent::_isAuthorized($user, $request);
}
As you can see I made $request to accept an array or Cake\Network\Request object. That's because CakePHP call it with Request object but when I call it I don't need to create this object, since my parameters are easy (see code above MenuCell.php).
Of course you can now do more complex logic like user can have more roles separated by comma and you can explode this and check if user has permission by in_array.
Now it's really up to you what is your logic behind permissions. Every controller can handle it's own permission managing while you can always access these permissions with every user and every page request.

CakePHP Auth with two tables (models)

I have two tables in my database, one for admins (named Admins) and the other one for normal users, named : utilisateurs (in french). I know i have to use cakePHP's convention which says i must create a table named users, which has the fields username and password. But the problem is that i have two tables and not only one. Each one has its particular fields, so i really need them to stay separated. And even informations required to login are different depending on the user :
admins need their login + password
normal users need a specific id (in their id cards) + password
What i want to do, is create two login pages, one for admins and the other for normal user. After logging in, the user is redirected to the page he is supposed to see. But yet, if the user tries attempt a forbidden location. I want be able to stop him (beforeFilter + isAuthorized i think)
How can i make all this work ?
I'm not beginner in cakephp, i've already made an authentication system in anotehr app using Auth Component, but it was a little easier because needed only one table for users.
Your help would be much appreciated.
Assuming the following:
You have 2 tables associated with model User and Admin, where:
User has idcard and password fields.
Admin has login and passowrd field.
The passwords are hashed using User::hasPassword and Admin::hasPassword functions.
You login form is created as follows:
echo $this->Form->create(null, '') ;
echo $this->Form->input('login') ;
echo $this->Form->input('password') ;
echo $this->Form->end(__('Submit')) ;
You can create a new Authenticate component under App/Controller/Component/Auth/MyAuthenticate.php:
<?php
App::uses('FormAuthenticate', 'Controller/Component/Auth');
class MyAuthenticate extends FormAuthenticate {
public function authenticate(CakeRequest $request, CakeResponse $response) {
$username = $request->data['login'] ;
$password = $request->data['password'] ;
App::import('Model', 'User') ;
$userModel = new User () ;
/* Try to authenticate as a user... */
$user = $userModel->find('first', array(
'conditions' => array(
'idcard' => $username,
'password' => User::hashPassword($password) ;
)
)) ;
if ($user) {
$user = $user['User'] ; // Get only useful info
$user['type'] = 'user'; // Save user type
return $user ;
}
/* Same thing for admin. */
App::import('Model', 'Admin') ;
$adminModel = new Admin () ;
$user = $adminModel->find('first', array(
'conditions' => array(
'login' => $username,
'password' => Admin::hashPassword($password) ;
)
)) ;
if ($user) {
$user = $user['Admin'] ; // Get only useful info
$user['type'] = 'admin'; // Save user type
return $user ;
}
return null ;
}
};
You just need to be sure that that a admin cannot be authenticated as a user, and reverse.
In your AppController:
public $components = array(
'Auth' => array(
'authenticate' => array('My'), // The prefix in front of your component
'loginAction' => array(/* ... */),
'loginRedirect' => array(/* ... */),
'logoutRedirect' => array(/* ... */),
'authError' => "...",
'authorize' => 'Controller'
)
) ;
Your login action is the same that for normal tables:
public function login () {
if ($this->request->is('post')) {
if ($this->Auth->login()) {
$this->redirect($this->Auth->redirect());
}
else {
$this->request->data['password'] = "" ;
$this->Session->setFlash('Invalid login/id or password.');
}
}
}
Then, in beforeFilter or isAuthorized you can check $this->Auth->user('type');. For example, in AppController:
public function isAuthorized () {
/* Assuming you keep CakePHP convention where action starting with admin_ set admin params. */
if(isset($this->params['admin'])) {
return $this->Auth->user('type') == 'admin' ;
}
return true ;
}
Or if you want to disable access for non admin users to all action in AdminController, use beforeFilter:
class AdminController extends AppController {
public function beforeFilter () {
if (!$this->Auth->loggedIn()) {
$this->Session->setFlash('You need to be logged to access this page.');
$this->redirect(/* Login url. */) ;
return ;
}
if ($this->Auth->user('type') != 'admin') {
$this->Session->setFlash('You need to be admin to access this page.');
$this->redirect(/* Somewhere... */) ;
return ;
}
return parent::beforeFilter () ;
}
}

JSON view not working after implementing authorization in CakePHP

Since I implemented authorization in my cakephp project, it seens I can't access my views with JSON anymore. Do I have to set a specific setting anywhere to make this work again?
I was hoping adding autoComplete to the allowedActions and isAuthorized would do the trick
app\Controller\BrandsController.php (stripped from unnecessary code)
<?php
App::uses('Sanitize', 'Utility');
class BrandsController extends AppController {
public $helpers = array('Html', 'Form', 'Session');
public $components = array('Session', 'RequestHandler');
public function beforeFilter() {
parent::beforeFilter();
$this->Auth->allowedActions = array('autoComplete');
}
public function isAuthorized($user) {
if ($this->action === 'view' || $this->action === 'index' || $this->action === 'autoComplete') {
return true;
}
return parent::isAuthorized($user);
}
public function autoComplete($name = null)
{
if(!$name)
{
$name = #$this->params->query['name'];
}
$name = Sanitize::paranoid($name, array(' '));
if (!$name || empty($name))
{
new NotFoundException(__('Invalid searchquery'));
}
else
{
$objects = $this->Brand->find('list',
array(
'conditions' => array("Brand.name LIKE" => "%" . $name . "%")
)
);
$this->layout = 'ajax';
$this->RequestHandler->setContent('json', 'application/json' );
$this->set(compact('objects'));
$this->render('json/output_json');
}
}
}
?>
app\View\Brands\json\output_json.ctp
<?php
Configure::write('debug', 0);
echo json_encode($objects);
?>
Call
<?php
$brandsAutoCompleteUrl = Router::url(array('controller' => 'brands', 'action' => 'autoComplete'));
?>
<script type="text/javascript">
$(document).ready(function(){
$.getJSON('<?php echo $brandsAutoCompleteUrl; ?>', { name: 'test' }, function(data) {
// never called
});
});
</script>
In my Chrome debug window it sais
GET http://localhost/cakephp/brands/autoComplete?name=tes 500 (Internal Server Error)
When I call this url from my browser directly, I get the expected results
I fixed this problem by replacing
$this->RequestHandler->setContent('json', 'application/json' );
by
$this->response->type(array('json' => 'application/json'));
correct allowing is
$this->Auth->allow('view', 'index');
This can be done only in a controller that is owner of allowed actions -
Link to documentation
Also - in firefox firebug or in chrome you can view response for your 500 HTTP error that is usually an html code with an exact CakePHP error - investigate this first.
Why do you add components and helpers in this controller instead of AppController? This components are common for any controller - it is reasonable to put them to AppController.
And if you haven't noticed - all helpers and components of AppController are merged with helpers and components of child controllers. You can't switch off Auth component by replacing components in child controller.
Recommendation:
Avoid using of '#' for code like #$this->params->query['name'];. Use !empty($this->params->query['name']) in 'if' condition
If you don't know: empty($name) == !$name, also !$name can trigger error in variable $name is not initiated - http://www.php.net/manual/ru/types.comparisons.php (1st table)

How do I use Model in Helper - CakePHP 2.x

I recently upgraded my app from cake 1.3.x to cake 2.x. Now I have a helper which uses model in some function. Initially syntax for loading model was (working for 1.3.x)
App::import('Model', $modelName);
$modelObject = &ClassRegistry::getObject($modelName);
$modelObject->find()
Now I changed it to following
App::uses($modelName,'Model');
$modelObject = &ClassRegistry::getObject($modelName);
$modelObject->find()
Problem is, this conversion is not working. Can anybody tell me where am I doing wrong. Thanking in advance.
PS:
Error message is:
Call to a member function find() on a non-object
working code should be
//let $modelName be User
App::import("Model", "User");
$model = new User();
$model->find("list");
I hope this will help some needy fellow
You can also load model in helper, add following method in helper class:
Step1:
public function loadModel($modelClass = null, $id = null) {
if ($modelClass === null) {
$modelClass = $this->modelClass;
}
$this->uses = ($this->uses) ? (array) $this->uses : array();
if (!in_array($modelClass, $this->uses, true)) {
$this->uses[] = $modelClass;
}
list($plugin, $modelClass) = pluginSplit($modelClass, true);
$this->{$modelClass} = ClassRegistry::init(array(
'class' => $plugin . $modelClass, 'alias' => $modelClass, 'id' => $id
));
if (!$this->{$modelClass}) {
throw new MissingModelException($modelClass);
}
return true;
}
Step2: call above method as you do in controller like this:
$this->loadModel(ModelName);
I hope this would resolve problem
I wanna go through helper to view data In the form of foreach
public function display()
{
$links = TableRegistry::getTableLocator()->get('category');
$category = $links->find('all')
->where(['status' => 1, 'position' => 0])
->select([
"category" => 'parent', 'name'
])
->toArray();
return $category['category'];
}

Is there a better way to change user password in cakephp using Auth?

I am learning cakephp by myself. I tried to create a user controller with a changepassword function. It works, but I am not sure if this is the best way, and I could not googled up useful tutorials on this.
Here is my code:
class UsersController extends AppController {
var $name = 'Users';
function login() {
}
function logout() {
$this->redirect($this->Auth->logout());
}
function changepassword() {
$session=$this->Session->read();
$id=$session['Auth']['User']['id'];
$user=$this->User->find('first',array('conditions' => array('id' => $id)));
$this->set('user',$user);
if (!empty($this->data)) {
if ($this->Auth->password($this->data['User']['password'])==$user['User']['password']) {
if ($this->data['User']['passwordn']==$this->data['User']['password2']) {
// Passwords match, continue processing
$data=$this->data;
$this->data=$user;
$this->data['User']['password']=$this->Auth->password($data['User']['passwordn']);
$this->User->id=$id;
$this->User->save($this->data);
$this->Session->setFlash('Password changed.');
$this->redirect(array('controller'=>'Toners','action' => 'index'));
} else {
$this->Session->setFlash('New passwords differ.');
}
} else {
$this->Session->setFlash('Typed passwords did not match.');
}
}
}
}
password is the old password, passwordn is the new one, password2 is the new one retyped.
Is there any other, more coomon way to do it in cake?
I see that you validate and manipulate data in the controller. Doing this in a model is generally a better practice. I implemented similar functionality just a few days ago. My change_password() method looks somewhat like this:
# app/controllers/users_controller.php
function change_password() {
if (!empty($this->data)) {
if ($this->User->save($this->data)) {
$this->Session->setFlash('Password has been changed.');
// call $this->redirect() here
} else {
$this->Session->setFlash('Password could not be changed.');
}
} else {
$this->data = $this->User->findById($this->Auth->user('id'));
}
}
And here's a stripped down version of the view used with that method:
# app/views/users/change_password.ctp
echo $this->Form->create('User');
echo $this->Form->input('id');
echo $this->Form->input('current_password');
echo $this->Form->input('password1');
echo $this->Form->input('password2');
echo $this->Form->end('Submit');
The code that does something interesting is in the model. I added the fields from the form to the validate property and wrote custom validation methods. This allows me to use password1 and password2 fields in any other place in the application, for example, on the registration form.
# app/models/user.php
var $validate = array(
'current_password' => array(
'rule' => 'checkCurrentPassword',
'message' => '...'
),
'password1' => array(
'rule' => 'checkPasswordStrength',
'message' => '...',
),
'password2' => array(
'rule' => 'passwordsMatch',
'message' => '...',
)
);
Finally, in the beforeSave() callback of the model I set password to the hash of password1 to prepare the data to be stored it in the database.
The solution provided by Mike is great, but he left out the "checkCurrentPassword" function. Here is an example of that function you can place in your Model:
# app/models/user.php
public function checkCurrentPassword($data) {
$this->id = AuthComponent::user('id');
$password = $this->field('password');
return(AuthComponent::password($data['current_password']) == $password);
}
This solution gets the current user ID from the Auth component and changes the model to point to that particular user. Then it compares the hash of the current_password entered on the form with the hashed password stored for that user.
Also, here is the beforeSave function you can use to hash the new password:
# app/models/user.php
public function beforeSave($options = array()) {
if (isset($this->data[$this->alias]['password1'])) {
$this->data[$this->alias]['password'] = AuthComponent::password($this->data[$this->alias]['password1']);
}
return true;
}
You can use the latest version of the cakeDC plugin. This plugin gives you all functionality of all functions related to login, logout, reset password, change password, etc. You can find the latest version here.
You can simply use the:-
Step1)
$password = $this->Auth->password($this->data['User']['password']); // It will generate the hashed password using the cakephp's Auth component.
Step2)
if($this->User->update(array('User.password'=>$password), array('User.id'=>$this->Auth->User('id')))) {
echo $this->Session->setFlash('Password changed successfully.', 'default',
array('class'=>'successMsg'));
}

Resources