* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Sonata\AdminBundle\Security\Handler; use Sonata\AdminBundle\Admin\AdminInterface; use Symfony\Component\Security\Acl\Domain\ObjectIdentity; use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity; use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity; use Symfony\Component\Security\Acl\Exception\AclNotFoundException; use Symfony\Component\Security\Acl\Exception\NotAllAclsFoundException; use Symfony\Component\Security\Acl\Model\AclInterface; use Symfony\Component\Security\Acl\Model\MutableAclProviderInterface; use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; use Symfony\Component\Security\Core\SecurityContextInterface; /** * @author Thomas Rabaix */ class AclSecurityHandler implements AclSecurityHandlerInterface { /** * @var TokenStorageInterface|SecurityContextInterface */ protected $tokenStorage; /** * @var AuthorizationCheckerInterface|SecurityContextInterface */ protected $authorizationChecker; /** * @var MutableAclProviderInterface */ protected $aclProvider; /** * @var array */ protected $superAdminRoles; /** * @var array */ protected $adminPermissions; /** * @var array */ protected $objectPermissions; /** * @var string */ protected $maskBuilderClass; /** * NEXT_MAJOR: Go back to signature class check when bumping requirements to SF 2.6+. * * @param TokenStorageInterface|SecurityContextInterface $tokenStorage * @param TokenStorageInterface|SecurityContextInterface $authorizationChecker * @param MutableAclProviderInterface $aclProvider * @param string $maskBuilderClass * @param array $superAdminRoles */ public function __construct($tokenStorage, $authorizationChecker, MutableAclProviderInterface $aclProvider, $maskBuilderClass, array $superAdminRoles) { if (!$tokenStorage instanceof TokenStorageInterface && !$tokenStorage instanceof SecurityContextInterface) { throw new \InvalidArgumentException('Argument 1 should be an instance of Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface or Symfony\Component\Security\Core\SecurityContextInterface'); } if (!$authorizationChecker instanceof AuthorizationCheckerInterface && !$authorizationChecker instanceof SecurityContextInterface) { throw new \InvalidArgumentException('Argument 2 should be an instance of Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface or Symfony\Component\Security\Core\SecurityContextInterface'); } $this->tokenStorage = $tokenStorage; $this->authorizationChecker = $authorizationChecker; $this->aclProvider = $aclProvider; $this->maskBuilderClass = $maskBuilderClass; $this->superAdminRoles = $superAdminRoles; } /** * {@inheritdoc} */ public function setAdminPermissions(array $permissions) { $this->adminPermissions = $permissions; } /** * {@inheritdoc} */ public function getAdminPermissions() { return $this->adminPermissions; } /** * {@inheritdoc} */ public function setObjectPermissions(array $permissions) { $this->objectPermissions = $permissions; } /** * {@inheritdoc} */ public function getObjectPermissions() { return $this->objectPermissions; } /** * {@inheritdoc} */ public function isGranted(AdminInterface $admin, $attributes, $object = null) { if (!is_array($attributes)) { $attributes = array($attributes); } try { return $this->authorizationChecker->isGranted($this->superAdminRoles) || $this->authorizationChecker->isGranted($attributes, $object); } catch (AuthenticationCredentialsNotFoundException $e) { return false; } } /** * {@inheritdoc} */ public function getBaseRole(AdminInterface $admin) { return 'ROLE_'.str_replace('.', '_', strtoupper($admin->getCode())).'_%s'; } /** * {@inheritdoc} */ public function buildSecurityInformation(AdminInterface $admin) { $baseRole = $this->getBaseRole($admin); $results = array(); foreach ($admin->getSecurityInformation() as $role => $permissions) { $results[sprintf($baseRole, $role)] = $permissions; } return $results; } /** * {@inheritdoc} */ public function createObjectSecurity(AdminInterface $admin, $object) { // retrieving the ACL for the object identity $objectIdentity = ObjectIdentity::fromDomainObject($object); $acl = $this->getObjectAcl($objectIdentity); if (is_null($acl)) { $acl = $this->createAcl($objectIdentity); } // retrieving the security identity of the currently logged-in user $user = $this->tokenStorage->getToken()->getUser(); $securityIdentity = UserSecurityIdentity::fromAccount($user); $this->addObjectOwner($acl, $securityIdentity); $this->addObjectClassAces($acl, $this->buildSecurityInformation($admin)); $this->updateAcl($acl); } /** * {@inheritdoc} */ public function deleteObjectSecurity(AdminInterface $admin, $object) { $objectIdentity = ObjectIdentity::fromDomainObject($object); $this->deleteAcl($objectIdentity); } /** * {@inheritdoc} */ public function getObjectAcl(ObjectIdentityInterface $objectIdentity) { try { $acl = $this->aclProvider->findAcl($objectIdentity); } catch (AclNotFoundException $e) { return; } return $acl; } /** * {@inheritdoc} */ public function findObjectAcls(\Traversable $oids, array $sids = array()) { try { $acls = $this->aclProvider->findAcls(iterator_to_array($oids), $sids); } catch (NotAllAclsFoundException $e) { $acls = $e->getPartialResult(); } catch (AclNotFoundException $e) { // if only one oid, this error is thrown $acls = new \SplObjectStorage(); } return $acls; } /** * {@inheritdoc} */ public function addObjectOwner(AclInterface $acl, UserSecurityIdentity $securityIdentity = null) { if (false === $this->findClassAceIndexByUsername($acl, $securityIdentity->getUsername())) { // only add if not already exists $acl->insertObjectAce($securityIdentity, constant("$this->maskBuilderClass::MASK_OWNER")); } } /** * {@inheritdoc} */ public function addObjectClassAces(AclInterface $acl, array $roleInformation = array()) { $builder = new $this->maskBuilderClass(); foreach ($roleInformation as $role => $permissions) { $aceIndex = $this->findClassAceIndexByRole($acl, $role); $hasRole = false; foreach ($permissions as $permission) { // add only the object permissions if (in_array($permission, $this->getObjectPermissions())) { $builder->add($permission); $hasRole = true; } } if ($hasRole) { if ($aceIndex === false) { $acl->insertClassAce(new RoleSecurityIdentity($role), $builder->get()); } else { $acl->updateClassAce($aceIndex, $builder->get()); } $builder->reset(); } elseif ($aceIndex !== false) { $acl->deleteClassAce($aceIndex); } } } /** * {@inheritdoc} */ public function createAcl(ObjectIdentityInterface $objectIdentity) { return $this->aclProvider->createAcl($objectIdentity); } /** * {@inheritdoc} */ public function updateAcl(AclInterface $acl) { $this->aclProvider->updateAcl($acl); } /** * {@inheritdoc} */ public function deleteAcl(ObjectIdentityInterface $objectIdentity) { $this->aclProvider->deleteAcl($objectIdentity); } /** * {@inheritdoc} */ public function findClassAceIndexByRole(AclInterface $acl, $role) { foreach ($acl->getClassAces() as $index => $entry) { if ($entry->getSecurityIdentity() instanceof RoleSecurityIdentity && $entry->getSecurityIdentity()->getRole() === $role) { return $index; } } return false; } /** * {@inheritdoc} */ public function findClassAceIndexByUsername(AclInterface $acl, $username) { foreach ($acl->getClassAces() as $index => $entry) { if ($entry->getSecurityIdentity() instanceof UserSecurityIdentity && $entry->getSecurityIdentity()->getUsername() === $username) { return $index; } } return false; } }