Symfony/DRY - check if user is granted in every action - symfony

I'm using this code to check if a user is granted in my Symfony application :
$securityContext = $this->container->get('security.context');
if($securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED') ){
$user = $this->get('security.context')->getToken()->getUser()->getId();
} else {
return $this->render('IelCategoryBundle:Category:home.html.twig');
}
I have to ckeck this in almost every CRUD action that I'm writing (edit, delete, ...).
I feel not DRY at all (no play on words ;-)). Is there a better way to check this in many actions ?

JMSSecurityExtraBundle provides the #Secure annotation which eases checking for a certain user-role before invoking a controller/service method.
use JMS\SecurityExtraBundle\Annotation as SecurityExtra;
/** #SecurityExtra\Secure(roles="IS_AUTHENTICATED_REMEMBERED") */
public function yourAction()
{
// ...
}

Your best bet would be to rely on Kernel event listeners: http://symfony.com/doc/current/cookbook/service_container/event_listener.html.
Implement your listener as a service and then you could set $this->user to desired value if isGranted results TRUE. Later on, you could easily retrieve the value within the controller using:
$myServiceListener->getUser();
On the other hand, if check fails you could easily redirect user to your home.html.twig.

Related

Symfony 4 Event Subscriber User

I created a middleware/event subscriber to generalise a few security checks I need to perform on routes with a specific request attribute.
But in order to perform the required database query I need to get access to the authenticated user.
When I call $event->getRequest()->getUser() it always returns null even when the user is authenticated.
Where can I find more information about this?
And one more question: is using EventSubscribers a good practice for my needs?
Thanks in advance.
In https://stackoverflow.com/a/55417508/4707978 is the answer.
use Symfony\Component\Security\Core\Security;
class ExampleService
{
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function someMethod()
{
$user = $this->security->getUser();
}
}
And one more question: is using EventSubscribers a good practice for my needs?
Probably not, you should check out security voters. With them you could implement your own custom security logic and enforce it with the given symfony tools like #IsGranted etc..
https://symfony.com/doc/current/security/voters.html

Handle multi level of roles

I have a multi applications which works with authenticated users.
All these applications can be deployed together for differents clients but with the same user database.
For example a chain of hotels.
User's roles informations are available in a header in each request.
For example a manager has full access in his own hotel but only read access in another hotel.
Ex:
{
["organization":"paris","roles":[ADMIN,ROOT]],
["organization":"london","roles":[READ]]
}
How can I handle many levels of roles by organizations?
I read some documentation in the symfony about voters and roles but nothing about roles in a kind of groups.
Voter is the way to go
// src/Security/PostVoter.php
namespace App\Security;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class OrganisationVoter extends Voter
{
// these strings are just invented: you can use anything
const READ= 'READ';
const EDIT = 'EDIT ';
protected function supports($attribute, $subject); bool //todo
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
// [...] check class like documentation
$organisation= $subject;
switch ($attribute) {
case self::READ:
return $this->canView($organisation, $user);
case self::EDIT:
return $this->canEdit($organisation, $user);
}
}
private function canView(Organisation $organisation, User $user)
{
//here your logic if your user has the same organisation
}
private function canEdit(Organisation $organisation, User $user)
{
//here your logic if your user has the same organisation than the one in parameter and the good level of right (admin, root)
}
}
Then in your controller (or twig or wherever)
if ($this->security->isGranted(OrganisationVoter::EDIT, $organisation)) {
return true;
}
What you describe has "attribute-based access control" written all over. abac helps you externalize / decouple authorization from the application / API you want to protect. This means you can develop functionality independently of the authorization logic.
There are a couple of standards out there - namely XACML and ALFA (abbreviated language for authorization).
This is what the architecture looks like:
the Policy Enforcement Point (PEP) intercepts the business flow and create an authorization request which it sends to the PDP
The Policy Decision Point (PDP) evaluates the incoming request against the policies it's been configured with. It eventually returns a decision to the PEP
The PDP may use Policy Information Points (PIP) to retrieve missing metadata (a user's department, role, location; a resource's department, owner...)
The previous answer forces you to implement a voter. That's very brittle and will require coding and updating it regularly as your requirements change. In ALFA, you don't need to do that. You simply write policies in plain old English that use the attributes you are interested in. For instance:
A user with role == "manager" can do action == "view" on object of type == "hotel"
A user with role == "manager" can do action == "edit" on object of type == "hotel" if hotel.owner == user.name

Api-Platform: using PUT for creating resources

I would like to use the PUT method for creating resources. They are identified by an UUID, and since it is possible to create UUIDs on the client side, I would like to enable the following behaviour:
on PUT /api/myresource/4dc6efae-1edd-4f46-b2fe-f00c968fd881 if this resource exists, update it
on PUT /api/myresource/4dc6efae-1edd-4f46-b2fe-f00c968fd881 if this resource does not exist, create it
It's possible to achieve this by implementing an ItemDataProviderInterface / RestrictedDataProviderInterface.
However, my resource is actually a subresource, so let's say I want to create a new Book which references an existing Author.
My constructor looks like this:
/**
* Book constructor
*/
public function __construct(Author $author, string $uuid) {
$this->author = $author;
$this->id = $uuid;
}
But I don't know how to access the Author entity (provided in the request body) from my BookItemProvider.
Any ideas?
In API Platform many things that should occur on item creation is based on the kind of request it is. It would be complicated to change.
Here are 2 possibilities to make what you want.
First, you may consider to do a custom route and use your own logic. If you do it you will probably be happy to know that using the option _api_resource_class on your custom route will enable some listeners of APIPlaform and avoid you some work.
The second solution, if you need global behavior for example, is to override API Platform. Your main problem for this is the ReadListener of ApiPlatform that will throw an exception if it can't found your resource. This code may not work but here is the idea of how to override this behavior:
class CustomReadListener
{
private $decoratedListener;
public function __construct($decoratedListener)
{
$this->decoratedListener = $decoratedListener;
}
public function onKernelRequest(GetResponseEvent $event)
{
try {
$this->decoratedListener->onKernelRequest($event);
} catch (NotFoundHttpException $e) {
// Don't forget to throw the exception if the http method isn't PUT
// else you're gonna break the 404 errors
$request = $event->getRequest();
if (Request::METHOD_PUT !== $request->getMethod()) {
throw $e;
}
// 2 solutions here:
// 1st is doing nothing except add the id inside request data
// so the deserializer listener will be able to build your object
// 2nd is to build the object, here is a possible implementation
// The resource class is stored in this property
$resourceClass = $request->attributes->get('_api_resource_class');
// You may want to use a factory? Do your magic.
$request->attributes->set('data', new $resourceClass());
}
}
}
And you need to specify a configuration to declare your class as service decorator:
services:
CustomReadListener:
decorate: api_platform.listener.request.read
arguments:
- "#CustomReadListener.inner"
Hope it helps. :)
More information:
Information about event dispatcher and kernel events: http://symfony.com/doc/current/components/event_dispatcher.html
ApiPlatform custom operation: https://api-platform.com/docs/core/operations#creating-custom-operations-and-controllers
Symfony service decoration: https://symfony.com/doc/current/service_container/service_decoration.html

Get the user in a RouteProviderInterface implementation

Is it possible to get the user in a RouteProviderInterface implementation?
My RouteProviderInterface implementation loads a number of new Routes, nothing special. But I want to customize the Routes based on a user setting, if a user is logged in.
If I inject the TokenStorage, the user is not loaded and null:
public function __construct(TokenStorage $tokenStorage) {
$this->user = $tokenStorage->getToken()->getUser(); // is null
}
Is there another way to get the user?
Some edit based on comments:
I am trying this with a authenticated user. I also dump the user in the actual controller being used and the user does exists there
All firewalls have "stateless: true" in the config

sfUser equivalent in Symfony 2

What is the sfUser equivalent in Symfony 2?
sfUser allowed getting user session attributes using getAttribute() method.
Is Symfony\Component\Security\Core\User\User its equivalent? But it doesn't have getAttribute() method in it.
To get session attributes, get the session service from the container. This example is in a controller, where the session is available via a helper method:
public function fooAction()
{
$session = $this->getRequest()->getSession();
// Setting
$session->set("foo", "bar");
// Retrieving
$foo = $session->get("foo");
}
See the documentation for details. You can also retrieve the session explicitly from the container should you need it, via $container->get("session");
If you need the User object, you can get it via:
public function fooAction()
{
// Get user from security token (assumes logged in/token present)
$user = $this->get("security.context")->getToken()->getUser();
}
Again, see the documentation for further details.

Resources