Skip to content

Commit d487244

Browse files
committed
Admin: Security: Add configuration setting 'security_password_rotate_days' to enable password rotation requirement - refs BT#21146 #4960
1 parent 12655c3 commit d487244

File tree

4 files changed

+130
-17
lines changed

4 files changed

+130
-17
lines changed

main/auth/profile.php

+19
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,25 @@ function show_image(image,width,height) {
675675
$sql = rtrim($sql, ',');
676676
if ($changePassword && !empty($password)) {
677677
UserManager::updatePassword(api_get_user_id(), $password);
678+
if (api_get_configuration_value('security_password_rotate_days') > 0) {
679+
$date = api_get_local_time(
680+
null,
681+
'UTC',
682+
'UTC',
683+
null,
684+
null,
685+
null,
686+
'Y-m-d H:i:s'
687+
);
688+
$extraFieldValue = new ExtraFieldValue('user');
689+
$extraFieldValue->save(
690+
[
691+
'item_id' => $user->getId(),
692+
'variable' => 'password_updated_at',
693+
'value' => $date
694+
]
695+
);
696+
}
678697
}
679698

680699
if (api_get_setting('profile', 'officialcode') === 'true' &&

main/auth/reset.php

+23
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
$form = new FormValidator('reset', 'POST', api_get_self().'?token='.$token);
2626
$form->addElement('header', get_lang('ResetPassword'));
2727
$form->addHidden('token', $token);
28+
if (!empty($_GET['rotate'])) {
29+
$form->addElement('html', Display::return_message(get_lang('PasswordExpiredPleaseSetNewPassword'), 'warning'));
30+
}
31+
2832
$form->addElement(
2933
'password',
3034
'pass1',
@@ -83,6 +87,25 @@
8387
$extraFieldValue->delete($value['id']);
8488
}
8589
}
90+
if (api_get_configuration_value('security_password_rotate_days') > 0) {
91+
$extraFieldValue = new ExtraFieldValue('user');
92+
$date = api_get_local_time(
93+
null,
94+
'UTC',
95+
'UTC',
96+
null,
97+
null,
98+
null,
99+
'Y-m-d H:i:s'
100+
);
101+
$extraFieldValue->save(
102+
[
103+
'item_id' => $user->getId(),
104+
'variable' => 'password_updated_at',
105+
'value' => $date
106+
]
107+
);
108+
}
86109

87110
Display::addFlash(Display::return_message(get_lang('Updated')));
88111
header('Location: '.api_get_path(WEB_PATH));

main/inc/lib/usermanager.lib.php

+80-17
Original file line numberDiff line numberDiff line change
@@ -1661,6 +1661,23 @@ public static function update_user(
16611661
if (!is_null($password)) {
16621662
$user->setPlainPassword($password);
16631663
Event::addEvent(LOG_USER_PASSWORD_UPDATE, LOG_USER_ID, $user_id);
1664+
$date = api_get_local_time(
1665+
null,
1666+
null,
1667+
null,
1668+
null,
1669+
null,
1670+
null,
1671+
'Y-m-d'
1672+
);
1673+
$extraFieldValue = new ExtraFieldValue('user');
1674+
$extraFieldValue->save(
1675+
[
1676+
'item_id' => $user->getId(),
1677+
'variable' => 'password_updated_at',
1678+
'value' => $date
1679+
]
1680+
);
16641681
}
16651682

16661683
$userManager->updateUser($user, true);
@@ -7683,29 +7700,75 @@ public static function deleteUserFiles($userId)
76837700

76847701
public static function redirectToResetPassword($userId)
76857702
{
7686-
if (!api_get_configuration_value('force_renew_password_at_first_login')) {
7687-
return;
7703+
$forceRenew = api_get_configuration_value('force_renew_password_at_first_login');
7704+
7705+
if ($forceRenew) {
7706+
$askPassword = self::get_extra_user_data_by_field(
7707+
$userId,
7708+
'ask_new_password'
7709+
);
7710+
7711+
if (!empty($askPassword) && isset($askPassword['ask_new_password']) &&
7712+
1 === (int)$askPassword['ask_new_password']
7713+
) {
7714+
$uniqueId = api_get_unique_id();
7715+
$userObj = api_get_user_entity($userId);
7716+
7717+
$userObj->setConfirmationToken($uniqueId);
7718+
$userObj->setPasswordRequestedAt(new \DateTime());
7719+
7720+
Database::getManager()->persist($userObj);
7721+
Database::getManager()->flush();
7722+
7723+
$url = api_get_path(WEB_CODE_PATH).'auth/reset.php?token='.$uniqueId;
7724+
api_location($url);
7725+
}
76887726
}
76897727

7690-
$askPassword = self::get_extra_user_data_by_field(
7691-
$userId,
7692-
'ask_new_password'
7693-
);
7728+
$forceRotateDays = api_get_configuration_value('security_password_rotate_days');
7729+
$forceRotate = false;
76947730

7695-
if (!empty($askPassword) && isset($askPassword['ask_new_password']) &&
7696-
1 === (int) $askPassword['ask_new_password']
7697-
) {
7698-
$uniqueId = api_get_unique_id();
7699-
$userObj = api_get_user_entity($userId);
7731+
if ($forceRotateDays > 0) {
7732+
// get the date of the last password update recorded
7733+
$lastUpdate = self::get_extra_user_data_by_field(
7734+
$userId,
7735+
'password_updated_at'
7736+
);
77007737

7701-
$userObj->setConfirmationToken($uniqueId);
7702-
$userObj->setPasswordRequestedAt(new \DateTime());
7738+
if (empty($lastUpdate) or empty($lastUpdate['password_updated_at'])) {
7739+
error_log('No password_updated_at');
7740+
$userObj = api_get_user_entity($userId);
7741+
$registrationDate = $userObj->getRegistrationDate();
7742+
$now = new \DateTime(null, new DateTimeZone('UTC'));
7743+
$interval = $now->diff($registrationDate);
7744+
$daysSince = $interval->format('%a');
7745+
error_log('Days since registration: '.$daysSince);
7746+
if ($daysSince > $forceRotateDays) {
7747+
error_log('We need to force reset');
7748+
$forceRotate = true;
7749+
}
7750+
} else {
7751+
$now = new \DateTime(null, new DateTimeZone('UTC'));
7752+
$date = \DateTime::createFromFormat('Y-m-d H:i:s', $lastUpdate['password_updated_at'], new DateTimeZone('UTC'));
7753+
$interval = $now->diff($date);
7754+
$daysSince = $interval->format('%a');
7755+
if ($daysSince > $forceRotateDays) {
7756+
$forceRotate = true;
7757+
}
7758+
}
7759+
if ($forceRotate) {
7760+
$uniqueId = api_get_unique_id();
7761+
$userObj = api_get_user_entity($userId);
7762+
7763+
$userObj->setConfirmationToken($uniqueId);
7764+
$userObj->setPasswordRequestedAt(new \DateTime());
77037765

7704-
Database::getManager()->persist($userObj);
7705-
Database::getManager()->flush();
7766+
Database::getManager()->persist($userObj);
7767+
Database::getManager()->flush();
77067768

7707-
$url = api_get_path(WEB_CODE_PATH).'auth/reset.php?token='.$uniqueId;
7708-
api_location($url);
7769+
$url = api_get_path(WEB_CODE_PATH).'auth/reset.php?token='.$uniqueId.'&rotate=1';
7770+
api_location($url);
7771+
}
77097772
}
77107773
}
77117774

main/install/configuration.dist.php

+8
Original file line numberDiff line numberDiff line change
@@ -2503,3 +2503,11 @@
25032503

25042504
//hide copy icon in LP's authoring options
25052505
//$_configuration['lp_hide_copy_option'] = false;
2506+
2507+
// Password rotation
2508+
// Requires creating a "Date and time" extra user field with the system id "password_updated_at"
2509+
// Note: only a password change by the user itself will be taken into account.
2510+
// Admins editing someone else's password do not count as a password update that would avoid the rotation request.
2511+
// If this feature is enabled on an existing portal, the registration date of users will be taken as
2512+
// the latest password change date.
2513+
//$_configuration['security_password_rotate_days'] = 90;

0 commit comments

Comments
 (0)