diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index f0512a0..cc696a8 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -1,13 +1,42 @@ + * @link https://www.oxidmodule.com + */ + +declare(strict_types=1); + $finder = PhpCsFixer\Finder::create() ->in(__DIR__) ; +$fileHeaderComment = << +@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) -; \ No newline at end of file + ; \ No newline at end of file diff --git a/Application/Controller/Admin/d3force_2fa.php b/Application/Controller/Admin/d3force_2fa.php index 584edbd..46ad8e4 100644 --- a/Application/Controller/Admin/d3force_2fa.php +++ b/Application/Controller/Admin/d3force_2fa.php @@ -1,5 +1,16 @@ + * @link https://www.oxidmodule.com + */ + declare(strict_types=1); namespace D3\Totp\Application\Controller\Admin; @@ -27,7 +38,12 @@ class d3force_2fa extends d3user_totp return parent::render(); } - + /** + * @return bool + * @throws ContainerExceptionInterface + * @throws ModuleSettingNotFountException + * @throws NotFoundExceptionInterface + */ protected function authorize(): bool { $userID = $this->d3TotpGetSessionObject()->getVariable(d3totp_conf::OXID_ADMIN_AUTH); diff --git a/Application/Controller/Admin/d3totpadminlogin.php b/Application/Controller/Admin/d3totpadminlogin.php index dd044e0..f06723f 100644 --- a/Application/Controller/Admin/d3totpadminlogin.php +++ b/Application/Controller/Admin/d3totpadminlogin.php @@ -22,13 +22,16 @@ 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 @@ -44,16 +47,19 @@ class d3totpadminlogin extends AdminController } /** - * @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 { @@ -79,7 +85,10 @@ class d3totpadminlogin extends AdminController /** * @return string - * @throws DatabaseConnectionException + * @throws ContainerExceptionInterface + * @throws Exception + * @throws NotFoundExceptionInterface + * @throws DBALException */ public function render(): string { @@ -109,7 +118,10 @@ class d3totpadminlogin extends AdminController /** * @return string|void - * @throws DatabaseConnectionException + * @throws ContainerExceptionInterface + * @throws Exception + * @throws NotFoundExceptionInterface + * @throws DBALException */ public function getBackupCodeCountMessage() { @@ -140,16 +152,21 @@ 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() { @@ -192,7 +209,10 @@ class d3totpadminlogin extends AdminController * @param string|null $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 diff --git a/Application/Controller/Admin/d3user_totp.php b/Application/Controller/Admin/d3user_totp.php index 92f6e86..ac638f5 100644 --- a/Application/Controller/Admin/d3user_totp.php +++ b/Application/Controller/Admin/d3user_totp.php @@ -25,7 +25,6 @@ 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\Registry; use OxidEsales\Eshop\Core\UtilsView; use Psr\Container\ContainerExceptionInterface; @@ -122,7 +121,8 @@ class d3user_totp extends AdminDetailsController } /** - * @throws DatabaseConnectionException + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface */ public function delete(): void { diff --git a/Application/Controller/OtpManagementControllerTrait.php b/Application/Controller/OtpManagementControllerTrait.php index 4388ebb..77231ba 100644 --- a/Application/Controller/OtpManagementControllerTrait.php +++ b/Application/Controller/OtpManagementControllerTrait.php @@ -17,7 +17,9 @@ namespace D3\Totp\Application\Controller; use D3\Totp\Application\Model\d3backupcodelist; use D3\Totp\Application\Model\d3totp; -use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException; +use Doctrine\DBAL\Driver\Exception; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; trait OtpManagementControllerTrait { @@ -47,7 +49,10 @@ trait OtpManagementControllerTrait /** * @return int - * @throws DatabaseConnectionException + * @throws Exception + * @throws \Doctrine\DBAL\Exception + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface */ public function getAvailableBackupCodeCount(): int { @@ -62,4 +67,4 @@ trait OtpManagementControllerTrait { return oxNew(d3totp::class); } -} \ No newline at end of file +} diff --git a/Application/Controller/d3_account_totp.php b/Application/Controller/d3_account_totp.php index 304c3a5..18ebae3 100644 --- a/Application/Controller/d3_account_totp.php +++ b/Application/Controller/d3_account_totp.php @@ -23,7 +23,6 @@ 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; @@ -51,14 +50,18 @@ class d3_account_totp extends AccountController return $this->getUser()->getId(); } + /** + * @return void + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @throws \Doctrine\DBAL\Driver\Exception + */ 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'); @@ -94,11 +97,10 @@ class d3_account_totp extends AccountController } /** - * @throws DatabaseConnectionException - * @throws \Doctrine\DBAL\Driver\Exception - * @throws \Doctrine\DBAL\Exception * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface + * @throws \Doctrine\DBAL\Driver\Exception + * @throws \Doctrine\DBAL\Exception */ public function delete(): void { diff --git a/Application/Controller/d3totplogin.php b/Application/Controller/d3totplogin.php index 9136bd0..2895dec 100644 --- a/Application/Controller/d3totplogin.php +++ b/Application/Controller/d3totplogin.php @@ -18,16 +18,18 @@ 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 = '@'.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); @@ -48,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() { @@ -72,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); } @@ -101,7 +106,7 @@ class d3totplogin extends FrontendController * * @return array */ - public function getBreadCrumb() + public function getBreadCrumb(): array { $aPaths = []; $aPath = []; diff --git a/Application/Factory/BaconQrCodeFactory.php b/Application/Factory/BaconQrCodeFactory.php index d0bbe5b..8766cfd 100644 --- a/Application/Factory/BaconQrCodeFactory.php +++ b/Application/Factory/BaconQrCodeFactory.php @@ -1,5 +1,16 @@ + * @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, diff --git a/Application/Model/Constants.php b/Application/Model/Constants.php index 2cc36c2..a11fb2e 100644 --- a/Application/Model/Constants.php +++ b/Application/Model/Constants.php @@ -1,10 +1,8 @@ getRandomTotpBackupCode(); - $this->assign( - [ - 'oxuserid' => $sUserId, - 'backupcode' => $this->d3EncodeBC($sCode, $sUserId), - ] - ); + $this->assign([ + 'oxuserid' => $sUserId, + 'backupcode' => $this->d3EncodeBC($sCode, $sUserId), + ]); return $sCode; } - public function getRandomTotpBackupCode() + public function getRandomTotpBackupCode(): string { return d3RandomGenerator::getRandomTotpBackupCode(); } /** - * @param $code - * @param $sUserId - * @return false|string - * @throws DatabaseConnectionException + * @param string $code + * @param string $sUserId + * @return string + * @throws ContainerExceptionInterface + * @throws Exception + * @throws NotFoundExceptionInterface + * @throws \Doctrine\DBAL\Exception */ - public function d3EncodeBC($code, $sUserId) + public function d3EncodeBC(string $code, string $sUserId): string { - $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); + $qb = $this->getQueryBuilder(); + $qb->select('BINARY MD5( CONCAT('.$qb->createNamedParameter($code).', UNHEX('.$qb->createNamedParameter($salt).')))'); + + return $qb->execute()->fetchOne(); } - public function d3GetUser() + public function d3GetUser(): User { /** @var User|null $user */ $user = $this->getUser(); @@ -84,8 +93,18 @@ class d3backupcode extends BaseModel /** * @return User */ - public function d3TotpGetUserObject() + 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(); + } } diff --git a/Application/Model/d3backupcodelist.php b/Application/Model/d3backupcodelist.php index 0011874..4066552 100644 --- a/Application/Model/d3backupcodelist.php +++ b/Application/Model/d3backupcodelist.php @@ -16,32 +16,36 @@ 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\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; +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; - /** - * Core table name - * - * @var string - */ + /** @var string */ protected $_sCoreTable = 'd3totp_backupcodes'; - protected $_backupCodes = []; + protected array $_backupCodes = []; /** - * @param $sUserId - * @throws DatabaseConnectionException + * @param string $sUserId + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @throws DBALDriverException + * @throws DBALException */ - public function generateBackupCodes($sUserId) + public function generateBackupCodes(string $sUserId): void { $this->deleteAllFromUser($sUserId); @@ -59,7 +63,7 @@ class d3backupcodelist extends ListModel /** * @return d3backupcode */ - public function getD3BackupCodeObject() + public function getD3BackupCodeObject(): d3backupcode { return oxNew(d3backupcode::class); } @@ -67,7 +71,7 @@ class d3backupcodelist extends ListModel /** * @return Config */ - public function d3GetConfig() + public function d3GetConfig(): Config { return Registry::getConfig(); } @@ -75,7 +79,7 @@ class d3backupcodelist extends ListModel /** * @throws Exception */ - public function save() + public function save(): void { /** @var d3backupcode $oBackupCode */ foreach ($this->getArray() as $oBackupCode) { @@ -86,7 +90,7 @@ class d3backupcodelist extends ListModel /** * @return d3backupcode */ - public function getBaseObject() + public function getBaseObject(): d3backupcode { /** @var d3backupcode $object */ $object = parent::getBaseObject(); @@ -95,19 +99,32 @@ class d3backupcodelist extends ListModel } /** - * @param $totp + * @param string $totp * @return bool - * @throws DatabaseConnectionException + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @throws DBALDriverException + * @throws DBALException */ - public function verify($totp) + public function verify(string $totp): bool { - $oDb = $this->d3GetDb(); + $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()) + ) + ) + ); - $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); + $sVerify = $qb->execute()->fetchOne(); if ($sVerify) { $this->getBaseObject()->delete($sVerify); @@ -117,26 +134,23 @@ class d3backupcodelist extends ListModel } /** - * @return DatabaseInterface - * @throws DatabaseConnectionException + * @param string $sUserId + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface */ - public function d3GetDb() + public function deleteAllFromUser(string $sUserId): void { - return DatabaseProvider::getDb(DatabaseProvider::FETCH_MODE_ASSOC); - } + $qb = $this->getQueryBuilder(); + $qb->select('oxid') + ->from($this->getBaseObject()->getCoreTableName()) + ->where( + $qb->expr()->eq( + 'oxuserid', + $qb->createNamedParameter($sUserId) + ) + ); - /** - * @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); + $this->selectString($qb->getSQL(), $qb->getParameters()); /** @var d3backupcode $oBackupCode */ foreach ($this->getArray() as $oBackupCode) { @@ -145,22 +159,40 @@ class d3backupcodelist extends ListModel } /** - * @param $sUserId + * @param string $sUserId * @return int - * @throws DatabaseConnectionException + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @throws DBALDriverException + * @throws DBALException */ - public function getAvailableCodeCount($sUserId) + public function getAvailableCodeCount(string $sUserId): int { - $oDb = $this->d3GetDb(); + $qb = $this->getQueryBuilder(); + $qb->select('count(*)') + ->from($this->getBaseObject()->getViewName()) + ->where( + $qb->expr()->eq( + 'oxuserid', + $qb->createNamedParameter($sUserId) + ) + ); - $query = "SELECT count(*) FROM ".$oDb->quoteIdentifier($this->getBaseObject()->getViewName()). - " WHERE ".$oDb->quoteIdentifier('oxuserid')." = ".$oDb->quote($sUserId); - - return (int) $oDb->getOne($query); + return (int) $qb->execute()->fetchOne(); } - public function d3GetUser() + 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(); + } } diff --git a/Application/Model/d3totp.php b/Application/Model/d3totp.php index 732937e..a74045e 100644 --- a/Application/Model/d3totp.php +++ b/Application/Model/d3totp.php @@ -25,7 +25,6 @@ use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\Query\QueryBuilder; use OTPHP\TOTP; use OxidEsales\Eshop\Application\Model\User; -use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException; use OxidEsales\Eshop\Core\Model\BaseModel; use OxidEsales\Eshop\Core\Registry; use OxidEsales\EshopCommunity\Internal\Container\ContainerFactory; @@ -54,13 +53,13 @@ class d3totp extends BaseModel } /** - * @param $userId + * @param string $userId * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface - * @throws Exception * @throws DBALException + * @throws Exception + * @throws NotFoundExceptionInterface */ - public function loadByUserId($userId): void + public function loadByUserId(string $userId): void { $this->userId = $userId; @@ -69,7 +68,6 @@ class d3totp extends BaseModel ->executeQuery() ->fetchOne() ) { - /** @var QueryBuilder $qb */ $qb = $this->getQueryBuilder(); $qb->select('oxid') ->from($this->getViewName()) @@ -82,14 +80,14 @@ class d3totp extends BaseModel } /** - * @param $userId + * @param string $userId * @return bool * @throws ContainerExceptionInterface * @throws DBALException * @throws Exception * @throws NotFoundExceptionInterface */ - public function checkIfAlreadyExist($userId): bool + public function checkIfAlreadyExist(string $userId): bool { $qb = $this->getQueryBuilder(); $qb->select('1') @@ -98,6 +96,7 @@ class d3totp extends BaseModel $qb->expr()->eq('oxuserid', $qb->createNamedParameter($userId)) ) ->setMaxResults(1); + return (bool) $qb->execute()->fetchOne(); } @@ -177,10 +176,10 @@ class d3totp extends BaseModel } /** - * @param $seed + * @param string|null $seed * @return TOTP */ - public function getTotp($seed = null): TOTP + public function getTotp(string $seed = null): TOTP { if (null == $this->totp) { $this->totp = TOTP::create($seed ?: $this->getSavedSecret()); @@ -220,9 +219,9 @@ class d3totp extends BaseModel } /** - * @param $seed + * @param string $seed */ - public function saveSecret($seed): void + public function saveSecret(string $seed): void { $this->assign([ 'seed' => $this->encrypt($seed), @@ -230,13 +229,16 @@ class d3totp extends BaseModel } /** - * @param $totp - * @param $seed + * @param string $totp + * @param string|null $seed * @return bool - * @throws DatabaseConnectionException + * @throws ContainerExceptionInterface + * @throws DBALException * @throws d3totp_wrongOtpException + * @throws NotFoundExceptionInterface + * @throws Exception */ - public function verify($totp, $seed = null): bool + public function verify(string $totp, string $seed = null): bool { $blNotVerified = $this->getTotp($seed)->verify($totp, null, $this->timeWindow) == false; @@ -245,10 +247,14 @@ class d3totp extends BaseModel $blNotVerified = $oBC->verify($totp) == false; if ($blNotVerified) { - throw oxNew(d3totp_wrongOtpException::class); + /** @var d3totp_wrongOtpException $exception */ + $exception = oxNew(d3totp_wrongOtpException::class); + throw $exception; } } elseif ($blNotVerified && $seed !== null) { - throw oxNew(d3totp_wrongOtpException::class); + /** @var d3totp_wrongOtpException $exception */ + $exception = oxNew(d3totp_wrongOtpException::class); + throw $exception; } return !$blNotVerified; @@ -263,12 +269,12 @@ class d3totp extends BaseModel } /** - * @param $plaintext + * @param string $plaintext * @return string */ - public function encrypt($plaintext): string + public function encrypt(string $plaintext): string { - $ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC"); + $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); @@ -276,16 +282,16 @@ class d3totp extends BaseModel } /** - * @param $ciphertext + * @param string $ciphertext * @return false|string */ - public function decrypt($ciphertext): false|string + public function decrypt(string $ciphertext): false|string { $c = $this->d3Base64_decode($ciphertext); - $ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC"); + $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); + $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 @@ -297,18 +303,19 @@ class d3totp extends BaseModel /** * required for unit tests - * @param $source - * @return bool|string + * @param string $source + * @return string */ - public function d3Base64_decode($source): bool|string + public function d3Base64_decode(string $source): string { return base64_decode($source); } /** - * @param null|string $oxid + * @param string|null $oxid * @return bool - * @throws DatabaseConnectionException + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface */ public function delete($oxid = null): bool { diff --git a/Modules/Application/Component/d3_totp_UserComponent.php b/Modules/Application/Component/d3_totp_UserComponent.php index f68c80e..ef54aee 100644 --- a/Modules/Application/Component/d3_totp_UserComponent.php +++ b/Modules/Application/Component/d3_totp_UserComponent.php @@ -24,7 +24,6 @@ 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; @@ -38,7 +37,10 @@ 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) { @@ -68,7 +70,8 @@ class d3_totp_UserComponent extends d3_totp_UserComponent_parent $sUrl = Registry::getConfig()->getShopHomeUrl() . 'cl=d3totplogin'; $this->d3TotpGetUtils()->redirect($sUrl, false); } - } catch (InvalidArgumentException) {} + } catch (InvalidArgumentException) { + } return parent::afterLogin($oUser); } @@ -83,10 +86,9 @@ class d3_totp_UserComponent extends d3_totp_UserComponent_parent /** * @return false|string - * @throws DatabaseConnectionException - * @throws Exception - * @throws DBALException * @throws ContainerExceptionInterface + * @throws DBALException + * @throws Exception * @throws NotFoundExceptionInterface */ public function d3TotpCheckTotpLogin(): false|string @@ -148,7 +150,7 @@ class d3_totp_UserComponent extends d3_totp_UserComponent_parent * @param d3totp $totp * @return bool */ - public function d3TotpIsNoTotpOrNoLogin($totp): bool + public function d3TotpIsNoTotpOrNoLogin(d3totp $totp): bool { return false == Registry::getSession()->getVariable(d3totp_conf::SESSION_CURRENTUSER) || false == $totp->isActive(); @@ -158,10 +160,13 @@ 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): bool + public function d3TotpHasValidTotp(string $sTotp, d3totp $totp): bool { return Registry::getSession()->getVariable(d3totp_conf::SESSION_AUTH) || $totp->verify($sTotp); diff --git a/Modules/Application/Controller/Admin/d3_totp_LoginController.php b/Modules/Application/Controller/Admin/d3_totp_LoginController.php index 8ddf03c..20053c4 100644 --- a/Modules/Application/Controller/Admin/d3_totp_LoginController.php +++ b/Modules/Application/Controller/Admin/d3_totp_LoginController.php @@ -18,9 +18,6 @@ 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; @@ -48,7 +45,6 @@ class d3_totp_LoginController extends d3_totp_LoginController_parent /** * @return mixed|string - * @throws DatabaseConnectionException */ public function checklogin() { @@ -74,13 +70,13 @@ 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(); @@ -97,7 +93,7 @@ class d3_totp_LoginController extends d3_totp_LoginController_parent $iLang = key($aLanguages); } - $myUtilsServer->setOxCookie("oxidadminlanguage", $aLanguages[$iLang]->abbr, time() + 31536000, "/"); + $myUtilsServer->setOxCookie("oxidadminlanguage", $aLanguages[$iLang]->abbr, time() + 31536000); $this->d3TotpGetLangObject()->setTplLanguage($iLang); } diff --git a/Modules/Application/Controller/d3_totp_getUserTrait.php b/Modules/Application/Controller/d3_totp_getUserTrait.php index ebe9fef..33e7d24 100644 --- a/Modules/Application/Controller/d3_totp_getUserTrait.php +++ b/Modules/Application/Controller/d3_totp_getUserTrait.php @@ -18,20 +18,25 @@ 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']); @@ -55,7 +60,7 @@ trait d3_totp_getUserTrait /** * @return d3totp */ - public function d3GetTotpObject() + public function d3GetTotpObject(): d3totp { return oxNew(d3totp::class); } @@ -63,7 +68,7 @@ trait d3_totp_getUserTrait /** * @return Session */ - public function d3TotpGetSessionObject() + public function d3TotpGetSessionObject(): Session { return Registry::getSession(); } diff --git a/Modules/Core/d3_totp_utils.php b/Modules/Core/d3_totp_utils.php index bc5906c..213dbb2 100644 --- a/Modules/Core/d3_totp_utils.php +++ b/Modules/Core/d3_totp_utils.php @@ -18,9 +18,8 @@ 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; @@ -34,8 +33,11 @@ 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() { @@ -43,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); diff --git a/Modules/Core/totpSystemEventHandler.php b/Modules/Core/totpSystemEventHandler.php index b6d877a..50bb364 100644 --- a/Modules/Core/totpSystemEventHandler.php +++ b/Modules/Core/totpSystemEventHandler.php @@ -19,15 +19,26 @@ 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(); @@ -35,7 +46,14 @@ class totpSystemEventHandler extends totpSystemEventHandler_parent $this->d3CallMockableFunction([totpSystemEventHandler_parent::class, '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); @@ -86,7 +104,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); diff --git a/README.en.md b/README.en.md index 8857b03..dd7eccd 100644 --- a/README.en.md +++ b/README.en.md @@ -3,7 +3,7 @@ # 2-factor authentication (one-time password) for OXID eShop -This module provides a 2-factor authentication (time-dependent one-time password / TOTP) for login in front- and backend in addition to user name and password. +This module provides a 2-factor authentication (time-dependent one-time password / TOTP) for login in front- and backend in addition to username and password. ## Features @@ -48,7 +48,7 @@ The necessary configuration can be found in the same area in the "Settings" tab. ## Changelog -See [CHANGELOG](CHANGELOG.md) for further informations. +See [CHANGELOG](CHANGELOG.md) for further information. ## Contributing diff --git a/Setup/Actions.php b/Setup/Actions.php index b2cddeb..0d271b4 100644 --- a/Setup/Actions.php +++ b/Setup/Actions.php @@ -32,13 +32,13 @@ use Psr\Container\NotFoundExceptionInterface; class Actions { public array $seo_de = [ - '2-faktor-authentisierung/' + '2-faktor-authentisierung/', ]; public array $seo_en = [ - 'en/2-factor-authentication/' + 'en/2-factor-authentication/', ]; public array $stdClassName = [ - 'd3_account_totp' + 'd3_account_totp', ]; /** @@ -112,7 +112,7 @@ class Actions return true; } - protected function hasSeoUrl($item, $langId): bool + protected function hasSeoUrl(string $item, int $langId): bool { $seoEncoder = oxNew(SeoEncoder::class); $seoUrl = $seoEncoder->getStaticUrl( diff --git a/Setup/Events.php b/Setup/Events.php index f083fb9..ca9be84 100644 --- a/Setup/Events.php +++ b/Setup/Events.php @@ -15,16 +15,14 @@ declare(strict_types=1); namespace D3\Totp\Setup; -use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException; -use OxidEsales\Eshop\Core\Exception\DatabaseErrorException; +use Exception; // @codeCoverageIgnoreStart class Events { /** * @return void - * @throws DatabaseConnectionException - * @throws DatabaseErrorException + * @throws Exception */ public static function onActivate(): void { @@ -38,7 +36,7 @@ class Events /** * @codeCoverageIgnore */ - public static function onDeactivate() + public static function onDeactivate(): void { } } diff --git a/Tests/unit/Application/Controller/Admin/d3force_2faTest.php b/Tests/unit/Application/Controller/Admin/d3force_2faTest.php index 9eb0bf1..cb2a140 100644 --- a/Tests/unit/Application/Controller/Admin/d3force_2faTest.php +++ b/Tests/unit/Application/Controller/Admin/d3force_2faTest.php @@ -1,5 +1,16 @@ + * @link https://www.oxidmodule.com + */ + namespace D3\Totp\tests\unit\Application\Controller\Admin; use D3\Totp\Application\Controller\Admin\d3force_2fa; diff --git a/Tests/unit/Application/Factory/BaconQrCodeFactoryTest.php b/Tests/unit/Application/Factory/BaconQrCodeFactoryTest.php index 19c9a31..2739e2a 100644 --- a/Tests/unit/Application/Factory/BaconQrCodeFactoryTest.php +++ b/Tests/unit/Application/Factory/BaconQrCodeFactoryTest.php @@ -1,5 +1,16 @@ + * @link https://www.oxidmodule.com + */ + namespace D3\Totp\tests\unit\Application\Factory; use BaconQrCode\Renderer\ImageRenderer; diff --git a/Tests/unit/Modules/Application/Controller/Admin/d3_totp_LoginControllerTest.php b/Tests/unit/Modules/Application/Controller/Admin/d3_totp_LoginControllerTest.php index 470d79a..8ccf3b5 100644 --- a/Tests/unit/Modules/Application/Controller/Admin/d3_totp_LoginControllerTest.php +++ b/Tests/unit/Modules/Application/Controller/Admin/d3_totp_LoginControllerTest.php @@ -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], diff --git a/Tests/unit/d3TotpUnitTestCase.php b/Tests/unit/d3TotpUnitTestCase.php index 1b634db..0c9186d 100644 --- a/Tests/unit/d3TotpUnitTestCase.php +++ b/Tests/unit/d3TotpUnitTestCase.php @@ -1,4 +1,5 @@ + * @link https://www.oxidmodule.com + */ + declare(strict_types=1); namespace D3\Totp\Migrations; @@ -14,7 +25,7 @@ use Doctrine\Migrations\AbstractMigration; final class Version20240905232017 extends AbstractMigration { - public function getDescription() : string + public function getDescription(): string { return 'Extend Database by missing OTP tables and missing columns.'; } @@ -22,7 +33,7 @@ final class Version20240905232017 extends AbstractMigration /** * @throws Exception */ - public function up(Schema $schema) : void + public function up(Schema $schema): void { $this->connection->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string'); @@ -79,9 +90,9 @@ final class Version20240905232017 extends AbstractMigration ->setDefault('CURRENT_TIMESTAMP'); } - $table->hasPrimaryKey() ?:$table->setPrimaryKey(['oxid']); + $table->hasPrimaryKey() ?: $table->setPrimaryKey(['oxid']); - if($table->hasIndex('OXUSERID') === false){ + if ($table->hasIndex('OXUSERID') === false) { $table->addUniqueIndex(['OXUSERID'], 'OXUSERID'); } } @@ -129,18 +140,18 @@ final class Version20240905232017 extends AbstractMigration ->setDefault('CURRENT_TIMESTAMP'); } - $table->hasPrimaryKey() ?:$table->setPrimaryKey(['oxid']); + $table->hasPrimaryKey() ?: $table->setPrimaryKey(['oxid']); - if($table->hasIndex('OXUSERID') === false){ + if ($table->hasIndex('OXUSERID') === false) { $table->addIndex(['OXUSERID'], 'OXUSERID'); } - if($table->hasIndex('BACKUPCODE') === false){ + if ($table->hasIndex('BACKUPCODE') === false) { $table->addIndex(['BACKUPCODE'], 'BACKUPCODE'); } } - public function down(Schema $schema) : void + public function down(Schema $schema): void { // this down() migration is auto-generated, please modify it to your needs } diff --git a/phpstan.neon b/phpstan.neon index a4a669d..811daaf 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,14 +1,21 @@ parameters: scanFiles: - - src/IntelliSenseHelper.php + - IntelliSenseHelper.php + - ../../oxid-esales/oxideshop-ce/source/bootstrap.php - ../../oxid-esales/oxideshop-ce/source/oxfunctions.php + - ../../oxid-esales/oxideshop-ce/source/overridablefunctions.php paths: - - src - level: 5 - phpVersion: 70300 + - . + excludePaths: + - .php-cs-fixer.php + - rector.php + - Tests + level: 6 + phpVersion: 80300 ignoreErrors: - - '#Call to method getFieldData\(\) on an unknown class oxShop.#' - - '#Return type \(array\) of method D3\\Totp\\Application\\Controller\\d3totplogin::getBreadCrumb\(\) should be compatible with return type \(null\) of method OxidEsales\\EshopCommunity\\Application\\Controller\\FrontendController::getBreadCrumb\(\)#' - - '#Parameter \#\d+ \$value of method OxidEsales\\EshopCommunity\\Core\\Config::setConfigParam\(\) expects string, (true|false) given.#' + - '#Return type [()a-z]* of method .*getBreadCrumb\(\) should be compatible with return type [()a-z]* of method\s*.*getBreadCrumb\(\)#' + - '#Unable to resolve the template type T in call to function oxNew#' + - '#(Property|Method) D3\\Totp\\Modules\\.* has no (return type|type) specified.#' + - identifier: missingType.iterableValue parallel: processTimeout: 900.0 \ No newline at end of file