/** * 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. * * @author Olav Morken, UNINETT AS <*****@*****.**> * @author Jaime Perez, UNINETT AS <*****@*****.**> */ public static function checkSAMLMessage($message, $type) { $allowed_types = array('saml20', 'saml11', 'saml-meta'); if (!(is_string($message) && in_array($type, $allowed_types))) { 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) || array_key_exists('validatexml', $debug) && $debug['validatexml'] === true || $enabled)) { // 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); } }
/** * Autoload function for SimpleSAMLphp modules following PSR-0. * * @param string $className Name of the class. * @deprecated This method will be removed in SSP 2.0. * * TODO: this autoloader should be removed once everything has been migrated to namespaces. */ public static function autoloadPSR0($className) { $modulePrefixLength = strlen('sspmod_'); $classPrefix = substr($className, 0, $modulePrefixLength); if ($classPrefix !== 'sspmod_') { return; } $modNameEnd = strpos($className, '_', $modulePrefixLength); $module = substr($className, $modulePrefixLength, $modNameEnd - $modulePrefixLength); $path = explode('_', substr($className, $modNameEnd + 1)); if (!self::isModuleEnabled($module)) { return; } $file = self::getModuleDir($module) . '/lib/' . join('/', $path) . '.php'; if (!file_exists($file)) { return; } require_once $file; if (!class_exists($className, false) && !interface_exists($className, false)) { // the file exists, but the class is not defined. Is it using namespaces? $nspath = join('\\', $path); if (class_exists('SimpleSAML\\Module\\' . $module . '\\' . $nspath) || interface_exists('SimpleSAML\\Module\\' . $module . '\\' . $nspath)) { // the class has been migrated, create an alias and warn about it \SimpleSAML\Logger::warning("The class or interface '{$className}' is now using namespaces, please use 'SimpleSAML\\Module\\" . $module . "\\" . $nspath . "' instead."); class_alias("SimpleSAML\\Module\\{$module}\\{$nspath}", $className); } } }
/** * This method applies the filter, removing any values * * @param array &$request the current request */ public function process(&$request) { $src = $request['Source']; if (!count($this->scopedAttributes)) { // paranoia, should never happen Logger::warning('No scoped attributes configured.'); return; } $validScopes = array(); if (array_key_exists('scope', $src) && is_array($src['scope']) && !empty($src['scope'])) { $validScopes = $src['scope']; } foreach ($this->scopedAttributes as $attribute) { if (!isset($request['Attributes'][$attribute])) { continue; } $values = $request['Attributes'][$attribute]; $newValues = array(); foreach ($values as $value) { $ep = \SimpleSAML\Utils\Config\Metadata::getDefaultEndpoint($request['Source']['SingleSignOnService']); $loc = $ep['Location']; $host = parse_url($loc, PHP_URL_HOST); if ($host === null) { $host = ''; } $value_a = explode('@', $value, 2); if (count($value_a) < 2) { $newValues[] = $value; continue; // there's no scope } $scope = $value_a[1]; if (in_array($scope, $validScopes, true)) { $newValues[] = $value; } elseif (strpos($host, $scope) === strlen($host) - strlen($scope)) { $newValues[] = $value; } else { Logger::warning("Removing value '{$value}' for attribute '{$attribute}'. Undeclared scope."); } } if (empty($newValues)) { Logger::warning("No suitable values for attribute '{$attribute}', removing it."); unset($request['Attributes'][$attribute]); // remove empty attributes } else { $request['Attributes'][$attribute] = $newValues; } } }
/** * Initialize the filter. * * @param array $config Configuration information about this filter. * @param mixed $reserved For future use. * @throws \SimpleSAML_Error_Exception If the configuration is not valid. */ public function __construct($config, $reserved) { parent::__construct($config, $reserved); assert('is_array($config)'); // parse configuration foreach ($config as $name => $value) { if (is_int($name)) { // check if this is an option if ($value === '%replace') { $this->replace = true; } elseif ($value === '%keep') { $this->keep = true; } else { // unknown configuration option, log it and ignore the error \SimpleSAML\Logger::warning("AttributeValueMap: unknown configuration flag '" . var_export($value, true) . "'"); } continue; } // set the target attribute if ($name === 'targetattribute') { $this->targetattribute = $value; } // set the source attribute if ($name === 'sourceattribute') { $this->sourceattribute = $value; } // set the values if ($name === 'values') { $this->values = $value; } } // now validate it if (!is_string($this->sourceattribute)) { throw new \SimpleSAML_Error_Exception("AttributeValueMap: 'sourceattribute' configuration option not set."); } if (!is_string($this->targetattribute)) { throw new \SimpleSAML_Error_Exception("AttributeValueMap: 'targetattribute' configuration option not set."); } if (!is_array($this->values)) { throw new \SimpleSAML_Error_Exception("AttributeValueMap: 'values' configuration option is not an array."); } }
/** * 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) || is_null($relayState)'); 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; } self::logoutNextSP($state); }
/** * 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(), $fallbackdefault = true, $oldreplacements = array(), $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; }
/** * Set a cookie. * * @param string $name The name of the cookie. * @param string|NULL $value The value of the cookie. Set to NULL to delete the cookie. * @param array|NULL $params Cookie parameters. * @param bool $throw Whether to throw exception if setcookie() fails. * * @throws \InvalidArgumentException If any parameter has an incorrect type. * @throws \SimpleSAML_Error_Exception If the headers were already sent and the cookie cannot be set. * * @author Andjelko Horvat * @author Jaime Perez, UNINETT AS <*****@*****.**> */ public static function setCookie($name, $value, $params = null, $throw = true) { if (!(is_string($name) && (is_string($value) || is_null($value)) && (is_array($params) || is_null($params)) && is_bool($throw))) { throw new \InvalidArgumentException('Invalid input parameters.'); } $default_params = array('lifetime' => 0, 'expire' => null, 'path' => '/', 'domain' => null, 'secure' => false, 'httponly' => true, 'raw' => false); if ($params !== null) { $params = array_merge($default_params, $params); } else { $params = $default_params; } // Do not set secure cookie if not on HTTPS if ($params['secure'] && !self::isHTTPS()) { Logger::warning('Setting secure cookie on plain HTTP is not allowed.'); return; } if ($value === null) { $expire = time() - 365 * 24 * 60 * 60; } elseif (isset($params['expire'])) { $expire = $params['expire']; } elseif ($params['lifetime'] === 0) { $expire = 0; } else { $expire = time() + $params['lifetime']; } if ($params['raw']) { $success = setrawcookie($name, $value, $expire, $params['path'], $params['domain'], $params['secure'], $params['httponly']); } else { $success = setcookie($name, $value, $expire, $params['path'], $params['domain'], $params['secure'], $params['httponly']); } if (!$success) { if ($throw) { throw new \SimpleSAML_Error_Exception('Error setting cookie: headers already sent.'); } else { Logger::warning('Error setting cookie: headers already sent.'); } } }
/** * Logs with an arbitrary level. * * @param mixed $level * @param string $message * @param array $context * @return null */ public function log($level, $message, array $context = array()) { switch ($level) { case \SimpleSAML\Logger::ALERT: \SimpleSAML\Logger::alert($message); break; case \SimpleSAML\Logger::CRIT: \SimpleSAML\Logger::critical($message); break; case \SimpleSAML\Logger::DEBUG: \SimpleSAML\Logger::debug($message); break; case \SimpleSAML\Logger::EMERG: \SimpleSAML\Logger::emergency($message); break; case \SimpleSAML\Logger::ERR: \SimpleSAML\Logger::error($message); break; case \SimpleSAML\Logger::INFO: \SimpleSAML\Logger::info($message); break; case \SimpleSAML\Logger::NOTICE: \SimpleSAML\Logger::notice($message); break; case \SimpleSAML\Logger::WARNING: \SimpleSAML\Logger::warning($message); } }
/** * 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 downloading metadata, validating the signature or writing to cache. */ 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 $data = $this->getFromCache($set, $index); 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) { Logger::warning('Fetching metadata for ' . $index . ': ' . $e->getMessage()); } if (empty($xmldata)) { $error = error_get_last(); throw new \Exception('Error downloading metadata for "' . $index . '" from "' . $mdq_url . '": ' . $error['message']); } /** @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 . '".'); } $this->writeToCache($set, $index, $data); return $data; }