To Configure

<?php

Access::config(array(
	'permissions' => array(
		'adapter'   => 'Permissions',
		'model'     => 'app\models\Perms',
		'defaultNoUser'   => array(),
		'defaultUser' => array(
			'route' => array(
				'users' => array(
					'logout', 'account'
				)
			)
		),
		'userIdentifier' => 'PrincipalID'
	)
));

Perms::applyFilter('find', function($self, $params, $chain) {
	if($params['type'] != 'first') {
		return $chain->next($self, $params, $chain);
	}
	$cacheKey = 'permissions_' . $params['options']['conditions']['id'];
	$cache = Cache::read('default', $cacheKey);
	if($cache) {
		return $cache;
	}
	$result = $chain->next($self, $params, $chain);
	Cache::write('default', $cacheKey, $result, '+1 day');
	return $result;
});
?>

<?php

namespace li3_access\extensions\adapter\security\access;

use lithium\core\ConfigException;
use lithium\util\Set;

class Permissions extends \lithium\core\Object {
	const pathRoute = 'route';
	const pathCustom = 'custom';
	protected $_model = null;
	public function __construct(array $config = array()) {
		$defaults = array(
			'model' => 'app\models\perms',
			'defaultNoUser'  => array(),
			'defaultUser' => array(),
			'userIdentifier' => 'id'
		);
		parent::__construct($config + $defaults);
		$this->_model = $this->_config['model'];
	}

	/**
	 * @throws \lithium\core\ConfigException
	 * @param  $user
	 * @param  $request
	 * @param array $options
	 * @return array
	 */
	public function check($user, $request, array $options = array()) {
		$config = $this->_config;
		$model = $this->_model;
		$params = compact('user', 'request', 'options');
		return $this->_filter(__METHOD__, $params, function($self, $params) use($config, $model) {
			$user = $params['user'];
			$request = $params['request'];
			$options = $params['options'];
			$reqIsObject = is_object($request);
			$path = array();

			switch (true) {
				case $reqIsObject:
					$path = array(
						Permissions::pathRoute,
						$request->params['controller'],
						$request->params['action']
						);
					break;
				case (!$reqIsObject && is_string($request)):
					$path = explode('.', $request);
					array_unshift($path, Permissions::pathCustom);
					break;
				case (!$reqIsObject && is_array($request)):
					$path = $request;
					break;
			}
			switch(true) {
				case !$user:
					$hasAccess = $self->_processPath($path, $config['defaultNoUser']);
					return $hasAccess ? false : $options;
				case ($result = $self->_processPath($path, $config['defaultUser'])):
					return $result ? false : $options;
				default:
					$userId = $config['userIdentifier'];
					if(!isset($user[$userId])) {
						$message = "The user identifier '{$userId}' is not available.";
						throw new ConfigException($message);
					}
					$perms = $model::find('first', array(
						'conditions' => array(
							'id' =>  $user[$userId]
						)
					));
					if(!$perms) {
						return false;
					}
					$userPath = unserialize($perms->perms);
					$result = $self->_processPath($path, $userPath);
					return $result ? array() : $options;
			}
		});
	}

	/**
	 * Adds a custom route to the users permission list.
	 *
	 * $customRoute is formatted as a dot path string, this is done as 'foo.bar.baz' for example.
	 * Asterisks are usable at the end of the path however not in the middle. A user with access
	 * to 'foo.bar.*' will have access to foo.bar.baz, foo.bar.aaa etc.
	 *
	 * @param  $user
	 * @param  $customRoute
	 * @return bool
	 */
	public function addCustomPath($user, $customRoute) {
		if(!is_string($customRoute)) {
			return false;
		}
		$parts = explode('.', $customRoute);
		$value = array_pop($parts);
		$parts = array_merge((array)self::pathCustom, $parts, (array)0);
		return $this->add($user, Set::expand(array(implode('.', $parts) => $value)));
	}

	/**
	 * Adds an action to the users permission list. If the action is set to * the user will have
	 * access to all of the controllers actions.
	 */
	public function addAction($user, $controller, $action) {
		return $this->add($user, array(
			self::pathRoute => array(
				$controller => array(
					$action
				)
			)
		));
	}

	/**
	 * $user must contain the 'userIdentifier' key defined in config
	 * $paths are the paths which are to be added this is an array representation of the path and
	 * is from the origin, so 'route' or 'custom' must be specified. Multiple paths can be defined
	 * using this function
	 *
	 * @throws \lithium\core\ConfigException
	 * @param  $user
	 * @param array $paths
	 * @return bool
	 */
	public function add($user, array $paths = array()) {
		$model = $this->_model;
		$userId = $this->_config['userIdentifier'];
		$params = compact('model', 'userId', 'user', 'paths');
		return $this->_filter(__METHOD__, $params, function($self, $params) {
			$model = $params['model'];
			$userId = $params['userId'];
			$user = $params['user'];
			$paths = $params['paths'];

			if(!isset($user[$userId])) {
				throw new ConfigException("The user identifier '{$userId}' is not available.");
			}
			$result = $model::find('first', array(
				'conditions' => array(
					'id' => $user[$userId]
				)
			));
			if(!$result) {
				$perms = $model::create(array(
					'id' => $user[$userId],
					'perms' => serialize($paths)
				));
				return $perms->save();
			}
			$allowedPaths = unserialize($result->perms);
			$allowedPaths = array_merge_recursive($allowedPaths, $paths);
			$result->perms = serialize($allowedPaths);
			return $result->save();
		});
	}

	public function removeCustomPath($user, $customRoute) {
		if(!is_string($customRoute)) {
			return false;
		}
		$parts = explode('.', $customRoute);
		$value = array_pop($parts);
		$parts = array_merge((array)self::pathCustom, $parts, (array)0);
		return $this->remove($user, Set::expand(array(implode('.', $parts) => $value)));
	}

	/**
	 * Removes an action from a users permission list. Setting action to * removes all actions
	 * in the controller thus removing the controller from the users permission list.
	 */
	public function removeAction($user, $controller, $action) {
		return $this->remove($user, array(
			self::pathRoute => array(
				$controller => array(
					$action
				)
			)
		));
	}

	/**
	 * use this to remove permissions from a user, multiple permissions can be defined in the paths
	 * array. The user must have the configured userIdentifier available.
	 *
	 * @throws \lithium\core\ConfigException
	 * @param  $user
	 * @param array $paths
	 * @return bool
	 */
	public function remove($user, array $paths = array()) {
		$model = $this->_model;
		$userId = $this->_config['userIdentifier'];
		$params = compact('model', 'userId', 'user', 'paths');
		return $this->_filter(__METHOD__, $params, function($self, $params) {
			$model = $params['model'];
			$userId = $params['userId'];
			$user = $params['user'];
			$paths = $params['paths'];

			if (!isset($user[$userId])) {
				throw new ConfigException("The user identifier '{$userId}' is not available.");
			}
			$result = $model::find('first', array(
				'conditions' => array(
					'id' => $user[$userId]
				)
			));
			if (!$result) {
				return true;
			}
			$allowedPaths = unserialize($result->perms);
			$pathsFlat = Set::flatten($paths);
			foreach ($pathsFlat as $path => $value) {
				$pointer = &$allowedPaths;
				$pathParts = explode('.', $path);
				array_pop($pathParts);
				foreach ($pathParts as $pathPart) {
					if (!isset($pointer[$pathPart])) {
						unset($pointer);
						$pointer = null;
						break;
					}
					$pointer = &$pointer[$pathPart];
				}
				switch(true) {
					case !$pointer:
						break;
					case $value == '*':
						$pointer = null;
						break;
					case (($index = array_search($value, $pointer)) !== false):
						unset($pointer[$index]);
						break;
				}
			}
			$result->perms = serialize($self->_cleanPaths($allowedPaths));
			return $result->save();
		});
	}

	public function listPerms($user) {
		$userId = $user[$this->_config['userIdentifier']];
		$model  = $this->_model;
		$result = $model::find('first', array(
			'conditions' => array(
				'id' => $userId
			)
		));
		return $result ? unserialize($result->perms) : array();
	}

	public function _cleanPaths($paths) {
		foreach ($paths as &$path) {
			if (is_array($path)) {
				$path = $this->_cleanPaths($path);
			}
		}
		return array_filter($paths);
	}

	public function _processPath($path, &$allowedPaths) {
		$pointer = &$allowedPaths;
		foreach($path as $item) {
			switch(true) {
				case (in_array('*', $pointer)):
					return true;
				case (in_array($item, $pointer)):
					$pointer = array();
					continue;
				case (!isset($pointer[$item])):
					return false;
			}
			$pointer = &$pointer[$item];
		}
		return true;
	}
}

?>