diff --git a/src/Application/Controller/Admin/d3user_totp.php b/src/Application/Controller/Admin/d3user_totp.php index 83dfa85..2e71031 100644 --- a/src/Application/Controller/Admin/d3user_totp.php +++ b/src/Application/Controller/Admin/d3user_totp.php @@ -16,7 +16,7 @@ namespace D3\Totp\Application\Controller\Admin; use D3\Totp\Application\Model\d3totp; -use D3\Totp\Application\Model\Exceptions\d3backupcodelist; +use D3\Totp\Application\Model\d3backupcodelist; use D3\Totp\Modules\Application\Model\d3_totp_user; use Exception; use OxidEsales\Eshop\Application\Controller\Admin\AdminDetailsController; diff --git a/src/Application/Controller/d3totplogin.php b/src/Application/Controller/d3totplogin.php new file mode 100644 index 0000000..d70ecae --- /dev/null +++ b/src/Application/Controller/d3totplogin.php @@ -0,0 +1,76 @@ + + * @link http://www.oxidmodule.com + */ + +namespace D3\Totp\Application\Controller; + +use D3\Totp\Application\Model\d3backupcodelist; +use D3\Totp\Application\Model\d3totp; +use OxidEsales\Eshop\Application\Controller\FrontendController; +use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException; +use OxidEsales\Eshop\Core\Registry; + +class d3totplogin extends FrontendController +{ + protected $_sThisTemplate = 'd3totplogin.tpl'; + + public function render() + { + if (Registry::getSession()->hasVariable(d3totp::TOTP_SESSION_VARNAME) || + false == Registry::getSession()->hasVariable('d3totpCurrentUser') + ) { + Registry::getUtils()->redirect('index.php?cl=start', true, 302); + exit; + } + + $this->addTplParam('navFormParams', Registry::getSession()->getVariable('d3totpNavFormParams')); + + return parent::render(); + } + + /** + * @return string|void + * @throws DatabaseConnectionException + */ + public function getBackupCodeCountMessage() + { + $oBackupCodeList = oxNew(d3backupcodelist::class); + $iCount = $oBackupCodeList->getAvailableCodeCount(Registry::getSession()->getVariable('d3totpCurrentUser')); + + if ($iCount < 4) { + return sprintf( + Registry::getLang()->translateString('D3_TOTP_AVAILBACKUPCODECOUNT', null, true), + $iCount + ); + }; + + return; + } + + public function getPreviousClass() + { + return Registry::getSession()->getVariable('d3totpCurrentClass'); + } + + public function previousClassIsOrderStep() + { + $sClassKey = Registry::getSession()->getVariable('d3totpCurrentClass'); + $resolvedClass = Registry::getControllerClassNameResolver()->getClassNameById($sClassKey); + $resolvedClass = $resolvedClass ? $resolvedClass : 'start'; + + /** @var FrontendController $oController */ + $oController = oxNew($resolvedClass); + return $oController->getIsOrderStep(); + } +} \ No newline at end of file diff --git a/src/Application/Model/d3backupcode.php b/src/Application/Model/d3backupcode.php index 1caa013..c21e515 100644 --- a/src/Application/Model/d3backupcode.php +++ b/src/Application/Model/d3backupcode.php @@ -15,9 +15,11 @@ namespace D3\Totp\Application\Model; +use OxidEsales\Eshop\Application\Model\User; use OxidEsales\Eshop\Core\DatabaseProvider; use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException; use OxidEsales\Eshop\Core\Model\BaseModel; +use OxidEsales\Eshop\Core\Registry; use RandomLib\Factory; use RandomLib\Generator; @@ -54,9 +56,21 @@ class d3backupcode extends BaseModel public function d3EncodeBC($code) { $oDb = DatabaseProvider::getDb(); - $salt = $this->getUser()->getFieldData('oxpasssalt'); + $salt = $this->d3GetUser()->getFieldData('oxpasssalt'); $sSelect = "SELECT BINARY MD5( CONCAT( " . $oDb->quote($code) . ", UNHEX( ".$oDb->quote($salt)." ) ) )"; return $oDb->getOne($sSelect); } + + public function d3GetUser() + { + if ($this->getUser()) { + return $this->getUser(); + } + + $sUserId = Registry::getSession()->getVariable('d3totpCurrentUser'); + $oUser = oxNew(User::class); + $oUser->load($sUserId); + return $oUser; + } } \ No newline at end of file diff --git a/src/Application/Model/Exceptions/d3backupcodelist.php b/src/Application/Model/d3backupcodelist.php similarity index 89% rename from src/Application/Model/Exceptions/d3backupcodelist.php rename to src/Application/Model/d3backupcodelist.php index 26ef289..89947d3 100644 --- a/src/Application/Model/Exceptions/d3backupcodelist.php +++ b/src/Application/Model/d3backupcodelist.php @@ -13,11 +13,12 @@ * @link http://www.oxidmodule.com */ -namespace D3\Totp\Application\Model\Exceptions; +namespace D3\Totp\Application\Model; use D3\Totp\Application\Controller\Admin\d3user_totp; use D3\Totp\Application\Model\d3backupcode; use Exception; +use OxidEsales\Eshop\Application\Model\User; use OxidEsales\Eshop\Core\DatabaseProvider; use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException; use OxidEsales\Eshop\Core\Model\ListModel; @@ -88,7 +89,7 @@ class d3backupcodelist extends ListModel $query = "SELECT oxid FROM ".$this->getBaseObject()->getViewName(). " WHERE ".$oDb->quoteIdentifier('backupcode')." = ".$oDb->quote($this->getBaseObject()->d3EncodeBC($totp))." AND ". - $oDb->quoteIdentifier("oxuserid") ." = ".$oDb->quote($this->getUser()->getId()); + $oDb->quoteIdentifier("oxuserid") ." = ".$oDb->quote($this->d3GetUser()->getId()); $sVerify = $oDb->getOne($query); @@ -130,4 +131,16 @@ class d3backupcodelist extends ListModel return (int) $oDb->getOne($query); } + + public function d3GetUser() + { + if ($this->getUser()) { + return $this->getUser(); + } + + $sUserId = Registry::getSession()->getVariable('d3totpCurrentUser'); + $oUser = oxNew(User::class); + $oUser->load($sUserId); + return $oUser; + } } \ No newline at end of file diff --git a/src/Application/Model/d3totp.php b/src/Application/Model/d3totp.php index 83a843b..3ea275c 100644 --- a/src/Application/Model/d3totp.php +++ b/src/Application/Model/d3totp.php @@ -18,7 +18,6 @@ namespace D3\Totp\Application\Model; use BaconQrCode\Renderer\Image\Svg; use BaconQrCode\Writer; use D3\ModCfg\Application\Model\d3database; -use D3\Totp\Application\Model\Exceptions\d3backupcodelist; use D3\Totp\Application\Model\Exceptions\d3totp_wrongOtpException; use Doctrine\DBAL\DBALException; use OTPHP\TOTP; diff --git a/src/Application/translations/de/d3_totp_lang.php b/src/Application/translations/de/d3_totp_lang.php new file mode 100644 index 0000000..742398e --- /dev/null +++ b/src/Application/translations/de/d3_totp_lang.php @@ -0,0 +1,26 @@ + + * @link http://www.oxidmodule.com + */ + +$sLangName = "Deutsch"; + +$aLang = array( + 'charset' => 'UTF-8', + + 'TOTP_INPUT' => 'Authentisierungscode', + 'TOTP_INPUT_HELP' => 'Das Einmalpasswort erhalten Sie von der Zweifaktorauthentisierung-App auf Ihrem Gerät.', + 'TOTP_CANCEL_LOGIN' => 'Anmeldung abbrechen', +); diff --git a/src/Application/views/tpl/d3totplogin.tpl b/src/Application/views/tpl/d3totplogin.tpl new file mode 100644 index 0000000..a660147 --- /dev/null +++ b/src/Application/views/tpl/d3totplogin.tpl @@ -0,0 +1,42 @@ +[{capture append="oxidBlock_content"}] + [{assign var="template_title" value=""}] + + [{if $oView->previousClassIsOrderStep()}] + [{* ordering steps *}] + [{include file="page/checkout/inc/steps.tpl" active=2}] + [{/if}] + +
+ [{$oViewConf->getHiddenSid()}] + + + + [{$navFormParams}] + + [{if $Errors.default|@count}] + [{include file="inc_error.tpl" Errorlist=$Errors.default}] + [{/if}] + + [{$oView->getBackupCodeCountMessage()}] + + +
+ + [{oxmultilang ident="TOTP_INPUT_HELP"}] + + [{* prevent cancel button (1st button) action when form is sent via Enter key *}] + + + + +
+ + [{oxstyle include=$oViewConf->getModuleUrl('d3totp', 'out/admin/src/css/d3totplogin.css')}] + [{oxstyle}] + + [{insert name="oxid_tracker" title=$template_title}] +[{/capture}] + +[{include file="layout/page.tpl"}] \ No newline at end of file diff --git a/src/IntelliSenseHelper.php b/src/IntelliSenseHelper.php index 8bba996..8fb4397 100644 --- a/src/IntelliSenseHelper.php +++ b/src/IntelliSenseHelper.php @@ -13,6 +13,20 @@ * @link http://www.oxidmodule.com */ +namespace D3\Totp\Modules\Application\Component +{ + class d3_totp_UserComponent_parent extends \OxidEsales\Eshop\Application\Component\UserComponent { } +} + +namespace D3\Totp\Modules\Application\Controller +{ + class d3_totp_UserController_parent extends \OxidEsales\Eshop\Application\Controller\UserController { } + + class d3_totp_PaymentController_parent extends \OxidEsales\Eshop\Application\Controller\PaymentController { } + + class d3_totp_OrderController_parent extends \OxidEsales\Eshop\Application\Controller\OrderController { } +} + namespace D3\Totp\Modules\Application\Controller\Admin { class d3_totp_LoginController_parent extends \OxidEsales\Eshop\Application\Controller\Admin\LoginController { } diff --git a/src/Modules/Application/Component/d3_totp_UserComponent.php b/src/Modules/Application/Component/d3_totp_UserComponent.php new file mode 100644 index 0000000..9149bea --- /dev/null +++ b/src/Modules/Application/Component/d3_totp_UserComponent.php @@ -0,0 +1,134 @@ + + * @link http://www.oxidmodule.com + */ + +namespace D3\Totp\Modules\Application\Component; + +use D3\Totp\Application\Model\d3totp; +use D3\Totp\Application\Model\Exceptions\d3totp_wrongOtpException; +use Doctrine\DBAL\DBALException; +use OxidEsales\Eshop\Application\Model\User; +use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException; +use OxidEsales\Eshop\Core\Registry; + +class d3_totp_UserComponent extends d3_totp_UserComponent_parent +{ + /** + * @return string|void + * @throws DBALException + * @throws DatabaseConnectionException + */ + public function login_noredirect() + { + parent::login_noredirect(); + + $oUser = $this->getUser(); + + if ($oUser && $oUser->getId()) { + $totp = oxNew(d3totp::class); + $totp->loadByUserId($oUser->getId()); + + if ($totp->isActive() + && false == Registry::getSession()->getVariable(d3totp::TOTP_SESSION_VARNAME) + ) { + Registry::getSession()->setVariable( + 'd3totpCurrentClass', + $this->getParent()->getClassKey() != 'd3totplogin' ? $this->getParent()->getClassKey() : 'start'); + Registry::getSession()->setVariable('d3totpCurrentUser', $oUser->getId()); + Registry::getSession()->setVariable( + 'd3totpNavFormParams', + $this->getParent()->getViewConfig()->getNavFormParams() + ); + + $oUser->logout(); + + return "d3totplogin"; + } + } + } + + /** + * @throws DBALException + * @throws DatabaseConnectionException + */ + public function checkTotplogin() + { + $sTotp = Registry::getRequest()->getRequestEscapedParameter('d3totp', true); + + $sUserId = Registry::getSession()->getVariable('d3totpCurrentUser'); + $oUser = oxNew(User::class); + $oUser->load($sUserId); + + $totp = oxNew(d3totp::class); + $totp->loadByUserId($sUserId); + + try { + if (false == $this->isNoTotpOrNoLogin($totp) && $this->hasValidTotp($sTotp, $totp)) { + $this->d3TotpRelogin($oUser, $sTotp); + $this->d3TotpClearSessionVariables(); + + return false; + } + } catch (d3totp_wrongOtpException $oEx) { + Registry::getUtilsView()->addErrorToDisplay($oEx); + } + + return 'd3totplogin'; + } + + /** + * @param d3totp $totp + * @return bool + */ + public function isNoTotpOrNoLogin($totp) + { + return false == Registry::getSession()->getVariable("d3totpCurrentUser") + || false == $totp->isActive(); + } + + /** + * @param string $sTotp + * @param d3totp $totp + * @return bool + * @throws DatabaseConnectionException + * @throws d3totp_wrongOtpException + */ + public function hasValidTotp($sTotp, $totp) + { + return Registry::getSession()->getVariable(d3totp::TOTP_SESSION_VARNAME) || + ( + $sTotp && $totp->verify($sTotp) + ); + } + + /** + * @param User $oUser + * @param $sTotp + */ + public function d3TotpRelogin(User $oUser, $sTotp) + { + Registry::getSession()->setVariable(d3totp::TOTP_SESSION_VARNAME, $sTotp); + Registry::getSession()->setVariable('usr', $oUser->getId()); + $this->setUser(null); + $this->setLoginStatus(USER_LOGIN_SUCCESS); + $this->_afterLogin($oUser); + } + + public function d3TotpClearSessionVariables() + { + Registry::getSession()->deleteVariable('d3totpCurrentClass'); + Registry::getSession()->deleteVariable('d3totpCurrentUser'); + Registry::getSession()->deleteVariable('d3totpNavFormParams'); + } +} \ No newline at end of file diff --git a/src/Modules/Application/Controller/Admin/d3_totp_LoginController.php b/src/Modules/Application/Controller/Admin/d3_totp_LoginController.php index 96a3810..d1d5358 100644 --- a/src/Modules/Application/Controller/Admin/d3_totp_LoginController.php +++ b/src/Modules/Application/Controller/Admin/d3_totp_LoginController.php @@ -16,7 +16,7 @@ namespace D3\Totp\Modules\Application\Controller\Admin; use D3\Totp\Application\Model\d3totp; -use D3\Totp\Application\Model\Exceptions\d3backupcodelist; +use D3\Totp\Application\Model\d3backupcodelist; use D3\Totp\Application\Model\Exceptions\d3totp_wrongOtpException; use Doctrine\DBAL\DBALException; use OxidEsales\Eshop\Application\Model\User; diff --git a/src/Modules/Application/Controller/d3_totp_OrderController.php b/src/Modules/Application/Controller/d3_totp_OrderController.php new file mode 100644 index 0000000..af94879 --- /dev/null +++ b/src/Modules/Application/Controller/d3_totp_OrderController.php @@ -0,0 +1,21 @@ + + * @link http://www.oxidmodule.com + */ + +namespace D3\Totp\Modules\Application\Controller; + +class d3_totp_OrderController extends d3_totp_OrderController_parent +{ + use d3_totp_getUserTrait; +} \ No newline at end of file diff --git a/src/Modules/Application/Controller/d3_totp_PaymentController.php b/src/Modules/Application/Controller/d3_totp_PaymentController.php new file mode 100644 index 0000000..3163749 --- /dev/null +++ b/src/Modules/Application/Controller/d3_totp_PaymentController.php @@ -0,0 +1,21 @@ + + * @link http://www.oxidmodule.com + */ + +namespace D3\Totp\Modules\Application\Controller; + +class d3_totp_PaymentController extends d3_totp_PaymentController_parent +{ + use d3_totp_getUserTrait; +} \ No newline at end of file diff --git a/src/Modules/Application/Controller/d3_totp_UserController.php b/src/Modules/Application/Controller/d3_totp_UserController.php new file mode 100644 index 0000000..e72a5ba --- /dev/null +++ b/src/Modules/Application/Controller/d3_totp_UserController.php @@ -0,0 +1,25 @@ + + * @link http://www.oxidmodule.com + */ + +namespace D3\Totp\Modules\Application\Controller; + +use D3\Totp\Application\Model\d3backupcodelist; +use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException; +use OxidEsales\Eshop\Core\Registry; + +class d3_totp_UserController extends d3_totp_UserController_parent +{ + use d3_totp_getUserTrait; +} \ No newline at end of file diff --git a/src/Modules/Application/Controller/d3_totp_getUserTrait.php b/src/Modules/Application/Controller/d3_totp_getUserTrait.php new file mode 100644 index 0000000..c4c5f99 --- /dev/null +++ b/src/Modules/Application/Controller/d3_totp_getUserTrait.php @@ -0,0 +1,48 @@ + + * @link http://www.oxidmodule.com + */ + +namespace D3\Totp\Modules\Application\Controller; + +use D3\Totp\Application\Model\d3totp; +use Doctrine\DBAL\DBALException; +use OxidEsales\Eshop\Application\Model\User; +use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException; +use OxidEsales\Eshop\Core\Registry; + +trait d3_totp_getUserTrait +{ + /** + * @return bool|object|User + * @throws DatabaseConnectionException + * @throws DBALException + */ + public function getUser() + { + $oUser = parent::getUser(); + + if ($oUser && $oUser->getId()) { + $totp = oxNew(d3totp::class); + $totp->loadByUserId($oUser->getId()); + + if ($totp->isActive() + && false == Registry::getSession()->getVariable(d3totp::TOTP_SESSION_VARNAME) + ) { + return false; + } + } + + return $oUser; + } +} \ No newline at end of file diff --git a/src/Modules/Application/Model/d3_totp_user.php b/src/Modules/Application/Model/d3_totp_user.php index eefce77..e304710 100644 --- a/src/Modules/Application/Model/d3_totp_user.php +++ b/src/Modules/Application/Model/d3_totp_user.php @@ -30,17 +30,4 @@ class d3_totp_user extends d3_totp_user_parent return $return; } - - /** - * @return d3totp - * @throws DatabaseConnectionException - * @throws DBALException - */ - public function d3getTotp() - { - $oTotp = oxNew(d3totp::class); - $oTotp->loadByUserId($this->getId()); - - return $oTotp; - } } \ No newline at end of file diff --git a/src/metadata.php b/src/metadata.php index 134c330..92cd895 100644 --- a/src/metadata.php +++ b/src/metadata.php @@ -17,7 +17,11 @@ use D3\Totp\Setup as ModuleSetup; use D3\ModCfg\Application\Model\d3utils; +use OxidEsales\Eshop\Application\Component\UserComponent; use OxidEsales\Eshop\Application\Controller\Admin\LoginController; +use OxidEsales\Eshop\Application\Controller\OrderController; +use OxidEsales\Eshop\Application\Controller\PaymentController; +use OxidEsales\Eshop\Application\Controller\UserController; use OxidEsales\Eshop\Core\Utils; use OxidEsales\Eshop\Application\Model as OxidModel; @@ -48,15 +52,21 @@ $aModule = [ 'email' => 'support@shopmodule.com', 'url' => 'http://www.oxidmodule.com/', 'extend' => [ + UserController::class => \D3\Totp\Modules\Application\Controller\d3_totp_UserController::class, + PaymentController::class => \D3\Totp\Modules\Application\Controller\d3_totp_PaymentController::class, + OrderController::class => \D3\Totp\Modules\Application\Controller\d3_totp_OrderController::class, OxidModel\User::class => \D3\Totp\Modules\Application\Model\d3_totp_user::class, LoginController::class => \D3\Totp\Modules\Application\Controller\Admin\d3_totp_LoginController::class, Utils::class => \D3\Totp\Modules\Core\d3_totp_utils::class, + UserComponent::class => \D3\Totp\Modules\Application\Component\d3_totp_UserComponent::class, ], 'controllers' => [ - 'd3user_totp' => \D3\Totp\Application\Controller\Admin\d3user_totp::class + 'd3user_totp' => \D3\Totp\Application\Controller\Admin\d3user_totp::class, + 'd3totplogin' => \D3\Totp\Application\Controller\d3totplogin::class ], 'templates' => [ 'd3user_totp.tpl' => 'd3/totp/Application/views/admin/tpl/d3user_totp.tpl', + 'd3totplogin.tpl' => 'd3/totp/Application/views/tpl/d3totplogin.tpl', ], 'events' => [ 'onActivate' => '\D3\Totp\Setup\Events::onActivate',