如何在 Zend Framework 2 中为变量值构建 ACL 断言


How to build an ACL Assertion for a variable value in Zend Framework 2?

我在这样的acl.global.php中配置了一个简单的ACL:

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。docu 中的示例没有这个问题,因为它针对$_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_iditem.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;
    }
}
我不知道

你是否可以应用我的解决方案,因为我在包装Zend''Permission''Acl的AclService类中配置了我的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;
    }
}

如果您不能直接应用此解决方案,我希望它能为您指明正确的方向。