Why are symfony2 security voters always called? - php

I am using security voters as alternative to symfony's acl system.
example voter:
my voters look similar go the following one.
class FoobarVoter implements VoterInterface
{
public function supportsClass($class)
{
return in_array($class, array(
'Example\FoobarBundle\Entity\Foobar',
));
}
public function supportsAttribute($attribute)
{
return in_array(strtolower($attribute), array('foo', 'bar'));
}
public function vote(TokenInterface $token, $object, array $attributes)
{
$result = VoterInterface::ACCESS_ABSTAIN
if (!$this->supportsClass(get_class($object))) {
return VoterInterface::ACCESS_ABSTAIN;
}
foreach ($attributes as $attribute) {
$attribute = strtolower($attribute);
// skip not supported attributes
if (!$this->supportsAttribute($attribute)) {
continue;
}
[... some logic ...]
}
return $result;
}
}
questions:
reduce calls to Voter::vote()
my voters are included and called on every page load. even if they do not support decisions for a given class. FoobarVoter::vote() is always called. even if FoobarVoter::supportsClass() or FoobarVoter::supportsAttribute return false. thus i need to check class and attribute inside FoobarVoter::vote(). is this behaviour standard? how can i prevent this unnecessary call.
limit voters to bundles
some voters are only needed inside specific bundles. some are only needed to decide about specific classes. thus some voters are not needed in all parts of my application. is it possible to include voters per bundle/entity dynamically? e.g. only include voters into decision manager chain if a specific bundle or a specific entity is accessed/used?

Looking through the source code of Symfony, it appears to be because the AccessDecisionManager uses those methods (supportsClass and seupportsAttribute) to roll-up support to itself.
What this allows your voter to do is extend the cases when the manager will be applied. So you're not detailing the capability of your voter, but of the entire voting process. Whether or not that's what you want is something else...
As far as reducing the un-necessary calls, it's not un-necessary in the general case. The system is designed using one of three methods:
Allow based (decideAffirmative). This uses an "allow based" voting. Which means that if one plugin says "allow" then you're allowed.
Concensus Based (decideConsensus). This uses a concensus based permission, where if more voters agree to allow than to deny you're allowed...
Deny Based (decideUnanimous). This uses a "deny based" voting. Which means that if one plugin says "deny", then you're denied. Otherwise you need at least one grant.
So considering that all of them rely on the explicit Deny vs Allow, running all of the plugins for every request actually makes sense. Because even if you don't specifically support a class, you may want to allow or deny that request.
In short, there's not much to gain by limiting the voters by the supports attributes.

Related

How do I inject behaviour into an aggregate?

I’m working on an aggregate where certain behaviours can be performed by multiple roles within the application. But before that happens fairly complex validation occurs. It’s this validation that differs per role. Typically it means different configuration settings are checked to determine if the action can be performed.
So, as an example: lets say i have an Order to which i can add OrderLines. If i have role Employee i might be allowed to order up to € 100,- and if i have role Purchaser i might be allowed to order up to € 1000,-.
You could solve this by providing the user instance to the addOrderLine method but that leaks the user context into the ordering context. The next logical thing, and this is what I’ve been doing, is in to inject that validation logic into the method call. I’m calling those methods policies and instantiate the right policy in the application service as i have the relevant user info available there:
<?php
class Order {
public function addItem(OrderPolicy $policy, Item $item, int $amount) {
if (!$policy->canPurchase($item->getPrice() * $amount))
throw new LimitExceededException();
/* add item here */
}
class OrderService {
public function addItem(User $user, $orderId, $itemId, int $amount) {
$order = $this->orderRepo->getForUser($user, $orderId);
$item = $this->itemRepo->get($itemId);
$policy = $this->getOrderPolicyFor($user);
$order->addItem($policy, $item, $amount);
}
}
class PurchaserOrderPolicy
{
function canPurchase($amount) {
return ($amount <= 1000);
}
}
This seems fine, but now it seems to me my ordering context has logic based on user roles (the policy classes) its not supposed to know about.
Does DDD offer any other ways of dealing with this? Maybe Specification? Does this seem fine? Where do the policy classes belong?
It seems that you have two subdomains/systems involved here: ordering system and buying policies system. You need to keep thing separated, your gut was correct. This means that the validation about the maximum order value is checked in an eventual consistent manner relative to the actual item adding. You could do this before (you try to prevent an invalid order) or after (you try to recover from an invalid order).
If you do it before then the application service could orchestrate this and reject the order.
If you do it after then you could implement this as a process and have a ApplyPoliciesToOrdersSaga that listen to the ItemWasAddedToTheOrder event (if you have an event-driven architecture) or that runs as a scheduled task/cron job and checks all orders against the policies (in a classical architecture).

Symfony Workflow Component and Security Voters?

TL;DR: how can you add custom constraints (i.e. security voters) to transitions?
My application needs some workflow management system, so I'd like to try Symfony's new Workflow Component. Let's take a Pull Request workflow as an example.
In this example, only states and their transitions are describes. But what if I want to add other constraints to this workflow? I can image some constraints:
Only admins can accept Pull Request
Users can only reopen their own Pull Request
Users can not reopen PR's older than 1 year
While you can use Events in this case, I don't think that's the best way to handle it, because an event is fired after $workflow->apply(). I want to know beforehand if a user is allowed to change the state, so I can hide or disable the button. (not like this).
The LexikWorkflowBundle solved this problem partially, by adding roles to the steps (transitions). Switching to this bundle might be a good idea, but I'd like to figure out how I can solve this problem without.
What is the best way to add custom entity constraints ('PR older than 1 year can't be reopened') and security constraints ('only admins can accept PR's', maybe by using Symfony's Security Voters) to transitions?
Update:
To clarify: I want to add permission control to my workflow, but that doesn't necessarily mean I want to tightly couple it to the Workflow Component. I'd like to stick to good practices, so the given solution should respect the single responsibility principle.
The best way I found was implementing the AuthorizationChecker in the Workflow's GuardListener.
The demo application gives a good example:
namespace Acme\DemoBundle\Entity\Listener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
use Symfony\Component\Workflow\Event\GuardEvent;
class GuardListener implements EventSubscriberInterface
{
public function __construct(AuthorizationCheckerInterface $checker)
{
$this->checker = $checker;
}
public function onTransition(GuardEvent $event)
{
// For all action, user should be logger
if (!$this->checker->isGranted('IS_AUTHENTICATED_FULLY')) {
$event->setBlocked(true);
}
}
public function onTransitionJournalist(GuardEvent $event)
{
if (!$this->checker->isGranted('ROLE_JOURNALIST')) {
$event->setBlocked(true);
}
}
public function onTransitionSpellChecker(GuardEvent $event)
{
if (!$this->checker->isGranted('ROLE_SPELLCHECKER')) {
$event->setBlocked(true);
}
}
public static function getSubscribedEvents()
{
return [
'workflow.article.guard' => 'onTransition',
'workflow.article.guard.journalist_approval' => 'onTransitionJournalist',
'workflow.article.guard.spellchecker_approval' => 'onTransitionSpellChecker',
];
}

Controller as Service - How to pass and return values in an advanced case?

Using Symfony, I am displaying a table with some entries the user is able to select from. There is a little more complexity as this might include calling some further actions e. g. for filtering the table entries, sorting by different criteria, etc.
I have implemented the whole thing in an own bundle, let's say ChoiceTableBundle (with ChoiceTableController). Now I would like to be able to use this bundle from other bundles, sometimes with some more parametrization.
My desired workflow would then look like this:
User is currently working with Bundle OtherBundle and triggers chooseAction.
chooseAction forwards to ChoiceTableController (resp. its default entry action).
Within ChoiceTableBundle, the user is able to navigate, filter, sort, ... using the actions and routing supplied by this bundle.
When the user has made his choice, he triggers another action (like choiceFinishedAction) and the control flow returns to OtherBundle, handing over the results of the users choice.
Based on these results, OtherBundle can then continue working.
Additionally, OtherOtherBundle (and some more...) should also be able to use this workflow, possibly passing some configuration values to ChoiceTableBundle to make it behave a little different.
I have read about the "Controller as Service" pattern of Symfony 2 and IMHO it's the right approach here (if not, please tell me ;)). So I would make a service out of ChoiceTableController and use it from the other bundles. Anyway, with the workflow above in mind, I don't see a "good" way to achieve this:
How can I pass over configuration parameters to ChoiceTableBundle (resp. ChoiceTableController), if neccessary?
How can ChoiceTableBundle know from where it was called?
How can I return the results to this calling bundle?
Basic approaches could be to store the values in the session or to create an intermediate object being passed. Both do not seem particularly elegant to me. Can you please give me a shove in the right direction? Many thanks in advance!
The main question is if you really need to call your filtering / searching logic as a controller action. Do you really need to make a request?
I would say it could be also doable just by passing all the required data to a service you define.
This service you should create from the guts of your ChoiceTableBundleand let both you ChoiceTableBundle and your OtherBundle to use the extracted service.
service / library way
// register it in your service container
class FilteredDataProvider
{
/**
* #return customObjectInterface or scallar or whatever you like
*/
public function doFiltering($searchString, $order)
{
return $this->filterAndReturnData($searchString, $order)
}
}
...
class OtherBundleController extends Controller {
public function showStuffAction() {
$result = $this->container->get('filter_data_provider')
->doFiltering('text', 'ascending')
}
}
controller way
The whole thing can be accomplished with the same approach as lipp/imagine bundle uses.
Have a controller as service and call/send all the required information to that controller when you need some results, you can also send whole request.
class MyController extends Controller
{
public function indexAction()
{
// RedirectResponse object
$responeFromYourSearchFilterAction = $this->container
->get('my_search_filter_controller')
->filterSearchAction(
$this->request, // http request
'parameter1' // like search string
'parameterX' // like sorting direction
);
// do something with the response
// ..
}
}
A separate service class would be much more flexible. Also if you need other parameters or Request object you can always provide it.
Info how to declare controller as service is here:
http://symfony.com/doc/current/cookbook/controller/service.html
How liip uses it:
https://github.com/liip/LiipImagineBundle#using-the-controller-as-a-service

How to restrict access of action/method (of Controller) to specific user in Symfony?

Is there a way to restrict access to specific routes aka action/method of controller in Symfony based on user?
I am implementing FOSUserBundle for user management and it has roles for defining permission that works well if I have user with defined roles but if I want to restrict the page based on users I have to create role for every routes or are there any better approach.
I have looked into ACL and its perfect but I don't find solution for my case or am I missing something there.
Looking for some help and ideas.
Updates
#AJ Cerqueti - Answer can be quick fix but I am looking for better approach if any.
To be more specific is it possible to assign access permission for routes to user using ACL or some other better approach.
SYMFONY >=2.6
create a security voter
How to Use Voters to Check User Permissions
Now change the access decision strategy accordingly with the docs
SYMFONY <=2.5
For simple needs like this you can create a security voter that fit exactly what you need (ACL's are usually used for complex needs also due to the not easy implementation).
Then you can use the voter in your controller like described in the docs:
// get a Post instance
$post = ...;
// keep in mind, this will call all registered security voters
if (false === $this->get('security.authorization_checker')->isGranted('view', $post)) {
throw new AccessDeniedException('Unauthorised access!');
}
Read also How to Use Voters to Check User Permissions
UPDATE BASED ON COMMENT:
Maybe will be better to know on how many routes you want to add this behavior but in any case (and if you want to avoid to add in every controller the #AJCerqueti code) you can use a Voter like in this simple example:
Voter Class:
// src/AppBundle/Security/Authorization/Voter/RouteVoter.php
namespace AppBundle\Security\Authorization\Voter;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class RouteVoter implements VoterInterface
{
private $routes;
public function __construct(array $routes = array())
{
$this->routes = $routes;
}
public function supportsAttribute($attribute)
{
// you won't check against a user attribute, so return true
return true;
}
public function supportsClass($class)
{
// your voter supports all type of token classes, so return true
return true;
}
public function vote(TokenInterface $token, $object, array $attributes)
{
// get get allowed routes from current logged in user
$userRoutes = $token->getUser()->getRoutes();
// implement as you want the checks and return the related voter constant as below
if (...) {# your implementation
return VoterInterface::ACCESS_DENIED;
}
return VoterInterface::ACCESS_ABSTAIN;
}
}
Register the Voter:
<service id="security.access.route_voter"
class="AppBundle\Security\Authorization\Voter\RouteVoter" public="false">
<argument type="collection">
<argument>route_one</argument>
<argument>route_two</argument>
</argument>
<tag name="security.voter" />
Now change the access decision strategy accordingly with the docs.
Can this fit your needs?
Agree with previous answers that ACLs, Voters or some sort or role-based solution is definitely the best practice approach, but for this fringe case, would suggest extending your FOSUser to add a 'slug' field, and then check on that:
if('accessible_slug' !== $this->get('security.context')->getToken()->getUser()->getSlug()) {
throw new AccessDeniedException()
}
This means setting up a slug for groups of controllers/actions, and setting them to the user. Similar to roles, but without quite as much overhead. Still prefer Voters and some sort of role hierarchy, but hope this helps.
You can use AccessDeniedException of Symfony2
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
Then check logged in User By,
if (false === $this->get('security.authorization_checker')->isGranted('ROLE_ADMIN')) {
throw new AccessDeniedException();
} else {
Continue;
}
In app/config/security.yml there is a section access_control:. There you can define access restrictions for specific paths, eg.
- { path: ^/faq/admin, roles: ROLE_FAQ_ADMIN }
path argument is a regex so the above entry will restrict access to any path starting with /faq/admin, eg. /faq/admin/show-something, /faq/admin/show-something-else etc. Only users with specified role will have access to those paths. For other users AccessDeniedException will be thrown with HTTP code 403.
There is no need to change code in actions inside controllers.

Symfony Model Callback Equivalent

I'm working on a Symfony project (my first) where I have to retrieve, from my Widget class, a set of widgets that belong to a Page. Before returning the results, though, I need to verify--against an external service--that the user is authorized to view each widget. If not, of course, I need to remove the widget from the result set.
Using CakePHP or Rails, I'd use callbacks, but I haven't found anything similar for Symfony. I see events, but those seem more relevant to controllers/actions if I'm reading things correctly (which is always up for discussion). My fallback solution is to override the various retrieval methods in the WidgetPeer class, divert them through a custom method that does the authorization and modifies the result set appropriately. That feels like massive overkill, though, since I'd have to override every selection method to ensure that authorization was done without future developers having to think about it.
It looks like behaviors could be useful for this (especially since it's conceivable that I might need to authorize other class instances in the future), but I can't find any decent documentation on them to make a qualified evaluation.
Am I missing something? It seems like there must be a better way, but I haven't found it.
First of all, I think behavior-based approach is wrong, since it increases model layer coupling level.
There's sfEventDispatcher::filter() method which allows you to, respectively, filter parameters passed to it.
So, draft code will look like:
<somewhere>/actions.class.php
public function executeBlabla(sfWebRequest $request)
{
//...skip...
$widgets = WidgetPeer::getWidgetsYouNeedTo();
$widgets = $this->getEventDispatcher()->filter(new sfEvent($this, 'widgets.filter'), $widgets));
//...skip...
}
apps/<appname>/config/<appname>Configuration.class.php
//...skip...
public function configure()
{
$this->registerHandlers();
}
public function registerHandlers()
{
$this->getEventDispatcher()->connect('widgets.filter', array('WidgetFilter', 'filter'));
}
//...skip
lib/utility/WidgetFilter.class.php
class WidgetFilter
{
public static function filter(sfEvent $evt, $value)
{
$result = array();
foreach ($value as $v)
{
if (!Something::isWrong($v))
{
$result[] = $v;
}
}
}
}
Hope you got an idea.
Here's some documentation on Symfony 1.2 Propel behaviors: http://www.symfony-project.org/cookbook/1_2/en/behaviors.
Why not just have a 'getAllowedWidgets' method on your Page object that does the checks you're looking for? Something like:
public function getAllowedWidgets($criteria = null, PropelPDO $con = null) {
$widgets = $this->getWidgets($criteria, $con);
$allowed = array();
// get access rights from remote service
foreach($widgets as $widget) {
// widget allowed?
$allowed[] = $widget;
}
return $allowed;
}
However, if you always want this check to be performed when selecting a Page's Widgets then Propel's behaviours are your best bet.
Although, at least in theory, I still think that a behavior is the right approach, I can't find a sufficient level of documentation about their implementation in Symfony 1.4.x to give me a warm and fuzzy that it can be accomplished without a lot of heartburn, if at all. Even looking at Propel's own documentation for behaviors, I see no pre- or post-retrieval hook on which to trigger the action I need to take.
As a result, I took my fallback path. After some source code sifting, though, I realized that it wasn't quite as laborious as I'd first thought. Every retrieval method goes through the BasePeer model's doSelect() method, so I just overrode that one in the customizable Peer model:
static public function doSelect( Criteria $criteria, PropelPDO $con = null ) {
$all_widgets = parent::doSelect( $criteria, $con );
$widgets = array();
foreach ( $widgets as $i => $widget ) {
#if( authorized ) {
# array_push( $widgets, $widget );
#}
}
return $widgets;
}
I haven't wired up the service call for authorization yet, but this appears to work as expected for modifying result sets. When and if I have to provide authorization for additional model instances, I'll have to revisit behaviors to remain DRY, but it looks like this will suffice nicely until then.

Categories