-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Security] add an AbstractVoter implementation #11183
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
👍 How about going a bit further — replace
public function vote(TokenInterface $token, $object, array $attributes)
{
$result = self::ACCESS_ABSTAIN;
if (!$this->supportsClass(get_class($object))) {
return $result;
}
$user = $token->getUser();
foreach ($attributes as $attribute) {
if (!$this->supportsAttribute($attribute)) {
continue;
}
if ($this->isGranted($object, $attribute, $user)) {
return self::ACCESS_GRANTED;
}
$result = self::ACCESS_DENIED;
}
return $result;
} |
foreach ($attributes as $attribute) { | ||
if ($this->supportsAttribute($attribute)) { | ||
$vote = $this->voteOnAttribute($attribute, $object, $user); | ||
if (VoterInterface::ACCESS_ABSTAIN !== $vote) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this implementation is not consistent with the way core voters work. If you pass 2 attributes (which is not common anyway), they will grant the access as soon as one of them is accepted. Your implementation however gives more power to the first attribute by allowing it to reject everything
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point! will try to replicate core voters behavior
It still needs a test obviously, but I really like the implementation! About @rybakit's comment, I thought about this too, and I like it, but I'm not sure. I suppose the Such a great win imo - I hope this gets accepted - I'd be very happy to see how much more clear the documentation articles about voters become. Thanks! |
public function supportsClass($class) | ||
{ | ||
foreach ($this->getSupportedClasses() as $supportedClass) { | ||
if ($supportedClass === $class || is_subclass_of($class, $supportedClass)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
http://php.net/is_a is easier IMHO
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think is_a
still requires the first argument to be an object (all we have here are class names).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But you don't have to use get_class
on line 50 ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
supportsClass expects a string in the VoterInterface
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah ... Did not know ;) I never like this interface. It should be tweaked in SF3.0 IMHO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, supportsClass
and supportAttribute
are actually never used in Symfony except by voters themselves. They should be removed from the interface in 3.0. But in the meantime, the interface should be respected
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just ran few tests:
php > var_dump(is_a('BadFunctionCallException', 'Exception', true));
bool(true)
php > var_dump(is_a('Exception', 'Exception', true));
bool(true)
php > var_dump(is_a('Foobar', 'Exception', true));
bool(false)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think is_a still requires the first argument to be an object (all we have here are class names).
See the php doc:
allow_string
If this parameter set to FALSE, string class name as object is not allowed. This also prevents from calling autoloader if the class doesn't exist.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in 5.3.3 to 5.3.6, is_a
does not trigger the autoloading when passing a string. This won't cause issue for the behavior in the voter (as the class name comes from get_class
and so the class is already autoloaded) but it could for other usages of the method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point ;)
I've created something really similar to this recently and have tests more or less ready to go, so let me know if I can help out with the tests or if you just want to see them or anything. 👍 |
I really love this idea to simplify the voters. Thanks @inoryy. My question is: does anyone know what is needed to finish this PR? How can we help with that? |
oops, for some reason I was sure I wrapped it up. My bad, I'll put this on top of my todo list. |
* @return int either ACCESS_GRANTED, ACCESS_ABSTAIN, or ACCESS_DENIED | ||
*/ | ||
abstract protected function voteOnAttribute($attribute, $object, UserInterface $user = null); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing newline at end of file
@weaverryan @javiereguiluz this should be good to go. I've fixed inconsistencies, implemented the recommended ideas where they wouldn't cause conflicts with API & added test coverage. The build failed, but I think it's unrelated to the PR (something about Stopwatch on PHP 5.6?) |
... and now the build passed as well :) |
👍 great job @inoryy ! |
|
||
class ObjectFixture {} | ||
|
||
class UnsupportedObjectFixture {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(ver minor comment) It looks like you didn't add a new line at the end of the file. These are the things that fabbot.io
doesn't like ;)
👍 Great work Roman! Thanks a lot for helping us improve Symfony. |
👍 Awesome - one of my favorite features for 2.6! |
* It is safe to assume that $attribute and $object's class pass supportsAttribute/supportsClass | ||
* $user can be one of the following: | ||
* a UserInterface object (fully authenticated user) | ||
* a string (anonymously authenticated user) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a string user doesn't necessary mean it is an anonymously authenticated user
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's technically true, but I've never seen a case in practice other than the anonymous user. I'd rather be a little technically incorrect here to cover the vast majority of the use-cases (and if you are authentication a user without an object, it's likely you are more advanced and understand what's going on).
I'd like to write a "New in Symfony 2.6" post about this great feature. @inoryy do you know what's left to finish this PR? I think that you've done a terrific job so far and probably we only need a final effort. Do you think we could help you with anything? Thanks! |
@javiereguiluz I see two minor blocks:
|
Fwiw, we've weighed in on the arguable spots - I think we're all set there. Thanks for the fast response on this Roman! |
@weaverryan @javiereguiluz I've made the necessary changes, so it should be good to go as far as I know. However, the test suite seems to be failing now for some reason. The only difference since last build success are doc entries and added a newline, so it seems it's not relevant to this PR?
P.S. @javiereguiluz I'm flattered that you're planning to feature this in "New in Symfony 2.6", but I also want to give credit where it's due - @weaverryan came up with the idea originally, I just implemented it :) |
@inoryy the process timeout tests are volatile. They are failing from time to time depending on the load of the Travis servers |
In my opinion this can be safely merged. As @stof mentioned, the error reported by Travis has nothing to do with this code and it's caused by a volatile test. |
@inoryy You can rebase on current master as the volatile tests have been fixed now. Also, we need at least a PR on the docs for this new feature. |
…ove/update API based on PR comments
@fabpot I've rebased, but it still fails due to failures on master itself (failures are unrelated to this PR). |
I've just read the new documentation and I think this implementation has one drawback: it's not possible anymore to return Another idea if you think the constants are too cumbersome: instead of returning a Boolean, the developer can just return What do you think? |
Thinking about it more, I think we should supports the constants/numbers and remove the Booleans. That way, the abstract voter is closer to custom voters and this is still as easy as Boolean. |
@fabpot I'm not sure I see the benefit of the added flexibility? |
As I said, it's a small benefit, but more important, using numbers is not more "difficult" to understand and adds consistency across the board. So, I don't see any drawback here. |
@fabpot but how to implement this considering multiple attributes? |
@inoryy Indeed. 👍 |
Thank you @inoryy. |
public function vote(TokenInterface $token, $object, array $attributes) | ||
{ | ||
if (!$object || !$this->supportsClass(get_class($object))) { | ||
return self::ACCESS_ABSTAIN; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why returning ACCESS_ABSTAIN
so early? This avoids using is_granted(MY_ATTRIBUTE)
without passing object.
Take the role checking as example: is_granted('ROLE_ADMIN')
... no object is passed. Ans this kind of implementation is not possible here.
At first glance, I would do something like if(!!$object && !$this->supportsClass(...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should open an issue. At first glance, I agree that this is something we overlooked!
…plementation (Inoryy, javiereguiluz) This PR was submitted for the 2.7 branch but it was merged into the 2.6 branch instead (closes #5423). Discussion ---------- [Security] add & update doc entries on AbstractVoter implementation | Q | A | ------------- | --- | Doc fix? | no | New docs? | yes (symfony/symfony#11183) | Applies to | 2.6+ | Fixed tickets | - This PR finishes #4257. Commits ------- 95537d3 Added a link to the AbstractVoter cookbook 73bd908 Fixed some typos 60643f0 Removed the abstract_voter.rst.inc file 59c60b1 add fixes to abstract_voter include file e9053c0 fix problems pointed out by @javiereguiluz and @cordoval 968cb65 add & update doc entries on AbstractVoter implementation
The idea is to reduce boilerplate required to create custom Voter, doing most of the work for the developer and guiding him on the path by providing simple requirements via abstract methods that will be called by the AbstractVoter.
P.S. This is meant to be a DX Initiative improvement.