diff --git a/settings_example.php b/settings_example.php index 981a21a3..4f72f300 100644 --- a/settings_example.php +++ b/settings_example.php @@ -16,6 +16,16 @@ // http://example.com/sp/ 'baseurl' => null, + // Set a base path to the application on the server or local virtual host. + // Could be used when behin a proxy, in the view that process the SAML Message. + // Ex. /my/local/installation/directory/ + 'localUrlPath' => null, + + // If 'useProxy' is true, some Utils methods will take care of the + // $_SERVER["HTTP_X_FORWARDED_PORT"] and $_SERVER['HTTP_X_FORWARDED_PROTO'] + // vars (otherwise they are ignored). + 'useProxy' => false, + // Service Provider Data that we are deploying 'sp' => array( // Identifier of the SP entity (must be a URI) diff --git a/src/Saml2/Auth.php b/src/Saml2/Auth.php index a860b358..373cceee 100644 --- a/src/Saml2/Auth.php +++ b/src/Saml2/Auth.php @@ -174,6 +174,9 @@ class Auth public function __construct(?array $settings = null, bool $spValidationOnly = false) { $this->_settings = new Settings($settings, $spValidationOnly); + if ($this->_settings->proxyUsage()){ + Utils::setProxyUsage(true); + } } /** diff --git a/src/Saml2/LogoutRequest.php b/src/Saml2/LogoutRequest.php index 1e539105..7c40659b 100644 --- a/src/Saml2/LogoutRequest.php +++ b/src/Saml2/LogoutRequest.php @@ -65,11 +65,20 @@ public function __construct(\OneLogin\Saml2\Settings $settings, $request = null, { $this->_settings = $settings; + if ($this->_settings->proxyUsage()){ + Utils::setProxyUsage(true); + } + $baseURL = $this->_settings->getBaseURL(); if (!empty($baseURL)) { Utils::setBaseURL($baseURL); } + $localURLPath = $this->_settings->getLocalURLPath(); + if (!empty($localURLPath)) { + Utils::setLocalURLPath($localURLPath); + } + if (!isset($request) || empty($request)) { $spData = $this->_settings->getSPData(); $idpData = $this->_settings->getIdPData(); diff --git a/src/Saml2/LogoutResponse.php b/src/Saml2/LogoutResponse.php index 64e373c1..86b29e2f 100644 --- a/src/Saml2/LogoutResponse.php +++ b/src/Saml2/LogoutResponse.php @@ -71,11 +71,20 @@ public function __construct(\OneLogin\Saml2\Settings $settings, $response = null { $this->_settings = $settings; + if ($this->_settings->proxyUsage()){ + Utils::setProxyUsage(true); + } + $baseURL = $this->_settings->getBaseURL(); if (!empty($baseURL)) { Utils::setBaseURL($baseURL); } + $localURLPath = $this->_settings->getLocalURLPath(); + if (!empty($localURLPath)) { + Utils::setLocalURLPath($localURLPath); + } + if ($response) { $decoded = base64_decode($response); $inflated = @gzinflate($decoded); diff --git a/src/Saml2/Response.php b/src/Saml2/Response.php index a3c1859a..02eed77c 100644 --- a/src/Saml2/Response.php +++ b/src/Saml2/Response.php @@ -95,11 +95,20 @@ public function __construct(\OneLogin\Saml2\Settings $settings, $response) { $this->_settings = $settings; + if ($this->_settings->proxyUsage()){ + Utils::setProxyUsage(true); + } + $baseURL = $this->_settings->getBaseURL(); if (!empty($baseURL)) { Utils::setBaseURL($baseURL); } + $localURLPath = $this->_settings->getLocalURLPath(); + if (!empty($localURLPath)) { + Utils::setLocalURLPath($localURLPath); + } + $this->response = base64_decode($response); $this->document = new DOMDocument(); diff --git a/src/Saml2/Settings.php b/src/Saml2/Settings.php index 53261ffb..015209b1 100644 --- a/src/Saml2/Settings.php +++ b/src/Saml2/Settings.php @@ -36,6 +36,16 @@ class Settings */ private $_baseurl; + /** + * @var string + */ + private $_localUrlPath; + + /** + * @var bool + */ + private $_useProxy = false; + /** * Strict. If active, PHP Toolkit will reject unsigned or unencrypted messages * if it expects them signed or encrypted. If not, the messages will be accepted @@ -262,10 +272,18 @@ private function _loadSettingsFromArray(array $settings) $this->_debug = $settings['debug']; } + if (isset($settings['useProxy'])) { + $this->_useProxy = $settings['useProxy']; + } + if (isset($settings['baseurl'])) { $this->_baseurl = $settings['baseurl']; } + if (isset($settings['localUrlPath'])) { + $this->_localUrlPath = $settings['localUrlPath']; + } + if (isset($settings['compress'])) { $this->_compress = $settings['compress']; } @@ -1132,6 +1150,16 @@ public function isDebugActive() return $this->_debug; } + /** + * Returns if the app is behind a reverse proxy. + * + * @return bool Proxy usage parameter + */ + public function proxyUsage() + { + return $this->_useProxy; + } + /** * Set a baseurl value. * @@ -1152,6 +1180,26 @@ public function getBaseURL() return $this->_baseurl; } + /** + * Set a baseurl value. + * + * @param string $baseurl Base URL. + */ + public function setLocalURLPath($localurlpath) + { + $this->_localUrlPath = $localurlpath; + } + + /** + * Returns the localUrlPath set on the settings if any. + * + * @return null|string The localUrlPath + */ + public function getLocalURLPath() + { + return $this->_localUrlPath; + } + /** * Sets the IdP certificate. * @@ -1162,4 +1210,4 @@ public function setIdPCert($cert) $this->_idp['x509cert'] = $cert; $this->formatIdPCert(); } -} +} \ No newline at end of file diff --git a/src/Saml2/Utils.php b/src/Saml2/Utils.php index 9dfa526a..0027e18a 100644 --- a/src/Saml2/Utils.php +++ b/src/Saml2/Utils.php @@ -34,10 +34,11 @@ class Utils const RESPONSE_SIGNATURE_XPATH = "/samlp:Response/ds:Signature"; const ASSERTION_SIGNATURE_XPATH = "/samlp:Response/saml:Assertion/ds:Signature"; + /** * @var bool Control if the `Forwarded-For-*` headers are used */ - private static $_proxyVars = false; + private static $_proxyUsage = false; /** * @var string|null @@ -64,6 +65,11 @@ class Utils */ private static $_baseurlpath; + /** + * @var string|null + */ + private static $_localpath; + /** * This function load an XML string in a save way. * Prevent XEE/XXE Attacks @@ -431,19 +437,61 @@ public static function setBaseURL($baseurl) } /** + * Set the local path value. + * + * @param string $path The path of base path of application on server + */ + public static function setLocalURLPath($path) + { + if (empty($path)) { + self::$_localpath = null; + } else if ($path == '/') { + self::$_localpath = '/'; + } else { + self::$_localpath = '/' . trim($path, '/') . '/'; + } + } + + /** + * @return string The local path to be used when constructing URLs + */ + public static function getLocalURLPath() + { + return self::$_localpath; + } + + /** + * @param bool $proxyUsage Whether to use `X-Forwarded-*` headers to determine port/domain/protocol + */ + public static function setProxyUsage($proxyUsage) + { + self::$_proxyUsage = (bool)$proxyUsage; + } + + /** + * @return bool + */ + public static function getProxyUsage() + { + return self::$_proxyUsage; + } + + /** + * For retro-compatibility * @param bool $proxyVars Whether to use `X-Forwarded-*` headers to determine port/domain/protocol */ public static function setProxyVars($proxyVars) { - self::$_proxyVars = (bool)$proxyVars; + self::setProxyUsage($proxyVars); } /** + * For retro-compatibility * @return bool */ public static function getProxyVars() { - return self::$_proxyVars; + return self::getProxyUsage(); } /** @@ -621,18 +669,14 @@ public static function isHTTPS() public static function getSelfURLNoQuery() { $selfURLNoQuery = self::getSelfURLhost(); + $route = self::shiftLocalURLPath($_SERVER['SCRIPT_NAME']); + $route = self::buildWithBaseURLPath($route); - $infoWithBaseURLPath = self::buildWithBaseURLPath($_SERVER['SCRIPT_NAME']); - if (!empty($infoWithBaseURLPath)) { - $selfURLNoQuery .= $infoWithBaseURLPath; - } else { - $selfURLNoQuery .= $_SERVER['SCRIPT_NAME']; - } - + $selfURLNoQuery .= $route; + if (isset($_SERVER['PATH_INFO'])) { $selfURLNoQuery .= $_SERVER['PATH_INFO']; } - return $selfURLNoQuery; } @@ -648,12 +692,8 @@ public static function getSelfRoutedURLNoQuery() if (!empty($_SERVER['REQUEST_URI'])) { $route = $_SERVER['REQUEST_URI']; - if (!empty($_SERVER['QUERY_STRING'])) { - $route = self::strLreplace($_SERVER['QUERY_STRING'], '', $route); - if (substr($route, -1) == '?') { - $route = substr($route, 0, -1); - } - } + $route = self::truncateQueryString($route); + $route = self::shiftLocalURLPath($route); } $infoWithBaseURLPath = self::buildWithBaseURLPath($route); @@ -671,14 +711,17 @@ public static function getSelfRoutedURLNoQuery() return $selfRoutedURLNoQuery; } - public static function strLreplace($search, $replace, $subject) + public static function truncateQueryString($subject) { - $pos = strrpos($subject, $search); - - if ($pos !== false) { - $subject = substr_replace($subject, $replace, $pos, strlen($search)); + if (!empty($_SERVER['QUERY_STRING'])) { + $pos = strrpos($subject, $_SERVER['QUERY_STRING']); + if ($pos !== false) { + $subject = substr_replace($subject, '', $pos, strlen($_SERVER['QUERY_STRING'])); + } + } + if (substr($subject, -1) == '?') { + $subject = substr($subject, 0, -1); } - return $subject; } @@ -694,17 +737,14 @@ public static function getSelfURL() $requestURI = ''; if (!empty($_SERVER['REQUEST_URI'])) { $requestURI = $_SERVER['REQUEST_URI']; - $matches = array(); - if ($requestURI[0] !== '/' && preg_match('#^https?://[^/]*(/.*)#i', $requestURI, $matches)) { - $requestURI = $matches[1]; - } + $requestURI = self::shiftLocalURLPath($requestURI); } $infoWithBaseURLPath = self::buildWithBaseURLPath($requestURI); if (!empty($infoWithBaseURLPath)) { $requestURI = $infoWithBaseURLPath; } - + return $selfURLhost . $requestURI; } @@ -734,6 +774,40 @@ protected static function buildWithBaseURLPath($info) } } } + else{ + $result = $info; + } + return $result; + } + + /** + * Returns the part of the URL without the localPath. + * + * @param string $info Contains path info + * + * @return string + */ + protected static function shiftLocalURLPath($info) + { + $result = '/'; + if (!empty($info)) { + $localURLPath = self::getLocalURLPath(); + if (!empty($localURLPath)) { + $extractedInfo = $info; + if ($localURLPath != '/') { + // Remove base path from the path info. + $extractedInfo = str_replace($localURLPath, '', $info); + } + // Remove starting and ending slash. + $extractedInfo = trim($extractedInfo, '/'); + if (!empty($extractedInfo)) { + $result .= $extractedInfo; + } + } + else{ + $result = $info; + } + } return $result; } diff --git a/tests/.phpunit.result.cache b/tests/.phpunit.result.cache new file mode 100644 index 00000000..8cc49ab7 --- /dev/null +++ b/tests/.phpunit.result.cache @@ -0,0 +1 @@ +{"version":1,"defects":{"OneLogin\\Saml2\\Tests\\UtilsTest::testFormatCert":4,"OneLogin\\Saml2\\Tests\\UtilsTest::testFormatPrivateKey":4,"OneLogin\\Saml2\\Tests\\UtilsTest::testGetSelfURL":3,"OneLogin\\Saml2\\Tests\\UtilsTest::testGenerateNameIdWithSPNameQualifier":4,"OneLogin\\Saml2\\Tests\\UtilsTest::testGenerateNameIdWithoutFormat":4,"OneLogin\\Saml2\\Tests\\UtilsTest::testGenerateNameIdWithoutSPNameQualifier":4,"OneLogin\\Saml2\\Tests\\UtilsTest::testCalculateX509Fingerprint":4,"OneLogin\\Saml2\\Tests\\UtilsTest::testDecryptElement":4,"OneLogin\\Saml2\\Tests\\UtilsTest::testAddSign":4,"OneLogin\\Saml2\\Tests\\UtilsTest::testValidateSign":4,"OneLogin\\Saml2\\Tests\\UtilsTest::testValidateBinarySignIsValid":4,"OneLogin\\Saml2\\Tests\\UtilsTest::testValidateBinarySignIsValidx509certMulti":4,"OneLogin\\Saml2\\Tests\\UtilsTest::testValidateBinarySignSignatureWrong":4,"OneLogin\\Saml2\\Tests\\UtilsTest::testValidateBinarySignCertWrong":4,"OneLogin\\Saml2\\Tests\\UtilsTest::testValidateBinarySignRemovedParam":4,"OneLogin\\Saml2\\Tests\\UtilsTest::testValidateBinarySignNoQueryString":4,"OneLogin\\Saml2\\Tests\\UtilsTest::testValidateBinarySignNoCert":4,"OneLogin\\Saml2\\Tests\\UtilsTest::testValidateBinarySignReqAndRes":4,"OneLogin\\Saml2\\Tests\\UtilsTest::testValidateBinarySignDuplicatedParameters":4},"times":{"OneLogin\\Saml2\\Tests\\UtilsTest::testLoadXML":0.001,"OneLogin\\Saml2\\Tests\\UtilsTest::testXMLAttacks":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testValidateXML":0.008,"OneLogin\\Saml2\\Tests\\UtilsTest::testFormatCert":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testFormatPrivateKey":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testRedirect":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testSetselfhost":0.001,"OneLogin\\Saml2\\Tests\\UtilsTest::testProxyvars":0.001,"OneLogin\\Saml2\\Tests\\UtilsTest::testGetselfhost":0.001,"OneLogin\\Saml2\\Tests\\UtilsTest::testisHTTPS":0.001,"OneLogin\\Saml2\\Tests\\UtilsTest::testGetselfurlhostdoubleport":0.001,"OneLogin\\Saml2\\Tests\\UtilsTest::testGetselfPort":0.001,"OneLogin\\Saml2\\Tests\\UtilsTest::testSetselfprotocol":0.001,"OneLogin\\Saml2\\Tests\\UtilsTest::testSetBaseURLPath":0.001,"OneLogin\\Saml2\\Tests\\UtilsTest::testSetBaseURLPath2":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testSetBaseURL":0.001,"OneLogin\\Saml2\\Tests\\UtilsTest::testSetBaseURL2":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testSetLocalURLPath":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testGetSelfURLhost":0.001,"OneLogin\\Saml2\\Tests\\UtilsTest::testGetSelfURL":0.001,"OneLogin\\Saml2\\Tests\\UtilsTest::testGetSelfURLNoQuery":0.001,"OneLogin\\Saml2\\Tests\\UtilsTest::testGetStatus":0.001,"OneLogin\\Saml2\\Tests\\UtilsTest::testParseDuration":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testParseSAML2Time":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testParseTime2SAML":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testGetExpireTime":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testQuery":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testGenerateNameIdWithSPNameQualifier":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testGenerateNameIdWithoutFormat":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testGenerateNameIdWithoutSPNameQualifier":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testDeleteLocalSession":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testisSessionStarted":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testCalculateX509Fingerprint":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testFormatFingerPrint":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testDecryptElement":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testAddSign":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testValidateSign":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testValidateBinarySignIsValid":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testValidateBinarySignIsValidx509certMulti":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testValidateBinarySignSignatureWrong":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testValidateBinarySignCertWrong":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testValidateBinarySignRemovedParam":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testValidateBinarySignNoQueryString":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testValidateBinarySignNoCert":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testValidateBinarySignReqAndRes":0,"OneLogin\\Saml2\\Tests\\UtilsTest::testValidateBinarySignDuplicatedParameters":0}} \ No newline at end of file diff --git a/tests/src/OneLogin/Saml2/UtilsTest.php b/tests/src/OneLogin/Saml2/UtilsTest.php index 5b75bd18..7c5f571c 100644 --- a/tests/src/OneLogin/Saml2/UtilsTest.php +++ b/tests/src/OneLogin/Saml2/UtilsTest.php @@ -585,6 +585,25 @@ public function testSetBaseURL2() } + /** + * @covers OneLogin\Saml2\Utils::setLocalURLPath + */ + public function testSetLocalURLPath() + { + $_SERVER['HTTP_HOST'] = 'sp.example.com'; + $_SERVER['HTTPS'] = 'https'; + $_SERVER['REQUEST_URI'] = '/localPathToApplication/route.php'; + $_SERVER['SCRIPT_NAME'] = '/localPathToApplication/route.php'; + //unset($_SERVER['PATH_INFO']); + + $expectedUrlNQ = 'https://sp.example.com/route.php'; + + Utils::setLocalURLPath('/localPathToApplication/'); + + $this->assertEquals('sp.example.com', Utils::getSelfHost()); + $this->assertEquals($expectedUrlNQ, Utils::getSelfURLNoQuery()); + } + /** * Tests the getSelfURLhost method of the Utils *