Browse Source

Add support for contained Role array in TinyAuth and add missing tests

euromark 12 years ago
parent
commit
2f917954a5

+ 35 - 23
Controller/Component/Auth/TinyAuthorize.php

@@ -1,5 +1,6 @@
 <?php
 App::uses('Inflector', 'Utility');
+App::uses('Hash', 'Utility');
 App::uses('BaseAuthorize', 'Controller/Component/Auth');
 
 if (!defined('CLASS_USER')) {
@@ -34,7 +35,7 @@ if (!defined('ACL_FILE')) {
  */
 class TinyAuthorize extends BaseAuthorize {
 
-	protected $_matchArray = array();
+	protected $_acl = null;
 
 	protected $_defaults = array(
 		'allowUser' => false, # quick way to allow user access to non prefixed urls
@@ -53,7 +54,6 @@ class TinyAuthorize extends BaseAuthorize {
 		if (Cache::config($settings['cache']) === false) {
 			throw new CakeException(__d('dev', 'TinyAuth could not find `%s` cache - expects at least a `default` cache', $settings['cache']));
 		}
-		$this->_matchArray = $this->_getRoles();
 	}
 
 	/**
@@ -66,12 +66,12 @@ class TinyAuthorize extends BaseAuthorize {
 	 *
 	 * @param array $user The user to authorize
 	 * @param CakeRequest $request The request needing authorization.
-	 * @return boolean
+	 * @return bool Success
 	 */
 	public function authorize($user, CakeRequest $request) {
 		if (isset($user[$this->settings['aclModel']])) {
-			if (isset($user[$this->settings['aclModel']]['id'])) {
-				$roles = (array)$user[$this->settings['aclModel']]['id'];
+			if (isset($user[$this->settings['aclModel']][0]['id'])) {
+				$roles = Hash::extract($user[$this->settings['aclModel']], '{n}.id');
 			} else {
 				$roles = (array)$user[$this->settings['aclModel']];
 			}
@@ -88,7 +88,8 @@ class TinyAuthorize extends BaseAuthorize {
 	/**
 	 * validate the url to the role(s)
 	 * allows single or multi role based authorization
-	 * @return bool $success
+	 *
+	 * @return bool Success
 	 */
 	public function validate($roles, $plugin, $controller, $action) {
 		$action = Inflector::underscore($action);
@@ -102,29 +103,36 @@ class TinyAuthorize extends BaseAuthorize {
 			}
 		}
 
-		if (isset($this->_matchArray[$controller]['*'])) {
-			$matchArray = $this->_matchArray[$controller]['*'];
-			if (in_array(-1, $matchArray)) {
+		if ($this->_acl === null) {
+			$this->_acl = $this->_getAcl();
+		}
+
+		// controller wildcard
+		if (isset($this->_acl[$controller]['*'])) {
+			$matchArray = $this->_acl[$controller]['*'];
+			if (in_array('-1', $matchArray)) {
 				return true;
 			}
 			foreach ($roles as $role) {
-				if (in_array($role, $matchArray)) {
+				if (in_array((string)$role, $matchArray)) {
 					return true;
 				}
 			}
 		}
+
+		// specific controller/action
 		if (!empty($controller) && !empty($action)) {
-			if (array_key_exists($controller, $this->_matchArray) && !empty($this->_matchArray[$controller][$action])) {
-				$matchArray = $this->_matchArray[$controller][$action];
+			if (array_key_exists($controller, $this->_acl) && !empty($this->_acl[$controller][$action])) {
+				$matchArray = $this->_acl[$controller][$action];
 
-				# direct access? (even if he has no roles = GUEST)
-				if (in_array(-1, $matchArray)) {
+				// direct access? (even if he has no roles = GUEST)
+				if (in_array('-1', $matchArray)) {
 					return true;
 				}
 
-				# normal access (rolebased)
+				// normal access (rolebased)
 				foreach ($roles as $role) {
-					if (in_array($role, $matchArray)) {
+					if (in_array((string)$role, $matchArray)) {
 						return true;
 					}
 				}
@@ -134,7 +142,7 @@ class TinyAuthorize extends BaseAuthorize {
 	}
 
 	/**
-	 * @return object $User: the User model
+	 * @return object The User model
 	 */
 	public function getModel() {
 		return ClassRegistry::init(CLASS_USER);
@@ -146,9 +154,13 @@ class TinyAuthorize extends BaseAuthorize {
 	 * improved speed by several actions before caching:
 	 * - resolves role slugs to their primary key / identifier
 	 * - resolves wildcards to their verbose translation
-	 * @return array $roles
+	 * @return array Roles
 	 */
-	protected function _getRoles() {
+	protected function _getAcl($path = null) {
+		if ($path === null) {
+			$path = APP . 'Config' . DS;
+		}
+
 		$res = array();
 		if ($this->settings['autoClearCache'] && Configure::read('debug') > 0) {
 			Cache::delete($this->settings['cacheKey'], $this->settings['cache']);
@@ -156,10 +168,10 @@ class TinyAuthorize extends BaseAuthorize {
 		if (($roles = Cache::read($this->settings['cacheKey'], $this->settings['cache'])) !== false) {
 			return $roles;
 		}
-		if (!file_exists(APP . 'Config' . DS . ACL_FILE)) {
-			touch(APP . 'Config' . DS . ACL_FILE);
+		if (!file_exists($path . ACL_FILE)) {
+			touch($path . ACL_FILE);
 		}
-		$iniArray = parse_ini_file(APP . 'Config' . DS . ACL_FILE, true);
+		$iniArray = parse_ini_file($path . ACL_FILE, true);
 
 		$availableRoles = Configure::read($this->settings['aclModel']);
 		if (!is_array($availableRoles)) {
@@ -201,7 +213,7 @@ class TinyAuthorize extends BaseAuthorize {
 							continue;
 						}
 						$newRole = Configure::read($this->settings['aclModel'] . '.' . strtolower($role));
-						if (!empty($res[$controllerName][$actionName]) && in_array($newRole, $res[$controllerName][$actionName])) {
+						if (!empty($res[$controllerName][$actionName]) && in_array((string)$newRole, $res[$controllerName][$actionName])) {
 							continue;
 						}
 						$res[$controllerName][$actionName][] = $newRole;

+ 274 - 0
Test/Case/Controller/Component/Auth/TinyAuthorizeTest.php

@@ -0,0 +1,274 @@
+<?php
+/**
+ * TinyAuthorizeTest file
+ *
+ * 2012-11-05 ms
+ */
+//App::uses('AuthComponent', 'Controller/Component');
+App::uses('TinyAuthorize', 'Tools.Controller/Component/Auth');
+App::uses('MyCakeTestCase', 'Tools.TestSuite');
+App::uses('Controller', 'Controller');
+App::uses('ComponentCollection', 'Controller');
+App::uses('CakeRequest', 'Network');
+
+/**
+ * Test case for DirectAuthentication
+ *
+ * @package       Cake.Test.Case.Controller.Component.Auth
+ */
+class TinyAuthorizeTest extends MyCakeTestCase {
+
+	public $fixtures = array('core.user', 'core.auth_user');
+
+	public $Collection;
+
+	public $request;
+
+/**
+ * setup
+ *
+ * @return void
+ */
+	public function setUp() {
+		parent::setUp();
+		//$this->Controller = new Controller();
+		$this->Collection = new ComponentCollection();
+
+		//$this->auth = new TinyAuthorize($this->Collection, array());
+		//$User = ClassRegistry::init('User');
+		$this->request = new CakeRequest(null, false);
+
+		$aclData = <<<INI
+[Users]
+; add = public
+edit = user
+admin_index = admin
+[Comments]
+; index is public
+add,edit,delete = user
+* = admin
+[Tags]
+add = *
+public_action = public
+INI;
+		file_put_contents(TMP . 'acl.ini', $aclData);
+		$this->assertTrue(file_exists(TMP . 'acl.ini'));
+
+		Configure::write('Role', array('user' => 1, 'moderator' => 2, 'admin' => 3, 'public' => -1));
+	}
+
+	public function tearDown() {
+		unlink(TMP . 'acl.ini');
+
+		parent::tearDown();
+	}
+
+	/**
+	 * test applying settings in the constructor
+	 *
+	 * @return void
+	 */
+	public function testConstructor() {
+		$object = new TestTinyAuthorize($this->Collection, array(
+			'aclModel' => 'AuthRole',
+			'aclKey' => 'auth_role_id',
+			'autoClearCache' => true,
+		));
+		$this->assertEquals('AuthRole', $object->settings['aclModel']);
+		$this->assertEquals('auth_role_id', $object->settings['aclKey']);
+	}
+
+	/**
+	 * @return void
+	 */
+	public function testGetAcl() {
+		$object = new TestTinyAuthorize($this->Collection, array('autoClearCache' => true));
+		$res = $object->getAcl();
+
+		$expected = array(
+			'users' => array(
+				'edit' => array(1),
+				'admin_index' => array(3)
+			),
+			'comments' => array(
+				'add' => array(1),
+				'edit' => array(1),
+				'delete' => array(1),
+				'*' => array(3),
+			),
+			'tags' => array(
+				'add' => array(1, 2, 3, -1),
+				'public_action' => array(-1)
+			),
+		);
+		$this->debug($res);
+		$this->assertEquals($expected, $res);
+	}
+
+	/**
+	 * @return void
+	 */
+	public function testBasicUserMethodDisallowed() {
+		$this->request->params['controller'] = 'users';
+		$this->request->params['action'] = 'edit';
+
+		$object = new TestTinyAuthorize($this->Collection, array('autoClearCache' => true));
+		$this->assertEquals('Role', $object->settings['aclModel']);
+		$this->assertEquals('role_id', $object->settings['aclKey']);
+
+		$user = array(
+			'role_id' => 4,
+		);
+		$res = $object->authorize($user, $this->request);
+		$this->assertFalse($res);
+
+		$user = array(
+			'role_id' => 3,
+		);
+		$res = $object->authorize($user, $this->request);
+		$this->assertFalse($res);
+	}
+
+	/**
+	 * @return void
+	 */
+	public function testBasicUserMethodAllowed() {
+		$this->request->params['controller'] = 'users';
+		$this->request->params['action'] = 'edit';
+
+		$object = new TestTinyAuthorize($this->Collection, array('autoClearCache' => true));
+
+		// single role_id field in users table
+		$user = array(
+			'role_id' => 1,
+		);
+		$res = $object->authorize($user, $this->request);
+		$this->assertTrue($res);
+
+		$this->request->params['action'] = 'admin_index';
+
+		$user = array(
+			'role_id' => 3,
+		);
+		$res = $object->authorize($user, $this->request);
+		$this->assertTrue($res);
+	}
+
+	/**
+	 * @return void
+	 */
+	public function testBasicUserMethodAllowedMultiRole() {
+		$this->request->params['controller'] = 'users';
+		$this->request->params['action'] = 'admin_index';
+
+		$object = new TestTinyAuthorize($this->Collection, array('autoClearCache' => true));
+
+		// flat list of roles
+		$user = array(
+			'Role' => array(1, 3),
+		);
+		$res = $object->authorize($user, $this->request);
+		$this->assertTrue($res);
+
+		// verbose role defition using the new 2.x contain param for Auth
+		$user = array(
+			'Role' => array(array('id' => 1, 'RoleUser' => array()), array('id' => 3, 'RoleUser' => array())),
+		);
+		$res = $object->authorize($user, $this->request);
+		$this->assertTrue($res);
+	}
+
+	/**
+	 * @return void
+	 */
+	public function testBasicUserMethodAllowedWildcard() {
+		$this->request->params['controller'] = 'tags';
+		$this->request->params['action'] = 'public_action';
+
+		$object = new TestTinyAuthorize($this->Collection, array('autoClearCache' => true));
+
+		$user = array(
+			'role_id' => 6,
+		);
+		$res = $object->authorize($user, $this->request);
+		$this->assertTrue($res);
+	}
+
+	/**
+	 * @return void
+	 */
+	public function testUserMethodsAllowed() {
+		$this->request->params['controller'] = 'users';
+		$this->request->params['action'] = 'some_action';
+
+		$object = new TestTinyAuthorize($this->Collection, array('allowUser' => true, 'autoClearCache' => true));
+
+		$user = array(
+			'role_id' => 1,
+		);
+		$res = $object->authorize($user, $this->request);
+		$this->assertTrue($res);
+
+		$this->request->params['controller'] = 'users';
+		$this->request->params['action'] = 'admin_index';
+
+		$object = new TestTinyAuthorize($this->Collection, array('allowUser' => true, 'autoClearCache' => true));
+
+		$user = array(
+			'role_id' => 1,
+		);
+		$res = $object->authorize($user, $this->request);
+		$this->assertFalse($res);
+
+		$user = array(
+			'role_id' => 3,
+		);
+		$res = $object->authorize($user, $this->request);
+		$this->assertTrue($res);
+	}
+
+	/**
+	 * Should only be used in combination with Auth->allow() to mark those as public in the acl.ini, as well.
+	 * Not necessary and certainly not recommended as acl.ini only.
+	 *
+	 * @return void
+	 */
+	public function testBasicUserMethodAllowedPublically() {
+		$this->request->params['controller'] = 'tags';
+		$this->request->params['action'] = 'add';
+
+		$object = new TestTinyAuthorize($this->Collection, array('autoClearCache' => true));
+
+		$user = array(
+			'role_id' => 2,
+		);
+		$res = $object->authorize($user, $this->request);
+		$this->assertTrue($res);
+
+		$this->request->params['controller'] = 'comments';
+		$this->request->params['action'] = 'foo';
+
+		$user = array(
+			'role_id' => 3,
+		);
+		$res = $object->authorize($user, $this->request);
+		$this->assertTrue($res);
+	}
+
+}
+
+class TestTinyAuthorize extends TinyAuthorize {
+
+	public function matchArray() {
+		return $this->_matchArray;
+	}
+
+	public function getAcl() {
+		return $this->_getAcl();
+	}
+
+	protected function _getAcl($path = TMP) {
+		return parent::_getAcl($path);
+	}
+
+}