[TikiWiki-commits] [Git][tikiwiki/tiki][master] [NEW] 2FA: Apply a grace period per user and per group for enforced 2FA method subjects
"Merci Jacob \(@mercihabam\) via TikiWiki-cvs" <[email protected]>
| Newsgroups | gmane.comp.cms.tiki.cvs |
|---|---|
| Message-ID | <68fcc0cf837e1_2ce1b044768a@gitlab-sidekiq-low-urgency-cpu-bound-v2-854849f664-whvp4.mail> |
Merci Jacob pushed to branch master at Tiki Wiki CMS Groupware / Tiki
Commits:
a4dad2b1 by Merci Jacob at 2025-10-25T12:13:54+00:00
[NEW] 2FA: Apply a grace period per user and per group for enforced 2FA method subjects
---
* reset user grace period start only when a grace period is given
* fix db
* add per-user grace period
* fix php linter
* [NEW] 2FA: Apply a grace period per user and per group for enforced 2FA method subjects
See merge request tikiwiki/tiki!8710
- - - - -
12 changed files:
- db/tiki.sql
- + installer/schema/20251003_2fa_grace_period_tiki.sql
- lib/core/Services/Group/Controller.php
- lib/core/Tiki/Profile/Installer.php
- lib/prefs/global.php
- lib/userslib.php
- templates/admin/include_login.tpl
- templates/tiki-admingroups.tpl
- templates/tiki-adminusers.tpl
- tiki-admingroups.php
- tiki-adminusers.php
- tiki-setup.php
Changes:
=====================================
db/tiki.sql
=====================================
@@ -2944,6 +2944,7 @@ CREATE TABLE `users_groups` (
`prorateInterval` varchar(255) default '',
`isRole` char(1) DEFAULT 'n',
`isTplGroup` char(1) DEFAULT 'n',
+ `twoFactorAuthGracePeriod` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `groupName` (`groupName` (191)),
KEY `expireAfter` (`expireAfter`)
@@ -3006,6 +3007,8 @@ CREATE TABLE `users_users` (
`unsuccessful_logins` int(14) default 0,
`waiting` char(1) default NULL,
`twoFactorSecret` varchar(32) default NULL,
+ `twoFactorAuthGracePeriod` int(11) DEFAULT NULL,
+ `twoFactorGracePeriodStart` int(14) DEFAULT NULL,
`last_mfa_date` bigint DEFAULT NULL,
PRIMARY KEY (`userId`),
UNIQUE KEY `login` (login (191)),
=====================================
installer/schema/20251003_2fa_grace_period_tiki.sql
=====================================
@@ -0,0 +1,5 @@
+ALTER TABLE `users_users`
+ADD `twoFactorAuthGracePeriod` INT(11) DEFAULT NULL AFTER `twoFactorSecret`,
+ADD `twoFactorGracePeriodStart` INT(14) DEFAULT NULL AFTER `twoFactorAuthGracePeriod`;
+
+ALTER TABLE `users_groups` ADD `twoFactorAuthGracePeriod` INT(11) DEFAULT NULL AFTER `isTplGroup`;
=====================================
lib/core/Services/Group/Controller.php
=====================================
@@ -373,7 +373,8 @@ class Services_Group_Controller
$params['color'],
$params['isRole'],
$params['isTplGroup'],
- $params['include_groups'] ?? []
+ $params['include_groups'] ?? [],
+ $params['twoFactorAuthGracePeriod']
);
@@ -723,7 +724,7 @@ class Services_Group_Controller
'groupfield' => 0,
'userstracker' => 0,
'usersfield' => 0,
- 'registrationUsersFieldIds' => ''
+ 'registrationUsersFieldIds' => '',
];
global $prefs;
$prefGroupTracker = isset($prefs['groupTracker']) and $prefs['groupTracker'] == 'y';
=====================================
lib/core/Tiki/Profile/Installer.php
=====================================
@@ -634,7 +634,8 @@ class Tiki_Profile_Installer
$info['emailPattern'],
$info['anniversary'],
$info['prorateInterval'],
- $info['groupColor']
+ $info['groupColor'],
+ twaFAGracePeriod: $info['twoFactorAuthGracePeriod']
);
$this->setFeedback(tra('Group modified') . ': ' . $info['groupName']);
}
@@ -808,7 +809,8 @@ class Tiki_Profile_Installer
$info['email_pattern'],
$info['anniversary'],
$info['prorate_interval'],
- $info['color']
+ $info['color'],
+ twaFAGracePeriod: $info['twoFactorAuthGracePeriod']
);
}
}
=====================================
lib/prefs/global.php
=====================================
@@ -259,6 +259,15 @@ function prefs_global_list($partial = false)
],
'default' => 'n',
],
+ 'twoFactorAuthGracePeriod' => [
+ 'name' => tra('2FA Grace Period'),
+ 'description' => tra('Number of days to allow users to access the site without 2FA before forcing them to set it up. Note: this applies globally. If you want specific periods per groups, visit the groups settings.'),
+ 'type' => 'text',
+ 'default' => '0',
+ 'dependencies' => [
+ 'twoFactorAuth',
+ ],
+ ],
'twoFactorAuthIncludedGroup' => [
'name' => tra('Force users in the indicated groups to enable 2FA'),
'description' => tra('List of group names.'),
=====================================
lib/userslib.php
=====================================
@@ -7514,6 +7514,51 @@ class UsersLib extends TikiLib
return $this->getOne($query, [$user]);
}
+ public function get2FAGracePeriod($user)
+ {
+ global $prefs;
+
+ $userInfo = $this->get_user_info($user);
+ if ($userInfo['twoFactorAuthGracePeriod'] !== null) {
+ return (int)$userInfo['twoFactorAuthGracePeriod'];
+ }
+
+ $userGroups = $this->get_user_groups($user);
+ $groupsGracePeriods = array_filter(array_map(function ($group) {
+ return $this->get_group_info($group)['twoFactorAuthGracePeriod'];
+ }, $userGroups), function ($value) {
+ return $value !== null;
+ });
+ $maxGroupGracePeriod = ! empty($groupsGracePeriods) ? max($groupsGracePeriods) : null;
+
+ return (int)($maxGroupGracePeriod !== null ? $maxGroupGracePeriod : $prefs['twoFactorAuthGracePeriod']);
+ }
+
+ public function get2FAGracePeriodStart($user)
+ {
+ $query = 'select `twoFactorGracePeriodStart` from `users_users` where `login`=?';
+ return $this->getOne($query, [$user]);
+ }
+
+ public function reset2FAGracePeriodStart($user)
+ {
+ $query = 'update `users_users` set `twoFactorGracePeriodStart`=? where binary `login`=?';
+ $now = time();
+ $this->query($query, [$now, $user]);
+ return $now;
+ }
+
+ public function setTwoFactorAuthGracePeriod($user, $gracePeriod)
+ {
+ if ($gracePeriod === '') {
+ $gracePeriod = null;
+ }
+
+ $query = 'update `users_users` set `twoFactorAuthGracePeriod`=? where binary `login`=?';
+ $this->query($query, [$gracePeriod, $user]);
+ return $gracePeriod;
+ }
+
public function validate_two_factor($twoFactorSecret, $pin, $user)
{
$google2fa = new Google2FA();
@@ -7639,7 +7684,8 @@ class UsersLib extends TikiLib
$color = '',
$isRole = '',
$isTplGroup = '',
- $include_groups = []
+ $include_groups = [],
+ $twaFAGracePeriod = null
) {
$tikilib = TikiLib::lib('tiki');
@@ -7669,6 +7715,7 @@ class UsersLib extends TikiLib
'prorateInterval' => $prorateInterval,
'isRole' => $isRole,
'isTplGroup' => empty($isTplGroup) ? 'n' : $isTplGroup,
+ 'twoFactorAuthGracePeriod' => $twaFAGracePeriod === '' ? null : $twaFAGracePeriod,
];
$id = $this->table('users_groups')->insert($data);
@@ -7709,7 +7756,8 @@ class UsersLib extends TikiLib
$color = '',
$isRole = '',
$isTplGroup = '',
- $include_groups = []
+ $include_groups = [],
+ $twaFAGracePeriod = null
) {
$isTplGroup = empty($isTplGroup) ? 'n' : $isTplGroup;
$users = $this->get_group_users($group);
@@ -7742,7 +7790,9 @@ class UsersLib extends TikiLib
$prorateInterval,
$color,
$isRole,
- $isTplGroup
+ $isTplGroup,
+ [],
+ $twaFAGracePeriod
);
}
@@ -7770,6 +7820,7 @@ class UsersLib extends TikiLib
'prorateInterval' => $prorateInterval,
'isRole' => $isRole,
'isTplGroup' => $isTplGroup,
+ 'twoFactorAuthGracePeriod' => $twaFAGracePeriod === '' ? null : $twaFAGracePeriod,
];
$this->table('users_groups')->update($data, ['groupName' => $olgroup]);
@@ -7883,7 +7934,8 @@ class UsersLib extends TikiLib
$groupInfo["groupColor"],
$groupInfo["isRole"],
$groupInfo["isTplGroup"],
- $includeGroups
+ $includeGroups,
+ $groupInfo["twoFactorAuthGracePeriod"]
);
return true;
}
=====================================
templates/admin/include_login.tpl
=====================================
@@ -227,6 +227,7 @@
{preference name=twoFactorAuthEmailTokenChars}
{preference name=twoFactorAuthEmailTokenTTL}
{preference name=twoFactorAuthAllUsers}
+ {preference name=twoFactorAuthGracePeriod}
{preference name=twoFactorAuthIncludedGroup}
{preference name=twoFactorAuthIncludedUsers}
{preference name=twoFactorAuthExcludedGroup}
=====================================
templates/tiki-admingroups.tpl
=====================================
@@ -417,6 +417,17 @@
</div>
</div>
{/if}
+ {if $prefs.twoFactorAuth eq 'y'}
+ <div class="mb-3 row">
+ <label class="col-form-label col-md-3">{tr}2FA Grace Period{/tr}</label>
+ <div class="col-md-9">
+ <input type="number" class="form-control" name="twoFactorAuthGracePeriod" value="{$twoFactorAuthGracePeriod|escape}">
+ <div class="form-text">
+ {tr}Number of days to allow users in this group to access the site without 2FA before forcing them to set it up.{/tr}
+ </div>
+ </div>
+ </div>
+ {/if}
{if $groupname neq 'Anonymous' and $groupname neq 'Registered' and $groupname neq 'Admins'}
<div class="mb-3 row">
<label class="col-form-label col-md-3">{tr}User Choice{/tr}</label>
=====================================
templates/tiki-adminusers.tpl
=====================================
@@ -613,6 +613,19 @@
<small class="form-text text-muted">{tr}This will require the user to reset up 2FA the next time they log in.{/tr}</small>
</div>
</div>
+
+ {if $force2FA}
+ <div class="mb-3 row">
+ <label class="col-form-label col-md-2">{tr}2FA Grace Period{/tr}</label>
+ <div class="col-md-6">
+ <input type="number" class="form-control" name="twoFactorAuthGracePeriod" value="{$userinfo.twoFactorAuthGracePeriod|escape}">
+ <div class="form-text">
+ {tr}Number of days to allow this user to access the site without 2FA before forcing them to set it up.{/tr}
+ </div>
+ </div>
+ </div>
+ {/if}
+
{/if}
<div class="mb-3 row">
=====================================
tiki-admingroups.php
=====================================
@@ -241,6 +241,10 @@ if (! empty($_REQUEST["group"])) {
}
}
}
+
+ if ($prefs['twoFactorAuth'] == 'y') {
+ $smarty->assign('twoFactorAuthGracePeriod', $re['twoFactorAuthGracePeriod']);
+ }
$groupperms = $re["perms"];
//$allgroups = $userlib->list_all_groups();
$allgroups = $userlib->list_can_include_groups($re["groupName"]);
=====================================
tiki-adminusers.php
=====================================
@@ -555,6 +555,11 @@ if (isset($_REQUEST['user']) and $_REQUEST['user']) {
$userlib->send_validation_email($_POST['login'], $userinfo['valid'], $_POST['email'], 'y');
}
+ if (isset($_POST['twoFactorAuthGracePeriod']) && $userinfo['twoFactorAuthGracePeriod'] != $_POST['twoFactorAuthGracePeriod']) {
+ $userlib->setTwoFactorAuthGracePeriod($userinfo['login'], $_POST['twoFactorAuthGracePeriod']);
+ $userlib->reset2FAGracePeriodStart($userinfo['login']);
+ }
+
$cookietab = '1';
}
=====================================
tiki-setup.php
=====================================
@@ -128,14 +128,32 @@ $twoFactorSecret = $userlib->get_2_factor_secret($user);
$force2FA = $userlib->forceTwoFactorAuth($user);
//Check if 2FA is required for a user and if it not yet enabled
if ($prefs['twoFactorAuth'] == 'y' && empty($twoFactorSecret) && $force2FA && ! empty($user)) {
- $accesslib = TikiLib::lib('access');
- //URL to send user who has not yet enabled 2FA
- $accessibleUrl = $base_url . 'tiki-user_preferences.php';
- $pageUrl = $url_scheme . "://" . $host . $requestUri;
- //Do not redirect the user if it is a logout action
- if ($accessibleUrl !== $pageUrl && $pageUrl !== $base_url . 'tiki-logout.php') {
- header('location: tiki-user_preferences.php');
- exit();
+ $gracePeriod = $userlib->get2FAGracePeriod($user);
+ $userGracePeriodStart = $userlib->get2FAGracePeriodStart($user);
+
+ if (! $userGracePeriodStart) {
+ if ($gracePeriod > 0) {
+ $userGracePeriodStart = $userlib->reset2FAGracePeriodStart($user);
+ } else {
+ $userGracePeriodStart = 0;
+ }
+ }
+
+ $userGracePeriodShouldEnd = $userGracePeriodStart + ($gracePeriod * 24 * 3600);
+
+ if ($userGracePeriodShouldEnd < time()) {
+ $accesslib = TikiLib::lib('access');
+ //URL to send user who has not yet enabled 2FA
+ $accessibleUrl = $base_url . 'tiki-user_preferences.php';
+ $pageUrl = $url_scheme . "://" . $host . $requestUri;
+ //Do not redirect the user if it is a logout action
+ if ($accessibleUrl !== $pageUrl && $pageUrl !== $base_url . 'tiki-logout.php') {
+ header('location: tiki-user_preferences.php');
+ exit();
+ }
+ } else {
+ $daysLeft = ceil(($userGracePeriodShouldEnd - time()) / (24 * 3600));
+ Feedback::warning(tra(strtr('You must enable Two-Factor Authentication within the next %days% day(s) to continue using your account. Please update your <a href="%url%">user preferences</a>.', ['%days%' => $daysLeft, '%url%' => 'tiki-user_preferences.php'])));
}
}
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/a4dad2b1e820fa7df8061cf6a3d77e5fede46021
--
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/a4dad2b1e820fa7df8061cf6a3d77e5fede46021
You're receiving this email because of your account on gitlab.com.
_______________________________________________
TikiWiki-cvs mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/tikiwiki-cvs