Book Image

CakePHP 1.3 Application Development Cookbook

Book Image

CakePHP 1.3 Application Development Cookbook

Overview of this book

CakePHP is a rapid development framework for PHP that provides an extensible architecture for developing, maintaining, and deploying web applications. While the framework has a lot of documentation and reference guides available for beginners, developing more sophisticated and scalable applications require a deeper knowledge of CakePHP features, a challenge that proves difficult even for well established developers.The recipes in this cookbook will give you instant results and help you to develop web applications, leveraging the CakePHP features that allow you to build robust and complex applications. Following the recipes in this book you will be able to understand and use these features in no time. We start with setting up authentication on a CakePHP application. One of the most important aspects of a CakePHP application: the relationship between models, also known as model bindings. Model binding is an integral part of any application's logic and we can manipulate it to get the data we need and when we need. We will go through a series of recipes that will show us how to change the way bindings are fetched, what bindings and what information from a binding is returned, how to create new bindings, and how to build hierarchical data structures. We also define our custom find types that will extend the three basic ones, allowing our code to be even more readable and also create our own find type, with pagination support. This book also has recipes that cover two aspects of CakePHP models that are fundamental to most applications: validation, and behaviors.
Table of Contents (17 chapters)
CakePHP 1.3 Application Development Cookbook
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface

Setting up Access Control Layer-based authentication


The more roles an application has, the more complex its Access Control Layer becomes. Luckily, one of the authentication schemes provided by the Auth component allows us to easily define which actions are accessible by certain roles (known as groups), using command-line tools. In this recipe, you will learn how to set up ACL on your application.

Getting ready

We should have a table to hold the roles, named groups.

If you do not have one already, create it using the following statement:

CREATE TABLE `groups`(
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
PRIMARY KEY(`id`)
);

If you do not have any records in your groups table, create some by running the following SQL statement:

INSERT INTO `groups`(`id`, `name`) VALUES
(1, 'Administrator'),
(2, 'Manager'),
(3, 'User');

We must also have a users table to hold the users, which should contain a field (named group_id) to contain a reference to the group a user belongs to. If you do not have such a table, create it using the following statement:

CREATE TABLE `users`(
`id` INT NOT NULL AUTO_INCREMENT,
`group_id` INT NOT NULL,
`username` VARCHAR(255) NOT NULL,
`password` CHAR(40) NOT NULL,
PRIMARY KEY(`id`),
KEY `group_id`(`group_id`),
CONSTRAINT `users__groups` FOREIGN KEY(`group_id`) REFERENCES `groups`(`id`)
);

We also need to have the ARO / ACO tables initialized. Using your operating system console, switch to your application directory, and run:

  • If you are on a GNU Linux / Mac / Unix system:

    ../cake/console/cake schema create DbAcl
    
  • If you are on Microsoft Windows:

    ..\cake\console\cake.bat schema create DbAcl
    

How to do it...

Note

The following initial steps are very similar to what is shown in Setting up a basic authentication system. However, there are some differences between the two that are crucial, so make sure you go through these instructions carefully.

  1. 1. Create a controller for the User model (in a file named users_controller.php placed inside your app/controllers folder), which should contain the following:

    <?php
    class UsersController extends AppController {
    public function login() {
    }
    public function logout() {
    $this->redirect($this->Auth->logout());
    }
    }
    ?>
    
  2. 2. Create a file named login.ctp in your app/views/users folder (create the folder if you do not have one already), with the following contents:

    <?php
    echo $this->Form->create(array('action'=>'login'));
    echo $this->Form->inputs(array(
    'legend' => 'Login',
    'username',
    'password'
    ));
    echo $this->Form->end('Login');
    ?>
    
  3. 3. Create a file named app_controller.php in your app/ folder. Make sure it contains the following:

    <?php
    class AppController extends Controller {
    public $components = array(
    'Acl',
    'Auth' => array(
    'authorize' => 'actions',
    'loginRedirect' => array(
    'admin' => false,
    'controller' => 'users',
    'action' => 'dashboard'
    )
    ),
    'Session'
    );
    }
    ?>
    
  4. 4. Modify the UsersController class and add the following code before its login() method:

    public function beforeFilter() {
    parent::beforeFilter();
    $this->Auth->allow('add');
    }
    public function add() {
    if (!empty($this->data)) {
    $this->User->create();
    if ($this->User->save($this->data)) {
    $this->Session->setFlash('User created!');
    $this->redirect(array('action'=>'login'));
    } else {
    $this->Session->setFlash('Please correct the errors');
    }
    }
    $this->set('groups', $this->User->Group->find('list'));
    }
    
  5. 5. Add the view for the action in the folder app/views/users by creating a file named add.ctp with the following contents:

    <?php
    echo $this->Form->create();
    echo $this->Form->inputs(array(
    'legend' => 'Signup',
    'username',
    'password',
    'group_id'
    ));
    echo $this->Form->end('Submit');
    ?>
    
  6. 6. Create a file named group.php and place it in your app/models folder with the following contents:

    <?php
    class Group extends AppModel {
    public $actsAs = array('Acl' => 'requester');
    public function parentNode() {
    if (empty($this->id) && empty($this->data)) {
    return null;
    }
    $data = $this->data;
    if (empty($data)) {
    $data = $this->find('first', array(
    'conditions' => array('id' => $this->id),
    'fields' => array('parent_id'),
    'recursive' => -1
    ));
    }
    if (!empty($data[$this->alias]['parent_id'])) {
    return $data[$this->alias]['parent_id'];
    }
    return null;
    }
    }
    ?>
    
  7. 7. Create a file named user.php and place it in your app/models folder with the following contents:

    <?php
    class User extends AppModel {
    public $belongsTo = array('Group');
    public $actsAs = array('Acl' => 'requester');
    public function parentNode() {
    }
    public function bindNode($object) {
    if (!empty($object[$this->alias]['group_id'])) {
    return array(
    'model' => 'Group',
    'foreign_key' => $object[$this->alias]['group_id']
    );
    }
    }
    }
    ?>
    

    Note

    Take note of the IDs for all the records in your groups table, as they are needed to link each group to an ARO record.

  8. 8. Run the following commands in your console (change the references to 1, 2, 3 to meet your own group IDs, if they are different).

    • If you are on a GNU Linux / Mac / Unix system, the commands are:

      ../cake/console/cake acl create aro root Groups
      ../cake/console/cake acl create aro Groups Group.1
      ../cake/console/cake acl create aro Groups Group.2
      ../cake/console/cake acl create aro Groups Group.3
      
    • If you are on Microsoft Windows, the commands are:

      ..\cake\console\cake.bat acl create aro root Groups
      ..\cake\console\cake.bat acl create aro Groups Group.1
      ..\cake\console\cake.bat acl create aro Groups Group.2
      ..\cake\console\cake.bat acl create aro Groups Group.3
      
  9. 9. Add the following code at the end of your UsersController class definition:

    public function dashboard() {
    $groupName = $this->User->Group->field('name',
    array('Group.id'=>$this->Auth->user('group_id'))
    );
    $this->redirect(array('action'=>strtolower($groupName)));
    }
    public function user() {
    }
    public function manager() {
    }
    public function administrator() {
    }
    
  10. 10. Create a view for each of these actions, and put some distinctive content on each one of them to reflect which view is being rendered. Therefore, you have to create three files:

    • app/views/users/user.ctp

    • app/views/users/manager.ctp

    • app/views/users/administrator.ctp.

    For example the contents for user.ctp could simply be:

    <h1>Dashboard (User)</h1>
    
  11. 11. We have to tell ACL about these restricted actions. Run the following commands in your console.

    • If you are on a GNU Linux / Mac / Unix system, the commands are:

      ../cake/console/cake acl create aco root controllers
      ../cake/console/cake acl create aco controllers Users
      ../cake/console/cake acl create aco controllers/Users logout
      ../cake/console/cake acl create aco controllers/Users user
      ../cake/console/cake acl create aco controllers/Users manager
      ../cake/console/cake acl create aco controllers/Users administrator
      
    • If you are on Microsoft Windows, the commands are:

      ..\cake\console\cake.bat acl create aco root controllers
      ..\cake\console\cake.bat acl create aco controllers Users
      ..\cake\console\cake.bat acl create aco controllers/Users logout
      ..\cake\console\cake.bat acl create aco controllers/Users user
      ..\cake\console\cake.bat acl create aco controllers/Users manager
      ..\cake\console\cake.bat acl create aco controllers/Users administrator
      
  12. 12. Finally, we have to grant permissions by linking each ARO (groups) to each ACO (controller's actions). Run the following commands in your console.

    • If you are on a GNU Linux / Mac / Unix system, the commands are:

      ../cake/console/cake acl grant Group.1 controllers/Users all
      ../cake/console/cake acl grant Group.2 controllers/Users/logout all
      ../cake/console/cake acl grant Group.2 controllers/Users/manager all
      ../cake/console/cake acl grant Group.3 controllers/Users/logout all
      ../cake/console/cake acl grant Group.3 controllers/Users/user all
      
    • If you are on Microsoft Windows, the commands are:

      ..\cake\console\cake.bat acl grant Group.1 controllers/Users all
      ..\cake\console\cake.bat acl grant Group.2 controllers/Users/logout all
      ..\cake\console\cake.bat acl grant Group.2 controllers/Users/manager all
      ..\cake\console\cake.bat acl grant Group.3 controllers/Users/logout all
      ..\cake\console\cake.bat acl grant Group.3 controllers/Users/user all
      

    We now have a fully working ACL based authentication system. We can add new users by browsing to http://localhost/users/add, logging in with http://localhost/users/login, and finally logging out with http://localhost/users/logout.

Users should only have access to http://localhost/users/user, managers to http://localhost/users/manager, and administrators should be able to access all those actions, including http://localhost/users/administrator.

How it works...

When setting the authorize configuration option of the Auth component to actions, and after adding Acl to the list of controller-wide components, CakePHP will check to see if the current action being accessed is a public action. If this is not the case, it will check for a logged-in user with a matching ACO record. If there is no such record, it will deny access.

Once there is a matching ACO for the controller action, it will use the bindNode method in the User model to see how a user record is matched to an ARO. The method implementation we added specifies that a user record should be looked up in the aros table by means of the group that the user belongs to.

After having both the matching ACO and ARO, it lastly checks to see whether there is a valid permission set up (in the aros_acos table) for the given ARO and ACO records. If it finds one, it allows access, otherwise it will reject authorization.

It is of vital importance that each record in the groups table has a matching ARO record. We set that association by issuing aro create commands to link each group ID to an ARO record of the form Group.ID, where ID is the actual ID.

Similarly, all controller actions that are not within the defined public actions should have a matching ACO record. Just as with AROs, we create the association between controller's actions and ACOs issuing aco create commands, setting the ACO name to be the action name, and making them child of an ACO which name is the controller name.

Finally, to grant the permission of an ARO (group) to an ACO (controller's actions), we issue acl grant commands, specifying as the first argument the ARO (Group.ID) and the second argument either a whole controller (such as controllers/Users), or a specific controller action (such as controllers/Users/logout). The last argument to the grant command (all) simply gives a further control of the type of access, and makes more sense when using ACL to control access to custom objects, or when using the crud authentication scheme.

There's more...

While developing an application, the task of matching each controller action to an ACO may be somewhat troublesome. Fortunately, several people in the CakePHP community felt the need for an easier solution. One of the solutions that I'd recommend is adopting acl_extras, a plugin developed by Mark Story, the lead developer of the CakePHP 1.3 release. By using this plugin, you will be able to continuously synchronize your controllers with the acos table. Find more about it, including its installation instructions, at http://github.com/markstory/acl_extras.

See also

  • Using prefixes for role-based access control.