Skip to content

Commit

Permalink
merge current enhancements from main branch to v2.2 (#1261)
Browse files Browse the repository at this point in the history
  • Loading branch information
d00p authored Jul 21, 2024
1 parent b3dc7f9 commit b888e92
Show file tree
Hide file tree
Showing 21 changed files with 201 additions and 55 deletions.
62 changes: 54 additions & 8 deletions index.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
exit();
}
$code = Request::post('2fa_code');
$remember = Request::post('2fa_remember');
// verify entered code
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
// get user-data
Expand Down Expand Up @@ -105,20 +106,49 @@
$userinfo['adminsession'] = $isadmin;
$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
if ($userinfo['type_2fa'] == '1') {
$del_stmt = Database::prepare("UPDATE " . $table . " SET `data_2fa` = '' WHERE `" . $field . "` = :uid");
$userinfo = Database::pexecute_first($del_stmt, [
'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();
}
// wrong 2fa code - treat like "wrong password"
Expand Down Expand Up @@ -349,6 +379,22 @@

// 2FA activated
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
$_SESSION['secret_2fa'] = ($userinfo['type_2fa'] == 2 ? $userinfo['data_2fa'] : 'email');
$_SESSION['uid_2fa'] = $userinfo[$uid];
Expand Down Expand Up @@ -829,8 +875,8 @@ function finishLogin($userinfo)
$theme = $userinfo['theme'];
} else {
$theme = Settings::Get('panel.default_theme');
CurrentUser::setField('theme', $theme);
}
CurrentUser::setField('theme', $theme);

$qryparams = [];
if (!empty($_SESSION['lastqrystr'])) {
Expand Down
15 changes: 13 additions & 2 deletions install/froxlor.sql.php
Original file line number Diff line number Diff line change
Expand Up @@ -730,8 +730,8 @@
('panel', 'logo_overridecustom', '0'),
('panel', 'settings_mode', '0'),
('panel', 'menu_collapsed', '1'),
('panel', 'version', '2.2.0-rc1'),
('panel', 'db_version', '202401090');
('panel', 'version', '2.2.0-rc2'),
('panel', 'db_version', '202407200');
DROP TABLE IF EXISTS `panel_tasks`;
Expand Down Expand Up @@ -1049,4 +1049,15 @@
`allowed_from` text NOT NULL,
UNIQUE KEY `loginname` (`loginname`)
) 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;
23 changes: 23 additions & 0 deletions install/updates/froxlor/update_2.2.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,26 @@
Update::showUpdateStep("Updating from 2.2.0-dev1 to 2.2.0-rc1", false);
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');
}
54 changes: 28 additions & 26 deletions lib/Froxlor/Api/Commands/Emails.php
Original file line number Diff line number Diff line change
Expand Up @@ -270,15 +270,6 @@ public function update()
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);
$ea_optional = $id > 0;
$emailaddr = $this->getParam('emailaddr', $ea_optional, '');
Expand All @@ -297,30 +288,41 @@ public function update()
$iscatchall = $this->getBoolParam('iscatchall', true, $result['iscatchall']);
$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
$customer = $this->getCustomerData();

// check for catchall-flag
$email = $result['email_full'];
if ($iscatchall) {
$iscatchall = '1';
$email_parts = explode('@', $result['email_full']);
$email = '@' . $email_parts[1];
// catchall check
$stmt = Database::prepare("
SELECT `email_full` FROM `" . TABLE_MAIL_VIRTUAL . "`
WHERE `email` = :email AND `customerid` = :cid AND `iscatchall` = '1'
");
$params = [
"email" => $email,
"cid" => $customer['customerid']
];
$email_check = Database::pexecute_first($stmt, $params, true, true);
if ($email_check) {
Response::standardError('youhavealreadyacatchallforthisdomain', '', true);
$email = $result['email'];
// update only required if it was not a catchall before
if ($result['iscatchall'] == 0) {
$email_parts = explode('@', $result['email_full']);
$email = '@' . $email_parts[1];
// catchall check
$stmt = Database::prepare("
SELECT `email_full` FROM `" . TABLE_MAIL_VIRTUAL . "`
WHERE `email` = :email AND `customerid` = :cid AND `iscatchall` = '1'
");
$params = [
"email" => $email,
"cid" => $customer['customerid']
];
$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);
Expand Down
7 changes: 4 additions & 3 deletions lib/Froxlor/Api/Commands/SubDomains.php
Original file line number Diff line number Diff line change
Expand Up @@ -983,9 +983,11 @@ public function listing()
'`d`.`letsencrypt`',
'`d`.`registration_date`',
'`d`.`termination_date`',
'`d`.`deactivated`'
'`d`.`deactivated`',
'`d`.`email_only`',
];
}

$query_fields = [];

// prepare select statement
Expand All @@ -996,7 +998,6 @@ public function listing()
LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` `da` ON `da`.`aliasdomain`=`d`.`id`
LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` `pd` ON `pd`.`id`=`d`.`parentdomainid`
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());

$result = [];
Expand Down Expand Up @@ -1092,13 +1093,13 @@ public function listingCount()
$this->getUserDetail('customerid')
];
}

if (!empty($customer_ids)) {
// prepare select statement
$domains_stmt = Database::prepare("
SELECT COUNT(*) as num_subdom
FROM `" . TABLE_PANEL_DOMAINS . "` `d`
WHERE `d`.`customerid` IN (" . implode(', ', $customer_ids) . ")
AND `d`.`email_only` = '0'
");
$result = Database::pexecute_first($domains_stmt, null, true, true);
if ($result) {
Expand Down
3 changes: 2 additions & 1 deletion lib/Froxlor/Cli/MasterCron.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int
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_2FA_TOKENS . "` WHERE `valid_until` < UNIX_TIMESTAMP()");

return $result;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/Froxlor/Dns/Dns.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public static function getAllowedDomainEntry(int $domain_id, string $area = 'cus
$dom_data['uid'] = $userinfo['userid'];
}
} else {
$where_clause = '`customerid` = :uid AND ';
$where_clause = '`customerid` = :uid AND `email_only` = "0" AND ';
$dom_data['uid'] = $userinfo['userid'];
}

Expand Down
4 changes: 2 additions & 2 deletions lib/Froxlor/Froxlor.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ final class Froxlor
{

// Main version variable
const VERSION = '2.2.0-rc1';
const VERSION = '2.2.0-rc2';

// Database version (YYYYMMDDC where C is a daily counter)
const DBVERSION = '202401090';
const DBVERSION = '202407200';

// Distribution branding-tag (used for Debian etc.)
const BRANDING = '';
Expand Down
10 changes: 7 additions & 3 deletions lib/Froxlor/UI/Callbacks/Domain.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ public static function domainTarget(array $attributes)
if ($attributes['fields']['deactivated']) {
return lng('admin.deactivated');
}
if ($attributes['fields']['email_only']) {
return lng('domains.email_only');
}
// path or redirect
if (preg_match('/^https?\:\/\//', $attributes['fields']['documentroot'])) {
return [
Expand Down Expand Up @@ -127,7 +130,7 @@ public static function canEdit(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']) {
return true;
} elseif ((int)UI::getCurrentUser()['adminsession'] == 1) {
Expand Down Expand Up @@ -157,6 +160,7 @@ public static function canEditDNS(array $attributes): bool
&& $attributes['fields']['caneditdomain'] == '1'
&& Settings::Get('system.bind_enable') == '1'
&& Settings::Get('system.dnsenabled') == '1'
&& !$attributes['fields']['email_only']
&& !$attributes['fields']['deactivated'];
}

Expand All @@ -169,7 +173,7 @@ public static function adminCanEditDNS(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);
}

/**
Expand All @@ -181,7 +185,7 @@ public static function canEditSSL(array $attributes): bool
&& DDomain::domainHasSslIpPort($attributes['fields']['id'])
&& (CurrentUser::isAdmin() || (!CurrentUser::isAdmin() && (int)$attributes['fields']['caneditdomain'] == 1))
&& (int)$attributes['fields']['letsencrypt'] == 0
&& (!CurrentUser::isAdmin() || (CurrentUser::isAdmin() && (int)$attributes['fields']['email_only'] == 0))
&& !(int)$attributes['fields']['email_only']
&& !$attributes['fields']['deactivated']
) {
return true;
Expand Down
8 changes: 8 additions & 0 deletions lib/Froxlor/UI/Callbacks/Text.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ public static function yesno(array $attributes): array
];
}

public static function type2fa(array $attributes): array
{
return [
'macro' => 'type2fa',
'data' => (int)$attributes['data']
];
}

public static function customerfullname(array $attributes): string
{
return User::getCorrectFullUserDetails($attributes['fields'], true);
Expand Down
Loading

0 comments on commit b888e92

Please sign in to comment.