diff options
Diffstat (limited to 'lib/SimpleSAML')
52 files changed, 3046 insertions, 2161 deletions
diff --git a/lib/SimpleSAML/Auth/Default.php b/lib/SimpleSAML/Auth/Default.php index e3687bd..0498554 100644 --- a/lib/SimpleSAML/Auth/Default.php +++ b/lib/SimpleSAML/Auth/Default.php @@ -21,11 +21,11 @@ class SimpleSAML_Auth_Default { * @param string|array $return The URL or function we should direct the * user to after authentication. If using a URL obtained from user input, * please make sure to check it by calling - * SimpleSAML_Utilities::checkURLAllowed(). + * \SimpleSAML\Utils\HTTP::checkURLAllowed(). * @param string|NULL $errorURL The URL we should direct the user to after * failed authentication. Can be NULL, in which case a standard error page * will be shown. If using a URL obtained from user input, please make sure - * to check it by calling SimpleSAML_Utilities::checkURLAllowed(). + * to check it by calling \SimpleSAML\Utils\HTTP::checkURLAllowed(). * @param array $params Extra information about the login. Different * authentication requestors may provide different information. Optional, * will default to an empty array. @@ -128,7 +128,7 @@ class SimpleSAML_Auth_Default { if (is_string($return)) { /* Redirect... */ - SimpleSAML_Utilities::redirectTrustedURL($return); + \SimpleSAML\Utils\HTTP::redirectTrustedURL($return); } else { call_user_func($return, $state); assert('FALSE'); @@ -146,7 +146,7 @@ class SimpleSAML_Auth_Default { * @param string $returnURL The URL we should redirect the user to after * logging out. No checking is performed on the URL, so make sure to verify * it on beforehand if the URL is obtained from user input. Refer to - * SimpleSAML_Utilities::checkURLAllowed() for more information. + * \SimpleSAML\Utils\HTTP::checkURLAllowed() for more information. * @param string $authority The authentication source we are logging * out from. */ @@ -181,7 +181,7 @@ class SimpleSAML_Auth_Default { * @param string $returnURL The URL we should redirect the user to after * logging out. No checking is performed on the URL, so make sure to verify * it on beforehand if the URL is obtained from user input. Refer to - * SimpleSAML_Utilities::checkURLAllowed() for more information. + * \SimpleSAML\Utils\HTTP::checkURLAllowed() for more information. * @param string|NULL $authority The authentication source we are logging * out from. * @return void This function never returns. @@ -193,7 +193,7 @@ class SimpleSAML_Auth_Default { self::initLogoutReturn($returnURL, $authority); /* Redirect... */ - SimpleSAML_Utilities::redirectTrustedURL($returnURL); + \SimpleSAML\Utils\HTTP::redirectTrustedURL($returnURL); } @@ -211,7 +211,7 @@ class SimpleSAML_Auth_Default { $returnURL = $state['SimpleSAML_Auth_Default.ReturnURL']; /* Redirect... */ - SimpleSAML_Utilities::redirectTrustedURL($returnURL); + \SimpleSAML\Utils\HTTP::redirectTrustedURL($returnURL); } @@ -265,7 +265,7 @@ class SimpleSAML_Auth_Default { $session = SimpleSAML_Session::getSessionFromRequest(); $session->doLogin($authId, self::extractPersistentAuthState($state)); - SimpleSAML_Utilities::redirectUntrustedURL($redirectTo); + \SimpleSAML\Utils\HTTP::redirectUntrustedURL($redirectTo); } } diff --git a/lib/SimpleSAML/Auth/LDAP.php b/lib/SimpleSAML/Auth/LDAP.php index 24e0a28..50cd5da 100644 --- a/lib/SimpleSAML/Auth/LDAP.php +++ b/lib/SimpleSAML/Auth/LDAP.php @@ -261,7 +261,7 @@ class SimpleSAML_Auth_LDAP { public function searchfordn($base, $attribute, $value, $allowZeroHits = FALSE) { // Traverse all search bases, returning DN if found. - $bases = SimpleSAML_Utilities::arrayize($base); + $bases = SimpleSAML\Utils\Arrays::arrayize($base); $result = NULL; foreach ($bases AS $current) { try { @@ -586,7 +586,7 @@ class SimpleSAML_Auth_LDAP { $dn = $this->searchfordn($config['searchbase'], $config['searchattributes'], $username); } - if ($password != null) { /* checking users credentials ... assuming below that she may read her own attributes ... */ + if ($password !== null) { /* checking users credentials ... assuming below that she may read her own attributes ... */ if (!$this->bind($dn, $password)) { SimpleSAML_Logger::info('Library - LDAP validate(): Failed to authenticate \''. $username . '\' using DN \'' . $dn . '\''); return FALSE; diff --git a/lib/SimpleSAML/Auth/ProcessingChain.php b/lib/SimpleSAML/Auth/ProcessingChain.php index da75fcc..b034220 100644 --- a/lib/SimpleSAML/Auth/ProcessingChain.php +++ b/lib/SimpleSAML/Auth/ProcessingChain.php @@ -247,7 +247,7 @@ class SimpleSAML_Auth_ProcessingChain { * in $state['ReturnURL']. */ $id = SimpleSAML_Auth_State::saveState($state, self::COMPLETED_STAGE); - SimpleSAML_Utilities::redirectTrustedURL($state['ReturnURL'], array(self::AUTHPARAM => $id)); + \SimpleSAML\Utils\HTTP::redirectTrustedURL($state['ReturnURL'], array(self::AUTHPARAM => $id)); } else { /* Pass the state to the function defined in $state['ReturnCall']. */ @@ -302,7 +302,7 @@ class SimpleSAML_Auth_ProcessingChain { * Retrieve a state which has finished processing. * * @param string $id The state identifier. - * @see SimpleSAML_Utilities::parseStateID() + * @see SimpleSAML_Auth_State::parseStateID() * @return Array The state referenced by the $id parameter. */ public static function fetchProcessedState($id) { diff --git a/lib/SimpleSAML/Auth/Simple.php b/lib/SimpleSAML/Auth/Simple.php index da0881d..a82419f 100644 --- a/lib/SimpleSAML/Auth/Simple.php +++ b/lib/SimpleSAML/Auth/Simple.php @@ -110,11 +110,11 @@ class SimpleSAML_Auth_Simple { } else if (array_key_exists('ReturnCallback', $params)) { $returnTo = (array)$params['ReturnCallback']; } else { - $returnTo = SimpleSAML_Utilities::selfURL(); + $returnTo = \SimpleSAML\Utils\HTTP::getSelfURL(); } if (is_string($returnTo) && $keepPost && $_SERVER['REQUEST_METHOD'] === 'POST') { - $returnTo = SimpleSAML_Utilities::createPostRedirectLink($returnTo, $_POST); + $returnTo = \SimpleSAML\Utils\HTTP::getPOSTRedirectURL($returnTo, $_POST); } if (array_key_exists('ErrorURL', $params)) { @@ -159,7 +159,7 @@ class SimpleSAML_Auth_Simple { assert('is_array($params) || is_string($params) || is_null($params)'); if ($params === NULL) { - $params = SimpleSAML_Utilities::selfURL(); + $params = \SimpleSAML\Utils\HTTP::getSelfURL(); } if (is_string($params)) { @@ -217,8 +217,7 @@ class SimpleSAML_Auth_Simple { $stateID = SimpleSAML_Auth_State::saveState($state, $state['ReturnStateStage']); $params[$state['ReturnStateParam']] = $stateID; } - - SimpleSAML_Utilities::redirectTrustedURL($state['ReturnTo'], $params); + \SimpleSAML\Utils\HTTP::redirectTrustedURL($state['ReturnTo'], $params); } } @@ -290,7 +289,7 @@ class SimpleSAML_Auth_Simple { assert('is_null($returnTo) || is_string($returnTo)'); if ($returnTo === NULL) { - $returnTo = SimpleSAML_Utilities::selfURL(); + $returnTo = \SimpleSAML\Utils\HTTP::getSelfURL(); } $login = SimpleSAML_Module::getModuleURL('core/as_login.php', array( @@ -313,7 +312,7 @@ class SimpleSAML_Auth_Simple { assert('is_null($returnTo) || is_string($returnTo)'); if ($returnTo === NULL) { - $returnTo = SimpleSAML_Utilities::selfURL(); + $returnTo = \SimpleSAML\Utils\HTTP::getSelfURL(); } $logout = SimpleSAML_Module::getModuleURL('core/as_logout.php', array( diff --git a/lib/SimpleSAML/Auth/State.php b/lib/SimpleSAML/Auth/State.php index 4684f5d..4f5e263 100644 --- a/lib/SimpleSAML/Auth/State.php +++ b/lib/SimpleSAML/Auth/State.php @@ -105,7 +105,7 @@ class SimpleSAML_Auth_State { assert('is_bool($rawId)'); if (!array_key_exists(self::ID, $state)) { - $state[self::ID] = SimpleSAML_Utilities::generateID(); + $state[self::ID] = SimpleSAML\Utils\Random::generateID(); } $id = $state[self::ID]; @@ -210,7 +210,7 @@ class SimpleSAML_Auth_State { assert('is_bool($allowMissing)'); SimpleSAML_Logger::debug('Loading state: ' . var_export($id, TRUE)); - $sid = SimpleSAML_Utilities::parseStateID($id); + $sid = self::parseStateID($id); $session = SimpleSAML_Session::getSessionFromRequest(); $state = $session->getData('SimpleSAML_Auth_State', $sid['id']); @@ -225,7 +225,7 @@ class SimpleSAML_Auth_State { throw new SimpleSAML_Error_NoState(); } - SimpleSAML_Utilities::redirectUntrustedURL($sid['url']); + \SimpleSAML\Utils\HTTP::redirectUntrustedURL($sid['url']); } $state = unserialize($state); @@ -249,7 +249,7 @@ class SimpleSAML_Auth_State { throw new Exception($msg); } - SimpleSAML_Utilities::redirectUntrustedURL($sid['url']); + \SimpleSAML\Utils\HTTP::redirectUntrustedURL($sid['url']); } return $state; @@ -294,7 +294,7 @@ class SimpleSAML_Auth_State { $id = self::saveState($state, self::EXCEPTION_STAGE); /* Redirect to the exception handler. */ - SimpleSAML_Utilities::redirectTrustedURL($state[self::EXCEPTION_HANDLER_URL], array(self::EXCEPTION_PARAM => $id)); + \SimpleSAML\Utils\HTTP::redirectTrustedURL($state[self::EXCEPTION_HANDLER_URL], array(self::EXCEPTION_PARAM => $id)); } elseif (array_key_exists(self::EXCEPTION_HANDLER_FUNC, $state)) { /* Call the exception handler. */ @@ -337,4 +337,26 @@ class SimpleSAML_Auth_State { return $state; } + + /** + * Get the ID and (optionally) a URL embedded in a StateID, in the form 'id:url'. + * + * @param string $stateId The state ID to use. + * + * @return array A hashed array with the ID and the URL (if any), in the 'id' and 'url' keys, respectively. If + * there's no URL in the input parameter, NULL will be returned as the value for the 'url' key. + * + * @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no> + * @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no> + */ + public static function parseStateID($stateId) { + $tmp = explode(':', $stateId, 2); + $id = $tmp[0]; + $url = null; + if (count($tmp) === 2) { + $url = $tmp[1]; + } + return array('id' => $id, 'url' => $url); + } + } diff --git a/lib/SimpleSAML/Auth/TimeLimitedToken.php b/lib/SimpleSAML/Auth/TimeLimitedToken.php index 3c991ce..5a59b7a 100644 --- a/lib/SimpleSAML/Auth/TimeLimitedToken.php +++ b/lib/SimpleSAML/Auth/TimeLimitedToken.php @@ -14,7 +14,7 @@ class SimpleSAML_Auth_TimeLimitedToken { */ public function __construct( $lifetime = 900, $secretSalt = NULL, $skew = 1) { if ($secretSalt === NULL) { - $secretSalt = SimpleSAML_Utilities::getSecretSalt(); + $secretSalt = SimpleSAML\Utils\Config::getSecretSalt(); } $this->secretSalt = $secretSalt; @@ -39,9 +39,6 @@ class SimpleSAML_Auth_TimeLimitedToken { * Calculate the given time slot for a given offset. */ private function calculate_time_slot($offset) { - - #echo 'lifetime is: ' . $this->lifetime; - $timeslot = floor( (time() - $offset) / ($this->lifetime + $this->skew) ); return $timeslot; } @@ -51,10 +48,6 @@ class SimpleSAML_Auth_TimeLimitedToken { */ private function calculate_tokenvalue($offset) { // A secret salt that should be randomly generated for each installation. - #echo 'Secret salt is: ' . $this->secretSalt; - - #echo '<p>Calculating sha1( ' . $this->calculate_time_slot($offset) . ':' . $this->secretSalt . ' )<br />'; - return sha1( $this->calculate_time_slot($offset) . ':' . $this->secretSalt); } @@ -74,10 +67,6 @@ class SimpleSAML_Auth_TimeLimitedToken { $splittedtoken = explode('-', $token); $offset = hexdec($splittedtoken[0]); $value = $splittedtoken[1]; - - - #echo 'compare [' . $this->calculate_tokenvalue($offset). '] with [' . $value . '] offset was [' . $offset. ']'; - return ($this->calculate_tokenvalue($offset) === $value); } diff --git a/lib/SimpleSAML/Bindings/Shib13/Artifact.php b/lib/SimpleSAML/Bindings/Shib13/Artifact.php index 4eda10b..0b27c03 100644 --- a/lib/SimpleSAML/Bindings/Shib13/Artifact.php +++ b/lib/SimpleSAML/Bindings/Shib13/Artifact.php @@ -48,9 +48,9 @@ class SimpleSAML_Bindings_Shib13_Artifact { $msg = '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">' . '<SOAP-ENV:Body>' . '<samlp:Request xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol"' . - ' RequestID="' . SimpleSAML_Utilities::generateID() . '"' . + ' RequestID="' . SimpleSAML\Utils\Random::generateID() . '"' . ' MajorVersion="1" MinorVersion="1"' . - ' IssueInstant="' . SimpleSAML_Utilities::generateTimestamp() . '"' . + ' IssueInstant="' . SimpleSAML\Utils\Time::generateTimestamp() . '"' . '>'; foreach ($artifacts as $a) { @@ -80,18 +80,18 @@ class SimpleSAML_Bindings_Shib13_Artifact { } $soapEnvelope = $doc->firstChild; - if (!SimpleSAML_Utilities::isDOMElementOfType($soapEnvelope, 'Envelope', 'http://schemas.xmlsoap.org/soap/envelope/')) { + if (!SimpleSAML\Utils\XML::isDOMElementOfType($soapEnvelope, 'Envelope', 'http://schemas.xmlsoap.org/soap/envelope/')) { throw new SimpleSAML_Error_Exception('Expected artifact response to contain a <soap:Envelope> element.'); } - $soapBody = SimpleSAML_Utilities::getDOMChildren($soapEnvelope, 'Body', 'http://schemas.xmlsoap.org/soap/envelope/'); + $soapBody = SimpleSAML\Utils\XML::getDOMChildren($soapEnvelope, 'Body', 'http://schemas.xmlsoap.org/soap/envelope/'); if (count($soapBody) === 0) { throw new SimpleSAML_Error_Exception('Couldn\'t find <soap:Body> in <soap:Envelope>.'); } $soapBody = $soapBody[0]; - $responseElement = SimpleSAML_Utilities::getDOMChildren($soapBody, 'Response', 'urn:oasis:names:tc:SAML:1.0:protocol'); + $responseElement = SimpleSAML\Utils\XML::getDOMChildren($soapBody, 'Response', 'urn:oasis:names:tc:SAML:1.0:protocol'); if (count($responseElement) === 0) { throw new SimpleSAML_Error_Exception('Couldn\'t find <saml1p:Response> in <soap:Body>.'); } @@ -121,7 +121,7 @@ class SimpleSAML_Bindings_Shib13_Artifact { $artifacts = self::getArtifacts(); $request = self::buildRequest($artifacts); - SimpleSAML_Utilities::debugMessage($msgStr, 'out'); + \SimpleSAML\Utils\XML::debugSAMLMessage($request, 'out'); $url = $idpMetadata->getDefaultEndpoint('ArtifactResolutionService', array('urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding')); $url = $url['Location']; @@ -137,12 +137,12 @@ class SimpleSAML_Bindings_Shib13_Artifact { "-----END CERTIFICATE-----\n"; } - $file = SimpleSAML_Utilities::getTempDir() . '/' . sha1($certData) . '.crt'; + $file = SimpleSAML\Utils\System::getTempDir() . DIRECTORY_SEPARATOR . sha1($certData) . '.crt'; if (!file_exists($file)) { - SimpleSAML_Utilities::writeFile($file, $certData); + SimpleSAML\Utils\System::writeFile($file, $certData); } - $spKeyCertFile = SimpleSAML_Utilities::resolveCert($spMetadata->getString('privatekey')); + $spKeyCertFile = \SimpleSAML\Utils\Config::getCertPath($spMetadata->getString('privatekey')); $opts = array( 'ssl' => array( @@ -161,12 +161,12 @@ class SimpleSAML_Bindings_Shib13_Artifact { ); /* Fetch the artifact. */ - $response = SimpleSAML_Utilities::fetch($url, $opts); + $response = \SimpleSAML\Utils\HTTP::fetch($url, $opts); if ($response === FALSE) { throw new SimpleSAML_Error_Exception('Failed to retrieve assertion from IdP.'); } - SimpleSAML_Utilities::debugMessage($response, 'in'); + \SimpleSAML\Utils\XML::debugSAMLMessage($response, 'in'); /* Find the response in the SOAP message. */ $response = self::extractResponse($response); diff --git a/lib/SimpleSAML/Bindings/Shib13/HTTPPost.php b/lib/SimpleSAML/Bindings/Shib13/HTTPPost.php index f9f5d8a..c97cf89 100644 --- a/lib/SimpleSAML/Bindings/Shib13/HTTPPost.php +++ b/lib/SimpleSAML/Bindings/Shib13/HTTPPost.php @@ -3,7 +3,7 @@ /** * Implementation of the Shibboleth 1.3 HTTP-POST binding. * - * @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no> + * @author Andreas Ã…kre Solberg, UNINETT AS. <andreas.solberg@uninett.no> * @package simpleSAMLphp */ class SimpleSAML_Bindings_Shib13_HTTPPost { @@ -27,10 +27,10 @@ class SimpleSAML_Bindings_Shib13_HTTPPost { */ public function sendResponse($response, SimpleSAML_Configuration $idpmd, SimpleSAML_Configuration $spmd, $relayState, $shire) { - SimpleSAML_Utilities::validateXMLDocument($response, 'saml11'); + \SimpleSAML\Utils\XML::checkSAMLMessage($response, 'saml11'); - $privatekey = SimpleSAML_Utilities::loadPrivateKey($idpmd, TRUE); - $publickey = SimpleSAML_Utilities::loadPublicKey($idpmd, TRUE); + $privatekey = SimpleSAML\Utils\Crypto::loadPrivateKey($idpmd, TRUE); + $publickey = SimpleSAML\Utils\Crypto::loadPublicKey($idpmd, TRUE); $responsedom = new DOMDocument(); $responsedom->loadXML(str_replace ("\r", "", $response)); @@ -67,7 +67,7 @@ class SimpleSAML_Bindings_Shib13_HTTPPost { if ($signResponse) { /* Sign the response - this must be done after encrypting the assertion. */ /* We insert the signature before the saml2p:Status element. */ - $statusElements = SimpleSAML_Utilities::getDOMChildren($responseroot, 'Status', '@saml1p'); + $statusElements = SimpleSAML\Utils\XML::getDOMChildren($responseroot, 'Status', '@saml1p'); assert('count($statusElements) === 1'); $signer->sign($responseroot, $responseroot, $statusElements[0]); @@ -78,9 +78,9 @@ class SimpleSAML_Bindings_Shib13_HTTPPost { $response = $responsedom->saveXML(); - SimpleSAML_Utilities::debugMessage($response, 'out'); + \SimpleSAML\Utils\XML::debugSAMLMessage($response, 'out'); - SimpleSAML_Utilities::postRedirect($shire, array( + \SimpleSAML\Utils\HTTP::submitPOSTData($shire, array( 'TARGET' => $relayState, 'SAMLResponse' => base64_encode($response), )); @@ -103,9 +103,9 @@ class SimpleSAML_Bindings_Shib13_HTTPPost { $rawResponse = $post['SAMLResponse']; $samlResponseXML = base64_decode($rawResponse); - SimpleSAML_Utilities::debugMessage($samlResponseXML, 'in'); + \SimpleSAML\Utils\XML::debugSAMLMessage($samlResponseXML, 'in'); - SimpleSAML_Utilities::validateXMLDocument($samlResponseXML, 'saml11'); + \SimpleSAML\Utils\XML::checkSAMLMessage($samlResponseXML, 'saml11'); $samlResponse = new SimpleSAML_XML_Shib13_AuthnResponse(); $samlResponse->setXML($samlResponseXML); diff --git a/lib/SimpleSAML/Configuration.php b/lib/SimpleSAML/Configuration.php index 40941d5..deff4a6 100644 --- a/lib/SimpleSAML/Configuration.php +++ b/lib/SimpleSAML/Configuration.php @@ -111,7 +111,7 @@ class SimpleSAML_Configuration { $host = $_SERVER['HTTP_HOST']; if (array_key_exists($host, $config['override.host'])) { $ofs = $config['override.host'][$host]; - foreach (SimpleSAML_Utilities::arrayize($ofs) AS $of) { + foreach (SimpleSAML\Utils\Arrays::arrayize($ofs) AS $of) { $overrideFile = dirname($filename) . '/' . $of; if (!file_exists($overrideFile)) { throw new Exception('Config file [' . $filename . '] requests override for host ' . $host . ' but file does not exists [' . $of . ']'); @@ -346,7 +346,7 @@ class SimpleSAML_Configuration { if (preg_match('/^\*(.*)$/D', $baseURL, $matches)) { /* deprecated behaviour, will be removed in the future */ - return SimpleSAML_Utilities::getFirstPathElement(false) . $matches[1]; + return \SimpleSAML\Utils\HTTP::getFirstPathElement(false) . $matches[1]; } if (preg_match('#^https?://[^/]*/(.*)$#', $baseURL, $matches)) { @@ -1020,7 +1020,7 @@ class SimpleSAML_Configuration { $endpoints = $this->getEndpoints($endpointType); - $defaultEndpoint = SimpleSAML_Utilities::getDefaultEndpoint($endpoints, $bindings); + $defaultEndpoint = \SimpleSAML\Utils\Config\Metadata::getDefaultEndpoint($endpoints, $bindings); if ($defaultEndpoint !== NULL) { return $defaultEndpoint; } @@ -1118,7 +1118,7 @@ class SimpleSAML_Configuration { ); } elseif ($this->hasValue($prefix . 'certificate')) { $file = $this->getString($prefix . 'certificate'); - $file = SimpleSAML_Utilities::resolveCert($file); + $file = \SimpleSAML\Utils\Config::getCertPath($file); $data = @file_get_contents($file); if ($data === FALSE) { diff --git a/lib/SimpleSAML/Error/Error.php b/lib/SimpleSAML/Error/Error.php index a08ffdd..a276d8f 100644 --- a/lib/SimpleSAML/Error/Error.php +++ b/lib/SimpleSAML/Error/Error.php @@ -202,7 +202,7 @@ class SimpleSAML_Error_Error extends SimpleSAML_Error_Exception { $emsg = array_shift($data); $etrace = implode("\n", $data); - $reportId = SimpleSAML_Utilities::stringToHex(SimpleSAML_Utilities::generateRandomBytes(4)); + $reportId = bin2hex(openssl_random_pseudo_bytes(4)); SimpleSAML_Logger::error('Error report with id ' . $reportId . ' generated.'); $config = SimpleSAML_Configuration::getInstance(); @@ -226,7 +226,7 @@ class SimpleSAML_Error_Error extends SimpleSAML_Error_Exception { 'exceptionTrace' => $etrace, 'reportId' => $reportId, 'trackId' => $session->getTrackID(), - 'url' => SimpleSAML_Utilities::selfURLNoQuery(), + 'url' => \SimpleSAML\Utils\HTTP::getSelfURLNoQuery(), 'version' => $config->getVersion(), 'referer' => $referer, ); @@ -265,7 +265,7 @@ class SimpleSAML_Error_Error extends SimpleSAML_Error_Exception { if($config->getBoolean('errorreporting', TRUE) && $config->getString('technicalcontact_email', 'na@example.org') !== 'na@example.org') { /* Enable error reporting. */ - $baseurl = SimpleSAML_Utilities::getBaseURL(); + $baseurl = \SimpleSAML\Utils\HTTP::getBaseURL(); $data['errorReportAddress'] = $baseurl . 'errorreport.php'; } diff --git a/lib/SimpleSAML/Error/NotFound.php b/lib/SimpleSAML/Error/NotFound.php index cb868e8..251ff19 100644 --- a/lib/SimpleSAML/Error/NotFound.php +++ b/lib/SimpleSAML/Error/NotFound.php @@ -27,7 +27,7 @@ class SimpleSAML_Error_NotFound extends SimpleSAML_Error_Error { assert('is_null($reason) || is_string($reason)'); - $url = SimpleSAML_Utilities::selfURL(); + $url = \SimpleSAML\Utils\HTTP::getSelfURL(); if($reason === NULL) { parent::__construct(array('NOTFOUND', '%URL%' => $url)); diff --git a/lib/SimpleSAML/IdP.php b/lib/SimpleSAML/IdP.php index e5566b8..7ba6193 100644 --- a/lib/SimpleSAML/IdP.php +++ b/lib/SimpleSAML/IdP.php @@ -531,7 +531,7 @@ class SimpleSAML_IdP { public static function finishLogoutRedirect(SimpleSAML_IdP $idp, array $state) { assert('isset($state["core:Logout:URL"])'); - SimpleSAML_Utilities::redirectTrustedURL($state['core:Logout:URL']); + \SimpleSAML\Utils\HTTP::redirectTrustedURL($state['core:Logout:URL']); assert('FALSE'); } diff --git a/lib/SimpleSAML/IdP/LogoutIFrame.php b/lib/SimpleSAML/IdP/LogoutIFrame.php index 44c3b3d..e7fdc6e 100644 --- a/lib/SimpleSAML/IdP/LogoutIFrame.php +++ b/lib/SimpleSAML/IdP/LogoutIFrame.php @@ -48,7 +48,7 @@ class SimpleSAML_IdP_LogoutIFrame extends SimpleSAML_IdP_LogoutHandler { } $url = SimpleSAML_Module::getModuleURL('core/idp/logout-iframe.php', $params); - SimpleSAML_Utilities::redirectTrustedURL($url); + \SimpleSAML\Utils\HTTP::redirectTrustedURL($url); } diff --git a/lib/SimpleSAML/IdP/LogoutTraditional.php b/lib/SimpleSAML/IdP/LogoutTraditional.php index 4cd16dd..7632cab 100644 --- a/lib/SimpleSAML/IdP/LogoutTraditional.php +++ b/lib/SimpleSAML/IdP/LogoutTraditional.php @@ -29,7 +29,7 @@ class SimpleSAML_IdP_LogoutTraditional extends SimpleSAML_IdP_LogoutHandler { try { $idp = SimpleSAML_IdP::getByState($association); $url = call_user_func(array($association['Handler'], 'getLogoutURL'), $idp, $association, $relayState); - SimpleSAML_Utilities::redirectTrustedURL($url); + \SimpleSAML\Utils\HTTP::redirectTrustedURL($url); } catch (Exception $e) { SimpleSAML_Logger::warning('Unable to initialize logout to ' . var_export($id, TRUE) . '.'); $this->idp->terminateAssociation($id); diff --git a/lib/SimpleSAML/Logger/LoggingHandlerFile.php b/lib/SimpleSAML/Logger/LoggingHandlerFile.php index 9ed795e..feab437 100644 --- a/lib/SimpleSAML/Logger/LoggingHandlerFile.php +++ b/lib/SimpleSAML/Logger/LoggingHandlerFile.php @@ -56,7 +56,7 @@ class SimpleSAML_Logger_LoggingHandlerFile implements SimpleSAML_Logger_LoggingH } } - SimpleSAML_Utilities::initTimezone(); + SimpleSAML\Utils\Time::initTimezone(); } diff --git a/lib/SimpleSAML/Logger/LoggingHandlerSyslog.php b/lib/SimpleSAML/Logger/LoggingHandlerSyslog.php index f6d58b1..6b8abef 100644 --- a/lib/SimpleSAML/Logger/LoggingHandlerSyslog.php +++ b/lib/SimpleSAML/Logger/LoggingHandlerSyslog.php @@ -27,7 +27,7 @@ class SimpleSAML_Logger_LoggingHandlerSyslog implements SimpleSAML_Logger_Loggin $processname = $config->getString('logging.processname', 'simpleSAMLphp'); // Setting facility to LOG_USER (only valid in Windows), enable log level rewrite on windows systems. - if (SimpleSAML_Utilities::isWindowsOS()) { + if (SimpleSAML\Utils\System::getOS() === SimpleSAML\Utils\System::WINDOWS) { $this->isWindows = TRUE; $facility = LOG_USER; } diff --git a/lib/SimpleSAML/Memcache.php b/lib/SimpleSAML/Memcache.php index 33d4584..791f43a 100644 --- a/lib/SimpleSAML/Memcache.php +++ b/lib/SimpleSAML/Memcache.php @@ -403,7 +403,7 @@ class SimpleSAML_Memcache { throw new Exception('Failed to get memcache server status.'); } - $stats = SimpleSAML_Utilities::transposeArray($stats); + $stats = SimpleSAML\Utils\Arrays::transpose($stats); $ret = array_merge_recursive($ret, $stats); } diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php b/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php index 3e08619..aa93416 100644 --- a/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php +++ b/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php @@ -89,7 +89,7 @@ class SimpleSAML_Metadata_MetaDataStorageHandler { $config = SimpleSAML_Configuration::getInstance(); assert($config instanceof SimpleSAML_Configuration); - $baseurl = SimpleSAML_Utilities::selfURLhost() . '/' . + $baseurl = \SimpleSAML\Utils\HTTP::getSelfURLHost() . '/' . $config->getBaseURL(); if ($set == 'saml20-sp-hosted') { @@ -144,7 +144,7 @@ class SimpleSAML_Metadata_MetaDataStorageHandler { unset($srcList[$key]); SimpleSAML_Logger::warning("Dropping metadata entity " . var_export($key,true) . ", expired " . - SimpleSAML_Utilities::generateTimestamp($le['expire']) . + SimpleSAML\Utils\Time::generateTimestamp($le['expire']) . "."); } } @@ -187,7 +187,7 @@ class SimpleSAML_Metadata_MetaDataStorageHandler { assert('is_string($set)'); /* First we look for the hostname/path combination. */ - $currenthostwithpath = SimpleSAML_Utilities::getSelfHostWithPath(); // sp.example.org/university + $currenthostwithpath = \SimpleSAML\Utils\HTTP::getSelfHostWithPath(); // sp.example.org/university foreach($this->sources as $source) { $index = $source->getEntityIdFromHostPath($currenthostwithpath, $set, $type); @@ -198,7 +198,7 @@ class SimpleSAML_Metadata_MetaDataStorageHandler { /* Then we look for the hostname. */ - $currenthost = SimpleSAML_Utilities::getSelfHost(); // sp.example.org + $currenthost = \SimpleSAML\Utils\HTTP::getSelfHost(); // sp.example.org if(strpos($currenthost, ":") !== FALSE) { $currenthostdecomposed = explode(":", $currenthost); $currenthost = $currenthostdecomposed[0]; diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php index 22688c2..656426b 100644 --- a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php +++ b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerFlatFile.php @@ -116,16 +116,16 @@ class SimpleSAML_Metadata_MetaDataStorageHandlerFlatFile extends SimpleSAML_Meta private function generateDynamicHostedEntityID($set) { /* Get the configuration. */ - $baseurl = SimpleSAML_Utilities::getBaseURL(); + $baseurl = \SimpleSAML\Utils\HTTP::getBaseURL(); if ($set === 'saml20-idp-hosted') { return $baseurl . 'saml2/idp/metadata.php'; } elseif($set === 'shib13-idp-hosted') { return $baseurl . 'shib13/idp/metadata.php'; } elseif($set === 'wsfed-sp-hosted') { - return 'urn:federation:' . SimpleSAML_Utilities::getSelfHost(); + return 'urn:federation:' . \SimpleSAML\Utils\HTTP::getSelfHost(); } elseif($set === 'adfs-idp-hosted') { - return 'urn:federation:' . SimpleSAML_Utilities::getSelfHost() . ':idp'; + return 'urn:federation:' . \SimpleSAML\Utils\HTTP::getSelfHost() . ':idp'; } else { throw new Exception('Can not generate dynamic EntityID for metadata of this type: [' . $set . ']'); } diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerMDX.php b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerMDX.php index 7364a6f..7f61a62 100644 --- a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerMDX.php +++ b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerMDX.php @@ -140,8 +140,9 @@ class SimpleSAML_Metadata_MetaDataStorageHandlerMDX extends SimpleSAML_Metadata_ $rawData = file_get_contents($cachefilename); if (empty($rawData)) { + $error = error_get_last(); throw new Exception('Error reading metadata from cache file "' . $cachefilename . '": ' . - SimpleSAML_Utilities::getLastError()); + $error['message']); } $data = unserialize($rawData); @@ -252,14 +253,15 @@ class SimpleSAML_Metadata_MetaDataStorageHandlerMDX extends SimpleSAML_Metadata_ SimpleSAML_Logger::debug('MetaData - Handler.MDX: Downloading metadata for "'. $index .'" from [' . $mdx_url . ']' ); try { - $xmldata = SimpleSAML_Utilities::fetch($mdx_url); + $xmldata = \SimpleSAML\Utils\HTTP::fetch($mdx_url); } catch(Exception $e) { SimpleSAML_Logger::warning('Fetching metadata for ' . $index . ': ' . $e->getMessage()); } if (empty($xmldata)) { + $error = error_get_last(); throw new Exception('Error downloading metadata for "'. $index .'" from "' . $mdx_url . '": ' . - SimpleSAML_Utilities::getLastError()); + $error['message']); } $entity = SimpleSAML_Metadata_SAMLParser::parseString($xmldata); diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php index 3415cd5..fae34c9 100644 --- a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php +++ b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php @@ -164,8 +164,9 @@ class SimpleSAML_Metadata_MetaDataStorageHandlerSerialize extends SimpleSAML_Met $data = @file_get_contents($filePath); if ($data === FALSE) { + $error = error_get_last(); SimpleSAML_Logger::warning('Error reading file ' . $filePath . - ': ' . SimpleSAML_Utilities::getLastError()); + ': ' . $error['message']); return NULL; } @@ -199,8 +200,9 @@ class SimpleSAML_Metadata_MetaDataStorageHandlerSerialize extends SimpleSAML_Met SimpleSAML_Logger::info('Creating directory: ' . $dir); $res = @mkdir($dir, 0777, TRUE); if ($res === FALSE) { + $error = error_get_last(); SimpleSAML_Logger::error('Failed to create directory ' . $dir . - ': ' . SimpleSAML_Utilities::getLastError()); + ': ' . $error['message']); return FALSE; } } @@ -211,15 +213,17 @@ class SimpleSAML_Metadata_MetaDataStorageHandlerSerialize extends SimpleSAML_Met $res = file_put_contents($newPath, $data); if ($res === FALSE) { + $error = error_get_last(); SimpleSAML_Logger::error('Error saving file ' . $newPath . - ': ' . SimpleSAML_Utilities::getLastError()); + ': ' . $error['message']); return FALSE; } $res = rename($newPath, $filePath); if ($res === FALSE) { + $error = error_get_last(); SimpleSAML_Logger::error('Error renaming ' . $newPath . ' to ' . $filePath . - ': ' . SimpleSAML_Utilities::getLastError()); + ': ' . $error['message']); return FALSE; } @@ -248,8 +252,9 @@ class SimpleSAML_Metadata_MetaDataStorageHandlerSerialize extends SimpleSAML_Met $res = unlink($filePath); if ($res === FALSE) { + $error = error_get_last(); SimpleSAML_Logger::error('Failed to delete file ' . $filePath . - ': ' . SimpleSAML_Utilities::getLastError()); + ': ' . $error['message']); } } diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageSource.php b/lib/SimpleSAML/Metadata/MetaDataStorageSource.php index 549d920..1fc1160 100644 --- a/lib/SimpleSAML/Metadata/MetaDataStorageSource.php +++ b/lib/SimpleSAML/Metadata/MetaDataStorageSource.php @@ -152,7 +152,7 @@ abstract class SimpleSAML_Metadata_MetaDataStorageSource { if(!is_array($entry['hint.cidr'])) continue; foreach ($entry['hint.cidr'] AS $hint_entry) { - if (SimpleSAML_Utilities::ipCIDRcheck($hint_entry, $ip)) { + if (SimpleSAML\Utils\Net::ipCIDRcheck($hint_entry, $ip)) { if ($type === 'entityid') { return $entry['entityid']; } else { @@ -178,7 +178,7 @@ abstract class SimpleSAML_Metadata_MetaDataStorageSource { $metadataSet = $this->getMetadataSet($set); /* Check for hostname. */ - $currenthost = SimpleSAML_Utilities::getSelfHost(); // sp.example.org + $currenthost = \SimpleSAML\Utils\HTTP::getSelfHost(); // sp.example.org if(strpos($currenthost, ":") !== FALSE) { $currenthostdecomposed = explode(":", $currenthost); $currenthost = $currenthostdecomposed[0]; diff --git a/lib/SimpleSAML/Metadata/SAMLBuilder.php b/lib/SimpleSAML/Metadata/SAMLBuilder.php index f684d82..703d28c 100644 --- a/lib/SimpleSAML/Metadata/SAMLBuilder.php +++ b/lib/SimpleSAML/Metadata/SAMLBuilder.php @@ -78,7 +78,7 @@ class SimpleSAML_Metadata_SAMLBuilder { $xml = $this->getEntityDescriptor(); if ($formatted) { - SimpleSAML_Utilities::formatDOMElement($xml); + SimpleSAML\Utils\XML::formatDOMElement($xml); } return $xml->ownerDocument->saveXML(); @@ -277,9 +277,9 @@ class SimpleSAML_Metadata_SAMLBuilder { return; } - $orgName = SimpleSAML_Utilities::arrayize($metadata['OrganizationName'], 'en'); - $orgDisplayName = SimpleSAML_Utilities::arrayize($metadata['OrganizationDisplayName'], 'en'); - $orgURL = SimpleSAML_Utilities::arrayize($metadata['OrganizationURL'], 'en'); + $orgName = SimpleSAML\Utils\Arrays::arrayize($metadata['OrganizationName'], 'en'); + $orgDisplayName = SimpleSAML\Utils\Arrays::arrayize($metadata['OrganizationDisplayName'], 'en'); + $orgURL = SimpleSAML\Utils\Arrays::arrayize($metadata['OrganizationURL'], 'en'); $this->addOrganization($orgName, $orgDisplayName, $orgURL); } @@ -441,6 +441,15 @@ class SimpleSAML_Metadata_SAMLBuilder { $e = new SAML2_XML_md_SPSSODescriptor(); $e->protocolSupportEnumeration = $protocols; + if ($metadata->hasValue('saml20.sign.assertion')) { + $e->WantAssertionsSigned = $metadata->getBoolean('saml20.sign.assertion'); + } + + if ($metadata->hasValue('redirect.validate')) { + $e->AuthnRequestsSigned = $metadata->getBoolean('redirect.validate'); + } elseif ($metadata->hasValue('validate.authnrequest')) { + $e->AuthnRequestsSigned = $metadata->getBoolean('validate.authnrequest'); + } $this->addExtensions($metadata, $e); @@ -465,7 +474,7 @@ class SimpleSAML_Metadata_SAMLBuilder { foreach ($metadata->getArray('contacts', array()) as $contact) { if (array_key_exists('contactType', $contact) && array_key_exists('emailAddress', $contact)) { - $this->addContact($contact['contactType'], SimpleSAML_Utils_Config_Metadata::getContact($contact)); + $this->addContact($contact['contactType'], \SimpleSAML\Utils\Config\Metadata::getContact($contact)); } } @@ -511,7 +520,7 @@ class SimpleSAML_Metadata_SAMLBuilder { foreach ($metadata->getArray('contacts', array()) as $contact) { if (array_key_exists('contactType', $contact) && array_key_exists('emailAddress', $contact)) { - $this->addContact($contact['contactType'], SimpleSAML_Utils_Config_Metadata::getContact($contact)); + $this->addContact($contact['contactType'], \SimpleSAML\Utils\Config\Metadata::getContact($contact)); } } @@ -624,7 +633,7 @@ class SimpleSAML_Metadata_SAMLBuilder { assert('in_array($type, array("technical", "support", "administrative", "billing", "other"), TRUE)'); // TODO: remove this check as soon as getContact() is called always before calling this function. - $details = SimpleSAML_Utils_Config_Metadata::getContact($details); + $details = \SimpleSAML\Utils\Config\Metadata::getContact($details); $e = new SAML2_XML_md_ContactPerson(); $e->contactType = $type; diff --git a/lib/SimpleSAML/Metadata/SAMLParser.php b/lib/SimpleSAML/Metadata/SAMLParser.php index cc5b84e..c141a5c 100644 --- a/lib/SimpleSAML/Metadata/SAMLParser.php +++ b/lib/SimpleSAML/Metadata/SAMLParser.php @@ -178,7 +178,7 @@ class SimpleSAML_Metadata_SAMLParser { public static function parseFile($file) { $doc = new DOMDocument(); - $data = SimpleSAML_Utilities::fetch($file); + $data = \SimpleSAML\Utils\HTTP::fetch($file); $res = $doc->loadXML($data); if($res !== TRUE) { @@ -248,7 +248,7 @@ class SimpleSAML_Metadata_SAMLParser { if ($file === NULL) throw new Exception('Cannot open file NULL. File name not specified.'); - $data = SimpleSAML_Utilities::fetch($file); + $data = \SimpleSAML\Utils\HTTP::fetch($file); $doc = new DOMDocument(); $res = $doc->loadXML($data); @@ -297,9 +297,9 @@ class SimpleSAML_Metadata_SAMLParser { assert('$element instanceof DOMElement'); - if(SimpleSAML_Utilities::isDOMElementOfType($element, 'EntityDescriptor', '@md') === TRUE) { + if (SimpleSAML\Utils\XML::isDOMElementOfType($element, 'EntityDescriptor', '@md') === TRUE) { return self::processDescriptorsElement(new SAML2_XML_md_EntityDescriptor($element)); - } elseif(SimpleSAML_Utilities::isDOMElementOfType($element, 'EntitiesDescriptor', '@md') === TRUE) { + } elseif (SimpleSAML\Utils\XML::isDOMElementOfType($element, 'EntitiesDescriptor', '@md') === TRUE) { return self::processDescriptorsElement(new SAML2_XML_md_EntitiesDescriptor($element)); } else { throw new Exception('Unexpected root node: [' . $element->namespaceURI . ']:' . @@ -1016,8 +1016,8 @@ class SimpleSAML_Metadata_SAMLParser { $name = $attribute->getAttribute('Name'); $values = array_map( - array('SimpleSAML_Utilities', 'getDOMText'), - SimpleSAML_Utilities::getDOMChildren($attribute, 'AttributeValue', '@saml2') + array('SimpleSAML\Utils\XML', 'getDOMText'), + SimpleSAML\Utils\XML::getDOMChildren($attribute, 'AttributeValue', '@saml2') ); if ($name === 'tags') { @@ -1293,7 +1293,7 @@ class SimpleSAML_Metadata_SAMLParser { throw new Exception('Failed to load SAML metadata from empty XML document.'); } - if(SimpleSAML_Utilities::isDOMElementOfType($ed, 'EntityDescriptor', '@md') === FALSE) { + if (SimpleSAML\Utils\XML::isDOMElementOfType($ed, 'EntityDescriptor', '@md') === FALSE) { throw new Exception('Expected first element in the metadata document to be an EntityDescriptor element.'); } @@ -1311,7 +1311,7 @@ class SimpleSAML_Metadata_SAMLParser { public function validateSignature($certificates) { foreach ($certificates as $cert) { assert('is_string($cert)'); - $certFile = SimpleSAML_Utilities::resolveCert($cert); + $certFile = \SimpleSAML\Utils\Config::getCertPath($cert); if (!file_exists($certFile)) { throw new Exception('Could not find certificate file [' . $certFile . '], which is needed to validate signature'); } diff --git a/lib/SimpleSAML/Metadata/Signer.php b/lib/SimpleSAML/Metadata/Signer.php index 51c29d3..a53201b 100644 --- a/lib/SimpleSAML/Metadata/Signer.php +++ b/lib/SimpleSAML/Metadata/Signer.php @@ -142,13 +142,13 @@ class SimpleSAML_Metadata_Signer { $keyCertFiles = self::findKeyCert($config, $entityMetadata, $type); - $keyFile = SimpleSAML_Utilities::resolveCert($keyCertFiles['privatekey']); + $keyFile = \SimpleSAML\Utils\Config::getCertPath($keyCertFiles['privatekey']); if (!file_exists($keyFile)) { throw new Exception('Could not find private key file [' . $keyFile . '], which is needed to sign the metadata'); } $keyData = file_get_contents($keyFile); - $certFile = SimpleSAML_Utilities::resolveCert($keyCertFiles['certificate']); + $certFile = \SimpleSAML\Utils\Config::getCertPath($keyCertFiles['certificate']); if (!file_exists($certFile)) { throw new Exception('Could not find certificate file [' . $certFile . '], which is needed to sign the metadata'); } diff --git a/lib/SimpleSAML/Module.php b/lib/SimpleSAML/Module.php index b5d8f02..45b1ae6 100644 --- a/lib/SimpleSAML/Module.php +++ b/lib/SimpleSAML/Module.php @@ -155,9 +155,9 @@ class SimpleSAML_Module { assert('is_string($resource)'); assert('$resource[0] !== "/"'); - $url = SimpleSAML_Utilities::getBaseURL() . 'module.php/' . $resource; + $url = \SimpleSAML\Utils\HTTP::getBaseURL() . 'module.php/' . $resource; if (!empty($parameters)) { - $url = SimpleSAML_Utilities::addURLparameter($url, $parameters); + $url = \SimpleSAML\Utils\HTTP::addURLParameters($url, $parameters); } return $url; } diff --git a/lib/SimpleSAML/Session.php b/lib/SimpleSAML/Session.php index 3f2af27..42f73b8 100644 --- a/lib/SimpleSAML/Session.php +++ b/lib/SimpleSAML/Session.php @@ -137,7 +137,7 @@ class SimpleSAML_Session $sh = SimpleSAML_SessionHandler::getSessionHandler(); $this->sessionId = $sh->newSessionId(); - $this->trackid = SimpleSAML_Utilities::stringToHex(SimpleSAML_Utilities::generateRandomBytes(5)); + $this->trackid = bin2hex(openssl_random_pseudo_bytes(5)); $this->dirty = true; @@ -408,7 +408,7 @@ class SimpleSAML_Session $this->authData[$authority] = $data; - $this->authToken = SimpleSAML_Utilities::generateID(); + $this->authToken = SimpleSAML\Utils\Random::generateID(); $sessionHandler = SimpleSAML_SessionHandler::getSessionHandler(); if (!$this->transient && (!empty($data['RememberMe']) || $this->rememberMeExpire) && diff --git a/lib/SimpleSAML/SessionHandler.php b/lib/SimpleSAML/SessionHandler.php index 8ad3a13..9586d56 100644 --- a/lib/SimpleSAML/SessionHandler.php +++ b/lib/SimpleSAML/SessionHandler.php @@ -155,7 +155,7 @@ abstract class SimpleSAML_SessionHandler { $params = $this->getCookieParams(); } - SimpleSAML_Utilities::setCookie($name, $value, $params); + \SimpleSAML\Utils\HTTP::setCookie($name, $value, $params); } } diff --git a/lib/SimpleSAML/SessionHandlerCookie.php b/lib/SimpleSAML/SessionHandlerCookie.php index 60b033a..9e47a8a 100644 --- a/lib/SimpleSAML/SessionHandlerCookie.php +++ b/lib/SimpleSAML/SessionHandlerCookie.php @@ -93,7 +93,7 @@ extends SimpleSAML_SessionHandler { * A random session id. */ private static function createSessionID() { - return SimpleSAML_Utilities::stringToHex(SimpleSAML_Utilities::generateRandomBytes(16)); + return bin2hex(openssl_random_pseudo_bytes(16)); } diff --git a/lib/SimpleSAML/SessionHandlerPHP.php b/lib/SimpleSAML/SessionHandlerPHP.php index afb62a6..9857d84 100644 --- a/lib/SimpleSAML/SessionHandlerPHP.php +++ b/lib/SimpleSAML/SessionHandlerPHP.php @@ -68,7 +68,7 @@ class SimpleSAML_SessionHandlerPHP extends SimpleSAML_SessionHandler { public function newSessionId() { $session_cookie_params = session_get_cookie_params(); - if ($session_cookie_params['secure'] && !SimpleSAML_Utilities::isHTTPS()) { + if ($session_cookie_params['secure'] && !\SimpleSAML\Utils\HTTP::isHTTPS()) { throw new SimpleSAML_Error_Exception('Session start with secure cookie not allowed on http.'); } @@ -77,7 +77,7 @@ class SimpleSAML_SessionHandlerPHP extends SimpleSAML_SessionHandler { } /* Generate new (secure) session id. */ - $sessionId = SimpleSAML_Utilities::stringToHex(SimpleSAML_Utilities::generateRandomBytes(16)); + $sessionId = bin2hex(openssl_random_pseudo_bytes(16)); SimpleSAML_Session::createSession($sessionId); if (session_id() !== '') { @@ -105,7 +105,7 @@ class SimpleSAML_SessionHandlerPHP extends SimpleSAML_SessionHandler { $session_cookie_params = session_get_cookie_params(); - if ($session_cookie_params['secure'] && !SimpleSAML_Utilities::isHTTPS()) { + if ($session_cookie_params['secure'] && !\SimpleSAML\Utils\HTTP::isHTTPS()) { throw new SimpleSAML_Error_Exception('Session start with secure cookie not allowed on http.'); } diff --git a/lib/SimpleSAML/Stats.php b/lib/SimpleSAML/Stats.php index ec76a3f..acaf1d8 100644 --- a/lib/SimpleSAML/Stats.php +++ b/lib/SimpleSAML/Stats.php @@ -80,7 +80,7 @@ class SimpleSAML_Stats { /* The ID generation is designed to cluster IDs related in time close together. */ $int_t = (int)$data['time']; - $hd = SimpleSAML_Utilities::generateRandomBytes(16); + $hd = openssl_random_pseudo_bytes(16); $data['_id'] = sprintf('%016x%s', $int_t, bin2hex($hd)); foreach (self::$outputs as $out) { diff --git a/lib/SimpleSAML/Store.php b/lib/SimpleSAML/Store.php index e45d4a7..2ea922d 100644 --- a/lib/SimpleSAML/Store.php +++ b/lib/SimpleSAML/Store.php @@ -47,9 +47,6 @@ abstract class SimpleSAML_Store { self::$instance = new SimpleSAML_Store_SQL(); break; default: - if (strpos($storeType, ':') === FALSE) { - throw new SimpleSAML_Error_Exception('Unknown datastore type: ' . var_export($storeType, TRUE)); - } /* Datastore from module. */ $className = SimpleSAML_Module::resolveClass($storeType, 'Store', 'SimpleSAML_Store'); self::$instance = new $className(); diff --git a/lib/SimpleSAML/Utilities.php b/lib/SimpleSAML/Utilities.php index 44a9fbf..5f0e7cf 100644 --- a/lib/SimpleSAML/Utilities.php +++ b/lib/SimpleSAML/Utilities.php @@ -29,341 +29,100 @@ class SimpleSAML_Utilities { /** - * Will return sp.example.org + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::getSelfHost() instead. */ public static function getSelfHost() { - - $url = self::getBaseURL(); - - $start = strpos($url,'://') + 3; - $length = strcspn($url,'/:',$start); - - return substr($url, $start, $length); - - } - - /** - * Retrieve Host value from $_SERVER environment variables - */ - private static function getServerHost() { - - if (array_key_exists('HTTP_HOST', $_SERVER)) { - $currenthost = $_SERVER['HTTP_HOST']; - } elseif (array_key_exists('SERVER_NAME', $_SERVER)) { - $currenthost = $_SERVER['SERVER_NAME']; - } else { - /* Almost certainly not what you want, but ... */ - $currenthost = 'localhost'; - } - - if(strstr($currenthost, ":")) { - $currenthostdecomposed = explode(":", $currenthost); - $port = array_pop($currenthostdecomposed); - if (!is_numeric($port)) { - array_push($currenthostdecomposed, $port); - } - $currenthost = implode($currenthostdecomposed, ":"); - } - return $currenthost; - + return \SimpleSAML\Utils\HTTP::getSelfHost(); } /** - * Will return https://sp.example.org[:PORT] + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::getSelfURLHost() instead. */ public static function selfURLhost() { - - $url = self::getBaseURL(); - - $start = strpos($url,'://') + 3; - $length = strcspn($url,'/',$start) + $start; - - return substr($url, 0, $length); + return \SimpleSAML\Utils\HTTP::getSelfURLHost(); } /** - * This function checks if we should set a secure cookie. - * - * @return TRUE if the cookie should be secure, FALSE otherwise. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::isHTTPS() instead. */ public static function isHTTPS() { - - $url = self::getBaseURL(); - - $end = strpos($url,'://'); - $protocol = substr($url, 0, $end); - - if ($protocol === 'https') { - return TRUE; - } else { - return FALSE; - } - - } - - /** - * retrieve HTTPS status from $_SERVER environment variables - */ - private static function getServerHTTPS() { - - if(!array_key_exists('HTTPS', $_SERVER)) { - /* Not an https-request. */ - return FALSE; - } - - if($_SERVER['HTTPS'] === 'off') { - /* IIS with HTTPS off. */ - return FALSE; - } - - /* Otherwise, HTTPS will be a non-empty string. */ - return $_SERVER['HTTPS'] !== ''; - + return \SimpleSAML\Utils\HTTP::isHTTPS(); } /** - * Retrieve port number from $_SERVER environment variables - * return it as a string such as ":80" if different from - * protocol default port, otherwise returns an empty string - */ - private static function getServerPort() { - - if (isset($_SERVER["SERVER_PORT"])) { - $portnumber = $_SERVER["SERVER_PORT"]; - } else { - $portnumber = 80; - } - $port = ':' . $portnumber; - - if (self::getServerHTTPS()) { - if ($portnumber == '443') $port = ''; - } else { - if ($portnumber == '80') $port = ''; - } - - return $port; - - } - - /** - * Will return https://sp.example.org/universities/ruc/baz/simplesaml/saml2/SSOService.php + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::getSelfURLNoQuery() instead. */ public static function selfURLNoQuery() { - - $selfURLhost = self::selfURLhost(); - $selfURLhost .= $_SERVER['SCRIPT_NAME']; - if (isset($_SERVER['PATH_INFO'])) { - $selfURLhost .= $_SERVER['PATH_INFO']; - } - return $selfURLhost; + return \SimpleSAML\Utils\HTTP::getSelfURLNoQuery(); } /** - * Will return sp.example.org/ssp/sp1 - * - * Please note this function will return the base URL for the current - * SP, as defined in the global configuration. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::getSelfHostWithPath() instead. */ public static function getSelfHostWithPath() { - - $baseurl = explode("/", self::getBaseURL()); - $elements = array_slice($baseurl, 3 - count($baseurl), count($baseurl) - 4); - $path = implode("/", $elements); - $selfhostwithpath = self::getSelfHost(); - return $selfhostwithpath . "/" . $path; + return \SimpleSAML\Utils\HTTP::getSelfHostWithPath(); } - + + /** - * Will return foo + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::getFirstPathElement() instead. */ public static function getFirstPathElement($trailingslash = true) { - - if (preg_match('|^/(.*?)/|', $_SERVER['SCRIPT_NAME'], $matches)) { - return ($trailingslash ? '/' : '') . $matches[1]; - } - return ''; + return \SimpleSAML\Utils\HTTP::getFirstPathElement($trailingslash); } - - public static function selfURL() { - - $selfURLhost = self::selfURLhost(); - - $requestURI = $_SERVER['REQUEST_URI']; - if ($requestURI[0] !== '/') { - /* We probably have a URL of the form: http://server/. */ - if (preg_match('#^https?://[^/]*(/.*)#i', $requestURI, $matches)) { - $requestURI = $matches[1]; - } - } - - return $selfURLhost . $requestURI; + /** + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::getSelfURL() instead. + */ + public static function selfURL() { + return \SimpleSAML\Utils\HTTP::getSelfURL(); } /** - * Retrieve and return the absolute base URL for the simpleSAMLphp installation. - * - * For example: https://idp.example.org/simplesaml/ - * - * The URL will always end with a '/'. - * - * @return string The absolute base URL for the simpleSAMLphp installation. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::getBaseURL() instead. */ public static function getBaseURL() { - - $globalConfig = SimpleSAML_Configuration::getInstance(); - $baseURL = $globalConfig->getString('baseurlpath', 'simplesaml/'); - - if (preg_match('#^https?://.*/$#D', $baseURL, $matches)) { - /* full URL in baseurlpath, override local server values */ - return $baseURL; - } elseif ( - (preg_match('#^/?([^/]?.*/)$#D', $baseURL, $matches)) || - (preg_match('#^\*(.*)/$#D', $baseURL, $matches)) || - ($baseURL === '')) { - /* get server values */ - - if (self::getServerHTTPS()) { - $protocol = 'https://'; - } else { - $protocol = 'http://'; - } - - $hostname = self::getServerHost(); - $port = self::getServerPort(); - $path = '/' . $globalConfig->getBaseURL(); - - return $protocol.$hostname.$port.$path; - } else { - throw new SimpleSAML_Error_Exception('Invalid value of \'baseurl\' in '. - 'config.php. Valid format is in the form: '. - '[(http|https)://(hostname|fqdn)[:port]]/[path/to/simplesaml/]. '. - 'It must end with a \'/\'.'); - } - + return \SimpleSAML\Utils\HTTP::getBaseURL(); } /** - * Add one or more query parameters to the given URL. - * - * @param $url The URL the query parameters should be added to. - * @param $parameter The query parameters which should be added to the url. This should be - * an associative array. For backwards comaptibility, it can also be a - * query string representing the new parameters. This will write a warning - * to the log. - * @return The URL with the new query parameters. - */ - public static function addURLparameter($url, $parameter) { - - /* For backwards compatibility - allow $parameter to be a string. */ - if(is_string($parameter)) { - /* Print warning to log. */ - $backtrace = debug_backtrace(); - $where = $backtrace[0]['file'] . ':' . $backtrace[0]['line']; - SimpleSAML_Logger::warning( - 'Deprecated use of SimpleSAML_Utilities::addURLparameter at ' . $where . - '. The parameter "$parameter" should now be an array, but a string was passed.'); - - $parameter = self::parseQueryString($parameter); - } - assert('is_array($parameter)'); - - $queryStart = strpos($url, '?'); - if($queryStart === FALSE) { - $oldQuery = array(); - $url .= '?'; - } else { - $oldQuery = substr($url, $queryStart + 1); - if($oldQuery === FALSE) { - $oldQuery = array(); - } else { - $oldQuery = self::parseQueryString($oldQuery); - } - $url = substr($url, 0, $queryStart + 1); - } - - $query = array_merge($oldQuery, $parameter); - $url .= http_build_query($query, '', '&'); - - return $url; + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::addURLParameters() instead. + */ + public static function addURLparameter($url, $parameters) { + return \SimpleSAML\Utils\HTTP::addURLParameters($url, $parameters); } /** - * Check if a URL is valid and is in our list of allowed URLs. - * - * @param string $url The URL to check. - * @param array $trustedSites An optional white list of domains. If none specified, the 'trusted.url.domains' - * configuration directive will be used. - * @return string The normalized URL itself if it is allowed. An empty string if the $url parameter is empty as - * defined by the empty() function. - * @throws SimpleSAML_Error_Exception if the URL is malformed or is not allowed by configuration. + * @deprecated This method will be removed in SSP 2.0. Please use \SimpleSAML\Utils\HTTP::checkURLAllowed() instead. */ public static function checkURLAllowed($url, array $trustedSites = NULL) { - if (empty($url)) { - return ''; - } - $url = self::normalizeURL($url); - - // get the white list of domains - if ($trustedSites === NULL) { - $trustedSites = SimpleSAML_Configuration::getInstance()->getArray('trusted.url.domains', NULL); - if ($trustedSites === NULL) { - $trustedSites = SimpleSAML_Configuration::getInstance()->getArray('redirect.trustedsites', NULL); - } - } - - // validates the URL's host is among those allowed - if ($trustedSites !== NULL) { - assert(is_array($trustedSites)); - preg_match('@^https?://([^/]+)@i', $url, $matches); - $hostname = $matches[1]; - - // add self host to the white list - $self_host = self::getSelfHost(); - $trustedSites[] = $self_host; - - /* Throw exception due to redirection to untrusted site */ - if (!in_array($hostname, $trustedSites)) { - throw new SimpleSAML_Error_Exception('URL not allowed: '.$url); - } - } - return $url; + return \SimpleSAML\Utils\HTTP::checkURLAllowed($url, $trustedSites); } /** - * Get the ID and (optionally) a URL embedded in a StateID, - * in the form 'id:url'. - * - * @param string $stateId The state ID to use. - * @return array A hashed array with the ID and the URL (if any), - * in the 'id' and 'url' keys, respectively. If there's no URL - * in the input parameter, NULL will be returned as the value for - * the 'url' key. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML_Auth_State::parseStateID() instead. */ public static function parseStateID($stateId) { - $tmp = explode(':', $stateId, 2); - $id = $tmp[0]; - $url = NULL; - if (count($tmp) === 2) { - $url = $tmp[1]; - } - return array('id' => $id, 'url' => $url); + return SimpleSAML_Auth_State::parseStateID($stateId); } + /** + * @deprecated This method will be removed in SSP 2.0. + */ public static function checkDateConditions($start=NULL, $end=NULL) { $currentTime = time(); - + if (!empty($start)) { $startTime = SAML2_Utils::xsDateTimeToTimestamp($start); /* Allow for a 10 minute difference in Time */ @@ -381,185 +140,45 @@ class SimpleSAML_Utilities { } + /** + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Random::generateID() instead. + */ public static function generateID() { - return '_' . self::stringToHex(self::generateRandomBytes(21)); + return SimpleSAML\Utils\Random::generateID(); } - + /** - * This function generates a timestamp on the form used by the SAML protocols. - * - * @param $instant The time the timestamp should represent. - * @return The timestamp. + * @deprecated This method will be removed in SSP 2.0. Please use \SimpleSAML\Utils\Time::generateTimestamp() instead. */ public static function generateTimestamp($instant = NULL) { - if($instant === NULL) { - $instant = time(); - } - return gmdate('Y-m-d\TH:i:s\Z', $instant); + return SimpleSAML\Utils\Time::generateTimestamp($instant); } /** - * Interpret a ISO8601 duration value relative to a given timestamp. - * - * @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. + * @deprecated This method will be removed in SSP 2.0. Please use \SimpleSAML\Utils\Time::parseDuration() instead. */ public static function parseDuration($duration, $timestamp = NULL) { - assert('is_string($duration)'); - assert('is_null($timestamp) || is_int($timestamp)'); - - /* 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 Exception('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; + return SimpleSAML\Utils\Time::parseDuration($duration, $timestamp); } /** - * Show and log fatal error message. - * - * This function logs a error message to the error log and shows the - * message to the user. Script execution terminates afterwards. - * - * The error code comes from the errors-dictionary. It can optionally include parameters, which - * will be substituted into the output string. - * - * @param string $trackId The trackid of the user, from $session->getTrackID(). - * @param mixed $errorCode Either a string with the error code, or an array with the error code and - * additional parameters. - * @param Exception $e The exception which caused the error. - * @deprecated + * @deprecated This method will be removed in SSP 2.0. Please raise a SimpleSAML_Error_Error exception instead. */ public static function fatalError($trackId = 'na', $errorCode = null, Exception $e = null) { - throw new SimpleSAML_Error_Error($errorCode, $e); } /** - * Check whether an IP address is part of an CIDR. + * @deprecated This method will be removed in version 2.0. Use SimpleSAML\Utils\Net::ipCIDRcheck() instead. */ static function ipCIDRcheck($cidr, $ip = null) { - if ($ip == null) $ip = $_SERVER['REMOTE_ADDR']; - list ($net, $mask) = explode('/', $cidr); - - 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 already issued) - 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; + return SimpleSAML\Utils\Net::ipCIDRcheck($cidr, $ip); } - /* - * This is a temporary function, holding the redirect() functionality, - * meanwhile we are deprecating the it. - */ private static function _doRedirect($url, $parameters = array()) { assert('is_string($url)'); assert('!empty($url)'); @@ -617,27 +236,8 @@ class SimpleSAML_Utilities { /** - * This function redirects the user to the specified address. - * - * This function will use the "HTTP 303 See Other" redirection if the current request used the POST method and the - * HTTP version is 1.1. Otherwise, a "HTTP 302 Found" redirection will be used. - * - * The function will also generate a simple web page with a clickable link to the target page. - * - * @param string $url The URL we should redirect to. This URL may include query parameters. If this URL is a - * relative URL (starting with '/'), then it will be turned into an absolute URL by prefixing it with the absolute - * URL to the root of the website. - * @param string[] $parameters An array with extra query string parameters which should be appended to the URL. The - * name of the parameter is the array index. The value of the parameter is the value stored in the index. Both the - * name and the value will be urlencoded. If the value is NULL, then the parameter will be encoded as just the - * name, without a value. - * @param string[] $allowed_redirect_hosts An array with a whitelist of hosts for which redirects are allowed. If - * NULL, redirections will be allowed to any host. Otherwise, the host of the $url provided must be present in this - * parameter. If the host is not whitelisted, an exception will be thrown. - * - * @return void This function never returns. - * @deprecated 1.12.0 This function will be removed from the API. Instead, use the redirectTrustedURL or - * redirectUntrustedURL functions accordingly. + * @deprecated 1.12.0 This method will be removed from the API. Instead, use the redirectTrustedURL() or + * redirectUntrustedURL() functions accordingly. */ public static function redirect($url, $parameters = array(), $allowed_redirect_hosts = NULL) { assert('is_string($url)'); @@ -653,395 +253,88 @@ class SimpleSAML_Utilities { } /** - * This function redirects to the specified URL without performing any security checks. Please, do NOT use this - * function with user supplied URLs. - * - * This function will use the "HTTP 303 See Other" redirection if the current request used the POST method and the - * HTTP version is 1.1. Otherwise, a "HTTP 302 Found" redirection will be used. - * - * The function will also generate a simple web page with a clickable link to the target URL. - * - * @param string $url The URL we should redirect to. This URL may include query parameters. If this URL is a - * relative URL (starting with '/'), then it will be turned into an absolute URL by prefixing it with the absolute - * URL to the root of the website. - * @param string[] $parameters An array with extra query string parameters which should be appended to the URL. The - * name of the parameter is the array index. The value of the parameter is the value stored in the index. Both the - * name and the value will be urlencoded. If the value is NULL, then the parameter will be encoded as just the - * name, without a value. - * - * @return void This function never returns. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::redirectTrustedURL() instead. */ public static function redirectTrustedURL($url, $parameters = array()) { - assert('is_string($url)'); - assert('is_array($parameters)'); - - $url = self::normalizeURL($url); - self::_doRedirect($url, $parameters); + \SimpleSAML\Utils\HTTP::redirectTrustedURL($url, $parameters); } /** - * This function redirects to the specified URL after performing the appropriate security checks on it. - * Particularly, it will make sure that the provided URL is allowed by the 'redirect.trustedsites' directive in the - * configuration. - * - * If the aforementioned option is not set or the URL does correspond to a trusted site, it performs a redirection - * to it. If the site is not trusted, an exception will be thrown. - * - * See the redirectTrustedURL function for more details. - * - * @return void This function never returns. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::redirectUntrustedURL() instead. */ public static function redirectUntrustedURL($url, $parameters = array()) { - assert('is_string($url)'); - assert('is_array($parameters)'); - - $url = self::checkURLAllowed($url); - self::_doRedirect($url, $parameters); + \SimpleSAML\Utils\HTTP::redirectUntrustedURL($url, $parameters); } /** - * This function transposes a two-dimensional array, so that - * $a['k1']['k2'] becomes $a['k2']['k1']. - * - * @param $in Input two-dimensional array. - * @return The transposed array. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Arrays::transpose() instead. */ public static function transposeArray($in) { - assert('is_array($in)'); - - $ret = array(); - - foreach($in as $k1 => $a2) { - assert('is_array($a2)'); - - foreach($a2 as $k2 => $v) { - if(!array_key_exists($k2, $ret)) { - $ret[$k2] = array(); - } - - $ret[$k2][$k1] = $v; - } - } - - return $ret; + return SimpleSAML\Utils\Arrays::transpose($in); } /** - * 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 $element The element we should check. - * @param $name The localname the element should have. - * @param $nsURI The namespaceURI the element should have. - * @return TRUE if both namespace and localname matches, FALSE otherwise. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\XML::isDOMElementOfType() instead. */ public static function isDOMElementOfType(DOMNode $element, $name, $nsURI) { - assert('is_string($name)'); - assert('is_string($nsURI)'); - assert('strlen($nsURI) > 0'); - - if (!($element instanceof DOMElement)) { - /* 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 Exception('Unknown namespace shortcut: ' . $nsURI); - } - - /* Expand the shortcut. */ - $nsURI = $shortcuts[$nsURI]; - } - - - if($element->localName !== $name) { - return FALSE; - } - - if($element->namespaceURI !== $nsURI) { - return FALSE; - } - - return TRUE; + return SimpleSAML\Utils\XML::isDOMElementOfType($element, $name, $nsURI); } /** - * 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 isDOMElementOfType function. - * - * @param DOMElement $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. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\XML::getDOMChildren() instead. */ public static function getDOMChildren(DOMElement $element, $localName, $namespaceURI) { - assert('is_string($localName)'); - assert('is_string($namespaceURI)'); - - $ret = array(); - - for($i = 0; $i < $element->childNodes->length; $i++) { - $child = $element->childNodes->item($i); - - /* Skip text nodes and comment elements. */ - if($child instanceof DOMText || $child instanceof DOMComment) { - continue; - } - - if(self::isDOMElementOfType($child, $localName, $namespaceURI) === TRUE) { - $ret[] = $child; - } - } - - return $ret; + return SimpleSAML\Utils\XML::getDOMChildren($element, $localName, $namespaceURI); } /** - * This function extracts the text from DOMElements which should contain - * only text content. - * - * @param $element The element we should extract text from. - * @return The text content of the element. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\XML::getDOMText() instead. */ public static function getDOMText($element) { - assert('$element instanceof DOMElement'); - - $txt = ''; - - for($i = 0; $i < $element->childNodes->length; $i++) { - $child = $element->childNodes->item($i); - if(!($child instanceof DOMText)) { - throw new Exception($element->localName . ' contained a non-text child node.'); - } - - $txt .= $child->wholeText; - } - - $txt = trim($txt); - return $txt; + return SimpleSAML\Utils\XML::getDOMText($element); } /** - * This function parses the Accept-Language http header and returns an associative array with each - * language and the score for that language. - * - * If an language includes a region, then the result will include both the language with the region - * and the language without the region. - * - * The returned array will be in the same order as the input. - * - * @return An associative array with each language and the score for that language. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::getAcceptLanguage() instead. */ public static function getAcceptLanguage() { - - if(!array_key_exists('HTTP_ACCEPT_LANGUAGE', $_SERVER)) { - /* No Accept-Language header - return empty set. */ - return array(); - } - - $languages = explode(',', strtolower($_SERVER['HTTP_ACCEPT_LANGUAGE'])); - - $ret = array(); - - foreach($languages as $l) { - $opts = explode(';', $l); - - $l = trim(array_shift($opts)); /* The language is the first element.*/ - - $q = 1.0; - - /* Iterate over all options, and check for the quality option. */ - foreach($opts as $o) { - $o = explode('=', $o); - if(count($o) < 2) { - /* Skip option with no value. */ - continue; - } - - $name = trim($o[0]); - $value = trim($o[1]); - - if($name === 'q') { - $q = (float)$value; - } - } - - /* Remove the old key to ensure that the element is added to the end. */ - unset($ret[$l]); - - /* Set the quality in the result. */ - $ret[$l] = $q; - - if(strpos($l, '-')) { - /* The language includes a region part. */ - - /* Extract the language without the region. */ - $l = explode('-', $l); - $l = $l[0]; - - /* Add this language to the result (unless it is defined already). */ - if(!array_key_exists($l, $ret)) { - $ret[$l] = $q; - } - } - } - - return $ret; + return \SimpleSAML\Utils\HTTP::getAcceptLanguage(); } /** - * 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. - * - * @param $xml The XML string or document which should be validated. - * @param $schema The schema which should be used. - * @return Returns a string with the errors if validation fails. An empty string is - * returned if validation passes. - * @deprecated + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\XML::isValid() instead. */ public static function validateXML($xml, $schema) { - assert('is_string($xml) || $xml instanceof DOMDocument'); - assert('is_string($schema)'); - - SimpleSAML_XML_Errors::begin(); - - if($xml instanceof DOMDocument) { - $dom = $xml; - $res = TRUE; - } else { - $dom = new DOMDocument; - $res = $dom->loadXML($xml); - } - - if($res) { - - $config = SimpleSAML_Configuration::getInstance(); - $schemaPath = $config->resolvePath('schemas') . '/'; - $schemaFile = $schemaPath . $schema; - - $res = $dom->schemaValidate($schemaFile); - if($res) { - SimpleSAML_XML_Errors::end(); - return ''; - } - - $errorText = "Schema validation failed on XML string:\n"; - } else { - $errorText = "Failed to parse XML string for schema validation:\n"; - } - - $errors = SimpleSAML_XML_Errors::end(); - $errorText .= SimpleSAML_XML_Errors::formatErrors($errors); - - return $errorText; + $result = \SimpleSAML\Utils\XML::isValid($xml, $schema); + return ($result === true) ? '' : $result; } /** - * This function performs some sanity checks on XML documents, and optionally validates them - * against their schema. A warning will be printed to the log if validation fails. - * - * @param $message The message which should be validated, as a string. - * @param $type The type of document - can be either 'saml20', 'saml11' or 'saml-meta'. - * @deprecated + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\XML::checkSAMLMessage() instead. */ public static function validateXMLDocument($message, $type) { - assert('is_string($message)'); - assert($type === 'saml11' || $type === 'saml20' || $type === 'saml-meta'); - - /* A SAML message should not contain a doctype-declaration. */ - if(strpos($message, '<!DOCTYPE') !== FALSE) { - throw new Exception('XML contained a doctype declaration.'); - } - - $enabled = SimpleSAML_Configuration::getInstance()->getBoolean('debug.validatexml', NULL); - if($enabled === NULL) { - /* Fall back to old configuration option. */ - $enabled = SimpleSAML_Configuration::getInstance()->getBoolean('debug.validatesamlmessages', NULL); - if($enabled === NULL) { - /* Fall back to even older configuration option. */ - $enabled = SimpleSAML_Configuration::getInstance()->getBoolean('debug.validatesaml2messages', FALSE); - } - } - - if(!$enabled) { - return; - } - - switch($type) { - case 'saml11': - $result = self::validateXML($message, 'oasis-sstc-saml-schema-protocol-1.1.xsd'); - break; - case 'saml20': - $result = self::validateXML($message, 'saml-schema-protocol-2.0.xsd'); - break; - case 'saml-meta': - $result = self::validateXML($message, 'saml-schema-metadata-2.0.xsd'); - break; - default: - throw new Exception('Invalid message type.'); - } - - if($result !== '') { - SimpleSAML_Logger::warning($result); - } + \SimpleSAML\Utils\XML::checkSAMLMessage($message, $type); } /** - * This function generates a binary string containing random bytes. - * - * It is implemented as a wrapper of the openssl_random_pseudo_bytes function, - * available since PHP 5.3.0. - * - * @param int $length The number of random bytes to return. - * @return string A string of $length random bytes. + * @deprecated This method will be removed in SSP 2.0. Please use openssl_random_pseudo_bytes() instead. */ public static function generateRandomBytes($length) { assert('is_int($length)'); - return openssl_random_pseudo_bytes($length); + return openssl_random_pseudo_bytes($length); } /** - * This function converts a binary string to hexadecimal characters. - * - * @param $bytes Input string. - * @return String with lowercase hexadecimal characters. + * @deprecated This method will be removed in SSP 2.0. Please use bin2hex() instead. */ public static function stringToHex($bytes) { $ret = ''; @@ -1053,262 +346,56 @@ class SimpleSAML_Utilities { /** - * 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 simpleSAMPphp installation. - * - * @param $path The path we should resolve. - * @param $base The base path, where we should search for $path from. Default value is the root - * of the simpleSAMLphp installation. - * @return An absolute path referring to $path. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\System::resolvePath() instead. */ public static function resolvePath($path, $base = NULL) { - if($base === NULL) { - $config = SimpleSAML_Configuration::getInstance(); - $base = $config->getBaseDir(); - } - - /* Remove trailing slashes from $base. */ - while(substr($base, -1) === '/') { - $base = substr($base, 0, -1); - } - - /* Check for absolute path. */ - if(substr($path, 0, 1) === '/') { - /* Absolute 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(substr($ret, -1) !== '/') { - $ret .= '/'; - } - $ret .= $d; - } - } - - return $ret; + return \SimpleSAML\Utils\System::resolvePath($path, $base); } /** - * Resolve a (possibly) relative URL relative to a given base URL. - * - * This function supports these forms of relative URLs: - * ^\w+: Absolute URL - * ^// Same protocol. - * ^/ Same protocol and host. - * ^? Same protocol, host and path, replace query string & fragment - * ^# Same protocol, host, path and query, replace fragment - * The rest: Relative to the base path. - * - * @param $url The relative URL. - * @param $base The base URL. Defaults to the base URL of this installation of simpleSAMLphp. - * @return An absolute URL for the given relative URL. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::resolveURL() instead. */ public static function resolveURL($url, $base = NULL) { - if($base === NULL) { - $base = self::getBaseURL(); - } - - if(!preg_match('/^((((\w+:)\/\/[^\/]+)(\/[^?#]*))(?:\?[^#]*)?)(?:#.*)?/', $base, $baseParsed)) { - throw new Exception('Unable to parse base url: ' . $base); - } - - $baseDir = dirname($baseParsed[5] . 'filename'); - $baseScheme = $baseParsed[4]; - $baseHost = $baseParsed[3]; - $basePath = $baseParsed[2]; - $baseQuery = $baseParsed[1]; - - if(preg_match('$^\w+:$', $url)) { - return $url; - } - - if(substr($url, 0, 2) === '//') { - return $baseScheme . $url; - } - - $firstChar = substr($url, 0, 1); - - if($firstChar === '/') { - return $baseHost . $url; - } - - if($firstChar === '?') { - return $basePath . $url; - } - - if($firstChar === '#') { - return $baseQuery . $url; - } - - - /* We have a relative path. Remove query string/fragment and save it as $tail. */ - $queryPos = strpos($url, '?'); - $fragmentPos = strpos($url, '#'); - if($queryPos !== FALSE || $fragmentPos !== FALSE) { - if($queryPos === FALSE) { - $tailPos = $fragmentPos; - } elseif($fragmentPos === FALSE) { - $tailPos = $queryPos; - } elseif($queryPos < $fragmentPos) { - $tailPos = $queryPos; - } else { - $tailPos = $fragmentPos; - } - - $tail = substr($url, $tailPos); - $dir = substr($url, 0, $tailPos); - } else { - $dir = $url; - $tail = ''; - } - - $dir = self::resolvePath($dir, $baseDir); - - return $baseHost . $dir . $tail; + return \SimpleSAML\Utils\HTTP::resolveURL($url, $base); } /** - * Normalizes a URL to an absolute URL and validate it. - * - * In addition to resolving the URL, this function makes sure that it is - * a link to a http or https site. - * - * @param string $url The relative URL. - * @return string An absolute URL for the given relative URL. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::normalizeURL() instead. */ public static function normalizeURL($url) { - assert('is_string($url)'); - - $url = SimpleSAML_Utilities::resolveURL($url, SimpleSAML_Utilities::selfURL()); - - /* Verify that the URL is to a http or https site. */ - if (!preg_match('@^https?://@i', $url)) { - throw new SimpleSAML_Error_Exception('Invalid URL: ' . $url); - } - - return $url; + return \SimpleSAML\Utils\HTTP::normalizeURL($url); } /** - * Parse a query string into an array. - * - * This function parses a query string into an array, similar to the way the builtin - * 'parse_str' works, except it doesn't handle arrays, and it doesn't do "magic quotes". - * - * Query parameters without values will be set to an empty string. - * - * @param $query_string The query string which should be parsed. - * @return The query string as an associative array. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::parseQueryString() instead. */ public static function parseQueryString($query_string) { - assert('is_string($query_string)'); - - $res = array(); - foreach(explode('&', $query_string) as $param) { - $param = explode('=', $param); - $name = urldecode($param[0]); - if(count($param) === 1) { - $value = ''; - } else { - $value = urldecode($param[1]); - } - - $res[$name] = $value; - } - - return $res; + return \SimpleSAML\Utils\HTTP::parseQueryString($query_string); } /** - * Parse and validate 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 attributes we should parse and validate. - * @return array The parsed attributes. + * @deprecated This method will be removed in SSP 2.0. Please use + * SimpleSAML\Utils\Arrays::normalizeAttributesArray() instead. */ public static function parseAttributes($attributes) { - - if (!is_array($attributes)) { - throw new Exception('Attributes was not an array. Was: ' . var_export($attributes, TRUE)); - } - - $newAttrs = array(); - foreach ($attributes as $name => $values) { - if (!is_string($name)) { - throw new Exception('Invalid attribute name: ' . var_export($name, TRUE)); - } - - if (!is_array($values)) { - $values = array($values); - } - - foreach ($values as $value) { - if (!is_string($value)) { - throw new Exception('Invalid attribute value for attribute ' . $name . - ': ' . var_export($value, TRUE)); - } - } - - $newAttrs[$name] = $values; - } - - return $newAttrs; + return SimpleSAML\Utils\Arrays::normalizeAttributesArray($attributes); } /** - * Retrieve 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. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Config::getSecretSalt() instead. */ public static function getSecretSalt() { - - $secretSalt = SimpleSAML_Configuration::getInstance()->getString('secretsalt'); - if ($secretSalt === 'defaultsecretsalt') { - throw new Exception('The "secretsalt" configuration option must be set to a secret' . - ' value.'); - } - - return $secretSalt; + return SimpleSAML\Utils\Config::getSecretSalt(); } /** - * Retrieve last error message. - * - * This function retrieves the last error message. If no error has occurred, - * '[No error message found]' will be returned. If the required function isn't available, - * '[Cannot get error message]' will be returned. - * - * @return string Last error message. + * @deprecated This method will be removed in SSP 2.0. Please call error_get_last() directly. */ public static function getLastError() { @@ -1326,396 +413,99 @@ class SimpleSAML_Utilities { /** - * 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. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Config::getCertPath() instead. */ public static function resolveCert($path) { - assert('is_string($path)'); - - $globalConfig = SimpleSAML_Configuration::getInstance(); - $base = $globalConfig->getPathValue('certdir', 'cert/'); - return SimpleSAML_Utilities::resolvePath($path, $base); + return \SimpleSAML\Utils\Config::getCertPath($path); } /** - * 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. - * - * 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. (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. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Crypto::loadPublicKey() instead. */ public static function loadPublicKey(SimpleSAML_Configuration $metadata, $required = FALSE, $prefix = '') { - assert('is_bool($required)'); - assert('is_string($prefix)'); - - $keys = $metadata->getPublicKeys(NULL, FALSE, $prefix); - if ($keys !== NULL) { - 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 Exception('No public key / certificate found in metadata.'); - } else { - return NULL; - } + return SimpleSAML\Utils\Crypto::loadPublicKey($metadata, $required, $prefix); } /** - * Load private key from metadata. - * - * This function loads a private key from a metadata array. It searches 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. Default is FALSE. - * @param string $prefix The prefix which should be used when reading from the metadata - * array. Defaults to ''. - * @return array|NULL Extracted private key, or NULL if no private key is present. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Crypto::loadPrivateKey() instead. */ public static function loadPrivateKey(SimpleSAML_Configuration $metadata, $required = FALSE, $prefix = '') { - assert('is_bool($required)'); - assert('is_string($prefix)'); - - $file = $metadata->getString($prefix . 'privatekey', NULL); - if ($file === NULL) { - /* No private key found. */ - if ($required) { - throw new Exception('No private key found in metadata.'); - } else { - return NULL; - } - } - - $file = SimpleSAML_Utilities::resolveCert($file); - $data = @file_get_contents($file); - if ($data === FALSE) { - throw new 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; + return SimpleSAML\Utils\Crypto::loadPrivateKey($metadata, $required, $prefix); } /** - * 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 DOMElement $root The root element which should be formatted. - * @param string $indentBase The indentation this element should be assumed to - * have. Default is an empty string. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\XML::formatDOMElement() instead. */ public static function formatDOMElement(DOMElement $root, $indentBase = '') { - assert(is_string($indentBase)); - - /* 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++) { - $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 text. */ - $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)); + SimpleSAML\Utils\XML::formatDOMElement($root, $indentBase); } /** - * Format an XML string. - * - * This function formats an XML string using the formatDOMElement function. - * - * @param string $xml XML string which should be formatted. - * @param string $indentBase Optional indentation which should be applied to all - * the output. Optional, defaults to ''. - * @return string Formatted string. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\XML::formatXMLString() instead. */ public static function formatXMLString($xml, $indentBase = '') { - assert('is_string($xml)'); - assert('is_string($indentBase)'); - - $doc = new DOMDocument(); - if (!$doc->loadXML($xml)) { - throw new Exception('Error parsing XML string.'); - } - - $root = $doc->firstChild; - self::formatDOMElement($root); - - return $doc->saveXML($root); + return SimpleSAML\Utils\XML::formatXMLString($xml, $indentBase); } - /* - * Input is single value or array, returns an array. + /** + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Arrays::arrayize() instead. */ public static function arrayize($data, $index = 0) { - if (is_array($data)) { - return $data; - } else { - return array($index => $data); - } + return SimpleSAML\Utils\Arrays::arrayize($data, $index); } /** - * Check whether the current user is a admin user. - * - * @return bool TRUE if the current user is a admin user, FALSE if not. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Auth::isAdmin() instead. */ public static function isAdmin() { - - $session = SimpleSAML_Session::getSessionFromRequest(); - - return $session->isValid('admin') || $session->isValid('login-admin'); + return SimpleSAML\Utils\Auth::isAdmin(); } /** - * Retrieve a admin login URL. - * - * @param string|NULL $returnTo The URL the user should arrive on after admin authentication. - * @return string A URL which can be used for admin authentication. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Auth::getAdminLoginURL instead(); */ public static function getAdminLoginURL($returnTo = NULL) { - assert('is_string($returnTo) || is_null($returnTo)'); - - if ($returnTo === NULL) { - $returnTo = SimpleSAML_Utilities::selfURL(); - } - - return SimpleSAML_Module::getModuleURL('core/login-admin.php', array('ReturnTo' => $returnTo)); + return SimpleSAML\Utils\Auth::getAdminLoginURL($returnTo); } /** - * Require admin access for current page. - * - * This is a helper-function for limiting a page to admin access. It will redirect - * the user to a login page if the current user doesn't have admin access. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Auth::requireAdmin() instead. */ 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 Exception('Cannot find "admin" auth source, and admin privileges are required.'); - } + \SimpleSAML\Utils\Auth::requireAdmin(); } /** - * Do a POST redirect to a page. - * - * This function never returns. - * - * @param string $destination The destination URL. - * @param array $post An array of name-value pairs which will be posted. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::submitPOSTData() instead. */ public static function postRedirect($destination, $post) { - assert('is_string($destination)'); - assert('is_array($post)'); - - $config = SimpleSAML_Configuration::getInstance(); - $httpRedirect = $config->getBoolean('enable.http_post', FALSE); - - if ($httpRedirect && preg_match("#^http:#", $destination) && self::isHTTPS()) { - $url = self::createHttpPostRedirectLink($destination, $post); - self::redirect($url); - assert('FALSE'); - } - - $p = new SimpleSAML_XHTML_Template($config, 'post.php'); - $p->data['destination'] = $destination; - $p->data['post'] = $post; - $p->show(); - exit(0); + \SimpleSAML\Utils\HTTP::submitPOSTData($destination, $post); } /** - * Create a link which will POST data. - * - * @param string $destination The destination URL. - * @param array $post The name-value pairs which will be posted to the destination. - * @return string A URL which can be accessed to post the data. + * @deprecated This method will be removed in SSP 2.0. PLease use SimpleSAML\Utils\HTTP::getPOSTRedirectURL() instead. */ public static function createPostRedirectLink($destination, $post) { - assert('is_string($destination)'); - assert('is_array($post)'); - - $config = SimpleSAML_Configuration::getInstance(); - $httpRedirect = $config->getBoolean('enable.http_post', FALSE); - - if ($httpRedirect && preg_match("#^http:#", $destination) && self::isHTTPS()) { - $url = self::createHttpPostRedirectLink($destination, $post); - } else { - $postId = SimpleSAML_Utilities::generateID(); - $postData = array( - 'post' => $post, - 'url' => $destination, - ); - - $session = SimpleSAML_Session::getSessionFromRequest(); - $session->setData('core_postdatalink', $postId, $postData); - - $url = SimpleSAML_Module::getModuleURL('core/postredirect.php', array('RedirId' => $postId)); - } - - return $url; + return \SimpleSAML\Utils\HTTP::getPOSTRedirectURL($destination, $post); } /** - * Create a link which will POST data to HTTP in a secure way. - * - * @param string $destination The destination URL. - * @param array $post The name-value pairs which will be posted to the destination. - * @return string A URL which can be accessed to post the data. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::getPOSTRedirectURL() instead. */ public static function createHttpPostRedirectLink($destination, $post) { assert('is_string($destination)'); assert('is_array($post)'); - $postId = SimpleSAML_Utilities::generateID(); + $postId = SimpleSAML\Utils\Random::generateID(); $postData = array( 'post' => $post, 'url' => $destination, @@ -1724,7 +514,7 @@ class SimpleSAML_Utilities { $session = SimpleSAML_Session::getSessionFromRequest(); $session->setData('core_postdatalink', $postId, $postData); - $redirInfo = base64_encode(self::aesEncrypt($session->getSessionId() . ':' . $postId)); + $redirInfo = base64_encode(SimpleSAML\Utils\Crypto::aesEncrypt($session->getSessionId() . ':' . $postId)); $url = SimpleSAML_Module::getModuleURL('core/postredirect.php', array('RedirInfo' => $redirInfo)); $url = preg_replace("#^https:#", "http:", $url); @@ -1734,181 +524,22 @@ class SimpleSAML_Utilities { /** - * Validate a certificate against a CA file, by using the builtin - * openssl_x509_checkpurpose function - * - * @param string $certificate The certificate, in PEM format. - * @param string $caFile File with trusted certificates, in PEM-format. - * @return boolean|string TRUE on success, or a string with error messages if it failed. - * @deprecated - */ - private static function validateCABuiltIn($certificate, $caFile) { - assert('is_string($certificate)'); - assert('is_string($caFile)'); - - /* Clear openssl errors. */ - while(openssl_error_string() !== FALSE); - - $res = openssl_x509_checkpurpose($certificate, X509_PURPOSE_ANY, array($caFile)); - - $errors = ''; - /* Log errors. */ - while( ($error = openssl_error_string()) !== FALSE) { - $errors .= ' [' . $error . ']'; - } - - if($res !== TRUE) { - return $errors; - } - - return TRUE; - } - - - /** - * Validate the certificate used to sign the XML against a CA file, by using the "openssl verify" command. - * - * This function uses the openssl verify command to verify a certificate, to work around limitations - * on the openssl_x509_checkpurpose function. That function will not work on certificates without a purpose - * set. - * - * @param string $certificate The certificate, in PEM format. - * @param string $caFile File with trusted certificates, in PEM-format. - * @return boolean|string TRUE on success, a string with error messages on failure. - * @deprecated - */ - private static function validateCAExec($certificate, $caFile) { - assert('is_string($certificate)'); - assert('is_string($caFile)'); - - $command = array( - 'openssl', 'verify', - '-CAfile', $caFile, - '-purpose', 'any', - ); - - $cmdline = ''; - foreach($command as $c) { - $cmdline .= escapeshellarg($c) . ' '; - } - - $cmdline .= '2>&1'; - $descSpec = array( - 0 => array('pipe', 'r'), - 1 => array('pipe', 'w'), - ); - $process = proc_open($cmdline, $descSpec, $pipes); - if (!is_resource($process)) { - throw new Exception('Failed to execute verification command: ' . $cmdline); - } - - if (fwrite($pipes[0], $certificate) === FALSE) { - throw new Exception('Failed to write certificate for verification.'); - } - fclose($pipes[0]); - - $out = ''; - while (!feof($pipes[1])) { - $line = trim(fgets($pipes[1])); - if(strlen($line) > 0) { - $out .= ' [' . $line . ']'; - } - } - fclose($pipes[1]); - - $status = proc_close($process); - if ($status !== 0 || $out !== ' [stdin: OK]') { - return $out; - } - - return TRUE; - } - - - /** - * Validate the certificate used to sign the XML against a CA file. - * - * This function throws an exception if unable to validate against the given CA file. - * - * @param string $certificate The certificate, in PEM format. - * @param string $caFile File with trusted certificates, in PEM-format. - * @deprecated + * @deprecated This method will be removed in SSP 2.0. */ public static function validateCA($certificate, $caFile) { - assert('is_string($certificate)'); - assert('is_string($caFile)'); - - if (!file_exists($caFile)) { - throw new Exception('Could not load CA file: ' . $caFile); - } - - SimpleSAML_Logger::debug('Validating certificate against CA file: ' . var_export($caFile, TRUE)); - - $resBuiltin = self::validateCABuiltIn($certificate, $caFile); - if ($resBuiltin !== TRUE) { - SimpleSAML_Logger::debug('Failed to validate with internal function: ' . var_export($resBuiltin, TRUE)); - - $resExternal = self::validateCAExec($certificate, $caFile); - if ($resExternal !== TRUE) { - SimpleSAML_Logger::debug('Failed to validate with external function: ' . var_export($resExternal, TRUE)); - throw new Exception('Could not verify certificate against CA file "' - . $caFile . '". Internal result:' . $resBuiltin . - ' External result:' . $resExternal); - } - } - - SimpleSAML_Logger::debug('Successfully validated certificate.'); + SimpleSAML_XML_Validator::validateCertificate($certificate, $caFile); } /** - * Initialize the timezone. - * - * This function should be called before any calls to date(). + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Time::initTimezone() instead. */ public static function initTimezone() { - static $initialized = FALSE; - - if ($initialized) { - return; - } - - $initialized = TRUE; - - $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.'); - } - return; - } - - /* We don't have a timezone configured. */ - - /* - * The date_default_timezone_get()-function is likely to cause a warning. - * Since we have a custom error handler which logs the errors with a backtrace, - * this error will be logged even if we prefix the function call with '@'. - * Instead we temporarily replace the error handler. - */ - function ignoreError() { - /* Don't do anything with this error. */ - return TRUE; - } - set_error_handler('ignoreError'); - $serverTimezone = date_default_timezone_get(); - restore_error_handler(); - - /* Set the timezone to the default. */ - date_default_timezone_set($serverTimezone); + \SimpleSAML\Utils\Time::initTimezone(); } /** - * Disable the loading of external entities in XML documents to prevent local and - * remote file inclusion attacks. This is in most cases already disabled by default - * in system libraries, but to be safe we explicitly disable it also. + * @deprecated This method will be removed in SSP 2.0. Please use libxml_disable_entity_loader() instead. */ public static function disableXMLEntityLoader() { /* Function only present in PHP >= 5.2.11 while we support 5.2+ */ @@ -1918,90 +549,23 @@ class SimpleSAML_Utilities { } /** - * Atomically write a file. - * - * This is a helper function for safely writing file data atomically. - * It does this by writing the file data to a temporary file, and then - * renaming this to the correct name. - * - * @param string $filename The name of the file. - * @param string $data The data we should write to the file. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\System::writeFile() instead. */ public static function writeFile($filename, $data, $mode=0600) { - assert('is_string($filename)'); - assert('is_string($data)'); - assert('is_numeric($mode)'); - - $tmpFile = $filename . '.new.' . getmypid() . '.' . php_uname('n'); - - $res = @file_put_contents($tmpFile, $data); - if ($res === FALSE) { - throw new SimpleSAML_Error_Exception('Error saving file ' . $tmpFile . - ': ' . SimpleSAML_Utilities::getLastError()); - } - - if (!self::isWindowsOS()) { - $res = chmod($tmpFile, $mode); - if ($res === FALSE) { - unlink($tmpFile); - throw new SimpleSAML_Error_Exception('Error changing file mode ' . $tmpFile . - ': ' . SimpleSAML_Utilities::getLastError()); - } - } - - $res = rename($tmpFile, $filename); - if ($res === FALSE) { - unlink($tmpFile); - throw new SimpleSAML_Error_Exception('Error renaming ' . $tmpFile . ' to ' . - $filename . ': ' . SimpleSAML_Utilities::getLastError()); - } + \SimpleSAML\Utils\System::writeFile($filename, $data, $mode); } /** - * Get temp directory path. - * - * This function retrieves the path to a directory where - * temporary files can be saved. - * - * @return string Path to temp directory, without a trailing '/'. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\System::getTempDir instead. */ public static function getTempDir() { - - $globalConfig = SimpleSAML_Configuration::getInstance(); - - $tempDir = $globalConfig->getString('tempdir', '/tmp/simplesaml'); - - while (substr($tempDir, -1) === '/') { - $tempDir = substr($tempDir, 0, -1); - } - - if (!is_dir($tempDir)) { - $ret = mkdir($tempDir, 0700, TRUE); - if (!$ret) { - throw new SimpleSAML_Error_Exception('Error creating temp dir ' . - var_export($tempDir, TRUE) . ': ' . SimpleSAML_Utilities::getLastError()); - } - } elseif (function_exists('posix_getuid')) { - - /* Check that the owner of the temp diretory is the current user. */ - $stat = lstat($tempDir); - if ($stat['uid'] !== posix_getuid()) { - throw new SimpleSAML_Error_Exception('Temp directory (' . var_export($tempDir, TRUE) . - ') not owned by current user.'); - } - } - - return $tempDir; + return SimpleSAML\Utils\System::getTempDir(); } /** - * Disable reporting of the given log levels. - * - * Every call to this function must be followed by a call to popErrorMask(); - * - * @param int $mask The log levels that should be masked. + * @deprecated This method will be removed in SSP 2.0. */ public static function maskErrors($mask) { assert('is_int($mask)'); @@ -2016,12 +580,9 @@ class SimpleSAML_Utilities { /** - * Pop an error mask. - * - * This function restores the previous error mask. + * @deprecated This method will be removed in SSP 2.0. */ public static function popErrorMask() { - $lastMask = array_pop(self::$logLevelStack); error_reporting($lastMask[0]); self::$logMask = $lastMask[1]; @@ -2029,344 +590,66 @@ class SimpleSAML_Utilities { /** - * Find the default endpoint in an endpoint array. - * - * @param array $endpoints Array with endpoints. - * @param array $bindings 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. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Config\Metadata::getDefaultEndpoint() instead. */ public static function getDefaultEndpoint(array $endpoints, array $bindings = NULL) { - - $firstNotFalse = NULL; - $firstAllowed = NULL; - - /* Look through the endpoint list for acceptable endpoints. */ - foreach ($endpoints as $i => $ep) { - if ($bindings !== NULL && !in_array($ep['Binding'], $bindings, TRUE)) { - /* Unsupported binding. Skip it. */ - continue; - } - - if (array_key_exists('isDefault', $ep)) { - if ($ep['isDefault'] === TRUE) { - /* This is the first endpoitn with isDefault set to TRUE. */ - return $ep; - } - /* isDefault is set to FALSE, but the endpoint is still useable 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 the value of it. - */ - return $firstAllowed; + return \SimpleSAML\Utils\Config\Metadata::getDefaultEndpoint($endpoints, $bindings); } /** - * Check for session cookie, and show missing-cookie page if it is missing. - * - * @param string|NULL $retryURL The URL the user should access to retry the operation. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::checkSessionCookie() instead. */ public static function checkCookie($retryURL = NULL) { - assert('is_string($retryURL) || is_null($retryURL)'); - - $session = SimpleSAML_Session::getSessionFromRequest(); - if ($session->hasSessionCookie()) { - return; - } - - /* We didn't have a session cookie. Redirect to the no-cookie page. */ - - $url = SimpleSAML_Module::getModuleURL('core/no_cookie.php'); - if ($retryURL !== NULL) { - $url = self::addURLParameter($url, array('retryURL' => $retryURL)); - } - self::redirectTrustedURL($url); + \SimpleSAML\Utils\HTTP::checkSessionCookie($retryURL); } /** - * Helper function to log messages that we send or receive. - * - * @param string|DOMElement $message The message, as an XML string or an XML element. - * @param string $type Whether this message is sent or received, encrypted or decrypted. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\XML::debugSAMLMessage() instead. */ public static function debugMessage($message, $type) { - assert('is_string($message) || $message instanceof DOMElement'); - - $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': - SimpleSAML_Logger::debug('Received message:'); - break; - case 'out': - SimpleSAML_Logger::debug('Sending message:'); - break; - case 'decrypt': - SimpleSAML_Logger::debug('Decrypted message:'); - break; - case 'encrypt': - SimpleSAML_Logger::debug('Encrypted message:'); - break; - default: - assert(FALSE); - } - - $str = self::formatXMLString($message); - foreach (explode("\n", $str) as $line) { - SimpleSAML_Logger::debug($line); - } + \SimpleSAML\Utils\XML::debugSAMLMessage($message, $type); } /** - * Helper function to retrieve a file or URL with proxy support. - * - * An exception will be thrown if we are unable to retrieve the data. - * - * @param string $path The path or URL we should fetch. - * @param array $context Extra context options. This parameter is optional. - * @param boolean $getHeaders Whether to also return response headers. Optional. - * @return mixed array if $getHeaders is set, string otherwise + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::fetch() instead. */ public static function fetch($path, $context = array(), $getHeaders = FALSE) { - assert('is_string($path)'); - - $config = SimpleSAML_Configuration::getInstance(); - - $proxy = $config->getString('proxy', NULL); - if ($proxy !== NULL) { - if (!isset($context['http']['proxy'])) { - $context['http']['proxy'] = $proxy; - } - if (!isset($context['http']['request_fulluri'])) { - $context['http']['request_fulluri'] = TRUE; - } - // If the remote endpoint over HTTPS uses the SNI extension - // (Server Name Indication RFC 4366), the proxy could - // introduce a mismatch between the names in the - // Host: HTTP header and the SNI_server_name in TLS - // negotiation (thanks to Cristiano Valli @ GARR-IDEM - // to have pointed this problem). - // See: https://bugs.php.net/bug.php?id=63519 - // These controls will force the same value for both fields. - // Marco Ferrante (marco@csita.unige.it), Nov 2012 - if (preg_match('#^https#i', $path) - && defined('OPENSSL_TLSEXT_SERVER_NAME') - && OPENSSL_TLSEXT_SERVER_NAME) { - // Extract the hostname - $hostname = parse_url($path, PHP_URL_HOST); - if (!empty($hostname)) { - $context['ssl'] = array( - 'SNI_server_name' => $hostname, - 'SNI_enabled' => TRUE, - ); - } - else { - SimpleSAML_Logger::warning('Invalid URL format or local URL used through a proxy'); - } - } - } - - $context = stream_context_create($context); - - $data = file_get_contents($path, FALSE, $context); - if ($data === FALSE) { - throw new SimpleSAML_Error_Exception('Error fetching ' . var_export($path, TRUE) . ':' . self::getLastError()); - } - - // Data and headers. - if ($getHeaders) { - - if (isset($http_response_header)) { - $headers = array(); - foreach($http_response_header as $h) { - if(preg_match('@^HTTP/1\.[01]\s+\d{3}\s+@', $h)) { - $headers = array(); // reset - $headers[0] = $h; - continue; - } - $bits = explode(':', $h, 2); - if(count($bits) === 2) { - $headers[strtolower($bits[0])] = trim($bits[1]); - } - } - } else { - /* No HTTP headers - probably a different protocol, e.g. file. */ - $headers = NULL; - } - - return array($data, $headers); - } - - return $data; + return \SimpleSAML\Utils\HTTP::fetch($path, $context, $getHeaders); } /** - * Function to AES encrypt data. - * - * @param string $clear Data to encrypt. - * @return array The encrypted data and IV. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Crypto::aesEncrypt() instead. */ public static function aesEncrypt($clear) { - assert('is_string($clear)'); - - if (!function_exists("mcrypt_encrypt")) { - throw new Exception("aesEncrypt needs mcrypt php module."); - } - - $enc = MCRYPT_RIJNDAEL_256; - $mode = MCRYPT_MODE_CBC; - - $blockSize = mcrypt_get_block_size($enc, $mode); - $ivSize = mcrypt_get_iv_size($enc, $mode); - $keySize = mcrypt_get_key_size($enc, $mode); - - $key = hash('sha256', self::getSecretSalt(), TRUE); - $key = substr($key, 0, $keySize); - - $len = strlen($clear); - $numpad = $blockSize - ($len % $blockSize); - $clear = str_pad($clear, $len + $numpad, chr($numpad)); - - $iv = self::generateRandomBytes($ivSize); - - $data = mcrypt_encrypt($enc, $key, $clear, $mode, $iv); - - return $iv . $data; + return SimpleSAML\Utils\Crypto::aesEncrypt($clear); } /** - * Function to AES decrypt data. - * - * @param $data Encrypted data. - * @param $iv IV of encrypted data. - * @return string The decrypted data. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\Crypto::aesDecrypt() instead. */ public static function aesDecrypt($encData) { - assert('is_string($encData)'); - - if (!function_exists("mcrypt_encrypt")) { - throw new Exception("aesDecrypt needs mcrypt php module."); - } - - $enc = MCRYPT_RIJNDAEL_256; - $mode = MCRYPT_MODE_CBC; - - $ivSize = mcrypt_get_iv_size($enc, $mode); - $keySize = mcrypt_get_key_size($enc, $mode); - - $key = hash('sha256', self::getSecretSalt(), TRUE); - $key = substr($key, 0, $keySize); - - $iv = substr($encData, 0, $ivSize); - $data = substr($encData, $ivSize); - - $clear = mcrypt_decrypt($enc, $key, $data, $mode, $iv); - - $len = strlen($clear); - $numpad = ord($clear[$len - 1]); - $clear = substr($clear, 0, $len - $numpad); - - return $clear; + return SimpleSAML\Utils\Crypto::aesDecrypt($encData); } /** - * This function checks if we are running on Windows OS. - * - * @return TRUE if we are on Windows OS, FALSE otherwise. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\System::getOS() instead. */ public static function isWindowsOS() { - return substr(strtoupper(PHP_OS),0,3) == 'WIN'; + return SimpleSAML\Utils\System::getOS() === SimpleSAML\Utils\System::WINDOWS; } /** - * Set a cookie. - * - * @param string $name The name of the session 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. + * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML\Utils\HTTP::setCookie() instead. */ public static function setCookie($name, $value, array $params = NULL, $throw = TRUE) { - assert('is_string($name)'); - assert('is_string($value) || is_null($value)'); - - $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()) { - SimpleSAML_Logger::warning('Setting secure cookie on http 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 { - SimpleSAML_Logger::warning('Error setting cookie - headers already sent.'); - } - } + \SimpleSAML\Utils\HTTP::setCookie($name, $value, $params, $throw); } } diff --git a/lib/SimpleSAML/Utils/Arrays.php b/lib/SimpleSAML/Utils/Arrays.php new file mode 100644 index 0000000..a620a11 --- /dev/null +++ b/lib/SimpleSAML/Utils/Arrays.php @@ -0,0 +1,104 @@ +<?php +namespace SimpleSAML\Utils; + +/** + * Array-related utility methods. + * + * @package SimpleSAMLphp + */ +class Arrays +{ + + /** + * Put a non-array variable into an array. + * + * @param array $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); + } + + /** + * 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('Attributes was not an array. Was: '.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 = self::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; + } + + /** + * 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; + } +}
\ No newline at end of file diff --git a/lib/SimpleSAML/Utils/Auth.php b/lib/SimpleSAML/Utils/Auth.php new file mode 100644 index 0000000..089f94e --- /dev/null +++ b/lib/SimpleSAML/Utils/Auth.php @@ -0,0 +1,72 @@ +<?php +namespace SimpleSAML\Utils; + +/** + * 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 = \SimpleSAML\Utils\HTTP::getSelfURL(); + } + + return \SimpleSAML_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.'); + } + } +}
\ No newline at end of file diff --git a/lib/SimpleSAML/Utils/Config.php b/lib/SimpleSAML/Utils/Config.php new file mode 100644 index 0000000..e0c3f57 --- /dev/null +++ b/lib/SimpleSAML/Utils/Config.php @@ -0,0 +1,58 @@ +<?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; + } +}
\ No newline at end of file diff --git a/lib/SimpleSAML/Utils/Config/Metadata.php b/lib/SimpleSAML/Utils/Config/Metadata.php index d85abe4..82c47ba 100644 --- a/lib/SimpleSAML/Utils/Config/Metadata.php +++ b/lib/SimpleSAML/Utils/Config/Metadata.php @@ -1,11 +1,13 @@ <?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 SimpleSAML_Utils_Config_Metadata +class Metadata { /** @@ -13,7 +15,12 @@ class SimpleSAML_Utils_Config_Metadata * @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', + 'contactType', + 'emailAddress', + 'givenName', + 'surName', + 'telephoneNumber', + 'company', ); @@ -22,7 +29,11 @@ class SimpleSAML_Utils_Config_Metadata * @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', + 'technical', + 'support', + 'administrative', + 'billing', + 'other', ); @@ -57,24 +68,28 @@ class SimpleSAML_Utils_Config_Metadata * 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 the contact does not conform to valid configuration rules for contacts. + * @throws \InvalidArgumentException If $contact is neither a string nor null, or the contact does not conform to + * valid configuration rules for contacts. */ public static function getContact($contact) { - assert('is_array($contact) || is_null($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) { + function ($t) { return '"'.$t.'"'; }, self::$VALID_CONTACT_TYPES )); - throw new InvalidArgumentException('"contactType" is mandatory and must be one of '. $types."."); + throw new \InvalidArgumentException('"contactType" is mandatory and must be one of '.$types."."); } // try to fill in givenName and surName from name @@ -100,34 +115,38 @@ class SimpleSAML_Utils_Config_Metadata // 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.'); + ) + ) { + 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.'); + ) + ) { + 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.'); + ) + ) { + 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.'); + !(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.'); + throw new \InvalidArgumentException('Email addresses must be a string and cannot be empty.'); } } } @@ -136,13 +155,14 @@ class SimpleSAML_Utils_Config_Metadata // 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.'); + !(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.'); + throw new \InvalidArgumentException('Telephone numbers must be a string and cannot be empty.'); } } } @@ -152,4 +172,55 @@ class SimpleSAML_Utils_Config_Metadata 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 $i => $ep) { + if ($bindings !== null && !in_array($ep['Binding'], $bindings, true)) { + // unsupported binding, skip it + continue; + } + + if (array_key_exists('isDefault', $ep)) { + 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; + } } diff --git a/lib/SimpleSAML/Utils/Crypto.php b/lib/SimpleSAML/Utils/Crypto.php index 1bf2dd2..e09bbfe 100644 --- a/lib/SimpleSAML/Utils/Crypto.php +++ b/lib/SimpleSAML/Utils/Crypto.php @@ -1,142 +1,334 @@ <?php +namespace SimpleSAML\Utils; + /** - * A class for crypto related functions + * A class for cryptography-related functions * - * @author Dyonisius Visser, TERENA. <visser@terena.org> - * @package simpleSAMLphp + * @package SimpleSAMLphp */ -class SimpleSAML_Utils_Crypto { - - /** - * This function generates a password hash - * @param $password The unencrypted password - * @param $algo The hashing algorithm, capitals, optionally prepended with 'S' (salted) - * @param $salt Optional salt - */ - public static function pwHash($password, $algo, $salt = NULL) { - assert('is_string($algo)'); - assert('is_string($password)'); - - if(in_array(strtolower($algo), hash_algos())) { - $php_algo = strtolower($algo); // 'sha256' etc - // LDAP compatibility - return '{' . preg_replace('/^SHA1$/', 'SHA', $algo) . '}' - .base64_encode(hash($php_algo, $password, TRUE)); - } - - // Salt - if(!$salt) { - // Default 8 byte salt, but 4 byte for LDAP SHA1 hashes - $bytes = ($algo == 'SSHA1') ? 4 : 8; - $salt = SimpleSAML_Utilities::generateRandomBytes($bytes); - } - - if($algo[0] == 'S' && in_array(substr(strtolower($algo),1), hash_algos())) { - $php_algo = substr(strtolower($algo),1); // 'sha256' etc - // Salted hash, with LDAP compatibility - return '{' . preg_replace('/^SSHA1$/', 'SSHA', $algo) . '}' . - base64_encode(hash($php_algo, $password.$salt, TRUE) . $salt); - } - - throw new Exception('Hashing algoritm \'' . strtolower($algo) . '\' not supported'); - - } - - - /** - * This function checks if a password is valid - * @param $crypted Password as appears in password file, optionally prepended with algorithm - * @param $clear Password to check - */ - public static function pwValid($crypted, $clear) { - assert('is_string($crypted)'); - assert('is_string($clear)'); - - // Match algorithm string ('{SSHA256}', '{MD5}') - if(preg_match('/^{(.*?)}(.*)$/', $crypted, $matches)) { - - // LDAP compatibility - $algo = preg_replace('/^(S?SHA)$/', '${1}1', $matches[1]); - - $cryptedpw = $matches[2]; - - if(in_array(strtolower($algo), hash_algos())) { - // Unsalted hash - return ( $crypted == self::pwHash($clear, $algo) ); - } - - if($algo[0] == 'S' && in_array(substr(strtolower($algo),1), hash_algos())) { - $php_algo = substr(strtolower($algo),1); - // Salted hash - $hash_length = strlen(hash($php_algo, 'whatever', TRUE)); - $salt = substr(base64_decode($cryptedpw), $hash_length); - return ( $crypted == self::pwHash($clear, $algo, $salt) ); - } - - throw new Exception('Hashing algoritm \'' . strtolower($algo) . '\' not supported'); - - } else { - return $crypted === $clear; - } - } - - /** - * This function generates an Apache 'apr1' password hash, which uses a modified - * version of MD5: http://httpd.apache.org/docs/2.2/misc/password_encryptions.html - * @param $password The unencrypted password - * @param $salt Optional salt - */ - public static function apr1Md5Hash($password, $salt = NULL) { - assert('is_string($password)'); - - $chars = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - if(!$salt) { - $salt = substr(str_shuffle($allowed_chars), 0, 8); - } - - $len = strlen($password); - $text = $password.'$apr1$'.$salt; - $bin = pack("H32", md5($password.$salt.$password)); - for($i = $len; $i > 0; $i -= 16) { $text .= substr($bin, 0, min(16, $i)); } - for($i = $len; $i > 0; $i >>= 1) { $text .= ($i & 1) ? chr(0) : $password{0}; } - $bin = pack("H32", md5($text)); - for($i = 0; $i < 1000; $i++) { - $new = ($i & 1) ? $password : $bin; - if ($i % 3) $new .= $salt; - if ($i % 7) $new .= $password; - $new .= ($i & 1) ? $bin : $password; - $bin = pack("H32", md5($new)); - } - $tmp= ''; - for ($i = 0; $i < 5; $i++) { - $k = $i + 6; - $j = $i + 12; - if ($j == 16) $j = 5; - $tmp = $bin[$i].$bin[$k].$bin[$j].$tmp; - } - $tmp = chr(0).chr(0).$bin[11].$tmp; - $tmp = strtr( - strrev(substr(base64_encode($tmp), 2)), - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", - $chars - ); - return "$"."apr1"."$".$salt."$".$tmp; - } - - - /** - * This function verifies an Apache 'apr1' password hash - */ - public static function apr1Md5Valid($crypted, $clear) { - assert('is_string($crypted)'); - assert('is_string($clear)'); - $pattern = '/^\$apr1\$([A-Za-z0-9\.\/]{8})\$([A-Za-z0-9\.\/]{22})$/'; - - if(preg_match($pattern, $crypted, $matches)) { - $salt = $matches[1]; - return ( $crypted == self::apr1Md5Hash($clear, $salt) ); - } - return FALSE; - } +class Crypto +{ + + /** + * Decrypt data using AES and the system-wide secret salt as key. + * + * @param string $ciphertext The encrypted data to decrypt. + * + * @return string The decrypted data. + * @htorws \InvalidArgumentException If $ciphertext is not a string. + * @throws \SimpleSAML_Error_Exception If the mcrypt 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) + { + if (!is_string($ciphertext)) { + throw new \InvalidArgumentException('Input parameter "$ciphertext" must be a string.'); + } + if (!function_exists("mcrypt_encrypt")) { + throw new \SimpleSAML_Error_Exception("The mcrypt PHP module is not loaded."); + } + + $enc = MCRYPT_RIJNDAEL_256; + $mode = MCRYPT_MODE_CBC; + + $ivSize = mcrypt_get_iv_size($enc, $mode); + $keySize = mcrypt_get_key_size($enc, $mode); + + $key = hash('sha256', Config::getSecretSalt(), true); + $key = substr($key, 0, $keySize); + + $iv = substr($ciphertext, 0, $ivSize); + $data = substr($ciphertext, $ivSize); + + $clear = mcrypt_decrypt($enc, $key, $data, $mode, $iv); + + $len = strlen($clear); + $numpad = ord($clear[$len - 1]); + $clear = substr($clear, 0, $len - $numpad); + + return $clear; + } + + + /** + * Encrypt data using AES and the system-wide secret salt as key. + * + * @param string $data The data to encrypt. + * + * @return string The encrypted data and IV. + * @throws \InvalidArgumentException If $data is not a string. + * @throws \SimpleSAML_Error_Exception If the mcrypt 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) + { + if (!is_string($data)) { + throw new \InvalidArgumentException('Input parameter "$data" must be a string.'); + } + if (!function_exists("mcrypt_encrypt")) { + throw new \SimpleSAML_Error_Exception('The mcrypt PHP module is not loaded.'); + } + + $enc = MCRYPT_RIJNDAEL_256; + $mode = MCRYPT_MODE_CBC; + + $blockSize = mcrypt_get_block_size($enc, $mode); + $ivSize = mcrypt_get_iv_size($enc, $mode); + $keySize = mcrypt_get_key_size($enc, $mode); + + $key = hash('sha256', Config::getSecretSalt(), true); + $key = substr($key, 0, $keySize); + + $len = strlen($data); + $numpad = $blockSize - ($len % $blockSize); + $data = str_pad($data, $len + $numpad, chr($numpad)); + + $iv = openssl_random_pseudo_bytes($ivSize); + + $data = mcrypt_encrypt($enc, $key, $data, $mode, $iv); + + return $iv.$data; + } + + + /** + * 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 ''. + * + * @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 = '') + { + if (!is_bool($required) || !is_string($prefix)) { + 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; + } + } + + $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. + * + * 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. (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 ($keys !== null) { + 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; + } + } + + + /** + * 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())) { + $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) { // 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())) { + $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'); + } + + + /** + * 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())) { + return $hash === self::pwHash($password, $alg); + } + + // hash w/ salt + if ($alg[0] === 'S' && in_array(substr(strtolower($alg), 1), hash_algos())) { + $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 ($hash === self::pwHash($password, $alg, $salt)); + } + } else { + return $hash === $password; + } + + throw new \SimpleSAML_Error_Exception('Hashing algorithm \''.strtolower($alg).'\' is not supported'); + } } diff --git a/lib/SimpleSAML/Utils/HTTP.php b/lib/SimpleSAML/Utils/HTTP.php new file mode 100644 index 0000000..7518bfa --- /dev/null +++ b/lib/SimpleSAML/Utils/HTTP.php @@ -0,0 +1,1027 @@ +<?php +namespace SimpleSAML\Utils; + + +/** + * HTTP-related utility methods. + * + * @package SimpleSAMLphp + */ +class HTTP +{ + + /** + * Obtain a URL where we can redirect to securely post a form with the given data to a specific destination. + * + * @param string $destination The destination URL. + * @param array $data An associative array containing the data to be posted to $destination. + * + * @return string A URL which allows to securely post a form to $destination. + * + * @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no> + */ + private static function getSecurePOSTRedirectURL($destination, $data) + { + $session = \SimpleSAML_Session::getSessionFromRequest(); + $id = self::savePOSTData($session, $destination, $data); + + // encrypt the session ID and the random ID + $info = base64_encode(Crypto::aesEncrypt($session->getSessionId().':'.$id)); + + $url = \SimpleSAML_Module::getModuleURL('core/postredirect.php', array('RedirInfo' => $info)); + return preg_replace('#^https:#', 'http:', $url); + } + + + /** + * Retrieve Host value from $_SERVER environment variables. + * + * @return string The current host name, including the port if needed. It will use localhost when unable to + * determine the current host. + * + * @author Olav Morken, UNINETT AS <olav.morken@uninett.no> + */ + private static function getServerHost() + { + if (array_key_exists('HTTP_HOST', $_SERVER)) { + $current = $_SERVER['HTTP_HOST']; + } elseif (array_key_exists('SERVER_NAME', $_SERVER)) { + $current = $_SERVER['SERVER_NAME']; + } else { + // almost certainly not what you want, but... + $current = 'localhost'; + } + + if (strstr($current, ":")) { + $decomposed = explode(":", $current); + $port = array_pop($decomposed); + if (!is_numeric($port)) { + array_push($decomposed, $port); + } + $current = implode($decomposed, ":"); + } + return $current; + } + + + /** + * Retrieve HTTPS status from $_SERVER environment variables. + * + * @return boolean True if the request was performed through HTTPS, false otherwise. + * + * @author Olav Morken, UNINETT AS <olav.morken@uninett.no> + */ + private static function getServerHTTPS() + { + if (!array_key_exists('HTTPS', $_SERVER)) { + // not an https-request + return false; + } + + if ($_SERVER['HTTPS'] === 'off') { + // IIS with HTTPS off + return false; + } + + // otherwise, HTTPS will be a non-empty string + return $_SERVER['HTTPS'] !== ''; + } + + + /** + * Retrieve the port number from $_SERVER environment variables. + * + * @return string The port number prepended by a colon, if it is different than the default port for the protocol + * (80 for HTTP, 443 for HTTPS), or an empty string otherwise. + * + * @author Olav Morken, UNINETT AS <olav.morken@uninett.no> + */ + private static function getServerPort() + { + $port = (isset($_SERVER['SERVER_PORT'])) ? $_SERVER['SERVER_PORT'] : '80'; + if (self::getServerHTTPS()) { + if ($port !== '443') { + return ':'.$port; + } + } else { + if ($port !== '80') { + return ':'.$port; + } + } + return ''; + } + + + /** + * This function redirects the user to the specified address. + * + * This function will use the "HTTP 303 See Other" redirection if the current request used the POST method and the + * HTTP version is 1.1. Otherwise, a "HTTP 302 Found" redirection will be used. + * + * The function will also generate a simple web page with a clickable link to the target page. + * + * @param string $url The URL we should redirect to. This URL may include query parameters. If this URL is a + * relative URL (starting with '/'), then it will be turned into an absolute URL by prefixing it with the + * absolute URL to the root of the website. + * @param string[] $parameters An array with extra query string parameters which should be appended to the URL. The + * name of the parameter is the array index. The value of the parameter is the value stored in the index. Both + * the name and the value will be urlencoded. If the value is NULL, then the parameter will be encoded as just + * the name, without a value. + * + * @return void This function never returns. + * @throws \InvalidArgumentException If $url is not a string or is empty, or $parameters is not an array. + * + * @author Olav Morken, UNINETT AS <olav.morken@uninett.no> + * @author Mads Freek Petersen + * @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no> + */ + private static function redirect($url, $parameters = array()) + { + if (!is_string($url) || empty($url) || !is_array($parameters)) { + throw new \InvalidArgumentException('Invalid input parameters.'); + } + if (!empty($parameters)) { + $url = self::addURLParameters($url, $parameters); + } + + /* Set the HTTP result code. This is either 303 See Other or + * 302 Found. HTTP 303 See Other is sent if the HTTP version + * is HTTP/1.1 and the request type was a POST request. + */ + if ($_SERVER['SERVER_PROTOCOL'] === 'HTTP/1.1' && + $_SERVER['REQUEST_METHOD'] === 'POST' + ) { + $code = 303; + } else { + $code = 302; + } + + if (strlen($url) > 2048) { + \SimpleSAML_Logger::warning('Redirecting to a URL longer than 2048 bytes.'); + } + + // set the location header + header('Location: '.$url, true, $code); + + // disable caching of this response + header('Pragma: no-cache'); + header('Cache-Control: no-cache, must-revalidate'); + + // show a minimal web page with a clickable link to the URL + echo '<?xml version="1.0" encoding="UTF-8"?>'."\n"; + echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"'; + echo ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'."\n"; + echo '<html xmlns="http://www.w3.org/1999/xhtml">'."\n"; + echo " <head>\n"; + echo ' <meta http-equiv="content-type" content="text/html; charset=utf-8">'."\n"; + echo " <title>Redirect</title>\n"; + echo " </head>\n"; + echo " <body>\n"; + echo " <h1>Redirect</h1>\n"; + echo ' <p>You were redirected to: <a id="redirlink" href="'.htmlspecialchars($url).'">'; + echo htmlspecialchars($url)."</a>\n"; + echo ' <script type="text/javascript">document.getElementById("redirlink").focus();</script>'."\n"; + echo " </p>\n"; + echo " </body>\n"; + echo '</html>'; + + // end script execution + exit; + } + + + /** + * Save the given HTTP POST data and the destination where it should be posted to a given session. + * + * @param \SimpleSAML_Session $session The session where to temporarily store the data. + * @param string $destination The destination URL where the form should be posted. + * @param array $data An associative array with the data to be posted to $destination. + * + * @return string A random identifier that can be used to retrieve the data from the current session. + * + * @author Andjelko Horvat + * @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no> + */ + private static function savePOSTData(\SimpleSAML_Session $session, $destination, $data) + { + // generate a random ID to avoid replay attacks + $id = Random::generateID(); + $postData = array( + 'post' => $data, + 'url' => $destination, + ); + + // save the post data to the session, tied to the random ID + $session->setData('core_postdatalink', $id, $postData); + + return $id; + } + + + /** + * Add one or more query parameters to the given URL. + * + * @param string $url The URL the query parameters should be added to. + * @param array $parameters The query parameters which should be added to the url. This should be an associative + * array. + * + * @return string The URL with the new query parameters. + * @throws \InvalidArgumentException If $url is not a string or $parameters is not an array. + * + * @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no> + * @author Olav Morken, UNINETT AS <olav.morken@uninett.no> + */ + public static function addURLParameters($url, $parameters) + { + if (!is_string($url) || !is_array($parameters)) { + throw new \InvalidArgumentException('Invalid input parameters.'); + } + + $queryStart = strpos($url, '?'); + if ($queryStart === false) { + $oldQuery = array(); + $url .= '?'; + } else { + $oldQuery = substr($url, $queryStart + 1); + if ($oldQuery === false) { + $oldQuery = array(); + } else { + $oldQuery = self::parseQueryString($oldQuery); + } + $url = substr($url, 0, $queryStart + 1); + } + + $query = array_merge($oldQuery, $parameters); + $url .= http_build_query($query, '', '&'); + + return $url; + } + + + /** + * Check for session cookie, and show missing-cookie page if it is missing. + * + * @param string|NULL $retryURL The URL the user should access to retry the operation. Defaults to null. + * + * @return void If there is a session cookie, nothing will be returned. Otherwise, the user will be redirected to a + * page telling about the missing cookie. + * @throws \InvalidArgumentException If $retryURL is neither a string nor null. + * + * @author Olav Morken, UNINETT AS <olav.morken@uninett.no> + */ + public static function checkSessionCookie($retryURL = null) + { + if (!is_string($retryURL) || !is_null($retryURL)) { + throw new \InvalidArgumentException('Invalid input parameters.'); + } + + $session = \SimpleSAML_Session::getSessionFromRequest(); + if ($session->hasSessionCookie()) { + return; + } + + // we didn't have a session cookie. Redirect to the no-cookie page + + $url = \SimpleSAML_Module::getModuleURL('core/no_cookie.php'); + if ($retryURL !== null) { + $url = self::addURLParameters($url, array('retryURL' => $retryURL)); + } + self::redirectTrustedURL($url); + } + + + /** + * Check if a URL is valid and is in our list of allowed URLs. + * + * @param string $url The URL to check. + * @param array $trustedSites An optional white list of domains. If none specified, the 'trusted.url.domains' + * configuration directive will be used. + * + * @return string The normalized URL itself if it is allowed. An empty string if the $url parameter is empty as + * defined by the empty() function. + * @throws \InvalidArgumentException If the URL is malformed. + * @throws \SimpleSAML_Error_Exception If the URL is not allowed by configuration. + * + * @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no> + */ + public static function checkURLAllowed($url, array $trustedSites = null) + { + if (empty($url)) { + return ''; + } + $url = self::normalizeURL($url); + + // get the white list of domains + if ($trustedSites === null) { + $trustedSites = \SimpleSAML_Configuration::getInstance()->getArray('trusted.url.domains', null); + // TODO: remove this before 2.0 + if ($trustedSites === null) { + $trustedSites = \SimpleSAML_Configuration::getInstance()->getArray('redirect.trustedsites', null); + } + } + + // validates the URL's host is among those allowed + if ($trustedSites !== null) { + assert(is_array($trustedSites)); + preg_match('@^https?://([^/]+)@i', $url, $matches); + $hostname = $matches[1]; + + // add self host to the white list + $self_host = self::getSelfHost(); + $trustedSites[] = $self_host; + + // throw exception due to redirection to untrusted site + if (!in_array($hostname, $trustedSites)) { + throw new \SimpleSAML_Error_Exception('URL not allowed: '.$url); + } + } + return $url; + } + + + /** + * Helper function to retrieve a file or URL with proxy support. + * + * An exception will be thrown if we are unable to retrieve the data. + * + * @param string $url The path or URL we should fetch. + * @param array $context Extra context options. This parameter is optional. + * @param boolean $getHeaders Whether to also return response headers. Optional. + * + * @return mixed array if $getHeaders is set, string otherwise + * @throws \InvalidArgumentException If the input parameters are invalid. + * @throws \SimpleSAML_Error_Exception If the file or URL cannot be retrieved. + * + * @author Andjelko Horvat + * @author Olav Morken, UNINETT AS <olav.morken@uninett.no> + * @author Marco Ferrante, University of Genova <marco@csita.unige.it> + */ + public static function fetch($url, $context = array(), $getHeaders = false) + { + if (!is_string($url)) { + throw new \InvalidArgumentException('Invalid input parameters.'); + } + + $config = \SimpleSAML_Configuration::getInstance(); + + $proxy = $config->getString('proxy', null); + if ($proxy !== null) { + if (!isset($context['http']['proxy'])) { + $context['http']['proxy'] = $proxy; + } + if (!isset($context['http']['request_fulluri'])) { + $context['http']['request_fulluri'] = true; + } + /* + * If the remote endpoint over HTTPS uses the SNI extension (Server Name Indication RFC 4366), the proxy + * could introduce a mismatch between the names in the Host: HTTP header and the SNI_server_name in TLS + * negotiation (thanks to Cristiano Valli @ GARR-IDEM to have pointed this problem). + * See: https://bugs.php.net/bug.php?id=63519 + * These controls will force the same value for both fields. + * Marco Ferrante (marco@csita.unige.it), Nov 2012 + */ + if (preg_match('#^https#i', $url) + && defined('OPENSSL_TLSEXT_SERVER_NAME') + && OPENSSL_TLSEXT_SERVER_NAME + ) { + // extract the hostname + $hostname = parse_url($url, PHP_URL_HOST); + if (!empty($hostname)) { + $context['ssl'] = array( + 'SNI_server_name' => $hostname, + 'SNI_enabled' => true, + ); + } else { + \SimpleSAML_Logger::warning('Invalid URL format or local URL used through a proxy'); + } + } + } + + $context = stream_context_create($context); + $data = file_get_contents($url, false, $context); + if ($data === false) { + $error = error_get_last(); + throw new \SimpleSAML_Error_Exception('Error fetching '.var_export($url, true).':'.$error['message']); + } + + // data and headers. + if ($getHeaders) { + if (isset($http_response_header)) { + $headers = array(); + foreach ($http_response_header as $h) { + if (preg_match('@^HTTP/1\.[01]\s+\d{3}\s+@', $h)) { + $headers = array(); // reset + $headers[0] = $h; + continue; + } + $bits = explode(':', $h, 2); + if (count($bits) === 2) { + $headers[strtolower($bits[0])] = trim($bits[1]); + } + } + } else { + // no HTTP headers, probably a different protocol, e.g. file + $headers = null; + } + return array($data, $headers); + } + + return $data; + } + + + /** + * This function parses the Accept-Language HTTP header and returns an associative array with each language and the + * score for that language. If a language includes a region, then the result will include both the language with + * the region and the language without the region. + * + * The returned array will be in the same order as the input. + * + * @return array An associative array with each language and the score for that language. + * + * @author Olav Morken, UNINETT AS <olav.morken@uninett.no> + */ + public static function getAcceptLanguage() + { + if (!array_key_exists('HTTP_ACCEPT_LANGUAGE', $_SERVER)) { + // no Accept-Language header, return an empty set + return array(); + } + + $languages = explode(',', strtolower($_SERVER['HTTP_ACCEPT_LANGUAGE'])); + + $ret = array(); + + foreach ($languages as $l) { + $opts = explode(';', $l); + + $l = trim(array_shift($opts)); // the language is the first element + + $q = 1.0; + + // iterate over all options, and check for the quality option + foreach ($opts as $o) { + $o = explode('=', $o); + if (count($o) < 2) { + // skip option with no value + continue; + } + + $name = trim($o[0]); + $value = trim($o[1]); + + if ($name === 'q') { + $q = (float) $value; + } + } + + // remove the old key to ensure that the element is added to the end + unset($ret[$l]); + + // set the quality in the result + $ret[$l] = $q; + + if (strpos($l, '-')) { + // the language includes a region part + + // extract the language without the region + $l = explode('-', $l); + $l = $l[0]; + + // add this language to the result (unless it is defined already) + if (!array_key_exists($l, $ret)) { + $ret[$l] = $q; + } + } + } + return $ret; + } + + + /** + * Retrieve the base URL of the SimpleSAMLphp installation. The URL will always end with a '/'. For example: + * https://idp.example.org/simplesaml/ + * + * @return string The absolute base URL for the simpleSAMLphp installation. + * @throws \SimpleSAML_Error_Exception If 'baseurlpath' has an invalid format. + * + * @author Olav Morken, UNINETT AS <olav.morken@uninett.no> + */ + public static function getBaseURL() + { + $globalConfig = \SimpleSAML_Configuration::getInstance(); + $baseURL = $globalConfig->getString('baseurlpath', 'simplesaml/'); + + if (preg_match('#^https?://.*/$#D', $baseURL, $matches)) { + // full URL in baseurlpath, override local server values + return $baseURL; + } elseif ( + (preg_match('#^/?([^/]?.*/)$#D', $baseURL, $matches)) || + (preg_match('#^\*(.*)/$#D', $baseURL, $matches)) || + ($baseURL === '') + ) { + // get server values + $protocol = 'http'; + $protocol .= (self::getServerHTTPS()) ? 's' : ''; + $protocol .= '://'; + + $hostname = self::getServerHost(); + $port = self::getServerPort(); + $path = '/'.$globalConfig->getBaseURL(); + + return $protocol.$hostname.$port.$path; + } else { + throw new \SimpleSAML_Error_Exception('Invalid value for \'baseurlpath\' in '. + 'config.php. Valid format is in the form: '. + '[(http|https)://(hostname|fqdn)[:port]]/[path/to/simplesaml/]. '. + 'It must end with a \'/\'.'); + } + } + + + /** + * Retrieve the first element of the URL path. + * + * @param boolean $trailingslash Whether to add a trailing slash to the element or not. Defaults to true. + * + * @return string The first element of the URL path, with an optional, trailing slash. + * + * @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no> + */ + public static function getFirstPathElement($trailingslash = true) + { + if (preg_match('|^/(.*?)/|', $_SERVER['SCRIPT_NAME'], $matches)) { + return ($trailingslash ? '/' : '').$matches[1]; + } + return ''; + } + + + /** + * Create a link which will POST data. + * + * @param string $destination The destination URL. + * @param array $data The name-value pairs which will be posted to the destination. + * + * @return string A URL which can be accessed to post the data. + * @throws \InvalidArgumentException If $destination is not a string or $data is not an array. + * + * @author Andjelko Horvat + * @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no> + */ + public static function getPOSTRedirectURL($destination, $data) + { + if (!is_string($destination) || !is_array($data)) { + throw new \InvalidArgumentException('Invalid input parameters.'); + } + + $config = \SimpleSAML_Configuration::getInstance(); + $allowed = $config->getBoolean('enable.http_post', false); + + if ($allowed && preg_match("#^http:#", $destination) && self::isHTTPS()) { + // we need to post the data to HTTP + $url = self::getSecurePOSTRedirectURL($destination, $data); + } else { // post the data directly + $session = \SimpleSAML_Session::getSessionFromRequest(); + $id = self::savePOSTData($session, $destination, $data); + $url = \SimpleSAML_Module::getModuleURL('core/postredirect.php', array('RedirId' => $id)); + } + + return $url; + } + + + /** + * Retrieve our own host. + * + * @return string The current host (with non-default ports included). + * + * @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no> + * @author Olav Morken, UNINETT AS <olav.morken@uninett.no> + */ + public static function getSelfHost() + { + $url = self::getBaseURL(); + + $start = strpos($url, '://') + 3; + $length = strcspn($url, '/:', $start); + + return substr($url, $start, $length); + } + + + /** + * Retrieve our own host together with the URL path. Please note this function will return the base URL for the + * current SP, as defined in the global configuration. + * + * @return string The current host (with non-default ports included) plus the URL path. + * + * @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no> + * @author Olav Morken, UNINETT AS <olav.morken@uninett.no> + */ + public static function getSelfHostWithPath() + { + $baseurl = explode("/", self::getBaseURL()); + $elements = array_slice($baseurl, 3 - count($baseurl), count($baseurl) - 4); + $path = implode("/", $elements); + return self::getSelfHost()."/".$path; + } + + + /** + * Retrieve the current, complete URL. + * + * @return string The current URL, including query parameters. + * + * @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no> + * @author Olav Morken, UNINETT AS <olav.morken@uninett.no> + */ + public static function getSelfURL() + { + $url = self::getSelfURLHost(); + $requestURI = $_SERVER['REQUEST_URI']; + if ($requestURI[0] !== '/') { + // we probably have a URL of the form: http://server/ + if (preg_match('#^https?://[^/]*(/.*)#i', $requestURI, $matches)) { + $requestURI = $matches[1]; + } + } + return $url.$requestURI; + } + + + /** + * Retrieve a URL containing the protocol, the current host and optionally, the port number. + * + * @return string The current URL without a URL path or query parameters. + * + * @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no> + * @author Olav Morken, UNINETT AS <olav.morken@uninett.no> + */ + public static function getSelfURLHost() + { + $url = self::getBaseURL(); + $start = strpos($url, '://') + 3; + $length = strcspn($url, '/', $start) + $start; + return substr($url, 0, $length); + } + + + /** + * Retrieve the current URL without the query parameters. + * + * @return string The current URL, not including query parameters. + * + * @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no> + */ + public static function getSelfURLNoQuery() + { + $url = self::getSelfURLHost(); + $url .= $_SERVER['SCRIPT_NAME']; + if (isset($_SERVER['PATH_INFO'])) { + $url .= $_SERVER['PATH_INFO']; + } + return $url; + } + + + /** + * This function checks if we are using HTTPS as protocol. + * + * @return boolean True if the HTTPS is used, false otherwise. + * + * @author Olav Morken, UNINETT AS <olav.morken@uninett.no> + * @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no> + */ + public static function isHTTPS() + { + return strpos(self::getBaseURL(), 'https://') === 0; + } + + + /** + * Normalizes a URL to an absolute URL and validate it. In addition to resolving the URL, this function makes sure + * that it is a link to an http or https site. + * + * @param string $url The relative URL. + * + * @return string An absolute URL for the given relative URL. + * @throws \InvalidArgumentException If $url is not a string or a valid URL. + * + * @author Olav Morken, UNINETT AS <olav.morken@uninett.no> + * @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no> + */ + public static function normalizeURL($url) + { + if (!is_string($url)) { + throw new \InvalidArgumentException('Invalid input parameters.'); + } + + $url = self::resolveURL($url, self::getSelfURL()); + + // verify that the URL is to a http or https site + if (!preg_match('@^https?://@i', $url)) { + throw new \InvalidArgumentException('Invalid URL: '.$url); + } + + return $url; + } + + + /** + * Parse a query string into an array. + * + * This function parses a query string into an array, similar to the way the builtin 'parse_str' works, except it + * doesn't handle arrays, and it doesn't do "magic quotes". + * + * Query parameters without values will be set to an empty string. + * + * @param string $query_string The query string which should be parsed. + * + * @return array The query string as an associative array. + * @throws \InvalidArgumentException If $query_string is not a string. + * + * @author Olav Morken, UNINETT AS <olav.morken@uninett.no> + */ + public static function parseQueryString($query_string) + { + if (!is_string($query_string)) { + throw new \InvalidArgumentException('Invalid input parameters.'); + } + + $res = array(); + foreach (explode('&', $query_string) as $param) { + $param = explode('=', $param); + $name = urldecode($param[0]); + if (count($param) === 1) { + $value = ''; + } else { + $value = urldecode($param[1]); + } + $res[$name] = $value; + } + return $res; + } + + + /** + * This function redirects to the specified URL without performing any security checks. Please, do NOT use this + * function with user supplied URLs. + * + * This function will use the "HTTP 303 See Other" redirection if the current request used the POST method and the + * HTTP version is 1.1. Otherwise, a "HTTP 302 Found" redirection will be used. + * + * The function will also generate a simple web page with a clickable link to the target URL. + * + * @param string $url The URL we should redirect to. This URL may include query parameters. If this URL is a + * relative URL (starting with '/'), then it will be turned into an absolute URL by prefixing it with the absolute + * URL to the root of the website. + * @param string[] $parameters An array with extra query string parameters which should be appended to the URL. The + * name of the parameter is the array index. The value of the parameter is the value stored in the index. Both the + * name and the value will be urlencoded. If the value is NULL, then the parameter will be encoded as just the + * name, without a value. + * + * @return void This function never returns. + * @throws \InvalidArgumentException If $url is not a string or $parameters is not an array. + * + * @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no> + */ + public static function redirectTrustedURL($url, $parameters = array()) + { + if (!is_string($url) || !is_array($parameters)) { + throw new \InvalidArgumentException('Invalid input parameters.'); + } + + $url = self::normalizeURL($url); + self::redirect($url, $parameters); + } + + + /** + * This function redirects to the specified URL after performing the appropriate security checks on it. + * Particularly, it will make sure that the provided URL is allowed by the 'redirect.trustedsites' directive in the + * configuration. + * + * If the aforementioned option is not set or the URL does correspond to a trusted site, it performs a redirection + * to it. If the site is not trusted, an exception will be thrown. + * + * @param string $url The URL we should redirect to. This URL may include query parameters. If this URL is a + * relative URL (starting with '/'), then it will be turned into an absolute URL by prefixing it with the absolute + * URL to the root of the website. + * @param string[] $parameters An array with extra query string parameters which should be appended to the URL. The + * name of the parameter is the array index. The value of the parameter is the value stored in the index. Both the + * name and the value will be urlencoded. If the value is NULL, then the parameter will be encoded as just the + * name, without a value. + * + * @return void This function never returns. + * @throws \InvalidArgumentException If $url is not a string or $parameters is not an array. + * + * @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no> + */ + public static function redirectUntrustedURL($url, $parameters = array()) + { + if (!is_string($url) || !is_array($parameters)) { + throw new \InvalidArgumentException('Invalid input parameters.'); + } + + $url = self::checkURLAllowed($url); + self::redirect($url, $parameters); + } + + + /** + * Resolve a (possibly relative) URL relative to a given base URL. + * + * This function supports these forms of relative URLs: + * - ^\w+: Absolute URL. E.g. "http://www.example.com:port/path?query#fragment". + * - ^// Same protocol. E.g. "//www.example.com:port/path?query#fragment". + * - ^/ Same protocol and host. E.g. "/path?query#fragment". + * - ^? Same protocol, host and path, replace query string & fragment. E.g. "?query#fragment". + * - ^# Same protocol, host, path and query, replace fragment. E.g. "#fragment". + * - The rest: Relative to the base path. + * + * @param string $url The relative URL. + * @param string $base The base URL. Defaults to the base URL of this installation of SimpleSAMLphp. + * + * @return string An absolute URL for the given relative URL. + * @throws \InvalidArgumentException If the base URL cannot be parsed into a valid URL, or the given parameters + * are not strings. + * + * @author Olav Morken, UNINETT AS <olav.morken@uninett.no> + * @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no> + */ + public static function resolveURL($url, $base = null) + { + if ($base === null) { + $base = self::getBaseURL(); + } + + if (!is_string($url) || !is_string($base)) { + throw new \InvalidArgumentException('Invalid input parameters.'); + } + + if (!preg_match('/^((((\w+:)\/\/[^\/]+)(\/[^?#]*))(?:\?[^#]*)?)(?:#.*)?/', $base, $baseParsed)) { + throw new \InvalidArgumentException('Unable to parse base url: '.$base); + } + + $baseDir = dirname($baseParsed[5].'filename'); + $baseScheme = $baseParsed[4]; + $baseHost = $baseParsed[3]; + $basePath = $baseParsed[2]; + $baseQuery = $baseParsed[1]; + + if (preg_match('$^\w+:$', $url)) { + return $url; + } + + if (substr($url, 0, 2) === '//') { + return $baseScheme.$url; + } + + $firstChar = substr($url, 0, 1); + if ($firstChar === '/') { + return $baseHost.$url; + } + if ($firstChar === '?') { + return $basePath.$url; + } + if ($firstChar === '#') { + return $baseQuery.$url; + } + + // we have a relative path. Remove query string/fragment and save it as $tail + $queryPos = strpos($url, '?'); + $fragmentPos = strpos($url, '#'); + if ($queryPos !== false || $fragmentPos !== false) { + if ($queryPos === false) { + $tailPos = $fragmentPos; + } elseif ($fragmentPos === false) { + $tailPos = $queryPos; + } elseif ($queryPos < $fragmentPos) { + $tailPos = $queryPos; + } else { + $tailPos = $fragmentPos; + } + + $tail = substr($url, $tailPos); + $dir = substr($url, 0, $tailPos); + } else { + $dir = $url; + $tail = ''; + } + + $dir = System::resolvePath($dir, $baseDir); + + return $baseHost.$dir.$tail; + } + + + /** + * 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 <jaime.perez@uninett.no> + */ + public static function setCookie($name, $value, $params = null, $throw = true) + { + if (!(is_string($name) && // $name must be a string + (is_string($value) || is_null($value)) && // $value can be a string or null + (is_array($params) || is_null($params)) && // $params can be an array or null + is_bool($throw)) // $throw must be boolean + ) { + 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()) { + \SimpleSAML_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 { + \SimpleSAML_Logger::warning('Error setting cookie: headers already sent.'); + } + } + } + + + /** + * Submit a POST form to a specific destination. + * + * This function never returns. + * + * @param string $destination The destination URL. + * @param array $data An associative array with the data to be posted to $destination. + * + * @throws \InvalidArgumentException If $destination is not a string or $data is not an array. + * + * @author Olav Morken, UNINETT AS <olav.morken@uninett.no> + * @author Andjelko Horvat + * @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no> + */ + public static function submitPOSTData($destination, $data) + { + if (!is_string($destination) || !is_array($data)) { + throw new \InvalidArgumentException('Invalid input parameters.'); + } + + $config = \SimpleSAML_Configuration::getInstance(); + $allowed = $config->getBoolean('enable.http_post', false); + + if ($allowed && preg_match("#^http:#", $destination) && self::isHTTPS()) { + // we need to post the data to HTTP + self::redirect(self::getSecurePOSTRedirectURL($destination, $data)); + } + + $p = new \SimpleSAML_XHTML_Template($config, 'post.php'); + $p->data['destination'] = $destination; + $p->data['post'] = $data; + $p->show(); + exit(0); + } +}
\ No newline at end of file diff --git a/lib/SimpleSAML/Utils/Net.php b/lib/SimpleSAML/Utils/Net.php new file mode 100644 index 0000000..22082b7 --- /dev/null +++ b/lib/SimpleSAML/Utils/Net.php @@ -0,0 +1,82 @@ +<?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, TERENA + * @author Jaime Perez, UNINETT AS <jaime.perez@uninett.no> + */ + static function ipCIDRcheck($cidr, $ip = null) + { + if ($ip === null) { + $ip = $_SERVER['REMOTE_ADDR']; + } + if (strpos($cidr, '/') === false) { + return false; + } + + list ($net, $mask) = explode('/', $cidr); + + 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 already issued) + 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; + } +} diff --git a/lib/SimpleSAML/Utils/Random.php b/lib/SimpleSAML/Utils/Random.php new file mode 100644 index 0000000..fc87dcf --- /dev/null +++ b/lib/SimpleSAML/Utils/Random.php @@ -0,0 +1,25 @@ +<?php +namespace SimpleSAML\Utils; + +/** + * Utility class for random data generation and manipulation. + * + * @package SimpleSAMLphp + */ +class Random +{ + + /** + * Generate a random identifier, 22 bytes long. + * + * @return string A 22-bytes long string with a random, hex 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(21)); + } +}
\ No newline at end of file diff --git a/lib/SimpleSAML/Utils/System.php b/lib/SimpleSAML/Utils/System.php new file mode 100644 index 0000000..8889251 --- /dev/null +++ b/lib/SimpleSAML/Utils/System.php @@ -0,0 +1,199 @@ +<?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, 'WIN')) { + return self::WINDOWS; + } + if (stristr(PHP_OS, 'DARWIN')) { + return self::OSX; + } + 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. + '": '.$error['message']); + } + } 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(); + } + + // remove trailing slashes from $base + while (substr($base, -1) === '/') { + $base = substr($base, 0, -1); + } + + // check for absolute path + if (substr($path, 0, 1) === '/') { + // absolute 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 (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> + */ + 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. + '": '.$error['message']); + } + + if (self::getOS() !== self::WINDOWS) { + if (!chmod($tmpFile, $mode)) { + unlink($tmpFile); + $error = error_get_last(); + throw new \SimpleSAML_Error_Exception('Error changing file mode of "'.$tmpFile. + '": '.$error['message']); + } + } + + if (!rename($tmpFile, $filename)) { + unlink($tmpFile); + $error = error_get_last(); + throw new \SimpleSAML_Error_Exception('Error moving "'.$tmpFile.'" to "'. + $filename.'": '.$error['message']); + } + } +} diff --git a/lib/SimpleSAML/Utils/Time.php b/lib/SimpleSAML/Utils/Time.php new file mode 100644 index 0000000..9898f8b --- /dev/null +++ b/lib/SimpleSAML/Utils/Time.php @@ -0,0 +1,162 @@ +<?php +/** + * Time-related utility methods. + * + * @package SimpleSAMLphp + */ + +namespace SimpleSAML\Utils; + + +class Time +{ + + /** + * 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> + */ + public static function initTimezone() + { + static $initialized = false; + + if ($initialized) { + return; + } + + $initialized = true; + + $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.'); + } + return; + } + // we don't have a timezone configured + + /* + * The date_default_timezone_get() function is likely to cause a warning. + * Since we have a custom error handler which logs the errors with a backtrace, + * this error will be logged even if we prefix the function call with '@'. + * Instead we temporarily replace the error handler. + */ + set_error_handler(function () { + return true; + }); + $serverTimezone = date_default_timezone_get(); + restore_error_handler(); + + // set the timezone to the default + date_default_timezone_set($serverTimezone); + } + + + /** + * Interpret a ISO8601 duration value relative to a given timestamp. + * + * @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; + } +}
\ No newline at end of file diff --git a/lib/SimpleSAML/Utils/XML.php b/lib/SimpleSAML/Utils/XML.php new file mode 100644 index 0000000..bd09a31 --- /dev/null +++ b/lib/SimpleSAML/Utils/XML.php @@ -0,0 +1,428 @@ +<?php +/** + * Utility class for XML and DOM manipulation. + * + * @package SimpleSAMLphp + */ + +namespace SimpleSAML\Utils; + + +class XML +{ + + /** + * This function performs some sanity checks on XML documents, and optionally validates them against their schema + * if the 'debug.validatexml' 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 <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))) { + 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.'); + } + + $enabled = \SimpleSAML_Configuration::getInstance()->getBoolean('debug.validatexml', null); + if (!$enabled) { + 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) { + \SimpleSAML_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. + * + * @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.'); + } + + $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': + \SimpleSAML_Logger::debug('Received message:'); + break; + case 'out': + \SimpleSAML_Logger::debug('Sending message:'); + break; + case 'decrypt': + \SimpleSAML_Logger::debug('Decrypted message:'); + break; + case 'encrypt': + \SimpleSAML_Logger::debug('Encrypted message:'); + break; + default: + assert(false); + } + + $str = self::formatXMLString($message); + foreach (explode("\n", $str) as $line) { + \SimpleSAML_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 \DOMElement $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. + * + * @author Olav Morken, UNINETT AS <olav.morken@uninett.no> + */ + public static function formatDOMElement(\DOMElement $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++) { + $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'); + } + + $doc = new \DOMDocument(); + if (!$doc->loadXML($xml)) { + 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 isDOMElementOfType function. + * + * @param \DOMElement $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(\DOMElement $element, $localName, $namespaceURI) + { + if (!($element instanceof \DOMElement) || !is_string($localName) || !is_string($namespaceURI)) { + throw new \InvalidArgumentException('Invalid input parameters.'); + } + + $ret = array(); + + for ($i = 0; $i < $element->childNodes->length; $i++) { + $child = $element->childNodes->item($i); + + // skip text nodes and comment elements + if ($child instanceof \DOMText || $child instanceof \DOMComment) { + continue; + } + + if (self::isDOMElementOfType($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 \InvalidArgumentException If $element is not an instance of DOMElement. + * @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) + { + if (!($element instanceof \DOMElement)) { + throw new \InvalidArgumentException('Invalid input parameters'); + } + + $txt = ''; + + for ($i = 0; $i < $element->childNodes->length; $i++) { + $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 isDOMElementOfType(\DOMNode $element, $name, $nsURI) + { + if (!($element instanceof \DOMElement) || !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.'); + } + + \SimpleSAML_XML_Errors::begin(); + + if ($xml instanceof \DOMDocument) { + $dom = $xml; + $res = true; + } else { + $dom = new \DOMDocument; + $res = $dom->loadXML($xml); + } + + if ($res) { + + $config = \SimpleSAML_Configuration::getInstance(); + $schemaPath = $config->resolvePath('schemas').'/'; + $schemaFile = $schemaPath.$schema; + + $res = $dom->schemaValidate($schemaFile); + if ($res) { + \SimpleSAML_XML_Errors::end(); + return true; + } + + $errorText = "Schema validation failed on XML string:\n"; + } else { + $errorText = "Failed to parse XML string for schema validation:\n"; + } + + $errors = \SimpleSAML_XML_Errors::end(); + $errorText .= \SimpleSAML_XML_Errors::formatErrors($errors); + + return $errorText; + } +} diff --git a/lib/SimpleSAML/XHTML/EMail.php b/lib/SimpleSAML/XHTML/EMail.php index 67989b1..5b69962 100644 --- a/lib/SimpleSAML/XHTML/EMail.php +++ b/lib/SimpleSAML/XHTML/EMail.php @@ -65,7 +65,7 @@ pre { if ($this->subject == NULL) throw new Exception('EMail field [subject] is required and not set.'); if ($this->body == NULL) throw new Exception('EMail field [body] is required and not set.'); - $random_hash = SimpleSAML_Utilities::stringToHex(SimpleSAML_Utilities::generateRandomBytes(16)); + $random_hash = bin2hex(openssl_random_pseudo_bytes(16)); if (isset($this->from)) $this->headers[]= 'From: ' . $this->from; diff --git a/lib/SimpleSAML/XHTML/IdPDisco.php b/lib/SimpleSAML/XHTML/IdPDisco.php index 87e7db3..8b084f3 100644 --- a/lib/SimpleSAML/XHTML/IdPDisco.php +++ b/lib/SimpleSAML/XHTML/IdPDisco.php @@ -123,7 +123,7 @@ class SimpleSAML_XHTML_IdPDisco { if(!array_key_exists('return', $_GET)) { throw new Exception('Missing parameter: return'); } else { - $this->returnURL = SimpleSAML_Utilities::checkURLAllowed($_GET['return']); + $this->returnURL = \SimpleSAML\Utils\HTTP::checkURLAllowed($_GET['return']); } $this->isPassive = FALSE; @@ -197,7 +197,7 @@ class SimpleSAML_XHTML_IdPDisco { 'httponly' => FALSE, ); - SimpleSAML_Utilities::setCookie($prefixedName, $value, $params, FALSE); + \SimpleSAML\Utils\HTTP::setCookie($prefixedName, $value, $params, FALSE); } @@ -462,8 +462,7 @@ class SimpleSAML_XHTML_IdPDisco { $extDiscoveryStorage = $this->config->getString('idpdisco.extDiscoveryStorage', NULL); if ($extDiscoveryStorage !== NULL) { $this->log('Choice made [' . $idp . '] (Forwarding to external discovery storage)'); - SimpleSAML_Utilities::redirectTrustedURL($extDiscoveryStorage, array( -// $this->returnIdParam => $idp, + \SimpleSAML\Utils\HTTP::redirectTrustedURL($extDiscoveryStorage, array( 'entityID' => $this->spEntityId, 'IdPentityID' => $idp, 'returnIDParam' => $this->returnIdParam, @@ -473,7 +472,7 @@ class SimpleSAML_XHTML_IdPDisco { } else { $this->log('Choice made [' . $idp . '] (Redirecting the user back. returnIDParam=' . $this->returnIdParam . ')'); - SimpleSAML_Utilities::redirectTrustedURL($this->returnURL, array($this->returnIdParam => $idp)); + \SimpleSAML\Utils\HTTP::redirectTrustedURL($this->returnURL, array($this->returnIdParam => $idp)); } return; @@ -481,7 +480,7 @@ class SimpleSAML_XHTML_IdPDisco { if ($this->isPassive) { $this->log('Choice not made. (Redirecting the user back without answer)'); - SimpleSAML_Utilities::redirectTrustedURL($this->returnURL); + \SimpleSAML\Utils\HTTP::redirectTrustedURL($this->returnURL); return; } @@ -495,12 +494,12 @@ class SimpleSAML_XHTML_IdPDisco { $idpList = array_intersect_key($idpList, array_fill_keys($idpintersection, NULL)); } - $idpintersection = array_values($idpintersection); - - if(sizeof($idpintersection) == 1) { - $this->log('Choice made [' . $idpintersection[0] . '] (Redirecting the user back. returnIDParam=' . $this->returnIdParam . ')'); - SimpleSAML_Utilities::redirectTrustedURL($this->returnURL, array($this->returnIdParam => $idpintersection[0])); - } + $idpintersection = array_values($idpintersection); + + if(sizeof($idpintersection) == 1) { + $this->log('Choice made [' . $idpintersection[0] . '] (Redirecting the user back. returnIDParam=' . $this->returnIdParam . ')'); + \SimpleSAML\Utils\HTTP::redirectTrustedURL($this->returnURL, array($this->returnIdParam => $idpintersection[0])); + } /* * Make use of an XHTML template to present the select IdP choice to the user. @@ -523,7 +522,7 @@ class SimpleSAML_XHTML_IdPDisco { $t->data['return'] = $this->returnURL; $t->data['returnIDParam'] = $this->returnIdParam; $t->data['entityID'] = $this->spEntityId; - $t->data['urlpattern'] = htmlspecialchars(SimpleSAML_Utilities::selfURLNoQuery()); + $t->data['urlpattern'] = htmlspecialchars(\SimpleSAML\Utils\HTTP::getSelfURLNoQuery()); $t->data['rememberenabled'] = $this->config->getBoolean('idpdisco.enableremember', FALSE); $t->show(); } diff --git a/lib/SimpleSAML/XHTML/Template.php b/lib/SimpleSAML/XHTML/Template.php index 4186eb9..68275de 100644 --- a/lib/SimpleSAML/XHTML/Template.php +++ b/lib/SimpleSAML/XHTML/Template.php @@ -141,7 +141,7 @@ class SimpleSAML_XHTML_Template { * languages in the header were available. */ private function getHTTPLanguage() { - $languageScore = SimpleSAML_Utilities::getAcceptLanguage(); + $languageScore = \SimpleSAML\Utils\HTTP::getAcceptLanguage(); /* For now we only use the default language map. We may use a configurable language map * in the future. @@ -413,7 +413,6 @@ class SimpleSAML_XHTML_Template { $translated = $this->getTranslation($tagData); -# if (!empty($replacements)){ echo('<pre> [' . $tag . ']'); print_r($replacements); exit; } foreach ($replacements as $k => $v) { /* try to translate if no replacement is given */ if ($v == NULL) $v = $this->t($k); @@ -712,7 +711,7 @@ class SimpleSAML_XHTML_Template { 'httponly' => FALSE, ); - SimpleSAML_Utilities::setCookie($name, $language, $params, FALSE); + \SimpleSAML\Utils\HTTP::setCookie($name, $language, $params, FALSE); } } diff --git a/lib/SimpleSAML/XML/Parser.php b/lib/SimpleSAML/XML/Parser.php index b309666..d73472d 100644 --- a/lib/SimpleSAML/XML/Parser.php +++ b/lib/SimpleSAML/XML/Parser.php @@ -10,11 +10,8 @@ class SimpleSAML_XML_Parser { var $simplexml = null; - - function __construct($xml) { - #parent::construct($xml); + function __construct($xml) {; $this->simplexml = new SimpleXMLElement($xml); - $this->simplexml->registerXPathNamespace('saml2', 'urn:oasis:names:tc:SAML:2.0:assertion'); $this->simplexml->registerXPathNamespace('saml2meta', 'urn:oasis:names:tc:SAML:2.0:metadata'); $this->simplexml->registerXPathNamespace('ds', 'http://www.w3.org/2000/09/xmldsig#'); diff --git a/lib/SimpleSAML/XML/Shib13/AuthnRequest.php b/lib/SimpleSAML/XML/Shib13/AuthnRequest.php index 0d91446..0424245 100644 --- a/lib/SimpleSAML/XML/Shib13/AuthnRequest.php +++ b/lib/SimpleSAML/XML/Shib13/AuthnRequest.php @@ -4,7 +4,7 @@ * The Shibboleth 1.3 Authentication Request. Not part of SAML 1.1, * but an extension using query paramters no XML. * - * @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no> + * @author Andreas Ã…kre Solberg, UNINETT AS. <andreas.solberg@uninett.no> * @package simpleSAMLphp */ class SimpleSAML_XML_Shib13_AuthnRequest { diff --git a/lib/SimpleSAML/XML/Shib13/AuthnResponse.php b/lib/SimpleSAML/XML/Shib13/AuthnResponse.php index 12e6c6d..d228d81 100644 --- a/lib/SimpleSAML/XML/Shib13/AuthnResponse.php +++ b/lib/SimpleSAML/XML/Shib13/AuthnResponse.php @@ -3,7 +3,7 @@ /** * A Shibboleth 1.3 authentication response. * - * @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no> + * @author Andreas Ã…kre Solberg, UNINETT AS. <andreas.solberg@uninett.no> * @package simpleSAMLphp */ class SimpleSAML_XML_Shib13_AuthnResponse { @@ -106,7 +106,7 @@ class SimpleSAML_XML_Shib13_AuthnResponse { $this->validator->validateFingerprint($certFingerprints); } elseif ($md->hasValue('caFile')) { /* Validate against CA. */ - $this->validator->validateCA(SimpleSAML_Utilities::resolveCert($md->getString('caFile'))); + $this->validator->validateCA(\SimpleSAML\Utils\Config::getCertPath($md->getString('caFile'))); } else { throw new SimpleSAML_Error_Exception('Missing certificate in Shibboleth 1.3 IdP Remote metadata for identity provider [' . $issuer . '].'); } @@ -115,7 +115,7 @@ class SimpleSAML_XML_Shib13_AuthnResponse { } - /* Checks if the given node is validated by the signatore on this response. + /* Checks if the given node is validated by the signature on this response. * * Returns: * TRUE if the node is validated or FALSE if not. @@ -212,7 +212,7 @@ class SimpleSAML_XML_Shib13_AuthnResponse { $end = $condition->getAttribute('NotOnOrAfter'); if ($start && $end) { - if (! SimpleSAML_Utilities::checkDateConditions($start, $end)) { + if (!self::checkDateConditions($start, $end)) { error_log('Date check failed ... (from ' . $start . ' to ' . $end . ')'); continue; } @@ -304,16 +304,16 @@ class SimpleSAML_XML_Shib13_AuthnResponse { $scopedAttributes = array(); } - $id = SimpleSAML_Utilities::generateID(); + $id = SimpleSAML\Utils\Random::generateID(); - $issueInstant = SimpleSAML_Utilities::generateTimestamp(); + $issueInstant = SimpleSAML\Utils\Time::generateTimestamp(); // 30 seconds timeskew back in time to allow differing clocks. - $notBefore = SimpleSAML_Utilities::generateTimestamp(time() - 30); + $notBefore = SimpleSAML\Utils\Time::generateTimestamp(time() - 30); - $assertionExpire = SimpleSAML_Utilities::generateTimestamp(time() + 60 * 5);# 5 minutes - $assertionid = SimpleSAML_Utilities::generateID(); + $assertionExpire = SimpleSAML\Utils\Time::generateTimestamp(time() + 60 * 5);# 5 minutes + $assertionid = SimpleSAML\Utils\Random::generateID(); $spEntityId = $sp->getString('entityid'); @@ -321,7 +321,7 @@ class SimpleSAML_XML_Shib13_AuthnResponse { $base64 = $sp->getBoolean('base64attributes', FALSE); $namequalifier = $sp->getString('NameQualifier', $spEntityId); - $nameid = SimpleSAML_Utilities::generateID(); + $nameid = SimpleSAML\Utils\Random::generateID(); $subjectNode = '<Subject>' . '<NameIdentifier' . @@ -427,5 +427,42 @@ class SimpleSAML_XML_Shib13_AuthnResponse { return $attr; } + /** + * Check if we are currently between the given date & time conditions. + * + * Note that this function allows a 10-minute leap from the initial time as marked by $start. + * + * @param string|null $start A SAML2 timestamp marking the start of the period to check. Defaults to null, in which + * case there's no limitations in the past. + * @param string|null $end A SAML2 timestamp marking the end of the period to check. Defaults to null, in which + * case there's no limitations in the future. + * + * @return bool True if the current time belongs to the period specified by $start and $end. False otherwise. + * + * @see \SAML2_Utils::xsDateTimeToTimestamp. + * + * @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no> + * @author Olav Morken, UNINETT AS <olav.morken@uninett.no> + */ + protected static function checkDateConditions($start = null, $end = null) + { + $currentTime = time(); + + if (!empty($start)) { + $startTime = \SAML2_Utils::xsDateTimeToTimestamp($start); + // allow for a 10 minute difference in time + if (($startTime < 0) || (($startTime - 600) > $currentTime)) { + return false; + } + } + if (!empty($end)) { + $endTime = \SAML2_Utils::xsDateTimeToTimestamp($end); + if (($endTime < 0) || ($endTime <= $currentTime)) { + return false; + } + } + return true; + } + } diff --git a/lib/SimpleSAML/XML/Signer.php b/lib/SimpleSAML/XML/Signer.php index 15bf719..d855358 100644 --- a/lib/SimpleSAML/XML/Signer.php +++ b/lib/SimpleSAML/XML/Signer.php @@ -117,7 +117,7 @@ class SimpleSAML_XML_Signer { assert('is_string($file)'); assert('is_string($pass) || is_null($pass)'); - $keyFile = SimpleSAML_Utilities::resolveCert($file); + $keyFile = \SimpleSAML\Utils\Config::getCertPath($file); if (!file_exists($keyFile)) { throw new Exception('Could not find private key file "' . $keyFile . '".'); } @@ -167,7 +167,7 @@ class SimpleSAML_XML_Signer { public function loadCertificate($file) { assert('is_string($file)'); - $certFile = SimpleSAML_Utilities::resolveCert($file); + $certFile = \SimpleSAML\Utils\Config::getCertPath($file); if (!file_exists($certFile)) { throw new Exception('Could not find certificate file "' . $certFile . '".'); } @@ -202,7 +202,7 @@ class SimpleSAML_XML_Signer { public function addCertificate($file) { assert('is_string($file)'); - $certFile = SimpleSAML_Utilities::resolveCert($file); + $certFile = \SimpleSAML\Utils\Config::getCertPath($file); if (!file_exists($certFile)) { throw new Exception('Could not find extra certificate file "' . $certFile . '".'); } diff --git a/lib/SimpleSAML/XML/Validator.php b/lib/SimpleSAML/XML/Validator.php index c973636..b9a9dfd 100644 --- a/lib/SimpleSAML/XML/Validator.php +++ b/lib/SimpleSAML/XML/Validator.php @@ -289,7 +289,134 @@ class SimpleSAML_XML_Validator { throw new Exception('Key used to sign the message was not an X509 certificate.'); } - SimpleSAML_Utilities::validateCA($this->x509Certificate, $caFile); + self::validateCertificate($this->x509Certificate, $caFile); + } + + /** + * Validate a certificate against a CA file, by using the builtin + * openssl_x509_checkpurpose function + * + * @param string $certificate The certificate, in PEM format. + * @param string $caFile File with trusted certificates, in PEM-format. + * @return boolean|string TRUE on success, or a string with error messages if it failed. + * @deprecated + */ + private static function validateCABuiltIn($certificate, $caFile) { + assert('is_string($certificate)'); + assert('is_string($caFile)'); + + /* Clear openssl errors. */ + while(openssl_error_string() !== FALSE); + + $res = openssl_x509_checkpurpose($certificate, X509_PURPOSE_ANY, array($caFile)); + + $errors = ''; + /* Log errors. */ + while( ($error = openssl_error_string()) !== FALSE) { + $errors .= ' [' . $error . ']'; + } + + if($res !== TRUE) { + return $errors; + } + + return TRUE; + } + + + /** + * Validate the certificate used to sign the XML against a CA file, by using the "openssl verify" command. + * + * This function uses the openssl verify command to verify a certificate, to work around limitations + * on the openssl_x509_checkpurpose function. That function will not work on certificates without a purpose + * set. + * + * @param string $certificate The certificate, in PEM format. + * @param string $caFile File with trusted certificates, in PEM-format. + * @return boolean|string TRUE on success, a string with error messages on failure. + * @deprecated + */ + private static function validateCAExec($certificate, $caFile) { + assert('is_string($certificate)'); + assert('is_string($caFile)'); + + $command = array( + 'openssl', 'verify', + '-CAfile', $caFile, + '-purpose', 'any', + ); + + $cmdline = ''; + foreach($command as $c) { + $cmdline .= escapeshellarg($c) . ' '; + } + + $cmdline .= '2>&1'; + $descSpec = array( + 0 => array('pipe', 'r'), + 1 => array('pipe', 'w'), + ); + $process = proc_open($cmdline, $descSpec, $pipes); + if (!is_resource($process)) { + throw new Exception('Failed to execute verification command: ' . $cmdline); + } + + if (fwrite($pipes[0], $certificate) === FALSE) { + throw new Exception('Failed to write certificate for verification.'); + } + fclose($pipes[0]); + + $out = ''; + while (!feof($pipes[1])) { + $line = trim(fgets($pipes[1])); + if(strlen($line) > 0) { + $out .= ' [' . $line . ']'; + } + } + fclose($pipes[1]); + + $status = proc_close($process); + if ($status !== 0 || $out !== ' [stdin: OK]') { + return $out; + } + + return TRUE; + } + + + /** + * Validate the certificate used to sign the XML against a CA file. + * + * This function throws an exception if unable to validate against the given CA file. + * + * @param string $certificate The certificate, in PEM format. + * @param string $caFile File with trusted certificates, in PEM-format. + * @deprecated + */ + public static function validateCertificate($certificate, $caFile) { + assert('is_string($certificate)'); + assert('is_string($caFile)'); + + if (!file_exists($caFile)) { + throw new Exception('Could not load CA file: ' . $caFile); + } + + SimpleSAML_Logger::debug('Validating certificate against CA file: ' . var_export($caFile, TRUE)); + + $resBuiltin = self::validateCABuiltIn($certificate, $caFile); + if ($resBuiltin !== TRUE) { + SimpleSAML_Logger::debug('Failed to validate with internal function: ' . var_export($resBuiltin, TRUE)); + + $resExternal = self::validateCAExec($certificate, $caFile); + if ($resExternal !== TRUE) { + SimpleSAML_Logger::debug('Failed to validate with external function: ' . var_export($resExternal, TRUE)); + throw new Exception('Could not verify certificate against CA file "' + . $caFile . '". Internal result:' . $resBuiltin . + ' External result:' . $resExternal); + } + } + + SimpleSAML_Logger::debug('Successfully validated certificate.'); } } |