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