From 62a4b388cb0828ef91c7496b68df7cc0ce165680 Mon Sep 17 00:00:00 2001 From: nuxsmin Date: Tue, 20 Feb 2018 01:31:32 +0100 Subject: [PATCH] * [MOD] Config module. Work in progress * [MOD] Improved import module. Work in progress --- .../web/Controllers/BootstrapController.php | 4 +- .../Controllers/ConfigImportController.php | 74 ++++ .../material-blue/views/config/import.inc | 4 +- lib/SP/Controller/ConfigActionController.php | 9 +- lib/SP/Import/CsvImportBase.php | 115 ------ lib/SP/Import/Import.php | 119 ------ lib/SP/Import/ImportBase.php | 258 ------------ lib/SP/Import/SyspassImport.php | 337 ---------------- .../Category/CategoryRepository.php | 2 - lib/SP/Services/Category/CategoryService.php | 2 - lib/SP/{ => Services}/Import/CsvImport.php | 21 +- lib/SP/Services/Import/CsvImportBase.php | 149 +++++++ lib/SP/{ => Services}/Import/FileImport.php | 27 +- lib/SP/Services/Import/ImportException.php | 37 ++ .../{ => Services}/Import/ImportInterface.php | 4 +- lib/SP/{ => Services}/Import/ImportParams.php | 4 +- lib/SP/Services/Import/ImportService.php | 131 ++++++ lib/SP/Services/Import/ImportTrait.php | 164 ++++++++ .../{ => Services}/Import/KeepassImport.php | 62 ++- .../{ => Services}/Import/KeepassXImport.php | 62 ++- lib/SP/Services/Import/SyspassImport.php | 375 ++++++++++++++++++ .../{ => Services}/Import/XmlFileImport.php | 32 +- lib/SP/{ => Services}/Import/XmlImport.php | 87 ++-- lib/SP/Services/Import/XmlImportBase.php | 109 +++++ .../{ => Services}/Import/XmlImportTrait.php | 17 +- lib/SP/Storage/DbWrapper.php | 13 +- public/js/app-main.js | 17 +- public/js/app-main.min.js | 44 +- public/js/app-triggers.js | 3 +- public/js/app-triggers.min.js | 20 +- 30 files changed, 1258 insertions(+), 1044 deletions(-) create mode 100644 app/modules/web/Controllers/ConfigImportController.php delete mode 100644 lib/SP/Import/CsvImportBase.php delete mode 100644 lib/SP/Import/Import.php delete mode 100644 lib/SP/Import/ImportBase.php delete mode 100644 lib/SP/Import/SyspassImport.php rename lib/SP/{ => Services}/Import/CsvImport.php (72%) create mode 100644 lib/SP/Services/Import/CsvImportBase.php rename lib/SP/{ => Services}/Import/FileImport.php (80%) create mode 100644 lib/SP/Services/Import/ImportException.php rename lib/SP/{ => Services}/Import/ImportInterface.php (94%) rename lib/SP/{ => Services}/Import/ImportParams.php (97%) create mode 100644 lib/SP/Services/Import/ImportService.php create mode 100644 lib/SP/Services/Import/ImportTrait.php rename lib/SP/{ => Services}/Import/KeepassImport.php (67%) rename lib/SP/{ => Services}/Import/KeepassXImport.php (65%) create mode 100644 lib/SP/Services/Import/SyspassImport.php rename lib/SP/{ => Services}/Import/XmlFileImport.php (79%) rename lib/SP/{ => Services}/Import/XmlImport.php (50%) create mode 100644 lib/SP/Services/Import/XmlImportBase.php rename lib/SP/{ => Services}/Import/XmlImportTrait.php (83%) diff --git a/app/modules/web/Controllers/BootstrapController.php b/app/modules/web/Controllers/BootstrapController.php index f0390109..bbc71e7f 100644 --- a/app/modules/web/Controllers/BootstrapController.php +++ b/app/modules/web/Controllers/BootstrapController.php @@ -63,7 +63,9 @@ class BootstrapController extends SimpleControllerBase 'plugins' => [], 'loggedin' => $this->session->isLoggedIn(), 'authbasic_autologin' => Browser::getServerAuthUser() && $configData->isAuthBasicAutoLoginEnabled(), - 'pk' => $this->session->getPublicKey() ?: (new CryptPKI())->getPublicKey() + 'pk' => $this->session->getPublicKey() ?: (new CryptPKI())->getPublicKey(), + 'import_allowed_exts' => ['CSV', 'XML'], + 'files_allowed_exts' => $configData->getFilesAllowedExts() ]; Response::printJson($data, 0); diff --git a/app/modules/web/Controllers/ConfigImportController.php b/app/modules/web/Controllers/ConfigImportController.php new file mode 100644 index 00000000..52d1620b --- /dev/null +++ b/app/modules/web/Controllers/ConfigImportController.php @@ -0,0 +1,74 @@ +. + */ + +namespace SP\Modules\Web\Controllers; + + +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; +use SP\Http\JsonResponse; +use SP\Http\Request; +use SP\Modules\Web\Controllers\Traits\JsonTrait; +use SP\Services\Import\FileImport; +use SP\Services\Import\ImportParams; +use SP\Services\Import\ImportService; + +/** + * Class ConfigImportController + * + * @package SP\Modules\Web\Controllers + */ +class ConfigImportController extends SimpleControllerBase +{ + use JsonTrait; + + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function importAction() + { + if ($this->config->getConfigData()->isDemoEnabled()) { + $this->returnJsonResponse(JsonResponse::JSON_WARNING, __u('Ey, esto es una DEMO!!')); + } + + $importParams = new ImportParams(); + $importParams->setDefaultUser(Request::analyze('import_defaultuser', $this->session->getUserData()->getId())); + $importParams->setDefaultGroup(Request::analyze('import_defaultgroup', $this->session->getUserData()->getUserGroupId())); + $importParams->setImportPwd(Request::analyzeEncrypted('importPwd')); + $importParams->setImportMasterPwd(Request::analyzeEncrypted('importMasterPwd')); + $importParams->setCsvDelimiter(Request::analyze('csvDelimiter')); + + try { + $importService = $this->dic->get(ImportService::class); + $importService->doImport($importParams, new FileImport($this->router->request()->files()->get('inFile'))); + + $this->returnJsonResponse(JsonResponse::JSON_SUCCESS, __u('Importación finalizada'), [__u('Revise el registro de eventos para más detalles')]); + } catch (\Exception $e) { + processException($e); + + $this->returnJsonResponseException($e); + } + } +} \ No newline at end of file diff --git a/app/modules/web/themes/material-blue/views/config/import.inc b/app/modules/web/themes/material-blue/views/config/import.inc index cd554bbb..8543f824 100644 --- a/app/modules/web/themes/material-blue/views/config/import.inc +++ b/app/modules/web/themes/material-blue/views/config/import.inc @@ -72,8 +72,8 @@
+ title="" + data-action-route="configImport/import"> cloud_upload
diff --git a/lib/SP/Controller/ConfigActionController.php b/lib/SP/Controller/ConfigActionController.php index 68f356ff..c726c721 100644 --- a/lib/SP/Controller/ConfigActionController.php +++ b/lib/SP/Controller/ConfigActionController.php @@ -26,8 +26,6 @@ namespace SP\Controller; use SP\Account\AccountCrypt; use SP\Account\AccountHistoryCrypt; -use SP\Config\Config; -use SP\Config\ConfigData; use SP\Config\ConfigDB; use SP\Core\ActionsInterface; use SP\Core\Backup; @@ -43,15 +41,14 @@ use SP\Core\TaskFactory; use SP\Core\Traits\InjectableTrait; use SP\Core\XmlExport; use SP\Http\Request; -use SP\Import\Import; -use SP\Import\ImportParams; use SP\Log\Email; use SP\Log\Log; use SP\Mgmt\CustomFields\CustomFieldsUtil; use SP\Mgmt\Users\UserPass; use SP\Mgmt\Users\UserUtil; +use SP\Services\Import\ImportParams; +use SP\Services\Import\ImportService; use SP\Storage\DbWrapper; -use SP\Util\Checks; use SP\Util\Json; use SP\Util\Util; @@ -710,7 +707,7 @@ class ConfigActionController implements ItemControllerInterface $ImportParams->setImportMasterPwd(Request::analyzeEncrypted('importMasterPwd')); $ImportParams->setCsvDelimiter(Request::analyze('csvDelimiter')); - $Import = new Import($ImportParams); + $Import = new ImportService($ImportParams); $LogMessage = $Import->doImport($_FILES['inFile']); $this->JsonResponse->setDescription($LogMessage->getHtmlDescription(true)); diff --git a/lib/SP/Import/CsvImportBase.php b/lib/SP/Import/CsvImportBase.php deleted file mode 100644 index ecd01601..00000000 --- a/lib/SP/Import/CsvImportBase.php +++ /dev/null @@ -1,115 +0,0 @@ -. - */ - -namespace SP\Import; - -use SP\Core\Exceptions\SPException; -use SP\DataModel\AccountExtData; -use SP\DataModel\CategoryData; -use SP\DataModel\ClientData; - -defined('APP_ROOT') || die(); - -/** - * Clase CsvImportBase para base de clases de importación desde archivos CSV - * - * @package SP - */ -abstract class CsvImportBase extends ImportBase -{ - /** - * @var int - */ - protected $numFields = 7; - /** - * @var array - */ - protected $mapFields = []; - - /** - * @param int $numFields - */ - public function setNumFields($numFields) - { - $this->numFields = $numFields; - } - - /** - * @param array $mapFields - */ - public function setMapFields($mapFields) - { - $this->mapFields = $mapFields; - } - - /** - * Obtener los datos de las entradas de sysPass y crearlas - * - * @throws SPException - */ - protected function processAccounts() - { - $line = 0; - - foreach ($this->file->getFileContent() as $data) { - $line++; - $fields = str_getcsv($data, $this->ImportParams->getCsvDelimiter(), '"'); - $numfields = count($fields); - - // Comprobar el número de campos de la línea - if ($numfields !== $this->numFields) { - throw new SPException( - sprintf(__('El número de campos es incorrecto (%d)', false), $numfields), SPException::CRITICAL, sprintf(__('Compruebe el formato del archivo CSV en línea %s', false), $line) - ); - } - - // Asignar los valores del array a variables - list($accountName, $customerName, $categoryName, $url, $login, $password, $notes) = $fields; - - // Obtener los ids de cliente y categoría - $CustomerData = new ClientData(null, $customerName); - $this->addCustomer($CustomerData); - $CategoryData = new CategoryData(null, $categoryName); - $this->addCategory($CategoryData); - - // Crear la nueva cuenta - $AccountData = new AccountExtData(); - $AccountData->setName($accountName); - $AccountData->setLogin($login); - $AccountData->setClientId($CustomerData->getId()); - $AccountData->setCategoryId($CategoryData->getId()); - $AccountData->setNotes($notes); - $AccountData->setUrl($url); - $AccountData->setPass($password); - - try { - $this->addAccount($AccountData); - } catch (SPException $e) { - $this->LogMessage->addDetails(__('Error importando cuenta', false), $accountName); - $this->LogMessage->addDetails(__('Error procesando línea', false), $line); - $this->LogMessage->addDetails(__('Error', false), $e->getMessage()); - } - } - } -} \ No newline at end of file diff --git a/lib/SP/Import/Import.php b/lib/SP/Import/Import.php deleted file mode 100644 index 2ec53cb3..00000000 --- a/lib/SP/Import/Import.php +++ /dev/null @@ -1,119 +0,0 @@ -. - */ - -namespace SP\Import; - -use SP\Core\Exceptions\SPException; -use SP\Core\Messages\LogMessage; -use SP\Log\Email; -use SP\Log\Log; -use SP\Storage\DbWrapper; - -defined('APP_ROOT') || die(); - -/** - * Esta clase es la encargada de importar cuentas. - */ -class Import -{ - /** - * @var ImportParams Parámetros de importación - */ - protected $ImportParams; - - /** - * Import constructor. - * - * @param ImportParams $ImportParams - */ - public function __construct(ImportParams $ImportParams) - { - $this->ImportParams = $ImportParams; - } - - /** - * Iniciar la importación de cuentas. - * - * @param array $fileData Los datos del archivo - * @return LogMessage - * @throws SPException - */ - public function doImport(&$fileData) - { - set_time_limit(0); - - $LogMessage = new LogMessage(); - $LogMessage->setAction(__('Importar Cuentas', false)); - $Log = new Log($LogMessage); - - try { - $file = new FileImport($fileData); - - switch ($file->getFileType()) { - case 'text/csv': - case 'application/vnd.ms-excel': - $Import = new CsvImport($file, $this->ImportParams, $LogMessage); - break; - case 'text/xml': - $Import = new XmlImport($file, $this->ImportParams, $LogMessage); - break; - default: - throw new SPException( - sprintf(__('Tipo mime no soportado ("%s")'), $file->getFileType()), SPException::WARNING, __('Compruebe el formato del archivo', false) - ); - } - - if (!DbWrapper::beginTransaction()) { - throw new SPException(__('No es posible iniciar una transacción', false), SPException::ERROR); - } - - $Import->doImport(); - - if (!DbWrapper::endTransaction()) { - throw new SPException(__('No es posible finalizar una transacción', false), SPException::ERROR); - } - - $LogMessage->addDetails(__('Cuentas importadas'), $Import->getCounter()); - } catch (SPException $e) { - DbWrapper::rollbackTransaction(); - - $LogMessage->addDescription($e->getMessage()); - $LogMessage->addDetails(__('Ayuda', false), $e->getHint()); - $Log->setLogLevel(Log::ERROR); - $Log->writeLog(); - - throw $e; - } - - $Log->writeLog(true); - - Email::sendEmail($LogMessage); - - $LogMessage->addDescription(__('Importación finalizada', false)); - $LogMessage->addDescription(__('Revise el registro de eventos para más detalles', false)); - - return $LogMessage; - } -} \ No newline at end of file diff --git a/lib/SP/Import/ImportBase.php b/lib/SP/Import/ImportBase.php deleted file mode 100644 index f586539c..00000000 --- a/lib/SP/Import/ImportBase.php +++ /dev/null @@ -1,258 +0,0 @@ -. - */ - -namespace SP\Import; - -use SP\Account\Account; -use SP\Account\AccountTags; -use SP\Core\Crypt\Crypt; -use SP\Core\Exceptions\SPException; -use SP\Core\Messages\LogMessage; -use SP\Core\OldCrypt; -use SP\DataModel\AccountExtData; -use SP\DataModel\CategoryData; -use SP\DataModel\ClientData; -use SP\DataModel\TagData; -use SP\Log\Log; -use SP\Mgmt\Categories\Category; -use SP\Mgmt\Customers\Customer; -use SP\Mgmt\Tags\Tag; - -defined('APP_ROOT') || die(); - -/** - * Class ImportBase abstracta para manejo de archivos de importación - * - * @package SP - */ -abstract class ImportBase implements ImportInterface -{ - /** - * @var ImportParams - */ - protected $ImportParams; - /** - * @var FileImport - */ - protected $file; - /** - * @var LogMessage - */ - protected $LogMessage; - /** - * @var int - */ - protected $counter = 0; - /** - * @var int - */ - protected $version = 0; - /** - * @var bool Indica si el hash de la clave suministrada es igual a la actual - */ - protected $mPassValidHash = false; - - /** - * ImportBase constructor. - * - * @param FileImport $File - * @param ImportParams $ImportParams - * @param LogMessage $LogMessage - */ - public function __construct(FileImport $File = null, ImportParams $ImportParams = null, LogMessage $LogMessage = null) - { - $this->file = $File; - $this->ImportParams = $ImportParams; - $this->LogMessage = null !== $LogMessage ? $LogMessage : new LogMessage(__('Importar Cuentas', false)); - } - - /** - * @return LogMessage - */ - public function getLogMessage() - { - return $this->LogMessage; - } - - /** - * @param LogMessage $LogMessage - */ - public function setLogMessage($LogMessage) - { - $this->LogMessage = $LogMessage; - } - - /** - * @return int - */ - public function getCounter() - { - return $this->counter; - } - - /** - * @param ImportParams $ImportParams - */ - public function setImportParams($ImportParams) - { - $this->ImportParams = $ImportParams; - } - - /** - * Añadir una cuenta desde un archivo importado. - * - * @param \SP\DataModel\AccountExtData $AccountData - * @return bool - */ - protected function addAccount(AccountExtData $AccountData) - { - if ($AccountData->getCategoryId() === 0) { - Log::writeNewLog(__FUNCTION__, __('Id de categoría no definido. No es posible importar cuenta.', false), Log::INFO); - return false; - } - - if ($AccountData->getClientId() === 0) { - Log::writeNewLog(__FUNCTION__, __('Id de cliente no definido. No es posible importar cuenta.', false), Log::INFO); - return false; - } - - try { - $AccountData->setUserId($this->ImportParams->getDefaultUser()); - $AccountData->setUserGroupId($this->ImportParams->getDefaultGroup()); - - if ($this->mPassValidHash === false && $this->ImportParams->getImportMasterPwd() !== '') { - if ($this->version >= 210) { - $securedKey = Crypt::unlockSecuredKey($AccountData->getKey(), $this->ImportParams->getImportMasterPwd()); - $pass = Crypt::decrypt($AccountData->getPass(), $securedKey, $this->ImportParams->getImportMasterPwd()); - } else { - $pass = OldCrypt::getDecrypt($AccountData->getPass(), $AccountData->getKey(), $this->ImportParams->getImportMasterPwd()); - } - - $AccountData->setPass($pass); - $AccountData->setKey(''); - } - - $encrypt = $AccountData->getKey() === ''; - - $Account = new Account($AccountData); - $Account->createAccount($encrypt); - - $this->LogMessage->addDetails(__('Cuenta creada', false), $AccountData->getName()); - $this->counter++; - } catch (SPException $e) { - $this->LogMessage->addDetails($e->getMessage(), $AccountData->getName()); - $this->LogMessage->addDetails(__('Error', false), $e->getHint()); - } catch (\Exception $e) { - $this->LogMessage->addDetails(__('Error', false), $e->getMessage()); - $this->LogMessage->addDetails(__('Cuenta', false), $AccountData->getName()); - } - - return true; - } - - /** - * Añadir una categoría y devolver el Id - * - * @param CategoryData $CategoryData - * @return Category|null - */ - protected function addCategory(CategoryData $CategoryData) - { - try { - $Category = Category::getItem($CategoryData)->add(); - - $this->LogMessage->addDetails(__('Categoría creada', false), $CategoryData->getName()); - - return $Category; - } catch (SPException $e) { - $this->LogMessage->addDetails($e->getMessage(), $CategoryData->name); - $this->LogMessage->addDetails(__('Error', false), $e->getHint()); - } - - return null; - } - - /** - * Añadir un cliente y devolver el Id - * - * @param ClientData $CustomerData - * @return Customer|null - */ - protected function addCustomer(ClientData $CustomerData) - { - try { - $Customer = Customer::getItem($CustomerData)->add(); - - $this->LogMessage->addDetails(__('Cliente creado', false), $CustomerData->getName()); - - return $Customer; - } catch (SPException $e) { - $this->LogMessage->addDetails($e->getMessage(), $CustomerData->getName()); - $this->LogMessage->addDetails(__('Error', false), $e->getHint()); - } - - return null; - } - - /** - * Añadir una etiqueta y devolver el Id - * - * @param TagData $TagData - * @return Tag|null - */ - protected function addTag(TagData $TagData) - { - try { - $Tag = Tag::getItem($TagData)->add(); - - $this->LogMessage->addDetails(__('Etiqueta creada', false), $TagData->getName()); - - return $Tag; - } catch (SPException $e) { - $this->LogMessage->addDetails($e->getMessage(), $TagData->getName()); - $this->LogMessage->addDetails(__('Error', false), $e->getHint()); - } - - return null; - } - - /** - * Añadir las etiquetas de la cuenta - * - * @param AccountExtData $accountExtData - * @param array $tags - */ - protected function addAccountTags(AccountExtData $accountExtData, array $tags) - { - try { - $accountExtData->setTags($tags); - - $AccountTags = new AccountTags(); - $AccountTags->addTags($accountExtData); - } catch (SPException $e) { - $this->LogMessage->addDetails($e->getMessage(), $accountExtData->getName()); - $this->LogMessage->addDetails(__('Error', false), $e->getHint()); - } - } -} \ No newline at end of file diff --git a/lib/SP/Import/SyspassImport.php b/lib/SP/Import/SyspassImport.php deleted file mode 100644 index 13289403..00000000 --- a/lib/SP/Import/SyspassImport.php +++ /dev/null @@ -1,337 +0,0 @@ -. - */ - -namespace SP\Import; - -use DOMXPath; -use SP\Config\ConfigDB; -use SP\Core\Crypt\Crypt; -use SP\Core\Crypt\Hash; -use SP\Core\Exceptions\SPException; -use SP\Core\OldCrypt; -use SP\DataModel\AccountExtData; -use SP\DataModel\CategoryData; -use SP\DataModel\ClientData; -use SP\DataModel\TagData; - -defined('APP_ROOT') || die(); - -/** - * Esta clase es la encargada de importar cuentas desde sysPass - */ -class SyspassImport extends ImportBase -{ - use XmlImportTrait; - - /** - * Mapeo de etiquetas - * - * @var array - */ - protected $tags = []; - /** - * Mapeo de categorías. - * - * @var array - */ - protected $categories = []; - /** - * Mapeo de clientes. - * - * @var array - */ - protected $customers = []; - - /** - * Iniciar la importación desde sysPass. - * - * @throws SPException - */ - public function doImport() - { - try { - if ($this->ImportParams->getImportMasterPwd() !== '') { - $this->mPassValidHash = Hash::checkHashKey($this->ImportParams->getImportMasterPwd(), ConfigDB::getValue('masterPwd')); - } - - $this->getXmlVersion(); - - if ($this->detectEncrypted()) { - if ($this->ImportParams->getImportPwd() === '') { - throw new SPException(__('Clave de encriptación no indicada', false), SPException::ERROR); - } - - $this->processEncrypted(); - } - - $this->processCategories(); - $this->processCustomers(); - $this->processTags(); - $this->processAccounts(); - } catch (SPException $e) { - throw $e; - } catch (\Exception $e) { - throw new SPException($e->getMessage(), SPException::CRITICAL); - } - } - - /** - * Obtener la versión del XML - */ - protected function getXmlVersion() - { - $DomXpath = new DOMXPath($this->xmlDOM); - $this->version = (int)str_replace('.', '', $DomXpath->query('/Root/Meta/Version')->item(0)->nodeValue); - } - - /** - * Verificar si existen datos encriptados - * - * @return bool - */ - protected function detectEncrypted() - { - return ($this->xmlDOM->getElementsByTagName('Encrypted')->length > 0); - } - - /** - * Procesar los datos encriptados y añadirlos al árbol DOM desencriptados - * - * @throws \SP\Core\Exceptions\SPException - * @throws \Defuse\Crypto\Exception\CryptoException - */ - protected function processEncrypted() - { - $hash = $this->xmlDOM->getElementsByTagName('Encrypted')->item(0)->getAttribute('hash'); - - if ($hash !== '' && !Hash::checkHashKey($this->ImportParams->getImportPwd(), $hash)) { - throw new SPException(__('Clave de encriptación incorrecta', false), SPException::ERROR); - } - - foreach ($this->xmlDOM->getElementsByTagName('Data') as $node) { - /** @var $node \DOMElement */ - $data = base64_decode($node->nodeValue); - - if ($this->version >= 210) { - $securedKey = Crypt::unlockSecuredKey($node->getAttribute('key'), $this->ImportParams->getImportPwd()); - $xmlDecrypted = Crypt::decrypt($data, $securedKey, $this->ImportParams->getImportPwd()); - } else { - $xmlDecrypted = OldCrypt::getDecrypt($data, base64_decode($node->getAttribute('iv'), $this->ImportParams->getImportPwd())); - } - - $newXmlData = new \DOMDocument(); -// $newXmlData->preserveWhiteSpace = true; - if (!$newXmlData->loadXML($xmlDecrypted)) { - throw new SPException(__('Clave de encriptación incorrecta', false), SPException::ERROR); - } - - $newNode = $this->xmlDOM->importNode($newXmlData->documentElement, TRUE); - - $this->xmlDOM->documentElement->appendChild($newNode); - } - - // Eliminar los datos encriptados tras desencriptar los mismos - if ($this->xmlDOM->getElementsByTagName('Data')->length > 0) { - $nodeData = $this->xmlDOM->getElementsByTagName('Encrypted')->item(0); - $nodeData->parentNode->removeChild($nodeData); - } - } - - /** - * Obtener las categorías y añadirlas a sysPass. - * - * @param \DOMElement $Category - * @throws SPException - * @throws \SP\Core\Exceptions\InvalidClassException - */ - protected function processCategories(\DOMElement $Category = null) - { - if ($Category === null) { - $this->getNodesData('Categories', 'Category', __FUNCTION__); - return; - } - - $CategoryData = new CategoryData(); - - foreach ($Category->childNodes as $categoryNode) { - if (isset($categoryNode->tagName)) { - switch ($categoryNode->tagName) { - case 'name': - $CategoryData->setName($categoryNode->nodeValue); - break; - case 'description': - $CategoryData->setDescription($categoryNode->nodeValue); - break; - } - } - } - - $this->addCategory($CategoryData); - - $this->categories[$Category->getAttribute('id')] = $CategoryData->getId(); - } - - /** - * Obtener los clientes y añadirlos a sysPass. - * - * @param \DOMElement $Customer - * @throws SPException - * @throws \SP\Core\Exceptions\InvalidClassException - */ - protected function processCustomers(\DOMElement $Customer = null) - { - if ($Customer === null) { - $this->getNodesData('Customers', 'Customer', __FUNCTION__); - return; - } - - $CustomerData = new ClientData(); - - foreach ($Customer->childNodes as $customerNode) { - if (isset($customerNode->tagName)) { - switch ($customerNode->tagName) { - case 'name': - $CustomerData->setName($customerNode->nodeValue); - break; - case 'description': - $CustomerData->setDescription($customerNode->nodeValue); - break; - } - } - } - - $this->addCustomer($CustomerData); - - $this->customers[$Customer->getAttribute('id')] = $CustomerData->getId(); - } - - /** - * Obtener las etiquetas y añadirlas a sysPass. - * - * @param \DOMElement $Tag - * @throws SPException - * @throws \SP\Core\Exceptions\InvalidClassException - */ - protected function processTags(\DOMElement $Tag = null) - { - if ($Tag === null) { - $this->getNodesData('Tags', 'Tag', __FUNCTION__, false); - return; - } - - $TagData = new TagData(); - - foreach ($Tag->childNodes as $tagNode) { - if (isset($tagNode->tagName)) { - switch ($tagNode->tagName) { - case 'name': - $TagData->setName($tagNode->nodeValue); - break; - } - } - } - - $this->addTag($TagData); - - $this->tags[$Tag->getAttribute('id')] = $TagData->getId(); - } - - /** - * Obtener los datos de las cuentas de sysPass y crearlas. - * - * @param \DOMElement $Account - * @throws SPException - */ - protected function processAccounts(\DOMElement $Account = null) - { - if ($Account === null) { - $this->getNodesData('Accounts', 'Account', __FUNCTION__); - return; - } - - $AccountData = new AccountExtData(); - - /** @var \DOMElement $accountNode */ - foreach ($Account->childNodes as $accountNode) { - if (isset($accountNode->tagName)) { - switch ($accountNode->tagName) { - case 'name'; - $AccountData->setName($accountNode->nodeValue); - break; - case 'login'; - $AccountData->setLogin($accountNode->nodeValue); - break; - case 'categoryId'; - $AccountData->setCategoryId($this->categories[(int)$accountNode->nodeValue]); - break; - case 'customerId'; - $AccountData->setClientId($this->customers[(int)$accountNode->nodeValue]); - break; - case 'url'; - $AccountData->setUrl($accountNode->nodeValue); - break; - case 'pass'; - $AccountData->setPass($accountNode->nodeValue); - break; - case 'key'; - $AccountData->setKey($accountNode->nodeValue); - break; - case 'notes'; - $AccountData->setNotes($accountNode->nodeValue); - break; - case 'tags': - $tags = $this->processAccountTags($accountNode->childNodes); - } - } - } - - $this->addAccount($AccountData); - - if (isset($tags) && count($tags)) { - $this->addAccountTags($AccountData, $tags); - } - } - - /** - * Procesar las etiquetas de la cuenta - * - * @param \DOMNodeList $nodes - * @return array - */ - protected function processAccountTags(\DOMNodeList $nodes) - { - $tags = []; - - if ($nodes->length > 0) { - /** @var \DOMElement $accountTagNode */ - foreach ($nodes as $accountTagNode) { - if (isset($accountTagNode->tagName)) { - $tags[] = $accountTagNode->getAttribute('id'); - } - } - } - - return $tags; - } -} \ No newline at end of file diff --git a/lib/SP/Repositories/Category/CategoryRepository.php b/lib/SP/Repositories/Category/CategoryRepository.php index 00a62978..23b0ecc9 100644 --- a/lib/SP/Repositories/Category/CategoryRepository.php +++ b/lib/SP/Repositories/Category/CategoryRepository.php @@ -48,8 +48,6 @@ class CategoryRepository extends Repository implements RepositoryItemInterface * @param CategoryData $itemData * @return mixed * @throws SPException - * @throws \SP\Core\Exceptions\ConstraintException - * @throws \SP\Core\Exceptions\QueryException */ public function create($itemData) { diff --git a/lib/SP/Services/Category/CategoryService.php b/lib/SP/Services/Category/CategoryService.php index 50113da8..1d104d5c 100644 --- a/lib/SP/Services/Category/CategoryService.php +++ b/lib/SP/Services/Category/CategoryService.php @@ -93,8 +93,6 @@ class CategoryService extends Service * @param $itemData * @return mixed * @throws SPException - * @throws \SP\Core\Exceptions\ConstraintException - * @throws \SP\Core\Exceptions\QueryException */ public function create($itemData) { diff --git a/lib/SP/Import/CsvImport.php b/lib/SP/Services/Import/CsvImport.php similarity index 72% rename from lib/SP/Import/CsvImport.php rename to lib/SP/Services/Import/CsvImport.php index fa626951..072044c1 100644 --- a/lib/SP/Import/CsvImport.php +++ b/lib/SP/Services/Import/CsvImport.php @@ -2,8 +2,8 @@ /** * sysPass * - * @author nuxsmin - * @link http://syspass.org + * @author nuxsmin + * @link http://syspass.org * @copyright 2012-2017, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. @@ -22,9 +22,7 @@ * along with sysPass. If not, see . */ -namespace SP\Import; - -use SP\Core\Exceptions\SPException; +namespace SP\Services\Import; defined('APP_ROOT') || die(); @@ -33,7 +31,7 @@ defined('APP_ROOT') || die(); * * @package SP */ -class CsvImport extends CsvImportBase +class CsvImport extends CsvImportBase implements ImportInterface { /** * Iniciar la importación desde XML. @@ -42,13 +40,10 @@ class CsvImport extends CsvImportBase */ public function doImport() { - try { - $this->LogMessage->addDescription(sprintf(__('Formato detectado: %s'), 'CSV')); +// $this->LogMessage->addDescription(sprintf(__('Formato detectado: %s'), 'CSV')); + $this->fileImport->readFileToArray(); + $this->processAccounts(); - $this->file->readFileToArray(); - $this->processAccounts(); - } catch (SPException $e) { - throw $e; - } + return $this; } } \ No newline at end of file diff --git a/lib/SP/Services/Import/CsvImportBase.php b/lib/SP/Services/Import/CsvImportBase.php new file mode 100644 index 00000000..716e59aa --- /dev/null +++ b/lib/SP/Services/Import/CsvImportBase.php @@ -0,0 +1,149 @@ +. + */ + +namespace SP\Services\Import; + +use SP\Account\AccountRequest; +use SP\Bootstrap; +use SP\DataModel\CategoryData; +use SP\DataModel\ClientData; +use SP\Services\Account\AccountService; +use SP\Services\Category\CategoryService; +use SP\Services\Client\ClientService; +use SP\Services\Tag\TagService; + +defined('APP_ROOT') || die(); + +/** + * Clase CsvImportBase para base de clases de importación desde archivos CSV + * + * @package SP + */ +abstract class CsvImportBase +{ + use ImportTrait; + + /** + * @var int + */ + protected $numFields = 7; + /** + * @var array + */ + protected $mapFields = []; + /** + * @var FileImport + */ + protected $fileImport; + + /** + * ImportBase constructor. + * + * @param FileImport $fileImport + * @param ImportParams $importParams + * @throws \Psr\Container\ContainerExceptionInterface + * @throws \Psr\Container\NotFoundExceptionInterface + */ + public function __construct(FileImport $fileImport, ImportParams $importParams) + { + $this->fileImport = $fileImport; + $this->importParams = $importParams; + + $dic = Bootstrap::getContainer(); + $this->accountService = $dic->get(AccountService::class); + $this->categoryService = $dic->get(CategoryService::class); + $this->clientService = $dic->get(ClientService::class); + $this->tagService = $dic->get(TagService::class); + } + + /** + * @param int $numFields + */ + public function setNumFields($numFields) + { + $this->numFields = $numFields; + } + + /** + * @param array $mapFields + */ + public function setMapFields($mapFields) + { + $this->mapFields = $mapFields; + } + + /** + * Obtener los datos de las entradas de sysPass y crearlas + * + * @throws ImportException + */ + protected function processAccounts() + { + $line = 0; + + foreach ($this->fileImport->getFileContent() as $data) { + $line++; + $fields = str_getcsv($data, $this->importParams->getCsvDelimiter(), '"'); + $numfields = count($fields); + + // Comprobar el número de campos de la línea + if ($numfields !== $this->numFields) { + throw new ImportException( + sprintf(__('El número de campos es incorrecto (%d)'), $numfields), + ImportException::ERROR, + sprintf(__('Compruebe el formato del archivo CSV en línea %s'), $line) + ); + } + + // Asignar los valores del array a variables + list($accountName, $clientName, $categoryName, $url, $login, $password, $notes) = $fields; + + try { + // Obtener los ids de cliente y categoría + $clientData = new ClientData(null, $clientName); + $this->addClient($clientData); + + $categoryData = new CategoryData(null, $categoryName); + $this->addCategory($categoryData); + + // Crear la nueva cuenta + $accountRequest = new AccountRequest(); + $accountRequest->name = $accountName; + $accountRequest->login = $login; + $accountRequest->clientId = $clientData->getId(); + $accountRequest->categoryId = $categoryData->getId(); + $accountRequest->notes = $notes; + $accountRequest->url = $url; + $accountRequest->pass = $password; + + $this->addAccount($accountRequest); + } catch (\Exception $e) { + processException($e); +// $this->LogMessage->addDetails(__('Error importando cuenta', false), $accountName); +// $this->LogMessage->addDetails(__('Error procesando línea', false), $line); +// $this->LogMessage->addDetails(__('Error', false), $e->getMessage()); + } + } + } +} \ No newline at end of file diff --git a/lib/SP/Import/FileImport.php b/lib/SP/Services/Import/FileImport.php similarity index 80% rename from lib/SP/Import/FileImport.php rename to lib/SP/Services/Import/FileImport.php index 70abf404..51ad1d7d 100644 --- a/lib/SP/Import/FileImport.php +++ b/lib/SP/Services/Import/FileImport.php @@ -22,7 +22,7 @@ * along with sysPass. If not, see . */ -namespace SP\Import; +namespace SP\Services\Import; use SP\Core\Exceptions\SPException; use SP\Util\Util; @@ -63,7 +63,7 @@ class FileImport * @param array $fileData Datos del archivo a importar * @throws SPException */ - public function __construct(&$fileData) + public function __construct($fileData) { try { $this->checkFile($fileData); @@ -78,11 +78,14 @@ class FileImport * @param array $fileData con los datos del archivo * @throws SPException */ - private function checkFile(&$fileData) + private function checkFile($fileData) { if (!is_array($fileData)) { throw new SPException( - __('Archivo no subido correctamente', false), SPException::CRITICAL, __('Verifique los permisos del usuario del servidor web', false)); + __u('Archivo no subido correctamente'), + SPException::ERROR, + __u('Verifique los permisos del usuario del servidor web') + ); } if ($fileData['name']) { @@ -91,7 +94,9 @@ class FileImport if ($fileExtension !== 'CSV' && $fileExtension !== 'XML') { throw new SPException( - __('Tipo de archivo no soportado', false), SPException::CRITICAL, __('Compruebe la extensión del archivo', false) + __u('Tipo de archivo no soportado'), + SPException::ERROR, + __u('Compruebe la extensión del archivo') ); } } @@ -105,7 +110,9 @@ class FileImport Util::getMaxUpload(); throw new SPException( - __('Error interno al leer el archivo', false), SPException::CRITICAL, __('Compruebe la configuración de PHP para subir archivos', false) + __u('Error interno al leer el archivo'), + SPException::ERROR, + __u('Compruebe la configuración de PHP para subir archivos') ); } } @@ -147,7 +154,9 @@ class FileImport if ($this->fileContent === false) { throw new SPException( - __('Error interno al leer el archivo', false), SPException::CRITICAL, __('Compruebe los permisos del directorio temporal', false) + __u('Error interno al leer el archivo'), + SPException::ERROR, + __u('Compruebe los permisos del directorio temporal') ); } } @@ -173,7 +182,9 @@ class FileImport if ($this->fileContent === false) { throw new SPException( - __('Error interno al leer el archivo', false), SPException::CRITICAL, __('Compruebe los permisos del directorio temporal', false) + __u('Error interno al leer el archivo'), + SPException::ERROR, + __u('Compruebe los permisos del directorio temporal') ); } } diff --git a/lib/SP/Services/Import/ImportException.php b/lib/SP/Services/Import/ImportException.php new file mode 100644 index 00000000..ad493b68 --- /dev/null +++ b/lib/SP/Services/Import/ImportException.php @@ -0,0 +1,37 @@ +. + */ + +namespace SP\Services\Import; + +use SP\Core\Exceptions\SPException; + +/** + * Class ImportException + * + * @package SP\Services\Import + */ +class ImportException extends SPException +{ + +} \ No newline at end of file diff --git a/lib/SP/Import/ImportInterface.php b/lib/SP/Services/Import/ImportInterface.php similarity index 94% rename from lib/SP/Import/ImportInterface.php rename to lib/SP/Services/Import/ImportInterface.php index 25325ce2..91cb6890 100644 --- a/lib/SP/Import/ImportInterface.php +++ b/lib/SP/Services/Import/ImportInterface.php @@ -22,7 +22,7 @@ * along with sysPass. If not, see . */ -namespace SP\Import; +namespace SP\Services\Import; /** * Interface ImportInterface @@ -33,6 +33,8 @@ interface ImportInterface { /** * Iniciar la importación + * + * @return ImportInterface */ public function doImport(); diff --git a/lib/SP/Import/ImportParams.php b/lib/SP/Services/Import/ImportParams.php similarity index 97% rename from lib/SP/Import/ImportParams.php rename to lib/SP/Services/Import/ImportParams.php index 0279d52b..19f2a654 100644 --- a/lib/SP/Import/ImportParams.php +++ b/lib/SP/Services/Import/ImportParams.php @@ -22,13 +22,13 @@ * along with sysPass. If not, see . */ -namespace SP\Import; +namespace SP\Services\Import; /** * Class ImportParams * - * @package SP\Import + * @package SP\Services\Import */ class ImportParams { diff --git a/lib/SP/Services/Import/ImportService.php b/lib/SP/Services/Import/ImportService.php new file mode 100644 index 00000000..60218f53 --- /dev/null +++ b/lib/SP/Services/Import/ImportService.php @@ -0,0 +1,131 @@ +. + */ + +namespace SP\Services\Import; + +use SP\Services\Service; +use SP\Storage\Database; +use SP\Storage\DbWrapper; + +defined('APP_ROOT') || die(); + +/** + * Esta clase es la encargada de importar cuentas. + */ +class ImportService extends Service +{ + /** + * @var ImportParams + */ + protected $importParams; + /** + * @var FileImport + */ + protected $fileImport; + + /** + * Iniciar la importación de cuentas. + * + * @param ImportParams $importParams + * @param FileImport $fileImport + * @throws \Exception + * @throws \Psr\Container\ContainerExceptionInterface + * @throws \Psr\Container\NotFoundExceptionInterface + */ + public function doImport(ImportParams $importParams, FileImport $fileImport) + { + $this->importParams = $importParams; + $this->fileImport = $fileImport; + +// $LogMessage->setAction(__('Importar Cuentas', false)); + + $import = $this->selectImportType(); + + $db = $this->dic->get(Database::class); + + try { + if (!DbWrapper::beginTransaction($db)) { + throw new ImportException(__u('No es posible iniciar una transacción')); + } + + $import->doImport(); + + if (!DbWrapper::endTransaction($db)) { + throw new ImportException(__u('No es posible finalizar una transacción')); + } + +// $LogMessage->addDetails(__('Cuentas importadas'), $Import->getCounter()); + } catch (\Exception $e) { + if (DbWrapper::rollbackTransaction($db)) { + debugLog('Rollback'); + } + +// $LogMessage->addDescription($e->getMessage()); +// $LogMessage->addDetails(__('Ayuda', false), $e->getHint()); +// $Log->setLogLevel(Log::ERROR); +// $Log->writeLog(); + + throw $e; + } + + +// $LogMessage->addDescription(__('Importación finalizada', false)); +// $LogMessage->addDescription(__('Revise el registro de eventos para más detalles', false)); + } + + /** + * @return ImportInterface + * @throws ImportException + * @throws \Psr\Container\ContainerExceptionInterface + * @throws \Psr\Container\NotFoundExceptionInterface + */ + protected function selectImportType() + { + switch ($this->fileImport->getFileType()) { + case 'text/csv': + case 'application/vnd.ms-excel': + return new CsvImport($this->fileImport, $this->importParams); + break; + case 'text/xml': + return new XmlImport(new XmlFileImport($this->fileImport), $this->importParams); + break; + } + + throw new ImportException( + sprintf(__('Tipo mime no soportado ("%s")'), $this->fileImport->getFileType()), + ImportException::ERROR, + __u('Compruebe el formato del archivo') + ); + } + + /** + * @throws \Psr\Container\ContainerExceptionInterface + * @throws \Psr\Container\NotFoundExceptionInterface + */ + protected function initialize() + { + set_time_limit(0); + } +} \ No newline at end of file diff --git a/lib/SP/Services/Import/ImportTrait.php b/lib/SP/Services/Import/ImportTrait.php new file mode 100644 index 00000000..7a85e96a --- /dev/null +++ b/lib/SP/Services/Import/ImportTrait.php @@ -0,0 +1,164 @@ +. + */ + +namespace SP\Services\Import; + +use SP\Account\AccountRequest; +use SP\Core\Crypt\Crypt; +use SP\Core\Exceptions\SPException; +use SP\Core\OldCrypt; +use SP\DataModel\CategoryData; +use SP\DataModel\ClientData; +use SP\DataModel\TagData; +use SP\Services\Account\AccountService; +use SP\Services\Category\CategoryService; +use SP\Services\Client\ClientService; +use SP\Services\Tag\TagService; + +/** + * Trait ImportTrait + * + * @package SP\Services\Import + */ +trait ImportTrait +{ + /** + * @var ImportParams + */ + protected $importParams; + /** + * @var int + */ + protected $version = 0; + /** + * @var bool Indica si el hash de la clave suministrada es igual a la actual + */ + protected $mPassValidHash = false; + /** + * @var int + */ + protected $counter = 0; + /** + * @var AccountService + */ + private $accountService; + /** + * @var CategoryService + */ + private $categoryService; + /** + * @var ClientService + */ + private $clientService; + /** + * @var TagService + */ + private $tagService; + + /** + * @return int + */ + public function getCounter() + { + return $this->counter; + } + + /** + * Añadir una cuenta desde un archivo importado. + * + * @param AccountRequest $accountRequest + * @throws ImportException + * @throws SPException + * @throws \Defuse\Crypto\Exception\CryptoException + * @throws \SP\Core\Dic\ContainerException + * @throws \SP\Core\Exceptions\ConstraintException + * @throws \SP\Core\Exceptions\QueryException + */ + protected function addAccount(AccountRequest $accountRequest) + { + if ($accountRequest->categoryId === 0) { + throw new ImportException(__u('Id de categoría no definido. No es posible importar cuenta.')); + } + + if ($accountRequest->clientId === 0) { + throw new ImportException(__u('Id de cliente no definido. No es posible importar cuenta.')); + } + + $accountRequest->userId = $this->importParams->getDefaultUser(); + $accountRequest->userGroupId = $this->importParams->getDefaultGroup(); + + if ($this->mPassValidHash === false && $this->importParams->getImportMasterPwd() !== '') { + if ($this->version >= 210) { + $securedKey = Crypt::unlockSecuredKey($accountRequest->key, $this->importParams->getImportMasterPwd()); + $pass = Crypt::decrypt($accountRequest->pass, $securedKey, $this->importParams->getImportMasterPwd()); + } else { + $pass = OldCrypt::getDecrypt($accountRequest->pass, $accountRequest->key, $this->importParams->getImportMasterPwd()); + } + + $accountRequest->pass = $pass; + $accountRequest->key = ''; + } + + $this->accountService->create($accountRequest); + +// $this->LogMessage->addDetails(__('Cuenta creada', false), $accountRequest->name); + $this->counter++; + } + + /** + * Añadir una categoría y devolver el Id + * + * @param CategoryData $categoryData + * @return int + * @throws SPException + */ + protected function addCategory(CategoryData $categoryData) + { + return $this->categoryService->create($categoryData); + } + + /** + * Añadir un cliente y devolver el Id + * + * @param ClientData $clientData + * @return int + * @throws SPException + */ + protected function addClient(ClientData $clientData) + { + return $this->clientService->create($clientData); + } + + /** + * Añadir una etiqueta y devolver el Id + * + * @param TagData $tagData + * @return int + * @throws SPException + */ + protected function addTag(TagData $tagData) + { + return $this->tagService->create($tagData); + } +} \ No newline at end of file diff --git a/lib/SP/Import/KeepassImport.php b/lib/SP/Services/Import/KeepassImport.php similarity index 67% rename from lib/SP/Import/KeepassImport.php rename to lib/SP/Services/Import/KeepassImport.php index 04661eae..a3c8939c 100644 --- a/lib/SP/Import/KeepassImport.php +++ b/lib/SP/Services/Import/KeepassImport.php @@ -22,11 +22,11 @@ * along with sysPass. If not, see . */ -namespace SP\Import; +namespace SP\Services\Import; use DOMElement; use DOMXPath; -use SP\DataModel\AccountExtData; +use SP\Account\AccountRequest; use SP\DataModel\CategoryData; use SP\DataModel\ClientData; @@ -35,53 +35,51 @@ defined('APP_ROOT') || die(); /** * Esta clase es la encargada de importar cuentas desde KeePass */ -class KeepassImport extends ImportBase +class KeepassImport extends XmlImportBase implements ImportInterface { - use XmlImportTrait; - - /** - * @var int - */ - protected $customerId = 0; - /** * Iniciar la importación desde KeePass * * @throws \SP\Core\Exceptions\SPException - * @throws \SP\Core\Exceptions\InvalidClassException + * @return ImportInterface */ public function doImport() { - $customerData = new ClientData(null, 'KeePass'); - $this->addCustomer($customerData); - - $this->customerId = $customerData->getId(); - $this->process(); + + return $this; } /** * Obtener los grupos y procesar lan entradas de KeePass. + * + * @throws \SP\Core\Exceptions\SPException */ protected function process() { + $clientId = $this->addClient(new ClientData(null, 'KeePass')); + foreach ($this->getItems() as $group => $entry) { - $CategoryData = new CategoryData(null, $group); - $this->addCategory($CategoryData); + try { + $categoryData = new CategoryData(null, $group); + $this->addCategory($categoryData); - if (count($entry) > 0) { - foreach ($entry as $account) { - $AccountData = new AccountExtData(); - $AccountData->setNotes($account['Notes']); - $AccountData->setPass($account['Password']); - $AccountData->setName($account['Title']); - $AccountData->setUrl($account['URL']); - $AccountData->setLogin($account['UserName']); - $AccountData->setCategoryId($CategoryData->getId()); - $AccountData->setClientId($this->customerId); + if (count($entry) > 0) { + foreach ($entry as $account) { + $accountRequest = new AccountRequest(); + $accountRequest->notes = $account['Notes']; + $accountRequest->pass = $account['Password']; + $accountRequest->name = $account['Title']; + $accountRequest->url = $account['URL']; + $accountRequest->login = $account['UserName']; + $accountRequest->categoryId = $categoryData->getId(); + $accountRequest->clientId = $clientId; - $this->addAccount($AccountData); + $this->addAccount($accountRequest); + } } + } catch (\Exception $e) { + processException($e); } } } @@ -94,11 +92,11 @@ class KeepassImport extends ImportBase protected function getItems() { $DomXpath = new DOMXPath($this->xmlDOM); - $Tags = $DomXpath->query('/KeePassFile/Root/Group//Group|/KeePassFile/Root/Group//Entry'); + $tags = $DomXpath->query('/KeePassFile/Root/Group//Group|/KeePassFile/Root/Group//Entry'); $items = []; - /** @var DOMElement[] $Tags */ - foreach ($Tags as $tag) { + /** @var DOMElement[] $tags */ + foreach ($tags as $tag) { if ($tag->nodeType === 1) { if ($tag->nodeName === 'Entry') { $path = $tag->getNodePath(); diff --git a/lib/SP/Import/KeepassXImport.php b/lib/SP/Services/Import/KeepassXImport.php similarity index 65% rename from lib/SP/Import/KeepassXImport.php rename to lib/SP/Services/Import/KeepassXImport.php index 07e81d06..7719d4ed 100644 --- a/lib/SP/Import/KeepassXImport.php +++ b/lib/SP/Services/Import/KeepassXImport.php @@ -2,8 +2,8 @@ /** * sysPass * - * @author nuxsmin - * @link http://syspass.org + * @author nuxsmin + * @link http://syspass.org * @copyright 2012-2017, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. @@ -22,10 +22,10 @@ * along with sysPass. If not, see . */ -namespace SP\Import; +namespace SP\Services\Import; use SimpleXMLElement; -use SP\DataModel\AccountExtData; +use SP\Account\AccountRequest; use SP\DataModel\CategoryData; use SP\DataModel\ClientData; @@ -33,31 +33,30 @@ defined('APP_ROOT') || die(); /** * Esta clase es la encargada de importar cuentas desde KeePassX + * + * @todo Use xmlDOM */ -class KeepassXImport extends ImportBase +class KeepassXImport extends XmlImportBase implements ImportInterface { - use XmlImportTrait; - /** * @var int */ - protected $customerId = 0; + protected $clientId; /** * Iniciar la importación desde KeePassX. * * @throws \SP\Core\Exceptions\SPException - * @return bool * @throws \SP\Core\Exceptions\InvalidClassException + * @return ImportInterface */ public function doImport() { - $customerData = new ClientData(null, 'KeePassX'); - $this->addCustomer($customerData); + $this->clientId = $this->addClient(new ClientData(null, 'KeePassX')); - $this->customerId = $customerData->getId(); + $this->processCategories($this->xmlDOM); - $this->processCategories($this->xml); + return $this; } /** @@ -75,11 +74,8 @@ class KeepassXImport extends ImportBase // Analizar grupo if ($node->group->entry) { // Crear la categoría - $CategoryData = new CategoryData(null, $group->title, 'KeePassX'); - $this->addCategory($CategoryData); - // Crear cuentas - $this->processAccounts($group->entry, $CategoryData->getId()); + $this->processAccounts($group->entry, $this->addCategory(new CategoryData(null, $group->title, 'KeePassX'))); } if ($group->group) { @@ -90,11 +86,8 @@ class KeepassXImport extends ImportBase } if ($node->entry) { - $CategoryData = new CategoryData(null, $node->title, 'KeePassX'); - $this->addCategory($CategoryData); - // Crear cuentas - $this->processAccounts($node->entry, $CategoryData->getId()); + $this->processAccounts($node->entry, $this->addCategory(new CategoryData(null, $node->title, 'KeePassX'))); } } } @@ -102,9 +95,8 @@ class KeepassXImport extends ImportBase /** * Obtener los datos de las entradas de KeePass. * - * @param SimpleXMLElement $entries El objeto XML con las entradas - * @param int $categoryId Id de la categoría - * @throws \SP\Core\Exceptions\SPException + * @param SimpleXMLElement $entries El objeto XML con las entradas + * @param int $categoryId Id de la categoría */ protected function processAccounts(SimpleXMLElement $entries, $categoryId) { @@ -115,16 +107,20 @@ class KeepassXImport extends ImportBase $notes = isset($entry->comment) ? (string)$entry->comment : ''; $username = isset($entry->username) ? (string)$entry->username : ''; - $AccountData = new AccountExtData(); - $AccountData->setPass($password); - $AccountData->setNotes($notes); - $AccountData->setName($name); - $AccountData->setUrl($url); - $AccountData->setLogin($username); - $AccountData->setClientId($this->customerId); - $AccountData->setCategoryId($categoryId); + $accountRequest = new AccountRequest(); + $accountRequest->pass = $password; + $accountRequest->notes = $notes; + $accountRequest->name = $name; + $accountRequest->url = $url; + $accountRequest->login = $username; + $accountRequest->clientId = $this->clientId; + $accountRequest->categoryId = $categoryId; - $this->addAccount($AccountData); + try { + $this->addAccount($accountRequest); + } catch (\Exception $e) { + processException($e); + } } } } \ No newline at end of file diff --git a/lib/SP/Services/Import/SyspassImport.php b/lib/SP/Services/Import/SyspassImport.php new file mode 100644 index 00000000..3543d1a6 --- /dev/null +++ b/lib/SP/Services/Import/SyspassImport.php @@ -0,0 +1,375 @@ +. + */ + +namespace SP\Services\Import; + +use Defuse\Crypto\Exception\CryptoException; +use DOMXPath; +use SP\Account\AccountRequest; +use SP\Config\ConfigDB; +use SP\Core\Crypt\Crypt; +use SP\Core\Crypt\Hash; +use SP\Core\OldCrypt; +use SP\DataModel\CategoryData; +use SP\DataModel\ClientData; +use SP\DataModel\TagData; + +defined('APP_ROOT') || die(); + +/** + * Esta clase es la encargada de importar cuentas desde sysPass + */ +class SyspassImport extends XmlImportBase implements ImportInterface +{ + /** + * Mapeo de etiquetas + * + * @var array + */ + protected $tags = []; + /** + * Mapeo de categorías. + * + * @var array + */ + protected $categories = []; + /** + * Mapeo de clientes. + * + * @var array + */ + protected $clients = []; + + /** + * Iniciar la importación desde sysPass. + * + * @throws ImportException + * @return ImportInterface + */ + public function doImport() + { + try { + if ($this->importParams->getImportMasterPwd() !== '') { + $this->mPassValidHash = Hash::checkHashKey($this->importParams->getImportMasterPwd(), ConfigDB::getValue('masterPwd')); + } + + $this->version = $this->getXmlVersion(); + + if ($this->detectEncrypted()) { + if ($this->importParams->getImportPwd() === '') { + throw new ImportException(__u('Clave de encriptación no indicada'), ImportException::INFO); + } + + $this->processEncrypted(); + } + + $this->processCategories(); + + if ($this->version >= 300) { + $this->processClients(); + } else { + $this->processCustomers(); + } + + $this->processTags(); + $this->processAccounts(); + + return $this; + } catch (ImportException $e) { + throw $e; + } catch (\Exception $e) { + throw new ImportException($e->getMessage(), ImportException::CRITICAL); + } + } + + /** + * Obtener la versión del XML + */ + protected function getXmlVersion() + { + return (int)str_replace('.', '', (new DOMXPath($this->xmlDOM))->query('/Root/Meta/Version')->item(0)->nodeValue); + } + + /** + * Verificar si existen datos encriptados + * + * @return bool + */ + protected function detectEncrypted() + { + return ($this->xmlDOM->getElementsByTagName('Encrypted')->length > 0); + } + + /** + * Procesar los datos encriptados y añadirlos al árbol DOM desencriptados + * + * @throws ImportException + */ + protected function processEncrypted() + { + $hash = $this->xmlDOM->getElementsByTagName('Encrypted')->item(0)->getAttribute('hash'); + + if ($hash !== '' && !Hash::checkHashKey($this->importParams->getImportPwd(), $hash)) { + throw new ImportException(__u('Clave de encriptación incorrecta')); + } + + foreach ($this->xmlDOM->getElementsByTagName('Data') as $node) { + /** @var $node \DOMElement */ + $data = base64_decode($node->nodeValue); + + try { + if ($this->version >= 210) { + $securedKey = Crypt::unlockSecuredKey($node->getAttribute('key'), $this->importParams->getImportPwd()); + $xmlDecrypted = Crypt::decrypt($data, $securedKey, $this->importParams->getImportPwd()); + } else { + $xmlDecrypted = OldCrypt::getDecrypt($data, base64_decode($node->getAttribute('iv'), $this->importParams->getImportPwd())); + } + } catch (CryptoException $e) { + processException($e); + + continue; + } + + $newXmlData = new \DOMDocument(); +// $newXmlData->preserveWhiteSpace = true; + if (!$newXmlData->loadXML($xmlDecrypted)) { + throw new ImportException(__u('Clave de encriptación incorrecta')); + } + + $newNode = $this->xmlDOM->importNode($newXmlData->documentElement, TRUE); + + $this->xmlDOM->documentElement->appendChild($newNode); + } + + // Eliminar los datos encriptados tras desencriptar los mismos + if ($this->xmlDOM->getElementsByTagName('Data')->length > 0) { + $nodeData = $this->xmlDOM->getElementsByTagName('Encrypted')->item(0); + $nodeData->parentNode->removeChild($nodeData); + } + } + + /** + * Obtener las categorías y añadirlas a sysPass. + * + * @throws ImportException + */ + protected function processCategories() + { + $this->getNodesData('Categories', 'Category', + function (\DOMElement $category) { + $categoryData = new CategoryData(); + + foreach ($category->childNodes as $node) { + if (isset($node->tagName)) { + switch ($node->tagName) { + case 'name': + $categoryData->setName($node->nodeValue); + break; + case 'description': + $categoryData->setDescription($node->nodeValue); + break; + } + } + } + + try { + $this->categories[$category->getAttribute('id')] = $this->addCategory($categoryData); + } catch (\Exception $e) { + processException($e); + } + }); + } + + /** + * Obtener los clientes y añadirlos a sysPass. + * + * @throws ImportException + */ + protected function processClients() + { + $this->getNodesData('Clients', 'Client', + function (\DOMElement $client) { + $clientData = new ClientData(); + + foreach ($client->childNodes as $node) { + if (isset($node->tagName)) { + switch ($node->tagName) { + case 'name': + $clientData->setName($node->nodeValue); + break; + case 'description': + $clientData->setDescription($node->nodeValue); + break; + } + } + } + + try { + + $this->clients[$client->getAttribute('id')] = $this->addClient($clientData); + } catch (\Exception $e) { + processException($e); + } + }); + } + + /** + * Obtener los clientes y añadirlos a sysPass. + * + * @throws ImportException + * @deprecated + */ + protected function processCustomers() + { + $this->getNodesData('Customers', 'Customer', + function (\DOMElement $client) { + $clientData = new ClientData(); + + foreach ($client->childNodes as $node) { + if (isset($node->tagName)) { + switch ($node->tagName) { + case 'name': + $clientData->setName($node->nodeValue); + break; + case 'description': + $clientData->setDescription($node->nodeValue); + break; + } + } + } + + try { + + $this->clients[$client->getAttribute('id')] = $this->addClient($clientData); + } catch (\Exception $e) { + processException($e); + } + }); + } + + /** + * Obtener las etiquetas y añadirlas a sysPass. + * + * @throws ImportException + */ + protected function processTags() + { + $this->getNodesData('Tags', 'Tag', + function (\DOMElement $tag) { + $tagData = new TagData(); + + foreach ($tag->childNodes as $node) { + if (isset($node->tagName)) { + switch ($node->tagName) { + case 'name': + $tagData->setName($node->nodeValue); + break; + } + } + } + + try { + $this->tags[$tag->getAttribute('id')] = $this->addTag($tagData); + } catch (\Exception $e) { + processException($e); + } + }, false); + } + + /** + * Obtener los datos de las cuentas de sysPass y crearlas. + * + * @throws ImportException + */ + protected function processAccounts() + { + $this->getNodesData('Accounts', 'Account', + function (\DOMElement $account) { + $accountRequest = new AccountRequest(); + + /** @var \DOMElement $node */ + foreach ($account->childNodes as $node) { + if (isset($node->tagName)) { + switch ($node->tagName) { + case 'name'; + $accountRequest->name = $node->nodeValue; + break; + case 'login'; + $accountRequest->login = $node->nodeValue; + break; + case 'categoryId'; + $accountRequest->categoryId = $this->categories[(int)$node->nodeValue]; + break; + case 'clientId'; + case 'customerId'; + $accountRequest->clientId = $this->clients[(int)$node->nodeValue]; + break; + case 'url'; + $accountRequest->url = $node->nodeValue; + break; + case 'pass'; + $accountRequest->pass = $node->nodeValue; + break; + case 'key'; + $accountRequest->key = $node->nodeValue; + break; + case 'notes'; + $accountRequest->notes = $node->nodeValue; + break; + case 'tags': + $accountRequest->tags = $this->processAccountTags($node->childNodes); + } + } + } + + try { + $this->addAccount($accountRequest); + } catch (\Exception $e) { + processException($e); + } + }); + } + + /** + * Procesar las etiquetas de la cuenta + * + * @param \DOMNodeList $nodes + * @return array + */ + protected function processAccountTags(\DOMNodeList $nodes) + { + $tags = []; + + if ($nodes->length > 0) { + /** @var \DOMElement $node */ + foreach ($nodes as $node) { + if (isset($node->tagName)) { + $tags[] = $node->getAttribute('id'); + } + } + } + + return $tags; + } +} \ No newline at end of file diff --git a/lib/SP/Import/XmlFileImport.php b/lib/SP/Services/Import/XmlFileImport.php similarity index 79% rename from lib/SP/Import/XmlFileImport.php rename to lib/SP/Services/Import/XmlFileImport.php index 4f61c834..04597766 100644 --- a/lib/SP/Import/XmlFileImport.php +++ b/lib/SP/Services/Import/XmlFileImport.php @@ -22,9 +22,7 @@ * along with sysPass. If not, see . */ -namespace SP\Import; - -use SP\Core\Exceptions\SPException; +namespace SP\Services\Import; /** * Class XmlFileImport @@ -36,7 +34,7 @@ class XmlFileImport /** * @var FileImport */ - protected $FileImport; + protected $fileImport; /** * @var \DOMDocument */ @@ -45,17 +43,17 @@ class XmlFileImport /** * XmlFileImport constructor. * - * @param FileImport $FileImport + * @param FileImport $fileImport */ - public function __construct(FileImport $FileImport) + public function __construct(FileImport $fileImport) { - $this->FileImport = $FileImport; + $this->fileImport = $fileImport; } /** * Detectar la aplicación que generó el XML. * - * @throws SPException + * @throws ImportException */ public function detectXMLFormat() { @@ -80,8 +78,10 @@ class XmlFileImport break; } } else { - throw new SPException( - __('Archivo XML no soportado', false), SPException::CRITICAL, __('No es posible detectar la aplicación que exportó los datos', false) + throw new ImportException( + __u('Archivo XML no soportado'), + ImportException::ERROR, + __u('No es posible detectar la aplicación que exportó los datos') ); } @@ -91,16 +91,18 @@ class XmlFileImport /** * Leer el archivo a un objeto XML. * - * @throws SPException + * @throws ImportException */ protected function readXMLFile() { // Cargar el XML con DOM $this->xmlDOM = new \DOMDocument(); - if ($this->xmlDOM->load($this->FileImport->getTmpFile()) === false) { - throw new SPException( - __('Error interno', false), SPException::CRITICAL, __('No es posible procesar el archivo XML', false) + if ($this->xmlDOM->load($this->fileImport->getTmpFile()) === false) { + throw new ImportException( + __u('Error interno'), + ImportException::ERROR, + __u('No es posible procesar el archivo XML') ); } } @@ -112,7 +114,7 @@ class XmlFileImport */ protected function parseFileHeader() { - $handle = @fopen($this->FileImport->getTmpFile(), 'r'); + $handle = @fopen($this->fileImport->getTmpFile(), 'r'); $headersRegex = '/(KEEPASSX_DATABASE|revelationdata)/i'; if ($handle) { diff --git a/lib/SP/Import/XmlImport.php b/lib/SP/Services/Import/XmlImport.php similarity index 50% rename from lib/SP/Import/XmlImport.php rename to lib/SP/Services/Import/XmlImport.php index 99949b42..4909e447 100644 --- a/lib/SP/Import/XmlImport.php +++ b/lib/SP/Services/Import/XmlImport.php @@ -22,9 +22,7 @@ * along with sysPass. If not, see . */ -namespace SP\Import; - -use SP\Core\Messages\LogMessage; +namespace SP\Services\Import; defined('APP_ROOT') || die(); @@ -39,75 +37,66 @@ class XmlImport implements ImportInterface /** * @var FileImport */ - protected $File; + protected $xmlFileImport; /** * @var ImportParams */ - protected $ImportParams; - /** - * @var LogMessage - */ - protected $LogMessage; - /** - * @var ImportBase - */ - protected $Import; + protected $importParams; /** * XmlImport constructor. * - * @param FileImport $File - * @param ImportParams $ImportParams - * @param LogMessage $LogMessage + * @param XmlFileImport $xmlFileImport + * @param ImportParams $importParams */ - public function __construct(FileImport $File, ImportParams $ImportParams, LogMessage $LogMessage) + public function __construct(XmlFileImport $xmlFileImport, ImportParams $importParams) { - $this->File = $File; - $this->ImportParams = $ImportParams; - $this->LogMessage = $LogMessage; + $this->xmlFileImport = $xmlFileImport; + $this->importParams = $importParams; } /** * Iniciar la importación desde XML. * - * @throws \SP\Core\Exceptions\SPException + * @throws ImportException + * @throws \Psr\Container\ContainerExceptionInterface + * @throws \Psr\Container\NotFoundExceptionInterface + * @return ImportInterface */ public function doImport() { - $XmlFileImport = new XmlFileImport($this->File); + $format = $this->xmlFileImport->detectXMLFormat(); - $format = $XmlFileImport->detectXMLFormat(); - - switch ($format) { - case 'syspass': - $this->Import = new SyspassImport(); - break; - case 'keepass': - $this->Import = new KeepassImport(); - break; - case 'keepassx': - $this->Import = new KeepassXImport(); - break; - default: - return; - } - - $this->Import->setImportParams($this->ImportParams); - $this->Import->setXmlDOM($XmlFileImport->getXmlDOM()); - $this->Import->setLogMessage($this->LogMessage); - - $this->LogMessage->addDescription(sprintf(__('Formato detectado: %s'), mb_strtoupper($format))); - - $this->Import->doImport(); +// $this->LogMessage->addDescription(sprintf(__('Formato detectado: %s'), mb_strtoupper($format))); + return $this->selectImportType($format)->doImport(); } /** - * Devolver el contador de objetos importados - * - * @return int + * @param $format + * @return KeepassImport|KeepassXImport|SyspassImport + * @throws ImportException + * @throws \Psr\Container\ContainerExceptionInterface + * @throws \Psr\Container\NotFoundExceptionInterface + */ + protected function selectImportType($format) + { + switch ($format) { + case 'syspass': + return new SyspassImport($this->xmlFileImport, $this->importParams); + case 'keepass': + return new KeepassImport($this->xmlFileImport, $this->importParams); + case 'keepassx': + return new KeepassXImport($this->xmlFileImport, $this->importParams); + } + + throw new ImportException(__u('Formato no detectado')); + } + + /** + * @throws ImportException */ public function getCounter() { - return $this->Import->getCounter(); + throw new ImportException(__u('Not implemented')); } } \ No newline at end of file diff --git a/lib/SP/Services/Import/XmlImportBase.php b/lib/SP/Services/Import/XmlImportBase.php new file mode 100644 index 00000000..f60fb0d2 --- /dev/null +++ b/lib/SP/Services/Import/XmlImportBase.php @@ -0,0 +1,109 @@ +. + */ + +namespace SP\Services\Import; + +use SP\Bootstrap; +use SP\Services\Account\AccountService; +use SP\Services\Category\CategoryService; +use SP\Services\Client\ClientService; +use SP\Services\Tag\TagService; + +/** + * Class XmlImportBase + * + * @package SP\Services\Import + */ +abstract class XmlImportBase +{ + use ImportTrait; + + /** + * @var XmlFileImport + */ + protected $xmlFileImport; + /** + * @var \DOMDocument + */ + protected $xmlDOM; + + /** + * ImportBase constructor. + * + * @param XmlFileImport $xmlFileImport + * @param ImportParams $importParams + * @throws \Psr\Container\ContainerExceptionInterface + * @throws \Psr\Container\NotFoundExceptionInterface + */ + public function __construct(XmlFileImport $xmlFileImport, ImportParams $importParams) + { + $this->xmlFileImport = $xmlFileImport; + $this->importParams = $importParams; + $this->xmlDOM = $xmlFileImport->getXmlDOM(); + + $dic = Bootstrap::getContainer(); + $this->accountService = $dic->get(AccountService::class); + $this->categoryService = $dic->get(CategoryService::class); + $this->clientService = $dic->get(ClientService::class); + $this->tagService = $dic->get(TagService::class); + } + + /** + * Obtener los datos de los nodos + * + * @param string $nodeName Nombre del nodo principal + * @param string $childNodeName Nombre de los nodos hijos + * @param callable $callback Método a ejecutar + * @param bool $required Indica si el nodo es requerido + * @throws ImportException + */ + protected function getNodesData($nodeName, $childNodeName, $callback, $required = true) + { + $nodeList = $this->xmlDOM->getElementsByTagName($nodeName); + + if ($nodeList->length === 0) { + if ($required === true) { + throw new ImportException( + __u('Formato de XML inválido'), + ImportException::WARNING, + sprintf(__('El nodo "%s" no existe'), $nodeName) + ); + } + + return; + } + + if (!is_callable($callback)) { + throw new ImportException(__u('Método inválido'), ImportException::WARNING); + } + + /** @var \DOMElement $nodes */ + foreach ($nodeList as $nodes) { + /** @var \DOMElement $Account */ + foreach ($nodes->getElementsByTagName($childNodeName) as $node) { + $callback($node); + } + } + } +} \ No newline at end of file diff --git a/lib/SP/Import/XmlImportTrait.php b/lib/SP/Services/Import/XmlImportTrait.php similarity index 83% rename from lib/SP/Import/XmlImportTrait.php rename to lib/SP/Services/Import/XmlImportTrait.php index 7ddecd91..f6343892 100644 --- a/lib/SP/Import/XmlImportTrait.php +++ b/lib/SP/Services/Import/XmlImportTrait.php @@ -22,7 +22,7 @@ * along with sysPass. If not, see . */ -namespace SP\Import; +namespace SP\Services\Import; use SP\Core\Exceptions\SPException; @@ -55,7 +55,7 @@ trait XmlImportTrait * @param string $childNodeName Nombre de los nodos hijos * @param string $callback Método a ejecutar * @param bool $required Indica si el nodo es requerido - * @throws SPException + * @throws ImportException */ protected function getNodesData($nodeName, $childNodeName, $callback, $required = true) { @@ -63,22 +63,25 @@ trait XmlImportTrait if ($ParentNode->length === 0) { if ($required === true) { - throw new SPException( - __('Formato de XML inválido', false), SPException::WARNING, sprintf(__('El nodo "%s" no existe'), $nodeName)); + throw new ImportException( + __u('Formato de XML inválido'), + SPException::WARNING, + sprintf(__('El nodo "%s" no existe'), $nodeName) + ); } return; } if (!is_callable([$this, $callback])) { - throw new SPException(__('Método inválido', false), SPException::WARNING); + throw new ImportException(__u('Método inválido'), SPException::WARNING); } /** @var \DOMElement $nodes */ foreach ($ParentNode as $nodes) { /** @var \DOMElement $Account */ - foreach ($nodes->getElementsByTagName($childNodeName) as $Node) { - $this->$callback($Node); + foreach ($nodes->getElementsByTagName($childNodeName) as $node) { + $this->$callback($node); } } } diff --git a/lib/SP/Storage/DbWrapper.php b/lib/SP/Storage/DbWrapper.php index a75c14f3..5e6317e3 100644 --- a/lib/SP/Storage/DbWrapper.php +++ b/lib/SP/Storage/DbWrapper.php @@ -188,8 +188,8 @@ class DbWrapper * @param QueryData $queryData Los datos para realizar la consulta * @param DatabaseInterface $db * @return bool - * @throws ConstraintException * @throws QueryException + * @throws ConstraintException */ public static function getQuery(QueryData $queryData, DatabaseInterface $db = null) { @@ -220,7 +220,13 @@ class DbWrapper switch ($e->getCode()) { case 23000: - throw new ConstraintException(__u('Restricción de integridad'), SPException::ERROR, $e->getMessage(), $e->getCode()); + throw new ConstraintException( + __u('Restricción de integridad'), + SPException::ERROR, + $e->getMessage(), + $e->getCode(), + $e + ); } throw new QueryException($errorMessage, SPException::ERROR, $e->getMessage(), $e->getCode()); @@ -240,6 +246,7 @@ class DbWrapper * * @param DatabaseInterface $db * @return bool + * @throws SPException */ public static function beginTransaction(DatabaseInterface $db) { @@ -253,6 +260,7 @@ class DbWrapper * * @param DatabaseInterface $db * @return bool + * @throws SPException */ public static function endTransaction(DatabaseInterface $db) { @@ -266,6 +274,7 @@ class DbWrapper * * @param DatabaseInterface $db * @return bool + * @throws SPException */ public static function rollbackTransaction(DatabaseInterface $db) { diff --git a/public/js/app-main.js b/public/js/app-main.js index 5e3e47a6..ec887ecd 100644 --- a/public/js/app-main.js +++ b/public/js/app-main.js @@ -368,7 +368,8 @@ sysPass.Main = function () { return requestData; }, beforeSendAction: "", - url: "" + url: "", + allowedExts: [] }; // Subir un archivo @@ -418,10 +419,8 @@ sysPass.Main = function () { }; const checkFileExtension = function (name) { - const file_exts_ok = $obj.data("files-ext").toLowerCase().split(","); - - for (let i = 0; i <= file_exts_ok.length; i++) { - if (name.indexOf(file_exts_ok[i]) !== -1) { + for (let ext in options.allowedExts) { + if (name.indexOf(options.allowedExts[ext]) !== -1){ return true; } } @@ -440,7 +439,7 @@ sysPass.Main = function () { const file = filesArray[i]; if (checkFileSize(file.size)) { msg.error(config.LANG[18] + "
" + file.name + " (Max: " + config.MAX_FILE_SIZE + ")"); - } else if (!checkFileExtension(file.name)) { + } else if (!checkFileExtension(file.name.toUpperCase())) { msg.error(config.LANG[19] + "
" + file.name); } else { sendFile(filesArray[i]); @@ -557,7 +556,9 @@ sysPass.Main = function () { COOKIES_ENABLED: false, PLUGINS: [], LOGGEDIN: false, - AUTHBASIC_AUTOLOGIN: false + AUTHBASIC_AUTOLOGIN: false, + FILES_ALLOWED_EXTS: "", + IMPORT_ALLOWED_EXTS: [] }; // Atributos del generador de claves @@ -791,6 +792,8 @@ sysPass.Main = function () { config.PLUGINS = json.plugins; config.LOGGEDIN = json.loggedin; config.AUTHBASIC_AUTOLOGIN = json.authbasic_autologin; + config.IMPORT_ALLOWED_EXTS = json.import_allowed_exts; + config.FILES_ALLOWED_EXTS = json.files_allowed_exts; Object.freeze(config); }); diff --git a/public/js/app-main.min.js b/public/js/app-main.min.js index cff0f6f3..a133040a 100644 --- a/public/js/app-main.min.js +++ b/public/js/app-main.min.js @@ -1,25 +1,25 @@ -var $jscomp={scope:{},findInternal:function(b,k,l){b instanceof String&&(b=String(b));for(var m=b.length,n=0;n]*>([\S\s]*?)<\/script>/gmi,""),g=g.replace(/<\/?\w(?:[^"'>]|"[^"]*"|'[^']*')*>/gmi,""),a.innerHTML=g,g=a.textContent, -a.textContent="");return g}}(),l=function(a){f.info("resizeImage");var g=.9*$(window).width(),d=.9*$(window).height(),b={width:a.width(),height:a.height()},c={calc:0,main:0,secondary:0,factor:.9,rel:b.width/b.height},e=function(a){a.main>a.secondary?a.calc=a.main/a.rel:a.maina.secondary&&(a.main*=a.factor,e(a));return a},D=function(){c.main=g;c.secondary=d;var h=e(c);a.css({width:h.main,height:h.calc});b.width=h.main;b.height=h.calc},E=function(){c.main= -d;c.secondary=g;var h=e(c);a.css({width:h.calc,height:h.main});b.width=h.calc;b.height=h.main};b.width>g?D():b.height>d&&(f.info("height"),E());return b},m=function(a,b){f.info("Eval: "+a);if("function"===typeof a)a(b);else throw Error("Function not found: "+a);},n=function(){f.info("bindPassEncrypt");$("body").on("blur",":input[type=password]",function(a){a=$(this);a.hasClass("passwordfield__no-pki")||z(a)}).on("keypress",":input[type=password]",function(a){13===a.keyCode&&(a.preventDefault(),a= +sysPass.Main=function(){var b=function(){f.info("checkPluginUpdates");for(var a in t)"function"===typeof t[a].checkVersion&&t[a].checkVersion().then(function(a){0===a.status&&void 0!==a.data.plugin&&e.info(String.format(c.LANG[67],a.data.plugin,a.data.remoteVersion))})},l=function(){var a=document.createElement("div");return function(g){g&&"string"===typeof g&&(g=g.replace(/]*>([\S\s]*?)<\/script>/gmi,""),g=g.replace(/<\/?\w(?:[^"'>]|"[^"]*"|'[^']*')*>/gmi,""),a.innerHTML=g,g=a.textContent, +a.textContent="");return g}}(),h=function(a){f.info("resizeImage");var g=.9*$(window).width(),d=.9*$(window).height(),b={width:a.width(),height:a.height()},c={calc:0,main:0,secondary:0,factor:.9,rel:b.width/b.height},e=function(a){a.main>a.secondary?a.calc=a.main/a.rel:a.maina.secondary&&(a.main*=a.factor,e(a));return a},D=function(){c.main=g;c.secondary=d;var k=e(c);a.css({width:k.main,height:k.calc});b.width=k.main;b.height=k.calc},E=function(){c.main= +d;c.secondary=g;var k=e(c);a.css({width:k.calc,height:k.main});b.width=k.calc;b.height=k.main};b.width>g?D():b.height>d&&(f.info("height"),E());return b},m=function(a,b){f.info("Eval: "+a);if("function"===typeof a)a(b);else throw Error("Function not found: "+a);},n=function(){f.info("bindPassEncrypt");$("body").on("blur",":input[type=password]",function(a){a=$(this);a.hasClass("passwordfield__no-pki")||z(a)}).on("keypress",":input[type=password]",function(a){13===a.keyCode&&(a.preventDefault(),a= $(this),z(a),a.closest("form").submit())})},w=function(){f.info("initializeClipboard");if(clipboard.isSupported())$("body").on("click",".clip-pass-button",function(){var a=p.account.copyPass($(this)).done(function(a){if(0!==a.status)return e.out(a),!1;x.set(a.csrf)});!1!==a&&clipboard.copy(a.responseJSON.data.accpass).then(function(){e.ok(c.LANG[45])},function(a){e.error(c.LANG[46])})}).on("click",".dialog-clip-button",function(){var a=$(this.dataset.clipboardTarget);clipboard.copy(a.text()).then(function(){$(".dialog-text").removeClass("dialog-clip-copy"); -a.addClass("dialog-clip-copy")},function(a){e.error(c.LANG[46])})}).on("click",".clip-pass-icon",function(){var a=$(this.dataset.clipboardTarget);clipboard.copy(k(a.val())).then(function(){e.ok(c.LANG[45])},function(a){e.error(c.LANG[46])})});else f.warn(c.LANG[65])},z=function(a){f.info("encryptFormValue");var b=a.val();""!==b&&parseInt(a.attr("data-length"))!==b.length&&(b=c.CRYPT.encrypt(b),a.val(b),a.attr("data-length",b.length))},C=function(a,b){f.info("outputResult");var d=$(".passLevel-"+b.attr("id")), +a.addClass("dialog-clip-copy")},function(a){e.error(c.LANG[46])})}).on("click",".clip-pass-icon",function(){var a=$(this.dataset.clipboardTarget);clipboard.copy(l(a.val())).then(function(){e.ok(c.LANG[45])},function(a){e.error(c.LANG[46])})});else f.warn(c.LANG[65])},z=function(a){f.info("encryptFormValue");var b=a.val();""!==b&&parseInt(a.attr("data-length"))!==b.length&&(b=c.CRYPT.encrypt(b),a.val(b),a.attr("data-length",b.length))},C=function(a,b){f.info("outputResult");var d=$(".passLevel-"+b.attr("id")), g=a.score;d.show();d.removeClass("weak good strong strongest");0===u.passLength?d.attr("title","").empty():u.passLengthc.MAX_FILE_SIZE)e.error(c.LANG[18]+ -"
"+g.name+" (Max: "+c.MAX_FILE_SIZE+")");else{var h;a:{h=g.name;for(var f=a.data("files-ext").toLowerCase().split(","),k=0;k<=f.length;k++)if(-1!==h.indexOf(f[k])){h=!0;break a}h=!1}h?l(b[d]):e.error(c.LANG[19]+"
"+g.name)}}};window.File&&window.FileList&&window.FileReader?function(){f.info("fileUpload:init");var d=b(!1);a.on("dragover dragenter",function(a){f.info("fileUpload:drag");a.stopPropagation();a.preventDefault()});a.on("drop",function(a){f.info("fileUpload:drop");a.stopPropagation(); -a.preventDefault();"function"===typeof h.beforeSendAction&&h.beforeSendAction();k(a.originalEvent.dataTransfer.files)});a.on("click",function(){d.click()})}():b(!0);return h},A=function(a){window.location.replace(a)},I=function(){f.info("checkLogout");return"login/logout"===parseInt(H("r"))?(e.sticky(c.LANG[61],function(){A("index.php?r=login")}),!0):!1},J=function(){$("html, body").animate({scrollTop:0},"slow")},K=function(){var a=$("#container");a.hasClass("content-no-auto-resize")||a.css("height", -$("#content").height()+200)},x={get:function(){f.info("sk:get");return $("#container").attr("data-sk")},set:function(a){f.info("sk:set");f.debug(a);$("#container").attr("data-sk",a)}},c={APP_ROOT:"",LANG:[],PK:"",MAX_FILE_SIZE:1024,CRYPT:new JSEncrypt,CHECK_UPDATES:!1,TIMEZONE:"",LOCALE:"",DEBUG:"",COOKIES_ENABLED:!1,PLUGINS:[],LOGGEDIN:!1,AUTHBASIC_AUTOLOGIN:!1},u={passLength:0,minPasswordLength:8,complexity:{chars:!0,numbers:!0,symbols:!0,uppercase:!0,numlength:12}};Object.seal(u);var B={},q={}, -p={},v={},t={},y={},r={},f={log:function(a){!0===c.DEBUG&&console.log(a)},info:function(a){!0===c.DEBUG&&console.info(a)},error:function(a){console.error(a)},warn:function(a){console.warn(a)},debug:function(a){!0===c.DEBUG&&console.debug(a)}};Object.freeze(f);toastr.options={closeButton:!0,debug:!1,newestOnTop:!1,progressBar:!1,positionClass:"toast-top-center",preventDuplicates:!1,onclick:null,showDuration:"300",hideDuration:"1000",timeOut:"5000",extendedTimeOut:"1000",showEasing:"swing",hideEasing:"linear", -showMethod:"fadeIn",hideMethod:"fadeOut"};var L=function(){f.info("setupCallbacks");var a=$("#container").data("page");if(""!==a&&"function"===typeof q.views[a])q.views[a]();0<$("footer").length&&q.views.footer();$("#btnBack").click(function(){A("index.php")});q.bodyHooks()},e={ok:function(a){toastr.success(a)},error:function(a){toastr.error(a)},warn:function(a){toastr.warning(a)},info:function(a){toastr.info(a)},sticky:function(a,b){var d={timeOut:0};"function"===typeof b&&(d.onHidden=b);toastr.warning(a, -c.LANG[60],d)},out:function(a){if("object"===typeof a){var b=a.status,d=a.description;void 0!==a.messages&&0"+a.messages.join("
"));switch(b){case 0:e.ok(d);break;case 1:e.error(d);break;case 2:e.warn(d);break;case 10:p.main.logout();break;case 100:e.ok(d);e.sticky(d);break;case 101:e.error(d);e.sticky(d);break;case 102:e.warn(d);e.sticky(d);break;default:e.error(d)}}},html:{error:function(a){return'

Oops...
'+c.LANG[1]+"
"+a+"

"}}}; -Object.freeze(e);String.format||(String.format=function(a){var b=Array.prototype.slice.call(arguments,1);return a.replace(/{(\d+)}/g,function(a,c){return"undefined"!==typeof b[c]?b[c]:a})});var M=function(){f.info("getEnvironment");var a=window.location.pathname.split("/");c.APP_ROOT=window.location.protocol+"//"+window.location.host+function(){for(var b="",c=1;c<=a.length-2;c++)b+="/"+a[c];return b}();var b=v.getRequestOpts();b.url="/index.php?r=bootstrap/getEnvironment";b.method="get";b.useLoading= -!1;b.data={isAjax:1};return v.getActionCall(b,function(a){c.LANG=a.lang;c.PK=a.pk;c.CHECK_UPDATES=a.check_updates;c.CRYPT.setPublicKey(a.pk);c.TIMEZONE=a.timezone;c.LOCALE=a.locale;c.DEBUG=a.debug;c.MAX_FILE_SIZE=parseInt(a.max_file_size);c.COOKIES_ENABLED=a.cookies_enabled;c.PLUGINS=a.plugins;c.LOGGEDIN=a.loggedin;c.AUTHBASIC_AUTOLOGIN=a.authbasic_autologin;Object.freeze(c)})},H=function(a){for(var b=[],c,e=window.location.href.slice(window.location.href.indexOf("?")+1).split("&"),f=0;fc.MAX_FILE_SIZE)e.error(c.LANG[18]+ +"
"+d.name+" (Max: "+c.MAX_FILE_SIZE+")");else{var g;a:{g=d.name.toUpperCase();var f=void 0;for(f in k.allowedExts)if(-1!==g.indexOf(k.allowedExts[f])){g=!0;break a}g=!1}g?h(a[b]):e.error(c.LANG[19]+"
"+d.name)}}};window.File&&window.FileList&&window.FileReader?function(){f.info("fileUpload:init");var d=b(!1);a.on("dragover dragenter",function(a){f.info("fileUpload:drag");a.stopPropagation();a.preventDefault()});a.on("drop",function(a){f.info("fileUpload:drop");a.stopPropagation();a.preventDefault(); +"function"===typeof k.beforeSendAction&&k.beforeSendAction();l(a.originalEvent.dataTransfer.files)});a.on("click",function(){d.click()})}():b(!0);return k},A=function(a){window.location.replace(a)},I=function(){f.info("checkLogout");return"login/logout"===parseInt(H("r"))?(e.sticky(c.LANG[61],function(){A("index.php?r=login")}),!0):!1},J=function(){$("html, body").animate({scrollTop:0},"slow")},K=function(){var a=$("#container");a.hasClass("content-no-auto-resize")||a.css("height",$("#content").height()+ +200)},x={get:function(){f.info("sk:get");return $("#container").attr("data-sk")},set:function(a){f.info("sk:set");f.debug(a);$("#container").attr("data-sk",a)}},c={APP_ROOT:"",LANG:[],PK:"",MAX_FILE_SIZE:1024,CRYPT:new JSEncrypt,CHECK_UPDATES:!1,TIMEZONE:"",LOCALE:"",DEBUG:"",COOKIES_ENABLED:!1,PLUGINS:[],LOGGEDIN:!1,AUTHBASIC_AUTOLOGIN:!1,FILES_ALLOWED_EXTS:"",IMPORT_ALLOWED_EXTS:[]},u={passLength:0,minPasswordLength:8,complexity:{chars:!0,numbers:!0,symbols:!0,uppercase:!0,numlength:12}};Object.seal(u); +var B={},q={},p={},v={},t={},y={},r={},f={log:function(a){!0===c.DEBUG&&console.log(a)},info:function(a){!0===c.DEBUG&&console.info(a)},error:function(a){console.error(a)},warn:function(a){console.warn(a)},debug:function(a){!0===c.DEBUG&&console.debug(a)}};Object.freeze(f);toastr.options={closeButton:!0,debug:!1,newestOnTop:!1,progressBar:!1,positionClass:"toast-top-center",preventDuplicates:!1,onclick:null,showDuration:"300",hideDuration:"1000",timeOut:"5000",extendedTimeOut:"1000",showEasing:"swing", +hideEasing:"linear",showMethod:"fadeIn",hideMethod:"fadeOut"};var L=function(){f.info("setupCallbacks");var a=$("#container").data("page");if(""!==a&&"function"===typeof q.views[a])q.views[a]();0<$("footer").length&&q.views.footer();$("#btnBack").click(function(){A("index.php")});q.bodyHooks()},e={ok:function(a){toastr.success(a)},error:function(a){toastr.error(a)},warn:function(a){toastr.warning(a)},info:function(a){toastr.info(a)},sticky:function(a,b){var d={timeOut:0};"function"===typeof b&&(d.onHidden= +b);toastr.warning(a,c.LANG[60],d)},out:function(a){if("object"===typeof a){var b=a.status,d=a.description;void 0!==a.messages&&0"+a.messages.join("
"));switch(b){case 0:e.ok(d);break;case 1:e.error(d);break;case 2:e.warn(d);break;case 10:p.main.logout();break;case 100:e.ok(d);e.sticky(d);break;case 101:e.error(d);e.sticky(d);break;case 102:e.warn(d);e.sticky(d);break;default:e.error(d)}}},html:{error:function(a){return'

Oops...
'+c.LANG[1]+ +"
"+a+"

"}}};Object.freeze(e);String.format||(String.format=function(a){var b=Array.prototype.slice.call(arguments,1);return a.replace(/{(\d+)}/g,function(a,c){return"undefined"!==typeof b[c]?b[c]:a})});var M=function(){f.info("getEnvironment");var a=window.location.pathname.split("/");c.APP_ROOT=window.location.protocol+"//"+window.location.host+function(){for(var b="",c=1;c<=a.length-2;c++)b+="/"+a[c];return b}();var b=v.getRequestOpts();b.url="/index.php?r=bootstrap/getEnvironment";b.method= +"get";b.useLoading=!1;b.data={isAjax:1};return v.getActionCall(b,function(a){c.LANG=a.lang;c.PK=a.pk;c.CHECK_UPDATES=a.check_updates;c.CRYPT.setPublicKey(a.pk);c.TIMEZONE=a.timezone;c.LOCALE=a.locale;c.DEBUG=a.debug;c.MAX_FILE_SIZE=parseInt(a.max_file_size);c.COOKIES_ENABLED=a.cookies_enabled;c.PLUGINS=a.plugins;c.LOGGEDIN=a.loggedin;c.AUTHBASIC_AUTOLOGIN=a.authbasic_autologin;c.IMPORT_ALLOWED_EXTS=a.import_allowed_exts;c.FILES_ALLOWED_EXTS=a.files_allowed_exts;Object.freeze(c)})},H=function(a){for(var b= +[],c,e=window.location.href.slice(window.location.href.indexOf("?")+1).split("&"),f=0;f 0) { const upload = Common.fileUpload($dropFiles); - upload.url = Common.appActions().ajaxUrl.config.import; + upload.url = Common.appActions().ajaxUrl.entrypoint + "?r=" + $dropFiles.data("action-route"); + upload.allowedExts = Common.config().IMPORT_ALLOWED_EXTS; upload.beforeSendAction = function () { upload.setRequestData({ sk: Common.sk.get(), diff --git a/public/js/app-triggers.min.js b/public/js/app-triggers.min.js index 59c3c3f0..9bd8dfc2 100644 --- a/public/js/app-triggers.min.js +++ b/public/js/app-triggers.min.js @@ -1,17 +1,17 @@ var $jscomp={scope:{},findInternal:function(b,d,f){b instanceof String&&(b=String(b));for(var a=b.length,c=0;cform").each(function(){var a= -$(this);a.find("button.btn-clear").on("click",function(b){b.preventDefault();a.trigger("reset")})})},config:function(){d.info("views:config");var a=$("#drop-import-files");if(0