From f519f6b23ee316fb3209d75cf8579c641badaafb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20D?= Date: Sat, 9 Dec 2023 11:26:26 +0100 Subject: [PATCH] chore: Refactor AuthTokenRepository MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rubén D --- .../AuthToken/AuthTokenViewBase.php | 4 +- app/modules/web/Forms/AuthTokenForm.php | 10 +- .../views/itemshow/auth_token.inc | 4 +- .../{AuthTokenData.php => AuthToken.php} | 26 +- lib/SP/Domain/Api/Services/ApiService.php | 8 +- .../Ports/AuthTokenRepositoryInterface.php | 155 ++++++ .../Auth/Ports/AuthTokenServiceInterface.php | 16 +- .../Domain/Auth/Services/AuthTokenService.php | 19 +- lib/SP/Domain/Common/Models/Model.php | 2 +- .../Common/Ports/RepositoryInterface.php | 10 +- .../Auth/Repositories/AuthTokenRepository.php | 493 +++++++--------- .../Infrastructure/Database/QueryResult.php | 12 +- .../SP/Domain/Api/Services/ApiServiceTest.php | 14 +- tests/SP/Generators/AuthTokenGenerator.php | 64 +++ .../Repositories/AccountRepositoryTest.php | 6 +- tests/SP/Modules/Api/ApiTestCase.php | 7 +- .../Repositories/AuthTokenRepositoryTest.php | 27 +- .../Repositories/AuthTokenRepositoryTest.php | 525 ++++++++++++++++++ .../AuthToken/AuthTokenServiceTest.php | 18 +- 19 files changed, 1030 insertions(+), 390 deletions(-) rename lib/SP/DataModel/{AuthTokenData.php => AuthToken.php} (75%) create mode 100644 lib/SP/Domain/Auth/Ports/AuthTokenRepositoryInterface.php create mode 100644 tests/SP/Generators/AuthTokenGenerator.php create mode 100644 tests/SP/SP/Tests/Infrastructure/Auth/Repositories/AuthTokenRepositoryTest.php diff --git a/app/modules/web/Controllers/AuthToken/AuthTokenViewBase.php b/app/modules/web/Controllers/AuthToken/AuthTokenViewBase.php index 081e8d8b..24fd93de 100644 --- a/app/modules/web/Controllers/AuthToken/AuthTokenViewBase.php +++ b/app/modules/web/Controllers/AuthToken/AuthTokenViewBase.php @@ -27,7 +27,7 @@ namespace SP\Modules\Web\Controllers\AuthToken; use SP\Core\Acl\Acl; use SP\Core\Application; -use SP\DataModel\AuthTokenData; +use SP\DataModel\AuthToken; use SP\Domain\Auth\Ports\AuthTokenServiceInterface; use SP\Domain\Auth\Services\AuthTokenService; use SP\Domain\Common\Services\ServiceException; @@ -85,7 +85,7 @@ abstract class AuthTokenViewBase extends ControllerBase $authToken = $authTokenId ? $this->authTokenService->getById($authTokenId) - : new AuthTokenData(); + : new AuthToken(); $this->view->assign('authToken', $authToken); diff --git a/app/modules/web/Forms/AuthTokenForm.php b/app/modules/web/Forms/AuthTokenForm.php index bf7f35a6..6fdcc2e0 100644 --- a/app/modules/web/Forms/AuthTokenForm.php +++ b/app/modules/web/Forms/AuthTokenForm.php @@ -24,7 +24,7 @@ namespace SP\Modules\Web\Forms; -use SP\DataModel\AuthTokenData; +use SP\DataModel\AuthToken; use SP\Domain\Auth\Services\AuthTokenService; use SP\Domain\Core\Acl\AclActionsInterface; use SP\Domain\Core\Exceptions\ValidationException; @@ -36,8 +36,8 @@ use SP\Domain\Core\Exceptions\ValidationException; */ final class AuthTokenForm extends FormBase implements FormInterface { - protected ?AuthTokenData $authTokenData = null; - protected bool $refresh = false; + protected ?AuthToken $authTokenData = null; + protected bool $refresh = false; /** * Validar el formulario @@ -74,7 +74,7 @@ final class AuthTokenForm extends FormBase implements FormInterface { $this->refresh = $this->request->analyzeBool('refreshtoken', false); - $this->authTokenData = new AuthTokenData(); + $this->authTokenData = new AuthToken(); $this->authTokenData->setId($this->itemId); $this->authTokenData->setUserId($this->request->analyzeInt('users')); $this->authTokenData->setActionId($this->request->analyzeInt('actions')); @@ -106,7 +106,7 @@ final class AuthTokenForm extends FormBase implements FormInterface return $this->refresh; } - public function getItemData(): ?AuthTokenData + public function getItemData(): ?AuthToken { return $this->authTokenData; } diff --git a/app/modules/web/themes/material-blue/views/itemshow/auth_token.inc b/app/modules/web/themes/material-blue/views/itemshow/auth_token.inc index a18ec10b..854975eb 100644 --- a/app/modules/web/themes/material-blue/views/itemshow/auth_token.inc +++ b/app/modules/web/themes/material-blue/views/itemshow/auth_token.inc @@ -23,14 +23,14 @@ */ /** - * @var AuthTokenData $authToken + * @var AuthToken $authToken * @var ThemeIconsInterface $icons * @var ConfigDataInterface $configData * @var callable $_getvar * @var TemplateInterface $this */ -use SP\DataModel\AuthTokenData; +use SP\DataModel\AuthToken; use SP\Domain\Config\Ports\ConfigDataInterface; use SP\Domain\Core\UI\ThemeIconsInterface; use SP\Mvc\View\Components\SelectItem; diff --git a/lib/SP/DataModel/AuthTokenData.php b/lib/SP/DataModel/AuthToken.php similarity index 75% rename from lib/SP/DataModel/AuthTokenData.php rename to lib/SP/DataModel/AuthToken.php index 655f4fd5..38a59565 100644 --- a/lib/SP/DataModel/AuthTokenData.php +++ b/lib/SP/DataModel/AuthToken.php @@ -24,28 +24,25 @@ namespace SP\DataModel; -use SP\Domain\Common\Adapters\DataModelInterface; use SP\Domain\Common\Models\Model; /** - * Class AuthTokenData - * - * @package SP\DataModel + * Class AuthToken */ -class AuthTokenData extends Model implements DataModelInterface +class AuthToken extends Model { - public ?int $userId = null; - public ?string $token = null; - public ?int $createdBy = null; - public ?int $startDate = null; - public ?int $actionId = null; - public ?string $hash = null; protected ?int $id = null; + protected ?int $userId = null; + protected ?string $token = null; + protected ?int $createdBy = null; + protected ?int $startDate = null; + protected ?int $actionId = null; + protected ?string $hash = null; protected ?string $vault = null; public function getId(): ?int { - return (int)$this->id; + return $this->id; } public function getVault(): ?string @@ -73,11 +70,6 @@ class AuthTokenData extends Model implements DataModelInterface return $this->startDate; } - public function getName(): ?string - { - return null; - } - public function getActionId(): ?int { return $this->actionId; diff --git a/lib/SP/Domain/Api/Services/ApiService.php b/lib/SP/Domain/Api/Services/ApiService.php index ec05ec9b..941d41e5 100644 --- a/lib/SP/Domain/Api/Services/ApiService.php +++ b/lib/SP/Domain/Api/Services/ApiService.php @@ -30,7 +30,7 @@ use SP\Core\Context\ContextException; use SP\Core\Crypt\Crypt; use SP\Core\Crypt\Hash; use SP\Core\Crypt\Vault; -use SP\DataModel\AuthTokenData; +use SP\DataModel\AuthToken; use SP\Domain\Api\Ports\ApiRequestInterface; use SP\Domain\Api\Ports\ApiServiceInterface; use SP\Domain\Auth\Ports\AuthTokenServiceInterface; @@ -66,9 +66,9 @@ final class ApiService extends Service implements ApiServiceInterface private const STATUS_INITIALIZED = 0; private const STATUS_INITIALIZING = 1; private TrackServiceInterface $trackService; - private TrackRequest $trackRequest; - private ?AuthTokenData $authTokenData = null; - private ?string $helpClass = null; + private TrackRequest $trackRequest; + private ?AuthToken $authTokenData = null; + private ?string $helpClass = null; private ?int $status = null; /** diff --git a/lib/SP/Domain/Auth/Ports/AuthTokenRepositoryInterface.php b/lib/SP/Domain/Auth/Ports/AuthTokenRepositoryInterface.php new file mode 100644 index 00000000..5f869881 --- /dev/null +++ b/lib/SP/Domain/Auth/Ports/AuthTokenRepositoryInterface.php @@ -0,0 +1,155 @@ +. + */ + +namespace SP\Domain\Auth\Ports; + +use Exception; +use SP\DataModel\AuthToken; +use SP\DataModel\ItemSearchData; +use SP\Domain\Common\Ports\RepositoryInterface; +use SP\Domain\Core\Exceptions\ConstraintException; +use SP\Domain\Core\Exceptions\QueryException; +use SP\Infrastructure\Common\Repositories\DuplicatedItemException; +use SP\Infrastructure\Database\QueryResult; + +/** + * Class AuthTokenRepository + * + * @template T of AuthToken + */ +interface AuthTokenRepositoryInterface extends RepositoryInterface +{ + /** + * @param int $id + * @return QueryResult + * @throws ConstraintException + * @throws QueryException + */ + public function delete(int $id): QueryResult; + + /** + * Returns the item for given id + * + * @param int $authTokenId + * + * @return QueryResult + */ + public function getById(int $authTokenId): QueryResult; + + /** + * Returns all the items + * + * @return QueryResult + */ + public function getAll(): QueryResult; + + /** + * Deletes all the items for given ids + * + * @param array $authTokensId + * + * @return QueryResult + * @throws ConstraintException + * @throws QueryException + */ + public function deleteByIdBatch(array $authTokensId): QueryResult; + + /** + * Searches for items by a given filter + * + * @param ItemSearchData $itemSearchData + * + * @return QueryResult + * @throws ConstraintException + * @throws QueryException + * @throws Exception + */ + public function search(ItemSearchData $itemSearchData): QueryResult; + + /** + * Creates an item + * + * @param AuthToken $authToken + * + * @return QueryResult + * @throws ConstraintException + * @throws DuplicatedItemException + * @throws QueryException + */ + public function create(AuthToken $authToken): QueryResult; + + /** + * Obtener el token de la API de un usuario + * + * @param int $userId + * @return QueryResult + */ + public function getTokenByUserId(int $userId): QueryResult; + + /** + * Updates an item + * + * @param AuthToken $authToken + * @return bool + * @throws ConstraintException + * @throws DuplicatedItemException + * @throws QueryException + */ + public function update(AuthToken $authToken): bool; + + /** + * Regenerar el hash de los tokens de un usuario + * + * @param int $userId + * @param string $token + * + * @return int + * @throws ConstraintException + * @throws QueryException + */ + public function refreshTokenByUserId(int $userId, string $token): int; + + /** + * Regenerar el hash de los tokens de un usuario + * + * @param int $userId + * @param string $vault + * @param string $hash + * + * @return int + * @throws ConstraintException + * @throws QueryException + */ + public function refreshVaultByUserId(int $userId, string $vault, string $hash): int; + + /** + * Devolver los datos de un token + * + * @param $actionId int El id de la accion + * @param $token string El token de seguridad + * + * @return QueryResult + */ + public function getTokenByToken(int $actionId, string $token): QueryResult; +} diff --git a/lib/SP/Domain/Auth/Ports/AuthTokenServiceInterface.php b/lib/SP/Domain/Auth/Ports/AuthTokenServiceInterface.php index b6175f79..d9b00463 100644 --- a/lib/SP/Domain/Auth/Ports/AuthTokenServiceInterface.php +++ b/lib/SP/Domain/Auth/Ports/AuthTokenServiceInterface.php @@ -4,7 +4,7 @@ * * @author nuxsmin * @link https://syspass.org - * @copyright 2012-2022, Rubén Domínguez nuxsmin@$syspass.org + * @copyright 2012-2023, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. * @@ -28,7 +28,7 @@ namespace SP\Domain\Auth\Ports; use Defuse\Crypto\Exception\CryptoException; use Defuse\Crypto\Exception\EnvironmentIsBrokenException; use Exception; -use SP\DataModel\AuthTokenData; +use SP\DataModel\AuthToken; use SP\DataModel\ItemSearchData; use SP\Domain\Auth\Services\AuthTokenService; use SP\Domain\Common\Services\ServiceException; @@ -56,7 +56,7 @@ interface AuthTokenServiceInterface * @throws ConstraintException * @throws QueryException */ - public function getById(int $id): AuthTokenData; + public function getById(int $id): AuthToken; /** * @throws ConstraintException @@ -81,12 +81,12 @@ interface AuthTokenServiceInterface * @throws ConstraintException * @throws QueryException */ - public function create(AuthTokenData $itemData): int; + public function create(AuthToken $itemData): int; /** * @throws Exception */ - public function refreshAndUpdate(AuthTokenData $itemData): void; + public function refreshAndUpdate(AuthToken $itemData): void; /** * @throws CryptoException @@ -97,14 +97,14 @@ interface AuthTokenServiceInterface * @throws NoSuchItemException * @throws ServiceException */ - public function update(AuthTokenData $itemData, ?string $token = null): void; + public function update(AuthToken $itemData, ?string $token = null): void; /** * @throws SPException * @throws ConstraintException * @throws QueryException */ - public function updateRaw(AuthTokenData $itemData): void; + public function updateRaw(AuthToken $itemData): void; /** * Devolver los datos de un token @@ -116,7 +116,7 @@ interface AuthTokenServiceInterface public function getTokenByToken(int $actionId, string $token); /** - * @return AuthTokenData[] + * @return AuthToken[] * @throws ConstraintException * @throws QueryException */ diff --git a/lib/SP/Domain/Auth/Services/AuthTokenService.php b/lib/SP/Domain/Auth/Services/AuthTokenService.php index d820c121..6aa4fe8e 100644 --- a/lib/SP/Domain/Auth/Services/AuthTokenService.php +++ b/lib/SP/Domain/Auth/Services/AuthTokenService.php @@ -31,8 +31,9 @@ use SP\Core\Acl\Acl; use SP\Core\Application; use SP\Core\Crypt\Hash; use SP\Core\Crypt\Vault; -use SP\DataModel\AuthTokenData; +use SP\DataModel\AuthToken; use SP\DataModel\ItemSearchData; +use SP\Domain\Auth\Ports\AuthTokenRepositoryInterface; use SP\Domain\Auth\Ports\AuthTokenServiceInterface; use SP\Domain\Common\Services\Service; use SP\Domain\Common\Services\ServiceException; @@ -71,7 +72,7 @@ final class AuthTokenService extends Service implements AuthTokenServiceInterfac private AuthTokenRepository $authTokenRepository; - public function __construct(Application $application, AuthTokenRepository $authTokenRepository) + public function __construct(Application $application, AuthTokenRepositoryInterface $authTokenRepository) { parent::__construct($application); @@ -132,7 +133,7 @@ final class AuthTokenService extends Service implements AuthTokenServiceInterfac * @throws ConstraintException * @throws QueryException */ - public function getById(int $id): AuthTokenData + public function getById(int $id): AuthToken { return $this->authTokenRepository->getById($id)->getData(); } @@ -179,7 +180,7 @@ final class AuthTokenService extends Service implements AuthTokenServiceInterfac * @throws ConstraintException * @throws QueryException */ - public function create(AuthTokenData $itemData): int + public function create(AuthToken $itemData): int { return $this->authTokenRepository->create($this->injectSecureData($itemData)); } @@ -193,7 +194,7 @@ final class AuthTokenService extends Service implements AuthTokenServiceInterfac * @throws ConstraintException * @throws QueryException */ - private function injectSecureData(AuthTokenData $authTokenData, ?string $token = null): AuthTokenData + private function injectSecureData(AuthToken $authTokenData, ?string $token = null): AuthToken { if ($token === null) { $token = $this->authTokenRepository @@ -254,7 +255,7 @@ final class AuthTokenService extends Service implements AuthTokenServiceInterfac /** * @throws Exception */ - public function refreshAndUpdate(AuthTokenData $itemData): void + public function refreshAndUpdate(AuthToken $itemData): void { $this->transactionAware( function () use ($itemData) { @@ -287,7 +288,7 @@ final class AuthTokenService extends Service implements AuthTokenServiceInterfac * @throws NoSuchItemException * @throws ServiceException */ - public function update(AuthTokenData $itemData, ?string $token = null): void + public function update(AuthToken $itemData, ?string $token = null): void { if ($this->authTokenRepository->update($this->injectSecureData($itemData, $token)) === 0) { throw new NoSuchItemException(__u('Token not found')); @@ -299,7 +300,7 @@ final class AuthTokenService extends Service implements AuthTokenServiceInterfac * @throws ConstraintException * @throws QueryException */ - public function updateRaw(AuthTokenData $itemData): void + public function updateRaw(AuthToken $itemData): void { if ($this->authTokenRepository->update($itemData) === 0) { throw new NoSuchItemException(__u('Token not found')); @@ -325,7 +326,7 @@ final class AuthTokenService extends Service implements AuthTokenServiceInterfac } /** - * @return AuthTokenData[] + * @return AuthToken[] * @throws ConstraintException * @throws QueryException */ diff --git a/lib/SP/Domain/Common/Models/Model.php b/lib/SP/Domain/Common/Models/Model.php index 89c3e09b..05234b3e 100644 --- a/lib/SP/Domain/Common/Models/Model.php +++ b/lib/SP/Domain/Common/Models/Model.php @@ -29,7 +29,7 @@ use JsonException; use JsonSerializable; /** - * Class DataModel + * Class Model */ abstract class Model implements JsonSerializable, ArrayAccess { diff --git a/lib/SP/Domain/Common/Ports/RepositoryInterface.php b/lib/SP/Domain/Common/Ports/RepositoryInterface.php index 33e5c8a4..0f339aa9 100644 --- a/lib/SP/Domain/Common/Ports/RepositoryInterface.php +++ b/lib/SP/Domain/Common/Ports/RepositoryInterface.php @@ -25,6 +25,8 @@ namespace SP\Domain\Common\Ports; use Closure; +use Exception; +use SP\Domain\Common\Services\ServiceException; use SP\Infrastructure\Database\QueryResult; /** @@ -37,12 +39,12 @@ interface RepositoryInterface /** * Bubbles a Closure in a database transaction * - * @param \Closure $closure + * @param Closure $closure * @param object $newThis * * @return mixed - * @throws \SP\Domain\Common\Services\ServiceException - * @throws \Exception + * @throws ServiceException + * @throws Exception */ public function transactionAware(Closure $closure, object $newThis): mixed; @@ -54,7 +56,7 @@ interface RepositoryInterface * @param string|null $where * @param array|null $bindValues * - * @return \SP\Infrastructure\Database\QueryResult + * @return QueryResult */ public function getAny( array $columns, diff --git a/lib/SP/Infrastructure/Auth/Repositories/AuthTokenRepository.php b/lib/SP/Infrastructure/Auth/Repositories/AuthTokenRepository.php index 239960c3..ed9572a7 100644 --- a/lib/SP/Infrastructure/Auth/Repositories/AuthTokenRepository.php +++ b/lib/SP/Infrastructure/Auth/Repositories/AuthTokenRepository.php @@ -4,7 +4,7 @@ * * @author nuxsmin * @link https://syspass.org - * @copyright 2012-2022, Rubén Domínguez nuxsmin@$syspass.org + * @copyright 2012-2023, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. * @@ -24,10 +24,10 @@ namespace SP\Infrastructure\Auth\Repositories; -use RuntimeException; -use SP\DataModel\AuthTokenData; +use Exception; +use SP\DataModel\AuthToken; use SP\DataModel\ItemSearchData; -use SP\Domain\Common\Ports\RepositoryInterface; +use SP\Domain\Auth\Ports\AuthTokenRepositoryInterface; use SP\Domain\Core\Exceptions\ConstraintException; use SP\Domain\Core\Exceptions\QueryException; use SP\Infrastructure\Common\Repositories\DuplicatedItemException; @@ -36,61 +36,56 @@ use SP\Infrastructure\Common\Repositories\RepositoryItemTrait; use SP\Infrastructure\Database\QueryData; use SP\Infrastructure\Database\QueryResult; +use function SP\__u; + /** * Class AuthTokenRepository * - * @package SP\Infrastructure\Common\Repositories\ApiToken + * @template T of AuthToken */ -final class AuthTokenRepository extends Repository implements RepositoryInterface +final class AuthTokenRepository extends Repository implements AuthTokenRepositoryInterface { use RepositoryItemTrait; + public const TABLE = 'AuthToken'; + /** - * Deletes an item - * - * @param int $id - * - * @return int + * @param int $id + * @return QueryResult * @throws ConstraintException * @throws QueryException */ - public function delete(int $id): int + public function delete(int $id): QueryResult { - $queryData = new QueryData(); - $queryData->setQuery('DELETE FROM AuthToken WHERE id = ? LIMIT 1'); - $queryData->addParam($id); - $queryData->setOnErrorMessage(__u('Internal error')); + $query = $this->queryFactory + ->newDelete() + ->from(self::TABLE) + ->where('id = :id') + ->bindValues(['id' => $id]); - return $this->db->doQuery($queryData)->getAffectedNumRows(); + $queryData = QueryData::build($query)->setOnErrorMessage(__u('Internal error')); + + return $this->db->doQuery($queryData); } /** * Returns the item for given id * - * @param int $id + * @param int $authTokenId * - * @return QueryResult - * @throws ConstraintException - * @throws QueryException + * @return QueryResult */ - public function getById(int $id): QueryResult + public function getById(int $authTokenId): QueryResult { - $query = /** @lang SQL */ - 'SELECT id, - userId, - actionId, - createdBy, - startDate, - vault, - token, - `hash` - FROM AuthToken - WHERE id = ? LIMIT 1'; + $query = $this->queryFactory + ->newSelect() + ->from(self::TABLE) + ->cols(AuthToken::getCols()) + ->where('id = :id') + ->bindValues(['id' => $authTokenId]) + ->limit(1); - $queryData = new QueryData(); - $queryData->setMapClassName(AuthTokenData::class); - $queryData->setQuery($query); - $queryData->addParam($id); + $queryData = QueryData::buildWithMapper($query, AuthToken::class); return $this->db->doSelect($queryData); } @@ -98,302 +93,237 @@ final class AuthTokenRepository extends Repository implements RepositoryInterfac /** * Returns all the items * - * @return QueryResult - * - * @throws ConstraintException - * @throws QueryException + * @return QueryResult */ public function getAll(): QueryResult { - $query = /** @lang SQL */ - 'SELECT id, - userId, - actionId, - createdBy, - startDate, - vault, - token, - `hash` - FROM AuthToken - ORDER BY actionId, userId'; + $query = $this->queryFactory + ->newSelect() + ->from(self::TABLE) + ->cols(AuthToken::getCols()); - $queryData = new QueryData(); - $queryData->setMapClassName(AuthTokenData::class); - $queryData->setQuery($query); - - return $this->db->doSelect($queryData); - } - - /** - * Returns all the items for given ids - * - * @param array $ids - * - * @return void - */ - public function getByIdBatch(array $ids): QueryResult - { - throw new RuntimeException('Not implemented'); + return $this->db->doSelect(QueryData::buildWithMapper($query, AuthToken::class)); } /** * Deletes all the items for given ids * - * @param array $ids - * - * @return int - * @throws ConstraintException - * @throws QueryException - */ - public function deleteByIdBatch(array $ids): int - { - if (count($ids) === 0) { - return 0; - } - - $queryData = new QueryData(); - $queryData->setQuery('DELETE FROM AuthToken WHERE id IN ('.$this->buildParamsFromArray($ids).')'); - $queryData->setParams($ids); - $queryData->setOnErrorMessage(__u('Internal error')); - - return $this->db->doQuery($queryData)->getAffectedNumRows(); - } - - /** - * Checks whether the item is in use or not - * - * @param $id int - * - * @return void - */ - public function checkInUse(int $id): bool - { - throw new RuntimeException('Not implemented'); - } - - /** - * Searches for items by a given filter - * - * @param ItemSearchData $itemSearchData + * @param array $authTokensId * * @return QueryResult * @throws ConstraintException * @throws QueryException */ - public function search(ItemSearchData $itemSearchData): QueryResult + public function deleteByIdBatch(array $authTokensId): QueryResult { - $queryData = new QueryData(); - $queryData->setSelect( - 'AuthToken.id, - AuthToken.userId, - AuthToken.actionId, - AuthToken.token, - CONCAT(User.name, \' (\', User.login, \')\') AS userLogin' - ); - $queryData->setFrom( - 'AuthToken - INNER JOIN User ON AuthToken.userid = User.id' - ); - - if (!empty($itemSearchData->getSeachString())) { - $queryData->setWhere('User.login LIKE ? OR User.name LIKE ?'); - - $search = '%'.$itemSearchData->getSeachString().'%'; - $queryData->addParam($search); - $queryData->addParam($search); + if (count($authTokensId) === 0) { + return new QueryResult(); } - $queryData->setOrder('User.login, AuthToken.actionId'); - $queryData->setLimit( - '?,?', - [$itemSearchData->getLimitStart(), $itemSearchData->getLimitCount()] - ); + $query = $this->queryFactory + ->newDelete() + ->from(self::TABLE) + ->where('id IN (:ids)', ['ids' => $authTokensId]); - return $this->db->doSelect($queryData, true); + $queryData = QueryData::build($query)->setOnErrorMessage(__u('Internal error')); + + return $this->db->doQuery($queryData); + } + + /** + * Searches for items by a given filter + * + * @param ItemSearchData $itemSearchData + * + * @return QueryResult + * @throws ConstraintException + * @throws QueryException + * @throws Exception + */ + public function search(ItemSearchData $itemSearchData): QueryResult + { + $query = $this->queryFactory + ->newSelect() + ->from(self::TABLE) + ->innerJoin('User', 'AuthToken.userid = User.id') + ->cols([ + 'AuthToken.id', + 'AuthToken.userId', + 'AuthToken.actionId', + 'AuthToken.token', + 'User.name', + 'User.login' + ]) + ->orderBy(['name ASC', 'clientName ASC']) + ->limit($itemSearchData->getLimitCount()) + ->offset($itemSearchData->getLimitStart()); + + if (!empty($itemSearchData->getSeachString())) { + $query->where('User.login LIKE :userLogin OR User.name LIKE :userName'); + + $search = '%' . $itemSearchData->getSeachString() . '%'; + + $query->bindValues(['userLogin' => $search, 'userName' => $search]); + } + + return $this->db->doSelect(QueryData::build($query), true); } /** * Creates an item * - * @param AuthTokenData $itemData + * @param AuthToken $authToken * - * @return int - * @throws DuplicatedItemException + * @return QueryResult * @throws ConstraintException + * @throws DuplicatedItemException * @throws QueryException */ - public function create($itemData): int + public function create(AuthToken $authToken): QueryResult { - if ($this->checkDuplicatedOnAdd($itemData)) { - throw new DuplicatedItemException(__u('The authorization already exist')); + if ($this->checkDuplicatedOnAdd($authToken)) { + throw new DuplicatedItemException(__u('Authorization already exist')); } - $query = /** @lang SQL */ - 'INSERT INTO AuthToken - SET userId = ?, - actionId = ?, - createdBy = ?, - token = ?, - vault = ?, - `hash` = ?, - startDate = UNIX_TIMESTAMP()'; + $query = $this->queryFactory + ->newInsert() + ->into(self::TABLE) + ->cols($authToken->toArray(null, ['id', 'startDate'])) + ->set('startDate', 'UNIX_TIMESTAMP()'); - $queryData = new QueryData(); - $queryData->setQuery($query); - $queryData->setParams([ - $itemData->getUserId(), - $itemData->getActionId(), - $itemData->getCreatedBy(), - $itemData->getToken(), - $itemData->getVault(), - $itemData->getHash(), - ]); - $queryData->setOnErrorMessage(__u('Internal error')); + $queryData = QueryData::build($query)->setOnErrorMessage(__u('Internal error')); - return $this->db->doQuery($queryData)->getLastId(); + return $this->db->doQuery($queryData); } /** * Checks whether the item is duplicated on adding * - * @param AuthTokenData $itemData - * + * @param AuthToken $authToken * @return bool * @throws ConstraintException * @throws QueryException */ - public function checkDuplicatedOnAdd($itemData): bool + private function checkDuplicatedOnAdd(AuthToken $authToken): bool { - $query = /** @lang SQL */ - 'SELECT id FROM AuthToken - WHERE (userId = ? OR token = ?) - AND actionId = ? LIMIT 1'; + $query = $this->queryFactory + ->newSelect() + ->cols(['id']) + ->from(self::TABLE) + ->where('userId = :userId') + ->orWhere('token = :token') + ->where('actionId = :actionId') + ->bindValues( + [ + 'userId' => $authToken->getUserId(), + 'token' => $authToken->getToken(), + 'actionId' => $authToken->getActionId() + ] + ); - $queryData = new QueryData(); - $queryData->setQuery($query); - $queryData->setParams([ - $itemData->getUserId(), - $itemData->getToken(), - $itemData->getActionId(), - ]); - - return $this->db->doSelect($queryData)->getNumRows() === 1; + return $this->db->doQuery(QueryData::build($query))->getNumRows() === 1; } /** * Obtener el token de la API de un usuario * - * @param int $id - * - * @return string - * @throws ConstraintException - * @throws QueryException + * @param int $userId + * @return QueryResult */ - public function getTokenByUserId(int $id): ?string + public function getTokenByUserId(int $userId): QueryResult { - $queryData = new QueryData(); - $queryData->setQuery('SELECT token FROM AuthToken WHERE userId = ? AND token <> \'\' LIMIT 1'); - $queryData->addParam($id); + $query = $this->queryFactory + ->newSelect() + ->from(self::TABLE) + ->cols(AuthToken::getCols()) + ->where('userId = :userId') + ->where('token <> \'\'') + ->bindValues(['userId' => $userId]) + ->limit(1); - $result = $this->db->doSelect($queryData); + $queryData = QueryData::buildWithMapper($query, AuthToken::class); - return $result->getNumRows() === 1 ? $result->getData()->token : null; + return $this->db->doSelect($queryData); } /** * Updates an item * - * @param AuthTokenData $itemData - * - * @return int - * @throws DuplicatedItemException + * @param AuthToken $authToken + * @return bool * @throws ConstraintException + * @throws DuplicatedItemException * @throws QueryException */ - public function update($itemData): int + public function update(AuthToken $authToken): bool { - if ($this->checkDuplicatedOnUpdate($itemData)) { - throw new DuplicatedItemException(__u('The authorization already exist')); + if ($this->checkDuplicatedOnUpdate($authToken)) { + throw new DuplicatedItemException(__u('Authorization already exist')); } - $query = /** @lang SQL */ - 'UPDATE AuthToken - SET userId = ?, - actionId = ?, - createdBy = ?, - token = ?, - vault = ?, - `hash` = ?, - startDate = UNIX_TIMESTAMP() - WHERE id = ? LIMIT 1'; + $query = $this->queryFactory + ->newUpdate() + ->table(self::TABLE) + ->cols($authToken->toArray(null, ['id', 'startDate'])) + ->set('startDate', 'UNIX_TIMESTAMP()') + ->where('id = :id') + ->limit(1) + ->bindValues(['id' => $authToken->getId()]); - $queryData = new QueryData(); - $queryData->setQuery($query); - $queryData->setParams([ - $itemData->getUserId(), - $itemData->getActionId(), - $itemData->getCreatedBy(), - $itemData->getToken(), - $itemData->getVault(), - $itemData->getHash(), - $itemData->getId(), - ]); - $queryData->setOnErrorMessage(__u('Internal error')); + $queryData = QueryData::build($query)->setOnErrorMessage(__u('Internal error')); - return $this->db->doQuery($queryData)->getAffectedNumRows(); + return $this->db->doQuery($queryData)->getAffectedNumRows() === 1; } /** * Checks whether the item is duplicated on updating * - * @param AuthTokenData $itemData - * + * @param AuthToken $authToken * @return bool * @throws ConstraintException * @throws QueryException */ - public function checkDuplicatedOnUpdate($itemData): bool + private function checkDuplicatedOnUpdate(AuthToken $authToken): bool { - $query = /** @lang SQL */ - 'SELECT id FROM AuthToken - WHERE (userId = ? OR token = ?) - AND actionId = ? - AND id <> ? LIMIT 1'; + $query = $this->queryFactory + ->newSelect() + ->cols(['id']) + ->from(self::TABLE) + ->where('(userId = :userId OR token = :token)') + ->where('actionId = :actionId') + ->where('id <> :id') + ->bindValues( + [ + 'id' => $authToken->getId(), + 'userId' => $authToken->getUserId(), + 'token' => $authToken->getToken(), + 'actionId' => $authToken->getActionId() + ] + ); - $queryData = new QueryData(); - $queryData->setQuery($query); - $queryData->setParams([ - $itemData->getUserId(), - $itemData->getToken(), - $itemData->getActionId(), - $itemData->getId(), - ]); - - return $this->db->doSelect($queryData)->getNumRows() === 1; + return $this->db->doQuery(QueryData::build($query))->getNumRows() === 1; } /** * Regenerar el hash de los tokens de un usuario * - * @param int $id - * @param string $token + * @param int $userId + * @param string $token * * @return int * @throws ConstraintException * @throws QueryException */ - public function refreshTokenByUserId(int $id, string $token): int + public function refreshTokenByUserId(int $userId, string $token): int { - $query = /** @lang SQL */ - 'UPDATE AuthToken - SET token = ?, - startDate = UNIX_TIMESTAMP() - WHERE userId = ?'; + $query = $this->queryFactory + ->newUpdate() + ->table(self::TABLE) + ->col('token', $token) + ->set('startDate', 'UNIX_TIMESTAMP()') + ->where('userId = :userId', ['userId' => $userId]); - $queryData = new QueryData(); - $queryData->setQuery($query); - $queryData->setParams([$token, $id]); - $queryData->setOnErrorMessage(__u('Internal error')); + $queryData = QueryData::build($query)->setOnErrorMessage(__u('Internal error')); return $this->db->doQuery($queryData)->getAffectedNumRows(); } @@ -401,74 +331,51 @@ final class AuthTokenRepository extends Repository implements RepositoryInterfac /** * Regenerar el hash de los tokens de un usuario * - * @param int $id - * @param string $vault - * @param string $hash + * @param int $userId + * @param string $vault + * @param string $hash * * @return int * @throws ConstraintException * @throws QueryException */ - public function refreshVaultByUserId(int $id, string $vault, string $hash): int + public function refreshVaultByUserId(int $userId, string $vault, string $hash): int { - $query = /** @lang SQL */ - 'UPDATE AuthToken - SET vault = ?, - `hash` = ?, - startDate = UNIX_TIMESTAMP() - WHERE userId = ? AND vault IS NOT NULL'; + $query = $this->queryFactory + ->newUpdate() + ->table(self::TABLE) + ->cols([ + 'vault' => $vault, + 'hash' => $hash + ]) + ->set('startDate', 'UNIX_TIMESTAMP()') + ->where('userId = :userId', ['userId' => $userId]) + ->where('vault IS NOT NULL'); - $queryData = new QueryData(); - $queryData->setQuery($query); - $queryData->setParams([$vault, $hash, $id]); - $queryData->setOnErrorMessage(__u('Internal error')); + $queryData = QueryData::build($query)->setOnErrorMessage(__u('Internal error')); return $this->db->doQuery($queryData)->getAffectedNumRows(); } - /** - * Obtener el usuario a partir del token - * - * @param $token string El token de autorización - * - * @return false|int - * @throws ConstraintException - * @throws QueryException - */ - public function getUserIdForToken(string $token) - { - $queryData = new QueryData(); - $queryData->setQuery('SELECT userId FROM AuthToken WHERE token = ? LIMIT 1'); - $queryData->addParam($token); - - $result = $this->db->doSelect($queryData); - - return $result->getNumRows() === 1 ? (int)$result->getData()->userId : false; - } - /** * Devolver los datos de un token * * @param $actionId int El id de la accion * @param $token string El token de seguridad * - * @return QueryResult - * @throws ConstraintException - * @throws QueryException + * @return QueryResult */ public function getTokenByToken(int $actionId, string $token): QueryResult { - $query = /** @lang SQL */ - 'SELECT id, actionId, userId, vault, `hash`, token - FROM AuthToken - WHERE actionId = ? - AND token = ? LIMIT 1'; + $query = $this->queryFactory + ->newSelect() + ->from(self::TABLE) + ->cols(AuthToken::getCols()) + ->where('actionId = :actionId') + ->where('token = :token') + ->bindValues(['actionId' => $actionId, 'token' => $token]) + ->limit(1); - $queryData = new QueryData(); - $queryData->setMapClassName(AuthTokenData::class); - $queryData->setQuery($query); - $queryData->setParams([$actionId, $token]); - - return $this->db->doSelect($queryData); + return $this->db->doSelect(QueryData::buildWithMapper($query, AuthToken::class)); } } diff --git a/lib/SP/Infrastructure/Database/QueryResult.php b/lib/SP/Infrastructure/Database/QueryResult.php index d7dfa416..91e83dc8 100644 --- a/lib/SP/Infrastructure/Database/QueryResult.php +++ b/lib/SP/Infrastructure/Database/QueryResult.php @@ -24,6 +24,7 @@ namespace SP\Infrastructure\Database; +use SP\Domain\Common\Models\Model; use SP\Domain\Core\Exceptions\SPException; use function SP\__u; @@ -31,7 +32,7 @@ use function SP\__u; /** * Class QueryResult * - * @package SP\Infrastructure\Database + * @template T of Model */ class QueryResult { @@ -71,8 +72,6 @@ class QueryResult } /** - * @template T - * * @param class-string|null $dataType * * @return T|mixed|null @@ -100,8 +99,6 @@ class QueryResult } /** - * @template T - * * @param class-string|null $dataType * * @return T[] @@ -159,9 +156,4 @@ class QueryResult return $this; } - - public function getDataType(): ?string - { - return $this->dataType; - } } diff --git a/tests/SP/Domain/Api/Services/ApiServiceTest.php b/tests/SP/Domain/Api/Services/ApiServiceTest.php index 1b677e41..17992768 100644 --- a/tests/SP/Domain/Api/Services/ApiServiceTest.php +++ b/tests/SP/Domain/Api/Services/ApiServiceTest.php @@ -31,7 +31,7 @@ use ReflectionClass; use SP\Core\Context\ContextException; use SP\Core\Crypt\Crypt; use SP\Core\Crypt\Vault; -use SP\DataModel\AuthTokenData; +use SP\DataModel\AuthToken; use SP\Domain\Api\Ports\ApiRequestInterface; use SP\Domain\Api\Services\ApiService; use SP\Domain\Auth\Ports\AuthTokenServiceInterface; @@ -270,7 +270,7 @@ class ApiServiceTest extends UnitaryTestCase $userId = self::$faker->randomNumber(); - $authTokenData = new AuthTokenData(['actionId' => $actionId, 'userId' => $userId]); + $authTokenData = new AuthToken(['actionId' => $actionId, 'userId' => $userId]); $this->authTokenService ->expects(self::once()) @@ -407,7 +407,7 @@ class ApiServiceTest extends UnitaryTestCase $userId = self::$faker->randomNumber(); - $authTokenData = new AuthTokenData(['actionId' => self::$faker->randomNumber(), 'userId' => $userId]); + $authTokenData = new AuthToken(['actionId' => self::$faker->randomNumber(), 'userId' => $userId]); $this->authTokenService ->expects(self::once()) @@ -451,7 +451,7 @@ class ApiServiceTest extends UnitaryTestCase $userId = self::$faker->randomNumber(); $authTokenData = - new AuthTokenData( + new AuthToken( ['actionId' => $actionId, 'userId' => $userId, 'hash' => $authTokenHash, 'vault' => serialize($vault)] ); @@ -500,7 +500,7 @@ class ApiServiceTest extends UnitaryTestCase $userId = self::$faker->randomNumber(); $authTokenData = - new AuthTokenData( + new AuthToken( ['actionId' => $actionId, 'userId' => $userId, 'hash' => $authTokenHash, 'vault' => serialize($vault)] ); @@ -586,7 +586,7 @@ class ApiServiceTest extends UnitaryTestCase $userId = self::$faker->randomNumber(); $authTokenData = - new AuthTokenData( + new AuthToken( ['actionId' => $actionId, 'userId' => $userId, 'hash' => $authTokenHash, 'vault' => serialize($vault)] ); @@ -649,7 +649,7 @@ class ApiServiceTest extends UnitaryTestCase $userId = self::$faker->randomNumber(); $authTokenData = - new AuthTokenData( + new AuthToken( ['actionId' => $actionId, 'userId' => $userId, 'hash' => $authTokenHash, 'vault' => serialize($vault)] ); diff --git a/tests/SP/Generators/AuthTokenGenerator.php b/tests/SP/Generators/AuthTokenGenerator.php new file mode 100644 index 00000000..21e43b21 --- /dev/null +++ b/tests/SP/Generators/AuthTokenGenerator.php @@ -0,0 +1,64 @@ +. + */ + +namespace SP\Tests\Generators; + +use SP\Core\Crypt\Crypt; +use SP\Core\Crypt\Vault; +use SP\DataModel\AuthToken; +use SP\Domain\Core\Exceptions\CryptException; + +/** + * Class AuthTokenGenerator + */ +final class AuthTokenGenerator extends DataGenerator +{ + public function buildAuthToken(): AuthToken + { + return new AuthToken($this->authTokenProperties()); + } + + private function authTokenProperties(): array + { + return [ + 'id' => $this->faker->randomNumber(), + 'userId' => $this->faker->randomNumber(), + 'token' => $this->faker->sha1(), + 'createdBy' => $this->faker->randomNumber(), + 'startDate' => $this->faker->unixTime(), + 'actionId' => $this->faker->randomNumber(), + 'hash' => $this->faker->sha1(), + 'vault' => serialize($this->getVault()) + ]; + } + + private function getVault(): ?Vault + { + try { + return Vault::factory(new Crypt())->saveData($this->faker->text(), $this->faker->text); + } catch (CryptException) { + return null; + } + } +} diff --git a/tests/SP/Infrastructure/Account/Repositories/AccountRepositoryTest.php b/tests/SP/Infrastructure/Account/Repositories/AccountRepositoryTest.php index 4779cafb..a0a786e7 100644 --- a/tests/SP/Infrastructure/Account/Repositories/AccountRepositoryTest.php +++ b/tests/SP/Infrastructure/Account/Repositories/AccountRepositoryTest.php @@ -646,7 +646,7 @@ class AccountRepositoryTest extends UnitaryTestCase $callback = new Callback( static function (QueryData $arg) use ($item) { $params = $arg->getQuery()->getBindValues(); - $searchStringLike = '%'.$item->getSeachString().'%'; + $searchStringLike = '%' . $item->getSeachString() . '%'; return count($params) === 5 && $params['name'] === $searchStringLike @@ -659,7 +659,7 @@ class AccountRepositoryTest extends UnitaryTestCase } ); - $this->database->expects(self::once())->method('doSelect')->with($callback); + $this->database->expects(self::once())->method('doSelect')->with($callback, true); $this->accountRepository->search($item); } @@ -674,7 +674,7 @@ class AccountRepositoryTest extends UnitaryTestCase } ); - $this->database->expects(self::once())->method('doSelect')->with($callback); + $this->database->expects(self::once())->method('doSelect')->with($callback, true); $this->accountRepository->search(new ItemSearchData()); } diff --git a/tests/SP/Modules/Api/ApiTestCase.php b/tests/SP/Modules/Api/ApiTestCase.php index b83871e1..68f7e6c0 100644 --- a/tests/SP/Modules/Api/ApiTestCase.php +++ b/tests/SP/Modules/Api/ApiTestCase.php @@ -38,7 +38,7 @@ use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; use RuntimeException; use SP\Core\Bootstrap\BootstrapApi; -use SP\DataModel\AuthTokenData; +use SP\DataModel\AuthToken; use SP\Domain\Api\Services\ApiRequest; use SP\Domain\Auth\Services\AuthTokenService; use SP\Domain\Config\Ports\ConfigDataInterface; @@ -226,8 +226,9 @@ abstract class ApiTestCase extends TestCase private static function createApiToken( AuthTokenService $service, int $actionId - ): AuthTokenData { - $data = new AuthTokenData(); + ): AuthToken + { + $data = new AuthToken(); $data->setActionId($actionId); $data->setCreatedBy(1); $data->setHash(self::AUTH_TOKEN_PASS); diff --git a/tests/SP/Repositories/AuthTokenRepositoryTest.php b/tests/SP/Repositories/AuthTokenRepositoryTest.php index 74161bb4..4ad970cf 100644 --- a/tests/SP/Repositories/AuthTokenRepositoryTest.php +++ b/tests/SP/Repositories/AuthTokenRepositoryTest.php @@ -31,8 +31,9 @@ use DI\NotFoundException; use SP\Core\Context\ContextException; use SP\Core\Crypt\Hash; use SP\Core\Crypt\Vault; -use SP\DataModel\AuthTokenData; +use SP\DataModel\AuthToken; use SP\DataModel\ItemSearchData; +use SP\Domain\Auth\Ports\AuthTokenRepositoryInterface; use SP\Domain\Core\Acl\AclActionsInterface; use SP\Domain\Core\Crypt\VaultInterface; use SP\Domain\Core\Exceptions\ConstraintException; @@ -58,7 +59,7 @@ class AuthTokenRepositoryTest extends DatabaseTestCase public const AUTH_TOKEN_PASS = 123456; /** - * @var AuthTokenRepository + * @var AuthTokenRepositoryInterface */ private static $repository; @@ -88,7 +89,7 @@ class AuthTokenRepositoryTest extends DatabaseTestCase $data = $result->getData(); - $this->assertInstanceOf(AuthTokenData::class, $data); + $this->assertInstanceOf(AuthToken::class, $data); $this->assertEquals(1, $data->getId()); $this->assertEquals(AclActionsInterface::ACCOUNT_SEARCH, $data->getActionId()); $this->assertEquals('12b9027d24efff7bfbaca8bd774a4c34b45de35e033d2b192a88f4dfaee5c233', $data->getToken()); @@ -99,7 +100,7 @@ class AuthTokenRepositoryTest extends DatabaseTestCase $data = $result->getData(); - $this->assertInstanceOf(AuthTokenData::class, $data); + $this->assertInstanceOf(AuthToken::class, $data); $this->assertEquals(2, $data->getId()); $this->assertEquals(AclActionsInterface::ACCOUNT_VIEW_PASS, $data->getActionId()); $this->assertEquals(self::AUTH_TOKEN, $data->getToken()); @@ -125,7 +126,7 @@ class AuthTokenRepositoryTest extends DatabaseTestCase public function testGetTokenByToken() { $result = self::$repository->getTokenByToken(AclActionsInterface::ACCOUNT_VIEW_PASS, self::AUTH_TOKEN); - /** @var AuthTokenData $data */ + /** @var AuthToken $data */ $data = $result->getData(); $this->assertEquals(1, $result->getNumRows()); @@ -158,12 +159,12 @@ class AuthTokenRepositoryTest extends DatabaseTestCase $this->assertEquals(1, self::$repository->refreshVaultByUserId(1, $vault, $hash)); $result = self::$repository->getTokenByToken(AclActionsInterface::ACCOUNT_VIEW_PASS, self::AUTH_TOKEN); - /** @var AuthTokenData $data */ + /** @var AuthToken $data */ $data = $result->getData(); $this->assertEquals(1, $result->getNumRows()); - $this->assertInstanceOf(AuthTokenData::class, $data); + $this->assertInstanceOf(AuthToken::class, $data); $this->assertTrue(Hash::checkHashKey(self::AUTH_TOKEN_PASS, $data->getHash())); $this->assertEquals($vault, $data->getVault()); @@ -203,7 +204,7 @@ class AuthTokenRepositoryTest extends DatabaseTestCase $hash = Hash::hashKey('prueba123'); $vault = Vault::getInstance()->saveData('prueba', 'prueba123'); - $authTokenData = new AuthTokenData(); + $authTokenData = new AuthToken(); $authTokenData->setId(1); $authTokenData->setActionId(AclActionsInterface::ACCOUNT_CREATE); $authTokenData->setCreatedBy(1); @@ -215,11 +216,11 @@ class AuthTokenRepositoryTest extends DatabaseTestCase $this->assertEquals(1, self::$repository->update($authTokenData)); $result = self::$repository->getTokenByToken(AclActionsInterface::ACCOUNT_CREATE, $token); - /** @var AuthTokenData $data */ + /** @var AuthToken $data */ $data = $result->getData(); $this->assertEquals(1, $result->getNumRows()); - $this->assertInstanceOf(AuthTokenData::class, $data); + $this->assertInstanceOf(AuthToken::class, $data); $this->assertEquals(AclActionsInterface::ACCOUNT_CREATE, $data->getActionId()); $this->assertEquals($hash, $data->getHash()); $this->assertEquals(2, $data->getUserId()); @@ -316,7 +317,7 @@ class AuthTokenRepositoryTest extends DatabaseTestCase $hash = Hash::hashKey('prueba123'); $vault = Vault::getInstance()->saveData('prueba', 'prueba123'); - $authTokenData = new AuthTokenData(); + $authTokenData = new AuthToken(); $authTokenData->setActionId(AclActionsInterface::ACCOUNT_CREATE); $authTokenData->setCreatedBy(1); $authTokenData->setHash($hash); @@ -328,11 +329,11 @@ class AuthTokenRepositoryTest extends DatabaseTestCase $this->assertEquals(6, self::getRowCount('AuthToken')); $result = self::$repository->getTokenByToken(AclActionsInterface::ACCOUNT_CREATE, $token); - /** @var AuthTokenData $data */ + /** @var AuthToken $data */ $data = $result->getData(); $this->assertEquals(1, $result->getNumRows()); - $this->assertInstanceOf(AuthTokenData::class, $data); + $this->assertInstanceOf(AuthToken::class, $data); $this->assertEquals(AclActionsInterface::ACCOUNT_CREATE, $data->getActionId()); $this->assertEquals($hash, $data->getHash()); $this->assertEquals(6, $data->getId()); diff --git a/tests/SP/SP/Tests/Infrastructure/Auth/Repositories/AuthTokenRepositoryTest.php b/tests/SP/SP/Tests/Infrastructure/Auth/Repositories/AuthTokenRepositoryTest.php new file mode 100644 index 00000000..d37e83df --- /dev/null +++ b/tests/SP/SP/Tests/Infrastructure/Auth/Repositories/AuthTokenRepositoryTest.php @@ -0,0 +1,525 @@ +. + */ + +namespace SP\Tests\Infrastructure\Auth\Repositories; + +use Aura\SqlQuery\Common\DeleteInterface; +use Aura\SqlQuery\Common\InsertInterface; +use Aura\SqlQuery\Common\SelectInterface; +use Aura\SqlQuery\Common\UpdateInterface; +use Aura\SqlQuery\QueryFactory; +use PHPUnit\Framework\Constraint\Callback; +use PHPUnit\Framework\MockObject\MockObject; +use SP\DataModel\AuthToken; +use SP\DataModel\ItemSearchData; +use SP\Domain\Common\Models\Simple; +use SP\Domain\Core\Exceptions\ConstraintException; +use SP\Domain\Core\Exceptions\QueryException; +use SP\Infrastructure\Auth\Repositories\AuthTokenRepository; +use SP\Infrastructure\Common\Repositories\DuplicatedItemException; +use SP\Infrastructure\Database\DatabaseInterface; +use SP\Infrastructure\Database\QueryData; +use SP\Infrastructure\Database\QueryResult; +use SP\Tests\Generators\AuthTokenGenerator; +use SP\Tests\UnitaryTestCase; + +/** + * Class AuthTokenRepositoryTest + * + * @group unitary + */ +class AuthTokenRepositoryTest extends UnitaryTestCase +{ + + private AuthTokenRepository $authTokenRepository; + private MockObject|DatabaseInterface $database; + + /** + * @throws ConstraintException + * @throws QueryException + */ + public function testSearch() + { + $item = new ItemSearchData(self::$faker->name); + + $callback = new Callback( + static function (QueryData $arg) use ($item) { + $query = $arg->getQuery(); + $params = $query->getBindValues(); + $searchStringLike = '%' . $item->getSeachString() . '%'; + + return count($params) === 2 + && $params['userLogin'] === $searchStringLike + && $params['userName'] === $searchStringLike + && $arg->getMapClassName() === Simple::class + && is_a($query, SelectInterface::class) + && !empty($query->getStatement()); + } + ); + + $this->database + ->expects(self::once()) + ->method('doSelect') + ->with($callback, true); + + $this->authTokenRepository->search($item); + } + + /** + * @throws ConstraintException + * @throws QueryException + */ + public function testSearchWithoutString(): void + { + $callback = new Callback( + static function (QueryData $arg) { + $query = $arg->getQuery(); + return count($query->getBindValues()) === 0 + && $arg->getMapClassName() === Simple::class + && is_a($query, SelectInterface::class) + && !empty($query->getStatement()); + } + ); + + $this->database + ->expects(self::once()) + ->method('doSelect') + ->with($callback, true); + + $this->authTokenRepository->search(new ItemSearchData()); + } + + /** + * @throws ConstraintException + * @throws QueryException + */ + public function testDeleteByIdBatch() + { + $ids = [self::$faker->randomNumber(), self::$faker->randomNumber(), self::$faker->randomNumber()]; + + $callback = new Callback( + static function (QueryData $arg) use ($ids) { + $query = $arg->getQuery(); + $values = $query->getBindValues(); + + return count($values) === 3 + && array_shift($values) === array_shift($ids) + && array_shift($values) === array_shift($ids) + && array_shift($values) === array_shift($ids) + && $arg->getMapClassName() === Simple::class + && is_a($query, DeleteInterface::class) + && !empty($query->getStatement()); + } + ); + + $this->database + ->expects(self::once()) + ->method('doQuery') + ->with($callback); + + $this->authTokenRepository->deleteByIdBatch($ids); + } + + /** + * @throws ConstraintException + * @throws QueryException + */ + public function testDeleteByIdBatchWithNoIds(): void + { + $this->database + ->expects(self::never()) + ->method('doQuery'); + + $this->authTokenRepository->deleteByIdBatch([]); + } + + public function testGetTokenByUserId() + { + $id = self::$faker->randomNumber(); + + $callback = new Callback( + static function (QueryData $arg) use ($id) { + $query = $arg->getQuery(); + $params = $query->getBindValues(); + + return count($params) === 1 + && $params['userId'] === $id + && $arg->getMapClassName() === AuthToken::class + && is_a($query, SelectInterface::class) + && !empty($query->getStatement()); + } + ); + + $this->database + ->expects(self::once()) + ->method('doSelect') + ->with($callback); + + $this->authTokenRepository->getTokenByUserId($id); + } + + public function testGetById() + { + $id = self::$faker->randomNumber(); + + $callback = new Callback( + static function (QueryData $arg) use ($id) { + $query = $arg->getQuery(); + $params = $query->getBindValues(); + + return count($params) === 1 + && $params['id'] === $id + && $arg->getMapClassName() === AuthToken::class + && is_a($query, SelectInterface::class) + && !empty($query->getStatement()); + } + ); + + $this->database + ->expects(self::once()) + ->method('doSelect') + ->with($callback); + + $this->authTokenRepository->getById($id); + } + + /** + * @throws ConstraintException + * @throws DuplicatedItemException + * @throws QueryException + */ + public function testUpdate() + { + $authToken = AuthTokenGenerator::factory()->buildAuthToken(); + + $callbackDuplicate = new Callback( + static function (QueryData $arg) use ($authToken) { + $query = $arg->getQuery(); + $params = $query->getBindValues(); + + return count($params) === 4 + && $params['id'] === $authToken->getId() + && $params['userId'] === $authToken->getUserId() + && $params['token'] === $authToken->getToken() + && $params['actionId'] === $authToken->getActionId() + && is_a($query, SelectInterface::class) + && !empty($query->getStatement()); + } + ); + + $callbackUpdate = new Callback( + static function (QueryData $arg) use ($authToken) { + $query = $arg->getQuery(); + $params = $query->getBindValues(); + + return count($params) === 7 + && $params['id'] === $authToken->getId() + && $params['userId'] === $authToken->getUserId() + && $params['token'] === $authToken->getToken() + && $params['createdBy'] === $authToken->getCreatedBy() + && $params['actionId'] === $authToken->getActionId() + && $params['hash'] === $authToken->getHash() + && $params['vault'] === $authToken->getVault() + && is_a($query, UpdateInterface::class) + && !empty($query->getStatement()); + } + ); + + $this->database + ->expects(self::exactly(2)) + ->method('doQuery') + ->with(...self::withConsecutive([$callbackDuplicate], [$callbackUpdate])) + ->willReturn(new QueryResult([]), new QueryResult([1])); + + $this->authTokenRepository->update($authToken); + } + + /** + * @throws ConstraintException + * @throws DuplicatedItemException + * @throws QueryException + */ + public function testUpdateWithDuplicate() + { + $authToken = AuthTokenGenerator::factory()->buildAuthToken(); + + $callback = new Callback( + static function (QueryData $arg) use ($authToken) { + $query = $arg->getQuery(); + $params = $query->getBindValues(); + + return count($params) === 4 + && $params['id'] === $authToken->getId() + && $params['userId'] === $authToken->getUserId() + && $params['token'] === $authToken->getToken() + && $params['actionId'] === $authToken->getActionId() + && is_a($query, SelectInterface::class) + && !empty($query->getStatement()); + } + ); + + $this->database + ->expects(self::once()) + ->method('doQuery') + ->with($callback) + ->willReturn(new QueryResult([1])); + + $this->expectException(DuplicatedItemException::class); + $this->expectExceptionMessage('Authorization already exist'); + + $this->authTokenRepository->update($authToken); + } + + /** + * @throws ConstraintException + * @throws QueryException + */ + public function testRefreshVaultByUserId() + { + $userId = self::$faker->randomNumber(); + $token = self::$faker->sha1(); + + $callback = new Callback( + static function (QueryData $arg) use ($userId, $token) { + $query = $arg->getQuery(); + $params = $query->getBindValues(); + + return count($params) === 2 + && $params['userId'] === $userId + && $params['token'] === $token + && $arg->getOnErrorMessage() === 'Internal error' + && is_a($query, UpdateInterface::class) + && !empty($query->getStatement()); + } + ); + + $this->database + ->expects(self::once()) + ->method('doQuery') + ->with($callback) + ->willReturn(new QueryResult([1])); + + $this->authTokenRepository->refreshTokenByUserId($userId, $token); + } + + /** + * @throws ConstraintException + * @throws QueryException + */ + public function testDelete() + { + $id = self::$faker->randomNumber(); + + $callback = new Callback( + static function (QueryData $arg) use ($id) { + $query = $arg->getQuery(); + + return $query->getBindValues()['id'] === $id + && is_a($query, DeleteInterface::class) + && !empty($query->getStatement()); + } + ); + + $this->database->expects(self::once())->method('doQuery')->with($callback); + + $this->authTokenRepository->delete($id); + } + + public function testGetAll() + { + $callback = new Callback( + static function (QueryData $arg) { + $query = $arg->getQuery(); + return $arg->getMapClassName() === AuthToken::class + && is_a($query, SelectInterface::class) + && !empty($query->getStatement()); + } + ); + + $this->database + ->expects(self::once()) + ->method('doSelect') + ->with($callback); + + $this->authTokenRepository->getAll(); + } + + /** + * @throws ConstraintException + * @throws QueryException + */ + public function testRefreshTokenByUserId() + { + $userId = self::$faker->randomNumber(); + $token = self::$faker->sha1(); + + $callback = new Callback( + static function (QueryData $arg) use ($userId, $token) { + $query = $arg->getQuery(); + $params = $query->getBindValues(); + + return count($params) === 2 + && $params['userId'] === $userId + && $params['token'] === $token + && $arg->getOnErrorMessage() === 'Internal error' + && is_a($query, UpdateInterface::class) + && !empty($query->getStatement()); + } + ); + + $this->database + ->expects(self::once()) + ->method('doQuery') + ->with($callback) + ->willReturn(new QueryResult([1])); + + $this->authTokenRepository->refreshTokenByUserId($userId, $token); + } + + public function testGetTokenByToken() + { + $actionId = self::$faker->randomNumber(); + $token = self::$faker->sha1(); + + $callback = new Callback( + static function (QueryData $arg) use ($actionId, $token) { + $query = $arg->getQuery(); + $params = $query->getBindValues(); + + return count($params) === 2 + && $params['actionId'] === $actionId + && $params['token'] === $token + && $arg->getMapClassName() === AuthToken::class + && is_a($query, SelectInterface::class) + && !empty($query->getStatement()); + } + ); + + $this->database + ->expects(self::once()) + ->method('doSelect') + ->with($callback); + + $this->authTokenRepository->getTokenByToken($actionId, $token); + } + + /** + * @throws ConstraintException + * @throws DuplicatedItemException + * @throws QueryException + */ + public function testCreate() + { + $authToken = AuthTokenGenerator::factory()->buildAuthToken(); + + $callbackDuplicate = new Callback( + static function (QueryData $arg) use ($authToken) { + $query = $arg->getQuery(); + $params = $query->getBindValues(); + + return count($params) === 3 + && $params['userId'] === $authToken->getUserId() + && $params['token'] === $authToken->getToken() + && $params['actionId'] === $authToken->getActionId() + && is_a($query, SelectInterface::class) + && !empty($query->getStatement()); + } + ); + + $callbackUpdate = new Callback( + static function (QueryData $arg) use ($authToken) { + $query = $arg->getQuery(); + $params = $query->getBindValues(); + + return count($params) === 6 + && $params['userId'] === $authToken->getUserId() + && $params['token'] === $authToken->getToken() + && $params['createdBy'] === $authToken->getCreatedBy() + && $params['actionId'] === $authToken->getActionId() + && $params['hash'] === $authToken->getHash() + && $params['vault'] === $authToken->getVault() + && is_a($query, InsertInterface::class) + && !empty($query->getStatement()); + } + ); + + $this->database + ->expects(self::exactly(2)) + ->method('doQuery') + ->with(...self::withConsecutive([$callbackDuplicate], [$callbackUpdate])) + ->willReturn(new QueryResult([]), new QueryResult([1])); + + $this->authTokenRepository->create($authToken); + } + + /** + * @throws ConstraintException + * @throws DuplicatedItemException + * @throws QueryException + */ + public function testCreateWithDuplicate() + { + $authToken = AuthTokenGenerator::factory()->buildAuthToken(); + + $callback = new Callback( + static function (QueryData $arg) use ($authToken) { + $query = $arg->getQuery(); + $params = $query->getBindValues(); + + return count($params) === 3 + && $params['userId'] === $authToken->getUserId() + && $params['token'] === $authToken->getToken() + && $params['actionId'] === $authToken->getActionId() + && is_a($query, SelectInterface::class) + && !empty($query->getStatement()); + } + ); + + $this->database + ->expects(self::once()) + ->method('doQuery') + ->with($callback) + ->willReturn(new QueryResult([1])); + + $this->expectException(DuplicatedItemException::class); + $this->expectExceptionMessage('Authorization already exist'); + + $this->authTokenRepository->create($authToken); + } + + protected function setUp(): void + { + parent::setUp(); + + $this->database = $this->createMock(DatabaseInterface::class); + $queryFactory = new QueryFactory('mysql'); + + $this->authTokenRepository = new AuthTokenRepository( + $this->database, + $this->context, + $this->application->getEventDispatcher(), + $queryFactory, + ); + } +} diff --git a/tests/SP/Services/AuthToken/AuthTokenServiceTest.php b/tests/SP/Services/AuthToken/AuthTokenServiceTest.php index ad4585a3..8312b032 100644 --- a/tests/SP/Services/AuthToken/AuthTokenServiceTest.php +++ b/tests/SP/Services/AuthToken/AuthTokenServiceTest.php @@ -32,7 +32,7 @@ use Exception; use SP\Core\Context\ContextException; use SP\Core\Crypt\Hash; use SP\Core\Crypt\Vault; -use SP\DataModel\AuthTokenData; +use SP\DataModel\AuthToken; use SP\DataModel\ItemSearchData; use SP\Domain\Auth\Services\AuthTokenService; use SP\Domain\Common\Services\ServiceException; @@ -119,7 +119,7 @@ class AuthTokenServiceTest extends DatabaseTestCase */ public function testRefreshAndUpdate() { - $data = new AuthTokenData(); + $data = new AuthToken(); $data->setId(1); $data->setActionId(AclActionsInterface::ACCOUNT_CREATE); $data->setCreatedBy(1); @@ -152,7 +152,7 @@ class AuthTokenServiceTest extends DatabaseTestCase { $data = self::$service->getTokenByToken(AclActionsInterface::ACCOUNT_VIEW_PASS, self::AUTH_TOKEN); - $this->assertInstanceOf(AuthTokenData::class, $data); + $this->assertInstanceOf(AuthToken::class, $data); $this->assertEquals(2, $data->getId()); $this->assertEquals(AclActionsInterface::ACCOUNT_VIEW_PASS, $data->getActionId()); $this->assertTrue(Hash::checkHashKey(self::AUTH_TOKEN_PASS, $data->getHash())); @@ -176,7 +176,7 @@ class AuthTokenServiceTest extends DatabaseTestCase */ public function testUpdate() { - $data = new AuthTokenData(); + $data = new AuthToken(); $data->setId(1); $data->setActionId(AclActionsInterface::ACCOUNT_CREATE); $data->setCreatedBy(1); @@ -187,7 +187,7 @@ class AuthTokenServiceTest extends DatabaseTestCase $data = self::$service->getTokenByToken(AclActionsInterface::ACCOUNT_CREATE, $data->getToken()); - $this->assertInstanceOf(AuthTokenData::class, $data); + $this->assertInstanceOf(AuthToken::class, $data); $this->assertEquals(AclActionsInterface::ACCOUNT_CREATE, $data->getActionId()); $this->assertTrue(Hash::checkHashKey(self::AUTH_TOKEN_PASS, $data->getHash())); $this->assertEquals(2, $data->getUserId()); @@ -213,7 +213,7 @@ class AuthTokenServiceTest extends DatabaseTestCase { $data = self::$service->getById(1); - $this->assertInstanceOf(AuthTokenData::class, $data); + $this->assertInstanceOf(AuthToken::class, $data); $this->assertEquals(1, $data->getId()); $this->assertEquals(AclActionsInterface::ACCOUNT_SEARCH, $data->getActionId()); $this->assertEquals(pack('H*', '31326239303237643234656666663762666261636138626437373461346333346234356465333565303333643262313932613838663464666165653563323333'), $data->getToken()); @@ -221,7 +221,7 @@ class AuthTokenServiceTest extends DatabaseTestCase $data = self::$service->getById(2); - $this->assertInstanceOf(AuthTokenData::class, $data); + $this->assertInstanceOf(AuthToken::class, $data); $this->assertEquals(2, $data->getId()); $this->assertEquals(AclActionsInterface::ACCOUNT_VIEW_PASS, $data->getActionId()); $this->assertEquals(self::AUTH_TOKEN, $data->getToken()); @@ -275,7 +275,7 @@ class AuthTokenServiceTest extends DatabaseTestCase */ public function testCreate() { - $authTokenData = new AuthTokenData(); + $authTokenData = new AuthToken(); $authTokenData->setActionId(AclActionsInterface::ACCOUNT_CREATE); $authTokenData->setCreatedBy(1); $authTokenData->setHash(self::AUTH_TOKEN_PASS); @@ -286,7 +286,7 @@ class AuthTokenServiceTest extends DatabaseTestCase $data = self::$service->getTokenByToken(AclActionsInterface::ACCOUNT_CREATE, $authTokenData->getToken()); - $this->assertInstanceOf(AuthTokenData::class, $data); + $this->assertInstanceOf(AuthToken::class, $data); $this->assertEquals(AclActionsInterface::ACCOUNT_CREATE, $data->getActionId()); $this->assertTrue(Hash::checkHashKey(self::AUTH_TOKEN_PASS, $data->getHash())); $this->assertEquals(6, $data->getId());