diff --git a/src/AbstractIterator.php b/src/AbstractIterator.php
new file mode 100644
index 00000000..be9fd93c
--- /dev/null
+++ b/src/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/src/AbstractRole.php b/src/AbstractRole.php
new file mode 100644
index 00000000..85de86a1
--- /dev/null
+++ b/src/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/src/AssertionInterface.php b/src/AssertionInterface.php
new file mode 100644
index 00000000..868ed7fd
--- /dev/null
+++ b/src/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/src/Role.php b/src/Role.php
new file mode 100644
index 00000000..b83e2c81
--- /dev/null
+++ b/src/Role.php
@@ -0,0 +1,27 @@
+name = $name;
+ }
+}
diff --git a/test/RbacTest.php b/test/RbacTest.php
new file mode 100644
index 00000000..51342bdb
--- /dev/null
+++ b/test/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/test/TestAsset/RoleMustMatchAssertion.php b/test/TestAsset/RoleMustMatchAssertion.php
new file mode 100644
index 00000000..4032e864
--- /dev/null
+++ b/test/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/test/TestAsset/SimpleFalseAssertion.php b/test/TestAsset/SimpleFalseAssertion.php
new file mode 100644
index 00000000..4b8e2e59
--- /dev/null
+++ b/test/TestAsset/SimpleFalseAssertion.php
@@ -0,0 +1,34 @@
+