Compare commits
No commits in common. "master" and "2.1.2" have entirely different histories.
11 changed files with 24 additions and 229 deletions
|
@ -1,8 +0,0 @@
|
||||||
pipeline:
|
|
||||||
code-style:
|
|
||||||
image: composer
|
|
||||||
commands:
|
|
||||||
- composer global config repositories.repo-name vcs https://git.digital-competence.de/Packages/php-codesniffer
|
|
||||||
- composer global config --no-plugins allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
|
|
||||||
- composer global require digicomp/php-codesniffer:@dev
|
|
||||||
- composer global exec -- phpcs --runtime-set ignore_warnings_on_exit 1 --standard=DigiComp Classes/ Tests/
|
|
|
@ -1,31 +0,0 @@
|
||||||
workspace:
|
|
||||||
base: /woodpecker
|
|
||||||
path: package
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- FLOW_VERSION: 7.3
|
|
||||||
PHP_VERSION: 7.4
|
|
||||||
- FLOW_VERSION: 7.3
|
|
||||||
PHP_VERSION: 8.1
|
|
||||||
- FLOW_VERSION: 8.2
|
|
||||||
PHP_VERSION: 8.1
|
|
||||||
|
|
||||||
|
|
||||||
pipeline:
|
|
||||||
functional-tests:
|
|
||||||
image: "thecodingmachine/php:${PHP_VERSION}-v4-cli"
|
|
||||||
environment:
|
|
||||||
# Enable the PDO_SQLITE extension
|
|
||||||
- "PHP_EXTENSION_PDO_SQLITE=1"
|
|
||||||
- "FLOW_VERSION=${FLOW_VERSION}"
|
|
||||||
- "NEOS_BUILD_DIR=/woodpecker/Build-${FLOW_VERSION}"
|
|
||||||
commands:
|
|
||||||
- "sudo mkdir $NEOS_BUILD_DIR"
|
|
||||||
- "sudo chown -R docker:docker $NEOS_BUILD_DIR"
|
|
||||||
- "cd $NEOS_BUILD_DIR"
|
|
||||||
- "composer create-project --no-install neos/flow-base-distribution:^$FLOW_VERSION ."
|
|
||||||
- "composer config repositories.repo-name path /woodpecker/package"
|
|
||||||
- "composer config --no-plugins allow-plugins.neos/composer-plugin true"
|
|
||||||
- "composer require digicomp/flowsessionlock:@dev"
|
|
||||||
- "bin/phpunit --configuration Build/BuildEssentials/PhpUnit/FunctionalTests.xml Packages/Application/DigiComp.FlowSessionLock/Tests/Functional"
|
|
|
@ -8,7 +8,6 @@ namespace DigiComp\FlowSessionLock\Annotations;
|
||||||
* @Annotation
|
* @Annotation
|
||||||
* @Target({"METHOD"})
|
* @Target({"METHOD"})
|
||||||
*/
|
*/
|
||||||
#[\Attribute(\Attribute::TARGET_METHOD)]
|
final class ReadOnly
|
||||||
final class Unlock
|
|
||||||
{
|
{
|
||||||
}
|
}
|
|
@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace DigiComp\FlowSessionLock\Aspects;
|
namespace DigiComp\FlowSessionLock\Aspects;
|
||||||
|
|
||||||
use DigiComp\FlowSessionLock\Http\SessionLockRequestMiddleware;
|
use DigiComp\FlowSessionLock\Http\SessionLockRequestComponent;
|
||||||
use Neos\Flow\Annotations as Flow;
|
use Neos\Flow\Annotations as Flow;
|
||||||
use Neos\Flow\Aop\JoinPointInterface;
|
use Neos\Flow\Aop\JoinPointInterface;
|
||||||
use Neos\Flow\Core\Bootstrap;
|
use Neos\Flow\Core\Bootstrap;
|
||||||
|
@ -36,7 +36,7 @@ class ReadOnlyAspect
|
||||||
protected bool $readOnly = false;
|
protected bool $readOnly = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Flow\Around("methodAnnotatedWith(DigiComp\FlowSessionLock\Annotations\Unlock) || filter(DigiComp\FlowSessionLock\Aspects\ReadOnlyFilter)")
|
* @Flow\Around("methodAnnotatedWith(DigiComp\FlowSessionLock\Annotations\ReadOnly) || filter(DigiComp\FlowSessionLock\Aspects\ReadOnlyFilter)")
|
||||||
* @param JoinPointInterface $joinPoint
|
* @param JoinPointInterface $joinPoint
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
|
@ -52,8 +52,9 @@ class ReadOnlyAspect
|
||||||
$this->readOnly = true;
|
$this->readOnly = true;
|
||||||
|
|
||||||
/** @var Lock|null $lock */
|
/** @var Lock|null $lock */
|
||||||
$lock = $activeRequestHandler->getHttpRequest()->getAttribute(
|
$lock = $activeRequestHandler->getComponentContext()->getParameter(
|
||||||
SessionLockRequestMiddleware::class . '.' . SessionLockRequestMiddleware::PARAMETER_NAME
|
SessionLockRequestComponent::class,
|
||||||
|
SessionLockRequestComponent::PARAMETER_NAME
|
||||||
);
|
);
|
||||||
if ($lock !== null) {
|
if ($lock !== null) {
|
||||||
$this->logger->debug('SessionLock: Release, as this is marked read only.');
|
$this->logger->debug('SessionLock: Release, as this is marked read only.');
|
||||||
|
|
|
@ -5,16 +5,14 @@ declare(strict_types=1);
|
||||||
namespace DigiComp\FlowSessionLock\Http;
|
namespace DigiComp\FlowSessionLock\Http;
|
||||||
|
|
||||||
use Neos\Flow\Annotations as Flow;
|
use Neos\Flow\Annotations as Flow;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Neos\Flow\Http\Component\ComponentContext;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Neos\Flow\Http\Component\ComponentInterface;
|
||||||
use Psr\Http\Server\MiddlewareInterface;
|
|
||||||
use Psr\Http\Server\RequestHandlerInterface;
|
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\Lock\Exception\LockAcquiringException;
|
use Symfony\Component\Lock\Exception\LockAcquiringException;
|
||||||
use Symfony\Component\Lock\Key;
|
use Symfony\Component\Lock\Key;
|
||||||
use Symfony\Component\Lock\LockFactory;
|
use Symfony\Component\Lock\LockFactory;
|
||||||
|
|
||||||
class SessionLockRequestMiddleware implements MiddlewareInterface
|
class SessionLockRequestComponent implements ComponentInterface
|
||||||
{
|
{
|
||||||
public const PARAMETER_NAME = 'sessionLock';
|
public const PARAMETER_NAME = 'sessionLock';
|
||||||
|
|
||||||
|
@ -57,13 +55,13 @@ class SessionLockRequestMiddleware implements MiddlewareInterface
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
public function handle(ComponentContext $componentContext): void
|
||||||
{
|
{
|
||||||
$sessionCookieName = $this->sessionSettings['name'];
|
$sessionCookieName = $this->sessionSettings['name'];
|
||||||
|
|
||||||
$cookies = $request->getCookieParams();
|
$cookies = $componentContext->getHttpRequest()->getCookieParams();
|
||||||
if (!isset($cookies[$sessionCookieName])) {
|
if (!isset($cookies[$sessionCookieName])) {
|
||||||
return $handler->handle($request);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: sessionIdentifier might be wrong, probably it should probably be storage identifier
|
// TODO: sessionIdentifier might be wrong, probably it should probably be storage identifier
|
||||||
|
@ -71,7 +69,7 @@ class SessionLockRequestMiddleware implements MiddlewareInterface
|
||||||
|
|
||||||
$lock = $this->lockFactory->createLockFromKey($key, $this->timeToLive, $this->autoRelease);
|
$lock = $this->lockFactory->createLockFromKey($key, $this->timeToLive, $this->autoRelease);
|
||||||
|
|
||||||
$request = $request->withAttribute(SessionLockRequestMiddleware::class . '.' . static::PARAMETER_NAME, $lock);
|
$componentContext->setParameter(SessionLockRequestComponent::class, static::PARAMETER_NAME, $lock);
|
||||||
|
|
||||||
$this->logger->debug('SessionLock: Try to get "' . $key . '"');
|
$this->logger->debug('SessionLock: Try to get "' . $key . '"');
|
||||||
$timedOut = \time() + $this->secondsToWait;
|
$timedOut = \time() + $this->secondsToWait;
|
||||||
|
@ -85,6 +83,5 @@ class SessionLockRequestMiddleware implements MiddlewareInterface
|
||||||
\usleep(100000);
|
\usleep(100000);
|
||||||
}
|
}
|
||||||
$this->logger->debug('SessionLock: Acquired "' . $key . '"');
|
$this->logger->debug('SessionLock: Acquired "' . $key . '"');
|
||||||
return $handler->handle($request);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -9,7 +9,9 @@ DigiComp:
|
||||||
Neos:
|
Neos:
|
||||||
Flow:
|
Flow:
|
||||||
http:
|
http:
|
||||||
middlewares:
|
chain:
|
||||||
|
preprocess:
|
||||||
|
chain:
|
||||||
lockSession:
|
lockSession:
|
||||||
position: "before session"
|
position: "before getSessionCookieFromRequest"
|
||||||
middleware: "DigiComp\\FlowSessionLock\\Http\\SessionLockRequestMiddleware"
|
component: "DigiComp\\FlowSessionLock\\Http\\SessionLockRequestComponent"
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
DigiComp:
|
DigiComp:
|
||||||
FlowSessionLock:
|
FlowSessionLock:
|
||||||
lockStoreConnection: "flock://%FLOW_PATH_DATA%Temporary/Testing/SessionLocks/"
|
lockStoreConnection: "flock://%FLOW_PATH_DATA%Temporary/Testing/SessionLocks/"
|
||||||
readOnlyExpressions:
|
|
||||||
TestUnprotected: "method(DigiComp\\FlowSessionLock\\Tests\\Functional\\Fixtures\\Controller\\ExampleController->unprotectedByConfigurationAction())"
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
DigiComp.FlowSessionLock
|
DigiComp.FlowSessionLock
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
By default, the session established by Flow is not "protected" in any way. This package restricts every request to load
|
By default, the session established by Flow is not "protected" in any way. This package restricts every request to load
|
||||||
the session only, if there are no other requests having it in access currently. It allows to set custom pointcut which
|
the session only, if there are no other requests having it in access currently. It allows to set custom pointcut which
|
||||||
will set the session in "ReadOnly" mode, which allows concurrent requests to read, but disallows the current request to
|
will set the session in "ReadOnly" mode, which allows concurrent requests to read, but disallows the current request to
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace DigiComp\FlowSessionLock\Tests\Functional\Fixtures\Controller;
|
|
||||||
|
|
||||||
use DigiComp\FlowSessionLock\Annotations as FlowSessionLock;
|
|
||||||
use Neos\Flow\Annotations as Flow;
|
|
||||||
use Neos\Flow\Mvc\Controller\ActionController;
|
|
||||||
|
|
||||||
class ExampleController extends ActionController
|
|
||||||
{
|
|
||||||
public const CONTROLLER_TIME = 200;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Flow\Session(autoStart=true);
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function protectedAction()
|
|
||||||
{
|
|
||||||
\usleep(static::CONTROLLER_TIME * 1000);
|
|
||||||
return 'Hello World!';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Flow\Session(autoStart=true);
|
|
||||||
* @FlowSessionLock\Unlock
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function unprotectedByAnnotationAction()
|
|
||||||
{
|
|
||||||
\usleep(static::CONTROLLER_TIME * 1000);
|
|
||||||
return 'Hello World!';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Flow\Session(autoStart=true);
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function unprotectedByConfigurationAction()
|
|
||||||
{
|
|
||||||
\usleep(static::CONTROLLER_TIME * 1000);
|
|
||||||
return 'Hello World!';
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,103 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace DigiComp\FlowSessionLock\Tests\Functional;
|
|
||||||
|
|
||||||
use DigiComp\FlowSessionLock\Tests\Functional\Fixtures\Controller\ExampleController;
|
|
||||||
use GuzzleHttp\Psr7\Uri;
|
|
||||||
use Neos\Flow\Http\Cookie;
|
|
||||||
use Neos\Flow\Mvc\Routing\Route;
|
|
||||||
use Neos\Flow\Tests\FunctionalTestCase;
|
|
||||||
use Psr\Http\Message\ServerRequestFactoryInterface;
|
|
||||||
|
|
||||||
class SessionLockRequestComponentTest extends FunctionalTestCase
|
|
||||||
{
|
|
||||||
protected ServerRequestFactoryInterface $serverRequestFactory;
|
|
||||||
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
$this->serverRequestFactory = $this->objectManager->get(ServerRequestFactoryInterface::class);
|
|
||||||
$route = new Route();
|
|
||||||
$route->setName('Functional Test - SessionRequestComponent::Restricted');
|
|
||||||
$route->setUriPattern('test/sessionlock/{@action}');
|
|
||||||
$route->setDefaults([
|
|
||||||
'@package' => 'DigiComp.FlowSessionLock',
|
|
||||||
'@subpackage' => 'Tests\Functional\Fixtures',
|
|
||||||
'@controller' => 'Example',
|
|
||||||
'@action' => 'protected',
|
|
||||||
'@format' => 'html',
|
|
||||||
]);
|
|
||||||
$route->setAppendExceedingArguments(true);
|
|
||||||
$this->router->addRoute($route);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function expectedDuration(): array
|
|
||||||
{
|
|
||||||
$parallelChecker = function ($allRequests, $oneRequest) {
|
|
||||||
self::assertGreaterThan(ExampleController::CONTROLLER_TIME, $oneRequest * 1000);
|
|
||||||
self::assertLessThan(ExampleController::CONTROLLER_TIME * 4, $allRequests * 1000);
|
|
||||||
};
|
|
||||||
return [
|
|
||||||
[
|
|
||||||
'http://localhost/test/sessionlock/protected',
|
|
||||||
function ($allRequests, $oneRequest) {
|
|
||||||
self::assertGreaterThan(ExampleController::CONTROLLER_TIME, $oneRequest * 1000);
|
|
||||||
self::assertGreaterThan(ExampleController::CONTROLLER_TIME * 4, $allRequests * 1000);
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'http://localhost/test/sessionlock/unprotectedbyannotation',
|
|
||||||
$parallelChecker,
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'http://localhost/test/sessionlock/unprotectedbyconfiguration',
|
|
||||||
$parallelChecker,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dataProvider expectedDuration
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function itDoesNotAllowToEnterMoreThanOneWithTheSameSession(string $url, \Closure $checker): void
|
|
||||||
{
|
|
||||||
// Functional tests are currently broken, until a version containing
|
|
||||||
// https://github.com/neos/flow-development-collection/commit/bebfc4e6566bc4ba2ba28330344105adb2d6ada0
|
|
||||||
// gets released
|
|
||||||
$request = $this->serverRequestFactory
|
|
||||||
->createServerRequest('GET', new Uri($url));
|
|
||||||
$start = \microtime(true);
|
|
||||||
$response = $this->browser->sendRequest($request);
|
|
||||||
$neededForOne = \microtime(true) - $start;
|
|
||||||
|
|
||||||
$sessionCookies = \array_map(static function ($cookie) {
|
|
||||||
return Cookie::createFromRawSetCookieHeader($cookie);
|
|
||||||
}, $response->getHeader('Set-Cookie'));
|
|
||||||
self::assertNotEmpty($sessionCookies);
|
|
||||||
|
|
||||||
$cookies = \array_reduce($sessionCookies, static function ($out, $cookie) {
|
|
||||||
$out[$cookie->getName()] = $cookie->getValue();
|
|
||||||
return $out;
|
|
||||||
}, []);
|
|
||||||
$nextRequest = $this->serverRequestFactory
|
|
||||||
->createServerRequest('GET', new Uri($url))
|
|
||||||
->withCookieParams($cookies);
|
|
||||||
$childs = [];
|
|
||||||
$start = \microtime(true);
|
|
||||||
for ($i = 0; $i < 4; $i++) {
|
|
||||||
$child = \pcntl_fork();
|
|
||||||
if ($child === 0) {
|
|
||||||
$this->browser->sendRequest($nextRequest);
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
$childs[] = $child;
|
|
||||||
}
|
|
||||||
foreach ($childs as $child) {
|
|
||||||
\pcntl_waitpid($child, $status);
|
|
||||||
}
|
|
||||||
$neededForAll = \microtime(true) - $start;
|
|
||||||
|
|
||||||
$checker($neededForAll, $neededForOne);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,12 +3,9 @@
|
||||||
"description": "Session locking for Neos Flow - it secures the session becoming corrupted by concurrent access to the same session by different requests",
|
"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",
|
"type": "neos-package",
|
||||||
"require": {
|
"require": {
|
||||||
"neos/flow": "^7.3.0 | ^8.2",
|
"neos/flow": "^6.3.0",
|
||||||
"php": "^7.4 | ^8.1",
|
"php": ">=7.4",
|
||||||
"symfony/lock": "^5.2.0 | ^6.2.0"
|
"symfony/lock": "^5.2.0"
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"ext-pcntl": "*"
|
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
@ -20,20 +17,8 @@
|
||||||
"package-key": "DigiComp.FlowSessionLock"
|
"package-key": "DigiComp.FlowSessionLock"
|
||||||
},
|
},
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-develop": "4.0.x-dev",
|
"dev-develop": "2.0.x-dev"
|
||||||
"dev-version/2.x-dev": "2.1.x-dev"
|
}
|
||||||
},
|
|
||||||
"applied-flow-migrations": [
|
|
||||||
"Neos.SwiftMailer-20161130105617",
|
|
||||||
"Neos.Flow-20180415105700",
|
|
||||||
"Neos.Flow-20190425144900",
|
|
||||||
"Neos.Flow-20190515215000",
|
|
||||||
"Neos.Flow-20200813181400",
|
|
||||||
"Neos.Flow-20201003165200",
|
|
||||||
"Neos.Flow-20201109224100",
|
|
||||||
"Neos.Flow-20201205172733",
|
|
||||||
"Neos.Flow-20201207104500"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Reference in a new issue