Today I’ve got a new solution about data level authorization. Let me start with the problem case the topic is about.
Users Jack and Andy has a Policy records in the database. One record belongs to Jack and another to Andy. System should allow Jack to make CRUD only on his records, and behave the same way for Andy. This means Andy do not see and is not able to modify Jacks’ records in the database.

To provide requred functionality we will create new model behaviour class and attach it to the model we want to personalize. The behaiviour will act much as interceptor design pattern. Here is the code of /app/models/behaviors/owner.php

<?php 
/**
 * Data level authorization to enable authenticated user to access only own records.
 *
 * @filesource
 * @author Rostislav Palivoda
 * @link http://www.palivoda.eu
 * @version	$Revision$
 * @license	http://www.opensource.org/licenses/mit-license.php The MIT License
 * @package app
 * @subpackage app.models.behaviors
 */
class OwnerBehavior extends ModelBehavior {
 
	/**
	 * - ownerFiled [ string ]
	 *      The model filed name that identifies owner.
	 *	 
	 * @version 0.1
	 * @since   0.1
	 * @access  private
	 * @var     array
	 */
 
    var $settings = array(
        'ownerFiled'       			=> 'user_id'
    );
 
	/**
	 * Startup hook from the model
	 *
	 * @version 0.1
	 * @since   0.1
	 * @access  public
	 * @param   AppModel $model
	 * @param   array $config
	 */
    function setup(&$model, &$config = array()) {
		/**
		 * If there was given any config settings to this behavior, merge them
		 * with our default settings array
		 * If the input arrays have the same string keys, then the later value for that key will overwrite the previous one
		 */
        if( !is_null( $config ) && is_array( $config ) ) {
            $this->settings = array_merge( $this->settings, $config );
        }
    }
 
	/**
	 * Sets owner identifier to identifier or current authenticated user.
	 *
	 * @version 0.1
	 * @since   0.1
	 * @access  public
	 * @param   AppModel $model
	 * @return  boolean True to continue execution, false to cancel execution.
	 */
	function beforeValidate(&$model) {
		if (!$model->exists()) {
			$model->data[$model->name][$this->settings['ownerFiled']] = $this->getAuthUserId();
		}
		return true;
	}
 
	/**
	 * Appends owner filter to the query data
	 *
	 * @version 0.1
	 * @since   0.1
	 * @access  public
	 * @param   AppModel $model
	 * @param  $queryData
	 * @return  boolean True to continue execution, false to cancel execution.
	 */
	function beforeFind(&$model, &$queryData) {
		$queryData['conditions'][$model->name.'.'.$this->settings['ownerFiled']]=$this->getAuthUserId();
		return true;
	}
 
	/**
	 * Cancels request if it's not from the owner
	 *
	 * @version 0.1
	 * @since   0.1
	 * @access  public
	 * @param   AppModel $model
	 * @return  boolean True to continue execution, false to cancel execution.
	 */
	function beforeSave(&$model) {
		$ownerId = $model->field($this->settings['ownerFiled'], array('id' => $model->id));	
		return $ownerId == $this->getAuthUserId() || !$model->exists();
	}
 
	/**
	 * Cancels request if it's not from the owner
	 *
	 * @version 0.1
	 * @since   0.1
	 * @access  public
	 * @param   AppModel $model
	 * @return  boolean True to continue execution, false to cancel execution.
	 */
	function beforeDelete(&$model, &$cascade = true) {
		$ownerId = $model->field($this->settings['ownerFiled'], array('id' => $model->id));	
		return $ownerId == $this->getAuthUserId();
	}
 
	/**
	 * Get id of current authenticated user. If no user in session then 0.
	 *
	 * @version 0.1
	 * @since   0.1
	 * @access  protect
	 * @return  int Authenticated user identifier.
	 */
	function getAuthUserId() {
		try {
			return $_SESSION['Auth']['User']['id'];
		}
		catch (Exception $e) {
			return 0;
		}
	}	
 
}
?>

By default our behaviour expects user_id column in the database to authorize against. With beforeValidate functions all created models will have user_id the same as identifier of current authenticated user. With beforeSave and beforeDelete functions current user will not be able to save or delete the records of other users. It’s up to you to modify getAuthUserId function to retrieve current user identifier.
To attach behaviour to the model just add it to the list of $astAs, like:

var $actsAs   = array('owner');

If you need to turn off the owner behavior and select all records then write following line in the action:

unset($this->Model->behaviors['owner']);

Have a nice day.

Posted by Rostislav Palivoda, filed under PHP. Date: April 4, 2008, 3:20 pm |

Leave a Comment

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.