Chapter 8. Handling Security in a Zend Framework Project

Security should be the first and foremost concern of any web application project. The same tools that you are building to make it easy for your clients to manage their sites can be leveraged by hackers if you're not careful. This is a serious responsibility that should not be taken lightly.

The good news is that the Zend Framework developers take security very seriously and have built a stable, well-tested set of components that make it easier to write more secure programs. These components include Zend_Auth and Zend_Acl.

  • Zend_Auth is solely concerned with authenticating (and persisting) the application users.

  • Zend_Acl handles resources (pages), roles (user roles), and which roles can access which resources.

By separating these areas of responsibility, you are able to manage users, and the access they are allowed, depending on the unique needs of your particular project.

In the case of the CMS you're building in this book, you will manage the users with the database; you already have the database set up for the content, so this will be the easiest way. Implementing your site security scheme encompasses several steps:

  1. Create the tools to manage users.

  2. Create a way for users to log in and out.

  3. Add access control to security-sensitive parts of the site.

  4. Integrate the access control into the application.

Managing CMS Users

Anyone who visits a site can be considered a user. From an anonymous visitor to your site administrators, everyone has a role. The CMS uses these roles to determine whether the user has permission to access restricted areas of the site or specific resources, such as files. You can have as many roles as you need, but I generally try to keep things as simple as possible. Initially, you will have two roles:

  • Users: These are registered users who don't have admin privileges.

  • Administrators: These are the site managers who can access any area of the CMS.

User Data and Model

As mentioned earlier, you will store the CMS user data in a database table. At a minimum, this table will need to store the username, password, and role. You will also add fields for a user's first and last names. You can add or remove fields depending on your specific project. To create the users table, run the SQL statement shown in Listing 8-1.

Example 8.1. SQL Statement to Create the users Table

CREATE TABLE `users` (
  `id` int(11) NOT NULL auto_increment,
  `username` varchar(50) default NULL,
  `password` varchar(250) default NULL,
  `first_name` varchar(50) default NULL,
  `last_name` varchar(50) default NULL,
  `role` varchar(25) default NULL,
  PRIMARY KEY  (`id`)
) DEFAULT CHARSET=utf8;

Now that you've created the users table, you need to set up a model to manage it. Create a file in the application/models folder named User.php. Open this file, and create the User model class, as shown in Listing 8-2.

Example 8.2. The User Model Class in application/user/models/User.php

<?php
require_once 'Zend/Db/Table/Abstract.php';
class Model_User extends Zend_Db_Table_Abstract {
    /**
     * The default table name
     */
    protected $_name = 'users';
}

Creating a New User

The process of managing your users will be very similar to that of your pages and menus. This consistency makes it much easier to both develop and maintain applications.

Creating the User Controller

Now that you have the user model set up, the next step is to create a controller to manage the users. You can do this with Zend_Tool using the command in Listing 8-3.

Example 8.3. Creating the User Controller with Zend_Tool

zf create controller user

This will create the controller, its view folder, and the index action/view script.

Creating the User Form

Now you're ready to create the user form. Create a new file in application/forms named User.php, and then create the new user form, as shown in Listing 8-4.

Example 8.4. The User Form in application/forms/User.php

<?php
class Form_User extends Zend_Form
{
    public function init()
    {
        $this->setMethod('post'),

        // create new element
        $id = $this->createElement('hidden', 'id'),
        // element options
        $id->setDecorators(array('ViewHelper'));
        // add the element to the form
        $this->addElement($id);

        //create the form elements
        $username = $this->createElement('text','username'),
        $username->setLabel('Username: '),
        $username->setRequired('true'),
        $username->addFilter('StripTags'),
        $username->addErrorMessage('The username is required!'),
        $this->addElement($username);

        $password = $this->createElement('password', 'password'),
        $password->setLabel('Password: '),
        $password->setRequired('true'),
        $this->addElement($password);

        $firstName = $this->createElement('text','first_name'),
        $firstName->setLabel('First Name: '),
        $firstName->setRequired('true'),
        $firstName->addFilter('StripTags'),
        $this->addElement($firstName);

        $lastName = $this->createElement('text','last_name'),
        $lastName->setLabel('Last Name: '),
        $lastName->setRequired('true'),
        $lastName->addFilter('StripTags'),
        $this->addElement($lastName);

        $role = $this->createElement('select', 'role'),
$role->setLabel("Select a role:");
        $role->addMultiOption('User', 'user'),
        $role->addMultiOption('Administrator', 'administrator'),
        $this->addElement($role);

        $submit = $this->addElement('submit', 'submit', array('label' => 'Submit'));
    }
}
?>

Rendering the Create User Form

Once the form is set up, the next step is to render it. This requires a controller action and view script. You can create this action and view script with Zend_Tool using the command in Listing 8-5.

Example 8.5. Creating the create user Action with Zend_Tool

zf create action create user

This command creates the createAction() method in the user controller. Open the user controller, and locate this method. Now create a new instance of the user form, and set the form action to /user/create, as shown in Listing 8-6. Then pass the form instance to the view to render.

Example 8.6. Loading the Create User Form in application/controllers/UserController.php

public function createAction()
{
    $userForm = new Form_User();
    $userForm->setAction('/user/create'),
    $this->view->form = $userForm;
}

The next step is to update the view script that Zend_Tool created. Open application/views/scripts/user/create.phtml, and set the page title and headline as you did with the view scripts you created earlier. Then render the form. You can do this by simply echoing it; Zend_Form has a view helper that builds the form's XHTML and renders it, as shown in Listing 8-7.

Example 8.7. The Create User View in application/user/views/scripts/user/create.phtml

<h2>Create a new user</h2>
<p>To create a new admin user complete this form and click submit...</p>
<?php echo $this->form; ?>

Now if you point your browser to http://localhost/user/create, you should see the create user form, as shown in Figure 8-1.

The create user form

Figure 8.1. The create user form

Processing the Form

Processing the form is a two-step process. The controller will load and validate the data but will rely on the User model to write the data to the database.

You can use the Zend_Db_Table method directly from the controller, but I prefer to add a method to the model class to do this. You will create this function first and then update the controller. Open the User model class, and add a createUser method. This method will need to take the username, password, first name, last name, and admin role as arguments. It will first create a new row and then set each of the column values (Listing 8-8).

Example 8.8. The createUser Method in application/user/models/User.php

public function createUser($username, $password, $firstName, $lastName, $role)
{
    // create a new row
    $rowUser = $this->createRow();
    if($rowUser) {
        // update the row values
        $rowUser->username = $username;
        $rowUser->password = md5($password);
        $rowUser->first_name = $firstName;
        $rowUser->last_name = $lastName;
        $rowUser->role = $role;
        $rowUser->save();
        //return the new user
        return $rowUser;
    } else {
        throw new Zend_Exception("Could not create user!");
    }
}

Now that you have the method to add a new user to the database, you are ready to process the form. When you submit the create user form, it posts back to the createAction() method in the user controller, but the action does nothing but load and render the form. You need to update this function so it detects whether the form has been posted back. You do this with the request->isPost() function. If it is posted back, then populate the form with the POST data, and check to see whether the form is valid. If it is, you pass the form data to the createUser method and redirect to the list action, which you will create in the next section (see Listing 8-9).

Example 8.9. The Updated createAction in application/user/controllers/UserController.php

public function createAction ()
{
    $userForm = new Form_User();
    if ($this->_request->isPost()) {
        if ($userForm->isValid($_POST)) {
            $userModel = new Model_User();
            $userModel->createUser(
                $userForm->getValue('username'),
                $userForm->getValue('password'),
                $userForm->getValue('first_name'),
                $userForm->getValue('last_name'),
                $userForm->getValue('role')
            );
            return $this->_forward('list'),        }
    }
    $userForm->setAction('/user/create'),
    $this->view->form = $userForm;
}

Managing Existing Users

Now that there is a way to add users to the database, you need a way to manage them. First you will need a list of all the current users. The best way to do this is to create a new method in the user model. I like to use static methods for these types of functions; it makes the method easier to use. Create a new static method in the user model to get the current users, as in Listing 8-10.

Example 8.10. Getting the Users from application/user/models/User.php

public static function getUsers()
{
    $userModel = new self();
    $select = $userModel->select();
    $select->order(array('last_name', 'first_name'));
    return $userModel->fetchAll($select);
}

Then you need methods to update their accounts and delete them if necessary. To get started, create the list user action with Zend_Tool using the command in Listing 8-11.

Example 8.11. Creating the list user Action with Zend_Tool

zf create action list user

Since there will be no form processing done in the listAction() method, it is very simple. It will just load the current users and pass them to the view.

First create an instance of the user model. Then fetch all the current users. If the query returns users, then pass them to the view to render, as shown in Listing 8-12.

Example 8.12. The list users Action in application/controllers/UserController.php

public function listAction ()
{
    $currentUsers = Model_User::getUsers();
    if ($currentUsers->count() > 0) {
        $this->view->users = $currentUsers;
    } else {
        $this->view->users = null;
    }
}

The index view script will be a little more complicated than the previous ones. It needs to display a list of all the current users. Zend_View makes this very easy with its partialLoop() helper. This helper takes two arguments: the path to a script to render for each row and the data. You will render the users in a table, so this partialLoop will need to render a table row for each user. You will need to create this partial script first. Create a new file in application/views/scripts/partials named _user-row.phtml.

This partial script will render a table row. It needs fields for the user's username, first name, last name, and role. It also needs to render links to update and delete the user. Note that the partialLoop helper casts the values of the row to view variables, as shown in Listing 8-13.

Example 8.13. User Row Partial in application/views/scripts/partials/_user-row.phtml

<tr>
    <td class='links'>
        <a href='/user/update/id/<?php echo $this->id;?>'>Update</a>
        <a href='/user/delete/id/<?php echo $this->id;?>'>Delete</a>
    </td>
    <td><?php echo $this->last_name ?></td>
    <td><?php echo $this->first_name ?></td>
    <td><?php echo $this->username ?></td>
    <td><?php echo $this->role ?></td>
</tr>

With the data and partial in place, you are ready to create the index view script. First you need to set the page title and headline. Then check whether there are any users. If there are, then create a table and let the partialLoop helper render the user rows. If there are no users, then display a message. You should also add a create user link since it will be the main user management page (Listing 8-14).

Example 8.14. The User List in application/views/scripts/user/list.phtml

<h2>Current Users</h2>
<?php
if($this->users != null) {
?>
<table class='spreadsheet' cellpadding='0' cellspacing='0'>
    <tr>
        <th>Links</th>
        <th>Last Name</th>
        <th>First Name</th>
        <th>Username</th>
        <th>Role</th>
        <th>
    </tr>
    <?php echo $this->partialLoop('partials/_user-row.phtml', $this->users); ?>
</table>
<?php }else{?>
<p>You do not have any users yet.</p>
<?php }?>
<p><a href='/user/create'>Create a new user</a></p>

Once this is done, add a few test users. After you successfully create a new user, you should be directed to the list page, which looks like Figure 8-2.

The user list

Figure 8.2. The user list

You should note that the user row has links to update or delete the user. These links assume that there is an update and a delete action in the user controller, which you will create next.

Note

The user list view will be the main admin page for CMS users. You may want to add this page to the admin menu that you created in Chapter 7.

Updating Users

Updating a user is similar to creating a user in many ways. If a form has been posted back, it processes it. If not, it renders the form, loading it with the user's current account information.

The first thing you need to do is create the update user action. You can do this with Zend_Tool, as shown in Listing 8-15.

Example 8.15. Creating the update user Action with Zend_Tool

zf create action update user

Now open the updateAction() method in the user controller. In this method, you need to create a new instance of the user form.

Now you need to remove the password element from the form. This is done because the password is encrypted using one-way encryption, which means that you can't just fetch the value to populate the password field.

The user list passed the user's ID as a URL parameter; you need to fetch it and load the user row. Then you need to load the user form with this data and pass it to the view to render, as shown in Listing 8-16.

Example 8.16. The update user Action in application/controllers/UserController.php

public function updateAction ()
{
      $userForm = new Form_User();
      $userForm->setAction('/user/update'),
      $userForm->removeElement('password'),
      $id = $this->_request->getParam('id'),
      $userModel = new Model_User();
      $currentUser = $userModel->find($id)->current();
      $userForm->populate($currentUser->toArray());
      $this->view->form = $userForm;
}

Next you need to update the view script that Zend_Tool created for the update action. This is a very straightforward script that is nearly identical to the create action's view (see Listing 8-17).

Example 8.17. The Update User View Script in application/views/scripts/user/update.phtml

<h2>Update user</h2>
<?php echo $this->form; ?>

Next, you'll process the form and update the user. This once again is very similar to the create action, except you will need to separate the updateUser() method (see Listing 8-18) and the updatePassword() method (see Listing 8-19) functions in the User model so you can update the password conditionally. Both of these methods will work like the createUser() method, updating values on a Zend_Db_Table_Row. The difference is that rather than creating a new row, you will find the row that matches the ID that is passed and update that.

Example 8.18. The updateUser Method in application/user/models/User.php

public function updateUser($id, $username, $firstName, $lastName, $role)
{
    // fetch the user's row
    $rowUser = $this->find($id)->current();

    if($rowUser) {
        // update the row values
        $rowUser->username = $username;
        $rowUser->first_name = $firstName;
        $rowUser->last_name = $lastName;
        $rowUser->role = $role;
        $rowUser->save();
        //return the updated user
        return $rowUser;
    }else{
        throw new Zend_Exception("User update failed.  User not found!");
    }
}

Example 8.19. The updatePassword Method in application/user/models/User.php

public function updatePassword($id, $password)
{
    // fetch the user's row
    $rowUser = $this->find($id)->current();

    if($rowUser) {
        //update the password
        $rowUser->password = md5($password);
        $rowUser->save();
    }else{
        throw new Zend_Exception("Password update failed.  User not found!");
    }
}

Now that the update user and password methods are created, you need to update the user controller's update action like you did the create function. When the form is posted back, you need to validate the data that was posted back and update the user if it passes, as shown in Listing 8-20.

Example 8.20. The Updated Update Action in application/controllers/UserController.php

public function updateAction ()
{
    $userForm = new Form_User();
    $userForm->setAction('/user/update'),
    $userForm->removeElement('password'),
    $userModel = new Model_User();
    if ($this->_request->isPost()) {
if ($userForm->isValid($_POST)) {
            $userModel->updateUser(
                $userForm->getValue('id'),
                $userForm->getValue('username'),
                $userForm->getValue('first_name'),
                $userForm->getValue('last_name'),
                $userForm->getValue('role')
            );
            return $this->_forward('list'),         }
    } else {
        $id = $this->_request->getParam('id'),
        $currentUser = $userModel->find($id)->current();
        $userForm->populate($currentUser->toArray());
    }
    $this->view->form = $userForm;
}

Next you need to create a method to update the user's password. Create the password action using Zend_Tool, as shown in Listing 8-21.

Example 8.21. Creating the User Password Action Using Zend_Tool

zf create action password user

The password action and view script will be nearly identical to the update action's. It will use the same form as the update action but will remove all the form controls except the ID and password. Then it will populate the user's ID, but not the password. On the postback, it will validate the post data and then call the User model's updatePassword() method, as shown in Listing 8-22.

Example 8.22. The Password Action in application/controllers/UserController.php

public function passwordAction ()
{
    $passwordForm = new Form_User();
    $passwordForm->setAction('/user/password'),
    $passwordForm->removeElement('first_name'),
    $passwordForm->removeElement('last_name'),
    $passwordForm->removeElement('username'),
    $passwordForm->removeElement('role'),
    $userModel = new Model_User();
    if ($this->_request->isPost()) {
        if ($passwordForm->isValid($_POST)) {
            $userModel->updatePassword(
                $passwordForm->getValue('id'),
                $passwordForm->getValue('password')
            );
            return $this->_forward('list'),
        }
    } else {
        $id = $this->_request->getParam('id'),
$currentUser = $userModel->find($id)->current();
        $passwordForm->populate($currentUser->toArray());
    }
    $this->view->form = $passwordForm;
}

Now you have to update the views, so update the password view to render the form. You can copy the code from the update user view script; you need to update only the headline (see Listing 8-23) because you already updated the form in the controller.

Example 8.23. The Updated Password View Script in application/views/scripts/user/password.phtml

<h2>Update user password</h2>
<?php echo $this->form; ?>

Once the password update method is implemented, you should update the update user form, adding a link to update the password. Note that you can fetch values from the form (in the view), as shown in Listing 8-24.

Example 8.24. The Updated Update User Form in application/views/scripts/user/update.phtml

<h2>Update user</h2>
<?php echo $this->form; ?>
<p>
    <a href='/user/password/id/
               <?php echo $this->form->getElement('id')->getValue(); ?>'>
        Update Password
    </a>
</p>

Deleting Users

The final step in managing users is to create a method to delete them. This is the easiest part of the administration process since it does not require a form or view.

To do this, you need to create the delete user method in the User model. This method will try to find the user ID that is passed to it and will delete the row if it is successful, as shown in Listing 8-25.

Example 8.25. The deleteUser Method in application/user/models/User.php

public function deleteUser($id)
{
    // fetch the user's row
    $rowUser = $this->find($id)->current();
    if($rowUser) {
        $rowUser->delete();
    }else{
        throw new Zend_Exception("Could not delete user.  User not found!");
    }
}

Then you need to add the delete action to the admin controller. You can do this with Zend_Tool, but since it does not require a view, it is probably simpler to type it. This action will simply fetch the ID that is passed in the URL and pass it to the User model to delete. Once the user is deleted, it redirects to the user list (Listing 8-26).

Example 8.26. The Delete Action in application/controllers/UserController.php

public function deleteAction()
{

    $id = $this->_request->getParam('id'),
    $userModel = new Model_User();
    $userModel->deleteUser($id);
    return $this->_forward('list'),}

Note

You will probably want to add a function to the user list that confirms that the administrator truly wants to delete the user. You can do this with JavaScript, but it is beyond the scope of this book.

Authenticating Users with Zend_Auth

You will use the Zend_Auth component to handle user authentication for this CMS project. It provides an authentication API that is implemented using authentication adapters. These adapters implement the Zend_Auth_Adapter_Interface interface, which standardize the authentication methods regardless of the method you employ.

The framework comes with a number of concrete auth adapters for common authentication methods that include:

  • Database table authentication: This adapter authenticates against a database table.

  • Digest authentication: Digest authentication is an improved method of HTTP authentication that does not transmit the password in plain text across the network.

  • LDAP authentication: This method authenticates LDAP services.

  • Open ID: Open ID authentication creates a single digital identity that can be used across the Internet.

You will use Zend_Auth_Adapter_DbTable since you're storing the site users and their credentials in a database table already.

Creating the User Landing Page

The default user page will provide a link to log in if the current user is not logged in; otherwise, it will display a link to log out. When you created the user controller with Zend_Tool, it added the default action, indexAction(), to the controller. You need to update this to load the current user and pass it to the view. You will need to fetch the current instance of Zend_Auth to do this. Note that because Zend_Auth implements the singleton pattern, there is only one instance at any given time. Once you have the Zend_Auth, use it to check whether an identity is set and pass this to the view, as shown in Listing 8-27.

Example 8.27. The indexAction() in application/controllers/UserController.php

public function indexAction()
{
    $auth = Zend_Auth::getInstance();

    if($auth->hasIdentity()) {
        $this->view->identity = $auth->getIdentity();
    }
}

Next you need to update the view script for the user controller's index action, as shown in Listing 8-28. This view script will need to check and see whether the identity has been set. If not, it will display a link to the login form. If there is an identity, it will display a logout link instead.

Example 8.28. The Default User Page in application/views/scripts/user/index.phtml

<h2>My Account</h2>
<?php if($this->identity == null) { ?>
    <p>To log in <a href='/user/login'>click here</a></p>
<?php }else{ ?>
    <p>Welcome back <?php echo $this->identity->first_name;?></p>
    <p>To log out <a href='/user/logout'>click here</a></p>
<?php } ?>

Creating the User Login

The user login action will require a couple of components; you need to create the login action that will render a login form. Then you need to authenticate the user with the username and password given. The first step is to create the login action. You can do this with Zend_Tool, as shown in Listing 8-29.

Example 8.29. Creating the User Login Action with Zend_Tool

zf create action login user

Next you need to update the login action, loading the login form and passing it to the view to render. This is another example of a situation where you can reuse your user form—just remove all the elements except the username and password, as shown in Listing 8-30.

Example 8.30. Loading the Login Form in application/controllers/UserController.php

public function loginAction ()
{
    $userForm = new Form_User();
    $userForm->setAction('/user/login'),
    $userForm->removeElement('first_name'),
    $userForm->removeElement('last_name'),
    $userForm->removeElement('role'),
    $this->view->form = $userForm;
}

Then you need to update the view script to render the form, as shown in Listing 8-31. This script will need to render the login form the same way as you did with the create user form. It will also need to render one other thing, the login message. This is how you will tell the user whether their login attempt failed.

Example 8.31. The User Login Form in application/views/scripts/user/login.phtml

<h2>User Login</h2>
<p>To login to your account please enter your username and password below...</p>
<?php if($this->loginMessage) { ?>
<p><?php echo $this->loginMessage?></p>
<?php }
echo $this->form;
?>

Now you need to process this form and authenticate the user. This will take a few steps:

  1. Confirm that the form is valid.

  2. Get the Zend_Auth adapter. Pass this adapter the username and password the user entered, and then authenticate them.

  3. If the user is valid, save the identity in the Zend_Auth storage, which will store the identity in a PHP session by default. Then send them to the welcome page. Otherwise, let them know the username or password they entered was invalid, and display the form again.

Listing 8-32 shows the updated login action.

Example 8.32. The Updated loginAction() with User Authentication in application/controllers/UserController.php

public function loginAction ()
{
    $userForm = new Form_User();
    $userForm->setAction('/user/login'),
    $userForm->removeElement('first_name'),
    $userForm->removeElement('last_name'),
    $userForm->removeElement('role'),
if ($this->_request->isPost() && $userForm->isValid($_POST)) {
        $data = $userForm->getValues();
        //set up the auth adapter
        // get the default db adapter
        $db = Zend_Db_Table::getDefaultAdapter();
        //create the auth adapter
        $authAdapter = new Zend_Auth_Adapter_DbTable($db, 'users',
            'username', 'password'),
        //set the username and password
        $authAdapter->setIdentity($data['username']);
        $authAdapter->setCredential(md5($data['password']));
        //authenticate
        $result = $authAdapter->authenticate();
        if ($result->isValid()) {
            // store the username, first and last names of the user
            $auth = Zend_Auth::getInstance();
            $storage = $auth->getStorage();
            $storage->write($authAdapter->getResultRowObject(
                array('username' , 'first_name' , 'last_name', 'role')));
            return $this->_forward('index'),        } else {
            $this->view->loginMessage = "Sorry, your username or
                password was incorrect";
        }
    }
    $this->view->form = $userForm;
}

Logging Users Out

Now that users can log in, you need to create the function for them to log out. This is very simple with Zend_Auth; you just clear the Zend_Auth instance's identity. Create the logoutAction(), which you can do with the Zend_Tool command in Listing 8-33.

Example 8.33. Creating the User Logout Action with Zend_Tool

zf create action logout user

Next you need to fetch the current auth instance. Then clear the Zend_Auth identity using the clearIdentity() method, as shown in Listing 8-34.

Example 8.34. The Logout Action in application/controllers/UserController.php

public function logoutAction ()
{
    $authAdapter = Zend_Auth::getInstance();
    $authAdapter->clearIdentity();
}

Then you just need to render a simple confirmation message for the user, letting them know that they did log out successfully. Add the message shown in Listing 8-35 to the logout view script.

Example 8.35. The Logout Confirmation View in application/views/scripts/user/logout.phtml

<h2>User Logout</h2>
<p>You have successfully logged out...</p>

Adding User Controls to the Main CMS Interface

Now that the user authentication functions are set up, you need to make it easier for people to log in and log out. The user controller's index action displays these login/logout links; you only have to add it to the main site layout using the Zend_View action helper by adding the code in Listing 8-36 to the head of your site layout.

Example 8.36. Loading the User Links into the Site Layout in application/layouts/scripts/layout.phtml

<?php
echo '<?xml version="1.0" encoding="UTF-8" ?>';
echo $this->doctype();
$this->layout()->nav = $this->action('render', 'menu', null,
    array('menu' => $this->mainMenuId));
$this->layout()->adminMenu = $this->action(
    'render', 'menu', null, array('menu' => $this->adminMenuId)
);
$this->layout()->userForm = $this->action('index', 'user'),
?>

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <?php
    $this->loadSkin($this->skin);
    echo $this->headTitle();
    echo $this->headScript();
    echo $this->headLink();
    ?>
</head>

<body>
<div id="pageWrapper">
<div id="header">
<h1>Zend Framework CMS</h1>
</div>
<div id="nav">
            <?php
            echo $this->layout()->nav;
            ?>&nbsp;
        </div>
<div id="sidebar">
<div id="subNav">
            <?php
            echo $this->layout()->subNav;
            ?>&nbsp;
        </div>
<div id="adminMenu">
            <?php
            echo $this->layout()->adminMenu;
            ?>&nbsp;
        </div>
<div id="userForm">
            <?php
            echo $this->layout()->userForm;
            ?>&nbsp;
        </div>
</div>
<div id="main">
            <?php
            echo $this->layout()->content?>&nbsp;
        </div>
<div id="footer">
<p><em>Powered by the Zend Framework</em></p>
</div>
</div>
</body>
</html>

Once this is loaded into the layout file, go to the home page of your site. You should now see the My Account section with a link to log in or log out on each page, as shown in Figure 8-3.

The updated home page with login/logout links

Figure 8.3. The updated home page with login/logout links

Controlling Access with Zend_Acl

Zend_Acl provides a simple and straightforward implementation of access control lists (ACLs). ACL consists of two lists:

  • One list of resources (pages in your application)

  • One list of roles (access roles, such as administrator)

Zend_Acl manages these lists as well as the relationships between the two. It then provides a method, isAllowed(), which you use to query the lists.

Using Zend_Acl

The first thing you need to do to use Zend_Acl is set up the roles. These roles represent the users in your application. You add these roles with the addRole() method, which you pass a new instance of Zend_Acl_Role.

These roles also support inheritance, so you more easily manage your user hierarchy. For example, you may have authors and publishers in a model. Say the authors have access to manage content but not publish it. Then the publishers are authors themselves, but they also have the authority to publish content to the site. In this example the publishers would extend the authors.

Next you set up the resources, which represent the modules, controllers, and actions in your application. You add resources to Zend_Acl using the add() method, to which you pass a new instance of Zend_Acl_Resource.

Now that you have the roles and resources set, you can define the ACL rules-in other words, who can access which resources. You do this with the allow() and deny() methods.

Once these rules are set up, your instance of ACL is ready to query, as shown in the simplistic example in Listing 8-37.

Example 8.37. Sample Zend_Acl Usage

$acl = new Zend_Acl();

// create the user role
$acl->addRole(new Zend_Acl_Role('user'));

// create the admin role, which inherits all of the user's permissions
$acl->addRole(new Zend_Acl_Role('admin'), 'user'),

// add a new resource
$acl->add(new Zend_Acl_Resource('cms'));

// set access rules
$acl->allow('admin', 'cms'),
$acl->deny('guest', 'cms'),

// query acl
// this will print 'allowed'echo $acl->isAllowed('admin', 'cms') ? 'allowed' : 'denied';
// this will print 'denied'
echo $acl->isAllowed('guest', 'cms') ? 'allowed' : 'denied';

Securing Your CMS Project

Once all the pieces are in place, securing a Zend Framework project is a straightforward process. This is because of, in a large part, the front controller pattern. All the page requests are processed by the controller, which means you can implement your security model in one place for the whole application.

The front controller uses a plug-in system to enable you to add this custom functionality without altering the core code library. Again, when the Bootstrap class initializes the front controller, it registers any plug-ins that are specified in the application.ini file.

In this example it makes sense to create a controller plugin to manage the access control. By doing this you are able to intercept the request and validate it before it is dispatched.

To get started, create a new folder in the CMS library named Controller and then a subfolder in Controller named Plugin. Add a new file to this folder named Acl.php. Next create a new class in this file named CMS_Controller_Plugin_Acl, which should extend Zend_Controller_Plugin_Abstract, as shown in Listing 8-38.

Example 8.38. The ACL Controller Plug-in Class in /library/CMS/Controller/Plugin/Acl.php

<?php
class CMS_Controller_Plugin_Acl extends Zend_Controller_Plugin_Abstract
{
}

Controller plug-ins respond to a number of events that have corresponding methods. In this case, you want to validate the user's permissions after the request has been processed but before it is dispatched. Create a new method in the ACL plug-in called preDispatch(), as shown in Listing 8-39.

Example 8.39. The preDispatch Method in /library/CMS/Controller/Plugin/Acl.php

public function preDispatch(Zend_Controller_Request_Abstract $request)
{
}

The first thing you need to do in this method is create an instance of Zend_Acl. Next you add the roles for each kind of CMS user as well as an unauthenticated guest role. This project will have three types of users:

  • Guests: These are unauthenticated visitors

  • Users: These are authenticated visitors. At this point you are not using this role, but it is nice to have. For example, you may want to enable logged in users to comment on content items. The logged in users will inherit all of the rights from guests.

  • Administrator: The administrator manages the site. He has full access to the CMS.

Once you create the roles, you add a new resource for each of the controllers, as shown in Listing 8-40.

Example 8.40. Adding ACL Roles and Resources in the preDispatch() Method of /library/CMS/Controller/Plugin/Acl.php

// set up acl
$acl = new Zend_Acl();
// add the roles
$acl->addRole(new Zend_Acl_Role('guest'));
$acl->addRole(new Zend_Acl_Role('user'), 'guest'),
$acl->addRole(new Zend_Acl_Role('administrator'), 'user'),

// add the resources
$acl->add(new Zend_Acl_Resource('index'));
$acl->add(new Zend_Acl_Resource('error'));
$acl->add(new Zend_Acl_Resource('page'));
$acl->add(new Zend_Acl_Resource('menu'));
$acl->add(new Zend_Acl_Resource('menuitem'));
$acl->add(new Zend_Acl_Resource('user'));

Now you need to define the access rules. You can use two Zend_Acl methods to do this: allow() and deny(). You pass these methods three arguments: the role, the resources, and the permissions. See Listing 8-41 for an example of the rules for this CMS.

Note

If you want to grant a user access to any resources, pass a null to the resource argument in the allow method. I did this for the administrators.

Example 8.41. Setting the Access Rules in the preDispatch() Method of /library/CMS/Controller/Plugin/Acl.php

// set up the access rules
$acl->allow(null, array('index', 'error'));

// a guest can only read content and login
$acl->allow('guest', 'page', array('index', 'open'));
$acl->allow('guest', 'menu', array('render'));
$acl->allow('guest', 'user', array('login'));

// cms users can also work with content
$acl->allow('user', 'page', array('list', 'create', 'edit', 'delete'));

// administrators can do anything
$acl->allow('administrator', null);

With the ACL set up, you are ready to authenticate the user's request. First get the current user's role. If this is not set, then set the role to guest. Next, you query ACL, passing it the current role and request controller/action (see Listing 8-42). If there is an issue, you need to do one of two things. If the current user is a guest, then direct them to the login page. Otherwise, direct them to a "not authorized" error page, which you will create in one moment.

Example 8.42. Querying ACL in the preDispatch() Method of /library/CMS/Controller/Plugin/Acl.php

// fetch the current user
$auth = Zend_Auth::getInstance();
if($auth->hasIdentity()) {
    $identity = $auth->getIdentity();
    $role = strtolower($identity->role);
}else{
    $role = 'guest';
}

$controller = $request->controller;
$action = $request->action;

if (!$acl->isAllowed($role, $controller, $action)) {
    if ($role == 'guest') {
        $request->setControllerName('user'),
        $request->setActionName('login'),
    } else {
       $request->setControllerName('error'),
       $request->setActionName('noauth'),
   }
}

Once this plugin is set up, you need to register the plugin with the front controller. You can do this by passing the plugin to the front controller application resource in the application.ini file (see Listing 8-43).

Example 8.43. Registering the ACL Plugin with the Front Controller in application/configs/application.ini

resources.frontController.plugins.acl = "CMS_Controller_Plugin_Acl"

Finally, you need to create the "not authorized" error page, which you can do with Zend_Tool. Execute the command in Listing 8-44 from your command line.

Example 8.44. Creating the noauth Error Page with Zend_Tool

zf create action noauth error

Then update this page with an error message. This message should let the user know that they are not authorized to access the resource, as shown in Listing 8-45.

Example 8.45. The noauth Error Page in application/views/scripts/error/noauth.phtml

<h2>Error: Not Authorized!</h2>
<p>Sorry, you are not authorized to access this resource.</p>

Summary

In this chapter, you learned how to manage security in a Zend Framework application. You started by creating the tools to manage the CMS users. Then you built the login and logout functionality, which you integrated with Zend_Auth. Once users could log in and out, you set up the access control plug-in using Zend_Acl and secured the CMS project.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset