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