First working version
This commit is contained in:
parent
ef6ce83d51
commit
a2c7fabfea
9 changed files with 341 additions and 0 deletions
11
Classes/Annotations/ReadOnly.php
Normal file
11
Classes/Annotations/ReadOnly.php
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace DigiComp\FlowSessionLock\Annotations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Annotation
|
||||||
|
* @Target("METHOD")
|
||||||
|
*/
|
||||||
|
final class ReadOnly
|
||||||
|
{
|
||||||
|
}
|
72
Classes/Aspects/ReadOnlyAspect.php
Normal file
72
Classes/Aspects/ReadOnlyAspect.php
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace DigiComp\FlowSessionLock\Aspects;
|
||||||
|
|
||||||
|
use DigiComp\FlowSessionLock\Http\SessionLockRequestComponent;
|
||||||
|
use Neos\Flow\Annotations as Flow;
|
||||||
|
use Neos\Flow\Aop\JoinPointInterface;
|
||||||
|
use Neos\Flow\Core\Bootstrap;
|
||||||
|
use Neos\Flow\Http\HttpRequestHandlerInterface;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Symfony\Component\Lock\Lock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Flow\Aspect
|
||||||
|
* @Flow\Scope("singleton")
|
||||||
|
*/
|
||||||
|
class ReadOnlyAspect
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Flow\Inject
|
||||||
|
* @var Bootstrap
|
||||||
|
*/
|
||||||
|
protected $bootstrap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Flow\Inject
|
||||||
|
* @var LoggerInterface
|
||||||
|
*/
|
||||||
|
protected $logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected bool $readOnly = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Flow\Around("methodAnnotatedWith(DigiComp\FlowSessionLock\Annotations\ReadOnly) || filter(DigiComp\FlowSessionLock\Aspects\ReadOnlyFilter)")
|
||||||
|
* @param JoinPointInterface $joinPoint
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function demoteLockToReadOnly(JoinPointInterface $joinPoint)
|
||||||
|
{
|
||||||
|
$handler = $this->bootstrap->getActiveRequestHandler();
|
||||||
|
if (! $handler instanceof HttpRequestHandlerInterface) {
|
||||||
|
$this->logger->debug(\get_class($handler));
|
||||||
|
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');
|
||||||
|
$lock->release();
|
||||||
|
}
|
||||||
|
return $joinPoint->getAdviceChain()->proceed($joinPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Flow\Around("method(Neos\Flow\Session\Session->shutdownObject())")
|
||||||
|
*
|
||||||
|
* @param JoinPointInterface $joinPoint
|
||||||
|
*/
|
||||||
|
public function doNotSaveSession(JoinPointInterface $joinPoint)
|
||||||
|
{
|
||||||
|
if ($this->readOnly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$joinPoint->getAdviceChain()->proceed($joinPoint);
|
||||||
|
}
|
||||||
|
}
|
114
Classes/Aspects/ReadOnlyFilter.php
Normal file
114
Classes/Aspects/ReadOnlyFilter.php
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace DigiComp\FlowSessionLock\Aspects;
|
||||||
|
|
||||||
|
use Neos\Flow\Annotations as Flow;
|
||||||
|
use Neos\Flow\Aop\Builder\ClassNameIndex;
|
||||||
|
use Neos\Flow\Aop\Pointcut\PointcutFilterComposite;
|
||||||
|
use Neos\Flow\Aop\Pointcut\PointcutFilterInterface;
|
||||||
|
use Neos\Flow\Configuration\ConfigurationManager;
|
||||||
|
use Neos\Flow\Security\Authorization\Privilege\Method\MethodTargetExpressionParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Flow\Proxy(false)
|
||||||
|
* @Flow\Scope("singleton")
|
||||||
|
*/
|
||||||
|
class ReadOnlyFilter implements PointcutFilterInterface
|
||||||
|
{
|
||||||
|
protected ConfigurationManager $configurationManager;
|
||||||
|
|
||||||
|
protected MethodTargetExpressionParser $methodTargetExpressionParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var PointcutFilterComposite[]
|
||||||
|
*/
|
||||||
|
protected ?array $filters = null;
|
||||||
|
|
||||||
|
public function injectConfigurationManager(ConfigurationManager $configurationManager): void
|
||||||
|
{
|
||||||
|
$this->configurationManager = $configurationManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function injectMethodTargetExpressionParser(MethodTargetExpressionParser $methodTargetExpressionParser): void
|
||||||
|
{
|
||||||
|
$this->methodTargetExpressionParser = $methodTargetExpressionParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function matches($className, $methodName, $methodDeclaringClassName, $pointcutQueryIdentifier): bool
|
||||||
|
{
|
||||||
|
if ($this->filters === null) {
|
||||||
|
$this->buildPointcutFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
$matchingFilters = \array_filter(
|
||||||
|
$this->filters,
|
||||||
|
function (PointcutFilterInterface $filter) use (
|
||||||
|
$className,
|
||||||
|
$methodName,
|
||||||
|
$methodDeclaringClassName,
|
||||||
|
$pointcutQueryIdentifier
|
||||||
|
): bool {
|
||||||
|
return $filter->matches($className, $methodName, $methodDeclaringClassName, $pointcutQueryIdentifier);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($matchingFilters === []) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function hasRuntimeEvaluationsDefinition(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getRuntimeEvaluationsDefinition(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function reduceTargetClassNames(ClassNameIndex $classNameIndex): ClassNameIndex
|
||||||
|
{
|
||||||
|
if ($this->filters === null) {
|
||||||
|
$this->buildPointcutFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = new ClassNameIndex();
|
||||||
|
foreach ($this->filters as $filter) {
|
||||||
|
$result->applyUnion($filter->reduceTargetClassNames($classNameIndex));
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$this->filters[] = $this->methodTargetExpressionParser->parse(
|
||||||
|
$pointcut,
|
||||||
|
'Settings.yaml at "DigiComp.FlowSessionLock.readOnlyExpressions", key: "' . $key . '"'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
61
Classes/Http/SessionLockRequestComponent.php
Normal file
61
Classes/Http/SessionLockRequestComponent.php
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Flow\Inject(lazy=false)
|
||||||
|
* @var LoggerInterface
|
||||||
|
*/
|
||||||
|
protected $logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Flow\Inject(name="DigiComp.FlowSessionLock:LockFactory")
|
||||||
|
* @var LockFactory
|
||||||
|
*/
|
||||||
|
protected $lockFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function handle(ComponentContext $componentContext)
|
||||||
|
{
|
||||||
|
$sessionCookieName = $this->sessionSettings['name'];
|
||||||
|
$request = $componentContext->getHttpRequest();
|
||||||
|
$cookies = $request->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
|
||||||
|
|
||||||
|
$lock = $this->lockFactory->createLockFromKey($key, 300, false);
|
||||||
|
|
||||||
|
$componentContext->setParameter(SessionLockRequestComponent::class, 'sessionLock', $lock);
|
||||||
|
|
||||||
|
$this->logger->debug('SessionLock: Get ' . $key);
|
||||||
|
$lock->acquire(true);
|
||||||
|
$this->logger->debug('SessionLock: Acquired ' . $key);
|
||||||
|
}
|
||||||
|
}
|
31
Classes/Package.php
Normal file
31
Classes/Package.php
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<?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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
3
Configuration/Development/Settings.yaml
Normal file
3
Configuration/Development/Settings.yaml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
DigiComp:
|
||||||
|
FlowSessionLock:
|
||||||
|
lockStoreDir: '%FLOW_PATH_DATA%Temporary/Development/SessionLocks'
|
12
Configuration/Objects.yaml
Normal file
12
Configuration/Objects.yaml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
DigiComp.FlowSessionLock: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'
|
14
Configuration/Settings.yaml
Normal file
14
Configuration/Settings.yaml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
Neos:
|
||||||
|
Flow:
|
||||||
|
http:
|
||||||
|
chain:
|
||||||
|
preprocess:
|
||||||
|
chain:
|
||||||
|
lockSession:
|
||||||
|
position: 'before getSessionCookieFromRequest'
|
||||||
|
component: 'DigiComp\FlowSessionLock\Http\SessionLockRequestComponent'
|
||||||
|
|
||||||
|
DigiComp:
|
||||||
|
FlowSessionLock:
|
||||||
|
lockStoreDir: '%FLOW_PATH_DATA%Temporary/Production/SessionLocks'
|
||||||
|
readOnlyExpressions: []
|
23
composer.json
Normal file
23
composer.json
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"name": "digicomp/flowsessionlock",
|
||||||
|
"description": "Sesion locking for Neos Flow",
|
||||||
|
"type": "neos-package",
|
||||||
|
"require": {
|
||||||
|
"neos/flow": "^6.2",
|
||||||
|
"php": "^7.4",
|
||||||
|
"symfony/lock": "^5.2"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"DigiComp\\FlowSessionLock\\": "Classes/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"neos": {
|
||||||
|
"package-key": "DigiComp.FlowSessionLock"
|
||||||
|
},
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-develop": "1.0.x-dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue