Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for specific username setting in RedisCluster #122

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/.phpunit.result.cache
/.phpunit.cache
/.psalm-cache
/docs/html/
/laminas-mkdoc-theme.tgz
/laminas-mkdoc-theme/
/vendor/
/.phpcs-cache
/.idea
6 changes: 1 addition & 5 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="5.24.0@462c80e31c34e58cc4f750c656be3927e80e550e">
<file src="src/Exception/InvalidRedisClusterConfigurationException.php">
<PossiblyUnusedMethod>
<code><![CDATA[fromInvalidSeedsConfiguration]]></code>
</PossiblyUnusedMethod>
</file>
<file src="src/Redis.php">
<InvalidOperand>
<code><![CDATA[$pttl]]></code>
Expand Down Expand Up @@ -52,6 +47,7 @@
<code><![CDATA[setSeeds]]></code>
<code><![CDATA[setSslContext]]></code>
<code><![CDATA[setTimeout]]></code>
<code><![CDATA[setUser]]></code>
</PossiblyUnusedMethod>
</file>
<file src="src/RedisOptions.php">
Expand Down
51 changes: 51 additions & 0 deletions src/RedisAuthenticationInfo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace Laminas\Cache\Storage\Adapter;

/**
* @internal
*/
final class RedisAuthenticationInfo
{
/**
* @param non-empty-string|null $username
* @param non-empty-string $password
*/
private function __construct(
private readonly string|null $username,
private readonly string $password,
) {
}

public static function fromOptions(RedisClusterOptions|RedisOptions $options): self|null
{
$username = $options->getUser();
$password = $options->getPassword();

if ($password === null || $password === '') {
return null;
}

if ($username === null || $username === '') {
return new self(null, $password);
}

return new self($username, $password);
}

/**
* @see https://github.com/phpredis/phpredis/blob/4cd3f59356582a65aec1cceed44741bd5d161d9e/library.c#L4382
*
* @return array{0:non-empty-string,1?:non-empty-string}
*/
public function toRedisAuthInfo(): array
{
if ($this->username === null) {
return [$this->password];
}

return [$this->username, $this->password];
}
}
15 changes: 15 additions & 0 deletions src/RedisClusterOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ final class RedisClusterOptions extends AdapterOptions
/** @psalm-var array<positive-int,mixed> */
private array $libOptions = [];

private string $user = '';

private string $password = '';

private ?SslContext $sslContext = null;
Expand Down Expand Up @@ -248,6 +250,19 @@ public function getLibOption(int $option, mixed $default = null): mixed
return $this->libOptions[$option] ?? $default;
}

public function getUser(): string
{
return $this->user;
}

/**
* @psalm-param non-empty-string $user
*/
public function setUser(string $user): void
{
$this->user = $user;
}

public function getPassword(): string
{
return $this->password;
Expand Down
30 changes: 18 additions & 12 deletions src/RedisClusterOptionsFromIni.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Laminas\Cache\Storage\Adapter;

use InvalidArgumentException;
use Laminas\Cache\Storage\Adapter\Exception\InvalidRedisClusterConfigurationException;
use Webmozart\Assert\Assert;

Expand All @@ -28,7 +29,7 @@ final class RedisClusterOptionsFromIni
private array $readTimeoutByName;

/** @var array<non-empty-string,string> */
private array $authenticationByName;
private array $passwordByName;

public function __construct()
{
Expand All @@ -43,12 +44,17 @@ public function __construct()

$seedsByName = [];
parse_str($seedsConfiguration, $parsedSeedsByName);
foreach ($parsedSeedsByName as $name => $seeds) {
assert(is_string($name) && $name !== '');
Assert::isNonEmptyList($seeds);
Assert::allStringNotEmpty($seeds);
$seedsByName[$name] = $seeds;
try {
foreach ($parsedSeedsByName as $name => $seeds) {
assert(is_string($name) && $name !== '');
Assert::isNonEmptyList($seeds);
Assert::allStringNotEmpty($seeds);
$seedsByName[$name] = $seeds;
}
} catch (InvalidArgumentException) {
throw InvalidRedisClusterConfigurationException::fromInvalidSeedsConfiguration($seedsConfiguration);
}

$this->seedsByName = $seedsByName;

$timeoutConfiguration = ini_get('redis.clusters.timeout');
Expand Down Expand Up @@ -88,16 +94,16 @@ public function __construct()
$authenticationConfiguration = '';
}

$authenticationByName = [];
$passwordByName = [];
boesing marked this conversation as resolved.
Show resolved Hide resolved
if ($authenticationConfiguration !== '') {
parse_str($authenticationConfiguration, $parsedAuthenticationByName);
foreach ($parsedAuthenticationByName as $name => $authentication) {
assert(is_string($name) && $name !== '' && is_string($authentication));
$authenticationByName[$name] = $authentication;
foreach ($parsedAuthenticationByName as $name => $password) {
assert(is_string($name) && $name !== '' && is_string($password));
$passwordByName[$name] = $password;
}
}

$this->authenticationByName = $authenticationByName;
$this->passwordByName = $passwordByName;
}

/**
Expand Down Expand Up @@ -135,6 +141,6 @@ public function getReadTimeout(string $name, float $fallback): float
*/
public function getPasswordByName(string $name, string $fallback): string
{
return $this->authenticationByName[$name] ?? $fallback;
return $this->passwordByName[$name] ?? $fallback;
}
}
34 changes: 17 additions & 17 deletions src/RedisClusterResourceManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,35 +48,33 @@ public function getResource(): RedisClusterFromExtension

private function createRedisResource(RedisClusterOptions $options): RedisClusterFromExtension
{
$authenticationInfo = RedisAuthenticationInfo::fromOptions($options);

if ($options->hasName()) {
return $this->createRedisResourceFromName(
$options->getName(),
$options->getTimeout(),
$options->getReadTimeout(),
$options->isPersistent(),
$options->getPassword(),
$authenticationInfo,
$options->getSslContext()
);
}

$password = $options->getPassword();
if ($password === '') {
$password = null;
}

/**
* Psalm currently (<= 5.23.1) uses an outdated (phpredis < 5.3.2) constructor signature for the RedisCluster
* Psalm currently (<= 5.26.1) uses an outdated (phpredis < 5.3.2) constructor signature for the RedisCluster
* class in the phpredis extension.
*
* @psalm-suppress TooManyArguments https://github.com/vimeo/psalm/pull/10862
* @psalm-suppress TooManyArguments
* @psalm-suppress InvalidArgument
*/
return new RedisClusterFromExtension(
null,
$options->getSeeds(),
$options->getTimeout(),
$options->getReadTimeout(),
$options->isPersistent(),
$password,
$authenticationInfo?->toRedisAuthInfo(),
$options->getSslContext()?->toSslContextArray()
);
}
Expand All @@ -89,7 +87,7 @@ private function createRedisResourceFromName(
float $fallbackTimeout,
float $fallbackReadTimeout,
bool $persistent,
string $fallbackPassword,
RedisAuthenticationInfo|null $fallbackAuthentication,
?SslContext $sslContext
): RedisClusterFromExtension {
try {
Expand All @@ -100,24 +98,26 @@ private function createRedisResourceFromName(
throw new InvalidRedisClusterConfigurationException($throwable->getMessage(), previous: $throwable);
}

$seeds = $options->getSeeds($name);
$timeout = $options->getTimeout($name, $fallbackTimeout);
$readTimeout = $options->getReadTimeout($name, $fallbackReadTimeout);
$password = $options->getPasswordByName($name, $fallbackPassword);
$seeds = $options->getSeeds($name);
$timeout = $options->getTimeout($name, $fallbackTimeout);
$readTimeout = $options->getReadTimeout($name, $fallbackReadTimeout);
$password = $options->getPasswordByName($name, '');
$authentication = $password === '' ? $fallbackAuthentication?->toRedisAuthInfo() : $password;

/**
* Psalm currently (<= 5.23.1) uses an outdated (phpredis < 5.3.2) constructor signature for the RedisCluster
* Psalm currently (<= 5.26.1) uses an outdated (phpredis < 5.3.2) constructor signature for the RedisCluster
* class in the phpredis extension.
*
* @psalm-suppress TooManyArguments https://github.com/vimeo/psalm/pull/10862
* @psalm-suppress TooManyArguments
* @psalm-suppress PossiblyInvalidArgument
*/
return new RedisClusterFromExtension(
null,
$seeds,
$timeout,
$readTimeout,
$persistent,
$password,
$authentication,
$sslContext?->toSslContextArray()
);
}
Expand Down
6 changes: 6 additions & 0 deletions src/RedisOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,9 @@ public function setPassword(string $password): void
$this->password = $password;
}

/**
* @return non-empty-string|null
*/
public function getPassword(): string|null
{
return $this->password;
Expand All @@ -242,6 +245,9 @@ public function setUser(string $user): void
$this->user = $user;
}

/**
* @return non-empty-string|null
*/
public function getUser(): ?string
{
return $this->user;
Expand Down
18 changes: 2 additions & 16 deletions src/RedisResourceManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,28 +57,14 @@ private function createRedisFromExtension(RedisOptions $options): RedisFromExten
$port = $server['port'] ?? self::DEFAULT_REDIS_PORT;
}

$authentication = [];
$user = $options->getUser();
$password = $options->getPassword();

if ($user !== null) {
$authentication[] = $user;
}

if ($password !== null) {
$authentication[] = $password;
}

if ($authentication === []) {
$authentication = null;
}
$authenticationInfo = RedisAuthenticationInfo::fromOptions($options);

$resourceOptions = [
'host' => $host,
'port' => $port,
'connectTimeout' => $server['timeout'] ?? null,
'persistent' => $options->getPersistentId() ?? $options->isPersistent(),
'auth' => $authentication,
'auth' => $authenticationInfo?->toRedisAuthInfo(),
];

$resource = new RedisFromExtension(array_filter($resourceOptions, fn (mixed $value) => $value !== null));
Expand Down
Loading