System / Auth / Radius - add group (class) sync and user creation for RADIUS, closes https://github.com/opnsense/core/issues/6111

This commit is contained in:
Ad Schellevis 2022-11-10 15:28:37 +01:00
parent 2a5510a9d8
commit ef0da3ea59
2 changed files with 125 additions and 73 deletions

View File

@ -77,7 +77,24 @@ class Radius extends Base implements IAuthConnector
/**
* @var array internal list of authentication properties (returned by radius auth)
*/
private $lastAuthProperties = array();
private $lastAuthProperties = [];
/**
* @var boolean when set, synchronize groups defined in memberOf attribute to local database
*/
private $syncMemberOf = false;
/**
* @var boolean when set, allow local user creation
*/
private $syncCreateLocalUsers = false;
/**
* @var array limit the groups which will be considered for sync, empty means all
*/
private $syncMemberOfLimit = [];
/**
* type name in configuration
@ -119,6 +136,15 @@ class Radius extends Base implements IAuthConnector
$this->$objectProperty = $config[$confSetting];
}
}
if (!empty($config['sync_create_local_users'])) {
$this->syncCreateLocalUsers = true;
}
if (!empty($config['sync_memberof'])) {
$this->syncMemberOf = true;
}
if (!empty($config['sync_memberof_groups'])) {
$this->syncMemberOfLimit = explode(",", strtolower($config['sync_memberof_groups']));
}
}
/**
@ -433,10 +459,26 @@ class Radius extends Base implements IAuthConnector
}
$this->lastAuthProperties['Framed-Route'][] = $resa['data'];
break;
case RADIUS_CLASS:
if (!empty($this->lastAuthProperties['class'])) {
$this->lastAuthProperties['class'] .= "\n" . $resa['data'];
} else {
$this->lastAuthProperties['class'] = $resa['data'];
}
break;
default:
break;
}
}
// update group policies when applicable
if ($this->syncMemberOf) {
$this->setGroupMembership(
$username,
$this->lastAuthProperties['class'] ?? '',
$this->syncMemberOfLimit,
$this->syncCreateLocalUsers
);
}
return true;
break;
case RADIUS_ACCESS_REJECT:

View File

@ -1,7 +1,7 @@
<?php
/*
* Copyright (C) 2014-2015 Deciso B.V.
* Copyright (C) 2014-2022 Deciso B.V.
* Copyright (C) 2010 Ermal Luçi
* Copyright (C) 2008 Shrew Soft Inc. <mgrooms@shrew.net>
* All rights reserved.
@ -37,7 +37,7 @@ $authCNFOptions = $authFactory->listConfigOptions();
config_read_array('system', 'authserver');
config_read_array('ca');
$a_server = array();
$a_server = [];
foreach (auth_get_authserver_list() as $servers) {
$a_server[] = $servers;
}
@ -51,8 +51,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
if (isset($_GET['act'])) {
$act = $_GET['act'];
}
$pconfig = array();
$pconfig['ldap_sync_memberof_groups'] = array();
$pconfig = [];
$pconfig['sync_memberof_groups'] = [];
if ($act == "new") {
$pconfig['ldap_protver'] = 3;
$pconfig['radius_srvcs'] = "both";
@ -89,17 +89,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$pconfig['ldap_bindpw'] = $a_server[$id]['ldap_bindpw'];
}
$pconfig['ldap_read_properties'] = !empty($a_server[$id]['ldap_read_properties']);
$pconfig['ldap_sync_memberof'] = !empty($a_server[$id]['ldap_sync_memberof']);
$pconfig['ldap_sync_create_local_users'] = !empty($a_server[$id]['ldap_sync_create_local_users']);
$pconfig['sync_memberof'] = !empty($a_server[$id]['ldap_sync_memberof']);
$pconfig['sync_create_local_users'] = !empty($a_server[$id]['ldap_sync_create_local_users']);
if (!empty($a_server[$id]['ldap_sync_memberof_groups'])) {
$pconfig['ldap_sync_memberof_groups'] = explode(",", $a_server[$id]['ldap_sync_memberof_groups']);
$pconfig['sync_memberof_groups'] = explode(",", $a_server[$id]['ldap_sync_memberof_groups']);
}
} elseif ($pconfig['type'] == "radius") {
$pconfig['radius_host'] = $a_server[$id]['host'];
$pconfig['radius_auth_port'] = $a_server[$id]['radius_auth_port'];
$pconfig['radius_acct_port'] = $a_server[$id]['radius_acct_port'];
$pconfig['radius_secret'] = $a_server[$id]['radius_secret'];
$pconfig['radius_timeout'] = $a_server[$id]['radius_timeout'];
$pconfig['radius_host'] = $a_server[$id]['host'] ?? '';
$pconfig['radius_auth_port'] = $a_server[$id]['radius_auth_port'] ?? '';
$pconfig['radius_acct_port'] = $a_server[$id]['radius_acct_port'] ?? '';
$pconfig['radius_secret'] = $a_server[$id]['radius_secret'] ?? '';
$pconfig['radius_timeout'] = $a_server[$id]['radius_timeout'] ?? '';
if (!empty($pconfig['radius_auth_port']) &&
!empty($pconfig['radius_acct_port'])) {
@ -111,9 +111,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
if (empty($pconfig['radius_auth_port'])) {
$pconfig['radius_auth_port'] = 1812;
}
$pconfig['sync_memberof'] = !empty($a_server[$id]['sync_memberof']);
$pconfig['sync_create_local_users'] = !empty($a_server[$id]['sync_create_local_users']);
if (!empty($a_server[$id]['sync_memberof_groups'])) {
$pconfig['sync_memberof_groups'] = explode(",", $a_server[$id]['sync_memberof_groups']);
}
} elseif ($pconfig['type'] == 'local') {
foreach (array('password_policy_duration', 'enable_password_policy_constraints',
'password_policy_complexity', 'password_policy_length') as $fieldname) {
foreach (['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 {
@ -128,7 +133,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
}
}
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input_errors = array();
$input_errors = [];
$pconfig = $_POST;
if (isset($pconfig['id']) && isset($a_server[$pconfig['id']])) {
$id = $pconfig['id'];
@ -211,7 +216,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
}
if (count($input_errors) == 0) {
$server = array();
$server = [];
$server['refid'] = uniqid();
if (isset($id)) {
$server = $a_server[$id];
@ -242,9 +247,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
}
}
$server['ldap_read_properties'] = !empty($pconfig['ldap_read_properties']);
$server['ldap_sync_memberof'] = !empty($pconfig['ldap_sync_memberof']);
$server['ldap_sync_memberof_groups'] = !empty($pconfig['ldap_sync_memberof_groups']) ? implode(",", $pconfig['ldap_sync_memberof_groups']) : array();
$server['ldap_sync_create_local_users'] = !empty($pconfig['ldap_sync_create_local_users']);
$server['ldap_sync_memberof'] = !empty($pconfig['sync_memberof']);
$server['ldap_sync_memberof_groups'] = !empty($pconfig['sync_memberof_groups']) ? implode(",", $pconfig['sync_memberof_groups']) : [];
$server['ldap_sync_create_local_users'] = !empty($pconfig['sync_create_local_users']);
} elseif ($server['type'] == "radius") {
$server['host'] = $pconfig['radius_host'];
@ -267,6 +272,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$server['radius_auth_port'] = $pconfig['radius_auth_port'];
unset($server['radius_acct_port']);
}
$server['sync_memberof'] = !empty($pconfig['sync_memberof']);
$server['sync_memberof_groups'] = !empty($pconfig['sync_memberof_groups']) ? implode(",", $pconfig['sync_memberof_groups']) : [];
$server['sync_create_local_users'] = !empty($pconfig['sync_create_local_users']);
} elseif ($server['type'] == 'local') {
foreach (array('password_policy_duration', 'enable_password_policy_constraints',
'password_policy_complexity', 'password_policy_length') as $fieldname) {
@ -315,8 +323,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$all_authfields = array(
'type','name','ldap_host','ldap_port','ldap_urltype','ldap_protver','ldap_scope',
'ldap_basedn','ldap_authcn','ldap_extended_query','ldap_binddn','ldap_bindpw','ldap_attr_user',
'ldap_read_properties', 'ldap_sync_memberof', 'ldap_sync_create_local_users', 'radius_host',
'radius_auth_port','radius_acct_port','radius_secret','radius_timeout','radius_srvcs'
'ldap_read_properties', 'sync_memberof', 'sync_create_local_users', 'radius_host',
'radius_auth_port','radius_acct_port','radius_secret','radius_timeout','radius_srvcs',
'password_policy_duration', 'enable_password_policy_constraints',
'password_policy_complexity', 'password_policy_length'
);
foreach ($all_authfields as $fieldname) {
@ -483,15 +493,15 @@ $( document ).ready(function() {
}, "json");
}
});
$("#ldap_read_properties").change(function(){
if ($(this).is(":checked")) {
$("#ldap_sync_memberof").prop('disabled', false);
$("#ldap_sync_memberof_groups").prop('disabled', false);
$("#ldap_sync_create_local_users").prop('disabled', false);
$("#ldap_read_properties, #type").change(function(){
if ($(this).is(":checked") || $("#type").val() == 'radius' ) {
$("#sync_memberof").prop('disabled', false);
$("#sync_memberof_groups").prop('disabled', false);
$("#sync_create_local_users").prop('disabled', false);
} else {
$("#ldap_sync_memberof").prop('disabled', true);
$("#ldap_sync_memberof_groups").prop('disabled', true);
$("#ldap_sync_create_local_users").prop('disabled', true);
$("#sync_memberof").prop('disabled', true);
$("#sync_memberof_groups").prop('disabled', true);
$("#sync_create_local_users").prop('disabled', true);
}
});
$("#ldap_read_properties").change();
@ -742,47 +752,6 @@ endif; ?>
</div>
</td>
</tr>
<tr class="auth_ldap auth_ldap-totp auth_options hidden">
<td><a id="help_for_ldap_sync_memberof" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext('Synchronize groups'); ?></td>
<td>
<input id="ldap_sync_memberof" name="ldap_sync_memberof" type="checkbox" <?= empty($pconfig['ldap_sync_memberof']) ? '' : 'checked="checked"';?> />
<div class="hidden" data-for="help_for_ldap_sync_memberof">
<?= gettext("Synchronize groups specified by memberOf attribute after login, this option requires to enable read properties. ".
"Groups will be extracted from the first CN= section and will only be considered when already existing in OPNsense. ".
"Group memberships will be persisted in OPNsense. ".
"Use the server test tool to check if memberOf is returned by your LDAP server before enabling.");?>
</div>
</td>
</tr>
<tr class="auth_ldap auth_ldap-totp auth_options hidden">
<td><a id="help_for_ldap_sync_memberof_groups" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext('Limit groups'); ?></td>
<td>
<select name='ldap_sync_memberof_groups[]' id="ldap_sync_memberof_groups" class="selectpicker" multiple="multiple">
<?php
foreach (config_read_array('system', 'group') as $group):
$selected = !empty($pconfig['ldap_sync_memberof_groups']) && in_array($group['name'], $pconfig['ldap_sync_memberof_groups']) ? 'selected="selected"' : ''; ?>
<option value="<?= $group['name'] ?>" <?= $selected ?>><?= $group['name'] ?></option>
<?php
endforeach; ?>
</select>
<div class="hidden" data-for="help_for_ldap_sync_memberof_groups">
<?= gettext("Limit the groups which may be used by ldap, keep empty to consider all local groups in OPNsense. ".
"When groups are selected, you can assign unassigned groups to the user manually ");?>
</div>
</td>
</tr>
<tr class="auth_ldap auth_ldap-totp auth_options hidden">
<td><a id="help_for_ldap_sync_create_local_users" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Automatic user creation"); ?></td>
<td>
<input id="ldap_sync_create_local_users" name="ldap_sync_create_local_users" type="checkbox" <?= empty($pconfig['ldap_sync_create_local_users']) ? '' : 'checked="checked"';?> />
<div class="hidden" data-for="help_for_ldap_sync_create_local_users">
<?= gettext(
"To be used in combination with synchronize groups, allow the authenticator to create new local users after ".
"successful login with group memberships returned for the user."
);?>
</div>
</td>
</tr>
<!-- RADIUS -->
<tr class="auth_radius auth_options hidden">
<td><i class="fa fa-info-circle text-muted"></i> <?=gettext("Hostname or IP address");?></td>
@ -832,6 +801,47 @@ endif; ?>
</div>
</td>
</tr>
<tr class="auth_ldap auth_radius auth_ldap-totp auth_options hidden">
<td><a id="help_for_sync_memberof" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext('Synchronize groups'); ?></td>
<td>
<input id="sync_memberof" name="sync_memberof" type="checkbox" <?= empty($pconfig['sync_memberof']) ? '' : 'checked="checked"';?> />
<div class="hidden" data-for="help_for_sync_memberof">
<?= gettext("Synchronize groups specified by memberOf or class attribute after login, this option requires to enable read properties. ".
"Groups will be extracted from the first CN= section and will only be considered when already existing in OPNsense. ".
"Group memberships will be persisted in OPNsense. ".
"Use the server test tool to check if memberOf is returned by your LDAP server before enabling.");?>
</div>
</td>
</tr>
<tr class="auth_ldap auth_radius auth_ldap-totp auth_options hidden">
<td><a id="help_for_sync_memberof_groups" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext('Limit groups'); ?></td>
<td>
<select name='sync_memberof_groups[]' id="sync_memberof_groups" class="selectpicker" multiple="multiple">
<?php
foreach (config_read_array('system', 'group') as $group):
$selected = !empty($pconfig['sync_memberof_groups']) && in_array($group['name'], $pconfig['sync_memberof_groups']) ? 'selected="selected"' : ''; ?>
<option value="<?= $group['name'] ?>" <?= $selected ?>><?= $group['name'] ?></option>
<?php
endforeach; ?>
</select>
<div class="hidden" data-for="help_for_sync_memberof_groups">
<?= gettext("Limit the groups which may be used by this authenticator, keep empty to consider all local groups in OPNsense. ".
"When groups are selected, you can assign unassigned groups to the user manually ");?>
</div>
</td>
</tr>
<tr class="auth_ldap auth_radius auth_ldap-totp auth_options hidden">
<td><a id="help_for_sync_create_local_users" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Automatic user creation"); ?></td>
<td>
<input id="sync_create_local_users" name="sync_create_local_users" type="checkbox" <?= empty($pconfig['sync_create_local_users']) ? '' : 'checked="checked"';?> />
<div class="hidden" data-for="help_for_sync_create_local_users">
<?= gettext(
"To be used in combination with synchronize groups, allow the authenticator to create new local users after ".
"successful login with group memberships returned for the user."
);?>
</div>
</td>
</tr>
<!-- pluggable options -->
<?php
foreach ($authCNFOptions as $typename => $authtype):
@ -853,13 +863,13 @@ endif; ?>
<td>
<?php
if ($field['type'] == 'text'):?>
<input name="<?=$fieldname;?>" type="text" value="<?=$pconfig[$fieldname];?>"/>
<input name="<?=$fieldname;?>" type="text" value="<?=$pconfig[$fieldname] ?? '';?>"/>
<?php
elseif ($field['type'] == 'dropdown'):?>
<select name="<?=$fieldname;?>" class="selectpicker" data-style="btn-default">
<?php
foreach ($field['options'] as $option => $optiontext):?>
<option value="<?=$option;?>" <?=(empty($pconfig[$fieldname]) && $field['default'] == $option) || $pconfig[$fieldname] == $option ? "selected=\"selected\"" : "";?> >
<option value="<?=$option;?>" <?=(empty($pconfig[$fieldname]) && $field['default'] == $option) || ($pconfig[$fieldname] ?? '') == $option ? "selected=\"selected\"" : "";?> >
<?=$optiontext;?>
</option>
<?php