-
-
Save aertmann/51ae0040b1ef179c208e to your computer and use it in GitHub Desktop.
Custom entity security for Flow/Neos (securing specific site dynamically)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Admhuset\RealEstateWebsites\Security\Authorization\Privilege\Node\Doctrine; | |
/* * | |
* This script belongs to the TYPO3 Flow package "TYPO3.TYPO3CR". * | |
* * | |
* It is free software; you can redistribute it and/or modify it under * | |
* the terms of the GNU General Public License, either version 3 of the * | |
* License, or (at your option) any later version. * | |
* * | |
* The TYPO3 project - inspiring people to share! * | |
* */ | |
use Admhuset\RealEstateWebsites\Domain\Model\Site; | |
use Admhuset\RealEstateWebsites\Domain\Repository\SiteRepository; | |
use TYPO3\Flow\Annotations as Flow; | |
use TYPO3\Flow\Security\Authorization\Privilege\Entity\Doctrine\FalseConditionGenerator; | |
use TYPO3\Flow\Security\Authorization\Privilege\Entity\Doctrine\DisjunctionGenerator; | |
use TYPO3\Flow\Security\Authorization\Privilege\Entity\Doctrine\NotExpressionGenerator; | |
use TYPO3\Flow\Security\Authorization\Privilege\Entity\Doctrine\PropertyConditionGenerator; | |
use TYPO3\Flow\Security\Authorization\Privilege\Entity\Doctrine\TrueConditionGenerator; | |
use TYPO3\Neos\Domain\Service\UserService; | |
/** | |
* A SQL condition generator, supporting special SQL constraints | |
* for nodes. | |
*/ | |
class ConditionGenerator extends \TYPO3\TYPO3CR\Security\Authorization\Privilege\Node\Doctrine\ConditionGenerator { | |
/** | |
* @Flow\Inject | |
* @var UserService | |
*/ | |
protected $userService; | |
/** | |
* @var SiteRepository | |
* @Flow\Inject | |
*/ | |
protected $siteRepository; | |
/** RUNTIME CACHING!!! */ | |
/** | |
* @return PropertyConditionGenerator | |
*/ | |
public function accessToSite() { | |
if ($this->securityContext->hasRole('TYPO3.Neos:Administrator')) { | |
return new FalseConditionGenerator(); | |
} | |
$user = $this->userService->getCurrentUser(); | |
if ($user === NULL) { | |
return new FalseConditionGenerator(); | |
} | |
/** @var Site $site */ | |
$site = $this->siteRepository->findOneByUser($user); | |
if ($site === NULL) { | |
return new TrueConditionGenerator(); | |
} | |
$pathPropertyConditionGenerator1 = new PropertyConditionGenerator('path'); | |
$pathPropertyConditionGenerator2 = new PropertyConditionGenerator('path'); | |
$pathPropertyConditionGenerator3 = new PropertyConditionGenerator('path'); | |
$nodePath = '/sites/' . $site->getNeosSiteModel()->getNodeName(); | |
return new NotExpressionGenerator(new DisjunctionGenerator(array($pathPropertyConditionGenerator1->like($nodePath . '/%'), $pathPropertyConditionGenerator2->equals($nodePath), $pathPropertyConditionGenerator3->equals('/')))); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# # | |
# Security policy for the TYPO3 Neos package # | |
# # | |
privilegeTargets: | |
'TYPO3\Flow\Security\Authorization\Privilege\Method\MethodPrivilege': | |
'Admhuset.RealEstateWebsites:Backend.Module.Administration.Sites': | |
matcher: 'method(Admhuset\RealEstateWebsites\Controller\Module\Administration\SitesController->(index|newSite|createSite|deleteSite)Action())' | |
'Admhuset\RealEstateWebsites\Security\Authorization\Privilege\Node\SitePrivilege': | |
'Admhuset.RealEstateWebsites:SiteAccess': | |
matcher: 'accessToSite()' | |
roles: | |
'TYPO3.Neos:Administrator': | |
parentRoles: ['TYPO3.Neos:Editor'] | |
privileges: | |
- | |
privilegeTarget: 'Admhuset.RealEstateWebsites:Backend.Module.Administration.Sites' | |
permission: GRANT | |
'TYPO3.Neos:Editor': | |
privileges: | |
- | |
privilegeTarget: 'Admhuset.RealEstateWebsites:SiteAccess' | |
permission: DENY |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Admhuset\RealEstateWebsites\Security\Authorization\Privilege\Node; | |
/* * | |
* This script belongs to the TYPO3 Flow package "TYPO3.TYPO3CR". * | |
* * | |
* It is free software; you can redistribute it and/or modify it under * | |
* the terms of the GNU General Public License, either version 3 of the * | |
* License, or (at your option) any later version. * | |
* * | |
* The TYPO3 project - inspiring people to share! * | |
* */ | |
use Admhuset\RealEstateWebsites\Security\Authorization\Privilege\Node\Doctrine\ConditionGenerator; | |
use TYPO3\TYPO3CR\Security\Authorization\Privilege\Node\ReadNodePrivilege; | |
/** | |
* A node privilege to restricting reading of nodes. | |
* Nodes not granted for reading will be filtered via SQL. | |
* | |
* Currently only doctrine persistence is supported as we use | |
* the doctrine filter api, to rewrite SQL queries. | |
*/ | |
class SitePrivilege extends ReadNodePrivilege { | |
/** | |
* @return ConditionGenerator | |
*/ | |
protected function getConditionGenerator() { | |
return new ConditionGenerator(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace TYPO3\Flow\Security\Authorization\Privilege\Entity\Doctrine; | |
/* * | |
* This script belongs to the TYPO3 Flow framework. * | |
* * | |
* It is free software; you can redistribute it and/or modify it under * | |
* the terms of the GNU Lesser General Public License, either version 3 * | |
* of the License, or (at your option) any later version. * | |
* * | |
* The TYPO3 project - inspiring people to share! * | |
* */ | |
use Doctrine\ORM\Mapping\ClassMetaData; | |
use Doctrine\ORM\Query\Filter\SQLFilter as DoctrineSqlFilter; | |
use TYPO3\Flow\Annotations as Flow; | |
use TYPO3\Flow\Core\Bootstrap; | |
use TYPO3\Flow\Security\Context; | |
use TYPO3\Flow\Security\Policy\PolicyService; | |
use TYPO3\Flow\Security\Authorization\Privilege\Entity\EntityPrivilegeInterface; | |
/** | |
* A filter to rewrite doctrine queries according to the security policy. | |
* | |
* @Flow\Proxy(false) | |
*/ | |
class SqlFilter extends DoctrineSqlFilter { | |
/** | |
* @var PolicyService | |
*/ | |
protected $policyService; | |
/** | |
* @var Context | |
*/ | |
protected $securityContext; | |
/** | |
* Gets the SQL query part to add to a query. | |
* | |
* @param ClassMetaData $targetEntity Metadata object for the target entity to be filtered | |
* @param string $targetTableAlias The target table alias used in the current query | |
* @return string The constraint SQL if there is available, empty string otherwise | |
*/ | |
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias) { | |
$this->initializeDependencies(); | |
/* | |
* TODO: Instead of checking for class account we could introduce some interface for white listing entities from entity security checks | |
* Problem with checking the Account is, that this filter calls getRoles() on the security context while accounts are not | |
* yet fully initialized. By this we get a half built account object that will end up in access denied exception, | |
* as it has no roles (and other properties) set | |
*/ | |
if ($this->securityContext->areAuthorizationChecksDisabled() || $targetEntity->getName() === 'TYPO3\Flow\Security\Account') { | |
return ''; | |
} | |
if (!$this->securityContext->isInitialized()) { | |
if (!$this->securityContext->canBeInitialized()) { | |
return ''; | |
} | |
$this->securityContext->initialize(); | |
} | |
//This is needed to include the current context of roles into query cache identifier | |
$this->setParameter('__contextHash', $this->securityContext->getContextHash(), 'string'); | |
$sqlConstraints = array(); | |
$grantedConstraints = array(); | |
$deniedConstraints = array(); | |
foreach ($this->securityContext->getRoles() as $role) { | |
$entityPrivileges = $role->getPrivilegesByType('TYPO3\Flow\Security\Authorization\Privilege\Entity\EntityPrivilegeInterface'); | |
/** @var EntityPrivilegeInterface $privilege */ | |
foreach ($entityPrivileges as $privilege) { | |
if (!$privilege->matchesEntityType($targetEntity->getName())) { | |
continue; | |
} | |
$sqlConstraint = $privilege->getSqlConstraint($targetEntity, $targetTableAlias); | |
if ($sqlConstraint === NULL) { | |
continue; | |
} | |
$sqlConstraints[] = ' NOT (' . $sqlConstraint . ')'; | |
if ($privilege->isGranted()) { | |
$grantedConstraints[] = ' NOT (' . $sqlConstraint . ')'; | |
} elseif ($privilege->isDenied()) { | |
$deniedConstraints[] = ' NOT (' . $sqlConstraint . ')'; | |
} | |
} | |
} | |
$grantedConstraints = array_diff($grantedConstraints, $deniedConstraints); | |
$effectiveConstraints = array_diff($sqlConstraints, $grantedConstraints); | |
if (count($effectiveConstraints) > 0) { | |
// Workaround to prevent doctrine caching the filtered queries | |
$this->setParameter(uniqid('dirty'), NULL); | |
return ' (' . implode(') AND (', $effectiveConstraints) . ') '; | |
} | |
return ''; | |
} | |
/** | |
* Initializes the dependencies by retrieving them from the object manager | |
* | |
* @return void | |
*/ | |
protected function initializeDependencies() { | |
if ($this->securityContext === NULL) { | |
$this->securityContext = Bootstrap::$staticObjectManager->get('TYPO3\Flow\Security\Context'); | |
} | |
if ($this->policyService === NULL) { | |
$this->policyService = Bootstrap::$staticObjectManager->get('TYPO3\Flow\Security\Policy\PolicyService'); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment