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

initial commit

parents
No related branches found
No related tags found
No related merge requests found
config.php
discofeed.json
# AAI Login page
A login page for multiple AAI organisations.
Its initial focus is on ilias
* PHP 8.x
* License: GNU GPL 3.0
* Source: https://git-repo.iml.unibe.ch/iml-open-source/login-aai
## Description
We have projects with multiple organisations in different countries.
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.
![Login screen with mode "boxes"](docs/images/login_mode_boxes.png)
## Requirements
* PHP 8.2
## Installation
* In the webroot create a subfolder "login_aai".
* Set a Link for Login to `/login_aai/` where needed
* Copy config.php.dist to config.php and make your changes.
## Configuration
First an example configuration:
```php
<?php
return [
'title' => 'AAI Login',
'mode' => 'wayf',
'text-info' => '+++ Hinweis +++ Hinweis +++ Hinweis +++ Hinweis +++',
'text-before-wayf' => '<p>Studierende und Dozenten nutzen in der Regel das nachfolgende "Anmelden":</p>',
'text-after-logins' => '<br><br><h2>Anmeldung ohne AAI</h2>
<p>
Klicken Sie auf: <br />
<a href="/login.php" onclick="">Gast-Zugang</a>
</p>',
'idps' => [
"https://aai-idp.unibe.ch/idp/shibboleth",
"https://aai.insel.ch/idp/shibboleth",
"https://aai-logon.vho-switchaai.ch/idp/shibboleth",
"https://aai-logon.unibas.ch/idp/shibboleth",
]
];
```
| Key | Type | Description
|-- |-- |--
| title | string | Title of the login page; used for title tag and h1 header
| mode | string | Selection mode; one of <br>-`"wayf"` Selection with WAYF script from Switch or<br>- `"boxes"` Boxes with images incl. filter field
| text-info | string | When not empty: show a warning banner with its text on top eg. for maintenance messages
| text-before-wayf | string | Text to show before wayf select box (for mode = "wayf" only)
| text-after-logins | string | Fisnishing text after
| idps | array | List of enabled idps to whitelist; it will filtered by enabled organisatzions by shibboleth
## Devlopment hints
* Requirements: local installation of php; you can use its internal webservice
* Extract files in a local folder or use git clone
* start `php -S localhost:9000` and open <http://localhost:9000> in your webbrowser
* Get the files of the discofeed from a production machine running shibboleth and store it as discofeed cache file:<br> `curl https://example.com/Shibboleth.sso/DiscoFeed > discofeed.json`
* After reachin cache TTL you can repeat the curl command or `touch discofeed.json`
<?php
return [
'title' => 'AAI Login',
'mode' => 'boxes',
// 'mode' => 'wayf',
// 'text-info' => '+++ Hinweis +++ Hinweis +++ Hinweis +++ Hinweis +++',
'text-before' => '<p>Studierende und Dozenten nutzen in der Regel das nachfolgende "Anmelden":</p>',
'text-after' => '<br><br><h2>Anmeldung ohne AAI</h2>
<p>
Klicken Sie auf: <br />
<a href="/login.php" onclick="">Gast-Zugang</a>
</p>',
'idps' => [
"https://aai-idp.unibe.ch/idp/shibboleth",
"https://aai.insel.ch/idp/shibboleth",
"https://aai-logon.vho-switchaai.ch/idp/shibboleth",
"https://aai-logon.unibas.ch/idp/shibboleth",
]
];
\ No newline at end of file
docs/images/login_mode_boxes.png

66 KiB

// ----------------------------------------------------------------------
// VARS
// ----------------------------------------------------------------------
var oFilter = document.getElementById('filterbox');
var sLsvar = 'aailogin-q';
// ----------------------------------------------------------------------
// FUNCTIONS
// ----------------------------------------------------------------------
/**
* Apply filter and reduce listed Idps
*/
function applyfilter(){
if(oFilter){
var q = document.getElementById('filter').value;
for(var i=0; i<document.getElementsByClassName('idp').length; i++){
var idp = document.getElementsByClassName('idp')[i];
var bShow=idp.innerText.toLowerCase().indexOf(q.toLowerCase())>=0;
if(q==""){
bShow=true;
}
idp.style.display = bShow ? 'block' : 'none';
document.getElementById('resetfilter').style.display = (q>"") ? 'inline': 'none';
}
localStorage.setItem(sLsvar,q);
};
}
function resetFilter(){
document.getElementById('filter').value="";
applyfilter();
}
/**
* Enable filter box if static_link is in use
* It renders an input field, reads the last search value from local storage
* and apllies this filter
*/
function showFilterBox(){
if(oFilter){
var q=localStorage.getItem(sLsvar);
if(!q){
q='';
}
oFilter.style.display = 'block';
oFilter.innerHTML='<input type="text" id="filter" placeholder="" onchange="applyfilter()" onkeydown="applyfilter()" onkeyup="applyfilter()" value="'+q+'"/><button id="resetfilter" onclick="resetFilter(); return false;"> X </button>';
applyfilter();
for(var i=0; i<document.getElementsByClassName('idp').length; i++){
var idp = document.getElementsByClassName('idp')[i];
idp.setAttribute("onclick","localStorage.setItem(sLsvar,this.innerText);");
}
};
};
// ----------------------------------------------------------------------
// INIT
// ----------------------------------------------------------------------
showFilterBox();
// ----------------------------------------------------------------------
<?php
$SELFURL = isset($_SERVER['SERVER_NAME']) ? "https://" . $_SERVER['SERVER_NAME'] : '';
$url_list = "$SELFURL/Shibboleth.sso/DiscoFeed";
$listcache = "discofeed.json";
$ttlcache = 60 * 10;
// get the user config
if (!file_exists('config.php')) {
die("ERROR: file config.php does not exist yet.");
}
$aConfig = require_once('config.php');
// ----------------------------------------------------------------------
// functions
// ----------------------------------------------------------------------
/**
* Show a info or error message
* @param string $sLevel level: one of "info", "error"
* @param string $sMessage Message to show
* @return void
*/
function showMessage(string $sLevel, string $sMessage)
{
echo "<div class=\"msg $sLevel\">$sMessage</div>";
}
/**
* Get List if IDPs from cache file if possible or from Shibboleth Disco feed
* @return array
*/
function getAllIdps(): array
{
global $listcache, $ttlcache, $url_list, $aConfig;
if (!file_exists($listcache) || filemtime($listcache) < time() - $ttlcache) {
// echo "DEBUG: IDP - reading from Shibboleth<br>";
$aReturn = json_decode(file_get_contents($url_list), 1);
if ($aReturn && is_array($aReturn)) {
// echo "DEBUG: IDP - storing cache<br>";
file_put_contents($listcache, json_encode($aReturn));
}
} else {
// echo "DEBUG: IDP - reading cache<br>";
$aReturn = json_decode(file_get_contents($listcache), 1);
}
return isset($aReturn) && is_array($aReturn) ? $aReturn : [];
}
/**
* Get list of active IDPs
* @return mixed
*/
function getIdps()
{
global $aConfig, $SELFURL;
$aAllIdps = getAllIdps();
if (is_array($aAllIdps) && count($aAllIdps)) {
foreach ($aAllIdps as $aEntry) {
$sEntityId = $aEntry['entityID'];
if (in_array($sEntityId, $aConfig['idps'])) {
$sLabel = $aEntry['DisplayNames'][0]['value'] ?? parse_url($sEntityId, PHP_URL_HOST);
$sImage = $aEntry['Logos'][1]['value'] ?? ($aEntry['Logos'][0]['value'] ?? '');
$sUrl = "$SELFURL/Shibboleth.sso/Login?entityID=" . urlencode($sEntityId) . "&target=" . urlencode("$SELFURL/shib_login.php");
$aReturn[] = [
'label' => $sLabel,
'image' => $sImage,
'url' => $sUrl,
// for debugging
'_entity' => $aEntry
];
}
}
}
return $aReturn;
}
/**
* Get a list of static links for browsers without javascript
* @param array $aIdplist
* @return string
*/
function getStaticlinks($aIdplist){
$sReturn='';
foreach ($aIdplist as $aEntry) {
$sReturn .= '<a href="' . $aEntry['url']. '">' . $aEntry['label'] . '</a><br>' . "\n";
}
return $sReturn;
}
\ No newline at end of file
<?php
$sOut = '';
// $sOut.='<pre>'.print_r($aIdplist, 1).'</pre>';
if (is_array($aIdplist) && count($aIdplist)) {
$sOut .= '<div id="filterbox"></div><br>';
foreach ($aIdplist as $aEntry) {
$sOut .= '<div class="idp">
<a href="' . $aEntry['url']. '">' . $aEntry['label'] . '<br>
<img src="' . $aEntry['image'] . '"><br>
</a>
</div>'
. "\n"
;
}
echo "$sOut<div style='clear:both'></div>";
} else {
echo '<div class="msg error">No IDPs found in Discovery Feed.</div>';
}
<?php
// ======================================================================
//
// AAI LOGIN * WAYF
//
// ======================================================================
?>
<!-- EMBEDDED-WAYF-START -->
<script type="text/javascript"><!--
// To use this JavaScript, please access:
// https://wayf.switch.ch/SWITCHaai/WAYF/embedded-wayf.js/snippet.html
// and copy/paste the resulting HTML snippet to an unprotected web page that
// you want the embedded WAYF to be displayed
//////////////////// ESSENTIAL SETTINGS ////////////////////
// URL of the WAYF to use
// Examples: "https://wayf.switch.ch/SWITCHaai/WAYF", "https://wayf-test.switch.ch/aaitest/WAYF";
// [Mandatory]
var wayf_URL = "https://wayf.switch.ch/SWITCHaai/WAYF";
// EntityID of the Service Provider that protects this Resource
// Examples: "https://econf.switch.ch/shibboleth", "https://dokeos.unige.ch/shibboleth"
// [Mandatory]
var wayf_sp_entityID = "<?php echo $SELFURL; ?>/shibboleth";
// Shibboleth Service Provider handler URL
// Examples: "https://point.switch.ch/Shibboleth.sso", "https://rr.aai.switch.ch/aaitest/Shibboleth.sso"
// [Mandatory, if wayf_use_discovery_service = false]
var wayf_sp_handlerURL = "<?php echo $SELFURL; ?>/Shibboleth.sso";
// URL on this resource that the user shall be returned to after authentication
// Examples: "https://econf.switch.ch/aai/home", "https://olat.uzh.ch/my/courses"
// [Mandatory]
var wayf_return_url = "<?php echo $SELFURL; ?>/shib_login.php";
//////////////////// RECOMMENDED SETTINGS ////////////////////
// Width of the embedded WAYF in pixels or "auto"
// This is the width of the content only (without padding and border).
// Add 2 x (10px + 1px) = 22px for padding and border to get the actual
// width of everything that is drawn.
// [Optional, default: "auto"]
// Example for fixed size:
// var wayf_width = 250;
//var wayf_width = "auto";
// Height of the embedded WAYF in pixels or "auto"
// This is the height of the content only (without padding and border).
// Add 2 x (10px + 1px) = 22px for padding and border to get the actual
// height of everything that is drawn.
// [Optional, default: "auto"]
// Example for fixed size:
// var wayf_height = 150;
//var wayf_height = "auto";
// Whether to show the checkbox to remember settings for this session
// [Optional, default: true]
//var wayf_show_remember_checkbox = true;
// Force the user's Home Organisation selection to be remembered for the
// current browser session. If wayf_show_remember_checkbox is true
// the checkbox will be shown but will be read only.
// WARNING: Only use this feature if you know exactly what you are doing
// This option will cause problems that are difficult to find
// in case they accidentially select a wrong Home Organisation
// [Optional, false]
//var wayf_force_remember_for_session = false;
// Logo size
// Choose whether the small or large logo shall be used
// [Optional, default: true]
//var wayf_use_small_logo = true;
// Font size
// [Optional, default: 12]
//var wayf_font_size = 12;
// Font color
// [Optional, default: #000000]
//var wayf_font_color = '#000000';
// Border color
// [Optional, default: #969696]
//var wayf_border_color = '#969696';
var wayf_border_color = '#bcd';
// Background color
// [Optional, default: #F0F0F0]
//var wayf_background_color = '#F0F0F0';
var wayf_background_color = 'none';
// Whether to automatically log in user if he has a session/permanent redirect
// cookie set at central wayf
// [Optional, default: true]
//var wayf_auto_login = true;
// Whether to hide the WAYF after the user was logged in
// This requires that the _shib_session_* cookie is set when a user
// could be authenticated, which is the default case when Shibboleth is used.
// For other Service Provider implementations have a look at the setting
// wayf_check_login_state_function that allows you to customize this
// [Optional, default: true]
//var wayf_hide_after_login = true;
// Whether or not to show the categories in the drop-down list
// Possible values are: true or false
// [Optional, default: true]
//var wayf_show_categories = true;
// Most used Identity Providers will be shown as top category in the drop down
// list if this feature is used.
// [Optional, commented out by default]
// var wayf_most_used_idps = new Array("https://aai-logon.unibas.ch/idp/shibboleth", "https://aai.unil.ch/idp/shibboleth");
// Categories of Identity Provider that shall not be shown
// Possible values are: "university","uas","hospital","library","tertiaryb","uppersecondary","vho","others","all"
// [Optional, commented out by default]
// Example of how to hide categories
// var wayf_hide_categories = new Array("other", "library");
// [Optional, commented out by default]
// var wayf_hide_categories = new Array();
// var wayf_hide_categories = new Array("uas","hospital","library","tertiaryb","uppersecondary","vho","others");
var wayf_hide_categories = new Array("all");
// EntityIDs of Identity Provider whose category is hidden but that shall be shown anyway
// If this array is not empty, wayf_show_categories will be disabled because
// otherwise, unhidden IdPs may be displayed in the wrong category
// Example of how to unhide certain Identity Providers
// var wayf_unhide_idps = new Array("https://aai-login.uzh.ch/idp/shibboleth");
// [Optional, commented out by default]
// Example of how to unhide certain Identity Providers
// var wayf_unhide_idps = new Array("urn:mace:switch.ch:aaitest:dukono.switch.ch");
var wayf_unhide_idps = new Array(<?php
if (is_array($aConfig['idps']) && count($aConfig['idps'])) {
echo '"'
.implode('", "', $aConfig['idps'])
.'"'
;
}
?>);
// EntityIDs of Identity Provider that shall not be shown at all
// Example of how to hide certain Identity Provider
// var wayf_hide_idps = new Array("https://idp.unige.ch/idp/shibboleth", "https://lewotolo.switch.ch/idp/shibboleth");
// [Optional, commented out by default]
var wayf_hide_idps = new Array(
"https://aai-login.libraries.ch/idp/shibboleth"
);
//////////////////// ADVANCED SETTINGS ////////////////////
// Use the SAML2/Shibboleth 2 Discovery Service protocol where
// the user is sent back to the Service Provider after selection
// of his Home Organisation.
// This is true by default and it should only be uncommented and set to false
// if there is a good reason why to use the old and deprecated Shibboleth WAYF
// protocol instead.
// [Optional, default: commented out]
// var wayf_use_discovery_service = false;
// Session Initiator URL of the Service Provider
// Examples: "https://econf.switch.ch/Shibboleth.sso/DS", "https://dokeos.unige.ch/Shibboleth.sso/DS"
// This will implicitely be set to wayf_sp_samlDSURL = wayf_sp_handlerURL + "/DS";
// or will be set automatically if the page where the Embedded WAYF is placed is called
// with a 'return' and an 'entityID' GET Arguments
// [Optional, if wayf_use_discovery_service = true
// or if wayf_additional_idps is not empty, default: commented out]
var wayf_sp_samlDSURL = "<?php echo $SELFURL; ?>/Shibboleth.sso/Login";
// Default IdP to preselect when central WAYF couldn't guess IdP either
// This is usually the case the first time ever a user accesses a resource
// [Optional, default: commented out]
// var wayf_default_idp = "https://aai.switch.ch/idp/shibboleth";
// Set a custom Assertion Consumer URL instead of
// the default wayf_sp_handlerURL + '/SAML/POST'
// Only relevant if wayf_use_discovery_service is false
// Examples: "https://olat.uzh.ch/shib/samlaa",
// This will implicitely be set to wayf_sp_samlACURL = wayf_sp_handlerURL + "/SAML/POST";
// "https://foodle.feide.no/simplesaml/shib13/sp/AssertionConsumerService.php"
// [Optional, commented out by default]
// var wayf_sp_samlACURL = "https://maclh.switch.ch/foo/bar";
// Overwites the text of the checkbox if
// wayf_show_remember_checkbox is set to true
// [Optional, commented out by default]
// var wayf_overwrite_checkbox_label_text = 'Save setting for today';
var wayf_overwrite_checkbox_label_text = 'Auswahl merken';
// Overwrites the text of the submit button
// [Optional, commented out by default]
// var wayf_overwrite_submit_button_text = 'Go';
var wayf_overwrite_submit_button_text = 'Zur Anmeldung';
// Overwrites the intro text above the drop-down list
// [Optional, commented out by default]
// var wayf_overwrite_intro_text = 'Select your Home Organisation to log in';
// Overwrites the category name of the most used IdP category in the drop-down list
// [Optional, commented out by default]
// var wayf_overwrite_most_used_idps_text = 'Most popular';
// Whether to hide the WAYF after the user was logged in
// This requires that the _shib_session_* cookie is set when a user
// could be authenticated
// If you want to hide the embedded WAYF completely, uncomment
// the property and set it to "". This then won't draw anything
// [Optional, default commented out: You are already logged in]
// var wayf_logged_in_messsage = "";
// Provide the name of a JavaScript function that checks whether the user
// already is logged in. The function should return true if the user is logged
// in or false otherwise. If the user is logged in, the Embedded WAYF will
// hide itself or draw a custom message depending on the
// setting wayf_logged_in_messsage
// The function you specify has of course to be implemented by yourself!
// [Optional, commented out by default]
// var wayf_check_login_state_function = function() {
// if (# specify user-is-logged-in condition#)
// return true;
// else
// return false;
// }
// EntityIDs, Names and SSO URLs of Identity Providers from other federations
// that shall be added to the drop-down list
// The IdPs will be displayed in the sequence they are defined
// [Optional, commented out by default]
// var wayf_additional_idps = [ ];
// Example of how to add Identity Provider from other federations
// var wayf_additional_idps = [
//
// {name:"International University X",
// entityID:"urn:mace:switch.ch:SWITCHaai:example.university.org",
// SAML1SSOurl:"https://int.univ.org/shibboleth-idp/SSO"},
//
// {name:"Some Other University",
// entityID:"https://other.univ.edu/idp/shibboleth",
// SAML1SSOurl:"https://other.univ.edu/shibboleth-idp/SSO"},
// ];
// Whether to load Identity Providers from the Discovery Feed provided by
// the Service Provider.
// IdPs that are not listed in the Discovery Feed and that the SP therefore is
// not are able to accept assertions from, are hidden by the Embedded WAYF
// IdPs that are in the Discovery Feed but are unknown to the SWITCHwayf
// are added to the wayf_additional_idps.
// The list wayf_additional_idps will be sorted alphabetically
// The SP must have configured the discovery feed handler that generates a
// JSON object. Otherwise it won't generate the JSON data containing the IdPs.
// [Optional, commented out by default]
var wayf_use_disco_feed = true;
// URL where to load the Discovery Feed from in case wayf_use_disco_feed is true
// [Optional, commented out by default]
var wayf_discofeed_url = "<?php echo $SELFURL; ?>/Shibboleth.sso/DiscoFeed";
//////////////////// ADDITIONAL CSS CUSTOMIZATIONS ////////////////////
// To further customize the appearance of the Embedded WAYF you could
// define CSS rules for the following CSS IDs that are used within the
// Embedded WAYF:
// #wayf_div - Container for complete Embedded WAYF
// #wayf_logo_div - Container for logo
// #wayf_logo - Image for logo
// #wayf_intro_div - Container of drop-down list intro label
// #wayf_intro_label - Label of intro text
// #IdPList - The form element
// #user_idp - Select element for drop-down list
// #wayf_remember_checkbox_div - Container of checkbox and its label
// #wayf_remember_checkbox - Checkbox for remembering settings for session
// #wayf_remember_checkbox_label - Text of checkbox
// #wayf_submit_button - Submit button
//
// Use these CSS IDs carefully and at own risk because future updates could
// interfere with the rules you created and the IDs may change without notice!
//-->
</script>
<script type="text/javascript" src="https://wayf.switch.ch/SWITCHaai/WAYF/embedded-wayf.js"></script>
<noscript>
<!--
Fallback to Shibboleth DS session initiator for non-JavaScript users
You should set the value of the target GET parameter to an URL-encoded
absolute URL that points to a Shibboleth protected web page where the user
is logged in into your application.
-->
<p>
<strong>Login:</strong> Javascript is not enabled for your web browser. <br>
<br>
<?php echo getStaticlinks($aIdplist); ?>
</p>
</noscript>
<!-- EMBEDDED-WAYF-END -->
<?php
/*
======================================================================
AAI LOGIN FOR ILIAS 9
----------------------------------------------------------------------
When editing login pages javascript is filtering while saving.
The WAYF script of Switch didn't work anymore.
This script is a workaround and acts as an additional logon page
next to the login.php file.
Institute for Medical Education; University of Bern
GNU GPL 3.0
----------------------------------------------------------------------
2024-09-13 Initial version
======================================================================
*/
require 'inc_functions.php';
?><!DOCTYPE html>
<html lang="" dir="">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title><?php echo $aConfig['title'] ?? 'AAI Login'; ?></title>
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
<link rel="stylesheet" type="text/css" href="login_aai.css" media="screen" />
</head>
<body>
<div id="head">
<h1><?php echo $aConfig['title'] ?? 'AAI Login'; ?></h1>
</div>
<div class="content">
<?php
$aIdplist=getIdps();
// --- messages
echo $SELFURL ? '' : showMessage('error', 'SELFURL is not set. $_SERVER[\'SERVER_NAME\'] is not available.');
echo $aConfig['text-info'] ? showMessage('info', $aConfig['text-info']) : '';
// --- Text before
echo $aConfig['text-before'] ?? '' ;
// --- show possible logins
include "inc_mode_".$aConfig['mode'].".php";
// --- finishing text
echo $aConfig['text-after'] ?? '';
?>
</div>
<script type="text/javascript" defer src="functions.js"></script>
</body>
</html>
\ No newline at end of file
:root{
--main-bg: linear-gradient(-5deg, #abc,#e0e8f0) fixed;
--main-color: #123;
--h1-color: #68a;
--h2-color: #68a;
--a-color: #68a;
--content-bg: #fff;
--content-border: 3px solid #d0d8e0;
--content-shadow: 0.5em 0.5em 2em #fff;
--error-bg: #faa;
--error-color: #400;
--info-bg: #fc3;
--info-color: #650;
/** ---------- mode boxes */
--idp-border: 1px solid #bcd;
--idp-color: #46c;
--idp-shadow: 0 0 2em #eee inset;
--idp-hover-shadow: 0 0 2em #eee;
--inputfilter-border: 2px solid #eee;
--inputfilter-color:#789;
--resetfilter-border: 2px solid #dcc;
--resetfilter-bg:#edd;
--resetfilter-color: #800;
}
body {
background_: linear-gradient(-5deg, #abc, #cee, #f0f8ff, #e0e8f0, #fee) fixed;
background: var(--main-bg);
color: var(--main-color);
font-family: 'Open Sans', sans-serif;
font-size: 1em;
padding: 0;
margin: 0;
}
h1{color:var(--h1-color);}
h2{color: var(--h2-color);}
div#head {
margin: 1em auto 1em;
width: 60%;
}
div.content {
background: var(--content-bg);
border-radius: 2em;
border: var(--content-border);
box-shadow: var(--content-shadow);
margin: 2em auto 2em;
padding: 2em;
width: 60%;
}
div.msg{
border-radius: 1em;
border: 1px solid;
padding: 1em;
margin-bottom: 1em;
}
div.error{
background: var(--error-bg);
color: var(--error-color);
}
div.info{
background: var(--info-bg);
color: var(--info-color);
}
/** ---------- mode boxes */
div.idp a{
border: var(--idp-border);
border-radius: 1em;
box-shadow: var(--idp-shadow);
color: var(--idp-color);
float: left;
height: 7em;
margin: 0 1em 1em 0;
overflow: hidden;
padding: 1em;
text-align: center;
width: 10em;
}
div.idp img { height: 80px;}
div.idp a:hover{
box-shadow: var(--idp-hover-shadow);
}
input#filter{
border: var(--inputfilter-border);
padding: 0.5em;
color:var(--inputfilter-color);
font-size: 130%;
width: 80%;
}
button#resetfilter{
border: var(--resetfilter-border);
font-weight: bold;
font-size: 130%;
padding: 0.5em 2em;
background:var(--resetfilter-bg);
color: var(--resetfilter-color);
margin-left: 1em;
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment