Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork9.7k
Fix the supports() method argument type of the security voter#37325
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Fix the supports() method argument type of the security voter#37325
Uh oh!
There was an error while loading.Please reload this page.
Conversation
GrahamCampbell commentedJun 18, 2020
Should this doc be changed to |
francoispluchino commentedJun 18, 2020 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
As I said, if we force typing on a Only the helper |
derrabus commentedJun 18, 2020
This change should be covered by a test. Also, your change might break some (probably rare) scenarios that would work right now:
|
francoispluchino commentedJun 18, 2020
Of course, I'm going to add a test :-)
Indeed, we can very well have a stringable object, however, as has never been indicated in the documentation or the phpdoc, some voters are therefore not stringable.
It was my first intention, however, we will break the Backward Compatibility if some voters override the |
derrabus commentedJun 18, 2020
All voters that extend the abstract
I'm not suggesting to change the |
francoispluchino commentedJun 18, 2020
Not all tests are passed in php 7.3, but these are tests for the Cache component. There was no error in the previous commit of this PR, so maybe just restart the tests for php 7.3 will remove the error |
francoispluchino commentedJun 18, 2020 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
@derrabus We do indeed agree on the 2 points. However, send a non-stringable attribute to a voter extending the abstract class `Voter', and you will get the error: Given that the |
GrahamCampbell commentedJun 18, 2020
I know, This is why I said v6. |
derrabus commentedJun 18, 2020
Not necessarily, please see the second 3v4l link I posted. |
francoispluchino commentedJun 18, 2020
Why limit arbitrarily the type to an array of strings? It is only the aid class to build the Voter that has become blocking. In addition, if we just change the phpdoc to The principle of the |
francoispluchino commentedJun 18, 2020
Remove my modification in the class |
derrabus commentedJun 18, 2020
Again, please see the second 3v4l link I posted above. It shows you a voter that passes. |
francoispluchino commentedJun 18, 2020
Sorry, but I will repeat myself :-), but the test passes with my modification, but without my modification you get: |
derrabus commentedJun 18, 2020
All right, I give up. 🤷🏻♂️ |
ro0NL commentedJun 18, 2020 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
@francoispluchino try a testcase with a custom voter that removes the string type here; symfony/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/VoterTest.php Line 69 in73ce604
we could simply remove the string types in core:
|
francoispluchino commentedJun 18, 2020
Yes, I had misunderstood the meaning of your answer, I am in the process of making the modification for this case. |
francoispluchino commentedJun 18, 2020
@ro0NL If we delete the type PHP Fatal error:Declaration of Symfony\Component\Security\Core\Tests\Authorization\Voter\VoterTest_Voter::supports(string$attribute,$object): bool must be compatible withSymfony\Component\Security\Core\Authorization\Voter\Voter::supports($attribute,$subject) in E:\Git\php\symfony\symfony\src\Symfony\Component\Security\Core\Tests\Authorization\Voter\VoterTest.php on line72 |
ro0NL commentedJun 18, 2020
Ah true. We could reflect e.g. |
francoispluchino commentedJun 18, 2020
I just added the test concerning the use case of attributes with a type |
francoispluchino commentedJun 18, 2020
As I point out to@ro0NL, your example can break compatibility with Voters implementing the new signature (seethis comment). However, thank you for your example concerning the fact that a stringable object can no longer be validated by a Voter when it was the case before my PR. I just add this use case. |
ro0NL commentedJun 18, 2020
There's still the second case:https://3v4l.org/MrcDq, where attributes are just objects (non stringable), or maybe someone uses ints. |
francoispluchino commentedJun 18, 2020
@ro0NL Yes, but in this case, the Voter cannot extend the abstract class |
ro0NL commentedJun 18, 2020 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Im not sure i follow now 😕 https://3v4l.org/MrcDq bool(true) |
francoispluchino commentedJun 18, 2020
I don't think it is advisable to remove the typing from the method |
francoispluchino commentedJun 18, 2020 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
You remove the type of the Note: However, your example is still valid, but if we remove the type in the abstract class, an error is thrown if the Voter use the new signature. It is for this reason that I chose to do a test before calling the method |
derrabus commentedJun 18, 2020 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
We cannot do this and nobody suggested to do this. Yet, the type can be removed by userland code when implementing the voter. That is a valid thing to do in php and that is all that my second example shows. And since the type declaration wasn't there in Symfony 2/3/4, there are probably quite a few voter implementations out there that do not have the type declaration. Expecting an object (or anything but a string) at this point might not be an execution path that was intended, but it's one that currently works and that will be broken by the change you're suggesting. But I don't really know how to deal with it. It might even be okay to break it. |
francoispluchino commentedJun 18, 2020
Yes, I was looking for a solution when we delete the type. However, I don't see how to check whether the typing is present or not on the instantiated class. |
francoispluchino commentedJun 18, 2020
The problem arises when we want to call the method |
derrabus commentedJun 18, 2020
You you catch and parse the |
francoispluchino commentedJun 18, 2020
It's not a problem to add the méthode
Totally agree with you, but I don't see any other more elegant solution. |
francoispluchino commentedJun 18, 2020
I just modified the PR to use a try/catch for the TypeError, and added tests for the attributes of type |
wouterj commentedJun 18, 2020 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Apart from |
Uh oh!
There was an error while loading.Please reload this page.
jvasseur commentedJun 18, 2020
@wouterj how would you implement the I'm all in favor of restricting what we can use as attributes but I'm not sure it's a reasonable move. Another thing to take into account is that PHP will probably add support for enums at some points and people will want to use enums as attributes. |
wouterj commentedJun 18, 2020
Let's not take PHP enums into consideration. First of all, it'll take a while before it will be added (it's already discussed for many years). Secondly, as everyone is now migrating to strict typed code bases, they will need to provide compatibility with current day strict typing for enums (e.g. an enum of strings should be able to be passed to a About |
derrabus commentedJun 18, 2020
That's a change we can hardly anticipate right now, can we? There are however libraries like |
francoispluchino commentedJun 18, 2020
@wouterj. I aggree with you, but for the moment, it is necessary to fix the bug, and depreciate or not for version 5.2 and prohibit the use of a non-string attribute in version 6.0, if we decide to take this direction. Personally I have a use case which led me to do it like voting Example 1: In my case, I use several voters including one which allows to validate permissions according to the subject. This subject can very well be a string (name of the class), an instance of a class, or an instance of a In attribute, I used a permission name in the same style as the // Check the authorization on the object$authorizationChecker->isGranted('perm_update',$entity);$authorizationChecker->isGranted('perm_update', PostInterface::class);// Check the authorization on the field of object$authorizationChecker->isGranted('perm_edit',newFieldVote($entity,'title'));$authorizationChecker->isGranted('perm_edit',newFieldVote(PostInterface::class,'title')); As you can see, it is not "natural" for a user to have to do magic by prefixing the name of the permission with: Of course, I could use an array of the style Using the example below is much more meaningful and logical, while being certain of the expected behavior: // Check the authorization on the object$authorizationChecker->isGranted(newPermissionVote('update'),$entity);$authorizationChecker->isGranted(newPermissionVote('update'), PostInterface::class);// Check the authorization on the field of object$authorizationChecker->isGranted(newPermissionVote('edit'),newFieldVote($entity,'title'));$authorizationChecker->isGranted(newPermissionVote('edit'),newFieldVote(PostInterface::class,'title')); You can see the old version of the component using the prefix The fact of define the permission in the attribute and leaving the subject to the Of course, it is possible to make the class Example 2: There is also another reason to use something other than an attribute string. This time, it's with a component validating Oauth2 scopes. The Voter waits in attribute for an instance of the class The class You will therefore understand that there is still a possibility of unexpected behavior if we put in attribute, only the name of the scope without prefix like // Check if the current user is authorized to read a Post with a his Oauth2 scopes$authorizationChecker->isGranted(newScopeVote(['post','post.readonly'],false));// Check if the current user is authorized to manage a Post with a his Oauth2 scopes$authorizationChecker->isGranted(newScopeVote('post'));
Another problem if we use a string instead of an instance of an object, it is likely easier that another Voter can validate the attribute and the subject when it was not intended for him. With an object, we can be certain that we reach the correct Voter which checks if the instance of the attribute is compatible, while making the code more readable and understandable. And as I said, put a For example, it is more complicated to have: $authorizationChecker->isGranted('permission',newPermissionVote('edit',newFieldVote(PostInterface::class,'title')));$authorizationChecker->isGranted('scope',newScopeVote('post')); that: $authorizationChecker->isGranted(newPermissionVote('edit'),newFieldVote(PostInterface::class,'title'));$authorizationChecker->isGranted(newScopeVote('post')); Regarding strict typing, I agree with you, and it is also for this reason that I used classes as an attribute, knowing that the documentation of Symfony authorized it. Of course, we can very well decide to deprecate the use of non-string attribute, but I think it would be more appropriate to allow at least stringable objects. This comment is very long, and I hope I have been understandable enough with my English and the wish to be as brief as possible, but it is ultimately quite difficult :-). |
wouterj commentedJun 18, 2020
Yes, agreed. But I think the bug can be fixed a bit more "hacky" if we know we can clean it up in 6.0 :) Thanks for your interesting insights! I need some time to process all the information - I'll follow-up with an RFC issue. Let's finish this bug report - as I agree that a bug has been introduced. |
| continue; | ||
| } | ||
| }catch (\TypeError$e) { | ||
| if (preg_match((sprintf('/Argument 1 passed to ([\w\\\]+)::supports\(\) must be of the type string, (\w+) given/')),$e->getMessage())) { |
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.
| if (preg_match((sprintf('/Argument 1 passed to ([\w\\\]+)::supports\(\) must be of the type string, (\w+) given/')),$e->getMessage())) { | |
| if (0 ===strpos($e->getMessage(),'Argument 1 passed to') &&false !==strpos($e->getMessage(),'::supports() must be of the type string')) { |
Strpos seems to be about 50% quicker:https://3v4l.org/47iXF
This method is potentially called quite a lot (for every voter in access_control and all isGranted calls), so it's performance critical, so I think it's worth considering.
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.
Indeed, this is to be considered. Your test is less strict but it is probably sufficient, but above all faster. I make the modification.
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.
It is done.
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.
@wouterj Please keep in mind that the error message format changes with php 8. The error message does not begin withArgument anymore, but with the method name, IIRC.
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.
Travis agrees:
TypeError: Symfony\Component\Security\Core\Tests\Authorization\Voter\VoterTest_Voter::supports(): Argument #1 ($attribute) must be of type string, stdClass given, called in /home/travis/build/symfony/symfony/src/Symfony/Component/Security/Core/Authorization/Voter/Voter.php on line 34So it probably should do something like:
if (\PHP_VERSION_ID <80000) {if (0 ===strpos($e->getMessage(),'Argument 1 passed to') &&false !==strpos($e->getMessage(),'::supports() must be of the type string')) {continue; }}elseif (false !==strpos($e->getMessage(),'supports(): Argument #1')) {continue;}throw$e;
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.
@wouterj Thank you, I didn't have PHP 8 on hand right now. I take your code.
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 almost nobody has. Fortunately, the Symfony test suite runs on PHP 8. You can find its results in the "Travis" link below. The relevant failure ishttps://travis-ci.org/github/symfony/symfony/jobs/699831814#L4276 (you can ignore the other failures, lots of things are work-in-progress for PHP 8/external dependency support).
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.
It is done.
wouterj left a comment• edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
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.
Thanks for your very quick responses! Looks good to me now (looks like all test failures are unrelated)
fabpot commentedJun 25, 2020
Thank you@francoispluchino. |
…ter (francoispluchino)This PR was submitted for the master branch but it was merged into the 5.0 branch instead.Discussion----------Fix the supports() method argument type of the security voter| Q | A| ------------- | ---| Branch? | 5.0 and 5.1| Bug fix? | yes| New feature? | no| Deprecations? | no| Tickets | ~| License | MIT| Doc PR | ~Since adding types to method arguments in the version 5.0 (and therefore also 5.1), there is a type mismatch on the first argument of the `supports()` method of the abstract class `Symfony\Component\Security\Core\Authorization\Voter\Voter`.Indeed, the `supports()` method had in previous versions (4.x), the phpdoc indicating that the argument `$attribute` must be a `string`, but this one is not compatible with the `isGranted()` method of the interface `Symfony\Component\Security\Core\AuthorizationAuthorizationCheckerInterface` whose the `$attribute` argument is of type `mixed`.The problem arises when you have voters extending the abstract class `Voter` positioned before a vote with an attribute of a type other than `string`.Apart from Voters created by third parties, there is the voter `ExpressionVoter` which waits in attribute, an instance of the class `Symfony\Component\ExpressionLanguage\Expression` (you can see the [doc](https://symfony.com/doc/current/security/expressions.html) for an example). Just add a voter extending the abstract class `Voter` with a higher priority than the voter `ExpressionVoter` to get the error:```Argument 1 passed to FooVoter::supports() must be of the type string, object given```To avoid removing the type of the `$attribute` argument from the method `Symfony\Component\Security\Core\Authorization\Voter\Voter::supports(string $attribute, $subject)`, which can break the backward compatibility, you just have to test in the `vote()` method if the attribute is not a `string` and continue before calling the `supports()` method.Commits-------b8192ee Fix the 'supports' method argument type of the security voter
Warxcell commentedFeb 2, 2023
This is very confusing, and still NOT fixed in Symfony 6.0 - the type there is STILL string, but |
Since adding types to method arguments in the version 5.0 (and therefore also 5.1), there is a type mismatch on the first argument of the
supports()method of the abstract classSymfony\Component\Security\Core\Authorization\Voter\Voter.Indeed, the
supports()method had in previous versions (4.x), the phpdoc indicating that the argument$attributemust be astring, but this one is not compatible with theisGranted()method of the interfaceSymfony\Component\Security\Core\AuthorizationAuthorizationCheckerInterfacewhose the$attributeargument is of typemixed.The problem arises when you have voters extending the abstract class
Voterpositioned before a vote with an attribute of a type other thanstring.Apart from Voters created by third parties, there is the voter
ExpressionVoterwhich waits in attribute, an instance of the classSymfony\Component\ExpressionLanguage\Expression(you can see thedoc for an example). Just add a voter extending the abstract classVoterwith a higher priority than the voterExpressionVoterto get the error:To avoid removing the type of the
$attributeargument from the methodSymfony\Component\Security\Core\Authorization\Voter\Voter::supports(string $attribute, $subject), which can break the backward compatibility, you just have to test in thevote()method if the attribute is not astringand continue before calling thesupports()method.