Sunday, August 30, 2015

Cakephp Simple Acl Controlled Application

This article will cover all following aspects

  1. How to install cakephp, 
  2. How to set environment variable path on window 7,  
  3. How to generate code by using bake console feature of cakephp 
  4. How to permission users on the basis of their groups (ACL) .
To achieve first point just click on  How to install cakephp

Then set environment variable path  Click here

Execute Following query if you created database in during installation 

CREATE TABLE users (
    id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(255) NOT NULL UNIQUE,
    password CHAR(40) NOT NULL,
    group_id INT(11) NOT NULL,
    created DATETIME,
    modified DATETIME
);

CREATE TABLE groups (
    id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    created DATETIME,
    modified DATETIME
);

CREATE TABLE posts (
    id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    user_id INT(11) NOT NULL,
    title VARCHAR(255) NOT NULL,
    body TEXT,
    created DATETIME,
    modified DATETIME
);

CREATE TABLE widgets (
    id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    part_no VARCHAR(12),
    quantity INT(11)
);

These are the tables we will be using to build the rest of our application. Once we have the table structure in the database we can start cooking. Use Code Generation with Bake to quickly create your models, controllers, and views. 
To bake you just need to follow this video, in video we only baked Group model so repeat same step for User, Post and Widget model. This will have generated the 4 controllers, models and your views for you.

Video

Our third point would be completed here.
Now we are going to set permission to users on the basis of their Group. Which is our last 4th point.

Open app/Controller/UsersController.php and paste following function in that

public function login() {
    if ($this->request->is('post')) {
        if ($this->Auth->login()) {
            return $this->redirect($this->Auth->redirectUrl());
        }
        $this->Flash->error(__('Your username or password was incorrect.'));
    }
}
 
public function logout() {
    //Leave empty for now.
}

Create login.ctp  in app/View/Users/login.ctp and put following code in that

<?php
echo $this->Form->create('User', array('action' => 'login'));
echo $this->Form->inputs(array(
    'legend' => __('Login'),
    'username',
    'password'
));
echo $this->Form->end('Login');
?>

 In app/Model/User.php add the following code

public function beforeSave($options = array()) {
        $this->data['User']['password'] = AuthComponent::password(
          $this->data['User']['password']
        );
        return true;
    }

Open  /app/Controller/AppController.php and put following code in AppController class

public $components = array(
        'Acl',
        'Auth' => array(
            'authorize' => array(
                'Actions' => array('actionPath' => 'controllers')
            )
        ),
        'Session',
                    'Flash'
    );
    public $helpers = array('Html', 'Form', 'Session');
 
    public function beforeFilter() {
        //Configure AuthComponent
        $this->Auth->loginAction = array(
          'controller' => 'users',
          'action' => 'login'
        );
        $this->Auth->logoutRedirect = array(
          'controller' => 'users',
          'action' => 'login'
        );
        $this->Auth->loginRedirect = array(
          'controller' => 'posts',
          'action' => 'add'
        );
    }

In both your /app/Controller/GroupsController.php and your /app/Controller/UsersController.php Add the following code

public function beforeFilter() {
    parent::beforeFilter();
 
    // For CakePHP 2.1 and up
    $this->Auth->allow();
}

Create ACL tables , you can do in two ways first is by Command and second by execution of SQL, so just do one which you like most

1) Execute following command if you want to do by command

cake schema create DbAcl

see image 

2) Execute following query if you want to do by sql 
you can see SQL in app/Config/Schema/db_acl.sql

Acts As a Requester

Open app/Model/User.php  and put the below code.

public $actsAs = array('Acl' => array('type' => 'requester', 'enabled' => false));

public function parentNode() {
        if (!$this->id && empty($this->data)) {
            return null;
        }
        if (isset($this->data['User']['group_id'])) {
            $groupId = $this->data['User']['group_id'];
        } else {
            $groupId = $this->field('group_id');
        }
        if (!$groupId) {
            return null;
        }
        return array('Group' => array('id' => $groupId));
    }
 
public function bindNode($user) {
    return array('model' => 'Group', 'foreign_key' => $user['User']['group_id']);
}



Open app/Model/Group.php  and put the below code.

public $actsAs = array('Acl' => array('type' => 'requester'));
 
    public function parentNode() {
        return null;
    }

Now Run application http://localhost/acl/groups/add

Add Following Groups
·         administrators
·         managers
·         users

Then Add users for each  group,  
http://localhost/acl/users/add

Note: acl is my project name so do change if your project name is different.

Creating ACOs (Access Control Objects)

Now that we have our users and groups (aros), we can begin inputting our existing controllers into the Acl and setting permissions for our groups and users, as well as enabling login / logout.
Our ARO are automatically creating themselves when new users and groups are created. What about a way to auto-generate ACOs from our controllers and their actions? Well unfortunately there is no magic way in CakePHP’s core to accomplish this. The core classes offer a few ways to manually create ACO’s though. You can create ACO objects from the Acl shell OR by Query. Creating Acos from the shell looks like below OR execute below query

By Shell or Command
cake acl create aco root controllers

see image 


By Query
INSERT INTO `acos` (`id`, `parent_id`, `model`, `foreign_key`, `alias`, `lft`, `rght`) VALUES
(1, NULL, NULL, NULL, 'controllers', 1, 2);

An Automated tool for creating ACOs

As mentioned before, there is no pre-built way to input all of our controllers and actions into the Acl. However, we all hate doing repetitive things like typing in what could be hundreds of actions in a large application.
For this purpose exists a very handy plugin available on GitHub, called AclExtras which can be downloaded in The GitHub Downloads page.

https://github.com/markstory/acl_extras/zipball/master

After download just extract zip file and rename folder “AclExtras”  and put “AclExtras”  folder in  app/Plugin/

Now open /app/Config/boostrap.php   and Put the below code to load plugin
CakePlugin::load('AclExtras');

Finally execute the following command in the CakePHP console
cake AclExtras.AclExtras aco_sync

see image


Setting permission for all  Groups

Add below code in /app/Controller/UsersController.php

public function initDB() {
    $group = $this->User->Group;
 
    // Allow admins to everything
    $group->id = 1;
    $this->Acl->allow($group, 'controllers');
 
    // allow managers to posts and widgets
    $group->id = 2;
    $this->Acl->deny($group, 'controllers');
    $this->Acl->allow($group, 'controllers/Posts');
    $this->Acl->allow($group, 'controllers/Widgets');
         $this->Acl->allow($group, 'controllers/users/logout');
 
    // allow users to only add and edit on posts and widgets
    $group->id = 3;
    $this->Acl->deny($group, 'controllers');
    $this->Acl->allow($group, 'controllers/Posts/add');
    $this->Acl->allow($group, 'controllers/Posts/edit');
    $this->Acl->allow($group, 'controllers/Widgets/add');
    $this->Acl->allow($group, 'controllers/Widgets/edit');
 
    // allow basic users to log out
    $this->Acl->allow($group, 'controllers/users/logout');
 
    // we add an exit to avoid an ugly "missing views" error message
    echo "all done";
    exit;
}

Now run initDB function on browser by below link you will get all done message.

After execution  remove this line $this->Auth->allow();  from  beforeFilter function and also remove initDB function from usersController.
Also remove this line $this->Auth->allow();  from  beforeFilter function  groupsController.

We now have set up some basic access rules. We’ve allowed administrators to everything. Managers can access everything in posts and widgets. While users can only access add and edit in posts & widgets.

You may have noticed that I deliberately left out index and view from my Acl permissions. We are going to make view and index public actions in PostsController and WidgetsController. This allows non-authorized users to view these pages, making them public pages.

So add below beforeFilter function  in /app/Controller/PostsController.php and /app/Controller/WidgetsController.php 

public function beforeFilter() {
    parent::beforeFilter();
    $this->Auth->allow('index', 'view');
}

In AppController::beforeFilter() add the following line
$this->Auth->allow('display');


This makes the ‘display’ action public. This will keep our PagesController::display() public. This is important as often the default routing has this action as the home page for your application.


Replace Login and logout function in /app/Controller/UsersController.php with below Login and logout function.

              public function login() {
                                if ($this->request->is('post')) {
                                                if ($this->Auth->login()) {
                                                                return $this->redirect($this->Auth->redirectUrl());
                                                }
                                                $this->Flash->error(__('Your username or password was incorrect.'));
                                }
                                if ($this->Session->read('Auth.User')) {
                                                $this->Flash->success('You are logged in!');
                                                return $this->redirect('/');
                                }
                }
               
                public function logout() {
                                $this->Flash->success(__('Good-Bye Ubed'));
                                $this->redirect($this->Auth->logout());
                }

When access is denied Auth messages will be displayed if you added the echo $this->Session->flash('auth'); 
just below  $this->flash->render   in  app\View\Layouts\default.ctp 


Hope you will enjoy ACL , Good or Bad, both comments will be appreciate. ;)

Read More »