If I use Acl I don’t need authorization plugin and the policies in src/Policy directory?
the ACL plugin and the Authorization plugin clashes. I wrote a middleware to leverage the ACL plugin in the same way authorization would.
<?php
declare(strict_types=1);
namespace App\Middleware;
use Acl\AclInterface;
use App\Model\Entity\User;
use Authentication\AuthenticationServiceInterface;
use Cake\Controller\Component;
use Cake\Controller\ComponentRegistry;
use Cake\Core\App;
use Cake\Core\Configure;
use Cake\Http\Exception\InternalErrorException;
use Cake\Http\Exception\UnauthorizedException;
use Cake\ORM\TableRegistry;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class AclMiddleware implements MiddlewareInterface
{
/**
* Instance of an ACL class
*
* @var \Acl\AclInterface
*/
protected $_Instance = null;
/**
* Constructor. Will return an instance of the correct ACL class as defined in `Configure::read('Acl.classname')`
*
* @throws \Cake\Core\Exception\Exception when Acl.classname could not be loaded.
*/
public function __construct()
{
$className = Configure::read('Acl.classname');
if (!is_string($className) || !$className) {
throw new \RuntimeException('Config `Acl.classname` not configured');
}
$name = $className;
if (!class_exists($className)) {
$className = App::className('Acl.' . $name, 'Adapter');
if (!$className) {
throw new \RuntimeException(sprintf('Could not find ACL Adapter: %s.', $name));
}
}
$this->adapter($className);
}
/**
* Sets or gets the Adapter object currently in the AclComponent.
*
* `$this->Acl->adapter();` will get the current adapter class while
* `$this->Acl->adapter($obj);` will set the adapter class
*
* Will call the initialize method on the adapter if setting a new one.
*
* @param \Acl\AclInterface|string $adapter Instance of AclInterface or a string name of the class to use. (optional)
* @return \Acl\AclInterface either null, or the adapter implementation.
* @throws \Cake\Core\Exception\Exception when the given class is not an instance of AclInterface
*/
public function adapter($adapter = null)
{
if ($adapter) {
if (is_string($adapter)) {
$adapter = new $adapter();
}
if (!$adapter instanceof AclInterface) {
throw new \RuntimeException('AclComponent adapters must implement AclInterface');
}
$this->_Instance = $adapter;
$registry = new ComponentRegistry();
$component = new Component($registry);
$this->_Instance->initialize($component);
}
return $this->_Instance;
}
/**
* @param \Psr\Http\Message\ServerRequestInterface $request Requests
* @param \Psr\Http\Server\RequestHandlerInterface $handler siguiente middleware
* @return \Psr\Http\Message\ResponseInterface
* @throws \Cake\Http\Exception\UnauthorizedException Cuando el usuario no tiene permisos
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$auth = $request->getAttribute('authentication');
if (!($auth instanceof AuthenticationServiceInterface)) {
throw new \RuntimeException('AuthenticationService not found in request');
}
/** @var \Authentication\Authenticator\Result|null $result */
$result = $auth->getResult();
if (!$result || !$result->isValid()) {
return $handler->handle($request);
}
$user = $result->getData();
if (!$user instanceof User) { // my User entity
throw new \RuntimeException('No user found in session.');
}
$acoPath = $this->getPathFromRequest($request);
/** @var \Acl\Model\Table\AcosTable $Acos */
$Acos = TableRegistry::getTableLocator()->get('Acl.Acos');
/** @var \Cake\ORM\Query|null $query */
$query = $Acos->node($acoPath);
if (!$query) {
throw new InternalErrorException('Aco not found');
}
/** @var \Acl\Model\Entity\Aco|null $aco */
$aco = $query->first();
if (!$aco) {
throw new InternalErrorException('Aco not found');
}
/** @var string $acoId */
$acoId = $aco->get('id');
$can = $this->_Instance->check($user->aclNode(), $acoId, 'read');
if (!$can) {
throw new UnauthorizedException('You are not authorized to perform this action.');
}
return $handler->handle($request);
}
/**
* @param \Psr\Http\Message\ServerRequestInterface $request Request
* @return string ACO path
*/
protected function getPathFromRequest(ServerRequestInterface $request): string
{
/** @var array $params */
$params = $request->getAttribute('params');
$path = 'controllers/';
if ($params['plugin']) {
$path .= str_replace('/', '\\', $params['plugin']) . '/';
}
if (array_key_exists('prefix', $params) && $params['prefix']) {
$path .= $params['prefix'] . '/';
}
$path .= $params['controller'] . '/' . $params['action'];
return $path;
}
}
and in the App middleware function i would just add
$middlewareQueue
// ... some middlewares
->add(new AuthenticationMiddleware($this))
->add(new AclMiddleware()) // add this line after Authentication
// ... more middlewares
This middleware leverages authorization from the ACL controllers tree. I modified it even more for example handling public routes and such, but have it in spanish and linked with other internal developments.
The aclNode of the user function, I took it from this example