From dc8fce6df5385c7e3c12e8f10ed40a6d9bd5fb01 Mon Sep 17 00:00:00 2001 From: Wildan M Date: Wed, 26 Mar 2025 11:03:53 +0700 Subject: [PATCH 1/4] Handle permission access errors (#216) --- css/frame.css | 10 +++++ edit.php | 115 +++++++++++++++++++++++++++----------------------- index.php | 8 +++- view.php | 14 +++++- 4 files changed, 92 insertions(+), 55 deletions(-) diff --git a/css/frame.css b/css/frame.css index 8969e10..7cf935d 100644 --- a/css/frame.css +++ b/css/frame.css @@ -52,3 +52,13 @@ float: left; margin: 1em; } + +.exception { +border: 1px solid #ff0000; +background-color: rgba(255, 0, 0, 0.2); +color: #880000; +padding: 10px; +border-radius: 5px; +margin: 20px; +} + diff --git a/edit.php b/edit.php index 4424d24..998012c 100644 --- a/edit.php +++ b/edit.php @@ -34,72 +34,83 @@ die('ERROR: could not encode value'); } - // String - if ($_POST['type'] == 'string') { - $redis->set($key, $value); - } - - // Hash - else if (($_POST['type'] == 'hash') && isset($_POST['hkey'])) { - if (strlen($_POST['hkey']) > $config['maxkeylen']) { - die('ERROR: Your hash key is to long (max length is '.$config['maxkeylen'].')'); + try { + // String + if ($_POST['type'] == 'string') { + $redis->set($key, $value); } - if ($edit && !$redis->hExists($key, input_convert($_POST['hkey']))) { - $redis->hDel($key, input_convert($_GET['hkey'])); + // Hash + else if (($_POST['type'] == 'hash') && isset($_POST['hkey'])) { + if (strlen($_POST['hkey']) > $config['maxkeylen']) { + die('ERROR: Your hash key is to long (max length is '.$config['maxkeylen'].')'); + } + + if ($edit && !$redis->hExists($key, input_convert($_POST['hkey']))) { + $redis->hDel($key, input_convert($_GET['hkey'])); + } + + $redis->hSet($key, input_convert($_POST['hkey']), $value); } - $redis->hSet($key, input_convert($_POST['hkey']), $value); - } + // List + else if (($_POST['type'] == 'list') && isset($_POST['index'])) { + $size = $redis->lLen($key); + + if (($_POST['index'] == '') || + ($_POST['index'] == $size)) { + // Push it at the end + $redis->rPush($key, $value); + } else if ($_POST['index'] == -1) { + // Push it at the start + $redis->lPush($key, $value); + } else if (($_POST['index'] >= 0) && + ($_POST['index'] < $size)) { + // Overwrite an index + $redis->lSet($key, input_convert($_POST['index']), $value); + } else { + die('ERROR: Out of bounds index'); + } + } - // List - else if (($_POST['type'] == 'list') && isset($_POST['index'])) { - $size = $redis->lLen($key); - - if (($_POST['index'] == '') || - ($_POST['index'] == $size)) { - // Push it at the end - $redis->rPush($key, $value); - } else if ($_POST['index'] == -1) { - // Push it at the start - $redis->lPush($key, $value); - } else if (($_POST['index'] >= 0) && - ($_POST['index'] < $size)) { - // Overwrite an index - $redis->lSet($key, input_convert($_POST['index']), $value); - } else { - die('ERROR: Out of bounds index'); + // Set + else if ($_POST['type'] == 'set') { + if ($_POST['value'] != $_POST['oldvalue']) { + // The only way to edit a Set value is to add it and remove the old value. + $redis->sRem($key, encodeOrDecode('save', $key, input_convert($_POST['oldvalue']))); + $redis->sAdd($key, $value); + } } - } - // Set - else if ($_POST['type'] == 'set') { - if ($_POST['value'] != $_POST['oldvalue']) { - // The only way to edit a Set value is to add it and remove the old value. - $redis->sRem($key, encodeOrDecode('save', $key, input_convert($_POST['oldvalue']))); - $redis->sAdd($key, $value); + // ZSet + else if (($_POST['type'] == 'zset') && isset($_POST['score']) && is_numeric($_POST['score'])) { + // The only way to edit a ZSet value is to add it and remove the old value. + $redis->zRem($key, encodeOrDecode('save', $key, input_convert($_POST['oldvalue']))); + $redis->zAdd($key, input_convert($_POST['score']), $value); } - } - // ZSet - else if (($_POST['type'] == 'zset') && isset($_POST['score']) && is_numeric($_POST['score'])) { - // The only way to edit a ZSet value is to add it and remove the old value. - $redis->zRem($key, encodeOrDecode('save', $key, input_convert($_POST['oldvalue']))); - $redis->zAdd($key, input_convert($_POST['score']), $value); - } + // Refresh the top so the key tree is updated. + require 'includes/header.inc.php'; - // Refresh the top so the key tree is updated. - require 'includes/header.inc.php'; + ?> + + - - +
+

getMessage() ?>

+
+ type($fullkey)) { + $type = ''; + try { + $type = $redis->type($fullkey); + } catch (\Predis\Response\ServerException $th) { + $class[] = 'empty'; + } + switch ($type) { case 'hash': $len = $redis->hLen($fullkey); break; diff --git a/view.php b/view.php index 1836979..0fdb1b5 100644 --- a/view.php +++ b/view.php @@ -17,8 +17,18 @@ die; } -$type = $redis->type($_GET['key']); -$exists = $redis->exists($_GET['key']); +$type = ''; +$exists = false; +try { + $type = $redis->type($_GET['key']); + $exists = $redis->exists($_GET['key']); +} catch (\Predis\Response\ServerException $th) { + ?> +
+

getMessage() ?>

+
+ Date: Sat, 29 Mar 2025 12:04:03 +0700 Subject: [PATCH 2/4] Change TTL input type (#217) --- ttl.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttl.php b/ttl.php index 92e6cca..c5d7a88 100644 --- a/ttl.php +++ b/ttl.php @@ -31,7 +31,7 @@

-> (-1 to remove the TTL) +> (-1 to remove the TTL)

From 2d977bce2a3225bacf9ab04ba0ca27c5d58324ab Mon Sep 17 00:00:00 2001 From: Wildan M Date: Mon, 31 Mar 2025 11:33:09 +0700 Subject: [PATCH 3/4] Implement ACL login (#218) * Implement ACL login * Make password form optional --- includes/common.inc.php | 3 + includes/config.sample.inc.php | 6 ++ includes/login_acl.inc.php | 100 +++++++++++++++++++++++++++++++++ login.php | 2 +- logout.php | 4 ++ overview.php | 33 ++++++++--- 6 files changed, 138 insertions(+), 10 deletions(-) create mode 100644 includes/login_acl.inc.php diff --git a/includes/common.inc.php b/includes/common.inc.php index a92a3ed..78e791f 100644 --- a/includes/common.inc.php +++ b/includes/common.inc.php @@ -148,6 +148,9 @@ } } +if (!isset($config['login']) && !empty($config['login_as_acl_auth'])) { + require_once PHPREDIS_ADMIN_PATH . '/includes/login_acl.inc.php'; +} if ($server['db'] != 0) { if (!$redis->select($server['db'])) { diff --git a/includes/config.sample.inc.php b/includes/config.sample.inc.php index cca5b81..923a60c 100644 --- a/includes/config.sample.inc.php +++ b/includes/config.sample.inc.php @@ -61,6 +61,12 @@ ) ),*/ + // Uncomment to enable login as ACL authentication (won't work if 'login' or 'auth' is also used) + // Only support using one server at this moment. + // If you set the default user off, browsers will be redirected to login page. + // The user and password will be stored in browser as plaintext so using HTTPS is strongly recommended. + // 'login_as_acl_auth' => true, + // Use HTML form/cookie-based auth instead of HTTP Basic/Digest auth 'cookie_auth' => false, diff --git a/includes/login_acl.inc.php b/includes/login_acl.inc.php new file mode 100644 index 0000000..e8b7d72 --- /dev/null +++ b/includes/login_acl.inc.php @@ -0,0 +1,100 @@ +auth($login['username'], $login['password']); + } catch (Predis\Response\ServerException $exception) { + return false; + } + return true; +} + +// This fill will perform HTTP basic authentication. The authentication data will be sent and stored as plaintext +// Please make sure to use HTTPS proxy such as Apache or Nginx to prevent traffic eavesdropping. +function authHttpBasic() +{ + $realm = 'phpRedisAdmin'; + + if (!isset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) { + try { + global $redis; + $redis->ping(); + } catch (Predis\Response\ServerException $exception) { + header('HTTP/1.1 401 Unauthorized'); + header('WWW-Authenticate: Basic realm="' . $realm . '"'); + die('NOAUTH -- Authentication required'); + } + } + + $login = [ + 'username' => $_SERVER['PHP_AUTH_USER'], + 'password' => $_SERVER['PHP_AUTH_PW'], + ]; + + if (!authCheck($login)) { + header('HTTP/1.1 401 Unauthorized'); + header('WWW-Authenticate: Basic realm="' . $realm . '"'); + die('NOAUTH -- Authentication required'); + } + + return $login; +} + +// Perform auth using a standard HTML
submission and cookies to save login state +function authCookie() +{ + if (!empty($_COOKIE['phpRedisAdminLogin'])) { + // We have a cookie; is it correct? + // Cookie value looks like "username:password-hash" + $login = explode(':', $_COOKIE['phpRedisAdminLogin'], 2); + if (count($login) === 2) { + $login = [ + 'username' => $login[0], + 'password' => $login[1], + ]; + if (authCheck($login)) { + return $login; + } + } + } + + if (isset($_POST['username'], $_POST['password'])) { + // Login form submitted; correctly? + $login = [ + 'username' => $_POST['username'], + 'password' => $_POST['password'], + ]; + + if (authCheck($login)) { + setcookie('phpRedisAdminLogin', $login['username'] . ':' . $login['password']); + // This should be an absolute URL, but that's a bit of a pain to generate; this will work + header("Location: index.php"); + die(); + } + } + + try { + global $redis; + $redis->ping(); + } catch (Predis\Response\ServerException $exception) { + // If we're here, we don't have a valid login cookie and we don't have a + // valid form submission, so redirect to the login page if we aren't + // already on that page + if (!defined('LOGIN_PAGE')) { + header("Location: login.php"); + die(); + } + } + + // We must be on the login page without a valid cookie or submission + return null; +} + +if (!empty($config['cookie_auth'])) { + $login = authCookie(); +} else { + $login = authHttpBasic(); +} diff --git a/login.php b/login.php index a80a24c..5c12301 100644 --- a/login.php +++ b/login.php @@ -33,7 +33,7 @@ > + >
diff --git a/logout.php b/logout.php index 12cc80b..07e10ae 100644 --- a/logout.php +++ b/logout.php @@ -8,6 +8,10 @@ setcookie('phpRedisAdminLogin', '', 1); header("Location: login.php"); die(); +} else if (isset($config['login_as_acl_auth'])) { + // HTTP Basic auth + header('HTTP/1.1 401 Unauthorized'); + die(''); } else { // HTTP Digest auth $needed_parts = array( diff --git a/overview.php b/overview.php index 3e426db..5c67370 100644 --- a/overview.php +++ b/overview.php @@ -10,16 +10,24 @@ $server['db'] = 0; } - // Setup a connection to Redis. - if(isset($server['scheme']) && $server['scheme'] === 'unix' && $server['path']) { - $redis = new Predis\Client(array('scheme' => 'unix', 'path' => $server['path'])); + + if (isset($config['login_as_acl_auth'])) { + // Currently only support one server at a time + if ($i > 0) { + break; + } } else { - $redis = !$server['port'] ? new Predis\Client($server['host']) : new Predis\Client('tcp://'.$server['host'].':'.$server['port']); - } - try { - $redis->connect(); - } catch (Predis\CommunicationException $exception) { - $redis = false; + // Setup a connection to Redis. + if(isset($server['scheme']) && $server['scheme'] === 'unix' && $server['path']) { + $redis = new Predis\Client(array('scheme' => 'unix', 'path' => $server['path'])); + } else { + $redis = !$server['port'] ? new Predis\Client($server['host']) : new Predis\Client('tcp://'.$server['host'].':'.$server['port']); + } + try { + $redis->connect(); + } catch (Predis\CommunicationException $exception) { + $redis = false; + } } if(!$redis) { @@ -38,6 +46,9 @@ $info[$i] = $redis->info(); $info[$i]['size'] = $redis->dbSize(); + if (isset($config['login_as_acl_auth'])) { + $info[$i]['username'] = $redis->acl->whoami(); + } if (!isset($info[$i]['Server'])) { $info[$i]['Server'] = array( @@ -83,6 +94,10 @@
Uptime:
+ +
Username:
+ +
Last save:
Date: Mon, 31 Mar 2025 06:36:23 +0200 Subject: [PATCH 4/4] v1.24.0 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index fb69cc8..3b3ab81 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "erik-dubbelboer/php-redis-admin", "description": "Simple web interface to manage Redis databases.", - "version": "1.23.0", + "version": "1.24.0", "license": "CC-BY-3.0", "homepage": "https://github.com/ErikDubbelboer/phpRedisAdmin", "authors": [