-2

How can I get voters who voted for "granted", inside event subscriber? Is there any service which I can fetch and use it?

My use case. My app has two voters: main voter and admin voter. I want to show additional flash message when access is disallowed by main voter but allowed by admin voter. I can solve this problem by two different solutions: by dispatching event inside admin voter or by setting flash msg inside voter — but I want to avoid it because I need to be sure that ONLY that one voter voted for access.

  • can't you use an access decision manager and use those two voters? https://symfony.com/doc/current/components/security/authorization.html#components-security-access-decision-manager or you might have to implement your own decision strategy. however, it's semantically misplaced inside a voter. – Jakumi Apr 09 '19 at 13:18
  • Theoretically I can but my goal is to get only these voters who voted for access, not all voters. By fetching access decision manager I can get only all voters. – Tomasz Gąsior Apr 09 '19 at 18:58

1 Answers1

2

So, I'll start with solutions, that answer the question you asked. I'll conclude with a solution to the (IMHO) underlying problem.

Extremely trivial solution:

Merge both voters into one, replacing them both. Setting the flash message if your condition is met is the responsibility of that merged voter, but it's trivial, since you have the "results" of both voters ...

Slightly more eloquent:

Create a CombinedVoter, replacing both. The CombinedVoter would receive both voters as dependencies and forward vote requests to them and return / cast the appropriate combination of them (so essentially a AccessDecisionManager disguised as a voter). The voter obviously would set the flash message, when your condition is met.

A little more useless:

Create a CombinedVoter, not replacing anything, but still receiving both voters as dependencies, forward vote requests to them and return ABSTAIN in all cases. The voter obviously would set the flash message, when your condition is met.

A little more over-engineered:

Add an EventDispatcher to both Voters and send out the vote events. Let a listener listen to those events and either trigger when it receives the second one (dirty af) or triggers again on a kernel event to set the flash message when your condition is met.

Slightly less over-engineered:

Add a service that is also a EventListener, that listens to the same kernel event to set the flash message, but doesn't listen to vote events, instead, inject that service into both voters and set the result of the vote directly on that service via setAdminVote()/setMainVote() or whatever.

The underlying problem:

Semantically, your voters are not meant to care about flash messages and what other voters have voted. It's neither their purpose nor their responsibility. To add this to their function is IMHO misguided, it's the wrong place and time! (Semantics!)

Instead, since this is probably a special message anyway, why not add this to your base template (there are certainly ways to check if someone is an admin but not allowed by "main voter", for example using is_granted in the template or app.user.roles directly (if appropriate). you could also write a small twig extension that adds that function and uses both voters somehow.

Why do I propose putting this logic in the template: because it's a display issue. You want something to be displayed, so it should be placed in the display part of your application. Also, there are many edge cases which you probably ignore, are unaware of, or put effort into avoiding: Submitting a form on a page that would trigger the flash message will probably get the flash message added on the POST (which, if done cleanly triggers a redirect to a page with GET) AND on the following GET, essentially adding two flash messages. Now you could not add the flash message on POST requests, but then the invalid POSTED forms would be without the flash message (because there'd be no GET). It's a mind f***. I really would advise against it.

Display is the frontend's job, add some helpers in services/extensions. Let the voters keep their single responsibility (which certainly is NOT adding flash messages). possibly extend your base template to function as a "pages using the extended base template should display the message", and then use that on the pages you want to show the message.

Really, do this cleanly ;o)

Addendum:

Flash messages are usually to provide feedback to the user to an action (s)he has done recently. For example, the user deleted a blog post. Then the flash message should read "blog post was deleted" (it might contain more information ^^).

And the utility of the sessions flash bag is the concept, that the flash messages will be kept there, until they are retrieved. This is convenient for feedback, because it is usually rendered the next time the page is actually rendered.

There are many many cases where this would be hard to manage otherwise. What if you only load fragments of your page (maybe ajax refreshing stuff), don't want your flash message in there, also not removed. What if you POSTed a form and are successfully redirected to some completely unrelated page. Should that page know anything about where the user comes from? Preferably not (separation of concerns). Flash messages don't care (single responsibility). The developer decides where flash messages can be displayed (usually on "full page render" or if accessed specifically).

They aren't a silver bullet either. If you have multiple browser tabs open, they can be confusing as hell (because flash messages might be shown, that were produced from another tab). However, if your web page requires multiple tabs in the browser, you have some other problems as well. However, I feel acceptance for the occasional "stray flash message" is good.

Jakumi
  • 8,043
  • 2
  • 15
  • 32
  • You slightly misunderstood part of my post from " I can solve this problem by…" — I know that these solutions are hacky and I want to avoid it, that's the reason for question on Stack Overflow! ;) In my application I'm setting contents of flash messages in PHP, twig templates are resposible for displaying them. My initial idea was to subscribe to *some* kernel or security event and check *somehow* which voters voted to "yes!" and if only admin voter voted for "yes!" — showing proper message. Unfortunately, access decision manager does not store voters for "granted" to just fetch it. – Tomasz Gąsior Apr 09 '19 at 23:59
  • that's why I proposed to extend/replace it with one that does. ;o) tbh, take a look at the TraceableAccessManager, which already does this to some extent. The challenge will be, to find out, how to replace this. But it's probably not really hard. – Jakumi Apr 10 '19 at 05:15
  • The class is called TraceableAccess**Decision**Manager by the way (just because I had troubles finding it on Google). Love your answer, I have exactly the same setup with a voter creating flash messages with the problems you described (lost, duplicate, or wrong flash messages). In your opinion, what should flash messages be used for? – Arno Hilke Apr 10 '19 at 14:49
  • 1
    in my opinion, flash messages should be used to display responses to actions. like ... when I deleted something, I like the information "[something] was deleted". it's just for reassurance, that the action was successful (or not). sometimes, the user gets redirected to some other page where the result of his recent action may not be visible. flash messages have one specific property that are related to this: they disappear after being accessed, and they stay until being accessed (even spanning multiple requests). @ArnoHilke you're right, I missed the `Decision`, my bad ;o) – Jakumi Apr 10 '19 at 15:30
  • oh, and with "accessed" I mean literally retrieved from the `FlashBag` in the session. – Jakumi Apr 10 '19 at 15:41
  • Solution which I choose in my context. I modified AdminVoter to only vote for "granted" if main voter does not vote for "granted" — in my context it was simple. Probably not always it is. Then, inside AdminVoter I am dispatching event. In my context flash message seems to be good but basically I can agree that using flash messages this way can be wrong. – Tomasz Gąsior Apr 17 '19 at 09:11