Problem
Actions check to see if the user has permission to perform the action when Action::show() is executed by executing Action::checkCanExecute()
Titles check to see if the user has permission to perform an action by calling Title::userCan() or Title::getUserPermissionsErrors().
These two code paths should return the same results, but they use different logic and therefor can often return different results. This leads to inconsistencies like T208862 where a title can report that a user cannot perform an action, but the action itself allows it.
Example
Let's say I have a HowlAction that has a name howl and a permission howl. The permission is set for all users, because, this is a free society and anyone can howl at the moon if they want too.
But in the HowlAction class, I have this in checkCanExecute()
$moon = new Solaris\MoonPhase(); return $moon->phase_name() === 'Full Moon';
This means that the user can only perform the action if the moon is full.
If someone calls Title::userCan('howl') or User::isAllowed('howl') they will always get true returned to them, but when the user actually goes to perform the action, they wont be able to do so (unless it happens to be a full moon).
This could also be a problem in reverse: No user has the howl right, but HowlAction::checkCanExecute() always returns true because howling is not allowed, but nothing is stopping you from doing it.
Solution
Given that the Action class contains the title, you could argue that Title::userCan ought to be deprecated and authorization should be checked from the Action class (i.e. Action::checkCanExecute()). Alternatively, perhaps it would be better to create a ActionPermission class that contains the authorization code for an action and can be used by both Title and by Action. This class could have sensible defaults (like using the permission, etc.) or it can be extended to perform it's own logic (see above).
Doing this would result in a consistent result (and code path) for both Title::userCan() and Action::checkCanExecute() which would, in this example. always return the same result (as it should).
Related