Skip to content
This repository has been archived by the owner on Jan 30, 2020. It is now read-only.

Refactor of internal role hierarchy model + changes for 3.0.0 #34

Merged
merged 35 commits into from
Mar 5, 2018

Conversation

ezimuel
Copy link
Contributor

@ezimuel ezimuel commented Feb 13, 2018

This PR contains a big refactoring of the role hierarchy model with minor changes in the APIs. This PR contains a potential 3.0.0 release for zend-permissions-rbac, containing BC breaks.

This PR includes the following changes:

Added

  • Check for circular references in role hierarchy when using Role::addChild() and Role::addParent() functions.

Changed

  • Role::addChild(RoleInterface $child), accepts only RoleInterface parameter, no string anymore.
  • Role::addParent(RoleInterface $parent), accepts only RoleInterface parameter, no string anymore.
  • Zend\Permissions\Rbac\AssertionInterface, added the optional parameters $permission and $role in the assert() function, as follows:
    assert(Rbac $rbac, string $permission = null, RoleInterface $role = null)

Deprecated

  • Nothing.

Removed

  • AbstractIterator, removed the class. The role hierarchy is not based anymore on RecursiveIterator.

  • AbstractRole, removed the class. All the functions have been moved in Zend\Permissions\Rbac\Role.

  • Role::setParent(), use Role::addParent() instead.

Fixed

  • #30, Fixed circular references with the protected functions Role::hasAncestor($role) used in Role::addChild() and Role::hasDescendant($role) in Role::addParent().

@ezimuel ezimuel added this to the 3.0.0 milestone Feb 13, 2018
@michalbundyra
Copy link
Member

@ezimuel just a quick question - are we not going to bump PHP requirements to version 7.1 with 3.0.0 release??

@ezimuel
Copy link
Contributor Author

ezimuel commented Feb 13, 2018

@webimpress yes, I'm removing the support for PHP 5.6 and PHP 7.0, thanks for the reminder.

@ezimuel
Copy link
Contributor Author

ezimuel commented Feb 13, 2018

@webimpress fixed now, requiring PHP 7.1+.

@ezimuel
Copy link
Contributor Author

ezimuel commented Feb 13, 2018

Ping @Ocramius, @webimpress for review.
Ping also @olegkrivtsov, @asgrim, @DominicDetta, @codeaid for comments and feedback.

Copy link
Member

@michalbundyra michalbundyra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ezimuel I've tried not to duplicate my comments, but basically what I've pointed should be applied across the whole PR. Also as we are using PHP 7.1 we should add strict type declarations for all files (can be done later on with other PHP 7.1 improvements, to keep this PR clear).

In general it looks good, I need to have another detailed look on functionality changes.

composer.json Outdated
@@ -3,7 +3,7 @@
"description": "provides a role-based access control management",
"license": "BSD-3-Clause",
"keywords": [
"zf2",
"zf3",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We stop using version with zf components, so I'd suggest to use only zf and add please zendframework (please see maintainers repository and templates there)

composer.json Outdated
@@ -29,7 +29,7 @@
}
},
"require-dev": {
"phpunit/phpunit": "^5.7.15|| ^6.2.1",
"phpunit/phpunit": "^6.4.4",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can switch to PHPUnit 7.0.1 as it supports only PHP 7.1+ !

@@ -98,7 +98,7 @@ class AssertUserIdMatches implements AssertionInterface
$this->article = $article;
}

public function assert(Rbac $rbac)
public function assert(Rbac $rbac, $permission = null, $role = null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add some typehints? If these types are mixed could we please add PHPDocs about allowed types?

*
* @param Rbac $rbac
* @return bool
* {@inheritDoc}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment doesn't have any value, I would remove it.

*/
public function assert(Rbac $rbac)
public function assert(Rbac $rbac, string $permission = null, RoleInterface $role = null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add also return type in function declaration, please?

* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2018 Zend Technologies USA Inc. (http://www.zend.com)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For new files we should use only current year.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this requirement defined?

/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be nice to update all headers to point to the repository (as we have in many others repos already, for example see any expressive repo)

*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2018 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same here, this link doesn't even work....

src/Rbac.php Outdated
* @return \Zend\Permissions\Rbac\Rbac
*/
public function setCreateMissingRoles($createMissingRoles)
public function setCreateMissingRoles(bool $createMissingRoles): Rbac
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are using single space before colon in return type declaration.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also as a return type I would use self (it's clearer when you read class in the middle then we know straight away that we return the same class).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or we should drop the fluent interface altogether, as we have in other components, and return void. (This comment applies to all setter methods that currently implement a fluent interface.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed fluent interface in all the library.

src/Rbac.php Outdated
@@ -22,20 +25,19 @@ class Rbac extends AbstractIterator
protected $createMissingRoles = false;

/**
* @param bool $createMissingRoles
* @param bool $createMissingRoles
* @return \Zend\Permissions\Rbac\Rbac
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imho this should be updated to return $this

@codeaid
Copy link

codeaid commented Feb 14, 2018

Hi all,

Thanks for including me in this conversation!

As you can imagine, a lot has happened since I posted the original issue. I ended up developing my own solution, which, to be honest, I'm really happy with and having successfully used it for several months in production environment there is no real need for me to switch back to Zend RBAC.

My solution literally consists of two classes - container and role - and supports adding roles by names and works around circular references. I posted the related files in this Gist in case anyone's interested to have a look. There's a usage.php file at the bottom showing how I'm using the container by building it from my service, which could really be anything.

Speaking about the PR - looks good to me at a glance. I like that the add* methods use type hinting -
stricter is always better in my books. Also, getting rid of the iterators should result in some performance increase (even if it's a small one). It's always good to strip out something that is not absolutely needed.

Other than that, I can only go by what @weierophinney says as he's the expert when it comes to framework standards. Sorry, if I'm not really being helpful :/

@froschdesign
Copy link
Member

@codeaid

…there is no real need for me to switch back to Zend RBAC.

Maybe (unit) tests? 😉

@codeaid
Copy link

codeaid commented Feb 14, 2018

@froschdesign You mean unit testing my implementation or zend-permissions-rbac?

@froschdesign
Copy link
Member

froschdesign commented Feb 14, 2018

@codeaid
Do you have unit tests for your implementation? If not, this should be a reason to use zend-permissions-rbac. // off-topic ends here

@ezimuel
Copy link
Contributor Author

ezimuel commented Feb 14, 2018

@codeaid thanks for sharing your code with us. It's always interesting to compare different solutions to the same problem. If this works for you that's great! Of course, as @froschdesign suggested unit test will help. I suggest to add it in your implementation to find issues before they will happens in production. Thanks again for your feedbacks!

@codeaid
Copy link

codeaid commented Feb 14, 2018

@froschdesign @ezimuel - Of course I have unit tests! How could you even assume otherwise!? :D I added them to the gist (if it helps). I believe they yield a 100% coverage too so I'm covered (pun intended).

@ezimuel
Copy link
Contributor Author

ezimuel commented Feb 14, 2018

@webimpress, @weierophinney I incorporated all the feedbacks, including: declare(strict_types=1); on each file, and removing fluent interface. Moreover, I added some unit tests to improve the code coverage.

@ezimuel
Copy link
Contributor Author

ezimuel commented Feb 14, 2018

Now we have 100% code coverage!

If a param or return annotation does not provide information beyond the
typing and name already present in the signature, they are redundant.
- Adds a `callable` typehint to the `$callback` argument of the constructor.
- Decorates the callback in a closure to ensure type safety when invoked; no more casting!
- Remove redundant `@param` and `@return` annotations.
- Add descriptions for `@throws` annotations.
- Remove unnecessary whitespace.
- Add necessary whitespace.
- Throw exceptions only from conditions.
- Removes redundant `@param` and `@return` annotations.
- Corrects `@throws` annotations.
- Adds whitespace where necessary.
- Removes whitespace where necessary.
*/
public function assert(Rbac $rbac);
public function assert(Rbac $rbac, RoleInterface $role = null, string $permission = null) : bool;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still not entirely convinced these arguments should be optional.

While I understand the argument of keeping BC breaks to a minimum, I also think we could address this in a new v2.X minor release by doing the following:

  • Updating the Rbac class to pass the role and permission when calling an assertion.
  • Documenting in migration notes to users that they should add these arguments to their assertions in order to prepare for the v3 release. Their assertions can ignore them for now — or even update their logic to make use of them!

Taking those steps allows the v3 release to make these required, as they are always passed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@weierophinney ok, this plan sounds good to me.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In talking with @ezimuel today, we realized that for anybody implementing the AssertionInterface, they will need to make a signature change when upgrading to version 3 regardless of any changes we'd make in the v2 release branch. As such, we will not do another v2 release, and move ahead instead with the v3 release.

Copy link
Member

@weierophinney weierophinney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only blocker at this point is a migration guide, as we already discussed, @ezimuel!

@ezimuel
Copy link
Contributor Author

ezimuel commented Feb 22, 2018

@weierophinney I wrote the migration guide, removed the optionals in AssertionInterface and fixed an issue on CHANGELOG.

Adds a complete section on `RoleInterface` changes, incorporating the
sections on `setParent` and `addChild` changes, and adding information
on type hint changes, renaming of getParent to getParents, and addition
of getChildren.
@weierophinney
Copy link
Member

@ezimuel I've edited and expanded the migration document; I think this is good to go at this point!


## `Zend\Permissions\Rbac\Rbac`

`Rbac` is the object with which you will interact within your application in
order to query for permissions. It extends `AbstractIterator`.
order to query for permissions.

Method signature | Description
--------------------------------------------------------------------------- | -----------
`addRole(string|RoleInterface $child, array|RoleInterface $parents = null)` | Add a role to the RBAC. If `$parents` is non-null, the `$child` is also added to any parents provided.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backslash is required for pipe:

addRole(string\|RoleInterface $child, array\|RoleInterface $parents = null)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Problem is the rendering inside tables.


Method signature | Description
--------------------------------------------------------------------------- | -----------
`addRole(string|RoleInterface $child, array|RoleInterface $parents = null)` | Add a role to the RBAC. If `$parents` is non-null, the `$child` is also added to any parents provided.
`getRole(string|RoleInterface $role) : RoleInterface` | Recursively queries the RBAC for the given role, returning it if found, and raising an exception otherwise.
`getRole(string $role) : RoleInterface` | Get the role specified by name, raising an exception if not found.
`hasRole(string|RoleInterface $role) : bool` | Recursively queries the RBAC for the given role, returning `true` if found, `false` otherwise.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here:

hasRole(string\|RoleInterface $role) : bool


Method signature | Description
--------------------------------------------------------------------------- | -----------
`addRole(string|RoleInterface $child, array|RoleInterface $parents = null)` | Add a role to the RBAC. If `$parents` is non-null, the `$child` is also added to any parents provided.
`getRole(string|RoleInterface $role) : RoleInterface` | Recursively queries the RBAC for the given role, returning it if found, and raising an exception otherwise.
`getRole(string $role) : RoleInterface` | Get the role specified by name, raising an exception if not found.
`hasRole(string|RoleInterface $role) : bool` | Recursively queries the RBAC for the given role, returning `true` if found, `false` otherwise.
`getCreateMissingRoles() : bool` | Retrieve the flag that determines whether or not `$parent` roles are added automatically if not present when calling `addRole()`.
`setCreateMissingRoles(bool $flag) : void` | Set the flag that determines whether or not `$parent` roles are added automatically if not present when calling `addRole()`.
`isGranted(string|RoleInterface $role, string $permission, $assert = null)` | Determine if the role has the given permission. If `$assert` is provided and either an `AssertInterface` instance or callable, it will be queried before checking against the given role.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here:

isGranted(string\|RoleInterface $role, string $permission, $assert = null)

@ezimuel
Copy link
Contributor Author

ezimuel commented Mar 1, 2018

@froschdesign I added the backslash for pipe in docs, as you suggested. Thanks!

@weierophinney weierophinney merged commit 60bf337 into zendframework:develop Mar 5, 2018
weierophinney added a commit that referenced this pull request Mar 5, 2018
@weierophinney
Copy link
Member

Thanks, @ezimuel - great improvement! Merged to develop.

@ezimuel ezimuel mentioned this pull request Aug 7, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants