From 0a528f993bf432a27974507948d1dd51e5dc706d Mon Sep 17 00:00:00 2001 From: Daniel Seifert Date: Thu, 18 Oct 2018 15:33:59 +0200 Subject: [PATCH] implement OTP check, add exception --- .../Controller/Admin/d3user_totp.php | 27 ++++ .../Exceptions/d3totp_wrongOtpException.php | 32 ++++ src/Application/Model/d3totp.php | 150 ++++++++++++++++++ .../views/admin/de/d3totp_lang.php | 6 + .../views/admin/tpl/d3user_totp.tpl | 93 ++++++++++- .../Admin/d3_totp_LoginController.php | 56 +++++-- .../Application/Model/d3_totp_user.php | 32 ++-- src/Modules/Core/d3_totp_utils.php | 3 +- 8 files changed, 372 insertions(+), 27 deletions(-) create mode 100644 src/Application/Model/Exceptions/d3totp_wrongOtpException.php create mode 100644 src/Application/Model/d3totp.php diff --git a/src/Application/Controller/Admin/d3user_totp.php b/src/Application/Controller/Admin/d3user_totp.php index 4112e43..49cc164 100644 --- a/src/Application/Controller/Admin/d3user_totp.php +++ b/src/Application/Controller/Admin/d3user_totp.php @@ -15,9 +15,36 @@ namespace D3\Totp\Application\Controller\Admin; +use D3\Totp\Application\Model\d3totp; +use Doctrine\DBAL\DBALException; use OxidEsales\Eshop\Application\Controller\Admin\AdminDetailsController; +use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException; class d3user_totp extends AdminDetailsController { protected $_sThisTemplate = 'd3user_totp.tpl'; + + /** + * @return string + * @throws DBALException + * @throws DatabaseConnectionException + */ + public function render() + { + parent::render(); + + $soxId = $this->_aViewData["oxid"] = $this->getEditObjectId(); + if (isset($soxId) && $soxId != "-1") { + /** @var d3totp $oTotp */ + $oTotp = oxNew(d3totp::class); + $oTotp->loadByUserId($soxId); + $this->_aViewData["edit"] = $oTotp; + } + + if (!$this->_allowAdminEdit($soxId)) { + $this->_aViewData['readonly'] = true; + } + + return $this->_sThisTemplate; + } } \ No newline at end of file diff --git a/src/Application/Model/Exceptions/d3totp_wrongOtpException.php b/src/Application/Model/Exceptions/d3totp_wrongOtpException.php new file mode 100644 index 0000000..ad7beb7 --- /dev/null +++ b/src/Application/Model/Exceptions/d3totp_wrongOtpException.php @@ -0,0 +1,32 @@ + + * @link http://www.oxidmodule.com + */ + +namespace D3\Totp\Application\Model\Exceptions; + +use D3\ModCfg\Application\Model\DependencyInjectionContainer\d3DicHandler; +use D3\ModCfg\Application\Model\Exception\d3_cfg_mod_exception; +use D3\ModCfg\Application\Model\Exception\d3ShopCompatibilityAdapterException; +use D3\ModCfg\Application\Model\Log\d3log; +use Doctrine\DBAL\DBALException; +use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException; +use OxidEsales\Eshop\Core\Exception\DatabaseErrorException; +use OxidEsales\Eshop\Core\Exception\StandardException; + +class d3totp_wrongOtpException extends StandardException +{ + +} diff --git a/src/Application/Model/d3totp.php b/src/Application/Model/d3totp.php new file mode 100644 index 0000000..bb07148 --- /dev/null +++ b/src/Application/Model/d3totp.php @@ -0,0 +1,150 @@ + + * @link http://www.oxidmodule.com + */ + +namespace D3\Totp\Application\Model; + +use D3\ModCfg\Application\Model\d3database; +use D3\Totp\Application\Model\Exceptions\d3totp_wrongOtpException; +use Doctrine\DBAL\DBALException; +use OTPHP\TOTP; +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; + +class d3totp extends BaseModel +{ + public $tableName = 'd3totp'; + public $userId; + public $totp; + + /** + * d3totp constructor. + */ + public function __construct() + { + $this->init($this->tableName); + + return parent::__construct(); + } + + /** + * @param $userId + * @return bool + * @throws DBALException + * @throws DatabaseConnectionException + */ + public function loadByUserId($userId) + { + $this->userId = $userId; + $oQB = d3database::getInstance()->getQueryBuilder(); + $oQB->select('oxid') + ->from($this->getViewName()) + ->where("oxuserid = ".$oQB->createNamedParameter($userId)) + ->setMaxResults(1); + + return $this->load(DatabaseProvider::getDb(DatabaseProvider::FETCH_MODE_ASSOC)->getOne($oQB->getSQL(), $oQB->getParameters())); + } + + /** + * @return User + */ + public function getUser() + { + $userId = $this->userId ? $this->userId : $this->getFieldData('oxuserid'); + + $user = oxNew(User::class); + $user->load($userId); + return $user; + } + + + + /** + * @param $userId + * @return bool + */ + public function UserUseTotp() + { + return $this->getFieldData('usetotp'); + } + + /** + * @param $userId + * @return string + */ + public function getSavedSecret() + { + $secret = $this->getFieldData('seed'); + + if ($secret) { + return $secret; + } + + return null; + } + + /** + * @return TOTP + */ + public function getTotp() + { + if (false == $this->totp) { + $this->totp = oxNew( + TOTP::class, + $this->getUser()->getFieldData('oxusername') + ? $this->getUser()->getFieldData('oxusername') + : null, + $this->getSavedSecret() + ); + $this->totp->setIssuer(Registry::getConfig()->getActiveShop()->getFieldData('oxname')); + } + + return $this->totp; + } + + /** + * @return string + */ + public function getQrCodeUri() + { + return $this->getTotp()->getQrCodeUri(); + } + + /** + * @return string + */ + public function getSecret() + { + return $this->getTotp()->getSecret(); + } + + /** + * @param $totp + * @return string + * @throws d3totp_wrongOtpException + */ + public function verify($totp) + { + $blVerify = $this->getTotp()->verify($totp, null, 2); + if (false == $blVerify) { + $oException = oxNew(d3totp_wrongOtpException::class, 'unvalid TOTP'); + throw $oException; + } + + return $blVerify; + } +} \ No newline at end of file diff --git a/src/Application/views/admin/de/d3totp_lang.php b/src/Application/views/admin/de/d3totp_lang.php index 478d60d..1cde743 100644 --- a/src/Application/views/admin/de/d3totp_lang.php +++ b/src/Application/views/admin/de/d3totp_lang.php @@ -24,4 +24,10 @@ $aLang = [ 'TOTP_INPUT_HELP' => 'Den Authentisierungscode erhalten Sie von der Zweifaktorauthentisierung-App auf Ihrem Gerät.', 'd3mxuser_totp' => '2-Faktor-Authentisierung', + + 'D3_TOTP_ACTIVE' => 'Benutzer verwendet 2-Faktor-Authentisierung', + 'D3_TOTP_ACTIVE_HELP' => 'Benutzer verwendet 2-Faktor-Authentisierung', + 'D3_TOTP_QRCODE' => 'QR-Code', + 'D3_TOTP_SECRET' => 'QR-Code kann nicht gescannt werden?', + 'D3_TOTP_CURROTP' => 'Bestätigung mit Einmalpasswort', ]; diff --git a/src/Application/views/admin/tpl/d3user_totp.tpl b/src/Application/views/admin/tpl/d3user_totp.tpl index 5fa15e7..3acc096 100644 --- a/src/Application/views/admin/tpl/d3user_totp.tpl +++ b/src/Application/views/admin/tpl/d3user_totp.tpl @@ -1,6 +1,97 @@ [{include file="headitem.tpl" title="GENERAL_ADMIN_TITLE"|oxmultilangassign}] -foo +[{if $readonly}] + [{assign var="readonly" value="readonly disabled"}] +[{else}] + [{assign var="readonly" value=""}] +[{/if}] + +
+ [{$oViewConf->getHiddenSid()}] + + +
+ +
+ [{$oViewConf->getHiddenSid()}] + + + + + + + + + + + +
+ + [{block name="user_d3user_totp_form1"}] + + + + + + [{/block}] + + + + +
+ [{oxmultilang ident="D3_TOTP_ACTIVE"}] + + + getFieldData('usetotp') == 1}]checked[{/if}] [{$readonly}]> + [{oxinputhelp ident="D3_TOTP_ACTIVE_HELP"}] +


+ oxarticles__oxtitle->value && !$oxparentid}]disabled[{/if}] [{$readonly}]> + [{if $oxid!=-1 && !$readonly}] +     + [{/if}] +
+
+ + [{block name="user_d3user_totp_form2"}] + + + + + + + + + + + + + + + [{/block}] +
+ [{oxmultilang ident="D3_TOTP_QRCODE"}]  + + + [{* + + [{oxinputhelp ident="HELP_ARTICLE_MAIN_TITLE"}] + *}] +
+ [{oxmultilang ident="D3_TOTP_SECRET"}]  + + [{$edit->getSecret()}] + [{* + + [{oxinputhelp ident="HELP_ARTICLE_MAIN_ARTNUM"}] + *}] +
+ [{oxmultilang ident="D3_TOTP_CURROTP"}]  + + + [{oxinputhelp ident="D3_TOTP_CURROTP_HELP"}] +
+
+
[{include file="bottomnaviitem.tpl"}] [{include file="bottomitem.tpl"}] \ 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 d65da5f..7b8b5eb 100644 --- a/src/Modules/Application/Controller/Admin/d3_totp_LoginController.php +++ b/src/Modules/Application/Controller/Admin/d3_totp_LoginController.php @@ -16,6 +16,7 @@ namespace D3\Totp\Modules\Application\Controller\Admin; use D3\Totp\Application\Model\d3totp; +use D3\Totp\Application\Model\Exceptions\d3totp_wrongOtpException; use Doctrine\DBAL\DBALException; use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException; use OxidEsales\Eshop\Core\Registry; @@ -33,12 +34,16 @@ class d3_totp_LoginController extends d3_totp_LoginController_parent $return = parent::render(); + $totp = oxNew(d3totp::class); + $totp->loadByUserId($auth); + if ($auth - && oxNew(d3totp::class)->UserUseTotp($auth) + && $totp->UserUseTotp() && false == Registry::getSession()->getVariable("totp_auth") ) { // set auth as secured parameter; - $return = 'd3login_totp.tpl'; + Registry::getSession()->setVariable("auth", $auth); + $this->addTplParam('request_totp', true); } return $return; @@ -51,19 +56,48 @@ class d3_totp_LoginController extends d3_totp_LoginController_parent */ public function checklogin() { - $return = parent::checklogin(); + $sTotp = Registry::getRequest()->getRequestEscapedParameter('d3totp', true); - if ($return == "admin_start") { - if ((bool) $this->getSession()->checkSessionChallenge() - && count(\OxidEsales\Eshop\Core\Registry::getUtilsServer()->getOxCookie()) - && Registry::getSession()->getVariable("auth") - && oxNew(d3totp::class)->UserUseTotp(Registry::getSession()->getVariable("auth")) - && false == Registry::getSession()->getVariable("totp_auth") - ) { - $return = "login"; + $totp = oxNew(d3totp::class); + $totp->loadByUserId(Registry::getSession()->getVariable("auth")); + + $return = 'login'; + + try { + if ($this->isNoTotpOrNoLogin($totp)) { + $return = parent::checklogin(); + } elseif ($this->hasValidTotp($sTotp, $totp)) { + Registry::getSession()->setVariable('totp_auth', $sTotp); + $return = "admin_start"; } + } catch (d3totp_wrongOtpException $oEx) { + Registry::getUtilsView()->addErrorToDisplay($oEx); } return $return; } + + /** + * @param d3totp $totp + * @return bool + */ + public function isNoTotpOrNoLogin($totp) + { + return false == Registry::getSession()->getVariable("auth") + || false == $totp->UserUseTotp(); + } + + /** + * @param string $sTotp + * @param d3totp $totp + * @return bool + * @throws d3totp_wrongOtpException + */ + public function hasValidTotp($sTotp, $totp) + { + return Registry::getSession()->getVariable("totp_auth") || + ( + $sTotp && $totp->verify($sTotp) + ); + } } \ 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 59a75c5..1e0c719 100644 --- a/src/Modules/Application/Model/d3_totp_user.php +++ b/src/Modules/Application/Model/d3_totp_user.php @@ -1,27 +1,31 @@ + * @link http://www.oxidmodule.com */ namespace D3\Totp\Modules\Application\Model; +use OxidEsales\Eshop\Core\Registry; + class d3_totp_user extends d3_totp_user_parent { - public function d3UseTotp() + public function logout() { - return false; - } + $return = parent::logout(); - public function d3GetTotpSecret() - { - return false; - } + // deleting session info + Registry::getSession()->deleteVariable('totp_auth'); - public function d3SetTotpSecret() - { - return false; + return $return; } } \ No newline at end of file diff --git a/src/Modules/Core/d3_totp_utils.php b/src/Modules/Core/d3_totp_utils.php index 23f4dca..a7ba3dc 100644 --- a/src/Modules/Core/d3_totp_utils.php +++ b/src/Modules/Core/d3_totp_utils.php @@ -35,8 +35,9 @@ class d3_totp_utils extends d3_totp_utils_parent $totpAuth = (bool) Registry::getSession()->getVariable("totp_auth"); /** @var d3totp $totp */ $totp = oxNew(d3totp::class); + $totp->loadByUserId($userID); - if (1 == 0 && $blAuth && $totp->UserUseTotp($userID) && false === $totpAuth) { + if ($blAuth && $totp->UserUseTotp() && false === $totpAuth) { Registry::getUtils()->redirect('index.php?cl=login', true, 302); exit; }