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

北慕城南 提交于 2019-12-10 18:03:37

问题


I have a simple ACL configures in an acl.global.php like this:

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
                ]
            ]
        ],
    ]
];

A parser iterates through the configuration and generates from the array elements calls to Zend\Permissions\Acl#allow(...) like $this->allow($role, $controller, $action);.

Now I need additionally to restrict the access of the users to the item's single view (mydomain.tld/item/:id). A user should only get the access, if its id equals to the item.user_id (means: the user is the author/owner).

The way I see to implement this requirement is to extend the config

'Item\Controller\Process' => [
    'index' => 'member',
    'create' => 'member',
    'showItem' => [
        'role' => 'member',
        'assertion' => 'UserIsOwner'
    ]
    'showList' => 'member'
]

and to inject the Assertion to 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 === ???;
    }
}

But now I have no idea, how the assertion should get the item.user_id injected. The example in the docu doesn't have this problem, since it assets against the $_SERVER['REMOTE_ADDR'].

I can inject the ItemService to find out the 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;
}

Though then I still need external data -- the current item.id.

At what place can/should the variable item's data (in this case the item.user_id or item.id) be injected to an assertion?


回答1:


Finally I resolved the problem by injecting the variable data via the resource. Don't think, that it's the cleanest or a recommended solution. Anyway it works. But it would be nice to know, how to resolve it a clean / more elegant way.

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:


I don't know if you can apply my solution because I configure my Acl in a AclService Class which wraps the Zend\Permission\Acl.

In this AclService I have defined a $assertions variable, which is an array which stores an object of every assertion that I have to use.

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;
    }
    }

}

When is time to check if somebody is allowed to use a resource, I inject the parameters of the route matched in the AclService which injects them in every assertion class previously instantiated (function '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);

    }

}

In this way, you can access these parameters in your assertion classes.

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;
    }

}

If you can not apply this solution directly, I hope it can point you to the right direction.



来源:https://stackoverflow.com/questions/37298894/how-to-build-an-acl-assertion-for-a-variable-value-in-zend-framework-2

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!