diff --git a/Dockerfile b/Dockerfile index fd40ac1..610dbda 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,15 @@ -FROM composer:1.7 +FROM composer:2.2 -ENV TINI_VERSION 0.18.0-r0 - -RUN apk add --no-cache tini=$TINI_VERSION - -ADD . /src/app/ +RUN apk add --no-cache tini tzdata WORKDIR /src/app -RUN composer install +COPY . . -RUN cp includes/config.environment.inc.php includes/config.inc.php +RUN set -xe; \ + composer install; \ + cp includes/config.environment.inc.php includes/config.inc.php +ENV PORT 80 EXPOSE 80 -ENTRYPOINT [ "tini", "--", "php", "-S", "0.0.0.0:80" ] +ENTRYPOINT [ "sh", "-c", "tini -- php -S 0.0.0.0:$PORT" ] diff --git a/README.markdown b/README.markdown index ddf39d3..0938c08 100644 --- a/README.markdown +++ b/README.markdown @@ -41,7 +41,7 @@ git clone https://github.com/nrk/predis.git vendor Docker Image ============ -A public [phpRedisAdmin Docker image](https://hub.docker.com/r/erikdubbelboer/phpredisadmin/) is available on Docker Hub [automatically built](https://docs.docker.com/docker-hub/builds/) from latest source. +A public [phpRedisAdmin Docker image](https://hub.docker.com/r/erikdubbelboer/phpredisadmin/) is available on Docker Hub built from the latest tag. The file ```includes/config.environment.inc.php``` is used as the configuration file to allow environment variables to be used as configuration values. Example: ``` @@ -55,7 +55,10 @@ Environment variables summary * ``REDIS_1_HOST`` - define host of the Redis server * ``REDIS_1_NAME`` - define name of the Redis server * ``REDIS_1_PORT`` - define port of the Redis server +* ``REDIS_1_SCHEME`` - define scheme of the Redis server (tcp or tls) * ``REDIS_1_AUTH`` - define password of the Redis server +* ``REDIS_1_AUTH_FILE`` - define file containing the password of the Redis server +* ``REDIS_1_DATABASES`` - You can modify you config to prevent phpRedisAdmin from using CONFIG command * ``ADMIN_USER`` - define username for user-facing Basic Auth * ``ADMIN_PASS`` - define password for user-facing Basic Auth diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..1627ff2 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 1.13.x | :white_check_mark: | +| 1.12.x | :white_check_mark: | +| < 1.12 | :x: | + +## Reporting a Vulnerability + +Vulnerabilities can be emailed to erik@dubbelboer.com diff --git a/composer.json b/composer.json index f66ba98..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.13.2", + "version": "1.24.0", "license": "CC-BY-3.0", "homepage": "https://github.com/ErikDubbelboer/phpRedisAdmin", "authors": [ @@ -13,8 +13,11 @@ } ], "require": { - "predis/predis": "1.1.x-dev" + "ext-mbstring": "*", + "ext-json": "*", + "predis/predis": "v2.3.0", + "paragonie/random_compat": ">=2" }, "minimum-stability": "stable", - "target-dir": "ErikDubbelboer/phpRedisAdmin" + "target-dir": "ErikDubbelboer/phpRedisAdmin" } diff --git a/composer.lock b/composer.lock index ac3a614..f724f31 100644 --- a/composer.lock +++ b/composer.lock @@ -4,31 +4,82 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0c346d5e2f2e0897260911e1b2966180", + "content-hash": "401ff61cebe5223d003a47192d7c3d6a", "packages": [ + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com/" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "time": "2020-10-15T08:29:30+00:00" + }, { "name": "predis/predis", - "version": "v1.1.x-dev", + "version": "v2.3.0", "source": { "type": "git", - "url": "https://github.com/nrk/predis.git", - "reference": "111d100ee389d624036b46b35ed0c9ac59c71313" + "url": "https://github.com/predis/predis.git", + "reference": "bac46bfdb78cd6e9c7926c697012aae740cb9ec9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nrk/predis/zipball/111d100ee389d624036b46b35ed0c9ac59c71313", - "reference": "111d100ee389d624036b46b35ed0c9ac59c71313", + "url": "https://api.github.com/repos/predis/predis/zipball/bac46bfdb78cd6e9c7926c697012aae740cb9ec9", + "reference": "bac46bfdb78cd6e9c7926c697012aae740cb9ec9", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": "^7.2 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "~4.8" + "friendsofphp/php-cs-fixer": "^3.3", + "phpstan/phpstan": "^1.9", + "phpunit/phpunit": "^8.0 || ^9.4" }, "suggest": { - "ext-curl": "Allows access to Webdis when paired with phpiredis", - "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol" + "ext-relay": "Faster connection with in-memory caching (>=0.6.2)" }, "type": "library", "autoload": { @@ -42,29 +93,41 @@ ], "authors": [ { - "name": "Daniele Alessandri", - "email": "suppakilla@gmail.com", - "homepage": "http://clorophilla.net/" + "name": "Till Krüss", + "homepage": "https://till.im/", + "role": "Maintainer" } ], - "description": "Flexible and feature-complete Redis client for PHP and HHVM", - "homepage": "http://github.com/nrk/predis", + "description": "A flexible and feature-complete Redis client for PHP.", + "homepage": "http://github.com/predis/predis", "keywords": [ "nosql", "predis", "redis" ], - "time": "2017-07-12T14:39:17+00:00" + "support": { + "issues": "https://github.com/predis/predis/issues", + "source": "https://github.com/predis/predis/tree/v2.3.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/tillkruss", + "type": "github" + } + ], + "time": "2024-11-21T20:00:02+00:00" } ], "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "predis/predis": 20 - }, + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [] + "platform": { + "ext-mbstring": "*", + "ext-json": "*" + }, + "platform-dev": {}, + "plugin-api-version": "2.6.0" } diff --git a/css/frame.css b/css/frame.css index e54ca9e..7cf935d 100644 --- a/css/frame.css +++ b/css/frame.css @@ -14,7 +14,6 @@ margin-left: -8em; } form .button { -margin-left: -7em; } @@ -53,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/delete.php b/delete.php index cde8539..c329919 100644 --- a/delete.php +++ b/delete.php @@ -1,13 +1,13 @@ del($key); } @@ -73,4 +73,3 @@ die('?view&s=' . $server['id'] . '&d=' . $server['db'] . '&key=' . urlencode($keys[0])); } -?> diff --git a/docker-compose.yml b/docker-compose.yml index dab64b9..5ef83df 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,15 +1,16 @@ -phpredisadmin: - build: . - environment: - - ADMIN_USER=admin - - ADMIN_PASS=admin - - REDIS_1_HOST=redis - - REDIS_1_PORT=6379 - links: - - redis - ports: - - "80:80" +services: + phpredisadmin: + build: . + environment: + - ADMIN_USER=admin + - ADMIN_PASS=admin + - REDIS_1_HOST=redis + - REDIS_1_PORT=6379 + links: + - redis + ports: + - "80:80" -redis: - image: redis - command: --loglevel verbose + redis: + image: redis + command: --loglevel verbose diff --git a/edit.php b/edit.php index 0940503..998012c 100644 --- a/edit.php +++ b/edit.php @@ -2,8 +2,7 @@ require_once 'includes/common.inc.php'; - - +global $redis, $config, $csrfToken, $server; // Are we editing or creating a new key? $edit = false; @@ -35,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'])) { - // 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() ?>

+
+

+

@@ -184,13 +195,11 @@ -

-

+?> \ No newline at end of file diff --git a/export.php b/export.php index c886e46..d17839e 100644 --- a/export.php +++ b/export.php @@ -2,6 +2,7 @@ require_once 'includes/common.inc.php'; +global $redis, $config, $csrfToken, $server; // Export to redis-cli commands function export_redis($key, $filter = false, $transform = false) { @@ -15,7 +16,7 @@ function export_redis($key, $filter = false, $transform = false) { $outputKey = str_replace($filter, $transform, $key); else $outputKey = $key; - + // String if ($type == 'string') { echo 'SET "',addslashes($outputKey),'" "',addslashes($redis->get($key)),'"',PHP_EOL; @@ -124,7 +125,7 @@ function export_json($key) { // JSON if ($_POST['type'] == 'json') { - + // Single key if (isset($_GET['key'])) { echo json_encode(export_json($_GET['key'])); @@ -137,13 +138,13 @@ function export_json($key) { // if we have a filter and no match, nothing to do if($filter !== false && stripos($key, $filter) === false) continue; - + // we rename the keys as necessary if($filter !== false && $transform !== false) $outputKey = str_replace($filter, $transform, $key); else $outputKey = $key; - + $vals[$outputKey] = export_json($key); } @@ -165,7 +166,7 @@ function export_json($key) { // if we have a filter and no match, we skip if($filter !== false && stripos($key, $filter) === false) continue; - + export_redis($key, $filter, $transform); } } @@ -187,6 +188,7 @@ function export_json($key) {

Export

+

@@ -194,7 +196,7 @@ function export_json($key) { -

+

@@ -208,9 +210,7 @@ function export_json($key) {

-

-

flushdb(); diff --git a/import.php b/import.php index 1786164..5e5a89e 100644 --- a/import.php +++ b/import.php @@ -1,9 +1,7 @@

Import

+

-

-

'unix', 'path' => $server['path'])); } else { - $redis = !$server['port'] ? new Predis\Client($server['host']) : new Predis\Client('tcp://'.$server['host'].':'.$server['port']); + $redis = !$server['port'] ? new Predis\Client($server['host']) : new Predis\Client($server['scheme'].'://'.$server['host'].':'.$server['port']); } try { @@ -118,11 +148,12 @@ } } +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'])) { die('ERROR: Selecting database failed ('.$server['host'].':'.$server['port'].','.$server['db'].')'); } } - -?> diff --git a/includes/config.environment.inc.php b/includes/config.environment.inc.php index 587899c..06a06fe 100644 --- a/includes/config.environment.inc.php +++ b/includes/config.environment.inc.php @@ -2,6 +2,19 @@ include 'config.sample.inc.php'; +// get configs from environment variables +$config['cookie_auth'] = getenv('COOKIE_AUTH') ?: false; +$config['count_elements_page'] = getenv('COUNT_ELEMENTS_PAGE') ?: 100; +$config['faster'] = getenv('FASTER') ?: true; +$config['filter'] = getenv('FILTER') ?: '*'; +$config['hideEmptyDBs'] = getenv('HIDE_EMPTY_DBS') ?: false; +$config['keys'] = getenv('KEYS') ?: false; +$config['maxkeylen'] = getenv('MAX_KEY_LEN') ?: 100; +$config['scansize'] = getenv('SCAN_SIZE') ?: 1000; +$config['scanmax'] = getenv('SCAN_MAX') ?: 1000; +$config['seperator'] = getenv('SEPERATOR') ?: ':'; +$config['showEmptyNamespaceAsKey'] = getenv('SHOW_EMPTY_NAMESPACE_AS_KEY') ?: false; + $admin_user = getenv('ADMIN_USER'); $admin_pass = getenv('ADMIN_PASS'); @@ -23,7 +36,12 @@ $server_name = getenv($prefix . 'NAME'); $server_host = getenv($prefix . 'HOST'); $server_port = getenv($prefix . 'PORT'); - $server_auth = getenv($prefix . 'AUTH'); + $server_scheme = getenv($prefix . 'SCHEME'); + if (getenv($prefix . 'AUTH_FILE') !== false) { + $server_auth = file_get_contents(getenv($prefix . 'AUTH_FILE')); + } else { + $server_auth = getenv($prefix . 'AUTH'); + } $server_databases = getenv($prefix . 'DATABASES'); if (empty($server_host)) { @@ -33,29 +51,36 @@ if (empty($server_name)) { $server_name = $server_host; } - + if (empty($server_auth)) { $server_auth = ""; - } + } - if (empty($server_port)) { + if (empty($server_port) && strpos($server_host, ':') === false) { $server_port = 6379; } + if (empty($server_scheme)) { + $server_scheme = 'tcp'; + } + $config['servers'][] = array( - 'name' => $server_name, - 'host' => $server_host, - 'port' => $server_port, - 'filter' => '*', + 'name' => $server_name, + 'host' => $server_host, + 'port' => $server_port, + 'filter' => $config['filter'], + 'scansize' => $config['scansize'], + 'scanmax' => $config['scanmax'], + 'scheme' => $server_scheme, ); - + if (!empty($server_auth)) { $config['servers'][$i-1]['auth'] = $server_auth; - } - + } + if (!empty($server_databases)) { $config['servers'][$i-1]['databases'] = $server_databases; - } + } $i++; } diff --git a/includes/config.sample.inc.php b/includes/config.sample.inc.php index 10ed201..923a60c 100644 --- a/includes/config.sample.inc.php +++ b/includes/config.sample.inc.php @@ -32,7 +32,8 @@ 'flush' => false, // Set to true to enable the flushdb button for this instance. 'charset' => 'cp1251', // Keys and values are stored in redis using this encoding (default utf-8). 'keys' => false, // Use the old KEYS command instead of SCAN to fetch all keys for this server (default uses config default). - 'scansize' => 1000 // How many entries to fetch using each SCAN command for this server (default uses config default). + 'scansize' => 1000, // How many entries to fetch using each SCAN command for this server (default uses config default). + 'scanmax' => 1000, // In each query, SCAN command may be executed several times. To shorten the duration, it is recommended to limit the total number of entries to fetch (default uses config default). ),*/ ), @@ -60,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, @@ -83,5 +90,8 @@ 'keys' => false, // How many entries to fetch using each SCAN command. - 'scansize' => 1000 + 'scansize' => 1000, + + // The total number of entries to fetch. Set to 0 or -1 for no limit. + 'scanmax' => 0 ); diff --git a/includes/header.inc.php b/includes/header.inc.php index aa618f1..7679bd6 100644 --- a/includes/header.inc.php +++ b/includes/header.inc.php @@ -4,6 +4,7 @@ header('Content-Type: text/html; charset=utf-8'); header('Cache-Control: private'); +header('X-Frame-Options: sameorigin'); ?> @@ -35,5 +36,9 @@ + + diff --git a/includes/login.inc.php b/includes/login.inc.php index 75ed28d..8cfb26d 100644 --- a/includes/login.inc.php +++ b/includes/login.inc.php @@ -56,7 +56,7 @@ function authHttpDigest() $response = md5($password.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.md5($_SERVER['REQUEST_METHOD'].':'.$data['uri'])); - if ($data['response'] != $response) { + if ($data['response'] !== $response) { header('HTTP/1.1 401 Unauthorized'); header('WWW-Authenticate: Digest realm="'.$realm.'",qop="auth",nonce="'.uniqid().'",opaque="'.$opaque.'"'); die('Invalid username and/or password combination.'); 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/includes/page.inc.php b/includes/page.inc.php index 2f1a0e9..2941e68 100644 --- a/includes/page.inc.php +++ b/includes/page.inc.php @@ -1,6 +1,5 @@ array('common'), 'js' => array('jquery') diff --git a/index.php b/index.php index 933796a..ea509c7 100644 --- a/index.php +++ b/index.php @@ -1,6 +1,7 @@ scan($next, 'MATCH', $server['filter'], 'COUNT', $server['scansize']); - $next = $r[0]; $keys = array_merge($keys, $r[1]); - if ($next == 0) { break; } + if ($server['scanmax'] > 0 && count($keys) >= $server['scanmax']) { + break; + } } } @@ -33,7 +34,7 @@ continue; } - $key = explode($server['seperator'], $key); + $key = explode($server['seperator'], $key); //@todo: may be separator ? if ($config['showEmptyNamespaceAsKey'] && $key[count($key) - 1] == '') { array_pop($key); $key[count($key) - 1] .= ':'; @@ -81,7 +82,13 @@ function print_namespace($item, $name, $fullkey, $islast) { // Get the number of items in the key. if (!isset($config['faster']) || !$config['faster']) { - switch ($redis->type($fullkey)) { + $type = ''; + try { + $type = $redis->type($fullkey); + } catch (\Predis\Response\ServerException $th) { + $class[] = 'empty'; + } + switch ($type) { case 'hash': $len = $redis->hLen($fullkey); break; @@ -107,7 +114,7 @@ function print_namespace($item, $name, $fullkey, $islast) { ?> > - + ()
+
+ scanned keys 0 && count($keys) >= $server['scanmax']) ? ', reached scanmax' : '' ?> +
diff --git a/info.php b/info.php index d657533..77c6a3d 100644 --- a/info.php +++ b/info.php @@ -1,9 +1,7 @@ config('resetstat'); @@ -12,15 +10,10 @@ die; } - - // Fetch the info $info = $redis->info(); $alt = false; - - - $page['css'][] = 'frame'; $page['js'][] = 'frame'; diff --git a/js/frame.js b/js/frame.js index b3b84b5..0196452 100644 --- a/js/frame.js +++ b/js/frame.js @@ -3,14 +3,12 @@ $(function() { window.parent.history.replaceState({}, '', document.location.href.replace('?', '&').replace(/\/([a-z]*)\.php/, '/?$1')); } - $('#type').change(function(e) { $('#hkeyp' ).css('display', e.target.value == 'hash' ? 'block' : 'none'); $('#indexp').css('display', e.target.value == 'list' ? 'block' : 'none'); $('#scorep').css('display', e.target.value == 'zset' ? 'block' : 'none'); }).change(); - $('.delkey, .delval').click(function(e) { e.preventDefault(); @@ -18,7 +16,7 @@ $(function() { $.ajax({ type: "POST", url: this.href, - data: 'post=1', + data: 'post=1&csrf=' + phpRedisAdmin_csrfToken, success: function(url) { top.location.href = top.location.pathname+url; } diff --git a/js/index.js b/js/index.js index 3557014..a3f3414 100644 --- a/js/index.js +++ b/js/index.js @@ -14,13 +14,13 @@ $(function() { }) $('#sidebar').on('click', 'a', function(e) { - if (e.currentTarget.className.indexOf('batch_del') !== -1){ + if (e.currentTarget.className.indexOf('batch_del') !== -1) { e.preventDefault(); - var selected_keys = ''; + var selected_keys = []; $('input[name=checked_keys]:checked').each(function () { - selected_keys += $(this).val() + ','; + selected_keys.push($(this).val()); }); - if (!selected_keys) { + if (selected_keys.length == 0) { alert('Please select the keys you want to delete.'); return; } @@ -28,20 +28,27 @@ $(function() { $.ajax({ type: "POST", url: this.href, - data: 'post=1&selected_keys=' + selected_keys, + data: { + post: 1, + selected_keys: JSON.stringify(selected_keys), + csrf: phpRedisAdmin_csrfToken + }, success: function(url) { top.location.href = top.location.pathname+url; } }); } - }else if (e.currentTarget.className.indexOf('deltree') !== -1) { + } else if (e.currentTarget.className.indexOf('deltree') !== -1) { e.preventDefault(); if (confirm('Are you sure you want to delete this whole tree and all it\'s keys?')) { $.ajax({ type: "POST", url: this.href, - data: 'post=1', + data: { + post: 1, + csrf: phpRedisAdmin_csrfToken + }, success: function(url) { top.location.href = top.location.pathname+url; } @@ -74,7 +81,10 @@ $(function() { $.ajax({ type: "POST", url: href, - data: 'post=1', + data: { + post: 1, + csrf: phpRedisAdmin_csrfToken + }, success: function() { window.location.reload(); } diff --git a/login.php b/login.php index 82c5514..5c12301 100644 --- a/login.php +++ b/login.php @@ -2,6 +2,7 @@ define('LOGIN_PAGE', true); require_once 'includes/common.inc.php'; +global $redis, $config, $csrfToken, $server; $page['css'][] = 'login'; @@ -13,6 +14,7 @@

phpRedisAdmin

+ @@ -31,7 +33,7 @@ > + > diff --git a/logout.php b/logout.php index 44b1785..07e10ae 100644 --- a/logout.php +++ b/logout.php @@ -1,12 +1,17 @@ '); } else { // HTTP Digest auth $needed_parts = array( diff --git a/overview.php b/overview.php index 007e2d6..5c67370 100644 --- a/overview.php +++ b/overview.php @@ -1,9 +1,7 @@ '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) { @@ -40,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( @@ -85,6 +94,10 @@
Uptime:
+ +
Username:
+ +
Last save:

-Redis Documentation +Redis Documentation

$config['maxkeylen']) { @@ -36,6 +34,7 @@ ?>

Edit Name of

+ @@ -44,9 +43,7 @@ >

-

-

Edit TTL

+

@@ -35,12 +31,10 @@

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

-

-

Invalid key @@ -18,17 +17,23 @@ 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() ?>

+
+

@@ -48,8 +53,6 @@ die; } - - $alt = false; $ttl = $redis->ttl($_GET['key']); @@ -59,7 +62,6 @@ $encoding = null; } - switch ($type) { case 'string': $value = $redis->get($_GET['key']); @@ -96,6 +98,9 @@ } $size = count($values); break; + + default: + $size = -1; } if (isset($values) && ($count_elements_page !== false)) { @@ -113,7 +118,19 @@
Encoding:
-
Size:
+
Size:
+ +