From 13d477c03725de034ee9d8768177a22d8a36b096 Mon Sep 17 00:00:00 2001 From: Olav Morken <olav.morken@uninett.no> Date: Fri, 30 Jul 2010 08:40:15 +0000 Subject: [PATCH] exampleauth:External: Example of integration with external authentication. git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@2460 44740490-163a-0410-bde0-09ae8108e29a --- config-templates/authsources.php | 10 + .../exampleauth/lib/Auth/Source/External.php | 274 ++++++++++++++++++ modules/exampleauth/www/authpage.php | 124 ++++++++ modules/exampleauth/www/resume.php | 12 + 4 files changed, 420 insertions(+) create mode 100644 modules/exampleauth/lib/Auth/Source/External.php create mode 100644 modules/exampleauth/www/authpage.php create mode 100644 modules/exampleauth/www/resume.php diff --git a/config-templates/authsources.php b/config-templates/authsources.php index 68ccd64e1..67ffb3491 100644 --- a/config-templates/authsources.php +++ b/config-templates/authsources.php @@ -63,6 +63,16 @@ $config = array( ), */ + /* + // This authentication source serves as an example of integration with an + // external authentication engine. Take a look at the comment in the beginning + // of modules/exampleauth/lib/Auth/Source/External.php for a description of + // how to adjust it to your own site. + 'example-external' => array( + 'exampleauth:External', + ), + */ + /* 'yubikey' => array( 'authYubiKey:YubiKey', diff --git a/modules/exampleauth/lib/Auth/Source/External.php b/modules/exampleauth/lib/Auth/Source/External.php new file mode 100644 index 000000000..fee9489aa --- /dev/null +++ b/modules/exampleauth/lib/Auth/Source/External.php @@ -0,0 +1,274 @@ +<?php + +/** + * Example external authentication source. + * + * This class is an example 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/exampleauth/www/resume.php to their corresponding + * location in the new module. + * 4. Replace all occurrences of "exampleauth" 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 + * @version $Id$ + */ +class sspmod_exampleauth_Auth_Source_External extends SimpleSAML_Auth_Source { + + /** + * 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() { + + /* + * 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(); + } + + if (!isset($_SESSION['uid'])) { + /* The user isn't authenticated. */ + return NULL; + } + + /* + * Find the attributes for the user. + * Note that all attributes in simpleSAMLphp are multivalued, so we need + * to store them as arrays. + */ + + $attributes = array( + 'uid' => array($_SESSION['uid']), + 'displayName' => array($_SESSION['name']), + 'mail' => array($_SESSION['mail']), + ); + + /* Here we generate a multivalued attribute based on the account type. */ + $attributes['eduPersonAffiliation'] = array( + $_SESSION['type'], /* In this example, either 'student' or 'employee'. */ + 'member', + ); + + return $attributes; + } + + + /** + * Log in using an external authentication helper. + * + * @param array &$state Information about the current authentication. + */ + 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['exampleauth: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 = SimpleSAML_Auth_State::saveState($state, 'exampleauth:External'); + + /* + * Now we generate an 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 = SimpleSAML_Module::getModuleURL('exampleauth/resume.php', array( + '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 = SimpleSAML_Module::getModuleURL('exampleauth/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. + */ + SimpleSAML_Utilities::redirect($authPage, array( + '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. + * + * @param array &$state The authentication state. + */ + 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 SimpleSAML_Error_BadRequest('Missing "State" parameter.'); + } + $stateId = (string)$_REQUEST['State']; + + /* + * Once again, note the second parameter to the loadState function. This must + * match the string we used in the saveState-call above. + */ + $state = SimpleSAML_Auth_State::loadState($stateId, 'exampleauth:External'); + + /* + * Now we have the $state-array, and can use it to locate the authentication + * source. + */ + $source = SimpleSAML_Auth_Source::getById($state['exampleauth: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 SimpleSAML_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 SimpleSAML_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 SimpleSAML_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; + SimpleSAML_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. + */ + 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['uid']); + + /* + * 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/exampleauth/www/authpage.php b/modules/exampleauth/www/authpage.php new file mode 100644 index 000000000..21a284b22 --- /dev/null +++ b/modules/exampleauth/www/authpage.php @@ -0,0 +1,124 @@ +<?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 + * @version $Id$ + */ + +if (!isset($_REQUEST['ReturnTo'])) { + die('Missing ReturnTo parameter.'); +} + +$returnTo = $_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.'); +} +$stateId = urldecode($matches[1]); +SimpleSAML_Auth_State::loadState($stateId, 'exampleauth:External'); + +/* + * 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 exampleauth:External authentication page. + */ + + +/* + * Our list of users. + */ +$users = array( + 'student' => array( + 'password' => 'student', + 'uid' => 'student', + 'name' => 'Student Name', + 'mail' => 'somestudent@example.org', + 'type' => 'student', + ), + 'admin' => array( + 'password' => 'admin', + 'uid' => 'admin', + 'name' => 'Admin Name', + 'mail' => 'someadmin@example.org', + 'type' => 'employee', + ), +); + + +/* + * 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']; + $password = (string)$_REQUEST['password']; + + if (!isset($users[$username]) || $users[$username]['password'] !== $password) { + $badUserPass = TRUE; + } else { + + $user = $users[$username]; + + if (!session_id()) { + /* session_start not called before. Do it here. */ + session_start(); + } + + $_SESSION['uid'] = $user['uid']; + $_SESSION['name'] = $user['name']; + $_SESSION['mail'] = $user['mail']; + $_SESSION['type'] = $user['type']; + + header('Location: ' . $returnTo); + exit(); + } +} + + +/* + * If we get this far, we need to show the login page to the user. + */ +?><!DOCTYPE html> +<html> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> +<title>exampleauth login page</title> +</head> +<body> +<h1>exampleauth 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> +<?php if ($badUserPass) { ?> +<p>Bad username or password.</p> +<?php } ?> +<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="<?php echo htmlspecialchars($returnTo); ?>"> +<p><input type="submit" value="Log in"></p> +</form> +</body> +</html> diff --git a/modules/exampleauth/www/resume.php b/modules/exampleauth/www/resume.php new file mode 100644 index 000000000..de0597abb --- /dev/null +++ b/modules/exampleauth/www/resume.php @@ -0,0 +1,12 @@ +<?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 + * @version $Id$ + */ +sspmod_exampleauth_Auth_Source_External::resume(); -- GitLab