cookie_name = $config->getOptionalString( 'session.phpsession.cookiename', ini_get('session.name') ?: 'PHPSESSID', ); if (session_status() === PHP_SESSION_ACTIVE) { if (session_name() === $this->cookie_name) { Logger::warning( 'There is already a PHP session with the same name as SimpleSAMLphp\'s session, or the ' . "'session.phpsession.cookiename' configuration option is not set. Make sure to set " . "SimpleSAMLphp's cookie name with a value not used by any other applications.", ); } /* * We shouldn't have a session at this point, so it might be an application session. Save the details to * retrieve it later and commit. */ $this->previous_session['cookie_params'] = session_get_cookie_params(); $this->previous_session['id'] = session_id(); $this->previous_session['name'] = session_name(); session_write_close(); } if (empty($this->cookie_name)) { $this->cookie_name = session_name(); } $params = $this->getCookieParams(); if (!headers_sent()) { session_name($this->cookie_name); /** @psalm-suppress InvalidArgument */ session_set_cookie_params([ 'lifetime' => $params['lifetime'], 'path' => $params['path'], 'domain' => $params['domain'], 'secure' => $params['secure'], 'httponly' => $params['httponly'], 'samesite' => $params['samesite'], ]); } $savepath = $config->getOptionalString('session.phpsession.savepath', null); if (!empty($savepath)) { session_save_path($savepath); } } /** * Restore a previously-existing session. * * Use this method to restore a previous PHP session existing before SimpleSAMLphp initialized its own session. * * WARNING: do not use this method directly, unless you know what you are doing. Calling this method directly, * outside of \SimpleSAML\Session, could cause SimpleSAMLphp's session to be lost or mess the application's one. The * session must always be saved properly before calling this method. If you don't understand what this is about, * don't use this method. * */ public function restorePrevious(): void { if (empty($this->previous_session)) { return; // nothing to do here } // close our own session session_write_close(); session_name($this->previous_session['name']); session_set_cookie_params($this->previous_session['cookie_params']); session_id($this->previous_session['id']); $this->previous_session = []; @session_start(); /* * At this point, we have restored a previously-existing session, so we can't continue to use our session here. * Therefore, we need to load our session again in case we need it. We remove this handler from the parent * class so that the handler is initialized again if we ever need to do something with the session. */ parent::$sessionHandler = null; } /** * Create a new session id. * * @return string The new session id. * @throws \Exception */ public function newSessionId(): string { if ($this->hasSessionCookie()) { session_regenerate_id(false); $sessionId = session_id(); } else { // generate new (secure) session id $sid_length = (int) ini_get('session.sid_length'); $sid_bits_per_char = (int) ini_get('session.sid_bits_per_character'); if (($sid_length * $sid_bits_per_char) < 128) { Logger::warning("Unsafe defaults used for sessionId generation!"); } $sessionId = session_create_id(); } if (!$sessionId) { Logger::warning("Secure session ID generation failed, falling back to custom ID generation."); $sessionId = bin2hex(openssl_random_pseudo_bytes(16)); } Session::createSession($sessionId); return $sessionId; } /** * Retrieve the session ID saved in the session cookie, if there's one. * * @return string|null The session id saved in the cookie or null if no session cookie was set. * * @throws \SimpleSAML\Error\Exception If the cookie is marked as secure but we are not using HTTPS. */ public function getCookieSessionId(): ?string { if (!$this->hasSessionCookie()) { // there's no session cookie, can't return ID return null; } if (headers_sent()) { // latest versions of PHP don't allow loading a session when output sent, get the ID from the cookie return $_COOKIE[$this->cookie_name]; } // do not rely on session_id() as it can return the ID of a previous session. Get it from the cookie instead. session_id($_COOKIE[$this->cookie_name]); $session_cookie_params = session_get_cookie_params(); $httpUtils = new Utils\HTTP(); if ($session_cookie_params['secure'] && !$httpUtils->isSecureCookieAllowed()) { throw new Error\Exception('Session start with secure cookie not allowed on http (except on localhost).'); } @session_start(); return session_id(); } /** * Retrieve the session cookie name. * * @return string The session cookie name. */ public function getSessionCookieName(): string { return $this->cookie_name; } /** * Save the current session to the PHP session array. * * @param \SimpleSAML\Session $session The session object we should save. */ public function saveSession(Session $session): void { $_SESSION['SimpleSAMLphp_SESSION'] = serialize($session); } /** * Load the session from the PHP session array. * * @param string|null $sessionId The ID of the session we should load, or null to use the default. * * @return \SimpleSAML\Session|null The session object, or null if it doesn't exist. * * @throws \SimpleSAML\Error\Exception If it wasn't possible to disable session cookies or we are trying to load a * @throws \Exception * PHP session with a specific identifier and it doesn't match with the current session identifier. */ public function loadSession(?string $sessionId = null): ?Session { if ($sessionId !== session_id()) { throw new Error\Exception('Cannot load PHP session with a specific ID.'); } elseif (session_id() === '') { $this->getCookieSessionId(); } if (!isset($_SESSION['SimpleSAMLphp_SESSION'])) { return null; } $session = $_SESSION['SimpleSAMLphp_SESSION']; Assert::string($session); try { $session = unserialize($session); } catch (\Throwable $e) { Logger::warning('Session load failed using unserialize().' . 'If you have just upgraded this might be ok. ' . 'If not there might be an issue with your storage. ' . $e->getMessage()); $session = null; # sometimes deserializing fails, so we throw it away } return ($session !== false) ? $session : null; } /** * Check whether the session cookie is set. * * This function will only return false if is is certain that the cookie isn't set. * * @return boolean True if it was set, false otherwise. */ public function hasSessionCookie(): bool { return array_key_exists($this->cookie_name, $_COOKIE); } /** * Get the cookie parameters that should be used for session cookies. * * This function contains some adjustments from the default to provide backwards-compatibility. * * @return array The cookie parameters for our sessions. * @link http://www.php.net/manual/en/function.session-get-cookie-params.php * * @throws \SimpleSAML\Error\Exception If both 'session.phpsession.limitedpath' and 'session.cookie.path' options * are set at the same time in the configuration. * @throws \Exception */ public function getCookieParams(): array { $config = Configuration::getInstance(); $ret = parent::getCookieParams(); if ($config->hasValue('session.phpsession.limitedpath') && $config->hasValue('session.cookie.path')) { throw new Error\Exception( 'You cannot set both the session.phpsession.limitedpath and session.cookie.path options.', ); } elseif ($config->hasValue('session.phpsession.limitedpath')) { $ret['path'] = $config->getOptionalBoolean( 'session.phpsession.limitedpath', false, ) ? $config->getBasePath() : '/'; } $ret['httponly'] = $config->getOptionalBoolean('session.phpsession.httponly', true); return $ret; } /** * Set a session cookie. * * @param string $sessionName The name of the session. * @param string|null $sessionID The session ID to use. Set to null to delete the cookie. * @param array|null $cookieParams Additional parameters to use for the session cookie. * * @throws \SimpleSAML\Error\CannotSetCookie If we can't set the cookie. * @throws \SimpleSAML\Error\CriticalConfigurationError */ public function setCookie(string $sessionName, ?string $sessionID, ?array $cookieParams = null): void { if ($cookieParams === null) { $cookieParams = session_get_cookie_params(); } $httpUtils = new Utils\HTTP(); if ($cookieParams['secure'] && !$httpUtils->isSecureCookieAllowed()) { throw new Error\CannotSetCookie( 'Setting secure cookie on plain HTTP (except on localhost) is not allowed.', Error\CannotSetCookie::SECURE_COOKIE, ); } if (headers_sent()) { throw new Error\CannotSetCookie( 'Headers already sent.', Error\CannotSetCookie::HEADERS_SENT, ); } if (session_id() !== '') { // session already started, close it session_write_close(); } if (array_key_exists('expire', $cookieParams)) { // Similar to the Utils\HTTP::setCookie() if (isset($cookieParams['expire'])) { $expire = intval($cookieParams['expire']); $cookieParams['lifetime'] = $expire; unset($cookieParams['expire']); } } /** @psalm-suppress InvalidArgument */ session_set_cookie_params($cookieParams); session_id(strval($sessionID)); @session_start(); } }