From 9af8ccfc03eae20ecac4c8ccc9deae7912c41aaf Mon Sep 17 00:00:00 2001 From: "Phil E. Taylor" Date: Thu, 12 Jan 2023 10:30:38 +0000 Subject: [PATCH] [Messenger] Allow password in redis dsn when using sockets --- .../Redis/Tests/Transport/ConnectionTest.php | 61 +++++++++++++++++++ .../Bridge/Redis/Transport/Connection.php | 27 ++++++-- 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/ConnectionTest.php b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/ConnectionTest.php index 12635ad14b590..07bc9039431be 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/ConnectionTest.php @@ -407,4 +407,65 @@ public function testInvalidSentinelMasterName() Connection::fromDsn(sprintf('%s/messenger-clearlasterror', $master), ['delete_after_ack' => true, 'sentinel_master' => $uid], null); } + + public function testFromDsnOnUnixSocketWithUserAndPassword() + { + $redis = $this->createMock(\Redis::class); + + $redis->expects($this->exactly(1))->method('auth') + ->with(['user', 'password']) + ->willReturn(true); + + $this->assertEquals( + new Connection([ + 'stream' => 'queue', + 'delete_after_ack' => true, + 'host' => '/var/run/redis/redis.sock', + 'port' => 0, + 'user' => 'user', + 'pass' => 'password', + ], $redis), + Connection::fromDsn('redis://user:password@/var/run/redis/redis.sock', ['stream' => 'queue', 'delete_after_ack' => true], $redis) + ); + } + + public function testFromDsnOnUnixSocketWithPassword() + { + $redis = $this->createMock(\Redis::class); + + $redis->expects($this->exactly(1))->method('auth') + ->with('password') + ->willReturn(true); + + $this->assertEquals( + new Connection([ + 'stream' => 'queue', + 'delete_after_ack' => true, + 'host' => '/var/run/redis/redis.sock', + 'port' => 0, + 'pass' => 'password', + ], $redis), + Connection::fromDsn('redis://password@/var/run/redis/redis.sock', ['stream' => 'queue', 'delete_after_ack' => true], $redis) + ); + } + + public function testFromDsnOnUnixSocketWithUser() + { + $redis = $this->createMock(\Redis::class); + + $redis->expects($this->exactly(1))->method('auth') + ->with('user') + ->willReturn(true); + + $this->assertEquals( + new Connection([ + 'stream' => 'queue', + 'delete_after_ack' => true, + 'host' => '/var/run/redis/redis.sock', + 'port' => 0, + 'user' => 'user', + ], $redis), + Connection::fromDsn('redis://user:@/var/run/redis/redis.sock', ['stream' => 'queue', 'delete_after_ack' => true], $redis) + ); + } } diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php index 6c9a8fa6e5c2f..9e21dfaecc6b5 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php @@ -23,6 +23,7 @@ * @author Robin Chalas * * @internal + * * @final */ class Connection @@ -203,13 +204,13 @@ public static function fromDsn(string $dsn, array $options = [], \Redis|\RedisCl }; } + $pass = '' !== ($parsedUrl['pass'] ?? '') ? urldecode($parsedUrl['pass']) : null; + $user = '' !== ($parsedUrl['user'] ?? '') ? urldecode($parsedUrl['user']) : null; + $options['auth'] ??= null !== $pass && null !== $user ? [$user, $pass] : ($pass ?? $user); + if (isset($parsedUrl['host'])) { - $pass = '' !== ($parsedUrl['pass'] ?? '') ? urldecode($parsedUrl['pass']) : null; - $user = '' !== ($parsedUrl['user'] ?? '') ? urldecode($parsedUrl['user']) : null; $options['host'] = $parsedUrl['host'] ?? $options['host']; $options['port'] = $parsedUrl['port'] ?? $options['port']; - // See: https://github.com/phpredis/phpredis/#auth - $options['auth'] ??= null !== $pass && null !== $user ? [$user, $pass] : ($pass ?? $user); $pathParts = explode('/', rtrim($parsedUrl['path'] ?? '', '/')); $options['stream'] = $pathParts[1] ?? $options['stream']; @@ -232,9 +233,27 @@ private static function parseDsn(string $dsn, array &$options): array $url = str_replace($scheme.':', 'file:', $dsn); } + $url = preg_replace_callback('#^'.$scheme.':(//)?(?:(?:(?[^:@]*+):)?(?[^@]*+)@)?#', function ($m) use (&$auth) { + if (isset($m['password'])) { + if (!\in_array($m['user'], ['', 'default'], true)) { + $auth['user'] = $m['user']; + } + + $auth['pass'] = $m['password']; + } + + return 'file:'.($m[1] ?? ''); + }, $url); + if (false === $parsedUrl = parse_url($url)) { throw new InvalidArgumentException(sprintf('The given Redis DSN "%s" is invalid.', $dsn)); } + + if (null !== $auth) { + unset($parsedUrl['user']); // parse_url thinks //0@localhost/ is a username of "0"! doh! + $parsedUrl += ($auth ?? []); // But don't worry as $auth array will have user, user/pass or pass as needed + } + if (isset($parsedUrl['query'])) { parse_str($parsedUrl['query'], $dsnOptions); $options = array_merge($options, $dsnOptions);