chore(tests): UT for UserPassRecover repository

Signed-off-by: Rubén D <nuxsmin@syspass.org>
This commit is contained in:
Rubén D
2024-03-25 09:28:12 +01:00
parent 8bc7d87c65
commit 7c1bc2658f
7 changed files with 400 additions and 285 deletions

View File

@@ -1,117 +0,0 @@
<?php
/*
* sysPass
*
* @author nuxsmin
* @link https://syspass.org
* @copyright 2012-2022, 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;
use SP\Domain\Common\Models\Model;
/**
* Class UserPassRecoverData
*
* @package SP\DataModel
*/
class UserPassRecoverData extends Model
{
/**
* @var int
*/
public $userId = 0;
/**
* @var string
*/
public $hash = '';
/**
* @var int
*/
public $date = 0;
/**
* @var bool
*/
public $used = 0;
/**
* @return int
*/
public function getUserId()
{
return (int)$this->userId;
}
/**
* @param int $userId
*/
public function setUserId($userId)
{
$this->userId = (int)$userId;
}
/**
* @return string
*/
public function getHash()
{
return $this->hash;
}
/**
* @param string $hash
*/
public function setHash($hash)
{
$this->hash = $hash;
}
/**
* @return int
*/
public function getDate()
{
return $this->date;
}
/**
* @param int $date
*/
public function setDate($date)
{
$this->date = $date;
}
/**
* @return boolean
*/
public function isUsed()
{
return (int)$this->used;
}
/**
* @param boolean $used
*/
public function setUsed($used)
{
$this->used = (int)$used;
}
}

View File

@@ -0,0 +1,60 @@
<?php
/*
* sysPass
*
* @author nuxsmin
* @link https://syspass.org
* @copyright 2012-2024, 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\Domain\User\Models;
use SP\Domain\Common\Models\Model;
/**
* Class UserPassRecover
*/
class UserPassRecover extends Model
{
public const TABLE = 'UserPassRecover';
public ?int $userId = null;
public ?string $hash = null;
public ?int $date = null;
public ?bool $used = null;
public function getUserId(): ?int
{
return $this->userId;
}
public function getHash(): ?string
{
return $this->hash;
}
public function getDate(): ?int
{
return $this->date;
}
public function isUsed(): ?bool
{
return $this->used;
}
}

View File

@@ -4,7 +4,7 @@
*
* @author nuxsmin
* @link https://syspass.org
* @copyright 2012-2022, Rubén Domínguez nuxsmin@$syspass.org
* @copyright 2012-2024, Rubén Domínguez nuxsmin@$syspass.org
*
* This file is part of sysPass.
*
@@ -27,20 +27,21 @@ namespace SP\Domain\User\Ports;
use SP\Domain\Core\Exceptions\ConstraintException;
use SP\Domain\Core\Exceptions\QueryException;
use SP\Domain\Core\Exceptions\SPException;
use SP\Domain\User\Models\UserPassRecover as UserPassRecoverModel;
use SP\Infrastructure\Database\QueryResult;
/**
* Class UserPassRecoverRepository
*
* @package SP\Infrastructure\Common\Repositories\UserPassRecover
* @template T of UserPassRecoverModel
*/
interface UserPassRecoverRepositoryInterface
interface UserPassRecoverRepository
{
/**
* Checks recovery limit attempts by user's id and time
*
* @param int $userId
* @param int $time
* @param int $userId
* @param int $time
*
* @return int
* @throws ConstraintException
@@ -51,20 +52,20 @@ interface UserPassRecoverRepositoryInterface
/**
* Adds a hash for a user's id
*
* @param int $userId
* @param string $hash
* @param int $userId
* @param string $hash
*
* @return int
* @return QueryResult
* @throws ConstraintException
* @throws QueryException
*/
public function add(int $userId, string $hash): int;
public function add(int $userId, string $hash): QueryResult;
/**
* Toggles a hash used
*
* @param string $hash
* @param int $time
* @param string $hash
* @param int $time
*
* @return int
* @throws SPException
@@ -74,12 +75,10 @@ interface UserPassRecoverRepositoryInterface
/**
* Comprobar el hash de recuperación de clave.
*
* @param string $hash
* @param int $time
* @param string $hash
* @param int $time
*
* @return QueryResult
* @throws ConstraintException
* @throws QueryException
* @return QueryResult<T>
*/
public function getUserIdForHash(string $hash, int $time): QueryResult;
}

View File

@@ -33,10 +33,9 @@ use SP\Domain\Common\Services\ServiceException;
use SP\Domain\Core\Exceptions\ConstraintException;
use SP\Domain\Core\Exceptions\QueryException;
use SP\Domain\Core\Exceptions\SPException;
use SP\Domain\User\Ports\UserPassRecoverRepositoryInterface;
use SP\Domain\User\Ports\UserPassRecoverRepository;
use SP\Domain\User\Ports\UserPassRecoverServiceInterface;
use SP\Html\Html;
use SP\Infrastructure\User\Repositories\UserPassRecoverBaseRepository;
use SP\Util\PasswordUtil;
/**
@@ -55,9 +54,9 @@ final class UserPassRecoverService extends Service implements UserPassRecoverSer
*/
public const MAX_PASS_RECOVER_LIMIT = 3;
protected UserPassRecoverBaseRepository $userPassRecoverRepository;
protected UserPassRecoverRepository $userPassRecoverRepository;
public function __construct(Application $application, UserPassRecoverRepositoryInterface $userPassRecoverRepository)
public function __construct(Application $application, UserPassRecoverRepository $userPassRecoverRepository)
{
parent::__construct($application);
@@ -73,7 +72,7 @@ final class UserPassRecoverService extends Service implements UserPassRecoverSer
$mailMessage->addDescription(__('In order to complete the process, please go to this URL:'));
$mailMessage->addDescriptionLine();
$mailMessage->addDescription(
Html::anchorText(BootstrapBase::$WEBURI.'/index.php?r=userPassReset/reset/'.$hash)
Html::anchorText(BootstrapBase::$WEBURI . '/index.php?r=userPassReset/reset/' . $hash)
);
$mailMessage->addDescriptionLine();
$mailMessage->addDescription(__('If you have not requested this action, please dismiss this message.'));

View File

@@ -0,0 +1,145 @@
<?php
/*
* sysPass
*
* @author nuxsmin
* @link https://syspass.org
* @copyright 2012-2024, 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\Infrastructure\User\Repositories;
use SP\Domain\Core\Exceptions\ConstraintException;
use SP\Domain\Core\Exceptions\QueryException;
use SP\Domain\Core\Exceptions\SPException;
use SP\Domain\User\Models\UserPassRecover as UserPassRecoverModel;
use SP\Domain\User\Ports\UserPassRecoverRepository;
use SP\Infrastructure\Common\Repositories\BaseRepository;
use SP\Infrastructure\Database\QueryData;
use SP\Infrastructure\Database\QueryResult;
use function SP\__u;
/**
* Class UserPassRecover
*
* @template T of UserPassRecoverModel
*/
final class UserPassRecover extends BaseRepository implements UserPassRecoverRepository
{
/**
* Checks recovery limit attempts by user's id and time
*
* @param int $userId
* @param int $time
*
* @return int
*/
public function getAttemptsByUserId(int $userId, int $time): int
{
$query = $this->queryFactory
->newSelect()
->from(UserPassRecoverModel::TABLE)
->cols(['date'])
->where('userId = :userId')
->where('used = 0')
->where('date >= :date')
->bindValues(['userId' => $userId, 'date' => $time]);
return $this->db->doSelect(QueryData::build($query))->getNumRows();
}
/**
* Adds a hash for a user's id
*
* @param int $userId
* @param string $hash
*
* @return QueryResult
* @throws ConstraintException
* @throws QueryException
*/
public function add(int $userId, string $hash): QueryResult
{
$query = $this->queryFactory
->newInsert()
->into(UserPassRecoverModel::TABLE)
->cols(['userId' => $userId, 'hash' => $hash])
->set('date', 'UNIX_TIMESTAMP()')
->set('used', 0);
$queryData = QueryData::build($query)->setOnErrorMessage(__u('Error while generating the recovering hash'));
return $this->db->doQuery($queryData);
}
/**
* Toggles a hash used
*
* @param string $hash
* @param int $time
*
* @return int
* @throws SPException
*/
public function toggleUsedByHash(string $hash, int $time): int
{
$query = $this->queryFactory
->newUpdate()
->table(UserPassRecoverModel::TABLE)
->cols(['used' => 1])
->where('hash = :hash', ['hash' => $hash])
->where('date >= :date', ['date' => $time])
->where('used = 0')
->limit(1);
$queryData = QueryData::build($query);
$queryData->setOnErrorMessage(__u('Error while checking hash'));
return $this->db->doQuery($queryData)->getAffectedNumRows();
}
/**
* Comprobar el hash de recuperación de clave.
*
* @param string $hash
* @param int $time
*
* @return QueryResult<T>
*/
public function getUserIdForHash(string $hash, int $time): QueryResult
{
$query = $this->queryFactory
->newSelect()
->cols(UserPassRecoverModel::getCols())
->from(UserPassRecoverModel::TABLE)
->where('hash = :hash')
->where('used = 0')
->where('date >= :date')
->orderBy(['date DESC'])
->limit(1)
->bindValues(
[
'hash' => $hash,
'date' => $time
]
);
return $this->db->doSelect(QueryData::build($query)->setMapClassName(UserPassRecoverModel::class));
}
}

View File

@@ -1,147 +0,0 @@
<?php
/*
* sysPass
*
* @author nuxsmin
* @link https://syspass.org
* @copyright 2012-2024, 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\Infrastructure\User\Repositories;
use SP\Domain\Core\Exceptions\ConstraintException;
use SP\Domain\Core\Exceptions\QueryException;
use SP\Domain\Core\Exceptions\SPException;
use SP\Domain\User\Ports\UserPassRecoverRepositoryInterface;
use SP\Infrastructure\Common\Repositories\BaseRepository;
use SP\Infrastructure\Database\QueryData;
use SP\Infrastructure\Database\QueryResult;
/**
* Class UserPassRecoverRepository
*
* @package SP\Infrastructure\Common\Repositories\UserPassRecover
*/
final class UserPassRecoverBaseRepository extends BaseRepository implements UserPassRecoverRepositoryInterface
{
/**
* Checks recovery limit attempts by user's id and time
*
* @param int $userId
* @param int $time
*
* @return int
* @throws ConstraintException
* @throws QueryException
*/
public function getAttemptsByUserId(int $userId, int $time): int
{
$query = /** @lang SQL */
'SELECT userId
FROM UserPassRecover
WHERE userId = ?
AND used = 0
AND `date` >= ?';
$queryData = new QueryData();
$queryData->setQuery($query);
$queryData->setParams([$userId, $time]);
return $this->db->doSelect($queryData)->getNumRows();
}
/**
* Adds a hash for a user's id
*
* @param int $userId
* @param string $hash
*
* @return int
* @throws ConstraintException
* @throws QueryException
*/
public function add(int $userId, string $hash): int
{
$query = /** @lang SQL */
'INSERT INTO UserPassRecover SET
userId = ?,
`hash` = ?,
`date` = UNIX_TIMESTAMP(),
used = 0';
$queryData = new QueryData();
$queryData->setQuery($query);
$queryData->setParams([(int)$userId, $hash]);
$queryData->setOnErrorMessage(__u('Error while generating the recovering hash'));
return $this->db->doQuery($queryData)->getLastId();
}
/**
* Toggles a hash used
*
* @param string $hash
* @param int $time
*
* @return int
* @throws SPException
*/
public function toggleUsedByHash(string $hash, int $time): int
{
$query = /** @lang SQL */
'UPDATE UserPassRecover SET used = 1
WHERE `hash` = ?
AND used = 0
AND `date` >= ?
LIMIT 1';
$queryData = new QueryData();
$queryData->setQuery($query);
$queryData->setParams([$hash, $time]);
$queryData->setOnErrorMessage(__u('Error while checking hash'));
return $this->db->doQuery($queryData)->getAffectedNumRows();
}
/**
* Comprobar el hash de recuperación de clave.
*
* @param string $hash
* @param int $time
*
* @return QueryResult
* @throws ConstraintException
* @throws QueryException
*/
public function getUserIdForHash(string $hash, int $time): QueryResult
{
$query = /** @lang SQL */
'SELECT userId
FROM UserPassRecover
WHERE `hash` = ?
AND used = 0
AND `date` >= ?
ORDER BY `date` DESC LIMIT 1';
$queryData = new QueryData();
$queryData->setQuery($query);
$queryData->setParams([$hash, $time]);
return $this->db->doSelect($queryData);
}
}

View File

@@ -0,0 +1,176 @@
<?php
/*
* sysPass
*
* @author nuxsmin
* @link https://syspass.org
* @copyright 2012-2024, 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 SPT\Infrastructure\User\Repositories;
use Aura\SqlQuery\Common\InsertInterface;
use Aura\SqlQuery\Common\UpdateInterface;
use Aura\SqlQuery\QueryFactory;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Constraint\Callback;
use PHPUnit\Framework\MockObject\MockObject;
use SP\Domain\Common\Models\Simple;
use SP\Domain\Core\Exceptions\ConstraintException;
use SP\Domain\Core\Exceptions\QueryException;
use SP\Domain\Core\Exceptions\SPException;
use SP\Domain\User\Models\UserPassRecover as UserPassRecoverModel;
use SP\Infrastructure\Database\DatabaseInterface;
use SP\Infrastructure\Database\QueryData;
use SP\Infrastructure\Database\QueryResult;
use SP\Infrastructure\User\Repositories\UserPassRecover;
use SPT\UnitaryTestCase;
/**
* Class UserPassRecoverTest
*/
#[Group('unitary')]
class UserPassRecoverTest extends UnitaryTestCase
{
private UserPassRecover $userPassRecover;
private MockObject|DatabaseInterface $database;
public function testGetAttemptsByUserId()
{
$time = self::$faker->unixTime();
$this->database
->expects($this->once())
->method('doSelect')
->with(
self::callback(static function (QueryData $queryData) use ($time) {
$params = $queryData->getQuery()->getBindValues();
return count($params) === 2
&& $params['userId'] === 100
&& $params['date'] === $time
&& $queryData->getMapClassName() === Simple::class;
})
);
$this->userPassRecover->getAttemptsByUserId(100, $time);
}
public function testGetUserIdForHash()
{
$time = self::$faker->unixTime();
$hash = self::$faker->sha1();
$this->database
->expects($this->once())
->method('doSelect')
->with(
self::callback(static function (QueryData $queryData) use ($hash, $time) {
$params = $queryData->getQuery()->getBindValues();
return count($params) === 2
&& $params['hash'] === $hash
&& $params['date'] === $time
&& $queryData->getMapClassName() === UserPassRecoverModel::class;
})
);
$this->userPassRecover->getUserIdForHash($hash, $time);
}
/**
* @throws ConstraintException
* @throws QueryException
*/
public function testAdd()
{
$hash = self::$faker->sha1();
$callbackCreate = new Callback(
static function (QueryData $arg) use ($hash) {
$query = $arg->getQuery();
$params = $query->getBindValues();
return count($params) === 2
&& $params['userId'] === 100
&& $params['hash'] === $hash
&& is_a($query, InsertInterface::class)
&& !empty($query->getStatement());
}
);
$this->database
->expects(self::once())
->method('doQuery')
->with($callbackCreate)
->willReturn(new QueryResult([1]));
$this->userPassRecover->add(100, $hash);
}
/**
* @throws SPException
*/
public function testToggleUsedByHash()
{
$time = self::$faker->unixTime();
$hash = self::$faker->sha1();
$callbackUpdate = new Callback(
static function (QueryData $arg) use ($hash, $time) {
$query = $arg->getQuery();
$params = $query->getBindValues();
return count($params) === 3
&& $params['hash'] === $hash
&& $params['date'] === $time
&& $params['used'] === 1
&& is_a($query, UpdateInterface::class)
&& !empty($query->getStatement());
}
);
$queryResult = new QueryResult();
$this->database
->expects(self::once())
->method('doQuery')
->with($callbackUpdate)
->willReturn($queryResult->setAffectedNumRows(1));
$out = $this->userPassRecover->toggleUsedByHash($hash, $time);
self::assertEquals(1, $out);
}
protected function setUp(): void
{
parent::setUp();
$this->database = $this->createMock(DatabaseInterface::class);
$queryFactory = new QueryFactory('mysql');
$this->userPassRecover = new UserPassRecover(
$this->database,
$this->context,
$this->application->getEventDispatcher(),
$queryFactory,
);
}
}