diff --git a/lib/SimpleSAML/Auth/Source.php b/lib/SimpleSAML/Auth/Source.php index 8e71524e7ebb82e2cd9ec0a4bcf4dde11dcf10a7..15dab4196d1dd9411202da4460ed69c29fc70a2f 100644 --- a/lib/SimpleSAML/Auth/Source.php +++ b/lib/SimpleSAML/Auth/Source.php @@ -1,5 +1,6 @@ <?php + /** * This class defines a base class for authentication source. * @@ -8,348 +9,370 @@ * @author Olav Morken, UNINETT AS. * @package simpleSAMLphp */ -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. - * - * @var string - */ - 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']; - } - - - /** - * Get sources of a specific type. - * - * @param string $type The type of the authentication source. - * @return SimpleSAML_Auth_Source[] Array of SimpleSAML_Auth_Source objects of the specified type. - * @throws Exception If the authentication source is invalid. - */ - public static function getSourcesOfType($type) { - assert('is_string($type)'); - - $config = SimpleSAML_Configuration::getConfig('authsources.php'); - - $ret = array(); - - $sources = $config->getOptions(); - foreach ($sources as $id) { - $source = $config->getArray($id); - - if (!array_key_exists(0, $source) || !is_string($source[0])) { - throw new Exception('Invalid authentication source \'' . $id . - '\': First element must be a string which identifies the authentication source.'); - } - - if ($source[0] !== $type) { - continue; - } - - $ret[] = self::parseAuthSource($id, $source); - } - - return $ret; - } - - - /** - * Retrieve the ID of this authentication source. - * - * @return string The ID of this authentication source. - */ - public function getAuthId() { - - return $this->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); - - - /** - * Reauthenticate an user. - * - * This function is called by the IdP to give the authentication source a chance to - * interact with the user even in the case when the user is already authenticated. - * - * @param array &$state Information about the current authentication. - */ - public function reauthenticate(array &$state) { - assert('isset($state["ReturnCallback"])'); - - /* The default implementation just copies over the previous authentication data. */ - $session = SimpleSAML_Session::getSessionFromRequest(); - $data = $session->getAuthState($this->authId); - foreach ($data as $k => $v) { - $state[$k] = $v; - } - } - - - /** - * 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); - } - - - /** - * Log out from this authentication source. - * - * This function should be overridden if the authentication source requires special - * steps to complete a logout operation. - * - * If the logout process requires a redirect, the state should be saved. Once the - * logout operation is completed, the state should be restored, and completeLogout - * should be called with the state. If this operation can be completed without - * showing the user a page, or redirecting, this function should return. - * - * @param array &$state Information about the current logout operation. - */ - public function logout(&$state) { - assert('is_array($state)'); - - /* Default logout handler which doesn't do anything. */ - } - - - /** - * Complete logout. - * - * This function should be called after logout 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 completeLogout(&$state) { - assert('is_array($state)'); - assert('array_key_exists("LogoutCompletedHandler", $state)'); - - SimpleSAML_Auth_State::deleteState($state); - - $func = $state['LogoutCompletedHandler']; - 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. - * @throws Exception If the authentication source is invalid. - */ - 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. If no authentication source with the given id can be found, - * NULL will be returned. - * - * If the $type parameter is specified, this function will return an - * authentication source of the given type. If no authentication source or if an - * authentication source of a different type is found, an exception will be thrown. - * - * @param string $authId The authentication source identifier. - * @param string|NULL $type The type of authentication source. If NULL, any type will be accepted. - * @return SimpleSAML_Auth_Source|NULL The AuthSource object, or NULL if no authentication - * source with the given identifier is found. - * @throws SimpleSAML_Error_Exception If no such authentication source is found or it is invalid. - */ - public static function getById($authId, $type = NULL) { - assert('is_string($authId)'); - assert('is_null($type) || is_string($type)'); - - /* For now - load and parse config file. */ - $config = SimpleSAML_Configuration::getConfig('authsources.php'); - - $authConfig = $config->getArray($authId, NULL); - if ($authConfig === NULL) { - if ($type !== NULL) { - throw new SimpleSAML_Error_Exception('No authentication source with id ' . - var_export($authId, TRUE) . ' found.'); - } - return NULL; - } - - $ret = self::parseAuthSource($authId, $authConfig); - - if ($type === NULL || $ret instanceof $type) { - return $ret; - } - - /* The authentication source doesn't have the correct type. */ - throw new SimpleSAML_Error_Exception('Invalid type of authentication source ' . - var_export($authId, TRUE) . '. Was ' . var_export(get_class($ret), TRUE) . - ', should be ' . var_export($type, TRUE) . '.'); - } - - - /** - * Add a logout callback association. - * - * This function adds a logout callback association, which allows us to initiate - * a logout later based on the $assoc-value. - * - * Note that logout-associations exists per authentication source. A logout association - * from one authentication source cannot be called from a different authentication source. - * - * @param string $assoc The identifier for this logout association. - * @param array $state The state array passed to the authenticate-function. - */ - protected function addLogoutCallback($assoc, $state) { - assert('is_string($assoc)'); - assert('is_array($state)'); - - if (!array_key_exists('LogoutCallback', $state)) { - /* The authentication requester doesn't have a logout callback. */ - return; - } - $callback = $state['LogoutCallback']; - - if (array_key_exists('LogoutCallbackState', $state)) { - $callbackState = $state['LogoutCallbackState']; - } else { - $callbackState = array(); - } - - $id = strlen($this->authId) . ':' . $this->authId . $assoc; - - $data = array( - 'callback' => $callback, - 'state' => $callbackState, - ); - - - $session = SimpleSAML_Session::getSessionFromRequest(); - $session->setData('SimpleSAML_Auth_Source.LogoutCallbacks', $id, $data, - SimpleSAML_Session::DATA_TIMEOUT_SESSION_END); - } - - - /** - * Call a logout callback based on association. - * - * This function calls a logout callback based on an association saved with - * addLogoutCallback(...). - * - * This function always returns. - * - * @param string $assoc The logout association which should be called. - */ - protected function callLogoutCallback($assoc) { - assert('is_string($assoc)'); - - $id = strlen($this->authId) . ':' . $this->authId . $assoc; - - $session = SimpleSAML_Session::getSessionFromRequest(); - - $data = $session->getData('SimpleSAML_Auth_Source.LogoutCallbacks', $id); - if ($data === NULL) { - /* FIXME: fix for IdP-first flow (issue 397) -> reevaluate logout callback infrastructure */ - $session->doLogout($this->authId); - - return; - } - - assert('is_array($data)'); - assert('array_key_exists("callback", $data)'); - assert('array_key_exists("state", $data)'); - - $callback = $data['callback']; - $callbackState = $data['state']; - - $session->deleteData('SimpleSAML_Auth_Source.LogoutCallbacks', $id); - call_user_func($callback, $callbackState); - } - - - /** - * Retrieve list of authentication sources. - * - * @return array The id of all authentication sources. - */ - public static function getSources() { - - $config = SimpleSAML_Configuration::getOptionalConfig('authsources.php'); - - return $config->getOptions(); - } - +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. + * + * @var string + */ + 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']; + } + + + /** + * Get sources of a specific type. + * + * @param string $type The type of the authentication source. + * + * @return SimpleSAML_Auth_Source[] Array of SimpleSAML_Auth_Source objects of the specified type. + * @throws Exception If the authentication source is invalid. + */ + public static function getSourcesOfType($type) + { + assert('is_string($type)'); + + $config = SimpleSAML_Configuration::getConfig('authsources.php'); + + $ret = array(); + + $sources = $config->getOptions(); + foreach ($sources as $id) { + $source = $config->getArray($id); + + if (!array_key_exists(0, $source) || !is_string($source[0])) { + throw new Exception( + 'Invalid authentication source \''.$id. + '\': First element must be a string which identifies the authentication source.' + ); + } + + if ($source[0] !== $type) { + continue; + } + + $ret[] = self::parseAuthSource($id, $source); + } + + return $ret; + } + + + /** + * Retrieve the ID of this authentication source. + * + * @return string The ID of this authentication source. + */ + public function getAuthId() + { + return $this->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); + + + /** + * Reauthenticate an user. + * + * This function is called by the IdP to give the authentication source a chance to + * interact with the user even in the case when the user is already authenticated. + * + * @param array &$state Information about the current authentication. + */ + public function reauthenticate(array &$state) + { + assert('isset($state["ReturnCallback"])'); + + // the default implementation just copies over the previous authentication data + $session = SimpleSAML_Session::getSessionFromRequest(); + $data = $session->getAuthState($this->authId); + foreach ($data as $k => $v) { + $state[$k] = $v; + } + } + + + /** + * 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); + } + + + /** + * Log out from this authentication source. + * + * This function should be overridden if the authentication source requires special + * steps to complete a logout operation. + * + * If the logout process requires a redirect, the state should be saved. Once the + * logout operation is completed, the state should be restored, and completeLogout + * should be called with the state. If this operation can be completed without + * showing the user a page, or redirecting, this function should return. + * + * @param array &$state Information about the current logout operation. + */ + public function logout(&$state) + { + assert('is_array($state)'); + // default logout handler which doesn't do anything + } + + + /** + * Complete logout. + * + * This function should be called after logout 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 completeLogout(&$state) + { + assert('is_array($state)'); + assert('array_key_exists("LogoutCompletedHandler", $state)'); + + SimpleSAML_Auth_State::deleteState($state); + + $func = $state['LogoutCompletedHandler']; + 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. + * @throws Exception If the authentication source is invalid. + */ + 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. If no authentication source with the given id can be found, + * NULL will be returned. + * + * If the $type parameter is specified, this function will return an + * authentication source of the given type. If no authentication source or if an + * authentication source of a different type is found, an exception will be thrown. + * + * @param string $authId The authentication source identifier. + * @param string|NULL $type The type of authentication source. If NULL, any type will be accepted. + * + * @return SimpleSAML_Auth_Source|NULL The AuthSource object, or NULL if no authentication + * source with the given identifier is found. + * @throws SimpleSAML_Error_Exception If no such authentication source is found or it is invalid. + */ + public static function getById($authId, $type = null) + { + assert('is_string($authId)'); + assert('is_null($type) || is_string($type)'); + + // for now - load and parse config file + $config = SimpleSAML_Configuration::getConfig('authsources.php'); + + $authConfig = $config->getArray($authId, null); + if ($authConfig === null) { + if ($type !== null) { + throw new SimpleSAML_Error_Exception( + 'No authentication source with id '. + var_export($authId, true).' found.' + ); + } + return null; + } + + $ret = self::parseAuthSource($authId, $authConfig); + + if ($type === null || $ret instanceof $type) { + return $ret; + } + + // the authentication source doesn't have the correct type + throw new SimpleSAML_Error_Exception( + 'Invalid type of authentication source '. + var_export($authId, true).'. Was '.var_export(get_class($ret), true). + ', should be '.var_export($type, true).'.' + ); + } + + + /** + * Add a logout callback association. + * + * This function adds a logout callback association, which allows us to initiate + * a logout later based on the $assoc-value. + * + * Note that logout-associations exists per authentication source. A logout association + * from one authentication source cannot be called from a different authentication source. + * + * @param string $assoc The identifier for this logout association. + * @param array $state The state array passed to the authenticate-function. + */ + protected function addLogoutCallback($assoc, $state) + { + assert('is_string($assoc)'); + assert('is_array($state)'); + + if (!array_key_exists('LogoutCallback', $state)) { + // the authentication requester doesn't have a logout callback + return; + } + $callback = $state['LogoutCallback']; + + if (array_key_exists('LogoutCallbackState', $state)) { + $callbackState = $state['LogoutCallbackState']; + } else { + $callbackState = array(); + } + + $id = strlen($this->authId).':'.$this->authId.$assoc; + + $data = array( + 'callback' => $callback, + 'state' => $callbackState, + ); + + $session = SimpleSAML_Session::getSessionFromRequest(); + $session->setData( + 'SimpleSAML_Auth_Source.LogoutCallbacks', + $id, + $data, + SimpleSAML_Session::DATA_TIMEOUT_SESSION_END + ); + } + + + /** + * Call a logout callback based on association. + * + * This function calls a logout callback based on an association saved with + * addLogoutCallback(...). + * + * This function always returns. + * + * @param string $assoc The logout association which should be called. + */ + protected function callLogoutCallback($assoc) + { + assert('is_string($assoc)'); + + $id = strlen($this->authId).':'.$this->authId.$assoc; + + $session = SimpleSAML_Session::getSessionFromRequest(); + + $data = $session->getData('SimpleSAML_Auth_Source.LogoutCallbacks', $id); + if ($data === null) { + // FIXME: fix for IdP-first flow (issue 397) -> reevaluate logout callback infrastructure + $session->doLogout($this->authId); + + return; + } + + assert('is_array($data)'); + assert('array_key_exists("callback", $data)'); + assert('array_key_exists("state", $data)'); + + $callback = $data['callback']; + $callbackState = $data['state']; + + $session->deleteData('SimpleSAML_Auth_Source.LogoutCallbacks', $id); + call_user_func($callback, $callbackState); + } + + + /** + * Retrieve list of authentication sources. + * + * @return array The id of all authentication sources. + */ + public static function getSources() + { + $config = SimpleSAML_Configuration::getOptionalConfig('authsources.php'); + + return $config->getOptions(); + } }