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.
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([]);
}
}
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.