Merge pull request #299 from phpservermon/develop

Authentication for website checks
pull/300/head
Samuel Denis-D'Ortun 2016-05-21 20:13:46 -04:00
commit 0780fdef3a
17 changed files with 280 additions and 16 deletions

2
.gitattributes vendored
View File

@ -6,7 +6,7 @@
*.php text eol=lf
*.css text eol=lf
*.js text eol=lf
composer.phar text eol=input
*.phar binary
.gitignore export-ignore
.gitattributes export-ignore

1
.htaccess Normal file
View File

@ -0,0 +1 @@
Options -Indexes

View File

@ -23,6 +23,7 @@ v3.2.0 not yet released
* #287: Default language - English
* #286: Add popular ports drop dowwn
* #269: Added Slovenian language
* #96: Authentication for website checks
v3.1.1 (released November 6, 2014)
----------------------------------

View File

@ -53,6 +53,9 @@ The following SMS gateways are currently available:
* Textmarketer - <http://www.textmarketer.co.uk>
* FreeVoipDeal - <http://www.freevoipdeal.com>
* Nexmo - <https://www.nexmo.com/>
* OctoPush - <http://www.octopush.com/>
Please note: for these gateways you will need an account with sufficient credits.

View File

@ -291,9 +291,11 @@ function psm_parse_msg($status, $type, $vars) {
* @param boolean $body return body?
* @param int $timeout connection timeout in seconds. defaults to PSM_CURL_TIMEOUT (10 secs).
* @param boolean $add_agent add user agent?
* @param string|bool $website_username Username website
* @param string|bool $website_password Password website
* @return string cURL result
*/
function psm_curl_get($href, $header = false, $body = true, $timeout = null, $add_agent = true) {
function psm_curl_get($href, $header = false, $body = true, $timeout = null, $add_agent = true, $website_username = false, $website_password = false) {
$timeout = $timeout == null ? PSM_CURL_TIMEOUT : intval($timeout);
$ch = curl_init();
@ -306,7 +308,13 @@ function psm_curl_get($href, $header = false, $body = true, $timeout = null, $ad
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_ENCODING, '');
if($website_username !== false && $website_password !== false && !empty($website_username) && !empty($website_password)) {
curl_setopt($ch, CURLOPT_USERPWD, $website_username . ":" . $website_password);
}
curl_setopt($ch, CURLOPT_URL, $href);
if($add_agent) {
curl_setopt ($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; phpservermon/'.PSM_VERSION.'; +http://www.phpservermonitor.org)');
}
@ -611,4 +619,72 @@ function psm_no_cache() {
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-cache, must-revalidate");
header("Pragma: no-cache");
}
/**
* Encrypts the password for storage in the database
*
* @param string $key
* @param string $password
* @return string
* @author Pavel Laupe Dvorak <pavel@pavel-dvorak.cz>
*/
function psm_password_encrypt($key, $password)
{
if(empty($password))
return '';
if (empty($key))
throw new \InvalidArgumentException('invalid_encryption_key');
$iv = mcrypt_create_iv(
mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC),
MCRYPT_DEV_URANDOM
);
$encrypted = base64_encode(
$iv .
mcrypt_encrypt(
MCRYPT_RIJNDAEL_128,
hash('sha256', $key, true),
$password,
MCRYPT_MODE_CBC,
$iv
)
);
return $encrypted;
}
/**
* Decrypts password stored in the database for future use
*
* @param string $key
* @param string $encryptedString
* @return string
* @author Pavel Laupe Dvorak <pavel@pavel-dvorak.cz>
*/
function psm_password_decrypt($key, $encryptedString)
{
if(empty($encryptedString))
return '';
if (empty($key))
throw new \InvalidArgumentException('invalid_encryption_key');
$data = base64_decode($encryptedString);
$iv = substr($data, 0, mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));
$decrypted = rtrim(
mcrypt_decrypt(
MCRYPT_RIJNDAEL_128,
hash('sha256', $key, true),
substr($data, mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC)),
MCRYPT_MODE_CBC,
$iv
),
"\0"
);
return $decrypted;
}

View File

@ -122,6 +122,13 @@ $sm_lang = array(
'domain' => 'Doména/IP',
'timeout' => 'Časový limit',
'timeout_description' => 'Počet vteřin čekání na odpověď serveru.',
'authentication_settings' => 'Nastavení autentizace (volitelný)',
'website_username' => 'Uživatelské jméno',
'website_username_description' => 'Uživatelské jméno pro přístup na stránku. (Pouze Apache autorizace je podporovaná.)',
'website_password' => 'Heslo',
'website_password_description' => 'Heslo pro přístup na stránku. Heslo je v databázi šifrované.',
'fieldset_monitoring' => 'Monitoring',
'fieldset_permissions' => 'Oprávnění',
'port' => 'Port',
'custom_port' => 'Uživatelský Port',
'popular_ports' => 'Populární Porty',
@ -180,6 +187,8 @@ $sm_lang = array(
'general' => 'Obecné',
'language' => 'Jazyk',
'show_update' => 'Kontrolovat aktualizace?',
'password_encrypt_key' => 'Šifrovací klíč pro hesla',
'password_encrypt_key_note' => 'Tímto klíčem se šifrují hesla, která se ukládají u serverů pro přístup na webové stránky. Pokud klíč změníte budou uložená hesla neplatná!',
'email_status' => 'Povolit odesílání e-mailu',
'email_from_email' => 'E-mailová adresa odesilatele',
'email_from_name' => 'Jméno odesilatele',

View File

@ -121,6 +121,13 @@ $sm_lang = array(
'domain' => 'Domain/IP',
'timeout' => 'Timeout',
'timeout_description' => 'Number of seconds to wait for the server to respond.',
'authentication_settings' => 'Authentication Settings (Optional)',
'website_username' => 'Username',
'website_username_description' => 'Username to access the site. (Only Apache authentication is supported.)',
'website_password' => 'Password',
'website_password_description' => 'Password to access the site. The password is encrypted in the database.',
'fieldset_monitoring' => 'Monitoring',
'fieldset_permissions' => 'Permissions',
'port' => 'Port',
'custom_port' => 'Custom Port',
'popular_ports' => 'Popular Ports',
@ -179,6 +186,8 @@ $sm_lang = array(
'general' => 'General',
'language' => 'Language',
'show_update' => 'Check for updates?',
'password_encrypt_key' => 'The encryption key password',
'password_encrypt_key_note' => 'This key is used to encrypt passwords that are stored on servers for access to websites. If the key will change the stored password is invalid!',
'email_status' => 'Allow sending email',
'email_from_email' => 'Email from address',
'email_from_name' => 'Email from name',

View File

@ -117,6 +117,7 @@ class ConfigController extends AbstractController {
$tpl_data['email_smtp_security_selected_' . $smtp_sec] = 'selected="selected"';
$tpl_data['auto_refresh_servers'] = (isset($config['auto_refresh_servers'])) ? $config['auto_refresh_servers'] : '0';
$tpl_data['log_retention_period'] = (isset($config['log_retention_period'])) ? $config['log_retention_period'] : '365';
$tpl_data['password_encrypt_key'] = (isset($config['password_encrypt_key'])) ? $config['password_encrypt_key'] : sha1(microtime());
foreach($this->checkboxes as $input_key) {
$tpl_data[$input_key . '_checked'] =
@ -159,6 +160,7 @@ class ConfigController extends AbstractController {
: '',
'auto_refresh_servers' => intval(psm_POST('auto_refresh_servers', 0)),
'log_retention_period' => intval(psm_POST('log_retention_period', 365)),
'password_encrypt_key' => psm_POST('password_encrypt_key', sha1(microtime())),
);
foreach($this->checkboxes as $input_key) {
$clean[$input_key] = (isset($_POST[$input_key])) ? '1': '0';
@ -294,6 +296,8 @@ class ConfigController extends AbstractController {
'label_general' => psm_get_lang('config', 'general'),
'label_language' => psm_get_lang('config', 'language'),
'label_show_update' => psm_get_lang('config', 'show_update'),
'label_password_encrypt_key' => psm_get_lang('config', 'password_encrypt_key'),
'label_password_encrypt_key_note' => psm_get_lang('config', 'password_encrypt_key_note'),
'label_email_status' => psm_get_lang('config', 'email_status'),
'label_email_from_email' => psm_get_lang('config', 'email_from_email'),
'label_email_from_name' => psm_get_lang('config', 'email_from_name'),

View File

@ -75,7 +75,9 @@ abstract class AbstractServerController extends AbstractController {
`s`.`pushover`,
`s`.`warning_threshold`,
`s`.`warning_threshold_counter`,
`s`.`timeout`
`s`.`timeout`,
`s`.`website_username`,
`s`.`website_password`
FROM `".PSM_DB_PREFIX."servers` AS `s`
{$sql_join}
{$sql_where}

View File

@ -199,6 +199,8 @@ class ServerController extends AbstractServerController {
'default_value_timeout' => PSM_CURL_TIMEOUT,
'edit_value_pattern' => $edit_server['pattern'],
'edit_value_warning_threshold' => $edit_server['warning_threshold'],
'edit_website_username' => $edit_server['website_username'],
'edit_website_password' => empty($edit_server['website_password']) ? '' : sha1($edit_server['website_password']),
'edit_type_selected_' . $edit_server['type'] => 'selected="selected"',
'edit_active_selected_' . $edit_server['active'] => 'selected="selected"',
'edit_email_selected_' . $edit_server['email'] => 'selected="selected"',
@ -227,14 +229,37 @@ class ServerController extends AbstractServerController {
* Executes the saving of one of the servers
*/
protected function executeSave() {
if(empty($_POST)) {
if (empty($_POST)) {
// dont process anything if no data has been posted
return $this->executeIndex();
}
$encrypted_password = '';
if ( !empty( $_POST['website_password'] )) {
$new_password = psm_POST('website_password');
if ($this->server_id > 0) {
$edit_server = $this->getServers($this->server_id);
$hash = sha1($edit_server['website_password']);
if ($new_password == $hash) {
$encrypted_password = $edit_server['website_password'];
} else {
$encrypted_password = psm_password_encrypt($this->server_id . psm_get_conf('password_encrypt_key'), $new_password);
}
} else {
// We need the server id to encrypt the password. Encryption will be done after the server is added
$encrypted_password = '';
}
}
$clean = array(
'label' => trim(strip_tags(psm_POST('label', ''))),
'ip' => trim(strip_tags(psm_POST('ip', ''))),
'timeout' => (isset($_POST['timeout']) && intval($_POST['timeout']) > 0) ? intval($_POST['timeout']) : null,
'website_username' => psm_POST('website_username', null),
'website_password' => $encrypted_password,
'port' => intval(psm_POST('port', 0)),
'type' => psm_POST('type', ''),
'pattern' => psm_POST('pattern', ''),
@ -278,6 +303,23 @@ class ServerController extends AbstractServerController {
// add
$clean['status'] = 'on';
$this->server_id = $this->db->save(PSM_DB_PREFIX.'servers', $clean);
// server has been added, re-encrypt
if (!empty($_POST['website_password'])) {
$cleanWebsitePassword = array(
'website_password' => psm_password_encrypt(
$this->server_id . psm_get_conf('password_encrypt_key'),
psm_POST('website_password')
),
);
$this->db->save(
PSM_DB_PREFIX . 'servers',
$cleanWebsitePassword,
array('server_id' => $this->server_id)
);
}
$this->addMessage(psm_get_lang('servers', 'inserted'), 'success');
}
@ -390,6 +432,13 @@ class ServerController extends AbstractServerController {
'label_domain' => psm_get_lang('servers', 'domain'),
'label_timeout' => psm_get_lang('servers', 'timeout'),
'label_timeout_description' => psm_get_lang('servers', 'timeout_description'),
'label_authentication_settings' => psm_get_lang('servers', 'authentication_settings'),
'label_website_username' => psm_get_lang('servers', 'website_username'),
'label_website_username_description' => psm_get_lang('servers', 'website_username_description'),
'label_website_password' => psm_get_lang('servers', 'website_password'),
'label_website_password_description' => psm_get_lang('servers', 'website_password_description'),
'label_fieldset_monitoring' => psm_get_lang('servers', 'fieldset_monitoring'),
'label_fieldset_permissions' => psm_get_lang('servers', 'fieldset_permissions'),
'label_port' => psm_get_lang('servers', 'port'),
'label_custom_port' => psm_get_lang('servers', 'custom_port'),
'label_please_select' => psm_get_lang('servers', 'please_select'),

View File

@ -146,6 +146,7 @@ class Installer {
('sms_from', '1234567890'),
('pushover_status', '0'),
('pushover_api_token', ''),
('password_encrypt_key', '" . sha1(microtime()) . "'),
('alert_type', 'status'),
('log_status', '1'),
('log_email', '1'),
@ -227,6 +228,8 @@ class Installer {
`warning_threshold` mediumint(1) unsigned NOT NULL DEFAULT '1',
`warning_threshold_counter` mediumint(1) unsigned NOT NULL DEFAULT '0',
`timeout` smallint(1) unsigned NULL DEFAULT NULL,
`website_username` varchar(255) DEFAULT NULL,
`website_password` varchar(255) DEFAULT NULL,
PRIMARY KEY (`server_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;",
PSM_DB_PREFIX . 'servers_uptime' => "CREATE TABLE IF NOT EXISTS `" . PSM_DB_PREFIX . "servers_uptime` (
@ -398,6 +401,9 @@ class Installer {
$this->execSQL("ALTER TABLE `".PSM_DB_PREFIX."users` DROP `server_id`;");
}
/**
* Upgrade for v3.1.0 release
*/
protected function upgrade310() {
$queries = array();
psm_update_conf('log_retention_period', '365');
@ -423,10 +429,15 @@ class Installer {
$this->execSQL($queries);
}
/**
* Upgrade for v3.2.0 release
*/
protected function upgrade320() {
$queries = array();
psm_update_conf('password_encrypt_key', sha1(microtime()));
$queries[] = "ALTER TABLE `" . PSM_DB_PREFIX . "servers` CHANGE `ip` `ip` VARCHAR(500) NOT NULL;";
$queries[] = "ALTER TABLE `" . PSM_DB_PREFIX . "servers` ADD `website_username` varchar(255) NULL, ADD `website_password` varchar(255) NULL AFTER `website_username`;";
$this->execSQL($queries);
}

View File

@ -82,7 +82,8 @@ class StatusUpdater {
$this->server = $this->db->selectRow(PSM_DB_PREFIX . 'servers', array(
'server_id' => $server_id,
), array(
'server_id', 'ip', 'port', 'label', 'type', 'pattern', 'status', 'active', 'warning_threshold', 'warning_threshold_counter', 'timeout',
'server_id', 'ip', 'port', 'label', 'type', 'pattern', 'status', 'active', 'warning_threshold',
'warning_threshold_counter', 'timeout', 'website_username', 'website_password'
));
if(empty($this->server)) {
return false;
@ -176,7 +177,10 @@ class StatusUpdater {
$this->server['ip'],
true,
($this->server['pattern'] == '' ? false : true),
$this->server['timeout']
$this->server['timeout'],
true,
$this->server['website_username'],
psm_password_decrypt($this->server['server_id'] . psm_get_conf('password_encrypt_key'), $this->server['website_password'])
);
$this->rtime = (microtime(true) - $starttime);

View File

@ -31,7 +31,17 @@
<input type="text" class="input-mini" id="auto_refresh_servers" name="auto_refresh_servers" value="{{ auto_refresh_servers }}" maxlength="10" data-toggle="tooltip" title="{{ label_auto_refresh_servers }}" />&nbsp;{{ label_seconds }}
</div>
</div>
</fieldset>
<div class="control-group">
<label class="control-label" for="password_encrypt_key">
{{ label_password_encrypt_key }}
</label>
<div class="controls">
<input type="text" class="input-xxlarge" id="password_encrypt_key" name="password_encrypt_key"
value="{{ password_encrypt_key }}" maxlength="40" data-toggle="tooltip"
title="{{ label_password_encrypt_key_note }}"/>
</div>
</div>
</fieldset>
<fieldset>
<legend>{{ label_settings_notification }}</legend>
<div class="control-group">

View File

@ -19,12 +19,13 @@
<label class="control-label" for="type">{{ label_type }}</label>
<div class="controls">
<select id="type" name="type">
<option value="">{{ label_please_select }}</option>
<option value="service" {{ edit_type_selected_service|raw }}>{{ label_service }}</option>
<option value="website" {{ edit_type_selected_website|raw }}>{{ label_website }}</option>
</select>
</div>
</div>
<div class="control-group popularPortsGroup">
<div class="control-group popularPortsGroup types typeService">
<label class="control-label" for="popularPorts">{{ label_port }}</label>
<div class="controls">
<select id="popularPorts" name="popularPorts">
@ -50,30 +51,50 @@
</select>
</div>
</div>
<div class="control-group portGroup">
<div class="control-group portGroup types typeService">
<label class="control-label" for="port">{{ label_custom_port }}</label>
<div class="controls">
<input class="input-mini" type="text" id="port" name="port" value="{{ edit_value_port }}" maxlength="5" />
</div>
</div>
<div class="control-group">
<div class="control-group types typeWebsite">
<label class="control-label" for="pattern">{{ label_pattern }}</label>
<div class="controls">
<input type="text" id="pattern" name="pattern" value="{{ edit_value_pattern }}" maxlength="255" data-toggle="tooltip" title="{{ label_pattern_description }}" />
</div>
</div>
<div class="control-group">
<div class="control-group types typeWebsite">
<label class="control-label" for="warning_threshold">{{ label_warning_threshold }}</label>
<div class="controls">
<input class="input-mini" type="text" id="warning_threshold" name="warning_threshold" value="{{ edit_value_warning_threshold }}" maxlength="5" data-toggle="tooltip" title="{{ label_warning_threshold_description }}" />
</div>
</div>
<div class="control-group">
<div class="control-group types typeWebsite">
<label class="control-label" for="timeout">{{ label_timeout }}</label>
<div class="controls">
<input class="input-mini" type="text" id="timeout" name="timeout" value="{{ edit_value_timeout }}" placeholder="{{ default_value_timeout }}" maxlength="10" data-toggle="tooltip" title="{{ label_timeout_description }}" /> s
</div>
</div>
</fieldset>
<fieldset>
<legend class="types typeWebsite">{{ label_authentication_settings}}</legend>
<div class="control-group types typeWebsite">
<label class="control-label" for="website_username">{{ label_website_username }}</label>
<div class="controls">
<input type="text" id="website_username" name="website_username" value="{{ edit_website_username }}" data-toggle="tooltip" title="{{ label_website_username_description }}" />
</div>
</div>
<div class="control-group types typeWebsite">
<label class="control-label" for="website_password">{{ label_website_password }}</label>
<div class="controls">
<input type="password" id="website_password" name="website_password" value="{{ edit_website_password }}" data-toggle="tooltip" title="{{ label_website_password_description }}" />
</div>
</div>
</fieldset>
<fieldset>
<legend>{{ label_fieldset_monitoring }}</legend>
<div class="control-group">
<label class="control-label" for="active">{{ label_monitoring }}</label>
<div class="controls">
@ -116,6 +137,10 @@
</select>
</div>
</div>
</fieldset>
<fieldset>
<legend>{{ label_fieldset_permissions }}</legend>
<div class="control-group">
<label class="control-label" for="user_id">{{ label_users }}</label>
<div class="controls">

View File

@ -73,6 +73,18 @@
<td>{{ label_timeout }}:</td>
<td>{{ timeout }} s</td>
</tr>
<tr>
<td>{{ label_website_username }}:</td>
<td>{{ website_username }}</td>
</tr>
<tr>
<td>{{ label_website_password }}:</td>
<td>
{% if (website_password is not empty) %}
******
{% endif %}
</td>
</tr>
{% if has_admin_actions %}
<tr>
<td class="hidden-small">&nbsp;</td>

View File

@ -285,6 +285,17 @@ legend {
}
/* Status page */
h2 {
font-size: 16px;
line-height: 12px;
}
h2 small {
font-size: 18px;
}
.offline, .online {
display: inline-block;
width: 100%;

View File

@ -56,7 +56,6 @@ $().ready(function() {
if (portInput != '') {
var findPopularPorts = $('#popularPorts').find('option[value=' + portInput + ']');
if(findPopularPorts.length) {
$(findPopularPorts).attr("selected", "selected");
} else {
@ -66,16 +65,54 @@ $().ready(function() {
}
$('#popularPorts').change(function () {
var popularPorts = $(this).val();
changePopularPorts($(this).val(), false, $('#type').val());
});
// server type
$('.types').hide();
changeTypeSwitch($('#type').val());
$('#type').change(function () {
changeTypeSwitch($('#type').val());
changePopularPorts($('#popularPorts').val(), true, $('#type').val());
});
});
function changeTypeSwitch(typeInput) {
switch (typeInput) {
case 'service':
$('.types').slideUp();
$('.typeService').slideDown();
break;
case 'website':
$('.types').slideUp();
$('.typeWebsite').slideDown();
break;
default:
$('.types').hide();
}
}
function changePopularPorts(popularPorts, changeType, typeInput) {
if (changeType === true) {
if (typeInput == 'service') {
if (popularPorts == 'custom') {
$('.portGroup').slideDown();
} else {
$('.portGroup').hide();
}
}
} else {
if (popularPorts == 'custom') {
$('.portGroup').slideDown();
} else {
$('#port').val(popularPorts);
$('.portGroup').slideUp();
}
});
});
}
}
function psm_xhr(mod, params, method, on_complete, options) {
method = (typeof method == 'undefined') ? 'GET' : method;