diff --git a/settings_example.php b/settings_example.php index 981a21a3..ce5c5f70 100644 --- a/settings_example.php +++ b/settings_example.php @@ -15,6 +15,16 @@ // Ex. http://sp.example.com/ // 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( diff --git a/src/Saml2/Auth.php b/src/Saml2/Auth.php index a860b358..d016fdbb 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..8386cea4 100644 --- a/src/Saml2/LogoutRequest.php +++ b/src/Saml2/LogoutRequest.php @@ -65,10 +65,19 @@ 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(); diff --git a/src/Saml2/LogoutResponse.php b/src/Saml2/LogoutResponse.php index 64e373c1..2574d9f4 100644 --- a/src/Saml2/LogoutResponse.php +++ b/src/Saml2/LogoutResponse.php @@ -71,10 +71,19 @@ 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); diff --git a/src/Saml2/Response.php b/src/Saml2/Response.php index a3c1859a..a1e23621 100644 --- a/src/Saml2/Response.php +++ b/src/Saml2/Response.php @@ -94,11 +94,20 @@ class Response 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); diff --git a/src/Saml2/Settings.php b/src/Saml2/Settings.php index 53261ffb..0baa391f 100644 --- a/src/Saml2/Settings.php +++ b/src/Saml2/Settings.php @@ -35,7 +35,19 @@ class Settings * @var string */ 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 @@ -261,10 +273,16 @@ private function _loadSettingsFromArray(array $settings) if (isset($settings['debug'])) { $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']; @@ -1131,6 +1149,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. @@ -1151,6 +1179,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. diff --git a/src/Saml2/Utils.php b/src/Saml2/Utils.php index 9dfa526a..dfb55011 100644 --- a/src/Saml2/Utils.php +++ b/src/Saml2/Utils.php @@ -37,7 +37,7 @@ class Utils /** * @var bool Control if the `Forwarded-For-*` headers are used */ - private static $_proxyVars = false; + private static $_proxyUsage = false; /** * @var string|null @@ -63,6 +63,11 @@ class Utils * @var string|null */ private static $_baseurlpath; + + /** + * @var string|null + */ + private static $_localpath; /** * This function load an XML string in a save way. @@ -430,20 +435,63 @@ 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,14 +669,12 @@ public static function isHTTPS() public static function getSelfURLNoQuery() { $selfURLNoQuery = self::getSelfURLhost(); - - $infoWithBaseURLPath = self::buildWithBaseURLPath($_SERVER['SCRIPT_NAME']); - if (!empty($infoWithBaseURLPath)) { - $selfURLNoQuery .= $infoWithBaseURLPath; - } else { - $selfURLNoQuery .= $_SERVER['SCRIPT_NAME']; - } - + + $route = self::shiftLocalURLPath($_SERVER['SCRIPT_NAME']); + $route = self::buildWithBaseURLPath($route); + + $selfURLNoQuery .= $route; + if (isset($_SERVER['PATH_INFO'])) { $selfURLNoQuery .= $_SERVER['PATH_INFO']; } @@ -645,22 +691,17 @@ public static function getSelfRoutedURLNoQuery() { $selfURLhost = self::getSelfURLhost(); $route = ''; - 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); if (!empty($infoWithBaseURLPath)) { $route = $infoWithBaseURLPath; } - + $selfRoutedURLNoQuery = $selfURLhost . $route; $pos = strpos($selfRoutedURLNoQuery, "?"); @@ -671,14 +712,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; } @@ -691,20 +735,17 @@ public static function getSelfURL() { $selfURLhost = self::getSelfURLhost(); - $requestURI = ''; + $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; } @@ -736,6 +777,34 @@ protected static function buildWithBaseURLPath($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; + } + } + } + return $result; + } /** * Extract a query param - as it was sent - from $_SERVER[QUERY_STRING]