'.$timezone;
-if (!is_metaconsole()) {
+if (is_metaconsole() === false) {
echo '

diff --git a/pandora_console/include/api.php b/pandora_console/include/api.php
index cb3dae8e72..c0ee23e172 100644
--- a/pandora_console/include/api.php
+++ b/pandora_console/include/api.php
@@ -14,7 +14,7 @@
* |___| |___._|__|__|_____||_____|__| |___._| |___| |__|_|__|_______|
*
* ============================================================================
- * Copyright (c) 2005-2021 Artica Soluciones Tecnologicas
+ * Copyright (c) 2005-2022 Artica Soluciones Tecnologicas
* Please see http://pandorafms.org for full contribution list
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -69,9 +69,6 @@ $id2 = get_parameter('id2');
$otherSerialize = get_parameter('other');
$otherMode = get_parameter('other_mode', 'url_encode');
$returnType = get_parameter('return_type', 'string');
-$api_password = get_parameter('apipass', '');
-$password = get_parameter('pass', '');
-$user = get_parameter('user', '');
$info = get_parameter('info', '');
$raw_decode = (bool) get_parameter('raw_decode', false);
@@ -84,6 +81,21 @@ $apiPassword = io_output_password(
)
);
+$apiTokenValid = false;
+// Try getting bearer token from header.
+// TODO. Getting token from url will be removed.
+$apiToken = (string) getBearerToken();
+if (empty($apiToken) === true) {
+ // Legacy user/pass token.
+ // TODO. Revome in future.
+ $api_password = get_parameter('apipass', '');
+ $user = get_parameter('user', '');
+ $password = get_parameter('pass', '');
+} else {
+ $apiTokenValid = (bool) api_token_check($apiToken);
+}
+
+
$correctLogin = false;
$no_login_msg = '';
@@ -94,8 +106,8 @@ ob_clean();
// Special call without checks to retrieve version and build of the Pandora FMS
// This info is avalable from the web console without login
// Don't change the format, it is parsed by applications.
-if ($info == 'version') {
- if (!$config['MR']) {
+if ($info === 'version') {
+ if ((bool) $config['MR'] === false) {
$config['MR'] = 0;
}
@@ -105,6 +117,7 @@ if ($info == 'version') {
if (empty($apiPassword) === true
|| (empty($apiPassword) === false && $api_password === $apiPassword)
+ || $apiTokenValid === true
) {
if (enterprise_hook('metaconsole_validate_origin', [get_parameter('server_auth')]) === true
|| enterprise_hook('console_validate_origin', [get_parameter('server_auth')]) === true
@@ -118,7 +131,14 @@ if (empty($apiPassword) === true
$correctLogin = true;
} else if ((bool) isInACL($ipOrigin) === true) {
// External access.
- $user_in_db = process_user_login($user, $password, true);
+ // Token is valid. Bypass the credentials.
+ if ($apiTokenValid === true) {
+ $credentials = db_get_row('tusuario', 'api_token', $apiToken);
+ $user = $credentials['id_user'];
+ $password = $credentials['password'];
+ }
+
+ $user_in_db = process_user_login($user, $password, true, $apiTokenValid);
if ($user_in_db !== false) {
$config['id_usuario'] = $user_in_db;
// Compat.
@@ -144,19 +164,19 @@ if (empty($apiPassword) === true
$no_login_msg = 'Incorrect given API password';
}
-if ($correctLogin) {
+if ($correctLogin === true) {
if (($op !== 'get') && ($op !== 'set') && ($op !== 'help')) {
returnError('no_set_no_get_no_help', $returnType);
} else {
$function_name = '';
// Check if is an extension function and get the function name.
- if ($op2 == 'extension') {
+ if ($op2 === 'extension') {
$extension_api_url = $config['homedir'].'/'.EXTENSIONS_DIR.'/'.$ext_name.'/'.$ext_name.'.api.php';
// The extension API file must exist and the extension must be
// enabled.
- if (file_exists($extension_api_url)
- && !in_array($ext_name, extensions_get_disabled_extensions())
+ if (file_exists($extension_api_url) === true
+ && in_array($ext_name, extensions_get_disabled_extensions()) === false
) {
include_once $extension_api_url;
$function_name = 'apiextension_'.$op.'_'.$ext_function;
@@ -164,7 +184,7 @@ if ($correctLogin) {
} else {
$function_name = 'api_'.$op.'_'.$op2;
- if ($op == 'set' && $id) {
+ if ($op === 'set' && $id) {
switch ($op2) {
case 'update_agent':
case 'add_module_in_conf':
@@ -173,7 +193,7 @@ if ($correctLogin) {
$agent = agents_locate_agent($id);
if ($agent !== false) {
$id_os = $agent['id_os'];
- if ($id_os == 100) {
+ if ((int) $id_os === 100) {
returnError(
'not_allowed_operation_cluster',
$returnType
diff --git a/pandora_console/include/auth/mysql.php b/pandora_console/include/auth/mysql.php
index a2c1ded77d..be845f3fc0 100644
--- a/pandora_console/include/auth/mysql.php
+++ b/pandora_console/include/auth/mysql.php
@@ -94,7 +94,7 @@ $config['admin_can_make_admin'] = true;
* @return mixed False in case of error or invalid credentials, the username in
* case it's correct.
*/
-function process_user_login($login, $pass, $api=false)
+function process_user_login($login, $pass, $api=false, $passAlreadyEncrypted=false)
{
global $config;
@@ -130,10 +130,10 @@ function process_user_login($login, $pass, $api=false)
if ($config['fallback_local_auth']
|| is_user_admin($login)
|| $local_user === true
- || strtolower($config['auth']) == 'mysql'
+ || strtolower($config['auth']) === 'mysql'
|| (bool) $user_not_login === true
) {
- return process_user_login_local($login, $pass, $api);
+ return process_user_login_local($login, $pass, $api, $passAlreadyEncrypted);
} else {
return false;
}
@@ -144,12 +144,11 @@ function process_user_login($login, $pass, $api=false)
}
-function process_user_login_local($login, $pass, $api=false)
+function process_user_login_local($login, $pass, $api=false, $passAlreadyEncrypted=false)
{
global $config, $mysql_cache;
- // Connect to Database.
- if (!$api) {
+ if ((bool) $api === false) {
$sql = sprintf(
"SELECT `id_user`, `password`
FROM `tusuario`
@@ -169,13 +168,17 @@ function process_user_login_local($login, $pass, $api=false)
$row = db_get_row_sql($sql);
- // Perform password check whether it is MD5-hashed (old hashing) or Bcrypt-hashed.
- if (strlen($row['password']) === 32) {
- // MD5.
- $credentials_check = $row !== false && $row['password'] !== md5('') && $row['password'] == md5($pass);
+ if ($passAlreadyEncrypted) {
+ $credentials_check = $pass === $row['password'];
} else {
- // Bcrypt.
- $credentials_check = password_verify($pass, $row['password']);
+ // Perform password check whether it is MD5-hashed (old hashing) or Bcrypt-hashed.
+ if (strlen($row['password']) === 32) {
+ // MD5.
+ $credentials_check = $row !== false && $row['password'] !== md5('') && $row['password'] == md5($pass);
+ } else {
+ // Bcrypt.
+ $credentials_check = password_verify($pass, $row['password']);
+ }
}
if ($credentials_check === true) {
@@ -184,10 +187,10 @@ function process_user_login_local($login, $pass, $api=false)
// is not case sensitive)
// We get DB nick to put in PHP Session variable,
// to avoid problems with case-sensitive usernames.
- // Thanks to David Muñiz for Bug discovery :)
+ // Thanks to David Muñiz for Bug discovery :).
$filter = ['id_usuario' => $login];
$user_profile = db_get_row_filter('tusuario_perfil', $filter);
- if (!users_is_admin($login) && !$user_profile) {
+ if ((bool) users_is_admin($login) === false && (bool) $user_profile === false) {
$mysql_cache['auth_error'] = 'User does not have any profile';
$config['auth_error'] = 'User does not have any profile';
return false;
@@ -200,7 +203,7 @@ function process_user_login_local($login, $pass, $api=false)
return $row['id_user'];
} else {
- if (!user_can_login($login)) {
+ if (user_can_login($login) === false) {
$mysql_cache['auth_error'] = 'User only can use the API.';
$config['auth_error'] = 'User only can use the API.';
} else {
diff --git a/pandora_console/include/functions.php b/pandora_console/include/functions.php
index 7c2df5603b..43fef71ced 100644
--- a/pandora_console/include/functions.php
+++ b/pandora_console/include/functions.php
@@ -6326,7 +6326,7 @@ function arrayOutputSorting($sort, $sortField)
/**
* Get dowload started cookie from js and set ready cokkie for download ready comntrol.
*
- * @return
+ * @return void
*/
function setDownloadCookieToken()
{
@@ -6342,3 +6342,48 @@ function setDownloadCookieToken()
);
}
}
+
+
+/**
+ * Get header Authorization
+ * */
+function getAuthorizationHeader()
+{
+ $headers = null;
+ if (isset($_SERVER['Authorization'])) {
+ $headers = trim($_SERVER['Authorization']);
+ } else if (isset($_SERVER['HTTP_AUTHORIZATION'])) {
+ // Nginx or fast CGI
+ $headers = trim($_SERVER['HTTP_AUTHORIZATION']);
+ } else if (function_exists('apache_request_headers')) {
+ $requestHeaders = apache_request_headers();
+ // Server-side fix for bug in old Android versions (a nice side-effect of this fix means we don't care about capitalization for Authorization)
+ $requestHeaders = array_combine(array_map('ucwords', array_keys($requestHeaders)), array_values($requestHeaders));
+ // print_r($requestHeaders);
+ if (isset($requestHeaders['Authorization'])) {
+ $headers = trim($requestHeaders['Authorization']);
+ }
+ }
+
+ return $headers;
+}
+
+
+/**
+ * Get access token from header
+ *
+ * @return array/false Token received, false in case thre is no token.
+ * */
+function getBearerToken()
+{
+ $headers = getAuthorizationHeader();
+
+ // HEADER: Get the access token from the header
+ if (!empty($headers)) {
+ if (preg_match('/Bearer\s(\S+)/', $headers, $matches)) {
+ return $matches[1];
+ }
+ }
+
+ return false;
+}
diff --git a/pandora_console/include/functions_api.php b/pandora_console/include/functions_api.php
index db79ed3c86..d141160f9b 100644
--- a/pandora_console/include/functions_api.php
+++ b/pandora_console/include/functions_api.php
@@ -17648,3 +17648,20 @@ function api_set_send_report($thrash1, $thrash2, $other, $returnType)
returnData($returnType, $data, ';');
}
}
+
+
+/**
+ * Check if token is correct.
+ *
+ * @param string $token Token for check.
+ *
+ * @return mixed Id of user. If returns 0 there is not valid token.
+ */
+function api_token_check(string $token)
+{
+ if (empty($token) === true) {
+ return 0;
+ } else {
+ return db_get_value('id_user', 'tusuario', 'api_token', $token);
+ }
+}
diff --git a/pandora_console/include/functions_ui.php b/pandora_console/include/functions_ui.php
index 52ad703edd..798c8cf72a 100755
--- a/pandora_console/include/functions_ui.php
+++ b/pandora_console/include/functions_ui.php
@@ -2370,13 +2370,17 @@ function ui_print_help_tip(
$return=false,
$img='images/tip_help.png',
$is_relative=false,
- $style=''
+ $style='',
+ $blink=false
) {
$output = '
';
$output .= html_print_image(
$img,
true,
- ['title' => $text],
+ [
+ 'title' => $text,
+ 'class' => $blink === true ? 'blink' : '',
+ ],
false,
$is_relative && is_metaconsole()
).'';
diff --git a/pandora_console/include/functions_users.php b/pandora_console/include/functions_users.php
index 17dc7911ee..ad3e6ac3b2 100755
--- a/pandora_console/include/functions_users.php
+++ b/pandora_console/include/functions_users.php
@@ -884,6 +884,69 @@ function users_get_users_group_by_group($id_group)
}
+/**
+ * Generates a cryptographically secure chain for use with API.
+ *
+ * @return string
+ */
+function api_token_generate()
+{
+ include_once 'functions_api.php';
+ // Generate a cryptographically secure chain.
+ $generateToken = bin2hex(openssl_random_pseudo_bytes(16));
+ // Check if token exists in DB.
+ $tokenExists = (bool) api_token_check($generateToken);
+ // If not exists, can be assigned. In other case, try again.
+ return ($tokenExists === false) ? $generateToken : api_token_generate();
+}
+
+
+/**
+ * Returns User API Token
+ *
+ * @param string $idUser Id of the user.
+ *
+ * @return string
+ */
+function users_get_API_token(string $idUser)
+{
+ $output = db_get_value('api_token', 'tusuario', 'id_user', $idUser);
+
+ if (empty($output) === true) {
+ $output = '<< '.__('NONE').' >>';
+ }
+
+ return $output;
+}
+
+
+/**
+ * Renews the API Token.
+ *
+ * @param integer $idUser Id of the user.
+ *
+ * @return boolean Return true if the token was renewed.
+ */
+function users_renew_API_token(int $idUser)
+{
+ $apiToken = api_token_generate();
+
+ if (empty($apiToken) === false) {
+ $result = db_process_sql_update(
+ 'tusuario',
+ ['api_token' => $apiToken],
+ ['id_user' => $idUser]
+ );
+
+ if ($result !== false) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
/**
* Check if IP is in range. Check wildcard `*`, single IP and IP ranges.
*
diff --git a/pandora_console/include/javascript/pandora.js b/pandora_console/include/javascript/pandora.js
index 03a2ec8fe3..4a427caeff 100644
--- a/pandora_console/include/javascript/pandora.js
+++ b/pandora_console/include/javascript/pandora.js
@@ -2185,6 +2185,38 @@ $.fn.filterByText = function(textbox) {
});
};
+/**
+ * Confirm Dialog for API token renewal request.
+ *
+ * @param {string} title Title for show.
+ * @param {string} message Message for show.
+ * @param {string} form Form to attach renewAPIToken element.
+ */
+function renewAPIToken(title, message, form) {
+ confirmDialog({
+ title: title,
+ message: message,
+ onAccept: function() {
+ $("#" + form)
+ .append("
")
+ .submit();
+ }
+ });
+}
+
+/**
+ * Show Dialog for view the API token.
+ *
+ * @param {string} title Title for show.
+ * @param {string} message Base64 encoded message for show.
+ */
+function showAPIToken(title, message) {
+ confirmDialog({
+ title: title,
+ message: atob(message),
+ hideCancelButton: true
+ });
+}
function loadPasswordConfig(id, value) {
$.ajax({
url: "ajax.php",
diff --git a/pandora_console/include/lib/ClusterViewer/ClusterManager.php b/pandora_console/include/lib/ClusterViewer/ClusterManager.php
index 884be3f2a7..c7e0385fa6 100644
--- a/pandora_console/include/lib/ClusterViewer/ClusterManager.php
+++ b/pandora_console/include/lib/ClusterViewer/ClusterManager.php
@@ -105,7 +105,6 @@ class ClusterManager
*/
public function run()
{
-
$operation = get_parameter('op', '');
switch ($operation) {
@@ -329,10 +328,8 @@ class ClusterManager
*/
public function showClusterEditor(string $operation)
{
-
global $config;
if (!check_acl($config['id_user'], 0, 'AW')) {
-
db_pandora_audit(
AUDIT_LOG_ACL_VIOLATION,
'Trying to create clusters'
diff --git a/pandora_console/include/lib/Dashboard/Manager.php b/pandora_console/include/lib/Dashboard/Manager.php
index f22aa7a4f0..fed596e08e 100644
--- a/pandora_console/include/lib/Dashboard/Manager.php
+++ b/pandora_console/include/lib/Dashboard/Manager.php
@@ -818,9 +818,9 @@ class Manager implements PublicLogin
$string_groups = io_safe_output($string_groups);
$sql_dashboard = sprintf(
- "SELECT COUNT(*)
+ 'SELECT COUNT(*)
FROM tdashboard
- WHERE (id_group IN (%s))",
+ WHERE (id_group IN (%s))',
$string_groups
);
} else {
diff --git a/pandora_console/include/rest-api/models/VisualConsole/Container.php b/pandora_console/include/rest-api/models/VisualConsole/Container.php
index b4bc3ae2ed..d8042c8054 100644
--- a/pandora_console/include/rest-api/models/VisualConsole/Container.php
+++ b/pandora_console/include/rest-api/models/VisualConsole/Container.php
@@ -553,4 +553,6 @@ final class Container extends Model
return $item;
}
+
+
}
diff --git a/pandora_console/include/rest-api/models/VisualConsole/Item.php b/pandora_console/include/rest-api/models/VisualConsole/Item.php
index b6fb740c3c..afdb679b97 100644
--- a/pandora_console/include/rest-api/models/VisualConsole/Item.php
+++ b/pandora_console/include/rest-api/models/VisualConsole/Item.php
@@ -2724,4 +2724,6 @@ class Item extends CachedModel
return false;
}
+
+
}
diff --git a/pandora_console/include/styles/pandora.css b/pandora_console/include/styles/pandora.css
index f67f840744..b5ade74fc0 100644
--- a/pandora_console/include/styles/pandora.css
+++ b/pandora_console/include/styles/pandora.css
@@ -4678,6 +4678,21 @@ input:checked + .p-slider:before {
animation: fadein 0.5s, fadeout 0.5s 7.5s;
}
+.blink {
+ animation: blink-animation 1s steps(5, start) infinite;
+ -webkit-animation: blink-animation 1s steps(5, start) infinite;
+}
+@keyframes blink-animation {
+ to {
+ visibility: hidden;
+ }
+}
+@-webkit-keyframes blink-animation {
+ to {
+ visibility: hidden;
+ }
+}
+
.snackbar p,
.snackbar h3 {
text-align: left;
@@ -9065,6 +9080,14 @@ div#err_msg_centralised {
width: 75%;
}
+.renew_api_token_link {
+ margin: 3px 0.5em 0 0;
+ float: right;
+}
+
+.renew_api_token_image {
+ width: 16px;
+}
@media screen and (max-width: 1369px) {
.div-col {
width: 50%;
diff --git a/pandora_console/operation/cluster/cluster.php b/pandora_console/operation/cluster/cluster.php
index 2cc078ef4b..e9a2ec98c5 100755
--- a/pandora_console/operation/cluster/cluster.php
+++ b/pandora_console/operation/cluster/cluster.php
@@ -37,7 +37,6 @@ $ajaxPage = 'operation/cluster/cluster';
// Control call flow.
try {
-
// User access and validation is being processed on class constructor.
$obj = new ClusterManager($ajaxPage);
} catch (Exception $e) {
diff --git a/pandora_console/operation/users/user_edit.php b/pandora_console/operation/users/user_edit.php
index c1f2e1fee2..f77a5dfa49 100644
--- a/pandora_console/operation/users/user_edit.php
+++ b/pandora_console/operation/users/user_edit.php
@@ -83,9 +83,12 @@ if (isset($_GET['modified']) && !$view_mode) {
$upd_info['id_skin'] = get_parameter('skin', $user_info['id_skin']);
$upd_info['default_event_filter'] = get_parameter('event_filter', null);
$upd_info['block_size'] = get_parameter('block_size', $config['block_size']);
+ // API Token information.
+ $apiTokenRenewed = (bool) get_parameter('renewAPIToken');
+ $upd_info['api_token'] = ($apiTokenRenewed === true) ? api_token_generate() : users_get_API_token($config['id_user']);
$default_block_size = get_parameter('default_block_size', 0);
- if ($default_block_size) {
+ if ($default_block_size > 0) {
$upd_info['block_size'] = 0;
}
@@ -161,16 +164,16 @@ if (isset($_GET['modified']) && !$view_mode) {
} else if ($password_new !== 'NON-INIT') {
$error_msg = __('Passwords didn\'t match or other problem encountered while updating passwords');
}
- } else if (empty($password_new) && empty($password_confirm)) {
+ } else if (empty($password_new) === true && empty($password_confirm) === true) {
$return = true;
- } else if (empty($password_new) || empty($password_confirm)) {
+ } else if (empty($password_new) === true || empty($password_confirm) === true) {
$return = false;
}
// No need to display "error" here, because when no update is needed
// (no changes in data) SQL function returns 0 (FALSE), but is not an error,
// just no change. Previous error message could be confussing to the user.
- if ($return) {
+ if ($return !== false) {
if (empty($password_new) === false && empty($password_confirm) === false) {
$success_msg = __('Password successfully updated');
}
@@ -184,7 +187,11 @@ if (isset($_GET['modified']) && !$view_mode) {
if ($return_update_user === false) {
$error_msg = __('Error updating user info');
} else if ($return_update_user == true) {
- $success_msg = __('User info successfully updated');
+ if ($apiTokenRenewed === true) {
+ $success_msg = __('You have generated a new API Token.');
+ } else {
+ $success_msg = __('User info successfully updated');
+ }
} else {
if (empty($password_new) === false && empty($password_confirm) === false) {
$success_msg = __('Password successfully updated');
@@ -226,7 +233,7 @@ if (isset($_GET['modified']) && !$view_mode) {
}
// Prints action status for current message.
-if ($status != -1) {
+if ((int) $status !== -1) {
ui_print_result_message(
$status,
__('User info successfully updated'),
@@ -261,6 +268,73 @@ if (is_metaconsole() === false && is_management_allowed() === false) {
$user_id = '
'.__('User ID').':
';
$user_id .= '
'.$id.'';
+$user_id .= '
'.__('API Token').'
';
+if (is_management_allowed()) {
+ $user_id .= html_print_anchor(
+ [
+ 'onClick' => sprintf(
+ 'javascript:renewAPIToken(\'%s\', \'%s\', \'%s\')',
+ __('Warning'),
+ __('The API token will be renewed. After this action, the last token you were using will not work. Are you sure?'),
+ 'user_profile_form',
+ ),
+ 'content' => html_print_image(
+ 'images/icono-refrescar.png',
+ true,
+ [
+ 'class' => 'renew_api_token_image clickable',
+ 'title' => __('Renew API Token'),
+ ]
+ ),
+ 'class' => 'renew_api_token_link',
+ ],
+ true
+ );
+}
+
+
+// Check php conf for header auth.
+$lines = file('/etc/httpd/conf.d/php.conf');
+$http_authorization = false;
+
+foreach ($lines as $l) {
+ if (preg_match('/SetEnvIfNoCase \^Authorization\$ \"\(\.\+\)\" HTTP_AUTHORIZATION=\$1/', $l)) {
+ $http_authorization = true;
+ }
+}
+
+$user_id .= html_print_anchor(
+ [
+ 'onClick' => sprintf(
+ 'javascript:showAPIToken(\'%s\', \'%s\')',
+ __('API Token'),
+ base64_encode(__('Your API Token is:').'
'.users_get_API_token($config['id_user']).''.__('Please, avoid share this string with others.')),
+ ),
+ 'content' => html_print_image(
+ 'images/eye_show.png',
+ true,
+ [
+ 'class' => 'renew_api_token_image clickable',
+ 'title' => __('Show API Token'),
+ ]
+ ),
+ 'class' => 'renew_api_token_link',
+ ],
+ true
+);
+
+if ($http_authorization === false) {
+ $user_id .= ui_print_help_tip(
+ __('Directive HTTP_AUTHORIZATION=$1 is not set. Please, add it to /etc/httpd/conf.d/php.conf'),
+ true,
+ 'images/warn.png',
+ false,
+ '',
+ true
+ );
+}
+
+$user_id .= '
';
$full_name = '
'.html_print_input_text_extended(
'fullname',
$user_info['fullname'],
@@ -278,7 +352,7 @@ $full_name = '
'.html_print_input_text_extended
).'
';
// Show "Picture" (in future versions, why not, allow users to upload it's own avatar here.
-if (is_user_admin($id)) {
+if (is_user_admin($id) === true) {
$avatar = html_print_image('images/people_1.png', true, ['class' => 'user_avatar']);
} else {
$avatar = html_print_image('images/people_2.png', true, ['class' => 'user_avatar']);
@@ -654,10 +728,10 @@ foreach ($timezones as $timezone_name => $tz) {
}
}
-if (is_metaconsole()) {
- echo '