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