Skip to content

Commit

Permalink
Support username for ACL auth for Redis v6+ (#161)
Browse files Browse the repository at this point in the history
* Pass scheme to redis connections
* Update tests to use upstream redis
* ACL Auth requires phpredis v5.3+
* Improve compatibility with phpredis pre-v5.3


Co-authored-by: kodumbeats <[email protected]>
  • Loading branch information
Xon and kodumbeats authored Apr 7, 2022
1 parent 77e6ede commit afec8e5
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 9 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
extensions: redis

- name: Setup Redis
run: sudo apt install redis -y ; sudo systemctl stop redis\* ; redis-cli --version
run: sudo add-apt-repository ppa:redislabs/redis -y -u ; sudo apt install redis -y ; sudo systemctl stop redis\* ; redis-cli --version

- name: Install phpunit
env:
Expand Down
35 changes: 28 additions & 7 deletions Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,11 @@ class Credis_Client {
*/
protected $isWatching = FALSE;

/**
* @var string
*/
protected $authUsername;

/**
* @var string
*/
Expand Down Expand Up @@ -315,8 +320,9 @@ class Credis_Client {
* @param string $persistent Flag to establish persistent connection
* @param int $db The selected datbase of the Redis server
* @param string $password The authentication password of the Redis server
* @param string $username The authentication username of the Redis server
*/
public function __construct($host = '127.0.0.1', $port = 6379, $timeout = null, $persistent = '', $db = 0, $password = null)
public function __construct($host = '127.0.0.1', $port = 6379, $timeout = null, $persistent = '', $db = 0, $password = null, $username = null)
{
$this->host = (string) $host;
$this->port = (int) $port;
Expand All @@ -325,10 +331,15 @@ public function __construct($host = '127.0.0.1', $port = 6379, $timeout = null,
$this->persistent = (string) $persistent;
$this->standalone = ! extension_loaded('redis');
$this->authPassword = $password;
$this->authUsername = $username;
$this->selectedDb = (int)$db;
$this->convertHost();
// PHP Redis extension support TLS since 5.3.0
if ($this->scheme == 'tls' && !$this->standalone && version_compare(phpversion('redis'),'5.3.0','<')){
// PHP Redis extension support TLS/ACL AUTH since 5.3.0
if ((
$this->scheme === 'tls'
|| $this->authUsername !== null
)
&& !$this->standalone && version_compare(phpversion('redis'),'5.3.0','<')){
$this->standalone = true;
}
}
Expand Down Expand Up @@ -504,9 +515,8 @@ public function connect()
if ($this->readTimeout) {
$this->setReadTimeout($this->readTimeout);
}

if($this->authPassword) {
$this->auth($this->authPassword);
$this->auth($this->authPassword, $this->authUsername);
}
if($this->selectedDb !== 0) {
$this->select($this->selectedDb);
Expand Down Expand Up @@ -641,11 +651,17 @@ public function getRenamedCommand($command)

/**
* @param string $password
* @param string|null $username
* @return bool
*/
public function auth($password)
public function auth($password, $username = null)
{
$response = $this->__call('auth', array($password));
if ($username !== null) {
$response = $this->__call('auth', array($username, $password));
$this->authUsername= $username;
} else {
$response = $this->__call('auth', array($password));
}
$this->authPassword = $password;
return $response;
}
Expand Down Expand Up @@ -1150,6 +1166,10 @@ public function __call($name, $args)
// allow phpredis to see the caller's reference
//$param_ref =& $args[0];
break;
case 'auth':
// For phpredis pre-v5.3, the type signature is string, not array|string
$args = (is_array($args) && count($args) === 1) ? $args : array($args);
break;
default:
// Flatten arguments
$args = self::_flattenArguments($args);
Expand Down Expand Up @@ -1185,6 +1205,7 @@ public function __call($name, $args)
return $this;
}


// Send request, retry one time when using persistent connections on the first request only
$this->requests++;
try {
Expand Down
67 changes: 67 additions & 0 deletions tests/CredisTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,73 @@ public function testPassword()
$this->assertTrue($this->credis->set('key','value'));
}

/**
* @group Auth
*/
public function testUsernameAndPassword()
{
$this->tearDown();
$this->assertArrayHasKey('password',$this->redisConfig[7]);
$this->credis = new Credis_Client($this->redisConfig[7]['host'], $this->redisConfig[7]['port'], $this->redisConfig[7]['timeout'], false, 0, $this->redisConfig[7]['password'], $this->redisConfig[7]['username']);
if ($this->useStandalone) {
$this->credis->forceStandalone();
}
$this->assertInstanceOf('Credis_Client',$this->credis->connect());
$this->assertTrue($this->credis->set('key','value'));
$this->credis->close();
$this->credis = new Credis_Client($this->redisConfig[7]['host'], $this->redisConfig[7]['port'], $this->redisConfig[7]['timeout'], false, 0, 'wrongpassword', $this->redisConfig[7]['username']);
if ($this->useStandalone) {
$this->credis->forceStandalone();
}
try
{
$this->credis->connect();
$this->fail('connect should fail with wrong password');
}
catch(CredisException $e)
{
if (strpos($e->getMessage(), 'username') !== false)
{
$this->assertStringStartsWith('WRONGPASS invalid username-password pair', $e->getMessage());
}
else
{
$this->assertStringStartsWith('ERR invalid password', $e->getMessage());
}

$this->credis->close();
}
$this->credis = new Credis_Client($this->redisConfig[7]['host'], $this->redisConfig[7]['port'], $this->redisConfig[7]['timeout'], false, 0);
if ($this->useStandalone) {
$this->credis->forceStandalone();
}
try
{
$this->credis->set('key', 'value');
}
catch(CredisException $e)
{
$this->assertStringStartsWith('NOAUTH Authentication required', $e->getMessage());
}
try
{
$this->credis->auth('anotherwrongpassword');
}
catch(CredisException $e)
{
if (strpos($e->getMessage(), 'username') !== false)
{
$this->assertStringStartsWith('WRONGPASS invalid username-password pair', $e->getMessage());
}
else
{
$this->assertStringStartsWith('ERR invalid password', $e->getMessage());
}
}
$this->assertTrue($this->credis->auth('thepassword'));
$this->assertTrue($this->credis->set('key','value'));
}

public function testGettersAndSetters()
{
$this->assertEquals($this->credis->getHost(),$this->redisConfig[0]['host']);
Expand Down
3 changes: 2 additions & 1 deletion tests/redis_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
{"host":"127.0.0.1","port":6382,"timeout":2.5,"alias":"fourth"},
{"host":"127.0.0.1","port":6383,"timeout":2.5,"alias":"auth", "password": "thepassword"},
{"host":"127.0.0.1","port":6384,"timeout":2.5,"alias":"socket"},
{"host":"127.0.0.1","port":6385,"timeout":2.5,"alias":"slave"}
{"host":"127.0.0.1","port":6385,"timeout":2.5,"alias":"slave"},
{"host":"127.0.0.1","port":6383,"timeout":2.5,"alias":"userauth", "username": "default", "password": "thepassword"}
]

0 comments on commit afec8e5

Please sign in to comment.