Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion classes/form/selectidp_buttons.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,12 @@ public function definition() {
* @return string
*/
private function get_idpbutton($idpentityid, $idpname, $logourl, $rememberedidp = false) {
$logo = !is_null($logourl) ? "<img src=\"{$logourl}\"> " : "";
$logo = '';
$logourl = clean_param($logourl, PARAM_URL);
if (!is_null($logourl)) {
$logo = "<img src=\"{$logourl}\"> ";
}
$idpname = clean_param($idpname, PARAM_TEXT);
$extraclasses = $rememberedidp ? "rememberedidp" : "";
return <<<EOD
<div class="fitem fitem_actionbuttons fitem_fsubmit ">
Expand Down
21 changes: 20 additions & 1 deletion idp/sso.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,14 @@

// Get data from input request.
$id = $xpath->evaluate('normalize-space(/*/@ID)');
$destination = htmlspecialchars($xpath->evaluate('normalize-space(/*/@AssertionConsumerServiceURL)'));
$destinationraw = $xpath->evaluate('normalize-space(/*/@AssertionConsumerServiceURL)');
$sp = $xpath->evaluate('normalize-space(/*/*[local-name() = "Issuer"])');

// Validate ID.
if (!auth_saml2_is_valid_saml_id($id)) {
throw new saml2_exception('invalid_id_error', get_string('invalid_id_error', 'auth_saml2', $id));
}

// Confirm we know about this SP.
$knownsps = [];
foreach (explode(PHP_EOL, $saml2auth->config->moodleidpsplist) as $ksp) {
Expand All @@ -70,6 +75,20 @@
throw new saml2_exception('unknown_sp_error', get_string('moodleidpsplist_error', 'auth_saml2', $sp));
}

// Validate ACS.
$destinationhost = parse_url($destinationraw, PHP_URL_HOST);
$sphost = parse_url($sp, PHP_URL_HOST);

if (empty($destinationhost)) {
throw new saml2_exception('unknown_sp_error', get_string('moodleidpsplist_error', 'auth_saml2', $sp));
}

if ($saml2auth->config->acsmatchissuer && strcasecmp($destinationhost, $sphost) !== 0) {
throw new saml2_exception('acs_mismatch_error', get_string('acs_mismatch_error', 'auth_saml2'));
}

$destination = htmlspecialchars($destinationraw);

// Get time in UTC.
$datetime = new DateTime();
$datetime->setTimezone(new DatetimeZone('UTC'));
Expand Down
4 changes: 4 additions & 0 deletions lang/en/auth_saml2.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

$string['acs_mismatch_error'] = 'ACS URL does not match issuer';
$string['acsmatchissuer'] = 'Match ACS with issuer';
$string['acsmatchissuer_help'] = 'If enabled, the Assertion Consumer Service (ACS) URL must match the issuer host. If you know that they do/should, enable this option to improve login security.';
$string['allowcreate'] = 'Allow create';
$string['allowcreate_help'] = 'Allow creation of IdP users on demand';
$string['alterlogout'] = 'Alternative Logout URL';
Expand Down Expand Up @@ -129,6 +132,7 @@
$string['idpname_help'] = 'eg myUNI - this is detected from the metadata and will show on the dual login page (if enabled)';
$string['idpnamedefault'] = 'Login via SAML2';
$string['idpnamedefault_varaible'] = 'Login via SAML2 ({$a})';
$string['invalid_id_error'] = 'Invalid SAML ID';
$string['localityname'] = 'Locality';
$string['locked'] = 'Locked';
$string['logdir'] = 'Log Directory';
Expand Down
11 changes: 11 additions & 0 deletions locallib.php
Original file line number Diff line number Diff line change
Expand Up @@ -519,3 +519,14 @@ function auth_saml2_admin_nav($title, $url) {
$PAGE->set_heading(get_string('pluginname', 'auth_saml2') . ': ' . $title);
$PAGE->set_title(get_string('pluginname', 'auth_saml2') . ': ' . $title);
}

/**
* Validate a SAML protocol ID (xs:ID / NCName syntax) to prevent XML injection.
*
* @param string $id
* @return bool
*/
function auth_saml2_is_valid_saml_id(string $id): bool {
// NCName: starts with a letter or underscore, followed by letters, digits, '-', '_' or '.'.
return (bool) preg_match('/^[A-Za-z_][A-Za-z0-9_.-]*$/', $id) && strlen($id) <= 256;
}
8 changes: 8 additions & 0 deletions settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,14 @@
$acssetting->set_updatedcallback('auth_saml2_update_sp_metadata');
$settings->add($acssetting);

$settings->add(new admin_setting_configselect(
'auth_saml2/acsmatchissuer',
get_string('acsmatchissuer', 'auth_saml2'),
get_string('acsmatchissuer_help', 'auth_saml2'),
0,
$yesno,
));

$settings->add(new admin_setting_configselect(
'auth_saml2/allowcreate',
get_string('allowcreate', 'auth_saml2'),
Expand Down
4 changes: 2 additions & 2 deletions version.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@

defined('MOODLE_INTERNAL') || die();

$plugin->version = 2026040202; // The current plugin version (Date: YYYYMMDDXX).
$plugin->release = 2026040202; // Match release exactly to version.
$plugin->version = 2026040203; // The current plugin version (Date: YYYYMMDDXX).
$plugin->release = 2026040203; // 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;
Expand Down
Loading