i'm throwing a exception in my controller when the user is not authorized to make any action, i created a custom exception class to do it. i'm using this package too: https://github.com/esbenp/heimdal to format my response in a better format.
however, when i set my status code in my exception to 401, my response is 500.
<?php
namespace App\Acl\Exceptions;
use Exception;
use Throwable;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
class UserUnauthorizedException extends Exception
{
public function __construct($message = "", $code = 0, \Throwable $previous = null)
{
parent::__construct('you are not authorized to perform this action', 401, $previous);
}
Instead of
class UserUnauthorizedException extends Exception
try:
class UserUnauthorizedException extends UnauthorizedHttpException
Note that 401 that you are using in the constructor is not a HTTP code. It's an arbitrary value that you can set for custom evaluation somewhere else.
Try below code:
<?php
namespace App\Acl\Exceptions;
use Exception;
use Throwable;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
class UserUnauthorizedException extends UnauthorizedHttpException
{
public function __construct($message = "", $code = 0, \Throwable $previous = null)
{
parent::__construct('you are not authorized to perform this action', 401, $previous);
}
This is caused by the library you use. You could create an issue or submit a PR yourself otherwise you might have to switch to an alternative.
For reference, this is where the response status code is set: https://github.com/esbenp/heimdal/blob/master/src/Formatters/ExceptionFormatter.php#L13
edit: Basically you have to either extend the correct HttpException that is already being handled by the HttpExceptionFormatter in your library or update the configuration specifying that your Exception should be handled by it. The config could look something like this:
return [
'formatters' => [
\App\Acl\Exceptions\UserUnauthorizedException::class => \Optimus\Heimdal\Formatters\HttpExceptionFormatter::class,
\Exception::class => \Optimus\Heimdal\Formatters\ExceptionFormatter::class,
]
];
This will use the HttpExceptionFormatter for your exception (and every class inheriting from it) whereas all other exceptions are handled by the generic ExceptionFormatter.
The default config can be found in the project's README: https://github.com/esbenp/heimdal#formatters
From this you can also gather that if you extend Symfony\Component\HttpKernel\Exception\HttpException the HttpExceptionFormatter should kick in and should use the status code that you provide.
Related
I have an endpoint I would like to post a bulk update request like this:
{
"resources": [
{
"id": 5,
"name": "ABC"
}
]
}
I am trying to prevent someone updating a resource they are not the owner of. I can create the following rule to prevent this:
'resources.*.id' => ['required', 'exists:resources,id,team_id,' . $this->team()->id],
I'd like to customize the error code so that I receive a 403 error code when this rule is violated. All other rules I am happy with the normal 422 error code.
I am aware I can customize the message in the messages() method. Is there something similar so I can return my own error code? At the moment I just get the standard 422 code.
I am also aware I could load all the team resources in the authorize() method, but I wonder if there is a better way?
Thanks.
Maybe I'm a bit late, but hopefully, my answer can still help you or someone in the future.
You can see the Form Request Validation Class you created extends from Illuminate\Foundation\Http\FormRequest. Then if you look at the source code on the Laravel Framework, there is a function to handle failed validation attempts.
So the solution, you can override the default failedValidation function in the Form Request Validation Class you created. Here is a sample code you can use:
// ./app/Http/Requests/UpdateFooBarRequest.php
...
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Response;
use Illuminate\Validation\ValidationException;
class UpdateFooBarRequest extends FormRequest
{
...
/**
* Handle a failed validation attempt.
*
* #param \Illuminate\Contracts\Validation\Validator $validator
* #return void
*
* #throws \Illuminate\Validation\ValidationException
*/
protected function failedValidation(Validator $validator)
{
throw (new ValidationException($validator))
->errorBag($this->errorBag)
->redirectTo($this->getRedirectUrl())
->status(Response::HTTP_FORBIDDEN);
}
}
Credit: Discussion from Laracasts - How to make a custom FormRequest error response in Laravel 5.5
If you are extending FormRequest, then you can add your custom messages in the messages function,
public function messages() {
$messages = [];
foreach ($this->get('resources') as $key => $val) {
$messages["resources.$key.id.exists"] = "The data :attribute"; // will send 'The data exists'
$messages["resources.$key.id.required"] = "The data is :attribute";
}
return $messages;
}
#Edit: 2nd Part of Question
If the authorize method returns false, an HTTP response with a 403 status code will automatically be returned and your controller method will not execute.
Source
public function authorize()
{
return false;
}
Then it will send 403 requests instead of 422.
I have a form request file handling my validations separate from my controller. How do i return validation errors after an api call within the controller?
//my controller
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function orders(GetOrdersRequest $request, OrderRepository $orderRepository)
{
$order = $orderRepository->allOrders($request->paginate);
return $this->sendSuccess('Orders retrieved successfully', $order);
}
In the FormRequest class there is a function called failedValidation:
protected function failedValidation(Validator $validator)
{
throw (new ValidationException($validator))
->errorBag($this->errorBag)
->redirectTo($this->getRedirectUrl());
}
It triggers when your validation fails. For API endpoints, this request is a bad response because it is a redirect and contains way too much information. To return a clean and
lightweight json respond, simply oerwrite the function failedValidation in your FormRequest for a matching response for your API. For example like this:
protected function failedValidation(Validator $validator)
{
$errors = $validator->errors();
$response = response()->json([
'message' => 'Invalid data send',
'details' => $errors->messages(),
], 422);
throw new HttpResponseException($response);
}
Credit to https://stackoverflow.com/a/56726819/2311074
Laravel request class returns back automatically when validation fails.
You should show your error messages in view(blade) file.
You can follow official documentation.
For API's it returns automatically a JSON response including error messages.
Basically you can do it in blade file:
#if($errors->has('email'))
<span class="error">{{ $errors->get('email') }}</span>
#endif
You will not be able to fetch the errors in that way since the FormRequest will throw an exception before the request reaches your controller in the event that there is a validation error. However, you can catch the error in the form request it self and modify the response there using the failedValidation method
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
class OrderRequest extends FormRequest
{
public function authorize()
{
//
}
public function rules()
{
//
}
public function failedValidation(Validator $validator)
{
$errors = $validator->errors(); // Here is your array of errors
throw new HttpResponseException($errors);
}
}
I think that's not possible, but you can use the prepareForValidation() method within you FormRequest and manipulate data before validate.
Even when using form request, Laravel handles validation for you automatically without you doing anything. On failed validation, an errors will be returned that you can access in your request response. response.data.errors (depending on the library you use for requests, of course) will contain the errors. For example:
errors: {
name: ["The name must be at least 2 characters"]
}
This is just a dummy example.
Do a quick test by forcing the request to fail and console log the response to see how it will show.
In Symfony, when a user attempts to access a route which is forbidden for that specific user (according to the user roles), a page with response code 403 will be returned.
So the user can still see that there is a valid route there.
I would like to overwrite this behavior by replacing the status code 403 with 404, so the user will just see that there is no valid route when she/he is not allowed to access that resource.
How can I accomplish that?
This is doable, however almost undocumented. I'm aware of two ways but there might be even more:
Using access_denied_url configuration option. See security config reference. With this option you can set URL where the user is redirected when the user in unauthorized (I think it should work also with route name). See a similar question: Symfony2 Redirection for unauthorised page with access_denied_url
There're also "Entry Points" as mentioned in The Firewall and Authorization. However, no examples, no explanation how to use it.
I looks like this option expects a service name as can be seen in security config reference (search for entry_point option).
One possible solution, as partially explained here, can be the following:
1) Defining a new service controller in services.yml
exception_controller:
class: Path\To\MyBundle\Controller\MyExceptionController
arguments: ['#twig', '%kernel.debug%']
2) Creating the new class which overrides Symfony\Bundle\TwigBundle\Controller\ExceptionController:
namespace Path\To\MyBundle\Controller;
use Symfony\Bundle\TwigBundle\Controller\ExceptionController;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class MyExceptionController extends ExceptionController
{
public function showAction(Request $request, FlattenException $exception, DebugLoggerInterface $logger = null)
{
$currentContent = $this->getAndCleanOutputBuffering($request->headers->get('X-Php-Ob-Level', -1));
$showException = $request->attributes->get('showException', $this->debug); // As opposed to an additional parameter, this maintains BC
$code = $exception->getStatusCode();
if ($code == 403) {
$code = 404;
// other customizations ...
}
return new Response($this->twig->render(
(string) $this->findTemplate($request, $request->getRequestFormat(), $code, $showException),
array(
'status_code' => $code,
'status_text' => isset(Response::$statusTexts[$code]) ? Response::$statusTexts[$code] : '',
'exception' => $exception,
'logger' => $logger,
'currentContent' => $currentContent,
)
));
}
}
3) Setting the following in config.yml under twig:
twig:
exception_controller: 'exception_controller:showAction'
Even though my original goal was to prevent such an exception to be thrown at all with that code.
Another solution can be overwriting the AccessListener service of the Symfony Security component.
The generic procedure about how to override a service of a bundle is documented here. The following is the concrete example about this particular situation.
First of all let's create the class which overrides the AccessListener class:
<?php
namespace Path\To\My\Bundle\Services;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Http\Firewall\AccessListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class OverrideAccessListener extends AccessListener
{
public function handle(GetResponseEvent $event)
{
try {
parent::handle($event);
} catch (AccessDeniedException $e) {
$request = $event->getRequest();
$message = sprintf('No route found for "%s %s"', $request->getMethod(), $request->getPathInfo());
if ($referer = $request->headers->get('referer')) {
$message .= sprintf(' (from "%s")', $referer);
}
throw new NotFoundHttpException($message);
}
}
}
then we need to create a Compiler Pass in order to change the class attribute of the original service with the new class:
<?php
namespace Path\To\My\Bundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class OverrideServiceCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition('security.access_listener');
$definition->setClass('Path\To\My\Bundle\Services\OverrideAccessListener');
}
}
finally we need to register the Compiler Pass in the build method of the bundle:
<?php
namespace Path\To\My\Bundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Path\To\My\Bundle\DependencyInjection\Compiler\OverrideServiceCompilerPass;
class MyBundleName extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new OverrideServiceCompilerPass());
}
}
Finally I found a simpler solution: using an access denied handler.
Unfortunately there is no much documentation about how to create an access denied handler, but it is very simple.
First create a class that implements the AccessDeniedHandlerInterface and set it as a service (for example naming it my_access_denied_handler_service).
In the handle method a Response should be created and returned (in my case I wanted a 404 response).
Then in the security.yml configuration file we have to set the access_denied_handler under the firewall:
...
firewalls:
my_firewall:
...
access_denied_handler: my_access_denied_handler_service
...
...
I have been developing a set of rest APIs to be exposed for mobile apps. I am following the repository pattern for the development on the Laravel project. How do I implement a presenter and transformer for formatting a constant JSON output throughout the set of all my APIs?
For example I have the following controller for login
public function authenticate()
{
$request = Request::all();
try {
// If authenticated, issue JWT token
//Showing a dummy response
return $token;
} catch (ValidatorException $e) {
return Response::json([
'error' =>true,
'message' =>$e->getMessageBag()
]);
}
}
Now where does a transformer and presenter come into the picture? I know that both are used to format the output by converting the db object and produce a formatted JSON so that it remains uniform across my APIs.
The dingo API and fractal or even the framework (L5 repository) don't provide detailed documentation and I can't find any tutorials on this.
I have created the following presenter and transformer for another API which gives the list of products
namespace App\Api\V1\Transformers;
use App\Entities\Product;
use League\Fractal\TransformerAbstract;
class UserTransformer extends TransformerAbstract {
public function transform(\Product $product)
{
return [
'id' => (int) $product->products_id
];
}
}
Presenter
<?php
namespace App\Api\V1\Presenters;
use App\Api\V1\Transformers\ProductTransformer;
use Prettus\Repository\Presenter\FractalPresenter;
/**
* Class ProductPresenter
*
* #package namespace App\Presenters;
*/
class ProductPresenter extends FractalPresenter
{
/**
* Transformer
*
* #return \League\Fractal\TransformerAbstract
*/
public function getTransformer()
{
return new UserTransformer();
}
}
How will I set the presenter in the controller and respond back? Tried
$this->repository->setPresenter("App\\Presenter\\PostPresenter");
But it doesn't seems to work and the doc doesn't shows the complete steps.
In the above example, how can I make a template for an error response which I can use throughout my APIs and how will I pass my error exceptions to it?
It seems like presenter and transformer can be used to convert database objects into presentable JSON and not anything else. Is that right?
How do you use a presenter and a transformer for a success response and an error response? By passing exceptions, instead of DB objects to the transformer?
I had the same exact problem and here is how I used dingo with transformer
Controller:
public function update(Request $request)
{
$bus = new CommandBus([
$this->commandHandlerMiddleware
]);
$agency = $bus->handle(
new UpdateAgencyCommand($request->user()->getId(), $request->route('id'), $request->only('name'))
);
return $this->response->item($agency, new AgencyTransformer());
}
Transformer:
class AgencyTransformer extends TransformerAbstract
{
public function transform(AgencyEntity $agencyEntity)
{
return [
'id' => (int) $agencyEntity->getId(),
'name' => $agencyEntity->getName(),
];
}
}
and this is how I handle errors:
throw new UpdateResourceFailedException('Could not update agency.', $this->agencyUpdateValidator->errors());
I just now see your similar question here as well. So see my answer on your other question here: https://stackoverflow.com/a/34430595/429719.
From the other question I derived you're using Dingo, so use that as a structured response class. Make sure you're controller extends from Dingo and then you can just return items and collections in a structured way like:
return $this->response->item($user, new UserTransformer);
return $this->response->collection($users, new UserTransformer);
If you want a nice error handling look for the docs here: https://github.com/dingo/api/wiki/Errors-And-Error-Responses
Basically you can throw any of the core exceptions or a few custom Dingo ones. The Dingo layer will catch them and returns a structured JSON response.
As per the Dingo docs:
throw new Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException('Nope, no entry today!');
Will generate:
{
"message": "Nope, no entry today!",
"status_code": 403
}
You can try use,[Midresapi]: https://github.com/oktorino/midresapi. this will return consistens success or failled response, work in laravel 7 & 8 , Handling validation response, handling 500 response:
$users=\App\User::latest()->limit(2)->get();
return response($users);
#or
return fractal()
->collection($users)
->transformWith(new \App\Transformers\UserTransformer)
->toArray();
Response :
{
"status_code": 200,
"success": true,
"message": "ok",
"data": [
{
"username": "dany",
"email": "jancuk#sabarbanget.com"
},
{
"username": "scc-client-5150",
"email": "dancuk#gmail.com"
}
]
}
Fractal is fully documented here: http://fractal.thephpleague.com/
There is an excellent book that I regularly read from the Phil Sturgeon https://leanpub.com/build-apis-you-wont-hate You can find most of the books code available in github https://github.com/philsturgeon/build-apis-you-wont-hate. You can find really nice examples of Fractal in there.
I would create an Api Controller and extend it from my Controllers.In there there should be all the respond functions (respondWithError, respondWithArray etc.)
Tranformers are transforming objects in a consistent json format so that all your endpoints return the same thing for each entity.
Dont really have an answer on this
There are enough examples in Fractal documentation.
I am writing a unit test in Laravel 5.0 and in my request class I am using a different bag to show the validation error messages.
I am using this in my file:
/* ExampleRequest.php */
namespace App\Http\Requests;
use App\Http\Requests\Request;
use Illuminate\Support\Facades\Auth;
class ExampleRequest extends Request {
protected $errorBag = 'otherbag';
public function rules(){
return [
'my_field' => 'required'
];
}
}
In my test file, I am testing using this:
/* ExampleTest.php */
class ExampleTest extends TestCase {
public function testPostWithoutData(){
$response = $this->call('POST', 'url/to/post',[
'my_field' => ''
]);
$this->assertSessionHasErrors('my_field');
}
}
If I run the tests, it can't get the right assert and return this problem:
Session missing error: my_field
Failed asserting that false is true.
If I take out the $errorBag attribute from the request file, I have no problems.
I can give more details as needed.
You can get an alternate bag from the session store like this:
$myBag = $this->app('session_store')->getBag('otherBag');
$this->assertTrue($myBag->any());
However, Laravel does not use an alternate bag by default, so I'm assuming you're doing something in your code to register your App\Request::$errorBag with the session handler.
I don't know if you are setting your session elsewhere but I guess you may do something like:
$this->session(['foo' => 'bar']);
Before you can assert something in session. See testing helpers section for Laravel 5.0