Hello I have a problem with exception handler in Laravel 5.4.
I have in my controller a function that make a check for permission like:
function foo(){
try{
$this->authorize("bar", MyClass::class);
}catch(AuthorizationException $e){
}
}
In my handler.php I have this:
namespace App\Exceptions;
use Exception;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
protected $dontReport = [
\Illuminate\Auth\AuthenticationException::class,
\Symfony\Component\HttpKernel\Exception\HttpException::class,
\Illuminate\Database\Eloquent\ModelNotFoundException::class,
\Illuminate\Session\TokenMismatchException::class,
\Illuminate\Validation\ValidationException::class,
];
public function render($request, Exception $exception)
{
if ($exception instanceof AuthorizationException) {
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthorized.'], 403);
}
// TODO: Redirect to error page instead
}
return parent::render($request, $exception);
}
}
But the problem is that handler is never called.
But If I do:
function foo(){
try{
throw new AuthorizationException() //test for throw exception
$this->authorize("bar", MyClass::class);
}catch(AuthorizationException $e){
}
}
It will call handler.
So Can I handler AuthorizationException with $this->authorize ?
For Now I've resolve in this way:
I created a Custom class that extends AuthorizationException like:
namespace App\Exceptions;
namespace Illuminate\Auth\Access;
use Throwable;
class CustomAuthorizationException extends AuthorizationException
{
public function __construct($message = "", $code = 0, Throwable $previous = null)
{
throw new AuthorizationException();
}
}
And In Controller:
function foo(){
try{
$this->authorize("bar", MyClass::class);
}catch(CustomAuthorizationException $e){
}
}
And now it works, but I don't know if It is a good way.
The advices are well accepted!
Related
Here is a custom exception
namespace App\Exceptions;
use Exception;
class CustomException extends Exception
{
public function render($request)
{
return response()->view('custom-exception');
}
}
I throw it inside a Request class
class LoginRequest extends FormRequest
{
public function authenticate()
{
if (! Auth::attempt($this->only('email', 'password'))) {
throw CustomException(); //
}
}
}
This is the controller which call the LoginRequest class
class AuthenticatedSessionController extends Controller
{
public function store(LoginRequest $request) //
{
$request->authenticateMember();
$request->session()->regenerate();
return redirect()->intended(RouteServiceProvider::Home);
}
}
This is the test
use Tests\TestCase;
use App\Models\User;
use App\Exceptions\CustomException;
class EmailVerificationTest extends TestCase
{
public function test_email_verification_screen_can_be_rendered()
{
$user = User::factory()->create([
'email_verified_at' => null,
]);
// $this->expectException(CustomException::class); //this cannot pass
$response = $this->post(
'/login',
[
'email' => 'john#example.com',
'password' => 'secret'
]
);
$response->assertViewIs('custom-exception');
$this->assertInstanceOf(CustomException::class, $response->exception);
}
}
These assertions can pass:
$response->assertViewIs('custom-exception');
$this->assertInstanceOf(CustomException::class, $response->exception);
But this one cannot pass:
$this->expectException(CustomException::class);
Failed asserting that exception of type "App\Exceptions\CustomException" is thrown.
Why? Any idea?
The method expectException() will only work when the exception thrown is not handled.
Please add the below line in your function
$this->withoutExceptionHandling();
then this method expectException() will work.
Check if the request record exists in the method show() .If they do not exist call the method DislayNotFound.
Have any of you tried this method? Can you help me?
This is my file BaseController:
<?php
namespace App\Http\Controllers;
class BaseController extends Controller
{
public function display_not_found ()
{
return response(['message' => 'Not found'], 404);
}
}
This is my file FontController:
<?php
namespace App\Http\Controllers;
use App\Http\Resources\BaseCollection;
use App\Http\Resources\FontResource;
use App\Models\Design;
use App\Models\Font;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use App\Enums\CursorPaginate;
use App\Enums\ResponseMessages;
use App\Http\Resources\DesignResource;
use Illuminate\Support\Facades\Redis;
class FontController extends BaseController
{
public function show(Font $font)
{
if(!$font->exists()){
return $this->display_not_found();
}
if($font['user_id'] !== auth()->user()->id) {
return response(['message' => 'Forbidden'], 403);
}
return new FontResource($font);
}
}
Error:
If you would like to change the error response, you can override the Exception Hander. To do this, add the following to the register method in your your app/Exceptions/Handler.php file:
$this->renderable(function (NotFoundHttpException $e, $request) {
$previous = $e->getPrevious();
if (
$previous instanceof ModelNotFoundException &&
$previous->getModel() === Font::class &&
$request->expectsJson()
) {
return response()->json(['message' => 'Not found'], 404);
}
});
Don't forget to import the necessary classes are the top:
use App\Models\Font;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
Alternatively, you could remove route model binding for this method (the easiest way to do this would be to remove the Font type hint) and manually try to find the model.
I have a method named response in my controller. But it shows the following error
Fatal error: Cannot redeclare response()
HomeController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class HomeController extends Controller
{
function response($params, $salt) {
if (!is_array($params))
throw new Exception('response params is empty');
if (empty($salt))
throw new Exception('Salt is empty');
if (empty($params['status']))
throw new Exception('Status is empty');
$response = new Response($salt);
$result = $response->get_response($_POST);
unset($response);
return $result;
}
}
The response method is already defined in laravel base controller and can't be overridden. Its provided by the framework as a convenience to create a new response object.
If you want to change the base response functionnality, just extend the Response class
If you want something else, just use another name.
You can extend the response class...
use Illuminate\Support\Facades\Response;
class myResponse extends Response{
public function __construct()
{
// do something cool...
}
}
Or maybe...
use Illuminate\Support\Facades\Response as BaseResponse;
class Response extends BaseResponse{
public function __construct()
{
// do something cool...
}
}
Then you need to replace Laravels facade with your own in config/app.php.
'Response' => 'Path\Facades\Response',
in my ZF2 (2.4.5) project I have main (parent) controller with function to validate user rights so every inherited controller can easily acces it. However there's a problem with redirect. I know that inherited controller's action has to return response, but is it possible to force redirection in parent controller?
Parent controller
<?php
namespace Application;
use Zend\Mvc\Controller\AbstractActionController;
class CoreController extends AbstractActionController{
public function checkAccess($moduleName, $accessName){
if($this->getAclService()->isAllowed($moduleName, $accessName)){
self::redirect()->toRoute('access-denied');
}
}
}
Inherited controller
namespace Application\Controller;
use Application\CoreController;
use Zend\View\Model\ViewModel;
class InterfaceController extends CoreController{
public function indexAction(){
$this->checkAccess('Foo', 'Bar');
return new ViewModel([
]);
}
}
TL;DR If I call $this->checkAccess('Foo', 'Bar'); in InterfaceController and $this->getAclService()->isAllowed($moduleName, $accessName) in CoreController returns false I want to redirect user to route 'access-denied' immediately without completing rest of InterfaceController::indexAction
Important: I want to avoid checking what checkAccess returns, I just force redirection.
Thanks in advance for response.
Ok I did this using global exception handler
Child controller
<?php
namespace Warehouse\Controller;
use Application\CoreController;
use Zend\View\Model\ViewModel;
class IndexController extends CoreController {
public function getWarehouseDocumentAction() {
parent::checkAccess('Warehouse', 'incoming-goods');
return new ViewModel([
'foo' => 'bar',
]);
}
}
Parent controller
namespace Application;
use Application\Exception\InsufficientPermissionException;
use Zend\Mvc\Controller\AbstractActionController;
class CoreController extends AbstractActionController {
public function checkAccess($moduleName, $accessName){
if(!$this->getServiceLocator()->get(MyAcl::class)->isAllowed($moduleName, $accessName, $this->identity())){
throw new InsufficientPermissionException('Access denied. Insufficient permission.');
}
}
}
Module.php
<?php
namespace Application;
use Application\Exception\InsufficientPermissionException;
use Application\Monolog\Handler\DoctrineLogMessageHandler;
use Zend\Mvc\MvcEvent;
class Module {
public function onBootstrap(MvcEvent $e) {
$sharedEvents = $e->getApplication()->getEventManager()->getSharedManager();
$sharedEvents->attach('Zend\Mvc\Application', 'dispatch.error', function (MvcEvent $event) {
if (php_sapi_name() !== 'cli') {
$exception = $event->getParam('exception');
if ($exception instanceof InsufficientPermissionException) {
$target = $event->getTarget();
return $target->redirect()->toRoute('access-denied');
}
}
});
}
}
Permissions are held in database.
You are doing a simple 302 http redirect to "access-denied", so you could just render the response object thus far and stop php execution:
public function checkAccess($moduleName, $accessName){
if (!$this->getAclService()->isAllowed($moduleName, $accessName))) {
self::redirect()->toRoute('access-denied');
$this->getResponse()->send();
exit;
} else {
return true;
}
}
or you could simple throw an exception:
public function checkAccess($moduleName, $accessName){
if (!$this->getAclService()->isAllowed($moduleName, $accessName))) {
self::redirect()->toRoute('access-denied');
throw new \Exception('access denied');
} else {
return true;
}
}
the exception will prevent further code execution and the redirect will prevent the exception error page to be rendered.
I'm going out on a limb here by saying that is not possible. The calling code, InterfaceController::indexAction, needs to at least return which will start the redirect process.
You can clean it up a bit by letting the base controller set the redirect but the inherited controller needs to stop the script execution by calling return.
Base Controller
use Zend\Mvc\Controller\AbstractActionController;
class CoreController extends AbstractActionController{
public function checkAccess($moduleName, $accessName){
if (!$this->getAclService()->isAllowed($moduleName, $accessName))) {
self::redirect()->toRoute('access-denied');
return false;
} else {
return true;
}
}
}
Inherited controller
use Application\CoreController;
use Zend\View\Model\ViewModel;
class InterfaceController extends CoreController{
public function indexAction(){
if (!$this->checkAccess('Foo', 'Bar')) {
return;
}
return new ViewModel([
]);
}
}
EDIT
As a side note, in our company we do our ACL checks in the base controller's init() method with is not overridden and therefore can do the redirect instantly before any action code is run.
EDIT #2
I totally forgot about the init method which we use. Give this a go if you're looking for another solution.
Base controller
use Zend\Mvc\Controller\AbstractActionController;
class CoreController extends AbstractActionController{
public function init() {
// bootstrap code...
// This should redirect
if (!$this->_isUserAuthorized()) {
return;
}
// If the ACL check was OK then pass control to controllers
return parent::init();
}
private function _isUserAuthorized() {
// checks done here
// return true if OK
// else
$this->_response->setRedirect($this->view->defaultUrl($redirect))->sendResponse();
return false;
}
}
Inherited controller
use Application\CoreController;
use Zend\View\Model\ViewModel;
class InterfaceController extends CoreController{
public function indexAction(){
// nothing to do here
return new ViewModel([]);
}
}
I have this base controller:
abstract class ApiController extends BaseController {
use DispatchesCommands, ValidatesRequests;
public function __construct()
{
try {
$user = JWTAuth::parseToken()->toUser();
} catch (Exception $e) {
return Response::json(['error' => $e->getMessage()], HttpResponse::HTTP_UNAUTHORIZED);
}
}
}
How do I get the $user variable in child controllers? Please provide an example child controller if possible.
First of all:
abstract class ApiController extends BaseController {
use DispatchesCommands, ValidatesRequests;
protected $user; // or public
public function __construct()
{
try {
$this->user = JWTAuth::parseToken()->toUser();
} catch (Exception $e) {
return Response::json(['error' => $e->getMessage()], HttpResponse::HTTP_UNAUTHORIZED);
}
}
}
And then:
class Ctrl extends ApiController
{
public function whatever()
{
echo $this->user;
}
}
Last but not least: http://www.phpfreaks.com/tutorial/oo-php-part-1-oop-in-full-effect
Happy coding !