From 82164e5c7691630fc39091de57c85d34143c4665 Mon Sep 17 00:00:00 2001
From: Martin <martin.vanes@surf.nl>
Date: Fri, 25 Jun 2021 13:26:30 +0200
Subject: [PATCH] Add customauth module and idp-hosted changes

---
 README.md                                     |  14 +
 metadata/saml20-idp-hosted.php                |  38 +-
 modules/customauth/.gitignore                 |   1 +
 modules/customauth/README.md                  |   3 +
 modules/customauth/default-disable            |   3 +
 .../lib/Auth/Process/RedirectTest.php         |  36 +
 .../customauth/lib/Auth/Source/External.php   | 298 ++++++++
 .../lib/Auth/Source/StaticSource.php          |  62 ++
 .../customauth/lib/Auth/Source/UserPass.php   | 100 +++
 .../customauth/templates/authenticate.tpl.bak |  75 ++
 .../customauth/templates/authenticate.tpl.php | 106 +++
 .../customauth/templates/authenticate.twig    |  29 +
 modules/customauth/www/assets/css/default.css | 651 ++++++++++++++++++
 modules/customauth/www/assets/favicon.ico     | Bin 0 -> 1150 bytes
 modules/customauth/www/assets/script.js       |  44 ++
 modules/customauth/www/authpage.php           |  85 +++
 modules/customauth/www/redirecttest.php       |  19 +
 modules/customauth/www/resume.php             |  14 +
 18 files changed, 1568 insertions(+), 10 deletions(-)
 create mode 100644 modules/customauth/.gitignore
 create mode 100644 modules/customauth/README.md
 create mode 100644 modules/customauth/default-disable
 create mode 100644 modules/customauth/lib/Auth/Process/RedirectTest.php
 create mode 100644 modules/customauth/lib/Auth/Source/External.php
 create mode 100644 modules/customauth/lib/Auth/Source/StaticSource.php
 create mode 100644 modules/customauth/lib/Auth/Source/UserPass.php
 create mode 100644 modules/customauth/templates/authenticate.tpl.bak
 create mode 100644 modules/customauth/templates/authenticate.tpl.php
 create mode 100644 modules/customauth/templates/authenticate.twig
 create mode 100644 modules/customauth/www/assets/css/default.css
 create mode 100644 modules/customauth/www/assets/favicon.ico
 create mode 100644 modules/customauth/www/assets/script.js
 create mode 100644 modules/customauth/www/authpage.php
 create mode 100644 modules/customauth/www/redirecttest.php
 create mode 100644 modules/customauth/www/resume.php

diff --git a/README.md b/README.md
index b23667b..3650997 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,16 @@
 # Test_IdP
 
+metadata/saml20-idp-hosted.php
+```
+$metadata['__DYNAMIC:1__'] = [
+
++ 'auth' => 'custom-userpass',
+
+```
+
+config/authsources.php
+```
++    'custom-userpass' => [
++        'customauth:External',
++    ],
+```
diff --git a/metadata/saml20-idp-hosted.php b/metadata/saml20-idp-hosted.php
index 86a1aec..fa72469 100644
--- a/metadata/saml20-idp-hosted.php
+++ b/metadata/saml20-idp-hosted.php
@@ -63,28 +63,46 @@ $metadata['__DYNAMIC:1__'] = [
     ],
 
     // X.509 key and certificate. Relative to the cert directory.
-    'privatekey' => 'server.pem',
+    'privatekey' => 'server.key',
     'certificate' => 'server.crt',
 
     /*
      * Authentication source to use. Must be one that is configured in
      * 'config/authsources.php'.
      */
-    'auth' => 'example-userpass',
+    'auth' => 'custom-userpass',
+    //'auth' => 'example-userpass',
 
     /* Uncomment the following to use the uri NameFormat on attributes. */
-    /*
-    'attributes.NameFormat' => 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri',
+    //'attributes.NameFormat' => 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri',
+
     'authproc' => [
-        // Convert LDAP names to oids.
-        100 => ['class' => 'core:AttributeMap', 'name2oid'],
+	5 => [
+          'class' => 'core:TargetedID',
+	  'identifyingAttribute' => 'eduPersonTargetedId',
+	  'nameId' => TRUE,
+	],
+        8 => [
+          'class' => 'core:PHP',
+          'code' => '
+            unset($attributes["eduPersonTargetedId"]);
+          ',
+        ],	
+        // Convert LDAP names to urn.
+	10 => [
+	  'class' => 'core:AttributeMap',
+	  'name2oid'
+	],
     ],
-    */
+    'attributes.NameFormat' => 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri',
+    'attributeencodings' => array(
+        'urn:oid:1.3.6.1.4.1.5923.1.1.1.10' => 'raw', /* eduPersonTargetedID with oid NameFormat. */
+    ),
 ];
 
 $query = "select o.name, o.type, c.value  from idps i
-  left join config c on c.idp_id = i.idp_id
-  left join options o on c.option_id = o.option_id
+  right join config c on c.idp_id = i.idp_id
+  right join options o on c.option_id = o.option_id
   where i.host = :host";
 
 
@@ -97,7 +115,7 @@ foreach ($result as $row) {
   $name = explode(':', $row['name']);
   $value = $row['value'];
   $config = array_merge_recursive($config, nest($name, $value));
-} 
+}
 
 $metadata['__DYNAMIC:1__'] = array_replace_recursive($metadata['__DYNAMIC:1__'], $config);
 
diff --git a/modules/customauth/.gitignore b/modules/customauth/.gitignore
new file mode 100644
index 0000000..87836d5
--- /dev/null
+++ b/modules/customauth/.gitignore
@@ -0,0 +1 @@
+enable
diff --git a/modules/customauth/README.md b/modules/customauth/README.md
new file mode 100644
index 0000000..b340ecf
--- /dev/null
+++ b/modules/customauth/README.md
@@ -0,0 +1,3 @@
+# simplesamlphp-module-customauth
+
+CustomAuth simpleSAML php auth source
diff --git a/modules/customauth/default-disable b/modules/customauth/default-disable
new file mode 100644
index 0000000..fa0bd82
--- /dev/null
+++ b/modules/customauth/default-disable
@@ -0,0 +1,3 @@
+This file indicates that the default state of this module
+is disabled. To enable, create a file named enable in the
+same directory as this file.
diff --git a/modules/customauth/lib/Auth/Process/RedirectTest.php b/modules/customauth/lib/Auth/Process/RedirectTest.php
new file mode 100644
index 0000000..ee56a95
--- /dev/null
+++ b/modules/customauth/lib/Auth/Process/RedirectTest.php
@@ -0,0 +1,36 @@
+<?php
+
+declare(strict_types=1);
+
+namespace SimpleSAML\Module\customauth\Auth\Process;
+
+use SimpleSAML\Auth;
+use SimpleSAML\Module;
+use SimpleSAML\Utils;
+
+/**
+ * A simple processing filter for testing that redirection works as it should.
+ *
+ */
+class RedirectTest extends \SimpleSAML\Auth\ProcessingFilter
+{
+    /**
+     * Initialize processing of the redirect test.
+     *
+     * @param array &$state  The state we should update.
+     * @return void
+     */
+    public function process(&$state)
+    {
+        assert(is_array($state));
+        assert(array_key_exists('Attributes', $state));
+
+        // To check whether the state is saved correctly
+        $state['Attributes']['RedirectTest1'] = ['OK'];
+
+        // Save state and redirect
+        $id = Auth\State::saveState($state, 'customauth:redirectfilter-test');
+        $url = Module::getModuleURL('customauth/redirecttest.php');
+        Utils\HTTP::redirectTrustedURL($url, ['StateId' => $id]);
+    }
+}
diff --git a/modules/customauth/lib/Auth/Source/External.php b/modules/customauth/lib/Auth/Source/External.php
new file mode 100644
index 0000000..95049c3
--- /dev/null
+++ b/modules/customauth/lib/Auth/Source/External.php
@@ -0,0 +1,298 @@
+<?php
+
+declare(strict_types=1);
+
+namespace SimpleSAML\Module\customauth\Auth\Source;
+
+use SimpleSAML\Auth;
+use SimpleSAML\Error;
+use SimpleSAML\Module;
+use SimpleSAML\Utils;
+
+/**
+ * Example external authentication source.
+ *
+ * This class is an custom authentication source which is designed to
+ * hook into an external authentication system.
+ *
+ * To adapt this to your own web site, you should:
+ * 1. Create your own module directory.
+ * 2. Add a file "default-enable" to that directory.
+ * 3. Copy this file and modules/customauth/www/resume.php to their corresponding
+ *    location in the new module.
+ * 4. Replace all occurrences of "customauth" in this file and in resume.php with the name of your module.
+ * 5. Adapt the getUser()-function, the authenticate()-function and the logout()-function to your site.
+ * 6. Add an entry in config/authsources.php referencing your module. E.g.:
+ *        'myauth' => array(
+ *            '<mymodule>:External',
+ *        ),
+ *
+ * @package SimpleSAMLphp
+ */
+class External extends \SimpleSAML\Auth\Source
+{
+    /**
+     * The key of the AuthId field in the state.
+     */
+    public const AUTHID = 'SimpleSAML\Module\customauth\Auth\Source\External.AuthId';
+
+
+    /**
+     * Constructor for this authentication source.
+     *
+     * @param array $info  Information about this authentication source.
+     * @param array $config  Configuration.
+     */
+    public function __construct($info, $config)
+    {
+        assert(is_array($info));
+        assert(is_array($config));
+
+        // Call the parent constructor first, as required by the interface
+        parent::__construct($info, $config);
+
+        // Do any other configuration we need here
+    }
+
+
+    /**
+     * Retrieve attributes for the user.
+     *
+     * @return array|null  The user's attributes, or NULL if the user isn't authenticated.
+     */
+    private function getUser(): ?array
+    {
+        /*
+         * In this example we assume that the attributes are
+         * stored in the users PHP session, but this could be replaced
+         * with anything.
+         */
+
+        if (!session_id()) {
+            // session_start not called before. Do it here
+            session_start();
+        }
+
+        /*
+         * Find the attributes for the user.
+         * Note that all attributes in SimpleSAMLphp are multivalued, so we need
+         * to store them as arrays.
+         */
+
+	$attributes = null;
+
+        if (isset($_SESSION['attributes'])) foreach($_SESSION['attributes'] as $key => $value) {
+            #$attributes[$key] = array_filter(array_map('trim', explode(',', $value)));
+            $attributes[$key] = is_array($value)?$value:[$value];
+        }
+
+        return $attributes;
+    }
+
+
+    /**
+     * Log in using an external authentication helper.
+     *
+     * @param array &$state  Information about the current authentication.
+     * @return void
+     */
+    public function authenticate(&$state)
+    {
+        assert(is_array($state));
+
+        $attributes = $this->getUser();
+        if ($attributes !== null) {
+            /*
+             * The user is already authenticated.
+             *
+             * Add the users attributes to the $state-array, and return control
+             * to the authentication process.
+             */
+            $state['Attributes'] = $attributes;
+            return;
+        }
+
+        /*
+         * The user isn't authenticated. We therefore need to
+         * send the user to the login page.
+         */
+
+        /*
+         * First we add the identifier of this authentication source
+         * to the state array, so that we know where to resume.
+         */
+        $state['customauth:AuthID'] = $this->authId;
+
+        /*
+         * We need to save the $state-array, so that we can resume the
+         * login process after authentication.
+         *
+         * Note the second parameter to the saveState-function. This is a
+         * unique identifier for where the state was saved, and must be used
+         * again when we retrieve the state.
+         *
+         * The reason for it is to prevent
+         * attacks where the user takes a $state-array saved in one location
+         * and restores it in another location, and thus bypasses steps in
+         * the authentication process.
+         */
+        $stateId = Auth\State::saveState($state, 'customauth:External');
+
+        /*
+         * Now we generate a URL the user should return to after authentication.
+         * We assume that whatever authentication page we send the user to has an
+         * option to return the user to a specific page afterwards.
+         */
+        $returnTo = Module::getModuleURL('customauth/resume.php', [
+            'State' => $stateId,
+        ]);
+
+        /*
+         * Get the URL of the authentication page.
+         *
+         * Here we use the getModuleURL function again, since the authentication page
+         * is also part of this module, but in a real example, this would likely be
+         * the absolute URL of the login page for the site.
+         */
+        $authPage = Module::getModuleURL('customauth/authpage.php');
+
+        /*
+         * The redirect to the authentication page.
+         *
+         * Note the 'ReturnTo' parameter. This must most likely be replaced with
+         * the real name of the parameter for the login page.
+         */
+        Utils\HTTP::redirectTrustedURL($authPage, [
+            'ReturnTo' => $returnTo,
+        ]);
+
+        /*
+         * The redirect function never returns, so we never get this far.
+         */
+        assert(false);
+    }
+
+
+    /**
+     * Resume authentication process.
+     *
+     * This function resumes the authentication process after the user has
+     * entered his or her credentials.
+     *
+     * @return void
+     * @throws \SimpleSAML\Error\BadRequest
+     * @throws \SimpleSAML\Error\Exception
+     */
+    public static function resume()
+    {
+        /*
+         * First we need to restore the $state-array. We should have the identifier for
+         * it in the 'State' request parameter.
+         */
+        if (!isset($_REQUEST['State'])) {
+            throw new Error\BadRequest('Missing "State" parameter.');
+        }
+
+        /*
+         * Once again, note the second parameter to the loadState function. This must
+         * match the string we used in the saveState-call above.
+         */
+        /** @var array $state */
+        $state = Auth\State::loadState($_REQUEST['State'], 'customauth:External');
+
+	/*
+	 * Raise SAML Error Response when the 'Create SAML Error Response'
+	 * checkbox was checked
+	 */
+	if (isset($_SESSION['ErrorResponse'])) {
+          unset($_SESSION['ErrorResponse']);
+	  Auth\State::throwException($state, 
+              new \SimpleSAML\Module\saml\Error(
+                \SAML2\Constants::STATUS_RESPONDER,
+                \SAML2\Constants::STATUS_AUTHN_FAILED,
+                'Authentication failed'
+	      )
+	  );
+	}
+	
+        /*
+         * Now we have the $state-array, and can use it to locate the authentication
+         * source.
+         */
+        $source = Auth\Source::getById($state['customauth:AuthID']);
+        if ($source === null) {
+            /*
+             * The only way this should fail is if we remove or rename the authentication source
+             * while the user is at the login page.
+             */
+            throw new Error\Exception('Could not find authentication source with id ' . $state[self::AUTHID]);
+        }
+
+        /*
+         * Make sure that we haven't switched the source type while the
+         * user was at the authentication page. This can only happen if we
+         * change config/authsources.php while an user is logging in.
+         */
+        if (!($source instanceof self)) {
+            throw new Error\Exception('Authentication source type changed.');
+        }
+
+        /*
+         * OK, now we know that our current state is sane. Time to actually log the user in.
+         *
+         * First we check that the user is acutally logged in, and didn't simply skip the login page.
+         */
+        $attributes = $source->getUser();
+        if ($attributes === null) {
+            /*
+             * The user isn't authenticated.
+             *
+             * Here we simply throw an exception, but we could also redirect the user back to the
+             * login page.
+             */
+            throw new Error\Exception('User not authenticated after login page.');
+        }
+
+
+        /*
+         * So, we have a valid user. Time to resume the authentication process where we
+         * paused it in the authenticate()-function above.
+         */
+
+        $state['Attributes'] = $attributes;
+        Auth\Source::completeAuth($state);
+
+        /*
+         * The completeAuth-function never returns, so we never get this far.
+         */
+        assert(false);
+    }
+
+
+    /**
+     * This function is called when the user start a logout operation, for example
+     * by logging out of a SP that supports single logout.
+     *
+     * @param array &$state  The logout state array.
+     * @return void
+     */
+    public function logout(&$state)
+    {
+        assert(is_array($state));
+
+        if (!session_id()) {
+            // session_start not called before. Do it here
+            session_start();
+        }
+
+        /*
+         * In this example we simply remove the 'uid' from the session.
+         */
+        unset($_SESSION['attributes']);
+
+        /*
+         * If we need to do a redirect to a different page, we could do this
+         * here, but in this example we don't need to do this.
+         */
+    }
+}
diff --git a/modules/customauth/lib/Auth/Source/StaticSource.php b/modules/customauth/lib/Auth/Source/StaticSource.php
new file mode 100644
index 0000000..faad9f0
--- /dev/null
+++ b/modules/customauth/lib/Auth/Source/StaticSource.php
@@ -0,0 +1,62 @@
+<?php
+
+declare(strict_types=1);
+
+namespace SimpleSAML\Module\customauth\Auth\Source;
+
+use SimpleSAML\Utils;
+
+/**
+ * Example authentication source.
+ *
+ * This class is an example authentication source which will always return a user with
+ * a static set of attributes.
+ *
+ * @author Olav Morken, UNINETT AS.
+ * @package SimpleSAMLphp
+ */
+class StaticSource extends \SimpleSAML\Auth\Source
+{
+    /**
+     * The attributes we return.
+     * @var array
+     */
+    private $attributes;
+
+
+    /**
+     * Constructor for this authentication source.
+     *
+     * @param array $info  Information about this authentication source.
+     * @param array $config  Configuration.
+     */
+    public function __construct($info, $config)
+    {
+        assert(is_array($info));
+        assert(is_array($config));
+
+        // Call the parent constructor first, as required by the interface
+        parent::__construct($info, $config);
+
+        // Parse attributes
+        try {
+            $this->attributes = Utils\Attributes::normalizeAttributesArray($config);
+        } catch (\Exception $e) {
+            throw new \Exception('Invalid attributes for authentication source ' .
+                $this->authId . ': ' . $e->getMessage());
+        }
+    }
+
+
+    /**
+     * Log in using static attributes.
+     *
+     * @param array &$state  Information about the current authentication.
+     * @return void
+     */
+    public function authenticate(&$state)
+    {
+        assert(is_array($state));
+        $state['Attributes'] = $this->attributes;
+    }
+}
diff --git a/modules/customauth/lib/Auth/Source/UserPass.php b/modules/customauth/lib/Auth/Source/UserPass.php
new file mode 100644
index 0000000..1aa147b
--- /dev/null
+++ b/modules/customauth/lib/Auth/Source/UserPass.php
@@ -0,0 +1,100 @@
+<?php
+
+declare(strict_types=1);
+
+namespace SimpleSAML\Module\customauth\Auth\Source;
+
+use SimpleSAML\Error;
+use SimpleSAML\Utils;
+
+/**
+ * Example authentication source - username & password.
+ *
+ * This class is an example authentication source which stores all username/passwords in an array,
+ * and authenticates users against this array.
+ *
+ * @author Olav Morken, UNINETT AS.
+ * @package SimpleSAMLphp
+ */
+
+class UserPass extends \SimpleSAML\Module\core\Auth\UserPassBase
+{
+    /**
+     * Our users, stored in an associative array. The key of the array is "<username>:<password>",
+     * while the value of each element is a new array with the attributes for each user.
+     *
+     * @var array
+     */
+    private $users;
+
+
+    /**
+     * Constructor for this authentication source.
+     *
+     * @param array $info  Information about this authentication source.
+     * @param array $config  Configuration.
+     */
+    public function __construct($info, $config)
+    {
+        assert(is_array($info));
+        assert(is_array($config));
+
+        // Call the parent constructor first, as required by the interface
+        parent::__construct($info, $config);
+
+        $this->users = [];
+
+        // Validate and parse our configuration
+        foreach ($config as $userpass => $attributes) {
+            if (!is_string($userpass)) {
+                throw new \Exception(
+                    'Invalid <username>:<password> for authentication source ' . $this->authId . ': ' . $userpass
+                );
+            }
+
+            $userpass = explode(':', $userpass, 2);
+            if (count($userpass) !== 2) {
+                throw new \Exception(
+                    'Invalid <username>:<password> for authentication source ' . $this->authId . ': ' . $userpass[0]
+                );
+            }
+            $username = $userpass[0];
+            $password = $userpass[1];
+
+            try {
+                $attributes = Utils\Attributes::normalizeAttributesArray($attributes);
+            } catch (\Exception $e) {
+                throw new \Exception('Invalid attributes for user ' . $username .
+                    ' in authentication source ' . $this->authId . ': ' . $e->getMessage());
+            }
+            $this->users[$username . ':' . $password] = $attributes;
+        }
+    }
+
+
+    /**
+     * Attempt to log in using the given username and password.
+     *
+     * On a successful login, this function should return the users attributes. On failure,
+     * it should throw an exception. If the error was caused by the user entering the wrong
+     * username or password, a \SimpleSAML\Error\Error('WRONGUSERPASS') should be thrown.
+     *
+     * Note that both the username and the password are UTF-8 encoded.
+     *
+     * @param string $username  The username the user wrote.
+     * @param string $password  The password the user wrote.
+     * @return array  Associative array with the users attributes.
+     */
+    protected function login($username, $password)
+    {
+        assert(is_string($username));
+        assert(is_string($password));
+
+        $userpass = $username . ':' . $password;
+        if (!array_key_exists($userpass, $this->users)) {
+            throw new Error\Error('WRONGUSERPASS');
+        }
+
+        return $this->users[$userpass];
+    }
+}
diff --git a/modules/customauth/templates/authenticate.tpl.bak b/modules/customauth/templates/authenticate.tpl.bak
new file mode 100644
index 0000000..58919b4
--- /dev/null
+++ b/modules/customauth/templates/authenticate.tpl.bak
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
+    <title>customauth login page</title>
+  </head>
+  <body>
+    <h1>CustomAuth login page</h1>
+    <form method="post" action="?">
+    <p><b>Choose User profile</b><p>
+<?php
+    foreach ($this->data['users'] as $user => $values) {
+        echo "<input type=radio name=username value='$user'> " . $values['displayName'] . "<br>\n";
+    }
+?>
+    <p><b>Choose attributes</b></p>
+      <div style="float: left" id="attributes"></div>
+      <div  id="explanation"></div><br style="clear: both" />
+    <p><b>Released attributes</b></p>
+      <div id="output"></div>
+    <input type="hidden" name="ReturnTo" value="<?= htmlspecialchars($this->data['returnTo']) ?>">
+    <p><input type="submit" value="Log in"></p>
+    </form>
+  </body>
+
+  <script>
+    var users = [];
+    var keys = [];
+    var user = '';
+<?php
+    foreach ($this->data['users'] as $user => $values) {
+        echo "users['$user'] = [];\n";
+        foreach ($values as $key => $value) {
+            echo "users['$user']['$key'] = '$value';\n";
+            echo "keys['$key'] = true;";
+        }
+    }
+
+?>
+    function update() {
+        var html = "";
+        for (let key in users[user]) {
+            if (keys[key]) {
+                html += key + ": " + users[user][key] + "<br />\n";
+            }
+        }
+        $('#output').html(html);
+
+    };
+    $('input[type=radio][name=username]').change(function() {
+        var attributes = "";
+        user = this.value;
+        for (let key in users[user]) {
+            attribute = users[user][key];
+            attributes += "<input id=chkbx_" + key + " type=checkbox name=keys[" + key + "] " + (keys[key]?'checked':'') + ">" + key + "<br>\n";
+        };
+        $('#attributes').html(attributes);
+        $('#explanation').html('');
+        $('input[id^=chkbx_]').change(function() {
+            var key = this.name.slice(5,-1);
+            keys[key] = this.checked;
+            if (this.checked) $('#explanation').html('Ah! You enabled ' + key + '!<br />\nIt\'s allways good to enable ' + key + ', because it\'s good you know!');
+            else $('#explanation').html('Never disable ' + key + '! You should leave it there.<br />No good will come from disabling ' + key + '!');
+        });
+        update();
+    });
+
+    //$('#attributes').change(update);
+    $('#attributes').change(function() {
+        update();
+    });
+
+  </script>
+</html>
diff --git a/modules/customauth/templates/authenticate.tpl.php b/modules/customauth/templates/authenticate.tpl.php
new file mode 100644
index 0000000..5498c1b
--- /dev/null
+++ b/modules/customauth/templates/authenticate.tpl.php
@@ -0,0 +1,106 @@
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+    <meta name="viewport" content="initial-scale=1.0"/>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
+    <script type="text/javascript" src="<?=SimpleSAML\Module::getModuleURL("customauth/assets/script.js")?>"></script>
+    <title>Please select a profile to release attributes</title>
+    <link rel="stylesheet" type="text/css" href="<?=SimpleSAML\Module::getModuleURL("customauth/assets/css/default.css")?>"/>
+    <link rel="icon" type="image/icon" href="<?=SimpleSAML\Module::getModuleURL("customauth/assets/favicon.ico")?>"/>
+    <meta name="robots" content="noindex, nofollow"/>
+</head>
+<body onload="SimpleSAML_focus('password');">
+<div id="wrap">
+  <div id="header">
+    <br>
+    <img src="https://idp.test.inacademia.org/module.php/inacademia_theme/login_icon.svg" alt="IKONA"/>
+    <h1><a href="/">Enter your username and password</a></h1>
+  </div>
+  <div class="romb"></div>
+  <div id="content">
+    <p class="logintext">A service has requested you to authenticate yourself. <br>Please select a profile and optionally which attributes to release from the information below.</p>
+
+    <form method="post" action="?">
+   <p><hr/><b>Choose User profile</b></p>
+  <div style="float:left; margin-right: 10px">
+  <select name=username size=5>
+<?php
+    foreach ($this->data['users'] as $user => $values) {
+	$uarr = explode(':', $user);
+        $uid = $uarr[0];
+	$display = $this->data['displays'][$user];
+        echo "<option name=\"$uid\" value=\"$uid\">$display</option>\n";
+    }
+?>
+  </select>
+  </div>
+  <div id="explanation">Woohaa!</div><br style="clear: both" />
+    <p><hr/><b>Choose attributes</b></p>
+      <div style="float: left" id="attributes"></div>
+      <div  id="about"></div><br style="clear: both" />
+    <p><hr/><b>Released attributes</b></p>
+      <div id="output"></div>
+    <p><hr/><b>Special</b></p>
+      <div><input type="checkbox" name="error" value="true"> Create SAML Error Response</div>
+    <input type="hidden" name="ReturnTo" value="<?= htmlspecialchars($this->data['returnTo']) ?>">
+    <p><input id="submit_button" class="btn" type="submit" value="Log in"></p>
+    </form>
+  </div>
+</body>
+  <script>
+    var users = [];
+    var keys = [];
+    var message = [];
+    var explanations = [];
+    var user = '';
+<?php
+    foreach ($this->data['users'] as $user => $values) {
+        echo "users['$user'] = [];\n";
+        foreach ($values as $key => $value) {
+            echo "users['$user']['$key'] = [\"" . (is_array($value)?implode("\",\"", $value):$value) . "\"]; ";
+            echo "keys['$key'] = true;\n";
+        }
+    }
+    foreach ($this->data['attributes'] as $attribute => $message) {
+        echo "message['$attribute'] = \"$message\";\n";
+    }
+    foreach ($this->data['explanations'] as $user => $explanation) {
+        echo "explanations['$user'] = \"$explanation\";\n";
+    }
+
+?>
+    function update() {
+        var html = "";
+        for (let key in users[user]) {
+            if (keys[key]) {
+                html += key + ": " + users[user][key] + "<br />\n";
+            }
+        }
+        $('#output').html(html);
+
+    };
+    $('select[name=username]').change(function() {
+        var attributes = "";
+        user = this.value;
+        for (let key in users[user]) {
+            attribute = users[user][key];
+            attributes += "<input id=chkbx_" + key + " type=checkbox name=keys[" + key + "] " + (keys[key]?'checked':'') + ">" + key + "<br>\n";
+        };
+	$('#explanation').html(explanations[user]);
+        $('#attributes').html(attributes);
+        $('#about').html('');
+        $('input[id^=chkbx_]').change(function() {
+            var key = this.name.slice(5,-1);
+            keys[key] = this.checked;
+            if (this.checked) $('#about').html(message[key] + '<br>Enabled');
+            else $('#about').html(message[key] + '<br>Disabled');
+        });
+        update();
+    });
+
+    $('#attributes').change(function() {
+        update();
+    });
+
+  </script>
+</html>
diff --git a/modules/customauth/templates/authenticate.twig b/modules/customauth/templates/authenticate.twig
new file mode 100644
index 0000000..6a2f52a
--- /dev/null
+++ b/modules/customauth/templates/authenticate.twig
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <title>customauth login page</title>
+  </head>
+  <body>
+    <h1>customauth login page</h1>
+    <p>
+      In this example you can log in with two accounts: <code>student</code> and <code>admin</code>.
+      In both cases, the password is the same as the username.
+    </p>
+    <form method="post" action="?">
+      <p>
+        Username:
+        <input type="text" name="username">
+      </p>
+      <p>
+        Password:
+        <input type="text" name="password">
+      </p>
+      <input type="hidden" name="ReturnTo" value="{{ returnTo|escape('html') }}">
+      <p><input type="submit" value="Log in"></p>
+    </form>
+{% if badUserPass == true %}
+    <p>!!! Bad username or password !!!</p>
+{% endif %}
+  </body>
+</html>
diff --git a/modules/customauth/www/assets/css/default.css b/modules/customauth/www/assets/css/default.css
new file mode 100644
index 0000000..f43f547
--- /dev/null
+++ b/modules/customauth/www/assets/css/default.css
@@ -0,0 +1,651 @@
+/* these styles are in the head of this page because this is a unique page */
+
+/* THE BIG GUYS */
+* {
+    margin: 0;
+    padding: 0;
+}
+
+body {
+    text-align: center;
+    /*padding: 10px 0;*/
+    background: #2a333d;
+    /*    background-image: url(icons/ssplogo-fish.png); */
+    /*    background-repeat: no-repeat; */
+    color: #333;
+    font: 83%/1.5 Roboto, tahoma, verdana, sans-serif;
+
+}
+
+.body-embed {
+    padding: 0;
+    background: #ffffff;
+    font: 83%/1.5 arial, tahoma, verdana, sans-serif;
+}
+
+img {
+    border: none;
+    display: block;
+}
+
+hr {
+    margin: 1em 0;
+    background: #eee;
+    height: 1px;
+    color: #eee;
+    border: none;
+    clear: both;
+}
+
+/* LINKS */
+a, a:link, a:link, a:link, a:hover {
+    text-decoration: none;
+    color: #777;
+    border-bottom: 1px dotted #ccc;
+    font-weight: normal;
+}
+
+a:link, a:visited {
+    text-decoration: none;
+    color: #777;
+    border-bottom: 1px dotted #ccc;
+    font-weight: normal;
+}
+
+.ui-tabs-nav a {
+    border: none ! important;
+    text-decoration: none;
+}
+
+a:visited {
+    color: #999;
+}
+
+a:hover, a:active {
+    color: #069;
+    text-decoration: none;
+    color: #333;
+    border-bottom: 1px solid #333;
+}
+
+#header a {
+    color: #fff;
+    text-decoration: none;
+}
+
+/* LISTS */
+ul {
+    margin: .3em 0 1.5em 2em;
+}
+
+ul.related {
+    margin-top: -1em;
+}
+
+li {
+    margin-left: 2em;
+}
+
+dt {
+    font-weight: bold;
+}
+
+#wrap {
+    background: #ffffff;
+    /*border: 1px solid #fff;*/
+    position: relative;
+    text-align: left;
+    /*margin: 20px 75px 2em 75px;*/
+    /*max-width: 950px;*/
+    width: 750px;
+    margin-left: auto;
+    margin-right: auto;
+    margin-top: 2%;
+    /*height: 650px;*/
+    /*height: auto;*/
+    box-shadow: 5px 5px 5px 5px #141c25;
+
+}
+
+#languagebar {
+    padding-left: 10px;
+    padding-right: 10px;
+}
+
+#languagebar a:link, #languagebar a:visited {
+    text-decoration: none;
+    color: #777;
+    border-bottom: 1px dotted #ccc;
+    font-weight: normal;
+}
+
+#languagebar a:hover {
+    text-decoration: none;
+    color: #333;
+    border-bottom: 1px solid #333;
+}
+
+#header {
+    background: #fe5258;
+    margin: 0px;
+    padding: 0 0 8px;
+    height: 140px;
+}
+
+#header h1 {
+    color: #fff;
+    font-size: 15px;
+    padding: 20px 20px 12px;
+    text-align: center;
+}
+
+h1 {
+    letter-spacing: 0px !important;
+}
+#content, #footer {
+    padding: 0 20px;
+}
+
+/* TYPOGRAPHY */
+p, ul, ol {
+    margin-top: 1em;
+}
+
+h1, h2, h3, h4, h5, h6 {
+    letter-spacing: -1px;
+    font-family: arial, verdana, sans-serif;
+    margin: 1.2em 0 .3em;
+    color: #000;
+    border-bottom: 1px solid #eee;
+    padding-bottom: .1em;
+}
+
+h1 {
+    font-size: 196%;
+    margin-top: 0;
+    border: none;
+}
+
+h2 {
+    font-size: 136%;
+}
+
+h3 {
+    font-size: 126%;
+}
+
+h4 {
+    font-size: 116%;
+    font-weight: bold;
+}
+
+h5 {
+    font-size: 106%;
+}
+
+h6 {
+    font-size: 96%;
+}
+
+input {
+    border: 1px solid #bbb;
+    border-radius: 3px;
+    padding: 10px;
+    line-height: 1.5em;
+    /*width: 60%;*/
+    margin: 4px;
+    color: #9d9d9d;
+    /*margin-left: 18%;*/
+    /*margin-right: 18%;*/
+}
+
+
+h1 a {
+    text-decoration: none;
+    border: none ! important;
+    color: white;
+}
+
+h1 a:hover {
+    border-bottom: 1px dotted #eee;
+}
+
+#content {
+    margin-top: 4em;
+
+}
+
+.old {
+    text-decoration: line-through;
+}
+
+dl dt {
+    color: #333;
+}
+
+dl dd {
+    color: #666;
+    margin-left: 3em;
+    /*    font-family: monospace; */
+}
+
+.efieldlist {
+    padding: .4em;
+    margin: .8em;
+    border-top: 1px solid #e6e6e6;
+    border-left: 1px solid #e6e6e6;
+}
+
+.efieldlist.warning {
+    background-color: #922;
+    border: 1px solid #333;
+    color: white;
+}
+
+.efieldlist.warning h5 {
+    color: white;
+}
+
+.efieldlist h5 {
+    font-weight: bold;
+    color: #200;
+    margin: .3em;
+}
+
+.trackidtext {
+    border: 1px dashed #aaa;
+    background: #eaeaea;
+    padding: .6em;
+    margin: .4em;
+}
+
+.trackidtext .trackid {
+    border: 1px solid #ccc;
+    background: #eee;
+    margin: .4em;
+    padding: .4em;
+    font-family: monospace;
+    font-size: large;
+}
+
+div.caution {
+    background-color: #FF9;
+    background-image: url('../themes/icons/experience/gtk-dialog-warning.48x48.png');
+    background-repeat: no-repeat;
+    border: thin solid #444;
+    padding: .2em .2em .2em 60px;
+    margin: 1em 0px 1em 0px;
+}
+
+th.rowtitle {
+    text-align: left;
+}
+
+.enablebox table {
+    border: 1px solid #eee;
+    margin-left: 1em;
+}
+
+.enablebox.mini table {
+    float: right;
+}
+
+.enablebox tr td {
+    padding: .5px 1em 1px .5em;
+    margin: 0px;
+}
+
+.enablebox {
+    font-size: 85%;
+}
+
+.enablebox tr.enabled td {
+    background: #eee;
+}
+
+.enablebox tr.disabled td {
+    background: #ccc;
+}
+
+.metadatabox {
+    overflow: scroll;
+    border: 1px solid #eee;
+    padding: 0.5em;
+    border-radius: 3px;
+}
+
+div.preferredidp {
+    border: 1px dashed #ccc;
+    background: #eee;
+    padding: 2px 2em 2px 2em;
+}
+
+table.modules {
+    border-collapse: collapse;
+}
+
+table.modules tr td {
+    border-bottom: 1px solid #ddd;
+}
+
+table.modules tr.even td {
+    background: #f0f0f0;
+}
+
+/* Attribute presentation in example page */
+table.attributes {
+    width: 100%;
+    margin: 0px;
+    border: 1px solid #bbb;
+    border-collapse: collapse;
+}
+
+table.attributes td.attrname {
+    text-align: right;
+}
+
+table.attributes tr.even td {
+    background: #eee;
+}
+
+table.attributes tr td {
+    border-bottom: 1px solid #bbb;
+    border-left: 0px;
+    border-right: 0px;
+    background: #fff;
+    padding-top: 5px;
+    padding-left: 1em;
+    padding-right: 1em;
+    vertical-align: top;
+}
+
+.attrvalue {
+    word-break: break-all;
+    word-wrap: break-word;
+}
+
+table#table_with_attributes tr:last-child td {
+    border-bottom: none;
+}
+
+fieldset.fancyfieldset {
+    margin: 2em 1em 1em 0px;
+    border: 1px solid #bbb;
+}
+
+fieldset.fancyfieldset legend {
+    margin-left: 2em;
+    padding: 3px 2em 3px 2em;
+    border: 1px solid #bbb;
+}
+
+div#confirmation input {
+    margin-top: .5em;
+    margin-bottom: .5em;
+}
+
+div#confirmation {
+    border: 1px solid #aaa;
+    background: #eee;
+    padding: .6em 1em .1em 1em;
+}
+
+caption {
+    display: none;
+}
+
+/* Left-to-Right CSS for RTL (Right to Left Support) */
+.float-r {
+    float: right;
+}
+
+.float-l {
+    float: left;
+}
+
+#mobile_remember_username, #mobile_remember_me {
+    display: none;
+}
+
+@media handheld, only screen and (max-width: 480px), only screen and (max-device-width: 480px) {
+    /*#header, #languagebar, #footer, .erroricon, .loginicon, .logintext,*/
+    /*#regular_remember_username, #regular_remember_me {*/
+    /*    display: none;*/
+    /*}*/
+
+    body {
+        font-size: 20px;
+    }
+
+    /*#wrap {*/
+    /*    margin: 0;*/
+    /*}*/
+
+    h1, h2, h3, h4 {
+        font-size: 110%;
+    }
+
+    #content {
+        margin-bottom: 10px;
+        padding: 0;
+        padding-left: 5px;
+    }
+
+    input[type="text"], input[type="password"] {
+        height: 1.5em;
+        font-size: 1em;
+    }
+
+    .youareadmin {
+        font-size: 50%;
+    }
+
+    #mobilesubmit, #mobile_remember_username, #mobile_remember_me {
+        display: table-row;
+    }
+
+}
+
+.btn, .btnaddonright {
+    border: 1px solid #47494a;
+    border-radius: 3px;
+    background-color: #3d3d3d !important;
+    background-image: linear-gradient(#575757, #2e2e2e);
+    text-align: center;
+    padding: 15px;
+    cursor: hand;
+    margin: 10px;
+    color: white;
+    margin-left: 18%;
+    margin-right: 18%;
+
+
+}
+
+.btn:hover, .btnaddonright:hover {
+    border-color: #ccc;
+    background-color: #ddd;
+    background-image: linear-gradient(#eee, #ddd);
+}
+
+.btn img,
+.btnaddonright img {
+    max-height: 15px;
+    max-width: 15px;
+}
+
+.topright {
+    position: absolute;
+    right: 2em;
+}
+
+.input-group {
+    display: table;
+}
+
+.input-group pre {
+    background: white;
+    position: relative;
+    width: 100%;
+    vertical-align: middle;
+    border: 1px solid #eee;
+    padding: 0.5em;
+    display: table-cell;
+}
+
+.input-group .btnaddonright {
+    position: relative;
+    display: inline-block;
+    border-bottom-left-radius: 0;
+    border-bottom-right-radius: 3px;
+    border-top-left-radius: 0;
+    border-top-right-radius: 3px;
+    border-left: none;
+}
+
+.input-group .btnaddonright:hover {
+    border-left: 1px solid #ccc;
+}
+
+.input-group .input-left {
+    border-bottom-left-radius: 3px;
+    border-bottom-right-radius: 0;
+    border-top-left-radius: 3px;
+    border-top-right-radius: 0;
+}
+
+.logintext {
+    text-align: left;
+    margin-right: 18%;
+    margin-left: 18%;
+    color: #777777;
+    font-size: 12px;
+    margin-top: 15px;
+}
+
+#footer {
+    /*background-color: #f2f2f2;*/
+    text-align: center;
+    font-size: 10px;
+    padding-bottom: 10px;
+    position: absolute;
+    bottom: 0;
+    width: 510px;
+
+}
+
+#submit_button {
+    width: 65%;
+    background-color: #3d3d3d !important;
+
+}
+
+form {
+
+    margin-bottom: 3%;
+}
+
+table {
+    width: 100%;
+}
+
+.romb {
+    background-color: #fe5258;
+    width: 30px;
+    height: 30px;
+    transform: rotate(45deg);
+    margin-left: 47%;
+    margin-right: 47%;
+    margin-top: -15px;
+    position: absolute;
+}
+
+@media handheld, only screen and (max-width: 800px), only screen and (max-device-width: 800px) {
+    #wrap {
+        width: 70%;
+        margin-left: 15%;
+        /*margin-right: 5%;*/
+
+    }
+
+    #footer {
+        width: 92%;
+    }
+
+    .content {
+        height: 100%;
+    }
+}
+
+@media handheld, only screen and (max-width: 600px), only screen and (max-device-width: 600px) {
+    #wrap {
+        /*height: 800px;*/
+        width: 100%;
+        margin-left: 0;
+        margin-top: 10%;
+    }
+
+    #footer {
+        width: 92%;
+    }
+}
+
+@media handheld, only screen and(min-width: 1024px) and (max-width: 1500px), only screen and (min-device-width: 1024px)and (max-device-width: 1500px) {
+    #wrap {
+        margin-left: 30%;
+    }
+}
+
+@media handheld, only screen and (width: 1024px), only screen and (device-width: 1024px) {
+    #wrap {
+        margin-left: 25%;
+    }
+}
+
+img {
+    width: 50%;
+    height: 50%;
+    margin-left: 25%;
+}
+
+.searchIcon {
+    padding: 0.5rem;
+}
+
+@media handheld, only screen and (max-width: 480px), only screen and (max-device-width: 480px) {
+    #header h1 {
+        margin-top: -10px;
+    }
+}
+
+@media handheld, only screen and(min-width: 300px) and (max-width: 400px), only screen and (min-device-width: 300px)and (max-device-width: 400px) {
+    #footer {
+        width: 88%;
+    }
+}
+
+input {
+    /*text-indent: 32px;*/
+}
+
+.icon_class #input_img {
+    position: absolute;
+    /*top:275px;*/
+    margin-top: 20px;
+    left: -15px;
+    width: 22px;
+    height: 22px;
+    float: left;
+
+}
+
+.icon_class #pass_img {
+    position: absolute;
+    /*top:340px;*/
+    margin-top: 20px;
+    left: -15px;
+    width: 20px;
+    height: 20px;
+    float: left;
+
+}
diff --git a/modules/customauth/www/assets/favicon.ico b/modules/customauth/www/assets/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..ddded01e615a2a4cf9ff0e335dd23e5e19d0f09c
GIT binary patch
literal 1150
zcmZQzU}Ruq5D);-3Je)63=Con3=A3!3=9Gc3=9ek5OD?^;Q#;s47e}@1A{OF149f0
z14AqW1A{m&b=bri7#MgsI5^(Ay1IV$@bLJ|&d&acfq_93n_6sQ3=9m8^78VZKYsl9
zAB2^ZltB7zv8lx-#=yY9!NS7w^ZNDc|1Vs)@Sl~H^&0~N0~a>6=wb{E4BWiDyqEO!
z^uD}){rdmgw{QQuxw(Dj;^KPEz`&q}t{%i^U|^77Vq&UhU|_J~<>j3n5)$(L*RNmy
zfB*jd|I3#z|EEr!`p@0n{U0A6-z5eH1}2amWX!|Eb1ytR{HL6p+-E5%sc#=XeE1Io
zxw*MNczAe@3knLJVPRnb`9ld=F9QRE5C;dxkAMIEfwlbo`xi{Z2x!=ufb?Lqn}LCW
zg`J)K+mk0x{+~Q~^8fDLyJ1?vw3U_BM+OFla0UhjX$A%cKae@-SU^CagO!!_H#ax;
zD^5<%&xZ~j0_z14J9qB<$IHw6jhUJG4-*s9Uj_ySIduIXJ~YWPGB7X%ii(PUJ9X+5
zNIMvB-n{w0g@wg;Zf@=#kXk~RnVC73ot^#1?c2BiKX~xqKN}m{FK%w`4h9AWHbQzq
z@}Tt2$;tWs^XJe1fByXWUr<mG6z=LEIbtva1B1DQgv8g!k01ZPd-v{tIXSs6pfpX4
zUYHsI0fD)koSdJyxw+r*^Yiz^#HmX&F#MB8;6MBf3_sWz7!H82Jp;o55N2m!_`}Y?
P@Q)v%4y5P*e+C8s6vcIT

literal 0
HcmV?d00001

diff --git a/modules/customauth/www/assets/script.js b/modules/customauth/www/assets/script.js
new file mode 100644
index 0000000..a195c24
--- /dev/null
+++ b/modules/customauth/www/assets/script.js
@@ -0,0 +1,44 @@
+/**
+ * Set focus to the element with the given id.
+ *
+ * @param id  The id of the element which should receive focus.
+ */
+function SimpleSAML_focus(id)
+{
+    element = document.getElementById(id);
+    if (element != null) {
+        element.focus();
+    }
+}
+
+
+/**
+ * Show the given DOM element.
+ *
+ * @param id  The id of the element which should be shown.
+ */
+function SimpleSAML_show(id)
+{
+    element = document.getElementById(id);
+    if (element == null) {
+        return;
+    }
+
+    element.style.display = 'block';
+}
+
+
+/**
+ * Hide the given DOM element.
+ *
+ * @param id  The id of the element which should be hidden.
+ */
+function SimpleSAML_hide(id)
+{
+    element = document.getElementById(id);
+    if (element == null) {
+        return;
+    }
+
+    element.style.display = 'none';
+}
diff --git a/modules/customauth/www/authpage.php b/modules/customauth/www/authpage.php
new file mode 100644
index 0000000..09f045f
--- /dev/null
+++ b/modules/customauth/www/authpage.php
@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * This page serves as a dummy login page.
+ *
+ * Note that we don't actually validate the user in this example. This page
+ * just serves to make the example work out of the box.
+ *
+ * @package SimpleSAMLphp
+ */
+
+if (!isset($_REQUEST['ReturnTo'])) {
+    die('Missing ReturnTo parameter.');
+}
+
+$returnTo = \SimpleSAML\Utils\HTTP::checkURLAllowed($_REQUEST['ReturnTo']);
+
+/**
+ * The following piece of code would never be found in a real authentication page. Its
+ * purpose in this example is to make this example safer in the case where the
+ * administrator of the IdP leaves the exampleauth-module enabled in a production
+ * environment.
+ *
+ * What we do here is to extract the $state-array identifier, and check that it belongs to
+ * the exampleauth:External process.
+ */
+if (!preg_match('@State=(.*)@', $returnTo, $matches)) {
+    die('Invalid ReturnTo URL for this example.');
+}
+
+/**
+ * The loadState-function will not return if the second parameter does not
+ * match the parameter passed to saveState, so by now we know that we arrived here
+ * through the customauth:External authentication page.
+ */
+\SimpleSAML\Auth\State::loadState(urldecode($matches[1]), 'customauth:External');
+
+// our list of users.
+$raw_users = json_decode(file_get_contents('/opt/simplesamlphp/config/logins.json'), true);
+$attributes = json_decode(file_get_contents('/opt/simplesamlphp/config/attributes.json'), true);
+
+$users = [];
+$explanations = [];
+$displays = [];
+foreach ($raw_users as $user => $values) {
+    $explanations[$user] = $values['explanation'];
+    $displays[$user] = $values['display'];
+    unset($values['explanation']);
+    unset($values['display']);
+    $users[$user] = $values;
+}
+
+// time to handle login responses; since this is a dummy example, we accept any data
+$badUserPass = false;
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+    $username = (string) $_REQUEST['username'];
+
+    $user_aa = $users[$username];
+
+    if (!session_id()) {
+        // session_start not called before. Do it here.
+        session_start();
+    }
+
+    foreach ($user_aa as $key => $value) {
+        if (isset($_POST['keys'][$key])) {
+            $_SESSION['attributes'][$key] = $value;
+        }
+    }
+    if (isset($_POST['error'])) {
+	$_SESSION['ErrorResponse'] = TRUE;
+    }
+
+    \SimpleSAML\Utils\HTTP::redirectTrustedURL($returnTo);
+}
+
+// if we get this far, we need to show the login page to the user
+$config = \SimpleSAML\Configuration::getInstance();
+$t = new \SimpleSAML\XHTML\Template($config, 'customauth:authenticate.tpl.php');
+$t->data['users'] = $users;
+$t->data['attributes'] = $attributes;
+$t->data['explanations'] = $explanations;
+$t->data['displays'] = $displays;
+$t->data['returnTo'] = $returnTo;
+$t->show();
diff --git a/modules/customauth/www/redirecttest.php b/modules/customauth/www/redirecttest.php
new file mode 100644
index 0000000..eb33355
--- /dev/null
+++ b/modules/customauth/www/redirecttest.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * Request handler for redirect filter test.
+ *
+ * @author Olav Morken, UNINETT AS.
+ * @package SimpleSAMLphp
+ */
+
+if (!array_key_exists('StateId', $_REQUEST)) {
+    throw new \SimpleSAML\Error\BadRequest('Missing required StateId query parameter.');
+}
+
+/** @var array $state */
+$state = \SimpleSAML\Auth\State::loadState($_REQUEST['StateId'], 'customauth:redirectfilter-test');
+
+$state['Attributes']['RedirectTest2'] = ['OK'];
+
+\SimpleSAML\Auth\ProcessingChain::resumeProcessing($state);
diff --git a/modules/customauth/www/resume.php b/modules/customauth/www/resume.php
new file mode 100644
index 0000000..8b52c4a
--- /dev/null
+++ b/modules/customauth/www/resume.php
@@ -0,0 +1,14 @@
+<?php
+
+/**
+ * This page serves as the point where the user's authentication
+ * process is resumed after the login page.
+ *
+ * It simply passes control back to the class.
+ *
+ * @package SimpleSAMLphp
+ */
+
+namespace SimpleSAML\Module\customauth\Auth\Source;
+
+External::resume();
-- 
GitLab