From 7d526140642e46bb3849f4480d2db8b8b3adce08 Mon Sep 17 00:00:00 2001 From: fbsanchez Date: Tue, 24 Sep 2019 11:25:53 +0200 Subject: [PATCH] Ent 4615 implementar cifrado en los contenedores de credenciales usando el sistema de cifrado interno de pandora --- .../godmode/groups/credential_store.php | 604 +-------- .../include/class/CredentialStore.class.php | 1203 +++++++++++++++++ .../include/functions_credential_store.php | 420 +----- pandora_console/include/functions_html.php | 8 + pandora_console/include/functions_io.php | 29 +- pandora_console/include/javascript/pandora.js | 20 +- .../include/styles/credential_store.css | 30 + pandora_server/lib/PandoraFMS/Config.pm | 2 +- pandora_server/lib/PandoraFMS/Core.pm | 13 +- pandora_server/lib/PandoraFMS/Tools.pm | 2 +- 10 files changed, 1321 insertions(+), 1010 deletions(-) create mode 100644 pandora_console/include/class/CredentialStore.class.php diff --git a/pandora_console/godmode/groups/credential_store.php b/pandora_console/godmode/groups/credential_store.php index 3273e1c038..46d1a929b7 100644 --- a/pandora_console/godmode/groups/credential_store.php +++ b/pandora_console/godmode/groups/credential_store.php @@ -29,599 +29,43 @@ // Begin. global $config; -// Check access. -check_login(); +require_once $config['homedir'].'/include/class/CredentialStore.class.php'; -if (! check_acl($config['id_user'], 0, 'PM')) { - db_pandora_audit( - 'ACL Violation', - 'Trying to access event viewer' - ); +$ajaxPage = 'godmode/groups/credential_store'; +// Control call flow. +try { + // User access and validation is being processed on class constructor. + $cs = new CredentialStore($ajaxPage); +} catch (Exception $e) { if (is_ajax()) { - return ['error' => 'noaccess']; + echo json_encode(['error' => '[CredentialStore]'.$e->getMessage() ]); + exit; + } else { + echo '[CredentialStore]'.$e->getMessage(); } - include 'general/noaccess.php'; + // Stop this execution, but continue 'globally'. return; } -// Required files. -ui_require_css_file('credential_store'); -require_once $config['homedir'].'/include/functions_credential_store.php'; -require_once $config['homedir'].'/include/functions_io.php'; - +// AJAX controller. if (is_ajax()) { - $draw = get_parameter('draw', 0); - $filter = get_parameter('filter', []); - $get_key = get_parameter('get_key', 0); - $new_form = get_parameter('new_form', 0); - $new_key = get_parameter('new_key', 0); - $update_key = get_parameter('update_key', 0); - $delete_key = get_parameter('delete_key', 0); + $method = get_parameter('method'); - if ($new_form) { - echo print_inputs(); - exit; - } - - if ($delete_key) { - $identifier = get_parameter('identifier', null); - - if (empty($identifier)) { - ajax_msg('error', __('identifier cannot be empty')); - } - - if (db_process_sql_delete( - 'tcredential_store', - ['identifier' => $identifier] - ) === false - ) { - ajax_msg('error', $config['dbconnection']->error, true); + if (method_exists($cs, $method) === true) { + if ($cs->ajaxMethod($method) === true) { + $cs->{$method}(); } else { - ajax_msg('result', $identifier, true); + $cs->error('Unavailable method.'); } + } else { + $cs->error('Method not found. ['.$method.']'); } - if ($update_key) { - $data = get_parameter('values', null); - - if ($data === null || !is_array($data)) { - echo json_encode(['error' => __('Invalid parameters, please retry')]); - exit; - } - - $values = []; - foreach ($data as $key => $value) { - if ($key == 'identifier') { - $identifier = base64_decode($value); - } else if ($key == 'product') { - $product = base64_decode($value); - } else { - $values[$key] = base64_decode($value); - } - } - - if (empty($identifier)) { - ajax_msg('error', __('identifier cannot be empty')); - } - - if (empty($product)) { - ajax_msg('error', __('product cannot be empty')); - } - - if (db_process_sql_update( - 'tcredential_store', - $values, - ['identifier' => $identifier] - ) === false - ) { - ajax_msg('error', $config['dbconnection']->error); - } else { - ajax_msg('result', $identifier); - } - - exit; - } - - if ($new_key) { - $data = get_parameter('values', null); - - if ($data === null || !is_array($data)) { - echo json_encode(['error' => __('Invalid parameters, please retry')]); - exit; - } - - $values = []; - foreach ($data as $key => $value) { - $values[$key] = base64_decode($value); - if ($key == 'identifier') { - $values[$key] = preg_replace('/\s+/', '-', trim($values[$key])); - } - } - - $identifier = $values['identifier']; - - if (empty($identifier)) { - ajax_msg('error', __('identifier cannot be empty')); - } - - if (empty($values['product'])) { - ajax_msg('error', __('product cannot be empty')); - } - - if (db_process_sql_insert('tcredential_store', $values) === false) { - ajax_msg('error', $config['dbconnection']->error); - } else { - ajax_msg('result', $identifier); - } - - exit; - } - - if ($get_key) { - $identifier = get_parameter('identifier', null); - - $key = get_key($identifier); - echo print_inputs($key); - - exit; - } - - if ($draw) { - // Datatables offset, limit and order. - $start = get_parameter('start', 0); - $length = get_parameter('length', $config['block_size']); - $order = get_datatable_order(true); - try { - ob_start(); - - $fields = [ - 'cs.*', - 'tg.nombre as `group`', - ]; - - // Retrieve data. - $data = credentials_get_all( - // Fields. - $fields, - // Filter. - $filter, - // Offset. - $start, - // Limit. - $length, - // Order. - $order['direction'], - // Sort field. - $order['field'] - ); - - // Retrieve counter. - $count = credentials_get_all( - 'count', - $filter - ); - - if ($data) { - $data = array_reduce( - $data, - function ($carry, $item) { - // Transforms array of arrays $data into an array - // of objects, making a post-process of certain fields. - $tmp = (object) $item; - $tmp->username = io_safe_output($tmp->username); - - if (empty($tmp->group)) { - $tmp->group = __('All'); - } else { - $tmp->group = io_safe_output($tmp->group); - } - - $carry[] = $tmp; - return $carry; - } - ); - } - - // Datatables format: RecordsTotal && recordsfiltered. - echo json_encode( - [ - 'data' => $data, - 'recordsTotal' => $count, - 'recordsFiltered' => $count, - ] - ); - // Capture output. - $response = ob_get_clean(); - } catch (Exception $e) { - return json_encode(['error' => $e->getMessage()]); - } - - // If not valid, show error with issue. - json_decode($response); - if (json_last_error() == JSON_ERROR_NONE) { - // If valid dump. - echo $response; - } else { - echo json_encode( - ['error' => $response] - ); - } - - - exit; - } - + // Stop any execution. exit; +} else { + // Run. + $cs->run(); } - -// Datatables list. -try { - $columns = [ - 'group', - 'identifier', - 'product', - 'username', - 'options', - ]; - - $column_names = [ - __('Group'), - __('Identifier'), - __('Product'), - __('User'), - [ - 'text' => __('Options'), - 'class' => 'action_buttons', - ], - ]; - - $table_id = 'keystore'; - // Load datatables user interface. - ui_print_datatable( - [ - 'id' => $table_id, - 'class' => 'info_table', - 'style' => 'width: 100%', - 'columns' => $columns, - 'column_names' => $column_names, - 'ajax_url' => 'godmode/groups/credential_store', - 'ajax_postprocess' => 'process_datatables_item(item)', - 'no_sortable_columns' => [-1], - 'order' => [ - 'field' => 'identifier', - 'direction' => 'asc', - ], - 'search_button_class' => 'sub filter float-right', - 'form' => [ - 'inputs' => [ - [ - 'label' => __('Group'), - 'type' => 'select', - 'id' => 'filter_id_group', - 'name' => 'filter_id_group', - 'options' => users_get_groups_for_select( - $config['id_user'], - 'AR', - true, - true, - false - ), - ], - [ - 'label' => __('Free search'), - 'type' => 'text', - 'class' => 'mw250px', - 'id' => 'free_search', - 'name' => 'free_search', - ], - ], - ], - ] - ); -} catch (Exception $e) { - echo $e->getMessage(); -} - -// Auxiliar div. -$new = ''; -$details = ''; -$aux = ''; - - -echo $new.$details.$aux; - -// Create button. -echo '
'; -html_print_submit_button( - __('Add key'), - 'create', - false, - 'class="sub next"' -); -echo '
'; - -?> - - diff --git a/pandora_console/include/class/CredentialStore.class.php b/pandora_console/include/class/CredentialStore.class.php new file mode 100644 index 0000000000..188950d92a --- /dev/null +++ b/pandora_console/include/class/CredentialStore.class.php @@ -0,0 +1,1203 @@ +AJAXMethods); + } + + + /** + * Generates a JSON error. + * + * @param string $msg Error message. + * + * @return void + */ + public function error($msg) + { + echo json_encode( + ['error' => $msg] + ); + } + + + /** + * Minor function to dump json message as ajax response. + * + * @param string $type Type: result || error. + * @param string $msg Message. + * @param boolean $delete Deletion messages. + * + * @return void + */ + private function ajaxMsg($type, $msg, $delete=false) + { + $msg_err = 'Failed while saving: %s'; + $msg_ok = 'Successfully saved into keystore '; + + if ($delete) { + $msg_err = 'Failed while removing: %s'; + $msg_ok = 'Successfully deleted '; + } + + if ($type == 'error') { + echo json_encode( + [ + $type => ui_print_error_message( + __( + $msg_err, + $msg + ), + '', + true + ), + ] + ); + } else { + echo json_encode( + [ + $type => ui_print_success_message( + __( + $msg_ok, + $msg + ), + '', + true + ), + ] + ); + } + + exit; + } + + + /** + * Initializes object and validates user access. + * + * @param string $ajax_controller Path of ajaxController, is the 'page' + * variable sent in ajax calls. + * + * @return Object + */ + public function __construct($ajax_controller) + { + global $config; + + // Check access. + check_login(); + + if (! check_acl($config['id_user'], 0, 'AR')) { + db_pandora_audit( + 'ACL Violation', + 'Trying to access event viewer' + ); + + if (is_ajax()) { + echo json_encode(['error' => 'noaccess']); + } + + include 'general/noaccess.php'; + exit; + } + + $this->ajaxController = $ajax_controller; + + return $this; + } + + + /** + * Returns an array with all the credentials matching filter and ACL. + * + * @param array $fields Fields array or 'count' keyword to retrieve count. + * @param array $filter Filters to be applied. + * @param integer $offset Offset (pagination). + * @param integer $limit Limit (pagination). + * @param string $order Sort order. + * @param string $sort_field Sort field. + * + * @return array With all results or false if error. + * @throws Exception On error. + */ + public static function getAll( + $fields, + $filter, + $offset=null, + $limit=null, + $order=null, + $sort_field=null + ) { + $sql_filters = []; + $order_by = ''; + $pagination = ''; + + global $config; + + if (!is_array($filter)) { + error_log('[credential_get_all] Filter must be an array.'); + throw new Exception('[credential_get_all] Filter must be an array.'); + } + + $count = false; + if (!is_array($fields) && $fields == 'count') { + $fields = ['cs.*']; + $count = true; + } else if (!is_array($fields)) { + error_log('[credential_get_all] Fields must be an array or "count".'); + throw new Exception('[credential_get_all] Fields must be an array or "count".'); + } + + if (isset($filter['product']) && !empty($filter['product'])) { + $sql_filters[] = sprintf(' AND cs.product = "%s"', $filter['product']); + } + + if (isset($filter['free_search']) && !empty($filter['free_search'])) { + $sql_filters[] = vsprintf( + ' AND (lower(cs.username) like lower("%%%s%%") + OR cs.identifier like "%%%s%%" + OR lower(cs.product) like lower("%%%s%%"))', + array_fill(0, 3, $filter['free_search']) + ); + } + + if (isset($filter['filter_id_group']) && $filter['filter_id_group'] > 0) { + $propagate = db_get_value( + 'propagate', + 'tgrupo', + 'id_grupo', + $filter['filter_id_group'] + ); + + if (!$propagate) { + $sql_filters[] = sprintf( + ' AND cs.id_group = %d ', + $filter['filter_id_group'] + ); + } else { + $groups = [ $filter['filter_id_group'] ]; + $childrens = groups_get_childrens($id_group, null, true); + if (!empty($childrens)) { + foreach ($childrens as $child) { + $groups[] = (int) $child['id_grupo']; + } + } + + $filter['filter_id_group'] = $groups; + $sql_filters[] = sprintf( + ' AND cs.id_group IN (%s) ', + join(',', $filter['filter_id_group']) + ); + } + } + + if (isset($filter['group_list']) && is_array($filter['group_list'])) { + $sql_filters[] = sprintf( + ' AND cs.id_group IN (%s) ', + join(',', $filter['group_list']) + ); + } else if (users_is_admin() !== true) { + $user_groups = users_get_groups( + $config['id_user'], + 'AR' + ); + + // Always add group 'ALL' because 'ALL' group credentials + // must be available for all users. + if (is_array($user_groups) === true) { + $user_groups = ([0] + array_keys($user_groups)); + } else { + $user_groups = [0]; + } + + $sql_filters[] = sprintf( + ' AND cs.id_group IN (%s) ', + join(',', $user_groups) + ); + } + + if (isset($filter['identifier'])) { + $sql_filters[] = sprintf( + ' AND cs.identifier = "%s" ', + $filter['identifier'] + ); + } + + if (isset($order)) { + $dir = 'asc'; + if ($order == 'desc') { + $dir = 'desc'; + }; + + if (in_array( + $sort_field, + [ + 'group', + 'identifier', + 'product', + 'username', + 'options', + ] + ) + ) { + $order_by = sprintf( + 'ORDER BY `%s` %s', + $sort_field, + $dir + ); + } + } + + if (isset($limit) && $limit > 0 + && isset($offset) && $offset >= 0 + ) { + $pagination = sprintf( + ' LIMIT %d OFFSET %d ', + $limit, + $offset + ); + } + + $sql = sprintf( + 'SELECT %s + FROM tcredential_store cs + LEFT JOIN tgrupo tg + ON tg.id_grupo = cs.id_group + WHERE 1=1 + %s + %s + %s', + join(',', $fields), + join(' ', $sql_filters), + $order_by, + $pagination + ); + + if ($count) { + $sql = sprintf('SELECT count(*) as n FROM ( %s ) tt', $sql); + + return db_get_value_sql($sql); + } + + return db_get_all_rows_sql($sql); + } + + + /** + * Retrieves target key from keystore or false in case of error. + * + * @param string $identifier Key identifier. + * + * @return array Key or false if error. + */ + public static function getKey($identifier) + { + global $config; + + if (empty($identifier)) { + return false; + } + + $keys = self::getAll( + [ + 'cs.*', + 'tg.nombre as `group`', + ], + ['identifier' => $identifier] + ); + + if (is_array($keys) === true) { + // Only 1 must exist. + $key = $keys[0]; + + // Decrypt content. + $key['username'] = io_output_password($key['username']); + $key['password'] = io_output_password($key['password']); + + return $key; + } + + return false; + } + + + /** + * Return all keys avaliable for current user. + * + * @param string $product Filter by product. + * + * @return array Keys or false if error. + */ + public static function getKeys($product=false) + { + global $config; + + if ($product !== false) { + $filter['product'] = $product; + } + + $keys = self::getAll( + [ + 'cs.*', + 'tg.nombre as `group`', + ], + $filter + ); + + if (is_array($keys) === true) { + // Improve usage and decode output. + $return = array_reduce( + $keys, + function ($carry, $item) { + $item['username'] = io_output_password($item['username']); + $item['password'] = io_output_password($item['password']); + $carry[$item['identifier']] = $item['identifier']; + return $carry; + } + ); + + return $return; + } + + return false; + } + + + /** + * Ajax method invoked by datatables to draw content. + * + * @return void + */ + public function draw() + { + // Datatables offset, limit and order. + $filter = get_parameter('filter', []); + $start = get_parameter('start', 0); + $length = get_parameter('length', $config['block_size']); + $order = get_datatable_order(true); + try { + ob_start(); + + $fields = [ + 'cs.*', + 'tg.nombre as `group`', + ]; + + // Retrieve data. + $data = $this->getAll( + // Fields. + $fields, + // Filter. + $filter, + // Offset. + $start, + // Limit. + $length, + // Order. + $order['direction'], + // Sort field. + $order['field'] + ); + + // Retrieve counter. + $count = $this->getAll( + 'count', + $filter + ); + + if ($data) { + $data = array_reduce( + $data, + function ($carry, $item) { + // Transforms array of arrays $data into an array + // of objects, making a post-process of certain fields. + $tmp = (object) $item; + $tmp->username = io_output_password($tmp->username); + + if (empty($tmp->group)) { + $tmp->group = __('All'); + } else { + $tmp->group = io_safe_output($tmp->group); + } + + $carry[] = $tmp; + return $carry; + } + ); + } + + // Datatables format: RecordsTotal && recordsfiltered. + echo json_encode( + [ + 'data' => $data, + 'recordsTotal' => $count, + 'recordsFiltered' => $count, + ] + ); + // Capture output. + $response = ob_get_clean(); + } catch (Exception $e) { + echo json_encode(['error' => $e->getMessage()]); + exit; + } + + // If not valid, show error with issue. + json_decode($response); + if (json_last_error() == JSON_ERROR_NONE) { + // If valid dump. + echo $response; + } else { + echo json_encode( + ['error' => $response] + ); + } + + exit; + } + + + /** + * Prints inputs for modal "Add key". + * + * @return void + */ + public function loadModal() + { + $identifier = get_parameter('identifier', null); + $key = self::getKey($identifier); + + echo $this->printInputs($key); + } + + + /** + * Prepare variables received using form. AJAX environment only. + * + * @return array of values processed or false in case of error. + */ + private function prepareKeyValues() + { + $identifier = get_parameter('identifier', null); + $id_group = get_parameter('id_group', null); + $product = get_parameter('product', null); + $username = get_parameter('username', null); + $password = get_parameter('password', null); + $extra_1 = get_parameter('extra_1', null); + $extra_2 = get_parameter('extra_2', null); + + if (empty($identifier)) { + $error = __('Key identifier is required'); + } else if ($id_group === null) { + $error = __('You must select a group where store this key!'); + } else if (empty($product)) { + $error = __('You must specify a product type'); + } else if (empty($username) && (empty($password))) { + $error = __('You must specify a username and/or password'); + } + + // Encrypt content (if needed). + $values = [ + 'identifier' => $identifier, + 'id_group' => $id_group, + 'product' => $product, + 'username' => io_input_password($username), + 'password' => io_input_password($password), + 'extra_1' => $extra_1, + 'extra_2' => $extra_2, + ]; + + // Spaces are not allowed. + $values['identifier'] = preg_replace('/\s+/', '-', trim($identifier)); + + return $values; + } + + + /** + * Stores a key into credential store. + * + * @param array $values Key definition. + * @param string $identifier Update or create. + * + * @return boolean True if ok, false if not ok. + */ + private function storeKey($values, $identifier=false) + { + if ($identifier === false) { + // New. + return db_process_sql_insert('tcredential_store', $values); + } else { + // Update. + return db_process_sql_update( + 'tcredential_store', + $values, + ['identifier' => $identifier] + ); + } + + } + + + /** + * Add a new key into Credential Store + * + * @return void + */ + public function addKey() + { + global $config; + + $values = $this->prepareKeyValues(); + + if ($this->storeKey($values) === false) { + $this->ajaxMsg('error', $config['dbconnection']->error); + } else { + $this->ajaxMsg('result', $values['identifier']); + } + + exit; + } + + + /** + * Add a new key into Credential Store + * + * @return void + */ + public function updateKey() + { + global $config; + + $values = $this->prepareKeyValues(); + $identifier = $values['identifier']; + + if ($this->storeKey($values, $identifier) === false) { + $this->ajaxMsg('error', $config['dbconnection']->error); + } else { + $this->ajaxMsg('result', $identifier); + } + + exit; + } + + + /** + * AJAX method. Delete key from keystore. + * + * @return void + */ + public function deleteKey() + { + global $config; + + $identifier = get_parameter('identifier', null); + + if (empty($identifier)) { + $this->ajaxMsg('error', __('identifier cannot be empty'), true); + } + + if (self::getKey($identifier) === false) { + // User has no grants to delete target key. + $this->ajaxMsg('error', __('Not allowed'), true); + } + + if (db_process_sql_delete( + 'tcredential_store', + ['identifier' => $identifier] + ) === false + ) { + $this->ajaxMsg('error', $config['dbconnection']->error, true); + } else { + $this->ajaxMsg('result', $identifier, true); + } + + } + + + /** + * Run CredentialStore (main page). + * + * @return void + */ + public function run() + { + global $config; + + // Require specific CSS and JS. + ui_require_css_file('wizard'); + ui_require_css_file('discovery'); + ui_require_css_file('credential_store'); + + if (!isset($config['encryption_passphrase'])) { + $url = 'https://pandorafms.com/docs/index.php?title=Pandora:Documentation_en:Password_Encryption'; + if ($config['language'] == 'es') { + $url = 'https://pandorafms.com/docs/index.php?title=Pandora:Documentation_es:Cifrado_Contrase%C3%B1as'; + } + + ui_print_warning_message( + __( + 'Database encryption is not enabled. Credentials will be stored in plaintext. %s', + ''.__('How to configure encryption.').'' + ) + ); + } + + // Datatables list. + try { + $columns = [ + 'group', + 'identifier', + 'product', + 'username', + 'options', + ]; + + $column_names = [ + __('Group'), + __('Identifier'), + __('Product'), + __('User'), + [ + 'text' => __('Options'), + 'class' => 'action_buttons', + ], + ]; + + $this->tableId = 'keystore'; + // Load datatables user interface. + ui_print_datatable( + [ + 'id' => $this->tableId, + 'class' => 'info_table', + 'style' => 'width: 100%', + 'columns' => $columns, + 'column_names' => $column_names, + 'ajax_url' => $this->ajaxController, + 'ajax_data' => ['method' => 'draw'], + 'ajax_postprocess' => 'process_datatables_item(item)', + 'no_sortable_columns' => [-1], + 'order' => [ + 'field' => 'identifier', + 'direction' => 'asc', + ], + 'search_button_class' => 'sub filter float-right', + 'form' => [ + 'inputs' => [ + [ + 'label' => __('Group'), + 'type' => 'select', + 'id' => 'filter_id_group', + 'name' => 'filter_id_group', + 'options' => users_get_groups_for_select( + $config['id_user'], + 'AR', + true, + true, + false + ), + ], + [ + 'label' => __('Free search'), + 'type' => 'text', + 'class' => 'mw250px', + 'id' => 'free_search', + 'name' => 'free_search', + ], + ], + ], + ] + ); + } catch (Exception $e) { + echo $e->getMessage(); + } + + // Auxiliar div. + $modal = ''; + $msg = ''; + $aux = ''; + + echo $modal.$msg.$aux; + + // Create button. + echo '
'; + html_print_submit_button( + __('Add key'), + 'create', + false, + 'class="sub next"' + ); + echo '
'; + + echo $this->loadJS(); + + } + + + /** + * Generates inputs for new/update forms. + * + * @param array $values Values or null. + * + * @return string Inputs. + */ + public function printInputs($values=null) + { + if (!is_array($values)) { + $values = []; + } + + $form = [ + 'action' => '#', + 'id' => 'modal_form', + 'onsubmit' => 'return false;', + 'class' => 'modal', + 'extra' => 'autocomplete="new-password"', + ]; + + $inputs = []; + + $inputs[] = [ + 'label' => __('Identifier'), + 'id' => 'div-identifier', + 'arguments' => [ + 'name' => 'identifier', + 'type' => 'text', + 'value' => $values['identifier'], + 'disabled' => (bool) $values['identifier'], + 'return' => true, + ], + ]; + + $inputs[] = [ + 'label' => __('Group'), + 'arguments' => [ + 'name' => 'id_group', + 'id' => 'id_group', + 'input_class' => 'flex-row', + 'type' => 'select_groups', + 'selected' => $values['id_group'], + 'return' => true, + 'class' => 'w50p', + ], + ]; + + $inputs[] = [ + 'label' => __('Product'), + 'id' => 'div-product', + 'arguments' => [ + 'name' => 'product', + 'input_class' => 'flex-row', + 'type' => 'select', + 'script' => 'calculate_inputs()', + 'fields' => [ + 'CUSTOM' => __('Custom'), + 'AWS' => __('Aws'), + 'AZURE' => __('Azure'), + // 'GOOGLE' => __('Google'), + ], + 'selected' => (isset($values['product']) ? $values['product'] : 'CUSTOM'), + 'disabled' => (bool) $values['product'], + 'return' => true, + ], + ]; + + $user_label = __('Username'); + $pass_label = __('Password'); + $extra_1_label = __('Extra'); + $extra_2_label = __('Extra (2)'); + $extra1 = true; + $extra2 = true; + + // Remember to update credential_store.php also. + switch ($values['product']) { + case 'AWS': + $user_label = __('Access key ID'); + $pass_label = __('Secret access key'); + $extra1 = false; + $extra2 = false; + break; + + case 'AZURE': + $user_label = __('Account ID'); + $pass_label = __('Application secret'); + $extra_1_label = __('Tenant or domain name'); + $extra_2_label = __('Subscription id'); + break; + + case 'GOOGLE': + // Need further investigation. + case 'CUSTOM': + $user_label = __('Account ID'); + $pass_label = __('Password'); + $extra1 = false; + $extra2 = false; + default: + // Use defaults. + break; + } + + $inputs[] = [ + 'label' => $user_label, + 'id' => 'div-username', + 'arguments' => [ + 'name' => 'username', + 'input_class' => 'flex-row', + 'type' => 'text', + 'value' => $values['username'], + 'return' => true, + ], + ]; + + $inputs[] = [ + 'label' => $pass_label, + 'id' => 'div-password', + 'arguments' => [ + 'name' => 'password', + 'input_class' => 'flex-row', + 'type' => 'password', + 'value' => $values['password'], + 'return' => true, + ], + ]; + + if ($extra1) { + $inputs[] = [ + 'label' => $extra_1_label, + 'id' => 'div-extra_1', + 'arguments' => [ + 'name' => 'extra_1', + 'input_class' => 'flex-row', + 'type' => 'text', + 'value' => $values['extra_1'], + 'return' => true, + ], + ]; + } + + if ($extra2) { + $inputs[] = [ + 'label' => $extra_2_label, + 'id' => 'div-extra_2', + 'arguments' => [ + 'name' => 'extra_2', + 'input_class' => 'flex-row', + 'type' => 'text', + 'value' => $values['extra_2'], + 'return' => true, + 'display' => $extra2, + ], + + ]; + } + + return $this->printForm( + [ + 'form' => $form, + 'inputs' => $inputs, + ], + true + ); + } + + + /** + * Loads JS content. + * + * @return string JS content. + */ + public function loadJS() + { + ob_start(); + + // Javascript content. + ?> + + 0) { - $propagate = db_get_value( - 'propagate', - 'tgrupo', - 'id_grupo', - $filter['filter_id_group'] - ); - - if (!$propagate) { - $sql_filters[] = sprintf( - ' AND cs.id_group = %d ', - $filter['filter_id_group'] - ); - } else { - $groups = [ $filter['filter_id_group'] ]; - $childrens = groups_get_childrens($id_group, null, true); - if (!empty($childrens)) { - foreach ($childrens as $child) { - $groups[] = (int) $child['id_grupo']; - } - } - - $filter['filter_id_group'] = $groups; - $sql_filters[] = sprintf( - ' AND cs.id_group IN (%s) ', - join(',', $filter['filter_id_group']) - ); - } - } - - if (isset($filter['group_list']) && is_array($filter['group_list'])) { - $sql_filters[] = sprintf( - ' AND cs.id_group IN (%s) ', - join(',', $filter['group_list']) - ); - } - - if (isset($order)) { - $dir = 'asc'; - if ($order == 'desc') { - $dir = 'desc'; - }; - - if (in_array( - $sort_field, - [ - 'group', - 'identifier', - 'product', - 'username', - 'options', - ] - ) - ) { - $order_by = sprintf( - 'ORDER BY `%s` %s', - $sort_field, - $dir - ); - } - } - - if (isset($limit) && $limit > 0 - && isset($offset) && $offset >= 0 - ) { - $pagination = sprintf( - ' LIMIT %d OFFSET %d ', - $limit, - $offset - ); - } - - $sql = sprintf( - 'SELECT %s - FROM tcredential_store cs - LEFT JOIN tgrupo tg - ON tg.id_grupo = cs.id_group - WHERE 1=1 - %s - %s - %s', - join(',', $fields), - join(' ', $sql_filters), - $order_by, - $pagination - ); - - if ($count) { - $sql = sprintf('SELECT count(*) as n FROM ( %s ) tt', $sql); - - return db_get_value_sql($sql); - } - - return db_get_all_rows_sql($sql); -} - - -/** - * Retrieves target key from keystore or false in case of error. - * - * @param string $identifier Key identifier. - * - * @return array Key or false if error. - */ -function get_key($identifier) -{ - return db_get_row_filter( - 'tcredential_store', - [ 'identifier' => $identifier ] - ); -} - - -/** - * Minor function to dump json message as ajax response. - * - * @param string $type Type: result || error. - * @param string $msg Message. - * @param boolean $delete Deletion messages. - * - * @return void - */ -function ajax_msg($type, $msg, $delete=false) -{ - $msg_err = 'Failed while saving: %s'; - $msg_ok = 'Successfully saved into keystore '; - - if ($delete) { - $msg_err = 'Failed while removing: %s'; - $msg_ok = 'Successfully deleted '; - } - - if ($type == 'error') { - echo json_encode( - [ - $type => ui_print_error_message( - __( - $msg_err, - $msg - ), - '', - true - ), - ] - ); - } else { - echo json_encode( - [ - $type => ui_print_success_message( - __( - $msg_ok, - $msg - ), - '', - true - ), - ] - ); - } - - exit; -} - - -/** - * Generates inputs for new/update forms. - * - * @param array $values Values or null. - * - * @return string Inputs. - */ -function print_inputs($values=null) -{ - if (!is_array($values)) { - $values = []; - } - - $return = ''; - $return .= html_print_input( - [ - 'label' => __('Identifier'), - 'name' => 'identifier', - 'input_class' => 'flex-row', - 'type' => 'text', - 'value' => $values['identifier'], - 'disabled' => (bool) $values['identifier'], - 'return' => true, - 'script' => 'alert(\'puta\')', - ] - ); - $return .= html_print_input( - [ - 'label' => __('Group'), - 'name' => 'id_group', - 'id' => 'id_group', - 'input_class' => 'flex-row', - 'type' => 'select_groups', - 'selected' => $values['id_group'], - 'return' => true, - 'class' => 'w50p', - ] - ); - $return .= html_print_input( - [ - 'label' => __('Product'), - 'name' => 'product', - 'input_class' => 'flex-row', - 'type' => 'select', - 'script' => 'calculate_inputs()', - 'fields' => [ - 'CUSTOM' => __('Custom'), - 'AWS' => __('Aws'), - 'AZURE' => __('Azure'), - // 'GOOGLE' => __('Google'), - ], - 'selected' => $values['product'], - 'disabled' => (bool) $values['product'], - 'return' => true, - ] - ); - $user_label = __('Username'); - $pass_label = __('Password'); - $extra_1_label = __('Extra'); - $extra_2_label = __('Extra (2)'); - $extra1 = true; - $extra2 = true; - - // Remember to update credential_store.php also. - switch ($values['product']) { - case 'AWS': - $user_label = __('Access key ID'); - $pass_label = __('Secret access key'); - $extra1 = false; - $extra2 = false; - break; - - case 'AZURE': - $user_label = __('Account ID'); - $pass_label = __('Application secret'); - $extra_1_label = __('Tenant or domain name'); - $extra_2_label = __('Subscription id'); - break; - - case 'GOOGLE': - // Need further investigation. - case 'CUSTOM': - $user_label = __('Account ID'); - $pass_label = __('Password'); - $extra1 = false; - $extra2 = false; - default: - // Use defaults. - break; - } - - $return .= html_print_input( - [ - 'label' => $user_label, - 'name' => 'username', - 'input_class' => 'flex-row', - 'type' => 'text', - 'value' => $values['username'], - 'return' => true, - ] - ); - $return .= html_print_input( - [ - 'label' => $pass_label, - 'name' => 'password', - 'input_class' => 'flex-row', - 'type' => 'password', - 'value' => $values['password'], - 'return' => true, - ] - ); - if ($extra1) { - $return .= html_print_input( - [ - 'label' => $extra_1_label, - 'name' => 'extra_1', - 'input_class' => 'flex-row', - 'type' => 'text', - 'value' => $values['extra_1'], - 'return' => true, - ] - ); - } - - if ($extra2) { - $return .= html_print_input( - [ - 'label' => $extra_2_label, - 'name' => 'extra_2', - 'input_class' => 'flex-row', - 'type' => 'text', - 'value' => $values['extra_2'], - 'return' => true, - 'display' => $extra2, - ] - ); - } - - return $return; -} - - -/** - * Retrieve all identifiers available for current user. - * - * @param string $product Target product. - * - * @return array Of account identifiers. - */ -function credentials_list_accounts($product) -{ - global $config; - - check_login(); - - include_once $config['homedir'].'/include/functions_users.php'; - - static $user_groups; - - if (!isset($user_groups)) { - $user_groups = users_get_groups( - $config['id_user'], - 'AR' - ); - - // Always add group 'ALL' because 'ALL' group credentials - // must be available for all users. - if (is_array($user_groups)) { - $user_groups = ([0] + array_keys($user_groups)); - } else { - $user_groups = [0]; - } - } - - $creds = credentials_get_all( - ['identifier'], - [ - 'product' => $product, - 'group_list' => $user_groups, - ] - ); - - if ($creds === false) { - return []; - } - - $ret = array_reduce( - $creds, - function ($carry, $item) { - $carry[$item['identifier']] = $item['identifier']; - return $carry; - } - ); - - return $ret; -} +// Deprecated. diff --git a/pandora_console/include/functions_html.php b/pandora_console/include/functions_html.php index 5fa4824a1e..12d4aecc01 100644 --- a/pandora_console/include/functions_html.php +++ b/pandora_console/include/functions_html.php @@ -1475,6 +1475,14 @@ function html_print_input_password( $attr['class'] = $class; } + if ($disabled === false) { + // Trick to avoid password completion on most browsers. + if ($autocomplete !== 'on') { + $disabled = true; + $attr['onfocus'] = "this.removeAttribute('readonly');"; + } + } + return html_print_input_text_extended($name, $value, 'password-'.$name, $alt, $size, $maxlength, $disabled, '', $attr, $return, true, '', $autocomplete); } diff --git a/pandora_console/include/functions_io.php b/pandora_console/include/functions_io.php index 32c66ca1e3..baddb18531 100755 --- a/pandora_console/include/functions_io.php +++ b/pandora_console/include/functions_io.php @@ -507,7 +507,7 @@ function ___($string /*, variable arguments */) } -/* +/** * json_encode for multibyte characters. * * @param string Text string to be encoded. @@ -528,7 +528,7 @@ function io_json_mb_encode($string, $encode_options=0) } -/* +/** * Prepare the given password to be stored in the Pandora FMS Database, * encrypting it if necessary. * @@ -541,16 +541,22 @@ function io_input_password($password) global $config; enterprise_include_once('include/functions_crypto.php'); - $ciphertext = enterprise_hook('openssl_encrypt_decrypt', ['encrypt', io_safe_output($password)]); + $ciphertext = enterprise_hook( + 'openssl_encrypt_decrypt', + [ + 'encrypt', + io_safe_input($password), + ] + ); if ($ciphertext === ENTERPRISE_NOT_HOOK) { - return $password; + return io_safe_input($password); } return $ciphertext; } -/* +/** * Process the given password read from the Pandora FMS Database, * decrypting it if necessary. * @@ -563,10 +569,17 @@ function io_output_password($password) global $config; enterprise_include_once('include/functions_crypto.php'); - $plaintext = enterprise_hook('openssl_encrypt_decrypt', ['decrypt', io_safe_output($password)]); + $plaintext = enterprise_hook( + 'openssl_encrypt_decrypt', + [ + 'decrypt', + $password, + ] + ); + if ($plaintext === ENTERPRISE_NOT_HOOK) { - return $password; + return io_safe_output($password); } - return $plaintext; + return io_safe_output($plaintext); } diff --git a/pandora_console/include/javascript/pandora.js b/pandora_console/include/javascript/pandora.js index 1fc5912fb9..33ab956a1f 100644 --- a/pandora_console/include/javascript/pandora.js +++ b/pandora_console/include/javascript/pandora.js @@ -1890,6 +1890,16 @@ function load_modal(settings) { width = settings.onshow.width; } + settings.target.html("Loading modal..."); + settings.target + .dialog({ + title: "Loading", + close: false, + width: 200, + buttons: [] + }) + .show(); + $.ajax({ method: "post", url: settings.url, @@ -1898,6 +1908,9 @@ function load_modal(settings) { data: data, success: function(data) { settings.target.html(data); + if (settings.onload != undefined) { + settings.onload(data); + } settings.target.dialog({ resizable: true, draggable: true, @@ -1927,6 +1940,9 @@ function load_modal(settings) { click: function() { if (AJAX_RUNNING) return; AJAX_RUNNING = 1; + if (settings.onsubmit.preaction != undefined) { + settings.onsubmit.preaction(); + } var formdata = new FormData(); if (settings.extradata) { settings.extradata.forEach(function(item) { @@ -1954,7 +1970,9 @@ function load_modal(settings) { contentType: false, data: formdata, success: function(data) { - settings.ajax_callback(data); + if (settings.ajax_callback != undefined) { + settings.ajax_callback(data); + } AJAX_RUNNING = 0; } }); diff --git a/pandora_console/include/styles/credential_store.css b/pandora_console/include/styles/credential_store.css index aa77985188..5707d90780 100644 --- a/pandora_console/include/styles/credential_store.css +++ b/pandora_console/include/styles/credential_store.css @@ -10,3 +10,33 @@ #new_key select { width: 60%; } + +ul.wizard li > label:not(.p-switch) { + width: auto; +} + +form.top-action-buttons ul.wizard { + display: flex; + flex-direction: row; +} + +ul.wizard li { + margin-right: 1em; +} + +form.modal ul.wizard li { + display: flex; + flex-direction: row; + width: 90%; + margin: 0 auto; + justify-items: center; +} + +form.modal ul.wizard li * { + flex: 1; +} + +ul.wizard li.flex-indep { + flex: 1; + margin: 0; +} diff --git a/pandora_server/lib/PandoraFMS/Config.pm b/pandora_server/lib/PandoraFMS/Config.pm index 3de6b374b3..20934859c7 100644 --- a/pandora_server/lib/PandoraFMS/Config.pm +++ b/pandora_server/lib/PandoraFMS/Config.pm @@ -1037,7 +1037,7 @@ sub pandora_load_config { $pa_config->{'console_pass'}= safe_input(clean_blank($1)); } elsif ($parametro =~ m/^encryption_passphrase\s(.*)/i) { # 6.0 - $pa_config->{'encryption_passphrase'}= safe_input(clean_blank($1)); + $pa_config->{'encryption_passphrase'} = clean_blank($1); } elsif ($parametro =~ m/^unknown_interval\s+([0-9]*)/i) { # > 5.1SP2 $pa_config->{'unknown_interval'}= clean_blank($1); diff --git a/pandora_server/lib/PandoraFMS/Core.pm b/pandora_server/lib/PandoraFMS/Core.pm index e1556d74b6..667cd7c909 100644 --- a/pandora_server/lib/PandoraFMS/Core.pm +++ b/pandora_server/lib/PandoraFMS/Core.pm @@ -3155,11 +3155,20 @@ sub pandora_get_config_value ($$) { ########################################################################## ## Get credential from credential store ########################################################################## -sub pandora_get_credential ($$) { - my ($dbh, $identifier) = @_; +sub pandora_get_credential ($$$) { + my ($pa_config, $dbh, $identifier) = @_; my $key = get_db_single_row($dbh, 'SELECT * FROM tcredential_store WHERE identifier = ?', $identifier); + $key->{'username'} = pandora_output_password( + $pa_config, + safe_output($key->{'username'}) + ); + $key->{'password'} = pandora_output_password( + $pa_config, + safe_output($key->{'password'}) + ); + return $key; } diff --git a/pandora_server/lib/PandoraFMS/Tools.pm b/pandora_server/lib/PandoraFMS/Tools.pm index 9710913efe..715703b5b6 100755 --- a/pandora_server/lib/PandoraFMS/Tools.pm +++ b/pandora_server/lib/PandoraFMS/Tools.pm @@ -624,7 +624,7 @@ sub logger ($$;$) { $message = safe_output ($message); $level = 1 unless defined ($level); - return if ($level > $pa_config->{'verbosity'}); + return if (!defined ($pa_config->{'verbosity'}) || $level > $pa_config->{'verbosity'}); if (!defined($pa_config->{'log_file'})) { print strftime ("%Y-%m-%d %H:%M:%S", localtime()) . " [V". $level ."] " . $message . "\n";