ManyToMany itemOperations "access_control" - php

Thats the code from the docu
// https://api-platform.com/docs/core/security/#security
itemOperations={
"get"={"access_control"="is_granted('ROLE_USER') and object.owner == user"}
}
how can i get that realized with many to many, i tried many different expressions but everytime i get a error.
<?php
// api/src/Entity/Book.php
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Secured resource.
*
* #ApiResource(
* itemOperations={
* "get"={"access_control"="is_granted('ROLE_USER') and object.users == user"}
* }
* )
* #ORM\Entity
*/
class Book
{
// ...
/**
* #var User The owner
*
* #ORM\ManyToMany(targetEntity="App\Entity\User", mappedBy="book", cascade={"persist"})
*/
public $users;
// ...
}

nYou cant in those cases where the target relation is a collection. In this case, users collection.
For these cases, you should create a subscriber with PRE_SERIALIZE event and throw Access Denied exception there.
You have to do something like this. As you say you have a ManyToMany relation, I guess that you have an intermediate entity between book and user, so you should use that repository for find user <-> book then.
<?php
namespace App\EventSubscriber;
use ApiPlatform\Core\EventListener\EventPriorities;
use App\Entity\User;
use App\Entity\Book;
use App\Repository\UserRepository;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
class ChatMessagePreSerializeSubscriber implements EventSubscriberInterface
{
private $tokenStorage;
private $userRepository;
private $authorizationChecker;
public function __construct(
TokenStorageInterface $tokenStorage,
UserRepository $userRepository,
AuthorizationCheckerInterface $authorizationChecker
) {
$this->tokenStorage = $tokenStorage;
$this->userRepository = $userRepository;
$this->authorizationChecker = $authorizationChecker;
}
/**
* {#inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
KernelEvents::VIEW => ['bookPreSerialize', EventPriorities::PRE_SERIALIZE],
];
}
public function bookPreSerialize(GetResponseForControllerResultEvent $event)
{
$book = $event->getControllerResult();
$method = $event->getRequest()->getMethod();
if (!$book instanceof Book || (Request::METHOD_GET !== $method)) {
return;
}
$currentUser = $this->tokenStorage->getToken()->getUser();
if (!$currentUser instanceof User)
return;
$user = $this->userRepository->findOneBy(['id' => $currentUser->getId(), 'book' => $book]);
if (!$user instanceof User)
throw new AccessDeniedHttpException();
}
}

Here is something I did for a resource that is ManytoOne related to intermediate entity Events ManytoOne related to one Organizer, with Users ManyToMany related to Organizers (collection).
I transform the collection to Array and use "in" operator to compare data. For a more sophisticated operation you should look at Doctrine Extension as it's describe in API Platform doc.
#[ApiResource(
operations: [
new GetCollection(),
new Post(),
new Get(security: "object.getEvent().getOrganizer() in user.getOrganizers().toArray()"),
new Patch(),
new Delete()
]
)]

Related

Redirect users based on attribute

Alright so what am I trying to do is that I check if user status is "pending" and if so, I'd redirect him to "/pending" page.
Now I need this check on almost the entire website.
I tried with the decision manager but was unable to redirect, any other way to do this?
This should be called only for logged users
security.yaml
access_decision_manager:
service: App\Security\StatusAuthenticator
And the StatusAuthenticator
<?php
namespace App\Security;
use App\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
class StatusAuthenticator implements AccessDecisionManagerInterface
{
/**
* #param TokenInterface $token
* #param array $attributes
* #param null $object
* #return bool|void
*/
public function decide(TokenInterface $token, array $attributes, $object = null)
{
if($token->getUser()->getStatus() == User::USER_STATUS_PENDING) {
// Needs to be redirected to /pending
return false;
}
return true;
}
}
Since you need to "check this on almost the entire website", you can use an EventListener that will fire on every request and there you can check if you have an authenticated user and their status.
// src/EventListener/PendingUserListener.php
namespace App\EventListener;
use App\Entity\User;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
class PendingUserListener implements EventSubscriberInterface
{
/**
* #var Security
*/
private $security;
/**
* #var UrlGeneratorInterface
*/
private $urlGenerator;
public function __construct(Security $security, UrlGeneratorInterface $urlGenerator)
{
$this->security = $security;
$this->urlGenerator = $urlGenerator;
}
public static function getSubscribedEvents()
{
return [ KernelEvents::REQUEST => 'onKernelRequest' ];
}
public function onKernelRequest(RequestEvent $event)
{
$pending_route = 'pending';
$user = $this->security->getUser();
if (!$event->isMasterRequest()) {
return;
}
if (!$user instanceof UserInterface) {
return;
}
// Check if the requested page is 'pending', prevent redirect loops
if ($pending_route === $event->getRequest()->get('_route')) {
return;
}
// RedirectResponse expects a full url, generate from route name
if (User::USER_STATUS_PENDING == $user->getStatus()) {
$event->setResponse(
new RedirectResponse($this->urlGenerator->generate($pending_route))
);
}
}
}

Symfony 5 Can't Inject EntityManagerInterface Into Validator

I've just moved to symfony 5 and baffled! I've done this same thing with validators many times with Symfony 4, but now dependency injection of EntityManagerInterface into a custom validator produces this error:
Too few arguments to function
App\Validator\Constraints\UserUsernameConstraintValidator::__construct(),
0 passed in
/var/www/appbaseV4/vendor/symfony/validator/ContainerConstraintValidatorFactory.php
on line 52 and exactly 1 expected
The validator class is as follows:
<?php
namespace App\Validator\Constraints;
use App\Entity\User;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class UserUsernameConstraintValidator extends ConstraintValidator
{
/**
* #var EntityManagerInterface
*/
private $em;
/**
* UserEmailValidator constructor.
* #param EntityManagerInterface $entityManager
*/
public function __construct(EntityManagerInterface $entityManager)
{
$this->em = $entityManager;
}
/**
* #param mixed $value
* #param Constraint $constraint
*/
public function validate($value, Constraint $constraint) : void
{
if(!$constraint instanceof UserUsernameConstraint){
throw new UnexpectedTypeException($constraint,UserUsernameConstraint::class);
}
if(null === $value || '' === $value){
return;
}
if($value != $constraint->value){
/** #var UserRepository $repo */
$repo = $this->em->getRepository(User::class);
if($repo->findOneBy(['username'=>$value])){
$this->context->buildViolation($constraint->errorMessage)->setParameter('username',$value)->addViolation();
}
}
}
}
And then using it in the form type as i always do:
$builder
->add('username',TextType::class,[
'attr'=>[
'class'=>'form-control'
],
'required'=>true,
'constraints'=>[
new UserUsernameConstraint(),
new Length([
'min'=>6,
'max'=>20
]),
]
])
What's going on here? Have they changed this in Symfony 5 because it's just not injecting the entity manager like it normally does when i use symfony 4.
It seems that you are not using the default services.yaml configuration, in this case you have to tag your constraint validator with the validator.constraint_validator tag. Check the documentation for Constraint Validators with Dependencies or fix/update your Automatic Service Loading in config/services.yaml.

Laravel 5.5: Authorization Policy AccessDeniedHttpException This action is unauthorized

I created a policy for authorization, so I faced with this problem.
I have seen these solutions, but my problem didn't solve yet:
Solution 1
Solution 2
Solution 3
Here are the Codes:
Function used in ArticalesController Class:
public function show(Articale $articale)
{
$this->authorize('view', $articale);
return view('articales.show',compact('articale'));
}
ArticalePolicy Class:
<?php
namespace App\Policies;
use App\User;
use App\Articale;
use Illuminate\Auth\Access\HandlesAuthorization;
class ArticalePolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view the articale.
*
* #param \App\User $user
* #param \App\Articale $articale
* #return mixed
*/
public function view(User $user, Articale $articale)
{
return $user->id == $articale->user_id;
}
AuthServiceProvider Class:
namespace App\Providers;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
Articale::class => ArticalePolicy::class,
];
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
//
}
}
Try with your model name as like this replace it with your AuthServiceProvider
replace
Articale::class => ArticalePolicy::class,
with
'App\Articale' => 'App\Policies\ArticalePolicy',

Symfony 4 Doctrine LifecycleEventArgs getEntity() vs getObject()

What is the difference between LifecycleEventArgs::getObject() and LifecycleEventArgs::getEntity()?
namespace App\EventListener;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Events;
/**
* Class MyListener
*
* #package App\EventListener
*/
class MyListener implements EventSubscriber
{
/**
* #return array|string[]
*/
public function getSubscribedEvents()
{
return [
Events::postUpdate,
];
}
/**
* #param LifecycleEventArgs $event
*/
public function postUpdate(LifecycleEventArgs $event)
{
$entity = $event->getEntity();
$object = $event->getObject();
$entity === $object; //true...
}
}
So far as I can tell these two methods return the exact same object, ie they point to the same instance of a given Entity.
Is that always the case?
Should one be used over the other or does it not matter?
There is no difference. The getObject() method comes from the parent class of the LifecycleEventArgs class which is provided by the doctrine/persistence package.
The base event class is mainly helpful when you want to build an integration layer for several Doctrine implementations (e.g. ORM and ODM) and in which case you would use getObject().

Symfony4 - Error while visting /register page

I am using FOSUserBundle with Symfony4.
When trying to visit /register url, I am getting this error message:
Attempted to load class "User" from namespace "AppBundle\Entity".
Did you forget a "use" statement for e.g. "Symfony\Component\Security\Core\User\User", "Symfony\Bridge\Doctrine\Tests\Fixtures\User" or "FOS\UserBundle\Model\User"?
Another SO user posted this question, but the suggested answer still not working in my case.
Here is the code for User.php class.
namespace AppBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function __construct()
{
parent::__construct();
// your own logic
}
}
And here for config/packages/fos_user.yaml
fos_user:
db_driver: orm
firewall_name: main
user_class: AppBundle\Entity\User
Here is the RegistrationController code related to register action. (note: this is auto-generate code, I have not changed anything)
/*
* This file is part of the FOSUserBundle package.
*
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\UserBundle\Controller;
use FOS\UserBundle\Event\FilterUserResponseEvent;
use FOS\UserBundle\Event\FormEvent;
use FOS\UserBundle\Event\GetResponseUserEvent;
use FOS\UserBundle\Form\Factory\FactoryInterface;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Model\UserInterface;
use FOS\UserBundle\Model\UserManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
/**
* Controller managing the registration.
*
* #author Thibault Duplessis <thibault.duplessis#gmail.com>
* #author Christophe Coevoet <stof#notk.org>
*/
class RegistrationController extends Controller
{
private $eventDispatcher;
private $formFactory;
private $userManager;
private $tokenStorage;
public function __construct(EventDispatcherInterface $eventDispatcher, FactoryInterface $formFactory, UserManagerInterface $userManager, TokenStorageInterface $tokenStorage)
{
$this->eventDispatcher = $eventDispatcher;
$this->formFactory = $formFactory;
$this->userManager = $userManager;
$this->tokenStorage = $tokenStorage;
}
/**
* #param Request $request
*
* #return Response
*/
public function registerAction(Request $request)
{
$user = $this->userManager->createUser();
$user->setEnabled(true);
$event = new GetResponseUserEvent($user, $request);
$this->eventDispatcher->dispatch(FOSUserEvents::REGISTRATION_INITIALIZE, $event);
if (null !== $event->getResponse()) {
return $event->getResponse();
}
$form = $this->formFactory->createForm();
$form->setData($user);
$form->handleRequest($request);
if ($form->isSubmitted()) {
if ($form->isValid()) {
$event = new FormEvent($form, $request);
$this->eventDispatcher->dispatch(FOSUserEvents::REGISTRATION_SUCCESS, $event);
$this->userManager->updateUser($user);
if (null === $response = $event->getResponse()) {
$url = $this->generateUrl('fos_user_registration_confirmed');
$response = new RedirectResponse($url);
}
$this->eventDispatcher->dispatch(FOSUserEvents::REGISTRATION_COMPLETED, new FilterUserResponseEvent($user, $request, $response));
return $response;
}
$event = new FormEvent($form, $request);
$this->eventDispatcher->dispatch(FOSUserEvents::REGISTRATION_FAILURE, $event);
if (null !== $response = $event->getResponse()) {
return $response;
}
}
return $this->render('#FOSUser/Registration/register.html.twig', array(
'form' => $form->createView(),
));
}
}
And Stack Trace:
Symfony\Component\Debug\Exception\ClassNotFoundException:
Attempted to load class "User" from namespace "AppBundle\Entity".
Did you forget a "use" statement for e.g. "Symfony\Component\Security\Core\User\User", "Symfony\Bridge\Doctrine\Tests\Fixtures\User" or "FOS\UserBundle\Model\User"?
at vendor\friendsofsymfony\user-bundle\Model\UserManager.php:40
at FOS\UserBundle\Model\UserManager->createUser()
(vendor\friendsofsymfony\user-bundle\Controller\RegistrationController.php:59)
at FOS\UserBundle\Controller\RegistrationController->registerAction(object(Request))
(vendor\symfony\http-kernel\HttpKernel.php:149)
at Symfony\Component\HttpKernel\HttpKernel->handleRaw(object(Request), 1)
(vendor\symfony\http-kernel\HttpKernel.php:66)
at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), 1, true)
(vendor\symfony\http-kernel\Kernel.php:190)
at Symfony\Component\HttpKernel\Kernel->handle(object(Request))
(public\index.php:37)
How to fix this?
You're using Symfony 4 which doesn't have bundles by default, it stores entities in the src/Entity directory.
The error comes from the namespace in your User class that you probably copied.
Try setting the namespace of your class to App/Entity.

Categories