runff 1.0 commit
This commit is contained in:
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user