runff 1.0 commit
This commit is contained in:
139
lib/SimpleSAML/Auth/Default.php
Executable file
139
lib/SimpleSAML/Auth/Default.php
Executable file
@@ -0,0 +1,139 @@
|
||||
<?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
|
||||
*
|
||||
* @deprecated This class will be removed in SSP 2.0.
|
||||
*/
|
||||
class SimpleSAML_Auth_Default
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Use SimpleSAML_Auth_Source::initLogin() instead.
|
||||
*/
|
||||
public static function initLogin(
|
||||
$authId,
|
||||
$return,
|
||||
$errorURL = null,
|
||||
array $params = array()
|
||||
) {
|
||||
|
||||
$as = self::getAuthSource($authId);
|
||||
$as->initLogin($return, $errorURL, $params);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use
|
||||
* SimpleSAML_Auth_State::getPersistentAuthData() instead.
|
||||
*/
|
||||
public static function extractPersistentAuthState(array &$state)
|
||||
{
|
||||
|
||||
return SimpleSAML_Auth_State::getPersistentAuthData($state);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML_Auth_Source::loginCompleted() instead.
|
||||
*/
|
||||
public static function loginCompleted($state)
|
||||
{
|
||||
SimpleSAML_Auth_Source::loginCompleted($state);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0.
|
||||
*/
|
||||
public static function initLogoutReturn($returnURL, $authority)
|
||||
{
|
||||
assert(is_string($returnURL));
|
||||
assert(is_string($authority));
|
||||
|
||||
$session = SimpleSAML_Session::getSessionFromRequest();
|
||||
|
||||
$state = $session->getAuthData($authority, 'LogoutState');
|
||||
$session->doLogout($authority);
|
||||
|
||||
$state['SimpleSAML_Auth_Default.ReturnURL'] = $returnURL;
|
||||
$state['LogoutCompletedHandler'] = array(get_class(), 'logoutCompleted');
|
||||
|
||||
$as = SimpleSAML_Auth_Source::getById($authority);
|
||||
if ($as === null) {
|
||||
// The authority wasn't an authentication source...
|
||||
self::logoutCompleted($state);
|
||||
}
|
||||
|
||||
$as->logout($state);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0.
|
||||
*/
|
||||
public static function initLogout($returnURL, $authority)
|
||||
{
|
||||
assert(is_string($returnURL));
|
||||
assert(is_string($authority));
|
||||
|
||||
self::initLogoutReturn($returnURL, $authority);
|
||||
|
||||
\SimpleSAML\Utils\HTTP::redirectTrustedURL($returnURL);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0.
|
||||
*/
|
||||
public static function logoutCompleted($state)
|
||||
{
|
||||
assert(is_array($state));
|
||||
assert(array_key_exists('SimpleSAML_Auth_Default.ReturnURL', $state));
|
||||
|
||||
\SimpleSAML\Utils\HTTP::redirectTrustedURL($state['SimpleSAML_Auth_Default.ReturnURL']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML_Auth_Source::logoutCallback() instead.
|
||||
*/
|
||||
public static function logoutCallback($state)
|
||||
{
|
||||
SimpleSAML_Auth_Source::logoutCallback($state);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use
|
||||
* sspmod_saml_Auth_Source_SP::handleUnsolicitedAuth() instead.
|
||||
*/
|
||||
public static function handleUnsolicitedAuth($authId, array $state, $redirectTo)
|
||||
{
|
||||
sspmod_saml_Auth_Source_SP::handleUnsolicitedAuth($authId, $state, $redirectTo);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return an authentication source by ID.
|
||||
*
|
||||
* @param string $id The id of the authentication source.
|
||||
* @return SimpleSAML_Auth_Source The authentication source.
|
||||
* @throws Exception If the $id does not correspond with an authentication source.
|
||||
*/
|
||||
private static function getAuthSource($id)
|
||||
{
|
||||
$as = SimpleSAML_Auth_Source::getById($id);
|
||||
if ($as === null) {
|
||||
throw new Exception('Invalid authentication source: ' . $id);
|
||||
}
|
||||
return $as;
|
||||
}
|
||||
}
|
||||
775
lib/SimpleSAML/Auth/LDAP.php
Executable file
775
lib/SimpleSAML/Auth/LDAP.php
Executable file
@@ -0,0 +1,775 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Constants defining possible errors
|
||||
*/
|
||||
define('ERR_INTERNAL', 1);
|
||||
define('ERR_NO_USER', 2);
|
||||
define('ERR_WRONG_PW', 3);
|
||||
define('ERR_AS_DATA_INCONSIST', 4);
|
||||
define('ERR_AS_INTERNAL', 5);
|
||||
define('ERR_AS_ATTRIBUTE', 6);
|
||||
|
||||
// not defined in earlier PHP versions
|
||||
if (!defined('LDAP_OPT_DIAGNOSTIC_MESSAGE')) {
|
||||
define('LDAP_OPT_DIAGNOSTIC_MESSAGE', 0x0032);
|
||||
}
|
||||
|
||||
/**
|
||||
* The LDAP class holds helper functions to access an LDAP database.
|
||||
*
|
||||
* @author Andreas Aakre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
|
||||
* @author Anders Lund, UNINETT AS. <anders.lund@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SimpleSAML_Auth_LDAP
|
||||
{
|
||||
/**
|
||||
* LDAP link identifier.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
protected $ldap = null;
|
||||
|
||||
/**
|
||||
* LDAP user: authz_id if SASL is in use, binding dn otherwise
|
||||
*/
|
||||
protected $authz_id = null;
|
||||
|
||||
/**
|
||||
* Timeout value, in seconds.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $timeout = 0;
|
||||
|
||||
/**
|
||||
* Private constructor restricts instantiation to getInstance().
|
||||
*
|
||||
* @param string $hostname
|
||||
* @param bool $enable_tls
|
||||
* @param bool $debug
|
||||
* @param int $timeout
|
||||
* @param int $port
|
||||
* @param bool $referrals
|
||||
*/
|
||||
public function __construct($hostname, $enable_tls = true, $debug = false, $timeout = 0, $port = 389, $referrals = true)
|
||||
{
|
||||
// Debug
|
||||
SimpleSAML\Logger::debug('Library - LDAP __construct(): Setup LDAP with '.
|
||||
'host=\''.$hostname.
|
||||
'\', tls='.var_export($enable_tls, true).
|
||||
', debug='.var_export($debug, true).
|
||||
', timeout='.var_export($timeout, true).
|
||||
', referrals='.var_export($referrals, true));
|
||||
|
||||
/*
|
||||
* Set debug level before calling connect. Note that this passes
|
||||
* NULL to ldap_set_option, which is an undocumented feature.
|
||||
*
|
||||
* OpenLDAP 2.x.x or Netscape Directory SDK x.x needed for this option.
|
||||
*/
|
||||
if ($debug && !ldap_set_option(null, LDAP_OPT_DEBUG_LEVEL, 7)) {
|
||||
SimpleSAML\Logger::warning('Library - LDAP __construct(): Unable to set debug level (LDAP_OPT_DEBUG_LEVEL) to 7');
|
||||
}
|
||||
|
||||
/*
|
||||
* Prepare a connection for to this LDAP server. Note that this function
|
||||
* doesn't actually connect to the server.
|
||||
*/
|
||||
$this->ldap = @ldap_connect($hostname, $port);
|
||||
if ($this->ldap === false) {
|
||||
throw $this->makeException('Library - LDAP __construct(): Unable to connect to \''.$hostname.'\'', ERR_INTERNAL);
|
||||
}
|
||||
|
||||
// Enable LDAP protocol version 3
|
||||
if (!@ldap_set_option($this->ldap, LDAP_OPT_PROTOCOL_VERSION, 3)) {
|
||||
throw $this->makeException('Library - LDAP __construct(): Failed to set LDAP Protocol version (LDAP_OPT_PROTOCOL_VERSION) to 3', ERR_INTERNAL);
|
||||
}
|
||||
|
||||
// Set referral option
|
||||
if (!@ldap_set_option($this->ldap, LDAP_OPT_REFERRALS, $referrals)) {
|
||||
throw $this->makeException('Library - LDAP __construct(): Failed to set LDAP Referrals (LDAP_OPT_REFERRALS) to '.$referrals, ERR_INTERNAL);
|
||||
}
|
||||
|
||||
// Set timeouts, if supported
|
||||
// (OpenLDAP 2.x.x or Netscape Directory SDK x.x needed)
|
||||
$this->timeout = $timeout;
|
||||
if ($timeout > 0) {
|
||||
if (!@ldap_set_option($this->ldap, LDAP_OPT_NETWORK_TIMEOUT, $timeout)) {
|
||||
SimpleSAML\Logger::warning('Library - LDAP __construct(): Unable to set timeouts (LDAP_OPT_NETWORK_TIMEOUT) to '.$timeout);
|
||||
}
|
||||
if (!@ldap_set_option($this->ldap, LDAP_OPT_TIMELIMIT, $timeout)) {
|
||||
SimpleSAML\Logger::warning('Library - LDAP __construct(): Unable to set timeouts (LDAP_OPT_TIMELIMIT) to '.$timeout);
|
||||
}
|
||||
}
|
||||
|
||||
// Enable TLS, if needed
|
||||
if (stripos($hostname, "ldaps:") === false && $enable_tls) {
|
||||
if (!@ldap_start_tls($this->ldap)) {
|
||||
throw $this->makeException('Library - LDAP __construct(): Unable to force TLS', ERR_INTERNAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convenience method to create an LDAPException as well as log the
|
||||
* description.
|
||||
*
|
||||
* @param string $description
|
||||
* The exception's description
|
||||
* @return Exception
|
||||
*/
|
||||
private function makeException($description, $type = null)
|
||||
{
|
||||
$errNo = 0x00;
|
||||
|
||||
// Log LDAP code and description, if possible
|
||||
if (empty($this->ldap)) {
|
||||
SimpleSAML\Logger::error($description);
|
||||
} else {
|
||||
$errNo = @ldap_errno($this->ldap);
|
||||
}
|
||||
|
||||
// Decide exception type and return
|
||||
if ($type) {
|
||||
if ($errNo !== 0) {
|
||||
// Only log real LDAP errors; not success
|
||||
SimpleSAML\Logger::error($description.'; cause: \''.ldap_error($this->ldap).'\' (0x'.dechex($errNo).')');
|
||||
} else {
|
||||
SimpleSAML\Logger::error($description);
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case ERR_INTERNAL:// 1 - ExInternal
|
||||
return new SimpleSAML_Error_Exception($description, $errNo);
|
||||
case ERR_NO_USER:// 2 - ExUserNotFound
|
||||
return new SimpleSAML_Error_UserNotFound($description, $errNo);
|
||||
case ERR_WRONG_PW:// 3 - ExInvalidCredential
|
||||
return new SimpleSAML_Error_InvalidCredential($description, $errNo);
|
||||
case ERR_AS_DATA_INCONSIST:// 4 - ExAsDataInconsist
|
||||
return new SimpleSAML_Error_AuthSource('ldap', $description);
|
||||
case ERR_AS_INTERNAL:// 5 - ExAsInternal
|
||||
return new SimpleSAML_Error_AuthSource('ldap', $description);
|
||||
}
|
||||
} else {
|
||||
if ($errNo !== 0) {
|
||||
$description .= '; cause: \''.ldap_error($this->ldap).'\' (0x'.dechex($errNo).')';
|
||||
if (@ldap_get_option($this->ldap, LDAP_OPT_DIAGNOSTIC_MESSAGE, $extendedError) && !empty($extendedError)) {
|
||||
$description .= '; additional: \''.$extendedError.'\'';
|
||||
}
|
||||
}
|
||||
switch ($errNo) {
|
||||
case 0x20://LDAP_NO_SUCH_OBJECT
|
||||
SimpleSAML\Logger::warning($description);
|
||||
return new SimpleSAML_Error_UserNotFound($description, $errNo);
|
||||
case 0x31://LDAP_INVALID_CREDENTIALS
|
||||
SimpleSAML\Logger::info($description);
|
||||
return new SimpleSAML_Error_InvalidCredential($description, $errNo);
|
||||
case -1://NO_SERVER_CONNECTION
|
||||
SimpleSAML\Logger::error($description);
|
||||
return new SimpleSAML_Error_AuthSource('ldap', $description);
|
||||
default:
|
||||
SimpleSAML\Logger::error($description);
|
||||
return new SimpleSAML_Error_AuthSource('ldap', $description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Search for DN from a single base.
|
||||
*
|
||||
* @param string $base
|
||||
* Indication of root of subtree to search
|
||||
* @param string|array $attribute
|
||||
* The attribute name(s) to search for.
|
||||
* @param string $value
|
||||
* The attribute value to search for.
|
||||
* Additional search filter
|
||||
* @param string|null $searchFilter
|
||||
* The scope of the search
|
||||
* @param string $scope
|
||||
* @return string
|
||||
* The DN of the resulting found element.
|
||||
* @throws SimpleSAML_Error_Exception if:
|
||||
* - Attribute parameter is wrong type
|
||||
* @throws SimpleSAML_Error_AuthSource if:
|
||||
* - Not able to connect to LDAP server
|
||||
* - False search result
|
||||
* - Count return false
|
||||
* - Searche found more than one result
|
||||
* - Failed to get first entry from result
|
||||
* - Failed to get DN for entry
|
||||
* @throws SimpleSAML_Error_UserNotFound if:
|
||||
* - Zero entries were found
|
||||
*/
|
||||
private function search($base, $attribute, $value, $searchFilter = null, $scope = "subtree")
|
||||
{
|
||||
// Create the search filter
|
||||
$attribute = self::escape_filter_value($attribute, false);
|
||||
$value = self::escape_filter_value($value, true);
|
||||
$filter = '';
|
||||
foreach ($attribute as $attr) {
|
||||
$filter .= '('.$attr.'='.$value.')';
|
||||
}
|
||||
$filter = '(|'.$filter.')';
|
||||
|
||||
// Append LDAP filters if defined
|
||||
if ($searchFilter != null) {
|
||||
$filter = "(&".$filter."".$searchFilter.")";
|
||||
}
|
||||
|
||||
// Search using generated filter
|
||||
SimpleSAML\Logger::debug('Library - LDAP search(): Searching base ('.$scope.') \''.$base.'\' for \''.$filter.'\'');
|
||||
if ($scope === 'base') {
|
||||
$result = @ldap_read($this->ldap, $base, $filter, array(), 0, 0, $this->timeout, LDAP_DEREF_NEVER);
|
||||
} else if ($scope === 'onelevel') {
|
||||
$result = @ldap_list($this->ldap, $base, $filter, array(), 0, 0, $this->timeout, LDAP_DEREF_NEVER);
|
||||
} else {
|
||||
$result = @ldap_search($this->ldap, $base, $filter, array(), 0, 0, $this->timeout, LDAP_DEREF_NEVER);
|
||||
}
|
||||
|
||||
if ($result === false) {
|
||||
throw $this->makeException('Library - LDAP search(): Failed search on base \''.$base.'\' for \''.$filter.'\'');
|
||||
}
|
||||
|
||||
// Sanity checks on search results
|
||||
$count = @ldap_count_entries($this->ldap, $result);
|
||||
if ($count === false) {
|
||||
throw $this->makeException('Library - LDAP search(): Failed to get number of entries returned');
|
||||
} elseif ($count > 1) {
|
||||
// More than one entry is found. External error
|
||||
throw $this->makeException('Library - LDAP search(): Found '.$count.' entries searching base \''.$base.'\' for \''.$filter.'\'', ERR_AS_DATA_INCONSIST);
|
||||
} elseif ($count === 0) {
|
||||
// No entry is fond => wrong username is given (or not registered in the catalogue). User error
|
||||
throw $this->makeException('Library - LDAP search(): Found no entries searching base \''.$base.'\' for \''.$filter.'\'', ERR_NO_USER);
|
||||
}
|
||||
|
||||
|
||||
// Resolve the DN from the search result
|
||||
$entry = @ldap_first_entry($this->ldap, $result);
|
||||
if ($entry === false) {
|
||||
throw $this->makeException('Library - LDAP search(): Unable to retrieve result after searching base \''.$base.'\' for \''.$filter.'\'');
|
||||
}
|
||||
$dn = @ldap_get_dn($this->ldap, $entry);
|
||||
if ($dn === false) {
|
||||
throw $this->makeException('Library - LDAP search(): Unable to get DN after searching base \''.$base.'\' for \''.$filter.'\'');
|
||||
}
|
||||
return $dn;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Search for a DN.
|
||||
*
|
||||
* @param string|array $base
|
||||
* The base, or bases, which to search from.
|
||||
* @param string|array $attribute
|
||||
* The attribute name(s) searched for.
|
||||
* @param string $value
|
||||
* The attribute value searched for.
|
||||
* @param bool $allowZeroHits
|
||||
* Determines if the method will throw an exception if no hits are found.
|
||||
* Defaults to FALSE.
|
||||
* @param string|null $searchFilter
|
||||
* Additional searchFilter to be added to the (attribute=value) filter
|
||||
* @param string $scope
|
||||
* The scope of the search
|
||||
* @return string
|
||||
* The DN of the matching element, if found. If no element was found and
|
||||
* $allowZeroHits is set to FALSE, an exception will be thrown; otherwise
|
||||
* NULL will be returned.
|
||||
* @throws SimpleSAML_Error_AuthSource if:
|
||||
* - LDAP search encounter some problems when searching cataloge
|
||||
* - Not able to connect to LDAP server
|
||||
* @throws SimpleSAML_Error_UserNotFound if:
|
||||
* - $allowZeroHits is FALSE and no result is found
|
||||
*
|
||||
*/
|
||||
public function searchfordn($base, $attribute, $value, $allowZeroHits = false, $searchFilter = null, $scope = 'subtree')
|
||||
{
|
||||
// Traverse all search bases, returning DN if found
|
||||
$bases = SimpleSAML\Utils\Arrays::arrayize($base);
|
||||
foreach ($bases as $current) {
|
||||
try {
|
||||
// Single base search
|
||||
$result = $this->search($current, $attribute, $value, $searchFilter, $scope);
|
||||
|
||||
// We don't hawe to look any futher if user is found
|
||||
if (!empty($result)) {
|
||||
return $result;
|
||||
}
|
||||
// If search failed, attempt the other base DNs
|
||||
} catch (SimpleSAML_Error_UserNotFound $e) {
|
||||
// Just continue searching
|
||||
}
|
||||
}
|
||||
// Decide what to do for zero entries
|
||||
SimpleSAML\Logger::debug('Library - LDAP searchfordn(): No entries found');
|
||||
if ($allowZeroHits) {
|
||||
// Zero hits allowed
|
||||
return null;
|
||||
} else {
|
||||
// Zero hits not allowed
|
||||
throw $this->makeException('Library - LDAP searchfordn(): LDAP search returned zero entries for filter \'('.
|
||||
join(' | ', $attribute).' = '.$value.')\' on base(s) \'('.join(' & ', $bases).')\'', 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method was created specifically for the ldap:AttributeAddUsersGroups->searchActiveDirectory()
|
||||
* method, but could be used for other LDAP search needs. It will search LDAP and return all the entries.
|
||||
*
|
||||
* @throws Exception
|
||||
* @param string|array $bases
|
||||
* @param string|array $filters Array of 'attribute' => 'values' to be combined into the filter, or a raw filter string
|
||||
* @param string|array $attributes Array of attributes requested from LDAP
|
||||
* @param bool $and If multiple filters defined, then either bind them with & or |
|
||||
* @param bool $escape Weather to escape the filter values or not
|
||||
* @param string $scope The scope of the search
|
||||
* @return array
|
||||
*/
|
||||
public function searchformultiple($bases, $filters, $attributes = array(), $and = true, $escape = true, $scope = 'subtree')
|
||||
{
|
||||
// Escape the filter values, if requested
|
||||
if ($escape) {
|
||||
$filters = $this->escape_filter_value($filters, false);
|
||||
}
|
||||
|
||||
// Build search filter
|
||||
$filter = '';
|
||||
if (is_array($filters)) {
|
||||
foreach ($filters as $attribute => $value) {
|
||||
$filter .= "($attribute=$value)";
|
||||
}
|
||||
if (count($filters) > 1) {
|
||||
$filter = ($and ? '(&' : '(|').$filter.')';
|
||||
}
|
||||
} elseif (is_string($filters)) {
|
||||
$filter = $filters;
|
||||
}
|
||||
|
||||
// Verify filter was created
|
||||
if ($filter == '' || $filter == '(=)') {
|
||||
throw $this->makeException('ldap:LdapConnection->search_manual : No search filters defined', ERR_INTERNAL);
|
||||
}
|
||||
|
||||
// Verify at least one base was passed
|
||||
$bases = (array) $bases;
|
||||
if (empty($bases)) {
|
||||
throw $this->makeException('ldap:LdapConnection->search_manual : No base DNs were passed', ERR_INTERNAL);
|
||||
}
|
||||
|
||||
// Search each base until result is found
|
||||
$result = false;
|
||||
foreach ($bases as $base) {
|
||||
if ($scope === 'base') {
|
||||
$result = @ldap_read($this->ldap, $base, $filter, $attributes, 0, 0, $this->timeout);
|
||||
} else if ($scope === 'onelevel') {
|
||||
$result = @ldap_list($this->ldap, $base, $filter, $attributes, 0, 0, $this->timeout);
|
||||
} else {
|
||||
$result = @ldap_search($this->ldap, $base, $filter, $attributes, 0, 0, $this->timeout);
|
||||
}
|
||||
|
||||
if ($result !== false && @ldap_count_entries($this->ldap, $result) > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that a result was found in one of the bases
|
||||
if ($result === false) {
|
||||
throw $this->makeException(
|
||||
'ldap:LdapConnection->search_manual : Failed to search LDAP using base(s) ['.
|
||||
implode('; ', $bases).'] with filter ['.$filter.']. LDAP error ['.
|
||||
ldap_error($this->ldap).']'
|
||||
);
|
||||
} elseif (@ldap_count_entries($this->ldap, $result) < 1) {
|
||||
throw $this->makeException(
|
||||
'ldap:LdapConnection->search_manual : No entries found in LDAP using base(s) ['.
|
||||
implode('; ', $bases).'] with filter ['.$filter.']',
|
||||
ERR_NO_USER
|
||||
);
|
||||
}
|
||||
|
||||
// Get all results
|
||||
$results = ldap_get_entries($this->ldap, $result);
|
||||
if ($results === false) {
|
||||
throw $this->makeException(
|
||||
'ldap:LdapConnection->search_manual : Unable to retrieve entries from search results'
|
||||
);
|
||||
}
|
||||
|
||||
// parse each entry and process its attributes
|
||||
for ($i = 0; $i < $results['count']; $i++) {
|
||||
$entry = $results[$i];
|
||||
|
||||
// iterate over the attributes of the entry
|
||||
for ($j = 0; $j < $entry['count']; $j++) {
|
||||
$name = $entry[$j];
|
||||
$attribute = $entry[$name];
|
||||
|
||||
// decide whether to base64 encode or not
|
||||
for ($k = 0; $k < $attribute['count']; $k++) {
|
||||
// base64 encode binary attributes
|
||||
if (strtolower($name) === 'jpegphoto' || strtolower($name) === 'objectguid') {
|
||||
$results[$i][$name][$k] = base64_encode($attribute[$k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the count and return
|
||||
unset($results['count']);
|
||||
return $results;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Bind to LDAP with a specific DN and password. Simple wrapper around
|
||||
* ldap_bind() with some additional logging.
|
||||
*
|
||||
* @param string $dn
|
||||
* The DN used.
|
||||
* @param string $password
|
||||
* The password used.
|
||||
* @param array $sasl_args
|
||||
* Array of SASL options for SASL bind
|
||||
* @return bool
|
||||
* Returns TRUE if successful, FALSE if
|
||||
* LDAP_INVALID_CREDENTIALS, LDAP_X_PROXY_AUTHZ_FAILURE,
|
||||
* LDAP_INAPPROPRIATE_AUTH, LDAP_INSUFFICIENT_ACCESS
|
||||
* @throws SimpleSAML_Error_Exception on other errors
|
||||
*/
|
||||
public function bind($dn, $password, array $sasl_args = null)
|
||||
{
|
||||
if ($sasl_args != null) {
|
||||
if (!function_exists('ldap_sasl_bind')) {
|
||||
$ex_msg = 'Library - missing SASL support';
|
||||
throw $this->makeException($ex_msg);
|
||||
}
|
||||
|
||||
// SASL Bind, with error handling
|
||||
$authz_id = $sasl_args['authz_id'];
|
||||
$error = @ldap_sasl_bind(
|
||||
$this->ldap,
|
||||
$dn,
|
||||
$password,
|
||||
$sasl_args['mech'],
|
||||
$sasl_args['realm'],
|
||||
$sasl_args['authc_id'],
|
||||
$sasl_args['authz_id'],
|
||||
$sasl_args['props']
|
||||
);
|
||||
} else {
|
||||
// Simple Bind, with error handling
|
||||
$authz_id = $dn;
|
||||
$error = @ldap_bind($this->ldap, $dn, $password);
|
||||
}
|
||||
|
||||
if ($error === true) {
|
||||
// Good
|
||||
$this->authz_id = $authz_id;
|
||||
SimpleSAML\Logger::debug('Library - LDAP bind(): Bind successful with DN \''.$dn.'\'');
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Handle errors
|
||||
* LDAP_INVALID_CREDENTIALS
|
||||
* LDAP_INSUFFICIENT_ACCESS */
|
||||
switch (ldap_errno($this->ldap)) {
|
||||
case 32: // LDAP_NO_SUCH_OBJECT
|
||||
// no break
|
||||
case 47: // LDAP_X_PROXY_AUTHZ_FAILURE
|
||||
// no break
|
||||
case 48: // LDAP_INAPPROPRIATE_AUTH
|
||||
// no break
|
||||
case 49: // LDAP_INVALID_CREDENTIALS
|
||||
// no break
|
||||
case 50: // LDAP_INSUFFICIENT_ACCESS
|
||||
return false;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Bad
|
||||
throw $this->makeException('Library - LDAP bind(): Bind failed with DN \''.$dn.'\'');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Applies an LDAP option to the current connection.
|
||||
*
|
||||
* @throws Exception
|
||||
* @param $option
|
||||
* @param $value
|
||||
* @return void
|
||||
*/
|
||||
public function setOption($option, $value)
|
||||
{
|
||||
// Attempt to set the LDAP option
|
||||
if (!@ldap_set_option($this->ldap, $option, $value)) {
|
||||
throw $this->makeException(
|
||||
'ldap:LdapConnection->setOption : Failed to set LDAP option ['.
|
||||
$option.'] with the value ['.$value.'] error: '.ldap_error($this->ldap),
|
||||
ERR_INTERNAL
|
||||
);
|
||||
}
|
||||
|
||||
// Log debug message
|
||||
SimpleSAML\Logger::debug(
|
||||
'ldap:LdapConnection->setOption : Set the LDAP option ['.
|
||||
$option.'] with the value ['.$value.']'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Search a given DN for attributes, and return the resulting associative
|
||||
* array.
|
||||
*
|
||||
* @param string $dn
|
||||
* The DN of an element.
|
||||
* @param string|array $attributes
|
||||
* The names of the attribute(s) to retrieve. Defaults to NULL; that is,
|
||||
* all available attributes. Note that this is not very effective.
|
||||
* @param int $maxsize
|
||||
* The maximum size of any attribute's value(s). If exceeded, the attribute
|
||||
* will not be returned.
|
||||
* @return array
|
||||
* The array of attributes and their values.
|
||||
* @see http://no.php.net/manual/en/function.ldap-read.php
|
||||
*/
|
||||
public function getAttributes($dn, $attributes = null, $maxsize = null)
|
||||
{
|
||||
// Preparations, including a pretty debug message...
|
||||
$description = 'all attributes';
|
||||
if (is_array($attributes)) {
|
||||
$description = '\''.join(',', $attributes).'\'';
|
||||
} else {
|
||||
// Get all attributes...
|
||||
// TODO: Verify that this originally was the intended behaviour. Could $attributes be a string?
|
||||
$attributes = array();
|
||||
}
|
||||
SimpleSAML\Logger::debug('Library - LDAP getAttributes(): Getting '.$description.' from DN \''.$dn.'\'');
|
||||
|
||||
// Attempt to get attributes
|
||||
// TODO: Should aliases be dereferenced?
|
||||
$result = @ldap_read($this->ldap, $dn, 'objectClass=*', $attributes, 0, 0, $this->timeout);
|
||||
if ($result === false) {
|
||||
throw $this->makeException('Library - LDAP getAttributes(): Failed to get attributes from DN \''.$dn.'\'');
|
||||
}
|
||||
$entry = @ldap_first_entry($this->ldap, $result);
|
||||
if ($entry === false) {
|
||||
throw $this->makeException('Library - LDAP getAttributes(): Could not get first entry from DN \''.$dn.'\'');
|
||||
}
|
||||
$attributes = @ldap_get_attributes($this->ldap, $entry); // Recycling $attributes... Possibly bad practice.
|
||||
if ($attributes === false) {
|
||||
throw $this->makeException('Library - LDAP getAttributes(): Could not get attributes of first entry from DN \''.$dn.'\'');
|
||||
}
|
||||
|
||||
// Parsing each found attribute into our result set
|
||||
$result = array(); // Recycling $result... Possibly bad practice.
|
||||
for ($i = 0; $i < $attributes['count']; $i++) {
|
||||
// Ignore attributes that exceed the maximum allowed size
|
||||
$name = $attributes[$i];
|
||||
$attribute = $attributes[$name];
|
||||
|
||||
// Deciding whether to base64 encode
|
||||
$values = array();
|
||||
for ($j = 0; $j < $attribute['count']; $j++) {
|
||||
$value = $attribute[$j];
|
||||
|
||||
if (!empty($maxsize) && strlen($value) > $maxsize) {
|
||||
// Ignoring and warning
|
||||
SimpleSAML\Logger::warning('Library - LDAP getAttributes(): Attribute \''.
|
||||
$name.'\' exceeded maximum allowed size by '.(strlen($value) - $maxsize));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Base64 encode binary attributes
|
||||
if (strtolower($name) === 'jpegphoto' || strtolower($name) === 'objectguid' || strtolower($name) === 'ms-ds-consistencyguid') {
|
||||
$values[] = base64_encode($value);
|
||||
} else {
|
||||
$values[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// Adding
|
||||
$result[$name] = $values;
|
||||
}
|
||||
|
||||
// We're done
|
||||
SimpleSAML\Logger::debug('Library - LDAP getAttributes(): Found attributes \'('.join(',', array_keys($result)).')\'');
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Enter description here...
|
||||
*
|
||||
* @param array $config
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return array|bool
|
||||
*/
|
||||
public function validate($config, $username, $password = null)
|
||||
{
|
||||
/* Escape any characters with a special meaning in LDAP. The following
|
||||
* characters have a special meaning (according to RFC 2253):
|
||||
* ',', '+', '"', '\', '<', '>', ';', '*'
|
||||
* These characters are escaped by prefixing them with '\'.
|
||||
*/
|
||||
$username = addcslashes($username, ',+"\\<>;*');
|
||||
|
||||
if (isset($config['priv_user_dn'])) {
|
||||
$this->bind($config['priv_user_dn'], $config['priv_user_pw']);
|
||||
}
|
||||
if (isset($config['dnpattern'])) {
|
||||
$dn = str_replace('%username%', $username, $config['dnpattern']);
|
||||
} else {
|
||||
$dn = $this->searchfordn($config['searchbase'], $config['searchattributes'], $username);
|
||||
}
|
||||
|
||||
if ($password !== null) { // checking users credentials ... assuming below that she may read her own attributes ...
|
||||
// escape characters with a special meaning, also in the password
|
||||
$password = addcslashes($password, ',+"\\<>;*');
|
||||
if (!$this->bind($dn, $password)) {
|
||||
SimpleSAML\Logger::info('Library - LDAP validate(): Failed to authenticate \''.$username.'\' using DN \''.$dn.'\'');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieve attributes from LDAP
|
||||
*/
|
||||
$attributes = $this->getAttributes($dn, $config['attributes']);
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Borrowed function from PEAR:LDAP.
|
||||
*
|
||||
* Escapes the given VALUES according to RFC 2254 so that they can be safely used in LDAP filters.
|
||||
*
|
||||
* Any control characters with an ACII code < 32 as well as the characters with special meaning in
|
||||
* LDAP filters "*", "(", ")", and "\" (the backslash) are converted into the representation of a
|
||||
* backslash followed by two hex digits representing the hexadecimal value of the character.
|
||||
*
|
||||
* @static
|
||||
* @param string|array $values Array of values to escape
|
||||
* @return array Array $values, but escaped
|
||||
*/
|
||||
public static function escape_filter_value($values = array(), $singleValue = true)
|
||||
{
|
||||
// Parameter validation
|
||||
$values = \SimpleSAML\Utils\Arrays::arrayize($values);
|
||||
|
||||
foreach ($values as $key => $val) {
|
||||
// Escaping of filter meta characters
|
||||
$val = str_replace('\\', '\5c', $val);
|
||||
$val = str_replace('*', '\2a', $val);
|
||||
$val = str_replace('(', '\28', $val);
|
||||
$val = str_replace(')', '\29', $val);
|
||||
|
||||
// ASCII < 32 escaping
|
||||
$val = self::asc2hex32($val);
|
||||
|
||||
if (null === $val) {
|
||||
$val = '\0'; // apply escaped "null" if string is empty
|
||||
}
|
||||
|
||||
$values[$key] = $val;
|
||||
}
|
||||
if ($singleValue) {
|
||||
return $values[0];
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Borrowed function from PEAR:LDAP.
|
||||
*
|
||||
* Converts all ASCII chars < 32 to "\HEX"
|
||||
*
|
||||
* @param string $string String to convert
|
||||
*
|
||||
* @static
|
||||
* @return string
|
||||
*/
|
||||
public static function asc2hex32($string)
|
||||
{
|
||||
for ($i = 0; $i < strlen($string); $i++) {
|
||||
$char = substr($string, $i, 1);
|
||||
if (ord($char) < 32) {
|
||||
$hex = dechex(ord($char));
|
||||
if (strlen($hex) == 1) {
|
||||
$hex = '0'.$hex;
|
||||
}
|
||||
$string = str_replace($char, '\\'.$hex, $string);
|
||||
}
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert SASL authz_id into a DN
|
||||
*/
|
||||
private function authzid_to_dn($searchBase, $searchAttributes, $authz_id)
|
||||
{
|
||||
if (preg_match("/^dn:/", $authz_id)) {
|
||||
return preg_replace("/^dn:/", "", $authz_id);
|
||||
}
|
||||
|
||||
if (preg_match("/^u:/", $authz_id)) {
|
||||
return $this->searchfordn(
|
||||
$searchBase,
|
||||
$searchAttributes,
|
||||
preg_replace("/^u:/", "", $authz_id)
|
||||
);
|
||||
}
|
||||
return $authz_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* ldap_exop_whoami accessor, if available. Use requested authz_id
|
||||
* otherwise.
|
||||
*
|
||||
* ldap_exop_whoami() has been provided as a third party patch that
|
||||
* waited several years to get its way upstream:
|
||||
* http://cvsweb.netbsd.org/bsdweb.cgi/pkgsrc/databases/php-ldap/files
|
||||
*
|
||||
* When it was integrated into PHP repository, the function prototype
|
||||
* was changed, The new prototype was used in third party patch for
|
||||
* PHP 7.0 and 7.1, hence the version test below.
|
||||
*/
|
||||
public function whoami($searchBase, $searchAttributes)
|
||||
{
|
||||
$authz_id = '';
|
||||
if (function_exists('ldap_exop_whoami')) {
|
||||
if (version_compare(phpversion(), '7', '<')) {
|
||||
if (ldap_exop_whoami($this->ldap, $authz_id) !== true) {
|
||||
throw $this->makeException('LDAP whoami exop failure');
|
||||
}
|
||||
} else {
|
||||
if (($authz_id = ldap_exop_whoami($this->ldap)) === false) {
|
||||
throw $this->makeException('LDAP whoami exop failure');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$authz_id = $this->authz_id;
|
||||
}
|
||||
|
||||
$dn = $this->authzid_to_dn($searchBase, $searchAttributes, $authz_id);
|
||||
|
||||
if (!isset($dn) || ($dn == '')) {
|
||||
throw $this->makeException('Cannot figure userID');
|
||||
}
|
||||
|
||||
return $dn;
|
||||
}
|
||||
}
|
||||
369
lib/SimpleSAML/Auth/ProcessingChain.php
Executable file
369
lib/SimpleSAML/Auth/ProcessingChain.php
Executable file
@@ -0,0 +1,369 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class for implementing authentication processing chains for IdPs.
|
||||
*
|
||||
* This class implements a system for additional steps which should be taken by an IdP before
|
||||
* submitting a response to a SP. Examples of additional steps can be additional authentication
|
||||
* checks, or attribute consent requirements.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS.
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SimpleSAML_Auth_ProcessingChain
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* The list of remaining filters which should be applied to the state.
|
||||
*/
|
||||
const FILTERS_INDEX = 'SimpleSAML_Auth_ProcessingChain.filters';
|
||||
|
||||
|
||||
/**
|
||||
* The stage we use for completed requests.
|
||||
*/
|
||||
const COMPLETED_STAGE = 'SimpleSAML_Auth_ProcessingChain.completed';
|
||||
|
||||
|
||||
/**
|
||||
* The request parameter we will use to pass the state identifier when we redirect after
|
||||
* having completed processing of the state.
|
||||
*/
|
||||
const AUTHPARAM = 'AuthProcId';
|
||||
|
||||
|
||||
/**
|
||||
* All authentication processing filters, in the order they should be applied.
|
||||
*/
|
||||
private $filters;
|
||||
|
||||
|
||||
/**
|
||||
* Initialize an authentication processing chain for the given service provider
|
||||
* and identity provider.
|
||||
*
|
||||
* @param array $idpMetadata The metadata for the IdP.
|
||||
* @param array $spMetadata The metadata for the SP.
|
||||
*/
|
||||
public function __construct($idpMetadata, $spMetadata, $mode = 'idp')
|
||||
{
|
||||
assert(is_array($idpMetadata));
|
||||
assert(is_array($spMetadata));
|
||||
|
||||
$this->filters = array();
|
||||
|
||||
$config = SimpleSAML_Configuration::getInstance();
|
||||
$configauthproc = $config->getArray('authproc.' . $mode, null);
|
||||
|
||||
if (!empty($configauthproc)) {
|
||||
$configfilters = self::parseFilterList($configauthproc);
|
||||
self::addFilters($this->filters, $configfilters);
|
||||
}
|
||||
|
||||
if (array_key_exists('authproc', $idpMetadata)) {
|
||||
$idpFilters = self::parseFilterList($idpMetadata['authproc']);
|
||||
self::addFilters($this->filters, $idpFilters);
|
||||
}
|
||||
|
||||
if (array_key_exists('authproc', $spMetadata)) {
|
||||
$spFilters = self::parseFilterList($spMetadata['authproc']);
|
||||
self::addFilters($this->filters, $spFilters);
|
||||
}
|
||||
|
||||
|
||||
SimpleSAML\Logger::debug('Filter config for ' . $idpMetadata['entityid'] . '->' .
|
||||
$spMetadata['entityid'] . ': ' . str_replace("\n", '', var_export($this->filters, true)));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sort & merge filter configuration
|
||||
*
|
||||
* Inserts unsorted filters into sorted filter list. This sort operation is stable.
|
||||
*
|
||||
* @param array &$target Target filter list. This list must be sorted.
|
||||
* @param array $src Source filters. May be unsorted.
|
||||
*/
|
||||
private static function addFilters(&$target, $src)
|
||||
{
|
||||
assert(is_array($target));
|
||||
assert(is_array($src));
|
||||
|
||||
foreach ($src as $filter) {
|
||||
$fp = $filter->priority;
|
||||
|
||||
// Find insertion position for filter
|
||||
for ($i = count($target)-1; $i >= 0; $i--) {
|
||||
if ($target[$i]->priority <= $fp) {
|
||||
// The new filter should be inserted after this one
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* $i now points to the filter which should preceede the current filter. */
|
||||
array_splice($target, $i+1, 0, array($filter));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse an array of authentication processing filters.
|
||||
*
|
||||
* @param array $filterSrc Array with filter configuration.
|
||||
* @return array Array of SimpleSAML_Auth_ProcessingFilter objects.
|
||||
*/
|
||||
private static function parseFilterList($filterSrc)
|
||||
{
|
||||
assert(is_array($filterSrc));
|
||||
|
||||
$parsedFilters = array();
|
||||
|
||||
foreach ($filterSrc as $priority => $filter) {
|
||||
if (is_string($filter)) {
|
||||
$filter = array('class' => $filter);
|
||||
}
|
||||
|
||||
if (!is_array($filter)) {
|
||||
throw new Exception('Invalid authentication processing filter configuration: ' .
|
||||
'One of the filters wasn\'t a string or an array.');
|
||||
}
|
||||
|
||||
$parsedFilters[] = self::parseFilter($filter, $priority);
|
||||
}
|
||||
|
||||
return $parsedFilters;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse an authentication processing filter.
|
||||
*
|
||||
* @param array $config Array with the authentication processing filter configuration.
|
||||
* @param int $priority The priority of the current filter, (not included in the filter
|
||||
* definition.)
|
||||
* @return SimpleSAML_Auth_ProcessingFilter The parsed filter.
|
||||
*/
|
||||
private static function parseFilter($config, $priority)
|
||||
{
|
||||
assert(is_array($config));
|
||||
|
||||
if (!array_key_exists('class', $config)) {
|
||||
throw new Exception('Authentication processing filter without name given.');
|
||||
}
|
||||
|
||||
$className = SimpleSAML\Module::resolveClass($config['class'], 'Auth_Process', 'SimpleSAML_Auth_ProcessingFilter');
|
||||
$config['%priority'] = $priority;
|
||||
unset($config['class']);
|
||||
return new $className($config, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process the given state.
|
||||
*
|
||||
* This function will only return if processing completes. If processing requires showing
|
||||
* a page to the user, we will not be able to return from this function. There are two ways
|
||||
* this can be handled:
|
||||
* - Redirect to a URL: We will redirect to the URL set in $state['ReturnURL'].
|
||||
* - Call a function: We will call the function set in $state['ReturnCall'].
|
||||
*
|
||||
* If an exception is thrown during processing, it should be handled by the caller of
|
||||
* this function. If the user has redirected to a different page, the exception will be
|
||||
* returned through the exception handler defined on the state array. See
|
||||
* SimpleSAML_Auth_State for more information.
|
||||
*
|
||||
* @see SimpleSAML_Auth_State
|
||||
* @see SimpleSAML_Auth_State::EXCEPTION_HANDLER_URL
|
||||
* @see SimpleSAML_Auth_State::EXCEPTION_HANDLER_FUNC
|
||||
*
|
||||
* @param array &$state The state we are processing.
|
||||
*/
|
||||
public function processState(&$state)
|
||||
{
|
||||
assert(is_array($state));
|
||||
assert(array_key_exists('ReturnURL', $state) || array_key_exists('ReturnCall', $state));
|
||||
assert(!array_key_exists('ReturnURL', $state) || !array_key_exists('ReturnCall', $state));
|
||||
|
||||
$state[self::FILTERS_INDEX] = $this->filters;
|
||||
|
||||
try {
|
||||
// TODO: remove this in SSP 2.0
|
||||
if (!array_key_exists('UserID', $state)) {
|
||||
// No unique user ID present. Attempt to add one.
|
||||
self::addUserID($state);
|
||||
}
|
||||
|
||||
while (count($state[self::FILTERS_INDEX]) > 0) {
|
||||
$filter = array_shift($state[self::FILTERS_INDEX]);
|
||||
$filter->process($state);
|
||||
}
|
||||
} catch (SimpleSAML_Error_Exception $e) {
|
||||
// No need to convert the exception
|
||||
throw $e;
|
||||
} catch (Exception $e) {
|
||||
/*
|
||||
* To be consistent with the exception we return after an redirect,
|
||||
* we convert this exception before returning it.
|
||||
*/
|
||||
throw new SimpleSAML_Error_UnserializableException($e);
|
||||
}
|
||||
|
||||
// Completed
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Continues processing of the state.
|
||||
*
|
||||
* This function is used to resume processing by filters which for example needed to show
|
||||
* a page to the user.
|
||||
*
|
||||
* This function will never return. Exceptions thrown during processing will be passed
|
||||
* to whatever exception handler is defined in the state array.
|
||||
*
|
||||
* @param array $state The state we are processing.
|
||||
*/
|
||||
public static function resumeProcessing($state)
|
||||
{
|
||||
assert(is_array($state));
|
||||
|
||||
while (count($state[self::FILTERS_INDEX]) > 0) {
|
||||
$filter = array_shift($state[self::FILTERS_INDEX]);
|
||||
try {
|
||||
$filter->process($state);
|
||||
} catch (SimpleSAML_Error_Exception $e) {
|
||||
SimpleSAML_Auth_State::throwException($state, $e);
|
||||
} catch (Exception $e) {
|
||||
$e = new SimpleSAML_Error_UnserializableException($e);
|
||||
SimpleSAML_Auth_State::throwException($state, $e);
|
||||
}
|
||||
}
|
||||
|
||||
// Completed
|
||||
|
||||
assert(array_key_exists('ReturnURL', $state) || array_key_exists('ReturnCall', $state));
|
||||
assert(!array_key_exists('ReturnURL', $state) || !array_key_exists('ReturnCall', $state));
|
||||
|
||||
|
||||
if (array_key_exists('ReturnURL', $state)) {
|
||||
/*
|
||||
* Save state information, and redirect to the URL specified
|
||||
* in $state['ReturnURL'].
|
||||
*/
|
||||
$id = SimpleSAML_Auth_State::saveState($state, self::COMPLETED_STAGE);
|
||||
\SimpleSAML\Utils\HTTP::redirectTrustedURL($state['ReturnURL'], array(self::AUTHPARAM => $id));
|
||||
} else {
|
||||
/* Pass the state to the function defined in $state['ReturnCall']. */
|
||||
|
||||
// We are done with the state array in the session. Delete it.
|
||||
SimpleSAML_Auth_State::deleteState($state);
|
||||
|
||||
$func = $state['ReturnCall'];
|
||||
assert(is_callable($func));
|
||||
|
||||
call_user_func($func, $state);
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process the given state passivly.
|
||||
*
|
||||
* Modules with user interaction are expected to throw an \SimpleSAML\Module\saml\Error\NoPassive exception
|
||||
* which are silently ignored. Exceptions of other types are passed further up the call stack.
|
||||
*
|
||||
* This function will only return if processing completes.
|
||||
*
|
||||
* @param array &$state The state we are processing.
|
||||
*/
|
||||
public function processStatePassive(&$state)
|
||||
{
|
||||
assert(is_array($state));
|
||||
// Should not be set when calling this method
|
||||
assert(!array_key_exists('ReturnURL', $state));
|
||||
|
||||
// Notify filters about passive request
|
||||
$state['isPassive'] = true;
|
||||
|
||||
$state[self::FILTERS_INDEX] = $this->filters;
|
||||
|
||||
// TODO: remove this in SSP 2.0
|
||||
if (!array_key_exists('UserID', $state)) {
|
||||
// No unique user ID present. Attempt to add one.
|
||||
self::addUserID($state);
|
||||
}
|
||||
|
||||
while (count($state[self::FILTERS_INDEX]) > 0) {
|
||||
$filter = array_shift($state[self::FILTERS_INDEX]);
|
||||
try {
|
||||
$filter->process($state);
|
||||
// Ignore SimpleSAML_Error_NoPassive exceptions
|
||||
} catch (SimpleSAML_Error_NoPassive $e) {
|
||||
// @deprecated will be removed in 2.0
|
||||
// Ignore \SimpleSAML\Error\NoPassive exceptions
|
||||
} catch (\SimpleSAML\Module\saml\Error\NoPassive $e) {
|
||||
// Ignore \SimpleSAML\Module\saml\Error\NoPassive exceptions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a state which has finished processing.
|
||||
*
|
||||
* @param string $id The state identifier.
|
||||
* @see SimpleSAML_Auth_State::parseStateID()
|
||||
* @return Array The state referenced by the $id parameter.
|
||||
*/
|
||||
public static function fetchProcessedState($id)
|
||||
{
|
||||
assert(is_string($id));
|
||||
|
||||
return SimpleSAML_Auth_State::loadState($id, self::COMPLETED_STAGE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0.
|
||||
*/
|
||||
private static function addUserID(&$state)
|
||||
{
|
||||
assert(is_array($state));
|
||||
assert(array_key_exists('Attributes', $state));
|
||||
|
||||
if (isset($state['Destination']['userid.attribute'])) {
|
||||
$attributeName = $state['Destination']['userid.attribute'];
|
||||
SimpleSAML\Logger::warning("The 'userid.attribute' option has been deprecated.");
|
||||
} elseif (isset($state['Source']['userid.attribute'])) {
|
||||
$attributeName = $state['Source']['userid.attribute'];
|
||||
SimpleSAML\Logger::warning("The 'userid.attribute' option has been deprecated.");
|
||||
} else {
|
||||
// Default attribute
|
||||
$attributeName = 'eduPersonPrincipalName';
|
||||
}
|
||||
|
||||
if (!array_key_exists($attributeName, $state['Attributes'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$uid = $state['Attributes'][$attributeName];
|
||||
if (count($uid) === 0) {
|
||||
SimpleSAML\Logger::warning('Empty user id attribute [' . $attributeName . '].');
|
||||
return;
|
||||
}
|
||||
|
||||
if (count($uid) > 1) {
|
||||
SimpleSAML\Logger::warning('Multiple attribute values for user id attribute [' . $attributeName . '].');
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: the attribute value should be trimmed
|
||||
$uid = $uid[0];
|
||||
|
||||
if (empty($uid)) {
|
||||
SimpleSAML\Logger::warning('Empty value in attribute '.$attributeName.". on user. Cannot set UserID.");
|
||||
return;
|
||||
}
|
||||
$state['UserID'] = $uid;
|
||||
}
|
||||
}
|
||||
67
lib/SimpleSAML/Auth/ProcessingFilter.php
Executable file
67
lib/SimpleSAML/Auth/ProcessingFilter.php
Executable file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* Base class for authentication processing filters.
|
||||
*
|
||||
* All authentication processing filters must support serialization.
|
||||
*
|
||||
* The current request is stored in an associative array. It has the following defined attributes:
|
||||
* - 'Attributes' The attributes of the user.
|
||||
* - 'Destination' Metadata of the destination (SP).
|
||||
* - 'Source' Metadata of the source (IdP).
|
||||
*
|
||||
* It may also contain other attributes. If an authentication processing filter wishes to store other
|
||||
* information in it, it should have a name on the form 'module:filter:attributename', to avoid name
|
||||
* collisions.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS.
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
abstract class SimpleSAML_Auth_ProcessingFilter
|
||||
{
|
||||
|
||||
/**
|
||||
* Priority of this filter.
|
||||
*
|
||||
* Used when merging IdP and SP processing chains.
|
||||
* The priority can be any integer. The default for most filters is 50. Filters may however
|
||||
* specify their own default, if they typically should be amongst the first or the last filters.
|
||||
*
|
||||
* The prioroty can also be overridden by the user by specifying the '%priority' option.
|
||||
*/
|
||||
public $priority = 50;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor for a processing filter.
|
||||
*
|
||||
* Any processing filter which implements its own constructor must call this
|
||||
* constructor first.
|
||||
*
|
||||
* @param array &$config Configuration for this filter.
|
||||
* @param mixed $reserved For future use.
|
||||
*/
|
||||
public function __construct(&$config, $reserved)
|
||||
{
|
||||
assert(is_array($config));
|
||||
|
||||
if (array_key_exists('%priority', $config)) {
|
||||
$this->priority = $config['%priority'];
|
||||
if (!is_int($this->priority)) {
|
||||
throw new Exception('Invalid priority: ' . var_export($this->priority, true));
|
||||
}
|
||||
unset($config['%priority']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process a request.
|
||||
*
|
||||
* When a filter returns from this function, it is assumed to have completed its task.
|
||||
*
|
||||
* @param array &$request The request we are currently processing.
|
||||
*/
|
||||
abstract public function process(&$request);
|
||||
}
|
||||
401
lib/SimpleSAML/Auth/Simple.php
Executable file
401
lib/SimpleSAML/Auth/Simple.php
Executable file
@@ -0,0 +1,401 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleSAML\Auth;
|
||||
|
||||
use \SimpleSAML_Auth_Source as Source;
|
||||
use \SimpleSAML_Auth_State as State;
|
||||
use \SimpleSAML_Configuration as Configuration;
|
||||
use \SimpleSAML_Error_AuthSource as AuthSourceError;
|
||||
use \SimpleSAML\Module;
|
||||
use \SimpleSAML_Session as Session;
|
||||
use \SimpleSAML\Utils\HTTP;
|
||||
|
||||
/**
|
||||
* Helper class for simple authentication applications.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class Simple
|
||||
{
|
||||
|
||||
/**
|
||||
* The id of the authentication source we are accessing.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $authSource;
|
||||
|
||||
/**
|
||||
* @var \SimpleSAML_Configuration|null
|
||||
*/
|
||||
protected $app_config;
|
||||
|
||||
/**
|
||||
* Create an instance with the specified authsource.
|
||||
*
|
||||
* @param string $authSource The id of the authentication source.
|
||||
*/
|
||||
public function __construct($authSource)
|
||||
{
|
||||
assert(is_string($authSource));
|
||||
|
||||
$this->authSource = $authSource;
|
||||
$this->app_config = Configuration::getInstance()->getConfigItem('application', null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the implementing authentication source.
|
||||
*
|
||||
* @return \SimpleSAML_Auth_Source The authentication source.
|
||||
*
|
||||
* @throws \SimpleSAML_Error_AuthSource If the requested auth source is unknown.
|
||||
*/
|
||||
public function getAuthSource()
|
||||
{
|
||||
$as = Source::getById($this->authSource);
|
||||
if ($as === null) {
|
||||
throw new AuthSourceError($this->authSource, 'Unknown authentication source.');
|
||||
}
|
||||
return $as;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the user is authenticated.
|
||||
*
|
||||
* This function checks if the user is authenticated with the default authentication source selected by the
|
||||
* 'default-authsource' option in 'config.php'.
|
||||
*
|
||||
* @return bool True if the user is authenticated, false if not.
|
||||
*/
|
||||
public function isAuthenticated()
|
||||
{
|
||||
$session = Session::getSessionFromRequest();
|
||||
|
||||
return $session->isValid($this->authSource);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Require the user to be authenticated.
|
||||
*
|
||||
* If the user is authenticated, this function returns immediately.
|
||||
*
|
||||
* If the user isn't authenticated, this function will authenticate the user with the authentication source, and
|
||||
* then return the user to the current page.
|
||||
*
|
||||
* This function accepts an array $params, which controls some parts of the authentication. See the login()
|
||||
* method for a description.
|
||||
*
|
||||
* @param array $params Various options to the authentication request. See the documentation.
|
||||
*/
|
||||
public function requireAuth(array $params = array())
|
||||
{
|
||||
|
||||
$session = Session::getSessionFromRequest();
|
||||
|
||||
if ($session->isValid($this->authSource)) {
|
||||
// Already authenticated
|
||||
return;
|
||||
}
|
||||
|
||||
$this->login($params);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Start an authentication process.
|
||||
*
|
||||
* This function accepts an array $params, which controls some parts of the authentication. The accepted parameters
|
||||
* depends on the authentication source being used. Some parameters are generic:
|
||||
* - 'ErrorURL': A URL that should receive errors from the authentication.
|
||||
* - 'KeepPost': If the current request is a POST request, keep the POST data until after the authentication.
|
||||
* - 'ReturnTo': The URL the user should be returned to after authentication.
|
||||
* - 'ReturnCallback': The function we should call after the user has finished authentication.
|
||||
*
|
||||
* Please note: this function never returns.
|
||||
*
|
||||
* @param array $params Various options to the authentication request.
|
||||
*/
|
||||
public function login(array $params = array())
|
||||
{
|
||||
|
||||
if (array_key_exists('KeepPost', $params)) {
|
||||
$keepPost = (bool) $params['KeepPost'];
|
||||
} else {
|
||||
$keepPost = true;
|
||||
}
|
||||
|
||||
if (array_key_exists('ReturnTo', $params)) {
|
||||
$returnTo = (string) $params['ReturnTo'];
|
||||
} else {
|
||||
if (array_key_exists('ReturnCallback', $params)) {
|
||||
$returnTo = (array) $params['ReturnCallback'];
|
||||
} else {
|
||||
$returnTo = HTTP::getSelfURL();
|
||||
}
|
||||
}
|
||||
|
||||
if (is_string($returnTo) && $keepPost && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$returnTo = HTTP::getPOSTRedirectURL($returnTo, $_POST);
|
||||
}
|
||||
|
||||
if (array_key_exists('ErrorURL', $params)) {
|
||||
$errorURL = (string) $params['ErrorURL'];
|
||||
} else {
|
||||
$errorURL = null;
|
||||
}
|
||||
|
||||
|
||||
if (!isset($params[State::RESTART]) && is_string($returnTo)) {
|
||||
/*
|
||||
* A URL to restart the authentication, in case the user bookmarks
|
||||
* something, e.g. the discovery service page.
|
||||
*/
|
||||
$restartURL = $this->getLoginURL($returnTo);
|
||||
$params[State::RESTART] = $restartURL;
|
||||
}
|
||||
|
||||
$as = $this->getAuthSource();
|
||||
$as->initLogin($returnTo, $errorURL, $params);
|
||||
assert(false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log the user out.
|
||||
*
|
||||
* This function logs the user out. It will never return. By default, it will cause a redirect to the current page
|
||||
* after logging the user out, but a different URL can be given with the $params parameter.
|
||||
*
|
||||
* Generic parameters are:
|
||||
* - 'ReturnTo': The URL the user should be returned to after logout.
|
||||
* - 'ReturnCallback': The function that should be called after logout.
|
||||
* - 'ReturnStateParam': The parameter we should return the state in when redirecting.
|
||||
* - 'ReturnStateStage': The stage the state array should be saved with.
|
||||
*
|
||||
* @param string|array|null $params Either the URL the user should be redirected to after logging out, or an array
|
||||
* with parameters for the logout. If this parameter is null, we will return to the current page.
|
||||
*/
|
||||
public function logout($params = null)
|
||||
{
|
||||
assert(is_array($params) || is_string($params) || $params === null);
|
||||
|
||||
if ($params === null) {
|
||||
$params = HTTP::getSelfURL();
|
||||
}
|
||||
|
||||
if (is_string($params)) {
|
||||
$params = array(
|
||||
'ReturnTo' => $params,
|
||||
);
|
||||
}
|
||||
|
||||
assert(is_array($params));
|
||||
assert(isset($params['ReturnTo']) || isset($params['ReturnCallback']));
|
||||
|
||||
if (isset($params['ReturnStateParam']) || isset($params['ReturnStateStage'])) {
|
||||
assert(isset($params['ReturnStateParam'], $params['ReturnStateStage']));
|
||||
}
|
||||
|
||||
$session = Session::getSessionFromRequest();
|
||||
if ($session->isValid($this->authSource)) {
|
||||
$state = $session->getAuthData($this->authSource, 'LogoutState');
|
||||
if ($state !== null) {
|
||||
$params = array_merge($state, $params);
|
||||
}
|
||||
|
||||
$session->doLogout($this->authSource);
|
||||
|
||||
$params['LogoutCompletedHandler'] = array(get_class(), 'logoutCompleted');
|
||||
|
||||
$as = Source::getById($this->authSource);
|
||||
if ($as !== null) {
|
||||
$as->logout($params);
|
||||
}
|
||||
}
|
||||
|
||||
self::logoutCompleted($params);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when logout operation completes.
|
||||
*
|
||||
* This function never returns.
|
||||
*
|
||||
* @param array $state The state after the logout.
|
||||
*/
|
||||
public static function logoutCompleted($state)
|
||||
{
|
||||
assert(is_array($state));
|
||||
assert(isset($state['ReturnTo']) || isset($state['ReturnCallback']));
|
||||
|
||||
if (isset($state['ReturnCallback'])) {
|
||||
call_user_func($state['ReturnCallback'], $state);
|
||||
assert(false);
|
||||
} else {
|
||||
$params = array();
|
||||
if (isset($state['ReturnStateParam']) || isset($state['ReturnStateStage'])) {
|
||||
assert(isset($state['ReturnStateParam'], $state['ReturnStateStage']));
|
||||
$stateID = State::saveState($state, $state['ReturnStateStage']);
|
||||
$params[$state['ReturnStateParam']] = $stateID;
|
||||
}
|
||||
\SimpleSAML\Utils\HTTP::redirectTrustedURL($state['ReturnTo'], $params);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve attributes of the current user.
|
||||
*
|
||||
* This function will retrieve the attributes of the current user if the user is authenticated. If the user isn't
|
||||
* authenticated, it will return an empty array.
|
||||
*
|
||||
* @return array The users attributes.
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
|
||||
if (!$this->isAuthenticated()) {
|
||||
// Not authenticated
|
||||
return array();
|
||||
}
|
||||
|
||||
// Authenticated
|
||||
$session = Session::getSessionFromRequest();
|
||||
return $session->getAuthData($this->authSource, 'Attributes');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve authentication data.
|
||||
*
|
||||
* @param string $name The name of the parameter, e.g. 'Attributes', 'Expire' or 'saml:sp:IdP'.
|
||||
*
|
||||
* @return mixed|null The value of the parameter, or null if it isn't found or we are unauthenticated.
|
||||
*/
|
||||
public function getAuthData($name)
|
||||
{
|
||||
assert(is_string($name));
|
||||
|
||||
if (!$this->isAuthenticated()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$session = Session::getSessionFromRequest();
|
||||
return $session->getAuthData($this->authSource, $name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve all authentication data.
|
||||
*
|
||||
* @return array|null All persistent authentication data, or null if we aren't authenticated.
|
||||
*/
|
||||
public function getAuthDataArray()
|
||||
{
|
||||
|
||||
if (!$this->isAuthenticated()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$session = Session::getSessionFromRequest();
|
||||
return $session->getAuthState($this->authSource);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve a URL that can be used to log the user in.
|
||||
*
|
||||
* @param string|null $returnTo The page the user should be returned to afterwards. If this parameter is null, the
|
||||
* user will be returned to the current page.
|
||||
*
|
||||
* @return string A URL which is suitable for use in link-elements.
|
||||
*/
|
||||
public function getLoginURL($returnTo = null)
|
||||
{
|
||||
assert($returnTo === null || is_string($returnTo));
|
||||
|
||||
if ($returnTo === null) {
|
||||
$returnTo = HTTP::getSelfURL();
|
||||
}
|
||||
|
||||
$login = Module::getModuleURL('core/as_login.php', array(
|
||||
'AuthId' => $this->authSource,
|
||||
'ReturnTo' => $returnTo,
|
||||
));
|
||||
|
||||
return $login;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve a URL that can be used to log the user out.
|
||||
*
|
||||
* @param string|null $returnTo The page the user should be returned to afterwards. If this parameter is null, the
|
||||
* user will be returned to the current page.
|
||||
*
|
||||
* @return string A URL which is suitable for use in link-elements.
|
||||
*/
|
||||
public function getLogoutURL($returnTo = null)
|
||||
{
|
||||
assert($returnTo === null || is_string($returnTo));
|
||||
|
||||
if ($returnTo === null) {
|
||||
$returnTo = HTTP::getSelfURL();
|
||||
}
|
||||
|
||||
$logout = Module::getModuleURL('core/as_logout.php', array(
|
||||
'AuthId' => $this->authSource,
|
||||
'ReturnTo' => $returnTo,
|
||||
));
|
||||
|
||||
return $logout;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process a URL and modify it according to the application/baseURL configuration option, if present.
|
||||
*
|
||||
* @param string|null $url The URL to process, or null if we want to use the current URL. Both partial and full
|
||||
* URLs can be used as a parameter. The maximum precedence is given to the application/baseURL configuration option,
|
||||
* then the URL specified (if it specifies scheme, host and port) and finally the environment observed in the
|
||||
* server.
|
||||
*
|
||||
* @return string The URL modified according to the precedence rules.
|
||||
*/
|
||||
protected function getProcessedURL($url = null)
|
||||
{
|
||||
if ($url === null) {
|
||||
$url = HTTP::getSelfURL();
|
||||
}
|
||||
|
||||
$scheme = parse_url($url, PHP_URL_SCHEME);
|
||||
$host = parse_url($url, PHP_URL_HOST) ?: HTTP::getSelfHost();
|
||||
$port = parse_url($url, PHP_URL_PORT) ?: (
|
||||
$scheme ? '' : trim(HTTP::getServerPort(), ':')
|
||||
);
|
||||
$scheme = $scheme ?: (HTTP::getServerHTTPS() ? 'https' : 'http');
|
||||
$path = parse_url($url, PHP_URL_PATH) ?: '/';
|
||||
$query = parse_url($url, PHP_URL_QUERY) ?: '';
|
||||
$fragment = parse_url($url, PHP_URL_FRAGMENT) ?: '';
|
||||
|
||||
$port = !empty($port) ? ':'.$port : '';
|
||||
if (($scheme === 'http' && $port === ':80') || ($scheme === 'https' && $port === ':443')) {
|
||||
$port = '';
|
||||
}
|
||||
|
||||
if (is_null($this->app_config)) {
|
||||
// nothing more we can do here
|
||||
return $scheme.'://'.$host.$port.$path.($query ? '?'.$query : '').($fragment ? '#'.$fragment : '');
|
||||
}
|
||||
|
||||
$base = trim($this->app_config->getString(
|
||||
'baseURL',
|
||||
$scheme.'://'.$host.$port
|
||||
), '/');
|
||||
return $base.$path.($query ? '?'.$query : '').($fragment ? '#'.$fragment : '');
|
||||
}
|
||||
}
|
||||
513
lib/SimpleSAML/Auth/Source.php
Executable file
513
lib/SimpleSAML/Auth/Source.php
Executable file
@@ -0,0 +1,513 @@
|
||||
<?php
|
||||
|
||||
use SimpleSAML\Auth\SourceFactory;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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);
|
||||
|
||||
self::validateSource($source, $id);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Start authentication.
|
||||
*
|
||||
* This method never returns.
|
||||
*
|
||||
* @param string|array $return The URL or function we should direct the user to after authentication. If using a
|
||||
* URL obtained from user input, please make sure to check it by calling \SimpleSAML\Utils\HTTP::checkURLAllowed().
|
||||
* @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. If using a URL obtained from user input, please make sure to
|
||||
* check it by calling \SimpleSAML\Utils\HTTP::checkURLAllowed().
|
||||
* @param array $params Extra information about the login. Different authentication requestors may provide different
|
||||
* information. Optional, will default to an empty array.
|
||||
*/
|
||||
public function initLogin($return, $errorURL = null, array $params = array())
|
||||
{
|
||||
assert(is_string($return) || is_array($return));
|
||||
assert(is_string($errorURL) || $errorURL === null);
|
||||
|
||||
$state = array_merge($params, array(
|
||||
'SimpleSAML_Auth_Default.id' => $this->authId, // TODO: remove in 2.0
|
||||
'SimpleSAML_Auth_Source.id' => $this->authId,
|
||||
'SimpleSAML_Auth_Default.Return' => $return, // TODO: remove in 2.0
|
||||
'SimpleSAML_Auth_Source.Return' => $return,
|
||||
'SimpleSAML_Auth_Default.ErrorURL' => $errorURL, // TODO: remove in 2.0
|
||||
'SimpleSAML_Auth_Source.ErrorURL' => $errorURL,
|
||||
'LoginCompletedHandler' => array(get_class(), 'loginCompleted'),
|
||||
'LogoutCallback' => array(get_class(), 'logoutCallback'),
|
||||
'LogoutCallbackState' => array(
|
||||
'SimpleSAML_Auth_Default.logoutSource' => $this->authId, // TODO: remove in 2.0
|
||||
'SimpleSAML_Auth_Source.logoutSource' => $this->authId,
|
||||
),
|
||||
));
|
||||
|
||||
if (is_string($return)) {
|
||||
$state['SimpleSAML_Auth_Default.ReturnURL'] = $return; // TODO: remove in 2.0
|
||||
$state['SimpleSAML_Auth_Source.ReturnURL'] = $return;
|
||||
}
|
||||
|
||||
if ($errorURL !== null) {
|
||||
$state[SimpleSAML_Auth_State::EXCEPTION_HANDLER_URL] = $errorURL;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->authenticate($state);
|
||||
} catch (SimpleSAML_Error_Exception $e) {
|
||||
SimpleSAML_Auth_State::throwException($state, $e);
|
||||
} catch (Exception $e) {
|
||||
$e = new SimpleSAML_Error_UnserializableException($e);
|
||||
SimpleSAML_Auth_State::throwException($state, $e);
|
||||
}
|
||||
self::loginCompleted($state);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when a login operation has finished.
|
||||
*
|
||||
* This method never returns.
|
||||
*
|
||||
* @param array $state The state after the login has completed.
|
||||
*/
|
||||
public static function loginCompleted($state)
|
||||
{
|
||||
assert(is_array($state));
|
||||
assert(array_key_exists('SimpleSAML_Auth_Source.Return', $state));
|
||||
assert(array_key_exists('SimpleSAML_Auth_Source.id', $state));
|
||||
assert(array_key_exists('Attributes', $state));
|
||||
assert(!array_key_exists('LogoutState', $state) || is_array($state['LogoutState']));
|
||||
|
||||
$return = $state['SimpleSAML_Auth_Source.Return'];
|
||||
|
||||
// save session state
|
||||
$session = SimpleSAML_Session::getSessionFromRequest();
|
||||
$authId = $state['SimpleSAML_Auth_Source.id'];
|
||||
$session->doLogin($authId, SimpleSAML_Auth_State::getPersistentAuthData($state));
|
||||
|
||||
if (is_string($return)) { // redirect...
|
||||
\SimpleSAML\Utils\HTTP::redirectTrustedURL($return);
|
||||
} else {
|
||||
call_user_func($return, $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));
|
||||
|
||||
self::validateSource($config, $authId);
|
||||
|
||||
$id = $config[0];
|
||||
$info = array('AuthId' => $authId);
|
||||
$authSource = null;
|
||||
|
||||
unset($config[0]);
|
||||
|
||||
try {
|
||||
// Check whether or not there's a factory responsible for instantiating our Auth Source instance
|
||||
$factoryClass = SimpleSAML\Module::resolveClass($id, 'Auth_Source_Factory', 'SimpleSAML\Auth\SourceFactory');
|
||||
|
||||
/** @var SourceFactory $factory */
|
||||
$factory = new $factoryClass;
|
||||
$authSource = $factory->create($info, $config);
|
||||
} catch (Exception $e) {
|
||||
// If not, instantiate the Auth Source here
|
||||
$className = SimpleSAML\Module::resolveClass($id, 'Auth_Source', 'SimpleSAML_Auth_Source');
|
||||
$authSource = new $className($info, $config);
|
||||
}
|
||||
|
||||
return $authSource;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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($type === null || 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).'.'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when the authentication source receives an external logout request.
|
||||
*
|
||||
* @param array $state State array for the logout operation.
|
||||
*/
|
||||
public static function logoutCallback($state)
|
||||
{
|
||||
assert(is_array($state));
|
||||
assert(array_key_exists('SimpleSAML_Auth_Source.logoutSource', $state));
|
||||
|
||||
$source = $state['SimpleSAML_Auth_Source.logoutSource'];
|
||||
|
||||
$session = SimpleSAML_Session::getSessionFromRequest();
|
||||
if (!$session->isValid($source)) {
|
||||
SimpleSAML\Logger::warning(
|
||||
'Received logout from an invalid authentication source '.
|
||||
var_export($source, true)
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
$session->doLogout($source);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make sure that the first element of an auth source is its identifier.
|
||||
*
|
||||
* @param array $source An array with the auth source configuration.
|
||||
* @param string $id The auth source identifier.
|
||||
*
|
||||
* @throws Exception If the first element of $source is not an identifier for the auth source.
|
||||
*/
|
||||
protected static function validateSource($source, $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.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
15
lib/SimpleSAML/Auth/SourceFactory.php
Executable file
15
lib/SimpleSAML/Auth/SourceFactory.php
Executable file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleSAML\Auth;
|
||||
|
||||
use SimpleSAML_Auth_Source;
|
||||
|
||||
interface SourceFactory
|
||||
{
|
||||
/**
|
||||
* @param array $info
|
||||
* @param array $config
|
||||
* @return SimpleSAML_Auth_Source
|
||||
*/
|
||||
public function create(array $info, array $config);
|
||||
}
|
||||
420
lib/SimpleSAML/Auth/State.php
Executable file
420
lib/SimpleSAML/Auth/State.php
Executable file
@@ -0,0 +1,420 @@
|
||||
<?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>".
|
||||
*
|
||||
* There is also support for passing exceptions through the state.
|
||||
* By defining an exception handler when creating the state array, users of the state
|
||||
* array can call throwException with the state and the exception. This exception will
|
||||
* be passed to the handler defined by the EXCEPTION_HANDLER_URL or EXCEPTION_HANDLER_FUNC
|
||||
* elements of the state array.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS.
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SimpleSAML_Auth_State
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* The index in the state array which contains the identifier.
|
||||
*/
|
||||
const ID = 'SimpleSAML_Auth_State.id';
|
||||
|
||||
|
||||
/**
|
||||
* The index in the cloned state array which contains the identifier of the
|
||||
* original state.
|
||||
*/
|
||||
const CLONE_ORIGINAL_ID = 'SimpleSAML_Auth_State.cloneOriginalId';
|
||||
|
||||
|
||||
/**
|
||||
* 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';
|
||||
|
||||
|
||||
/**
|
||||
* The index in the state array which contains the exception handler URL.
|
||||
*/
|
||||
const EXCEPTION_HANDLER_URL = 'SimpleSAML_Auth_State.exceptionURL';
|
||||
|
||||
|
||||
/**
|
||||
* The index in the state array which contains the exception handler function.
|
||||
*/
|
||||
const EXCEPTION_HANDLER_FUNC = 'SimpleSAML_Auth_State.exceptionFunc';
|
||||
|
||||
|
||||
/**
|
||||
* The index in the state array which contains the exception data.
|
||||
*/
|
||||
const EXCEPTION_DATA = 'SimpleSAML_Auth_State.exceptionData';
|
||||
|
||||
|
||||
/**
|
||||
* The stage of a state with an exception.
|
||||
*/
|
||||
const EXCEPTION_STAGE = 'SimpleSAML_Auth_State.exceptionStage';
|
||||
|
||||
|
||||
/**
|
||||
* The URL parameter which contains the exception state id.
|
||||
*/
|
||||
const EXCEPTION_PARAM = 'SimpleSAML_Auth_State_exceptionId';
|
||||
|
||||
|
||||
/**
|
||||
* State timeout.
|
||||
*/
|
||||
private static $stateTimeout = null;
|
||||
|
||||
|
||||
/**
|
||||
* Get the persistent authentication state from the state array.
|
||||
*
|
||||
* @param array $state The state array to analyze.
|
||||
*
|
||||
* @return array The persistent authentication state.
|
||||
*/
|
||||
public static function getPersistentAuthData(array $state)
|
||||
{
|
||||
// save persistent authentication data
|
||||
$persistent = array();
|
||||
|
||||
if (array_key_exists('PersistentAuthData', $state)) {
|
||||
foreach ($state['PersistentAuthData'] as $key) {
|
||||
if (isset($state[$key])) {
|
||||
$persistent[$key] = $state[$key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add those that should always be included
|
||||
$mandatory = array(
|
||||
'Attributes',
|
||||
'Expire',
|
||||
'LogoutState',
|
||||
'AuthInstant',
|
||||
'RememberMe',
|
||||
'saml:sp:NameID'
|
||||
);
|
||||
foreach ($mandatory as $key) {
|
||||
if (isset($state[$key])) {
|
||||
$persistent[$key] = $state[$key];
|
||||
}
|
||||
}
|
||||
|
||||
return $persistent;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the ID of a state array.
|
||||
*
|
||||
* Note that this function will not save the state.
|
||||
*
|
||||
* @param array &$state The state array.
|
||||
* @param bool $rawId Return a raw ID, without a restart URL. Defaults to FALSE.
|
||||
*
|
||||
* @return string Identifier which can be used to retrieve the state later.
|
||||
*/
|
||||
public static function getStateId(&$state, $rawId = false)
|
||||
{
|
||||
assert(is_array($state));
|
||||
assert(is_bool($rawId));
|
||||
|
||||
if (!array_key_exists(self::ID, $state)) {
|
||||
$state[self::ID] = SimpleSAML\Utils\Random::generateID();
|
||||
}
|
||||
|
||||
$id = $state[self::ID];
|
||||
|
||||
if ($rawId || !array_key_exists(self::RESTART, $state)) {
|
||||
// Either raw ID or no restart URL. In any case, return the raw ID.
|
||||
return $id;
|
||||
}
|
||||
|
||||
// We have a restart URL. Return the ID with that URL.
|
||||
return $id.':'.$state[self::RESTART];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve state timeout.
|
||||
*
|
||||
* @return integer State timeout.
|
||||
*/
|
||||
private static function getStateTimeout()
|
||||
{
|
||||
if (self::$stateTimeout === null) {
|
||||
$globalConfig = SimpleSAML_Configuration::getInstance();
|
||||
self::$stateTimeout = $globalConfig->getInteger('session.state.timeout', 60 * 60);
|
||||
}
|
||||
|
||||
return self::$stateTimeout;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param bool $rawId Return a raw ID, without a restart URL.
|
||||
*
|
||||
* @return string Identifier which can be used to retrieve the state later.
|
||||
*/
|
||||
public static function saveState(&$state, $stage, $rawId = false)
|
||||
{
|
||||
assert(is_array($state));
|
||||
assert(is_string($stage));
|
||||
assert(is_bool($rawId));
|
||||
|
||||
$return = self::getStateId($state, $rawId);
|
||||
$id = $state[self::ID];
|
||||
|
||||
// Save stage
|
||||
$state[self::STAGE] = $stage;
|
||||
|
||||
// Save state
|
||||
$serializedState = serialize($state);
|
||||
$session = SimpleSAML_Session::getSessionFromRequest();
|
||||
$session->setData('SimpleSAML_Auth_State', $id, $serializedState, self::getStateTimeout());
|
||||
|
||||
SimpleSAML\Logger::debug('Saved state: '.var_export($return, true));
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clone the state.
|
||||
*
|
||||
* This function clones and returns the new cloned state.
|
||||
*
|
||||
* @param array $state The original request state.
|
||||
*
|
||||
* @return array Cloned state data.
|
||||
*/
|
||||
public static function cloneState(array $state)
|
||||
{
|
||||
$clonedState = $state;
|
||||
|
||||
if (array_key_exists(self::ID, $state)) {
|
||||
$clonedState[self::CLONE_ORIGINAL_ID] = $state[self::ID];
|
||||
unset($clonedState[self::ID]);
|
||||
|
||||
SimpleSAML\Logger::debug('Cloned state: '.var_export($state[self::ID], true));
|
||||
} else {
|
||||
SimpleSAML\Logger::debug('Cloned state with undefined id.');
|
||||
}
|
||||
|
||||
return $clonedState;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param bool $allowMissing Whether to allow the state to be missing.
|
||||
*
|
||||
* @throws SimpleSAML_Error_NoState If we couldn't find the state and there's no URL defined to redirect to.
|
||||
* @throws Exception If the stage of the state is invalid and there's no URL defined to redirect to.
|
||||
*
|
||||
* @return array|NULL State information, or null if the state is missing and $allowMissing is true.
|
||||
*/
|
||||
public static function loadState($id, $stage, $allowMissing = false)
|
||||
{
|
||||
assert(is_string($id));
|
||||
assert(is_string($stage));
|
||||
assert(is_bool($allowMissing));
|
||||
SimpleSAML\Logger::debug('Loading state: '.var_export($id, true));
|
||||
|
||||
$sid = self::parseStateID($id);
|
||||
|
||||
$session = SimpleSAML_Session::getSessionFromRequest();
|
||||
$state = $session->getData('SimpleSAML_Auth_State', $sid['id']);
|
||||
|
||||
if ($state === null) {
|
||||
// Could not find saved data
|
||||
if ($allowMissing) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($sid['url'] === null) {
|
||||
throw new SimpleSAML_Error_NoState();
|
||||
}
|
||||
|
||||
\SimpleSAML\Utils\HTTP::redirectUntrustedURL($sid['url']);
|
||||
}
|
||||
|
||||
$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].
|
||||
'\', should be \''.$stage.'\'.';
|
||||
|
||||
SimpleSAML\Logger::warning($msg);
|
||||
|
||||
if ($sid['url'] === null) {
|
||||
throw new Exception($msg);
|
||||
}
|
||||
|
||||
\SimpleSAML\Utils\HTTP::redirectUntrustedURL($sid['url']);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
SimpleSAML\Logger::debug('Deleting state: '.var_export($state[self::ID], true));
|
||||
|
||||
$session = SimpleSAML_Session::getSessionFromRequest();
|
||||
$session->deleteData('SimpleSAML_Auth_State', $state[self::ID]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Throw exception to the state exception handler.
|
||||
*
|
||||
* @param array $state The state array.
|
||||
* @param SimpleSAML_Error_Exception $exception The exception.
|
||||
*
|
||||
* @throws SimpleSAML_Error_Exception If there is no exception handler defined, it will just throw the $exception.
|
||||
*/
|
||||
public static function throwException($state, SimpleSAML_Error_Exception $exception)
|
||||
{
|
||||
assert(is_array($state));
|
||||
|
||||
if (array_key_exists(self::EXCEPTION_HANDLER_URL, $state)) {
|
||||
// Save the exception
|
||||
$state[self::EXCEPTION_DATA] = $exception;
|
||||
$id = self::saveState($state, self::EXCEPTION_STAGE);
|
||||
|
||||
// Redirect to the exception handler
|
||||
\SimpleSAML\Utils\HTTP::redirectTrustedURL(
|
||||
$state[self::EXCEPTION_HANDLER_URL],
|
||||
array(self::EXCEPTION_PARAM => $id)
|
||||
);
|
||||
} elseif (array_key_exists(self::EXCEPTION_HANDLER_FUNC, $state)) {
|
||||
// Call the exception handler
|
||||
$func = $state[self::EXCEPTION_HANDLER_FUNC];
|
||||
assert(is_callable($func));
|
||||
|
||||
call_user_func($func, $exception, $state);
|
||||
assert(false);
|
||||
} else {
|
||||
/*
|
||||
* No exception handler is defined for the current state.
|
||||
*/
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve an exception state.
|
||||
*
|
||||
* @param string|NULL $id The exception id. Can be NULL, in which case it will be retrieved from the request.
|
||||
*
|
||||
* @return array|NULL The state array with the exception, or NULL if no exception was thrown.
|
||||
*/
|
||||
public static function loadExceptionState($id = null)
|
||||
{
|
||||
assert(is_string($id) || $id === null);
|
||||
|
||||
if ($id === null) {
|
||||
if (!array_key_exists(self::EXCEPTION_PARAM, $_REQUEST)) {
|
||||
// No exception
|
||||
return null;
|
||||
}
|
||||
$id = $_REQUEST[self::EXCEPTION_PARAM];
|
||||
}
|
||||
|
||||
$state = self::loadState($id, self::EXCEPTION_STAGE);
|
||||
assert(array_key_exists(self::EXCEPTION_DATA, $state));
|
||||
|
||||
return $state;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the ID and (optionally) a URL embedded in a StateID, in the form 'id:url'.
|
||||
*
|
||||
* @param string $stateId The state ID to use.
|
||||
*
|
||||
* @return array A hashed array with the ID and the URL (if any), in the 'id' and 'url' keys, respectively. If
|
||||
* there's no URL in the input parameter, NULL will be returned as the value for the 'url' key.
|
||||
*
|
||||
* @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no>
|
||||
* @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no>
|
||||
*/
|
||||
public static function parseStateID($stateId)
|
||||
{
|
||||
$tmp = explode(':', $stateId, 2);
|
||||
$id = $tmp[0];
|
||||
$url = null;
|
||||
if (count($tmp) === 2) {
|
||||
$url = $tmp[1];
|
||||
}
|
||||
return array('id' => $id, 'url' => $url);
|
||||
}
|
||||
}
|
||||
147
lib/SimpleSAML/Auth/TimeLimitedToken.php
Executable file
147
lib/SimpleSAML/Auth/TimeLimitedToken.php
Executable file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleSAML\Auth;
|
||||
|
||||
/**
|
||||
* A class that generates and verifies time-limited tokens.
|
||||
*/
|
||||
class TimeLimitedToken
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $secretSalt;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $lifetime;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $skew;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $algo;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new time-limited token.
|
||||
*
|
||||
* Please note that the default algorithm will change in SSP 1.15.0 to SHA-256 instead of SHA-1.
|
||||
*
|
||||
* @param int $lifetime Token lifetime in seconds. Defaults to 900 (15 min).
|
||||
* @param string $secretSalt A random and unique salt per installation. Defaults to the salt in the configuration.
|
||||
* @param int $skew The allowed time skew (in seconds) to correct clock deviations. Defaults to 1 second.
|
||||
* @param string $algo The hash algorithm to use to generate the tokens. Defaults to SHA-1.
|
||||
*
|
||||
* @throws \InvalidArgumentException if the given parameters are invalid.
|
||||
*/
|
||||
public function __construct($lifetime = 900, $secretSalt = null, $skew = 1, $algo = 'sha1')
|
||||
{
|
||||
if ($secretSalt === null) {
|
||||
$secretSalt = \SimpleSAML\Utils\Config::getSecretSalt();
|
||||
}
|
||||
|
||||
if (!in_array($algo, hash_algos(), true)) {
|
||||
throw new \InvalidArgumentException('Invalid hash algorithm "'.$algo.'"');
|
||||
}
|
||||
|
||||
$this->secretSalt = $secretSalt;
|
||||
$this->lifetime = $lifetime;
|
||||
$this->skew = $skew;
|
||||
$this->algo = $algo;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add some given data to the current token. This data will be needed later too for token validation.
|
||||
*
|
||||
* This mechanism can be used to provide context for a token, such as a user identifier of the only subject
|
||||
* authorised to use it. Note also that multiple data can be added to the token. This means that upon validation,
|
||||
* not only the same data must be added, but also in the same order.
|
||||
*
|
||||
* @param string $data The data to incorporate into the current token.
|
||||
*/
|
||||
public function addVerificationData($data)
|
||||
{
|
||||
$this->secretSalt .= '|'.$data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates a token value for a given offset.
|
||||
*
|
||||
* @param int $offset The offset to use.
|
||||
* @param int|null $time The time stamp to which the offset is relative to. Defaults to the current time.
|
||||
*
|
||||
* @return string The token for the given time and offset.
|
||||
*/
|
||||
private function calculateTokenValue($offset, $time = null)
|
||||
{
|
||||
if ($time === null) {
|
||||
$time = time();
|
||||
}
|
||||
// a secret salt that should be randomly generated for each installation
|
||||
return hash(
|
||||
$this->algo,
|
||||
$offset.':'.floor(($time - $offset) / ($this->lifetime + $this->skew)).':'.$this->secretSalt
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates a token that contains an offset and a token value, using the current offset.
|
||||
*
|
||||
* @return string A time-limited token with the offset respect to the beginning of its time slot prepended.
|
||||
*/
|
||||
public function generate()
|
||||
{
|
||||
$time = time();
|
||||
$current_offset = ($time - $this->skew) % ($this->lifetime + $this->skew);
|
||||
return dechex($current_offset).'-'.$this->calculateTokenValue($current_offset, $time);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @see generate
|
||||
* @deprecated This method will be removed in SSP 2.0. Use generate() instead.
|
||||
*/
|
||||
public function generate_token()
|
||||
{
|
||||
return $this->generate();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates a token by calculating the token value for the provided offset and comparing it.
|
||||
*
|
||||
* @param string $token The token to validate.
|
||||
*
|
||||
* @return boolean True if the given token is currently valid, false otherwise.
|
||||
*/
|
||||
public function validate($token)
|
||||
{
|
||||
$splittoken = explode('-', $token);
|
||||
if (count($splittoken) !== 2) {
|
||||
return false;
|
||||
}
|
||||
$offset = intval(hexdec($splittoken[0]));
|
||||
$value = $splittoken[1];
|
||||
return ($this->calculateTokenValue($offset) === $value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @see validate
|
||||
* @deprecated This method will be removed in SSP 2.0. Use validate() instead.
|
||||
*/
|
||||
public function validate_token($token)
|
||||
{
|
||||
return $this->validate($token);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user