Skip to content
Snippets Groups Projects
Commit fa1bd39b authored by Hahn Axel (hahn)'s avatar Hahn Axel (hahn)
Browse files

tld selection; filter uses now AND condition

parent a39b5cfa
No related branches found
No related tags found
No related merge requests found
......@@ -16,8 +16,17 @@ We used the WAYF script in the login page so far.
When editing login pages in Ilias 9 then javascript is now filtered out while saving. That's why we couldn't embed our current WAYF script anymore.
This is a standalone login page that offers a list of organisations and can be customized.
This reads the discofeed. By a given positive list of Idps it shows a list of identity providers to login on the visitors organisation.
![Login screen with mode "boxes"](docs/images/login_mode_boxes.png)
## Features
* Displays a list of allowed Identity providers
* The list is filtered during typing in the search field. When entering multiple words all keywords must match.
* The filter is stored in localstorage of the webbrowser. On reload or the next week the last filter will be activated.
* A set of filter buttons by TLD will be generated. It is shown when minimum 2 TLDs were found.
## Requirements
* PHP 8.2
......
<?php
/**
* ======================================================================
*
* AAI LOGIN WITH SHIBBOLETH HANDLING MULTIPLE ORGANIZATIONS
*
* included functions
* License: GNU GPL 3.0
* Source: https://git-repo.iml.unibe.ch/iml-open-source/login-aai
* ======================================================================
*/
class shibd_discofeed
{
protected string $url_list = '/Shibboleth.sso/DiscoFeed';
protected string $url_login = '/Shibboleth.sso/Login';
/**
* Url to the discofeed that returns a json with al idps
* @var string
*/
protected string $_sDiscofeedUrl = '/Shibboleth.sso/DiscoFeed';
/**
* Url to generate a static Shibboleth login url
* @var string
*/
protected string $_sShibLoginUrl = '/Shibboleth.sso/Login';
/**
* Language to search for in the discofeed; a 2 letter code
* @var string
*/
protected string $lang = 'en';
// caching of discofeed
/**
* Filename of the cache file for the Shibboleth discofeed
* @var string
*/
protected string $_sCachefile = 'discofeed.json';
/**
* Caching time for the discofeed cache in seconds
* @var int
*/
protected int $_iCacheTtl;
protected array $aConfig = [];
/**
* Self base URL of the current app tu build Shibboleth links
* @var string
*/
protected string $SELFURL = '';
// protected array $aConfig = [];
// ----------------------------------------------------------------------
......@@ -33,9 +70,10 @@ class shibd_discofeed
if(!$SELFURL) {
die("ERROR: SELFURL is not set. \$_SERVER['SERVER_NAME'] is not available.");
}
$this->SELFURL = $SELFURL;
$this->url_list = "$SELFURL$this->url_list";
$this->url_login = "$SELFURL$this->url_login";
$this->_sDiscofeedUrl = "$SELFURL$this->_sDiscofeedUrl";
$this->_sShibLoginUrl = "$SELFURL$this->_sShibLoginUrl";
$this->_sCachefile = dirname(__DIR__).'/'
.($aConfig['cachefile']??'discofeed.json')
......@@ -56,7 +94,7 @@ class shibd_discofeed
{
if (!file_exists($this->_sCachefile) || filemtime($this->_sCachefile) < time() - $this->_iCacheTtl) {
// echo "DEBUG: IDP - reading from Shibboleth<br>";
$aReturn = json_decode(file_get_contents($this->url_list), 1);
$aReturn = json_decode(file_get_contents($this->_sDiscofeedUrl), 1);
if ($aReturn && is_array($aReturn)) {
// echo "DEBUG: IDP - storing cache<br>";
......@@ -100,13 +138,15 @@ class shibd_discofeed
$sImage = $aEntry['Logos'][1]['value'] ?? ($aEntry['Logos'][0]['value'] ?? '');
$sUrl = $this->url_login
// see also https://help.switch.ch/aai/guides/discovery/login-link-composer/
$sUrl = $this->_sShibLoginUrl
. '?entityID='. urlencode($sEntityId)
. "&target=" . urlencode($sEntityId)
. "&target=" . urlencode($this->SELFURL.($aConfig['return-url']??''))
;
$aReturn[] = array_merge([
$sKey=$sLabel;
$aReturn[$sKey] = array_merge([
'_label' => $sLabel,
'_description' => $sDescription,
'_keywords' => $sKeywords,
......@@ -120,6 +160,7 @@ class shibd_discofeed
}
}
}
return $aReturn;
ksort($aReturn);
return array_values($aReturn);
}
}
\ No newline at end of file
docs/images/login_mode_boxes.png

66 KiB | W: | H:

docs/images/login_mode_boxes.png

96.1 KiB | W: | H:

docs/images/login_mode_boxes.png
docs/images/login_mode_boxes.png
docs/images/login_mode_boxes.png
docs/images/login_mode_boxes.png
  • 2-up
  • Swipe
  • Onion skin
......@@ -28,28 +28,57 @@ function applyfilter(){
if (oFilter) {
var q = document.getElementById('filter').value;
var aQ = q.split(" ");
// reduce boxes based on filter using AND condition
for (var i = 0; i < document.getElementsByClassName('idp').length; i++) {
var idp = document.getElementsByClassName('idp')[i];
var bShow=(q==""
? true
// strip innerHTML and compare lowercase of it with lowercase of query
: idp.innerHTML.replace(/<[^>]*>/g, "").toLowerCase().indexOf(q.toLowerCase())>=0
);
var bShow = true;
if (q != "") {
var sText = idp.innerHTML.replace(/<[^>]*>/g, "");
for (var iPart = 0; iPart < aQ.length; iPart++) {
var qPart = aQ[iPart];
bShow = bShow & sText.toLowerCase().indexOf(qPart.toLowerCase()) >= 0;
}
}
idp.className = bShow ? 'idp' : 'idp hide';
document.getElementById('resetfilter').style.display = (q > "") ? 'inline' : 'none';
}
var aBtns = document.getElementsByClassName('filterbutton');
for (var i = 0; i < aBtns.length; i++) {
var bMarked = false;
for (var iPart = 0; iPart < aQ.length; iPart++) {
var qPart = aQ[iPart];
var id2search = 'filterbtn-dot-' + qPart.replace(/^./, '');
var qPart = aQ[iPart];
if(aBtns[i].id == id2search){
bMarked = true;
break;
}
}
aBtns[i].className = bMarked ? 'filterbutton active' : 'filterbutton';
}
document.getElementById('filter').focus();
localStorage.setItem(sLsvar, q);
};
}
/**
* Set a new filter value or reset it
* @param {string} sNewFiltervalue New value to write into the filter field
*/
function setFilter(sNewFiltervalue) {
document.getElementById('filter').value = sNewFiltervalue;
applyfilter();
}
/**
* Reset the filter and show all Items
*/
function resetFilter() {
setFilter("");
}
......@@ -65,29 +94,14 @@ function showFilterBox(){
q = '';
}
var btnList = '';
var aTlds = [];
for(var i=0; i<document.getElementsByClassName('idp').length; i++){
var idp = document.getElementsByClassName('idp')[i];
var sText=idp.innerHTML.replace(/<[^>]*>/g, "").split('.').pop().trim().toUpperCase();
if (sText){
aTlds[sText]=1;
}
}
// if(aTlds.length){
for(var tld in aTlds){
btnList+=' <a href="#" class="filterbutton" onclick="setFilter(\' .'+tld+'\'); return false;">.'+tld+'</a> ';
}
// }
oFilter.style.display = 'block';
oFilter.innerHTML='<input type="text" id="filter" placeholder="" onchange="applyfilter()" onkeydown="applyfilter()" onkeyup="applyfilter()" value="'+q+'"/>'
oFilter.innerHTML = '<input type="text" id="filter" placeholder="" onchange="applyfilter()" onkeydown="applyfilter()" onkeyup="applyfilter()" value=""/>'
+ '<a id="resetfilter" onclick="resetFilter(); return false;"> X </a><br>'
+ (btnList ? '<br>'+btnList : '' )
;
applyfilter();
setFilter(q);
for (var i = 0; i < document.getElementsByClassName('idp').length; i++) {
var idp = document.getElementsByClassName('idp')[i];
idp.setAttribute("onclick", "localStorage.setItem(sLsvar,this.innerText);");
......
......@@ -16,7 +16,6 @@
require 'classes/shibd_discofeed.class.php';
// get the user config
if (!file_exists('config.php')) {
die("ERROR: file config.php does not exist yet.");
......@@ -35,6 +34,7 @@ $aIdplist = $oDiscofeed->getIdps();
/**
* Show a info or error message
*
* @param string $sLevel level: one of "info", "error"
* @param string $sMessage Message to show
* @return void
......@@ -47,6 +47,8 @@ function showMessage(string $sLevel, string $sMessage)
/**
* Get a list of static links for browsers without javascript
* see also https://help.switch.ch/aai/guides/discovery/login-link-composer/
*
* @param array $aIdplist
* @return string
*/
......@@ -59,3 +61,4 @@ function getStaticlinks($aIdplist){
}
// ----------------------------------------------------------------------
......@@ -11,22 +11,41 @@
if (is_array($aIdplist) && count($aIdplist)) {
$sOut = '';
$sOut .= '<div id="filterbox"></div><div class="boxes">';
$sDomainFilter = '';
$aTld = [];
foreach ($aIdplist as $aEntry) {
$aTld[$aEntry['_tld']] = 1;
$sOut .= '
<a href="' . $aEntry['_url']. '"
class="idp"
title="' . str_replace('"', '',$aEntry['_description']) . '"
>' . htmlentities($aEntry['_label']) . '<br>
<span class="hidden">' . str_replace('"', '', $aEntry['_description'] . ' ' . $aEntry['_keywords']) . ' .'. $aEntry['_tld'] . '</span>
title="' . strip_tags($aEntry['_description']) . '"
>
<span>' . htmlentities($aEntry['_label']) . '</span><br>
<span class="hidden"> .'. $aEntry['_tld'] . '</span>
<img src="' . $aEntry['_image'] . '"><br>
</a>
'
. "\n"
;
}
$sOut.='</div><div style="clear:both"></div>';
echo $sOut;
// Generate a list of TLDs to filter by it.
if(count($aTld)>1) {
ksort($aTld);
foreach(array_keys($aTld) as $sTld) {
$sId='filterbtn-dot-'.str_replace('.', '', $sTld);
$sDomainFilter.='<a href="#" id="'.$sId.'" class="filterbutton" onclick="setFilter(\'.'.$sTld.' \'); return false;">.'.$sTld.'</a> ';
}
$sDomainFilter = '<div id="filterByDomain">' . $sDomainFilter . '</div>';
}
echo '<div id="filterbox"></div>'
. $sDomainFilter
.'<div class="boxes">
'.$sOut.'
</div><div style="clear:both"></div>';
} else {
echo '<div class="msg error">No IDPs found in Discovery Feed.</div>';
}
......@@ -59,7 +59,7 @@
</div>
<script type="text/javascript" defer src="functions.js"></script>
<footer><?php echo $aConfig['title'] ?? 'AAI Login'; ?><br>AAI Login v0.2 - <a href="https://git-repo.iml.unibe.ch/iml-open-source/login-aai">Source</a></a></footer>
<footer><strong><?php echo $aConfig['title'] ?? 'AAI Login'; ?></strong><br>AAI Login v0.2 - <a href="https://git-repo.iml.unibe.ch/iml-open-source/login-aai">Source</a></a></footer>
</body>
</html>
\ No newline at end of file
......@@ -28,8 +28,8 @@
--inputfilter-border: 2px solid #eee;
--inputfilter-color:#789;
--btn-border: 2px solid #ddd;
--btn-bg:#eee;
--btn-border: 2px solid #eee;
--btn-bg:#f8f8f8;
--btn-color: #888;
--resetfilter-border: 2px solid #dcc;
......@@ -53,9 +53,11 @@ body {
h1{color:var(--h1-color);}
h2{color: var(--h2-color);}
div#head, div.content, footer {
width: 70%;
}
div#head {
margin: 1em auto 1em;
width: 60%;
}
div.content {
background: var(--content-bg);
......@@ -64,7 +66,6 @@ div.content {
box-shadow: var(--content-shadow);
margin: 2em auto 2em;
padding: 2em;
width: 60%;
}
div.msg{
border-radius: 1em;
......@@ -83,7 +84,6 @@ div.info{
}
footer{
width: 60%;
color: var(--footer-color);
margin: 10em auto 5em;
text-align: right;
......@@ -94,7 +94,12 @@ footer{
div#filterbox{
margin-bottom: 1em;
}
div#filterbox a.filterbutton, a#resetfilter{
#filterByDomain{
margin-top: 3em;
margin-bottom: 3em;
}
a.filterbutton, a#resetfilter{
background: var(--btn-bg);
border: var(--btn-border);
border-radius: 0.2em;
......@@ -105,12 +110,16 @@ div#filterbox a.filterbutton, a#resetfilter{
padding: 0.5em;
text-decoration: none;
}
a.filterbutton.active{
background: #fff;
border-top-color: #68a;
}
input#filter{
border: var(--inputfilter-border);
padding: 0.5em;
color:var(--inputfilter-color);
font-size: 130%;
width: 20em;
width: 70%;
}
a#resetfilter{
background:var(--resetfilter-bg);
......@@ -141,9 +150,9 @@ div.boxes a.idp{
text-align: center;
text-decoration: none;
transition: all 0.1s ease-in-out;
width: 10em;
width: 14em;
}
div.boxes .idp img { height: 80px;}
div.boxes .idp img { height: 48px;}
div.boxes a.idp:hover{
box-shadow: var(--idp-hover-shadow);
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment