Pass request to exception - laravel

Since I started logging exceptions on a production site I'm noticing a lot of them, especially 404s, more than I would expect for a site with barely any traffic, and I'd like to get to the bottom of whether they're genuine users or just bots. To help with this, I want to capture the URL that the user was trying to visit before being redirected to the 404 route, so I can keep track of which non-existent routes are being mistakenly hit. I think I'm correct in assuming this URL should be available in the request, and that I just need to store the request and pass it through to the exception.
What's the best way to do this in Laravel 8 onwards?

Catch 404 exceptions in Handler(App\Exceptions\Handler).
If you see Rendering Exceptions
By default, the Laravel exception handler will convert exceptions into
an HTTP response for you. However, you are free to register a custom
rendering closure for exceptions of a given type. You may accomplish
this via the renderable method of your exception handler.
The closure passed to the renderable method should return an instance
of Illuminate\Http\Response, which may be generated via the response
helper. Laravel will deduce what type of exception the closure renders
by examining the type-hint of the closure:
so in the register method,call renderable
public function register()
{
$this->renderable(function (NotFoundHttpException $e, $request) {
Log::alert("404",[
"fullUrl"=>$request->fullUrl(),
"path"=>$request->path(),
"message" =>$e->getMessage()
]);
return response()->view('errors.404', [], $e->getStatusCode());
});
}
Also, don't forget to import and use
Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

EDIT: just a few hours of using this solution in production, with the benefit of the newly-added request URLs, has confirmed for me just how many of these HTTP 4xx errors are junk - mostly automated bots and maybe a few script kiddies trying common routes. For this reason I've added some logic to ignore 404 and 405 errors, and may still add others that mostly contribute noise to the logfile.
This was harder than it should have been, but this is the solution I'm currently using to log the request with all exceptions. It's probably not the cleanest way to do it, but it works perfectly for my needs. Thanks to John Lobo's answer for pointing me in the right direction.
It works by inspecting each instance of the Exception class and using PHP's instanceof to check whether it's a HTTP exception or not. If it is, it gets logged with the request URL and returns a view with a status code. If it's a generic non-HTTP exception, it gets logged with the request URL and returns another view with no status code (or you can keep the default exception behaviour by removing the return block, which renders a blank screen in production).
public function register()
{
$this->renderable(function (Exception $exception, $request) {
$url = $request->fullUrl();
if ($exception instanceof HttpException) {
$status = $exception->getStatusCode();
// Do not log HTTP 404 and 405s errors for reasons that will
// become apparent after a few hours of logging 404s and 405s
if(($status !== 404) && ($status !== 405)) {
Log::warning("Error $status occurred when trying to visit $url. Received the following message: " . $exception->getMessage());
}
return response()->view("errors.error", [
"exception" => $exception,
"status" => $status
],
$status
);
} else {
$status = $exception->getCode();
Log::warning("Exception $status occurred when trying to visit $url. Received the following message: " . $exception->getMessage());
return response()->view("errors.exception", [
"exception" => $exception,
"status" => $status
]);
}
});
// Optionally suppress all Laravel's default logging for exceptions, so only your own logs go to the logfile
$this->reportable(function (Exception $e) {
})->stop();
}

Related

Laravel redirect ALL types of errors to custom view

I'm looking for a way to catch all possible errors and redirect all types of errors to 1 page.
My current code in /Exceptions/Handler.php:
if ($this->isHttpException($exception)) {
$statusCode = $exception->getStatusCode();
switch ($statusCode) {
case '404':
return response()->view('layouts/404');
}
}
Problem is that ErrorException (E_NOTICE) types (which are caused by possible bugs in the code) aren't redirected to the 404 page. These errors end up on the 'Woops something went wrong' page.
I basically am trying to make every type of error end up on my custom error page.
All attempts end up on white pages.
What am I not seeing?
Well, to do what you're asking for you should go to App\Exceptions\Handler.php and there, you need to modify the render method:
public function render($request, Exception $exception)
{
abort(404);
}
But I insist, this is a terrible idea, you should catch the Exceptions that your app may get and show the error message to the user in a clean way, for example:
try{
//... your fancy code here
}catch(Exception $e){
// return with the message error $e->getMessage()
}

Laravel checking if POST !isset then redirect throwing an error

Currently have a POST route setup in Laravel
Route::post('/confirm', 'MainController#confirm');
'/confirm' makes use of some POST information sent over from the previous page.
What I am trying to accomplish is, if a user just types in 'index/confirm' into the url and not all of the POST info is present redirect to 'index'
I have the below code and cannot see why this would not work. I think it may be because inorder for the route to trigger at least some POST info needs to be present and if none is then error.
$input = $request->all();
if ( !isset($input) ) {
return redirect('');
}
Error message:
Symfony \ Component \ HttpKernel \ Exception \ MethodNotAllowedHttpException
No message
/Users/&name/Desktop/$filepath/vendor/laravel/framework/src/Illuminate/Routing/RouteCollection.php
protected function methodNotAllowed(array $others)
{
throw new MethodNotAllowedHttpException($others);
}
Commenters answered my question. Needed a seperate route for GET to handle unallowed user submitted url requests.
Route::get('/confirm', 'MainController#index');
That's fine for guarding one endpoint, but there's a more sweeping solution you can apply. Look to your App\Exceptions\Handler class. The render() method is given an awareness of the failed request, and the specific error that was thrown. public function render($request, Exception $e) .... Add this:
if ($e instanceof Symfony\Component\HttpKernel\Exception\NotFoundHttpException) {
return redirect('/index');
}
Just be careful with this. If you typo your redirect URL, you'll end up in an infinite loop. I'd also recommend an additional condition that respects the value of config('app.env') for debugging purposes.

Design 500 error page only for production

I'm looking for a modern (Laravel 5.4) way to display custom 500 error page only for HTTP (non ajax/fetch) response. I read some threads but each response looks like a trick or is outdated. There is probably something to modify in \App\Exceptions\Handler, but I did not find the "right way".
Is there a simple way to display a specific page on fatal error (uncatched, returning 500) in Laravel 5.4?
In other words, when I have a syntax error on one of my controller, it displays "Whoops something went wrong" with some HTML and 500 error code. I would like to display something else, with the same rules as default behavior (ideally only for HTML browser, not for ajax/fetch, etc.).
EDIT: only in production environment.
Laravel makes it easy to display custom error pages for various HTTP status codes. For example, if you wish to customize the error page for 404 HTTP status codes, create a resources/views/errors/404.blade.php. This file will be served on all 404 errors generated by your application. The views within this directory should be named to match the HTTP status code they correspond to. The HttpException instance raised by the abort function will be passed to the view as an $exception variable.
https://laravel.com/docs/5.4/errors#custom-http-error-pages
From the selected "best answer" of this thread: https://laracasts.com/discuss/channels/general-discussion/custom-error-page-er500
You could modify \App\Exceptions\Handler::render():
public function render($request, Exception $exception)
{
if (config('app.debug') && !$this->isHttpException($exception)) {
$exception = new \Symfony\Component\HttpKernel\Exception\HttpException(500);
}
return parent::render($request, $exception);
}
Your exception will be reported in the logs as usually, but woops page will be replaced by your 500.blade.php view.
Sometimes you have to catch the specific exception in order to render the error view. in Laravel 5.4 you can do this by editing the report() method in the App\Exceptions\Handler class
public function report(Exception $exception)
{
if ($exception instanceof CustomException) {
// here you can log the error and return the view, redirect, etc...
}
return parent::report($exception);
}

Laravel 5.3 Passing AuthorizationException a message when a Policy fails

I'm trying to find a clean way to override the AuthorizationException to take a dynamic string that can be passed back when a Policy fails.
Things I know I can do are:
Wrap the Policy in the Controller with a try-catch, then rethrow a custom exception that takes a specific string, which seems a bit verbose
abort(403, '...') in the Policy prior to returning, which seems a bit hacky since policies are already doing the work
and then in /Exceptions/Handler::render I can send back the response as JSON
Is there a nicer way to do this to get a message in the response of a policy failure? Or is 1 or 2 my best choices.
I noticed if you throw AuthorizationException($message) in a policy using Laravel's exception it jumps you out of the policy, but continues execution in the controller, and doesn't progress to Handler::render. Which I'm assuming this is them handling the exception somehow, but I couldn't find where they were doing it... so if anyone finds where this is happening I'd still like to know.
If you create your own AuthorizationException and throw it, it will stop execution as expected, and drop into Handler::render so I ended up adding this method to my policy:
use App\Exceptions\AuthorizationException;
// ... removed for brevity
private function throwExceptionIfNotPermitted(bool $hasPermission = false, bool $allowExceptions = false, $exceptionMessage = null): bool
{
// Only throw when a message is provided, or use the default
// behaviour provided by policies
if (!$hasPermission && $allowExceptions && !is_null($exceptionMessage)) {
throw new \App\Exceptions\AuthorizationException($exceptionMessage);
}
return $hasPermission;
}
New exception for throwing in policies only in \App\Exceptions:
namespace App\Exceptions;
use Exception;
/**
* The AuthorizationException class is used by policies where authorization has
* failed, and a message is required to indicate the type of failure.
* ---
* NOTE: For consistency and clarity with the framework the exception was named
* for the similarly named exception provided by Laravel that does not stop
* execution when thrown in a policy due to internal handling of the
* exception.
*/
class AuthorizationException extends Exception
{
private $statusCode = 403;
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct($message, $code, $previous);
}
public function getStatusCode()
{
return $this->statusCode;
}
}
Handle the exception and provide the message in a JSON response in Handler::render():
public function render($request, Exception $exception)
{
if ($exception instanceof AuthorizationException && $request->expectsJson()) {
return response()->json([
'message' => $exception->getMessage()
], $exception->getStatusCode());
}
return parent::render($request, $exception);
}
and I also removed it from being logged in Handler::report.
What I found was not "passing" a custom message to authorize, just defining a custom message in the policy it selfs, so, for example, if you have the method "canUseIt", in your UserPolicy, like the following:
public function canUseIt(User $user, MachineGun $machineGun)
{
if ($user->isChuckNorris()) {
return true;
}
return false;
}
You can change it and do something like this:
public function canUseIt(User $user, MachineGun $machineGun)
{
if ($user->isChuckNorris()) {
return true;
}
$this->deny('Sorry man, you are not Chuck Norris');
}
It uses the deny() method from the HandlesAuthorization trait.
Then when you use it like $this->authorize('canUseIt', $user) and it fails, it will return a 403 HTTP error code with the message "Sorry man, you are not Chuck Norris".
Laravel does have an option to pass arguments to customize the errors in the authorize() method of a Controller Class accessed through the Gate Class's implementation of the GateContract made available by the Gate Facade.
However, it seems that they forgot to pass those arguments to the allow()/deny() methods responsible for returning error messages, implemented in the HandlesAuthorization Trait.
You need to pass those arguments by following these steps:
Modify the authorize method in the vendor/laravel/framework/src/Illuminate/Auth/Access/Gate.php file
public function authorize($ability, $arguments = []) {
$result = $this->raw($ability, $arguments);
if ($result instanceof Response) {
return $result;
}
return $result ? $this->allow() : $this->deny($arguments);
}
Call authorize from the controller with an extra argument, ie: your custom $message -
$message = "You can not delete this comment!";
$response = $this->authorize('delete', $message);
I have made a pull request to fix this, hopefully someone will merge it soon.
I think the best way to think about Policies is they are simply a way to split controller logic, and move all authorization related logic to a separate file. Thus abort(403, 'message') is the right way to do this, in most cases.
The only downside is you may want some policies to be 'pure' logic for use in business logic only, and thus not to have any response control. They could be kept separate, and a commenting system could be used distinguish them.

How to send a response from a method that is not the controller method?

I've got a Controller.php whose show($id) method is hit by a route.
public function show($id)
{
// fetch a couple attributes from the request ...
$this->checkEverythingIsOk($attributes);
// ... return the requested resource.
return $response;
}
Now, in checkEverythingIsOk(), I perform some validation and authorization stuff. These checks are common to several routes within the same controller, so I'd like to extract these checks and call the method everytime I need to perform the same operations.
The problem is, I'm unable to send some responses from this method:
private function checkEverythingIsOk($attributes)
{
if (checkSomething()) {
return response()->json('Something went wrong'); // this does not work - it will return, but the response won't be sent.
}
// more checks...
return response()->callAResponseMacro('Something else went wrong'); // does not work either.
dd($attributes); // this works.
abort(422); // this works too.
}
Note: Yes, I know in general one can use middleware or validation services to perform the checks before the request hits the controller, but I don't want to. I need to do it this way.
As of Laravel 5.6 you can now use for example response()->json([1])->send();.
There is no need for it to be the return value of a controller method.
Note that calling send() will not terminate the output. You may want to call exit; manually after send().
You are probably looking for this:
function checkEverythingIsOk() {
if (checkSomething()) {
return Response::json('Something went wrong');
}
if(checkSomethingElse()) {
return Response::someMacro('Something else is wrong')
}
return null; // all is fine
}
And in the controller method:
$response = $this->checkEverythingIsOk();
if($response !== null) { // $response instanceof Response
return $response;
}
It's probably overkill, but I will throw it in anyway. You might want to look into internal requests. Also this is just pseudoish code, I have not actually done this, so take this bit of information with caution.
// build a new request
$returnEarly = Request::create('/returnearly');
// dispatch the new request
app()->handle($newRequest);
// have a route set up to catch those
Route::get('/returnearly', ...);
Now you can have a Controller sitting at the end of that route and interpret the parameters, or you use multiple routes answered by multiple Controllers/Methods ... up to you, but the approach stays the same.
UPDATE
Ok I just tried that myself, creating a new request and dispatching that, it works this way. Problem is, the execution does not stop after the child-request has exited. It goes on in the parent request. Which makes this whole approach kind of useless.
But I was thinking about another way, why not throw an Exception and catch it in an appropriate place to return a specified response?
Turns out, thats already built into Laravel:
// create intended Response
$response = Response::create(''); // or use the response() helper
// throw it, it is a Illuminate\Http\Exception\HttpResponseException
$response->throwResponse();
Now usually an Exception would be logged and you if you are in Debug mode, you would see it on screen etc. etc. But if you take a look into \Illuminate\Foundation\Exceptions\Handler within the render method you can see that it inspects the thrown Exception if it is an instance of HttpResponseException. If it is then the Response will be returned immediately.
To me the most simple and elegant way is:
response()->json($messages_array, $status_code)->throwResponse();
(you don`t need return)
It can be called from a private function or another class...
I use this in a helper class to check for permissions, and if the user doesn`t have it I throw with the above code.

Resources