我在这样的acl.global.php
中配置了一个简单的ACL:
return [
'acl' => [
'roles' => [
'guest' => null,
'member' => 'guest',
'admin' => 'member'
],
'resources' => [
'allow' => [
'ApplicationControllerIndex' => ['all' => 'member'],
'ApplicationControllerError' => ['all' => 'member'],
'ItemControllerProcess' => [
'index' => 'member',
'create' => 'member',
'showItem' => 'member', // website.tld/item/:id
'showList' => 'member' // website.tld/list-items
]
]
],
]
];
解析器遍历配置并从数组元素生成对ZendPermissionsAcl#allow(...)
的调用,如$this->allow($role, $controller, $action);
。
现在,我还需要限制用户对项目的单一视图(mydomain.tld/item/:id
(的访问。仅当用户id
等于item.user_id
时,用户才应获得访问权限(表示:用户是作者/所有者(。
我看到实现此要求的方法是扩展配置
'ItemControllerProcess' => [
'index' => 'member',
'create' => 'member',
'showItem' => [
'role' => 'member',
'assertion' => 'UserIsOwner'
]
'showList' => 'member'
]
并将Assertion
注入ZendPermissionsAcl#allow(...)
:$this->allow($role, $controller, $action, $assertion);
.
namespace AuthorizationAclAssertion;
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_id
或item.id
(可以/应该在什么位置注入断言?
我通过resource
注入变量数据解决了这个问题。不要认为这是最干净或推荐的解决方案。无论如何它有效。但很高兴知道,如何以干净/更优雅的方式解决它。
UserIsOwner
namespace AuthorizationAclAssertion;
use ZendPermissionsAclAssertionAssertionInterface;
use ZendPermissionsAclAcl;
use ZendPermissionsAclRoleRoleInterface;
use ZendPermissionsAclResourceResourceInterface;
use ItemServiceItemService;
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 AuthorizationAclAssertionFactory;
use ZendServiceManagerFactoryInterface;
use ZendServiceManagerServiceLocatorInterface;
use AuthorizationAclAssertionUserIsOwner;
class UserIsOwnerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$itemFieldsetService = $serviceLocator->get('ItemServiceItemService');
$authenticationService = $serviceLocator->get('AuthenticationService');
$userId = !empty($authenticationService->getIdentity()['id']) ? $authenticationService->getIdentity()['id'] : null;
$service = new UserIsOwner($userId, $itemFieldsetService);
return $service;
}
}
ParametrizedResource
namespace AuthorizationAclResource;
use ZendPermissionsAclResourceGenericResource;
use ZendMvcRouterHttpRouteMatch;
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 AuthorizationAclFactory;
use ZendServiceManagerFactoryInterface;
use ZendServiceManagerServiceLocatorInterface;
use AuthorizationAclAcl;
class AclFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$config = $serviceLocator->get('Config');
$assertions = [
'UserIsOwner' => $serviceLocator->get('AssertionUserIsOwner')
];
$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 UserService;
use ZendPermissionsAclRoleGenericRole as Role;
use ZendPermissionsAclResourceGenericResource as Resource;
use ZendPermissionsAclAcl;
use UserServiceAssertionRightLeagueAssertion;
use UserServiceAssertionRightLeagueTeamAssertion;
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]);
//ZendDebugDebug::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 UserEntityUser */
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'];
ZendDebugDebug::dump($idTeam);
return false;
}
}
如果您不能直接应用此解决方案,我希望它能为您指明正确的方向。