Symfony 4 Event Subscriber User - symfony

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

Related

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

Where should I place my users method in Symfony 4?

I followed the symfony 4 documentation to make a login form (https://symfony.com/doc/current/security/form_login_setup.html) and I added a registration form in the same controller.
I'm a beginner, and I would like to make an account page where the user will be able to change his informations, but I would like to know if I should create a new Controller who work with the user entity, on just work onthe same controller than the login and registration ?
Or maybe my user controller have to inherit the securityController?
I'm a noob, sorry ^^'
Thank you
You can give a look at https://symfony.com/doc/current/service_container.html#creating-configuring-services-in-the-container
The path is creating your own service(s), for example App\Servie\UserManager that performs every task on a User object
For example, you could have:
App\Service\UserManager
class UserManager
{
// ...
public function handleUpdatePasswordRequest(Request $request) {...}
// or
public function handleUpdatePasswordForm(Form $form) {...}
// or:
public function handleUpdatePassword(User $user, $newPlainPassword) {...}
...
}
as to say, whatever you want to implement, keeping in mind that the thinner the controllers are better it is, while services can grow (and be split) indefinitely

Laravel service provider for sharing a variable in all views

I want to share a variable in all views but i'm not sure if this is the right way to do it? I have made a service provider:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Worktype;
class ShareWorktypesInViewsProwider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
$worktypes = Worktype::all();
view()->share('worktypes', $worktypes);
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
//
}
}
but i still get an error in my view. Do i need to register my service provider or it should work out of the box?
Firstly, for such a small piece of code I wouldn't worry about creating a brand new service provider. I would just add the above to your AppServiceProvider. Also, you code inline the above as well:
view()->share('worktypes', Worktype::all());
As for registering a provider. Literally all you have to do is go to config/app.php, find the providers array and add your provider to it.
In your can you would add:
App\Providers\ShareWorktypesInViewsProwider::class,
The documentation for it:
https://laravel.com/docs/5.3/providers#registering-providers
Hope this helps!
A more recent update on this. While #Rwd solution works great, you may run into difficulties as the service provider is run every single request.
As a result, you'll end up requesting Worktype from the database regardless of whether you're on a view, etc.
The best way to achieve this now is by using Laravel View composers.
By adding the below into your service provider, you'll only call the Worktype::all() when needed within a view.
view()->composer('*', function($view) {
$view->with(['worktypes' => Worktype::all()]);
});
Although make sure to use some caching otherwise it'll get called for every view!

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

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.

How to contextualize Symfony 2 routes without request parameters

I've been searching high and low for a solution here, but I'm not really finding what I need. It may just be that I'm not searching for the right thing.
For the admin backend of my app, I currently grab a user for a controller like this:
//routing.yml
view_user:
pattern: /user/{id}/
defaults: { _controller: AppBundle:User:view }
//UserController.php
use Sauce\AppBundle\Entity\User;
public function viewAction(User $user)
{
$user->getSauce();
}
For the frontend where a user doesn't fetch themselves at each URL using an id, this doesn't work. Is it possible to do this without having an id in the URL? My first thought of how to make a user easily available is something like:
/**
* A fast way to grab the user throughout this class.
*/
public function getUser() {
return $this->get('security.context')->getToken()->getUser();
}
public function viewAction()
{
$user = $this->getUser();
...
}
But this, to me, seems way below Symfony 2 standards. I must be overlooking something more elegant. Any insights out there?
Yea, creating a helper method is the way to go. You could move it to a base controller to stay DRY. My abstract base controller provides such a method.
There is no “magic” way of doing this I'm aware of.

Resources