diff --git a/Neos.Cache/Classes/Backend/RedisBackend.php b/Neos.Cache/Classes/Backend/RedisBackend.php index ee0fb006c0..66a3741f4d 100644 --- a/Neos.Cache/Classes/Backend/RedisBackend.php +++ b/Neos.Cache/Classes/Backend/RedisBackend.php @@ -34,6 +34,7 @@ * - port: The TCP port of the redis server (will be ignored if connecting to a socket) * - database: The database index that will be used. By default, * Redis has 16 databases with index number 0 - 15 + * - username: The username needed for the redis clients to connect to the server (hostname) * - password: The password needed for redis clients to connect to the server (hostname) * - batchSize: Maximum number of parameters per query for batch operations * @@ -69,6 +70,8 @@ class RedisBackend extends IndependentAbstractBackend implements TaggableBackend protected int $database = 0; + protected string $username = ''; + protected string $password = ''; protected int $compressionLevel = 0; @@ -192,8 +195,8 @@ public function has(string $entryIdentifier): bool * old entries for the identifier still exist, they are removed as well. * * @param string $entryIdentifier Specifies the cache entry to remove - * @throws \RuntimeException * @return boolean true if (at least) an entry could be removed or false if no entry was found + * @throws \RuntimeException * @api */ public function remove(string $entryIdentifier): bool @@ -257,8 +260,8 @@ public function collectGarbage(): void * Removes all cache entries of this cache which are tagged by the specified tag. * * @param string $tag The tag the entries must have - * @throws \RuntimeException * @return integer The number of entries which have been affected by this flush + * @throws \RuntimeException * @api */ public function flushByTag(string $tag): int @@ -290,8 +293,8 @@ public function flushByTag(string $tag): int * Removes all cache entries of this cache which are tagged by the specified tags. * * @param array $tags The tag the entries must have - * @throws \RuntimeException * @return integer The number of entries which have been affected by this flush + * @throws \RuntimeException * @api */ public function flushByTags(array $tags): int @@ -468,6 +471,11 @@ public function setDatabase(int|string $database): void $this->database = (int)$database; } + public function setUsername(string $username): void + { + $this->username = $username; + } + public function setPassword(string $password): void { $this->password = $password; @@ -500,7 +508,7 @@ private function uncompress(bool|string $value): bool|string if (empty($value)) { return $value; } - return $this->useCompression() ? gzdecode((string) $value) : $value; + return $this->useCompression() ? gzdecode((string)$value) : $value; } private function compress(string $value): string @@ -535,8 +543,16 @@ private function getRedisClient(): \Redis } } - if ($this->password !== '' && !$redis->auth($this->password)) { - throw new CacheException('Redis authentication failed.', 1502366200); + if ($this->username !== '' && $this->password !== '') { + $result = $redis->auth([$this->username, $this->password]); + if ($result === false) { + throw new CacheException(sprintf('Redis authentication failed, using username "%s" and a %s bytes long password.', $this->username, strlen($this->password)), 1725607160); + } + } elseif ($this->password !== '') { + $result = $redis->auth($this->password); + if ($result === false) { + throw new CacheException(sprintf('Redis authentication failed, using a %s bytes long password.', strlen($this->password)), 1502366200); + } } $redis->select($this->database); return $redis; diff --git a/Neos.Cache/Tests/Functional/Backend/RedisBackendAuthenticationTest.php b/Neos.Cache/Tests/Functional/Backend/RedisBackendAuthenticationTest.php new file mode 100644 index 0000000000..2d977df52f --- /dev/null +++ b/Neos.Cache/Tests/Functional/Backend/RedisBackendAuthenticationTest.php @@ -0,0 +1,111 @@ + + * test_no_password: + * test_password: + * + * The users can be added by: + * acl setuser test_no_password on > ~* &* +@all + * acl setuser test_password on >secret_password ~* &* +@all + * + * @requires extension redis + */ +class RedisBackendAuthenticationTest extends BaseTestCase +{ + /** + * Set up test case + * + * @return void + */ + protected function setUp(): void + { + $phpredisVersion = phpversion('redis'); + if (version_compare($phpredisVersion, '5.0.0', '<')) { + $this->markTestSkipped(sprintf('phpredis extension version %s is not supported. Please update to version 5.0.0+.', $phpredisVersion)); + } + try { + if (!@fsockopen('127.0.0.1', 6379)) { + $this->markTestSkipped('redis server not reachable'); + } + } catch (Exception $e) { + $this->markTestSkipped('redis server not reachable'); + } + } + + + /** + * @test + */ + public function defaultUserNoPassword() + { + $backend = new RedisBackend( + new EnvironmentConfiguration('Redis a wonderful color Testing', '/some/path', PHP_MAXPATHLEN), + ['hostname' => '127.0.0.1', 'database' => 0] + ); + $this->assertInstanceOf('Neos\Cache\Backend\RedisBackend', $backend); + } + + /** + * @test + */ + public function usernameNoPassword() + { + $backend = new RedisBackend( + new EnvironmentConfiguration('Redis a wonderful color Testing', '/some/path', PHP_MAXPATHLEN), + ['hostname' => '127.0.0.1', 'database' => 0, 'username' => 'test_no_password'] + ); + $this->assertInstanceOf('Neos\Cache\Backend\RedisBackend', $backend); + } + + /** + * @test + */ + public function usernamePassword() + { + $backend = new RedisBackend( + new EnvironmentConfiguration('Redis a wonderful color Testing', '/some/path', PHP_MAXPATHLEN), + ['hostname' => '127.0.0.1', 'database' => 0, 'username' => 'test_password', 'password' => 'secret_password'] + ); + $this->assertInstanceOf('Neos\Cache\Backend\RedisBackend', $backend); + } + + /** + * @test + */ + public function incorrectUsernamePassword() + { + $this->expectException(RedisException::class); + $backend = new RedisBackend( + new EnvironmentConfiguration('Redis a wonderful color Testing', '/some/path', PHP_MAXPATHLEN), + ['hostname' => '127.0.0.1', 'database' => 0, 'username' => 'test_password', 'password' => 'incorrect_password'] + ); + } +} diff --git a/Neos.Flow/Documentation/TheDefinitiveGuide/PartIII/Caching.rst b/Neos.Flow/Documentation/TheDefinitiveGuide/PartIII/Caching.rst index f19665561a..58b60d4281 100644 --- a/Neos.Flow/Documentation/TheDefinitiveGuide/PartIII/Caching.rst +++ b/Neos.Flow/Documentation/TheDefinitiveGuide/PartIII/Caching.rst @@ -567,6 +567,9 @@ Options | | unit tests and should not be | | | | | | used if possible. | | | | +------------------+---------------------------------+-----------+-----------+-----------+ +| username | Username to use for the | No | string | | +| | database connection. | | | | ++------------------+---------------------------------+-----------+-----------+-----------+ | password | Password used to connect to the | No | string | | | | redis instance if the redis | | | | | | server needs authentication. | | | | @@ -577,7 +580,7 @@ Options | compressionLevel | Set gzip compression level to a | No | integer | 0 | | | specific value. | | (0 to 9) | | +------------------+---------------------------------+-----------+-----------+-----------+ -| batchSize | Maximum number of parameters | No | int | 100000 | +| batchSize | Maximum number of parameters | No | integer | 100000 | | | per query for batch operations. | | | | | | | | | | | | Redis supports up to | | | |