Redirect if request authorization is failed in Laravel 5.5 - redirect

I am trying to redirect request if authorization is failed for it. I have following code:
class ValidateRequest extends Request{
public function authorize(){
// some logic here...
return false;
}
public function rules(){ /* ... */}
public function failedAuthorization() {
return redirect('safepage');
}
}
By default I am redirected to the 403 error page, but I would like to specify some specific route. I noticed that method failedAuthorization() is run, but redirect() method does not work...
Previously this code worked well with Laravel 5.1 but I used forbiddenResponse() method to redirect wrong request. How can I fix it with new LTS version?

Looks like it is impossible to redirect() directly from the custom ValidateRequest class. The only solution that I found is create custom exception and than handle it in the Handler class. So, now it works with following code:
Update: The method redirectTo() was updated to make solution work on Laravel 6.x and higher
app/Requests/ValidateRequest.php
class ValidateRequest extends Request{
public function authorize(){
// some logic here...
return false;
}
public function rules(){
return [];
}
public function failedAuthorization() {
$exception = new NotAuthorizedException('This action is unauthorized.', 403);
throw $exception->redirectTo("safepage");
}
}
app/Exceptions/NotAuthorizedException.php
<?php
namespace App\Exceptions;
use Exception;
class NotAuthorizedException extends Exception
{
protected $route;
public function redirectTo($route) {
$this->route = $route;
abort(Redirect::to($route));
}
public function route() {
return $this->route;
}
}
and app/Exceptions/Handler.php
...
public function render($request, Exception $exception){
...
if($exception instanceof NotAuthorizedException){
return redirect($exception->route());
}
...
}
So, it works, but much more slower than I expected... Simple measuring shows that handling and redirecting take 2.1 s, but with Laravel 5.1 the same action (and the same code) takes only 0.3 s
Adding NotAuthorizedException::class to the $dontReport property does not help at all...
Update
It runs much more faster with php 7.2, it takes 0.7 s

If you are revisiting this thread because in 2021 you are looking to redirect after failed authorization here's what you can do:
You cannot redirect from the failedAuthorization() method because it is expected to throw an exception (check the method in the base FormRequest class that you extend), the side effect of changing the return type is the $request hitting the controller instead of being handled on FormRequest authorization level.
You do not need to create a custom exception class, neither meddle with the Laravel core files like editing the render() of app/Exceptions/Handler.php, which will pick up the exception you threw and by default render the bland 403 page.
All you need to do is throw new HttpResponseException()
In the Laravel reference API we can see its job is to "Create a new HTTP response exception instance." and that is exactly what we want, right?
So we need to pass this Exception a $response. We can pass a redirect or JSON response!
Redirecting:
protected function failedAuthorization()
{
throw new HttpResponseException(response()->redirectToRoute('postOverview')
->with(['error' => 'This action is not authorized!']));
}
So we are creating a new instance of the HttpResponseException and we use the response() helper, which has this super helpful redirectToRoute('routeName') method, which we can further chain with the well known with() method and pass an error message to display on the front-end.
JSON:
Inspired by this topic
throw new HttpResponseException(response()->json(['error' => 'Unauthorized action!'], 403));
Thats'it.
You don't have to make ANY checks for validation or authorization in your controller, everything is done in the background before it hits the controller. You can test this by putting a dd('reached controller'); at the top of your controller method and it shouln't get trigered.
This way you keep your controller thin and separate concerns :)
Sidenote:
forbiddenResponse() has been replaced after lara 5.4 with failedAuthorization()

You can do through a middleware/policy i think. I don't know if you can do it from the validation.
You can override the function from FormRequest like this below:
/**
* Handle a failed authorization attempt.
*
* #return void
*
* #throws \Illuminate\Auth\Access\AuthorizationException
*/
protected function failedAuthorization()
{
throw new AuthorizationException('This action is unauthorized.');
}
And redirect where you want.

Related

Proper way to access the container from a class not in a slim controller

I have a regular php class outside of a controller, so it doesn't benefit from automatic injection of container. I need to access the response object from that class, and I guess I should get it from the container.
What's the proper way to access it ? Just pass it as argument so the outside class can use it ? Is there a better way ?
You need to use middleware for that because the response object is immutable so "changing" it will not update the response which will be used by slim.
$app->add(function($request, $response, $next) {
if($shouldRedirect === true) {
return $response->withRedirect('myurl'); // do not execute next middleware/route and redirect
}
return $next($request, $response); // execute next middleware/ the route
});
For more information about middleware have a look at this.
If you need to send a subrequest, Slim provides such functionality. Use it carefully though, as in some situations its result is not obvious.
<?php
class MySortOfOutsideClass
{
/**
* If you need to send a subrequest, you have to access application instance,
* so let's inject it here.
*/
public function __construct(\Slim\App $app)
{
$this->$app = $app;
}
/**
* Method that makes a subrequest, and returns the result of it.
*/
public function myMethod()
{
if ($subRequestIsRequired) {
return $this->app->subRequest('GET', '/hello');
}
}
}

Is splitting an index action into multiple ones a restful-friendly approach?

I need to display two different index pages to two different user groups. For example, a regular user should see one page, and a privileged user - another one. I see two ways of approaching this issue:
One index action with conditionals:
public function index()
{
// view for privileged users
if(request()->user()->hasRole('privileged')){
return view('index_privileged');
}
// view for regular users
if(request()->user()->hasRole('regular')){
return view('index_regular');
}
return redirect('/');
}
Multiple actions:
public function index_privileged()
{
return view('index_privileged');
}
public function index_regular()
{
return view('index_regular');
}
Which approach is more "restful-friendly" and generally better?
I'm a big fan of light controllers. This might be a little overboard for a simple problem but if something like this pops up again, you'd already have everything all setup.
With that said, it might be best to create a PrivilegedUser class and a RegularUser class and give them both an index method which returns their respective views. Code them both to an interface UserInterface and make sure they both implement that.
Here is what those looked like in my test.
class RegularUser implements UserInterface
{
public function index()
{
return view('index_regular');
}
}
class PrivilegedUser implements UserInterface
{
public function index()
{
return view('index_privileged');
}
}
interface UserInterface
{
public function index();
}
Then you can add a listener which should run for the event Illuminate\Auth\Events\Login. Laravel will fire this event for you automatically when someone logs in. This goes into the file EventServiceProvider.php.
protected $listen = [
'Illuminate\Auth\Events\Login' => [
'App\Listeners\AuthLoginListener',
],
];
Now you can run php artisan event:generate to generate the new listener. Here is what my listener looks like, it should work for you.
namespace App\Listeners;
use Illuminate\Auth\Events\Login;
use Illuminate\Foundation\Application;
class AuthLoginListener
{
/**
* Create the event listener.
*
* #param Application $app
*/
public function __construct(Application $app)
{
$this->app = $app;
}
/**
* Handle the event.
*
* #param Login $event
* #return void
*/
public function handle(Login $event)
{
if ($event->user->hasRole('privileged')) {
$this->app->bind('App\Repositories\UserInterface', 'App\Repositories\PrivilegedUser');
} else if ($event->user->hasRole('regular')) {
$this->app->bind('App\Repositories\UserInterface', 'App\Repositories\RegularUser');
}
}
}
Essentially what this is doing is telling Laravel to load up a certain class based on the type of user that just logged in. The User instance is available through the Login object which was automatically passed in by Laravel.
Now that everything is setup, we barely have to do anything in our controller and if you need to do more things that are different depending on the user, just add them to the RegularUser or PrivilegedUser class. If you get more types of users, simply write a new class for them that implements the interface, add an additional else if to your AuthLoginListener and you should be good to go.
To use this, in your controller, you'd do something like the following...
// Have Laravel make our user class
$userRepository = App::make('App\Repositories\UserInterface');
return $userRepository->index()->with('someData', $data);
Or even better, inject it as a dependency.
use App\Repositories\UserInterface;
class YourController extends Controller
{
public function index(UserInterface $user)
{
return $user->index();
}
}
Edit:
I just realized I forgot the part where you wanted to return redirect('/'); if no condition was met. You could create a new class GuestUser (I know this sounds like an oxymoron) which implements UserInterface but instead of using the AuthLoginListener, I'd bind it in a service provider when Laravel boots. This way Laravel will always have something to return when it needs an implementation of UserInterface in the event it needs this class if no one is logged in.
Well, its more like a refactoring "issue" than a rest-friendly issue. Check this guideline and you can see that most of the things that makes an api friendly is concerned to the url.
But, lets answer what you are asking. The thing you wanna do is a refactoring method but it is not only the move method but something like the extract variable.
The second option would make the code more readable, either ways are right but the second is more developer friendly. It enhances the code readability from any developer. I would recommend using the second option.
Refactoring is never enough, but read something like this, it will help you a lot writing more readable codes.

Choosing an appropriate Zend Framework exception class

Using Zend Framework, I want to throw an exception inside a particular method in my model class if there are arguments passed that are considered illegal for that method. In Java, for example, I would do something like this:
public void addName(String name) {
if (name.equals('')) {
throw new IllegalArgumentException();
}
// Other code if everything is ok.
}
However, as far as I can see, PHP and Zend Framework lack such basic built-in exception classes like IllegalArgumentException. So what should I use to properly pass an exception that actually describes what went wrong? Create such exception class myself? But isn't it this kind of code what a framework is supposed to eliminate?
I'm just starting learning Zend Framework. I haven't wrote a lot of PHP in my life so please feel free to explain me some things that you think should be obvious for a decent PHP programmer.
Here is the list of available exceptions in the PHP SPL exception class.
Exception
LogicException
BadFunctionCallException
BadMethodCallException
DomainException
InvalidArgumentException
LengthException
OutOfRangeException
RuntimeException
OutOfBoundsException
OverflowException
RangeException
UnderflowException
UnexpectedValueException
Zend Framework's Zend_Exception is just a wrapper for PHP's built in exceptions, however most of the major components have a callable exception class.
for example:
public function setId($id)
{
$validator = new My_Validator_Id();
if ($validator->isValid($id)) {
$this->id = $id;
return $this;
} else {
throw new Zend_Validate_Exception("$id is not a valid value for the ID field.");
}
}
or with PHP's built in excepition:
public function __get($name)
{
$property = strtolower($name);
if (!property_exists($this, $property)) {
throw new \InvalidArgumentException(
"Getting the property '$property' is not valid for this entity");
}
//truncated...
}
Zend Framework 2 has even more specific exceptions available.

ZF2 Use Redirect in outside of controller

I'm working on an ACL which is called in Module.php and attached to the bootstrap.
Obviously the ACL restricts access to certain areas of the site, which brings the need for redirects. However, when trying to use the controller plugin for redirects it doesn't work as the plugin appears to require a controller.
What's the best way to redirect outside from outside of a controller? The vanilla header() function is not suitable as I need to use defined routes.
Any help would be great!
Cheers-
In general, you want to short-circuit the dispatch process by returning a response. During route or dispatch you can return a response to stop the usual code flow stop and directly finish the result. In case of an ACL check it is very likely you want to return that response early and redirect to the user's login page.
You either construct the response in the controller or you check the plugin's return value and redirect when it's a response. Notice the second method is like how the PRG plugin works.
An example of the first method:
use Zend\Mvc\Controller\AbstractActionController;
class MyController extends AbstractActionController
{
public function fooAction()
{
if (!$this->aclAllowsAccess()) {
// Use redirect plugin to redirect
return $this->redirect('user/login');
}
// normal code flow
}
}
An example like the PRG plugin works:
use Zend\Mvc\Controller\AbstractActionController;
use Zend\Http\Response;
class MyController extends AbstractActionController
{
public function fooAction()
{
$result = $this->aclCheck();
if ($result instanceof Response) {
// Use return value to short-circuit
return $result
}
// normal code flow
}
}
The plugin could then look like this (in the second case):
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
class AclCheck extends AbstractPlugin
{
public function __invoke()
{
// Check the ACL
if (false === $result) {
$controller = $this->getController();
$redirector = $controller->getPluginManager()->get('Redirect');
$response = $redirector->toRoute('user/login');
return $response;
}
}
}
In your question you say:
[...] it doesn't work as the plugin appears to require a controller.
This can be a problem inside the controller plugin when you want to do $this->getController() in the plugin. You either must extend Zend\Mvc\Controller\Plugin\AbstractPlugin or implement Zend\Mvc\Controller\Plugin\PluginInterface to make sure your ACL plugin is injected with the controller.
If you do not want this, there is an alternative you directly return a response you create yourself. It is a bit less flexible and you create a response object while there is already a response object (causing possible conflicts with both responses), but the plugin code would change like this:
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
use Zend\Http\PhpEnvironment\Response;
class AclCheck extends AbstractPlugin
{
public function __invoke()
{
// Check the ACL
if (false === $result) {
$response = new Response;
$response->setStatusCode(302);
$response->getHeaders()
->addHeaderLine('Location', '/user/login');
return $response;
}
}
}

Zend PHPUnit testing a model, asserting a Zend_Exception indeed occurs as expected

Consider the following code in a model. the function deleteUser(NULL) will trigger an exception.
class Model_UserModel extends Zend_Db_Table_Abstract{
protected $_name = 'users';
protected $_primary = 'id';
public function deleteUser($id){
$row=$this->find($id)->current();
if($row){
$row->delete();
return true;
}else{
throw new Zend_Exception("Delete function failed; could not find row!");
}
}
}
I use PHPUnit to test this code and I want to check that the exception is indeed triggered when NULL is passed to the function deleteUser. The code in the testing class goes as follows:
class Application_Model_UserModelTest extends PHPUnit_Framework_TestCase{
...
//deleting a user with ID=NULL should fail
public function testDeleteNull(){
$e = null;
try{
$this->_users->deleteUser(NULL);
}catch (Exception $e) {}
if($e) {
$this->assertTrue(TRUE);
}else{
$this->assertTrue(FALSE);;
}
}
While this seems to work, I am wondering if there is a better way to do this. I have reviewed questions:
PHPUnit assert that an exception was thrown?
Problem testing exceptions with PHPUnit and Zend Framework
But I did not fully understand them / see how that applies in this case (testing a model, not a controller).
Any better way to test the exception is thrown?
Any advice will be much appreciated.
A problem with the way you do it is that it accepts any exception since all exceptions inherit from Exception. So you may miss a bug because the exception thrown was not the exception you expected.
Use annotations.
/**
* #expectedException Zend_Exception
*/
public function testDeleteNull(){
$this->_users->deleteUser(NULL);
}
You might want to create custom exception class to be more accurate though. Some people will argue that using exceptedException you can't assert the exception message and they are right. Simply there isn't universal and 'correct' solution for it.