Skip to content

Commit 749d56a

Browse files
committed
Merge branch '8.4' into 9.0
2 parents 7d1c46b + 153cd40 commit 749d56a

27 files changed

Lines changed: 652 additions & 54 deletions

File tree

Neos.Cache/Classes/Backend/RedisBackend.php

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
* - port: The TCP port of the redis server (will be ignored if connecting to a socket)
3535
* - database: The database index that will be used. By default,
3636
* Redis has 16 databases with index number 0 - 15
37+
* - username: The username needed for the redis clients to connect to the server (hostname)
3738
* - password: The password needed for redis clients to connect to the server (hostname)
3839
* - batchSize: Maximum number of parameters per query for batch operations
3940
*
@@ -69,6 +70,8 @@ class RedisBackend extends IndependentAbstractBackend implements TaggableBackend
6970

7071
protected int $database = 0;
7172

73+
protected string $username = '';
74+
7275
protected string $password = '';
7376

7477
protected int $compressionLevel = 0;
@@ -192,8 +195,8 @@ public function has(string $entryIdentifier): bool
192195
* old entries for the identifier still exist, they are removed as well.
193196
*
194197
* @param string $entryIdentifier Specifies the cache entry to remove
195-
* @throws \RuntimeException
196198
* @return boolean true if (at least) an entry could be removed or false if no entry was found
199+
* @throws \RuntimeException
197200
* @api
198201
*/
199202
public function remove(string $entryIdentifier): bool
@@ -262,8 +265,8 @@ public function collectGarbage(): void
262265
* Removes all cache entries of this cache which are tagged by the specified tag.
263266
*
264267
* @param string $tag The tag the entries must have
265-
* @throws \RuntimeException
266268
* @return integer The number of entries which have been affected by this flush
269+
* @throws \RuntimeException
267270
* @api
268271
*/
269272
public function flushByTag(string $tag): int
@@ -295,8 +298,8 @@ public function flushByTag(string $tag): int
295298
* Removes all cache entries of this cache which are tagged by the specified tags.
296299
*
297300
* @param array<string> $tags The tag the entries must have
298-
* @throws \RuntimeException
299301
* @return integer The number of entries which have been affected by this flush
302+
* @throws \RuntimeException
300303
* @api
301304
*/
302305
public function flushByTags(array $tags): int
@@ -473,6 +476,11 @@ public function setDatabase(int|string $database): void
473476
$this->database = (int)$database;
474477
}
475478

479+
public function setUsername(string $username): void
480+
{
481+
$this->username = $username;
482+
}
483+
476484
public function setPassword(string $password): void
477485
{
478486
$this->password = $password;
@@ -505,7 +513,7 @@ private function uncompress(bool|string $value): bool|string
505513
if (empty($value)) {
506514
return $value;
507515
}
508-
return $this->useCompression() ? gzdecode((string) $value) : $value;
516+
return $this->useCompression() ? gzdecode((string)$value) : $value;
509517
}
510518

511519
private function compress(string $value): string
@@ -540,8 +548,16 @@ private function getRedisClient(): \Redis
540548
}
541549
}
542550

543-
if ($this->password !== '' && !$redis->auth($this->password)) {
544-
throw new CacheException('Redis authentication failed.', 1502366200);
551+
if ($this->username !== '' && $this->password !== '') {
552+
$result = $redis->auth([$this->username, $this->password]);
553+
if ($result === false) {
554+
throw new CacheException(sprintf('Redis authentication failed, using username "%s" and a %s bytes long password.', $this->username, strlen($this->password)), 1725607160);
555+
}
556+
} elseif ($this->password !== '') {
557+
$result = $redis->auth($this->password);
558+
if ($result === false) {
559+
throw new CacheException(sprintf('Redis authentication failed, using a %s bytes long password.', strlen($this->password)), 1502366200);
560+
}
545561
}
546562
$redis->select($this->database);
547563
return $redis;

Neos.Cache/Classes/Backend/SimpleFileBackend.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ private function tryRemoveWithLock(string $fileName): bool
221221
// In the worst case, the unlink will just fail due to concurrent access and the caller needs to deal with that.
222222
return unlink($fileName);
223223
}
224-
$file = fopen($fileName, 'rb');
224+
$file = @fopen($fileName, 'rb');
225225
if ($file === false) {
226226
return false;
227227
}
@@ -542,7 +542,7 @@ protected function writeCacheFile(string $cacheEntryPathAndFilename, string $dat
542542
// It's important that we use the 'c' flag below, as `fopen` will otherwise truncate the file
543543
// if it already exists. If we used 'w' and then didn't get the lock, the file would end up empty.
544544
// https://www.php.net/manual/en/function.fopen.php#refsect1-function.fopen-parameters
545-
$file = fopen($cacheEntryPathAndFilename, 'cb');
545+
$file = @fopen($cacheEntryPathAndFilename, 'cb');
546546
if ($file === false) {
547547
continue;
548548
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
<?php
2+
3+
namespace Neos\Cache\Tests\Functional\Backend;
4+
5+
include_once(__DIR__ . '/../../BaseTestCase.php');
6+
7+
/*
8+
* This file is part of the Neos.Cache package.
9+
*
10+
* (c) Contributors of the Neos Project - www.neos.io
11+
*
12+
* This package is Open Source Software. For the full copyright and license
13+
* information, please view the LICENSE file which was distributed with this
14+
* source code.
15+
*/
16+
17+
use Exception;
18+
use Neos\Cache\Backend\RedisBackend;
19+
use Neos\Cache\EnvironmentConfiguration;
20+
use Neos\Cache\Tests\BaseTestCase;
21+
use RedisException;
22+
use Redis;
23+
24+
/**
25+
* Testcase for the redis cache backend
26+
*
27+
* These tests use an actual Redis instance and will place and remove keys in db 0!
28+
* Since all keys have the 'TestCache:' prefix, running the tests should have
29+
* no side effects on non-related cache entries.
30+
*
31+
* Tests require Redis listening on 127.0.0.1:6379. Furthermore, the following users are required
32+
* default:<no_password>
33+
* test_no_password:<no_password>
34+
* test_password:<secret_password>
35+
*
36+
* The users can be added by:
37+
* acl setuser test_no_password on > ~* &* +@all
38+
* acl setuser test_password on >secret_password ~* &* +@all
39+
*
40+
* @requires extension redis
41+
*/
42+
class RedisBackendAuthenticationTest extends BaseTestCase
43+
{
44+
/**
45+
* @var Redis|null
46+
*/
47+
protected static ?Redis $redis = null;
48+
49+
/**
50+
* Create required Redis users for the test suite
51+
*/
52+
public static function setUpBeforeClass(): void
53+
{
54+
try {
55+
self::$redis = new Redis();
56+
self::$redis->connect('127.0.0.1', 6379);
57+
58+
// clean state before
59+
@self::$redis->rawCommand('ACL', 'DELUSER', 'test_no_password');
60+
@self::$redis->rawCommand('ACL', 'DELUSER', 'test_password');
61+
62+
// add users
63+
self::$redis->rawCommand('ACL', 'SETUSER', 'test_no_password', 'on', '>', '~*', '&*', '+@all');
64+
self::$redis->rawCommand('ACL', 'SETUSER', 'test_password', 'on', '>secret_password', '~*', '&*', '+@all');
65+
} catch (Exception $e) {
66+
self::markTestSkipped('Could not prepare Redis users: ' . $e->getMessage());
67+
}
68+
}
69+
70+
/**
71+
* Tear down Redis users after the suite has run
72+
*/
73+
public static function tearDownAfterClass(): void
74+
{
75+
if (self::$redis instanceof Redis) {
76+
try {
77+
self::$redis->rawCommand('ACL', 'DELUSER', 'test_no_password');
78+
self::$redis->rawCommand('ACL', 'DELUSER', 'test_password');
79+
} catch (Exception $e) {
80+
// ignore
81+
}
82+
self::$redis->close();
83+
self::$redis = null;
84+
}
85+
}
86+
87+
/**
88+
* Set up test case
89+
*
90+
* @return void
91+
*/
92+
protected function setUp(): void
93+
{
94+
$phpredisVersion = phpversion('redis');
95+
if (version_compare($phpredisVersion, '5.0.0', '<')) {
96+
$this->markTestSkipped(sprintf('phpredis extension version %s is not supported. Please update to version 5.0.0+.', $phpredisVersion));
97+
}
98+
try {
99+
if (!@fsockopen('127.0.0.1', 6379)) {
100+
$this->markTestSkipped('redis server not reachable');
101+
}
102+
} catch (Exception $e) {
103+
$this->markTestSkipped('redis server not reachable');
104+
}
105+
}
106+
107+
/**
108+
* @test
109+
*/
110+
public function defaultUserNoPassword()
111+
{
112+
$backend = new RedisBackend(
113+
new EnvironmentConfiguration('Redis a wonderful color Testing', '/some/path', PHP_MAXPATHLEN),
114+
['hostname' => '127.0.0.1', 'database' => 0]
115+
);
116+
$this->assertInstanceOf('Neos\Cache\Backend\RedisBackend', $backend);
117+
}
118+
119+
/**
120+
* @test
121+
*/
122+
public function usernameNoPassword()
123+
{
124+
$backend = new RedisBackend(
125+
new EnvironmentConfiguration('Redis a wonderful color Testing', '/some/path', PHP_MAXPATHLEN),
126+
['hostname' => '127.0.0.1', 'database' => 0, 'username' => 'test_no_password']
127+
);
128+
$this->assertInstanceOf('Neos\Cache\Backend\RedisBackend', $backend);
129+
}
130+
131+
/**
132+
* @test
133+
*/
134+
public function usernamePassword()
135+
{
136+
$backend = new RedisBackend(
137+
new EnvironmentConfiguration('Redis a wonderful color Testing', '/some/path', PHP_MAXPATHLEN),
138+
['hostname' => '127.0.0.1', 'database' => 0, 'username' => 'test_password', 'password' => 'secret_password']
139+
);
140+
$this->assertInstanceOf('Neos\Cache\Backend\RedisBackend', $backend);
141+
}
142+
143+
/**
144+
* @test
145+
*/
146+
public function incorrectUsernamePassword()
147+
{
148+
$this->expectException(RedisException::class);
149+
$backend = new RedisBackend(
150+
new EnvironmentConfiguration('Redis a wonderful color Testing', '/some/path', PHP_MAXPATHLEN),
151+
['hostname' => '127.0.0.1', 'database' => 0, 'username' => 'test_password', 'password' => 'incorrect_password']
152+
);
153+
}
154+
}

Neos.Cache/Tests/Unit/Backend/AbstractBackendTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ protected function setUp(): void
3636
class_exists(AbstractBackend::class);
3737
$className = 'ConcreteBackend_' . md5(uniqid(mt_rand(), true));
3838
eval('
39+
#[\AllowDynamicProperties]
3940
class ' . $className . ' extends \Neos\Cache\Backend\AbstractBackend {
4041
public function set(string $entryIdentifier, string $data, array $tags = [], int $lifetime = NULL): void {}
4142
public function get(string $entryIdentifier): string {}

Neos.Eel/Resources/Private/PHP/php-peg/tests/ParserTestBase.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88

99
class ParserTestWrapper {
1010

11-
function __construct($testcase, $class) {
11+
private mixed $testcase;
12+
private mixed $class;
13+
14+
function __construct($testcase, $class) {
1215
$this->testcase = $testcase;
1316
$this->class = $class;
1417
}

Neos.Flow.Log/Tests/Unit/Backend/AbstractBackendTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ protected function setUp(): void
3232
$this->backendClassName = 'ConcreteBackend_' . md5(uniqid(mt_rand(), true));
3333
eval('
3434
class ' . $this->backendClassName . ' extends \Neos\Flow\Log\Backend\AbstractBackend {
35+
protected $someOption;
3536
public function open(): void {}
3637
public function append(string $message, int $severity = 1, $additionalData = NULL, string $packageKey = NULL, string $className = NULL, string $methodName = NULL): void {}
3738
public function close(): void {}

Neos.Flow/Classes/Aop/Builder/ProxyClassBuilder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ public function buildProxyClass(string $targetClassName, array $aspectContainers
409409
$proxyClass->addProperty($propertyName, var_export($propertyIntroduction->getInitialValue(), true), $propertyIntroduction->getPropertyVisibility(), $propertyIntroduction->getPropertyDocComment());
410410
}
411411

412-
$proxyClass->getMethod('Flow_Aop_Proxy_buildMethodsAndAdvicesArray')->addPreParentCallCode(" if (method_exists(get_parent_class(\$this), 'Flow_Aop_Proxy_buildMethodsAndAdvicesArray') && is_callable([parent::class, 'Flow_Aop_Proxy_buildMethodsAndAdvicesArray'])) parent::Flow_Aop_Proxy_buildMethodsAndAdvicesArray();\n");
412+
$proxyClass->getMethod('Flow_Aop_Proxy_buildMethodsAndAdvicesArray')->addPreParentCallCode(" if (method_exists(parent::class, 'Flow_Aop_Proxy_buildMethodsAndAdvicesArray') && is_callable([parent::class, 'Flow_Aop_Proxy_buildMethodsAndAdvicesArray'])) parent::Flow_Aop_Proxy_buildMethodsAndAdvicesArray();\n");
413413
$proxyClass->getMethod('Flow_Aop_Proxy_buildMethodsAndAdvicesArray')->addPreParentCallCode($this->buildMethodsAndAdvicesArrayCode($interceptedMethods));
414414
$proxyClass->getMethod('Flow_Aop_Proxy_buildMethodsAndAdvicesArray')->setVisibility(ProxyMethodGenerator::VISIBILITY_PROTECTED);
415415

Neos.Flow/Classes/Http/Middleware/TrustedProxiesMiddleware.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,9 +274,20 @@ protected function getTrustedClientIpAddress(ServerRequestInterface $request)
274274

275275
$ipAddress = false;
276276
foreach (array_reverse($trustedIpHeader) as $headerIpAddress) {
277-
$portPosition = strpos($headerIpAddress, ':');
278-
$ipAddress = $portPosition !== false ? substr($headerIpAddress, 0, $portPosition) : $headerIpAddress;
279-
if (!$this->ipIsTrustedProxy($ipAddress)) {
277+
if (preg_match('/^\[([^]]+)]:(\d+)$/', $headerIpAddress, $matches)) { // IPv6 with port [ip]:port
278+
$ipAddress = $matches[1];
279+
} elseif (filter_var($headerIpAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { // IPv6 without port
280+
$ipAddress = $headerIpAddress;
281+
} elseif (strpos($headerIpAddress, ':') !== false) { // IPv4 with port
282+
$parts = explode(':', $headerIpAddress);
283+
if (count($parts) === 2) {
284+
$ipAddress = $parts[0];
285+
}
286+
} elseif (filter_var($headerIpAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { // IPv4 address
287+
$ipAddress = $headerIpAddress;
288+
}
289+
290+
if ($ipAddress !== false && !$this->ipIsTrustedProxy($ipAddress)) {
280291
break;
281292
}
282293
}

Neos.Flow/Classes/Utility/Ip.php

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public static function cidrMatch(string $ip, string $range): bool
4646
$bits = null;
4747
$subnet = $range;
4848
} else {
49-
list($subnet, $bits) = explode('/', $range);
49+
[$subnet, $bits] = explode('/', $range);
5050
}
5151

5252
$ip = inet_pton($ip);
@@ -63,16 +63,16 @@ public static function cidrMatch(string $ip, string $range): bool
6363

6464
if ($bits === null) {
6565
return ($ip === $subnet);
66-
} else {
67-
for ($i = 0; $i < strlen($ip); $i++) {
68-
$mask = 0;
69-
if ($bits > 0) {
70-
$mask = ($bits >= 8) ? 255 : (256 - (1 << (8 - $bits)));
71-
$bits -= 8;
72-
}
73-
if ((ord($ip[$i]) & $mask) !== (ord($subnet[$i]) & $mask)) {
74-
return false;
75-
}
66+
}
67+
68+
for ($i = 0, $ipLength = strlen($ip); $i < $ipLength; $i++) {
69+
$mask = 0;
70+
if ($bits > 0) {
71+
$mask = ($bits >= 8) ? 255 : (256 - (1 << (8 - $bits)));
72+
$bits -= 8;
73+
}
74+
if ((ord($ip[$i]) & $mask) !== (ord($subnet[$i]) & $mask)) {
75+
return false;
7676
}
7777
}
7878
return true;

Neos.Flow/Documentation/TheDefinitiveGuide/PartIII/Caching.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,9 @@ Options
567567
| | unit tests and should not be | | | |
568568
| | used if possible. | | | |
569569
+------------------+---------------------------------+-----------+-----------+-----------+
570+
| username | Username to use for the | No | string | |
571+
| | database connection. | | | |
572+
+------------------+---------------------------------+-----------+-----------+-----------+
570573
| password | Password used to connect to the | No | string | |
571574
| | redis instance if the redis | | | |
572575
| | server needs authentication. | | | |
@@ -577,7 +580,7 @@ Options
577580
| compressionLevel | Set gzip compression level to a | No | integer | 0 |
578581
| | specific value. | | (0 to 9) | |
579582
+------------------+---------------------------------+-----------+-----------+-----------+
580-
| batchSize | Maximum number of parameters | No | int | 100000 |
583+
| batchSize | Maximum number of parameters | No | integer | 100000 |
581584
| | per query for batch operations. | | | |
582585
| | | | | |
583586
| | Redis supports up to | | | |

0 commit comments

Comments
 (0)