add otp security check to critical settings
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
This commit is contained in:
@@ -25,10 +25,13 @@
|
||||
|
||||
namespace Froxlor;
|
||||
|
||||
use Exception;
|
||||
use Froxlor\Api\Commands\Customers;
|
||||
use Froxlor\Api\Commands\SubDomains;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\UI\Collection;
|
||||
use Froxlor\UI\Response;
|
||||
use RobThree\Auth\TwoFactorAuthException;
|
||||
|
||||
/**
|
||||
* Class to manage the current user / session
|
||||
@@ -169,4 +172,65 @@ class CurrentUser
|
||||
|
||||
return ($_SESSION['userinfo'][$resource . '_used'] < $_SESSION['userinfo'][$resource] || $_SESSION['userinfo'][$resource] == '-1') && $addition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws TwoFactorAuthException
|
||||
*/
|
||||
public static function sendOtpEmail()
|
||||
{
|
||||
global $mail;
|
||||
|
||||
if (self::getField('type_2fa') == 1) {
|
||||
// generate code
|
||||
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
|
||||
$code = $tfa->getCode($tfa->createSecret());
|
||||
// set code for user
|
||||
$table = TABLE_PANEL_CUSTOMERS;
|
||||
$uid = 'customerid';
|
||||
if (self::isAdmin()) {
|
||||
$table = TABLE_PANEL_ADMINS;
|
||||
$uid = 'adminid';
|
||||
}
|
||||
$stmt = Database::prepare("UPDATE $table SET `data_2fa` = :d2fa WHERE `$uid` = :uid");
|
||||
Database::pexecute($stmt, [
|
||||
"d2fa" => $code,
|
||||
"uid" => self::getField($uid)
|
||||
]);
|
||||
// build up & send email
|
||||
$_mailerror = false;
|
||||
$mailerr_msg = "";
|
||||
$replace_arr = [
|
||||
'CODE' => $code
|
||||
];
|
||||
$mail_body = html_entity_decode(PhpHelper::replaceVariables(lng('mails.2fa.mailbody'), $replace_arr));
|
||||
|
||||
try {
|
||||
$mail->Subject = lng('mails.2fa.subject');
|
||||
$mail->AltBody = $mail_body;
|
||||
$mail->MsgHTML(str_replace("\n", "<br />", $mail_body));
|
||||
$mail->AddAddress(self::getField('email'), User::getCorrectUserSalutation(self::getData()));
|
||||
$mail->Send();
|
||||
} catch (\PHPMailer\PHPMailer\Exception $e) {
|
||||
$mailerr_msg = $e->errorMessage();
|
||||
$_mailerror = true;
|
||||
} catch (Exception $e) {
|
||||
$mailerr_msg = $e->getMessage();
|
||||
$_mailerror = true;
|
||||
}
|
||||
|
||||
if ($_mailerror) {
|
||||
$rstlog = FroxlorLogger::getInstanceOf([
|
||||
'loginname' => '2fa code-sending'
|
||||
]);
|
||||
$rstlog->logAction(FroxlorLogger::ADM_ACTION, LOG_ERR, "Error sending mail: " . $mailerr_msg);
|
||||
Response::redirectTo('index.php', [
|
||||
'showmessage' => '4',
|
||||
'customermail' => self::getField('email')
|
||||
]);
|
||||
exit();
|
||||
}
|
||||
|
||||
$mail->ClearAddresses();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +129,8 @@ class Settings
|
||||
{
|
||||
// set defaults
|
||||
self::$conf = [
|
||||
'enable_webupdate' => false
|
||||
'enable_webupdate' => false,
|
||||
'disable_otp_security_check' => false,
|
||||
];
|
||||
|
||||
$configfile = Froxlor::getInstallDir() . '/lib/config.inc.php';
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
namespace Froxlor\UI;
|
||||
|
||||
use Froxlor\CurrentUser;
|
||||
use Froxlor\FroxlorTwoFactorAuth;
|
||||
use Froxlor\Settings;
|
||||
use Froxlor\Validate\Check;
|
||||
|
||||
@@ -183,6 +185,21 @@ class Form
|
||||
}
|
||||
}
|
||||
|
||||
// OTP security validation for sensitive settings
|
||||
if (!Settings::Config('disable_otp_security_check') && isset($fielddata['required_otp']) && $do_show) {
|
||||
$otp_enabled_system = (bool)Settings::Get('2fa.enabled');
|
||||
$otp_enabled_user = (int)CurrentUser::getField('type_2fa') != 0;
|
||||
$do_show = !$fielddata['required_otp'] || ($otp_enabled_system && $otp_enabled_user);
|
||||
if (!$do_show) {
|
||||
$fielddata['note'] = lng('serversettings.option_required_otp');
|
||||
if (!$otp_enabled_system) {
|
||||
$fielddata['note'] .= '<br>' . lng('2fa.2fa_not_activated');
|
||||
} elseif (!$otp_enabled_user) {
|
||||
$fielddata['note'] .= '<br>' . lng('2fa.2fa_not_activated_for_user');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$do_show) {
|
||||
$fielddata['visible'] = false;
|
||||
}
|
||||
@@ -283,6 +300,38 @@ class Form
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!Settings::Config('disable_otp_security_check') && isset($fielddetails['required_otp']) && isset($changed_fields[$fieldname])) {
|
||||
$otp_enabled_system = (bool)Settings::Get('2fa.enabled');
|
||||
$otp_enabled_user = (int)CurrentUser::getField('type_2fa') != 0;
|
||||
$do_update = !$fielddetails['required_otp'] || ($otp_enabled_system && $otp_enabled_user);
|
||||
if ($do_update) {
|
||||
// setting that requires OTP verification
|
||||
if (empty($input['otp_verification'])) {
|
||||
// in case email 2fa is enabled, send it now
|
||||
CurrentUser::sendOtpEmail();
|
||||
// build up form
|
||||
if (is_array($url_params) && isset($url_params['filename'])) {
|
||||
$filename = $url_params['filename'];
|
||||
unset($url_params['filename']);
|
||||
} else {
|
||||
$filename = '';
|
||||
}
|
||||
HTML::askOTP('please_enter_otp', $filename, array_merge($url_params, $submitted_fields));
|
||||
} else {
|
||||
// validate given OTP code
|
||||
$code = trim($input['otp_verification']);
|
||||
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
|
||||
$result = $tfa->verifyCode(CurrentUser::getField('data_2fa'), $code, 3);
|
||||
if (!$result) {
|
||||
Response::standardError('otpnotvalidated');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// do not update this setting
|
||||
unset($changed_fields[$fieldname]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,4 +221,17 @@ class HTML
|
||||
]);
|
||||
exit();
|
||||
}
|
||||
|
||||
public static function askOTP(string $text, string $targetfile, array $params = [], string $replacer = '', array $back_link = [])
|
||||
{
|
||||
$text = lng('question.' . $text, [htmlspecialchars($replacer)]);
|
||||
|
||||
Panel\UI::view('form/otpquestion.html.twig', [
|
||||
'action' => $targetfile,
|
||||
'url_params' => $params,
|
||||
'question' => $text,
|
||||
'back_link' => $back_link
|
||||
]);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,4 +11,9 @@ return [
|
||||
* updates the way the providers does it (e.g. automation, etc.)
|
||||
*/
|
||||
'enable_webupdate' => false,
|
||||
|
||||
/**
|
||||
* @todo description
|
||||
*/
|
||||
'disable_otp_security_check' => false,
|
||||
];
|
||||
|
||||
@@ -44,7 +44,8 @@ return [
|
||||
'type' => 'text',
|
||||
'maxlength' => 255,
|
||||
'value' => 'service php7.4-fpm restart',
|
||||
'mandatory' => true
|
||||
'mandatory' => true,
|
||||
'required_otp' => true
|
||||
],
|
||||
'config_dir' => [
|
||||
'label' => lng('serversettings.phpfpm_settings.configdir'),
|
||||
|
||||
@@ -45,7 +45,8 @@ return [
|
||||
'type' => 'text',
|
||||
'maxlength' => 255,
|
||||
'value' => $result['reload_cmd'],
|
||||
'mandatory' => true
|
||||
'mandatory' => true,
|
||||
'required_otp' => true
|
||||
],
|
||||
'config_dir' => [
|
||||
'label' => lng('serversettings.phpfpm_settings.configdir'),
|
||||
|
||||
@@ -46,7 +46,8 @@ return [
|
||||
'label' => lng('admin.phpsettings.binary'),
|
||||
'type' => 'text',
|
||||
'maxlength' => 255,
|
||||
'value' => '/usr/bin/php-cgi'
|
||||
'value' => '/usr/bin/php-cgi',
|
||||
'required_otp' => true
|
||||
],
|
||||
'fpmconfig' => [
|
||||
'visible' => Settings::Get('phpfpm.enabled') == 1,
|
||||
@@ -61,7 +62,8 @@ return [
|
||||
'desc' => lng('admin.phpsettings.file_extensions_note'),
|
||||
'type' => 'text',
|
||||
'maxlength' => 255,
|
||||
'value' => 'php'
|
||||
'value' => 'php',
|
||||
'required_otp' => true
|
||||
],
|
||||
'mod_fcgid_starter' => [
|
||||
'visible' => Settings::Get('system.mod_fcgid') == 1,
|
||||
@@ -181,7 +183,8 @@ return [
|
||||
'cols' => 80,
|
||||
'rows' => 20,
|
||||
'value' => $result['phpsettings'],
|
||||
'mandatory' => true
|
||||
'mandatory' => true,
|
||||
'required_otp' => true
|
||||
],
|
||||
'allow_all_customers' => [
|
||||
'label' => lng('serversettings.phpfpm_settings.allow_all_customers.title'),
|
||||
|
||||
@@ -47,7 +47,8 @@ return [
|
||||
'label' => lng('admin.phpsettings.binary'),
|
||||
'type' => 'text',
|
||||
'maxlength' => 255,
|
||||
'value' => $result['binary']
|
||||
'value' => $result['binary'],
|
||||
'required_otp' => true
|
||||
],
|
||||
'fpmconfig' => [
|
||||
'visible' => Settings::Get('phpfpm.enabled') == 1,
|
||||
@@ -62,7 +63,8 @@ return [
|
||||
'desc' => lng('admin.phpsettings.file_extensions_note'),
|
||||
'type' => 'text',
|
||||
'maxlength' => 255,
|
||||
'value' => $result['file_extensions']
|
||||
'value' => $result['file_extensions'],
|
||||
'required_otp' => true
|
||||
],
|
||||
'mod_fcgid_starter' => [
|
||||
'visible' => Settings::Get('system.mod_fcgid') == 1,
|
||||
@@ -185,7 +187,8 @@ return [
|
||||
'cols' => 80,
|
||||
'rows' => 20,
|
||||
'value' => $result['phpsettings'],
|
||||
'mandatory' => true
|
||||
'mandatory' => true,
|
||||
'required_otp' => true
|
||||
],
|
||||
'allow_all_customers' => [
|
||||
'label' => lng('serversettings.phpfpm_settings.allow_all_customers.title'),
|
||||
|
||||
Reference in New Issue
Block a user