merge current enhancements from main branch to v2.2 (#1261)
This commit is contained in:
62
index.php
62
index.php
@@ -72,6 +72,7 @@ if ($action == '2fa_entercode') {
|
|||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
$code = Request::post('2fa_code');
|
$code = Request::post('2fa_code');
|
||||||
|
$remember = Request::post('2fa_remember');
|
||||||
// verify entered code
|
// verify entered code
|
||||||
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
|
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
|
||||||
// get user-data
|
// get user-data
|
||||||
@@ -105,13 +106,6 @@ if ($action == '2fa_entercode') {
|
|||||||
$userinfo['adminsession'] = $isadmin;
|
$userinfo['adminsession'] = $isadmin;
|
||||||
$userinfo['userid'] = $uid;
|
$userinfo['userid'] = $uid;
|
||||||
|
|
||||||
// if not successful somehow - start again
|
|
||||||
if (!finishLogin($userinfo)) {
|
|
||||||
Response::redirectTo('index.php', [
|
|
||||||
'showmessage' => '2'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// when using email-2fa, remove the one-time-code
|
// when using email-2fa, remove the one-time-code
|
||||||
if ($userinfo['type_2fa'] == '1') {
|
if ($userinfo['type_2fa'] == '1') {
|
||||||
$del_stmt = Database::prepare("UPDATE " . $table . " SET `data_2fa` = '' WHERE `" . $field . "` = :uid");
|
$del_stmt = Database::prepare("UPDATE " . $table . " SET `data_2fa` = '' WHERE `" . $field . "` = :uid");
|
||||||
@@ -119,6 +113,42 @@ if ($action == '2fa_entercode') {
|
|||||||
'uid' => $uid
|
'uid' => $uid
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// when remember is activated, set the cookie
|
||||||
|
if ($remember) {
|
||||||
|
$selector = base64_encode(Froxlor::genSessionId(9));
|
||||||
|
$authenticator = Froxlor::genSessionId(33);
|
||||||
|
$valid_until = time()+60*60*24*30;
|
||||||
|
$ins_stmt = Database::prepare("
|
||||||
|
INSERT INTO `".TABLE_PANEL_2FA_TOKENS."` SET
|
||||||
|
`selector` = :selector,
|
||||||
|
`token` = :authenticator,
|
||||||
|
`userid` = :userid,
|
||||||
|
`valid_until` = :valid_until
|
||||||
|
");
|
||||||
|
Database::pexecute($ins_stmt, [
|
||||||
|
'selector' => $selector,
|
||||||
|
'authenticator' => hash('sha256', $authenticator),
|
||||||
|
'userid' => $uid,
|
||||||
|
'valid_until' => $valid_until
|
||||||
|
]);
|
||||||
|
$cookie_params = [
|
||||||
|
'expires' => $valid_until, // 30 days
|
||||||
|
'path' => '/',
|
||||||
|
'domain' => UI::getCookieHost(),
|
||||||
|
'secure' => UI::requestIsHttps(),
|
||||||
|
'httponly' => true,
|
||||||
|
'samesite' => 'Strict'
|
||||||
|
];
|
||||||
|
setcookie('frx_2fa_remember', $selector.':'.base64_encode($authenticator), $cookie_params);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if not successful somehow - start again
|
||||||
|
if (!finishLogin($userinfo)) {
|
||||||
|
Response::redirectTo('index.php', [
|
||||||
|
'showmessage' => '2'
|
||||||
|
]);
|
||||||
|
}
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
// wrong 2fa code - treat like "wrong password"
|
// wrong 2fa code - treat like "wrong password"
|
||||||
@@ -349,6 +379,22 @@ if ($action == '2fa_entercode') {
|
|||||||
|
|
||||||
// 2FA activated
|
// 2FA activated
|
||||||
if (Settings::Get('2fa.enabled') == '1' && $userinfo['type_2fa'] > 0) {
|
if (Settings::Get('2fa.enabled') == '1' && $userinfo['type_2fa'] > 0) {
|
||||||
|
|
||||||
|
// check for remember cookie
|
||||||
|
if (!empty($_COOKIE['frx_2fa_remember'])) {
|
||||||
|
list($selector, $authenticator) = explode(':', $_COOKIE['frx_2fa_remember']);
|
||||||
|
$sel_stmt = Database::prepare("SELECT `token` FROM `".TABLE_PANEL_2FA_TOKENS."` WHERE `selector` = :selector AND `userid` = :uid AND `valid_until` >= UNIX_TIMESTAMP()");
|
||||||
|
$token_check = Database::pexecute_first($sel_stmt, ['selector' => $selector, 'uid' => $userinfo[$uid]]);
|
||||||
|
if ($token_check && hash_equals($token_check['token'], hash('sha256', base64_decode($authenticator)))) {
|
||||||
|
if (!finishLogin($userinfo)) {
|
||||||
|
Response::redirectTo('index.php', [
|
||||||
|
'showmessage' => '2'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// redirect to code-enter-page
|
// redirect to code-enter-page
|
||||||
$_SESSION['secret_2fa'] = ($userinfo['type_2fa'] == 2 ? $userinfo['data_2fa'] : 'email');
|
$_SESSION['secret_2fa'] = ($userinfo['type_2fa'] == 2 ? $userinfo['data_2fa'] : 'email');
|
||||||
$_SESSION['uid_2fa'] = $userinfo[$uid];
|
$_SESSION['uid_2fa'] = $userinfo[$uid];
|
||||||
@@ -829,8 +875,8 @@ function finishLogin($userinfo)
|
|||||||
$theme = $userinfo['theme'];
|
$theme = $userinfo['theme'];
|
||||||
} else {
|
} else {
|
||||||
$theme = Settings::Get('panel.default_theme');
|
$theme = Settings::Get('panel.default_theme');
|
||||||
CurrentUser::setField('theme', $theme);
|
|
||||||
}
|
}
|
||||||
|
CurrentUser::setField('theme', $theme);
|
||||||
|
|
||||||
$qryparams = [];
|
$qryparams = [];
|
||||||
if (!empty($_SESSION['lastqrystr'])) {
|
if (!empty($_SESSION['lastqrystr'])) {
|
||||||
|
|||||||
@@ -730,8 +730,8 @@ opcache.validate_timestamps'),
|
|||||||
('panel', 'logo_overridecustom', '0'),
|
('panel', 'logo_overridecustom', '0'),
|
||||||
('panel', 'settings_mode', '0'),
|
('panel', 'settings_mode', '0'),
|
||||||
('panel', 'menu_collapsed', '1'),
|
('panel', 'menu_collapsed', '1'),
|
||||||
('panel', 'version', '2.2.0-rc1'),
|
('panel', 'version', '2.2.0-rc2'),
|
||||||
('panel', 'db_version', '202401090');
|
('panel', 'db_version', '202407200');
|
||||||
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `panel_tasks`;
|
DROP TABLE IF EXISTS `panel_tasks`;
|
||||||
@@ -1049,4 +1049,15 @@ CREATE TABLE `panel_loginlinks` (
|
|||||||
`allowed_from` text NOT NULL,
|
`allowed_from` text NOT NULL,
|
||||||
UNIQUE KEY `loginname` (`loginname`)
|
UNIQUE KEY `loginname` (`loginname`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||||
|
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `panel_2fa_tokens`;
|
||||||
|
CREATE TABLE `panel_2fa_tokens` (
|
||||||
|
`id` int(11) NOT NULL auto_increment,
|
||||||
|
`selector` varchar(20) NOT NULL,
|
||||||
|
`token` varchar(200) NOT NULL,
|
||||||
|
`userid` int(11) NOT NULL default '0',
|
||||||
|
`valid_until` int(15) NOT NULL,
|
||||||
|
PRIMARY KEY (id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||||
FROXLORSQL;
|
FROXLORSQL;
|
||||||
|
|||||||
@@ -122,3 +122,26 @@ if (Froxlor::isFroxlorVersion('2.2.0-dev1')) {
|
|||||||
Update::showUpdateStep("Updating from 2.2.0-dev1 to 2.2.0-rc1", false);
|
Update::showUpdateStep("Updating from 2.2.0-dev1 to 2.2.0-rc1", false);
|
||||||
Froxlor::updateToVersion('2.2.0-rc1');
|
Froxlor::updateToVersion('2.2.0-rc1');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Froxlor::isDatabaseVersion('202401090')) {
|
||||||
|
|
||||||
|
Update::showUpdateStep("Adding new table for 2fa tokens");
|
||||||
|
Database::query("DROP TABLE IF EXISTS `panel_2fa_tokens`;");
|
||||||
|
$sql = "CREATE TABLE `panel_2fa_tokens` (
|
||||||
|
`id` int(11) NOT NULL auto_increment,
|
||||||
|
`selector` varchar(20) NOT NULL,
|
||||||
|
`token` varchar(200) NOT NULL,
|
||||||
|
`userid` int(11) NOT NULL default '0',
|
||||||
|
`valid_until` int(15) NOT NULL,
|
||||||
|
PRIMARY KEY (id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;";
|
||||||
|
Database::query($sql);
|
||||||
|
Update::lastStepStatus(0);
|
||||||
|
|
||||||
|
Froxlor::updateToDbVersion('202407200');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Froxlor::isFroxlorVersion('2.2.0-rc1')) {
|
||||||
|
Update::showUpdateStep("Updating from 2.2.0-rc1 to 2.2.0-rc2", false);
|
||||||
|
Froxlor::updateToVersion('2.2.0-rc2');
|
||||||
|
}
|
||||||
|
|||||||
@@ -270,15 +270,6 @@ class Emails extends ApiCommand implements ResourceEntity
|
|||||||
throw new Exception("You cannot access this resource", 405);
|
throw new Exception("You cannot access this resource", 405);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if enabling catchall is not allowed by settings, we do not need
|
|
||||||
// to run update()
|
|
||||||
if (Settings::Get('catchall.catchall_enabled') != '1') {
|
|
||||||
Response::standardError([
|
|
||||||
'operationnotpermitted',
|
|
||||||
'featureisdisabled'
|
|
||||||
], 'catchall', true);
|
|
||||||
}
|
|
||||||
|
|
||||||
$id = $this->getParam('id', true, 0);
|
$id = $this->getParam('id', true, 0);
|
||||||
$ea_optional = $id > 0;
|
$ea_optional = $id > 0;
|
||||||
$emailaddr = $this->getParam('emailaddr', $ea_optional, '');
|
$emailaddr = $this->getParam('emailaddr', $ea_optional, '');
|
||||||
@@ -297,30 +288,41 @@ class Emails extends ApiCommand implements ResourceEntity
|
|||||||
$iscatchall = $this->getBoolParam('iscatchall', true, $result['iscatchall']);
|
$iscatchall = $this->getBoolParam('iscatchall', true, $result['iscatchall']);
|
||||||
$description = $this->getParam('description', true, $result['description']);
|
$description = $this->getParam('description', true, $result['description']);
|
||||||
|
|
||||||
|
// if enabling catchall is not allowed by settings, we do not need
|
||||||
|
// to run update()
|
||||||
|
if ($iscatchall && $result['iscatchall'] == 0 && Settings::Get('catchall.catchall_enabled') != '1') {
|
||||||
|
Response::standardError([
|
||||||
|
'operationnotpermitted',
|
||||||
|
'featureisdisabled'
|
||||||
|
], 'catchall', true);
|
||||||
|
}
|
||||||
|
|
||||||
// get needed customer info to reduce the email-address-counter by one
|
// get needed customer info to reduce the email-address-counter by one
|
||||||
$customer = $this->getCustomerData();
|
$customer = $this->getCustomerData();
|
||||||
|
|
||||||
// check for catchall-flag
|
// check for catchall-flag
|
||||||
|
$email = $result['email_full'];
|
||||||
if ($iscatchall) {
|
if ($iscatchall) {
|
||||||
$iscatchall = '1';
|
$iscatchall = '1';
|
||||||
$email_parts = explode('@', $result['email_full']);
|
$email = $result['email'];
|
||||||
$email = '@' . $email_parts[1];
|
// update only required if it was not a catchall before
|
||||||
// catchall check
|
if ($result['iscatchall'] == 0) {
|
||||||
$stmt = Database::prepare("
|
$email_parts = explode('@', $result['email_full']);
|
||||||
SELECT `email_full` FROM `" . TABLE_MAIL_VIRTUAL . "`
|
$email = '@' . $email_parts[1];
|
||||||
WHERE `email` = :email AND `customerid` = :cid AND `iscatchall` = '1'
|
// catchall check
|
||||||
");
|
$stmt = Database::prepare("
|
||||||
$params = [
|
SELECT `email_full` FROM `" . TABLE_MAIL_VIRTUAL . "`
|
||||||
"email" => $email,
|
WHERE `email` = :email AND `customerid` = :cid AND `iscatchall` = '1'
|
||||||
"cid" => $customer['customerid']
|
");
|
||||||
];
|
$params = [
|
||||||
$email_check = Database::pexecute_first($stmt, $params, true, true);
|
"email" => $email,
|
||||||
if ($email_check) {
|
"cid" => $customer['customerid']
|
||||||
Response::standardError('youhavealreadyacatchallforthisdomain', '', true);
|
];
|
||||||
|
$email_check = Database::pexecute_first($stmt, $params, true, true);
|
||||||
|
if ($email_check) {
|
||||||
|
Response::standardError('youhavealreadyacatchallforthisdomain', '', true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
$iscatchall = '0';
|
|
||||||
$email = $result['email_full'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$spam_tag_level = Validate::validate($spam_tag_level, 'spam_tag_level', '/^\d{1,}(\.\d{1,2})?$/', '', [7.0], true);
|
$spam_tag_level = Validate::validate($spam_tag_level, 'spam_tag_level', '/^\d{1,}(\.\d{1,2})?$/', '', [7.0], true);
|
||||||
|
|||||||
@@ -983,9 +983,11 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
|||||||
'`d`.`letsencrypt`',
|
'`d`.`letsencrypt`',
|
||||||
'`d`.`registration_date`',
|
'`d`.`registration_date`',
|
||||||
'`d`.`termination_date`',
|
'`d`.`termination_date`',
|
||||||
'`d`.`deactivated`'
|
'`d`.`deactivated`',
|
||||||
|
'`d`.`email_only`',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$query_fields = [];
|
$query_fields = [];
|
||||||
|
|
||||||
// prepare select statement
|
// prepare select statement
|
||||||
@@ -996,7 +998,6 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
|||||||
LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` `da` ON `da`.`aliasdomain`=`d`.`id`
|
LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` `da` ON `da`.`aliasdomain`=`d`.`id`
|
||||||
LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` `pd` ON `pd`.`id`=`d`.`parentdomainid`
|
LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` `pd` ON `pd`.`id`=`d`.`parentdomainid`
|
||||||
WHERE `d`.`customerid` IN (" . implode(', ', $customer_ids) . ")
|
WHERE `d`.`customerid` IN (" . implode(', ', $customer_ids) . ")
|
||||||
AND `d`.`email_only` = '0'
|
|
||||||
" . $this->getSearchWhere($query_fields, true) . " GROUP BY `d`.`id` ORDER BY `parentdomainname` ASC, `d`.`parentdomainid` ASC " . $this->getOrderBy(true) . $this->getLimit());
|
" . $this->getSearchWhere($query_fields, true) . " GROUP BY `d`.`id` ORDER BY `parentdomainname` ASC, `d`.`parentdomainid` ASC " . $this->getOrderBy(true) . $this->getLimit());
|
||||||
|
|
||||||
$result = [];
|
$result = [];
|
||||||
@@ -1092,13 +1093,13 @@ class SubDomains extends ApiCommand implements ResourceEntity
|
|||||||
$this->getUserDetail('customerid')
|
$this->getUserDetail('customerid')
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($customer_ids)) {
|
if (!empty($customer_ids)) {
|
||||||
// prepare select statement
|
// prepare select statement
|
||||||
$domains_stmt = Database::prepare("
|
$domains_stmt = Database::prepare("
|
||||||
SELECT COUNT(*) as num_subdom
|
SELECT COUNT(*) as num_subdom
|
||||||
FROM `" . TABLE_PANEL_DOMAINS . "` `d`
|
FROM `" . TABLE_PANEL_DOMAINS . "` `d`
|
||||||
WHERE `d`.`customerid` IN (" . implode(', ', $customer_ids) . ")
|
WHERE `d`.`customerid` IN (" . implode(', ', $customer_ids) . ")
|
||||||
AND `d`.`email_only` = '0'
|
|
||||||
");
|
");
|
||||||
$result = Database::pexecute_first($domains_stmt, null, true, true);
|
$result = Database::pexecute_first($domains_stmt, null, true, true);
|
||||||
if ($result) {
|
if ($result) {
|
||||||
|
|||||||
@@ -171,8 +171,9 @@ final class MasterCron extends CliCommand
|
|||||||
FroxlorLogger::getInstanceOf()->setCronLog(0);
|
FroxlorLogger::getInstanceOf()->setCronLog(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// clean up possible old login-links
|
// clean up possible old login-links and 2fa tokens
|
||||||
Database::query("DELETE FROM `" . TABLE_PANEL_LOGINLINKS . "` WHERE `valid_until` < UNIX_TIMESTAMP()");
|
Database::query("DELETE FROM `" . TABLE_PANEL_LOGINLINKS . "` WHERE `valid_until` < UNIX_TIMESTAMP()");
|
||||||
|
Database::query("DELETE FROM `" . TABLE_PANEL_2FA_TOKENS . "` WHERE `valid_until` < UNIX_TIMESTAMP()");
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class Dns
|
|||||||
$dom_data['uid'] = $userinfo['userid'];
|
$dom_data['uid'] = $userinfo['userid'];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$where_clause = '`customerid` = :uid AND ';
|
$where_clause = '`customerid` = :uid AND `email_only` = "0" AND ';
|
||||||
$dom_data['uid'] = $userinfo['userid'];
|
$dom_data['uid'] = $userinfo['userid'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ final class Froxlor
|
|||||||
{
|
{
|
||||||
|
|
||||||
// Main version variable
|
// Main version variable
|
||||||
const VERSION = '2.2.0-rc1';
|
const VERSION = '2.2.0-rc2';
|
||||||
|
|
||||||
// Database version (YYYYMMDDC where C is a daily counter)
|
// Database version (YYYYMMDDC where C is a daily counter)
|
||||||
const DBVERSION = '202401090';
|
const DBVERSION = '202407200';
|
||||||
|
|
||||||
// Distribution branding-tag (used for Debian etc.)
|
// Distribution branding-tag (used for Debian etc.)
|
||||||
const BRANDING = '';
|
const BRANDING = '';
|
||||||
|
|||||||
@@ -74,6 +74,9 @@ class Domain
|
|||||||
if ($attributes['fields']['deactivated']) {
|
if ($attributes['fields']['deactivated']) {
|
||||||
return lng('admin.deactivated');
|
return lng('admin.deactivated');
|
||||||
}
|
}
|
||||||
|
if ($attributes['fields']['email_only']) {
|
||||||
|
return lng('domains.email_only');
|
||||||
|
}
|
||||||
// path or redirect
|
// path or redirect
|
||||||
if (preg_match('/^https?\:\/\//', $attributes['fields']['documentroot'])) {
|
if (preg_match('/^https?\:\/\//', $attributes['fields']['documentroot'])) {
|
||||||
return [
|
return [
|
||||||
@@ -127,7 +130,7 @@ class Domain
|
|||||||
|
|
||||||
public static function canViewLogs(array $attributes): bool
|
public static function canViewLogs(array $attributes): bool
|
||||||
{
|
{
|
||||||
if ((!CurrentUser::isAdmin() || (CurrentUser::isAdmin() && (int)$attributes['fields']['email_only'] == 0)) && !$attributes['fields']['deactivated']) {
|
if ((int)$attributes['fields']['email_only'] == 0 && !$attributes['fields']['deactivated']) {
|
||||||
if ((int)UI::getCurrentUser()['adminsession'] == 0 && (bool)UI::getCurrentUser()['logviewenabled']) {
|
if ((int)UI::getCurrentUser()['adminsession'] == 0 && (bool)UI::getCurrentUser()['logviewenabled']) {
|
||||||
return true;
|
return true;
|
||||||
} elseif ((int)UI::getCurrentUser()['adminsession'] == 1) {
|
} elseif ((int)UI::getCurrentUser()['adminsession'] == 1) {
|
||||||
@@ -157,6 +160,7 @@ class Domain
|
|||||||
&& $attributes['fields']['caneditdomain'] == '1'
|
&& $attributes['fields']['caneditdomain'] == '1'
|
||||||
&& Settings::Get('system.bind_enable') == '1'
|
&& Settings::Get('system.bind_enable') == '1'
|
||||||
&& Settings::Get('system.dnsenabled') == '1'
|
&& Settings::Get('system.dnsenabled') == '1'
|
||||||
|
&& !$attributes['fields']['email_only']
|
||||||
&& !$attributes['fields']['deactivated'];
|
&& !$attributes['fields']['deactivated'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +173,7 @@ class Domain
|
|||||||
|
|
||||||
public static function hasLetsEncryptActivated(array $attributes): bool
|
public static function hasLetsEncryptActivated(array $attributes): bool
|
||||||
{
|
{
|
||||||
return ((bool)$attributes['fields']['letsencrypt'] && (!CurrentUser::isAdmin() || (CurrentUser::isAdmin() && (int)$attributes['fields']['email_only'] == 0)));
|
return ((bool)$attributes['fields']['letsencrypt'] && (int)$attributes['fields']['email_only'] == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -181,7 +185,7 @@ class Domain
|
|||||||
&& DDomain::domainHasSslIpPort($attributes['fields']['id'])
|
&& DDomain::domainHasSslIpPort($attributes['fields']['id'])
|
||||||
&& (CurrentUser::isAdmin() || (!CurrentUser::isAdmin() && (int)$attributes['fields']['caneditdomain'] == 1))
|
&& (CurrentUser::isAdmin() || (!CurrentUser::isAdmin() && (int)$attributes['fields']['caneditdomain'] == 1))
|
||||||
&& (int)$attributes['fields']['letsencrypt'] == 0
|
&& (int)$attributes['fields']['letsencrypt'] == 0
|
||||||
&& (!CurrentUser::isAdmin() || (CurrentUser::isAdmin() && (int)$attributes['fields']['email_only'] == 0))
|
&& !(int)$attributes['fields']['email_only']
|
||||||
&& !$attributes['fields']['deactivated']
|
&& !$attributes['fields']['deactivated']
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -52,6 +52,14 @@ class Text
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function type2fa(array $attributes): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'macro' => 'type2fa',
|
||||||
|
'data' => (int)$attributes['data']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public static function customerfullname(array $attributes): string
|
public static function customerfullname(array $attributes): string
|
||||||
{
|
{
|
||||||
return User::getCorrectFullUserDetails($attributes['fields'], true);
|
return User::getCorrectFullUserDetails($attributes['fields'], true);
|
||||||
|
|||||||
@@ -47,13 +47,14 @@ return [
|
|||||||
'values' => $domainips
|
'values' => $domainips
|
||||||
],
|
],
|
||||||
'alias' => [
|
'alias' => [
|
||||||
'visible' => $alias_check == '0',
|
'visible' => $alias_check == '0' && (int)$result['email_only'] == 0,
|
||||||
'label' => lng('domains.aliasdomain'),
|
'label' => lng('domains.aliasdomain'),
|
||||||
'type' => 'select',
|
'type' => 'select',
|
||||||
'select_var' => $domains,
|
'select_var' => $domains,
|
||||||
'selected' => $result['aliasdomain']
|
'selected' => $result['aliasdomain']
|
||||||
],
|
],
|
||||||
'path' => [
|
'path' => [
|
||||||
|
'visible' => (int)$result['email_only'] == 0,
|
||||||
'label' => lng('panel.path'),
|
'label' => lng('panel.path'),
|
||||||
'desc' => (Settings::Get('panel.pathedit') != 'Dropdown' ? lng('panel.pathDescriptionSubdomain').(Settings::Get('system.documentroot_use_default_value') == 1 ? lng('panel.pathDescriptionEx') : '') : null),
|
'desc' => (Settings::Get('panel.pathedit') != 'Dropdown' ? lng('panel.pathDescriptionSubdomain').(Settings::Get('system.documentroot_use_default_value') == 1 ? lng('panel.pathDescriptionEx') : '') : null),
|
||||||
'type' => $pathSelect['type'],
|
'type' => $pathSelect['type'],
|
||||||
@@ -63,13 +64,13 @@ return [
|
|||||||
'note' => $pathSelect['note'] ?? '',
|
'note' => $pathSelect['note'] ?? '',
|
||||||
],
|
],
|
||||||
'url' => [
|
'url' => [
|
||||||
'visible' => Settings::Get('panel.pathedit') == 'Dropdown',
|
'visible' => Settings::Get('panel.pathedit') == 'Dropdown' && (int)$result['email_only'] == 0,
|
||||||
'label' => lng('panel.urloverridespath'),
|
'label' => lng('panel.urloverridespath'),
|
||||||
'type' => 'text',
|
'type' => 'text',
|
||||||
'value' => $urlvalue
|
'value' => $urlvalue
|
||||||
],
|
],
|
||||||
'redirectcode' => [
|
'redirectcode' => [
|
||||||
'visible' => Settings::Get('customredirect.enabled') == '1',
|
'visible' => Settings::Get('customredirect.enabled') == '1' && (int)$result['email_only'] == 0,
|
||||||
'label' => lng('domains.redirectifpathisurl'),
|
'label' => lng('domains.redirectifpathisurl'),
|
||||||
'desc' => lng('domains.redirectifpathisurlinfo'),
|
'desc' => lng('domains.redirectifpathisurlinfo'),
|
||||||
'type' => 'select',
|
'type' => 'select',
|
||||||
@@ -77,7 +78,7 @@ return [
|
|||||||
'selected' => $def_code
|
'selected' => $def_code
|
||||||
],
|
],
|
||||||
'selectserveralias' => [
|
'selectserveralias' => [
|
||||||
'visible' => ($result['parentdomainid'] == '0' && $userinfo['subdomains'] != '0') || $result['parentdomainid'] != '0',
|
'visible' => (($result['parentdomainid'] == '0' && $userinfo['subdomains'] != '0') || $result['parentdomainid'] != '0') && (int)$result['email_only'] == 0,
|
||||||
'label' => lng('admin.selectserveralias'),
|
'label' => lng('admin.selectserveralias'),
|
||||||
'desc' => lng('admin.selectserveralias_desc'),
|
'desc' => lng('admin.selectserveralias_desc'),
|
||||||
'type' => 'select',
|
'type' => 'select',
|
||||||
@@ -85,27 +86,28 @@ return [
|
|||||||
'selected' => $serveraliasoptions_selected
|
'selected' => $serveraliasoptions_selected
|
||||||
],
|
],
|
||||||
'isemaildomain' => [
|
'isemaildomain' => [
|
||||||
'visible' => ($result['subcanemaildomain'] == '1' || $result['subcanemaildomain'] == '2') && $result['parentdomainid'] != '0',
|
'visible' => (($result['subcanemaildomain'] == '1' || $result['subcanemaildomain'] == '2') && $result['parentdomainid'] != '0') && (int)$result['email_only'] == 0,
|
||||||
'label' => 'Emaildomain',
|
'label' => 'Emaildomain',
|
||||||
'type' => 'checkbox',
|
'type' => 'checkbox',
|
||||||
'value' => '1',
|
'value' => '1',
|
||||||
'checked' => $result['isemaildomain']
|
'checked' => $result['isemaildomain']
|
||||||
],
|
],
|
||||||
'openbasedir_path' => [
|
'openbasedir_path' => [
|
||||||
'visible' => $result['openbasedir'] == '1',
|
'visible' => $result['openbasedir'] == '1' && (int)$result['email_only'] == 0,
|
||||||
'label' => lng('domain.openbasedirpath'),
|
'label' => lng('domain.openbasedirpath'),
|
||||||
'type' => 'select',
|
'type' => 'select',
|
||||||
'select_var' => $openbasedir,
|
'select_var' => $openbasedir,
|
||||||
'selected' => $result['openbasedir_path']
|
'selected' => $result['openbasedir_path']
|
||||||
],
|
],
|
||||||
'phpsettingid' => [
|
'phpsettingid' => [
|
||||||
'visible' => ((int)Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) && count($phpconfigs) > 0 && $userinfo['phpenabled'] == '1' && $result['phpenabled'] == '1',
|
'visible' => ((int)Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) && count($phpconfigs) > 0 && $userinfo['phpenabled'] == '1' && $result['phpenabled'] == '1' && (int)$result['email_only'] == 0,
|
||||||
'label' => lng('admin.phpsettings.title'),
|
'label' => lng('admin.phpsettings.title'),
|
||||||
'type' => 'select',
|
'type' => 'select',
|
||||||
'select_var' => $phpconfigs,
|
'select_var' => $phpconfigs,
|
||||||
'selected' => $result['phpsettingid']
|
'selected' => $result['phpsettingid']
|
||||||
],
|
],
|
||||||
'speciallogfile' => [
|
'speciallogfile' => [
|
||||||
|
'visible' => (int)$result['email_only'] == 0,
|
||||||
'label' => lng('admin.speciallogfile.title'),
|
'label' => lng('admin.speciallogfile.title'),
|
||||||
'desc' => lng('admin.speciallogfile.description'),
|
'desc' => lng('admin.speciallogfile.description'),
|
||||||
'type' => 'checkbox',
|
'type' => 'checkbox',
|
||||||
@@ -139,7 +141,7 @@ return [
|
|||||||
'section_bssl' => [
|
'section_bssl' => [
|
||||||
'title' => lng('admin.webserversettings_ssl'),
|
'title' => lng('admin.webserversettings_ssl'),
|
||||||
'image' => 'icons/domain_edit.png',
|
'image' => 'icons/domain_edit.png',
|
||||||
'visible' => Settings::Get('system.use_ssl') == '1' && $ssl_ipsandports && Domain::domainHasSslIpPort($result['id']),
|
'visible' => Settings::Get('system.use_ssl') == '1' && $ssl_ipsandports && Domain::domainHasSslIpPort($result['id']) && (int)$result['email_only'] == 0,
|
||||||
'fields' => [
|
'fields' => [
|
||||||
'sslenabled' => [
|
'sslenabled' => [
|
||||||
'label' => lng('admin.domain_sslenabled'),
|
'label' => lng('admin.domain_sslenabled'),
|
||||||
@@ -194,6 +196,7 @@ return [
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
],
|
||||||
|
'buttons' => ((int)$result['email_only'] == 1) ? [] : null
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -110,6 +110,12 @@ return [
|
|||||||
'class' => 'text-center',
|
'class' => 'text-center',
|
||||||
'callback' => [Text::class, 'boolean'],
|
'callback' => [Text::class, 'boolean'],
|
||||||
],
|
],
|
||||||
|
'type_2fa' => [
|
||||||
|
'label' => lng('2fa.type_2fa'),
|
||||||
|
'field' => 'type_2fa',
|
||||||
|
'class' => 'text-center',
|
||||||
|
'callback' => [Text::class, 'type2fa'],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
'visible_columns' => Listing::getVisibleColumnsForListing('admin_list', [
|
'visible_columns' => Listing::getVisibleColumnsForListing('admin_list', [
|
||||||
'loginname',
|
'loginname',
|
||||||
|
|||||||
@@ -149,6 +149,12 @@ return [
|
|||||||
'class' => 'text-center',
|
'class' => 'text-center',
|
||||||
'callback' => [Text::class, 'boolean'],
|
'callback' => [Text::class, 'boolean'],
|
||||||
],
|
],
|
||||||
|
'c.type_2fa' => [
|
||||||
|
'label' => lng('2fa.type_2fa'),
|
||||||
|
'field' => 'type_2fa',
|
||||||
|
'class' => 'text-center',
|
||||||
|
'callback' => [Text::class, 'type2fa'],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
'visible_columns' => Listing::getVisibleColumnsForListing('customer_list', [
|
'visible_columns' => Listing::getVisibleColumnsForListing('customer_list', [
|
||||||
'c.name',
|
'c.name',
|
||||||
|
|||||||
@@ -57,3 +57,4 @@ const TABLE_PANEL_PLANS = 'panel_plans';
|
|||||||
const TABLE_API_KEYS = 'api_keys';
|
const TABLE_API_KEYS = 'api_keys';
|
||||||
const TABLE_PANEL_USERCOLUMNS = 'panel_usercolumns';
|
const TABLE_PANEL_USERCOLUMNS = 'panel_usercolumns';
|
||||||
const TABLE_PANEL_LOGINLINKS = 'panel_loginlinks';
|
const TABLE_PANEL_LOGINLINKS = 'panel_loginlinks';
|
||||||
|
const TABLE_PANEL_2FA_TOKENS = 'panel_2fa_tokens';
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ return [
|
|||||||
'2fa_ga_desc' => 'Das Konto ist eingerichtet, um zeitbasierte Einmalpasswörter via Authenticator-App zu erhalten. Um die gewünschte Authenticator-App einzurichten, scanne bitte den untenstehenden QR-Code. Zum Deaktivieren, klicke auf "2FA deaktivieren"',
|
'2fa_ga_desc' => 'Das Konto ist eingerichtet, um zeitbasierte Einmalpasswörter via Authenticator-App zu erhalten. Um die gewünschte Authenticator-App einzurichten, scanne bitte den untenstehenden QR-Code. Zum Deaktivieren, klicke auf "2FA deaktivieren"',
|
||||||
'2fa_not_activated' => 'Zwei-Faktor Authentifizierung ist nicht aktiviert',
|
'2fa_not_activated' => 'Zwei-Faktor Authentifizierung ist nicht aktiviert',
|
||||||
'2fa_not_activated_for_user' => 'Zwei-Faktor Authentifizierung ist für den aktuellen Benutzer nicht aktiviert',
|
'2fa_not_activated_for_user' => 'Zwei-Faktor Authentifizierung ist für den aktuellen Benutzer nicht aktiviert',
|
||||||
|
'type_2fa' => '2FA Status',
|
||||||
],
|
],
|
||||||
'admin' => [
|
'admin' => [
|
||||||
'overview' => 'Übersicht',
|
'overview' => 'Übersicht',
|
||||||
@@ -713,6 +714,7 @@ return [
|
|||||||
'hsts' => 'HSTS aktiviert',
|
'hsts' => 'HSTS aktiviert',
|
||||||
'aliasdomainid' => 'ID der Alias-Domain',
|
'aliasdomainid' => 'ID der Alias-Domain',
|
||||||
'nodomainsassignedbyadmin' => 'Diesem Account wurde noch keine (aktive) Domain zugewiesen. Bitte kontaktiere deinen Administrator, wenn du der Meinung bist, das ist nicht korrekt.',
|
'nodomainsassignedbyadmin' => 'Diesem Account wurde noch keine (aktive) Domain zugewiesen. Bitte kontaktiere deinen Administrator, wenn du der Meinung bist, das ist nicht korrekt.',
|
||||||
|
'email_only' => 'Nur E-Mail',
|
||||||
],
|
],
|
||||||
'emails' => [
|
'emails' => [
|
||||||
'description' => 'Hier können Sie Ihre E-Mail-Adressen einrichten.<br />Ein Konto ist wie Ihr Briefkasten vor der Haustür. Wenn jemand eine E-Mail an Sie schreibt, wird diese in dieses Konto gelegt.<br /><br />Die Zugangsdaten lauten wie folgt: (Die Angaben in <i>kursiver</i> Schrift sind durch die jeweiligen Einträge zu ersetzen)<br /><br />Hostname: <b><i>Domainname</i></b><br />Benutzername: <b><i>Kontoname / E-Mail-Adresse</i></b><br />Passwort: <b><i>das gewählte Passwort</i></b>',
|
'description' => 'Hier können Sie Ihre E-Mail-Adressen einrichten.<br />Ein Konto ist wie Ihr Briefkasten vor der Haustür. Wenn jemand eine E-Mail an Sie schreibt, wird diese in dieses Konto gelegt.<br /><br />Die Zugangsdaten lauten wie folgt: (Die Angaben in <i>kursiver</i> Schrift sind durch die jeweiligen Einträge zu ersetzen)<br /><br />Hostname: <b><i>Domainname</i></b><br />Benutzername: <b><i>Kontoname / E-Mail-Adresse</i></b><br />Passwort: <b><i>das gewählte Passwort</i></b>',
|
||||||
@@ -1031,6 +1033,7 @@ return [
|
|||||||
'combination_not_found' => 'Kombination aus Benutzername und E-Mail Adresse stimmen nicht überein.',
|
'combination_not_found' => 'Kombination aus Benutzername und E-Mail Adresse stimmen nicht überein.',
|
||||||
'2fa' => 'Zwei-Faktor Authentifizierung (2FA)',
|
'2fa' => 'Zwei-Faktor Authentifizierung (2FA)',
|
||||||
'2facode' => 'Bitte 2FA Code angeben',
|
'2facode' => 'Bitte 2FA Code angeben',
|
||||||
|
'2faremember' => 'Browser vertrauen',
|
||||||
],
|
],
|
||||||
'mails' => [
|
'mails' => [
|
||||||
'pop_success' => [
|
'pop_success' => [
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ return [
|
|||||||
'2fa_ga_desc' => 'Your account is set up to use time-based one-time passwords via authenticator-app. Please scan the QR code below with your desired authenticator app to generate the codes. To deactivate, click on "Deactivate 2FA"',
|
'2fa_ga_desc' => 'Your account is set up to use time-based one-time passwords via authenticator-app. Please scan the QR code below with your desired authenticator app to generate the codes. To deactivate, click on "Deactivate 2FA"',
|
||||||
'2fa_not_activated' => 'Two-factor authentication is not enabled',
|
'2fa_not_activated' => 'Two-factor authentication is not enabled',
|
||||||
'2fa_not_activated_for_user' => 'Two-factor authentication is not enabled for the current user',
|
'2fa_not_activated_for_user' => 'Two-factor authentication is not enabled for the current user',
|
||||||
|
'type_2fa' => '2FA status',
|
||||||
],
|
],
|
||||||
'admin' => [
|
'admin' => [
|
||||||
'overview' => 'Overview',
|
'overview' => 'Overview',
|
||||||
@@ -784,6 +785,7 @@ return [
|
|||||||
'hsts' => 'HSTS enabled',
|
'hsts' => 'HSTS enabled',
|
||||||
'aliasdomainid' => 'ID of alias domain',
|
'aliasdomainid' => 'ID of alias domain',
|
||||||
'nodomainsassignedbyadmin' => 'Your account has currently no (active) domains assigned to it. Please contact your administrator if you think this is wrong.',
|
'nodomainsassignedbyadmin' => 'Your account has currently no (active) domains assigned to it. Please contact your administrator if you think this is wrong.',
|
||||||
|
'email_only' => 'Email only',
|
||||||
],
|
],
|
||||||
'emails' => [
|
'emails' => [
|
||||||
'description' => 'Here you can create and change your email addresses.<br />An account is like your letterbox in front of your house. If someone sends you an email, it will be dropped into the account.<br /><br />To download your emails use the following settings in your mailprogram: (The data in <i>italics</i> has to be changed to the equivalents you typed in!)<br />Hostname: <b><i>domainname</i></b><br />Username: <b><i>account name / e-mail address</i></b><br />password: <b><i>the password you\'ve chosen</i></b>',
|
'description' => 'Here you can create and change your email addresses.<br />An account is like your letterbox in front of your house. If someone sends you an email, it will be dropped into the account.<br /><br />To download your emails use the following settings in your mailprogram: (The data in <i>italics</i> has to be changed to the equivalents you typed in!)<br />Hostname: <b><i>domainname</i></b><br />Username: <b><i>account name / e-mail address</i></b><br />password: <b><i>the password you\'ve chosen</i></b>',
|
||||||
@@ -1103,6 +1105,7 @@ return [
|
|||||||
'combination_not_found' => 'Combination of user and email address not found.',
|
'combination_not_found' => 'Combination of user and email address not found.',
|
||||||
'2fa' => 'Two-factor authentication (2FA)',
|
'2fa' => 'Two-factor authentication (2FA)',
|
||||||
'2facode' => 'Please enter 2FA code',
|
'2facode' => 'Please enter 2FA code',
|
||||||
|
'2faremember' => 'Trust browser',
|
||||||
],
|
],
|
||||||
'mails' => [
|
'mails' => [
|
||||||
'pop_success' => [
|
'pop_success' => [
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ if (function_exists('exec')) {
|
|||||||
}
|
}
|
||||||
$domain = json_decode($json_result, true)['data'];
|
$domain = json_decode($json_result, true)['data'];
|
||||||
|
|
||||||
|
if ($domain['email_only']) {
|
||||||
|
Response::dynamicError("There are no webserver logfiles for email only domains.");
|
||||||
|
}
|
||||||
|
|
||||||
$speciallogfile = '';
|
$speciallogfile = '';
|
||||||
if ($domain['speciallogfile'] == '1') {
|
if ($domain['speciallogfile'] == '1') {
|
||||||
if ($domain['parentdomainid'] == '0') {
|
if ($domain['parentdomainid'] == '0') {
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ if ($action == '' || $action == 'view') {
|
|||||||
}
|
}
|
||||||
$result_domain = json_decode($json_result, true)['data'];
|
$result_domain = json_decode($json_result, true)['data'];
|
||||||
|
|
||||||
|
if ($result_domain['email_only']) {
|
||||||
|
Response::dynamicError("There are no ssl-certificates for email only domains.");
|
||||||
|
}
|
||||||
|
|
||||||
if (Request::post('send') == 'send') {
|
if (Request::post('send') == 'send') {
|
||||||
$do_insert = Request::post('do_insert', 0) == 1;
|
$do_insert = Request::post('do_insert', 0) == 1;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -22,6 +22,14 @@
|
|||||||
<input class="form-control" type="text" name="2fa_code" id="2fa_code" value="" autocomplete="off" autofocus required/>
|
<input class="form-control" type="text" name="2fa_code" id="2fa_code" value="" autocomplete="off" autofocus required/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input type="hidden" name="2fa_remember" value="0"/>
|
||||||
|
<input class="form-check-input" type="checkbox" id="2fa_remember" name="2fa_remember" value="1">
|
||||||
|
<label class="form-check-label" for="2fa_remember">{{ lng('login.2faremember') }}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body d-grid gap-2">
|
<div class="card-body d-grid gap-2">
|
||||||
|
|||||||
@@ -29,6 +29,16 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro type2fa(data) %}
|
||||||
|
{% if (data == 1) %}
|
||||||
|
<i class="fa-solid fa-envelope text-success"></i>
|
||||||
|
{% elseif (data == 2) %}
|
||||||
|
<i class="fa-solid fa-mobile text-success"></i>
|
||||||
|
{% else %}
|
||||||
|
<i class="fa-solid fa-lock-open text-warning"></i>
|
||||||
|
{% endif %}
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro link(data) %}
|
{% macro link(data) %}
|
||||||
{% apply spaceless %}
|
{% apply spaceless %}
|
||||||
<a href="{{ data.href }}" {% if data.class is defined %} class="{{ data.class }}" {% endif %} {% if data.target is defined %} target="{{ data.target }}" {% endif %} {% if data.title is defined %} title="{{ data.title }}" {% endif %}>
|
<a href="{{ data.href }}" {% if data.class is defined %} class="{{ data.class }}" {% endif %} {% if data.target is defined %} target="{{ data.target }}" {% endif %} {% if data.title is defined %} title="{{ data.title }}" {% endif %}>
|
||||||
|
|||||||
@@ -47,6 +47,8 @@
|
|||||||
{{ macros.domainWithSan(td.data.data) }}
|
{{ macros.domainWithSan(td.data.data) }}
|
||||||
{% elseif td.data.macro == 'actions' %}
|
{% elseif td.data.macro == 'actions' %}
|
||||||
{{ macros.actions(td.data.data) }}
|
{{ macros.actions(td.data.data) }}
|
||||||
|
{% elseif td.data.macro == 'type2fa' %}
|
||||||
|
{{ macros.type2fa(td.data.data) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
Table macro '{{ td.data.macro|json_encode }}' is not implemented!
|
Table macro '{{ td.data.macro|json_encode }}' is not implemented!
|
||||||
Unable to handle this data: {{ td.data|json_encode }}
|
Unable to handle this data: {{ td.data|json_encode }}
|
||||||
|
|||||||
Reference in New Issue
Block a user