diff --git a/composer.json b/composer.json
index 624e9cde..44c376c3 100644
--- a/composer.json
+++ b/composer.json
@@ -1,10 +1,10 @@
{
"name": "zendframework/zend-permissions-rbac",
- "description": "Zend\\Permissions\\Rbac component",
+ "description": "provides a role-based access control management",
"license": "BSD-3-Clause",
"keywords": [
"zf2",
- "permissions-rbac"
+ "Rbac"
],
"homepage": "https://github.com/zendframework/zend-permissions-rbac",
"autoload": {
@@ -13,22 +13,22 @@
}
},
"require": {
- "php": ">=5.3.23"
+ "php": ">=5.3.3"
},
"extra": {
"branch-alias": {
- "dev-master": "2.4-dev",
- "dev-develop": "2.5-dev"
+ "dev-master": "2.2-dev",
+ "dev-develop": "2.3-dev"
}
},
- "require-dev": {
- "fabpot/php-cs-fixer": "1.7.*",
- "satooshi/php-coveralls": "dev-master",
- "phpunit/PHPUnit": "~4.0"
- },
"autoload-dev": {
"psr-4": {
"ZendTest\\Permissions\\Rbac\\": "test/"
}
+ },
+ "require-dev": {
+ "fabpot/php-cs-fixer": "1.7.*",
+ "satooshi/php-coveralls": "dev-master",
+ "phpunit/PHPUnit": "~4.0"
}
}
\ No newline at end of file
diff --git a/src/AbstractIterator.php b/src/AbstractIterator.php
new file mode 100644
index 00000000..4f922ec1
--- /dev/null
+++ b/src/AbstractIterator.php
@@ -0,0 +1,100 @@
+
+ * 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 bool 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()
+ {
+ if ($this->valid() && ($this->current() instanceof RecursiveIterator)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * (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/src/AbstractRole.php b/src/AbstractRole.php
new file mode 100644
index 00000000..fff1a9ac
--- /dev/null
+++ b/src/AbstractRole.php
@@ -0,0 +1,118 @@
+name;
+ }
+
+ /**
+ * Add permission to the role.
+ *
+ * @param $name
+ * @return RoleInterface
+ */
+ 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 RoleInterface $leaf */
+ if ($leaf->hasPermission($name)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Add a child.
+ *
+ * @param RoleInterface|string $child
+ * @return Role
+ */
+ public function addChild($child)
+ {
+ if (is_string($child)) {
+ $child = new Role($child);
+ }
+ if (!$child instanceof RoleInterface) {
+ throw new Exception\InvalidArgumentException(
+ 'Child must be a string or implement Zend\Permissions\Rbac\RoleInterface'
+ );
+ }
+
+ $child->setParent($this);
+ $this->children[] = $child;
+
+ return $this;
+ }
+
+ /**
+ * @param RoleInterface $parent
+ * @return RoleInterface
+ */
+ public function setParent($parent)
+ {
+ $this->parent = $parent;
+
+ return $this;
+ }
+
+ /**
+ * @return null|RoleInterface
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+}
diff --git a/src/AssertionInterface.php b/src/AssertionInterface.php
new file mode 100644
index 00000000..b94e0aa5
--- /dev/null
+++ b/src/AssertionInterface.php
@@ -0,0 +1,21 @@
+createMissingRoles = $createMissingRoles;
+
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getCreateMissingRoles()
+ {
+ return $this->createMissingRoles;
+ }
+
+ /**
+ * Add a child.
+ *
+ * @param string|RoleInterface $child
+ * @param array|RoleInterface|null $parents
+ * @return self
+ * @throws Exception\InvalidArgumentException
+ */
+ public function addRole($child, $parents = null)
+ {
+ if (is_string($child)) {
+ $child = new Role($child);
+ }
+ if (!$child instanceof RoleInterface) {
+ throw new Exception\InvalidArgumentException(
+ 'Child must be a string or implement Zend\Permissions\Rbac\RoleInterface'
+ );
+ }
+
+ 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\RoleInterface|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\RoleInterface|string $objectOrName
+ * @return RoleInterface
+ * @throws Exception\InvalidArgumentException
+ */
+ public function getRole($objectOrName)
+ {
+ if (!is_string($objectOrName) && !$objectOrName instanceof RoleInterface) {
+ throw new Exception\InvalidArgumentException(
+ 'Expected string or implement \Zend\Permissions\Rbac\RoleInterface'
+ );
+ }
+
+ $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 RoleInterface|string $role
+ * @param string $permission
+ * @param AssertionInterface|Callable|null $assert
+ * @return bool
+ */
+ 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/src/Role.php b/src/Role.php
new file mode 100644
index 00000000..212bd0d0
--- /dev/null
+++ b/src/Role.php
@@ -0,0 +1,21 @@
+name = $name;
+ }
+}
diff --git a/src/RoleInterface.php b/src/RoleInterface.php
new file mode 100644
index 00000000..6b9d734c
--- /dev/null
+++ b/src/RoleInterface.php
@@ -0,0 +1,55 @@
+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()));
+ }
+
+ /**
+ * @tesdox Test adding custom child roles works
+ */
+ public function testAddCustomChildRole()
+ {
+ $role = $this->getMockForAbstractClass('Zend\Permissions\Rbac\RoleInterface');
+ $this->rbac->setCreateMissingRoles(true)->addRole($role, array('parent'));
+
+ $role->expects($this->any())
+ ->method('getName')
+ ->will($this->returnValue('customchild'));
+
+ $role->expects($this->once())
+ ->method('hasPermission')
+ ->with('test')
+ ->will($this->returnValue(true));
+
+ $this->assertTrue($this->rbac->isGranted('parent', 'test'));
+ }
+}
diff --git a/test/TestAsset/RoleMustMatchAssertion.php b/test/TestAsset/RoleMustMatchAssertion.php
new file mode 100644
index 00000000..5cefa212
--- /dev/null
+++ b/test/TestAsset/RoleMustMatchAssertion.php
@@ -0,0 +1,45 @@
+role = $role;
+ }
+
+ /**
+ * Assertion method - must return a boolean.
+ *
+ * @param Rbac $bac
+ * @return bool
+ */
+ public function assert(Rbac $rbac)
+ {
+ return $this->role->getName() == 'foo';
+ }
+}
diff --git a/test/TestAsset/SimpleFalseAssertion.php b/test/TestAsset/SimpleFalseAssertion.php
new file mode 100644
index 00000000..637e1df4
--- /dev/null
+++ b/test/TestAsset/SimpleFalseAssertion.php
@@ -0,0 +1,34 @@
+