mirror of
https://github.com/lucaspalomodevelop/core.git
synced 2026-03-20 03:16:12 +00:00
implement password policies for local accounts. for https://github.com/opnsense/core/issues/2252
This change consists of two components: 1) enforcing the user to change his or her password every x days, when pwd_changed_at is not set or longer ago then specified only access to the password page is prohibited 2) enforce minimal length or complexity settings depending on selected choices
This commit is contained in:
parent
8fb70ac4b1
commit
dc74006c9a
@ -788,7 +788,12 @@ function auth_get_authserver_list()
|
||||
return $list;
|
||||
}
|
||||
|
||||
function authenticate_user($username, $password, $authcfg = NULL)
|
||||
/**
|
||||
* return authenticator object
|
||||
* @param array|null $authcfg configuration
|
||||
* @return Auth\Base type object
|
||||
*/
|
||||
function get_authenticator($authcfg = NULL)
|
||||
{
|
||||
if (empty($authcfg)) {
|
||||
$authName = 'Local Database';
|
||||
@ -804,8 +809,12 @@ function authenticate_user($username, $password, $authcfg = NULL)
|
||||
}
|
||||
|
||||
$authFactory = new OPNsense\Auth\AuthenticationFactory;
|
||||
$authenticator = $authFactory->get($authName);
|
||||
return $authFactory->get($authName);
|
||||
}
|
||||
|
||||
function authenticate_user($username, $password, $authcfg = NULL)
|
||||
{
|
||||
$authenticator = get_authenticator($authcfg);
|
||||
if ($authenticator != null) {
|
||||
return $authenticator->authenticate($username, $password) ;
|
||||
} else {
|
||||
|
||||
@ -195,7 +195,7 @@ function session_auth(&$Login_Error)
|
||||
}
|
||||
|
||||
// Detect protocol change
|
||||
if (!isset($_POST['login']) && !empty($_SESSION['Logged_In']) && $_SESSION['protocol'] != $config['system']['webgui']['protocol']) {
|
||||
if (!isset($_POST['login']) && !empty($_SESSION['Username']) && $_SESSION['protocol'] != $config['system']['webgui']['protocol']) {
|
||||
session_write_close();
|
||||
return false;
|
||||
}
|
||||
@ -224,20 +224,34 @@ function session_auth(&$Login_Error)
|
||||
}
|
||||
|
||||
// authenticate using config settings, or local if failed
|
||||
if (authenticate_user($_POST['usernamefld'], $_POST['passwordfld'], $authcfg) ||
|
||||
($authcfg_fallback !== false && authenticate_user($_POST['usernamefld'], $_POST['passwordfld'], $authcfg_fallback))
|
||||
) {
|
||||
$authenticator = get_authenticator($authcfg);
|
||||
$is_authenticated = false;
|
||||
if ($authenticator != null && $authenticator->authenticate($_POST['usernamefld'], $_POST['passwordfld'])) {
|
||||
$is_authenticated = true;
|
||||
}
|
||||
if (!$is_authenticated && $authcfg_fallback !== false) {
|
||||
$authenticator = get_authenticator($authcfg_fallback);
|
||||
if ($authenticator != null && $authenticator->authenticate($_POST['usernamefld'], $_POST['passwordfld'])) {
|
||||
$is_authenticated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($is_authenticated) {
|
||||
// Generate a new id to avoid session fixation
|
||||
session_regenerate_id();
|
||||
$_SESSION['Logged_In'] = "True";
|
||||
$_SESSION['Username'] = $_POST['usernamefld'];
|
||||
$_SESSION['last_access'] = time();
|
||||
$_SESSION['protocol'] = $config['system']['webgui']['protocol'];
|
||||
if ($authenticator->shouldChangePassword($_SESSION['Username'])) {
|
||||
$_SESSION['user_shouldChangePassword'] = true;
|
||||
}
|
||||
if (!isset($config['system']['webgui']['quietlogin'])) {
|
||||
log_error(sprintf("Successful login for user '%s' from: %s", $_POST['usernamefld'], $_SERVER['REMOTE_ADDR']));
|
||||
}
|
||||
if (!empty($_GET['url'])) {
|
||||
header(url_safe("Location: {$_GET['url']}"));
|
||||
} elseif (!empty($_SESSION['user_shouldChangePassword'])) {
|
||||
header("Location: system_usermanager_passwordmg.php");
|
||||
} else {
|
||||
header(url_safe("Location: {$_SERVER['REQUEST_URI']}"));
|
||||
}
|
||||
@ -249,7 +263,7 @@ function session_auth(&$Login_Error)
|
||||
}
|
||||
|
||||
/* Show login page if they aren't logged in */
|
||||
if (empty($_SESSION['Logged_In'])) {
|
||||
if (empty($_SESSION['Username'])) {
|
||||
session_write_close();
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -65,6 +65,28 @@ abstract class Base
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if password meets policy constraints, needs implementation if it applies.
|
||||
* @param string $username username to check
|
||||
* @param string $old_password current password
|
||||
* @param string $new_password password to check
|
||||
* @return array of unmet policy constraints
|
||||
*/
|
||||
public function checkPolicy($username, $old_password, $new_password)
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the user should change his or hers password, needs implementation if it applies.
|
||||
* @param string $username username to check
|
||||
* @return boolean
|
||||
*/
|
||||
public function shouldChangePassword($username)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* user allowed in local group
|
||||
* @param string $username username to check
|
||||
|
||||
@ -64,6 +64,68 @@ class Local extends Base implements IAuthConnector
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* check if password meets policy constraints
|
||||
* @param string $username username to check
|
||||
* @param string $old_password current password
|
||||
* @param string $new_password password to check
|
||||
* @return array of unmet policy constraints
|
||||
*/
|
||||
public function checkPolicy($username, $old_password, $new_password)
|
||||
{
|
||||
$result = array();
|
||||
$configObj = Config::getInstance()->object();
|
||||
if (!empty($configObj->system->webgui->enable_password_policy_constraints)) {
|
||||
if (!empty($configObj->system->webgui->password_policy_length)) {
|
||||
if (strlen($new_password) < $configObj->system->webgui->password_policy_length) {
|
||||
$result[] = sprintf(gettext("Password must have at least %d characters"),
|
||||
$configObj->system->webgui->password_policy_length);
|
||||
}
|
||||
}
|
||||
if (!empty($configObj->system->webgui->password_policy_complexity)) {
|
||||
$pwd_has_upper = preg_match_all('/[A-Z]/',$new_password, $o) > 0;
|
||||
$pwd_has_lower = preg_match_all('/[a-z]/',$new_password, $o) > 0;
|
||||
$pwd_has_number = preg_match_all('/[0-9]/',$new_password, $o) > 0;
|
||||
$pwd_has_special = preg_match_all('/[!@#$%^&*()\-_=+{};:,<.>]/',$new_password, $o) > 0;
|
||||
if ($old_password == $new_password) {
|
||||
// equal password is not allowed
|
||||
$result[] = gettext("Current password equals new password");
|
||||
}
|
||||
if (($pwd_has_upper+$pwd_has_lower+$pwd_has_number+$pwd_has_special) < 3) {
|
||||
// passwords should at least contain 3 of the 4 available character types
|
||||
$result[] = gettext("Password should contain at least 3 of the 4 different character groups".
|
||||
" (lowercase, uppercase, number, special)");
|
||||
} elseif (strpos($new_password, $username) !== false) {
|
||||
$result[] = gettext("The username may not be a part of the password");
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the user should change his or hers password, calculated by the time difference of the last pwd change
|
||||
* @param string $username username to check
|
||||
*/
|
||||
public function shouldChangePassword($username)
|
||||
{
|
||||
$configObj = Config::getInstance()->object();
|
||||
if (!empty($configObj->system->webgui->enable_password_policy_constraints)) {
|
||||
if (!empty($configObj->system->webgui->password_policy_duration)) {
|
||||
$duration = $configObj->system->webgui->password_policy_duration;
|
||||
$userObject = $this->getUser($username);
|
||||
if ($userObject != null) {
|
||||
$now = microtime(true);
|
||||
$pwdChangedAt = empty($userObject->pwd_changed_at) ? 0 : $userObject->pwd_changed_at;
|
||||
if (abs($now - $pwdChangedAt)/60/60/24 >= $configObj->system->webgui->password_policy_duration) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* authenticate user against local database (in config.xml)
|
||||
* @param string|SimpleXMLElement $username username (or xml object) to authenticate
|
||||
|
||||
@ -223,6 +223,9 @@ class ACL
|
||||
if ($url == '/index.php?logout') {
|
||||
// always allow logout, could use better structuring...
|
||||
return true;
|
||||
} elseif (!empty($_SESSION['user_shouldChangePassword'])) {
|
||||
// when a password change is enforced, lock all other endpoints
|
||||
return $this->urlMatch($url, 'system_usermanager_passwordmg.php*');
|
||||
}
|
||||
|
||||
if (array_key_exists($username, $this->userDatabase)) {
|
||||
|
||||
@ -236,6 +236,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
|
||||
if ($pconfig['passwordfld1'] != $pconfig['passwordfld2']) {
|
||||
$input_errors[] = gettext('The passwords do not match.');
|
||||
} elseif (empty($pconfig['gen_new_password'])) {
|
||||
// check against local password policy
|
||||
$authenticator = get_authenticator();
|
||||
$input_errors = array_merge(
|
||||
$input_errors, $authenticator->checkPolicy($pconfig['usernamefld'], null, $pconfig['passwordfld1'])
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($pconfig['passwordfld1']) && !empty($pconfig['gen_new_password'])) {
|
||||
|
||||
@ -47,6 +47,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
|
||||
if (isset($_GET['savemsg'])) {
|
||||
$savemsg = htmlspecialchars(gettext($_GET['savemsg']));
|
||||
} elseif (!empty($_SESSION['user_shouldChangePassword'])) {
|
||||
$savemsg = gettext("Your password has expired, please provide a new one");
|
||||
}
|
||||
|
||||
if ($userFound) {
|
||||
@ -68,12 +70,23 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
|
||||
if (!$userFound) {
|
||||
$input_errors[] = gettext("Sorry, you cannot change settings for a non-local user.");
|
||||
} elseif (count($input_errors) == 0) {
|
||||
$authenticator = get_authenticator();
|
||||
$input_errors = $authenticator->checkPolicy($username, $pconfig['passwordfld0'], $pconfig['passwordfld1']);
|
||||
}
|
||||
}
|
||||
|
||||
if (count($input_errors) == 0) {
|
||||
$config['system']['user'][$userindex[$username]]['language'] = $pconfig['language'];
|
||||
|
||||
// only update password change date if there is a policy constraint
|
||||
if (!empty($config['system']['webgui']['enable_password_policy_constraints']) &&
|
||||
!empty($config['system']['webgui']['password_policy_length'])
|
||||
) {
|
||||
$config['system']['user'][$userindex[$username]]['pwd_changed_at'] = microtime(true);
|
||||
}
|
||||
if (!empty($_SESSION['user_shouldChangePassword'])) {
|
||||
unset($_SESSION['user_shouldChangePassword']);
|
||||
}
|
||||
if ($pconfig['passwordfld1'] !== '' || $pconfig['passwordfld2'] !== '') {
|
||||
local_user_set_password($config['system']['user'][$userindex[$username]], $pconfig['passwordfld1']);
|
||||
local_user_set($config['system']['user'][$userindex[$username]]);
|
||||
|
||||
@ -33,10 +33,17 @@ require_once("guiconfig.inc");
|
||||
$save_and_test = false;
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
$pconfig = array();
|
||||
$pconfig['session_timeout'] = $config['system']['webgui']['session_timeout'];
|
||||
$pconfig['authmode'] = $config['system']['webgui']['authmode'];
|
||||
$pconfig['authmode_fallback'] = !empty($config['system']['webgui']['authmode_fallback']) ? $config['system']['webgui']['authmode_fallback'] : "Local Database";
|
||||
$pconfig['backend'] = $config['system']['webgui']['backend'];
|
||||
foreach (array('session_timeout', 'authmode', 'password_policy_duration',
|
||||
'enable_password_policy_constraints',
|
||||
'password_policy_complexity', 'password_policy_length') as $fieldname) {
|
||||
if (!empty($config['system']['webgui'][$fieldname])) {
|
||||
$pconfig[$fieldname] = $config['system']['webgui'][$fieldname];
|
||||
} else {
|
||||
$pconfig[$fieldname] = null;
|
||||
}
|
||||
}
|
||||
|
||||
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$pconfig = $_POST;
|
||||
$input_errors = array();
|
||||
@ -54,23 +61,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($pconfig['session_timeout'])) {
|
||||
$config['system']['webgui']['session_timeout'] = intval($pconfig['session_timeout']);
|
||||
} elseif (isset($config['system']['webgui']['session_timeout'])) {
|
||||
unset($config['system']['webgui']['session_timeout']);
|
||||
foreach (array('session_timeout', 'authmode', 'authmode_fallback', 'password_policy_duration',
|
||||
'enable_password_policy_constraints',
|
||||
'password_policy_complexity', 'password_policy_length') as $fieldname) {
|
||||
if (!empty($pconfig[$fieldname])) {
|
||||
$config['system']['webgui'][$fieldname] = $pconfig[$fieldname];
|
||||
} elseif (isset($config['system']['webgui'][$fieldname])) {
|
||||
unset($config['system']['webgui'][$fieldname]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($pconfig['authmode'])) {
|
||||
$config['system']['webgui']['authmode'] = $pconfig['authmode'];
|
||||
} elseif (isset($config['system']['webgui']['authmode'])) {
|
||||
unset($config['system']['webgui']['authmode']);
|
||||
}
|
||||
|
||||
if (!empty($pconfig['authmode_fallback'])) {
|
||||
$config['system']['webgui']['authmode_fallback'] = $pconfig['authmode_fallback'];
|
||||
} elseif (isset($config['system']['webgui']['authmode_fallback'])) {
|
||||
unset($config['system']['webgui']['authmode_fallback']);
|
||||
}
|
||||
|
||||
write_config();
|
||||
}
|
||||
@ -81,7 +81,24 @@ include("head.inc");
|
||||
?>
|
||||
|
||||
<body>
|
||||
<style>
|
||||
.password_policy_constraints {
|
||||
display:none;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$("#enable_password_policy_constraints").change(function(){
|
||||
if ($("#enable_password_policy_constraints").prop('checked')) {
|
||||
$(".password_policy_constraints").show();
|
||||
} else {
|
||||
$(".password_policy_constraints").hide();
|
||||
}
|
||||
});
|
||||
$("#enable_password_policy_constraints").change();
|
||||
});
|
||||
|
||||
</script>
|
||||
<?php
|
||||
if ($save_and_test):?>
|
||||
<script>
|
||||
@ -146,7 +163,59 @@ endif;?>
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a id="help_for_enable_password_policy_constraints" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext('Policy'); ?></td>
|
||||
<td>
|
||||
<input id="enable_password_policy_constraints" name="enable_password_policy_constraints" type="checkbox" <?= empty($pconfig['enable_password_policy_constraints']) ? '' : 'checked="checked"';?> />
|
||||
<strong><?= gettext('Enable password policy constraints') ?></strong>
|
||||
<output class="hidden" for="help_for_enable_password_policy_constraints">
|
||||
<?= gettext("Harden security on local accounts, for methods other then local these will usually be configured on the " .
|
||||
"respective provider (e.g. ldap/radius/..). ");?>
|
||||
</output>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="password_policy_constraints">
|
||||
<td><a id="help_for_password_policy_duration" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext('Duration'); ?></td>
|
||||
<td>
|
||||
<select id="password_policy_duration" name="password_policy_duration" class="selectpicker" data-style="btn-default">
|
||||
<option <?=empty($pconfig['password_policy_duration']) ? "selected=\"selected\"" : "";?> value="0"><?=gettext("Disable");?></option>
|
||||
<option <?=$pconfig['password_policy_duration'] == '30' ? "selected=\"selected\"" : "";?> value="30"><?=sprintf(gettext("%d days"), "30");?></option>
|
||||
<option <?=$pconfig['password_policy_duration'] == '90' ? "selected=\"selected\"" : "";?> value="90"><?=sprintf(gettext("%d days"), "90");?></option>
|
||||
<option <?=$pconfig['password_policy_duration'] == '180' ? "selected=\"selected\"" : "";?> value="180"><?=sprintf(gettext("%d days"), "180");?></option>
|
||||
<option <?=$pconfig['password_policy_duration'] == '360' ? "selected=\"selected\"" : "";?> value="360"><?=sprintf(gettext("%d days"), "360");?></option>
|
||||
</select>
|
||||
<output class="hidden" for="help_for_password_policy_duration">
|
||||
<?= gettext("Password duration settings, the interval in days in which passwords stay valid. ".
|
||||
"When reached, the user will be forced to change his or her password before continuing.");?>
|
||||
</output>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="password_policy_constraints">
|
||||
<td><a id="help_for_password_policy_length" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext('Length'); ?></td>
|
||||
<td>
|
||||
<select id="password_policy_length" name="password_policy_length" class="selectpicker" data-style="btn-default">
|
||||
<option <?=empty($pconfig['password_policy_length']) || $pconfig['password_policy_length'] == '8' ? "selected=\"selected\"" : "";?> value="8">8</option>
|
||||
<option <?=$pconfig['password_policy_length'] == '10' ? "selected=\"selected\"" : "";?> value="10">10</option>
|
||||
<option <?=$pconfig['password_policy_length'] == '12' ? "selected=\"selected\"" : "";?> value="12">12</option>
|
||||
<option <?=$pconfig['password_policy_length'] == '14' ? "selected=\"selected\"" : "";?> value="14">14</option>
|
||||
<option <?=$pconfig['password_policy_length'] == '16' ? "selected=\"selected\"" : "";?> value="16">16</option>
|
||||
</select>
|
||||
<output class="hidden" for="help_for_password_policy_length">
|
||||
<?= gettext("Sets the minimum length for a password");?>
|
||||
</output>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="password_policy_constraints">
|
||||
<td><a id="help_for_password_policy_complexity" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext('Complexity'); ?></td>
|
||||
<td>
|
||||
<input id="password_policy_complexity" name="password_policy_complexity" type="checkbox" <?= empty($pconfig['password_policy_complexity']) ? '' : 'checked="checked"';?> />
|
||||
<strong><?= gettext('Enable complexity requirements') ?></strong>
|
||||
<output class="hidden" for="help_for_password_policy_complexity">
|
||||
<?= gettext("Require passwords to meet complexity rules");?>
|
||||
</output>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user