Check if user object is set inside Symfony expression language annotation - php

I am using the #Security annotation to control which roles have access to certain routes in my Symfony 3.4 application, it works when I am logged in however when the user object doesn't exist such as when the session times out I get the following exception thrown.
Unable to get a property on a non-object.
vendor/symfony/symfony/src/Symfony/Component/ExpressionLanguage/ExpressionLanguage.php:78
at
Symfony\Component\ExpressionLanguage\ExpressionLanguage->evaluate('\'ROLE_MANAGER\'
in user.getRoles()',
My method definition looks like this:
/**
* #Route("/club/{id}/trophies", name="club_trophies", methods={"GET","POST"})
* #IsGranted("IS_AUTHENTICATED_FULLY")
* #Security("'ROLE_MANAGER' in user.getRoles()")
* #param Club $club
* #return Response
*/
public function trophies(Club $club): Response
{
Is there a way using the Symfony Expression Language, or similar, that I can check that user exists. Or is there a better way?

When you are not authenticated, the value of user is null, so it's normal that your check is throwing an exception (as you're trying to access the method getRoles() of a null object).
The proper ways to check if a user has a given role using annotations are :
#IsGranted("ROLE_MANAGER")
Or :
#Security("is_granted('ROLE_MANAGER')")
You can see more here : https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/security.html

Related

Custom collection operation and IRI conversion problem

I have a problem with Api Platform and custom collection operation, when I need to manually require an argument in the route.
My first need is to GET on this route: query/userjob/[USER UUID] and retrieve a collection of all jobs for the given user.
My second need is to be able to GET on query/userjob/[USER UUID]/[JOB UUID] and retrieve details for the given user's job.
It might be important to say that I have no Api resource nor entity User, so I exclude all kind of subresource mapping or query.
So, let's say i have a UserJob ApiResource mapped as below:
App\Domain\User\Projection\UserJob:
itemOperations:
get:
method: 'GET'
path: '/userjob/{userId}/{jobId}'
requirements:
userId: '%uuid_regex%'
jobId: '%uuid_regex%'
collectionOperations:
get:
method: 'GET'
path: '/userjob/{userId}'
requirements:
userId: '%uuid_regex%'
attributes:
route_prefix: "/query"
In the class, I have:
final class UserJob
{
public $id; //int Auto inc
public $userId; //a UUID
public $jobId; //a UUID
public function __construct($userId, $jobId)
{
$this->userId = $userId;
$this->jobId = $jobId;
}
public function getId(): int
{
return $this->id;
}
public function getUserId()
{
return $this->userId;
}
public function getJobId()
{
return $this->jobId
}
I built a custom data provider for this class, in which I wrote the way to get the resource from the giver parameter (userId):
public function getCollection(string $resourceClass, string $operationName = null)
{
$userId = $this->request->getCurrentRequest()->attributes->get('userId');
return $this->repository->entityManager->getRepository($resourceClass)->findByUserId($userId);
}
When i make a GET call to, let's say, query/userjob/148e3200-f793-447b-bde8-af6b7b27372c it throws an exception:
Unable to generate an IRI for App\Domain\User\Projection\UserJob
And if I debug deeper, in the IRIConverter class, I find that the original exception is thrown from Router:
Some mandatory parameters are missing ("userId") to generate a URL for route "api_user_jobs_get_collection".
Nevertheless, if i dump the result of $this->repository->entityManager->getRepository($resourceClass)->findByUserId($userId);, all the elements that i'm looking for are well fetched from database.
So my intuition is that somehow ApiPlatform process fails to build the collection IRI that we usually can find at the beginning of the payload, and which in my case would be query/userjob/148e3200-f793-447b-bde8-af6b7b27372c.
And it fails while on the normalization or serialization process, because the "extra" param of my custom operation (the user UUID) is not passed to the collection normalizer, iri converter classes, so it has no way to give to the router the missing param to build the "api_user_jobs_get_collection" route.
What am I missing here? Is this a well-known problem that has a readymade solution that I missed ?
Or do I have to look for:
decorate the IRI converter?
use a custom normalizer?
do something with composite ids?
something else?
Your use case may have more solutions, and it depends what is preferred:
decorate iri converter, as you are using identifier in collection and this is not supported out of the box by API platform, as per my knowledge. And this is best choice if url like this are the style of your api.
use custom controller action with custom url style (docs: https://api-platform.com/docs/core/controllers/#creating-custom-operations-and-controllers), best if this is rare url in your api
Annotate your ids as identifiers in your api resource class (doc: https://api-platform.com/docs/core/identifiers/#custom-identifier-normalizer)
/**
* #var Uuid
* #ApiProperty(identifier=true)
*/
public $code;
but I haven't tried this with multiple ids, and this may work only for item url.
You may try to use custom data provider (docs: https://api-platform.com/docs/core/data-providers/). This will need to be done per resource or globally (supports() method) and you will need somehow (regex?) extract ids from url from $context array in getCollection() and getItem() methods. But as ApiPlatform will try to generate item iri, you may still end up with decorating iri converter.
Note: Using id in collection url may lead to other problems, like OpenAPI documentation generation. You may consider if what you want is not filtering of collection by "id" field, nicely supported, or retrieving collection of only "your" items. Which can be done by your data provider injecting security or by doctrine query extensions if someone uses doctrine.

PHP Symfony FOSRestBundle and Dependency Injection

Assumed I have a normal Symfony entity "Car", a self written service "Log" and I want to build a rest route
PUT http://mysite.de/api/{version}/cars/{oldcar}
to do something with a car given in "oldcar" stored in my database. In the body of the PUT there comes another Car object in JSON notation with the new data. Then I have following method:
/**
* #Rest\Put("/api/{version}/cars/{oldcar}")
* #ParamConverter("oldcar", class="MyBundle\Entity\Car")
* #ParamConverter("newcar", class="MyBundle\Entity\\Car", converter="fos_rest.request_body")
*/
public function putAction(Log $logger, EntityManagerInterface $em, Car $oldcar, Car $newcar)
{
... update the oldcar in the database with data from the newcar ...
}
I got this allready working!
But now I want to know: Is there a configuration of the FosRestBundle to omit the #ParamConverter annotations? In my opinion there are enough information for the framework to do this automatically without them.
Log and EntityManagerInterface can be found in the service container. oldcar's and newcar's types are defined in the method signature. Primary key for database access for oldcar is given by the route. There is only the JSON data in the body left for newcar.
Does anybody here get this working without the #ParamConverter annotations?
Please tell me if and a short idear how.

Using custom ParamConverter for POST Request in Symfony 2

I'm using Symfony 2.6 and the FOS Rest Bundle.
Param converters for PATCH , DELETE and GET requests work nicely and reduce the code in the controller actions. However for POST requests I have a problem. The default \Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter gets called every time. This results in an exception:
Unable to guess how to get a Doctrine instance from the request information.
I checked the \Sensio\Bundle\FrameworkExtraBundle\EventListener\ParamConverterListener and saw that it's always including the Doctrine param converter in the onKernelController method. From the documentation it seems that the doctrine param converter is automatically applied for all type hinted controller actions unless you set it to off:
sensio_framework_extra:
request:
converters: true
auto_convert: false
I found a kind of hacky way to prevent this. The array of param converters to be applied will be indexed by the name of the type hinted argument in the controller method (which symfony gets by reflection). If I just name my param converter the same as this type hint then the default doctrine param converter will not be added to the list of param converters. For example:
...
* #ParamConverter(
* "MyEntity",
* class="Foo\Bar\MyEntity",
* converter="my_custom_converter"
* )
*
* #param MyEntity $myEntity
* #return MyEntity
*/
public function postMyEntityAction(MyEntity $myEntity)
{
I sort of wrote this question as I was digging deeper into the code and I'm not even really sure what my question is anymore. I guess it's "Is it logical to apply multiple param converters?" or would also like to know if it's possible to turn off param converters for certain actions. Maybe my logic is completely wrong here and this isn't what param converters were intended for.
I'd appreciate any advice.
Alright, I realized where I was going wrong. It was a simple case of not returning true from my custom paramConverter apply method. If it does return true then the doctrine param converter won't be applied.

Get default route from dependency injection services

I am using request object in twig extension class to get the current route. For instance, having the following url:
http://www.localhost/project/user/page/2
Inside of the twig extension I'm able to get user/page/2 string and do something with it.
The problem arises when I wanna get the default route using the same method, which I have to do. For example, accessing the following url:
http://www.localhost/project/user
I want to get user/page/1 string inside the twig extension class, and not just user.
The controller looks like this:
/**
* #Route(name="user",
* default="user/page/1")
*/
Is there a way to achieve that? Or do I have to stop using default routes?
Write a comment if you need more explanation, it's 9AM here in Poland and I'm sleeping yet.
The #Route documentation explains that you can do this for set a default page number:
/**
* #Route("/project/user/page/{page}",
* name="user",
* defaults={"page" = 1},
* requirements={"page" = "\d+"}
* )
*/

Validation POST & GET methods in Symfony2

I'm using annotation to set my routes and method types. Is there a way to only allow certain types of post data. Currently I'm doing the following:
/**
* #Route("/myurl", requirements={"varID" = "\d+"} )
* #Method({"POST"})
* #Template()
*/
But if a varID gets submitted with a string value then it goes through anyway... I'm guessing due partly to there being no {varID} in the route? Is there a way to validate POST data like this in Symfony?
Change annotation into this:
/**
* #Route("/myurl/{varID}", requirements={"varID" = "\d+"} )
* #Method({"POST"})
* #Template()
*/
You must tell symfony wich part of url is yours varID variable to allow engine to check datatype. Than you get an exception:
No route found for "GET /myurl/somestring"
404 Not Found - NotFoundHttpException
1 linked Exception:

Categories