У меня есть простые настройки ACL в acl.global.php
как это:
return [
'acl' => [
'roles' => [
'guest' => null,
'member' => 'guest',
'admin' => 'member'
],
'resources' => [
'allow' => [
'Application\Controller\Index' => ['all' => 'member'],
'Application\Controller\Error' => ['all' => 'member'],
'Item\Controller\Process' => [
'index' => 'member',
'create' => 'member',
'showItem' => 'member', // website.tld/item/:id
'showList' => 'member' // website.tld/list-items
]
]
],
]
];
Парсер перебирает конфигурацию и генерирует из элементов массива обращения к Zend\Permissions\Acl#allow(...)
лайк $this->allow($role, $controller, $action);
,
Теперь мне нужно дополнительно ограничить доступ пользователей к единственному представлению элемента (mydomain.tld/item/:id
). Пользователь должен получить доступ, только если его id
равно item.user_id
(означает: пользователь является автором / владельцем).
Я вижу, как реализовать это требование, чтобы расширить конфигурацию
'Item\Controller\Process' => [
'index' => 'member',
'create' => 'member',
'showItem' => [
'role' => 'member',
'assertion' => 'UserIsOwner'
]
'showList' => 'member'
]
и ввести Assertion
в Zend\Permissions\Acl#allow(...)
: $this->allow($role, $controller, $action, $assertion);
,
namespace Authorization\Acl\Assertion;
use ...
class UserIsOwner implements AssertionInterface
{
protected $userId;
// To inject the $userId can be the job of the factory.
public function __construct(int $userId)
{
$this->userId = $userId;
}
public function assert(Acl $acl, RoleInterface $role = null, ResourceInterface $resource = null, $privilege = null)
{
return return $this->userId === ???;
}
}
Но теперь я понятия не имею, как утверждение должно получить item.user_id
впрыскивается. Пример в доку не имеет этой проблемы, так как это активы против $_SERVER['REMOTE_ADDR']
,
Я могу ввести ItemService
выяснить item.user_id
:
public function assert(Acl $acl, RoleInterface $role = null, ResourceInterface $resource = null, $privilege = null)
{
return $this->isUserOwner();
}
protected function isUserOwner()
{
$itemId = ???;
$item = $this->itemService->findOne($itemId);
$itemOwnerId = $item->getUser()->getId();
return $this->userId == $itemOwnerId;
}
Хотя тогда мне все еще нужны внешние данные — текущие item.id
,
В каком месте могут / должны быть данные переменного элемента (в этом случае item.user_id
или же item.id
а) быть введенным в утверждение?
Наконец, я решил проблему, введя переменные данные через resource
, Не думайте, что это самое чистое или рекомендуемое решение. Во всяком случае, это работает. Но было бы неплохо узнать, как решить это чистым / более элегантным способом.
UserIsOwner
namespace Authorization\Acl\Assertion;
use Zend\Permissions\Acl\Assertion\AssertionInterface;
use Zend\Permissions\Acl\Acl;
use Zend\Permissions\Acl\Role\RoleInterface;
use Zend\Permissions\Acl\Resource\ResourceInterface;
use Item\Service\ItemService;
class UserIsOwner implements AssertionInterface
{
/**
*
* @var integer
*/
protected $userId;
/**
*
* @var ItemService
*/
protected $itemService;
public function __construct(int $userId, ItemService $itemService)
{
$this->userId = $userId;
$this->itemService = $itemService;
}
public function assert(Acl $acl, RoleInterface $role = null, ResourceInterface $resource = null, $privilege = null)
{
return isset($resource->getParams()['id']) ? $this->isUserOwner($resource->getParams()['id']) : false;
}
protected function isUserOwner($itemId)
{
$item = $this->itemService->findOne($itemId);
$itemOwnerId = $item->getUser()->getId();
return $this->userId == $itemOwnerId;
}
}
UserIsOwnerFactory
namespace Authorization\Acl\Assertion\Factory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Authorization\Acl\Assertion\UserIsOwner;
class UserIsOwnerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$itemFieldsetService = $serviceLocator->get('Item\Service\ItemService');
$authenticationService = $serviceLocator->get('AuthenticationService');
$userId = !empty($authenticationService->getIdentity()['id']) ? $authenticationService->getIdentity()['id'] : null;
$service = new UserIsOwner($userId, $itemFieldsetService);
return $service;
}
}
ParametrizedResource
namespace Authorization\Acl\Resource;
use Zend\Permissions\Acl\Resource\GenericResource;
use Zend\Mvc\Router\Http\RouteMatch;
class ParametrizedResource extends GenericResource
{
/**
* @var array Params. Here the RouteMatch#params.
* @see RouteMatch
*/
protected $params;
public function __construct($resourceId, array $params = [])
{
parent::__construct($resourceId);
$this->setParams($params);
}
/**
*
* @return the $params
*/
public function getParams()
{
return $this->params;
}
/**
*
* @param multitype: $params
*/
public function setParams($params)
{
$this->params = $params;
}
}
Acl
...
// @todo refactor
protected function addResources(array $resources)
{
foreach ($resources as $permission => $controllers) {
foreach ($controllers as $controller => $actions) {
if ($controller == 'all') {
$controller = null;
} else {
if (! $this->hasResource($controller)) {
$this->addResource(new Resource($controller, $this->routeMatchParams));
}
}
foreach ($actions as $action => $roleConfig) {
if (is_array($roleConfig)) {
foreach ($roleConfig as $role => $assertion) {
if ($action == 'all') {
$action = null;
}
$assertion = !empty($this->assertions[$assertion]) ? $this->assertions[$assertion] : null;
if ($permission == 'allow') {
$this->allow($role, $controller, $action, $assertion);
} elseif ($permission == 'deny') {
$this->deny($role, $controller, $action, $assertion);
} else {
throw new \Exception('No valid permission defined: ' . $permission);
}
}
} elseif (is_string($roleConfig)) {
if ($action == 'all') {
$action = null;
}
if ($permission == 'allow') {
$this->allow($roleConfig, $controller, $action);
} elseif ($permission == 'deny') {
$this->deny($roleConfig, $controller, $action);
} else {
throw new \Exception('No valid permission defined: ' . $permission);
}
}
}
}
}
return $this;
}
...
AclFactory
namespace Authorization\Acl\Factory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Authorization\Acl\Acl;
class AclFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$config = $serviceLocator->get('Config');
$assertions = [
'UserIsOwner' => $serviceLocator->get('Assertion\UserIsOwner')
];
$routeMatch = $serviceLocator->get('Application')->getMvcEvent()->getRouteMatch();
$routeMatchParams = $routeMatch->getParams();
$service = new Acl($config, $assertions, $routeMatchParams);
return $service;
}
}
Я не знаю, можете ли вы применить мое решение, потому что я настраиваю свой Acl в классе AclService, который упаковывает Zend \ Permission \ Acl.
В этом AclService я определил переменную $ assertions, которая является массивом, в котором хранится объект каждого утверждения, которое я должен использовать.
namespace User\Service;
use Zend\Permissions\Acl\Role\GenericRole as Role;
use Zend\Permissions\Acl\Resource\GenericResource as Resource;
use Zend\Permissions\Acl\Acl;
use User\Service\Assertion\RightLeagueAssertion;
use User\Service\Assertion\RightLeagueTeamAssertion;
class AclService {
const ROLE_GUEST = 'guest';
const ROLE_MEMBER = 'member';
const ROLE_COMISSIONER = 'comissioner';
const ROLE_ADMIN = 'admin';
const ROLE_GOD = 'god';
const ASSERTION_RIGHT_LEAGUE_TEAM = 'RightLeagueTeamAssertion';
protected $acl = null;
protected $assertions;
/**
* Constructor
*
* @param Acl $acl
* @return void
* @throws \Exception
*/
public function __construct($acl)
{
$this->acl = $acl;
$this->assertions[self::ASSERTION_RIGHT_LEAGUE_TEAM] = $rightLeagueTeam;
/* Declaramos los roles */
$this->acl->addRole(new Role(self::ROLE_GUEST));
$this->acl->addRole(new Role(self::ROLE_MEMBER), self::ROLE_GUEST);
$this->acl->addRole(new Role(self::ROLE_COMISSIONER), self::ROLE_MEMBER);
$this->acl->addRole(new Role(self::ROLE_ADMIN), self::ROLE_MEMBER);
//unique role for superadmin
$this->acl->addRole(new Role(self::ROLE_GOD));
/* Declaramos los recursos (module:controller) */
$this->acl->addResource(new Resource('application:index'));
$this->acl->addResource(new Resource('application:error'));
$this->acl->addResource(new Resource('user:user'));
$this->acl->addResource(new Resource('leueroneyear:league'));
$this->acl->addResource(new Resource('leueroneyear:team'));
/*** Permisos ***/
//'God' tiene permiso para todo
$this->acl->allow(self::ROLE_GOD);
//Una persona no logueada podrá ver solo el índice, errores, darse de alta y recuperar el password
$this->acl->allow(self::ROLE_GUEST, 'application:index', 'index');
$this->acl->allow(self::ROLE_GUEST, 'user:user', array('register','forgotpassword','resetpassword','login'));
$this->acl->allow(self::ROLE_GUEST, 'application:error');
$this->acl->allow(self::ROLE_GUEST, 'nba:test');
//Los usuarios sí que podrán visitar las páginas
$this->acl->allow(self::ROLE_MEMBER, 'user:user', array('get','edit', 'logout'));
$this->acl->allow(self::ROLE_MEMBER, 'leueroneyear:league', array('index','get','list','add','enter'));
$this->acl->allow(self::ROLE_MEMBER, 'leueroneyear:team', array('get','add'));
$this->acl->allow(self::ROLE_MEMBER, 'leueroneyear:team', 'index',$this->assertions[self::ASSERTION_RIGHT_LEAGUE_TEAM]);
}
public function getAcl()
{
return $this->acl;
}
public function isAllowed($role, $controller, $action)
{
$a = explode("\\",$controller);
$resource = strtolower($a[0]).":".strtolower($a[2]);
//\Zend\Debug\Debug::dump($resource); die();
return $this->acl->isAllowed($role, $resource, $action);
}
public function setRequestParams($params)
{
$a = explode("\\",$params["controller"]);
$controller = strtolower($a[2]);
switch ($controller) {
case 'team': $this->assertions[self::ASSERTION_RIGHT_LEAGUE_TEAM]->setRequestParams($params);
break;
}
}
}
Когда пришло время проверить, разрешено ли кому-либо использовать ресурс, я внедряю параметры маршрута, соответствующие в AclService, который внедряет их в каждый ранее созданный класс утверждения (функция ‘setRequestParams’).
/**
* @param MvcEvent $e
*/
public function onRoute(MvcEvent $event)
{
$matches = $event->getRouteMatch();
$controller = $matches->getParam('controller');
$action = $matches->getParam('action','index');
$auth = $this->authService;
/* @var $user User\Entity\User */
if ($user = $auth->getIdentity()) {
$session = new Container("League");
if (isset($session->isCommissioner) && $session->isCommissioner)
$role = AclService::ROLE_COMISSIONER;
else
$role = AclService::ROLE_MEMBER;
} else {
$role = AclService::ROLE_GUEST;
}
$acl = $this->aclService;
$acl->setRequestParams($matches->getParams());
if (!$acl->isAllowed($role,$controller, $action)) {
//El usuario no tiene los permisos necesarios
$app = $event->getTarget();
$route = $event->getRouteMatch();
$event -> setError(RouteGuard::ERROR)
-> setParam('route', $route->getMatchedRouteName());
$app->getEventManager()->trigger('dispatch.error', $event);
}
}
Таким образом, вы можете получить доступ к этим параметрам в ваших классах утверждений.
class RightLeagueTeamAssertion implements AssertionInterface
{
protected $requestParams;
public function setRequestParams($params)
{
$this->requestParams = $params;
}/**
* Comprueba que el idTeam que pasan por parámetro pertenece a la liga en la que estás logueado
*/
public function assert(Acl $acl, RoleInterface $role = null, ResourceInterface $resource = null, $privilege = null) {
$appSession = new Container("Application");
$leagueSession = new Container("League");
$idLeague = $leagueSession->idLeague;
$idTeam = $this->requestParams['id'];
\Zend\Debug\Debug::dump($idTeam);
return false;
}
}
Если вы не можете применить это решение напрямую, я надеюсь, что оно может указать вам правильное направление.