diff --git a/ajax/ajax_import.php b/ajax/ajax_import.php index 8548d572..1029a40a 100644 --- a/ajax/ajax_import.php +++ b/ajax/ajax_import.php @@ -1 +1 @@ -. * */ define('APP_ROOT', '..'); require_once APP_ROOT.DIRECTORY_SEPARATOR.'inc'.DIRECTORY_SEPARATOR.'init.php'; SP_Util::checkReferer('POST'); if (!SP_Init::isLoggedIn()) { SP_Common::printJSON(_('La sesión no se ha iniciado o ha caducado'), 10); } if (SP_Util::demoIsEnabled()) { SP_Common::printJSON(_('Ey, esto es una DEMO!!')); } $sk = SP_Common::parseParams('p', 'sk', false); if (!$sk || !SP_Common::checkSessionKey($sk)) { SP_Common::printJSON(_('CONSULTA INVÁLIDA')); } $res = SP_Import::doImport($_FILES["inFile"]); if (is_array($res['error'])) { foreach ($res['error'] as $error) { $errors [] = $error['description']; $errors [] = $error['hint']; error_log($error['hint']); } $out = implode('\n\n', $errors); SP_Common::printJSON($out); } else if (is_array($res['ok'])) { $out = implode('\n\n', $res['ok']); SP_Common::printJSON($out, 0); } \ No newline at end of file +. * */ define('APP_ROOT', '..'); require_once APP_ROOT.DIRECTORY_SEPARATOR.'inc'.DIRECTORY_SEPARATOR.'init.php'; SP_Util::checkReferer('POST'); if (!SP_Init::isLoggedIn()) { SP_Common::printJSON(_('La sesión no se ha iniciado o ha caducado'), 10); } if (SP_Util::demoIsEnabled()) { SP_Common::printJSON(_('Ey, esto es una DEMO!!')); } $sk = SP_Common::parseParams('p', 'sk', false); if (!$sk || !SP_Common::checkSessionKey($sk)) { SP_Common::printJSON(_('CONSULTA INVÁLIDA')); } $res = SP_Import::doImport($_FILES["inFile"]); if (isset($res['error']) && is_array($res['error'])) { foreach ($res['error'] as $error) { $errors [] = $error['description']; $errors [] = $error['hint']; error_log($error['hint']); } $out = implode('\n\n', $errors); SP_Common::printJSON($out); } else if (is_array($res['ok'])) { $out = implode('\n\n', $res['ok']); SP_Common::printJSON($out, 0); } \ No newline at end of file diff --git a/ajax/ajax_search.php b/ajax/ajax_search.php index ddd8e20f..d0f2ae65 100644 --- a/ajax/ajax_search.php +++ b/ajax/ajax_search.php @@ -218,7 +218,10 @@ foreach ($resQuery as $account) { } } - $strAccNotes = (strlen($account->account_notes) > 300) ? substr($account->account_notes, 0, 300) . "..." : $account->account_notes; + if ($account->account_notes){ + $strAccNotes = (strlen($account->account_notes) > 300) ? substr($account->account_notes, 0, 300) . "..." : $account->account_notes; + $strAccNotes = nl2br(wordwrap(htmlspecialchars($strAccNotes), 50, '
', true)); + } } //echo '
'; @@ -254,7 +257,7 @@ foreach ($resQuery as $account) { echo '
'; echo ''; - echo ($strAccNotes) ? '' : ''; + echo ($strAccNotes) ? '' : ''; if ($filesEnabled) { $intNumFiles = SP_Files::countFiles($account->account_id); diff --git a/inc/account.class.php b/inc/account.class.php index 0edaab6f..c5402ff8 100644 --- a/inc/account.class.php +++ b/inc/account.class.php @@ -550,16 +550,20 @@ class SP_Account $message['action'] = __FUNCTION__; - if (!SP_Groups::addGroupsForAccount($this->accountId, $this->accountUserGroupsId)) { - $message['text'][] = _('Error al actualizar los grupos secundarios'); - SP_Log::wrLogInfo($message); - $message['text'] = array(); + if ( is_array($this->accountUserGroupsId) ){ + if (!SP_Groups::addGroupsForAccount($this->accountId, $this->accountUserGroupsId)) { + $message['text'][] = _('Error al actualizar los grupos secundarios'); + SP_Log::wrLogInfo($message); + $message['text'] = array(); + } } - if (!SP_Users::addUsersForAccount($this->accountId, $this->accountUsersId)) { - $message['text'][] = _('Error al actualizar los usuarios de la cuenta'); - SP_Log::wrLogInfo($message); - $message['text'] = array(); + if ( is_array($this->accountUsersId) ){ + if (!SP_Users::addUsersForAccount($this->accountId, $this->accountUsersId)) { + $message['text'][] = _('Error al actualizar los usuarios de la cuenta'); + SP_Log::wrLogInfo($message); + $message['text'] = array(); + } } $accountInfo = array('customer_name'); diff --git a/inc/common.class.php b/inc/common.class.php index be360c5b..f7ed9530 100644 --- a/inc/common.class.php +++ b/inc/common.class.php @@ -225,7 +225,7 @@ class SP_Common $msgHelp[20] = _('Guarda las acciones realizadas en la aplicación'); $msgHelp[21] = _('Comprobar actualizaciones de la aplicación (sólo para los usuarios administradores)'); $msgHelp[22] = _('Extensiones de máximo 4 caracteres.') . "

" . _('Escribir extensión y pulsar intro para añadir.'); - $msgHelp[23] = _('Importar desde un archivo CSV con el formato') . ":

" . _('nombre_de_cuenta;cliente;categoría;url;usuario;clave;notas') . "

" . _('Si el cliente o la categoría no están creados, se crean automáticamente.'); + $msgHelp[23] = _('Importar desde KeePass o KeePassX. El nombre del cliente será igual a KeePass o KeePassX')."

"._('Importar desde un archivo CSV con el formato') . ":

" . _('nombre_de_cuenta;cliente;categoría;url;usuario;clave;notas') . "

" . _('Si el cliente o la categoría no están creados, se crean automáticamente.'); $msgHelp[24] = _('Permite que las cuentas sin acceso sean visibles sólo para las búsquedas.'); $msgHelp[25] = _('Muestra los resultados de búsqueda de cuentas en formato tarjeta.'); diff --git a/inc/import.class.php b/inc/import.class.php index 149e30f3..b3746589 100644 --- a/inc/import.class.php +++ b/inc/import.class.php @@ -65,6 +65,7 @@ class SP_Import { private static $result = array(); private static $fileContent; + private static $tmpFile; /** * @brief Iniciar la importación de cuentas @@ -75,7 +76,6 @@ class SP_Import { try { self::readDataFromFile($fileData); - self::parseData(); } catch (ImportException $e) { $message['action'] = _('Importar Cuentas'); $message['text'][] = $e->getMessage(); @@ -99,22 +99,21 @@ class SP_Import */ private static function readDataFromFile(&$fileData) { - if (!is_array($fileData)) { throw new ImportException('critical', _('Archivo no subido correctamente'), _('Verifique los permisos del usuario del servidor web')); } - if ($fileData['inFile']['name']) { + if ($fileData['name']) { // Comprobamos la extensión del archivo - $fileExtension = strtoupper(pathinfo($_FILES['inFile']['name'], PATHINFO_EXTENSION)); + $fileExtension = strtoupper(pathinfo($fileData['name'], PATHINFO_EXTENSION)); - if ($fileExtension != 'csv') { + if ($fileExtension != 'CSV' && $fileExtension != 'XML') { throw new ImportException('critical', _('Tipo de archivo no soportado'), _('Compruebe la extensión del archivo')); } } // Variables con información del archivo - $tmpName = $_FILES['inFile']['tmp_name']; + $tmpName = $fileData['tmp_name']; if (!file_exists($tmpName) || !is_readable($tmpName)) { // Registramos el máximo tamaño permitido por PHP @@ -123,12 +122,21 @@ class SP_Import throw new ImportException('critical', _('Error interno al leer el archivo'), _('Compruebe la configuración de PHP para subir archivos')); } + if ($fileData['type'] === 'text/csv'){ + // Leemos el archivo a un array + self::$fileContent = file($tmpName); - // Leemos el archivo a una variable - self::$fileContent = file($tmpName); - - if (!is_array(self::$fileContent)) { - throw new ImportException('critical', _('Error interno al leer el archivo'), _('Compruebe los permisos del directorio temporal')); + if (!is_array(self::$fileContent)) { + throw new ImportException('critical', _('Error interno al leer el archivo'), _('Compruebe los permisos del directorio temporal')); + } + // Obtenemos las cuentas desde el archivo CSV + self::parseFileData(); + } elseif ($fileData['type'] === 'text/xml'){ + self::$tmpFile = $tmpName; + // Analizamos el XML y seleccionamos el formato a importar + self::detectXMLFormat(); + } else{ + throw new ImportException('critical', _('Tipo mime no soportado'), _('Compruebe el formato del archivo')); } return true; @@ -139,52 +147,16 @@ class SP_Import * @throws ImportException * @return bool */ - private static function parseData() + private static function parseFileData() { - // Datos del Usuario - $userId = SP_Common::parseParams('s', 'uid', 0); - $groupId = SP_Common::parseParams('s', 'ugroup', 0); - - $account = new SP_Account; - foreach (self::$fileContent as $data) { $fields = explode(';', $data); - if (count($fields) != 7) { + if (count($fields) < 7) { throw new ImportException('critical', _('El número de campos es incorrecto'), _('Compruebe el formato del archivo CSV')); } - list($accountName, $customerName, $categoryName, $url, $username, $password, $notes) = $fields; - - SP_Customer::$customerName = $customerName; - if (!SP_Customer::checkDupCustomer()) { - $customerId = SP_Customer::getCustomerByName(); - } else { - SP_Customer::addCustomer(); - $customerId = SP_Customer::$customerLastId; - } - - $categoryId = SP_Category::getCategoryIdByName($categoryName); - if ($categoryId === 0 || $categoryId === false) { - SP_Category::$categoryName = $categoryName; - SP_Category::addCategory($categoryName); - $categoryId = SP_Category::$categoryLastId; - } - - $pass = self::encryptPass($password); - - $account->accountName = $accountName; - $account->accountCustomerId = $customerId; - $account->accountCategoryId = $categoryId; - $account->accountLogin = $username; - $account->accountUrl = $url; - $account->accountPass = $pass['pass']; - $account->accountIV = $pass['IV']; - $account->accountNotes = $notes; - $account->accountUserId = $userId; - $account->accountUserGroupId = $groupId; - - if (!$account->createAccount()) { + if (!self::addAccountData($fields)){ $message['action'] = _('Importar Cuentas'); $message['text'][] = _('Error importando cuenta'); $message['text'][] = $data; @@ -196,6 +168,56 @@ class SP_Import return true; } + /** + * @brief Crear una cuenta con los datos obtenidos + * @param array $data con los datos de la cuenta + * @throws ImportException + * @return bool + */ + public static function addAccountData($data) + { + // Datos del Usuario + $userId = SP_Common::parseParams('s', 'uid', 0); + $groupId = SP_Common::parseParams('s', 'ugroup', 0); + + // Asignamos los valores del array a variables + list($accountName, $customerName, $categoryName, $url, $username, $password, $notes) = $data; + + // Comprobamos si existe el cliente o lo creamos + SP_Customer::$customerName = $customerName; + if (!SP_Customer::checkDupCustomer()) { + $customerId = SP_Customer::getCustomerByName(); + } else { + SP_Customer::addCustomer(); + $customerId = SP_Customer::$customerLastId; + } + + // Comprobamos si existe la categoría o la creamos + $categoryId = SP_Category::getCategoryIdByName($categoryName); + if ($categoryId == 0) { + SP_Category::$categoryName = $categoryName; + SP_Category::addCategory($categoryName); + $categoryId = SP_Category::$categoryLastId; + } + + $pass = self::encryptPass($password); + + $account = new SP_Account; + $account->accountName = $accountName; + $account->accountCustomerId = $customerId; + $account->accountCategoryId = $categoryId; + $account->accountLogin = $username; + $account->accountUrl = $url; + $account->accountPass = $pass['pass']; + $account->accountIV = $pass['IV']; + $account->accountNotes = $notes; + $account->accountUserId = $userId; + $account->accountUserGroupId = $groupId; + + // Creamos la cuenta + return $account->createAccount(); + } + /** * @brief Encriptar la clave de una cuenta * @param string $password con la clave de la cuenta @@ -223,4 +245,72 @@ class SP_Import return $data; } -} + /** + * @brief Leer el archivo de KeePass a un objeto XML + * @throws ImportException + * @return bool + */ + private static function readXMLFile() + { + if ($xmlFile = simplexml_load_file(self::$tmpFile)){ + return $xmlFile; + } else{ + throw new ImportException('critical', _('Error interno'), _('No es posible procesar el archivo XML')); + } + } + + /** + * @brief Detectar la aplicación que generó el XML + * @throws ImportException + * @return bool + */ + private static function detectXMLFormat() + { + $xml = self::readXMLFile(); + + if ( $xml->Meta->Generator == 'KeePass' ){ + SP_KeePassImport::addKeepassAccounts($xml); + } else if ($xmlApp = self::parseFileHeader()){ + switch ($xmlApp) { + case 'keepassx_database': + SP_KeePassXImport::addKeepassXAccounts($xml); + break; + case 'revelationdata': + error_log('REVELATION'); + break; + default: + break; + } + } else{ + throw new ImportException('critical', _('Archivo XML no soportado'), _('No es posible detectar la aplicación que exportó los datos')); + } + } + + /** + * @brief Leer la cabecera del archivo XML y obtener patrones de aplicaciones conocidas + * @return bool + */ + private static function parseFileHeader() + { + $handle = @fopen(self::$tmpFile, "r"); + $headersRegex = '/(KEEPASSX_DATABASE|revelationdata)/i'; + + if ( $handle ){ + // No. de líneas a leer como máximo + $maxLines = 5; + $count = 0; + + while (($buffer = fgets($handle, 4096)) !== false && $count <= $maxLines){ + if ( preg_match($headersRegex,$buffer,$app) ){ + fclose($handle); + return strtolower($app[0]); + } + $count++; + } + + fclose($handle); + } + + return false; + } +} \ No newline at end of file diff --git a/inc/keepassimport.class.php b/inc/keepassimport.class.php new file mode 100644 index 00000000..466123f0 --- /dev/null +++ b/inc/keepassimport.class.php @@ -0,0 +1,112 @@ +. + * + */ + +defined('APP_ROOT') || die(_('No es posible acceder directamente a este archivo')); + +/** + * Esta clase es la encargada de importar cuentas desde KeePass + */ +class SP_KeePassImport +{ + + /** + * @brief Iniciar la importación desde KeePass + * @param object $xml + * @return bool + */ + public static function addKeepassAccounts($xml) + { + self::getGroups($xml->Root->Group); + } + + /** + * @brief Obtener los datos de las entradas de KeePass + * @param object $entries con el objeto XML con las entradas + * @param string $groupName con nombre del grupo a procesar + * @throws ImportException + * @return bool + */ + private static function getEntryData($entries, $groupName) + { + foreach ( $entries as $entry ){ + foreach ( $entry->String as $account ){ + switch ($account->Key){ + case 'Notes': + $notes = $account->Value; + break; + case 'Password': + $password = $account->Value; + break; + case 'Title': + $name = $account->Value; + break; + case 'url': + $url = $account->Value; + break; + case 'UserName': + $username = $account->Value; + break; + } + } + + $accountData = array($name,'KeePass',$groupName,$url,$username,$password,$notes); + SP_Import::addAccountData($accountData); + } + } + + /** + * @brief Obtener los grupos y procesar lan entradas de KeePass + * @param object $xml con objeto XML del archivo de KeePass + * @throws ImportException + * @return bool + */ + private static function getGroups($xml) + { + foreach($xml as $node){ + if ( $node->Group ){ + foreach ( $node->Group as $group ){ + $groupName = $group->Name; + // Analizar grupo + if ( $node->Group->Entry ){ + // Obtener entradas + self::getEntryData($group->Entry,$groupName); + } + + if ( $group->Group ){ + // Analizar subgrupo + self::getGroups($group); + } + } + } + + if ( $node->Entry ){ + $groupName = $node->Name; + // Obtener entradas + self::getEntryData($node->Entry,$groupName); + } + } + } +} \ No newline at end of file diff --git a/inc/keepassximport.class.php b/inc/keepassximport.class.php new file mode 100644 index 00000000..275cefb5 --- /dev/null +++ b/inc/keepassximport.class.php @@ -0,0 +1,98 @@ +. + * + */ + +defined('APP_ROOT') || die(_('No es posible acceder directamente a este archivo')); + +/** + * Esta clase es la encargada de importar cuentas desde KeePassX + */ +class SP_KeePassXImport +{ + + /** + * @brief Iniciar la importación desde KeePass + * @param object $xml + * @return bool + */ + public static function addKeepassXAccounts($xml) + { + self::getGroups($xml); + } + + /** + * @brief Obtener los datos de las entradas de KeePass + * @param object $entries con el objeto XML con las entradas + * @param string $groupName con nombre del grupo a procesar + * @throws ImportException + * @return bool + */ + private static function getEntryData($entries, $groupName) + { + foreach ( $entries as $entry ){ + $notes = $entry->comment; + $password = $entry->password; + $name = $entry->title; + $url = $entry->url; + $username = $entry->username; + + $accountData = array($name,'KeePassX',$groupName,$url,$username,$password,$notes); + SP_Import::addAccountData($accountData); + } + } + + /** + * @brief Obtener los grupos y procesar lan entradas de KeePass + * @param object $xml con objeto XML del archivo de KeePass + * @throws ImportException + * @return bool + */ + private static function getGroups($xml) + { + foreach($xml as $node){ + if ( $node->group ){ + foreach ( $node->group as $group ){ + $groupName = $group->title; + // Analizar grupo + if ( $node->group->entry ){ + // Obtener entradas + self::getEntryData($group->entry,$groupName); + } + + if ( $group->group ){ + // Analizar subgrupo + self::getGroups($group); + } + } + } + + if ( $node->entry ){ + $groupName = $node->title; + // Obtener entradas + self::getEntryData($node->entry,$groupName); + } + } + } +} \ No newline at end of file diff --git a/inc/locales/de_DE/LC_MESSAGES/messages.mo b/inc/locales/de_DE/LC_MESSAGES/messages.mo index f7be5a18..240437e1 100644 Binary files a/inc/locales/de_DE/LC_MESSAGES/messages.mo and b/inc/locales/de_DE/LC_MESSAGES/messages.mo differ diff --git a/inc/locales/en_US/LC_MESSAGES/messages.mo b/inc/locales/en_US/LC_MESSAGES/messages.mo index 08b782e1..533d6e7f 100644 Binary files a/inc/locales/en_US/LC_MESSAGES/messages.mo and b/inc/locales/en_US/LC_MESSAGES/messages.mo differ diff --git a/inc/locales/hu_HU/LC_MESSAGES/messages.mo b/inc/locales/hu_HU/LC_MESSAGES/messages.mo index 149170c5..a2063698 100644 Binary files a/inc/locales/hu_HU/LC_MESSAGES/messages.mo and b/inc/locales/hu_HU/LC_MESSAGES/messages.mo differ diff --git a/inc/tpl/migrate.php b/inc/tpl/migrate.php index 54839d99..8e57a645 100644 --- a/inc/tpl/migrate.php +++ b/inc/tpl/migrate.php @@ -100,7 +100,7 @@ $onCloseAction = $data['onCloseAction'];
- +
diff --git a/inc/util.class.php b/inc/util.class.php index 9d49d617..c4befb3b 100644 --- a/inc/util.class.php +++ b/inc/util.class.php @@ -254,7 +254,7 @@ class SP_Util */ public static function getVersion($retBuild = false) { - $build = 7; + $build = 8; $version = array(1, 1, 2); if ($retBuild) { diff --git a/js/functions.js b/js/functions.js index 8ab5395d..487b179d 100644 --- a/js/functions.js +++ b/js/functions.js @@ -557,7 +557,7 @@ function dropFile(accountId, sk, maxsize) { // Función para activar el Drag&Drop de archivos en la importación de cuentas function importFile(sk) { var dropfiles = $('#dropzone'); - var file_exts_ok = ['csv']; + var file_exts_ok = ['csv','xml']; dropfiles.filedrop({ fallback_id: 'inFile',