diff --git a/CHANGELOG b/CHANGELOG index a7b5174e..4cf3f813 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ # ?, 2014 # ######################### +- User login system - Switched from mysql_* to PDO. - Added SMTP support. - Updated PHPMailer package to v5.2.6. diff --git a/README.md b/README.md index dee4b8a7..62793ce3 100755 --- a/README.md +++ b/README.md @@ -98,15 +98,6 @@ The config file has actually been renamed since 2.0, but if you keep it there wh * Remove the old config.inc.php file -## Security - -By default the PHP Server Monitor does not (yet) come with any security methods. After uploading these files to -your public html folder these will be visible to everyone on the web. It is recommended to put a password -on this folder to prevent other people from playing with your tool. An example .htaccess login script can -be found in the example/ dir. To create your own username and password for the .htpasswd file, see - - - ## Setting up a cronjob In order to keep the server monitor up to date, the monitor.php file has to run regularly. diff --git a/index.php b/index.php index baf1a361..f4825b8c 100755 --- a/index.php +++ b/index.php @@ -29,13 +29,21 @@ require 'src/bootstrap.php'; psm_no_cache(); -if(isset($_GET['action']) && $_GET['action'] == 'check') { - require 'cron/status.cron.php'; - header('Location: index.php'); +$type = (!isset($_GET['type'])) ? 'servers' : $_GET['type']; + +// if user is not logged in, load login module +$user = new \psm\Service\User($db); +if(!$user->isUserLoggedIn()) { + $type = 'login'; } -$type = (!isset($_GET['type'])) ? 'servers' : $_GET['type']; -$allowed_types = array('servers', 'users', 'log', 'config', 'status'); +if($type == 'update') { + require 'cron/status.cron.php'; + header('Location: ' . psm_build_url()); + die(); +} + +$allowed_types = array('servers', 'users', 'log', 'config', 'status', 'login'); // make sure user selected a valid type. if so, include the file and add to template if(!in_array($type, $allowed_types)) { @@ -44,7 +52,9 @@ if(!in_array($type, $allowed_types)) { $tpl = new \psm\Service\Template(); eval('$mod = new psm\Module\\'.ucfirst($type).'($db, $tpl);'); +if($user->getUserLevel() > $mod->getMinUserLevelRequired()) { + die('You do not have the privileges to view this page.'); +} +$mod->setUser($user); // let the module prepare it's HTML code $mod->initialize(); - -?> \ No newline at end of file diff --git a/install.php b/install.php index 50d0fb8b..d09f1ad1 100755 --- a/install.php +++ b/install.php @@ -29,11 +29,11 @@ define('PSM_INSTALL', true); require 'src/bootstrap.php'; -psm_no_cache(); - $type = 'install'; $tpl = new \psm\Service\Template(); +$user = new \psm\Service\User($db); $mod = new psm\Module\Install($db, $tpl); +$mod->setUser($user); $mod->initialize(); ?> \ No newline at end of file diff --git a/src/bootstrap.php b/src/bootstrap.php index 267b59de..68789847 100755 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -21,12 +21,11 @@ * @author Pepijn Over * @copyright Copyright (c) 2008-2014 Pepijn Over * @license http://www.gnu.org/licenses/gpl.txt GNU GPL v3 - * @version Release: phpservermon 2.1.0 + * @version Release: @package_version@ * @link http://www.phpservermonitor.org/ * @since phpservermon 2.1.0 **/ -define('PSM_VERSION', '2.2.0-dev'); // Include paths define('PSM_PATH_SRC', dirname(__FILE__) . DIRECTORY_SEPARATOR); define('PSM_PATH_VENDOR', PSM_PATH_SRC . '..' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR); @@ -34,6 +33,11 @@ define('PSM_PATH_INC', PSM_PATH_SRC . 'includes' . DIRECTORY_SEPARATOR); define('PSM_PATH_TPL', PSM_PATH_SRC . 'templates' . DIRECTORY_SEPARATOR); define('PSM_PATH_LANG', PSM_PATH_SRC . 'lang' . DIRECTORY_SEPARATOR); +// user levels +define('PSM_USER_ADMIN', 10); +define('PSM_USER_USER', 20); +define('PSM_USER_ANONYMOUS', 30); + // find config file $path_conf = PSM_PATH_SRC . '../config.php'; if(file_exists($path_conf)) { diff --git a/src/includes/functions.inc.php b/src/includes/functions.inc.php index 5c7273f7..e39e4f88 100755 --- a/src/includes/functions.inc.php +++ b/src/includes/functions.inc.php @@ -369,6 +369,34 @@ function psm_build_url($params = array(), $urlencode = true) { return $url; } +/** + * Try existence of a GET var, if not return the alternative + * @param string $key + * @param string $alt + * @return mixed + */ +function psm_GET($key, $alt = null) { + if(isset($_GET[$key])) { + return $_GET[$key]; + } else { + return $alt; + } +} + +/** + * Try existence of a GET var, if not return the alternative + * @param string $key + * @param string $alt + * @return mixed + */ +function psm_POST($key, $alt = null) { + if(isset($_POST[$key])) { + return $_POST[$key]; + } else { + return $alt; + } +} + ############################################### # # Debug functions @@ -396,5 +424,3 @@ function psm_no_cache() { header("Cache-Control: no-cache, must-revalidate"); header("Pragma: no-cache"); } - -?> \ No newline at end of file diff --git a/src/includes/password_compatibility_library.inc.php b/src/includes/password_compatibility_library.inc.php new file mode 100644 index 00000000..6442b715 --- /dev/null +++ b/src/includes/password_compatibility_library.inc.php @@ -0,0 +1,246 @@ +. + * + * @package phpservermon + * @author Anthony Ferrara + * @copyright Copyright (c) 2008-2014 Pepijn Over + * @license http://www.gnu.org/licenses/gpl.txt GNU GPL v3 + * @version Release: @package_version@ + * @link http://www.phpservermonitor.org/ + * @since phpservermon 2.2.0 + **/ + +/** + * A Compatibility library with PHP 5.5's simplified password hashing API. + * + * @author Anthony Ferrara + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @copyright 2012 The Authors + */ + +if (!defined('PASSWORD_DEFAULT')) { + + define('PASSWORD_BCRYPT', 1); + define('PASSWORD_DEFAULT', PASSWORD_BCRYPT); + + /** + * Hash the password using the specified algorithm + * + * @param string $password The password to hash + * @param int $algo The algorithm to use (Defined by PASSWORD_* constants) + * @param array $options The options for the algorithm to use + * + * @return string|false The hashed password, or false on error. + */ + function password_hash($password, $algo, array $options = array()) { + if (!function_exists('crypt')) { + trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING); + return null; + } + if (!is_string($password)) { + trigger_error("password_hash(): Password must be a string", E_USER_WARNING); + return null; + } + if (!is_int($algo)) { + trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING); + return null; + } + switch ($algo) { + case PASSWORD_BCRYPT: + // Note that this is a C constant, but not exposed to PHP, so we don't define it here. + $cost = 10; + if (isset($options['cost'])) { + $cost = $options['cost']; + if ($cost < 4 || $cost > 31) { + trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING); + return null; + } + } + // The length of salt to generate + $raw_salt_len = 16; + // The length required in the final serialization + $required_salt_len = 22; + $hash_format = sprintf("$2y$%02d$", $cost); + break; + default: + trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING); + return null; + } + if (isset($options['salt'])) { + switch (gettype($options['salt'])) { + case 'NULL': + case 'boolean': + case 'integer': + case 'double': + case 'string': + $salt = (string) $options['salt']; + break; + case 'object': + if (method_exists($options['salt'], '__tostring')) { + $salt = (string) $options['salt']; + break; + } + case 'array': + case 'resource': + default: + trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING); + return null; + } + if (strlen($salt) < $required_salt_len) { + trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", strlen($salt), $required_salt_len), E_USER_WARNING); + return null; + } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) { + $salt = str_replace('+', '.', base64_encode($salt)); + } + } else { + $buffer = ''; + $buffer_valid = false; + if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) { + $buffer = mcrypt_create_iv($raw_salt_len, MCRYPT_DEV_URANDOM); + if ($buffer) { + $buffer_valid = true; + } + } + if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) { + $buffer = openssl_random_pseudo_bytes($raw_salt_len); + if ($buffer) { + $buffer_valid = true; + } + } + if (!$buffer_valid && is_readable('/dev/urandom')) { + $f = fopen('/dev/urandom', 'r'); + $read = strlen($buffer); + while ($read < $raw_salt_len) { + $buffer .= fread($f, $raw_salt_len - $read); + $read = strlen($buffer); + } + fclose($f); + if ($read >= $raw_salt_len) { + $buffer_valid = true; + } + } + if (!$buffer_valid || strlen($buffer) < $raw_salt_len) { + $bl = strlen($buffer); + for ($i = 0; $i < $raw_salt_len; $i++) { + if ($i < $bl) { + $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255)); + } else { + $buffer .= chr(mt_rand(0, 255)); + } + } + } + $salt = str_replace('+', '.', base64_encode($buffer)); + } + $salt = substr($salt, 0, $required_salt_len); + + $hash = $hash_format . $salt; + + $ret = crypt($password, $hash); + + if (!is_string($ret) || strlen($ret) <= 13) { + return false; + } + + return $ret; + } + + /** + * Get information about the password hash. Returns an array of the information + * that was used to generate the password hash. + * + * array( + * 'algo' => 1, + * 'algoName' => 'bcrypt', + * 'options' => array( + * 'cost' => 10, + * ), + * ) + * + * @param string $hash The password hash to extract info from + * + * @return array The array of information about the hash. + */ + function password_get_info($hash) { + $return = array( + 'algo' => 0, + 'algoName' => 'unknown', + 'options' => array(), + ); + if (substr($hash, 0, 4) == '$2y$' && strlen($hash) == 60) { + $return['algo'] = PASSWORD_BCRYPT; + $return['algoName'] = 'bcrypt'; + list($cost) = sscanf($hash, "$2y$%d$"); + $return['options']['cost'] = $cost; + } + return $return; + } + + /** + * Determine if the password hash needs to be rehashed according to the options provided + * + * If the answer is true, after validating the password using password_verify, rehash it. + * + * @param string $hash The hash to test + * @param int $algo The algorithm used for new password hashes + * @param array $options The options array passed to password_hash + * + * @return boolean True if the password needs to be rehashed. + */ + function password_needs_rehash($hash, $algo, array $options = array()) { + $info = password_get_info($hash); + if ($info['algo'] != $algo) { + return true; + } + switch ($algo) { + case PASSWORD_BCRYPT: + $cost = isset($options['cost']) ? $options['cost'] : 10; + if ($cost != $info['options']['cost']) { + return true; + } + break; + } + return false; + } + + /** + * Verify a password against a hash using a timing attack resistant approach + * + * @param string $password The password to verify + * @param string $hash The hash to verify against + * + * @return boolean If the password matches the hash + */ + function password_verify($password, $hash) { + if (!function_exists('crypt')) { + trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING); + return false; + } + $ret = crypt($password, $hash); + if (!is_string($ret) || strlen($ret) != strlen($hash) || strlen($ret) <= 13) { + return false; + } + + $status = 0; + for ($i = 0; $i < strlen($ret); $i++) { + $status |= (ord($ret[$i]) ^ ord($hash[$i])); + } + + return $status === 0; + } +} diff --git a/src/includes/psmconfig.inc.php b/src/includes/psmconfig.inc.php new file mode 100644 index 00000000..cc9c4bfe --- /dev/null +++ b/src/includes/psmconfig.inc.php @@ -0,0 +1,81 @@ +. + * + * @package phpservermon + * @author Pepijn Over + * @copyright Copyright (c) 2008-2014 Pepijn Over + * @license http://www.gnu.org/licenses/gpl.txt GNU GPL v3 + * @version Release: @package_version@ + * @link http://www.phpservermonitor.org/ + * @since phpservermon 2.2.0 + **/ + +/** + * Current PSM version + */ +define('PSM_VERSION', '2.2.0-dev'); + +/** + * Configuration for: Hashing strength + * This is the place where you define the strength of your password hashing/salting + * + * To make password encryption very safe and future-proof, the PHP 5.5 hashing/salting functions + * come with a clever so called COST FACTOR. This number defines the base-2 logarithm of the rounds of hashing, + * something like 2^12 if your cost factor is 12. By the way, 2^12 would be 4096 rounds of hashing, doubling the + * round with each increase of the cost factor and therefore doubling the CPU power it needs. + * Currently, in 2013, the developers of this functions have chosen a cost factor of 10, which fits most standard + * server setups. When time goes by and server power becomes much more powerful, it might be useful to increase + * the cost factor, to make the password hashing one step more secure. Have a look here + * (@see https://github.com/panique/php-login/wiki/Which-hashing-&-salting-algorithm-should-be-used-%3F) + * in the BLOWFISH benchmark table to get an idea how this factor behaves. For most people this is irrelevant, + * but after some years this might be very very useful to keep the encryption of your database up to date. + * + * Remember: Every time a user registers or tries to log in (!) this calculation will be done. + * Don't change this if you don't know what you do. + * + * To get more information about the best cost factor please have a look here + * @see http://stackoverflow.com/q/4443476/1114320 + * + * This constant will be used in the login and the registration class. + */ +define("PSM_LOGIN_HASH_COST_FACTOR", "10"); + +/** + * Configuration for: Cookies + * Please note: The COOKIE_DOMAIN needs the domain where your app is, + * in a format like this: .mydomain.com + * Note the . in front of the domain. No www, no http, no slash here! + * For local development .127.0.0.1 or .localhost is fine, but when deploying you should + * change this to your real domain, like '.mydomain.com' ! The leading dot makes the cookie available for + * sub-domains too. + * @see http://stackoverflow.com/q/9618217/1114320 + * @see http://www.php.net/manual/en/function.setcookie.php + * + * COOKIE_RUNTIME: How long should a cookie be valid ? 1209600 seconds = 2 weeks + * COOKIE_DOMAIN: The domain where the cookie is valid for, like '.mydomain.com' + * COOKIE_SECRET_KEY: Put a random value here to make your app more secure. When changed, all cookies are reset. + */ +define("PSM_LOGIN_COOKIE_RUNTIME", 1209600); +define("PSM_LOGIN_COOKIE_DOMAIN", null); +define("PSM_LOGIN_COOKIE_SECRET_KEY", "4w900de52e3ap7y77y8675jy6c594286"); + +/** + * Number of seconds the reset link is valid after sending it to the user. + */ +define('PSM_LOGIN_RESET_RUNTIME', 3600); \ No newline at end of file diff --git a/src/lang/bg.lang.php b/src/lang/bg.lang.php index 98ead862..6a53c0bf 100644 --- a/src/lang/bg.lang.php +++ b/src/lang/bg.lang.php @@ -50,14 +50,31 @@ $sm_lang = array( 'add_new' => 'Добави нов', 'update_available' => 'Налична е нова версия. Може да я свалите от тук.', 'back_to_top' => 'Нагоре', + 'go_back' => 'Go back', ), 'users' => array( 'user' => 'Потребител', 'name' => 'Име', + 'user_name' => 'Username', + 'password' => 'Password', + 'password_repeat' => 'Password repeat', + 'password_leave_blank' => 'Leave blank to keep unchanged', + 'level' => 'Level', + 'level_10' => 'Administrator', + 'level_20' => 'User', 'mobile' => 'Мобилен телефон', 'email' => 'Имейл', 'updated' => 'Информацията за потребителя е обновена.', 'inserted' => 'Потребителят е добавен.', + 'error_user_name_bad_length' => 'Usernames must be between 2 and 64 characters.', + 'error_user_name_invalid' => 'It may only contain alphabetic characters (a-z, A-Z), digits (0-9) and underscores (_).', + 'error_user_name_exists' => 'The given username already exists in the database.', + 'error_user_email_bad_length' => 'Email addresses must be between 5 and 255 characters.', + 'error_user_email_invalid' => 'The email address is invalid.', + 'error_user_level_invalid' => 'The given user level is invalid.', + 'error_user_no_match' => 'The user could not be found in the database.', + 'error_user_password_invalid' => 'The entered password is invalid.', + 'error_user_password_no_match' => 'The entered passwords do not match.', ), 'log' => array( 'title' => 'Записи в лога', @@ -149,6 +166,27 @@ $sm_lang = array( 'on_email_subject' => 'Връзката до \'%LABEL%\' е ВЪЗСТАНОВЕНА', 'on_email_body' => "Връзката до '%LABEL%' беше ВЪЗСТАНОВЕНА:

Сървър: %LABEL%
IP адрес: %IP%
Порт: %PORT%
Днес: %DATE%", ), + 'login' => array( + 'welcome_usermenu' => 'Welcome, %user_name%', + 'title_sign_in' => 'Please sign in', + 'title_forgot' => 'Forgot your password?', + 'title_reset' => 'Reset your password', + 'submit' => 'Submit', + 'remember_me' => 'Remember me', + 'login' => 'Login', + 'logout' => 'Logout', + 'username' => 'Username', + 'password' => 'Password', + 'password_repeat' => 'Repeat password', + 'password_forgot' => 'Forgot password?', + 'password_reset' => 'Reset password', + 'password_reset_email_subject' => 'Reset your password for PHP Server Monitor', + 'password_reset_email_body' => 'Please use the following link to reset your password. Please note it expires in 1 hour.

%link%', + 'error_user_incorrect' => 'The provided username could not be found.', + 'error_login_incorrect' => 'The information is incorrect.', + 'error_login_passwords_nomatch' => 'The provided passwords do not match.', + 'error_reset_invalid_link' => 'The reset link you provided is invalid.', + 'success_password_forgot' => 'An email has been sent to you with information how to reset your password.', + 'success_password_reset' => 'Your password has been reset successfully. Please login.', + ), ); - -?> diff --git a/src/lang/br.lang.php b/src/lang/br.lang.php index 1b1dc5be..be4bcb92 100755 --- a/src/lang/br.lang.php +++ b/src/lang/br.lang.php @@ -50,14 +50,31 @@ $sm_lang = array( 'add_new' => 'Adicionar novo?', 'update_available' => 'Uma atualização disponível em http://phpservermon.sourceforge.net.', 'back_to_top' => 'Voltar ao topo', + 'go_back' => 'Go back', ), 'users' => array( 'user' => 'usuário', 'name' => 'Nome', + 'user_name' => 'Username', + 'password' => 'Password', + 'password_repeat' => 'Password repeat', + 'password_leave_blank' => 'Leave blank to keep unchanged', + 'level' => 'Level', + 'level_10' => 'Administrator', + 'level_20' => 'User', 'mobile' => 'Celular', 'email' => 'Email', 'updated' => 'Usuário atualizado.', 'inserted' => 'Usuário adicionado.', + 'error_user_name_bad_length' => 'Usernames must be between 2 and 64 characters.', + 'error_user_name_invalid' => 'It may only contain alphabetic characters (a-z, A-Z), digits (0-9) and underscores (_).', + 'error_user_name_exists' => 'The given username already exists in the database.', + 'error_user_email_bad_length' => 'Email addresses must be between 5 and 255 characters.', + 'error_user_email_invalid' => 'The email address is invalid.', + 'error_user_level_invalid' => 'The given user level is invalid.', + 'error_user_no_match' => 'The user could not be found in the database.', + 'error_user_password_invalid' => 'The entered password is invalid.', + 'error_user_password_no_match' => 'The entered passwords do not match.', ), 'log' => array( 'title' => 'Entradas do Log', @@ -149,6 +166,27 @@ $sm_lang = array( 'on_email_subject' => 'IMPORTANTE: Servidor \'%LABEL%\' esta ONLINE', 'on_email_body' => "Servidor '%LABEL%' esta ONLINE novamente:

Servidor: %LABEL%
IP: %IP%
Porta: %PORT%
Data: %DATE%", ), + 'login' => array( + 'welcome_usermenu' => 'Welcome, %user_name%', + 'title_sign_in' => 'Please sign in', + 'title_forgot' => 'Forgot your password?', + 'title_reset' => 'Reset your password', + 'submit' => 'Submit', + 'remember_me' => 'Remember me', + 'login' => 'Login', + 'logout' => 'Logout', + 'username' => 'Username', + 'password' => 'Password', + 'password_repeat' => 'Repeat password', + 'password_forgot' => 'Forgot password?', + 'password_reset' => 'Reset password', + 'password_reset_email_subject' => 'Reset your password for PHP Server Monitor', + 'password_reset_email_body' => 'Please use the following link to reset your password. Please note it expires in 1 hour.

%link%', + 'error_user_incorrect' => 'The provided username could not be found.', + 'error_login_incorrect' => 'The information is incorrect.', + 'error_login_passwords_nomatch' => 'The provided passwords do not match.', + 'error_reset_invalid_link' => 'The reset link you provided is invalid.', + 'success_password_forgot' => 'An email has been sent to you with information how to reset your password.', + 'success_password_reset' => 'Your password has been reset successfully. Please login.', + ), ); - -?> \ No newline at end of file diff --git a/src/lang/de.lang.php b/src/lang/de.lang.php index 42a482bf..affcbd5f 100755 --- a/src/lang/de.lang.php +++ b/src/lang/de.lang.php @@ -50,14 +50,31 @@ $sm_lang = array( 'add_new' => 'Neuen Eintrag erstellen?', 'update_available' => 'Ein neues Update ist verfügbar auf http://phpservermon.sourceforge.net.', 'back_to_top' => 'Back to top', + 'go_back' => 'Go back', ), 'users' => array( 'user' => 'Benutzer', 'name' => 'Name', + 'user_name' => 'Username', + 'password' => 'Password', + 'password_repeat' => 'Password repeat', + 'password_leave_blank' => 'Leave blank to keep unchanged', + 'level' => 'Level', + 'level_10' => 'Administrator', + 'level_20' => 'User', 'mobile' => 'Mobil', 'email' => 'Email', 'updated' => 'Benutzer bearbeitet.', 'inserted' => 'Benutzer eingetragen.', + 'error_user_name_bad_length' => 'Usernames must be between 2 and 64 characters.', + 'error_user_name_invalid' => 'It may only contain alphabetic characters (a-z, A-Z), digits (0-9) and underscores (_).', + 'error_user_name_exists' => 'The given username already exists in the database.', + 'error_user_email_bad_length' => 'Email addresses must be between 5 and 255 characters.', + 'error_user_email_invalid' => 'The email address is invalid.', + 'error_user_level_invalid' => 'The given user level is invalid.', + 'error_user_no_match' => 'The user could not be found in the database.', + 'error_user_password_invalid' => 'The entered password is invalid.', + 'error_user_password_no_match' => 'The entered passwords do not match.', ), 'log' => array( 'title' => 'Log Einträge', @@ -149,6 +166,27 @@ $sm_lang = array( 'on_email_subject' => 'Wichtig: Server \'%LABEL%\' ist wieder Online', 'on_email_body' => "Server '%LABEL%' läuft wieder:

Server: %LABEL%
IP: %IP%
Port: %PORT%
Datum: %DATE%", ), + 'login' => array( + 'welcome_usermenu' => 'Welcome, %user_name%', + 'title_sign_in' => 'Please sign in', + 'title_forgot' => 'Forgot your password?', + 'title_reset' => 'Reset your password', + 'submit' => 'Submit', + 'remember_me' => 'Remember me', + 'login' => 'Login', + 'logout' => 'Logout', + 'username' => 'Username', + 'password' => 'Password', + 'password_repeat' => 'Repeat password', + 'password_forgot' => 'Forgot password?', + 'password_reset' => 'Reset password', + 'password_reset_email_subject' => 'Reset your password for PHP Server Monitor', + 'password_reset_email_body' => 'Please use the following link to reset your password. Please note it expires in 1 hour.

%link%', + 'error_user_incorrect' => 'The provided username could not be found.', + 'error_login_incorrect' => 'The information is incorrect.', + 'error_login_passwords_nomatch' => 'The provided passwords do not match.', + 'error_reset_invalid_link' => 'The reset link you provided is invalid.', + 'success_password_forgot' => 'An email has been sent to you with information how to reset your password.', + 'success_password_reset' => 'Your password has been reset successfully. Please login.', + ), ); - -?> \ No newline at end of file diff --git a/src/lang/en.lang.php b/src/lang/en.lang.php index 1f52a06e..bdafc5d4 100755 --- a/src/lang/en.lang.php +++ b/src/lang/en.lang.php @@ -50,14 +50,31 @@ $sm_lang = array( 'add_new' => 'Add new?', 'update_available' => 'A new update is available from http://phpservermon.sourceforge.net.', 'back_to_top' => 'Back to top', + 'go_back' => 'Go back', ), 'users' => array( 'user' => 'user', 'name' => 'Name', + 'user_name' => 'Username', + 'password' => 'Password', + 'password_repeat' => 'Password repeat', + 'password_leave_blank' => 'Leave blank to keep unchanged', + 'level' => 'Level', + 'level_10' => 'Administrator', + 'level_20' => 'User', 'mobile' => 'Mobile', 'email' => 'Email', 'updated' => 'User updated.', 'inserted' => 'User added.', + 'error_user_name_bad_length' => 'Usernames must be between 2 and 64 characters.', + 'error_user_name_invalid' => 'It may only contain alphabetic characters (a-z, A-Z), digits (0-9) and underscores (_).', + 'error_user_name_exists' => 'The given username already exists in the database.', + 'error_user_email_bad_length' => 'Email addresses must be between 5 and 255 characters.', + 'error_user_email_invalid' => 'The email address is invalid.', + 'error_user_level_invalid' => 'The given user level is invalid.', + 'error_user_no_match' => 'The user could not be found in the database.', + 'error_user_password_invalid' => 'The entered password is invalid.', + 'error_user_password_no_match' => 'The entered passwords do not match.', ), 'log' => array( 'title' => 'Log entries', @@ -149,6 +166,27 @@ $sm_lang = array( 'on_email_subject' => 'IMPORTANT: Server \'%LABEL%\' is RUNNING', 'on_email_body' => "Server '%LABEL%' is running again:

Server: %LABEL%
IP: %IP%
Port: %PORT%
Date: %DATE%", ), + 'login' => array( + 'welcome_usermenu' => 'Welcome, %user_name%', + 'title_sign_in' => 'Please sign in', + 'title_forgot' => 'Forgot your password?', + 'title_reset' => 'Reset your password', + 'submit' => 'Submit', + 'remember_me' => 'Remember me', + 'login' => 'Login', + 'logout' => 'Logout', + 'username' => 'Username', + 'password' => 'Password', + 'password_repeat' => 'Repeat password', + 'password_forgot' => 'Forgot password?', + 'password_reset' => 'Reset password', + 'password_reset_email_subject' => 'Reset your password for PHP Server Monitor', + 'password_reset_email_body' => 'Please use the following link to reset your password. Please note it expires in 1 hour.

%link%', + 'error_user_incorrect' => 'The provided username could not be found.', + 'error_login_incorrect' => 'The information is incorrect.', + 'error_login_passwords_nomatch' => 'The provided passwords do not match.', + 'error_reset_invalid_link' => 'The reset link you provided is invalid.', + 'success_password_forgot' => 'An email has been sent to you with information how to reset your password.', + 'success_password_reset' => 'Your password has been reset successfully. Please login.', + ), ); - -?> \ No newline at end of file diff --git a/src/lang/fr.lang.php b/src/lang/fr.lang.php index 47ea06b6..60faf91b 100755 --- a/src/lang/fr.lang.php +++ b/src/lang/fr.lang.php @@ -50,14 +50,31 @@ $sm_lang = array( 'add_new' => 'Rajouter un nouveau serveur?', 'update_available' => 'Une nouvelle version est disponible à l adresse http://phpservermon.sourceforge.net.', 'back_to_top' => 'Haut de page', + 'go_back' => 'Go back', ), 'users' => array( 'user' => 'Utilisateur', 'name' => 'Nom', + 'user_name' => 'Username', + 'password' => 'Password', + 'password_repeat' => 'Password repeat', + 'password_leave_blank' => 'Leave blank to keep unchanged', + 'level' => 'Level', + 'level_10' => 'Administrator', + 'level_20' => 'User', 'mobile' => 'Mobile', 'email' => 'Email', 'updated' => 'Utilisateur mis à jour.', 'inserted' => 'Utilisateur ajouté.', + 'error_user_name_bad_length' => 'Usernames must be between 2 and 64 characters.', + 'error_user_name_invalid' => 'It may only contain alphabetic characters (a-z, A-Z), digits (0-9) and underscores (_).', + 'error_user_name_exists' => 'The given username already exists in the database.', + 'error_user_email_bad_length' => 'Email addresses must be between 5 and 255 characters.', + 'error_user_email_invalid' => 'The email address is invalid.', + 'error_user_level_invalid' => 'The given user level is invalid.', + 'error_user_no_match' => 'The user could not be found in the database.', + 'error_user_password_invalid' => 'The entered password is invalid.', + 'error_user_password_no_match' => 'The entered passwords do not match.', ), 'log' => array( 'title' => 'Evenements', @@ -122,7 +139,6 @@ $sm_lang = array( 'Vous recevrez une seule notification à 1 heure du matin et uniquement celle-ci.
'. '
Toujours: '. 'Vous recevrez une notification à chaque passage de la tache planifiée si le site est en statut ETEINT ', - 'alert_type_status' => 'Changement de statut', 'alert_type_offline' => 'Eteint', 'alert_type_always' => 'Toujours', @@ -149,6 +165,27 @@ $sm_lang = array( 'on_email_subject' => 'IMPORTANT: Le Serveur \'%LABEL%\' est OK', 'on_email_body' => "Le Serveur '%LABEL%' est de nouveau OK:

Serveur: %LABEL%
IP: %IP%
Port: %PORT%
Date: %DATE%", ), + 'login' => array( + 'welcome_usermenu' => 'Welcome, %user_name%', + 'title_sign_in' => 'Please sign in', + 'title_forgot' => 'Forgot your password?', + 'title_reset' => 'Reset your password', + 'submit' => 'Submit', + 'remember_me' => 'Remember me', + 'login' => 'Login', + 'logout' => 'Logout', + 'username' => 'Username', + 'password' => 'Password', + 'password_repeat' => 'Repeat password', + 'password_forgot' => 'Forgot password?', + 'password_reset' => 'Reset password', + 'password_reset_email_subject' => 'Reset your password for PHP Server Monitor', + 'password_reset_email_body' => 'Please use the following link to reset your password. Please note it expires in 1 hour.

%link%', + 'error_user_incorrect' => 'The provided username could not be found.', + 'error_login_incorrect' => 'The information is incorrect.', + 'error_login_passwords_nomatch' => 'The provided passwords do not match.', + 'error_reset_invalid_link' => 'The reset link you provided is invalid.', + 'success_password_forgot' => 'An email has been sent to you with information how to reset your password.', + 'success_password_reset' => 'Your password has been reset successfully. Please login.', + ), ); - -?> \ No newline at end of file diff --git a/src/lang/kr.lang.php b/src/lang/kr.lang.php index 321bd5fe..8b1c61b3 100755 --- a/src/lang/kr.lang.php +++ b/src/lang/kr.lang.php @@ -23,7 +23,6 @@ * @license http://www.gnu.org/licenses/gpl.txt GNU GPL v3 * @version Release: @package_version@ * @link http://www.phpservermonitor.org/ - * @since phpservermon 2.1 **/ $sm_lang = array( @@ -51,14 +50,31 @@ $sm_lang = array( 'add_new' => '새계정 추가', 'update_available' => '새로운 업데이트가 있습니다. 다음사이트를 방문 해 주십시오. http://phpservermon.sourceforge.net.', 'back_to_top' => 'Back to top', + 'go_back' => 'Go back', ), 'users' => array( 'user' => '사용자', 'name' => '이름', + 'user_name' => 'Username', + 'password' => 'Password', + 'password_repeat' => 'Password repeat', + 'password_leave_blank' => 'Leave blank to keep unchanged', + 'level' => 'Level', + 'level_10' => 'Administrator', + 'level_20' => 'User', 'mobile' => '휴대폰', 'email' => 'Email', 'updated' => '수정되었습니다.', 'inserted' => '추가되었습니다.', + 'error_user_name_bad_length' => 'Usernames must be between 2 and 64 characters.', + 'error_user_name_invalid' => 'It may only contain alphabetic characters (a-z, A-Z), digits (0-9) and underscores (_).', + 'error_user_name_exists' => 'The given username already exists in the database.', + 'error_user_email_bad_length' => 'Email addresses must be between 5 and 255 characters.', + 'error_user_email_invalid' => 'The email address is invalid.', + 'error_user_level_invalid' => 'The given user level is invalid.', + 'error_user_no_match' => 'The user could not be found in the database.', + 'error_user_password_invalid' => 'The entered password is invalid.', + 'error_user_password_no_match' => 'The entered passwords do not match.', ), 'log' => array( 'title' => 'Log entries', @@ -148,6 +164,27 @@ $sm_lang = array( 'on_email_subject' => '중요: 서버(\'%LABEL%\')가 가동중입니다.', 'on_email_body' => "서버('%LABEL%')가 재가동됩니다.:

Server: %LABEL%
IP: %IP%
Port: %PORT%
Date: %DATE%", ), + 'login' => array( + 'welcome_usermenu' => 'Welcome, %user_name%', + 'title_sign_in' => 'Please sign in', + 'title_forgot' => 'Forgot your password?', + 'title_reset' => 'Reset your password', + 'submit' => 'Submit', + 'remember_me' => 'Remember me', + 'login' => 'Login', + 'logout' => 'Logout', + 'username' => 'Username', + 'password' => 'Password', + 'password_repeat' => 'Repeat password', + 'password_forgot' => 'Forgot password?', + 'password_reset' => 'Reset password', + 'password_reset_email_subject' => 'Reset your password for PHP Server Monitor', + 'password_reset_email_body' => 'Please use the following link to reset your password. Please note it expires in 1 hour.

%link%', + 'error_user_incorrect' => 'The provided username could not be found.', + 'error_login_incorrect' => 'The information is incorrect.', + 'error_login_passwords_nomatch' => 'The provided passwords do not match.', + 'error_reset_invalid_link' => 'The reset link you provided is invalid.', + 'success_password_forgot' => 'An email has been sent to you with information how to reset your password.', + 'success_password_reset' => 'Your password has been reset successfully. Please login.', + ), ); - -?> \ No newline at end of file diff --git a/src/lang/nl.lang.php b/src/lang/nl.lang.php index 0b3bdb59..96ecd21b 100755 --- a/src/lang/nl.lang.php +++ b/src/lang/nl.lang.php @@ -50,14 +50,31 @@ $sm_lang = array( 'add_new' => 'Voeg toe?', 'update_available' => 'Een nieuwe update is beschikbaar op http://phpservermon.sourceforge.net.', 'back_to_top' => 'Terug naar boven', + 'go_back' => 'Terug', ), 'users' => array( 'user' => 'gebruiker', 'name' => 'Naam', + 'user_name' => 'Username', + 'password' => 'Password', + 'password_repeat' => 'Password repeat', + 'password_leave_blank' => 'Leave blank to keep unchanged', + 'level' => 'Level', + 'level_10' => 'Administrator', + 'level_20' => 'User', 'mobile' => 'Mobiel', 'email' => 'Email', 'updated' => 'Gebruiker gewijzigd.', 'inserted' => 'Gebruiker toegevoegd.', + 'error_user_name_bad_length' => 'Usernames must be between 2 and 64 characters.', + 'error_user_name_invalid' => 'It may only contain alphabetic characters (a-z, A-Z), digits (0-9) and underscores (_).', + 'error_user_name_exists' => 'The given username already exists in the database.', + 'error_user_email_bad_length' => 'Email addresses must be between 5 and 255 characters.', + 'error_user_email_invalid' => 'The email address is invalid.', + 'error_user_level_invalid' => 'The given user level is invalid.', + 'error_user_no_match' => 'The user could not be found in the database.', + 'error_user_password_invalid' => 'The entered password is invalid.', + 'error_user_password_no_match' => 'The entered passwords do not match.', ), 'log' => array( 'title' => 'Log entries', @@ -148,6 +165,27 @@ $sm_lang = array( 'on_email_subject' => 'BELANGRIJK: Server %LABEL% is RUNNING', 'on_email_body' => "Server %LABEL% is weer online:

Server: %LABEL%
IP: %IP%
Poort: %PORT%
Datum: %DATE%", ), + 'login' => array( + 'welcome_usermenu' => 'Welcome, %user_name%', + 'title_sign_in' => 'Please sign in', + 'title_forgot' => 'Forgot your password?', + 'title_reset' => 'Reset your password', + 'submit' => 'Submit', + 'remember_me' => 'Remember me', + 'login' => 'Login', + 'logout' => 'Logout', + 'username' => 'Username', + 'password' => 'Password', + 'password_repeat' => 'Repeat password', + 'password_forgot' => 'Forgot password?', + 'password_reset' => 'Reset password', + 'password_reset_email_subject' => 'Reset your password for PHP Server Monitor', + 'password_reset_email_body' => 'Please use the following link to reset your password. Please note it expires in 1 hour.

%link%', + 'error_user_incorrect' => 'The provided username could not be found.', + 'error_login_incorrect' => 'The information is incorrect.', + 'error_login_passwords_nomatch' => 'The provided passwords do not match.', + 'error_reset_invalid_link' => 'The reset link you provided is invalid.', + 'success_password_forgot' => 'An email has been sent to you with information how to reset your password.', + 'success_password_reset' => 'Your password has been reset successfully. Please login.', + ), ); - -?> \ No newline at end of file diff --git a/src/psm/Module/AbstractModule.class.php b/src/psm/Module/AbstractModule.class.php index dddad3cf..1a9d4625 100755 --- a/src/psm/Module/AbstractModule.class.php +++ b/src/psm/Module/AbstractModule.class.php @@ -76,7 +76,7 @@ abstract class AbstractModule implements ModuleInterface { /** * Messages to show the user * @var array $messages - * @see getMessage() + * @see addMessage() */ protected $messages = array(); @@ -99,6 +99,19 @@ abstract class AbstractModule implements ModuleInterface { */ protected $tpl_id; + /** + * User service + * @var \psm\Service\User $user + */ + protected $user; + + /** + * Required user level for this module + * @var int $user_level_required + * @see setMinUserLevelRequired() + */ + protected $user_level_required = PSM_USER_USER; + function __construct(Database $db, Template $tpl) { $this->db = $db; $this->tpl = $tpl; @@ -153,13 +166,14 @@ abstract class AbstractModule implements ModuleInterface { // user wants updates, lets see what we can do $this->createHTMLUpdateAvailable(); } - $tpl_data = array( - 'message' => (empty($this->messages)) ? ' ' : implode('
', $this->messages), - ); + $tpl_data = array(); + + if(!empty($this->messages)) { + $this->tpl->addTemplateDataRepeat('main', 'messages', $this->messages); + } // add menu to page? if($this->add_menu) { - $this->tpl->newTemplate('main_menu', 'main.tpl.html'); - $tpl_data['html_menu'] = $this->tpl->getTemplate('main_menu'); + $tpl_data['html_menu'] = $this->createHTMLMenu(); } // add footer to page? if($this->add_footer) { @@ -181,15 +195,69 @@ abstract class AbstractModule implements ModuleInterface { echo $this->tpl->display($this->getTemplateId()); } + /** + * Create HTML code for the menu + * @return string + */ + protected function createHTMLMenu() { + // @todo globals..? seriously..? + global $type; + + $ulvl = ($this->user) ? $this->user->getUserLevel() : PSM_USER_ANONYMOUS; + + $tpl_id = 'main_menu'; + $this->tpl->newTemplate($tpl_id, 'main.tpl.html'); + + $tpl_data = array( + 'label_help' => psm_get_lang('system', 'help'), + 'label_logout' => psm_get_lang('login', 'logout'), + 'url_logout' => psm_build_url(array('logout' => 1)), + ); + + switch($ulvl) { + case PSM_USER_ADMIN: + $items = array('servers', 'users', 'log', 'status', 'config', 'update'); + break; + case PSM_USER_USER: + $items = array('servers', 'log', 'status', 'update'); + break; + default: + $items = array(); + break; + } + $menu = array(); + foreach($items as $key) { + $menu[] = array( + 'active' => ($key == $type) ? 'active' : '', + 'url' => psm_build_url(array('type' => $key)), + 'label' => psm_get_lang('system', $key), + ); + } + if(!empty($menu)) { + $this->tpl->addTemplateDataRepeat($tpl_id, 'menu', $menu); + } + + if($ulvl != PSM_USER_ANONYMOUS) { + $user = $this->user->getUser(); + $tpl_data['label_usermenu'] = str_replace( + '%user_name%', + $user->name, + psm_get_lang('login', 'welcome_usermenu') + ); + } + $this->tpl->addTemplateData($tpl_id, $tpl_data); + + return $this->tpl->getTemplate($tpl_id); + } + /** * First check if an update is available, if there is add a message * to the main template */ protected function createHTMLUpdateAvailable() { - // check for updates? - if(psm_check_updates()) { // yay, new update available =D + // @todo perhaps find a way to make the message more persistent? $this->addMessage(psm_get_lang('system', 'update_available')); } } @@ -207,14 +275,6 @@ abstract class AbstractModule implements ModuleInterface { array( 'title' => strtoupper(psm_get_lang('system', 'title')), 'subtitle' => psm_get_lang('system', $type), - 'active_' . $type => 'active', - 'label_servers' => psm_get_lang('system', 'servers'), - 'label_users' => psm_get_lang('system', 'users'), - 'label_log' => psm_get_lang('system', 'log'), - 'label_status' => psm_get_lang('system', 'status'), - 'label_config' => psm_get_lang('system', 'config'), - 'label_update' => psm_get_lang('system', 'update'), - 'label_help' => psm_get_lang('system', 'help'), 'label_back_to_top' => psm_get_lang('system', 'back_to_top'), ) ); @@ -298,15 +358,50 @@ abstract class AbstractModule implements ModuleInterface { /** * Add one or multiple message to the stack to be displayed to the user * @param string|array $msg + * @param string $status success/warning/error * @return \psm\Module\AbstractModule */ - public function addMessage($msg) { + public function addMessage($msg, $status = 'info') { if(!is_array($msg)) { $msg = array($msg); } - $this->messages = array_merge($this->messages, $msg); + if($status == 'error') { + $shortcode = 'important'; + } else { + $shortcode = $status; + } + + foreach($msg as $m) { + $this->messages[] = array( + 'message' => $m, + 'status' => ($status == null) ? '' : strtoupper($status), + 'shortcode' => $shortcode, + ); + } return $this; } -} -?> \ No newline at end of file + /** + * Set user service + * @param \psm\Service\User $user + */ + public function setUser(\psm\Service\User $user) { + $this->user = $user; + } + + /** + * Set the minimum required user level for this module + * @param int $level + */ + public function setMinUserLevelRequired($level) { + $this->user_level_required = intval($level); + } + + /** + * Get the minimum required user level for this module + * @return int + */ + public function getMinUserLevelRequired() { + return $this->user_level_required; + } +} diff --git a/src/psm/Module/Install.class.php b/src/psm/Module/Install.class.php index c1965206..6ab9cfc8 100755 --- a/src/psm/Module/Install.class.php +++ b/src/psm/Module/Install.class.php @@ -32,13 +32,6 @@ use psm\Service\Template; class Install extends AbstractModule { - /** - * Result messages to add to the main template - * @var array $install_results - * @see addResult() - */ - protected $install_results = array(); - /** * Full path to config file * @var string $path_config @@ -54,6 +47,8 @@ class Install extends AbstractModule { function __construct(Database $db, Template $tpl) { parent::__construct($db, $tpl); + $this->addMenu(false); + $this->path_config = PSM_PATH_SRC . '../config.php'; $this->path_config_old = PSM_PATH_SRC . '../config.inc.php'; @@ -63,18 +58,18 @@ class Install extends AbstractModule { } protected function createHTML() { - $tpl_id_custom = $this->getTemplateId(); - $this->setTemplateId('install', 'install.tpl.html'); - $html_install = ($tpl_id_custom) ? $this->tpl->getTemplate($tpl_id_custom) : ''; - $html_results = ''; - if(!empty($this->install_results)) { + if(!empty($this->messages)) { $this->tpl->newTemplate('install_results', 'install.tpl.html'); - $this->tpl->addTemplateDataRepeat('install_results', 'resultmsgs', $this->install_results); + $this->tpl->addTemplateDataRepeat('install_results', 'resultmsgs', $this->messages); $html_results = $this->tpl->getTemplate('install_results'); + $this->messages = array(); } + $tpl_id = $this->getTemplateId(); + $this->setTemplateId('install', 'install.tpl.html'); + $this->tpl->addTemplateData($this->getTemplateId(), array( - 'html_install' => $html_install, + 'html_install' => $this->tpl->getTemplate($tpl_id), 'html_results' => $html_results, )); @@ -82,11 +77,10 @@ class Install extends AbstractModule { } /** - * Generate the main install page with prerequisites + * Say hi to our new user */ protected function executeIndex() { - $this->addMenu(false); - $tpl_data = array(); + $this->setTemplateId('install_index', 'install.tpl.html'); // build prerequisites $errors = 0; @@ -94,30 +88,22 @@ class Install extends AbstractModule { $phpv = phpversion(); if(version_compare($phpv, '5.3.0', '<')) { $errors++; - $this->addResult('PHP 5.3+ is required to run PHP Server Monitor.', 'error'); + $this->addMessage('PHP 5.3+ is required to run PHP Server Monitor.', 'error'); } else { - $this->addResult('PHP version: ' . $phpv); + $this->addMessage('PHP version: ' . $phpv, 'success'); } if(!function_exists('curl_init')) { - $this->addResult('PHP is installed without the cURL module. Please install cURL.', 'warning'); + $this->addMessage('PHP is installed without the cURL module. Please install cURL.', 'warning'); } else { - $this->addResult('cURL installed'); + $this->addMessage('PHP cURL module found', 'success'); } if(!in_array('mysql', \PDO::getAvailableDrivers())) { $errors++; - $this->addResult('The PDO MySQL driver needs to be installed.', 'error'); + $this->addMessage('The PDO MySQL driver needs to be installed.', 'error'); } if($errors > 0) { - // cannot continue - $this->addResult($errors . ' error(s) have been encountered. Please fix them and refresh this page.', 'error'); - } else { - if(defined('PSM_CONFIG')) { - $this->addResult('Configuration file found.'); - return $this->executeInstall(); - } else { - return $this->executeConfig(); - } + $this->addMessage($errors . ' error(s) have been encountered. Please fix them and refresh this page.', 'error'); } } @@ -125,110 +111,163 @@ class Install extends AbstractModule { * Help the user create a new config file */ protected function executeConfig() { - if(defined('PSM_CONFIG')) { - return $this->executeInstall(); - } - // first detect "old" config file (2.0) - if(file_exists($this->path_config_old)) { - // oldtimer huh - $this->addResult('Configuration file for v2.0 found.'); - $this->addResult( - 'The location of the config file has been changed since the previous version.
' . - 'We will attempt to create a new config file for you.' - , 'warning'); - $values = $this->parseConfig20(); - } else { - // fresh install - $values = $_POST; - } - - $config = array( - 'host' => 'localhost', - 'name' => '', - 'user' => '', - 'pass' => '', - 'prefix' => 'psm_', - ); $this->setTemplateId('install_config_new', 'install.tpl.html'); + $tpl_data = array(); - $changed = false; - foreach($config as $ckey => &$cvalue) { - if(isset($values[$ckey])) { - $changed = true; - $cvalue = $values[$ckey]; + if(!defined('PSM_CONFIG')) { + // first detect "old" config file (2.0) + if(file_exists($this->path_config_old)) { + // oldtimer huh + $this->addMessage('Configuration file for v2.0 found.', 'success'); + $this->addMessage( + 'The location of the config file has been changed since v2.0.
' . + 'We will attempt to create a new config file for you.' + , 'warning'); + $values = $this->parseConfig20(); + } else { + // fresh install + $values = $_POST; } - } - // add config to template data for prefilling the form - $tpl_data = $config; - if($changed) { - // test db connection - $this->db = new \psm\Service\Database( - $config['host'], - $config['user'], - $config['pass'], - $config['name'] + $config = array( + 'host' => 'localhost', + 'name' => '', + 'user' => '', + 'pass' => '', + 'prefix' => 'psm_', ); - if($this->db->status()) { - $this->addResult('Connection to MySQL successful.'); - $config_php = $this->writeConfigFile($config); - if($config_php === true) { - $this->addResult('Configuration file written successfully.'); - return $this->executeInstall(); - } else { - $this->addResult('Config file is not writable, we cannot save it for you.', 'error'); - $this->tpl->newTemplate('install_config_new_copy', 'install.tpl.html'); - $tpl_data['html_config_copy'] = $this->tpl->getTemplate('install_config_new_copy'); - $tpl_data['php_config'] = $config_php; + $changed = false; + foreach($config as $ckey => &$cvalue) { + if(isset($values[$ckey])) { + $changed = true; + $cvalue = $values[$ckey]; + } + } + // add config to template data for prefilling the form + $tpl_data = $config; + + if($changed) { + // test db connection + $this->db = new \psm\Service\Database( + $config['host'], + $config['user'], + $config['pass'], + $config['name'] + ); + + if($this->db->status()) { + $this->addMessage('Connection to MySQL successful.', 'success'); + $config_php = $this->writeConfigFile($config); + if($config_php === true) { + $this->addMessage('Configuration file written successfully.', 'success'); + } else { + $this->addMessage('Config file is not writable, we cannot save it for you.', 'error'); + $this->tpl->newTemplate('install_config_new_copy', 'install.tpl.html'); + $tpl_data['html_config_copy'] = $this->tpl->getTemplate('install_config_new_copy'); + $tpl_data['php_config'] = $config_php; + } + } else { + $this->addMessage('Unable to connect to MySQL. Please check your information.', 'error'); } - } else { - $this->addResult('Unable to connect to MySQL. Please check your information.', 'error'); } } + if(defined('PSM_CONFIG')) { + if($this->db->status()) { + if($this->isUpgrade()) { + // upgrade + if(version_compare($version_from, '2.2.0', '<')) { + // upgrade from before 2.2, does not have passwords yet.. create new user first + $this->addMessage('PLEASE CREATE A USER!', 'warning'); + $this->setTemplateId('install_config_new_user', 'install.tpl.html'); + } else { + $this->setTemplateId('install_config_upgrade', 'install.tpl.html'); + $tpl_data['version'] = PSM_VERSION; + } + } else { + // fresh install ahead + $this->setTemplateId('install_config_new_user', 'install.tpl.html'); + + $tpl_data['username'] = (isset($_POST['username'])) ? $_POST['username'] : ''; + $tpl_data['email'] = (isset($_POST['email'])) ? $_POST['email'] : ''; + } + } else { + $this->addMessage('Configuration file found, but unable to connect to MySQL. Please check your information.', 'error'); + } + } $this->tpl->addTemplateData($this->getTemplateId(), $tpl_data); } /** - * Parse the 2.0 config file for prefilling - * @return array - */ - protected function parseConfig20() { - $config_old = file_get_contents($this->path_config_old); - $vars = array( - 'prefix' => '', - 'user' => '', - 'pass' => '', - 'name' => '', - 'host' => '', - ); - $pattern = "/define\('SM_DB_{key}', '(.*?)'/u"; - - foreach($vars as $key => $value) { - $pattern_key = str_replace('{key}', strtoupper($key), $pattern); - preg_match($pattern_key, $config_old, $value_matches); - $vars[$key] = (isset($value_matches[1])) ? $value_matches[1] : ''; - } - - return $vars; - } - - /** - * Execute the upgrade process to a newer version + * Execute the install and upgrade process to a newer version */ protected function executeInstall() { - if(!defined('PSM_CONFIG')) { - $this->addResult('No valid configuration found.', 'error'); + if(!defined('PSM_CONFIG') || !$this->db->status()) { return $this->executeConfig(); } - if(!$this->db->status()) { - $this->addResult('MySQL connection failed.', 'error'); - return; - } - $logger = array($this, 'addResult'); + // check if user submitted username + password in previous step + // this would only be the case for new installs, and install from + // before 2.2 + $new_user = array( + 'user_name' => psm_POST('username'), + 'name' => psm_POST('username'), + 'password' => psm_POST('password'), + 'password_repeat' => psm_POST('password_repeat'), + 'email' => psm_POST('email', ''), + 'level' => PSM_USER_ADMIN, + ); + + $validator = new \psm\Util\User\UserValidator($this->user); + + $logger = array($this, 'addMessage'); $installer = new \psm\Util\Install\Installer($this->db, $logger); - $installer->install(); + + if($this->isUpgrade()) { + $this->addMessage('Upgrade process started.', 'info'); + + $version_from = $this->getPreviousVersion(); + if($version_from === false) { + $this->addMessage('Unable to locate your previous version. Please run a fresh install.', 'error'); + } else { + if(version_compare($version_from, PSM_VERSION, '=')) { + $this->addMessage('Your installation is already at the latest version.', 'success'); + } elseif(version_compare($version_from, PSM_VERSION, '>')) { + $this->addMessage('This installer does not support downgrading, sorry.', 'error'); + } else { + $this->addMessage('Upgrading from ' . $version_from . ' to ' . PSM_VERSION, 'info'); + $installer->upgrade($version_from, PSM_VERSION); + + } + if(version_compare($version_from, '2.2.0', '<')) { + $add_user = true; + } + } + } else { + // validate the lot + try { + $validator->email($new_user['email']); + $validator->password($new_user['password'], $new_user['password_repeat']); + } catch(\InvalidArgumentException $e) { + $this->addMessage(psm_get_lang('users', 'error_' . $e->getMessage()), 'error'); + return $this->executeConfig(); + } + + $this->addMessage('Installation process started.', 'success'); + $installer->install(); + // add user + $add_user = true; + } + + if($add_user) { + unset($new_user['password_repeat']); + $user_id = $this->db->save(PSM_DB_PREFIX.'users', $new_user); + if(intval($user_id) > 0) { + $this->addMessage('User account has been created successfully.'); + } else { + $this->addMessage('There was an error adding your user account.'); + } + } $this->setTemplateId('install_success', 'install.tpl.html'); } @@ -262,30 +301,63 @@ class Install extends AbstractModule { } /** - * Add install result to be added to the main template - * @param string|array $msg - * @param string $status success/warning/error - * @return \psm\Module\Install + * Parse the 2.0 config file for prefilling + * @return array */ - public function addResult($msg, $status = 'success') { - if(!is_array($msg)) { - $msg = array($msg); - } - if($status == 'error') { - $shortcode = 'important'; - } else { - $shortcode = $status; + protected function parseConfig20() { + $config_old = file_get_contents($this->path_config_old); + $vars = array( + 'prefix' => '', + 'user' => '', + 'pass' => '', + 'name' => '', + 'host' => '', + ); + $pattern = "/define\('SM_DB_{key}', '(.*?)'/u"; + + foreach($vars as $key => $value) { + $pattern_key = str_replace('{key}', strtoupper($key), $pattern); + preg_match($pattern_key, $config_old, $value_matches); + $vars[$key] = (isset($value_matches[1])) ? $value_matches[1] : ''; } - foreach($msg as $m) { - $this->install_results[] = array( - 'message' => $m, - 'status' => strtoupper($status), - 'shortcode' => $shortcode, - ); + return $vars; + } + + /** + * Is it an upgrade or install? + */ + protected function isUpgrade() { + if(!$this->db->status()) { + return false; + } + $confExists = $this->db->query("SHOW TABLES LIKE '".PSM_DB_PREFIX."config';", false)->rowCount(); + + if($confExists > 0) { + return true; + } else { + return false; + } + } + + /** + * Get the previous version from the config table + * @return boolean|string FALSE on failure, string otherwise + */ + protected function getPreviousVersion() { + if(!$this->isUpgrade()) { + return false; + } + $version_conf = $this->db->selectRow(PSM_DB_PREFIX . 'config', array('key' => 'version'), array('value')); + if(empty($version_conf)) { + return false; + } else { + $version_from = $version_conf['value']; + if(strpos($version_from, '.') === false) { + // yeah, my bad.. previous version did not follow proper naming scheme + $version_from = rtrim(chunk_split($version_from, 1, '.'), '.'); + } + return $version_from; } - return $this; } } - -?> \ No newline at end of file diff --git a/src/psm/Module/Login.class.php b/src/psm/Module/Login.class.php new file mode 100644 index 00000000..37d29149 --- /dev/null +++ b/src/psm/Module/Login.class.php @@ -0,0 +1,182 @@ +. + * + * @package phpservermon + * @author Pepijn Over + * @copyright Copyright (c) 2008-2014 Pepijn Over + * @license http://www.gnu.org/licenses/gpl.txt GNU GPL v3 + * @version Release: @package_version@ + * @link http://www.phpservermonitor.org/ + * @since phpservermon 2.2.0 + **/ + +namespace psm\Module; +use psm\Service\Database; +use psm\Service\Template; + +class Login extends AbstractModule { + + function __construct(Database $db, Template $tpl) { + parent::__construct($db, $tpl); + + $this->setMinUserLevelRequired(PSM_USER_ANONYMOUS); + + $this->setActions(array( + 'login', 'forgot', 'reset', + ), 'login'); + + $this->addMenu(false); + } + + protected function executeLogin() { + $this->setTemplateId('login', 'login.tpl.html'); + + if(isset($_POST['user_name']) && isset($_POST['user_password'])) { + $rememberme = (isset($_POST['user_rememberme'])) ? true : false; + $result = $this->user->loginWithPostData( + $_POST['user_name'], + $_POST['user_password'], + $rememberme + ); + + if($result) { + // success login, redirect + header('Location: ' . $_SERVER['REQUEST_URI']); + die(); + } else { + $this->addMessage(psm_get_lang('login', 'error_login_incorrect'), 'error'); + } + } + + $tpl_data = array( + 'title_sign_in' => psm_get_lang('login', 'title_sign_in'), + 'label_username' => psm_get_lang('login', 'username'), + 'label_password' => psm_get_lang('login', 'password'), + 'label_remember_me' => psm_get_lang('login', 'remember_me'), + 'label_login' => psm_get_lang('login', 'login'), + 'label_password_forgot' => psm_get_lang('login', 'password_forgot'), + 'value_user_name' => (isset($_POST['user_name'])) ? $_POST['user_name'] : '', + 'value_rememberme' => (isset($rememberme) && $rememberme) ? 'checked="checked"' : '', + ); + + $this->tpl->addTemplateData($this->getTemplateId(), $tpl_data); + } + + /** + * Show/process the password forgot form (before the mail) + */ + protected function executeForgot() { + $this->setTemplateId('login_forgot', 'login.tpl.html'); + + if(isset($_POST['user_name'])) { + $user = $this->user->getUserByUsername($_POST['user_name']); + + if(!empty($user)) { + $token = $this->user->generatePasswordResetToken($user->user_id); + // we have a token, send it along + $this->sendPasswordForgotMail( + $user->user_id, + $user->email, + $token + ); + + $this->addMessage(psm_get_lang('login', 'success_password_forgot'), 'success'); + return $this->executeLogin(); + } else { + $this->addMessage(psm_get_lang('login', 'error_user_incorrect'), 'error'); + } + } + + $tpl_data = array( + 'title_forgot' => psm_get_lang('login', 'title_forgot'), + 'label_username' => psm_get_lang('login', 'username'), + 'label_submit' => psm_get_lang('login', 'submit'), + 'label_go_back' => psm_get_lang('system', 'go_back'), + ); + + $this->tpl->addTemplateData($this->getTemplateId(), $tpl_data); + } + + /** + * Show/process the password reset form (after the mail) + */ + protected function executeReset() { + $this->setTemplateId('login_reset', 'login.tpl.html'); + + $user_id = (isset($_GET['user_id'])) ? intval($_GET['user_id']) : 0; + $token = (isset($_GET['token'])) ? $_GET['token'] : ''; + + if(!$this->user->verifyPasswordResetToken($user_id, $token)) { + $this->addMessage(psm_get_lang('login', 'error_reset_invalid_link'), 'error'); + return $this->executeLogin(); + } + + if(!empty($_POST['user_password_new']) && !empty($_POST['user_password_repeat'])) { + if($_POST['user_password_new'] !== $_POST['user_password_repeat']) { + $this->addMessage(psm_get_lang('login', 'error_login_passwords_nomatch'), 'error'); + } else { + $result = $this->user->changePassword($user_id, $_POST['user_password_new']); + + if($result) { + $this->addMessage(psm_get_lang('login', 'success_password_reset'), 'success'); + return $this->executeLogin(); + } else { + $this->addMessage(psm_get_lang('login', 'error_login_incorrect'), 'error'); + } + } + } + $user = $this->user->getUser($user_id); + + $tpl_data = array( + 'title_reset' => psm_get_lang('login', 'title_reset'), + 'label_username' => psm_get_lang('login', 'username'), + 'label_password' => psm_get_lang('login', 'password'), + 'label_password_repeat' => psm_get_lang('login', 'password_repeat'), + 'label_reset' => psm_get_lang('login', 'password_reset'), + 'label_go_back' => psm_get_lang('system', 'go_back'), + 'value_user_name' => $user->user_name, + ); + + $this->tpl->addTemplateData($this->getTemplateId(), $tpl_data); + } + + /** + * Sends the password-reset-email. + * @param int $user_id + * @param string $user_email + * @param string $user_password_reset_hash + */ + protected function sendPasswordForgotMail($user_id, $user_email, $user_password_reset_hash) { + $mail = psm_build_mail(); + $mail->Subject = psm_get_lang('login' ,'password_reset_email_subject'); + + $url = psm_build_url(array( + 'action' => 'reset', + 'user_id' => $user_id, + 'token' => $user_password_reset_hash, + )); + $body = psm_get_lang('login', 'password_reset_email_body'); + $body = str_replace('%link%', $url, $body); + $mail->Body = $body; + $mail->AltBody = str_replace('
', "\n", $body); + + $mail->AddAddress($user_email); + $mail->Send(); + } +} diff --git a/src/psm/Module/Users.class.php b/src/psm/Module/Users.class.php index 4235bb4a..b9f0fd4f 100755 --- a/src/psm/Module/Users.class.php +++ b/src/psm/Module/Users.class.php @@ -36,77 +36,27 @@ use psm\Service\Template; class Users extends AbstractModule { public $servers; + /** + * User data validator + * @var \psm\Util\User\UserValidator $user_validator + */ + protected $user_validator; + function __construct(Database $db, Template $tpl) { parent::__construct($db, $tpl); + $this->setMinUserLevelRequired(PSM_USER_ADMIN); + $this->setActions(array( 'index', 'edit', 'delete', 'save', ), 'index'); - - $this->servers = $this->db->select(PSM_DB_PREFIX.'servers', null, array('server_id', 'label')); } - /** - * Prepare the template to show the update screen for a user - */ - protected function executeEdit() { - $this->setTemplateId('users_update', 'users.tpl.html'); + public function initialize() { + $this->user_validator = new \psm\Util\User\UserValidator($this->user); + $this->servers = $this->db->select(PSM_DB_PREFIX.'servers', null, array('server_id', 'label')); - $user_id = isset($_GET['id']) ? intval($_GET['id']) : 0; - - $tpl_data = array(); - $servers_count = count($this->servers); - - switch((int) $user_id) { - case 0: - // insert mode - $tpl_data['titlemode'] = psm_get_lang('system', 'insert'); - $tpl_data['edit_user_id'] = '0'; - - // add inactive class to all servers - for ($i = 0; $i < $servers_count; $i++) { - $this->servers[$i]['class'] = 'inactive'; - } - - break; - default: - // edit mode - $edit_user = $this->db->selectRow( - PSM_DB_PREFIX.'users', - array('user_id' => $user_id) - ); - if (empty($edit_user)) { - $this->addMessage('Invalid user.'); - return $this->initializeAction('index'); - } - - $tpl_data = array_merge($tpl_data, array( - 'titlemode' => psm_get_lang('system', 'edit') . ' ' . $edit_user['name'], - 'edit_user_id' => $edit_user['user_id'], - 'edit_value_name' => $edit_user['name'], - 'edit_value_mobile' => $edit_user['mobile'], - 'edit_value_email' => $edit_user['email'], - )); - - // select servers for this user - $user_servers = explode(',', $edit_user['server_id']); - - for ($h = 0; $h < $servers_count; $h++) { - if(in_array($this->servers[$h]['server_id'], $user_servers)) { - $this->servers[$h]['edit_checked'] = 'checked="checked"'; - $this->servers[$h]['class'] = 'active'; - } - } - - break; - } - - $this->tpl->addTemplateData( - $this->getTemplateId(), - $tpl_data - ); - // add servers to template for the edit form - $this->tpl->addTemplateDataRepeat('users_update', 'servers', $this->servers); + return parent::initialize(); } /** @@ -121,66 +71,164 @@ class Users extends AbstractModule { $servers_labels[$server['server_id']] = $server['label']; } - // get users from database $users = $this->db->select( PSM_DB_PREFIX.'users', null, - null, + array('user_id', 'user_name', 'level', 'server_id', 'name', 'mobile', 'email'), null, array('name') ); - $user_count = count($users); + foreach($users as $x => &$user) { + $user['class'] = ($x & 1) ? 'odd' : 'even'; - for ($x = 0; $x < $user_count; $x++) { - $users[$x]['class'] = ($x & 1) ? 'odd' : 'even'; - - $users[$x]['emp_servers'] = ''; + $user['emp_servers'] = ''; // fix server list - $user_servers = explode(',', $users[$x]['server_id']); - if (empty($user_servers)) continue; + $user_servers = explode(',', $user['server_id']); + if(empty($user_servers)) continue; - foreach ($user_servers as $server) { + foreach($user_servers as $server) { if (!isset($servers_labels[$server])) continue; - $users[$x]['emp_servers'] .= $servers_labels[$server] . '
'; + $user['emp_servers'] .= $servers_labels[$server] . '
'; } - $users[$x]['emp_servers'] = substr($users[$x]['emp_servers'], 0, -5); + $user['emp_servers'] = substr($user['emp_servers'], 0, -5); } - // add servers to template $this->tpl->addTemplateDataRepeat($this->getTemplateId(), 'users', $users); } + /** + * Prepare the template to show the update screen for a user + */ + protected function executeEdit() { + $this->setTemplateId('users_update', 'users.tpl.html'); + + $user_id = isset($_GET['id']) ? intval($_GET['id']) : 0; + $fields_prefill = array('name', 'user_name', 'mobile', 'email'); + + if($user_id == 0) { + // insert mode + $title = psm_get_lang('system', 'insert'); + $placeholder_password = ''; + $lvl_selected = PSM_USER_USER; // default level is regular user + + // attempt to prefill previously posted fields + $edit_user = new \stdClass(); + foreach($fields_prefill as $field) { + $edit_user->$field = (isset($_POST[$field])) ? $_POST[$field] : ''; + } + + // add inactive class to all servers + foreach($this->servers as &$server) { + $server['class'] = 'inactive'; + } + } else { + // edit mode + try { + $this->user_validator->userId($user_id); + } catch(\InvalidArgumentException $e) { + $this->addMessage(psm_get_lang('users', 'error_' . $e->getMessage()), 'error'); + return $this->executeIndex(); + } + $edit_user = $this->user->getUser($user_id); + $title = psm_get_lang('system', 'edit') . ' ' . $edit_user->name; + $placeholder_password = psm_get_lang('users', 'password_leave_blank'); + $lvl_selected = $edit_user->level; + + // select servers for this user + $user_servers = explode(',', $edit_user->server_id); + + foreach($this->servers as &$server) { + if(in_array($server['server_id'], $user_servers)) { + $server['edit_checked'] = 'checked="checked"'; + $server['class'] = 'active'; + } + } + } + $tpl_data = array( + 'titlemode' => $title, + 'placeholder_password' => $placeholder_password, + 'edit_user_id' => $user_id, + ); + foreach($fields_prefill as $field) { + if(isset($edit_user->$field)) { + $tpl_data['edit_value_' . $field] = $edit_user->$field; + } + } + + $ulvls_tpl = array(); + foreach($this->user_validator->getUserLevels() as $lvl) { + $ulvls_tpl[] = array( + 'value' => $lvl, + 'label' => psm_get_lang('users', 'level_' . $lvl), + 'selected' => ($lvl == $lvl_selected) ? 'selected="selected"' : '', + ); + } + $this->tpl->addTemplateDataRepeat($this->getTemplateId(), 'levels', $ulvls_tpl); + $this->tpl->addTemplateDataRepeat($this->getTemplateId(), 'servers', $this->servers); + $this->tpl->addTemplateData($this->getTemplateId(), $tpl_data); + } + /** * Executes the saving of a user */ protected function executeSave() { - // check for add/edit mode - if (isset($_POST['name']) && isset($_POST['mobile']) && isset($_POST['email'])) { - $clean = array( - 'name' => $_POST['name'], - 'mobile' => $_POST['mobile'], - 'email' => $_POST['email'], - 'server_id' => (isset($_POST['server_id'])) ? implode(',', $_POST['server_id']) : '' - ); - $id = (isset($_GET['id'])) ? intval($_GET['id']) : 0; + if(empty($_POST)) { + // dont process anything if no data has been posted + return $this->executeIndex(); + } + $user_id = (isset($_GET['id'])) ? intval($_GET['id']) : 0; - // check for edit or add - if ((int) $id > 0) { - // edit - $this->db->save( - PSM_DB_PREFIX.'users', - $clean, - array('user_id' => $id) - ); - $this->addMessage(psm_get_lang('users', 'updated')); + $fields = array('name', 'user_name', 'password', 'password_repeat', 'level', 'mobile', 'email', 'server_id'); + $clean = array(); + foreach($fields as $field) { + if(isset($_POST[$field])) { + if(is_array($_POST[$field])) { + $_POST[$field] = implode(',', $_POST[$field]); + } + $clean[$field] = trim(strip_tags($_POST[$field])); } else { - // add - $this->db->save(PSM_DB_PREFIX.'users', $clean); - $this->addMessage(psm_get_lang('users', 'inserted')); + $clean[$field] = ''; } } - $this->initializeAction('index'); + + // validate the lot + try { + $this->user_validator->username($clean['user_name'], $user_id); + $this->user_validator->email($clean['email']); + $this->user_validator->level($clean['level']); + + // always validate password for new users, + // but only validate it for existing users when they change it. + if($user_id == 0 || ($user_id > 0 && $clean['password'] != '')) { + $this->user_validator->password($clean['password'], $clean['password_repeat']); + } + if($user_id > 0) { + $this->user_validator->userId($user_id); + } + } catch(\InvalidArgumentException $e) { + $this->addMessage(psm_get_lang('users', 'error_' . $e->getMessage()), 'error'); + return $this->executeEdit(); + } + if(!empty($clean['password'])) { + $password = $clean['password']; + $clean['password'] = ''; + } + unset($clean['password_repeat']); + + if($user_id > 0) { + // edit user + $this->db->save(PSM_DB_PREFIX.'users', $clean, array('user_id' => $user_id)); + $this->addMessage(psm_get_lang('users', 'updated'), 'success'); + } else { + // add user + $user_id = $this->db->save(PSM_DB_PREFIX.'users', $clean); + $this->addMessage(psm_get_lang('users', 'inserted'), 'success'); + } + if(isset($password)) { + $this->user->changePassword($user_id, $password); + } + return $this->executeIndex(); } /** @@ -189,16 +237,19 @@ class Users extends AbstractModule { protected function executeDelete() { $id = (isset($_GET['id'])) ? intval($_GET['id']) : 0; - if($id > 0) { + try { + $this->user_validator->userId($id); + $this->db->delete( PSM_DB_PREFIX . 'users', - array( - 'user_id' => $id, - ) + array('user_id' => $id,) ); - $this->addMessage(psm_get_lang('system', 'deleted')); + $this->addMessage(psm_get_lang('system', 'deleted'), 'success'); + } catch(\InvalidArgumentException $e) { + $this->addMessage(psm_get_lang('users', 'error_' . $e->getMessage()), 'error'); } - $this->initializeAction('index'); + + return $this->executeIndex(); } // override parent::createHTMLLabels() @@ -208,11 +259,19 @@ class Users extends AbstractModule { array( 'label_users' => psm_get_lang('system', 'users'), 'label_name' => psm_get_lang('users', 'name'), + 'label_user_name' => psm_get_lang('users', 'user_name'), + 'label_password' => psm_get_lang('users', 'password'), + 'label_password_repeat' => psm_get_lang('users', 'password_repeat'), + 'label_level' => psm_get_lang('users', 'level'), + 'label_level_10' => psm_get_lang('users', 'level_10'), + 'label_level_20' => psm_get_lang('users', 'level_20'), + 'label_level_30' => psm_get_lang('users', 'level_30'), 'label_mobile' => psm_get_lang('users', 'mobile'), 'label_email' => psm_get_lang('users', 'email'), 'label_servers' => psm_get_lang('system', 'servers'), 'label_action' => psm_get_lang('system', 'action'), 'label_save' => psm_get_lang('system', 'save'), + 'label_go_back' => psm_get_lang('system', 'go_back'), 'label_edit' => psm_get_lang('system', 'edit') . ' ' . psm_get_lang('users', 'user'), 'label_delete' => psm_get_lang('system', 'delete') . ' ' . psm_get_lang('users', 'user'), 'label_add_new' => psm_get_lang('system', 'add_new'), @@ -222,5 +281,3 @@ class Users extends AbstractModule { return parent::createHTMLLabels(); } } - -?> \ No newline at end of file diff --git a/src/psm/Service/User.class.php b/src/psm/Service/User.class.php new file mode 100644 index 00000000..8b58fd44 --- /dev/null +++ b/src/psm/Service/User.class.php @@ -0,0 +1,423 @@ +. + * + * @package phpservermon + * @author Panique + * @author Pepijn Over + * @copyright Copyright (c) 2008-2014 Pepijn Over + * @license http://www.gnu.org/licenses/gpl.txt GNU GPL v3 + * @version Release: @package_version@ + * @link http://www.phpservermonitor.org/ + * @since phpservermon 2.2.0 + **/ + +namespace psm\Service; + +/** + * This is a heavily modified version of the php-login-advanced project by Panique. + * + * @author Panique + * @author Pepijn Over + * @link http://www.php-login.net + * @link https://github.com/panique/php-login-advanced/ + * @license http://opensource.org/licenses/MIT MIT License + */ +class User { + + /** + * The database connection + * @var \PDO $db_connection + */ + protected $db_connection = null; + + /** + * Local cache of user data + * @var array $user_data + */ + protected $user_data = array(); + + /** + * Current user id + * @var int $user_id + */ + protected $user_id; + + /** + * The user's login status + * @var boolean $user_is_logged_in + */ + protected $user_is_logged_in = false; + + /** + * the function "__construct()" automatically starts whenever an object of this class is created, + * you know, when you do "$login = new Login();" + */ + public function __construct(Database $db) { + $this->db_connection = $db->pdo(); + + session_start(); + + // check the possible login actions: + // 1. logout (happen when user clicks logout button) + // 2. login via session data (happens each time user opens a page on your php project AFTER he has successfully logged in via the login form) + // 3. login via cookie + + // if user has an active session on the server + if(!$this->loginWithSessionData()) { + $this->loginWithCookieData(); + } + + if(isset($_GET["logout"])) { + $this->doLogout(); + // logged out, redirect to login + header('Location: ' . psm_build_url()); + die(); + } + } + + /** + * Get user by id, or get current user. + * @return object|boolean FALSE if user not found, object otherwise + */ + public function getUser($user_id = null) { + if($user_id == null) { + if(!$this->isUserLoggedIn()) { + return false; + } else { + $user_id = $this->getUserId(); + } + } + + if(!isset($this->user_data[$user_id])) { + $query_user = $this->db_connection->prepare('SELECT * FROM '.PSM_DB_PREFIX.'users WHERE user_id = :user_id'); + $query_user->bindValue(':user_id', $user_id, \PDO::PARAM_INT); + $query_user->execute(); + // get result row (as an object) + $this->user_data[$user_id] = $query_user->fetchObject(); + } + return $this->user_data[$user_id]; + } + + /** + * Search into database for the user data of user_name specified as parameter + * @return object|boolean user data as an object if existing user + */ + public function getUserByUsername($user_name) { + // database query, getting all the info of the selected user + $query_user = $this->db_connection->prepare('SELECT * FROM '.PSM_DB_PREFIX.'users WHERE user_name = :user_name'); + $query_user->bindValue(':user_name', $user_name, \PDO::PARAM_STR); + $query_user->execute(); + // get result row (as an object) + return $query_user->fetchObject(); + } + + /** + * Logs in with S_SESSION data. + */ + private function loginWithSessionData() { + if(empty($_SESSION) || !isset($_SESSION['user_id'])) { + return false; + } + $user = $this->getUser($_SESSION['user_id']); + + if(!empty($user)) { + $this->setUserLoggedIn($user->user_id); + return true; + } else { + // user no longer exists in database + // call logout to clean up session vars + $this->doLogout(); + return false; + } + } + + /** + * Logs in via the Cookie + * @return bool success state of cookie login + */ + private function loginWithCookieData() { + if (isset($_COOKIE['rememberme'])) { + // extract data from the cookie + list ($user_id, $token, $hash) = explode(':', $_COOKIE['rememberme']); + // check cookie hash validity + if ($hash == hash('sha256', $user_id . ':' . $token . PSM_LOGIN_COOKIE_SECRET_KEY) && !empty($token)) { + // cookie looks good, try to select corresponding user + // get real token from database (and all other data) + $user = $this->getUser($user_id); + + if(!empty($user) && $token === $user->rememberme_token) { + $this->setUserLoggedIn($user->user_id, true); + + // Cookie token usable only once + $this->newRememberMeCookie(); + return true; + } + } + // call logout to remove invalid cookie + $this->doLogout(); + } + return false; + } + + /** + * Logs in with the data provided in $_POST, coming from the login form + * @param string $user_name + * @param string $user_password + * @param boolean $user_rememberme + * @return boolean + */ + public function loginWithPostData($user_name, $user_password, $user_rememberme = false) { + $user_name = trim($user_name); + $user_password = trim($user_password); + + if(empty($user_name) && empty($user_password)) { + return false; + } + $user = $this->getUserByUsername($user_name); + + // using PHP 5.5's password_verify() function to check if the provided passwords fits to the hash of that user's password + if(!isset($user->user_id)) { + password_verify($user_password, 'dummy_call_against_timing'); + return false; + } else if (! password_verify($user_password, $user->password)) { + return false; + } + + $this->setUserLoggedIn($user->user_id, true); + + // if user has check the "remember me" checkbox, then generate token and write cookie + if ($user_rememberme) { + $this->newRememberMeCookie(); + } + + // recalculate the user's password hash + // DELETE this if-block if you like, it only exists to recalculate users's hashes when you provide a cost factor, + // by default the script will use a cost factor of 10 and never change it. + // check if the have defined a cost factor in config/hashing.php + if (defined('PSM_LOGIN_HASH_COST_FACTOR')) { + // check if the hash needs to be rehashed + if (password_needs_rehash($user->password, PASSWORD_DEFAULT, array('cost' => PSM_LOGIN_HASH_COST_FACTOR))) { + $this->changePassword($user->user_id, $user_password); + } + } + return true; + } + + /** + * Set the user logged in + * @param int $user_id + * @param boolean $regenerate regenerate session id against session fixation? + */ + protected function setUserLoggedIn($user_id, $regenerate = false) { + if($regenerate) { + session_regenerate_id(); + } + $_SESSION['user_id'] = $user_id; + $_SESSION['user_logged_in'] = 1; + + // declare user id, set the login status to true + $this->user_id = $user_id; + $this->user_is_logged_in = true; + } + + /** + * Create all data needed for remember me cookie connection on client and server side + */ + private function newRememberMeCookie() { + // generate 64 char random string and store it in current user data + $random_token_string = hash('sha256', mt_rand()); + $sth = $this->db_connection->prepare('UPDATE '.PSM_DB_PREFIX.'users SET rememberme_token = :user_rememberme_token WHERE user_id = :user_id'); + $sth->execute(array(':user_rememberme_token' => $random_token_string, ':user_id' => $this->getUserId())); + + // generate cookie string that consists of userid, randomstring and combined hash of both + $cookie_string_first_part = $this->getUserId() . ':' . $random_token_string; + $cookie_string_hash = hash('sha256', $cookie_string_first_part . PSM_LOGIN_COOKIE_SECRET_KEY); + $cookie_string = $cookie_string_first_part . ':' . $cookie_string_hash; + + // set cookie + setcookie('rememberme', $cookie_string, time() + PSM_LOGIN_COOKIE_RUNTIME, "/", PSM_LOGIN_COOKIE_DOMAIN); + } + + /** + * Delete all data needed for remember me cookie connection on client and server side + */ + private function deleteRememberMeCookie() { + // Reset rememberme token + if(isset($_SESSION['user_id'])) { + $sth = $this->db_connection->prepare('UPDATE '.PSM_DB_PREFIX.'users SET rememberme_token = NULL WHERE user_id = :user_id'); + $sth->execute(array(':user_id' => $_SESSION['user_id'])); + } + + // set the rememberme-cookie to ten years ago (3600sec * 365 days * 10). + // that's obivously the best practice to kill a cookie via php + // @see http://stackoverflow.com/a/686166/1114320 + setcookie('rememberme', false, time() - (3600 * 3650), '/', PSM_LOGIN_COOKIE_DOMAIN); + } + + /** + * Perform the logout, resetting the session + */ + public function doLogout() { + $this->deleteRememberMeCookie(); + + $_SESSION = array(); + session_destroy(); + session_start(); + session_regenerate_id(); + + $this->user_is_logged_in = false; + } + + /** + * Simply return the current state of the user's login + * @return bool user's login status + */ + public function isUserLoggedIn() { + return $this->user_is_logged_in; + } + + /** + * Sets a random token into the database (that will verify the user when he/she comes back via the link + * in the email) and returns it + * @param int $user_id + * @return string|boolean FALSE on error, string otherwise + */ + public function generatePasswordResetToken($user_id) { + $user_id = intval($user_id); + + if($user_id == 0) { + return false; + } + // generate timestamp (to see when exactly the user (or an attacker) requested the password reset mail) + $temporary_timestamp = time(); + // generate random hash for email password reset verification (40 char string) + $user_password_reset_hash = sha1(uniqid(mt_rand(), true)); + + $query_update = $this->db_connection->prepare('UPDATE '.PSM_DB_PREFIX.'users SET password_reset_hash = :user_password_reset_hash, + password_reset_timestamp = :user_password_reset_timestamp + WHERE user_id = :user_id'); + $query_update->bindValue(':user_password_reset_hash', $user_password_reset_hash, \PDO::PARAM_STR); + $query_update->bindValue(':user_password_reset_timestamp', $temporary_timestamp, \PDO::PARAM_INT); + $query_update->bindValue(':user_id', $user_id, \PDO::PARAM_INT); + $query_update->execute(); + + // check if exactly one row was successfully changed: + if ($query_update->rowCount() == 1) { + return $user_password_reset_hash; + } else { + return false; + } + } + + /** + * Checks if the verification string in the account verification mail is valid and matches to the user. + * + * Please note it is valid for 1 hour. + * @param int $user_id + * @param string $token + * @return boolean + */ + public function verifyPasswordResetToken($user_id, $token) { + $user_id = intval($user_id); + + if(empty($user_id) || empty($token)) { + return false; + } + $user = $this->getUser($user_id); + + if(isset($user->user_id) && $user->password_reset_hash == $token) { + $runtime = (defined('PSM_LOGIN_RESET_RUNTIME')) ? PSM_LOGIN_RESET_RUNTIME : 3600; + $timestamp_max_interval = time() - $runtime; + + if($user->password_reset_timestamp > $timestamp_max_interval) { + return true; + } + } + return false; + } + + /** + * Change the password of a user + * @param int $user_id + * @param string $password + * @return boolean TRUE on success, FALSE on failure + */ + public function changePassword($user_id, $password) { + $user_id = intval($user_id); + + if(empty($user_id) || empty($password)) { + return false; + } + // now it gets a little bit crazy: check if we have a constant PSM_LOGIN_HASH_COST_FACTOR defined (in src/includes/psmconfig.inc.php), + // if so: put the value into $hash_cost_factor, if not, make $hash_cost_factor = null + $hash_cost_factor = (defined('PSM_LOGIN_HASH_COST_FACTOR') ? PSM_LOGIN_HASH_COST_FACTOR : null); + + // crypt the user's password with the PHP 5.5's password_hash() function, results in a 60 character hash string + // the PASSWORD_DEFAULT constant is defined by the PHP 5.5, or if you are using PHP 5.3/5.4, by the password hashing + // compatibility library. the third parameter looks a little bit shitty, but that's how those PHP 5.5 functions + // want the parameter: as an array with, currently only used with 'cost' => XX. + $user_password_hash = password_hash($password, PASSWORD_DEFAULT, array('cost' => $hash_cost_factor)); + + // write users new hash into database + $query_update = $this->db_connection->prepare('UPDATE '.PSM_DB_PREFIX.'users SET password = :user_password_hash, + password_reset_hash = NULL, password_reset_timestamp = NULL + WHERE user_id = :user_id'); + $query_update->bindValue(':user_password_hash', $user_password_hash, \PDO::PARAM_STR); + $query_update->bindValue(':user_id', $user_id, \PDO::PARAM_STR); + $query_update->execute(); + + // check if exactly one row was successfully changed: + if ($query_update->rowCount() == 1) { + return true; + } else { + return false; + } + } + + /** + * Gets the user id + * @return int + */ + public function getUserId() { + return $this->user_id; + } + + /** + * Gets the username + * @return string + */ + public function getUsername() { + $user = $this->getUser(); + return (isset($user->user_name) ? $user->user_name : null); + } + + /** + * Gets the user level + * @return int + */ + public function getUserLevel() { + $user = $this->getUser(); + + if(isset($user->level)) { + return $user->level; + } else { + return PSM_USER_ANONYMOUS; + } + } +} diff --git a/src/psm/Util/Install/Installer.class.php b/src/psm/Util/Install/Installer.class.php index 4a032bd6..ac8f1da3 100755 --- a/src/psm/Util/Install/Installer.class.php +++ b/src/psm/Util/Install/Installer.class.php @@ -100,23 +100,30 @@ class Installer { public function install() { $this->installTables(); - $version_conf = $this->db->selectRow(PSM_DB_PREFIX . 'config', array('key' => 'version'), array('key', 'value')); - - if(empty($version_conf)) { - // fresh install - $version_from = null; - } else { - // existing install - $version_from = $version_conf['value']; - if(strpos($version_from, '.') === false) { - // yeah, my bad.. previous version did not follow proper naming scheme - $version_from = rtrim(chunk_split($version_from, 1, '.'), '.'); - } - } - $this->upgrade(PSM_VERSION, $version_from); - - - $this->log('Installation finished!'); + $this->log('Populating database...'); + $queries = array(); + $queries[] = "INSERT INTO `" . PSM_DB_PREFIX . "servers` (`ip`, `port`, `label`, `type`, `status`, `error`, `rtime`, `last_online`, `last_check`, `active`, `email`, `sms`) VALUES ('http://sourceforge.net/index.php', 80, 'SourceForge', 'website', 'on', '', '', '0000-00-00 00:00:00', '0000-00-00 00:00:00', 'yes', 'yes', 'yes'), ('smtp.gmail.com', 465, 'Gmail SMTP', 'service', 'on', '', '', '0000-00-00 00:00:00', '0000-00-00 00:00:00', 'yes', 'yes', 'yes')"; + $queries[] = "INSERT INTO `" . PSM_DB_PREFIX . "config` (`key`, `value`) VALUE + ('language', 'en'), + ('email_status', '1'), + ('email_from_email', 'monitor@example.org'), + ('email_from_name', 'Server Monitor'), + ('sms_status', '1'), + ('sms_gateway', 'mollie'), + ('sms_gateway_username', 'username'), + ('sms_gateway_password', 'password'), + ('sms_from', '1234567890'), + ('alert_type', 'status'), + ('log_status', '1'), + ('log_email', '1'), + ('log_sms', '1'), + ('version', '" . PSM_VERSION . "'), + ('auto_refresh_servers', '0'), + ('show_update', '1'), + ('last_update_check', '0'), + ('cron_running', '0'), + ('cron_running_time', '0');"; + $this->execSQL($queries); } /** @@ -124,14 +131,21 @@ class Installer { */ protected function installTables() { $tables = array( - PSM_DB_PREFIX . 'users' => "CREATE TABLE `" . PSM_DB_PREFIX . "users` ( - `user_id` int(11) unsigned NOT NULL AUTO_INCREMENT, - `server_id` varchar(255) NOT NULL, - `name` varchar(255) NOT NULL, - `mobile` varchar(15) NOT NULL, - `email` varchar(255) NOT NULL, - PRIMARY KEY (`user_id`) - ) ENGINE=MyISAM DEFAULT CHARSET=utf8;", + PSM_DB_PREFIX . 'users' => "CREATE TABLE IF NOT EXISTS `monitor_users` ( + `user_id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `user_name` varchar(64) NOT NULL COMMENT 'user''s name, unique', + `password` varchar(255) NOT NULL COMMENT 'user''s password in salted and hashed format', + `password_reset_hash` char(40) DEFAULT NULL COMMENT 'user''s password reset code', + `password_reset_timestamp` bigint(20) DEFAULT NULL COMMENT 'timestamp of the password reset request', + `rememberme_token` varchar(64) DEFAULT NULL COMMENT 'user''s remember-me cookie token', + `level` tinyint(2) unsigned NOT NULL DEFAULT '20', + `server_id` varchar(255) NOT NULL, + `name` varchar(255) NOT NULL, + `mobile` varchar(15) NOT NULL, + `email` varchar(255) NOT NULL, + PRIMARY KEY (`user_id`), + UNIQUE KEY `unique_username` (`user_name`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8;", PSM_DB_PREFIX . 'log' => "CREATE TABLE `" . PSM_DB_PREFIX . "log` ( `log_id` int(11) unsigned NOT NULL AUTO_INCREMENT, `server_id` int(11) unsigned NOT NULL, @@ -179,50 +193,19 @@ class Installer { /** * Populate the tables and perform upgrades if necessary - * @param string $version * @param string $version_from + * @param string $version_to */ - public function upgrade($version, $version_from = null) { - if($version_from === null) { - $this->log('Populating database...'); - $queries = array(); - $queries[] = "INSERT INTO `" . PSM_DB_PREFIX . "users` (`server_id`, `name`, `mobile`, `email`) VALUES ('1,2', 'example_user', '0123456789', 'user@example.com')"; - $queries[] = "INSERT INTO `" . PSM_DB_PREFIX . "servers` (`ip`, `port`, `label`, `type`, `status`, `error`, `rtime`, `last_online`, `last_check`, `active`, `email`, `sms`) VALUES ('http://sourceforge.net/index.php', 80, 'SourceForge', 'website', 'on', '', '', '0000-00-00 00:00:00', '0000-00-00 00:00:00', 'yes', 'yes', 'yes'), ('smtp.gmail.com', 465, 'Gmail SMTP', 'service', 'on', '', '', '0000-00-00 00:00:00', '0000-00-00 00:00:00', 'yes', 'yes', 'yes')"; - $queries[] = "INSERT INTO `" . PSM_DB_PREFIX . "config` (`key`, `value`) VALUE - ('language', 'en'), - ('email_status', '1'), - ('email_from_email', 'monitor@example.org'), - ('email_from_name', 'Server Monitor'), - ('sms_status', '1'), - ('sms_gateway', 'mollie'), - ('sms_gateway_username', 'username'), - ('sms_gateway_password', 'password'), - ('sms_from', '1234567890'), - ('alert_type', 'status'), - ('log_status', '1'), - ('log_email', '1'), - ('log_sms', '1'), - ('version', '{$version}'), - ('auto_refresh_servers', '0'), - ('show_update', '1'), - ('last_update_check', '0'), - ('cron_running', '0'), - ('cron_running_time', '0');"; - $this->execSQL($queries); - } else { - if(version_compare($version_from, $version, '<')) { - $this->log('Upgrade detected, upgrading from ' . $version_from . ' to ' . $version); - if(version_compare($version_from, '2.1.0', '<')) { - // upgrade to 2.1.0 - $this->upgrade210(); - } - if(version_compare($version_from, '2.2.0', '<')) { - // upgrade to 2.2.0 - $this->upgrade220(); - } - } - $this->execSQL("UPDATE `" . PSM_DB_PREFIX . "config` SET `value` = '{$version}' WHERE `key` = 'version';"); + public function upgrade($version_from, $version_to) { + if(version_compare($version_from, '2.1.0', '<')) { + // upgrade to 2.1.0 + $this->upgrade210(); } + if(version_compare($version_from, '2.2.0', '<')) { + // upgrade to 2.2.0 + $this->upgrade220(); + } + $this->db->save(PSM_DB_PREFIX . 'config', array('value' => $version_from), array('key' => 'version')); } /** @@ -254,6 +237,17 @@ class Installer { $queries[] = "ALTER TABLE `" . PSM_DB_PREFIX . "log` CHANGE `server_id` `server_id` INT( 11 ) UNSIGNED NOT NULL;"; $queries[] = "ALTER TABLE `" . PSM_DB_PREFIX . "servers` CHANGE `server_id` `server_id` INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT;"; $queries[] = "ALTER TABLE `" . PSM_DB_PREFIX . "users` CHANGE `user_id` `user_id` INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT;"; + $queries[] = "ALTER TABLE `" . PSM_DB_PREFIX . "users` + ADD `user_name` varchar(64) COLLATE utf8_general_ci NOT NULL COMMENT 'user\'s name, unique' AFTER `user_id`, + ADD `password` varchar(255) COLLATE utf8_general_ci NOT NULL COMMENT 'user\'s password in salted and hashed format' AFTER `user_name`, + ADD `password_reset_hash` char(40) COLLATE utf8_general_ci DEFAULT NULL COMMENT 'user\'s password reset code' AFTER `password`, + ADD `password_reset_timestamp` bigint(20) DEFAULT NULL COMMENT 'timestamp of the password reset request' AFTER `password_reset_hash`, + ADD `rememberme_token` varchar(64) COLLATE utf8_general_ci DEFAULT NULL COMMENT 'user\'s remember-me cookie token' AFTER `password_reset_timestamp`, + ADD `level` TINYINT( 2 ) UNSIGNED NOT NULL DEFAULT '20' AFTER `rememberme_token`;"; + // make sure all current users are admins (previously we didnt have non-admins): + $queries[] = "UPDATE `" . PSM_DB_PREFIX . "users` SET `user_name`=`email`, `level`=10;"; + $queries[] = "ALTER TABLE `" . PSM_DB_PREFIX . "users` ADD UNIQUE `unique_username` ( `user_name` );"; + $queries[] = "CREATE TABLE IF NOT EXISTS `" . PSM_DB_PREFIX . "uptime` ( `server_id` INT( 11 ) NOT NULL, `date` DATETIME NOT NULL , @@ -264,5 +258,3 @@ class Installer { $this->execSQL($queries); } } - -?> \ No newline at end of file diff --git a/src/psm/Util/User/UserValidator.class.php b/src/psm/Util/User/UserValidator.class.php new file mode 100644 index 00000000..ef06be2a --- /dev/null +++ b/src/psm/Util/User/UserValidator.class.php @@ -0,0 +1,145 @@ +. + * + * @package phpservermon + * @author Pepijn Over + * @copyright Copyright (c) 2008-2014 Pepijn Over + * @license http://www.gnu.org/licenses/gpl.txt GNU GPL v3 + * @version Release: @package_version@ + * @link http://www.phpservermonitor.org/ + * @since phpservermon 2.2.0 + **/ + +namespace psm\Util\User; + +/** + * The UserValidator helps you to check input data for user accounts. + */ +class UserValidator { + + /** + * Available editable user levels + * @var array $user_levels + */ + protected $user_levels = array(PSM_USER_ADMIN, PSM_USER_USER); + + /** + * User service + * @var \psm\Service\User $user + */ + protected $user; + + public function __construct(\psm\Service\User $user) { + $this->user = $user; + } + + /** + * Check if the user id exists + * @param int $user_id + * @return boolean + * @throws \InvalidArgumentException + */ + public function userId($user_id) { + $user = $this->user->getUser($user_id); + if(empty($user)) { + throw new \InvalidArgumentException('user_no_match'); + } + return true; + } + + /** + * Check username on: + * + * - Length (2-64 chars) + * - Contents (alphabetic chars and digits only) + * - Unique + * @param string $username + * @param int $user_id to check whether the username is unique + * @return boolean + * @throws \InvalidArgumentException + */ + public function username($username, $user_id = 0) { + if(strlen($username) > 64 || strlen($username) < 2) { + throw new \InvalidArgumentException('user_name_bad_length'); + } + if (!preg_match('/^[a-zA-Z\d_]{2,64}$/i', $username)) { + throw new \InvalidArgumentException('user_name_invalid'); + } + $user_exists = $this->user->getUserByUsername($username); + + if(!empty($user_exists) && ($user_id == 0 || $user_id != $user_exists->user_id)) { + throw new \InvalidArgumentException('user_name_exists'); + } + return true; + } + + /** + * Check user password + * @param string $password + * @param string $password_repeat + * @return boolean + * @throws \InvalidArgumentException + */ + public function password($password, $password_repeat) { + if(empty($password) || empty($password_repeat)) { + throw new \InvalidArgumentException('user_password_invalid'); + } + if($password !== $password_repeat) { + throw new \InvalidArgumentException('user_password_no_match'); + } + return true; + } + + /** + * Check email + * @param string $email + * @return boolean + * @throws \InvalidArgumentException + */ + public function email($email) { + if(strlen($email) > 255 || strlen($email) < 5) { + throw new \InvalidArgumentException('user_email_bad_length'); + } + if(!filter_var($email, FILTER_VALIDATE_EMAIL)) { + throw new \InvalidArgumentException('user_email_invalid'); + } + return true; + } + + /** + * Check user level + * @param int $level + * @return boolean + * @throws \InvalidArgumentException + */ + public function level($level) { + if(!in_array($level, $this->user_levels)) { + throw new \InvalidArgumentException('user_level_invalid'); + } + return true; + } + + /** + * Get list of all available user levels + * @return array + */ + public function getUserLevels() { + return $this->user_levels; + } +} diff --git a/src/templates/install.tpl.html b/src/templates/install.tpl.html index 598a131f..5a927e65 100755 --- a/src/templates/install.tpl.html +++ b/src/templates/install.tpl.html @@ -1,25 +1,82 @@
-

 PHP Server Monitor

+

 PHP Server Monitor

 

-

- PHP Server Monitor - Twitter Bootstrap -

-

PHP Server Monitor is a script that checks whether the servers on your list are up and running on the selected ports. It comes with a web based user interface where you can add and remove servers or websites from the MySQL database, and you can manage users for each server with a mobile number and email address.

-

To install PHP Server Monitor, please follow the instructions below.

+ {html_install}
-
-
{html_results}
-
-{html_install} + +

Welcome to the installation of PHP Server Monitor. This page will guide you through the steps to install or upgrade your monitor.

+

Before we start, we need to verify your system meets the requirements. + If you see any errors in the list below, you may still continue, but PHP Server Monitor may not work correctly. + It is recommended you fix any errors before continuing. +

+{html_results} +

 

+

+ Let's go +

+ + + +

We have discovered a previous version.

+

In the next step we will upgrade your database to the latest version.

+{html_results} +

 

+

Upgrade to {version}

+ + + +

Sweet, your database connection is up and running!

+

Next, please set up a new account to access your monitor:

+{html_results} +

 

+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+{html_config_copy} +
+ + +
+
{html_results}
+
-
- +

Please enter your database info:

@@ -66,7 +123,8 @@

Your config file:

Unable to save your configuration.
-

Please create a new file in the project directory called "config.php" and copy the information below.

+

Your database information is valid, however we are unable to create the configuration file automatically. + Please create a new file in the project directory called "config.php" and copy the information below.

After you have copied the configuration, press the button to continue.

@@ -74,11 +132,19 @@ +
+
{html_results}
+
-The installation is complete. Please check above if errors have occured.
-If no errors have occurred, you are good to go.

-Click here to go to the monitor +

 

+

The installation is complete. Please check above if errors have occurred.
+If no errors have occurred, you are good to go.

+

+ Go to your monitor + PHP Server Monitor + Twitter Bootstrap +

diff --git a/src/templates/login.tpl.html b/src/templates/login.tpl.html new file mode 100644 index 00000000..0e5820d3 --- /dev/null +++ b/src/templates/login.tpl.html @@ -0,0 +1,39 @@ + +
+ + + + + + + + {label_password_forgot} + +
+ + + +
+ +
+ + + +
+ +
+ \ No newline at end of file diff --git a/src/templates/main.tpl.html b/src/templates/main.tpl.html index d8bd955c..84f54922 100755 --- a/src/templates/main.tpl.html +++ b/src/templates/main.tpl.html @@ -53,11 +53,18 @@
-
{message}
+
+ +
+

{status}

+

{message}

+
+ + {messages} +
{content}
- {html_footer}
@@ -67,25 +74,21 @@