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);
|
||||
}
|
||||
}
|
||||
166
lib/SimpleSAML/AuthMemCookie.php
Executable file
166
lib/SimpleSAML/AuthMemCookie.php
Executable file
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* This is a helper class for the Auth MemCookie module.
|
||||
* It handles the configuration, and implements the logout handler.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS.
|
||||
* @package SimpleSAMLphp
|
||||
*
|
||||
* @deprecated This class has been deprecated and will be removed in SSP 2.0. Use the memcookie module instead.
|
||||
*/
|
||||
class SimpleSAML_AuthMemCookie
|
||||
{
|
||||
|
||||
/**
|
||||
* @var SimpleSAML_AuthMemCookie This is the singleton instance of this class.
|
||||
*/
|
||||
private static $instance = null;
|
||||
|
||||
|
||||
/**
|
||||
* @var SimpleSAML_Configuration The configuration for Auth MemCookie.
|
||||
*/
|
||||
private $amcConfig;
|
||||
|
||||
|
||||
/**
|
||||
* This function is used to retrieve the singleton instance of this class.
|
||||
*
|
||||
* @return SimpleSAML_AuthMemCookie The singleton instance of this class.
|
||||
*/
|
||||
public static function getInstance()
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new SimpleSAML_AuthMemCookie();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function implements the constructor for this class. It loads the Auth MemCookie configuration.
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
// load AuthMemCookie configuration
|
||||
$this->amcConfig = SimpleSAML_Configuration::getConfig('authmemcookie.php');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the authentication source that should be used to authenticate the user.
|
||||
*
|
||||
* @return string The login type which should be used for Auth MemCookie.
|
||||
*/
|
||||
public function getAuthSource()
|
||||
{
|
||||
return $this->amcConfig->getString('authsource');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function retrieves the name of the cookie from the configuration.
|
||||
*
|
||||
* @return string The name of the cookie.
|
||||
* @throws Exception If the value of the 'cookiename' configuration option is invalid.
|
||||
*/
|
||||
public function getCookieName()
|
||||
{
|
||||
$cookieName = $this->amcConfig->getString('cookiename', 'AuthMemCookie');
|
||||
if (!is_string($cookieName) || strlen($cookieName) === 0) {
|
||||
throw new Exception(
|
||||
"Configuration option 'cookiename' contains an invalid value. This option should be a string."
|
||||
);
|
||||
}
|
||||
|
||||
return $cookieName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function retrieves the name of the attribute which contains the username from the configuration.
|
||||
*
|
||||
* @return string The name of the attribute which contains the username.
|
||||
*/
|
||||
public function getUsernameAttr()
|
||||
{
|
||||
$usernameAttr = $this->amcConfig->getString('username', null);
|
||||
|
||||
return $usernameAttr;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function retrieves the name of the attribute which contains the groups from the configuration.
|
||||
*
|
||||
* @return string The name of the attribute which contains the groups.
|
||||
*/
|
||||
public function getGroupsAttr()
|
||||
{
|
||||
$groupsAttr = $this->amcConfig->getString('groups', null);
|
||||
|
||||
return $groupsAttr;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function creates and initializes a Memcache object from our configuration.
|
||||
*
|
||||
* @return Memcache A Memcache object initialized from our configuration.
|
||||
* @throws Exception If the servers configuration is invalid.
|
||||
*/
|
||||
public function getMemcache()
|
||||
{
|
||||
$memcacheHost = $this->amcConfig->getString('memcache.host', '127.0.0.1');
|
||||
$memcachePort = $this->amcConfig->getInteger('memcache.port', 11211);
|
||||
|
||||
$class = class_exists('Memcache') ? 'Memcache' : (class_exists('Memcached') ? 'Memcached' : false);
|
||||
if (!$class) {
|
||||
throw new Exception('Missing Memcached implementation. You must install either the Memcache or Memcached extension.');
|
||||
}
|
||||
|
||||
// Create the Memcache(d) object.
|
||||
$memcache = new $class();
|
||||
|
||||
foreach (explode(',', $memcacheHost) as $memcacheHost) {
|
||||
$memcache->addServer($memcacheHost, $memcachePort);
|
||||
}
|
||||
|
||||
return $memcache;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function logs the user out by deleting the session information from memcache.
|
||||
*/
|
||||
private function doLogout()
|
||||
{
|
||||
$cookieName = $this->getCookieName();
|
||||
|
||||
// check if we have a valid cookie
|
||||
if (!array_key_exists($cookieName, $_COOKIE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sessionID = $_COOKIE[$cookieName];
|
||||
|
||||
// delete the session from memcache
|
||||
$memcache = $this->getMemcache();
|
||||
$memcache->delete($sessionID);
|
||||
|
||||
// delete the session cookie
|
||||
\SimpleSAML\Utils\HTTP::setCookie($cookieName, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function implements the logout handler. It deletes the information from Memcache.
|
||||
*/
|
||||
public static function logoutHandler()
|
||||
{
|
||||
self::getInstance()->doLogout();
|
||||
}
|
||||
}
|
||||
190
lib/SimpleSAML/Bindings/Shib13/Artifact.php
Executable file
190
lib/SimpleSAML/Bindings/Shib13/Artifact.php
Executable file
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Implementation of the Shibboleth 1.3 Artifact binding.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
namespace SimpleSAML\Bindings\Shib13;
|
||||
|
||||
use SAML2\DOMDocumentFactory;
|
||||
use SimpleSAML\Utils\Config;
|
||||
use SimpleSAML\Utils\HTTP;
|
||||
use SimpleSAML\Utils\Random;
|
||||
use SimpleSAML\Utils\System;
|
||||
use SimpleSAML\Utils\Time;
|
||||
use SimpleSAML\Utils\XML;
|
||||
|
||||
class Artifact
|
||||
{
|
||||
|
||||
/**
|
||||
* Parse the query string, and extract the SAMLart parameters.
|
||||
*
|
||||
* This function is required because each query contains multiple
|
||||
* artifact with the same parameter name.
|
||||
*
|
||||
* @return array The artifacts.
|
||||
*/
|
||||
private static function getArtifacts()
|
||||
{
|
||||
assert(array_key_exists('QUERY_STRING', $_SERVER));
|
||||
|
||||
// We need to process the query string manually, to capture all SAMLart parameters
|
||||
|
||||
$artifacts = array();
|
||||
|
||||
$elements = explode('&', $_SERVER['QUERY_STRING']);
|
||||
foreach ($elements as $element) {
|
||||
list($name, $value) = explode('=', $element, 2);
|
||||
$name = urldecode($name);
|
||||
$value = urldecode($value);
|
||||
|
||||
if ($name === 'SAMLart') {
|
||||
$artifacts[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $artifacts;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build the request we will send to the IdP.
|
||||
*
|
||||
* @param array $artifacts The artifacts we will request.
|
||||
* @return string The request, as an XML string.
|
||||
*/
|
||||
private static function buildRequest(array $artifacts)
|
||||
{
|
||||
$msg = '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">' .
|
||||
'<SOAP-ENV:Body>' .
|
||||
'<samlp:Request xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol"' .
|
||||
' RequestID="' . Random::generateID() . '"' .
|
||||
' MajorVersion="1" MinorVersion="1"' .
|
||||
' IssueInstant="' . Time::generateTimestamp() . '"' .
|
||||
'>';
|
||||
|
||||
foreach ($artifacts as $a) {
|
||||
$msg .= '<samlp:AssertionArtifact>' . htmlspecialchars($a) . '</samlp:AssertionArtifact>';
|
||||
}
|
||||
|
||||
$msg .= '</samlp:Request>' .
|
||||
'</SOAP-ENV:Body>' .
|
||||
'</SOAP-ENV:Envelope>';
|
||||
|
||||
return $msg;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract the response element from the SOAP response.
|
||||
*
|
||||
* @param string $soapResponse The SOAP response.
|
||||
* @return string The <saml1p:Response> element, as a string.
|
||||
* @throws \SimpleSAML_Error_Exception
|
||||
*/
|
||||
private static function extractResponse($soapResponse)
|
||||
{
|
||||
assert(is_string($soapResponse));
|
||||
|
||||
try {
|
||||
$doc = DOMDocumentFactory::fromString($soapResponse);
|
||||
} catch (\Exception $e) {
|
||||
throw new \SimpleSAML_Error_Exception('Error parsing SAML 1 artifact response.');
|
||||
}
|
||||
|
||||
$soapEnvelope = $doc->firstChild;
|
||||
if (!XML::isDOMNodeOfType($soapEnvelope, 'Envelope', 'http://schemas.xmlsoap.org/soap/envelope/')) {
|
||||
throw new \SimpleSAML_Error_Exception('Expected artifact response to contain a <soap:Envelope> element.');
|
||||
}
|
||||
|
||||
$soapBody = XML::getDOMChildren($soapEnvelope, 'Body', 'http://schemas.xmlsoap.org/soap/envelope/');
|
||||
if (count($soapBody) === 0) {
|
||||
throw new \SimpleSAML_Error_Exception('Couldn\'t find <soap:Body> in <soap:Envelope>.');
|
||||
}
|
||||
$soapBody = $soapBody[0];
|
||||
|
||||
|
||||
$responseElement = XML::getDOMChildren($soapBody, 'Response', 'urn:oasis:names:tc:SAML:1.0:protocol');
|
||||
if (count($responseElement) === 0) {
|
||||
throw new \SimpleSAML_Error_Exception('Couldn\'t find <saml1p:Response> in <soap:Body>.');
|
||||
}
|
||||
$responseElement = $responseElement[0];
|
||||
|
||||
/*
|
||||
* Save the <saml1p:Response> element. Note that we need to import it
|
||||
* into a new document, in order to preserve namespace declarations.
|
||||
*/
|
||||
$newDoc = DOMDocumentFactory::create();
|
||||
$newDoc->appendChild($newDoc->importNode($responseElement, true));
|
||||
$responseXML = $newDoc->saveXML();
|
||||
|
||||
return $responseXML;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function receives a SAML 1.1 artifact.
|
||||
*
|
||||
* @param \SimpleSAML_Configuration $spMetadata The metadata of the SP.
|
||||
* @param \SimpleSAML_Configuration $idpMetadata The metadata of the IdP.
|
||||
* @return string The <saml1p:Response> element, as an XML string.
|
||||
* @throws \SimpleSAML_Error_Exception
|
||||
*/
|
||||
public static function receive(\SimpleSAML_Configuration $spMetadata, \SimpleSAML_Configuration $idpMetadata)
|
||||
{
|
||||
$artifacts = self::getArtifacts();
|
||||
$request = self::buildRequest($artifacts);
|
||||
|
||||
XML::debugSAMLMessage($request, 'out');
|
||||
|
||||
$url = $idpMetadata->getDefaultEndpoint('ArtifactResolutionService', array('urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding'));
|
||||
$url = $url['Location'];
|
||||
|
||||
$peerPublicKeys = $idpMetadata->getPublicKeys('signing', true);
|
||||
$certData = '';
|
||||
foreach ($peerPublicKeys as $key) {
|
||||
if ($key['type'] !== 'X509Certificate') {
|
||||
continue;
|
||||
}
|
||||
$certData .= "-----BEGIN CERTIFICATE-----\n" .
|
||||
chunk_split($key['X509Certificate'], 64) .
|
||||
"-----END CERTIFICATE-----\n";
|
||||
}
|
||||
|
||||
$file = System::getTempDir() . DIRECTORY_SEPARATOR . sha1($certData) . '.crt';
|
||||
if (!file_exists($file)) {
|
||||
System::writeFile($file, $certData);
|
||||
}
|
||||
|
||||
$spKeyCertFile = Config::getCertPath($spMetadata->getString('privatekey'));
|
||||
|
||||
$opts = array(
|
||||
'ssl' => array(
|
||||
'verify_peer' => true,
|
||||
'cafile' => $file,
|
||||
'local_cert' => $spKeyCertFile,
|
||||
'capture_peer_cert' => true,
|
||||
'capture_peer_chain' => true,
|
||||
),
|
||||
'http' => array(
|
||||
'method' => 'POST',
|
||||
'content' => $request,
|
||||
'header' => 'SOAPAction: http://www.oasis-open.org/committees/security' . "\r\n" .
|
||||
'Content-Type: text/xml',
|
||||
),
|
||||
);
|
||||
|
||||
// Fetch the artifact
|
||||
$response = HTTP::fetch($url, $opts);
|
||||
/** @var string $response */
|
||||
XML::debugSAMLMessage($response, 'in');
|
||||
|
||||
// Find the response in the SOAP message
|
||||
$response = self::extractResponse($response);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
154
lib/SimpleSAML/Bindings/Shib13/HTTPPost.php
Executable file
154
lib/SimpleSAML/Bindings/Shib13/HTTPPost.php
Executable file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of the Shibboleth 1.3 HTTP-POST binding.
|
||||
*
|
||||
* @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
namespace SimpleSAML\Bindings\Shib13;
|
||||
|
||||
use SAML2\DOMDocumentFactory;
|
||||
use SimpleSAML\Utils\Crypto;
|
||||
use SimpleSAML\Utils\HTTP;
|
||||
use SimpleSAML\Utils\XML;
|
||||
use SimpleSAML\XML\Shib13\AuthnResponse;
|
||||
use SimpleSAML\XML\Signer;
|
||||
|
||||
class HTTPPost
|
||||
{
|
||||
|
||||
/**
|
||||
* @var \SimpleSAML_Configuration
|
||||
*/
|
||||
private $configuration = null;
|
||||
|
||||
/**
|
||||
* @var \SimpleSAML_Metadata_MetaDataStorageHandler
|
||||
*/
|
||||
private $metadata = null;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor for the \SimpleSAML\Bindings\Shib13\HTTPPost class.
|
||||
*
|
||||
* @param \SimpleSAML_Configuration $configuration The configuration to use.
|
||||
* @param \SimpleSAML_Metadata_MetaDataStorageHandler $metadatastore A store where to find metadata.
|
||||
*/
|
||||
public function __construct(
|
||||
\SimpleSAML_Configuration $configuration,
|
||||
\SimpleSAML_Metadata_MetaDataStorageHandler $metadatastore
|
||||
) {
|
||||
$this->configuration = $configuration;
|
||||
$this->metadata = $metadatastore;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send an authenticationResponse using HTTP-POST.
|
||||
*
|
||||
* @param string $response The response which should be sent.
|
||||
* @param \SimpleSAML_Configuration $idpmd The metadata of the IdP which is sending the response.
|
||||
* @param \SimpleSAML_Configuration $spmd The metadata of the SP which is receiving the response.
|
||||
* @param string|null $relayState The relaystate for the SP.
|
||||
* @param string $shire The shire which should receive the response.
|
||||
*/
|
||||
public function sendResponse(
|
||||
$response,
|
||||
\SimpleSAML_Configuration $idpmd,
|
||||
\SimpleSAML_Configuration $spmd,
|
||||
$relayState,
|
||||
$shire
|
||||
) {
|
||||
XML::checkSAMLMessage($response, 'saml11');
|
||||
|
||||
$privatekey = Crypto::loadPrivateKey($idpmd, true);
|
||||
$publickey = Crypto::loadPublicKey($idpmd, true);
|
||||
|
||||
$responsedom = DOMDocumentFactory::fromString(str_replace("\r", "", $response));
|
||||
|
||||
$responseroot = $responsedom->getElementsByTagName('Response')->item(0);
|
||||
$firstassertionroot = $responsedom->getElementsByTagName('Assertion')->item(0);
|
||||
|
||||
/* Determine what we should sign - either the Response element or the Assertion. The default is to sign the
|
||||
* Assertion, but that can be overridden by the 'signresponse' option in the SP metadata or
|
||||
* 'saml20.signresponse' in the global configuration.
|
||||
*
|
||||
* TODO: neither 'signresponse' nor 'shib13.signresponse' are valid options any longer. Remove!
|
||||
*/
|
||||
if ($spmd->hasValue('signresponse')) {
|
||||
$signResponse = $spmd->getBoolean('signresponse');
|
||||
} else {
|
||||
$signResponse = $this->configuration->getBoolean('shib13.signresponse', true);
|
||||
}
|
||||
|
||||
// check if we have an assertion to sign. Force to sign the response if not
|
||||
if ($firstassertionroot === null) {
|
||||
$signResponse = true;
|
||||
}
|
||||
|
||||
$signer = new Signer(array(
|
||||
'privatekey_array' => $privatekey,
|
||||
'publickey_array' => $publickey,
|
||||
'id' => ($signResponse ? 'ResponseID' : 'AssertionID'),
|
||||
));
|
||||
|
||||
if ($idpmd->hasValue('certificatechain')) {
|
||||
$signer->addCertificate($idpmd->getString('certificatechain'));
|
||||
}
|
||||
|
||||
if ($signResponse) {
|
||||
// sign the response - this must be done after encrypting the assertion
|
||||
// we insert the signature before the saml2p:Status element
|
||||
$statusElements = XML::getDOMChildren($responseroot, 'Status', '@saml1p');
|
||||
assert(count($statusElements) === 1);
|
||||
$signer->sign($responseroot, $responseroot, $statusElements[0]);
|
||||
} else {
|
||||
// Sign the assertion
|
||||
$signer->sign($firstassertionroot, $firstassertionroot);
|
||||
}
|
||||
|
||||
$response = $responsedom->saveXML();
|
||||
|
||||
XML::debugSAMLMessage($response, 'out');
|
||||
|
||||
HTTP::submitPOSTData($shire, array(
|
||||
'TARGET' => $relayState,
|
||||
'SAMLResponse' => base64_encode($response),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decode a received response.
|
||||
*
|
||||
* @param array $post POST data received.
|
||||
* @return \SimpleSAML\XML\Shib13\AuthnResponse The response decoded into an object.
|
||||
* @throws \Exception If there is no SAMLResponse parameter.
|
||||
*/
|
||||
public function decodeResponse($post)
|
||||
{
|
||||
assert(is_array($post));
|
||||
|
||||
if (!array_key_exists('SAMLResponse', $post)) {
|
||||
throw new \Exception('Missing required SAMLResponse parameter.');
|
||||
}
|
||||
$rawResponse = $post['SAMLResponse'];
|
||||
$samlResponseXML = base64_decode($rawResponse);
|
||||
|
||||
XML::debugSAMLMessage($samlResponseXML, 'in');
|
||||
|
||||
XML::checkSAMLMessage($samlResponseXML, 'saml11');
|
||||
|
||||
$samlResponse = new AuthnResponse();
|
||||
$samlResponse->setXML($samlResponseXML);
|
||||
|
||||
if (array_key_exists('TARGET', $post)) {
|
||||
$samlResponse->setRelayState($post['TARGET']);
|
||||
}
|
||||
|
||||
return $samlResponse;
|
||||
}
|
||||
}
|
||||
1392
lib/SimpleSAML/Configuration.php
Executable file
1392
lib/SimpleSAML/Configuration.php
Executable file
File diff suppressed because it is too large
Load Diff
293
lib/SimpleSAML/Database.php
Executable file
293
lib/SimpleSAML/Database.php
Executable file
@@ -0,0 +1,293 @@
|
||||
<?php
|
||||
namespace SimpleSAML;
|
||||
|
||||
/**
|
||||
* This file implements functions to read and write to a group of database servers.
|
||||
*
|
||||
* This database class supports a single database, or a master/slave configuration with as many defined slaves as a
|
||||
* user would like.
|
||||
*
|
||||
* The goal of this class is to provide a single mechanism to connect to a database that can be reused by any component
|
||||
* within SimpleSAMLphp including modules. When using this class, the global configuration should be passed here, but in
|
||||
* the case of a module that has a good reason to use a different database, such as sqlauth, an alternative config file
|
||||
* can be provided.
|
||||
*
|
||||
* @author Tyler Antonio, University of Alberta. <tantonio@ualberta.ca>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
class Database
|
||||
{
|
||||
|
||||
/**
|
||||
* This variable holds the instance of the session - Singleton approach.
|
||||
*/
|
||||
private static $instance = array();
|
||||
|
||||
/**
|
||||
* PDO Object for the Master database server
|
||||
*/
|
||||
private $dbMaster;
|
||||
|
||||
/**
|
||||
* Array of PDO Objects for configured database slaves
|
||||
*/
|
||||
private $dbSlaves = array();
|
||||
|
||||
/**
|
||||
* Prefix to apply to the tables
|
||||
*/
|
||||
private $tablePrefix;
|
||||
|
||||
/**
|
||||
* Array with information on the last error occurred.
|
||||
*/
|
||||
private $lastError;
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the current database instance. Will create a new one if there isn't an existing connection.
|
||||
*
|
||||
* @param \SimpleSAML_Configuration $altConfig Optional: Instance of a SimpleSAML_Configuration class
|
||||
*
|
||||
* @return \SimpleSAML\Database The shared database connection.
|
||||
*/
|
||||
public static function getInstance($altConfig = null)
|
||||
{
|
||||
$config = ($altConfig) ? $altConfig : \SimpleSAML_Configuration::getInstance();
|
||||
$instanceId = self::generateInstanceId($config);
|
||||
|
||||
// check if we already have initialized the session
|
||||
if (isset(self::$instance[$instanceId])) {
|
||||
return self::$instance[$instanceId];
|
||||
}
|
||||
|
||||
// create a new session
|
||||
self::$instance[$instanceId] = new Database($config);
|
||||
return self::$instance[$instanceId];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Private constructor that restricts instantiation to getInstance().
|
||||
*
|
||||
* @param \SimpleSAML_Configuration $config Instance of the SimpleSAML_Configuration class
|
||||
*/
|
||||
private function __construct($config)
|
||||
{
|
||||
$driverOptions = $config->getArray('database.driver_options', array());
|
||||
if ($config->getBoolean('database.persistent', true)) {
|
||||
$driverOptions = array(\PDO::ATTR_PERSISTENT => true);
|
||||
}
|
||||
|
||||
// connect to the master
|
||||
$this->dbMaster = $this->connect(
|
||||
$config->getString('database.dsn'),
|
||||
$config->getString('database.username', null),
|
||||
$config->getString('database.password', null),
|
||||
$driverOptions
|
||||
);
|
||||
|
||||
// connect to any configured slaves
|
||||
$slaves = $config->getArray('database.slaves', array());
|
||||
foreach ($slaves as $slave) {
|
||||
array_push(
|
||||
$this->dbSlaves,
|
||||
$this->connect(
|
||||
$slave['dsn'],
|
||||
$slave['username'],
|
||||
$slave['password'],
|
||||
$driverOptions
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->tablePrefix = $config->getString('database.prefix', '');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate an Instance ID based on the database configuration.
|
||||
*
|
||||
* @param \SimpleSAML_Configuration $config Configuration class
|
||||
*
|
||||
* @return string $instanceId
|
||||
*/
|
||||
private static function generateInstanceId($config)
|
||||
{
|
||||
$assembledConfig = array(
|
||||
'master' => array(
|
||||
'database.dsn' => $config->getString('database.dsn'),
|
||||
'database.username' => $config->getString('database.username', null),
|
||||
'database.password' => $config->getString('database.password', null),
|
||||
'database.prefix' => $config->getString('database.prefix', ''),
|
||||
'database.persistent' => $config->getBoolean('database.persistent', false),
|
||||
),
|
||||
'slaves' => $config->getArray('database.slaves', array()),
|
||||
);
|
||||
|
||||
return sha1(serialize($assembledConfig));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function connects to a database.
|
||||
*
|
||||
* @param string $dsn Database connection string
|
||||
* @param string $username SQL user
|
||||
* @param string $password SQL password
|
||||
* @param array $options PDO options
|
||||
*
|
||||
* @throws \Exception If an error happens while trying to connect to the database.
|
||||
* @return \PDO object
|
||||
*/
|
||||
private function connect($dsn, $username, $password, $options)
|
||||
{
|
||||
try {
|
||||
$db = new \PDO($dsn, $username, $password, $options);
|
||||
$db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
return $db;
|
||||
} catch (\PDOException $e) {
|
||||
throw new \Exception("Database error: ".$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function randomly selects a slave database server to query. In the event no slaves are configured, it will
|
||||
* return the master.
|
||||
*
|
||||
* @return \PDO object
|
||||
*/
|
||||
private function getSlave()
|
||||
{
|
||||
if (count($this->dbSlaves) > 0) {
|
||||
$slaveId = rand(0, count($this->dbSlaves) - 1);
|
||||
return $this->dbSlaves[$slaveId];
|
||||
} else {
|
||||
return $this->dbMaster;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function simply applies the table prefix to a supplied table name.
|
||||
*
|
||||
* @param string $table Table to apply prefix to, if configured
|
||||
*
|
||||
* @return string Table with configured prefix
|
||||
*/
|
||||
public function applyPrefix($table)
|
||||
{
|
||||
return $this->tablePrefix.$table;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function queries the database
|
||||
*
|
||||
* @param \PDO $db PDO object to use
|
||||
* @param string $stmt Prepared SQL statement
|
||||
* @param array $params Parameters
|
||||
*
|
||||
* @throws \Exception If an error happens while trying to execute the query.
|
||||
* @return \PDOStatement object
|
||||
*/
|
||||
private function query($db, $stmt, $params)
|
||||
{
|
||||
assert(is_object($db));
|
||||
assert(is_string($stmt));
|
||||
assert(is_array($params));
|
||||
|
||||
try {
|
||||
$query = $db->prepare($stmt);
|
||||
|
||||
foreach ($params as $param => $value) {
|
||||
if (is_array($value)) {
|
||||
$query->bindValue(":$param", $value[0], ($value[1]) ? $value[1] : \PDO::PARAM_STR);
|
||||
} else {
|
||||
$query->bindValue(":$param", $value, \PDO::PARAM_STR);
|
||||
}
|
||||
}
|
||||
|
||||
$query->execute();
|
||||
|
||||
return $query;
|
||||
} catch (\PDOException $e) {
|
||||
$this->lastError = $db->errorInfo();
|
||||
throw new \Exception("Database error: ".$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function queries the database without using a prepared statement.
|
||||
*
|
||||
* @param \PDO $db PDO object to use
|
||||
* @param string $stmt An SQL statement to execute, previously escaped.
|
||||
*
|
||||
* @throws \Exception If an error happens while trying to execute the query.
|
||||
* @return int The number of rows affected.
|
||||
*/
|
||||
private function exec($db, $stmt)
|
||||
{
|
||||
assert(is_object($db));
|
||||
assert(is_string($stmt));
|
||||
|
||||
try {
|
||||
return $db->exec($stmt);
|
||||
} catch (\PDOException $e) {
|
||||
$this->lastError = $db->errorInfo();
|
||||
throw new \Exception("Database error: ".$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This executes queries directly on the master.
|
||||
*
|
||||
* @param string $stmt Prepared SQL statement
|
||||
* @param array $params Parameters
|
||||
*
|
||||
* @return int The number of rows affected by the query.
|
||||
*/
|
||||
public function write($stmt, $params = array())
|
||||
{
|
||||
$db = $this->dbMaster;
|
||||
|
||||
if (is_array($params)) {
|
||||
$obj = $this->query($db, $stmt, $params);
|
||||
return $obj->rowCount();
|
||||
} else {
|
||||
return $this->exec($db, $stmt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This executes queries on a database server that is determined by this::getSlave().
|
||||
*
|
||||
* @param string $stmt Prepared SQL statement
|
||||
* @param array $params Parameters
|
||||
*
|
||||
* @return \PDOStatement object
|
||||
*/
|
||||
public function read($stmt, $params = array())
|
||||
{
|
||||
$db = $this->getSlave();
|
||||
|
||||
return $this->query($db, $stmt, $params);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return an array with information about the last operation performed in the database.
|
||||
*
|
||||
* @return array The array with error information.
|
||||
*/
|
||||
public function getLastError()
|
||||
{
|
||||
return $this->lastError;
|
||||
}
|
||||
}
|
||||
85
lib/SimpleSAML/Error/Assertion.php
Executable file
85
lib/SimpleSAML/Error/Assertion.php
Executable file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class for creating exceptions from assertion failures.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS.
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SimpleSAML_Error_Assertion extends SimpleSAML_Error_Exception
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* The assertion which failed, or null if only an expression was passed to the
|
||||
* assert-function.
|
||||
*/
|
||||
private $assertion;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor for the assertion exception.
|
||||
*
|
||||
* Should only be called from the onAssertion handler.
|
||||
*
|
||||
* @param string|null $assertion The assertion which failed, or null if the assert-function was
|
||||
* given an expression.
|
||||
*/
|
||||
public function __construct($assertion = null)
|
||||
{
|
||||
assert($assertion === null || is_string($assertion));
|
||||
|
||||
$msg = 'Assertion failed: ' . var_export($assertion, true);
|
||||
parent::__construct($msg);
|
||||
|
||||
$this->assertion = $assertion;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the assertion which failed.
|
||||
*
|
||||
* @return string|null The assertion which failed, or null if the assert-function was called with an expression.
|
||||
*/
|
||||
public function getAssertion()
|
||||
{
|
||||
return $this->assertion;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Install this assertion handler.
|
||||
*
|
||||
* This function will register this assertion handler. If will not enable assertions if they are
|
||||
* disabled.
|
||||
*/
|
||||
public static function installHandler()
|
||||
{
|
||||
|
||||
assert_options(ASSERT_WARNING, 0);
|
||||
assert_options(ASSERT_QUIET_EVAL, 0);
|
||||
assert_options(ASSERT_CALLBACK, array('SimpleSAML_Error_Assertion', 'onAssertion'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle assertion.
|
||||
*
|
||||
* This function handles an assertion.
|
||||
*
|
||||
* @param string $file The file assert was called from.
|
||||
* @param int $line The line assert was called from.
|
||||
* @param mixed $message The expression which was passed to the assert-function.
|
||||
*/
|
||||
public static function onAssertion($file, $line, $message)
|
||||
{
|
||||
|
||||
if (!empty($message)) {
|
||||
$exception = new self($message);
|
||||
} else {
|
||||
$exception = new self();
|
||||
}
|
||||
|
||||
$exception->logError();
|
||||
}
|
||||
}
|
||||
70
lib/SimpleSAML/Error/AuthSource.php
Executable file
70
lib/SimpleSAML/Error/AuthSource.php
Executable file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* Baseclass for auth source exceptions.
|
||||
*
|
||||
* @package SimpleSAMLphp_base
|
||||
*
|
||||
*/
|
||||
class SimpleSAML_Error_AuthSource extends SimpleSAML_Error_Error
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Authsource module name.
|
||||
*/
|
||||
private $authsource;
|
||||
|
||||
|
||||
/**
|
||||
* Reason why this request was invalid.
|
||||
*/
|
||||
private $reason;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new AuthSource error.
|
||||
*
|
||||
* @param string $authsource Authsource module name from where this error was thrown.
|
||||
* @param string $reason Description of the error.
|
||||
*/
|
||||
public function __construct($authsource, $reason, $cause = null)
|
||||
{
|
||||
assert(is_string($authsource));
|
||||
assert(is_string($reason));
|
||||
|
||||
$this->authsource = $authsource;
|
||||
$this->reason = $reason;
|
||||
parent::__construct(
|
||||
array(
|
||||
'AUTHSOURCEERROR',
|
||||
'%AUTHSOURCE%' => htmlspecialchars(var_export($this->authsource, true)),
|
||||
'%REASON%' => htmlspecialchars(var_export($this->reason, true))
|
||||
),
|
||||
$cause
|
||||
);
|
||||
|
||||
$this->message = "Error with authentication source '$authsource': $reason";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the authsource module name from where this error was thrown.
|
||||
*
|
||||
* @return string Authsource module name.
|
||||
*/
|
||||
public function getAuthSource()
|
||||
{
|
||||
return $this->authsource;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the reason why the request was invalid.
|
||||
*
|
||||
* @return string The reason why the request was invalid.
|
||||
*/
|
||||
public function getReason()
|
||||
{
|
||||
return $this->reason;
|
||||
}
|
||||
}
|
||||
46
lib/SimpleSAML/Error/BadRequest.php
Executable file
46
lib/SimpleSAML/Error/BadRequest.php
Executable file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception which will show a 400 Bad Request error page.
|
||||
*
|
||||
* This exception can be thrown from within an module page handler. The user will then be
|
||||
* shown a 400 Bad Request error page.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS.
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SimpleSAML_Error_BadRequest extends SimpleSAML_Error_Error
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Reason why this request was invalid.
|
||||
*/
|
||||
private $reason;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new BadRequest error.
|
||||
*
|
||||
* @param string $reason Description of why the request was unacceptable.
|
||||
*/
|
||||
public function __construct($reason)
|
||||
{
|
||||
assert(is_string($reason));
|
||||
|
||||
$this->reason = $reason;
|
||||
parent::__construct(array('BADREQUEST', '%REASON%' => $this->reason));
|
||||
$this->httpCode = 400;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the reason why the request was invalid.
|
||||
*
|
||||
* @return string The reason why the request was invalid.
|
||||
*/
|
||||
public function getReason()
|
||||
{
|
||||
return $this->reason;
|
||||
}
|
||||
}
|
||||
12
lib/SimpleSAML/Error/BadUserInput.php
Executable file
12
lib/SimpleSAML/Error/BadUserInput.php
Executable file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
/**
|
||||
* Exception indicating illegal innput from user.
|
||||
*
|
||||
* @author Thomas Graff <thomas.graff@uninett.no>
|
||||
* @package SimpleSAMLphp_base
|
||||
*
|
||||
*/
|
||||
class SimpleSAML_Error_BadUserInput extends SimpleSAML_Error_User
|
||||
{
|
||||
|
||||
}
|
||||
35
lib/SimpleSAML/Error/CannotSetCookie.php
Executable file
35
lib/SimpleSAML/Error/CannotSetCookie.php
Executable file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* Exception to indicate that we cannot set a cookie.
|
||||
*
|
||||
* @author Jaime Pérez Crespo <jaime.perez@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
namespace SimpleSAML\Error;
|
||||
|
||||
class CannotSetCookie extends \SimpleSAML_Error_Exception
|
||||
{
|
||||
|
||||
/**
|
||||
* The exception was thrown for unknown reasons.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const UNKNOWN = 0;
|
||||
|
||||
/**
|
||||
* The exception was due to the HTTP headers being already sent, and therefore we cannot send additional headers to
|
||||
* set the cookie.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const HEADERS_SENT = 1;
|
||||
|
||||
/**
|
||||
* The exception was due to trying to set a secure cookie over an insecure channel.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const SECURE_COOKIE = 2;
|
||||
}
|
||||
77
lib/SimpleSAML/Error/ConfigurationError.php
Executable file
77
lib/SimpleSAML/Error/ConfigurationError.php
Executable file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/**
|
||||
* This exception represents a configuration error.
|
||||
*
|
||||
* @author Jaime Perez Crespo, UNINETT AS <jaime.perez@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
namespace SimpleSAML\Error;
|
||||
|
||||
class ConfigurationError extends \SimpleSAML_Error_Error
|
||||
{
|
||||
|
||||
/**
|
||||
* The reason for this exception.
|
||||
*
|
||||
* @var null|string
|
||||
*/
|
||||
protected $reason;
|
||||
|
||||
/**
|
||||
* The configuration file that caused this exception.
|
||||
*
|
||||
* @var null|string
|
||||
*/
|
||||
protected $config_file;
|
||||
|
||||
|
||||
/**
|
||||
* ConfigurationError constructor.
|
||||
*
|
||||
* @param string|null $reason The reason for this exception.
|
||||
* @param string|null $file The configuration file that originated this error.
|
||||
* @param array|null $config The configuration array that led to this problem.
|
||||
*/
|
||||
public function __construct($reason = null, $file = null, array $config = null)
|
||||
{
|
||||
$file_str = '';
|
||||
$reason_str = '.';
|
||||
$params = array('CONFIG');
|
||||
if ($file !== null) {
|
||||
$params['%FILE%'] = $file;
|
||||
$basepath = dirname(dirname(dirname(dirname(__FILE__)))).'/';
|
||||
$file_str = '('.str_replace($basepath, '', $file).') ';
|
||||
}
|
||||
if ($reason !== null) {
|
||||
$params['%REASON%'] = $reason;
|
||||
$reason_str = ': '.$reason;
|
||||
}
|
||||
$this->reason = $reason;
|
||||
$this->config_file = $file;
|
||||
parent::__construct($params);
|
||||
$this->message = 'The configuration '.$file_str.'is invalid'.$reason_str;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the reason for this exception.
|
||||
*
|
||||
* @return null|string The reason for this exception.
|
||||
*/
|
||||
public function getReason()
|
||||
{
|
||||
return $this->reason;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the configuration file that caused this exception.
|
||||
*
|
||||
* @return null|string The configuration file that caused this exception.
|
||||
*/
|
||||
public function getConfFile()
|
||||
{
|
||||
return $this->config_file;
|
||||
}
|
||||
}
|
||||
79
lib/SimpleSAML/Error/CriticalConfigurationError.php
Executable file
79
lib/SimpleSAML/Error/CriticalConfigurationError.php
Executable file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/**
|
||||
* This exception represents a configuration error that we cannot recover from.
|
||||
*
|
||||
* Throwing a critical configuration error indicates that the configuration available is not usable, and as such
|
||||
* SimpleSAMLphp should not try to use it. However, in certain situations we might find a specific configuration
|
||||
* error that makes part of the configuration unusable, while the rest we can still use. In those cases, we can
|
||||
* just pass a configuration array to the constructor, making sure the offending configuration options are removed,
|
||||
* reset to defaults or guessed to some usable value.
|
||||
*
|
||||
* If, for example, we have an error in the 'baseurlpath' configuration option, we can still load the configuration
|
||||
* and substitute the value of that option with one guessed from the environment, using
|
||||
* \SimpleSAML\Utils\HTTP::guessPath(). Doing so, the error is still critical, but at least we can recover up to a
|
||||
* certain point and inform about the error in an ordered manner, without blank pages, logs out of place or even
|
||||
* segfaults.
|
||||
*
|
||||
* @author Jaime Perez Crespo, UNINETT AS <jaime.perez@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
namespace SimpleSAML\Error;
|
||||
|
||||
class CriticalConfigurationError extends ConfigurationError
|
||||
{
|
||||
|
||||
/**
|
||||
* This is the bare minimum configuration that we can use.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $minimum_config = array(
|
||||
'logging.handler' => 'errorlog',
|
||||
'logging.level' => \SimpleSAML\Logger::DEBUG,
|
||||
'errorreporting' => false,
|
||||
'debug' => true,
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* CriticalConfigurationError constructor.
|
||||
*
|
||||
* @param string|null $reason The reason for this critical error.
|
||||
* @param string|null $file The configuration file that originated this error.
|
||||
* @param array|null The configuration array that led to this problem.
|
||||
*/
|
||||
public function __construct($reason = null, $file = null, $config = null)
|
||||
{
|
||||
if ($config === null) {
|
||||
$config = self::$minimum_config;
|
||||
$config['baseurlpath'] = \SimpleSAML\Utils\HTTP::guessBasePath();
|
||||
}
|
||||
|
||||
\SimpleSAML_Configuration::loadFromArray(
|
||||
$config,
|
||||
'',
|
||||
'simplesaml'
|
||||
);
|
||||
parent::__construct($reason, $file);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \Exception $exception
|
||||
*
|
||||
* @return CriticalConfigurationError
|
||||
*/
|
||||
public static function fromException(\Exception $exception)
|
||||
{
|
||||
$reason = null;
|
||||
$file = null;
|
||||
if ($exception instanceof ConfigurationError) {
|
||||
$reason = $exception->getReason();
|
||||
$file = $exception->getConfFile();
|
||||
} else {
|
||||
$reason = $exception->getMessage();
|
||||
}
|
||||
return new CriticalConfigurationError($reason, $file);
|
||||
}
|
||||
}
|
||||
296
lib/SimpleSAML/Error/Error.php
Executable file
296
lib/SimpleSAML/Error/Error.php
Executable file
@@ -0,0 +1,296 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* Class that wraps SimpleSAMLphp errors in exceptions.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS.
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SimpleSAML_Error_Error extends SimpleSAML_Error_Exception
|
||||
{
|
||||
/**
|
||||
* The error code.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $errorCode;
|
||||
|
||||
/**
|
||||
* The http code.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $httpCode = 500;
|
||||
|
||||
/**
|
||||
* The error title tag in dictionary.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $dictTitle;
|
||||
|
||||
/**
|
||||
* The error description tag in dictionary.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $dictDescr;
|
||||
|
||||
/**
|
||||
* The name of module that threw the error.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $module = null;
|
||||
|
||||
/**
|
||||
* The parameters for the error.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $parameters;
|
||||
|
||||
/**
|
||||
* Name of custom include template for the error.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $includeTemplate = null;
|
||||
|
||||
/**
|
||||
* Constructor for this error.
|
||||
*
|
||||
* The error can either be given as a string, or as an array. If it is an array, the first element in the array
|
||||
* (with index 0), is the error code, while the other elements are replacements for the error text.
|
||||
*
|
||||
* @param mixed $errorCode One of the error codes defined in the errors dictionary.
|
||||
* @param Exception $cause The exception which caused this fatal error (if any). Optional.
|
||||
* @param int|null $httpCode The HTTP response code to use. Optional.
|
||||
*/
|
||||
public function __construct($errorCode, Exception $cause = null, $httpCode = null)
|
||||
{
|
||||
assert(is_string($errorCode) || is_array($errorCode));
|
||||
|
||||
if (is_array($errorCode)) {
|
||||
$this->parameters = $errorCode;
|
||||
unset($this->parameters[0]);
|
||||
$this->errorCode = $errorCode[0];
|
||||
} else {
|
||||
$this->parameters = array();
|
||||
$this->errorCode = $errorCode;
|
||||
}
|
||||
|
||||
if (isset($httpCode)) {
|
||||
$this->httpCode = $httpCode;
|
||||
}
|
||||
|
||||
$moduleCode = explode(':', $this->errorCode, 2);
|
||||
if (count($moduleCode) === 2) {
|
||||
$this->module = $moduleCode[0];
|
||||
$this->dictTitle = '{'.$this->module.':errors:title_'.$moduleCode[1].'}';
|
||||
$this->dictDescr = '{'.$this->module.':errors:descr_'.$moduleCode[1].'}';
|
||||
} else {
|
||||
$this->dictTitle = SimpleSAML\Error\ErrorCodes::getErrorCodeTitle($this->errorCode);
|
||||
$this->dictDescr = SimpleSAML\Error\ErrorCodes::getErrorCodeDescription($this->errorCode);
|
||||
}
|
||||
|
||||
if (!empty($this->parameters)) {
|
||||
$msg = $this->errorCode.'(';
|
||||
foreach ($this->parameters as $k => $v) {
|
||||
if ($k === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$msg .= var_export($k, true).' => '.var_export($v, true).', ';
|
||||
}
|
||||
$msg = substr($msg, 0, -2).')';
|
||||
} else {
|
||||
$msg = $this->errorCode;
|
||||
}
|
||||
parent::__construct($msg, -1, $cause);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the error code given when throwing this error.
|
||||
*
|
||||
* @return string The error code.
|
||||
*/
|
||||
public function getErrorCode()
|
||||
{
|
||||
return $this->errorCode;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the error parameters given when throwing this error.
|
||||
*
|
||||
* @return array The parameters.
|
||||
*/
|
||||
public function getParameters()
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the error title tag in dictionary.
|
||||
*
|
||||
* @return string The error title tag.
|
||||
*/
|
||||
public function getDictTitle()
|
||||
{
|
||||
return $this->dictTitle;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the error description tag in dictionary.
|
||||
*
|
||||
* @return string The error description tag.
|
||||
*/
|
||||
public function getDictDescr()
|
||||
{
|
||||
return $this->dictDescr;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the HTTP return code for this error.
|
||||
*
|
||||
* This should be overridden by subclasses who want a different return code than 500 Internal Server Error.
|
||||
*/
|
||||
protected function setHTTPCode()
|
||||
{
|
||||
// Some mostly used HTTP codes
|
||||
$httpCodesMap = array(
|
||||
400 => 'HTTP/1.0 400 Bad Request',
|
||||
403 => 'HTTP/1.0 403 Forbidden',
|
||||
404 => 'HTTP/1.0 404 Not Found',
|
||||
405 => 'HTTP/1.0 405 Method Not Allowed',
|
||||
500 => 'HTTP/1.0 500 Internal Server Error',
|
||||
501 => 'HTTP/1.0 501 Method Not Implemented',
|
||||
503 => 'HTTP/1.0 503 Service Temporarily Unavailable',
|
||||
);
|
||||
|
||||
$httpCode = $this->httpCode;
|
||||
|
||||
if (function_exists('http_response_code')) {
|
||||
http_response_code($httpCode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!array_key_exists($this->httpCode, $httpCodesMap)) {
|
||||
$httpCode = 500;
|
||||
SimpleSAML\Logger::warning('HTTP response code not defined: '.var_export($this->httpCode, true));
|
||||
}
|
||||
|
||||
header($httpCodesMap[$httpCode]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save an error report.
|
||||
*
|
||||
* @return array The array with the error report data.
|
||||
*/
|
||||
protected function saveError()
|
||||
{
|
||||
$data = $this->format(true);
|
||||
$emsg = array_shift($data);
|
||||
$etrace = implode("\n", $data);
|
||||
|
||||
$reportId = bin2hex(openssl_random_pseudo_bytes(4));
|
||||
SimpleSAML\Logger::error('Error report with id '.$reportId.' generated.');
|
||||
|
||||
$config = SimpleSAML_Configuration::getInstance();
|
||||
$session = SimpleSAML_Session::getSessionFromRequest();
|
||||
|
||||
if (isset($_SERVER['HTTP_REFERER'])) {
|
||||
$referer = $_SERVER['HTTP_REFERER'];
|
||||
// remove anything after the first '?' or ';', just in case it contains any sensitive data
|
||||
$referer = explode('?', $referer, 2);
|
||||
$referer = $referer[0];
|
||||
$referer = explode(';', $referer, 2);
|
||||
$referer = $referer[0];
|
||||
} else {
|
||||
$referer = 'unknown';
|
||||
}
|
||||
$errorData = array(
|
||||
'exceptionMsg' => $emsg,
|
||||
'exceptionTrace' => $etrace,
|
||||
'reportId' => $reportId,
|
||||
'trackId' => $session->getTrackID(),
|
||||
'url' => \SimpleSAML\Utils\HTTP::getSelfURLNoQuery(),
|
||||
'version' => $config->getVersion(),
|
||||
'referer' => $referer,
|
||||
);
|
||||
$session->setData('core:errorreport', $reportId, $errorData);
|
||||
|
||||
return $errorData;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display this error.
|
||||
*
|
||||
* This method displays a standard SimpleSAMLphp error page and exits.
|
||||
*/
|
||||
public function show()
|
||||
{
|
||||
$this->setHTTPCode();
|
||||
|
||||
// log the error message
|
||||
$this->logError();
|
||||
|
||||
$errorData = $this->saveError();
|
||||
$config = SimpleSAML_Configuration::getInstance();
|
||||
|
||||
$data = array();
|
||||
$data['showerrors'] = $config->getBoolean('showerrors', true);
|
||||
$data['error'] = $errorData;
|
||||
$data['errorCode'] = $this->errorCode;
|
||||
$data['parameters'] = $this->parameters;
|
||||
$data['module'] = $this->module;
|
||||
$data['dictTitle'] = $this->dictTitle;
|
||||
$data['dictDescr'] = $this->dictDescr;
|
||||
$data['includeTemplate'] = $this->includeTemplate;
|
||||
$data['clipboard.js'] = true;
|
||||
|
||||
// check if there is a valid technical contact email address
|
||||
if ($config->getBoolean('errorreporting', true) &&
|
||||
$config->getString('technicalcontact_email', 'na@example.org') !== 'na@example.org'
|
||||
) {
|
||||
// enable error reporting
|
||||
$baseurl = \SimpleSAML\Utils\HTTP::getBaseURL();
|
||||
$data['errorReportAddress'] = $baseurl.'errorreport.php';
|
||||
}
|
||||
|
||||
$data['email'] = '';
|
||||
$session = SimpleSAML_Session::getSessionFromRequest();
|
||||
$authorities = $session->getAuthorities();
|
||||
foreach ($authorities as $authority) {
|
||||
$attributes = $session->getAuthData($authority, 'Attributes');
|
||||
if ($attributes !== null && array_key_exists('mail', $attributes) && count($attributes['mail']) > 0) {
|
||||
$data['email'] = $attributes['mail'][0];
|
||||
break; // enough, don't need to get all available mails, if more than one
|
||||
}
|
||||
}
|
||||
|
||||
$show_function = $config->getArray('errors.show_function', null);
|
||||
if (isset($show_function)) {
|
||||
assert(is_callable($show_function));
|
||||
call_user_func($show_function, $config, $data);
|
||||
assert(false);
|
||||
} else {
|
||||
$t = new SimpleSAML_XHTML_Template($config, 'error.php', 'errors');
|
||||
$t->data = array_merge($t->data, $data);
|
||||
$t->data['dictTitleTranslated'] = $t->getTranslator()->t($t->data['dictTitle']);
|
||||
$t->data['dictDescrTranslated'] = $t->getTranslator()->t($t->data['dictDescr'], $t->data['parameters']);
|
||||
$t->show();
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
}
|
||||
188
lib/SimpleSAML/Error/ErrorCodes.php
Executable file
188
lib/SimpleSAML/Error/ErrorCodes.php
Executable file
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
/**
|
||||
* Class that maps SimpleSAMLphp error codes to translateable strings.
|
||||
*
|
||||
* @author Hanne Moa, UNINETT AS. <hanne.moa@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
namespace SimpleSAML\Error;
|
||||
|
||||
class ErrorCodes
|
||||
{
|
||||
/**
|
||||
* Fetch all default translation strings for error code titles.
|
||||
*
|
||||
* @return array A map from error code to error code title
|
||||
*/
|
||||
final public static function defaultGetAllErrorCodeTitles()
|
||||
{
|
||||
return array(
|
||||
'ACSPARAMS' => \SimpleSAML\Locale\Translate::noop('{errors:title_ACSPARAMS}'),
|
||||
'ARSPARAMS' => \SimpleSAML\Locale\Translate::noop('{errors:title_ARSPARAMS}'),
|
||||
'AUTHSOURCEERROR' => \SimpleSAML\Locale\Translate::noop('{errors:title_AUTHSOURCEERROR}'),
|
||||
'BADREQUEST' => \SimpleSAML\Locale\Translate::noop('{errors:title_BADREQUEST}'),
|
||||
'CASERROR' => \SimpleSAML\Locale\Translate::noop('{errors:title_CASERROR}'),
|
||||
'CONFIG' => \SimpleSAML\Locale\Translate::noop('{errors:title_CONFIG}'),
|
||||
'CREATEREQUEST' => \SimpleSAML\Locale\Translate::noop('{errors:title_CREATEREQUEST}'),
|
||||
'DISCOPARAMS' => \SimpleSAML\Locale\Translate::noop('{errors:title_DISCOPARAMS}'),
|
||||
'GENERATEAUTHNRESPONSE' => \SimpleSAML\Locale\Translate::noop('{errors:title_GENERATEAUTHNRESPONSE}'),
|
||||
'INVALIDCERT' => \SimpleSAML\Locale\Translate::noop('{errors:title_INVALIDCERT}'),
|
||||
'LDAPERROR' => \SimpleSAML\Locale\Translate::noop('{errors:title_LDAPERROR}'),
|
||||
'LOGOUTINFOLOST' => \SimpleSAML\Locale\Translate::noop('{errors:title_LOGOUTINFOLOST}'),
|
||||
'LOGOUTREQUEST' => \SimpleSAML\Locale\Translate::noop('{errors:title_LOGOUTREQUEST}'),
|
||||
'MEMCACHEDOWN' => \SimpleSAML\Locale\Translate::noop('{errors:title_MEMCACHEDOWN}'),
|
||||
'METADATA' => \SimpleSAML\Locale\Translate::noop('{errors:title_METADATA}'),
|
||||
'METADATANOTFOUND' => \SimpleSAML\Locale\Translate::noop('{errors:title_METADATANOTFOUND}'),
|
||||
'NOACCESS' => \SimpleSAML\Locale\Translate::noop('{errors:title_NOACCESS}'),
|
||||
'NOCERT' => \SimpleSAML\Locale\Translate::noop('{errors:title_NOCERT}'),
|
||||
'NORELAYSTATE' => \SimpleSAML\Locale\Translate::noop('{errors:title_NORELAYSTATE}'),
|
||||
'NOSTATE' => \SimpleSAML\Locale\Translate::noop('{errors:title_NOSTATE}'),
|
||||
'NOTFOUND' => \SimpleSAML\Locale\Translate::noop('{errors:title_NOTFOUND}'),
|
||||
'NOTFOUNDREASON' => \SimpleSAML\Locale\Translate::noop('{errors:title_NOTFOUNDREASON}'),
|
||||
'NOTSET' => \SimpleSAML\Locale\Translate::noop('{errors:title_NOTSET}'),
|
||||
'NOTVALIDCERT' => \SimpleSAML\Locale\Translate::noop('{errors:title_NOTVALIDCERT}'),
|
||||
'PROCESSASSERTION' => \SimpleSAML\Locale\Translate::noop('{errors:title_PROCESSASSERTION}'),
|
||||
'PROCESSAUTHNREQUEST' => \SimpleSAML\Locale\Translate::noop('{errors:title_PROCESSAUTHNREQUEST}'),
|
||||
'RESPONSESTATUSNOSUCCESS' => \SimpleSAML\Locale\Translate::noop('{errors:title_RESPONSESTATUSNOSUCCESS}'),
|
||||
'SLOSERVICEPARAMS' => \SimpleSAML\Locale\Translate::noop('{errors:title_SLOSERVICEPARAMS}'),
|
||||
'SSOPARAMS' => \SimpleSAML\Locale\Translate::noop('{errors:title_SSOPARAMS}'),
|
||||
'UNHANDLEDEXCEPTION' => \SimpleSAML\Locale\Translate::noop('{errors:title_UNHANDLEDEXCEPTION}'),
|
||||
'UNKNOWNCERT' => \SimpleSAML\Locale\Translate::noop('{errors:title_UNKNOWNCERT}'),
|
||||
'USERABORTED' => \SimpleSAML\Locale\Translate::noop('{errors:title_USERABORTED}'),
|
||||
'WRONGUSERPASS' => \SimpleSAML\Locale\Translate::noop('{errors:title_WRONGUSERPASS}'),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetch all translation strings for error code titles.
|
||||
*
|
||||
* Extend this to add error codes.
|
||||
*
|
||||
* @return array A map from error code to error code title
|
||||
*/
|
||||
public static function getAllErrorCodeTitles()
|
||||
{
|
||||
return self::defaultGetAllErrorCodeTitles();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetch all default translation strings for error code descriptions.
|
||||
*
|
||||
* @return string A map from error code to error code description
|
||||
*/
|
||||
final public static function defaultGetAllErrorCodeDescriptions()
|
||||
{
|
||||
return array(
|
||||
'ACSPARAMS' => \SimpleSAML\Locale\Translate::noop('{errors:descr_ACSPARAMS}'),
|
||||
'ARSPARAMS' => \SimpleSAML\Locale\Translate::noop('{errors:descr_ARSPARAMS}'),
|
||||
'AUTHSOURCEERROR' => \SimpleSAML\Locale\Translate::noop('{errors:descr_AUTHSOURCEERROR}'),
|
||||
'BADREQUEST' => \SimpleSAML\Locale\Translate::noop('{errors:descr_BADREQUEST}'),
|
||||
'CASERROR' => \SimpleSAML\Locale\Translate::noop('{errors:descr_CASERROR}'),
|
||||
'CONFIG' => \SimpleSAML\Locale\Translate::noop('{errors:descr_CONFIG}'),
|
||||
'CREATEREQUEST' => \SimpleSAML\Locale\Translate::noop('{errors:descr_CREATEREQUEST}'),
|
||||
'DISCOPARAMS' => \SimpleSAML\Locale\Translate::noop('{errors:descr_DISCOPARAMS}'),
|
||||
'GENERATEAUTHNRESPONSE' => \SimpleSAML\Locale\Translate::noop('{errors:descr_GENERATEAUTHNRESPONSE}'),
|
||||
'INVALIDCERT' => \SimpleSAML\Locale\Translate::noop('{errors:descr_INVALIDCERT}'),
|
||||
'LDAPERROR' => \SimpleSAML\Locale\Translate::noop('{errors:descr_LDAPERROR}'),
|
||||
'LOGOUTINFOLOST' => \SimpleSAML\Locale\Translate::noop('{errors:descr_LOGOUTINFOLOST}'),
|
||||
'LOGOUTREQUEST' => \SimpleSAML\Locale\Translate::noop('{errors:descr_LOGOUTREQUEST}'),
|
||||
'MEMCACHEDOWN' => \SimpleSAML\Locale\Translate::noop('{errors:descr_MEMCACHEDOWN}'),
|
||||
'METADATA' => \SimpleSAML\Locale\Translate::noop('{errors:descr_METADATA}'),
|
||||
'METADATANOTFOUND' => \SimpleSAML\Locale\Translate::noop('{errors:descr_METADATANOTFOUND}'),
|
||||
'NOACCESS' => \SimpleSAML\Locale\Translate::noop('{errors:descr_NOACCESS}'),
|
||||
'NOCERT' => \SimpleSAML\Locale\Translate::noop('{errors:descr_NOCERT}'),
|
||||
'NORELAYSTATE' => \SimpleSAML\Locale\Translate::noop('{errors:descr_NORELAYSTATE}'),
|
||||
'NOSTATE' => \SimpleSAML\Locale\Translate::noop('{errors:descr_NOSTATE}'),
|
||||
'NOTFOUND' => \SimpleSAML\Locale\Translate::noop('{errors:descr_NOTFOUND}'),
|
||||
'NOTFOUNDREASON' => \SimpleSAML\Locale\Translate::noop('{errors:descr_NOTFOUNDREASON}'),
|
||||
'NOTSET' => \SimpleSAML\Locale\Translate::noop('{errors:descr_NOTSET}'),
|
||||
'NOTVALIDCERT' => \SimpleSAML\Locale\Translate::noop('{errors:descr_NOTVALIDCERT}'),
|
||||
'PROCESSASSERTION' => \SimpleSAML\Locale\Translate::noop('{errors:descr_PROCESSASSERTION}'),
|
||||
'PROCESSAUTHNREQUEST' => \SimpleSAML\Locale\Translate::noop('{errors:descr_PROCESSAUTHNREQUEST}'),
|
||||
'RESPONSESTATUSNOSUCCESS' => \SimpleSAML\Locale\Translate::noop('{errors:descr_RESPONSESTATUSNOSUCCESS}'),
|
||||
'SLOSERVICEPARAMS' => \SimpleSAML\Locale\Translate::noop('{errors:descr_SLOSERVICEPARAMS}'),
|
||||
'SSOPARAMS' => \SimpleSAML\Locale\Translate::noop('{errors:descr_SSOPARAMS}'),
|
||||
'UNHANDLEDEXCEPTION' => \SimpleSAML\Locale\Translate::noop('{errors:descr_UNHANDLEDEXCEPTION}'),
|
||||
'UNKNOWNCERT' => \SimpleSAML\Locale\Translate::noop('{errors:descr_UNKNOWNCERT}'),
|
||||
'USERABORTED' => \SimpleSAML\Locale\Translate::noop('{errors:descr_USERABORTED}'),
|
||||
'WRONGUSERPASS' => \SimpleSAML\Locale\Translate::noop('{errors:descr_WRONGUSERPASS}'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all translation strings for error code descriptions.
|
||||
*
|
||||
* Extend this to add error codes.
|
||||
*
|
||||
* @return string A map from error code to error code description
|
||||
*/
|
||||
public static function getAllErrorCodeDescriptions()
|
||||
{
|
||||
return self::defaultGetAllErrorCodeDescriptions();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a map of both errorcode titles and descriptions
|
||||
*
|
||||
* Convenience-method for template-callers
|
||||
*
|
||||
* @return array An array containing both errorcode maps.
|
||||
*/
|
||||
public static function getAllErrorCodeMessages()
|
||||
{
|
||||
return array(
|
||||
'title' => self::getAllErrorCodeTitles(),
|
||||
'descr' => self::getAllErrorCodeDescriptions(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetch a translation string for a title for a given error code.
|
||||
*
|
||||
* @param string $errorCode The error code to look up
|
||||
*
|
||||
* @return string A string to translate
|
||||
*/
|
||||
public static function getErrorCodeTitle($errorCode)
|
||||
{
|
||||
$errorCodeTitles = self::getAllErrorCodeTitles();
|
||||
return $errorCodeTitles[$errorCode];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetch a translation string for a description for a given error code.
|
||||
*
|
||||
* @param string $errorCode The error code to look up
|
||||
*
|
||||
* @return string A string to translate
|
||||
*/
|
||||
public static function getErrorCodeDescription($errorCode)
|
||||
{
|
||||
$errorCodeDescriptions = self::getAllErrorCodeDescriptions();
|
||||
return $errorCodeDescriptions[$errorCode];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get both title and description for a specific error code
|
||||
*
|
||||
* Convenience-method for template-callers
|
||||
*
|
||||
* @param string $errorCode The error code to look up
|
||||
*
|
||||
* @return array An array containing both errorcode strings.
|
||||
*/
|
||||
public static function getErrorCodeMessage($errorCode)
|
||||
{
|
||||
return array(
|
||||
'title' => self::getErrorCodeTitle($errorCode),
|
||||
'descr' => self::getErrorCodeDescription($errorCode),
|
||||
);
|
||||
}
|
||||
}
|
||||
316
lib/SimpleSAML/Error/Exception.php
Executable file
316
lib/SimpleSAML/Error/Exception.php
Executable file
@@ -0,0 +1,316 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* Base class for SimpleSAMLphp Exceptions
|
||||
*
|
||||
* This class tries to make sure that every exception is serializable.
|
||||
*
|
||||
* @author Thomas Graff <thomas.graff@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SimpleSAML_Error_Exception extends Exception
|
||||
{
|
||||
|
||||
/**
|
||||
* The backtrace for this exception.
|
||||
*
|
||||
* We need to save the backtrace, since we cannot rely on
|
||||
* serializing the Exception::trace-variable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $backtrace;
|
||||
|
||||
|
||||
/**
|
||||
* The cause of this exception.
|
||||
*
|
||||
* @var SimpleSAML_Error_Exception
|
||||
*/
|
||||
private $cause;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor for this error.
|
||||
*
|
||||
* Note that the cause will be converted to a SimpleSAML_Error_UnserializableException unless it is a subclass of
|
||||
* SimpleSAML_Error_Exception.
|
||||
*
|
||||
* @param string $message Exception message
|
||||
* @param int $code Error code
|
||||
* @param Exception|null $cause The cause of this exception.
|
||||
*/
|
||||
public function __construct($message, $code = 0, Exception $cause = null)
|
||||
{
|
||||
assert(is_string($message));
|
||||
assert(is_int($code));
|
||||
|
||||
parent::__construct($message, $code);
|
||||
|
||||
$this->initBacktrace($this);
|
||||
|
||||
if ($cause !== null) {
|
||||
$this->cause = SimpleSAML_Error_Exception::fromException($cause);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert any exception into a SimpleSAML_Error_Exception.
|
||||
*
|
||||
* @param Exception $e The exception.
|
||||
*
|
||||
* @return SimpleSAML_Error_Exception The new exception.
|
||||
*/
|
||||
public static function fromException(Exception $e)
|
||||
{
|
||||
|
||||
if ($e instanceof SimpleSAML_Error_Exception) {
|
||||
return $e;
|
||||
}
|
||||
return new SimpleSAML_Error_UnserializableException($e);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load the backtrace from the given exception.
|
||||
*
|
||||
* @param Exception $exception The exception we should fetch the backtrace from.
|
||||
*/
|
||||
protected function initBacktrace(Exception $exception)
|
||||
{
|
||||
|
||||
$this->backtrace = array();
|
||||
|
||||
// position in the top function on the stack
|
||||
$pos = $exception->getFile().':'.$exception->getLine();
|
||||
|
||||
foreach ($exception->getTrace() as $t) {
|
||||
$function = $t['function'];
|
||||
if (array_key_exists('class', $t)) {
|
||||
$function = $t['class'].'::'.$function;
|
||||
}
|
||||
|
||||
$this->backtrace[] = $pos.' ('.$function.')';
|
||||
|
||||
if (array_key_exists('file', $t)) {
|
||||
$pos = $t['file'].':'.$t['line'];
|
||||
} else {
|
||||
$pos = '[builtin]';
|
||||
}
|
||||
}
|
||||
|
||||
$this->backtrace[] = $pos.' (N/A)';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the backtrace.
|
||||
*
|
||||
* @return array An array where each function call is a single item.
|
||||
*/
|
||||
public function getBacktrace()
|
||||
{
|
||||
return $this->backtrace;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the cause of this exception.
|
||||
*
|
||||
* @return SimpleSAML_Error_Exception|null The cause of this exception.
|
||||
*/
|
||||
public function getCause()
|
||||
{
|
||||
return $this->cause;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the class of this exception.
|
||||
*
|
||||
* @return string The name of the class.
|
||||
*/
|
||||
public function getClass()
|
||||
{
|
||||
return get_class($this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Format this exception for logging.
|
||||
*
|
||||
* Create an array of lines for logging.
|
||||
*
|
||||
* @param boolean $anonymize Whether the resulting messages should be anonymized or not.
|
||||
*
|
||||
* @return array Log lines that should be written out.
|
||||
*/
|
||||
public function format($anonymize = false)
|
||||
{
|
||||
$ret = array(
|
||||
$this->getClass().': '.$this->getMessage(),
|
||||
);
|
||||
return array_merge($ret, $this->formatBacktrace($anonymize));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Format the backtrace for logging.
|
||||
*
|
||||
* Create an array of lines for logging from the backtrace.
|
||||
*
|
||||
* @param boolean $anonymize Whether the resulting messages should be anonymized or not.
|
||||
*
|
||||
* @return array All lines of the backtrace, properly formatted.
|
||||
*/
|
||||
public function formatBacktrace($anonymize = false)
|
||||
{
|
||||
$ret = array();
|
||||
$basedir = SimpleSAML_Configuration::getInstance()->getBaseDir();
|
||||
|
||||
$e = $this;
|
||||
do {
|
||||
if ($e !== $this) {
|
||||
$ret[] = 'Caused by: '.$e->getClass().': '.$e->getMessage();
|
||||
}
|
||||
$ret[] = 'Backtrace:';
|
||||
|
||||
$depth = count($e->backtrace);
|
||||
foreach ($e->backtrace as $i => $trace) {
|
||||
if ($anonymize) {
|
||||
$trace = str_replace($basedir, '', $trace);
|
||||
}
|
||||
|
||||
$ret[] = ($depth - $i - 1).' '.$trace;
|
||||
}
|
||||
$e = $e->cause;
|
||||
} while ($e !== null);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Print the backtrace to the log if the 'debug' option is enabled in the configuration.
|
||||
*/
|
||||
protected function logBacktrace($level = \SimpleSAML\Logger::DEBUG)
|
||||
{
|
||||
// see if debugging is enabled for backtraces
|
||||
$debug = SimpleSAML_Configuration::getInstance()->getArrayize('debug', array('backtraces' => false));
|
||||
|
||||
if (!(in_array('backtraces', $debug, true) // implicitly enabled
|
||||
|| (array_key_exists('backtraces', $debug) && $debug['backtraces'] === true) // explicitly set
|
||||
// TODO: deprecate the old style and remove it in 2.0
|
||||
|| (array_key_exists(0, $debug) && $debug[0] === true) // old style 'debug' configuration option
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$backtrace = $this->formatBacktrace();
|
||||
|
||||
$callback = array('\SimpleSAML\Logger');
|
||||
$functions = array(
|
||||
\SimpleSAML\Logger::ERR => 'error',
|
||||
\SimpleSAML\Logger::WARNING => 'warning',
|
||||
\SimpleSAML\Logger::INFO => 'info',
|
||||
\SimpleSAML\Logger::DEBUG => 'debug',
|
||||
);
|
||||
$callback[] = $functions[$level];
|
||||
|
||||
foreach ($backtrace as $line) {
|
||||
call_user_func($callback, $line);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Print the exception to the log, by default with log level error.
|
||||
*
|
||||
* Override to allow errors extending this class to specify the log level themselves.
|
||||
*
|
||||
* @param int $default_level The log level to use if this method was not overridden.
|
||||
*/
|
||||
public function log($default_level)
|
||||
{
|
||||
$fn = array(
|
||||
SimpleSAML\Logger::ERR => 'logError',
|
||||
SimpleSAML\Logger::WARNING => 'logWarning',
|
||||
SimpleSAML\Logger::INFO => 'logInfo',
|
||||
SimpleSAML\Logger::DEBUG => 'logDebug',
|
||||
);
|
||||
call_user_func(array($this, $fn[$default_level]), $default_level);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Print the exception to the log with log level error.
|
||||
*
|
||||
* This function will write this exception to the log, including a full backtrace.
|
||||
*/
|
||||
public function logError()
|
||||
{
|
||||
SimpleSAML\Logger::error($this->getClass().': '.$this->getMessage());
|
||||
$this->logBacktrace(\SimpleSAML\Logger::ERR);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Print the exception to the log with log level warning.
|
||||
*
|
||||
* This function will write this exception to the log, including a full backtrace.
|
||||
*/
|
||||
public function logWarning()
|
||||
{
|
||||
SimpleSAML\Logger::warning($this->getClass().': '.$this->getMessage());
|
||||
$this->logBacktrace(\SimpleSAML\Logger::WARNING);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Print the exception to the log with log level info.
|
||||
*
|
||||
* This function will write this exception to the log, including a full backtrace.
|
||||
*/
|
||||
public function logInfo()
|
||||
{
|
||||
SimpleSAML\Logger::info($this->getClass().': '.$this->getMessage());
|
||||
$this->logBacktrace(\SimpleSAML\Logger::INFO);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Print the exception to the log with log level debug.
|
||||
*
|
||||
* This function will write this exception to the log, including a full backtrace.
|
||||
*/
|
||||
public function logDebug()
|
||||
{
|
||||
SimpleSAML\Logger::debug($this->getClass().': '.$this->getMessage());
|
||||
$this->logBacktrace(\SimpleSAML\Logger::DEBUG);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function for serialization.
|
||||
*
|
||||
* This function builds a list of all variables which should be serialized. It will serialize all variables except
|
||||
* the Exception::trace variable.
|
||||
*
|
||||
* @return array Array with the variables that should be serialized.
|
||||
*/
|
||||
public function __sleep()
|
||||
{
|
||||
|
||||
$ret = array_keys((array) $this);
|
||||
|
||||
foreach ($ret as $i => $e) {
|
||||
if ($e === "\0Exception\0trace") {
|
||||
unset($ret[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
12
lib/SimpleSAML/Error/InvalidCredential.php
Executable file
12
lib/SimpleSAML/Error/InvalidCredential.php
Executable file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
/**
|
||||
* Exception indicating wrong password given by user.
|
||||
*
|
||||
* @author Thomas Graff <thomas.graff@uninett.no>
|
||||
* @package SimpleSAMLphp_base
|
||||
*
|
||||
*/
|
||||
class SimpleSAML_Error_InvalidCredential extends SimpleSAML_Error_User
|
||||
{
|
||||
|
||||
}
|
||||
27
lib/SimpleSAML/Error/MetadataNotFound.php
Executable file
27
lib/SimpleSAML/Error/MetadataNotFound.php
Executable file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Error for missing metadata.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SimpleSAML_Error_MetadataNotFound extends SimpleSAML_Error_Error
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Create the error
|
||||
*
|
||||
* @param string $entityId The entityID we were unable to locate.
|
||||
*/
|
||||
public function __construct($entityId)
|
||||
{
|
||||
assert(is_string($entityId));
|
||||
|
||||
$this->includeTemplate = 'core:no_metadata.tpl.php';
|
||||
parent::__construct(array(
|
||||
'METADATANOTFOUND',
|
||||
'%ENTITYID%' => htmlspecialchars(var_export($entityId, true))
|
||||
));
|
||||
}
|
||||
}
|
||||
15
lib/SimpleSAML/Error/NoPassive.php
Executable file
15
lib/SimpleSAML/Error/NoPassive.php
Executable file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* Class SimpleSAML_Error_NoPassive
|
||||
*
|
||||
* @deprecated This class has been deprecated and will be removed in SimpleSAMLphp 2.0. Please use
|
||||
* SimpleSAML\Module\saml\Error\NoPassive instead.
|
||||
*
|
||||
* @see \SimpleSAML\Module\saml\Error\NoPassive
|
||||
*/
|
||||
class SimpleSAML_Error_NoPassive extends SimpleSAML_Error_Exception
|
||||
{
|
||||
|
||||
}
|
||||
21
lib/SimpleSAML/Error/NoState.php
Executable file
21
lib/SimpleSAML/Error/NoState.php
Executable file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception which will show a page telling the user
|
||||
* that we don't know what to do.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SimpleSAML_Error_NoState extends SimpleSAML_Error_Error
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Create the error
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->includeTemplate = 'core:no_state.tpl.php';
|
||||
parent::__construct('NOSTATE');
|
||||
}
|
||||
}
|
||||
72
lib/SimpleSAML/Error/NotFound.php
Executable file
72
lib/SimpleSAML/Error/NotFound.php
Executable file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception which will show a 404 Not Found error page.
|
||||
*
|
||||
* This exception can be thrown from within a module page handler. The user will then be shown a 404 Not Found error
|
||||
* page.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS.
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SimpleSAML_Error_NotFound extends SimpleSAML_Error_Error
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Reason why the given page could not be found.
|
||||
*/
|
||||
private $reason;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new NotFound error
|
||||
*
|
||||
* @param string $reason Optional description of why the given page could not be found.
|
||||
*/
|
||||
public function __construct($reason = null)
|
||||
{
|
||||
|
||||
assert($reason === null || is_string($reason));
|
||||
|
||||
$url = \SimpleSAML\Utils\HTTP::getSelfURL();
|
||||
|
||||
if ($reason === null) {
|
||||
parent::__construct(array('NOTFOUND', '%URL%' => $url));
|
||||
$this->message = "The requested page '$url' could not be found.";
|
||||
} else {
|
||||
parent::__construct(array('NOTFOUNDREASON', '%URL%' => $url, '%REASON%' => $reason));
|
||||
$this->message = "The requested page '$url' could not be found. ".$reason;
|
||||
}
|
||||
|
||||
$this->reason = $reason;
|
||||
$this->httpCode = 404;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the reason why the given page could not be found.
|
||||
*
|
||||
* @return string|null The reason why the page could not be found.
|
||||
*/
|
||||
public function getReason()
|
||||
{
|
||||
return $this->reason;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* NotFound exceptions don't need to display a backtrace, as they are very simple and the trace is usually trivial,
|
||||
* so just log the message without any backtrace at all.
|
||||
*
|
||||
* @param bool $anonymize Whether to anonymize the trace or not.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function format($anonymize = false)
|
||||
{
|
||||
return array(
|
||||
$this->getClass().': '.$this->getMessage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
15
lib/SimpleSAML/Error/ProxyCountExceeded.php
Executable file
15
lib/SimpleSAML/Error/ProxyCountExceeded.php
Executable file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* Class SimpleSAML_Error_ProxyCountExceeded
|
||||
*
|
||||
* @deprecated This class has been deprecated and will be removed in SimpleSAMLphp 2.0. Please use
|
||||
* SimpleSAML\Module\saml\Error\ProxyCountExceeded instead.
|
||||
*
|
||||
* @see \SimpleSAML\Module\saml\Error\ProxyCountExceeded
|
||||
*/
|
||||
class SimpleSAML_Error_ProxyCountExceeded extends SimpleSAML_Error_Exception
|
||||
{
|
||||
|
||||
}
|
||||
57
lib/SimpleSAML/Error/UnserializableException.php
Executable file
57
lib/SimpleSAML/Error/UnserializableException.php
Executable file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class for saving normal exceptions for serialization.
|
||||
*
|
||||
* This class is used by the SimpleSAML_Auth_State class when it needs
|
||||
* to serialize an exception which doesn't subclass the
|
||||
* SimpleSAML_Error_Exception class.
|
||||
*
|
||||
* It creates a new exception which contains the backtrace and message
|
||||
* of the original exception.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SimpleSAML_Error_UnserializableException extends SimpleSAML_Error_Exception
|
||||
{
|
||||
|
||||
/**
|
||||
* The classname of the original exception.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $class;
|
||||
|
||||
|
||||
/**
|
||||
* Create a serializable exception representing an unserializable exception.
|
||||
*
|
||||
* @param Exception $original The original exception.
|
||||
*/
|
||||
public function __construct(Exception $original)
|
||||
{
|
||||
|
||||
$this->class = get_class($original);
|
||||
$msg = $original->getMessage();
|
||||
$code = $original->getCode();
|
||||
|
||||
if (!is_int($code)) {
|
||||
// PDOException uses a string as the code. Filter it out here.
|
||||
$code = -1;
|
||||
}
|
||||
|
||||
parent::__construct($msg, $code);
|
||||
$this->initBacktrace($original);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the class of this exception.
|
||||
*
|
||||
* @return string The classname.
|
||||
*/
|
||||
public function getClass()
|
||||
{
|
||||
return $this->class;
|
||||
}
|
||||
}
|
||||
14
lib/SimpleSAML/Error/User.php
Executable file
14
lib/SimpleSAML/Error/User.php
Executable file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Baseclass for user error exceptions
|
||||
*
|
||||
*
|
||||
* @author Thomas Graff <thomas.graff@uninett.no>
|
||||
* @package SimpleSAMLphp_base
|
||||
*
|
||||
*/
|
||||
class SimpleSAML_Error_User extends SimpleSAML_Error_Exception
|
||||
{
|
||||
|
||||
}
|
||||
20
lib/SimpleSAML/Error/UserAborted.php
Executable file
20
lib/SimpleSAML/Error/UserAborted.php
Executable file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception indicating user aborting the authentication process.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SimpleSAML_Error_UserAborted extends SimpleSAML_Error_Error
|
||||
{
|
||||
|
||||
/**
|
||||
* Create the error
|
||||
*
|
||||
* @param Exception|null $cause The exception that caused this error.
|
||||
*/
|
||||
public function __construct(Exception $cause = null)
|
||||
{
|
||||
parent::__construct('USERABORTED', $cause);
|
||||
}
|
||||
}
|
||||
13
lib/SimpleSAML/Error/UserNotFound.php
Executable file
13
lib/SimpleSAML/Error/UserNotFound.php
Executable file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception indicating user not found by authsource.
|
||||
*
|
||||
* @author Thomas Graff <thomas.graff@uninett.no>
|
||||
* @package SimpleSAMLphp_base
|
||||
*
|
||||
*/
|
||||
class SimpleSAML_Error_UserNotFound extends SimpleSAML_Error_User
|
||||
{
|
||||
|
||||
}
|
||||
558
lib/SimpleSAML/IdP.php
Executable file
558
lib/SimpleSAML/IdP.php
Executable file
@@ -0,0 +1,558 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* IdP class.
|
||||
*
|
||||
* This class implements the various functions used by IdP.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SimpleSAML_IdP
|
||||
{
|
||||
/**
|
||||
* A cache for resolving IdP id's.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $idpCache = array();
|
||||
|
||||
/**
|
||||
* The identifier for this IdP.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* The "association group" for this IdP.
|
||||
*
|
||||
* We use this to support cross-protocol logout until
|
||||
* we implement a cross-protocol IdP.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $associationGroup;
|
||||
|
||||
/**
|
||||
* The configuration for this IdP.
|
||||
*
|
||||
* @var SimpleSAML_Configuration
|
||||
*/
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* Our authsource.
|
||||
*
|
||||
* @var \SimpleSAML\Auth\Simple
|
||||
*/
|
||||
private $authSource;
|
||||
|
||||
/**
|
||||
* Initialize an IdP.
|
||||
*
|
||||
* @param string $id The identifier of this IdP.
|
||||
*
|
||||
* @throws SimpleSAML_Error_Exception If the IdP is disabled or no such auth source was found.
|
||||
*/
|
||||
private function __construct($id)
|
||||
{
|
||||
assert(is_string($id));
|
||||
|
||||
$this->id = $id;
|
||||
|
||||
$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
|
||||
$globalConfig = SimpleSAML_Configuration::getInstance();
|
||||
|
||||
if (substr($id, 0, 6) === 'saml2:') {
|
||||
if (!$globalConfig->getBoolean('enable.saml20-idp', false)) {
|
||||
throw new SimpleSAML_Error_Exception('enable.saml20-idp disabled in config.php.');
|
||||
}
|
||||
$this->config = $metadata->getMetaDataConfig(substr($id, 6), 'saml20-idp-hosted');
|
||||
} elseif (substr($id, 0, 6) === 'saml1:') {
|
||||
if (!$globalConfig->getBoolean('enable.shib13-idp', false)) {
|
||||
throw new SimpleSAML_Error_Exception('enable.shib13-idp disabled in config.php.');
|
||||
}
|
||||
$this->config = $metadata->getMetaDataConfig(substr($id, 6), 'shib13-idp-hosted');
|
||||
} elseif (substr($id, 0, 5) === 'adfs:') {
|
||||
if (!$globalConfig->getBoolean('enable.adfs-idp', false)) {
|
||||
throw new SimpleSAML_Error_Exception('enable.adfs-idp disabled in config.php.');
|
||||
}
|
||||
$this->config = $metadata->getMetaDataConfig(substr($id, 5), 'adfs-idp-hosted');
|
||||
|
||||
try {
|
||||
// this makes the ADFS IdP use the same SP associations as the SAML 2.0 IdP
|
||||
$saml2EntityId = $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted');
|
||||
$this->associationGroup = 'saml2:'.$saml2EntityId;
|
||||
} catch (Exception $e) {
|
||||
// probably no SAML 2 IdP configured for this host. Ignore the error
|
||||
}
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
if ($this->associationGroup === null) {
|
||||
$this->associationGroup = $this->id;
|
||||
}
|
||||
|
||||
$auth = $this->config->getString('auth');
|
||||
if (SimpleSAML_Auth_Source::getById($auth) !== null) {
|
||||
$this->authSource = new \SimpleSAML\Auth\Simple($auth);
|
||||
} else {
|
||||
throw new SimpleSAML_Error_Exception('No such "'.$auth.'" auth source found.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the ID of this IdP.
|
||||
*
|
||||
* @return string The ID of this IdP.
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve an IdP by ID.
|
||||
*
|
||||
* @param string $id The identifier of the IdP.
|
||||
*
|
||||
* @return SimpleSAML_IdP The IdP.
|
||||
*/
|
||||
public static function getById($id)
|
||||
{
|
||||
assert(is_string($id));
|
||||
|
||||
if (isset(self::$idpCache[$id])) {
|
||||
return self::$idpCache[$id];
|
||||
}
|
||||
|
||||
$idp = new self($id);
|
||||
self::$idpCache[$id] = $idp;
|
||||
return $idp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the IdP "owning" the state.
|
||||
*
|
||||
* @param array &$state The state array.
|
||||
*
|
||||
* @return SimpleSAML_IdP The IdP.
|
||||
*/
|
||||
public static function getByState(array &$state)
|
||||
{
|
||||
assert(isset($state['core:IdP']));
|
||||
|
||||
return self::getById($state['core:IdP']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the configuration for this IdP.
|
||||
*
|
||||
* @return SimpleSAML_Configuration The configuration object.
|
||||
*/
|
||||
public function getConfig()
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get SP name.
|
||||
*
|
||||
* @param string $assocId The association identifier.
|
||||
*
|
||||
* @return array|null The name of the SP, as an associative array of language => text, or null if this isn't an SP.
|
||||
*/
|
||||
public function getSPName($assocId)
|
||||
{
|
||||
assert(is_string($assocId));
|
||||
|
||||
$prefix = substr($assocId, 0, 4);
|
||||
$spEntityId = substr($assocId, strlen($prefix) + 1);
|
||||
$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
|
||||
|
||||
if ($prefix === 'saml') {
|
||||
try {
|
||||
$spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
|
||||
} catch (Exception $e) {
|
||||
try {
|
||||
$spMetadata = $metadata->getMetaDataConfig($spEntityId, 'shib13-sp-remote');
|
||||
} catch (Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($prefix === 'adfs') {
|
||||
$spMetadata = $metadata->getMetaDataConfig($spEntityId, 'adfs-sp-remote');
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($spMetadata->hasValue('name')) {
|
||||
return $spMetadata->getLocalizedString('name');
|
||||
} elseif ($spMetadata->hasValue('OrganizationDisplayName')) {
|
||||
return $spMetadata->getLocalizedString('OrganizationDisplayName');
|
||||
} else {
|
||||
return array('en' => $spEntityId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add an SP association.
|
||||
*
|
||||
* @param array $association The SP association.
|
||||
*/
|
||||
public function addAssociation(array $association)
|
||||
{
|
||||
assert(isset($association['id']));
|
||||
assert(isset($association['Handler']));
|
||||
|
||||
$association['core:IdP'] = $this->id;
|
||||
|
||||
$session = SimpleSAML_Session::getSessionFromRequest();
|
||||
$session->addAssociation($this->associationGroup, $association);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve list of SP associations.
|
||||
*
|
||||
* @return array List of SP associations.
|
||||
*/
|
||||
public function getAssociations()
|
||||
{
|
||||
$session = SimpleSAML_Session::getSessionFromRequest();
|
||||
return $session->getAssociations($this->associationGroup);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove an SP association.
|
||||
*
|
||||
* @param string $assocId The association id.
|
||||
*/
|
||||
public function terminateAssociation($assocId)
|
||||
{
|
||||
assert(is_string($assocId));
|
||||
|
||||
$session = SimpleSAML_Session::getSessionFromRequest();
|
||||
$session->terminateAssociation($this->associationGroup, $assocId);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the current user authenticated?
|
||||
*
|
||||
* @return boolean True if the user is authenticated, false otherwise.
|
||||
*/
|
||||
public function isAuthenticated()
|
||||
{
|
||||
return $this->authSource->isAuthenticated();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called after authproc has run.
|
||||
*
|
||||
* @param array $state The authentication request state array.
|
||||
*/
|
||||
public static function postAuthProc(array $state)
|
||||
{
|
||||
assert(is_callable($state['Responder']));
|
||||
|
||||
if (isset($state['core:SP'])) {
|
||||
$session = SimpleSAML_Session::getSessionFromRequest();
|
||||
$session->setData(
|
||||
'core:idp-ssotime',
|
||||
$state['core:IdP'].';'.$state['core:SP'],
|
||||
time(),
|
||||
SimpleSAML_Session::DATA_TIMEOUT_SESSION_END
|
||||
);
|
||||
}
|
||||
|
||||
call_user_func($state['Responder'], $state);
|
||||
assert(false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The user is authenticated.
|
||||
*
|
||||
* @param array $state The authentication request state array.
|
||||
*
|
||||
* @throws SimpleSAML_Error_Exception If we are not authenticated.
|
||||
*/
|
||||
public static function postAuth(array $state)
|
||||
{
|
||||
$idp = SimpleSAML_IdP::getByState($state);
|
||||
|
||||
if (!$idp->isAuthenticated()) {
|
||||
throw new SimpleSAML_Error_Exception('Not authenticated.');
|
||||
}
|
||||
|
||||
$state['Attributes'] = $idp->authSource->getAttributes();
|
||||
|
||||
if (isset($state['SPMetadata'])) {
|
||||
$spMetadata = $state['SPMetadata'];
|
||||
} else {
|
||||
$spMetadata = array();
|
||||
}
|
||||
|
||||
if (isset($state['core:SP'])) {
|
||||
$session = SimpleSAML_Session::getSessionFromRequest();
|
||||
$previousSSOTime = $session->getData('core:idp-ssotime', $state['core:IdP'].';'.$state['core:SP']);
|
||||
if ($previousSSOTime !== null) {
|
||||
$state['PreviousSSOTimestamp'] = $previousSSOTime;
|
||||
}
|
||||
}
|
||||
|
||||
$idpMetadata = $idp->getConfig()->toArray();
|
||||
|
||||
$pc = new SimpleSAML_Auth_ProcessingChain($idpMetadata, $spMetadata, 'idp');
|
||||
|
||||
$state['ReturnCall'] = array('SimpleSAML_IdP', 'postAuthProc');
|
||||
$state['Destination'] = $spMetadata;
|
||||
$state['Source'] = $idpMetadata;
|
||||
|
||||
$pc->processState($state);
|
||||
|
||||
self::postAuthProc($state);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Authenticate the user.
|
||||
*
|
||||
* This function authenticates the user.
|
||||
*
|
||||
* @param array &$state The authentication request state.
|
||||
*
|
||||
* @throws \SimpleSAML\Module\saml\Error\NoPassive If we were asked to do passive authentication.
|
||||
*/
|
||||
private function authenticate(array &$state)
|
||||
{
|
||||
if (isset($state['isPassive']) && (bool) $state['isPassive']) {
|
||||
throw new \SimpleSAML\Module\saml\Error\NoPassive('Passive authentication not supported.');
|
||||
}
|
||||
|
||||
$this->authSource->login($state);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Re-authenticate the user.
|
||||
*
|
||||
* This function re-authenticates an user with an existing session. This gives the authentication source a chance
|
||||
* to do additional work when re-authenticating for SSO.
|
||||
*
|
||||
* Note: This function is not used when ForceAuthn=true.
|
||||
*
|
||||
* @param array &$state The authentication request state.
|
||||
*
|
||||
* @throws SimpleSAML_Error_Exception If there is no auth source defined for this IdP.
|
||||
*/
|
||||
private function reauthenticate(array &$state)
|
||||
{
|
||||
$sourceImpl = $this->authSource->getAuthSource();
|
||||
if ($sourceImpl === null) {
|
||||
throw new SimpleSAML_Error_Exception('No such auth source defined.');
|
||||
}
|
||||
|
||||
$sourceImpl->reauthenticate($state);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process authentication requests.
|
||||
*
|
||||
* @param array &$state The authentication request state.
|
||||
*/
|
||||
public function handleAuthenticationRequest(array &$state)
|
||||
{
|
||||
assert(isset($state['Responder']));
|
||||
|
||||
$state['core:IdP'] = $this->id;
|
||||
|
||||
if (isset($state['SPMetadata']['entityid'])) {
|
||||
$spEntityId = $state['SPMetadata']['entityid'];
|
||||
} elseif (isset($state['SPMetadata']['entityID'])) {
|
||||
$spEntityId = $state['SPMetadata']['entityID'];
|
||||
} else {
|
||||
$spEntityId = null;
|
||||
}
|
||||
$state['core:SP'] = $spEntityId;
|
||||
|
||||
// first, check whether we need to authenticate the user
|
||||
if (isset($state['ForceAuthn']) && (bool) $state['ForceAuthn']) {
|
||||
// force authentication is in effect
|
||||
$needAuth = true;
|
||||
} else {
|
||||
$needAuth = !$this->isAuthenticated();
|
||||
}
|
||||
|
||||
$state['IdPMetadata'] = $this->getConfig()->toArray();
|
||||
$state['ReturnCallback'] = array('SimpleSAML_IdP', 'postAuth');
|
||||
|
||||
try {
|
||||
if ($needAuth) {
|
||||
$this->authenticate($state);
|
||||
assert(false);
|
||||
} else {
|
||||
$this->reauthenticate($state);
|
||||
}
|
||||
$this->postAuth($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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find the logout handler of this IdP.
|
||||
*
|
||||
* @return \SimpleSAML\IdP\LogoutHandlerInterface The logout handler class.
|
||||
*
|
||||
* @throws SimpleSAML_Error_Exception If we cannot find a logout handler.
|
||||
*/
|
||||
public function getLogoutHandler()
|
||||
{
|
||||
// find the logout handler
|
||||
$logouttype = $this->getConfig()->getString('logouttype', 'traditional');
|
||||
switch ($logouttype) {
|
||||
case 'traditional':
|
||||
$handler = 'SimpleSAML\IdP\TraditionalLogoutHandler';
|
||||
break;
|
||||
case 'iframe':
|
||||
$handler = 'SimpleSAML\IdP\IFrameLogoutHandler';
|
||||
break;
|
||||
default:
|
||||
throw new SimpleSAML_Error_Exception('Unknown logout handler: '.var_export($logouttype, true));
|
||||
}
|
||||
|
||||
return new $handler($this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finish the logout operation.
|
||||
*
|
||||
* This function will never return.
|
||||
*
|
||||
* @param array &$state The logout request state.
|
||||
*/
|
||||
public function finishLogout(array &$state)
|
||||
{
|
||||
assert(isset($state['Responder']));
|
||||
|
||||
$idp = SimpleSAML_IdP::getByState($state);
|
||||
call_user_func($state['Responder'], $idp, $state);
|
||||
assert(false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process a logout request.
|
||||
*
|
||||
* This function will never return.
|
||||
*
|
||||
* @param array &$state The logout request state.
|
||||
* @param string|null $assocId The association we received the logout request from, or null if there was no
|
||||
* association.
|
||||
*/
|
||||
public function handleLogoutRequest(array &$state, $assocId)
|
||||
{
|
||||
assert(isset($state['Responder']));
|
||||
assert(is_string($assocId) || $assocId === null);
|
||||
|
||||
$state['core:IdP'] = $this->id;
|
||||
$state['core:TerminatedAssocId'] = $assocId;
|
||||
|
||||
if ($assocId !== null) {
|
||||
$this->terminateAssociation($assocId);
|
||||
$session = SimpleSAML_Session::getSessionFromRequest();
|
||||
$session->deleteData('core:idp-ssotime', $this->id.':'.$state['saml:SPEntityId']);
|
||||
}
|
||||
|
||||
// terminate the local session
|
||||
$id = SimpleSAML_Auth_State::saveState($state, 'core:Logout:afterbridge');
|
||||
$returnTo = SimpleSAML\Module::getModuleURL('core/idp/resumelogout.php', array('id' => $id));
|
||||
|
||||
$this->authSource->logout($returnTo);
|
||||
|
||||
$handler = $this->getLogoutHandler();
|
||||
$handler->startLogout($state, $assocId);
|
||||
assert(false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process a logout response.
|
||||
*
|
||||
* This function will never return.
|
||||
*
|
||||
* @param string $assocId The association that is terminated.
|
||||
* @param string|null $relayState The RelayState from the start of the logout.
|
||||
* @param SimpleSAML_Error_Exception|null $error The error that occurred during session termination (if any).
|
||||
*/
|
||||
public function handleLogoutResponse($assocId, $relayState, SimpleSAML_Error_Exception $error = null)
|
||||
{
|
||||
assert(is_string($assocId));
|
||||
assert(is_string($relayState) || $relayState === null);
|
||||
|
||||
$session = SimpleSAML_Session::getSessionFromRequest();
|
||||
$session->deleteData('core:idp-ssotime', $this->id.';'.substr($assocId, strpos($assocId, ':') + 1));
|
||||
|
||||
$handler = $this->getLogoutHandler();
|
||||
$handler->onResponse($assocId, $relayState, $error);
|
||||
|
||||
assert(false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log out, then redirect to a URL.
|
||||
*
|
||||
* This function never returns.
|
||||
*
|
||||
* @param string $url The URL the user should be returned to after logout.
|
||||
*/
|
||||
public function doLogoutRedirect($url)
|
||||
{
|
||||
assert(is_string($url));
|
||||
|
||||
$state = array(
|
||||
'Responder' => array('SimpleSAML_IdP', 'finishLogoutRedirect'),
|
||||
'core:Logout:URL' => $url,
|
||||
);
|
||||
|
||||
$this->handleLogoutRequest($state, null);
|
||||
assert(false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Redirect to a URL after logout.
|
||||
*
|
||||
* This function never returns.
|
||||
*
|
||||
* @param SimpleSAML_IdP $idp Deprecated. Will be removed.
|
||||
* @param array &$state The logout state from doLogoutRedirect().
|
||||
*/
|
||||
public static function finishLogoutRedirect(SimpleSAML_IdP $idp, array $state)
|
||||
{
|
||||
assert(isset($state['core:Logout:URL']));
|
||||
|
||||
\SimpleSAML\Utils\HTTP::redirectTrustedURL($state['core:Logout:URL']);
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
119
lib/SimpleSAML/IdP/IFrameLogoutHandler.php
Executable file
119
lib/SimpleSAML/IdP/IFrameLogoutHandler.php
Executable file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleSAML\IdP;
|
||||
|
||||
use SimpleSAML\Module;
|
||||
use SimpleSAML\Utils\HTTP;
|
||||
|
||||
/**
|
||||
* Class that handles iframe logout.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class IFrameLogoutHandler implements LogoutHandlerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* The IdP we are logging out from.
|
||||
*
|
||||
* @var \SimpleSAML_IdP
|
||||
*/
|
||||
private $idp;
|
||||
|
||||
|
||||
/**
|
||||
* LogoutIFrame constructor.
|
||||
*
|
||||
* @param \SimpleSAML_IdP $idp The IdP to log out from.
|
||||
*/
|
||||
public function __construct(\SimpleSAML_IdP $idp)
|
||||
{
|
||||
$this->idp = $idp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the logout operation.
|
||||
*
|
||||
* @param array &$state The logout state.
|
||||
* @param string|null $assocId The SP we are logging out from.
|
||||
*/
|
||||
public function startLogout(array &$state, $assocId)
|
||||
{
|
||||
assert(is_string($assocId) || $assocId === null);
|
||||
|
||||
$associations = $this->idp->getAssociations();
|
||||
|
||||
if (count($associations) === 0) {
|
||||
$this->idp->finishLogout($state);
|
||||
}
|
||||
|
||||
foreach ($associations as $id => &$association) {
|
||||
$idp = \SimpleSAML_IdP::getByState($association);
|
||||
$association['core:Logout-IFrame:Name'] = $idp->getSPName($id);
|
||||
$association['core:Logout-IFrame:State'] = 'onhold';
|
||||
}
|
||||
$state['core:Logout-IFrame:Associations'] = $associations;
|
||||
|
||||
if (!is_null($assocId)) {
|
||||
$spName = $this->idp->getSPName($assocId);
|
||||
if ($spName === null) {
|
||||
$spName = array('en' => $assocId);
|
||||
}
|
||||
|
||||
$state['core:Logout-IFrame:From'] = $spName;
|
||||
} else {
|
||||
$state['core:Logout-IFrame:From'] = null;
|
||||
}
|
||||
|
||||
$params = array(
|
||||
'id' => \SimpleSAML_Auth_State::saveState($state, 'core:Logout-IFrame'),
|
||||
);
|
||||
if (isset($state['core:Logout-IFrame:InitType'])) {
|
||||
$params['type'] = $state['core:Logout-IFrame:InitType'];
|
||||
}
|
||||
|
||||
$url = Module::getModuleURL('core/idp/logout-iframe.php', $params);
|
||||
HTTP::redirectTrustedURL($url);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Continue the logout operation.
|
||||
*
|
||||
* This function will never return.
|
||||
*
|
||||
* @param string $assocId The association that is terminated.
|
||||
* @param string|null $relayState The RelayState from the start of the logout.
|
||||
* @param \SimpleSAML_Error_Exception|null $error The error that occurred during session termination (if any).
|
||||
*/
|
||||
public function onResponse($assocId, $relayState, \SimpleSAML_Error_Exception $error = null)
|
||||
{
|
||||
assert(is_string($assocId));
|
||||
|
||||
$spId = sha1($assocId);
|
||||
$this->idp->terminateAssociation($assocId);
|
||||
|
||||
$header = <<<HEADER
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Logout response from %s</title>
|
||||
<script>
|
||||
HEADER;
|
||||
printf($header, htmlspecialchars(var_export($assocId, true)));
|
||||
if ($error) {
|
||||
$errorMsg = $error->getMessage();
|
||||
echo('window.parent.logoutFailed("'.$spId.'", "'.addslashes($errorMsg).'");');
|
||||
} else {
|
||||
echo('window.parent.logoutCompleted("'.$spId.'");');
|
||||
}
|
||||
echo <<<FOOTER
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
FOOTER;
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
43
lib/SimpleSAML/IdP/LogoutHandlerInterface.php
Executable file
43
lib/SimpleSAML/IdP/LogoutHandlerInterface.php
Executable file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleSAML\IdP;
|
||||
|
||||
/**
|
||||
* Interface that all logout handlers must implement.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
interface LogoutHandlerInterface
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Initialize this logout handler.
|
||||
*
|
||||
* @param \SimpleSAML_IdP $idp The IdP we are logging out from.
|
||||
*/
|
||||
public function __construct(\SimpleSAML_IdP $idp);
|
||||
|
||||
|
||||
/**
|
||||
* Start a logout operation.
|
||||
*
|
||||
* This function must never return.
|
||||
*
|
||||
* @param array &$state The logout state.
|
||||
* @param string|null $assocId The association that started the logout.
|
||||
*/
|
||||
public function startLogout(array &$state, $assocId);
|
||||
|
||||
|
||||
/**
|
||||
* Handles responses to our logout requests.
|
||||
*
|
||||
* This function will never return.
|
||||
*
|
||||
* @param string $assocId The association that is terminated.
|
||||
* @param string|null $relayState The RelayState from the start of the logout.
|
||||
* @param \SimpleSAML_Error_Exception|null $error The error that occurred during session termination (if any).
|
||||
*/
|
||||
public function onResponse($assocId, $relayState, \SimpleSAML_Error_Exception $error = null);
|
||||
}
|
||||
119
lib/SimpleSAML/IdP/TraditionalLogoutHandler.php
Executable file
119
lib/SimpleSAML/IdP/TraditionalLogoutHandler.php
Executable file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleSAML\IdP;
|
||||
|
||||
use SimpleSAML\Logger;
|
||||
use SimpleSAML\Utils\HTTP;
|
||||
|
||||
/**
|
||||
* Class that handles traditional logout.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class TraditionalLogoutHandler implements LogoutHandlerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* The IdP we are logging out from.
|
||||
*
|
||||
* @var \SimpleSAML_IdP
|
||||
*/
|
||||
private $idp;
|
||||
|
||||
|
||||
/**
|
||||
* TraditionalLogout constructor.
|
||||
*
|
||||
* @param \SimpleSAML_IdP $idp The IdP to log out from.
|
||||
*/
|
||||
public function __construct(\SimpleSAML_IdP $idp)
|
||||
{
|
||||
$this->idp = $idp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Picks the next SP and issues a logout request.
|
||||
*
|
||||
* This function never returns.
|
||||
*
|
||||
* @param array &$state The logout state.
|
||||
*/
|
||||
private function logoutNextSP(array &$state)
|
||||
{
|
||||
$association = array_pop($state['core:LogoutTraditional:Remaining']);
|
||||
if ($association === null) {
|
||||
$this->idp->finishLogout($state);
|
||||
}
|
||||
|
||||
$relayState = \SimpleSAML_Auth_State::saveState($state, 'core:LogoutTraditional', true);
|
||||
|
||||
$id = $association['id'];
|
||||
Logger::info('Logging out of '.var_export($id, true).'.');
|
||||
|
||||
try {
|
||||
$idp = \SimpleSAML_IdP::getByState($association);
|
||||
$url = call_user_func(array($association['Handler'], 'getLogoutURL'), $idp, $association, $relayState);
|
||||
HTTP::redirectTrustedURL($url);
|
||||
} catch (\Exception $e) {
|
||||
Logger::warning('Unable to initialize logout to '.var_export($id, true).'.');
|
||||
$this->idp->terminateAssociation($id);
|
||||
$state['core:Failed'] = true;
|
||||
|
||||
// Try the next SP
|
||||
$this->logoutNextSP($state);
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Start the logout operation.
|
||||
*
|
||||
* This function never returns.
|
||||
*
|
||||
* @param array &$state The logout state.
|
||||
* @param string $assocId The association that started the logout.
|
||||
*/
|
||||
public function startLogout(array &$state, $assocId)
|
||||
{
|
||||
$state['core:LogoutTraditional:Remaining'] = $this->idp->getAssociations();
|
||||
|
||||
$this->logoutNextSP($state);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Continue the logout operation.
|
||||
*
|
||||
* This function will never return.
|
||||
*
|
||||
* @param string $assocId The association that is terminated.
|
||||
* @param string|null $relayState The RelayState from the start of the logout.
|
||||
* @param \SimpleSAML_Error_Exception|null $error The error that occurred during session termination (if any).
|
||||
*
|
||||
* @throws \SimpleSAML_Error_Exception If the RelayState was lost during logout.
|
||||
*/
|
||||
public function onResponse($assocId, $relayState, \SimpleSAML_Error_Exception $error = null)
|
||||
{
|
||||
assert(is_string($assocId));
|
||||
assert(is_string($relayState) || $relayState === null);
|
||||
|
||||
if ($relayState === null) {
|
||||
throw new \SimpleSAML_Error_Exception('RelayState lost during logout.');
|
||||
}
|
||||
|
||||
$state = \SimpleSAML_Auth_State::loadState($relayState, 'core:LogoutTraditional');
|
||||
|
||||
if ($error === null) {
|
||||
Logger::info('Logged out of '.var_export($assocId, true).'.');
|
||||
$this->idp->terminateAssociation($assocId);
|
||||
} else {
|
||||
Logger::warning('Error received from '.var_export($assocId, true).' during logout:');
|
||||
$error->logWarning();
|
||||
$state['core:Failed'] = true;
|
||||
}
|
||||
|
||||
$this->logoutNextSP($state);
|
||||
}
|
||||
}
|
||||
424
lib/SimpleSAML/Locale/Language.php
Executable file
424
lib/SimpleSAML/Locale/Language.php
Executable file
@@ -0,0 +1,424 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Choosing the language to localize to for our minimalistic XHTML PHP based template system.
|
||||
*
|
||||
* @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
|
||||
* @author Hanne Moa, UNINETT AS. <hanne.moa@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
namespace SimpleSAML\Locale;
|
||||
|
||||
use SimpleSAML\Utils\HTTP;
|
||||
|
||||
class Language
|
||||
{
|
||||
|
||||
/**
|
||||
* This is the default language map. It is used to map languages codes from the user agent to other language codes.
|
||||
*/
|
||||
private static $defaultLanguageMap = array('nb' => 'no');
|
||||
|
||||
/**
|
||||
* The configuration to use.
|
||||
*
|
||||
* @var \SimpleSAML_Configuration
|
||||
*/
|
||||
private $configuration;
|
||||
|
||||
/**
|
||||
* An array holding a list of languages available.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $availableLanguages;
|
||||
|
||||
/**
|
||||
* The language currently in use.
|
||||
*
|
||||
* @var null|string
|
||||
*/
|
||||
private $language = null;
|
||||
|
||||
/**
|
||||
* The language to use by default.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $defaultLanguage;
|
||||
|
||||
/**
|
||||
* An array holding a list of languages that are written from right to left.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $rtlLanguages;
|
||||
|
||||
/**
|
||||
* HTTP GET language parameter name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $languageParameterName;
|
||||
|
||||
/**
|
||||
* A custom function to use in order to determine the language in use.
|
||||
*
|
||||
* @var callable|null
|
||||
*/
|
||||
private $customFunction;
|
||||
|
||||
/**
|
||||
* A list of languages supported with their names localized.
|
||||
* Indexed by something that mostly resembles ISO 639-1 code,
|
||||
* with some charming SimpleSAML-specific variants...
|
||||
* that must remain before 2.0 due to backwards compatibility
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $language_names = array(
|
||||
'no' => 'Bokmål', // Norwegian Bokmål
|
||||
'nn' => 'Nynorsk', // Norwegian Nynorsk
|
||||
'se' => 'Sámegiella', // Northern Sami
|
||||
'sma' => 'Åarjelh-saemien giele', // Southern Sami
|
||||
'da' => 'Dansk', // Danish
|
||||
'en' => 'English',
|
||||
'de' => 'Deutsch', // German
|
||||
'sv' => 'Svenska', // Swedish
|
||||
'fi' => 'Suomeksi', // Finnish
|
||||
'es' => 'Español', // Spanish
|
||||
'ca' => 'Català', // Catalan
|
||||
'fr' => 'Français', // French
|
||||
'it' => 'Italiano', // Italian
|
||||
'nl' => 'Nederlands', // Dutch
|
||||
'lb' => 'Lëtzebuergesch', // Luxembourgish
|
||||
'cs' => 'Čeština', // Czech
|
||||
'sl' => 'Slovenščina', // Slovensk
|
||||
'lt' => 'Lietuvių kalba', // Lithuanian
|
||||
'hr' => 'Hrvatski', // Croatian
|
||||
'hu' => 'Magyar', // Hungarian
|
||||
'pl' => 'Język polski', // Polish
|
||||
'pt' => 'Português', // Portuguese
|
||||
'pt-br' => 'Português brasileiro', // Portuguese
|
||||
'ru' => 'русский язык', // Russian
|
||||
'et' => 'eesti keel', // Estonian
|
||||
'tr' => 'Türkçe', // Turkish
|
||||
'el' => 'ελληνικά', // Greek
|
||||
'ja' => '日本語', // Japanese
|
||||
'zh' => '简体中文', // Chinese (simplified)
|
||||
'zh-tw' => '繁體中文', // Chinese (traditional)
|
||||
'ar' => 'العربية', // Arabic
|
||||
'fa' => 'پارسی', // Persian
|
||||
'ur' => 'اردو', // Urdu
|
||||
'he' => 'עִבְרִית', // Hebrew
|
||||
'id' => 'Bahasa Indonesia', // Indonesian
|
||||
'sr' => 'Srpski', // Serbian
|
||||
'lv' => 'Latviešu', // Latvian
|
||||
'ro' => 'Românește', // Romanian
|
||||
'eu' => 'Euskara', // Basque
|
||||
'af' => 'Afrikaans', // Afrikaans
|
||||
);
|
||||
|
||||
/**
|
||||
* A mapping of SSP languages to locales
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $languagePosixMapping = array(
|
||||
'no' => 'nb_NO',
|
||||
'nn' => 'nn_NO',
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \SimpleSAML_Configuration $configuration Configuration object
|
||||
*/
|
||||
public function __construct(\SimpleSAML_Configuration $configuration)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
$this->availableLanguages = $this->getInstalledLanguages();
|
||||
$this->defaultLanguage = $this->configuration->getString('language.default', 'en');
|
||||
$this->languageParameterName = $this->configuration->getString('language.parameter.name', 'language');
|
||||
$this->customFunction = $this->configuration->getArray('language.get_language_function', null);
|
||||
$this->rtlLanguages = $this->configuration->getArray('language.rtl', array());
|
||||
if (isset($_GET[$this->languageParameterName])) {
|
||||
$this->setLanguage(
|
||||
$_GET[$this->languageParameterName],
|
||||
$this->configuration->getBoolean('language.parameter.setcookie', true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filter configured (available) languages against installed languages.
|
||||
*
|
||||
* @return array The set of languages both in 'language.available' and $this->language_names.
|
||||
*/
|
||||
private function getInstalledLanguages()
|
||||
{
|
||||
$configuredAvailableLanguages = $this->configuration->getArray('language.available', array('en'));
|
||||
$availableLanguages = array();
|
||||
foreach ($configuredAvailableLanguages as $code) {
|
||||
if (array_key_exists($code, $this->language_names) && isset($this->language_names[$code])) {
|
||||
$availableLanguages[] = $code;
|
||||
} else {
|
||||
\SimpleSAML\Logger::error("Language \"$code\" not installed. Check config.");
|
||||
}
|
||||
}
|
||||
return $availableLanguages;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Rename to non-idiosyncratic language code.
|
||||
*
|
||||
* @param string $language Language code for the language to rename, if necessary.
|
||||
*
|
||||
* @return string The language code.
|
||||
*/
|
||||
public function getPosixLanguage($language)
|
||||
{
|
||||
if (isset($this->languagePosixMapping[$language])) {
|
||||
return $this->languagePosixMapping[$language];
|
||||
}
|
||||
return $language;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method will set a cookie for the user's browser to remember what language was selected.
|
||||
*
|
||||
* @param string $language Language code for the language to set.
|
||||
* @param boolean $setLanguageCookie Whether to set the language cookie or not. Defaults to true.
|
||||
*/
|
||||
public function setLanguage($language, $setLanguageCookie = true)
|
||||
{
|
||||
$language = strtolower($language);
|
||||
if (in_array($language, $this->availableLanguages, true)) {
|
||||
$this->language = $language;
|
||||
if ($setLanguageCookie === true) {
|
||||
self::setLanguageCookie($language);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method will return the language selected by the user, or the default language. It looks first for a cached
|
||||
* language code, then checks for a language cookie, then it tries to calculate the preferred language from HTTP
|
||||
* headers.
|
||||
*
|
||||
* @return string The language selected by the user according to the processing rules specified, or the default
|
||||
* language in any other case.
|
||||
*/
|
||||
public function getLanguage()
|
||||
{
|
||||
// language is set in object
|
||||
if (isset($this->language)) {
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
// run custom getLanguage function if defined
|
||||
if (isset($this->customFunction) && is_callable($this->customFunction)) {
|
||||
$customLanguage = call_user_func($this->customFunction, $this);
|
||||
if ($customLanguage !== null && $customLanguage !== false) {
|
||||
return $customLanguage;
|
||||
}
|
||||
}
|
||||
|
||||
// language is provided in a stored cookie
|
||||
$languageCookie = Language::getLanguageCookie();
|
||||
if ($languageCookie !== null) {
|
||||
$this->language = $languageCookie;
|
||||
return $languageCookie;
|
||||
}
|
||||
|
||||
// check if we can find a good language from the Accept-Language HTTP header
|
||||
$httpLanguage = $this->getHTTPLanguage();
|
||||
if ($httpLanguage !== null) {
|
||||
return $httpLanguage;
|
||||
}
|
||||
|
||||
// language is not set, and we get the default language from the configuration
|
||||
return $this->getDefaultLanguage();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the localized name of a language, by ISO 639-2 code.
|
||||
*
|
||||
* @param string $code The ISO 639-2 code of the language.
|
||||
*
|
||||
* @return string The localized name of the language.
|
||||
*/
|
||||
public function getLanguageLocalizedName($code)
|
||||
{
|
||||
if (array_key_exists($code, $this->language_names) && isset($this->language_names[$code])) {
|
||||
return $this->language_names[$code];
|
||||
}
|
||||
\SimpleSAML\Logger::error("Name for language \"$code\" not found. Check config.");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the language parameter name.
|
||||
*
|
||||
* @return string The language parameter name.
|
||||
*/
|
||||
public function getLanguageParameterName()
|
||||
{
|
||||
return $this->languageParameterName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method returns the preferred language for the user based on the Accept-Language HTTP header.
|
||||
*
|
||||
* @return string The preferred language based on the Accept-Language HTTP header, or null if none of the languages
|
||||
* in the header is available.
|
||||
*/
|
||||
private function getHTTPLanguage()
|
||||
{
|
||||
$languageScore = HTTP::getAcceptLanguage();
|
||||
|
||||
// for now we only use the default language map. We may use a configurable language map in the future
|
||||
$languageMap = self::$defaultLanguageMap;
|
||||
|
||||
// find the available language with the best score
|
||||
$bestLanguage = null;
|
||||
$bestScore = -1.0;
|
||||
|
||||
foreach ($languageScore as $language => $score) {
|
||||
// apply the language map to the language code
|
||||
if (array_key_exists($language, $languageMap)) {
|
||||
$language = $languageMap[$language];
|
||||
}
|
||||
|
||||
if (!in_array($language, $this->availableLanguages, true)) {
|
||||
// skip this language - we don't have it
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Some user agents use very limited precision of the quality value, but order the elements in descending
|
||||
* order. Therefore we rely on the order of the output from getAcceptLanguage() matching the order of the
|
||||
* languages in the header when two languages have the same quality.
|
||||
*/
|
||||
if ($score > $bestScore) {
|
||||
$bestLanguage = $language;
|
||||
$bestScore = $score;
|
||||
}
|
||||
}
|
||||
|
||||
return $bestLanguage;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the default language according to configuration.
|
||||
*
|
||||
* @return string The default language that has been configured. Defaults to english if not configured.
|
||||
*/
|
||||
public function getDefaultLanguage()
|
||||
{
|
||||
return $this->defaultLanguage;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return an alias for a language code, if any.
|
||||
*
|
||||
* @return string The alias, or null if the alias was not found.
|
||||
*/
|
||||
public function getLanguageCodeAlias($langcode)
|
||||
{
|
||||
if (isset(self::$defaultLanguageMap[$langcode])) {
|
||||
return self::$defaultLanguageMap[$langcode];
|
||||
}
|
||||
// No alias found, which is fine
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return an indexed list of all languages available.
|
||||
*
|
||||
* @return array An array holding all the languages available as the keys of the array. The value for each key is
|
||||
* true in case that the language specified by that key is currently active, or false otherwise.
|
||||
*/
|
||||
public function getLanguageList()
|
||||
{
|
||||
$current = $this->getLanguage();
|
||||
$list = array_fill_keys($this->availableLanguages, false);
|
||||
$list[$current] = true;
|
||||
return $list;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check whether a language is written from the right to the left or not.
|
||||
*
|
||||
* @return boolean True if the language is right-to-left, false otherwise.
|
||||
*/
|
||||
public function isLanguageRTL()
|
||||
{
|
||||
return in_array($this->getLanguage(), $this->rtlLanguages, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the user-selected language from a cookie.
|
||||
*
|
||||
* @return string|null The selected language or null if unset.
|
||||
*/
|
||||
public static function getLanguageCookie()
|
||||
{
|
||||
$config = \SimpleSAML_Configuration::getInstance();
|
||||
$availableLanguages = $config->getArray('language.available', array('en'));
|
||||
$name = $config->getString('language.cookie.name', 'language');
|
||||
|
||||
if (isset($_COOKIE[$name])) {
|
||||
$language = strtolower((string) $_COOKIE[$name]);
|
||||
if (in_array($language, $availableLanguages, true)) {
|
||||
return $language;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method will attempt to set the user-selected language in a cookie. It will do nothing if the language
|
||||
* specified is not in the list of available languages, or the headers have already been sent to the browser.
|
||||
*
|
||||
* @param string $language The language set by the user.
|
||||
*/
|
||||
public static function setLanguageCookie($language)
|
||||
{
|
||||
assert(is_string($language));
|
||||
|
||||
$language = strtolower($language);
|
||||
$config = \SimpleSAML_Configuration::getInstance();
|
||||
$availableLanguages = $config->getArray('language.available', array('en'));
|
||||
|
||||
if (!in_array($language, $availableLanguages, true) || headers_sent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$name = $config->getString('language.cookie.name', 'language');
|
||||
$params = array(
|
||||
'lifetime' => ($config->getInteger('language.cookie.lifetime', 60 * 60 * 24 * 900)),
|
||||
'domain' => ($config->getString('language.cookie.domain', null)),
|
||||
'path' => ($config->getString('language.cookie.path', '/')),
|
||||
'secure' => ($config->getBoolean('language.cookie.secure', false)),
|
||||
'httponly' => ($config->getBoolean('language.cookie.httponly', false)),
|
||||
);
|
||||
|
||||
HTTP::setCookie($name, $language, $params, false);
|
||||
}
|
||||
}
|
||||
265
lib/SimpleSAML/Locale/Localization.php
Executable file
265
lib/SimpleSAML/Locale/Localization.php
Executable file
@@ -0,0 +1,265 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Glue to connect one or more translation/locale systems to the rest
|
||||
*
|
||||
* @author Hanne Moa, UNINETT AS. <hanne.moa@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
namespace SimpleSAML\Locale;
|
||||
|
||||
use Gettext\Translations;
|
||||
use Gettext\Translator;
|
||||
|
||||
class Localization
|
||||
{
|
||||
/**
|
||||
* The configuration to use.
|
||||
*
|
||||
* @var \SimpleSAML_Configuration
|
||||
*/
|
||||
private $configuration;
|
||||
|
||||
/**
|
||||
* The default gettext domain.
|
||||
*/
|
||||
const DEFAULT_DOMAIN = 'messages';
|
||||
|
||||
/**
|
||||
* Old internationalization backend included in SimpleSAMLphp.
|
||||
*/
|
||||
const SSP_I18N_BACKEND = 'SimpleSAMLphp';
|
||||
|
||||
/**
|
||||
* An internationalization backend implemented purely in PHP.
|
||||
*/
|
||||
const GETTEXT_I18N_BACKEND = 'gettext/gettext';
|
||||
|
||||
/**
|
||||
* The default locale directory
|
||||
*/
|
||||
private $localeDir;
|
||||
|
||||
/**
|
||||
* Where specific domains are stored
|
||||
*/
|
||||
private $localeDomainMap = array();
|
||||
|
||||
/**
|
||||
* Pointer to currently active translator
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
/**
|
||||
* Pointer to current Language
|
||||
*/
|
||||
private $language;
|
||||
|
||||
/**
|
||||
* Language code representing the current Language
|
||||
*/
|
||||
private $langcode;
|
||||
|
||||
|
||||
/**
|
||||
* The language backend to use
|
||||
*/
|
||||
public $i18nBackend;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \SimpleSAML_Configuration $configuration Configuration object
|
||||
*/
|
||||
public function __construct(\SimpleSAML_Configuration $configuration)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
$this->localeDir = $this->configuration->resolvePath('locales');
|
||||
$this->language = new Language($configuration);
|
||||
$this->langcode = $this->language->getPosixLanguage($this->language->getLanguage());
|
||||
$this->i18nBackend = $this->configuration->getString('language.i18n.backend', self::SSP_I18N_BACKEND);
|
||||
$this->setupL10N();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Dump the default locale directory
|
||||
*/
|
||||
public function getLocaleDir()
|
||||
{
|
||||
return $this->localeDir;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the default locale dir for a specific module aka. domain
|
||||
*
|
||||
* @param string $domain Name of module/domain
|
||||
*/
|
||||
public function getDomainLocaleDir($domain)
|
||||
{
|
||||
$localeDir = $this->configuration->resolvePath('modules') . '/' . $domain . '/locales';
|
||||
return $localeDir;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Add a new translation domain from a module
|
||||
* (We're assuming that each domain only exists in one place)
|
||||
*
|
||||
* @param string $module Module name
|
||||
* @param string $localeDir Absolute path if the module is housed elsewhere
|
||||
*/
|
||||
public function addModuleDomain($module, $localeDir = null)
|
||||
{
|
||||
if (!$localeDir) {
|
||||
$localeDir = $this->getDomainLocaleDir($module);
|
||||
}
|
||||
$this->addDomain($localeDir, $module);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Add a new translation domain
|
||||
* (We're assuming that each domain only exists in one place)
|
||||
*
|
||||
* @param string $localeDir Location of translations
|
||||
* @param string $domain Domain at location
|
||||
*/
|
||||
public function addDomain($localeDir, $domain)
|
||||
{
|
||||
$this->localeDomainMap[$domain] = $localeDir;
|
||||
\SimpleSAML\Logger::debug("Localization: load domain '$domain' at '$localeDir'");
|
||||
$this->loadGettextGettextFromPO($domain);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get and check path of localization file
|
||||
*
|
||||
* @param string $domain Name of localization domain
|
||||
* @throws Exception If the path does not exist even for the default, fallback language
|
||||
*/
|
||||
public function getLangPath($domain = self::DEFAULT_DOMAIN)
|
||||
{
|
||||
$langcode = explode('_', $this->langcode);
|
||||
$langcode = $langcode[0];
|
||||
$localeDir = $this->localeDomainMap[$domain];
|
||||
$langPath = $localeDir.'/'.$langcode.'/LC_MESSAGES/';
|
||||
\SimpleSAML\Logger::debug("Trying langpath for '$langcode' as '$langPath'");
|
||||
if (is_dir($langPath) && is_readable($langPath)) {
|
||||
return $langPath;
|
||||
}
|
||||
|
||||
// Some langcodes have aliases..
|
||||
$alias = $this->language->getLanguageCodeAlias($langcode);
|
||||
if (isset($alias)) {
|
||||
$langPath = $localeDir.'/'.$alias.'/LC_MESSAGES/';
|
||||
\SimpleSAML\Logger::debug("Trying langpath for alternative '$alias' as '$langPath'");
|
||||
if (is_dir($langPath) && is_readable($langPath)) {
|
||||
return $langPath;
|
||||
}
|
||||
}
|
||||
|
||||
// Language not found, fall back to default
|
||||
$defLangcode = $this->language->getDefaultLanguage();
|
||||
$langPath = $localeDir.'/'.$defLangcode.'/LC_MESSAGES/';
|
||||
if (is_dir($langPath) && is_readable($langPath)) {
|
||||
// Report that the localization for the preferred language is missing
|
||||
$error = "Localization not found for langcode '$langcode' at '$langPath', falling back to langcode '".
|
||||
$defLangcode."'";
|
||||
\SimpleSAML\Logger::error($_SERVER['PHP_SELF'].' - '.$error);
|
||||
return $langPath;
|
||||
}
|
||||
|
||||
// Locale for default language missing even, error out
|
||||
$error = "Localization directory missing/broken for langcode '$langcode' and domain '$domain'";
|
||||
\SimpleSAML\Logger::critical($_SERVER['PHP_SELF'].' - '.$error);
|
||||
throw new \Exception($error);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Setup the translator
|
||||
*/
|
||||
private function setupTranslator()
|
||||
{
|
||||
$this->translator = new Translator();
|
||||
$this->translator->register();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load translation domain from Gettext/Gettext using .po
|
||||
*
|
||||
* Note: Since Twig I18N does not support domains, all loaded files are
|
||||
* merged. Use contexts if identical strings need to be disambiguated.
|
||||
*
|
||||
* @param string $domain Name of domain
|
||||
* @param boolean $catchException Whether to catch an exception on error or return early
|
||||
*
|
||||
* @throws \Exception If something is wrong with the locale file for the domain and activated language
|
||||
*/
|
||||
private function loadGettextGettextFromPO($domain = self::DEFAULT_DOMAIN, $catchException = true)
|
||||
{
|
||||
try {
|
||||
$langPath = $this->getLangPath($domain);
|
||||
} catch (\Exception $e) {
|
||||
$error = "Something went wrong when trying to get path to language file, cannot load domain '$domain'.";
|
||||
\SimpleSAML\Logger::error($_SERVER['PHP_SELF'].' - '.$error);
|
||||
if ($catchException) {
|
||||
// bail out!
|
||||
return;
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
$poFile = $domain.'.po';
|
||||
$poPath = $langPath.$poFile;
|
||||
if (file_exists($poPath) && is_readable($poPath)) {
|
||||
$translations = Translations::fromPoFile($poPath);
|
||||
$this->translator->loadTranslations($translations);
|
||||
} else {
|
||||
$error = "Localization file '$poFile' not found in '$langPath', falling back to default";
|
||||
\SimpleSAML\Logger::error($_SERVER['PHP_SELF'].' - '.$error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test to check if backend is set to default
|
||||
*
|
||||
* (if false: backend unset/there's an error)
|
||||
*/
|
||||
public function isI18NBackendDefault()
|
||||
{
|
||||
if ($this->i18nBackend === $this::SSP_I18N_BACKEND) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set up L18N if configured or fallback to old system
|
||||
*/
|
||||
private function setupL10N()
|
||||
{
|
||||
if ($this->i18nBackend === self::SSP_I18N_BACKEND) {
|
||||
\SimpleSAML\Logger::debug("Localization: using old system");
|
||||
return;
|
||||
}
|
||||
|
||||
$this->setupTranslator();
|
||||
// setup default domain
|
||||
$this->addDomain($this->localeDir, self::DEFAULT_DOMAIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show which domains are registered
|
||||
*/
|
||||
public function getRegisteredDomains()
|
||||
{
|
||||
return $this->localeDomainMap;
|
||||
}
|
||||
}
|
||||
546
lib/SimpleSAML/Locale/Translate.php
Executable file
546
lib/SimpleSAML/Locale/Translate.php
Executable file
@@ -0,0 +1,546 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* The translation-relevant bits from our original minimalistic XHTML PHP based template system.
|
||||
*
|
||||
* @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
|
||||
* @author Hanne Moa, UNINETT AS. <hanne.moa@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
namespace SimpleSAML\Locale;
|
||||
|
||||
class Translate
|
||||
{
|
||||
|
||||
/**
|
||||
* The configuration to be used for this translator.
|
||||
*
|
||||
* @var \SimpleSAML_Configuration
|
||||
*/
|
||||
private $configuration;
|
||||
|
||||
private $langtext = array();
|
||||
|
||||
/**
|
||||
* Associative array of dictionaries.
|
||||
*/
|
||||
private $dictionaries = array();
|
||||
|
||||
/**
|
||||
* The default dictionary.
|
||||
*/
|
||||
private $defaultDictionary = null;
|
||||
|
||||
/**
|
||||
* The language object we'll use internally.
|
||||
*
|
||||
* @var \SimpleSAML\Locale\Language
|
||||
*/
|
||||
private $language;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \SimpleSAML_Configuration $configuration Configuration object
|
||||
* @param string|null $defaultDictionary The default dictionary where tags will come from.
|
||||
*/
|
||||
public function __construct(\SimpleSAML_Configuration $configuration, $defaultDictionary = null)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
$this->language = new Language($configuration);
|
||||
|
||||
if ($defaultDictionary !== null && substr($defaultDictionary, -4) === '.php') {
|
||||
// TODO: drop this entire if clause for 2.0
|
||||
// for backwards compatibility - print warning
|
||||
$backtrace = debug_backtrace();
|
||||
$where = $backtrace[0]['file'].':'.$backtrace[0]['line'];
|
||||
\SimpleSAML\Logger::warning(
|
||||
'Deprecated use of new SimpleSAML\Locale\Translate(...) at '.$where.
|
||||
'. The last parameter is now a dictionary name, which should not end in ".php".'
|
||||
);
|
||||
|
||||
$this->defaultDictionary = substr($defaultDictionary, 0, -4);
|
||||
} else {
|
||||
$this->defaultDictionary = $defaultDictionary;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the internal language object used by this translator.
|
||||
*
|
||||
* @return \SimpleSAML\Locale\Language
|
||||
*/
|
||||
public function getLanguage()
|
||||
{
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method retrieves a dictionary with the name given.
|
||||
*
|
||||
* @param string $name The name of the dictionary, as the filename in the dictionary directory, without the
|
||||
* '.php' ending.
|
||||
*
|
||||
* @return array An associative array with the dictionary.
|
||||
*/
|
||||
private function getDictionary($name)
|
||||
{
|
||||
assert(is_string($name));
|
||||
|
||||
if (!array_key_exists($name, $this->dictionaries)) {
|
||||
$sepPos = strpos($name, ':');
|
||||
if ($sepPos !== false) {
|
||||
$module = substr($name, 0, $sepPos);
|
||||
$fileName = substr($name, $sepPos + 1);
|
||||
$dictDir = \SimpleSAML\Module::getModuleDir($module).'/dictionaries/';
|
||||
} else {
|
||||
$dictDir = $this->configuration->getPathValue('dictionarydir', 'dictionaries/');
|
||||
$fileName = $name;
|
||||
}
|
||||
|
||||
$this->dictionaries[$name] = $this->readDictionaryFile($dictDir.$fileName);
|
||||
}
|
||||
|
||||
return $this->dictionaries[$name];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method retrieves a tag as an array with language => string mappings.
|
||||
*
|
||||
* @param string $tag The tag name. The tag name can also be on the form '{<dictionary>:<tag>}', to retrieve a tag
|
||||
* from the specific dictionary.
|
||||
*
|
||||
* @return array An associative array with language => string mappings, or null if the tag wasn't found.
|
||||
*/
|
||||
public function getTag($tag)
|
||||
{
|
||||
assert(is_string($tag));
|
||||
|
||||
// first check translations loaded by the includeInlineTranslation and includeLanguageFile methods
|
||||
if (array_key_exists($tag, $this->langtext)) {
|
||||
return $this->langtext[$tag];
|
||||
}
|
||||
|
||||
// check whether we should use the default dictionary or a dictionary specified in the tag
|
||||
if (substr($tag, 0, 1) === '{' && preg_match('/^{((?:\w+:)?\w+?):(.*)}$/D', $tag, $matches)) {
|
||||
$dictionary = $matches[1];
|
||||
$tag = $matches[2];
|
||||
} else {
|
||||
$dictionary = $this->defaultDictionary;
|
||||
if ($dictionary === null) {
|
||||
// we don't have any dictionary to load the tag from
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
$dictionary = $this->getDictionary($dictionary);
|
||||
if (!array_key_exists($tag, $dictionary)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $dictionary[$tag];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the preferred translation of a given text.
|
||||
*
|
||||
* @param array $translations The translations, as an associative array with language => text mappings.
|
||||
*
|
||||
* @return string The preferred translation.
|
||||
*
|
||||
* @throws \Exception If there's no suitable translation.
|
||||
*/
|
||||
public function getPreferredTranslation($translations)
|
||||
{
|
||||
assert(is_array($translations));
|
||||
|
||||
// look up translation of tag in the selected language
|
||||
$selected_language = $this->language->getLanguage();
|
||||
if (array_key_exists($selected_language, $translations)) {
|
||||
return $translations[$selected_language];
|
||||
}
|
||||
|
||||
// look up translation of tag in the default language
|
||||
$default_language = $this->language->getDefaultLanguage();
|
||||
if (array_key_exists($default_language, $translations)) {
|
||||
return $translations[$default_language];
|
||||
}
|
||||
|
||||
// check for english translation
|
||||
if (array_key_exists('en', $translations)) {
|
||||
return $translations['en'];
|
||||
}
|
||||
|
||||
// pick the first translation available
|
||||
if (count($translations) > 0) {
|
||||
$languages = array_keys($translations);
|
||||
return $translations[$languages[0]];
|
||||
}
|
||||
|
||||
// we don't have anything to return
|
||||
throw new \Exception('Nothing to return from translation.');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Translate the name of an attribute.
|
||||
*
|
||||
* @param string $name The attribute name.
|
||||
*
|
||||
* @return string The translated attribute name, or the original attribute name if no translation was found.
|
||||
*/
|
||||
public function getAttributeTranslation($name)
|
||||
{
|
||||
// normalize attribute name
|
||||
$normName = strtolower($name);
|
||||
$normName = str_replace(":", "_", $normName);
|
||||
|
||||
// check for an extra dictionary
|
||||
$extraDict = $this->configuration->getString('attributes.extradictionary', null);
|
||||
if ($extraDict !== null) {
|
||||
$dict = $this->getDictionary($extraDict);
|
||||
if (array_key_exists($normName, $dict)) {
|
||||
return $this->getPreferredTranslation($dict[$normName]);
|
||||
}
|
||||
}
|
||||
|
||||
// search the default attribute dictionary
|
||||
$dict = $this->getDictionary('attributes');
|
||||
if (array_key_exists('attribute_'.$normName, $dict)) {
|
||||
return $this->getPreferredTranslation($dict['attribute_'.$normName]);
|
||||
}
|
||||
|
||||
// no translations found
|
||||
return $name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mark a string for translation without translating it.
|
||||
*
|
||||
* @param string $tag A tag name to mark for translation.
|
||||
*
|
||||
* @return string The tag, unchanged.
|
||||
*/
|
||||
public static function noop($tag)
|
||||
{
|
||||
return $tag;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Translate a tag into the current language, with a fallback to english.
|
||||
*
|
||||
* This function is used to look up a translation tag in dictionaries, and return the translation into the current
|
||||
* language. If no translation into the current language can be found, english will be tried, and if that fails,
|
||||
* placeholder text will be returned.
|
||||
*
|
||||
* An array can be passed as the tag. In that case, the array will be assumed to be on the form (language => text),
|
||||
* and will be used as the source of translations.
|
||||
*
|
||||
* This function can also do replacements into the translated tag. It will search the translated tag for the keys
|
||||
* provided in $replacements, and replace any found occurrences with the value of the key.
|
||||
*
|
||||
* @param string|array $tag A tag name for the translation which should be looked up, or an array with
|
||||
* (language => text) mappings. The array version will go away in 2.0
|
||||
* @param array $replacements An associative array of keys that should be replaced with values in the
|
||||
* translated string.
|
||||
* @param boolean $fallbackdefault Default translation to use as a fallback if no valid translation was found.
|
||||
* @deprecated Not used in twig, gettext
|
||||
*
|
||||
* @return string The translated tag, or a placeholder value if the tag wasn't found.
|
||||
*/
|
||||
public function t(
|
||||
$tag,
|
||||
$replacements = array(),
|
||||
// TODO: remove this for 2.0. Assume true
|
||||
$fallbackdefault = true,
|
||||
// TODO: remove this for 2.0
|
||||
$oldreplacements = array(),
|
||||
// TODO: remove this for 2.0
|
||||
$striptags = false
|
||||
) {
|
||||
$backtrace = debug_backtrace();
|
||||
$where = $backtrace[0]['file'].':'.$backtrace[0]['line'];
|
||||
if (!$fallbackdefault) {
|
||||
\SimpleSAML\Logger::warning(
|
||||
'Deprecated use of new SimpleSAML\Locale\Translate::t(...) at '.$where.
|
||||
'. This parameter will go away, the fallback will become' .
|
||||
' identical to the $tag in 2.0.'
|
||||
);
|
||||
}
|
||||
if (!is_array($replacements)) {
|
||||
// TODO: remove this entire if for 2.0
|
||||
|
||||
// old style call to t(...). Print warning to log
|
||||
\SimpleSAML\Logger::warning(
|
||||
'Deprecated use of SimpleSAML\Locale\Translate::t(...) at '.$where.
|
||||
'. Please update the code to use the new style of parameters.'
|
||||
);
|
||||
|
||||
// for backwards compatibility
|
||||
if (!$replacements && $this->getTag($tag) === null) {
|
||||
\SimpleSAML\Logger::warning(
|
||||
'Code which uses $fallbackdefault === FALSE should be updated to use the getTag() method instead.'
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
$replacements = $oldreplacements;
|
||||
}
|
||||
|
||||
if (is_array($tag)) {
|
||||
$tagData = $tag;
|
||||
\SimpleSAML\Logger::warning(
|
||||
'Deprecated use of new SimpleSAML\Locale\Translate::t(...) at '.$where.
|
||||
'. The $tag-parameter can only be a string in 2.0.'
|
||||
);
|
||||
} else {
|
||||
$tagData = $this->getTag($tag);
|
||||
if ($tagData === null) {
|
||||
// tag not found
|
||||
\SimpleSAML\Logger::info('Template: Looking up ['.$tag.']: not translated at all.');
|
||||
return $this->getStringNotTranslated($tag, $fallbackdefault);
|
||||
}
|
||||
}
|
||||
|
||||
$translated = $this->getPreferredTranslation($tagData);
|
||||
|
||||
foreach ($replacements as $k => $v) {
|
||||
// try to translate if no replacement is given
|
||||
if ($v == null) {
|
||||
$v = $this->t($k);
|
||||
}
|
||||
$translated = str_replace($k, $v, $translated);
|
||||
}
|
||||
return $translated;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the string that should be used when no translation was found.
|
||||
*
|
||||
* @param string $tag A name tag of the string that should be returned.
|
||||
* @param boolean $fallbacktag If set to true and string was not found in any languages, return the tag itself. If
|
||||
* false return null.
|
||||
*
|
||||
* @return string The string that should be used, or the tag name if $fallbacktag is set to false.
|
||||
*/
|
||||
private function getStringNotTranslated($tag, $fallbacktag)
|
||||
{
|
||||
if ($fallbacktag) {
|
||||
return 'not translated ('.$tag.')';
|
||||
} else {
|
||||
return $tag;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Include a translation inline instead of putting translations in dictionaries. This function is recommended to be
|
||||
* used ONLU from variable data, or when the translation is already provided by an external source, as a database
|
||||
* or in metadata.
|
||||
*
|
||||
* @param string $tag The tag that has a translation
|
||||
* @param array|string $translation The translation array
|
||||
*
|
||||
* @throws \Exception If $translation is neither a string nor an array.
|
||||
*/
|
||||
public function includeInlineTranslation($tag, $translation)
|
||||
{
|
||||
if (is_string($translation)) {
|
||||
$translation = array('en' => $translation);
|
||||
} elseif (!is_array($translation)) {
|
||||
throw new \Exception("Inline translation should be string or array. Is ".gettype($translation)." now!");
|
||||
}
|
||||
|
||||
\SimpleSAML\Logger::debug('Template: Adding inline language translation for tag ['.$tag.']');
|
||||
$this->langtext[$tag] = $translation;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Include a language file from the dictionaries directory.
|
||||
*
|
||||
* @param string $file File name of dictionary to include
|
||||
* @param \SimpleSAML_Configuration|null $otherConfig Optionally provide a different configuration object than the
|
||||
* one provided in the constructor to be used to find the directory of the dictionary. This allows to combine
|
||||
* dictionaries inside the SimpleSAMLphp main code distribution together with external dictionaries. Defaults to
|
||||
* null.
|
||||
*/
|
||||
public function includeLanguageFile($file, $otherConfig = null)
|
||||
{
|
||||
if (!empty($otherConfig)) {
|
||||
$filebase = $otherConfig->getPathValue('dictionarydir', 'dictionaries/');
|
||||
} else {
|
||||
$filebase = $this->configuration->getPathValue('dictionarydir', 'dictionaries/');
|
||||
}
|
||||
|
||||
$lang = $this->readDictionaryFile($filebase.$file);
|
||||
\SimpleSAML\Logger::debug('Template: Merging language array. Loading ['.$file.']');
|
||||
$this->langtext = array_merge($this->langtext, $lang);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read a dictionary file in JSON format.
|
||||
*
|
||||
* @param string $filename The absolute path to the dictionary file, minus the .definition.json ending.
|
||||
*
|
||||
* @return array An array holding all the translations in the file.
|
||||
*/
|
||||
private function readDictionaryJSON($filename)
|
||||
{
|
||||
$definitionFile = $filename.'.definition.json';
|
||||
assert(file_exists($definitionFile));
|
||||
|
||||
$fileContent = file_get_contents($definitionFile);
|
||||
$lang = json_decode($fileContent, true);
|
||||
|
||||
if (empty($lang)) {
|
||||
\SimpleSAML\Logger::error('Invalid dictionary definition file ['.$definitionFile.']');
|
||||
return array();
|
||||
}
|
||||
|
||||
$translationFile = $filename.'.translation.json';
|
||||
if (file_exists($translationFile)) {
|
||||
$fileContent = file_get_contents($translationFile);
|
||||
$moreTrans = json_decode($fileContent, true);
|
||||
if (!empty($moreTrans)) {
|
||||
$lang = array_merge_recursive($lang, $moreTrans);
|
||||
}
|
||||
}
|
||||
|
||||
return $lang;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read a dictionary file in PHP format.
|
||||
*
|
||||
* @param string $filename The absolute path to the dictionary file.
|
||||
*
|
||||
* @return array An array holding all the translations in the file.
|
||||
*/
|
||||
private function readDictionaryPHP($filename)
|
||||
{
|
||||
$phpFile = $filename.'.php';
|
||||
assert(file_exists($phpFile));
|
||||
|
||||
$lang = null;
|
||||
include($phpFile);
|
||||
if (isset($lang)) {
|
||||
return $lang;
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read a dictionary file.
|
||||
*
|
||||
* @param string $filename The absolute path to the dictionary file.
|
||||
*
|
||||
* @return array An array holding all the translations in the file.
|
||||
*/
|
||||
private function readDictionaryFile($filename)
|
||||
{
|
||||
assert(is_string($filename));
|
||||
|
||||
\SimpleSAML\Logger::debug('Template: Reading ['.$filename.']');
|
||||
|
||||
$jsonFile = $filename.'.definition.json';
|
||||
if (file_exists($jsonFile)) {
|
||||
return $this->readDictionaryJSON($filename);
|
||||
}
|
||||
|
||||
$phpFile = $filename.'.php';
|
||||
if (file_exists($phpFile)) {
|
||||
return $this->readDictionaryPHP($filename);
|
||||
}
|
||||
|
||||
\SimpleSAML\Logger::error(
|
||||
$_SERVER['PHP_SELF'].' - Template: Could not find dictionary file at ['.$filename.']'
|
||||
);
|
||||
return array();
|
||||
}
|
||||
|
||||
|
||||
public static function translateSingularGettext($original)
|
||||
{
|
||||
$text = \Gettext\BaseTranslator::$current->gettext($original);
|
||||
|
||||
if (func_num_args() === 1) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
$args = array_slice(func_get_args(), 1);
|
||||
|
||||
return strtr($text, is_array($args[0]) ? $args[0] : $args);
|
||||
}
|
||||
|
||||
|
||||
public static function translatePluralGettext($original, $plural, $value)
|
||||
{
|
||||
$text = \Gettext\BaseTranslator::$current->ngettext($original, $plural, $value);
|
||||
|
||||
if (func_num_args() === 3) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
$args = array_slice(func_get_args(), 3);
|
||||
|
||||
return strtr($text, is_array($args[0]) ? $args[0] : $args);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pick a translation from a given array of translations for the current language.
|
||||
*
|
||||
* @param array $context An array of options. The current language must be specified as an ISO 639 code accessible
|
||||
* with the key "currentLanguage" in the array.
|
||||
* @param array $translations An array of translations. Each translation has an ISO 639 code as its key, identifying
|
||||
* the language it corresponds to.
|
||||
*
|
||||
* @return null|string The translation appropriate for the current language, or null if none found. If the
|
||||
* $context or $translations arrays are null, or $context['currentLanguage'] is not defined, null is also returned.
|
||||
*/
|
||||
public static function translateFromArray($context, $translations)
|
||||
{
|
||||
if (!is_array($translations) || $translations === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!is_array($context) || !isset($context['currentLanguage'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isset($translations[$context['currentLanguage']])) {
|
||||
return $translations[$context['currentLanguage']];
|
||||
}
|
||||
|
||||
// we don't have a translation for the current language, load alternative priorities
|
||||
$sspcfg = \SimpleSAML_Configuration::getInstance();
|
||||
$langcfg = $sspcfg->getConfigItem('language', null);
|
||||
$priorities = array();
|
||||
if ($langcfg instanceof \SimpleSAML_Configuration) {
|
||||
$priorities = $langcfg->getArray('priorities', array());
|
||||
}
|
||||
|
||||
foreach ($priorities[$context['currentLanguage']] as $lang) {
|
||||
if (isset($translations[$lang])) {
|
||||
return $translations[$lang];
|
||||
}
|
||||
}
|
||||
|
||||
// nothing we can use, return null so that we can set a default
|
||||
return null;
|
||||
}
|
||||
}
|
||||
457
lib/SimpleSAML/Logger.php
Executable file
457
lib/SimpleSAML/Logger.php
Executable file
@@ -0,0 +1,457 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleSAML;
|
||||
|
||||
/**
|
||||
* The main logger class for SimpleSAMLphp.
|
||||
*
|
||||
* @author Lasse Birnbaum Jensen, SDU.
|
||||
* @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
|
||||
* @author Jaime Pérez Crespo, UNINETT AS <jaime.perez@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class Logger
|
||||
{
|
||||
|
||||
/**
|
||||
* @var \SimpleSAML\Logger\LoggingHandlerInterface|false|null
|
||||
*/
|
||||
private static $loggingHandler = null;
|
||||
|
||||
/**
|
||||
* @var integer|null
|
||||
*/
|
||||
private static $logLevel = null;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
private static $captureLog = false;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $capturedLog = array();
|
||||
|
||||
/**
|
||||
* Array with messages logged before the logging handler was initialized.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $earlyLog = array();
|
||||
|
||||
/**
|
||||
* List of log levels.
|
||||
*
|
||||
* This list is used to restore the log levels after some log levels have been disabled.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $logLevelStack = array();
|
||||
|
||||
/**
|
||||
* The current mask of log levels disabled.
|
||||
*
|
||||
* Note: this mask is not directly related to the PHP error reporting level.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private static $logMask = 0;
|
||||
|
||||
|
||||
/**
|
||||
* This constant defines the string we set the track ID to while we are fetching the track ID from the session
|
||||
* class. This is used to prevent infinite recursion.
|
||||
*/
|
||||
const NO_TRACKID = '_NOTRACKIDYET_';
|
||||
|
||||
/**
|
||||
* This variable holds the track ID we have retrieved from the session class. It can also be NULL, in which case
|
||||
* we haven't fetched the track ID yet, or self::NO_TRACKID, which means that we are fetching the track ID now.
|
||||
*/
|
||||
private static $trackid = self::NO_TRACKID;
|
||||
|
||||
/**
|
||||
* This variable holds the format used to log any message. Its use varies depending on the log handler used (for
|
||||
* instance, you cannot control here how dates are displayed when using syslog or errorlog handlers), but in
|
||||
* general the options are:
|
||||
*
|
||||
* - %date{<format>}: the date and time, with its format specified inside the brackets. See the PHP documentation
|
||||
* of the strftime() function for more information on the format. If the brackets are omitted, the standard
|
||||
* format is applied. This can be useful if you just want to control the placement of the date, but don't care
|
||||
* about the format.
|
||||
*
|
||||
* - %process: the name of the SimpleSAMLphp process. Remember you can configure this in the 'logging.processname'
|
||||
* option. The SyslogLoggingHandler will just remove this.
|
||||
*
|
||||
* - %level: the log level (name or number depending on the handler used). Please note different logging handlers
|
||||
* will print the log level differently.
|
||||
*
|
||||
* - %stat: if the log entry is intended for statistical purposes, it will print the string 'STAT ' (bear in mind
|
||||
* the trailing space).
|
||||
*
|
||||
* - %trackid: the track ID, an identifier that allows you to track a single session.
|
||||
*
|
||||
* - %srcip: the IP address of the client. If you are behind a proxy, make sure to modify the
|
||||
* $_SERVER['REMOTE_ADDR'] variable on your code accordingly to the X-Forwarded-For header.
|
||||
*
|
||||
* - %msg: the message to be logged.
|
||||
*
|
||||
* @var string The format of the log line.
|
||||
*/
|
||||
private static $format = '%date{%b %d %H:%M:%S} %process %level %stat[%trackid] %msg';
|
||||
|
||||
/**
|
||||
* This variable tells if we have a shutdown function registered or not.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private static $shutdownRegistered = false;
|
||||
|
||||
/**
|
||||
* This variable tells if we are shutting down.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private static $shuttingDown = false;
|
||||
|
||||
const EMERG = 0;
|
||||
const ALERT = 1;
|
||||
const CRIT = 2;
|
||||
const ERR = 3;
|
||||
const WARNING = 4;
|
||||
const NOTICE = 5;
|
||||
const INFO = 6;
|
||||
const DEBUG = 7;
|
||||
|
||||
|
||||
/**
|
||||
* Log an emergency message.
|
||||
*
|
||||
* @var string $string The message to log.
|
||||
*/
|
||||
public static function emergency($string)
|
||||
{
|
||||
self::log(self::EMERG, $string);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log a critical message.
|
||||
*
|
||||
* @var string $string The message to log.
|
||||
*/
|
||||
public static function critical($string)
|
||||
{
|
||||
self::log(self::CRIT, $string);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log an alert.
|
||||
*
|
||||
* @var string $string The message to log.
|
||||
*/
|
||||
public static function alert($string)
|
||||
{
|
||||
self::log(self::ALERT, $string);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log an error.
|
||||
*
|
||||
* @var string $string The message to log.
|
||||
*/
|
||||
public static function error($string)
|
||||
{
|
||||
self::log(self::ERR, $string);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log a warning.
|
||||
*
|
||||
* @var string $string The message to log.
|
||||
*/
|
||||
public static function warning($string)
|
||||
{
|
||||
self::log(self::WARNING, $string);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* We reserve the notice level for statistics, so do not use this level for other kind of log messages.
|
||||
*
|
||||
* @var string $string The message to log.
|
||||
*/
|
||||
public static function notice($string)
|
||||
{
|
||||
self::log(self::NOTICE, $string);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Info messages are a bit less verbose than debug messages. This is useful to trace a session.
|
||||
*
|
||||
* @var string $string The message to log.
|
||||
*/
|
||||
public static function info($string)
|
||||
{
|
||||
self::log(self::INFO, $string);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Debug messages are very verbose, and will contain more information than what is necessary for a production
|
||||
* system.
|
||||
*
|
||||
* @var string $string The message to log.
|
||||
*/
|
||||
public static function debug($string)
|
||||
{
|
||||
self::log(self::DEBUG, $string);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Statistics.
|
||||
*
|
||||
* @var string $string The message to log.
|
||||
*/
|
||||
public static function stats($string)
|
||||
{
|
||||
self::log(self::NOTICE, $string, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the logger to capture logs.
|
||||
*
|
||||
* @var boolean $val Whether to capture logs or not. Defaults to TRUE.
|
||||
*/
|
||||
public static function setCaptureLog($val = true)
|
||||
{
|
||||
self::$captureLog = $val;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the captured log.
|
||||
*/
|
||||
public static function getCapturedLog()
|
||||
{
|
||||
return self::$capturedLog;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the track identifier to use in all logs.
|
||||
*
|
||||
* @param $trackId string The track identifier to use during this session.
|
||||
*/
|
||||
public static function setTrackId($trackId)
|
||||
{
|
||||
self::$trackid = $trackId;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Flush any pending log messages to the logging handler.
|
||||
*
|
||||
* This method is intended to be registered as a shutdown handler, so that any pending messages that weren't sent
|
||||
* to the logging handler at that point, can still make it. It is therefore not intended to be called manually.
|
||||
*
|
||||
*/
|
||||
public static function flush()
|
||||
{
|
||||
try {
|
||||
$s = \SimpleSAML_Session::getSessionFromRequest();
|
||||
} catch (\Exception $e) {
|
||||
// loading session failed. We don't care why, at this point we have a transient session, so we use that
|
||||
self::error('Cannot load or create session: '.$e->getMessage());
|
||||
$s = \SimpleSAML_Session::getSessionFromRequest();
|
||||
}
|
||||
self::$trackid = $s->getTrackID();
|
||||
|
||||
self::$shuttingDown = true;
|
||||
foreach (self::$earlyLog as $msg) {
|
||||
self::log($msg['level'], $msg['string'], $msg['statsLog']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Evaluate whether errors of a certain error level are masked or not.
|
||||
*
|
||||
* @param int $errno The level of the error to check.
|
||||
*
|
||||
* @return bool True if the error is masked, false otherwise.
|
||||
*/
|
||||
public static function isErrorMasked($errno)
|
||||
{
|
||||
return ($errno & self::$logMask) || !($errno & error_reporting());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Disable error reporting for the given log levels.
|
||||
*
|
||||
* Every call to this function must be followed by a call to popErrorMask().
|
||||
*
|
||||
* @param int $mask The log levels that should be masked.
|
||||
*/
|
||||
public static function maskErrors($mask)
|
||||
{
|
||||
assert(is_int($mask));
|
||||
|
||||
$currentEnabled = error_reporting();
|
||||
self::$logLevelStack[] = array($currentEnabled, self::$logMask);
|
||||
|
||||
$currentEnabled &= ~$mask;
|
||||
error_reporting($currentEnabled);
|
||||
self::$logMask |= $mask;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pop an error mask.
|
||||
*
|
||||
* This function restores the previous error mask.
|
||||
*/
|
||||
public static function popErrorMask()
|
||||
{
|
||||
$lastMask = array_pop(self::$logLevelStack);
|
||||
error_reporting($lastMask[0]);
|
||||
self::$logMask = $lastMask[1];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Defer a message for later logging.
|
||||
*
|
||||
* @param int $level The log level corresponding to this message.
|
||||
* @param string $message The message itself to log.
|
||||
* @param boolean $stats Whether this is a stats message or a regular one.
|
||||
*/
|
||||
private static function defer($level, $message, $stats)
|
||||
{
|
||||
// save the message for later
|
||||
self::$earlyLog[] = array('level' => $level, 'string' => $message, 'statsLog' => $stats);
|
||||
|
||||
// register a shutdown handler if needed
|
||||
if (!self::$shutdownRegistered) {
|
||||
register_shutdown_function(array('SimpleSAML\Logger', 'flush'));
|
||||
self::$shutdownRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static function createLoggingHandler($handler = null)
|
||||
{
|
||||
// set to false to indicate that it is being initialized
|
||||
self::$loggingHandler = false;
|
||||
|
||||
// a set of known logging handlers
|
||||
$known_handlers = array(
|
||||
'syslog' => 'SimpleSAML\Logger\SyslogLoggingHandler',
|
||||
'file' => 'SimpleSAML\Logger\FileLoggingHandler',
|
||||
'errorlog' => 'SimpleSAML\Logger\ErrorLogLoggingHandler',
|
||||
);
|
||||
|
||||
// get the configuration
|
||||
$config = \SimpleSAML_Configuration::getInstance();
|
||||
assert($config instanceof \SimpleSAML_Configuration);
|
||||
|
||||
// setting minimum log_level
|
||||
self::$logLevel = $config->getInteger('logging.level', self::INFO);
|
||||
|
||||
// get the metadata handler option from the configuration
|
||||
if (is_null($handler)) {
|
||||
$handler = $config->getString('logging.handler', 'syslog');
|
||||
}
|
||||
|
||||
if (!array_key_exists($handler, $known_handlers) && class_exists($handler)) {
|
||||
if (!in_array('SimpleSAML\Logger\LoggingHandlerInterface', class_implements($handler), true)) {
|
||||
throw new \Exception("The logging handler '$handler' is invalid.");
|
||||
}
|
||||
} else {
|
||||
$handler = strtolower($handler);
|
||||
if (!array_key_exists($handler, $known_handlers)) {
|
||||
throw new \Exception(
|
||||
"Invalid value for the 'logging.handler' configuration option. Unknown handler '".$handler."''."
|
||||
);
|
||||
}
|
||||
$handler = $known_handlers[$handler];
|
||||
}
|
||||
self::$loggingHandler = new $handler($config);
|
||||
|
||||
self::$format = $config->getString('logging.format', self::$format);
|
||||
self::$loggingHandler->setLogFormat(self::$format);
|
||||
}
|
||||
|
||||
|
||||
private static function log($level, $string, $statsLog = false)
|
||||
{
|
||||
if (self::$loggingHandler === false) {
|
||||
// some error occurred while initializing logging
|
||||
self::defer($level, $string, $statsLog);
|
||||
return;
|
||||
} elseif (php_sapi_name() === 'cli' || defined('STDIN')) {
|
||||
// we are being executed from the CLI, nowhere to log
|
||||
if (is_null(self::$loggingHandler)) {
|
||||
self::createLoggingHandler('SimpleSAML\Logger\StandardErrorLoggingHandler');
|
||||
}
|
||||
$_SERVER['REMOTE_ADDR'] = "CLI";
|
||||
if (self::$trackid === self::NO_TRACKID) {
|
||||
self::$trackid = 'CL'.bin2hex(openssl_random_pseudo_bytes(4));
|
||||
}
|
||||
} elseif (self::$loggingHandler === null) {
|
||||
// Initialize logging
|
||||
self::createLoggingHandler();
|
||||
|
||||
if (!empty(self::$earlyLog)) {
|
||||
// output messages which were logged before we properly initialized logging
|
||||
foreach (self::$earlyLog as $msg) {
|
||||
self::log($msg['level'], $msg['string'], $msg['statsLog']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (self::$captureLog) {
|
||||
$ts = microtime(true);
|
||||
$msecs = (int) (($ts - (int) $ts) * 1000);
|
||||
$ts = gmdate('H:i:s', $ts).sprintf('.%03d', $msecs).'Z';
|
||||
self::$capturedLog[] = $ts.' '.$string;
|
||||
}
|
||||
|
||||
if (self::$logLevel >= $level || $statsLog) {
|
||||
if (is_array($string)) {
|
||||
$string = implode(",", $string);
|
||||
}
|
||||
|
||||
$formats = array('%trackid', '%msg', '%srcip', '%stat');
|
||||
$replacements = array(self::$trackid, $string, $_SERVER['REMOTE_ADDR']);
|
||||
|
||||
$stat = '';
|
||||
if ($statsLog) {
|
||||
$stat = 'STAT ';
|
||||
}
|
||||
array_push($replacements, $stat);
|
||||
|
||||
if (self::$trackid === self::NO_TRACKID && !self::$shuttingDown) {
|
||||
// we have a log without track ID and we are not still shutting down, so defer logging
|
||||
self::defer($level, $string, $statsLog);
|
||||
return;
|
||||
} elseif (self::$trackid === self::NO_TRACKID) {
|
||||
// shutting down without a track ID, prettify it
|
||||
array_shift($replacements);
|
||||
array_unshift($replacements, 'N/A');
|
||||
}
|
||||
|
||||
// we either have a track ID or we are shutting down, so just log the message
|
||||
$string = str_replace($formats, $replacements, self::$format);
|
||||
self::$loggingHandler->log($level, $string);
|
||||
}
|
||||
}
|
||||
}
|
||||
84
lib/SimpleSAML/Logger/ErrorLogLoggingHandler.php
Executable file
84
lib/SimpleSAML/Logger/ErrorLogLoggingHandler.php
Executable file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleSAML\Logger;
|
||||
|
||||
use SimpleSAML\Logger;
|
||||
|
||||
/**
|
||||
* A class for logging to the default php error log.
|
||||
*
|
||||
* @author Lasse Birnbaum Jensen, SDU.
|
||||
* @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
|
||||
* @author Olav Morken, UNINETT AS.
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class ErrorLogLoggingHandler implements LoggingHandlerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* This array contains the mappings from syslog log level to names.
|
||||
*/
|
||||
private static $levelNames = array(
|
||||
Logger::EMERG => 'EMERG',
|
||||
Logger::ALERT => 'ALERT',
|
||||
Logger::CRIT => 'CRIT',
|
||||
Logger::ERR => 'ERR',
|
||||
Logger::WARNING => 'WARNING',
|
||||
Logger::NOTICE => 'NOTICE',
|
||||
Logger::INFO => 'INFO',
|
||||
Logger::DEBUG => 'DEBUG',
|
||||
);
|
||||
|
||||
/**
|
||||
* The name of this process.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $processname;
|
||||
|
||||
|
||||
/**
|
||||
* ErrorLogLoggingHandler constructor.
|
||||
*
|
||||
* @param \SimpleSAML_Configuration $config The configuration object for this handler.
|
||||
*/
|
||||
public function __construct(\SimpleSAML_Configuration $config)
|
||||
{
|
||||
$this->processname = $config->getString('logging.processname', 'SimpleSAMLphp');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the format desired for the logs.
|
||||
*
|
||||
* @param string $format The format used for logs.
|
||||
*/
|
||||
public function setLogFormat($format)
|
||||
{
|
||||
// we don't need the format here
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log a message to syslog.
|
||||
*
|
||||
* @param int $level The log level.
|
||||
* @param string $string The formatted message to log.
|
||||
*/
|
||||
public function log($level, $string)
|
||||
{
|
||||
if (array_key_exists($level, self::$levelNames)) {
|
||||
$levelName = self::$levelNames[$level];
|
||||
} else {
|
||||
$levelName = sprintf('UNKNOWN%d', $level);
|
||||
}
|
||||
|
||||
$formats = array('%process', '%level');
|
||||
$replacements = array($this->processname, $levelName);
|
||||
$string = str_replace($formats, $replacements, $string);
|
||||
$string = preg_replace('/%\w+(\{[^\}]+\})?/', '', $string);
|
||||
$string = trim($string);
|
||||
|
||||
error_log($string);
|
||||
}
|
||||
}
|
||||
113
lib/SimpleSAML/Logger/FileLoggingHandler.php
Executable file
113
lib/SimpleSAML/Logger/FileLoggingHandler.php
Executable file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleSAML\Logger;
|
||||
|
||||
use SimpleSAML\Logger;
|
||||
|
||||
/**
|
||||
* A logging handler that dumps logs to files.
|
||||
*
|
||||
* @author Lasse Birnbaum Jensen, SDU.
|
||||
* @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class FileLoggingHandler implements LoggingHandlerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* A string with the path to the file where we should log our messages.
|
||||
*
|
||||
* @var null|string
|
||||
*/
|
||||
protected $logFile = null;
|
||||
|
||||
/**
|
||||
* This array contains the mappings from syslog log levels to names. Copied more or less directly from
|
||||
* SimpleSAML\Logger\ErrorLogLoggingHandler.
|
||||
*/
|
||||
private static $levelNames = array(
|
||||
Logger::EMERG => 'EMERGENCY',
|
||||
Logger::ALERT => 'ALERT',
|
||||
Logger::CRIT => 'CRITICAL',
|
||||
Logger::ERR => 'ERROR',
|
||||
Logger::WARNING => 'WARNING',
|
||||
Logger::NOTICE => 'NOTICE',
|
||||
Logger::INFO => 'INFO',
|
||||
Logger::DEBUG => 'DEBUG',
|
||||
);
|
||||
protected $processname = null;
|
||||
protected $format;
|
||||
|
||||
|
||||
/**
|
||||
* Build a new logging handler based on files.
|
||||
*/
|
||||
public function __construct(\SimpleSAML_Configuration $config)
|
||||
{
|
||||
// get the metadata handler option from the configuration
|
||||
$this->logFile = $config->getPathValue('loggingdir', 'log/').
|
||||
$config->getString('logging.logfile', 'simplesamlphp.log');
|
||||
$this->processname = $config->getString('logging.processname', 'SimpleSAMLphp');
|
||||
|
||||
if (@file_exists($this->logFile)) {
|
||||
if (!@is_writeable($this->logFile)) {
|
||||
throw new \Exception("Could not write to logfile: ".$this->logFile);
|
||||
}
|
||||
} else {
|
||||
if (!@touch($this->logFile)) {
|
||||
throw new \Exception(
|
||||
"Could not create logfile: ".$this->logFile.
|
||||
" The logging directory is not writable for the web server user."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
\SimpleSAML\Utils\Time::initTimezone();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the format desired for the logs.
|
||||
*
|
||||
* @param string $format The format used for logs.
|
||||
*/
|
||||
public function setLogFormat($format)
|
||||
{
|
||||
$this->format = $format;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log a message to the log file.
|
||||
*
|
||||
* @param int $level The log level.
|
||||
* @param string $string The formatted message to log.
|
||||
*/
|
||||
public function log($level, $string)
|
||||
{
|
||||
if (!is_null($this->logFile)) {
|
||||
// set human-readable log level. Copied from SimpleSAML\Logger\ErrorLogLoggingHandler.
|
||||
$levelName = sprintf('UNKNOWN%d', $level);
|
||||
if (array_key_exists($level, self::$levelNames)) {
|
||||
$levelName = self::$levelNames[$level];
|
||||
}
|
||||
|
||||
$formats = array('%process', '%level');
|
||||
$replacements = array($this->processname, $levelName);
|
||||
|
||||
$matches = array();
|
||||
if (preg_match('/%date(?:\{([^\}]+)\})?/', $this->format, $matches)) {
|
||||
$format = "%b %d %H:%M:%S";
|
||||
if (isset($matches[1])) {
|
||||
$format = $matches[1];
|
||||
}
|
||||
|
||||
array_push($formats, $matches[0]);
|
||||
array_push($replacements, strftime($format));
|
||||
}
|
||||
|
||||
$string = str_replace($formats, $replacements, $string);
|
||||
file_put_contents($this->logFile, $string.PHP_EOL, FILE_APPEND);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
lib/SimpleSAML/Logger/LoggingHandlerInterface.php
Executable file
38
lib/SimpleSAML/Logger/LoggingHandlerInterface.php
Executable file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleSAML\Logger;
|
||||
|
||||
/**
|
||||
* The interface that must be implemented by any log handler.
|
||||
*
|
||||
* @author Jaime Perez Crespo, UNINETT AS.
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
interface LoggingHandlerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructor for log handlers. It must accept receiving a \SimpleSAML_Configuration object.
|
||||
*
|
||||
* @param \SimpleSAML_Configuration $config The configuration to use in this log handler.
|
||||
*/
|
||||
public function __construct(\SimpleSAML_Configuration $config);
|
||||
|
||||
|
||||
/**
|
||||
* Log a message to its destination.
|
||||
*
|
||||
* @param int $level The log level.
|
||||
* @param string $string The message to log.
|
||||
*/
|
||||
public function log($level, $string);
|
||||
|
||||
|
||||
/**
|
||||
* Set the format desired for the logs.
|
||||
*
|
||||
* @param string $format The format used for logs.
|
||||
*/
|
||||
public function setLogFormat($format);
|
||||
}
|
||||
24
lib/SimpleSAML/Logger/StandardErrorLoggingHandler.php
Executable file
24
lib/SimpleSAML/Logger/StandardErrorLoggingHandler.php
Executable file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleSAML\Logger;
|
||||
|
||||
/**
|
||||
* A logging handler that outputs all messages to standard error.
|
||||
*
|
||||
* @author Jaime Perez Crespo, UNINETT AS <jaime.perez@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class StandardErrorLoggingHandler extends FileLoggingHandler
|
||||
{
|
||||
|
||||
/**
|
||||
* StandardError constructor.
|
||||
*
|
||||
* It runs the parent constructor and sets the log file to be the standard error descriptor.
|
||||
*/
|
||||
public function __construct(\SimpleSAML_Configuration $config)
|
||||
{
|
||||
$this->processname = $config->getString('logging.processname', 'SimpleSAMLphp');
|
||||
$this->logFile = 'php://stderr';
|
||||
}
|
||||
}
|
||||
75
lib/SimpleSAML/Logger/SyslogLoggingHandler.php
Executable file
75
lib/SimpleSAML/Logger/SyslogLoggingHandler.php
Executable file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleSAML\Logger;
|
||||
|
||||
use SimpleSAML\Utils\System;
|
||||
|
||||
/**
|
||||
* A logger that sends messages to syslog.
|
||||
*
|
||||
* @author Lasse Birnbaum Jensen, SDU.
|
||||
* @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SyslogLoggingHandler implements LoggingHandlerInterface
|
||||
{
|
||||
private $isWindows = false;
|
||||
private $format;
|
||||
|
||||
|
||||
/**
|
||||
* Build a new logging handler based on syslog.
|
||||
*/
|
||||
public function __construct(\SimpleSAML_Configuration $config)
|
||||
{
|
||||
$facility = $config->getInteger('logging.facility', defined('LOG_LOCAL5') ? constant('LOG_LOCAL5') : LOG_USER);
|
||||
|
||||
$processname = $config->getString('logging.processname', 'SimpleSAMLphp');
|
||||
|
||||
// Setting facility to LOG_USER (only valid in Windows), enable log level rewrite on windows systems
|
||||
if (System::getOS() === System::WINDOWS) {
|
||||
$this->isWindows = true;
|
||||
$facility = LOG_USER;
|
||||
}
|
||||
|
||||
openlog($processname, LOG_PID, $facility);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the format desired for the logs.
|
||||
*
|
||||
* @param string $format The format used for logs.
|
||||
*/
|
||||
public function setLogFormat($format)
|
||||
{
|
||||
$this->format = $format;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log a message to syslog.
|
||||
*
|
||||
* @param int $level The log level.
|
||||
* @param string $string The formatted message to log.
|
||||
*/
|
||||
public function log($level, $string)
|
||||
{
|
||||
// changing log level to supported levels if OS is Windows
|
||||
if ($this->isWindows) {
|
||||
if ($level <= 4) {
|
||||
$level = LOG_ERR;
|
||||
} else {
|
||||
$level = LOG_INFO;
|
||||
}
|
||||
}
|
||||
|
||||
$formats = array('%process', '%level');
|
||||
$replacements = array('', $level);
|
||||
$string = str_replace($formats, $replacements, $string);
|
||||
$string = preg_replace('/%\w+(\{[^\}]+\})?/', '', $string);
|
||||
$string = trim($string);
|
||||
|
||||
syslog($level, $string);
|
||||
}
|
||||
}
|
||||
492
lib/SimpleSAML/Memcache.php
Executable file
492
lib/SimpleSAML/Memcache.php
Executable file
@@ -0,0 +1,492 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* This file implements functions to read and write to a group of memcache
|
||||
* servers.
|
||||
*
|
||||
* The goals of this storage class is to provide failover, redudancy and load
|
||||
* balancing. This is accomplished by storing the data object to several
|
||||
* groups of memcache servers. Each data object is replicated to every group
|
||||
* of memcache servers, but it is only stored to one server in each group.
|
||||
*
|
||||
* For this code to work correctly, all web servers accessing the data must
|
||||
* have the same clock (as measured by the time()-function). Different clock
|
||||
* values will lead to incorrect behaviour.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS.
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SimpleSAML_Memcache
|
||||
{
|
||||
|
||||
/**
|
||||
* Cache of the memcache servers we are using.
|
||||
*
|
||||
* @var Memcache[]|null
|
||||
*/
|
||||
private static $serverGroups = null;
|
||||
|
||||
|
||||
/**
|
||||
* The flavor of memcache PHP extension we are using.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static $extension = '';
|
||||
|
||||
|
||||
/**
|
||||
* Find data stored with a given key.
|
||||
*
|
||||
* @param string $key The key of the data.
|
||||
*
|
||||
* @return mixed The data stored with the given key, or null if no data matching the key was found.
|
||||
*/
|
||||
public static function get($key)
|
||||
{
|
||||
SimpleSAML\Logger::debug("loading key $key from memcache");
|
||||
|
||||
$latestInfo = null;
|
||||
$latestTime = 0.0;
|
||||
$latestData = null;
|
||||
$mustUpdate = false;
|
||||
$allDown = true;
|
||||
|
||||
// search all the servers for the given id
|
||||
foreach (self::getMemcacheServers() as $server) {
|
||||
$serializedInfo = $server->get($key);
|
||||
if ($serializedInfo === false) {
|
||||
// either the server is down, or we don't have the value stored on that server
|
||||
$mustUpdate = true;
|
||||
$up = $server->getstats();
|
||||
if ($up !== false) {
|
||||
$allDown = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
$allDown = false;
|
||||
|
||||
// unserialize the object
|
||||
$info = unserialize($serializedInfo);
|
||||
|
||||
/*
|
||||
* Make sure that this is an array with two keys:
|
||||
* - 'timestamp': The time the data was saved.
|
||||
* - 'data': The data.
|
||||
*/
|
||||
if (!is_array($info)) {
|
||||
SimpleSAML\Logger::warning(
|
||||
'Retrieved invalid data from a memcache server. Data was not an array.'
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (!array_key_exists('timestamp', $info)) {
|
||||
SimpleSAML\Logger::warning(
|
||||
'Retrieved invalid data from a memcache server. Missing timestamp.'
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (!array_key_exists('data', $info)) {
|
||||
SimpleSAML\Logger::warning(
|
||||
'Retrieved invalid data from a memcache server. Missing data.'
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($latestInfo === null) {
|
||||
// first info found
|
||||
$latestInfo = $serializedInfo;
|
||||
$latestTime = $info['timestamp'];
|
||||
$latestData = $info['data'];
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($info['timestamp'] === $latestTime && $serializedInfo === $latestInfo) {
|
||||
// this data matches the data from the other server(s)
|
||||
continue;
|
||||
}
|
||||
|
||||
// different data from different servers. We need to update at least one of them to maintain sync
|
||||
$mustUpdate = true;
|
||||
|
||||
// update if data in $info is newer than $latestData
|
||||
if ($latestTime < $info['timestamp']) {
|
||||
$latestInfo = $serializedInfo;
|
||||
$latestTime = $info['timestamp'];
|
||||
$latestData = $info['data'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($latestData === null) {
|
||||
if ($allDown) {
|
||||
// all servers are down, panic!
|
||||
$e = new SimpleSAML_Error_Error('MEMCACHEDOWN', null, 503);
|
||||
throw new SimpleSAML_Error_Exception('All memcache servers are down', 503, $e);
|
||||
}
|
||||
// we didn't find any data matching the key
|
||||
SimpleSAML\Logger::debug("key $key not found in memcache");
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($mustUpdate) {
|
||||
// we found data matching the key, but some of the servers need updating
|
||||
SimpleSAML\Logger::debug("Memcache servers out of sync for $key, forcing sync");
|
||||
self::set($key, $latestData);
|
||||
}
|
||||
|
||||
return $latestData;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save a key-value pair to the memcache servers.
|
||||
*
|
||||
* @param string $key The key of the data.
|
||||
* @param mixed $value The value of the data.
|
||||
* @param integer|null $expire The expiration timestamp of the data.
|
||||
*/
|
||||
public static function set($key, $value, $expire = null)
|
||||
{
|
||||
SimpleSAML\Logger::debug("saving key $key to memcache");
|
||||
$savedInfo = array(
|
||||
'timestamp' => microtime(true),
|
||||
'data' => $value
|
||||
);
|
||||
|
||||
if ($expire === null) {
|
||||
$expire = self::getExpireTime();
|
||||
}
|
||||
|
||||
$savedInfoSerialized = serialize($savedInfo);
|
||||
|
||||
// store this object to all groups of memcache servers
|
||||
foreach (self::getMemcacheServers() as $server) {
|
||||
if (self::$extension === 'memcached') {
|
||||
$server->set($key, $savedInfoSerialized, $expire);
|
||||
} else {
|
||||
$server->set($key, $savedInfoSerialized, 0, $expire);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete a key-value pair from the memcache servers.
|
||||
*
|
||||
* @param string $key The key we should delete.
|
||||
*/
|
||||
public static function delete($key)
|
||||
{
|
||||
assert(is_string($key));
|
||||
SimpleSAML\Logger::debug("deleting key $key from memcache");
|
||||
|
||||
// store this object to all groups of memcache servers
|
||||
foreach (self::getMemcacheServers() as $server) {
|
||||
$server->delete($key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function adds a server from the 'memcache_store.servers'
|
||||
* configuration option to a Memcache object.
|
||||
*
|
||||
* The server parameter is an array with the following keys:
|
||||
* - hostname
|
||||
* Hostname or ip address to the memcache server.
|
||||
* - port (optional)
|
||||
* port number the memcache server is running on. This
|
||||
* defaults to memcache.default_port if no value is given.
|
||||
* The default value of memcache.default_port is 11211.
|
||||
* - weight (optional)
|
||||
* The weight of this server in the load balancing
|
||||
* cluster.
|
||||
* - timeout (optional)
|
||||
* The timeout for contacting this server, in seconds.
|
||||
* The default value is 3 seconds.
|
||||
*
|
||||
* @param Memcache $memcache The Memcache object we should add this server to.
|
||||
* @param array $server An associative array with the configuration options for the server to add.
|
||||
*
|
||||
* @throws Exception If any configuration option for the server is invalid.
|
||||
*/
|
||||
private static function addMemcacheServer($memcache, $server)
|
||||
{
|
||||
// the hostname option is required
|
||||
if (!array_key_exists('hostname', $server)) {
|
||||
throw new Exception(
|
||||
"hostname setting missing from server in the 'memcache_store.servers' configuration option."
|
||||
);
|
||||
}
|
||||
|
||||
$hostname = $server['hostname'];
|
||||
|
||||
// the hostname must be a valid string
|
||||
if (!is_string($hostname)) {
|
||||
throw new Exception(
|
||||
"Invalid hostname for server in the 'memcache_store.servers' configuration option. The hostname is".
|
||||
' supposed to be a string.'
|
||||
);
|
||||
}
|
||||
|
||||
// check if we are told to use a socket
|
||||
$socket = false;
|
||||
if (strpos($hostname, 'unix:///') === 0) {
|
||||
$socket = true;
|
||||
}
|
||||
|
||||
// check if the user has specified a port number
|
||||
if ($socket) {
|
||||
// force port to be 0 for sockets
|
||||
$port = 0;
|
||||
} elseif (array_key_exists('port', $server)) {
|
||||
// get the port number from the array, and validate it
|
||||
$port = (int) $server['port'];
|
||||
if (($port <= 0) || ($port > 65535)) {
|
||||
throw new Exception(
|
||||
"Invalid port for server in the 'memcache_store.servers' configuration option. The port number".
|
||||
' is supposed to be an integer between 0 and 65535.'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// use the default port number from the ini-file
|
||||
$port = (int) ini_get('memcache.default_port');
|
||||
if ($port <= 0 || $port > 65535) {
|
||||
// invalid port number from the ini-file. fall back to the default
|
||||
$port = 11211;
|
||||
}
|
||||
}
|
||||
|
||||
// check if the user has specified a weight for this server
|
||||
if (array_key_exists('weight', $server)) {
|
||||
// get the weight and validate it
|
||||
$weight = (int) $server['weight'];
|
||||
if ($weight <= 0) {
|
||||
throw new Exception(
|
||||
"Invalid weight for server in the 'memcache_store.servers' configuration option. The weight is".
|
||||
' supposed to be a positive integer.'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// use a default weight of 1
|
||||
$weight = 1;
|
||||
}
|
||||
|
||||
// check if the user has specified a timeout for this server
|
||||
if (array_key_exists('timeout', $server)) {
|
||||
// get the timeout and validate it
|
||||
$timeout = (int) $server['timeout'];
|
||||
if ($timeout <= 0) {
|
||||
throw new Exception(
|
||||
"Invalid timeout for server in the 'memcache_store.servers' configuration option. The timeout is".
|
||||
' supposed to be a positive integer.'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// use a default timeout of 3 seconds
|
||||
$timeout = 3;
|
||||
}
|
||||
|
||||
// add this server to the Memcache object
|
||||
if (self::$extension === 'memcached') {
|
||||
$memcache->addServer($hostname, $port);
|
||||
} else {
|
||||
$memcache->addServer($hostname, $port, true, $weight, $timeout, $timeout, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function takes in a list of servers belonging to a group and
|
||||
* creates a Memcache object from the servers in the group.
|
||||
*
|
||||
* @param array $group Array of servers which should be created as a group.
|
||||
*
|
||||
* @return Memcache A Memcache object of the servers in the group
|
||||
*
|
||||
* @throws Exception If the servers configuration is invalid.
|
||||
*/
|
||||
private static function loadMemcacheServerGroup(array $group)
|
||||
{
|
||||
$class = class_exists('Memcache') ? 'Memcache' : (class_exists('Memcached') ? 'Memcached' : false);
|
||||
if (!$class) {
|
||||
throw new Exception('Missing Memcached implementation. You must install either the Memcache or Memcached extension.');
|
||||
}
|
||||
self::$extension = strtolower($class);
|
||||
|
||||
// create the Memcache object
|
||||
$memcache = new $class();
|
||||
|
||||
// iterate over all the servers in the group and add them to the Memcache object
|
||||
foreach ($group as $index => $server) {
|
||||
// make sure that we don't have an index. An index would be a sign of invalid configuration
|
||||
if (!is_int($index)) {
|
||||
throw new Exception(
|
||||
"Invalid index on element in the 'memcache_store.servers' configuration option. Perhaps you".
|
||||
' have forgotten to add an array(...) around one of the server groups? The invalid index was: '.
|
||||
$index
|
||||
);
|
||||
}
|
||||
|
||||
// make sure that the server object is an array. Each server is an array with name-value pairs
|
||||
if (!is_array($server)) {
|
||||
throw new Exception(
|
||||
'Invalid value for the server with index '.$index.
|
||||
'. Remeber that the \'memcache_store.servers\' configuration option'.
|
||||
' contains an array of arrays of arrays.'
|
||||
);
|
||||
}
|
||||
|
||||
self::addMemcacheServer($memcache, $server);
|
||||
}
|
||||
|
||||
return $memcache;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function gets a list of all configured memcache servers. This list is initialized based
|
||||
* on the content of 'memcache_store.servers' in the configuration.
|
||||
*
|
||||
* @return Memcache[] Array with Memcache objects.
|
||||
*
|
||||
* @throws Exception If the servers configuration is invalid.
|
||||
*/
|
||||
private static function getMemcacheServers()
|
||||
{
|
||||
// check if we have loaded the servers already
|
||||
if (self::$serverGroups != null) {
|
||||
return self::$serverGroups;
|
||||
}
|
||||
|
||||
// initialize the servers-array
|
||||
self::$serverGroups = array();
|
||||
|
||||
// load the configuration
|
||||
$config = SimpleSAML_Configuration::getInstance();
|
||||
|
||||
|
||||
$groups = $config->getArray('memcache_store.servers');
|
||||
|
||||
// iterate over all the groups in the 'memcache_store.servers' configuration option
|
||||
foreach ($groups as $index => $group) {
|
||||
// make sure that the group doesn't have an index. An index would be a sign of invalid configuration
|
||||
if (!is_int($index)) {
|
||||
throw new Exception(
|
||||
"Invalid index on element in the 'memcache_store.servers'".
|
||||
' configuration option. Perhaps you have forgotten to add an array(...)'.
|
||||
' around one of the server groups? The invalid index was: '.$index
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure that the group is an array. Each group is an array of servers. Each server is
|
||||
* an array of name => value pairs for that server.
|
||||
*/
|
||||
if (!is_array($group)) {
|
||||
throw new Exception(
|
||||
"Invalid value for the server with index ".$index.
|
||||
". Remeber that the 'memcache_store.servers' configuration option".
|
||||
' contains an array of arrays of arrays.'
|
||||
);
|
||||
}
|
||||
|
||||
// parse and add this group to the server group list
|
||||
self::$serverGroups[] = self::loadMemcacheServerGroup($group);
|
||||
}
|
||||
|
||||
return self::$serverGroups;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is a helper-function which returns the expire value of data
|
||||
* we should store to the memcache servers.
|
||||
*
|
||||
* The value is set depending on the configuration. If no value is
|
||||
* set in the configuration, then we will use a default value of 0.
|
||||
* 0 means that the item will never expire.
|
||||
*
|
||||
* @return integer The value which should be passed in the set(...) calls to the memcache objects.
|
||||
*
|
||||
* @throws Exception If the option 'memcache_store.expires' has a negative value.
|
||||
*/
|
||||
private static function getExpireTime()
|
||||
{
|
||||
// get the configuration instance
|
||||
$config = SimpleSAML_Configuration::getInstance();
|
||||
assert($config instanceof SimpleSAML_Configuration);
|
||||
|
||||
// get the expire-value from the configuration
|
||||
$expire = $config->getInteger('memcache_store.expires', 0);
|
||||
|
||||
// it must be a positive integer
|
||||
if ($expire < 0) {
|
||||
throw new Exception(
|
||||
"The value of 'memcache_store.expires' in the configuration can't be a negative integer."
|
||||
);
|
||||
}
|
||||
|
||||
/* If the configuration option is 0, then we should return 0. This allows the user to specify that the data
|
||||
* shouldn't expire.
|
||||
*/
|
||||
if ($expire == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The expire option is given as the number of seconds into the future an item should expire. We convert this
|
||||
* to an actual timestamp.
|
||||
*/
|
||||
$expireTime = time() + $expire;
|
||||
|
||||
return $expireTime;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function retrieves statistics about all memcache server groups.
|
||||
*
|
||||
* @return array Array with the names of each stat and an array with the value for each server group.
|
||||
*
|
||||
* @throws Exception If memcache server status couldn't be retrieved.
|
||||
*/
|
||||
public static function getStats()
|
||||
{
|
||||
$ret = array();
|
||||
|
||||
foreach (self::getMemcacheServers() as $sg) {
|
||||
$stats = method_exists($sg, 'getExtendedStats') ? $sg->getExtendedStats() : $sg->getStats();
|
||||
foreach ($stats as $server => $data) {
|
||||
if ($data === false) {
|
||||
throw new Exception('Failed to get memcache server status.');
|
||||
}
|
||||
}
|
||||
|
||||
$stats = SimpleSAML\Utils\Arrays::transpose($stats);
|
||||
|
||||
$ret = array_merge_recursive($ret, $stats);
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve statistics directly in the form returned by getExtendedStats, for
|
||||
* all server groups.
|
||||
*
|
||||
* @return array An array with the extended stats output for each server group.
|
||||
*/
|
||||
public static function getRawStats()
|
||||
{
|
||||
$ret = array();
|
||||
|
||||
foreach (self::getMemcacheServers() as $sg) {
|
||||
$stats = method_exists($sg, 'getExtendedStats') ? $sg->getExtendedStats() : $sg->getStats();
|
||||
$ret[] = $stats;
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
361
lib/SimpleSAML/Metadata/MetaDataStorageHandler.php
Executable file
361
lib/SimpleSAML/Metadata/MetaDataStorageHandler.php
Executable file
@@ -0,0 +1,361 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* This file defines a class for metadata handling.
|
||||
*
|
||||
* @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SimpleSAML_Metadata_MetaDataStorageHandler
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* This static variable contains a reference to the current
|
||||
* instance of the metadata handler. This variable will be null if
|
||||
* we haven't instantiated a metadata handler yet.
|
||||
*
|
||||
* @var SimpleSAML_Metadata_MetaDataStorageHandler
|
||||
*/
|
||||
private static $metadataHandler = null;
|
||||
|
||||
|
||||
/**
|
||||
* This is a list of all the metadata sources we have in our metadata
|
||||
* chain. When we need metadata, we will look through this chain from start to end.
|
||||
*
|
||||
* @var SimpleSAML_Metadata_MetaDataStorageSource[]
|
||||
*/
|
||||
private $sources;
|
||||
|
||||
|
||||
/**
|
||||
* This function retrieves the current instance of the metadata handler.
|
||||
* The metadata handler will be instantiated if this is the first call
|
||||
* to this function.
|
||||
*
|
||||
* @return SimpleSAML_Metadata_MetaDataStorageHandler The current metadata handler instance.
|
||||
*/
|
||||
public static function getMetadataHandler()
|
||||
{
|
||||
if (self::$metadataHandler === null) {
|
||||
self::$metadataHandler = new SimpleSAML_Metadata_MetaDataStorageHandler();
|
||||
}
|
||||
|
||||
return self::$metadataHandler;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This constructor initializes this metadata storage handler. It will load and
|
||||
* parse the configuration, and initialize the metadata source list.
|
||||
*/
|
||||
protected function __construct()
|
||||
{
|
||||
$config = SimpleSAML_Configuration::getInstance();
|
||||
|
||||
$sourcesConfig = $config->getArray('metadata.sources', null);
|
||||
|
||||
// for backwards compatibility, and to provide a default configuration
|
||||
if ($sourcesConfig === null) {
|
||||
$type = $config->getString('metadata.handler', 'flatfile');
|
||||
$sourcesConfig = array(array('type' => $type));
|
||||
}
|
||||
|
||||
try {
|
||||
$this->sources = SimpleSAML_Metadata_MetaDataStorageSource::parseSources($sourcesConfig);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception(
|
||||
"Invalid configuration of the 'metadata.sources' configuration option: ".$e->getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function is used to generate some metadata elements automatically.
|
||||
*
|
||||
* @param string $property The metadata property which should be auto-generated.
|
||||
* @param string $set The set we the property comes from.
|
||||
*
|
||||
* @return string The auto-generated metadata property.
|
||||
* @throws Exception If the metadata cannot be generated automatically.
|
||||
*/
|
||||
public function getGenerated($property, $set)
|
||||
{
|
||||
// first we check if the user has overridden this property in the metadata
|
||||
try {
|
||||
$metadataSet = $this->getMetaDataCurrent($set);
|
||||
if (array_key_exists($property, $metadataSet)) {
|
||||
return $metadataSet[$property];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// probably metadata wasn't found. In any case we continue by generating the metadata
|
||||
}
|
||||
|
||||
// get the configuration
|
||||
$config = SimpleSAML_Configuration::getInstance();
|
||||
assert($config instanceof SimpleSAML_Configuration);
|
||||
|
||||
$baseurl = \SimpleSAML\Utils\HTTP::getSelfURLHost().$config->getBasePath();
|
||||
|
||||
if ($set == 'saml20-sp-hosted') {
|
||||
if ($property === 'SingleLogoutServiceBinding') {
|
||||
return \SAML2\Constants::BINDING_HTTP_REDIRECT;
|
||||
}
|
||||
} elseif ($set == 'saml20-idp-hosted') {
|
||||
switch ($property) {
|
||||
case 'SingleSignOnService':
|
||||
return $baseurl.'saml2/idp/SSOService.php';
|
||||
|
||||
case 'SingleSignOnServiceBinding':
|
||||
return \SAML2\Constants::BINDING_HTTP_REDIRECT;
|
||||
|
||||
case 'SingleLogoutService':
|
||||
return $baseurl.'saml2/idp/SingleLogoutService.php';
|
||||
|
||||
case 'SingleLogoutServiceBinding':
|
||||
return \SAML2\Constants::BINDING_HTTP_REDIRECT;
|
||||
}
|
||||
} elseif ($set == 'shib13-idp-hosted') {
|
||||
if ($property === 'SingleSignOnService') {
|
||||
return $baseurl.'shib13/idp/SSOService.php';
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception('Could not generate metadata property '.$property.' for set '.$set.'.');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function lists all known metadata in the given set. It is returned as an associative array
|
||||
* where the key is the entity id.
|
||||
*
|
||||
* @param string $set The set we want to list metadata from.
|
||||
*
|
||||
* @return array An associative array with the metadata from from the given set.
|
||||
*/
|
||||
public function getList($set = 'saml20-idp-remote')
|
||||
{
|
||||
assert(is_string($set));
|
||||
|
||||
$result = array();
|
||||
|
||||
foreach ($this->sources as $source) {
|
||||
$srcList = $source->getMetadataSet($set);
|
||||
|
||||
foreach ($srcList as $key => $le) {
|
||||
if (array_key_exists('expire', $le)) {
|
||||
if ($le['expire'] < time()) {
|
||||
unset($srcList[$key]);
|
||||
SimpleSAML\Logger::warning(
|
||||
"Dropping metadata entity ".var_export($key, true).", expired ".
|
||||
SimpleSAML\Utils\Time::generateTimestamp($le['expire'])."."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* $result is the last argument to array_merge because we want the content already
|
||||
* in $result to have precedence.
|
||||
*/
|
||||
$result = array_merge($srcList, $result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function retrieves metadata for the current entity based on the hostname/path the request
|
||||
* was directed to. It will throw an exception if it is unable to locate the metadata.
|
||||
*
|
||||
* @param string $set The set we want metadata from.
|
||||
*
|
||||
* @return array An associative array with the metadata.
|
||||
*/
|
||||
public function getMetaDataCurrent($set)
|
||||
{
|
||||
return $this->getMetaData(null, $set);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function locates the current entity id based on the hostname/path combination the user accessed.
|
||||
* It will throw an exception if it is unable to locate the entity id.
|
||||
*
|
||||
* @param string $set The set we look for the entity id in.
|
||||
* @param string $type Do you want to return the metaindex or the entityID. [entityid|metaindex]
|
||||
*
|
||||
* @return string The entity id which is associated with the current hostname/path combination.
|
||||
* @throws Exception If no default metadata can be found in the set for the current host.
|
||||
*/
|
||||
public function getMetaDataCurrentEntityID($set, $type = 'entityid')
|
||||
{
|
||||
assert(is_string($set));
|
||||
|
||||
// first we look for the hostname/path combination
|
||||
$currenthostwithpath = \SimpleSAML\Utils\HTTP::getSelfHostWithPath(); // sp.example.org/university
|
||||
|
||||
foreach ($this->sources as $source) {
|
||||
$index = $source->getEntityIdFromHostPath($currenthostwithpath, $set, $type);
|
||||
if ($index !== null) {
|
||||
return $index;
|
||||
}
|
||||
}
|
||||
|
||||
// then we look for the hostname
|
||||
$currenthost = \SimpleSAML\Utils\HTTP::getSelfHost(); // sp.example.org
|
||||
|
||||
foreach ($this->sources as $source) {
|
||||
$index = $source->getEntityIdFromHostPath($currenthost, $set, $type);
|
||||
if ($index !== null) {
|
||||
return $index;
|
||||
}
|
||||
}
|
||||
|
||||
// then we look for the DEFAULT entry
|
||||
foreach ($this->sources as $source) {
|
||||
$entityId = $source->getEntityIdFromHostPath('__DEFAULT__', $set, $type);
|
||||
if ($entityId !== null) {
|
||||
return $entityId;
|
||||
}
|
||||
}
|
||||
|
||||
// we were unable to find the hostname/path in any metadata source
|
||||
throw new Exception(
|
||||
'Could not find any default metadata entities in set ['.$set.'] for host ['.$currenthost.' : '.
|
||||
$currenthostwithpath.']'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method will call getPreferredEntityIdFromCIDRhint() on all of the
|
||||
* sources.
|
||||
*
|
||||
* @param string $set Which set of metadata we are looking it up in.
|
||||
* @param string $ip IP address
|
||||
*
|
||||
* @return string The entity id of a entity which have a CIDR hint where the provided
|
||||
* IP address match.
|
||||
*/
|
||||
public function getPreferredEntityIdFromCIDRhint($set, $ip)
|
||||
{
|
||||
foreach ($this->sources as $source) {
|
||||
$entityId = $source->getPreferredEntityIdFromCIDRhint($set, $ip);
|
||||
if ($entityId !== null) {
|
||||
return $entityId;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function looks up the metadata for the given entity id in the given set. It will throw an
|
||||
* exception if it is unable to locate the metadata.
|
||||
*
|
||||
* @param string $index The entity id we are looking up. This parameter may be NULL, in which case we look up
|
||||
* the current entity id based on the current hostname/path.
|
||||
* @param string $set The set of metadata we are looking up the entity id in.
|
||||
*
|
||||
* @return array The metadata array describing the specified entity.
|
||||
* @throws Exception If metadata for the specified entity is expired.
|
||||
* @throws SimpleSAML_Error_MetadataNotFound If no metadata for the entity specified can be found.
|
||||
*/
|
||||
public function getMetaData($index, $set)
|
||||
{
|
||||
assert(is_string($set));
|
||||
|
||||
if ($index === null) {
|
||||
$index = $this->getMetaDataCurrentEntityID($set, 'metaindex');
|
||||
}
|
||||
|
||||
assert(is_string($index));
|
||||
|
||||
foreach ($this->sources as $source) {
|
||||
$metadata = $source->getMetaData($index, $set);
|
||||
|
||||
if ($metadata !== null) {
|
||||
if (array_key_exists('expire', $metadata)) {
|
||||
if ($metadata['expire'] < time()) {
|
||||
throw new Exception(
|
||||
'Metadata for the entity ['.$index.'] expired '.
|
||||
(time() - $metadata['expire']).' seconds ago.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$metadata['metadata-index'] = $index;
|
||||
$metadata['metadata-set'] = $set;
|
||||
assert(array_key_exists('entityid', $metadata));
|
||||
return $metadata;
|
||||
}
|
||||
}
|
||||
|
||||
throw new SimpleSAML_Error_MetadataNotFound($index);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the metadata as a configuration object.
|
||||
*
|
||||
* This function will throw an exception if it is unable to locate the metadata.
|
||||
*
|
||||
* @param string $entityId The entity ID we are looking up.
|
||||
* @param string $set The metadata set we are searching.
|
||||
*
|
||||
* @return SimpleSAML_Configuration The configuration object representing the metadata.
|
||||
* @throws SimpleSAML_Error_MetadataNotFound If no metadata for the entity specified can be found.
|
||||
*/
|
||||
public function getMetaDataConfig($entityId, $set)
|
||||
{
|
||||
assert(is_string($entityId));
|
||||
assert(is_string($set));
|
||||
|
||||
$metadata = $this->getMetaData($entityId, $set);
|
||||
return SimpleSAML_Configuration::loadFromArray($metadata, $set.'/'.var_export($entityId, true));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Search for an entity's metadata, given the SHA1 digest of its entity ID.
|
||||
*
|
||||
* @param string $sha1 The SHA1 digest of the entity ID.
|
||||
* @param string $set The metadata set we are searching.
|
||||
*
|
||||
* @return null|SimpleSAML_Configuration The metadata corresponding to the entity, or null if the entity cannot be
|
||||
* found.
|
||||
*/
|
||||
public function getMetaDataConfigForSha1($sha1, $set)
|
||||
{
|
||||
assert(is_string($sha1));
|
||||
assert(is_string($set));
|
||||
|
||||
$result = array();
|
||||
|
||||
foreach ($this->sources as $source) {
|
||||
$srcList = $source->getMetadataSet($set);
|
||||
|
||||
/* $result is the last argument to array_merge because we want the content already
|
||||
* in $result to have precedence.
|
||||
*/
|
||||
$result = array_merge($srcList, $result);
|
||||
}
|
||||
foreach ($result as $remote_provider) {
|
||||
if (sha1($remote_provider['entityid']) == $sha1) {
|
||||
$remote_provider['metadata-set'] = $set;
|
||||
|
||||
return SimpleSAML_Configuration::loadFromArray(
|
||||
$remote_provider,
|
||||
$set.'/'.var_export($remote_provider['entityid'], true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
144
lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php
Executable file
144
lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php
Executable file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* This file defines a flat file metadata source.
|
||||
* Instantiation of session handler objects should be done through
|
||||
* the class method getMetadataHandler().
|
||||
*
|
||||
* @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SimpleSAML_Metadata_MetaDataStorageHandlerFlatFile extends SimpleSAML_Metadata_MetaDataStorageSource
|
||||
{
|
||||
|
||||
/**
|
||||
* This is the directory we will load metadata files from. The path will always end
|
||||
* with a '/'.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $directory;
|
||||
|
||||
|
||||
/**
|
||||
* This is an associative array which stores the different metadata sets we have loaded.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $cachedMetadata = array();
|
||||
|
||||
|
||||
/**
|
||||
* This constructor initializes the flatfile metadata storage handler with the
|
||||
* specified configuration. The configuration is an associative array with the following
|
||||
* possible elements:
|
||||
* - 'directory': The directory we should load metadata from. The default directory is
|
||||
* set in the 'metadatadir' configuration option in 'config.php'.
|
||||
*
|
||||
* @param array $config An associative array with the configuration for this handler.
|
||||
*/
|
||||
protected function __construct($config)
|
||||
{
|
||||
assert(is_array($config));
|
||||
|
||||
// get the configuration
|
||||
$globalConfig = SimpleSAML_Configuration::getInstance();
|
||||
|
||||
// find the path to the directory we should search for metadata in
|
||||
if (array_key_exists('directory', $config)) {
|
||||
$this->directory = $config['directory'];
|
||||
} else {
|
||||
$this->directory = $globalConfig->getString('metadatadir', 'metadata/');
|
||||
}
|
||||
|
||||
/* Resolve this directory relative to the SimpleSAMLphp directory (unless it is
|
||||
* an absolute path).
|
||||
*/
|
||||
$this->directory = $globalConfig->resolvePath($this->directory).'/';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function loads the given set of metadata from a file our metadata directory.
|
||||
* This function returns null if it is unable to locate the given set in the metadata directory.
|
||||
*
|
||||
* @param string $set The set of metadata we are loading.
|
||||
*
|
||||
* @return array An associative array with the metadata, or null if we are unable to load metadata from the given
|
||||
* file.
|
||||
* @throws Exception If the metadata set cannot be loaded.
|
||||
*/
|
||||
private function load($set)
|
||||
{
|
||||
$metadatasetfile = $this->directory.$set.'.php';
|
||||
|
||||
if (!file_exists($metadatasetfile)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$metadata = array();
|
||||
|
||||
include($metadatasetfile);
|
||||
|
||||
if (!is_array($metadata)) {
|
||||
throw new Exception('Could not load metadata set ['.$set.'] from file: '.$metadatasetfile);
|
||||
}
|
||||
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function retrieves the given set of metadata. It will return an empty array if it is
|
||||
* unable to locate it.
|
||||
*
|
||||
* @param string $set The set of metadata we are retrieving.
|
||||
*
|
||||
* @return array An associative array with the metadata. Each element in the array is an entity, and the
|
||||
* key is the entity id.
|
||||
*/
|
||||
public function getMetadataSet($set)
|
||||
{
|
||||
if (array_key_exists($set, $this->cachedMetadata)) {
|
||||
return $this->cachedMetadata[$set];
|
||||
}
|
||||
|
||||
$metadataSet = $this->load($set);
|
||||
if ($metadataSet === null) {
|
||||
$metadataSet = array();
|
||||
}
|
||||
|
||||
// add the entity id of an entry to each entry in the metadata
|
||||
foreach ($metadataSet as $entityId => &$entry) {
|
||||
if (preg_match('/__DYNAMIC(:[0-9]+)?__/', $entityId)) {
|
||||
$entry['entityid'] = $this->generateDynamicHostedEntityID($set);
|
||||
} else {
|
||||
$entry['entityid'] = $entityId;
|
||||
}
|
||||
}
|
||||
|
||||
$this->cachedMetadata[$set] = $metadataSet;
|
||||
|
||||
return $metadataSet;
|
||||
}
|
||||
|
||||
|
||||
private function generateDynamicHostedEntityID($set)
|
||||
{
|
||||
// get the configuration
|
||||
$baseurl = \SimpleSAML\Utils\HTTP::getBaseURL();
|
||||
|
||||
if ($set === 'saml20-idp-hosted') {
|
||||
return $baseurl.'saml2/idp/metadata.php';
|
||||
} elseif ($set === 'shib13-idp-hosted') {
|
||||
return $baseurl.'shib13/idp/metadata.php';
|
||||
} elseif ($set === 'wsfed-sp-hosted') {
|
||||
return 'urn:federation:'.\SimpleSAML\Utils\HTTP::getSelfHost();
|
||||
} elseif ($set === 'adfs-idp-hosted') {
|
||||
return 'urn:federation:'.\SimpleSAML\Utils\HTTP::getSelfHost().':idp';
|
||||
} else {
|
||||
throw new Exception('Can not generate dynamic EntityID for metadata of this type: ['.$set.']');
|
||||
}
|
||||
}
|
||||
}
|
||||
306
lib/SimpleSAML/Metadata/MetaDataStorageHandlerPdo.php
Executable file
306
lib/SimpleSAML/Metadata/MetaDataStorageHandlerPdo.php
Executable file
@@ -0,0 +1,306 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* Class for handling metadata files stored in a database.
|
||||
*
|
||||
* This class has been based off a previous version written by
|
||||
* mooknarf@gmail.com and patched to work with the latest version
|
||||
* of SimpleSAMLphp
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SimpleSAML_Metadata_MetaDataStorageHandlerPdo extends SimpleSAML_Metadata_MetaDataStorageSource
|
||||
{
|
||||
|
||||
/**
|
||||
* The PDO object
|
||||
*/
|
||||
private $db;
|
||||
|
||||
/**
|
||||
* Prefix to apply to the metadata table
|
||||
*/
|
||||
private $tablePrefix;
|
||||
|
||||
/**
|
||||
* This is an associative array which stores the different metadata sets we have loaded.
|
||||
*/
|
||||
private $cachedMetadata = array();
|
||||
|
||||
/**
|
||||
* All the metadata sets supported by this MetaDataStorageHandler
|
||||
*/
|
||||
public $supportedSets = array(
|
||||
'adfs-idp-hosted',
|
||||
'adfs-sp-remote',
|
||||
'saml20-idp-hosted',
|
||||
'saml20-idp-remote',
|
||||
'saml20-sp-remote',
|
||||
'shib13-idp-hosted',
|
||||
'shib13-idp-remote',
|
||||
'shib13-sp-hosted',
|
||||
'shib13-sp-remote',
|
||||
'wsfed-idp-remote',
|
||||
'wsfed-sp-hosted'
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* This constructor initializes the PDO metadata storage handler with the specified
|
||||
* configuration. The configuration is an associative array with the following
|
||||
* possible elements (set in config.php):
|
||||
* - 'usePersistentConnection': TRUE/FALSE if database connection should be persistent.
|
||||
* - 'dsn': The database connection string.
|
||||
* - 'username': Database user name
|
||||
* - 'password': Password for the database user.
|
||||
*
|
||||
* @param array $config An associative array with the configuration for this handler.
|
||||
*/
|
||||
public function __construct($config)
|
||||
{
|
||||
assert(is_array($config));
|
||||
|
||||
$this->db = SimpleSAML\Database::getInstance();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function loads the given set of metadata from a file to a configured database.
|
||||
* This function returns NULL if it is unable to locate the given set in the metadata directory.
|
||||
*
|
||||
* @param string $set The set of metadata we are loading.
|
||||
*
|
||||
* @return array $metadata Associative array with the metadata, or NULL if we are unable to load metadata from the
|
||||
* given file.
|
||||
*
|
||||
* @throws Exception If a database error occurs.
|
||||
* @throws SimpleSAML_Error_Exception If the metadata can be retrieved from the database, but cannot be decoded.
|
||||
*/
|
||||
private function load($set)
|
||||
{
|
||||
assert(is_string($set));
|
||||
|
||||
$tableName = $this->getTableName($set);
|
||||
|
||||
if (!in_array($set, $this->supportedSets, true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$stmt = $this->db->read("SELECT entity_id, entity_data FROM $tableName");
|
||||
if ($stmt->execute()) {
|
||||
$metadata = array();
|
||||
|
||||
while ($d = $stmt->fetch()) {
|
||||
$data = json_decode($d['entity_data'], true);
|
||||
if ($data === null) {
|
||||
throw new SimpleSAML_Error_Exception("Cannot decode metadata for entity '${d['entity_id']}'");
|
||||
}
|
||||
if (!array_key_exists('entityid', $data)) {
|
||||
$data['entityid'] = $d['entity_id'];
|
||||
}
|
||||
$metadata[$d['entity_id']] = $data;
|
||||
}
|
||||
|
||||
return $metadata;
|
||||
} else {
|
||||
throw new Exception('PDO metadata handler: Database error: '.var_export($this->db->getLastError(), true));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve a list of all available metadata for a given set.
|
||||
*
|
||||
* @param string $set The set we are looking for metadata in.
|
||||
*
|
||||
* @return array $metadata An associative array with all the metadata for the given set.
|
||||
*/
|
||||
public function getMetadataSet($set)
|
||||
{
|
||||
assert(is_string($set));
|
||||
|
||||
if (array_key_exists($set, $this->cachedMetadata)) {
|
||||
return $this->cachedMetadata[$set];
|
||||
}
|
||||
|
||||
$metadataSet = $this->load($set);
|
||||
if ($metadataSet === null) {
|
||||
$metadataSet = array();
|
||||
}
|
||||
|
||||
foreach ($metadataSet as $entityId => &$entry) {
|
||||
if (preg_match('/__DYNAMIC(:[0-9]+)?__/', $entityId)) {
|
||||
$entry['entityid'] = $this->generateDynamicHostedEntityID($set);
|
||||
} else {
|
||||
$entry['entityid'] = $entityId;
|
||||
}
|
||||
}
|
||||
|
||||
$this->cachedMetadata[$set] = $metadataSet;
|
||||
return $metadataSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a metadata entry.
|
||||
*
|
||||
* @param string $entityId The entityId we are looking up.
|
||||
* @param string $set The set we are looking for metadata in.
|
||||
*
|
||||
* @return array An associative array with metadata for the given entity, or NULL if we are unable to
|
||||
* locate the entity.
|
||||
*/
|
||||
public function getMetaData($entityId, $set)
|
||||
{
|
||||
assert(is_string($entityId));
|
||||
assert(is_string($set));
|
||||
|
||||
$tableName = $this->getTableName($set);
|
||||
|
||||
if (!in_array($set, $this->supportedSets, true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$stmt = $this->db->read("SELECT entity_id, entity_data FROM $tableName WHERE entity_id=:entityId", array('entityId' => $entityId));
|
||||
if ($stmt->execute()) {
|
||||
$rowCount = 0;
|
||||
$data = null;
|
||||
|
||||
while ($d = $stmt->fetch()) {
|
||||
if (++$rowCount > 1) {
|
||||
SimpleSAML\Logger::warning("Duplicate match for $entityId in set $set");
|
||||
break;
|
||||
}
|
||||
$data = json_decode($d['entity_data'], true);
|
||||
if ($data === null) {
|
||||
throw new SimpleSAML_Error_Exception("Cannot decode metadata for entity '${d['entity_id']}'");
|
||||
}
|
||||
if (!array_key_exists('entityid', $data)) {
|
||||
$data['entityid'] = $d['entity_id'];
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
} else {
|
||||
throw new Exception('PDO metadata handler: Database error: '.var_export($this->db->getLastError(), true));
|
||||
}
|
||||
}
|
||||
|
||||
private function generateDynamicHostedEntityID($set)
|
||||
{
|
||||
assert(is_string($set));
|
||||
|
||||
// get the configuration
|
||||
$baseurl = \SimpleSAML\Utils\HTTP::getBaseURL();
|
||||
|
||||
if ($set === 'saml20-idp-hosted') {
|
||||
return $baseurl.'saml2/idp/metadata.php';
|
||||
} elseif ($set === 'saml20-sp-hosted') {
|
||||
return $baseurl.'saml2/sp/metadata.php';
|
||||
} elseif ($set === 'shib13-idp-hosted') {
|
||||
return $baseurl.'shib13/idp/metadata.php';
|
||||
} elseif ($set === 'shib13-sp-hosted') {
|
||||
return $baseurl.'shib13/sp/metadata.php';
|
||||
} elseif ($set === 'wsfed-sp-hosted') {
|
||||
return 'urn:federation:'.\SimpleSAML\Utils\HTTP::getSelfHost();
|
||||
} elseif ($set === 'adfs-idp-hosted') {
|
||||
return 'urn:federation:'.\SimpleSAML\Utils\HTTP::getSelfHost().':idp';
|
||||
} else {
|
||||
throw new Exception('Can not generate dynamic EntityID for metadata of this type: ['.$set.']');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add metadata to the configured database
|
||||
*
|
||||
* @param string $index Entity ID
|
||||
* @param string $set The set to add the metadata to
|
||||
* @param array $entityData Metadata
|
||||
*
|
||||
* @return bool True/False if entry was successfully added
|
||||
*/
|
||||
public function addEntry($index, $set, $entityData)
|
||||
{
|
||||
assert(is_string($index));
|
||||
assert(is_string($set));
|
||||
assert(is_array($entityData));
|
||||
|
||||
if (!in_array($set, $this->supportedSets, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tableName = $this->getTableName($set);
|
||||
|
||||
$metadata = $this->db->read(
|
||||
"SELECT entity_id, entity_data FROM $tableName WHERE entity_id = :entity_id",
|
||||
array(
|
||||
'entity_id' => $index,
|
||||
)
|
||||
);
|
||||
|
||||
$retrivedEntityIDs = $metadata->fetch();
|
||||
|
||||
$params = array(
|
||||
'entity_id' => $index,
|
||||
'entity_data' => json_encode($entityData),
|
||||
);
|
||||
|
||||
if ($retrivedEntityIDs !== false && count($retrivedEntityIDs) > 0) {
|
||||
$rows = $this->db->write(
|
||||
"UPDATE $tableName SET entity_data = :entity_data WHERE entity_id = :entity_id",
|
||||
$params
|
||||
);
|
||||
} else {
|
||||
$rows = $this->db->write(
|
||||
"INSERT INTO $tableName (entity_id, entity_data) VALUES (:entity_id, :entity_data)",
|
||||
$params
|
||||
);
|
||||
}
|
||||
|
||||
return $rows === 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replace the -'s to an _ in table names for Metadata sets
|
||||
* since SQL does not allow a - in a table name.
|
||||
*
|
||||
* @param string $table Table
|
||||
*
|
||||
* @return string Replaced table name
|
||||
*/
|
||||
private function getTableName($table)
|
||||
{
|
||||
assert(is_string($table));
|
||||
|
||||
return $this->db->applyPrefix(str_replace("-", "_", $this->tablePrefix.$table));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the configured database
|
||||
*
|
||||
* @return int|false The number of SQL statements successfully executed, false if some error occurred.
|
||||
*/
|
||||
public function initDatabase()
|
||||
{
|
||||
$stmt = 0;
|
||||
$fine = true;
|
||||
foreach ($this->supportedSets as $set) {
|
||||
$tableName = $this->getTableName($set);
|
||||
$rows = $this->db->write(
|
||||
"CREATE TABLE IF NOT EXISTS $tableName (entity_id VARCHAR(255) PRIMARY KEY NOT NULL, entity_data ".
|
||||
"TEXT NOT NULL)"
|
||||
);
|
||||
if ($rows === 0) {
|
||||
$fine = false;
|
||||
} else {
|
||||
$stmt += $rows;
|
||||
}
|
||||
}
|
||||
if (!$fine) {
|
||||
return false;
|
||||
}
|
||||
return $stmt;
|
||||
}
|
||||
}
|
||||
286
lib/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php
Executable file
286
lib/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php
Executable file
@@ -0,0 +1,286 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* Class for handling metadata files in serialized format.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SimpleSAML_Metadata_MetaDataStorageHandlerSerialize extends SimpleSAML_Metadata_MetaDataStorageSource
|
||||
{
|
||||
|
||||
/**
|
||||
* The file extension we use for our metadata files.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const EXTENSION = '.serialized';
|
||||
|
||||
|
||||
/**
|
||||
* The base directory where metadata is stored.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $directory;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor for this metadata handler.
|
||||
*
|
||||
* Parses configuration.
|
||||
*
|
||||
* @param array $config The configuration for this metadata handler.
|
||||
*/
|
||||
public function __construct($config)
|
||||
{
|
||||
assert(is_array($config));
|
||||
|
||||
$globalConfig = SimpleSAML_Configuration::getInstance();
|
||||
|
||||
$cfgHelp = SimpleSAML_Configuration::loadFromArray($config, 'serialize metadata source');
|
||||
|
||||
$this->directory = $cfgHelp->getString('directory');
|
||||
|
||||
/* Resolve this directory relative to the SimpleSAMLphp directory (unless it is
|
||||
* an absolute path).
|
||||
*/
|
||||
$this->directory = $globalConfig->resolvePath($this->directory);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper function for retrieving the path of a metadata file.
|
||||
*
|
||||
* @param string $entityId The entity ID.
|
||||
* @param string $set The metadata set.
|
||||
*
|
||||
* @return string The path to the metadata file.
|
||||
*/
|
||||
private function getMetadataPath($entityId, $set)
|
||||
{
|
||||
assert(is_string($entityId));
|
||||
assert(is_string($set));
|
||||
|
||||
return $this->directory.'/'.rawurlencode($set).'/'.rawurlencode($entityId).self::EXTENSION;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve a list of all available metadata sets.
|
||||
*
|
||||
* @return array An array with the available sets.
|
||||
*/
|
||||
public function getMetadataSets()
|
||||
{
|
||||
$ret = array();
|
||||
|
||||
$dh = @opendir($this->directory);
|
||||
if ($dh === false) {
|
||||
SimpleSAML\Logger::warning(
|
||||
'Serialize metadata handler: Unable to open directory: '.var_export($this->directory, true)
|
||||
);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
while (($entry = readdir($dh)) !== false) {
|
||||
if ($entry[0] === '.') {
|
||||
// skip '..', '.' and hidden files
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = $this->directory.'/'.$entry;
|
||||
|
||||
if (!is_dir($path)) {
|
||||
SimpleSAML\Logger::warning(
|
||||
'Serialize metadata handler: Metadata directory contained a file where only directories should '.
|
||||
'exist: '.var_export($path, true)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$ret[] = rawurldecode($entry);
|
||||
}
|
||||
|
||||
closedir($dh);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve a list of all available metadata for a given set.
|
||||
*
|
||||
* @param string $set The set we are looking for metadata in.
|
||||
*
|
||||
* @return array An associative array with all the metadata for the given set.
|
||||
*/
|
||||
public function getMetadataSet($set)
|
||||
{
|
||||
assert(is_string($set));
|
||||
|
||||
$ret = array();
|
||||
|
||||
$dir = $this->directory.'/'.rawurlencode($set);
|
||||
if (!is_dir($dir)) {
|
||||
// probably some code asked for a metadata set which wasn't available
|
||||
return $ret;
|
||||
}
|
||||
|
||||
$dh = @opendir($dir);
|
||||
if ($dh === false) {
|
||||
SimpleSAML\Logger::warning('Serialize metadata handler: Unable to open directory: '.var_export($dir, true));
|
||||
return $ret;
|
||||
}
|
||||
|
||||
$extLen = strlen(self::EXTENSION);
|
||||
|
||||
while (($file = readdir($dh)) !== false) {
|
||||
if (strlen($file) <= $extLen) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (substr($file, -$extLen) !== self::EXTENSION) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$entityId = substr($file, 0, -$extLen);
|
||||
$entityId = rawurldecode($entityId);
|
||||
|
||||
$md = $this->getMetaData($entityId, $set);
|
||||
if ($md !== null) {
|
||||
$ret[$entityId] = $md;
|
||||
}
|
||||
}
|
||||
|
||||
closedir($dh);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve a metadata entry.
|
||||
*
|
||||
* @param string $entityId The entityId we are looking up.
|
||||
* @param string $set The set we are looking for metadata in.
|
||||
*
|
||||
* @return array An associative array with metadata for the given entity, or NULL if we are unable to
|
||||
* locate the entity.
|
||||
*/
|
||||
public function getMetaData($entityId, $set)
|
||||
{
|
||||
assert(is_string($entityId));
|
||||
assert(is_string($set));
|
||||
|
||||
$filePath = $this->getMetadataPath($entityId, $set);
|
||||
|
||||
if (!file_exists($filePath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = @file_get_contents($filePath);
|
||||
if ($data === false) {
|
||||
$error = error_get_last();
|
||||
SimpleSAML\Logger::warning(
|
||||
'Error reading file '.$filePath.': '.$error['message']
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = @unserialize($data);
|
||||
if ($data === false) {
|
||||
SimpleSAML\Logger::warning('Error unserializing file: '.$filePath);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!array_key_exists('entityid', $data)) {
|
||||
$data['entityid'] = $entityId;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save a metadata entry.
|
||||
*
|
||||
* @param string $entityId The entityId of the metadata entry.
|
||||
* @param string $set The metadata set this metadata entry belongs to.
|
||||
* @param array $metadata The metadata.
|
||||
*
|
||||
* @return boolean True if successfully saved, false otherwise.
|
||||
*/
|
||||
public function saveMetadata($entityId, $set, $metadata)
|
||||
{
|
||||
assert(is_string($entityId));
|
||||
assert(is_string($set));
|
||||
assert(is_array($metadata));
|
||||
|
||||
$filePath = $this->getMetadataPath($entityId, $set);
|
||||
$newPath = $filePath.'.new';
|
||||
|
||||
$dir = dirname($filePath);
|
||||
if (!is_dir($dir)) {
|
||||
SimpleSAML\Logger::info('Creating directory: '.$dir);
|
||||
$res = @mkdir($dir, 0777, true);
|
||||
if ($res === false) {
|
||||
$error = error_get_last();
|
||||
SimpleSAML\Logger::error('Failed to create directory '.$dir.': '.$error['message']);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$data = serialize($metadata);
|
||||
|
||||
SimpleSAML\Logger::debug('Writing: '.$newPath);
|
||||
|
||||
$res = file_put_contents($newPath, $data);
|
||||
if ($res === false) {
|
||||
$error = error_get_last();
|
||||
SimpleSAML\Logger::error('Error saving file '.$newPath.': '.$error['message']);
|
||||
return false;
|
||||
}
|
||||
|
||||
$res = rename($newPath, $filePath);
|
||||
if ($res === false) {
|
||||
$error = error_get_last();
|
||||
SimpleSAML\Logger::error('Error renaming '.$newPath.' to '.$filePath.': '.$error['message']);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete a metadata entry.
|
||||
*
|
||||
* @param string $entityId The entityId of the metadata entry.
|
||||
* @param string $set The metadata set this metadata entry belongs to.
|
||||
*/
|
||||
public function deleteMetadata($entityId, $set)
|
||||
{
|
||||
assert(is_string($entityId));
|
||||
assert(is_string($set));
|
||||
|
||||
$filePath = $this->getMetadataPath($entityId, $set);
|
||||
|
||||
if (!file_exists($filePath)) {
|
||||
SimpleSAML\Logger::warning(
|
||||
'Attempted to erase nonexistent metadata entry '.
|
||||
var_export($entityId, true).' in set '.var_export($set, true).'.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
$res = unlink($filePath);
|
||||
if ($res === false) {
|
||||
$error = error_get_last();
|
||||
SimpleSAML\Logger::error(
|
||||
'Failed to delete file '.$filePath.
|
||||
': '.$error['message']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
116
lib/SimpleSAML/Metadata/MetaDataStorageHandlerXML.php
Executable file
116
lib/SimpleSAML/Metadata/MetaDataStorageHandlerXML.php
Executable file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* This class implements a metadata source which loads metadata from XML files.
|
||||
* The XML files should be in the SAML 2.0 metadata format.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS.
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SimpleSAML_Metadata_MetaDataStorageHandlerXML extends SimpleSAML_Metadata_MetaDataStorageSource
|
||||
{
|
||||
|
||||
/**
|
||||
* This variable contains an associative array with the parsed metadata.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $metadata;
|
||||
|
||||
|
||||
/**
|
||||
* This function initializes the XML metadata source. The configuration must contain one of
|
||||
* the following options:
|
||||
* - 'file': Path to a file with the metadata. This path is relative to the SimpleSAMLphp
|
||||
* base directory.
|
||||
* - 'url': URL we should download the metadata from. This is only meant for testing.
|
||||
*
|
||||
* @param array $config The configuration for this instance of the XML metadata source.
|
||||
*
|
||||
* @throws Exception If neither the 'file' or 'url' options are defined in the configuration.
|
||||
*/
|
||||
protected function __construct($config)
|
||||
{
|
||||
$src = $srcXml = null;
|
||||
if (array_key_exists('file', $config)) {
|
||||
// get the configuration
|
||||
$globalConfig = SimpleSAML_Configuration::getInstance();
|
||||
$src = $globalConfig->resolvePath($config['file']);
|
||||
} elseif (array_key_exists('url', $config)) {
|
||||
$src = $config['url'];
|
||||
} elseif (array_key_exists('xml', $config)) {
|
||||
$srcXml = $config['xml'];
|
||||
} else {
|
||||
throw new Exception("Missing one of 'file', 'url' and 'xml' in XML metadata source configuration.");
|
||||
}
|
||||
|
||||
|
||||
$SP1x = array();
|
||||
$IdP1x = array();
|
||||
$SP20 = array();
|
||||
$IdP20 = array();
|
||||
$AAD = array();
|
||||
|
||||
if(isset($src)) {
|
||||
$entities = SimpleSAML_Metadata_SAMLParser::parseDescriptorsFile($src);
|
||||
} elseif(isset($srcXml)) {
|
||||
$entities = SimpleSAML_Metadata_SAMLParser::parseDescriptorsString($srcXml);
|
||||
} else {
|
||||
throw new Exception("Neither source file path/URI nor string data provided");
|
||||
}
|
||||
foreach ($entities as $entityId => $entity) {
|
||||
$md = $entity->getMetadata1xSP();
|
||||
if ($md !== null) {
|
||||
$SP1x[$entityId] = $md;
|
||||
}
|
||||
|
||||
$md = $entity->getMetadata1xIdP();
|
||||
if ($md !== null) {
|
||||
$IdP1x[$entityId] = $md;
|
||||
}
|
||||
|
||||
$md = $entity->getMetadata20SP();
|
||||
if ($md !== null) {
|
||||
$SP20[$entityId] = $md;
|
||||
}
|
||||
|
||||
$md = $entity->getMetadata20IdP();
|
||||
if ($md !== null) {
|
||||
$IdP20[$entityId] = $md;
|
||||
}
|
||||
|
||||
$md = $entity->getAttributeAuthorities();
|
||||
if (count($md) > 0) {
|
||||
$AAD[$entityId] = $md[0];
|
||||
}
|
||||
}
|
||||
|
||||
$this->metadata = array(
|
||||
'shib13-sp-remote' => $SP1x,
|
||||
'shib13-idp-remote' => $IdP1x,
|
||||
'saml20-sp-remote' => $SP20,
|
||||
'saml20-idp-remote' => $IdP20,
|
||||
'attributeauthority-remote' => $AAD,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function returns an associative array with metadata for all entities in the given set. The
|
||||
* key of the array is the entity id.
|
||||
*
|
||||
* @param string $set The set we want to list metadata for.
|
||||
*
|
||||
* @return array An associative array with all entities in the given set.
|
||||
*/
|
||||
public function getMetadataSet($set)
|
||||
{
|
||||
if (array_key_exists($set, $this->metadata)) {
|
||||
return $this->metadata[$set];
|
||||
}
|
||||
|
||||
// we don't have this metadata set
|
||||
return array();
|
||||
}
|
||||
}
|
||||
275
lib/SimpleSAML/Metadata/MetaDataStorageSource.php
Executable file
275
lib/SimpleSAML/Metadata/MetaDataStorageSource.php
Executable file
@@ -0,0 +1,275 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* This abstract class defines an interface for metadata storage sources.
|
||||
*
|
||||
* It also contains the overview of the different metadata storage sources.
|
||||
* A metadata storage source can be loaded by passing the configuration of it
|
||||
* to the getSource static function.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS.
|
||||
* @author Andreas Aakre Solberg, UNINETT AS.
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
abstract class SimpleSAML_Metadata_MetaDataStorageSource
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Parse array with metadata sources.
|
||||
*
|
||||
* This function accepts an array with metadata sources, and returns an array with
|
||||
* each metadata source as an object.
|
||||
*
|
||||
* @param array $sourcesConfig Array with metadata source configuration.
|
||||
*
|
||||
* @return array Parsed metadata configuration.
|
||||
*
|
||||
* @throws Exception If something is wrong in the configuration.
|
||||
*/
|
||||
public static function parseSources($sourcesConfig)
|
||||
{
|
||||
assert(is_array($sourcesConfig));
|
||||
|
||||
$sources = array();
|
||||
|
||||
foreach ($sourcesConfig as $sourceConfig) {
|
||||
if (!is_array($sourceConfig)) {
|
||||
throw new Exception("Found an element in metadata source configuration which wasn't an array.");
|
||||
}
|
||||
|
||||
$sources[] = self::getSource($sourceConfig);
|
||||
}
|
||||
|
||||
return $sources;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function creates a metadata source based on the given configuration.
|
||||
* The type of source is based on the 'type' parameter in the configuration.
|
||||
* The default type is 'flatfile'.
|
||||
*
|
||||
* @param array $sourceConfig Associative array with the configuration for this metadata source.
|
||||
*
|
||||
* @return mixed An instance of a metadata source with the given configuration.
|
||||
*
|
||||
* @throws Exception If the metadata source type is invalid.
|
||||
*/
|
||||
public static function getSource($sourceConfig)
|
||||
{
|
||||
assert(is_array($sourceConfig));
|
||||
|
||||
if (array_key_exists('type', $sourceConfig)) {
|
||||
$type = $sourceConfig['type'];
|
||||
} else {
|
||||
$type = 'flatfile';
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'flatfile':
|
||||
return new SimpleSAML_Metadata_MetaDataStorageHandlerFlatFile($sourceConfig);
|
||||
case 'xml':
|
||||
return new SimpleSAML_Metadata_MetaDataStorageHandlerXML($sourceConfig);
|
||||
case 'serialize':
|
||||
return new SimpleSAML_Metadata_MetaDataStorageHandlerSerialize($sourceConfig);
|
||||
case 'mdx':
|
||||
case 'mdq':
|
||||
return new \SimpleSAML\Metadata\Sources\MDQ($sourceConfig);
|
||||
case 'pdo':
|
||||
return new SimpleSAML_Metadata_MetaDataStorageHandlerPdo($sourceConfig);
|
||||
default:
|
||||
// metadata store from module
|
||||
try {
|
||||
$className = SimpleSAML\Module::resolveClass(
|
||||
$type,
|
||||
'MetadataStore',
|
||||
'SimpleSAML_Metadata_MetaDataStorageSource'
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
throw new SimpleSAML\Error\CriticalConfigurationError(
|
||||
"Invalid 'type' for metadata source. Cannot find store '$type'.",
|
||||
null
|
||||
);
|
||||
}
|
||||
return new $className($sourceConfig);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function attempts to generate an associative array with metadata for all entities in the
|
||||
* given set. The key of the array is the entity id.
|
||||
*
|
||||
* A subclass should override this function if it is able to easily generate this list.
|
||||
*
|
||||
* @param string $set The set we want to list metadata for.
|
||||
*
|
||||
* @return array An associative array with all entities in the given set, or an empty array if we are
|
||||
* unable to generate this list.
|
||||
*/
|
||||
public function getMetadataSet($set)
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function resolves an host/path combination to an entity id.
|
||||
*
|
||||
* This class implements this function using the getMetadataSet-function. A subclass should
|
||||
* override this function if it doesn't implement the getMetadataSet function, or if the
|
||||
* implementation of getMetadataSet is slow.
|
||||
*
|
||||
* @param string $hostPath The host/path combination we are looking up.
|
||||
* @param string $set Which set of metadata we are looking it up in.
|
||||
* @param string $type Do you want to return the metaindex or the entityID. [entityid|metaindex]
|
||||
*
|
||||
* @return string|null An entity id which matches the given host/path combination, or NULL if
|
||||
* we are unable to locate one which matches.
|
||||
*/
|
||||
public function getEntityIdFromHostPath($hostPath, $set, $type = 'entityid')
|
||||
{
|
||||
|
||||
$metadataSet = $this->getMetadataSet($set);
|
||||
if ($metadataSet === null) {
|
||||
// this metadata source does not have this metadata set
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($metadataSet as $index => $entry) {
|
||||
if (!array_key_exists('host', $entry)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($hostPath === $entry['host']) {
|
||||
if ($type === 'entityid') {
|
||||
return $entry['entityid'];
|
||||
} else {
|
||||
return $index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no entries matched, we should return null
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function will go through all the metadata, and check the DiscoHints->IPHint
|
||||
* parameter, which defines a network space (ip range) for each remote entry.
|
||||
* This function returns the entityID for any of the entities that have an
|
||||
* IP range which the IP falls within.
|
||||
*
|
||||
* @param string $set Which set of metadata we are looking it up in.
|
||||
* @param string $ip IP address
|
||||
* @param string $type Do you want to return the metaindex or the entityID. [entityid|metaindex]
|
||||
*
|
||||
* @return string The entity id of a entity which have a CIDR hint where the provided
|
||||
* IP address match.
|
||||
*/
|
||||
public function getPreferredEntityIdFromCIDRhint($set, $ip, $type = 'entityid')
|
||||
{
|
||||
|
||||
$metadataSet = $this->getMetadataSet($set);
|
||||
|
||||
foreach ($metadataSet as $index => $entry) {
|
||||
$cidrHints = array();
|
||||
|
||||
// support hint.cidr for idp discovery
|
||||
if (array_key_exists('hint.cidr', $entry) && is_array($entry['hint.cidr'])) {
|
||||
$cidrHints = $entry['hint.cidr'];
|
||||
}
|
||||
|
||||
// support discohints in idp metadata for idp discovery
|
||||
if (array_key_exists('DiscoHints', $entry)
|
||||
&& array_key_exists('IPHint', $entry['DiscoHints'])
|
||||
&& is_array($entry['DiscoHints']['IPHint'])) {
|
||||
// merge with hints derived from discohints, but prioritize hint.cidr in case it is used
|
||||
$cidrHints = array_merge($entry['DiscoHints']['IPHint'], $cidrHints);
|
||||
}
|
||||
|
||||
if (empty($cidrHints)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($cidrHints as $hint_entry) {
|
||||
if (SimpleSAML\Utils\Net::ipCIDRcheck($hint_entry, $ip)) {
|
||||
if ($type === 'entityid') {
|
||||
return $entry['entityid'];
|
||||
} else {
|
||||
return $index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no entries matched, we should return null
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
private function lookupIndexFromEntityId($entityId, $set)
|
||||
{
|
||||
assert(is_string($entityId));
|
||||
assert(isset($set));
|
||||
|
||||
$metadataSet = $this->getMetadataSet($set);
|
||||
|
||||
// check for hostname
|
||||
$currenthost = \SimpleSAML\Utils\HTTP::getSelfHost(); // sp.example.org
|
||||
|
||||
foreach ($metadataSet as $index => $entry) {
|
||||
if ($index === $entityId) {
|
||||
return $index;
|
||||
}
|
||||
if ($entry['entityid'] === $entityId) {
|
||||
if ($entry['host'] === '__DEFAULT__' || $entry['host'] === $currenthost) {
|
||||
return $index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function retrieves metadata for the given entity id in the given set of metadata.
|
||||
* It will return NULL if it is unable to locate the metadata.
|
||||
*
|
||||
* This class implements this function using the getMetadataSet-function. A subclass should
|
||||
* override this function if it doesn't implement the getMetadataSet function, or if the
|
||||
* implementation of getMetadataSet is slow.
|
||||
*
|
||||
* @param string $index The entityId or metaindex we are looking up.
|
||||
* @param string $set The set we are looking for metadata in.
|
||||
*
|
||||
* @return array An associative array with metadata for the given entity, or NULL if we are unable to
|
||||
* locate the entity.
|
||||
*/
|
||||
public function getMetaData($index, $set)
|
||||
{
|
||||
|
||||
assert(is_string($index));
|
||||
assert(isset($set));
|
||||
|
||||
$metadataSet = $this->getMetadataSet($set);
|
||||
|
||||
if (array_key_exists($index, $metadataSet)) {
|
||||
return $metadataSet[$index];
|
||||
}
|
||||
|
||||
$indexlookup = $this->lookupIndexFromEntityId($index, $set);
|
||||
if (isset($indexlookup) && array_key_exists($indexlookup, $metadataSet)) {
|
||||
return $metadataSet[$indexlookup];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
777
lib/SimpleSAML/Metadata/SAMLBuilder.php
Executable file
777
lib/SimpleSAML/Metadata/SAMLBuilder.php
Executable file
@@ -0,0 +1,777 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* Class for generating SAML 2.0 metadata from SimpleSAMLphp metadata arrays.
|
||||
*
|
||||
* This class builds SAML 2.0 metadata for an entity by examining the metadata for the entity.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SimpleSAML_Metadata_SAMLBuilder
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* The EntityDescriptor we are building.
|
||||
*
|
||||
* @var \SAML2\XML\md\EntityDescriptor
|
||||
*/
|
||||
private $entityDescriptor;
|
||||
|
||||
|
||||
/**
|
||||
* The maximum time in seconds the metadata should be cached.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
private $maxCache = null;
|
||||
|
||||
|
||||
/**
|
||||
* The maximum time in seconds since the current time that this metadata should be considered valid.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
private $maxDuration = null;
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the SAML builder.
|
||||
*
|
||||
* @param string $entityId The entity id of the entity.
|
||||
* @param double|null $maxCache The maximum time in seconds the metadata should be cached. Defaults to null
|
||||
* @param double|null $maxDuration The maximum time in seconds this metadata should be considered valid. Defaults
|
||||
* to null.
|
||||
*/
|
||||
public function __construct($entityId, $maxCache = null, $maxDuration = null)
|
||||
{
|
||||
assert(is_string($entityId));
|
||||
|
||||
$this->maxCache = $maxCache;
|
||||
$this->maxDuration = $maxDuration;
|
||||
|
||||
$this->entityDescriptor = new \SAML2\XML\md\EntityDescriptor();
|
||||
$this->entityDescriptor->entityID = $entityId;
|
||||
}
|
||||
|
||||
|
||||
private function setExpiration($metadata)
|
||||
{
|
||||
if (array_key_exists('expire', $metadata)) {
|
||||
if ($metadata['expire'] - time() < $this->maxDuration) {
|
||||
$this->maxDuration = $metadata['expire'] - time();
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->maxCache !== null) {
|
||||
$this->entityDescriptor->cacheDuration = 'PT'.$this->maxCache.'S';
|
||||
}
|
||||
if ($this->maxDuration !== null) {
|
||||
$this->entityDescriptor->validUntil = time() + $this->maxDuration;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the EntityDescriptor element which is generated for this entity.
|
||||
*
|
||||
* @return DOMElement The EntityDescriptor element of this entity.
|
||||
*/
|
||||
public function getEntityDescriptor()
|
||||
{
|
||||
$xml = $this->entityDescriptor->toXML();
|
||||
$xml->ownerDocument->appendChild($xml);
|
||||
|
||||
return $xml;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the EntityDescriptor as text.
|
||||
*
|
||||
* This function serializes this EntityDescriptor, and returns it as text.
|
||||
*
|
||||
* @param bool $formatted Whether the returned EntityDescriptor should be formatted first.
|
||||
*
|
||||
* @return string The serialized EntityDescriptor.
|
||||
*/
|
||||
public function getEntityDescriptorText($formatted = true)
|
||||
{
|
||||
assert(is_bool($formatted));
|
||||
|
||||
$xml = $this->getEntityDescriptor();
|
||||
if ($formatted) {
|
||||
SimpleSAML\Utils\XML::formatDOMElement($xml);
|
||||
}
|
||||
|
||||
return $xml->ownerDocument->saveXML();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a SecurityTokenServiceType for ADFS metadata.
|
||||
*
|
||||
* @param array $metadata The metadata with the information about the SecurityTokenServiceType.
|
||||
*/
|
||||
public function addSecurityTokenServiceType($metadata)
|
||||
{
|
||||
assert(is_array($metadata));
|
||||
assert(isset($metadata['entityid']));
|
||||
assert(isset($metadata['metadata-set']));
|
||||
|
||||
$metadata = SimpleSAML_Configuration::loadFromArray($metadata, $metadata['entityid']);
|
||||
$defaultEndpoint = $metadata->getDefaultEndpoint('SingleSignOnService');
|
||||
$e = new sspmod_adfs_SAML2_XML_fed_SecurityTokenServiceType();
|
||||
$e->Location = $defaultEndpoint['Location'];
|
||||
|
||||
$this->addCertificate($e, $metadata);
|
||||
|
||||
$this->entityDescriptor->RoleDescriptor[] = $e;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add extensions to the metadata.
|
||||
*
|
||||
* @param SimpleSAML_Configuration $metadata The metadata to get extensions from.
|
||||
* @param \SAML2\XML\md\RoleDescriptor $e Reference to the element where the Extensions element should be included.
|
||||
*/
|
||||
private function addExtensions(SimpleSAML_Configuration $metadata, \SAML2\XML\md\RoleDescriptor $e)
|
||||
{
|
||||
if ($metadata->hasValue('tags')) {
|
||||
$a = new \SAML2\XML\saml\Attribute();
|
||||
$a->Name = 'tags';
|
||||
foreach ($metadata->getArray('tags') as $tag) {
|
||||
$a->AttributeValue[] = new \SAML2\XML\saml\AttributeValue($tag);
|
||||
}
|
||||
$e->Extensions[] = $a;
|
||||
}
|
||||
|
||||
if ($metadata->hasValue('hint.cidr')) {
|
||||
$a = new \SAML2\XML\saml\Attribute();
|
||||
$a->Name = 'hint.cidr';
|
||||
foreach ($metadata->getArray('hint.cidr') as $hint) {
|
||||
$a->AttributeValue[] = new \SAML2\XML\saml\AttributeValue($hint);
|
||||
}
|
||||
$e->Extensions[] = $a;
|
||||
}
|
||||
|
||||
if ($metadata->hasValue('scope')) {
|
||||
foreach ($metadata->getArray('scope') as $scopetext) {
|
||||
$s = new \SAML2\XML\shibmd\Scope();
|
||||
$s->scope = $scopetext;
|
||||
// Check whether $ ^ ( ) * | \ are in a scope -> assume regex.
|
||||
if (1 === preg_match('/[\$\^\)\(\*\|\\\\]/', $scopetext)) {
|
||||
$s->regexp = true;
|
||||
} else {
|
||||
$s->regexp = false;
|
||||
}
|
||||
$e->Extensions[] = $s;
|
||||
}
|
||||
}
|
||||
|
||||
if ($metadata->hasValue('EntityAttributes')) {
|
||||
$ea = new \SAML2\XML\mdattr\EntityAttributes();
|
||||
foreach ($metadata->getArray('EntityAttributes') as $attributeName => $attributeValues) {
|
||||
$a = new \SAML2\XML\saml\Attribute();
|
||||
$a->Name = $attributeName;
|
||||
$a->NameFormat = 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri';
|
||||
|
||||
// Attribute names that is not URI is prefixed as this: '{nameformat}name'
|
||||
if (preg_match('/^\{(.*?)\}(.*)$/', $attributeName, $matches)) {
|
||||
$a->Name = $matches[2];
|
||||
$nameFormat = $matches[1];
|
||||
if ($nameFormat !== \SAML2\Constants::NAMEFORMAT_UNSPECIFIED) {
|
||||
$a->NameFormat = $nameFormat;
|
||||
}
|
||||
}
|
||||
foreach ($attributeValues as $attributeValue) {
|
||||
$a->AttributeValue[] = new \SAML2\XML\saml\AttributeValue($attributeValue);
|
||||
}
|
||||
$ea->children[] = $a;
|
||||
}
|
||||
$this->entityDescriptor->Extensions[] = $ea;
|
||||
}
|
||||
|
||||
if ($metadata->hasValue('RegistrationInfo')) {
|
||||
$ri = new \SAML2\XML\mdrpi\RegistrationInfo();
|
||||
foreach ($metadata->getArray('RegistrationInfo') as $riName => $riValues) {
|
||||
switch ($riName) {
|
||||
case 'authority':
|
||||
$ri->registrationAuthority = $riValues;
|
||||
break;
|
||||
case 'instant':
|
||||
$ri->registrationInstant = \SAML2\Utils::xsDateTimeToTimestamp($riValues);
|
||||
break;
|
||||
case 'policies':
|
||||
$ri->RegistrationPolicy = $riValues;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->entityDescriptor->Extensions[] = $ri;
|
||||
}
|
||||
|
||||
if ($metadata->hasValue('UIInfo')) {
|
||||
$ui = new \SAML2\XML\mdui\UIInfo();
|
||||
foreach ($metadata->getArray('UIInfo') as $uiName => $uiValues) {
|
||||
switch ($uiName) {
|
||||
case 'DisplayName':
|
||||
$ui->DisplayName = $uiValues;
|
||||
break;
|
||||
case 'Description':
|
||||
$ui->Description = $uiValues;
|
||||
break;
|
||||
case 'InformationURL':
|
||||
$ui->InformationURL = $uiValues;
|
||||
break;
|
||||
case 'PrivacyStatementURL':
|
||||
$ui->PrivacyStatementURL = $uiValues;
|
||||
break;
|
||||
case 'Keywords':
|
||||
foreach ($uiValues as $lang => $keywords) {
|
||||
$uiItem = new \SAML2\XML\mdui\Keywords();
|
||||
$uiItem->lang = $lang;
|
||||
$uiItem->Keywords = $keywords;
|
||||
$ui->Keywords[] = $uiItem;
|
||||
}
|
||||
break;
|
||||
case 'Logo':
|
||||
foreach ($uiValues as $logo) {
|
||||
$uiItem = new \SAML2\XML\mdui\Logo();
|
||||
$uiItem->url = $logo['url'];
|
||||
$uiItem->width = $logo['width'];
|
||||
$uiItem->height = $logo['height'];
|
||||
if (isset($logo['lang'])) {
|
||||
$uiItem->lang = $logo['lang'];
|
||||
}
|
||||
$ui->Logo[] = $uiItem;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
$e->Extensions[] = $ui;
|
||||
}
|
||||
|
||||
if ($metadata->hasValue('DiscoHints')) {
|
||||
$dh = new \SAML2\XML\mdui\DiscoHints();
|
||||
foreach ($metadata->getArray('DiscoHints') as $dhName => $dhValues) {
|
||||
switch ($dhName) {
|
||||
case 'IPHint':
|
||||
$dh->IPHint = $dhValues;
|
||||
break;
|
||||
case 'DomainHint':
|
||||
$dh->DomainHint = $dhValues;
|
||||
break;
|
||||
case 'GeolocationHint':
|
||||
$dh->GeolocationHint = $dhValues;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$e->Extensions[] = $dh;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add an Organization element based on data passed as parameters
|
||||
*
|
||||
* @param array $orgName An array with the localized OrganizationName.
|
||||
* @param array $orgDisplayName An array with the localized OrganizationDisplayName.
|
||||
* @param array $orgURL An array with the localized OrganizationURL.
|
||||
*/
|
||||
public function addOrganization(array $orgName, array $orgDisplayName, array $orgURL)
|
||||
{
|
||||
$org = new \SAML2\XML\md\Organization();
|
||||
|
||||
$org->OrganizationName = $orgName;
|
||||
$org->OrganizationDisplayName = $orgDisplayName;
|
||||
$org->OrganizationURL = $orgURL;
|
||||
|
||||
$this->entityDescriptor->Organization = $org;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add an Organization element based on metadata array.
|
||||
*
|
||||
* @param array $metadata The metadata we should extract the organization information from.
|
||||
*/
|
||||
public function addOrganizationInfo(array $metadata)
|
||||
{
|
||||
if (empty($metadata['OrganizationName']) ||
|
||||
empty($metadata['OrganizationDisplayName']) ||
|
||||
empty($metadata['OrganizationURL'])
|
||||
) {
|
||||
// empty or incomplete organization information
|
||||
return;
|
||||
}
|
||||
|
||||
$orgName = SimpleSAML\Utils\Arrays::arrayize($metadata['OrganizationName'], 'en');
|
||||
$orgDisplayName = SimpleSAML\Utils\Arrays::arrayize($metadata['OrganizationDisplayName'], 'en');
|
||||
$orgURL = SimpleSAML\Utils\Arrays::arrayize($metadata['OrganizationURL'], 'en');
|
||||
|
||||
$this->addOrganization($orgName, $orgDisplayName, $orgURL);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a list of endpoints to metadata.
|
||||
*
|
||||
* @param array $endpoints The endpoints.
|
||||
* @param bool $indexed Whether the endpoints should be indexed.
|
||||
*
|
||||
* @return array An array of endpoint objects, either \SAML2\XML\md\EndpointType or \SAML2\XML\md\IndexedEndpointType.
|
||||
*/
|
||||
private static function createEndpoints(array $endpoints, $indexed)
|
||||
{
|
||||
assert(is_bool($indexed));
|
||||
|
||||
$ret = array();
|
||||
|
||||
foreach ($endpoints as &$ep) {
|
||||
if ($indexed) {
|
||||
$t = new \SAML2\XML\md\IndexedEndpointType();
|
||||
} else {
|
||||
$t = new \SAML2\XML\md\EndpointType();
|
||||
}
|
||||
|
||||
$t->Binding = $ep['Binding'];
|
||||
$t->Location = $ep['Location'];
|
||||
if (isset($ep['ResponseLocation'])) {
|
||||
$t->ResponseLocation = $ep['ResponseLocation'];
|
||||
}
|
||||
if (isset($ep['hoksso:ProtocolBinding'])) {
|
||||
$t->setAttributeNS(
|
||||
\SAML2\Constants::NS_HOK,
|
||||
'hoksso:ProtocolBinding',
|
||||
\SAML2\Constants::BINDING_HTTP_REDIRECT
|
||||
);
|
||||
}
|
||||
|
||||
if ($indexed) {
|
||||
if (!isset($ep['index'])) {
|
||||
// Find the maximum index
|
||||
$maxIndex = -1;
|
||||
foreach ($endpoints as $ep) {
|
||||
if (!isset($ep['index'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($ep['index'] > $maxIndex) {
|
||||
$maxIndex = $ep['index'];
|
||||
}
|
||||
}
|
||||
|
||||
$ep['index'] = $maxIndex + 1;
|
||||
}
|
||||
|
||||
$t->index = $ep['index'];
|
||||
}
|
||||
|
||||
$ret[] = $t;
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add an AttributeConsumingService element to the metadata.
|
||||
*
|
||||
* @param \SAML2\XML\md\SPSSODescriptor $spDesc The SPSSODescriptor element.
|
||||
* @param SimpleSAML_Configuration $metadata The metadata.
|
||||
*/
|
||||
private function addAttributeConsumingService(
|
||||
\SAML2\XML\md\SPSSODescriptor $spDesc,
|
||||
SimpleSAML_Configuration $metadata
|
||||
) {
|
||||
$attributes = $metadata->getArray('attributes', array());
|
||||
$name = $metadata->getLocalizedString('name', null);
|
||||
|
||||
if ($name === null || count($attributes) == 0) {
|
||||
// we cannot add an AttributeConsumingService without name and attributes
|
||||
return;
|
||||
}
|
||||
|
||||
$attributesrequired = $metadata->getArray('attributes.required', array());
|
||||
|
||||
/*
|
||||
* Add an AttributeConsumingService element with information as name and description and list
|
||||
* of requested attributes
|
||||
*/
|
||||
$attributeconsumer = new \SAML2\XML\md\AttributeConsumingService();
|
||||
|
||||
$attributeconsumer->index = $metadata->getInteger('attributes.index', 0);
|
||||
|
||||
if ($metadata->hasValue('attributes.isDefault')) {
|
||||
$attributeconsumer->isDefault = $metadata->getBoolean('attributes.isDefault', false);
|
||||
}
|
||||
|
||||
$attributeconsumer->ServiceName = $name;
|
||||
$attributeconsumer->ServiceDescription = $metadata->getLocalizedString('description', array());
|
||||
|
||||
$nameFormat = $metadata->getString('attributes.NameFormat', \SAML2\Constants::NAMEFORMAT_UNSPECIFIED);
|
||||
foreach ($attributes as $friendlyName => $attribute) {
|
||||
$t = new \SAML2\XML\md\RequestedAttribute();
|
||||
$t->Name = $attribute;
|
||||
if (!is_int($friendlyName)) {
|
||||
$t->FriendlyName = $friendlyName;
|
||||
}
|
||||
if ($nameFormat !== \SAML2\Constants::NAMEFORMAT_UNSPECIFIED) {
|
||||
$t->NameFormat = $nameFormat;
|
||||
}
|
||||
if (in_array($attribute, $attributesrequired, true)) {
|
||||
$t->isRequired = true;
|
||||
}
|
||||
$attributeconsumer->RequestedAttribute[] = $t;
|
||||
}
|
||||
|
||||
$spDesc->AttributeConsumingService[] = $attributeconsumer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a specific type of metadata to an entity.
|
||||
*
|
||||
* @param string $set The metadata set this metadata comes from.
|
||||
* @param array $metadata The metadata.
|
||||
*/
|
||||
public function addMetadata($set, $metadata)
|
||||
{
|
||||
assert(is_string($set));
|
||||
assert(is_array($metadata));
|
||||
|
||||
$this->setExpiration($metadata);
|
||||
|
||||
switch ($set) {
|
||||
case 'saml20-sp-remote':
|
||||
$this->addMetadataSP20($metadata);
|
||||
break;
|
||||
case 'saml20-idp-remote':
|
||||
$this->addMetadataIdP20($metadata);
|
||||
break;
|
||||
case 'shib13-sp-remote':
|
||||
$this->addMetadataSP11($metadata);
|
||||
break;
|
||||
case 'shib13-idp-remote':
|
||||
$this->addMetadataIdP11($metadata);
|
||||
break;
|
||||
case 'attributeauthority-remote':
|
||||
$this->addAttributeAuthority($metadata);
|
||||
break;
|
||||
default:
|
||||
SimpleSAML\Logger::warning('Unable to generate metadata for unknown type \''.$set.'\'.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add SAML 2.0 SP metadata.
|
||||
*
|
||||
* @param array $metadata The metadata.
|
||||
* @param array $protocols The protocols supported. Defaults to \SAML2\Constants::NS_SAMLP.
|
||||
*/
|
||||
public function addMetadataSP20($metadata, $protocols = array(\SAML2\Constants::NS_SAMLP))
|
||||
{
|
||||
assert(is_array($metadata));
|
||||
assert(is_array($protocols));
|
||||
assert(isset($metadata['entityid']));
|
||||
assert(isset($metadata['metadata-set']));
|
||||
|
||||
$metadata = SimpleSAML_Configuration::loadFromArray($metadata, $metadata['entityid']);
|
||||
|
||||
$e = new \SAML2\XML\md\SPSSODescriptor();
|
||||
$e->protocolSupportEnumeration = $protocols;
|
||||
|
||||
if ($metadata->hasValue('saml20.sign.assertion')) {
|
||||
$e->WantAssertionsSigned = $metadata->getBoolean('saml20.sign.assertion');
|
||||
}
|
||||
|
||||
if ($metadata->hasValue('redirect.validate')) {
|
||||
$e->AuthnRequestsSigned = $metadata->getBoolean('redirect.validate');
|
||||
} elseif ($metadata->hasValue('validate.authnrequest')) {
|
||||
$e->AuthnRequestsSigned = $metadata->getBoolean('validate.authnrequest');
|
||||
}
|
||||
|
||||
$this->addExtensions($metadata, $e);
|
||||
|
||||
$this->addCertificate($e, $metadata);
|
||||
|
||||
$e->SingleLogoutService = self::createEndpoints($metadata->getEndpoints('SingleLogoutService'), false);
|
||||
|
||||
$e->NameIDFormat = $metadata->getArrayizeString('NameIDFormat', array());
|
||||
|
||||
$endpoints = $metadata->getEndpoints('AssertionConsumerService');
|
||||
foreach ($metadata->getArrayizeString('AssertionConsumerService.artifact', array()) as $acs) {
|
||||
$endpoints[] = array(
|
||||
'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact',
|
||||
'Location' => $acs,
|
||||
);
|
||||
}
|
||||
$e->AssertionConsumerService = self::createEndpoints($endpoints, true);
|
||||
|
||||
$this->addAttributeConsumingService($e, $metadata);
|
||||
|
||||
$this->entityDescriptor->RoleDescriptor[] = $e;
|
||||
|
||||
foreach ($metadata->getArray('contacts', array()) as $contact) {
|
||||
if (array_key_exists('contactType', $contact) && array_key_exists('emailAddress', $contact)) {
|
||||
$this->addContact($contact['contactType'], \SimpleSAML\Utils\Config\Metadata::getContact($contact));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add metadata of a SAML 2.0 identity provider.
|
||||
*
|
||||
* @param array $metadata The metadata.
|
||||
*/
|
||||
public function addMetadataIdP20($metadata)
|
||||
{
|
||||
assert(is_array($metadata));
|
||||
assert(isset($metadata['entityid']));
|
||||
assert(isset($metadata['metadata-set']));
|
||||
|
||||
$metadata = SimpleSAML_Configuration::loadFromArray($metadata, $metadata['entityid']);
|
||||
|
||||
$e = new \SAML2\XML\md\IDPSSODescriptor();
|
||||
$e->protocolSupportEnumeration[] = 'urn:oasis:names:tc:SAML:2.0:protocol';
|
||||
|
||||
if ($metadata->hasValue('sign.authnrequest')) {
|
||||
$e->WantAuthnRequestsSigned = $metadata->getBoolean('sign.authnrequest');
|
||||
} elseif ($metadata->hasValue('redirect.sign')) {
|
||||
$e->WantAuthnRequestsSigned = $metadata->getBoolean('redirect.sign');
|
||||
}
|
||||
|
||||
$this->addExtensions($metadata, $e);
|
||||
|
||||
$this->addCertificate($e, $metadata);
|
||||
|
||||
if ($metadata->hasValue('ArtifactResolutionService')) {
|
||||
$e->ArtifactResolutionService = self::createEndpoints(
|
||||
$metadata->getEndpoints('ArtifactResolutionService'),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
$e->SingleLogoutService = self::createEndpoints($metadata->getEndpoints('SingleLogoutService'), false);
|
||||
|
||||
$e->NameIDFormat = $metadata->getArrayizeString('NameIDFormat', array());
|
||||
|
||||
$e->SingleSignOnService = self::createEndpoints($metadata->getEndpoints('SingleSignOnService'), false);
|
||||
|
||||
$this->entityDescriptor->RoleDescriptor[] = $e;
|
||||
|
||||
foreach ($metadata->getArray('contacts', array()) as $contact) {
|
||||
if (array_key_exists('contactType', $contact) && array_key_exists('emailAddress', $contact)) {
|
||||
$this->addContact($contact['contactType'], \SimpleSAML\Utils\Config\Metadata::getContact($contact));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add metadata of a SAML 1.1 service provider.
|
||||
*
|
||||
* @param array $metadata The metadata.
|
||||
*/
|
||||
public function addMetadataSP11($metadata)
|
||||
{
|
||||
assert(is_array($metadata));
|
||||
assert(isset($metadata['entityid']));
|
||||
assert(isset($metadata['metadata-set']));
|
||||
|
||||
$metadata = SimpleSAML_Configuration::loadFromArray($metadata, $metadata['entityid']);
|
||||
|
||||
$e = new \SAML2\XML\md\SPSSODescriptor();
|
||||
$e->protocolSupportEnumeration[] = 'urn:oasis:names:tc:SAML:1.1:protocol';
|
||||
|
||||
$this->addCertificate($e, $metadata);
|
||||
|
||||
$e->NameIDFormat = $metadata->getArrayizeString('NameIDFormat', array());
|
||||
|
||||
$endpoints = $metadata->getEndpoints('AssertionConsumerService');
|
||||
foreach ($metadata->getArrayizeString('AssertionConsumerService.artifact', array()) as $acs) {
|
||||
$endpoints[] = array(
|
||||
'Binding' => 'urn:oasis:names:tc:SAML:1.0:profiles:artifact-01',
|
||||
'Location' => $acs,
|
||||
);
|
||||
}
|
||||
$e->AssertionConsumerService = self::createEndpoints($endpoints, true);
|
||||
|
||||
$this->addAttributeConsumingService($e, $metadata);
|
||||
|
||||
$this->entityDescriptor->RoleDescriptor[] = $e;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add metadata of a SAML 1.1 identity provider.
|
||||
*
|
||||
* @param array $metadata The metadata.
|
||||
*/
|
||||
public function addMetadataIdP11($metadata)
|
||||
{
|
||||
assert(is_array($metadata));
|
||||
assert(isset($metadata['entityid']));
|
||||
assert(isset($metadata['metadata-set']));
|
||||
|
||||
$metadata = SimpleSAML_Configuration::loadFromArray($metadata, $metadata['entityid']);
|
||||
|
||||
$e = new \SAML2\XML\md\IDPSSODescriptor();
|
||||
$e->protocolSupportEnumeration[] = 'urn:oasis:names:tc:SAML:1.1:protocol';
|
||||
$e->protocolSupportEnumeration[] = 'urn:mace:shibboleth:1.0';
|
||||
|
||||
$this->addCertificate($e, $metadata);
|
||||
|
||||
$e->NameIDFormat = $metadata->getArrayizeString('NameIDFormat', array());
|
||||
|
||||
$e->SingleSignOnService = self::createEndpoints($metadata->getEndpoints('SingleSignOnService'), false);
|
||||
|
||||
$this->entityDescriptor->RoleDescriptor[] = $e;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add metadata of a SAML attribute authority.
|
||||
*
|
||||
* @param array $metadata The AttributeAuthorityDescriptor, in the format returned by
|
||||
* SimpleSAML_Metadata_SAMLParser.
|
||||
*/
|
||||
public function addAttributeAuthority(array $metadata)
|
||||
{
|
||||
assert(is_array($metadata));
|
||||
assert(isset($metadata['entityid']));
|
||||
assert(isset($metadata['metadata-set']));
|
||||
|
||||
$metadata = SimpleSAML_Configuration::loadFromArray($metadata, $metadata['entityid']);
|
||||
|
||||
$e = new \SAML2\XML\md\AttributeAuthorityDescriptor();
|
||||
$e->protocolSupportEnumeration = $metadata->getArray('protocols', array(\SAML2\Constants::NS_SAMLP));
|
||||
|
||||
$this->addExtensions($metadata, $e);
|
||||
$this->addCertificate($e, $metadata);
|
||||
|
||||
$e->AttributeService = self::createEndpoints($metadata->getEndpoints('AttributeService'), false);
|
||||
$e->AssertionIDRequestService = self::createEndpoints(
|
||||
$metadata->getEndpoints('AssertionIDRequestService'),
|
||||
false
|
||||
);
|
||||
|
||||
$e->NameIDFormat = $metadata->getArrayizeString('NameIDFormat', array());
|
||||
|
||||
$this->entityDescriptor->RoleDescriptor[] = $e;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add contact information.
|
||||
*
|
||||
* Accepts a contact type, and a contact array that must be previously sanitized.
|
||||
*
|
||||
* WARNING: This function will change its signature and no longer parse a 'name' element.
|
||||
*
|
||||
* @param string $type The type of contact. Deprecated.
|
||||
* @param array $details The details about the contact.
|
||||
*
|
||||
* @todo Change the signature to remove $type.
|
||||
* @todo Remove the capability to pass a name and parse it inside the method.
|
||||
*/
|
||||
public function addContact($type, $details)
|
||||
{
|
||||
assert(is_string($type));
|
||||
assert(is_array($details));
|
||||
assert(in_array($type, array('technical', 'support', 'administrative', 'billing', 'other'), true));
|
||||
|
||||
// TODO: remove this check as soon as getContact() is called always before calling this function
|
||||
$details = \SimpleSAML\Utils\Config\Metadata::getContact($details);
|
||||
|
||||
$e = new \SAML2\XML\md\ContactPerson();
|
||||
$e->contactType = $type;
|
||||
|
||||
if (!empty($details['attributes'])) {
|
||||
$e->ContactPersonAttributes = $details['attributes'];
|
||||
}
|
||||
|
||||
if (isset($details['company'])) {
|
||||
$e->Company = $details['company'];
|
||||
}
|
||||
if (isset($details['givenName'])) {
|
||||
$e->GivenName = $details['givenName'];
|
||||
}
|
||||
if (isset($details['surName'])) {
|
||||
$e->SurName = $details['surName'];
|
||||
}
|
||||
|
||||
if (isset($details['emailAddress'])) {
|
||||
$eas = $details['emailAddress'];
|
||||
if (!is_array($eas)) {
|
||||
$eas = array($eas);
|
||||
}
|
||||
foreach ($eas as $ea) {
|
||||
$e->EmailAddress[] = $ea;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($details['telephoneNumber'])) {
|
||||
$tlfNrs = $details['telephoneNumber'];
|
||||
if (!is_array($tlfNrs)) {
|
||||
$tlfNrs = array($tlfNrs);
|
||||
}
|
||||
foreach ($tlfNrs as $tlfNr) {
|
||||
$e->TelephoneNumber[] = $tlfNr;
|
||||
}
|
||||
}
|
||||
|
||||
$this->entityDescriptor->ContactPerson[] = $e;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a KeyDescriptor with an X509 certificate.
|
||||
*
|
||||
* @param \SAML2\XML\md\RoleDescriptor $rd The RoleDescriptor the certificate should be added to.
|
||||
* @param string $use The value of the 'use' attribute.
|
||||
* @param string $x509data The certificate data.
|
||||
*/
|
||||
private function addX509KeyDescriptor(\SAML2\XML\md\RoleDescriptor $rd, $use, $x509data)
|
||||
{
|
||||
assert(in_array($use, array('encryption', 'signing'), true));
|
||||
assert(is_string($x509data));
|
||||
|
||||
$keyDescriptor = \SAML2\Utils::createKeyDescriptor($x509data);
|
||||
$keyDescriptor->use = $use;
|
||||
$rd->KeyDescriptor[] = $keyDescriptor;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a certificate.
|
||||
*
|
||||
* Helper function for adding a certificate to the metadata.
|
||||
*
|
||||
* @param \SAML2\XML\md\RoleDescriptor $rd The RoleDescriptor the certificate should be added to.
|
||||
* @param SimpleSAML_Configuration $metadata The metadata of the entity.
|
||||
*/
|
||||
private function addCertificate(\SAML2\XML\md\RoleDescriptor $rd, SimpleSAML_Configuration $metadata)
|
||||
{
|
||||
$keys = $metadata->getPublicKeys();
|
||||
foreach ($keys as $key) {
|
||||
if ($key['type'] !== 'X509Certificate') {
|
||||
continue;
|
||||
}
|
||||
if (!isset($key['signing']) || $key['signing'] === true) {
|
||||
$this->addX509KeyDescriptor($rd, 'signing', $key['X509Certificate']);
|
||||
}
|
||||
if (!isset($key['encryption']) || $key['encryption'] === true) {
|
||||
$this->addX509KeyDescriptor($rd, 'encryption', $key['X509Certificate']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($metadata->hasValue('https.certData')) {
|
||||
$this->addX509KeyDescriptor($rd, 'signing', $metadata->getString('https.certData'));
|
||||
}
|
||||
}
|
||||
}
|
||||
1478
lib/SimpleSAML/Metadata/SAMLParser.php
Executable file
1478
lib/SimpleSAML/Metadata/SAMLParser.php
Executable file
File diff suppressed because it is too large
Load Diff
283
lib/SimpleSAML/Metadata/Signer.php
Executable file
283
lib/SimpleSAML/Metadata/Signer.php
Executable file
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* This class implements a helper function for signing of metadata.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS.
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SimpleSAML_Metadata_Signer
|
||||
{
|
||||
|
||||
/**
|
||||
* This functions finds what key & certificate files should be used to sign the metadata
|
||||
* for the given entity.
|
||||
*
|
||||
* @param SimpleSAML_Configuration $config Our SimpleSAML_Configuration instance.
|
||||
* @param array $entityMetadata The metadata of the entity.
|
||||
* @param string $type A string which describes the type entity this is, e.g. 'SAML 2 IdP' or
|
||||
* 'Shib 1.3 SP'.
|
||||
*
|
||||
* @return array An associative array with the keys 'privatekey', 'certificate', and optionally 'privatekey_pass'.
|
||||
* @throws Exception If the key and certificate used to sign is unknown.
|
||||
*/
|
||||
private static function findKeyCert($config, $entityMetadata, $type)
|
||||
{
|
||||
// first we look for metadata.privatekey and metadata.certificate in the metadata
|
||||
if (array_key_exists('metadata.sign.privatekey', $entityMetadata)
|
||||
|| array_key_exists('metadata.sign.certificate', $entityMetadata)
|
||||
) {
|
||||
if (!array_key_exists('metadata.sign.privatekey', $entityMetadata)
|
||||
|| !array_key_exists('metadata.sign.certificate', $entityMetadata)
|
||||
) {
|
||||
throw new Exception(
|
||||
'Missing either the "metadata.sign.privatekey" or the'.
|
||||
' "metadata.sign.certificate" configuration option in the metadata for'.
|
||||
' the '.$type.' "'.$entityMetadata['entityid'].'". If one of'.
|
||||
' these options is specified, then the other must also be specified.'
|
||||
);
|
||||
}
|
||||
|
||||
$ret = array(
|
||||
'privatekey' => $entityMetadata['metadata.sign.privatekey'],
|
||||
'certificate' => $entityMetadata['metadata.sign.certificate']
|
||||
);
|
||||
|
||||
if (array_key_exists('metadata.sign.privatekey_pass', $entityMetadata)) {
|
||||
$ret['privatekey_pass'] = $entityMetadata['metadata.sign.privatekey_pass'];
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
// then we look for default values in the global configuration
|
||||
$privatekey = $config->getString('metadata.sign.privatekey', null);
|
||||
$certificate = $config->getString('metadata.sign.certificate', null);
|
||||
if ($privatekey !== null || $certificate !== null) {
|
||||
if ($privatekey === null || $certificate === null) {
|
||||
throw new Exception(
|
||||
'Missing either the "metadata.sign.privatekey" or the'.
|
||||
' "metadata.sign.certificate" configuration option in the global'.
|
||||
' configuration. If one of these options is specified, then the other'.
|
||||
' must also be specified.'
|
||||
);
|
||||
}
|
||||
$ret = array('privatekey' => $privatekey, 'certificate' => $certificate);
|
||||
|
||||
$privatekey_pass = $config->getString('metadata.sign.privatekey_pass', null);
|
||||
if ($privatekey_pass !== null) {
|
||||
$ret['privatekey_pass'] = $privatekey_pass;
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
// as a last resort we attempt to use the privatekey and certificate option from the metadata
|
||||
if (array_key_exists('privatekey', $entityMetadata)
|
||||
|| array_key_exists('certificate', $entityMetadata)
|
||||
) {
|
||||
if (!array_key_exists('privatekey', $entityMetadata)
|
||||
|| !array_key_exists('certificate', $entityMetadata)
|
||||
) {
|
||||
throw new Exception(
|
||||
'Both the "privatekey" and the "certificate" option must'.
|
||||
' be set in the metadata for the '.$type.' "'.
|
||||
$entityMetadata['entityid'].'" before it is possible to sign metadata'.
|
||||
' from this entity.'
|
||||
);
|
||||
}
|
||||
|
||||
$ret = array(
|
||||
'privatekey' => $entityMetadata['privatekey'],
|
||||
'certificate' => $entityMetadata['certificate']
|
||||
);
|
||||
|
||||
if (array_key_exists('privatekey_pass', $entityMetadata)) {
|
||||
$ret['privatekey_pass'] = $entityMetadata['privatekey_pass'];
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
throw new Exception(
|
||||
'Could not find what key & certificate should be used to sign the metadata'.
|
||||
' for the '.$type.' "'.$entityMetadata['entityid'].'".'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine whether metadata signing is enabled for the given metadata.
|
||||
*
|
||||
* @param SimpleSAML_Configuration $config Our SimpleSAML_Configuration instance.
|
||||
* @param array $entityMetadata The metadata of the entity.
|
||||
* @param string $type A string which describes the type entity this is, e.g. 'SAML 2 IdP' or
|
||||
* 'Shib 1.3 SP'.
|
||||
*
|
||||
* @return boolean True if metadata signing is enabled, false otherwise.
|
||||
* @throws Exception If the value of the 'metadata.sign.enable' option is not a boolean.
|
||||
*/
|
||||
private static function isMetadataSigningEnabled($config, $entityMetadata, $type)
|
||||
{
|
||||
// first check the metadata for the entity
|
||||
if (array_key_exists('metadata.sign.enable', $entityMetadata)) {
|
||||
if (!is_bool($entityMetadata['metadata.sign.enable'])) {
|
||||
throw new Exception(
|
||||
'Invalid value for the "metadata.sign.enable" configuration option for'.
|
||||
' the '.$type.' "'.$entityMetadata['entityid'].'". This option'.
|
||||
' should be a boolean.'
|
||||
);
|
||||
}
|
||||
|
||||
return $entityMetadata['metadata.sign.enable'];
|
||||
}
|
||||
|
||||
$enabled = $config->getBoolean('metadata.sign.enable', false);
|
||||
|
||||
return $enabled;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine the signature and digest algorithms to use when signing metadata.
|
||||
*
|
||||
* This method will look for the 'metadata.sign.algorithm' key in the $entityMetadata array, or look for such
|
||||
* a configuration option in the $config object.
|
||||
*
|
||||
* @param SimpleSAML_Configuration $config The global configuration.
|
||||
* @param array $entityMetadata An array containing the metadata related to this entity.
|
||||
* @param string $type A string describing the type of entity. E.g. 'SAML 2 IdP' or 'Shib 1.3 SP'.
|
||||
*
|
||||
* @return array An array with two keys, 'algorithm' and 'digest', corresponding to the signature and digest
|
||||
* algorithms to use, respectively.
|
||||
*
|
||||
* @throws \SimpleSAML\Error\CriticalConfigurationError
|
||||
*/
|
||||
private static function getMetadataSigningAlgorithm($config, $entityMetadata, $type)
|
||||
{
|
||||
// configure the algorithm to use
|
||||
if (array_key_exists('metadata.sign.algorithm', $entityMetadata)) {
|
||||
if (!is_string($entityMetadata['metadata.sign.algorithm'])) {
|
||||
throw new \SimpleSAML\Error\CriticalConfigurationError(
|
||||
"Invalid value for the 'metadata.sign.algorithm' configuration option for the ".$type.
|
||||
"'".$entityMetadata['entityid']."'. This option has restricted values"
|
||||
);
|
||||
}
|
||||
$alg = $entityMetadata['metadata.sign.algorithm'];
|
||||
} else {
|
||||
$alg = $config->getString('metadata.sign.algorithm', XMLSecurityKey::RSA_SHA256);
|
||||
}
|
||||
|
||||
$supported_algs = array(
|
||||
XMLSecurityKey::RSA_SHA1,
|
||||
XMLSecurityKey::RSA_SHA256,
|
||||
XMLSecurityKey::RSA_SHA384,
|
||||
XMLSecurityKey::RSA_SHA512,
|
||||
);
|
||||
|
||||
if (!in_array($alg, $supported_algs, true)) {
|
||||
throw new \SimpleSAML\Error\CriticalConfigurationError("Unknown signature algorithm '$alg'");
|
||||
}
|
||||
|
||||
switch ($alg) {
|
||||
case XMLSecurityKey::RSA_SHA256:
|
||||
$digest = XMLSecurityDSig::SHA256;
|
||||
break;
|
||||
case XMLSecurityKey::RSA_SHA384:
|
||||
$digest = XMLSecurityDSig::SHA384;
|
||||
break;
|
||||
case XMLSecurityKey::RSA_SHA512:
|
||||
$digest = XMLSecurityDSig::SHA512;
|
||||
break;
|
||||
default:
|
||||
$digest = XMLSecurityDSig::SHA1;
|
||||
}
|
||||
|
||||
return array(
|
||||
'algorithm' => $alg,
|
||||
'digest' => $digest,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Signs the given metadata if metadata signing is enabled.
|
||||
*
|
||||
* @param string $metadataString A string with the metadata.
|
||||
* @param array $entityMetadata The metadata of the entity.
|
||||
* @param string $type A string which describes the type entity this is, e.g. 'SAML 2 IdP' or 'Shib 1.3 SP'.
|
||||
*
|
||||
* @return string The $metadataString with the signature embedded.
|
||||
* @throws Exception If the certificate or private key cannot be loaded, or the metadata doesn't parse properly.
|
||||
*/
|
||||
public static function sign($metadataString, $entityMetadata, $type)
|
||||
{
|
||||
$config = SimpleSAML_Configuration::getInstance();
|
||||
|
||||
// check if metadata signing is enabled
|
||||
if (!self::isMetadataSigningEnabled($config, $entityMetadata, $type)) {
|
||||
return $metadataString;
|
||||
}
|
||||
|
||||
// find the key & certificate which should be used to sign the metadata
|
||||
$keyCertFiles = self::findKeyCert($config, $entityMetadata, $type);
|
||||
|
||||
$keyFile = \SimpleSAML\Utils\Config::getCertPath($keyCertFiles['privatekey']);
|
||||
if (!file_exists($keyFile)) {
|
||||
throw new Exception('Could not find private key file ['.$keyFile.'], which is needed to sign the metadata');
|
||||
}
|
||||
$keyData = file_get_contents($keyFile);
|
||||
|
||||
$certFile = \SimpleSAML\Utils\Config::getCertPath($keyCertFiles['certificate']);
|
||||
if (!file_exists($certFile)) {
|
||||
throw new Exception(
|
||||
'Could not find certificate file ['.$certFile.'], which is needed to sign the metadata'
|
||||
);
|
||||
}
|
||||
$certData = file_get_contents($certFile);
|
||||
|
||||
|
||||
// convert the metadata to a DOM tree
|
||||
try {
|
||||
$xml = \SAML2\DOMDocumentFactory::fromString($metadataString);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('Error parsing self-generated metadata.');
|
||||
}
|
||||
|
||||
$signature_cf = self::getMetadataSigningAlgorithm($config, $entityMetadata, $type);
|
||||
|
||||
// load the private key
|
||||
$objKey = new XMLSecurityKey($signature_cf['algorithm'], array('type' => 'private'));
|
||||
if (array_key_exists('privatekey_pass', $keyCertFiles)) {
|
||||
$objKey->passphrase = $keyCertFiles['privatekey_pass'];
|
||||
}
|
||||
$objKey->loadKey($keyData, false);
|
||||
|
||||
// get the EntityDescriptor node we should sign
|
||||
$rootNode = $xml->firstChild;
|
||||
|
||||
// sign the metadata with our private key
|
||||
$objXMLSecDSig = new XMLSecurityDSig();
|
||||
|
||||
$objXMLSecDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N);
|
||||
|
||||
$objXMLSecDSig->addReferenceList(
|
||||
array($rootNode),
|
||||
$signature_cf['digest'],
|
||||
array('http://www.w3.org/2000/09/xmldsig#enveloped-signature', XMLSecurityDSig::EXC_C14N),
|
||||
array('id_name' => 'ID')
|
||||
);
|
||||
|
||||
$objXMLSecDSig->sign($objKey);
|
||||
|
||||
// add the certificate to the signature
|
||||
$objXMLSecDSig->add509Cert($certData, true);
|
||||
|
||||
// add the signature to the metadata
|
||||
$objXMLSecDSig->insertSignature($rootNode, $rootNode->firstChild);
|
||||
|
||||
// return the DOM tree as a string
|
||||
return $xml->saveXML();
|
||||
}
|
||||
}
|
||||
337
lib/SimpleSAML/Metadata/Sources/MDQ.php
Executable file
337
lib/SimpleSAML/Metadata/Sources/MDQ.php
Executable file
@@ -0,0 +1,337 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleSAML\Metadata\Sources;
|
||||
|
||||
use SimpleSAML\Logger;
|
||||
use SimpleSAML\Utils\HTTP;
|
||||
|
||||
/**
|
||||
* This class implements SAML Metadata Query Protocol
|
||||
*
|
||||
* @author Andreas Åkre Solberg, UNINETT AS.
|
||||
* @author Olav Morken, UNINETT AS.
|
||||
* @author Tamas Frank, NIIFI
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class MDQ extends \SimpleSAML_Metadata_MetaDataStorageSource
|
||||
{
|
||||
|
||||
/**
|
||||
* The URL of MDQ server (url:port)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $server;
|
||||
|
||||
/**
|
||||
* The fingerprint of the certificate used to sign the metadata. You don't need this option if you don't want to
|
||||
* validate the signature on the metadata.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $validateFingerprint;
|
||||
|
||||
/**
|
||||
* The cache directory, or null if no cache directory is configured.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $cacheDir;
|
||||
|
||||
|
||||
/**
|
||||
* The maximum cache length, in seconds.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
private $cacheLength;
|
||||
|
||||
|
||||
/**
|
||||
* This function initializes the dynamic XML metadata source.
|
||||
*
|
||||
* Options:
|
||||
* - 'server': URL of the MDQ server (url:port). Mandatory.
|
||||
* - 'validateFingerprint': The fingerprint of the certificate used to sign the metadata.
|
||||
* You don't need this option if you don't want to validate the signature on the metadata.
|
||||
* Optional.
|
||||
* - 'cachedir': Directory where metadata can be cached. Optional.
|
||||
* - 'cachelength': Maximum time metadata cah be cached, in seconds. Default to 24
|
||||
* hours (86400 seconds).
|
||||
*
|
||||
* @param array $config The configuration for this instance of the XML metadata source.
|
||||
*
|
||||
* @throws \Exception If no server option can be found in the configuration.
|
||||
*/
|
||||
protected function __construct($config)
|
||||
{
|
||||
assert(is_array($config));
|
||||
|
||||
if (!array_key_exists('server', $config)) {
|
||||
throw new \Exception(__CLASS__.": the 'server' configuration option is not set.");
|
||||
} else {
|
||||
$this->server = $config['server'];
|
||||
}
|
||||
|
||||
if (array_key_exists('validateFingerprint', $config)) {
|
||||
$this->validateFingerprint = $config['validateFingerprint'];
|
||||
} else {
|
||||
$this->validateFingerprint = null;
|
||||
}
|
||||
|
||||
if (array_key_exists('cachedir', $config)) {
|
||||
$globalConfig = \SimpleSAML_Configuration::getInstance();
|
||||
$this->cacheDir = $globalConfig->resolvePath($config['cachedir']);
|
||||
} else {
|
||||
$this->cacheDir = null;
|
||||
}
|
||||
|
||||
if (array_key_exists('cachelength', $config)) {
|
||||
$this->cacheLength = $config['cachelength'];
|
||||
} else {
|
||||
$this->cacheLength = 86400;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function is not implemented.
|
||||
*
|
||||
* @param string $set The set we want to list metadata for.
|
||||
*
|
||||
* @return array An empty array.
|
||||
*/
|
||||
public function getMetadataSet($set)
|
||||
{
|
||||
// we don't have this metadata set
|
||||
return array();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find the cache file name for an entity,
|
||||
*
|
||||
* @param string $set The metadata set this entity belongs to.
|
||||
* @param string $entityId The entity id of this entity.
|
||||
*
|
||||
* @return string The full path to the cache file.
|
||||
*/
|
||||
private function getCacheFilename($set, $entityId)
|
||||
{
|
||||
assert(is_string($set));
|
||||
assert(is_string($entityId));
|
||||
|
||||
$cachekey = sha1($entityId);
|
||||
return $this->cacheDir.'/'.$set.'-'.$cachekey.'.cached.xml';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load a entity from the cache.
|
||||
*
|
||||
* @param string $set The metadata set this entity belongs to.
|
||||
* @param string $entityId The entity id of this entity.
|
||||
*
|
||||
* @return array|NULL The associative array with the metadata for this entity, or NULL
|
||||
* if the entity could not be found.
|
||||
* @throws \Exception If an error occurs while loading metadata from cache.
|
||||
*/
|
||||
private function getFromCache($set, $entityId)
|
||||
{
|
||||
assert(is_string($set));
|
||||
assert(is_string($entityId));
|
||||
|
||||
if (empty($this->cacheDir)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$cachefilename = $this->getCacheFilename($set, $entityId);
|
||||
if (!file_exists($cachefilename)) {
|
||||
return null;
|
||||
}
|
||||
if (!is_readable($cachefilename)) {
|
||||
throw new \Exception(__CLASS__.': could not read cache file for entity ['.$cachefilename.']');
|
||||
}
|
||||
Logger::debug(__CLASS__.': reading cache ['.$entityId.'] => ['.$cachefilename.']');
|
||||
|
||||
/* Ensure that this metadata isn't older that the cachelength option allows. This
|
||||
* must be verified based on the file, since this option may be changed after the
|
||||
* file is written.
|
||||
*/
|
||||
$stat = stat($cachefilename);
|
||||
if ($stat['mtime'] + $this->cacheLength <= time()) {
|
||||
Logger::debug(__CLASS__.': cache file older that the cachelength option allows.');
|
||||
return null;
|
||||
}
|
||||
|
||||
$rawData = file_get_contents($cachefilename);
|
||||
if (empty($rawData)) {
|
||||
$error = error_get_last();
|
||||
throw new \Exception(
|
||||
__CLASS__.': error reading metadata from cache file "'.$cachefilename.'": '.$error['message']
|
||||
);
|
||||
}
|
||||
|
||||
$data = unserialize($rawData);
|
||||
if ($data === false) {
|
||||
throw new \Exception(__CLASS__.': error unserializing cached data from file "'.$cachefilename.'".');
|
||||
}
|
||||
|
||||
if (!is_array($data)) {
|
||||
throw new \Exception(__CLASS__.': Cached metadata from "'.$cachefilename.'" wasn\'t an array.');
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save a entity to the cache.
|
||||
*
|
||||
* @param string $set The metadata set this entity belongs to.
|
||||
* @param string $entityId The entity id of this entity.
|
||||
* @param array $data The associative array with the metadata for this entity.
|
||||
*
|
||||
* @throws \Exception If metadata cannot be written to cache.
|
||||
*/
|
||||
private function writeToCache($set, $entityId, $data)
|
||||
{
|
||||
assert(is_string($set));
|
||||
assert(is_string($entityId));
|
||||
assert(is_array($data));
|
||||
|
||||
if (empty($this->cacheDir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cachefilename = $this->getCacheFilename($set, $entityId);
|
||||
if (!is_writable(dirname($cachefilename))) {
|
||||
throw new \Exception(__CLASS__.': could not write cache file for entity ['.$cachefilename.']');
|
||||
}
|
||||
Logger::debug(__CLASS__.': Writing cache ['.$entityId.'] => ['.$cachefilename.']');
|
||||
file_put_contents($cachefilename, serialize($data));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve metadata for the correct set from a SAML2Parser.
|
||||
*
|
||||
* @param \SimpleSAML_Metadata_SAMLParser $entity A SAML2Parser representing an entity.
|
||||
* @param string $set The metadata set we are looking for.
|
||||
*
|
||||
* @return array|NULL The associative array with the metadata, or NULL if no metadata for
|
||||
* the given set was found.
|
||||
*/
|
||||
private static function getParsedSet(\SimpleSAML_Metadata_SAMLParser $entity, $set)
|
||||
{
|
||||
assert(is_string($set));
|
||||
|
||||
switch ($set) {
|
||||
case 'saml20-idp-remote':
|
||||
return $entity->getMetadata20IdP();
|
||||
case 'saml20-sp-remote':
|
||||
return $entity->getMetadata20SP();
|
||||
case 'shib13-idp-remote':
|
||||
return $entity->getMetadata1xIdP();
|
||||
case 'shib13-sp-remote':
|
||||
return $entity->getMetadata1xSP();
|
||||
case 'attributeauthority-remote':
|
||||
$ret = $entity->getAttributeAuthorities();
|
||||
return $ret[0];
|
||||
|
||||
default:
|
||||
Logger::warning(__CLASS__.': unknown metadata set: \''.$set.'\'.');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Overriding this function from the superclass SimpleSAML_Metadata_MetaDataStorageSource.
|
||||
*
|
||||
* This function retrieves metadata for the given entity id in the given set of metadata.
|
||||
* It will return NULL if it is unable to locate the metadata.
|
||||
*
|
||||
* This class implements this function using the getMetadataSet-function. A subclass should
|
||||
* override this function if it doesn't implement the getMetadataSet function, or if the
|
||||
* implementation of getMetadataSet is slow.
|
||||
*
|
||||
* @param string $index The entityId or metaindex we are looking up.
|
||||
* @param string $set The set we are looking for metadata in.
|
||||
*
|
||||
* @return array An associative array with metadata for the given entity, or NULL if we are unable to
|
||||
* locate the entity.
|
||||
* @throws \Exception If an error occurs while validating the signature or the metadata is in an
|
||||
* incorrect set.
|
||||
*/
|
||||
public function getMetaData($index, $set)
|
||||
{
|
||||
assert(is_string($index));
|
||||
assert(is_string($set));
|
||||
|
||||
Logger::info(__CLASS__.': loading metadata entity ['.$index.'] from ['.$set.']');
|
||||
|
||||
// read from cache if possible
|
||||
try {
|
||||
$data = $this->getFromCache($set, $index);
|
||||
} catch (\Exception $e) {
|
||||
Logger::error($e->getMessage());
|
||||
// proceed with fetching metadata even if the cache is broken
|
||||
$data = null;
|
||||
}
|
||||
|
||||
if ($data !== null && array_key_exists('expires', $data) && $data['expires'] < time()) {
|
||||
// metadata has expired
|
||||
$data = null;
|
||||
}
|
||||
|
||||
if (isset($data)) {
|
||||
// metadata found in cache and not expired
|
||||
Logger::debug(__CLASS__.': using cached metadata for: '.$index.'.');
|
||||
return $data;
|
||||
}
|
||||
|
||||
// look at Metadata Query Protocol: https://github.com/iay/md-query/blob/master/draft-young-md-query.txt
|
||||
$mdq_url = $this->server.'/entities/'.urlencode($index);
|
||||
|
||||
Logger::debug(__CLASS__.': downloading metadata for "'.$index.'" from ['.$mdq_url.']');
|
||||
try {
|
||||
$xmldata = HTTP::fetch($mdq_url);
|
||||
} catch (\Exception $e) {
|
||||
// Avoid propagating the exception, make sure we can handle the error later
|
||||
$xmldata = false;
|
||||
}
|
||||
|
||||
if (empty($xmldata)) {
|
||||
$error = error_get_last();
|
||||
Logger::info('Unable to fetch metadata for "'.$index.'" from '.$mdq_url.': '.
|
||||
(is_array($error) ? $error['message'] : 'no error available'));
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var string $xmldata */
|
||||
$entity = \SimpleSAML_Metadata_SAMLParser::parseString($xmldata);
|
||||
Logger::debug(__CLASS__.': completed parsing of ['.$mdq_url.']');
|
||||
|
||||
if ($this->validateFingerprint !== null) {
|
||||
if (!$entity->validateFingerprint($this->validateFingerprint)) {
|
||||
throw new \Exception(__CLASS__.': error, could not verify signature for entity: '.$index.'".');
|
||||
}
|
||||
}
|
||||
|
||||
$data = self::getParsedSet($entity, $set);
|
||||
if ($data === null) {
|
||||
throw new \Exception(__CLASS__.': no metadata for set "'.$set.'" available from "'.$index.'".');
|
||||
}
|
||||
|
||||
try {
|
||||
$this->writeToCache($set, $index, $data);
|
||||
} catch (\Exception $e) {
|
||||
// Proceed without writing to cache
|
||||
Logger::error('Error writing MDQ result to cache: '.$e->getMessage());
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
311
lib/SimpleSAML/Module.php
Executable file
311
lib/SimpleSAML/Module.php
Executable file
@@ -0,0 +1,311 @@
|
||||
<?php
|
||||
namespace SimpleSAML;
|
||||
|
||||
/**
|
||||
* Helper class for accessing information about modules.
|
||||
*
|
||||
* @author Olav Morken <olav.morken@uninett.no>, UNINETT AS.
|
||||
* @author Boy Baukema, SURFnet.
|
||||
* @author Jaime Perez <jaime.perez@uninett.no>, UNINETT AS.
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class Module
|
||||
{
|
||||
|
||||
/**
|
||||
* A list containing the modules currently installed.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array();
|
||||
|
||||
/**
|
||||
* A cache containing specific information for modules, like whether they are enabled or not, or their hooks.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $module_info = array();
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the base directory for a module.
|
||||
*
|
||||
* The returned path name will be an absolute path.
|
||||
*
|
||||
* @param string $module Name of the module
|
||||
*
|
||||
* @return string The base directory of a module.
|
||||
*/
|
||||
public static function getModuleDir($module)
|
||||
{
|
||||
$baseDir = dirname(dirname(dirname(__FILE__))).'/modules';
|
||||
$moduleDir = $baseDir.'/'.$module;
|
||||
|
||||
return $moduleDir;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine whether a module is enabled.
|
||||
*
|
||||
* Will return false if the given module doesn't exist.
|
||||
*
|
||||
* @param string $module Name of the module
|
||||
*
|
||||
* @return bool True if the given module is enabled, false otherwise.
|
||||
*
|
||||
* @throws \Exception If module.enable is set and is not boolean.
|
||||
*/
|
||||
public static function isModuleEnabled($module)
|
||||
{
|
||||
$config = \SimpleSAML_Configuration::getOptionalConfig();
|
||||
return self::isModuleEnabledWithConf($module, $config->getArray('module.enable', array()));
|
||||
}
|
||||
|
||||
|
||||
private static function isModuleEnabledWithConf($module, $mod_config)
|
||||
{
|
||||
if (isset(self::$module_info[$module]['enabled'])) {
|
||||
return self::$module_info[$module]['enabled'];
|
||||
}
|
||||
|
||||
if (!empty(self::$modules) && !in_array($module, self::$modules, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$moduleDir = self::getModuleDir($module);
|
||||
|
||||
if (!is_dir($moduleDir)) {
|
||||
self::$module_info[$module]['enabled'] = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($mod_config[$module])) {
|
||||
if (is_bool($mod_config[$module])) {
|
||||
self::$module_info[$module]['enabled'] = $mod_config[$module];
|
||||
return $mod_config[$module];
|
||||
}
|
||||
|
||||
throw new \Exception("Invalid module.enable value for the '$module' module.");
|
||||
}
|
||||
|
||||
if (assert_options(ASSERT_ACTIVE) &&
|
||||
!file_exists($moduleDir.'/default-enable') &&
|
||||
!file_exists($moduleDir.'/default-disable')
|
||||
) {
|
||||
\SimpleSAML\Logger::error("Missing default-enable or default-disable file for the module $module");
|
||||
}
|
||||
|
||||
if (file_exists($moduleDir.'/enable')) {
|
||||
self::$module_info[$module]['enabled'] = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!file_exists($moduleDir.'/disable') && file_exists($moduleDir.'/default-enable')) {
|
||||
self::$module_info[$module]['enabled'] = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
self::$module_info[$module]['enabled'] = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get available modules.
|
||||
*
|
||||
* @return array One string for each module.
|
||||
*
|
||||
* @throws \Exception If we cannot open the module's directory.
|
||||
*/
|
||||
public static function getModules()
|
||||
{
|
||||
if (!empty(self::$modules)) {
|
||||
return self::$modules;
|
||||
}
|
||||
|
||||
$path = self::getModuleDir('.');
|
||||
|
||||
$dh = scandir($path);
|
||||
if ($dh === false) {
|
||||
throw new \Exception('Unable to open module directory "'.$path.'".');
|
||||
}
|
||||
|
||||
foreach ($dh as $f) {
|
||||
if ($f[0] === '.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_dir($path.'/'.$f)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
self::$modules[] = $f;
|
||||
}
|
||||
|
||||
return self::$modules;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolve module class.
|
||||
*
|
||||
* This function takes a string on the form "<module>:<class>" and converts it to a class
|
||||
* name. It can also check that the given class is a subclass of a specific class. The
|
||||
* resolved classname will be "sspmod_<module>_<$type>_<class>.
|
||||
*
|
||||
* It is also possible to specify a full classname instead of <module>:<class>.
|
||||
*
|
||||
* An exception will be thrown if the class can't be resolved.
|
||||
*
|
||||
* @param string $id The string we should resolve.
|
||||
* @param string $type The type of the class.
|
||||
* @param string|null $subclass The class should be a subclass of this class. Optional.
|
||||
*
|
||||
* @return string The classname.
|
||||
*
|
||||
* @throws \Exception If the class cannot be resolved.
|
||||
*/
|
||||
public static function resolveClass($id, $type, $subclass = null)
|
||||
{
|
||||
assert(is_string($id));
|
||||
assert(is_string($type));
|
||||
assert(is_string($subclass) || $subclass === null);
|
||||
|
||||
$tmp = explode(':', $id, 2);
|
||||
if (count($tmp) === 1) { // no module involved
|
||||
$className = $tmp[0];
|
||||
if (!class_exists($className)) {
|
||||
throw new \Exception("Could not resolve '$id': no class named '$className'.");
|
||||
}
|
||||
} else { // should be a module
|
||||
// make sure empty types are handled correctly
|
||||
$type = (empty($type)) ? '_' : '_'.$type.'_';
|
||||
|
||||
// check for the old-style class names
|
||||
$className = 'sspmod_'.$tmp[0].$type.$tmp[1];
|
||||
|
||||
if (!class_exists($className)) {
|
||||
// check for the new-style class names, using namespaces
|
||||
$type = str_replace('_', '\\', $type);
|
||||
$newClassName = 'SimpleSAML\Module\\'.$tmp[0].$type.$tmp[1];
|
||||
|
||||
if (!class_exists($newClassName)) {
|
||||
throw new \Exception("Could not resolve '$id': no class named '$className' or '$newClassName'.");
|
||||
}
|
||||
$className = $newClassName;
|
||||
}
|
||||
}
|
||||
|
||||
if ($subclass !== null && !is_subclass_of($className, $subclass)) {
|
||||
throw new \Exception(
|
||||
'Could not resolve \''.$id.'\': The class \''.$className.'\' isn\'t a subclass of \''.$subclass.'\'.'
|
||||
);
|
||||
}
|
||||
|
||||
return $className;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get absolute URL to a specified module resource.
|
||||
*
|
||||
* This function creates an absolute URL to a resource stored under ".../modules/<module>/www/".
|
||||
*
|
||||
* @param string $resource Resource path, on the form "<module name>/<resource>"
|
||||
* @param array $parameters Extra parameters which should be added to the URL. Optional.
|
||||
*
|
||||
* @return string The absolute URL to the given resource.
|
||||
*/
|
||||
public static function getModuleURL($resource, array $parameters = array())
|
||||
{
|
||||
assert(is_string($resource));
|
||||
assert($resource[0] !== '/');
|
||||
|
||||
$url = Utils\HTTP::getBaseURL().'module.php/'.$resource;
|
||||
if (!empty($parameters)) {
|
||||
$url = Utils\HTTP::addURLParameters($url, $parameters);
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the available hooks for a given module.
|
||||
*
|
||||
* @param string $module The module where we should look for hooks.
|
||||
*
|
||||
* @return array An array with the hooks available for this module. Each element is an array with two keys: 'file'
|
||||
* points to the file that contains the hook, and 'func' contains the name of the function implementing that hook.
|
||||
* When there are no hooks defined, an empty array is returned.
|
||||
*/
|
||||
public static function getModuleHooks($module)
|
||||
{
|
||||
if (isset(self::$modules[$module]['hooks'])) {
|
||||
return self::$modules[$module]['hooks'];
|
||||
}
|
||||
|
||||
$hook_dir = self::getModuleDir($module).'/hooks';
|
||||
if (!is_dir($hook_dir)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$hooks = array();
|
||||
$files = scandir($hook_dir);
|
||||
foreach ($files as $file) {
|
||||
if ($file[0] === '.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!preg_match('/hook_(\w+)\.php/', $file, $matches)) {
|
||||
continue;
|
||||
}
|
||||
$hook_name = $matches[1];
|
||||
$hook_func = $module.'_hook_'.$hook_name;
|
||||
$hooks[$hook_name] = array('file' => $hook_dir.'/'.$file, 'func' => $hook_func);
|
||||
}
|
||||
return $hooks;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Call a hook in all enabled modules.
|
||||
*
|
||||
* This function iterates over all enabled modules and calls a hook in each module.
|
||||
*
|
||||
* @param string $hook The name of the hook.
|
||||
* @param mixed &$data The data which should be passed to each hook. Will be passed as a reference.
|
||||
*
|
||||
* @throws \SimpleSAML_Error_Exception If an invalid hook is found in a module.
|
||||
*/
|
||||
public static function callHooks($hook, &$data = null)
|
||||
{
|
||||
assert(is_string($hook));
|
||||
|
||||
$modules = self::getModules();
|
||||
$config = \SimpleSAML_Configuration::getOptionalConfig()->getArray('module.enable', array());
|
||||
sort($modules);
|
||||
foreach ($modules as $module) {
|
||||
if (!self::isModuleEnabledWithConf($module, $config)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset(self::$module_info[$module]['hooks'])) {
|
||||
self::$module_info[$module]['hooks'] = self::getModuleHooks($module);
|
||||
}
|
||||
|
||||
if (!isset(self::$module_info[$module]['hooks'][$hook])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
require_once(self::$module_info[$module]['hooks'][$hook]['file']);
|
||||
|
||||
if (!is_callable(self::$module_info[$module]['hooks'][$hook]['func'])) {
|
||||
throw new \SimpleSAML_Error_Exception('Invalid hook \''.$hook.'\' for module \''.$module.'\'.');
|
||||
}
|
||||
|
||||
$fn = self::$module_info[$module]['hooks'][$hook]['func'];
|
||||
$fn($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
1171
lib/SimpleSAML/Session.php
Executable file
1171
lib/SimpleSAML/Session.php
Executable file
File diff suppressed because it is too large
Load Diff
160
lib/SimpleSAML/SessionHandler.php
Executable file
160
lib/SimpleSAML/SessionHandler.php
Executable file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of SimpleSAMLphp. See the file COPYING in the
|
||||
* root of the distribution for licence information.
|
||||
*
|
||||
* This file defines a base class for session handling.
|
||||
* Instantiation of session handler objects should be done through
|
||||
* the class method getSessionHandler().
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS. <andreas.solberg@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
namespace SimpleSAML;
|
||||
|
||||
abstract class SessionHandler
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* This static variable contains a reference to the current
|
||||
* instance of the session handler. This variable will be NULL if
|
||||
* we haven't instantiated a session handler yet.
|
||||
*
|
||||
* @var \SimpleSAML\SessionHandler
|
||||
*/
|
||||
protected static $sessionHandler = null;
|
||||
|
||||
|
||||
/**
|
||||
* This function retrieves the current instance of the session handler.
|
||||
* The session handler will be instantiated if this is the first call
|
||||
* to this function.
|
||||
*
|
||||
* @return \SimpleSAML\SessionHandler The current session handler.
|
||||
*/
|
||||
public static function getSessionHandler()
|
||||
{
|
||||
if (self::$sessionHandler === null) {
|
||||
self::createSessionHandler();
|
||||
}
|
||||
|
||||
return self::$sessionHandler;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This constructor is included in case it is needed in the
|
||||
* future. Including it now allows us to write parent::__construct() in
|
||||
* the subclasses of this class.
|
||||
*/
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new session id.
|
||||
*
|
||||
* @return string The new session id.
|
||||
*/
|
||||
abstract public function newSessionId();
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the session ID saved in the session cookie, if there's one.
|
||||
*
|
||||
* @return string|null The session id saved in the cookie or null if no session cookie was set.
|
||||
*/
|
||||
abstract public function getCookieSessionId();
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the session cookie name.
|
||||
*
|
||||
* @return string The session cookie name.
|
||||
*/
|
||||
abstract public function getSessionCookieName();
|
||||
|
||||
|
||||
/**
|
||||
* Save the session.
|
||||
*
|
||||
* @param \SimpleSAML_Session $session The session object we should save.
|
||||
*/
|
||||
abstract public function saveSession(\SimpleSAML_Session $session);
|
||||
|
||||
|
||||
/**
|
||||
* Load the session.
|
||||
*
|
||||
* @param string|null $sessionId The ID of the session we should load, or null to use the default.
|
||||
*
|
||||
* @return \SimpleSAML_Session|null The session object, or null if it doesn't exist.
|
||||
*/
|
||||
abstract public function loadSession($sessionId = null);
|
||||
|
||||
|
||||
/**
|
||||
* Check whether the session cookie is set.
|
||||
*
|
||||
* This function will only return false if is is certain that the cookie isn't set.
|
||||
*
|
||||
* @return bool True if it was set, false if not.
|
||||
*/
|
||||
abstract public function hasSessionCookie();
|
||||
|
||||
|
||||
/**
|
||||
* Set a session cookie.
|
||||
*
|
||||
* @param string $sessionName The name of the session.
|
||||
* @param string|null $sessionID The session ID to use. Set to null to delete the cookie.
|
||||
* @param array|null $cookieParams Additional parameters to use for the session cookie.
|
||||
*
|
||||
* @throws \SimpleSAML\Error\CannotSetCookie If we can't set the cookie.
|
||||
*/
|
||||
abstract public function setCookie($sessionName, $sessionID, array $cookieParams = null);
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the session handler.
|
||||
*
|
||||
* This function creates an instance of the session handler which is
|
||||
* selected in the 'session.handler' configuration directive. If no
|
||||
* session handler is selected, then we will fall back to the default
|
||||
* PHP session handler.
|
||||
*/
|
||||
private static function createSessionHandler()
|
||||
{
|
||||
$store = \SimpleSAML\Store::getInstance();
|
||||
if ($store === false) {
|
||||
self::$sessionHandler = new SessionHandlerPHP();
|
||||
} else {
|
||||
/** @var \SimpleSAML\Store $store At this point, $store can only be an object */
|
||||
self::$sessionHandler = new SessionHandlerStore($store);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the cookie parameters that should be used for session cookies.
|
||||
*
|
||||
* @return array An array with the cookie parameters.
|
||||
* @link http://www.php.net/manual/en/function.session-get-cookie-params.php
|
||||
*/
|
||||
public function getCookieParams()
|
||||
{
|
||||
$config = \SimpleSAML_Configuration::getInstance();
|
||||
|
||||
return array(
|
||||
'lifetime' => $config->getInteger('session.cookie.lifetime', 0),
|
||||
'path' => $config->getString('session.cookie.path', '/'),
|
||||
'domain' => $config->getString('session.cookie.domain', null),
|
||||
'secure' => $config->getBoolean('session.cookie.secure', false),
|
||||
'httponly' => true,
|
||||
);
|
||||
}
|
||||
}
|
||||
173
lib/SimpleSAML/SessionHandlerCookie.php
Executable file
173
lib/SimpleSAML/SessionHandlerCookie.php
Executable file
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* This file is part of SimpleSAMLphp. See the file COPYING in the root of the distribution for licence information.
|
||||
*
|
||||
* This file defines a base class for session handlers that need to store the session id in a cookie. It takes care of
|
||||
* storing and retrieving the session id.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS. <andreas.solberg@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
* @abstract
|
||||
*/
|
||||
|
||||
namespace SimpleSAML;
|
||||
|
||||
use SimpleSAML\Utils\HTTP;
|
||||
|
||||
abstract class SessionHandlerCookie extends SessionHandler
|
||||
{
|
||||
|
||||
/**
|
||||
* This variable contains the current session id.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $session_id = null;
|
||||
|
||||
|
||||
/**
|
||||
* This variable contains the session cookie name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cookie_name;
|
||||
|
||||
|
||||
/**
|
||||
* This constructor initializes the session id based on what we receive in a cookie. We create a new session id and
|
||||
* set a cookie with this id if we don't have a session id.
|
||||
*/
|
||||
protected function __construct()
|
||||
{
|
||||
// call the constructor in the base class in case it should become necessary in the future
|
||||
parent::__construct();
|
||||
|
||||
$config = \SimpleSAML_Configuration::getInstance();
|
||||
$this->cookie_name = $config->getString('session.cookie.name', 'SimpleSAMLSessionID');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new session id.
|
||||
*
|
||||
* @return string The new session id.
|
||||
*/
|
||||
public function newSessionId()
|
||||
{
|
||||
$this->session_id = self::createSessionID();
|
||||
\SimpleSAML_Session::createSession($this->session_id);
|
||||
|
||||
return $this->session_id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the session ID saved in the session cookie, if there's one.
|
||||
*
|
||||
* @return string|null The session id saved in the cookie or null if no session cookie was set.
|
||||
*/
|
||||
public function getCookieSessionId()
|
||||
{
|
||||
if ($this->session_id === null) {
|
||||
if (self::hasSessionCookie()) {
|
||||
// attempt to retrieve the session id from the cookie
|
||||
$this->session_id = $_COOKIE[$this->cookie_name];
|
||||
}
|
||||
|
||||
// check if we have a valid session id
|
||||
if (!self::isValidSessionID($this->session_id)) {
|
||||
// invalid, disregard this session
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->session_id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the session cookie name.
|
||||
*
|
||||
* @return string The session cookie name.
|
||||
*/
|
||||
public function getSessionCookieName()
|
||||
{
|
||||
return $this->cookie_name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This static function creates a session id. A session id consists of 32 random hexadecimal characters.
|
||||
*
|
||||
* @return string A random session id.
|
||||
*/
|
||||
private static function createSessionID()
|
||||
{
|
||||
return bin2hex(openssl_random_pseudo_bytes(16));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This static function validates a session id. A session id is valid if it only consists of characters which are
|
||||
* allowed in a session id and it is the correct length.
|
||||
*
|
||||
* @param string $session_id The session ID we should validate.
|
||||
*
|
||||
* @return boolean True if this session ID is valid, false otherwise.
|
||||
*/
|
||||
private static function isValidSessionID($session_id)
|
||||
{
|
||||
if (!is_string($session_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strlen($session_id) != 32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (preg_match('/[^0-9a-f]/', $session_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check whether the session cookie is set.
|
||||
*
|
||||
* This function will only return false if is is certain that the cookie isn't set.
|
||||
*
|
||||
* @return boolean True if it was set, false otherwise.
|
||||
*/
|
||||
public function hasSessionCookie()
|
||||
{
|
||||
return array_key_exists($this->cookie_name, $_COOKIE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set a session cookie.
|
||||
*
|
||||
* @param string $sessionName The name of the session.
|
||||
* @param string|null $sessionID The session ID to use. Set to null to delete the cookie.
|
||||
* @param array|null $cookieParams Additional parameters to use for the session cookie.
|
||||
*
|
||||
* @throws \SimpleSAML\Error\CannotSetCookie If we can't set the cookie.
|
||||
*/
|
||||
public function setCookie($sessionName, $sessionID, array $cookieParams = null)
|
||||
{
|
||||
assert(is_string($sessionName));
|
||||
assert(is_string($sessionID) || $sessionID === null);
|
||||
|
||||
if ($cookieParams !== null) {
|
||||
$params = array_merge($this->getCookieParams(), $cookieParams);
|
||||
} else {
|
||||
$params = $this->getCookieParams();
|
||||
}
|
||||
|
||||
HTTP::setCookie($sessionName, $sessionID, $params, true);
|
||||
}
|
||||
}
|
||||
362
lib/SimpleSAML/SessionHandlerPHP.php
Executable file
362
lib/SimpleSAML/SessionHandlerPHP.php
Executable file
@@ -0,0 +1,362 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of SimpleSAMLphp. See the file COPYING in the root of the distribution for licence information.
|
||||
*
|
||||
* This file defines a session handler which uses the default php session handler for storage.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS. <andreas.solberg@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
namespace SimpleSAML;
|
||||
|
||||
use SimpleSAML\Error\CannotSetCookie;
|
||||
use SimpleSAML\Utils\HTTP;
|
||||
|
||||
class SessionHandlerPHP extends SessionHandler
|
||||
{
|
||||
|
||||
/**
|
||||
* This variable contains the session cookie name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cookie_name;
|
||||
|
||||
/**
|
||||
* An associative array containing the details of a session existing previously to creating or loading one with this
|
||||
* session handler. The keys of the array will be:
|
||||
*
|
||||
* - id: the ID of the session, as returned by session_id().
|
||||
* - name: the name of the session, as returned by session_name().
|
||||
* - cookie_params: the parameters of the session cookie, as returned by session_get_cookie_params().
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $previous_session = array();
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the PHP session handling. This constructor is protected because it should only be called from
|
||||
* \SimpleSAML\SessionHandler::createSessionHandler(...).
|
||||
*/
|
||||
protected function __construct()
|
||||
{
|
||||
// call the parent constructor in case it should become necessary in the future
|
||||
parent::__construct();
|
||||
|
||||
$config = \SimpleSAML_Configuration::getInstance();
|
||||
$this->cookie_name = $config->getString('session.phpsession.cookiename', null);
|
||||
|
||||
if (session_status() === PHP_SESSION_ACTIVE) {
|
||||
if (session_name() === $this->cookie_name || $this->cookie_name === null) {
|
||||
Logger::warning(
|
||||
'There is already a PHP session with the same name as SimpleSAMLphp\'s session, or the '.
|
||||
"'session.phpsession.cookiename' configuration option is not set. Make sure to set ".
|
||||
"SimpleSAMLphp's cookie name with a value not used by any other applications."
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* We shouldn't have a session at this point, so it might be an application session. Save the details to
|
||||
* retrieve it later and commit.
|
||||
*/
|
||||
$this->previous_session['cookie_params'] = session_get_cookie_params();
|
||||
$this->previous_session['id'] = session_id();
|
||||
$this->previous_session['name'] = session_name();
|
||||
session_write_close();
|
||||
}
|
||||
|
||||
if (!empty($this->cookie_name)) {
|
||||
session_name($this->cookie_name);
|
||||
} else {
|
||||
$this->cookie_name = session_name();
|
||||
}
|
||||
|
||||
$params = $this->getCookieParams();
|
||||
|
||||
if (!headers_sent()) {
|
||||
session_set_cookie_params(
|
||||
$params['lifetime'],
|
||||
$params['path'],
|
||||
$params['domain'],
|
||||
$params['secure'],
|
||||
$params['httponly']
|
||||
);
|
||||
}
|
||||
|
||||
$savepath = $config->getString('session.phpsession.savepath', null);
|
||||
if (!empty($savepath)) {
|
||||
session_save_path($savepath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method starts a session, making sure no warnings are generated due to headers being already sent.
|
||||
*/
|
||||
private function sessionStart()
|
||||
{
|
||||
$cacheLimiter = session_cache_limiter();
|
||||
if (headers_sent()) {
|
||||
/*
|
||||
* session_start() tries to send HTTP headers depending on the configuration, according to the
|
||||
* documentation:
|
||||
*
|
||||
* http://php.net/manual/en/function.session-start.php
|
||||
*
|
||||
* If headers have been already sent, it will then trigger an error since no more headers can be sent.
|
||||
* Being unable to send headers does not mean we cannot recover the session by calling session_start(),
|
||||
* so we still want to call it. In this case, though, we want to avoid session_start() to send any
|
||||
* headers at all so that no error is generated, so we clear the cache limiter temporarily (no headers
|
||||
* sent then) and restore it after successfully starting the session.
|
||||
*/
|
||||
session_cache_limiter('');
|
||||
}
|
||||
session_cache_limiter($cacheLimiter);
|
||||
@session_start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Restore a previously-existing session.
|
||||
*
|
||||
* Use this method to restore a previous PHP session existing before SimpleSAMLphp initialized its own session.
|
||||
*
|
||||
* WARNING: do not use this method directly, unless you know what you are doing. Calling this method directly,
|
||||
* outside of SimpleSAML_Session, could cause SimpleSAMLphp's session to be lost or mess the application's one. The
|
||||
* session must always be saved properly before calling this method. If you don't understand what this is about,
|
||||
* don't use this method.
|
||||
*/
|
||||
public function restorePrevious()
|
||||
{
|
||||
if (empty($this->previous_session)) {
|
||||
return; // nothing to do here
|
||||
}
|
||||
|
||||
// close our own session
|
||||
session_write_close();
|
||||
|
||||
session_name($this->previous_session['name']);
|
||||
session_set_cookie_params(
|
||||
$this->previous_session['cookie_params']['lifetime'],
|
||||
$this->previous_session['cookie_params']['path'],
|
||||
$this->previous_session['cookie_params']['domain'],
|
||||
$this->previous_session['cookie_params']['secure'],
|
||||
$this->previous_session['cookie_params']['httponly']
|
||||
);
|
||||
session_id($this->previous_session['id']);
|
||||
$this->previous_session = array();
|
||||
$this->sessionStart();
|
||||
|
||||
/*
|
||||
* At this point, we have restored a previously-existing session, so we can't continue to use our session here.
|
||||
* Therefore, we need to load our session again in case we need it. We remove this handler from the parent
|
||||
* class so that the handler is initialized again if we ever need to do something with the session.
|
||||
*/
|
||||
parent::$sessionHandler = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new session id.
|
||||
*
|
||||
* @return string The new session id.
|
||||
*/
|
||||
public function newSessionId()
|
||||
{
|
||||
// generate new (secure) session id
|
||||
$sessionId = bin2hex(openssl_random_pseudo_bytes(16));
|
||||
\SimpleSAML_Session::createSession($sessionId);
|
||||
|
||||
return $sessionId;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the session ID saved in the session cookie, if there's one.
|
||||
*
|
||||
* @return string|null The session id saved in the cookie or null if no session cookie was set.
|
||||
*
|
||||
* @throws \SimpleSAML_Error_Exception If the cookie is marked as secure but we are not using HTTPS.
|
||||
*/
|
||||
public function getCookieSessionId()
|
||||
{
|
||||
if (!self::hasSessionCookie()) {
|
||||
return null; // there's no session cookie, can't return ID
|
||||
}
|
||||
|
||||
// do not rely on session_id() as it can return the ID of a previous session. Get it from the cookie instead.
|
||||
session_id($_COOKIE[$this->cookie_name]);
|
||||
|
||||
$session_cookie_params = session_get_cookie_params();
|
||||
|
||||
if ($session_cookie_params['secure'] && !HTTP::isHTTPS()) {
|
||||
throw new \SimpleSAML_Error_Exception('Session start with secure cookie not allowed on http.');
|
||||
}
|
||||
|
||||
$this->sessionStart();
|
||||
return session_id();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the session cookie name.
|
||||
*
|
||||
* @return string The session cookie name.
|
||||
*/
|
||||
public function getSessionCookieName()
|
||||
{
|
||||
return $this->cookie_name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save the current session to the PHP session array.
|
||||
*
|
||||
* @param \SimpleSAML_Session $session The session object we should save.
|
||||
*/
|
||||
public function saveSession(\SimpleSAML_Session $session)
|
||||
{
|
||||
$_SESSION['SimpleSAMLphp_SESSION'] = serialize($session);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load the session from the PHP session array.
|
||||
*
|
||||
* @param string|null $sessionId The ID of the session we should load, or null to use the default.
|
||||
*
|
||||
* @return \SimpleSAML_Session|null The session object, or null if it doesn't exist.
|
||||
*
|
||||
* @throws \SimpleSAML_Error_Exception If it wasn't possible to disable session cookies or we are trying to load a
|
||||
* PHP session with a specific identifier and it doesn't match with the current session identifier.
|
||||
*/
|
||||
public function loadSession($sessionId = null)
|
||||
{
|
||||
assert(is_string($sessionId) || $sessionId === null);
|
||||
|
||||
if ($sessionId !== null) {
|
||||
if (session_id() === '') {
|
||||
// session not initiated with getCookieSessionId(), start session without setting cookie
|
||||
$ret = ini_set('session.use_cookies', '0');
|
||||
if ($ret === false) {
|
||||
throw new \SimpleSAML_Error_Exception('Disabling PHP option session.use_cookies failed.');
|
||||
}
|
||||
|
||||
session_id($sessionId);
|
||||
$this->sessionStart();
|
||||
} elseif ($sessionId !== session_id()) {
|
||||
throw new \SimpleSAML_Error_Exception('Cannot load PHP session with a specific ID.');
|
||||
}
|
||||
} elseif (session_id() === '') {
|
||||
self::getCookieSessionId();
|
||||
}
|
||||
|
||||
if (!isset($_SESSION['SimpleSAMLphp_SESSION'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$session = $_SESSION['SimpleSAMLphp_SESSION'];
|
||||
assert(is_string($session));
|
||||
|
||||
$session = unserialize($session);
|
||||
|
||||
return ($session !== false) ? $session : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check whether the session cookie is set.
|
||||
*
|
||||
* This function will only return false if is is certain that the cookie isn't set.
|
||||
*
|
||||
* @return boolean True if it was set, false otherwise.
|
||||
*/
|
||||
public function hasSessionCookie()
|
||||
{
|
||||
return array_key_exists($this->cookie_name, $_COOKIE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the cookie parameters that should be used for session cookies.
|
||||
*
|
||||
* This function contains some adjustments from the default to provide backwards-compatibility.
|
||||
*
|
||||
* @return array The cookie parameters for our sessions.
|
||||
* @link http://www.php.net/manual/en/function.session-get-cookie-params.php
|
||||
*
|
||||
* @throws \SimpleSAML_Error_Exception If both 'session.phpsession.limitedpath' and 'session.cookie.path' options
|
||||
* are set at the same time in the configuration.
|
||||
*/
|
||||
public function getCookieParams()
|
||||
{
|
||||
$config = \SimpleSAML_Configuration::getInstance();
|
||||
|
||||
$ret = parent::getCookieParams();
|
||||
|
||||
if ($config->hasValue('session.phpsession.limitedpath') && $config->hasValue('session.cookie.path')) {
|
||||
throw new \SimpleSAML_Error_Exception(
|
||||
'You cannot set both the session.phpsession.limitedpath and session.cookie.path options.'
|
||||
);
|
||||
} elseif ($config->hasValue('session.phpsession.limitedpath')) {
|
||||
$ret['path'] = $config->getBoolean(
|
||||
'session.phpsession.limitedpath',
|
||||
false
|
||||
) ? $config->getBasePath() : '/';
|
||||
}
|
||||
|
||||
$ret['httponly'] = $config->getBoolean('session.phpsession.httponly', true);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set a session cookie.
|
||||
*
|
||||
* @param string $sessionName The name of the session.
|
||||
* @param string|null $sessionID The session ID to use. Set to null to delete the cookie.
|
||||
* @param array|null $cookieParams Additional parameters to use for the session cookie.
|
||||
*
|
||||
* @throws \SimpleSAML\Error\CannotSetCookie If we can't set the cookie.
|
||||
*/
|
||||
public function setCookie($sessionName, $sessionID, array $cookieParams = null)
|
||||
{
|
||||
if ($cookieParams === null) {
|
||||
$cookieParams = session_get_cookie_params();
|
||||
}
|
||||
|
||||
if ($cookieParams['secure'] && !HTTP::isHTTPS()) {
|
||||
throw new CannotSetCookie(
|
||||
'Setting secure cookie on plain HTTP is not allowed.',
|
||||
CannotSetCookie::SECURE_COOKIE
|
||||
);
|
||||
}
|
||||
|
||||
if (headers_sent()) {
|
||||
throw new CannotSetCookie(
|
||||
'Headers already sent.',
|
||||
CannotSetCookie::HEADERS_SENT
|
||||
);
|
||||
}
|
||||
|
||||
session_set_cookie_params(
|
||||
$cookieParams['lifetime'],
|
||||
$cookieParams['path'],
|
||||
$cookieParams['domain'],
|
||||
$cookieParams['secure'],
|
||||
$cookieParams['httponly']
|
||||
);
|
||||
|
||||
if (session_id() !== '') {
|
||||
// session already started, close it
|
||||
session_write_close();
|
||||
}
|
||||
|
||||
session_id($sessionID);
|
||||
$this->sessionStart();
|
||||
}
|
||||
}
|
||||
80
lib/SimpleSAML/SessionHandlerStore.php
Executable file
80
lib/SimpleSAML/SessionHandlerStore.php
Executable file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* Session storage in the data store.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
namespace SimpleSAML;
|
||||
|
||||
class SessionHandlerStore extends SessionHandlerCookie
|
||||
{
|
||||
|
||||
/**
|
||||
* The data store we save the session to.
|
||||
*
|
||||
* @var \SimpleSAML\Store
|
||||
*/
|
||||
private $store;
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the session.
|
||||
*
|
||||
* @param \SimpleSAML\Store $store The store to use.
|
||||
*/
|
||||
protected function __construct(Store $store)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->store = $store;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load a session from the data store.
|
||||
*
|
||||
* @param string|null $sessionId The ID of the session we should load, or null to use the default.
|
||||
*
|
||||
* @return \SimpleSAML_Session|null The session object, or null if it doesn't exist.
|
||||
*/
|
||||
public function loadSession($sessionId = null)
|
||||
{
|
||||
assert(is_string($sessionId) || $sessionId === null);
|
||||
|
||||
if ($sessionId === null) {
|
||||
$sessionId = $this->getCookieSessionId();
|
||||
if ($sessionId === null) {
|
||||
// no session cookie, nothing to load
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
$session = $this->store->get('session', $sessionId);
|
||||
if ($session !== null) {
|
||||
assert($session instanceof \SimpleSAML_Session);
|
||||
return $session;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save a session to the data store.
|
||||
*
|
||||
* @param \SimpleSAML_Session $session The session object we should save.
|
||||
*/
|
||||
public function saveSession(\SimpleSAML_Session $session)
|
||||
{
|
||||
$sessionId = $session->getSessionId();
|
||||
|
||||
$config = \SimpleSAML_Configuration::getInstance();
|
||||
$sessionDuration = $config->getInteger('session.duration', 8 * 60 * 60);
|
||||
$expire = time() + $sessionDuration;
|
||||
|
||||
$this->store->set('session', $sessionId, $session, $expire);
|
||||
}
|
||||
}
|
||||
100
lib/SimpleSAML/Stats.php
Executable file
100
lib/SimpleSAML/Stats.php
Executable file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* Statistics handler class.
|
||||
*
|
||||
* This class is responsible for taking a statistics event and logging it.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SimpleSAML_Stats
|
||||
{
|
||||
|
||||
/**
|
||||
* Whether this class is initialized.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private static $initialized = false;
|
||||
|
||||
|
||||
/**
|
||||
* The statistics output callbacks.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $outputs = null;
|
||||
|
||||
|
||||
/**
|
||||
* Create an output from a configuration object.
|
||||
*
|
||||
* @param SimpleSAML_Configuration $config The configuration object.
|
||||
*
|
||||
* @return mixed A new instance of the configured class.
|
||||
*/
|
||||
private static function createOutput(SimpleSAML_Configuration $config)
|
||||
{
|
||||
$cls = $config->getString('class');
|
||||
$cls = SimpleSAML\Module::resolveClass($cls, 'Stats_Output', 'SimpleSAML_Stats_Output');
|
||||
|
||||
$output = new $cls($config);
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the outputs.
|
||||
*/
|
||||
private static function initOutputs()
|
||||
{
|
||||
|
||||
$config = SimpleSAML_Configuration::getInstance();
|
||||
$outputCfgs = $config->getConfigList('statistics.out', array());
|
||||
|
||||
self::$outputs = array();
|
||||
foreach ($outputCfgs as $cfg) {
|
||||
self::$outputs[] = self::createOutput($cfg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Notify about an event.
|
||||
*
|
||||
* @param string $event The event.
|
||||
* @param array $data Event data. Optional.
|
||||
*
|
||||
* @return void|boolean False if output is not enabled, void otherwise.
|
||||
*/
|
||||
public static function log($event, array $data = array())
|
||||
{
|
||||
assert(is_string($event));
|
||||
assert(!isset($data['op']));
|
||||
assert(!isset($data['time']));
|
||||
assert(!isset($data['_id']));
|
||||
|
||||
if (!self::$initialized) {
|
||||
self::initOutputs();
|
||||
self::$initialized = true;
|
||||
}
|
||||
|
||||
if (empty(self::$outputs)) {
|
||||
// not enabled
|
||||
return;
|
||||
}
|
||||
|
||||
$data['op'] = $event;
|
||||
$data['time'] = microtime(true);
|
||||
|
||||
// the ID generation is designed to cluster IDs related in time close together
|
||||
$int_t = (int) $data['time'];
|
||||
$hd = openssl_random_pseudo_bytes(16);
|
||||
$data['_id'] = sprintf('%016x%s', $int_t, bin2hex($hd));
|
||||
|
||||
foreach (self::$outputs as $out) {
|
||||
$out->emit($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
lib/SimpleSAML/Stats/Output.php
Executable file
29
lib/SimpleSAML/Stats/Output.php
Executable file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* Interface for statistics outputs.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
abstract class SimpleSAML_Stats_Output
|
||||
{
|
||||
|
||||
/**
|
||||
* Initialize the output.
|
||||
*
|
||||
* @param SimpleSAML_Configuration $config The configuration for this output.
|
||||
*/
|
||||
public function __construct(SimpleSAML_Configuration $config)
|
||||
{
|
||||
// do nothing by default
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write a stats event.
|
||||
*
|
||||
* @param array $data The event.
|
||||
*/
|
||||
abstract public function emit(array $data);
|
||||
}
|
||||
106
lib/SimpleSAML/Store.php
Executable file
106
lib/SimpleSAML/Store.php
Executable file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleSAML;
|
||||
|
||||
use SimpleSAML\Error\CriticalConfigurationError;
|
||||
|
||||
/**
|
||||
* Base class for data stores.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
abstract class Store
|
||||
{
|
||||
/**
|
||||
* Our singleton instance.
|
||||
*
|
||||
* This is false if the data store isn't enabled, and null if we haven't attempted to initialize it.
|
||||
*
|
||||
* @var \SimpleSAML\Store|false|null
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve our singleton instance.
|
||||
*
|
||||
* @return false|\SimpleSAML\Store The data store, or false if it isn't enabled.
|
||||
*
|
||||
* @throws \SimpleSAML\Error\CriticalConfigurationError
|
||||
*/
|
||||
public static function getInstance()
|
||||
{
|
||||
if (self::$instance !== null) {
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
$config = \SimpleSAML_Configuration::getInstance();
|
||||
$storeType = $config->getString('store.type', null);
|
||||
if ($storeType === null) {
|
||||
$storeType = $config->getString('session.handler', 'phpsession');
|
||||
}
|
||||
|
||||
switch ($storeType) {
|
||||
case 'phpsession':
|
||||
// we cannot support advanced features with the PHP session store
|
||||
self::$instance = false;
|
||||
break;
|
||||
case 'memcache':
|
||||
self::$instance = new Store\Memcache();
|
||||
break;
|
||||
case 'sql':
|
||||
self::$instance = new Store\SQL();
|
||||
break;
|
||||
case 'redis':
|
||||
self::$instance = new Store\Redis();
|
||||
break;
|
||||
default:
|
||||
// datastore from module
|
||||
try {
|
||||
$className = Module::resolveClass($storeType, 'Store', '\SimpleSAML\Store');
|
||||
} catch (\Exception $e) {
|
||||
$c = $config->toArray();
|
||||
$c['store.type'] = 'phpsession';
|
||||
throw new CriticalConfigurationError(
|
||||
"Invalid 'store.type' configuration option. Cannot find store '$storeType'.",
|
||||
null,
|
||||
$c
|
||||
);
|
||||
}
|
||||
self::$instance = new $className();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve a value from the data store.
|
||||
*
|
||||
* @param string $type The data type.
|
||||
* @param string $key The key.
|
||||
*
|
||||
* @return mixed|null The value.
|
||||
*/
|
||||
abstract public function get($type, $key);
|
||||
|
||||
|
||||
/**
|
||||
* Save a value to the data store.
|
||||
*
|
||||
* @param string $type The data type.
|
||||
* @param string $key The key.
|
||||
* @param mixed $value The value.
|
||||
* @param int|null $expire The expiration time (unix timestamp), or null if it never expires.
|
||||
*/
|
||||
abstract public function set($type, $key, $value, $expire = null);
|
||||
|
||||
|
||||
/**
|
||||
* Delete a value from the data store.
|
||||
*
|
||||
* @param string $type The data type.
|
||||
* @param string $key The key.
|
||||
*/
|
||||
abstract public function delete($type, $key);
|
||||
}
|
||||
84
lib/SimpleSAML/Store/Memcache.php
Executable file
84
lib/SimpleSAML/Store/Memcache.php
Executable file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleSAML\Store;
|
||||
|
||||
use \SimpleSAML_Configuration as Configuration;
|
||||
use \SimpleSAML\Store;
|
||||
|
||||
/**
|
||||
* A memcache based data store.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class Memcache extends Store
|
||||
{
|
||||
/**
|
||||
* This variable contains the session name prefix.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $prefix;
|
||||
|
||||
|
||||
/**
|
||||
* This function implements the constructor for this class. It loads the Memcache configuration.
|
||||
*/
|
||||
protected function __construct()
|
||||
{
|
||||
$config = Configuration::getInstance();
|
||||
$this->prefix = $config->getString('memcache_store.prefix', 'simpleSAMLphp');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve a value from the data store.
|
||||
*
|
||||
* @param string $type The data type.
|
||||
* @param string $key The key.
|
||||
* @return mixed|null The value.
|
||||
*/
|
||||
public function get($type, $key)
|
||||
{
|
||||
assert(is_string($type));
|
||||
assert(is_string($key));
|
||||
|
||||
return \SimpleSAML_Memcache::get($this->prefix . '.' . $type . '.' . $key);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save a value to the data store.
|
||||
*
|
||||
* @param string $type The data type.
|
||||
* @param string $key The key.
|
||||
* @param mixed $value The value.
|
||||
* @param int|NULL $expire The expiration time (unix timestamp), or NULL if it never expires.
|
||||
*/
|
||||
public function set($type, $key, $value, $expire = null)
|
||||
{
|
||||
assert(is_string($type));
|
||||
assert(is_string($key));
|
||||
assert($expire === null || (is_int($expire) && $expire > 2592000));
|
||||
|
||||
if ($expire === null) {
|
||||
$expire = 0;
|
||||
}
|
||||
|
||||
\SimpleSAML_Memcache::set($this->prefix . '.' . $type . '.' . $key, $value, $expire);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete a value from the data store.
|
||||
*
|
||||
* @param string $type The data type.
|
||||
* @param string $key The key.
|
||||
*/
|
||||
public function delete($type, $key)
|
||||
{
|
||||
assert(is_string($type));
|
||||
assert(is_string($key));
|
||||
|
||||
\SimpleSAML_Memcache::delete($this->prefix . '.' . $type . '.' . $key);
|
||||
}
|
||||
}
|
||||
119
lib/SimpleSAML/Store/Redis.php
Executable file
119
lib/SimpleSAML/Store/Redis.php
Executable file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleSAML\Store;
|
||||
|
||||
use \SimpleSAML_Configuration as Configuration;
|
||||
use \SimpleSAML\Store;
|
||||
|
||||
/**
|
||||
* A data store using Redis to keep the data.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class Redis extends Store
|
||||
{
|
||||
public $redis;
|
||||
|
||||
/**
|
||||
* Initialize the Redis data store.
|
||||
*/
|
||||
public function __construct($redis = null)
|
||||
{
|
||||
assert($redis === null || is_subclass_of($redis, 'Predis\\Client'));
|
||||
|
||||
if (!class_exists('\Predis\Client')) {
|
||||
throw new \SimpleSAML\Error\CriticalConfigurationError('predis/predis is not available.');
|
||||
}
|
||||
|
||||
if ($redis === null) {
|
||||
$config = Configuration::getInstance();
|
||||
|
||||
$host = $config->getString('store.redis.host', 'localhost');
|
||||
$port = $config->getInteger('store.redis.port', 6379);
|
||||
$prefix = $config->getString('store.redis.prefix', 'SimpleSAMLphp');
|
||||
|
||||
$redis = new \Predis\Client(
|
||||
array(
|
||||
'scheme' => 'tcp',
|
||||
'host' => $host,
|
||||
'port' => $port,
|
||||
),
|
||||
array(
|
||||
'prefix' => $prefix,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->redis = $redis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deconstruct the Redis data store.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if (method_exists($this->redis, 'disconnect')) {
|
||||
$this->redis->disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a value from the data store.
|
||||
*
|
||||
* @param string $type The type of the data.
|
||||
* @param string $key The key to retrieve.
|
||||
*
|
||||
* @return mixed|null The value associated with that key, or null if there's no such key.
|
||||
*/
|
||||
public function get($type, $key)
|
||||
{
|
||||
assert(is_string($type));
|
||||
assert(is_string($key));
|
||||
|
||||
$result = $this->redis->get("{$type}.{$key}");
|
||||
|
||||
if ($result === false || $result === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return unserialize($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a value in the data store.
|
||||
*
|
||||
* @param string $type The type of the data.
|
||||
* @param string $key The key to insert.
|
||||
* @param mixed $value The value itself.
|
||||
* @param int|null $expire The expiration time (unix timestamp), or null if it never expires.
|
||||
*/
|
||||
public function set($type, $key, $value, $expire = null)
|
||||
{
|
||||
assert(is_string($type));
|
||||
assert(is_string($key));
|
||||
assert($expire === null || (is_int($expire) && $expire > 2592000));
|
||||
|
||||
$serialized = serialize($value);
|
||||
|
||||
if ($expire === null) {
|
||||
$this->redis->set("{$type}.{$key}", $serialized);
|
||||
} else {
|
||||
// setex expire time is in seconds (not unix timestamp)
|
||||
$this->redis->setex("{$type}.{$key}", $expire - time(), $serialized);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an entry from the data store.
|
||||
*
|
||||
* @param string $type The type of the data
|
||||
* @param string $key The key to delete.
|
||||
*/
|
||||
public function delete($type, $key)
|
||||
{
|
||||
assert(is_string($type));
|
||||
assert(is_string($key));
|
||||
|
||||
$this->redis->del("{$type}.{$key}");
|
||||
}
|
||||
}
|
||||
389
lib/SimpleSAML/Store/SQL.php
Executable file
389
lib/SimpleSAML/Store/SQL.php
Executable file
@@ -0,0 +1,389 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleSAML\Store;
|
||||
|
||||
use \SimpleSAML_Configuration as Configuration;
|
||||
use \SimpleSAML\Logger;
|
||||
use \SimpleSAML\Store;
|
||||
|
||||
/**
|
||||
* A data store using a RDBMS to keep the data.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SQL extends Store
|
||||
{
|
||||
/**
|
||||
* The PDO object for our database.
|
||||
*
|
||||
* @var \PDO
|
||||
*/
|
||||
public $pdo;
|
||||
|
||||
|
||||
/**
|
||||
* Our database driver.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $driver;
|
||||
|
||||
|
||||
/**
|
||||
* The prefix we should use for our tables.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $prefix;
|
||||
|
||||
|
||||
/**
|
||||
* Associative array of table versions.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $tableVersions;
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the SQL data store.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = Configuration::getInstance();
|
||||
|
||||
$dsn = $config->getString('store.sql.dsn');
|
||||
$username = $config->getString('store.sql.username', null);
|
||||
$password = $config->getString('store.sql.password', null);
|
||||
$options = $config->getArray('store.sql.options', null);
|
||||
$this->prefix = $config->getString('store.sql.prefix', 'simpleSAMLphp');
|
||||
try {
|
||||
$this->pdo = new \PDO($dsn, $username, $password, $options);
|
||||
} catch (\PDOException $e) {
|
||||
throw new \Exception("Database error: " . $e->getMessage());
|
||||
}
|
||||
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
$this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
|
||||
|
||||
if ($this->driver === 'mysql') {
|
||||
$this->pdo->exec('SET time_zone = "+00:00"');
|
||||
}
|
||||
|
||||
$this->initTableVersionTable();
|
||||
$this->initKVTable();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the table-version table.
|
||||
*/
|
||||
private function initTableVersionTable()
|
||||
{
|
||||
$this->tableVersions = array();
|
||||
|
||||
try {
|
||||
$fetchTableVersion = $this->pdo->query('SELECT _name, _version FROM '.$this->prefix.'_tableVersion');
|
||||
} catch (\PDOException $e) {
|
||||
$this->pdo->exec(
|
||||
'CREATE TABLE '.$this->prefix.
|
||||
'_tableVersion (_name VARCHAR(30) NOT NULL UNIQUE, _version INTEGER NOT NULL)'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
while (($row = $fetchTableVersion->fetch(\PDO::FETCH_ASSOC)) !== false) {
|
||||
$this->tableVersions[$row['_name']] = (int) $row['_version'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize key-value table.
|
||||
*/
|
||||
private function initKVTable()
|
||||
{
|
||||
$current_version = $this->getTableVersion('kvstore');
|
||||
|
||||
$text_t = 'TEXT';
|
||||
if ($this->driver === 'mysql') {
|
||||
// TEXT data type has size constraints that can be hit at some point, so we use LONGTEXT instead
|
||||
$text_t = 'LONGTEXT';
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries for updates, grouped by version.
|
||||
* New updates can be added as a new array in this array
|
||||
*/
|
||||
$table_updates = array(
|
||||
array(
|
||||
'CREATE TABLE '.$this->prefix.
|
||||
'_kvstore (_type VARCHAR(30) NOT NULL, _key VARCHAR(50) NOT NULL, _value '.$text_t.
|
||||
' NOT NULL, _expire TIMESTAMP, PRIMARY KEY (_key, _type))',
|
||||
'CREATE INDEX '.$this->prefix.'_kvstore_expire ON '.$this->prefix.'_kvstore (_expire)'
|
||||
),
|
||||
/**
|
||||
* This upgrade removes the default NOT NULL constraint on the _expire field in MySQL.
|
||||
* Because SQLite does not support field alterations, the approach is to:
|
||||
* Create a new table without the NOT NULL constraint
|
||||
* Copy the current data to the new table
|
||||
* Drop the old table
|
||||
* Rename the new table correctly
|
||||
* Readd the index
|
||||
*/
|
||||
array(
|
||||
'CREATE TABLE '.$this->prefix.
|
||||
'_kvstore_new (_type VARCHAR(30) NOT NULL, _key VARCHAR(50) NOT NULL, _value '.$text_t.
|
||||
' NOT NULL, _expire TIMESTAMP NULL, PRIMARY KEY (_key, _type))',
|
||||
'INSERT INTO '.$this->prefix.'_kvstore_new SELECT * FROM ' . $this->prefix.'_kvstore',
|
||||
'DROP TABLE '.$this->prefix.'_kvstore',
|
||||
'ALTER TABLE '.$this->prefix.'_kvstore_new RENAME TO ' . $this->prefix . '_kvstore',
|
||||
'CREATE INDEX '.$this->prefix.'_kvstore_expire ON '.$this->prefix.'_kvstore (_expire)'
|
||||
)
|
||||
);
|
||||
|
||||
$latest_version = count($table_updates);
|
||||
|
||||
if ($current_version == $latest_version) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only run queries for after the current version
|
||||
$updates_to_run = array_slice($table_updates, $current_version);
|
||||
|
||||
foreach ($updates_to_run as $version_updates) {
|
||||
foreach ($version_updates as $query) {
|
||||
$this->pdo->exec($query);
|
||||
}
|
||||
}
|
||||
|
||||
$this->setTableVersion('kvstore', $latest_version);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get table version.
|
||||
*
|
||||
* @param string $name Table name.
|
||||
*
|
||||
* @return int The table version, or 0 if the table doesn't exist.
|
||||
*/
|
||||
public function getTableVersion($name)
|
||||
{
|
||||
assert(is_string($name));
|
||||
|
||||
if (!isset($this->tableVersions[$name])) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $this->tableVersions[$name];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set table version.
|
||||
*
|
||||
* @param string $name Table name.
|
||||
* @param int $version Table version.
|
||||
*/
|
||||
public function setTableVersion($name, $version)
|
||||
{
|
||||
assert(is_string($name));
|
||||
assert(is_int($version));
|
||||
|
||||
$this->insertOrUpdate(
|
||||
$this->prefix.'_tableVersion',
|
||||
array('_name'),
|
||||
array('_name' => $name, '_version' => $version)
|
||||
);
|
||||
$this->tableVersions[$name] = $version;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Insert or update a key-value in the store.
|
||||
*
|
||||
* Since various databases implement different methods for doing this, we abstract it away here.
|
||||
*
|
||||
* @param string $table The table we should update.
|
||||
* @param array $keys The key columns.
|
||||
* @param array $data Associative array with columns.
|
||||
*/
|
||||
public function insertOrUpdate($table, array $keys, array $data)
|
||||
{
|
||||
assert(is_string($table));
|
||||
|
||||
$colNames = '('.implode(', ', array_keys($data)).')';
|
||||
$values = 'VALUES(:'.implode(', :', array_keys($data)).')';
|
||||
|
||||
switch ($this->driver) {
|
||||
case 'mysql':
|
||||
$query = 'REPLACE INTO '.$table.' '.$colNames.' '.$values;
|
||||
$query = $this->pdo->prepare($query);
|
||||
$query->execute($data);
|
||||
return;
|
||||
case 'sqlite':
|
||||
$query = 'INSERT OR REPLACE INTO '.$table.' '.$colNames.' '.$values;
|
||||
$query = $this->pdo->prepare($query);
|
||||
$query->execute($data);
|
||||
return;
|
||||
}
|
||||
|
||||
// default implementation, try INSERT, and UPDATE if that fails.
|
||||
$insertQuery = 'INSERT INTO '.$table.' '.$colNames.' '.$values;
|
||||
$insertQuery = $this->pdo->prepare($insertQuery);
|
||||
try {
|
||||
$insertQuery->execute($data);
|
||||
return;
|
||||
} catch (\PDOException $e) {
|
||||
$ecode = (string) $e->getCode();
|
||||
switch ($ecode) {
|
||||
case '23505': // PostgreSQL
|
||||
break;
|
||||
default:
|
||||
Logger::error('Error while saving data: '.$e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
$updateCols = array();
|
||||
$condCols = array();
|
||||
foreach ($data as $col => $value) {
|
||||
$tmp = $col.' = :'.$col;
|
||||
|
||||
if (in_array($col, $keys, true)) {
|
||||
$condCols[] = $tmp;
|
||||
} else {
|
||||
$updateCols[] = $tmp;
|
||||
}
|
||||
}
|
||||
|
||||
$updateQuery = 'UPDATE '.$table.' SET '.implode(',', $updateCols).' WHERE '.implode(' AND ', $condCols);
|
||||
$updateQuery = $this->pdo->prepare($updateQuery);
|
||||
$updateQuery->execute($data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clean the key-value table of expired entries.
|
||||
*/
|
||||
private function cleanKVStore()
|
||||
{
|
||||
Logger::debug('store.sql: Cleaning key-value store.');
|
||||
|
||||
$query = 'DELETE FROM '.$this->prefix.'_kvstore WHERE _expire < :now';
|
||||
$params = array('now' => gmdate('Y-m-d H:i:s'));
|
||||
|
||||
$query = $this->pdo->prepare($query);
|
||||
$query->execute($params);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve a value from the data store.
|
||||
*
|
||||
* @param string $type The type of the data.
|
||||
* @param string $key The key to retrieve.
|
||||
*
|
||||
* @return mixed|null The value associated with that key, or null if there's no such key.
|
||||
*/
|
||||
public function get($type, $key)
|
||||
{
|
||||
assert(is_string($type));
|
||||
assert(is_string($key));
|
||||
|
||||
if (strlen($key) > 50) {
|
||||
$key = sha1($key);
|
||||
}
|
||||
|
||||
$query = 'SELECT _value FROM '.$this->prefix.
|
||||
'_kvstore WHERE _type = :type AND _key = :key AND (_expire IS NULL OR _expire > :now)';
|
||||
$params = array('type' => $type, 'key' => $key, 'now' => gmdate('Y-m-d H:i:s'));
|
||||
|
||||
$query = $this->pdo->prepare($query);
|
||||
$query->execute($params);
|
||||
|
||||
$row = $query->fetch(\PDO::FETCH_ASSOC);
|
||||
if ($row === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$value = $row['_value'];
|
||||
if (is_resource($value)) {
|
||||
$value = stream_get_contents($value);
|
||||
}
|
||||
$value = urldecode($value);
|
||||
$value = unserialize($value);
|
||||
|
||||
if ($value === false) {
|
||||
return null;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save a value in the data store.
|
||||
*
|
||||
* @param string $type The type of the data.
|
||||
* @param string $key The key to insert.
|
||||
* @param mixed $value The value itself.
|
||||
* @param int|null $expire The expiration time (unix timestamp), or null if it never expires.
|
||||
*/
|
||||
public function set($type, $key, $value, $expire = null)
|
||||
{
|
||||
assert(is_string($type));
|
||||
assert(is_string($key));
|
||||
assert($expire === null || (is_int($expire) && $expire > 2592000));
|
||||
|
||||
if (rand(0, 1000) < 10) {
|
||||
$this->cleanKVStore();
|
||||
}
|
||||
|
||||
if (strlen($key) > 50) {
|
||||
$key = sha1($key);
|
||||
}
|
||||
|
||||
if ($expire !== null) {
|
||||
$expire = gmdate('Y-m-d H:i:s', $expire);
|
||||
}
|
||||
|
||||
$value = serialize($value);
|
||||
$value = rawurlencode($value);
|
||||
|
||||
$data = array(
|
||||
'_type' => $type,
|
||||
'_key' => $key,
|
||||
'_value' => $value,
|
||||
'_expire' => $expire,
|
||||
);
|
||||
|
||||
$this->insertOrUpdate($this->prefix.'_kvstore', array('_type', '_key'), $data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete an entry from the data store.
|
||||
*
|
||||
* @param string $type The type of the data
|
||||
* @param string $key The key to delete.
|
||||
*/
|
||||
public function delete($type, $key)
|
||||
{
|
||||
assert(is_string($type));
|
||||
assert(is_string($key));
|
||||
|
||||
if (strlen($key) > 50) {
|
||||
$key = sha1($key);
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'_type' => $type,
|
||||
'_key' => $key,
|
||||
);
|
||||
|
||||
$query = 'DELETE FROM '.$this->prefix.'_kvstore WHERE _type=:_type AND _key=:_key';
|
||||
$query = $this->pdo->prepare($query);
|
||||
$query->execute($data);
|
||||
}
|
||||
}
|
||||
707
lib/SimpleSAML/Utilities.php
Executable file
707
lib/SimpleSAML/Utilities.php
Executable file
@@ -0,0 +1,707 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* Misc static functions that is used several places.in example parsing and id generation.
|
||||
*
|
||||
* @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*
|
||||
* @deprecated This entire class will be removed in SimpleSAMLphp 2.0.
|
||||
*/
|
||||
class SimpleSAML_Utilities
|
||||
{
|
||||
|
||||
/**
|
||||
* @deprecated This property will be removed in SSP 2.0. Please use SimpleSAML\Logger::isErrorMasked() instead.
|
||||
*/
|
||||
public static $logMask = 0;
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::getSelfHost() instead.
|
||||
*/
|
||||
public static function getSelfHost()
|
||||
{
|
||||
return \SimpleSAML\Utils\HTTP::getSelfHost();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::getSelfURLHost() instead.
|
||||
*/
|
||||
public static function selfURLhost()
|
||||
{
|
||||
return \SimpleSAML\Utils\HTTP::getSelfURLHost();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::isHTTPS() instead.
|
||||
*/
|
||||
public static function isHTTPS()
|
||||
{
|
||||
return \SimpleSAML\Utils\HTTP::isHTTPS();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::getSelfURLNoQuery()
|
||||
* instead.
|
||||
*/
|
||||
public static function selfURLNoQuery()
|
||||
{
|
||||
return \SimpleSAML\Utils\HTTP::getSelfURLNoQuery();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::getSelfHostWithPath()
|
||||
* instead.
|
||||
*/
|
||||
public static function getSelfHostWithPath()
|
||||
{
|
||||
return \SimpleSAML\Utils\HTTP::getSelfHostWithPath();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::getFirstPathElement()
|
||||
* instead.
|
||||
*/
|
||||
public static function getFirstPathElement($trailingslash = true)
|
||||
{
|
||||
return \SimpleSAML\Utils\HTTP::getFirstPathElement($trailingslash);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::getSelfURL() instead.
|
||||
*/
|
||||
public static function selfURL()
|
||||
{
|
||||
return \SimpleSAML\Utils\HTTP::getSelfURL();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::getBaseURL() instead.
|
||||
*/
|
||||
public static function getBaseURL()
|
||||
{
|
||||
return \SimpleSAML\Utils\HTTP::getBaseURL();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::addURLParameters() instead.
|
||||
*/
|
||||
public static function addURLparameter($url, $parameters)
|
||||
{
|
||||
return \SimpleSAML\Utils\HTTP::addURLParameters($url, $parameters);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use \SimpleSAML\Utils\HTTP::checkURLAllowed() instead.
|
||||
*/
|
||||
public static function checkURLAllowed($url, array $trustedSites = null)
|
||||
{
|
||||
return \SimpleSAML\Utils\HTTP::checkURLAllowed($url, $trustedSites);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML_Auth_State::parseStateID() instead.
|
||||
*/
|
||||
public static function parseStateID($stateId)
|
||||
{
|
||||
return SimpleSAML_Auth_State::parseStateID($stateId);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0.
|
||||
*/
|
||||
public static function checkDateConditions($start = null, $end = null)
|
||||
{
|
||||
$currentTime = time();
|
||||
|
||||
if (!empty($start)) {
|
||||
$startTime = \SAML2\Utils::xsDateTimeToTimestamp($start);
|
||||
// Allow for a 10 minute difference in Time
|
||||
if (($startTime < 0) || (($startTime - 600) > $currentTime)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!empty($end)) {
|
||||
$endTime = \SAML2\Utils::xsDateTimeToTimestamp($end);
|
||||
if (($endTime < 0) || ($endTime <= $currentTime)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Random::generateID() instead.
|
||||
*/
|
||||
public static function generateID()
|
||||
{
|
||||
return SimpleSAML\Utils\Random::generateID();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use \SimpleSAML\Utils\Time::generateTimestamp()
|
||||
* instead.
|
||||
*/
|
||||
public static function generateTimestamp($instant = null)
|
||||
{
|
||||
return SimpleSAML\Utils\Time::generateTimestamp($instant);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use \SimpleSAML\Utils\Time::parseDuration() instead.
|
||||
*/
|
||||
public static function parseDuration($duration, $timestamp = null)
|
||||
{
|
||||
return SimpleSAML\Utils\Time::parseDuration($duration, $timestamp);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please raise a SimpleSAML_Error_Error exception instead.
|
||||
*/
|
||||
public static function fatalError($trackId = 'na', $errorCode = null, Exception $e = null)
|
||||
{
|
||||
throw new SimpleSAML_Error_Error($errorCode, $e);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in version 2.0. Use SimpleSAML\Utils\Net::ipCIDRcheck() instead.
|
||||
*/
|
||||
public static function ipCIDRcheck($cidr, $ip = null)
|
||||
{
|
||||
return SimpleSAML\Utils\Net::ipCIDRcheck($cidr, $ip);
|
||||
}
|
||||
|
||||
|
||||
private static function _doRedirect($url, $parameters = array())
|
||||
{
|
||||
assert(is_string($url));
|
||||
assert(!empty($url));
|
||||
assert(is_array($parameters));
|
||||
|
||||
if (!empty($parameters)) {
|
||||
$url = self::addURLparameter($url, $parameters);
|
||||
}
|
||||
|
||||
/* Set the HTTP result code. This is either 303 See Other or
|
||||
* 302 Found. HTTP 303 See Other is sent if the HTTP version
|
||||
* is HTTP/1.1 and the request type was a POST request.
|
||||
*/
|
||||
if ($_SERVER['SERVER_PROTOCOL'] === 'HTTP/1.1' &&
|
||||
$_SERVER['REQUEST_METHOD'] === 'POST'
|
||||
) {
|
||||
$code = 303;
|
||||
} else {
|
||||
$code = 302;
|
||||
}
|
||||
|
||||
if (strlen($url) > 2048) {
|
||||
SimpleSAML\Logger::warning('Redirecting to a URL longer than 2048 bytes.');
|
||||
}
|
||||
|
||||
// Set the location header
|
||||
header('Location: '.$url, true, $code);
|
||||
|
||||
// Disable caching of this response
|
||||
header('Pragma: no-cache');
|
||||
header('Cache-Control: no-cache, must-revalidate');
|
||||
|
||||
// Show a minimal web page with a clickable link to the URL
|
||||
echo '<?xml version="1.0" encoding="UTF-8"?>'."\n";
|
||||
echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"'.
|
||||
' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'."\n";
|
||||
echo '<html xmlns="http://www.w3.org/1999/xhtml">';
|
||||
echo '<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
||||
<title>Redirect</title>
|
||||
</head>';
|
||||
echo '<body>';
|
||||
echo '<h1>Redirect</h1>';
|
||||
echo '<p>';
|
||||
echo 'You were redirected to: ';
|
||||
echo '<a id="redirlink" href="'.
|
||||
htmlspecialchars($url).'">'.htmlspecialchars($url).'</a>';
|
||||
echo '<script type="text/javascript">document.getElementById("redirlink").focus();</script>';
|
||||
echo '</p>';
|
||||
echo '</body>';
|
||||
echo '</html>';
|
||||
|
||||
// End script execution
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated 1.12.0 This method will be removed from the API. Instead, use the redirectTrustedURL() or
|
||||
* redirectUntrustedURL() functions accordingly.
|
||||
*/
|
||||
public static function redirect($url, $parameters = array(), $allowed_redirect_hosts = null)
|
||||
{
|
||||
assert(is_string($url));
|
||||
assert(strlen($url) > 0);
|
||||
assert(is_array($parameters));
|
||||
|
||||
if ($allowed_redirect_hosts !== null) {
|
||||
$url = self::checkURLAllowed($url, $allowed_redirect_hosts);
|
||||
} else {
|
||||
$url = self::normalizeURL($url);
|
||||
}
|
||||
self::_doRedirect($url, $parameters);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::redirectTrustedURL()
|
||||
* instead.
|
||||
*/
|
||||
public static function redirectTrustedURL($url, $parameters = array())
|
||||
{
|
||||
\SimpleSAML\Utils\HTTP::redirectTrustedURL($url, $parameters);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::redirectUntrustedURL()
|
||||
* instead.
|
||||
*/
|
||||
public static function redirectUntrustedURL($url, $parameters = array())
|
||||
{
|
||||
\SimpleSAML\Utils\HTTP::redirectUntrustedURL($url, $parameters);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Arrays::transpose() instead.
|
||||
*/
|
||||
public static function transposeArray($in)
|
||||
{
|
||||
return SimpleSAML\Utils\Arrays::transpose($in);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\XML::isDOMNodeOfType()
|
||||
* instead.
|
||||
*/
|
||||
public static function isDOMElementOfType(DOMNode $element, $name, $nsURI)
|
||||
{
|
||||
return SimpleSAML\Utils\XML::isDOMNodeOfType($element, $name, $nsURI);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\XML::getDOMChildren() instead.
|
||||
*/
|
||||
public static function getDOMChildren(DOMElement $element, $localName, $namespaceURI)
|
||||
{
|
||||
return SimpleSAML\Utils\XML::getDOMChildren($element, $localName, $namespaceURI);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\XML::getDOMText() instead.
|
||||
*/
|
||||
public static function getDOMText($element)
|
||||
{
|
||||
return SimpleSAML\Utils\XML::getDOMText($element);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::getAcceptLanguage()
|
||||
* instead.
|
||||
*/
|
||||
public static function getAcceptLanguage()
|
||||
{
|
||||
return \SimpleSAML\Utils\HTTP::getAcceptLanguage();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\XML::isValid() instead.
|
||||
*/
|
||||
public static function validateXML($xml, $schema)
|
||||
{
|
||||
$result = \SimpleSAML\Utils\XML::isValid($xml, $schema);
|
||||
return ($result === true) ? '' : $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\XML::checkSAMLMessage() instead.
|
||||
*/
|
||||
public static function validateXMLDocument($message, $type)
|
||||
{
|
||||
\SimpleSAML\Utils\XML::checkSAMLMessage($message, $type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use openssl_random_pseudo_bytes() instead.
|
||||
*/
|
||||
public static function generateRandomBytes($length)
|
||||
{
|
||||
assert(is_int($length));
|
||||
|
||||
return openssl_random_pseudo_bytes($length);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use bin2hex() instead.
|
||||
*/
|
||||
public static function stringToHex($bytes)
|
||||
{
|
||||
$ret = '';
|
||||
for ($i = 0; $i < strlen($bytes); $i++) {
|
||||
$ret .= sprintf('%02x', ord($bytes[$i]));
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\System::resolvePath() instead.
|
||||
*/
|
||||
public static function resolvePath($path, $base = null)
|
||||
{
|
||||
return \SimpleSAML\Utils\System::resolvePath($path, $base);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::resolveURL() instead.
|
||||
*/
|
||||
public static function resolveURL($url, $base = null)
|
||||
{
|
||||
return \SimpleSAML\Utils\HTTP::resolveURL($url, $base);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::normalizeURL() instead.
|
||||
*/
|
||||
public static function normalizeURL($url)
|
||||
{
|
||||
return \SimpleSAML\Utils\HTTP::normalizeURL($url);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::parseQueryString() instead.
|
||||
*/
|
||||
public static function parseQueryString($query_string)
|
||||
{
|
||||
return \SimpleSAML\Utils\HTTP::parseQueryString($query_string);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use
|
||||
* SimpleSAML\Utils\Attributes::normalizeAttributesArray() instead.
|
||||
*/
|
||||
public static function parseAttributes($attributes)
|
||||
{
|
||||
return SimpleSAML\Utils\Attributes::normalizeAttributesArray($attributes);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Config::getSecretSalt() instead.
|
||||
*/
|
||||
public static function getSecretSalt()
|
||||
{
|
||||
return SimpleSAML\Utils\Config::getSecretSalt();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please call error_get_last() directly.
|
||||
*/
|
||||
public static function getLastError()
|
||||
{
|
||||
|
||||
if (!function_exists('error_get_last')) {
|
||||
return '[Cannot get error message]';
|
||||
}
|
||||
|
||||
$error = error_get_last();
|
||||
if ($error === null) {
|
||||
return '[No error message found]';
|
||||
}
|
||||
|
||||
return $error['message'];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Config::getCertPath() instead.
|
||||
*/
|
||||
public static function resolveCert($path)
|
||||
{
|
||||
return \SimpleSAML\Utils\Config::getCertPath($path);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Crypto::loadPublicKey() instead.
|
||||
*/
|
||||
public static function loadPublicKey(SimpleSAML_Configuration $metadata, $required = false, $prefix = '')
|
||||
{
|
||||
return SimpleSAML\Utils\Crypto::loadPublicKey($metadata, $required, $prefix);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Crypto::loadPrivateKey() instead.
|
||||
*/
|
||||
public static function loadPrivateKey(SimpleSAML_Configuration $metadata, $required = false, $prefix = '')
|
||||
{
|
||||
return SimpleSAML\Utils\Crypto::loadPrivateKey($metadata, $required, $prefix);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\XML::formatDOMElement() instead.
|
||||
*/
|
||||
public static function formatDOMElement(DOMElement $root, $indentBase = '')
|
||||
{
|
||||
SimpleSAML\Utils\XML::formatDOMElement($root, $indentBase);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\XML::formatXMLString() instead.
|
||||
*/
|
||||
public static function formatXMLString($xml, $indentBase = '')
|
||||
{
|
||||
return SimpleSAML\Utils\XML::formatXMLString($xml, $indentBase);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Arrays::arrayize() instead.
|
||||
*/
|
||||
public static function arrayize($data, $index = 0)
|
||||
{
|
||||
return SimpleSAML\Utils\Arrays::arrayize($data, $index);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Auth::isAdmin() instead.
|
||||
*/
|
||||
public static function isAdmin()
|
||||
{
|
||||
return SimpleSAML\Utils\Auth::isAdmin();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Auth::getAdminLoginURL instead();
|
||||
*/
|
||||
public static function getAdminLoginURL($returnTo = null)
|
||||
{
|
||||
return SimpleSAML\Utils\Auth::getAdminLoginURL($returnTo);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Auth::requireAdmin() instead.
|
||||
*/
|
||||
public static function requireAdmin()
|
||||
{
|
||||
\SimpleSAML\Utils\Auth::requireAdmin();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::submitPOSTData() instead.
|
||||
*/
|
||||
public static function postRedirect($destination, $post)
|
||||
{
|
||||
\SimpleSAML\Utils\HTTP::submitPOSTData($destination, $post);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. PLease use SimpleSAML\Utils\HTTP::getPOSTRedirectURL()
|
||||
* instead.
|
||||
*/
|
||||
public static function createPostRedirectLink($destination, $post)
|
||||
{
|
||||
return \SimpleSAML\Utils\HTTP::getPOSTRedirectURL($destination, $post);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::getPOSTRedirectURL()
|
||||
* instead.
|
||||
*/
|
||||
public static function createHttpPostRedirectLink($destination, $post)
|
||||
{
|
||||
assert(is_string($destination));
|
||||
assert(is_array($post));
|
||||
|
||||
$postId = SimpleSAML\Utils\Random::generateID();
|
||||
$postData = array(
|
||||
'post' => $post,
|
||||
'url' => $destination,
|
||||
);
|
||||
|
||||
$session = SimpleSAML_Session::getSessionFromRequest();
|
||||
$session->setData('core_postdatalink', $postId, $postData);
|
||||
|
||||
$redirInfo = base64_encode(SimpleSAML\Utils\Crypto::aesEncrypt($session->getSessionId().':'.$postId));
|
||||
|
||||
$url = SimpleSAML\Module::getModuleURL('core/postredirect.php', array('RedirInfo' => $redirInfo));
|
||||
$url = preg_replace("#^https:#", "http:", $url);
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0.
|
||||
*/
|
||||
public static function validateCA($certificate, $caFile)
|
||||
{
|
||||
\SimpleSAML\XML\Validator::validateCertificate($certificate, $caFile);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Time::initTimezone() instead.
|
||||
*/
|
||||
public static function initTimezone()
|
||||
{
|
||||
\SimpleSAML\Utils\Time::initTimezone();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\System::writeFile() instead.
|
||||
*/
|
||||
public static function writeFile($filename, $data, $mode = 0600)
|
||||
{
|
||||
\SimpleSAML\Utils\System::writeFile($filename, $data, $mode);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\System::getTempDir instead.
|
||||
*/
|
||||
public static function getTempDir()
|
||||
{
|
||||
return SimpleSAML\Utils\System::getTempDir();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Logger::maskErrors() instead.
|
||||
*/
|
||||
public static function maskErrors($mask)
|
||||
{
|
||||
SimpleSAML\Logger::maskErrors($mask);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Logger::popErrorMask() instead.
|
||||
*/
|
||||
public static function popErrorMask()
|
||||
{
|
||||
SimpleSAML\Logger::popErrorMask();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use
|
||||
* SimpleSAML\Utils\Config\Metadata::getDefaultEndpoint() instead.
|
||||
*/
|
||||
public static function getDefaultEndpoint(array $endpoints, array $bindings = null)
|
||||
{
|
||||
return \SimpleSAML\Utils\Config\Metadata::getDefaultEndpoint($endpoints, $bindings);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::checkSessionCookie()
|
||||
* instead.
|
||||
*/
|
||||
public static function checkCookie($retryURL = null)
|
||||
{
|
||||
\SimpleSAML\Utils\HTTP::checkSessionCookie($retryURL);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\XML::debugSAMLMessage() instead.
|
||||
*/
|
||||
public static function debugMessage($message, $type)
|
||||
{
|
||||
\SimpleSAML\Utils\XML::debugSAMLMessage($message, $type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::fetch() instead.
|
||||
*/
|
||||
public static function fetch($path, $context = array(), $getHeaders = false)
|
||||
{
|
||||
return \SimpleSAML\Utils\HTTP::fetch($path, $context, $getHeaders);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Crypto::aesEncrypt() instead.
|
||||
*/
|
||||
public static function aesEncrypt($clear)
|
||||
{
|
||||
return SimpleSAML\Utils\Crypto::aesEncrypt($clear);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Crypto::aesDecrypt() instead.
|
||||
*/
|
||||
public static function aesDecrypt($encData)
|
||||
{
|
||||
return SimpleSAML\Utils\Crypto::aesDecrypt($encData);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\System::getOS() instead.
|
||||
*/
|
||||
public static function isWindowsOS()
|
||||
{
|
||||
return SimpleSAML\Utils\System::getOS() === SimpleSAML\Utils\System::WINDOWS;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::setCookie() instead.
|
||||
*/
|
||||
public static function setCookie($name, $value, array $params = null, $throw = true)
|
||||
{
|
||||
\SimpleSAML\Utils\HTTP::setCookie($name, $value, $params, $throw);
|
||||
}
|
||||
}
|
||||
60
lib/SimpleSAML/Utils/Arrays.php
Executable file
60
lib/SimpleSAML/Utils/Arrays.php
Executable file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
namespace SimpleSAML\Utils;
|
||||
|
||||
/**
|
||||
* Array-related utility methods.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class Arrays
|
||||
{
|
||||
|
||||
/**
|
||||
* Put a non-array variable into an array.
|
||||
*
|
||||
* @param mixed $data The data to place into an array.
|
||||
* @param mixed $index The index or key of the array where to place the data. Defaults to 0.
|
||||
*
|
||||
* @return array An array with one element containing $data, with key $index, or $data itself if it's already an
|
||||
* array.
|
||||
*
|
||||
* @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no>
|
||||
* @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no>
|
||||
*/
|
||||
public static function arrayize($data, $index = 0)
|
||||
{
|
||||
return (is_array($data)) ? $data : array($index => $data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function transposes a two-dimensional array, so that $a['k1']['k2'] becomes $a['k2']['k1'].
|
||||
*
|
||||
* @param array $array The two-dimensional array to transpose.
|
||||
*
|
||||
* @return mixed The transposed array, or false if $array is not a valid two-dimensional array.
|
||||
*
|
||||
* @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no>
|
||||
*/
|
||||
public static function transpose($array)
|
||||
{
|
||||
if (!is_array($array)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$ret = array();
|
||||
foreach ($array as $k1 => $a2) {
|
||||
if (!is_array($a2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($a2 as $k2 => $v) {
|
||||
if (!array_key_exists($k2, $ret)) {
|
||||
$ret[$k2] = array();
|
||||
}
|
||||
$ret[$k2][$k1] = $v;
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
131
lib/SimpleSAML/Utils/Attributes.php
Executable file
131
lib/SimpleSAML/Utils/Attributes.php
Executable file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
namespace SimpleSAML\Utils;
|
||||
|
||||
/**
|
||||
* Attribute-related utility methods.
|
||||
*
|
||||
* @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no>
|
||||
* @package SimpleSAML
|
||||
*/
|
||||
class Attributes
|
||||
{
|
||||
|
||||
/**
|
||||
* Look for an attribute in a normalized attributes array, failing if it's not there.
|
||||
*
|
||||
* @param array $attributes The normalized array containing attributes.
|
||||
* @param string $expected The name of the attribute we are looking for.
|
||||
* @param bool $allow_multiple Whether to allow multiple values in the attribute or not.
|
||||
*
|
||||
* @return mixed The value of the attribute we are expecting. If the attribute has multiple values and
|
||||
* $allow_multiple is set to true, the first value will be returned.
|
||||
*
|
||||
* @throws \InvalidArgumentException If $attributes is not an array or $expected is not a string.
|
||||
* @throws \SimpleSAML_Error_Exception If the expected attribute was not found in the attributes array.
|
||||
*/
|
||||
public static function getExpectedAttribute($attributes, $expected, $allow_multiple = false)
|
||||
{
|
||||
if (!is_array($attributes)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'The attributes array is not an array, it is: '.print_r($attributes, true).'.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!is_string($expected)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'The expected attribute is not a string, it is: '.print_r($expected, true).'.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!array_key_exists($expected, $attributes)) {
|
||||
throw new \SimpleSAML_Error_Exception("No such attribute '".$expected."' found.");
|
||||
}
|
||||
$attribute = $attributes[$expected];
|
||||
|
||||
if (!is_array($attribute)) {
|
||||
throw new \InvalidArgumentException('The attributes array is not normalized, values should be arrays.');
|
||||
}
|
||||
|
||||
if (count($attribute) === 0) {
|
||||
throw new \SimpleSAML_Error_Exception("Empty attribute '".$expected."'.'");
|
||||
} elseif (count($attribute) > 1) {
|
||||
if ($allow_multiple === false) {
|
||||
throw new \SimpleSAML_Error_Exception(
|
||||
'More than one value found for the attribute, multiple values not allowed.'
|
||||
);
|
||||
}
|
||||
}
|
||||
return reset($attribute);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate and normalize an array with attributes.
|
||||
*
|
||||
* This function takes in an associative array with attributes, and parses and validates
|
||||
* this array. On success, it will return a normalized array, where each attribute name
|
||||
* is an index to an array of one or more strings. On failure an exception will be thrown.
|
||||
* This exception will contain an message describing what is wrong.
|
||||
*
|
||||
* @param array $attributes The array containing attributes that we should validate and normalize.
|
||||
*
|
||||
* @return array The normalized attributes array.
|
||||
* @throws \InvalidArgumentException If input is not an array, array keys are not strings or attribute values are
|
||||
* not strings.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
|
||||
* @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no>
|
||||
*/
|
||||
public static function normalizeAttributesArray($attributes)
|
||||
{
|
||||
if (!is_array($attributes)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'The attributes array is not an array, it is: '.print_r($attributes, true).'".'
|
||||
);
|
||||
}
|
||||
|
||||
$newAttrs = array();
|
||||
foreach ($attributes as $name => $values) {
|
||||
if (!is_string($name)) {
|
||||
throw new \InvalidArgumentException('Invalid attribute name: "'.print_r($name, true).'".');
|
||||
}
|
||||
|
||||
$values = Arrays::arrayize($values);
|
||||
|
||||
foreach ($values as $value) {
|
||||
if (!is_string($value)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Invalid attribute value for attribute '.$name.': "'.print_r($value, true).'".'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$newAttrs[$name] = $values;
|
||||
}
|
||||
|
||||
return $newAttrs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract an attribute's namespace, or revert to default.
|
||||
*
|
||||
* This function takes in a namespaced attribute name and splits it in a namespace/attribute name tuple.
|
||||
* When no namespace is found in the attribute name, it will be namespaced with the default namespace.
|
||||
* This default namespace can be overriden by supplying a second parameter to this function.
|
||||
*
|
||||
* @param string $name The namespaced attribute name.
|
||||
* @param string $defaultns The default namespace that should be used when no namespace is found.
|
||||
*
|
||||
* @return array The attribute name, split to the namespace and the actual attribute name.
|
||||
*/
|
||||
public static function getAttributeNamespace($name, $defaultns)
|
||||
{
|
||||
$slash = strrpos($name, '/');
|
||||
if ($slash !== false) {
|
||||
$defaultns = substr($name, 0, $slash);
|
||||
$name = substr($name, $slash + 1);
|
||||
}
|
||||
return array(htmlspecialchars($defaultns), htmlspecialchars($name));
|
||||
}
|
||||
}
|
||||
76
lib/SimpleSAML/Utils/Auth.php
Executable file
76
lib/SimpleSAML/Utils/Auth.php
Executable file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
namespace SimpleSAML\Utils;
|
||||
|
||||
use SimpleSAML\Module;
|
||||
|
||||
/**
|
||||
* Auth-related utility methods.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class Auth
|
||||
{
|
||||
|
||||
/**
|
||||
* Retrieve a admin login URL.
|
||||
*
|
||||
* @param string|NULL $returnTo The URL the user should arrive on after admin authentication. Defaults to null.
|
||||
*
|
||||
* @return string A URL which can be used for admin authentication.
|
||||
* @throws \InvalidArgumentException If $returnTo is neither a string nor null.
|
||||
*/
|
||||
public static function getAdminLoginURL($returnTo = null)
|
||||
{
|
||||
if (!(is_string($returnTo) || is_null($returnTo))) {
|
||||
throw new \InvalidArgumentException('Invalid input parameters.');
|
||||
}
|
||||
|
||||
if ($returnTo === null) {
|
||||
$returnTo = HTTP::getSelfURL();
|
||||
}
|
||||
|
||||
return Module::getModuleURL('core/login-admin.php', array('ReturnTo' => $returnTo));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current user is admin.
|
||||
*
|
||||
* @return boolean True if the current user is an admin user, false otherwise.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
|
||||
*/
|
||||
public static function isAdmin()
|
||||
{
|
||||
$session = \SimpleSAML_Session::getSessionFromRequest();
|
||||
return $session->isValid('admin') || $session->isValid('login-admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Require admin access to the current page.
|
||||
*
|
||||
* This is a helper function for limiting a page to those with administrative access. It will redirect the user to
|
||||
* a login page if the current user doesn't have admin access.
|
||||
*
|
||||
* @return void This function will only return if the user is admin.
|
||||
* @throws \SimpleSAML_Error_Exception If no "admin" authentication source was configured.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
|
||||
* @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no>
|
||||
*/
|
||||
public static function requireAdmin()
|
||||
{
|
||||
if (self::isAdmin()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// not authenticated as admin user, start authentication
|
||||
if (\SimpleSAML_Auth_Source::getById('admin') !== null) {
|
||||
$as = new \SimpleSAML\Auth\Simple('admin');
|
||||
$as->login();
|
||||
} else {
|
||||
throw new \SimpleSAML_Error_Exception(
|
||||
'Cannot find "admin" auth source, and admin privileges are required.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
lib/SimpleSAML/Utils/ClearableState.php
Executable file
17
lib/SimpleSAML/Utils/ClearableState.php
Executable file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleSAML\Utils;
|
||||
|
||||
/**
|
||||
* Indicates an implementation caches state internally and may be cleared.
|
||||
*
|
||||
* Primarily designed to allow SSP state to be cleared between unit tests.
|
||||
* @package SimpleSAML\Utils
|
||||
*/
|
||||
interface ClearableState
|
||||
{
|
||||
/**
|
||||
* Clear any cached internal state.
|
||||
*/
|
||||
public static function clearInternalState();
|
||||
}
|
||||
92
lib/SimpleSAML/Utils/Config.php
Executable file
92
lib/SimpleSAML/Utils/Config.php
Executable file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
namespace SimpleSAML\Utils;
|
||||
|
||||
/**
|
||||
* Utility class for SimpleSAMLphp configuration management and manipulation.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class Config
|
||||
{
|
||||
|
||||
/**
|
||||
* Resolves a path that may be relative to the cert-directory.
|
||||
*
|
||||
* @param string $path The (possibly relative) path to the file.
|
||||
*
|
||||
* @return string The file path.
|
||||
* @throws \InvalidArgumentException If $path is not a string.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
|
||||
*/
|
||||
public static function getCertPath($path)
|
||||
{
|
||||
if (!is_string($path)) {
|
||||
throw new \InvalidArgumentException('Invalid input parameters.');
|
||||
}
|
||||
|
||||
$globalConfig = \SimpleSAML_Configuration::getInstance();
|
||||
$base = $globalConfig->getPathValue('certdir', 'cert/');
|
||||
return System::resolvePath($path, $base);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the secret salt.
|
||||
*
|
||||
* This function retrieves the value which is configured as the secret salt. It will check that the value exists
|
||||
* and is set to a non-default value. If it isn't, an exception will be thrown.
|
||||
*
|
||||
* The secret salt can be used as a component in hash functions, to make it difficult to test all possible values
|
||||
* in order to retrieve the original value. It can also be used as a simple method for signing data, by hashing the
|
||||
* data together with the salt.
|
||||
*
|
||||
* @return string The secret salt.
|
||||
* @throws \InvalidArgumentException If the secret salt hasn't been configured.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
|
||||
*/
|
||||
public static function getSecretSalt()
|
||||
{
|
||||
$secretSalt = \SimpleSAML_Configuration::getInstance()->getString('secretsalt');
|
||||
if ($secretSalt === 'defaultsecretsalt') {
|
||||
throw new \InvalidArgumentException('The "secretsalt" configuration option must be set to a secret value.');
|
||||
}
|
||||
|
||||
return $secretSalt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the config dir
|
||||
*
|
||||
* If the SIMPLESAMLPHP_CONFIG_DIR environment variable has been set, it takes precedence over the default
|
||||
* $simplesamldir/config directory.
|
||||
*
|
||||
* @return string The path to the configuration directory.
|
||||
*/
|
||||
public static function getConfigDir()
|
||||
{
|
||||
$configDir = dirname(dirname(dirname(__DIR__))) . '/config';
|
||||
/** @var string|false $configDirEnv */
|
||||
$configDirEnv = getenv('SIMPLESAMLPHP_CONFIG_DIR');
|
||||
|
||||
if($configDirEnv === false) {
|
||||
$configDirEnv = getenv('REDIRECT_SIMPLESAMLPHP_CONFIG_DIR');
|
||||
}
|
||||
|
||||
if ($configDirEnv !== false) {
|
||||
if (!is_dir($configDirEnv)) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf(
|
||||
'Config directory specified by environment variable SIMPLESAMLPHP_CONFIG_DIR is not a ' .
|
||||
'directory. Given: "%s"',
|
||||
$configDirEnv
|
||||
)
|
||||
);
|
||||
}
|
||||
$configDir = $configDirEnv;
|
||||
}
|
||||
|
||||
return $configDir;
|
||||
}
|
||||
}
|
||||
282
lib/SimpleSAML/Utils/Config/Metadata.php
Executable file
282
lib/SimpleSAML/Utils/Config/Metadata.php
Executable file
@@ -0,0 +1,282 @@
|
||||
<?php
|
||||
namespace SimpleSAML\Utils\Config;
|
||||
|
||||
/**
|
||||
* Class with utilities to fetch different configuration objects from metadata configuration arrays.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
* @author Jaime Pérez Crespo, UNINETT AS <jaime.perez@uninett.no>
|
||||
*/
|
||||
class Metadata
|
||||
{
|
||||
|
||||
/**
|
||||
* The string that identities Entity Categories.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $ENTITY_CATEGORY = 'http://macedir.org/entity-category';
|
||||
|
||||
|
||||
/**
|
||||
* The string the identifies the REFEDS "Hide From Discovery" Entity Category.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $HIDE_FROM_DISCOVERY = 'http://refeds.org/category/hide-from-discovery';
|
||||
|
||||
|
||||
/**
|
||||
* Valid options for the ContactPerson element
|
||||
*
|
||||
* The 'attributes' option isn't defined in section 2.3.2.2 of the OASIS document, but
|
||||
* it is required to allow additons to the main contact person element for trust
|
||||
* frameworks.
|
||||
*
|
||||
* @var array The valid configuration options for a contact configuration array.
|
||||
* @see "Metadata for the OASIS Security Assertion Markup Language (SAML) V2.0", section 2.3.2.2.
|
||||
*/
|
||||
public static $VALID_CONTACT_OPTIONS = array(
|
||||
'contactType',
|
||||
'emailAddress',
|
||||
'givenName',
|
||||
'surName',
|
||||
'telephoneNumber',
|
||||
'company',
|
||||
'attributes',
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* @var array The valid types of contact for a contact configuration array.
|
||||
* @see "Metadata for the OASIS Security Assertion Markup Language (SAML) V2.0", section 2.3.2.2.
|
||||
*/
|
||||
public static $VALID_CONTACT_TYPES = array(
|
||||
'technical',
|
||||
'support',
|
||||
'administrative',
|
||||
'billing',
|
||||
'other',
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* Parse and sanitize a contact from an array.
|
||||
*
|
||||
* Accepts an array with the following elements:
|
||||
* - contactType The type of the contact (as string). Mandatory.
|
||||
* - emailAddress Email address (as string), or array of email addresses. Optional.
|
||||
* - telephoneNumber Telephone number of contact (as string), or array of telephone numbers. Optional.
|
||||
* - name Full name of contact, either as <GivenName> <SurName>, or as <SurName>, <GivenName>. Optional.
|
||||
* - surName Surname of contact (as string). Optional.
|
||||
* - givenName Given name of contact (as string). Optional.
|
||||
* - company Company name of contact (as string). Optional.
|
||||
*
|
||||
* The following values are allowed as "contactType":
|
||||
* - technical
|
||||
* - support
|
||||
* - administrative
|
||||
* - billing
|
||||
* - other
|
||||
*
|
||||
* If given a "name" it will try to decompose it into its given name and surname, only if neither givenName nor
|
||||
* surName are present. It works as follows:
|
||||
* - "surname1 surname2, given_name1 given_name2"
|
||||
* givenName: "given_name1 given_name2"
|
||||
* surname: "surname1 surname2"
|
||||
* - "given_name surname"
|
||||
* givenName: "given_name"
|
||||
* surname: "surname"
|
||||
*
|
||||
* otherwise it will just return the name as "givenName" in the resulting array.
|
||||
*
|
||||
* @param array $contact The contact to parse and sanitize.
|
||||
*
|
||||
* @return array An array holding valid contact configuration options. If a key 'name' was part of the input array,
|
||||
* it will try to decompose the name into its parts, and place the parts into givenName and surName, if those are
|
||||
* missing.
|
||||
* @throws \InvalidArgumentException If $contact is neither an array nor null, or the contact does not conform to
|
||||
* valid configuration rules for contacts.
|
||||
*/
|
||||
public static function getContact($contact)
|
||||
{
|
||||
if (!(is_array($contact) || is_null($contact))) {
|
||||
throw new \InvalidArgumentException('Invalid input parameters');
|
||||
}
|
||||
|
||||
// check the type
|
||||
if (!isset($contact['contactType']) || !in_array($contact['contactType'], self::$VALID_CONTACT_TYPES, true)) {
|
||||
$types = join(', ', array_map(
|
||||
function ($t) {
|
||||
return '"'.$t.'"';
|
||||
},
|
||||
self::$VALID_CONTACT_TYPES
|
||||
));
|
||||
throw new \InvalidArgumentException('"contactType" is mandatory and must be one of '.$types.".");
|
||||
}
|
||||
|
||||
// check attributes is an associative array
|
||||
if (isset($contact['attributes'])) {
|
||||
if (empty($contact['attributes'])
|
||||
|| !is_array($contact['attributes'])
|
||||
|| count(array_filter(array_keys($contact['attributes']), 'is_string')) === 0
|
||||
) {
|
||||
throw new \InvalidArgumentException('"attributes" must be an array and cannot be empty.');
|
||||
}
|
||||
}
|
||||
|
||||
// try to fill in givenName and surName from name
|
||||
if (isset($contact['name']) && !isset($contact['givenName']) && !isset($contact['surName'])) {
|
||||
// first check if it's comma separated
|
||||
$names = explode(',', $contact['name'], 2);
|
||||
if (count($names) === 2) {
|
||||
$contact['surName'] = preg_replace('/\s+/', ' ', trim($names[0]));
|
||||
$contact['givenName'] = preg_replace('/\s+/', ' ', trim($names[1]));
|
||||
} else {
|
||||
// check if it's in "given name surname" format
|
||||
$names = explode(' ', preg_replace('/\s+/', ' ', trim($contact['name'])));
|
||||
if (count($names) === 2) {
|
||||
$contact['givenName'] = preg_replace('/\s+/', ' ', trim($names[0]));
|
||||
$contact['surName'] = preg_replace('/\s+/', ' ', trim($names[1]));
|
||||
} else {
|
||||
// nothing works, return it as given name
|
||||
$contact['givenName'] = preg_replace('/\s+/', ' ', trim($contact['name']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check givenName
|
||||
if (isset($contact['givenName']) && (
|
||||
empty($contact['givenName']) || !is_string($contact['givenName'])
|
||||
)
|
||||
) {
|
||||
throw new \InvalidArgumentException('"givenName" must be a string and cannot be empty.');
|
||||
}
|
||||
|
||||
// check surName
|
||||
if (isset($contact['surName']) && (
|
||||
empty($contact['surName']) || !is_string($contact['surName'])
|
||||
)
|
||||
) {
|
||||
throw new \InvalidArgumentException('"surName" must be a string and cannot be empty.');
|
||||
}
|
||||
|
||||
// check company
|
||||
if (isset($contact['company']) && (
|
||||
empty($contact['company']) || !is_string($contact['company'])
|
||||
)
|
||||
) {
|
||||
throw new \InvalidArgumentException('"company" must be a string and cannot be empty.');
|
||||
}
|
||||
|
||||
// check emailAddress
|
||||
if (isset($contact['emailAddress'])) {
|
||||
if (empty($contact['emailAddress']) ||
|
||||
!(is_string($contact['emailAddress']) || is_array($contact['emailAddress']))
|
||||
) {
|
||||
throw new \InvalidArgumentException('"emailAddress" must be a string or an array and cannot be empty.');
|
||||
}
|
||||
if (is_array($contact['emailAddress'])) {
|
||||
foreach ($contact['emailAddress'] as $address) {
|
||||
if (!is_string($address) || empty($address)) {
|
||||
throw new \InvalidArgumentException('Email addresses must be a string and cannot be empty.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check telephoneNumber
|
||||
if (isset($contact['telephoneNumber'])) {
|
||||
if (empty($contact['telephoneNumber']) ||
|
||||
!(is_string($contact['telephoneNumber']) || is_array($contact['telephoneNumber']))
|
||||
) {
|
||||
throw new \InvalidArgumentException(
|
||||
'"telephoneNumber" must be a string or an array and cannot be empty.'
|
||||
);
|
||||
}
|
||||
if (is_array($contact['telephoneNumber'])) {
|
||||
foreach ($contact['telephoneNumber'] as $address) {
|
||||
if (!is_string($address) || empty($address)) {
|
||||
throw new \InvalidArgumentException('Telephone numbers must be a string and cannot be empty.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// make sure only valid options are outputted
|
||||
return array_intersect_key($contact, array_flip(self::$VALID_CONTACT_OPTIONS));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find the default endpoint in an endpoint array.
|
||||
*
|
||||
* @param array $endpoints An array with endpoints.
|
||||
* @param array $bindings An array with acceptable bindings. Can be null if any binding is allowed.
|
||||
*
|
||||
* @return array|NULL The default endpoint, or null if no acceptable endpoints are used.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
|
||||
*/
|
||||
public static function getDefaultEndpoint(array $endpoints, array $bindings = null)
|
||||
{
|
||||
$firstNotFalse = null;
|
||||
$firstAllowed = null;
|
||||
|
||||
// look through the endpoint list for acceptable endpoints
|
||||
foreach ($endpoints as $ep) {
|
||||
if ($bindings !== null && !in_array($ep['Binding'], $bindings, true)) {
|
||||
// unsupported binding, skip it
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($ep['isDefault'])) {
|
||||
if ($ep['isDefault'] === true) {
|
||||
// this is the first endpoint with isDefault set to true
|
||||
return $ep;
|
||||
}
|
||||
// isDefault is set to false, but the endpoint is still usable as a last resort
|
||||
if ($firstAllowed === null) {
|
||||
// this is the first endpoint that we can use
|
||||
$firstAllowed = $ep;
|
||||
}
|
||||
} else {
|
||||
if ($firstNotFalse === null) {
|
||||
// this is the first endpoint without isDefault set
|
||||
$firstNotFalse = $ep;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($firstNotFalse !== null) {
|
||||
// we have an endpoint without isDefault set to false
|
||||
return $firstNotFalse;
|
||||
}
|
||||
|
||||
/* $firstAllowed either contains the first endpoint we can use, or it contains null if we cannot use any of the
|
||||
* endpoints. Either way we return its value.
|
||||
*/
|
||||
return $firstAllowed;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine if an entity should be hidden in the discovery service.
|
||||
*
|
||||
* This method searches for the "Hide From Discovery" REFEDS Entity Category, and tells if the entity should be
|
||||
* hidden or not depending on it.
|
||||
*
|
||||
* @see https://refeds.org/category/hide-from-discovery
|
||||
*
|
||||
* @param array $metadata An associative array with the metadata representing an entity.
|
||||
*
|
||||
* @return boolean True if the entity should be hidden, false otherwise.
|
||||
*/
|
||||
public static function isHiddenFromDiscovery(array $metadata)
|
||||
{
|
||||
\SimpleSAML\Logger::maskErrors(E_ALL);
|
||||
$hidden = in_array(self::$HIDE_FROM_DISCOVERY, $metadata['EntityAttributes'][self::$ENTITY_CATEGORY], true);
|
||||
\SimpleSAML\Logger::popErrorMask();
|
||||
return $hidden === true;
|
||||
}
|
||||
}
|
||||
471
lib/SimpleSAML/Utils/Crypto.php
Executable file
471
lib/SimpleSAML/Utils/Crypto.php
Executable file
@@ -0,0 +1,471 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleSAML\Utils;
|
||||
|
||||
/**
|
||||
* A class for cryptography-related functions.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class Crypto
|
||||
{
|
||||
|
||||
/**
|
||||
* Decrypt data using AES-256-CBC and the key provided as a parameter.
|
||||
*
|
||||
* @param string $ciphertext The HMAC of the encrypted data, the IV used and the encrypted data, concatenated.
|
||||
* @param string $secret The secret to use to decrypt the data.
|
||||
*
|
||||
* @return string The decrypted data.
|
||||
* @throws \InvalidArgumentException If $ciphertext is not a string.
|
||||
* @throws \SimpleSAML_Error_Exception If the openssl module is not loaded.
|
||||
*
|
||||
* @see \SimpleSAML\Utils\Crypto::aesDecrypt()
|
||||
*/
|
||||
private static function _aesDecrypt($ciphertext, $secret)
|
||||
{
|
||||
if (!is_string($ciphertext)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Input parameter "$ciphertext" must be a string with more than 48 characters.'
|
||||
);
|
||||
}
|
||||
/** @var int $len */
|
||||
$len = mb_strlen($ciphertext, '8bit');
|
||||
if ($len < 48) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Input parameter "$ciphertext" must be a string with more than 48 characters.'
|
||||
);
|
||||
}
|
||||
if (!function_exists("openssl_decrypt")) {
|
||||
throw new \SimpleSAML_Error_Exception("The openssl PHP module is not loaded.");
|
||||
}
|
||||
|
||||
// derive encryption and authentication keys from the secret
|
||||
$key = openssl_digest($secret, 'sha512');
|
||||
|
||||
$hmac = mb_substr($ciphertext, 0, 32, '8bit');
|
||||
$iv = mb_substr($ciphertext, 32, 16, '8bit');
|
||||
$msg = mb_substr($ciphertext, 48, $len - 48, '8bit');
|
||||
|
||||
// authenticate the ciphertext
|
||||
if (self::secureCompare(hash_hmac('sha256', $iv.$msg, substr($key, 64, 64), true), $hmac)) {
|
||||
$plaintext = openssl_decrypt(
|
||||
$msg,
|
||||
'AES-256-CBC',
|
||||
substr($key, 0, 64),
|
||||
defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : 1,
|
||||
$iv
|
||||
);
|
||||
|
||||
if ($plaintext !== false) {
|
||||
return $plaintext;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \SimpleSAML_Error_Exception("Failed to decrypt ciphertext.");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decrypt data using AES-256-CBC and the system-wide secret salt as key.
|
||||
*
|
||||
* @param string $ciphertext The HMAC of the encrypted data, the IV used and the encrypted data, concatenated.
|
||||
*
|
||||
* @return string The decrypted data.
|
||||
* @throws \InvalidArgumentException If $ciphertext is not a string.
|
||||
* @throws \SimpleSAML_Error_Exception If the openssl module is not loaded.
|
||||
*
|
||||
* @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no>
|
||||
* @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no>
|
||||
*/
|
||||
public static function aesDecrypt($ciphertext)
|
||||
{
|
||||
return self::_aesDecrypt($ciphertext, Config::getSecretSalt());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encrypt data using AES-256-CBC and the key provided as a parameter.
|
||||
*
|
||||
* @param string $data The data to encrypt.
|
||||
* @param string $secret The secret to use to encrypt the data.
|
||||
*
|
||||
* @return string An HMAC of the encrypted data, the IV and the encrypted data, concatenated.
|
||||
* @throws \InvalidArgumentException If $data is not a string.
|
||||
* @throws \SimpleSAML_Error_Exception If the openssl module is not loaded.
|
||||
*
|
||||
* @see \SimpleSAML\Utils\Crypto::aesEncrypt()
|
||||
*/
|
||||
private static function _aesEncrypt($data, $secret)
|
||||
{
|
||||
if (!is_string($data)) {
|
||||
throw new \InvalidArgumentException('Input parameter "$data" must be a string.');
|
||||
}
|
||||
|
||||
if (!function_exists("openssl_encrypt")) {
|
||||
throw new \SimpleSAML_Error_Exception('The openssl PHP module is not loaded.');
|
||||
}
|
||||
|
||||
// derive encryption and authentication keys from the secret
|
||||
$key = openssl_digest($secret, 'sha512');
|
||||
|
||||
// generate a random IV
|
||||
$iv = openssl_random_pseudo_bytes(16);
|
||||
|
||||
// encrypt the message
|
||||
/** @var string|false $ciphertext */
|
||||
$ciphertext = openssl_encrypt(
|
||||
$data,
|
||||
'AES-256-CBC',
|
||||
substr($key, 0, 64),
|
||||
defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : 1,
|
||||
$iv
|
||||
);
|
||||
|
||||
if ($ciphertext === false) {
|
||||
throw new \SimpleSAML_Error_Exception("Failed to encrypt plaintext.");
|
||||
}
|
||||
|
||||
// return the ciphertext with proper authentication
|
||||
return hash_hmac('sha256', $iv.$ciphertext, substr($key, 64, 64), true).$iv.$ciphertext;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encrypt data using AES-256-CBC and the system-wide secret salt as key.
|
||||
*
|
||||
* @param string $data The data to encrypt.
|
||||
*
|
||||
* @return string An HMAC of the encrypted data, the IV and the encrypted data, concatenated.
|
||||
* @throws \InvalidArgumentException If $data is not a string.
|
||||
* @throws \SimpleSAML_Error_Exception If the openssl module is not loaded.
|
||||
*
|
||||
* @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no>
|
||||
* @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no>
|
||||
*/
|
||||
public static function aesEncrypt($data)
|
||||
{
|
||||
return self::_aesEncrypt($data, Config::getSecretSalt());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert data from DER to PEM encoding.
|
||||
*
|
||||
* @param string $der Data encoded in DER format.
|
||||
* @param string $type The type of data we are encoding, as expressed by the PEM header. Defaults to "CERTIFICATE".
|
||||
* @return string The same data encoded in PEM format.
|
||||
* @see RFC7648 for known types and PEM format specifics.
|
||||
*/
|
||||
public static function der2pem($der, $type = 'CERTIFICATE')
|
||||
{
|
||||
return "-----BEGIN ".$type."-----\n".
|
||||
chunk_split(base64_encode($der), 64, "\n").
|
||||
"-----END ".$type."-----\n";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load a private key from metadata.
|
||||
*
|
||||
* This function loads a private key from a metadata array. It looks for the following elements:
|
||||
* - 'privatekey': Name of a private key file in the cert-directory.
|
||||
* - 'privatekey_pass': Password for the private key.
|
||||
*
|
||||
* It returns and array with the following elements:
|
||||
* - 'PEM': Data for the private key, in PEM-format.
|
||||
* - 'password': Password for the private key.
|
||||
*
|
||||
* @param \SimpleSAML_Configuration $metadata The metadata array the private key should be loaded from.
|
||||
* @param bool $required Whether the private key is required. If this is true, a
|
||||
* missing key will cause an exception. Defaults to false.
|
||||
* @param string $prefix The prefix which should be used when reading from the metadata
|
||||
* array. Defaults to ''.
|
||||
* @param bool $full_path Whether the filename found in the configuration contains the
|
||||
* full path to the private key or not. Default to false.
|
||||
*
|
||||
* @return array|NULL Extracted private key, or NULL if no private key is present.
|
||||
* @throws \InvalidArgumentException If $required is not boolean or $prefix is not a string.
|
||||
* @throws \SimpleSAML_Error_Exception If no private key is found in the metadata, or it was not possible to load
|
||||
* it.
|
||||
*
|
||||
* @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no>
|
||||
* @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
|
||||
*/
|
||||
public static function loadPrivateKey(\SimpleSAML_Configuration $metadata, $required = false, $prefix = '', $full_path = false)
|
||||
{
|
||||
if (!is_bool($required) || !is_string($prefix) || !is_bool($full_path)) {
|
||||
throw new \InvalidArgumentException('Invalid input parameters.');
|
||||
}
|
||||
|
||||
$file = $metadata->getString($prefix.'privatekey', null);
|
||||
if ($file === null) {
|
||||
// no private key found
|
||||
if ($required) {
|
||||
throw new \SimpleSAML_Error_Exception('No private key found in metadata.');
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$full_path) {
|
||||
$file = Config::getCertPath($file);
|
||||
}
|
||||
|
||||
$data = @file_get_contents($file);
|
||||
if ($data === false) {
|
||||
throw new \SimpleSAML_Error_Exception('Unable to load private key from file "'.$file.'"');
|
||||
}
|
||||
|
||||
$ret = array(
|
||||
'PEM' => $data,
|
||||
);
|
||||
|
||||
if ($metadata->hasValue($prefix.'privatekey_pass')) {
|
||||
$ret['password'] = $metadata->getString($prefix.'privatekey_pass');
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get public key or certificate from metadata.
|
||||
*
|
||||
* This function implements a function to retrieve the public key or certificate from a metadata array.
|
||||
*
|
||||
* It will search for the following elements in the metadata:
|
||||
* - 'certData': The certificate as a base64-encoded string.
|
||||
* - 'certificate': A file with a certificate or public key in PEM-format.
|
||||
* - 'certFingerprint': The fingerprint of the certificate. Can be a single fingerprint, or an array of multiple
|
||||
* valid fingerprints. (deprecated)
|
||||
*
|
||||
* This function will return an array with these elements:
|
||||
* - 'PEM': The public key/certificate in PEM-encoding.
|
||||
* - 'certData': The certificate data, base64 encoded, on a single line. (Only present if this is a certificate.)
|
||||
* - 'certFingerprint': Array of valid certificate fingerprints. (Deprecated. Only present if this is a
|
||||
* certificate.)
|
||||
*
|
||||
* @param \SimpleSAML_Configuration $metadata The metadata.
|
||||
* @param bool $required Whether the private key is required. If this is TRUE, a missing key
|
||||
* will cause an exception. Default is FALSE.
|
||||
* @param string $prefix The prefix which should be used when reading from the metadata array.
|
||||
* Defaults to ''.
|
||||
*
|
||||
* @return array|NULL Public key or certificate data, or NULL if no public key or certificate was found.
|
||||
* @throws \InvalidArgumentException If $metadata is not an instance of \SimpleSAML_Configuration, $required is not
|
||||
* boolean or $prefix is not a string.
|
||||
* @throws \SimpleSAML_Error_Exception If no private key is found in the metadata, or it was not possible to load
|
||||
* it.
|
||||
*
|
||||
* @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no>
|
||||
* @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
|
||||
* @author Lasse Birnbaum Jensen
|
||||
*/
|
||||
public static function loadPublicKey(\SimpleSAML_Configuration $metadata, $required = false, $prefix = '')
|
||||
{
|
||||
if (!is_bool($required) || !is_string($prefix)) {
|
||||
throw new \InvalidArgumentException('Invalid input parameters.');
|
||||
}
|
||||
|
||||
$keys = $metadata->getPublicKeys(null, false, $prefix);
|
||||
if (!empty($keys)) {
|
||||
foreach ($keys as $key) {
|
||||
if ($key['type'] !== 'X509Certificate') {
|
||||
continue;
|
||||
}
|
||||
if ($key['signing'] !== true) {
|
||||
continue;
|
||||
}
|
||||
$certData = $key['X509Certificate'];
|
||||
$pem = "-----BEGIN CERTIFICATE-----\n".
|
||||
chunk_split($certData, 64).
|
||||
"-----END CERTIFICATE-----\n";
|
||||
$certFingerprint = strtolower(sha1(base64_decode($certData)));
|
||||
|
||||
return array(
|
||||
'certData' => $certData,
|
||||
'PEM' => $pem,
|
||||
'certFingerprint' => array($certFingerprint),
|
||||
);
|
||||
}
|
||||
// no valid key found
|
||||
} elseif ($metadata->hasValue($prefix.'certFingerprint')) {
|
||||
// we only have a fingerprint available
|
||||
$fps = $metadata->getArrayizeString($prefix.'certFingerprint');
|
||||
|
||||
// normalize fingerprint(s) - lowercase and no colons
|
||||
foreach ($fps as &$fp) {
|
||||
assert(is_string($fp));
|
||||
$fp = strtolower(str_replace(':', '', $fp));
|
||||
}
|
||||
|
||||
/*
|
||||
* We can't build a full certificate from a fingerprint, and may as well return an array with only the
|
||||
* fingerprint(s) immediately.
|
||||
*/
|
||||
return array('certFingerprint' => $fps);
|
||||
}
|
||||
|
||||
// no public key/certificate available
|
||||
if ($required) {
|
||||
throw new \SimpleSAML_Error_Exception('No public key / certificate found in metadata.');
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert from PEM to DER encoding.
|
||||
*
|
||||
* @param string $pem Data encoded in PEM format.
|
||||
* @return string The same data encoded in DER format.
|
||||
* @throws \InvalidArgumentException If $pem is not encoded in PEM format.
|
||||
* @see RFC7648 for PEM format specifics.
|
||||
*/
|
||||
public static function pem2der($pem)
|
||||
{
|
||||
$pem = trim($pem);
|
||||
$begin = "-----BEGIN ";
|
||||
$end = "-----END ";
|
||||
$lines = explode("\n", $pem);
|
||||
$last = count($lines) - 1;
|
||||
|
||||
if (strpos($lines[0], $begin) !== 0) {
|
||||
throw new \InvalidArgumentException("pem2der: input is not encoded in PEM format.");
|
||||
}
|
||||
unset($lines[0]);
|
||||
if (strpos($lines[$last], $end) !== 0) {
|
||||
throw new \InvalidArgumentException("pem2der: input is not encoded in PEM format.");
|
||||
}
|
||||
unset($lines[$last]);
|
||||
|
||||
return base64_decode(implode($lines));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function hashes a password with a given algorithm.
|
||||
*
|
||||
* @param string $password The password to hash.
|
||||
* @param string $algorithm The hashing algorithm, uppercase, optionally prepended with 'S' (salted). See
|
||||
* hash_algos() for a complete list of hashing algorithms.
|
||||
* @param string $salt An optional salt to use.
|
||||
*
|
||||
* @return string The hashed password.
|
||||
* @throws \InvalidArgumentException If the input parameters are not strings.
|
||||
* @throws \SimpleSAML_Error_Exception If the algorithm specified is not supported.
|
||||
*
|
||||
* @see hash_algos()
|
||||
*
|
||||
* @author Dyonisius Visser, TERENA <visser@terena.org>
|
||||
* @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no>
|
||||
*/
|
||||
public static function pwHash($password, $algorithm, $salt = null)
|
||||
{
|
||||
if (!is_string($algorithm) || !is_string($password)) {
|
||||
throw new \InvalidArgumentException('Invalid input parameters.');
|
||||
}
|
||||
|
||||
// hash w/o salt
|
||||
if (in_array(strtolower($algorithm), hash_algos(), true)) {
|
||||
$alg_str = '{'.str_replace('SHA1', 'SHA', $algorithm).'}'; // LDAP compatibility
|
||||
$hash = hash(strtolower($algorithm), $password, true);
|
||||
return $alg_str.base64_encode($hash);
|
||||
}
|
||||
|
||||
// hash w/ salt
|
||||
if ($salt === null) { // no salt provided, generate one
|
||||
// default 8 byte salt, but 4 byte for LDAP SHA1 hashes
|
||||
$bytes = ($algorithm == 'SSHA1') ? 4 : 8;
|
||||
$salt = openssl_random_pseudo_bytes($bytes);
|
||||
}
|
||||
|
||||
if ($algorithm[0] == 'S' && in_array(substr(strtolower($algorithm), 1), hash_algos(), true)) {
|
||||
$alg = substr(strtolower($algorithm), 1); // 'sha256' etc
|
||||
$alg_str = '{'.str_replace('SSHA1', 'SSHA', $algorithm).'}'; // LDAP compatibility
|
||||
$hash = hash($alg, $password.$salt, true);
|
||||
return $alg_str.base64_encode($hash.$salt);
|
||||
}
|
||||
|
||||
throw new \SimpleSAML_Error_Exception('Hashing algorithm \''.strtolower($algorithm).'\' is not supported');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compare two strings securely.
|
||||
*
|
||||
* This method checks if two strings are equal in constant time, avoiding timing attacks. Use it every time we need
|
||||
* to compare a string with a secret that shouldn't be leaked, i.e. when verifying passwords, one-time codes, etc.
|
||||
*
|
||||
* @param string $known A known string.
|
||||
* @param string $user A user-provided string to compare with the known string.
|
||||
*
|
||||
* @return bool True if both strings are equal, false otherwise.
|
||||
*/
|
||||
public static function secureCompare($known, $user)
|
||||
{
|
||||
if (function_exists('hash_equals')) {
|
||||
// use hash_equals() if available (PHP >= 5.6)
|
||||
return hash_equals($known, $user);
|
||||
}
|
||||
|
||||
// compare manually in constant time
|
||||
$len = mb_strlen($known, '8bit'); // see mbstring.func_overload
|
||||
if ($len !== mb_strlen($user, '8bit')) {
|
||||
return false; // length differs
|
||||
}
|
||||
$diff = 0;
|
||||
for ($i = 0; $i < $len; $i++) {
|
||||
$diff |= ord($known[$i]) ^ ord($user[$i]);
|
||||
}
|
||||
// if all the bytes in $a and $b are identical, $diff should be equal to 0
|
||||
return $diff === 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function checks if a password is valid
|
||||
*
|
||||
* @param string $hash The password as it appears in password file, optionally prepended with algorithm.
|
||||
* @param string $password The password to check in clear.
|
||||
*
|
||||
* @return boolean True if the hash corresponds with the given password, false otherwise.
|
||||
* @throws \InvalidArgumentException If the input parameters are not strings.
|
||||
* @throws \SimpleSAML_Error_Exception If the algorithm specified is not supported.
|
||||
*
|
||||
* @author Dyonisius Visser, TERENA <visser@terena.org>
|
||||
*/
|
||||
public static function pwValid($hash, $password)
|
||||
{
|
||||
if (!is_string($hash) || !is_string($password)) {
|
||||
throw new \InvalidArgumentException('Invalid input parameters.');
|
||||
}
|
||||
|
||||
// match algorithm string (e.g. '{SSHA256}', '{MD5}')
|
||||
if (preg_match('/^{(.*?)}(.*)$/', $hash, $matches)) {
|
||||
// LDAP compatibility
|
||||
$alg = preg_replace('/^(S?SHA)$/', '${1}1', $matches[1]);
|
||||
|
||||
// hash w/o salt
|
||||
if (in_array(strtolower($alg), hash_algos(), true)) {
|
||||
return self::secureCompare($hash, self::pwHash($password, $alg));
|
||||
}
|
||||
|
||||
// hash w/ salt
|
||||
if ($alg[0] === 'S' && in_array(substr(strtolower($alg), 1), hash_algos(), true)) {
|
||||
$php_alg = substr(strtolower($alg), 1);
|
||||
|
||||
// get hash length of this algorithm to learn how long the salt is
|
||||
$hash_length = strlen(hash($php_alg, '', true));
|
||||
$salt = substr(base64_decode($matches[2]), $hash_length);
|
||||
return self::secureCompare($hash, self::pwHash($password, $alg, $salt));
|
||||
}
|
||||
} else {
|
||||
return $hash === $password;
|
||||
}
|
||||
|
||||
throw new \SimpleSAML_Error_Exception('Hashing algorithm \''.strtolower($alg).'\' is not supported');
|
||||
}
|
||||
}
|
||||
1222
lib/SimpleSAML/Utils/HTTP.php
Executable file
1222
lib/SimpleSAML/Utils/HTTP.php
Executable file
File diff suppressed because it is too large
Load Diff
211
lib/SimpleSAML/Utils/HttpAdapter.php
Executable file
211
lib/SimpleSAML/Utils/HttpAdapter.php
Executable file
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleSAML\Utils;
|
||||
|
||||
/**
|
||||
* Provides a non-static wrapper for the HTTP utility class.
|
||||
*
|
||||
* @package SimpleSAML\Utils
|
||||
*/
|
||||
class HttpAdapter
|
||||
{
|
||||
/**
|
||||
* @see HTTP::getServerHTTPS()
|
||||
*/
|
||||
public function getServerHTTPS()
|
||||
{
|
||||
return HTTP::getServerHTTPS();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HTTP::getServerPort()
|
||||
*/
|
||||
public function getServerPort()
|
||||
{
|
||||
return HTTP::getServerPort();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HTTP::addURLParameters()
|
||||
*/
|
||||
public function addURLParameters($url, $parameters)
|
||||
{
|
||||
return HTTP::addURLParameters($url, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HTTP::checkSessionCookie()
|
||||
*/
|
||||
public function checkSessionCookie($retryURL = null)
|
||||
{
|
||||
HTTP::checkSessionCookie($retryURL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HTTP::checkURLAllowed()
|
||||
*/
|
||||
public function checkURLAllowed($url, array $trustedSites = null)
|
||||
{
|
||||
return HTTP::checkURLAllowed($url, $trustedSites);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HTTP::fetch()
|
||||
*/
|
||||
public function fetch($url, $context = array(), $getHeaders = false)
|
||||
{
|
||||
return HTTP::fetch($url, $context, $getHeaders);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HTTP::getAcceptLanguage()
|
||||
*/
|
||||
public function getAcceptLanguage()
|
||||
{
|
||||
return HTTP::getAcceptLanguage();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HTTP::guessBasePath()
|
||||
*/
|
||||
public function guessBasePath()
|
||||
{
|
||||
return HTTP::guessBasePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HTTP::getBaseURL()
|
||||
*/
|
||||
public function getBaseURL()
|
||||
{
|
||||
return HTTP::getBaseURL();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HTTP::getFirstPathElement()
|
||||
*/
|
||||
public function getFirstPathElement($trailingslash = true)
|
||||
{
|
||||
return HTTP::getFirstPathElement($trailingslash);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HTTP::getPOSTRedirectURL()
|
||||
*/
|
||||
public function getPOSTRedirectURL($destination, $data)
|
||||
{
|
||||
return HTTP::getPOSTRedirectURL($destination, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HTTP::getSelfHost()
|
||||
*/
|
||||
public function getSelfHost()
|
||||
{
|
||||
return HTTP::getSelfHost();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HTTP::getSelfHostWithNonStandardPort()
|
||||
*/
|
||||
public function getSelfHostWithNonStandardPort()
|
||||
{
|
||||
return HTTP::getSelfHostWithNonStandardPort();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HTTP::getSelfHostWithPath()
|
||||
*/
|
||||
public function getSelfHostWithPath()
|
||||
{
|
||||
return HTTP::getSelfHostWithPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HTTP::getSelfURL()
|
||||
*/
|
||||
public function getSelfURL()
|
||||
{
|
||||
return HTTP::getSelfURL();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HTTP::getSelfURLHost()
|
||||
*/
|
||||
public function getSelfURLHost()
|
||||
{
|
||||
return HTTP::getSelfURLHost();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HTTP::getSelfURLNoQuery()
|
||||
*/
|
||||
public function getSelfURLNoQuery()
|
||||
{
|
||||
return HTTP::getSelfURLNoQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HTTP::isHTTPS()
|
||||
*/
|
||||
public function isHTTPS()
|
||||
{
|
||||
return HTTP::isHTTPS();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HTTP::normalizeURL()
|
||||
*/
|
||||
public function normalizeURL($url)
|
||||
{
|
||||
return HTTP::normalizeURL($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HTTP::parseQueryString()
|
||||
*/
|
||||
public function parseQueryString($query_string)
|
||||
{
|
||||
return HTTP::parseQueryString($query_string);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HTTP::redirectTrustedURL()
|
||||
*/
|
||||
public function redirectTrustedURL($url, $parameters = array())
|
||||
{
|
||||
HTTP::redirectTrustedURL($url, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HTTP::redirectUntrustedURL()
|
||||
*/
|
||||
public function redirectUntrustedURL($url, $parameters = array())
|
||||
{
|
||||
HTTP::redirectUntrustedURL($url, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HTTP::resolveURL()
|
||||
*/
|
||||
public function resolveURL($url, $base = null)
|
||||
{
|
||||
return HTTP::resolveURL($url, $base);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HTTP::setCookie()
|
||||
*/
|
||||
public function setCookie($name, $value, $params = null, $throw = true)
|
||||
{
|
||||
HTTP::setCookie($name, $value, $params, $throw);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HTTP::submitPOSTData()
|
||||
*/
|
||||
public function submitPOSTData($destination, $data)
|
||||
{
|
||||
HTTP::submitPOSTData($destination, $data);
|
||||
}
|
||||
}
|
||||
85
lib/SimpleSAML/Utils/Net.php
Executable file
85
lib/SimpleSAML/Utils/Net.php
Executable file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
namespace SimpleSAML\Utils;
|
||||
|
||||
/**
|
||||
* Net-related utility methods.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class Net
|
||||
{
|
||||
|
||||
/**
|
||||
* Check whether an IP address is part of a CIDR.
|
||||
*
|
||||
* @param string $cidr The network CIDR address.
|
||||
* @param string $ip The IP address to check. Optional. Current remote address will be used if none specified. Do
|
||||
* not rely on default parameter if running behind load balancers.
|
||||
*
|
||||
* @return boolean True if the IP address belongs to the specified CIDR, false otherwise.
|
||||
*
|
||||
* @author Andreas Åkre Solberg, UNINETT AS <andreas.solberg@uninett.no>
|
||||
* @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
|
||||
* @author Brook Schofield, GÉANT
|
||||
* @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no>
|
||||
*/
|
||||
public static function ipCIDRcheck($cidr, $ip = null)
|
||||
{
|
||||
if ($ip === null) {
|
||||
$ip = $_SERVER['REMOTE_ADDR'];
|
||||
}
|
||||
if (strpos($cidr, '/') === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list ($net, $mask) = explode('/', $cidr);
|
||||
$mask = intval($mask);
|
||||
|
||||
$ip_ip = array();
|
||||
$ip_net = array();
|
||||
if (strstr($ip, ':') || strstr($net, ':')) {
|
||||
// Validate IPv6 with inet_pton, convert to hex with bin2hex
|
||||
// then store as a long with hexdec
|
||||
|
||||
$ip_pack = @inet_pton($ip);
|
||||
$net_pack = @inet_pton($net);
|
||||
|
||||
if ($ip_pack === false || $net_pack === false) {
|
||||
// not valid IPv6 address (warning silenced)
|
||||
return false;
|
||||
}
|
||||
|
||||
$ip_ip = str_split(bin2hex($ip_pack), 8);
|
||||
foreach ($ip_ip as &$value) {
|
||||
$value = hexdec($value);
|
||||
}
|
||||
|
||||
$ip_net = str_split(bin2hex($net_pack), 8);
|
||||
foreach ($ip_net as &$value) {
|
||||
$value = hexdec($value);
|
||||
}
|
||||
} else {
|
||||
$ip_ip[0] = ip2long($ip);
|
||||
$ip_net[0] = ip2long($net);
|
||||
}
|
||||
|
||||
for ($i = 0; $mask > 0 && $i < sizeof($ip_ip); $i++) {
|
||||
if ($mask > 32) {
|
||||
$iteration_mask = 32;
|
||||
} else {
|
||||
$iteration_mask = $mask;
|
||||
}
|
||||
$mask -= 32;
|
||||
|
||||
$ip_mask = ~((1 << (32 - $iteration_mask)) - 1);
|
||||
|
||||
$ip_net_mask = $ip_net[$i] & $ip_mask;
|
||||
$ip_ip_mask = $ip_ip[$i] & $ip_mask;
|
||||
|
||||
if ($ip_ip_mask != $ip_net_mask) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
30
lib/SimpleSAML/Utils/Random.php
Executable file
30
lib/SimpleSAML/Utils/Random.php
Executable file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace SimpleSAML\Utils;
|
||||
|
||||
/**
|
||||
* Utility class for random data generation and manipulation.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class Random
|
||||
{
|
||||
|
||||
/**
|
||||
* The fixed length of random identifiers.
|
||||
*/
|
||||
const ID_LENGTH = 43;
|
||||
|
||||
/**
|
||||
* Generate a random identifier, ID_LENGTH bytes long.
|
||||
*
|
||||
* @return string A ID_LENGTH-bytes long string with a random, hex-encoded string.
|
||||
*
|
||||
* @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no>
|
||||
* @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
|
||||
* @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no>
|
||||
*/
|
||||
public static function generateID()
|
||||
{
|
||||
return '_'.bin2hex(openssl_random_pseudo_bytes((int)((self::ID_LENGTH - 1)/2)));
|
||||
}
|
||||
}
|
||||
239
lib/SimpleSAML/Utils/System.php
Executable file
239
lib/SimpleSAML/Utils/System.php
Executable file
@@ -0,0 +1,239 @@
|
||||
<?php
|
||||
namespace SimpleSAML\Utils;
|
||||
|
||||
/**
|
||||
* System-related utility methods.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class System
|
||||
{
|
||||
|
||||
const WINDOWS = 1;
|
||||
const LINUX = 2;
|
||||
const OSX = 3;
|
||||
const HPUX = 4;
|
||||
const UNIX = 5;
|
||||
const BSD = 6;
|
||||
const IRIX = 7;
|
||||
const SUNOS = 8;
|
||||
|
||||
|
||||
/**
|
||||
* This function returns the Operating System we are running on.
|
||||
*
|
||||
* @return mixed A predefined constant identifying the OS we are running on. False if we are unable to determine it.
|
||||
*
|
||||
* @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no>
|
||||
*/
|
||||
public static function getOS()
|
||||
{
|
||||
if (stristr(PHP_OS, 'LINUX')) {
|
||||
return self::LINUX;
|
||||
}
|
||||
if (stristr(PHP_OS, 'DARWIN')) {
|
||||
return self::OSX;
|
||||
}
|
||||
if (stristr(PHP_OS, 'WIN')) {
|
||||
return self::WINDOWS;
|
||||
}
|
||||
if (stristr(PHP_OS, 'BSD')) {
|
||||
return self::BSD;
|
||||
}
|
||||
if (stristr(PHP_OS, 'UNIX')) {
|
||||
return self::UNIX;
|
||||
}
|
||||
if (stristr(PHP_OS, 'HP-UX')) {
|
||||
return self::HPUX;
|
||||
}
|
||||
if (stristr(PHP_OS, 'IRIX')) {
|
||||
return self::IRIX;
|
||||
}
|
||||
if (stristr(PHP_OS, 'SUNOS')) {
|
||||
return self::SUNOS;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function retrieves the path to a directory where temporary files can be saved.
|
||||
*
|
||||
* @return string Path to a temporary directory, without a trailing directory separator.
|
||||
* @throws \SimpleSAML_Error_Exception If the temporary directory cannot be created or it exists and does not belong
|
||||
* to the current user.
|
||||
*
|
||||
* @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no>
|
||||
* @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
|
||||
* @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no>
|
||||
*/
|
||||
public static function getTempDir()
|
||||
{
|
||||
$globalConfig = \SimpleSAML_Configuration::getInstance();
|
||||
|
||||
$tempDir = rtrim(
|
||||
$globalConfig->getString(
|
||||
'tempdir',
|
||||
sys_get_temp_dir().DIRECTORY_SEPARATOR.'simplesaml'
|
||||
),
|
||||
DIRECTORY_SEPARATOR
|
||||
);
|
||||
|
||||
if (!is_dir($tempDir)) {
|
||||
if (!mkdir($tempDir, 0700, true)) {
|
||||
$error = error_get_last();
|
||||
throw new \SimpleSAML_Error_Exception(
|
||||
'Error creating temporary directory "'.$tempDir.'": '.
|
||||
(is_array($error) ? $error['message'] : 'no error available')
|
||||
);
|
||||
}
|
||||
} elseif (function_exists('posix_getuid')) {
|
||||
// check that the owner of the temp directory is the current user
|
||||
$stat = lstat($tempDir);
|
||||
if ($stat['uid'] !== posix_getuid()) {
|
||||
throw new \SimpleSAML_Error_Exception(
|
||||
'Temporary directory "'.$tempDir.'" does not belong to the current user.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $tempDir;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolve a (possibly) relative path from the given base path.
|
||||
*
|
||||
* A path which starts with a '/' is assumed to be absolute, all others are assumed to be
|
||||
* relative. The default base path is the root of the SimpleSAMLphp installation.
|
||||
*
|
||||
* @param string $path The path we should resolve.
|
||||
* @param string|null $base The base path, where we should search for $path from. Default value is the root of the
|
||||
* SimpleSAMLphp installation.
|
||||
*
|
||||
* @return string An absolute path referring to $path.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
|
||||
*/
|
||||
public static function resolvePath($path, $base = null)
|
||||
{
|
||||
if ($base === null) {
|
||||
$config = \SimpleSAML_Configuration::getInstance();
|
||||
$base = $config->getBaseDir();
|
||||
}
|
||||
|
||||
// normalise directory separator
|
||||
$base = str_replace('\\', '/', $base);
|
||||
$path = str_replace('\\', '/', $path);
|
||||
|
||||
// remove trailing slashes
|
||||
$base = rtrim($base, '/');
|
||||
$path = rtrim($path, '/');
|
||||
|
||||
// check for absolute path
|
||||
if (substr($path, 0, 1) === '/') {
|
||||
// absolute path. */
|
||||
$ret = '/';
|
||||
} elseif (static::pathContainsDriveLetter($path)) {
|
||||
$ret = '';
|
||||
} else {
|
||||
// path relative to base
|
||||
$ret = $base;
|
||||
}
|
||||
|
||||
$path = explode('/', $path);
|
||||
foreach ($path as $d) {
|
||||
if ($d === '.') {
|
||||
continue;
|
||||
} elseif ($d === '..') {
|
||||
$ret = dirname($ret);
|
||||
} else {
|
||||
if ($ret && substr($ret, -1) !== '/') {
|
||||
$ret .= '/';
|
||||
}
|
||||
$ret .= $d;
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Atomically write a file.
|
||||
*
|
||||
* This is a helper function for writing data atomically to a file. It does this by writing the file data to a
|
||||
* temporary file, then renaming it to the required file name.
|
||||
*
|
||||
* @param string $filename The path to the file we want to write to.
|
||||
* @param string $data The data we should write to the file.
|
||||
* @param int $mode The permissions to apply to the file. Defaults to 0600.
|
||||
*
|
||||
* @throws \InvalidArgumentException If any of the input parameters doesn't have the proper types.
|
||||
* @throws \SimpleSAML_Error_Exception If the file cannot be saved, permissions cannot be changed or it is not
|
||||
* possible to write to the target file.
|
||||
*
|
||||
* @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no>
|
||||
* @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
|
||||
* @author Andjelko Horvat
|
||||
* @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no>
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function writeFile($filename, $data, $mode = 0600)
|
||||
{
|
||||
if (!is_string($filename) || !is_string($data) || !is_numeric($mode)) {
|
||||
throw new \InvalidArgumentException('Invalid input parameters');
|
||||
}
|
||||
|
||||
$tmpFile = self::getTempDir().DIRECTORY_SEPARATOR.rand();
|
||||
|
||||
$res = @file_put_contents($tmpFile, $data);
|
||||
if ($res === false) {
|
||||
$error = error_get_last();
|
||||
throw new \SimpleSAML_Error_Exception(
|
||||
'Error saving file "'.$tmpFile.'": '.
|
||||
(is_array($error) ? $error['message'] : 'no error available')
|
||||
);
|
||||
}
|
||||
|
||||
if (self::getOS() !== self::WINDOWS) {
|
||||
if (!chmod($tmpFile, $mode)) {
|
||||
unlink($tmpFile);
|
||||
$error = error_get_last();
|
||||
//$error = (is_array($error) ? $error['message'] : 'no error available');
|
||||
throw new \SimpleSAML_Error_Exception(
|
||||
'Error changing file mode of "'.$tmpFile.'": '.
|
||||
(is_array($error) ? $error['message'] : 'no error available')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!rename($tmpFile, $filename)) {
|
||||
unlink($tmpFile);
|
||||
$error = error_get_last();
|
||||
throw new \SimpleSAML_Error_Exception(
|
||||
'Error moving "'.$tmpFile.'" to "'.$filename.'": '.
|
||||
(is_array($error) ? $error['message'] : 'no error available')
|
||||
);
|
||||
}
|
||||
|
||||
if (function_exists('opcache_invalidate')) {
|
||||
opcache_invalidate($filename);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the supplied path contains a Windows-style drive letter.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function pathContainsDriveLetter($path)
|
||||
{
|
||||
$letterAsciiValue = ord(strtoupper(substr($path, 0, 1)));
|
||||
return substr($path, 1, 1) === ':'
|
||||
&& $letterAsciiValue >= 65 && $letterAsciiValue <= 90;
|
||||
}
|
||||
}
|
||||
167
lib/SimpleSAML/Utils/Time.php
Executable file
167
lib/SimpleSAML/Utils/Time.php
Executable file
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
/**
|
||||
* Time-related utility methods.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
namespace SimpleSAML\Utils;
|
||||
|
||||
use SimpleSAML\Logger;
|
||||
|
||||
class Time
|
||||
{
|
||||
|
||||
/**
|
||||
* Whether the timezone has been initialized or not.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private static $tz_initialized = false;
|
||||
|
||||
|
||||
/**
|
||||
* This function generates a timestamp on the form used by the SAML protocols.
|
||||
*
|
||||
* @param int $instant The time the timestamp should represent. Defaults to current time.
|
||||
*
|
||||
* @return string The timestamp.
|
||||
* @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
|
||||
*/
|
||||
public static function generateTimestamp($instant = null)
|
||||
{
|
||||
if ($instant === null) {
|
||||
$instant = time();
|
||||
}
|
||||
return gmdate('Y-m-d\TH:i:s\Z', $instant);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the timezone.
|
||||
*
|
||||
* This function should be called before any calls to date().
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
|
||||
*
|
||||
* @throws \SimpleSAML_Error_Exception If the timezone set in the configuration is invalid.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function initTimezone()
|
||||
{
|
||||
if (self::$tz_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
$globalConfig = \SimpleSAML_Configuration::getInstance();
|
||||
|
||||
$timezone = $globalConfig->getString('timezone', null);
|
||||
if ($timezone !== null) {
|
||||
if (!date_default_timezone_set($timezone)) {
|
||||
throw new \SimpleSAML_Error_Exception('Invalid timezone set in the "timezone" option in config.php.');
|
||||
}
|
||||
self::$tz_initialized = true;
|
||||
return;
|
||||
}
|
||||
// we don't have a timezone configured
|
||||
|
||||
Logger::maskErrors(E_ALL);
|
||||
$serverTimezone = date_default_timezone_get();
|
||||
Logger::popErrorMask();
|
||||
|
||||
// set the timezone to the default
|
||||
date_default_timezone_set($serverTimezone);
|
||||
self::$tz_initialized = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Interpret a ISO8601 duration value relative to a given timestamp. Please note no fractions are allowed, neither
|
||||
* durations specified in the formats PYYYYMMDDThhmmss nor P[YYYY]-[MM]-[DD]T[hh]:[mm]:[ss].
|
||||
*
|
||||
* @param string $duration The duration, as a string.
|
||||
* @param int $timestamp The unix timestamp we should apply the duration to. Optional, default to the current
|
||||
* time.
|
||||
*
|
||||
* @return int The new timestamp, after the duration is applied.
|
||||
* @throws \InvalidArgumentException If $duration is not a valid ISO 8601 duration or if the input parameters do
|
||||
* not have the right data types.
|
||||
*/
|
||||
public static function parseDuration($duration, $timestamp = null)
|
||||
{
|
||||
if (!(is_string($duration) && (is_int($timestamp) || is_null($timestamp)))) {
|
||||
throw new \InvalidArgumentException('Invalid input parameters');
|
||||
}
|
||||
|
||||
// parse the duration. We use a very strict pattern
|
||||
$durationRegEx = '#^(-?)P(?:(?:(?:(\\d+)Y)?(?:(\\d+)M)?(?:(\\d+)D)?(?:T(?:(\\d+)H)?(?:(\\d+)M)?(?:(\\d+)'.
|
||||
'(?:[.,]\d+)?S)?)?)|(?:(\\d+)W))$#D';
|
||||
if (!preg_match($durationRegEx, $duration, $matches)) {
|
||||
throw new \InvalidArgumentException('Invalid ISO 8601 duration: '.$duration);
|
||||
}
|
||||
|
||||
$durYears = (empty($matches[2]) ? 0 : (int) $matches[2]);
|
||||
$durMonths = (empty($matches[3]) ? 0 : (int) $matches[3]);
|
||||
$durDays = (empty($matches[4]) ? 0 : (int) $matches[4]);
|
||||
$durHours = (empty($matches[5]) ? 0 : (int) $matches[5]);
|
||||
$durMinutes = (empty($matches[6]) ? 0 : (int) $matches[6]);
|
||||
$durSeconds = (empty($matches[7]) ? 0 : (int) $matches[7]);
|
||||
$durWeeks = (empty($matches[8]) ? 0 : (int) $matches[8]);
|
||||
|
||||
if (!empty($matches[1])) {
|
||||
// negative
|
||||
$durYears = -$durYears;
|
||||
$durMonths = -$durMonths;
|
||||
$durDays = -$durDays;
|
||||
$durHours = -$durHours;
|
||||
$durMinutes = -$durMinutes;
|
||||
$durSeconds = -$durSeconds;
|
||||
$durWeeks = -$durWeeks;
|
||||
}
|
||||
|
||||
if ($timestamp === null) {
|
||||
$timestamp = time();
|
||||
}
|
||||
|
||||
if ($durYears !== 0 || $durMonths !== 0) {
|
||||
/* Special handling of months and years, since they aren't a specific interval, but
|
||||
* instead depend on the current time.
|
||||
*/
|
||||
|
||||
/* We need the year and month from the timestamp. Unfortunately, PHP doesn't have the
|
||||
* gmtime function. Instead we use the gmdate function, and split the result.
|
||||
*/
|
||||
$yearmonth = explode(':', gmdate('Y:n', $timestamp));
|
||||
$year = (int) ($yearmonth[0]);
|
||||
$month = (int) ($yearmonth[1]);
|
||||
|
||||
// remove the year and month from the timestamp
|
||||
$timestamp -= gmmktime(0, 0, 0, $month, 1, $year);
|
||||
|
||||
// add years and months, and normalize the numbers afterwards
|
||||
$year += $durYears;
|
||||
$month += $durMonths;
|
||||
while ($month > 12) {
|
||||
$year += 1;
|
||||
$month -= 12;
|
||||
}
|
||||
while ($month < 1) {
|
||||
$year -= 1;
|
||||
$month += 12;
|
||||
}
|
||||
|
||||
// add year and month back into timestamp
|
||||
$timestamp += gmmktime(0, 0, 0, $month, 1, $year);
|
||||
}
|
||||
|
||||
// add the other elements
|
||||
$timestamp += $durWeeks * 7 * 24 * 60 * 60;
|
||||
$timestamp += $durDays * 24 * 60 * 60;
|
||||
$timestamp += $durHours * 60 * 60;
|
||||
$timestamp += $durMinutes * 60;
|
||||
$timestamp += $durSeconds;
|
||||
|
||||
return $timestamp;
|
||||
}
|
||||
}
|
||||
453
lib/SimpleSAML/Utils/XML.php
Executable file
453
lib/SimpleSAML/Utils/XML.php
Executable file
@@ -0,0 +1,453 @@
|
||||
<?php
|
||||
/**
|
||||
* Utility class for XML and DOM manipulation.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
namespace SimpleSAML\Utils;
|
||||
|
||||
use SimpleSAML\Logger;
|
||||
use SimpleSAML\XML\Errors;
|
||||
|
||||
class XML
|
||||
{
|
||||
|
||||
/**
|
||||
* This function performs some sanity checks on XML documents, and optionally validates them against their schema
|
||||
* if the 'validatexml' debugging option is enabled. A warning will be printed to the log if validation fails.
|
||||
*
|
||||
* @param string $message The SAML document we want to check.
|
||||
* @param string $type The type of document. Can be one of:
|
||||
* - 'saml20'
|
||||
* - 'saml11'
|
||||
* - 'saml-meta'
|
||||
*
|
||||
* @throws \InvalidArgumentException If $message is not a string or $type is not a string containing one of the
|
||||
* values allowed.
|
||||
* @throws \SimpleSAML_Error_Exception If $message contains a doctype declaration.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
|
||||
* @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no>
|
||||
*/
|
||||
public static function checkSAMLMessage($message, $type)
|
||||
{
|
||||
$allowed_types = array('saml20', 'saml11', 'saml-meta');
|
||||
if (!(is_string($message) && in_array($type, $allowed_types, true))) {
|
||||
throw new \InvalidArgumentException('Invalid input parameters.');
|
||||
}
|
||||
|
||||
// a SAML message should not contain a doctype-declaration
|
||||
if (strpos($message, '<!DOCTYPE') !== false) {
|
||||
throw new \SimpleSAML_Error_Exception('XML contained a doctype declaration.');
|
||||
}
|
||||
|
||||
// see if debugging is enabled for XML validation
|
||||
$debug = \SimpleSAML_Configuration::getInstance()->getArrayize('debug', array('validatexml' => false));
|
||||
$enabled = \SimpleSAML_Configuration::getInstance()->getBoolean('debug.validatexml', false);
|
||||
|
||||
if (!(in_array('validatexml', $debug, true) // implicitly enabled
|
||||
|| (array_key_exists('validatexml', $debug) && $debug['validatexml'] === true) // explicitly enabled
|
||||
// TODO: deprecate this option and remove it in 2.0
|
||||
|| $enabled // old 'debug.validatexml' configuration option
|
||||
)) {
|
||||
// XML validation is disabled
|
||||
return;
|
||||
}
|
||||
|
||||
$result = true;
|
||||
switch ($type) {
|
||||
case 'saml11':
|
||||
$result = self::isValid($message, 'oasis-sstc-saml-schema-protocol-1.1.xsd');
|
||||
break;
|
||||
case 'saml20':
|
||||
$result = self::isValid($message, 'saml-schema-protocol-2.0.xsd');
|
||||
break;
|
||||
case 'saml-meta':
|
||||
$result = self::isValid($message, 'saml-schema-metadata-2.0.xsd');
|
||||
}
|
||||
if ($result !== true) {
|
||||
Logger::warning($result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper function to log SAML messages that we send or receive.
|
||||
*
|
||||
* @param string|\DOMElement $message The message, as an string containing the XML or an XML element.
|
||||
* @param string $type Whether this message is sent or received, encrypted or decrypted. The following
|
||||
* values are supported:
|
||||
* - 'in': for messages received.
|
||||
* - 'out': for outgoing messages.
|
||||
* - 'decrypt': for decrypted messages.
|
||||
* - 'encrypt': for encrypted messages.
|
||||
*
|
||||
* @throws \InvalidArgumentException If $type is not a string or $message is neither a string nor a \DOMElement.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
|
||||
*/
|
||||
public static function debugSAMLMessage($message, $type)
|
||||
{
|
||||
if (!(is_string($type) && (is_string($message) || $message instanceof \DOMElement))) {
|
||||
throw new \InvalidArgumentException('Invalid input parameters.');
|
||||
}
|
||||
|
||||
// see if debugging is enabled for SAML messages
|
||||
$debug = \SimpleSAML_Configuration::getInstance()->getArrayize('debug', array('saml' => false));
|
||||
|
||||
if (!(in_array('saml', $debug, true) // implicitly enabled
|
||||
|| (array_key_exists('saml', $debug) && $debug['saml'] === true) // explicitly enabled
|
||||
// TODO: deprecate the old style and remove it in 2.0
|
||||
|| (array_key_exists(0, $debug) && $debug[0] === true) // old style 'debug'
|
||||
)) {
|
||||
// debugging messages is disabled
|
||||
return;
|
||||
}
|
||||
|
||||
if ($message instanceof \DOMElement) {
|
||||
$message = $message->ownerDocument->saveXML($message);
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'in':
|
||||
Logger::debug('Received message:');
|
||||
break;
|
||||
case 'out':
|
||||
Logger::debug('Sending message:');
|
||||
break;
|
||||
case 'decrypt':
|
||||
Logger::debug('Decrypted message:');
|
||||
break;
|
||||
case 'encrypt':
|
||||
Logger::debug('Encrypted message:');
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
$str = self::formatXMLString($message);
|
||||
foreach (explode("\n", $str) as $line) {
|
||||
Logger::debug($line);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Format a DOM element.
|
||||
*
|
||||
* This function takes in a DOM element, and inserts whitespace to make it more readable. Note that whitespace
|
||||
* added previously will be removed.
|
||||
*
|
||||
* @param \DOMNode $root The root element which should be formatted.
|
||||
* @param string $indentBase The indentation this element should be assumed to have. Defaults to an empty
|
||||
* string.
|
||||
*
|
||||
* @throws \InvalidArgumentException If $root is not a DOMElement or $indentBase is not a string.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
|
||||
*/
|
||||
public static function formatDOMElement(\DOMNode $root, $indentBase = '')
|
||||
{
|
||||
if (!is_string($indentBase)) {
|
||||
throw new \InvalidArgumentException('Invalid input parameters');
|
||||
}
|
||||
|
||||
// check what this element contains
|
||||
$fullText = ''; // all text in this element
|
||||
$textNodes = array(); // text nodes which should be deleted
|
||||
$childNodes = array(); // other child nodes
|
||||
for ($i = 0; $i < $root->childNodes->length; $i++) {
|
||||
/** @var \DOMElement $child */
|
||||
$child = $root->childNodes->item($i);
|
||||
|
||||
if ($child instanceof \DOMText) {
|
||||
$textNodes[] = $child;
|
||||
$fullText .= $child->wholeText;
|
||||
} elseif ($child instanceof \DOMComment || $child instanceof \DOMElement) {
|
||||
$childNodes[] = $child;
|
||||
} else {
|
||||
// unknown node type. We don't know how to format this
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$fullText = trim($fullText);
|
||||
if (strlen($fullText) > 0) {
|
||||
// we contain textelf
|
||||
$hasText = true;
|
||||
} else {
|
||||
$hasText = false;
|
||||
}
|
||||
|
||||
$hasChildNode = (count($childNodes) > 0);
|
||||
|
||||
if ($hasText && $hasChildNode) {
|
||||
// element contains both text and child nodes - we don't know how to format this one
|
||||
return;
|
||||
}
|
||||
|
||||
// remove text nodes
|
||||
foreach ($textNodes as $node) {
|
||||
$root->removeChild($node);
|
||||
}
|
||||
|
||||
if ($hasText) {
|
||||
// only text - add a single text node to the element with the full text
|
||||
$root->appendChild(new \DOMText($fullText));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$hasChildNode) {
|
||||
// empty node. Nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
/* Element contains only child nodes - add indentation before each one, and
|
||||
* format child elements.
|
||||
*/
|
||||
$childIndentation = $indentBase.' ';
|
||||
foreach ($childNodes as $node) {
|
||||
// add indentation before node
|
||||
$root->insertBefore(new \DOMText("\n".$childIndentation), $node);
|
||||
|
||||
// format child elements
|
||||
if ($node instanceof \DOMElement) {
|
||||
self::formatDOMElement($node, $childIndentation);
|
||||
}
|
||||
}
|
||||
|
||||
// add indentation before closing tag
|
||||
$root->appendChild(new \DOMText("\n".$indentBase));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Format an XML string.
|
||||
*
|
||||
* This function formats an XML string using the formatDOMElement() function.
|
||||
*
|
||||
* @param string $xml An XML string which should be formatted.
|
||||
* @param string $indentBase Optional indentation which should be applied to all the output. Optional, defaults
|
||||
* to ''.
|
||||
*
|
||||
* @return string The formatted string.
|
||||
* @throws \InvalidArgumentException If the parameters are not strings.
|
||||
* @throws \DOMException If the input does not parse correctly as an XML string.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
|
||||
*/
|
||||
public static function formatXMLString($xml, $indentBase = '')
|
||||
{
|
||||
if (!is_string($xml) || !is_string($indentBase)) {
|
||||
throw new \InvalidArgumentException('Invalid input parameters');
|
||||
}
|
||||
|
||||
try {
|
||||
$doc = \SAML2\DOMDocumentFactory::fromString($xml);
|
||||
} catch (\Exception $e) {
|
||||
throw new \DOMException('Error parsing XML string.');
|
||||
}
|
||||
|
||||
$root = $doc->firstChild;
|
||||
self::formatDOMElement($root, $indentBase);
|
||||
|
||||
return $doc->saveXML($root);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function finds direct descendants of a DOM element with the specified
|
||||
* localName and namespace. They are returned in an array.
|
||||
*
|
||||
* This function accepts the same shortcuts for namespaces as the isDOMNodeOfType function.
|
||||
*
|
||||
* @param \DOMNode $element The element we should look in.
|
||||
* @param string $localName The name the element should have.
|
||||
* @param string $namespaceURI The namespace the element should have.
|
||||
*
|
||||
* @return array Array with the matching elements in the order they are found. An empty array is
|
||||
* returned if no elements match.
|
||||
* @throws \InvalidArgumentException If $element is not an instance of DOMElement, $localName is not a string or
|
||||
* $namespaceURI is not a string.
|
||||
*/
|
||||
public static function getDOMChildren(\DOMNode $element, $localName, $namespaceURI)
|
||||
{
|
||||
if (!is_string($localName) || !is_string($namespaceURI)) {
|
||||
throw new \InvalidArgumentException('Invalid input parameters.');
|
||||
}
|
||||
|
||||
$ret = array();
|
||||
|
||||
for ($i = 0; $i < $element->childNodes->length; $i++) {
|
||||
/** @var \DOMElement $child */
|
||||
$child = $element->childNodes->item($i);
|
||||
|
||||
// skip text nodes and comment elements
|
||||
if ($child instanceof \DOMText || $child instanceof \DOMComment) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (self::isDOMNodeOfType($child, $localName, $namespaceURI) === true) {
|
||||
$ret[] = $child;
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function extracts the text from DOMElements which should contain only text content.
|
||||
*
|
||||
* @param \DOMElement $element The element we should extract text from.
|
||||
*
|
||||
* @return string The text content of the element.
|
||||
* @throws \SimpleSAML_Error_Exception If the element contains a non-text child node.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
|
||||
*/
|
||||
public static function getDOMText(\DOMElement $element)
|
||||
{
|
||||
$txt = '';
|
||||
|
||||
for ($i = 0; $i < $element->childNodes->length; $i++) {
|
||||
/** @var \DOMElement $child */
|
||||
$child = $element->childNodes->item($i);
|
||||
if (!($child instanceof \DOMText)) {
|
||||
throw new \SimpleSAML_Error_Exception($element->localName.' contained a non-text child node.');
|
||||
}
|
||||
|
||||
$txt .= $child->wholeText;
|
||||
}
|
||||
|
||||
$txt = trim($txt);
|
||||
return $txt;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function checks if the DOMElement has the correct localName and namespaceURI.
|
||||
*
|
||||
* We also define the following shortcuts for namespaces:
|
||||
* - '@ds': 'http://www.w3.org/2000/09/xmldsig#'
|
||||
* - '@md': 'urn:oasis:names:tc:SAML:2.0:metadata'
|
||||
* - '@saml1': 'urn:oasis:names:tc:SAML:1.0:assertion'
|
||||
* - '@saml1md': 'urn:oasis:names:tc:SAML:profiles:v1metadata'
|
||||
* - '@saml1p': 'urn:oasis:names:tc:SAML:1.0:protocol'
|
||||
* - '@saml2': 'urn:oasis:names:tc:SAML:2.0:assertion'
|
||||
* - '@saml2p': 'urn:oasis:names:tc:SAML:2.0:protocol'
|
||||
*
|
||||
* @param \DOMNode $element The element we should check.
|
||||
* @param string $name The local name the element should have.
|
||||
* @param string $nsURI The namespaceURI the element should have.
|
||||
*
|
||||
* @return boolean True if both namespace and local name matches, false otherwise.
|
||||
* @throws \InvalidArgumentException If the namespace shortcut is unknown.
|
||||
*
|
||||
* @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no>
|
||||
* @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
|
||||
*/
|
||||
public static function isDOMNodeOfType(\DOMNode $element, $name, $nsURI)
|
||||
{
|
||||
if (!is_string($name) || !is_string($nsURI) || strlen($nsURI) === 0) {
|
||||
// most likely a comment-node
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if the namespace is a shortcut, and expand it if it is
|
||||
if ($nsURI[0] === '@') {
|
||||
// the defined shortcuts
|
||||
$shortcuts = array(
|
||||
'@ds' => 'http://www.w3.org/2000/09/xmldsig#',
|
||||
'@md' => 'urn:oasis:names:tc:SAML:2.0:metadata',
|
||||
'@saml1' => 'urn:oasis:names:tc:SAML:1.0:assertion',
|
||||
'@saml1md' => 'urn:oasis:names:tc:SAML:profiles:v1metadata',
|
||||
'@saml1p' => 'urn:oasis:names:tc:SAML:1.0:protocol',
|
||||
'@saml2' => 'urn:oasis:names:tc:SAML:2.0:assertion',
|
||||
'@saml2p' => 'urn:oasis:names:tc:SAML:2.0:protocol',
|
||||
'@shibmd' => 'urn:mace:shibboleth:metadata:1.0',
|
||||
);
|
||||
|
||||
// check if it is a valid shortcut
|
||||
if (!array_key_exists($nsURI, $shortcuts)) {
|
||||
throw new \InvalidArgumentException('Unknown namespace shortcut: '.$nsURI);
|
||||
}
|
||||
|
||||
// expand the shortcut
|
||||
$nsURI = $shortcuts[$nsURI];
|
||||
}
|
||||
if ($element->localName !== $name) {
|
||||
return false;
|
||||
}
|
||||
if ($element->namespaceURI !== $nsURI) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function attempts to validate an XML string against the specified schema. It will parse the string into a
|
||||
* DOM document and validate this document against the schema.
|
||||
*
|
||||
* Note that this function returns values that are evaluated as a logical true, both when validation works and when
|
||||
* it doesn't. Please use strict comparisons to check the values returned.
|
||||
*
|
||||
* @param string|\DOMDocument $xml The XML string or document which should be validated.
|
||||
* @param string $schema The filename of the schema that should be used to validate the document.
|
||||
*
|
||||
* @return boolean|string Returns a string with errors found if validation fails. True if validation passes ok.
|
||||
* @throws \InvalidArgumentException If $schema is not a string, or $xml is neither a string nor a \DOMDocument.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
|
||||
*/
|
||||
public static function isValid($xml, $schema)
|
||||
{
|
||||
if (!(is_string($schema) && (is_string($xml) || $xml instanceof \DOMDocument))) {
|
||||
throw new \InvalidArgumentException('Invalid input parameters.');
|
||||
}
|
||||
|
||||
Errors::begin();
|
||||
|
||||
if ($xml instanceof \DOMDocument) {
|
||||
$dom = $xml;
|
||||
$res = true;
|
||||
} else {
|
||||
try {
|
||||
$dom = \SAML2\DOMDocumentFactory::fromString($xml);
|
||||
$res = true;
|
||||
} catch (\Exception $e) {
|
||||
$res = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($res) {
|
||||
$config = \SimpleSAML_Configuration::getInstance();
|
||||
/** @var string $schemaPath */
|
||||
$schemaPath = $config->resolvePath('schemas');
|
||||
$schemaFile = $schemaPath.'/'.$schema;
|
||||
|
||||
$res = $dom->schemaValidate($schemaFile);
|
||||
if ($res) {
|
||||
Errors::end();
|
||||
return true;
|
||||
}
|
||||
|
||||
$errorText = "Schema validation failed on XML string:\n";
|
||||
} else {
|
||||
$errorText = "Failed to parse XML string for schema validation:\n";
|
||||
}
|
||||
|
||||
$errors = Errors::end();
|
||||
$errorText .= Errors::formatErrors($errors);
|
||||
|
||||
return $errorText;
|
||||
}
|
||||
}
|
||||
122
lib/SimpleSAML/XHTML/EMail.php
Executable file
122
lib/SimpleSAML/XHTML/EMail.php
Executable file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* A minimalistic Emailer class. Creates and sends HTML emails.
|
||||
*
|
||||
* @author Andreas kre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SimpleSAML_XHTML_EMail
|
||||
{
|
||||
private $to = null;
|
||||
private $cc = null;
|
||||
private $body = null;
|
||||
private $from = null;
|
||||
private $replyto = null;
|
||||
private $subject = null;
|
||||
private $headers = array();
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct($to, $subject, $from = null, $cc = null, $replyto = null)
|
||||
{
|
||||
$this->to = $to;
|
||||
$this->cc = $cc;
|
||||
$this->from = $from;
|
||||
$this->replyto = $replyto;
|
||||
$this->subject = $subject;
|
||||
}
|
||||
|
||||
/*
|
||||
* @param string $body
|
||||
* @return void
|
||||
*/
|
||||
public function setBody($body)
|
||||
{
|
||||
$this->body = $body;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* @param string $body
|
||||
* @return void
|
||||
*/
|
||||
private function getHTML($body)
|
||||
{
|
||||
return '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title>SimpleSAMLphp Email report</title>
|
||||
<style type="text/css">
|
||||
pre, div.box {
|
||||
margin: .4em 2em .4em 1em;
|
||||
padding: 4px;
|
||||
|
||||
}
|
||||
pre {
|
||||
background: #eee;
|
||||
border: 1px solid #aaa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container" style="background: #fafafa; border: 1px solid #eee; margin: 2em; padding: .6em;">
|
||||
' . $body . '
|
||||
</div>
|
||||
</body>
|
||||
</html>';
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* @return void
|
||||
*/
|
||||
public function send()
|
||||
{
|
||||
if ($this->to === null) {
|
||||
throw new Exception('EMail field [to] is required and not set.');
|
||||
} elseif ($this->subject === null) {
|
||||
throw new Exception('EMail field [subject] is required and not set.');
|
||||
} elseif ($this->body === null) {
|
||||
throw new Exception('EMail field [body] is required and not set.');
|
||||
}
|
||||
|
||||
$random_hash = bin2hex(openssl_random_pseudo_bytes(16));
|
||||
|
||||
if (isset($this->from)) {
|
||||
$this->headers[]= 'From: ' . $this->from;
|
||||
}
|
||||
if (isset($this->replyto)) {
|
||||
$this->headers[]= 'Reply-To: ' . $this->replyto;
|
||||
}
|
||||
|
||||
$this->headers[] = 'Content-Type: multipart/alternative; boundary="simplesamlphp-' . $random_hash . '"';
|
||||
|
||||
$message = '
|
||||
--simplesamlphp-' . $random_hash . '
|
||||
Content-Type: text/plain; charset="utf-8"
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
' . strip_tags(html_entity_decode($this->body)) . '
|
||||
|
||||
--simplesamlphp-' . $random_hash . '
|
||||
Content-Type: text/html; charset="utf-8"
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
' . $this->getHTML($this->body) . '
|
||||
|
||||
--simplesamlphp-' . $random_hash . '--
|
||||
';
|
||||
$headers = implode("\n", $this->headers);
|
||||
|
||||
$mail_sent = @mail($this->to, $this->subject, $message, $headers);
|
||||
SimpleSAML\Logger::debug('Email: Sending e-mail to [' . $this->to . '] : ' . ($mail_sent ? 'OK' : 'Failed'));
|
||||
if (!$mail_sent) {
|
||||
throw new Exception('Error when sending e-mail');
|
||||
}
|
||||
}
|
||||
}
|
||||
598
lib/SimpleSAML/XHTML/IdPDisco.php
Executable file
598
lib/SimpleSAML/XHTML/IdPDisco.php
Executable file
@@ -0,0 +1,598 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* This class implements a generic IdP discovery service, for use in various IdP
|
||||
* discovery service pages. This should reduce code duplication.
|
||||
*
|
||||
* Experimental support added for Extended IdP Metadata Discovery Protocol by Andreas 2008-08-28
|
||||
* More information: https://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-idp-discovery.pdf
|
||||
*
|
||||
* @author Jaime Pérez <jaime.perez@uninett.no>, UNINETT AS.
|
||||
* @author Olav Morken, UNINETT AS.
|
||||
* @author Andreas Åkre Solberg <andreas@uninett.no>, UNINETT AS.
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
class SimpleSAML_XHTML_IdPDisco
|
||||
{
|
||||
|
||||
/**
|
||||
* An instance of the configuration class.
|
||||
*
|
||||
* @var SimpleSAML_Configuration
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* The identifier of this discovery service.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $instance;
|
||||
|
||||
|
||||
/**
|
||||
* An instance of the metadata handler, which will allow us to fetch metadata about IdPs.
|
||||
*
|
||||
* @var SimpleSAML_Metadata_MetaDataStorageHandler
|
||||
*/
|
||||
protected $metadata;
|
||||
|
||||
|
||||
/**
|
||||
* The users session.
|
||||
*
|
||||
* @var SimpleSAML_Session
|
||||
*/
|
||||
protected $session;
|
||||
|
||||
|
||||
/**
|
||||
* The metadata sets we find allowed entities in, in prioritized order.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $metadataSets;
|
||||
|
||||
|
||||
/**
|
||||
* The entity id of the SP which accesses this IdP discovery service.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $spEntityId;
|
||||
|
||||
/**
|
||||
* HTTP parameter from the request, indicating whether the discovery service
|
||||
* can interact with the user or not.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $isPassive;
|
||||
|
||||
/**
|
||||
* The SP request to set the IdPentityID...
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $setIdPentityID = null;
|
||||
|
||||
|
||||
/**
|
||||
* The name of the query parameter which should contain the users choice of IdP.
|
||||
* This option default to 'entityID' for Shibboleth compatibility.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $returnIdParam;
|
||||
|
||||
/**
|
||||
* The list of scoped idp's. The intersection between the metadata idpList
|
||||
* and scopedIDPList (given as a $_GET IDPList[] parameter) is presented to
|
||||
* the user. If the intersection is empty the metadata idpList is used.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $scopedIDPList = array();
|
||||
|
||||
/**
|
||||
* The URL the user should be redirected to after choosing an IdP.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $returnURL;
|
||||
|
||||
|
||||
/**
|
||||
* Initializes this discovery service.
|
||||
*
|
||||
* The constructor does the parsing of the request. If this is an invalid request, it will throw an exception.
|
||||
*
|
||||
* @param array $metadataSets Array with metadata sets we find remote entities in.
|
||||
* @param string $instance The name of this instance of the discovery service.
|
||||
*
|
||||
* @throws Exception If the request is invalid.
|
||||
*/
|
||||
public function __construct(array $metadataSets, $instance)
|
||||
{
|
||||
assert(is_string($instance));
|
||||
|
||||
// initialize standard classes
|
||||
$this->config = SimpleSAML_Configuration::getInstance();
|
||||
$this->metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
|
||||
$this->session = SimpleSAML_Session::getSessionFromRequest();
|
||||
$this->instance = $instance;
|
||||
$this->metadataSets = $metadataSets;
|
||||
|
||||
$this->log('Accessing discovery service.');
|
||||
|
||||
// standard discovery service parameters
|
||||
if (!array_key_exists('entityID', $_GET)) {
|
||||
throw new Exception('Missing parameter: entityID');
|
||||
} else {
|
||||
$this->spEntityId = $_GET['entityID'];
|
||||
}
|
||||
|
||||
if (!array_key_exists('returnIDParam', $_GET)) {
|
||||
$this->returnIdParam = 'entityID';
|
||||
} else {
|
||||
$this->returnIdParam = $_GET['returnIDParam'];
|
||||
}
|
||||
|
||||
$this->log('returnIdParam initially set to ['.$this->returnIdParam.']');
|
||||
|
||||
if (!array_key_exists('return', $_GET)) {
|
||||
throw new Exception('Missing parameter: return');
|
||||
} else {
|
||||
$this->returnURL = \SimpleSAML\Utils\HTTP::checkURLAllowed($_GET['return']);
|
||||
}
|
||||
|
||||
$this->isPassive = false;
|
||||
if (array_key_exists('isPassive', $_GET)) {
|
||||
if ($_GET['isPassive'] === 'true') {
|
||||
$this->isPassive = true;
|
||||
}
|
||||
}
|
||||
$this->log('isPassive initially set to ['.($this->isPassive ? 'TRUE' : 'FALSE').']');
|
||||
|
||||
if (array_key_exists('IdPentityID', $_GET)) {
|
||||
$this->setIdPentityID = $_GET['IdPentityID'];
|
||||
}
|
||||
|
||||
if (array_key_exists('IDPList', $_REQUEST)) {
|
||||
$this->scopedIDPList = $_REQUEST['IDPList'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log a message.
|
||||
*
|
||||
* This is an helper function for logging messages. It will prefix the messages with our
|
||||
* discovery service type.
|
||||
*
|
||||
* @param string $message The message which should be logged.
|
||||
*/
|
||||
protected function log($message)
|
||||
{
|
||||
SimpleSAML\Logger::info('idpDisco.'.$this->instance.': '.$message);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve cookie with the given name.
|
||||
*
|
||||
* This function will retrieve a cookie with the given name for the current discovery
|
||||
* service type.
|
||||
*
|
||||
* @param string $name The name of the cookie.
|
||||
*
|
||||
* @return string The value of the cookie with the given name, or null if no cookie with that name exists.
|
||||
*/
|
||||
protected function getCookie($name)
|
||||
{
|
||||
$prefixedName = 'idpdisco_'.$this->instance.'_'.$name;
|
||||
if (array_key_exists($prefixedName, $_COOKIE)) {
|
||||
return $_COOKIE[$prefixedName];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save cookie with the given name and value.
|
||||
*
|
||||
* This function will save a cookie with the given name and value for the current discovery
|
||||
* service type.
|
||||
*
|
||||
* @param string $name The name of the cookie.
|
||||
* @param string $value The value of the cookie.
|
||||
*/
|
||||
protected function setCookie($name, $value)
|
||||
{
|
||||
$prefixedName = 'idpdisco_'.$this->instance.'_'.$name;
|
||||
|
||||
$params = array(
|
||||
// we save the cookies for 90 days
|
||||
'lifetime' => (60 * 60 * 24 * 90),
|
||||
// the base path for cookies. This should be the installation directory for SimpleSAMLphp
|
||||
'path' => $this->config->getBasePath(),
|
||||
'httponly' => false,
|
||||
);
|
||||
|
||||
\SimpleSAML\Utils\HTTP::setCookie($prefixedName, $value, $params, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates the given IdP entity id.
|
||||
*
|
||||
* Takes a string with the IdP entity id, and returns the entity id if it is valid, or
|
||||
* null if not.
|
||||
*
|
||||
* @param string|null $idp The entity id we want to validate. This can be null, in which case we will return null.
|
||||
*
|
||||
* @return string|null The entity id if it is valid, null if not.
|
||||
*/
|
||||
protected function validateIdP($idp)
|
||||
{
|
||||
if ($idp === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$this->config->getBoolean('idpdisco.validate', true)) {
|
||||
return $idp;
|
||||
}
|
||||
|
||||
foreach ($this->metadataSets as $metadataSet) {
|
||||
try {
|
||||
$this->metadata->getMetaData($idp, $metadataSet);
|
||||
return $idp;
|
||||
} catch (Exception $e) {
|
||||
// continue
|
||||
}
|
||||
}
|
||||
|
||||
$this->log('Unable to validate IdP entity id ['.$idp.'].');
|
||||
|
||||
// the entity id wasn't valid
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the users choice of IdP.
|
||||
*
|
||||
* This function finds out which IdP the user has manually chosen, if any.
|
||||
*
|
||||
* @return string The entity id of the IdP the user has chosen, or null if the user has made no choice.
|
||||
*/
|
||||
protected function getSelectedIdP()
|
||||
{
|
||||
/* Parameter set from the Extended IdP Metadata Discovery Service Protocol, indicating that the user prefers
|
||||
* this IdP.
|
||||
*/
|
||||
if (!empty($this->setIdPentityID)) {
|
||||
return $this->validateIdP($this->setIdPentityID);
|
||||
}
|
||||
|
||||
// user has clicked on a link, or selected the IdP from a drop-down list
|
||||
if (array_key_exists('idpentityid', $_GET)) {
|
||||
return $this->validateIdP($_GET['idpentityid']);
|
||||
}
|
||||
|
||||
/* Search for the IdP selection from the form used by the links view. This form uses a name which equals
|
||||
* idp_<entityid>, so we search for that.
|
||||
*
|
||||
* Unfortunately, php replaces periods in the name with underscores, and there is no reliable way to get them
|
||||
* back. Therefore we do some quick and dirty parsing of the query string.
|
||||
*/
|
||||
$qstr = $_SERVER['QUERY_STRING'];
|
||||
$matches = array();
|
||||
if (preg_match('/(?:^|&)idp_([^=]+)=/', $qstr, $matches)) {
|
||||
return $this->validateIdP(urldecode($matches[1]));
|
||||
}
|
||||
|
||||
// no IdP chosen
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the users saved choice of IdP.
|
||||
*
|
||||
* @return string The entity id of the IdP the user has saved, or null if the user hasn't saved any choice.
|
||||
*/
|
||||
protected function getSavedIdP()
|
||||
{
|
||||
if (!$this->config->getBoolean('idpdisco.enableremember', false)) {
|
||||
// saving of IdP choices is disabled
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->getCookie('remember') === '1') {
|
||||
$this->log('Return previously saved IdP because of remember cookie set to 1');
|
||||
return $this->getPreviousIdP();
|
||||
}
|
||||
|
||||
if ($this->isPassive) {
|
||||
$this->log('Return previously saved IdP because of isPassive');
|
||||
return $this->getPreviousIdP();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the previous IdP the user used.
|
||||
*
|
||||
* @return string The entity id of the previous IdP the user used, or null if this is the first time.
|
||||
*/
|
||||
protected function getPreviousIdP()
|
||||
{
|
||||
return $this->validateIdP($this->getCookie('lastidp'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve a recommended IdP based on the IP address of the client.
|
||||
*
|
||||
* @return string|null The entity ID of the IdP if one is found, or null if not.
|
||||
*/
|
||||
protected function getFromCIDRhint()
|
||||
{
|
||||
foreach ($this->metadataSets as $metadataSet) {
|
||||
$idp = $this->metadata->getPreferredEntityIdFromCIDRhint($metadataSet, $_SERVER['REMOTE_ADDR']);
|
||||
if (!empty($idp)) {
|
||||
return $idp;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Try to determine which IdP the user should most likely use.
|
||||
*
|
||||
* This function will first look at the previous IdP the user has chosen. If the user
|
||||
* hasn't chosen an IdP before, it will look at the IP address.
|
||||
*
|
||||
* @return string The entity id of the IdP the user should most likely use.
|
||||
*/
|
||||
protected function getRecommendedIdP()
|
||||
{
|
||||
$idp = $this->getPreviousIdP();
|
||||
if ($idp !== null) {
|
||||
$this->log('Preferred IdP from previous use ['.$idp.'].');
|
||||
return $idp;
|
||||
}
|
||||
|
||||
$idp = $this->getFromCIDRhint();
|
||||
|
||||
if (!empty($idp)) {
|
||||
$this->log('Preferred IdP from CIDR hint ['.$idp.'].');
|
||||
return $idp;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save the current IdP choice to a cookie.
|
||||
*
|
||||
* @param string $idp The entityID of the IdP.
|
||||
*/
|
||||
protected function setPreviousIdP($idp)
|
||||
{
|
||||
assert(is_string($idp));
|
||||
|
||||
$this->log('Choice made ['.$idp.'] Setting cookie.');
|
||||
$this->setCookie('lastidp', $idp);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine whether the choice of IdP should be saved.
|
||||
*
|
||||
* @return boolean True if the choice should be saved, false otherwise.
|
||||
*/
|
||||
protected function saveIdP()
|
||||
{
|
||||
if (!$this->config->getBoolean('idpdisco.enableremember', false)) {
|
||||
// saving of IdP choices is disabled
|
||||
return false;
|
||||
}
|
||||
|
||||
if (array_key_exists('remember', $_GET)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine which IdP the user should go to, if any.
|
||||
*
|
||||
* @return string The entity id of the IdP the user should be sent to, or null if the user should choose.
|
||||
*/
|
||||
protected function getTargetIdP()
|
||||
{
|
||||
// first, check if the user has chosen an IdP
|
||||
$idp = $this->getSelectedIdP();
|
||||
if ($idp !== null) {
|
||||
// the user selected this IdP. Save the choice in a cookie
|
||||
$this->setPreviousIdP($idp);
|
||||
|
||||
if ($this->saveIdP()) {
|
||||
$this->setCookie('remember', '1');
|
||||
} else {
|
||||
$this->setCookie('remember', '0');
|
||||
}
|
||||
|
||||
return $idp;
|
||||
}
|
||||
|
||||
$this->log('getSelectedIdP() returned null');
|
||||
|
||||
// check if the user has saved an choice earlier
|
||||
$idp = $this->getSavedIdP();
|
||||
if ($idp !== null) {
|
||||
$this->log('Using saved choice ['.$idp.'].');
|
||||
return $idp;
|
||||
}
|
||||
|
||||
// the user has made no choice
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the list of IdPs which are stored in the metadata.
|
||||
*
|
||||
* @return array An array with entityid => metadata mappings.
|
||||
*/
|
||||
protected function getIdPList()
|
||||
{
|
||||
$idpList = array();
|
||||
foreach ($this->metadataSets as $metadataSet) {
|
||||
$newList = $this->metadata->getList($metadataSet);
|
||||
/*
|
||||
* Note that we merge the entities in reverse order. This ensures that it is the entity in the first
|
||||
* metadata set that "wins" if two metadata sets have the same entity.
|
||||
*/
|
||||
$idpList = array_merge($newList, $idpList);
|
||||
}
|
||||
|
||||
return $idpList;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the list of scoped idp
|
||||
*
|
||||
* @return array An array of IdP entities
|
||||
*/
|
||||
protected function getScopedIDPList()
|
||||
{
|
||||
return $this->scopedIDPList;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filter the list of IdPs.
|
||||
*
|
||||
* This method returns the IdPs that comply with the following conditions:
|
||||
* - The IdP does not have the 'hide.from.discovery' configuration option.
|
||||
*
|
||||
* @param array $list An associative array containing metadata for the IdPs to apply the filtering to.
|
||||
*
|
||||
* @return array An associative array containing metadata for the IdPs that were not filtered out.
|
||||
*/
|
||||
protected function filterList($list)
|
||||
{
|
||||
foreach ($list as $entity => $metadata) {
|
||||
if (array_key_exists('hide.from.discovery', $metadata) && $metadata['hide.from.discovery'] === true) {
|
||||
unset($list[$entity]);
|
||||
}
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if an IdP is set or if the request is passive, and redirect accordingly.
|
||||
*
|
||||
* @return void If there is no IdP targeted and this is not a passive request.
|
||||
*/
|
||||
protected function start()
|
||||
{
|
||||
$idp = $this->getTargetIdp();
|
||||
if ($idp !== null) {
|
||||
$extDiscoveryStorage = $this->config->getString('idpdisco.extDiscoveryStorage', null);
|
||||
if ($extDiscoveryStorage !== null) {
|
||||
$this->log('Choice made ['.$idp.'] (Forwarding to external discovery storage)');
|
||||
\SimpleSAML\Utils\HTTP::redirectTrustedURL($extDiscoveryStorage, array(
|
||||
'entityID' => $this->spEntityId,
|
||||
'IdPentityID' => $idp,
|
||||
'returnIDParam' => $this->returnIdParam,
|
||||
'isPassive' => 'true',
|
||||
'return' => $this->returnURL
|
||||
));
|
||||
} else {
|
||||
$this->log(
|
||||
'Choice made ['.$idp.'] (Redirecting the user back. returnIDParam='.$this->returnIdParam.')'
|
||||
);
|
||||
\SimpleSAML\Utils\HTTP::redirectTrustedURL($this->returnURL, array($this->returnIdParam => $idp));
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->isPassive) {
|
||||
$this->log('Choice not made. (Redirecting the user back without answer)');
|
||||
\SimpleSAML\Utils\HTTP::redirectTrustedURL($this->returnURL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles a request to this discovery service.
|
||||
*
|
||||
* The IdP disco parameters should be set before calling this function.
|
||||
*/
|
||||
public function handleRequest()
|
||||
{
|
||||
$this->start();
|
||||
|
||||
// no choice made. Show discovery service page
|
||||
$idpList = $this->getIdPList();
|
||||
$idpList = $this->filterList($idpList);
|
||||
$preferredIdP = $this->getRecommendedIdP();
|
||||
|
||||
$idpintersection = array_intersect(array_keys($idpList), $this->getScopedIDPList());
|
||||
if (sizeof($idpintersection) > 0) {
|
||||
$idpList = array_intersect_key($idpList, array_fill_keys($idpintersection, null));
|
||||
}
|
||||
|
||||
$idpintersection = array_values($idpintersection);
|
||||
|
||||
if (sizeof($idpintersection) == 1) {
|
||||
$this->log(
|
||||
'Choice made ['.$idpintersection[0].'] (Redirecting the user back. returnIDParam='.
|
||||
$this->returnIdParam.')'
|
||||
);
|
||||
\SimpleSAML\Utils\HTTP::redirectTrustedURL(
|
||||
$this->returnURL,
|
||||
array($this->returnIdParam => $idpintersection[0])
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Make use of an XHTML template to present the select IdP choice to the user. Currently the supported options
|
||||
* is either a drop down menu or a list view.
|
||||
*/
|
||||
switch ($this->config->getString('idpdisco.layout', 'links')) {
|
||||
case 'dropdown':
|
||||
$templateFile = 'selectidp-dropdown.php';
|
||||
break;
|
||||
case 'links':
|
||||
$templateFile = 'selectidp-links.php';
|
||||
break;
|
||||
default:
|
||||
throw new Exception('Invalid value for the \'idpdisco.layout\' option.');
|
||||
}
|
||||
|
||||
$t = new SimpleSAML_XHTML_Template($this->config, $templateFile, 'disco');
|
||||
$t->data['idplist'] = $idpList;
|
||||
$t->data['preferredidp'] = $preferredIdP;
|
||||
$t->data['return'] = $this->returnURL;
|
||||
$t->data['returnIDParam'] = $this->returnIdParam;
|
||||
$t->data['entityID'] = $this->spEntityId;
|
||||
$t->data['urlpattern'] = htmlspecialchars(\SimpleSAML\Utils\HTTP::getSelfURLNoQuery());
|
||||
$t->data['rememberenabled'] = $this->config->getBoolean('idpdisco.enableremember', false);
|
||||
$t->show();
|
||||
}
|
||||
}
|
||||
724
lib/SimpleSAML/XHTML/Template.php
Executable file
724
lib/SimpleSAML/XHTML/Template.php
Executable file
@@ -0,0 +1,724 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* A minimalistic XHTML PHP based template system implemented for SimpleSAMLphp.
|
||||
*
|
||||
* @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
use JaimePerez\TwigConfigurableI18n\Twig\Environment as Twig_Environment;
|
||||
use JaimePerez\TwigConfigurableI18n\Twig\Extensions\Extension\I18n as Twig_Extensions_Extension_I18n;
|
||||
|
||||
class SimpleSAML_XHTML_Template
|
||||
{
|
||||
/**
|
||||
* The data associated with this template, accessible within the template itself.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $data = array();
|
||||
|
||||
/**
|
||||
* A translator instance configured to work with this template.
|
||||
*
|
||||
* @var \SimpleSAML\Locale\Translate
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
/**
|
||||
* The localization backend
|
||||
*
|
||||
* @var \SimpleSAML\Locale\Localization
|
||||
*/
|
||||
private $localization;
|
||||
|
||||
/**
|
||||
* The configuration to use in this template.
|
||||
*
|
||||
* @var SimpleSAML_Configuration
|
||||
*/
|
||||
private $configuration;
|
||||
|
||||
/**
|
||||
* The file to load in this template.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $template = 'default.php';
|
||||
|
||||
/**
|
||||
* The twig environment.
|
||||
*
|
||||
* @var false|Twig_Environment
|
||||
*/
|
||||
private $twig;
|
||||
|
||||
/**
|
||||
* The template name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $twig_template;
|
||||
|
||||
/**
|
||||
* Current module, if any.
|
||||
*/
|
||||
private $module;
|
||||
|
||||
/**
|
||||
* A template controller, if any.
|
||||
*
|
||||
* Used to intercept certain parts of the template handling, while keeping away unwanted/unexpected hooks. Set
|
||||
* the 'theme.controller' configuration option to a class that implements the
|
||||
* SimpleSAML\XHTML\TemplateControllerInterface interface to use it.
|
||||
*
|
||||
* @var SimpleSAML\XHTML\TemplateControllerInterface
|
||||
*/
|
||||
private $controller;
|
||||
|
||||
|
||||
/**
|
||||
* Whether we are using a non-default theme or not.
|
||||
*
|
||||
* If we are using a theme, this variable holds an array with two keys: "module" and "name", those being the name
|
||||
* of the module and the name of the theme, respectively. If we are using the default theme, the variable defaults
|
||||
* to false.
|
||||
*
|
||||
* @var bool|array
|
||||
*/
|
||||
private $theme;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param SimpleSAML_Configuration $configuration Configuration object
|
||||
* @param string $template Which template file to load
|
||||
* @param string|null $defaultDictionary The default dictionary where tags will come from.
|
||||
*/
|
||||
public function __construct(\SimpleSAML_Configuration $configuration, $template, $defaultDictionary = null)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
$this->template = $template;
|
||||
// TODO: do not remove the slash from the beginning, change the templates instead!
|
||||
$this->data['baseurlpath'] = ltrim($this->configuration->getBasePath(), '/');
|
||||
|
||||
// parse module and template name
|
||||
list($this->module) = $this->findModuleAndTemplateName($template);
|
||||
|
||||
// parse config to find theme and module theme is in, if any
|
||||
list($this->theme['module'], $this->theme['name']) = self::findModuleAndTemplateName(
|
||||
$this->configuration->getString('theme.use', 'default')
|
||||
);
|
||||
|
||||
// initialize internationalization system
|
||||
$this->translator = new SimpleSAML\Locale\Translate($configuration, $defaultDictionary);
|
||||
$this->localization = new \SimpleSAML\Locale\Localization($configuration);
|
||||
|
||||
// check if we need to attach a theme controller
|
||||
$controller = $this->configuration->getString('theme.controller', false);
|
||||
if ($controller && class_exists($controller) &&
|
||||
class_implements($controller, '\SimpleSAML\XHTML\TemplateControllerInterface')
|
||||
) {
|
||||
$this->controller = new $controller();
|
||||
}
|
||||
|
||||
$this->twig = $this->setupTwig();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Normalize the name of the template to one of the possible alternatives.
|
||||
*
|
||||
* @param string $templateName The template name to normalize.
|
||||
* @return string The filename we need to look for.
|
||||
*/
|
||||
private function normalizeTemplateName($templateName)
|
||||
{
|
||||
if (strripos($templateName, '.twig')) {
|
||||
return $templateName;
|
||||
}
|
||||
$phppos = strripos($templateName, '.php');
|
||||
if ($phppos) {
|
||||
$templateName = substr($templateName, 0, $phppos);
|
||||
}
|
||||
$tplpos = strripos($templateName, '.tpl');
|
||||
if ($tplpos) {
|
||||
$templateName = substr($templateName, 0, $tplpos);
|
||||
}
|
||||
return $templateName.'.twig';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set up the places where twig can look for templates.
|
||||
*
|
||||
* @return Twig_Loader_Filesystem The twig template loader or false if the template does not exist.
|
||||
* @throws Twig_Error_Loader In case a failure occurs.
|
||||
*/
|
||||
private function setupTwigTemplatepaths()
|
||||
{
|
||||
$filename = $this->normalizeTemplateName($this->template);
|
||||
|
||||
// get namespace if any
|
||||
list($namespace, $filename) = self::findModuleAndTemplateName($filename);
|
||||
$this->twig_template = ($namespace !== null) ? '@'.$namespace.'/'.$filename : $filename;
|
||||
$loader = new \Twig_Loader_Filesystem();
|
||||
$templateDirs = $this->findThemeTemplateDirs();
|
||||
if ($this->module) {
|
||||
$templateDirs[] = array($this->module => $this->getModuleTemplateDir($this->module));
|
||||
}
|
||||
if ($this->theme['module']) {
|
||||
try {
|
||||
$templateDirs[] = array($this->theme['module'] => $this->getModuleTemplateDir($this->theme['module']));
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
// either the module is not enabled or it has no "templates" directory, ignore
|
||||
}
|
||||
}
|
||||
|
||||
// default, themeless templates are checked last
|
||||
$templateDirs[] = array(
|
||||
\Twig_Loader_Filesystem::MAIN_NAMESPACE => $this->configuration->resolvePath('templates')
|
||||
);
|
||||
foreach ($templateDirs as $entry) {
|
||||
$loader->addPath($entry[key($entry)], key($entry));
|
||||
}
|
||||
return $loader;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Setup twig.
|
||||
*/
|
||||
private function setupTwig()
|
||||
{
|
||||
$auto_reload = $this->configuration->getBoolean('template.auto_reload', true);
|
||||
$cache = $this->configuration->getString('template.cache', false);
|
||||
// set up template paths
|
||||
$loader = $this->setupTwigTemplatepaths();
|
||||
// abort if twig template does not exist
|
||||
if (!$loader->exists($this->twig_template)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// load extra i18n domains
|
||||
if ($this->module) {
|
||||
$this->localization->addModuleDomain($this->module);
|
||||
}
|
||||
if ($this->theme['module'] !== null && $this->theme['module'] !== $this->module) {
|
||||
$this->localization->addModuleDomain($this->theme['module']);
|
||||
}
|
||||
|
||||
$options = array(
|
||||
'cache' => $cache,
|
||||
'auto_reload' => $auto_reload,
|
||||
'translation_function' => array('\SimpleSAML\Locale\Translate', 'translateSingularNativeGettext'),
|
||||
'translation_function_plural' => array('\SimpleSAML\Locale\Translate', 'translatePluralNativeGettext'),
|
||||
);
|
||||
|
||||
// set up translation
|
||||
if ($this->localization->i18nBackend === \SimpleSAML\Locale\Localization::GETTEXT_I18N_BACKEND) {
|
||||
$options['translation_function'] = array('\SimpleSAML\Locale\Translate', 'translateSingularGettext');
|
||||
$options['translation_function_plural'] = array(
|
||||
'\SimpleSAML\Locale\Translate',
|
||||
'translatePluralGettext'
|
||||
);
|
||||
} // TODO: add a branch for the old SimpleSAMLphp backend
|
||||
|
||||
$twig = new Twig_Environment($loader, $options);
|
||||
$twig->addExtension(new Twig_Extensions_Extension_I18n());
|
||||
|
||||
// initialize some basic context
|
||||
$langParam = $this->configuration->getString('language.parameter.name', 'language');
|
||||
$twig->addGlobal('languageParameterName', $langParam);
|
||||
$twig->addGlobal('localeBackend', $this->configuration->getString('language.i18n.backend', 'SimpleSAMLphp'));
|
||||
$twig->addGlobal('currentLanguage', $this->translator->getLanguage()->getLanguage());
|
||||
$twig->addGlobal('isRTL', false); // language RTL configuration
|
||||
if ($this->translator->getLanguage()->isLanguageRTL()) {
|
||||
$twig->addGlobal('isRTL', true);
|
||||
}
|
||||
$queryParams = $_GET; // add query parameters, in case we need them in the template
|
||||
if (isset($queryParams[$langParam])) {
|
||||
unset($queryParams[$langParam]);
|
||||
}
|
||||
$twig->addGlobal('queryParams', $queryParams);
|
||||
$twig->addGlobal('templateId', str_replace('.twig', '', $this->normalizeTemplateName($this->template)));
|
||||
$twig->addGlobal('isProduction', $this->configuration->getBoolean('production', true));
|
||||
|
||||
// add a filter for translations out of arrays
|
||||
$twig->addFilter(
|
||||
new \Twig_SimpleFilter(
|
||||
'translateFromArray',
|
||||
array('\SimpleSAML\Locale\Translate', 'translateFromArray'),
|
||||
array('needs_context' => true)
|
||||
)
|
||||
);
|
||||
|
||||
if ($this->controller) {
|
||||
$this->controller->setUpTwig($twig);
|
||||
}
|
||||
|
||||
return $twig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add overriding templates from the configured theme.
|
||||
*
|
||||
* @return array An array of module => templatedir lookups.
|
||||
*/
|
||||
private function findThemeTemplateDirs()
|
||||
{
|
||||
if ($this->theme['module'] === null) { // no module involved
|
||||
return array();
|
||||
}
|
||||
|
||||
// setup directories & namespaces
|
||||
$themeDir = \SimpleSAML\Module::getModuleDir($this->theme['module']).'/themes/'.$this->theme['name'];
|
||||
$subdirs = scandir($themeDir);
|
||||
if (empty($subdirs)) { // no subdirectories in the theme directory, nothing to do here
|
||||
// this is probably wrong, log a message
|
||||
\SimpleSAML\Logger::warning('Empty theme directory for theme "'.$this->theme['name'].'".');
|
||||
return array();
|
||||
}
|
||||
|
||||
$themeTemplateDirs = array();
|
||||
foreach ($subdirs as $entry) {
|
||||
// discard anything that's not a directory. Expression is negated to profit from lazy evaluation
|
||||
if (!($entry !== '.' && $entry !== '..' && is_dir($themeDir.'/'.$entry))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// set correct name for the default namespace
|
||||
$ns = ($entry === 'default') ? \Twig_Loader_Filesystem::MAIN_NAMESPACE : $entry;
|
||||
$themeTemplateDirs[] = array($ns => $themeDir.'/'.$entry);
|
||||
}
|
||||
return $themeTemplateDirs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template directory of a module, if it exists.
|
||||
*
|
||||
* @return string The templates directory of a module.
|
||||
*
|
||||
* @throws InvalidArgumentException If the module is not enabled or it has no templates directory.
|
||||
*/
|
||||
private function getModuleTemplateDir($module)
|
||||
{
|
||||
if (!\SimpleSAML\Module::isModuleEnabled($module)) {
|
||||
throw new InvalidArgumentException('The module \''.$module.'\' is not enabled.');
|
||||
}
|
||||
$moduledir = \SimpleSAML\Module::getModuleDir($module);
|
||||
// check if module has a /templates dir, if so, append
|
||||
$templatedir = $moduledir.'/templates';
|
||||
if (!is_dir($templatedir)) {
|
||||
throw new InvalidArgumentException('The module \''.$module.'\' has no templates directory.');
|
||||
}
|
||||
return $templatedir;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add the templates from a given module.
|
||||
*
|
||||
* Note that the module must be installed, enabled, and contain a "templates" directory.
|
||||
*
|
||||
* @param string $module The module where we need to search for templates.
|
||||
*
|
||||
* @throws InvalidArgumentException If the module is not enabled or it has no templates directory.
|
||||
*/
|
||||
public function addTemplatesFromModule($module)
|
||||
{
|
||||
$dir = $this->getModuleTemplateDir($module);
|
||||
/** @var Twig_Loader_Filesystem $loader */
|
||||
$loader = $this->twig->getLoader();
|
||||
$loader->addPath($dir, $module);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate an array for its use in the language bar, indexed by the ISO 639-2 codes of the languages available,
|
||||
* containing their localized names and the URL that should be used in order to change to that language.
|
||||
*
|
||||
* @return array The array containing information of all available languages.
|
||||
*/
|
||||
private function generateLanguageBar()
|
||||
{
|
||||
$languages = $this->translator->getLanguage()->getLanguageList();
|
||||
$langmap = null;
|
||||
if (count($languages) > 1) {
|
||||
$parameterName = $this->getTranslator()->getLanguage()->getLanguageParameterName();
|
||||
$langmap = array();
|
||||
foreach ($languages as $lang => $current) {
|
||||
$lang = strtolower($lang);
|
||||
$langname = $this->translator->getLanguage()->getLanguageLocalizedName($lang);
|
||||
$url = false;
|
||||
if (!$current) {
|
||||
$url = htmlspecialchars(\SimpleSAML\Utils\HTTP::addURLParameters(
|
||||
'',
|
||||
array($parameterName => $lang)
|
||||
));
|
||||
}
|
||||
$langmap[$lang] = array(
|
||||
'name' => $langname,
|
||||
'url' => $url,
|
||||
);
|
||||
}
|
||||
}
|
||||
return $langmap;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set some default context
|
||||
*/
|
||||
private function twigDefaultContext()
|
||||
{
|
||||
// show language bar by default
|
||||
if (!isset($this->data['hideLanguageBar'])) {
|
||||
$this->data['hideLanguageBar'] = false;
|
||||
}
|
||||
// get languagebar
|
||||
$this->data['languageBar'] = null;
|
||||
if ($this->data['hideLanguageBar'] === false) {
|
||||
$languageBar = $this->generateLanguageBar();
|
||||
if (is_null($languageBar)) {
|
||||
$this->data['hideLanguageBar'] = true;
|
||||
} else {
|
||||
$this->data['languageBar'] = $languageBar;
|
||||
}
|
||||
}
|
||||
|
||||
// assure that there is a <title> and <h1>
|
||||
if (isset($this->data['header']) && !isset($this->data['pagetitle'])) {
|
||||
$this->data['pagetitle'] = $this->data['header'];
|
||||
}
|
||||
if (!isset($this->data['pagetitle'])) {
|
||||
$this->data['pagetitle'] = 'SimpleSAMLphp';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show the template to the user.
|
||||
*/
|
||||
public function show()
|
||||
{
|
||||
if ($this->twig !== false) {
|
||||
$this->twigDefaultContext();
|
||||
if ($this->controller) {
|
||||
$this->controller->display($this->data);
|
||||
}
|
||||
echo $this->twig->render($this->twig_template, $this->data);
|
||||
} else {
|
||||
$filename = $this->findTemplatePath($this->template);
|
||||
require($filename);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find module the template is in, if any
|
||||
*
|
||||
* @param string $template The relative path from the theme directory to the template file.
|
||||
*
|
||||
* @return array An array with the name of the module and template
|
||||
*/
|
||||
private function findModuleAndTemplateName($template)
|
||||
{
|
||||
$tmp = explode(':', $template, 2);
|
||||
return (count($tmp) === 2) ? array($tmp[0], $tmp[1]) : array(null, $tmp[0]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find template path.
|
||||
*
|
||||
* This function locates the given template based on the template name. It will first search for the template in
|
||||
* the current theme directory, and then the default theme.
|
||||
*
|
||||
* The template name may be on the form <module name>:<template path>, in which case it will search for the
|
||||
* template file in the given module.
|
||||
*
|
||||
* @param string $template The relative path from the theme directory to the template file.
|
||||
*
|
||||
* @return string The absolute path to the template file.
|
||||
*
|
||||
* @throws Exception If the template file couldn't be found.
|
||||
*/
|
||||
private function findTemplatePath($template, $throw_exception = true)
|
||||
{
|
||||
assert(is_string($template));
|
||||
|
||||
list($templateModule, $templateName) = $this->findModuleAndTemplateName($template);
|
||||
$templateModule = ($templateModule !== null) ? $templateModule : 'default';
|
||||
|
||||
// first check the current theme
|
||||
if ($this->theme['module'] !== null) {
|
||||
// .../module/<themeModule>/themes/<themeName>/<templateModule>/<templateName>
|
||||
|
||||
$filename = \SimpleSAML\Module::getModuleDir($this->theme['module']).
|
||||
'/themes/'.$this->theme['name'].'/'.$templateModule.'/'.$templateName;
|
||||
} elseif ($templateModule !== 'default') {
|
||||
// .../module/<templateModule>/templates/<templateName>
|
||||
$filename = \SimpleSAML\Module::getModuleDir($templateModule).'/templates/'.$templateName;
|
||||
} else {
|
||||
// .../templates/<theme>/<templateName>
|
||||
$filename = $this->configuration->getPathValue('templatedir', 'templates/').$templateName;
|
||||
}
|
||||
|
||||
if (file_exists($filename)) {
|
||||
return $filename;
|
||||
}
|
||||
|
||||
// not found in current theme
|
||||
\SimpleSAML\Logger::debug(
|
||||
$_SERVER['PHP_SELF'].' - Template: Could not find template file ['.$template.'] at ['.
|
||||
$filename.'] - now trying the base template'
|
||||
);
|
||||
|
||||
// try default theme
|
||||
if ($templateModule !== 'default') {
|
||||
// .../module/<templateModule>/templates/<templateName>
|
||||
$filename = \SimpleSAML\Module::getModuleDir($templateModule).'/templates/'.$templateName;
|
||||
} else {
|
||||
// .../templates/<templateName>
|
||||
$filename = $this->configuration->getPathValue('templatedir', 'templates/').'/'.$templateName;
|
||||
}
|
||||
|
||||
if (file_exists($filename)) {
|
||||
return $filename;
|
||||
}
|
||||
|
||||
// not found in default template
|
||||
if ($throw_exception) {
|
||||
// log error and throw exception
|
||||
$error = 'Template: Could not find template file ['.$template.'] at ['.$filename.']';
|
||||
\SimpleSAML\Logger::critical($_SERVER['PHP_SELF'].' - '.$error);
|
||||
|
||||
throw new Exception($error);
|
||||
} else {
|
||||
// missing template expected, return NULL
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the internal translator object used by this template.
|
||||
*
|
||||
* @return \SimpleSAML\Locale\Translate The translator that will be used with this template.
|
||||
*/
|
||||
public function getTranslator()
|
||||
{
|
||||
return $this->translator;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the current instance of Twig in use.
|
||||
*
|
||||
* @return false|Twig_Environment The Twig instance in use, or false if Twig is not used.
|
||||
*/
|
||||
public function getTwig()
|
||||
{
|
||||
return $this->twig;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Deprecated methods of this interface, all of them should go away.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
*
|
||||
* @return string
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use \SimpleSAML\Locale\Language::getLanguage()
|
||||
* instead.
|
||||
*/
|
||||
public function getAttributeTranslation($name)
|
||||
{
|
||||
return $this->translator->getAttributeTranslation($name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use \SimpleSAML\Locale\Language::getLanguage()
|
||||
* instead.
|
||||
*/
|
||||
public function getLanguage()
|
||||
{
|
||||
return $this->translator->getLanguage()->getLanguage();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $language
|
||||
* @param bool $setLanguageCookie
|
||||
*
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use \SimpleSAML\Locale\Language::setLanguage()
|
||||
* instead.
|
||||
*/
|
||||
public function setLanguage($language, $setLanguageCookie = true)
|
||||
{
|
||||
$this->translator->getLanguage()->setLanguage($language, $setLanguageCookie);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return null|string
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use \SimpleSAML\Locale\Language::getLanguageCookie()
|
||||
* instead.
|
||||
*/
|
||||
public static function getLanguageCookie()
|
||||
{
|
||||
return \SimpleSAML\Locale\Language::getLanguageCookie();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $language
|
||||
*
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use \SimpleSAML\Locale\Language::setLanguageCookie()
|
||||
* instead.
|
||||
*/
|
||||
public static function setLanguageCookie($language)
|
||||
{
|
||||
\SimpleSAML\Locale\Language::setLanguageCookie($language);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wraps Language->getLanguageList
|
||||
*/
|
||||
private function getLanguageList()
|
||||
{
|
||||
return $this->translator->getLanguage()->getLanguageList();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $tag
|
||||
*
|
||||
* @return array
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use \SimpleSAML\Locale\Translate::getTag() instead.
|
||||
*/
|
||||
public function getTag($tag)
|
||||
{
|
||||
return $this->translator->getTag($tag);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Temporary wrapper for \SimpleSAML\Locale\Translate::getPreferredTranslation().
|
||||
*
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use
|
||||
* \SimpleSAML\Locale\Translate::getPreferredTranslation() instead.
|
||||
*/
|
||||
public function getTranslation($translations)
|
||||
{
|
||||
return $this->translator->getPreferredTranslation($translations);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Includes a file relative to the template base directory.
|
||||
* This function can be used to include headers and footers etc.
|
||||
*
|
||||
*/
|
||||
private function includeAtTemplateBase($file)
|
||||
{
|
||||
$data = $this->data;
|
||||
|
||||
$filename = $this->findTemplatePath($file);
|
||||
|
||||
include($filename);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wraps Translate->includeInlineTranslation()
|
||||
*
|
||||
* @see \SimpleSAML\Locale\Translate::includeInlineTranslation()
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use
|
||||
* \SimpleSAML\Locale\Translate::includeInlineTranslation() instead.
|
||||
*/
|
||||
public function includeInlineTranslation($tag, $translation)
|
||||
{
|
||||
$this->translator->includeInlineTranslation($tag, $translation);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $file
|
||||
* @param null $otherConfig
|
||||
*
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use
|
||||
* \SimpleSAML\Locale\Translate::includeLanguageFile() instead.
|
||||
*/
|
||||
public function includeLanguageFile($file, $otherConfig = null)
|
||||
{
|
||||
$this->translator->includeLanguageFile($file, $otherConfig);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wrap Language->isLanguageRTL
|
||||
*/
|
||||
private function isLanguageRTL()
|
||||
{
|
||||
return $this->translator->getLanguage()->isLanguageRTL();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Merge two translation arrays.
|
||||
*
|
||||
* @param array $def The array holding string definitions.
|
||||
* @param array $lang The array holding translations for every string.
|
||||
*
|
||||
* @return array The recursive merge of both arrays.
|
||||
* @deprecated This method will be removed in SimpleSAMLphp 2.0. Please use array_merge_recursive() instead.
|
||||
*/
|
||||
public static function lang_merge($def, $lang)
|
||||
{
|
||||
foreach ($def as $key => $value) {
|
||||
if (array_key_exists($key, $lang)) {
|
||||
$def[$key] = array_merge($value, $lang[$key]);
|
||||
}
|
||||
}
|
||||
return $def;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Behave like Language->noop to mark a tag for translation but actually do it later.
|
||||
*
|
||||
* @see \SimpleSAML\Locale\Translate::noop()
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use \SimpleSAML\Locale\Translate::noop() instead.
|
||||
*/
|
||||
public static function noop($tag)
|
||||
{
|
||||
return $tag;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wrap Language->t to translate tag into the current language, with a fallback to english.
|
||||
*
|
||||
* @see \SimpleSAML\Locale\Translate::t()
|
||||
* @deprecated This method will be removed in SSP 2.0. Please use \SimpleSAML\Locale\Translate::t() instead.
|
||||
*/
|
||||
public function t(
|
||||
$tag,
|
||||
$replacements = array(),
|
||||
$fallbackdefault = true,
|
||||
$oldreplacements = array(),
|
||||
$striptags = false
|
||||
) {
|
||||
return $this->translator->t($tag, $replacements, $fallbackdefault, $oldreplacements, $striptags);
|
||||
}
|
||||
}
|
||||
33
lib/SimpleSAML/XHTML/TemplateControllerInterface.php
Executable file
33
lib/SimpleSAML/XHTML/TemplateControllerInterface.php
Executable file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace SimpleSAML\XHTML;
|
||||
|
||||
/**
|
||||
* Interface that allows modules to run several hooks for templates.
|
||||
*
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
interface TemplateControllerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Implement to modify the twig environment after its initialization (e.g. add filters or extensions).
|
||||
*
|
||||
* @param \Twig_Environment $twig The current twig environment.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setUpTwig(\Twig_Environment &$twig);
|
||||
|
||||
|
||||
/**
|
||||
* Implement to add, delete or modify the data passed to the template.
|
||||
*
|
||||
* This method will be called right before displaying the template.
|
||||
*
|
||||
* @param array $data The current data used by the template.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display(&$data);
|
||||
}
|
||||
140
lib/SimpleSAML/XML/Errors.php
Executable file
140
lib/SimpleSAML/XML/Errors.php
Executable file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This class defines an interface for accessing errors from the XML library.
|
||||
*
|
||||
* In PHP versions which doesn't support accessing error information, this class
|
||||
* will hide that, and pretend that no errors were logged.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS.
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
namespace SimpleSAML\XML;
|
||||
|
||||
use LibXMLError;
|
||||
|
||||
class Errors
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array This is an stack of error logs. The topmost element is the one we are currently working on.
|
||||
*/
|
||||
private static $errorStack = array();
|
||||
|
||||
/**
|
||||
* @var bool This is the xml error state we had before we began logging.
|
||||
*/
|
||||
private static $xmlErrorState;
|
||||
|
||||
|
||||
/**
|
||||
* Append current XML errors to the current stack level.
|
||||
*/
|
||||
private static function addErrors()
|
||||
{
|
||||
$currentErrors = libxml_get_errors();
|
||||
libxml_clear_errors();
|
||||
|
||||
$level = count(self::$errorStack) - 1;
|
||||
self::$errorStack[$level] = array_merge(self::$errorStack[$level], $currentErrors);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Start error logging.
|
||||
*
|
||||
* A call to this function will begin a new error logging context. Every call must have
|
||||
* a corresponding call to end().
|
||||
*/
|
||||
public static function begin()
|
||||
{
|
||||
|
||||
// Check whether the error access functions are present
|
||||
if (!function_exists('libxml_use_internal_errors')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (count(self::$errorStack) === 0) {
|
||||
// No error logging is currently in progress. Initialize it.
|
||||
self::$xmlErrorState = libxml_use_internal_errors(true);
|
||||
libxml_clear_errors();
|
||||
} else {
|
||||
/* We have already started error logging. Append the current errors to the
|
||||
* list of errors in this level.
|
||||
*/
|
||||
self::addErrors();
|
||||
}
|
||||
|
||||
// Add a new level to the error stack
|
||||
self::$errorStack[] = array();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* End error logging.
|
||||
*
|
||||
* @return array An array with the LibXMLErrors which has occurred since begin() was called.
|
||||
*/
|
||||
public static function end()
|
||||
{
|
||||
|
||||
// Check whether the error access functions are present
|
||||
if (!function_exists('libxml_use_internal_errors')) {
|
||||
// Pretend that no errors occurred
|
||||
return array();
|
||||
}
|
||||
|
||||
// Add any errors which may have occurred
|
||||
self::addErrors();
|
||||
|
||||
|
||||
$ret = array_pop(self::$errorStack);
|
||||
|
||||
if (count(self::$errorStack) === 0) {
|
||||
// Disable our error logging and restore the previous state
|
||||
libxml_use_internal_errors(self::$xmlErrorState);
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Format an error as a string.
|
||||
*
|
||||
* This function formats the given LibXMLError object as a string.
|
||||
*
|
||||
* @param \LibXMLError $error The LibXMLError which should be formatted.
|
||||
* @return string A string representing the given LibXMLError.
|
||||
*/
|
||||
public static function formatError($error)
|
||||
{
|
||||
assert($error instanceof LibXMLError);
|
||||
return 'level=' . $error->level . ',code=' . $error->code . ',line=' . $error->line . ',col=' . $error->column .
|
||||
',msg=' . trim($error->message);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Format a list of errors as a string.
|
||||
*
|
||||
* This fucntion takes an array of LibXMLError objects and creates a string with all the errors.
|
||||
* Each error will be separated by a newline, and the string will end with a newline-character.
|
||||
*
|
||||
* @param array $errors An array of errors.
|
||||
* @return string A string representing the errors. An empty string will be returned if there were no
|
||||
* errors in the array.
|
||||
*/
|
||||
public static function formatErrors($errors)
|
||||
{
|
||||
assert(is_array($errors));
|
||||
|
||||
$ret = '';
|
||||
foreach ($errors as $error) {
|
||||
$ret .= self::formatError($error) . "\n";
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
77
lib/SimpleSAML/XML/Parser.php
Executable file
77
lib/SimpleSAML/XML/Parser.php
Executable file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file will help doing XPath queries in SAML 2 XML documents.
|
||||
*
|
||||
* @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
namespace SimpleSAML\XML;
|
||||
|
||||
class Parser
|
||||
{
|
||||
public $simplexml = null;
|
||||
|
||||
public function __construct($xml)
|
||||
{
|
||||
;
|
||||
$this->simplexml = new \SimpleXMLElement($xml);
|
||||
$this->simplexml->registerXPathNamespace('saml2', 'urn:oasis:names:tc:SAML:2.0:assertion');
|
||||
$this->simplexml->registerXPathNamespace('saml2meta', 'urn:oasis:names:tc:SAML:2.0:metadata');
|
||||
$this->simplexml->registerXPathNamespace('ds', 'http://www.w3.org/2000/09/xmldsig#');
|
||||
}
|
||||
|
||||
public static function fromSimpleXMLElement(\SimpleXMLElement $element)
|
||||
{
|
||||
|
||||
// Traverse all existing namespaces in element
|
||||
$namespaces = $element->getNamespaces();
|
||||
foreach ($namespaces as $prefix => $ns) {
|
||||
$element[(($prefix === '') ? 'xmlns' : 'xmlns:' . $prefix)] = $ns;
|
||||
}
|
||||
|
||||
/* Create a new parser with the xml document where the namespace definitions
|
||||
* are added.
|
||||
*/
|
||||
$parser = new Parser($element->asXML());
|
||||
return $parser;
|
||||
}
|
||||
|
||||
public function getValueDefault($xpath, $defvalue)
|
||||
{
|
||||
try {
|
||||
return $this->getValue($xpath, true);
|
||||
} catch (\Exception $e) {
|
||||
return $defvalue;
|
||||
}
|
||||
}
|
||||
|
||||
public function getValue($xpath, $required = false)
|
||||
{
|
||||
$result = $this->simplexml->xpath($xpath);
|
||||
if (!is_array($result) || empty($result)) {
|
||||
if ($required) {
|
||||
throw new \Exception('Could not get value from XML document using the following XPath expression: ' . $xpath);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return (string) $result[0];
|
||||
}
|
||||
|
||||
public function getValueAlternatives(array $xpath, $required = false)
|
||||
{
|
||||
foreach ($xpath as $x) {
|
||||
$seek = $this->getValue($x);
|
||||
if ($seek) {
|
||||
return $seek;
|
||||
}
|
||||
}
|
||||
if ($required) {
|
||||
throw new \Exception('Could not get value from XML document using multiple alternative XPath expressions.');
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
53
lib/SimpleSAML/XML/Shib13/AuthnRequest.php
Executable file
53
lib/SimpleSAML/XML/Shib13/AuthnRequest.php
Executable file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* The Shibboleth 1.3 Authentication Request. Not part of SAML 1.1,
|
||||
* but an extension using query paramters no XML.
|
||||
*
|
||||
* @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
namespace SimpleSAML\XML\Shib13;
|
||||
|
||||
class AuthnRequest
|
||||
{
|
||||
private $issuer = null;
|
||||
private $relayState = null;
|
||||
|
||||
public function setRelayState($relayState)
|
||||
{
|
||||
$this->relayState = $relayState;
|
||||
}
|
||||
|
||||
public function getRelayState()
|
||||
{
|
||||
return $this->relayState;
|
||||
}
|
||||
|
||||
public function setIssuer($issuer)
|
||||
{
|
||||
$this->issuer = $issuer;
|
||||
}
|
||||
public function getIssuer()
|
||||
{
|
||||
return $this->issuer;
|
||||
}
|
||||
|
||||
public function createRedirect($destination, $shire)
|
||||
{
|
||||
$metadata = \SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
|
||||
$idpmetadata = $metadata->getMetaDataConfig($destination, 'shib13-idp-remote');
|
||||
|
||||
$desturl = $idpmetadata->getDefaultEndpoint('SingleSignOnService', array('urn:mace:shibboleth:1.0:profiles:AuthnRequest'));
|
||||
$desturl = $desturl['Location'];
|
||||
|
||||
$target = $this->getRelayState();
|
||||
|
||||
$url = $desturl . '?' .
|
||||
'providerId=' . urlencode($this->getIssuer()) .
|
||||
'&shire=' . urlencode($shire) .
|
||||
(isset($target) ? '&target=' . urlencode($target) : '');
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
480
lib/SimpleSAML/XML/Shib13/AuthnResponse.php
Executable file
480
lib/SimpleSAML/XML/Shib13/AuthnResponse.php
Executable file
@@ -0,0 +1,480 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* A Shibboleth 1.3 authentication response.
|
||||
*
|
||||
* @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
namespace SimpleSAML\XML\Shib13;
|
||||
|
||||
use DOMDocument;
|
||||
use DOMNode;
|
||||
use SAML2\DOMDocumentFactory;
|
||||
use SAML2\Utils;
|
||||
use SimpleSAML\Utils\Config;
|
||||
use SimpleSAML\Utils\Random;
|
||||
use SimpleSAML\Utils\Time;
|
||||
use SimpleSAML\XML\Validator;
|
||||
|
||||
class AuthnResponse
|
||||
{
|
||||
|
||||
/**
|
||||
* @var \SimpleSAML\XML\Validator This variable contains an XML validator for this message.
|
||||
*/
|
||||
private $validator = null;
|
||||
|
||||
|
||||
/**
|
||||
* @var bool Whether this response was validated by some external means (e.g. SSL).
|
||||
*/
|
||||
private $messageValidated = false;
|
||||
|
||||
|
||||
const SHIB_PROTOCOL_NS = 'urn:oasis:names:tc:SAML:1.0:protocol';
|
||||
const SHIB_ASSERT_NS = 'urn:oasis:names:tc:SAML:1.0:assertion';
|
||||
|
||||
|
||||
/**
|
||||
* @var \DOMDocument The DOMDocument which represents this message.
|
||||
*/
|
||||
private $dom;
|
||||
|
||||
/**
|
||||
* @var string|null The relaystate which is associated with this response.
|
||||
*/
|
||||
private $relayState = null;
|
||||
|
||||
|
||||
/**
|
||||
* Set whether this message was validated externally.
|
||||
*
|
||||
* @param bool $messageValidated TRUE if the message is already validated, FALSE if not.
|
||||
*/
|
||||
public function setMessageValidated($messageValidated)
|
||||
{
|
||||
assert(is_bool($messageValidated));
|
||||
|
||||
$this->messageValidated = $messageValidated;
|
||||
}
|
||||
|
||||
|
||||
public function setXML($xml)
|
||||
{
|
||||
assert(is_string($xml));
|
||||
|
||||
try {
|
||||
$this->dom = DOMDocumentFactory::fromString(str_replace("\r", "", $xml));
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception('Unable to parse AuthnResponse XML.');
|
||||
}
|
||||
}
|
||||
|
||||
public function setRelayState($relayState)
|
||||
{
|
||||
$this->relayState = $relayState;
|
||||
}
|
||||
|
||||
public function getRelayState()
|
||||
{
|
||||
return $this->relayState;
|
||||
}
|
||||
|
||||
public function validate()
|
||||
{
|
||||
assert($this->dom instanceof DOMDocument);
|
||||
|
||||
if ($this->messageValidated) {
|
||||
// This message was validated externally
|
||||
return true;
|
||||
}
|
||||
|
||||
// Validate the signature
|
||||
$this->validator = new Validator($this->dom, array('ResponseID', 'AssertionID'));
|
||||
|
||||
// Get the issuer of the response
|
||||
$issuer = $this->getIssuer();
|
||||
|
||||
// Get the metadata of the issuer
|
||||
$metadata = \SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
|
||||
$md = $metadata->getMetaDataConfig($issuer, 'shib13-idp-remote');
|
||||
|
||||
$publicKeys = $md->getPublicKeys('signing');
|
||||
if (!empty($publicKeys)) {
|
||||
$certFingerprints = array();
|
||||
foreach ($publicKeys as $key) {
|
||||
if ($key['type'] !== 'X509Certificate') {
|
||||
continue;
|
||||
}
|
||||
$certFingerprints[] = sha1(base64_decode($key['X509Certificate']));
|
||||
}
|
||||
$this->validator->validateFingerprint($certFingerprints);
|
||||
} elseif ($md->hasValue('certFingerprint')) {
|
||||
$certFingerprints = $md->getArrayizeString('certFingerprint');
|
||||
|
||||
// Validate the fingerprint
|
||||
$this->validator->validateFingerprint($certFingerprints);
|
||||
} elseif ($md->hasValue('caFile')) {
|
||||
// Validate against CA
|
||||
$this->validator->validateCA(Config::getCertPath($md->getString('caFile')));
|
||||
} else {
|
||||
throw new \SimpleSAML_Error_Exception('Missing certificate in Shibboleth 1.3 IdP Remote metadata for identity provider [' . $issuer . '].');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the given node is validated by the signature on this response.
|
||||
*
|
||||
* @param \DOMElement $node Node to be validated.
|
||||
* @return bool TRUE if the node is validated or FALSE if not.
|
||||
*/
|
||||
private function isNodeValidated($node)
|
||||
{
|
||||
if ($this->messageValidated) {
|
||||
// This message was validated externally
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->validator === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convert the node to a DOM node if it is an element from SimpleXML
|
||||
if ($node instanceof \SimpleXMLElement) {
|
||||
$node = dom_import_simplexml($node);
|
||||
}
|
||||
|
||||
assert($node instanceof DOMNode);
|
||||
|
||||
return $this->validator->isNodeValidated($node);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function runs an xPath query on this authentication response.
|
||||
*
|
||||
* @param string $query The query which should be run.
|
||||
* @param \DOMNode $node The node which this query is relative to. If this node is NULL (the default)
|
||||
* then the query will be relative to the root of the response.
|
||||
* @return \DOMNodeList
|
||||
*/
|
||||
private function doXPathQuery($query, $node = null)
|
||||
{
|
||||
assert(is_string($query));
|
||||
assert($this->dom instanceof DOMDocument);
|
||||
|
||||
if ($node === null) {
|
||||
$node = $this->dom->documentElement;
|
||||
}
|
||||
|
||||
assert($node instanceof DOMNode);
|
||||
|
||||
$xPath = new \DOMXpath($this->dom);
|
||||
$xPath->registerNamespace('shibp', self::SHIB_PROTOCOL_NS);
|
||||
$xPath->registerNamespace('shib', self::SHIB_ASSERT_NS);
|
||||
|
||||
return $xPath->query($query, $node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the session index of this response.
|
||||
*
|
||||
* @return string|null The session index of this response.
|
||||
*/
|
||||
public function getSessionIndex()
|
||||
{
|
||||
assert($this->dom instanceof DOMDocument);
|
||||
|
||||
$query = '/shibp:Response/shib:Assertion/shib:AuthnStatement';
|
||||
$nodelist = $this->doXPathQuery($query);
|
||||
if ($node = $nodelist->item(0)) {
|
||||
return $node->getAttribute('SessionIndex');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public function getAttributes()
|
||||
{
|
||||
$metadata = \SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
|
||||
$md = $metadata->getMetadata($this->getIssuer(), 'shib13-idp-remote');
|
||||
$base64 = isset($md['base64attributes']) ? $md['base64attributes'] : false;
|
||||
|
||||
if (! ($this->dom instanceof \DOMDocument)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$attributes = array();
|
||||
|
||||
$assertions = $this->doXPathQuery('/shibp:Response/shib:Assertion');
|
||||
|
||||
foreach ($assertions as $assertion) {
|
||||
if (!$this->isNodeValidated($assertion)) {
|
||||
throw new \Exception('Shib13 AuthnResponse contained an unsigned assertion.');
|
||||
}
|
||||
|
||||
$conditions = $this->doXPathQuery('shib:Conditions', $assertion);
|
||||
if ($conditions && $conditions->length > 0) {
|
||||
$condition = $conditions->item(0);
|
||||
|
||||
$start = $condition->getAttribute('NotBefore');
|
||||
$end = $condition->getAttribute('NotOnOrAfter');
|
||||
|
||||
if ($start && $end) {
|
||||
if (!self::checkDateConditions($start, $end)) {
|
||||
error_log('Date check failed ... (from ' . $start . ' to ' . $end . ')');
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$attribute_nodes = $this->doXPathQuery('shib:AttributeStatement/shib:Attribute/shib:AttributeValue', $assertion);
|
||||
/** @var \DOMElement $attribute */
|
||||
foreach ($attribute_nodes as $attribute) {
|
||||
$value = $attribute->textContent;
|
||||
$name = $attribute->parentNode->getAttribute('AttributeName');
|
||||
|
||||
if ($attribute->hasAttribute('Scope')) {
|
||||
$scopePart = '@' . $attribute->getAttribute('Scope');
|
||||
} else {
|
||||
$scopePart = '';
|
||||
}
|
||||
|
||||
if (!is_string($name)) {
|
||||
throw new \Exception('Shib13 Attribute node without an AttributeName.');
|
||||
}
|
||||
|
||||
if (!array_key_exists($name, $attributes)) {
|
||||
$attributes[$name] = array();
|
||||
}
|
||||
|
||||
if ($base64) {
|
||||
$encodedvalues = explode('_', $value);
|
||||
foreach ($encodedvalues as $v) {
|
||||
$attributes[$name][] = base64_decode($v) . $scopePart;
|
||||
}
|
||||
} else {
|
||||
$attributes[$name][] = $value . $scopePart;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
|
||||
public function getIssuer()
|
||||
{
|
||||
$query = '/shibp:Response/shib:Assertion/@Issuer';
|
||||
$nodelist = $this->doXPathQuery($query);
|
||||
|
||||
if ($attr = $nodelist->item(0)) {
|
||||
return $attr->value;
|
||||
} else {
|
||||
throw new \Exception('Could not find Issuer field in Authentication response');
|
||||
}
|
||||
}
|
||||
|
||||
public function getNameID()
|
||||
{
|
||||
$nameID = array();
|
||||
|
||||
$query = '/shibp:Response/shib:Assertion/shib:AuthenticationStatement/shib:Subject/shib:NameIdentifier';
|
||||
$nodelist = $this->doXPathQuery($query);
|
||||
|
||||
if ($node = $nodelist->item(0)) {
|
||||
$nameID["Value"] = $node->nodeValue;
|
||||
$nameID["Format"] = $node->getAttribute('Format');
|
||||
}
|
||||
|
||||
return $nameID;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build a authentication response.
|
||||
*
|
||||
* @param \SimpleSAML_Configuration $idp Metadata for the IdP the response is sent from.
|
||||
* @param \SimpleSAML_Configuration $sp Metadata for the SP the response is sent to.
|
||||
* @param string $shire The endpoint on the SP the response is sent to.
|
||||
* @param array|null $attributes The attributes which should be included in the response.
|
||||
* @return string The response.
|
||||
*/
|
||||
public function generate(\SimpleSAML_Configuration $idp, \SimpleSAML_Configuration $sp, $shire, $attributes)
|
||||
{
|
||||
assert(is_string($shire));
|
||||
assert($attributes === null || is_array($attributes));
|
||||
|
||||
if ($sp->hasValue('scopedattributes')) {
|
||||
$scopedAttributes = $sp->getArray('scopedattributes');
|
||||
} elseif ($idp->hasValue('scopedattributes')) {
|
||||
$scopedAttributes = $idp->getArray('scopedattributes');
|
||||
} else {
|
||||
$scopedAttributes = array();
|
||||
}
|
||||
|
||||
$id = Random::generateID();
|
||||
|
||||
$issueInstant = Time::generateTimestamp();
|
||||
|
||||
// 30 seconds timeskew back in time to allow differing clocks
|
||||
$notBefore = Time::generateTimestamp(time() - 30);
|
||||
|
||||
|
||||
$assertionExpire = Time::generateTimestamp(time() + 60 * 5);# 5 minutes
|
||||
$assertionid = Random::generateID();
|
||||
|
||||
$spEntityId = $sp->getString('entityid');
|
||||
|
||||
$audience = $sp->getString('audience', $spEntityId);
|
||||
$base64 = $sp->getBoolean('base64attributes', false);
|
||||
|
||||
$namequalifier = $sp->getString('NameQualifier', $spEntityId);
|
||||
$nameid = Random::generateID();
|
||||
$subjectNode =
|
||||
'<Subject>' .
|
||||
'<NameIdentifier' .
|
||||
' Format="urn:mace:shibboleth:1.0:nameIdentifier"' .
|
||||
' NameQualifier="' . htmlspecialchars($namequalifier) . '"' .
|
||||
'>' .
|
||||
htmlspecialchars($nameid) .
|
||||
'</NameIdentifier>' .
|
||||
'<SubjectConfirmation>' .
|
||||
'<ConfirmationMethod>' .
|
||||
'urn:oasis:names:tc:SAML:1.0:cm:bearer' .
|
||||
'</ConfirmationMethod>' .
|
||||
'</SubjectConfirmation>' .
|
||||
'</Subject>';
|
||||
|
||||
$encodedattributes = '';
|
||||
|
||||
if (is_array($attributes)) {
|
||||
$encodedattributes .= '<AttributeStatement>';
|
||||
$encodedattributes .= $subjectNode;
|
||||
|
||||
foreach ($attributes as $name => $value) {
|
||||
$encodedattributes .= $this->enc_attribute($name, $value, $base64, $scopedAttributes);
|
||||
}
|
||||
|
||||
$encodedattributes .= '</AttributeStatement>';
|
||||
}
|
||||
|
||||
/*
|
||||
* The SAML 1.1 response message
|
||||
*/
|
||||
$response = '<Response xmlns="urn:oasis:names:tc:SAML:1.0:protocol"
|
||||
xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion"
|
||||
xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" IssueInstant="' . $issueInstant. '"
|
||||
MajorVersion="1" MinorVersion="1"
|
||||
Recipient="' . htmlspecialchars($shire) . '" ResponseID="' . $id . '">
|
||||
<Status>
|
||||
<StatusCode Value="samlp:Success" />
|
||||
</Status>
|
||||
<Assertion xmlns="urn:oasis:names:tc:SAML:1.0:assertion"
|
||||
AssertionID="' . $assertionid . '" IssueInstant="' . $issueInstant. '"
|
||||
Issuer="' . htmlspecialchars($idp->getString('entityid')) . '" MajorVersion="1" MinorVersion="1">
|
||||
<Conditions NotBefore="' . $notBefore. '" NotOnOrAfter="'. $assertionExpire . '">
|
||||
<AudienceRestrictionCondition>
|
||||
<Audience>' . htmlspecialchars($audience) . '</Audience>
|
||||
</AudienceRestrictionCondition>
|
||||
</Conditions>
|
||||
<AuthenticationStatement AuthenticationInstant="' . $issueInstant. '"
|
||||
AuthenticationMethod="urn:oasis:names:tc:SAML:1.0:am:unspecified">' .
|
||||
$subjectNode . '
|
||||
</AuthenticationStatement>
|
||||
' . $encodedattributes . '
|
||||
</Assertion>
|
||||
</Response>';
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Format a shib13 attribute.
|
||||
*
|
||||
* @param string $name Name of the attribute.
|
||||
* @param array $values Values of the attribute (as an array of strings).
|
||||
* @param bool $base64 Whether the attriubte values should be base64-encoded.
|
||||
* @param array $scopedAttributes Array of attributes names which are scoped.
|
||||
* @return string The attribute encoded as an XML-string.
|
||||
*/
|
||||
private function enc_attribute($name, $values, $base64, $scopedAttributes)
|
||||
{
|
||||
assert(is_string($name));
|
||||
assert(is_array($values));
|
||||
assert(is_bool($base64));
|
||||
assert(is_array($scopedAttributes));
|
||||
|
||||
if (in_array($name, $scopedAttributes, true)) {
|
||||
$scoped = true;
|
||||
} else {
|
||||
$scoped = false;
|
||||
}
|
||||
|
||||
$attr = '<Attribute AttributeName="' . htmlspecialchars($name) . '" AttributeNamespace="urn:mace:shibboleth:1.0:attributeNamespace:uri">';
|
||||
foreach ($values as $value) {
|
||||
$scopePart = '';
|
||||
if ($scoped) {
|
||||
$tmp = explode('@', $value, 2);
|
||||
if (count($tmp) === 2) {
|
||||
$value = $tmp[0];
|
||||
$scopePart = ' Scope="' . htmlspecialchars($tmp[1]) . '"';
|
||||
}
|
||||
}
|
||||
|
||||
if ($base64) {
|
||||
$value = base64_encode($value);
|
||||
}
|
||||
|
||||
$attr .= '<AttributeValue' . $scopePart . '>' . htmlspecialchars($value) . '</AttributeValue>';
|
||||
}
|
||||
$attr .= '</Attribute>';
|
||||
|
||||
return $attr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we are currently between the given date & time conditions.
|
||||
*
|
||||
* Note that this function allows a 10-minute leap from the initial time as marked by $start.
|
||||
*
|
||||
* @param string|null $start A SAML2 timestamp marking the start of the period to check. Defaults to null, in which
|
||||
* case there's no limitations in the past.
|
||||
* @param string|null $end A SAML2 timestamp marking the end of the period to check. Defaults to null, in which
|
||||
* case there's no limitations in the future.
|
||||
*
|
||||
* @return bool True if the current time belongs to the period specified by $start and $end. False otherwise.
|
||||
*
|
||||
* @see \SAML2\Utils::xsDateTimeToTimestamp.
|
||||
*
|
||||
* @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no>
|
||||
* @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
|
||||
*/
|
||||
protected static function checkDateConditions($start = null, $end = null)
|
||||
{
|
||||
$currentTime = time();
|
||||
|
||||
if (!empty($start)) {
|
||||
$startTime = Utils::xsDateTimeToTimestamp($start);
|
||||
// allow for a 10 minute difference in time
|
||||
if (($startTime < 0) || (($startTime - 600) > $currentTime)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!empty($end)) {
|
||||
$endTime = Utils::xsDateTimeToTimestamp($end);
|
||||
if (($endTime < 0) || ($endTime <= $currentTime)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
313
lib/SimpleSAML/XML/Signer.php
Executable file
313
lib/SimpleSAML/XML/Signer.php
Executable file
@@ -0,0 +1,313 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* A helper class for signing XML.
|
||||
*
|
||||
* This is a helper class for signing XML documents.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS.
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
namespace SimpleSAML\XML;
|
||||
|
||||
use DOMComment;
|
||||
use DOMElement;
|
||||
use DOMText;
|
||||
use RobRichards\XMLSecLibs\XMLSecurityDSig;
|
||||
use RobRichards\XMLSecLibs\XMLSecurityKey;
|
||||
use SimpleSAML\Utils\Config;
|
||||
|
||||
class Signer
|
||||
{
|
||||
/**
|
||||
* @var string The name of the ID attribute.
|
||||
*/
|
||||
private $idAttrName = '';
|
||||
|
||||
/**
|
||||
* @var XMLSecurityKey|bool The private key (as an XMLSecurityKey).
|
||||
*/
|
||||
private $privateKey = false;
|
||||
|
||||
/**
|
||||
* @var string The certificate (as text).
|
||||
*/
|
||||
private $certificate = '';
|
||||
|
||||
|
||||
/**
|
||||
* @var array Extra certificates which should be included in the response.
|
||||
*/
|
||||
private $extraCertificates = array();
|
||||
|
||||
|
||||
/**
|
||||
* Constructor for the metadata signer.
|
||||
*
|
||||
* You can pass an list of options as key-value pairs in the array. This allows you to initialize
|
||||
* a metadata signer in one call.
|
||||
*
|
||||
* The following keys are recognized:
|
||||
* - privatekey The file with the private key, relative to the cert-directory.
|
||||
* - privatekey_pass The passphrase for the private key.
|
||||
* - certificate The file with the certificate, relative to the cert-directory.
|
||||
* - privatekey_array The private key, as an array returned from SimpleSAML_Utilities::loadPrivateKey.
|
||||
* - publickey_array The public key, as an array returned from SimpleSAML_Utilities::loadPublicKey.
|
||||
* - id The name of the ID attribute.
|
||||
*
|
||||
* @param array $options Associative array with options for the constructor. Defaults to an empty array.
|
||||
*/
|
||||
public function __construct($options = array())
|
||||
{
|
||||
assert(is_array($options));
|
||||
|
||||
if (array_key_exists('privatekey', $options)) {
|
||||
$pass = null;
|
||||
if (array_key_exists('privatekey_pass', $options)) {
|
||||
$pass = $options['privatekey_pass'];
|
||||
}
|
||||
|
||||
$this->loadPrivateKey($options['privatekey'], $pass);
|
||||
}
|
||||
|
||||
if (array_key_exists('certificate', $options)) {
|
||||
$this->loadCertificate($options['certificate']);
|
||||
}
|
||||
|
||||
if (array_key_exists('privatekey_array', $options)) {
|
||||
$this->loadPrivateKeyArray($options['privatekey_array']);
|
||||
}
|
||||
|
||||
if (array_key_exists('publickey_array', $options)) {
|
||||
$this->loadPublicKeyArray($options['publickey_array']);
|
||||
}
|
||||
|
||||
if (array_key_exists('id', $options)) {
|
||||
$this->setIdAttribute($options['id']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the private key from an array.
|
||||
*
|
||||
* This function loads the private key from an array matching what is returned
|
||||
* by SimpleSAML_Utilities::loadPrivateKey(...).
|
||||
*
|
||||
* @param array $privatekey The private key.
|
||||
*/
|
||||
public function loadPrivateKeyArray($privatekey)
|
||||
{
|
||||
assert(is_array($privatekey));
|
||||
assert(array_key_exists('PEM', $privatekey));
|
||||
|
||||
$this->privateKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, array('type' => 'private'));
|
||||
if (array_key_exists('password', $privatekey)) {
|
||||
$this->privateKey->passphrase = $privatekey['password'];
|
||||
}
|
||||
$this->privateKey->loadKey($privatekey['PEM'], false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the private key.
|
||||
*
|
||||
* Will throw an exception if unable to load the private key.
|
||||
*
|
||||
* @param string $file The file which contains the private key. The path is assumed to be relative
|
||||
* to the cert-directory.
|
||||
* @param string|null $pass The passphrase on the private key. Pass no value or NULL if the private
|
||||
* key is unencrypted.
|
||||
* @param bool $full_path Whether the filename found in the configuration contains the
|
||||
* full path to the private key or not. Default to false.
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function loadPrivateKey($file, $pass = null, $full_path = false)
|
||||
{
|
||||
assert(is_string($file));
|
||||
assert(is_string($pass) || $pass === null);
|
||||
assert(is_bool($full_path));
|
||||
|
||||
if (!$full_path) {
|
||||
$keyFile = Config::getCertPath($file);
|
||||
} else {
|
||||
$keyFile = $file;
|
||||
}
|
||||
|
||||
if (!file_exists($keyFile)) {
|
||||
throw new \Exception('Could not find private key file "' . $keyFile . '".');
|
||||
}
|
||||
$keyData = file_get_contents($keyFile);
|
||||
if ($keyData === false) {
|
||||
throw new \Exception('Unable to read private key file "' . $keyFile . '".');
|
||||
}
|
||||
|
||||
$privatekey = array('PEM' => $keyData);
|
||||
if ($pass !== null) {
|
||||
$privatekey['password'] = $pass;
|
||||
}
|
||||
$this->loadPrivateKeyArray($privatekey);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the public key / certificate we should include in the signature.
|
||||
*
|
||||
* This function loads the public key from an array matching what is returned
|
||||
* by SimpleSAML_Utilities::loadPublicKey(...).
|
||||
*
|
||||
* @param array $publickey The public key.
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function loadPublicKeyArray($publickey)
|
||||
{
|
||||
assert(is_array($publickey));
|
||||
|
||||
if (!array_key_exists('PEM', $publickey)) {
|
||||
// We have a public key with only a fingerprint
|
||||
throw new \Exception('Tried to add a certificate fingerprint in a signature.');
|
||||
}
|
||||
|
||||
// For now, we only assume that the public key is an X509 certificate
|
||||
$this->certificate = $publickey['PEM'];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the certificate we should include in the signature.
|
||||
*
|
||||
* If this function isn't called, no certificate will be included.
|
||||
* Will throw an exception if unable to load the certificate.
|
||||
*
|
||||
* @param string $file The file which contains the certificate. The path is assumed to be relative to
|
||||
* the cert-directory.
|
||||
* @param bool $full_path Whether the filename found in the configuration contains the
|
||||
* full path to the private key or not. Default to false.
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function loadCertificate($file, $full_path = false)
|
||||
{
|
||||
assert(is_string($file));
|
||||
assert(is_bool($full_path));
|
||||
|
||||
if (!$full_path) {
|
||||
$certFile = Config::getCertPath($file);
|
||||
} else {
|
||||
$certFile = $file;
|
||||
}
|
||||
|
||||
if (!file_exists($certFile)) {
|
||||
throw new \Exception('Could not find certificate file "' . $certFile . '".');
|
||||
}
|
||||
|
||||
$cert = file_get_contents($certFile);
|
||||
if ($cert === false) {
|
||||
throw new \Exception('Unable to read certificate file "' . $certFile . '".');
|
||||
}
|
||||
$this->certificate = $cert;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the attribute name for the ID value.
|
||||
*
|
||||
* @param string $idAttrName The name of the attribute which contains the id.
|
||||
*/
|
||||
public function setIDAttribute($idAttrName)
|
||||
{
|
||||
assert(is_string($idAttrName));
|
||||
|
||||
$this->idAttrName = $idAttrName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add an extra certificate to the certificate chain in the signature.
|
||||
*
|
||||
* Extra certificates will be added to the certificate chain in the order they
|
||||
* are added.
|
||||
*
|
||||
* @param string $file The file which contains the certificate, relative to the cert-directory.
|
||||
* @param bool $full_path Whether the filename found in the configuration contains the
|
||||
* full path to the private key or not. Default to false.
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function addCertificate($file, $full_path = false)
|
||||
{
|
||||
assert(is_string($file));
|
||||
assert(is_bool($full_path));
|
||||
|
||||
if (!$full_path) {
|
||||
$certFile = Config::getCertPath($file);
|
||||
} else {
|
||||
$certFile = $file;
|
||||
}
|
||||
|
||||
if (!file_exists($certFile)) {
|
||||
throw new \Exception('Could not find extra certificate file "' . $certFile . '".');
|
||||
}
|
||||
|
||||
$certificate = file_get_contents($certFile);
|
||||
if ($certificate === false) {
|
||||
throw new \Exception('Unable to read extra certificate file "' . $certFile . '".');
|
||||
}
|
||||
|
||||
$this->extraCertificates[] = $certificate;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Signs the given DOMElement and inserts the signature at the given position.
|
||||
*
|
||||
* The private key must be set before calling this function.
|
||||
*
|
||||
* @param \DOMElement $node The DOMElement we should generate a signature for.
|
||||
* @param \DOMElement $insertInto The DOMElement we should insert the signature element into.
|
||||
* @param \DOMElement $insertBefore The element we should insert the signature element before. Defaults to NULL,
|
||||
* in which case the signature will be appended to the element spesified in
|
||||
* $insertInto.
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function sign($node, $insertInto, $insertBefore = null)
|
||||
{
|
||||
assert($node instanceof DOMElement);
|
||||
assert($insertInto instanceof DOMElement);
|
||||
assert($insertBefore === null || $insertBefore instanceof DOMElement ||
|
||||
$insertBefore instanceof DOMComment || $insertBefore instanceof DOMText);
|
||||
|
||||
if ($this->privateKey === false) {
|
||||
throw new \Exception('Private key not set.');
|
||||
}
|
||||
|
||||
|
||||
$objXMLSecDSig = new XMLSecurityDSig();
|
||||
$objXMLSecDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N);
|
||||
|
||||
$options = array();
|
||||
if (!empty($this->idAttrName)) {
|
||||
$options['id_name'] = $this->idAttrName;
|
||||
}
|
||||
|
||||
$objXMLSecDSig->addReferenceList(
|
||||
array($node),
|
||||
XMLSecurityDSig::SHA256,
|
||||
array('http://www.w3.org/2000/09/xmldsig#enveloped-signature', XMLSecurityDSig::EXC_C14N),
|
||||
$options
|
||||
);
|
||||
|
||||
/** @var \RobRichards\XMLSecLibs\XMLSecurityKey $this->privateKey */
|
||||
$objXMLSecDSig->sign($this->privateKey);
|
||||
|
||||
|
||||
// Add the certificate to the signature
|
||||
$objXMLSecDSig->add509Cert($this->certificate, true);
|
||||
|
||||
// Add extra certificates
|
||||
foreach ($this->extraCertificates as $certificate) {
|
||||
$objXMLSecDSig->add509Cert($certificate, true);
|
||||
}
|
||||
|
||||
$objXMLSecDSig->insertSignature($insertInto, $insertBefore);
|
||||
}
|
||||
}
|
||||
445
lib/SimpleSAML/XML/Validator.php
Executable file
445
lib/SimpleSAML/XML/Validator.php
Executable file
@@ -0,0 +1,445 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This class implements helper functions for XML validation.
|
||||
*
|
||||
* @author Olav Morken, UNINETT AS.
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
namespace SimpleSAML\XML;
|
||||
|
||||
use RobRichards\XMLSecLibs\XMLSecEnc;
|
||||
use RobRichards\XMLSecLibs\XMLSecurityDSig;
|
||||
use SimpleSAML\Logger;
|
||||
|
||||
class Validator
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string This variable contains the X509 certificate the XML document
|
||||
* was signed with, or NULL if it wasn't signed with an X509 certificate.
|
||||
*/
|
||||
private $x509Certificate;
|
||||
|
||||
/**
|
||||
* @var array|null This variable contains the nodes which are signed.
|
||||
*/
|
||||
private $validNodes = null;
|
||||
|
||||
|
||||
/**
|
||||
* This function initializes the validator.
|
||||
*
|
||||
* This function accepts an optional parameter $publickey, which is the public key
|
||||
* or certificate which should be used to validate the signature. This parameter can
|
||||
* take the following values:
|
||||
* - NULL/FALSE: No validation will be performed. This is the default.
|
||||
* - A string: Assumed to be a PEM-encoded certificate / public key.
|
||||
* - An array: Assumed to be an array returned by SimpleSAML_Utilities::loadPublicKey.
|
||||
*
|
||||
* @param \DOMNode $xmlNode The XML node which contains the Signature element.
|
||||
* @param string|array $idAttribute The ID attribute which is used in node references. If
|
||||
* this attribute is NULL (the default), then we will use whatever is the default
|
||||
* ID. Can be eigther a string with one value, or an array with multiple ID
|
||||
* attrbute names.
|
||||
* @param array|bool $publickey The public key / certificate which should be used to validate the XML node.
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct($xmlNode, $idAttribute = null, $publickey = false)
|
||||
{
|
||||
assert($xmlNode instanceof \DOMNode);
|
||||
|
||||
if ($publickey === null) {
|
||||
$publickey = false;
|
||||
} elseif (is_string($publickey)) {
|
||||
$publickey = array(
|
||||
'PEM' => $publickey,
|
||||
);
|
||||
} else {
|
||||
assert($publickey === false || is_array($publickey));
|
||||
}
|
||||
|
||||
// Create an XML security object
|
||||
$objXMLSecDSig = new XMLSecurityDSig();
|
||||
|
||||
// Add the id attribute if the user passed in an id attribute
|
||||
if ($idAttribute !== null) {
|
||||
if (is_string($idAttribute)) {
|
||||
$objXMLSecDSig->idKeys[] = $idAttribute;
|
||||
} elseif (is_array($idAttribute)) {
|
||||
foreach ($idAttribute as $ida) {
|
||||
$objXMLSecDSig->idKeys[] = $ida;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Locate the XMLDSig Signature element to be used
|
||||
$signatureElement = $objXMLSecDSig->locateSignature($xmlNode);
|
||||
if (!$signatureElement) {
|
||||
throw new \Exception('Could not locate XML Signature element.');
|
||||
}
|
||||
|
||||
// Canonicalize the XMLDSig SignedInfo element in the message
|
||||
$objXMLSecDSig->canonicalizeSignedInfo();
|
||||
|
||||
// Validate referenced xml nodes
|
||||
if (!$objXMLSecDSig->validateReference()) {
|
||||
throw new \Exception('XMLsec: digest validation failed');
|
||||
}
|
||||
|
||||
|
||||
// Find the key used to sign the document
|
||||
$objKey = $objXMLSecDSig->locateKey();
|
||||
if (empty($objKey)) {
|
||||
throw new \Exception('Error loading key to handle XML signature');
|
||||
}
|
||||
|
||||
// Load the key data
|
||||
if ($publickey !== false && array_key_exists('PEM', $publickey)) {
|
||||
// We have PEM data for the public key / certificate
|
||||
$objKey->loadKey($publickey['PEM']);
|
||||
} else {
|
||||
// No PEM data. Search for key in signature
|
||||
|
||||
if (!XMLSecEnc::staticLocateKeyInfo($objKey, $signatureElement)) {
|
||||
throw new \Exception('Error finding key data for XML signature validation.');
|
||||
}
|
||||
|
||||
if ($publickey !== false) {
|
||||
/* $publickey is set, and should therefore contain one or more fingerprints.
|
||||
* Check that the response contains a certificate with a matching
|
||||
* fingerprint.
|
||||
*/
|
||||
assert(is_array($publickey['certFingerprint']));
|
||||
|
||||
$certificate = $objKey->getX509Certificate();
|
||||
if ($certificate === null) {
|
||||
// Wasn't signed with an X509 certificate
|
||||
throw new \Exception('Message wasn\'t signed with an X509 certificate,' .
|
||||
' and no public key was provided in the metadata.');
|
||||
}
|
||||
|
||||
self::validateCertificateFingerprint($certificate, $publickey['certFingerprint']);
|
||||
// Key OK
|
||||
}
|
||||
}
|
||||
|
||||
// Check the signature
|
||||
if ($objXMLSecDSig->verify($objKey) !== 1) {
|
||||
throw new \Exception("Unable to validate Signature");
|
||||
}
|
||||
|
||||
// Extract the certificate
|
||||
$this->x509Certificate = $objKey->getX509Certificate();
|
||||
|
||||
// Find the list of validated nodes
|
||||
$this->validNodes = $objXMLSecDSig->getValidatedNodes();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the X509 certificate which was used to sign the XML.
|
||||
*
|
||||
* This function will return the certificate as a PEM-encoded string. If the XML
|
||||
* wasn't signed by an X509 certificate, NULL will be returned.
|
||||
*
|
||||
* @return string The certificate as a PEM-encoded string, or NULL if not signed with an X509 certificate.
|
||||
*/
|
||||
public function getX509Certificate()
|
||||
{
|
||||
return $this->x509Certificate;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the fingerprint of an X509 certificate.
|
||||
*
|
||||
* @param string $x509cert The certificate as a base64-encoded string. The string may optionally
|
||||
* be framed with '-----BEGIN CERTIFICATE-----' and '-----END CERTIFICATE-----'.
|
||||
* @return string The fingerprint as a 40-character lowercase hexadecimal number. NULL is returned if the
|
||||
* argument isn't an X509 certificate.
|
||||
*/
|
||||
private static function calculateX509Fingerprint($x509cert)
|
||||
{
|
||||
assert(is_string($x509cert));
|
||||
|
||||
$lines = explode("\n", $x509cert);
|
||||
|
||||
$data = '';
|
||||
|
||||
foreach ($lines as $line) {
|
||||
// Remove '\r' from end of line if present
|
||||
$line = rtrim($line);
|
||||
if ($line === '-----BEGIN CERTIFICATE-----') {
|
||||
// Delete junk from before the certificate
|
||||
$data = '';
|
||||
} elseif ($line === '-----END CERTIFICATE-----') {
|
||||
// Ignore data after the certificate
|
||||
break;
|
||||
} elseif ($line === '-----BEGIN PUBLIC KEY-----') {
|
||||
// This isn't an X509 certificate
|
||||
return null;
|
||||
} else {
|
||||
// Append the current line to the certificate data
|
||||
$data .= $line;
|
||||
}
|
||||
}
|
||||
|
||||
/* $data now contains the certificate as a base64-encoded string. The fingerprint
|
||||
* of the certificate is the sha1-hash of the certificate.
|
||||
*/
|
||||
return strtolower(sha1(base64_decode($data)));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper function for validating the fingerprint.
|
||||
*
|
||||
* Checks the fingerprint of a certificate against an array of valid fingerprints.
|
||||
* Will throw an exception if none of the fingerprints matches.
|
||||
*
|
||||
* @param string $certificate The X509 certificate we should validate.
|
||||
* @param array $fingerprints The valid fingerprints.
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function validateCertificateFingerprint($certificate, $fingerprints)
|
||||
{
|
||||
assert(is_string($certificate));
|
||||
assert(is_array($fingerprints));
|
||||
|
||||
$certFingerprint = self::calculateX509Fingerprint($certificate);
|
||||
if ($certFingerprint === null) {
|
||||
// Couldn't calculate fingerprint from X509 certificate. Should not happen.
|
||||
throw new \Exception('Unable to calculate fingerprint from X509' .
|
||||
' certificate. Maybe it isn\'t an X509 certificate?');
|
||||
}
|
||||
|
||||
foreach ($fingerprints as $fp) {
|
||||
assert(is_string($fp));
|
||||
|
||||
if ($fp === $certFingerprint) {
|
||||
// The fingerprints matched
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// None of the fingerprints matched. Throw an exception describing the error.
|
||||
throw new \Exception('Invalid fingerprint of certificate. Expected one of [' .
|
||||
implode('], [', $fingerprints) . '], but got [' . $certFingerprint . ']');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate the fingerprint of the certificate which was used to sign this document.
|
||||
*
|
||||
* This function accepts either a string, or an array of strings as a parameter. If this
|
||||
* is an array, then any string (certificate) in the array can match. If this is a string,
|
||||
* then that string must match,
|
||||
*
|
||||
* @param string|array $fingerprints The fingerprints which should match. This can be a single string,
|
||||
* or an array of fingerprints.
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function validateFingerprint($fingerprints)
|
||||
{
|
||||
assert(is_string($fingerprints) || is_array($fingerprints));
|
||||
|
||||
if ($this->x509Certificate === null) {
|
||||
throw new \Exception('Key used to sign the message was not an X509 certificate.');
|
||||
}
|
||||
|
||||
if (!is_array($fingerprints)) {
|
||||
$fingerprints = array($fingerprints);
|
||||
}
|
||||
|
||||
// Normalize the fingerprints
|
||||
foreach ($fingerprints as &$fp) {
|
||||
assert(is_string($fp));
|
||||
|
||||
// Make sure that the fingerprint is in the correct format
|
||||
$fp = strtolower(str_replace(":", "", $fp));
|
||||
}
|
||||
|
||||
self::validateCertificateFingerprint($this->x509Certificate, $fingerprints);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function checks if the given XML node was signed.
|
||||
*
|
||||
* @param \DOMNode $node The XML node which we should verify that was signed.
|
||||
*
|
||||
* @return bool TRUE if this node (or a parent node) was signed. FALSE if not.
|
||||
*/
|
||||
public function isNodeValidated($node)
|
||||
{
|
||||
assert($node instanceof \DOMNode);
|
||||
|
||||
while ($node !== null) {
|
||||
if (in_array($node, $this->validNodes, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$node = $node->parentNode;
|
||||
}
|
||||
|
||||
/* Neither this node nor any of the parent nodes could be found in the list of
|
||||
* signed nodes.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate the certificate used to sign the XML against a CA file.
|
||||
*
|
||||
* This function throws an exception if unable to validate against the given CA file.
|
||||
*
|
||||
* @param string $caFile File with trusted certificates, in PEM-format.
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function validateCA($caFile)
|
||||
{
|
||||
assert(is_string($caFile));
|
||||
|
||||
if ($this->x509Certificate === null) {
|
||||
throw new \Exception('Key used to sign the message was not an X509 certificate.');
|
||||
}
|
||||
|
||||
self::validateCertificate($this->x509Certificate, $caFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a certificate against a CA file, by using the builtin
|
||||
* openssl_x509_checkpurpose function
|
||||
*
|
||||
* @param string $certificate The certificate, in PEM format.
|
||||
* @param string $caFile File with trusted certificates, in PEM-format.
|
||||
* @return boolean|string TRUE on success, or a string with error messages if it failed.
|
||||
* @deprecated
|
||||
*/
|
||||
private static function validateCABuiltIn($certificate, $caFile)
|
||||
{
|
||||
assert(is_string($certificate));
|
||||
assert(is_string($caFile));
|
||||
|
||||
// Clear openssl errors
|
||||
while (openssl_error_string() !== false) {
|
||||
}
|
||||
|
||||
$res = openssl_x509_checkpurpose($certificate, X509_PURPOSE_ANY, array($caFile));
|
||||
|
||||
$errors = '';
|
||||
// Log errors
|
||||
while (($error = openssl_error_string()) !== false) {
|
||||
$errors .= ' [' . $error . ']';
|
||||
}
|
||||
|
||||
if ($res !== true) {
|
||||
return $errors;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate the certificate used to sign the XML against a CA file, by using the "openssl verify" command.
|
||||
*
|
||||
* This function uses the openssl verify command to verify a certificate, to work around limitations
|
||||
* on the openssl_x509_checkpurpose function. That function will not work on certificates without a purpose
|
||||
* set.
|
||||
*
|
||||
* @param string $certificate The certificate, in PEM format.
|
||||
* @param string $caFile File with trusted certificates, in PEM-format.
|
||||
* @return bool|string TRUE on success, a string with error messages on failure.
|
||||
* @throws \Exception
|
||||
* @deprecated
|
||||
*/
|
||||
private static function validateCAExec($certificate, $caFile)
|
||||
{
|
||||
assert(is_string($certificate));
|
||||
assert(is_string($caFile));
|
||||
|
||||
$command = array(
|
||||
'openssl', 'verify',
|
||||
'-CAfile', $caFile,
|
||||
'-purpose', 'any',
|
||||
);
|
||||
|
||||
$cmdline = '';
|
||||
foreach ($command as $c) {
|
||||
$cmdline .= escapeshellarg($c) . ' ';
|
||||
}
|
||||
|
||||
$cmdline .= '2>&1';
|
||||
$descSpec = array(
|
||||
0 => array('pipe', 'r'),
|
||||
1 => array('pipe', 'w'),
|
||||
);
|
||||
$process = proc_open($cmdline, $descSpec, $pipes);
|
||||
if (!is_resource($process)) {
|
||||
throw new \Exception('Failed to execute verification command: ' . $cmdline);
|
||||
}
|
||||
|
||||
if (fwrite($pipes[0], $certificate) === false) {
|
||||
throw new \Exception('Failed to write certificate for verification.');
|
||||
}
|
||||
fclose($pipes[0]);
|
||||
|
||||
$out = '';
|
||||
while (!feof($pipes[1])) {
|
||||
$line = trim(fgets($pipes[1]));
|
||||
if (strlen($line) > 0) {
|
||||
$out .= ' [' . $line . ']';
|
||||
}
|
||||
}
|
||||
fclose($pipes[1]);
|
||||
|
||||
$status = proc_close($process);
|
||||
if ($status !== 0 || $out !== ' [stdin: OK]') {
|
||||
return $out;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate the certificate used to sign the XML against a CA file.
|
||||
*
|
||||
* This function throws an exception if unable to validate against the given CA file.
|
||||
*
|
||||
* @param string $certificate The certificate, in PEM format.
|
||||
* @param string $caFile File with trusted certificates, in PEM-format.
|
||||
* @throws \Exception
|
||||
* @deprecated
|
||||
*/
|
||||
public static function validateCertificate($certificate, $caFile)
|
||||
{
|
||||
assert(is_string($certificate));
|
||||
assert(is_string($caFile));
|
||||
|
||||
if (!file_exists($caFile)) {
|
||||
throw new \Exception('Could not load CA file: ' . $caFile);
|
||||
}
|
||||
|
||||
Logger::debug('Validating certificate against CA file: ' . var_export($caFile, true));
|
||||
|
||||
$resBuiltin = self::validateCABuiltIn($certificate, $caFile);
|
||||
if ($resBuiltin !== true) {
|
||||
Logger::debug('Failed to validate with internal function: ' . var_export($resBuiltin, true));
|
||||
|
||||
$resExternal = self::validateCAExec($certificate, $caFile);
|
||||
if ($resExternal !== true) {
|
||||
Logger::debug('Failed to validate with external function: ' . var_export($resExternal, true));
|
||||
throw new \Exception('Could not verify certificate against CA file "'
|
||||
. $caFile . '". Internal result:' . $resBuiltin .
|
||||
' External result:' . $resExternal);
|
||||
}
|
||||
}
|
||||
|
||||
Logger::debug('Successfully validated certificate.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user