revised code

This commit is contained in:
Robin Krahnen 2022-03-15 09:32:42 +01:00
parent 759b50d2b0
commit 3b3566bb73
10 changed files with 135 additions and 136 deletions

View file

@ -4,7 +4,7 @@ namespace DigiComp\FlowSessionLock\Annotations;
/**
* @Annotation
* @Target("METHOD")
* @Target({"METHOD"})
*/
final class ReadOnly
{

View file

@ -36,37 +36,43 @@ class ReadOnlyAspect
/**
* @Flow\Around("methodAnnotatedWith(DigiComp\FlowSessionLock\Annotations\ReadOnly) || filter(DigiComp\FlowSessionLock\Aspects\ReadOnlyFilter)")
* @param JoinPointInterface $joinPoint
*
* @return void
* @return mixed
*/
public function demoteLockToReadOnly(JoinPointInterface $joinPoint)
{
$handler = $this->bootstrap->getActiveRequestHandler();
if (! $handler instanceof HttpRequestHandlerInterface) {
$this->logger->debug(\get_class($handler));
$activeRequestHandler = $this->bootstrap->getActiveRequestHandler();
if (!$activeRequestHandler instanceof HttpRequestHandlerInterface) {
$this->logger->debug('SessionLock: ' . \get_class($activeRequestHandler));
return $joinPoint->getAdviceChain()->proceed($joinPoint);
}
$componentContext = $handler->getComponentContext();
/** @var Lock $lock */
$lock = $componentContext->getParameter(SessionLockRequestComponent::class, 'sessionLock');
$this->readOnly = true;
if ($lock) {
$this->logger->debug('SessionLock: Release, as this is marked read only');
/** @var Lock|null $lock */
$lock = $activeRequestHandler->getComponentContext()->getParameter(
SessionLockRequestComponent::class,
SessionLockRequestComponent::PARAMETER_NAME
);
if ($lock !== null) {
$this->logger->debug('SessionLock: Release, as this is marked read only.');
$lock->release();
}
return $joinPoint->getAdviceChain()->proceed($joinPoint);
}
/**
* @Flow\Around("method(Neos\Flow\Session\Session->shutdownObject())")
*
* @param JoinPointInterface $joinPoint
* @return mixed|void
*/
public function doNotSaveSession(JoinPointInterface $joinPoint)
{
if ($this->readOnly) {
return;
}
$joinPoint->getAdviceChain()->proceed($joinPoint);
return $joinPoint->getAdviceChain()->proceed($joinPoint);
}
}

View file

@ -4,9 +4,12 @@ namespace DigiComp\FlowSessionLock\Aspects;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Aop\Builder\ClassNameIndex;
use Neos\Flow\Aop\Exception as NeosFlowAopException;
use Neos\Flow\Aop\Exception\InvalidPointcutExpressionException;
use Neos\Flow\Aop\Pointcut\PointcutFilterComposite;
use Neos\Flow\Aop\Pointcut\PointcutFilterInterface;
use Neos\Flow\Configuration\ConfigurationManager;
use Neos\Flow\Configuration\Exception\InvalidConfigurationTypeException;
use Neos\Flow\Security\Authorization\Privilege\Method\MethodTargetExpressionParser;
/**
@ -15,20 +18,32 @@ use Neos\Flow\Security\Authorization\Privilege\Method\MethodTargetExpressionPars
*/
class ReadOnlyFilter implements PointcutFilterInterface
{
/**
* @var ConfigurationManager
*/
protected ConfigurationManager $configurationManager;
/**
* @var MethodTargetExpressionParser
*/
protected MethodTargetExpressionParser $methodTargetExpressionParser;
/**
* @var PointcutFilterComposite[]
*/
protected ?array $filters = null;
protected ?array $pointcutFilterComposites = null;
/**
* @param ConfigurationManager $configurationManager
*/
public function injectConfigurationManager(ConfigurationManager $configurationManager): void
{
$this->configurationManager = $configurationManager;
}
/**
* @param MethodTargetExpressionParser $methodTargetExpressionParser
*/
public function injectMethodTargetExpressionParser(MethodTargetExpressionParser $methodTargetExpressionParser): void
{
$this->methodTargetExpressionParser = $methodTargetExpressionParser;
@ -36,30 +51,28 @@ class ReadOnlyFilter implements PointcutFilterInterface
/**
* @inheritDoc
* @throws InvalidConfigurationTypeException
* @throws InvalidPointcutExpressionException
* @throws NeosFlowAopException
*/
public function matches($className, $methodName, $methodDeclaringClassName, $pointcutQueryIdentifier): bool
{
if ($this->filters === null) {
$this->buildPointcutFilters();
}
$this->buildPointcutFilters();
$matchingFilters = \array_filter(
$this->filters,
function (PointcutFilterInterface $filter) use (
$className,
$methodName,
$methodDeclaringClassName,
$pointcutQueryIdentifier
): bool {
return $filter->matches($className, $methodName, $methodDeclaringClassName, $pointcutQueryIdentifier);
foreach ($this->pointcutFilterComposites as $pointcutFilterComposite) {
if (
$pointcutFilterComposite->matches(
$className,
$methodName,
$methodDeclaringClassName,
$pointcutQueryIdentifier
)
) {
return true;
}
);
if ($matchingFilters === []) {
return false;
}
return true;
return false;
}
/**
@ -80,33 +93,47 @@ class ReadOnlyFilter implements PointcutFilterInterface
/**
* @inheritDoc
* @throws InvalidConfigurationTypeException
* @throws InvalidPointcutExpressionException
* @throws NeosFlowAopException
*/
public function reduceTargetClassNames(ClassNameIndex $classNameIndex): ClassNameIndex
{
if ($this->filters === null) {
$this->buildPointcutFilters();
}
$this->buildPointcutFilters();
$result = new ClassNameIndex();
foreach ($this->filters as $filter) {
$result->applyUnion($filter->reduceTargetClassNames($classNameIndex));
foreach ($this->pointcutFilterComposites as $pointcutFilterComposite) {
$result->applyUnion($pointcutFilterComposite->reduceTargetClassNames($classNameIndex));
}
return $result;
}
/**
* @throws InvalidConfigurationTypeException
* @throws InvalidPointcutExpressionException
* @throws NeosFlowAopException
*/
protected function buildPointcutFilters(): void
{
$this->filters = [];
$readOnlyExpressions = $this->configurationManager->getConfiguration(
ConfigurationManager::CONFIGURATION_TYPE_SETTINGS,
'DigiComp.FlowSessionLock.readOnlyExpressions'
) ?? [];
foreach ($readOnlyExpressions as $key => $pointcut) {
if ($pointcut === null) {
if ($this->pointcutFilterComposites !== null) {
return;
}
$this->pointcutFilterComposites = [];
foreach (
$this->configurationManager->getConfiguration(
ConfigurationManager::CONFIGURATION_TYPE_SETTINGS,
'DigiComp.FlowSessionLock.readOnlyExpressions'
) as $key => $pointcutExpression
) {
if ($pointcutExpression === null) {
continue;
}
$this->filters[] = $this->methodTargetExpressionParser->parse(
$pointcut,
$this->pointcutFilterComposites[] = $this->methodTargetExpressionParser->parse(
$pointcutExpression,
'Settings.yaml at "DigiComp.FlowSessionLock.readOnlyExpressions", key: "' . $key . '"'
);
}

View file

@ -5,25 +5,19 @@ namespace DigiComp\FlowSessionLock\Http;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Http\Component\ComponentContext;
use Neos\Flow\Http\Component\ComponentInterface;
use Neos\Flow\Utility\Environment;
use Neos\Utility\Files;
use Psr\Log\LoggerInterface;
use Symfony\Component\Lock\Key;
use Symfony\Component\Lock\LockFactory;
class SessionLockRequestComponent implements ComponentInterface
{
/**
* @Flow\InjectConfiguration(package="Neos.Flow", path="session")
* @var array
*/
protected $sessionSettings;
public const PARAMETER_NAME = 'sessionLock';
/**
* @Flow\Inject(lazy=false)
* @var LoggerInterface
*/
protected $logger;
protected LoggerInterface $logger;
/**
* @Flow\Inject(name="DigiComp.FlowSessionLock:LockFactory")
@ -31,40 +25,42 @@ class SessionLockRequestComponent implements ComponentInterface
*/
protected $lockFactory;
/**
* @Flow\InjectConfiguration(package="Neos.Flow", path="session")
* @var array
*/
protected array $sessionSettings;
/**
* @Flow\InjectConfiguration(package="DigiComp.FlowSessionLock", path="timeToLive")
* @var float
*/
protected float $timeToLive;
/**
* @Flow\InjectConfiguration(package="DigiComp.FlowSessionLock", path="autoRelease")
* @var bool
*/
protected bool $autoRelease;
/**
* @Flow\InjectConfiguration(package="DigiComp.FlowSessionLock", path="timeToLive")
* @var int
*/
protected int $timeToLive;
/**
* @inheritDoc
*/
public function handle(ComponentContext $componentContext)
{
$sessionCookieName = $this->sessionSettings['name'];
$request = $componentContext->getHttpRequest();
$cookies = $request->getCookieParams();
$cookies = $componentContext->getHttpRequest()->getCookieParams();
if (!isset($cookies[$sessionCookieName])) {
return;
}
$sessionIdentifier = $cookies[$sessionCookieName];
$key = new Key(
'session-' . $sessionIdentifier
); //TODO: sessionIdentifier might be wrong, probably it should probably be storage identifier
// TODO: sessionIdentifier might be wrong, probably it should probably be storage identifier
$key = new Key('session-' . $cookies[$sessionCookieName]);
$lock = $this->lockFactory->createLockFromKey($key, $this->timeToLive, $this->autoRelease);
$componentContext->setParameter(SessionLockRequestComponent::class, 'sessionLock', $lock);
$componentContext->setParameter(SessionLockRequestComponent::class, static::PARAMETER_NAME, $lock);
$this->logger->debug('SessionLock: Get ' . $key);
$lock->acquire(true);

View file

@ -1,31 +0,0 @@
<?php
namespace DigiComp\FlowSessionLock;
use Neos\Flow\Configuration\ConfigurationManager;
use Neos\Flow\Core\Bootstrap;
use Neos\Flow\Package\Package as BasePackage;
use Neos\Utility\Files;
class Package extends BasePackage
{
public function boot(Bootstrap $bootstrap)
{
parent::boot($bootstrap);
$dispatcher = $bootstrap->getSignalSlotDispatcher();
$dispatcher->connect(
ConfigurationManager::class,
'configurationManagerReady',
function (ConfigurationManager $configurationManager) {
$lockStoreDir = $configurationManager->getConfiguration(
ConfigurationManager::CONFIGURATION_TYPE_SETTINGS,
'DigiComp.FlowSessionLock.lockStoreDir'
);
if (is_string($lockStoreDir)) {
Files::createDirectoryRecursively($lockStoreDir);
}
}
);
}
}

View file

@ -1,3 +1,3 @@
DigiComp:
FlowSessionLock:
lockStoreDir: '%FLOW_PATH_DATA%Temporary/Development/SessionLocks'
lockStoreConnection: "flock://%FLOW_PATH_DATA%Temporary/Development/SessionLocks/"

View file

@ -1,12 +1,10 @@
DigiComp.FlowSessionLock:LockFactory:
className: 'Symfony\Component\Lock\LockFactory'
className: "Symfony\\Component\\Lock\\LockFactory"
arguments:
1:
object: 'DigiComp.FlowSessionLock:LockStore'
DigiComp.FlowSessionLock:LockStore:
className: 'Symfony\Component\Lock\Store\FlockStore'
scope: 'singleton'
arguments:
1:
setting: 'DigiComp.FlowSessionLock.lockStoreDir'
object:
factoryObjectName: "Symfony\\Component\\Lock\\Store\\StoreFactory"
factoryMethodName: "createStore"
arguments:
1:
setting: "DigiComp.FlowSessionLock.lockStoreConnection"

View file

@ -1,3 +1,10 @@
DigiComp:
FlowSessionLock:
lockStoreConnection: "flock://%FLOW_PATH_DATA%Temporary/Production/SessionLocks/"
timeToLive: 300.0
autoRelease: true
readOnlyExpressions: {}
Neos:
Flow:
http:
@ -5,12 +12,5 @@ Neos:
preprocess:
chain:
lockSession:
position: 'before getSessionCookieFromRequest'
component: 'DigiComp\FlowSessionLock\Http\SessionLockRequestComponent'
DigiComp:
FlowSessionLock:
lockStoreDir: '%FLOW_PATH_DATA%Temporary/Production/SessionLocks'
readOnlyExpressions: []
autoRelease: true
timeToLive: 300
position: "before getSessionCookieFromRequest"
component: "DigiComp\\FlowSessionLock\\Http\\SessionLockRequestComponent"

View file

@ -0,0 +1,3 @@
DigiComp:
FlowSessionLock:
lockStoreConnection: "flock://%FLOW_PATH_DATA%Temporary/Testing/SessionLocks/"

View file

@ -1,25 +1,11 @@
{
"name": "digicomp/flowsessionlock",
"description": "Session locking for Neos Flow - it secures the session becoming corrupted by concurrent access to the same session by different requests",
"type": "neos-package",
"description": "Sesion locking for Neos Flow - it secures the session becoming corrupted by concurrent access to the same session by different requests",
"keywords": [
"flow",
"neos"
],
"authors": [
{
"name": "Ferdinand Kuhl",
"email": "f.kuhl@digital-competence.de",
"homepage": "http://www.digital-competence.de",
"role": "Developer"
}
],
"license": "MIT",
"homepage": "https://github.com/digicomp/DigiComp.FlowSessionLock",
"require": {
"neos/flow": "^6.2",
"php": "^7.4",
"symfony/lock": "^5.2"
"neos/flow": "^6.2.0",
"php": ">=7.4",
"symfony/lock": "^5.2.0"
},
"autoload": {
"psr-4": {
@ -33,5 +19,19 @@
"branch-alias": {
"dev-develop": "1.0.x-dev"
}
}
},
"authors": [
{
"name": "Ferdinand Kuhl",
"email": "f.kuhl@digital-competence.de",
"homepage": "https://www.digital-competence.de",
"role": "Developer"
}
],
"license": "MIT",
"homepage": "https://github.com/digital-competence/FlowSessionLock",
"keywords": [
"Neos",
"Flow"
]
}