From c1c2c004eb46ffabc3a61f3196d31a9600b34edc Mon Sep 17 00:00:00 2001
From: Olav Morken <olav.morken@uninett.no>
Date: Mon, 18 Aug 2008 11:36:30 +0000
Subject: [PATCH] Support authentication source modules.

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@810 44740490-163a-0410-bde0-09ae8108e29a
---
 config-templates/authsources.php              |  26 +++
 dictionaries/errors.php                       |   6 +
 lib/SimpleSAML/Auth/Default.php               | 114 ++++++++++++
 lib/SimpleSAML/Auth/Source.php                | 140 ++++++++++++++
 lib/SimpleSAML/Auth/State.php                 | 171 ++++++++++++++++++
 modules/core/lib/Auth/UserPassBase.php        | 136 ++++++++++++++
 .../core/templates/default/loginuserpass.php  |  61 +++++++
 modules/core/www/loginuserpass.php            |  46 +++++
 modules/exampleauth/default-disable           |   3 +
 .../exampleauth/lib/Auth/Source/Static.php    |  60 ++++++
 .../exampleauth/lib/Auth/Source/UserPass.php  |  93 ++++++++++
 www/saml2/idp/SSOService.php                  |  35 +++-
 www/shib13/idp/SSOService.php                 |  36 +++-
 13 files changed, 912 insertions(+), 15 deletions(-)
 create mode 100644 config-templates/authsources.php
 create mode 100644 lib/SimpleSAML/Auth/Default.php
 create mode 100644 lib/SimpleSAML/Auth/Source.php
 create mode 100644 lib/SimpleSAML/Auth/State.php
 create mode 100644 modules/core/lib/Auth/UserPassBase.php
 create mode 100644 modules/core/templates/default/loginuserpass.php
 create mode 100644 modules/core/www/loginuserpass.php
 create mode 100644 modules/exampleauth/default-disable
 create mode 100644 modules/exampleauth/lib/Auth/Source/Static.php
 create mode 100644 modules/exampleauth/lib/Auth/Source/UserPass.php

diff --git a/config-templates/authsources.php b/config-templates/authsources.php
new file mode 100644
index 000000000..85aec95bb
--- /dev/null
+++ b/config-templates/authsources.php
@@ -0,0 +1,26 @@
+<?php
+
+$config = array(
+
+	'example-static' => array(
+		'exampleauth:Static',
+		'uid' => 'testuser',
+		'eduPersonAffiliation' => array('member', 'employee'),
+		'cn' => array('Test User'),
+	),
+
+	'example-userpass' => array(
+		'exampleauth:UserPass',
+		'student:studentpass' => array(
+			'uid' => 'student',
+			'eduPersonAffiliation' => array('member', 'student'),
+		),
+		'employee:employeepass' => array(
+			'uid' => 'employee',
+			'eduPersonAffiliation' => array('member', 'employee'),
+		),
+	),
+
+);
+
+?>
\ No newline at end of file
diff --git a/dictionaries/errors.php b/dictionaries/errors.php
index 556a0b5e4..56a88202a 100644
--- a/dictionaries/errors.php
+++ b/dictionaries/errors.php
@@ -1036,6 +1036,12 @@ $lang = array(
 		'nn' => 'Det er ein feil i spørringa etter denne sida.  Grunnen til dette er %REASON%',
 		'en' => 'There is an error in the request to this page. The reason was: %REASON%',
 	),
+	'title_WRONGUSERPASS' => array (
+		'en' => 'Incorrect username or password',
+	),
+	'descr_WRONGUSERPASS' => array (
+		'en' => 'Either no user with the given username could be found, or the password you gave was wrong. Please check the username and try again.',
+	),
 
 );
 
diff --git a/lib/SimpleSAML/Auth/Default.php b/lib/SimpleSAML/Auth/Default.php
new file mode 100644
index 000000000..101365891
--- /dev/null
+++ b/lib/SimpleSAML/Auth/Default.php
@@ -0,0 +1,114 @@
+<?php
+
+/**
+ * Implements the default behaviour for authentication.
+ *
+ * This class contains an implementation for default behaviour when authenticating. It will
+ * save the session information it got from the authentication client in the users session.
+ *
+ * @author Olav Morken, UNINETT AS.
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SimpleSAML_Auth_Default {
+
+
+	/**
+	 * Start authentication.
+	 *
+	 * This function never returns.
+	 *
+	 * @param string $authId  The identifier of the authentication source.
+	 * @param string $returnURL  The URL we should direct the user to after authentication.
+	 * @param string|NULL $errorURL  The URL we should direct the user to after failed authentication.
+	 *                               Can be NULL, in which case a standard error page will be shown.
+	 * @param array $hints  Extra information about the login. Different authentication requestors may
+	 *                      provide different information. Optional, will default to an empty array.
+	 */
+	public static function initLogin($authId, $returnURL, $errorURL = NULL, $hints = array()) {
+		assert('is_string($authId)');
+		assert('is_string($returnURL)');
+		assert('is_string($errorURL) || is_null($errorURL)');
+		assert('is_array($hints)');
+
+		$state = array(
+			'SimpleSAML_Auth_Default.id' => $authId,
+			'SimpleSAML_Auth_Default.ReturnURL' => $returnURL,
+			'SimpleSAML_Auth_Default.ErrorURL' => $errorURL,
+			'LoginCompletedHandler' => array(get_class(), 'loginCompleted'),
+			'LoginFailedHandler' => array(get_class(), 'loginFailed'),
+			);
+
+		if (array_key_exists('SPMetadata', $hints)) {
+			$state['SPMetadata'] = $hints['SPMetadata'];
+		}
+		if (array_key_exists('IdPMetadata', $hints)) {
+			$state['IdPMetadata'] = $hints['IdPMetadata'];
+		}
+
+		$as = SimpleSAML_Auth_Source::getById($authId);
+		if ($as === NULL) {
+			throw new Exception('Invalid authentication source: ' . $authId);
+		}
+
+		$as->authenticate($state);
+		self::loginCompleted($state);
+	}
+
+
+	/**
+	 * Called when a login operation has finished.
+	 *
+	 * @param array $state  The state after the login.
+	 */
+	public static function loginCompleted($state) {
+		assert('is_array($state)');
+		assert('array_key_exists("SimpleSAML_Auth_Default.ReturnURL", $state)');
+		assert('array_key_exists("SimpleSAML_Auth_Default.id", $state)');
+		assert('array_key_exists("Attributes", $state)');
+
+		$returnURL = $state['SimpleSAML_Auth_Default.ReturnURL'];
+
+		/* Save session state. */
+		$session = SimpleSAML_Session::getInstance();
+		$session->doLogin($state['SimpleSAML_Auth_Default.id']);
+		$session->setAttributes($state['Attributes']);
+		if(array_key_exists('Expires', $state)) {
+			$session->setSessionDuration($state['Expires'] - time());
+		}
+
+		/* Redirect... */
+		SimpleSAML_Utilities::redirect($returnURL);
+	}
+
+
+	/**
+	 * Called when a login operation fails.
+	 *
+	 * @param array $state  The state array.
+	 * @param string $statusCode  A status code, in the form of an URI, which indicates why the login failed.
+	 * @param string $statusMessage  A text which describes why the login failed.
+	 */
+	public static function loginFailed($state, $statusCode, $statusMessage) {
+		assert('is_array($state)');
+		assert('array_key_exists("SimpleSAML_Auth_Default.ErrorURL", $state)');
+		assert('is_string($statusCode)');
+		assert('is_string($statusMessage)');
+
+		$url = $state['SimpleSAML_Auth_Default.ErrorURL'];
+		if ($url === NULL) {
+			/* We don't have an error handler. Show an error page. */
+			SimpleSAML_Utilities::fatalError($session->getTrackID(), 'RESPONSESTATUSNOSUCCESS',
+				new Exception('StatusCode = \'' . $statusCode . '\'; StatusMessage = \'' .
+					$statusMessage . '\';'));
+		}
+
+		$info = array('StatusCode' => $statusCode, 'StatusMessage' => $statusMessage);
+
+		/* Redirect... */
+		SimpleSAML_Utilities::redirect($url, $info);
+	}
+
+}
+
+?>
\ No newline at end of file
diff --git a/lib/SimpleSAML/Auth/Source.php b/lib/SimpleSAML/Auth/Source.php
new file mode 100644
index 000000000..2c2f8251c
--- /dev/null
+++ b/lib/SimpleSAML/Auth/Source.php
@@ -0,0 +1,140 @@
+<?php
+
+/**
+ * This class defines a base class for authentication source.
+ *
+ * An authentication source is any system which somehow authenticate the user.
+ *
+ * @author Olav Morken, UNINETT AS.
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+abstract class SimpleSAML_Auth_Source {
+
+
+	/**
+	 * The authentication source identifier.
+	 *
+	 * This identifier can be used to look up this object, for example when returning from a login form.
+	 */
+	protected $authId;
+
+
+	/**
+	 * Constructor for an authentication source.
+	 *
+	 * Any authentication source which implements its own constructor must call this
+	 * constructor first.
+	 *
+	 * @param array $info  Information about this authentication source.
+	 * @param array &$config  Configuration for this authentication source.
+	 */
+	public function __construct($info, &$config) {
+		assert('is_array($info)');
+		assert('is_array($config)');
+
+		assert('array_key_exists("AuthId", $info)');
+		$this->authId = $info['AuthId'];
+	}
+
+
+	/**
+	 * Process a request.
+	 *
+	 * If an authentication source returns from this function, it is assumed to have
+	 * authenticated the user, and should have set elements in $state with the attributes
+	 * of the user.
+	 *
+	 * If the authentication process requires additional steps which make it impossible to
+	 * complete before returning from this function, the authentication source should
+	 * save the state, and at a later stage, load the state, update it with the authentication
+	 * information about the user, and call completeAuth with the state array.
+	 *
+	 * @param array &$state  Information about the current authentication.
+	 */
+	abstract public function authenticate(&$state);
+
+
+	/**
+	 * Complete authentication.
+	 *
+	 * This function should be called if authentication has completed. It will never return,
+	 * except in the case of exceptions. Exceptions thrown from this page should not be caught,
+	 * but should instead be passed to the top-level exception handler.
+	 *
+	 * @param array &$state  Information about the current authentication.
+	 */
+	public static function completeAuth(&$state) {
+		assert('is_array($state)');
+		assert('array_key_exists("LoginCompletedHandler", $state)');
+
+		SimpleSAML_Auth_State::deleteState($state);
+
+		$func = $state['LoginCompletedHandler'];
+		assert('is_callable($func)');
+
+		call_user_func($func, $state);
+		assert(FALSE);
+	}
+
+
+	/**
+	 * Create authentication source object from configuration array.
+	 *
+	 * This function takes an array with the configuration for an authentication source object,
+	 * and returns the object.
+	 *
+	 * @param string $authId  The authentication source identifier.
+	 * @param array $config  The configuration.
+	 * @return SimpleSAML_Auth_Source  The parsed authentication source.
+	 */
+	private static function parseAuthSource($authId, $config) {
+		assert('is_string($authId)');
+		assert('is_array($config)');
+
+		if (!array_key_exists(0, $config) || !is_string($config[0])) {
+			throw new Exception('Invalid authentication source \'' . $authId .
+				'\': First element must be a string which identifies the authentication source.');
+		}
+
+		$className = SimpleSAML_Module::resolveClass($config[0], 'Auth_Source',
+			'SimpleSAML_Auth_Source');
+
+		$info = array('AuthId' => $authId);
+		unset($config[0]);
+		return new $className($info, $config);
+	}
+
+
+	/**
+	 * Retrieve authentication source.
+	 *
+	 * This function takes an id of an authentication source, and returns the
+	 * AuthSource object.
+	 *
+	 * @param string $authId  The authentication source identifier.
+	 * @return SimpleSAML_Auth_Source|NULL  The AuthSource object, or NULL if no authentication
+	 *     source with the given identifier is found.
+	 */
+	public static function getById($authId) {
+		assert('is_string($authId)');
+
+		/* For now - load and parse config file. */
+		$globalConfig = SimpleSAML_Configuration::getInstance();
+		$config = $globalConfig->copyFromBase('authsources', 'authsources.php');
+
+		$authConfig = $config->getValue($authId, NULL);
+		if ($authConfig === NULL) {
+			return NULL;
+		}
+
+		if (!is_array($authConfig)) {
+			throw new Exception('Invalid configuration for authentication source \'' . $authId . '\'.');
+		}
+
+		return self::parseAuthSource($authId, $authConfig);
+	}
+
+}
+
+?>
\ No newline at end of file
diff --git a/lib/SimpleSAML/Auth/State.php b/lib/SimpleSAML/Auth/State.php
new file mode 100644
index 000000000..8c16bc5ea
--- /dev/null
+++ b/lib/SimpleSAML/Auth/State.php
@@ -0,0 +1,171 @@
+<?php
+
+/**
+ * This is a helper class for saving and loading state information.
+ *
+ * The state must be an associative array. This class will add additional keys to this
+ * array. These keys will always start with 'SimpleSAML_Auth_State.'.
+ *
+ * It is also possible to add a restart URL to the state. If state information is lost, for
+ * example because it timed out, or the user loaded a bookmarked page, the loadState function
+ * will redirect to this URL. To use this, set $state[SimpleSAML_Auth_State::RESTART] to this
+ * URL.
+ *
+ * Both the saveState and the loadState function takes in a $stage parameter. This parameter is
+ * a security feature, and is used to prevent the user from taking a state saved one place and
+ * using it as input a different place.
+ *
+ * The $stage parameter must be a unique string. To maintain uniqueness, it must be on the form
+ * "<classname>.<identifier>" or "<module>:<identifier>".
+ *
+ * @author Olav Morken, UNINETT AS.
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SimpleSAML_Auth_State {
+
+
+	/**
+	 * The index in the state array which contains the identifier.
+	 */
+	const ID = 'SimpleSAML_Auth_State.id';
+
+
+	/**
+	 * The index in the state array which contains the current stage.
+	 */
+	const STAGE = 'SimpleSAML_Auth_State.stage';
+
+
+	/**
+	 * The index in the state array which contains the restart URL.
+	 */
+	const RESTART = 'SimpleSAML_Auth_State.restartURL';
+
+
+	/**
+	 * Save the state.
+	 *
+	 * This function saves the state, and returns an id which can be used to
+	 * retrieve it later. It will also update the $state array with the identifier.
+	 *
+	 * @param array &$state  The login request state.
+	 * @param string $stage  The current stage in the login process.
+	 * @return string  Identifier which can be used to retrieve the state later.
+	 */
+	public static function saveState(&$state, $stage) {
+		assert('is_array($state)');
+		assert('is_string($stage)');
+
+		/* Save stage. */
+		$state[self::STAGE] = $stage;
+
+		if (!array_key_exists(self::ID, $state)) {
+			$state[self::ID] = SimpleSAML_Utilities::generateID();
+		}
+
+		$id = $state[self::ID];
+
+		/* Embed the restart URL in the state identifier, if it is available. */
+		if (array_key_exists(self::RESTART, $state)) {
+			assert('is_string($state[self::RESTART])');
+			$return = $id . ':' . $state[self::RESTART];
+		} else {
+			$return = $id;
+		}
+
+		$serializedState = serialize($state);
+
+		$session = SimpleSAML_Session::getInstance();
+		$session->setData('SimpleSAML_Auth_State', $id, $serializedState, 60*60);
+
+		return $return;
+	}
+
+
+	/**
+	 * Retrieve saved state.
+	 *
+	 * This function retrieves saved state information. If the state information has been lost,
+	 * it will attempt to restart the request by calling the restart URL which is embedded in the
+	 * state information. If there is no restart information available, an exception will be thrown.
+	 *
+	 * @param string $id  State identifier (with embedded restart information).
+	 * @param string $stage  The stage the state should have been saved in.
+	 * @return array  State information.
+	 */
+	public static function loadState($id, $stage) {
+		assert('is_string($id)');
+		assert('is_string($stage)');
+
+		$tmp = explode(':', $id, 2);
+		$id = $tmp[0];
+		if (count($tmp) === 2) {
+			$restartURL = $tmp[1];
+		} else {
+			$restartURL = NULL;
+		}
+
+		$session = SimpleSAML_Session::getInstance();
+		$state = $session->getData('SimpleSAML_Auth_State', $id);
+
+		if ($state === NULL) {
+			/* Could not find saved data. Attempt to restart. */
+
+			if ($restartURL === NULL) {
+				throw new Exception('State information lost, and no way to restart the request.');
+			}
+
+			SimpleSAML_Utilities::redirect($restartURL);
+		}
+
+		$state = unserialize($state);
+		assert('is_array($state)');
+		assert('array_key_exists(self::ID, $state)');
+		assert('array_key_exists(self::STAGE, $state)');
+
+		/* Verify stage. */
+		if ($state[self::STAGE] !== $stage) {
+			/* This could be a user trying to bypass security, but most likely it is just
+			 * someone using the back-button in the browser. We try to restart the
+			 * request if that is possible. If not, show an error.
+			 */
+
+			$msg = 'Wrong stage in state. Was \'' . $state[self::STAGE] .
+				'\', shoud be \'' . $stage . '\'.';
+
+			SimpleSAML_Logger::warning($msg);
+
+			if ($restartURL === NULL) {
+				throw new Exception($msg);
+			}
+
+			SimpleSAML_Utilities::redirect($restartURL);
+		}
+
+		return $state;
+	}
+
+
+	/**
+	 * Delete state.
+	 *
+	 * This function deletes the given state to prevent the user from reusing it later.
+	 *
+	 * @param array &$state  The state which should be deleted.
+	 */
+	public static function deleteState(&$state) {
+		assert('is_array($state)');
+
+		if (!array_key_exists(self::ID, $state)) {
+			/* This state hasn't been saved. */
+			return;
+		}
+
+		$session = SimpleSAML_Session::getInstance();
+		$session->deleteData('SimpleSAML_Auth_State', $state[self::ID]);
+	}
+
+}
+
+?>
\ No newline at end of file
diff --git a/modules/core/lib/Auth/UserPassBase.php b/modules/core/lib/Auth/UserPassBase.php
new file mode 100644
index 000000000..01a1b8f3a
--- /dev/null
+++ b/modules/core/lib/Auth/UserPassBase.php
@@ -0,0 +1,136 @@
+<?php
+
+/**
+ * Helper class for username/password authentication.
+ *
+ * This helper class allows for implementations of username/password authentication by
+ * implementing a single function: login($username, $password)
+ *
+ * @author Olav Morken, UNINETT AS.
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+abstract class sspmod_core_Auth_UserPassBase extends SimpleSAML_Auth_Source {
+
+
+	/**
+	 * The string used to identify our states.
+	 */
+	const STAGEID = 'sspmod_core_Auth_UserPassBase.state';
+
+
+	/**
+	 * The key of the AuthId field in the state.
+	 */
+	const AUTHID = 'sspmod_core_Auth_UserPassBase.AuthId';
+
+
+	/**
+	 * Constructor for this authentication source.
+	 *
+	 * All subclasses who implement their own constructor must call this constructor before
+	 * using $config for anything.
+	 *
+	 * @param array $info  Information about this authentication source.
+	 * @param array &$config  Configuration for this authentication source.
+	 */
+	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);
+	}
+
+
+	/**
+	 * Initialize login.
+	 *
+	 * This function saves the information about the login, and redirects to a
+	 * login page.
+	 *
+	 * @param array &$state  Information about the current authentication.
+	 */
+	public function authenticate(&$state) {
+		assert('is_array($state)');
+
+		/* We are going to need the authId in order to retrieve this authentication source later. */
+		$state[self::AUTHID] = $this->authId;
+
+		$id = SimpleSAML_Auth_State::saveState($state, self::STAGEID);
+
+		$url = SimpleSAML_Module::getModuleURL('core/loginuserpass.php');
+		SimpleSAML_Utilities::redirect($url, array('AuthState' => $id));
+	}
+
+
+	/**
+	 * 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/error. 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 user's attributes.
+	 */
+	abstract protected function login($username, $password);
+
+
+	/**
+	 * Handle login request.
+	 *
+	 * This function is used by the login form (core/www/loginuserpass.php) when the user
+	 * enters a username and password. On success, it will not return. On wrong
+	 * username/password failure, it will return the error code. Other failures will throw an
+	 * exception.
+	 *
+	 * @param string $authStateId  The identifier of the authentication state.
+	 * @param string $username  The username the user wrote.
+	 * @param string $password  The password the user wrote.
+	 * @return string  Error code in the case of an error.
+	 */
+	public static function handleLogin($authStateId, $username, $password) {
+		assert('is_string($authStateId)');
+		assert('is_string($username)');
+		assert('is_string($password)');
+
+		/* Retrieve the authentication state. */
+		$state = SimpleSAML_Auth_State::loadState($authStateId, self::STAGEID);
+
+		/* Find authentication source. */
+		assert('array_key_exists(self::AUTHID, $state)');
+		$source = SimpleSAML_Auth_Source::getById($state[self::AUTHID]);
+		if ($source === NULL) {
+			throw new Exception('Could not find authentication source with id ' . $state[self::AUTHID]);
+		}
+
+
+		try {
+			/* Attempt to log in. */
+			$attributes = $source->login($username, $password);
+		} catch (SimpleSAML_Error_Error $e) {
+			/* An error occured during login. Check if it is because of the wrong
+			 * username/password - if it is, we pass that error up to the login form,
+			 * if not, we let the generic error handler deal with it.
+			 */
+			if ($e->getErrorCode() === 'WRONGUSERPASS') {
+				return 'WRONGUSERPASS';
+			}
+
+			/* Some other error occured. Rethrow exception and let the generic error
+			 * handler deal with it.
+			 */
+			throw $e;
+		}
+
+		$state['Attributes'] = $attributes;
+		SimpleSAML_Auth_Source::completeAuth($state);
+	}
+
+}
+
+?>
\ No newline at end of file
diff --git a/modules/core/templates/default/loginuserpass.php b/modules/core/templates/default/loginuserpass.php
new file mode 100644
index 000000000..810abda1f
--- /dev/null
+++ b/modules/core/templates/default/loginuserpass.php
@@ -0,0 +1,61 @@
+<?php
+$this->data['icon'] = 'lock.png';
+$this->data['header'] = $this->t('{login:user_pass_header}');
+
+if (strlen($this->data['username']) > 0) {
+	$this->data['autofocus'] = 'password';
+} else {
+	$this->data['autofocus'] = 'username';
+}
+$this->includeAtTemplateBase('includes/header.php');
+
+?>
+<div id="content">
+
+<?php
+if ($this->data['errorcode'] !== NULL) {
+?>
+	<div style="border-left: 1px solid #e8e8e8; border-bottom: 1px solid #e8e8e8; background: #f5f5f5">
+		<img src="/<?php echo $this->data['baseurlpath']; ?>resources/icons/bomb.png" style="float: left; margin: 15px " />
+		<h2><?php echo $this->t('{login:error_header}'); ?></h2>
+		<p><b><?php echo $this->t('{errors:title_' . $this->data['errorcode'] . '}'); ?></b></p>
+		<p><?php echo $this->t('{errors:descr_' . $this->data['errorcode'] . '}'); ?></p>
+	</div>
+<?php
+}
+?>
+	<h2 style="break: both"><?php echo $this->t('{login:user_pass_header}'); ?></h2>
+
+	<p><?php echo $this->t('{login:user_pass_text}'); ?></p>
+
+	<form action="?" method="post" name="f">
+
+	<table>
+		<tr>
+			<td rowspan="2"><img src="/<?php echo $this->data['baseurlpath']; ?>resources/icons/pencil.png" alt="" /></td>
+			<td style="padding: .3em;"><?php echo $this->t('{login:username}'); ?></td>
+			<td><input type="text" id="username" tabindex="1" name="username" value="<?php echo htmlspecialchars($this->data['username']); ?>" /></td>
+			<td style="padding: .4em;" rowspan="2">
+				<input type="submit" tabindex="3" value="<?php echo $this->t('{login:login_button}'); ?>" />
+			</td>
+		</tr>
+		<tr>
+			<td style="padding: .3em;"><?php echo $this->t('{login:password}'); ?></td>
+			<td><input id="password" type="password" tabindex="2" name="password" /></td>
+		</tr>
+	</table>
+
+<?php
+foreach ($this->data['stateparams'] as $name => $value) {
+	echo('<input type="hidden" name="' . htmlspecialchars($name) . '" value="' . htmlspecialchars($value) . '" />');
+}
+?>
+
+	</form>
+
+<?php
+echo('<h2>' . $this->t('{login:help_header}') . '</h2>');
+echo('<p>' . $this->t('{login:help_text}') . '</p>');
+
+$this->includeAtTemplateBase('includes/footer.php');
+?>
\ No newline at end of file
diff --git a/modules/core/www/loginuserpass.php b/modules/core/www/loginuserpass.php
new file mode 100644
index 000000000..a7bfdb291
--- /dev/null
+++ b/modules/core/www/loginuserpass.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * This page shows a username/password login form, and passes information from it
+ * to the sspmod_core_Auth_UserPassBase class, which is a generic class for
+ * username/password authentication.
+ *
+ * @author Olav Morken, UNINETT AS.
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+
+if (!array_key_exists('AuthState', $_REQUEST)) {
+	throw new SimpleSAML_Error_BadRequest('Missing AuthState parameter.');
+}
+$authStateId = $_REQUEST['AuthState'];
+
+if (array_key_exists('username', $_REQUEST)) {
+	$username = $_REQUEST['username'];
+} else {
+	$username = '';
+}
+
+if (array_key_exists('password', $_REQUEST)) {
+	$password = $_REQUEST['password'];
+} else {
+	$password = '';
+}
+
+if (!empty($username) || !empty($password)) {
+	/* Either username or password set - attempt to log in. */
+	$errorCode = sspmod_core_Auth_UserPassBase::handleLogin($authStateId, $username, $password);
+} else {
+	$errorCode = NULL;
+}
+
+$globalConfig = SimpleSAML_Configuration::getInstance();
+$t = new SimpleSAML_XHTML_Template($globalConfig, 'core:loginuserpass.php');
+$t->data['stateparams'] = array('AuthState' => $authStateId);
+$t->data['username'] = $username;
+$t->data['errorcode'] = $errorCode;
+$t->show();
+exit();
+
+
+?>
\ No newline at end of file
diff --git a/modules/exampleauth/default-disable b/modules/exampleauth/default-disable
new file mode 100644
index 000000000..fa0bd82e2
--- /dev/null
+++ b/modules/exampleauth/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/exampleauth/lib/Auth/Source/Static.php b/modules/exampleauth/lib/Auth/Source/Static.php
new file mode 100644
index 000000000..c2a07ccb4
--- /dev/null
+++ b/modules/exampleauth/lib/Auth/Source/Static.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * 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
+ * @version $Id$
+ */
+class sspmod_exampleauth_Auth_Source_Static extends SimpleSAML_Auth_Source {
+
+
+	/**
+	 * The attributes we return.
+	 */
+	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 = SimpleSAML_Utilities::parseAttributes($attributes);
+		} 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.
+	 */
+	public function authenticate(&$state) {
+		assert('is_array($state)');
+
+		$state['Attributes'] = $this->attributes;
+	}
+
+}
+
+?>
\ No newline at end of file
diff --git a/modules/exampleauth/lib/Auth/Source/UserPass.php b/modules/exampleauth/lib/Auth/Source/UserPass.php
new file mode 100644
index 000000000..98440f56a
--- /dev/null
+++ b/modules/exampleauth/lib/Auth/Source/UserPass.php
@@ -0,0 +1,93 @@
+<?php
+
+/**
+ * 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
+ * @version $Id$
+ */
+class sspmod_exampleauth_Auth_Source_UserPass extends sspmod_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.
+	 */
+	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 = array();
+
+		/* 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 = SimpleSAML_Utilities::parseAttributes($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 SimpleSAML_Error_Error('WRONGUSERPASS');
+		}
+
+		return $this->users[$userpass];
+	}
+
+}
+
+?>
\ No newline at end of file
diff --git a/www/saml2/idp/SSOService.php b/www/saml2/idp/SSOService.php
index 7f024c75e..55d53a062 100644
--- a/www/saml2/idp/SSOService.php
+++ b/www/saml2/idp/SSOService.php
@@ -144,7 +144,17 @@ if (isset($_GET['SAMLRequest'])) {
 }
 
 
-$authority = isset($idpmetadata['authority']) ? $idpmetadata['authority'] : NULL;
+/* Check whether we should authenticate with an AuthSource. Any time the auth-option matches a
+ * valid AuthSource, we assume that this is the case.
+ */
+if(SimpleSAML_Auth_Source::getById($idpmetadata['auth']) !== NULL) {
+	/* Authenticate with an AuthSource. */
+	$authSource = TRUE;
+	$authority = $idpmetadata['auth'];
+} else {
+	$authSource = FALSE;
+	$authority = isset($idpmetadata['authority']) ? $idpmetadata['authority'] : NULL;
+}
 
 
 /**
@@ -175,13 +185,24 @@ if($needAuth && !$isPassive) {
 	$session->setAuthnRequest('saml2', $authId, $requestcache);
 
 	$redirectTo = SimpleSAML_Utilities::selfURLNoQuery() . '?RequestID=' . urlencode($authId);
-	$authurl = '/' . $config->getBaseURL() . $idpmetadata['auth'];
 
-	SimpleSAML_Utilities::redirect($authurl, array(
-		'RelayState' => $redirectTo,
-		'AuthId' => $authId,
-		'protocol' => 'saml2',
-	));
+	if($authSource) {
+		/* Authenticate with an AuthSource. */
+		$hints = array(
+			'SPMetadata' => $metadata->getMetaData($requestcache['Issuer'], 'saml20-sp-remote'),
+			'IdPMetadata' => $idpmetadata,
+		);
+
+		SimpleSAML_Auth_Default::initLogin($idpmetadata['auth'], $redirectTo, NULL, $hints);
+	} else {
+		$authurl = '/' . $config->getBaseURL() . $idpmetadata['auth'];
+
+		SimpleSAML_Utilities::redirect($authurl, array(
+		       'RelayState' => $redirectTo,
+		       'AuthId' => $authId,
+		       'protocol' => 'saml2',
+		));
+	}
 
 } elseif($needAuth) {
 	/* We have a passive request, but need authentication. Send back a response indicating that
diff --git a/www/shib13/idp/SSOService.php b/www/shib13/idp/SSOService.php
index 9ae15e0ce..495f20d48 100644
--- a/www/shib13/idp/SSOService.php
+++ b/www/shib13/idp/SSOService.php
@@ -96,7 +96,17 @@ if (isset($_GET['shire'])) {
 	SimpleSAML_Utilities::fatalError($session->getTrackID(), 'SSOSERVICEPARAMS');
 }
 
-$authority = isset($idpmetadata['authority']) ? $idpmetadata['authority'] : null;
+/* Check whether we should authenticate with an AuthSource. Any time the auth-option matches a
+ * valid AuthSource, we assume that this is the case.
+ */
+if(SimpleSAML_Auth_Source::getById($idpmetadata['auth']) !== NULL) {
+	/* Authenticate with an AuthSource. */
+	$authSource = TRUE;
+	$authority = $idpmetadata['auth'];
+} else {
+	$authSource = FALSE;
+	$authority = isset($idpmetadata['authority']) ? $idpmetadata['authority'] : NULL;
+}
 
 /*
  * As we have passed the code above, we have an accociated request that is already processed.
@@ -113,14 +123,24 @@ if (!$session->isAuthenticated($authority) ) {
 	$session->setAuthnRequest('shib13', $authId, $requestcache);
 
 	$redirectTo = SimpleSAML_Utilities::selfURLNoQuery() . '?RequestID=' . urlencode($authId);
-	$authurl = '/' . $config->getBaseURL() . $idpmetadata['auth'];
 
-	SimpleSAML_Utilities::redirect($authurl, array(
-		'RelayState' => $redirectTo,
-		'AuthId' => $authId,
-		'protocol' => 'shib13',
-	));
-	
+	if($authSource) {
+		/* Authenticate with an AuthSource. */
+		$hints = array(
+			'SPMetadata' => $metadata->getMetaData($requestcache['Issuer'], 'shib13-sp-remote'),
+			'IdPMetadata' => $idpmetadata,
+		);
+
+		SimpleSAML_Auth_Default::initLogin($idpmetadata['auth'], $redirectTo, NULL, $hints);
+	} else {
+		$authurl = '/' . $config->getBaseURL() . $idpmetadata['auth'];
+
+		SimpleSAML_Utilities::redirect($authurl, array(
+			'RelayState' => $redirectTo,
+			'AuthId' => $authId,
+			'protocol' => 'shib13',
+		));
+	}
 	
 /*
  * We got an request, and we hav a valid session. Then we send an AuthenticationResponse back to the
-- 
GitLab