* [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.

This commit is contained in:
nuxsmin
2017-02-23 01:46:51 +01:00
parent 49ba3d8bfb
commit 526ca3c9e9
24 changed files with 950 additions and 579 deletions

View File

@@ -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);
}
}
}
}

View File

@@ -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());
}
}

View File

@@ -1,314 +0,0 @@
<?php
/**
* sysPass
*
* @author nuxsmin
* @link http://syspass.org
* @copyright 2012-2017, Rubén Domínguez nuxsmin@$syspass.org
*
* This file is part of sysPass.
*
* sysPass is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* sysPass is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@@ -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;
}
}

View File

@@ -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()

View File

@@ -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
*

View File

@@ -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));
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -0,0 +1,230 @@
<?php
/**
* sysPass
*
* @author nuxsmin
* @link http://syspass.org
* @copyright 2012-2017, Rubén Domínguez nuxsmin@$syspass.org
*
* This file is part of sysPass.
*
* sysPass is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* sysPass is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@@ -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;
}
}

View File

@@ -35,7 +35,7 @@ interface FormInterface
* Validar el formulario
*
* @param $action
* @return bool
* @return FormInterface
* @throws \SP\Core\Exceptions\ValidationException
*/
public function validate($action);

View File

@@ -0,0 +1,409 @@
<?php
/**
* sysPass
*
* @author nuxsmin
* @link http://syspass.org
* @copyright 2012-2017, Rubén Domínguez nuxsmin@$syspass.org
*
* This file is part of sysPass.
*
* sysPass is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* sysPass is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@@ -0,0 +1,66 @@
<?php
/**
* sysPass
*
* @author nuxsmin
* @link http://syspass.org
* @copyright 2012-2017, Rubén Domínguez nuxsmin@$syspass.org
*
* This file is part of sysPass.
*
* sysPass is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* sysPass is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
*/
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();
}
}

View File

@@ -0,0 +1,83 @@
<?php
/**
* sysPass
*
* @author nuxsmin
* @link http://syspass.org
* @copyright 2012-2017, Rubén Domínguez nuxsmin@$syspass.org
*
* This file is part of sysPass.
*
* sysPass is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* sysPass is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@@ -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());

View File

@@ -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;

View File

@@ -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; }

File diff suppressed because one or more lines are too long

View File

@@ -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;

View File

@@ -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 = "<button id=\"menu-speed-" + targetId + "\" class=\"mdl-button mdl-js-button mdl-button--icon\" type=\"button\" title=\"" + Common.config().LANG[27] + "\"><i class=\"material-icons\">more_vert</i></button>";
@@ -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();

View File

@@ -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<h;k++){var m=a[k];if(g.call(e,m,k,a))return{i:k,v:m}}return{i:-1,v:void 0}}};$jscomp.defineProperty="function"==typeof Object.defineProperties?Object.defineProperty:function(a,g,e){if(e.get||e.set)throw new TypeError("ES3 does not support getters and setters.");a!=Array.prototype&&a!=Object.prototype&&(a[g]=e.value)};
$jscomp.getGlobal=function(a){return"undefined"!=typeof window&&window===a?a:"undefined"!=typeof global&&null!=global?global:a};$jscomp.global=$jscomp.getGlobal(this);$jscomp.polyfill=function(a,g,e,h){if(g){e=$jscomp.global;a=a.split(".");for(h=0;h<a.length-1;h++){var k=a[h];k in e||(e[k]={});e=e[k]}a=a[a.length-1];h=e[a];g=g(h);g!=h&&null!=g&&$jscomp.defineProperty(e,a,{configurable:!0,writable:!0,value:g})}};
var $jscomp={scope:{},findInternal:function(a,g,e){a instanceof String&&(a=String(a));for(var h=a.length,k=0;k<h;k++){var l=a[k];if(g.call(e,l,k,a))return{i:k,v:l}}return{i:-1,v:void 0}}};$jscomp.defineProperty="function"==typeof Object.defineProperties?Object.defineProperty:function(a,g,e){if(e.get||e.set)throw new TypeError("ES3 does not support getters and setters.");a!=Array.prototype&&a!=Object.prototype&&(a[g]=e.value)};
$jscomp.getGlobal=function(a){return"undefined"!=typeof window&&window===a?a:"undefined"!=typeof global?global:a};$jscomp.global=$jscomp.getGlobal(this);$jscomp.polyfill=function(a,g,e,h){if(g){e=$jscomp.global;a=a.split(".");for(h=0;h<a.length-1;h++){var k=a[h];k in e||(e[k]={});e=e[k]}a=a[a.length-1];h=e[a];g=g(h);g!=h&&null!=g&&$jscomp.defineProperty(e,a,{configurable:!0,writable:!0,value:g})}};
$jscomp.polyfill("Array.prototype.find",function(a){return a?a:function(a,e){return $jscomp.findInternal(this,a,e).v}},"es6-impl","es3");
sysPass.Theme=function(a){var g=a.log,e={elems:{$wrap:$("#wrap-loading"),$loading:$("#loading")},show:function(a){void 0!==a&&!0===a&&e.elems.$wrap.addClass("overlay-full");e.elems.$wrap.show();e.elems.$loading.addClass("is-active")},hide:function(){e.elems.$wrap.removeClass("overlay-full").hide();e.elems.$loading.removeClass("is-active")},upgradeFull:function(){e.elems.$wrap.addClass("overlay-full")}},h=function(b){for(var d=0,f="",c;d<a.passwordData.complexity.numlength;)c=Math.floor(100*Math.random())%
94+33,!a.passwordData.complexity.symbols&&(33<=c&&47>=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='<div id="box-complexity"><div><label class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect" for="checkbox-numbers"><input type="checkbox" id="checkbox-numbers" class="mdl-checkbox__input" name="checkbox-numbers" checked/><span class="mdl-checkbox__label">'+
0<c.length&&(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='<div id="box-complexity"><div><label class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect" for="checkbox-numbers"><input type="checkbox" id="checkbox-numbers" class="mdl-checkbox__input" name="checkbox-numbers" checked/><span class="mdl-checkbox__label">'+
a.config().LANG[35]+'</span></label><label class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect" for="checkbox-uppercase"><input type="checkbox" id="checkbox-uppercase" class="mdl-checkbox__input" name="checkbox-uppercase"/><span class="mdl-checkbox__label">'+a.config().LANG[36]+'</span></label><label class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect" for="checkbox-symbols"><input type="checkbox" id="checkbox-symbols" class="mdl-checkbox__input" name="checkbox-symbols"/><span class="mdl-checkbox__label">'+
a.config().LANG[37]+'</span></label><div class="mdl-textfield mdl-js-textfield textfield-passlength"><input class="mdl-textfield__input" type="number" pattern="[0-9]*" id="passlength" /><label class="mdl-textfield__label" for="passlength">'+a.config().LANG[38]+"</label></div></div></div>";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='<button id="menu-speed-'+c+'" class="mdl-button mdl-js-button mdl-button--icon" type="button" title="'+a.config().LANG[27]+'"><i class="material-icons">more_vert</i></button>',l=l+('<ul class="mdl-menu mdl-js-menu" for="menu-speed-'+c+'">')+('<li class="mdl-menu__item passGen"><i class="material-icons">settings</i>'+
a.config().LANG[28]+"</li>"),l=l+('<li class="mdl-menu__item passComplexity"><i class="material-icons">vpn_key</i>'+a.config().LANG[29]+"</li>"),l=l+('<li class="mdl-menu__item reset"><i class="material-icons">refresh</i>'+a.config().LANG[30]+"</li>");f.after('<div class="password-actions" />');f.next(".password-actions").prepend('<span class="passLevel passLevel-'+c+' fullround" title="'+a.config().LANG[31]+'"></span>').prepend('<i class="showpass material-icons" title="'+a.config().LANG[32]+'">remove_red_eye</i>').prepend(l);
d.on("keyup",function(){a.checkPassLevel(d)});f=d.parent().next();f.find(".passGen").on("click",function(){h(d);d.focus()});f.find(".passComplexity").on("click",function(){k()});f.find(".showpass").on("mouseover",function(){$(this).attr("title",d.val())});f.find(".reset").on("click",function(){d.val("");b.val("");componentHandler.upgradeDom()});d.attr("data-pass-upgraded","true")}});b.find(".passwordfield__input-show").each(function(){var b=$(this),f=$('<i class="showpass material-icons" title="'+
a.config().LANG[32]+'" data-targetid="'+b.attr("id")+'">remove_red_eye</i>'),c=$('<i class="clip-pass-icon material-icons" title="'+a.config().LANG[34]+'" data-clipboard-text="'+b.val()+'">content_paste</i>');b.parent().after(c).after(f);f.on("mouseover",function(){f.attr("title",b.val())})})},n=function(b){g.info("setupDatePicker");var d={format:"YYYY-MM-DD",lang:a.config().LOCALE.substr(0,2),time:!1,cancelText:a.config().LANG[44],okText:a.config().LANG[43],clearText:a.config().LANG[30],nowText:a.config().LANG[56],
$("#passlength").val(a.passwordData.complexity.numlength)}})},l=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='<button id="menu-speed-'+c+'" class="mdl-button mdl-js-button mdl-button--icon" type="button" title="'+a.config().LANG[27]+'"><i class="material-icons">more_vert</i></button>',b=b+('<ul class="mdl-menu mdl-js-menu" for="menu-speed-'+c+'">')+('<li class="mdl-menu__item passGen"><i class="material-icons">settings</i>'+
a.config().LANG[28]+"</li>"),b=b+('<li class="mdl-menu__item passComplexity"><i class="material-icons">vpn_key</i>'+a.config().LANG[29]+"</li>"),b=b+('<li class="mdl-menu__item reset"><i class="material-icons">refresh</i>'+a.config().LANG[30]+"</li>");f.after('<div class="password-actions" />');f.next(".password-actions").prepend('<span class="passLevel passLevel-'+c+' fullround" title="'+a.config().LANG[31]+'"></span>').prepend('<i class="showpass material-icons" title="'+a.config().LANG[32]+'">remove_red_eye</i>').prepend(b);
d.on("keyup",function(){a.checkPassLevel(d)});f=d.parent().next();f.find(".passGen").on("click",function(){h(d);d.focus()});f.find(".passComplexity").on("click",function(){k()});f.find(".showpass").on("mouseover",function(){$(this).attr("title",d.val())});f.find(".reset").on("click",function(){d.val("");var a=$("#"+c+"R");0<a.length&&a.val("");componentHandler.upgradeDom()});d.attr("data-pass-upgraded","true")}});b.find(".passwordfield__input-show").each(function(){var b=$(this),f=$('<i class="showpass material-icons" title="'+
a.config().LANG[32]+'" data-targetid="'+b.attr("id")+'">remove_red_eye</i>'),c=$('<i class="clip-pass-icon material-icons" title="'+a.config().LANG[34]+'" data-clipboard-text="'+b.val()+'">content_paste</i>');b.parent().after(c).after(f);f.on("mouseover",function(){f.attr("title",b.val())})})},m=function(b){g.info("setupDatePicker");var d={format:"YYYY-MM-DD",lang:a.config().LOCALE.substr(0,2),time:!1,cancelText:a.config().LANG[44],okText:a.config().LANG[43],clearText:a.config().LANG[30],nowText:a.config().LANG[56],
minDate:new Date,triggerEvent:"dateIconClick"};b.find(".password-datefield__input").each(function(){var b=$(this);b.bootstrapMaterialDatePicker(d);b.parent().append("<input type='hidden' name='passworddatechange_unix' value='"+moment.tz(b.val(),a.config().TIMEZONE).format("X")+"' />");b.parent().next("i").on("click",function(){b.trigger("dateIconClick")});b.on("change",function(){var c;c=moment.tz(b.val(),a.config().TIMEZONE).format("X");b.parent().find("input[name='passworddatechange_unix']").val(c)})})};
return{passwordDetect:m,password:h,viewsTriggers:{search:function(){var b=$("#frmSearch"),d=$("#res-content");b.find(".icon-searchfav").on("click",function(){var c=$(this).find("i"),d=b.find("input[name='searchfav']");0==d.val()?(c.addClass("mdl-color-text--amber-A200"),c.attr("title",a.config().LANG[53]),d.val(1)):(c.removeClass("mdl-color-text--amber-A200"),c.attr("title",a.config().LANG[52]),d.val(0));b.submit()});var e=b.find("#tags")[0],c=b.find(".search-filters-tags"),g=b.find("i.show-filter");
return{passwordDetect:l,password:h,viewsTriggers:{search:function(){var b=$("#frmSearch"),d=$("#res-content");b.find(".icon-searchfav").on("click",function(){var c=$(this).find("i"),d=b.find("input[name='searchfav']");0==d.val()?(c.addClass("mdl-color-text--amber-A200"),c.attr("title",a.config().LANG[53]),d.val(1)):(c.removeClass("mdl-color-text--amber-A200"),c.attr("title",a.config().LANG[52]),d.val(0));b.submit()});var e=b.find("#tags")[0],c=b.find(".search-filters-tags"),g=b.find("i.show-filter");
d.on("click","#data-search-header .sort-down,#data-search-header .sort-up",function(){var b=$(this);b.parent().find("a").addClass("filterOn");a.appActions().account.sort(b)}).on("click","#search-rows i.icon-favorite",function(){var b=$(this);a.appActions().account.savefavorite(b,function(){"on"===b.data("status")?(b.addClass("mdl-color-text--amber-A100"),b.attr("title",a.config().LANG[50]),b.html("star")):(b.removeClass("mdl-color-text--amber-A100"),b.attr("title",a.config().LANG[49]),b.html("star_border"))})}).on("click",
"#search-rows span.tag",function(){c.is(":hidden")&&g.trigger("click");e.selectize.addItem($(this).data("tag-id"))});g.on("click",function(){var a=$(this);c.is(":hidden")?(c.slideDown("slow"),a.html(a.data("icon-up"))):(c.slideUp("slow"),a.html(a.data("icon-down")))});0<e.selectedOptions.length&&g.trigger("click")},common:function(a){m(a);n(a)}},loading:e,ajax:{complete:function(){g.info("ajax:complete");componentHandler.upgradeDom()}},html:{getList:function(a){var b=$('<ul class="ldap-list-item mdl-list"></ul>'),
"#search-rows span.tag",function(){c.is(":hidden")&&g.trigger("click");e.selectize.addItem($(this).data("tag-id"))});g.on("click",function(){var a=$(this);c.is(":hidden")?(c.slideDown("slow"),a.html(a.data("icon-up"))):(c.slideUp("slow"),a.html(a.data("icon-down")))});0<e.selectedOptions.length&&g.trigger("click")},common:function(a){l(a);m(a)}},loading:e,ajax:{complete:function(){g.info("ajax:complete");componentHandler.upgradeDom()}},html:{getList:function(a){var b=$('<ul class="ldap-list-item mdl-list"></ul>'),
e=$('<li class="mdl-list__item"></li>'),c=$('<span class="mdl-list__item-primary-content"></span>');a.forEach(function(a){var d=c.clone();d.append('<i class="material-icons mdl-list__item-icon">person</i>');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('<a href="#tabs-'+d+'" class="mdl-tabs__tab '+b+'">'+e+"</a>")}}}}};

View File

@@ -1,3 +1,7 @@
<?php
/** @var \SP\DataModel\ApiTokenData $ApiTokenData */
/** @var \SP\Core\UI\ThemeIconsBase $icons */
?>
<div id="box-popup">
<h2 class="center"><?php echo $header; ?><i class="btn-popup-close material-icons">close</i></h2>
@@ -13,10 +17,10 @@
<td class="valField">
<div class="lowres-title"><?php echo __('Usuario'); ?></div>
<select id="selUsers" name="users" class="select-box" required>
<select id="selUsers" name="users" class="select-box" required <?php echo $isDisabled; ?>>
<option value=""><?php echo __('Seleccionar Usuario'); ?></option>
<?php foreach ($users as $user): ?>
<?php $selected = ($gotData && $user->id == $token->authtoken_userId) ? 'selected' : ''; ?>
<?php $selected = ($user->id === $ApiTokenData->getAuthtokenUserId()) ? 'selected' : ''; ?>
<option value="<?php echo $user->id; ?>" <?php echo $selected; ?>><?php echo $user->name; ?></option>
<?php endforeach; ?>
</select>
@@ -28,16 +32,28 @@
<div class="lowres-title"><?php echo __('Acción'); ?></div>
<select id="selActions" name="actions"
class="select-box" required>
class="select-box" required <?php echo $isDisabled; ?>>
<option value=""><?php echo __('Seleccionar Acción'); ?></option>
<?php foreach ($actions as $id => $name): ?>
<?php $selected = ($gotData && $id == $token->authtoken_actionId) ? 'selected' : ''; ?>
<?php $selected = ($id === $ApiTokenData->getAuthtokenActionId()) ? 'selected' : ''; ?>
<option value="<?php echo $id; ?>" <?php echo $selected; ?>><?php echo $name; ?></option>
<?php endforeach; ?>
</select>
</td>
</tr>
<?php if (!$isView): ?>
<tr>
<td class="descField"><?php echo __('Clave'); ?></td>
<td class="valField">
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input id="pass" name="pass" type="password" required
class="mdl-textfield__input passwordfield__input mdl-color-text--indigo-400"
maxlength="50">
<label class="mdl-textfield__label"
for="pass"><?php echo __('Clave'); ?></label>
</div>
</td>
</tr>
<tr>
<td class="descField"><?php echo __('Opciones'); ?></td>
<td class="valField">
@@ -56,22 +72,22 @@
<td class="valField">
<div class="lowres-title"><?php echo __('Token'); ?></div>
<?php echo $gotData ? $token->authtoken_token : ''; ?>
<?php echo $ApiTokenData->getAuthtokenToken(); ?>
</td>
</tr>
<?php endif; ?>
</tbody>
</table>
<input type="hidden" name="itemId" value="<?php echo $gotData ? $token->authtoken_id : ''; ?>"/>
<input type="hidden" name="itemId" value="<?php echo $ApiTokenData->getAuthtokenId(); ?>"/>
<input type="hidden" name="actionId" value="<?php echo $actionId; ?>"/>
<input type="hidden" name="sk" value="">
<input type="hidden" name="isAjax" value="1">
</form>
<div class="action-in-box">
<button
class="mdl-button mdl-js-button mdl-button--fab mdl-button--mini-fab mdl-button--colored <?php echo $icons->getIconSave()->getClassButton(); ?>"
form="frmTokens" title="<?php echo $icons->getIconSave()->getTitle(); ?>">
class="mdl-button mdl-js-button mdl-button--fab mdl-button--mini-fab mdl-button--colored <?php echo $icons->getIconSave()->getClassButton(); ?>"
form="frmTokens" title="<?php echo $icons->getIconSave()->getTitle(); ?>">
<i class="material-icons"><?php echo $icons->getIconSave()->getIcon(); ?></i>
</button>
</div>