Compare commits

...

33 Commits

Author SHA1 Message Date
be4990f7f7 make database migrations executable via Doctrine 2024-09-30 11:20:18 +02:00
1251095eb3
add tests 2024-09-29 23:28:35 +02:00
fedd03a66e can generate PHPUnit code coverage 2024-09-23 09:17:43 +02:00
a0090ab3bb remove testing tools from productive code 2024-09-23 09:08:30 +02:00
be1f482aba adjust tests 2024-09-23 08:48:05 +02:00
30e13cb0dc
improve code style 2024-09-22 00:42:24 +02:00
ceaa9daf90
adjust documentation 2024-09-21 23:49:32 +02:00
e509aa7eff
get module setting from service 2024-09-21 13:34:44 +02:00
12d47d14f0
modify translations 2024-09-20 23:02:04 +02:00
f8f488f7a2
apply selected admin language and profile 2024-09-20 22:53:40 +02:00
bef6651a3f
assert sessioned OTP object 2024-09-20 21:57:13 +02:00
f43eb2584a
fix SEO URL generator 2024-09-20 21:56:36 +02:00
faf08ed786
align smarty templates 2024-09-20 21:55:25 +02:00
b2ba738b05 fix twig templates 2024-09-20 10:35:14 +02:00
5d4f29988e remove unused Smarty block extension 2024-09-09 11:47:18 +02:00
0b0f15605f
extend account pages 2024-09-09 00:10:31 +02:00
f23bd1339c
get seed from otp saved in session 2024-09-09 00:06:17 +02:00
90013b3832
prevent code duplications, add assertions, get seed from session 2024-09-09 00:04:21 +02:00
49e54d6866
restructure twig folders 2024-09-09 00:00:30 +02:00
95aa47fa09
fix twig templates 2024-09-08 00:21:35 +02:00
048057e40b
cleanup metadata 2024-09-08 00:19:54 +02:00
e6cb61e023
modify backend totp panel layout 2024-09-08 00:19:14 +02:00
209b05e347
add backend bootstrap styles 2024-09-08 00:13:29 +02:00
55ed6a1568
get seed from session instead of user input, replace removed confKey, use QueryBuilder 2024-09-08 00:05:06 +02:00
cfde971a0e
assert backend initialisation values 2024-09-08 00:02:27 +02:00
79fb841b37 user TemplateCacheService for setup action 2024-09-06 15:55:01 +02:00
d93e1517aa adjust translation files 2024-09-06 15:54:24 +02:00
5ee8e18e89 fix migration path 2024-09-06 15:54:01 +02:00
bedb60928d add Twig templates 2024-09-06 15:53:28 +02:00
43e8291bf9
change table structure by migrations 2024-09-05 23:53:55 +02:00
ed06705c40
align folder structure 2024-09-05 23:10:39 +02:00
9f6b689a56
make installable in OXID 7 2024-09-05 22:52:44 +02:00
0adcec6df2
use vector module icon 2023-12-05 22:41:28 +01:00
105 changed files with 4073 additions and 1626 deletions

4
.gitignore vendored
View File

@ -1,3 +1,3 @@
src/tests/.phpunit.result.cache
src/tests/reports/
Tests/.phpunit.result.cache
Tests/reports/
.php_cs.cache

View File

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

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

View File

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

View File

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

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

View File

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

View File

@ -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 = [];

View File

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

View File

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

View File

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

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

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

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

View File

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

View 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

View File

@ -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',
];

View 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

View File

@ -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',
];

View 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

View 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

View 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

View 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

View File

@ -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',
];

View File

@ -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',
];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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&amp;'.
'profile='.$this->d3TotpGetSession()->getVariable(d3totp_conf::SESSION_ADMIN_PROFILE).'&amp;'.
'chlanguage='.$this->d3TotpGetSession()->getVariable(d3totp_conf::SESSION_ADMIN_CHLANGUAGE),
'stoken='.Registry::getRequest()->getRequestEscapedParameter('stoken').'&amp;'.
'profile='.Registry::getRequest()->getRequestEscapedParameter('profile').'&amp;'.
'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);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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'];
}
}

View File

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

View File

@ -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],

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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'
)
);
}
}

View 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
View 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

File diff suppressed because one or more lines are too long

93
assets/picture.svg Normal file
View 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>

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@ -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
View 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&sup3; 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
View 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
View 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.

View 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');
}
}
}

View 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
View File

@ -0,0 +1,4 @@
table_storage:
table_name: d3migrations_totp
migrations_paths:
'D3\Totp\Migrations': data

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

View File

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

View 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>

View File

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

View File

@ -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'">

View File

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

View File

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

View 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>

View 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 %}

View 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>

View File

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

Some files were not shown because too many files have changed in this diff Show More