added ACL system to the Security Component

This commit is contained in:
Johannes Schmitt 2010-12-14 16:43:40 +01:00 committed by Fabien Potencier
parent 5c73619d80
commit b4288459cc
64 changed files with 7674 additions and 2 deletions

View File

@ -38,6 +38,9 @@
<!-- security/user -->
<parameter key="security.user.provider.entity.class">Symfony\Bundle\DoctrineBundle\Security\EntityUserProvider</parameter>
<!-- security/acl -->
<parameter key="security.acl.collection_cache.class">Symfony\Bundle\DoctrineBundle\Security\AclCollectionCache</parameter>
</parameters>
<services>

View File

@ -0,0 +1,58 @@
<?php
namespace Symfony\Bundle\DoctrineBundle\Security;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\Security\Acl\Model\AclProviderInterface;
use Symfony\Component\Security\Acl\Model\ObjectIdentityRetrievalStrategyInterface;
use Symfony\Component\Security\Acl\Model\SecurityIdentityRetrievalStrategyInterface;
/**
* This service caches ACLs for an entire collection
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class AclCollectionCache
{
protected $aclProvider;
protected $objectIdentityRetrievalStrategy;
protected $securityIdentityRetrievalStrategy;
/**
* Constructor
*
* @param AclProviderInterface $aclProvider
* @param ObjectIdentityRetrievalStrategy $oidRetrievalStrategy
* @param SecurityIdentityRetrievalStrategy $sidRetrievalStrategy
* @return void
*/
public function __construct(AclProviderInterface $aclProvider, ObjectIdentityRetrievalStrategyInterface $oidRetrievalStrategy, SecurityIdentityRetrievalStrategyInterface $sidRetrievalStrategy)
{
$this->aclProvider = $aclProvider;
$this->objectIdentityRetrievalStrategy = $oidRetrievalStrategy;
$this->securityIdentityRetrievalStrategy = $sidRetrievalStrategy;
}
/**
* Batch loads ACLs for an entire collection; thus, it reduces the number
* of required queries considerably.
*
* @param Collection $collection
* @param array $tokens an array of TokenInterface implementations
* @return void
*/
public function cache(Collection $collection, array $tokens = array())
{
$sids = array();
foreach ($tokens as $token) {
$sids = array_merge($sids, $this->securityIdentityRetrievalStrategy->getSecurityIdentities($token));
}
$oids = array();
foreach ($collection as $domainObject) {
$oids[] = $this->objectIdentityRetrievalStrategy->getObjectIdentity($domainObject);
}
$this->aclProvider->findAcls($oids, $sids);
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Security\Acl\Dbal\Schema;
use Doctrine\DBAL\DriverManager;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* Installs the tables required by the ACL system
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class InitAclCommand extends Command
{
/**
* @see Command
*/
protected function configure()
{
$this
->setName('init:acl')
;
}
/**
* @see Command
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$connection = $this->container->get('security.acl.dbal.connection');
$sm = $connection->getSchemaManager();
$tableNames = $sm->listTableNames();
$tables = array(
'class_table_name' => $this->container->getParameter('security.acl.dbal.class_table_name'),
'sid_table_name' => $this->container->getParameter('security.acl.dbal.sid_table_name'),
'oid_table_name' => $this->container->getParameter('security.acl.dbal.oid_table_name'),
'oid_ancestors_table_name' => $this->container->getParameter('security.acl.dbal.oid_ancestors_table_name'),
'entry_table_name' => $this->container->getParameter('security.acl.dbal.entry_table_name'),
);
foreach ($tables as $table) {
if (in_array($table, $tableNames, true)) {
$output->writeln(sprintf('The table "%s" already exists. Aborting.', $table));
return;
}
}
$schema = new Schema($tables);
foreach ($schema->toSql($connection->getDatabasePlatform()) as $sql) {
$connection->exec($sql);
}
$output->writeln('ACL tables have been initialized successfully.');
}
}

View File

@ -2,6 +2,7 @@
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Resource\FileResource;
@ -487,6 +488,25 @@ class SecurityExtension extends Extension
return $switchUserListenerId;
}
public function aclLoad(array $config, ContainerBuilder $container)
{
if (!$container->hasDefinition('security.acl')) {
$loader = new XmlFileLoader($container, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config'));
$loader->load('security_acl.xml');
}
if (isset($config['connection'])) {
$container->setAlias(sprintf('doctrine.dbal.%s_connection', $config['connection']), 'security.acl.dbal.connection');
}
if (isset($config['cache'])) {
$container->setAlias('security.acl.cache', sprintf('security.acl.cache.%s', $config['cache']));
} else {
$container->remove('security.acl.cache.doctrine');
$container->removeAlias('security.acl.cache.doctrine.cache_impl');
}
}
/**
* Returns the base path for the XSD files.

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<container xmlns="http://www.symfony-project.org/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.symfony-project.org/schema/dic/services http://www.symfony-project.org/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="security.acl.dbal.class_table_name">acl_classes</parameter>
<parameter key="security.acl.dbal.entry_table_name">acl_entries</parameter>
<parameter key="security.acl.dbal.oid_table_name">acl_object_identities</parameter>
<parameter key="security.acl.dbal.oid_ancestors_table_name">acl_object_identity_ancestors</parameter>
<parameter key="security.acl.dbal.sid_table_name">acl_security_identities</parameter>
<parameter key="security.acl.dbal.provider.class">Symfony\Component\Security\Acl\Dbal\MutableAclProvider</parameter>
<parameter key="security.acl.permission_granting_strategy.class">Symfony\Component\Security\Acl\Domain\PermissionGrantingStrategy</parameter>
<parameter key="security.acl.voter.class">Symfony\Component\Security\Acl\Voter\AclVoter</parameter>
<parameter key="security.acl.permission.map.class">Symfony\Component\Security\Acl\Permission\BasicPermissionMap</parameter>
<parameter key="security.acl.object_identity_retrieval_strategy.class">Symfony\Component\Security\Acl\Domain\ObjectIdentityRetrievalStrategy</parameter>
<parameter key="security.acl.security_identity_retrieval_strategy.class">Symfony\Component\Security\Acl\Domain\SecurityIdentityRetrievalStrategy</parameter>
<parameter key="security.acl.cache.doctrine.class">Symfony\Component\Security\Acl\Domain\DoctrineAclCache</parameter>
<parameter key="security.acl.cache.doctrine.prefix">sf2_acl_</parameter>
</parameters>
<services>
<service id="security.acl.dbal.connection" alias="doctrine.dbal.default_connection" />
<service id="security.acl.object_identity_retrieval_strategy" class="%security.acl.object_identity_retrieval_strategy.class%"></service>
<service id="security.acl.security_identity_retrieval_strategy" class="%security.acl.security_identity_retrieval_strategy.class%">
<argument type="service" id="security.role_hierarchy" />
<argument type="service" id="security.authentication.trust_resolver" />
</service>
<service id="security.acl.dbal.provider" class="%security.acl.dbal.provider.class%">
<argument type="service" id="security.acl.dbal.connection" />
<argument type="service" id="security.acl.permission_granting_strategy" />
<argument type="collection">
<argument key="class_table_name">%security.acl.dbal.class_table_name%</argument>
<argument key="entry_table_name">%security.acl.dbal.entry_table_name%</argument>
<argument key="oid_table_name">%security.acl.dbal.oid_table_name%</argument>
<argument key="oid_ancestors_table_name">%security.acl.dbal.oid_ancestors_table_name%</argument>
<argument key="sid_table_name">%security.acl.dbal.sid_table_name%</argument>
</argument>
<argument type="service" id="security.acl.cache" on-invalid="null" />
</service>
<service id="security.acl.provider" alias="security.acl.dbal.provider" />
<service id="security.acl.permission_granting_strategy" class="%security.acl.permission_granting_strategy.class%">
<call method="setAuditLogger">
<argument type="service" id="security.acl.audit_logger" on-invalid="ignore" />
</call>
</service>
<service id="security.acl.cache.doctrine" class="%security.acl.cache.doctrine.class%">
<argument type="service" id="security.acl.cache.doctrine_cache_impl" />
<argument type="service" id="security.acl.permission_granting_strategy" />
<argument>%security.acl.cache.doctrine.prefix%</argument>
</service>
<service id="security.acl.cache.doctrine.cache_impl" alias="doctrine.orm.default_result_cache" />
<service id="security.acl.permission.map" class="%security.acl.permission.map.class%"></service>
<service id="security.acl.voter.basic_permissions" class="%security.acl.voter.class%">
<argument type="service" id="security.acl.provider" />
<argument type="service" id="security.acl.object_identity_retrieval_strategy" />
<argument type="service" id="security.acl.security_identity_retrieval_strategy" />
<argument type="service" id="security.acl.permission.map" />
<tag name="security.voter" />
</service>
</services>
</container>

View File

@ -2,6 +2,7 @@
namespace Symfony\Bundle\FrameworkBundle\Templating\Helper;
use Symfony\Component\Security\Acl\Voter\FieldVote;
use Symfony\Component\Templating\Helper\Helper;
use Symfony\Component\Security\SecurityContext;
@ -33,11 +34,19 @@ class SecurityHelper extends Helper
$this->context = $context;
}
public function vote($role, $object = null)
public function vote($role, $object = null, $field = null)
{
if (null === $this->context) {
return false;
}
if ($field !== null) {
if (null === $object) {
throw new \InvalidArgumentException('$object cannot be null when field is not null.');
}
$object = new FieldVote($object, $field);
}
return $this->context->vote($role, $object);
}

View File

@ -27,11 +27,19 @@ class SecurityExtension extends \Twig_Extension
$this->context = $context;
}
public function vote($role, $object = null)
public function vote($role, $object = null, $field = null)
{
if (null === $this->context) {
return false;
}
if ($field !== null) {
if (null === $object) {
throw new \InvalidArgumentException('$object cannot be null when field is not null.');
}
$object = new FieldVote($object, $field);
}
return $this->context->vote($role, $object);
}

View File

@ -0,0 +1,624 @@
<?php
namespace Symfony\Component\Security\Acl\Dbal;
use Doctrine\DBAL\Driver\Connection;
use Doctrine\DBAL\Driver\Statement;
use Symfony\Component\Security\Acl\Domain\Acl;
use Symfony\Component\Security\Acl\Domain\Entry;
use Symfony\Component\Security\Acl\Domain\FieldEntry;
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\Model\AclCacheInterface;
use Symfony\Component\Security\Acl\Model\AclProviderInterface;
use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
use Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* An ACL provider implementation.
*
* This provider assumes that all ACLs share the same PermissionGrantingStrategy.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class AclProvider implements AclProviderInterface
{
const MAX_BATCH_SIZE = 30;
protected $aclCache;
protected $connection;
protected $loadedAces;
protected $loadedAcls;
protected $options;
protected $permissionGrantingStrategy;
/**
* Constructor
*
* @param Connection $connection
* @param PermissionGrantingStrategyInterface $permissionGrantingStrategy
* @param array $options
* @param AclCacheInterface $aclCache
*/
public function __construct(Connection $connection, PermissionGrantingStrategyInterface $permissionGrantingStrategy, array $options, AclCacheInterface $aclCache = null)
{
$this->aclCache = $aclCache;
$this->connection = $connection;
$this->loadedAces = array();
$this->loadedAcls = array();
$this->options = $options;
$this->permissionGrantingStrategy = $permissionGrantingStrategy;
}
/**
* {@inheritDoc}
*/
public function findChildren(ObjectIdentityInterface $parentOid, $directChildrenOnly = false)
{
$sql = $this->getFindChildrenSql($parentOid, $directChildrenOnly);
$children = array();
foreach ($this->connection->executeQuery($sql)->fetchAll() as $data) {
$children[] = new ObjectIdentity($data['object_identifier'], $data['class_type']);
}
return $children;
}
/**
* {@inheritDoc}
*/
public function findAcl(ObjectIdentityInterface $oid, array $sids = array())
{
return $this->findAcls(array($oid), $sids)->offsetGet($oid);
}
/**
* {@inheritDoc}
*/
public function findAcls(array $oids, array $sids = array())
{
$result = new \SplObjectStorage();
$currentBatch = array();
$oidLookup = array();
for ($i=0,$c=count($oids); $i<$c; $i++) {
$oid = $oids[$i];
$oidLookupKey = $oid->getIdentifier().$oid->getType();
$oidLookup[$oidLookupKey] = $oid;
$aclFound = false;
// check if result already contains an ACL
if ($result->contains($oid)) {
$aclFound = true;
}
// check if this ACL has already been hydrated
if (!$aclFound && isset($this->loadedAcls[$oid->getType()][$oid->getIdentifier()])) {
$acl = $this->loadedAcls[$oid->getType()][$oid->getIdentifier()];
if (!$acl->isSidLoaded($sids)) {
// FIXME: we need to load ACEs for the missing SIDs. This is never
// reached by the default implementation, since we do not
// filter by SID
throw new \RuntimeException('This is not supported by the default implementation.');
} else {
$result->attach($oid, $acl);
$aclFound = true;
}
}
// check if we can locate the ACL in the cache
if (!$aclFound && null !== $this->aclCache) {
$acl = $this->aclCache->getFromCacheByIdentity($oid);
if (null !== $acl) {
if ($acl->isSidLoaded($sids)) {
// check if any of the parents has been loaded since we need to
// ensure that there is only ever one ACL per object identity
$parentAcl = $acl->getParentAcl();
while (null !== $parentAcl) {
$parentOid = $parentAcl->getObjectIdentity();
if (isset($this->loadedAcls[$parentOid->getType()][$parentOid->getIdentifier()])) {
$acl->setParentAcl($this->loadedAcls[$parentOid->getType()][$parentOid->getIdentifier()]);
break;
} else {
$this->loadedAcls[$parentOid->getType()][$parentOid->getIdentifier()] = $parentAcl;
$this->updateAceIdentityMap($parentAcl);
}
$parentAcl = $parentAcl->getParentAcl();
}
$this->loadedAcls[$oid->getType()][$oid->getIdentifier()] = $acl;
$this->updateAceIdentityMap($acl);
$result->attach($oid, $acl);
$aclFound = true;
} else {
$this->aclCache->evictFromCacheByIdentity($oid);
foreach ($this->findChildren($oid) as $childOid) {
$this->aclCache->evictFromCacheByIdentity($childOid);
}
}
}
}
// looks like we have to load the ACL from the database
if (!$aclFound) {
$currentBatch[] = $oid;
}
// Is it time to load the current batch?
if ((self::MAX_BATCH_SIZE === count($currentBatch) || ($i + 1) === $c) && count($currentBatch) > 0) {
$loadedBatch = $this->lookupObjectIdentities($currentBatch, $sids, $oidLookup);
foreach ($loadedBatch as $loadedOid) {
$loadedAcl = $loadedBatch->offsetGet($loadedOid);
if (null !== $this->aclCache) {
$this->aclCache->putInCache($loadedAcl);
}
if (isset($oidLookup[$loadedOid->getIdentifier().$loadedOid->getType()])) {
$result->attach($loadedOid, $loadedAcl);
}
}
$currentBatch = array();
}
}
// check that we got ACLs for all the identities
foreach ($oids as $oid) {
if (!$result->contains($oid)) {
throw new AclNotFoundException(sprintf('No ACL found for %s.', $oid));
}
}
return $result;
}
/**
* This method is called when an ACL instance is retrieved from the cache.
*
* @param AclInterface $acl
* @return void
*/
protected function updateAceIdentityMap(AclInterface $acl)
{
foreach (array('classAces', 'classFieldAces', 'objectAces', 'objectFieldAces') as $property) {
$reflection = new \ReflectionProperty($acl, $property);
$reflection->setAccessible(true);
$value = $reflection->getValue($acl);
if ('classAces' === $property || 'objectAces' === $property) {
$this->doUpdateAceIdentityMap($value);
} else {
foreach ($value as $field => $aces) {
$this->doUpdateAceIdentityMap($value[$field]);
}
}
$reflection->setValue($acl, $value);
$reflection->setAccessible(false);
}
}
/**
* Does either overwrite the passed ACE, or saves it in the global identity
* map to ensure every ACE only gets instantiated once.
*
* @param array $aces
* @return void
*/
protected function doUpdateAceIdentityMap(array &$aces)
{
foreach ($aces as $index => $ace) {
if (isset($this->loadedAces[$ace->getId()])) {
$aces[$index] = $this->loadedAces[$ace->getId()];
} else {
$this->loadedAces[$ace->getId()] = $ace;
}
}
}
/**
* This method is called for object identities which could not be retrieved
* from the cache, and for which thus a database query is required.
*
* @param array $batch
* @param array $sids
* @param array $oidLookup
* @return \SplObjectStorage mapping object identites to ACL instances
*/
protected function lookupObjectIdentities(array $batch, array $sids, array $oidLookup)
{
$sql = $this->getLookupSql($batch, $sids);
$stmt = $this->connection->executeQuery($sql);
return $this->hydrateObjectIdentities($stmt, $oidLookup, $sids);
}
/**
* This method is called to hydrate ACLs and ACEs.
*
* This method was designed for performance; thus, a lot of code has been
* inlined at the cost of readability, and maintainability.
*
* Keep in mind that changes to this method might severely reduce the
* performance of the entire ACL system.
*
* @param Statement $stmt
* @param array $oidLookup
* @param array $sids
* @throws \RuntimeException
* @return \SplObjectStorage
*/
protected function hydrateObjectIdentities(Statement $stmt, array $oidLookup, array $sids) {
$parentIdToFill = new \SplObjectStorage();
$acls = $aces = $emptyArray = array();
$oidCache = $oidLookup;
$result = new \SplObjectStorage();
$loadedAces =& $this->loadedAces;
$loadedAcls =& $this->loadedAcls;
$permissionGrantingStrategy = $this->permissionGrantingStrategy;
// we need these to set protected properties on hydrated objects
$aclReflection = new \ReflectionClass('Symfony\Component\Security\Acl\Domain\Acl');
$aclClassAcesProperty = $aclReflection->getProperty('classAces');
$aclClassAcesProperty->setAccessible(true);
$aclClassFieldAcesProperty = $aclReflection->getProperty('classFieldAces');
$aclClassFieldAcesProperty->setAccessible(true);
$aclObjectAcesProperty = $aclReflection->getProperty('objectAces');
$aclObjectAcesProperty->setAccessible(true);
$aclObjectFieldAcesProperty = $aclReflection->getProperty('objectFieldAces');
$aclObjectFieldAcesProperty->setAccessible(true);
$aclParentAclProperty = $aclReflection->getProperty('parentAcl');
$aclParentAclProperty->setAccessible(true);
// fetchAll() consumes more memory than consecutive calls to fetch(),
// but it is faster
foreach ($stmt->fetchAll(\PDO::FETCH_NUM) as $data) {
list($aclId,
$objectIdentifier,
$parentObjectIdentityId,
$entriesInheriting,
$classType,
$aceId,
$objectIdentityId,
$fieldName,
$aceOrder,
$mask,
$granting,
$grantingStrategy,
$auditSuccess,
$auditFailure,
$username,
$securityIdentifier) = $data;
// has the ACL been hydrated during this hydration cycle?
if (isset($acls[$aclId])) {
$acl = $acls[$aclId];
}
// has the ACL been hydrated during any previous cycle, or was possibly loaded
// from cache?
else if (isset($loadedAcls[$classType][$objectIdentifier])) {
$acl = $loadedAcls[$classType][$objectIdentifier];
// keep reference in local array (saves us some hash calculations)
$acls[$aclId] = $acl;
// attach ACL to the result set; even though we do not enforce that every
// object identity has only one instance, we must make sure to maintain
// referential equality with the oids passed to findAcls()
if (!isset($oidCache[$objectIdentifier.$classType])) {
$oidCache[$objectIdentifier.$classType] = $acl->getObjectIdentity();
}
$result->attach($oidCache[$objectIdentifier.$classType], $acl);
}
// so, this hasn't been hydrated yet
else {
// create object identity if we haven't done so yet
$oidLookupKey = $objectIdentifier.$classType;
if (!isset($oidCache[$oidLookupKey])) {
$oidCache[$oidLookupKey] = new ObjectIdentity($objectIdentifier, $classType);
}
$acl = new Acl((integer) $aclId, $oidCache[$oidLookupKey], $permissionGrantingStrategy, $emptyArray, !!$entriesInheriting);
// keep a local, and global reference to this ACL
$loadedAcls[$classType][$objectIdentifier] = $acl;
$acls[$aclId] = $acl;
// try to fill in parent ACL, or defer until all ACLs have been hydrated
if (null !== $parentObjectIdentityId) {
if (isset($acls[$parentObjectIdentityId])) {
$aclParentAclProperty->setValue($acl, $acls[$parentObjectIdentityId]);
} else {
$parentIdToFill->attach($acl, $parentObjectIdentityId);
}
}
$result->attach($oidCache[$oidLookupKey], $acl);
}
// check if this row contains an ACE record
if (null !== $aceId) {
// have we already hydrated ACEs for this ACL?
if (!isset($aces[$aclId])) {
$aces[$aclId] = array($emptyArray, $emptyArray, $emptyArray, $emptyArray);
}
// has this ACE already been hydrated during a previous cycle, or
// possible been loaded from cache?
// It is important to only ever have one ACE instance per actual row since
// some ACEs are shared between ACL instances
if (!isset($loadedAces[$aceId])) {
if (!isset($sids[$key = ($username?'1':'0').$securityIdentifier])) {
if ($username) {
$sids[$key] = new UserSecurityIdentity($securityIdentifier);
} else {
$sids[$key] = new RoleSecurityIdentity($securityIdentifier);
}
}
if (null === $fieldName) {
$loadedAces[$aceId] = new Entry((integer) $aceId, $acl, $sids[$key], $grantingStrategy, (integer) $mask, !!$granting, !!$auditFailure, !!$auditSuccess);
} else {
$loadedAces[$aceId] = new FieldEntry((integer) $aceId, $acl, $fieldName, $sids[$key], $grantingStrategy, (integer) $mask, !!$granting, !!$auditFailure, !!$auditSuccess);
}
}
$ace = $loadedAces[$aceId];
// assign ACE to the correct property
if (null === $objectIdentityId) {
if (null === $fieldName) {
$aces[$aclId][0][$aceOrder] = $ace;
} else {
$aces[$aclId][1][$fieldName][$aceOrder] = $ace;
}
} else {
if (null === $fieldName) {
$aces[$aclId][2][$aceOrder] = $ace;
} else {
$aces[$aclId][3][$fieldName][$aceOrder] = $ace;
}
}
}
}
// We do not sort on database level since we only want certain subsets to be sorted,
// and we are going to read the entire result set anyway.
// Sorting on DB level increases query time by an order of magnitude while it is
// almost negligible when we use PHPs array sort functions.
foreach ($aces as $aclId => $aceData) {
$acl = $acls[$aclId];
ksort($aceData[0]);
$aclClassAcesProperty->setValue($acl, $aceData[0]);
foreach (array_keys($aceData[1]) as $fieldName) {
ksort($aceData[1][$fieldName]);
}
$aclClassFieldAcesProperty->setValue($acl, $aceData[1]);
ksort($aceData[2]);
$aclObjectAcesProperty->setValue($acl, $aceData[2]);
foreach (array_keys($aceData[3]) as $fieldName) {
ksort($aceData[3][$fieldName]);
}
$aclObjectFieldAcesProperty->setValue($acl, $aceData[3]);
}
// fill-in parent ACLs where this hasn't been done yet cause the parent ACL was not
// yet available
$processed = 0;
foreach ($parentIdToFill as $acl)
{
$parentId = $parentIdToFill->offsetGet($acl);
// let's see if we have already hydrated this
if (isset($acls[$parentId])) {
$aclParentAclProperty->setValue($acl, $acls[$parentId]);
$processed += 1;
continue;
}
}
// reset reflection changes
$aclClassAcesProperty->setAccessible(false);
$aclClassFieldAcesProperty->setAccessible(false);
$aclObjectAcesProperty->setAccessible(false);
$aclObjectFieldAcesProperty->setAccessible(false);
$aclParentAclProperty->setAccessible(false);
// this should never be true if the database integrity hasn't been compromised
if ($processed < count($parentIdToFill)) {
throw new \RuntimeException('Not all parent ids were populated. This implies an integrity problem.');
}
return $result;
}
/**
* Constructs the query used for looking up object identites and associated
* ACEs, and security identities.
*
* @param array $batch
* @param array $sids
* @throws AclNotFoundException
* @return string
*/
protected function getLookupSql(array $batch, array $sids)
{
// FIXME: add support for filtering by sids (right now we select all sids)
$ancestorIds = $this->getAncestorIds($batch);
if (0 === count($ancestorIds)) {
throw new AclNotFoundException('There is no ACL for the given object identity.');
}
$sql = <<<SELECTCLAUSE
SELECT
o.id as acl_id,
o.object_identifier,
o.parent_object_identity_id,
o.entries_inheriting,
c.class_type,
e.id as ace_id,
e.object_identity_id,
e.field_name,
e.ace_order,
e.mask,
e.granting,
e.granting_strategy,
e.audit_success,
e.audit_failure,
s.username,
s.identifier as security_identifier
FROM
{$this->options['oid_table_name']} o
INNER JOIN {$this->options['class_table_name']} c ON c.id = o.class_id
LEFT JOIN {$this->options['entry_table_name']} e ON (
e.class_id = o.class_id AND (e.object_identity_id = o.id OR {$this->connection->getDatabasePlatform()->getIsNullExpression('e.object_identity_id')})
)
LEFT JOIN {$this->options['sid_table_name']} s ON (
s.id = e.security_identity_id
)
WHERE (o.id =
SELECTCLAUSE;
$sql .= implode(' OR o.id = ', $ancestorIds).')';
return $sql;
}
/**
* Retrieves all the ids which need to be queried from the database
* including the ids of parent ACLs.
*
* @param array $batch
* @return array
*/
protected function getAncestorIds(array &$batch)
{
$sql = <<<SELECTCLAUSE
SELECT a.ancestor_id
FROM acl_object_identities o
INNER JOIN acl_classes c ON c.id = o.class_id
INNER JOIN acl_object_identity_ancestors a ON a.object_identity_id = o.id
WHERE (
SELECTCLAUSE;
$where = '(o.object_identifier = %s AND c.class_type = %s)';
for ($i=0,$c=count($batch); $i<$c; $i++) {
$sql .= sprintf(
$where,
$this->connection->quote($batch[$i]->getIdentifier()),
$this->connection->quote($batch[$i]->getType())
);
if ($i+1 < $c) {
$sql .= ' OR ';
}
}
$sql .= ')';
$ancestorIds = array();
foreach ($this->connection->executeQuery($sql)->fetchAll() as $data) {
// FIXME: skip ancestors which are cached
$ancestorIds[] = $data['ancestor_id'];
}
return $ancestorIds;
}
/**
* Constructs the SQL for retrieving child object identities for the given
* object identities.
*
* @param ObjectIdentityInterface $oid
* @param Boolean $directChildrenOnly
* @return string
*/
protected function getFindChildrenSql(ObjectIdentityInterface $oid, $directChildrenOnly)
{
if (false === $directChildrenOnly) {
$query = <<<FINDCHILDREN
SELECT o.object_identifier, c.class_type
FROM
{$this->options['oid_table_name']} as o
INNER JOIN {$this->options['class_table_name']} as c ON c.id = o.class_id
INNER JOIN {$this->options['oid_ancestors_table_name']} as a ON a.object_identity_id = o.id
WHERE
a.ancestor_id = %d AND a.object_identity_id != a.ancestor_id
FINDCHILDREN;
} else {
$query = <<<FINDCHILDREN
SELECT o.object_identifier, c.class_type
FROM {$this->options['oid_table_name']} as o
INNER JOIN {$this->options['class_table_name']} as c ON c.id = o.class_id
WHERE o.parent_object_identity_id = %d
FINDCHILDREN;
}
return sprintf($query, $this->retrieveObjectIdentityPrimaryKey($oid));
}
/**
* Constructs the SQL for retrieving the primary key of the given object
* identity.
*
* @param ObjectIdentityInterface $oid
* @return string
*/
protected function getSelectObjectIdentityIdSql(ObjectIdentityInterface $oid)
{
$query = <<<QUERY
SELECT o.id
FROM %s o
INNER JOIN %s c ON c.id = o.class_id
WHERE o.object_identifier = %s AND c.class_type = %s
LIMIT 1
QUERY;
return sprintf(
$query,
$this->options['oid_table_name'],
$this->options['class_table_name'],
$this->connection->quote($oid->getIdentifier()),
$this->connection->quote($oid->getType())
);
}
/**
* Returns the primary key of the passed object identity.
*
* @param ObjectIdentityInterface $oid
* @return integer
*/
protected function retrieveObjectIdentityPrimaryKey(ObjectIdentityInterface $oid)
{
return $this->connection->executeQuery($this->getSelectObjectIdentityIdSql($oid))->fetchColumn();
}
}

View File

@ -0,0 +1,887 @@
<?php
namespace Symfony\Component\Security\Acl\Dbal;
use Doctrine\Common\PropertyChangedListener;
use Doctrine\DBAL\Driver\Connection;
use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity;
use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
use Symfony\Component\Security\Acl\Exception\AclAlreadyExistsException;
use Symfony\Component\Security\Acl\Exception\ConcurrentModificationException;
use Symfony\Component\Security\Acl\Exception\Exception;
use Symfony\Component\Security\Acl\Model\AclCacheInterface;
use Symfony\Component\Security\Acl\Model\AclInterface;
use Symfony\Component\Security\Acl\Model\EntryInterface;
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;
use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* An implementation of the MutableAclProviderInterface using Doctrine DBAL.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class MutableAclProvider extends AclProvider implements MutableAclProviderInterface, PropertyChangedListener
{
protected $propertyChanges;
/**
* {@inheritDoc}
*/
public function __construct(Connection $connection, PermissionGrantingStrategyInterface $permissionGrantingStrategy, array $options, AclCacheInterface $aclCache = null)
{
parent::__construct($connection, $permissionGrantingStrategy, $options, $aclCache);
$this->propertyChanges = new \SplObjectStorage();
}
/**
* {@inheritDoc}
*/
public function createAcl(ObjectIdentityInterface $oid)
{
if (false !== $this->retrieveObjectIdentityPrimaryKey($oid)) {
throw new AclAlreadyExistsException(sprintf('%s is already associated with an ACL.', $oid));
}
$this->connection->beginTransaction();
try {
$this->createObjectIdentity($oid);
$pk = $this->retrieveObjectIdentityPrimaryKey($oid);
$this->connection->executeQuery($this->getInsertObjectIdentityRelationSql($pk, $pk));
$this->connection->commit();
} catch (\Exception $failed) {
$this->connection->rollBack();
throw $failed;
}
// re-read the ACL from the database to ensure proper caching, etc.
return $this->findAcl($oid);
}
/**
* {@inheritDoc}
*/
public function deleteAcl(ObjectIdentityInterface $oid)
{
$this->connection->beginTransaction();
try {
foreach ($this->findChildren($oid, true) as $childOid) {
$this->deleteAcl($childOid);
}
$oidPK = $this->retrieveObjectIdentityPrimaryKey($oid);
$this->deleteAccessControlEntries($oidPK);
$this->deleteObjectIdentityRelations($oidPK);
$this->deleteObjectIdentity($oidPK);
$this->connection->commit();
} catch (\Exception $failed) {
$this->connection->rollBack();
throw $failed;
}
// evict the ACL from the in-memory identity map
if (isset($this->loadedAcls[$oid->getType()][$oid->getIdentifier()])) {
$this->propertyChanges->offsetUnset($this->loadedAcls[$oid->getType()][$oid->getIdentifier()]);
unset($this->loadedAcls[$oid->getType()][$oid->getIdentifier()]);
}
// evict the ACL from any caches
if (null !== $this->aclCache) {
$this->aclCache->evictFromCacheByIdentity($oid);
}
}
/**
* {@inheritDoc}
*/
public function findAcls(array $oids, array $sids = array())
{
$result = parent::findAcls($oids, $sids);
foreach ($result as $oid) {
$acl = $result->offsetGet($oid);
if (false === $this->propertyChanges->contains($acl) && $acl instanceof MutableAclInterface) {
$acl->addPropertyChangedListener($this);
$this->propertyChanges->attach($acl, array());
}
$parentAcl = $acl->getParentAcl();
while (null !== $parentAcl) {
if (false === $this->propertyChanges->contains($parentAcl) && $acl instanceof MutableAclInterface) {
$parentAcl->addPropertyChangedListener($this);
$this->propertyChanges->attach($parentAcl, array());
}
$parentAcl = $parentAcl->getParentAcl();
}
}
return $result;
}
/**
* Implementation of PropertyChangedListener
*
* This allows us to keep track of which values have been changed, so we don't
* have to do a full introspection when ->updateAcl() is called.
*
* @param mixed $sender
* @param string $propertyName
* @param mixed $oldValue
* @param mixed $newValue
* @return void
*/
public function propertyChanged($sender, $propertyName, $oldValue, $newValue)
{
if (!$sender instanceof MutableAclInterface && !$sender instanceof EntryInterface) {
throw new \InvalidArgumentException('$sender must be an instance of MutableAclInterface, or EntryInterface.');
}
if ($sender instanceof EntryInterface) {
if (null === $sender->getId()) {
return;
}
$ace = $sender;
$sender = $ace->getAcl();
} else {
$ace = null;
}
if (false === $this->propertyChanges->contains($sender)) {
throw new \InvalidArgumentException('$sender is not being tracked by this provider.');
}
$propertyChanges = $this->propertyChanges->offsetGet($sender);
if (null === $ace) {
if (isset($propertyChanges[$propertyName])) {
$oldValue = $propertyChanges[$propertyName][0];
if ($oldValue === $newValue) {
unset($propertyChanges[$propertyName]);
} else {
$propertyChanges[$propertyName] = array($oldValue, $newValue);
}
} else {
$propertyChanges[$propertyName] = array($oldValue, $newValue);
}
} else {
if (!isset($propertyChanges['aces'])) {
$propertyChanges['aces'] = new \SplObjectStorage();
}
$acePropertyChanges = $propertyChanges['aces']->contains($ace)? $propertyChanges['aces']->offsetGet($ace) : array();
if (isset($acePropertyChanges[$propertyName])) {
$oldValue = $acePropertyChanges[$propertyName][0];
if ($oldValue === $newValue) {
unset($acePropertyChanges[$propertyName]);
} else {
$acePropertyChanges[$propertyName] = array($oldValue, $newValue);
}
} else {
$acePropertyChanges[$propertyName] = array($oldValue, $newValue);
}
if (count($acePropertyChanges) > 0) {
$propertyChanges['aces']->offsetSet($ace, $acePropertyChanges);
} else {
$propertyChanges['aces']->offsetUnset($ace);
if (0 === count($propertyChanges['aces'])) {
unset($propertyChanges['aces']);
}
}
}
$this->propertyChanges->offsetSet($sender, $propertyChanges);
}
/**
* {@inheritDoc}
*/
public function updateAcl(MutableAclInterface $acl)
{
if (!$this->propertyChanges->contains($acl)) {
throw new \InvalidArgumentException('$acl is not tracked by this provider.');
}
$propertyChanges = $this->propertyChanges->offsetGet($acl);
// check if any changes were made to this ACL
if (0 === count($propertyChanges)) {
return;
}
$sets = $sharedPropertyChanges = array();
$this->connection->beginTransaction();
try {
if (isset($propertyChanges['entriesInheriting'])) {
$sets[] = 'entries_inheriting = '.$this->connection->getDatabasePlatform()->convertBooleans($propertyChanges['entriesInheriting'][1]);
}
if (isset($propertyChanges['parentAcl'])) {
if (null === $propertyChanges['parentAcl'][1]) {
$sets[] = 'parent_object_identity_id = NULL';
} else {
$sets[] = 'parent_object_identity_id = '.intval($propertyChanges['parentAcl'][1]->getId());
}
$this->regenerateAncestorRelations($acl);
}
// this includes only updates of existing ACEs, but neither the creation, nor
// the deletion of ACEs; these are tracked by changes to the ACL's respective
// properties (classAces, classFieldAces, objectAces, objectFieldAces)
if (isset($propertyChanges['aces'])) {
$this->updateAces($propertyChanges['aces']);
}
// check properties for deleted, and created ACEs
if (isset($propertyChanges['classAces'])) {
$this->updateAceProperty('classAces', $propertyChanges['classAces']);
$sharedPropertyChanges['classAces'] = $propertyChanges['classAces'];
}
if (isset($propertyChanges['classFieldAces'])) {
$this->updateFieldAceProperty('classFieldAces', $propertyChanges['classFieldAces']);
$sharedPropertyChanges['classFieldAces'] = $propertyChanges['classFieldAces'];
}
if (isset($propertyChanges['objectAces'])) {
$this->updateAceProperty('objectAces', $propertyChanges['objectAces']);
}
if (isset($propertyChanges['objectFieldAces'])) {
$this->updateFieldAceProperty('objectFieldAces', $propertyChanges['objectFieldAces']);
}
// if there have been changes to shared properties, we need to synchronize other
// ACL instances for object identities of the same type that are already in-memory
if (count($sharedPropertyChanges) > 0) {
$classAcesProperty = new \ReflectionProperty('Symfony\Component\Security\Acl\Domain\Acl', 'classAces');
$classAcesProperty->setAccessible(true);
$classFieldAcesProperty = new \ReflectionProperty('Symfony\Component\Security\Acl\Domain\Acl', 'classFieldAces');
$classFieldAcesProperty->setAccessible(true);
foreach ($this->loadedAcls[$acl->getObjectIdentity()->getType()] as $sameTypeAcl) {
if (isset($sharedPropertyChanges['classAces'])) {
if ($acl !== $sameTypeAcl && $classAcesProperty->getValue($sameTypeAcl) !== $sharedPropertyChanges['classAces'][0]) {
throw new ConcurrentModificationException('The "classAces" property has been modified concurrently.');
}
$classAcesProperty->setValue($sameTypeAcl, $sharedPropertyChanges['classAces'][1]);
}
if (isset($sharedPropertyChanges['classFieldAces'])) {
if ($acl !== $sameTypeAcl && $classFieldAcesProperty->getValue($sameTypeAcl) !== $sharedPropertyChanges['classFieldAces'][0]) {
throw new ConcurrentModificationException('The "classFieldAces" property has been modified concurrently.');
}
$classFieldAcesProperty->setValue($sameTypeAcl, $sharedPropertyChanges['classFieldAces'][1]);
}
}
}
// persist any changes to the acl_object_identities table
if (count($sets) > 0) {
$this->connection->executeQuery($this->getUpdateObjectIdentitySql($acl->getId(), $sets));
}
$this->connection->commit();
} catch (\Exception $failed) {
$this->connection->rollBack();
throw $failed;
}
$this->propertyChanges->offsetSet($acl, array());
if (null !== $this->aclCache) {
if (count($sharedPropertyChanges) > 0) {
// FIXME: Currently, there is no easy way to clear the cache for ACLs
// of a certain type. The problem here is that we need to make
// sure to clear the cache of all child ACLs as well, and these
// child ACLs might be of a different class type.
$this->aclCache->clearCache();
} else {
// if there are no shared property changes, it's sufficient to just delete
// the cache for this ACL
$this->aclCache->evictFromCacheByIdentity($acl->getObjectIdentity());
foreach ($this->findChildren($acl->getObjectIdentity()) as $childOid) {
$this->aclCache->evictFromCacheByIdentity($childOid);
}
}
}
}
/**
* Creates the ACL for the passed object identity
*
* @param ObjectIdentityInterface $oid
* @return void
*/
protected function createObjectIdentity(ObjectIdentityInterface $oid)
{
$classId = $this->createOrRetrieveClassId($oid->getType());
$this->connection->executeQuery($this->getInsertObjectIdentitySql($oid->getIdentifier(), $classId, true));
}
/**
* Returns the primary key for the passed class type.
*
* If the type does not yet exist in the database, it will be created.
*
* @param string $classType
* @return integer
*/
protected function createOrRetrieveClassId($classType)
{
if (false !== $id = $this->connection->executeQuery($this->getSelectClassIdSql($classType))->fetchColumn()) {
return $id;
}
$this->connection->executeQuery($this->getInsertClassSql($classType));
return $this->connection->executeQuery($this->getSelectClassIdSql($classType))->fetchColumn();
}
/**
* Returns the primary key for the passed security identity.
*
* If the security identity does not yet exist in the database, it will be
* created.
*
* @param SecurityIdentityInterface $sid
* @return integer
*/
protected function createOrRetrieveSecurityIdentityId(SecurityIdentityInterface $sid)
{
if (false !== $id = $this->connection->executeQuery($this->getSelectSecurityIdentityIdSql($sid))->fetchColumn()) {
return $id;
}
$this->connection->executeQuery($this->getInsertSecurityIdentitySql($sid));
return $this->connection->executeQuery($this->getSelectSecurityIdentityIdSql($sid))->fetchColumn();
}
/**
* Deletes all ACEs for the given object identity primary key.
*
* @param integer $oidPK
* @return void
*/
protected function deleteAccessControlEntries($oidPK)
{
$this->connection->executeQuery($this->getDeleteAccessControlEntriesSql($oidPK));
}
/**
* Deletes the object identity from the database.
*
* @param integer $pk
* @return void
*/
protected function deleteObjectIdentity($pk)
{
$this->connection->executeQuery($this->getDeleteObjectIdentitySql($pk));
}
/**
* Deletes all entries from the relations table from the database.
*
* @param integer $pk
* @return void
*/
protected function deleteObjectIdentityRelations($pk)
{
$this->connection->executeQuery($this->getDeleteObjectIdentityRelationsSql($pk));
}
/**
* Constructs the SQL for deleting access control entries.
*
* @param integer $oidPK
* @return string
*/
protected function getDeleteAccessControlEntriesSql($oidPK)
{
return sprintf(
'DELETE FROM %s WHERE object_identity_id = %d',
$this->options['entry_table_name'],
$oidPK
);
}
/**
* Constructs the SQL for deleting a specific ACE.
*
* @param integer $acePK
* @return string
*/
protected function getDeleteAccessControlEntrySql($acePK)
{
return sprintf(
'DELETE FROM %s WHERE id = %d',
$this->options['entry_table_name'],
$acePK
);
}
/**
* Constructs the SQL for deleting an object identity.
*
* @param integer $pk
* @return string
*/
protected function getDeleteObjectIdentitySql($pk)
{
return sprintf(
'DELETE FROM %s WHERE id = %d',
$this->options['oid_table_name'],
$pk
);
}
/**
* Constructs the SQL for deleting relation entries.
*
* @param integer $pk
* @return string
*/
protected function getDeleteObjectIdentityRelationsSql($pk)
{
return sprintf(
'DELETE FROM %s WHERE object_identity_id = %d',
$this->options['oid_ancestors_table_name'],
$pk
);
}
/**
* Constructs the SQL for inserting an ACE.
*
* @param integer $classId
* @param integer|null $objectIdentityId
* @param string|null $field
* @param integer $aceOrder
* @param integer $securityIdentityId
* @param string $strategy
* @param integer $mask
* @param Boolean $granting
* @param Boolean $auditSuccess
* @param Boolean $auditFailure
* @return string
*/
protected function getInsertAccessControlEntrySql($classId, $objectIdentityId, $field, $aceOrder, $securityIdentityId, $strategy, $mask, $granting, $auditSuccess, $auditFailure)
{
$query = <<<QUERY
INSERT INTO %s (
class_id,
object_identity_id,
field_name,
ace_order,
security_identity_id,
mask,
granting,
granting_strategy,
audit_success,
audit_failure
)
VALUES (%d, %s, %s, %d, %d, %d, %s, %s, %s, %s)
QUERY;
return sprintf(
$query,
$this->options['entry_table_name'],
$classId,
null === $objectIdentityId? 'NULL' : intval($objectIdentityId),
null === $field? 'NULL' : $this->connection->quote($field),
$aceOrder,
$securityIdentityId,
$mask,
$this->connection->getDatabasePlatform()->convertBooleans($granting),
$this->connection->quote($strategy),
$this->connection->getDatabasePlatform()->convertBooleans($auditSuccess),
$this->connection->getDatabasePlatform()->convertBooleans($auditFailure)
);
}
/**
* Constructs the SQL for inserting a new class type.
*
* @param string $classType
* @return string
*/
protected function getInsertClassSql($classType)
{
return sprintf(
'INSERT INTO %s (class_type) VALUES (%s)',
$this->options['class_table_name'],
$this->connection->quote($classType)
);
}
/**
* Constructs the SQL for inserting a relation entry.
*
* @param integer $objectIdentityId
* @param integer $ancestorId
* @return string
*/
protected function getInsertObjectIdentityRelationSql($objectIdentityId, $ancestorId)
{
return sprintf(
'INSERT INTO %s (object_identity_id, ancestor_id) VALUES (%d, %d)',
$this->options['oid_ancestors_table_name'],
$objectIdentityId,
$ancestorId
);
}
/**
* Constructs the SQL for inserting an object identity.
*
* @param string $identifier
* @param integer $classId
* @param Boolean $entriesInheriting
* @return string
*/
protected function getInsertObjectIdentitySql($identifier, $classId, $entriesInheriting)
{
$query = <<<QUERY
INSERT INTO %s (class_id, object_identifier, entries_inheriting)
VALUES (%d, %s, %s)
QUERY;
return sprintf(
$query,
$this->options['oid_table_name'],
$classId,
$this->connection->quote($identifier),
$this->connection->getDatabasePlatform()->convertBooleans($entriesInheriting)
);
}
/**
* Constructs the SQL for inserting a security identity.
*
* @param SecurityIdentityInterface $sid
* @throws \InvalidArgumentException
* @return string
*/
protected function getInsertSecurityIdentitySql(SecurityIdentityInterface $sid)
{
if ($sid instanceof UserSecurityIdentity) {
$identifier = $sid->getUsername();
$username = true;
} else if ($sid instanceof RoleSecurityIdentity) {
$identifier = $sid->getRole();
$username = false;
} else {
throw new \InvalidArgumentException('$sid must either be an instance of UserSecurityIdentity, or RoleSecurityIdentity.');
}
return sprintf(
'INSERT INTO %s (identifier, username) VALUES (%s, %s)',
$this->options['sid_table_name'],
$this->connection->quote($identifier),
$this->connection->getDatabasePlatform()->convertBooleans($username)
);
}
/**
* Constructs the SQL for selecting an ACE.
*
* @param integer $classId
* @param integer $oid
* @param string $field
* @param integer $order
* @return string
*/
protected function getSelectAccessControlEntryIdSql($classId, $oid, $field, $order)
{
return sprintf(
'SELECT id FROM %s WHERE class_id = %d AND %s AND %s AND ace_order = %d',
$this->options['entry_table_name'],
$classId,
null === $oid ?
$this->connection->getDatabasePlatform()->getIsNullExpression('object_identity_id')
: 'object_identity_id = '.intval($oid),
null === $field ?
$this->connection->getDatabasePlatform()->getIsNullExpression('field_name')
: 'field_name = '.$this->connection->quote($field),
$order
);
}
/**
* Constructs the SQL for selecting the primary key associated with
* the passed class type.
*
* @param string $classType
* @return string
*/
protected function getSelectClassIdSql($classType)
{
return sprintf(
'SELECT id FROM %s WHERE class_type = %s',
$this->options['class_table_name'],
$this->connection->quote($classType)
);
}
/**
* Constructs the SQL for selecting the primary key of a security identity.
*
* @param SecurityIdentityInterface $sid
* @throws \InvalidArgumentException
* @return string
*/
protected function getSelectSecurityIdentityIdSql(SecurityIdentityInterface $sid)
{
if ($sid instanceof UserSecurityIdentity) {
$identifier = $sid->getUsername();
$username = true;
} else if ($sid instanceof RoleSecurityIdentity) {
$identifier = $sid->getRole();
$username = false;
} else {
throw new \InvalidArgumentException('$sid must either be an instance of UserSecurityIdentity, or RoleSecurityIdentity.');
}
return sprintf(
'SELECT id FROM %s WHERE identifier = %s AND username = %s',
$this->options['sid_table_name'],
$this->connection->quote($identifier),
$this->connection->getDatabasePlatform()->convertBooleans($username)
);
}
/**
* Constructs the SQL for updating an object identity.
*
* @param integer $pk
* @param array $changes
* @throws \InvalidArgumentException
* @return string
*/
protected function getUpdateObjectIdentitySql($pk, array $changes)
{
if (0 === count($changes)) {
throw new \InvalidArgumentException('There are no changes.');
}
return sprintf(
'UPDATE %s SET %s WHERE id = %d',
$this->options['oid_table_name'],
implode(', ', $changes),
$pk
);
}
/**
* Constructs the SQL for updating an ACE.
*
* @param integer $pk
* @param array $sets
* @throws \InvalidArgumentException
* @return string
*/
protected function getUpdateAccessControlEntrySql($pk, array $sets)
{
if (0 === count($sets)) {
throw new \InvalidArgumentException('There are no changes.');
}
return sprintf(
'UPDATE %s SET %s WHERE id = %d',
$this->options['entry_table_name'],
implode(', ', $sets),
$pk
);
}
/**
* This regenerates the ancestor table which is used for fast read access.
*
* @param AclInterface $acl
* @return void
*/
protected function regenerateAncestorRelations(AclInterface $acl)
{
$pk = $acl->getId();
$this->connection->executeQuery($this->getDeleteObjectIdentityRelationsSql($pk));
$this->connection->executeQuery($this->getInsertObjectIdentityRelationSql($pk, $pk));
$parentAcl = $acl->getParentAcl();
while (null !== $parentAcl) {
$this->connection->executeQuery($this->getInsertObjectIdentityRelationSql($pk, $parentAcl->getId()));
$parentAcl = $parentAcl->getParentAcl();
}
}
/**
* This processes changes on an ACE related property (classFieldAces, or objectFieldAces).
*
* @param string $name
* @param array $changes
* @return void
*/
protected function updateFieldAceProperty($name, array $changes)
{
$sids = new \SplObjectStorage();
$classIds = new \SplObjectStorage();
$currentIds = array();
foreach ($changes[1] as $field => $new) {
for ($i=0,$c=count($new); $i<$c; $i++) {
$ace = $new[$i];
if (null === $ace->getId()) {
if ($sids->contains($ace->getSecurityIdentity())) {
$sid = $sids->offsetGet($ace->getSecurityIdentity());
} else {
$sid = $this->createOrRetrieveSecurityIdentityId($ace->getSecurityIdentity());
}
$oid = $ace->getAcl()->getObjectIdentity();
if ($classIds->contains($oid)) {
$classId = $classIds->offsetGet($oid);
} else {
$classId = $this->createOrRetrieveClassId($oid->getType());
}
$objectIdentityId = $name === 'classFieldAces' ? null : $ace->getAcl()->getId();
$this->connection->executeQuery($this->getInsertAccessControlEntrySql($classId, $objectIdentityId, $field, $i, $sid, $ace->getStrategy(), $ace->getMask(), $ace->isGranting(), $ace->isAuditSuccess(), $ace->isAuditFailure()));
$aceId = $this->connection->executeQuery($this->getSelectAccessControlEntryIdSql($classId, $objectIdentityId, $field, $i))->fetchColumn();
$this->loadedAces[$aceId] = $ace;
$aceIdProperty = new \ReflectionProperty($ace, 'id');
$aceIdProperty->setAccessible(true);
$aceIdProperty->setValue($ace, intval($aceId));
} else {
$currentIds[$ace->getId()] = true;
}
}
}
foreach ($changes[0] as $field => $old) {
for ($i=0,$c=count($old); $i<$c; $i++) {
$ace = $old[$i];
if (!isset($currentIds[$ace->getId()])) {
$this->connection->executeQuery($this->getDeleteAccessControlEntrySql($ace->getId()));
unset($this->loadedAces[$ace->getId()]);
}
}
}
}
/**
* This processes changes on an ACE related property (classAces, or objectAces).
*
* @param string $name
* @param array $changes
* @return void
*/
protected function updateAceProperty($name, array $changes)
{
list($old, $new) = $changes;
$sids = new \SplObjectStorage();
$classIds = new \SplObjectStorage();
$currentIds = array();
for ($i=0,$c=count($new); $i<$c; $i++) {
$ace = $new[$i];
if (null === $ace->getId()) {
if ($sids->contains($ace->getSecurityIdentity())) {
$sid = $sids->offsetGet($ace->getSecurityIdentity());
} else {
$sid = $this->createOrRetrieveSecurityIdentityId($ace->getSecurityIdentity());
}
$oid = $ace->getAcl()->getObjectIdentity();
if ($classIds->contains($oid)) {
$classId = $classIds->offsetGet($oid);
} else {
$classId = $this->createOrRetrieveClassId($oid->getType());
}
$objectIdentityId = $name === 'classAces' ? null : $ace->getAcl()->getId();
$this->connection->executeQuery($this->getInsertAccessControlEntrySql($classId, $objectIdentityId, null, $i, $sid, $ace->getStrategy(), $ace->getMask(), $ace->isGranting(), $ace->isAuditSuccess(), $ace->isAuditFailure()));
$aceId = $this->connection->executeQuery($this->getSelectAccessControlEntryIdSql($classId, $objectIdentityId, null, $i))->fetchColumn();
$this->loadedAces[$aceId] = $ace;
$aceIdProperty = new \ReflectionProperty($ace, 'id');
$aceIdProperty->setAccessible(true);
$aceIdProperty->setValue($ace, intval($aceId));
} else {
$currentIds[$ace->getId()] = true;
}
}
for ($i=0,$c=count($old); $i<$c; $i++) {
$ace = $old[$i];
if (!isset($currentIds[$ace->getId()])) {
$this->connection->executeQuery($this->getDeleteAccessControlEntrySql($ace->getId()));
unset($this->loadedAces[$ace->getId()]);
}
}
}
/**
* Persists the changes which were made to ACEs to the database.
*
* @param \SplObjectStorage $aces
* @return void
*/
protected function updateAces(\SplObjectStorage $aces)
{
foreach ($aces as $ace)
{
$propertyChanges = $aces->offsetGet($ace);
$sets = array();
if (isset($propertyChanges['mask'])) {
$sets[] = sprintf('mask = %d', $propertyChanges['mask'][1]);
}
if (isset($propertyChanges['strategy'])) {
$sets[] = sprintf('granting_strategy = %s', $this->connection->quote($propertyChanges['strategy']));
}
if (isset($propertyChanges['aceOrder'])) {
$sets[] = sprintf('ace_order = %d', $propertyChanges['aceOrder'][1]);
}
if (isset($propertyChanges['auditSuccess'])) {
$sets[] = sprintf('audit_success = %s', $this->connection->getDatabasePlatform()->convertBooleans($propertyChanges['auditSuccess'][1]));
}
if (isset($propertyChanges['auditFailure'])) {
$sets[] = sprintf('audit_failure = %s', $this->connection->getDatabasePlatform()->convertBooleans($propertyChanges['auditFailure'][1]));
}
$this->connection->executeQuery($this->getUpdateAccessControlEntrySql($ace->getId(), $sets));
}
}
}

View File

@ -0,0 +1,145 @@
<?php
namespace Symfony\Component\Security\Acl\Dbal;
use Doctrine\DBAL\Schema\Schema as BaseSchema;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* The schema used for the ACL system.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class Schema extends BaseSchema
{
protected $options;
/**
* Constructor
*
* @param array $options the names for tables
* @return void
*/
public function __construct(array $options)
{
parent::__construct();
$this->options = $options;
$this->addClassTable();
$this->addSecurityIdentitiesTable();
$this->addObjectIdentitiesTable();
$this->addObjectIdentityAncestorsTable();
$this->addEntryTable();
}
/**
* Adds the class table to the schema
*
* @return void
*/
protected function addClassTable()
{
$table = $this->createTable($this->options['class_table_name']);
$table->addColumn('id', 'integer', array('unsigned' => true, 'autoincrement' => 'auto'));
$table->addColumn('class_type', 'string', array('length' => 200));
$table->setPrimaryKey(array('id'));
$table->addUniqueIndex(array('class_type'));
}
/**
* Adds the entry table to the schema
*
* @return void
*/
protected function addEntryTable()
{
$table = $this->createTable($this->options['entry_table_name']);
$table->addColumn('id', 'integer', array('unsigned' => true, 'autoincrement' => 'auto'));
$table->addColumn('class_id', 'integer', array('unsigned' => true));
$table->addColumn('object_identity_id', 'integer', array('unsigned' => true, 'notnull' => false));
$table->addColumn('field_name', 'string', array('length' => 50, 'notnull' => false));
$table->addColumn('ace_order', 'smallint', array('unsigned' => true));
$table->addColumn('security_identity_id', 'integer', array('unsigned' => true));
$table->addColumn('mask', 'integer');
$table->addColumn('granting', 'boolean');
$table->addColumn('granting_strategy', 'string', array('length' => 30));
$table->addColumn('audit_success', 'boolean', array('default' => 0));
$table->addColumn('audit_failure', 'boolean', array('default' => 0));
$table->setPrimaryKey(array('id'));
$table->addUniqueIndex(array('class_id', 'object_identity_id', 'field_name', 'ace_order'));
$table->addIndex(array('class_id', 'object_identity_id', 'security_identity_id'));
$table->addForeignKeyConstraint($this->getTable($this->options['class_table_name']), array('class_id'), array('id'), array('onDelete' => 'CASCADE', 'onUpdate' => 'CASCADE'));
$table->addForeignKeyConstraint($this->getTable($this->options['oid_table_name']), array('object_identity_id'), array('id'), array('onDelete' => 'CASCADE', 'onUpdate' => 'CASCADE'));
$table->addForeignKeyConstraint($this->getTable($this->options['sid_table_name']), array('security_identity_id'), array('id'), array('onDelete' => 'CASCADE', 'onUpdate' => 'CASCADE'));
}
/**
* Adds the object identity table to the schema
*
* @return void
*/
protected function addObjectIdentitiesTable()
{
$table = $this->createTable($this->options['oid_table_name']);
$table->addColumn('id', 'integer', array('unsigned' => true, 'autoincrement' => 'auto'));
$table->addColumn('class_id', 'integer', array('unsigned' => true));
$table->addColumn('object_identifier', 'string', array('length' => 100));
$table->addColumn('parent_object_identity_id', 'integer', array('unsigned' => true, 'notnull' => false));
$table->addColumn('entries_inheriting', 'boolean', array('default' => 0));
$table->setPrimaryKey(array('id'));
$table->addUniqueIndex(array('object_identifier', 'class_id'));
$table->addIndex(array('parent_object_identity_id'));
$table->addForeignKeyConstraint($table, array('parent_object_identity_id'), array('id'), array('onDelete' => 'RESTRICT', 'onUpdate' => 'RESTRICT'));
}
/**
* Adds the object identity relation table to the schema
*
* @return void
*/
protected function addObjectIdentityAncestorsTable()
{
$table = $this->createTable($this->options['oid_ancestors_table_name']);
$table->addColumn('object_identity_id', 'integer', array('unsigned' => true));
$table->addColumn('ancestor_id', 'integer', array('unsigned' => true));
$table->setPrimaryKey(array('object_identity_id', 'ancestor_id'));
$oidTable = $this->getTable($this->options['oid_table_name']);
$table->addForeignKeyConstraint($oidTable, array('object_identity_id'), array('id'), array('onDelete' => 'CASCADE', 'onUpdate' => 'CASCADE'));
$table->addForeignKeyConstraint($oidTable, array('ancestor_id'), array('id'), array('onDelete' => 'CASCADE', 'onUpdate' => 'CASCADE'));
}
/**
* Adds the security identity table to the schema
*
* @return void
*/
protected function addSecurityIdentitiesTable()
{
$table = $this->createTable($this->options['sid_table_name']);
$table->addColumn('id', 'integer', array('unsigned' => true, 'autoincrement' => 'auto'));
$table->addColumn('identifier', 'string', array('length' => 100));
$table->addColumn('username', 'boolean', array('default' => 0));
$table->setPrimaryKey(array('id'));
$table->addUniqueIndex(array('identifier', 'username'));
}
}

View File

@ -0,0 +1,679 @@
<?php
namespace Symfony\Component\Security\Acl\Domain;
use Doctrine\Common\PropertyChangedListener;
use Symfony\Component\Security\Acl\Model\AclInterface;
use Symfony\Component\Security\Acl\Model\AuditableAclInterface;
use Symfony\Component\Security\Acl\Model\EntryInterface;
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\PermissionInterface;
use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* An ACL implementation.
*
* Each object identity has exactly one associated ACL. Each ACL can have four
* different types of ACEs (class ACEs, object ACEs, class field ACEs, object field
* ACEs).
*
* You should not iterate over the ACEs yourself, but instead use isGranted(),
* or isFieldGranted(). These will utilize an implementation of PermissionGrantingStrategy
* internally.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class Acl implements AuditableAclInterface
{
protected $parentAcl;
protected $permissionGrantingStrategy;
protected $objectIdentity;
protected $classAces;
protected $classFieldAces;
protected $objectAces;
protected $objectFieldAces;
protected $id;
protected $loadedSids;
protected $entriesInheriting;
protected $listeners;
/**
* Constructor
*
* @param integer $id
* @param ObjectIdentityInterface $objectIdentity
* @param PermissionGrantingStrategyInterface $permissionGrantingStrategy
* @param array $loadedSids
* @param Boolean $entriesInheriting
* @return void
*/
public function __construct($id, ObjectIdentityInterface $objectIdentity, PermissionGrantingStrategyInterface $permissionGrantingStrategy, array $loadedSids = array(), $entriesInheriting)
{
$this->id = $id;
$this->objectIdentity = $objectIdentity;
$this->permissionGrantingStrategy = $permissionGrantingStrategy;
$this->loadedSids = $loadedSids;
$this->entriesInheriting = $entriesInheriting;
$this->parentAcl = null;
$this->classAces = array();
$this->classFieldAces = array();
$this->objectAces = array();
$this->objectFieldAces = array();
$this->listeners = array();
}
/**
* Adds a property changed listener
*
* @param PropertyChangedListener $listener
* @return void
*/
public function addPropertyChangedListener(PropertyChangedListener $listener)
{
$this->listeners[] = $listener;
}
/**
* {@inheritDoc}
*/
public function deleteClassAce($index)
{
$this->deleteAce('classAces', $index);
}
/**
* {@inheritDoc}
*/
public function deleteClassFieldAce($index, $field)
{
$this->deleteFieldAce('classFieldAces', $index, $field);
}
/**
* {@inheritDoc}
*/
public function deleteObjectAce($index)
{
$this->deleteAce('objectAces', $index);
}
/**
* {@inheritDoc}
*/
public function deleteObjectFieldAce($index, $field)
{
$this->deleteFieldAce('objectFieldAces', $index, $field);
}
/**
* {@inheritDoc}
*/
public function getClassAces()
{
return $this->classAces;
}
/**
* {@inheritDoc}
*/
public function getClassFieldAces($field)
{
return isset($this->classFieldAces[$field])? $this->classFieldAces[$field] : array();
}
/**
* {@inheritDoc}
*/
public function getObjectAces()
{
return $this->objectAces;
}
/**
* {@inheritDoc}
*/
public function getObjectFieldAces($field)
{
return isset($this->objectFieldAces[$field]) ? $this->objectFieldAces[$field] : array();
}
/**
* {@inheritDoc}
*/
public function getId()
{
return $this->id;
}
/**
* {@inheritDoc}
*/
public function getObjectIdentity()
{
return $this->objectIdentity;
}
/**
* {@inheritDoc}
*/
public function getParentAcl()
{
return $this->parentAcl;
}
/**
* {@inheritDoc}
*/
public function insertClassAce(SecurityIdentityInterface $sid, $mask, $index = 0, $granting = true, $strategy = null)
{
$this->insertAce('classAces', $index, $mask, $sid, $granting, $strategy);
}
/**
* {@inheritDoc}
*/
public function insertClassFieldAce($field, SecurityIdentityInterface $sid, $mask, $index = 0, $granting = true, $strategy = null)
{
$this->insertFieldAce('classFieldAces', $index, $field, $mask, $sid, $granting, $strategy);
}
/**
* {@inheritDoc}
*/
public function insertObjectAce(SecurityIdentityInterface $sid, $mask, $index = 0, $granting = true, $strategy = null)
{
$this->insertAce('objectAces', $index, $mask, $sid, $granting, $strategy);
}
/**
* {@inheritDoc}
*/
public function insertObjectFieldAce($field, SecurityIdentityInterface $sid, $mask, $index = 0, $granting = true, $strategy = null)
{
$this->insertFieldAce('objectFieldAces', $index, $field, $mask, $sid, $granting, $strategy);
}
/**
* {@inheritDoc}
*/
public function isEntriesInheriting()
{
return $this->entriesInheriting;
}
/**
* {@inheritDoc}
*/
public function isFieldGranted($field, array $masks, array $securityIdentities, $administrativeMode = false)
{
return $this->permissionGrantingStrategy->isFieldGranted($this, $field, $masks, $securityIdentities, $administrativeMode);
}
/**
* {@inheritDoc}
*/
public function isGranted(array $masks, array $securityIdentities, $administrativeMode = false)
{
return $this->permissionGrantingStrategy->isGranted($this, $masks, $securityIdentities, $administrativeMode);
}
/**
* {@inheritDoc}
*/
public function isSidLoaded($sids)
{
if (0 === count($this->loadedSids)) {
return true;
}
if (!is_array($sids)) {
$sids = array($sids);
}
foreach ($sids as $sid) {
if (!$sid instanceof SecurityIdentityInterface) {
throw new \InvalidArgumentException(
'$sid must be an instance of SecurityIdentityInterface.');
}
foreach ($this->loadedSids as $loadedSid) {
if ($loadedSid->equals($sid)) {
continue 2;
}
}
return false;
}
return true;
}
/**
* Implementation for the \Serializable interface
*
* @return string
*/
public function serialize()
{
return serialize(array(
null === $this->parentAcl ? null : $this->parentAcl->getId(),
$this->objectIdentity,
$this->classAces,
$this->classFieldAces,
$this->objectAces,
$this->objectFieldAces,
$this->id,
$this->loadedSids,
$this->entriesInheriting,
));
}
/**
* Implementation for the \Serializable interface
*
* @param string $serialized
* @return void
*/
public function unserialize($serialized)
{
list($this->parentAcl,
$this->objectIdentity,
$this->classAces,
$this->classFieldAces,
$this->objectAces,
$this->objectFieldAces,
$this->id,
$this->loadedSids,
$this->entriesInheriting
) = unserialize($serialized);
$this->listeners = array();
}
/**
* {@inheritDoc}
*/
public function setEntriesInheriting($boolean)
{
if ($this->entriesInheriting !== $boolean) {
$this->onPropertyChanged('entriesInheriting', $this->entriesInheriting, $boolean);
$this->entriesInheriting = $boolean;
}
}
/**
* {@inheritDoc}
*/
public function setParentAcl(AclInterface $acl)
{
if (null === $acl->getId()) {
throw new \InvalidArgumentException('$acl must have an ID.');
}
if ($this->parentAcl !== $acl) {
$this->onPropertyChanged('parentAcl', $this->parentAcl, $acl);
$this->parentAcl = $acl;
}
}
/**
* {@inheritDoc}
*/
public function updateClassAce($index, $mask, $strategy = null)
{
$this->updateAce('classAces', $index, $mask, $strategy);
}
/**
* {@inheritDoc}
*/
public function updateClassFieldAce($index, $field, $mask, $strategy = null)
{
$this->updateFieldAce('classFieldAces', $index, $field, $mask, $strategy);
}
/**
* {@inheritDoc}
*/
public function updateObjectAce($index, $mask, $strategy = null)
{
$this->updateAce('objectAces', $index, $mask, $strategy);
}
/**
* {@inheritDoc}
*/
public function updateObjectFieldAce($index, $field, $mask, $strategy = null)
{
$this->updateFieldAce('objectFieldAces', $index, $field, $mask, $strategy);
}
/**
* {@inheritDoc}
*/
public function updateClassAuditing($index, $auditSuccess, $auditFailure)
{
$this->updateAuditing($this->classAces, $index, $auditSuccess, $auditFailure);
}
/**
* {@inheritDoc}
*/
public function updateClassFieldAuditing($index, $field, $auditSuccess, $auditFailure)
{
if (!isset($this->classFieldAces[$field])) {
throw new \InvalidArgumentException(sprintf('There are no ACEs for field "%s".', $field));
}
$this->updateAuditing($this->classFieldAces[$field], $index, $auditSuccess, $auditFailure);
}
/**
* {@inheritDoc}
*/
public function updateObjectAuditing($index, $auditSuccess, $auditFailure)
{
$this->updateAuditing($this->objectAces, $index, $auditSuccess, $auditFailure);
}
/**
* {@inheritDoc}
*/
public function updateObjectFieldAuditing($index, $field, $auditSuccess, $auditFailure)
{
if (!isset($this->objectFieldAces[$field])) {
throw new \InvalidArgumentException(sprintf('There are no ACEs for field "%s".', $field));
}
$this->updateAuditing($this->objectFieldAces[$field], $index, $auditSuccess, $auditFailure);
}
/**
* Deletes an ACE
*
* @param string $property
* @param integer $index
* @throws \OutOfBoundsException
* @return void
*/
protected function deleteAce($property, $index)
{
$aces =& $this->$property;
if (!isset($aces[$index])) {
throw new \OutOfBoundsException(sprintf('The index "%d" does not exist.', $index));
}
$oldValue = $this->$property;
unset($aces[$index]);
$this->$property = array_values($this->$property);
$this->onPropertyChanged($property, $oldValue, $this->$property);
for ($i=$index,$c=count($this->$property); $i<$c; $i++) {
$this->onEntryPropertyChanged($aces[$i], 'aceOrder', $i+1, $i);
}
}
/**
* Deletes a field-based ACE
*
* @param string $property
* @param integer $index
* @param string $field
* @throws \OutOfBoundsException
* @return void
*/
protected function deleteFieldAce($property, $index, $field)
{
$aces =& $this->$property;
if (!isset($aces[$field][$index])) {
throw new \OutOfBoundsException(sprintf('The index "%d" does not exist.', $index));
}
$oldValue = $this->$property;
unset($aces[$field][$index]);
$aces[$field] = array_values($aces[$field]);
$this->onPropertyChanged($property, $oldValue, $this->$property);
for ($i=$index,$c=count($aces[$field]); $i<$c; $i++) {
$this->onEntryPropertyChanged($aces[$field][$i], 'aceOrder', $i+1, $i);
}
}
/**
* Inserts an ACE
*
* @param string $property
* @param integer $index
* @param integer $mask
* @param SecurityIdentityInterface $sid
* @param Boolean $granting
* @param string $strategy
* @throws \OutOfBoundsException
* @throws \InvalidArgumentException
* @return void
*/
protected function insertAce($property, $index, $mask, SecurityIdentityInterface $sid, $granting, $strategy = null)
{
if ($index < 0 || $index > count($this->$property)) {
throw new \OutOfBoundsException(sprintf('The index must be in the interval [0, %d].', count($this->$property)));
}
if (!is_int($mask)) {
throw new \InvalidArgumentException('$mask must be an integer.');
}
if (null === $strategy) {
if (true === $granting) {
$strategy = PermissionGrantingStrategy::ALL;
} else {
$strategy = PermissionGrantingStrategy::ANY;
}
}
$aces =& $this->$property;
$oldValue = $this->$property;
if (isset($aces[$index])) {
$this->$property = array_merge(
array_slice($this->$property, 0, $index),
array(true),
array_slice($this->$property, $index)
);
for ($i=$index,$c=count($this->$property)-1; $i<$c; $i++) {
$this->onEntryPropertyChanged($aces[$i+1], 'aceOrder', $i, $i+1);
}
}
$aces[$index] = new Entry(null, $this, $sid, $strategy, $mask, $granting, false, false);
$this->onPropertyChanged($property, $oldValue, $this->$property);
}
/**
* Inserts a field-based ACE
*
* @param string $property
* @param integer $index
* @param string $field
* @param integer $mask
* @param SecurityIdentityInterface $sid
* @param Boolean $granting
* @param string $strategy
* @throws \InvalidArgumentException
* @throws \OutOfBoundsException
* @return void
*/
protected function insertFieldAce($property, $index, $field, $mask, SecurityIdentityInterface $sid, $granting, $strategy = null)
{
if (0 === strlen($field)) {
throw new \InvalidArgumentException('$field cannot be empty.');
}
if (!is_int($mask)) {
throw new \InvalidArgumentException('$mask must be an integer.');
}
if (null === $strategy) {
if (true === $granting) {
$strategy = PermissionGrantingStrategy::ALL;
} else {
$strategy = PermissionGrantingStrategy::ANY;
}
}
$aces =& $this->$property;
if (!isset($aces[$field])) {
$aces[$field] = array();
}
if ($index < 0 || $index > count($aces[$field])) {
throw new \OutOfBoundsException(sprintf('The index must be in the interval [0, %d].', count($this->$property)));
}
$oldValue = $aces;
if (isset($aces[$field][$index])) {
$aces[$field] = array_merge(
array_slice($aces[$field], 0, $index),
array(true),
array_slice($aces[$field], $index)
);
for ($i=$index,$c=count($aces[$field])-1; $i<$c; $i++) {
$this->onEntryPropertyChanged($aces[$field][$i+1], 'aceOrder', $i, $i+1);
}
}
$aces[$field][$index] = new FieldEntry(null, $this, $field, $sid, $strategy, $mask, $granting, false, false);
$this->onPropertyChanged($property, $oldValue, $this->$property);
}
/**
* Called when a property of the ACL changes
*
* @param string $name
* @param mixed $oldValue
* @param mixed $newValue
* @return void
*/
protected function onPropertyChanged($name, $oldValue, $newValue)
{
foreach ($this->listeners as $listener) {
$listener->propertyChanged($this, $name, $oldValue, $newValue);
}
}
/**
* Called when a property of an ACE associated with this ACL changes
*
* @param EntryInterface $entry
* @param string $name
* @param mixed $oldValue
* @param mixed $newValue
* @return void
*/
protected function onEntryPropertyChanged(EntryInterface $entry, $name, $oldValue, $newValue)
{
foreach ($this->listeners as $listener) {
$listener->propertyChanged($entry, $name, $oldValue, $newValue);
}
}
/**
* Updates an ACE
*
* @param string $property
* @param integer $index
* @param integer $mask
* @param string $strategy
* @throws \OutOfBoundsException
* @return void
*/
protected function updateAce($property, $index, $mask, $strategy = null)
{
$aces =& $this->$property;
if (!isset($aces[$index])) {
throw new \OutOfBoundsException(sprintf('The index "%d" does not exist.', $index));
}
$ace = $aces[$index];
if ($mask !== $oldMask = $ace->getMask()) {
$this->onEntryPropertyChanged($ace, 'mask', $oldMask, $mask);
$ace->setMask($mask);
}
if (null !== $strategy && $strategy !== $oldStrategy = $ace->getStrategy()) {
$this->onEntryPropertyChanged($ace, 'strategy', $oldStrategy, $strategy);
$ace->setStrategy($strategy);
}
}
/**
* Updates auditing for an ACE
*
* @param array $aces
* @param integer $index
* @param Boolean $auditSuccess
* @param Boolean $auditFailure
* @throws \OutOfBoundsException
* @return void
*/
protected function updateAuditing(array &$aces, $index, $auditSuccess, $auditFailure)
{
if (!isset($aces[$index])) {
throw new \OutOfBoundsException(sprintf('The index "%d" does not exist.', $index));
}
if ($auditSuccess !== $aces[$index]->isAuditSuccess()) {
$this->onEntryPropertyChanged($aces[$index], 'auditSuccess', !$auditSuccess, $auditSuccess);
$aces[$index]->setAuditSuccess($auditSuccess);
}
if ($auditFailure !== $aces[$index]->isAuditFailure()) {
$this->onEntryPropertyChanged($aces[$index], 'auditFailure', !$auditFailure, $auditFailure);
$aces[$index]->setAuditFailure($auditFailure);
}
}
/**
* Updates a field-based ACE
*
* @param string $property
* @param integer $index
* @param string $field
* @param integer $mask
* @param string $strategy
* @throws \InvalidArgumentException
* @throws \OutOfBoundsException
* @return void
*/
protected function updateFieldAce($property, $index, $field, $mask, $strategy = null)
{
if (0 === strlen($field)) {
throw new \InvalidArgumentException('$field cannot be empty.');
}
$aces =& $this->$property;
if (!isset($aces[$field][$index])) {
throw new \OutOfBoundsException(sprintf('The index "%d" does not exist.', $index));
}
$ace = $aces[$field][$index];
if ($mask !== $oldMask = $ace->getMask()) {
$this->onEntryPropertyChanged($ace, 'mask', $oldMask, $mask);
$ace->setMask($mask);
}
if (null !== $strategy && $strategy !== $oldStrategy = $ace->getStrategy()) {
$this->onEntryPropertyChanged($ace, 'strategy', $oldStrategy, $strategy);
$ace->setStrategy($strategy);
}
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace Symfony\Component\Security\Acl\Domain;
use Symfony\Component\Security\Acl\Model\AuditableEntryInterface;
use Symfony\Component\Security\Acl\Model\EntryInterface;
use Symfony\Component\Security\Acl\Model\AuditLoggerInterface;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* Base audit logger implementation
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
abstract class AuditLogger implements AuditLoggerInterface
{
/**
* Performs some checks if logging was requested
*
* @param Boolean $granted
* @param EntryInterface $ace
* @return void
*/
public function logIfNeeded($granted, EntryInterface $ace)
{
if (!$ace instanceof AuditableEntryInterface) {
return;
}
if ($granted && $ace->isAuditSuccess()) {
$this->doLog($granted, $ace);
} else if (!$granted && $ace->isAuditFailure()) {
$this->doLog($granted, $ace);
}
}
/**
* This method is only called when logging is needed
*
* @param Boolean $granted
* @param EntryInterface $ace
* @return void
*/
abstract protected function doLog($granted, EntryInterface $ace);
}

View File

@ -0,0 +1,222 @@
<?php
namespace Symfony\Component\Security\Acl\Domain;
use Doctrine\Common\Cache\Cache;
use Symfony\Component\Security\Acl\Model\AclCacheInterface;
use Symfony\Component\Security\Acl\Model\AclInterface;
use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
use Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* This class is a wrapper around the actual cache implementation.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class DoctrineAclCache implements AclCacheInterface
{
const PREFIX = 'sf2_acl_';
protected $cache;
protected $prefix;
protected $permissionGrantingStrategy;
/**
* Constructor
*
* @param Cache $cache
* @param PermissionGrantingStrategyInterface $permissionGrantingStrategy
* @param string $prefix
* @return void
*/
public function __construct(Cache $cache, PermissionGrantingStrategyInterface $permissionGrantingStrategy, $prefix = self::PREFIX)
{
if (0 === strlen($prefix)) {
throw new \InvalidArgumentException('$prefix cannot be empty.');
}
$this->cache = $cache;
$this->permissionGrantingStrategy = $permissionGrantingStrategy;
$this->prefix = $prefix;
}
/**
* {@inheritDoc}
*/
public function clearCache()
{
$this->cache->deleteByPrefix($this->prefix);
}
/**
* {@inheritDoc}
*/
public function evictFromCacheById($aclId)
{
$lookupKey = $this->getAliasKeyForIdentity($aclId);
if (!$this->cache->contains($lookupKey)) {
return;
}
$key = $this->cache->fetch($lookupKey);
if ($this->cache->contains($key)) {
$this->cache->delete($key);
}
$this->cache->delete($lookupKey);
}
/**
* {@inheritDoc}
*/
public function evictFromCacheByIdentity(ObjectIdentityInterface $oid)
{
$key = $this->getDataKeyByIdentity($oid);
if (!$this->cache->contains($key)) {
return;
}
$this->cache->delete($key);
}
/**
* {@inheritDoc}
*/
public function getFromCacheById($aclId)
{
$lookupKey = $this->getAliasKeyForIdentity($aclId);
if (!$this->cache->contains($lookupKey)) {
return null;
}
$key = $this->cache->fetch($lookupKey);
if (!$this->cache->contains($key)) {
$this->cache->delete($lookupKey);
return null;
}
return $this->unserializeAcl($this->cache->fetch($key));
}
/**
* {@inheritDoc}
*/
public function getFromCacheByIdentity(ObjectIdentityInterface $oid)
{
$key = $this->getDataKeyByIdentity($oid);
if (!$this->cache->contains($key)) {
return null;
}
return $this->unserializeAcl($this->cache->fetch($key));
}
/**
* {@inheritDoc}
*/
public function putInCache(AclInterface $acl)
{
if (null === $acl->getId()) {
throw new \InvalidArgumentException('Transient ACLs cannot be cached.');
}
if (null !== $parentAcl = $acl->getParentAcl()) {
$this->putInCache($parentAcl);
}
$key = $this->getDataKeyByIdentity($acl->getObjectIdentity());
$this->cache->save($key, serialize($acl));
$this->cache->save($this->getAliasKeyForIdentity($acl->getId()), $key);
}
/**
* Unserializes the ACL.
*
* @param string $serialized
* @return AclInterface
*/
protected function unserializeAcl($serialized)
{
$acl = unserialize($serialized);
if (null !== $parentId = $acl->getParentAcl()) {
$parentAcl = $this->getFromCacheById($parentId);
if (null === $parentAcl) {
return null;
}
$acl->setParentAcl($parentAcl);
}
$reflectionProperty = new \ReflectionProperty($acl, 'permissionGrantingStrategy');
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($acl, $this->permissionGrantingStrategy);
$reflectionProperty->setAccessible(false);
$aceAclProperty = new \ReflectionProperty('Symfony\Component\Security\Acl\Domain\Entry', 'id');
$aceAclProperty->setAccessible(true);
foreach ($acl->getObjectAces() as $ace) {
$aceAclProperty->setValue($ace, $acl);
}
foreach ($acl->getClassAces() as $ace) {
$aceAclProperty->setValue($ace, $acl);
}
$aceClassFieldProperty = new \ReflectionProperty($acl, 'classFieldAces');
$aceClassFieldProperty->setAccessible(true);
foreach ($aceClassFieldProperty->getValue($acl) as $field => $aces) {
foreach ($aces as $ace) {
$aceAclProperty->setValue($ace, $acl);
}
}
$aceClassFieldProperty->setAccessible(false);
$aceObjectFieldProperty = new \ReflectionProperty($acl, 'objectFieldAces');
$aceObjectFieldProperty->setAccessible(true);
foreach ($aceObjectFieldProperty->getValue($acl) as $field => $aces) {
foreach ($aces as $ace) {
$aceAclProperty->setValue($ace, $acl);
}
}
$aceObjectFieldProperty->setAccessible(false);
$aceAclProperty->setAccessible(false);
return $acl;
}
/**
* Returns the key for the object identity
*
* @param ObjectIdentityInterface $oid
* @return string
*/
protected function getDataKeyByIdentity(ObjectIdentityInterface $oid)
{
return $this->prefix.md5($oid->getType()).sha1($oid->getType())
.'_'.md5($oid->getIdentifier()).sha1($oid->getIdentifier());
}
/**
* Returns the alias key for the object identity key
*
* @param string $aclId
* @return string
*/
protected function getAliasKeyForIdentity($aclId)
{
return $this->prefix.$aclId;
}
}

View File

@ -0,0 +1,215 @@
<?php
namespace Symfony\Component\Security\Acl\Domain;
use Symfony\Component\Security\Acl\Model\AclInterface;
use Symfony\Component\Security\Acl\Model\AuditableEntryInterface;
use Symfony\Component\Security\Acl\Model\EntryInterface;
use Symfony\Component\Security\Acl\Model\PermissionInterface;
use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* Auditable ACE implementation
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class Entry implements AuditableEntryInterface
{
protected $acl;
protected $mask;
protected $id;
protected $securityIdentity;
protected $strategy;
protected $auditFailure;
protected $auditSuccess;
protected $granting;
/**
* Constructor
*
* @param integer $id
* @param AclInterface $acl
* @param SecurityIdentityInterface $sid
* @param string $strategy
* @param integer $mask
* @param Boolean $granting
* @param Boolean $auditFailure
* @param Boolean $auditSuccess
*/
public function __construct($id, AclInterface $acl, SecurityIdentityInterface $sid, $strategy, $mask, $granting, $auditFailure, $auditSuccess)
{
$this->id = $id;
$this->acl = $acl;
$this->securityIdentity = $sid;
$this->strategy = $strategy;
$this->mask = $mask;
$this->granting = $granting;
$this->auditFailure = $auditFailure;
$this->auditSuccess = $auditSuccess;
}
/**
* {@inheritDoc}
*/
public function getAcl()
{
return $this->acl;
}
/**
* {@inheritDoc}
*/
public function getMask()
{
return $this->mask;
}
/**
* {@inheritDoc}
*/
public function getId()
{
return $this->id;
}
/**
* {@inheritDoc}
*/
public function getSecurityIdentity()
{
return $this->securityIdentity;
}
/**
* {@inheritDoc}
*/
public function getStrategy()
{
return $this->strategy;
}
/**
* {@inheritDoc}
*/
public function isAuditFailure()
{
return $this->auditFailure;
}
/**
* {@inheritDoc}
*/
public function isAuditSuccess()
{
return $this->auditSuccess;
}
/**
* {@inheritDoc}
*/
public function isGranting()
{
return $this->granting;
}
/**
* Turns on/off auditing on permissions denials.
*
* Do never call this method directly. Use the respective methods on the
* AclInterface instead.
*
* @param Boolean $boolean
* @return void
*/
public function setAuditFailure($boolean)
{
$this->auditFailure = $boolean;
}
/**
* Turns on/off auditing on permission grants.
*
* Do never call this method directly. Use the respective methods on the
* AclInterface instead.
*
* @param Boolean $boolean
* @return void
*/
public function setAuditSuccess($boolean)
{
$this->auditSuccess = $boolean;
}
/**
* Sets the permission mask
*
* Do never call this method directly. Use the respective methods on the
* AclInterface instead.
*
* @param integer $mask
* @return void
*/
public function setMask($mask)
{
$this->mask = $mask;
}
/**
* Sets the mask comparison strategy
*
* Do never call this method directly. Use the respective methods on the
* AclInterface instead.
*
* @param string $strategy
* @return void
*/
public function setStrategy($strategy)
{
$this->strategy = $strategy;
}
/**
* Implementation of \Serializable
*
* @return string
*/
public function serialize()
{
return serialize(array(
$this->mask,
$this->id,
$this->securityIdentity,
$this->strategy,
$this->auditFailure,
$this->auditSuccess,
$this->granting,
));
}
/**
* Implementation of \Serializable
*
* @param string $serialized
* @return void
*/
public function unserialize($serialized)
{
list($this->mask,
$this->id,
$this->securityIdentity,
$this->strategy,
$this->auditFailure,
$this->auditSuccess,
$this->granting
) = unserialize($serialized);
}
}

View File

@ -0,0 +1,88 @@
<?php
namespace Symfony\Component\Security\Acl\Domain;
use Symfony\Component\Security\Acl\Model\AclInterface;
use Symfony\Component\Security\Acl\Model\FieldAwareEntryInterface;
use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* Field-aware ACE implementation which is auditable
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class FieldEntry extends Entry implements FieldAwareEntryInterface
{
protected $field;
/**
* Constructor
*
* @param integer $id
* @param AclInterface $acl
* @param string $field
* @param SecurityIdentityInterface $sid
* @param string $strategy
* @param integer $mask
* @param Boolean $granting
* @param Boolean $auditFailure
* @param Boolean $auditSuccess
* @return void
*/
public function __construct($id, AclInterface $acl, $field, SecurityIdentityInterface $sid, $strategy, $mask, $granting, $auditFailure, $auditSuccess)
{
parent::__construct($id, $acl, $sid, $strategy, $mask, $granting, $auditFailure, $auditSuccess);
$this->field = $field;
}
/**
* {@inheritDoc}
*/
public function getField()
{
return $this->field;
}
/**
* {@inheritDoc}
*/
public function serialize()
{
return serialize(array(
$this->field,
$this->mask,
$this->id,
$this->securityIdentity,
$this->strategy,
$this->auditFailure,
$this->auditSuccess,
$this->granting,
));
}
/**
* {@inheritDoc}
*/
public function unserialize($serialized)
{
list($this->field,
$this->mask,
$this->id,
$this->securityIdentity,
$this->strategy,
$this->auditFailure,
$this->auditSuccess,
$this->granting
) = unserialize($serialized);
}
}

View File

@ -0,0 +1,106 @@
<?php
namespace Symfony\Component\Security\Acl\Domain;
use Symfony\Component\Security\Acl\Exception\InvalidDomainObjectException;
use Symfony\Component\Security\Acl\Model\DomainObjectInterface;
use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* ObjectIdentity implementation
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ObjectIdentity implements ObjectIdentityInterface
{
protected $identifier;
protected $type;
/**
* Constructor
*
* @param string $identifier
* @param string $type
* @return void
*/
public function __construct($identifier, $type)
{
if (0 === strlen($identifier)) {
throw new \InvalidArgumentException('$identifier cannot be empty.');
}
if (0 === strlen($type)) {
throw new \InvalidArgumentException('$type cannot be empty.');
}
$this->identifier = $identifier;
$this->type = $type;
}
/**
* Constructs an ObjectIdentity for the given domain object
*
* @param object $domainObject
* @throws \InvalidArgumentException
* @return ObjectIdentity
*/
public static function fromDomainObject($domainObject)
{
if (!is_object($domainObject)) {
throw new InvalidDomainObjectException('$domainObject must be an object.');
}
if ($domainObject instanceof DomainObjectInterface) {
return new self($domainObject->getObjectIdentifier(), get_class($domainObject));
} else if (method_exists($domainObject, 'getId')) {
return new self($domainObject->getId(), get_class($domainObject));
}
throw new InvalidDomainObjectException('$domainObject must either implement the DomainObjectInterface, or have a method named "getId".');
}
/**
* {@inheritDoc}
*/
public function getIdentifier()
{
return $this->identifier;
}
/**
* {@inheritDoc}
*/
public function getType()
{
return $this->type;
}
/**
* {@inheritDoc}
*/
public function equals(ObjectIdentityInterface $identity)
{
// comparing the identifier with === might lead to problems, so we
// waive this restriction
return $this->identifier == $identity->getIdentifier()
&& $this->type === $identity->getType();
}
/**
* Returns a textual representation of this object identity
*
* @return string
*/
public function __toString()
{
return sprintf('ObjectIdentity(%s, %s)', $this->identifier, $this->type);
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace Symfony\Component\Security\Acl\Domain;
use Symfony\Component\Security\Acl\Exception\InvalidDomainObjectException;
use Symfony\Component\Security\Acl\Model\ObjectIdentityRetrievalStrategyInterface;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* Strategy to be used for retrieving object identities from domain objects
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ObjectIdentityRetrievalStrategy implements ObjectIdentityRetrievalStrategyInterface
{
/**
* {@inheritDoc}
*/
public function getObjectIdentity($domainObject)
{
try {
return ObjectIdentity::fromDomainObject($domainObject);
} catch (InvalidDomainObjectException $failed) {
return null;
}
}
}

View File

@ -0,0 +1,229 @@
<?php
namespace Symfony\Component\Security\Acl\Domain;
use Symfony\Component\Security\Acl\Exception\NoAceFoundException;
use Symfony\Component\Security\Acl\Exception\SidNotLoadedException;
use Symfony\Component\Security\Acl\Model\AclInterface;
use Symfony\Component\Security\Acl\Model\AuditLoggerInterface;
use Symfony\Component\Security\Acl\Model\EntryInterface;
use Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface;
use Symfony\Component\Security\Acl\Model\PermissionInterface;
use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* The permission granting strategy to apply to the access control list.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class PermissionGrantingStrategy implements PermissionGrantingStrategyInterface
{
const EQUAL = 'equal';
const ALL = 'all';
const ANY = 'any';
protected $auditLogger;
/**
* Sets the audit logger
*
* @param AuditLoggerInterface $auditLogger
* @return void
*/
public function setAuditLogger(AuditLoggerInterface $auditLogger)
{
$this->auditLogger = $auditLogger;
}
/**
* Returns the audit logger
*
* @return AuditLoggerInterface
*/
public function getAuditLogger()
{
return $this->auditLogger;
}
/**
* {@inheritDoc}
*/
public function isGranted(AclInterface $acl, array $masks, array $sids, $administrativeMode = false)
{
try {
try {
$aces = $acl->getObjectAces();
if (0 === count($aces)) {
throw new NoAceFoundException('No applicable ACE was found.');
}
return $this->hasSufficientPermissions($acl, $aces, $masks, $sids, $administrativeMode);
} catch (NoAceFoundException $noObjectAce) {
$aces = $acl->getClassAces();
if (0 === count($aces)) {
throw new NoAceFoundException('No applicable ACE was found.');
}
return $this->hasSufficientPermissions($acl, $aces, $masks, $sids, $administrativeMode);
}
} catch (NoAceFoundException $noClassAce) {
if ($acl->isEntriesInheriting() && null !== $parentAcl = $acl->getParentAcl()) {
return $parentAcl->isGranted($masks, $sids, $administrativeMode);
}
throw new NoAceFoundException('No applicable ACE was found.');
}
}
/**
* {@inheritDoc}
*/
public function isFieldGranted(AclInterface $acl, $field, array $masks, array $sids, $administrativeMode = false)
{
try {
try {
$aces = $acl->getObjectFieldAces($field);
if (0 === count($aces)) {
throw new NoAceFoundException('No applicable ACE was found.');
}
return $this->hasSufficientPermissions($acl, $aces, $masks, $sids, $administrativeMode);
} catch (NoAceFoundException $noObjectAces) {
$aces = $acl->getClassFieldAces($field);
if (0 === count($aces)) {
throw new NoAceFoundException('No applicable ACE was found.');
}
return $this->hasSufficientPermissions($acl, $aces, $masks, $sids, $administrativeMode);
}
} catch (NoAceFoundException $noClassAces) {
if ($acl->isEntriesInheriting() && null !== $parentAcl = $acl->getParentAcl()) {
return $parentAcl->isFieldGranted($field, $masks, $sids, $administrativeMode);
}
throw new NoAceFoundException('No applicable ACE was found.');
}
}
/**
* Makes an authorization decision.
*
* The order of ACEs, and SIDs is significant; the order of permission masks
* not so much. It is important to note that the more specific security
* identities should be at the beginning of the SIDs array in order for this
* strategy to produce intuitive authorization decisions.
*
* First, we will iterate over permissions, then over security identities.
* For each combination of permission, and identity we will test the
* available ACEs until we find one which is applicable.
*
* The first applicable ACE will make the ultimate decision for the
* permission/identity combination. If it is granting, this method will return
* true, if it is denying, the method will continue to check the next
* permission/identity combination.
*
* This process is repeated until either a granting ACE is found, or no
* permission/identity combinations are left. In the latter case, we will
* call this method on the parent ACL if it exists, and isEntriesInheriting
* is true. Otherwise, we will either throw an NoAceFoundException, or deny
* access finally.
*
* @param AclInterface $acl
* @param array $aces an array of ACE to check against
* @param array $masks an array of permission masks
* @param array $sids an array of SecurityIdentityInterface implementations
* @param Boolean $administrativeMode true turns off audit logging
* @return Boolean true, or false; either granting, or denying access respectively.
*/
protected function hasSufficientPermissions(AclInterface $acl, array $aces, array $masks, array $sids, $administrativeMode)
{
$firstRejectedAce = null;
foreach ($masks as $requiredMask) {
foreach ($sids as $sid) {
if (!$acl->isSidLoaded($sid)) {
throw new SidNotLoadedException(sprintf('The SID "%s" has not been loaded.', $sid));
}
foreach ($aces as $ace) {
if ($this->isAceApplicable($requiredMask, $sid, $ace)) {
if ($ace->isGranting()) {
if (!$administrativeMode && null !== $this->auditLogger) {
$this->auditLogger->logIfNeeded(true, $ace);
}
return true;
}
if (null === $firstRejectedAce) {
$firstRejectedAce = $ace;
}
break 2;
}
}
}
}
if (null !== $firstRejectedAce) {
if (!$administrativeMode && null !== $this->auditLogger) {
$this->auditLogger->logIfNeeded(false, $firstRejectedAce);
}
return false;
}
throw new NoAceFoundException('No applicable ACE was found.');
}
/**
* Determines whether the ACE is applicable to the given permission/security
* identity combination.
*
* Per default, we support three different comparison strategies.
*
* Strategy ALL:
* The ACE will be considered applicable when all the turned-on bits in the
* required mask are also turned-on in the ACE mask.
*
* Strategy ANY:
* The ACE will be considered applicable when any of the turned-on bits in
* the required mask is also turned-on the in the ACE mask.
*
* Strategy EQUAL:
* The ACE will be considered applicable when the bitmasks are equal.
*
* @param SecurityIdentityInterface $sid
* @param EntryInterface $ace
* @param int $requiredMask
* @return Boolean
*/
protected function isAceApplicable($requiredMask, SecurityIdentityInterface $sid, EntryInterface $ace)
{
if (false === $ace->getSecurityIdentity()->equals($sid)) {
return false;
}
$strategy = $ace->getStrategy();
if (self::ALL === $strategy) {
return $requiredMask === ($ace->getMask() & $requiredMask);
} else if (self::ANY === $strategy) {
return 0 !== ($ace->getMask() & $requiredMask);
} else if (self::EQUAL === $strategy) {
return $requiredMask === $ace->getMask();
} else {
throw new \RuntimeException(sprintf('The strategy "%s" is not supported.', $strategy));
}
}
}

View File

@ -0,0 +1,74 @@
<?php
namespace Symfony\Component\Security\Acl\Domain;
use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface;
use Symfony\Component\Security\Role\Role;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* A SecurityIdentity implementation for roles
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class RoleSecurityIdentity implements SecurityIdentityInterface
{
protected $role;
/**
* Constructor
*
* @param mixed $role a Role instance, or its string representation
* @return void
*/
public function __construct($role)
{
if ($role instanceof Role) {
$role = $role->getRole();
}
$this->role = $role;
}
/**
* Returns the role name
*
* @return string
*/
public function getRole()
{
return $this->role;
}
/**
* {@inheritDoc}
*/
public function equals(SecurityIdentityInterface $sid)
{
if (!$sid instanceof RoleSecurityIdentity) {
return false;
}
return $this->role === $sid->getRole();
}
/**
* Returns a textual representation of this security identity.
*
* This is solely used for debugging purposes, not to make an equality decision.
*
* @return string
*/
public function __toString()
{
return sprintf('RoleSecurityIdentity(%s)', $this->role);
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace Symfony\Component\Security\Acl\Domain;
use Symfony\Component\Security\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Acl\Model\SecurityIdentityRetrievalStrategyInterface;
use Symfony\Component\Security\Authentication\AuthenticationTrustResolver;
use Symfony\Component\Security\Role\RoleHierarchyInterface;
use Symfony\Component\Security\Authorization\Voter\AuthenticatedVoter;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* Strategy for retrieving security identities
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class SecurityIdentityRetrievalStrategy implements SecurityIdentityRetrievalStrategyInterface
{
protected $roleHierarchy;
protected $authenticationTrustResolver;
/**
* Constructor
*
* @param RoleHierarchyInterface $roleHierarchy
* @param AuthenticationTrustResolver $authenticationTrustResolver
* @return void
*/
public function __construct(RoleHierarchyInterface $roleHierarchy, AuthenticationTrustResolver $authenticationTrustResolver)
{
$this->roleHierarchy = $roleHierarchy;
$this->authenticationTrustResolver = $authenticationTrustResolver;
}
/**
* {@inheritDoc}
*/
public function getSecurityIdentities(TokenInterface $token)
{
$sids = array();
if (false === $this->authenticationTrustResolver->isAnonymous($token)) {
$sids[] = new UserSecurityIdentity($token);
}
// add all reachable roles
foreach ($this->roleHierarchy->getReachableRoles($token->getRoles()) as $role) {
$sids[] = new RoleSecurityIdentity($role);
}
// add built-in special roles
if ($this->authenticationTrustResolver->isFullFledged($token)) {
$sids[] = new RoleSecurityIdentity(AuthenticatedVoter::IS_AUTHENTICATED_FULLY);
$sids[] = new RoleSecurityIdentity(AuthenticatedVoter::IS_AUTHENTICATED_REMEMBERED);
$sids[] = new RoleSecurityIdentity(AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY);
} else if ($this->authenticationTrustResolver->isRememberMe($token)) {
$sids[] = new RoleSecurityIdentity(AuthenticatedVoter::IS_AUTHENTICATED_REMEMBERED);
$sids[] = new RoleSecurityIdentity(AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY);
} else if ($this->authenticationTrustResolver->isAnonymous($token)) {
$sids[] = new RoleSecurityIdentity(AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY);
}
return $sids;
}
}

View File

@ -0,0 +1,83 @@
<?php
namespace Symfony\Component\Security\Acl\Domain;
use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface;
use Symfony\Component\Security\Authentication\Token\TokenInterface;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* A SecurityIdentity implementation used for actual users
*
* FIXME: We need to also store the user provider id since the
* username might not be unique across all available user
* providers.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class UserSecurityIdentity implements SecurityIdentityInterface
{
protected $username;
/**
* Constructor
*
* @param mixed $username the username representation, or a TokenInterface
* implementation
* @return void
*/
public function __construct($username)
{
if ($username instanceof TokenInterface) {
$username = (string) $username;
}
if (0 === strlen($username)) {
throw new \InvalidArgumentException('$username must not be empty.');
}
$this->username = $username;
}
/**
* Returns the username
*
* @return string
*/
public function getUsername()
{
return $this->username;
}
/**
* {@inheritDoc}
*/
public function equals(SecurityIdentityInterface $sid)
{
if (!$sid instanceof UserSecurityIdentity) {
return false;
}
return $this->username === $sid->getUsername();
}
/**
* A textual representation of this security identity.
*
* This is not used for equality comparison, but only for debugging.
*
* @return string
*/
public function __toString()
{
return sprintf('UserSecurityIdentity(%s)', $this->username);
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Symfony\Component\Security\Acl\Exception;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* This exception is thrown when someone tries to create an ACL for an object
* identity that already has one.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class AclAlreadyExistsException extends Exception
{
}

View File

@ -0,0 +1,22 @@
<?php
namespace Symfony\Component\Security\Acl\Exception;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* This exception is thrown when we cannot locate an ACL for a passed
* ObjectIdentity implementation.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class AclNotFoundException extends Exception
{
}

View File

@ -0,0 +1,13 @@
<?php
namespace Symfony\Component\Security\Acl\Exception;
/**
* This exception is thrown whenever you change shared properties of more than
* one ACL of the same class type concurrently.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ConcurrentModificationException extends Exception
{
}

View File

@ -0,0 +1,21 @@
<?php
namespace Symfony\Component\Security\Acl\Exception;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* Base ACL exception
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class Exception extends \Exception
{
}

View File

@ -0,0 +1,13 @@
<?php
namespace Symfony\Component\Security\Acl\Exception;
/**
* This exception is thrown when ObjectIdentity fails to construct an object
* identity from the passed domain object.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class InvalidDomainObjectException extends Exception
{
}

View File

@ -0,0 +1,22 @@
<?php
namespace Symfony\Component\Security\Acl\Exception;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* This exception is thrown when we cannot locate an ACE that matches the
* combination of permission masks and security identities.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class NoAceFoundException extends Exception
{
}

View File

@ -0,0 +1,22 @@
<?php
namespace Symfony\Component\Security\Acl\Exception;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* This exception is thrown when ACEs for an SID are requested which has not
* been loaded from the database.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class SidNotLoadedException extends Exception
{
}

View File

@ -0,0 +1,69 @@
<?php
namespace Symfony\Component\Security\Acl\Model;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* AclCache Interface
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface AclCacheInterface
{
/**
* Removes an ACL from the cache
*
* @param string $primaryKey a serialized primary key
* @return void
*/
function evictFromCacheById($primaryKey);
/**
* Removes an ACL from the cache
*
* The ACL which is returned, must reference the passed object identity.
*
* @param ObjectIdentityInterface $oid
* @return void
*/
function evictFromCacheByIdentity(ObjectIdentityInterface $oid);
/**
* Retrieves an ACL for the given object identity primary key from the cache
*
* @param integer $primaryKey
* @return AclInterface
*/
function getFromCacheById($primaryKey);
/**
* Retrieves an ACL for the given object identity from the cache
*
* @param ObjectIdentityInterface $oid
* @return AclInterface
*/
function getFromCacheByIdentity(ObjectIdentityInterface $oid);
/**
* Stores a new ACL in the cache
*
* @param AclInterface $acl
* @return void
*/
function putInCache(AclInterface $acl);
/**
* Removes all ACLs from the cache
*
* @return void
*/
function clearCache();
}

View File

@ -0,0 +1,106 @@
<?php
namespace Symfony\Component\Security\Acl\Model;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* This interface represents an access control list (ACL) for a domain object.
* Each domain object can have exactly one associated ACL.
*
* An ACL contains all access control entries (ACE) for a given domain object.
* In order to avoid needing references to the domain object itself, implementations
* use ObjectIdentity implementations as an additional level of indirection.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface AclInterface extends \Serializable
{
/**
* Returns all class-based ACEs associated with this ACL
*
* @return array
*/
function getClassAces();
/**
* Returns all class-field-based ACEs associated with this ACL
*
* @param string $field
* @return array
*/
function getClassFieldAces($field);
/**
* Returns all object-based ACEs associated with this ACL
*
* @return array
*/
function getObjectAces();
/**
* Returns all object-field-based ACEs associated with this ACL
*
* @param string $field
* @return array
*/
function getObjectFieldAces($field);
/**
* Returns the object identity associated with this ACL
*
* @return ObjectIdentityInterface
*/
function getObjectIdentity();
/**
* Returns the parent ACL, or null if there is none.
*
* @return AclInterface|null
*/
function getParentAcl();
/**
* Whether this ACL is inheriting ACEs from a parent ACL.
*
* @return Boolean
*/
function isEntriesInheriting();
/**
* Determines whether field access is granted
*
* @param string $field
* @param array $masks
* @param array $securityIdentities
* @param Boolean $administrativeMode
* @return Boolean
*/
function isFieldGranted($field, array $masks, array $securityIdentities, $administrativeMode = false);
/**
* Determines whether access is granted
*
* @throws NoAceFoundException when no ACE was applicable for this request
* @param array $masks
* @param array $securityIdentities
* @param Boolean $administrativeMode
* @return Boolean
*/
function isGranted(array $masks, array $securityIdentities, $administrativeMode = false);
/**
* Whether the ACL has loaded ACEs for all of the passed security identities
*
* @param mixed $securityIdentities an implementation of SecurityIdentityInterface, or an array thereof
* @return Boolean
*/
function isSidLoaded($securityIdentities);
}

View File

@ -0,0 +1,49 @@
<?php
namespace Symfony\Component\Security\Acl\Model;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* Provides a common interface for retrieving ACLs.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface AclProviderInterface
{
/**
* Retrieves all child object identities from the database
*
* @param ObjectIdentityInterface $parentOid
* @param Boolean $directChildrenOnly
* @return array returns an array of child 'ObjectIdentity's
*/
function findChildren(ObjectIdentityInterface $parentOid, $directChildrenOnly = false);
/**
* Returns the ACL that belongs to the given object identity
*
* @throws AclNotFoundException when there is no ACL
* @param ObjectIdentityInterface $oid
* @param array $sids
* @return AclInterface
*/
function findAcl(ObjectIdentityInterface $oid, array $sids = array());
/**
* Returns the ACLs that belong to the given object identities
*
* @throws AclNotFoundException when we cannot find an ACL for all identities
* @param array $oids an array of ObjectIdentityInterface implementations
* @param array $sids an array of SecurityIdentityInterface implementations
* @return \SplObjectStorage mapping the passed object identities to ACLs
*/
function findAcls(array $oids, array $sids = array());
}

View File

@ -0,0 +1,30 @@
<?php
namespace Symfony\Component\Security\Acl\Model;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* Interface for audit loggers
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface AuditLoggerInterface
{
/**
* This method is called whenever access is granted, or denied, and
* administrative mode is turned off.
*
* @param Boolean $granted
* @param EntryInterface $ace
* @return void
*/
function logIfNeeded($granted, EntryInterface $ace);
}

View File

@ -0,0 +1,63 @@
<?php
namespace Symfony\Component\Security\Acl\Model;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* This interface adds auditing capabilities to the ACL.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface AuditableAclInterface extends MutableAclInterface
{
/**
* Updates auditing for class-based ACE
*
* @param integer $index
* @param Boolean $auditSuccess
* @param Boolean $auditFailure
* @return void
*/
function updateClassAuditing($index, $auditSuccess, $auditFailure);
/**
* Updates auditing for class-field-based ACE
*
* @param integer $index
* @param string $field
* @param Boolean $auditSuccess
* @param Boolean $auditFailure
* @return void
*/
function updateClassFieldAuditing($index, $field, $auditSuccess, $auditFailure);
/**
* Updates auditing for object-based ACE
*
* @param integer $index
* @param Boolean $auditSuccess
* @param Boolean $auditFailure
* @return void
*/
function updateObjectAuditing($index, $auditSuccess, $auditFailure);
/**
* Updates auditing for object-field-based ACE
*
* @param integer $index
* @param string $field
* @param Boolean $auditSuccess
* @param Boolean $auditFailure
* @return void
*/
function updateObjectFieldAuditing($index, $field, $auditSuccess, $auditFailure);
}

View File

@ -0,0 +1,34 @@
<?php
namespace Symfony\Component\Security\Acl\Model;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* ACEs can implement this interface if they support auditing capabilities.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface AuditableEntryInterface extends EntryInterface
{
/**
* Whether auditing for successful grants is turned on
*
* @return Boolean
*/
function isAuditFailure();
/**
* Whether auditing for successful denies is turned on
*
* @return Boolean
*/
function isAuditSuccess();
}

View File

@ -0,0 +1,29 @@
<?php
namespace Symfony\Component\Security\Acl\Model;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* This method can be implemented by domain objects which you want to store
* ACLs for if they do not have a getId() method, or getId() does not return
* a unique identifier.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface DomainObjectInterface
{
/**
* Returns a unique identifier for this domain object.
*
* @return string
*/
function getObjectIdentifier();
}

View File

@ -0,0 +1,65 @@
<?php
namespace Symfony\Component\Security\Acl\Model;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* This class represents an individual entry in the ACL list.
*
* Instances MUST be immutable, as they are returned by the ACL and should not
* allow client modification.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface EntryInterface extends \Serializable
{
/**
* The ACL this ACE is associated with.
*
* @return AclInterface
*/
function getAcl();
/**
* The primary key of this ACE
*
* @return integer
*/
function getId();
/**
* The permission mask of this ACE
*
* @return integer
*/
function getMask();
/**
* The security identity associated with this ACE
*
* @return SecurityIdentityInterface
*/
function getSecurityIdentity();
/**
* The strategy for comparing masks
*
* @return string
*/
function getStrategy();
/**
* Returns whether this ACE is granting, or denying
*
* @return Boolean
*/
function isGranting();
}

View File

@ -0,0 +1,22 @@
<?php
namespace Symfony\Component\Security\Acl\Model;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* Interface for entries which are restricted to specific fields
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface FieldAwareEntryInterface
{
function getField();
}

View File

@ -0,0 +1,174 @@
<?php
namespace Symfony\Component\Security\Acl\Model;
use Doctrine\Common\NotifyPropertyChanged;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* This interface adds mutators for the AclInterface.
*
* All changes to Access Control Entries must go through this interface. Access
* Control Entries must never be modified directly.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface MutableAclInterface extends AclInterface, NotifyPropertyChanged
{
/**
* Deletes a class-based ACE
*
* @param integer $index
* @return void
*/
function deleteClassAce($index);
/**
* Deletes a class-field-based ACE
*
* @param integer $index
* @param string $field
* @return void
*/
function deleteClassFieldAce($index, $field);
/**
* Deletes an object-based ACE
*
* @param integer $index
* @return void
*/
function deleteObjectAce($index);
/**
* Deletes an object-field-based ACE
*
* @param integer $index
* @param string $field
* @return void
*/
function deleteObjectFieldAce($index, $field);
/**
* Returns the primary key of this ACL
*
* @return integer
*/
function getId();
/**
* Inserts a class-based ACE
*
* @param SecurityIdentityInterface $sid
* @param integer $mask
* @param integer $index
* @param Boolean $granting
* @param string $strategy
* @return void
*/
function insertClassAce(SecurityIdentityInterface $sid, $mask, $index = 0, $granting = true, $strategy = null);
/**
* Inserts a class-field-based ACE
*
* @param string $field
* @param SecurityIdentityInterface $sid
* @param integer $mask
* @param integer $index
* @param Boolean $granting
* @param string $strategy
* @return void
*/
function insertClassFieldAce($field, SecurityIdentityInterface $sid, $mask, $index = 0, $granting = true, $strategy = null);
/**
* Inserts an object-based ACE
*
* @param SecurityIdentityInterface $sid
* @param integer $mask
* @param integer $index
* @param Boolean $granting
* @param string $strategy
* @return void
*/
function insertObjectAce(SecurityIdentityInterface $sid, $mask, $index = 0, $granting = true, $strategy = null);
/**
* Inserts an object-field-based ACE
*
* @param string $field
* @param SecurityIdentityInterface $sid
* @param integer $mask
* @param integer $index
* @param Boolean $granting
* @param string $strategy
* @return void
*/
function insertObjectFieldAce($field, SecurityIdentityInterface $sid, $mask, $index = 0, $granting = true, $strategy = null);
/**
* Sets whether entries are inherited
*
* @param Boolean $boolean
* @return void
*/
function setEntriesInheriting($boolean);
/**
* Sets the parent ACL
*
* @param AclInterface $acl
* @return void
*/
function setParentAcl(AclInterface $acl);
/**
* Updates a class-based ACE
*
* @param integer $index
* @param integer $mask
* @param string $strategy if null the strategy should not be changed
* @return void
*/
function updateClassAce($index, $mask, $strategy = null);
/**
* 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
* @return void
*/
function updateClassFieldAce($index, $field, $mask, $strategy = null);
/**
* Updates an object-based ACE
*
* @param integer $index
* @param integer $mask
* @param string $strategy if null the strategy should not be changed
* @return void
*/
function updateObjectAce($index, $mask, $strategy = null);
/**
* 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
* @return void
*/
function updateObjectFieldAce($index, $field, $mask, $strategy = null);
}

View File

@ -0,0 +1,52 @@
<?php
namespace Symfony\Component\Security\Acl\Model;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* Provides support for creating and storing ACL instances.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface MutableAclProviderInterface extends AclProviderInterface
{
/**
* Creates a new ACL for the given object identity.
*
* @throws AclAlreadyExistsException when there already is an ACL for the given
* object identity
* @param ObjectIdentityInterface $oid
* @return AclInterface
*/
function createAcl(ObjectIdentityInterface $oid);
/**
* 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.
*
* @param ObjectIdentityInterface $oid
* @return void
*/
function deleteAcl(ObjectIdentityInterface $oid);
/**
* Persists any changes which were made to the ACL, or any associated
* access control entries.
*
* Changes to parent ACLs are not persisted.
*
* @param MutableAclInterface $acl
* @return void
*/
function updateAcl(MutableAclInterface $acl);
}

View File

@ -0,0 +1,49 @@
<?php
namespace Symfony\Component\Security\Acl\Model;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* Represents the identity of an individual domain object instance.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface ObjectIdentityInterface
{
/**
* We specifically require this method so we can check for object equality
* explicitly, and do not have to rely on referencial equality instead.
*
* Though in most cases, both checks should result in the same outcome.
*
* Referential Equality: $object1 === $object2
* Example for Object Equality: $object1->getId() === $object2->getId()
*
* @param ObjectIdentityInterface $identity
* @return Boolean
*/
function equals(ObjectIdentityInterface $identity);
/**
* Obtains a unique identifier for this object. The identifier must not be
* re-used for other objects with the same type.
*
* @return string cannot return null
*/
function getIdentifier();
/**
* Returns a type for the domain object. Typically, this is the PHP class name.
*
* @return string cannot return null
*/
function getType();
}

View File

@ -0,0 +1,19 @@
<?php
namespace Symfony\Component\Security\Acl\Model;
/**
* Retrieves the object identity for a given domain object
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface ObjectIdentityRetrievalStrategyInterface
{
/**
* Retrievies the object identity from a domain object
*
* @param object $domainObject
* @return ObjectIdentityInterface
*/
function getObjectIdentity($domainObject);
}

View File

@ -0,0 +1,43 @@
<?php
namespace Symfony\Component\Security\Acl\Model;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* Interface used by permission granting implementations.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface PermissionGrantingStrategyInterface
{
/**
* Determines whether access to a domain object is to be granted
*
* @param AclInterface $acl
* @param array $masks
* @param array $sids
* @param Boolean $administrativeMode
* @return Boolean
*/
function isGranted(AclInterface $acl, array $masks, array $sids, $administrativeMode = false);
/**
* Determines whether access to a domain object's field is to be granted
*
* @param AclInterface $acl
* @param string $field
* @param array $masks
* @param array $sids
* @param Boolean $adminstrativeMode
* @return Boolean
*/
function isFieldGranted(AclInterface $acl, $field, array $masks, array $sids, $adminstrativeMode = false);
}

View File

@ -0,0 +1,31 @@
<?php
namespace Symfony\Component\Security\Acl\Model;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* This interface provides an additional level of indirection, so that
* we can work with abstracted versions of security objects and do
* not have to save the entire objects.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface SecurityIdentityInterface
{
/**
* This method is used to compare two security identities in order to
* not rely on referential equality.
*
* @param SecurityIdentityInterface $identity
* @return void
*/
function equals(SecurityIdentityInterface $identity);
}

View File

@ -0,0 +1,25 @@
<?php
namespace Symfony\Component\Security\Acl\Model;
use Symfony\Component\Security\Authentication\Token\TokenInterface;
/**
* Interface for retrieving security identities from tokens
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface SecurityIdentityRetrievalStrategyInterface
{
/**
* Retrieves the available security identities for the given token
*
* The order in which the security identities are returned is significant.
* Typically, security identities should be ordered from most specific to
* least specific.
*
* @param TokenInterface $token
* @return array of SecurityIdentityInterface implementations
*/
function getSecurityIdentities(TokenInterface $token);
}

View File

@ -0,0 +1,103 @@
<?php
namespace Symfony\Component\Security\Acl\Permission;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* This is basic permission map complements the masks which have been defined
* on the standard implementation of the MaskBuilder.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class BasicPermissionMap implements PermissionMapInterface
{
const PERMISSION_VIEW = 'VIEW';
const PERMISSION_EDIT = 'EDIT';
const PERMISSION_CREATE = 'CREATE';
const PERMISSION_DELETE = 'DELETE';
const PERMISSION_UNDELETE = 'UNDELETE';
const PERMISSION_OPERATOR = 'OPERATOR';
const PERMISSION_MASTER = 'MASTER';
const PERMISSION_OWNER = 'OWNER';
protected $map = array(
self::PERMISSION_VIEW => array(
MaskBuilder::MASK_VIEW,
MaskBuilder::MASK_EDIT,
MaskBuilder::MASK_OPERATOR,
MaskBuilder::MASK_MASTER,
MaskBuilder::MASK_OWNER,
),
self::PERMISSION_EDIT => array(
MaskBuilder::MASK_EDIT,
MaskBuilder::MASK_OPERATOR,
MaskBuilder::MASK_MASTER,
MaskBuilder::MASK_OWNER,
),
self::PERMISSION_CREATE => array(
MaskBuilder::MASK_CREATE,
MaskBuilder::MASK_OPERATOR,
MaskBuilder::MASK_MASTER,
MaskBuilder::MASK_OWNER,
),
self::PERMISSION_DELETE => array(
MaskBuilder::MASK_DELETE,
MaskBuilder::MASK_OPERATOR,
MaskBuilder::MASK_MASTER,
MaskBuilder::MASK_OWNER,
),
self::PERMISSION_UNDELETE => array(
MaskBuilder::MASK_UNDELETE,
MaskBuilder::MASK_OPERATOR,
MaskBuilder::MASK_MASTER,
MaskBuilder::MASK_OWNER,
),
self::PERMISSION_OPERATOR => array(
MaskBuilder::MASK_OPERATOR,
MaskBuilder::MASK_MASTER,
MaskBuilder::MASK_OWNER,
),
self::PERMISSION_MASTER => array(
MaskBuilder::MASK_MASTER,
MaskBuilder::MASK_OWNER,
),
self::PERMISSION_OWNER => array(
MaskBuilder::MASK_OWNER,
),
);
/**
* {@inheritDoc}
*/
public function getMasks($permission)
{
if (!isset($this->map[$permission])) {
throw new \InvalidArgumentException(sprintf('The permission "%s" is not supported by this implementation.', $permission));
}
return $this->map[$permission];
}
/**
* {@inheritDoc}
*/
public function contains($permission)
{
return isset($this->map[$permission]);
}
}

View File

@ -0,0 +1,202 @@
<?php
namespace Symfony\Component\Security\Acl\Permission;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* This class allows you to build cumulative permissions easily, or convert
* masks to a human-readable format.
*
* <code>
* $builder = new MaskBuilder();
* $builder
* ->add('view')
* ->add('create')
* ->add('edit')
* ;
* var_dump($builder->get()); // int(7)
* var_dump($builder->getPattern()); // string(32) ".............................ECV"
* </code>
*
* We have defined some commonly used base permissions which you can use:
* - VIEW: the SID is allowed to view the domain object / field
* - CREATE: the SID is allowed to create new instances of the domain object / fields
* - EDIT: the SID is allowed to edit existing instances of the domain object / field
* - DELETE: the SID is allowed to delete domain objects
* - UNDELETE: the SID is allowed to recover domain objects from trash
* - OPERATOR: the SID is allowed to perform any action on the domain object
* except for granting others permissions
* - MASTER: the SID is allowed to perform any action on the domain object,
* and is allowed to grant other SIDs any permission except for
* MASTER and OWNER permissions
* - OWNER: the SID is owning the domain object in question and can perform any
* action on the domain object as well as grant any permission
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class MaskBuilder
{
const MASK_VIEW = 1; // 1 << 0
const MASK_CREATE = 2; // 1 << 1
const MASK_EDIT = 4; // 1 << 2
const MASK_DELETE = 8; // 1 << 3
const MASK_UNDELETE = 16; // 1 << 4
const MASK_OPERATOR = 32; // 1 << 5
const MASK_MASTER = 64; // 1 << 6
const MASK_OWNER = 128; // 1 << 7
const MASK_IDDQD = 1073741823; // 1 << 0 | 1 << 1 | ... | 1 << 30
const CODE_VIEW = 'V';
const CODE_CREATE = 'C';
const CODE_EDIT = 'E';
const CODE_DELETE = 'D';
const CODE_UNDELETE = 'U';
const CODE_OPERATOR = 'O';
const CODE_MASTER = 'M';
const CODE_OWNER = 'N';
const ALL_OFF = '................................';
const OFF = '.';
const ON = '*';
protected $mask;
/**
* Constructor
*
* @param integer $mask optional; defaults to 0
* @return void
*/
public function __construct($mask = 0)
{
if (!is_int($mask)) {
throw new \InvalidArgumentException('$mask must be an integer.');
}
$this->mask = $mask;
}
/**
* Adds a mask to the permission
*
* @param mixed $mask
* @return PermissionBuilder
*/
public function add($mask)
{
if (is_string($mask) && defined($name = 'self::MASK_'.strtoupper($mask))) {
$mask = constant($name);
} else if (!is_int($mask)) {
throw new \InvalidArgumentException('$mask must be an integer.');
}
$this->mask |= $mask;
return $this;
}
/**
* Returns the mask of this permission
*
* @return integer
*/
public function get()
{
return $this->mask;
}
/**
* Returns a human-readable representation of the permission
*
* @return string
*/
public function getPattern()
{
$pattern = self::ALL_OFF;
$length = strlen($pattern);
$bitmask = str_pad(decbin($this->mask), $length, '0', STR_PAD_LEFT);
for ($i=$length-1; $i>=0; $i--) {
if ('1' === $bitmask[$i]) {
try {
$pattern[$i] = self::getCode(1 << ($length - $i - 1));
} catch (\Exception $notPredefined) {
$pattern[$i] = self::ON;
}
}
}
return $pattern;
}
/**
* Removes a mask from the permission
*
* @param mixed $mask
* @return PermissionBuilder
*/
public function remove($mask)
{
if (is_string($mask) && defined($name = 'self::MASK_'.strtoupper($mask))) {
$mask = constant($name);
} else if (!is_int($mask)) {
throw new \InvalidArgumentException('$mask must be an integer.');
}
$this->mask &= ~$mask;
return $this;
}
/**
* Resets the PermissionBuilder
*
* @return PermissionBuilder
*/
public function reset()
{
$this->mask = 0;
return $this;
}
/**
* Returns the code for the passed mask
*
* @param integer $mask
* @throws \InvalidArgumentException
* @throws \RuntimeException
* @return string
*/
public static function getCode($mask)
{
if (!is_int($mask)) {
throw new \InvalidArgumentException('$mask must be an integer.');
}
$reflection = new \ReflectionClass(get_called_class());
foreach ($reflection->getConstants() as $name => $cMask) {
if (0 !== strpos($name, 'MASK_')) {
continue;
}
if ($mask === $cMask) {
if (!defined($cName = 'self::CODE_'.substr($name, 5))) {
throw new \RuntimeException('There was no code defined for this mask.');
}
return constant($cName);
}
}
throw new \InvalidArgumentException(sprintf('The mask "%d" is not supported.', $mask));
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Symfony\Component\Security\Acl\Permission;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* This is the interface that must be implemented by permission maps.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface PermissionMapInterface
{
/**
* Returns an array of bitmasks.
*
* The security identity must have been granted access to at least one of
* these bitmasks.
*
* @param string $permission
* @return array
*/
function getMasks($permission);
/**
* Whether this map contains the given permission
*
* @param string $permission
* @return Boolean
*/
function contains($permission);
}

View File

@ -0,0 +1,105 @@
<?php
namespace Symfony\Component\Security\Acl\Voter;
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\NoAceFoundException;
use Symfony\Component\Security\Acl\Exception\AclNotFoundException;
use Symfony\Component\Security\Acl\Model\AclProviderInterface;
use Symfony\Component\Security\Acl\Permission\PermissionMapInterface;
use Symfony\Component\Security\Acl\Model\SecurityIdentityRetrievalStrategyInterface;
use Symfony\Component\Security\Acl\Model\ObjectIdentityRetrievalStrategyInterface;
use Symfony\Component\Security\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Role\RoleHierarchyInterface;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* This voter can be used as a base class for implementing your own permissions.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class AclVoter implements VoterInterface
{
protected $aclProvider;
protected $permissionMap;
protected $objectIdentityRetrievalStrategy;
protected $securityIdentityRetrievalStrategy;
public function __construct(AclProviderInterface $aclProvider, ObjectIdentityRetrievalStrategyInterface $oidRetrievalStrategy, SecurityIdentityRetrievalStrategyInterface $sidRetrievalStrategy, PermissionMapInterface $permissionMap)
{
$this->aclProvider = $aclProvider;
$this->permissionMap = $permissionMap;
$this->objectIdentityRetrievalStrategy = $oidRetrievalStrategy;
$this->securityIdentityRetrievalStrategy = $sidRetrievalStrategy;
}
public function supportsAttribute($attribute)
{
return $this->permissionMap->contains($attribute);
}
public function vote(TokenInterface $token, $object, array $attributes)
{
if (null === $object) {
return self::ACCESS_ABSTAIN;
} else if ($object instanceof FieldVote) {
$field = $object->getField();
$object = $object->getDomainObject();
} else {
$field = null;
}
if (null === $oid = $this->objectIdentityRetrievalStrategy->getObjectIdentity($object)) {
return self::ACCESS_ABSTAIN;
}
$sids = $this->securityIdentityRetrievalStrategy->getSecurityIdentities($token);
foreach ($attributes as $attribute) {
if (!$this->supportsAttribute($attribute)) {
continue;
}
try {
$acl = $this->aclProvider->findAcl($oid, $sids);
} catch (AclNotFoundException $noAcl) {
return self::ACCESS_DENIED;
}
try {
if (null === $field && $acl->isGranted($this->permissionMap->getMasks($attribute), $sids, false)) {
return self::ACCESS_GRANTED;
} else if (null !== $field && $acl->isFieldGranted($field, $this->permissionMap->getMasks($attribute), $sids, false)) {
return self::ACCESS_GRANTED;
} else {
return self::ACCESS_DENIED;
}
} catch (NoAceFoundException $noAce) {
return self::ACCESS_DENIED;
}
}
return self::ACCESS_ABSTAIN;
}
/**
* You can override this method when writing a voter for a specific domain
* class.
*
* @return Boolean
*/
public function supportsClass($class)
{
return true;
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace Symfony\Component\Security\Acl\Voter;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* This class is a lightweight wrapper around field vote requests which does
* not violate any interface contracts.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class FieldVote
{
protected $domainObject;
protected $field;
public function __construct($domainObject, $field)
{
$this->domainObject = $domainObject;
$this->field = $field;
}
public function getDomainObject()
{
return $this->domainObject;
}
public function getField()
{
return $this->field;
}
}

View File

@ -0,0 +1,258 @@
<?php
namespace Symfony\Tests\Component\Security\Acl\Dbal;
use Symfony\Component\Security\Acl\Dbal\AclProvider;
use Symfony\Component\Security\Acl\Domain\PermissionGrantingStrategy;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
use Symfony\Component\Security\Acl\Dbal\Schema;
use Doctrine\DBAL\DriverManager;
class AclProviderBenchmarkTest extends \PHPUnit_Framework_TestCase
{
protected $con;
protected $insertClassStmt;
protected $insertSidStmt;
protected $insertOidAncestorStmt;
protected $insertOidStmt;
protected $insertEntryStmt;
public function testFindAcls()
{
// $this->generateTestData();
// get some random test object identities from the database
$oids = array();
$stmt = $this->con->executeQuery("SELECT object_identifier, class_type FROM acl_object_identities o INNER JOIN acl_classes c ON c.id = o.class_id ORDER BY RAND() LIMIT 25");
foreach ($stmt->fetchAll() as $oid) {
$oids[] = new ObjectIdentity($oid['object_identifier'], $oid['class_type']);
}
$provider = $this->getProvider();
$start = microtime(true);
$provider->findAcls($oids);
$time = microtime(true) - $start;
echo "Total Time: ".$time."s\n";
}
protected function setUp()
{
// comment the following line, and run only this test, if you need to benchmark
$this->markTestSkipped();
$this->con = DriverManager::getConnection(array(
'driver' => 'pdo_mysql',
'host' => 'localhost',
'user' => 'root',
'dbname' => 'testdb',
));
}
protected function tearDown()
{
$this->con = null;
}
/**
* This generates a huge amount of test data to be used mainly for benchmarking
* purposes, not so much for testing. That's why it's not called by default.
*/
protected function generateTestData()
{
$sm = $this->con->getSchemaManager();
$sm->dropAndCreateDatabase('testdb');
$this->con->exec("USE testdb");
// import the schema
$schema = new Schema($options = $this->getOptions());
foreach ($schema->toSql($this->con->getDatabasePlatform()) as $sql) {
$this->con->exec($sql);
}
// setup prepared statements
$this->insertClassStmt = $this->con->prepare('INSERT INTO acl_classes (id, class_type) VALUES (?, ?)');
$this->insertSidStmt = $this->con->prepare('INSERT INTO acl_security_identities (id, identifier, username) VALUES (?, ?, ?)');
$this->insertOidStmt = $this->con->prepare('INSERT INTO acl_object_identities (id, class_id, object_identifier, parent_object_identity_id, entries_inheriting) VALUES (?, ?, ?, ?, ?)');
$this->insertEntryStmt = $this->con->prepare('INSERT INTO acl_entries (id, class_id, object_identity_id, field_name, ace_order, security_identity_id, mask, granting, granting_strategy, audit_success, audit_failure) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
$this->insertOidAncestorStmt = $this->con->prepare('INSERT INTO acl_object_identity_ancestors (object_identity_id, ancestor_id) VALUES (?, ?)');
for ($i=0; $i<40000; $i++) {
$this->generateAclHierarchy();
}
}
protected function generateAclHierarchy()
{
$rootId = $this->generateAcl($this->chooseClassId(), null, array());
$this->generateAclLevel(rand(1, 15), $rootId, array($rootId));
}
protected function generateAclLevel($depth, $parentId, $ancestors)
{
$level = count($ancestors);
for ($i=0,$t=rand(1, 10); $i<$t; $i++) {
$id = $this->generateAcl($this->chooseClassId(), $parentId, $ancestors);
if ($level < $depth) {
$this->generateAclLevel($depth, $id, array_merge($ancestors, array($id)));
}
}
}
protected function chooseClassId()
{
static $id = 1000;
if ($id === 1000 || ($id < 1500 && rand(0, 1))) {
$this->insertClassStmt->execute(array($id, $this->getRandomString(rand(20, 100), 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\\_')));
$id += 1;
return $id-1;
}
else {
return rand(1000, $id-1);
}
}
protected function generateAcl($classId, $parentId, $ancestors)
{
static $id = 1000;
$this->insertOidStmt->execute(array(
$id,
$classId,
$this->getRandomString(rand(20, 50)),
$parentId,
rand(0, 1),
));
$this->insertOidAncestorStmt->execute(array($id, $id));
foreach ($ancestors as $ancestor) {
$this->insertOidAncestorStmt->execute(array($id, $ancestor));
}
$this->generateAces($classId, $id);
$id += 1;
return $id-1;
}
protected function chooseSid()
{
static $id = 1000;
if ($id === 1000 || ($id < 11000 && rand(0, 1))) {
$this->insertSidStmt->execute(array(
$id,
$this->getRandomString(rand(5, 30)),
rand(0, 1)
));
$id += 1;
return $id-1;
}
else {
return rand(1000, $id-1);
}
}
protected function generateAces($classId, $objectId)
{
static $id = 1000;
$sids = array();
$fieldOrder = array();
for ($i=0; $i<=30; $i++) {
$fieldName = rand(0, 1) ? null : $this->getRandomString(rand(10, 20));
do {
$sid = $this->chooseSid();
}
while (array_key_exists($sid, $sids) && in_array($fieldName, $sids[$sid], true));
$fieldOrder[$fieldName] = array_key_exists($fieldName, $fieldOrder) ? $fieldOrder[$fieldName]+1 : 0;
if (!isset($sids[$sid])) {
$sids[$sid] = array();
}
$sids[$sid][] = $fieldName;
$strategy = rand(0, 2);
if ($strategy === 0) {
$strategy = PermissionGrantingStrategy::ALL;
}
else if ($strategy === 1) {
$strategy = PermissionGrantingStrategy::ANY;
}
else {
$strategy = PermissionGrantingStrategy::EQUAL;
}
// id, cid, oid, field, order, sid, mask, granting, strategy, a success, a failure
$this->insertEntryStmt->execute(array(
$id,
$classId,
rand(0, 5) ? $objectId : null,
$fieldName,
$fieldOrder[$fieldName],
$sid,
$this->generateMask(),
rand(0, 1),
$strategy,
rand(0, 1),
rand(0, 1),
));
$id += 1;
}
}
protected function generateMask()
{
$i = rand(1, 30);
$mask = 0;
while ($i <= 30) {
$mask |= 1 << rand(0, 30);
$i++;
}
return $mask;
}
protected function getRandomString($length, $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789')
{
$s = '';
$cLength = strlen($chars);
while (strlen($s) < $length) {
$s .= $chars[mt_rand(0, $cLength-1)];
}
return $s;
}
protected function getOptions()
{
return array(
'oid_table_name' => 'acl_object_identities',
'oid_ancestors_table_name' => 'acl_object_identity_ancestors',
'class_table_name' => 'acl_classes',
'sid_table_name' => 'acl_security_identities',
'entry_table_name' => 'acl_entries',
);
}
protected function getStrategy()
{
return new PermissionGrantingStrategy();
}
protected function getProvider()
{
return new AclProvider($this->con, $this->getStrategy(), $this->getOptions());
}
}

View File

@ -0,0 +1,242 @@
<?php
namespace Symfony\Tests\Component\Security\Acl\Dbal;
use Symfony\Component\Security\Acl\Dbal\AclProvider;
use Symfony\Component\Security\Acl\Domain\PermissionGrantingStrategy;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
use Symfony\Component\Security\Acl\Dbal\Schema;
use Doctrine\DBAL\DriverManager;
class AclProviderTest extends \PHPUnit_Framework_TestCase
{
protected $con;
protected $insertClassStmt;
protected $insertEntryStmt;
protected $insertOidStmt;
protected $insertOidAncestorStmt;
protected $insertSidStmt;
/**
* @expectedException Symfony\Component\Security\Acl\Exception\AclNotFoundException
* @expectedMessage There is no ACL for the given object identity.
*/
public function testFindAclThrowsExceptionWhenNoAclExists()
{
$this->getProvider()->findAcl(new ObjectIdentity('foo', 'foo'));
}
/**
* @expectedException Symfony\Component\Security\Acl\Exception\AclNotFoundException
*/
public function testFindAclsThrowsExceptionUnlessAnACLIsFoundForEveryOID()
{
$oids = array();
$oids[] = new ObjectIdentity('1', 'foo');
$oids[] = new ObjectIdentity('foo', 'foo');
$this->getProvider()->findAcls($oids);
}
public function testFindAcls()
{
$oids = array();
$oids[] = new ObjectIdentity('1', 'foo');
$oids[] = new ObjectIdentity('2', 'foo');
$provider = $this->getProvider();
$acls = $provider->findAcls($oids);
$this->assertInstanceOf('SplObjectStorage', $acls);
$this->assertEquals(2, count($acls));
$this->assertInstanceOf('Symfony\Component\Security\Acl\Domain\Acl', $acl0 = $acls->offsetGet($oids[0]));
$this->assertInstanceOf('Symfony\Component\Security\Acl\Domain\Acl', $acl1 = $acls->offsetGet($oids[1]));
$this->assertTrue($oids[0]->equals($acl0->getObjectIdentity()));
$this->assertTrue($oids[1]->equals($acl1->getObjectIdentity()));
}
public function testFindAclCachesAclInMemory()
{
$oid = new ObjectIdentity('1', 'foo');
$provider = $this->getProvider();
$acl = $provider->findAcl($oid);
$this->assertSame($acl, $cAcl = $provider->findAcl($oid));
$cAces = $cAcl->getObjectAces();
foreach ($acl->getObjectAces() as $index => $ace) {
$this->assertSame($ace, $cAces[$index]);
}
}
public function testFindAcl()
{
$oid = new ObjectIdentity('1', 'foo');
$provider = $this->getProvider();
$acl = $provider->findAcl($oid);
$this->assertInstanceOf('Symfony\Component\Security\Acl\Domain\Acl', $acl);
$this->assertTrue($oid->equals($acl->getObjectIdentity()));
$this->assertEquals(4, $acl->getId());
$this->assertEquals(0, count($acl->getClassAces()));
$this->assertEquals(0, count($this->getField($acl, 'classFieldAces')));
$this->assertEquals(3, count($acl->getObjectAces()));
$this->assertEquals(0, count($this->getField($acl, 'objectFieldAces')));
$aces = $acl->getObjectAces();
$this->assertInstanceOf('Symfony\Component\Security\Acl\Domain\Entry', $aces[0]);
$this->assertTrue($aces[0]->isGranting());
$this->assertTrue($aces[0]->isAuditSuccess());
$this->assertTrue($aces[0]->isAuditFailure());
$this->assertEquals('all', $aces[0]->getStrategy());
$this->assertSame(2, $aces[0]->getMask());
// check ACE are in correct order
$i = 0;
foreach ($aces as $index => $ace) {
$this->assertEquals($i, $index);
$i++;
}
$sid = $aces[0]->getSecurityIdentity();
$this->assertInstanceOf('Symfony\Component\Security\Acl\Domain\UserSecurityIdentity', $sid);
$this->assertEquals('john.doe', $sid->getUsername());
}
protected function setUp()
{
$this->con = DriverManager::getConnection(array(
'driver' => 'pdo_sqlite',
'memory' => true,
));
// import the schema
$schema = new Schema($options = $this->getOptions());
foreach ($schema->toSql($this->con->getDatabasePlatform()) as $sql) {
$this->con->exec($sql);
}
// populate the schema with some test data
$this->insertClassStmt = $this->con->prepare('INSERT INTO acl_classes (id, class_type) VALUES (?, ?)');
foreach ($this->getClassData() as $data) {
$this->insertClassStmt->execute($data);
}
$this->insertSidStmt = $this->con->prepare('INSERT INTO acl_security_identities (id, identifier, username) VALUES (?, ?, ?)');
foreach ($this->getSidData() as $data) {
$this->insertSidStmt->execute($data);
}
$this->insertOidStmt = $this->con->prepare('INSERT INTO acl_object_identities (id, class_id, object_identifier, parent_object_identity_id, entries_inheriting) VALUES (?, ?, ?, ?, ?)');
foreach ($this->getOidData() as $data) {
$this->insertOidStmt->execute($data);
}
$this->insertEntryStmt = $this->con->prepare('INSERT INTO acl_entries (id, class_id, object_identity_id, field_name, ace_order, security_identity_id, mask, granting, granting_strategy, audit_success, audit_failure) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
foreach ($this->getEntryData() as $data) {
$this->insertEntryStmt->execute($data);
}
$this->insertOidAncestorStmt = $this->con->prepare('INSERT INTO acl_object_identity_ancestors (object_identity_id, ancestor_id) VALUES (?, ?)');
foreach ($this->getOidAncestorData() as $data) {
$this->insertOidAncestorStmt->execute($data);
}
}
protected function tearDown()
{
$this->con = null;
}
protected function getField($object, $field)
{
$reflection = new \ReflectionProperty($object, $field);
$reflection->setAccessible(true);
return $reflection->getValue($object);
}
protected function getEntryData()
{
// id, cid, oid, field, order, sid, mask, granting, strategy, a success, a failure
return array(
array(1, 1, 1, null, 0, 1, 1, 1, 'all', 1, 1),
array(2, 1, 1, null, 1, 2, 1 << 2 | 1 << 1, 0, 'any', 0, 0),
array(3, 3, 4, null, 0, 1, 2, 1, 'all', 1, 1),
array(4, 3, 4, null, 2, 2, 1, 1, 'all', 1, 1),
array(5, 3, 4, null, 1, 3, 1, 1, 'all', 1, 1),
);
}
protected function getOidData()
{
// id, cid, oid, parent_oid, entries_inheriting
return array(
array(1, 1, '123', null, 1),
array(2, 2, '123', 1, 1),
array(3, 2, 'i:3:123', 1, 1),
array(4, 3, '1', 2, 1),
array(5, 3, '2', 2, 1),
);
}
protected function getOidAncestorData()
{
return array(
array(1, 1),
array(2, 1),
array(2, 2),
array(3, 1),
array(3, 3),
array(4, 2),
array(4, 1),
array(4, 4),
array(5, 2),
array(5, 1),
array(5, 5),
);
}
protected function getSidData()
{
return array(
array(1, 'john.doe', 1),
array(2, 'john.doe@foo.com', 1),
array(3, '123', 1),
array(4, 'ROLE_USER', 1),
array(5, 'ROLE_USER', 0),
array(6, 'IS_AUTHENTICATED_FULLY', 0),
);
}
protected function getClassData()
{
return array(
array(1, 'Bundle\SomeVendor\MyBundle\Entity\SomeEntity'),
array(2, 'Bundle\MyBundle\Entity\AnotherEntity'),
array(3, 'foo'),
);
}
protected function getOptions()
{
return array(
'oid_table_name' => 'acl_object_identities',
'oid_ancestors_table_name' => 'acl_object_identity_ancestors',
'class_table_name' => 'acl_classes',
'sid_table_name' => 'acl_security_identities',
'entry_table_name' => 'acl_entries',
);
}
protected function getStrategy()
{
return new PermissionGrantingStrategy();
}
protected function getProvider()
{
return new AclProvider($this->con, $this->getStrategy(), $this->getOptions());
}
}

View File

@ -0,0 +1,452 @@
<?php
namespace Symfony\Tests\Component\Security\Acl\Dbal;
use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity;
use Symfony\Component\Security\Acl\Model\FieldAwareEntryInterface;
use Symfony\Component\Security\Acl\Model\AuditableEntryInterface;
use Symfony\Component\Security\Acl\Model\EntryInterface;
use Symfony\Component\Security\Acl\Domain\Entry;
use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
use Symfony\Component\Security\Acl\Domain\Acl;
use Symfony\Component\Security\Acl\Exception\AclNotFoundException;
use Symfony\Component\Security\Acl\Exception\ConcurrentModificationException;
use Symfony\Component\Security\Acl\Dbal\AclProvider;
use Symfony\Component\Security\Acl\Domain\PermissionGrantingStrategy;
use Symfony\Component\Security\Acl\Dbal\MutableAclProvider;
use Symfony\Component\Security\Acl\Dbal\Schema;
use Doctrine\DBAL\DriverManager;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
class MutableAclProviderTest extends \PHPUnit_Framework_TestCase
{
protected $con;
public static function assertAceEquals(EntryInterface $a, EntryInterface $b)
{
self::assertInstanceOf(get_class($a), $b);
foreach (array('getId', 'getMask', 'getStrategy', 'isGranting') as $getter) {
self::assertSame($a->$getter(), $b->$getter());
}
self::assertTrue($a->getSecurityIdentity()->equals($b->getSecurityIdentity()));
self::assertSame($a->getAcl()->getId(), $b->getAcl()->getId());
if ($a instanceof AuditableEntryInterface) {
self::assertSame($a->isAuditSuccess(), $b->isAuditSuccess());
self::assertSame($a->isAuditFailure(), $b->isAuditFailure());
}
if ($a instanceof FieldAwareEntryInterface) {
self::assertSame($a->getField(), $b->getField());
}
}
/**
* @expectedException Symfony\Component\Security\Acl\Exception\AclAlreadyExistsException
*/
public function testCreateAclThrowsExceptionWhenAclAlreadyExists()
{
$provider = $this->getProvider();
$oid = new ObjectIdentity('123456', 'FOO');
$provider->createAcl($oid);
$provider->createAcl($oid);
}
public function testCreateAcl()
{
$provider = $this->getProvider();
$oid = new ObjectIdentity('123456', 'FOO');
$acl = $provider->createAcl($oid);
$cachedAcl = $provider->findAcl($oid);
$this->assertInstanceOf('Symfony\Component\Security\Acl\Domain\Acl', $acl);
$this->assertSame($acl, $cachedAcl);
$this->assertTrue($acl->getObjectIdentity()->equals($oid));
}
public function testDeleteAcl()
{
$provider = $this->getProvider();
$oid = new ObjectIdentity(1, 'Foo');
$acl = $provider->createAcl($oid);
$provider->deleteAcl($oid);
$loadedAcls = $this->getField($provider, 'loadedAcls');
$this->assertEquals(0, count($loadedAcls['Foo']));
try {
$provider->findAcl($oid);
$this->fail('ACL has not been properly deleted.');
} catch (AclNotFoundException $notFound) { }
}
public function testDeleteAclDeletesChildren()
{
$provider = $this->getProvider();
$acl = $provider->createAcl(new ObjectIdentity(1, 'Foo'));
$parentAcl = $provider->createAcl(new ObjectIdentity(2, 'Foo'));
$acl->setParentAcl($parentAcl);
$provider->updateAcl($acl);
$provider->deleteAcl($parentAcl->getObjectIdentity());
try {
$provider->findAcl(new ObjectIdentity(1, 'Foo'));
$this->fail('Child-ACLs have not been deleted.');
} catch (AclNotFoundException $notFound) { }
}
public function testFindAclsAddsPropertyListener()
{
$provider = $this->getProvider();
$acl = $provider->createAcl(new ObjectIdentity(1, 'Foo'));
$propertyChanges = $this->getField($provider, 'propertyChanges');
$this->assertEquals(1, count($propertyChanges));
$this->assertTrue($propertyChanges->contains($acl));
$this->assertEquals(array(), $propertyChanges->offsetGet($acl));
$listeners = $this->getField($acl, 'listeners');
$this->assertSame($provider, $listeners[0]);
}
public function testFindAclsAddsPropertyListenerOnlyOnce()
{
$provider = $this->getProvider();
$acl = $provider->createAcl(new ObjectIdentity(1, 'Foo'));
$acl = $provider->findAcl(new ObjectIdentity(1, 'Foo'));
$propertyChanges = $this->getField($provider, 'propertyChanges');
$this->assertEquals(1, count($propertyChanges));
$this->assertTrue($propertyChanges->contains($acl));
$this->assertEquals(array(), $propertyChanges->offsetGet($acl));
$listeners = $this->getField($acl, 'listeners');
$this->assertEquals(1, count($listeners));
$this->assertSame($provider, $listeners[0]);
}
public function testFindAclsAddsPropertyListenerToParentAcls()
{
$provider = $this->getProvider();
$this->importAcls($provider, array(
'main' => array(
'object_identifier' => '1',
'class_type' => 'foo',
'parent_acl' => 'parent',
),
'parent' => array(
'object_identifier' => '1',
'class_type' => 'anotherFoo',
)
));
$propertyChanges = $this->getField($provider, 'propertyChanges');
$this->assertEquals(0, count($propertyChanges));
$acl = $provider->findAcl(new ObjectIdentity('1', 'foo'));
$this->assertEquals(2, count($propertyChanges));
$this->assertTrue($propertyChanges->contains($acl));
$this->assertTrue($propertyChanges->contains($acl->getParentAcl()));
}
/**
* @expectedException \InvalidArgumentException
*/
public function testPropertyChangedDoesNotTrackUnmanagedAcls()
{
$provider = $this->getProvider();
$acl = new Acl(1, new ObjectIdentity(1, 'foo'), new PermissionGrantingStrategy(), array(), false);
$provider->propertyChanged($acl, 'classAces', array(), array('foo'));
}
public function testPropertyChangedTracksChangesToAclProperties()
{
$provider = $this->getProvider();
$acl = $provider->createAcl(new ObjectIdentity(1, 'Foo'));
$propertyChanges = $this->getField($provider, 'propertyChanges');
$provider->propertyChanged($acl, 'entriesInheriting', false, true);
$changes = $propertyChanges->offsetGet($acl);
$this->assertTrue(isset($changes['entriesInheriting']));
$this->assertFalse($changes['entriesInheriting'][0]);
$this->assertTrue($changes['entriesInheriting'][1]);
$provider->propertyChanged($acl, 'entriesInheriting', true, false);
$provider->propertyChanged($acl, 'entriesInheriting', false, true);
$provider->propertyChanged($acl, 'entriesInheriting', true, false);
$changes = $propertyChanges->offsetGet($acl);
$this->assertFalse(isset($changes['entriesInheriting']));
}
public function testPropertyChangedTracksChangesToAceProperties()
{
$provider = $this->getProvider();
$acl = $provider->createAcl(new ObjectIdentity(1, 'Foo'));
$ace = new Entry(1, $acl, new UserSecurityIdentity('foo'), 'all', 1, true, true, true);
$ace2 = new Entry(2, $acl, new UserSecurityIdentity('foo'), 'all', 1, true, true, true);
$propertyChanges = $this->getField($provider, 'propertyChanges');
$provider->propertyChanged($ace, 'mask', 1, 3);
$changes = $propertyChanges->offsetGet($acl);
$this->assertTrue(isset($changes['aces']));
$this->assertInstanceOf('\SplObjectStorage', $changes['aces']);
$this->assertTrue($changes['aces']->contains($ace));
$aceChanges = $changes['aces']->offsetGet($ace);
$this->assertTrue(isset($aceChanges['mask']));
$this->assertEquals(1, $aceChanges['mask'][0]);
$this->assertEquals(3, $aceChanges['mask'][1]);
$provider->propertyChanged($ace, 'strategy', 'all', 'any');
$changes = $propertyChanges->offsetGet($acl);
$this->assertTrue(isset($changes['aces']));
$this->assertInstanceOf('\SplObjectStorage', $changes['aces']);
$this->assertTrue($changes['aces']->contains($ace));
$aceChanges = $changes['aces']->offsetGet($ace);
$this->assertTrue(isset($aceChanges['mask']));
$this->assertTrue(isset($aceChanges['strategy']));
$this->assertEquals('all', $aceChanges['strategy'][0]);
$this->assertEquals('any', $aceChanges['strategy'][1]);
$provider->propertyChanged($ace, 'mask', 3, 1);
$changes = $propertyChanges->offsetGet($acl);
$aceChanges = $changes['aces']->offsetGet($ace);
$this->assertFalse(isset($aceChanges['mask']));
$this->assertTrue(isset($aceChanges['strategy']));
$provider->propertyChanged($ace2, 'mask', 1, 3);
$provider->propertyChanged($ace, 'strategy', 'any', 'all');
$changes = $propertyChanges->offsetGet($acl);
$this->assertTrue(isset($changes['aces']));
$this->assertFalse($changes['aces']->contains($ace));
$this->assertTrue($changes['aces']->contains($ace2));
$provider->propertyChanged($ace2, 'mask', 3, 4);
$provider->propertyChanged($ace2, 'mask', 4, 1);
$changes = $propertyChanges->offsetGet($acl);
$this->assertFalse(isset($changes['aces']));
}
/**
* @expectedException \InvalidArgumentException
*/
public function testUpdateAclDoesNotAcceptUntrackedAcls()
{
$provider = $this->getProvider();
$acl = new Acl(1, new ObjectIdentity(1, 'Foo'), new PermissionGrantingStrategy(), array(), true);
$provider->updateAcl($acl);
}
public function testUpdateDoesNothingWhenThereAreNoChanges()
{
$con = $this->getMock('Doctrine\DBAL\Connection', array(), array(), '', false);
$con
->expects($this->never())
->method('beginTransaction')
;
$con
->expects($this->never())
->method('executeQuery')
;
$provider = new MutableAclProvider($con, new PermissionGrantingStrategy(), array());
$acl = new Acl(1, new ObjectIdentity(1, 'Foo'), new PermissionGrantingStrategy(), array(), true);
$propertyChanges = $this->getField($provider, 'propertyChanges');
$propertyChanges->offsetSet($acl, array());
$provider->updateAcl($acl);
}
public function testUpdateAclThrowsExceptionOnConcurrentModifcationOfSharedProperties()
{
$provider = $this->getProvider();
$acl1 = $provider->createAcl(new ObjectIdentity(1, 'Foo'));
$acl2 = $provider->createAcl(new ObjectIdentity(2, 'Foo'));
$acl3 = $provider->createAcl(new ObjectIdentity(1, 'AnotherFoo'));
$sid = new RoleSecurityIdentity('ROLE_FOO');
$acl1->insertClassAce($sid, 1);
$acl3->insertClassAce($sid, 1);
$provider->updateAcl($acl1);
$provider->updateAcl($acl3);
$acl2->insertClassAce($sid, 16);
$provider->updateAcl($acl2);
$acl1->insertClassAce($sid, 3);
$acl2->insertClassAce($sid, 5);
try {
$provider->updateAcl($acl1);
$this->fail('Provider failed to detect a concurrent modification.');
} catch (ConcurrentModificationException $ex) { }
}
public function testUpdateAcl()
{
$provider = $this->getProvider();
$acl = $provider->createAcl(new ObjectIdentity(1, 'Foo'));
$sid = new UserSecurityIdentity('johannes');
$acl->setEntriesInheriting(!$acl->isEntriesInheriting());
$acl->insertObjectAce($sid, 1);
$acl->insertClassAce($sid, 5, 0, false);
$acl->insertObjectAce($sid, 2, 1, true);
$provider->updateAcl($acl);
$acl->updateObjectAce(0, 3);
$acl->deleteObjectAce(1);
$acl->updateObjectAuditing(0, true, false);
$provider->updateAcl($acl);
$reloadProvider = $this->getProvider();
$reloadedAcl = $reloadProvider->findAcl(new ObjectIdentity(1, 'Foo'));
$this->assertNotSame($acl, $reloadedAcl);
$this->assertSame($acl->isEntriesInheriting(), $reloadedAcl->isEntriesInheriting());
$aces = $acl->getObjectAces();
$reloadedAces = $reloadedAcl->getObjectAces();
$this->assertEquals(count($aces), count($reloadedAces));
foreach ($aces as $index => $ace) {
$this->assertAceEquals($ace, $reloadedAces[$index]);
}
}
public function testUpdateAclWorksForChangingTheParentAcl()
{
$provider = $this->getProvider();
$acl = $provider->createAcl(new ObjectIdentity(1, 'Foo'));
$parentAcl = $provider->createAcl(new ObjectIdentity(1, 'AnotherFoo'));
$acl->setParentAcl($parentAcl);
$provider->updateAcl($acl);
$reloadProvider = $this->getProvider();
$reloadedAcl = $reloadProvider->findAcl(new ObjectIdentity(1, 'Foo'));
$this->assertNotSame($acl, $reloadedAcl);
$this->assertSame($parentAcl->getId(), $reloadedAcl->getParentAcl()->getId());
}
/**
* Data must have the following format:
* array(
* *name* => array(
* 'object_identifier' => *required*
* 'class_type' => *required*,
* 'parent_acl' => *name (optional)*
* ),
* )
*
* @param AclProvider $provider
* @param array $data
* @throws \InvalidArgumentException
* @throws Exception
*/
protected function importAcls(AclProvider $provider, array $data)
{
$aclIds = $parentAcls = array();
$con = $this->getField($provider, 'connection');
$con->beginTransaction();
try {
foreach ($data as $name => $aclData) {
if (!isset($aclData['object_identifier'], $aclData['class_type'])) {
throw new \InvalidArgumentException('"object_identifier", and "class_type" must be present.');
}
$this->callMethod($provider, 'createObjectIdentity', array(new ObjectIdentity($aclData['object_identifier'], $aclData['class_type'])));
$aclId = $con->lastInsertId();
$aclIds[$name] = $aclId;
$sql = $this->callMethod($provider, 'getInsertObjectIdentityRelationSql', array($aclId, $aclId));
$con->executeQuery($sql);
if (isset($aclData['parent_acl'])) {
if (isset($aclIds[$aclData['parent_acl']])) {
$con->executeQuery("UPDATE acl_object_identities SET parent_object_identity_id = ".$aclIds[$aclData['parent_acl']]." WHERE id = ".$aclId);
$con->executeQuery($this->callMethod($provider, 'getInsertObjectIdentityRelationSql', array($aclId, $aclIds[$aclData['parent_acl']])));
} else {
$parentAcls[$aclId] = $aclData['parent_acl'];
}
}
}
foreach ($parentAcls as $aclId => $name) {
if (!isset($aclIds[$name])) {
throw new \InvalidArgumentException(sprintf('"%s" does not exist.', $name));
}
$con->executeQuery(sprintf("UPDATE acl_object_identities SET parent_object_identity_id = %d WHERE id = %d", $aclIds[$name], $aclId));
$con->executeQuery($this->callMethod($provider, 'getInsertObjectIdentityRelationSql', array($aclId, $aclIds[$name])));
}
$con->commit();
} catch (\Exception $e) {
$con->rollBack();
throw $e;
}
}
protected function callMethod($object, $method, array $args)
{
$method = new \ReflectionMethod($object, $method);
$method->setAccessible(true);
return $method->invokeArgs($object, $args);
}
protected function setUp()
{
$this->con = DriverManager::getConnection(array(
'driver' => 'pdo_sqlite',
'memory' => true,
));
// import the schema
$schema = new Schema($this->getOptions());
foreach ($schema->toSql($this->con->getDatabasePlatform()) as $sql) {
$this->con->exec($sql);
}
}
protected function tearDown()
{
$this->con = null;
}
protected function getField($object, $field)
{
$reflection = new \ReflectionProperty($object, $field);
$reflection->setAccessible(true);
return $reflection->getValue($object);
}
public function setField($object, $field, $value)
{
$reflection = new \ReflectionProperty($object, $field);
$reflection->setAccessible(true);
$reflection->setValue($object, $value);
$reflection->setAccessible(false);
}
protected function getOptions()
{
return array(
'oid_table_name' => 'acl_object_identities',
'oid_ancestors_table_name' => 'acl_object_identity_ancestors',
'class_table_name' => 'acl_classes',
'sid_table_name' => 'acl_security_identities',
'entry_table_name' => 'acl_entries',
);
}
protected function getStrategy()
{
return new PermissionGrantingStrategy();
}
protected function getProvider($cache = null)
{
return new MutableAclProvider($this->con, $this->getStrategy(), $this->getOptions(), $cache);
}
}

View File

@ -0,0 +1,502 @@
<?php
namespace Symfony\Tests\Component\Security\Acl\Domain;
use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity;
use Symfony\Component\Security\Acl\Domain\PermissionGrantingStrategy;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
use Symfony\Component\Security\Acl\Domain\Acl;
class AclTest extends \PHPUnit_Framework_TestCase
{
public function testConstructor()
{
$acl = new Acl(1, $oid = new ObjectIdentity('foo', 'foo'), $permissionStrategy = new PermissionGrantingStrategy(), array(), true);
$this->assertSame(1, $acl->getId());
$this->assertSame($oid, $acl->getObjectIdentity());
$this->assertNull($acl->getParentAcl());
$this->assertTrue($acl->isEntriesInheriting());
}
/**
* @expectedException \OutOfBoundsException
* @dataProvider getDeleteAceTests
*/
public function testDeleteAceThrowsExceptionOnInvalidIndex($type)
{
$acl = $this->getAcl();
$acl->{'delete'.$type.'Ace'}(0);
}
/**
* @dataProvider getDeleteAceTests
*/
public function testDeleteAce($type)
{
$acl = $this->getAcl();
$acl->{'insert'.$type.'Ace'}(new RoleSecurityIdentity('foo'), 1);
$acl->{'insert'.$type.'Ace'}(new RoleSecurityIdentity('foo'), 2, 1);
$acl->{'insert'.$type.'Ace'}(new RoleSecurityIdentity('foo'), 3, 2);
$listener = $this->getListener(array(
$type.'Aces', 'aceOrder', 'aceOrder', $type.'Aces',
));
$acl->addPropertyChangedListener($listener);
$this->assertEquals(3, count($acl->{'get'.$type.'Aces'}()));
$acl->{'delete'.$type.'Ace'}(0);
$this->assertEquals(2, count($aces = $acl->{'get'.$type.'Aces'}()));
$this->assertEquals(2, $aces[0]->getMask());
$this->assertEquals(3, $aces[1]->getMask());
$acl->{'delete'.$type.'Ace'}(1);
$this->assertEquals(1, count($aces = $acl->{'get'.$type.'Aces'}()));
$this->assertEquals(2, $aces[0]->getMask());
}
public function getDeleteAceTests()
{
return array(
array('class'),
array('object'),
);
}
/**
* @expectedException \OutOfBoundsException
* @dataProvider getDeleteFieldAceTests
*/
public function testDeleteFieldAceThrowsExceptionOnInvalidIndex($type)
{
$acl = $this->getAcl();
$acl->{'delete'.$type.'Ace'}('foo', 0);
}
/**
* @dataProvider getDeleteFieldAceTests
*/
public function testDeleteFieldAce($type)
{
$acl = $this->getAcl();
$acl->{'insert'.$type.'Ace'}('foo', new RoleSecurityIdentity('foo'), 1, 0);
$acl->{'insert'.$type.'Ace'}('foo', new RoleSecurityIdentity('foo'), 2, 1);
$acl->{'insert'.$type.'Ace'}('foo', new RoleSecurityIdentity('foo'), 3, 2);
$listener = $this->getListener(array(
$type.'Aces', 'aceOrder', 'aceOrder', $type.'Aces',
));
$acl->addPropertyChangedListener($listener);
$this->assertEquals(3, count($acl->{'get'.$type.'Aces'}('foo')));
$acl->{'delete'.$type.'Ace'}(0, 'foo');
$this->assertEquals(2, count($aces = $acl->{'get'.$type.'Aces'}('foo')));
$this->assertEquals(2, $aces[0]->getMask());
$this->assertEquals(3, $aces[1]->getMask());
$acl->{'delete'.$type.'Ace'}(1, 'foo');
$this->assertEquals(1, count($aces = $acl->{'get'.$type.'Aces'}('foo')));
$this->assertEquals(2, $aces[0]->getMask());
}
public function getDeleteFieldAceTests()
{
return array(
array('classField'),
array('objectField'),
);
}
/**
* @dataProvider getInsertAceTests
*/
public function testInsertAce($property, $method)
{
$acl = $this->getAcl();
$listener = $this->getListener(array(
$property, 'aceOrder', $property, 'aceOrder', $property
));
$acl->addPropertyChangedListener($listener);
$sid = new RoleSecurityIdentity('foo');
$acl->$method($sid, 1);
$acl->$method($sid, 2);
$acl->$method($sid, 3, 1, false);
$this->assertEquals(3, count($aces = $acl->{'get'.$property}()));
$this->assertEquals(2, $aces[0]->getMask());
$this->assertEquals(3, $aces[1]->getMask());
$this->assertEquals(1, $aces[2]->getMask());
}
/**
* @expectedException \OutOfBoundsException
* @dataProvider getInsertAceTests
*/
public function testInsertClassAceThrowsExceptionOnInvalidIndex($property, $method)
{
$acl = $this->getAcl();
$acl->$method(new RoleSecurityIdentity('foo'), 1, 1);
}
public function getInsertAceTests()
{
return array(
array('classAces', 'insertClassAce'),
array('objectAces', 'insertObjectAce'),
);
}
/**
* @dataProvider getInsertFieldAceTests
*/
public function testInsertClassFieldAce($property, $method)
{
$acl = $this->getAcl();
$listener = $this->getListener(array(
$property, $property, 'aceOrder', $property,
'aceOrder', 'aceOrder', $property,
));
$acl->addPropertyChangedListener($listener);
$sid = new RoleSecurityIdentity('foo');
$acl->$method('foo', $sid, 1);
$acl->$method('foo2', $sid, 1);
$acl->$method('foo', $sid, 3);
$acl->$method('foo', $sid, 2);
$this->assertEquals(3, count($aces = $acl->{'get'.$property}('foo')));
$this->assertEquals(1, count($acl->{'get'.$property}('foo2')));
$this->assertEquals(2, $aces[0]->getMask());
$this->assertEquals(3, $aces[1]->getMask());
$this->assertEquals(1, $aces[2]->getMask());
}
/**
* @expectedException \OutOfBoundsException
* @dataProvider getInsertFieldAceTests
*/
public function testInsertClassFieldAceThrowsExceptionOnInvalidIndex($property, $method)
{
$acl = $this->getAcl();
$acl->$method('foo', new RoleSecurityIdentity('foo'), 1, 1);
}
public function getInsertFieldAceTests()
{
return array(
array('classFieldAces', 'insertClassFieldAce'),
array('objectFieldAces', 'insertObjectFieldAce'),
);
}
public function testIsFieldGranted()
{
$sids = array(new RoleSecurityIdentity('ROLE_FOO'), new RoleSecurityIdentity('ROLE_IDDQD'));
$masks = array(1, 2, 4);
$strategy = $this->getMock('Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface');
$acl = new Acl(1, new ObjectIdentity(1, 'foo'), $strategy, array(), true);
$strategy
->expects($this->once())
->method('isFieldGranted')
->with($this->equalTo($acl), $this->equalTo('foo'), $this->equalTo($masks), $this->equalTo($sids), $this->isTrue())
->will($this->returnValue(true))
;
$this->assertTrue($acl->isFieldGranted('foo', $masks, $sids, true));
}
public function testIsGranted()
{
$sids = array(new RoleSecurityIdentity('ROLE_FOO'), new RoleSecurityIdentity('ROLE_IDDQD'));
$masks = array(1, 2, 4);
$strategy = $this->getMock('Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface');
$acl = new Acl(1, new ObjectIdentity(1, 'foo'), $strategy, array(), true);
$strategy
->expects($this->once())
->method('isGranted')
->with($this->equalTo($acl), $this->equalTo($masks), $this->equalTo($sids), $this->isTrue())
->will($this->returnValue(true))
;
$this->assertTrue($acl->isGranted($masks, $sids, true));
}
public function testSetGetParentAcl()
{
$acl = $this->getAcl();
$parentAcl = $this->getAcl();
$listener = $this->getListener(array('parentAcl'));
$acl->addPropertyChangedListener($listener);
$this->assertNull($acl->getParentAcl());
$acl->setParentAcl($parentAcl);
$this->assertSame($parentAcl, $acl->getParentAcl());
}
public function testSetIsEntriesInheriting()
{
$acl = $this->getAcl();
$listener = $this->getListener(array('entriesInheriting'));
$acl->addPropertyChangedListener($listener);
$this->assertTrue($acl->isEntriesInheriting());
$acl->setEntriesInheriting(false);
$this->assertFalse($acl->isEntriesInheriting());
}
public function testIsSidLoadedWhenAllSidsAreLoaded()
{
$acl = $this->getAcl();
$this->assertTrue($acl->isSidLoaded(new UserSecurityIdentity('foo')));
$this->assertTrue($acl->isSidLoaded(new RoleSecurityIdentity('ROLE_FOO')));
}
public function testIsSidLoaded()
{
$acl = new Acl(1, new ObjectIdentity('1', 'foo'), new PermissionGrantingStrategy(), array(new UserSecurityIdentity('foo'), new UserSecurityIdentity('johannes')), true);
$this->assertTrue($acl->isSidLoaded(new UserSecurityIdentity('foo')));
$this->assertTrue($acl->isSidLoaded(new UserSecurityIdentity('johannes')));
$this->assertTrue($acl->isSidLoaded(array(
new UserSecurityIdentity('foo'),
new UserSecurityIdentity('johannes'),
)));
$this->assertFalse($acl->isSidLoaded(new RoleSecurityIdentity('ROLE_FOO')));
$this->assertFalse($acl->isSidLoaded(new UserSecurityIdentity('schmittjoh@gmail.com')));
$this->assertFalse($acl->isSidLoaded(array(
new UserSecurityIdentity('foo'),
new UserSecurityIdentity('johannes'),
new RoleSecurityIdentity('ROLE_FOO'),
)));
}
/**
* @dataProvider getUpdateAceTests
* @expectedException \OutOfBoundsException
*/
public function testUpdateAceThrowsOutOfBoundsExceptionOnInvalidIndex($type)
{
$acl = $this->getAcl();
$acl->{'update'.$type}(0, 1);
}
/**
* @dataProvider getUpdateAceTests
*/
public function testUpdateAce($type)
{
$acl = $this->getAcl();
$acl->{'insert'.$type}(new RoleSecurityIdentity('foo'), 1);
$listener = $this->getListener(array(
'mask', 'mask', 'strategy',
));
$acl->addPropertyChangedListener($listener);
$aces = $acl->{'get'.$type.'s'}();
$ace = reset($aces);
$this->assertEquals(1, $ace->getMask());
$this->assertEquals('all', $ace->getStrategy());
$acl->{'update'.$type}(0, 3);
$this->assertEquals(3, $ace->getMask());
$this->assertEquals('all', $ace->getStrategy());
$acl->{'update'.$type}(0, 1, 'foo');
$this->assertEquals(1, $ace->getMask());
$this->assertEquals('foo', $ace->getStrategy());
}
public function getUpdateAceTests()
{
return array(
array('classAce'),
array('objectAce'),
);
}
/**
* @dataProvider getUpdateFieldAceTests
* @expectedException \OutOfBoundsException
*/
public function testUpdateFieldAceThrowsExceptionOnInvalidIndex($type)
{
$acl = $this->getAcl();
$acl->{'update'.$type}(0, 'foo', 1);
}
/**
* @dataProvider getUpdateFieldAceTests
*/
public function testUpdateFieldAce($type)
{
$acl = $this->getAcl();
$acl->{'insert'.$type}('foo', new UserSecurityIdentity('foo'), 1);
$listener = $this->getListener(array(
'mask', 'mask', 'strategy'
));
$acl->addPropertyChangedListener($listener);
$aces = $acl->{'get'.$type.'s'}('foo');
$ace = reset($aces);
$this->assertEquals(1, $ace->getMask());
$this->assertEquals('all', $ace->getStrategy());
$acl->{'update'.$type}(0, 'foo', 3);
$this->assertEquals(3, $ace->getMask());
$this->assertEquals('all', $ace->getStrategy());
$acl->{'update'.$type}(0, 'foo', 1, 'foo');
$this->assertEquals(1, $ace->getMask());
$this->assertEquals('foo', $ace->getStrategy());
}
public function getUpdateFieldAceTests()
{
return array(
array('classFieldAce'),
array('objectFieldAce'),
);
}
/**
* @dataProvider getUpdateAuditingTests
* @expectedException \OutOfBoundsException
*/
public function testUpdateAuditingThrowsExceptionOnInvalidIndex($type)
{
$acl = $this->getAcl();
$acl->{'update'.$type.'Auditing'}(0, true, false);
}
/**
* @dataProvider getUpdateAuditingTests
*/
public function testUpdateAuditing($type)
{
$acl = $this->getAcl();
$acl->{'insert'.$type.'Ace'}(new RoleSecurityIdentity('foo'), 1);
$listener = $this->getListener(array(
'auditFailure', 'auditSuccess', 'auditFailure',
));
$acl->addPropertyChangedListener($listener);
$aces = $acl->{'get'.$type.'Aces'}();
$ace = reset($aces);
$this->assertFalse($ace->isAuditSuccess());
$this->assertFalse($ace->isAuditFailure());
$acl->{'update'.$type.'Auditing'}(0, false, true);
$this->assertFalse($ace->isAuditSuccess());
$this->assertTrue($ace->isAuditFailure());
$acl->{'update'.$type.'Auditing'}(0, true, false);
$this->assertTrue($ace->isAuditSuccess());
$this->assertFalse($ace->isAuditFailure());
}
public function getUpdateAuditingTests()
{
return array(
array('class'),
array('object'),
);
}
/**
* @expectedException \InvalidArgumentException
* @dataProvider getUpdateFieldAuditingTests
*/
public function testUpdateFieldAuditingthrowsExceptionOnInvalidField($type)
{
$acl = $this->getAcl();
$acl->{'update'.$type.'Auditing'}(0, 'foo', true, true);
}
/**
* @expectedException \OutOfBoundsException
* @dataProvider getUpdateFieldAuditingTests
*/
public function testUpdateFieldAuditingThrowsExceptionOnInvalidIndex($type)
{
$acl = $this->getAcl();
$acl->{'insert'.$type.'Ace'}('foo', new RoleSecurityIdentity('foo'), 1);
$acl->{'update'.$type.'Auditing'}(1, 'foo', true, false);
}
/**
* @dataProvider getUpdateFieldAuditingTests
*/
public function testUpdateFieldAuditing($type)
{
$acl = $this->getAcl();
$acl->{'insert'.$type.'Ace'}('foo', new RoleSecurityIdentity('foo'), 1);
$listener = $this->getListener(array(
'auditSuccess', 'auditSuccess', 'auditFailure',
));
$acl->addPropertyChangedListener($listener);
$aces = $acl->{'get'.$type.'Aces'}('foo');
$ace = reset($aces);
$this->assertFalse($ace->isAuditSuccess());
$this->assertFalse($ace->isAuditFailure());
$acl->{'update'.$type.'Auditing'}(0, 'foo', true, false);
$this->assertTrue($ace->isAuditSuccess());
$this->assertFalse($ace->isAuditFailure());
$acl->{'update'.$type.'Auditing'}(0, 'foo', false, true);
$this->assertFalse($ace->isAuditSuccess());
$this->assertTrue($ace->isAuditFailure());
}
public function getUpdateFieldAuditingTests()
{
return array(
array('classField'),
array('objectField'),
);
}
protected function getListener($expectedChanges)
{
$aceProperties = array('aceOrder', 'mask', 'strategy', 'auditSuccess', 'auditFailure');
$listener = $this->getMock('Doctrine\Common\PropertyChangedListener');
foreach ($expectedChanges as $index => $property) {
if (in_array($property, $aceProperties)) {
$class = 'Symfony\Component\Security\Acl\Domain\Entry';
} else {
$class = 'Symfony\Component\Security\Acl\Domain\Acl';
}
$listener
->expects($this->at($index))
->method('propertyChanged')
->with($this->isInstanceOf($class), $this->equalTo($property))
;
}
return $listener;
}
protected function getAcl()
{
return new Acl(1, new ObjectIdentity(1, 'foo'), new PermissionGrantingStrategy(), array(), true);
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace Symfony\Component\Security\Acl\Domain;
class AuditLoggerTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider getTestLogData
*/
public function testLogIfNeeded($granting, $audit)
{
$logger = $this->getLogger();
$ace = $this->getEntry();
if (true === $granting) {
$ace
->expects($this->once())
->method('isAuditSuccess')
->will($this->returnValue($audit))
;
$ace
->expects($this->never())
->method('isAuditFailure')
;
}
else {
$ace
->expects($this->never())
->method('isAuditSuccess')
;
$ace
->expects($this->once())
->method('isAuditFailure')
->will($this->returnValue($audit))
;
}
if (true === $audit) {
$logger
->expects($this->once())
->method('doLog')
->with($this->equalTo($granting), $this->equalTo($ace))
;
}
else {
$logger
->expects($this->never())
->method('doLog')
;
}
$logger->logIfNeeded($granting, $ace);
}
public function getTestLogData()
{
return array(
array(true, false),
array(true, true),
array(false, false),
array(false, true),
);
}
protected function getEntry()
{
return $this->getMock('Symfony\Component\Security\Acl\Model\AuditableEntryInterface');
}
protected function getLogger()
{
return $this->getMockForAbstractClass('Symfony\Component\Security\Acl\Domain\AuditLogger');
}
}

View File

@ -0,0 +1,94 @@
<?php
namespace Symfony\Tests\Component\Security\Acl\Domain;
use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
use Symfony\Component\Security\Acl\Domain\PermissionGrantingStrategy;
use Symfony\Component\Security\Acl\Domain\Acl;
use Symfony\Component\Security\Acl\Domain\DoctrineAclCache;
use Doctrine\Common\Cache\ArrayCache;
class DoctrineAclCacheTest extends \PHPUnit_Framework_TestCase
{
protected $permissionGrantingStrategy;
/**
* @expectedException \InvalidArgumentException
* @dataProvider getEmptyValue
*/
public function testConstructorDoesNotAcceptEmptyPrefix($empty)
{
new DoctrineAclCache(new ArrayCache(), $this->getPermissionGrantingStrategy(), $empty);
}
public function getEmptyValue()
{
return array(
array(null),
array(false),
array(''),
);
}
public function test()
{
$cache = $this->getCache();
$aclWithParent = $this->getAcl(1);
$acl = $this->getAcl();
$cache->putInCache($aclWithParent);
$cache->putInCache($acl);
$cachedAcl = $cache->getFromCacheByIdentity($acl->getObjectIdentity());
$this->assertEquals($acl->getId(), $cachedAcl->getId());
$this->assertNull($acl->getParentAcl());
$cachedAclWithParent = $cache->getFromCacheByIdentity($aclWithParent->getObjectIdentity());
$this->assertEquals($aclWithParent->getId(), $cachedAclWithParent->getId());
$this->assertNotNull($cachedParentAcl = $cachedAclWithParent->getParentAcl());
$this->assertEquals($aclWithParent->getParentAcl()->getId(), $cachedParentAcl->getId());
}
protected function getAcl($depth = 0)
{
static $id = 1;
$acl = new Acl($id, new ObjectIdentity($id, 'foo'), $this->getPermissionGrantingStrategy(), array(), $depth > 0);
// insert some ACEs
$sid = new UserSecurityIdentity('johannes');
$acl->insertClassAce($sid, 1);
$acl->insertClassFieldAce('foo', $sid, 1);
$acl->insertObjectAce($sid, 1);
$acl->insertObjectFieldAce('foo', $sid, 1);
$id++;
if ($depth > 0) {
$acl->setParentAcl($this->getAcl($depth - 1));
}
return $acl;
}
protected function getPermissionGrantingStrategy()
{
if (null === $this->permissionGrantingStrategy) {
$this->permissionGrantingStrategy = new PermissionGrantingStrategy();
}
return $this->permissionGrantingStrategy;
}
protected function getCache($cacheDriver = null, $prefix = DoctrineAclCache::PREFIX)
{
if (null === $cacheDriver) {
$cacheDriver = new ArrayCache();
}
return new DoctrineAclCache($cacheDriver, $this->getPermissionGrantingStrategy(), $prefix);
}
}

View File

@ -0,0 +1,110 @@
<?php
namespace Symfony\Tests\Component\Security\Domain;
use Symfony\Component\Security\Acl\Domain\Entry;
class EntryTest extends \PHPUnit_Framework_TestCase
{
public function testConstructor()
{
$ace = $this->getAce($acl = $this->getAcl(), $sid = $this->getSid());
$this->assertEquals(123, $ace->getId());
$this->assertSame($acl, $ace->getAcl());
$this->assertSame($sid, $ace->getSecurityIdentity());
$this->assertEquals('foostrat', $ace->getStrategy());
$this->assertEquals(123456, $ace->getMask());
$this->assertTrue($ace->isGranting());
$this->assertTrue($ace->isAuditSuccess());
$this->assertFalse($ace->isAuditFailure());
}
public function testSetAuditSuccess()
{
$ace = $this->getAce();
$this->assertTrue($ace->isAuditSuccess());
$ace->setAuditSuccess(false);
$this->assertFalse($ace->isAuditSuccess());
$ace->setAuditsuccess(true);
$this->assertTrue($ace->isAuditSuccess());
}
public function testSetAuditFailure()
{
$ace = $this->getAce();
$this->assertFalse($ace->isAuditFailure());
$ace->setAuditFailure(true);
$this->assertTrue($ace->isAuditFailure());
$ace->setAuditFailure(false);
$this->assertFalse($ace->isAuditFailure());
}
public function testSetMask()
{
$ace = $this->getAce();
$this->assertEquals(123456, $ace->getMask());
$ace->setMask(4321);
$this->assertEquals(4321, $ace->getMask());
}
public function testSetStrategy()
{
$ace = $this->getAce();
$this->assertEquals('foostrat', $ace->getStrategy());
$ace->setStrategy('foo');
$this->assertEquals('foo', $ace->getStrategy());
}
public function testSerializeUnserialize()
{
$ace = $this->getAce();
$serialized = serialize($ace);
$uAce = unserialize($serialized);
$this->assertNull($uAce->getAcl());
$this->assertInstanceOf('Symfony\Component\Security\Acl\Model\SecurityIdentityInterface', $uAce->getSecurityIdentity());
$this->assertEquals($ace->getId(), $uAce->getId());
$this->assertEquals($ace->getMask(), $uAce->getMask());
$this->assertEquals($ace->getStrategy(), $uAce->getStrategy());
$this->assertEquals($ace->isGranting(), $uAce->isGranting());
$this->assertEquals($ace->isAuditSuccess(), $uAce->isAuditSuccess());
$this->assertEquals($ace->isAuditFailure(), $uAce->isAuditFailure());
}
protected function getAce($acl = null, $sid = null)
{
if (null === $acl) {
$acl = $this->getAcl();
}
if (null === $sid) {
$sid = $this->getSid();
}
return new Entry(
123,
$acl,
$sid,
'foostrat',
123456,
true,
false,
true
);
}
protected function getAcl()
{
return $this->getMock('Symfony\Component\Security\Acl\Model\AclInterface');
}
protected function getSid()
{
return $this->getMock('Symfony\Component\Security\Acl\Model\SecurityIdentityInterface');
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace Symfony\Tests\Component\Security\Acl\Domain;
use Symfony\Component\Security\Acl\Domain\FieldEntry;
class FieldEntryTest extends \PHPUnit_Framework_TestCase
{
public function testConstructor()
{
$ace = $this->getAce();
$this->assertEquals('foo', $ace->getField());
}
public function testSerializeUnserialize()
{
$ace = $this->getAce();
$serialized = serialize($ace);
$uAce = unserialize($serialized);
$this->assertNull($uAce->getAcl());
$this->assertInstanceOf('Symfony\Component\Security\Acl\Model\SecurityIdentityInterface', $uAce->getSecurityIdentity());
$this->assertEquals($ace->getId(), $uAce->getId());
$this->assertEquals($ace->getField(), $uAce->getField());
$this->assertEquals($ace->getMask(), $uAce->getMask());
$this->assertEquals($ace->getStrategy(), $uAce->getStrategy());
$this->assertEquals($ace->isGranting(), $uAce->isGranting());
$this->assertEquals($ace->isAuditSuccess(), $uAce->isAuditSuccess());
$this->assertEquals($ace->isAuditFailure(), $uAce->isAuditFailure());
}
protected function getAce($acl = null, $sid = null)
{
if (null === $acl) {
$acl = $this->getAcl();
}
if (null === $sid) {
$sid = $this->getSid();
}
return new FieldEntry(
123,
$acl,
'foo',
$sid,
'foostrat',
123456,
true,
false,
true
);
}
protected function getAcl()
{
return $this->getMock('Symfony\Component\Security\Acl\Model\AclInterface');
}
protected function getSid()
{
return $this->getMock('Symfony\Component\Security\Acl\Model\SecurityIdentityInterface');
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace Symfony\Tests\Component\Security\Acl\Domain;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
use Symfony\Component\Security\Acl\Domain\ObjectIdentityRetrievalStrategy;
class ObjectIdentityRetrievalStrategyTest extends \PHPUnit_Framework_TestCase
{
public function testGetObjectIdentityReturnsNullForInvalidDomainObject()
{
$strategy = new ObjectIdentityRetrievalStrategy();
$this->assertNull($strategy->getObjectIdentity('foo'));
}
public function testGetObjectIdentity()
{
$strategy = new ObjectIdentityRetrievalStrategy();
$domainObject = new DomainObject();
$objectIdentity = $strategy->getObjectIdentity($domainObject);
$this->assertEquals($domainObject->getId(), $objectIdentity->getIdentifier());
$this->assertEquals(get_class($domainObject), $objectIdentity->getType());
}
}
class DomainObject
{
public function getId()
{
return 'foo';
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace Symfony\Tests\Component\Security\Acl\Domain;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
class ObjectIdentityTest extends \PHPUnit_Framework_TestCase
{
public function testConstructor()
{
$id = new ObjectIdentity('fooid', 'footype');
$this->assertEquals('fooid', $id->getIdentifier());
$this->assertEquals('footype', $id->getType());
}
public function testFromDomainObjectPrefersInterfaceOverGetId()
{
$domainObject = $this->getMock('Symfony\Component\Security\Acl\Model\DomainObjectInterface');
$domainObject
->expects($this->once())
->method('getObjectIdentifier')
->will($this->returnValue('getObjectIdentifier()'))
;
$domainObject
->expects($this->never())
->method('getId')
->will($this->returnValue('getId()'))
;
$id = ObjectIdentity::fromDomainObject($domainObject);
$this->assertEquals('getObjectIdentifier()', $id->getIdentifier());
}
public function testFromDomainObjectWithoutInterface()
{
$id = ObjectIdentity::fromDomainObject(new TestDomainObject());
$this->assertEquals('getId()', $id->getIdentifier());
}
/**
* @dataProvider getCompareData
*/
public function testEquals($oid1, $oid2, $equal)
{
if ($equal) {
$this->assertTrue($oid1->equals($oid2));
}
else {
$this->assertFalse($oid1->equals($oid2));
}
}
public function getCompareData()
{
return array(
array(new ObjectIdentity('123', 'foo'), new ObjectIdentity('123', 'foo'), true),
array(new ObjectIdentity('123', 'foo'), new ObjectIdentity(123, 'foo'), true),
array(new ObjectIdentity('1', 'foo'), new ObjectIdentity('2', 'foo'), false),
array(new ObjectIdentity('1', 'bla'), new ObjectIdentity('1', 'blub'), false),
);
}
}
class TestDomainObject
{
public function getObjectIdentifier()
{
return 'getObjectIdentifier()';
}
public function getId()
{
return 'getId()';
}
}

View File

@ -0,0 +1,190 @@
<?php
namespace Symfony\Tests\Component\Security\Acl\Domain;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity;
use Symfony\Component\Security\Acl\Domain\Acl;
use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
use Symfony\Component\Security\Acl\Domain\Entry;
use Symfony\Component\Security\Acl\Domain\PermissionGrantingStrategy;
use Symfony\Component\Security\Acl\Exception\NoAceFoundException;
class PermissionGrantingStrategyTest extends \PHPUnit_Framework_TestCase
{
/**
* @covers:Symfony\Component\Security\Acl\Domain\PermissionGrantingStrategy::getAuditLogger
* @covers:Symfony\Component\Security\Acl\Domain\PermissionGrantingStrategy::setAuditLogger
*/
public function testGetSetAuditLogger()
{
$strategy = new PermissionGrantingStrategy();
$logger = $this->getMock('Symfony\Component\Security\Acl\Model\AuditLoggerInterface');
$this->assertNull($strategy->getAuditLogger());
$strategy->setAuditLogger($logger);
$this->assertSame($logger, $strategy->getAuditLogger());
}
public function testIsGrantedObjectAcesHavePriority()
{
$strategy = new PermissionGrantingStrategy();
$acl = $this->getAcl($strategy);
$sid = new UserSecurityIdentity('johannes');
$acl->insertClassAce($sid, 1);
$acl->insertObjectAce($sid, 1, 0, false);
$this->assertFalse($strategy->isGranted($acl, array(1), array($sid)));
}
public function testIsGrantedFallsbackToClassAcesIfNoApplicableObjectAceWasFound()
{
$strategy = new PermissionGrantingStrategy();
$acl = $this->getAcl($strategy);
$sid = new UserSecurityIdentity('johannes');
$acl->insertClassAce($sid, 1);
$this->assertTrue($strategy->isGranted($acl, array(1), array($sid)));
}
public function testIsGrantedFavorsLocalAcesOverParentAclAces()
{
$strategy = new PermissionGrantingStrategy();
$sid = new UserSecurityIdentity('johannes');
$acl = $this->getAcl($strategy);
$acl->insertClassAce($sid, 1);
$parentAcl = $this->getAcl($strategy);
$acl->setParentAcl($parentAcl);
$parentAcl->insertClassAce($sid, 1, 0, false);
$this->assertTrue($strategy->isGranted($acl, array(1), array($sid)));
}
public function testIsGrantedFallsBackToParentAcesIfNoLocalAcesAreApplicable()
{
$strategy = new PermissionGrantingStrategy();
$sid = new UserSecurityIdentity('johannes');
$anotherSid = new UserSecurityIdentity('ROLE_USER');
$acl = $this->getAcl($strategy);
$acl->insertClassAce($anotherSid, 1, 0, false);
$parentAcl = $this->getAcl($strategy);
$acl->setParentAcl($parentAcl);
$parentAcl->insertClassAce($sid, 1);
$this->assertTrue($strategy->isGranted($acl, array(1), array($sid)));
}
/**
* @expectedException Symfony\Component\Security\Acl\Exception\NoAceFoundException
*/
public function testIsGrantedReturnsExceptionIfNoAceIsFound()
{
$strategy = new PermissionGrantingStrategy();
$acl = $this->getAcl($strategy);
$sid = new UserSecurityIdentity('johannes');
$strategy->isGranted($acl, array(1), array($sid));
}
public function testIsGrantedFirstApplicableEntryMakesUltimateDecisionForPermissionIdentityCombination()
{
$strategy = new PermissionGrantingStrategy();
$acl = $this->getAcl($strategy);
$sid = new UserSecurityIdentity('johannes');
$aSid = new RoleSecurityIdentity('ROLE_USER');
$acl->insertClassAce($aSid, 1);
$acl->insertClassAce($sid, 1, 1, false);
$acl->insertClassAce($sid, 1, 2);
$this->assertFalse($strategy->isGranted($acl, array(1), array($sid, $aSid)));
$acl->insertObjectAce($sid, 1, 0, false);
$acl->insertObjectAce($aSid, 1, 1);
$this->assertFalse($strategy->isGranted($acl, array(1), array($sid, $aSid)));
}
public function testIsGrantedCallsAuditLoggerOnGrant()
{
$strategy = new PermissionGrantingStrategy();
$acl = $this->getAcl($strategy);
$sid = new UserSecurityIdentity('johannes');
$logger = $this->getMock('Symfony\Component\Security\Acl\Model\AuditLoggerInterface');
$logger
->expects($this->once())
->method('logIfNeeded')
;
$strategy->setAuditLogger($logger);
$acl->insertObjectAce($sid, 1);
$acl->updateObjectAuditing(0, true, false);
$this->assertTrue($strategy->isGranted($acl, array(1), array($sid)));
}
public function testIsGrantedCallsAuditLoggerOnDeny()
{
$strategy = new PermissionGrantingStrategy();
$acl = $this->getAcl($strategy);
$sid = new UserSecurityIdentity('johannes');
$logger = $this->getMock('Symfony\Component\Security\Acl\Model\AuditLoggerInterface');
$logger
->expects($this->once())
->method('logIfNeeded')
;
$strategy->setAuditLogger($logger);
$acl->insertObjectAce($sid, 1, 0, false);
$acl->updateObjectAuditing(0, false, true);
$this->assertFalse($strategy->isGranted($acl, array(1), array($sid)));
}
/**
* @dataProvider getAllStrategyTests
*/
public function testIsGrantedStrategies($maskStrategy, $aceMask, $requiredMask, $result)
{
$strategy = new PermissionGrantingStrategy();
$acl = $this->getAcl($strategy);
$sid = new UserSecurityIdentity('johannes');
$acl->insertObjectAce($sid, $aceMask, 0, true, $maskStrategy);
if (false === $result) {
try {
$strategy->isGranted($acl, array($requiredMask), array($sid));
$this->fail('The ACE is not supposed to match.');
} catch (NoAceFoundException $noAce) { }
} else {
$this->assertTrue($strategy->isGranted($acl, array($requiredMask), array($sid)));
}
}
public function getAllStrategyTests()
{
return array(
array('all', 1 << 0 | 1 << 1, 1 << 0, true),
array('all', 1 << 0 | 1 << 1, 1 << 2, false),
array('all', 1 << 0 | 1 << 10, 1 << 0 | 1 << 10, true),
array('all', 1 << 0 | 1 << 1, 1 << 0 | 1 << 1 || 1 << 2, false),
array('any', 1 << 0 | 1 << 1, 1 << 0, true),
array('any', 1 << 0 | 1 << 1, 1 << 0 | 1 << 2, true),
array('any', 1 << 0 | 1 << 1, 1 << 2, false),
array('equal', 1 << 0 | 1 << 1, 1 << 0, false),
array('equal', 1 << 0 | 1 << 1, 1 << 1, false),
array('equal', 1 << 0 | 1 << 1, 1 << 0 | 1 << 1, true),
);
}
protected function getAcl($strategy)
{
static $id = 1;
return new Acl($id++, new ObjectIdentity(1, 'Foo'), $strategy, array(), true);
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace Symfony\Tests\Component\Security\Acl\Domain;
use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
use Symfony\Component\Security\Role\Role;
use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity;
class RoleSecurityIdentityTest extends \PHPUnit_Framework_TestCase
{
public function testConstructor()
{
$id = new RoleSecurityIdentity('ROLE_FOO');
$this->assertEquals('ROLE_FOO', $id->getRole());
}
public function testConstructorWithRoleInstance()
{
$id = new RoleSecurityIdentity(new Role('ROLE_FOO'));
$this->assertEquals('ROLE_FOO', $id->getRole());
}
/**
* @dataProvider getCompareData
*/
public function testEquals($id1, $id2, $equal)
{
if ($equal) {
$this->assertTrue($id1->equals($id2));
}
else {
$this->assertFalse($id1->equals($id2));
}
}
public function getCompareData()
{
return array(
array(new RoleSecurityIdentity('ROLE_FOO'), new RoleSecurityIdentity('ROLE_FOO'), true),
array(new RoleSecurityIdentity('ROLE_FOO'), new RoleSecurityIdentity(new Role('ROLE_FOO')), true),
array(new RoleSecurityIdentity('ROLE_USER'), new RoleSecurityIdentity('ROLE_FOO'), false),
array(new RoleSecurityIdentity('ROLE_FOO'), new UserSecurityIdentity('ROLE_FOO'), false),
);
}
}

View File

@ -0,0 +1,131 @@
<?php
namespace Symfony\Tests\Component\Security\Acl\Domain;
use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity;
use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
use Symfony\Component\Security\Acl\Domain\SecurityIdentityRetrievalStrategy;
class SecurityIdentityRetrievalStrategyTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider getSecurityIdentityRetrievalTests
*/
public function testGetSecurityIdentities($username, array $roles, $authenticationStatus, array $sids)
{
$strategy = $this->getStrategy($roles, $authenticationStatus);
$token = $this->getMock('Symfony\Component\Security\Authentication\Token\TokenInterface');
if ('anonymous' !== $authenticationStatus) {
$token
->expects($this->once())
->method('__toString')
->will($this->returnValue($username))
;
}
$token
->expects($this->once())
->method('getRoles')
->will($this->returnValue(array('foo')))
;
$extractedSids = $strategy->getSecurityIdentities($token);
foreach ($extractedSids as $index => $extractedSid) {
if (!isset($sids[$index])) {
$this->fail(sprintf('Expected SID at index %d, but there was none.', true));
}
if (false === $sids[$index]->equals($extractedSid)) {
$this->fail(sprintf('Index: %d, expected SID "%s", but got "%s".', $index, $sids[$index], $extractedSid));
}
}
}
public function getSecurityIdentityRetrievalTests()
{
return array(
array('johannes', array('ROLE_USER', 'ROLE_SUPERADMIN'), 'fullFledged', array(
new UserSecurityIdentity('johannes'),
new RoleSecurityIdentity('ROLE_USER'),
new RoleSecurityIdentity('ROLE_SUPERADMIN'),
new RoleSecurityIdentity('IS_AUTHENTICATED_FULLY'),
new RoleSecurityIdentity('IS_AUTHENTICATED_REMEMBERED'),
new RoleSecurityIdentity('IS_AUTHENTICATED_ANONYMOUSLY'),
)),
array('foo', array('ROLE_FOO'), 'rememberMe', array(
new UserSecurityIdentity('foo'),
new RoleSecurityIdentity('ROLE_FOO'),
new RoleSecurityIdentity('IS_AUTHENTICATED_REMEMBERED'),
new RoleSecurityIdentity('IS_AUTHENTICATED_ANONYMOUSLY'),
)),
array('guest', array('ROLE_FOO'), 'anonymous', array(
new RoleSecurityIdentity('ROLE_FOO'),
new RoleSecurityIdentity('IS_AUTHENTICATED_ANONYMOUSLY'),
))
);
}
protected function getStrategy(array $roles = array(), $authenticationStatus = 'fullFledged')
{
$roleHierarchy = $this->getMock('Symfony\Component\Security\Role\RoleHierarchyInterface');
$roleHierarchy
->expects($this->once())
->method('getReachableRoles')
->with($this->equalTo(array('foo')))
->will($this->returnValue($roles))
;
$trustResolver = $this->getMock('Symfony\Component\Security\Authentication\AuthenticationTrustResolver', array(), array('', ''));
$trustResolver
->expects($this->at(0))
->method('isAnonymous')
->will($this->returnValue('anonymous' === $authenticationStatus))
;
if ('fullFledged' === $authenticationStatus) {
$trustResolver
->expects($this->once())
->method('isFullFledged')
->will($this->returnValue(true))
;
$trustResolver
->expects($this->never())
->method('isRememberMe')
;
} else if ('rememberMe' === $authenticationStatus) {
$trustResolver
->expects($this->once())
->method('isFullFledged')
->will($this->returnValue(false))
;
$trustResolver
->expects($this->once())
->method('isRememberMe')
->will($this->returnValue(true))
;
} else {
$trustResolver
->expects($this->at(1))
->method('isAnonymous')
->will($this->returnValue(true))
;
$trustResolver
->expects($this->once())
->method('isFullFledged')
->will($this->returnValue(false))
;
$trustResolver
->expects($this->once())
->method('isRememberMe')
->will($this->returnValue(false))
;
}
return new SecurityIdentityRetrievalStrategy($roleHierarchy, $trustResolver);
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace Symfony\Tests\Component\Security\Acl\Domain;
use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity;
use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
class UserSecurityIdentityTest extends \PHPUnit_Framework_TestCase
{
public function testConstructor()
{
$id = new UserSecurityIdentity('foo');
$this->assertEquals('foo', $id->getUsername());
}
public function testConstructorWithToken()
{
$token = $this->getMock('Symfony\Component\Security\Authentication\Token\TokenInterface');
$token
->expects($this->once())
->method('__toString')
->will($this->returnValue('foo'))
;
$id = new UserSecurityIdentity($token);
$this->assertEquals('foo', $id->getUsername());
}
/**
* @dataProvider getCompareData
*/
public function testEquals($id1, $id2, $equal)
{
if ($equal) {
$this->assertTrue($id1->equals($id2));
}
else {
$this->assertFalse($id1->equals($id2));
}
}
public function getCompareData()
{
$token = $this->getMock('Symfony\Component\Security\Authentication\Token\TokenInterface');
$token
->expects($this->once())
->method('__toString')
->will($this->returnValue('foo'))
;
return array(
array(new UserSecurityIdentity('foo'), new UserSecurityIdentity('foo'), true),
array(new UserSecurityIdentity('foo'), new UserSecurityIdentity($token), true),
array(new UserSecurityIdentity('bla'), new UserSecurityIdentity('blub'), false),
array(new UserSecurityIdentity('foo'), new RoleSecurityIdentity('foo'), false),
);
}
}

View File

@ -0,0 +1,94 @@
<?php
namespace Symfony\Tests\Component\Security\Acl\Util;
use Symfony\Component\Security\Acl\Permission\MaskBuilder;
class MaskBuilderTest extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException \InvalidArgumentException
* @dataProvider getInvalidConstructorData
*/
public function testConstructorWithNonInteger($invalidMask)
{
new MaskBuilder($invalidMask);
}
public function getInvalidConstructorData()
{
return array(
array(234.463),
array('asdgasdf'),
array(array()),
array(new \stdClass()),
);
}
public function testConstructorWithoutArguments()
{
$builder = new MaskBuilder();
$this->assertEquals(0, $builder->get());
}
public function testConstructor()
{
$builder = new MaskBuilder(123456);
$this->assertEquals(123456, $builder->get());
}
public function testAddAndRemove()
{
$builder = new MaskBuilder();
$builder
->add('view')
->add('eDiT')
->add('ownEr')
;
$mask = $builder->get();
$this->assertEquals(MaskBuilder::MASK_VIEW, $mask & MaskBuilder::MASK_VIEW);
$this->assertEquals(MaskBuilder::MASK_EDIT, $mask & MaskBuilder::MASK_EDIT);
$this->assertEquals(MaskBuilder::MASK_OWNER, $mask & MaskBuilder::MASK_OWNER);
$this->assertEquals(0, $mask & MaskBuilder::MASK_MASTER);
$this->assertEquals(0, $mask & MaskBuilder::MASK_CREATE);
$this->assertEquals(0, $mask & MaskBuilder::MASK_DELETE);
$this->assertEquals(0, $mask & MaskBuilder::MASK_UNDELETE);
$builder->remove('edit')->remove('OWner');
$mask = $builder->get();
$this->assertEquals(0, $mask & MaskBuilder::MASK_EDIT);
$this->assertEquals(0, $mask & MaskBuilder::MASK_OWNER);
$this->assertEquals(MaskBuilder::MASK_VIEW, $mask & MaskBuilder::MASK_VIEW);
}
public function testGetPattern()
{
$builder = new MaskBuilder;
$this->assertEquals(MaskBuilder::ALL_OFF, $builder->getPattern());
$builder->add('view');
$this->assertEquals(str_repeat('.', 31).'V', $builder->getPattern());
$builder->add('owner');
$this->assertEquals(str_repeat('.', 24).'N......V', $builder->getPattern());
$builder->add(1 << 10);
$this->assertEquals(str_repeat('.', 21).MaskBuilder::ON.'..N......V', $builder->getPattern());
}
public function testReset()
{
$builder = new MaskBuilder();
$this->assertEquals(0, $builder->get());
$builder->add('view');
$this->assertTrue($builder->get() > 0);
$builder->reset();
$this->assertEquals(0, $builder->get());
}
}