Continue work on ACL rewritting

* fix center resolver dispatcher
* add scope resolver
* tests for authorization helper
This commit is contained in:
2021-09-19 20:56:20 +02:00
parent 74598ee926
commit b6c58a5c31
18 changed files with 498 additions and 29 deletions

View File

@@ -24,6 +24,9 @@ use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\HasCenterInterface;
use Chill\MainBundle\Entity\HasScopeInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher;
use Chill\MainBundle\Security\Resolver\ScopeResolverInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
use Symfony\Component\Security\Core\Role\Role;
@@ -56,16 +59,24 @@ class AuthorizationHelper
protected CenterResolverDispatcher $centerResolverDispatcher;
protected ScopeResolverDispatcher $scopeResolverDispatcher;
protected LoggerInterface $logger;
public function __construct(
RoleHierarchyInterface $roleHierarchy,
ParameterBagInterface $parameterBag,
EntityManagerInterface $em,
CenterResolverDispatcher $centerResolverDispatcher
CenterResolverDispatcher $centerResolverDispatcher,
LoggerInterface $logger,
ScopeResolverDispatcher $scopeResolverDispatcher
) {
$this->roleHierarchy = $roleHierarchy;
$this->hierarchy = $parameterBag->get('security.role_hierarchy.roles');
$this->em = $em;
$this->centerResolverDispatcher = $centerResolverDispatcher;
$this->logger = $logger;
$this->scopeResolverDispatcher = $scopeResolverDispatcher;
}
/**
@@ -114,11 +125,32 @@ class AuthorizationHelper
*/
public function userHasAccess(User $user, $entity, $attribute)
{
if (NULL === $center = $this->centerResolverDispatcher->resolveCenter($entity)) {
return false;
}
$center = $this->centerResolverDispatcher->resolveCenter($entity);
if (is_iterable($center)) {
foreach ($center as $c) {
if ($this->userHasAccessForCenter($user, $c, $entity, $attribute)) {
return true;
}
}
return false;
} elseif ($center instanceof Center) {
return $this->userHasAccessForCenter($user, $center, $entity, $attribute);
} elseif (NULL === $center) {
return false;
} else {
throw new \UnexpectedValueException("could not resolver a center");
}
}
private function userHasAccessForCenter(User $user, Center $center, $entity, $attribute): bool
{
if (!$this->userCanReachCenter($user, $center)) {
$this->logger->debug("user cannot reach center of entity", [
'center_name' => $center->getName(),
'user' => $user->getUsername()
]);
return false;
}
@@ -132,24 +164,35 @@ class AuthorizationHelper
if ($this->isRoleReached($attribute, $roleScope->getRole())) {
//if yes, we have a right on something...
// perform check on scope if necessary
if ($entity instanceof HasScopeInterface) {
$scope = $entity->getScope();
if ($scope === NULL) {
return true;
}
if ($scope->getId() === $roleScope
->getScope()->getId()) {
return true;
}
if ($this->scopeResolverDispatcher->isConcerned($entity)) {
$scope = $this->scopeResolverDispatcher->resolveScope($entity);
if (NULL === $scope) {
return true;
} elseif (is_iterable($scope)) {
foreach ($scope as $s) {
if ($s === $roleScope->getScope()) {
return true;
}
}
} else {
if ($scope === $roleScope->getScope()) {
return true;
}
}
} else {
return true;
}
}
}
}
}
$this->logger->debug("user can reach center entity, but not role", [
'username' => $user->getUsername(),
'center' => $center->getName()
]);
return false;
}

View File

@@ -9,8 +9,6 @@ class CenterResolverDispatcher
*/
private iterable $resolvers = [];
private CenterResolverInterface $defaultResolver;
public function __construct(iterable $resolvers)
{
$this->resolvers = $resolvers;

View File

@@ -4,12 +4,13 @@ namespace Chill\MainBundle\Security\Resolver;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\HasCenterInterface;
use Chill\MainBundle\Entity\HasCentersInterface;
class DefaultCenterResolver implements CenterResolverInterface
{
public function supports($entity, ?array $options = []): bool
{
return $entity instanceof HasCenterInterface;
return $entity instanceof HasCenterInterface || $entity instanceof HasCentersInterface;
}
/**
@@ -20,7 +21,13 @@ class DefaultCenterResolver implements CenterResolverInterface
*/
public function resolveCenter($entity, ?array $options = [])
{
return $entity->getCenter();
if ($entity instanceof HasCenterInterface) {
return $entity->getCenter();
} elseif ($entity instanceof HasCentersInterface) {
return $entity->getCenters();
} else {
throw new \UnexpectedValueException("should be an instanceof");
}
}
public static function getDefaultPriority(): int

View File

@@ -0,0 +1,42 @@
<?php
namespace Chill\MainBundle\Security\Resolver;
use Chill\MainBundle\Entity\HasScopeInterface;
use Chill\MainBundle\Entity\HasScopesInterface;
class DefaultScopeResolver implements ScopeResolverInterface
{
public function supports($entity, ?array $options = []): bool
{
return $entity instanceof HasScopeInterface || $entity instanceof HasScopesInterface;
}
/**
* @inheritDoc
*
* @param HasScopeInterface|HasScopesInterface $entity
*/
public function resolveScope($entity, ?array $options = [])
{
if ($entity instanceof HasScopeInterface) {
return $entity->getScope();
} elseif ($entity instanceof HasScopesInterface) {
return $entity->getScopes();
} else {
throw new \UnexpectedValueException("should be an instanceof %s or %s",
HasScopesInterface::class, HasScopeInterface::class);
}
}
public function isConcerned($entity, ?array $options = []): bool
{
return $entity instanceof HasScopeInterface || $entity instanceof HasScopesInterface;
}
public static function getDefaultPriority(): int
{
return -256;
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Chill\MainBundle\Security\Resolver;
use Chill\MainBundle\Entity\Scope;
final class ScopeResolverDispatcher
{
/**
* @var iterable|ScopeResolverInterface[]
*/
private iterable $resolvers;
public function __construct(iterable $resolvers)
{
$this->resolvers = $resolvers;
}
/**
* @param $entity
* @return Scope|Scope[]|iterable
*/
public function resolveScope($entity, ?array $options = [])
{
foreach ($this->resolvers as $resolver) {
if ($resolver->supports($entity, $options)) {
return $resolver->resolveScope($entity, $options);
}
}
return null;
}
public function isConcerned($entity, ?array $options = []): bool
{
foreach ($this->resolvers as $resolver) {
if ($resolver->supports($entity, $options)) {
return $resolver->isConcerned($entity, $options);
}
}
return false;
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Chill\MainBundle\Security\Resolver;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Scope;
interface ScopeResolverInterface
{
public function supports($entity, ?array $options = []): bool;
/**
* @param $entity
* @param array|null $options
* @return Scope|array|Scope[]
*/
public function resolveScope($entity, ?array $options = []);
/**
* Return true if the entity is concerned by scope, false otherwise.
*
* @param $entity
* @param array|null $options
* @return bool
*/
public function isConcerned($entity, ?array $options = []): bool;
public static function getDefaultPriority(): int;
}