Vergelijk commits
33 Commits
master
...
dev_3.x_OX
Auteur | SHA1 | Datum | |
---|---|---|---|
be4990f7f7 | |||
1251095eb3 | |||
fedd03a66e | |||
a0090ab3bb | |||
be1f482aba | |||
30e13cb0dc | |||
ceaa9daf90 | |||
e509aa7eff | |||
12d47d14f0 | |||
f8f488f7a2 | |||
bef6651a3f | |||
f43eb2584a | |||
faf08ed786 | |||
b2ba738b05 | |||
5d4f29988e | |||
0b0f15605f | |||
f23bd1339c | |||
90013b3832 | |||
49e54d6866 | |||
95aa47fa09 | |||
048057e40b | |||
e6cb61e023 | |||
209b05e347 | |||
55ed6a1568 | |||
cfde971a0e | |||
79fb841b37 | |||
d93e1517aa | |||
5ee8e18e89 | |||
bedb60928d | |||
43e8291bf9 | |||
ed06705c40 | |||
9f6b689a56 | |||
0adcec6df2 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,3 +1,3 @@
|
||||
src/tests/.phpunit.result.cache
|
||||
src/tests/reports/
|
||||
Tests/.phpunit.result.cache
|
||||
Tests/reports/
|
||||
.php_cs.cache
|
||||
|
@ -1,13 +1,42 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$finder = PhpCsFixer\Finder::create()
|
||||
->in(__DIR__)
|
||||
;
|
||||
|
||||
$fileHeaderComment = <<<EOF
|
||||
For the full copyright and license information, please view the LICENSE
|
||||
file that was distributed with this source code.
|
||||
|
||||
https://www.d3data.de
|
||||
|
||||
@copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
@author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
@link https://www.oxidmodule.com
|
||||
EOF;
|
||||
|
||||
$config = new PhpCsFixer\Config();
|
||||
return $config->setRules([
|
||||
'@PHP73Migration' => true,
|
||||
'@PSR12' => true
|
||||
])
|
||||
'header_comment' => [
|
||||
'header' => $fileHeaderComment,
|
||||
'comment_type' => 'PHPDoc',
|
||||
'location' => 'after_open'
|
||||
],
|
||||
'@PHP80Migration' => true,
|
||||
'@PSR12' => true
|
||||
])
|
||||
->setFinder($finder)
|
||||
;
|
||||
;
|
80
Application/Controller/Admin/d3force_2fa.php
Normal file
80
Application/Controller/Admin/d3force_2fa.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Application\Controller\Admin;
|
||||
|
||||
use D3\Totp\Application\Model\Constants;
|
||||
use D3\Totp\Application\Model\d3totp_conf;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
use OxidEsales\Eshop\Core\Session;
|
||||
use OxidEsales\EshopCommunity\Internal\Container\ContainerFactory;
|
||||
use OxidEsales\EshopCommunity\Internal\Framework\Module\Configuration\Bridge\ModuleConfigurationDaoBridgeInterface;
|
||||
use OxidEsales\EshopCommunity\Internal\Framework\Module\Configuration\DataObject\ModuleConfiguration;
|
||||
use OxidEsales\EshopCommunity\Internal\Framework\Module\Configuration\Exception\ModuleSettingNotFountException;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
class d3force_2fa extends d3user_totp
|
||||
{
|
||||
public function render(): string
|
||||
{
|
||||
$this->addTplParam('force2FA', true);
|
||||
|
||||
$userID = $this->d3TotpGetSessionObject()->getVariable(d3totp_conf::OXID_ADMIN_AUTH);
|
||||
$this->_sEditObjectId = $userID;
|
||||
|
||||
return parent::render();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws ModuleSettingNotFountException
|
||||
*/
|
||||
protected function authorize(): bool
|
||||
{
|
||||
$userID = $this->d3TotpGetSessionObject()->getVariable(d3totp_conf::OXID_ADMIN_AUTH);
|
||||
|
||||
return ($this->d3IsAdminForce2FA() && !empty($userID));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Session
|
||||
*/
|
||||
private function d3TotpGetSessionObject(): Session
|
||||
{
|
||||
return Registry::getSession();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws ModuleSettingNotFountException
|
||||
*/
|
||||
private function d3IsAdminForce2FA(): bool
|
||||
{
|
||||
if (!$this->isAdmin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $this->getModuleConfiguration()->getModuleSetting('D3_TOTP_ADMIN_FORCE_2FA')->getValue();
|
||||
}
|
||||
|
||||
protected function getModuleConfiguration(): ModuleConfiguration
|
||||
{
|
||||
$container = ContainerFactory::getInstance()->getContainer();
|
||||
$moduleConfigurationBridge = $container->get(ModuleConfigurationDaoBridgeInterface::class);
|
||||
/** @var ModuleConfiguration $moduleConfiguration */
|
||||
return $moduleConfigurationBridge->get(Constants::OXID_MODULE_ID);
|
||||
}
|
||||
}
|
@ -15,44 +15,51 @@ declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Application\Controller\Admin;
|
||||
|
||||
use D3\Totp\Application\Model\Constants;
|
||||
use D3\Totp\Application\Model\d3backupcodelist;
|
||||
use D3\Totp\Application\Model\d3totp;
|
||||
use D3\Totp\Application\Model\d3totp_conf;
|
||||
use D3\Totp\Application\Model\Exceptions\d3totp_wrongOtpException;
|
||||
use D3\Totp\Modules\Application\Controller\Admin\d3_totp_LoginController;
|
||||
use D3\Totp\Modules\Application\Model\d3_totp_user;
|
||||
use Doctrine\DBAL\Driver\Exception;
|
||||
use Doctrine\DBAL\Exception as DBALException;
|
||||
use OxidEsales\Eshop\Application\Controller\Admin\AdminController;
|
||||
use OxidEsales\Eshop\Application\Controller\Admin\LoginController;
|
||||
use OxidEsales\Eshop\Application\Model\User;
|
||||
use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
use OxidEsales\Eshop\Core\Session;
|
||||
use OxidEsales\Eshop\Core\Utils;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class d3totpadminlogin extends AdminController
|
||||
{
|
||||
protected $_sThisTemplate = 'd3totpadminlogin.tpl';
|
||||
protected $_sThisTemplate = '@'.Constants::OXID_MODULE_ID.'/admin/d3totplogin';
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function _authorize(): bool
|
||||
protected function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return d3totp|mixed
|
||||
* @return d3totp
|
||||
*/
|
||||
public function d3TotpGetTotpObject()
|
||||
public function d3TotpGetTotpObject(): d3totp
|
||||
{
|
||||
return oxNew(d3totp::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws DatabaseConnectionException
|
||||
* @throws Exception
|
||||
* @throws DBALException
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
protected function isTotpIsNotRequired(): bool
|
||||
{
|
||||
@ -78,7 +85,10 @@ class d3totpadminlogin extends AdminController
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws DatabaseConnectionException
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws Exception
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws DBALException
|
||||
*/
|
||||
public function render(): string
|
||||
{
|
||||
@ -108,12 +118,15 @@ class d3totpadminlogin extends AdminController
|
||||
|
||||
/**
|
||||
* @return string|void
|
||||
* @throws DatabaseConnectionException
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws Exception
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws DBALException
|
||||
*/
|
||||
public function getBackupCodeCountMessage()
|
||||
{
|
||||
/** @var d3_totp_user $user */
|
||||
$user = oxNew(User::class);
|
||||
$user = $this->d3TotpGetUserObject();
|
||||
$userId = $user->d3TotpGetCurrentUser();
|
||||
|
||||
$oBackupCodeList = $this->d3GetBackupCodeListObject();
|
||||
@ -139,22 +152,27 @@ class d3totpadminlogin extends AdminController
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User
|
||||
* @return d3_totp_user
|
||||
*/
|
||||
public function d3TotpGetUserObject(): User
|
||||
{
|
||||
return oxNew(User::class);
|
||||
/** @var d3_totp_user $user */
|
||||
$user = oxNew(User::class);
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|void
|
||||
* @throws DatabaseConnectionException
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws DBALException
|
||||
* @throws Exception
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function checklogin()
|
||||
{
|
||||
$session = $this->d3TotpGetSession();
|
||||
/** @var d3_totp_user $user */
|
||||
$user = oxNew(User::class);
|
||||
$user = $this->d3TotpGetUserObject();
|
||||
$userId = $user->d3TotpGetCurrentUser();
|
||||
|
||||
try {
|
||||
@ -188,13 +206,16 @@ class d3totpadminlogin extends AdminController
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $sTotp
|
||||
* @param string $sTotp
|
||||
* @param d3totp $totp
|
||||
* @return bool
|
||||
* @throws DatabaseConnectionException
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws Exception
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws DBALException
|
||||
* @throws d3totp_wrongOtpException
|
||||
*/
|
||||
public function d3TotpHasValidTotp(string $sTotp = null, d3totp $totp): bool
|
||||
public function d3TotpHasValidTotp(string $sTotp, d3totp $totp): bool
|
||||
{
|
||||
return $this->d3TotpGetSession()->getVariable(d3totp_conf::SESSION_ADMIN_AUTH)
|
||||
|| $totp->verify($sTotp);
|
@ -15,29 +15,35 @@ declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Application\Controller\Admin;
|
||||
|
||||
use Assert\Assert;
|
||||
use D3\Totp\Application\Controller\OtpManagementControllerTrait;
|
||||
use D3\Totp\Application\Model\Constants;
|
||||
use D3\Totp\Application\Model\d3totp;
|
||||
use D3\Totp\Application\Model\d3backupcodelist;
|
||||
use D3\Totp\Application\Model\d3totp_conf;
|
||||
use D3\Totp\Modules\Application\Model\d3_totp_user;
|
||||
use Doctrine\DBAL\Driver\Exception as DBALDriverException;
|
||||
use Exception;
|
||||
use OxidEsales\Eshop\Application\Controller\Admin\AdminDetailsController;
|
||||
use OxidEsales\Eshop\Application\Model\User;
|
||||
use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException;
|
||||
use OxidEsales\Eshop\Core\Exception\StandardException;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
use OxidEsales\Eshop\Core\UtilsView;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
class d3user_totp extends AdminDetailsController
|
||||
{
|
||||
protected $_sSaveError = null;
|
||||
use OtpManagementControllerTrait;
|
||||
|
||||
protected $_sThisTemplate = 'd3user_totp.tpl';
|
||||
protected null|string $_sSaveError = null;
|
||||
|
||||
public $aBackupCodes = [];
|
||||
protected $_sThisTemplate = '@'.Constants::OXID_MODULE_ID.'/admin/d3user_totp';
|
||||
|
||||
public array $aBackupCodes = [];
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
public function render(): string
|
||||
{
|
||||
parent::render();
|
||||
|
||||
@ -64,50 +70,43 @@ class d3user_totp extends AdminDetailsController
|
||||
/**
|
||||
* @return User
|
||||
*/
|
||||
public function getUserObject()
|
||||
public function getUserObject(): User
|
||||
{
|
||||
return oxNew(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return d3totp
|
||||
* @throws DBALDriverException
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function getTotpObject()
|
||||
{
|
||||
return oxNew(d3totp::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return d3backupcodelist
|
||||
*/
|
||||
public function getBackupcodeListObject()
|
||||
{
|
||||
return oxNew(d3backupcodelist::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function save()
|
||||
public function save(): void
|
||||
{
|
||||
parent::save();
|
||||
|
||||
$aParams = Registry::getRequest()->getRequestEscapedParameter("editval");
|
||||
$aParams = Registry::getRequest()->getRequestEscapedParameter("editval") ?? [];
|
||||
|
||||
try {
|
||||
$oTotp = $this->getTotpObject();
|
||||
if ($oTotp->checkIfAlreadyExist($this->getEditObjectId())) {
|
||||
throw oxNew(StandardException::class, 'D3_TOTP_ALREADY_EXIST');
|
||||
}
|
||||
|
||||
Assert::that($oTotp->checkIfAlreadyExist($this->getCurrentUserId()))->false('D3_TOTP_ALREADY_EXIST');
|
||||
|
||||
$oTotpBackupCodes = $this->getBackupcodeListObject();
|
||||
if ($aParams['d3totp__oxid']) {
|
||||
if (isset($aParams['d3totp__oxid'])) {
|
||||
$oTotp->load($aParams['d3totp__oxid']);
|
||||
} else {
|
||||
$aParams['d3totp__usetotp'] = 1;
|
||||
$seed = Registry::getRequest()->getRequestEscapedParameter("secret");
|
||||
/** @var d3totp $init */
|
||||
$init = Registry::getSession()->getVariable(d3totp_conf::OTP_SESSION_VARNAME);
|
||||
Assert::that($init)->isInstanceOf(d3totp::class, 'D3_TOTP_INITOBJECT_MISSING');
|
||||
$seed = $init->getSecret();
|
||||
$otp = Registry::getRequest()->getRequestEscapedParameter("otp");
|
||||
|
||||
Assert::that($seed)->notBlank('D3_TOTP_EMPTY_SEED');
|
||||
Assert::that($otp)
|
||||
->integerish('D3_TOTP_MISSING_VALIDATION')
|
||||
->length(6, 'D3_TOTP_MISSING_VALIDATION');
|
||||
|
||||
$oTotp->saveSecret($seed);
|
||||
$oTotp->assign($aParams);
|
||||
$oTotp->verify($otp, $seed);
|
||||
@ -116,19 +115,19 @@ class d3user_totp extends AdminDetailsController
|
||||
}
|
||||
$oTotp->save();
|
||||
$oTotpBackupCodes->save();
|
||||
} catch (Exception $oExcp) {
|
||||
$this->_sSaveError = $oExcp->getMessage();
|
||||
} catch (Exception $exception) {
|
||||
$this->_sSaveError = $exception->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DatabaseConnectionException
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function delete()
|
||||
public function delete(): void
|
||||
{
|
||||
$aParams = Registry::getRequest()->getRequestEscapedParameter("editval");
|
||||
|
||||
/** @var d3totp $oTotp */
|
||||
$oTotp = $this->getTotpObject();
|
||||
if ($aParams['d3totp__oxid']) {
|
||||
$oTotp->load($aParams['d3totp__oxid']);
|
||||
@ -137,29 +136,8 @@ class d3user_totp extends AdminDetailsController
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $aCodes
|
||||
*/
|
||||
public function setBackupCodes($aCodes)
|
||||
public function getCurrentUserId(): string
|
||||
{
|
||||
$this->aBackupCodes = $aCodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getBackupCodes()
|
||||
{
|
||||
return implode(PHP_EOL, $this->aBackupCodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* @throws DatabaseConnectionException
|
||||
*/
|
||||
public function getAvailableBackupCodeCount()
|
||||
{
|
||||
$oBackupCodeList = $this->getBackupcodeListObject();
|
||||
return $oBackupCodeList->getAvailableCodeCount($this->getEditObjectId());
|
||||
return $this->getEditObjectId();
|
||||
}
|
||||
}
|
70
Application/Controller/OtpManagementControllerTrait.php
Normal file
70
Application/Controller/OtpManagementControllerTrait.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Application\Controller;
|
||||
|
||||
use D3\Totp\Application\Model\d3backupcodelist;
|
||||
use D3\Totp\Application\Model\d3totp;
|
||||
use Doctrine\DBAL\Driver\Exception;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
trait OtpManagementControllerTrait
|
||||
{
|
||||
/**
|
||||
* @param array $aCodes
|
||||
*/
|
||||
public function setBackupCodes(array $aCodes): void
|
||||
{
|
||||
$this->aBackupCodes = $aCodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getBackupCodes(): string
|
||||
{
|
||||
return implode(PHP_EOL, $this->aBackupCodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return d3backupcodelist
|
||||
*/
|
||||
public function getBackupCodeListObject(): d3backupcodelist
|
||||
{
|
||||
return oxNew(d3backupcodelist::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* @throws Exception
|
||||
* @throws \Doctrine\DBAL\Exception
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function getAvailableBackupCodeCount(): int
|
||||
{
|
||||
$oBackupCodeList = $this->getBackupCodeListObject();
|
||||
return $oBackupCodeList->getAvailableCodeCount($this->getCurrentUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return d3totp
|
||||
*/
|
||||
public function getTotpObject(): d3totp
|
||||
{
|
||||
return oxNew(d3totp::class);
|
||||
}
|
||||
}
|
@ -15,23 +15,28 @@ declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Application\Controller;
|
||||
|
||||
use D3\Totp\Application\Model\d3backupcodelist;
|
||||
use Assert\Assert;
|
||||
use D3\Totp\Application\Model\Constants;
|
||||
use D3\Totp\Application\Model\d3totp;
|
||||
use D3\Totp\Application\Model\d3totp_conf;
|
||||
use D3\Totp\Modules\Application\Model\d3_totp_user;
|
||||
use Exception;
|
||||
use OxidEsales\Eshop\Application\Controller\AccountController;
|
||||
use OxidEsales\Eshop\Application\Model\User;
|
||||
use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
use OxidEsales\Eshop\Core\UtilsView;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
class d3_account_totp extends AccountController
|
||||
{
|
||||
protected $_sThisTemplate = 'd3_account_totp.tpl';
|
||||
use OtpManagementControllerTrait;
|
||||
|
||||
public $aBackupCodes = [];
|
||||
protected $_sThisTemplate = '@'.Constants::OXID_MODULE_ID.'/tpl/d3_account_totp';
|
||||
|
||||
public function render()
|
||||
public array $aBackupCodes = [];
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
$sRet = parent::render();
|
||||
|
||||
@ -40,65 +45,43 @@ class d3_account_totp extends AccountController
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aCodes
|
||||
*/
|
||||
public function setBackupCodes(array $aCodes)
|
||||
public function getCurrentUserId(): string
|
||||
{
|
||||
$this->aBackupCodes = $aCodes;
|
||||
return $this->getUser()->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @return void
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws \Doctrine\DBAL\Driver\Exception
|
||||
*/
|
||||
public function getBackupCodes()
|
||||
{
|
||||
return implode(PHP_EOL, $this->aBackupCodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return d3backupcodelist
|
||||
*/
|
||||
public function getBackupCodeListObject()
|
||||
{
|
||||
return oxNew(d3backupcodelist::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* @throws DatabaseConnectionException
|
||||
*/
|
||||
public function getAvailableBackupCodeCount()
|
||||
{
|
||||
$oBackupCodeList = $this->getBackupCodeListObject();
|
||||
return $oBackupCodeList->getAvailableCodeCount($this->getUser()->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return d3totp
|
||||
*/
|
||||
public function getTotpObject()
|
||||
{
|
||||
return oxNew(d3totp::class);
|
||||
}
|
||||
|
||||
public function create()
|
||||
public function create(): void
|
||||
{
|
||||
if (Registry::getRequest()->getRequestEscapedParameter('totp_use') === '1') {
|
||||
try {
|
||||
/** @var d3_totp_user $oUser */
|
||||
$oUser = $this->getUser();
|
||||
|
||||
/** @var d3totp $oTotp */
|
||||
$oTotp = $this->getTotpObject();
|
||||
|
||||
Assert::that($oTotp->checkIfAlreadyExist($this->getCurrentUserId()))->false('D3_TOTP_ALREADY_EXIST');
|
||||
|
||||
$oTotpBackupCodes = $this->getBackupCodeListObject();
|
||||
|
||||
$aParams = [
|
||||
'd3totp__usetotp' => 1,
|
||||
'd3totp__oxuserid' => $oUser->getId(),
|
||||
];
|
||||
$seed = Registry::getRequest()->getRequestEscapedParameter("secret");
|
||||
/** @var d3totp $init */
|
||||
$init = Registry::getSession()->getVariable(d3totp_conf::OTP_SESSION_VARNAME);
|
||||
$seed = $init->getSecret();
|
||||
$otp = Registry::getRequest()->getRequestEscapedParameter("otp");
|
||||
|
||||
Assert::that($seed)->notBlank('D3_TOTP_EMPTY_SEED');
|
||||
Assert::that($otp)
|
||||
->integerish('D3_TOTP_MISSING_VALIDATION')
|
||||
->length(6, 'D3_TOTP_MISSING_VALIDATION');
|
||||
|
||||
$oTotp->saveSecret($seed);
|
||||
$oTotp->assign($aParams);
|
||||
$oTotp->verify($otp, $seed);
|
||||
@ -114,13 +97,15 @@ class d3_account_totp extends AccountController
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DatabaseConnectionException
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws \Doctrine\DBAL\Driver\Exception
|
||||
* @throws \Doctrine\DBAL\Exception
|
||||
*/
|
||||
public function delete()
|
||||
public function delete(): void
|
||||
{
|
||||
if (Registry::getRequest()->getRequestEscapedParameter('totp_use') !== '1') {
|
||||
$oUser = $this->getUser();
|
||||
/** @var d3totp $oTotp */
|
||||
$oTotp = $this->getTotpObject();
|
||||
if ($oUser instanceof User && $oUser->getId()) {
|
||||
$oTotp->loadByUserId($oUser->getId());
|
@ -15,18 +15,21 @@ declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Application\Controller;
|
||||
|
||||
use D3\Totp\Application\Model\Constants;
|
||||
use D3\Totp\Application\Model\d3backupcodelist;
|
||||
use D3\Totp\Application\Model\d3totp_conf;
|
||||
use Doctrine\DBAL\Driver\Exception;
|
||||
use OxidEsales\Eshop\Application\Controller\FrontendController;
|
||||
use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
use OxidEsales\Eshop\Core\Utils;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
class d3totplogin extends FrontendController
|
||||
{
|
||||
protected $_sThisTemplate = 'd3totplogin.tpl';
|
||||
protected $_sThisTemplate = '@'.Constants::OXID_MODULE_ID.'/tpl/d3totplogin';
|
||||
|
||||
public function render()
|
||||
public function render(): string
|
||||
{
|
||||
if (!Registry::getSession()->hasVariable(d3totp_conf::SESSION_CURRENTUSER)) {
|
||||
$this->getUtils()->redirect('index.php?cl=start', false);
|
||||
@ -47,7 +50,10 @@ class d3totplogin extends FrontendController
|
||||
|
||||
/**
|
||||
* @return string|void
|
||||
* @throws DatabaseConnectionException
|
||||
* @throws Exception
|
||||
* @throws \Doctrine\DBAL\Exception
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function getBackupCodeCountMessage()
|
||||
{
|
||||
@ -71,7 +77,7 @@ class d3totplogin extends FrontendController
|
||||
return oxNew(d3backupcodelist::class);
|
||||
}
|
||||
|
||||
public function getPreviousClass()
|
||||
public function getPreviousClass(): string
|
||||
{
|
||||
return Registry::getSession()->getVariable(d3totp_conf::SESSION_CURRENTCLASS);
|
||||
}
|
||||
@ -100,7 +106,7 @@ class d3totplogin extends FrontendController
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getBreadCrumb()
|
||||
public function getBreadCrumb(): array
|
||||
{
|
||||
$aPaths = [];
|
||||
$aPath = [];
|
@ -1,5 +1,16 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Application\Factory;
|
||||
@ -12,14 +23,15 @@ use BaconQrCode\Renderer\RendererStyle\RendererStyle; // v2.0.0
|
||||
class BaconQrCodeFactory
|
||||
{
|
||||
/**
|
||||
* @param int $size
|
||||
* @return RendererInterface
|
||||
*/
|
||||
public static function renderer($size)
|
||||
public static function renderer(int $size): RendererInterface
|
||||
{
|
||||
return self::v200($size);
|
||||
}
|
||||
|
||||
private static function v200($size)
|
||||
private static function v200(int $size): RendererInterface
|
||||
{
|
||||
return oxNew(
|
||||
ImageRenderer::class,
|
@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
@ -12,10 +13,9 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\tests\unit;
|
||||
namespace D3\Totp\Application\Model;
|
||||
|
||||
use OxidEsales\TestingLibrary\UnitTestCase;
|
||||
|
||||
abstract class d3TotpUnitTestCase extends UnitTestCase
|
||||
class Constants
|
||||
{
|
||||
public const OXID_MODULE_ID = 'd3totp';
|
||||
}
|
@ -24,7 +24,7 @@ class d3RandomGenerator extends Rand
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getRandomTotpBackupCode()
|
||||
public static function getRandomTotpBackupCode(): string
|
||||
{
|
||||
return self::getString(6, self::CHAR_DIGITS);
|
||||
}
|
110
Application/Model/d3backupcode.php
Normal file
110
Application/Model/d3backupcode.php
Normal file
@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Application\Model;
|
||||
|
||||
use D3\Totp\Modules\Application\Model\d3_totp_user;
|
||||
use Doctrine\DBAL\Driver\Exception;
|
||||
use Doctrine\DBAL\Query\QueryBuilder;
|
||||
use OxidEsales\Eshop\Application\Model\User;
|
||||
use OxidEsales\Eshop\Core\Model\BaseModel;
|
||||
use OxidEsales\EshopCommunity\Internal\Container\ContainerFactory;
|
||||
use OxidEsales\EshopCommunity\Internal\Framework\Database\QueryBuilderFactoryInterface;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
class d3backupcode extends BaseModel
|
||||
{
|
||||
protected $_sCoreTable = 'd3totp_backupcodes';
|
||||
|
||||
/**
|
||||
* @param string $sUserId
|
||||
* @return string
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws Exception
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws \Doctrine\DBAL\Exception
|
||||
*/
|
||||
public function generateCode(string $sUserId): string
|
||||
{
|
||||
$sCode = $this->getRandomTotpBackupCode();
|
||||
$this->assign([
|
||||
'oxuserid' => $sUserId,
|
||||
'backupcode' => $this->d3EncodeBC($sCode, $sUserId),
|
||||
]);
|
||||
|
||||
return $sCode;
|
||||
}
|
||||
|
||||
public function getRandomTotpBackupCode(): string
|
||||
{
|
||||
return d3RandomGenerator::getRandomTotpBackupCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $code
|
||||
* @param string $sUserId
|
||||
* @return string
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws Exception
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws \Doctrine\DBAL\Exception
|
||||
*/
|
||||
public function d3EncodeBC(string $code, string $sUserId): string
|
||||
{
|
||||
$oUser = $this->d3TotpGetUserObject();
|
||||
$oUser->load($sUserId);
|
||||
$salt = $oUser->getFieldData('oxpasssalt');
|
||||
|
||||
$qb = $this->getQueryBuilder();
|
||||
$qb->select('BINARY MD5( CONCAT('.$qb->createNamedParameter($code).', UNHEX('.$qb->createNamedParameter($salt).')))');
|
||||
|
||||
return $qb->execute()->fetchOne();
|
||||
}
|
||||
|
||||
public function d3GetUser(): User
|
||||
{
|
||||
/** @var User|null $user */
|
||||
$user = $this->getUser();
|
||||
|
||||
if ($user instanceof User) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
/** @var d3_totp_user $oUser */
|
||||
$oUser = $this->d3TotpGetUserObject();
|
||||
$sUserId = $oUser->d3TotpGetCurrentUser();
|
||||
$oUser->load($sUserId);
|
||||
return $oUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User
|
||||
*/
|
||||
public function d3TotpGetUserObject(): User
|
||||
{
|
||||
return oxNew(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return QueryBuilder
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function getQueryBuilder(): QueryBuilder
|
||||
{
|
||||
return ContainerFactory::getInstance()->getContainer()->get(QueryBuilderFactoryInterface::class)->create();
|
||||
}
|
||||
}
|
198
Application/Model/d3backupcodelist.php
Normal file
198
Application/Model/d3backupcodelist.php
Normal file
@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Application\Model;
|
||||
|
||||
use D3\Totp\Application\Controller\Admin\d3user_totp;
|
||||
use Doctrine\DBAL\Driver\Exception as DBALDriverException;
|
||||
use Doctrine\DBAL\Exception as DBALException;
|
||||
use Doctrine\DBAL\Query\QueryBuilder;
|
||||
use Exception;
|
||||
use OxidEsales\Eshop\Application\Model\User;
|
||||
use OxidEsales\Eshop\Core\Config;
|
||||
use OxidEsales\Eshop\Core\Model\ListModel;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
use OxidEsales\EshopCommunity\Internal\Container\ContainerFactory;
|
||||
use OxidEsales\EshopCommunity\Internal\Framework\Database\QueryBuilderFactoryInterface;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
class d3backupcodelist extends ListModel
|
||||
{
|
||||
protected $_sObjectsInListName = d3backupcode::class;
|
||||
|
||||
/** @var string */
|
||||
protected $_sCoreTable = 'd3totp_backupcodes';
|
||||
|
||||
protected array $_backupCodes = [];
|
||||
|
||||
/**
|
||||
* @param string $sUserId
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws DBALDriverException
|
||||
* @throws DBALException
|
||||
*/
|
||||
public function generateBackupCodes(string $sUserId): void
|
||||
{
|
||||
$this->deleteAllFromUser($sUserId);
|
||||
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
$oBackupCode = $this->getD3BackupCodeObject();
|
||||
$this->_backupCodes[] = $oBackupCode->generateCode($sUserId);
|
||||
$this->offsetSet(md5((string) rand()), $oBackupCode);
|
||||
}
|
||||
|
||||
/** @var d3user_totp $oActView */
|
||||
$oActView = $this->d3GetConfig()->getActiveView();
|
||||
$oActView->setBackupCodes($this->_backupCodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return d3backupcode
|
||||
*/
|
||||
public function getD3BackupCodeObject(): d3backupcode
|
||||
{
|
||||
return oxNew(d3backupcode::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Config
|
||||
*/
|
||||
public function d3GetConfig(): Config
|
||||
{
|
||||
return Registry::getConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function save(): void
|
||||
{
|
||||
/** @var d3backupcode $oBackupCode */
|
||||
foreach ($this->getArray() as $oBackupCode) {
|
||||
$oBackupCode->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return d3backupcode
|
||||
*/
|
||||
public function getBaseObject(): d3backupcode
|
||||
{
|
||||
/** @var d3backupcode $object */
|
||||
$object = parent::getBaseObject();
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $totp
|
||||
* @return bool
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws DBALDriverException
|
||||
* @throws DBALException
|
||||
*/
|
||||
public function verify(string $totp): bool
|
||||
{
|
||||
$qb = $this->getQueryBuilder();
|
||||
$qb->select('oxid')
|
||||
->from($this->getBaseObject()->getViewName())
|
||||
->where(
|
||||
$qb->expr()->and(
|
||||
$qb->expr()->eq(
|
||||
'backupcode',
|
||||
$qb->createNamedParameter($this->getBaseObject()->d3EncodeBC($totp, $this->d3GetUser()->getId()))
|
||||
),
|
||||
$qb->expr()->eq(
|
||||
'oxuserid',
|
||||
$qb->createNamedParameter($this->d3GetUser()->getId())
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$sVerify = $qb->execute()->fetchOne();
|
||||
|
||||
if ($sVerify) {
|
||||
$this->getBaseObject()->delete($sVerify);
|
||||
}
|
||||
|
||||
return (bool) $sVerify;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sUserId
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function deleteAllFromUser(string $sUserId): void
|
||||
{
|
||||
$qb = $this->getQueryBuilder();
|
||||
$qb->select('oxid')
|
||||
->from($this->getBaseObject()->getCoreTableName())
|
||||
->where(
|
||||
$qb->expr()->eq(
|
||||
'oxuserid',
|
||||
$qb->createNamedParameter($sUserId)
|
||||
)
|
||||
);
|
||||
|
||||
$this->selectString($qb->getSQL(), $qb->getParameters());
|
||||
|
||||
/** @var d3backupcode $oBackupCode */
|
||||
foreach ($this->getArray() as $oBackupCode) {
|
||||
$oBackupCode->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sUserId
|
||||
* @return int
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws DBALDriverException
|
||||
* @throws DBALException
|
||||
*/
|
||||
public function getAvailableCodeCount(string $sUserId): int
|
||||
{
|
||||
$qb = $this->getQueryBuilder();
|
||||
$qb->select('count(*)')
|
||||
->from($this->getBaseObject()->getViewName())
|
||||
->where(
|
||||
$qb->expr()->eq(
|
||||
'oxuserid',
|
||||
$qb->createNamedParameter($sUserId)
|
||||
)
|
||||
);
|
||||
|
||||
return (int) $qb->execute()->fetchOne();
|
||||
}
|
||||
|
||||
public function d3GetUser(): User
|
||||
{
|
||||
return $this->getBaseObject()->d3GetUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return QueryBuilder
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function getQueryBuilder(): QueryBuilder
|
||||
{
|
||||
return ContainerFactory::getInstance()->getContainer()->get(QueryBuilderFactoryInterface::class)->create();
|
||||
}
|
||||
}
|
330
Application/Model/d3totp.php
Normal file
330
Application/Model/d3totp.php
Normal file
@ -0,0 +1,330 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Application\Model;
|
||||
|
||||
use BaconQrCode\Renderer\RendererInterface;
|
||||
use BaconQrCode\Writer;
|
||||
use D3\Totp\Application\Factory\BaconQrCodeFactory;
|
||||
use D3\Totp\Application\Model\Exceptions\d3totp_wrongOtpException;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Driver\Exception;
|
||||
use Doctrine\DBAL\Exception as DBALException;
|
||||
use Doctrine\DBAL\Query\QueryBuilder;
|
||||
use OTPHP\TOTP;
|
||||
use OxidEsales\Eshop\Application\Model\User;
|
||||
use OxidEsales\Eshop\Core\Model\BaseModel;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
use OxidEsales\EshopCommunity\Internal\Container\ContainerFactory;
|
||||
use OxidEsales\EshopCommunity\Internal\Framework\Database\ConnectionProviderInterface;
|
||||
use OxidEsales\EshopCommunity\Internal\Framework\Database\QueryBuilderFactoryInterface;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
class d3totp extends BaseModel
|
||||
{
|
||||
protected const ENC_KEY = 'fq45QS09_fqyx09239QQ';
|
||||
|
||||
protected $_sCoreTable = 'd3totp';
|
||||
public null|string $userId = null;
|
||||
public null|TOTP $totp = null;
|
||||
protected int $timeWindow = 2;
|
||||
|
||||
/**
|
||||
* d3totp constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->init($this->getCoreTableName());
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $userId
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws DBALException
|
||||
* @throws Exception
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function loadByUserId(string $userId): void
|
||||
{
|
||||
$this->userId = $userId;
|
||||
|
||||
if ($this->getDbConnection()
|
||||
->prepare("SHOW TABLES LIKE ".$this->getDbConnection()->quote($this->getCoreTableName()))
|
||||
->executeQuery()
|
||||
->fetchOne()
|
||||
) {
|
||||
$qb = $this->getQueryBuilder();
|
||||
$qb->select('oxid')
|
||||
->from($this->getViewName())
|
||||
->where(
|
||||
$qb->expr()->eq('oxuserid', $qb->createNamedParameter($userId))
|
||||
)
|
||||
->setMaxResults(1);
|
||||
|
||||
if ($oxid = $qb->execute()->fetchOne()) {
|
||||
$this->load($oxid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $userId
|
||||
* @return bool
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws DBALException
|
||||
* @throws Exception
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function checkIfAlreadyExist(string $userId): bool
|
||||
{
|
||||
$qb = $this->getQueryBuilder();
|
||||
$qb->select('1')
|
||||
->from($this->getViewName())
|
||||
->where(
|
||||
$qb->expr()->eq('oxuserid', $qb->createNamedParameter($userId))
|
||||
)
|
||||
->setMaxResults(1);
|
||||
|
||||
return (bool) $qb->execute()->fetchOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Connection
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function getDbConnection(): Connection
|
||||
{
|
||||
return ContainerFactory::getInstance()->getContainer()->get(ConnectionProviderInterface::class)->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return QueryBuilder
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function getQueryBuilder(): QueryBuilder
|
||||
{
|
||||
return ContainerFactory::getInstance()->getContainer()->get(QueryBuilderFactoryInterface::class)->create();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User
|
||||
*/
|
||||
public function getUser(): User
|
||||
{
|
||||
$userId = $this->userId ?? $this->getFieldData('oxuserid');
|
||||
|
||||
$user = $this->d3GetUser();
|
||||
$user->load($userId);
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User
|
||||
*/
|
||||
public function d3GetUser(): User
|
||||
{
|
||||
return oxNew(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isActive(): bool
|
||||
{
|
||||
return false == Registry::getConfig()->getConfigParam('blDisableTotpGlobally')
|
||||
&& $this->UserUseTotp();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function UserUseTotp(): bool
|
||||
{
|
||||
return $this->getFieldData('usetotp')
|
||||
&& $this->getFieldData('seed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getSavedSecret(): ?string
|
||||
{
|
||||
$seed_enc = $this->getFieldData('seed');
|
||||
|
||||
if ($seed_enc) {
|
||||
$seed = $this->decrypt($seed_enc);
|
||||
if ($seed) {
|
||||
return $seed;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $seed
|
||||
* @return TOTP
|
||||
*/
|
||||
public function getTotp(string $seed = null): TOTP
|
||||
{
|
||||
if (null == $this->totp) {
|
||||
$this->totp = TOTP::create($seed ?: $this->getSavedSecret());
|
||||
$this->totp->setLabel($this->getUser()->getFieldData('oxusername') ?: '');
|
||||
$this->totp->setIssuer(Registry::getConfig()->getActiveShop()->getFieldData('oxname'));
|
||||
}
|
||||
|
||||
return $this->totp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getQrCodeElement(): string
|
||||
{
|
||||
$renderer = BaconQrCodeFactory::renderer(200);
|
||||
$writer = $this->d3GetWriter($renderer);
|
||||
|
||||
return $writer->writeString($this->getTotp()->getProvisioningUri());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RendererInterface $renderer
|
||||
* @return Writer
|
||||
*/
|
||||
public function d3GetWriter(RendererInterface $renderer): Writer
|
||||
{
|
||||
return oxNew(Writer::class, $renderer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSecret(): string
|
||||
{
|
||||
return trim($this->getTotp()->getSecret());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $seed
|
||||
*/
|
||||
public function saveSecret(string $seed): void
|
||||
{
|
||||
$this->assign([
|
||||
'seed' => $this->encrypt($seed),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $totp
|
||||
* @param string|null $seed
|
||||
* @return bool
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws DBALException
|
||||
* @throws d3totp_wrongOtpException
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
public function verify(string $totp, string $seed = null): bool
|
||||
{
|
||||
$blNotVerified = $this->getTotp($seed)->verify($totp, null, $this->timeWindow) == false;
|
||||
|
||||
if ($blNotVerified && null == $seed) {
|
||||
$oBC = $this->d3GetBackupCodeListObject();
|
||||
$blNotVerified = $oBC->verify($totp) == false;
|
||||
|
||||
if ($blNotVerified) {
|
||||
/** @var d3totp_wrongOtpException $exception */
|
||||
$exception = oxNew(d3totp_wrongOtpException::class);
|
||||
throw $exception;
|
||||
}
|
||||
} elseif ($blNotVerified && $seed !== null) {
|
||||
/** @var d3totp_wrongOtpException $exception */
|
||||
$exception = oxNew(d3totp_wrongOtpException::class);
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
return !$blNotVerified;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return d3backupcodelist
|
||||
*/
|
||||
public function d3GetBackupCodeListObject(): d3backupcodelist
|
||||
{
|
||||
return oxNew(d3backupcodelist::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $plaintext
|
||||
* @return string
|
||||
*/
|
||||
public function encrypt(string $plaintext): string
|
||||
{
|
||||
$ivlen = openssl_cipher_iv_length($cipher = "AES-128-CBC");
|
||||
$iv = openssl_random_pseudo_bytes($ivlen);
|
||||
$ciphertext_raw = openssl_encrypt($plaintext, $cipher, self::ENC_KEY, OPENSSL_RAW_DATA, $iv);
|
||||
$hmac = hash_hmac('sha256', $ciphertext_raw, self::ENC_KEY, true);
|
||||
return base64_encode($iv.$hmac.$ciphertext_raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $ciphertext
|
||||
* @return false|string
|
||||
*/
|
||||
public function decrypt(string $ciphertext): false|string
|
||||
{
|
||||
$c = $this->d3Base64_decode($ciphertext);
|
||||
$ivlen = openssl_cipher_iv_length($cipher = "AES-128-CBC");
|
||||
$iv = substr($c, 0, $ivlen);
|
||||
$hmac = substr($c, $ivlen, $sha2len = 32);
|
||||
$ciphertext_raw = substr($c, $ivlen + $sha2len);
|
||||
$original_plaintext = openssl_decrypt($ciphertext_raw, $cipher, self::ENC_KEY, OPENSSL_RAW_DATA, $iv);
|
||||
$calcmac = hash_hmac('sha256', $ciphertext_raw, self::ENC_KEY, true);
|
||||
if (hash_equals($hmac, $calcmac)) { // PHP 5.6+ compute attack-safe comparison
|
||||
return $original_plaintext;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* required for unit tests
|
||||
* @param string $source
|
||||
* @return string
|
||||
*/
|
||||
public function d3Base64_decode(string $source): string
|
||||
{
|
||||
return base64_decode($source);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $oxid
|
||||
* @return bool
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function delete($oxid = null): bool
|
||||
{
|
||||
$oBackupCodeList = $this->d3GetBackupCodeListObject();
|
||||
$oBackupCodeList->deleteAllFromUser($this->getFieldData('oxuserid'));
|
||||
|
||||
return parent::delete($oxid);
|
||||
}
|
||||
}
|
@ -27,4 +27,5 @@ class d3totp_conf
|
||||
public const SESSION_ADMIN_CURRENTUSER = 'd3Totp_currentUser'; // oxid assigned to user from entered username
|
||||
public const SESSION_ADMIN_PROFILE = 'd3Totp_currentProfile'; // selected profile
|
||||
public const SESSION_ADMIN_CHLANGUAGE = 'd3Totp_currentChLanguage'; // selected language
|
||||
public const OTP_SESSION_VARNAME = 'd3totpinstance'; // saved OTP initialisation
|
||||
}
|
22
Application/translations/de/d3totp_lang.php
Normal file
22
Application/translations/de/d3totp_lang.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
|
||||
$sLangName = 'Deutsch';
|
||||
|
||||
$aLang = include __DIR__."/translations.php";
|
||||
|
||||
// @codeCoverageIgnoreEnd
|
@ -13,9 +13,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$sLangName = "Deutsch";
|
||||
|
||||
$aLang = [
|
||||
return [
|
||||
'charset' => 'UTF-8',
|
||||
|
||||
'D3_TOTP_INPUT' => 'Authentisierungscode',
|
||||
@ -27,8 +25,6 @@ $aLang = [
|
||||
'D3_TOTP_ACCOUNT' => '2-Faktor-Authentisierung',
|
||||
'D3_TOTP_ACCOUNT_DESC' => 'Sichern Sie Ihre Kontoanmeldung mit einem zweiten Faktor.',
|
||||
|
||||
'D3_TOTP_ACCOUNT_USE' => '2-Faktor-Authentisierung verwenden',
|
||||
|
||||
'D3_TOTP_REGISTERNEW' => 'neue Registrierung erstellen',
|
||||
'D3_TOTP_QRCODE' => 'QR-Code',
|
||||
'D3_TOTP_QRCODE_HELP' => 'Scannen Sie diesen QR-Code mit Ihrer Authentisierungs-App, um dieses Benutzerkonto dort zu hinterlegen.',
|
||||
@ -37,6 +33,9 @@ $aLang = [
|
||||
'D3_TOTP_CURROTP' => 'Bestätigung mit Einmalpasswort',
|
||||
'D3_TOTP_CURROTP_HELP' => 'Haben Sie dieses Kundenkonto in Ihrer Authentisierungs-App registriert, generieren Sie damit ein Einmalpasswort, tragen Sie es hier ein und senden das Formular direkt darauf hin ab.',
|
||||
|
||||
'D3_TOTP_STATUS' => 'Status',
|
||||
'D3_TOTP_ACCOUNT_USE' => '2-Faktor-Authentisierung verwenden',
|
||||
|
||||
'D3_TOTP_REGISTEREXIST' => 'vorhandene Registrierung',
|
||||
'D3_TOTP_REGISTERDELETE_DESC' => 'Um die Registrierung zu ändern, löschen Sie diese bitte vorerst. Sie können sofort im Anschluss eine neue Registrierung anlegen.<br>Wenn Sie die Registrierung löschen, ist das Konto nicht mehr durch die Zwei-Faktor-Authentisierung geschützt.',
|
||||
'D3_TOTP_REGISTERDELETE_CONFIRM' => 'Soll die bestehende 2-Faktor-Authentisierung gelöscht werden?',
|
||||
@ -46,6 +45,13 @@ $aLang = [
|
||||
'D3_TOTP_AVAILBACKUPCODECOUNT' => 'noch %1$s Backupcode(s) verfügbar',
|
||||
'D3_TOTP_AVAILBACKUPCODECOUNT_DESC' => 'Um neue Backupcodes zu erstellen, löschen Sie die bestehende Registrierung und legen diese bitte neu an.',
|
||||
|
||||
'D3_TOTP_INPUT_FIRST' => 'erste TOTP-Ziffer',
|
||||
'D3_TOTP_INPUT_SECOND' => 'zweite TOTP-Ziffer',
|
||||
'D3_TOTP_INPUT_THIRD' => 'dritte TOTP-Ziffer',
|
||||
'D3_TOTP_INPUT_FOURTH' => 'vierte TOTP-Ziffer',
|
||||
'D3_TOTP_INPUT_FIFTH' => 'fünfte TOTP-Ziffer',
|
||||
'D3_TOTP_INPUT_SIXTH' => 'sechste TOTP-Ziffer',
|
||||
|
||||
'D3_TOTP_ACCOUNT_SAVE' => 'Einstellungen übernehmen',
|
||||
|
||||
];
|
22
Application/translations/en/d3totp_lang.php
Normal file
22
Application/translations/en/d3totp_lang.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
|
||||
$sLangName = 'English';
|
||||
|
||||
$aLang = include __DIR__."/translations.php";
|
||||
|
||||
// @codeCoverageIgnoreEnd
|
@ -13,9 +13,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$sLangName = "English";
|
||||
|
||||
$aLang = [
|
||||
return [
|
||||
'charset' => 'UTF-8',
|
||||
|
||||
'D3_TOTP_INPUT' => 'authentication code',
|
||||
@ -27,8 +25,6 @@ $aLang = [
|
||||
'D3_TOTP_ACCOUNT' => '2-factor authentication',
|
||||
'D3_TOTP_ACCOUNT_DESC' => 'Secure your account login with a second factor.',
|
||||
|
||||
'D3_TOTP_ACCOUNT_USE' => 'Use 2-factor authentication',
|
||||
|
||||
'D3_TOTP_REGISTERNEW' => 'create a new registration',
|
||||
'D3_TOTP_QRCODE' => 'QR code',
|
||||
'D3_TOTP_QRCODE_HELP' => 'Scan this QR code with your authentication app to store this user account there.',
|
||||
@ -37,15 +33,25 @@ $aLang = [
|
||||
'D3_TOTP_CURROTP' => 'Confirmation with one-time password',
|
||||
'D3_TOTP_CURROTP_HELP' => 'If you have registered this customer account in your authentication app, use it to generate a one-time password, enter it here and send the form directly afterwards.',
|
||||
|
||||
'D3_TOTP_REGISTEREXIST' => 'existing registration',
|
||||
'D3_TOTP_STATUS' => 'Status',
|
||||
'D3_TOTP_ACCOUNT_USE' => 'use 2-factor authentication',
|
||||
|
||||
'D3_TOTP_REGISTEREXIST' => 'Existing registration',
|
||||
'D3_TOTP_REGISTERDELETE_DESC' => 'To change the registration, please delete it first. You can then create a new registration immediately. <br>If you delete the registration, the account is no longer protected by two-factor authentication.',
|
||||
'D3_TOTP_REGISTERDELETE_CONFIRM' => 'Should the existing 2-factor authentication be deleted?',
|
||||
|
||||
'D3_TOTP_BACKUPCODES' => 'backup codes',
|
||||
'D3_TOTP_BACKUPCODES' => 'Backup codes',
|
||||
'D3_TOTP_BACKUPCODES_DESC' => 'You can use these backup codes to log on if it is not possible to generate the one-time password (e.g. device lost or newly installed). You can then change the settings to use 2-factor authentication or create a new account. Please save these codes securely at this moment. After leaving this page, these codes cannot be displayed again.',
|
||||
'D3_TOTP_AVAILBACKUPCODECOUNT' => 'still %1$s backup code(s) available',
|
||||
'D3_TOTP_AVAILBACKUPCODECOUNT_DESC' => 'To create new backup codes, delete the existing registry and create a new one.',
|
||||
|
||||
'D3_TOTP_INPUT_FIRST' => 'first TOTP digit',
|
||||
'D3_TOTP_INPUT_SECOND' => 'second TOTP digit',
|
||||
'D3_TOTP_INPUT_THIRD' => 'third TOTP digit',
|
||||
'D3_TOTP_INPUT_FOURTH' => 'fourth TOTP digit',
|
||||
'D3_TOTP_INPUT_FIFTH' => 'fifth TOTP digit',
|
||||
'D3_TOTP_INPUT_SIXTH' => 'sixth TOTP digit',
|
||||
|
||||
'D3_TOTP_ACCOUNT_SAVE' => 'Confirm settings',
|
||||
|
||||
];
|
22
Application/views/admin_smarty/de/d3totp_lang.php
Normal file
22
Application/views/admin_smarty/de/d3totp_lang.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
|
||||
$sLangName = 'Deutsch';
|
||||
|
||||
$aLang = include __DIR__."/../../de/translations.php";
|
||||
|
||||
// @codeCoverageIgnoreEnd
|
22
Application/views/admin_smarty/en/d3totp_lang.php
Normal file
22
Application/views/admin_smarty/en/d3totp_lang.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
|
||||
$sLangName = 'English';
|
||||
|
||||
$aLang = include __DIR__."/../../en/translations.php";
|
||||
|
||||
// @codeCoverageIgnoreEnd
|
22
Application/views/admin_twig/de/d3totp_lang.php
Normal file
22
Application/views/admin_twig/de/d3totp_lang.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
|
||||
$sLangName = 'Deutsch';
|
||||
|
||||
$aLang = include __DIR__."/../../de/translations.php";
|
||||
|
||||
// @codeCoverageIgnoreEnd
|
22
Application/views/admin_twig/en/d3totp_lang.php
Normal file
22
Application/views/admin_twig/en/d3totp_lang.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
|
||||
$sLangName = 'English';
|
||||
|
||||
$aLang = include __DIR__."/../../en/translations.php";
|
||||
|
||||
// @codeCoverageIgnoreEnd
|
@ -11,9 +11,9 @@
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
$sLangName = "Deutsch";
|
||||
declare(strict_types=1);
|
||||
|
||||
$aLang = [
|
||||
return [
|
||||
'charset' => 'UTF-8',
|
||||
|
||||
'TOTP_INPUT' => 'Authentisierungscode',
|
||||
@ -47,10 +47,19 @@ $aLang = [
|
||||
'D3_TOTP_AVAILBACKUPCODECOUNT' => 'noch %1$s Backupcode(s) verfügbar',
|
||||
'D3_TOTP_AVAILBACKUPCODECOUNT_DESC' => 'Um neue Backupcodes zu erstellen, löschen Sie die bestehende Registrierung und legen diese bitte neu an.',
|
||||
|
||||
'D3_TOTP_INPUT_FIRST' => 'erste TOTP-Ziffer',
|
||||
'D3_TOTP_INPUT_SECOND' => 'zweite TOTP-Ziffer',
|
||||
'D3_TOTP_INPUT_THIRD' => 'dritte TOTP-Ziffer',
|
||||
'D3_TOTP_INPUT_FOURTH' => 'vierte TOTP-Ziffer',
|
||||
'D3_TOTP_INPUT_FIFTH' => 'fünfte TOTP-Ziffer',
|
||||
'D3_TOTP_INPUT_SIXTH' => 'sechste TOTP-Ziffer',
|
||||
|
||||
'D3_TOTP_SAVE' => 'Speichern',
|
||||
|
||||
'D3_TOTP_ERROR_UNVALID' => 'Das Einmalpasswort ist ungültig.',
|
||||
'D3_TOTP_ALREADY_EXIST' => 'Die Registrierung wurde schon gespeichert.',
|
||||
'D3_TOTP_MISSING_VALIDATION' => 'Das bestätigende Einmalkennwort muss aus 6 Ziffern bestehen.',
|
||||
'D3_TOTP_EMPTY_SEED' => 'Der verwendete Schlüssel kann nicht ermittelt werden.',
|
||||
|
||||
'SHOP_MODULE_D3_TOTP_ADMIN_FORCE_2FA' => 'Administratoren sind verpflichtet 2FA zu aktivieren',
|
||||
];
|
@ -11,24 +11,24 @@
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
$sLangName = "English";
|
||||
declare(strict_types=1);
|
||||
|
||||
$aLang = [
|
||||
return [
|
||||
'charset' => 'UTF-8',
|
||||
|
||||
'TOTP_INPUT' => 'authentication code',
|
||||
'TOTP_INPUT_HELP' => 'The authentication code is available from the Two-Factor Authentication app on your device.',
|
||||
'TOTP_INPUT_HELP' => 'You get the one-time password from the two-factor authentication app on your device.',
|
||||
'TOTP_CANCEL_LOGIN' => 'Cancel login',
|
||||
|
||||
'd3mxuser_totp' => 'Two-factor authentication',
|
||||
|
||||
'D3_TOTP_REGISTERNEW' => 'create new registration',
|
||||
'D3_TOTP_QRCODE' => 'QR code',
|
||||
'D3_TOTP_QRCODE_HELP' => 'Scan this QR code with your authentication app to deposit this user account.',
|
||||
'D3_TOTP_SECRET' => 'Can not scan QR code?',
|
||||
'D3_TOTP_SECRET_HELP' => 'If you do not use an app that can scan the QR code, you can also copy this string into your authentication tool. Please also set the password length to 6 characters and the time interval to 30 seconds.',
|
||||
'D3_TOTP_QRCODE_HELP' => 'Scan this QR code with your authentication app to store this user account there.',
|
||||
'D3_TOTP_SECRET' => 'QR code cannot be scanned?',
|
||||
'D3_TOTP_SECRET_HELP' => 'If you are not using an app that can scan the QR code, you can also copy this character string into your authentication tool. Please set the password length to 6 characters and the time interval to 30 seconds.',
|
||||
'D3_TOTP_CURROTP' => 'Confirmation with one-time password',
|
||||
'D3_TOTP_CURROTP_HELP' => 'If you have registered this customer account in your authentication app, you generate a one-time password, enter it here and send the form out immediately.',
|
||||
'D3_TOTP_CURROTP_HELP' => 'If you have registered this customer account in your authentication app, use it to generate a one-time password, enter it here and submit the form directly.',
|
||||
|
||||
'SHOP_MODULE_GROUP_d3totp_main' => 'Basic settings',
|
||||
'D3_TOTP_FORCE2FATITLE' => 'Mandates two-factor authentication',
|
||||
@ -47,10 +47,19 @@ $aLang = [
|
||||
'D3_TOTP_AVAILBACKUPCODECOUNT' => '%1$s backup code(s) still available',
|
||||
'D3_TOTP_AVAILBACKUPCODECOUNT_DESC' => 'To create new backup codes, delete the existing registry and create a new one.',
|
||||
|
||||
'D3_TOTP_INPUT_FIRST' => 'first TOTP digit',
|
||||
'D3_TOTP_INPUT_SECOND' => 'second TOTP digit',
|
||||
'D3_TOTP_INPUT_THIRD' => 'third TOTP digit',
|
||||
'D3_TOTP_INPUT_FOURTH' => 'fourth TOTP digit',
|
||||
'D3_TOTP_INPUT_FIFTH' => 'fifth TOTP digit',
|
||||
'D3_TOTP_INPUT_SIXTH' => 'sixth TOTP digit',
|
||||
|
||||
'D3_TOTP_SAVE' => 'Save',
|
||||
|
||||
'D3_TOTP_ERROR_UNVALID' => 'The one-time password is invalid.',
|
||||
'D3_TOTP_ALREADY_EXIST' => 'The registration has already been saved.',
|
||||
'D3_TOTP_MISSING_VALIDATION' => 'The confirming one-time password must consist of 6 digits',
|
||||
'D3_TOTP_EMPTY_SEED' => 'The key used cannot be determined.',
|
||||
|
||||
'SHOP_MODULE_D3_TOTP_ADMIN_FORCE_2FA' => 'Administrators are required to activate 2FA',
|
||||
];
|
13
CHANGELOG.md
13
CHANGELOG.md
@ -4,13 +4,22 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased](https://git.d3data.de/D3Public/oxtotp/compare/2.1.1.0...rel_2.x)
|
||||
## [Unreleased](https://git.d3data.de/D3Public/oxtotp/compare/3.0.0.0...rel_3.x)
|
||||
|
||||
## [3.0.0.0](https://git.d3data.de/D3Public/oxtotp/compare/2.1.1.0...3.0.0.0) - 2024-09-23
|
||||
### Added
|
||||
- installable in OXID eShop 7.0 + 7.1
|
||||
### Removed
|
||||
- support for OXID eShop < 7
|
||||
|
||||
## [2.1.1.0](https://git.d3data.de/D3Public/oxtotp/compare/2.1.0.0...2.1.1.0) - 2023-09-07
|
||||
- add product logo
|
||||
### Added
|
||||
- installable in OXID 6.5.2 + 6.5.3
|
||||
### Changed
|
||||
- product logo
|
||||
|
||||
## [2.1.0.0](https://git.d3data.de/D3Public/oxtotp/compare/2.0.0.1...2.1.0.0) - 2023-02-18
|
||||
### Added
|
||||
- add compatibility to D3 Webauthn plugin
|
||||
- installable in OXID 6.5.1
|
||||
|
||||
|
@ -15,18 +15,21 @@ declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Modules\Application\Component;
|
||||
|
||||
use Assert\Assert;
|
||||
use D3\Totp\Application\Model\d3totp;
|
||||
use D3\Totp\Application\Model\d3totp_conf;
|
||||
use D3\Totp\Application\Model\Exceptions\d3totp_wrongOtpException;
|
||||
use D3\Totp\Modules\Application\Model\d3_totp_user;
|
||||
use Doctrine\DBAL\DBALException;
|
||||
use Doctrine\DBAL\Driver\Exception;
|
||||
use Doctrine\DBAL\Exception as DBALException;
|
||||
use InvalidArgumentException;
|
||||
use OxidEsales\Eshop\Application\Model\User;
|
||||
use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
use OxidEsales\Eshop\Core\Session;
|
||||
use OxidEsales\Eshop\Core\Utils;
|
||||
use OxidEsales\Eshop\Core\UtilsView;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
class d3_totp_UserComponent extends d3_totp_UserComponent_parent
|
||||
{
|
||||
@ -34,15 +37,17 @@ class d3_totp_UserComponent extends d3_totp_UserComponent_parent
|
||||
* @param User $oUser
|
||||
*
|
||||
* @return string
|
||||
* @throws DatabaseConnectionException
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws DBALException
|
||||
* @throws Exception
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
protected function _afterLogin($oUser)
|
||||
protected function afterLogin($oUser)
|
||||
{
|
||||
if (!$oUser instanceof User) {
|
||||
throw oxNew(InvalidArgumentException::class, 'user argument must an instance of User class');
|
||||
}
|
||||
Assert::that($oUser)->isInstanceOf(User::class, 'user argument must an instance of User class');
|
||||
|
||||
if ($oUser->getId()) {
|
||||
try {
|
||||
Assert::that($oUser->getId())->notBlank('user must logged in');
|
||||
$totp = $this->d3GetTotpObject();
|
||||
$totp->loadByUserId($oUser->getId());
|
||||
|
||||
@ -65,9 +70,10 @@ class d3_totp_UserComponent extends d3_totp_UserComponent_parent
|
||||
$sUrl = Registry::getConfig()->getShopHomeUrl() . 'cl=d3totplogin';
|
||||
$this->d3TotpGetUtils()->redirect($sUrl, false);
|
||||
}
|
||||
} catch (InvalidArgumentException) {
|
||||
}
|
||||
|
||||
return parent::_afterLogin($oUser);
|
||||
return parent::afterLogin($oUser);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,10 +85,13 @@ class d3_totp_UserComponent extends d3_totp_UserComponent_parent
|
||||
}
|
||||
|
||||
/**
|
||||
* @return false|string
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws DBALException
|
||||
* @throws DatabaseConnectionException
|
||||
* @throws Exception
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function d3TotpCheckTotpLogin()
|
||||
public function d3TotpCheckTotpLogin(): false|string
|
||||
{
|
||||
$sTotp = implode('', Registry::getRequest()->getRequestEscapedParameter('d3totp') ?: []);
|
||||
|
||||
@ -101,7 +110,7 @@ class d3_totp_UserComponent extends d3_totp_UserComponent_parent
|
||||
$this->d3TotpGetSession()->setVariable(d3totp_conf::OXID_FRONTEND_AUTH, $oUser->getId());
|
||||
$this->setUser($oUser);
|
||||
$this->setLoginStatus(USER_LOGIN_SUCCESS);
|
||||
$this->_afterLogin($oUser);
|
||||
$this->afterLogin($oUser);
|
||||
|
||||
$this->d3TotpClearSessionVariables();
|
||||
|
||||
@ -117,7 +126,7 @@ class d3_totp_UserComponent extends d3_totp_UserComponent_parent
|
||||
/**
|
||||
* @return UtilsView
|
||||
*/
|
||||
public function d3TotpGetUtilsView()
|
||||
public function d3TotpGetUtilsView(): UtilsView
|
||||
{
|
||||
return Registry::getUtilsView();
|
||||
}
|
||||
@ -125,12 +134,12 @@ class d3_totp_UserComponent extends d3_totp_UserComponent_parent
|
||||
/**
|
||||
* @return Utils
|
||||
*/
|
||||
public function d3TotpGetUtils()
|
||||
public function d3TotpGetUtils(): Utils
|
||||
{
|
||||
return Registry::getUtils();
|
||||
}
|
||||
|
||||
public function d3TotpCancelTotpLogin()
|
||||
public function d3TotpCancelTotpLogin(): bool
|
||||
{
|
||||
$this->d3TotpClearSessionVariables();
|
||||
|
||||
@ -141,7 +150,7 @@ class d3_totp_UserComponent extends d3_totp_UserComponent_parent
|
||||
* @param d3totp $totp
|
||||
* @return bool
|
||||
*/
|
||||
public function d3TotpIsNoTotpOrNoLogin($totp)
|
||||
public function d3TotpIsNoTotpOrNoLogin(d3totp $totp): bool
|
||||
{
|
||||
return false == Registry::getSession()->getVariable(d3totp_conf::SESSION_CURRENTUSER)
|
||||
|| false == $totp->isActive();
|
||||
@ -151,16 +160,19 @@ class d3_totp_UserComponent extends d3_totp_UserComponent_parent
|
||||
* @param string $sTotp
|
||||
* @param d3totp $totp
|
||||
* @return bool
|
||||
* @throws DatabaseConnectionException
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws DBALException
|
||||
* @throws Exception
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws d3totp_wrongOtpException
|
||||
*/
|
||||
public function d3TotpHasValidTotp($sTotp, $totp)
|
||||
public function d3TotpHasValidTotp(string $sTotp, d3totp $totp): bool
|
||||
{
|
||||
return Registry::getSession()->getVariable(d3totp_conf::SESSION_AUTH) ||
|
||||
$totp->verify($sTotp);
|
||||
}
|
||||
|
||||
public function d3TotpClearSessionVariables()
|
||||
public function d3TotpClearSessionVariables(): void
|
||||
{
|
||||
$this->d3TotpGetSession()->deleteVariable(d3totp_conf::SESSION_CURRENTCLASS);
|
||||
$this->d3TotpGetSession()->deleteVariable(d3totp_conf::SESSION_CURRENTUSER);
|
||||
@ -170,7 +182,7 @@ class d3_totp_UserComponent extends d3_totp_UserComponent_parent
|
||||
/**
|
||||
* @return Session
|
||||
*/
|
||||
public function d3TotpGetSession()
|
||||
public function d3TotpGetSession(): Session
|
||||
{
|
||||
return Registry::getSession();
|
||||
}
|
@ -15,12 +15,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Modules\Application\Controller\Admin;
|
||||
|
||||
use D3\TestingTools\Production\IsMockable;
|
||||
use D3\Totp\Application\Model\d3totp;
|
||||
use D3\Totp\Application\Model\d3totp_conf;
|
||||
use D3\Totp\Modules\Application\Model\d3_totp_user;
|
||||
use OxidEsales\Eshop\Application\Model\User;
|
||||
use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException;
|
||||
use OxidEsales\Eshop\Core\Language;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
use OxidEsales\Eshop\Core\Session;
|
||||
@ -28,8 +24,6 @@ use OxidEsales\Eshop\Core\UtilsServer;
|
||||
|
||||
class d3_totp_LoginController extends d3_totp_LoginController_parent
|
||||
{
|
||||
use IsMockable;
|
||||
|
||||
/**
|
||||
* @return d3totp
|
||||
*/
|
||||
@ -48,7 +42,6 @@ class d3_totp_LoginController extends d3_totp_LoginController_parent
|
||||
|
||||
/**
|
||||
* @return mixed|string
|
||||
* @throws DatabaseConnectionException
|
||||
*/
|
||||
public function checklogin()
|
||||
{
|
||||
@ -61,10 +54,19 @@ class d3_totp_LoginController extends d3_totp_LoginController_parent
|
||||
Registry::getRequest()->getRequestEscapedParameter('chlanguage')
|
||||
);
|
||||
|
||||
return $this->d3CallMockableFunction([d3_totp_LoginController_parent::class, 'checklogin']);
|
||||
return $this->parent__checklogin();
|
||||
}
|
||||
|
||||
public function d3totpAfterLogin()
|
||||
/**
|
||||
* mockable parent method
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
protected function parent__checklogin()
|
||||
{
|
||||
return parent::checklogin();
|
||||
}
|
||||
|
||||
public function d3totpAfterLogin(): void
|
||||
{
|
||||
$myUtilsServer = $this->d3TotpGetUtilsServer();
|
||||
$sProfile = $this->d3TotpGetSession()->getVariable(d3totp_conf::SESSION_ADMIN_PROFILE);
|
||||
@ -74,30 +76,30 @@ class d3_totp_LoginController extends d3_totp_LoginController_parent
|
||||
$aProfiles = $this->d3TotpGetSession()->getVariable("aAdminProfiles");
|
||||
if ($aProfiles && isset($aProfiles[$sProfile])) {
|
||||
// setting cookie to store last locally used profile
|
||||
$myUtilsServer->setOxCookie("oxidadminprofile", $sProfile . "@" . implode("@", $aProfiles[$sProfile]), time() + 31536000, "/");
|
||||
$myUtilsServer->setOxCookie("oxidadminprofile", $sProfile . "@" . implode("@", $aProfiles[$sProfile]), time() + 31536000);
|
||||
$this->d3TotpGetSession()->setVariable("profile", $aProfiles[$sProfile]);
|
||||
$this->d3TotpGetSession()->deleteVariable(d3totp_conf::SESSION_ADMIN_PROFILE);
|
||||
}
|
||||
} else {
|
||||
//deleting cookie info, as setting profile to default
|
||||
$myUtilsServer->setOxCookie("oxidadminprofile", "", time() - 3600, "/");
|
||||
$myUtilsServer->setOxCookie("oxidadminprofile", "", time() - 3600);
|
||||
}
|
||||
|
||||
$this->d3totpAfterLoginSetLanguage();
|
||||
$this->d3TotpGetSession()->deleteVariable(d3totp_conf::SESSION_ADMIN_CHLANGUAGE);
|
||||
}
|
||||
|
||||
public function d3totpAfterLoginSetLanguage()
|
||||
public function d3totpAfterLoginSetLanguage(): void
|
||||
{
|
||||
$myUtilsServer = $this->d3TotpGetUtilsServer();
|
||||
$iLang = $this->d3TotpGetSession()->getVariable(d3totp_conf::SESSION_ADMIN_CHLANGUAGE);
|
||||
$iLang = Registry::getRequest()->getRequestEscapedParameter('chlanguage');
|
||||
|
||||
$aLanguages = $this->d3TotpGetLangObject()->getAdminTplLanguageArray();
|
||||
if (!isset($aLanguages[$iLang])) {
|
||||
$iLang = key($aLanguages);
|
||||
}
|
||||
|
||||
$myUtilsServer->setOxCookie("oxidadminlanguage", $aLanguages[$iLang]->abbr, time() + 31536000, "/");
|
||||
$myUtilsServer->setOxCookie("oxidadminlanguage", $aLanguages[$iLang]->abbr, time() + 31536000);
|
||||
$this->d3TotpGetLangObject()->setTplLanguage($iLang);
|
||||
}
|
||||
|
||||
@ -105,7 +107,7 @@ class d3_totp_LoginController extends d3_totp_LoginController_parent
|
||||
* @param d3totp $totp
|
||||
* @return bool
|
||||
*/
|
||||
public function d3TotpLoginMissing($totp)
|
||||
public function d3TotpLoginMissing(d3totp $totp): bool
|
||||
{
|
||||
return $totp->isActive()
|
||||
&& false == $this->d3TotpGetSession()->getVariable(d3totp_conf::SESSION_ADMIN_AUTH);
|
@ -15,26 +15,27 @@ declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Modules\Application\Controller;
|
||||
|
||||
use D3\TestingTools\Production\IsMockable;
|
||||
use D3\Totp\Application\Model\d3totp;
|
||||
use D3\Totp\Application\Model\d3totp_conf;
|
||||
use Doctrine\DBAL\Driver\Exception;
|
||||
use OxidEsales\Eshop\Application\Model\User;
|
||||
use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
use OxidEsales\Eshop\Core\Session;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
trait d3_totp_getUserTrait
|
||||
{
|
||||
use IsMockable;
|
||||
|
||||
/**
|
||||
* @return bool|object|User
|
||||
* @throws DatabaseConnectionException
|
||||
* @return false|User
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws Exception
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws \Doctrine\DBAL\Exception
|
||||
*/
|
||||
public function getUser()
|
||||
public function getUser(): false|User
|
||||
{
|
||||
/** @var User|null $user */
|
||||
$user = $this->d3CallMockableFunction([$this->parentClass, 'getUser']);
|
||||
$user = parent::getUser();
|
||||
|
||||
if ($user && $user->isLoaded() && $user->getId()) {
|
||||
$totp = $this->d3GetTotpObject();
|
||||
@ -55,7 +56,7 @@ trait d3_totp_getUserTrait
|
||||
/**
|
||||
* @return d3totp
|
||||
*/
|
||||
public function d3GetTotpObject()
|
||||
public function d3GetTotpObject(): d3totp
|
||||
{
|
||||
return oxNew(d3totp::class);
|
||||
}
|
||||
@ -63,7 +64,7 @@ trait d3_totp_getUserTrait
|
||||
/**
|
||||
* @return Session
|
||||
*/
|
||||
public function d3TotpGetSessionObject()
|
||||
public function d3TotpGetSessionObject(): Session
|
||||
{
|
||||
return Registry::getSession();
|
||||
}
|
@ -37,7 +37,7 @@ class d3_totp_user extends d3_totp_user_parent
|
||||
/**
|
||||
* @return d3totp
|
||||
*/
|
||||
public function d3getTotp()
|
||||
public function d3getTotp(): d3totp
|
||||
{
|
||||
return oxNew(d3totp::class);
|
||||
}
|
||||
@ -45,11 +45,18 @@ class d3_totp_user extends d3_totp_user_parent
|
||||
/**
|
||||
* @return Session
|
||||
*/
|
||||
public function d3TotpGetSession()
|
||||
public function d3TotpGetSession(): Session
|
||||
{
|
||||
return Registry::getSession();
|
||||
}
|
||||
|
||||
public function d3getSessionedTotp(): d3totp
|
||||
{
|
||||
$totp = $this->d3getTotp();
|
||||
$this->d3TotpGetSession()->setVariable(d3totp_conf::OTP_SESSION_VARNAME, $totp);
|
||||
return $totp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
@ -15,20 +15,29 @@ declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Modules\Core;
|
||||
|
||||
use D3\Totp\Application\Model\Constants;
|
||||
use D3\Totp\Application\Model\d3totp;
|
||||
use D3\Totp\Application\Model\d3totp_conf;
|
||||
use Doctrine\DBAL\DBALException;
|
||||
use Doctrine\DBAL\Driver\Exception;
|
||||
use OxidEsales\Eshop\Core\Config;
|
||||
use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
use OxidEsales\Eshop\Core\Session;
|
||||
use OxidEsales\EshopCommunity\Internal\Container\ContainerFactory;
|
||||
use OxidEsales\EshopCommunity\Internal\Framework\Module\Configuration\Bridge\ModuleConfigurationDaoBridgeInterface;
|
||||
use OxidEsales\EshopCommunity\Internal\Framework\Module\Configuration\DataObject\ModuleConfiguration;
|
||||
use OxidEsales\EshopCommunity\Internal\Framework\Module\Configuration\Exception\ModuleSettingNotFountException;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
class d3_totp_utils extends d3_totp_utils_parent
|
||||
{
|
||||
/**
|
||||
* @return bool
|
||||
* @throws DBALException
|
||||
* @throws DatabaseConnectionException
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws ModuleSettingNotFountException
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws Exception
|
||||
* @throws \Doctrine\DBAL\Exception
|
||||
*/
|
||||
public function checkAccessRights()
|
||||
{
|
||||
@ -36,7 +45,6 @@ class d3_totp_utils extends d3_totp_utils_parent
|
||||
$blAuth = $this->d3AuthHook($blAuth);
|
||||
$userID = $this->d3TotpGetSessionObject()->getVariable(d3totp_conf::OXID_ADMIN_AUTH);
|
||||
$totpAuth = (bool) $this->d3TotpGetSessionObject()->getVariable(d3totp_conf::SESSION_ADMIN_AUTH);
|
||||
/** @var d3totp $totp */
|
||||
$totp = $this->d3GetTotpObject();
|
||||
$totp->loadByUserId($userID);
|
||||
|
||||
@ -60,7 +68,7 @@ class d3_totp_utils extends d3_totp_utils_parent
|
||||
/**
|
||||
* @return Session
|
||||
*/
|
||||
public function d3TotpGetSessionObject()
|
||||
public function d3TotpGetSessionObject(): Session
|
||||
{
|
||||
return Registry::getSession();
|
||||
}
|
||||
@ -68,7 +76,7 @@ class d3_totp_utils extends d3_totp_utils_parent
|
||||
/**
|
||||
* @return d3totp
|
||||
*/
|
||||
public function d3GetTotpObject()
|
||||
public function d3GetTotpObject(): d3totp
|
||||
{
|
||||
return oxNew(d3totp::class);
|
||||
}
|
||||
@ -83,11 +91,17 @@ class d3_totp_utils extends d3_totp_utils_parent
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws ModuleSettingNotFountException
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
protected function d3IsAdminForce2FA()
|
||||
protected function d3IsAdminForce2FA(): bool
|
||||
{
|
||||
return $this->isAdmin() &&
|
||||
$this->d3GetConfig()->getConfigParam('D3_TOTP_ADMIN_FORCE_2FA') === true;
|
||||
if (!$this->isAdmin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $this->getModuleConfiguration()->getModuleSetting('D3_TOTP_ADMIN_FORCE_2FA')->getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -98,4 +112,12 @@ class d3_totp_utils extends d3_totp_utils_parent
|
||||
{
|
||||
return $blAuth;
|
||||
}
|
||||
|
||||
protected function getModuleConfiguration(): ModuleConfiguration
|
||||
{
|
||||
$container = ContainerFactory::getInstance()->getContainer();
|
||||
$moduleConfigurationBridge = $container->get(ModuleConfigurationDaoBridgeInterface::class);
|
||||
/** @var ModuleConfiguration $moduleConfiguration */
|
||||
return $moduleConfigurationBridge->get(Constants::OXID_MODULE_ID);
|
||||
}
|
||||
}
|
@ -15,27 +15,42 @@ declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Modules\Core;
|
||||
|
||||
use D3\TestingTools\Production\IsMockable;
|
||||
use D3\Totp\Application\Model\d3totp;
|
||||
use D3\Totp\Application\Model\d3totp_conf;
|
||||
use D3\Totp\Modules\Application\Model\d3_totp_user;
|
||||
use Doctrine\DBAL\Driver\Exception as DBALDriverException;
|
||||
use Doctrine\DBAL\Exception as DBALException;
|
||||
use OxidEsales\Eshop\Application\Model\User;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
use OxidEsales\Eshop\Core\Session;
|
||||
use OxidEsales\Eshop\Core\Utils;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
class totpSystemEventHandler extends totpSystemEventHandler_parent
|
||||
{
|
||||
use IsMockable;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws DBALDriverException
|
||||
* @throws DBALException
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function onAdminLogin()
|
||||
{
|
||||
$this->d3RequestTotp();
|
||||
$this->d3requestTotp();
|
||||
|
||||
$this->d3CallMockableFunction([totpSystemEventHandler_parent::class, 'onAdminLogin']);
|
||||
parent::onAdminLogin();
|
||||
}
|
||||
|
||||
protected function d3requestTotp()
|
||||
/**
|
||||
* @return void
|
||||
* @throws DBALDriverException
|
||||
* @throws DBALException
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
protected function d3requestTotp(): void
|
||||
{
|
||||
$totp = $this->d3GetTotpObject();
|
||||
$userId = $this->d3TotpGetSession()->getVariable(d3totp_conf::OXID_ADMIN_AUTH);
|
||||
@ -50,8 +65,9 @@ class totpSystemEventHandler extends totpSystemEventHandler_parent
|
||||
|
||||
$this->getUtilsObject()->redirect(
|
||||
'index.php?cl=d3totpadminlogin&'.
|
||||
'profile='.$this->d3TotpGetSession()->getVariable(d3totp_conf::SESSION_ADMIN_PROFILE).'&'.
|
||||
'chlanguage='.$this->d3TotpGetSession()->getVariable(d3totp_conf::SESSION_ADMIN_CHLANGUAGE),
|
||||
'stoken='.Registry::getRequest()->getRequestEscapedParameter('stoken').'&'.
|
||||
'profile='.Registry::getRequest()->getRequestEscapedParameter('profile').'&'.
|
||||
'chlanguage='.Registry::getRequest()->getRequestEscapedParameter('chlanguage'),
|
||||
false
|
||||
);
|
||||
}
|
||||
@ -85,7 +101,7 @@ class totpSystemEventHandler extends totpSystemEventHandler_parent
|
||||
* @param d3totp $totp
|
||||
* @return bool
|
||||
*/
|
||||
public function d3TotpLoginMissing($totp)
|
||||
public function d3TotpLoginMissing(d3totp $totp)
|
||||
{
|
||||
return $totp->isActive()
|
||||
&& false == $this->d3TotpGetSession()->getVariable(d3totp_conf::SESSION_ADMIN_AUTH);
|
15
README.en.md
15
README.en.md
@ -3,7 +3,7 @@
|
||||
|
||||
# 2-factor authentication (one-time password) for OXID eShop
|
||||
|
||||
This module provides a 2-factor authentication (time-dependent one-time password / TOTP) for login in front- and backend in addition to user name and password.
|
||||
This module provides a 2-factor authentication (time-dependent one-time password / TOTP) for login in front- and backend in addition to username and password.
|
||||
|
||||
## Features
|
||||
|
||||
@ -27,13 +27,12 @@ This module provides a 2-factor authentication (time-dependent one-time password
|
||||
|
||||
This package requires an OXID eShop installed with Composer in one of the following versions:
|
||||
|
||||
- 6.3.x
|
||||
- 6.4.x
|
||||
- 6.5.x
|
||||
- 7.0.x
|
||||
- 7.1.x
|
||||
|
||||
and its requirements.
|
||||
|
||||
The Flow and Wave themes are supported by default. Other themes may require customisation.
|
||||
The Apex (Twig based) and Wave (Smarty based) themes are supported by default. Other themes may require customisation.
|
||||
|
||||
## Getting Started
|
||||
|
||||
@ -47,9 +46,13 @@ Activate the module in the admin area of the shop in "Extensions -> Modules".
|
||||
|
||||
The necessary configuration can be found in the same area in the "Settings" tab.
|
||||
|
||||
### Additional installation instructions
|
||||
|
||||
- [Customising the database structure](migration/README.en.md)
|
||||
|
||||
## Changelog
|
||||
|
||||
See [CHANGELOG](CHANGELOG.md) for further informations.
|
||||
See [CHANGELOG](CHANGELOG.md) for further information.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
11
README.md
11
README.md
@ -27,13 +27,12 @@ Dieses Modul stellt eine 2-Faktor-Authentisierung (zeitabhängiges Einmalpasswor
|
||||
|
||||
Dieses Paket erfordert einen mit Composer installierten OXID eShop in einer der folgenden Versionen:
|
||||
|
||||
- 6.3.x
|
||||
- 6.4.x
|
||||
- 6.5.x
|
||||
- 7.0.x
|
||||
- 7.1.x
|
||||
|
||||
und dessen Anforderungen.
|
||||
|
||||
Im Standard wird das Flow- und Wave-Theme unterstützt. Andere Themes können Anpassungen erfordern.
|
||||
Im Standard wird das Apex- (Twig) und Wave- (Smarty) Theme unterstützt. Andere Themes können Anpassungen erfordern.
|
||||
|
||||
## Erste Schritte
|
||||
|
||||
@ -47,6 +46,10 @@ Aktivieren Sie das Modul im Shopadmin unter "Erweiterungen -> Module".
|
||||
|
||||
Die nötige Konfiguration finden Sie im selben Bereich im Tab "Einstell.".
|
||||
|
||||
### ergänzende Installationhinweise
|
||||
|
||||
- [Anpassen der Datenbankstruktur](migration/README.md)
|
||||
|
||||
## Changelog
|
||||
|
||||
Siehe [CHANGELOG](CHANGELOG.md) für weitere Informationen.
|
||||
|
188
Setup/Actions.php
Normal file
188
Setup/Actions.php
Normal file
@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Setup;
|
||||
|
||||
use D3\OxidServiceBridges\Internal\Framework\Templating\Cache\ShopTemplateCacheServiceBridge;
|
||||
use D3\OxidServiceBridges\Internal\Framework\Templating\Cache\ShopTemplateCacheServiceBridgeInterface;
|
||||
use Exception;
|
||||
use OxidEsales\DoctrineMigrationWrapper\MigrationsBuilder;
|
||||
use OxidEsales\Eshop\Application\Controller\FrontendController;
|
||||
use OxidEsales\Eshop\Core\DbMetaDataHandler;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
use OxidEsales\Eshop\Core\SeoEncoder;
|
||||
use OxidEsales\Eshop\Core\Utils;
|
||||
use OxidEsales\EshopCommunity\Internal\Container\ContainerFactory;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class Actions
|
||||
{
|
||||
public array $seo_de = [
|
||||
'2-faktor-authentisierung/',
|
||||
];
|
||||
public array $seo_en = [
|
||||
'en/2-factor-authentication/',
|
||||
];
|
||||
public array $stdClassName = [
|
||||
'd3_account_totp',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return MigrationsBuilder
|
||||
*/
|
||||
protected function getMigrationsBuilder(): MigrationsBuilder
|
||||
{
|
||||
return oxNew(MigrationsBuilder::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function runModuleMigrations(): void
|
||||
{
|
||||
$migrationsBuilder = $this->getMigrationsBuilder();
|
||||
$migrations = $migrationsBuilder->build();
|
||||
$migrations->execute('migrations:migrate', 'd3totp');
|
||||
}
|
||||
|
||||
protected function getDbMetaDataHandler(): DbMetaDataHandler
|
||||
{
|
||||
return oxNew(DbMetaDataHandler::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate views for changed tables
|
||||
* @throws Exception
|
||||
*/
|
||||
public function regenerateViews(): void
|
||||
{
|
||||
$oDbMetaDataHandler = $this->getDbMetaDataHandler();
|
||||
$oDbMetaDataHandler->updateViews();
|
||||
}
|
||||
|
||||
protected function getUtils(): Utils
|
||||
{
|
||||
return oxNew(Utils::class);
|
||||
}
|
||||
|
||||
protected function getLogger(): LoggerInterface
|
||||
{
|
||||
return Registry::getLogger();
|
||||
}
|
||||
|
||||
/**
|
||||
* clear cache
|
||||
* @throws Exception
|
||||
*/
|
||||
public function clearCache(): void
|
||||
{
|
||||
try {
|
||||
/** @var ShopTemplateCacheServiceBridge $templateCacheService */
|
||||
$templateCacheService = $this->getDIContainer()->get(ShopTemplateCacheServiceBridgeInterface::class);
|
||||
$templateCacheService->invalidateCache(Registry::getConfig()->getShopId());
|
||||
$oUtils = $this->getUtils();
|
||||
$oUtils->resetLanguageCache();
|
||||
} catch (ContainerExceptionInterface $e) {
|
||||
Registry::getLogger()->error($e->getMessage(), [$this]);
|
||||
Registry::getUtilsView()->addErrorToDisplay($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function seoUrl(): void
|
||||
{
|
||||
try {
|
||||
$seoEncoder = oxNew(SeoEncoder::class);
|
||||
if (!$this->hasSeoUrls($seoEncoder)) {
|
||||
$this->createSeoUrls($seoEncoder);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->getLogger()->error($e->getMessage(), [$this]);
|
||||
Registry::getUtilsView()->addErrorToDisplay('error wile creating SEO URLs: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SeoEncoder $seoEncoder
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSeoUrls(SeoEncoder $seoEncoder): bool
|
||||
{
|
||||
foreach ($this->stdClassName as $item) {
|
||||
foreach ([0, 1] as $lang) {
|
||||
if (false === $this->hasSeoUrl($seoEncoder, $item, $lang)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function hasSeoUrl(SeoEncoder $seoEncoder, string $item, int $langId): bool
|
||||
{
|
||||
$seoUrl = $seoEncoder->getStaticUrl(
|
||||
oxNew(FrontendController::class)->getViewConfig()->getSelfLink() .
|
||||
"cl=" . $item,
|
||||
$langId
|
||||
);
|
||||
return (bool)strlen($seoUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SeoEncoder $seoEncoder
|
||||
* @return void
|
||||
*/
|
||||
public function createSeoUrls(SeoEncoder $seoEncoder): void
|
||||
{
|
||||
foreach (array_keys($this->stdClassName) as $id) {
|
||||
$objectid = md5(strtolower(Registry::getConfig()->getShopId() . 'index.php?cl=' . $this->stdClassName[$id]));
|
||||
if (!$this->hasSeoUrl($seoEncoder, $this->stdClassName[$id], 0)) {
|
||||
$seoEncoder->addSeoEntry(
|
||||
$objectid,
|
||||
Registry::getConfig()->getShopId(),
|
||||
0,
|
||||
'index.php?cl=' . $this->stdClassName[$id],
|
||||
$this->seo_de[$id],
|
||||
'static',
|
||||
false
|
||||
);
|
||||
}
|
||||
if (!$this->hasSeoUrl($seoEncoder, $this->stdClassName[$id], 1)) {
|
||||
$seoEncoder->addSeoEntry(
|
||||
$objectid,
|
||||
Registry::getConfig()->getShopId(),
|
||||
1,
|
||||
'index.php?cl=' . $this->stdClassName[$id],
|
||||
$this->seo_en[$id],
|
||||
'static',
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ContainerInterface|null
|
||||
*/
|
||||
protected function getDIContainer(): ?ContainerInterface
|
||||
{
|
||||
return ContainerFactory::getInstance()->getContainer();
|
||||
}
|
||||
}
|
43
Setup/Events.php
Normal file
43
Setup/Events.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Setup;
|
||||
|
||||
use Exception;
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
class Events
|
||||
{
|
||||
/**
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function onActivate(): void
|
||||
{
|
||||
$actions = oxNew(Actions::class);
|
||||
$actions->runModuleMigrations();
|
||||
$actions->regenerateViews();
|
||||
$actions->clearCache();
|
||||
$actions->seoUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function onDeactivate(): void
|
||||
{
|
||||
}
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
@ -1,11 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace D3\Totp\tests\unit\Application\Controller\Admin;
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
namespace D3\Totp\Tests\Unit\Application\Controller\Admin;
|
||||
|
||||
use D3\Totp\Application\Controller\Admin\d3force_2fa;
|
||||
use D3\Totp\Application\Model\d3totp_conf;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
use OxidEsales\Eshop\Core\Session;
|
||||
use OxidEsales\EshopCommunity\Internal\Framework\Module\Configuration\DataObject\ModuleConfiguration;
|
||||
use OxidEsales\EshopCommunity\Internal\Framework\Module\Setting\Setting;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use ReflectionException;
|
||||
|
||||
@ -51,19 +64,28 @@ class d3force_2faTest extends d3user_totpTest
|
||||
* @test
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Application\Controller\Admin\d3force_2fa::_authorize
|
||||
* @covers \D3\Totp\Application\Controller\Admin\d3force_2fa::authorize
|
||||
* @covers \D3\Totp\Application\Controller\Admin\d3force_2fa::d3IsAdminForce2FA
|
||||
* @dataProvider authorizeDataProvider
|
||||
*/
|
||||
public function testAuthorize($expected, $isAdmin, $force2FA, $givenUserId)
|
||||
{
|
||||
/** @var d3force_2fa|MockObject $oController */
|
||||
$oController = $this->getMockBuilder(d3force_2fa::class)
|
||||
->onlyMethods(['isAdmin'])
|
||||
$settingMock = $this->d3getMockBuilder(Setting::class)
|
||||
->onlyMethods(['getValue'])
|
||||
->getMock();
|
||||
$oController->expects($this->once())->method('isAdmin')->willReturn($isAdmin);
|
||||
$settingMock->method('getValue')->willReturn($force2FA);
|
||||
|
||||
Registry::getConfig()->setConfigParam('D3_TOTP_ADMIN_FORCE_2FA', $force2FA);
|
||||
$moduleConfigurationMock = $this->d3getMockBuilder(ModuleConfiguration::class)
|
||||
->onlyMethods(['getModuleSetting'])
|
||||
->getMock();
|
||||
$moduleConfigurationMock->method('getModuleSetting')->willReturn($settingMock);
|
||||
|
||||
/** @var d3force_2fa|MockObject $oController */
|
||||
$oController = $this->d3getMockBuilder(d3force_2fa::class)
|
||||
->onlyMethods(['isAdmin', 'getModuleConfiguration'])
|
||||
->getMock();
|
||||
$oController->expects($this->any())->method('isAdmin')->willReturn($isAdmin);
|
||||
$oController->method('getModuleConfiguration')->willReturn($moduleConfigurationMock);
|
||||
|
||||
Registry::getSession()->setVariable(d3totp_conf::OXID_ADMIN_AUTH, $givenUserId);
|
||||
|
||||
@ -71,7 +93,7 @@ class d3force_2faTest extends d3user_totpTest
|
||||
$expected,
|
||||
$this->callMethod(
|
||||
$oController,
|
||||
'_authorize'
|
||||
'authorize'
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -79,7 +101,7 @@ class d3force_2faTest extends d3user_totpTest
|
||||
/**
|
||||
* @return array[]
|
||||
*/
|
||||
public function authorizeDataProvider(): array
|
||||
public static function authorizeDataProvider(): array
|
||||
{
|
||||
return [
|
||||
'noAdmin' => [false, false, true, 'userId'],
|
||||
@ -105,4 +127,21 @@ class d3force_2faTest extends d3user_totpTest
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Application\Controller\Admin\d3force_2fa::getModuleConfiguration
|
||||
*/
|
||||
public function canGetModuleConfiguration()
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
ModuleConfiguration::class,
|
||||
$this->callMethod(
|
||||
$this->_oController,
|
||||
'getModuleConfiguration'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\tests\unit\Application\Controller\Admin;
|
||||
namespace D3\Totp\Tests\Unit\Application\Controller\Admin;
|
||||
|
||||
use D3\TestingTools\Development\CanAccessRestricted;
|
||||
use D3\Totp\Application\Controller\Admin\d3totpadminlogin;
|
||||
@ -23,7 +23,7 @@ use D3\Totp\Application\Model\d3totp_conf;
|
||||
use D3\Totp\Application\Model\Exceptions\d3totp_wrongOtpException;
|
||||
use D3\Totp\Modules\Application\Controller\Admin\d3_totp_LoginController;
|
||||
use D3\Totp\Modules\Application\Model\d3_totp_user;
|
||||
use D3\Totp\tests\unit\d3TotpUnitTestCase;
|
||||
use D3\Totp\Tests\Unit\d3TotpUnitTestCase;
|
||||
use OxidEsales\Eshop\Application\Controller\Admin\LoginController;
|
||||
use OxidEsales\Eshop\Application\Model\User;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
@ -62,14 +62,14 @@ class d3totpadminloginTest extends d3TotpUnitTestCase
|
||||
* @test
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Application\Controller\Admin\d3totpadminlogin::_authorize
|
||||
* @covers \D3\Totp\Application\Controller\Admin\d3totpadminlogin::authorize
|
||||
*/
|
||||
public function testAuthorize()
|
||||
{
|
||||
$this->assertTrue(
|
||||
$this->callMethod(
|
||||
$this->_oController,
|
||||
'_authorize'
|
||||
'authorize'
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -103,8 +103,13 @@ class d3totpadminloginTest extends d3TotpUnitTestCase
|
||||
*/
|
||||
public function isTotpIsNotRequiredPassed($hasAuthAlready, $totpActive, $expected)
|
||||
{
|
||||
$oUserMock = $this->d3getMockBuilder(User::class)
|
||||
->onlyMethods(['d3TotpGetCurrentUser'])
|
||||
->getMock();
|
||||
$oUserMock->method('d3TotpGetCurrentUser')->willReturn('foo');
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'isActive',
|
||||
'loadByUserId',
|
||||
@ -112,10 +117,10 @@ class d3totpadminloginTest extends d3TotpUnitTestCase
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$oTotpMock->method('isActive')->willReturn($totpActive);
|
||||
$oTotpMock->method('loadByUserId')->willReturn(true);
|
||||
$oTotpMock->method('loadByUserId');
|
||||
|
||||
/** @var Session|MockObject $oSessionMock */
|
||||
$oSessionMock = $this->getMockBuilder(Session::class)
|
||||
$oSessionMock = $this->d3getMockBuilder(Session::class)
|
||||
->onlyMethods([
|
||||
'hasVariable',
|
||||
])
|
||||
@ -126,14 +131,16 @@ class d3totpadminloginTest extends d3TotpUnitTestCase
|
||||
$oSessionMock->method('hasVariable')->willReturnMap($hasVariableMap);
|
||||
|
||||
/** @var d3totpadminlogin|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3totpadminlogin::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3totpadminlogin::class)
|
||||
->onlyMethods([
|
||||
'd3TotpGetSession',
|
||||
'd3TotpGetTotpObject',
|
||||
'd3TotpGetUserObject'
|
||||
])
|
||||
->getMock();
|
||||
$oControllerMock->method('d3TotpGetSession')->willReturn($oSessionMock);
|
||||
$oControllerMock->method('d3TotpGetTotpObject')->willReturn($oTotpMock);
|
||||
$oControllerMock->method('d3TotpGetUserObject')->willReturn($oUserMock);
|
||||
|
||||
$this->_oController = $oControllerMock;
|
||||
|
||||
@ -149,7 +156,7 @@ class d3totpadminloginTest extends d3TotpUnitTestCase
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function isTotpIsNotRequiredPassedDataProvider(): array
|
||||
public static function isTotpIsNotRequiredPassedDataProvider(): array
|
||||
{
|
||||
return [
|
||||
'auth already finished' => [true, true, true],
|
||||
@ -170,13 +177,13 @@ class d3totpadminloginTest extends d3TotpUnitTestCase
|
||||
public function isTotpLoginNotPossiblePassed($userId, $expected)
|
||||
{
|
||||
/** @var d3_totp_user|MockObject $oUserMock */
|
||||
$oUserMock = $this->getMockBuilder(User::class)
|
||||
$oUserMock = $this->d3getMockBuilder(User::class)
|
||||
->onlyMethods(['d3TotpGetCurrentUser'])
|
||||
->getMock();
|
||||
$oUserMock->method('d3TotpGetCurrentUser')->willReturn($userId);
|
||||
|
||||
/** @var d3totpadminlogin|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3totpadminlogin::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3totpadminlogin::class)
|
||||
->onlyMethods(['d3TotpGetUserObject'])
|
||||
->getMock();
|
||||
$oControllerMock->method('d3TotpGetUserObject')->willReturn($oUserMock);
|
||||
@ -195,7 +202,7 @@ class d3totpadminloginTest extends d3TotpUnitTestCase
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function isTotpLoginNotPossiblePassedDataProvider(): array
|
||||
public static function isTotpLoginNotPossiblePassedDataProvider(): array
|
||||
{
|
||||
return [
|
||||
'no user' => [null, true],
|
||||
@ -216,7 +223,7 @@ class d3totpadminloginTest extends d3TotpUnitTestCase
|
||||
public function canRender($totpNotRequired, $totpNotPossible, $redirect)
|
||||
{
|
||||
/** @var Utils|MockObject $oUtilsMock */
|
||||
$oUtilsMock = $this->getMockBuilder(Utils::class)
|
||||
$oUtilsMock = $this->d3getMockBuilder(Utils::class)
|
||||
->onlyMethods(['redirect'])
|
||||
->getMock();
|
||||
$oUtilsMock
|
||||
@ -226,14 +233,13 @@ class d3totpadminloginTest extends d3TotpUnitTestCase
|
||||
->willReturn(true);
|
||||
|
||||
/** @var d3_totp_LoginController|MockObject $loginControllerMock */
|
||||
$loginControllerMock = $this->getMockBuilder(LoginController::class)
|
||||
$loginControllerMock = $this->d3getMockBuilder(LoginController::class)
|
||||
->onlyMethods(['d3totpAfterLoginSetLanguage'])
|
||||
->getMock();
|
||||
$loginControllerMock->expects($this->once())->method('d3totpAfterLoginSetLanguage')
|
||||
->willReturn(true);
|
||||
$loginControllerMock->expects($this->once())->method('d3totpAfterLoginSetLanguage');
|
||||
|
||||
/** @var d3totpadminlogin|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3totpadminlogin::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3totpadminlogin::class)
|
||||
->onlyMethods([
|
||||
'isTotpIsNotRequired',
|
||||
'isTotpLoginNotPossible',
|
||||
@ -257,7 +263,7 @@ class d3totpadminloginTest extends d3TotpUnitTestCase
|
||||
/**
|
||||
* @return array[]
|
||||
*/
|
||||
public function canRenderDataProvider(): array
|
||||
public static function canRenderDataProvider(): array
|
||||
{
|
||||
return [
|
||||
'not required' => [true, false, 'admin_start'],
|
||||
@ -286,17 +292,23 @@ class d3totpadminloginTest extends d3TotpUnitTestCase
|
||||
*/
|
||||
public function getBackupCodeCountMessageShowMessage()
|
||||
{
|
||||
$userMock = $this->d3getMockBuilder(User::class)
|
||||
->onlyMethods(['d3TotpGetCurrentUser'])
|
||||
->getMock();
|
||||
$userMock->method('d3TotpGetCurrentUser')->willReturn('foo');
|
||||
|
||||
/** @var d3backupcodelist|MockObject $oBackupCodeListMock */
|
||||
$oBackupCodeListMock = $this->getMockBuilder(d3backupcodelist::class)
|
||||
$oBackupCodeListMock = $this->d3getMockBuilder(d3backupcodelist::class)
|
||||
->onlyMethods(['getAvailableCodeCount'])
|
||||
->getMock();
|
||||
$oBackupCodeListMock->method('getAvailableCodeCount')->willReturn(2);
|
||||
|
||||
/** @var d3totpadminlogin|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3totpadminlogin::class)
|
||||
->onlyMethods(['d3GetBackupCodeListObject'])
|
||||
$oControllerMock = $this->d3getMockBuilder(d3totpadminlogin::class)
|
||||
->onlyMethods(['d3GetBackupCodeListObject', 'd3TotpGetUserObject'])
|
||||
->getMock();
|
||||
$oControllerMock->method('d3GetBackupCodeListObject')->willReturn($oBackupCodeListMock);
|
||||
$oControllerMock->method('d3TotpGetUserObject')->willReturn($userMock);
|
||||
|
||||
$this->_oController = $oControllerMock;
|
||||
|
||||
@ -316,17 +328,23 @@ class d3totpadminloginTest extends d3TotpUnitTestCase
|
||||
*/
|
||||
public function getBackupCodeCountMessageDontShowMessage()
|
||||
{
|
||||
$userMock = $this->d3getMockBuilder(User::class)
|
||||
->onlyMethods(['d3TotpGetCurrentUser'])
|
||||
->getMock();
|
||||
$userMock->method('d3TotpGetCurrentUser')->willReturn('foo');
|
||||
|
||||
/** @var d3backupcodelist|MockObject $oBackupCodeListMock */
|
||||
$oBackupCodeListMock = $this->getMockBuilder(d3backupcodelist::class)
|
||||
$oBackupCodeListMock = $this->d3getMockBuilder(d3backupcodelist::class)
|
||||
->onlyMethods(['getAvailableCodeCount'])
|
||||
->getMock();
|
||||
$oBackupCodeListMock->method('getAvailableCodeCount')->willReturn(10);
|
||||
|
||||
/** @var d3totpadminlogin|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3totpadminlogin::class)
|
||||
->onlyMethods(['d3GetBackupCodeListObject'])
|
||||
$oControllerMock = $this->d3getMockBuilder(d3totpadminlogin::class)
|
||||
->onlyMethods(['d3GetBackupCodeListObject', 'd3TotpGetUserObject'])
|
||||
->getMock();
|
||||
$oControllerMock->method('d3GetBackupCodeListObject')->willReturn($oBackupCodeListMock);
|
||||
$oControllerMock->method('d3TotpGetUserObject')->willReturn($userMock);
|
||||
|
||||
$this->_oController = $oControllerMock;
|
||||
|
||||
@ -344,13 +362,13 @@ class d3totpadminloginTest extends d3TotpUnitTestCase
|
||||
public function canCancelLogin()
|
||||
{
|
||||
/** @var d3_totp_user|MockObject $userMock */
|
||||
$userMock = $this->getMockBuilder(User::class)
|
||||
$userMock = $this->d3getMockBuilder(User::class)
|
||||
->onlyMethods(['logout'])
|
||||
->getMock();
|
||||
$userMock->expects($this->once())->method('logout')->willReturn(true);
|
||||
|
||||
/** @var d3totpadminlogin|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3totpadminlogin::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3totpadminlogin::class)
|
||||
->onlyMethods(['d3TotpGetUserObject'])
|
||||
->getMock();
|
||||
$oControllerMock->method('d3TotpGetUserObject')->willReturn($userMock);
|
||||
@ -386,8 +404,13 @@ class d3totpadminloginTest extends d3TotpUnitTestCase
|
||||
*/
|
||||
public function checkloginUnvalidTotp()
|
||||
{
|
||||
$userMock = $this->d3getMockBuilder(User::class)
|
||||
->onlyMethods(['d3TotpGetCurrentUser'])
|
||||
->getMock();
|
||||
$userMock->method('d3TotpGetCurrentUser')->willReturn('foo');
|
||||
|
||||
/** @var LoggerWrapper|MockObject $loggerMock */
|
||||
$loggerMock = $this->getMockBuilder(LoggerWrapper::class)
|
||||
$loggerMock = $this->d3getMockBuilder(LoggerWrapper::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['error', 'debug'])
|
||||
->getMock();
|
||||
@ -395,14 +418,14 @@ class d3totpadminloginTest extends d3TotpUnitTestCase
|
||||
$loggerMock->expects($this->atLeastOnce())->method('debug')->willReturn(true);
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['loadByUserId'])
|
||||
->getMock();
|
||||
$oTotpMock->method('loadByUserId')->willReturn(true);
|
||||
$oTotpMock->method('loadByUserId');
|
||||
|
||||
/** @var Session|MockObject $oSessionMock */
|
||||
$oSessionMock = $this->getMockBuilder(Session::class)
|
||||
$oSessionMock = $this->d3getMockBuilder(Session::class)
|
||||
->onlyMethods([
|
||||
'initNewSession',
|
||||
'setVariable',
|
||||
@ -414,18 +437,19 @@ class d3totpadminloginTest extends d3TotpUnitTestCase
|
||||
$oSessionMock->expects($this->never())->method('deleteVariable')->willReturn(false);
|
||||
|
||||
/** @var d3_totp_LoginController|MockObject $loginControllerMock */
|
||||
$loginControllerMock = $this->getMockBuilder(LoginController::class)
|
||||
$loginControllerMock = $this->d3getMockBuilder(LoginController::class)
|
||||
->onlyMethods(['d3totpAfterLogin'])
|
||||
->getMock();
|
||||
$loginControllerMock->expects($this->never())->method('d3totpAfterLogin')->willReturn(true);
|
||||
$loginControllerMock->expects($this->never())->method('d3totpAfterLogin');
|
||||
|
||||
/** @var d3totpadminlogin|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3totpadminlogin::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3totpadminlogin::class)
|
||||
->onlyMethods([
|
||||
'getLogger',
|
||||
'd3TotpHasValidTotp',
|
||||
'd3TotpGetSession',
|
||||
'd3GetLoginController',
|
||||
'd3TotpGetUserObject',
|
||||
])
|
||||
->getMock();
|
||||
$oControllerMock->method('d3TotpHasValidTotp')
|
||||
@ -433,6 +457,7 @@ class d3totpadminloginTest extends d3TotpUnitTestCase
|
||||
$oControllerMock->method('d3TotpGetSession')->willReturn($oSessionMock);
|
||||
$oControllerMock->method('getLogger')->willReturn($loggerMock);
|
||||
$oControllerMock->method('d3GetLoginController')->willReturn($loginControllerMock);
|
||||
$oControllerMock->method('d3TotpGetUserObject')->willReturn($userMock);
|
||||
|
||||
$this->_oController = $oControllerMock;
|
||||
|
||||
@ -449,8 +474,13 @@ class d3totpadminloginTest extends d3TotpUnitTestCase
|
||||
*/
|
||||
public function checkloginValidTotp()
|
||||
{
|
||||
$userMock = $this->d3getMockBuilder(User::class)
|
||||
->onlyMethods(['d3TotpGetCurrentUser'])
|
||||
->getMock();
|
||||
$userMock->method('d3TotpGetCurrentUser')->willReturn('foo');
|
||||
|
||||
/** @var LoggerWrapper|MockObject $loggerMock */
|
||||
$loggerMock = $this->getMockBuilder(LoggerWrapper::class)
|
||||
$loggerMock = $this->d3getMockBuilder(LoggerWrapper::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['error', 'debug'])
|
||||
->getMock();
|
||||
@ -458,14 +488,14 @@ class d3totpadminloginTest extends d3TotpUnitTestCase
|
||||
$loggerMock->expects($this->never())->method('debug')->willReturn(true);
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['loadByUserId'])
|
||||
->getMock();
|
||||
$oTotpMock->method('loadByUserId')->willReturn(true);
|
||||
$oTotpMock->method('loadByUserId');
|
||||
|
||||
/** @var Session|MockObject $oSessionMock */
|
||||
$oSessionMock = $this->getMockBuilder(Session::class)
|
||||
$oSessionMock = $this->d3getMockBuilder(Session::class)
|
||||
->onlyMethods([
|
||||
'initNewSession',
|
||||
'setVariable',
|
||||
@ -477,24 +507,26 @@ class d3totpadminloginTest extends d3TotpUnitTestCase
|
||||
$oSessionMock->expects($this->atLeastOnce())->method('deleteVariable')->willReturn(false);
|
||||
|
||||
/** @var d3_totp_LoginController|MockObject $loginControllerMock */
|
||||
$loginControllerMock = $this->getMockBuilder(LoginController::class)
|
||||
$loginControllerMock = $this->d3getMockBuilder(LoginController::class)
|
||||
->onlyMethods(['d3totpAfterLogin'])
|
||||
->getMock();
|
||||
$loginControllerMock->expects($this->once())->method('d3totpAfterLogin')->willReturn(true);
|
||||
$loginControllerMock->expects($this->once())->method('d3totpAfterLogin');
|
||||
|
||||
/** @var d3totpadminlogin|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3totpadminlogin::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3totpadminlogin::class)
|
||||
->onlyMethods([
|
||||
'getLogger',
|
||||
'd3TotpHasValidTotp',
|
||||
'd3TotpGetSession',
|
||||
'd3GetLoginController',
|
||||
'd3TotpGetUserObject'
|
||||
])
|
||||
->getMock();
|
||||
$oControllerMock->method('d3TotpHasValidTotp')->willReturn(true);
|
||||
$oControllerMock->method('d3TotpGetSession')->willReturn($oSessionMock);
|
||||
$oControllerMock->method('getLogger')->willReturn($loggerMock);
|
||||
$oControllerMock->method('d3GetLoginController')->willReturn($loginControllerMock);
|
||||
$oControllerMock->method('d3TotpGetUserObject')->willReturn($userMock);
|
||||
|
||||
$this->_oController = $oControllerMock;
|
||||
|
||||
@ -514,7 +546,7 @@ class d3totpadminloginTest extends d3TotpUnitTestCase
|
||||
Registry::getSession()->setVariable(d3totp_conf::SESSION_ADMIN_AUTH, true);
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['verify'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
@ -535,7 +567,7 @@ class d3totpadminloginTest extends d3TotpUnitTestCase
|
||||
Registry::getSession()->setVariable(d3totp_conf::SESSION_ADMIN_AUTH, false);
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['verify'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
@ -556,14 +588,14 @@ class d3totpadminloginTest extends d3TotpUnitTestCase
|
||||
Registry::getSession()->setVariable(d3totp_conf::SESSION_ADMIN_AUTH, false);
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['verify'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$oTotpMock->method('verify')->willThrowException(oxNew(d3totp_wrongOtpException::class));
|
||||
|
||||
$this->expectException(d3totp_wrongOtpException::class);
|
||||
$this->callMethod($this->_oController, 'd3TotpHasValidTotp', [null, $oTotpMock]);
|
||||
$this->callMethod($this->_oController, 'd3TotpHasValidTotp', ['123456', $oTotpMock]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -576,7 +608,7 @@ class d3totpadminloginTest extends d3TotpUnitTestCase
|
||||
Registry::getSession()->setVariable(d3totp_conf::SESSION_ADMIN_AUTH, false);
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['verify'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
@ -13,15 +13,17 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\tests\unit\Application\Controller\Admin;
|
||||
namespace D3\Totp\Tests\Unit\Application\Controller\Admin;
|
||||
|
||||
use D3\TestingTools\Development\CanAccessRestricted;
|
||||
use D3\Totp\Application\Controller\Admin\d3user_totp;
|
||||
use D3\Totp\Application\Model\d3backupcodelist;
|
||||
use D3\Totp\Application\Model\d3totp;
|
||||
use D3\Totp\tests\unit\d3TotpUnitTestCase;
|
||||
use D3\Totp\Application\Model\d3totp_conf;
|
||||
use D3\Totp\Tests\Unit\d3TotpUnitTestCase;
|
||||
use Exception;
|
||||
use OxidEsales\Eshop\Application\Model\User;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use ReflectionException;
|
||||
|
||||
@ -57,22 +59,24 @@ class d3user_totpTest extends d3TotpUnitTestCase
|
||||
*/
|
||||
public function canRenderNoSelectedUser()
|
||||
{
|
||||
$userMock = oxNew(User::class);
|
||||
|
||||
/** @var d3user_totp|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3user_totp::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3user_totp::class)
|
||||
->onlyMethods([
|
||||
'getEditObjectId',
|
||||
'getUserObject',
|
||||
])
|
||||
->getMock();
|
||||
$oControllerMock->method('getEditObjectId')->willReturn('-1');
|
||||
$oControllerMock->expects($this->never())->method('getUserObject')->willReturn(false);
|
||||
$oControllerMock->expects($this->never())->method('getUserObject')->willReturn($userMock);
|
||||
|
||||
$this->_oController = $oControllerMock;
|
||||
|
||||
$sTpl = $this->callMethod($this->_oController, 'render');
|
||||
$tplUser = $this->callMethod($this->_oController, 'getViewDataElement', ['edit']);
|
||||
|
||||
$this->assertSame('d3user_totp.tpl', $sTpl);
|
||||
$this->assertSame('@d3totp/admin/d3user_totp', $sTpl);
|
||||
$this->assertSame($tplUser, null);
|
||||
}
|
||||
|
||||
@ -85,7 +89,7 @@ class d3user_totpTest extends d3TotpUnitTestCase
|
||||
public function canRenderSelectedUser()
|
||||
{
|
||||
/** @var User|MockObject $oUserMock */
|
||||
$oUserMock = $this->getMockBuilder(User::class)
|
||||
$oUserMock = $this->d3getMockBuilder(User::class)
|
||||
->onlyMethods([
|
||||
'getId',
|
||||
'load',
|
||||
@ -95,7 +99,7 @@ class d3user_totpTest extends d3TotpUnitTestCase
|
||||
$oUserMock->expects($this->atLeast(1))->method('load')->willReturn(true);
|
||||
|
||||
/** @var d3user_totp|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3user_totp::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3user_totp::class)
|
||||
->onlyMethods([
|
||||
'getEditObjectId',
|
||||
'getUserObject',
|
||||
@ -110,7 +114,7 @@ class d3user_totpTest extends d3TotpUnitTestCase
|
||||
$tplUser = $this->callMethod($this->_oController, 'getViewDataElement', ['edit']);
|
||||
$oxid = $this->callMethod($this->_oController, 'getViewDataElement', ['oxid']);
|
||||
|
||||
$this->assertSame('d3user_totp.tpl', $sTpl);
|
||||
$this->assertSame('@d3totp/admin/d3user_totp', $sTpl);
|
||||
$this->assertSame($tplUser, $oUserMock);
|
||||
$this->assertSame($oxid, 'foobar');
|
||||
}
|
||||
@ -124,7 +128,7 @@ class d3user_totpTest extends d3TotpUnitTestCase
|
||||
public function canRenderUnloadableUser()
|
||||
{
|
||||
/** @var User|MockObject $oUserMock */
|
||||
$oUserMock = $this->getMockBuilder(User::class)
|
||||
$oUserMock = $this->d3getMockBuilder(User::class)
|
||||
->onlyMethods([
|
||||
'getId',
|
||||
'load',
|
||||
@ -134,7 +138,7 @@ class d3user_totpTest extends d3TotpUnitTestCase
|
||||
$oUserMock->expects($this->atLeast(1))->method('load')->willReturn(false);
|
||||
|
||||
/** @var d3user_totp|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3user_totp::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3user_totp::class)
|
||||
->onlyMethods([
|
||||
'getEditObjectId',
|
||||
'getUserObject',
|
||||
@ -159,7 +163,7 @@ class d3user_totpTest extends d3TotpUnitTestCase
|
||||
$tplUser = $this->callMethod($this->_oController, 'getViewDataElement', ['edit']);
|
||||
$oxid = $this->callMethod($this->_oController, 'getViewDataElement', ['oxid']);
|
||||
|
||||
$this->assertSame('d3user_totp.tpl', $sTpl);
|
||||
$this->assertSame('@d3totp/admin/d3user_totp', $sTpl);
|
||||
$this->assertNull($tplUser);
|
||||
$this->assertSame($oxid, 'foobar');
|
||||
}
|
||||
@ -220,13 +224,13 @@ class d3user_totpTest extends d3TotpUnitTestCase
|
||||
public function cantSaveBecauseOfNotVerifiable()
|
||||
{
|
||||
/** @var d3backupcodelist|MockObject $oBackupCodeListMock */
|
||||
$oBackupCodeListMock = $this->getMockBuilder(d3backupcodelist::class)
|
||||
$oBackupCodeListMock = $this->d3getMockBuilder(d3backupcodelist::class)
|
||||
->onlyMethods(['save'])
|
||||
->getMock();
|
||||
$oBackupCodeListMock->expects($this->never())->method('save')->willReturn(true);
|
||||
$oBackupCodeListMock->expects($this->never())->method('save');
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'load',
|
||||
'save',
|
||||
@ -240,12 +244,12 @@ class d3user_totpTest extends d3TotpUnitTestCase
|
||||
$oTotpMock->method('load')->willReturn(true);
|
||||
$oTotpMock->expects($this->never())->method('save')->willReturn(true);
|
||||
$oTotpMock->expects($this->once())->method('verify')->willThrowException(new Exception());
|
||||
$oTotpMock->method('saveSecret')->willReturn(true);
|
||||
$oTotpMock->method('saveSecret');
|
||||
$oTotpMock->method('assign')->willReturn(true);
|
||||
$oTotpMock->method('checkIfAlreadyExist')->willReturn(false);
|
||||
|
||||
/** @var d3user_totp|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3user_totp::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3user_totp::class)
|
||||
->onlyMethods([
|
||||
'getEditObjectId',
|
||||
'getUserObject',
|
||||
@ -259,6 +263,9 @@ class d3user_totpTest extends d3TotpUnitTestCase
|
||||
|
||||
$this->_oController = $oControllerMock;
|
||||
|
||||
$_GET['otp'] = '123456';
|
||||
Registry::getSession()->setVariable(d3totp_conf::OTP_SESSION_VARNAME, $oTotpMock);
|
||||
|
||||
$this->callMethod($this->_oController, 'save');
|
||||
}
|
||||
|
||||
@ -270,13 +277,13 @@ class d3user_totpTest extends d3TotpUnitTestCase
|
||||
public function cantSaveBecauseExistingRegistration()
|
||||
{
|
||||
/** @var d3backupcodelist|MockObject $oBackupCodeListMock */
|
||||
$oBackupCodeListMock = $this->getMockBuilder(d3backupcodelist::class)
|
||||
$oBackupCodeListMock = $this->d3getMockBuilder(d3backupcodelist::class)
|
||||
->onlyMethods(['save'])
|
||||
->getMock();
|
||||
$oBackupCodeListMock->expects($this->never())->method('save')->willReturn(true);
|
||||
$oBackupCodeListMock->expects($this->never())->method('save');
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods([
|
||||
'load',
|
||||
@ -290,12 +297,12 @@ class d3user_totpTest extends d3TotpUnitTestCase
|
||||
$oTotpMock->method('load')->willReturn(true);
|
||||
$oTotpMock->expects($this->never())->method('save')->willReturn(true);
|
||||
$oTotpMock->expects($this->never())->method('verify')->willThrowException(new Exception());
|
||||
$oTotpMock->method('saveSecret')->willReturn(true);
|
||||
$oTotpMock->method('saveSecret');
|
||||
$oTotpMock->method('assign')->willReturn(true);
|
||||
$oTotpMock->method('checkIfAlreadyExist')->willReturn(true);
|
||||
|
||||
/** @var d3user_totp|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3user_totp::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3user_totp::class)
|
||||
->onlyMethods([
|
||||
'getEditObjectId',
|
||||
'getUserObject',
|
||||
@ -320,17 +327,17 @@ class d3user_totpTest extends d3TotpUnitTestCase
|
||||
public function canSave()
|
||||
{
|
||||
/** @var d3backupcodelist|MockObject $oBackupCodeListMock */
|
||||
$oBackupCodeListMock = $this->getMockBuilder(d3backupcodelist::class)
|
||||
$oBackupCodeListMock = $this->d3getMockBuilder(d3backupcodelist::class)
|
||||
->onlyMethods([
|
||||
'save',
|
||||
'generateBackupCodes',
|
||||
])
|
||||
->getMock();
|
||||
$oBackupCodeListMock->expects($this->once())->method('save')->willReturn(true);
|
||||
$oBackupCodeListMock->method('generateBackupCodes')->willReturn(true);
|
||||
$oBackupCodeListMock->expects($this->once())->method('save');
|
||||
$oBackupCodeListMock->method('generateBackupCodes');
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'load',
|
||||
'save',
|
||||
@ -344,12 +351,12 @@ class d3user_totpTest extends d3TotpUnitTestCase
|
||||
$oTotpMock->expects($this->never())->method('load')->willReturn(true);
|
||||
$oTotpMock->expects($this->once())->method('save')->willReturn(true);
|
||||
$oTotpMock->expects($this->once())->method('verify')->willReturn(true);
|
||||
$oTotpMock->method('saveSecret')->willReturn(true);
|
||||
$oTotpMock->method('saveSecret');
|
||||
$oTotpMock->method('assign')->willReturn(true);
|
||||
$oTotpMock->method('checkIfAlreadyExist')->willReturn(false);
|
||||
|
||||
/** @var d3user_totp|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3user_totp::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3user_totp::class)
|
||||
->onlyMethods([
|
||||
'getEditObjectId',
|
||||
'getUserObject',
|
||||
@ -363,6 +370,9 @@ class d3user_totpTest extends d3TotpUnitTestCase
|
||||
|
||||
$this->_oController = $oControllerMock;
|
||||
|
||||
$_GET['otp'] = '123456';
|
||||
Registry::getSession()->setVariable(d3totp_conf::OTP_SESSION_VARNAME, $oTotpMock);
|
||||
|
||||
$this->callMethod($this->_oController, 'save');
|
||||
}
|
||||
|
||||
@ -379,17 +389,17 @@ class d3user_totpTest extends d3TotpUnitTestCase
|
||||
$_GET['editval'] = $aEditval;
|
||||
|
||||
/** @var d3backupcodelist|MockObject $oBackupCodeListMock */
|
||||
$oBackupCodeListMock = $this->getMockBuilder(d3backupcodelist::class)
|
||||
$oBackupCodeListMock = $this->d3getMockBuilder(d3backupcodelist::class)
|
||||
->onlyMethods([
|
||||
'save',
|
||||
'generateBackupCodes',
|
||||
])
|
||||
->getMock();
|
||||
$oBackupCodeListMock->expects($this->once())->method('save')->willReturn(true);
|
||||
$oBackupCodeListMock->method('generateBackupCodes')->willReturn(true);
|
||||
$oBackupCodeListMock->expects($this->once())->method('save');
|
||||
$oBackupCodeListMock->method('generateBackupCodes');
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'load',
|
||||
'save',
|
||||
@ -403,12 +413,12 @@ class d3user_totpTest extends d3TotpUnitTestCase
|
||||
$oTotpMock->expects($this->once())->method('load')->willReturn(true);
|
||||
$oTotpMock->expects($this->once())->method('save')->willReturn(true);
|
||||
$oTotpMock->expects($this->never())->method('verify')->willReturn(true);
|
||||
$oTotpMock->method('saveSecret')->willReturn(true);
|
||||
$oTotpMock->method('saveSecret');
|
||||
$oTotpMock->method('assign')->willReturn(true);
|
||||
$oTotpMock->method('checkIfAlreadyExist')->willReturn(false);
|
||||
|
||||
/** @var d3user_totp|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3user_totp::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3user_totp::class)
|
||||
->onlyMethods([
|
||||
'getEditObjectId',
|
||||
'getUserObject',
|
||||
@ -456,14 +466,14 @@ class d3user_totpTest extends d3TotpUnitTestCase
|
||||
$_GET['editval'] = $editval;
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['delete'])
|
||||
->getMock();
|
||||
$oTotpMock->expects($this->never())->method('delete');
|
||||
|
||||
/** @var d3user_totp|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3user_totp::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3user_totp::class)
|
||||
->onlyMethods(['getTotpObject'])
|
||||
->getMock();
|
||||
$oControllerMock->expects($this->never())->method('getTotpObject')->willReturn($oTotpMock);
|
||||
@ -486,7 +496,7 @@ class d3user_totpTest extends d3TotpUnitTestCase
|
||||
$_GET['editval'] = $editval;
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods([
|
||||
'delete',
|
||||
@ -497,7 +507,7 @@ class d3user_totpTest extends d3TotpUnitTestCase
|
||||
$oTotpMock->method('load')->willReturn(true);
|
||||
|
||||
/** @var d3user_totp|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3user_totp::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3user_totp::class)
|
||||
->onlyMethods(['getTotpObject'])
|
||||
->getMock();
|
||||
$oControllerMock->method('getTotpObject')->willReturn($oTotpMock);
|
||||
@ -515,7 +525,7 @@ class d3user_totpTest extends d3TotpUnitTestCase
|
||||
public function canGetAvailableBackupCodeCount()
|
||||
{
|
||||
/** @var d3backupcodelist|MockObject $oBackupCodeListMock */
|
||||
$oBackupCodeListMock = $this->getMockBuilder(d3backupcodelist::class)
|
||||
$oBackupCodeListMock = $this->d3getMockBuilder(d3backupcodelist::class)
|
||||
->onlyMethods(['getAvailableCodeCount'])
|
||||
->getMock();
|
||||
$oBackupCodeListMock->method('getAvailableCodeCount')->willReturn(25);
|
||||
@ -524,14 +534,16 @@ class d3user_totpTest extends d3TotpUnitTestCase
|
||||
$oUser->setId('foo');
|
||||
|
||||
/** @var d3user_totp|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3user_totp::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3user_totp::class)
|
||||
->onlyMethods([
|
||||
'getBackupCodeListObject',
|
||||
'getUser',
|
||||
'getEditObjectId'
|
||||
])
|
||||
->getMock();
|
||||
$oControllerMock->method('getBackupCodeListObject')->willReturn($oBackupCodeListMock);
|
||||
$oControllerMock->method('getUser')->willReturn($oUser);
|
||||
$oControllerMock->method('getEditObjectId')->willReturn('foo');
|
||||
|
||||
$this->_oController = $oControllerMock;
|
||||
|
||||
@ -540,4 +552,28 @@ class d3user_totpTest extends d3TotpUnitTestCase
|
||||
$this->callMethod($this->_oController, 'getAvailableBackupCodeCount')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Application\Controller\Admin\d3user_totp::getCurrentUserId
|
||||
*/
|
||||
public function canGetCurrentUserId(): void
|
||||
{
|
||||
/** @var d3user_totp|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->d3getMockBuilder(d3user_totp::class)
|
||||
->onlyMethods([
|
||||
'getEditObjectId'
|
||||
])
|
||||
->getMock();
|
||||
$oControllerMock->expects($this->once())->method('getEditObjectId')->willReturn('foo');
|
||||
|
||||
$this->_oController = $oControllerMock;
|
||||
|
||||
$this->callMethod(
|
||||
$this->_oController,
|
||||
'getCurrentUserId'
|
||||
);
|
||||
}
|
||||
}
|
@ -13,15 +13,17 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\tests\unit\Application\Controller;
|
||||
namespace D3\Totp\Tests\Unit\Application\Controller;
|
||||
|
||||
use D3\TestingTools\Development\CanAccessRestricted;
|
||||
use D3\Totp\Application\Controller\d3_account_totp;
|
||||
use D3\Totp\Application\Model\d3backupcodelist;
|
||||
use D3\Totp\Application\Model\d3totp;
|
||||
use D3\Totp\tests\unit\d3TotpUnitTestCase;
|
||||
use D3\Totp\Application\Model\d3totp_conf;
|
||||
use D3\Totp\Tests\Unit\d3TotpUnitTestCase;
|
||||
use Exception;
|
||||
use OxidEsales\Eshop\Application\Model\User;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use ReflectionException;
|
||||
|
||||
@ -66,7 +68,7 @@ class d3_account_totpTest extends d3TotpUnitTestCase
|
||||
);
|
||||
|
||||
/** @var d3_account_totp|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3_account_totp::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3_account_totp::class)
|
||||
->onlyMethods(['getUser'])
|
||||
->getMock();
|
||||
$oControllerMock->method('getUser')->willReturn($oUser);
|
||||
@ -76,7 +78,7 @@ class d3_account_totpTest extends d3TotpUnitTestCase
|
||||
$sTpl = $this->callMethod($this->_oController, 'render');
|
||||
$tplUser = $this->callMethod($this->_oController, 'getViewDataElement', ['user']);
|
||||
|
||||
$this->assertSame('d3_account_totp.tpl', $sTpl);
|
||||
$this->assertSame('@d3totp/tpl/d3_account_totp', $sTpl);
|
||||
$this->assertSame($tplUser, $oUser);
|
||||
}
|
||||
|
||||
@ -91,7 +93,7 @@ class d3_account_totpTest extends d3TotpUnitTestCase
|
||||
$oUser = null;
|
||||
|
||||
/** @var d3_account_totp|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3_account_totp::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3_account_totp::class)
|
||||
->onlyMethods(['getUser'])
|
||||
->getMock();
|
||||
$oControllerMock->method('getUser')->willReturn($oUser);
|
||||
@ -101,7 +103,7 @@ class d3_account_totpTest extends d3TotpUnitTestCase
|
||||
$sTpl = $this->callMethod($this->_oController, 'render');
|
||||
$tplUser = $this->callMethod($this->_oController, 'getViewDataElement', ['user']);
|
||||
|
||||
$this->assertSame('page/account/login.tpl', $sTpl);
|
||||
$this->assertSame('page/account/login', $sTpl);
|
||||
$this->assertNull($tplUser);
|
||||
}
|
||||
|
||||
@ -149,7 +151,7 @@ class d3_account_totpTest extends d3TotpUnitTestCase
|
||||
public function canGetAvailableBackupCodeCount()
|
||||
{
|
||||
/** @var d3backupcodelist|MockObject $oBackupCodeListMock */
|
||||
$oBackupCodeListMock = $this->getMockBuilder(d3backupcodelist::class)
|
||||
$oBackupCodeListMock = $this->d3getMockBuilder(d3backupcodelist::class)
|
||||
->onlyMethods(['getAvailableCodeCount'])
|
||||
->getMock();
|
||||
$oBackupCodeListMock->method('getAvailableCodeCount')->willReturn(25);
|
||||
@ -158,7 +160,7 @@ class d3_account_totpTest extends d3TotpUnitTestCase
|
||||
$oUser->setId('foo');
|
||||
|
||||
/** @var d3_account_totp|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3_account_totp::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3_account_totp::class)
|
||||
->onlyMethods([
|
||||
'getBackupCodeListObject',
|
||||
'getUser',
|
||||
@ -184,11 +186,13 @@ class d3_account_totpTest extends d3TotpUnitTestCase
|
||||
{
|
||||
$_GET['totp_use'] = 0;
|
||||
|
||||
$totp = oxNew(d3totp::class);
|
||||
|
||||
/** @var d3_account_totp|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3_account_totp::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3_account_totp::class)
|
||||
->onlyMethods(['getTotpObject'])
|
||||
->getMock();
|
||||
$oControllerMock->expects($this->never())->method('getTotpObject')->willReturn(true);
|
||||
$oControllerMock->expects($this->never())->method('getTotpObject')->willReturn($totp);
|
||||
|
||||
$this->_oController = $oControllerMock;
|
||||
|
||||
@ -203,9 +207,10 @@ class d3_account_totpTest extends d3TotpUnitTestCase
|
||||
public function cantCreateIfTotpNotVerfiable()
|
||||
{
|
||||
$_GET['totp_use'] = '1';
|
||||
$_GET['otp'] = '123456';
|
||||
|
||||
/** @var d3backupcodelist|MockObject $oBackupCodeListMock */
|
||||
$oBackupCodeListMock = $this->getMockBuilder(d3backupcodelist::class)
|
||||
$oBackupCodeListMock = $this->d3getMockBuilder(d3backupcodelist::class)
|
||||
->onlyMethods([
|
||||
'generateBackupCodes',
|
||||
'save',
|
||||
@ -215,7 +220,7 @@ class d3_account_totpTest extends d3TotpUnitTestCase
|
||||
$oBackupCodeListMock->expects($this->never())->method('save');
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods([
|
||||
'saveSecret',
|
||||
@ -224,7 +229,7 @@ class d3_account_totpTest extends d3TotpUnitTestCase
|
||||
'save',
|
||||
])
|
||||
->getMock();
|
||||
$oTotpMock->method('saveSecret')->willReturn(true);
|
||||
$oTotpMock->method('saveSecret');
|
||||
$oTotpMock->method('assign')->willReturn(true);
|
||||
$oTotpMock->expects($this->once())->method('verify')->willThrowException(new Exception('foo'));
|
||||
$oTotpMock->expects($this->never())->method('save');
|
||||
@ -233,16 +238,18 @@ class d3_account_totpTest extends d3TotpUnitTestCase
|
||||
$oUser->setId('foo');
|
||||
|
||||
/** @var d3_account_totp|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3_account_totp::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3_account_totp::class)
|
||||
->onlyMethods([
|
||||
'getTotpObject',
|
||||
'getUser',
|
||||
'getBackupCodeListObject',
|
||||
'getTotpObject'
|
||||
])
|
||||
->getMock();
|
||||
$oControllerMock->method('getTotpObject')->willReturn($oTotpMock);
|
||||
$oControllerMock->method('getUser')->willReturn($oUser);
|
||||
$oControllerMock->method('getBackupCodeListObject')->willReturn($oBackupCodeListMock);
|
||||
$oControllerMock->method('getTotpObject')->willReturn($oTotpMock);
|
||||
|
||||
Registry::getSession()->setVariable(d3totp_conf::OTP_SESSION_VARNAME, $oTotpMock);
|
||||
|
||||
$this->_oController = $oControllerMock;
|
||||
|
||||
@ -257,20 +264,21 @@ class d3_account_totpTest extends d3TotpUnitTestCase
|
||||
public function canCreate()
|
||||
{
|
||||
$_GET['totp_use'] = '1';
|
||||
$_GET['otp'] = '123456';
|
||||
|
||||
/** @var d3backupcodelist|MockObject $oBackupCodeListMock */
|
||||
$oBackupCodeListMock = $this->getMockBuilder(d3backupcodelist::class)
|
||||
$oBackupCodeListMock = $this->d3getMockBuilder(d3backupcodelist::class)
|
||||
->onlyMethods([
|
||||
'generateBackupCodes',
|
||||
'save',
|
||||
])
|
||||
->getMock();
|
||||
$oBackupCodeListMock->method('generateBackupCodes')->willReturn(['0123', '1234']);
|
||||
$oBackupCodeListMock->expects($this->once())->method('save')->willReturn(true);
|
||||
$oBackupCodeListMock->method('save')->willReturn(true);
|
||||
$oBackupCodeListMock->method('generateBackupCodes');
|
||||
$oBackupCodeListMock->expects($this->once())->method('save');
|
||||
$oBackupCodeListMock->method('save');
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods([
|
||||
'saveSecret',
|
||||
@ -280,7 +288,7 @@ class d3_account_totpTest extends d3TotpUnitTestCase
|
||||
'setId',
|
||||
])
|
||||
->getMock();
|
||||
$oTotpMock->method('saveSecret')->willReturn(true);
|
||||
$oTotpMock->method('saveSecret');
|
||||
$oTotpMock->method('assign')->willReturn(true);
|
||||
$oTotpMock->method('verify')->willReturn(true);
|
||||
$oTotpMock->method('setId')->willReturn(true);
|
||||
@ -290,13 +298,14 @@ class d3_account_totpTest extends d3TotpUnitTestCase
|
||||
$oUser->setId('foo');
|
||||
|
||||
/** @var d3_account_totp|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3_account_totp::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3_account_totp::class)
|
||||
->onlyMethods([
|
||||
'getTotpObject',
|
||||
'getUser',
|
||||
'getBackupCodeListObject',
|
||||
'getTotpObject'
|
||||
])
|
||||
->getMock();
|
||||
Registry::getSession()->setVariable(d3totp_conf::OTP_SESSION_VARNAME, $oTotpMock);
|
||||
$oControllerMock->method('getTotpObject')->willReturn($oTotpMock);
|
||||
$oControllerMock->method('getUser')->willReturn($oUser);
|
||||
$oControllerMock->method('getBackupCodeListObject')->willReturn($oBackupCodeListMock);
|
||||
@ -315,11 +324,13 @@ class d3_account_totpTest extends d3TotpUnitTestCase
|
||||
{
|
||||
$_GET['totp_use'] = '1';
|
||||
|
||||
$totpMock = oxNew(d3totp::class);
|
||||
|
||||
/** @var d3_account_totp|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3_account_totp::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3_account_totp::class)
|
||||
->onlyMethods(['getTotpObject'])
|
||||
->getMock();
|
||||
$oControllerMock->expects($this->never())->method('getTotpObject')->willReturn(true);
|
||||
$oControllerMock->expects($this->never())->method('getTotpObject')->willReturn($totpMock);
|
||||
|
||||
$this->_oController = $oControllerMock;
|
||||
|
||||
@ -336,14 +347,14 @@ class d3_account_totpTest extends d3TotpUnitTestCase
|
||||
$_GET['totp_use'] = '0';
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['delete'])
|
||||
->getMock();
|
||||
$oTotpMock->expects($this->never())->method('delete');
|
||||
|
||||
/** @var d3_account_totp|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3_account_totp::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3_account_totp::class)
|
||||
->onlyMethods([
|
||||
'getTotpObject',
|
||||
'getUser',
|
||||
@ -367,7 +378,7 @@ class d3_account_totpTest extends d3TotpUnitTestCase
|
||||
$_GET['totp_use'] = '0';
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods([
|
||||
'delete',
|
||||
@ -375,13 +386,13 @@ class d3_account_totpTest extends d3TotpUnitTestCase
|
||||
])
|
||||
->getMock();
|
||||
$oTotpMock->expects($this->once())->method('delete')->willReturn(true);
|
||||
$oTotpMock->method('loadByUserId')->willReturn(true);
|
||||
$oTotpMock->method('loadByUserId');
|
||||
|
||||
$oUser = oxNew(User::class);
|
||||
$oUser->setId('foo');
|
||||
|
||||
/** @var d3_account_totp|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3_account_totp::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3_account_totp::class)
|
||||
->onlyMethods([
|
||||
'getTotpObject',
|
||||
'getUser',
|
||||
@ -410,4 +421,36 @@ class d3_account_totpTest extends d3TotpUnitTestCase
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Application\Controller\d3_account_totp::getCurrentUserId
|
||||
*/
|
||||
public function canGetCurrentUserId(): void
|
||||
{
|
||||
$userMock = $this->d3getMockBuilder(User::class)
|
||||
->onlyMethods(['getId'])
|
||||
->getMock();
|
||||
$userMock->expects($this->once())->method('getId')->willReturn('foo');
|
||||
|
||||
/** @var d3_account_totp|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->d3getMockBuilder(d3_account_totp::class)
|
||||
->onlyMethods([
|
||||
'getUser'
|
||||
])
|
||||
->getMock();
|
||||
$oControllerMock->expects($this->once())->method('getUser')->willReturn($userMock);
|
||||
|
||||
$this->_oController = $oControllerMock;
|
||||
|
||||
$this->assertSame(
|
||||
'foo',
|
||||
$this->callMethod(
|
||||
$this->_oController,
|
||||
'getCurrentUserId'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -13,13 +13,13 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\tests\unit\Application\Controller;
|
||||
namespace D3\Totp\Tests\Unit\Application\Controller;
|
||||
|
||||
use D3\TestingTools\Development\CanAccessRestricted;
|
||||
use D3\Totp\Application\Controller\d3totplogin;
|
||||
use D3\Totp\Application\Model\d3backupcodelist;
|
||||
use D3\Totp\Application\Model\d3totp_conf;
|
||||
use D3\Totp\tests\unit\d3TotpUnitTestCase;
|
||||
use D3\Totp\Tests\Unit\d3TotpUnitTestCase;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
use OxidEsales\Eshop\Core\Utils;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
@ -60,17 +60,19 @@ class d3totploginTest extends d3TotpUnitTestCase
|
||||
public function renderRedirectIfNoTotp()
|
||||
{
|
||||
/** @var Utils|MockObject $oUtilsMock */
|
||||
$oUtilsMock = $this->getMockBuilder(Utils::class)
|
||||
$oUtilsMock = $this->d3getMockBuilder(Utils::class)
|
||||
->onlyMethods(['redirect'])
|
||||
->getMock();
|
||||
$oUtilsMock->expects($this->once())->method('redirect')->willReturn(true);
|
||||
|
||||
/** @var d3totplogin|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3totplogin::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3totplogin::class)
|
||||
->onlyMethods(['getUtils'])
|
||||
->getMock();
|
||||
$oControllerMock->method('getUtils')->willReturn($oUtilsMock);
|
||||
|
||||
Registry::getSession()->setVariable(d3totp_conf::SESSION_CURRENTCLASS, 'currentClass');
|
||||
|
||||
$this->_oController = $oControllerMock;
|
||||
|
||||
$this->callMethod($this->_oController, 'render');
|
||||
@ -86,21 +88,23 @@ class d3totploginTest extends d3TotpUnitTestCase
|
||||
Registry::getSession()->setVariable(d3totp_conf::SESSION_CURRENTUSER, 'foo');
|
||||
|
||||
/** @var Utils|MockObject $oUtilsMock */
|
||||
$oUtilsMock = $this->getMockBuilder(Utils::class)
|
||||
$oUtilsMock = $this->d3getMockBuilder(Utils::class)
|
||||
->onlyMethods(['redirect'])
|
||||
->getMock();
|
||||
$oUtilsMock->expects($this->never())->method('redirect')->willReturn(true);
|
||||
|
||||
/** @var d3totplogin|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3totplogin::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3totplogin::class)
|
||||
->onlyMethods(['getUtils'])
|
||||
->getMock();
|
||||
$oControllerMock->method('getUtils')->willReturn($oUtilsMock);
|
||||
|
||||
$this->_oController = $oControllerMock;
|
||||
|
||||
Registry::getSession()->setVariable(d3totp_conf::SESSION_CURRENTCLASS, 'currentClass');
|
||||
|
||||
$this->assertSame(
|
||||
'd3totplogin.tpl',
|
||||
'@d3totp/tpl/d3totplogin',
|
||||
$this->callMethod($this->_oController, 'render')
|
||||
);
|
||||
}
|
||||
@ -129,19 +133,21 @@ class d3totploginTest extends d3TotpUnitTestCase
|
||||
public function getBackupCodeCountMessageReturnMessage()
|
||||
{
|
||||
/** @var d3backupcodelist|MockObject $oBackupCodesListMock */
|
||||
$oBackupCodesListMock = $this->getMockBuilder(d3backupcodelist::class)
|
||||
$oBackupCodesListMock = $this->d3getMockBuilder(d3backupcodelist::class)
|
||||
->onlyMethods(['getAvailableCodeCount'])
|
||||
->getMock();
|
||||
$oBackupCodesListMock->method('getAvailableCodeCount')->willReturn(1);
|
||||
|
||||
/** @var d3totplogin|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3totplogin::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3totplogin::class)
|
||||
->onlyMethods(['getBackupCodeListObject'])
|
||||
->getMock();
|
||||
$oControllerMock->method('getBackupCodeListObject')->willReturn($oBackupCodesListMock);
|
||||
|
||||
$this->_oController = $oControllerMock;
|
||||
|
||||
Registry::getSession()->setVariable(d3totp_conf::SESSION_CURRENTUSER, 'userId');
|
||||
|
||||
$this->assertGreaterThan(
|
||||
0,
|
||||
strpos(
|
||||
@ -159,17 +165,19 @@ class d3totploginTest extends d3TotpUnitTestCase
|
||||
public function getBackupCodeCountMessageReturnNoMessage()
|
||||
{
|
||||
/** @var d3backupcodelist|MockObject $oBackupCodesListMock */
|
||||
$oBackupCodesListMock = $this->getMockBuilder(d3backupcodelist::class)
|
||||
$oBackupCodesListMock = $this->d3getMockBuilder(d3backupcodelist::class)
|
||||
->onlyMethods(['getAvailableCodeCount'])
|
||||
->getMock();
|
||||
$oBackupCodesListMock->method('getAvailableCodeCount')->willReturn(1234);
|
||||
|
||||
/** @var d3totplogin|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3totplogin::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3totplogin::class)
|
||||
->onlyMethods(['getBackupCodeListObject'])
|
||||
->getMock();
|
||||
$oControllerMock->method('getBackupCodeListObject')->willReturn($oBackupCodesListMock);
|
||||
|
||||
Registry::getSession()->setVariable(d3totp_conf::SESSION_CURRENTUSER, 'userId');
|
||||
|
||||
$this->_oController = $oControllerMock;
|
||||
|
||||
$this->assertEmpty(
|
||||
@ -228,7 +236,7 @@ class d3totploginTest extends d3TotpUnitTestCase
|
||||
/**
|
||||
* @return array[]
|
||||
*/
|
||||
public function classIsOrderStepDataProvider(): array
|
||||
public static function classIsOrderStepDataProvider(): array
|
||||
{
|
||||
return [
|
||||
'order step class' => ['order', true],
|
||||
@ -262,7 +270,12 @@ class d3totploginTest extends d3TotpUnitTestCase
|
||||
*/
|
||||
public function canGetBreadCrumb()
|
||||
{
|
||||
$aBreadCrumb = $this->callMethod($this->_oController, 'getBreadCrumb');
|
||||
$controllerMock = $this->d3getMockBuilder(d3totplogin::class)
|
||||
->onlyMethods(['getLink'])
|
||||
->getMock();
|
||||
$controllerMock->method('getLink')->willReturn('linkFixture');
|
||||
|
||||
$aBreadCrumb = $this->callMethod($controllerMock, 'getBreadCrumb');
|
||||
|
||||
$this->assertIsString($aBreadCrumb[0]['title']);
|
||||
$this->assertTrue(strlen($aBreadCrumb[0]['title']) > 1);
|
@ -1,10 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace D3\Totp\tests\unit\Application\Factory;
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
namespace D3\Totp\Tests\Unit\Application\Factory;
|
||||
|
||||
use BaconQrCode\Renderer\ImageRenderer;
|
||||
use D3\Totp\Application\Factory\BaconQrCodeFactory;
|
||||
use D3\Totp\tests\unit\d3TotpUnitTestCase;
|
||||
use D3\Totp\Tests\Unit\d3TotpUnitTestCase;
|
||||
|
||||
class BaconQrCodeFactoryTest extends d3TotpUnitTestCase
|
||||
{
|
@ -13,11 +13,11 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\tests\unit\Application\Model\Exceptions;
|
||||
namespace D3\Totp\Tests\Unit\Application\Model\Exceptions;
|
||||
|
||||
use D3\TestingTools\Development\CanAccessRestricted;
|
||||
use D3\Totp\Application\Model\Exceptions\d3totp_wrongOtpException;
|
||||
use D3\Totp\tests\unit\d3TotpUnitTestCase;
|
||||
use D3\Totp\Tests\Unit\d3TotpUnitTestCase;
|
||||
use ReflectionException;
|
||||
|
||||
class d3totp_wrongOtpExceptionTest extends d3TotpUnitTestCase
|
@ -13,11 +13,11 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\tests\unit\Application\Model;
|
||||
namespace D3\Totp\Tests\Unit\Application\Model;
|
||||
|
||||
use D3\TestingTools\Development\CanAccessRestricted;
|
||||
use D3\Totp\Application\Model\d3RandomGenerator;
|
||||
use D3\Totp\tests\unit\d3TotpUnitTestCase;
|
||||
use D3\Totp\Tests\Unit\d3TotpUnitTestCase;
|
||||
use ReflectionException;
|
||||
|
||||
class d3RandomGeneratorTest extends d3TotpUnitTestCase
|
||||
@ -25,7 +25,7 @@ class d3RandomGeneratorTest extends d3TotpUnitTestCase
|
||||
use CanAccessRestricted;
|
||||
|
||||
/** @var d3RandomGenerator */
|
||||
protected $_oModel;
|
||||
protected d3RandomGenerator $_oModel;
|
||||
|
||||
/**
|
||||
* setup basic requirements
|
||||
@ -51,7 +51,7 @@ class d3RandomGeneratorTest extends d3TotpUnitTestCase
|
||||
*/
|
||||
public function getRandomTotpBackupCodeReturnsRightCode()
|
||||
{
|
||||
$this->assertRegExp(
|
||||
$this->assertMatchesRegularExpression(
|
||||
'@[0-9]{6}@',
|
||||
$this->callMethod($this->_oModel, 'getRandomTotpBackupCode')
|
||||
);
|
@ -13,12 +13,13 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\tests\unit\Application\Model;
|
||||
namespace D3\Totp\Tests\Unit\Application\Model;
|
||||
|
||||
use D3\TestingTools\Development\CanAccessRestricted;
|
||||
use D3\Totp\Application\Model\d3backupcode;
|
||||
use D3\Totp\Application\Model\d3totp_conf;
|
||||
use D3\Totp\tests\unit\d3TotpUnitTestCase;
|
||||
use D3\Totp\Tests\Unit\d3TotpUnitTestCase;
|
||||
use Doctrine\DBAL\Query\QueryBuilder;
|
||||
use OxidEsales\Eshop\Application\Model\User;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
@ -59,7 +60,7 @@ class d3backupcodeTest extends d3TotpUnitTestCase
|
||||
$sBackupCode = '123456';
|
||||
|
||||
/** @var d3backupcode|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3backupcode::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3backupcode::class)
|
||||
->onlyMethods([
|
||||
'getRandomTotpBackupCode',
|
||||
'd3EncodeBC',
|
||||
@ -87,7 +88,7 @@ class d3backupcodeTest extends d3TotpUnitTestCase
|
||||
*/
|
||||
public function getRandomTotpBackupCodePass()
|
||||
{
|
||||
$this->assertRegExp(
|
||||
$this->assertMatchesRegularExpression(
|
||||
'@[0-9]{6}@',
|
||||
$this->callMethod($this->_oModel, 'getRandomTotpBackupCode')
|
||||
);
|
||||
@ -101,9 +102,8 @@ class d3backupcodeTest extends d3TotpUnitTestCase
|
||||
public function d3EncodeBCPass()
|
||||
{
|
||||
/** @var User|MockObject $oUserMock */
|
||||
$oUserMock = $this->getMockBuilder(User::class)
|
||||
$oUserMock = $this->d3getMockBuilder(User::class)
|
||||
->onlyMethods(['load'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$oUserMock->method('load')->willReturn(true);
|
||||
$oUserMock->assign(
|
||||
@ -113,7 +113,7 @@ class d3backupcodeTest extends d3TotpUnitTestCase
|
||||
);
|
||||
|
||||
/** @var d3backupcode|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3backupcode::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3backupcode::class)
|
||||
->onlyMethods(['d3TotpGetUserObject'])
|
||||
->getMock();
|
||||
$oModelMock->method('d3TotpGetUserObject')->willReturn($oUserMock);
|
||||
@ -134,7 +134,7 @@ class d3backupcodeTest extends d3TotpUnitTestCase
|
||||
public function d3GetUserReturnCachedUser()
|
||||
{
|
||||
/** @var User|MockObject $oUserMock */
|
||||
$oUserMock = $this->getMockBuilder(User::class)
|
||||
$oUserMock = $this->d3getMockBuilder(User::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$oUserMock->assign(
|
||||
@ -158,15 +158,32 @@ class d3backupcodeTest extends d3TotpUnitTestCase
|
||||
*/
|
||||
public function d3GetUserReturnCurrentUser()
|
||||
{
|
||||
Registry::getSession()->setVariable(d3totp_conf::SESSION_CURRENTUSER, 'foobar');
|
||||
/** @var User|MockObject $oUserMock */
|
||||
$oUserMock = $this->d3getMockBuilder(User::class)
|
||||
->onlyMethods(['d3TotpGetCurrentUser', 'load'])
|
||||
->getMock();
|
||||
$oUserMock->expects($this->once())->method('d3TotpGetCurrentUser')->willReturn('currentUserId');
|
||||
$oUserMock->expects($this->once())->method('load');
|
||||
$oUserMock->assign([
|
||||
'oxid' => 'currentUserId',
|
||||
]);
|
||||
|
||||
$oUser = $this->callMethod($this->_oModel, 'd3GetUser');
|
||||
/** @var d3backupcode|MockObject $oModelMock */
|
||||
$oModelMock = $this->d3getMockBuilder(d3backupcode::class)
|
||||
->onlyMethods(['d3TotpGetUserObject'])
|
||||
->getMock();
|
||||
$oModelMock->method('d3TotpGetUserObject')->willReturn($oUserMock);
|
||||
|
||||
$this->_oModel->setUser(null);
|
||||
|
||||
$oUser = $this->callMethod($oModelMock, 'd3GetUser');
|
||||
|
||||
$this->assertInstanceOf(
|
||||
User::class,
|
||||
$oUser
|
||||
);
|
||||
$this->assertNull(
|
||||
$this->assertSame(
|
||||
'currentUserId',
|
||||
$oUser->getId()
|
||||
);
|
||||
}
|
||||
@ -183,4 +200,21 @@ class d3backupcodeTest extends d3TotpUnitTestCase
|
||||
$this->callMethod($this->_oModel, 'd3TotpGetUserObject')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Application\Model\d3backupcode::getQueryBuilder
|
||||
*/
|
||||
public function canGetQueryBuilder(): void
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
QueryBuilder::class,
|
||||
$this->callMethod(
|
||||
$this->_oModel,
|
||||
'getQueryBuilder'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -13,17 +13,22 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\tests\unit\Application\Model;
|
||||
namespace D3\Totp\Tests\Unit\Application\Model;
|
||||
|
||||
use D3\TestingTools\Development\CanAccessRestricted;
|
||||
use D3\Totp\Application\Model\d3backupcode;
|
||||
use D3\Totp\Application\Model\d3backupcodelist;
|
||||
use D3\Totp\tests\unit\d3TotpUnitTestCase;
|
||||
use D3\Totp\Tests\Unit\d3TotpUnitTestCase;
|
||||
use Doctrine\DBAL\ForwardCompatibility\Result;
|
||||
use Doctrine\DBAL\Query\QueryBuilder;
|
||||
use OxidEsales\Eshop\Application\Controller\FrontendController;
|
||||
use OxidEsales\Eshop\Application\Model\User;
|
||||
use OxidEsales\Eshop\Core\Config;
|
||||
use OxidEsales\Eshop\Core\Database\Adapter\Doctrine\Database;
|
||||
use OxidEsales\EshopCommunity\Internal\Container\ContainerFactory;
|
||||
use OxidEsales\EshopCommunity\Internal\Framework\Database\ConnectionProviderInterface;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use ReflectionException;
|
||||
|
||||
class d3backupcodelistTest extends d3TotpUnitTestCase
|
||||
@ -31,7 +36,7 @@ class d3backupcodelistTest extends d3TotpUnitTestCase
|
||||
use CanAccessRestricted;
|
||||
|
||||
/** @var d3backupcodelist */
|
||||
protected $_oModel;
|
||||
protected d3backupcodelist $_oModel;
|
||||
|
||||
/**
|
||||
* setup basic requirements
|
||||
@ -58,32 +63,32 @@ class d3backupcodelistTest extends d3TotpUnitTestCase
|
||||
public function generateBackupCodes()
|
||||
{
|
||||
/** @var FrontendController|MockObject $oViewMock */
|
||||
$oViewMock = $this->getMockBuilder(FrontendController::class)
|
||||
$oViewMock = $this->d3getMockBuilder(FrontendController::class)
|
||||
->addMethods(['setBackupCodes'])
|
||||
->getMock();
|
||||
$oViewMock->expects($this->once())->method('setBackupCodes')->willReturn(true);
|
||||
|
||||
/** @var Config|MockObject $oConfigMock */
|
||||
$oConfigMock = $this->getMockBuilder(Config::class)
|
||||
$oConfigMock = $this->d3getMockBuilder(Config::class)
|
||||
->onlyMethods(['getActiveView'])
|
||||
->getMock();
|
||||
$oConfigMock->method('getActiveView')->willReturn($oViewMock);
|
||||
|
||||
/** @var d3backupcode|MockObject $oBackupCodeMock */
|
||||
$oBackupCodeMock = $this->getMockBuilder(d3backupcode::class)
|
||||
$oBackupCodeMock = $this->d3getMockBuilder(d3backupcode::class)
|
||||
->onlyMethods(['generateCode'])
|
||||
->getMock();
|
||||
$oBackupCodeMock->expects($this->exactly(10))->method('generateCode');
|
||||
|
||||
/** @var d3backupcodelist|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3backupcodelist::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3backupcodelist::class)
|
||||
->onlyMethods([
|
||||
'deleteAllFromUser',
|
||||
'getD3BackupCodeObject',
|
||||
'd3GetConfig',
|
||||
])
|
||||
->getMock();
|
||||
$oModelMock->expects($this->once())->method('deleteAllFromUser')->willReturn(true);
|
||||
$oModelMock->expects($this->once())->method('deleteAllFromUser');
|
||||
$oModelMock->method('getD3BackupCodeObject')->willReturn($oBackupCodeMock);
|
||||
$oModelMock->method('d3GetConfig')->willReturn($oConfigMock);
|
||||
|
||||
@ -126,7 +131,7 @@ class d3backupcodelistTest extends d3TotpUnitTestCase
|
||||
public function savePass()
|
||||
{
|
||||
/** @var d3backupcode|MockObject $oBackupCodeMock */
|
||||
$oBackupCodeMock = $this->getMockBuilder(d3backupcode::class)
|
||||
$oBackupCodeMock = $this->d3getMockBuilder(d3backupcode::class)
|
||||
->onlyMethods(['save'])
|
||||
->getMock();
|
||||
$oBackupCodeMock->expects($this->once())->method('save')->willReturn(true);
|
||||
@ -136,7 +141,7 @@ class d3backupcodelistTest extends d3TotpUnitTestCase
|
||||
];
|
||||
|
||||
/** @var d3backupcodelist|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3backupcodelist::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3backupcodelist::class)
|
||||
->onlyMethods(['getArray'])
|
||||
->getMock();
|
||||
$oModelMock->expects($this->once())->method('getArray')->willReturn($aBackupCodeArray);
|
||||
@ -161,45 +166,48 @@ class d3backupcodelistTest extends d3TotpUnitTestCase
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Application\Model\d3backupcodelist::verify
|
||||
*/
|
||||
public function verifyFoundTotp()
|
||||
{
|
||||
/** @var User|MockObject $oUserMock */
|
||||
$oUserMock = $this->getMockBuilder(User::class)
|
||||
$oUserMock = $this->d3getMockBuilder(User::class)
|
||||
->onlyMethods(['getId'])
|
||||
->getMock();
|
||||
$oUserMock->method('getId')->willReturn('foobar');
|
||||
$oUserMock->assign(['oxpasssalt' => '6162636465666768696A6B']);
|
||||
|
||||
/** @var d3backupcode|MockObject $oBackupCodeMock */
|
||||
$oBackupCodeMock = $this->getMockBuilder(d3backupcode::class)
|
||||
->onlyMethods(['delete'])
|
||||
$oBackupCodeMock = $this->d3getMockBuilder(d3backupcode::class)
|
||||
->onlyMethods(['delete', 'd3TotpGetUserObject'])
|
||||
->getMock();
|
||||
$oBackupCodeMock->expects($this->once())->method('delete')->willReturn(true);
|
||||
$oBackupCodeMock->method('d3TotpGetUserObject')->willReturn($oUserMock);
|
||||
|
||||
/** @var Database|MockObject $oDbMock */
|
||||
$oDbMock = $this->getMockBuilder(Database::class)
|
||||
->onlyMethods([
|
||||
'getOne',
|
||||
'quoteIdentifier',
|
||||
'quote',
|
||||
])
|
||||
$resultMock = $this->d3getMockBuilder(Result::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['fetchOne'])
|
||||
->getMock();
|
||||
$oDbMock->expects($this->once())->method('getOne')->willReturn('foobar');
|
||||
$oDbMock->method('quoteIdentifier')->willReturn(true);
|
||||
$oDbMock->method('quote')->willReturn(true);
|
||||
$resultMock->method('fetchOne')->willReturn('1');
|
||||
|
||||
$qbMock = $this->d3getMockBuilder(QueryBuilder::class)
|
||||
->setConstructorArgs([ContainerFactory::getInstance()->getContainer()->get(ConnectionProviderInterface::class)->get()])
|
||||
->onlyMethods(['execute'])
|
||||
->getMock();
|
||||
$qbMock->method('execute')->willReturn($resultMock);
|
||||
|
||||
/** @var d3backupcodelist|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3backupcodelist::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3backupcodelist::class)
|
||||
->onlyMethods([
|
||||
'd3GetDb',
|
||||
'getQueryBuilder',
|
||||
'getBaseObject',
|
||||
'd3GetUser',
|
||||
])
|
||||
->getMock();
|
||||
$oModelMock->method('d3GetDb')->willReturn($oDbMock);
|
||||
$oModelMock->method('getQueryBuilder')->willReturn($qbMock);
|
||||
$oModelMock->method('getBaseObject')->willReturn($oBackupCodeMock);
|
||||
$oModelMock->method('d3GetUser')->willReturn($oUserMock);
|
||||
|
||||
@ -212,45 +220,48 @@ class d3backupcodelistTest extends d3TotpUnitTestCase
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Application\Model\d3backupcodelist::verify
|
||||
*/
|
||||
public function verifyNotFoundTotp()
|
||||
{
|
||||
/** @var User|MockObject $oUserMock */
|
||||
$oUserMock = $this->getMockBuilder(User::class)
|
||||
$oUserMock = $this->d3getMockBuilder(User::class)
|
||||
->onlyMethods(['getId'])
|
||||
->getMock();
|
||||
$oUserMock->method('getId')->willReturn('foobar');
|
||||
$oUserMock->assign(['oxpasssalt' => '6162636465666768696A6B']);
|
||||
|
||||
/** @var d3backupcode|MockObject $oBackupCodeMock */
|
||||
$oBackupCodeMock = $this->getMockBuilder(d3backupcode::class)
|
||||
->onlyMethods(['delete'])
|
||||
$oBackupCodeMock = $this->d3getMockBuilder(d3backupcode::class)
|
||||
->onlyMethods(['delete', 'd3TotpGetUserObject'])
|
||||
->getMock();
|
||||
$oBackupCodeMock->expects($this->never())->method('delete')->willReturn(true);
|
||||
$oBackupCodeMock->method('d3TotpGetUserObject')->willReturn($oUserMock);
|
||||
|
||||
/** @var Database|MockObject $oDbMock */
|
||||
$oDbMock = $this->getMockBuilder(Database::class)
|
||||
->onlyMethods([
|
||||
'getOne',
|
||||
'quoteIdentifier',
|
||||
'quote',
|
||||
])
|
||||
$resultMock = $this->d3getMockBuilder(Result::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['fetchOne'])
|
||||
->getMock();
|
||||
$oDbMock->expects($this->once())->method('getOne')->willReturn(null);
|
||||
$oDbMock->method('quoteIdentifier')->willReturn(true);
|
||||
$oDbMock->method('quote')->willReturn(true);
|
||||
$resultMock->method('fetchOne')->willReturn('');
|
||||
|
||||
$qbMock = $this->d3getMockBuilder(QueryBuilder::class)
|
||||
->setConstructorArgs([ContainerFactory::getInstance()->getContainer()->get(ConnectionProviderInterface::class)->get()])
|
||||
->onlyMethods(['execute'])
|
||||
->getMock();
|
||||
$qbMock->method('execute')->willReturn($resultMock);
|
||||
|
||||
/** @var d3backupcodelist|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3backupcodelist::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3backupcodelist::class)
|
||||
->onlyMethods([
|
||||
'd3GetDb',
|
||||
'getQueryBuilder',
|
||||
'getBaseObject',
|
||||
'd3GetUser',
|
||||
])
|
||||
->getMock();
|
||||
$oModelMock->method('d3GetDb')->willReturn($oDbMock);
|
||||
$oModelMock->method('getQueryBuilder')->willReturn($qbMock);
|
||||
$oModelMock->method('getBaseObject')->willReturn($oBackupCodeMock);
|
||||
$oModelMock->method('d3GetUser')->willReturn($oUserMock);
|
||||
|
||||
@ -264,36 +275,16 @@ class d3backupcodelistTest extends d3TotpUnitTestCase
|
||||
/**
|
||||
* @test
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Application\Model\d3backupcodelist::d3GetDb
|
||||
*/
|
||||
public function d3GetDbReturnsRightInstance()
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
Database::class,
|
||||
$this->callMethod($this->_oModel, 'd3GetDb')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @throws ReflectionException
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @covers \D3\Totp\Application\Model\d3backupcodelist::deleteAllFromUser
|
||||
*/
|
||||
public function deleteAllFromUserCodesFound()
|
||||
{
|
||||
/** @var Database|MockObject $oDbMock */
|
||||
$oDbMock = $this->getMockBuilder(Database::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods([
|
||||
'quoteIdentifier',
|
||||
'quote',
|
||||
])
|
||||
->getMock();
|
||||
$oDbMock->method('quoteIdentifier')->willReturn(true);
|
||||
$oDbMock->method('quote')->willReturn(true);
|
||||
$qbMock = new QueryBuilder(ContainerFactory::getInstance()->getContainer()->get(ConnectionProviderInterface::class)->get());
|
||||
|
||||
/** @var d3backupcode|MockObject $oBackupCodeMock */
|
||||
$oBackupCodeMock = $this->getMockBuilder(d3backupcode::class)
|
||||
$oBackupCodeMock = $this->d3getMockBuilder(d3backupcode::class)
|
||||
->onlyMethods(['delete'])
|
||||
->getMock();
|
||||
$oBackupCodeMock->expects($this->once())->method('delete')->willReturn(true);
|
||||
@ -303,16 +294,16 @@ class d3backupcodelistTest extends d3TotpUnitTestCase
|
||||
];
|
||||
|
||||
/** @var d3backupcodelist|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3backupcodelist::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3backupcodelist::class)
|
||||
->onlyMethods([
|
||||
'getArray',
|
||||
'selectString',
|
||||
'd3GetDb',
|
||||
'getQueryBuilder',
|
||||
])
|
||||
->getMock();
|
||||
$oModelMock->expects($this->once())->method('getArray')->willReturn($aBackupCodeArray);
|
||||
$oModelMock->expects($this->once())->method('selectString')->willReturn(true);
|
||||
$oModelMock->method('d3GetDb')->willReturn($oDbMock);
|
||||
$oModelMock->method('getQueryBuilder')->willReturn($qbMock);
|
||||
|
||||
$this->_oModel = $oModelMock;
|
||||
|
||||
@ -321,24 +312,17 @@ class d3backupcodelistTest extends d3TotpUnitTestCase
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Application\Model\d3backupcodelist::deleteAllFromUser
|
||||
*/
|
||||
public function deleteAllFromUserNoCodesFound()
|
||||
{
|
||||
/** @var Database|MockObject $oDbMock */
|
||||
$oDbMock = $this->getMockBuilder(Database::class)
|
||||
->onlyMethods([
|
||||
'quoteIdentifier',
|
||||
'quote',
|
||||
])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$oDbMock->method('quoteIdentifier')->willReturn(true);
|
||||
$oDbMock->method('quote')->willReturn(true);
|
||||
$qbMock = new QueryBuilder(ContainerFactory::getInstance()->getContainer()->get(ConnectionProviderInterface::class)->get());
|
||||
|
||||
/** @var d3backupcode|MockObject $oBackupCodeMock */
|
||||
$oBackupCodeMock = $this->getMockBuilder(d3backupcode::class)
|
||||
$oBackupCodeMock = $this->d3getMockBuilder(d3backupcode::class)
|
||||
->onlyMethods(['delete'])
|
||||
->getMock();
|
||||
$oBackupCodeMock->expects($this->never())->method('delete')->willReturn(true);
|
||||
@ -346,16 +330,16 @@ class d3backupcodelistTest extends d3TotpUnitTestCase
|
||||
$aBackupCodeArray = [];
|
||||
|
||||
/** @var d3backupcodelist|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3backupcodelist::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3backupcodelist::class)
|
||||
->onlyMethods([
|
||||
'getArray',
|
||||
'selectString',
|
||||
'd3GetDb',
|
||||
'getQueryBuilder',
|
||||
])
|
||||
->getMock();
|
||||
$oModelMock->expects($this->once())->method('getArray')->willReturn($aBackupCodeArray);
|
||||
$oModelMock->expects($this->once())->method('selectString')->willReturn(true);
|
||||
$oModelMock->method('d3GetDb')->willReturn($oDbMock);
|
||||
$oModelMock->method('getQueryBuilder')->willReturn($qbMock);
|
||||
|
||||
$this->_oModel = $oModelMock;
|
||||
|
||||
@ -364,29 +348,30 @@ class d3backupcodelistTest extends d3TotpUnitTestCase
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Application\Model\d3backupcodelist::getAvailableCodeCount
|
||||
*/
|
||||
public function getAvailableCodeCountPass()
|
||||
{
|
||||
/** @var Database|MockObject $oDbMock */
|
||||
$oDbMock = $this->getMockBuilder(Database::class)
|
||||
->onlyMethods([
|
||||
'getOne',
|
||||
'quoteIdentifier',
|
||||
'quote',
|
||||
])
|
||||
$resultMock = $this->d3getMockBuilder(Result::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['fetchOne'])
|
||||
->getMock();
|
||||
$oDbMock->expects($this->once())->method('getOne')->willReturn('25');
|
||||
$oDbMock->method('quoteIdentifier')->willReturn(true);
|
||||
$oDbMock->method('quote')->willReturn(true);
|
||||
$resultMock->method('fetchOne')->willReturn(25);
|
||||
|
||||
$qbMock = $this->d3getMockBuilder(QueryBuilder::class)
|
||||
->setConstructorArgs([ContainerFactory::getInstance()->getContainer()->get(ConnectionProviderInterface::class)->get()])
|
||||
->onlyMethods(['execute'])
|
||||
->getMock();
|
||||
$qbMock->method('execute')->willReturn($resultMock);
|
||||
|
||||
/** @var d3backupcodelist|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3backupcodelist::class)
|
||||
->onlyMethods(['d3GetDb'])
|
||||
$oModelMock = $this->d3getMockBuilder(d3backupcodelist::class)
|
||||
->onlyMethods(['getQueryBuilder'])
|
||||
->getMock();
|
||||
$oModelMock->method('d3GetDb')->willReturn($oDbMock);
|
||||
$oModelMock->method('getQueryBuilder')->willReturn($qbMock);
|
||||
|
||||
$this->_oModel = $oModelMock;
|
||||
|
||||
@ -408,4 +393,21 @@ class d3backupcodelistTest extends d3TotpUnitTestCase
|
||||
$this->callMethod($this->_oModel, 'd3GetUser')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Application\Model\d3backupcodelist::getQueryBuilder
|
||||
*/
|
||||
public function canGetQueryBuilder(): void
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
QueryBuilder::class,
|
||||
$this->callMethod(
|
||||
$this->_oModel,
|
||||
'getQueryBuilder'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -13,29 +13,35 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\tests\unit\Application\Model;
|
||||
namespace D3\Totp\Tests\Unit\Application\Model;
|
||||
|
||||
use BaconQrCode\Writer;
|
||||
use D3\TestingTools\Development\CanAccessRestricted;
|
||||
use D3\Totp\Application\Factory\BaconQrCodeFactory;
|
||||
use D3\Totp\Application\Model\d3backupcode;
|
||||
use D3\Totp\Application\Model\d3backupcodelist;
|
||||
use D3\Totp\Application\Model\d3totp;
|
||||
use D3\Totp\Application\Model\Exceptions\d3totp_wrongOtpException;
|
||||
use D3\Totp\tests\unit\d3TotpUnitTestCase;
|
||||
use D3\Totp\Tests\Unit\d3TotpUnitTestCase;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\ForwardCompatibility\Result;
|
||||
use Doctrine\DBAL\Query\QueryBuilder;
|
||||
use OTPHP\TOTP;
|
||||
use OxidEsales\Eshop\Application\Model\User;
|
||||
use OxidEsales\Eshop\Core\Database\Adapter\Doctrine\Database;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
use OxidEsales\EshopCommunity\Internal\Container\ContainerFactory;
|
||||
use OxidEsales\EshopCommunity\Internal\Framework\Database\ConnectionProviderInterface;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use ReflectionException;
|
||||
use stdClass;
|
||||
|
||||
class d3totpTest extends d3TotpUnitTestCase
|
||||
{
|
||||
use CanAccessRestricted;
|
||||
|
||||
/** @var d3totp */
|
||||
protected $_oModel;
|
||||
protected d3totp $_oModel;
|
||||
|
||||
/**
|
||||
* setup basic requirements
|
||||
@ -62,7 +68,7 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
public function constructCallsInit()
|
||||
{
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['init'])
|
||||
->getMock();
|
||||
$oModelMock->expects($this->once())->method('init');
|
||||
@ -75,25 +81,32 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
/**
|
||||
* @test
|
||||
* @throws ReflectionException
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @covers \D3\Totp\Application\Model\d3totp::loadByUserId
|
||||
*/
|
||||
public function loadByUserIdTableNotExist()
|
||||
{
|
||||
/** @var Database|MockObject $oDbMock */
|
||||
$oDbMock = $this->getMockBuilder(Database::class)
|
||||
$resultMock = $this->d3getMockBuilder(Result::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['getOne'])
|
||||
->onlyMethods(['fetchOne'])
|
||||
->getMock();
|
||||
$oDbMock->expects($this->once())->method('getOne')->willReturnOnConsecutiveCalls(false, true);
|
||||
$resultMock->method('fetchOne')->willReturn('');
|
||||
|
||||
$qbMock = $this->d3getMockBuilder(QueryBuilder::class)
|
||||
->setConstructorArgs([ContainerFactory::getInstance()->getContainer()->get(ConnectionProviderInterface::class)->get()])
|
||||
->onlyMethods(['execute'])
|
||||
->getMock();
|
||||
$qbMock->method('execute')->willReturn($resultMock);
|
||||
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'd3GetDb',
|
||||
'getQueryBuilder',
|
||||
'load',
|
||||
])
|
||||
->getMock();
|
||||
$oModelMock->method('d3GetDb')->willReturn($oDbMock);
|
||||
$oModelMock->method('getQueryBuilder')->willReturn($qbMock);
|
||||
$oModelMock->expects($this->never())->method('load')->willReturn(true);
|
||||
|
||||
$this->_oModel = $oModelMock;
|
||||
@ -103,29 +116,33 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Application\Model\d3totp::loadByUserId
|
||||
*/
|
||||
public function loadByUserIdTableExist()
|
||||
{
|
||||
/** @var Database|MockObject $oDbMock */
|
||||
$oDbMock = $this->getMockBuilder(Database::class)
|
||||
$resultMock = $this->d3getMockBuilder(Result::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods([
|
||||
'getOne',
|
||||
'quote',
|
||||
])->getMock();
|
||||
$oDbMock->expects($this->exactly(2))->method('getOne')->willReturnOnConsecutiveCalls(true, true);
|
||||
$oDbMock->method('quote')->willReturn(true);
|
||||
->onlyMethods(['fetchOne'])
|
||||
->getMock();
|
||||
$resultMock->method('fetchOne')->willReturn('oxid');
|
||||
|
||||
$qbMock = $this->d3getMockBuilder(QueryBuilder::class)
|
||||
->setConstructorArgs([ContainerFactory::getInstance()->getContainer()->get(ConnectionProviderInterface::class)->get()])
|
||||
->onlyMethods(['execute'])
|
||||
->getMock();
|
||||
$qbMock->method('execute')->willReturn($resultMock);
|
||||
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'd3GetDb',
|
||||
'getQueryBuilder',
|
||||
'load',
|
||||
])
|
||||
->getMock();
|
||||
$oModelMock->method('d3GetDb')->willReturn($oDbMock);
|
||||
$oModelMock->method('getQueryBuilder')->willReturn($qbMock);
|
||||
$oModelMock->expects($this->once())->method('load')->willReturn(true);
|
||||
|
||||
$this->_oModel = $oModelMock;
|
||||
@ -141,13 +158,13 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
public function getUserFromMember()
|
||||
{
|
||||
/** @var User|MockObject $oUserMock */
|
||||
$oUserMock = $this->getMockBuilder(User::class)
|
||||
$oUserMock = $this->d3getMockBuilder(User::class)
|
||||
->onlyMethods(['load'])
|
||||
->getMock();
|
||||
$oUserMock->method('load')->with('foobar')->willReturn(true);
|
||||
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'd3GetUser',
|
||||
'getFieldData',
|
||||
@ -176,13 +193,13 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
$this->setValue($this->_oModel, 'userId', null);
|
||||
|
||||
/** @var User|MockObject $oUserMock */
|
||||
$oUserMock = $this->getMockBuilder(User::class)
|
||||
$oUserMock = $this->d3getMockBuilder(User::class)
|
||||
->onlyMethods(['load'])
|
||||
->getMock();
|
||||
$oUserMock->method('load')->with('barfoo')->willReturn(true);
|
||||
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'd3GetUser',
|
||||
'getFieldData',
|
||||
@ -201,27 +218,30 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Application\Model\d3totp::checkIfAlreadyExist
|
||||
*/
|
||||
public function checkIfAlreadyExistPass()
|
||||
{
|
||||
/** @var Database|MockObject $oDbMock */
|
||||
$oDbMock = $this->getMockBuilder(Database::class)
|
||||
->onlyMethods([
|
||||
'getOne',
|
||||
'quote',
|
||||
])
|
||||
$resultMock = $this->d3getMockBuilder(Result::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['fetchOne'])
|
||||
->getMock();
|
||||
$oDbMock->expects($this->once())->method('getOne')->willReturn(1);
|
||||
$oDbMock->method('quote')->willReturn(true);
|
||||
$resultMock->method('fetchOne')->willReturn(2);
|
||||
|
||||
$qbMock = $this->d3getMockBuilder(QueryBuilder::class)
|
||||
->setConstructorArgs([ContainerFactory::getInstance()->getContainer()->get(ConnectionProviderInterface::class)->get()])
|
||||
->onlyMethods(['execute'])
|
||||
->getMock();
|
||||
$qbMock->method('execute')->willReturn($resultMock);
|
||||
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['d3GetDb'])
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['getQueryBuilder'])
|
||||
->getMock();
|
||||
$oModelMock->method('d3GetDb')->willReturn($oDbMock);
|
||||
$oModelMock->method('getQueryBuilder')->willReturn($qbMock);
|
||||
|
||||
$this->_oModel = $oModelMock;
|
||||
|
||||
@ -230,19 +250,6 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Application\Model\d3totp::d3GetDb
|
||||
*/
|
||||
public function d3GetDbReturnsRightInstance()
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
Database::class,
|
||||
$this->callMethod($this->_oModel, 'd3GetDb')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @throws ReflectionException
|
||||
@ -266,7 +273,7 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
Registry::getConfig()->setConfigParam('blDisableTotpGlobally', false);
|
||||
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['UserUseTotp'])
|
||||
->getMock();
|
||||
$oModelMock->method('UserUseTotp')->willReturn(true);
|
||||
@ -288,7 +295,7 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
Registry::getConfig()->setConfigParam('blDisableTotpGlobally', false);
|
||||
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['UserUseTotp'])
|
||||
->getMock();
|
||||
$oModelMock->method('UserUseTotp')->willReturn(false);
|
||||
@ -310,7 +317,7 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
Registry::getConfig()->setConfigParam('blDisableTotpGlobally', true);
|
||||
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['UserUseTotp'])
|
||||
->getMock();
|
||||
$oModelMock->method('UserUseTotp')->willReturn(true);
|
||||
@ -332,7 +339,7 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
Registry::getConfig()->setConfigParam('blDisableTotpGlobally', true);
|
||||
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['UserUseTotp'])
|
||||
->getMock();
|
||||
$oModelMock->method('UserUseTotp')->willReturn(false);
|
||||
@ -352,7 +359,7 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
public function UserUseTotpPass()
|
||||
{
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['getFieldData'])
|
||||
->getMock();
|
||||
$oModelMock->method('getFieldData')->willReturnOnConsecutiveCalls(true, true);
|
||||
@ -372,7 +379,7 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
public function UserUseTotpNoTotp()
|
||||
{
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['getFieldData'])
|
||||
->getMock();
|
||||
$oModelMock->method('getFieldData')->willReturnOnConsecutiveCalls(false, true);
|
||||
@ -392,7 +399,7 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
public function UserUseTotpNoSeed()
|
||||
{
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['getFieldData'])
|
||||
->getMock();
|
||||
$oModelMock->method('getFieldData')->willReturnOnConsecutiveCalls(true, false);
|
||||
@ -412,7 +419,7 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
public function UserUseTotpNoTotpAndNoSeed()
|
||||
{
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['getFieldData'])
|
||||
->getMock();
|
||||
$oModelMock->method('getFieldData')->willReturnOnConsecutiveCalls(false, false);
|
||||
@ -432,7 +439,7 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
public function getSavedSecretExistingSeed()
|
||||
{
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'getFieldData',
|
||||
'decrypt',
|
||||
@ -457,7 +464,7 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
public function getSavedSecretNoSeed()
|
||||
{
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'getFieldData',
|
||||
'decrypt',
|
||||
@ -481,7 +488,7 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
public function getSavedSecretCantDecrypt()
|
||||
{
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'getFieldData',
|
||||
'decrypt',
|
||||
@ -504,15 +511,12 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
*/
|
||||
public function getTotpReturnsCachedObject()
|
||||
{
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$otpMock = TOTP::createFromSecret('abc');
|
||||
|
||||
$this->setValue($this->_oModel, 'totp', $oTotpMock);
|
||||
$this->setValue($this->_oModel, 'totp', $otpMock);
|
||||
|
||||
$this->assertSame(
|
||||
$oTotpMock,
|
||||
$otpMock,
|
||||
$this->callMethod($this->_oModel, 'getTotp')
|
||||
);
|
||||
}
|
||||
@ -525,13 +529,13 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
public function getTotpReturnsNewObject()
|
||||
{
|
||||
/** @var User|MockObject $oUserMock */
|
||||
$oUserMock = $this->getMockBuilder(User::class)
|
||||
$oUserMock = $this->d3getMockBuilder(User::class)
|
||||
->onlyMethods(['getFieldData'])
|
||||
->getMock();
|
||||
$oUserMock->method('getFieldData')->willReturn('username');
|
||||
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'getUser',
|
||||
'getSavedSecret',
|
||||
@ -558,7 +562,7 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
public function getTotpReturnsNewObjectNoUserGivenSeed()
|
||||
{
|
||||
/** @var User|MockObject $oUserMock */
|
||||
$oUserMock = $this->getMockBuilder(User::class)
|
||||
$oUserMock = $this->d3getMockBuilder(User::class)
|
||||
->onlyMethods(['getFieldData'])
|
||||
->getMock();
|
||||
$oUserMock->method('getFieldData')->willReturnMap(
|
||||
@ -566,7 +570,7 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
);
|
||||
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'getUser',
|
||||
'getSavedSecret',
|
||||
@ -594,18 +598,15 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
{
|
||||
BaconQrCodeFactory::renderer(200);
|
||||
|
||||
/** @var stdClass|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(stdClass::class)
|
||||
->addMethods(['getProvisioningUri'])
|
||||
->getMock();
|
||||
$oTotpMock->method('getProvisioningUri')->willReturn('uri');
|
||||
$otpMock = TOTP::createFromSecret('abc');
|
||||
$otpMock->setLabel('label');
|
||||
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['getTotp'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$oModelMock->method('getTotp')->willReturn($oTotpMock);
|
||||
$oModelMock->method('getTotp')->willReturn($otpMock);
|
||||
|
||||
$this->_oModel = $oModelMock;
|
||||
|
||||
@ -636,17 +637,13 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
*/
|
||||
public function getSecretPass()
|
||||
{
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['getSecret'])
|
||||
->getMock();
|
||||
$oTotpMock->expects($this->once())->method('getSecret')->willReturn('fixture');
|
||||
$otpMock = TOTP::createFromSecret('abc');
|
||||
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['getTotp'])
|
||||
->getMock();
|
||||
$oModelMock->method('getTotp')->willReturn($oTotpMock);
|
||||
$oModelMock->method('getTotp')->willReturn($otpMock);
|
||||
|
||||
$this->_oModel = $oModelMock;
|
||||
|
||||
@ -662,7 +659,7 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
public function saveSecretPass()
|
||||
{
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['encrypt'])
|
||||
->getMock();
|
||||
$oModelMock->method('encrypt')->willReturn('enc_secret');
|
||||
@ -683,17 +680,30 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
*/
|
||||
public function verifyPass()
|
||||
{
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['verify'])
|
||||
$otpMock = TOTP::createFromSecret('abc');
|
||||
|
||||
$userMock = oxNew(User::class);
|
||||
$userMock->setId('foo');
|
||||
$userMock->assign(['oxpasssalt' => '6162636465666768696A6B']);
|
||||
|
||||
$backupCodeMock = $this->d3getMockBuilder(d3backupcode::class)
|
||||
->onlyMethods(['d3TotpGetUserObject'])
|
||||
->getMock();
|
||||
$oTotpMock->expects($this->once())->method('verify')->willReturn(true);
|
||||
$backupCodeMock->method('d3TotpGetUserObject')->willReturn($userMock);
|
||||
|
||||
$backupCodeListMock = $this->d3getMockBuilder(d3backupcodelist::class)
|
||||
->onlyMethods(['d3GetUser', 'getBaseObject', 'verify'])
|
||||
->getMock();
|
||||
$backupCodeListMock->method('d3GetUser')->willReturn($userMock);
|
||||
$backupCodeListMock->method('getBaseObject')->willReturn($backupCodeMock);
|
||||
$backupCodeListMock->method('verify')->willReturn(true);
|
||||
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['getTotp'])
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['getTotp', 'd3GetBackupCodeListObject'])
|
||||
->getMock();
|
||||
$oModelMock->method('getTotp')->willReturn($oTotpMock);
|
||||
$oModelMock->method('getTotp')->willReturn($otpMock);
|
||||
$oModelMock->method('d3GetBackupCodeListObject')->willReturn($backupCodeListMock);
|
||||
|
||||
$this->_oModel = $oModelMock;
|
||||
|
||||
@ -710,25 +720,21 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
public function verifyBackupCodePass()
|
||||
{
|
||||
/** @var d3backupcodelist|MockObject $oBackupCodeListMock */
|
||||
$oBackupCodeListMock = $this->getMockBuilder(d3backupcodelist::class)
|
||||
$oBackupCodeListMock = $this->d3getMockBuilder(d3backupcodelist::class)
|
||||
->onlyMethods(['verify'])
|
||||
->getMock();
|
||||
$oBackupCodeListMock->expects($this->once())->method('verify')->willReturn(true);
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['verify'])
|
||||
->getMock();
|
||||
$oTotpMock->expects($this->once())->method('verify')->willReturn(false);
|
||||
$otpMock = TOTP::createFromSecret('abc');
|
||||
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'getTotp',
|
||||
'd3GetBackupCodeListObject',
|
||||
])
|
||||
->getMock();
|
||||
$oModelMock->method('getTotp')->willReturn($oTotpMock);
|
||||
$oModelMock->method('getTotp')->willReturn($otpMock);
|
||||
$oModelMock->method('d3GetBackupCodeListObject')->willReturn($oBackupCodeListMock);
|
||||
|
||||
$this->_oModel = $oModelMock;
|
||||
@ -748,25 +754,21 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
$this->expectException(d3totp_wrongOtpException::class);
|
||||
|
||||
/** @var d3backupcodelist|MockObject $oBackupCodeListMock */
|
||||
$oBackupCodeListMock = $this->getMockBuilder(d3backupcodelist::class)
|
||||
$oBackupCodeListMock = $this->d3getMockBuilder(d3backupcodelist::class)
|
||||
->onlyMethods(['verify'])
|
||||
->getMock();
|
||||
$oBackupCodeListMock->expects($this->once())->method('verify')->willReturn(false);
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['verify'])
|
||||
->getMock();
|
||||
$oTotpMock->expects($this->once())->method('verify')->willReturn(false);
|
||||
$otpMock = TOTP::createFromSecret('abc');
|
||||
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'getTotp',
|
||||
'd3GetBackupCodeListObject',
|
||||
])
|
||||
->getMock();
|
||||
$oModelMock->method('getTotp')->willReturn($oTotpMock);
|
||||
$oModelMock->method('getTotp')->willReturn($otpMock);
|
||||
$oModelMock->method('d3GetBackupCodeListObject')->willReturn($oBackupCodeListMock);
|
||||
|
||||
$this->_oModel = $oModelMock;
|
||||
@ -784,25 +786,21 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
$this->expectException(d3totp_wrongOtpException::class);
|
||||
|
||||
/** @var d3backupcodelist|MockObject $oBackupCodeListMock */
|
||||
$oBackupCodeListMock = $this->getMockBuilder(d3backupcodelist::class)
|
||||
$oBackupCodeListMock = $this->d3getMockBuilder(d3backupcodelist::class)
|
||||
->onlyMethods(['verify'])
|
||||
->getMock();
|
||||
$oBackupCodeListMock->expects($this->never())->method('verify')->willReturn(false);
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['verify'])
|
||||
->getMock();
|
||||
$oTotpMock->expects($this->once())->method('verify')->willReturn(false);
|
||||
$otpMock = TOTP::createFromSecret('abc');
|
||||
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'getTotp',
|
||||
'd3GetBackupCodeListObject',
|
||||
])
|
||||
->getMock();
|
||||
$oModelMock->method('getTotp')->willReturn($oTotpMock);
|
||||
$oModelMock->method('getTotp')->willReturn($otpMock);
|
||||
$oModelMock->method('d3GetBackupCodeListObject')->willReturn($oBackupCodeListMock);
|
||||
|
||||
$this->_oModel = $oModelMock;
|
||||
@ -863,7 +861,7 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
public function decryptFailed()
|
||||
{
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['d3Base64_decode'])
|
||||
->getMock();
|
||||
$oModelMock->method('d3Base64_decode')->willReturn(
|
||||
@ -902,22 +900,20 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
public function deletePass()
|
||||
{
|
||||
/** @var d3backupcodelist|MockObject $oBackupCodeListMock */
|
||||
$oBackupCodeListMock = $this->getMockBuilder(d3backupcodelist::class)
|
||||
$oBackupCodeListMock = $this->d3getMockBuilder(d3backupcodelist::class)
|
||||
->onlyMethods(['deleteAllFromUser'])
|
||||
->getMock();
|
||||
$oBackupCodeListMock->expects($this->once())->method('deleteAllFromUser')->willReturn(true);
|
||||
$oBackupCodeListMock->expects($this->once())->method('deleteAllFromUser');
|
||||
|
||||
/** @var d3totp|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(d3totp::class)
|
||||
$oModelMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'd3GetBackupCodeListObject',
|
||||
'getFieldData',
|
||||
'canDelete',
|
||||
])
|
||||
->getMock();
|
||||
$oModelMock->method('d3GetBackupCodeListObject')->willReturn($oBackupCodeListMock);
|
||||
$oModelMock->method('getFieldData')->willReturn('newId');
|
||||
$oModelMock->method('canDelete')->willReturn(false);
|
||||
|
||||
$this->_oModel = $oModelMock;
|
||||
|
||||
@ -925,4 +921,38 @@ class d3totpTest extends d3TotpUnitTestCase
|
||||
$this->callMethod($this->_oModel, 'delete')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Application\Model\d3totp::getQueryBuilder
|
||||
*/
|
||||
public function canGetQueryBuilder(): void
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
QueryBuilder::class,
|
||||
$this->callMethod(
|
||||
$this->_oModel,
|
||||
'getQueryBuilder'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Application\Model\d3totp::getDbConnection
|
||||
*/
|
||||
public function canGetDbConnection(): void
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
Connection::class,
|
||||
$this->callMethod(
|
||||
$this->_oModel,
|
||||
'getDbConnection'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
31
Tests/Unit/Application/TranslationTest.php
Normal file
31
Tests/Unit/Application/TranslationTest.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Unit\Application;
|
||||
|
||||
use D3\Totp\Tests\Unit\d3TotpUnitTestCase;
|
||||
use Generator;
|
||||
|
||||
class TranslationTest extends d3TotpUnitTestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
* @return void
|
||||
* @dataProvider canGetTranslationDataProvider
|
||||
*/
|
||||
public function canGetTranslation(string $path): void
|
||||
{
|
||||
$list = require __DIR__.'/../../../'.$path;
|
||||
|
||||
$this->assertIsArray($list);
|
||||
$this->assertTrue(count($list) > 1);
|
||||
$this->assertTrue($list['charset'] === 'UTF-8');
|
||||
}
|
||||
|
||||
public static function canGetTranslationDataProvider(): Generator
|
||||
{
|
||||
yield 'frontend DE' => ['Application/translations/de/translations.php'];
|
||||
yield 'frontend EN' => ['Application/translations/en/translations.php'];
|
||||
yield 'backend DE' => ['Application/views/de/translations.php'];
|
||||
yield 'backend EN' => ['Application/views/en/translations.php'];
|
||||
}
|
||||
}
|
@ -13,14 +13,14 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\tests\unit\Modules\Application\Component;
|
||||
namespace D3\Totp\Tests\Unit\Modules\Application\Component;
|
||||
|
||||
use D3\TestingTools\Development\CanAccessRestricted;
|
||||
use D3\Totp\Application\Model\d3totp;
|
||||
use D3\Totp\Application\Model\d3totp_conf;
|
||||
use D3\Totp\Application\Model\Exceptions\d3totp_wrongOtpException;
|
||||
use D3\Totp\Modules\Application\Component\d3_totp_UserComponent;
|
||||
use D3\Totp\tests\unit\d3TotpUnitTestCase;
|
||||
use D3\Totp\Tests\Unit\d3TotpUnitTestCase;
|
||||
use InvalidArgumentException;
|
||||
use OxidEsales\Eshop\Application\Component\UserComponent;
|
||||
use OxidEsales\Eshop\Application\Model\User;
|
||||
@ -39,33 +39,33 @@ class d3_totp_UserComponentTest extends d3TotpUnitTestCase
|
||||
/**
|
||||
* @test
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Modules\Application\Component\d3_totp_UserComponent::_afterLogin
|
||||
* @covers \D3\Totp\Modules\Application\Component\d3_totp_UserComponent::afterLogin
|
||||
*/
|
||||
public function afterLoginFailsIfNoUserLoggedIn()
|
||||
{
|
||||
$oUser = false;
|
||||
|
||||
/** @var Utils|MockObject $oUtilsMock */
|
||||
$oUtilsMock = $this->getMockBuilder(Utils::class)
|
||||
$oUtilsMock = $this->d3getMockBuilder(Utils::class)
|
||||
->onlyMethods(['redirect'])
|
||||
->getMock();
|
||||
$oUtilsMock->expects($this->never())->method('redirect')->willReturn(true);
|
||||
|
||||
/** @var Session|MockObject $oSessionMock */
|
||||
$oSessionMock = $this->getMockBuilder(Session::class)
|
||||
$oSessionMock = $this->d3getMockBuilder(Session::class)
|
||||
->onlyMethods(['setVariable'])
|
||||
->getMock();
|
||||
$oSessionMock->expects($this->never())->method('setVariable');
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['isActive'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$oTotpMock->expects($this->never())->method('isActive')->willReturn(false);
|
||||
|
||||
/** @var UserComponent|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(UserComponent::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(UserComponent::class)
|
||||
->onlyMethods([
|
||||
'd3GetTotpObject',
|
||||
'd3TotpGetSession',
|
||||
@ -79,19 +79,19 @@ class d3_totp_UserComponentTest extends d3TotpUnitTestCase
|
||||
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
|
||||
$this->callMethod($oControllerMock, '_afterLogin', [$oUser]);
|
||||
$this->callMethod($oControllerMock, 'afterLogin', [$oUser]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Modules\Application\Component\d3_totp_UserComponent::_afterLogin
|
||||
* @covers \D3\Totp\Modules\Application\Component\d3_totp_UserComponent::afterLogin
|
||||
* @dataProvider afterLoginFailTotpNotActiveOrAlreadyCheckedDataProvider
|
||||
*/
|
||||
public function afterLoginFailTotpNotActiveOrAlreadyChecked($isActive, $checked)
|
||||
{
|
||||
/** @var User|MockObject $oUserMock */
|
||||
$oUserMock = $this->getMockBuilder(User::class)
|
||||
$oUserMock = $this->d3getMockBuilder(User::class)
|
||||
->onlyMethods([
|
||||
'logout',
|
||||
'getId',
|
||||
@ -101,20 +101,20 @@ class d3_totp_UserComponentTest extends d3TotpUnitTestCase
|
||||
$oUserMock->method('getId')->willReturn('foo');
|
||||
|
||||
/** @var Utils|MockObject $oUtilsMock */
|
||||
$oUtilsMock = $this->getMockBuilder(Utils::class)
|
||||
$oUtilsMock = $this->d3getMockBuilder(Utils::class)
|
||||
->onlyMethods(['redirect'])
|
||||
->getMock();
|
||||
$oUtilsMock->expects($this->never())->method('redirect')->willReturn(true);
|
||||
|
||||
/** @var Session|MockObject $oSessionMock */
|
||||
$oSessionMock = $this->getMockBuilder(Session::class)
|
||||
$oSessionMock = $this->d3getMockBuilder(Session::class)
|
||||
->onlyMethods(['setVariable', 'getVariable'])
|
||||
->getMock();
|
||||
$oSessionMock->expects($this->never())->method('setVariable');
|
||||
$oSessionMock->method('getVariable')->willReturn($checked);
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'isActive',
|
||||
'loadByUserId',
|
||||
@ -122,10 +122,10 @@ class d3_totp_UserComponentTest extends d3TotpUnitTestCase
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$oTotpMock->expects($this->once())->method('isActive')->willReturn($isActive);
|
||||
$oTotpMock->method('loadByUserId')->willReturn(true);
|
||||
$oTotpMock->method('loadByUserId');
|
||||
|
||||
/** @var UserComponent|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(UserComponent::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(UserComponent::class)
|
||||
->onlyMethods([
|
||||
'd3GetTotpObject',
|
||||
'd3TotpGetSession',
|
||||
@ -136,7 +136,7 @@ class d3_totp_UserComponentTest extends d3TotpUnitTestCase
|
||||
$oControllerMock->method('d3TotpGetSession')->willReturn($oSessionMock);
|
||||
$oControllerMock->method('d3TotpGetUtils')->willReturn($oUtilsMock);
|
||||
|
||||
$this->callMethod($oControllerMock, '_afterLogin', [$oUserMock]);
|
||||
$this->callMethod($oControllerMock, 'afterLogin', [$oUserMock]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -154,12 +154,12 @@ class d3_totp_UserComponentTest extends d3TotpUnitTestCase
|
||||
/**
|
||||
* @test
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Modules\Application\Component\d3_totp_UserComponent::_afterLogin
|
||||
* @covers \D3\Totp\Modules\Application\Component\d3_totp_UserComponent::afterLogin
|
||||
*/
|
||||
public function afterLoginPass()
|
||||
{
|
||||
/** @var User|MockObject $oUserMock */
|
||||
$oUserMock = $this->getMockBuilder(User::class)
|
||||
$oUserMock = $this->d3getMockBuilder(User::class)
|
||||
->onlyMethods([
|
||||
'logout',
|
||||
'getId',
|
||||
@ -169,26 +169,26 @@ class d3_totp_UserComponentTest extends d3TotpUnitTestCase
|
||||
$oUserMock->method('getId')->willReturn('foo');
|
||||
|
||||
/** @var Utils|MockObject $oUtilsMock */
|
||||
$oUtilsMock = $this->getMockBuilder(Utils::class)
|
||||
$oUtilsMock = $this->d3getMockBuilder(Utils::class)
|
||||
->onlyMethods(['redirect'])
|
||||
->getMock();
|
||||
$oUtilsMock->expects($this->once())->method('redirect')->willReturn(true);
|
||||
|
||||
/** @var Session|MockObject $oSessionMock */
|
||||
$oSessionMock = $this->getMockBuilder(Session::class)
|
||||
$oSessionMock = $this->d3getMockBuilder(Session::class)
|
||||
->onlyMethods(['setVariable', 'getVariable'])
|
||||
->getMock();
|
||||
$oSessionMock->expects($this->atLeast(3))->method('setVariable');
|
||||
$oSessionMock->method('getVariable')->willReturn(null);
|
||||
|
||||
/** @var BaseController|MockObject $oParentMock */
|
||||
$oParentMock = $this->getMockBuilder(BaseController::class)
|
||||
$oParentMock = $this->d3getMockBuilder(BaseController::class)
|
||||
->onlyMethods(['getClassKey'])
|
||||
->getMock();
|
||||
$oParentMock->method('getClassKey')->willReturn('foo');
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'isActive',
|
||||
'loadByUserId',
|
||||
@ -196,10 +196,10 @@ class d3_totp_UserComponentTest extends d3TotpUnitTestCase
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$oTotpMock->expects($this->once())->method('isActive')->willReturn(true);
|
||||
$oTotpMock->method('loadByUserId')->willReturn(true);
|
||||
$oTotpMock->method('loadByUserId');
|
||||
|
||||
/** @var UserComponent|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(UserComponent::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(UserComponent::class)
|
||||
->onlyMethods([
|
||||
'd3GetTotpObject',
|
||||
'd3TotpGetSession',
|
||||
@ -212,7 +212,71 @@ class d3_totp_UserComponentTest extends d3TotpUnitTestCase
|
||||
$oControllerMock->method('d3TotpGetSession')->willReturn($oSessionMock);
|
||||
$oControllerMock->method('d3TotpGetUtils')->willReturn($oUtilsMock);
|
||||
|
||||
$this->callMethod($oControllerMock, '_afterLogin', [$oUserMock]);
|
||||
$this->callMethod($oControllerMock, 'afterLogin', [$oUserMock]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Modules\Application\Component\d3_totp_UserComponent::afterLogin
|
||||
*/
|
||||
public function afterFailedNoUserLoaded()
|
||||
{
|
||||
/** @var User|MockObject $oUserMock */
|
||||
$oUserMock = $this->d3getMockBuilder(User::class)
|
||||
->onlyMethods([
|
||||
'logout',
|
||||
'getId',
|
||||
])
|
||||
->getMock();
|
||||
$oUserMock->expects($this->never())->method('logout')->willReturn(false);
|
||||
$oUserMock->method('getId')->willReturn('');
|
||||
|
||||
/** @var Utils|MockObject $oUtilsMock */
|
||||
$oUtilsMock = $this->d3getMockBuilder(Utils::class)
|
||||
->onlyMethods(['redirect'])
|
||||
->getMock();
|
||||
$oUtilsMock->expects($this->never())->method('redirect')->willReturn(true);
|
||||
|
||||
/** @var Session|MockObject $oSessionMock */
|
||||
$oSessionMock = $this->d3getMockBuilder(Session::class)
|
||||
->onlyMethods(['setVariable', 'getVariable'])
|
||||
->getMock();
|
||||
$oSessionMock->expects($this->never())->method('setVariable');
|
||||
$oSessionMock->method('getVariable')->willReturn(null);
|
||||
|
||||
/** @var BaseController|MockObject $oParentMock */
|
||||
$oParentMock = $this->d3getMockBuilder(BaseController::class)
|
||||
->onlyMethods(['getClassKey'])
|
||||
->getMock();
|
||||
$oParentMock->method('getClassKey')->willReturn('foo');
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'isActive',
|
||||
'loadByUserId',
|
||||
])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$oTotpMock->expects($this->never())->method('isActive')->willReturn(true);
|
||||
$oTotpMock->method('loadByUserId');
|
||||
|
||||
/** @var UserComponent|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->d3getMockBuilder(UserComponent::class)
|
||||
->onlyMethods([
|
||||
'd3GetTotpObject',
|
||||
'd3TotpGetSession',
|
||||
'd3TotpGetUtils',
|
||||
'getParent',
|
||||
])
|
||||
->getMock();
|
||||
$oControllerMock->method('d3GetTotpObject')->willReturn($oTotpMock);
|
||||
$oControllerMock->method('getParent')->willReturn($oParentMock);
|
||||
$oControllerMock->method('d3TotpGetSession')->willReturn($oSessionMock);
|
||||
$oControllerMock->method('d3TotpGetUtils')->willReturn($oUtilsMock);
|
||||
|
||||
$this->callMethod($oControllerMock, 'afterLogin', [$oUserMock]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -239,20 +303,20 @@ class d3_totp_UserComponentTest extends d3TotpUnitTestCase
|
||||
public function checkTotploginNoTotpLogin()
|
||||
{
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['loadByUserId'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$oTotpMock->method('loadByUserId')->willReturn(true);
|
||||
$oTotpMock->method('loadByUserId');
|
||||
|
||||
/** @var Session|MockObject $oSessionMock */
|
||||
$oSessionMock = $this->getMockBuilder(Session::class)
|
||||
$oSessionMock = $this->d3getMockBuilder(Session::class)
|
||||
->onlyMethods(['setVariable'])
|
||||
->getMock();
|
||||
$oSessionMock->expects($this->never())->method('setVariable');
|
||||
|
||||
/** @var UserComponent|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(UserComponent::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(UserComponent::class)
|
||||
->onlyMethods([
|
||||
'd3TotpIsNoTotpOrNoLogin',
|
||||
'd3TotpHasValidTotp',
|
||||
@ -265,6 +329,8 @@ class d3_totp_UserComponentTest extends d3TotpUnitTestCase
|
||||
$oControllerMock->method('d3GetTotpObject')->willReturn($oTotpMock);
|
||||
$oControllerMock->method('d3TotpGetSession')->willReturn($oSessionMock);
|
||||
|
||||
Registry::getSession()->setVariable(d3totp_conf::SESSION_CURRENTUSER, 'oxid');
|
||||
|
||||
$this->assertSame(
|
||||
'd3totplogin',
|
||||
$this->callMethod($oControllerMock, 'd3TotpCheckTotpLogin')
|
||||
@ -279,31 +345,31 @@ class d3_totp_UserComponentTest extends d3TotpUnitTestCase
|
||||
public function checkTotploginUnvalidTotp()
|
||||
{
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['loadByUserId'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$oTotpMock->method('loadByUserId')->willReturn(true);
|
||||
$oTotpMock->method('loadByUserId');
|
||||
|
||||
/** @var Session|MockObject $oSessionMock */
|
||||
$oSessionMock = $this->getMockBuilder(Session::class)
|
||||
$oSessionMock = $this->d3getMockBuilder(Session::class)
|
||||
->onlyMethods(['setVariable'])
|
||||
->getMock();
|
||||
$oSessionMock->expects($this->never())->method('setVariable');
|
||||
|
||||
/** @var d3totp_wrongOtpException|MockObject $oTotpExceptionMock */
|
||||
$oTotpExceptionMock = $this->getMockBuilder(d3totp_wrongOtpException::class)
|
||||
$oTotpExceptionMock = $this->d3getMockBuilder(d3totp_wrongOtpException::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
/** @var UtilsView|MockObject $oUtilsViewMock */
|
||||
$oUtilsViewMock = $this->getMockBuilder(UtilsView::class)
|
||||
$oUtilsViewMock = $this->d3getMockBuilder(UtilsView::class)
|
||||
->onlyMethods(['addErrorToDisplay'])
|
||||
->getMock();
|
||||
$oUtilsViewMock->expects($this->atLeast(1))->method('addErrorToDisplay')->willReturn(true);
|
||||
|
||||
/** @var UserComponent|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(UserComponent::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(UserComponent::class)
|
||||
->onlyMethods([
|
||||
'd3TotpIsNoTotpOrNoLogin',
|
||||
'd3TotpHasValidTotp',
|
||||
@ -318,6 +384,8 @@ class d3_totp_UserComponentTest extends d3TotpUnitTestCase
|
||||
$oControllerMock->method('d3GetTotpObject')->willReturn($oTotpMock);
|
||||
$oControllerMock->method('d3TotpGetSession')->willReturn($oSessionMock);
|
||||
|
||||
Registry::getSession()->setVariable(d3totp_conf::SESSION_CURRENTUSER, 'oxid');
|
||||
|
||||
$this->assertSame(
|
||||
'd3totplogin',
|
||||
$this->callMethod($oControllerMock, 'd3TotpCheckTotpLogin')
|
||||
@ -332,26 +400,26 @@ class d3_totp_UserComponentTest extends d3TotpUnitTestCase
|
||||
public function checkTotploginValidTotp()
|
||||
{
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['loadByUserId'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$oTotpMock->method('loadByUserId')->willReturn(true);
|
||||
$oTotpMock->method('loadByUserId');
|
||||
|
||||
/** @var Session|MockObject $oSessionMock */
|
||||
$oSessionMock = $this->getMockBuilder(Session::class)
|
||||
$oSessionMock = $this->d3getMockBuilder(Session::class)
|
||||
->onlyMethods(['setVariable'])
|
||||
->getMock();
|
||||
$oSessionMock->expects($this->atLeast(2))->method('setVariable');
|
||||
|
||||
/** @var UtilsView|MockObject $oUtilsViewMock */
|
||||
$oUtilsViewMock = $this->getMockBuilder(UtilsView::class)
|
||||
$oUtilsViewMock = $this->d3getMockBuilder(UtilsView::class)
|
||||
->onlyMethods(['addErrorToDisplay'])
|
||||
->getMock();
|
||||
$oUtilsViewMock->expects($this->never())->method('addErrorToDisplay')->willReturn(true);
|
||||
|
||||
/** @var UserComponent|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(UserComponent::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(UserComponent::class)
|
||||
->onlyMethods([
|
||||
'd3TotpIsNoTotpOrNoLogin',
|
||||
'd3TotpHasValidTotp',
|
||||
@ -370,6 +438,8 @@ class d3_totp_UserComponentTest extends d3TotpUnitTestCase
|
||||
$this->identicalTo(USER_LOGIN_SUCCESS)
|
||||
);
|
||||
|
||||
Registry::getSession()->setVariable(d3totp_conf::SESSION_CURRENTUSER, 'oxid');
|
||||
|
||||
$this->assertFalse(
|
||||
$this->callMethod($oControllerMock, 'd3TotpCheckTotpLogin')
|
||||
);
|
||||
@ -415,10 +485,10 @@ class d3_totp_UserComponentTest extends d3TotpUnitTestCase
|
||||
public function canCancelTotpLogin()
|
||||
{
|
||||
/** @var UserComponent|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(UserComponent::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(UserComponent::class)
|
||||
->onlyMethods(['d3TotpClearSessionVariables'])
|
||||
->getMock();
|
||||
$oControllerMock->expects($this->once())->method('d3TotpClearSessionVariables')->willReturn(false);
|
||||
$oControllerMock->expects($this->once())->method('d3TotpClearSessionVariables');
|
||||
|
||||
$this->callMethod($oControllerMock, 'd3TotpCancelTotpLogin');
|
||||
}
|
||||
@ -433,7 +503,7 @@ class d3_totp_UserComponentTest extends d3TotpUnitTestCase
|
||||
Registry::getSession()->setVariable(d3totp_conf::SESSION_CURRENTUSER, false);
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['isActive'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
@ -457,7 +527,7 @@ class d3_totp_UserComponentTest extends d3TotpUnitTestCase
|
||||
Registry::getSession()->setVariable(d3totp_conf::SESSION_CURRENTUSER, true);
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['isActive'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
@ -481,7 +551,7 @@ class d3_totp_UserComponentTest extends d3TotpUnitTestCase
|
||||
Registry::getSession()->setVariable(d3totp_conf::SESSION_CURRENTUSER, true);
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['isActive'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
@ -505,7 +575,7 @@ class d3_totp_UserComponentTest extends d3TotpUnitTestCase
|
||||
Registry::getSession()->setVariable(d3totp_conf::SESSION_AUTH, true);
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['verify'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
@ -529,7 +599,7 @@ class d3_totp_UserComponentTest extends d3TotpUnitTestCase
|
||||
Registry::getSession()->setVariable(d3totp_conf::SESSION_AUTH, false);
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['verify'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
@ -553,7 +623,7 @@ class d3_totp_UserComponentTest extends d3TotpUnitTestCase
|
||||
Registry::getSession()->setVariable(d3totp_conf::SESSION_AUTH, false);
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['verify'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
@ -563,7 +633,7 @@ class d3_totp_UserComponentTest extends d3TotpUnitTestCase
|
||||
$oController = oxNew(UserComponent::class);
|
||||
|
||||
$this->expectException(d3totp_wrongOtpException::class);
|
||||
$this->callMethod($oController, 'd3TotpHasValidTotp', [null, $oTotpMock]);
|
||||
$this->callMethod($oController, 'd3TotpHasValidTotp', ['', $oTotpMock]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -576,7 +646,7 @@ class d3_totp_UserComponentTest extends d3TotpUnitTestCase
|
||||
Registry::getSession()->setVariable(d3totp_conf::SESSION_AUTH, false);
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['verify'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
@ -598,13 +668,13 @@ class d3_totp_UserComponentTest extends d3TotpUnitTestCase
|
||||
public function d3TotpClearSessionVariablesPass()
|
||||
{
|
||||
/** @var Session|MockObject $oSessionMock */
|
||||
$oSessionMock = $this->getMockBuilder(Session::class)
|
||||
$oSessionMock = $this->d3getMockBuilder(Session::class)
|
||||
->onlyMethods(['deleteVariable'])
|
||||
->getMock();
|
||||
$oSessionMock->expects($this->atLeast(3))->method('deleteVariable')->willReturn(false);
|
||||
|
||||
/** @var UserComponent|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(UserComponent::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(UserComponent::class)
|
||||
->onlyMethods(['d3TotpGetSession'])
|
||||
->getMock();
|
||||
$oControllerMock->method('d3TotpGetSession')->willReturn($oSessionMock);
|
@ -13,14 +13,14 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\tests\unit\Modules\Application\Controller\Admin;
|
||||
namespace D3\Totp\Tests\Unit\Modules\Application\Controller\Admin;
|
||||
|
||||
use D3\TestingTools\Development\CanAccessRestricted;
|
||||
use D3\Totp\Application\Model\d3totp;
|
||||
use D3\Totp\Application\Model\d3totp_conf;
|
||||
use D3\Totp\Modules\Application\Controller\Admin\d3_totp_LoginController;
|
||||
use D3\Totp\Modules\Application\Model\d3_totp_user;
|
||||
use D3\Totp\tests\unit\d3TotpUnitTestCase;
|
||||
use D3\Totp\Tests\Unit\d3TotpUnitTestCase;
|
||||
use OxidEsales\Eshop\Application\Controller\Admin\LoginController;
|
||||
use OxidEsales\Eshop\Application\Model\User;
|
||||
use OxidEsales\Eshop\Core\Language;
|
||||
@ -89,10 +89,10 @@ class d3_totp_LoginControllerTest extends d3TotpUnitTestCase
|
||||
$fixture = 'returnString';
|
||||
|
||||
/** @var d3_totp_LoginController|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3_totp_LoginController::class)
|
||||
->onlyMethods(['d3CallMockableFunction'])
|
||||
$oControllerMock = $this->d3getMockBuilder(d3_totp_LoginController::class)
|
||||
->onlyMethods(['parent__checklogin'])
|
||||
->getMock();
|
||||
$oControllerMock->method('d3CallMockableFunction')->willReturn($fixture);
|
||||
$oControllerMock->method('parent__checklogin')->willReturn($fixture);
|
||||
|
||||
$this->_oController = $oControllerMock;
|
||||
|
||||
@ -120,7 +120,7 @@ class d3_totp_LoginControllerTest extends d3TotpUnitTestCase
|
||||
public function canRunTotpAfterLogin($selectedProfile, $setCookie, $expectedCookie, $setSession)
|
||||
{
|
||||
/** @var Session|MockObject $sessionMock */
|
||||
$sessionMock = $this->getMockBuilder(Session::class)
|
||||
$sessionMock = $this->d3getMockBuilder(Session::class)
|
||||
->onlyMethods(['getVariable', 'setVariable'])
|
||||
->getMock();
|
||||
$variableMap = [
|
||||
@ -135,7 +135,7 @@ class d3_totp_LoginControllerTest extends d3TotpUnitTestCase
|
||||
$sessionMock->expects($setSession)->method('setVariable')->willReturnMap($variableMap);
|
||||
|
||||
/** @var UtilsServer|MockObject $utilsServerMock */
|
||||
$utilsServerMock = $this->getMockBuilder(UtilsServer::class)
|
||||
$utilsServerMock = $this->d3getMockBuilder(UtilsServer::class)
|
||||
->onlyMethods(['setOxCookie'])
|
||||
->getMock();
|
||||
$utilsServerMock->expects($setCookie)->method('setOxCookie')->with(
|
||||
@ -144,12 +144,12 @@ class d3_totp_LoginControllerTest extends d3TotpUnitTestCase
|
||||
);
|
||||
|
||||
/** @var d3_totp_LoginController|MockObject $sut */
|
||||
$sut = $this->getMockBuilder(LoginController::class)
|
||||
$sut = $this->d3getMockBuilder(LoginController::class)
|
||||
->onlyMethods(['d3TotpGetUtilsServer', 'd3TotpGetSession', 'd3totpAfterLoginSetLanguage'])
|
||||
->getMock();
|
||||
$sut->method('d3TotpGetUtilsServer')->willReturn($utilsServerMock);
|
||||
$sut->method('d3TotpGetSession')->willReturn($sessionMock);
|
||||
$sut->expects($this->once())->method('d3totpAfterLoginSetLanguage')->willReturn($sessionMock);
|
||||
$sut->expects($this->once())->method('d3totpAfterLoginSetLanguage');
|
||||
|
||||
$this->callMethod(
|
||||
$sut,
|
||||
@ -178,25 +178,25 @@ class d3_totp_LoginControllerTest extends d3TotpUnitTestCase
|
||||
public function canRunTotpAfterLoginSetLanguage($languageId)
|
||||
{
|
||||
/** @var Session|MockObject $sessionMock */
|
||||
$sessionMock = $this->getMockBuilder(Session::class)
|
||||
$sessionMock = $this->d3getMockBuilder(Session::class)
|
||||
->onlyMethods(['getVariable'])
|
||||
->getMock();
|
||||
$sessionMock->method('getVariable')->willReturn($languageId);
|
||||
|
||||
/** @var UtilsServer|MockObject $utilsServerMock */
|
||||
$utilsServerMock = $this->getMockBuilder(UtilsServer::class)
|
||||
$utilsServerMock = $this->d3getMockBuilder(UtilsServer::class)
|
||||
->onlyMethods(['setOxCookie'])
|
||||
->getMock();
|
||||
$utilsServerMock->expects($this->once())->method('setOxCookie');
|
||||
|
||||
/** @var Language|MockObject $langMock */
|
||||
$langMock = $this->getMockBuilder(Language::class)
|
||||
$langMock = $this->d3getMockBuilder(Language::class)
|
||||
->onlyMethods(['setTplLanguage'])
|
||||
->getMock();
|
||||
$langMock->expects($this->once())->method('setTplLanguage');
|
||||
|
||||
/** @var d3_totp_LoginController|MockObject $sut */
|
||||
$sut = $this->getMockBuilder(LoginController::class)
|
||||
$sut = $this->d3getMockBuilder(LoginController::class)
|
||||
->onlyMethods(['d3TotpGetUtilsServer', 'd3TotpGetSession', 'd3TotpGetLangObject'])
|
||||
->getMock();
|
||||
$sut->method('d3TotpGetUtilsServer')->willReturn($utilsServerMock);
|
||||
@ -233,20 +233,20 @@ class d3_totp_LoginControllerTest extends d3TotpUnitTestCase
|
||||
public function d3TotpLoginMissingTest($totpActive, $loggedin, $expected)
|
||||
{
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['isActive'])
|
||||
->getMock();
|
||||
$oTotpMock->method('isActive')->willReturn($totpActive);
|
||||
|
||||
/** @var Session|MockObject $oSessionMock */
|
||||
$oSessionMock = $this->getMockBuilder(Session::class)
|
||||
$oSessionMock = $this->d3getMockBuilder(Session::class)
|
||||
->onlyMethods(['getVariable'])
|
||||
->getMock();
|
||||
$oSessionMock->method('getVariable')->with(d3totp_conf::SESSION_ADMIN_AUTH)->willReturn($loggedin);
|
||||
|
||||
/** @var d3_totp_LoginController|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder(d3_totp_LoginController::class)
|
||||
$oControllerMock = $this->d3getMockBuilder(d3_totp_LoginController::class)
|
||||
->onlyMethods([
|
||||
'd3TotpGetSession',
|
||||
])
|
||||
@ -271,7 +271,7 @@ class d3_totp_LoginControllerTest extends d3TotpUnitTestCase
|
||||
public function d3TotpLoginMissingTestDataProvider(): array
|
||||
{
|
||||
return [
|
||||
'totp not active, not logged in'=> [false, false, false],
|
||||
'totp not active, not logged in' => [false, false, false],
|
||||
'totp active, logged in' => [true, true, false],
|
||||
'totp active, not logged in' => [true, false, true],
|
||||
'totp not active, logged in' => [false, true, false],
|
@ -13,11 +13,11 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\tests\unit\Modules\Application\Controller;
|
||||
namespace D3\Totp\Tests\Unit\Modules\Application\Controller;
|
||||
|
||||
use D3\Totp\Modules\Application\Controller\d3_totp_OrderController;
|
||||
use D3\Totp\Modules\Application\Controller\d3_totp_OrderController_parent;
|
||||
use D3\Totp\tests\unit\d3TotpUnitTestCase;
|
||||
use D3\Totp\Tests\Unit\d3TotpUnitTestCase;
|
||||
use OxidEsales\Eshop\Application\Controller\OrderController;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
|
@ -13,10 +13,10 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\tests\unit\Modules\Application\Controller;
|
||||
namespace D3\Totp\Tests\Unit\Modules\Application\Controller;
|
||||
|
||||
use D3\Totp\Modules\Application\Controller\d3_totp_PaymentController;
|
||||
use D3\Totp\tests\unit\d3TotpUnitTestCase;
|
||||
use D3\Totp\Tests\Unit\d3TotpUnitTestCase;
|
||||
use OxidEsales\Eshop\Application\Controller\PaymentController;
|
||||
|
||||
class d3_totp_PaymentControllerTest extends d3TotpUnitTestCase
|
@ -13,10 +13,10 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\tests\unit\Modules\Application\Controller;
|
||||
namespace D3\Totp\Tests\Unit\Modules\Application\Controller;
|
||||
|
||||
use D3\Totp\Modules\Application\Controller\d3_totp_UserController;
|
||||
use D3\Totp\tests\unit\d3TotpUnitTestCase;
|
||||
use D3\Totp\Tests\Unit\d3TotpUnitTestCase;
|
||||
use OxidEsales\Eshop\Application\Controller\UserController;
|
||||
|
||||
class d3_totp_UserControllerTest extends d3TotpUnitTestCase
|
@ -13,7 +13,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\tests\unit\Modules\Application\Controller;
|
||||
namespace D3\Totp\Tests\Unit\Modules\Application\Controller;
|
||||
|
||||
use D3\TestingTools\Development\CanAccessRestricted;
|
||||
use D3\Totp\Application\Model\d3totp;
|
||||
@ -29,20 +29,24 @@ trait d3_totp_getUserTestTrait
|
||||
{
|
||||
use CanAccessRestricted;
|
||||
|
||||
protected $userFixtureId = 'userIdFixture1';
|
||||
protected string $userFixtureId = 'userIdFixture1';
|
||||
|
||||
/** @var User */
|
||||
protected $userFixture;
|
||||
protected User $userFixture;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->userFixture = oxNew(User::class);
|
||||
$this->userFixture->setId($this->userFixtureId);
|
||||
$this->userFixture->assign(['oxlname' => __METHOD__]);
|
||||
$this->userFixture->assign(['oxlname' => __METHOD__, 'oxusername' => __METHOD__, 'oxpassword' => __METHOD__]);
|
||||
$this->userFixture->save();
|
||||
$this->userFixture->load($this->userFixtureId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function tearDown(): void
|
||||
{
|
||||
$this->userFixture->delete($this->userFixtureId);
|
||||
@ -55,14 +59,16 @@ trait d3_totp_getUserTestTrait
|
||||
* @covers \D3\Totp\Modules\Application\Controller\d3_totp_PaymentController::getUser
|
||||
* @covers \D3\Totp\Modules\Application\Controller\d3_totp_UserController::getUser
|
||||
*/
|
||||
public function getUserHasNoUser()
|
||||
public function getUserHasNoUser(): void
|
||||
{
|
||||
/** @var d3_totp_orderController|d3_totp_UserController|d3_totp_PaymentController|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder($this->sControllerClass)
|
||||
$oControllerMock = $this->d3getMockBuilder($this->sControllerClass)
|
||||
->onlyMethods(['d3GetTotpObject'])
|
||||
->getMock();
|
||||
$oControllerMock->expects($this->never())->method('d3GetTotpObject');
|
||||
|
||||
$this->setValue($oControllerMock, '_oActUser', false);
|
||||
|
||||
$this->assertFalse(
|
||||
$this->callMethod($oControllerMock, 'getUser')
|
||||
);
|
||||
@ -75,16 +81,16 @@ trait d3_totp_getUserTestTrait
|
||||
* @covers \D3\Totp\Modules\Application\Controller\d3_totp_PaymentController::getUser
|
||||
* @covers \D3\Totp\Modules\Application\Controller\d3_totp_UserController::getUser
|
||||
*/
|
||||
public function getUserTotpNotActive()
|
||||
public function getUserTotpNotActive(): void
|
||||
{
|
||||
/** @var Session|MockObject $oSessionMock */
|
||||
$oSessionMock = $this->getMockBuilder(Session::class)
|
||||
$oSessionMock = $this->d3getMockBuilder(Session::class)
|
||||
->onlyMethods(['getVariable'])
|
||||
->getMock();
|
||||
$oSessionMock->method('getVariable')->willReturn(true);
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods([
|
||||
'isActive',
|
||||
@ -92,10 +98,10 @@ trait d3_totp_getUserTestTrait
|
||||
])
|
||||
->getMock();
|
||||
$oTotpMock->method('isActive')->willReturn(false);
|
||||
$oTotpMock->method('loadByUserId')->willReturn(true);
|
||||
$oTotpMock->method('loadByUserId');
|
||||
|
||||
/** @var d3_totp_orderController|d3_totp_UserController|d3_totp_PaymentController|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder($this->sControllerClass)
|
||||
$oControllerMock = $this->d3getMockBuilder($this->sControllerClass)
|
||||
->onlyMethods([
|
||||
'd3GetTotpObject',
|
||||
'd3TotpGetSessionObject',
|
||||
@ -120,26 +126,26 @@ trait d3_totp_getUserTestTrait
|
||||
* @covers \D3\Totp\Modules\Application\Controller\d3_totp_PaymentController::getUser
|
||||
* @covers \D3\Totp\Modules\Application\Controller\d3_totp_UserController::getUser
|
||||
*/
|
||||
public function getUserTotpFinished()
|
||||
public function getUserTotpFinished(): void
|
||||
{
|
||||
/** @var Session|MockObject $oSessionMock */
|
||||
$oSessionMock = $this->getMockBuilder(Session::class)
|
||||
$oSessionMock = $this->d3getMockBuilder(Session::class)
|
||||
->onlyMethods(['getVariable'])
|
||||
->getMock();
|
||||
$oSessionMock->method('getVariable')->willReturn(true);
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'isActive',
|
||||
'loadByUserId',
|
||||
])
|
||||
->getMock();
|
||||
$oTotpMock->method('isActive')->willReturn(true);
|
||||
$oTotpMock->method('loadByUserId')->willReturn(true);
|
||||
$oTotpMock->method('loadByUserId');
|
||||
|
||||
/** @var d3_totp_orderController|d3_totp_UserController|d3_totp_PaymentController|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder($this->sControllerClass)
|
||||
$oControllerMock = $this->d3getMockBuilder($this->sControllerClass)
|
||||
->onlyMethods([
|
||||
'd3GetTotpObject',
|
||||
'd3TotpGetSessionObject',
|
||||
@ -164,16 +170,16 @@ trait d3_totp_getUserTestTrait
|
||||
* @covers \D3\Totp\Modules\Application\Controller\d3_totp_PaymentController::getUser
|
||||
* @covers \D3\Totp\Modules\Application\Controller\d3_totp_UserController::getUser
|
||||
*/
|
||||
public function getUserTotpNotFinished()
|
||||
public function getUserTotpNotFinished(): void
|
||||
{
|
||||
/** @var Session|MockObject $oSessionMock */
|
||||
$oSessionMock = $this->getMockBuilder(Session::class)
|
||||
$oSessionMock = $this->d3getMockBuilder(Session::class)
|
||||
->onlyMethods(['getVariable'])
|
||||
->getMock();
|
||||
$oSessionMock->method('getVariable')->willReturn(false);
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods([
|
||||
'isActive',
|
||||
@ -181,10 +187,10 @@ trait d3_totp_getUserTestTrait
|
||||
])
|
||||
->getMock();
|
||||
$oTotpMock->method('isActive')->willReturn(true);
|
||||
$oTotpMock->method('loadByUserId')->willReturn(true);
|
||||
$oTotpMock->method('loadByUserId');
|
||||
|
||||
/** @var d3_totp_orderController|d3_totp_UserController|d3_totp_PaymentController|MockObject $oControllerMock */
|
||||
$oControllerMock = $this->getMockBuilder($this->sControllerClass)
|
||||
$oControllerMock = $this->d3getMockBuilder($this->sControllerClass)
|
||||
->onlyMethods([
|
||||
'd3GetTotpObject',
|
||||
'd3TotpGetSessionObject',
|
||||
@ -208,7 +214,7 @@ trait d3_totp_getUserTestTrait
|
||||
* @covers \D3\Totp\Modules\Application\Controller\d3_totp_PaymentController::d3GetTotpObject
|
||||
* @covers \D3\Totp\Modules\Application\Controller\d3_totp_UserController::d3GetTotpObject
|
||||
*/
|
||||
public function d3GetTotpObjectReturnsRightObject()
|
||||
public function d3GetTotpObjectReturnsRightObject(): void
|
||||
{
|
||||
/** @var d3_totp_UserController|d3_totp_PaymentController|d3_totp_OrderController $oController */
|
||||
$oController = oxNew($this->sControllerClass);
|
||||
@ -226,7 +232,7 @@ trait d3_totp_getUserTestTrait
|
||||
* @covers \D3\Totp\Modules\Application\Controller\d3_totp_PaymentController::d3TotpGetSessionObject
|
||||
* @covers \D3\Totp\Modules\Application\Controller\d3_totp_UserController::d3TotpGetSessionObject
|
||||
*/
|
||||
public function d3GetSessionObjectReturnsRightObject()
|
||||
public function d3GetSessionObjectReturnsRightObject(): void
|
||||
{
|
||||
/** @var d3_totp_UserController|d3_totp_PaymentController|d3_totp_OrderController $oController */
|
||||
$oController = oxNew($this->sControllerClass);
|
@ -13,14 +13,15 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\tests\unit\Modules\Application\Model;
|
||||
namespace D3\Totp\Tests\Unit\Modules\Application\Model;
|
||||
|
||||
use D3\TestingTools\Development\CanAccessRestricted;
|
||||
use D3\Totp\Application\Model\d3totp;
|
||||
use D3\Totp\Application\Model\d3totp_conf;
|
||||
use D3\Totp\Modules\Application\Model\d3_totp_user;
|
||||
use D3\Totp\tests\unit\d3TotpUnitTestCase;
|
||||
use D3\Totp\Tests\Unit\d3TotpUnitTestCase;
|
||||
use OxidEsales\Eshop\Application\Model\User;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
use OxidEsales\Eshop\Core\Session;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use ReflectionException;
|
||||
@ -37,13 +38,13 @@ class d3_totp_userTest extends d3TotpUnitTestCase
|
||||
public function logout()
|
||||
{
|
||||
/** @var Session|MockObject $oSessionMock */
|
||||
$oSessionMock = $this->getMockBuilder(Session::class)
|
||||
$oSessionMock = $this->d3getMockBuilder(Session::class)
|
||||
->onlyMethods(['deleteVariable'])
|
||||
->getMock();
|
||||
$oSessionMock->expects($this->atLeast(2))->method('deleteVariable')->willReturn(true);
|
||||
|
||||
/** @var d3_totp_user|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(User::class)
|
||||
$oModelMock = $this->d3getMockBuilder(User::class)
|
||||
->onlyMethods(['d3TotpGetSession'])
|
||||
->getMock();
|
||||
$oModelMock->method('d3TotpGetSession')->willReturn($oSessionMock);
|
||||
@ -94,6 +95,31 @@ class d3_totp_userTest extends d3TotpUnitTestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Modules\Application\Model\d3_totp_user::d3getSessionedTotp
|
||||
*/
|
||||
public function d3getSessionedTotpReturnsRightInstance()
|
||||
{
|
||||
$sut = oxNew(User::class);
|
||||
|
||||
$otp = $this->callMethod(
|
||||
$sut,
|
||||
'd3getSessionedTotp'
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(
|
||||
d3totp::class,
|
||||
$otp
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
$otp,
|
||||
Registry::getSession()->getVariable(d3totp_conf::OTP_SESSION_VARNAME)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @param $currentUser
|
||||
@ -109,7 +135,7 @@ class d3_totp_userTest extends d3TotpUnitTestCase
|
||||
public function d3TotpGetCurrentUserTest($currentUser, $isAdmin, $adminAuth, $frontendAuth, $expected)
|
||||
{
|
||||
/** @var Session|MockObject $oSessionMock */
|
||||
$oSessionMock = $this->getMockBuilder(Session::class)
|
||||
$oSessionMock = $this->d3getMockBuilder(Session::class)
|
||||
->onlyMethods(['hasVariable', 'getVariable'])
|
||||
->getMock();
|
||||
$oSessionMock->expects($this->once())->method('hasVariable')->willReturn((bool) $currentUser);
|
||||
@ -122,7 +148,7 @@ class d3_totp_userTest extends d3TotpUnitTestCase
|
||||
$oSessionMock->method('getVariable')->willReturnMap($getVariableMap);
|
||||
|
||||
/** @var d3_totp_user|MockObject $oModelMock */
|
||||
$oModelMock = $this->getMockBuilder(User::class)
|
||||
$oModelMock = $this->d3getMockBuilder(User::class)
|
||||
->onlyMethods(['d3TotpGetSession', 'isAdmin'])
|
||||
->getMock();
|
||||
$oModelMock->method('d3TotpGetSession')->willReturn($oSessionMock);
|
@ -13,17 +13,19 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\tests\unit\Modules\Core;
|
||||
namespace D3\Totp\Tests\Unit\Modules\Core;
|
||||
|
||||
use D3\TestingTools\Development\CanAccessRestricted;
|
||||
use D3\Totp\Application\Model\d3totp;
|
||||
use D3\Totp\Application\Model\d3totp_conf;
|
||||
use D3\Totp\Modules\Core\d3_totp_utils;
|
||||
use D3\Totp\tests\unit\d3TotpUnitTestCase;
|
||||
use D3\Totp\Tests\Unit\d3TotpUnitTestCase;
|
||||
use OxidEsales\Eshop\Core\Config;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
use OxidEsales\Eshop\Core\Session;
|
||||
use OxidEsales\Eshop\Core\Utils;
|
||||
use OxidEsales\EshopCommunity\Internal\Framework\Module\Configuration\DataObject\ModuleConfiguration;
|
||||
use OxidEsales\EshopCommunity\Internal\Framework\Module\Setting\Setting;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use ReflectionException;
|
||||
|
||||
@ -61,24 +63,26 @@ class d3_totp_utilsTest extends d3TotpUnitTestCase
|
||||
Registry::getSession()->setVariable(d3totp_conf::OXID_ADMIN_AUTH, false);
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'loadByUserId',
|
||||
'isActive',
|
||||
])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$oTotpMock->method('loadByUserId')->willReturn(true);
|
||||
$oTotpMock->method('loadByUserId');
|
||||
$oTotpMock->method('isActive')->willReturn(false);
|
||||
|
||||
/** @var d3_totp_utils|MockObject $oCoreMock */
|
||||
$oCoreMock = $this->getMockBuilder(Utils::class)
|
||||
$oCoreMock = $this->d3getMockBuilder(Utils::class)
|
||||
->onlyMethods(['d3GetTotpObject'])
|
||||
->getMock();
|
||||
$oCoreMock->method('d3GetTotpObject')->willReturn($oTotpMock);
|
||||
|
||||
$this->_oCoreClass = $oCoreMock;
|
||||
|
||||
Registry::getSession()->setVariable(d3totp_conf::OXID_ADMIN_AUTH, 'oxid');
|
||||
|
||||
$this->assertFalse(
|
||||
$this->callMethod($this->_oCoreClass, 'checkAccessRights')
|
||||
);
|
||||
@ -94,18 +98,18 @@ class d3_totp_utilsTest extends d3TotpUnitTestCase
|
||||
Registry::getSession()->setVariable(d3totp_conf::OXID_ADMIN_AUTH, false);
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'loadByUserId',
|
||||
'isActive',
|
||||
])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$oTotpMock->method('loadByUserId')->willReturn(true);
|
||||
$oTotpMock->method('loadByUserId');
|
||||
$oTotpMock->method('isActive')->willReturn(false);
|
||||
|
||||
/** @var d3_totp_utils|MockObject $oCoreMock */
|
||||
$oCoreMock = $this->getMockBuilder(Utils::class)
|
||||
$oCoreMock = $this->d3getMockBuilder(Utils::class)
|
||||
->onlyMethods(['d3GetTotpObject', 'd3AuthHook', 'redirect', 'd3IsAdminForce2FA'])
|
||||
->getMock();
|
||||
$oCoreMock->method('d3GetTotpObject')->willReturn($oTotpMock);
|
||||
@ -116,6 +120,8 @@ class d3_totp_utilsTest extends d3TotpUnitTestCase
|
||||
|
||||
$this->_oCoreClass = $oCoreMock;
|
||||
|
||||
Registry::getSession()->setVariable(d3totp_conf::OXID_ADMIN_AUTH, 'oxid');
|
||||
|
||||
$this->assertTrue(
|
||||
$this->callMethod($this->_oCoreClass, 'checkAccessRights')
|
||||
);
|
||||
@ -131,18 +137,18 @@ class d3_totp_utilsTest extends d3TotpUnitTestCase
|
||||
Registry::getSession()->setVariable(d3totp_conf::OXID_ADMIN_AUTH, 'foo');
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'loadByUserId',
|
||||
'isActive',
|
||||
])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$oTotpMock->method('loadByUserId')->willReturn(true);
|
||||
$oTotpMock->method('loadByUserId');
|
||||
$oTotpMock->method('isActive')->willReturn(false);
|
||||
|
||||
/** @var d3_totp_utils|MockObject $oCoreMock */
|
||||
$oCoreMock = $this->getMockBuilder(Utils::class)
|
||||
$oCoreMock = $this->d3getMockBuilder(Utils::class)
|
||||
->onlyMethods([
|
||||
'd3GetTotpObject',
|
||||
'fetchRightsForUser',
|
||||
@ -168,24 +174,24 @@ class d3_totp_utilsTest extends d3TotpUnitTestCase
|
||||
Registry::getSession()->setVariable(d3totp_conf::OXID_ADMIN_AUTH, 'foo');
|
||||
|
||||
/** @var Session|MockObject $oSessionMock */
|
||||
$oSessionMock = $this->getMockBuilder(Session::class)
|
||||
$oSessionMock = $this->d3getMockBuilder(Session::class)
|
||||
->onlyMethods(['getVariable'])
|
||||
->getMock();
|
||||
$oSessionMock->method('getVariable')->will($this->onConsecutiveCalls('foo', true));
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'loadByUserId',
|
||||
'isActive',
|
||||
])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$oTotpMock->method('loadByUserId')->willReturn(true);
|
||||
$oTotpMock->method('loadByUserId');
|
||||
$oTotpMock->method('isActive')->willReturn(true);
|
||||
|
||||
/** @var d3_totp_utils|MockObject $oCoreMock */
|
||||
$oCoreMock = $this->getMockBuilder(Utils::class)
|
||||
$oCoreMock = $this->d3getMockBuilder(Utils::class)
|
||||
->onlyMethods([
|
||||
'd3GetTotpObject',
|
||||
'd3TotpGetSessionObject',
|
||||
@ -215,30 +221,30 @@ class d3_totp_utilsTest extends d3TotpUnitTestCase
|
||||
Registry::getSession()->setVariable(d3totp_conf::OXID_ADMIN_AUTH, 'foo');
|
||||
|
||||
/** @var Session|MockObject $oSessionMock */
|
||||
$oSessionMock = $this->getMockBuilder(Session::class)
|
||||
$oSessionMock = $this->d3getMockBuilder(Session::class)
|
||||
->onlyMethods(['getVariable'])
|
||||
->getMock();
|
||||
$oSessionMock->method('getVariable')->will($this->onConsecutiveCalls('foo', false));
|
||||
|
||||
/** @var Session|MockObject $oSessionMock */
|
||||
$oSessionMock = $this->getMockBuilder(Session::class)
|
||||
$oSessionMock = $this->d3getMockBuilder(Session::class)
|
||||
->onlyMethods(['getVariable'])
|
||||
->getMock();
|
||||
$oSessionMock->method('getVariable')->will($this->onConsecutiveCalls('foo', false));
|
||||
|
||||
/** @var d3totp|MockObject $oTotpMock */
|
||||
$oTotpMock = $this->getMockBuilder(d3totp::class)
|
||||
$oTotpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods([
|
||||
'loadByUserId',
|
||||
'isActive',
|
||||
])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$oTotpMock->method('loadByUserId')->willReturn(true);
|
||||
$oTotpMock->method('loadByUserId');
|
||||
$oTotpMock->method('isActive')->willReturn(true);
|
||||
|
||||
/** @var d3_totp_utils|MockObject $oCoreMock */
|
||||
$oCoreMock = $this->getMockBuilder(Utils::class)
|
||||
$oCoreMock = $this->d3getMockBuilder(Utils::class)
|
||||
->onlyMethods([
|
||||
'd3GetTotpObject',
|
||||
'd3TotpGetSessionObject',
|
||||
@ -309,18 +315,29 @@ class d3_totp_utilsTest extends d3TotpUnitTestCase
|
||||
public function d3IsAdminForce2FA($isAdmin, $hasConfig, $expected)
|
||||
{
|
||||
/** @var Config|MockObject $configMock */
|
||||
$configMock = $this->getMockBuilder(Config::class)
|
||||
$configMock = $this->d3getMockBuilder(Config::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['getConfigParam'])
|
||||
->getMock();
|
||||
$configMock->method('getConfigParam')->with($this->equalTo('D3_TOTP_ADMIN_FORCE_2FA'))->willReturn($hasConfig);
|
||||
|
||||
$settingMock = $this->d3getMockBuilder(Setting::class)
|
||||
->onlyMethods(['getValue'])
|
||||
->getMock();
|
||||
$settingMock->method('getValue')->willReturn($hasConfig);
|
||||
|
||||
$moduleConfigurationMock = $this->d3getMockBuilder(ModuleConfiguration::class)
|
||||
->onlyMethods(['getModuleSetting'])
|
||||
->getMock();
|
||||
$moduleConfigurationMock->method('getModuleSetting')->willReturn($settingMock);
|
||||
|
||||
/** @var d3_totp_utils|MockObject $oCoreMock */
|
||||
$oCoreMock = $this->getMockBuilder(Utils::class)
|
||||
->onlyMethods(['isAdmin', 'd3GetConfig'])
|
||||
$oCoreMock = $this->d3getMockBuilder(Utils::class)
|
||||
->onlyMethods(['isAdmin', 'd3GetConfig', 'getModuleConfiguration'])
|
||||
->getMock();
|
||||
$oCoreMock->method('isAdmin')->willReturn($isAdmin);
|
||||
$oCoreMock->method('d3GetConfig')->willReturn($configMock);
|
||||
$oCoreMock->method('getModuleConfiguration')->willReturn($moduleConfigurationMock);
|
||||
|
||||
$this->_oCoreClass = $oCoreMock;
|
||||
|
||||
@ -339,9 +356,9 @@ class d3_totp_utilsTest extends d3TotpUnitTestCase
|
||||
public function d3IsAdminForce2FADataProvider(): array
|
||||
{
|
||||
return [
|
||||
//'noAdmin, noConfig' => [false, false, false],
|
||||
//'noAdmin' => [false, true, false],
|
||||
//'noConfig' => [true, false, false],
|
||||
'noAdmin, noConfig' => [false, false, false],
|
||||
'noAdmin' => [false, true, false],
|
||||
'noConfig' => [true, false, false],
|
||||
'passed' => [true, true, true],
|
||||
];
|
||||
}
|
||||
@ -365,6 +382,23 @@ class d3_totp_utilsTest extends d3TotpUnitTestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Modules\Core\d3_totp_utils::getModuleConfiguration
|
||||
*/
|
||||
public function canGetModuleConfiguration()
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
ModuleConfiguration::class,
|
||||
$this->callMethod(
|
||||
$this->_oCoreClass,
|
||||
'getModuleConfiguration'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
@ -19,15 +19,16 @@ use D3\TestingTools\Development\CanAccessRestricted;
|
||||
use D3\Totp\Application\Model\d3totp;
|
||||
use D3\Totp\Modules\Application\Model\d3_totp_user;
|
||||
use D3\Totp\Modules\Core\totpSystemEventHandler;
|
||||
use D3\Totp\Tests\Unit\d3TotpUnitTestCase;
|
||||
use OxidEsales\Eshop\Application\Model\User;
|
||||
use OxidEsales\Eshop\Core\Session;
|
||||
use OxidEsales\Eshop\Core\SystemEventHandler;
|
||||
use OxidEsales\Eshop\Core\Utils;
|
||||
use OxidEsales\TestingLibrary\UnitTestCase;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionException;
|
||||
|
||||
class totpSystemEventHandlerTest extends UnitTestCase
|
||||
class totpSystemEventHandlerTest extends d3TotpUnitTestCase
|
||||
{
|
||||
use CanAccessRestricted;
|
||||
|
||||
@ -40,12 +41,11 @@ class totpSystemEventHandlerTest extends UnitTestCase
|
||||
public function runOnAdminLogin()
|
||||
{
|
||||
/** @var totpSystemEventHandler|MockObject $sut */
|
||||
$sut = $this->getMockBuilder(SystemEventHandler::class)
|
||||
->onlyMethods(['d3CallMockableFunction', 'd3requestTotp'])
|
||||
$sut = $this->d3getMockBuilder(SystemEventHandler::class)
|
||||
->onlyMethods(['d3requestTotp'])
|
||||
->getMock();
|
||||
|
||||
$sut->method('d3CallMockableFunction')->willReturn(true);
|
||||
$sut->expects($this->once())->method('d3requestTotp')->willReturn(true);
|
||||
$sut->expects($this->once())->method('d3requestTotp');
|
||||
|
||||
$this->callMethod(
|
||||
$sut,
|
||||
@ -68,31 +68,31 @@ class totpSystemEventHandlerTest extends UnitTestCase
|
||||
public function canRequestTotp($totpMissing, $doLogout, $doRedirect)
|
||||
{
|
||||
/** @var Session|MockObject $sessionMock */
|
||||
$sessionMock = $this->getMockBuilder(Session::class)
|
||||
$sessionMock = $this->d3getMockBuilder(Session::class)
|
||||
->onlyMethods(['getVariable'])
|
||||
->getMock();
|
||||
$sessionMock->method('getVariable')->willReturn('myUserId');
|
||||
|
||||
/** @var d3_totp_user|MockObject $userMock */
|
||||
$userMock = $this->getMockBuilder(User::class)
|
||||
$userMock = $this->d3getMockBuilder(User::class)
|
||||
->onlyMethods(['logout'])
|
||||
->getMock();
|
||||
$userMock->expects($doLogout)->method('logout')->willReturn(true);
|
||||
|
||||
/** @var Utils|MockObject $utilsMock */
|
||||
$utilsMock = $this->getMockBuilder(Utils::class)
|
||||
$utilsMock = $this->d3getMockBuilder(Utils::class)
|
||||
->onlyMethods(['redirect'])
|
||||
->getMock();
|
||||
$utilsMock->expects($doRedirect)->method('redirect')->willReturn(true);
|
||||
|
||||
/** @var d3totp|MockObject $totpMock */
|
||||
$totpMock = $this->getMockBuilder(d3totp::class)
|
||||
$totpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['loadByUserId'])
|
||||
->getMock();
|
||||
$totpMock->expects($this->atLeastOnce())->method('loadByUserId')->with('myUserId')->willReturn(1);
|
||||
$totpMock->expects($this->atLeastOnce())->method('loadByUserId')->with('myUserId');
|
||||
|
||||
/** @var totpSystemEventHandler|MockObject $sut */
|
||||
$sut = $this->getMockBuilder(SystemEventHandler::class)
|
||||
$sut = $this->d3getMockBuilder(SystemEventHandler::class)
|
||||
->onlyMethods(['d3GetTotpObject', 'd3TotpGetSession', 'd3TotpLoginMissing',
|
||||
'd3TotpGetUserObject', 'getUtilsObject', ])
|
||||
->getMock();
|
||||
@ -212,19 +212,19 @@ class totpSystemEventHandlerTest extends UnitTestCase
|
||||
public function checkTotpLoginMissing($isActive, $hasTotpAuth, $expected)
|
||||
{
|
||||
/** @var Session|MockObject $sessionMock */
|
||||
$sessionMock = $this->getMockBuilder(Session::class)
|
||||
$sessionMock = $this->d3getMockBuilder(Session::class)
|
||||
->onlyMethods(['getVariable'])
|
||||
->getMock();
|
||||
$sessionMock->method('getVariable')->willReturn($hasTotpAuth);
|
||||
|
||||
/** @var d3totp|MockObject $totpMock */
|
||||
$totpMock = $this->getMockBuilder(d3totp::class)
|
||||
$totpMock = $this->d3getMockBuilder(d3totp::class)
|
||||
->onlyMethods(['isActive'])
|
||||
->getMock();
|
||||
$totpMock->method('isActive')->willReturn($isActive);
|
||||
|
||||
/** @var totpSystemEventHandler|MockObject $sut */
|
||||
$sut = $this->getMockBuilder(SystemEventHandler::class)
|
||||
$sut = $this->d3getMockBuilder(SystemEventHandler::class)
|
||||
->onlyMethods(['d3TotpGetSession'])
|
||||
->getMock();
|
||||
$sut->method('d3TotpGetSession')->willReturn($sessionMock);
|
387
Tests/Unit/Setup/ActionsTest.php
Normal file
387
Tests/Unit/Setup/ActionsTest.php
Normal file
@ -0,0 +1,387 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Tests\Unit\Setup;
|
||||
|
||||
use D3\OxidServiceBridges\Internal\Framework\Templating\Cache\ShopTemplateCacheServiceBridge;
|
||||
use D3\TestingTools\Development\CanAccessRestricted;
|
||||
use D3\Totp\Setup\Actions;
|
||||
use D3\Totp\Tests\Unit\d3TotpUnitTestCase;
|
||||
use Generator;
|
||||
use OxidEsales\DoctrineMigrationWrapper\Migrations;
|
||||
use OxidEsales\DoctrineMigrationWrapper\MigrationsBuilder;
|
||||
use OxidEsales\Eshop\Core\DbMetaDataHandler;
|
||||
use OxidEsales\Eshop\Core\Exception\StandardException;
|
||||
use OxidEsales\Eshop\Core\SeoEncoder;
|
||||
use OxidEsales\Eshop\Core\Utils;
|
||||
use OxidEsales\EshopCommunity\Internal\Framework\Logger\Wrapper\LoggerWrapper;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\MockObject\Rule\InvokedCount;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ReflectionException;
|
||||
use Symfony\Component\DependencyInjection\Container;
|
||||
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
|
||||
|
||||
class ActionsTest extends d3TotpUnitTestCase
|
||||
{
|
||||
use CanAccessRestricted;
|
||||
|
||||
/** @var Actions */
|
||||
protected Actions $_sut;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->_sut = oxNew(Actions::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Setup\Actions::getMigrationsBuilder
|
||||
*/
|
||||
public function canGetMigrationsBuilder(): void
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
MigrationsBuilder::class,
|
||||
$this->callMethod(
|
||||
$this->_sut,
|
||||
'getMigrationsBuilder'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Setup\Actions::runModuleMigrations
|
||||
*/
|
||||
public function canRunModuleMigrations(): void
|
||||
{
|
||||
$migrationsMock = $this->d3getMockBuilder(Migrations::class)
|
||||
->onlyMethods(['execute'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$migrationsMock->expects($this->once())->method('execute');
|
||||
|
||||
$migrationsBuilderMock = $this->d3getMockBuilder(MigrationsBuilder::class)
|
||||
->onlyMethods(['build'])
|
||||
->getMock();
|
||||
$migrationsBuilderMock->method("build")->willReturn($migrationsMock);
|
||||
|
||||
$sutMock = $this->d3getMockBuilder(Actions::class)
|
||||
->onlyMethods(['getMigrationsBuilder'])
|
||||
->getMock();
|
||||
$sutMock->method('getMigrationsBuilder')->willReturn($migrationsBuilderMock);
|
||||
|
||||
$this->callMethod(
|
||||
$sutMock,
|
||||
'runModuleMigrations'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Setup\Actions::getDbMetaDataHandler
|
||||
*/
|
||||
public function canGetDbMetaDataHandler(): void
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
DbMetaDataHandler::class,
|
||||
$this->callMethod(
|
||||
$this->_sut,
|
||||
'getDbMetaDataHandler'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Setup\Actions::regenerateViews
|
||||
*/
|
||||
public function canRegenerateViews(): void
|
||||
{
|
||||
$dbMetaDataHandlerMock = $this->d3getMockBuilder(DbMetaDataHandler::class)
|
||||
->onlyMethods(['updateViews'])
|
||||
->getMock();
|
||||
$dbMetaDataHandlerMock->expects($this->once())->method("updateViews");
|
||||
|
||||
$sutMock = $this->d3getMockBuilder(Actions::class)
|
||||
->onlyMethods(['getDbMetaDataHandler'])
|
||||
->getMock();
|
||||
$sutMock->method('getDbMetaDataHandler')->willReturn($dbMetaDataHandlerMock);
|
||||
|
||||
$this->callMethod(
|
||||
$sutMock,
|
||||
'regenerateViews'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Setup\Actions::getUtils
|
||||
*/
|
||||
public function canGetUtils(): void
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
Utils::class,
|
||||
$this->callMethod(
|
||||
$this->_sut,
|
||||
'getUtils'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Setup\Actions::getLogger
|
||||
*/
|
||||
public function canGetLogger(): void
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
LoggerInterface::class,
|
||||
$this->callMethod(
|
||||
$this->_sut,
|
||||
'getLogger'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @param string|null $thrownException
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Setup\Actions::clearCache
|
||||
* @dataProvider canClearCacheDataProvider
|
||||
*/
|
||||
public function canClearCache(string $thrownException = null): void
|
||||
{
|
||||
$shopTemplateCacheServiceMock = $this->d3getMockBuilder(ShopTemplateCacheServiceBridge::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['invalidateCache'])
|
||||
->getMock();
|
||||
$shopTemplateCacheServiceMock->expects($this->exactly((int) !$thrownException))->method("invalidateCache");
|
||||
|
||||
$DIContainerMock = $this->d3getMockBuilder(Container::class)
|
||||
->onlyMethods(['get', 'has'])
|
||||
->getMock();
|
||||
if ($thrownException === null) {
|
||||
$DIContainerMock->method('get')->willReturn($shopTemplateCacheServiceMock);
|
||||
} else {
|
||||
/** @var ServiceNotFoundException|MockObject $exceptionMock */
|
||||
$exceptionMock = $this->d3getMockBuilder(ServiceNotFoundException::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$DIContainerMock->method('get')->willThrowException($exceptionMock);
|
||||
}
|
||||
|
||||
$utilsMock = $this->d3getMockBuilder(Utils::class)
|
||||
->onlyMethods(['resetLanguageCache'])
|
||||
->getMock();
|
||||
$utilsMock->expects($this->exactly((int) !$thrownException))->method('resetLanguageCache');
|
||||
|
||||
$loggerMock = $this->d3getMockBuilder(LoggerWrapper::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['error'])
|
||||
->getMock();
|
||||
$loggerMock->expects($this->exactly((int) $thrownException))->method('error');
|
||||
|
||||
$sutMock = $this->d3getMockBuilder(Actions::class)
|
||||
->onlyMethods(['getDIContainer', 'getUtils', 'getLogger'])
|
||||
->getMock();
|
||||
$sutMock->method('getDIContainer')->willReturn($DIContainerMock);
|
||||
$sutMock->method('getUtils')->willReturn($utilsMock);
|
||||
$sutMock->method('getLogger')->willReturn($loggerMock);
|
||||
|
||||
$this->callMethod(
|
||||
$sutMock,
|
||||
'clearCache'
|
||||
);
|
||||
}
|
||||
|
||||
public static function canClearCacheDataProvider(): Generator
|
||||
{
|
||||
yield 'passed' => [];
|
||||
yield 'container exception' => [ServiceNotFoundException::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @param bool $hasSeoUrls
|
||||
* @param InvokedCount $createCount
|
||||
* @param bool $throwException
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Setup\Actions::seoUrl
|
||||
* @dataProvider canHandleSeoUrlsDataProvider
|
||||
*/
|
||||
public function canHandleSeoUrls(bool $hasSeoUrls, InvokedCount $createCount, bool $throwException = false): void
|
||||
{
|
||||
$loggerMock = $this->d3getMockBuilder(LoggerWrapper::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['error'])
|
||||
->getMock();
|
||||
$loggerMock->expects($this->exactly((int) $throwException))->method('error');
|
||||
|
||||
$sutMock = $this->d3getMockBuilder(Actions::class)
|
||||
->onlyMethods(['hasSeoUrls', 'createSeoUrls', 'getLogger'])
|
||||
->getMock();
|
||||
$sutMock->method('hasSeoUrls')->willReturn($hasSeoUrls);
|
||||
$sutMock->method('getLogger')->willReturn($loggerMock);
|
||||
if ($throwException) {
|
||||
$sutMock->expects($createCount)->method('createSeoUrls')->willThrowException(new StandardException());
|
||||
} else {
|
||||
$sutMock->expects($createCount)->method('createSeoUrls');
|
||||
}
|
||||
|
||||
$this->callMethod(
|
||||
$sutMock,
|
||||
'seoUrl'
|
||||
);
|
||||
}
|
||||
|
||||
public static function canHandleSeoUrlsDataProvider(): Generator
|
||||
{
|
||||
yield 'urls exists' => [true, self::never()];
|
||||
yield 'urls not exists' => [false, self::once()];
|
||||
yield 'throw exception' => [false, self::once(), true];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @param bool $hasSeoUrl
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Setup\Actions::hasSeoUrls
|
||||
* @dataProvider hasSeoUrlsDataProvider
|
||||
*/
|
||||
public function testHasSeoUrls(bool $hasSeoUrl): void
|
||||
{
|
||||
$sutMock = $this->d3getMockBuilder(Actions::class)
|
||||
->onlyMethods(['hasSeoUrl'])
|
||||
->getMock();
|
||||
$sutMock->method('hasSeoUrl')->willReturn($hasSeoUrl);
|
||||
|
||||
$this->assertSame(
|
||||
$hasSeoUrl,
|
||||
$this->callMethod(
|
||||
$sutMock,
|
||||
'hasSeoUrls',
|
||||
[oxNew(SeoEncoder::class)]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static function hasSeoUrlsDataProvider(): Generator
|
||||
{
|
||||
yield 'url exists' => [true];
|
||||
yield 'url not exists' => [false];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @param string $staticUrl
|
||||
* @param bool $expected
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Setup\Actions::hasSeoUrl
|
||||
* @dataProvider hasSeoUrlDataProvider
|
||||
*/
|
||||
public function testHasSeoUrl(string $staticUrl, bool $expected)
|
||||
{
|
||||
$seoEncoderMock = $this->d3getMockBuilder(SeoEncoder::class)
|
||||
->onlyMethods(['getStaticUrl'])
|
||||
->getMock();
|
||||
$seoEncoderMock->expects($this->once())->method('getStaticUrl')->willReturn($staticUrl);
|
||||
|
||||
$this->assertSame(
|
||||
$expected,
|
||||
$this->callMethod(
|
||||
$this->_sut,
|
||||
'hasSeoUrl',
|
||||
[$seoEncoderMock, 'item', 0]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static function hasSeoUrlDataProvider(): Generator
|
||||
{
|
||||
yield 'passed' => ['staticFixture', true];
|
||||
yield 'failed' => ['', false];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @param bool $hasSeoUrl
|
||||
* @param InvokedCount $expectedCount
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Setup\Actions::createSeoUrls
|
||||
* @dataProvider canCreateSeoUrlsDataProvider
|
||||
*/
|
||||
public function canCreateSeoUrls(bool $hasSeoUrl, invokedCount $expectedCount): void
|
||||
{
|
||||
$seoEncoderMock = $this->d3getMockBuilder(SeoEncoder::class)
|
||||
->onlyMethods(['addSeoEntry'])
|
||||
->getMock();
|
||||
$seoEncoderMock->expects($expectedCount)->method('addSeoEntry')->willReturn('addSeoEntry');
|
||||
|
||||
$sutMock = $this->d3getMockBuilder(Actions::class)
|
||||
->onlyMethods(['hasSeoUrl'])
|
||||
->getMock();
|
||||
$sutMock->method('hasSeoUrl')->willReturn($hasSeoUrl);
|
||||
|
||||
$this->callMethod(
|
||||
$sutMock,
|
||||
'createSeoUrls',
|
||||
[$seoEncoderMock]
|
||||
);
|
||||
}
|
||||
|
||||
public static function canCreateSeoUrlsDataProvider(): Generator
|
||||
{
|
||||
yield 'url not exists' => [false, self::exactly(2)];
|
||||
yield 'url exists' => [true, self::never()];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
* @covers \D3\Totp\Setup\Actions::getDIContainer
|
||||
*/
|
||||
public function canGetDIContainer(): void
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
ContainerInterface::class,
|
||||
$this->callMethod(
|
||||
$this->_sut,
|
||||
'getDIContainer'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
33
Tests/Unit/d3TotpUnitTestCase.php
Normal file
33
Tests/Unit/d3TotpUnitTestCase.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Tests\Unit;
|
||||
|
||||
use OxidEsales\Eshop\Core\Registry as RegistryAlias;
|
||||
use PHPUnit\Framework\MockObject\MockBuilder;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
abstract class d3TotpUnitTestCase extends TestCase
|
||||
{
|
||||
public function d3getMockBuilder($className): MockBuilder
|
||||
{
|
||||
if (strpos($className, '\\') === false) {
|
||||
$className = strtolower($className);
|
||||
}
|
||||
$editionClassName = RegistryAlias::getUtilsObject()->getClassName($className);
|
||||
|
||||
return parent::getMockBuilder($editionClassName);
|
||||
}
|
||||
}
|
41
Tests/phpunit.xml
Normal file
41
Tests/phpunit.xml
Normal file
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
backupGlobals="true"
|
||||
bootstrap="../../../../source/bootstrap.php"
|
||||
colors="true"
|
||||
backupStaticAttributes="false"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="false"
|
||||
convertWarningsToExceptions="false"
|
||||
forceCoversAnnotation="false"
|
||||
processIsolation="false"
|
||||
stopOnError="false"
|
||||
stopOnFailure="false"
|
||||
stopOnIncomplete="false"
|
||||
stopOnSkipped="false"
|
||||
beStrictAboutTestsThatDoNotTestAnything="false"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.6/phpunit.xsd">
|
||||
<testsuites>
|
||||
<testsuite name="Unit">
|
||||
<directory>Unit/</directory>
|
||||
</testsuite>
|
||||
<!-- <testsuite name="Integration">-->
|
||||
<!-- <directory>integration/</directory>-->
|
||||
<!-- </testsuite>-->
|
||||
</testsuites>
|
||||
<coverage includeUncoveredFiles="true" processUncoveredFiles="true">
|
||||
<include>
|
||||
<directory suffix=".php">../</directory>
|
||||
</include>
|
||||
<exclude>
|
||||
<file>../.php-cs-fixer.php</file>
|
||||
<file>../IntelliSenseHelper.php</file>
|
||||
<file>../rector.php</file>
|
||||
<file>../metadata.php</file>
|
||||
<directory>../Tests/</directory>
|
||||
</exclude>
|
||||
</coverage>
|
||||
<php>
|
||||
<const name="OXID_PHP_UNIT" value="true"/>
|
||||
</php>
|
||||
</phpunit>
|
7
assets/out/src/css/bootstrap.min.css
vendored
Normal file
7
assets/out/src/css/bootstrap.min.css
vendored
Normal file
Bestand-diff onderdrukt omdat een of meer regels te lang zijn
93
assets/picture.svg
Normal file
93
assets/picture.svg
Normal file
@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="201px" height="124px" viewBox="0 0 201 124" enable-background="new 0 0 201 124" xml:space="preserve">
|
||||
<g>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="47.0591" y1="67.5117" x2="47.0591" y2="54.6143">
|
||||
<stop offset="0.0056" style="stop-color:#3266A9"/>
|
||||
<stop offset="1" style="stop-color:#0099FF"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_1_)" d="M50.282,55.502c-0.784-0.592-2.104-0.888-3.961-0.888h-1.376l-2.283,12.898h1.779
|
||||
c3.76,0,6.032-2.245,6.815-6.733c0.134-0.871,0.202-1.642,0.202-2.313C51.457,57.081,51.064,56.093,50.282,55.502z"/>
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="65.9609" y1="49.104" x2="65.9609" y2="36.9434">
|
||||
<stop offset="0.0056" style="stop-color:#3266A9"/>
|
||||
<stop offset="1" style="stop-color:#0099FF"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_2_)" d="M65.72,40.482c1.074,0,1.611,0.381,1.611,1.143c0,0.701-0.321,1.201-0.962,1.5
|
||||
c-0.209,0.119-0.366,0.194-0.471,0.224c-0.065,0.019-0.158,0.037-0.271,0.056c1.98,1.621,3.702,3.544,5.097,5.699
|
||||
c0.117-0.321,0.21-0.658,0.277-1.013l0.09-1.008c0-1.223-0.568-2.081-1.701-2.574c0.776-0.402,1.376-0.94,1.801-1.611
|
||||
c0.425-0.672,0.638-1.418,0.638-2.239c0-0.642-0.198-1.265-0.593-1.868c-0.396-0.605-0.98-1.049-1.757-1.333
|
||||
c-0.433-0.193-0.876-0.328-1.332-0.402c-0.456-0.075-1.003-0.113-1.645-0.113c-0.82,0-1.663,0.124-2.529,0.37
|
||||
c-0.865,0.246-1.6,0.563-2.204,0.952s-1.13,0.907-1.578,1.557c-0.036,0.052-0.066,0.109-0.101,0.163
|
||||
c1.196,0.534,2.341,1.163,3.426,1.874C63.947,40.943,64.68,40.482,65.72,40.482z"/>
|
||||
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="50.0576" y1="87.0566" x2="50.0576" y2="37.8525">
|
||||
<stop offset="0.0056" style="stop-color:#3266A9"/>
|
||||
<stop offset="1" style="stop-color:#0099FF"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_3_)" d="M70.725,49.104c-0.433,1.189-1.208,2.147-2.331,2.871c-1.425,0.918-3.182,1.377-5.271,1.377
|
||||
c-1.179,0-2.175-0.176-2.988-0.525c-0.813-0.35-1.444-0.864-1.891-1.543c-0.448-0.678-0.671-1.481-0.671-2.405l0.022-0.694
|
||||
l0.156-0.693h4.367l-0.028,0.179v0.179v0.246c0,1.164,0.628,1.746,1.884,1.746c0.635,0,1.201-0.217,1.696-0.649
|
||||
c0.495-0.434,0.742-0.94,0.742-1.522c0-0.522-0.194-0.887-0.582-1.097c-0.329-0.208-1.007-0.313-2.036-0.313l0.47-2.754
|
||||
l1.141-0.067c0.083-0.011,0.154-0.022,0.221-0.033c-0.674-0.551-1.378-1.067-2.11-1.546c-0.044,0.096-0.087,0.195-0.125,0.302
|
||||
h-4.185c0.192-0.837,0.49-1.56,0.884-2.175c-3.064-1.372-6.46-2.133-10.034-2.133c-13.588,0-24.603,11.014-24.603,24.601
|
||||
c0,13.59,11.015,24.604,24.603,24.604S74.66,76.043,74.66,62.453C74.66,57.532,73.214,52.949,70.725,49.104z M59.413,59.233
|
||||
l-0.168,1.275c-0.538,2.953-1.511,5.404-2.921,7.35c-1.298,1.835-3.016,3.179-5.153,4.028c-2.138,0.851-4.494,1.274-7.067,1.274
|
||||
H33.731l4.264-24.198h10.441c1.141,0,2.204,0.073,3.189,0.218c0.984,0.146,1.868,0.364,2.651,0.655
|
||||
c1.611,0.537,2.887,1.471,3.827,2.802c0.94,1.332,1.41,2.992,1.41,4.984L59.413,59.233z"/>
|
||||
</g>
|
||||
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="107.3027" y1="105.8555" x2="93.0727" y2="16.0106">
|
||||
<stop offset="0" style="stop-color:#B2B2B2;stop-opacity:0"/>
|
||||
<stop offset="0.2" style="stop-color:#B2B2B2"/>
|
||||
<stop offset="0.8" style="stop-color:#B2B2B2"/>
|
||||
<stop offset="1" style="stop-color:#B2B2B2;stop-opacity:0"/>
|
||||
</linearGradient>
|
||||
<rect x="99.875" y="14.933" fill="url(#SVGID_4_)" width="0.625" height="92"/>
|
||||
<script xmlns=""></script>
|
||||
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)">
|
||||
<path d="M1280.234,4748.803c-7.021-2.446-12.822-6.802-17.158-12.822c-7.041-9.702-6.592,5.796-6.367-238.306l0.332-222.49
|
||||
l2.344-5.117c3.467-7.48,9.697-13.828,16.953-17.393l6.357-3.135h104.258h104.268l6.367,3.135
|
||||
c7.256,3.564,13.486,9.912,16.953,17.393c2.334,5.117,2.334,5.801,2.666,97.344l0.234,92.344h109.502
|
||||
c82.861,0,110.518,0.342,113.965,1.348c6.025,1.777,14.277,10.039,16.064,16.045c1.895,6.592,1.895,159.365,0,165.942
|
||||
c-1.787,6.021-10.039,14.277-16.064,16.06c-3.447,1.006-30.996,1.343-113.965,1.343h-109.395l-0.342,32.109
|
||||
c-0.332,29.888-0.439,32.349-2.666,37.129c-3.467,7.48-9.697,13.838-16.953,17.402l-6.367,3.125l-102.588,0.225
|
||||
C1292.392,4750.698,1285.713,4750.595,1280.234,4748.803z M1417.637,4727.836c3.789-1.782,7.236-7.241,7.236-11.367
|
||||
c0-3.564-4.795-9.585-8.916-11.045c-2.568-0.889-12.49-1.343-29.004-1.343c-28.096,0-31.445,0.679-35.342,6.475
|
||||
c-3.018,4.458-3.242,6.802-0.781,11.821c3.125,6.689,5.684,7.251,35.908,7.251
|
||||
C1409.267,4729.628,1414.609,4729.292,1417.637,4727.836z M1495.127,4671.635v-11.143h-68.242c-46.621,0-69.805-0.449-72.813-1.23
|
||||
c-5.918-1.68-14.395-10.151-16.064-16.06c-1.67-6.25-1.67-159.922,0-166.152c1.67-5.908,10.146-14.395,16.064-16.064
|
||||
c2.666-0.674,14.375-1.23,28.652-1.23h24.189l0.342-22.969l0.342-22.969l3.672-3.242c4.248-3.789,10.264-4.336,14.717-1.23
|
||||
c1.563,1.006,13.604,12.822,26.66,26.094l23.857,24.316h9.375h9.248v-71.357v-71.377h-108.174h-108.174v182.876v182.896h108.174
|
||||
h108.174V4671.635z M1435.576,4608.75l3.799-3.789v-11.27c0-6.25,0.215-11.27,0.557-11.27s4.58,2.236,9.355,5.02
|
||||
c9.043,5.244,13.281,6.025,18.525,3.691c6.25-2.798,8.145-12.837,3.447-18.408c-1.436-1.67-6.23-5.132-10.586-7.578
|
||||
c-4.355-2.461-7.92-4.692-7.92-5.02c0-0.342,4.131-2.788,9.033-5.586c9.473-5.229,12.158-8.354,12.158-14.38
|
||||
c0-4.79-1.895-8.369-5.586-10.488c-5.225-3.11-9.805-2.334-18.945,3.125c-4.58,2.783-8.701,5.02-9.141,5.02
|
||||
c-0.566,0-0.898-5.02-0.898-11.255v-11.27l-3.799-3.789c-3.115-3.125-4.678-3.804-8.467-3.804s-5.361,0.679-8.467,3.804
|
||||
l-3.809,3.789v11.27c0,6.235-0.322,11.255-0.771,11.255c-0.557,0-4.688-2.236-9.268-5.02c-9.131-5.459-13.711-6.235-18.945-3.125
|
||||
c-3.691,2.119-5.586,5.698-5.586,10.488c0,6.025,2.676,9.15,12.158,14.38c4.912,2.798,9.043,5.244,9.043,5.586
|
||||
c0,0.327-3.564,2.559-7.92,5.02c-4.355,2.446-9.15,5.908-10.605,7.578c-4.678,5.571-2.783,15.61,3.467,18.408
|
||||
c5.244,2.334,9.473,1.553,18.506-3.691c4.795-2.783,9.033-5.02,9.375-5.02c0.322,0,0.547,5.02,0.547,11.27v11.27l3.809,3.789
|
||||
c3.105,3.11,4.678,3.789,8.467,3.789S1432.461,4611.86,1435.576,4608.75z M1556.025,4608.75l3.789-3.789v-11.27
|
||||
c0-6.25,0.225-11.27,0.557-11.27s4.57,2.236,9.365,5.02c9.033,5.244,13.271,6.025,18.506,3.691
|
||||
c6.25-2.798,8.145-12.837,3.467-18.408c-1.455-1.67-6.25-5.132-10.596-7.578c-4.355-2.461-7.91-4.692-7.91-5.02
|
||||
c0-0.342,4.111-2.788,9.014-5.586c9.492-5.229,12.158-8.354,12.158-14.38c0-4.79-1.895-8.369-5.566-10.488
|
||||
c-5.244-3.11-9.814-2.334-18.965,3.125c-4.57,2.783-8.691,5.02-9.141,5.02c-0.557,0-0.889-5.02-0.889-11.255v-11.27l-3.789-3.789
|
||||
c-3.125-3.125-4.688-3.804-8.486-3.804c-3.789,0-5.342,0.679-8.467,3.804l-3.789,3.789v11.27c0,6.235-0.342,11.255-0.781,11.255
|
||||
c-0.566,0-4.697-2.236-9.258-5.02c-9.15-5.459-13.721-6.235-18.965-3.125c-3.672,2.119-5.566,5.698-5.566,10.488
|
||||
c0,6.025,2.676,9.15,12.148,14.38c4.902,2.798,9.033,5.244,9.033,5.586c0,0.327-3.564,2.559-7.92,5.02
|
||||
c-4.346,2.446-9.131,5.908-10.586,7.578c-4.688,5.571-2.793,15.61,3.457,18.408c5.234,2.334,9.473,1.553,18.506-3.691
|
||||
c4.795-2.783,9.033-5.02,9.365-5.02c0.342,0,0.566,5.02,0.566,11.27v11.27l3.789,3.789c3.125,3.11,4.678,3.789,8.467,3.789
|
||||
C1551.338,4612.539,1552.9,4611.86,1556.025,4608.75z M1676.455,4608.75l3.799-3.789v-11.27c0-6.25,0.215-11.27,0.557-11.27
|
||||
c0.332,0,4.58,2.236,9.355,5.02c9.033,5.244,13.271,6.025,18.525,3.691c6.24-2.798,8.135-12.837,3.447-18.408
|
||||
c-1.445-1.67-6.24-5.132-10.596-7.578c-4.346-2.461-7.91-4.692-7.91-5.02c0-0.342,4.121-2.788,9.033-5.586
|
||||
c9.473-5.229,12.158-8.354,12.158-14.38c0-4.79-1.895-8.369-5.586-10.488c-5.234-3.11-9.814-2.334-18.945,3.125
|
||||
c-4.58,2.783-8.711,5.02-9.15,5.02c-0.566,0-0.889-5.02-0.889-11.255v-11.27l-3.799-3.789c-3.115-3.125-4.688-3.804-8.477-3.804
|
||||
s-5.352,0.679-8.467,3.804l-3.799,3.789v11.27c0,6.235-0.332,11.255-0.781,11.255c-0.547,0-4.678-2.236-9.258-5.02
|
||||
c-9.131-5.459-13.711-6.235-18.945-3.125c-3.691,2.119-5.586,5.698-5.586,10.488c0,6.025,2.686,9.15,12.158,14.38
|
||||
c4.902,2.798,9.033,5.244,9.033,5.586c0,0.327-3.574,2.559-7.92,5.02c-4.346,2.446-9.141,5.908-10.586,7.578
|
||||
c-4.697,5.571-2.803,15.61,3.447,18.408c5.244,2.334,9.492,1.553,18.516-3.691c4.785-2.783,9.033-5.02,9.365-5.02
|
||||
c0.342,0,0.557,5.02,0.557,11.27v11.27l3.799,3.789c3.115,3.11,4.678,3.789,8.467,3.789S1673.34,4611.86,1676.455,4608.75z
|
||||
M1416.514,4294.257c8.584-3.555,10.479-13.496,3.906-20.176l-3.35-3.33h-30.117h-30.098l-3.35,3.33
|
||||
c-6.465,6.582-4.678,16.514,3.672,20.078C1362.422,4296.494,1411.045,4296.494,1416.514,4294.257z"/>
|
||||
</g>
|
||||
</svg>
|
Na Breedte: | Hoogte: | Grootte: 8.6 KiB |
@ -36,33 +36,44 @@
|
||||
"license": [
|
||||
"GPL-3.0-only"
|
||||
],
|
||||
"extra": {
|
||||
"oxideshop": {
|
||||
"source-directory": "/src",
|
||||
"target-directory": "d3/totp"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2",
|
||||
"php": ">=8.0",
|
||||
"ext-xmlwriter": "*",
|
||||
"ext-openssl": "*",
|
||||
"oxid-esales/oxideshop-ce": "6.8.0 - 6.14",
|
||||
"oxid-esales/oxideshop-ce": "7.0 - 7.1",
|
||||
"spomky-labs/otphp": "^10.0 || ^11.0",
|
||||
"bacon/bacon-qr-code": "^2.0",
|
||||
"bacon/bacon-qr-code": "^2.0 || ^3.0",
|
||||
"laminas/laminas-math": "^3.2",
|
||||
"d3/testingtools": "^1.0"
|
||||
"d3/oxidservicebridges": "^2.1.0.0",
|
||||
"beberlei/assert": "^v3.3.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^2.19",
|
||||
"phpstan/phpstan": "^1.8"
|
||||
"phpunit/phpunit" : "^9.6",
|
||||
"d3/testingtools": "^1.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.9",
|
||||
"phpstan/phpstan": "^1.8",
|
||||
"boxblinkracer/phpunuhi": "^1.12"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"D3\\Totp\\": "../../../source/modules/d3/totp"
|
||||
"D3\\Totp\\": ""
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"D3\\Totp\\Tests\\": "Tests"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"totp_phpstan": "./vendor/bin/phpstan -c./vendor/d3/oxtotp/phpstan.neon"
|
||||
"php-cs-fixer": "./vendor/bin/php-cs-fixer fix --config=vendor/d3/oxid-twofactor-onetimepassword/.php-cs-fixer.php",
|
||||
|
||||
"phpstan": "./vendor/bin/phpstan --configuration=./vendor/d3/oxid-twofactor-onetimepassword/phpstan.neon",
|
||||
|
||||
"phpunit": "XDEBUG_MODE=off vendor/bin/phpunit --config=vendor/d3/oxid-twofactor-onetimepassword/Tests/",
|
||||
"phpunit-line-coverage": "XDEBUG_MODE=coverage ./vendor/bin/phpunit --config=vendor/d3/oxid-twofactor-onetimepassword/Tests/ --coverage-html=vendor/d3/oxid-twofactor-onetimepassword/Tests/reports/coverage",
|
||||
"phpunit-path-coverage": "XDEBUG_MODE=coverage ./vendor/bin/phpunit --config=vendor/d3/oxid-twofactor-onetimepassword/Tests/ --coverage-html=vendor/d3/oxid-twofactor-onetimepassword/Tests/reports/coverage --path-coverage",
|
||||
|
||||
"phpunuhi": "./vendor/bin/phpunuhi --configuration=vendor/d3/oxid-twofactor-onetimepassword/phpunuhi.xml validate"
|
||||
},
|
||||
"suggest": {
|
||||
"d3/oxid-twofactor-passwordless": "Passwordless login with FIDO2 hardware token."
|
||||
|
43
src/metadata.php → metadata.php
Executable file → Normal file
43
src/metadata.php → metadata.php
Executable file → Normal file
@ -18,6 +18,7 @@ use D3\Totp\Application\Controller\Admin\d3user_totp;
|
||||
use D3\Totp\Application\Controller\Admin\d3force_2fa;
|
||||
use D3\Totp\Application\Controller\d3_account_totp;
|
||||
use D3\Totp\Application\Controller\d3totplogin;
|
||||
use D3\Totp\Application\Model\Constants;
|
||||
use D3\Totp\Modules\Application\Component\d3_totp_UserComponent;
|
||||
use D3\Totp\Modules\Application\Controller\Admin\d3_totp_LoginController;
|
||||
use D3\Totp\Modules\Application\Controller\d3_totp_OrderController;
|
||||
@ -36,32 +37,23 @@ use OxidEsales\Eshop\Core\SystemEventHandler;
|
||||
use OxidEsales\Eshop\Core\Utils;
|
||||
use OxidEsales\Eshop\Application\Model as OxidModel;
|
||||
|
||||
/**
|
||||
* Metadata version
|
||||
*/
|
||||
$sMetadataVersion = '2.1';
|
||||
|
||||
$sModuleId = 'd3totp';
|
||||
$logo = '<img src="https://logos.oxidmodule.com/d3logo.svg" alt="(D3)" style="height:1em;width:1em">';
|
||||
|
||||
/**
|
||||
* Module information
|
||||
*/
|
||||
$aModule = [
|
||||
'id' => $sModuleId,
|
||||
'id' => Constants::OXID_MODULE_ID,
|
||||
'title' => [
|
||||
'de' => $logo . ' zweiter Faktor - Einmalpasswort',
|
||||
'en' => $logo . ' second factor - one-time password',
|
||||
'de' => '(D3) zweiter Faktor - Einmalpasswort',
|
||||
'en' => '(D3) second factor - one-time password',
|
||||
],
|
||||
'description' => [
|
||||
'de' => 'Einmalpasswort (TOTP) als zweiter Faktor bei der Anmeldung im OXID eSales Shop',
|
||||
'en' => 'One-time password (TOTP) as second factor for login in OXID eSales shop',
|
||||
],
|
||||
'version' => '2.1.1.0',
|
||||
'version' => '3.0.0.0',
|
||||
'author' => 'D³ Data Development (Inh.: Thomas Dartsch)',
|
||||
'email' => 'support@shopmodule.com',
|
||||
'url' => 'https://www.oxidmodule.com/',
|
||||
'thumbnail' => 'logo.png',
|
||||
'thumbnail' => 'picture.svg',
|
||||
'extend' => [
|
||||
UserController::class => d3_totp_UserController::class,
|
||||
PaymentController::class => d3_totp_PaymentController::class,
|
||||
@ -80,10 +72,11 @@ $aModule = [
|
||||
'd3totpadminlogin' => d3totpadminlogin::class,
|
||||
],
|
||||
'templates' => [
|
||||
'd3user_totp.tpl' => 'd3/totp/Application/views/admin/tpl/d3user_totp.tpl',
|
||||
'd3totplogin.tpl' => 'd3/totp/Application/views/tpl/d3totplogin.tpl',
|
||||
'd3_account_totp.tpl' => 'd3/totp/Application/views/tpl/d3_account_totp.tpl',
|
||||
'd3totpadminlogin.tpl' => 'd3/totp/Application/views/admin/tpl/d3totplogin.tpl',
|
||||
'@'.Constants::OXID_MODULE_ID.'/admin/d3user_totp.tpl' => 'views/smarty/admin/tpl/d3user_totp.tpl',
|
||||
'@'.Constants::OXID_MODULE_ID.'/admin/d3totplogin.tpl' => 'views/smarty/admin/tpl/d3totplogin.tpl',
|
||||
'@'.Constants::OXID_MODULE_ID.'/admin/inc/bootstrap.tpl' => 'views/smarty/admin/inc/bootstrap.tpl',
|
||||
'@'.Constants::OXID_MODULE_ID.'/tpl/d3_account_totp.tpl' => 'views/smarty/tpl/d3_account_totp.tpl',
|
||||
'@'.Constants::OXID_MODULE_ID.'/tpl/d3totplogin.tpl' => 'views/smarty/tpl/d3totplogin.tpl',
|
||||
],
|
||||
'settings' => [
|
||||
[
|
||||
@ -101,28 +94,22 @@ $aModule = [
|
||||
[
|
||||
'template' => 'login.tpl',
|
||||
'block' => 'admin_login_form',
|
||||
'file' => 'Application/views/admin/blocks/d3totp_login_admin_login_form.tpl',
|
||||
'file' => 'views/smarty/admin/blocks/d3totp_login_admin_login_form.tpl',
|
||||
],
|
||||
[
|
||||
'template' => 'page/account/inc/account_menu.tpl',
|
||||
'block' => 'account_menu',
|
||||
'file' => 'Application/views/blocks/page/account/inc/account_menu.tpl',
|
||||
'file' => 'views/smarty/blocks/page/account/inc/account_menu.tpl',
|
||||
],
|
||||
[
|
||||
'template' => 'page/account/dashboard.tpl',
|
||||
'block' => 'account_dashboard_col2',
|
||||
'file' => 'Application/views/blocks/page/account/account_dashboard_col2_wave.tpl',
|
||||
],
|
||||
[
|
||||
'theme' => 'flow',
|
||||
'template' => 'page/account/dashboard.tpl',
|
||||
'block' => 'account_dashboard_col2',
|
||||
'file' => 'Application/views/blocks/page/account/account_dashboard_col2_flow.tpl',
|
||||
'file' => 'views/smarty/blocks/page/account/account_dashboard_col2_wave.tpl',
|
||||
],
|
||||
[
|
||||
'template' => 'widget/header/servicebox.tpl',
|
||||
'block' => 'widget_header_servicebox_items',
|
||||
'file' => 'Application/views/blocks/widget/header/widget_header_servicebox_items.tpl',
|
||||
'file' => 'views/smarty/blocks/widget/header/widget_header_servicebox_items.tpl',
|
||||
],
|
||||
],
|
||||
];
|
30
migration/README.en.md
Normal file
30
migration/README.en.md
Normal file
@ -0,0 +1,30 @@
|
||||
# Working with [Doctrine Migrations](https://www.doctrine-project.org/projects/doctrine-migrations/en/3.4/reference/introduction.html)
|
||||
|
||||
Migrations map the changes to the database structure in programmed form. Each structural change is stored in a single (new) migration file, which is part of the module.
|
||||
|
||||
## Applying the migrations that have not yet been executed
|
||||
|
||||
Doctrine itself monitors which migrations have already been executed and thus prevents multiple executions of the same migration.
|
||||
|
||||
For this module, migrations can be executed in various ways:
|
||||
|
||||
- by activating the module in the shop backend or
|
||||
- by executing all shop migrations via the OXID migration wrapper with `./vendor/bin/oe-eshop-db_migrate migrations:migrate` or
|
||||
- by executing this module migration via the OXID Migration Wrapper with `./vendor/bin/oe-eshop-db_migrate migrations:migrate d3totp` or
|
||||
- by executing this module migration via the Doctrine Migrations with `./vendor/bin/doctrine-migrations migrate --configuration ./vendor/d3/oxid-twofactor-onetimepassword/migration/migrations.yml --db-configuration ./vendor/d3/oxid-twofactor-onetimepassword/migration/migrations-db.php`
|
||||
|
||||
## Create a skeleton for the first or additional migrations
|
||||
|
||||
Adapt the `migrations.yml` to your module.
|
||||
|
||||
```
|
||||
./vendor/bin/oe-eshop-doctrine_migration migrations:generate d3moduleid
|
||||
```
|
||||
|
||||
Edit the created file according to your requirements.
|
||||
|
||||
## Differences between Doctrine Migrations and the OXID Migration Wrapper
|
||||
|
||||
No suites can be specified in the original Doctrine Migrations. However, it is possible to specify the direction (up / down) and a target version.
|
||||
|
||||
With OXID, migrations can only be executed upwards (up) and always fixed up to the latest version.
|
30
migration/README.md
Normal file
30
migration/README.md
Normal file
@ -0,0 +1,30 @@
|
||||
# Arbeiten mit [Doctrine Migrations](https://www.doctrine-project.org/projects/doctrine-migrations/en/3.4/reference/introduction.html)
|
||||
|
||||
Migrations bilden die Veränderung der Datenbankstruktur in programmierter Form ab. Jede Strukturänderung wird in einer einzelnen (jeweils neuen) Migrationsdatei abgelegt, die Teil des Moduls ist.
|
||||
|
||||
## Anwenden der noch nicht ausgeführten Migrations
|
||||
|
||||
Doctrine überwacht selbst, welche Migrationen schon ausgeführt wurden und verhindert damit mehrfache Ausführungen der selben Migration.
|
||||
|
||||
Für dieses Modul können Migrationen auf verschiedenen Arten ausgeführt werden:
|
||||
|
||||
- durch Aktivieren des Moduls im Shopbackend oder
|
||||
- durch Ausführen aller Shopmigrationen über den OXID Migration Wrapper mit `./vendor/bin/oe-eshop-db_migrate migrations:migrate` oder
|
||||
- durch Ausführen dieser Modulmigration über den OXID Migration Wrapper mit `./vendor/bin/oe-eshop-db_migrate migrations:migrate d3totp` oder
|
||||
- durch Ausführen dieser Modulmigration über die Doctrine Migrations mit `./vendor/bin/doctrine-migrations migrate --configuration ./vendor/d3/oxid-twofactor-onetimepassword/migration/migrations.yml --db-configuration ./vendor/d3/oxid-twofactor-onetimepassword/migration/migrations-db.php`
|
||||
|
||||
## Erstellen eines Skeletons für die erste oder zusätzliche Migrationen
|
||||
|
||||
Passe die `migrations.yml` an Dein Modul an.
|
||||
|
||||
```
|
||||
./vendor/bin/oe-eshop-doctrine_migration migrations:generate d3moduleid
|
||||
```
|
||||
|
||||
Arbeite die angelegte Datei entsprechend Deinen Anforderungen um.
|
||||
|
||||
## Abweichungen zwischen Doctrine Migrations und dem OXID Migration Wrapper
|
||||
|
||||
In den originalen Doctrine Migrations können keine Suiten angegeben werden. Dafür gibt es die Möglichkeit, die Richtung (up / down) und eine Zielversion anzugeben.
|
||||
|
||||
Bei OXID können Migrations ausschließlich aufwärts (up) und immer fix bis zur aktuellsten Version ausgeführt werden.
|
185
migration/data/Version20240905232017.php
Normal file
185
migration/data/Version20240905232017.php
Normal file
@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Migrations;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Types\BooleanType;
|
||||
use Doctrine\DBAL\Types\DateTimeType;
|
||||
use Doctrine\DBAL\Types\StringType;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20240905232017 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Extend Database by missing OTP tables and missing columns.';
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->connection->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string');
|
||||
|
||||
$this->addTotpTable($schema);
|
||||
$this->addTotpBackupCodesTable($schema);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->connection->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string');
|
||||
|
||||
$this->removeTotpTable($schema);
|
||||
$this->removeTotpBackupCodesTable($schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
* @return void
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function addTotpTable(Schema $schema): void
|
||||
{
|
||||
$table = !$schema->hasTable('d3totp') ?
|
||||
$schema->createTable('d3totp')->setComment('totp setting') :
|
||||
$schema->getTable('d3totp');
|
||||
|
||||
// OXID
|
||||
if (!$table->hasColumn('OXID')) {
|
||||
$table->addColumn('OXID', (new StringType())->getName())
|
||||
->setLength(32)
|
||||
->setFixed(true)
|
||||
->setNotnull(true);
|
||||
}
|
||||
|
||||
// OXUSERID
|
||||
if (!$table->hasColumn('OXUSERID')) {
|
||||
$table->addColumn('OXUSERID', (new StringType())->getName())
|
||||
->setLength(32)
|
||||
->setFixed(true)
|
||||
->setNotnull(true);
|
||||
}
|
||||
|
||||
// useTotp
|
||||
if (!$table->hasColumn('USETOTP')) {
|
||||
$table->addColumn('USETOTP', (new BooleanType())->getName())
|
||||
->setLength(1)
|
||||
->setDefault(0)
|
||||
->setNotnull(true);
|
||||
}
|
||||
|
||||
// Seed
|
||||
if (!$table->hasColumn('SEED')) {
|
||||
$table->addColumn('SEED', (new StringType())->getName())
|
||||
->setLength(256)
|
||||
->setNotnull(true);
|
||||
}
|
||||
|
||||
// oxtimestamp
|
||||
if (!$table->hasColumn('OXTIMESTAMP')) {
|
||||
$table->addColumn('OXTIMESTAMP', (new DateTimeType())->getName())
|
||||
->setNotnull(true)
|
||||
->setDefault('CURRENT_TIMESTAMP');
|
||||
}
|
||||
|
||||
$table->hasPrimaryKey() ?: $table->setPrimaryKey(['oxid']);
|
||||
|
||||
if ($table->hasIndex('OXUSERID') === false) {
|
||||
$table->addUniqueIndex(['OXUSERID'], 'OXUSERID');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function removeTotpTable(Schema $schema): void
|
||||
{
|
||||
if ($schema->hasTable('d3totp')) {
|
||||
$schema->dropTable('d3totp');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
* @return void
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function addTotpBackupCodesTable(Schema $schema): void
|
||||
{
|
||||
$table = !$schema->hasTable('d3totp_backupcodes') ?
|
||||
$schema->createTable('d3totp_backupcodes')->setComment('totp backup codes') :
|
||||
$schema->getTable('d3totp_backupcodes');
|
||||
|
||||
// OXID
|
||||
if (!$table->hasColumn('OXID')) {
|
||||
$table->addColumn('OXID', (new StringType())->getName())
|
||||
->setLength(32)
|
||||
->setFixed(true)
|
||||
->setNotnull(true);
|
||||
}
|
||||
|
||||
// OXUSERID
|
||||
if (!$table->hasColumn('OXUSERID')) {
|
||||
$table->addColumn('OXUSERID', (new StringType())->getName())
|
||||
->setLength(32)
|
||||
->setFixed(true)
|
||||
->setNotnull(true)
|
||||
->setComment('User ID');
|
||||
}
|
||||
|
||||
// useTotp
|
||||
if (!$table->hasColumn('BACKUPCODE')) {
|
||||
$table->addColumn('BACKUPCODE', (new StringType())->getName())
|
||||
->setFixed(false)
|
||||
->setLength(64)
|
||||
->setNotnull(true);
|
||||
}
|
||||
|
||||
// oxtimestamp
|
||||
if (!$table->hasColumn('OXTIMESTAMP')) {
|
||||
$table->addColumn('OXTIMESTAMP', (new DateTimeType())->getName())
|
||||
->setNotnull(true)
|
||||
->setDefault('CURRENT_TIMESTAMP');
|
||||
}
|
||||
|
||||
$table->hasPrimaryKey() ?: $table->setPrimaryKey(['oxid']);
|
||||
|
||||
if ($table->hasIndex('OXUSERID') === false) {
|
||||
$table->addIndex(['OXUSERID'], 'OXUSERID');
|
||||
}
|
||||
|
||||
if ($table->hasIndex('BACKUPCODE') === false) {
|
||||
$table->addIndex(['BACKUPCODE'], 'BACKUPCODE');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function removeTotpBackupCodesTable(Schema $schema): void
|
||||
{
|
||||
if ($schema->hasTable('d3totp_backupcodes')) {
|
||||
$schema->dropTable('d3totp_backupcodes');
|
||||
}
|
||||
}
|
||||
}
|
23
migration/migrations-db.php
Normal file
23
migration/migrations-db.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use OxidEsales\ComposerPlugin\Installer\Package\ShopPackageInstaller;
|
||||
use OxidEsales\EshopCommunity\Internal\Container\ContainerFactory;
|
||||
use OxidEsales\EshopCommunity\Internal\Framework\Database\ConnectionProviderInterface;
|
||||
|
||||
require_once(__DIR__.'/../../../autoload.php');
|
||||
require_once __DIR__.'/../../../../'. ShopPackageInstaller::SHOP_SOURCE_DIRECTORY .'/bootstrap.php';
|
||||
|
||||
return ContainerFactory::getInstance()->getContainer()->get(ConnectionProviderInterface::class)->get();
|
4
migration/migrations.yml
Normal file
4
migration/migrations.yml
Normal file
@ -0,0 +1,4 @@
|
||||
table_storage:
|
||||
table_name: d3migrations_totp
|
||||
migrations_paths:
|
||||
'D3\Totp\Migrations': data
|
21
phpstan.neon
21
phpstan.neon
@ -1,14 +1,21 @@
|
||||
parameters:
|
||||
scanFiles:
|
||||
- src/IntelliSenseHelper.php
|
||||
- IntelliSenseHelper.php
|
||||
- ../../oxid-esales/oxideshop-ce/source/bootstrap.php
|
||||
- ../../oxid-esales/oxideshop-ce/source/oxfunctions.php
|
||||
- ../../oxid-esales/oxideshop-ce/source/overridablefunctions.php
|
||||
paths:
|
||||
- src
|
||||
level: 5
|
||||
phpVersion: 70300
|
||||
- .
|
||||
excludePaths:
|
||||
- .php-cs-fixer.php
|
||||
- rector.php
|
||||
- Tests
|
||||
level: 6
|
||||
phpVersion: 80300
|
||||
ignoreErrors:
|
||||
- '#Call to method getFieldData\(\) on an unknown class oxShop.#'
|
||||
- '#Return type \(array\) of method D3\\Totp\\Application\\Controller\\d3totplogin::getBreadCrumb\(\) should be compatible with return type \(null\) of method OxidEsales\\EshopCommunity\\Application\\Controller\\FrontendController::getBreadCrumb\(\)#'
|
||||
- '#Parameter \#\d+ \$value of method OxidEsales\\EshopCommunity\\Core\\Config::setConfigParam\(\) expects string, (true|false) given.#'
|
||||
- '#Return type [()a-z]* of method .*getBreadCrumb\(\) should be compatible with return type [()a-z]* of method\s*.*getBreadCrumb\(\)#'
|
||||
- '#Unable to resolve the template type T in call to function oxNew#'
|
||||
- '#(Property|Method) D3\\Totp\\Modules\\.* has no (return type|type) specified.#'
|
||||
- identifier: missingType.iterableValue
|
||||
parallel:
|
||||
processTimeout: 900.0
|
25
phpunuhi.xml
Normal file
25
phpunuhi.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<phpunuhi
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../boxblinkracer/phpunuhi/config.xsd"
|
||||
>
|
||||
<translations>
|
||||
<set name="Frontend">
|
||||
<format>
|
||||
<php/>
|
||||
</format>
|
||||
<locales>
|
||||
<locale name="de">./Application/translations/%locale_lc%/translations.php</locale>
|
||||
<locale name="en">./Application/translations/%locale_lc%/translations.php</locale>
|
||||
</locales>
|
||||
</set>
|
||||
<set name="Backend">
|
||||
<format>
|
||||
<php/>
|
||||
</format>
|
||||
<locales>
|
||||
<locale name="de">./Application/views/%locale_lc%/translations.php</locale>
|
||||
<locale name="en">./Application/views/%locale_lc%/translations.php</locale>
|
||||
</locales>
|
||||
</set>
|
||||
</translations>
|
||||
</phpunuhi>
|
@ -1,47 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Application\Controller\Admin;
|
||||
|
||||
use D3\Totp\Application\Model\d3totp_conf;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
use OxidEsales\Eshop\Core\Session;
|
||||
|
||||
class d3force_2fa extends d3user_totp
|
||||
{
|
||||
public function render()
|
||||
{
|
||||
$this->addTplParam('force2FA', true);
|
||||
|
||||
$userID = $this->d3TotpGetSessionObject()->getVariable(d3totp_conf::OXID_ADMIN_AUTH);
|
||||
$this->_sEditObjectId = $userID;
|
||||
|
||||
return parent::render();
|
||||
}
|
||||
|
||||
|
||||
protected function _authorize()
|
||||
{
|
||||
$userID = $this->d3TotpGetSessionObject()->getVariable(d3totp_conf::OXID_ADMIN_AUTH);
|
||||
|
||||
return ($this->d3IsAdminForce2FA() && !empty($userID));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Session
|
||||
*/
|
||||
private function d3TotpGetSessionObject()
|
||||
{
|
||||
return Registry::getSession();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private function d3IsAdminForce2FA()
|
||||
{
|
||||
return $this->isAdmin() &&
|
||||
Registry::getConfig()->getConfigParam('D3_TOTP_ADMIN_FORCE_2FA') == true;
|
||||
}
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Application\Model;
|
||||
|
||||
use D3\Totp\Modules\Application\Model\d3_totp_user;
|
||||
use OxidEsales\Eshop\Application\Model\User;
|
||||
use OxidEsales\Eshop\Core\DatabaseProvider;
|
||||
use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException;
|
||||
use OxidEsales\Eshop\Core\Model\BaseModel;
|
||||
|
||||
class d3backupcode extends BaseModel
|
||||
{
|
||||
protected $_sCoreTable = 'd3totp_backupcodes';
|
||||
|
||||
/**
|
||||
* @param $sUserId
|
||||
* @return string
|
||||
* @throws DatabaseConnectionException
|
||||
*/
|
||||
public function generateCode($sUserId)
|
||||
{
|
||||
$sCode = $this->getRandomTotpBackupCode();
|
||||
$this->assign(
|
||||
[
|
||||
'oxuserid' => $sUserId,
|
||||
'backupcode' => $this->d3EncodeBC($sCode, $sUserId),
|
||||
]
|
||||
);
|
||||
|
||||
return $sCode;
|
||||
}
|
||||
|
||||
public function getRandomTotpBackupCode()
|
||||
{
|
||||
return d3RandomGenerator::getRandomTotpBackupCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $code
|
||||
* @param $sUserId
|
||||
* @return false|string
|
||||
* @throws DatabaseConnectionException
|
||||
*/
|
||||
public function d3EncodeBC($code, $sUserId)
|
||||
{
|
||||
$oDb = DatabaseProvider::getDb();
|
||||
$oUser = $this->d3TotpGetUserObject();
|
||||
$oUser->load($sUserId);
|
||||
$salt = $oUser->getFieldData('oxpasssalt');
|
||||
$sSelect = "SELECT BINARY MD5( CONCAT( " . $oDb->quote($code) . ", UNHEX( ".$oDb->quote($salt)." ) ) )";
|
||||
|
||||
return $oDb->getOne($sSelect);
|
||||
}
|
||||
|
||||
public function d3GetUser()
|
||||
{
|
||||
/** @var User|null $user */
|
||||
$user = $this->getUser();
|
||||
|
||||
if ($user instanceof User) {
|
||||
return $this->getUser();
|
||||
}
|
||||
|
||||
/** @var d3_totp_user $oUser */
|
||||
$oUser = oxNew(User::class);
|
||||
$sUserId = $oUser->d3TotpGetCurrentUser();
|
||||
$oUser->load($sUserId);
|
||||
return $oUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User
|
||||
*/
|
||||
public function d3TotpGetUserObject()
|
||||
{
|
||||
return oxNew(User::class);
|
||||
}
|
||||
}
|
@ -1,166 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Application\Model;
|
||||
|
||||
use D3\Totp\Application\Controller\Admin\d3user_totp;
|
||||
use Exception;
|
||||
use OxidEsales\Eshop\Core\Config;
|
||||
use OxidEsales\Eshop\Core\Database\Adapter\DatabaseInterface;
|
||||
use OxidEsales\Eshop\Core\DatabaseProvider;
|
||||
use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException;
|
||||
use OxidEsales\Eshop\Core\Model\ListModel;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
|
||||
class d3backupcodelist extends ListModel
|
||||
{
|
||||
protected $_sObjectsInListName = d3backupcode::class;
|
||||
|
||||
/**
|
||||
* Core table name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_sCoreTable = 'd3totp_backupcodes';
|
||||
|
||||
protected $_backupCodes = [];
|
||||
|
||||
/**
|
||||
* @param $sUserId
|
||||
* @throws DatabaseConnectionException
|
||||
*/
|
||||
public function generateBackupCodes($sUserId)
|
||||
{
|
||||
$this->deleteAllFromUser($sUserId);
|
||||
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
$oBackupCode = $this->getD3BackupCodeObject();
|
||||
$this->_backupCodes[] = $oBackupCode->generateCode($sUserId);
|
||||
$this->offsetSet(md5((string) rand()), $oBackupCode);
|
||||
}
|
||||
|
||||
/** @var d3user_totp $oActView */
|
||||
$oActView = $this->d3GetConfig()->getActiveView();
|
||||
$oActView->setBackupCodes($this->_backupCodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return d3backupcode
|
||||
*/
|
||||
public function getD3BackupCodeObject()
|
||||
{
|
||||
return oxNew(d3backupcode::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Config
|
||||
*/
|
||||
public function d3GetConfig()
|
||||
{
|
||||
return Registry::getConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
/** @var d3backupcode $oBackupCode */
|
||||
foreach ($this->getArray() as $oBackupCode) {
|
||||
$oBackupCode->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return d3backupcode
|
||||
*/
|
||||
public function getBaseObject()
|
||||
{
|
||||
/** @var d3backupcode $object */
|
||||
$object = parent::getBaseObject();
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $totp
|
||||
* @return bool
|
||||
* @throws DatabaseConnectionException
|
||||
*/
|
||||
public function verify($totp)
|
||||
{
|
||||
$oDb = $this->d3GetDb();
|
||||
|
||||
$query = "SELECT oxid FROM ".$this->getBaseObject()->getViewName().
|
||||
" WHERE ".$oDb->quoteIdentifier('backupcode')." = ".$oDb->quote($this->getBaseObject()->d3EncodeBC($totp, $this->d3GetUser()->getId()))." AND ".
|
||||
$oDb->quoteIdentifier("oxuserid") ." = ".$oDb->quote($this->d3GetUser()->getId());
|
||||
|
||||
$sVerify = $oDb->getOne($query);
|
||||
|
||||
if ($sVerify) {
|
||||
$this->getBaseObject()->delete($sVerify);
|
||||
}
|
||||
|
||||
return (bool) $sVerify;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DatabaseInterface
|
||||
* @throws DatabaseConnectionException
|
||||
*/
|
||||
public function d3GetDb()
|
||||
{
|
||||
return DatabaseProvider::getDb(DatabaseProvider::FETCH_MODE_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sUserId
|
||||
* @throws DatabaseConnectionException
|
||||
*/
|
||||
public function deleteAllFromUser($sUserId)
|
||||
{
|
||||
$oDb = $this->d3GetDb();
|
||||
|
||||
$query = "SELECT OXID FROM ".$oDb->quoteIdentifier($this->getBaseObject()->getCoreTableName()).
|
||||
" WHERE ".$oDb->quoteIdentifier('oxuserid')." = ".$oDb->quote($sUserId);
|
||||
|
||||
$this->selectString($query);
|
||||
|
||||
/** @var d3backupcode $oBackupCode */
|
||||
foreach ($this->getArray() as $oBackupCode) {
|
||||
$oBackupCode->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sUserId
|
||||
* @return int
|
||||
* @throws DatabaseConnectionException
|
||||
*/
|
||||
public function getAvailableCodeCount($sUserId)
|
||||
{
|
||||
$oDb = $this->d3GetDb();
|
||||
|
||||
$query = "SELECT count(*) FROM ".$oDb->quoteIdentifier($this->getBaseObject()->getViewName()).
|
||||
" WHERE ".$oDb->quoteIdentifier('oxuserid')." = ".$oDb->quote($sUserId);
|
||||
|
||||
return (int) $oDb->getOne($query);
|
||||
}
|
||||
|
||||
public function d3GetUser()
|
||||
{
|
||||
return $this->getBaseObject()->d3GetUser();
|
||||
}
|
||||
}
|
@ -1,282 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Application\Model;
|
||||
|
||||
use BaconQrCode\Renderer\RendererInterface;
|
||||
use BaconQrCode\Writer;
|
||||
use D3\Totp\Application\Factory\BaconQrCodeFactory;
|
||||
use D3\Totp\Application\Model\Exceptions\d3totp_wrongOtpException;
|
||||
use OTPHP\TOTP;
|
||||
use OxidEsales\Eshop\Application\Model\User;
|
||||
use OxidEsales\Eshop\Core\Database\Adapter\DatabaseInterface;
|
||||
use OxidEsales\Eshop\Core\DatabaseProvider;
|
||||
use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException;
|
||||
use OxidEsales\Eshop\Core\Model\BaseModel;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
|
||||
class d3totp extends BaseModel
|
||||
{
|
||||
public $tableName = 'd3totp';
|
||||
public $userId;
|
||||
public $totp;
|
||||
protected $timeWindow = 2;
|
||||
|
||||
/**
|
||||
* d3totp constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->init($this->tableName);
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $userId
|
||||
* @throws DatabaseConnectionException
|
||||
*/
|
||||
public function loadByUserId($userId)
|
||||
{
|
||||
$this->userId = $userId;
|
||||
$oDb = $this->d3GetDb();
|
||||
|
||||
if ($oDb->getOne("SHOW TABLES LIKE '".$this->tableName."'")) {
|
||||
$query = "SELECT oxid FROM ".$this->getViewName().' WHERE oxuserid = '.$oDb->quote($userId).' LIMIT 1';
|
||||
$this->load($oDb->getOne($query));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $userId
|
||||
* @return bool
|
||||
* @throws DatabaseConnectionException
|
||||
*/
|
||||
public function checkIfAlreadyExist($userId)
|
||||
{
|
||||
$oDb = $this->d3GetDb();
|
||||
$query = "SELECT 1 FROM ".$this->getViewName().' WHERE oxuserid = '.$oDb->quote($userId).' LIMIT 1';
|
||||
return (bool) $oDb->getOne($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DatabaseInterface
|
||||
* @throws DatabaseConnectionException
|
||||
*/
|
||||
public function d3GetDb()
|
||||
{
|
||||
return DatabaseProvider::getDb(DatabaseProvider::FETCH_MODE_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
$userId = $this->userId ?: $this->getFieldData('oxuserid');
|
||||
|
||||
$user = $this->d3GetUser();
|
||||
$user->load($userId);
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User
|
||||
*/
|
||||
public function d3GetUser()
|
||||
{
|
||||
return oxNew(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isActive()
|
||||
{
|
||||
return false == Registry::getConfig()->getConfigParam('blDisableTotpGlobally')
|
||||
&& $this->UserUseTotp();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function UserUseTotp()
|
||||
{
|
||||
return $this->getFieldData('usetotp')
|
||||
&& $this->getFieldData('seed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getSavedSecret(): ?string
|
||||
{
|
||||
$seed_enc = $this->getFieldData('seed');
|
||||
|
||||
if ($seed_enc) {
|
||||
$seed = $this->decrypt($seed_enc);
|
||||
if ($seed) {
|
||||
return $seed;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $seed
|
||||
* @return TOTP
|
||||
*/
|
||||
public function getTotp($seed = null)
|
||||
{
|
||||
if (false == $this->totp) {
|
||||
$this->totp = TOTP::create($seed ?: $this->getSavedSecret());
|
||||
$this->totp->setLabel($this->getUser()->getFieldData('oxusername') ?: '');
|
||||
$this->totp->setIssuer(Registry::getConfig()->getActiveShop()->getFieldData('oxname'));
|
||||
}
|
||||
|
||||
return $this->totp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getQrCodeElement()
|
||||
{
|
||||
$renderer = BaconQrCodeFactory::renderer(200);
|
||||
$writer = $this->d3GetWriter($renderer);
|
||||
return $writer->writeString($this->getTotp()->getProvisioningUri());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RendererInterface $renderer
|
||||
* @return Writer
|
||||
*/
|
||||
public function d3GetWriter(RendererInterface $renderer)
|
||||
{
|
||||
return oxNew(Writer::class, $renderer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSecret()
|
||||
{
|
||||
return trim($this->getTotp()->getSecret());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $seed
|
||||
*/
|
||||
public function saveSecret($seed)
|
||||
{
|
||||
$this->assign(
|
||||
[
|
||||
'seed' => $this->encrypt($seed),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $totp
|
||||
* @param $seed
|
||||
* @return bool
|
||||
* @throws DatabaseConnectionException
|
||||
* @throws d3totp_wrongOtpException
|
||||
*/
|
||||
public function verify($totp, $seed = null)
|
||||
{
|
||||
$blNotVerified = $this->getTotp($seed)->verify($totp, null, $this->timeWindow) == false;
|
||||
|
||||
if ($blNotVerified && null == $seed) {
|
||||
$oBC = $this->d3GetBackupCodeListObject();
|
||||
$blNotVerified = $oBC->verify($totp) == false;
|
||||
|
||||
if ($blNotVerified) {
|
||||
throw oxNew(d3totp_wrongOtpException::class);
|
||||
}
|
||||
} elseif ($blNotVerified && $seed !== null) {
|
||||
throw oxNew(d3totp_wrongOtpException::class);
|
||||
}
|
||||
|
||||
return !$blNotVerified;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return d3backupcodelist
|
||||
*/
|
||||
public function d3GetBackupCodeListObject()
|
||||
{
|
||||
return oxNew(d3backupcodelist::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $plaintext
|
||||
* @return string
|
||||
*/
|
||||
public function encrypt($plaintext)
|
||||
{
|
||||
$key = Registry::getConfig()->getConfigParam('sConfigKey');
|
||||
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
|
||||
$iv = openssl_random_pseudo_bytes($ivlen);
|
||||
$ciphertext_raw = openssl_encrypt($plaintext, $cipher, $key, OPENSSL_RAW_DATA, $iv);
|
||||
$hmac = hash_hmac('sha256', $ciphertext_raw, $key, true);
|
||||
return base64_encode($iv.$hmac.$ciphertext_raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $ciphertext
|
||||
* @return bool|string
|
||||
*/
|
||||
public function decrypt($ciphertext)
|
||||
{
|
||||
$key = Registry::getConfig()->getConfigParam('sConfigKey');
|
||||
$c = $this->d3Base64_decode($ciphertext);
|
||||
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
|
||||
$iv = substr($c, 0, $ivlen);
|
||||
$hmac = substr($c, $ivlen, $sha2len=32);
|
||||
$ciphertext_raw = substr($c, $ivlen+$sha2len);
|
||||
$original_plaintext = openssl_decrypt($ciphertext_raw, $cipher, $key, OPENSSL_RAW_DATA, $iv);
|
||||
$calcmac = hash_hmac('sha256', $ciphertext_raw, $key, true);
|
||||
if (hash_equals($hmac, $calcmac)) { // PHP 5.6+ compute attack-safe comparison
|
||||
return $original_plaintext;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* required for unit tests
|
||||
* @param $source
|
||||
* @return bool|string
|
||||
*/
|
||||
public function d3Base64_decode($source)
|
||||
{
|
||||
return base64_decode($source);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|string $oxid
|
||||
* @return bool
|
||||
* @throws DatabaseConnectionException
|
||||
*/
|
||||
public function delete($oxid = null)
|
||||
{
|
||||
$oBackupCodeList = $this->d3GetBackupCodeListObject();
|
||||
$oBackupCodeList->deleteAllFromUser($this->getFieldData('oxuserid'));
|
||||
|
||||
return parent::delete($oxid);
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
[{$smarty.block.parent}]
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<a href="[{oxgetseourl ident=$oViewConf->getSelfLink()|cat:"cl=d3_account_totp"}]">[{oxmultilang ident="D3_TOTP_ACCOUNT"}]</a>
|
||||
<a href="[{oxgetseourl ident=$oViewConf->getSslSelfLink()|cat:"cl=d3_account_totp"}]" class="btn btn-default btn-xs pull-right">
|
||||
<i class="fa fa-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="panel-body">[{oxmultilang ident="D3_TOTP_ACCOUNT_DESC"}]</div>
|
||||
</div>
|
@ -1,135 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Setup;
|
||||
|
||||
use OxidEsales\Eshop\Core\DatabaseProvider;
|
||||
use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException;
|
||||
use OxidEsales\Eshop\Core\Exception\DatabaseErrorException;
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
class Events
|
||||
{
|
||||
/**
|
||||
* @return void
|
||||
* @throws DatabaseConnectionException
|
||||
* @throws DatabaseErrorException
|
||||
*/
|
||||
public static function onActivate(): void
|
||||
{
|
||||
self::addTotpTable();
|
||||
self::addTotpBackupCodesTable();
|
||||
self::addSeoItem1();
|
||||
self::addSeoItem2();
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function onDeactivate()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws DatabaseConnectionException
|
||||
* @throws DatabaseErrorException
|
||||
*/
|
||||
public static function addTotpTable(): void
|
||||
{
|
||||
$query = "CREATE TABLE IF NOT EXISTS `d3totp` (
|
||||
`OXID` CHAR(32) NOT NULL ,
|
||||
`OXUSERID` CHAR(32) NOT NULL ,
|
||||
`USETOTP` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
`SEED` VARCHAR(256) NOT NULL ,
|
||||
`OXTIMESTAMP` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Timestamp',
|
||||
PRIMARY KEY (`OXID`) ,
|
||||
UNIQUE KEY `OXUSERID` (`OXUSERID`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='totp setting';";
|
||||
|
||||
DatabaseProvider::getDb()->execute($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws DatabaseConnectionException
|
||||
* @throws DatabaseErrorException
|
||||
*/
|
||||
public static function addTotpBackupCodesTable(): void
|
||||
{
|
||||
$query = "CREATE TABLE IF NOT EXISTS `d3totp_backupcodes` (
|
||||
`OXID` CHAR(32) NOT NULL ,
|
||||
`OXUSERID` CHAR(32) NOT NULL COMMENT 'user id',
|
||||
`BACKUPCODE` VARCHAR(64) NOT NULL COMMENT 'BackupCode',
|
||||
`OXTIMESTAMP` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Timestamp',
|
||||
PRIMARY KEY (`OXID`) ,
|
||||
KEY `OXUSERID` (`OXUSERID`) ,
|
||||
KEY `BACKUPCODE` (`BACKUPCODE`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='totp backup codes';";
|
||||
|
||||
DatabaseProvider::getDb()->execute($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws DatabaseConnectionException
|
||||
* @throws DatabaseErrorException
|
||||
*/
|
||||
public static function addSeoItem1(): void
|
||||
{
|
||||
if (!DatabaseProvider::getDb()->getOne('SELECT 1 FROM oxseo WHERE oxident = "76282e134ad4e40a3578e121a6cb1f6a"')) {
|
||||
$query = "INSERT INTO `oxseo`
|
||||
(
|
||||
`OXOBJECTID`, `OXIDENT`, `OXSHOPID`,
|
||||
`OXLANG`, `OXSTDURL`, `OXSEOURL`,
|
||||
`OXTYPE`, `OXFIXED`, `OXEXPIRED`,
|
||||
`OXPARAMS`, `OXTIMESTAMP`
|
||||
) VALUES (
|
||||
'39f744f17e974988e515558698a29df4', '76282e134ad4e40a3578e121a6cb1f6a', 1,
|
||||
1, 'index.php?cl=d3_account_totp', 'en/2-factor-authintication/',
|
||||
'static', 0, 0,
|
||||
'', NOW()
|
||||
);";
|
||||
|
||||
DatabaseProvider::getDb()->execute($query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws DatabaseConnectionException
|
||||
* @throws DatabaseErrorException
|
||||
*/
|
||||
public static function addSeoItem2(): void
|
||||
{
|
||||
if (!DatabaseProvider::getDb()->getOne('SELECT 1 FROM oxseo WHERE oxident = "c1f8b5506e2b5d6ac184dcc5ebdfb591"')) {
|
||||
$query = "INSERT INTO `oxseo`
|
||||
(
|
||||
`OXOBJECTID`, `OXIDENT`, `OXSHOPID`,
|
||||
`OXLANG`, `OXSTDURL`, `OXSEOURL`,
|
||||
`OXTYPE`, `OXFIXED`, `OXEXPIRED`,
|
||||
`OXPARAMS`, `OXTIMESTAMP`
|
||||
) VALUES (
|
||||
'39f744f17e974988e515558698a29df4', 'c1f8b5506e2b5d6ac184dcc5ebdfb591', 1,
|
||||
0, 'index.php?cl=d3_account_totp', '2-faktor-authentisierung/',
|
||||
'static', 0, 0,
|
||||
'', NOW()
|
||||
);";
|
||||
|
||||
DatabaseProvider::getDb()->execute($query);
|
||||
}
|
||||
}
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
BIN
src/logo.png
BIN
src/logo.png
Binair bestand niet weergegeven.
Voor Breedte: | Hoogte: | Grootte: 4.4 KiB |
@ -1,29 +0,0 @@
|
||||
<phpunit backupGlobals="true"
|
||||
backupStaticAttributes="false"
|
||||
cacheTokens="true"
|
||||
colors="false"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="false"
|
||||
convertWarningsToExceptions="true"
|
||||
forceCoversAnnotation="false"
|
||||
processIsolation="false"
|
||||
stopOnError="false"
|
||||
stopOnFailure="false"
|
||||
stopOnIncomplete="false"
|
||||
stopOnSkipped="false"
|
||||
verbose="false">
|
||||
<filter>
|
||||
<whitelist processUncoveredFilesFromWhitelist="true">
|
||||
<directory suffix=".php">../Application</directory>
|
||||
<directory suffix=".php">../Modules</directory>
|
||||
<directory suffix=".php">../Setup</directory>
|
||||
<exclude>
|
||||
<directory suffix=".php">../Application/views</directory>
|
||||
<directory suffix=".php">../Application/translations</directory>
|
||||
</exclude>
|
||||
</whitelist>
|
||||
</filter>
|
||||
<logging>
|
||||
<log type="junit" target="reports/logfile.xml"/>
|
||||
</logging>
|
||||
</phpunit>
|
16
views/smarty/admin/inc/bootstrap.tpl
Normal file
16
views/smarty/admin/inc/bootstrap.tpl
Normal file
@ -0,0 +1,16 @@
|
||||
<link rel="stylesheet" href="[{$oViewConf->getModuleUrl('d3totp', 'out/src/css/bootstrap.min.css')}]">
|
||||
<style>
|
||||
* {
|
||||
font-size: 12px;
|
||||
}
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
select {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
div.actions {
|
||||
box-sizing: initial;
|
||||
}
|
||||
</style>
|
@ -33,17 +33,17 @@
|
||||
[{$oView->getBackupCodeCountMessage()}]
|
||||
|
||||
<div class="container">
|
||||
<label for="1st">erste TOTP-Ziffer</label>
|
||||
<label for="1st">[{oxmultilang ident="D3_TOTP_INPUT_FIRST"}]</label>
|
||||
<input type="text" name="d3totp[]" class="digit" id='1st' inputmode="numeric" pattern="[0-9]*" maxlength="1" onkeyup="clickEvent(null, '2nd')" autofocus autocomplete="off">
|
||||
<label for="2nd">zweite TOTP-Ziffer</label>
|
||||
<label for="2nd">[{oxmultilang ident="D3_TOTP_INPUT_SECOND"}]</label>
|
||||
<input type="text" name="d3totp[]" class="digit" id="2nd" inputmode="numeric" pattern="[0-9]*" maxlength="1" onkeyup="clickEvent('1st', '3rd')" autocomplete="off">
|
||||
<label for="3rd">dritte TOTP-Ziffer</label>
|
||||
<label for="3rd">[{oxmultilang ident="D3_TOTP_INPUT_THIRD"}]</label>
|
||||
<input type="text" name="d3totp[]" class="digit" id="3rd" inputmode="numeric" pattern="[0-9]*" maxlength="1" onkeyup="clickEvent('2nd', '4th')" autocomplete="off">
|
||||
<label for="4th">vierte TOTP-Ziffer</label>
|
||||
<label for="4th">[{oxmultilang ident="D3_TOTP_INPUT_FOURTH"}]</label>
|
||||
<input type="text" name="d3totp[]" class="digit" id="4th" inputmode="numeric" pattern="[0-9]*" maxlength="1" onkeyup="clickEvent('3rd', '5th')" autocomplete="off">
|
||||
<label for="5th">fünfte TOTP-Ziffer</label>
|
||||
<label for="5th">[{oxmultilang ident="D3_TOTP_INPUT_FIFTH"}]</label>
|
||||
<input type="text" name="d3totp[]" class="digit" id="5th" inputmode="numeric" pattern="[0-9]*" maxlength="1" onkeyup="clickEvent('4th', '6th')" autocomplete="off">
|
||||
<label for="6th">sechste TOTP-Ziffer</label>
|
||||
<label for="6th">[{oxmultilang ident="D3_TOTP_INPUT_SIXTH"}]</label>
|
||||
<input type="text" name="d3totp[]" class="digit" id="6th" inputmode="numeric" pattern="[0-9]*" maxlength="1" onkeyup="clickEvent('5th', null)" autocomplete="off">
|
||||
</div>
|
||||
|
@ -1,13 +1,7 @@
|
||||
[{include file="headitem.tpl" title="GENERAL_ADMIN_TITLE"|oxmultilangassign}]
|
||||
[{include file="@d3totp/admin/inc/bootstrap.tpl" }]
|
||||
|
||||
[{oxstyle include="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"}]
|
||||
[{oxscript include="https://code.jquery.com/jquery-3.2.1.slim.min.js"}]
|
||||
[{oxscript include="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"}]
|
||||
[{oxscript include="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"}]
|
||||
[{oxstyle include="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/solid.min.css"}]
|
||||
[{oxstyle}]
|
||||
|
||||
[{assign var="totp" value=$edit->d3GetTotp()}]
|
||||
[{assign var="totp" value=$edit->d3GetSessionedTotp()}]
|
||||
[{assign var="userid" value=$edit->getId()}]
|
||||
[{$totp->loadByUserId($userid)}]
|
||||
|
||||
@ -69,7 +63,7 @@
|
||||
[{if $oxid && $oxid != '-1'}]
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<div class="col-6">
|
||||
<div class="card">
|
||||
[{block name="user_d3user_totp_form1"}]
|
||||
[{if false == $totp->getId()}]
|
||||
@ -86,6 +80,15 @@
|
||||
[{oxinputhelp ident="D3_TOTP_QRCODE_HELP"}]
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<label for="secret">[{oxmultilang ident="D3_TOTP_SECRET" suffix="COLON"}]</label>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<textarea rows="3" cols="50" id="secret" name="secret" class="editinput" readonly="readonly">[{$totp->getSecret()}]</textarea>
|
||||
[{oxinputhelp ident="D3_TOTP_SECRET_HELP"}]
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
[{elseif $force2FA}]
|
||||
<div class="card-header">
|
||||
@ -108,7 +111,7 @@
|
||||
[{oxmultilang ident="D3_TOTP_REGISTERDELETE_DESC"}]
|
||||
<br>
|
||||
<br>
|
||||
<button type="submit" [{$readonly}] class="btn btn-primary btn-outline-danger btn-sm" onClick="document.myedit.fnc.value='delete'">
|
||||
<button type="submit" [{$readonly}] class="btn btn-outline-danger btn-sm" onClick="document.myedit.fnc.value='delete'">
|
||||
[{oxmultilang ident="D3_TOTP_REGISTERDELETE"}]
|
||||
</button>
|
||||
</div>
|
||||
@ -119,7 +122,7 @@
|
||||
[{/block}]
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<div class="col-6">
|
||||
<div class="card">
|
||||
[{block name="user_d3user_totp_form2"}]
|
||||
[{if false == $totp->getId()}]
|
||||
@ -127,15 +130,6 @@
|
||||
[{oxmultilang ident="D3_TOTP_CONFIRMATION"}]
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<label for="secret">[{oxmultilang ident="D3_TOTP_SECRET" suffix="COLON"}]</label>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<textarea rows="3" cols="50" id="secret" name="secret" class="editinput" readonly="readonly">[{$totp->getSecret()}]</textarea>
|
||||
[{oxinputhelp ident="D3_TOTP_SECRET_HELP"}]
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" style="margin-top: 20px;">
|
||||
<div class="col-4">
|
||||
<label for="otp">[{oxmultilang ident="D3_TOTP_CURROTP"}]</label>
|
||||
@ -145,7 +139,7 @@
|
||||
[{oxinputhelp ident="D3_TOTP_CURROTP_HELP"}]
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="row" style="margin-top: 20px">
|
||||
<div class="col-4"></div>
|
||||
<div class="col-8">
|
||||
<button type="submit" [{$readonly}] class="btn btn-primary btn-success btn-sm" onClick="document.myedit.fnc.value='save'">
|
@ -2,7 +2,7 @@
|
||||
|
||||
<h1 class="page-header">[{oxmultilang ident="D3_TOTP_ACCOUNT"}]</h1>
|
||||
|
||||
[{assign var="totp" value=$user->d3GetTotp()}]
|
||||
[{assign var="totp" value=$user->d3GetSessionedTotp()}]
|
||||
[{assign var="userid" value=$user->getId()}]
|
||||
[{$totp->loadByUserId($userid)}]
|
||||
|
||||
@ -37,10 +37,15 @@
|
||||
<input type="hidden" name="cl" value="[{$oViewConf->getActiveClassName()}]">
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<input id="totp_use" value="1" type="checkbox" name="totp_use" [{if $totp->getId()}] checked[{/if}] [{if false == $totp->getId()}]onclick="$('.registerNew').toggle(); $('.submitBtn').toggle();"[{/if}]>
|
||||
<label for="totp_use">[{oxmultilang ident="D3_TOTP_ACCOUNT_USE"}]</label>
|
||||
</p>
|
||||
<div class="[{* wave *}] card">
|
||||
<div class="[{* wave *}] card-header">
|
||||
[{oxmultilang ident="D3_TOTP_STATUS"}]
|
||||
</div>
|
||||
<div class="[{* wave *}] card-body">
|
||||
<input id="totp_use" value="1" type="checkbox" name="totp_use" [{if $totp->getId()}] checked[{/if}] [{if false == $totp->getId()}]onclick="$('.registerNew').toggle(); $('.submitBtn').toggle();"[{/if}]>
|
||||
<label for="totp_use">[{oxmultilang ident="D3_TOTP_ACCOUNT_USE"}]</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
[{if false == $totp->getId()}]
|
||||
<div class="registerNew [{* flow *}] panel panel-default [{* wave *}] card">
|
||||
@ -93,22 +98,22 @@
|
||||
|
||||
[{if $totp->getId()}]
|
||||
[{block name="d3_account_totp_deletenotes"}]
|
||||
<div class="[{* flow *}] panel panel-default [{* wave *}] card">
|
||||
<div class="[{* flow *}] panel-heading [{* wave *}] card-header">
|
||||
<div class="[{* wave *}] card">
|
||||
<div class="[{* wave *}] card-header">
|
||||
[{oxmultilang ident="D3_TOTP_REGISTEREXIST"}]
|
||||
</div>
|
||||
<div class="[{* flow *}] panel-body [{* wave *}] card-body">
|
||||
<div class="[{* wave *}] card-body">
|
||||
[{oxmultilang ident="D3_TOTP_REGISTERDELETE_DESC"}]
|
||||
</div>
|
||||
</div>
|
||||
[{/block}]
|
||||
|
||||
[{block name="d3_account_totp_backupcodes"}]
|
||||
<div class="[{* flow *}] panel panel-default [{* wave *}] card">
|
||||
<div class="[{* flow *}] panel-heading [{* wave *}] card-header">
|
||||
<div class="[{* wave *}] card">
|
||||
<div class="[{* wave *}] card-header">
|
||||
[{oxmultilang ident="D3_TOTP_BACKUPCODES"}]
|
||||
</div>
|
||||
<div class="[{* flow *}] panel-body [{* wave *}] card-body">
|
||||
<div class="[{* wave *}] card-body">
|
||||
[{if $oView->getBackupCodes()}]
|
||||
[{block name="d3_account_totp_backupcodes_list"}]
|
||||
<label for="backupcodes">[{oxmultilang ident="D3_TOTP_BACKUPCODES_DESC"}]</label>
|
@ -17,24 +17,26 @@
|
||||
|
||||
<h3>[{oxmultilang ident="D3_TOTP_INPUT"}]</h3>
|
||||
|
||||
[{if !empty($Errors.default)}]
|
||||
[{include file="inc_error.tpl" Errorlist=$Errors.default}]
|
||||
[{/if}]
|
||||
[{* [{if !empty($Errors.default)}]*}]
|
||||
[{* [{include file="inc_error.tpl" Errorlist=$Errors.default}]*}]
|
||||
[{* [{/if}]*}]
|
||||
|
||||
<div>[{oxmultilang ident="D3_TOTP_INPUT_HELP"}]</div>
|
||||
|
||||
[{$oView->getBackupCodeCountMessage()}]
|
||||
|
||||
<div class="container">
|
||||
<label for="1st">erste TOTP-Ziffer</label>
|
||||
<label for="1st">[{oxmultilang ident="D3_TOTP_INPUT_FIRST"}]</label>
|
||||
<input type="text" name="d3totp[]" class="digit" id='1st' inputmode="numeric" pattern="[0-9]*" maxlength="1" onkeyup="clickEvent(null, '2nd')" autofocus autocomplete="off">
|
||||
<label for="2nd">zweite TOTP-Ziffer</label>
|
||||
<label for="2nd">[{oxmultilang ident="D3_TOTP_INPUT_SECOND"}]</label>
|
||||
<input type="text" name="d3totp[]" class="digit" id="2nd" inputmode="numeric" pattern="[0-9]*" maxlength="1" onkeyup="clickEvent('1st', '3rd')" autocomplete="off">
|
||||
<label for="3rd">dritte TOTP-Ziffer</label>
|
||||
<label for="3rd">[{oxmultilang ident="D3_TOTP_INPUT_THIRD"}]</label>
|
||||
<input type="text" name="d3totp[]" class="digit" id="3rd" inputmode="numeric" pattern="[0-9]*" maxlength="1" onkeyup="clickEvent('2nd', '4th')" autocomplete="off">
|
||||
<label for="4th">vierte TOTP-Ziffer</label>
|
||||
<label for="4th">[{oxmultilang ident="D3_TOTP_INPUT_FOURTH"}]</label>
|
||||
<input type="text" name="d3totp[]" class="digit" id="4th" inputmode="numeric" pattern="[0-9]*" maxlength="1" onkeyup="clickEvent('3rd', '5th')" autocomplete="off">
|
||||
<label for="5th">fünfte TOTP-Ziffer</label>
|
||||
<label for="5th">[{oxmultilang ident="D3_TOTP_INPUT_FIFTH"}]</label>
|
||||
<input type="text" name="d3totp[]" class="digit" id="5th" inputmode="numeric" pattern="[0-9]*" maxlength="1" onkeyup="clickEvent('4th', '6th')" autocomplete="off">
|
||||
<label for="6th">sechste TOTP-Ziffer</label>
|
||||
<label for="6th">[{oxmultilang ident="D3_TOTP_INPUT_SIXTH"}]</label>
|
||||
<input type="text" name="d3totp[]" class="digit" id="6th" inputmode="numeric" pattern="[0-9]*" maxlength="1" onkeyup="clickEvent('5th', null)" autocomplete="off">
|
||||
</div>
|
||||
|
||||
@ -60,8 +62,6 @@
|
||||
[{/capture}]
|
||||
[{oxscript add=$smarty.capture.d3js}]
|
||||
|
||||
<div>[{oxmultilang ident="D3_TOTP_INPUT_HELP"}]</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
[{oxmultilang ident="D3_TOTP_SUBMIT_LOGIN"}]
|
||||
</button><br>
|
92
views/twig/admin/d3totplogin.html.twig
Normal file
92
views/twig/admin/d3totplogin.html.twig
Normal file
@ -0,0 +1,92 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ translate({ ident: "LOGIN_TITLE" }) }}</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset={{ charset }}">
|
||||
<meta name="ROBOTS" content="NOINDEX, NOFOLLOW">
|
||||
<link rel="shortcut icon" href="{{ oViewConf.getImageUrl()|raw }}favicon.ico">
|
||||
<link rel="stylesheet" href="{{ oViewConf.getResourceUrl()|raw }}login.css">
|
||||
<link rel="stylesheet" href="{{ oViewConf.getResourceUrl()|raw }}colors_{{ oViewConf.getEdition()|lower }}.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="admin-login-box">
|
||||
|
||||
<div id="shopLogo"><img src="{{ oViewConf.getImageUrl('logo_dark.svg')|raw }}" alt="" /></div>
|
||||
|
||||
<form action="{{ oViewConf.getSelfLink()|raw }}" method="post" id="login">
|
||||
|
||||
{% block admin_login_form %}
|
||||
{{ oViewConf.getHiddenSid()|raw }}
|
||||
|
||||
<input type="hidden" name="fnc" value="checklogin">
|
||||
<input type="hidden" name="cl" value="{{ oViewConf.getActiveClassName() }}">
|
||||
<input type="hidden" name="profile" value="{{ selectedProfile }}">
|
||||
<input type="hidden" name="chlanguage" value="{{ selectedChLanguage }}">
|
||||
|
||||
<h3>{{ translate({ ident: "TOTP_INPUT" }) }}</h3>
|
||||
|
||||
{% if not empty(Errors.default) %}
|
||||
{% include "inc_error.html.twig" with {Errorlist: Errors.default} %}
|
||||
{% endif %}
|
||||
|
||||
{{ oView.getBackupCodeCountMessage()|raw }}
|
||||
|
||||
<div class="container">
|
||||
<label for="1st">{{ translate({ ident: "D3_TOTP_INPUT_FIRST" }) }}</label>
|
||||
<input type="text" name="d3totp[]" class="digit" id='1st' inputmode="numeric" pattern="[0-9]*" maxlength="1" onkeyup="clickEvent(null, '2nd')" autofocus autocomplete="off">
|
||||
<label for="2nd">{{ translate({ ident: "D3_TOTP_INPUT_SECOND" }) }}</label>
|
||||
<input type="text" name="d3totp[]" class="digit" id="2nd" inputmode="numeric" pattern="[0-9]*" maxlength="1" onkeyup="clickEvent('1st', '3rd')" autocomplete="off">
|
||||
<label for="3rd">{{ translate({ ident: "D3_TOTP_INPUT_THIRD" }) }}</label>
|
||||
<input type="text" name="d3totp[]" class="digit" id="3rd" inputmode="numeric" pattern="[0-9]*" maxlength="1" onkeyup="clickEvent('2nd', '4th')" autocomplete="off">
|
||||
<label for="4th">{{ translate({ ident: "D3_TOTP_INPUT_FOURTH" }) }}</label>
|
||||
<input type="text" name="d3totp[]" class="digit" id="4th" inputmode="numeric" pattern="[0-9]*" maxlength="1" onkeyup="clickEvent('3rd', '5th')" autocomplete="off">
|
||||
<label for="5th">{{ translate({ ident: "D3_TOTP_INPUT_FIFTH" }) }}</label>
|
||||
<input type="text" name="d3totp[]" class="digit" id="5th" inputmode="numeric" pattern="[0-9]*" maxlength="1" onkeyup="clickEvent('4th', '6th')" autocomplete="off">
|
||||
<label for="6th">{{ translate({ ident: "D3_TOTP_INPUT_SIXTH" }) }}</label>
|
||||
<input type="text" name="d3totp[]" class="digit" id="6th" inputmode="numeric" pattern="[0-9]*" maxlength="1" onkeyup="clickEvent('5th', null)" autocomplete="off">
|
||||
</div>
|
||||
|
||||
{% set d3js %}
|
||||
function clickEvent(previous, next){
|
||||
const digitKeys = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||||
const deleteKeys = ['Backspace', 'Delete'];
|
||||
if(next && digitKeys.includes(event.key)){
|
||||
document.getElementById(next).focus();
|
||||
} else if(previous && deleteKeys.includes(event.key)){
|
||||
document.getElementById(previous).focus();
|
||||
}
|
||||
}
|
||||
document.addEventListener("paste", function(e) {
|
||||
if (e.target.type === "text") {
|
||||
var data = e.clipboardData.getData('Text');
|
||||
data = data.split('');
|
||||
[].forEach.call(document.querySelectorAll("#login input[type=text]"), (node, index) => {
|
||||
node.value = data[index];
|
||||
});
|
||||
}
|
||||
});
|
||||
{% endset %}
|
||||
{{ script({ add: d3js.__toString(), dynamic: __oxid_include_dynamic }) }}
|
||||
|
||||
<div>{{ translate({ ident: "TOTP_INPUT_HELP" }) }}</div>
|
||||
|
||||
<input type="submit" value="{{ translate({ ident: "LOGIN_START" }) }}" class="btn"><br>
|
||||
|
||||
<input class="btn btn_cancel" value="{{ translate({ ident: "TOTP_CANCEL_LOGIN" }) }}" type="submit"
|
||||
onclick="document.getElementById('login').fnc.value='d3CancelLogin'; document.getElementById('login').submit();"
|
||||
>
|
||||
|
||||
{{ style({ include: oViewConf.getModuleUrl('d3totp', 'out/admin/src/css/d3totplogin.css') }) }}
|
||||
|
||||
{{ style() }}
|
||||
|
||||
{% endblock %}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{{ script({ dynamic: __oxid_include_dynamic }) }}
|
||||
<script type="text/javascript">if (window !== window.top) top.location.href = document.location.href;</script>
|
||||
|
||||
</body>
|
||||
</html>
|
188
views/twig/admin/d3user_totp.html.twig
Normal file
188
views/twig/admin/d3user_totp.html.twig
Normal file
@ -0,0 +1,188 @@
|
||||
{% include "headitem.html.twig" with {title: "GENERAL_ADMIN_TITLE"|translate} %}
|
||||
{% include "@d3totp/admin/inc/bootstrap.html.twig" %}
|
||||
|
||||
{% set totp = edit.d3GetSessionedTotp() %}
|
||||
{% set userid = edit.getId() %}
|
||||
{{ totp.loadByUserId(userid) }}
|
||||
|
||||
{% if readonly %}
|
||||
{% set readonly = "readonly disabled" %}
|
||||
{% else %}
|
||||
{% set readonly = "" %}
|
||||
{% endif %}
|
||||
|
||||
<style>
|
||||
td.edittext {
|
||||
white-space: normal;
|
||||
}
|
||||
.hero {
|
||||
display: inline-block;
|
||||
}
|
||||
.hero > h1 {
|
||||
padding: 0.3em 0;
|
||||
}
|
||||
.hero > div {
|
||||
text-align: right;
|
||||
color: #6c7c98;
|
||||
}
|
||||
.container-fluid {
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
|
||||
{% if force2FA %}
|
||||
<div class="hero">
|
||||
<h1>{{ translate({ ident: "D3_TOTP_FORCE2FATITLE" }) }}</h1>
|
||||
<div>{{ translate({ ident: "D3_TOTP_FORCE2FASUB" }) }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form name="transfer" id="transfer" action="{{ oViewConf.getSelfLink()|raw }}" method="post">
|
||||
{{ oViewConf.getHiddenSid()|raw }}
|
||||
<input type="hidden" name="oxid" value="{{ oxid }}">
|
||||
<input type="hidden" name="cl" value="{{ oViewConf.getActiveClassName() }}">
|
||||
</form>
|
||||
|
||||
<form name="myedit" id="myedit" action="{{ oViewConf.getSelfLink()|raw }}" method="post" style="padding: 0;margin: 0;height:0;">
|
||||
{{ oViewConf.getHiddenSid()|raw }}
|
||||
<input type="hidden" name="cl" value="{{ oViewConf.getActiveClassName() }}">
|
||||
<input type="hidden" name="fnc" value="">
|
||||
<input type="hidden" name="oxid" value="{{ oxid }}">
|
||||
<input type="hidden" name="editval[d3totp__oxid]" value="{{ totp.getId() }}">
|
||||
<input type="hidden" name="editval[d3totp__oxuserid]" value="{{ oxid }}">
|
||||
|
||||
{% if sSaveError %}
|
||||
<table style="padding:0; border:0; width:98%;">
|
||||
<tr>
|
||||
<td></td>
|
||||
<td class="errorbox">{{ translate({ ident: sSaveError }) }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% if oxid and oxid != ' - 1' %}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="card">
|
||||
{% block user_d3user_totp_form1 %}
|
||||
{% if false == totp.getId() %}
|
||||
<div class="card-header">
|
||||
{{ translate({ ident: "D3_TOTP_REGISTERNEW" }) }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
{{ translate({ ident: "D3_TOTP_QRCODE", suffix: "COLON" }) }}
|
||||
</div>
|
||||
<div class="col-8">
|
||||
{{ totp.getQrCodeElement()|raw }}
|
||||
{% include "inputhelp.html.twig" with {'sHelpId': help_id("D3_TOTP_QRCODE_HELP"), 'sHelpText': help_text("D3_TOTP_QRCODE_HELP")} %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<label for="secret">{{ translate({ ident: "D3_TOTP_SECRET", suffix: "COLON" }) }}</label>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<textarea rows="3" cols="50" id="secret" name="secret" class="editinput" readonly="readonly">{{ totp.getSecret() }}</textarea>
|
||||
{% include "inputhelp.html.twig" with {'sHelpId': help_id("D3_TOTP_SECRET_HELP"), 'sHelpText': help_text("D3_TOTP_SECRET_HELP")} %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% elseif force2FA %}
|
||||
<div class="card-header">
|
||||
{{ translate({ ident: "D3_TOTP_ADMINBACKEND" }) }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<input
|
||||
type="submit" class="edittext" id="oLockButton" name="delete"
|
||||
value="{{ translate({ ident: "D3_TOTP_ADMINCONTINUE" }) }}"
|
||||
onClick="document.myedit.fnc.value='';document.myedit.cl.value='admin_start'"
|
||||
>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="card-header">
|
||||
{{ translate({ ident: "D3_TOTP_REGISTEREXIST" }) }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
{{ translate({ ident: "D3_TOTP_REGISTERDELETE_DESC" }) }}
|
||||
<br>
|
||||
<br>
|
||||
<button type="submit" {{ readonly }} class="btn btn-outline-danger btn-sm" onClick="document.myedit.fnc.value='delete'">
|
||||
{{ translate({ ident: "D3_TOTP_REGISTERDELETE" }) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="card">
|
||||
{% block user_d3user_totp_form2 %}
|
||||
{% if false == totp.getId() %}
|
||||
<div class="card-header">
|
||||
{{ translate({ ident: "D3_TOTP_CONFIRMATION" }) }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row" style="margin-top: 20px;">
|
||||
<div class="col-4">
|
||||
<label for="otp">{{ translate({ ident: "D3_TOTP_CURROTP" }) }}</label>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<input type="text" class="editinput" size="6" maxlength="6" id="otp" name="otp" value="" autofocus="autofocus" {{ readonly }}>
|
||||
{% include "inputhelp.html.twig" with {'sHelpId': help_id("D3_TOTP_CURROTP_HELP"), 'sHelpText': help_text("D3_TOTP_CURROTP_HELP")} %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" style="margin-top: 20px;">
|
||||
<div class="col-4"></div>
|
||||
<div class="col-8">
|
||||
<button type="submit" {{ readonly }} class="btn btn-primary btn-success btn-sm" onClick="document.myedit.fnc.value='save'">
|
||||
{{ translate({ ident: "D3_TOTP_SAVE" }) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="card-header">
|
||||
{{ translate({ ident: "D3_TOTP_BACKUPCODES" }) }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if oView.getBackupCodes() %}
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<label for="backupcodes">{{ translate({ ident: "D3_TOTP_BACKUPCODES_DESC" }) }}</label>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<textarea id="backupcodes" rows="10" cols="20">{{ oView.getBackupCodes() }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
{{ translate({ ident: "D3_TOTP_AVAILBACKUPCODECOUNT", args: oView.getAvailableBackupCodeCount() }) }}
|
||||
<br>
|
||||
<br>
|
||||
{{ translate({ ident: "D3_TOTP_AVAILBACKUPCODECOUNT_DESC" }) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
|
||||
{% if not force2FA %}
|
||||
{% include "bottomnaviitem.html.twig" %}
|
||||
{% include "bottomitem.html.twig" %}
|
||||
{% endif %}
|
16
views/twig/admin/inc/bootstrap.html.twig
Normal file
16
views/twig/admin/inc/bootstrap.html.twig
Normal file
@ -0,0 +1,16 @@
|
||||
<link rel="stylesheet" href="{{ oViewConf.getModuleUrl('d3totp', 'out/src/css/bootstrap.min.css') }}">
|
||||
<style>
|
||||
* {
|
||||
font-size: 12px;
|
||||
}
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
select {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
div.actions {
|
||||
box-sizing: initial;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,29 @@
|
||||
{% if request_totp %}
|
||||
{{ oViewConf.getHiddenSid()|raw }}
|
||||
|
||||
<input type="hidden" name="fnc" value="checklogin">
|
||||
<input type="hidden" name="cl" value="login">
|
||||
|
||||
{% if not empty(Errors.default) %}
|
||||
{% include "inc_error.html.twig" with {Errorlist: Errors.default} %}
|
||||
{% endif %}
|
||||
|
||||
{{ oView.getBackupCodeCountMessage() }}
|
||||
|
||||
<label for="d3totp">{{ translate({ ident: "TOTP_INPUT" }) }}</label>
|
||||
<input type="text" name="d3totp" id="d3totp" value="" size="49" autofocus autocomplete="off"><br>
|
||||
|
||||
{{ translate({ ident: "TOTP_INPUT_HELP" }) }}
|
||||
|
||||
{# prevent cancel button (1st button) action when form is sent via Enter key #}
|
||||
<input type="submit" style="display:none !important;">
|
||||
|
||||
<input class="btn btn_cancel" value="{{ translate({ ident: "TOTP_CANCEL_LOGIN" }) }}" type="submit"
|
||||
onclick="document.getElementById('login').fnc.value='d3CancelLogin'; document.getElementById('login').submit();"
|
||||
>
|
||||
|
||||
{{ style({ include: oViewConf.getModuleUrl('d3totp', 'out/admin/src/css/d3totplogin.css') }) }}
|
||||
{{ style() }}
|
||||
{% else %}
|
||||
{{ parent() }}
|
||||
{% endif %}
|
Sommige bestanden werden niet getoond omdat er teveel bestanden zijn veranderd in deze diff Meer weergeven
Laden…
Verwijs in nieuw issue
Block a user