From 0090982ee22bca1a111b2b941a0f375c279db7b2 Mon Sep 17 00:00:00 2001 From: BaranekD <0Baranek.dominik0@gmail.com> Date: Mon, 22 Aug 2022 10:45:56 +0200 Subject: [PATCH] feat: timeout dialog --- config-templates/module_campusmultiauth.php | 10 ++++ lib/Auth/Source/Campusidp.php | 65 +++++++++++++++++++++ locales/cs/LC_MESSAGES/campusmultiauth.po | 6 ++ locales/en/LC_MESSAGES/campusmultiauth.po | 6 ++ package.json | 3 + templates/includes/timeout-dialog.twig | 10 ++++ templates/selectsource.twig | 2 +- themes/campus/default/base.twig | 3 + www/resources/campus-idp.css | 4 ++ www/resources/campus-idp.js | 11 ++++ www/selectsource.php | 53 +++++++++++++++++ 11 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 templates/includes/timeout-dialog.twig diff --git a/config-templates/module_campusmultiauth.php b/config-templates/module_campusmultiauth.php index 0d5daf3..817f9e2 100644 --- a/config-templates/module_campusmultiauth.php +++ b/config-templates/module_campusmultiauth.php @@ -105,4 +105,14 @@ $config = [ 'cs' => 'ÄŚeština', 'en' => 'English', ], + 'timeout_dialog' => [ + 'ldap.hostname' => '', + 'ldap.username' => '', + 'ldap.password' => '', + 'ldap.basedn' => '', + 'ldap.timeout' => 0, + // 'refresh.dialog.timeout' => 5 * 60, + // 'identifier.attr.name' => 'OIDCClientID', + // 'url.attr.name' => 'rploginurl', + ], ]; diff --git a/lib/Auth/Source/Campusidp.php b/lib/Auth/Source/Campusidp.php index 8a5e5d1..e51198d 100644 --- a/lib/Auth/Source/Campusidp.php +++ b/lib/Auth/Source/Campusidp.php @@ -11,8 +11,10 @@ use SimpleSAML\Auth\State; use SimpleSAML\Configuration; use SimpleSAML\Error; use SimpleSAML\Error\UnserializableException; +use SimpleSAML\Logger; use SimpleSAML\Module; use SimpleSAML\Module\core\Auth\UserPassBase; +use SimpleSAML\Module\ldap\Auth\Ldap; use SimpleSAML\Session; use SimpleSAML\Utils; use Transliterator; @@ -385,6 +387,69 @@ class Campusidp extends Source return $filteredMetadata; } + /** + * @deprecated + */ + public static function useLoginURL($state, $config, $restartUrl) + { + $queryVarsStr = parse_url($state['saml:RelayState'], PHP_URL_QUERY); + if ($queryVarsStr) { + parse_str($queryVarsStr, $queryVars); + + if (!empty($queryVars['client_id'])) { + $OIDCClientID = $queryVars['client_id']; + $OIDCLoginURL = self::getLoginURL($config, $OIDCClientID); + if ($OIDCLoginURL) { + $restartUrl = $OIDCLoginURL; + } + } + } + + return $restartUrl; + } + + /** + * @deprecated + */ + public static function getLoginURL($config, $clientId) + { + $hostname = $config->getString('ldap.hostname'); + $port = $config->getInteger('ldap.port', 389); + $enable_tls = $config->getBoolean('ldap.enable_tls', false); + $debug = $config->getBoolean('ldap.debug', false); + $referrals = $config->getBoolean('ldap.referrals', true); + $timeout = $config->getInteger('ldap.timeout', 0); + $username = $config->getString('ldap.username', null); + $password = $config->getString('ldap.password', null); + + try { + $ldap = new Ldap($hostname, $enable_tls, $debug, $timeout, $port, $referrals); + } catch (\Exception $e) { + Logger::warning($e->getMessage()); + + return null; + } + $ldap->bind($username, $password); + + $identifierAttrName = $config->getString('identifier.attr.name', 'OIDCClientID'); + $urlAttrName = $config->getString('url.attr.name', 'rploginurl'); + + $base = $config->getString('ldap.basedn'); + $filter = '(&(objectClass=perunFacility)(' . $identifierAttrName . '=' . $clientId . '))'; + + try { + $entries = $ldap->searchformultiple([$base], $filter, [$urlAttrName], [], true, false); + } catch (\Exception $e) { + $entries = []; + } + + if (count($entries) < 1 || empty($entries[0][$urlAttrName])) { + return null; + } + + return $entries[0][$urlAttrName][0]; + } + public function logout(&$state) { assert(is_array($state)); diff --git a/locales/cs/LC_MESSAGES/campusmultiauth.po b/locales/cs/LC_MESSAGES/campusmultiauth.po index eab25a8..329df66 100644 --- a/locales/cs/LC_MESSAGES/campusmultiauth.po +++ b/locales/cs/LC_MESSAGES/campusmultiauth.po @@ -72,3 +72,9 @@ msgstr "zavĹ™Ăt" msgid "{campusmultiauth:otp_help}" msgstr "VloĹľte jednorázovĂ˝ kĂłd, napĹ™Ăklad z TOTP aplikace." + +msgid "{campusmultiauth:timeout_dialog}" +msgstr "Z dĹŻvodu neaktivity je nutnĂ© stránku obnovit." + +msgid "{campusmultiauth:refresh}" +msgstr "obnovit" diff --git a/locales/en/LC_MESSAGES/campusmultiauth.po b/locales/en/LC_MESSAGES/campusmultiauth.po index 9db11c7..50ce5b4 100644 --- a/locales/en/LC_MESSAGES/campusmultiauth.po +++ b/locales/en/LC_MESSAGES/campusmultiauth.po @@ -72,3 +72,9 @@ msgstr "close" msgid "{campusmultiauth:otp_help}" msgstr "Enter a one time code, e.g. from a TOTP app." + +msgid "{campusmultiauth:timeout_dialog}" +msgstr "Because of inactivity, it is needed to refresh the page." + +msgid "{campusmultiauth:refresh}" +msgstr "refresh" diff --git a/package.json b/package.json index 794f9a0..e393e70 100644 --- a/package.json +++ b/package.json @@ -18,5 +18,8 @@ "cz-conventional-changelog": "^3.3.0", "prettier": "^2.6.0", "semantic-release": "^19.0.2" + }, + "dependencies": { + "dialog-polyfill": "^0.5.4" } } diff --git a/templates/includes/timeout-dialog.twig b/templates/includes/timeout-dialog.twig new file mode 100644 index 0000000..4730c55 --- /dev/null +++ b/templates/includes/timeout-dialog.twig @@ -0,0 +1,10 @@ +<dialog role="dialog" id="refresh-required-dialog" data-timeout="{{ refresh_dialog_timeout }}"> + <p role="alert"> + {{ '{campusmultiauth:timeout_dialog}'|trans }} + </p> + <p{% if muni_jvs %} class="btn-wrap"{% endif %}> + <a href="{{ restart_url }}" class="btn btn-primary{% if muni_jvs %} btn-s{% endif %}"> + <span class="{% if muni_jvs %}upper{% else %}text-uppercase{% endif %}">{{ '{campusmultiauth:refresh}'|trans }}</span> + </a> + </p> +</dialog> diff --git a/templates/selectsource.twig b/templates/selectsource.twig index 1aa6626..636dd3e 100644 --- a/templates/selectsource.twig +++ b/templates/selectsource.twig @@ -22,7 +22,7 @@ {% if idphint is not defined %}{% set idphint = [] %}{% endif %} <meta name="idphint" content="{{ idphint | json_encode }}"> - <script src="/{{baseurlpath}}module.php/campusmultiauth/resources/campus-idp.js"></script> + <script type="module" src="/{{baseurlpath}}module.php/campusmultiauth/resources/campus-idp.js"></script> {% endblock %} {% block contentwrapper %} diff --git a/themes/campus/default/base.twig b/themes/campus/default/base.twig index 1d68daa..910a197 100644 --- a/themes/campus/default/base.twig +++ b/themes/campus/default/base.twig @@ -30,6 +30,9 @@ {% endblock contentwrapper %} <div id="push"></div> </div>{# layout #} + {% if restart_url is defined %} + {% block timeoutDialog %}{% include '@campusmultiauth/includes/timeout-dialog.twig' %}{% endblock %} + {% endif %} <div id="foot"> {% block footer %}{% include "_footer.twig" %}{% endblock %} </div> diff --git a/www/resources/campus-idp.css b/www/resources/campus-idp.css index 4c4501a..050e75f 100644 --- a/www/resources/campus-idp.css +++ b/www/resources/campus-idp.css @@ -353,6 +353,10 @@ body { width: 100%; } +#refresh-required-dialog { + text-align: center; +} + .idps-form-div { margin-top: 0; margin-bottom: 24px; diff --git a/www/resources/campus-idp.js b/www/resources/campus-idp.js index a6b3f4f..26dea11 100644 --- a/www/resources/campus-idp.js +++ b/www/resources/campus-idp.js @@ -1,3 +1,5 @@ +import dialogPolyfill from "dialog-polyfill"; + function hideElement(element) { element.classList.add("vhide", "d-none"); } @@ -91,6 +93,15 @@ function selectizeLoad(query, callback) { } document.addEventListener("DOMContentLoaded", function () { + // show dialog after the specified timeout to refresh the page + const dialog = document.getElementById("refresh-required-dialog"); + if (dialog && dialog.dataset.timeout) { + dialogPolyfill.registerDialog(dialog); + setTimeout(() => { + dialog.showModal(); + }, dialog.dataset.timeout * 1000); + } + var moreOptions = document.querySelectorAll(".more-options"); if (moreOptions) { moreOptions.forEach(function (showButton) { diff --git a/www/selectsource.php b/www/selectsource.php index 7dd0939..f7b85ca 100644 --- a/www/selectsource.php +++ b/www/selectsource.php @@ -160,6 +160,54 @@ if (!empty($_POST['q'])) { curl_close($ch); } +// <timeout dialog> +$timeoutDialogConfig = + Configuration::getConfig('module_campusmultiauth.php')->getConfigItem('timeout_dialog'); + +if (!empty($timeoutDialogConfig)) { + $restartUrl = '#'; + + if (isset($state['SPMetadata']['RelayState'])) { + $rsUrl = filter_var( + $state['SPMetadata']['RelayState'], + FILTER_VALIDATE_URL, + FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED + ); + if ($rsUrl !== false) { + $restartUrl = $rsUrl; + } + } + + if (isset($state['saml:RelayState'])) { + $rsUrl = filter_var( + $state['saml:RelayState'], + FILTER_VALIDATE_URL, + FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED + ); + + if ($rsUrl !== false) { + $rs = parse_url($rsUrl); + $sp = $state['SPMetadata']['AssertionConsumerService']; + if (is_array($sp) && isset($sp[0])) { + $sp = $sp[0]; + } + if (is_array($sp) && isset($sp['Location'])) { + $sp = $sp['Location']; + } + if (is_string($sp)) { + $sp = parse_url($sp); + if ($rs['scheme'] === $sp['scheme'] && $rs['host'] === $sp['host']) { + $restartUrl = $rsUrl; + } + } + + // use login URL instead of redirecting to OIDC + $restartUrl = Campusidp::useLoginURL($state, $timeoutDialogConfig, $restartUrl); + } + } +} +// </timeout dialog> + $globalConfig = Configuration::getInstance(); $t = new Template($globalConfig, 'campusmultiauth:selectsource.php'); @@ -196,6 +244,11 @@ if (!empty($idphint)) { } } +if (isset($restartUrl)) { + $t->data['restart_url'] = $restartUrl; + $t->data['refresh_dialog_timeout'] = $timeoutDialogConfig->getInteger('refresh.dialog.timeout', 5 * 60); +} + $t->data['searchbox_indexes'] = json_encode(array_values(array_filter(array_map(function ($config, $index) { return $config['name'] === 'searchbox' ? $index : null; }, $wayfConfig['components'], array_keys($wayfConfig['components'])), function ($a) { -- GitLab