/** * Returns a list of entities with metadata */ public function getSources() { $sourcesDef = $this->aConfig->getArray('sources'); try { $sources = SimpleSAML_Metadata_MetaDataStorageSource::parseSources($sourcesDef); } catch (Exception $e) { throw new Exception('Invalid aggregator source configuration for aggregator ' . var_export($id, TRUE) . ': ' . $e->getMessage()); } /* Find list of all available entities. */ $entities = array(); foreach ($sources as $source) { foreach ($this->sets as $set) { foreach ($source->getMetadataSet($set) as $entityId => $metadata) { $metadata['entityid'] = $entityId; $metadata['metadata-set'] = $set; if (isset($metadata['tags']) && count(array_intersect($this->excludeTags, $metadata['tags'])) > 0) { \SimpleSAML\Logger::debug('Excluding entity ID [' . $entityId . '] becuase it is tagged with one of [' . var_export($this->excludeTags, TRUE) . ']'); continue; } else { #echo('<pre>'); print_r($metadata); exit; } if (!array_key_exists($entityId, $entities)) { $entities[$entityId] = array(); } if (array_key_exists($set, $entities[$entityId])) { /* Entity already has metadata for the given set. */ continue; } $entities[$entityId][$set] = $metadata; } } } return $entities; }
/** * 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 timezone. * * This function should be called before any calls to date(). * * @author Olav Morken, UNINETT AS <*****@*****.**> */ 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; }
/** * Apply filter. * * @param array &$request The current request */ public function process(&$request) { \SimpleSAML\Logger::debug('Processing the AttributeValueMap filter.'); assert('is_array($request)'); assert('array_key_exists("Attributes", $request)'); $attributes =& $request['Attributes']; if (!array_key_exists($this->sourceattribute, $attributes)) { // the source attribute does not exist, nothing to do here return; } $sourceattribute = $attributes[$this->sourceattribute]; $targetvalues = array(); if (is_array($sourceattribute)) { foreach ($this->values as $value => $values) { if (!is_array($values)) { $values = array($values); } if (count(array_intersect($values, $sourceattribute)) > 0) { \SimpleSAML\Logger::debug("AttributeValueMap: intersect match for '{$value}'"); $targetvalues[] = $value; } } } if (count($targetvalues) > 0) { if ($this->replace || !array_key_exists($this->targetattribute, $attributes)) { $attributes[$this->targetattribute] = $targetvalues; } else { $attributes[$this->targetattribute] = array_unique(array_merge($attributes[$this->targetattribute], $targetvalues)); } } if (!$this->keep) { // no need to keep the source attribute unset($attributes[$this->sourceattribute]); } }
/** * Read a dictionary file. * * @param string $filename The absolute path to the dictionary file. * * @return array An array holding all the translations in the file. */ private function readDictionaryFile($filename) { assert('is_string($filename)'); \SimpleSAML\Logger::debug('Template: Reading [' . $filename . ']'); $jsonFile = $filename . '.definition.json'; if (file_exists($jsonFile)) { return $this->readDictionaryJSON($filename); } $phpFile = $filename . '.php'; if (file_exists($phpFile)) { return $this->readDictionaryPHP($filename); } \SimpleSAML\Logger::error($_SERVER['PHP_SELF'] . ' - Template: Could not find dictionary file at [' . $filename . ']'); return array(); }
/** * Set up L18N if configured or fallback to old system */ private function setupL10N() { if ($this->i18nBackend === self::SSP_I18N_BACKEND) { \SimpleSAML\Logger::debug("Localization: using old system"); return; } $this->setupTranslator(); // setup default domain $this->addDomain($this->localeDir, self::DEFAULT_DOMAIN); }
/** * 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.'); } } }
/** * 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]); \SimpleSAML\Logger::popErrorMask(); if (is_bool($hidden)) { return $hidden; } return false; }
// From a remote idp (as bridge) $idp_entityid = $as->getAuthData('saml:sp:IdP'); $idp_metadata = $metadata->getMetaData($idp_entityid, 'saml20-idp-remote'); } else { // from the local idp $idp_entityid = $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted'); $idp_metadata = $metadata->getMetaData($idp_entityid, 'saml20-idp-hosted'); } \SimpleSAML\Logger::debug('consentAdmin: IdP is [' . $idp_entityid . ']'); $source = $idp_metadata['metadata-set'] . '|' . $idp_entityid; // Parse consent config $consent_storage = sspmod_consent_Store::parseStoreConfig($consentconfig->getValue('store')); // Calc correct user ID hash $hashed_user_id = sspmod_consent_Auth_Process_Consent::getHashedUserID($userid, $source); // Check if button with withdraw all consent was clicked if (array_key_exists('withdraw', $_REQUEST)) { \SimpleSAML\Logger::info('consentAdmin: UserID [' . $hashed_user_id . '] has requested to withdraw all consents given...'); $consent_storage->deleteAllConsents($hashed_user_id); } // Get all consents for user $user_consent_list = $consent_storage->getConsents($hashed_user_id); $consentServices = array(); foreach ($user_consent_list as $c) { $consentServices[$c[1]] = 1; } \SimpleSAML\Logger::debug('consentAdmin: no of consents [' . count($user_consent_list) . '] no of services [' . count($consentServices) . ']'); // Init template $t = new SimpleSAML_XHTML_Template($config, 'consentSimpleAdmin:consentadmin.php'); $t->data['consentServices'] = count($consentServices); $t->data['consents'] = count($user_consent_list); $t->show();
/** * Get the localized name of a language, by ISO 639-2 code. * * @param string $code The ISO 639-2 code of the language. * * @return string The localized name of the language. */ public function getLanguageLocalizedName($code) { if (array_key_exists($code, $this->language_names) && isset($this->language_names[$code])) { return $this->language_names[$code]; } \SimpleSAML\Logger::error("Name for language \"{$code}\" not found. Check config."); return null; }
/** * Determine whether a module is enabled. * * Will return false if the given module doesn't exist. * * @param string $module Name of the module * * @return bool True if the given module is enabled, false otherwise. * * @throws \Exception If module.enable is set and is not boolean. */ public static function isModuleEnabled($module) { $moduleDir = self::getModuleDir($module); if (!is_dir($moduleDir)) { return false; } $globalConfig = \SimpleSAML_Configuration::getOptionalConfig(); $moduleEnable = $globalConfig->getArray('module.enable', array()); if (isset($moduleEnable[$module])) { if (is_bool($moduleEnable[$module]) === true) { return $moduleEnable[$module]; } throw new \Exception("Invalid module.enable value for the '{$module}' module."); } if (assert_options(ASSERT_ACTIVE) && !file_exists($moduleDir . '/default-enable') && !file_exists($moduleDir . '/default-disable')) { \SimpleSAML\Logger::error("Missing default-enable or default-disable file for the module {$module}"); } if (file_exists($moduleDir . '/enable')) { return true; } if (!file_exists($moduleDir . '/disable') && file_exists($moduleDir . '/default-enable')) { return true; } return false; }
/** * 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); }
/** * 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. * * @author Olav Morken, UNINETT AS <*****@*****.**> */ 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) || array_key_exists('saml', $debug) && $debug['saml'] === true || array_key_exists(0, $debug) && $debug[0] === true)) { // 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); } }
/** * Clean the key-value table of expired entries. */ public function cleanKVStore() { \SimpleSAML\Logger::debug('store.dbal: Cleaning key-value store.'); try { $qb = $this->createQueryBuilder(); $qb->delete($this->kvstorePrefix)->where($qb->expr()->lt('_expire', ':now'))->setParameter('now', new \DateTime(), Type::DATETIME)->execute(); } catch (TableNotFoundException $e) { throw new \SimpleSAML_Error_Error('KVStore table on the datastore is missing. Did you create the schema? See README.md from dbal module for more information.'); } }
/** * 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); } }
/** * 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. * * @author Olav Morken, UNINETT AS <*****@*****.**> */ public static function debugSAMLMessage($message, $type) { if (!(is_string($type) && (is_string($message) || $message instanceof \DOMElement))) { throw new \InvalidArgumentException('Invalid input parameters.'); } $globalConfig = \SimpleSAML_Configuration::getInstance(); if (!$globalConfig->getBoolean('debug', false)) { // message debug 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); } }
/** * Find template path. * * This function locates the given template based on the template name. It will first search for the template in * the current theme directory, and then the default theme. * * The template name may be on the form <module name>:<template path>, in which case it will search for the * template file in the given module. * * @param string $template The relative path from the theme directory to the template file. * * @return string The absolute path to the template file. * * @throws Exception If the template file couldn't be found. */ private function findTemplatePath($template, $throw_exception = true) { assert('is_string($template)'); $result = $this->findModuleAndTemplateName($template); $templateModule = $result[0] ? $result[0] : 'default'; $templateName = $result[1]; $tmp = explode(':', $this->configuration->getString('theme.use', 'default'), 2); if (count($tmp) === 2) { $themeModule = $tmp[0]; $themeName = $tmp[1]; } else { $themeModule = null; $themeName = $tmp[0]; } // first check the current theme if ($themeModule !== null) { // .../module/<themeModule>/themes/<themeName>/<templateModule>/<templateName> $filename = \SimpleSAML\Module::getModuleDir($themeModule) . '/themes/' . $themeName . '/' . $templateModule . '/' . $templateName; } elseif ($templateModule !== 'default') { // .../module/<templateModule>/templates/<templateName> $filename = \SimpleSAML\Module::getModuleDir($templateModule) . '/templates/' . $templateName; } else { // .../templates/<theme>/<templateName> $filename = $this->configuration->getPathValue('templatedir', 'templates/') . $templateName; } if (file_exists($filename)) { return $filename; } // not found in current theme \SimpleSAML\Logger::debug($_SERVER['PHP_SELF'] . ' - Template: Could not find template file [' . $template . '] at [' . $filename . '] - now trying the base template'); // try default theme if ($templateModule !== 'default') { // .../module/<templateModule>/templates/<templateName> $filename = \SimpleSAML\Module::getModuleDir($templateModule) . '/templates/' . $templateName; } else { // .../templates/<templateName> $filename = $this->configuration->getPathValue('templatedir', 'templates/') . '/' . $templateName; } if (file_exists($filename)) { return $filename; } // not found in default template if ($throw_exception) { // log error and throw exception $error = 'Template: Could not find template file [' . $template . '] at [' . $filename . ']'; \SimpleSAML\Logger::critical($_SERVER['PHP_SELF'] . ' - ' . $error); throw new Exception($error); } else { // missing template expected, return NULL return null; } }
/** * Marks the user as logged in with the specified authority. * * If the user already has logged in, the user will be logged out first. * * @param string $authority The authority the user logged in with. * @param array|null $data The authentication data for this authority. * * @throws \SimpleSAML\Error\CannotSetCookie If the authentication token cannot be set for some reason. */ public function doLogin($authority, array $data = null) { assert('is_string($authority)'); assert('is_array($data) || is_null($data)'); SimpleSAML\Logger::debug('Session: doLogin("' . $authority . '")'); $this->markDirty(); if (isset($this->authData[$authority])) { // we are already logged in, log the user out first $this->doLogout($authority); } if ($data === null) { $data = array(); } $data['Authority'] = $authority; $globalConfig = SimpleSAML_Configuration::getInstance(); if (!isset($data['AuthnInstant'])) { $data['AuthnInstant'] = time(); } $maxSessionExpire = time() + $globalConfig->getInteger('session.duration', 8 * 60 * 60); if (!isset($data['Expire']) || $data['Expire'] > $maxSessionExpire) { // unset, or beyond our session lifetime. Clamp it to our maximum session lifetime $data['Expire'] = $maxSessionExpire; } // check if we have non-serializable attribute values foreach ($data['Attributes'] as $attribute => $values) { foreach ($values as $idx => $value) { if (is_string($value) || is_int($value)) { continue; } // at this point, this should be a DOMNodeList object... if (!is_a($value, 'DOMNodeList')) { continue; } /* @var \DOMNodeList $value */ if ($value->length === 0) { continue; } // create an AttributeValue object and save it to 'RawAttributes', using same attribute name and index $attrval = new \SAML2\XML\saml\AttributeValue($value->item(0)->parentNode); $data['RawAttributes'][$attribute][$idx] = $attrval; } } $this->authData[$authority] = $data; $this->authToken = SimpleSAML\Utils\Random::generateID(); $sessionHandler = SimpleSAML_SessionHandler::getSessionHandler(); if (!$this->transient && (!empty($data['RememberMe']) || $this->rememberMeExpire) && $globalConfig->getBoolean('session.rememberme.enable', false)) { $this->setRememberMeExpire(); } else { try { SimpleSAML\Utils\HTTP::setCookie($globalConfig->getString('session.authtoken.cookiename', 'SimpleSAMLAuthToken'), $this->authToken, $sessionHandler->getCookieParams()); } catch (SimpleSAML\Error\CannotSetCookie $e) { /* * Something went wrong when setting the auth token. We cannot recover from this, so we better log a * message and throw an exception. The user is not properly logged in anyway, so clear all login * information from the session. */ unset($this->authToken); unset($this->authData[$authority]); \SimpleSAML\Logger::error('Cannot set authentication token cookie: ' . $e->getMessage()); throw $e; } } }
/** * 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; }