From f2d25d35de08ae8c6adba644b587c504fa03fb90 Mon Sep 17 00:00:00 2001 From: Pascal Meunier Date: Fri, 1 May 2026 09:23:25 -0400 Subject: [PATCH] fix Escaping/data corruption; Slow/heavy unfriendly UI. Fix is proper htmlspecialchars escaping, a streaming XMLReader parser over the local federation metadata copy, a simple file-based cache, and a checkbox list UI --- .../shibboleth/assets/js/admin.js | 271 +++++------------- .../shibboleth/fields/institutions.php | 263 +++++++++++------ .../authentication/shibboleth/shibboleth.xml | 2 +- 3 files changed, 250 insertions(+), 286 deletions(-) diff --git a/core/plugins/authentication/shibboleth/assets/js/admin.js b/core/plugins/authentication/shibboleth/assets/js/admin.js index d3430c1c295..d360b3d1fff 100644 --- a/core/plugins/authentication/shibboleth/assets/js/admin.js +++ b/core/plugins/authentication/shibboleth/assets/js/admin.js @@ -22,219 +22,80 @@ jQuery(function($) { jQuery(function($) { $('#jform_params_institutions-lbl').hide(); - var prnt = $('.shibboleth'), - // control values are stored in a JSON string so they fit in the extensions table - serialized = $('.shibboleth input.serialized'), - // initialize from existing params - val = JSON.parse(serialized.val()), - // update hidden input to reflect form state - update = function() { - console.log('upd'); - serialized.val(JSON.stringify(val)); - console.log('update', serialized.val()); - }, - // update active idp list state - updateIdps = function() { - val.activeIdps = []; - var anyInvalid = false; - prnt.find('ul.active li').each(function(_, li) { - addedEntities = {} - var idp = {}, thisInvalid = false; - // copy form data to 'val' - $(li).find('input').each(function(_, inp) { - inp = $(inp); - var name = inp.attr('name'); - idp[name] = inp.val(); - thisInvalid = thisInvalid || (name == 'entity_id' && !idp[name].replace(/\s/g, '')) || (name == 'label' && !idp[name].replace(/\s/g, '')); - anyInvalid = anyInvalid || thisInvalid; - if (name == 'logo') { - //idp.logo_data = inp.data('logo_data'); - } - }); - if (!thisInvalid) { - val.activeIdps.push(idp); - } - }); - if (anyInvalid) { - idpWarning.show(); - } - else { - idpWarning.hide(); - } - // propagate to JSON representation - update(); - }, - idpWarning = $('

Not all ID providers will be saved! Each entry must have an entity ID and a label.

').hide() - ; - // link xml input to JSON encoding - $('.shibboleth input[name="xmlPath"]').change(function() { - val.xmlPath = $(this).val(); - update(); - }); - - // make idp attribute keys slightly more presentable - var keyToLabel = function(str) { - return str[0].toUpperCase() + str.substr(1).replace('_', ' ') + ': '; - }; + var serialized = $('.shibboleth input.serialized'); + if (!serialized.length) { console.log('[shib] no serialized input found'); return; } + console.log('[shib] init, serialized name=', serialized.attr('name')); + console.log('[shib] init, serialized initial value=', serialized.val()); + var val; + try { + val = JSON.parse(serialized.val()); + } catch (e) { + console.error('[shib] failed to parse serialized value:', e, serialized.val()); + val = { xmlPath: '', activeIdps: [] }; + } + console.log('[shib] init, parsed val=', val); - // try to show a preview of the given logo URL - var updateLogo = function(li) { - return; - var logoInp = li.find('input[name="logo"]'), - href = logoInp ? logoInp.val() : null; - if (!href || !href.replace(/s+/g, '')) { - return; - } - var imgData; - li.find('.preview').remove(); - if (href != logoInp.data('orig') || !(imgData = logoInp.data('logo_data'))) { - $.ajax({ - 'url': '/core/plugins/authentication/shibboleth/fields/institutions.php', - 'data': {'img': href}, - 'success': function(res) { - li.append($('').attr('src', res)); - logoInp.data('logo_data', res); - updateIdps(); - }, - 'error': updateIdps - }) - } - else { - li.append($('').attr('src', imgData)); - updateIdps(); - } - }; + // xmlPath field change + $('#shib-xmlpath').on('change', function() { + val.xmlPath = this.value; + serialized.val(JSON.stringify(val)); + console.log('[shib] xmlPath changed; serialized now=', serialized.val()); + }); - // make a new entry in the idp list - var newActiveIdp = function(idp, before) { - var li = $('
  • ') - .append($('')) - .append($('').click(function() { - li.remove(); - updateIdps(); - })) - [before === true ? 'prependTo' : 'appendTo'](existing); - if (before) { - li.animate('pulsate', 'slow'); - } - for (var k in idp) { - if (k === 'logo_data' || k === 'logoData') { - continue; - } - var control = mkInp(k, idp[k]); - if (k == 'logo') { -// control.input.change(function() { -// updateLogo(li); -// }); - } - else { - control.input.change(updateIdps); - } - li.append(control.label); - } -// if (idp.logo_data) { -// li.find('input[name="logo"]').data('logo_data', idp.logo_data); -// } - updateLogo(li); - }; + // Rebuild activeIdps from currently-checked boxes and sync to hidden input + function updateActiveIdps(reason) { + val.activeIdps = []; + $('.shib-idp-checkbox:checked').each(function() { + var entityId = $(this).val(); + var label = $(this).data('label'); + var m = entityId.match(/([^.\/:]+\.[^.\/:]+?)(?:\/|$)/); + val.activeIdps.push({ + entity_id: entityId, + label: label, + host: m ? m[1] : '' + }); + }); + serialized.val(JSON.stringify(val)); + console.log('[shib] updateActiveIdps (' + reason + '): ' + val.activeIdps.length + ' active; hidden input value length=' + serialized.val().length); + console.log('[shib] hidden input current value=', serialized.val()); + } - var normalizeUniversity = function(x) { - if (!x) { - return ''; + // Pre-check boxes for already-active IDPs + var activeIds = {}; + (val.activeIdps || []).forEach(function(idp) { + activeIds[idp.entity_id] = true; + }); + var preChecked = 0; + $('.shib-idp-checkbox').each(function() { + if (activeIds[$(this).val()]) { + $(this).prop('checked', true); + preChecked++; } - return x.toLowerCase().replace(/^((the|of|university|college)\W)+/, ''); - }; + }); + console.log('[shib] pre-checked ' + preChecked + ' of ' + Object.keys(activeIds).length + ' saved active IDPs'); - var addedEntities = {}; - if (val.activeIdps) { - val.activeIdps.forEach(function(idp) { - addedEntities[idp.entity_id] = 1; - }); - } - // list entities read from the shibboleth conf, if any - if (val.xmlRead) { - var ul = $('