-
-
Notifications
You must be signed in to change notification settings - Fork 224
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
FEATURE (WIP): Draft of adjusted security framework for Nodes
- Loading branch information
1 parent
ece5861
commit 2ce2b4f
Showing
17 changed files
with
1,236 additions
and
10 deletions.
There are no files selected for viewing
178 changes: 178 additions & 0 deletions
178
...ContentRepository.Security/Classes/Authorization/Privilege/Node/AbstractNodePrivilege.php
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
<?php | ||
namespace Neos\EventSourcedContentRepository\Security\Authorization\Privilege\Node; | ||
|
||
/* | ||
* This file is part of the Neos.ContentRepository package. | ||
* | ||
* (c) Contributors of the Neos Project - www.neos.io | ||
* | ||
* This package is Open Source Software. For the full copyright and license | ||
* information, please view the LICENSE file which was distributed with this | ||
* source code. | ||
*/ | ||
|
||
use Neos\Eel\CompilingEvaluator; | ||
use Neos\Eel\Context; | ||
use Neos\Flow\Aop\Pointcut\PointcutFilterInterface; | ||
use Neos\Flow\Security\Authorization\Privilege\AbstractPrivilege; | ||
use Neos\Flow\Security\Authorization\Privilege\Method\MethodPrivilege; | ||
use Neos\Flow\Security\Authorization\Privilege\Method\MethodPrivilegeInterface; | ||
use Neos\Flow\Security\Authorization\Privilege\Method\MethodPrivilegeSubject; | ||
use Neos\Flow\Security\Authorization\Privilege\PrivilegeSubjectInterface; | ||
use Neos\Flow\Security\Authorization\Privilege\PrivilegeTarget; | ||
use Neos\Flow\Security\Exception\InvalidPrivilegeTypeException; | ||
|
||
/** | ||
* An abstract node privilege acting as a base class for other | ||
* node privileges restricting operations and data on nodes. | ||
*/ | ||
abstract class AbstractNodePrivilege extends AbstractPrivilege implements MethodPrivilegeInterface | ||
{ | ||
/** | ||
* @var CompilingEvaluator | ||
*/ | ||
protected $eelCompilingEvaluator; | ||
|
||
/** | ||
* @var string | ||
*/ | ||
protected $nodeContextClassName = NodePrivilegeContext::class; | ||
|
||
/** | ||
* @var NodePrivilegeContext | ||
*/ | ||
protected $nodeContext; | ||
|
||
/** | ||
* @var MethodPrivilegeInterface | ||
*/ | ||
protected $methodPrivilege; | ||
|
||
/** | ||
* @var boolean | ||
*/ | ||
protected $initialized = false; | ||
|
||
/** | ||
* Constructor | ||
* | ||
* @param PrivilegeTarget $privilegeTarget | ||
* @param string $matcher | ||
* @param string $permission | ||
* @param $parameters | ||
*/ | ||
public function __construct(PrivilegeTarget $privilegeTarget, string $matcher, string $permission, $parameters) | ||
{ | ||
parent::__construct($privilegeTarget, $matcher, $permission, $parameters); | ||
$this->cacheEntryIdentifier = null; | ||
} | ||
|
||
/** | ||
* @return void | ||
*/ | ||
public function initialize() | ||
{ | ||
if ($this->initialized) { | ||
return; | ||
} | ||
$this->initialized = true; | ||
$this->eelCompilingEvaluator = $this->objectManager->get(CompilingEvaluator::class); | ||
$this->nodeContext = new $this->nodeContextClassName(); | ||
$this->initializeMethodPrivilege(); | ||
} | ||
|
||
/** | ||
* @return void | ||
*/ | ||
protected function buildCacheEntryIdentifier() | ||
{ | ||
$this->cacheEntryIdentifier = md5($this->privilegeTarget->getIdentifier() . '__methodPrivilege' . '|' . $this->buildMethodPrivilegeMatcher()); | ||
} | ||
|
||
/** | ||
* Unique identifier of this privilege | ||
* | ||
* @return string | ||
*/ | ||
public function getCacheEntryIdentifier(): string | ||
{ | ||
if ($this->cacheEntryIdentifier === null) { | ||
$this->buildCacheEntryIdentifier(); | ||
} | ||
|
||
return $this->cacheEntryIdentifier; | ||
} | ||
|
||
/** | ||
* @param PrivilegeSubjectInterface|NodePrivilegeSubject|MethodPrivilegeSubject $subject (one of NodePrivilegeSubject or MethodPrivilegeSubject) | ||
* @return boolean | ||
* @throws InvalidPrivilegeTypeException | ||
*/ | ||
public function matchesSubject(PrivilegeSubjectInterface $subject) | ||
{ | ||
if ($subject instanceof NodePrivilegeSubject === false && $subject instanceof MethodPrivilegeSubject === false) { | ||
throw new InvalidPrivilegeTypeException(sprintf('Privileges of type "%s" only support subjects of type "%s" or "%s", but we got a subject of type: "%s".', AbstractNodePrivilege::class, NodePrivilegeSubject::class, MethodPrivilegeSubject::class, get_class($subject)), 1417014368); | ||
} | ||
|
||
if ($subject instanceof MethodPrivilegeSubject) { | ||
$this->initializeMethodPrivilege(); | ||
return $this->methodPrivilege->matchesSubject($subject); | ||
} | ||
|
||
$this->initialize(); | ||
$nodeContext = new $this->nodeContextClassName($subject->getNode()); | ||
$eelContext = new Context($nodeContext); | ||
|
||
return $this->eelCompilingEvaluator->evaluate($this->getParsedMatcher(), $eelContext); | ||
} | ||
|
||
/** | ||
* @param string $className | ||
* @param string $methodName | ||
* @return boolean | ||
*/ | ||
public function matchesMethod($className, $methodName) | ||
{ | ||
$this->initializeMethodPrivilege(); | ||
return $this->methodPrivilege->matchesMethod($className, $methodName); | ||
} | ||
|
||
/** | ||
* @return PointcutFilterInterface | ||
*/ | ||
public function getPointcutFilterComposite() | ||
{ | ||
$this->initializeMethodPrivilege(); | ||
return $this->methodPrivilege->getPointcutFilterComposite(); | ||
} | ||
|
||
/** | ||
* @throws \Neos\Flow\Security\Exception | ||
*/ | ||
protected function initializeMethodPrivilege() | ||
{ | ||
if ($this->methodPrivilege !== null) { | ||
return; | ||
} | ||
$methodPrivilegeMatcher = $this->buildMethodPrivilegeMatcher(); | ||
$methodPrivilegeTarget = new PrivilegeTarget($this->privilegeTarget->getIdentifier() . '__methodPrivilege', MethodPrivilege::class, $methodPrivilegeMatcher); | ||
$methodPrivilegeTarget->injectObjectManager($this->objectManager); | ||
$this->methodPrivilege = $methodPrivilegeTarget->createPrivilege($this->getPermission(), $this->getParameters()); | ||
} | ||
|
||
/** | ||
* Evaluates the matcher with this objects nodeContext and returns the result. | ||
* | ||
* @return mixed | ||
*/ | ||
protected function evaluateNodeContext() | ||
{ | ||
$eelContext = new Context($this->nodeContext); | ||
return $this->eelCompilingEvaluator->evaluate($this->getParsedMatcher(), $eelContext); | ||
} | ||
|
||
/** | ||
* @return string | ||
*/ | ||
abstract protected function buildMethodPrivilegeMatcher(); | ||
} |
95 changes: 95 additions & 0 deletions
95
...epository.Security/Classes/Authorization/Privilege/Node/AbstractNodePropertyPrivilege.php
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
<?php | ||
namespace Neos\EventSourcedContentRepository\Security\Authorization\Privilege\Node; | ||
|
||
/* | ||
* This file is part of the Neos.ContentRepository package. | ||
* | ||
* (c) Contributors of the Neos Project - www.neos.io | ||
* | ||
* This package is Open Source Software. For the full copyright and license | ||
* information, please view the LICENSE file which was distributed with this | ||
* source code. | ||
*/ | ||
|
||
use Neos\ContentRepository\Domain\Projection\Content\TraversableNodeInterface; | ||
use Neos\Flow\Security\Authorization\Privilege\Method\MethodPrivilegeSubject; | ||
use Neos\Flow\Security\Authorization\Privilege\PrivilegeSubjectInterface; | ||
use Neos\Flow\Security\Exception\InvalidPrivilegeTypeException; | ||
|
||
/** | ||
* Base class for privileges restricting node properties. | ||
*/ | ||
abstract class AbstractNodePropertyPrivilege extends AbstractNodePrivilege | ||
{ | ||
/** | ||
* @var PropertyAwareNodePrivilegeContext | ||
*/ | ||
protected $nodeContext; | ||
|
||
/** | ||
* @var string | ||
*/ | ||
protected $nodeContextClassName = PropertyAwareNodePrivilegeContext::class; | ||
|
||
/** | ||
* With this mapping we can treat methods like properties. E.g. we want to be able to have a property "hidden" even though there is no real property | ||
* called like this. Instead the set/getHidden() methods should match this "property". | ||
* | ||
* @var array | ||
*/ | ||
protected $methodNameToPropertyMapping = []; | ||
|
||
/** | ||
* @param PrivilegeSubjectInterface|PropertyAwareNodePrivilegeSubject|MethodPrivilegeSubject $subject | ||
* @return boolean | ||
* @throws InvalidPrivilegeTypeException | ||
*/ | ||
public function matchesSubject(PrivilegeSubjectInterface $subject) | ||
{ | ||
if ($subject instanceof PropertyAwareNodePrivilegeSubject === false && $subject instanceof MethodPrivilegeSubject === false) { | ||
throw new InvalidPrivilegeTypeException(sprintf('Privileges of type "%s" only support subjects of type "%s" or "%s", but we got a subject of type: "%s".', ReadNodePropertyPrivilege::class, PropertyAwareNodePrivilegeSubject::class, MethodPrivilegeSubject::class, get_class($subject)), 1417018448); | ||
} | ||
|
||
$this->initialize(); | ||
$this->evaluateNodeContext(); | ||
if ($subject instanceof MethodPrivilegeSubject) { | ||
if ($this->methodPrivilege->matchesSubject($subject) === false) { | ||
return false; | ||
} | ||
|
||
$joinPoint = $subject->getJoinPoint(); | ||
|
||
// if the context isn't restricted to certain properties, it matches *all* properties | ||
if ($this->nodeContext->hasProperties()) { | ||
$methodName = $joinPoint->getMethodName(); | ||
$actualPropertyName = null; | ||
|
||
if (isset($this->methodNameToPropertyMapping[$methodName])) { | ||
$propertyName = $this->methodNameToPropertyMapping[$methodName]; | ||
} else { | ||
$propertyName = $joinPoint->getMethodArgument('propertyName'); | ||
} | ||
if (!in_array($propertyName, $this->nodeContext->getNodePropertyNames())) { | ||
return false; | ||
} | ||
} | ||
|
||
/** @var TraversableNodeInterface $node */ | ||
$node = $joinPoint->getProxy(); | ||
$nodePrivilegeSubject = new NodePrivilegeSubject($node); | ||
return parent::matchesSubject($nodePrivilegeSubject); | ||
} | ||
if ($subject->hasPropertyName() && in_array($subject->getPropertyName(), $this->nodeContext->getNodePropertyNames()) === false) { | ||
return false; | ||
} | ||
return parent::matchesSubject($subject); | ||
} | ||
|
||
/** | ||
* @return array | ||
*/ | ||
public function getNodePropertyNames() | ||
{ | ||
return $this->nodeContext->getNodePropertyNames(); | ||
} | ||
} |
90 changes: 90 additions & 0 deletions
90
...edContentRepository.Security/Classes/Authorization/Privilege/Node/CreateNodePrivilege.php
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
<?php | ||
namespace Neos\EventSourcedContentRepository\Security\Authorization\Privilege\Node; | ||
|
||
/* | ||
* This file is part of the Neos.ContentRepository package. | ||
* | ||
* (c) Contributors of the Neos Project - www.neos.io | ||
* | ||
* This package is Open Source Software. For the full copyright and license | ||
* information, please view the LICENSE file which was distributed with this | ||
* source code. | ||
*/ | ||
|
||
use Neos\ContentRepository\Domain\Projection\Content\TraversableNodeInterface; | ||
use Neos\EventSourcedContentRepository\Domain\Context\NodeAggregate\Command\CreateNodeAggregateWithNode; | ||
use Neos\EventSourcedContentRepository\Domain\Context\NodeAggregate\Command\CreateNodeVariant; | ||
use Neos\Flow\Security\Authorization\Privilege\Method\MethodPrivilegeSubject; | ||
use Neos\Flow\Security\Authorization\Privilege\PrivilegeSubjectInterface; | ||
use Neos\Flow\Security\Exception\InvalidPrivilegeTypeException; | ||
|
||
/** | ||
* A privilege to restrict node creation | ||
*/ | ||
class CreateNodePrivilege extends AbstractNodePrivilege | ||
{ | ||
/** | ||
* @var CreateNodePrivilegeContext | ||
*/ | ||
protected $nodeContext; | ||
|
||
/** | ||
* @var string | ||
*/ | ||
protected $nodeContextClassName = CreateNodePrivilegeContext::class; | ||
|
||
/** | ||
* @param PrivilegeSubjectInterface|CreateNodePrivilegeSubject|MethodPrivilegeSubject $subject | ||
* @return boolean | ||
* @throws InvalidPrivilegeTypeException | ||
*/ | ||
public function matchesSubject(PrivilegeSubjectInterface $subject) | ||
{ | ||
if ($subject instanceof CreateNodePrivilegeSubject === false && $subject instanceof MethodPrivilegeSubject === false) { | ||
throw new InvalidPrivilegeTypeException(sprintf('Privileges of type "%s" only support subjects of type "%s" or "%s", but we got a subject of type: "%s".', CreateNodePrivilege::class, CreateNodePrivilegeSubject::class, MethodPrivilegeSubject::class, get_class($subject)), 1417014353); | ||
} | ||
|
||
$this->initialize(); | ||
$this->evaluateNodeContext(); | ||
if ($subject instanceof MethodPrivilegeSubject) { | ||
if ($this->methodPrivilege->matchesSubject($subject) === false) { | ||
return false; | ||
} | ||
|
||
$joinPoint = $subject->getJoinPoint(); | ||
$allowedCreationNodeTypes = $this->nodeContext->getCreationNodeTypes(); | ||
$actualNodeType = $joinPoint->getMethodName() === 'createNodeFromTemplate' ? $joinPoint->getMethodArgument('nodeTemplate')->getNodeType()->getName() : $joinPoint->getMethodArgument('nodeType')->getName(); | ||
|
||
if ($allowedCreationNodeTypes !== [] && !in_array($actualNodeType, $allowedCreationNodeTypes)) { | ||
return false; | ||
} | ||
|
||
/** @var TraversableNodeInterface $node */ | ||
$node = $joinPoint->getProxy(); | ||
$nodePrivilegeSubject = new NodePrivilegeSubject($node); | ||
$result = parent::matchesSubject($nodePrivilegeSubject); | ||
return $result; | ||
} | ||
|
||
if ($this->nodeContext->getCreationNodeTypes() === [] || ($subject->hasCreationNodeType() === false) || in_array($subject->getCreationNodeType()->getName(), $this->nodeContext->getCreationNodeTypes()) === true) { | ||
return parent::matchesSubject($subject); | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* @return array $creationNodeTypes | ||
*/ | ||
public function getCreationNodeTypes() | ||
{ | ||
return $this->nodeContext->getCreationNodeTypes(); | ||
} | ||
|
||
/** | ||
* @return string | ||
*/ | ||
protected function buildMethodPrivilegeMatcher() | ||
{ | ||
return 'method(' . CreateNodeVariant::class . '->__construct()) && method(' . CreateNodeAggregateWithNode::class . '->__construct())'; | ||
} | ||
} |
Oops, something went wrong.