diff --git a/classes/admin/setting_idpmetadata.php b/classes/admin/setting_idpmetadata.php
index cf49bd185..d0e62d4f9 100644
--- a/classes/admin/setting_idpmetadata.php
+++ b/classes/admin/setting_idpmetadata.php
@@ -193,6 +193,7 @@ private function remove_old_idps($oldidps) {
foreach ($oldidps as $metadataidps) {
foreach ($metadataidps as $oldidp) {
$DB->delete_records('auth_saml2_idps', ['id' => $oldidp->id]);
+ $DB->delete_records('auth_saml2_idpsettings', ['idpid' => $oldidp->id]);
}
}
}
diff --git a/classes/auth.php b/classes/auth.php
index a03b6c067..284d4c303 100644
--- a/classes/auth.php
+++ b/classes/auth.php
@@ -83,6 +83,12 @@ class auth extends \auth_plugin_base {
*/
public $metadatalist;
+ /**
+ * Idp list.
+ *
+ * @var array $idplist - array of idps.
+ */
+ public $idplist;
/**
* @var array $defaults The config defaults
@@ -156,6 +162,40 @@ public function __construct() {
// Check if we have mutiple IdPs configured.
// If we have mutliple metadata entries set multiidp to true.
$this->multiidp = (count($this->metadataentities) > 1);
+
+ // Get the list of IdPs and their mapping settings.
+ $this->idplist = $this->get_idp_list();
+ if (isset($_GET['idp']) && !empty($_GET['idp'])) {
+ $selectedidp = $_GET['idp'];
+ if (isset($this->idplist[$selectedidp]) && !empty($this->idplist[$selectedidp])) {
+ foreach ($this->idplist[$selectedidp] as $key => $value) {
+ $this->config->$key = $value;
+ }
+ }
+ }
+ }
+
+ /**
+ * Return array of all the IdPs and their configuration settings.
+ *
+ * @return array
+ **/
+ public function get_idp_list() {
+ $idps = auth_saml2_get_idps(true);
+ $idpconfig = [];
+
+ // Re-index the object to use shortname as the key.
+ foreach ($idps as $idp) {
+ foreach ($idp as $key => $value) {
+ $config = auth_saml2_get_idp_settings($value->id);
+ if ($config) {
+ $arrayconfig[$key] = $config;
+ } else {
+ $arrayconfig[$key] = [];
+ }
+ }
+ }
+ return $idpconfig;
}
/**
@@ -261,6 +301,8 @@ public function loginpage_idp_list($wantsurl) {
// The array of IdPs to return.
$idplist = [];
+ // Get the list of IdPs and their configuration settings.
+ $this->idplist = $this->get_idp_list();
// Create IdP metadata url => name mapping.
$idpurls = array_combine(array_column($this->metadatalist, 'idpurl'), array_column($this->metadatalist, 'idpname'));
@@ -605,21 +647,16 @@ public function saml_login() {
// We store the IdP in the session to generate the config/config.php array with the default local SP.
$idpalias = optional_param('idpalias', '', PARAM_TEXT);
if (!empty($idpalias)) {
- $idpfound = false;
-
- foreach ($this->metadataentities as $idpentity) {
- if ($idpalias == $idpentity->alias) {
- $SESSION->saml2idp = $idpentity->md5entityid;
- $idpfound = true;
- break;
- }
- }
-
+ $idpfound = $this->saml_validateidp('alias', $idpalias);
if (!$idpfound) {
$this->error_page(get_string('noidpfound', 'auth_saml2', $idpalias));
}
} else if (isset($_GET['idp'])) {
$SESSION->saml2idp = $_GET['idp'];
+ $idpfound = $this->saml_validateidp('md5entityid', $_GET['idp']);
+ if (!$idpfound) {
+ $this->error_page(get_string('noidpfound', 'auth_saml2', $_GET['idp']));
+ }
} else if (!is_null($this->defaultidp)) {
$SESSION->saml2idp = $this->defaultidp->md5entityid;
} else if ($this->multiidp) {
@@ -656,6 +693,26 @@ public function saml_login() {
$this->saml_login_complete($attributes);
}
+ /**
+ * Check if valid IdP and is Active.
+ *
+ * @param string $idptype alias or md5entityid
+ * @param string $idp IdP Alias or IdP entity
+ * @return bool
+ */
+ public function saml_validateidp(string $idptype, string $idp) {
+ global $SESSION;
+ $idpfound = false;
+
+ foreach ($this->metadataentities as $idpentity) {
+ if ($idp == $idpentity->{$idptype}) {
+ $SESSION->saml2idp = $idpentity->md5entityid;
+ $idpfound = true;
+ break;
+ }
+ }
+ return $idpfound;
+ }
/**
* The user has done the SAML handshake now we can log them in
@@ -995,7 +1052,7 @@ public function simplify_attr($attributes) {
public function update_user_record_from_attribute_map(&$user, $attributes, $newuser= false) {
global $CFG;
- $mapconfig = get_config('auth_saml2');
+ $mapconfig = $this->config;
$allkeys = array_keys(get_object_vars($mapconfig));
$update = false;
diff --git a/classes/form/availableidps.php b/classes/form/availableidps.php
index 0639da3b9..f27cdf269 100644
--- a/classes/form/availableidps.php
+++ b/classes/form/availableidps.php
@@ -74,6 +74,11 @@ public function definition() {
$mform->addElement('text', $fieldkey.'[alias]', get_string('multiidp:label:alias', 'auth_saml2'));
$mform->setType($fieldkey.'[alias]', PARAM_TEXT);
+ // Update IdP configuration settings.
+ $editmappings = new \moodle_url('edit.php', ['id' => $idpentity['id']]);
+ $mform->addElement('static', $fieldkey.'[mapping]',
+ get_string('mappings', 'auth_saml2'), get_string('edit', 'auth_saml2', $editmappings));
+
// Add the activeidp checkbox.
$mform->addElement('advcheckbox', $fieldkey.'[activeidp]',
get_string('status', 'auth_saml2'), get_string('multiidp:label:active', 'auth_saml2'), [], [false, true]);
diff --git a/db/install.xml b/db/install.xml
index 48b89fdde..ac479384c 100644
--- a/db/install.xml
+++ b/db/install.xml
@@ -37,5 +37,17 @@
+urn:mace:dir:attribute-def:eduPersonPrincipalName +urn:mace:dir:attribute-def:mail *"])); + $mform->setDefault('requestedattributes', ''); + + $mform->addElement('selectyesno', 'autocreate', get_string('autocreate', 'auth_saml2')); + $mform->addElement('static', 'autocreate_help', + null, + get_string('autocreate_help', 'auth_saml2')); + + $mform->addElement('textarea', 'grouprules', get_string('grouprules', 'auth_saml2'), + 'wrap="virtual" rows="8" cols="50"'); + $mform->setType('grouprules', PARAM_TEXT); + $mform->addElement('static', 'grouprules_help', null, + get_string('grouprules_help', 'auth_saml2')); + $mform->setDefault('grouprules', ''); + + $mform->addElement('text', 'alterlogout', get_string('alterlogout', 'auth_saml2'), ['size' => 40, 'maxlength' => 50]); + $mform->setType('alterlogout', PARAM_URL); + $mform->addElement('static', 'alterlogout_help', + null, + get_string('alterlogout_help', 'auth_saml2')); + + $mform->addElement('selectyesno', 'attemptsignout', get_string('attemptsignout', 'auth_saml2')); + $mform->addElement('static', 'attemptsignout_help', + null, + get_string('attemptsignout_help', 'auth_saml2')); + + $authplugin = get_auth_plugin('saml2'); + $mform->addElement('static', 'sspversion', + get_string('sspversion', 'auth_saml2'), + $authplugin->get_ssp_version()); + + $mform->addElement('html', html_writer::tag('h3', get_string('blockredirectheading', 'auth_saml2'))); + + // Flagged login response options. + $flaggedloginresponseoptions = [ + saml2_settings::OPTION_FLAGGED_LOGIN_MESSAGE => get_string('flaggedresponsetypemessage', 'auth_saml2'), + saml2_settings::OPTION_FLAGGED_LOGIN_REDIRECT => get_string('flaggedresponsetyperedirect', 'auth_saml2'), + ]; + + $mform->addElement('select', 'flagresponsetype', get_string('flagresponsetype', 'auth_saml2'), + $flaggedloginresponseoptions); + $mform->getElement('flagresponsetype')->setSelected(saml2_settings::OPTION_FLAGGED_LOGIN_REDIRECT); + $mform->addElement('static', 'flagresponsetype_help', null, + get_string('flagresponsetype_help', 'auth_saml2')); + + $mform->addElement('text', 'flagredirecturl', get_string('flagredirecturl', 'auth_saml2'), + ['size' => 40, 'maxlength' => 50]); + $mform->setType('flagredirecturl', PARAM_URL); + $mform->addElement('static', 'flagredirecturl_help', null, + get_string('flagredirecturl_help', 'auth_saml2')); + + $mform->addElement('textarea', 'flagmessage', get_string('flagmessage', 'auth_saml2'), + 'wrap="virtual" rows="3" cols="50"'); + $mform->setType('flagmessage', PARAM_TEXT); + $mform->addElement('static', 'flagmessage_help', null, + get_string('flagmessage_help', 'auth_saml2')); + $mform->setDefault('flagmessage', ''); + + // Display locking / mapping of profile fields. + $help = get_string('auth_updatelocal_expl', 'auth'); + $help .= get_string('auth_fieldlock_expl', 'auth'); + $help .= get_string('auth_updateremote_expl', 'auth'); + auth_saml2_display_auth_lock_options( + $mform, + $authplugin->authtype, + $authplugin->userfields, + $help, + true, + true, + $authplugin->get_custom_user_profile_fields(), + 'form' + ); + + if ($id !== false) { + $mform->addElement('hidden', 'id', $id); + $mform->setType('id', PARAM_INT); + } + + $this->add_action_buttons(); + + } + + /** + * Called from form method definition_after_data + * Can be overridden if more functionality is needed. + */ + public function definition_after_data() { + $mform =& $this->_form; + foreach ($this->_customdata['data'] as $key => $value) { + $mform->setDefault($key, $value); + } + } +} diff --git a/lang/en/auth_saml2.php b/lang/en/auth_saml2.php index f0974abc5..2a5ec986c 100644 --- a/lang/en/auth_saml2.php +++ b/lang/en/auth_saml2.php @@ -243,6 +243,10 @@ $string['wantassertionssigned'] = 'Want assertions signed'; $string['wantassertionssigned_help'] = 'Whether assertions received by this SP must be signed'; $string['wrongauth'] = 'You have logged in successfully as \'{$a}\' but are not authorized to access Moodle.'; +$string['edit'] = 'Edit'; +$string['editidp'] = 'Edit IdP'; +$string['mappings'] = 'Mappings'; +$string['metadatalink'] = 'Metadata'; /* * Privacy provider (GDPR) */ diff --git a/locallib.php b/locallib.php index fdcb36ace..6e231a231 100644 --- a/locallib.php +++ b/locallib.php @@ -285,16 +285,23 @@ function auth_saml2_update_sp_metadata() { * @param boolean $mapremotefields Map fields or lock only. * @param boolean $updateremotefields Allow remote updates * @param array $customfields list of custom profile fields + * @param string $type show config setting or form fields * @since Moodle 3.3 */ -function auth_saml2_display_auth_lock_options($settings, $auth, $userfields, $helptext, $mapremotefields, $updateremotefields, $customfields = array()) { +function auth_saml2_display_auth_lock_options($settings, $auth, $userfields, $helptext, $mapremotefields, + $updateremotefields, $customfields = array(), $type = 'settings') { global $DB; // Introductory explanation and help text. - if ($mapremotefields) { - $settings->add(new admin_setting_heading($auth.'/data_mapping', new lang_string('auth_data_mapping', 'auth'), $helptext)); + if ($type == 'settings') { + if ($mapremotefields) { + $settings->add(new admin_setting_heading($auth.'/data_mapping', new lang_string('auth_data_mapping', 'auth'), $helptext)); + } else { + $settings->add(new admin_setting_heading($auth.'/auth_fieldlocks', new lang_string('auth_fieldlocks', 'auth'), $helptext)); + } } else { - $settings->add(new admin_setting_heading($auth.'/auth_fieldlocks', new lang_string('auth_fieldlocks', 'auth'), $helptext)); + $settings->addElement('html', html_writer::tag('h3', get_string('auth_data_mapping', 'auth'))); + $settings->addElement('html', $helptext); } // Generate the list of options. @@ -322,7 +329,7 @@ function auth_saml2_display_auth_lock_options($settings, $auth, $userfields, $he } else if (!empty($customfields) && in_array($field, $customfields)) { // If custom field then pick name from database. $fieldshortname = str_replace('profile_field_', '', $fieldname); - $fieldname = $customfieldname[$fieldshortname]->name; + $fieldname = format_string($customfieldname[$fieldshortname]->name); if (core_text::strlen($fieldshortname) > 67) { // If custom profile field name is longer than 67 characters we will not be able to store the setting // such as 'field_updateremote_profile_field_NOTSOSHORTSHORTNAME' in the database because the character @@ -342,28 +349,56 @@ function auth_saml2_display_auth_lock_options($settings, $auth, $userfields, $he // Display a message that the field can not be mapped because it's too long. $url = new moodle_url('/user/profile/index.php'); $a = (object)['fieldname' => s($fieldname), 'shortname' => s($field), 'charlimit' => 67, 'link' => $url->out()]; - $settings->add(new admin_setting_heading($auth.'/field_not_mapped_'.sha1($field), '', - get_string('cannotmapfield', 'auth_saml2', $a))); - } else if ($mapremotefields) { - // We are mapping to a remote field here. - // Mapping. - $settings->add(new admin_setting_configtext("auth_{$auth}/field_map_{$field}", - get_string('auth_fieldmapping', 'auth_saml2', $fieldname), '', '', PARAM_RAW, 30)); - - // Update local. - $settings->add(new admin_setting_configselect("auth_{$auth}/field_updatelocal_{$field}", - get_string('auth_updatelocalfield', 'auth_saml2', $fieldname), '', 'oncreate', $updatelocaloptions)); - - // Update remote. - if ($updateremotefields) { - $settings->add(new admin_setting_configselect("auth_{$auth}/field_updateremote_{$field}", - get_string('auth_updateremotefield', 'auth_saml2', $fieldname), '', 0, $updateextoptions)); + if ($type == 'settings') { + $settings->add(new admin_setting_heading($auth.'/field_not_mapped_'.sha1($field), '', + get_string('cannotmapfield', 'auth_saml2', $a))); + } else { + $mform->addElement('html', html_writer::tag('h3', get_string('cannotmapfield', 'auth_saml2', $a))); } + } else if ($mapremotefields) { + if ($type == 'settings') { + // We are mapping to a remote field here. + // Mapping. + $settings->add(new admin_setting_configtext("auth_{$auth}/field_map_{$field}", + get_string('auth_fieldmapping', 'auth_saml2', $fieldname), '', '', PARAM_RAW, 30)); + + // Update local. + $settings->add(new admin_setting_configselect("auth_{$auth}/field_updatelocal_{$field}", + get_string('auth_updatelocalfield', 'auth_saml2', $fieldname), '', 'oncreate', $updatelocaloptions)); + + // Update remote. + if ($updateremotefields) { + $settings->add(new admin_setting_configselect("auth_{$auth}/field_updateremote_{$field}", + get_string('auth_updateremotefield', 'auth_saml2', $fieldname), '', 0, $updateextoptions)); + } + + // Lock fields. + $settings->add(new admin_setting_configselect("auth_{$auth}/field_lock_{$field}", + get_string('auth_fieldlockfield', 'auth_saml2', $fieldname), '', 'unlocked', $lockoptions)); + } else { + // We are mapping to a remote field here. + // Mapping. + $settings->addElement('text', "field_map_{$field}", get_string('auth_fieldmapping', 'auth', $fieldname), + array('size' => 40, 'maxlength' => 30)); + $settings->setType("field_map_{$field}", PARAM_RAW); + + // Update local. + $settings->addElement('select', "field_updatelocal_{$field}", + get_string('auth_updatelocalfield', 'auth', $fieldname), $updatelocaloptions); + $settings->setDefault("field_updatelocal_{$field}", 'oncreate'); + + // Update remote. + if ($updateremotefields) { + $settings->addElement('select', "field_updateremote_{$field}", + get_string('auth_updateremotefield', 'auth', $fieldname), $updateextoptions); + } + + // Lock fields. + $settings->addElement('select', "field_lock_{$field}", get_string('auth_fieldlockfield', 'auth', $fieldname), + $lockoptions); + $settings->setDefault("field_lock_{$field}", 'unlocked'); - // Lock fields. - $settings->add(new admin_setting_configselect("auth_{$auth}/field_lock_{$field}", - get_string('auth_fieldlockfield', 'auth_saml2', $fieldname), '', 'unlocked', $lockoptions)); - + } } else { // Lock fields Only. $settings->add(new admin_setting_configselect("auth_{$auth}/field_lock_{$field}", @@ -479,6 +514,57 @@ function auth_saml2_process_regenerate_form($fromform) { return $error; } } + +/** + * Get IdP configuration settings for the selected IdP + * @param int $id IdP ID. + * @return array + */ +function auth_saml2_get_idp_settings($id) { + global $DB; + + $fields = $DB->get_records_menu('auth_saml2_idpsettings', ['idpid' => $id], null, 'k, value'); + return $fields; +} + +/** + * Save IdP settings for the selected IdP + * @param object $formdata form object + * @param int $id IdP ID. + * @return array an associative array + */ +function auth_saml2_save_idp($formdata, $id) { + global $DB; + + foreach ($formdata as $field => $value) { + // We don't want some values in the idpsettings table. + if ($field !== 'id' && $field !== 'addnew' && $field !== 'submitbutton') { + $data = new stdClass(); + $data->idpid = $id; + $data->k = $field; + $data->value = $value; + + if ($fieldid = $DB->get_field('auth_saml2_idpsettings', 'id', ['idpid' => $id, 'k' => $field])) { + $data->id = $fieldid; + $DB->update_record('auth_saml2_idpsettings', $data); + } else { + $DB->insert_record('auth_saml2_idpsettings', $data); + } + } + } + + // Check if we have multiple IdPs, and force Dual Login if we do. + if (count(auth_saml2_get_idps()) > 1) { + $duallogin = new stdClass(); + $fieldid = $DB->get_field('auth_saml2_idpsettings', 'id', ['idpid' => 0, 'k' => 'duallogin']); + $duallogin->id = $fieldid; + $duallogin->idpid = 0; + $duallogin->k = 'duallogin'; + $duallogin->value = 1; + $DB->update_record('auth_saml2_idpsettings', $duallogin); + } +} + // @codingStandardsIgnoreEnd /** diff --git a/version.php b/version.php index e3cc27694..e329d5b7a 100644 --- a/version.php +++ b/version.php @@ -24,8 +24,8 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2025040400; // The current plugin version (Date: YYYYMMDDXX). -$plugin->release = 2025040400; // Match release exactly to version. +$plugin->version = 2025052200; // The current plugin version (Date: YYYYMMDDXX). +$plugin->release = 2025052200; // Match release exactly to version. $plugin->requires = 2025040400; // Requires Moodle 5.0 $plugin->component = 'auth_saml2'; // Full name of the plugin (used for diagnostics). $plugin->maturity = MATURITY_STABLE;