Added ACL related features

This commit is contained in:
Kévin Gomez 2013-12-12 16:48:16 +00:00
parent 186de0e93c
commit 12bcfbde5e
27 changed files with 2864 additions and 29 deletions

View file

@ -12,6 +12,7 @@ namespace Propel\PropelBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Filesystem\Filesystem;
@ -125,7 +126,7 @@ abstract class AbstractCommand extends ContainerAwareCommand
if ($this->input->hasOption('connection')) {
$connections = $this->input->getOption('connection') ?: array($this->getContainer()->getParameter('propel.dbal.default_connection'));
if (!in_array($database['name'], $connections)) {
if (!in_array((string) $database['name'], $connections)) {
// we skip this schema because the connection name doesn't
// match the input values
unset($this->tempSchemas[$tempSchema]);
@ -164,6 +165,27 @@ abstract class AbstractCommand extends ContainerAwareCommand
return $this->getSchemaLocator()->locateFromBundles($kernel->getBundles());
}
protected function runCommand(Command $command, array $parameters, InputInterface $input, OutputInterface $output)
{
// add the command's name to the parameters
array_unshift($parameters, $this->getName());
// merge the default parameters
$parameters = array_merge(array(
'--input-dir' => $this->cacheDir,
'--verbose' => $input->getOption('verbose'),
), $parameters);
if ($input->hasOption('platform')) {
$parameters['--platform'] = $input->getOption('platform');
}
$command->setApplication($this->getApplication());
// and run the sub-command
return $command->run(new ArrayInput($parameters), $output);
}
/*
* Create an XML file which represents propel.configuration
*

124
Command/AclInitCommand.php Normal file
View file

@ -0,0 +1,124 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\Output;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
use Symfony\Component\HttpKernel\KernelInterface;
/**
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class AclInitCommand extends AbstractCommand
{
protected function configure()
{
$this
->setDescription('Initialize "Access Control Lists" model and SQL')
->addOption('force', null, InputOption::VALUE_NONE, 'Set this parameter to execute this action.')
->addOption('connection', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Connection to use. Example: default, bookstore')
->setHelp(<<<EOT
The <info>%command.name%</info> command connects to the database and executes all SQL statements required to setup the ACL database, it also generates the ACL model.
<info>php %command.full_name%</info>
The <info>--force</info> parameter has to be used to actually insert SQL.
The <info>--connection</info> parameter allows you to change the connection to use.
The default connection is the active connection (propel.dbal.default_connection).
EOT
)
->setName('propel:acl:init')
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$outputDir = realpath($this->getApplication()->getKernel()->getRootDir().'/../');
// Generate ACL model
$modelBuildCmd = new \Propel\Generator\Command\ModelBuildCommand();
$modelBuildArgs = array(
'--output-dir' => $outputDir,
);
if ($this->runCommand($modelBuildCmd, $modelBuildArgs, $input, $output) === 0) {
$output->writeln(sprintf(
'>> <info>%20s</info> Generated model classes from <comment>%s</comment>',
$this->getApplication()->getKernel()->getBundle('PropelBundle')->getName(),
'acl_schema.xml'
));
} else {
$this->writeTaskError($output, 'model:build');
return 1;
}
// Prepare SQL
$sqlBuildCmd = new \Propel\Generator\Command\SqlBuildCommand();
$sqlBuildArgs = array(
'--connection' => $this->getConnections($input->getOption('connection')),
'--output-dir' => $this->getCacheDir(),
);
if ($this->runCommand($sqlBuildCmd, $sqlBuildArgs, $input, $output) === 0) {
$this->writeSection(
$output,
'<comment>1</comment> <info>SQL file has been generated.</info>'
);
} else {
$this->writeTaskError($output, 'sql:build');
return 2;
}
// insert sql
$sqlInsertCmd = new \Propel\Generator\Command\SqlInsertCommand();
$sqlInsertArgs = array(
'--connection' => $this->getConnections($input->getOption('connection')),
);
if ($this->runCommand($sqlBuildCmd, $sqlBuildArgs, $input, $output) === 0) {
$this->writeSection(
$output,
'<comment>1</comment> <info>SQL file has been inserted.</info>'
);
} else {
$this->writeTaskError($output, 'sql:insert');
return 3;
}
}
protected function getFinalSchemas(KernelInterface $kernel, BundleInterface $bundle = null)
{
$aclSchema = new \SplFileInfo($kernel->locateResource('@PropelBundle/Resources/acl_schema.xml'));
return array(
array($kernel->getBundle('PropelBundle'), $aclSchema)
);
}
/**
* {@inheritdoc}
*/
protected function initialize(InputInterface $input, OutputInterface $output)
{
parent::initialize($input, $output);
$this->cacheDir = $this->cacheDir . '/acl';
$this->setupBuildTimeFiles();
}
}

View file

@ -12,8 +12,6 @@ namespace Propel\PropelBundle\Command;
use Propel\Generator\Command\AbstractCommand as BaseCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@ -60,25 +58,4 @@ abstract class WrappedCommand extends AbstractCommand
return $this->runCommand($command, $params, $input, $output);
}
protected function runCommand(Command $command, array $parameters, InputInterface $input, OutputInterface $output)
{
// add the command's name to the parameters
array_unshift($parameters, $this->getName());
// merge the default parameters
$parameters = array_merge(array(
'--input-dir' => $this->cacheDir,
'--verbose' => $input->getOption('verbose'),
), $parameters);
if ($input->hasOption('platform')) {
$parameters['--platform'] = $input->getOption('platform');
}
$command->setApplication($this->getApplication());
// and run the sub-command
return $command->run(new ArrayInput($parameters), $output);
}
}

View file

@ -36,11 +36,7 @@ class PropelExtension extends Extension
$configuration = $this->getConfiguration($configs, $container);
$config = $processor->processConfiguration($configuration, $configs);
if (isset($config['logging']) && $config['logging']) {
$logging = $config['logging'];
} else {
$logging = false;
}
$logging = isset($config['logging']) && $config['logging'];
$container->setParameter('propel.logging', $logging);
@ -49,6 +45,7 @@ class PropelExtension extends Extension
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('propel.xml');
$loader->load('converters.xml');
$loader->load('security.xml');
}
// build properties

43
Model/Acl/AclClass.php Normal file
View file

@ -0,0 +1,43 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Model\Acl;
use Propel\PropelBundle\Model\Acl\Base\AclClass as BaseAclClass;
use Propel\Runtime\Connection\ConnectionInterface;
use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
class AclClass extends BaseAclClass
{
/**
* Return an AclClass for the given ACL ObjectIdentity.
*
* If none can be found, a new one will be saved.
*
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $objectIdentity
* @param ConnectionInterface $con
*
* @return \Propel\PropelBundle\Model\Acl\AclClass
*/
public static function fromAclObjectIdentity(ObjectIdentityInterface $objectIdentity, ConnectionInterface $con = null)
{
$obj = AclClassQuery::create()
->filterByType($objectIdentity->getType())
->findOneOrCreate($con)
;
if ($obj->isNew()) {
$obj->save($con);
}
return $obj;
}
}

View file

@ -0,0 +1,18 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Model\Acl;
use Propel\PropelBundle\Model\Acl\Base\AclClassQuery as BaseAclClassQuery;
class AclClassQuery extends BaseAclClassQuery
{
}

80
Model/Acl/Entry.php Normal file
View file

@ -0,0 +1,80 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Model\Acl;
use Propel\PropelBundle\Model\Acl\Base\Entry as BaseEntry;
use Propel\PropelBundle\Security\Acl\Domain\Entry as AclEntry;
use Propel\PropelBundle\Security\Acl\Domain\FieldEntry as AclFieldEntry;
use Symfony\Component\Security\Acl\Model\AclInterface;
use Symfony\Component\Security\Acl\Model\EntryInterface;
use Symfony\Component\Security\Acl\Model\AuditableEntryInterface;
use Symfony\Component\Security\Acl\Model\FieldEntryInterface;
class Entry extends BaseEntry
{
/**
* Transform a given ACL entry into a Entry model.
*
* The entry will not be persisted!
*
* @param \Symfony\Component\Security\Acl\Model\EntryInterface $aclEntry
*
* @return \Propel\PropelBundle\Model\Acl\Entry
*/
public static function fromAclEntry(EntryInterface $aclEntry)
{
$entry = new self();
// Already persisted before?
if ($aclEntry->getId()) {
$entry->setId($aclEntry->getId());
}
$entry
->setMask($aclEntry->getMask())
->setGranting($aclEntry->isGranting())
->setGrantingStrategy($aclEntry->getStrategy())
->setSecurityIdentity(SecurityIdentity::fromAclIdentity($aclEntry->getSecurityIdentity()))
;
if ($aclEntry instanceof FieldEntryInterface) {
$entry->setFieldName($aclEntry->getField());
}
if ($aclEntry instanceof AuditableEntryInterface) {
$entry
->setAuditFailure($aclEntry->isAuditFailure())
->setAuditSuccess($aclEntry->isAuditSuccess())
;
}
return $entry;
}
/**
* Transform a given model entry into an ACL related Entry (ACE).
*
* @param \Propel\PropelBundle\Model\Acl\Entry $modelEntry
* @param \Symfony\Component\Security\Acl\Model\AclInterface $acl
*
* @return \Symfony\Component\Security\Acl\Model\EntryInterface
*/
public static function toAclEntry(Entry $modelEntry, AclInterface $acl)
{
if (null === $modelEntry->getFieldName()) {
return new AclEntry($modelEntry, $acl);
}
return new AclFieldEntry($modelEntry, $acl);
}
}

70
Model/Acl/EntryQuery.php Normal file
View file

@ -0,0 +1,70 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Model\Acl;
use Propel\PropelBundle\Model\Acl\Base\EntryQuery as BaseEntryQuery;
use Propel\PropelBundle\Model\Acl\Map\EntryTableMap;
use Propel\PropelBundle\Model\Acl\Map\ObjectIdentityTableMap;
use Propel\Runtime\ActiveQuery\Criteria;
use Propel\Runtime\Connection\ConnectionInterface;
use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface;
class EntryQuery extends BaseEntryQuery
{
/**
* Return Entry objects filtered by an ACL related ObjectIdentity.
*
* @see find()
*
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $objectIdentity An ACL related ObjectIdentity.
* @param array $securityIdentities A list of SecurityIdentity to filter by.
* @param \ConnectionInterface $con
*
* @return \PropelObjectCollection
*/
public function findByAclIdentity(ObjectIdentityInterface $objectIdentity, array $securityIdentities = array(), ConnectionInterface $con = null)
{
$securityIds = array();
foreach ($securityIdentities as $eachIdentity) {
if (!$eachIdentity instanceof SecurityIdentityInterface) {
if (is_object($eachIdentity)) {
$errorMessage = sprintf('The list of security identities contains at least one invalid entry of class "%s". Please provide objects of classes implementing "Symfony\Component\Security\Acl\Model\SecurityIdentityInterface" only.', get_class($eachIdentity));
} else {
$errorMessage = sprintf('The list of security identities contains at least one invalid entry "%s". Please provide objects of classes implementing "Symfony\Component\Security\Acl\Model\SecurityIdentityInterface" only.', $eachIdentity);
}
throw new \InvalidArgumentException($errorMessage);
}
if ($securityIdentity = SecurityIdentity::fromAclIdentity($eachIdentity)) {
$securityIds[$securityIdentity->getId()] = $securityIdentity->getId();
}
}
$this
->useAclClassQuery(null, Criteria::INNER_JOIN)
->filterByType((string) $objectIdentity->getType())
->endUse()
->leftJoinObjectIdentity()
->add(ObjectIdentityTableMap::OBJECT_IDENTIFIER, (string) $objectIdentity->getIdentifier(), Criteria::EQUAL)
->addOr(EntryTableMap::OBJECT_IDENTITY_ID, null, Criteria::ISNULL)
;
if (!empty($securityIdentities)) {
$this->filterBySecurityIdentityId($securityIds);
}
return $this->find($con);
}
}

View file

@ -0,0 +1,141 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Model\Acl;
use Propel\PropelBundle\Model\Acl\Base\ObjectIdentity as BaseObjectIdentity;
use Propel\PropelBundle\Model\Acl\Map\ObjectIdentityTableMap;
use Propel\Runtime\ActiveQuery\Criteria;
use Propel\Runtime\Connection\ConnectionInterface;
class ObjectIdentity extends BaseObjectIdentity
{
public function preInsert(ConnectionInterface $con = null)
{
// Compatibility with default implementation.
$ancestor = new ObjectIdentityAncestor();
$ancestor->setObjectIdentityRelatedByObjectIdentityId($this);
$ancestor->setObjectIdentityRelatedByAncestorId($this);
$this->addObjectIdentityAncestorRelatedByAncestorId($ancestor);
if ($this->getParentObjectIdentityId()) {
$this->updateAncestorsTree($con);
}
return true;
}
public function preUpdate(ConnectionInterface $con = null)
{
if ($this->isColumnModified(ObjectIdentityTableMap::PARENT_OBJECT_IDENTITY_ID)) {
$this->updateAncestorsTree($con);
}
return true;
}
public function preDelete(ConnectionInterface $con = null)
{
// Only retrieve direct children, it's faster and grand children will be retrieved recursively.
$children = ObjectIdentityQuery::create()->findChildren($this, $con);
$objIds = $children->getPrimaryKeys(false);
$objIds[] = $this->getId();
$children->delete($con);
// Manually delete those for DBAdapter not capable of cascading the DELETE.
ObjectIdentityAncestorQuery::create()
->filterByObjectIdentityId($objIds, Criteria::IN)
->delete($con)
;
return true;
}
/**
* Update all ancestor entries to reflect changes on this instance.
*
* @param ConnectionInterface $con
*
* @return \Propel\PropelBundle\Model\Acl\ObjectIdentity $this
*/
protected function updateAncestorsTree(ConnectionInterface $con = null)
{
$con->beginTransaction();
$oldAncestors = ObjectIdentityQuery::create()->findAncestors($this, $con);
$children = ObjectIdentityQuery::create()->findGrandChildren($this, $con);
$children->append($this);
if (count($oldAncestors)) {
foreach ($children as $eachChild) {
/*
* Delete only those entries, that are ancestors based on the parent relation.
* Ancestors of grand children up to the current node will be kept.
*/
$query = ObjectIdentityAncestorQuery::create()
->filterByObjectIdentityId($eachChild->getId())
->filterByObjectIdentityRelatedByAncestorId($oldAncestors, Criteria::IN)
;
if ($eachChild->getId() !== $this->getId()) {
$query->filterByAncestorId(array($eachChild->getId(), $this->getId()), Criteria::NOT_IN);
} else {
$query->filterByAncestorId($this->getId(), Criteria::NOT_EQUAL);
}
$query->delete($con);
}
}
// This is the new parent object identity!
$parent = $this->getObjectIdentityRelatedByParentObjectIdentityId($con);
if (null !== $parent) {
$newAncestors = ObjectIdentityQuery::create()->findAncestors($parent, $con);
$newAncestors->append($parent);
foreach ($newAncestors as $eachAncestor) {
// This collection contains the current object identity!
foreach ($children as $eachChild) {
$ancestor = ObjectIdentityAncestorQuery::create()
->filterByObjectIdentityId($eachChild->getId())
->filterByAncestorId($eachAncestor->getId())
->findOneOrCreate($con)
;
// If the entry already exists, next please.
if (!$ancestor->isNew()) {
continue;
}
if ($eachChild->getId() === $this->getId()) {
// Do not save() here, as it would result in an infinite recursion loop!
$this->addObjectIdentityAncestorRelatedByObjectIdentityId($ancestor);
} else {
// Save the new ancestor to avoid integrity constraint violation.
$ancestor->save($con);
$eachChild
->addObjectIdentityAncestorRelatedByObjectIdentityId($ancestor)
->save($con)
;
}
}
}
}
$con->commit();
return $this;
}
}

View file

@ -0,0 +1,18 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Model\Acl;
use Propel\PropelBundle\Model\Acl\Base\ObjectIdentityAncestor as BaseObjectIdentityAncestor;
class ObjectIdentityAncestor extends BaseObjectIdentityAncestor
{
}

View file

@ -0,0 +1,18 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Model\Acl;
use Propel\PropelBundle\Model\Acl\Base\ObjectIdentityAncestorQuery as BaseObjectIdentityAncestorQuery;
class ObjectIdentityAncestorQuery extends BaseObjectIdentityAncestorQuery
{
}

View file

@ -0,0 +1,115 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Model\Acl;
use Propel\PropelBundle\Model\Acl\ObjectIdentity;
use Propel\PropelBundle\Model\Acl\Base\ObjectIdentityQuery as BaseObjectIdentityQuery;
use Propel\Runtime\ActiveQuery\Criteria;
use Propel\Runtime\Connection\ConnectionInterface;
use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
class ObjectIdentityQuery extends BaseObjectIdentityQuery
{
/**
* Filter by an ObjectIdentity object belonging to the given ACL related ObjectIdentity.
*
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $objectIdentity
* @param ConnectionInterface $con
*
* @return \Propel\PropelBundle\Model\Acl\ObjectIdentityQuery $this
*/
public function filterByAclObjectIdentity(ObjectIdentityInterface $objectIdentity, ConnectionInterface $con = null)
{
/*
* Not using a JOIN here, because the filter may be applied on 'findOneOrCreate',
* which is currently (Propel 1.6.4-dev) not working.
*/
$aclClass = AclClass::fromAclObjectIdentity($objectIdentity, $con);
$this
->filterByClassId($aclClass->getId())
->filterByIdentifier($objectIdentity->getIdentifier())
;
return $this;
}
/**
* Return an ObjectIdentity object belonging to the given ACL related ObjectIdentity.
*
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $objectIdentity
* @param ConnectionInterface $con
*
* @return \Propel\PropelBundle\Model\Acl\ObjectIdentity
*/
public function findOneByAclObjectIdentity(ObjectIdentityInterface $objectIdentity, ConnectionInterface $con = null)
{
return $this
->filterByAclObjectIdentity($objectIdentity, $con)
->findOne($con)
;
}
/**
* Return all children of the given object identity.
*
* @param \Propel\PropelBundle\Model\Acl\ObjectIdentity $objectIdentity
* @param ConnectionInterface $con
*
* @return \PropelObjectCollection
*/
public function findChildren(ObjectIdentity $objectIdentity, ConnectionInterface $con = null)
{
return $this
->filterByObjectIdentityRelatedByParentObjectIdentityId($objectIdentity)
->find($con)
;
}
/**
* Return all children and grand-children of the given object identity.
*
* @param \Propel\PropelBundle\Model\Acl\ObjectIdentity $objectIdentity
* @param ConnectionInterface $con
*
* @return \PropelObjectCollection
*/
public function findGrandChildren(ObjectIdentity $objectIdentity, ConnectionInterface $con = null)
{
return $this
->useObjectIdentityAncestorRelatedByObjectIdentityIdQuery()
->filterByObjectIdentityRelatedByAncestorId($objectIdentity)
->filterByObjectIdentityRelatedByObjectIdentityId($objectIdentity, Criteria::NOT_EQUAL)
->endUse()
->find($con)
;
}
/**
* Return all ancestors of the given object identity.
*
* @param ObjectIdentity $objectIdentity
* @param ConnectionInterface $con
*
* @return \PropelObjectCollection
*/
public function findAncestors(ObjectIdentity $objectIdentity, ConnectionInterface $con = null)
{
return $this
->useObjectIdentityAncestorRelatedByAncestorIdQuery()
->filterByObjectIdentityRelatedByObjectIdentityId($objectIdentity)
->filterByObjectIdentityRelatedByAncestorId($objectIdentity, Criteria::NOT_EQUAL)
->endUse()
->find($con)
;
}
}

View file

@ -0,0 +1,87 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Model\Acl;
use Propel\PropelBundle\Model\Acl\Base\SecurityIdentity as BaseSecurityIdentity;
use Propel\Runtime\Connection\ConnectionInterface;
use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity;
use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface;
class SecurityIdentity extends BaseSecurityIdentity
{
/**
* Transform a given mode security identity into an ACL related SecurityIdentity.
*
* @param \Propel\PropelBundle\Model\Acl\SecurityIdentity $securityIdentity
*
* @return \Symfony\Component\Security\Acl\Model\SecurityIdentityInterface
*/
public static function toAclIdentity(SecurityIdentity $securityIdentity)
{
$identifier = $securityIdentity->getIdentifier();
if ($securityIdentity->getUsername()) {
if (false === strpos($identifier, '-')) {
throw new \InvalidArgumentException('The given identifier does not resolve to a UserSecurityIdentity.');
}
list($class, $username) = explode('-', $identifier, 2);
return new UserSecurityIdentity($username, $class);
}
if (0 === strpos($identifier, 'ROLE_') or 0 === strpos($identifier, 'IS_AUTHENTICATED_')) {
return new RoleSecurityIdentity($identifier);
}
throw new \InvalidArgumentException('The security identity does not resolve to either UserSecurityIdentity or RoleSecurityIdentity.');
}
/**
* Transform a given ACL security identity into a SecurityIdentity model.
*
* If there is no model entry given, a new one will be created and saved to the database.
*
* @throws \InvalidArgumentException
*
* @param \Symfony\Component\Security\Acl\Model\SecurityIdentityInterface $aclIdentity
* @param ConnectionInterface $con
*
* @return \Propel\PropelBundle\Model\Acl\SecurityIdentity
*/
public static function fromAclIdentity(SecurityIdentityInterface $aclIdentity, ConnectionInterface $con = null)
{
if ($aclIdentity instanceof UserSecurityIdentity) {
$identifier = $aclIdentity->getClass().'-'.$aclIdentity->getUsername();
$username = true;
} elseif ($aclIdentity instanceof RoleSecurityIdentity) {
$identifier = $aclIdentity->getRole();
$username = false;
} else {
throw new \InvalidArgumentException('The ACL identity must either be an instance of UserSecurityIdentity or RoleSecurityIdentity.');
}
$obj = SecurityIdentityQuery::create()
->filterByIdentifier($identifier)
->filterByUsername($username)
->findOneOrCreate($con)
;
if ($obj->isNew()) {
$obj->save($con);
}
return $obj;
}
}

View file

@ -0,0 +1,18 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Model\Acl;
use Propel\PropelBundle\Model\Acl\Base\SecurityIdentityQuery as BaseSecurityIdentityQuery;
class SecurityIdentityQuery extends BaseSecurityIdentityQuery
{
}

104
Resources/acl_schema.xml Normal file
View file

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<database name="default" namespace="Propel\PropelBundle\Model\Acl" defaultIdMethod="native" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://xsd.propelorm.org/1.6/database.xsd">
<table name="acl_classes" phpName="AclClass">
<column name="id" type="integer" autoIncrement="true" primaryKey="true" />
<column name="class_type" type="varchar" size="200" required="true" phpName="Type" />
<unique>
<unique-column name="class_type" />
</unique>
</table>
<table name="acl_security_identities" phpName="SecurityIdentity">
<column name="id" type="integer" autoIncrement="true" primaryKey="true" />
<column name="identifier" type="varchar" size="200" required="true" />
<column name="username" type="boolean" required="true" />
<unique>
<unique-column name="identifier" />
<unique-column name="username" />
</unique>
</table>
<table name="acl_object_identities" phpName="ObjectIdentity">
<column name="id" type="integer" autoIncrement="true" primaryKey="true" />
<column name="class_id" type="integer" required="true" />
<column name="object_identifier" type="varchar" size="200" required="true" phpName="Identifier" />
<column name="parent_object_identity_id" type="integer" required="false" defaultValue="null" />
<column name="entries_inheriting" type="boolean" required="true" defaultValue="true" />
<unique>
<unique-column name="class_id" />
<unique-column name="object_identifier" />
</unique>
<index>
<index-column name="parent_object_identity_id" />
</index>
<foreign-key foreignTable="acl_classes" onDelete="RESTRICT" onUpdate="CASCADE">
<reference local="class_id" foreign="id" />
</foreign-key>
<foreign-key foreignTable="acl_object_identities" onDelete="RESTRICT" onUpdate="CASCADE">
<reference local="parent_object_identity_id" foreign="id" />
</foreign-key>
</table>
<table name="acl_object_identity_ancestors" phpName="ObjectIdentityAncestor" heavyIndexing="true">
<column name="object_identity_id" type="integer" primaryKey="true" />
<column name="ancestor_id" type="integer" primaryKey="true" />
<foreign-key foreignTable="acl_object_identities" onDelete="CASCADE" onUpdate="CASCADE">
<reference local="object_identity_id" foreign="id" />
</foreign-key>
<foreign-key foreignTable="acl_object_identities" onDelete="CASCADE" onUpdate="CASCADE">
<reference local="ancestor_id" foreign="id" />
</foreign-key>
</table>
<table name="acl_entries" phpName="Entry">
<column name="id" type="integer" autoIncrement="true" primaryKey="true" />
<column name="class_id" type="integer" required="true" />
<column name="object_identity_id" type="integer" required="false" defaultValue="null" />
<column name="security_identity_id" type="integer" required="true" />
<column name="field_name" type="varchar" size="50" />
<column name="ace_order" type="integer" required="true" />
<column name="mask" type="integer" required="true" />
<column name="granting" type="boolean" required="true" />
<column name="granting_strategy" type="varchar" size="30" required="true" />
<column name="audit_success" type="boolean" required="true" defaultValue="false" />
<column name="audit_failure" type="boolean" required="true" defaultValue="true" />
<unique>
<unique-column name="class_id" />
<unique-column name="object_identity_id" />
<unique-column name="field_name" />
<unique-column name="ace_order" />
</unique>
<index>
<index-column name="class_id" />
<index-column name="object_identity_id" />
<index-column name="security_identity_id" />
</index>
<index>
<index-column name="class_id" />
</index>
<index>
<index-column name="object_identity_id" />
</index>
<index>
<index-column name="security_identity_id" />
</index>
<foreign-key foreignTable="acl_classes" onDelete="CASCADE" onUpdate="CASCADE">
<reference local="class_id" foreign="id" />
</foreign-key>
<foreign-key foreignTable="acl_object_identities" onDelete="CASCADE" onUpdate="CASCADE">
<reference local="object_identity_id" foreign="id" />
</foreign-key>
<foreign-key foreignTable="acl_security_identities" onDelete="CASCADE" onUpdate="CASCADE">
<reference local="security_identity_id" foreign="id" />
</foreign-key>
</table>
</database>

View file

@ -0,0 +1,21 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="propel.security.acl.provider.model.class">Propel\PropelBundle\Security\Acl\AuditableAclProvider</parameter>
<parameter key="propel.security.user.provider.class">Propel\PropelBundle\Security\User\PropelUserProvider</parameter>
</parameters>
<services>
<service id="propel.security.acl.provider" class="%propel.security.acl.provider.model.class%" public="false">
<argument type="service" id="security.acl.permission_granting_strategy" />
<argument type="service" id="propel.security.acl.connection" on-invalid="null" />
<argument type="service" id="security.acl.cache" on-invalid="null" />
</service>
<service id="propel.security.user.provider" class="%propel.security.user.provider.class%" abstract="true" public="false" />
</services>
</container>

View file

@ -0,0 +1,181 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Security\Acl;
use Propel\Runtime\Collection\ObjectCollection;
use Propel\Runtime\Connection\ConnectionInterface;
use Propel\PropelBundle\Model\Acl\EntryQuery;
use Propel\PropelBundle\Model\Acl\ObjectIdentityQuery;
use Propel\PropelBundle\Model\Acl\SecurityIdentity;
use Propel\PropelBundle\Security\Acl\Domain\Acl;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
use Symfony\Component\Security\Acl\Exception\AclNotFoundException;
use Symfony\Component\Security\Acl\Model\AclInterface;
use Symfony\Component\Security\Acl\Model\AclCacheInterface;
use Symfony\Component\Security\Acl\Model\AclProviderInterface;
use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
use Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface;
/**
* An implementation of the AclProviderInterface using Propel ORM.
*
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class AclProvider implements AclProviderInterface
{
protected $permissionGrantingStrategy;
protected $connection;
protected $cache;
/**
* Constructor.
*
* @param \Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface $permissionGrantingStrategy
* @param ConnectionInterface $con
* @param \Symfony\Component\Security\Acl\Model\AclCacheInterface $cache
*/
public function __construct(PermissionGrantingStrategyInterface $permissionGrantingStrategy, ConnectionInterface $connection = null, AclCacheInterface $cache = null)
{
$this->permissionGrantingStrategy = $permissionGrantingStrategy;
$this->connection = $connection;
$this->cache = $cache;
}
/**
* Retrieves all child object identities from the database.
*
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $parentObjectIdentity
* @param bool $directChildrenOnly
*
* @return array
*/
public function findChildren(ObjectIdentityInterface $parentObjectIdentity, $directChildrenOnly = false)
{
$modelIdentity = ObjectIdentityQuery::create()->findOneByAclObjectIdentity($parentObjectIdentity, $this->connection);
if (empty($modelIdentity)) {
return array();
}
if ($directChildrenOnly) {
$collection = ObjectIdentityQuery::create()->findChildren($modelIdentity, $this->connection);
} else {
$collection = ObjectIdentityQuery::create()->findGrandChildren($modelIdentity, $this->connection);
}
$children = array();
foreach ($collection as $eachChild) {
$children[] = new ObjectIdentity($eachChild->getIdentifier(), $eachChild->getAclClass($this->connection)->getType());
}
return $children;
}
/**
* Returns the ACL that belongs to the given object identity
*
* @throws \Symfony\Component\Security\Acl\Exception\AclNotFoundException
*
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $objectIdentity
* @param array $securityIdentities
*
* @return \Symfony\Component\Security\Acl\Model\AclInterface
*/
public function findAcl(ObjectIdentityInterface $objectIdentity, array $securityIdentities = array())
{
$modelObj = ObjectIdentityQuery::create()->findOneByAclObjectIdentity($objectIdentity, $this->connection);
if (null !== $this->cache and null !== $modelObj) {
$cachedAcl = $this->cache->getFromCacheById($modelObj->getId());
if ($cachedAcl instanceof AclInterface) {
return $cachedAcl;
}
}
$collection = EntryQuery::create()->findByAclIdentity($objectIdentity, $securityIdentities, $this->connection);
if (0 === count($collection)) {
if (empty($securityIdentities)) {
$errorMessage = 'There is no ACL available for this object identity. Please create one using the MutableAclProvider.';
} else {
$errorMessage = 'There is at least no ACL for this object identity and the given security identities. Try retrieving the ACL without security identity filter and add ACEs for the security identities.';
}
throw new AclNotFoundException($errorMessage);
}
$loadedSecurityIdentities = array();
foreach ($collection as $eachEntry) {
if (!isset($loadedSecurityIdentities[$eachEntry->getSecurityIdentity()->getId()])) {
$loadedSecurityIdentities[$eachEntry->getSecurityIdentity()->getId()] = SecurityIdentity::toAclIdentity($eachEntry->getSecurityIdentity());
}
}
$parentAcl = null;
$entriesInherited = true;
if (null !== $modelObj) {
$entriesInherited = $modelObj->getEntriesInheriting();
if (null !== $modelObj->getParentObjectIdentityId()) {
$parentObj = $modelObj->getObjectIdentityRelatedByParentObjectIdentityId($this->connection);
try {
$parentAcl = $this->findAcl(new ObjectIdentity($parentObj->getIdentifier(), $parentObj->getAclClass($this->connection)->getType()));
} catch (AclNotFoundException $e) {
/*
* This happens e.g. if the parent ACL is created, but does not contain any ACE by now.
* The ACEs may be applied later on.
*/
}
}
}
return $this->getAcl($collection, $objectIdentity, $loadedSecurityIdentities, $parentAcl, $entriesInherited);
}
/**
* Returns the ACLs that belong to the given object identities
*
* @throws \Symfony\Component\Security\Acl\Exception\AclNotFoundException When at least one object identity is missing its ACL.
*
* @param array $objectIdentities an array of ObjectIdentityInterface implementations
* @param array $securityIdentities an array of SecurityIdentityInterface implementations
*
* @return \SplObjectStorage mapping the passed object identities to ACLs
*/
public function findAcls(array $objectIdentities, array $securityIdentities = array())
{
$result = new \SplObjectStorage();
foreach ($objectIdentities as $eachIdentity) {
$result[$eachIdentity] = $this->findAcl($eachIdentity, $securityIdentities);
}
return $result;
}
/**
* Create an ACL.
*
* @param ObjectCollection $collection
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $objectIdentity
* @param array $loadedSecurityIdentities
* @param \Symfony\Component\Security\Acl\Model\AclInterface $parentAcl
* @param bool $inherited
*
* @return \Propel\PropelBundle\Security\Acl\Domain\Acl
*/
protected function getAcl(ObjectCollection $collection, ObjectIdentityInterface $objectIdentity, array $loadedSecurityIdentities = array(), AclInterface $parentAcl = null, $inherited = true)
{
return new Acl($collection, $objectIdentity, $this->permissionGrantingStrategy, $loadedSecurityIdentities, $parentAcl, $inherited);
}
}

View file

@ -0,0 +1,39 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Security\Acl;
use Propel\Runtime\Collection\ObjectCollection;
use Propel\PropelBundle\Security\Acl\Domain\AuditableAcl;
use Symfony\Component\Security\Acl\Model\AclInterface;
use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
/**
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class AuditableAclProvider extends MutableAclProvider
{
/**
* Get an ACL for this provider.
*
* @param Propel\Runtime\Collection\ObjectCollection $collection
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $objectIdentity
* @param array $loadedSecurityIdentities
* @param \Symfony\Component\Security\Acl\Model\AclInterface $parentAcl
* @param bool $inherited
*
* @return \Propel\PropelBundle\Security\Acl\Domain\AuditableAcl
*/
protected function getAcl(ObjectCollection $collection, ObjectIdentityInterface $objectIdentity, array $loadedSecurityIdentities = array(), AclInterface $parentAcl = null, $inherited = true)
{
return new AuditableAcl($collection, $objectIdentity, $this->permissionGrantingStrategy, $loadedSecurityIdentities, $parentAcl, $inherited, $this->connection);
}
}

316
Security/Acl/Domain/Acl.php Normal file
View file

@ -0,0 +1,316 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Security\Acl\Domain;
use Propel\Runtime\Collection\ObjectCollection;
use Symfony\Component\Security\Acl\Exception\Exception as AclException;
use Symfony\Component\Security\Acl\Model\AclInterface;
use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
use Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface;
use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface;
/**
* An ACL implementation that is immutable based on data from a ObjectCollection of Propel\PropelBundle\Model\Acl\Entry.
*
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class Acl implements AclInterface
{
protected $model = 'Propel\PropelBundle\Model\Acl\Entry';
protected $classAces = array();
protected $classFieldAces = array();
protected $objectAces = array();
protected $objectFieldAces = array();
protected $objectIdentity;
protected $parentAcl;
protected $permissionGrantingStrategy;
protected $inherited;
protected $loadedSecurityIdentities = array();
/**
* A list of known associated fields on this ACL.
*
* @var array
*/
protected $fields = array();
/**
* Constructor.
*
* @param ObjectCollection $entries
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $objectIdentity
* @param \Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface $permissionGrantingStrategy
* @param array $loadedSecurityIdentities
* @param \Symfony\Component\Security\Acl\Model\AclInterface $parentAcl
* @param bool $inherited
*/
public function __construct(ObjectCollection $entries, ObjectIdentityInterface $objectIdentity, PermissionGrantingStrategyInterface $permissionGrantingStrategy, array $loadedSecurityIdentities = array(), AclInterface $parentAcl = null, $inherited = true)
{
if ($entries->getModel() !== $this->model) {
throw new AclException(sprintf('The given collection does not contain models of class "%s" but of class "%s".', $this->model, $entries->getModel()));
}
foreach ($entries as $eachEntry) {
if (null === $eachEntry->getFieldName() and null === $eachEntry->getObjectIdentityId()) {
$this->classAces[] = new Entry($eachEntry, $this);
}
if (null !== $eachEntry->getFieldName() and null === $eachEntry->getObjectIdentityId()) {
if (empty($this->classFieldAces[$eachEntry->getFieldName()])) {
$this->classFieldAces[$eachEntry->getFieldName()] = array();
$this->updateFields($eachEntry->getFieldName());
}
$this->classFieldAces[$eachEntry->getFieldName()][] = new FieldEntry($eachEntry, $this);
}
if (null === $eachEntry->getFieldName() and null !== $eachEntry->getObjectIdentityId()) {
$this->objectAces[] = new Entry($eachEntry, $this);
}
if (null !== $eachEntry->getFieldName() and null !== $eachEntry->getObjectIdentityId()) {
if (empty($this->objectFieldAces[$eachEntry->getFieldName()])) {
$this->objectFieldAces[$eachEntry->getFieldName()] = array();
$this->updateFields($eachEntry->getFieldName());
}
$this->objectFieldAces[$eachEntry->getFieldName()][] = new FieldEntry($eachEntry, $this);
}
}
$this->objectIdentity = $objectIdentity;
$this->permissionGrantingStrategy = $permissionGrantingStrategy;
$this->parentAcl = $parentAcl;
$this->inherited = $inherited;
$this->loadedSecurityIdentities = $loadedSecurityIdentities;
$this->fields = array_unique($this->fields);
}
/**
* Returns all class-based ACEs associated with this ACL
*
* @return array
*/
public function getClassAces()
{
return $this->classAces;
}
/**
* Returns all class-field-based ACEs associated with this ACL
*
* @param string $field
*
* @return array
*/
public function getClassFieldAces($field)
{
return isset($this->classFieldAces[$field]) ? $this->classFieldAces[$field] : array();
}
/**
* Returns all object-based ACEs associated with this ACL
*
* @return array
*/
public function getObjectAces()
{
return $this->objectAces;
}
/**
* Returns all object-field-based ACEs associated with this ACL
*
* @param string $field
*
* @return array
*/
public function getObjectFieldAces($field)
{
return isset($this->objectFieldAces[$field]) ? $this->objectFieldAces[$field] : array();
}
/**
* Returns the object identity associated with this ACL
*
* @return \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface
*/
public function getObjectIdentity()
{
return $this->objectIdentity;
}
/**
* Returns the parent ACL, or null if there is none.
*
* @return \Symfony\Component\Security\Acl\Model\AclInterface|null
*/
public function getParentAcl()
{
return $this->parentAcl;
}
/**
* Whether this ACL is inheriting ACEs from a parent ACL.
*
* @return bool
*/
public function isEntriesInheriting()
{
return $this->inherited;
}
/**
* Determines whether field access is granted
*
* @param string $field
* @param array $masks
* @param array $securityIdentities
* @param bool $administrativeMode
*
* @return bool
*/
public function isFieldGranted($field, array $masks, array $securityIdentities, $administrativeMode = false)
{
return $this->permissionGrantingStrategy->isFieldGranted($this, $field, $masks, $securityIdentities, $administrativeMode);
}
/**
* Determines whether access is granted
*
* @throws \Symfony\Component\Security\Acl\Exception\NoAceFoundException when no ACE was applicable for this request
*
* @param array $masks
* @param array $securityIdentities
* @param bool $administrativeMode
*
* @return bool
*/
public function isGranted(array $masks, array $securityIdentities, $administrativeMode = false)
{
return $this->permissionGrantingStrategy->isGranted($this, $masks, $securityIdentities, $administrativeMode);
}
/**
* Whether the ACL has loaded ACEs for all of the passed security identities
*
* @throws \InvalidArgumentException
*
* @param mixed $securityIdentities an implementation of SecurityIdentityInterface, or an array thereof
*
* @return bool
*/
public function isSidLoaded($securityIdentities)
{
if (!is_array($securityIdentities)) {
$securityIdentities = array($securityIdentities);
}
$found = 0;
foreach ($securityIdentities as $eachSecurityIdentity) {
if (!$eachSecurityIdentity instanceof SecurityIdentityInterface) {
throw new \InvalidArgumentException('At least one entry of the given list is not implementing the "SecurityIdentityInterface".');
}
foreach ($this->loadedSecurityIdentities as $eachLoadedIdentity) {
if ($eachSecurityIdentity->equals($eachLoadedIdentity)) {
$found++;
break;
}
}
}
return ($found === count($securityIdentities));
}
/**
* String representation of object
*
* @link http://php.net/manual/en/serializable.serialize.php
*
* @return string the string representation of the object or &null;
*/
public function serialize()
{
return serialize(array(
$this->model,
$this->classAces,
$this->classFieldAces,
$this->objectAces,
$this->objectFieldAces,
$this->objectIdentity,
$this->parentAcl,
$this->permissionGrantingStrategy,
$this->inherited,
$this->loadedSecurityIdentities,
));
}
/**
* Constructs the object
*
* @link http://php.net/manual/en/serializable.unserialize.php
*
* @param string $serialized
*
* @return mixed the original value unserialized.
*/
public function unserialize($serialized)
{
list(
$this->model,
$this->classAces,
$this->classFieldAces,
$this->objectAces,
$this->objectFieldAces,
$this->objectIdentity,
$this->parentAcl,
$this->permissionGrantingStrategy,
$this->inherited,
$this->loadedSecurityIdentities,
) = unserialize($serialized);
return $this;
}
/**
* Returns a list of associated fields on this ACL.
*
* @return array
*/
public function getFields()
{
return $this->fields;
}
/**
* Update the internal list of associated fields on this ACL.
*
* @param string $field
*
* @return \Propel\PropelBundle\Security\Acl\Domain\Acl $this
*/
protected function updateFields($field)
{
if (!in_array($field, $this->fields)) {
$this->fields[] = $field;
}
return $this;
}
}

View file

@ -0,0 +1,103 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Security\Acl\Domain;
use Propel\PropelBundle\Model\Acl\Entry as ModelEntry;
use Symfony\Component\Security\Acl\Model\AuditableAclInterface;
/**
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class AuditableAcl extends MutableAcl implements AuditableAclInterface
{
/**
* Updates auditing for class-based ACE
*
* @param integer $index
* @param bool $auditSuccess
* @param bool $auditFailure
*/
public function updateClassAuditing($index, $auditSuccess, $auditFailure)
{
$this->updateAuditing($this->classAces, $index, $auditSuccess, $auditFailure);
}
/**
* Updates auditing for class-field-based ACE
*
* @param integer $index
* @param string $field
* @param bool $auditSuccess
* @param bool $auditFailure
*/
public function updateClassFieldAuditing($index, $field, $auditSuccess, $auditFailure)
{
$this->validateField($this->classFieldAces, $field);
$this->updateAuditing($this->classFieldAces[$field], $index, $auditSuccess, $auditFailure);
}
/**
* Updates auditing for object-based ACE
*
* @param integer $index
* @param bool $auditSuccess
* @param bool $auditFailure
*/
public function updateObjectAuditing($index, $auditSuccess, $auditFailure)
{
$this->updateAuditing($this->objectAces, $index, $auditSuccess, $auditFailure);
}
/**
* Updates auditing for object-field-based ACE
*
* @param integer $index
* @param string $field
* @param bool $auditSuccess
* @param bool $auditFailure
*/
public function updateObjectFieldAuditing($index, $field, $auditSuccess, $auditFailure)
{
$this->validateField($this->objectFieldAces, $field);
$this->updateAuditing($this->objectFieldAces[$field], $index, $auditSuccess, $auditFailure);
}
/**
* Update auditing on a single ACE.
*
* @throws \InvalidArgumentException
*
* @param array $list
* @param int $index
* @param bool $auditSuccess
* @param bool $auditFailure
*
* @return \Propel\PropelBundle\Security\Acl\Domain\AuditableAcl $this
*/
protected function updateAuditing(array &$list, $index, $auditSuccess, $auditFailure)
{
if (!is_bool($auditSuccess) or !is_bool($auditFailure)) {
throw new \InvalidArgumentException('The given auditing flags are invalid. Please provide boolean only.');
}
$this->validateIndex($list, $index);
$entry = ModelEntry::fromAclEntry($list[$index])
->setAuditSuccess($auditSuccess)
->setAuditFailure($auditFailure)
;
$list[$index] = ModelEntry::toAclEntry($entry, $this);
return $this;
}
}

View file

@ -0,0 +1,192 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Security\Acl\Domain;
use Propel\PropelBundle\Model\Acl\Entry as ModelEntry;
use Propel\PropelBundle\Model\Acl\SecurityIdentity;
use Symfony\Component\Security\Acl\Model\AclInterface;
use Symfony\Component\Security\Acl\Model\AuditableEntryInterface;
use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface;
/**
* An ACE implementation retrieving data from a given Propel\PropelBundle\Model\Acl\Entry.
*
* The entry is only used to grab a "snapshot" of its data as an EntryInterface is immutable!
*
* @see \Symfony\Component\Security\Acl\Model\EntryInterface
*
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class Entry implements AuditableEntryInterface
{
protected $acl;
protected $id;
protected $securityIdentity;
protected $mask;
protected $isGranting;
protected $strategy;
protected $auditSuccess;
protected $auditFailure;
/**
* Constructor.
*
* @param \Propel\PropelBundle\Model\Acl\Entry $entry
* @param \Symfony\Component\Security\Acl\Model\AclInterface $acl
*/
public function __construct(ModelEntry $entry, AclInterface $acl)
{
$this->acl = $acl;
$this->securityIdentity = SecurityIdentity::toAclIdentity($entry->getSecurityIdentity());
/*
* A new ACE (from a MutableAcl) does not have an ID,
* but will be persisted by the MutableAclProvider afterwards, if issued.
*/
if ($entry->getId()) {
$this->id = $entry->getId();
}
$this->mask = $entry->getMask();
$this->isGranting = $entry->getGranting();
$this->strategy = $entry->getGrantingStrategy();
$this->auditFailure = $entry->getAuditFailure();
$this->auditSuccess = $entry->getAuditSuccess();
}
/**
* String representation of object
*
* @link http://php.net/manual/en/serializable.serialize.php
*
* @return string the string representation of the object or &null;
*/
public function serialize()
{
return serialize(array(
$this->acl,
$this->securityIdentity,
$this->id,
$this->mask,
$this->isGranting,
$this->strategy,
$this->auditFailure,
$this->auditSuccess,
));
}
/**
* Constructs the object
*
* @link http://php.net/manual/en/serializable.unserialize.php
*
* @param string $serialized
*
* @return mixed the original value unserialized.
*/
public function unserialize($serialized)
{
list(
$this->acl,
$this->securityIdentity,
$this->id,
$this->mask,
$this->isGranting,
$this->strategy,
$this->auditFailure,
$this->auditSuccess,
) = unserialize($serialized);
return $this;
}
/**
* The ACL this ACE is associated with.
*
* @return \Symfony\Component\Security\Acl\Model\AclInterface
*/
public function getAcl()
{
return $this->acl;
}
/**
* The security identity associated with this ACE
*
* @return \Symfony\Component\Security\Acl\Model\SecurityIdentityInterface
*/
public function getSecurityIdentity()
{
return $this->securityIdentity;
}
/**
* The primary key of this ACE
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* The permission mask of this ACE
*
* @return integer
*/
public function getMask()
{
return $this->mask;
}
/**
* The strategy for comparing masks
*
* @return string
*/
public function getStrategy()
{
return $this->strategy;
}
/**
* Returns whether this ACE is granting, or denying
*
* @return bool
*/
public function isGranting()
{
return $this->isGranting;
}
/**
* Whether auditing for successful grants is turned on
*
* @return bool
*/
public function isAuditFailure()
{
return $this->auditFailure;
}
/**
* Whether auditing for successful denies is turned on
*
* @return bool
*/
public function isAuditSuccess()
{
return $this->auditSuccess;
}
}

View file

@ -0,0 +1,101 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Security\Acl\Domain;
use Propel\PropelBundle\Model\Acl\Entry as ModelEntry;
use Symfony\Component\Security\Acl\Model\AclInterface;
use Symfony\Component\Security\Acl\Model\FieldEntryInterface;
/**
* An ACE implementation retrieving data from a given \Propel\PropelBundle\Model\Acl\Entry.
*
* The entry is only used to grab a "snapshot" of its data as an \Symfony\Component\Security\Acl\Model\EntryInterface is immutable!
*
* @see \Symfony\Component\Security\Acl\Model\EntryInterface
*
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class FieldEntry extends Entry implements FieldEntryInterface
{
protected $field;
/**
* Constructor.
*
* @param \Propel\PropelBundle\Model\Acl\Entry $entry
* @param \Symfony\Component\Security\Acl\Model\AclInterface $acl
*/
public function __construct(ModelEntry $entry, AclInterface $acl)
{
$this->field = $entry->getFieldName();
parent::__construct($entry, $acl);
}
/**
* Returns the field used for this entry.
*
* @return string
*/
public function getField()
{
return $this->field;
}
/**
* String representation of object
*
* @link http://php.net/manual/en/serializable.serialize.php
*
* @return string the string representation of the object or &null;
*/
public function serialize()
{
return serialize(array(
$this->acl,
$this->securityIdentity,
$this->id,
$this->mask,
$this->isGranting,
$this->strategy,
$this->auditFailure,
$this->auditSuccess,
$this->field,
));
}
/**
* Constructs the object
*
* @link http://php.net/manual/en/serializable.unserialize.php
*
* @param string $serialized
*
* @return mixed the original value unserialized.
*/
public function unserialize($serialized)
{
list(
$this->acl,
$this->securityIdentity,
$this->id,
$this->mask,
$this->isGranting,
$this->strategy,
$this->auditFailure,
$this->auditSuccess,
$this->field,
) = unserialize($serialized);
return $this;
}
}

View file

@ -0,0 +1,531 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Security\Acl\Domain;
use Propel\Runtime\Collection\ObjectCollection;
use Propel\Runtime\Connection\ConnectionInterface;
use Propel\PropelBundle\Model\Acl\Entry as ModelEntry;
use Propel\PropelBundle\Model\Acl\SecurityIdentity;
use Propel\PropelBundle\Model\Acl\ObjectIdentity;
use Propel\PropelBundle\Model\Acl\ObjectIdentityQuery;
use Symfony\Component\Security\Acl\Domain\PermissionGrantingStrategy;
use Symfony\Component\Security\Acl\Model\AclInterface;
use Symfony\Component\Security\Acl\Model\MutableAclInterface;
use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
use Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface;
use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface;
/**
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class MutableAcl extends Acl implements MutableAclInterface
{
/**
* The id of the current ACL.
*
* It's the id of the ObjectIdentity model.
*
* @var int
*/
protected $id;
/**
* A reference to the ObjectIdentity this ACL is mapped to.
*
* @var \Propel\PropelBundle\Model\Acl\ObjectIdentity
*/
protected $modelObjectIdentity;
/**
* A connection to be used for all changes on the ACL.
*
* @var ConnectionInterface
*/
protected $con;
/**
* Constructor.
*
* @param ObjectCollection $entries
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $objectIdentity
* @param \Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface $permissionGrantingStrategy
* @param array $loadedSecurityIdentities
* @param \Symfony\Component\Security\Acl\Model\AclInterface $parentAcl
* @param bool $inherited
* @param ConnectionInterface $con
*/
public function __construct(ObjectCollection $entries, ObjectIdentityInterface $objectIdentity, PermissionGrantingStrategyInterface $permissionGrantingStrategy, array $loadedSecurityIdentities = array(), AclInterface $parentAcl = null, $inherited = true, ConnectionInterface $con = null)
{
parent::__construct($entries, $objectIdentity, $permissionGrantingStrategy, $loadedSecurityIdentities, $parentAcl, $inherited);
$this->modelObjectIdentity = ObjectIdentityQuery::create()
->filterByAclObjectIdentity($objectIdentity, $con)
->findOneOrCreate($con)
;
if ($this->modelObjectIdentity->isNew()) {
$this->modelObjectIdentity->save($con);
}
$this->id = $this->modelObjectIdentity->getId();
$this->con = $con;
}
/**
* Returns the primary key of this ACL
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Sets whether entries are inherited
*
* @param bool $boolean
*/
public function setEntriesInheriting($boolean)
{
$this->inherited = $boolean;
}
/**
* Sets the parent ACL
*
* @param \Symfony\Component\Security\Acl\Model\AclInterface|null $acl
*/
public function setParentAcl(AclInterface $acl = null)
{
$this->parentAcl = $acl;
}
/**
* Deletes a class-based ACE
*
* @param integer $index
*/
public function deleteClassAce($index)
{
$this->deleteIndex($this->classAces, $index);
}
/**
* Deletes a class-field-based ACE
*
* @param integer $index
* @param string $field
*/
public function deleteClassFieldAce($index, $field)
{
$this
->validateField($this->classFieldAces, $field)
->deleteIndex($this->classFieldAces[$field], $index)
;
}
/**
* Deletes an object-based ACE
*
* @param integer $index
*/
public function deleteObjectAce($index)
{
$this->deleteIndex($this->objectAces, $index);
}
/**
* Deletes an object-field-based ACE
*
* @param integer $index
* @param string $field
*/
public function deleteObjectFieldAce($index, $field)
{
$this
->validateField($this->objectFieldAces, $field)
->deleteIndex($this->objectFieldAces[$field], $index)
;
}
/**
* Inserts a class-based ACE
*
* @param \Symfony\Component\Security\Acl\Model\SecurityIdentityInterface $securityIdentity
* @param integer $mask
* @param integer $index
* @param bool $granting
* @param string $strategy
*/
public function insertClassAce(SecurityIdentityInterface $securityIdentity, $mask, $index = 0, $granting = true, $strategy = null)
{
$this->insertToList($this->classAces, $index, $this->createAce($mask, $index, $securityIdentity, $strategy, $granting));
}
/**
* Inserts a class-field-based ACE
*
* @param string $field
* @param \Symfony\Component\Security\Acl\Model\SecurityIdentityInterface $securityIdentity
* @param integer $mask
* @param integer $index
* @param boolean $granting
* @param string $strategy
*/
public function insertClassFieldAce($field, SecurityIdentityInterface $securityIdentity, $mask, $index = 0, $granting = true, $strategy = null)
{
if (!isset($this->classFieldAces[$field])) {
$this->classFieldAces[$field] = array();
}
$this->insertToList($this->classFieldAces[$field], $index, $this->createAce($mask, $index, $securityIdentity, $strategy, $granting, $field));
}
/**
* Inserts an object-based ACE
*
* @param \Symfony\Component\Security\Acl\Model\SecurityIdentityInterface $securityIdentity
* @param integer $mask
* @param integer $index
* @param boolean $granting
* @param string $strategy
*/
public function insertObjectAce(SecurityIdentityInterface $securityIdentity, $mask, $index = 0, $granting = true, $strategy = null)
{
$this->insertToList($this->objectAces, $index, $this->createAce($mask, $index, $securityIdentity, $strategy, $granting));
}
/**
* Inserts an object-field-based ACE
*
* @param string $field
* @param \Symfony\Component\Security\Acl\Model\SecurityIdentityInterface $securityIdentity
* @param integer $mask
* @param integer $index
* @param boolean $granting
* @param string $strategy
*/
public function insertObjectFieldAce($field, SecurityIdentityInterface $securityIdentity, $mask, $index = 0, $granting = true, $strategy = null)
{
if (!isset($this->objectFieldAces[$field])) {
$this->objectFieldAces[$field] = array();
}
$this->insertToList($this->objectFieldAces[$field], $index, $this->createAce($mask, $index, $securityIdentity, $strategy, $granting, $field));
}
/**
* Updates a class-based ACE
*
* @param integer $index
* @param integer $mask
* @param string $strategy if null the strategy should not be changed
*/
public function updateClassAce($index, $mask, $strategy = null)
{
$this->updateAce($this->classAces, $index, $mask, $strategy);
}
/**
* Updates a class-field-based ACE
*
* @param integer $index
* @param string $field
* @param integer $mask
* @param string $strategy if null the strategy should not be changed
*/
public function updateClassFieldAce($index, $field, $mask, $strategy = null)
{
$this
->validateField($this->classFieldAces, $field)
->updateAce($this->classFieldAces[$field], $index, $mask, $strategy)
;
}
/**
* Updates an object-based ACE
*
* @param integer $index
* @param integer $mask
* @param string $strategy if null the strategy should not be changed
*/
public function updateObjectAce($index, $mask, $strategy = null)
{
$this->updateAce($this->objectAces, $index, $mask, $strategy);
}
/**
* Updates an object-field-based ACE
*
* @param integer $index
* @param string $field
* @param integer $mask
* @param string $strategy if null the strategy should not be changed
*/
public function updateObjectFieldAce($index, $field, $mask, $strategy = null)
{
$this->validateField($this->objectFieldAces, $field);
$this->updateAce($this->objectFieldAces[$field], $index, $mask, $strategy);
}
/**
* String representation of object
*
* @link http://php.net/manual/en/serializable.serialize.php
*
* @return string the string representation of the object or &null;
*/
public function serialize()
{
return serialize(array(
$this->id,
$this->modelObjectIdentity,
$this->model,
$this->classAces,
$this->classFieldAces,
$this->objectAces,
$this->objectFieldAces,
$this->objectIdentity,
$this->parentAcl,
$this->permissionGrantingStrategy,
$this->inherited,
$this->loadedSecurityIdentities,
));
}
/**
* Constructs the object
*
* @link http://php.net/manual/en/serializable.unserialize.php
*
* @param string $serialized
*
* @return mixed the original value unserialized.
*/
public function unserialize($serialized)
{
list(
$this->id,
$this->modelObjectIdentity,
$this->model,
$this->classAces,
$this->classFieldAces,
$this->objectAces,
$this->objectFieldAces,
$this->objectIdentity,
$this->parentAcl,
$this->permissionGrantingStrategy,
$this->inherited,
$this->loadedSecurityIdentities,
) = unserialize($serialized);
return $this;
}
/**
* Insert a given entry into the list on the given index by shifting all others.
*
* @param array $list
* @param int $index
* @param \Propel\PropelBundle\Model\Acl\Entry\Entry $entry
*
* @return \Propel\PropelBundle\Security\Acl\Domain\MutableAcl $this
*/
protected function insertToList(array &$list, $index, Entry $entry)
{
$this->isWithinBounds($list, $index);
if ($entry instanceof FieldEntry) {
$this->updateFields($entry->getField());
}
$list = array_merge(
array_slice($list, 0, $index),
array($entry),
array_splice($list, $index)
);
return $this;
}
/**
* Update a single ACE of this ACL.
*
* @param array $list
* @param int $index
* @param int $mask
* @param string $strategy
* @param string $field
*
* @return \Propel\PropelBundle\Security\Acl\Domain\MutableAcl $this
*/
protected function updateAce(array &$list, $index, $mask, $strategy = null)
{
$this->validateIndex($list, $index);
$entry = ModelEntry::fromAclEntry($list[$index]);
// Apply updates
$entry->setMask($mask);
if (null !== $strategy) {
$entry->setGrantingStrategy($strategy);
}
$list[$index] = ModelEntry::toAclEntry($entry, $this);
return $this;
}
/**
* Delete the ACE of the given list and index.
*
* The list will be re-ordered to have a valid 0..x list.
*
* @param array $list
* @param $index
*
* @return \Propel\PropelBundle\Security\Acl\Domain\MutableAcl $this
*/
protected function deleteIndex(array &$list, $index)
{
$this->validateIndex($list, $index);
unset($list[$index]);
$this->reorderList($list, $index-1);
return $this;
}
/**
* Validate the index on the given list of ACEs.
*
* @throws \OutOfBoundsException
*
* @param array $list
* @param int $index
*
* @return \Propel\PropelBundle\Security\Acl\Domain\MutableAcl $this
*/
protected function isWithinBounds(array &$list, $index)
{
// No count()-1, the count is one ahead of index, and could create the next valid entry!
if ($index < 0 or $index > count($list)) {
throw new \OutOfBoundsException(sprintf('The index must be in the interval [0, %d].', count($list)));
}
return $this;
}
/**
* Check the index for existence in the given list.
*
* @throws \OutOfBoundsException
*
* @param array $list
* @param $index
*
* @return \Propel\PropelBundle\Security\Acl\Domain\MutableAcl $this
*/
protected function validateIndex(array &$list, $index)
{
if (!isset($list[$index])) {
throw new \OutOfBoundsException(sprintf('The index "%d" does not exist.', $index));
}
return $this;
}
/**
* Validate the given field to be present.
*
* @throws \InvalidArgumentException
*
* @param array $list
* @param string $field
*
* @return \Propel\PropelBundle\Security\Acl\Domain\MutableAcl $this
*/
protected function validateField(array &$list, $field)
{
if (!isset($list[$field])) {
throw new \InvalidArgumentException(sprintf('The given field "%s" does not exist.', $field));
}
return $this;
}
/**
* Order the given list to have numeric indexes from 0..x
*
* @param array $list
* @param int $index The right boundary to which the list is valid.
*
* @return \Propel\PropelBundle\Security\Acl\Domain\MutableAcl $this
*/
protected function reorderList(array &$list, $index)
{
$list = array_merge(
array_slice($list, 0, $index+1), // +1 to get length
array_splice($list, $index+1) // +1 to get first index to re-order
);
return $this;
}
/**
* Create a new ACL Entry.
*
* @param int $mask
* @param int $index
* @param \Symfony\Component\Security\Acl\Model\SecurityIdentityInterface $securityIdentity
* @param string $strategy
* @param bool $granting
* @param string $field
*
* @return \Propel\PropelBundle\Security\Acl\Domain\Entry|\Propel\PropelBundle\Security\Acl\Domain\FieldEntry
*/
protected function createAce($mask, $index, SecurityIdentityInterface $securityIdentity, $strategy = null, $granting = true, $field = null)
{
if (!is_int($mask)) {
throw new \InvalidArgumentException('The given mask is not valid. Please provide an integer.');
}
// Compatibility with default implementation
if (null === $strategy) {
if (true === $granting) {
$strategy = PermissionGrantingStrategy::ALL;
} else {
$strategy = PermissionGrantingStrategy::ANY;
}
}
$model = new ModelEntry();
$model
->setAceOrder($index)
->setMask($mask)
->setGrantingStrategy($strategy)
->setGranting($granting)
->setSecurityIdentity(SecurityIdentity::fromAclIdentity($securityIdentity))
;
if (null !== $field) {
$model->setFieldName($field);
return new FieldEntry($model, $this);
}
return new Entry($model, $this);
}
}

View file

@ -0,0 +1,340 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Security\Acl;
use Propel\Runtime\ActiveQuery\Criteria;
use Propel\Runtime\Collection\ObjectCollection;
use Propel\Runtime\Connection\ConnectionInterface;
use Propel\Runtime\Propel;
use Propel\Runtime\ServiceContainer\ServiceContainerInterface;
use Propel\PropelBundle\Model\Acl\Entry as ModelEntry;
use Propel\PropelBundle\Model\Acl\Map\EntryTableMap;
use Propel\PropelBundle\Model\Acl\EntryQuery;
use Propel\PropelBundle\Model\Acl\SecurityIdentity;
use Propel\PropelBundle\Model\Acl\ObjectIdentity;
use Propel\PropelBundle\Model\Acl\ObjectIdentityQuery;
use Propel\PropelBundle\Security\Acl\Domain\Acl;
use Propel\PropelBundle\Security\Acl\Domain\MutableAcl;
use Propel\PropelBundle\Security\Acl\Domain\Entry;
use Symfony\Component\Security\Acl\Exception\AclAlreadyExistsException;
use Symfony\Component\Security\Acl\Exception\Exception as AclException;
use Symfony\Component\Security\Acl\Model\AclInterface;
use Symfony\Component\Security\Acl\Model\EntryInterface;
use Symfony\Component\Security\Acl\Model\FieldEntryInterface;
use Symfony\Component\Security\Acl\Model\AuditableEntryInterface;
use Symfony\Component\Security\Acl\Model\AclCacheInterface;
use Symfony\Component\Security\Acl\Model\MutableAclInterface;
use Symfony\Component\Security\Acl\Model\MutableAclProviderInterface;
use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
use Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface;
/**
* An implementation of the MutableAclProviderInterface using Propel ORM.
*
* @author Toni Uebernickel <tuebernickel@gmail.com>
*/
class MutableAclProvider extends AclProvider implements MutableAclProviderInterface
{
/**
* Constructor.
*
* @param \Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface $permissionGrantingStrategy
* @param ConnectionInterface $connection
* @param \Symfony\Component\Security\Acl\Model\AclCacheInterface $cache
*/
public function __construct(PermissionGrantingStrategyInterface $permissionGrantingStrategy, ConnectionInterface $connection = null, AclCacheInterface $cache = null)
{
// @codeCoverageIgnoreStart
if (null === $connection) {
$connection = Propel::getConnection(EntryTableMap::DATABASE_NAME, ServiceContainerInterface::CONNECTION_WRITE);
}
// @codeCoverageIgnoreEnd
parent::__construct($permissionGrantingStrategy, $connection, $cache);
}
/**
* Creates a new ACL for the given object identity.
*
* @throws \Symfony\Component\Security\Acl\Exception\AclAlreadyExistsException When there already is an ACL for the given object identity.
*
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $objectIdentity
*
* @return \Propel\PropelBundle\Security\Acl\Domain\MutableAcl
*/
public function createAcl(ObjectIdentityInterface $objectIdentity)
{
$entries = EntryQuery::create()->findByAclIdentity($objectIdentity, array(), $this->connection);
if (count($entries)) {
throw new AclAlreadyExistsException('An ACL for the given object identity already exists, find and update that one.');
}
$objIdentity = ObjectIdentityQuery::create()
->filterByAclObjectIdentity($objectIdentity, $this->connection)
->findOneOrCreate($this->connection)
;
if ($objIdentity->isNew()) {
// This is safe to do, it makes the ID available and does not affect changes to any ACL.
$objIdentity->save($this->connection);
}
return $this->getAcl($entries, $objectIdentity, array(), null, false);
}
/**
* Deletes the ACL for a given object identity.
*
* This will automatically trigger a delete for any child ACLs. If you don't
* want child ACLs to be deleted, you will have to set their parent ACL to null.
*
* @throws \Symfony\Component\Security\Acl\Exception\Exception
*
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $objectIdentity
*
* @return bool
*/
public function deleteAcl(ObjectIdentityInterface $objectIdentity)
{
try {
$objIdentity = ObjectIdentityQuery::create()->findOneByAclObjectIdentity($objectIdentity, $this->connection);
if (null === $objIdentity) {
// No object identity, no ACL, so deletion is successful (expected result is given).
return true;
}
$this->connection->beginTransaction();
// Retrieve all class and class-field ACEs, if any.
$aces = EntryQuery::create()->findByAclIdentity($objectIdentity, array(), $this->connection);
if (count($aces)) {
// In case this is the last of its kind, delete the class and class-field ACEs.
$count = ObjectIdentityQuery::create()->filterByClassId($objIdentity->getClassId())->count($this->connection);
if (1 === $count) {
$aces->delete($this->connection);
}
}
/*
* If caching is enabled, retrieve the (grand-)children of this ACL.
* Those will be removed from the cache as well, as their parents do not exist anymore.
*/
if (null !== $this->cache) {
$children = ObjectIdentityQuery::create()->findGrandChildren($objIdentity, $this->connection);
}
// This deletes all object and object-field ACEs, too.
$objIdentity->delete($this->connection);
$this->connection->commit();
if (null !== $this->cache) {
$this->cache->evictFromCacheById($objIdentity->getId());
foreach ($children as $eachChild) {
$this->cache->evictFromCacheById($eachChild->getId());
}
}
return true;
// @codeCoverageIgnoreStart
} catch (Exception $e) {
throw new AclException('An error occurred while deleting the ACL.', 1, $e);
}
// @codeCoverageIgnoreEnd
}
/**
* Persists any changes which were made to the ACL, or any associated access control entries.
*
* Changes to parent ACLs are not persisted.
*
* @throws \Symfony\Component\Security\Acl\Exception\Exception
*
* @param \Symfony\Component\Security\Acl\Model\MutableAclInterface $acl
*
* @return bool
*/
public function updateAcl(MutableAclInterface $acl)
{
if (!$acl instanceof MutableAcl) {
throw new \InvalidArgumentException('The given ACL is not tracked by this provider. Please provide \Propel\PropelBundle\Security\Acl\Domain\MutableAcl only.');
}
try {
$modelEntries = EntryQuery::create()->findByAclIdentity($acl->getObjectIdentity(), array(), $this->connection);
$objectIdentity = ObjectIdentityQuery::create()->findOneByAclObjectIdentity($acl->getObjectIdentity(), $this->connection);
$this->connection->beginTransaction();
$keepEntries = array_merge(
$this->persistAcl($acl->getClassAces(), $objectIdentity),
$this->persistAcl($acl->getObjectAces(), $objectIdentity, true)
);
foreach ($acl->getFields() as $eachField) {
$keepEntries = array_merge($keepEntries,
$this->persistAcl($acl->getClassFieldAces($eachField), $objectIdentity),
$this->persistAcl($acl->getObjectFieldAces($eachField), $objectIdentity, true)
);
}
foreach ($modelEntries as &$eachEntry) {
if (!in_array($eachEntry->getId(), $keepEntries)) {
$eachEntry->delete($this->connection);
}
}
if (null === $acl->getParentAcl()) {
$objectIdentity
->setParentObjectIdentityId(null)
->save($this->connection)
;
} else {
$objectIdentity
->setParentObjectIdentityId($acl->getParentAcl()->getId())
->save($this->connection)
;
}
$this->connection->commit();
// After successfully committing the transaction, we are good to update the cache.
if (null !== $this->cache) {
$this->cache->evictFromCacheById($objectIdentity->getId());
$this->cache->putInCache($acl);
}
return true;
// @codeCoverageIgnoreStart
} catch (Exception $e) {
$this->connection->rollBack();
throw new AclException('An error occurred while updating the ACL.', 0, $e);
}
// @codeCoverageIgnoreEnd
}
/**
* Persist the given ACEs.
*
* @param array $accessControlEntries
* @param \Propel\PropelBundle\Model\Acl\ObjectIdentity $objectIdentity
* @param bool $object
*
* @return array The IDs of the persisted ACEs.
*/
protected function persistAcl(array $accessControlEntries, ObjectIdentity $objectIdentity, $object = false)
{
$entries = array();
/* @var $eachAce \Symfony\Component\Security\Acl\Model\EntryInterface */
foreach ($accessControlEntries as $order => $eachAce) {
// If the given ACE has never been persisted, create a new one.
if (null === $entry = $this->getPersistedAce($eachAce, $objectIdentity, $object)) {
$entry = ModelEntry::fromAclEntry($eachAce);
}
if (in_array($entry->getId(), $entries)) {
$entry = ModelEntry::fromAclEntry($eachAce);
}
// Apply possible changes from local ACE.
$entry
->setAceOrder($order)
->setAclClass($objectIdentity->getAclClass())
->setMask($eachAce->getMask())
;
if ($eachAce instanceof AuditableEntryInterface) {
if (is_bool($eachAce->isAuditSuccess())) {
$entry->setAuditSuccess($eachAce->isAuditSuccess());
}
if (is_bool($eachAce->isAuditFailure())) {
$entry->setAuditFailure($eachAce->isAuditFailure());
}
}
if (true === $object) {
$entry->setObjectIdentity($objectIdentity);
}
$entry->save($this->connection);
$entries[] = $entry->getId();
}
return $entries;
}
/**
* Retrieve the persisted model for the given ACE.
*
* If none is given, null is returned.
*
* @param \Symfony\Component\Security\Acl\Model\EntryInterface $ace
*
* @return \Propel\PropelBundle\Model\Acl\Entry|null
*/
protected function getPersistedAce(EntryInterface $ace, ObjectIdentity $objectIdentity, $object = false)
{
if (null !== $ace->getId() and null !== $entry = EntryQuery::create()->findPk($ace->getId(), $this->connection)) {
$entry->reload(true, $this->connection);
return $entry;
}
/*
* The id is not set, but there may be an ACE in the database.
*
* This happens if the ACL has created new ACEs, but was not reloaded.
* We try to retrieve one by the unique key.
*/
$ukQuery = EntryQuery::create()
->filterByAclClass($objectIdentity->getAclClass($this->connection))
->filterBySecurityIdentity(SecurityIdentity::fromAclIdentity($ace->getSecurityIdentity(), $this->connection))
;
if (true === $object) {
$ukQuery->filterByObjectIdentity($objectIdentity);
} else {
$ukQuery->filterByObjectIdentityId(null, Criteria::ISNULL);
}
if ($ace instanceof FieldEntryInterface) {
$ukQuery->filterByFieldName($ace->getField());
} else {
$ukQuery->filterByFieldName(null, Criteria::ISNULL);
}
return $ukQuery->findOne($this->connection);
}
/**
* Get an ACL for this provider.
*
* @param ObjectCollection $collection
* @param \Symfony\Component\Security\Acl\Model\ObjectIdentityInterface $objectIdentity
* @param array $loadedSecurityIdentities
* @param \Symfony\Component\Security\Acl\Model\AclInterface $parentAcl
* @param bool $inherited
*
* @return \Propel\PropelBundle\Security\Acl\Domain\MutableAcl
*/
protected function getAcl(ObjectCollection $collection, ObjectIdentityInterface $objectIdentity, array $loadedSecurityIdentities = array(), AclInterface $parentAcl = null, $inherited = true)
{
return new MutableAcl($collection, $objectIdentity, $this->permissionGrantingStrategy, $loadedSecurityIdentities, $parentAcl, $inherited, $this->connection);
}
}

View file

@ -0,0 +1,103 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Security\User;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
/**
* Provides easy to use provisioning for Propel model users.
*
* @author William DURAND <william.durand1@gmail.com>
*/
class PropelUserProvider implements UserProviderInterface
{
/**
* A Model class name.
*
* @var string
*/
protected $class;
/**
* A Query class name.
*
* @var string
*/
protected $queryClass;
/**
* A property to use to retrieve the user.
*
* @var string
*/
protected $property;
/**
* Default constructor
*
* @param string $class The User model class.
* @param string|null $property The property to use to retrieve a user.
*/
public function __construct($class, $property = null)
{
$this->class = $class;
$this->queryClass = $class.'Query';
$this->property = $property;
}
/**
* {@inheritdoc}
*/
public function loadUserByUsername($username)
{
$queryClass = $this->queryClass;
$query = $queryClass::create();
if (null !== $this->property) {
$filter = 'filterBy'.ucfirst($this->property);
$query->$filter($username);
} else {
$query->filterByUsername($username);
}
if (null === $user = $query->findOne()) {
throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username));
}
return $user;
}
/**
* {@inheritdoc}
*/
public function refreshUser(UserInterface $user)
{
if (!$user instanceof $this->class) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
}
$queryClass = $this->queryClass;
return $queryClass::create()->findPk($user->getPrimaryKey());
}
/**
* {@inheritdoc}
*/
public function supportsClass($class)
{
return $class === $this->class;
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace Propel\PropelBundle\Tests\Fixtures\Model;
use Propel\PropelBundle\Tests\Fixtures\Model\Base\User as BaseUser;
use Symfony\Component\Security\Core\User\UserInterface;
class User extends BaseUser implements UserInterface
{
public function eraseCredentials()
{
}
}

View file

@ -0,0 +1,63 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Tests\Security\User;
use Propel\PropelBundle\Security\User\PropelUserProvider;
use Propel\PropelBundle\Tests\Fixtures\Model\User;
use Propel\PropelBundle\Tests\TestCase;
/**
* @author William Durand <william.durand1@gmail.com>
*/
class PropelUserProviderTest extends TestCase
{
public function setUp()
{
$schema = <<<SCHEMA
<database name="users" defaultIdMethod="native" namespace="Propel\\PropelBundle\\Tests\\Fixtures\\Model">
<table name="user">
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" />
<column name="username" type="varchar" size="255" primaryString="true" />
<column name="algorithm" type="varchar" size="50" />
<column name="salt" type="varchar" size="255" />
<column name="password" type="varchar" size="255" />
<column name="expires_at" type="timestamp" />
<column name="roles" type="array" />
</table>
</database>
SCHEMA;
$builder = new QuickBuilder();
$builder->setSchema($schema);
$classTargets = null;
$this->con = $builder->build($dsn = null, $user = null, $pass = null, $adapter = null, $classTargets);
}
public function testRefreshUserGetsUserByPrimaryKey()
{
$user1 = new User();
$user1->setUsername('user1');
$user1->save();
$user2 = new User();
$user2->setUsername('user2');
$user2->save();
$provider = new PropelUserProvider('Propel\PropelBundle\Tests\Fixtures\Model\User', 'username');
// try to change the user identity
$user1->setUsername('user2');
$resultUser = $provider->refreshUser($user1);
$this->assertSame($user1, $resultUser);
}
}