I'm trying to use #Security annotations for my routes. Like this:
/**
* #return Response
* #Route("/action")
* #Security("has_role('ROLE_USER')")
* #Template()
*/
public function someAction()
{
return array();
}
When the security restriction fires an exception, I get the message Expression "has_role('ROLE_USER')" denied access.
This is not acceptable to be shown to the end user, so I'm trying to find a way to customize the message for annotation.
Simple workaround is to not to use #Secutity annotations and write code like these:
/**
* #return Response
* #Route("/action")
*
* #Template()
*/
public function someAction()
{
if (!$this->get('security.context')->isGranted('ROLE_USER')) {
throw new AccessDeniedException('You have to be logged in in order to use this feature');
}
return array();
}
But this is less convenient and less readable.
Is it possible to write custom message to #Security annotations?
As soon as I realized that this is not possible, I have made a pull request to the Sensio FrameworkExtra Bundle to make this possible.
This PR allows to customize displayed message by specifying the message parameter like
#Security("has_role('ROLE_USER')",message="You have to be logged in")
Related
I have 2 services, BlueWorkerService and YellowWorkerService, both implementing the same interface, WorkerServiceInterface. Each of these services use the same entities but with different required logic.
I need to inject one of, but not both, of these classes and use them in ProcessorService so that the interface methods are called using on correct Worker. Which worker service to use is dependent on which Worker is currently being processed. I'll break it down:
Class WorkerProcessor {
private $workerService;
public function __construct(WorkerServiceInterface $workerServiceInterface)
{
$this->workerService = $workerServiceInterface;
}
public function getMixedColourWithRed() {
return $this->workerService->mixWithRed();
}
}
The worker service that is being used would be based on whether the worker being processed has the colour property of Blue or Yellow.
I know I can probably use a Factory to achieve this as described here but my problem is how to tell the factory which Worker colour I am processing?
Running on Symfony 3.4
If you need more info, just ask and I will update the question.
NOTE: I'm using Symfony 4.3.1. I'll post it like that, then I'll help you to move all code from this architecture to Symfony 3.4.
I'm using a similar concept to load different classes in my project. Let me explain first, then I'll add code under this text.
Firstly, I'm loading a custom compiler pass under src/Kernel.php (your file is app/AppKernel.php):
/**
* {#inheritDoc}
*/
public function build(ContainerBuilder $container)
{
$container->addCompilerPass(new BannerManagerPass());
}
BannerManagerPass its created under src/DependencyInjection/Compiler (in your case should be src/BUNDLE/DependencyInjection/Compiler`).
class BannerManagerPass implements CompilerPassInterface
{
/**
* {#inheritDoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->has(BannerManager::class)) {
return;
}
$definition = $container->findDefinition(BannerManager::class);
$taggedServices = $container->findTaggedServiceIds('banner.process_banners');
foreach (array_keys($taggedServices) as $id) {
$definition->addMethodCall('addBannerType', [new Reference($id)]);
}
}
}
As you see, this class should implement CompilerPassInterface. You can observe that I'm looking for specific services tagged as banner.process_banners. I'll show how I tagged services a little bit later. Then, I'm calling addBannerType method from BannerManager.
App\Service\BannerManager.php: (in your case src/BUNDLE/Service/BannerManager.php)
class BannerManager
{
/**
* #var array
*/
private $bannerTypes = [];
/**
* #param BannerInterface $banner
*/
public function addBannerType(BannerInterface $banner)
{
$this->bannerTypes[$banner->getType()] = $banner;
}
/**
* #param string $type
*
* #return BannerInterface|null
*/
public function getBannerType(string $type)
{
if (!array_key_exists($type, $this->bannerTypes)) {
return null;
}
return $this->bannerTypes[$type];
}
/**
* Process request and return banner.
*
* #param string $type
* #param Server $server
* #param Request $request
*
* #return Response
*/
public function process(string $type, Server $server, Request $request)
{
return $this->getBannerType($type)->process($request, $server);
}
}
This class has a custom method (created by me) called process(). You can name it whatever you want it, but I think that's pretty verbose. All parameters are sent by me, so don't mind. You can send whatever you want.
Now we have our Manager and compiler pass is set. It's time to set our banner types (based on my example) and tag them!
My banner types are under src/Service/Banner/Types (in your case should be src/BUNDLE/Service/WhateverYouWant/Type. This does not matter! You can change it later from services.yaml).
These types are implementing my BannerInterface. It does not matter the code under the class in this instance. One more thing that I should warn you! You should see that under BannerManager, inside the addBannerType() I'm calling $banner->getType(). This is one method inherited from BannerInterface in my case and it has a unique string (in my example I have three banner types: small, normal, large). This method can have any name, but don't forget to update it as well in your manager.
We are almost ready! We should tag them, then we are ready to try them!
Go to your services.yaml and add these lines:
App\Service\Banner\Types\:
resource: '../src/Service/Banner/Types/'
tags: [banner.process_banners]
Please see the tag!
Whatever I want to show a custom banner, I'm using a simple URL with $_GET where I keep my banner type, then I load it like this:
public function view(?Server $server, Request $request, BannerManager $bannerManager)
{
...
return $bannerManager->getBannerType($request->query->get('slug'))->process($request, $server);
}
I created a Entity with a custom contoller:
// api/src/Entity/UserRegistration.php
namespace App\Entity;
use ...
/**
* UserRegistraion Data
*
* #ApiResource(collectionOperations={},itemOperations={"post"={
* "method"="POST",
* "path"="/register",
* "controller"=CreateUser::class}})
*
*/
class UserRegistration
{
.....
/**
* #var string The E-mail
*
* #Assert\NotBlank
* #Assert\Email(
* message = "The email '{{ value }}' is not a valid email.",
* checkMX = true
* )
*/
public $email;
.....
And a custom Controller:
// api/src/Controller/CreateUser.php
class CreateUser
{
.....
public function __invoke(UserRegistration $data): UserRegistration
{
return $data;
}
}
When I call the controller with wrong data (e.g wrong email-address) I would expect an validation error, but it is not checked.
Is there a way to do this?
Api Platform does the validation on the result of your controller, to make sure your data persisters will receive the right information. Thus you may get invalid data when entering your controller, and need to perform the validation manually if your action needs a valid object.
The most common approaches are either using a Form, which provides among other things validation, or just the Validator as a standalone component. In your case you - since are using ApiPlatform - the latter would be the better choice as you don't need to render a form back to the user, but instead return an error response.
First you will need to inject the Validator into your Controller:
use ApiPlatform\Core\Bridge\Symfony\Validator\Exception\ValidationException;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class CreateUser
{
private $validator;
public function __construct(ValidatorInterface $validator)
{
$this->validator = $validator;
}
public function __invoke(UserRegistration $data): UserRegistration
{
$errors = $this->validator->validate($data);
if (count($errors) > 0) {
throw new ValidationException($errors);
}
return $data;
}
}
You can also check how ApiPlatform does it by looking at the ValidateListener. It provides some additional features, e.g. for validation groups, which you don't seem to need at this point, but might be interesting later. ApiPlatform will then use its ValidationExceptionListener to react on the Exception you throw and render it appropriately.
So, I've made an app with its own controller and entities. I've just installed the FOSUserBundle following the guide and I want to connect my app to the login, register, etc. I tried using the function 'path'. This is what looks like... 'href="{{path(menu.url)}}' ok, so I get an error: 'such route does not exist'. I decided to create another controller, this one called FosController, next to my previous one the DefaultController, I extended the FosController from SecurityController(login fosuserbundle) and decided to create with annotation a route called login and connect it to the parent method...
class FosController extends SecurityController
{
/**
* #Route("/hospitallogin", name="login")
* #Method({"GET", "POST"})
*
* #param Request $request
*
* #return Response
*/
public function login2Action(Request $request) {
parent::loginAction($request);
}
}
And I get this error:
The controller must return a response (null given). Did you forget to add a return statement somewhere in your controller?
I dont know what else to do, thanks for your time.
I decided to use this though it's not correct...
public function indexAction()
{
return $this->redirect('http://localhost:8000/login');
}
I find it really handy that in Symfony I can use annotations to add extra functionality to my controller methods in a clean way. Like this for example:
/**
* #Route("/{id}")
* #IsGranted("view", subject="product")
* #return Response
*/
public function view(Product $product)
{
dump(compact('product'));
return new Response('It worked!');
}
However, for the create method, I don't have a product instance, so I'd like to use the #IsGranted annotation with as the subject the string "App\Entity\Post". I hoped I could do that like this:
/**
* #Route("/")
* #IsGranted("create", subject=Product::class)
* #return Response
*/
public function create()
{
return new Response('Did it work?');
}
But unfortunately I get the following error: Could not find the subject "App\Entity\Product" for the #IsGranted annotation. Try adding a "$App\Entity\Product" argument to your controller method.
So #IsGranted is still under the impression that it's supposed to look for a method parameter with the name $App\Entity\Product. Is there a way I can use it with just a string literal?
Can't you just omit the subject attribute?
I haven't used the annotation but I know that Symfony auth checker allows to call "isGranted" without a subject.
See example here: https://symfony.com/doc/current/security.html#securing-controllers-and-other-code
This is old post, but this is how you could have accomplished it:
/**
* #Route("/", defaults={"my_variable": "my_string"})
* #IsGranted("MY_VOTER", subject="my_variable")
*/
Another way:
class AnotherController extends AbstractDashboardController
{
public function index(): Response
{
$this->denyAccessUnlessGranted('MY_VOTER', 'my_variable');
//...
}
}
unlike the 'IsGranted' annotation, method 'denyAccessUnlessGranted' takes string easier ;)
I have a controller which handles a GET request. I need to set requirement parameters for GET request, e.g.: 'http://localhost/site/main?id=10&sort=asc
My controller class
class IndexController extends Controller {
` /**
* #Route
* (
* "/site/main",
* name="main"
* )
*
* #Method("GET")
*/
public function mainAction(Request $request)
{
return new Response('', 200);
}
}
How could I do that?
UPD: I need to set requirement for URL parameters like
id: "\d+",
sort: "\w+"
Etc.
The same as symfony allows to do with POST request.
You can specify the requirements in the "#Route" annotation like this:
class IndexController extends Controller {
` /**
* #Route
* (
* "/site/main",
* name="main",
* requirements={
* "id": "\d+",
* "sort": "\w+"
* })
* )
*
* #Method("GET")
*/
public function mainAction(Request $request)
{
return new Response('', 200);
}
}
#Method is what you need http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/routing.html#route-method
If you try to use this route with POST, you will have 404
I couldn't understand your question well.
However, if what you need is to set up a filter mechanism for the GET method parameters as it is already available for the URL using route requirements, I think there is no ready to use tools for this in the Route component, as commented #Yoshi.
I had to do this kind of work myself and used this. I hope it helps you too
public function indexAction(Request $request)
{
// Parameter names used in the current request
$current_request_params=array_keys($request->query->all());
// $ALLOWED_INDEX_PARAMS should be declared as Class static property array and hold names of the query parameters you want to allow for this method/action
$unallowed_request_params=array_diff($current_request_params,PersonController::$ALLOWED_INDEX_PARAMS);
if (!empty($unallowed_request_params))
{
$result=array("error"=>sprintf("Unknown parameters: %s. PLease check the API documentation for more details.",implode($unallowed_request_params,", ")));
$jsonRsp=$this->get("serializer")->serialize($result,"json");
return new Response($jsonRsp,Response::HTTP_BAD_REQUEST,array("Content-Type"=>"application/json"));
}
// We are sure all parameters are correct, process the query job ..
}