diff --git a/library/Zend/Permissions/Rbac/AbstractIterator.php b/library/Zend/Permissions/Rbac/AbstractIterator.php new file mode 100644 index 00000000000..be9fd93cf47 --- /dev/null +++ b/library/Zend/Permissions/Rbac/AbstractIterator.php @@ -0,0 +1,102 @@ + + * Return the current element + * @link http://php.net/manual/en/iterator.current.php + * @return mixed Can return any type. + */ + public function current() + { + return $this->children[$this->index]; + } + + /** + * (PHP 5 >= 5.0.0)
+ * Move forward to next element + * @link http://php.net/manual/en/iterator.next.php + * @return void Any returned value is ignored. + */ + public function next() + { + $this->index++; + } + + /** + * (PHP 5 >= 5.0.0)
+ * Return the key of the current element + * @link http://php.net/manual/en/iterator.key.php + * @return scalar scalar on success, or null on failure. + */ + public function key() + { + return $this->index; + } + + /** + * (PHP 5 >= 5.0.0)
+ * Checks if current position is valid + * @link http://php.net/manual/en/iterator.valid.php + * @return boolean The return value will be casted to boolean and then evaluated. + * Returns true on success or false on failure. + */ + public function valid() + { + return isset($this->children[$this->index]); + } + + /** + * (PHP 5 >= 5.0.0)
+ * Rewind the Iterator to the first element + * @link http://php.net/manual/en/iterator.rewind.php + * @return void Any returned value is ignored. + */ + public function rewind() + { + $this->index = 0; + } + + /** + * (PHP 5 >= 5.1.0)
+ * Returns if an iterator can be created fot the current entry. + * @link http://php.net/manual/en/recursiveiterator.haschildren.php + * @return bool true if the current entry can be iterated over, otherwise returns false. + */ + public function hasChildren() + { + return count($this->children) > 0; + } + + /** + * (PHP 5 >= 5.1.0)
+ * Returns an iterator for the current entry. + * @link http://php.net/manual/en/recursiveiterator.getRoles.php + * @return RecursiveIterator An iterator for the current entry. + */ + public function getChildren() + { + return $this->children[$this->index]; + } +} diff --git a/library/Zend/Permissions/Rbac/AbstractRole.php b/library/Zend/Permissions/Rbac/AbstractRole.php new file mode 100644 index 00000000000..85de86a11a6 --- /dev/null +++ b/library/Zend/Permissions/Rbac/AbstractRole.php @@ -0,0 +1,124 @@ +name; + } + + /** + * Add permission to the role. + * + * @param $name + * @return AbstractRole + */ + public function addPermission($name) + { + $this->permissions[$name] = true; + + return $this; + } + + /** + * Checks if a permission exists for this role or any child roles. + * + * @param string $name + * @return bool + */ + public function hasPermission($name) + { + if (isset($this->permissions[$name])) { + return true; + } + + $it = new RecursiveIteratorIterator($this, RecursiveIteratorIterator::CHILD_FIRST); + foreach ($it as $leaf) { + /** @var AbstractRole $leaf */ + if ($leaf->hasPermission($name)) { + return true; + } + } + + return false; + } + + /** + * Add a child. + * + * @param AbstractRole|string $child + * @return Role + */ + public function addChild($child) + { + if (is_string($child)) { + $child = new Role($child); + } + if (!$child instanceof AbstractRole) { + throw new Exception\InvalidArgumentException( + 'Child must be a string or instance of Zend\Permissions\Rbac\AbstractRole' + ); + } + + $child->setParent($this); + $this->children[] = $child; + + return $this; + } + + /** + * @param AbstractRole $parent + * @return AbstractRole + */ + public function setParent($parent) + { + $this->parent = $parent; + + return $this; + } + + /** + * @return null|AbstractRole + */ + public function getParent() + { + return $this->parent; + } +} diff --git a/library/Zend/Permissions/Rbac/AssertionInterface.php b/library/Zend/Permissions/Rbac/AssertionInterface.php new file mode 100644 index 00000000000..868ed7fd616 --- /dev/null +++ b/library/Zend/Permissions/Rbac/AssertionInterface.php @@ -0,0 +1,27 @@ +createMissingRoles = $createMissingRoles; + + return $this; + } + + /** + * @return boolean + */ + public function getCreateMissingRoles() + { + return $this->createMissingRoles; + } + + /** + * Add a child. + * + * @param string|AbstractRole $child + * @return AbstractRole + * @throws Exception\InvalidArgumentException + */ + public function addRole($child, $parents = null) + { + if (is_string($child)) { + $child = new Role($child); + } + if (!$child instanceof AbstractRole) { + throw new Exception\InvalidArgumentException( + 'Child must be a string or instance of Zend\Permissions\Rbac\AbstractRole' + ); + } + + if ($parents) { + if (!is_array($parents)) { + $parents = array($parents); + } + foreach ($parents as $parent) { + if ($this->createMissingRoles && !$this->hasRole($parent)) { + $this->addRole($parent); + } + $this->getRole($parent)->addChild($child); + } + } + + $this->children[] = $child; + + return $this; + } + + /** + * Is a child with $name registered? + * + * @param \Zend\Permissions\Rbac\AbstractRole|string $objectOrName + * @return bool + */ + public function hasRole($objectOrName) + { + try { + $this->getRole($objectOrName); + + return true; + } catch (Exception\InvalidArgumentException $e) { + return false; + } + } + + /** + * Get a child. + * + * @param \Zend\Permissions\Rbac\AbstractRole|string $objectOrName + * @return AbstractRole + * @throws Exception\InvalidArgumentException + */ + public function getRole($objectOrName) + { + if (!is_string($objectOrName) && !$objectOrName instanceof AbstractRole) { + throw new Exception\InvalidArgumentException( + 'Expected string or instance of \Zend\Permissions\Rbac\AbstractRole' + ); + } + + $it = new RecursiveIteratorIterator($this, RecursiveIteratorIterator::CHILD_FIRST); + foreach ($it as $leaf) { + if ((is_string($objectOrName) && $leaf->getName() == $objectOrName) || $leaf == $objectOrName) { + return $leaf; + } + } + + throw new Exception\InvalidArgumentException(sprintf( + 'No child with name "%s" could be found', + is_object($objectOrName) ? $objectOrName->getName() : $objectOrName + )); + } + + /** + * Determines if access is granted by checking the role and child roles for permission. + * + * @param string $permission + * @param \Zend\Permissions\Rbac\AssertionInterface|Callable|null $assert + */ + public function isGranted($role, $permission, $assert = null) + { + if ($assert) { + if ($assert instanceof AssertionInterface) { + if (!$assert->assert($this)) { + return false; + } + } elseif (is_callable($assert)) { + if (!$assert($this)) { + return false; + } + } else { + throw new Exception\InvalidArgumentException( + 'Assertions must be a Callable or an instance of Zend\Permissions\Rbac\AssertionInterface' + ); + } + } + + if ($this->getRole($role)->hasPermission($permission)) { + return true; + } + + return false; + } +} diff --git a/library/Zend/Permissions/Rbac/Role.php b/library/Zend/Permissions/Rbac/Role.php new file mode 100644 index 00000000000..b83e2c818d9 --- /dev/null +++ b/library/Zend/Permissions/Rbac/Role.php @@ -0,0 +1,27 @@ +name = $name; + } +} diff --git a/tests/ZendTest/Permissions/Rbac/RbacTest.php b/tests/ZendTest/Permissions/Rbac/RbacTest.php new file mode 100644 index 00000000000..51342bdb01c --- /dev/null +++ b/tests/ZendTest/Permissions/Rbac/RbacTest.php @@ -0,0 +1,144 @@ +rbac = new Rbac\Rbac(); + } + + public function testIsGrantedAssertion() + { + $foo = new Rbac\Role('foo'); + $bar = new Rbac\Role('bar'); + + $true = new TestAsset\SimpleTrueAssertion(); + $false = new TestAsset\SimpleFalseAssertion(); + + $roleNoMatch = new TestAsset\RoleMustMatchAssertion($bar); + $roleMatch = new TestAsset\RoleMustMatchAssertion($foo); + + $foo->addPermission('can.foo'); + $bar->addPermission('can.bar'); + + $this->rbac->addRole($foo); + $this->rbac->addRole($bar); + + $this->assertEquals(true, $this->rbac->isGranted($foo, 'can.foo', $true)); + $this->assertEquals(false, $this->rbac->isGranted($bar, 'can.bar', $false)); + + $this->assertEquals(false, $this->rbac->isGranted($bar, 'can.bar', $roleNoMatch)); + $this->assertEquals(false, $this->rbac->isGranted($bar, 'can.foo', $roleNoMatch)); + + $this->assertEquals(true, $this->rbac->isGranted($foo, 'can.foo', $roleMatch)); + } + + public function testIsGrantedSingleRole() + { + $foo = new Rbac\Role('foo'); + $foo->addPermission('can.bar'); + + $this->rbac->addRole($foo); + + $this->assertEquals(true, $this->rbac->isGranted('foo', 'can.bar')); + $this->assertEquals(false, $this->rbac->isGranted('foo', 'can.baz')); + } + + public function testIsGrantedChildRoles() + { + $foo = new Rbac\Role('foo'); + $bar = new Rbac\Role('bar'); + + $foo->addPermission('can.foo'); + $bar->addPermission('can.bar'); + + $this->rbac->addRole($foo); + $this->rbac->addRole($bar, $foo); + + $this->assertEquals(true, $this->rbac->isGranted('foo', 'can.bar')); + $this->assertEquals(true, $this->rbac->isGranted('foo', 'can.foo')); + $this->assertEquals(true, $this->rbac->isGranted('bar', 'can.bar')); + + $this->assertEquals(false, $this->rbac->isGranted('foo', 'can.baz')); + $this->assertEquals(false, $this->rbac->isGranted('bar', 'can.baz')); + } + + public function testHasRole() + { + $foo = new Rbac\Role('foo'); + + $this->rbac->addRole('bar'); + $this->rbac->addRole($foo); + + $this->assertEquals(true, $this->rbac->hasRole($foo)); + $this->assertEquals(true, $this->rbac->hasRole('bar')); + $this->assertEquals(false, $this->rbac->hasRole('baz')); + } + + public function testAddRoleFromString() + { + $this->rbac->addRole('foo'); + + $foo = $this->rbac->getRole('foo'); + $this->assertInstanceOf('Zend\Permissions\Rbac\Role', $foo); + } + + public function testAddRoleFromClass() + { + $foo = new Rbac\Role('foo'); + + $this->rbac->addRole('foo'); + $foo2 = $this->rbac->getRole('foo'); + + $this->assertEquals($foo, $foo2); + $this->assertInstanceOf('Zend\Permissions\Rbac\Role', $foo2); + } + + public function testAddRoleWithParentsUsingRbac() + { + $foo = new Rbac\Role('foo'); + $bar = new Rbac\Role('bar'); + + $this->rbac->addRole($foo); + $this->rbac->addRole($bar, $foo); + + $this->assertEquals($bar->getParent(), $foo); + $this->assertEquals(1, count($foo->getChildren())); + } + + public function testAddRoleWithAutomaticParentsUsingRbac() + { + $foo = new Rbac\Role('foo'); + $bar = new Rbac\Role('bar'); + + $this->rbac->setCreateMissingRoles(true); + $this->rbac->addRole($bar, $foo); + + $this->assertEquals($bar->getParent(), $foo); + $this->assertEquals(1, count($foo->getChildren())); + } +} diff --git a/tests/ZendTest/Permissions/Rbac/TestAsset/RoleMustMatchAssertion.php b/tests/ZendTest/Permissions/Rbac/TestAsset/RoleMustMatchAssertion.php new file mode 100644 index 00000000000..4032e8647b3 --- /dev/null +++ b/tests/ZendTest/Permissions/Rbac/TestAsset/RoleMustMatchAssertion.php @@ -0,0 +1,45 @@ +role = $role; + } + + /** + * Assertion method - must return a boolean. + * + * @param Rbac $bac + * @return boolean + */ + public function assert(Rbac $rbac) + { + return $this->role->getName() == 'foo'; + } +} diff --git a/tests/ZendTest/Permissions/Rbac/TestAsset/SimpleFalseAssertion.php b/tests/ZendTest/Permissions/Rbac/TestAsset/SimpleFalseAssertion.php new file mode 100644 index 00000000000..4b8e2e59d5d --- /dev/null +++ b/tests/ZendTest/Permissions/Rbac/TestAsset/SimpleFalseAssertion.php @@ -0,0 +1,34 @@ +