From 526ca3c9e9319aff2c0483fff249e6aeffb9ed15 Mon Sep 17 00:00:00 2001 From: nuxsmin Date: Thu, 23 Feb 2017 01:46:51 +0100 Subject: [PATCH] * [MOD] Improved API auth security. There is no need to provide the user's password, it will ask for a token password when generating it. --- inc/Base.php | 6 +- inc/SP/Api/ApiBase.class.php | 85 ++-- inc/SP/Api/ApiTokens.class.php | 314 -------------- inc/SP/Api/ApiTokensUtil.class.php | 122 +----- inc/SP/Api/SyspassApi.class.php | 10 +- inc/SP/Auth/AuthUtil.class.php | 28 -- .../Controller/ItemActionController.class.php | 29 +- .../Controller/ItemListController.class.php | 4 +- .../Controller/ItemSearchController.class.php | 4 +- .../Controller/ItemShowController.class.php | 12 +- inc/SP/DataModel/ApiTokenData.class.php | 230 ++++++++++ inc/SP/Forms/ApiTokenForm.class.php | 30 +- inc/SP/Forms/FormInterface.class.php | 2 +- inc/SP/Mgmt/ApiTokens/ApiToken.class.php | 409 ++++++++++++++++++ inc/SP/Mgmt/ApiTokens/ApiTokenBase.class.php | 66 +++ .../Mgmt/ApiTokens/ApiTokenSearch.class.php | 83 ++++ inc/SP/Storage/DB.class.php | 13 +- inc/sql/20117022101.sql | 4 + inc/themes/material-blue/css/styles.css | 4 +- inc/themes/material-blue/css/styles.min.css | 2 +- inc/themes/material-blue/css/styles.scss | 4 +- inc/themes/material-blue/js/app-theme.js | 18 +- inc/themes/material-blue/js/app-theme.min.js | 18 +- .../material-blue/views/itemshow/tokens.inc | 32 +- 24 files changed, 950 insertions(+), 579 deletions(-) delete mode 100644 inc/SP/Api/ApiTokens.class.php create mode 100644 inc/SP/DataModel/ApiTokenData.class.php create mode 100644 inc/SP/Mgmt/ApiTokens/ApiToken.class.php create mode 100644 inc/SP/Mgmt/ApiTokens/ApiTokenBase.class.php create mode 100644 inc/SP/Mgmt/ApiTokens/ApiTokenSearch.class.php diff --git a/inc/Base.php b/inc/Base.php index 89fe7003..511a5ea6 100644 --- a/inc/Base.php +++ b/inc/Base.php @@ -81,7 +81,11 @@ function debugLog($data, $printLastCaller = false) for ($i = 1; $i <= $n - 1; $i++) { $class = isset($backtrace[$i]['class']) ? $backtrace[$i]['class'] : ''; - error_log(sprintf('Caller %d: %s\%s', $i, $class, $backtrace[$i]['function'])); + $line = sprintf('Caller %d: %s\%s', $i, $class, $backtrace[$i]['function']); + + if (!error_log($line . PHP_EOL, 3, LOG_FILE)) { + error_log($line); + } } } } diff --git a/inc/SP/Api/ApiBase.class.php b/inc/SP/Api/ApiBase.class.php index 113de794..40da4e0e 100644 --- a/inc/SP/Api/ApiBase.class.php +++ b/inc/SP/Api/ApiBase.class.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. @@ -26,18 +26,18 @@ namespace SP\Api; defined('APP_ROOT') || die(); -use SP\Auth\Auth; -use SP\Auth\AuthResult; -use SP\Auth\AuthUtil; +use Defuse\Crypto\Exception\CryptoException; +use SP\Core\Crypt\Crypt; +use SP\Core\Crypt\Hash; use SP\Core\Exceptions\InvalidArgumentException; use SP\Core\Exceptions\SPException; use SP\Core\Session; use SP\Core\SessionUtil; -use SP\DataModel\UserData; +use SP\DataModel\ApiTokenData; use SP\DataModel\UserLoginData; use SP\Log\Log; +use SP\Mgmt\ApiTokens\ApiToken; use SP\Mgmt\Users\User; -use SP\Mgmt\Users\UserPass; use SP\Util\Json; /** @@ -71,10 +71,6 @@ abstract class ApiBase implements ApiInterface * @var mixed */ protected $data; - /** - * @var string - */ - protected $mPass = ''; /** * @var UserLoginData */ @@ -83,6 +79,10 @@ abstract class ApiBase implements ApiInterface * @var Log */ protected $Log; + /** + * @var ApiTokenData + */ + protected $ApiTokenData; /** * @param $data @@ -91,18 +91,19 @@ abstract class ApiBase implements ApiInterface public function __construct($data) { $this->actionId = $this->getActionId($data->method); + $this->ApiTokenData = ApiToken::getItem()->getTokenByToken($this->actionId, $data->params->authToken); - if (!AuthUtil::checkAuthToken($this->actionId, $data->params->authToken)) { + if ($this->ApiTokenData === false) { throw new SPException(SPException::SP_CRITICAL, __('Acceso no permitido', false)); } $this->data = $data; - $this->userId = ApiTokensUtil::getUserIdForToken($data->params->authToken); + $this->userId = $this->ApiTokenData->getAuthtokenUserId(); $this->loadUserData(); - if ($this->getParam('userPass') !== null) { + if ($this->getParam('pass') !== null) { $this->doAuth(); } @@ -127,17 +128,11 @@ abstract class ApiBase implements ApiInterface /** * Cargar los datos del usuario * - * @return UserData - * @throws \SP\Core\Exceptions\InvalidClassException * @throws \SP\Core\Exceptions\SPException */ protected function loadUserData() { - $UserData = new UserData(); - $UserData->setUserId($this->userId); - $UserData->setUserPass($this->getParam('userPass')); - - $this->UserData = User::getItem($UserData)->getById($this->userId); + $this->UserData = User::getItem()->getById($this->ApiTokenData->getAuthtokenUserId()); SessionUtil::loadUserSession($this->UserData); } @@ -145,9 +140,9 @@ abstract class ApiBase implements ApiInterface /** * Devolver el valor de un parámetro * - * @param string $name Nombre del parámetro - * @param bool $required Si es requerido - * @param mixed $default Valor por defecto + * @param string $name Nombre del parámetro + * @param bool $required Si es requerido + * @param mixed $default Valor por defecto * @return int|string * @throws SPException */ @@ -168,36 +163,30 @@ abstract class ApiBase implements ApiInterface * Realizar la autentificación del usuario * * @throws SPException - * @throws \SP\Core\Exceptions\InvalidClassException - * @throws \Defuse\Crypto\Exception\BadFormatException - * @throws \Defuse\Crypto\Exception\CryptoException - * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException */ protected function doAuth() { - $Auth = new Auth($this->UserData); - $resAuth = $Auth->doAuth(); - - if ($resAuth !== false) { - /** @var AuthResult $AuthResult */ - foreach ($resAuth as $AuthResult) { - $data = $AuthResult->getData(); - - if ($data->getAuthenticated() && $data->getStatusCode() === 0) { - break; - } - } - } else { + if ($this->UserData->isUserIsDisabled() + || !Hash::checkHashKey($this->getParam('pass'), $this->ApiTokenData->getAuthtokenHash()) + ) { throw new SPException(SPException::SP_CRITICAL, __('Acceso no permitido', false)); } + } - if (!$this->UserData->isUserIsDisabled() - && UserPass::loadUserMPass($this->UserData) === UserPass::MPASS_OK - ) { - $this->auth = true; - $this->mPass = UserPass::getClearUserMPass(); - } else { - throw new SPException(SPException::SP_CRITICAL, __('Acceso no permitido', false)); + /** + * Devolver la clave maestra + * + * @return string + * @throws SPException + */ + protected function getMPass() + { + try { + $key = $this->getParam('pass') . $this->getParam('authToken'); + $securedKey = Crypt::unlockSecuredKey($this->ApiTokenData->getAuthtokenKey(), $key); + return Crypt::decrypt($this->ApiTokenData->getAuthtokenPass(), $securedKey, $key); + } catch (CryptoException $e) { + throw new SPException(SPException::SP_ERROR, __('Error interno', false), $e->getMessage()); } } diff --git a/inc/SP/Api/ApiTokens.class.php b/inc/SP/Api/ApiTokens.class.php deleted file mode 100644 index bf06a0e1..00000000 --- a/inc/SP/Api/ApiTokens.class.php +++ /dev/null @@ -1,314 +0,0 @@ -. - */ - -namespace SP\Api; - -defined('APP_ROOT') || die(); - -use SP\Core\Exceptions\SPException; -use SP\Core\Session; -use SP\Storage\DB; -use SP\Storage\QueryData; - -/** - * Class ApiTokens para la gestión de autorizaciones de acceso a la API de sysPass - * - * @package SP - */ -class ApiTokens -{ - /** - * @var int - */ - private $tokenId = 0; - /** - * @var int - */ - private $userId = 0; - /** - * @var int - */ - private $actionId = 0; - /** - * @var string - */ - private $token = ''; - /** - * @var bool - */ - private $refreshToken = false; - - /** - * @param boolean $refreshToken - */ - public function setRefreshToken($refreshToken) - { - $this->refreshToken = $refreshToken; - } - - /** - * Añadir un nuevo token - * - * @throws SPException - */ - public function addToken() - { - $this->checkTokenExist(); - - if ($this->refreshToken) { - $this->refreshToken(); - } - - $query = /** @lang SQL */ - 'INSERT INTO authTokens - SET authtoken_userId = :userid, - authtoken_actionId = :actionid, - authtoken_createdBy = :createdby, - authtoken_token = :token, - authtoken_startDate = UNIX_TIMESTAMP()'; - - $Data = new QueryData(); - $Data->setQuery($query); - $Data->addParam($this->userId, 'userid'); - $Data->addParam($this->actionId, 'actionid'); - $Data->addParam(Session::getUserData()->getUserId(), 'createdby'); - $Data->addParam($this->getUserToken() ? $this->token : $this->generateToken(), 'token'); - $Data->setOnErrorMessage(__('Error interno', false)); - - DB::getQuery($Data); - } - - /** - * Comprobar si el token ya existe - * - * @return bool - * @throws SPException - */ - private function checkTokenExist() - { - $query = /** @lang SQL */ - 'SELECT authtoken_id FROM authTokens - WHERE authtoken_userId = :userid - AND authtoken_actionId = :actionid - AND authtoken_id <> :id LIMIT 1'; - - $Data = new QueryData(); - $Data->setQuery($query); - $Data->addParam($this->tokenId, 'id'); - $Data->addParam($this->userId, 'userid'); - $Data->addParam($this->actionId, 'actionid'); - - try { - DB::getResults($Data); - } catch (SPException $e) { - throw new SPException(SPException::SP_CRITICAL, __('Error interno', false)); - } - - if ($Data->getQueryNumRows() === 1) { - throw new SPException(SPException::SP_WARNING, __('La autorización ya existe', false)); - } - } - - /** - * Regenerar el hash de los tokens de un usuario - * - * @throws SPException - */ - private function refreshToken() - { - $query = /** @lang SQL */ - 'UPDATE authTokens SET - authtoken_token = :token, - authtoken_startDate = UNIX_TIMESTAMP() - WHERE authtoken_userId = :userid'; - - $Data = new QueryData(); - $Data->setQuery($query); - $Data->addParam($this->userId, 'userid'); - $Data->addParam($this->generateToken(), 'token'); - $Data->setOnErrorMessage(__('Error interno', false)); - - DB::getQuery($Data); - } - - /** - * Generar un token de acceso - * - * @return string - */ - private function generateToken() - { - return sha1(uniqid('sysPass-API', true) . time()); - } - - /** - * Obtener el token de la API de un usuario - * - * @return bool - * @throws SPException - */ - private function getUserToken() - { - $query = /** @lang SQL */ - 'SELECT authtoken_token FROM authTokens WHERE authtoken_userId = :userid LIMIT 1'; - - $Data = new QueryData(); - $Data->setQuery($query); - $Data->addParam($this->userId, 'userid'); - - try { - $queryRes = DB::getResults($Data); - } catch (SPException $e) { - throw new SPException(SPException::SP_CRITICAL, __('Error interno', false)); - } - - if ($Data->getQueryNumRows() === 0) { - return false; - } - - $this->token = $queryRes->authtoken_token; - - return true; - } - - /** - * Actualizar un token - * - * @throws \SP\Core\Exceptions\SPException - */ - public function updateToken() - { - $this->checkTokenExist(); - - if ($this->refreshToken) { - $this->refreshToken(); - } - - $query = /** @lang SQL */ - 'UPDATE authTokens - SET authtoken_userId = :userid, - authtoken_actionId = :actionid, - authtoken_createdBy = :createdby, - authtoken_token = :token, - authtoken_startDate = UNIX_TIMESTAMP() - WHERE authtoken_id = :id LIMIT 1'; - - $Data = new QueryData(); - $Data->setQuery($query); - $Data->addParam($this->tokenId, 'id'); - $Data->addParam($this->userId, 'userid'); - $Data->addParam($this->actionId, 'actionid'); - $Data->addParam(Session::getUserData()->getUserId(), 'createdby'); - $Data->addParam($this->getUserToken() ? $this->token : $this->generateToken(), 'token'); - $Data->setOnErrorMessage(__('Error interno', false)); - - DB::getQuery($Data); - } - - /** - * Eliminar token - * - * @param $id - */ - public function deleteToken($id) - { - $query = /** @lang SQL */ - 'DELETE FROM authTokens WHERE authtoken_id = ? LIMIT 1'; - - $Data = new QueryData(); - $Data->setQuery($query); - $Data->addParam($id); - $Data->setOnErrorMessage(__('Error interno', false)); - - DB::getQuery($Data); - } - - - /** - * Eliminar token - * - * @param array $ids - * @throws \SP\Core\Exceptions\ConstraintException - */ - public function deleteTokenBatch(array $ids) - { - $query = /** @lang SQL */ - 'DELETE FROM authTokens WHERE authtoken_id IN (' . implode(',', array_fill(0, count($ids), '?')) . ')'; - - $Data = new QueryData(); - $Data->setQuery($query); - $Data->setParams($ids); - $Data->setOnErrorMessage(__('Error interno', false)); - - DB::getQuery($Data); - } - - /** - * @return int - */ - public function getUserId() - { - return $this->userId; - } - - /** - * @param int $userId - */ - public function setUserId($userId) - { - $this->userId = $userId; - } - - /** - * @return int - */ - public function getTokenId() - { - return $this->tokenId; - } - - /** - * @param int $tokenId - */ - public function setTokenId($tokenId) - { - $this->tokenId = $tokenId; - } - - /** - * @return int - */ - public function getActionId() - { - return $this->actionId; - } - - /** - * @param int $actionId - */ - public function setActionId($actionId) - { - $this->actionId = $actionId; - } -} \ No newline at end of file diff --git a/inc/SP/Api/ApiTokensUtil.class.php b/inc/SP/Api/ApiTokensUtil.class.php index 724735fb..b00bfbe4 100644 --- a/inc/SP/Api/ApiTokensUtil.class.php +++ b/inc/SP/Api/ApiTokensUtil.class.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. @@ -26,10 +26,6 @@ namespace SP\Api; use SP\Core\Acl; use SP\Core\ActionsInterface; -use SP\Core\Exceptions\SPException; -use SP\DataModel\ItemSearchData; -use SP\Storage\DB; -use SP\Storage\QueryData; defined('APP_ROOT') || die(); @@ -40,92 +36,6 @@ defined('APP_ROOT') || die(); */ class ApiTokensUtil { - /** - * Obtener los tokens de la API - * - * @param int $tokenId opcional, con el Id del token a consultar - * @param bool $returnRawData Devolver la consulta tal cual - * @return array|object con la lista de tokens - */ - public static function getTokens($tokenId = null, $returnRawData = false) - { - $query = 'SELECT authtoken_id,' . - 'authtoken_userId,' . - 'authtoken_actionId, ' . - 'authtoken_token, ' . - 'user_login ' . - 'FROM authTokens ' . - 'LEFT JOIN usrData ON user_id = authtoken_userId '; - - $Data = new QueryData(); - - if (null !== $tokenId) { - $query .= 'WHERE authtoken_id = ? LIMIT 1'; - $Data->addParam($tokenId); - } else { - $query .= 'ORDER BY user_login'; - } - - $Data->setQuery($query); - - if (!$returnRawData) { - $queryRes = DB::getResultsArray($Data); - - foreach ($queryRes as &$token) { - $token->authtoken_actionId = Acl::getActionName($token->authtoken_actionId); - } - } else { - $queryRes = DB::getResults($Data); - } - - return $queryRes; - } - - /** - * Obtener los tokens de la API de una búsqueda - * @param ItemSearchData $SearchData - * @return array|object con la lista de tokens - */ - public static function getTokensMgmtSearch(ItemSearchData $SearchData) - { - $query = 'SELECT authtoken_id,' . - 'authtoken_userId,' . - 'authtoken_actionId, ' . - 'authtoken_token, ' . - 'user_login ' . - 'FROM authTokens ' . - 'LEFT JOIN usrData ON user_id = authtoken_userId '; - - $Data = new QueryData(); - - if ($SearchData->getSeachString() !== '') { - $search = '%' . $SearchData->getSeachString() . '%'; - $query .= ' WHERE user_login LIKE ?'; - - $Data->addParam($search); - } - - $query .= ' ORDER BY user_login'; - $query .= ' LIMIT ?, ?'; - - $Data->addParam($SearchData->getLimitStart()); - $Data->addParam($SearchData->getLimitCount()); - - $Data->setQuery($query); - - DB::setFullRowCount(); - - $queryRes = DB::getResultsArray($Data); - - foreach ($queryRes as &$token) { - $token->authtoken_actionId = Acl::getActionName($token->authtoken_actionId); - } - - $queryRes['count'] = $Data->getQueryNumRows(); - - return $queryRes; - } - /** * Devuelver un array de acciones posibles para los tokens * @@ -146,32 +56,4 @@ class ApiTokensUtil return $actions; } - - /** - * Obtener el usuario a partir del token - * - * @param $token string El token de autorización - * @return bool|mixed - * @throws \SP\Core\Exceptions\SPException - */ - public static function getUserIdForToken($token) - { - $query = 'SELECT authtoken_userId FROM authTokens WHERE authtoken_token = ? LIMIT 1'; - - $Data = new QueryData(); - $Data->setQuery($query); - $Data->addParam($token); - - try { - $queryRes = DB::getResults($Data); - } catch (SPException $e) { - throw new SPException(SPException::SP_CRITICAL, __('Error interno', false)); - } - - if ($Data->getQueryNumRows() === 0) { - return false; - } - - return $queryRes->authtoken_userId; - } } \ No newline at end of file diff --git a/inc/SP/Api/SyspassApi.class.php b/inc/SP/Api/SyspassApi.class.php index 619a90c8..76f17579 100644 --- a/inc/SP/Api/SyspassApi.class.php +++ b/inc/SP/Api/SyspassApi.class.php @@ -88,11 +88,12 @@ class SyspassApi extends ApiBase $LogMessage->addDetails(__('Origen', false), 'API'); $this->Log->writeLog(); - $securedKey = Crypt::unlockSecuredKey($AccountData->getAccountKey(), $this->mPass); + $mPass = $this->getMPass(); + $securedKey = Crypt::unlockSecuredKey($AccountData->getAccountKey(), $mPass); $ret = [ 'itemId' => $accountId, - 'pass' => Crypt::decrypt($AccountData->getAccountPass(), $securedKey) + 'pass' => Crypt::decrypt($AccountData->getAccountPass(), $securedKey, $mPass) ]; if ($this->getParam('details', false, 0)) { @@ -158,6 +159,11 @@ class SyspassApi extends ApiBase * Añadir una nueva cuenta * * @return string La cadena en formato JSON + * @throws \SP\Core\Exceptions\QueryException + * @throws \SP\Core\Exceptions\ConstraintException + * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException + * @throws \Defuse\Crypto\Exception\CryptoException + * @throws \Defuse\Crypto\Exception\BadFormatException * @throws \SP\Core\Exceptions\SPException */ public function addAccount() diff --git a/inc/SP/Auth/AuthUtil.class.php b/inc/SP/Auth/AuthUtil.class.php index e44237ba..56f67dbb 100644 --- a/inc/SP/Auth/AuthUtil.class.php +++ b/inc/SP/Auth/AuthUtil.class.php @@ -31,8 +31,6 @@ use SP\DataModel\UserPassRecoverData; use SP\Html\Html; use SP\Log\Email; use SP\Mgmt\Users\UserPassRecover; -use SP\Storage\DB; -use SP\Storage\QueryData; use SP\Util\Util; /** @@ -79,32 +77,6 @@ class AuthUtil return false; } - /** - * Comprobar el token de seguridad - * - * @param $actionId int El id de la accion - * @param $token string El token de seguridad - * @return bool - * @throws \SP\Core\Exceptions\SPException - */ - public static function checkAuthToken($actionId, $token) - { - $query = /** @lang SQL */ - 'SELECT authtoken_id - FROM authTokens - WHERE authtoken_actionId = ? - AND authtoken_token = ? LIMIT 1'; - - $Data = new QueryData(); - $Data->setQuery($query); - $Data->addParam($actionId); - $Data->addParam($token); - - DB::getQuery($Data); - - return $Data->getQueryNumRows() === 1; - } - /** * Devuelve el typo de autentificación del servidor web * diff --git a/inc/SP/Controller/ItemActionController.class.php b/inc/SP/Controller/ItemActionController.class.php index 0785394d..608b482c 100644 --- a/inc/SP/Controller/ItemActionController.class.php +++ b/inc/SP/Controller/ItemActionController.class.php @@ -29,7 +29,6 @@ use SP\Account\AccountFavorites; use SP\Account\AccountHistory; use SP\Account\AccountHistoryUtil; use SP\Account\AccountUtil; -use SP\Api\ApiTokens; use SP\Auth\AuthUtil; use SP\Core\ActionsInterface; use SP\Core\Messages\LogMessage; @@ -51,6 +50,7 @@ use SP\Forms\UserForm; use SP\Http\Request; use SP\Log\Email; use SP\Log\Log; +use SP\Mgmt\ApiTokens\ApiToken; use SP\Mgmt\Categories\Category; use SP\Mgmt\Customers\Customer; use SP\Mgmt\CustomFields\CustomField; @@ -593,37 +593,48 @@ class ItemActionController implements ItemControllerInterface * @throws \SP\Core\Exceptions\SPException * @throws \phpmailer\phpmailerException * @throws \SP\Core\Exceptions\ConstraintException + * @throws \SP\Core\Exceptions\QueryException */ protected function tokenAction() { $Form = new ApiTokenForm($this->itemId); + $refresh = Request::analyze('refreshtoken', false, false, true); + switch ($this->actionId) { case ActionsInterface::ACTION_MGM_APITOKENS_NEW: $Form->validate($this->actionId); - $Form->getItemData()->addToken(); + + if ($refresh === true) { + ApiToken::getItem($Form->getItemData())->refreshToken()->add(); + } else { + ApiToken::getItem($Form->getItemData())->add(); + } $this->LogMessage->setAction(__('Crear Autorización', false)); $this->LogMessage->addDescription(__('Autorización creada', false)); - $this->LogMessage->addDetails(__('Usuario', false), UserUtil::getUserLoginById($Form->getItemData()->getUserId())); + $this->LogMessage->addDetails(__('Usuario', false), UserUtil::getUserLoginById($Form->getItemData()->getAuthtokenUserId())); break; case ActionsInterface::ACTION_MGM_APITOKENS_EDIT: $Form->validate($this->actionId); - $Form->getItemData()->updateToken(); + + if ($refresh === true) { + ApiToken::getItem($Form->getItemData())->refreshToken()->update(); + } else { + ApiToken::getItem($Form->getItemData())->update(); + } $this->LogMessage->setAction(__('Actualizar Autorización', false)); $this->LogMessage->addDescription(__('Autorización actualizada', false)); - $this->LogMessage->addDetails(__('Usuario', false), UserUtil::getUserLoginById($Form->getItemData()->getUserId())); + $this->LogMessage->addDetails(__('Usuario', false), UserUtil::getUserLoginById($Form->getItemData()->getAuthtokenUserId())); break; case ActionsInterface::ACTION_MGM_APITOKENS_DELETE: - $ApiToken = new ApiTokens(); - if (is_array($this->itemId)) { - $ApiToken->deleteTokenBatch($this->itemId); + ApiToken::getItem()->deleteBatch($this->itemId); $this->LogMessage->addDescription(__('Autorizaciones eliminadas', false)); } else { - $ApiToken->deleteToken($this->itemId); + ApiToken::getItem()->delete($this->itemId); $this->LogMessage->addDescription(__('Autorización eliminada', false)); } diff --git a/inc/SP/Controller/ItemListController.class.php b/inc/SP/Controller/ItemListController.class.php index c9e0c6fb..94451963 100644 --- a/inc/SP/Controller/ItemListController.class.php +++ b/inc/SP/Controller/ItemListController.class.php @@ -36,6 +36,8 @@ use SP\Core\Exceptions\SPException; use SP\Core\Template; use SP\DataModel\ItemSearchData; use SP\Http\Request; +use SP\Mgmt\ApiTokens\ApiToken; +use SP\Mgmt\ApiTokens\ApiTokenSearch; use SP\Mgmt\Categories\CategorySearch; use SP\Mgmt\Customers\CustomerSearch; use SP\Mgmt\CustomFields\CustomFieldDefSearch; @@ -377,7 +379,7 @@ class ItemListController extends GridTabControllerBase implements ActionsInterfa } $Grid = $this->getGrids()->getTokensGrid(); - $Grid->getData()->setData(ApiTokensUtil::getTokensMgmtSearch($this->ItemSearchData)); + $Grid->getData()->setData(ApiTokenSearch::getItem()->getMgmtSearch($this->ItemSearchData)); $Grid->updatePager(); $this->view->append('tabs', $Grid); diff --git a/inc/SP/Controller/ItemSearchController.class.php b/inc/SP/Controller/ItemSearchController.class.php index 979c68f4..22c25f93 100644 --- a/inc/SP/Controller/ItemSearchController.class.php +++ b/inc/SP/Controller/ItemSearchController.class.php @@ -36,6 +36,7 @@ use SP\Core\SessionUtil; use SP\Core\Template; use SP\DataModel\ItemSearchData; use SP\Http\Request; +use SP\Mgmt\ApiTokens\ApiTokenSearch; use SP\Mgmt\Categories\CategorySearch; use SP\Mgmt\Customers\CustomerSearch; use SP\Mgmt\CustomFields\CustomFieldDefSearch; @@ -248,6 +249,7 @@ class ItemSearchController extends GridItemsSearchController implements ActionsI * Obtener los tokens API de una búsqueda * * @throws \InvalidArgumentException + * @throws \SP\Core\Exceptions\InvalidArgumentException */ public function getTokens() { @@ -260,7 +262,7 @@ class ItemSearchController extends GridItemsSearchController implements ActionsI $this->view->addTemplate('datagrid-table', 'grid'); $Grid = $this->getGrids()->getTokensGrid(); - $Grid->getData()->setData(ApiTokensUtil::getTokensMgmtSearch($this->ItemSearchData)); + $Grid->getData()->setData(ApiTokenSearch::getItem()->getMgmtSearch($this->ItemSearchData)); $Grid->updatePager(); $this->updatePager($Grid->getPager(), $this->ItemSearchData); diff --git a/inc/SP/Controller/ItemShowController.class.php b/inc/SP/Controller/ItemShowController.class.php index 182a6a18..fe373626 100644 --- a/inc/SP/Controller/ItemShowController.class.php +++ b/inc/SP/Controller/ItemShowController.class.php @@ -39,6 +39,7 @@ use SP\Core\Session; use SP\Core\SessionUtil; use SP\Core\Template; use SP\DataModel\AccountExtData; +use SP\DataModel\ApiTokenData; use SP\DataModel\CategoryData; use SP\DataModel\CustomerData; use SP\DataModel\CustomFieldData; @@ -51,6 +52,7 @@ use SP\DataModel\UserPassData; use SP\Http\Request; use SP\Log\Email; use SP\Log\Log; +use SP\Mgmt\ApiTokens\ApiToken; use SP\Mgmt\Categories\Category; use SP\Mgmt\Customers\Customer; use SP\Mgmt\CustomFields\CustomField; @@ -66,6 +68,7 @@ use SP\Mgmt\PublicLinks\PublicLink; use SP\Mgmt\Tags\Tag; use SP\Mgmt\Users\User; use SP\Mgmt\Users\UserPass; +use SP\Mgmt\Users\UserUtil; use SP\Util\Checks; use SP\Util\ImageUtil; use SP\Util\Json; @@ -392,18 +395,19 @@ class ItemShowController extends ControllerBase implements ActionsInterface, Ite $this->module = self::ACTION_MGM_APITOKENS; $this->view->addTemplate('tokens'); - $token = ApiTokensUtil::getTokens($this->itemId, true); + $ApiTokenData = $this->itemId ? ApiToken::getItem()->getById($this->itemId) : new ApiTokenData(); $this->view->assign('users', User::getItem()->getItemsForSelect()); $this->view->assign('actions', ApiTokensUtil::getTokenActions()); - $this->view->assign('token', $token); - $this->view->assign('gotData', is_object($token)); + $this->view->assign('ApiTokenData', $ApiTokenData); + $this->view->assign('isDisabled', ($this->view->actionId === self::ACTION_MGM_APITOKENS_VIEW) ? 'disabled' : ''); + $this->view->assign('isReadonly', $this->view->isDisabled ? 'readonly' : ''); if ($this->view->isView === true) { $Log = Log::newLog(__('Autorizaciones', false)); $LogMessage = $Log->getLogMessage(); $LogMessage->addDescription(__('Token de autorización visualizado')); - $LogMessage->addDetails(__('Usuario'), $token->user_login); + $LogMessage->addDetails(__('Usuario'), UserUtil::getUserLoginById($ApiTokenData->authtoken_userId)); $Log->writeLog(); Email::sendEmail($LogMessage); diff --git a/inc/SP/DataModel/ApiTokenData.class.php b/inc/SP/DataModel/ApiTokenData.class.php new file mode 100644 index 00000000..413559de --- /dev/null +++ b/inc/SP/DataModel/ApiTokenData.class.php @@ -0,0 +1,230 @@ +. + */ + +namespace SP\DataModel; + +/** + * Class ApiTokenData + * + * @package SP\DataModel + */ +class ApiTokenData extends DataModelBase implements DataModelInterface +{ + /** + * @var int + */ + public $authtoken_id; + /** + * @var string + */ + public $authtoken_key; + /** + * @var string + */ + public $authtoken_pass; + /** + * @var int + */ + public $authtoken_userId; + /** + * @var string + */ + public $authtoken_token = ''; + /** + * @var int + */ + public $authtoken_createdBy; + /** + * @var int + */ + public $authtoken_startDate; + /** + * @var int + */ + public $authtoken_actionId; + /** + * @var string + */ + public $authtoken_hash; + + /** + * @return int + */ + public function getAuthtokenId() + { + return (int)$this->authtoken_id; + } + + /** + * @param int $authtoken_id + */ + public function setAuthtokenId($authtoken_id) + { + $this->authtoken_id = (int)$authtoken_id; + } + + /** + * @return mixed + */ + public function getAuthtokenKey() + { + return $this->authtoken_key; + } + + /** + * @param mixed $authtoken_key + */ + public function setAuthtokenKey($authtoken_key) + { + $this->authtoken_key = $authtoken_key; + } + + /** + * @return int + */ + public function getAuthtokenUserId() + { + return (int)$this->authtoken_userId; + } + + /** + * @param int $authtoken_userId + */ + public function setAuthtokenUserId($authtoken_userId) + { + $this->authtoken_userId = (int)$authtoken_userId; + } + + /** + * @return string + */ + public function getAuthtokenToken() + { + return $this->authtoken_token; + } + + /** + * @param string $authtoken_token + */ + public function setAuthtokenToken($authtoken_token) + { + $this->authtoken_token = $authtoken_token; + } + + /** + * @return int + */ + public function getAuthtokenCreatedBy() + { + return (int)$this->authtoken_createdBy; + } + + /** + * @param int $authtoken_createdBy + */ + public function setAuthtokenCreatedBy($authtoken_createdBy) + { + $this->authtoken_createdBy = (int)$authtoken_createdBy; + } + + /** + * @return int + */ + public function getAuthtokenStartDate() + { + return (int)$this->authtoken_startDate; + } + + /** + * @param int $authtoken_startDate + */ + public function setAuthtokenStartDate($authtoken_startDate) + { + $this->authtoken_startDate = (int)$authtoken_startDate; + } + + /** + * @return int + */ + public function getId() + { + return (int)$this->authtoken_id; + } + + /** + * @return string + */ + public function getName() + { + return ''; + } + + /** + * @return int + */ + public function getAuthtokenActionId() + { + return (int)$this->authtoken_actionId; + } + + /** + * @param int $authtoken_actionId + */ + public function setAuthtokenActionId($authtoken_actionId) + { + $this->authtoken_actionId = (int)$authtoken_actionId; + } + + /** + * @return string + */ + public function getAuthtokenHash() + { + return $this->authtoken_hash; + } + + /** + * @param string $authtoken_hash + */ + public function setAuthtokenHash($authtoken_hash) + { + $this->authtoken_hash = $authtoken_hash; + } + + /** + * @return string + */ + public function getAuthtokenPass() + { + return $this->authtoken_pass; + } + + /** + * @param string $authtoken_pass + */ + public function setAuthtokenPass($authtoken_pass) + { + $this->authtoken_pass = $authtoken_pass; + } +} \ No newline at end of file diff --git a/inc/SP/Forms/ApiTokenForm.class.php b/inc/SP/Forms/ApiTokenForm.class.php index c1393ee1..5f0fdd65 100644 --- a/inc/SP/Forms/ApiTokenForm.class.php +++ b/inc/SP/Forms/ApiTokenForm.class.php @@ -24,9 +24,9 @@ namespace SP\Forms; -use SP\Api\ApiTokens; use SP\Core\ActionsInterface; use SP\Core\Exceptions\ValidationException; +use SP\DataModel\ApiTokenData; use SP\Http\Request; /** @@ -37,15 +37,15 @@ use SP\Http\Request; class ApiTokenForm extends FormBase implements FormInterface { /** - * @var ApiTokens + * @var ApiTokenData */ - protected $ApiTokens; + protected $ApiTokenData; /** * Validar el formulario * * @param $action - * @return bool + * @return ApiTokenForm * @throws \SP\Core\Exceptions\ValidationException */ public function validate($action) @@ -58,7 +58,7 @@ class ApiTokenForm extends FormBase implements FormInterface break; } - return true; + return $this; } /** @@ -68,11 +68,11 @@ class ApiTokenForm extends FormBase implements FormInterface */ protected function analyzeRequestData() { - $this->ApiTokens = new ApiTokens(); - $this->ApiTokens->setTokenId($this->itemId); - $this->ApiTokens->setUserId(Request::analyze('users', 0)); - $this->ApiTokens->setActionId(Request::analyze('actions', 0)); - $this->ApiTokens->setRefreshToken(Request::analyze('refreshtoken', false, false, true)); + $this->ApiTokenData = new ApiTokenData(); + $this->ApiTokenData->setAuthtokenId($this->itemId); + $this->ApiTokenData->setAuthtokenUserId(Request::analyze('users', 0)); + $this->ApiTokenData->setAuthtokenActionId(Request::analyze('actions', 0)); + $this->ApiTokenData->setAuthtokenHash(Request::analyzeEncrypted('pass')); } /** @@ -80,18 +80,20 @@ class ApiTokenForm extends FormBase implements FormInterface */ protected function checkCommon() { - if ($this->ApiTokens->getUserId() === 0) { + if ($this->ApiTokenData->getAuthtokenUserId() === 0) { throw new ValidationException(__('Usuario no indicado', false)); - } elseif ($this->ApiTokens->getActionId() === 0) { + } elseif ($this->ApiTokenData->getAuthtokenActionId() === 0) { throw new ValidationException(__('Acción no indicada', false)); + } elseif ($this->ApiTokenData->getAuthtokenKey() === '') { + throw new ValidationException(__('La clave no puede estar en blanco', false)); } } /** - * @return ApiTokens + * @return ApiTokenData */ public function getItemData() { - return $this->ApiTokens; + return $this->ApiTokenData; } } \ No newline at end of file diff --git a/inc/SP/Forms/FormInterface.class.php b/inc/SP/Forms/FormInterface.class.php index c8b36c24..6a8389ac 100644 --- a/inc/SP/Forms/FormInterface.class.php +++ b/inc/SP/Forms/FormInterface.class.php @@ -35,7 +35,7 @@ interface FormInterface * Validar el formulario * * @param $action - * @return bool + * @return FormInterface * @throws \SP\Core\Exceptions\ValidationException */ public function validate($action); diff --git a/inc/SP/Mgmt/ApiTokens/ApiToken.class.php b/inc/SP/Mgmt/ApiTokens/ApiToken.class.php new file mode 100644 index 00000000..58f33cba --- /dev/null +++ b/inc/SP/Mgmt/ApiTokens/ApiToken.class.php @@ -0,0 +1,409 @@ +. + */ + +namespace SP\Mgmt\ApiTokens; + +use SP\Core\Crypt\Crypt; +use SP\Core\Crypt\Hash; +use SP\Core\Crypt\Session as CryptSession; +use SP\Core\Exceptions\SPException; +use SP\Core\Session; +use SP\DataModel\ApiTokenData; +use SP\Mgmt\ItemInterface; +use SP\Mgmt\ItemTrait; +use SP\Storage\DB; +use SP\Storage\QueryData; +use SP\Util\Util; + +/** + * Class ApiToken + * + * @package SP\Mgmt\ApiTokens + */ +class ApiToken extends ApiTokenBase implements ItemInterface +{ + use ItemTrait; + + /** + * @return mixed + * @throws \Defuse\Crypto\Exception\CryptoException + * @throws \SP\Core\Exceptions\SPException + * @throws \SP\Core\Exceptions\QueryException + * @throws \SP\Core\Exceptions\ConstraintException + */ + public function add() + { + if ($this->checkDuplicatedOnAdd()) { + throw new SPException(SPException::SP_WARNING, __('La autorización ya existe', false)); + } + + $token = $this->getTokenByUserId($this->itemData->getAuthtokenUserId()); + $this->setSecureData($token); + + $query = /** @lang SQL */ + 'INSERT INTO authTokens + SET authtoken_userId = ?, + authtoken_actionId = ?, + authtoken_createdBy = ?, + authtoken_token = ?, + authtoken_key = ?, + authtoken_pass = ?, + authtoken_hash = ?, + authtoken_startDate = UNIX_TIMESTAMP()'; + + $Data = new QueryData(); + $Data->setQuery($query); + $Data->addParam($this->itemData->getAuthtokenUserId()); + $Data->addParam($this->itemData->getAuthtokenActionId()); + $Data->addParam(Session::getUserData()->getUserId()); + $Data->addParam($token); + $Data->addParam($this->itemData->getAuthtokenKey()); + $Data->addParam($this->itemData->getAuthtokenPass()); + $Data->addParam(Hash::hashKey($this->itemData->getAuthtokenHash())); + $Data->setOnErrorMessage(__('Error interno', false)); + + DB::getQuery($Data); + + return $this; + } + + /** + * @return bool + * @throws SPException + */ + public function checkDuplicatedOnAdd() + { + $query = /** @lang SQL */ + 'SELECT authtoken_id FROM authTokens + WHERE authtoken_userId = ? + AND authtoken_actionId = ? LIMIT 1'; + + $Data = new QueryData(); + $Data->setQuery($query); + $Data->addParam($this->itemData->getAuthtokenUserId()); + $Data->addParam($this->itemData->getAuthtokenActionId()); + + DB::getResults($Data); + + return $Data->getQueryNumRows() === 1; + } + + /** + * Obtener el token de la API de un usuario + * + * @param $id + * @return bool + */ + private function getTokenByUserId($id) + { + $query = /** @lang SQL */ + 'SELECT authtoken_token FROM authTokens WHERE authtoken_userId = ? LIMIT 1'; + + $Data = new QueryData(); + $Data->setQuery($query); + $Data->addParam($id); + + $queryRes = DB::getResults($Data); + + return $Data->getQueryNumRows() === 1 ? $queryRes->authtoken_token : $this->generateToken(); + } + + /** + * Generar un token de acceso + * + * @return string + */ + private function generateToken() + { + return Util::generateRandomBytes(32); + } + + /** + * @param $id int + * @return $this + * @throws \SP\Core\Exceptions\SPException + */ + public function delete($id) + { + $query = /** @lang SQL */ + 'DELETE FROM authTokens WHERE authtoken_id = ? LIMIT 1'; + + $Data = new QueryData(); + $Data->setQuery($query); + $Data->addParam($id); + $Data->setOnErrorMessage(__('Error interno', false)); + + DB::getQuery($Data); + + if ($Data->getQueryNumRows() === 0) { + throw new SPException(SPException::SP_INFO, __('Token no encontrado', false)); + } + + return $this; + } + + /** + * @return mixed + * @throws \SP\Core\Exceptions\QueryException + * @throws \SP\Core\Exceptions\ConstraintException + * @throws \Defuse\Crypto\Exception\CryptoException + * @throws \SP\Core\Exceptions\SPException + */ + public function update() + { + if ($this->checkDuplicatedOnUpdate()) { + throw new SPException(SPException::SP_WARNING, __('La autorización ya existe', false)); + } + + $token = $this->getTokenByUserId($this->itemData->getAuthtokenUserId()); + $this->setSecureData($token); + + $query = /** @lang SQL */ + 'UPDATE authTokens + SET authtoken_userId = ?, + authtoken_actionId = ?, + authtoken_createdBy = ?, + authtoken_token = ?, + authtoken_key = ?, + authtoken_pass = ?, + authtoken_hash = ?, + authtoken_startDate = UNIX_TIMESTAMP() + WHERE authtoken_id = ? LIMIT 1'; + + $Data = new QueryData(); + $Data->setQuery($query); + $Data->addParam($this->itemData->getAuthtokenUserId()); + $Data->addParam($this->itemData->getAuthtokenActionId()); + $Data->addParam(Session::getUserData()->getUserId()); + $Data->addParam($token); + $Data->addParam($this->itemData->getAuthtokenKey()); + $Data->addParam($this->itemData->getAuthtokenPass()); + $Data->addParam(Hash::hashKey($this->itemData->getAuthtokenHash())); + $Data->addParam($this->itemData->getAuthtokenId()); + $Data->setOnErrorMessage(__('Error interno', false)); + + DB::getQuery($Data); + + return $this; + } + + /** + * @return bool + * @throws \SP\Core\Exceptions\SPException + */ + public function checkDuplicatedOnUpdate() + { + $query = /** @lang SQL */ + 'SELECT authtoken_id FROM authTokens + WHERE authtoken_userId = ? + AND authtoken_actionId = ? + AND authtoken_id <> ? LIMIT 1'; + + $Data = new QueryData(); + $Data->setQuery($query); + $Data->addParam($this->itemData->getAuthtokenUserId()); + $Data->addParam($this->itemData->getAuthtokenActionId()); + $Data->addParam($this->itemData->getAuthtokenId()); + + DB::getResults($Data); + + return $Data->getQueryNumRows() === 1; + } + + /** + * Regenerar el hash de los tokens de un usuario + * + * @throws \SP\Core\Exceptions\ConstraintException + * @throws \SP\Core\Exceptions\QueryException + * @throws \Defuse\Crypto\Exception\CryptoException + */ + public function refreshToken() + { + $token = $this->generateToken(); + $this->setSecureData($token); + + $query = /** @lang SQL */ + 'UPDATE authTokens + SET authtoken_token = ?, + authtoken_hash = ?, + authtoken_key = ?, + authtoken_pass = ?, + authtoken_startDate = UNIX_TIMESTAMP() + WHERE authtoken_userId = ? LIMIT 1'; + + $Data = new QueryData(); + $Data->setQuery($query); + $Data->addParam($this->generateToken()); + $Data->addParam(Hash::hashKey($this->itemData->getAuthtokenHash())); + $Data->addParam($this->itemData->getAuthtokenKey()); + $Data->addParam($this->itemData->getAuthtokenPass()); + $Data->addParam($this->itemData->getAuthtokenUserId()); + $Data->setOnErrorMessage(__('Error interno', false)); + + DB::getQuery($Data); + + return $this; + } + + /** + * Generar la llave segura del token + * + * @param $token + * @throws \Defuse\Crypto\Exception\CryptoException + */ + private function setSecureData($token) + { + $key = $this->itemData->getAuthtokenHash() . $token; + $securedKey = Crypt::makeSecuredKey($key); + + $this->itemData->setAuthtokenKey($securedKey); + $this->itemData->setAuthtokenPass(Crypt::encrypt(CryptSession::getSessionKey(), $securedKey, $key)); + } + + /** + * @param $id int + * @return ApiTokenData + */ + public function getById($id) + { + $query = /** @lang SQL */ + 'SELECT authtoken_id, + authtoken_userId, + authtoken_actionId, + authtoken_createdBy, + authtoken_startDate, + authtoken_token + FROM authTokens + WHERE authtoken_id = ? LIMIT 1'; + + $Data = new QueryData(); + $Data->setMapClassName($this->getDataModel()); + $Data->setQuery($query); + $Data->addParam($id); + + return DB::getResults($Data); + } + + /** + * @return mixed + */ + public function getAll() + { + // TODO: Implement getAll() method. + } + + /** + * @param $id int + * @return mixed + */ + public function checkInUse($id) + { + // TODO: Implement checkInUse() method. + } + + /** + * Eliminar elementos en lote + * + * @param array $ids + * @return $this + * @throws \SP\Core\Exceptions\QueryException + * @throws \SP\Core\Exceptions\ConstraintException + */ + public function deleteBatch(array $ids) + { + $query = /** @lang SQL */ + 'DELETE FROM authTokens WHERE authtoken_id IN (' . $this->getParamsFromArray($ids) . ')'; + + $Data = new QueryData(); + $Data->setQuery($query); + $Data->setParams($ids); + $Data->setOnErrorMessage(__('Error interno', false)); + + DB::getQuery($Data); + + return $this; + } + + /** + * Devolver los elementos con los ids especificados + * + * @param array $ids + * @return mixed + */ + public function getByIdBatch(array $ids) + { + // TODO: Implement getByIdBatch() method. + } + + /** + * Obtener el usuario a partir del token + * + * @param $token string El token de autorización + * @return bool|mixed + * @throws \SP\Core\Exceptions\SPException + */ + public function getUserIdForToken($token) + { + $query = /** @lang SQL */ + 'SELECT authtoken_userId FROM authTokens WHERE authtoken_token = ? LIMIT 1'; + + $Data = new QueryData(); + $Data->setQuery($query); + $Data->addParam($token); + + $queryRes = DB::getResults($Data); + + return $Data->getQueryNumRows() === 1 ? $queryRes->authtoken_userId : false; + } + + /** + * Devolver los datos de un token + * + * @param $actionId int El id de la accion + * @param $token string El token de seguridad + * @return false|ApiTokenData + * @throws \SP\Core\Exceptions\SPException + */ + public function getTokenByToken($actionId, $token) + { + $query = /** @lang SQL */ + 'SELECT authtoken_userId, + authtoken_key, + authtoken_pass, + authtoken_hash + FROM authTokens + WHERE authtoken_actionId = ? + AND authtoken_token = ? LIMIT 1'; + + $Data = new QueryData(); + $Data->setMapClassName($this->getDataModel()); + $Data->setQuery($query); + $Data->addParam($actionId); + $Data->addParam($token); + + $queryRes = DB::getResults($Data); + + return $Data->getQueryNumRows() === 1 ? $queryRes : false; + } +} \ No newline at end of file diff --git a/inc/SP/Mgmt/ApiTokens/ApiTokenBase.class.php b/inc/SP/Mgmt/ApiTokens/ApiTokenBase.class.php new file mode 100644 index 00000000..dcfa6ff3 --- /dev/null +++ b/inc/SP/Mgmt/ApiTokens/ApiTokenBase.class.php @@ -0,0 +1,66 @@ +. + */ + +namespace SP\Mgmt\ApiTokens; + +defined('APP_ROOT') || die(); + +use SP\DataModel\ApiTokenData; +use SP\Mgmt\ItemBase; + +/** + * Class ApiTokensBase + * + * @package SP\Mgmt\ApiTokens + */ +abstract class ApiTokenBase extends ItemBase +{ + /** @var ApiTokenData */ + protected $itemData; + + /** + * ApiTokensBase constructor. + * + * @param $itemData + * @throws \SP\Core\Exceptions\InvalidClassException + */ + public function __construct($itemData = null) + { + if (!$this->dataModel) { + $this->setDataModel(ApiTokenData::class); + } + + parent::__construct($itemData); + } + + /** + * Devolver los datos del elemento + * + * @return ApiTokenData + */ + public function getItemData() + { + return parent::getItemData(); + } +} \ No newline at end of file diff --git a/inc/SP/Mgmt/ApiTokens/ApiTokenSearch.class.php b/inc/SP/Mgmt/ApiTokens/ApiTokenSearch.class.php new file mode 100644 index 00000000..60859649 --- /dev/null +++ b/inc/SP/Mgmt/ApiTokens/ApiTokenSearch.class.php @@ -0,0 +1,83 @@ +. + */ + +namespace SP\Mgmt\ApiTokens; + +use SP\Core\Acl; +use SP\DataModel\ItemSearchData; +use SP\Mgmt\ItemSearchInterface; +use SP\Storage\DB; +use SP\Storage\QueryData; + +/** + * Class ApiTokenSearch + * + * @package SP\Mgmt\ApiTokens + */ +class ApiTokenSearch extends ApiTokenBase implements ItemSearchInterface +{ + /** + * @param ItemSearchData $SearchData + * @return mixed + */ + public function getMgmtSearch(ItemSearchData $SearchData) + { + $query = 'SELECT authtoken_id,' . + 'authtoken_userId,' . + 'authtoken_actionId, ' . + 'authtoken_token, ' . + 'user_login ' . + 'FROM authTokens ' . + 'LEFT JOIN usrData ON user_id = authtoken_userId '; + + $Data = new QueryData(); + + if ($SearchData->getSeachString() !== '') { + $search = '%' . $SearchData->getSeachString() . '%'; + $query .= ' WHERE user_login LIKE ?'; + + $Data->addParam($search); + } + + $query .= ' ORDER BY user_login'; + $query .= ' LIMIT ?, ?'; + + $Data->addParam($SearchData->getLimitStart()); + $Data->addParam($SearchData->getLimitCount()); + + $Data->setQuery($query); + + DB::setFullRowCount(); + + $queryRes = DB::getResultsArray($Data); + + foreach ($queryRes as $token) { + $token->authtoken_actionId = Acl::getActionName($token->authtoken_actionId); + } + + $queryRes['count'] = $Data->getQueryNumRows(); + + return $queryRes; + } +} \ No newline at end of file diff --git a/inc/SP/Storage/DB.class.php b/inc/SP/Storage/DB.class.php index cc15ee4d..d0573649 100644 --- a/inc/SP/Storage/DB.class.php +++ b/inc/SP/Storage/DB.class.php @@ -272,19 +272,18 @@ class DB /** * Método para registar los eventos de BD en el log * - * @param $query string La consulta que genera el error - * @param $errorMsg string El mensaje de error - * @param $errorCode int El código de error - * @param $queryFunction + * @param string $query La consulta que genera el error + * @param \Exception $e + * @param string $queryFunction */ - private static function logDBException($query, $errorMsg, $errorCode, $queryFunction) + private static function logDBException($query, \Exception $e, $queryFunction) { $caller = Util::traceLastCall($queryFunction); $LogMessage = new LogMessage(); $LogMessage->setAction($caller); $LogMessage->addDescription(__('Error en la consulta', false)); - $LogMessage->addDescription(sprintf('%s (%s)', $errorMsg, $errorCode)); + $LogMessage->addDescription(sprintf('%s (%s)', $e->getMessage(), $e->getCode())); $LogMessage->addDetails('SQL', DBUtil::escape($query)); debugLog($LogMessage->getDescription(), true); @@ -345,7 +344,7 @@ class DB } catch (SPException $e) { $queryData->setQueryStatus($e->getCode()); - self::logDBException($queryData->getQuery(), $e->getMessage(), $e->getCode(), __FUNCTION__); + self::logDBException($queryData->getQuery(), $e, __FUNCTION__); if ($e->getCode() === 23000) { throw new ConstraintException(SPException::SP_ERROR, __('Restricción de integridad', false), $e->getMessage(), $e->getCode()); diff --git a/inc/sql/20117022101.sql b/inc/sql/20117022101.sql index 5986b296..d14a12ca 100644 --- a/inc/sql/20117022101.sql +++ b/inc/sql/20117022101.sql @@ -7,4 +7,8 @@ ALTER TABLE `customFieldsData` ALTER TABLE `usrData` CHANGE COLUMN `user_mPass` `user_mPass` VARBINARY(1000) NULL DEFAULT NULL, CHANGE COLUMN `user_mIV` `user_mKey` VARBINARY(1000) NULL DEFAULT NULL; +ALTER TABLE `authTokens` + ADD COLUMN `authtoken_pass` VARBINARY(1000) NULL, + ADD COLUMN `authtoken_key` VARBINARY(1000) NULL, + ADD COLUMN `authtoken_hash` VARBINARY(100) NULL; diff --git a/inc/themes/material-blue/css/styles.css b/inc/themes/material-blue/css/styles.css index a9c7ef83..74d1c980 100644 --- a/inc/themes/material-blue/css/styles.css +++ b/inc/themes/material-blue/css/styles.css @@ -530,8 +530,8 @@ pre, code, samp, kbd { padding: .3em 0; } #box-popup { - min-width: 25em; - max-width: 50em; + min-width: 30em; + max-width: 60em; margin: 5em auto; padding: 0; background-color: #fff; } diff --git a/inc/themes/material-blue/css/styles.min.css b/inc/themes/material-blue/css/styles.min.css index 70a1794a..3b9aca69 100644 --- a/inc/themes/material-blue/css/styles.min.css +++ b/inc/themes/material-blue/css/styles.min.css @@ -1 +1 @@ -html,body{margin:0;padding:0;text-align:left;background-color:#f5f5f5;color:#555;font-size:12px;font-weight:normal;box-sizing:border-box}*{font-family:"Roboto Regular",Verdana,Tahoma,sans-serif;box-sizing:inherit}*:before,*:after{box-sizing:inherit}table{font-size:11px;border-spacing:0}table th{border-bottom:2px solid transparent;vertical-align:middle}table th .icon{width:24px;height:24px}table tr{height:20px}table tr.odd{background-color:#f9f9f9}table tr.even>td,table tr.odd>td{border-bottom:1px solid #d9d9d9!important}table tr.even:hover,table tr.odd:hover{background-color:#e8ff99}table td{padding:3px}table td.txtCliente{font-weight:bold;text-align:center}form{font-size:11px;margin:0}input.inputImg,img.inputImg{background-color:transparent!important;width:24px!important;height:24px!important;border:0;vertical-align:middle;margin:0 .5em}input.txtFile{width:200px}input.txtLong{width:300px}textarea{width:350px;resize:none}select.files{width:250px}input.spinner{width:5em}img{margin:0;padding:0;border:0;cursor:pointer}img.inputImgMini{background-color:transparent!important;width:16px!important;height:16px!important;margin:0 5px 0 5px;border:0;vertical-align:middle}i{cursor:pointer}form .form-field{display:flex;justify-content:space-between}form .form-field>label{min-width:12em;padding:.5em 0;font-size:16px;align-self:center}form .form-field>div{width:100%;align-self:center}a{text-decoration:none;color:#536dfe}a:visited{text-decoration:none;color:#536dfe}a:hover,a:active,a:focus{text-decoration:none;cursor:pointer}pre,code,samp,kbd{font-family:Consolas,"Andale Mono WT","Andale Mono","Bitstream Vera Sans Mono","Nimbus Mono L",Monaco,"Courier New",monospace;font-size:1em;direction:ltr;text-align:left;background-color:#fbfaf9;color:#333;box-shadow:inset 0 0 .3em #ccc;border-radius:2px}#nojs{width:80%;text-align:center;vertical-align:middle;margin:10px auto;padding:3px;background-color:#ef5350;color:white;font-weight:bold;font-size:14px}#wrap{height:auto!important;min-height:100%;width:100%;background-color:#f5f5f5}#wrap-loading{position:fixed;z-index:9999;top:50%;left:50%;padding:1em;background-color:rgba(255,255,255,0.8);display:none;border-radius:5px!important;-moz-border-radius:5px!important;-webkit-border-radius:5px!important}#wrap-loading.overlay-full{top:0;left:0;width:100%;height:100%;background-color:rgba(255,255,255,0.5)}#wrap-loading.overlay-full #loading{position:absolute;top:50%;left:50%}#container{margin:auto;width:100%}#container.login{padding-top:5%}#container.error,#container.install,#container.passreset{width:100%}#container .logo{height:64px}#container #actions-bar{z-index:100;display:flex;justify-content:space-between;position:fixed;border:0 none;top:0;left:0;width:100%;padding:1em 0;background-color:transparent}#container #actions-bar-icons{flex-grow:1;text-align:center}#container #actions-bar-logo{display:none;padding:0 .5em}#container #actions-bar-logo img{display:inline-block;width:50px;opacity:.75}#container #content{width:95%;margin:2em auto 8em auto}#container #content.public-link{width:70%;min-height:0;margin:5em auto}#content td.descField,#box-popup td.descField{text-align:right;padding-right:20px;width:25%;border-right:1px solid #d9d9d9;color:#999;font-size:12px;font-weight:bold}#content td.valField,#box-popup td.valField{padding-left:1em;width:100%}#content td.valField .lowres-title,#box-popup td.valField .lowres-title{display:none;width:100%;color:#607d8b;font-size:12px}#content .pager{width:100%;margin-top:15px;padding:.5em;vertical-align:middle;font-size:11px;color:#999;background-color:#fcfcfc}#content .pager img{margin-left:5px;vertical-align:middle}#content .pager a{margin-left:5px;font-size:12px;color:#999}#content .pager>div{display:inline-block;width:49%}#content .pager .pager-left{text-align:left}#content .pager .pager-right{text-align:right}#content #title{width:50%;padding:7px;margin:auto;background-color:#d9d9d9;color:#fff;font-size:17px;letter-spacing:.3em;text-align:center}#content #title.titleNormal{background-color:#607d8b;color:#fff}#content .data-container{width:75%;margin:0 auto}#content fieldset.data{margin:2em auto}#content fieldset.data>legend{color:#607d8b;padding:0 .5em;font-size:1.5em}#content fieldset.data>div{display:none}#content fieldset.data>div table{width:100%}#content .data{width:100%;padding:10px;border:1px solid #c9c9c9;margin:0 auto;background-color:#f9f9f9}#content .data #history-icon{position:relative;top:5em;right:2em}#content .data td{text-align:left}#content .data td.descField{text-align:right}#content .data select{min-width:210px}#content .data .list-wrap{max-height:10em;overflow:auto;padding:.5em;margin:1em 0}#content .data .dropzone{width:30em;padding:1em;border:2px dashed #26a69a;text-align:center}#content .data .dropzone img{vertical-align:middle}#content .data .file-upload{display:none}#content .data .account-permissions{width:100%}#content .data .account-permissions fieldset{border:1px solid #c9c9c9;padding:1em}#content .data .account-permissions legend{font-weight:bold;color:#999;padding:.2em 0}#content .data .account-permissions fieldset>span{font-weight:bold;color:#999;padding:.2em 0;display:inline-block;width:100px;text-align:right}#content span.tag{margin:0 3px 3px 0;padding:.2em;background:#5c6bc0;color:#fff;border:0 solid transparent;border-radius:3px!important;-moz-border-radius:3px!important;-webkit-border-radius:3px!important}#content .extra-info{margin-top:20px}#content #tabs fieldset{border:1px solid #c9c9c9}#content #tabs #frmConfig label{float:left}#content .tblConfig{margin-bottom:2em}#content .tblConfig td.descField{width:35%;font-size:11px;font-weight:bold}#content .tblConfig td.rowHeader{padding:5px 0 5px 0;background-color:#f5f5f5;text-align:center;font-weight:bold;border-top:15px solid #f9f9f9;border-bottom:3px solid #a9c1d7;letter-spacing:.5em;color:#696969}#content .tblConfig input.checkbox{width:15px;text-align:left;padding:0}#content .tblConfig .option-disabled{text-align:center;background-color:#fff8e1;color:#ffca28;font-weight:bold}#content h2{width:100%;height:1.5em;font-size:18px;color:white;background-color:#a9c1d7;margin:0;padding-top:.1em}#content .section{margin-top:2.5em;border-bottom:1px solid #d9d9d9;text-align:left;font-size:14px;font-weight:bold;color:#5c6bc0}#content .row_even>td{background-color:#f5f5f5}#content .row_odd>td{background-color:white}#content .data-header ul{list-style:none;width:100%;margin:0 0 10px 0;padding:0}#content .data-header li{display:inline-block;padding:.2em .5em;font-weight:bold;letter-spacing:.2em;color:#fff;text-align:center}#content .data-header li a{color:#777}#content .data-header li img{float:right;width:24px;height:24px;vertical-align:middle}#content .data-header-minimal{border-bottom:1px solid #dfdfdf}#content .data-header-minimal ul{display:flex;flex-wrap:wrap;justify-content:flex-start;margin:0}#content .data-header-minimal li{display:inline-flex;min-width:10em;font-weight:normal;letter-spacing:normal}#content .data-header-minimal li a{color:#b9b9b9;padding:.3em .8em}#content .data-table{width:100%}#content .data-table td:first-of-type,#content .data-table th:first-of-type{width:5em}#content .data-table thead th{background-color:#607d8b;color:#fff}#content .data-table tbody td.cell-data{text-align:left}#content .data-table tbody td.cell-nodata{padding:0 .5em;text-align:left}#content .data-table tbody td.cell-actions{text-align:right}#content .data-table tbody td.cell-actions i{opacity:.5}#content .data-table tbody td.cell-actions i:hover{opacity:1}#content .data-rows ul{display:table;list-style:none;width:100%;margin:0 0 10px 0;padding:0;background-color:#fcfcfc}#content .data-rows li{float:left;display:block;padding:1em;color:#696969;text-align:center;min-height:2em}#content .data-rows li.cell-nodata{padding:1em 0;min-height:2em;text-align:left}#content .data-rows li.cell-actions{float:right;min-height:2em;padding:1em 0;text-align:left;background-color:#fcfcfc;width:15em}#content .data-rows li.cell-nodata img,#content .data-rows li.cell-actions img{width:24px;height:24px;margin:0 .5em}#content #resEventLog .data{width:100%}#content #resEventLog thead{text-align:center}#content #resEventLog tbody{width:100%;height:500px;overflow:auto}#content #resEventLog td{border-bottom:1px solid #d9d9d9}#content #resEventLog .cell{text-align:center}#content #resEventLog .cell-description{width:60%}#content #searchbox{background-color:#fcfcfc;vertical-align:middle;position:relative;height:auto;padding:.5em 1em;margin-bottom:2em}#content #searchbox form{display:flex;flex-wrap:wrap;justify-content:flex-start;align-items:center;text-align:left}#content #searchbox .search-filters>*{margin:0 1em}#content #searchbox .search-filters .filter-buttons{display:inline-block}#content #searchbox .search-filters .filter-slider{width:10em}#content #searchbox .search-filters-tags{display:none;flex-grow:2}#content .btn-clear{opacity:.35;filter:alpha(opacity=35)}#content .btn-clear:hover{opacity:1;filter:alpha(opacity=100)}#content .actions-optional{display:none}#content .error{width:350px;padding:15px;margin:0 auto;text-align:center;font-size:16px;line-height:1.5em;color:#ffca28;background-color:#fff8e1;border:1px solid #ffca28}#box-popup .list-wrap{max-height:10em;overflow:auto;padding:.5em;margin:1em 0}#content .data .list-wrap ul,#box-popup .list-wrap ul{list-style-type:none;margin:0;padding:0}#content .data .list-wrap li,#box-popup .list-wrap li{display:flex;background:#f2f2f2;padding:.5em;font-size:1em;margin-bottom:.5em}#content .data .list-wrap li:hover,#box-popup .list-wrap li:hover{background:#e8eaf6;color:#000}#content .data .list-wrap div.files-item-info,#box-popup .list-wrap div.files-item-info{flex-grow:2}#content .data .list-wrap div.files-item-info img,#box-popup .list-wrap div.files-item-info img{margin:0 .5em}#content .data .list-wrap div.files-item-actions,#box-popup .list-wrap div.files-item-actions{padding:.3em 0}#box-popup{min-width:25em;max-width:50em;margin:5em auto;padding:0;background-color:#fff}#box-popup.box-password-view{min-width:30em;max-width:35em}#box-popup>h2{position:relative;width:100%;font-size:18px;color:#fff;background-color:#607d8b;margin:0;padding:.5em 0;line-height:1em}#box-popup>h2 .btn-popup-close{display:none;position:absolute;right:.5em;top:.2em}#box-popup>table{width:100%;padding-bottom:1em}#box-popup select{width:220px}#box-popup #resFancyAccion{display:none}#box-popup #resCheck{display:inline-block;width:80%;height:4em;padding:1em 0}#box-popup.image{background-color:transparent;max-width:100%;margin:0 auto;border-radius:0!important;-moz-border-radius:0!important;-webkit-border-radius:0!important}#box-popup.image img{width:auto;margin:0 auto}#box-popup.image>div.title{background-color:#607d8b;color:#fff;padding:.5em}#box-popup.help{min-height:100px;background-color:#f5f5f5}#box-popup.help p{font-size:14px;text-align:justify;line-height:2em}#box-complexity>div{text-align:justify;line-height:1.5em;margin-top:1em}#debug{float:left;text-align:left}#debuginfo{width:100%;min-height:10em;padding:1em;background-color:#fff8e1;text-align:left;line-height:1.5em}#debuginfo H3{text-align:center}.popup-data{width:100%;min-width:400px;border:0;text-align:left;margin:0;padding:1em .5em}.popup-data .descField{min-width:100px}footer{display:flex;justify-content:space-between;position:fixed;bottom:0;z-index:100;width:100%;padding:.5em 0;background-color:#f5f5f5;color:#b9b9b9;font-size:1em;-webkit-box-shadow:0 -3px 2px -2px rgba(0,0,0,0.14);-moz-box-shadow:0 -3px 2px -2px rgba(0,0,0,0.14);box-shadow:0 -3px 2px -2px rgba(0,0,0,0.14)}footer .footer-parts{display:flex;justify-content:space-between}footer #footer-left{width:50%;margin:0 1em}footer #footer-right{width:50%;margin:0 1em;justify-content:flex-end;text-align:right}footer #updates{min-width:10em;text-align:center;cursor:pointer}footer #status{margin:0 1em}footer #status>div{display:inline-block}footer #status .status-info{padding:.5em}footer #session{text-align:left;color:#999;font-size:.8em}footer a{color:#b9b9b9}footer a:visited{color:#b9b9b9}footer #project a:hover{color:#a9c1d7;border-bottom:1px solid #a9c1d7}footer #updates a:hover{color:#a9c1d7}footer img{border:0;width:16px;height:16px;vertical-align:middle}.round,.round5{border-radius:5px!important;-moz-border-radius:5px!important;-webkit-border-radius:5px!important}.midround{border-radius:0 0 10px 10px!important;-moz-border-radius:0 0 10px 10px!important;-webkit-border-radius:0 0 10px 10px!important}.midroundup{border-radius:10px 10px 0 0!important;-moz-border-radius:10px 10px 0 0!important;-webkit-border-radius:10px 10px 0 0!important}.fullround{border-radius:50%!important;-moz-border-radius:50%!important;-webkit-border-radius:50%!important}.iconMini{width:16px!important;height:16px!important;vertical-align:middle}.hide{display:none!important}.btn-checks{padding:5px;margin:.2em 0;width:30em;border-bottom:1px solid #c9c9c9}.shadow{-webkit-box-shadow:2px 2px 3px -3px rgba(0,0,0,0.14);-moz-box-shadow:2px 2px 3px -3px rgba(0,0,0,0.14);box-shadow:1px 1px 2px rgba(0,0,0,0.14)}.noRes{width:60%;padding:15px;background-color:#f9f9f9;color:#a9a9a9;border:#c9c9c9 1px solid;margin:20px auto;text-align:center;font-size:16px}.header-grey{background-color:#607d8b;color:#fff;min-height:2em}.no-background{background:none!important}.action-in-box{padding:1em;text-align:right}.action-in-box ul{list-style:none;margin:0;padding:0}.tab-data{margin:2em auto 0;width:75%}.item-actions{margin:1em auto}.tab-actions{margin:2em 0}.item-actions>ul,.tab-actions>ul{display:flex;flex-wrap:wrap;justify-content:flex-end;align-items:center;list-style:none;margin:0;padding:0}.item-actions>ul>li,.tab-actions>ul>li{width:auto;min-width:2em;margin-left:.5em}.item-actions>ul>li.datagrid-action-search,.tab-actions>ul>li.datagrid-action-search{min-width:5em}.item-actions>ul>li.datagrid-action-search form,.tab-actions>ul>li.datagrid-action-search form{width:100%}.fullWidth{max-width:100%!important}.filter-on{color:#26a69a;background-color:#e0f2f1;border:1px solid #26a69a;padding:.3em 1em}.global-on{color:#ffca28;background-color:#fff8e1;border:1px solid #ffca28;padding:.3em 1em}.opacity50{filter:alpha(opacity=50);opacity:.5}.custom-combobox{position:relative;display:inline-block}.custom-combobox input{width:80%}.custom-combobox-toggle{position:absolute;top:0;bottom:0;margin-left:-1px;padding:0;*height:1.7em;*top:.1em}.custom-combobox-input{margin:0;padding:.3em}.passLevel{width:20px;height:20px;display:inline-block;position:relative;top:2px}.passLevel.strongest{color:#26a69a;background-color:#e0f2f1;border:1px solid #26a69a;font-weight:bold}.passLevel.strongest:hover{color:#26a69a;background-color:#e0f2f1;border:1px solid #26a69a;font-weight:bold}.passLevel.strong{color:#2196f3;background-color:#e3f2fd;border:1px solid #2196f3;font-weight:bold}.passLevel.strong:hover{color:#2196f3;background-color:#e3f2fd;border:1px solid #2196f3;font-weight:bold}.passLevel.good{color:#ffca28;background-color:#fff8e1;border:1px solid #ffca28;font-weight:bold}.passLevel.good:hover{color:#ffca28;background-color:#fff8e1;border:1px solid #ffca28;font-weight:bold}.passLevel.weak{color:#ef5350;background-color:#ffebee;border:1px solid #ef5350;font-weight:bold}.passLevel.weak:hover{color:#ef5350;background-color:#ffebee;border:1px solid #ef5350;font-weight:bold}#alert #alert-text{margin:15px auto;font-size:14px;font-weight:bold}#alert #alert-pass{width:50%;padding:10px;margin:15px auto;border:1px solid #c9c9c9;color:#555;font-weight:bold}.dialog-text,.dialog-user-text,.dialog-pass-text{font-family:Consolas,"Andale Mono WT","Andale Mono","Bitstream Vera Sans Mono","Nimbus Mono L",Monaco,"Courier New",monospace;padding:.5em;text-align:center;min-width:200px}.dialog-user-text{border-bottom:#d9d9d9 1px solid;color:#a9a9a9}.dialog-pass-text{border:transparent 1px solid;letter-spacing:.2em}.dialog-buttons{text-align:center;padding:.5em;border-top:1px solid #c9c9c9;line-height:2.5em}.dialog-clip-copy{color:#26a69a;background-color:#e0f2f1}.help-box{display:none;background-color:#fff!important;color:#607d8b}.help-box>*{font-weight:bold}.help-text{text-align:justify;line-height:1.5em;margin-top:1em}.tooltip{width:300px;max-width:300px;background-color:#777;color:#fff;z-index:101}.cursor-pointer{cursor:pointer}.password-actions{display:inline-block;width:12em}.password-actions>span,.password-actions i{margin-right:.6em}.custom-input-color{width:3em;height:1em;display:inline-block}.account-pass-image{height:32px;width:auto}.select-box{min-width:20em}fieldset.warning{padding:8px;border-radius:5px;color:#ef5350;background-color:#ffebee;border:1px solid #ef5350}fieldset.warning legend{color:#ef5350!important}fieldset.warning a{color:#ef5350!important;font-weight:bold}#actions{width:100%;line-height:2em;margin-bottom:5em}#actions #logo{display:flex;width:100%;margin-bottom:30px;color:#607d8b;align-items:center;background:url("../imgs/logo_full_bg.png") left no-repeat;background-size:auto 150px;height:150px}#actions #page-title{width:100%;color:#607d8b;text-align:center}#actions #page-title h1{font-weight:bold;font-size:24px;letter-spacing:3px}#actions ul.errors{max-width:40vw;margin:0 auto;list-style:none;font-size:14px;text-align:left}#actions ul.errors>li{margin:1.5em auto;border-radius:3px;padding:1em .5em}#actions ul.errors>li.msg-critical{color:#ef5350;background-color:#ffebee;border:1px solid #ef5350}#actions ul.errors>li.msg-warning{color:#ffca28;background-color:#fff8e1;border:1px solid #ffca28;color:#555}#actions ul.errors>li.msg-ok{color:#26a69a;background-color:#e0f2f1;border:1px solid #26a69a}#actions ul.errors>li>p.hint{color:#555;font-size:12px}#actions ul.errors>li>p.hint i{margin-right:.5em}#actions form{width:450px;margin:0 auto;text-align:left}#actions form fieldset{margin-bottom:2em}#actions form fieldset legend{width:100%;color:#fff;font-size:14px;font-weight:bold;text-align:center;background-color:#607d8b;margin:1em 0;letter-spacing:.2em;padding:.2em 0}#actions div.buttons{margin-top:2em;text-align:center}.center{text-align:center!important}.right{text-align:right!important}.left{text-align:left!important}.opacity50{opacity:.5!important}#login-container{width:40em;margin:0 auto;background:transparent url("../imgs/logo_full_bg.png") no-repeat top left;background-size:auto 10em}#login-container #boxSpacer{height:11em;background-color:transparent}#login-container #boxLogin{position:relative;margin:0 auto;width:100%;min-height:14em;padding:1em;background-color:#fff}#login-container #boxLogin #boxData{height:100%;min-height:14em;text-align:left;background-color:transparent}#login-container #boxLogin #boxData i{margin-right:.5em;opacity:.5}#login-container #boxLogin #boxData .extra-hidden{display:none}#login-container #boxLogin #boxButton{position:absolute;top:2em;right:2em}#login-container #boxLogin #boxActions{width:100%;text-align:right}#login-container #boxLogin #boxActions a{color:#c9c9c9}#login-container #boxLogout{margin-top:4em;width:100%}#login-container #boxLogout>div{margin:0 auto;width:250px;font-size:14px;padding:.5em;text-align:center;color:#ffca28;background-color:#fff8e1;border:1px solid #ffca28}#login-container #boxUpdated{width:350px;margin:3em auto;font-size:14px;text-align:center;padding:.5em;color:#26a69a;background-color:#e0f2f1;border:1px solid #26a69a}#login-container #demo-info{margin:3em auto;color:#c9c9c9;border-top:1px solid #d9d9d9;border-bottom:1px solid #d9d9d9;padding:.5em}#login-container #demo-info ul{display:flex;justify-content:space-around;list-style:none}#login-container #demo-info ul li span{margin:0 2em}@media screen and (max-width:1000px){#content #searchbox .search-filters>*{margin:.5em 1em .5em 0}#content .data-container #title,#content .tab-data #title{width:90%}footer{display:none;justify-content:space-between;flex-wrap:wrap}footer .footer-parts{justify-content:space-between;flex-wrap:wrap}footer #footer-left,footer #footer-right{width:100%}footer .footer-parts>div{width:100%;padding:.5em 0}}@media screen and (max-width:600px){#content input,#box-popup input{width:100%}#content .mdl-textfield,#box-popup .mdl-textfield{width:100%}#content td.descField,#box-popup td.descField{display:none}#content td.valField .lowres-title,#box-popup td.valField .lowres-title{display:block}#content #searchbox .mdl-textfield{width:90%}#content #searchbox .search-text{width:90%}#content #searchbox .search-filters .selectize-control{width:100%}#content .data-container,#content .tab-data{width:100%}#content .data-container #title,#content .tab-data #title{width:90%}#content .data-container .selectize-control,#content .tab-data .selectize-control{width:100%}#box-popup h2>.btn-popup-close{display:inline-block}.mdl-data-table{table-layout:fixed;width:100%}.table-responsive td,.table-responsive th{width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;-o-text-overflow:ellipsis}.table-responsive .cell-actions i{display:block!important}} \ No newline at end of file +html,body{margin:0;padding:0;text-align:left;background-color:#f5f5f5;color:#555;font-size:12px;font-weight:normal;box-sizing:border-box}*{font-family:"Roboto Regular",Verdana,Tahoma,sans-serif;box-sizing:inherit}*:before,*:after{box-sizing:inherit}table{font-size:11px;border-spacing:0}table th{border-bottom:2px solid transparent;vertical-align:middle}table th .icon{width:24px;height:24px}table tr{height:20px}table tr.odd{background-color:#f9f9f9}table tr.even>td,table tr.odd>td{border-bottom:1px solid #d9d9d9 !important}table tr.even:hover,table tr.odd:hover{background-color:#e8ff99}table td{padding:3px}table td.txtCliente{font-weight:bold;text-align:center}form{font-size:11px;margin:0}input.inputImg,img.inputImg{background-color:transparent !important;width:24px !important;height:24px !important;border:0;vertical-align:middle;margin:0 .5em}input.txtFile{width:200px}input.txtLong{width:300px}textarea{width:350px;resize:none}select.files{width:250px}input.spinner{width:5em}img{margin:0;padding:0;border:0;cursor:pointer}img.inputImgMini{background-color:transparent !important;width:16px !important;height:16px !important;margin:0 5px 0 5px;border:0;vertical-align:middle}i{cursor:pointer}form .form-field{display:flex;justify-content:space-between}form .form-field>label{min-width:12em;padding:.5em 0;font-size:16px;align-self:center}form .form-field>div{width:100%;align-self:center}a{text-decoration:none;color:#536dfe}a:visited{text-decoration:none;color:#536dfe}a:hover,a:active,a:focus{text-decoration:none;cursor:pointer}pre,code,samp,kbd{font-family:Consolas,"Andale Mono WT","Andale Mono","Bitstream Vera Sans Mono","Nimbus Mono L",Monaco,"Courier New",monospace;font-size:1em;direction:ltr;text-align:left;background-color:#fbfaf9;color:#333;box-shadow:inset 0 0 .3em #ccc;border-radius:2px}#nojs{width:80%;text-align:center;vertical-align:middle;margin:10px auto;padding:3px;background-color:#ef5350;color:white;font-weight:bold;font-size:14px}#wrap{height:auto !important;min-height:100%;width:100%;background-color:#f5f5f5}#wrap-loading{position:fixed;z-index:9999;top:50%;left:50%;padding:1em;background-color:rgba(255,255,255,0.8);display:none;border-radius:5px !important;-moz-border-radius:5px !important;-webkit-border-radius:5px !important}#wrap-loading.overlay-full{top:0;left:0;width:100%;height:100%;background-color:rgba(255,255,255,0.5)}#wrap-loading.overlay-full #loading{position:absolute;top:50%;left:50%}#container{margin:auto;width:100%}#container.login{padding-top:5%}#container.error,#container.install,#container.passreset{width:100%}#container .logo{height:64px}#container #actions-bar{z-index:100;display:flex;justify-content:space-between;position:fixed;border:0 none;top:0;left:0;width:100%;padding:1em 0;background-color:transparent}#container #actions-bar-icons{flex-grow:1;text-align:center}#container #actions-bar-logo{display:none;padding:0 .5em}#container #actions-bar-logo img{display:inline-block;width:50px;opacity:.75}#container #content{width:95%;margin:2em auto 8em auto}#container #content.public-link{width:70%;min-height:0;margin:5em auto}#content td.descField,#box-popup td.descField{text-align:right;padding-right:20px;width:25%;border-right:1px solid #d9d9d9;color:#999;font-size:12px;font-weight:bold}#content td.valField,#box-popup td.valField{padding-left:1em;width:100%}#content td.valField .lowres-title,#box-popup td.valField .lowres-title{display:none;width:100%;color:#607d8b;font-size:12px}#content .pager{width:100%;margin-top:15px;padding:.5em;vertical-align:middle;font-size:11px;color:#999;background-color:#fcfcfc}#content .pager img{margin-left:5px;vertical-align:middle}#content .pager a{margin-left:5px;font-size:12px;color:#999}#content .pager>div{display:inline-block;width:49%}#content .pager .pager-left{text-align:left}#content .pager .pager-right{text-align:right}#content #title{width:50%;padding:7px;margin:auto;background-color:#d9d9d9;color:#fff;font-size:17px;letter-spacing:.3em;text-align:center}#content #title.titleNormal{background-color:#607d8b;color:#fff}#content .data-container{width:75%;margin:0 auto}#content fieldset.data{margin:2em auto}#content fieldset.data>legend{color:#607d8b;padding:0 .5em;font-size:1.5em}#content fieldset.data>div{display:none}#content fieldset.data>div table{width:100%}#content .data{width:100%;padding:10px;border:1px solid #c9c9c9;margin:0 auto;background-color:#f9f9f9}#content .data #history-icon{position:relative;top:5em;right:2em}#content .data td{text-align:left}#content .data td.descField{text-align:right}#content .data select{min-width:210px}#content .data .list-wrap{max-height:10em;overflow:auto;padding:.5em;margin:1em 0}#content .data .dropzone{width:30em;padding:1em;border:2px dashed #26a69a;text-align:center}#content .data .dropzone img{vertical-align:middle}#content .data .file-upload{display:none}#content .data .account-permissions{width:100%}#content .data .account-permissions fieldset{border:1px solid #c9c9c9;padding:1em}#content .data .account-permissions legend{font-weight:bold;color:#999;padding:.2em 0}#content .data .account-permissions fieldset>span{font-weight:bold;color:#999;padding:.2em 0;display:inline-block;width:100px;text-align:right}#content span.tag{margin:0 3px 3px 0;padding:.2em;background:#5c6bc0;color:#fff;border:0 solid transparent;border-radius:3px !important;-moz-border-radius:3px !important;-webkit-border-radius:3px !important}#content .extra-info{margin-top:20px}#content #tabs fieldset{border:1px solid #c9c9c9}#content #tabs #frmConfig label{float:left}#content .tblConfig{margin-bottom:2em}#content .tblConfig td.descField{width:35%;font-size:11px;font-weight:bold}#content .tblConfig td.rowHeader{padding:5px 0 5px 0;background-color:#f5f5f5;text-align:center;font-weight:bold;border-top:15px solid #f9f9f9;border-bottom:3px solid #a9c1d7;letter-spacing:.5em;color:#696969}#content .tblConfig input.checkbox{width:15px;text-align:left;padding:0}#content .tblConfig .option-disabled{text-align:center;background-color:#fff8e1;color:#ffca28;font-weight:bold}#content h2{width:100%;height:1.5em;font-size:18px;color:white;background-color:#a9c1d7;margin:0;padding-top:.1em}#content .section{margin-top:2.5em;border-bottom:1px solid #d9d9d9;text-align:left;font-size:14px;font-weight:bold;color:#5c6bc0}#content .row_even>td{background-color:#f5f5f5}#content .row_odd>td{background-color:white}#content .data-header ul{list-style:none;width:100%;margin:0 0 10px 0;padding:0}#content .data-header li{display:inline-block;padding:.2em .5em;font-weight:bold;letter-spacing:.2em;color:#fff;text-align:center}#content .data-header li a{color:#777}#content .data-header li img{float:right;width:24px;height:24px;vertical-align:middle}#content .data-header-minimal{border-bottom:1px solid #dfdfdf}#content .data-header-minimal ul{display:flex;flex-wrap:wrap;justify-content:flex-start;margin:0}#content .data-header-minimal li{display:inline-flex;min-width:10em;font-weight:normal;letter-spacing:normal}#content .data-header-minimal li a{color:#b9b9b9;padding:.3em .8em}#content .data-table{width:100%}#content .data-table td:first-of-type,#content .data-table th:first-of-type{width:5em}#content .data-table thead th{background-color:#607d8b;color:#fff}#content .data-table tbody td.cell-data{text-align:left}#content .data-table tbody td.cell-nodata{padding:0 .5em;text-align:left}#content .data-table tbody td.cell-actions{text-align:right}#content .data-table tbody td.cell-actions i{opacity:.5}#content .data-table tbody td.cell-actions i:hover{opacity:1}#content .data-rows ul{display:table;list-style:none;width:100%;margin:0 0 10px 0;padding:0;background-color:#fcfcfc}#content .data-rows li{float:left;display:block;padding:1em;color:#696969;text-align:center;min-height:2em}#content .data-rows li.cell-nodata{padding:1em 0;min-height:2em;text-align:left}#content .data-rows li.cell-actions{float:right;min-height:2em;padding:1em 0;text-align:left;background-color:#fcfcfc;width:15em}#content .data-rows li.cell-nodata img,#content .data-rows li.cell-actions img{width:24px;height:24px;margin:0 .5em}#content #resEventLog .data{width:100%}#content #resEventLog thead{text-align:center}#content #resEventLog tbody{width:100%;height:500px;overflow:auto}#content #resEventLog td{border-bottom:1px solid #d9d9d9}#content #resEventLog .cell{text-align:center}#content #resEventLog .cell-description{width:60%}#content #searchbox{background-color:#fcfcfc;vertical-align:middle;position:relative;height:auto;padding:.5em 1em;margin-bottom:2em}#content #searchbox form{display:flex;flex-wrap:wrap;justify-content:flex-start;align-items:center;text-align:left}#content #searchbox .search-filters>*{margin:0 1em}#content #searchbox .search-filters .filter-buttons{display:inline-block}#content #searchbox .search-filters .filter-slider{width:10em}#content #searchbox .search-filters-tags{display:none;flex-grow:2}#content .btn-clear{opacity:.35;filter:alpha(opacity=35)}#content .btn-clear:hover{opacity:1;filter:alpha(opacity=100)}#content .actions-optional{display:none}#content .error{width:350px;padding:15px;margin:0 auto;text-align:center;font-size:16px;line-height:1.5em;color:#ffca28;background-color:#fff8e1;border:1px solid #ffca28}#box-popup .list-wrap{max-height:10em;overflow:auto;padding:.5em;margin:1em 0}#content .data .list-wrap ul,#box-popup .list-wrap ul{list-style-type:none;margin:0;padding:0}#content .data .list-wrap li,#box-popup .list-wrap li{display:flex;background:#f2f2f2;padding:.5em;font-size:1em;margin-bottom:.5em}#content .data .list-wrap li:hover,#box-popup .list-wrap li:hover{background:#e8eaf6;color:#000}#content .data .list-wrap div.files-item-info,#box-popup .list-wrap div.files-item-info{flex-grow:2}#content .data .list-wrap div.files-item-info img,#box-popup .list-wrap div.files-item-info img{margin:0 .5em}#content .data .list-wrap div.files-item-actions,#box-popup .list-wrap div.files-item-actions{padding:.3em 0}#box-popup{min-width:30em;max-width:60em;margin:5em auto;padding:0;background-color:#fff}#box-popup.box-password-view{min-width:30em;max-width:35em}#box-popup>h2{position:relative;width:100%;font-size:18px;color:#fff;background-color:#607d8b;margin:0;padding:.5em 0;line-height:1em}#box-popup>h2 .btn-popup-close{display:none;position:absolute;right:.5em;top:.2em}#box-popup>table{width:100%;padding-bottom:1em}#box-popup select{width:220px}#box-popup #resFancyAccion{display:none}#box-popup #resCheck{display:inline-block;width:80%;height:4em;padding:1em 0}#box-popup.image{background-color:transparent;max-width:100%;margin:0 auto;border-radius:0 !important;-moz-border-radius:0 !important;-webkit-border-radius:0 !important}#box-popup.image img{width:auto;margin:0 auto}#box-popup.image>div.title{background-color:#607d8b;color:#fff;padding:.5em}#box-popup.help{min-height:100px;background-color:#f5f5f5}#box-popup.help p{font-size:14px;text-align:justify;line-height:2em}#box-complexity>div{text-align:justify;line-height:1.5em;margin-top:1em}#debug{float:left;text-align:left}#debuginfo{width:100%;min-height:10em;padding:1em;background-color:#fff8e1;text-align:left;line-height:1.5em}#debuginfo H3{text-align:center}.popup-data{width:100%;min-width:400px;border:0;text-align:left;margin:0;padding:1em .5em}.popup-data .descField{min-width:100px}footer{display:flex;justify-content:space-between;position:fixed;bottom:0;z-index:100;width:100%;padding:.5em 0;background-color:#f5f5f5;color:#b9b9b9;font-size:1em;-webkit-box-shadow:0 -3px 2px -2px rgba(0,0,0,0.14);-moz-box-shadow:0 -3px 2px -2px rgba(0,0,0,0.14);box-shadow:0 -3px 2px -2px rgba(0,0,0,0.14)}footer .footer-parts{display:flex;justify-content:space-between}footer #footer-left{width:50%;margin:0 1em}footer #footer-right{width:50%;margin:0 1em;justify-content:flex-end;text-align:right}footer #updates{min-width:10em;text-align:center;cursor:pointer}footer #status{margin:0 1em}footer #status>div{display:inline-block}footer #status .status-info{padding:.5em}footer #session{text-align:left;color:#999;font-size:.8em}footer a{color:#b9b9b9}footer a:visited{color:#b9b9b9}footer #project a:hover{color:#a9c1d7;border-bottom:1px solid #a9c1d7}footer #updates a:hover{color:#a9c1d7}footer img{border:0;width:16px;height:16px;vertical-align:middle}.round,.round5{border-radius:5px !important;-moz-border-radius:5px !important;-webkit-border-radius:5px !important}.midround{border-radius:0 0 10px 10px !important;-moz-border-radius:0 0 10px 10px !important;-webkit-border-radius:0 0 10px 10px !important}.midroundup{border-radius:10px 10px 0 0 !important;-moz-border-radius:10px 10px 0 0 !important;-webkit-border-radius:10px 10px 0 0 !important}.fullround{border-radius:50% !important;-moz-border-radius:50% !important;-webkit-border-radius:50% !important}.iconMini{width:16px !important;height:16px !important;vertical-align:middle}.hide{display:none !important}.btn-checks{padding:5px;margin:.2em 0;width:30em;border-bottom:1px solid #c9c9c9}.shadow{-webkit-box-shadow:2px 2px 3px -3px rgba(0,0,0,0.14);-moz-box-shadow:2px 2px 3px -3px rgba(0,0,0,0.14);box-shadow:1px 1px 2px rgba(0,0,0,0.14)}.noRes{width:60%;padding:15px;background-color:#f9f9f9;color:#a9a9a9;border:#c9c9c9 1px solid;margin:20px auto;text-align:center;font-size:16px}.header-grey{background-color:#607d8b;color:#fff;min-height:2em}.no-background{background:none !important}.action-in-box{padding:1em;text-align:right}.action-in-box ul{list-style:none;margin:0;padding:0}.tab-data{margin:2em auto 0;width:75%}.item-actions{margin:1em auto}.tab-actions{margin:2em 0}.item-actions>ul,.tab-actions>ul{display:flex;flex-wrap:wrap;justify-content:flex-end;align-items:center;list-style:none;margin:0;padding:0}.item-actions>ul>li,.tab-actions>ul>li{width:auto;min-width:2em;margin-left:.5em}.item-actions>ul>li.datagrid-action-search,.tab-actions>ul>li.datagrid-action-search{min-width:5em}.item-actions>ul>li.datagrid-action-search form,.tab-actions>ul>li.datagrid-action-search form{width:100%}.fullWidth{max-width:100% !important}.filter-on{color:#26a69a;background-color:#e0f2f1;border:1px solid #26a69a;padding:.3em 1em}.global-on{color:#ffca28;background-color:#fff8e1;border:1px solid #ffca28;padding:.3em 1em}.opacity50{filter:alpha(opacity=50);opacity:.5}.custom-combobox{position:relative;display:inline-block}.custom-combobox input{width:80%}.custom-combobox-toggle{position:absolute;top:0;bottom:0;margin-left:-1px;padding:0;*height:1.7em;*top:.1em}.custom-combobox-input{margin:0;padding:.3em}.passLevel{width:20px;height:20px;display:inline-block;position:relative;top:2px}.passLevel.strongest{color:#26a69a;background-color:#e0f2f1;border:1px solid #26a69a;font-weight:bold}.passLevel.strongest:hover{color:#26a69a;background-color:#e0f2f1;border:1px solid #26a69a;font-weight:bold}.passLevel.strong{color:#2196f3;background-color:#e3f2fd;border:1px solid #2196f3;font-weight:bold}.passLevel.strong:hover{color:#2196f3;background-color:#e3f2fd;border:1px solid #2196f3;font-weight:bold}.passLevel.good{color:#ffca28;background-color:#fff8e1;border:1px solid #ffca28;font-weight:bold}.passLevel.good:hover{color:#ffca28;background-color:#fff8e1;border:1px solid #ffca28;font-weight:bold}.passLevel.weak{color:#ef5350;background-color:#ffebee;border:1px solid #ef5350;font-weight:bold}.passLevel.weak:hover{color:#ef5350;background-color:#ffebee;border:1px solid #ef5350;font-weight:bold}#alert #alert-text{margin:15px auto;font-size:14px;font-weight:bold}#alert #alert-pass{width:50%;padding:10px;margin:15px auto;border:1px solid #c9c9c9;color:#555;font-weight:bold}.dialog-text,.dialog-user-text,.dialog-pass-text{font-family:Consolas,"Andale Mono WT","Andale Mono","Bitstream Vera Sans Mono","Nimbus Mono L",Monaco,"Courier New",monospace;padding:.5em;text-align:center;min-width:200px}.dialog-user-text{border-bottom:#d9d9d9 1px solid;color:#a9a9a9}.dialog-pass-text{border:transparent 1px solid;letter-spacing:.2em}.dialog-buttons{text-align:center;padding:.5em;border-top:1px solid #c9c9c9;line-height:2.5em}.dialog-clip-copy{color:#26a69a;background-color:#e0f2f1}.help-box{display:none;background-color:#fff !important;color:#607d8b}.help-box>*{font-weight:bold}.help-text{text-align:justify;line-height:1.5em;margin-top:1em}.tooltip{width:300px;max-width:300px;background-color:#777;color:#fff;z-index:101}.cursor-pointer{cursor:pointer}.password-actions{display:inline-block;width:12em}.password-actions>span,.password-actions i{margin-right:.6em}.custom-input-color{width:3em;height:1em;display:inline-block}.account-pass-image{height:32px;width:auto}.select-box{min-width:20em}fieldset.warning{padding:8px;border-radius:5px;color:#ef5350;background-color:#ffebee;border:1px solid #ef5350}fieldset.warning legend{color:#ef5350 !important}fieldset.warning a{color:#ef5350 !important;font-weight:bold}#actions{width:100%;line-height:2em;margin-bottom:5em}#actions #logo{display:flex;width:100%;margin-bottom:30px;color:#607d8b;align-items:center;background:url("../imgs/logo_full_bg.png") left no-repeat;background-size:auto 150px;height:150px}#actions #page-title{width:100%;color:#607d8b;text-align:center}#actions #page-title h1{font-weight:bold;font-size:24px;letter-spacing:3px}#actions ul.errors{max-width:40vw;margin:0 auto;list-style:none;font-size:14px;text-align:left}#actions ul.errors>li{margin:1.5em auto;border-radius:3px;padding:1em .5em}#actions ul.errors>li.msg-critical{color:#ef5350;background-color:#ffebee;border:1px solid #ef5350}#actions ul.errors>li.msg-warning{color:#ffca28;background-color:#fff8e1;border:1px solid #ffca28;color:#555}#actions ul.errors>li.msg-ok{color:#26a69a;background-color:#e0f2f1;border:1px solid #26a69a}#actions ul.errors>li>p.hint{color:#555;font-size:12px}#actions ul.errors>li>p.hint i{margin-right:.5em}#actions form{width:450px;margin:0 auto;text-align:left}#actions form fieldset{margin-bottom:2em}#actions form fieldset legend{width:100%;color:#fff;font-size:14px;font-weight:bold;text-align:center;background-color:#607d8b;margin:1em 0;letter-spacing:.2em;padding:.2em 0}#actions div.buttons{margin-top:2em;text-align:center}.center{text-align:center !important}.right{text-align:right !important}.left{text-align:left !important}.opacity50{opacity:.5 !important}#login-container{width:40em;margin:0 auto;background:transparent url("../imgs/logo_full_bg.png") no-repeat top left;background-size:auto 10em}#login-container #boxSpacer{height:11em;background-color:transparent}#login-container #boxLogin{position:relative;margin:0 auto;width:100%;min-height:14em;padding:1em;background-color:#fff}#login-container #boxLogin #boxData{height:100%;min-height:14em;text-align:left;background-color:transparent}#login-container #boxLogin #boxData i{margin-right:.5em;opacity:.5}#login-container #boxLogin #boxData .extra-hidden{display:none}#login-container #boxLogin #boxButton{position:absolute;top:2em;right:2em}#login-container #boxLogin #boxActions{width:100%;text-align:right}#login-container #boxLogin #boxActions a{color:#c9c9c9}#login-container #boxLogout{margin-top:4em;width:100%}#login-container #boxLogout>div{margin:0 auto;width:250px;font-size:14px;padding:.5em;text-align:center;color:#ffca28;background-color:#fff8e1;border:1px solid #ffca28}#login-container #boxUpdated{width:350px;margin:3em auto;font-size:14px;text-align:center;padding:.5em;color:#26a69a;background-color:#e0f2f1;border:1px solid #26a69a}#login-container #demo-info{margin:3em auto;color:#c9c9c9;border-top:1px solid #d9d9d9;border-bottom:1px solid #d9d9d9;padding:.5em}#login-container #demo-info ul{display:flex;justify-content:space-around;list-style:none}#login-container #demo-info ul li span{margin:0 2em}@media screen and (max-width:1000px){#content #searchbox .search-filters>*{margin:.5em 1em .5em 0}#content .data-container #title,#content .tab-data #title{width:90%}footer{display:none;justify-content:space-between;flex-wrap:wrap}footer .footer-parts{justify-content:space-between;flex-wrap:wrap}footer #footer-left,footer #footer-right{width:100%}footer .footer-parts>div{width:100%;padding:.5em 0}}@media screen and (max-width:600px){#content input,#box-popup input{width:100%}#content .mdl-textfield,#box-popup .mdl-textfield{width:100%}#content td.descField,#box-popup td.descField{display:none}#content td.valField .lowres-title,#box-popup td.valField .lowres-title{display:block}#content #searchbox .mdl-textfield{width:90%}#content #searchbox .search-text{width:90%}#content #searchbox .search-filters .selectize-control{width:100%}#content .data-container,#content .tab-data{width:100%}#content .data-container #title,#content .tab-data #title{width:90%}#content .data-container .selectize-control,#content .tab-data .selectize-control{width:100%}#box-popup h2>.btn-popup-close{display:inline-block}.mdl-data-table{table-layout:fixed;width:100%}.table-responsive td,.table-responsive th{width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;-o-text-overflow:ellipsis}.table-responsive .cell-actions i{display:block !important}} \ No newline at end of file diff --git a/inc/themes/material-blue/css/styles.scss b/inc/themes/material-blue/css/styles.scss index 75f8d333..77727632 100644 --- a/inc/themes/material-blue/css/styles.scss +++ b/inc/themes/material-blue/css/styles.scss @@ -532,8 +532,8 @@ } #box-popup { - min-width: 25em; - max-width: 50em; + min-width: 30em; + max-width: 60em; margin: 5em auto; padding: 0; background-color: #fff; diff --git a/inc/themes/material-blue/js/app-theme.js b/inc/themes/material-blue/js/app-theme.js index f0c924d9..942952b7 100644 --- a/inc/themes/material-blue/js/app-theme.js +++ b/inc/themes/material-blue/js/app-theme.js @@ -114,9 +114,12 @@ sysPass.Theme = function (Common) { // Poner la clave en los input y actualizar MDL $dstParent.find("input:password").val(genPassword); $dstParent.addClass(mdl.CssClasses_.IS_DIRTY).removeClass(mdl.CssClasses_.IS_INVALID); + // Poner la clave en el input de repetición y encriptarla - $targetR.val(genPassword).parent().addClass(mdl.CssClasses_.IS_DIRTY).removeClass(mdl.CssClasses_.IS_INVALID); - Common.encryptFormValue($targetR); + if ($targetR.length > 0) { + $targetR.val(genPassword).parent().addClass(mdl.CssClasses_.IS_DIRTY).removeClass(mdl.CssClasses_.IS_INVALID); + Common.encryptFormValue($targetR); + } // Mostar el indicador de complejidad $dstParent.find("#passLevel").show(500); @@ -127,8 +130,6 @@ sysPass.Theme = function (Common) { } }; - - // FIXME // Diálogo de configuración de complejidad de clave var complexityDialog = function () { @@ -193,8 +194,6 @@ sysPass.Theme = function (Common) { var $thisParent = $this.parent(); var targetId = $this.attr("id"); - var $targetIdR = $("#" + targetId + "R"); - var btnMenu = ""; @@ -234,7 +233,12 @@ sysPass.Theme = function (Common) { // Reset de los campos de clave $passwordActions.find(".reset").on("click", function () { $this.val(""); - $targetIdR.val(""); + + var $targetIdR = $("#" + targetId + "R"); + + if ($targetIdR.length > 0) { + $targetIdR.val(""); + } // Actualizar objetos de MDL componentHandler.upgradeDom(); diff --git a/inc/themes/material-blue/js/app-theme.min.js b/inc/themes/material-blue/js/app-theme.min.js index df2dbcb8..55798540 100644 --- a/inc/themes/material-blue/js/app-theme.min.js +++ b/inc/themes/material-blue/js/app-theme.min.js @@ -1,18 +1,18 @@ -var $jscomp={scope:{},findInternal:function(a,g,e){a instanceof String&&(a=String(a));for(var h=a.length,k=0;k=c||58<=c&&64>=c||91<=c&&96>=c||123<=c&&126>=c)||!a.passwordData.complexity.numbers&&48<=c&&57>=c||!a.passwordData.complexity.uppercase&&65<=c&&90>=c||(d++,f+=String.fromCharCode(c));$("#viewPass").attr("title",f);var e=zxcvbn(f);a.passwordData.passLength=f.length;b?(d=b.parent(),c=$("#"+b.attr("id")+"R"),a.outputResult(e,b),b=new MaterialTextfield,d.find("input:password").val(f),d.addClass(b.CssClasses_.IS_DIRTY).removeClass(b.CssClasses_.IS_INVALID), -c.val(f).parent().addClass(b.CssClasses_.IS_DIRTY).removeClass(b.CssClasses_.IS_INVALID),a.encryptFormValue(c),d.find("#passLevel").show(500)):(a.outputResult(e),$("input:password, input.password").val(f),$("#passLevel").show(500))},k=function(){var b='
";showDialog({title:a.config().LANG[29],text:b,negative:{title:a.config().LANG[44]},positive:{title:a.config().LANG[43],onClick:function(d){d.preventDefault();a.passwordData.complexity.numbers=$("#checkbox-numbers").is(":checked"); a.passwordData.complexity.uppercase=$("#checkbox-uppercase").is(":checked");a.passwordData.complexity.symbols=$("#checkbox-symbols").is(":checked");a.passwordData.complexity.numlength=parseInt($("#passlength").val())}},cancelable:!0,contentStyle:{"max-width":"300px"},onLoaded:function(){$("#checkbox-numbers").prop("checked",a.passwordData.complexity.numbers);$("#checkbox-uppercase").prop("checked",a.passwordData.complexity.uppercase);$("#checkbox-symbols").prop("checked",a.passwordData.complexity.symbols); -$("#passlength").val(a.passwordData.complexity.numlength)}})},m=function(b){b.find(".passwordfield__input").each(function(){var d=$(this);if("true"!==d.attr("data-pass-upgraded")){var f=d.parent(),c=d.attr("id"),b=$("#"+c+"R"),l='',l=l+(''), e=$('
  • '),c=$('');a.forEach(function(a){var d=c.clone();d.append('person');d.append(a);a=e.clone().append(d);b.append(a)});return b},tabs:{add:function(a,d,e,c){a=$(a);var b="";1===c&&(a.parent().find("#tabs-"+d).addClass("is-active"),b="is-active");a.append(''+e+"")}}}}}; diff --git a/inc/themes/material-blue/views/itemshow/tokens.inc b/inc/themes/material-blue/views/itemshow/tokens.inc index 8e17ac83..f15112aa 100644 --- a/inc/themes/material-blue/views/itemshow/tokens.inc +++ b/inc/themes/material-blue/views/itemshow/tokens.inc @@ -1,3 +1,7 @@ +

    close

    @@ -13,10 +17,10 @@
    - > - id == $token->authtoken_userId) ? 'selected' : ''; ?> + id === $ApiTokenData->getAuthtokenUserId()) ? 'selected' : ''; ?> @@ -28,16 +32,28 @@
    + + + +
    + + +
    + + @@ -56,22 +72,22 @@
    - authtoken_token : ''; ?> + getAuthtokenToken(); ?> - +