├── .github ├── psalm │ ├── .gitignore │ └── psalm.baseline.xml └── workflows │ ├── ci.yml │ └── psalm.yml ├── .gitignore ├── .php-cs-fixer.dist.php ├── CHANGELOG.md ├── Dbal ├── AclProvider.php ├── MutableAclProvider.php └── Schema.php ├── Domain ├── Acl.php ├── AclCacheTrait.php ├── AclCollectionCache.php ├── AuditLogger.php ├── DoctrineAclCache.php ├── Entry.php ├── FieldEntry.php ├── ObjectIdentity.php ├── ObjectIdentityRetrievalStrategy.php ├── PermissionGrantingStrategy.php ├── PsrAclCache.php ├── RoleSecurityIdentity.php ├── SecurityIdentityRetrievalStrategy.php └── UserSecurityIdentity.php ├── Exception ├── AclAlreadyExistsException.php ├── AclNotFoundException.php ├── ConcurrentModificationException.php ├── Exception.php ├── InvalidDomainObjectException.php ├── NoAceFoundException.php ├── NotAllAclsFoundException.php └── SidNotLoadedException.php ├── LICENSE ├── Model ├── AclCacheInterface.php ├── AclInterface.php ├── AclProviderInterface.php ├── AuditLoggerInterface.php ├── AuditableAclInterface.php ├── AuditableEntryInterface.php ├── DomainObjectInterface.php ├── EntryInterface.php ├── FieldEntryInterface.php ├── MutableAclInterface.php ├── MutableAclProviderInterface.php ├── ObjectIdentityInterface.php ├── ObjectIdentityRetrievalStrategyInterface.php ├── PermissionGrantingStrategyInterface.php ├── SecurityIdentityInterface.php └── SecurityIdentityRetrievalStrategyInterface.php ├── Permission ├── AbstractMaskBuilder.php ├── BasicPermissionMap.php ├── MaskBuilder.php ├── MaskBuilderInterface.php ├── MaskBuilderRetrievalInterface.php └── PermissionMapInterface.php ├── README.md ├── Resources ├── bin │ └── generateSql.php └── schema │ ├── db2.sql │ ├── drizzle.sql │ ├── mssql.sql │ ├── mysql.sql │ ├── oracle.sql │ ├── postgresql.sql │ ├── sqlanywhere.sql │ └── sqlite.sql ├── Tests ├── Dbal │ ├── AclProviderBenchmarkTest.php │ ├── AclProviderTest.php │ └── MutableAclProviderTest.php ├── Domain │ ├── AclTest.php │ ├── AuditLoggerTest.php │ ├── DoctrineAclCacheTest.php │ ├── EntryTest.php │ ├── FieldEntryTest.php │ ├── ObjectIdentityRetrievalStrategyTest.php │ ├── ObjectIdentityTest.php │ ├── PermissionGrantingStrategyTest.php │ ├── PsrAclCacheTest.php │ ├── RoleSecurityIdentityTest.php │ ├── SecurityIdentityRetrievalStrategyTest.php │ └── UserSecurityIdentityTest.php ├── Fixtures │ ├── Account.php │ ├── SerializableAclInterface.php │ └── SerializableAuditableEntryInterface.php ├── Permission │ ├── BasicPermissionMapTest.php │ └── MaskBuilderTest.php └── Voter │ └── AclVoterTest.php ├── Util └── ClassUtils.php ├── Voter ├── AclVoter.php └── FieldVote.php ├── composer.json ├── phpunit.xml.dist └── psalm.xml /.github/psalm/.gitignore: -------------------------------------------------------------------------------- 1 | /cache 2 | -------------------------------------------------------------------------------- /.github/psalm/psalm.baseline.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [main] 7 | 8 | jobs: 9 | test: 10 | name: 'Test ${{ matrix.deps }} on PHP ${{ matrix.php }}' 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | php: ['7.2.5', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] 17 | include: 18 | - php: '7.4' 19 | deps: lowest 20 | deprecations: max[self]=0 21 | - php: '8.4' 22 | deps: highest 23 | 24 | steps: 25 | - name: Checkout code 26 | uses: actions/checkout@v2 27 | 28 | - name: Setup PHP 29 | uses: shivammathur/setup-php@v2 30 | with: 31 | php-version: '${{ matrix.php }}' 32 | coverage: none 33 | 34 | - name: Configure composer 35 | if: "${{ matrix.deps == 'highest' }}" 36 | run: composer config minimum-stability dev 37 | 38 | - name: Composer install 39 | uses: ramsey/composer-install@v1 40 | with: 41 | dependency-versions: '${{ matrix.deps }}' 42 | 43 | - name: Install PHPUnit 44 | run: vendor/bin/simple-phpunit install 45 | 46 | - name: Run tests 47 | run: vendor/bin/simple-phpunit 48 | env: 49 | SYMFONY_DEPRECATIONS_HELPER: '${{ matrix.deprecations }}' 50 | 51 | cs: 52 | name: 'Code Style' 53 | runs-on: ubuntu-latest 54 | 55 | steps: 56 | - name: Checkout code 57 | uses: actions/checkout@v2 58 | - name: PHP-CS-Fixer 59 | uses: docker://oskarstark/php-cs-fixer-ga:3.11.0 60 | with: 61 | args: --diff --dry-run 62 | -------------------------------------------------------------------------------- /.github/workflows/psalm.yml: -------------------------------------------------------------------------------- 1 | name: Static analysis 2 | 3 | on: 4 | pull_request: ~ 5 | 6 | defaults: 7 | run: 8 | shell: bash 9 | 10 | jobs: 11 | psalm: 12 | name: Psalm 13 | runs-on: Ubuntu-22.04 14 | 15 | steps: 16 | - name: Setup PHP 17 | uses: shivammathur/setup-php@v2 18 | with: 19 | php-version: '8.0' 20 | ini-values: "memory_limit=-1" 21 | coverage: none 22 | 23 | - name: Checkout target branch 24 | uses: actions/checkout@v2 25 | with: 26 | ref: ${{ github.base_ref }} 27 | 28 | - name: Checkout PR 29 | uses: actions/checkout@v2 30 | 31 | - name: Install dependencies 32 | run: | 33 | composer remove --dev --no-update --no-interaction symfony/phpunit-bridge 34 | composer require --no-update vimeo/psalm phpunit/phpunit:^9.5 psalm/plugin-phpunit 35 | composer update --no-progress --ansi 36 | git checkout -- composer.json 37 | 38 | ./vendor/bin/psalm --version 39 | 40 | - name: Generate Psalm baseline 41 | run: | 42 | git checkout -m ${{ github.base_ref }} 43 | ./vendor/bin/psalm --set-baseline=.github/psalm/psalm.baseline.xml --no-progress 44 | git checkout -m FETCH_HEAD 45 | 46 | - name: Psalm 47 | run: | 48 | ./vendor/bin/psalm --output-format=github --no-progress 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.lock 3 | phpunit.xml 4 | .phpunit.result.cache 5 | .php-cs-fixer.cache 6 | .php-cs-fixer.php 7 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | setRules([ 5 | '@PHP71Migration' => true, 6 | '@PHPUnit75Migration:risky' => true, 7 | '@Symfony' => true, 8 | '@Symfony:risky' => true, 9 | 'protected_to_private' => false, 10 | 'phpdoc_to_comment' => ['ignored_tags' => ['psalm-suppress']], 11 | ]) 12 | ->setRiskyAllowed(true) 13 | ->setFinder( 14 | (new PhpCsFixer\Finder()) 15 | ->in(__DIR__) 16 | ->append([__FILE__]) 17 | ) 18 | ; 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | ========= 3 | 4 | 3.2.0 5 | ----- 6 | 7 | * Change minimum PHP version to 7.2.5 8 | * Add PSR-6 support for ACL caching 9 | * Add support for `doctrine/cache` v2 10 | * Drop support for Symfony 3 11 | * Deprecate not implementing `__serialize()` and `__unserialize()` methods in 12 | `AclInterface` and `EntryInterface` implementations. The methods will be 13 | added to the interfaces in 4.0. 14 | -------------------------------------------------------------------------------- /Dbal/Schema.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Dbal; 13 | 14 | use Doctrine\DBAL\Connection; 15 | use Doctrine\DBAL\Platforms\SQLServerPlatform; 16 | use Doctrine\DBAL\Schema\Schema as BaseSchema; 17 | use Doctrine\DBAL\Schema\SchemaConfig; 18 | 19 | /** 20 | * The schema used for the ACL system. 21 | * 22 | * @author Johannes M. Schmitt 23 | */ 24 | final class Schema extends BaseSchema 25 | { 26 | protected $options; 27 | protected $platform; 28 | 29 | /** 30 | * @param array $options the names for tables 31 | */ 32 | public function __construct(array $options, ?Connection $connection = null) 33 | { 34 | $schemaConfig = $this->createSchemaConfig($connection); 35 | 36 | parent::__construct([], [], $schemaConfig); 37 | 38 | $this->options = $options; 39 | $this->platform = $connection ? $connection->getDatabasePlatform() : null; 40 | 41 | $this->addClassTable(); 42 | $this->addSecurityIdentitiesTable(); 43 | $this->addObjectIdentitiesTable(); 44 | $this->addObjectIdentityAncestorsTable(); 45 | $this->addEntryTable(); 46 | } 47 | 48 | /** 49 | * Merges ACL schema with the given schema. 50 | */ 51 | public function addToSchema(BaseSchema $schema) 52 | { 53 | foreach ($this->getTables() as $table) { 54 | $schema->_addTable($table); 55 | } 56 | 57 | foreach ($this->getSequences() as $sequence) { 58 | $schema->_addSequence($sequence); 59 | } 60 | } 61 | 62 | /** 63 | * Adds the class table to the schema. 64 | */ 65 | protected function addClassTable() 66 | { 67 | $table = $this->createTable($this->options['class_table_name']); 68 | $table->addColumn('id', 'integer', ['unsigned' => true, 'autoincrement' => true]); 69 | $table->addColumn('class_type', 'string', ['length' => 200]); 70 | $table->setPrimaryKey(['id']); 71 | $table->addUniqueIndex(['class_type']); 72 | } 73 | 74 | /** 75 | * Adds the entry table to the schema. 76 | */ 77 | protected function addEntryTable() 78 | { 79 | $table = $this->createTable($this->options['entry_table_name']); 80 | 81 | $table->addColumn('id', 'integer', ['unsigned' => true, 'autoincrement' => true]); 82 | $table->addColumn('class_id', 'integer', ['unsigned' => true]); 83 | $table->addColumn('object_identity_id', 'integer', ['unsigned' => true, 'notnull' => false]); 84 | $table->addColumn('field_name', 'string', ['length' => 50, 'notnull' => false]); 85 | $table->addColumn('ace_order', 'smallint', ['unsigned' => true]); 86 | $table->addColumn('security_identity_id', 'integer', ['unsigned' => true]); 87 | $table->addColumn('mask', 'integer'); 88 | $table->addColumn('granting', 'boolean'); 89 | $table->addColumn('granting_strategy', 'string', ['length' => 30]); 90 | $table->addColumn('audit_success', 'boolean'); 91 | $table->addColumn('audit_failure', 'boolean'); 92 | 93 | $table->setPrimaryKey(['id']); 94 | $table->addUniqueIndex(['class_id', 'object_identity_id', 'field_name', 'ace_order']); 95 | $table->addIndex(['class_id', 'object_identity_id', 'security_identity_id']); 96 | 97 | $table->addForeignKeyConstraint($this->getTable($this->options['class_table_name']), ['class_id'], ['id'], ['onDelete' => 'CASCADE', 'onUpdate' => 'CASCADE']); 98 | $table->addForeignKeyConstraint($this->getTable($this->options['oid_table_name']), ['object_identity_id'], ['id'], ['onDelete' => 'CASCADE', 'onUpdate' => 'CASCADE']); 99 | $table->addForeignKeyConstraint($this->getTable($this->options['sid_table_name']), ['security_identity_id'], ['id'], ['onDelete' => 'CASCADE', 'onUpdate' => 'CASCADE']); 100 | } 101 | 102 | /** 103 | * Adds the object identity table to the schema. 104 | */ 105 | protected function addObjectIdentitiesTable() 106 | { 107 | $table = $this->createTable($this->options['oid_table_name']); 108 | 109 | $table->addColumn('id', 'integer', ['unsigned' => true, 'autoincrement' => true]); 110 | $table->addColumn('class_id', 'integer', ['unsigned' => true]); 111 | $table->addColumn('object_identifier', 'string', ['length' => 100]); 112 | $table->addColumn('parent_object_identity_id', 'integer', ['unsigned' => true, 'notnull' => false]); 113 | $table->addColumn('entries_inheriting', 'boolean'); 114 | 115 | $table->setPrimaryKey(['id']); 116 | $table->addUniqueIndex(['object_identifier', 'class_id']); 117 | $table->addIndex(['parent_object_identity_id']); 118 | 119 | $table->addForeignKeyConstraint($table, ['parent_object_identity_id'], ['id']); 120 | } 121 | 122 | /** 123 | * Adds the object identity relation table to the schema. 124 | */ 125 | protected function addObjectIdentityAncestorsTable() 126 | { 127 | $table = $this->createTable($this->options['oid_ancestors_table_name']); 128 | 129 | $table->addColumn('object_identity_id', 'integer', ['unsigned' => true]); 130 | $table->addColumn('ancestor_id', 'integer', ['unsigned' => true]); 131 | 132 | $table->setPrimaryKey(['object_identity_id', 'ancestor_id']); 133 | 134 | $oidTable = $this->getTable($this->options['oid_table_name']); 135 | $action = 'CASCADE'; 136 | if ($this->platform instanceof SQLServerPlatform) { 137 | // MS SQL Server does not support recursive cascading 138 | $action = 'NO ACTION'; 139 | } 140 | $table->addForeignKeyConstraint($oidTable, ['object_identity_id'], ['id'], ['onDelete' => $action, 'onUpdate' => $action]); 141 | $table->addForeignKeyConstraint($oidTable, ['ancestor_id'], ['id'], ['onDelete' => $action, 'onUpdate' => $action]); 142 | } 143 | 144 | /** 145 | * Adds the security identity table to the schema. 146 | */ 147 | protected function addSecurityIdentitiesTable() 148 | { 149 | $table = $this->createTable($this->options['sid_table_name']); 150 | 151 | $table->addColumn('id', 'integer', ['unsigned' => true, 'autoincrement' => true]); 152 | $table->addColumn('identifier', 'string', ['length' => 200]); 153 | $table->addColumn('username', 'boolean'); 154 | 155 | $table->setPrimaryKey(['id']); 156 | $table->addUniqueIndex(['identifier', 'username']); 157 | } 158 | 159 | private function createSchemaConfig(?Connection $connection): ?SchemaConfig 160 | { 161 | if (null === $connection) { 162 | return null; 163 | } 164 | 165 | $schemaManager = method_exists($connection, 'createSchemaManager') 166 | ? $connection->createSchemaManager() 167 | : $connection->getSchemaManager() 168 | ; 169 | 170 | return $schemaManager->createSchemaConfig(); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /Domain/AclCacheTrait.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Domain; 13 | 14 | use Symfony\Component\Security\Acl\Model\AclInterface; 15 | use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface; 16 | 17 | /** 18 | * @author Johannes M. Schmitt 19 | * 20 | * @internal 21 | */ 22 | trait AclCacheTrait 23 | { 24 | private $prefix; 25 | private $permissionGrantingStrategy; 26 | 27 | /** 28 | * Unserializes the ACL. 29 | */ 30 | private function unserializeAcl(string $serialized): ?AclInterface 31 | { 32 | $acl = unserialize($serialized); 33 | 34 | if (null !== $parentId = $acl->getParentAcl()) { 35 | $parentAcl = $this->getFromCacheById($parentId); 36 | 37 | if (null === $parentAcl) { 38 | return null; 39 | } 40 | 41 | $acl->setParentAcl($parentAcl); 42 | } 43 | 44 | $reflectionProperty = new \ReflectionProperty($acl, 'permissionGrantingStrategy'); 45 | $reflectionProperty->setAccessible(true); 46 | $reflectionProperty->setValue($acl, $this->permissionGrantingStrategy); 47 | $reflectionProperty->setAccessible(false); 48 | 49 | $aceAclProperty = new \ReflectionProperty(Entry::class, 'acl'); 50 | $aceAclProperty->setAccessible(true); 51 | 52 | foreach ($acl->getObjectAces() as $ace) { 53 | $aceAclProperty->setValue($ace, $acl); 54 | } 55 | foreach ($acl->getClassAces() as $ace) { 56 | $aceAclProperty->setValue($ace, $acl); 57 | } 58 | 59 | $aceClassFieldProperty = new \ReflectionProperty($acl, 'classFieldAces'); 60 | $aceClassFieldProperty->setAccessible(true); 61 | foreach ($aceClassFieldProperty->getValue($acl) as $aces) { 62 | foreach ($aces as $ace) { 63 | $aceAclProperty->setValue($ace, $acl); 64 | } 65 | } 66 | $aceClassFieldProperty->setAccessible(false); 67 | 68 | $aceObjectFieldProperty = new \ReflectionProperty($acl, 'objectFieldAces'); 69 | $aceObjectFieldProperty->setAccessible(true); 70 | foreach ($aceObjectFieldProperty->getValue($acl) as $aces) { 71 | foreach ($aces as $ace) { 72 | $aceAclProperty->setValue($ace, $acl); 73 | } 74 | } 75 | $aceObjectFieldProperty->setAccessible(false); 76 | 77 | $aceAclProperty->setAccessible(false); 78 | 79 | return $acl; 80 | } 81 | 82 | /** 83 | * Returns the key for the object identity. 84 | */ 85 | private function getDataKeyByIdentity(ObjectIdentityInterface $oid): string 86 | { 87 | return $this->prefix.md5($oid->getType()).sha1($oid->getType()) 88 | .'_'.md5($oid->getIdentifier()).sha1($oid->getIdentifier()); 89 | } 90 | 91 | /** 92 | * Returns the alias key for the object identity key. 93 | */ 94 | private function getAliasKeyForIdentity(string $aclId): string 95 | { 96 | return $this->prefix.$aclId; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Domain/AclCollectionCache.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Domain; 13 | 14 | use Symfony\Component\Security\Acl\Model\AclProviderInterface; 15 | use Symfony\Component\Security\Acl\Model\ObjectIdentityRetrievalStrategyInterface; 16 | use Symfony\Component\Security\Acl\Model\SecurityIdentityRetrievalStrategyInterface; 17 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 18 | 19 | /** 20 | * This service caches ACLs for an entire collection of objects. 21 | * 22 | * @author Johannes M. Schmitt 23 | */ 24 | class AclCollectionCache 25 | { 26 | private $aclProvider; 27 | private $objectIdentityRetrievalStrategy; 28 | private $securityIdentityRetrievalStrategy; 29 | 30 | /** 31 | * Constructor. 32 | */ 33 | public function __construct(AclProviderInterface $aclProvider, ObjectIdentityRetrievalStrategyInterface $oidRetrievalStrategy, SecurityIdentityRetrievalStrategyInterface $sidRetrievalStrategy) 34 | { 35 | $this->aclProvider = $aclProvider; 36 | $this->objectIdentityRetrievalStrategy = $oidRetrievalStrategy; 37 | $this->securityIdentityRetrievalStrategy = $sidRetrievalStrategy; 38 | } 39 | 40 | /** 41 | * Batch loads ACLs for an entire collection; thus, it reduces the number 42 | * of required queries considerably. 43 | * 44 | * @param mixed $collection anything that can be passed to foreach() 45 | * @param TokenInterface[] $tokens an array of TokenInterface implementations 46 | */ 47 | public function cache($collection, array $tokens = []) 48 | { 49 | $sids = []; 50 | foreach ($tokens as $token) { 51 | $sids = array_merge($sids, $this->securityIdentityRetrievalStrategy->getSecurityIdentities($token)); 52 | } 53 | 54 | $oids = []; 55 | foreach ($collection as $domainObject) { 56 | $oids[] = $this->objectIdentityRetrievalStrategy->getObjectIdentity($domainObject); 57 | } 58 | 59 | $this->aclProvider->findAcls($oids, $sids); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Domain/AuditLogger.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Domain; 13 | 14 | use Symfony\Component\Security\Acl\Model\AuditableEntryInterface; 15 | use Symfony\Component\Security\Acl\Model\AuditLoggerInterface; 16 | use Symfony\Component\Security\Acl\Model\EntryInterface; 17 | 18 | /** 19 | * Base audit logger implementation. 20 | * 21 | * @author Johannes M. Schmitt 22 | */ 23 | abstract class AuditLogger implements AuditLoggerInterface 24 | { 25 | /** 26 | * Performs some checks if logging was requested. 27 | * 28 | * @param bool $granted 29 | */ 30 | public function logIfNeeded($granted, EntryInterface $ace) 31 | { 32 | if (!$ace instanceof AuditableEntryInterface) { 33 | return; 34 | } 35 | 36 | if ($granted && $ace->isAuditSuccess()) { 37 | $this->doLog($granted, $ace); 38 | } elseif (!$granted && $ace->isAuditFailure()) { 39 | $this->doLog($granted, $ace); 40 | } 41 | } 42 | 43 | /** 44 | * This method is only called when logging is needed. 45 | * 46 | * @param bool $granted 47 | */ 48 | abstract protected function doLog($granted, EntryInterface $ace); 49 | } 50 | -------------------------------------------------------------------------------- /Domain/DoctrineAclCache.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Domain; 13 | 14 | use Doctrine\Common\Cache\Cache; 15 | use Doctrine\Common\Cache\CacheProvider; 16 | use Symfony\Component\Security\Acl\Model\AclCacheInterface; 17 | use Symfony\Component\Security\Acl\Model\AclInterface; 18 | use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface; 19 | use Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface; 20 | 21 | /** 22 | * This class is a wrapper around the actual cache implementation. 23 | * 24 | * @author Johannes M. Schmitt 25 | */ 26 | class DoctrineAclCache implements AclCacheInterface 27 | { 28 | use AclCacheTrait; 29 | 30 | public const PREFIX = 'sf2_acl_'; 31 | 32 | private $cache; 33 | 34 | /** 35 | * Constructor. 36 | * 37 | * @param string $prefix 38 | * 39 | * @throws \InvalidArgumentException 40 | */ 41 | public function __construct(Cache $cache, PermissionGrantingStrategyInterface $permissionGrantingStrategy, $prefix = self::PREFIX) 42 | { 43 | $prefix = (string) $prefix; 44 | if ('' === $prefix) { 45 | throw new \InvalidArgumentException('$prefix cannot be empty.'); 46 | } 47 | 48 | $this->cache = $cache; 49 | $this->permissionGrantingStrategy = $permissionGrantingStrategy; 50 | $this->prefix = $prefix; 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public function clearCache() 57 | { 58 | if ($this->cache instanceof CacheProvider) { 59 | $this->cache->deleteAll(); 60 | } 61 | } 62 | 63 | /** 64 | * {@inheritdoc} 65 | */ 66 | public function evictFromCacheById($aclId) 67 | { 68 | $lookupKey = $this->getAliasKeyForIdentity($aclId); 69 | if (!$this->cache->contains($lookupKey)) { 70 | return; 71 | } 72 | 73 | $key = $this->cache->fetch($lookupKey); 74 | if ($this->cache->contains($key)) { 75 | $this->cache->delete($key); 76 | } 77 | 78 | $this->cache->delete($lookupKey); 79 | } 80 | 81 | /** 82 | * {@inheritdoc} 83 | */ 84 | public function evictFromCacheByIdentity(ObjectIdentityInterface $oid) 85 | { 86 | $key = $this->getDataKeyByIdentity($oid); 87 | if (!$this->cache->contains($key)) { 88 | return; 89 | } 90 | 91 | $this->cache->delete($key); 92 | } 93 | 94 | /** 95 | * {@inheritdoc} 96 | */ 97 | public function getFromCacheById($aclId) 98 | { 99 | $lookupKey = $this->getAliasKeyForIdentity($aclId); 100 | if (!$this->cache->contains($lookupKey)) { 101 | return; 102 | } 103 | 104 | $key = $this->cache->fetch($lookupKey); 105 | if (!$this->cache->contains($key)) { 106 | $this->cache->delete($lookupKey); 107 | 108 | return; 109 | } 110 | 111 | return $this->unserializeAcl($this->cache->fetch($key)); 112 | } 113 | 114 | /** 115 | * {@inheritdoc} 116 | */ 117 | public function getFromCacheByIdentity(ObjectIdentityInterface $oid) 118 | { 119 | $key = $this->getDataKeyByIdentity($oid); 120 | if (!$this->cache->contains($key)) { 121 | return; 122 | } 123 | 124 | return $this->unserializeAcl($this->cache->fetch($key)); 125 | } 126 | 127 | /** 128 | * {@inheritdoc} 129 | */ 130 | public function putInCache(AclInterface $acl) 131 | { 132 | if (null === $acl->getId()) { 133 | throw new \InvalidArgumentException('Transient ACLs cannot be cached.'); 134 | } 135 | 136 | if (null !== $parentAcl = $acl->getParentAcl()) { 137 | $this->putInCache($parentAcl); 138 | } 139 | 140 | $key = $this->getDataKeyByIdentity($acl->getObjectIdentity()); 141 | $this->cache->save($key, serialize($acl)); 142 | $this->cache->save($this->getAliasKeyForIdentity($acl->getId()), $key); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /Domain/Entry.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Domain; 13 | 14 | use Symfony\Component\Security\Acl\Model\AclInterface; 15 | use Symfony\Component\Security\Acl\Model\AuditableEntryInterface; 16 | use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface; 17 | 18 | /** 19 | * Auditable ACE implementation. 20 | * 21 | * @author Johannes M. Schmitt 22 | */ 23 | class Entry implements AuditableEntryInterface 24 | { 25 | private $acl; 26 | private $mask; 27 | private $id; 28 | private $securityIdentity; 29 | private $strategy; 30 | private $auditFailure; 31 | private $auditSuccess; 32 | private $granting; 33 | 34 | public function __construct(?int $id, AclInterface $acl, SecurityIdentityInterface $sid, string $strategy, int $mask, bool $granting, bool $auditFailure, bool $auditSuccess) 35 | { 36 | $this->id = $id; 37 | $this->acl = $acl; 38 | $this->securityIdentity = $sid; 39 | $this->strategy = $strategy; 40 | $this->mask = $mask; 41 | $this->granting = $granting; 42 | $this->auditFailure = $auditFailure; 43 | $this->auditSuccess = $auditSuccess; 44 | } 45 | 46 | /** 47 | * {@inheritdoc} 48 | */ 49 | public function getAcl() 50 | { 51 | return $this->acl; 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function getMask() 58 | { 59 | return $this->mask; 60 | } 61 | 62 | /** 63 | * {@inheritdoc} 64 | */ 65 | public function getId() 66 | { 67 | return $this->id; 68 | } 69 | 70 | /** 71 | * {@inheritdoc} 72 | */ 73 | public function getSecurityIdentity() 74 | { 75 | return $this->securityIdentity; 76 | } 77 | 78 | /** 79 | * {@inheritdoc} 80 | */ 81 | public function getStrategy() 82 | { 83 | return $this->strategy; 84 | } 85 | 86 | /** 87 | * {@inheritdoc} 88 | */ 89 | public function isAuditFailure() 90 | { 91 | return $this->auditFailure; 92 | } 93 | 94 | /** 95 | * {@inheritdoc} 96 | */ 97 | public function isAuditSuccess() 98 | { 99 | return $this->auditSuccess; 100 | } 101 | 102 | /** 103 | * {@inheritdoc} 104 | */ 105 | public function isGranting() 106 | { 107 | return $this->granting; 108 | } 109 | 110 | /** 111 | * Turns on/off auditing on permissions denials. 112 | * 113 | * Do never call this method directly. Use the respective methods on the 114 | * AclInterface instead. 115 | * 116 | * @param bool $boolean 117 | */ 118 | public function setAuditFailure($boolean) 119 | { 120 | $this->auditFailure = $boolean; 121 | } 122 | 123 | /** 124 | * Turns on/off auditing on permission grants. 125 | * 126 | * Do never call this method directly. Use the respective methods on the 127 | * AclInterface instead. 128 | * 129 | * @param bool $boolean 130 | */ 131 | public function setAuditSuccess($boolean) 132 | { 133 | $this->auditSuccess = $boolean; 134 | } 135 | 136 | /** 137 | * Sets the permission mask. 138 | * 139 | * Do never call this method directly. Use the respective methods on the 140 | * AclInterface instead. 141 | * 142 | * @param int $mask 143 | */ 144 | public function setMask($mask) 145 | { 146 | $this->mask = $mask; 147 | } 148 | 149 | /** 150 | * Sets the mask comparison strategy. 151 | * 152 | * Do never call this method directly. Use the respective methods on the 153 | * AclInterface instead. 154 | * 155 | * @param string $strategy 156 | */ 157 | public function setStrategy($strategy) 158 | { 159 | $this->strategy = $strategy; 160 | } 161 | 162 | public function __serialize(): array 163 | { 164 | return [ 165 | $this->mask, 166 | $this->id, 167 | $this->securityIdentity, 168 | $this->strategy, 169 | $this->auditFailure, 170 | $this->auditSuccess, 171 | $this->granting, 172 | ]; 173 | } 174 | 175 | public function __unserialize(array $data): void 176 | { 177 | [$this->mask, 178 | $this->id, 179 | $this->securityIdentity, 180 | $this->strategy, 181 | $this->auditFailure, 182 | $this->auditSuccess, 183 | $this->granting 184 | ] = $data; 185 | } 186 | 187 | /** 188 | * @internal 189 | * 190 | * @final 191 | * 192 | * @return string 193 | */ 194 | public function serialize() 195 | { 196 | return serialize($this->__serialize()); 197 | } 198 | 199 | /** 200 | * @internal 201 | * 202 | * @final 203 | * 204 | * @param string $serialized 205 | */ 206 | public function unserialize($serialized) 207 | { 208 | $this->__unserialize(\is_array($serialized) ? $serialized : unserialize($serialized)); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /Domain/FieldEntry.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Domain; 13 | 14 | use Symfony\Component\Security\Acl\Model\AclInterface; 15 | use Symfony\Component\Security\Acl\Model\FieldEntryInterface; 16 | use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface; 17 | 18 | /** 19 | * Field-aware ACE implementation which is auditable. 20 | * 21 | * @author Johannes M. Schmitt 22 | */ 23 | class FieldEntry extends Entry implements FieldEntryInterface 24 | { 25 | private $field; 26 | 27 | public function __construct(?int $id, AclInterface $acl, string $field, SecurityIdentityInterface $sid, string $strategy, int $mask, bool $granting, bool $auditFailure, $auditSuccess) 28 | { 29 | parent::__construct($id, $acl, $sid, $strategy, $mask, $granting, $auditFailure, $auditSuccess); 30 | 31 | $this->field = $field; 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function getField() 38 | { 39 | return $this->field; 40 | } 41 | 42 | /** 43 | * {@inheritdoc} 44 | */ 45 | public function __serialize(): array 46 | { 47 | return [$this->field, parent::__serialize()]; 48 | } 49 | 50 | /** 51 | * {@inheritdoc} 52 | */ 53 | public function __unserialize(array $data): void 54 | { 55 | [$this->field, $parentData] = $data; 56 | $parentData = \is_array($parentData) ? $parentData : unserialize($parentData); 57 | parent::__unserialize($parentData); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Domain/ObjectIdentity.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Domain; 13 | 14 | use Symfony\Component\Security\Acl\Exception\InvalidDomainObjectException; 15 | use Symfony\Component\Security\Acl\Model\DomainObjectInterface; 16 | use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface; 17 | use Symfony\Component\Security\Acl\Util\ClassUtils; 18 | 19 | /** 20 | * ObjectIdentity implementation. 21 | * 22 | * @author Johannes M. Schmitt 23 | */ 24 | final class ObjectIdentity implements ObjectIdentityInterface 25 | { 26 | private $identifier; 27 | private $type; 28 | 29 | /** 30 | * Constructor. 31 | * 32 | * @param string $identifier 33 | * @param string $type 34 | * 35 | * @throws \InvalidArgumentException 36 | */ 37 | public function __construct($identifier, $type) 38 | { 39 | if ('' === $identifier) { 40 | throw new \InvalidArgumentException('$identifier cannot be empty.'); 41 | } 42 | if (empty($type)) { 43 | throw new \InvalidArgumentException('$type cannot be empty.'); 44 | } 45 | 46 | $this->identifier = $identifier; 47 | $this->type = $type; 48 | } 49 | 50 | /** 51 | * Constructs an ObjectIdentity for the given domain object. 52 | * 53 | * @param object $domainObject 54 | * 55 | * @return ObjectIdentity 56 | * 57 | * @throws InvalidDomainObjectException 58 | */ 59 | public static function fromDomainObject($domainObject) 60 | { 61 | if (!\is_object($domainObject)) { 62 | throw new InvalidDomainObjectException('$domainObject must be an object.'); 63 | } 64 | 65 | try { 66 | if ($domainObject instanceof DomainObjectInterface) { 67 | return new self($domainObject->getObjectIdentifier(), ClassUtils::getRealClass($domainObject)); 68 | } elseif (method_exists($domainObject, 'getId')) { 69 | return new self((string) $domainObject->getId(), ClassUtils::getRealClass($domainObject)); 70 | } 71 | } catch (\InvalidArgumentException $e) { 72 | throw new InvalidDomainObjectException($e->getMessage(), 0, $e); 73 | } 74 | 75 | throw new InvalidDomainObjectException('$domainObject must either implement the DomainObjectInterface, or have a method named "getId".'); 76 | } 77 | 78 | /** 79 | * {@inheritdoc} 80 | */ 81 | public function getIdentifier() 82 | { 83 | return $this->identifier; 84 | } 85 | 86 | /** 87 | * {@inheritdoc} 88 | */ 89 | public function getType() 90 | { 91 | return $this->type; 92 | } 93 | 94 | /** 95 | * {@inheritdoc} 96 | */ 97 | public function equals(ObjectIdentityInterface $identity) 98 | { 99 | // comparing the identifier with === might lead to problems, so we 100 | // waive this restriction 101 | return $this->identifier == $identity->getIdentifier() 102 | && $this->type === $identity->getType(); 103 | } 104 | 105 | /** 106 | * Returns a textual representation of this object identity. 107 | * 108 | * @return string 109 | */ 110 | public function __toString() 111 | { 112 | return sprintf('ObjectIdentity(%s, %s)', $this->identifier, $this->type); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Domain/ObjectIdentityRetrievalStrategy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Domain; 13 | 14 | use Symfony\Component\Security\Acl\Exception\InvalidDomainObjectException; 15 | use Symfony\Component\Security\Acl\Model\ObjectIdentityRetrievalStrategyInterface; 16 | 17 | /** 18 | * Strategy to be used for retrieving object identities from domain objects. 19 | * 20 | * @author Johannes M. Schmitt 21 | */ 22 | class ObjectIdentityRetrievalStrategy implements ObjectIdentityRetrievalStrategyInterface 23 | { 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | public function getObjectIdentity($domainObject) 28 | { 29 | try { 30 | return ObjectIdentity::fromDomainObject($domainObject); 31 | } catch (InvalidDomainObjectException $e) { 32 | return; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Domain/PermissionGrantingStrategy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Domain; 13 | 14 | use Symfony\Component\Security\Acl\Exception\NoAceFoundException; 15 | use Symfony\Component\Security\Acl\Model\AclInterface; 16 | use Symfony\Component\Security\Acl\Model\AuditLoggerInterface; 17 | use Symfony\Component\Security\Acl\Model\EntryInterface; 18 | use Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface; 19 | use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface; 20 | 21 | /** 22 | * The permission granting strategy to apply to the access control list. 23 | * 24 | * @author Johannes M. Schmitt 25 | */ 26 | class PermissionGrantingStrategy implements PermissionGrantingStrategyInterface 27 | { 28 | public const EQUAL = 'equal'; 29 | public const ALL = 'all'; 30 | public const ANY = 'any'; 31 | 32 | private $auditLogger; 33 | 34 | /** 35 | * Sets the audit logger. 36 | */ 37 | public function setAuditLogger(AuditLoggerInterface $auditLogger) 38 | { 39 | $this->auditLogger = $auditLogger; 40 | } 41 | 42 | /** 43 | * {@inheritdoc} 44 | */ 45 | public function isGranted(AclInterface $acl, array $masks, array $sids, $administrativeMode = false) 46 | { 47 | try { 48 | try { 49 | $aces = $acl->getObjectAces(); 50 | 51 | if (!$aces) { 52 | throw new NoAceFoundException(); 53 | } 54 | 55 | return $this->hasSufficientPermissions($acl, $aces, $masks, $sids, $administrativeMode); 56 | } catch (NoAceFoundException $e) { 57 | $aces = $acl->getClassAces(); 58 | 59 | if (!$aces) { 60 | throw $e; 61 | } 62 | 63 | return $this->hasSufficientPermissions($acl, $aces, $masks, $sids, $administrativeMode); 64 | } 65 | } catch (NoAceFoundException $e) { 66 | if ($acl->isEntriesInheriting() && null !== $parentAcl = $acl->getParentAcl()) { 67 | return $parentAcl->isGranted($masks, $sids, $administrativeMode); 68 | } 69 | 70 | throw $e; 71 | } 72 | } 73 | 74 | /** 75 | * {@inheritdoc} 76 | */ 77 | public function isFieldGranted(AclInterface $acl, $field, array $masks, array $sids, $administrativeMode = false) 78 | { 79 | try { 80 | try { 81 | $aces = $acl->getObjectFieldAces($field); 82 | if (!$aces) { 83 | throw new NoAceFoundException(); 84 | } 85 | 86 | return $this->hasSufficientPermissions($acl, $aces, $masks, $sids, $administrativeMode); 87 | } catch (NoAceFoundException $e) { 88 | $aces = $acl->getClassFieldAces($field); 89 | if (!$aces) { 90 | throw $e; 91 | } 92 | 93 | return $this->hasSufficientPermissions($acl, $aces, $masks, $sids, $administrativeMode); 94 | } 95 | } catch (NoAceFoundException $e) { 96 | if ($acl->isEntriesInheriting() && null !== $parentAcl = $acl->getParentAcl()) { 97 | return $parentAcl->isFieldGranted($field, $masks, $sids, $administrativeMode); 98 | } 99 | 100 | throw $e; 101 | } 102 | } 103 | 104 | /** 105 | * Makes an authorization decision. 106 | * 107 | * The order of ACEs, and SIDs is significant; the order of permission masks 108 | * not so much. It is important to note that the more specific security 109 | * identities should be at the beginning of the SIDs array in order for this 110 | * strategy to produce intuitive authorization decisions. 111 | * 112 | * First, we will iterate over permissions, then over security identities. 113 | * For each combination of permission, and identity we will test the 114 | * available ACEs until we find one which is applicable. 115 | * 116 | * The first applicable ACE will make the ultimate decision for the 117 | * permission/identity combination. If it is granting, this method will return 118 | * true, if it is denying, the method will continue to check the next 119 | * permission/identity combination. 120 | * 121 | * This process is repeated until either a granting ACE is found, or no 122 | * permission/identity combinations are left. Finally, we will either throw 123 | * an NoAceFoundException, or deny access. 124 | * 125 | * @param EntryInterface[] $aces An array of ACE to check against 126 | * @param array $masks An array of permission masks 127 | * @param SecurityIdentityInterface[] $sids An array of SecurityIdentityInterface implementations 128 | * @param bool $administrativeMode True turns off audit logging 129 | * 130 | * @return bool true, or false; either granting, or denying access respectively 131 | * 132 | * @throws NoAceFoundException 133 | */ 134 | private function hasSufficientPermissions(AclInterface $acl, array $aces, array $masks, array $sids, $administrativeMode) 135 | { 136 | $firstRejectedAce = null; 137 | 138 | foreach ($masks as $requiredMask) { 139 | foreach ($sids as $sid) { 140 | foreach ($aces as $ace) { 141 | if ($sid->equals($ace->getSecurityIdentity()) && $this->isAceApplicable($requiredMask, $ace)) { 142 | if ($ace->isGranting()) { 143 | if (!$administrativeMode && null !== $this->auditLogger) { 144 | $this->auditLogger->logIfNeeded(true, $ace); 145 | } 146 | 147 | return true; 148 | } 149 | 150 | if (null === $firstRejectedAce) { 151 | $firstRejectedAce = $ace; 152 | } 153 | 154 | break 2; 155 | } 156 | } 157 | } 158 | } 159 | 160 | if (null !== $firstRejectedAce) { 161 | if (!$administrativeMode && null !== $this->auditLogger) { 162 | $this->auditLogger->logIfNeeded(false, $firstRejectedAce); 163 | } 164 | 165 | return false; 166 | } 167 | 168 | throw new NoAceFoundException(); 169 | } 170 | 171 | /** 172 | * Determines whether the ACE is applicable to the given permission/security 173 | * identity combination. 174 | * 175 | * Per default, we support three different comparison strategies. 176 | * 177 | * Strategy ALL: 178 | * The ACE will be considered applicable when all the turned-on bits in the 179 | * required mask are also turned-on in the ACE mask. 180 | * 181 | * Strategy ANY: 182 | * The ACE will be considered applicable when any of the turned-on bits in 183 | * the required mask is also turned-on the in the ACE mask. 184 | * 185 | * Strategy EQUAL: 186 | * The ACE will be considered applicable when the bitmasks are equal. 187 | * 188 | * @param int $requiredMask 189 | * 190 | * @return bool 191 | * 192 | * @throws \RuntimeException if the ACE strategy is not supported 193 | */ 194 | private function isAceApplicable($requiredMask, EntryInterface $ace) 195 | { 196 | $strategy = $ace->getStrategy(); 197 | if (self::ALL === $strategy) { 198 | return $requiredMask === ($ace->getMask() & $requiredMask); 199 | } elseif (self::ANY === $strategy) { 200 | return 0 !== ($ace->getMask() & $requiredMask); 201 | } elseif (self::EQUAL === $strategy) { 202 | return $requiredMask === $ace->getMask(); 203 | } 204 | 205 | throw new \RuntimeException(sprintf('The strategy "%s" is not supported.', $strategy)); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /Domain/PsrAclCache.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Domain; 13 | 14 | use Psr\Cache\CacheItemPoolInterface; 15 | use Symfony\Component\Security\Acl\Model\AclCacheInterface; 16 | use Symfony\Component\Security\Acl\Model\AclInterface; 17 | use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface; 18 | use Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface; 19 | 20 | /** 21 | * This class is a wrapper around a PSR-6 cache implementation. 22 | * 23 | * @author Michael Babker 24 | */ 25 | class PsrAclCache implements AclCacheInterface 26 | { 27 | use AclCacheTrait; 28 | 29 | public const PREFIX = 'sf_acl_'; 30 | 31 | private $cache; 32 | 33 | /** 34 | * @throws \InvalidArgumentException When $prefix is empty 35 | */ 36 | public function __construct(CacheItemPoolInterface $cache, PermissionGrantingStrategyInterface $permissionGrantingStrategy, string $prefix = self::PREFIX) 37 | { 38 | if ('' === $prefix) { 39 | throw new \InvalidArgumentException('$prefix cannot be empty.'); 40 | } 41 | 42 | $this->cache = $cache; 43 | $this->permissionGrantingStrategy = $permissionGrantingStrategy; 44 | $this->prefix = $prefix; 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | public function clearCache(): void 51 | { 52 | $this->cache->clear(); 53 | } 54 | 55 | /** 56 | * {@inheritdoc} 57 | */ 58 | public function evictFromCacheById($aclId): void 59 | { 60 | $lookupKey = $this->getAliasKeyForIdentity($aclId); 61 | $cacheItem = $this->cache->getItem($lookupKey); 62 | if (!$cacheItem->isHit()) { 63 | return; 64 | } 65 | 66 | $this->cache->deleteItems([$cacheItem->get(), $lookupKey]); 67 | } 68 | 69 | /** 70 | * {@inheritdoc} 71 | */ 72 | public function evictFromCacheByIdentity(ObjectIdentityInterface $oid): void 73 | { 74 | $this->cache->deleteItem($this->getDataKeyByIdentity($oid)); 75 | } 76 | 77 | /** 78 | * {@inheritdoc} 79 | */ 80 | public function getFromCacheById($aclId): ?AclInterface 81 | { 82 | $lookupKey = $this->getAliasKeyForIdentity($aclId); 83 | $lookupKeyItem = $this->cache->getItem($lookupKey); 84 | if (!$lookupKeyItem->isHit()) { 85 | return null; 86 | } 87 | 88 | $key = $lookupKeyItem->get(); 89 | $keyItem = $this->cache->getItem($key); 90 | if (!$keyItem->isHit()) { 91 | $this->cache->deleteItem($lookupKey); 92 | 93 | return null; 94 | } 95 | 96 | return $this->unserializeAcl($keyItem->get()); 97 | } 98 | 99 | /** 100 | * {@inheritdoc} 101 | */ 102 | public function getFromCacheByIdentity(ObjectIdentityInterface $oid): ?AclInterface 103 | { 104 | $key = $this->getDataKeyByIdentity($oid); 105 | $cacheItem = $this->cache->getItem($key); 106 | if (!$cacheItem->isHit()) { 107 | return null; 108 | } 109 | 110 | return $this->unserializeAcl($cacheItem->get()); 111 | } 112 | 113 | /** 114 | * {@inheritdoc} 115 | */ 116 | public function putInCache(AclInterface $acl): void 117 | { 118 | if (null === $acl->getId()) { 119 | throw new \InvalidArgumentException('Transient ACLs cannot be cached.'); 120 | } 121 | 122 | if (null !== $parentAcl = $acl->getParentAcl()) { 123 | $this->putInCache($parentAcl); 124 | } 125 | 126 | $key = $this->getDataKeyByIdentity($acl->getObjectIdentity()); 127 | $objectIdentityItem = $this->cache->getItem($key); 128 | $objectIdentityItem->set(serialize($acl)); 129 | 130 | $this->cache->saveDeferred($objectIdentityItem); 131 | 132 | $aliasKey = $this->getAliasKeyForIdentity($acl->getId()); 133 | $aliasItem = $this->cache->getItem($aliasKey); 134 | $aliasItem->set($key); 135 | 136 | $this->cache->saveDeferred($aliasItem); 137 | 138 | $this->cache->commit(); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Domain/RoleSecurityIdentity.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Domain; 13 | 14 | use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface; 15 | 16 | /** 17 | * A SecurityIdentity implementation for roles. 18 | * 19 | * @author Johannes M. Schmitt 20 | */ 21 | final class RoleSecurityIdentity implements SecurityIdentityInterface 22 | { 23 | private $role; 24 | 25 | public function __construct(string $role) 26 | { 27 | $this->role = $role; 28 | } 29 | 30 | /** 31 | * Returns the role name. 32 | * 33 | * @return string 34 | */ 35 | public function getRole() 36 | { 37 | return $this->role; 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public function equals(SecurityIdentityInterface $sid) 44 | { 45 | if (!$sid instanceof self) { 46 | return false; 47 | } 48 | 49 | return $this->role === $sid->getRole(); 50 | } 51 | 52 | /** 53 | * Returns a textual representation of this security identity. 54 | * 55 | * This is solely used for debugging purposes, not to make an equality decision. 56 | * 57 | * @return string 58 | */ 59 | public function __toString() 60 | { 61 | return sprintf('RoleSecurityIdentity(%s)', $this->role); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Domain/SecurityIdentityRetrievalStrategy.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Domain; 13 | 14 | use Symfony\Component\Security\Acl\Model\SecurityIdentityRetrievalStrategyInterface; 15 | use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; 16 | use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; 17 | use Symfony\Component\Security\Core\Authentication\Token\NullToken; 18 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 19 | use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter; 20 | use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; 21 | 22 | /** 23 | * Strategy for retrieving security identities. 24 | * 25 | * @author Johannes M. Schmitt 26 | */ 27 | class SecurityIdentityRetrievalStrategy implements SecurityIdentityRetrievalStrategyInterface 28 | { 29 | private $roleHierarchy; 30 | private $authenticationTrustResolver; 31 | 32 | /** 33 | * Constructor. 34 | */ 35 | public function __construct(RoleHierarchyInterface $roleHierarchy, AuthenticationTrustResolverInterface $authenticationTrustResolver) 36 | { 37 | $this->roleHierarchy = $roleHierarchy; 38 | $this->authenticationTrustResolver = $authenticationTrustResolver; 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | * 44 | * @return RoleSecurityIdentity[] 45 | */ 46 | public function getSecurityIdentities(TokenInterface $token) 47 | { 48 | $sids = []; 49 | 50 | // add user security identity 51 | if (!$token instanceof AnonymousToken && !$token instanceof NullToken) { 52 | try { 53 | $sids[] = UserSecurityIdentity::fromToken($token); 54 | } catch (\InvalidArgumentException $e) { 55 | // ignore, user has no user security identity 56 | } 57 | } 58 | 59 | // add all reachable roles 60 | foreach ($this->roleHierarchy->getReachableRoleNames($token->getRoleNames()) as $role) { 61 | $sids[] = new RoleSecurityIdentity($role); 62 | } 63 | 64 | // add built-in special roles 65 | if ($this->authenticationTrustResolver->isFullFledged($token)) { 66 | $sids[] = new RoleSecurityIdentity(AuthenticatedVoter::IS_AUTHENTICATED_FULLY); 67 | $sids[] = new RoleSecurityIdentity(AuthenticatedVoter::IS_AUTHENTICATED_REMEMBERED); 68 | $this->addAnonymousRoles($sids); 69 | } elseif ($this->authenticationTrustResolver->isRememberMe($token)) { 70 | $sids[] = new RoleSecurityIdentity(AuthenticatedVoter::IS_AUTHENTICATED_REMEMBERED); 71 | $this->addAnonymousRoles($sids); 72 | } elseif ($this->isNotAuthenticated($token)) { 73 | $this->addAnonymousRoles($sids); 74 | } 75 | 76 | return $sids; 77 | } 78 | 79 | private function isNotAuthenticated(TokenInterface $token): bool 80 | { 81 | if (method_exists($this->authenticationTrustResolver, 'isAuthenticated')) { 82 | return !$this->authenticationTrustResolver->isAuthenticated($token); 83 | } 84 | 85 | return $this->authenticationTrustResolver->isAnonymous($token); 86 | } 87 | 88 | private function addAnonymousRoles(array &$sids) 89 | { 90 | $sids[] = new RoleSecurityIdentity('IS_AUTHENTICATED_ANONYMOUSLY'); 91 | if (\defined('\Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter::PUBLIC_ACCESS')) { 92 | $sids[] = new RoleSecurityIdentity(AuthenticatedVoter::PUBLIC_ACCESS); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Domain/UserSecurityIdentity.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Domain; 13 | 14 | use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface; 15 | use Symfony\Component\Security\Acl\Util\ClassUtils; 16 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 17 | use Symfony\Component\Security\Core\User\UserInterface; 18 | 19 | /** 20 | * A SecurityIdentity implementation used for actual users. 21 | * 22 | * @author Johannes M. Schmitt 23 | */ 24 | final class UserSecurityIdentity implements SecurityIdentityInterface 25 | { 26 | private $username; 27 | private $class; 28 | 29 | /** 30 | * Constructor. 31 | * 32 | * @param string $username the username representation 33 | * @param string $class the user's fully qualified class name 34 | * 35 | * @throws \InvalidArgumentException 36 | */ 37 | public function __construct($username, $class) 38 | { 39 | if ('' === $username || null === $username) { 40 | throw new \InvalidArgumentException('$username must not be empty.'); 41 | } 42 | if (empty($class)) { 43 | throw new \InvalidArgumentException('$class must not be empty.'); 44 | } 45 | 46 | $this->username = (string) $username; 47 | $this->class = $class; 48 | } 49 | 50 | /** 51 | * Creates a user security identity from a UserInterface. 52 | * 53 | * @return UserSecurityIdentity 54 | */ 55 | public static function fromAccount(UserInterface $user) 56 | { 57 | return new self(method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(), ClassUtils::getRealClass($user)); 58 | } 59 | 60 | /** 61 | * Creates a user security identity from a TokenInterface. 62 | * 63 | * @return UserSecurityIdentity 64 | */ 65 | public static function fromToken(TokenInterface $token) 66 | { 67 | $user = $token->getUser(); 68 | 69 | if ($user instanceof UserInterface) { 70 | return self::fromAccount($user); 71 | } 72 | 73 | return new self((string) $user, \is_object($user) ? ClassUtils::getRealClass($user) : ClassUtils::getRealClass($token)); 74 | } 75 | 76 | /** 77 | * Returns the username. 78 | * 79 | * @return string 80 | */ 81 | public function getUsername() 82 | { 83 | return $this->username; 84 | } 85 | 86 | /** 87 | * Returns the user's class name. 88 | * 89 | * @return string 90 | */ 91 | public function getClass() 92 | { 93 | return $this->class; 94 | } 95 | 96 | /** 97 | * {@inheritdoc} 98 | */ 99 | public function equals(SecurityIdentityInterface $sid) 100 | { 101 | if (!$sid instanceof self) { 102 | return false; 103 | } 104 | 105 | return $this->username === $sid->getUsername() 106 | && $this->class === $sid->getClass(); 107 | } 108 | 109 | /** 110 | * A textual representation of this security identity. 111 | * 112 | * This is not used for equality comparison, but only for debugging. 113 | * 114 | * @return string 115 | */ 116 | public function __toString() 117 | { 118 | return sprintf('UserSecurityIdentity(%s, %s)', $this->username, $this->class); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Exception/AclAlreadyExistsException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Exception; 13 | 14 | /** 15 | * This exception is thrown when someone tries to create an ACL for an object 16 | * identity that already has one. 17 | * 18 | * @author Johannes M. Schmitt 19 | */ 20 | class AclAlreadyExistsException extends Exception 21 | { 22 | } 23 | -------------------------------------------------------------------------------- /Exception/AclNotFoundException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Exception; 13 | 14 | /** 15 | * This exception is thrown when we cannot locate an ACL for a passed 16 | * ObjectIdentity implementation. 17 | * 18 | * @author Johannes M. Schmitt 19 | */ 20 | class AclNotFoundException extends Exception 21 | { 22 | } 23 | -------------------------------------------------------------------------------- /Exception/ConcurrentModificationException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Exception; 13 | 14 | /** 15 | * This exception is thrown whenever you change shared properties of more than 16 | * one ACL of the same class type concurrently. 17 | * 18 | * @author Johannes M. Schmitt 19 | */ 20 | class ConcurrentModificationException extends Exception 21 | { 22 | } 23 | -------------------------------------------------------------------------------- /Exception/Exception.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Exception; 13 | 14 | /** 15 | * Base ACL exception. 16 | * 17 | * @author Johannes M. Schmitt 18 | */ 19 | class Exception extends \RuntimeException 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /Exception/InvalidDomainObjectException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Exception; 13 | 14 | /** 15 | * This exception is thrown when ObjectIdentity fails to construct an object 16 | * identity from the passed domain object. 17 | * 18 | * @author Johannes M. Schmitt 19 | */ 20 | class InvalidDomainObjectException extends Exception 21 | { 22 | } 23 | -------------------------------------------------------------------------------- /Exception/NoAceFoundException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Exception; 13 | 14 | /** 15 | * This exception is thrown when we cannot locate an ACE that matches the 16 | * combination of permission masks and security identities. 17 | * 18 | * @author Johannes M. Schmitt 19 | */ 20 | class NoAceFoundException extends Exception 21 | { 22 | public function __construct() 23 | { 24 | parent::__construct('No applicable ACE was found.'); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Exception/NotAllAclsFoundException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Exception; 13 | 14 | use Symfony\Component\Security\Acl\Model\AclInterface; 15 | use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface; 16 | 17 | /** 18 | * This exception is thrown when you have requested ACLs for multiple object 19 | * identities, but the AclProvider implementation failed to find ACLs for all 20 | * identities. 21 | * 22 | * This exception contains the partial result. 23 | * 24 | * @author Johannes M. Schmitt 25 | */ 26 | class NotAllAclsFoundException extends AclNotFoundException 27 | { 28 | private $partialResult; 29 | 30 | /** 31 | * @param \SplObjectStorage $result 32 | */ 33 | public function setPartialResult(\SplObjectStorage $result) 34 | { 35 | $this->partialResult = $result; 36 | } 37 | 38 | /** 39 | * Returns the partial result. 40 | * 41 | * @return \SplObjectStorage 42 | */ 43 | public function getPartialResult() 44 | { 45 | return $this->partialResult; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Exception/SidNotLoadedException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Exception; 13 | 14 | /** 15 | * This exception is thrown when ACEs for an SID are requested which has not 16 | * been loaded from the database. 17 | * 18 | * @author Johannes M. Schmitt 19 | */ 20 | class SidNotLoadedException extends Exception 21 | { 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2004-2021 Fabien Potencier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Model/AclCacheInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Model; 13 | 14 | /** 15 | * AclCache Interface. 16 | * 17 | * @author Johannes M. Schmitt 18 | */ 19 | interface AclCacheInterface 20 | { 21 | /** 22 | * Removes an ACL from the cache. 23 | * 24 | * @param string $aclId a serialized primary key 25 | */ 26 | public function evictFromCacheById($aclId); 27 | 28 | /** 29 | * Removes an ACL from the cache. 30 | * 31 | * The ACL which is returned, must reference the passed object identity. 32 | */ 33 | public function evictFromCacheByIdentity(ObjectIdentityInterface $oid); 34 | 35 | /** 36 | * Retrieves an ACL for the given object identity primary key from the cache. 37 | * 38 | * @param int $aclId 39 | * 40 | * @return AclInterface|null 41 | */ 42 | public function getFromCacheById($aclId); 43 | 44 | /** 45 | * Retrieves an ACL for the given object identity from the cache. 46 | * 47 | * @return AclInterface|null 48 | */ 49 | public function getFromCacheByIdentity(ObjectIdentityInterface $oid); 50 | 51 | /** 52 | * Stores a new ACL in the cache. 53 | */ 54 | public function putInCache(AclInterface $acl); 55 | 56 | /** 57 | * Removes all ACLs from the cache. 58 | */ 59 | public function clearCache(); 60 | } 61 | -------------------------------------------------------------------------------- /Model/AclInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Model; 13 | 14 | use Symfony\Component\Security\Acl\Exception\NoAceFoundException; 15 | 16 | /** 17 | * This interface represents an access control list (ACL) for a domain object. 18 | * Each domain object can have exactly one associated ACL. 19 | * 20 | * An ACL contains all access control entries (ACE) for a given domain object. 21 | * In order to avoid needing references to the domain object itself, implementations 22 | * use ObjectIdentity implementations as an additional level of indirection. 23 | * 24 | * @author Johannes M. Schmitt 25 | * 26 | * @method array __serialize() returns all the necessary state of the object for serialization purposes 27 | * @method void __unserialize(array $data) restores the object state from an array given by {@see __serialize} 28 | */ 29 | interface AclInterface extends \Serializable 30 | { 31 | /** 32 | * Returns all class-based ACEs associated with this ACL. 33 | * 34 | * @return array 35 | */ 36 | public function getClassAces(); 37 | 38 | /** 39 | * Returns all class-field-based ACEs associated with this ACL. 40 | * 41 | * @return array 42 | */ 43 | public function getClassFieldAces(string $field); 44 | 45 | /** 46 | * Returns all object-based ACEs associated with this ACL. 47 | * 48 | * @return array 49 | */ 50 | public function getObjectAces(); 51 | 52 | /** 53 | * Returns all object-field-based ACEs associated with this ACL. 54 | * 55 | * @return array 56 | */ 57 | public function getObjectFieldAces(string $field); 58 | 59 | /** 60 | * Returns the object identity associated with this ACL. 61 | * 62 | * @return ObjectIdentityInterface 63 | */ 64 | public function getObjectIdentity(); 65 | 66 | /** 67 | * Returns the parent ACL, or null if there is none. 68 | * 69 | * @return AclInterface|null 70 | */ 71 | public function getParentAcl(); 72 | 73 | /** 74 | * Whether this ACL is inheriting ACEs from a parent ACL. 75 | * 76 | * @return bool 77 | */ 78 | public function isEntriesInheriting(); 79 | 80 | /** 81 | * Determines whether field access is granted. 82 | * 83 | * @return bool 84 | */ 85 | public function isFieldGranted(string $field, array $masks, array $securityIdentities, bool $administrativeMode = false); 86 | 87 | /** 88 | * Determines whether access is granted. 89 | * 90 | * @return bool 91 | * 92 | * @throws NoAceFoundException when no ACE was applicable for this request 93 | */ 94 | public function isGranted(array $masks, array $securityIdentities, bool $administrativeMode = false); 95 | 96 | /** 97 | * Whether the ACL has loaded ACEs for all of the passed security identities. 98 | * 99 | * @param SecurityIdentityInterface|SecurityIdentityInterface[] $securityIdentities 100 | * 101 | * @return bool 102 | */ 103 | public function isSidLoaded($securityIdentities); 104 | } 105 | -------------------------------------------------------------------------------- /Model/AclProviderInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Model; 13 | 14 | use Symfony\Component\Security\Acl\Exception\AclNotFoundException; 15 | 16 | /** 17 | * Provides a common interface for retrieving ACLs. 18 | * 19 | * @author Johannes M. Schmitt 20 | */ 21 | interface AclProviderInterface 22 | { 23 | /** 24 | * Retrieves all child object identities from the database. 25 | * 26 | * @param bool $directChildrenOnly 27 | * 28 | * @return array returns an array of child 'ObjectIdentity's 29 | */ 30 | public function findChildren(ObjectIdentityInterface $parentOid, $directChildrenOnly = false); 31 | 32 | /** 33 | * Returns the ACL that belongs to the given object identity. 34 | * 35 | * @param SecurityIdentityInterface[] $sids 36 | * 37 | * @return AclInterface 38 | * 39 | * @throws AclNotFoundException when there is no ACL 40 | */ 41 | public function findAcl(ObjectIdentityInterface $oid, array $sids = []); 42 | 43 | /** 44 | * Returns the ACLs that belong to the given object identities. 45 | * 46 | * @param ObjectIdentityInterface[] $oids an array of ObjectIdentityInterface implementations 47 | * @param SecurityIdentityInterface[] $sids an array of SecurityIdentityInterface implementations 48 | * 49 | * @return \SplObjectStorage mapping the passed object identities to ACLs 50 | * 51 | * @throws AclNotFoundException when we cannot find an ACL for all identities 52 | */ 53 | public function findAcls(array $oids, array $sids = []); 54 | } 55 | -------------------------------------------------------------------------------- /Model/AuditLoggerInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Model; 13 | 14 | /** 15 | * Interface for audit loggers. 16 | * 17 | * @author Johannes M. Schmitt 18 | */ 19 | interface AuditLoggerInterface 20 | { 21 | /** 22 | * This method is called whenever access is granted, or denied, and 23 | * administrative mode is turned off. 24 | * 25 | * @param bool $granted 26 | */ 27 | public function logIfNeeded($granted, EntryInterface $ace); 28 | } 29 | -------------------------------------------------------------------------------- /Model/AuditableAclInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Model; 13 | 14 | /** 15 | * This interface adds auditing capabilities to the ACL. 16 | * 17 | * @author Johannes M. Schmitt 18 | */ 19 | interface AuditableAclInterface extends MutableAclInterface 20 | { 21 | /** 22 | * Updates auditing for class-based ACE. 23 | */ 24 | public function updateClassAuditing(int $index, bool $auditSuccess, bool $auditFailure); 25 | 26 | /** 27 | * Updates auditing for class-field-based ACE. 28 | */ 29 | public function updateClassFieldAuditing(int $index, string $field, bool $auditSuccess, bool $auditFailure); 30 | 31 | /** 32 | * Updates auditing for object-based ACE. 33 | */ 34 | public function updateObjectAuditing(int $index, bool $auditSuccess, bool $auditFailure); 35 | 36 | /** 37 | * Updates auditing for object-field-based ACE. 38 | */ 39 | public function updateObjectFieldAuditing(int $index, string $field, bool $auditSuccess, bool $auditFailure); 40 | } 41 | -------------------------------------------------------------------------------- /Model/AuditableEntryInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Model; 13 | 14 | /** 15 | * ACEs can implement this interface if they support auditing capabilities. 16 | * 17 | * @author Johannes M. Schmitt 18 | */ 19 | interface AuditableEntryInterface extends EntryInterface 20 | { 21 | /** 22 | * Whether auditing for successful grants is turned on. 23 | * 24 | * @return bool 25 | */ 26 | public function isAuditFailure(); 27 | 28 | /** 29 | * Whether auditing for successful denies is turned on. 30 | * 31 | * @return bool 32 | */ 33 | public function isAuditSuccess(); 34 | } 35 | -------------------------------------------------------------------------------- /Model/DomainObjectInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Model; 13 | 14 | /** 15 | * This method can be implemented by domain objects which you want to store 16 | * ACLs for if they do not have a getId() method, or getId() does not return 17 | * a unique identifier. 18 | * 19 | * @author Johannes M. Schmitt 20 | */ 21 | interface DomainObjectInterface 22 | { 23 | /** 24 | * Returns a unique identifier for this domain object. 25 | * 26 | * @return string 27 | */ 28 | public function getObjectIdentifier(); 29 | } 30 | -------------------------------------------------------------------------------- /Model/EntryInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Model; 13 | 14 | /** 15 | * This class represents an individual entry in the ACL list. 16 | * 17 | * Instances MUST be immutable, as they are returned by the ACL and should not 18 | * allow client modification. 19 | * 20 | * @author Johannes M. Schmitt 21 | * 22 | * @method array __serialize() returns all the necessary state of the object for serialization purposes 23 | * @method void __unserialize(array $data) restores the object state from an array given by {@see __serialize} 24 | */ 25 | interface EntryInterface extends \Serializable 26 | { 27 | /** 28 | * The ACL this ACE is associated with. 29 | * 30 | * @return AclInterface 31 | */ 32 | public function getAcl(); 33 | 34 | /** 35 | * The primary key of this ACE. 36 | * 37 | * @return int|null 38 | */ 39 | public function getId(); 40 | 41 | /** 42 | * The permission mask of this ACE. 43 | * 44 | * @return int 45 | */ 46 | public function getMask(); 47 | 48 | /** 49 | * The security identity associated with this ACE. 50 | * 51 | * @return SecurityIdentityInterface 52 | */ 53 | public function getSecurityIdentity(); 54 | 55 | /** 56 | * The strategy for comparing masks. 57 | * 58 | * @return string 59 | */ 60 | public function getStrategy(); 61 | 62 | /** 63 | * Returns whether this ACE is granting, or denying. 64 | * 65 | * @return bool 66 | */ 67 | public function isGranting(); 68 | } 69 | -------------------------------------------------------------------------------- /Model/FieldEntryInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Model; 13 | 14 | /** 15 | * Interface for entries which are restricted to specific fields. 16 | * 17 | * @author Johannes M. Schmitt 18 | */ 19 | interface FieldEntryInterface extends EntryInterface 20 | { 21 | /** 22 | * Returns the field used for this entry. 23 | * 24 | * @return string 25 | */ 26 | public function getField(); 27 | } 28 | -------------------------------------------------------------------------------- /Model/MutableAclInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Model; 13 | 14 | /** 15 | * This interface adds mutators for the AclInterface. 16 | * 17 | * All changes to Access Control Entries must go through this interface. Access 18 | * Control Entries must never be modified directly. 19 | * 20 | * @author Johannes M. Schmitt 21 | */ 22 | interface MutableAclInterface extends AclInterface 23 | { 24 | /** 25 | * Deletes a class-based ACE. 26 | */ 27 | public function deleteClassAce(int $index); 28 | 29 | /** 30 | * Deletes a class-field-based ACE. 31 | */ 32 | public function deleteClassFieldAce(int $index, string $field); 33 | 34 | /** 35 | * Deletes an object-based ACE. 36 | */ 37 | public function deleteObjectAce(int $index); 38 | 39 | /** 40 | * Deletes an object-field-based ACE. 41 | */ 42 | public function deleteObjectFieldAce(int $index, string $field); 43 | 44 | /** 45 | * Returns the primary key of this ACL. 46 | * 47 | * @return int 48 | */ 49 | public function getId(); 50 | 51 | /** 52 | * Inserts a class-based ACE. 53 | */ 54 | public function insertClassAce(SecurityIdentityInterface $sid, int $mask, int $index = 0, bool $granting = true, ?string $strategy = null); 55 | 56 | /** 57 | * Inserts a class-field-based ACE. 58 | */ 59 | public function insertClassFieldAce(string $field, SecurityIdentityInterface $sid, int $mask, int $index = 0, bool $granting = true, ?string $strategy = null); 60 | 61 | /** 62 | * Inserts an object-based ACE. 63 | */ 64 | public function insertObjectAce(SecurityIdentityInterface $sid, int $mask, int $index = 0, bool $granting = true, ?string $strategy = null); 65 | 66 | /** 67 | * Inserts an object-field-based ACE. 68 | */ 69 | public function insertObjectFieldAce(string $field, SecurityIdentityInterface $sid, int $mask, int $index = 0, bool $granting = true, ?string $strategy = null); 70 | 71 | /** 72 | * Sets whether entries are inherited. 73 | */ 74 | public function setEntriesInheriting(bool $boolean); 75 | 76 | /** 77 | * Sets the parent ACL. 78 | */ 79 | public function setParentAcl(?AclInterface $acl = null); 80 | 81 | /** 82 | * Updates a class-based ACE. 83 | * 84 | * @param string|null $strategy if null the strategy should not be changed 85 | */ 86 | public function updateClassAce(int $index, int $mask, ?string $strategy = null); 87 | 88 | /** 89 | * Updates a class-field-based ACE. 90 | * 91 | * @param string|null $strategy if null the strategy should not be changed 92 | */ 93 | public function updateClassFieldAce(int $index, string $field, int $mask, ?string $strategy = null); 94 | 95 | /** 96 | * Updates an object-based ACE. 97 | * 98 | * @param string|null $strategy if null the strategy should not be changed 99 | */ 100 | public function updateObjectAce(int $index, int $mask, ?string $strategy = null); 101 | 102 | /** 103 | * Updates an object-field-based ACE. 104 | * 105 | * @param string|null $strategy if null the strategy should not be changed 106 | */ 107 | public function updateObjectFieldAce(int $index, string $field, int $mask, ?string $strategy = null); 108 | } 109 | -------------------------------------------------------------------------------- /Model/MutableAclProviderInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Model; 13 | 14 | use Symfony\Component\Security\Acl\Exception\AclAlreadyExistsException; 15 | 16 | /** 17 | * Provides support for creating and storing ACL instances. 18 | * 19 | * @author Johannes M. Schmitt 20 | */ 21 | interface MutableAclProviderInterface extends AclProviderInterface 22 | { 23 | /** 24 | * {@inheritdoc} 25 | * 26 | * @return MutableAclInterface 27 | */ 28 | public function findAcl(ObjectIdentityInterface $oid, array $sids = []); 29 | 30 | /** 31 | * {@inheritdoc} 32 | * 33 | * @return \SplObjectStorage mapping the passed object identities to ACLs 34 | */ 35 | public function findAcls(array $oids, array $sids = []); 36 | 37 | /** 38 | * Creates a new ACL for the given object identity. 39 | * 40 | * @return MutableAclInterface 41 | * 42 | * @throws AclAlreadyExistsException when there already is an ACL for the given 43 | * object identity 44 | */ 45 | public function createAcl(ObjectIdentityInterface $oid); 46 | 47 | /** 48 | * Deletes the ACL for a given object identity. 49 | * 50 | * This will automatically trigger a delete for any child ACLs. If you don't 51 | * want child ACLs to be deleted, you will have to set their parent ACL to null. 52 | */ 53 | public function deleteAcl(ObjectIdentityInterface $oid); 54 | 55 | /** 56 | * Persists any changes which were made to the ACL, or any associated 57 | * access control entries. 58 | * 59 | * Changes to parent ACLs are not persisted. 60 | */ 61 | public function updateAcl(MutableAclInterface $acl); 62 | } 63 | -------------------------------------------------------------------------------- /Model/ObjectIdentityInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Model; 13 | 14 | /** 15 | * Represents the identity of an individual domain object instance. 16 | * 17 | * @author Johannes M. Schmitt 18 | */ 19 | interface ObjectIdentityInterface 20 | { 21 | /** 22 | * We specifically require this method so we can check for object equality 23 | * explicitly, and do not have to rely on referencial equality instead. 24 | * 25 | * Though in most cases, both checks should result in the same outcome. 26 | * 27 | * Referential Equality: $object1 === $object2 28 | * Example for Object Equality: $object1->getId() === $object2->getId() 29 | * 30 | * @return bool 31 | */ 32 | public function equals(self $identity); 33 | 34 | /** 35 | * Obtains a unique identifier for this object. The identifier must not be 36 | * re-used for other objects with the same type. 37 | * 38 | * @return string cannot return null 39 | */ 40 | public function getIdentifier(); 41 | 42 | /** 43 | * Returns a type for the domain object. Typically, this is the PHP class name. 44 | * 45 | * @return string cannot return null 46 | */ 47 | public function getType(); 48 | } 49 | -------------------------------------------------------------------------------- /Model/ObjectIdentityRetrievalStrategyInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Model; 13 | 14 | /** 15 | * Retrieves the object identity for a given domain object. 16 | * 17 | * @author Johannes M. Schmitt 18 | */ 19 | interface ObjectIdentityRetrievalStrategyInterface 20 | { 21 | /** 22 | * Retrieves the object identity from a domain object. 23 | * 24 | * @param object $domainObject 25 | * 26 | * @return ObjectIdentityInterface 27 | */ 28 | public function getObjectIdentity($domainObject); 29 | } 30 | -------------------------------------------------------------------------------- /Model/PermissionGrantingStrategyInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Model; 13 | 14 | /** 15 | * Interface used by permission granting implementations. 16 | * 17 | * @author Johannes M. Schmitt 18 | */ 19 | interface PermissionGrantingStrategyInterface 20 | { 21 | /** 22 | * Determines whether access to a domain object is to be granted. 23 | * 24 | * @param bool $administrativeMode 25 | * 26 | * @return bool 27 | */ 28 | public function isGranted(AclInterface $acl, array $masks, array $sids, $administrativeMode = false); 29 | 30 | /** 31 | * Determines whether access to a domain object's field is to be granted. 32 | * 33 | * @param string $field 34 | * @param bool $administrativeMode 35 | * 36 | * @return bool 37 | */ 38 | public function isFieldGranted(AclInterface $acl, $field, array $masks, array $sids, $administrativeMode = false); 39 | } 40 | -------------------------------------------------------------------------------- /Model/SecurityIdentityInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Model; 13 | 14 | /** 15 | * This interface provides an additional level of indirection, so that 16 | * we can work with abstracted versions of security objects and do 17 | * not have to save the entire objects. 18 | * 19 | * @author Johannes M. Schmitt 20 | */ 21 | interface SecurityIdentityInterface 22 | { 23 | /** 24 | * This method is used to compare two security identities in order to 25 | * not rely on referential equality. 26 | */ 27 | public function equals(self $sid); 28 | } 29 | -------------------------------------------------------------------------------- /Model/SecurityIdentityRetrievalStrategyInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Model; 13 | 14 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 15 | 16 | /** 17 | * Interface for retrieving security identities from tokens. 18 | * 19 | * @author Johannes M. Schmitt 20 | */ 21 | interface SecurityIdentityRetrievalStrategyInterface 22 | { 23 | /** 24 | * Retrieves the available security identities for the given token. 25 | * 26 | * The order in which the security identities are returned is significant. 27 | * Typically, security identities should be ordered from most specific to 28 | * least specific. 29 | * 30 | * @return SecurityIdentityInterface[] An array of SecurityIdentityInterface implementations 31 | */ 32 | public function getSecurityIdentities(TokenInterface $token); 33 | } 34 | -------------------------------------------------------------------------------- /Permission/AbstractMaskBuilder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Permission; 13 | 14 | /** 15 | * This abstract class implements nearly all the MaskBuilderInterface methods. 16 | */ 17 | abstract class AbstractMaskBuilder implements MaskBuilderInterface 18 | { 19 | /** 20 | * @var int 21 | */ 22 | protected $mask; 23 | 24 | /** 25 | * Constructor. 26 | * 27 | * @param int $mask optional; defaults to 0 28 | */ 29 | public function __construct($mask = 0) 30 | { 31 | $this->set($mask); 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function set($mask) 38 | { 39 | if (!\is_int($mask)) { 40 | throw new \InvalidArgumentException('$mask must be an integer.'); 41 | } 42 | 43 | $this->mask = $mask; 44 | 45 | return $this; 46 | } 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | public function get() 52 | { 53 | return $this->mask; 54 | } 55 | 56 | /** 57 | * {@inheritdoc} 58 | */ 59 | public function add($mask) 60 | { 61 | $this->mask |= $this->resolveMask($mask); 62 | 63 | return $this; 64 | } 65 | 66 | /** 67 | * {@inheritdoc} 68 | */ 69 | public function remove($mask) 70 | { 71 | $this->mask &= ~$this->resolveMask($mask); 72 | 73 | return $this; 74 | } 75 | 76 | /** 77 | * {@inheritdoc} 78 | */ 79 | public function reset() 80 | { 81 | $this->mask = 0; 82 | 83 | return $this; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Permission/BasicPermissionMap.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Permission; 13 | 14 | /** 15 | * This is basic permission map complements the masks which have been defined 16 | * on the standard implementation of the MaskBuilder. 17 | * 18 | * @author Johannes M. Schmitt 19 | */ 20 | class BasicPermissionMap implements PermissionMapInterface, MaskBuilderRetrievalInterface 21 | { 22 | public const PERMISSION_VIEW = 'VIEW'; 23 | public const PERMISSION_EDIT = 'EDIT'; 24 | public const PERMISSION_CREATE = 'CREATE'; 25 | public const PERMISSION_DELETE = 'DELETE'; 26 | public const PERMISSION_UNDELETE = 'UNDELETE'; 27 | public const PERMISSION_OPERATOR = 'OPERATOR'; 28 | public const PERMISSION_MASTER = 'MASTER'; 29 | public const PERMISSION_OWNER = 'OWNER'; 30 | 31 | protected $map; 32 | 33 | public function __construct() 34 | { 35 | $this->map = [ 36 | self::PERMISSION_VIEW => [ 37 | MaskBuilder::MASK_VIEW, 38 | MaskBuilder::MASK_EDIT, 39 | MaskBuilder::MASK_OPERATOR, 40 | MaskBuilder::MASK_MASTER, 41 | MaskBuilder::MASK_OWNER, 42 | ], 43 | 44 | self::PERMISSION_EDIT => [ 45 | MaskBuilder::MASK_EDIT, 46 | MaskBuilder::MASK_OPERATOR, 47 | MaskBuilder::MASK_MASTER, 48 | MaskBuilder::MASK_OWNER, 49 | ], 50 | 51 | self::PERMISSION_CREATE => [ 52 | MaskBuilder::MASK_CREATE, 53 | MaskBuilder::MASK_OPERATOR, 54 | MaskBuilder::MASK_MASTER, 55 | MaskBuilder::MASK_OWNER, 56 | ], 57 | 58 | self::PERMISSION_DELETE => [ 59 | MaskBuilder::MASK_DELETE, 60 | MaskBuilder::MASK_OPERATOR, 61 | MaskBuilder::MASK_MASTER, 62 | MaskBuilder::MASK_OWNER, 63 | ], 64 | 65 | self::PERMISSION_UNDELETE => [ 66 | MaskBuilder::MASK_UNDELETE, 67 | MaskBuilder::MASK_OPERATOR, 68 | MaskBuilder::MASK_MASTER, 69 | MaskBuilder::MASK_OWNER, 70 | ], 71 | 72 | self::PERMISSION_OPERATOR => [ 73 | MaskBuilder::MASK_OPERATOR, 74 | MaskBuilder::MASK_MASTER, 75 | MaskBuilder::MASK_OWNER, 76 | ], 77 | 78 | self::PERMISSION_MASTER => [ 79 | MaskBuilder::MASK_MASTER, 80 | MaskBuilder::MASK_OWNER, 81 | ], 82 | 83 | self::PERMISSION_OWNER => [ 84 | MaskBuilder::MASK_OWNER, 85 | ], 86 | ]; 87 | } 88 | 89 | /** 90 | * {@inheritdoc} 91 | */ 92 | public function getMasks($permission, $object) 93 | { 94 | if (!isset($this->map[$permission])) { 95 | return; 96 | } 97 | 98 | return $this->map[$permission]; 99 | } 100 | 101 | /** 102 | * {@inheritdoc} 103 | */ 104 | public function contains($permission) 105 | { 106 | return isset($this->map[$permission]); 107 | } 108 | 109 | /** 110 | * {@inheritdoc} 111 | */ 112 | public function getMaskBuilder() 113 | { 114 | return new MaskBuilder(); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Permission/MaskBuilder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Permission; 13 | 14 | /** 15 | * This class allows you to build cumulative permissions easily, or convert 16 | * masks to a human-readable format. 17 | * 18 | * 19 | * $builder = new MaskBuilder(); 20 | * $builder 21 | * ->add('view') 22 | * ->add('create') 23 | * ->add('edit') 24 | * ; 25 | * var_dump($builder->get()); // int(7) 26 | * var_dump($builder->getPattern()); // string(32) ".............................ECV" 27 | * 28 | * 29 | * We have defined some commonly used base permissions which you can use: 30 | * - VIEW: the SID is allowed to view the domain object / field 31 | * - CREATE: the SID is allowed to create new instances of the domain object / fields 32 | * - EDIT: the SID is allowed to edit existing instances of the domain object / field 33 | * - DELETE: the SID is allowed to delete domain objects 34 | * - UNDELETE: the SID is allowed to recover domain objects from trash 35 | * - OPERATOR: the SID is allowed to perform any action on the domain object 36 | * except for granting others permissions 37 | * - MASTER: the SID is allowed to perform any action on the domain object, 38 | * and is allowed to grant other SIDs any permission except for 39 | * MASTER and OWNER permissions 40 | * - OWNER: the SID is owning the domain object in question and can perform any 41 | * action on the domain object as well as grant any permission 42 | * 43 | * @author Johannes M. Schmitt 44 | */ 45 | class MaskBuilder extends AbstractMaskBuilder 46 | { 47 | public const MASK_VIEW = 1; // 1 << 0 48 | public const MASK_CREATE = 2; // 1 << 1 49 | public const MASK_EDIT = 4; // 1 << 2 50 | public const MASK_DELETE = 8; // 1 << 3 51 | public const MASK_UNDELETE = 16; // 1 << 4 52 | public const MASK_OPERATOR = 32; // 1 << 5 53 | public const MASK_MASTER = 64; // 1 << 6 54 | public const MASK_OWNER = 128; // 1 << 7 55 | public const MASK_IDDQD = 1073741823; // 1 << 0 | 1 << 1 | ... | 1 << 30 56 | 57 | public const CODE_VIEW = 'V'; 58 | public const CODE_CREATE = 'C'; 59 | public const CODE_EDIT = 'E'; 60 | public const CODE_DELETE = 'D'; 61 | public const CODE_UNDELETE = 'U'; 62 | public const CODE_OPERATOR = 'O'; 63 | public const CODE_MASTER = 'M'; 64 | public const CODE_OWNER = 'N'; 65 | 66 | public const ALL_OFF = '................................'; 67 | public const OFF = '.'; 68 | public const ON = '*'; 69 | 70 | /** 71 | * Returns a human-readable representation of the permission. 72 | * 73 | * @return string 74 | */ 75 | public function getPattern() 76 | { 77 | $pattern = self::ALL_OFF; 78 | $length = \strlen($pattern); 79 | $bitmask = str_pad(decbin($this->mask), $length, '0', \STR_PAD_LEFT); 80 | 81 | for ($i = $length - 1; $i >= 0; --$i) { 82 | if ('1' === $bitmask[$i]) { 83 | try { 84 | $pattern[$i] = self::getCode(1 << ($length - $i - 1)); 85 | } catch (\Exception $e) { 86 | $pattern[$i] = self::ON; 87 | } 88 | } 89 | } 90 | 91 | return $pattern; 92 | } 93 | 94 | /** 95 | * Returns the code for the passed mask. 96 | * 97 | * @param int $mask 98 | * 99 | * @return string 100 | * 101 | * @throws \InvalidArgumentException 102 | * @throws \RuntimeException 103 | */ 104 | public static function getCode($mask) 105 | { 106 | if (!\is_int($mask)) { 107 | throw new \InvalidArgumentException('$mask must be an integer.'); 108 | } 109 | 110 | $reflection = new \ReflectionClass(static::class); 111 | foreach ($reflection->getConstants() as $name => $cMask) { 112 | if (0 !== strpos($name, 'MASK_') || $mask !== $cMask) { 113 | continue; 114 | } 115 | 116 | if (!\defined($cName = 'static::CODE_'.substr($name, 5))) { 117 | throw new \RuntimeException('There was no code defined for this mask.'); 118 | } 119 | 120 | return \constant($cName); 121 | } 122 | 123 | throw new \InvalidArgumentException(sprintf('The mask "%d" is not supported.', $mask)); 124 | } 125 | 126 | /** 127 | * Returns the mask for the passed code. 128 | * 129 | * @param mixed $code 130 | * 131 | * @return int 132 | * 133 | * @throws \InvalidArgumentException 134 | */ 135 | public function resolveMask($code) 136 | { 137 | if (\is_string($code)) { 138 | if (!\defined($name = sprintf('static::MASK_%s', strtoupper($code)))) { 139 | throw new \InvalidArgumentException(sprintf('The code "%s" is not supported', $code)); 140 | } 141 | 142 | return \constant($name); 143 | } 144 | 145 | if (!\is_int($code)) { 146 | throw new \InvalidArgumentException('$code must be an integer.'); 147 | } 148 | 149 | return $code; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /Permission/MaskBuilderInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Permission; 13 | 14 | /** 15 | * This is the interface that must be implemented by mask builders. 16 | */ 17 | interface MaskBuilderInterface 18 | { 19 | /** 20 | * Set the mask of this permission. 21 | * 22 | * @param int $mask 23 | * 24 | * @return MaskBuilderInterface 25 | * 26 | * @throws \InvalidArgumentException if $mask is not an integer 27 | */ 28 | public function set($mask); 29 | 30 | /** 31 | * Returns the mask of this permission. 32 | * 33 | * @return int 34 | */ 35 | public function get(); 36 | 37 | /** 38 | * Adds a mask to the permission. 39 | * 40 | * @param mixed $mask 41 | * 42 | * @return MaskBuilderInterface 43 | * 44 | * @throws \InvalidArgumentException 45 | */ 46 | public function add($mask); 47 | 48 | /** 49 | * Removes a mask from the permission. 50 | * 51 | * @param mixed $mask 52 | * 53 | * @return MaskBuilderInterface 54 | * 55 | * @throws \InvalidArgumentException 56 | */ 57 | public function remove($mask); 58 | 59 | /** 60 | * Resets the PermissionBuilder. 61 | * 62 | * @return MaskBuilderInterface 63 | */ 64 | public function reset(); 65 | 66 | /** 67 | * Returns the mask for the passed code. 68 | * 69 | * @param mixed $code 70 | * 71 | * @return int 72 | * 73 | * @throws \InvalidArgumentException 74 | */ 75 | public function resolveMask($code); 76 | } 77 | -------------------------------------------------------------------------------- /Permission/MaskBuilderRetrievalInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Permission; 13 | 14 | /** 15 | * Retrieves the MaskBuilder. 16 | */ 17 | interface MaskBuilderRetrievalInterface 18 | { 19 | /** 20 | * Returns a new instance of the MaskBuilder used in the permissionMap. 21 | * 22 | * @return MaskBuilderInterface 23 | */ 24 | public function getMaskBuilder(); 25 | } 26 | -------------------------------------------------------------------------------- /Permission/PermissionMapInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Permission; 13 | 14 | /** 15 | * This is the interface that must be implemented by permission maps. 16 | * 17 | * @author Johannes M. Schmitt 18 | */ 19 | interface PermissionMapInterface 20 | { 21 | /** 22 | * Returns an array of bitmasks. 23 | * 24 | * The security identity must have been granted access to at least one of 25 | * these bitmasks. 26 | * 27 | * @param string $permission 28 | * @param object $object 29 | * 30 | * @return array|null may return null if permission/object combination is not supported 31 | */ 32 | public function getMasks($permission, $object); 33 | 34 | /** 35 | * Whether this map contains the given permission. 36 | * 37 | * @param string $permission 38 | * 39 | * @return bool 40 | */ 41 | public function contains($permission); 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Security Component - ACL (Access Control List) 2 | ============================================== 3 | 4 | Security provides an infrastructure for sophisticated authorization systems, 5 | which makes it possible to easily separate the actual authorization logic from 6 | so called user providers that hold the users credentials. It is inspired by 7 | the Java Spring framework. 8 | 9 | Resources 10 | --------- 11 | 12 | Documentation: 13 | 14 | https://github.com/symfony/acl-bundle/blob/main/src/Resources/doc/index.rst 15 | 16 | Tests 17 | ----- 18 | 19 | You can run the unit tests with the following command: 20 | 21 | $ cd path/to/Symfony/Component/Security/Acl/ 22 | $ composer.phar install --dev 23 | $ phpunit 24 | -------------------------------------------------------------------------------- /Resources/bin/generateSql.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | require_once __DIR__.'/../../vendor/autoload.php'; 13 | 14 | use Symfony\Component\Finder\Finder; 15 | use Symfony\Component\Security\Acl\Dbal\Schema; 16 | 17 | $schema = new Schema([ 18 | 'class_table_name' => 'acl_classes', 19 | 'entry_table_name' => 'acl_entries', 20 | 'oid_table_name' => 'acl_object_identities', 21 | 'oid_ancestors_table_name' => 'acl_object_identity_ancestors', 22 | 'sid_table_name' => 'acl_security_identities', 23 | ]); 24 | 25 | $reflection = new ReflectionClass('Doctrine\\DBAL\\Platforms\\AbstractPlatform'); 26 | $finder = new Finder(); 27 | $finder->name('*Platform.php')->in(dirname($reflection->getFileName())); 28 | foreach ($finder as $file) { 29 | $className = 'Doctrine\\DBAL\\Platforms\\'.$file->getBasename('.php'); 30 | 31 | $reflection = new ReflectionClass($className); 32 | if ($reflection->isAbstract()) { 33 | continue; 34 | } 35 | 36 | $platform = $reflection->newInstance(); 37 | $targetFile = sprintf(__DIR__.'/../schema/%s.sql', $platform->getName()); 38 | file_put_contents($targetFile, implode("\n\n", $schema->toSql($platform))); 39 | } 40 | -------------------------------------------------------------------------------- /Resources/schema/db2.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE acl_classes (id INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL, class_type VARCHAR(200) NOT NULL, PRIMARY KEY(id)) 2 | 3 | CREATE UNIQUE INDEX UNIQ_69DD750638A36066 ON acl_classes (class_type) 4 | 5 | CREATE TABLE acl_security_identities (id INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL, identifier VARCHAR(200) NOT NULL, username SMALLINT NOT NULL, PRIMARY KEY(id)) 6 | 7 | CREATE UNIQUE INDEX UNIQ_8835EE78772E836AF85E0677 ON acl_security_identities (identifier, username) 8 | 9 | COMMENT ON COLUMN acl_security_identities.username IS '(DC2Type:boolean)' 10 | 11 | CREATE TABLE acl_object_identities (id INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL, parent_object_identity_id INTEGER DEFAULT NULL, class_id INTEGER NOT NULL, object_identifier VARCHAR(100) NOT NULL, entries_inheriting SMALLINT NOT NULL, PRIMARY KEY(id)) 12 | 13 | CREATE UNIQUE INDEX UNIQ_9407E5494B12AD6EA000B10 ON acl_object_identities (object_identifier, class_id) 14 | 15 | CREATE INDEX IDX_9407E54977FA751A ON acl_object_identities (parent_object_identity_id) 16 | 17 | COMMENT ON COLUMN acl_object_identities.entries_inheriting IS '(DC2Type:boolean)' 18 | 19 | CREATE TABLE acl_object_identity_ancestors (object_identity_id INTEGER NOT NULL, ancestor_id INTEGER NOT NULL, PRIMARY KEY(object_identity_id, ancestor_id)) 20 | 21 | CREATE INDEX IDX_825DE2993D9AB4A6 ON acl_object_identity_ancestors (object_identity_id) 22 | 23 | CREATE INDEX IDX_825DE299C671CEA1 ON acl_object_identity_ancestors (ancestor_id) 24 | 25 | CREATE TABLE acl_entries (id INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL, class_id INTEGER NOT NULL, object_identity_id INTEGER DEFAULT NULL, security_identity_id INTEGER NOT NULL, field_name VARCHAR(50) DEFAULT NULL, ace_order SMALLINT NOT NULL, mask INTEGER NOT NULL, granting SMALLINT NOT NULL, granting_strategy VARCHAR(30) NOT NULL, audit_success SMALLINT NOT NULL, audit_failure SMALLINT NOT NULL, PRIMARY KEY(id)) 26 | 27 | CREATE UNIQUE INDEX UNIQ_46C8B806EA000B103D9AB4A64DEF17BCE4289BF4 ON acl_entries (class_id, object_identity_id, field_name, ace_order) 28 | 29 | CREATE INDEX IDX_46C8B806EA000B103D9AB4A6DF9183C9 ON acl_entries (class_id, object_identity_id, security_identity_id) 30 | 31 | CREATE INDEX IDX_46C8B806EA000B10 ON acl_entries (class_id) 32 | 33 | CREATE INDEX IDX_46C8B8063D9AB4A6 ON acl_entries (object_identity_id) 34 | 35 | CREATE INDEX IDX_46C8B806DF9183C9 ON acl_entries (security_identity_id) 36 | 37 | COMMENT ON COLUMN acl_entries.granting IS '(DC2Type:boolean)' 38 | 39 | COMMENT ON COLUMN acl_entries.audit_success IS '(DC2Type:boolean)' 40 | 41 | COMMENT ON COLUMN acl_entries.audit_failure IS '(DC2Type:boolean)' 42 | 43 | ALTER TABLE acl_object_identities ADD CONSTRAINT FK_9407E54977FA751A FOREIGN KEY (parent_object_identity_id) REFERENCES acl_object_identities (id) 44 | 45 | ALTER TABLE acl_object_identity_ancestors ADD CONSTRAINT FK_825DE2993D9AB4A6 FOREIGN KEY (object_identity_id) REFERENCES acl_object_identities (id) ON UPDATE CASCADE ON DELETE CASCADE 46 | 47 | ALTER TABLE acl_object_identity_ancestors ADD CONSTRAINT FK_825DE299C671CEA1 FOREIGN KEY (ancestor_id) REFERENCES acl_object_identities (id) ON UPDATE CASCADE ON DELETE CASCADE 48 | 49 | ALTER TABLE acl_entries ADD CONSTRAINT FK_46C8B806EA000B10 FOREIGN KEY (class_id) REFERENCES acl_classes (id) ON UPDATE CASCADE ON DELETE CASCADE 50 | 51 | ALTER TABLE acl_entries ADD CONSTRAINT FK_46C8B8063D9AB4A6 FOREIGN KEY (object_identity_id) REFERENCES acl_object_identities (id) ON UPDATE CASCADE ON DELETE CASCADE 52 | 53 | ALTER TABLE acl_entries ADD CONSTRAINT FK_46C8B806DF9183C9 FOREIGN KEY (security_identity_id) REFERENCES acl_security_identities (id) ON UPDATE CASCADE ON DELETE CASCADE -------------------------------------------------------------------------------- /Resources/schema/drizzle.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE acl_classes (id INT AUTO_INCREMENT NOT NULL, class_type VARCHAR(200) NOT NULL, UNIQUE INDEX UNIQ_69DD750638A36066 (class_type), PRIMARY KEY(id)) COLLATE utf8_unicode_ci ENGINE = InnoDB 2 | 3 | CREATE TABLE acl_security_identities (id INT AUTO_INCREMENT NOT NULL, identifier VARCHAR(200) NOT NULL, username BOOLEAN NOT NULL, UNIQUE INDEX UNIQ_8835EE78772E836AF85E0677 (identifier, username), PRIMARY KEY(id)) COLLATE utf8_unicode_ci ENGINE = InnoDB 4 | 5 | CREATE TABLE acl_object_identities (id INT AUTO_INCREMENT NOT NULL, parent_object_identity_id INT DEFAULT NULL, class_id INT NOT NULL, object_identifier VARCHAR(100) NOT NULL, entries_inheriting BOOLEAN NOT NULL, UNIQUE INDEX UNIQ_9407E5494B12AD6EA000B10 (object_identifier, class_id), INDEX IDX_9407E54977FA751A (parent_object_identity_id), PRIMARY KEY(id)) COLLATE utf8_unicode_ci ENGINE = InnoDB 6 | 7 | CREATE TABLE acl_object_identity_ancestors (object_identity_id INT NOT NULL, ancestor_id INT NOT NULL, INDEX IDX_825DE2993D9AB4A6 (object_identity_id), INDEX IDX_825DE299C671CEA1 (ancestor_id), PRIMARY KEY(object_identity_id, ancestor_id)) COLLATE utf8_unicode_ci ENGINE = InnoDB 8 | 9 | CREATE TABLE acl_entries (id INT AUTO_INCREMENT NOT NULL, class_id INT NOT NULL, object_identity_id INT DEFAULT NULL, security_identity_id INT NOT NULL, field_name VARCHAR(50) DEFAULT NULL, ace_order INT NOT NULL, mask INT NOT NULL, granting BOOLEAN NOT NULL, granting_strategy VARCHAR(30) NOT NULL, audit_success BOOLEAN NOT NULL, audit_failure BOOLEAN NOT NULL, UNIQUE INDEX UNIQ_46C8B806EA000B103D9AB4A64DEF17BCE4289BF4 (class_id, object_identity_id, field_name, ace_order), INDEX IDX_46C8B806EA000B103D9AB4A6DF9183C9 (class_id, object_identity_id, security_identity_id), INDEX IDX_46C8B806EA000B10 (class_id), INDEX IDX_46C8B8063D9AB4A6 (object_identity_id), INDEX IDX_46C8B806DF9183C9 (security_identity_id), PRIMARY KEY(id)) COLLATE utf8_unicode_ci ENGINE = InnoDB 10 | 11 | ALTER TABLE acl_object_identities ADD CONSTRAINT FK_9407E54977FA751A FOREIGN KEY (parent_object_identity_id) REFERENCES acl_object_identities (id) 12 | 13 | ALTER TABLE acl_object_identity_ancestors ADD CONSTRAINT FK_825DE2993D9AB4A6 FOREIGN KEY (object_identity_id) REFERENCES acl_object_identities (id) ON UPDATE CASCADE ON DELETE CASCADE 14 | 15 | ALTER TABLE acl_object_identity_ancestors ADD CONSTRAINT FK_825DE299C671CEA1 FOREIGN KEY (ancestor_id) REFERENCES acl_object_identities (id) ON UPDATE CASCADE ON DELETE CASCADE 16 | 17 | ALTER TABLE acl_entries ADD CONSTRAINT FK_46C8B806EA000B10 FOREIGN KEY (class_id) REFERENCES acl_classes (id) ON UPDATE CASCADE ON DELETE CASCADE 18 | 19 | ALTER TABLE acl_entries ADD CONSTRAINT FK_46C8B8063D9AB4A6 FOREIGN KEY (object_identity_id) REFERENCES acl_object_identities (id) ON UPDATE CASCADE ON DELETE CASCADE 20 | 21 | ALTER TABLE acl_entries ADD CONSTRAINT FK_46C8B806DF9183C9 FOREIGN KEY (security_identity_id) REFERENCES acl_security_identities (id) ON UPDATE CASCADE ON DELETE CASCADE -------------------------------------------------------------------------------- /Resources/schema/mssql.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE acl_classes (id INT IDENTITY NOT NULL, class_type NVARCHAR(200) NOT NULL, PRIMARY KEY (id)) 2 | 3 | CREATE UNIQUE INDEX UNIQ_69DD750638A36066 ON acl_classes (class_type) WHERE class_type IS NOT NULL 4 | 5 | CREATE TABLE acl_security_identities (id INT IDENTITY NOT NULL, identifier NVARCHAR(200) NOT NULL, username BIT NOT NULL, PRIMARY KEY (id)) 6 | 7 | CREATE UNIQUE INDEX UNIQ_8835EE78772E836AF85E0677 ON acl_security_identities (identifier, username) WHERE identifier IS NOT NULL AND username IS NOT NULL 8 | 9 | CREATE TABLE acl_object_identities (id INT IDENTITY NOT NULL, parent_object_identity_id INT, class_id INT NOT NULL, object_identifier NVARCHAR(100) NOT NULL, entries_inheriting BIT NOT NULL, PRIMARY KEY (id)) 10 | 11 | CREATE UNIQUE INDEX UNIQ_9407E5494B12AD6EA000B10 ON acl_object_identities (object_identifier, class_id) WHERE object_identifier IS NOT NULL AND class_id IS NOT NULL 12 | 13 | CREATE INDEX IDX_9407E54977FA751A ON acl_object_identities (parent_object_identity_id) 14 | 15 | CREATE TABLE acl_object_identity_ancestors (object_identity_id INT NOT NULL, ancestor_id INT NOT NULL, PRIMARY KEY (object_identity_id, ancestor_id)) 16 | 17 | CREATE INDEX IDX_825DE2993D9AB4A6 ON acl_object_identity_ancestors (object_identity_id) 18 | 19 | CREATE INDEX IDX_825DE299C671CEA1 ON acl_object_identity_ancestors (ancestor_id) 20 | 21 | CREATE TABLE acl_entries (id INT IDENTITY NOT NULL, class_id INT NOT NULL, object_identity_id INT, security_identity_id INT NOT NULL, field_name NVARCHAR(50), ace_order SMALLINT NOT NULL, mask INT NOT NULL, granting BIT NOT NULL, granting_strategy NVARCHAR(30) NOT NULL, audit_success BIT NOT NULL, audit_failure BIT NOT NULL, PRIMARY KEY (id)) 22 | 23 | CREATE UNIQUE INDEX UNIQ_46C8B806EA000B103D9AB4A64DEF17BCE4289BF4 ON acl_entries (class_id, object_identity_id, field_name, ace_order) WHERE class_id IS NOT NULL AND object_identity_id IS NOT NULL AND field_name IS NOT NULL AND ace_order IS NOT NULL 24 | 25 | CREATE INDEX IDX_46C8B806EA000B103D9AB4A6DF9183C9 ON acl_entries (class_id, object_identity_id, security_identity_id) 26 | 27 | CREATE INDEX IDX_46C8B806EA000B10 ON acl_entries (class_id) 28 | 29 | CREATE INDEX IDX_46C8B8063D9AB4A6 ON acl_entries (object_identity_id) 30 | 31 | CREATE INDEX IDX_46C8B806DF9183C9 ON acl_entries (security_identity_id) 32 | 33 | ALTER TABLE acl_object_identities ADD CONSTRAINT FK_9407E54977FA751A FOREIGN KEY (parent_object_identity_id) REFERENCES acl_object_identities (id) 34 | 35 | ALTER TABLE acl_object_identity_ancestors ADD CONSTRAINT FK_825DE2993D9AB4A6 FOREIGN KEY (object_identity_id) REFERENCES acl_object_identities (id) ON UPDATE CASCADE ON DELETE CASCADE 36 | 37 | ALTER TABLE acl_object_identity_ancestors ADD CONSTRAINT FK_825DE299C671CEA1 FOREIGN KEY (ancestor_id) REFERENCES acl_object_identities (id) ON UPDATE CASCADE ON DELETE CASCADE 38 | 39 | ALTER TABLE acl_entries ADD CONSTRAINT FK_46C8B806EA000B10 FOREIGN KEY (class_id) REFERENCES acl_classes (id) ON UPDATE CASCADE ON DELETE CASCADE 40 | 41 | ALTER TABLE acl_entries ADD CONSTRAINT FK_46C8B8063D9AB4A6 FOREIGN KEY (object_identity_id) REFERENCES acl_object_identities (id) ON UPDATE CASCADE ON DELETE CASCADE 42 | 43 | ALTER TABLE acl_entries ADD CONSTRAINT FK_46C8B806DF9183C9 FOREIGN KEY (security_identity_id) REFERENCES acl_security_identities (id) ON UPDATE CASCADE ON DELETE CASCADE -------------------------------------------------------------------------------- /Resources/schema/mysql.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE acl_classes (id INT UNSIGNED AUTO_INCREMENT NOT NULL, class_type VARCHAR(200) NOT NULL, UNIQUE INDEX UNIQ_69DD750638A36066 (class_type), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB 2 | 3 | CREATE TABLE acl_security_identities (id INT UNSIGNED AUTO_INCREMENT NOT NULL, identifier VARCHAR(200) NOT NULL, username TINYINT(1) NOT NULL, UNIQUE INDEX UNIQ_8835EE78772E836AF85E0677 (identifier, username), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB 4 | 5 | CREATE TABLE acl_object_identities (id INT UNSIGNED AUTO_INCREMENT NOT NULL, parent_object_identity_id INT UNSIGNED DEFAULT NULL, class_id INT UNSIGNED NOT NULL, object_identifier VARCHAR(100) NOT NULL, entries_inheriting TINYINT(1) NOT NULL, UNIQUE INDEX UNIQ_9407E5494B12AD6EA000B10 (object_identifier, class_id), INDEX IDX_9407E54977FA751A (parent_object_identity_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB 6 | 7 | CREATE TABLE acl_object_identity_ancestors (object_identity_id INT UNSIGNED NOT NULL, ancestor_id INT UNSIGNED NOT NULL, INDEX IDX_825DE2993D9AB4A6 (object_identity_id), INDEX IDX_825DE299C671CEA1 (ancestor_id), PRIMARY KEY(object_identity_id, ancestor_id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB 8 | 9 | CREATE TABLE acl_entries (id INT UNSIGNED AUTO_INCREMENT NOT NULL, class_id INT UNSIGNED NOT NULL, object_identity_id INT UNSIGNED DEFAULT NULL, security_identity_id INT UNSIGNED NOT NULL, field_name VARCHAR(50) DEFAULT NULL, ace_order SMALLINT UNSIGNED NOT NULL, mask INT NOT NULL, granting TINYINT(1) NOT NULL, granting_strategy VARCHAR(30) NOT NULL, audit_success TINYINT(1) NOT NULL, audit_failure TINYINT(1) NOT NULL, UNIQUE INDEX UNIQ_46C8B806EA000B103D9AB4A64DEF17BCE4289BF4 (class_id, object_identity_id, field_name, ace_order), INDEX IDX_46C8B806EA000B103D9AB4A6DF9183C9 (class_id, object_identity_id, security_identity_id), INDEX IDX_46C8B806EA000B10 (class_id), INDEX IDX_46C8B8063D9AB4A6 (object_identity_id), INDEX IDX_46C8B806DF9183C9 (security_identity_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB 10 | 11 | ALTER TABLE acl_object_identities ADD CONSTRAINT FK_9407E54977FA751A FOREIGN KEY (parent_object_identity_id) REFERENCES acl_object_identities (id) 12 | 13 | ALTER TABLE acl_object_identity_ancestors ADD CONSTRAINT FK_825DE2993D9AB4A6 FOREIGN KEY (object_identity_id) REFERENCES acl_object_identities (id) ON UPDATE CASCADE ON DELETE CASCADE 14 | 15 | ALTER TABLE acl_object_identity_ancestors ADD CONSTRAINT FK_825DE299C671CEA1 FOREIGN KEY (ancestor_id) REFERENCES acl_object_identities (id) ON UPDATE CASCADE ON DELETE CASCADE 16 | 17 | ALTER TABLE acl_entries ADD CONSTRAINT FK_46C8B806EA000B10 FOREIGN KEY (class_id) REFERENCES acl_classes (id) ON UPDATE CASCADE ON DELETE CASCADE 18 | 19 | ALTER TABLE acl_entries ADD CONSTRAINT FK_46C8B8063D9AB4A6 FOREIGN KEY (object_identity_id) REFERENCES acl_object_identities (id) ON UPDATE CASCADE ON DELETE CASCADE 20 | 21 | ALTER TABLE acl_entries ADD CONSTRAINT FK_46C8B806DF9183C9 FOREIGN KEY (security_identity_id) REFERENCES acl_security_identities (id) ON UPDATE CASCADE ON DELETE CASCADE -------------------------------------------------------------------------------- /Resources/schema/oracle.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE acl_classes (id NUMBER(10) NOT NULL, class_type VARCHAR2(200) NOT NULL, PRIMARY KEY(id)) 2 | 3 | DECLARE 4 | constraints_Count NUMBER; 5 | BEGIN 6 | SELECT COUNT(CONSTRAINT_NAME) INTO constraints_Count FROM USER_CONSTRAINTS WHERE TABLE_NAME = 'ACL_CLASSES' AND CONSTRAINT_TYPE = 'P'; 7 | IF constraints_Count = 0 OR constraints_Count = '' THEN 8 | EXECUTE IMMEDIATE 'ALTER TABLE ACL_CLASSES ADD CONSTRAINT ACL_CLASSES_AI_PK PRIMARY KEY (ID)'; 9 | END IF; 10 | END; 11 | 12 | CREATE SEQUENCE ACL_CLASSES_SEQ START WITH 1 MINVALUE 1 INCREMENT BY 1 13 | 14 | CREATE TRIGGER ACL_CLASSES_AI_PK 15 | BEFORE INSERT 16 | ON ACL_CLASSES 17 | FOR EACH ROW 18 | DECLARE 19 | last_Sequence NUMBER; 20 | last_InsertID NUMBER; 21 | BEGIN 22 | SELECT ACL_CLASSES_SEQ.NEXTVAL INTO :NEW.ID FROM DUAL; 23 | IF (:NEW.ID IS NULL OR :NEW.ID = 0) THEN 24 | SELECT ACL_CLASSES_SEQ.NEXTVAL INTO :NEW.ID FROM DUAL; 25 | ELSE 26 | SELECT NVL(Last_Number, 0) INTO last_Sequence 27 | FROM User_Sequences 28 | WHERE Sequence_Name = 'ACL_CLASSES_SEQ'; 29 | SELECT :NEW.ID INTO last_InsertID FROM DUAL; 30 | WHILE (last_InsertID > last_Sequence) LOOP 31 | SELECT ACL_CLASSES_SEQ.NEXTVAL INTO last_Sequence FROM DUAL; 32 | END LOOP; 33 | END IF; 34 | END; 35 | 36 | CREATE UNIQUE INDEX UNIQ_69DD750638A36066 ON acl_classes (class_type) 37 | 38 | CREATE TABLE acl_security_identities (id NUMBER(10) NOT NULL, identifier VARCHAR2(200) NOT NULL, username NUMBER(1) NOT NULL, PRIMARY KEY(id)) 39 | 40 | DECLARE 41 | constraints_Count NUMBER; 42 | BEGIN 43 | SELECT COUNT(CONSTRAINT_NAME) INTO constraints_Count FROM USER_CONSTRAINTS WHERE TABLE_NAME = 'ACL_SECURITY_IDENTITIES' AND CONSTRAINT_TYPE = 'P'; 44 | IF constraints_Count = 0 OR constraints_Count = '' THEN 45 | EXECUTE IMMEDIATE 'ALTER TABLE ACL_SECURITY_IDENTITIES ADD CONSTRAINT ACL_SECURITY_IDENTITIES_AI_PK PRIMARY KEY (ID)'; 46 | END IF; 47 | END; 48 | 49 | CREATE SEQUENCE ACL_SECURITY_IDENTITIES_SEQ START WITH 1 MINVALUE 1 INCREMENT BY 1 50 | 51 | CREATE TRIGGER ACL_SECURITY_IDENTITIES_AI_PK 52 | BEFORE INSERT 53 | ON ACL_SECURITY_IDENTITIES 54 | FOR EACH ROW 55 | DECLARE 56 | last_Sequence NUMBER; 57 | last_InsertID NUMBER; 58 | BEGIN 59 | SELECT ACL_SECURITY_IDENTITIES_SEQ.NEXTVAL INTO :NEW.ID FROM DUAL; 60 | IF (:NEW.ID IS NULL OR :NEW.ID = 0) THEN 61 | SELECT ACL_SECURITY_IDENTITIES_SEQ.NEXTVAL INTO :NEW.ID FROM DUAL; 62 | ELSE 63 | SELECT NVL(Last_Number, 0) INTO last_Sequence 64 | FROM User_Sequences 65 | WHERE Sequence_Name = 'ACL_SECURITY_IDENTITIES_SEQ'; 66 | SELECT :NEW.ID INTO last_InsertID FROM DUAL; 67 | WHILE (last_InsertID > last_Sequence) LOOP 68 | SELECT ACL_SECURITY_IDENTITIES_SEQ.NEXTVAL INTO last_Sequence FROM DUAL; 69 | END LOOP; 70 | END IF; 71 | END; 72 | 73 | CREATE UNIQUE INDEX UNIQ_8835EE78772E836AF85E0677 ON acl_security_identities (identifier, username) 74 | 75 | CREATE TABLE acl_object_identities (id NUMBER(10) NOT NULL, parent_object_identity_id NUMBER(10) DEFAULT NULL NULL, class_id NUMBER(10) NOT NULL, object_identifier VARCHAR2(100) NOT NULL, entries_inheriting NUMBER(1) NOT NULL, PRIMARY KEY(id)) 76 | 77 | DECLARE 78 | constraints_Count NUMBER; 79 | BEGIN 80 | SELECT COUNT(CONSTRAINT_NAME) INTO constraints_Count FROM USER_CONSTRAINTS WHERE TABLE_NAME = 'ACL_OBJECT_IDENTITIES' AND CONSTRAINT_TYPE = 'P'; 81 | IF constraints_Count = 0 OR constraints_Count = '' THEN 82 | EXECUTE IMMEDIATE 'ALTER TABLE ACL_OBJECT_IDENTITIES ADD CONSTRAINT ACL_OBJECT_IDENTITIES_AI_PK PRIMARY KEY (ID)'; 83 | END IF; 84 | END; 85 | 86 | CREATE SEQUENCE ACL_OBJECT_IDENTITIES_SEQ START WITH 1 MINVALUE 1 INCREMENT BY 1 87 | 88 | CREATE TRIGGER ACL_OBJECT_IDENTITIES_AI_PK 89 | BEFORE INSERT 90 | ON ACL_OBJECT_IDENTITIES 91 | FOR EACH ROW 92 | DECLARE 93 | last_Sequence NUMBER; 94 | last_InsertID NUMBER; 95 | BEGIN 96 | SELECT ACL_OBJECT_IDENTITIES_SEQ.NEXTVAL INTO :NEW.ID FROM DUAL; 97 | IF (:NEW.ID IS NULL OR :NEW.ID = 0) THEN 98 | SELECT ACL_OBJECT_IDENTITIES_SEQ.NEXTVAL INTO :NEW.ID FROM DUAL; 99 | ELSE 100 | SELECT NVL(Last_Number, 0) INTO last_Sequence 101 | FROM User_Sequences 102 | WHERE Sequence_Name = 'ACL_OBJECT_IDENTITIES_SEQ'; 103 | SELECT :NEW.ID INTO last_InsertID FROM DUAL; 104 | WHILE (last_InsertID > last_Sequence) LOOP 105 | SELECT ACL_OBJECT_IDENTITIES_SEQ.NEXTVAL INTO last_Sequence FROM DUAL; 106 | END LOOP; 107 | END IF; 108 | END; 109 | 110 | CREATE UNIQUE INDEX UNIQ_9407E5494B12AD6EA000B10 ON acl_object_identities (object_identifier, class_id) 111 | 112 | CREATE INDEX IDX_9407E54977FA751A ON acl_object_identities (parent_object_identity_id) 113 | 114 | CREATE TABLE acl_object_identity_ancestors (object_identity_id NUMBER(10) NOT NULL, ancestor_id NUMBER(10) NOT NULL, PRIMARY KEY(object_identity_id, ancestor_id)) 115 | 116 | CREATE INDEX IDX_825DE2993D9AB4A6 ON acl_object_identity_ancestors (object_identity_id) 117 | 118 | CREATE INDEX IDX_825DE299C671CEA1 ON acl_object_identity_ancestors (ancestor_id) 119 | 120 | CREATE TABLE acl_entries (id NUMBER(10) NOT NULL, class_id NUMBER(10) NOT NULL, object_identity_id NUMBER(10) DEFAULT NULL NULL, security_identity_id NUMBER(10) NOT NULL, field_name VARCHAR2(50) DEFAULT NULL NULL, ace_order NUMBER(5) NOT NULL, mask NUMBER(10) NOT NULL, granting NUMBER(1) NOT NULL, granting_strategy VARCHAR2(30) NOT NULL, audit_success NUMBER(1) NOT NULL, audit_failure NUMBER(1) NOT NULL, PRIMARY KEY(id)) 121 | 122 | DECLARE 123 | constraints_Count NUMBER; 124 | BEGIN 125 | SELECT COUNT(CONSTRAINT_NAME) INTO constraints_Count FROM USER_CONSTRAINTS WHERE TABLE_NAME = 'ACL_ENTRIES' AND CONSTRAINT_TYPE = 'P'; 126 | IF constraints_Count = 0 OR constraints_Count = '' THEN 127 | EXECUTE IMMEDIATE 'ALTER TABLE ACL_ENTRIES ADD CONSTRAINT ACL_ENTRIES_AI_PK PRIMARY KEY (ID)'; 128 | END IF; 129 | END; 130 | 131 | CREATE SEQUENCE ACL_ENTRIES_SEQ START WITH 1 MINVALUE 1 INCREMENT BY 1 132 | 133 | CREATE TRIGGER ACL_ENTRIES_AI_PK 134 | BEFORE INSERT 135 | ON ACL_ENTRIES 136 | FOR EACH ROW 137 | DECLARE 138 | last_Sequence NUMBER; 139 | last_InsertID NUMBER; 140 | BEGIN 141 | SELECT ACL_ENTRIES_SEQ.NEXTVAL INTO :NEW.ID FROM DUAL; 142 | IF (:NEW.ID IS NULL OR :NEW.ID = 0) THEN 143 | SELECT ACL_ENTRIES_SEQ.NEXTVAL INTO :NEW.ID FROM DUAL; 144 | ELSE 145 | SELECT NVL(Last_Number, 0) INTO last_Sequence 146 | FROM User_Sequences 147 | WHERE Sequence_Name = 'ACL_ENTRIES_SEQ'; 148 | SELECT :NEW.ID INTO last_InsertID FROM DUAL; 149 | WHILE (last_InsertID > last_Sequence) LOOP 150 | SELECT ACL_ENTRIES_SEQ.NEXTVAL INTO last_Sequence FROM DUAL; 151 | END LOOP; 152 | END IF; 153 | END; 154 | 155 | CREATE UNIQUE INDEX UNIQ_46C8B806EA000B103D9AB4A64DEF17BCE4289BF4 ON acl_entries (class_id, object_identity_id, field_name, ace_order) 156 | 157 | CREATE INDEX IDX_46C8B806EA000B103D9AB4A6DF9183C9 ON acl_entries (class_id, object_identity_id, security_identity_id) 158 | 159 | CREATE INDEX IDX_46C8B806EA000B10 ON acl_entries (class_id) 160 | 161 | CREATE INDEX IDX_46C8B8063D9AB4A6 ON acl_entries (object_identity_id) 162 | 163 | CREATE INDEX IDX_46C8B806DF9183C9 ON acl_entries (security_identity_id) 164 | 165 | ALTER TABLE acl_object_identities ADD CONSTRAINT FK_9407E54977FA751A FOREIGN KEY (parent_object_identity_id) REFERENCES acl_object_identities (id) 166 | 167 | ALTER TABLE acl_object_identity_ancestors ADD CONSTRAINT FK_825DE2993D9AB4A6 FOREIGN KEY (object_identity_id) REFERENCES acl_object_identities (id) ON DELETE CASCADE 168 | 169 | ALTER TABLE acl_object_identity_ancestors ADD CONSTRAINT FK_825DE299C671CEA1 FOREIGN KEY (ancestor_id) REFERENCES acl_object_identities (id) ON DELETE CASCADE 170 | 171 | ALTER TABLE acl_entries ADD CONSTRAINT FK_46C8B806EA000B10 FOREIGN KEY (class_id) REFERENCES acl_classes (id) ON DELETE CASCADE 172 | 173 | ALTER TABLE acl_entries ADD CONSTRAINT FK_46C8B8063D9AB4A6 FOREIGN KEY (object_identity_id) REFERENCES acl_object_identities (id) ON DELETE CASCADE 174 | 175 | ALTER TABLE acl_entries ADD CONSTRAINT FK_46C8B806DF9183C9 FOREIGN KEY (security_identity_id) REFERENCES acl_security_identities (id) ON DELETE CASCADE -------------------------------------------------------------------------------- /Resources/schema/postgresql.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE acl_classes (id SERIAL NOT NULL, class_type VARCHAR(200) NOT NULL, PRIMARY KEY(id)) 2 | 3 | CREATE UNIQUE INDEX UNIQ_69DD750638A36066 ON acl_classes (class_type) 4 | 5 | CREATE TABLE acl_security_identities (id SERIAL NOT NULL, identifier VARCHAR(200) NOT NULL, username BOOLEAN NOT NULL, PRIMARY KEY(id)) 6 | 7 | CREATE UNIQUE INDEX UNIQ_8835EE78772E836AF85E0677 ON acl_security_identities (identifier, username) 8 | 9 | CREATE TABLE acl_object_identities (id SERIAL NOT NULL, parent_object_identity_id INT DEFAULT NULL, class_id INT NOT NULL, object_identifier VARCHAR(100) NOT NULL, entries_inheriting BOOLEAN NOT NULL, PRIMARY KEY(id)) 10 | 11 | CREATE UNIQUE INDEX UNIQ_9407E5494B12AD6EA000B10 ON acl_object_identities (object_identifier, class_id) 12 | 13 | CREATE INDEX IDX_9407E54977FA751A ON acl_object_identities (parent_object_identity_id) 14 | 15 | CREATE TABLE acl_object_identity_ancestors (object_identity_id INT NOT NULL, ancestor_id INT NOT NULL, PRIMARY KEY(object_identity_id, ancestor_id)) 16 | 17 | CREATE INDEX IDX_825DE2993D9AB4A6 ON acl_object_identity_ancestors (object_identity_id) 18 | 19 | CREATE INDEX IDX_825DE299C671CEA1 ON acl_object_identity_ancestors (ancestor_id) 20 | 21 | CREATE TABLE acl_entries (id SERIAL NOT NULL, class_id INT NOT NULL, object_identity_id INT DEFAULT NULL, security_identity_id INT NOT NULL, field_name VARCHAR(50) DEFAULT NULL, ace_order SMALLINT NOT NULL, mask INT NOT NULL, granting BOOLEAN NOT NULL, granting_strategy VARCHAR(30) NOT NULL, audit_success BOOLEAN NOT NULL, audit_failure BOOLEAN NOT NULL, PRIMARY KEY(id)) 22 | 23 | CREATE UNIQUE INDEX UNIQ_46C8B806EA000B103D9AB4A64DEF17BCE4289BF4 ON acl_entries (class_id, object_identity_id, field_name, ace_order) 24 | 25 | CREATE INDEX IDX_46C8B806EA000B103D9AB4A6DF9183C9 ON acl_entries (class_id, object_identity_id, security_identity_id) 26 | 27 | CREATE INDEX IDX_46C8B806EA000B10 ON acl_entries (class_id) 28 | 29 | CREATE INDEX IDX_46C8B8063D9AB4A6 ON acl_entries (object_identity_id) 30 | 31 | CREATE INDEX IDX_46C8B806DF9183C9 ON acl_entries (security_identity_id) 32 | 33 | ALTER TABLE acl_object_identities ADD CONSTRAINT FK_9407E54977FA751A FOREIGN KEY (parent_object_identity_id) REFERENCES acl_object_identities (id) NOT DEFERRABLE INITIALLY IMMEDIATE 34 | 35 | ALTER TABLE acl_object_identity_ancestors ADD CONSTRAINT FK_825DE2993D9AB4A6 FOREIGN KEY (object_identity_id) REFERENCES acl_object_identities (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE 36 | 37 | ALTER TABLE acl_object_identity_ancestors ADD CONSTRAINT FK_825DE299C671CEA1 FOREIGN KEY (ancestor_id) REFERENCES acl_object_identities (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE 38 | 39 | ALTER TABLE acl_entries ADD CONSTRAINT FK_46C8B806EA000B10 FOREIGN KEY (class_id) REFERENCES acl_classes (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE 40 | 41 | ALTER TABLE acl_entries ADD CONSTRAINT FK_46C8B8063D9AB4A6 FOREIGN KEY (object_identity_id) REFERENCES acl_object_identities (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE 42 | 43 | ALTER TABLE acl_entries ADD CONSTRAINT FK_46C8B806DF9183C9 FOREIGN KEY (security_identity_id) REFERENCES acl_security_identities (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE -------------------------------------------------------------------------------- /Resources/schema/sqlanywhere.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE acl_classes (id UNSIGNED INT IDENTITY NOT NULL, class_type VARCHAR(200) NOT NULL, PRIMARY KEY (id)) 2 | 3 | CREATE UNIQUE INDEX UNIQ_69DD750638A36066 ON acl_classes (class_type) 4 | 5 | CREATE TABLE acl_security_identities (id UNSIGNED INT IDENTITY NOT NULL, identifier VARCHAR(200) NOT NULL, username BIT NOT NULL, PRIMARY KEY (id)) 6 | 7 | CREATE UNIQUE INDEX UNIQ_8835EE78772E836AF85E0677 ON acl_security_identities (identifier, username) 8 | 9 | CREATE TABLE acl_object_identities (id UNSIGNED INT IDENTITY NOT NULL, parent_object_identity_id UNSIGNED INT DEFAULT NULL, class_id UNSIGNED INT NOT NULL, object_identifier VARCHAR(100) NOT NULL, entries_inheriting BIT NOT NULL, PRIMARY KEY (id)) 10 | 11 | CREATE UNIQUE INDEX UNIQ_9407E5494B12AD6EA000B10 ON acl_object_identities (object_identifier, class_id) 12 | 13 | CREATE INDEX IDX_9407E54977FA751A ON acl_object_identities (parent_object_identity_id) 14 | 15 | CREATE TABLE acl_object_identity_ancestors (object_identity_id UNSIGNED INT NOT NULL, ancestor_id UNSIGNED INT NOT NULL, PRIMARY KEY (object_identity_id, ancestor_id)) 16 | 17 | CREATE INDEX IDX_825DE2993D9AB4A6 ON acl_object_identity_ancestors (object_identity_id) 18 | 19 | CREATE INDEX IDX_825DE299C671CEA1 ON acl_object_identity_ancestors (ancestor_id) 20 | 21 | CREATE TABLE acl_entries (id UNSIGNED INT IDENTITY NOT NULL, class_id UNSIGNED INT NOT NULL, object_identity_id UNSIGNED INT DEFAULT NULL, security_identity_id UNSIGNED INT NOT NULL, field_name VARCHAR(50) DEFAULT NULL, ace_order UNSIGNED SMALLINT NOT NULL, mask INT NOT NULL, granting BIT NOT NULL, granting_strategy VARCHAR(30) NOT NULL, audit_success BIT NOT NULL, audit_failure BIT NOT NULL, PRIMARY KEY (id)) 22 | 23 | CREATE UNIQUE INDEX UNIQ_46C8B806EA000B103D9AB4A64DEF17BCE4289BF4 ON acl_entries (class_id, object_identity_id, field_name, ace_order) 24 | 25 | CREATE INDEX IDX_46C8B806EA000B103D9AB4A6DF9183C9 ON acl_entries (class_id, object_identity_id, security_identity_id) 26 | 27 | CREATE INDEX IDX_46C8B806EA000B10 ON acl_entries (class_id) 28 | 29 | CREATE INDEX IDX_46C8B8063D9AB4A6 ON acl_entries (object_identity_id) 30 | 31 | CREATE INDEX IDX_46C8B806DF9183C9 ON acl_entries (security_identity_id) 32 | 33 | ALTER TABLE acl_object_identities ADD CONSTRAINT FK_9407E54977FA751A FOREIGN KEY (parent_object_identity_id) REFERENCES acl_object_identities (id) 34 | 35 | ALTER TABLE acl_object_identity_ancestors ADD CONSTRAINT FK_825DE2993D9AB4A6 FOREIGN KEY (object_identity_id) REFERENCES acl_object_identities (id) ON UPDATE CASCADE ON DELETE CASCADE 36 | 37 | ALTER TABLE acl_object_identity_ancestors ADD CONSTRAINT FK_825DE299C671CEA1 FOREIGN KEY (ancestor_id) REFERENCES acl_object_identities (id) ON UPDATE CASCADE ON DELETE CASCADE 38 | 39 | ALTER TABLE acl_entries ADD CONSTRAINT FK_46C8B806EA000B10 FOREIGN KEY (class_id) REFERENCES acl_classes (id) ON UPDATE CASCADE ON DELETE CASCADE 40 | 41 | ALTER TABLE acl_entries ADD CONSTRAINT FK_46C8B8063D9AB4A6 FOREIGN KEY (object_identity_id) REFERENCES acl_object_identities (id) ON UPDATE CASCADE ON DELETE CASCADE 42 | 43 | ALTER TABLE acl_entries ADD CONSTRAINT FK_46C8B806DF9183C9 FOREIGN KEY (security_identity_id) REFERENCES acl_security_identities (id) ON UPDATE CASCADE ON DELETE CASCADE -------------------------------------------------------------------------------- /Resources/schema/sqlite.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE acl_classes (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, class_type VARCHAR(200) NOT NULL) 2 | 3 | CREATE UNIQUE INDEX UNIQ_69DD750638A36066 ON acl_classes (class_type) 4 | 5 | CREATE TABLE acl_security_identities (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, identifier VARCHAR(200) NOT NULL, username BOOLEAN NOT NULL) 6 | 7 | CREATE UNIQUE INDEX UNIQ_8835EE78772E836AF85E0677 ON acl_security_identities (identifier, username) 8 | 9 | CREATE TABLE acl_object_identities (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_object_identity_id INTEGER UNSIGNED DEFAULT NULL, class_id INTEGER UNSIGNED NOT NULL, object_identifier VARCHAR(100) NOT NULL, entries_inheriting BOOLEAN NOT NULL, CONSTRAINT FK_9407E54977FA751A FOREIGN KEY (parent_object_identity_id) REFERENCES acl_object_identities (id) NOT DEFERRABLE INITIALLY IMMEDIATE) 10 | 11 | CREATE UNIQUE INDEX UNIQ_9407E5494B12AD6EA000B10 ON acl_object_identities (object_identifier, class_id) 12 | 13 | CREATE INDEX IDX_9407E54977FA751A ON acl_object_identities (parent_object_identity_id) 14 | 15 | CREATE TABLE acl_object_identity_ancestors (object_identity_id INTEGER UNSIGNED NOT NULL, ancestor_id INTEGER UNSIGNED NOT NULL, PRIMARY KEY(object_identity_id, ancestor_id), CONSTRAINT FK_825DE2993D9AB4A6 FOREIGN KEY (object_identity_id) REFERENCES acl_object_identities (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_825DE299C671CEA1 FOREIGN KEY (ancestor_id) REFERENCES acl_object_identities (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE) 16 | 17 | CREATE INDEX IDX_825DE2993D9AB4A6 ON acl_object_identity_ancestors (object_identity_id) 18 | 19 | CREATE INDEX IDX_825DE299C671CEA1 ON acl_object_identity_ancestors (ancestor_id) 20 | 21 | CREATE TABLE acl_entries (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, class_id INTEGER UNSIGNED NOT NULL, object_identity_id INTEGER UNSIGNED DEFAULT NULL, security_identity_id INTEGER UNSIGNED NOT NULL, field_name VARCHAR(50) DEFAULT NULL, ace_order SMALLINT UNSIGNED NOT NULL, mask INTEGER NOT NULL, granting BOOLEAN NOT NULL, granting_strategy VARCHAR(30) NOT NULL, audit_success BOOLEAN NOT NULL, audit_failure BOOLEAN NOT NULL, CONSTRAINT FK_46C8B806EA000B10 FOREIGN KEY (class_id) REFERENCES acl_classes (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_46C8B8063D9AB4A6 FOREIGN KEY (object_identity_id) REFERENCES acl_object_identities (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_46C8B806DF9183C9 FOREIGN KEY (security_identity_id) REFERENCES acl_security_identities (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE) 22 | 23 | CREATE UNIQUE INDEX UNIQ_46C8B806EA000B103D9AB4A64DEF17BCE4289BF4 ON acl_entries (class_id, object_identity_id, field_name, ace_order) 24 | 25 | CREATE INDEX IDX_46C8B806EA000B103D9AB4A6DF9183C9 ON acl_entries (class_id, object_identity_id, security_identity_id) 26 | 27 | CREATE INDEX IDX_46C8B806EA000B10 ON acl_entries (class_id) 28 | 29 | CREATE INDEX IDX_46C8B8063D9AB4A6 ON acl_entries (object_identity_id) 30 | 31 | CREATE INDEX IDX_46C8B806DF9183C9 ON acl_entries (security_identity_id) -------------------------------------------------------------------------------- /Tests/Dbal/AclProviderBenchmarkTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Tests\Dbal; 13 | 14 | use Doctrine\DBAL\Connection; 15 | use Doctrine\DBAL\DriverManager; 16 | use PHPUnit\Framework\TestCase; 17 | use Symfony\Component\Security\Acl\Dbal\AclProvider; 18 | use Symfony\Component\Security\Acl\Dbal\Schema; 19 | use Symfony\Component\Security\Acl\Domain\ObjectIdentity; 20 | use Symfony\Component\Security\Acl\Domain\PermissionGrantingStrategy; 21 | 22 | /** 23 | * @group benchmark 24 | */ 25 | class AclProviderBenchmarkTest extends TestCase 26 | { 27 | /** @var Connection */ 28 | protected $connection; 29 | protected $insertClassStmt; 30 | protected $insertSidStmt; 31 | protected $insertOidAncestorStmt; 32 | protected $insertOidStmt; 33 | protected $insertEntryStmt; 34 | 35 | protected function setUp(): void 36 | { 37 | try { 38 | $this->connection = DriverManager::getConnection([ 39 | 'driver' => 'pdo_mysql', 40 | 'host' => 'localhost', 41 | 'user' => 'root', 42 | 'dbname' => 'testdb', 43 | ]); 44 | $this->connection->connect(); 45 | } catch (\Exception $e) { 46 | $this->markTestSkipped('Unable to connect to the database: '.$e->getMessage()); 47 | } 48 | } 49 | 50 | protected function tearDown(): void 51 | { 52 | $this->connection = null; 53 | } 54 | 55 | public function testFindAcls() 56 | { 57 | // $this->generateTestData(); 58 | 59 | // get some random test object identities from the database 60 | $oids = []; 61 | $stmt = $this->connection->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'); 62 | foreach ($stmt->fetchAllAssociative() as $oid) { 63 | $oids[] = new ObjectIdentity($oid['object_identifier'], $oid['class_type']); 64 | } 65 | 66 | $provider = $this->getProvider(); 67 | 68 | $start = microtime(true); 69 | $provider->findAcls($oids); 70 | $time = microtime(true) - $start; 71 | echo 'Total Time: '.$time."s\n"; 72 | } 73 | 74 | /** 75 | * This generates a huge amount of test data to be used mainly for benchmarking 76 | * purposes, not so much for testing. That's why it's not called by default. 77 | */ 78 | protected function generateTestData() 79 | { 80 | $sm = $this->connection->createSchemaManager(); 81 | $sm->dropDatabase('testdb'); 82 | $sm->createDatabase('testdb'); 83 | $this->connection->executeStatement('USE testdb'); 84 | 85 | // import the schema 86 | $schema = new Schema($options = $this->getOptions()); 87 | foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) { 88 | $this->connection->executeStatement($sql); 89 | } 90 | 91 | // setup prepared statements 92 | $this->insertClassStmt = $this->connection->prepare('INSERT INTO acl_classes (id, class_type) VALUES (?, ?)'); 93 | $this->insertSidStmt = $this->connection->prepare('INSERT INTO acl_security_identities (id, identifier, username) VALUES (?, ?, ?)'); 94 | $this->insertOidStmt = $this->connection->prepare('INSERT INTO acl_object_identities (id, class_id, object_identifier, parent_object_identity_id, entries_inheriting) VALUES (?, ?, ?, ?, ?)'); 95 | $this->insertEntryStmt = $this->connection->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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'); 96 | $this->insertOidAncestorStmt = $this->connection->prepare('INSERT INTO acl_object_identity_ancestors (object_identity_id, ancestor_id) VALUES (?, ?)'); 97 | 98 | for ($i = 0; $i < 40000; ++$i) { 99 | $this->generateAclHierarchy(); 100 | } 101 | } 102 | 103 | protected function generateAclHierarchy() 104 | { 105 | $rootId = $this->generateAcl($this->chooseClassId(), null, []); 106 | 107 | $this->generateAclLevel(rand(1, 15), $rootId, [$rootId]); 108 | } 109 | 110 | protected function generateAclLevel($depth, $parentId, $ancestors) 111 | { 112 | $level = \count($ancestors); 113 | for ($i = 0, $t = rand(1, 10); $i < $t; ++$i) { 114 | $id = $this->generateAcl($this->chooseClassId(), $parentId, $ancestors); 115 | 116 | if ($level < $depth) { 117 | $this->generateAclLevel($depth, $id, array_merge($ancestors, [$id])); 118 | } 119 | } 120 | } 121 | 122 | protected function chooseClassId() 123 | { 124 | static $id = 1000; 125 | 126 | if (1000 === $id || ($id < 1500 && rand(0, 1))) { 127 | $this->insertClassStmt->executeStatement([$id, $this->getRandomString(rand(20, 100), 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\\_')]); 128 | ++$id; 129 | 130 | return $id - 1; 131 | } else { 132 | return rand(1000, $id - 1); 133 | } 134 | } 135 | 136 | protected function generateAcl($classId, $parentId, $ancestors) 137 | { 138 | static $id = 1000; 139 | 140 | $this->insertOidStmt->executeStatement([ 141 | $id, 142 | $classId, 143 | $this->getRandomString(rand(20, 50)), 144 | $parentId, 145 | rand(0, 1), 146 | ]); 147 | 148 | $this->insertOidAncestorStmt->executeStatement([$id, $id]); 149 | foreach ($ancestors as $ancestor) { 150 | $this->insertOidAncestorStmt->executeStatement([$id, $ancestor]); 151 | } 152 | 153 | $this->generateAces($classId, $id); 154 | ++$id; 155 | 156 | return $id - 1; 157 | } 158 | 159 | protected function chooseSid() 160 | { 161 | static $id = 1000; 162 | 163 | if (1000 === $id || ($id < 11000 && rand(0, 1))) { 164 | $this->insertSidStmt->executeStatement([ 165 | $id, 166 | $this->getRandomString(rand(5, 30)), 167 | rand(0, 1), 168 | ]); 169 | ++$id; 170 | 171 | return $id - 1; 172 | } else { 173 | return rand(1000, $id - 1); 174 | } 175 | } 176 | 177 | protected function generateAces($classId, $objectId) 178 | { 179 | static $id = 1000; 180 | 181 | $sids = []; 182 | $fieldOrder = []; 183 | 184 | for ($i = 0; $i <= 30; ++$i) { 185 | $fieldName = rand(0, 1) ? null : $this->getRandomString(rand(10, 20)); 186 | 187 | do { 188 | $sid = $this->chooseSid(); 189 | } while (\array_key_exists($sid, $sids) && \in_array($fieldName, $sids[$sid], true)); 190 | 191 | $fieldOrder[$fieldName] = \array_key_exists($fieldName, $fieldOrder) ? $fieldOrder[$fieldName] + 1 : 0; 192 | if (!isset($sids[$sid])) { 193 | $sids[$sid] = []; 194 | } 195 | $sids[$sid][] = $fieldName; 196 | 197 | $strategy = rand(0, 2); 198 | if (0 === $strategy) { 199 | $strategy = PermissionGrantingStrategy::ALL; 200 | } elseif (1 === $strategy) { 201 | $strategy = PermissionGrantingStrategy::ANY; 202 | } else { 203 | $strategy = PermissionGrantingStrategy::EQUAL; 204 | } 205 | 206 | // id, cid, oid, field, order, sid, mask, granting, strategy, a success, a failure 207 | $this->insertEntryStmt->executeStatement([ 208 | $id, 209 | $classId, 210 | rand(0, 5) ? $objectId : null, 211 | $fieldName, 212 | $fieldOrder[$fieldName], 213 | $sid, 214 | $this->generateMask(), 215 | rand(0, 1), 216 | $strategy, 217 | rand(0, 1), 218 | rand(0, 1), 219 | ]); 220 | 221 | ++$id; 222 | } 223 | } 224 | 225 | protected function generateMask() 226 | { 227 | $i = rand(1, 30); 228 | $mask = 0; 229 | 230 | while ($i <= 30) { 231 | $mask |= 1 << rand(0, 30); 232 | ++$i; 233 | } 234 | 235 | return $mask; 236 | } 237 | 238 | protected function getRandomString($length, $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') 239 | { 240 | $s = ''; 241 | $cLength = \strlen($chars); 242 | 243 | while (\strlen($s) < $length) { 244 | $s .= $chars[mt_rand(0, $cLength - 1)]; 245 | } 246 | 247 | return $s; 248 | } 249 | 250 | protected function getOptions() 251 | { 252 | return [ 253 | 'oid_table_name' => 'acl_object_identities', 254 | 'oid_ancestors_table_name' => 'acl_object_identity_ancestors', 255 | 'class_table_name' => 'acl_classes', 256 | 'sid_table_name' => 'acl_security_identities', 257 | 'entry_table_name' => 'acl_entries', 258 | ]; 259 | } 260 | 261 | protected function getStrategy() 262 | { 263 | return new PermissionGrantingStrategy(); 264 | } 265 | 266 | protected function getProvider() 267 | { 268 | return new AclProvider($this->connection, $this->getStrategy(), $this->getOptions()); 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /Tests/Domain/AuditLoggerTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Tests\Domain; 13 | 14 | use PHPUnit\Framework\TestCase; 15 | use Symfony\Component\Security\Acl\Domain\AuditLogger; 16 | use Symfony\Component\Security\Acl\Tests\Fixtures\SerializableAuditableEntryInterface; 17 | 18 | class AuditLoggerTest extends TestCase 19 | { 20 | /** 21 | * @dataProvider getTestLogData 22 | */ 23 | public function testLogIfNeeded($granting, $audit) 24 | { 25 | $logger = $this->getLogger(); 26 | $ace = $this->getEntry(); 27 | 28 | if (true === $granting) { 29 | $ace 30 | ->expects($this->once()) 31 | ->method('isAuditSuccess') 32 | ->willReturn($audit) 33 | ; 34 | 35 | $ace 36 | ->expects($this->never()) 37 | ->method('isAuditFailure') 38 | ; 39 | } else { 40 | $ace 41 | ->expects($this->never()) 42 | ->method('isAuditSuccess') 43 | ; 44 | 45 | $ace 46 | ->expects($this->once()) 47 | ->method('isAuditFailure') 48 | ->willReturn($audit) 49 | ; 50 | } 51 | 52 | if (true === $audit) { 53 | $logger 54 | ->expects($this->once()) 55 | ->method('doLog') 56 | ->with($this->equalTo($granting), $this->equalTo($ace)) 57 | ; 58 | } else { 59 | $logger 60 | ->expects($this->never()) 61 | ->method('doLog') 62 | ; 63 | } 64 | 65 | $logger->logIfNeeded($granting, $ace); 66 | } 67 | 68 | public function getTestLogData() 69 | { 70 | return [ 71 | [true, false], 72 | [true, true], 73 | [false, false], 74 | [false, true], 75 | ]; 76 | } 77 | 78 | protected function getEntry() 79 | { 80 | return $this->createMock(SerializableAuditableEntryInterface::class); 81 | } 82 | 83 | protected function getLogger() 84 | { 85 | return $this->getMockForAbstractClass(AuditLogger::class); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Tests/Domain/DoctrineAclCacheTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Tests\Domain; 13 | 14 | use Doctrine\Common\Cache\Psr6\DoctrineProvider; 15 | use PHPUnit\Framework\TestCase; 16 | use Symfony\Component\Cache\Adapter\ArrayAdapter; 17 | use Symfony\Component\Security\Acl\Domain\Acl; 18 | use Symfony\Component\Security\Acl\Domain\DoctrineAclCache; 19 | use Symfony\Component\Security\Acl\Domain\ObjectIdentity; 20 | use Symfony\Component\Security\Acl\Domain\PermissionGrantingStrategy; 21 | use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity; 22 | 23 | class DoctrineAclCacheTest extends TestCase 24 | { 25 | protected $permissionGrantingStrategy; 26 | 27 | /** 28 | * @dataProvider getEmptyValue 29 | */ 30 | public function testConstructorDoesNotAcceptEmptyPrefix($empty) 31 | { 32 | $this->expectException(\InvalidArgumentException::class); 33 | 34 | new DoctrineAclCache(DoctrineProvider::wrap(new ArrayAdapter()), $this->getPermissionGrantingStrategy(), $empty); 35 | } 36 | 37 | public function getEmptyValue() 38 | { 39 | return [ 40 | [null], 41 | [false], 42 | [''], 43 | ]; 44 | } 45 | 46 | public function test() 47 | { 48 | $cache = $this->getCache(); 49 | 50 | $aclWithParent = $this->getAcl(1); 51 | $acl = $this->getAcl(); 52 | 53 | $cache->putInCache($aclWithParent); 54 | $cache->putInCache($acl); 55 | 56 | $cachedAcl = $cache->getFromCacheByIdentity($acl->getObjectIdentity()); 57 | $this->assertEquals($acl->getId(), $cachedAcl->getId()); 58 | $this->assertNull($acl->getParentAcl()); 59 | 60 | $cachedAclWithParent = $cache->getFromCacheByIdentity($aclWithParent->getObjectIdentity()); 61 | $this->assertEquals($aclWithParent->getId(), $cachedAclWithParent->getId()); 62 | $this->assertNotNull($cachedParentAcl = $cachedAclWithParent->getParentAcl()); 63 | $this->assertEquals($aclWithParent->getParentAcl()->getId(), $cachedParentAcl->getId()); 64 | } 65 | 66 | protected function getAcl($depth = 0) 67 | { 68 | static $id = 1; 69 | 70 | $acl = new Acl($id, new ObjectIdentity($id, 'foo'), $this->getPermissionGrantingStrategy(), [], $depth > 0); 71 | 72 | // insert some ACEs 73 | $sid = new UserSecurityIdentity('johannes', 'Foo'); 74 | $acl->insertClassAce($sid, 1); 75 | $acl->insertClassFieldAce('foo', $sid, 1); 76 | $acl->insertObjectAce($sid, 1); 77 | $acl->insertObjectFieldAce('foo', $sid, 1); 78 | ++$id; 79 | 80 | if ($depth > 0) { 81 | $acl->setParentAcl($this->getAcl($depth - 1)); 82 | } 83 | 84 | return $acl; 85 | } 86 | 87 | protected function getPermissionGrantingStrategy() 88 | { 89 | if (null === $this->permissionGrantingStrategy) { 90 | $this->permissionGrantingStrategy = new PermissionGrantingStrategy(); 91 | } 92 | 93 | return $this->permissionGrantingStrategy; 94 | } 95 | 96 | protected function getCache($cacheDriver = null, $prefix = DoctrineAclCache::PREFIX) 97 | { 98 | if (null === $cacheDriver) { 99 | $cacheDriver = DoctrineProvider::wrap(new ArrayAdapter()); 100 | } 101 | 102 | return new DoctrineAclCache($cacheDriver, $this->getPermissionGrantingStrategy(), $prefix); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Tests/Domain/EntryTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Tests\Domain; 13 | 14 | use PHPUnit\Framework\TestCase; 15 | use Symfony\Component\Security\Acl\Domain\Entry; 16 | use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface; 17 | use Symfony\Component\Security\Acl\Tests\Fixtures\SerializableAclInterface; 18 | 19 | class EntryTest extends TestCase 20 | { 21 | public function testConstructor() 22 | { 23 | $ace = $this->getAce($acl = $this->getAcl(), $sid = $this->getSid()); 24 | 25 | $this->assertEquals(123, $ace->getId()); 26 | $this->assertSame($acl, $ace->getAcl()); 27 | $this->assertSame($sid, $ace->getSecurityIdentity()); 28 | $this->assertEquals('foostrat', $ace->getStrategy()); 29 | $this->assertEquals(123456, $ace->getMask()); 30 | $this->assertTrue($ace->isGranting()); 31 | $this->assertTrue($ace->isAuditSuccess()); 32 | $this->assertFalse($ace->isAuditFailure()); 33 | } 34 | 35 | public function testSetAuditSuccess() 36 | { 37 | $ace = $this->getAce(); 38 | 39 | $this->assertTrue($ace->isAuditSuccess()); 40 | $ace->setAuditSuccess(false); 41 | $this->assertFalse($ace->isAuditSuccess()); 42 | $ace->setAuditSuccess(true); 43 | $this->assertTrue($ace->isAuditSuccess()); 44 | } 45 | 46 | public function testSetAuditFailure() 47 | { 48 | $ace = $this->getAce(); 49 | 50 | $this->assertFalse($ace->isAuditFailure()); 51 | $ace->setAuditFailure(true); 52 | $this->assertTrue($ace->isAuditFailure()); 53 | $ace->setAuditFailure(false); 54 | $this->assertFalse($ace->isAuditFailure()); 55 | } 56 | 57 | public function testSetMask() 58 | { 59 | $ace = $this->getAce(); 60 | 61 | $this->assertEquals(123456, $ace->getMask()); 62 | $ace->setMask(4321); 63 | $this->assertEquals(4321, $ace->getMask()); 64 | } 65 | 66 | public function testSetStrategy() 67 | { 68 | $ace = $this->getAce(); 69 | 70 | $this->assertEquals('foostrat', $ace->getStrategy()); 71 | $ace->setStrategy('foo'); 72 | $this->assertEquals('foo', $ace->getStrategy()); 73 | } 74 | 75 | public function testSerializeUnserialize() 76 | { 77 | $ace = $this->getAce(); 78 | 79 | $serialized = serialize($ace); 80 | $uAce = unserialize($serialized); 81 | 82 | $this->assertNull($uAce->getAcl()); 83 | $this->assertInstanceOf(SecurityIdentityInterface::class, $uAce->getSecurityIdentity()); 84 | $this->assertEquals($ace->getId(), $uAce->getId()); 85 | $this->assertEquals($ace->getMask(), $uAce->getMask()); 86 | $this->assertEquals($ace->getStrategy(), $uAce->getStrategy()); 87 | $this->assertEquals($ace->isGranting(), $uAce->isGranting()); 88 | $this->assertEquals($ace->isAuditSuccess(), $uAce->isAuditSuccess()); 89 | $this->assertEquals($ace->isAuditFailure(), $uAce->isAuditFailure()); 90 | } 91 | 92 | protected function getAce($acl = null, $sid = null) 93 | { 94 | if (null === $acl) { 95 | $acl = $this->getAcl(); 96 | } 97 | if (null === $sid) { 98 | $sid = $this->getSid(); 99 | } 100 | 101 | return new Entry( 102 | 123, 103 | $acl, 104 | $sid, 105 | 'foostrat', 106 | 123456, 107 | true, 108 | false, 109 | true 110 | ); 111 | } 112 | 113 | protected function getAcl() 114 | { 115 | return $this->createMock(SerializableAclInterface::class); 116 | } 117 | 118 | protected function getSid() 119 | { 120 | return $this->createMock(SecurityIdentityInterface::class); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Tests/Domain/FieldEntryTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Tests\Domain; 13 | 14 | use PHPUnit\Framework\TestCase; 15 | use Symfony\Component\Security\Acl\Domain\FieldEntry; 16 | use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface; 17 | use Symfony\Component\Security\Acl\Tests\Fixtures\SerializableAclInterface; 18 | 19 | class FieldEntryTest extends TestCase 20 | { 21 | public function testConstructor() 22 | { 23 | $ace = $this->getAce(); 24 | 25 | $this->assertEquals('foo', $ace->getField()); 26 | } 27 | 28 | public function testSerializeUnserialize() 29 | { 30 | $ace = $this->getAce(); 31 | 32 | $serialized = serialize($ace); 33 | $uAce = unserialize($serialized); 34 | 35 | $this->assertNull($uAce->getAcl()); 36 | $this->assertInstanceOf(SecurityIdentityInterface::class, $uAce->getSecurityIdentity()); 37 | $this->assertEquals($ace->getId(), $uAce->getId()); 38 | $this->assertEquals($ace->getField(), $uAce->getField()); 39 | $this->assertEquals($ace->getMask(), $uAce->getMask()); 40 | $this->assertEquals($ace->getStrategy(), $uAce->getStrategy()); 41 | $this->assertEquals($ace->isGranting(), $uAce->isGranting()); 42 | $this->assertEquals($ace->isAuditSuccess(), $uAce->isAuditSuccess()); 43 | $this->assertEquals($ace->isAuditFailure(), $uAce->isAuditFailure()); 44 | } 45 | 46 | public function testSerializeUnserializeMoreAceWithSameSecurityIdentity() 47 | { 48 | $sid = $this->getSid(); 49 | 50 | $aceFirst = $this->getAce(null, $sid); 51 | $aceSecond = $this->getAce(null, $sid); 52 | 53 | // as used in DoctrineAclCache::putInCache (line 142) 54 | $serialized = serialize([[ 55 | 'fieldOne' => [$aceFirst], 56 | 'fieldTwo' => [$aceSecond], 57 | ]]); 58 | 59 | $unserialized = unserialize($serialized); 60 | $uAceFirst = $unserialized[0]['fieldOne'][0]; 61 | $uAceSecond = $unserialized[0]['fieldTwo'][0]; 62 | 63 | $this->assertInstanceOf(SecurityIdentityInterface::class, $uAceFirst->getSecurityIdentity()); 64 | $this->assertInstanceOf(SecurityIdentityInterface::class, $uAceSecond->getSecurityIdentity()); 65 | } 66 | 67 | protected function getAce($acl = null, $sid = null) 68 | { 69 | if (null === $acl) { 70 | $acl = $this->getAcl(); 71 | } 72 | if (null === $sid) { 73 | $sid = $this->getSid(); 74 | } 75 | 76 | return new FieldEntry( 77 | 123, 78 | $acl, 79 | 'foo', 80 | $sid, 81 | 'foostrat', 82 | 123456, 83 | true, 84 | false, 85 | true 86 | ); 87 | } 88 | 89 | protected function getAcl() 90 | { 91 | return $this->createMock(SerializableAclInterface::class); 92 | } 93 | 94 | protected function getSid() 95 | { 96 | return $this->createMock(SecurityIdentityInterface::class); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Tests/Domain/ObjectIdentityRetrievalStrategyTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Tests\Domain; 13 | 14 | use Symfony\Component\Security\Acl\Domain\ObjectIdentityRetrievalStrategy; 15 | 16 | class ObjectIdentityRetrievalStrategyTest extends \PHPUnit\Framework\TestCase 17 | { 18 | public function testGetObjectIdentityReturnsNullForInvalidDomainObject() 19 | { 20 | $strategy = new ObjectIdentityRetrievalStrategy(); 21 | $this->assertNull($strategy->getObjectIdentity('foo')); 22 | } 23 | 24 | public function testGetObjectIdentity() 25 | { 26 | $strategy = new ObjectIdentityRetrievalStrategy(); 27 | $domainObject = new DomainObject(); 28 | $objectIdentity = $strategy->getObjectIdentity($domainObject); 29 | 30 | $this->assertEquals($domainObject->getId(), $objectIdentity->getIdentifier()); 31 | $this->assertEquals(\get_class($domainObject), $objectIdentity->getType()); 32 | } 33 | } 34 | 35 | class DomainObject 36 | { 37 | public function getId() 38 | { 39 | return 'foo'; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Tests/Domain/ObjectIdentityTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Tests\Domain 13 | { 14 | use Symfony\Component\Security\Acl\Domain\ObjectIdentity; 15 | use Symfony\Component\Security\Acl\Model\DomainObjectInterface; 16 | 17 | class ObjectIdentityTest extends \PHPUnit\Framework\TestCase 18 | { 19 | public function testConstructor() 20 | { 21 | $id = new ObjectIdentity('fooid', 'footype'); 22 | 23 | $this->assertEquals('fooid', $id->getIdentifier()); 24 | $this->assertEquals('footype', $id->getType()); 25 | } 26 | 27 | // Test that constructor never changes passed type, even with proxies 28 | public function testConstructorWithProxy() 29 | { 30 | $id = new ObjectIdentity('fooid', 'Acme\DemoBundle\Proxy\__CG__\Symfony\Component\Security\Acl\Tests\Domain\TestDomainObject'); 31 | 32 | $this->assertEquals('fooid', $id->getIdentifier()); 33 | $this->assertEquals('Acme\DemoBundle\Proxy\__CG__\Symfony\Component\Security\Acl\Tests\Domain\TestDomainObject', $id->getType()); 34 | } 35 | 36 | public function testFromDomainObjectPrefersInterfaceOverGetId() 37 | { 38 | $domainObject = new class() implements DomainObjectInterface { 39 | public function getObjectIdentifier() 40 | { 41 | return 'getObjectIdentifier()'; 42 | } 43 | 44 | public function getId() 45 | { 46 | return 'getId()'; 47 | } 48 | }; 49 | 50 | $id = ObjectIdentity::fromDomainObject($domainObject); 51 | $this->assertEquals('getObjectIdentifier()', $id->getIdentifier()); 52 | } 53 | 54 | public function testFromDomainObjectWithoutInterface() 55 | { 56 | $id = ObjectIdentity::fromDomainObject(new TestDomainObject()); 57 | $this->assertEquals('getId()', $id->getIdentifier()); 58 | $this->assertEquals(TestDomainObject::class, $id->getType()); 59 | } 60 | 61 | public function testFromDomainObjectWithProxy() 62 | { 63 | $id = ObjectIdentity::fromDomainObject(new \Acme\DemoBundle\Proxy\__CG__\Symfony\Component\Security\Acl\Tests\Domain\TestDomainObject()); 64 | $this->assertEquals('getId()', $id->getIdentifier()); 65 | $this->assertEquals(TestDomainObject::class, $id->getType()); 66 | } 67 | 68 | public function testFromDomainObjectWithoutInterfaceEnforcesStringIdentifier() 69 | { 70 | $domainObject = new TestDomainObject(); 71 | $domainObject->id = 1; 72 | $id = ObjectIdentity::fromDomainObject($domainObject); 73 | 74 | $this->assertSame('1', $id->getIdentifier()); 75 | $this->assertEquals(TestDomainObject::class, $id->getType()); 76 | } 77 | 78 | public function testFromDomainObjectWithoutInterfaceAllowsZeroAsIdentifier() 79 | { 80 | $domainObject = new TestDomainObject(); 81 | $domainObject->id = '0'; 82 | $id = ObjectIdentity::fromDomainObject($domainObject); 83 | 84 | $this->assertSame('0', $id->getIdentifier()); 85 | $this->assertEquals(TestDomainObject::class, $id->getType()); 86 | } 87 | 88 | /** 89 | * @dataProvider getCompareData 90 | */ 91 | public function testEquals($oid1, $oid2, $equal) 92 | { 93 | if ($equal) { 94 | $this->assertTrue($oid1->equals($oid2)); 95 | } else { 96 | $this->assertFalse($oid1->equals($oid2)); 97 | } 98 | } 99 | 100 | public function getCompareData() 101 | { 102 | return [ 103 | [new ObjectIdentity('123', 'foo'), new ObjectIdentity('123', 'foo'), true], 104 | [new ObjectIdentity('123', 'foo'), new ObjectIdentity(123, 'foo'), true], 105 | [new ObjectIdentity('1', 'foo'), new ObjectIdentity('2', 'foo'), false], 106 | [new ObjectIdentity('1', 'bla'), new ObjectIdentity('1', 'blub'), false], 107 | ]; 108 | } 109 | } 110 | 111 | class TestDomainObject 112 | { 113 | public $id = 'getId()'; 114 | 115 | public function getObjectIdentifier() 116 | { 117 | return 'getObjectIdentifier()'; 118 | } 119 | 120 | public function getId() 121 | { 122 | return $this->id; 123 | } 124 | } 125 | } 126 | 127 | namespace Acme\DemoBundle\Proxy\__CG__\Symfony\Component\Security\Acl\Tests\Domain 128 | { 129 | class TestDomainObject extends \Symfony\Component\Security\Acl\Tests\Domain\TestDomainObject 130 | { 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /Tests/Domain/PermissionGrantingStrategyTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Tests\Domain; 13 | 14 | use PHPUnit\Framework\TestCase; 15 | use Symfony\Component\Security\Acl\Domain\Acl; 16 | use Symfony\Component\Security\Acl\Domain\ObjectIdentity; 17 | use Symfony\Component\Security\Acl\Domain\PermissionGrantingStrategy; 18 | use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity; 19 | use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity; 20 | use Symfony\Component\Security\Acl\Exception\NoAceFoundException; 21 | use Symfony\Component\Security\Acl\Model\AuditLoggerInterface; 22 | 23 | class PermissionGrantingStrategyTest extends TestCase 24 | { 25 | public function testIsGrantedObjectAcesHavePriority() 26 | { 27 | $strategy = new PermissionGrantingStrategy(); 28 | $acl = $this->getAcl($strategy); 29 | $sid = new UserSecurityIdentity('johannes', 'Foo'); 30 | 31 | $acl->insertClassAce($sid, 1); 32 | $acl->insertObjectAce($sid, 1, 0, false); 33 | $this->assertFalse($strategy->isGranted($acl, [1], [$sid])); 34 | } 35 | 36 | public function testIsGrantedFallsBackToClassAcesIfNoApplicableObjectAceWasFound() 37 | { 38 | $strategy = new PermissionGrantingStrategy(); 39 | $acl = $this->getAcl($strategy); 40 | $sid = new UserSecurityIdentity('johannes', 'Foo'); 41 | 42 | $acl->insertClassAce($sid, 1); 43 | $this->assertTrue($strategy->isGranted($acl, [1], [$sid])); 44 | } 45 | 46 | public function testIsGrantedFavorsLocalAcesOverParentAclAces() 47 | { 48 | $strategy = new PermissionGrantingStrategy(); 49 | $sid = new UserSecurityIdentity('johannes', 'Foo'); 50 | 51 | $acl = $this->getAcl($strategy); 52 | $acl->insertClassAce($sid, 1); 53 | 54 | $parentAcl = $this->getAcl($strategy); 55 | $acl->setParentAcl($parentAcl); 56 | $parentAcl->insertClassAce($sid, 1, 0, false); 57 | 58 | $this->assertTrue($strategy->isGranted($acl, [1], [$sid])); 59 | } 60 | 61 | public function testIsGrantedFallsBackToParentAcesIfNoLocalAcesAreApplicable() 62 | { 63 | $strategy = new PermissionGrantingStrategy(); 64 | $sid = new UserSecurityIdentity('johannes', 'Foo'); 65 | $anotherSid = new UserSecurityIdentity('ROLE_USER', 'Foo'); 66 | 67 | $acl = $this->getAcl($strategy); 68 | $acl->insertClassAce($anotherSid, 1, 0, false); 69 | 70 | $parentAcl = $this->getAcl($strategy); 71 | $acl->setParentAcl($parentAcl); 72 | $parentAcl->insertClassAce($sid, 1); 73 | 74 | $this->assertTrue($strategy->isGranted($acl, [1], [$sid])); 75 | } 76 | 77 | public function testIsGrantedReturnsExceptionIfNoAceIsFound() 78 | { 79 | $strategy = new PermissionGrantingStrategy(); 80 | $acl = $this->getAcl($strategy); 81 | $sid = new UserSecurityIdentity('johannes', 'Foo'); 82 | 83 | $this->expectException(NoAceFoundException::class); 84 | $strategy->isGranted($acl, [1], [$sid]); 85 | } 86 | 87 | public function testIsGrantedFirstApplicableEntryMakesUltimateDecisionForPermissionIdentityCombination() 88 | { 89 | $strategy = new PermissionGrantingStrategy(); 90 | $acl = $this->getAcl($strategy); 91 | $sid = new UserSecurityIdentity('johannes', 'Foo'); 92 | $aSid = new RoleSecurityIdentity('ROLE_USER'); 93 | 94 | $acl->insertClassAce($aSid, 1); 95 | $acl->insertClassAce($sid, 1, 1, false); 96 | $acl->insertClassAce($sid, 1, 2); 97 | $this->assertFalse($strategy->isGranted($acl, [1], [$sid, $aSid])); 98 | 99 | $acl->insertObjectAce($sid, 1, 0, false); 100 | $acl->insertObjectAce($aSid, 1, 1); 101 | $this->assertFalse($strategy->isGranted($acl, [1], [$sid, $aSid])); 102 | } 103 | 104 | public function testIsGrantedCallsAuditLoggerOnGrant() 105 | { 106 | $strategy = new PermissionGrantingStrategy(); 107 | $acl = $this->getAcl($strategy); 108 | $sid = new UserSecurityIdentity('johannes', 'Foo'); 109 | 110 | $logger = $this->createMock(AuditLoggerInterface::class); 111 | $logger 112 | ->expects($this->once()) 113 | ->method('logIfNeeded') 114 | ; 115 | $strategy->setAuditLogger($logger); 116 | 117 | $acl->insertObjectAce($sid, 1); 118 | $acl->updateObjectAuditing(0, true, false); 119 | 120 | $this->assertTrue($strategy->isGranted($acl, [1], [$sid])); 121 | } 122 | 123 | public function testIsGrantedCallsAuditLoggerOnDeny() 124 | { 125 | $strategy = new PermissionGrantingStrategy(); 126 | $acl = $this->getAcl($strategy); 127 | $sid = new UserSecurityIdentity('johannes', 'Foo'); 128 | 129 | $logger = $this->createMock(AuditLoggerInterface::class); 130 | $logger 131 | ->expects($this->once()) 132 | ->method('logIfNeeded') 133 | ; 134 | $strategy->setAuditLogger($logger); 135 | 136 | $acl->insertObjectAce($sid, 1, 0, false); 137 | $acl->updateObjectAuditing(0, false, true); 138 | 139 | $this->assertFalse($strategy->isGranted($acl, [1], [$sid])); 140 | } 141 | 142 | /** 143 | * @dataProvider getAllStrategyTests 144 | */ 145 | public function testIsGrantedStrategies($maskStrategy, $aceMask, $requiredMask, $result) 146 | { 147 | $strategy = new PermissionGrantingStrategy(); 148 | $acl = $this->getAcl($strategy); 149 | $sid = new UserSecurityIdentity('johannes', 'Foo'); 150 | 151 | $acl->insertObjectAce($sid, $aceMask, 0, true, $maskStrategy); 152 | 153 | if (false === $result) { 154 | $this->expectException(NoAceFoundException::class); 155 | $this->expectExceptionMessage('No applicable ACE was found.'); 156 | 157 | $strategy->isGranted($acl, [$requiredMask], [$sid]); 158 | } else { 159 | $this->assertTrue($strategy->isGranted($acl, [$requiredMask], [$sid])); 160 | } 161 | } 162 | 163 | public function getAllStrategyTests() 164 | { 165 | return [ 166 | ['all', 1 << 0 | 1 << 1, 1 << 0, true], 167 | ['all', 1 << 0 | 1 << 1, 1 << 2, false], 168 | ['all', 1 << 0 | 1 << 10, 1 << 0 | 1 << 10, true], 169 | ['all', 1 << 0 | 1 << 1, 1 << 0 | 1 << 1 || 1 << 2, false], 170 | ['any', 1 << 0 | 1 << 1, 1 << 0, true], 171 | ['any', 1 << 0 | 1 << 1, 1 << 0 | 1 << 2, true], 172 | ['any', 1 << 0 | 1 << 1, 1 << 2, false], 173 | ['equal', 1 << 0 | 1 << 1, 1 << 0, false], 174 | ['equal', 1 << 0 | 1 << 1, 1 << 1, false], 175 | ['equal', 1 << 0 | 1 << 1, 1 << 0 | 1 << 1, true], 176 | ]; 177 | } 178 | 179 | protected function getAcl($strategy) 180 | { 181 | static $id = 1; 182 | 183 | return new Acl($id++, new ObjectIdentity(1, 'Foo'), $strategy, [], true); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /Tests/Domain/PsrAclCacheTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Tests\Domain; 13 | 14 | use PHPUnit\Framework\TestCase; 15 | use Symfony\Component\Cache\Adapter\ArrayAdapter; 16 | use Symfony\Component\Security\Acl\Domain\Acl; 17 | use Symfony\Component\Security\Acl\Domain\ObjectIdentity; 18 | use Symfony\Component\Security\Acl\Domain\PermissionGrantingStrategy; 19 | use Symfony\Component\Security\Acl\Domain\PsrAclCache; 20 | use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity; 21 | 22 | class PsrAclCacheTest extends TestCase 23 | { 24 | protected $permissionGrantingStrategy; 25 | 26 | public function testConstructorDoesNotAcceptEmptyPrefix() 27 | { 28 | $this->expectException(\InvalidArgumentException::class); 29 | 30 | new PsrAclCache(new ArrayAdapter(), $this->getPermissionGrantingStrategy(), ''); 31 | } 32 | 33 | public function test() 34 | { 35 | $cache = $this->getCache(); 36 | 37 | $aclWithParent = $this->getAcl(1); 38 | $acl = $this->getAcl(); 39 | 40 | $cache->putInCache($aclWithParent); 41 | $cache->putInCache($acl); 42 | 43 | $cachedAcl = $cache->getFromCacheByIdentity($acl->getObjectIdentity()); 44 | $this->assertEquals($acl->getId(), $cachedAcl->getId()); 45 | $this->assertNull($acl->getParentAcl()); 46 | 47 | $cachedAclWithParent = $cache->getFromCacheByIdentity($aclWithParent->getObjectIdentity()); 48 | $this->assertEquals($aclWithParent->getId(), $cachedAclWithParent->getId()); 49 | $this->assertNotNull($cachedParentAcl = $cachedAclWithParent->getParentAcl()); 50 | $this->assertEquals($aclWithParent->getParentAcl()->getId(), $cachedParentAcl->getId()); 51 | } 52 | 53 | protected function getAcl($depth = 0) 54 | { 55 | static $id = 1; 56 | 57 | $acl = new Acl($id, new ObjectIdentity($id, 'foo'), $this->getPermissionGrantingStrategy(), [], $depth > 0); 58 | 59 | // insert some ACEs 60 | $sid = new UserSecurityIdentity('johannes', 'Foo'); 61 | $acl->insertClassAce($sid, 1); 62 | $acl->insertClassFieldAce('foo', $sid, 1); 63 | $acl->insertObjectAce($sid, 1); 64 | $acl->insertObjectFieldAce('foo', $sid, 1); 65 | ++$id; 66 | 67 | if ($depth > 0) { 68 | $acl->setParentAcl($this->getAcl($depth - 1)); 69 | } 70 | 71 | return $acl; 72 | } 73 | 74 | protected function getPermissionGrantingStrategy() 75 | { 76 | if (null === $this->permissionGrantingStrategy) { 77 | $this->permissionGrantingStrategy = new PermissionGrantingStrategy(); 78 | } 79 | 80 | return $this->permissionGrantingStrategy; 81 | } 82 | 83 | protected function getCache($cacheDriver = null, $prefix = PsrAclCache::PREFIX) 84 | { 85 | if (null === $cacheDriver) { 86 | $cacheDriver = new ArrayAdapter(); 87 | } 88 | 89 | return new PsrAclCache($cacheDriver, $this->getPermissionGrantingStrategy(), $prefix); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Tests/Domain/RoleSecurityIdentityTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Tests\Domain; 13 | 14 | use PHPUnit\Framework\TestCase; 15 | use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity; 16 | use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity; 17 | use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface; 18 | 19 | class RoleSecurityIdentityTest extends TestCase 20 | { 21 | public function testConstructor() 22 | { 23 | $id = new RoleSecurityIdentity('ROLE_FOO'); 24 | 25 | $this->assertEquals('ROLE_FOO', $id->getRole()); 26 | } 27 | 28 | /** 29 | * @dataProvider getCompareData 30 | */ 31 | public function testEquals(RoleSecurityIdentity $id1, SecurityIdentityInterface $id2, bool $equal) 32 | { 33 | if ($equal) { 34 | $this->assertTrue($id1->equals($id2)); 35 | } else { 36 | $this->assertFalse($id1->equals($id2)); 37 | } 38 | } 39 | 40 | public function getCompareData(): array 41 | { 42 | return [ 43 | [new RoleSecurityIdentity('ROLE_FOO'), new RoleSecurityIdentity('ROLE_FOO'), true], 44 | [new RoleSecurityIdentity('ROLE_USER'), new RoleSecurityIdentity('ROLE_FOO'), false], 45 | [new RoleSecurityIdentity('ROLE_FOO'), new UserSecurityIdentity('ROLE_FOO', 'Foo'), false], 46 | ]; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Tests/Domain/SecurityIdentityRetrievalStrategyTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Tests\Domain; 13 | 14 | use PHPUnit\Framework\TestCase; 15 | use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity; 16 | use Symfony\Component\Security\Acl\Domain\SecurityIdentityRetrievalStrategy; 17 | use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity; 18 | use Symfony\Component\Security\Acl\Tests\Fixtures\Account; 19 | use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; 20 | use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; 21 | use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; 22 | use Symfony\Component\Security\Core\Authentication\Token\NullToken; 23 | use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter; 24 | use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; 25 | use Symfony\Component\Security\Core\User\UserInterface; 26 | 27 | class SecurityIdentityRetrievalStrategyTest extends TestCase 28 | { 29 | /** 30 | * @dataProvider getSecurityIdentityRetrievalTests 31 | */ 32 | public function testGetSecurityIdentities($user, array $roles, string $authenticationStatus, array $sids) 33 | { 34 | $token = class_exists(NullToken::class) ? new NullToken() : new AnonymousToken('', ''); 35 | if ('anonymous' !== $authenticationStatus) { 36 | $class = ''; 37 | if (\is_string($user)) { 38 | $class = 'MyCustomTokenImpl'; 39 | } 40 | 41 | $token = $this->getMockBuilder(AbstractToken::class) 42 | ->setMockClassName($class) 43 | ->getMock(); 44 | 45 | $token 46 | ->expects($this->once()) 47 | ->method('getRoleNames') 48 | ->willReturn(['foo']) 49 | ; 50 | 51 | $token 52 | ->expects($this->once()) 53 | ->method('getUser') 54 | ->willReturn($user) 55 | ; 56 | } 57 | 58 | $strategy = $this->getStrategy($roles, $authenticationStatus); 59 | $extractedSids = $strategy->getSecurityIdentities($token); 60 | 61 | foreach ($extractedSids as $index => $extractedSid) { 62 | if (!isset($sids[$index])) { 63 | $this->fail(sprintf('Expected SID at index %d, but there was none.', $index)); 64 | } 65 | 66 | if (false === $sids[$index]->equals($extractedSid)) { 67 | $this->fail(sprintf('Index: %d, expected SID "%s", but got "%s".', $index, $sids[$index], (string) $extractedSid)); 68 | } 69 | } 70 | } 71 | 72 | /** 73 | * @group legacy 74 | * 75 | * @dataProvider getDeprecatedSecurityIdentityRetrievalTests 76 | */ 77 | public function testDeprecatedGetSecurityIdentities($user, array $roles, string $authenticationStatus, array $sids) 78 | { 79 | if (method_exists(AuthenticationTrustResolverInterface::class, 'isAuthenticated')) { 80 | $this->markTestSkipped(); 81 | } 82 | 83 | if ('anonymous' === $authenticationStatus) { 84 | $token = $this->getMockBuilder(AnonymousToken::class) 85 | ->disableOriginalConstructor() 86 | ->getMock(); 87 | } else { 88 | $class = ''; 89 | if (\is_string($user)) { 90 | $class = 'MyCustomTokenImpl'; 91 | } 92 | 93 | $token = $this->getMockBuilder(AbstractToken::class) 94 | ->setMockClassName($class) 95 | ->getMock(); 96 | } 97 | 98 | $strategy = $this->getStrategy($roles, $authenticationStatus); 99 | 100 | $token 101 | ->expects($this->once()) 102 | ->method('getRoleNames') 103 | ->willReturn(['foo']) 104 | ; 105 | 106 | if ('anonymous' === $authenticationStatus) { 107 | $token 108 | ->expects($this->never()) 109 | ->method('getUser') 110 | ; 111 | } else { 112 | $token 113 | ->expects($this->once()) 114 | ->method('getUser') 115 | ->willReturn($user) 116 | ; 117 | } 118 | 119 | $extractedSids = $strategy->getSecurityIdentities($token); 120 | 121 | foreach ($extractedSids as $index => $extractedSid) { 122 | if (!isset($sids[$index])) { 123 | $this->fail(sprintf('Expected SID at index %d, but there was none.', $index)); 124 | } 125 | 126 | if (false === $sids[$index]->equals($extractedSid)) { 127 | $this->fail(sprintf('Index: %d, expected SID "%s", but got "%s".', $index, $sids[$index], (string) $extractedSid)); 128 | } 129 | } 130 | } 131 | 132 | public function getSecurityIdentityRetrievalTests(): array 133 | { 134 | $anonymousRoles = [new RoleSecurityIdentity('IS_AUTHENTICATED_ANONYMOUSLY')]; 135 | if (\defined('\Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter::PUBLIC_ACCESS')) { 136 | $anonymousRoles[] = new RoleSecurityIdentity(AuthenticatedVoter::PUBLIC_ACCESS); 137 | } 138 | 139 | return [ 140 | [new Account('johannes'), ['ROLE_USER', 'ROLE_SUPERADMIN'], 'fullFledged', array_merge([ 141 | new UserSecurityIdentity('johannes', Account::class), 142 | new RoleSecurityIdentity('ROLE_USER'), 143 | new RoleSecurityIdentity('ROLE_SUPERADMIN'), 144 | new RoleSecurityIdentity('IS_AUTHENTICATED_FULLY'), 145 | new RoleSecurityIdentity('IS_AUTHENTICATED_REMEMBERED'), 146 | ], $anonymousRoles)], 147 | [new CustomUserImpl('johannes'), ['ROLE_FOO'], 'fullFledged', array_merge([ 148 | new UserSecurityIdentity('johannes', CustomUserImpl::class), 149 | new RoleSecurityIdentity('ROLE_FOO'), 150 | new RoleSecurityIdentity('IS_AUTHENTICATED_FULLY'), 151 | new RoleSecurityIdentity('IS_AUTHENTICATED_REMEMBERED'), 152 | ], $anonymousRoles)], 153 | [new Account('foo'), ['ROLE_FOO'], 'rememberMe', array_merge([ 154 | new UserSecurityIdentity('foo', Account::class), 155 | new RoleSecurityIdentity('ROLE_FOO'), 156 | new RoleSecurityIdentity('IS_AUTHENTICATED_REMEMBERED'), 157 | ], $anonymousRoles)], 158 | ['guest', [], 'anonymous', $anonymousRoles], 159 | ]; 160 | } 161 | 162 | public function getDeprecatedSecurityIdentityRetrievalTests() 163 | { 164 | $anonymousRoles = [new RoleSecurityIdentity('IS_AUTHENTICATED_ANONYMOUSLY')]; 165 | if (\defined('\Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter::PUBLIC_ACCESS')) { 166 | $anonymousRoles[] = new RoleSecurityIdentity(AuthenticatedVoter::PUBLIC_ACCESS); 167 | } 168 | 169 | return [ 170 | ['johannes', ['ROLE_FOO'], 'fullFledged', array_merge([ 171 | new UserSecurityIdentity('johannes', 'MyCustomTokenImpl'), 172 | new RoleSecurityIdentity('ROLE_FOO'), 173 | new RoleSecurityIdentity('IS_AUTHENTICATED_FULLY'), 174 | new RoleSecurityIdentity('IS_AUTHENTICATED_REMEMBERED'), 175 | ], $anonymousRoles)], 176 | ['guest', ['ROLE_FOO'], 'anonymous', array_merge([ 177 | new RoleSecurityIdentity('ROLE_FOO'), 178 | ], $anonymousRoles)], 179 | ]; 180 | } 181 | 182 | private function getStrategy(array $roles, string $authenticationStatus): SecurityIdentityRetrievalStrategy 183 | { 184 | $roleHierarchy = new class($roles) implements RoleHierarchyInterface { 185 | private $roles; 186 | 187 | public function __construct(array $roles) 188 | { 189 | $this->roles = $roles; 190 | } 191 | 192 | public function getReachableRoleNames(array $roles): array 193 | { 194 | return $this->roles; 195 | } 196 | }; 197 | 198 | $trustResolverMockBuild = $this->getMockBuilder(AuthenticationTrustResolverInterface::class); 199 | if (\defined('\Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter::PUBLIC_ACCESS')) { 200 | if (method_exists(AuthenticationTrustResolverInterface::class, 'isAuthenticated')) { 201 | $trustResolver = $trustResolverMockBuild->getMock(); 202 | } else { 203 | $trustResolver = $trustResolverMockBuild 204 | ->onlyMethods(['isAnonymous', 'isRememberMe', 'isFullFledged']) 205 | ->addMethods(['isAuthenticated']) 206 | ->getMock() 207 | ; 208 | } 209 | $trustResolver 210 | ->method('isAuthenticated') 211 | ->willReturn('anonymous' !== $authenticationStatus); 212 | } else { 213 | $trustResolver = $trustResolverMockBuild->getMock(); 214 | $trustResolver 215 | ->method('isAnonymous') 216 | ->willReturn('anonymous' === $authenticationStatus); 217 | } 218 | 219 | if ('fullFledged' === $authenticationStatus) { 220 | $trustResolver 221 | ->expects($this->once()) 222 | ->method('isFullFledged') 223 | ->willReturn(true) 224 | ; 225 | $trustResolver 226 | ->expects($this->never()) 227 | ->method('isRememberMe') 228 | ; 229 | } elseif ('rememberMe' === $authenticationStatus) { 230 | $trustResolver 231 | ->expects($this->once()) 232 | ->method('isFullFledged') 233 | ->willReturn(false) 234 | ; 235 | $trustResolver 236 | ->expects($this->once()) 237 | ->method('isRememberMe') 238 | ->willReturn(true) 239 | ; 240 | } else { 241 | if (method_exists(AuthenticationTrustResolverInterface::class, 'isAuthenticated')) { 242 | $trustResolver 243 | ->method('isAuthenticated') 244 | ->willReturn(false) 245 | ; 246 | } else { 247 | $trustResolver 248 | ->method('isAnonymous') 249 | ->willReturn(true); 250 | } 251 | 252 | $trustResolver 253 | ->expects($this->once()) 254 | ->method('isFullFledged') 255 | ->willReturn(false) 256 | ; 257 | $trustResolver 258 | ->expects($this->once()) 259 | ->method('isRememberMe') 260 | ->willReturn(false) 261 | ; 262 | } 263 | 264 | return new SecurityIdentityRetrievalStrategy($roleHierarchy, $trustResolver); 265 | } 266 | } 267 | 268 | class CustomUserImpl implements UserInterface 269 | { 270 | protected $name; 271 | 272 | public function __construct($name) 273 | { 274 | $this->name = $name; 275 | } 276 | 277 | public function __toString() 278 | { 279 | return $this->name; 280 | } 281 | 282 | public function getRoles(): array 283 | { 284 | return []; 285 | } 286 | 287 | public function eraseCredentials(): void 288 | { 289 | } 290 | 291 | public function getUserIdentifier(): string 292 | { 293 | return $this->name; 294 | } 295 | 296 | public function getPassword() 297 | { 298 | return null; 299 | } 300 | 301 | public function getSalt() 302 | { 303 | return null; 304 | } 305 | 306 | public function getUsername(): string 307 | { 308 | return $this->getUserIdentifier(); 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /Tests/Domain/UserSecurityIdentityTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Tests\Domain; 13 | 14 | use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity; 15 | use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity; 16 | use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface; 17 | use Symfony\Component\Security\Acl\Tests\Fixtures\Account; 18 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 19 | 20 | class UserSecurityIdentityTest extends \PHPUnit\Framework\TestCase 21 | { 22 | public function testConstructor() 23 | { 24 | $id = new UserSecurityIdentity('foo', 'Foo'); 25 | 26 | $this->assertEquals('foo', $id->getUsername()); 27 | $this->assertEquals('Foo', $id->getClass()); 28 | } 29 | 30 | // Test that constructor never changes the type, even for proxies 31 | public function testConstructorWithProxy() 32 | { 33 | $id = new UserSecurityIdentity('foo', 'Acme\DemoBundle\Proxy\__CG__\Symfony\Component\Security\Acl\Tests\Domain\Foo'); 34 | 35 | $this->assertEquals('foo', $id->getUsername()); 36 | $this->assertEquals('Acme\DemoBundle\Proxy\__CG__\Symfony\Component\Security\Acl\Tests\Domain\Foo', $id->getClass()); 37 | } 38 | 39 | /** 40 | * @dataProvider getCompareData 41 | */ 42 | public function testEquals(UserSecurityIdentity $id1, SecurityIdentityInterface $id2, bool $equal) 43 | { 44 | $this->assertSame($equal, $id1->equals($id2)); 45 | } 46 | 47 | public function getCompareData(): array 48 | { 49 | $account = new Account('foo'); 50 | 51 | $token = $this->createMock(TokenInterface::class); 52 | $token 53 | ->expects($this->any()) 54 | ->method('getUser') 55 | ->willReturn($account) 56 | ; 57 | 58 | return [ 59 | [new UserSecurityIdentity('foo', 'Foo'), new UserSecurityIdentity('foo', 'Foo'), true], 60 | [new UserSecurityIdentity('foo', 'Bar'), new UserSecurityIdentity('foo', 'Foo'), false], 61 | [new UserSecurityIdentity('foo', 'Foo'), new UserSecurityIdentity('bar', 'Foo'), false], 62 | [new UserSecurityIdentity('foo', 'Foo'), UserSecurityIdentity::fromAccount($account), false], 63 | [new UserSecurityIdentity('bla', 'Foo'), new UserSecurityIdentity('blub', 'Foo'), false], 64 | [new UserSecurityIdentity('foo', 'Foo'), new RoleSecurityIdentity('foo'), false], 65 | [new UserSecurityIdentity('foo', 'Foo'), UserSecurityIdentity::fromToken($token), false], 66 | [new UserSecurityIdentity('foo', Account::class), UserSecurityIdentity::fromToken($token), true], 67 | ]; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Tests/Fixtures/Account.php: -------------------------------------------------------------------------------- 1 | identifier = $identifier; 15 | } 16 | 17 | public function getUserIdentifier(): string 18 | { 19 | return $this->identifier; 20 | } 21 | 22 | public function getUsername(): string 23 | { 24 | return $this->getUserIdentifier(); 25 | } 26 | 27 | public function getRoles(): array 28 | { 29 | return ['ROLE_USER']; 30 | } 31 | 32 | public function getPassword(): ?string 33 | { 34 | return null; 35 | } 36 | 37 | public function getSalt(): ?string 38 | { 39 | return null; 40 | } 41 | 42 | public function eraseCredentials(): void 43 | { 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Tests/Fixtures/SerializableAclInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Tests\Permission; 13 | 14 | use Symfony\Component\Security\Acl\Permission\BasicPermissionMap; 15 | 16 | class BasicPermissionMapTest extends \PHPUnit\Framework\TestCase 17 | { 18 | public function testGetMasksReturnsNullWhenNotSupportedMask() 19 | { 20 | $map = new BasicPermissionMap(); 21 | $this->assertNull($map->getMasks('IS_AUTHENTICATED_REMEMBERED', null)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Tests/Permission/MaskBuilderTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Tests\Permission; 13 | 14 | use Symfony\Component\Security\Acl\Permission\MaskBuilder; 15 | 16 | class MaskBuilderTest extends \PHPUnit\Framework\TestCase 17 | { 18 | /** 19 | * @dataProvider getInvalidConstructorData 20 | */ 21 | public function testConstructorWithNonInteger($invalidMask) 22 | { 23 | $this->expectException(\InvalidArgumentException::class); 24 | 25 | new MaskBuilder($invalidMask); 26 | } 27 | 28 | public function getInvalidConstructorData() 29 | { 30 | return [ 31 | [234.463], 32 | ['asdgasdf'], 33 | [[]], 34 | [new \stdClass()], 35 | ]; 36 | } 37 | 38 | public function testConstructorWithoutArguments() 39 | { 40 | $builder = new MaskBuilder(); 41 | 42 | $this->assertEquals(0, $builder->get()); 43 | } 44 | 45 | public function testConstructor() 46 | { 47 | $builder = new MaskBuilder(123456); 48 | 49 | $this->assertEquals(123456, $builder->get()); 50 | } 51 | 52 | public function testAddAndRemove() 53 | { 54 | $builder = new MaskBuilder(); 55 | 56 | $builder 57 | ->add('view') 58 | ->add('eDiT') 59 | ->add('ownEr') 60 | ; 61 | $mask = $builder->get(); 62 | 63 | $this->assertEquals(MaskBuilder::MASK_VIEW, $mask & MaskBuilder::MASK_VIEW); 64 | $this->assertEquals(MaskBuilder::MASK_EDIT, $mask & MaskBuilder::MASK_EDIT); 65 | $this->assertEquals(MaskBuilder::MASK_OWNER, $mask & MaskBuilder::MASK_OWNER); 66 | $this->assertEquals(0, $mask & MaskBuilder::MASK_MASTER); 67 | $this->assertEquals(0, $mask & MaskBuilder::MASK_CREATE); 68 | $this->assertEquals(0, $mask & MaskBuilder::MASK_DELETE); 69 | $this->assertEquals(0, $mask & MaskBuilder::MASK_UNDELETE); 70 | 71 | $builder->remove('edit')->remove('OWner'); 72 | $mask = $builder->get(); 73 | $this->assertEquals(0, $mask & MaskBuilder::MASK_EDIT); 74 | $this->assertEquals(0, $mask & MaskBuilder::MASK_OWNER); 75 | $this->assertEquals(MaskBuilder::MASK_VIEW, $mask & MaskBuilder::MASK_VIEW); 76 | } 77 | 78 | public function testGetPattern() 79 | { 80 | $builder = new MaskBuilder(); 81 | $this->assertEquals(MaskBuilder::ALL_OFF, $builder->getPattern()); 82 | 83 | $builder->add('view'); 84 | $this->assertEquals(str_repeat('.', 31).'V', $builder->getPattern()); 85 | 86 | $builder->add('owner'); 87 | $this->assertEquals(str_repeat('.', 24).'N......V', $builder->getPattern()); 88 | 89 | $builder->add(1 << 10); 90 | $this->assertEquals(str_repeat('.', 21).MaskBuilder::ON.'..N......V', $builder->getPattern()); 91 | } 92 | 93 | public function testReset() 94 | { 95 | $builder = new MaskBuilder(); 96 | $this->assertEquals(0, $builder->get()); 97 | 98 | $builder->add('view'); 99 | $this->assertTrue($builder->get() > 0); 100 | 101 | $builder->reset(); 102 | $this->assertEquals(0, $builder->get()); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Util/ClassUtils.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Util; 13 | 14 | use Doctrine\Common\Util\ClassUtils as DoctrineClassUtils; 15 | 16 | /** 17 | * Class related functionality for objects that 18 | * might or might not be proxy objects at the moment. 19 | * 20 | * @see DoctrineClassUtils 21 | * 22 | * @author Johannes Schmitt 23 | * @author Iltar van der Berg 24 | */ 25 | final class ClassUtils 26 | { 27 | /** 28 | * Marker for Proxy class names. 29 | * 30 | * @var string 31 | */ 32 | public const MARKER = '__CG__'; 33 | 34 | /** 35 | * Length of the proxy marker. 36 | * 37 | * @var int 38 | */ 39 | public const MARKER_LENGTH = 6; 40 | 41 | /** 42 | * This class should not be instantiated. 43 | */ 44 | private function __construct() 45 | { 46 | } 47 | 48 | /** 49 | * Gets the real class name of a class name that could be a proxy. 50 | * 51 | * @param string|object $object 52 | * 53 | * @return string 54 | */ 55 | public static function getRealClass($object) 56 | { 57 | $class = \is_object($object) ? \get_class($object) : $object; 58 | 59 | if (class_exists(DoctrineClassUtils::class)) { 60 | return DoctrineClassUtils::getRealClass($class); 61 | } 62 | 63 | // fallback in case doctrine common is not installed 64 | if (false === $pos = strrpos($class, '\\'.self::MARKER.'\\')) { 65 | return $class; 66 | } 67 | 68 | return substr($class, $pos + self::MARKER_LENGTH + 2); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Voter/AclVoter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Voter; 13 | 14 | use Psr\Log\LoggerInterface; 15 | use Symfony\Component\Security\Acl\Exception\AclNotFoundException; 16 | use Symfony\Component\Security\Acl\Exception\NoAceFoundException; 17 | use Symfony\Component\Security\Acl\Model\AclProviderInterface; 18 | use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface; 19 | use Symfony\Component\Security\Acl\Model\ObjectIdentityRetrievalStrategyInterface; 20 | use Symfony\Component\Security\Acl\Model\SecurityIdentityRetrievalStrategyInterface; 21 | use Symfony\Component\Security\Acl\Permission\PermissionMapInterface; 22 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 23 | use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; 24 | 25 | if (class_exists(\Symfony\Component\Security\Core\Security::class)) { 26 | /** 27 | * @internal 28 | */ 29 | trait AclVoterTrait 30 | { 31 | public function vote(TokenInterface $token, $subject, array $attributes) 32 | { 33 | return $this->doVote($token, $subject, $attributes); 34 | } 35 | } 36 | } else { 37 | /** 38 | * @internal 39 | */ 40 | trait AclVoterTrait 41 | { 42 | public function vote(TokenInterface $token, mixed $subject, array $attributes): int 43 | { 44 | return $this->doVote($token, $subject, $attributes); 45 | } 46 | } 47 | } 48 | 49 | /** 50 | * This voter can be used as a base class for implementing your own permissions. 51 | * 52 | * @author Johannes M. Schmitt 53 | */ 54 | class AclVoter implements VoterInterface 55 | { 56 | use AclVoterTrait; 57 | 58 | private $aclProvider; 59 | private $permissionMap; 60 | private $objectIdentityRetrievalStrategy; 61 | private $securityIdentityRetrievalStrategy; 62 | private $allowIfObjectIdentityUnavailable; 63 | private $logger; 64 | 65 | public function __construct(AclProviderInterface $aclProvider, ObjectIdentityRetrievalStrategyInterface $oidRetrievalStrategy, SecurityIdentityRetrievalStrategyInterface $sidRetrievalStrategy, PermissionMapInterface $permissionMap, ?LoggerInterface $logger = null, $allowIfObjectIdentityUnavailable = true) 66 | { 67 | $this->aclProvider = $aclProvider; 68 | $this->permissionMap = $permissionMap; 69 | $this->objectIdentityRetrievalStrategy = $oidRetrievalStrategy; 70 | $this->securityIdentityRetrievalStrategy = $sidRetrievalStrategy; 71 | $this->logger = $logger; 72 | $this->allowIfObjectIdentityUnavailable = $allowIfObjectIdentityUnavailable; 73 | } 74 | 75 | public function supportsAttribute($attribute) 76 | { 77 | return \is_string($attribute) && $this->permissionMap->contains($attribute); 78 | } 79 | 80 | private function doVote(TokenInterface $token, $subject, array $attributes): int 81 | { 82 | foreach ($attributes as $attribute) { 83 | if (!$this->supportsAttribute($attribute)) { 84 | continue; 85 | } 86 | 87 | if (null === $masks = $this->permissionMap->getMasks($attribute, $subject)) { 88 | continue; 89 | } 90 | 91 | if (null === $subject) { 92 | if (null !== $this->logger) { 93 | $this->logger->debug(sprintf('Object identity unavailable. Voting to %s.', $this->allowIfObjectIdentityUnavailable ? 'grant access' : 'abstain')); 94 | } 95 | 96 | return $this->allowIfObjectIdentityUnavailable ? self::ACCESS_GRANTED : self::ACCESS_ABSTAIN; 97 | } elseif ($subject instanceof FieldVote) { 98 | $field = $subject->getField(); 99 | $subject = $subject->getDomainObject(); 100 | } else { 101 | $field = null; 102 | } 103 | 104 | if ($subject instanceof ObjectIdentityInterface) { 105 | $oid = $subject; 106 | } elseif (null === $oid = $this->objectIdentityRetrievalStrategy->getObjectIdentity($subject)) { 107 | if (null !== $this->logger) { 108 | $this->logger->debug(sprintf('Object identity unavailable. Voting to %s.', $this->allowIfObjectIdentityUnavailable ? 'grant access' : 'abstain')); 109 | } 110 | 111 | return $this->allowIfObjectIdentityUnavailable ? self::ACCESS_GRANTED : self::ACCESS_ABSTAIN; 112 | } 113 | 114 | if (!$this->supportsClass($oid->getType())) { 115 | return self::ACCESS_ABSTAIN; 116 | } 117 | 118 | $sids = $this->securityIdentityRetrievalStrategy->getSecurityIdentities($token); 119 | 120 | try { 121 | $acl = $this->aclProvider->findAcl($oid, $sids); 122 | 123 | if (null === $field && $acl->isGranted($masks, $sids, false)) { 124 | if (null !== $this->logger) { 125 | $this->logger->debug('ACL found, permission granted. Voting to grant access.'); 126 | } 127 | 128 | return self::ACCESS_GRANTED; 129 | } elseif (null !== $field && $acl->isFieldGranted($field, $masks, $sids, false)) { 130 | if (null !== $this->logger) { 131 | $this->logger->debug('ACL found, permission granted. Voting to grant access.'); 132 | } 133 | 134 | return self::ACCESS_GRANTED; 135 | } 136 | 137 | if (null !== $this->logger) { 138 | $this->logger->debug('ACL found, insufficient permissions. Voting to deny access.'); 139 | } 140 | 141 | return self::ACCESS_DENIED; 142 | } catch (AclNotFoundException $e) { 143 | if (null !== $this->logger) { 144 | $this->logger->debug('No ACL found for the object identity. Voting to deny access.'); 145 | } 146 | 147 | return self::ACCESS_DENIED; 148 | } catch (NoAceFoundException $e) { 149 | if (null !== $this->logger) { 150 | $this->logger->debug('ACL found, no ACE applicable. Voting to deny access.'); 151 | } 152 | 153 | return self::ACCESS_DENIED; 154 | } 155 | } 156 | 157 | // no attribute was supported 158 | return self::ACCESS_ABSTAIN; 159 | } 160 | 161 | /** 162 | * You can override this method when writing a voter for a specific domain 163 | * class. 164 | * 165 | * @param string $class The class name 166 | * 167 | * @return bool 168 | */ 169 | public function supportsClass($class) 170 | { 171 | return true; 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /Voter/FieldVote.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\Security\Acl\Voter; 13 | 14 | /** 15 | * This class is a lightweight wrapper around field vote requests which does 16 | * not violate any interface contracts. 17 | * 18 | * @author Johannes M. Schmitt 19 | */ 20 | class FieldVote 21 | { 22 | private $domainObject; 23 | private $field; 24 | 25 | public function __construct($domainObject, $field) 26 | { 27 | $this->domainObject = $domainObject; 28 | $this->field = $field; 29 | } 30 | 31 | public function getDomainObject() 32 | { 33 | return $this->domainObject; 34 | } 35 | 36 | public function getField() 37 | { 38 | return $this->field; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "symfony/security-acl", 3 | "type": "library", 4 | "description": "Symfony Security Component - ACL (Access Control List)", 5 | "keywords": [], 6 | "homepage": "https://symfony.com", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Fabien Potencier", 11 | "email": "fabien@symfony.com" 12 | }, 13 | { 14 | "name": "Symfony Community", 15 | "homepage": "https://symfony.com/contributors" 16 | } 17 | ], 18 | "require": { 19 | "php": ">=7.2.5", 20 | "symfony/security-core": "^4.4|^5.0|^6.0|^7.0" 21 | }, 22 | "require-dev": { 23 | "symfony/cache": "^4.4|^5.0|^6.0|^7.0", 24 | "symfony/finder": "^4.4|^5.0|^6.0|^7.0", 25 | "symfony/phpunit-bridge": "^5.2|^6.0|^7.0", 26 | "doctrine/cache": "^1.11|^2.0", 27 | "doctrine/common": "^2.2|^3", 28 | "doctrine/persistence": "^1.3.3|^2|^3", 29 | "doctrine/dbal": "^2.13.1|^3.1", 30 | "psr/log": "^1|^2|^3" 31 | }, 32 | "autoload": { 33 | "psr-4": { "Symfony\\Component\\Security\\Acl\\": "" }, 34 | "exclude-from-classmap": [ 35 | "/Tests/" 36 | ] 37 | }, 38 | "conflict": { 39 | "doctrine/cache": "<1.11", 40 | "doctrine/dbal": "<2.13.1|~3.0.0" 41 | }, 42 | "extra": { 43 | "branch-alias": { 44 | "dev-main": "3.x-dev" 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ./Tests/ 16 | 17 | 18 | 19 | 20 | 21 | ./ 22 | 23 | ./Resources 24 | ./Tests 25 | ./vendor 26 | 27 | 28 | 29 | 30 | 31 | benchmark 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | --------------------------------------------------------------------------------