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