Как построить утверждение ACL для значения переменной в Zend Framework 2?

У меня есть простые настройки 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а) быть введенным в утверждение?

3

Решение

Наконец, я решил проблему, введя переменные данные через 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;
}
}
2

Другие решения

Я не знаю, можете ли вы применить мое решение, потому что я настраиваю свой 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;
}

}

Если вы не можете применить это решение напрямую, я надеюсь, что оно может указать вам правильное направление.

0

По вопросам рекламы [email protected]