Skip to content

Instantly share code, notes, and snippets.

@aertmann
Created June 30, 2015 16:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aertmann/51ae0040b1ef179c208e to your computer and use it in GitHub Desktop.
Save aertmann/51ae0040b1ef179c208e to your computer and use it in GitHub Desktop.
Custom entity security for Flow/Neos (securing specific site dynamically)
<?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('/'))));
}
}
# #
# 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
<?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();
}
}
<?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