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. ...@@ -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. 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 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) ![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 ## Requirements
* PHP 8.2 * PHP 8.2
......
<?php <?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 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'; protected string $lang = 'en';
// caching of discofeed // caching of discofeed
/**
* Filename of the cache file for the Shibboleth discofeed
* @var string
*/
protected string $_sCachefile = 'discofeed.json'; protected string $_sCachefile = 'discofeed.json';
/**
* Caching time for the discofeed cache in seconds
* @var int
*/
protected int $_iCacheTtl; 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 ...@@ -33,9 +70,10 @@ class shibd_discofeed
if(!$SELFURL) { if(!$SELFURL) {
die("ERROR: SELFURL is not set. \$_SERVER['SERVER_NAME'] is not available."); die("ERROR: SELFURL is not set. \$_SERVER['SERVER_NAME'] is not available.");
} }
$this->SELFURL = $SELFURL;
$this->url_list = "$SELFURL$this->url_list"; $this->_sDiscofeedUrl = "$SELFURL$this->_sDiscofeedUrl";
$this->url_login = "$SELFURL$this->url_login"; $this->_sShibLoginUrl = "$SELFURL$this->_sShibLoginUrl";
$this->_sCachefile = dirname(__DIR__).'/' $this->_sCachefile = dirname(__DIR__).'/'
.($aConfig['cachefile']??'discofeed.json') .($aConfig['cachefile']??'discofeed.json')
...@@ -56,7 +94,7 @@ class shibd_discofeed ...@@ -56,7 +94,7 @@ class shibd_discofeed
{ {
if (!file_exists($this->_sCachefile) || filemtime($this->_sCachefile) < time() - $this->_iCacheTtl) { if (!file_exists($this->_sCachefile) || filemtime($this->_sCachefile) < time() - $this->_iCacheTtl) {
// echo "DEBUG: IDP - reading from Shibboleth<br>"; // 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)) { if ($aReturn && is_array($aReturn)) {
// echo "DEBUG: IDP - storing cache<br>"; // echo "DEBUG: IDP - storing cache<br>";
...@@ -100,13 +138,15 @@ class shibd_discofeed ...@@ -100,13 +138,15 @@ class shibd_discofeed
$sImage = $aEntry['Logos'][1]['value'] ?? ($aEntry['Logos'][0]['value'] ?? ''); $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) . '?entityID='. urlencode($sEntityId)
. "&target=" . urlencode($sEntityId) . "&target=" . urlencode($sEntityId)
. "&target=" . urlencode($this->SELFURL.($aConfig['return-url']??'')) . "&target=" . urlencode($this->SELFURL.($aConfig['return-url']??''))
; ;
$aReturn[] = array_merge([ $sKey=$sLabel;
$aReturn[$sKey] = array_merge([
'_label' => $sLabel, '_label' => $sLabel,
'_description' => $sDescription, '_description' => $sDescription,
'_keywords' => $sKeywords, '_keywords' => $sKeywords,
...@@ -120,6 +160,7 @@ class shibd_discofeed ...@@ -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(){ ...@@ -28,28 +28,57 @@ function applyfilter(){
if (oFilter) { if (oFilter) {
var q = document.getElementById('filter').value; 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++) { for (var i = 0; i < document.getElementsByClassName('idp').length; i++) {
var idp = document.getElementsByClassName('idp')[i]; var idp = document.getElementsByClassName('idp')[i];
var bShow=(q=="" var bShow = true;
? true if (q != "") {
// strip innerHTML and compare lowercase of it with lowercase of query var sText = idp.innerHTML.replace(/<[^>]*>/g, "");
: idp.innerHTML.replace(/<[^>]*>/g, "").toLowerCase().indexOf(q.toLowerCase())>=0 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'; idp.className = bShow ? 'idp' : 'idp hide';
document.getElementById('resetfilter').style.display = (q > "") ? 'inline' : 'none'; 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); 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) { function setFilter(sNewFiltervalue) {
document.getElementById('filter').value = sNewFiltervalue; document.getElementById('filter').value = sNewFiltervalue;
applyfilter(); applyfilter();
} }
/**
* Reset the filter and show all Items
*/
function resetFilter() { function resetFilter() {
setFilter(""); setFilter("");
} }
...@@ -65,29 +94,14 @@ function showFilterBox(){ ...@@ -65,29 +94,14 @@ function showFilterBox(){
q = ''; 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.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>' + '<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++) { for (var i = 0; i < document.getElementsByClassName('idp').length; i++) {
var idp = document.getElementsByClassName('idp')[i]; var idp = document.getElementsByClassName('idp')[i];
idp.setAttribute("onclick", "localStorage.setItem(sLsvar,this.innerText);"); idp.setAttribute("onclick", "localStorage.setItem(sLsvar,this.innerText);");
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
require 'classes/shibd_discofeed.class.php'; require 'classes/shibd_discofeed.class.php';
// get the user config // get the user config
if (!file_exists('config.php')) { if (!file_exists('config.php')) {
die("ERROR: file config.php does not exist yet."); die("ERROR: file config.php does not exist yet.");
...@@ -35,6 +34,7 @@ $aIdplist = $oDiscofeed->getIdps(); ...@@ -35,6 +34,7 @@ $aIdplist = $oDiscofeed->getIdps();
/** /**
* Show a info or error message * Show a info or error message
*
* @param string $sLevel level: one of "info", "error" * @param string $sLevel level: one of "info", "error"
* @param string $sMessage Message to show * @param string $sMessage Message to show
* @return void * @return void
...@@ -47,6 +47,8 @@ function showMessage(string $sLevel, string $sMessage) ...@@ -47,6 +47,8 @@ function showMessage(string $sLevel, string $sMessage)
/** /**
* Get a list of static links for browsers without javascript * 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 * @param array $aIdplist
* @return string * @return string
*/ */
...@@ -59,3 +61,4 @@ function getStaticlinks($aIdplist){ ...@@ -59,3 +61,4 @@ function getStaticlinks($aIdplist){
} }
// ----------------------------------------------------------------------
...@@ -11,22 +11,41 @@ ...@@ -11,22 +11,41 @@
if (is_array($aIdplist) && count($aIdplist)) { if (is_array($aIdplist) && count($aIdplist)) {
$sOut = ''; $sOut = '';
$sOut .= '<div id="filterbox"></div><div class="boxes">';
$sDomainFilter = '';
$aTld = [];
foreach ($aIdplist as $aEntry) { foreach ($aIdplist as $aEntry) {
$aTld[$aEntry['_tld']] = 1;
$sOut .= ' $sOut .= '
<a href="' . $aEntry['_url']. '" <a href="' . $aEntry['_url']. '"
class="idp" class="idp"
title="' . str_replace('"', '',$aEntry['_description']) . '" title="' . strip_tags($aEntry['_description']) . '"
>' . htmlentities($aEntry['_label']) . '<br> >
<span class="hidden">' . str_replace('"', '', $aEntry['_description'] . ' ' . $aEntry['_keywords']) . ' .'. $aEntry['_tld'] . '</span> <span>' . htmlentities($aEntry['_label']) . '</span><br>
<span class="hidden"> .'. $aEntry['_tld'] . '</span>
<img src="' . $aEntry['_image'] . '"><br> <img src="' . $aEntry['_image'] . '"><br>
</a> </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 { } else {
echo '<div class="msg error">No IDPs found in Discovery Feed.</div>'; echo '<div class="msg error">No IDPs found in Discovery Feed.</div>';
} }
...@@ -59,7 +59,7 @@ ...@@ -59,7 +59,7 @@
</div> </div>
<script type="text/javascript" defer src="functions.js"></script> <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> </body>
</html> </html>
\ No newline at end of file
...@@ -28,8 +28,8 @@ ...@@ -28,8 +28,8 @@
--inputfilter-border: 2px solid #eee; --inputfilter-border: 2px solid #eee;
--inputfilter-color:#789; --inputfilter-color:#789;
--btn-border: 2px solid #ddd; --btn-border: 2px solid #eee;
--btn-bg:#eee; --btn-bg:#f8f8f8;
--btn-color: #888; --btn-color: #888;
--resetfilter-border: 2px solid #dcc; --resetfilter-border: 2px solid #dcc;
...@@ -53,9 +53,11 @@ body { ...@@ -53,9 +53,11 @@ body {
h1{color:var(--h1-color);} h1{color:var(--h1-color);}
h2{color: var(--h2-color);} h2{color: var(--h2-color);}
div#head, div.content, footer {
width: 70%;
}
div#head { div#head {
margin: 1em auto 1em; margin: 1em auto 1em;
width: 60%;
} }
div.content { div.content {
background: var(--content-bg); background: var(--content-bg);
...@@ -64,7 +66,6 @@ div.content { ...@@ -64,7 +66,6 @@ div.content {
box-shadow: var(--content-shadow); box-shadow: var(--content-shadow);
margin: 2em auto 2em; margin: 2em auto 2em;
padding: 2em; padding: 2em;
width: 60%;
} }
div.msg{ div.msg{
border-radius: 1em; border-radius: 1em;
...@@ -83,7 +84,6 @@ div.info{ ...@@ -83,7 +84,6 @@ div.info{
} }
footer{ footer{
width: 60%;
color: var(--footer-color); color: var(--footer-color);
margin: 10em auto 5em; margin: 10em auto 5em;
text-align: right; text-align: right;
...@@ -94,7 +94,12 @@ footer{ ...@@ -94,7 +94,12 @@ footer{
div#filterbox{ div#filterbox{
margin-bottom: 1em; margin-bottom: 1em;
} }
div#filterbox a.filterbutton, a#resetfilter{ #filterByDomain{
margin-top: 3em;
margin-bottom: 3em;
}
a.filterbutton, a#resetfilter{
background: var(--btn-bg); background: var(--btn-bg);
border: var(--btn-border); border: var(--btn-border);
border-radius: 0.2em; border-radius: 0.2em;
...@@ -105,12 +110,16 @@ div#filterbox a.filterbutton, a#resetfilter{ ...@@ -105,12 +110,16 @@ div#filterbox a.filterbutton, a#resetfilter{
padding: 0.5em; padding: 0.5em;
text-decoration: none; text-decoration: none;
} }
a.filterbutton.active{
background: #fff;
border-top-color: #68a;
}
input#filter{ input#filter{
border: var(--inputfilter-border); border: var(--inputfilter-border);
padding: 0.5em; padding: 0.5em;
color:var(--inputfilter-color); color:var(--inputfilter-color);
font-size: 130%; font-size: 130%;
width: 20em; width: 70%;
} }
a#resetfilter{ a#resetfilter{
background:var(--resetfilter-bg); background:var(--resetfilter-bg);
...@@ -141,9 +150,9 @@ div.boxes a.idp{ ...@@ -141,9 +150,9 @@ div.boxes a.idp{
text-align: center; text-align: center;
text-decoration: none; text-decoration: none;
transition: all 0.1s ease-in-out; 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{ div.boxes a.idp:hover{
box-shadow: var(--idp-hover-shadow); 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