diff --git a/src/Application/Controller/Admin/d3user_totp.php b/src/Application/Controller/Admin/d3user_totp.php
index 49cc164..7d30741 100644
--- a/src/Application/Controller/Admin/d3user_totp.php
+++ b/src/Application/Controller/Admin/d3user_totp.php
@@ -16,12 +16,18 @@
namespace D3\Totp\Application\Controller\Admin;
use D3\Totp\Application\Model\d3totp;
+use D3\Totp\Modules\Application\Model\d3_totp_user;
use Doctrine\DBAL\DBALException;
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;
class d3user_totp extends AdminDetailsController
{
+ protected $_sSaveError = null;
+
protected $_sThisTemplate = 'd3user_totp.tpl';
/**
@@ -33,18 +39,62 @@ class d3user_totp extends AdminDetailsController
{
parent::render();
- $soxId = $this->_aViewData["oxid"] = $this->getEditObjectId();
+ $soxId = $this->getEditObjectId();
+
if (isset($soxId) && $soxId != "-1") {
- /** @var d3totp $oTotp */
- $oTotp = oxNew(d3totp::class);
- $oTotp->loadByUserId($soxId);
- $this->_aViewData["edit"] = $oTotp;
+ /** @var d3_totp_user $oUser */
+ $oUser = oxNew(User::class);
+ if ($oUser->load($soxId)) {
+ $this->addTplParam("oxid", $oUser->getId());
+ } else {
+ $this->addTplParam("oxid", '-1');
+ }
+ $this->addTplParam("edit", $oUser);
}
- if (!$this->_allowAdminEdit($soxId)) {
- $this->_aViewData['readonly'] = true;
+ if ($this->_sSaveError) {
+ $this->addTplParam("sSaveError", $this->_sSaveError);
}
return $this->_sThisTemplate;
}
+
+ /**
+ * @throws \Exception
+ */
+ public function save()
+ {
+ parent::save();
+
+ $aParams = Registry::getRequest()->getRequestEscapedParameter("editval");
+
+ try {
+ $pwd = Registry::getRequest()->getRequestEscapedParameter("pwd");
+
+ /** @var d3_totp_user $oUser */
+ $oUser = oxNew(User::class);
+ if (false == $oUser->d3CheckPasswordPass($this->getEditObjectId(), $pwd)) {
+ $oException = oxNew(StandardException::class, 'EXCEPTION_USER_PASSWORDDONTPASS');
+ throw $oException;
+ }
+
+ /** @var d3totp $oTotp */
+ $oTotp = oxNew(d3totp::class);
+ if ($aParams['d3totp__oxid']) {
+ $oTotp->load($aParams['d3totp__oxid']);
+ } else {
+ $aParams['d3totp__usetotp'] = 1;
+ $seed = Registry::getRequest()->getRequestEscapedParameter("secret");
+ $otp = Registry::getRequest()->getRequestEscapedParameter("otp");
+
+ $oTotp->saveSecret($seed, $pwd);
+ $oTotp->assign($aParams);
+ $oTotp->verify($otp, $seed);
+ $oTotp->setId();
+ }
+ $oTotp->save();
+ } catch (\Exception $oExcp) {
+ $this->_sSaveError = $oExcp->getMessage();
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Application/Model/d3totp.php b/src/Application/Model/d3totp.php
index 3e4cfb3..72f6568 100644
--- a/src/Application/Model/d3totp.php
+++ b/src/Application/Model/d3totp.php
@@ -81,7 +81,8 @@ class d3totp extends BaseModel
*/
public function UserUseTotp()
{
- return $this->getFieldData('usetotp');
+ return $this->getFieldData('usetotp')
+ && $this->getFieldData('seed');
}
/**
@@ -90,20 +91,24 @@ class d3totp extends BaseModel
*/
public function getSavedSecret()
{
- $secret = $this->getFieldData('seed');
+ $seed_enc = $this->getFieldData('seed');
$sPwd = Registry::getSession()->getVariable('pwdTransmit');
- if ($secret) {
- return $secret;
+ if ($seed_enc && $sPwd) {
+ $seed = $this->decrypt($seed_enc, $sPwd);
+ if ($seed) {
+ return $seed;
+ }
}
return null;
}
/**
+ * @param $seed
* @return TOTP
*/
- public function getTotp()
+ public function getTotp($seed = null)
{
if (false == $this->totp) {
@@ -113,10 +118,12 @@ class d3totp extends BaseModel
$this->getUser()->getFieldData('oxusername')
? $this->getUser()->getFieldData('oxusername')
: null,
- $this->getSavedSecret()
+ $seed
+ ? $seed
+ : $this->getSavedSecret()
);
} else { // version 0.9 (>= PHP 7.1)
- $this->totp = TOTP::create($this->getSavedSecret());
+ $this->totp = TOTP::create($seed ? $seed : $this->getSavedSecret());
$this->totp->setLabel($this->getUser()->getFieldData('oxusername')
? $this->getUser()->getFieldData('oxusername')
: null
@@ -151,17 +158,31 @@ class d3totp extends BaseModel
*/
public function getSecret()
{
- return $this->getTotp()->getSecret();
+ return trim($this->getTotp()->getSecret());
+ }
+
+ /**
+ * @param $seed
+ * @param $key
+ */
+ public function saveSecret($seed, $key)
+ {
+ $this->assign(
+ array(
+ 'seed' => $this->encrypt($seed, $key)
+ )
+ );
}
/**
* @param $totp
+ * @param $seed
* @return string
* @throws d3totp_wrongOtpException
*/
- public function verify($totp)
+ public function verify($totp, $seed = null)
{
- $blVerify = $this->getTotp()->verify($totp, null, 2);
+ $blVerify = $this->getTotp($seed)->verify($totp, null, 2);
if (false == $blVerify) {
$oException = oxNew(d3totp_wrongOtpException::class, 'unvalid TOTP');
throw $oException;
@@ -169,4 +190,43 @@ class d3totp extends BaseModel
return $blVerify;
}
+
+ /**
+ * $key should have previously been generated in a cryptographically secure manner, e.g. via openssl_random_pseudo_bytes
+ *
+ * @param $plaintext
+ * @param $key
+ * @return string
+ */
+ public function encrypt($plaintext, $key)
+ {
+ $ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
+ $iv = openssl_random_pseudo_bytes($ivlen);
+ $ciphertext_raw = openssl_encrypt($plaintext, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv);
+ $hmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true);
+ return base64_encode($iv.$hmac.$ciphertext_raw);
+ }
+
+ /**
+ * $key should have previously been generated in a cryptographically secure manner, e.g. via openssl_random_pseudo_bytes
+ *
+ * @param $ciphertext
+ * @param $key
+ * @return bool|string
+ */
+ public function decrypt($ciphertext, $key)
+ {
+ $c = base64_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, $options=OPENSSL_RAW_DATA, $iv);
+ $calcmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true);
+ if (hash_equals($hmac, $calcmac)) { // PHP 5.6+ compute attack-safe comparison
+ return $original_plaintext;
+ }
+
+ return false;
+ }
}
\ No newline at end of file
diff --git a/src/Application/views/admin/blocks/d3totp_login_admin_login_form.tpl b/src/Application/views/admin/blocks/d3totp_login_admin_login_form.tpl
index 409d79b..5462e85 100644
--- a/src/Application/views/admin/blocks/d3totp_login_admin_login_form.tpl
+++ b/src/Application/views/admin/blocks/d3totp_login_admin_login_form.tpl
@@ -12,6 +12,8 @@
[{oxmultilang ident="TOTP_INPUT_HELP"}]
+
+ --Anmeldung abbrechen--
[{else}]
[{$smarty.block.parent}]
-[{/if}]
+[{/if}]
\ 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 1cde743..34f791f 100644
--- a/src/Application/views/admin/de/d3totp_lang.php
+++ b/src/Application/views/admin/de/d3totp_lang.php
@@ -25,9 +25,13 @@ $aLang = [
'd3mxuser_totp' => '2-Faktor-Authentisierung',
- 'D3_TOTP_ACTIVE' => 'Benutzer verwendet 2-Faktor-Authentisierung',
- 'D3_TOTP_ACTIVE_HELP' => 'Benutzer verwendet 2-Faktor-Authentisierung',
+ '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.',
'D3_TOTP_SECRET' => 'QR-Code kann nicht gescannt werden?',
+ 'D3_TOTP_SECRET_HELP' => 'Setzen Sie keine App ein, die den QR-Code scannen kann, können Sie diese Zeichenkette auch in Ihr Authentisierungstool kopieren. Stellen Sie bitte zusätzlich die Passwortlänge auf 6 Zeichen und das Zeitinterval auf 30 Sekunden ein.',
+ 'D3_TOTP_CURRPWD' => 'Anmeldepasswort des Benutzerkontos',
+ 'D3_TOTP_CURRPWD_HELP' => 'Die Zeichenkette wird verschlüsselt im Shop abgelegt. Zum Verschlüsseln wird das Passwort des ausgewählten Kundenkontos benötigt. Zugleich stellt dies sicher, dass nur Berechtigte Änderungen an diesen Einstellungen vornehmen dürfen.',
'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.',
];
diff --git a/src/Application/views/admin/tpl/d3user_totp.tpl b/src/Application/views/admin/tpl/d3user_totp.tpl
index 3acc096..f40e043 100644
--- a/src/Application/views/admin/tpl/d3user_totp.tpl
+++ b/src/Application/views/admin/tpl/d3user_totp.tpl
@@ -1,5 +1,7 @@
[{include file="headitem.tpl" title="GENERAL_ADMIN_TITLE"|oxmultilangassign}]
+[{assign var="totp" value=$edit->d3GetTotp()}]
+
[{if $readonly}]
[{assign var="readonly" value="readonly disabled"}]
[{else}]
@@ -17,80 +19,98 @@
+
-
-
-
-
+ |
+
+
+
+ [{block name="user_d3user_totp_form2"}][{/block}]
+
+ |
+
+
+
+ [{/if}]
[{include file="bottomnaviitem.tpl"}]
diff --git a/src/Modules/Application/Controller/Admin/d3_totp_LoginController.php b/src/Modules/Application/Controller/Admin/d3_totp_LoginController.php
index a70bc99..df0dec8 100644
--- a/src/Modules/Application/Controller/Admin/d3_totp_LoginController.php
+++ b/src/Modules/Application/Controller/Admin/d3_totp_LoginController.php
@@ -72,6 +72,7 @@ class d3_totp_LoginController extends d3_totp_LoginController_parent
$return = parent::checklogin();
} elseif ($this->hasValidTotp($sTotp, $totp)) {
Registry::getSession()->setVariable(d3totp::TOTP_SESSION_VARNAME, $sTotp);
+ Registry::getSession()->deleteVariable('pwdTransmit');
$return = "admin_start";
}
} catch (d3totp_wrongOtpException $oEx) {
diff --git a/src/Modules/Application/Model/d3_totp_user.php b/src/Modules/Application/Model/d3_totp_user.php
index c59c53a..f0031fc 100644
--- a/src/Modules/Application/Model/d3_totp_user.php
+++ b/src/Modules/Application/Model/d3_totp_user.php
@@ -16,6 +16,9 @@
namespace D3\Totp\Modules\Application\Model;
use D3\Totp\Application\Model\d3totp;
+use Doctrine\DBAL\DBALException;
+use OxidEsales\Eshop\Core\DatabaseProvider;
+use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException;
use OxidEsales\Eshop\Core\Registry;
class d3_totp_user extends d3_totp_user_parent
@@ -29,4 +32,51 @@ class d3_totp_user extends d3_totp_user_parent
return $return;
}
+
+ /**
+ * @param $sUserId
+ * @param $sPassword
+ * @return bool
+ * @throws DatabaseConnectionException
+ */
+ public function d3CheckPasswordPass($sUserId, $sPassword)
+ {
+ return (bool) DatabaseProvider::getDb(DatabaseProvider::FETCH_MODE_ASSOC)->getOne(
+ $this->d3GetPasswordCheckQuery($sUserId, $sPassword)
+ );
+ }
+
+ /**
+ * @param $sUserId
+ * @param $sPassword
+ * @return string
+ * @throws DatabaseConnectionException
+ */
+ public function d3GetPasswordCheckQuery($sUserId, $sPassword)
+ {
+ $oDb = \OxidEsales\Eshop\Core\DatabaseProvider::getDb();
+
+ $sUserSelect = "oxuser.oxid = " . $oDb->quote($sUserId);
+
+ $sSalt = $oDb->getOne("SELECT `oxpasssalt` FROM `oxuser` WHERE " . $sUserSelect);
+
+ $sPassSelect = " oxuser.oxpassword = " . $oDb->quote($this->encodePassword($sPassword, $sSalt));
+
+ $sSelect = "select `oxid` from oxuser where 1 and {$sPassSelect} and {$sUserSelect} ";
+
+ return $sSelect;
+ }
+
+ /**
+ * @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 a1caa33..57d5c16 100644
--- a/src/metadata.php
+++ b/src/metadata.php
@@ -47,7 +47,7 @@ $aModule = [
'email' => 'support@shopmodule.com',
'url' => 'http://www.oxidmodule.com/',
'extend' => [
- //OxidModel\User::class => \D3\Totp\Modules\Application\Model\d3_totp_user::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,
],