131bc7874SGreg Roach<?php 231bc7874SGreg Roach/** 331bc7874SGreg Roach * webtrees: online genealogy 41062a142SGreg Roach * Copyright (C) 2018 webtrees development team 531bc7874SGreg Roach * This program is free software: you can redistribute it and/or modify 631bc7874SGreg Roach * it under the terms of the GNU General Public License as published by 731bc7874SGreg Roach * the Free Software Foundation, either version 3 of the License, or 831bc7874SGreg Roach * (at your option) any later version. 931bc7874SGreg Roach * This program is distributed in the hope that it will be useful, 1031bc7874SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 1131bc7874SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1231bc7874SGreg Roach * GNU General Public License for more details. 1331bc7874SGreg Roach * You should have received a copy of the GNU General Public License 1431bc7874SGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>. 1531bc7874SGreg Roach */ 1676692c8bSGreg Roachnamespace Fisharebest\Webtrees; 1731bc7874SGreg Roach 184c891c40SGreg Roachuse Symfony\Component\HttpFoundation\Request; 194c891c40SGreg Roach 2031bc7874SGreg Roach/** 2142af74e7SGreg Roach * Session handling 2231bc7874SGreg Roach */ 23c1010edaSGreg Roachclass Session 24c1010edaSGreg Roach{ 2531bc7874SGreg Roach /** 2631bc7874SGreg Roach * Start a session 2731bc7874SGreg Roach */ 28*08df3d18SGreg Roach public static function start() 29c1010edaSGreg Roach { 30*08df3d18SGreg Roach $lifetime = (int) Site::getPreference('SESSION_TIME', '7200'); 31*08df3d18SGreg Roach $domain = ''; 32*08df3d18SGreg Roach $path = parse_url(WT_BASE_URL, PHP_URL_PATH); 33*08df3d18SGreg Roach $secure = parse_url(WT_BASE_URL, PHP_URL_SCHEME) === 'https'; 34*08df3d18SGreg Roach $httponly = true; 35*08df3d18SGreg Roach 36*08df3d18SGreg Roach // Paths containing UTF-8 characters need special handling. 37*08df3d18SGreg Roach $path = implode('/', array_map('rawurlencode', explode('/', $path))); 38*08df3d18SGreg Roach 39*08df3d18SGreg Roach self::setSaveHandler(); 40*08df3d18SGreg Roach 41*08df3d18SGreg Roach session_name('WT_SESSION'); 4231bc7874SGreg Roach session_register_shutdown(); 43*08df3d18SGreg Roach session_set_cookie_params($lifetime, $path, $domain, $secure, $httponly); 4431bc7874SGreg Roach session_start(); 45*08df3d18SGreg Roach 46*08df3d18SGreg Roach // A new session? Prevent session fixation attacks by choosing a new session ID. 47*08df3d18SGreg Roach if (!self::get('initiated')) { 48*08df3d18SGreg Roach self::regenerate(true); 49*08df3d18SGreg Roach self::put('initiated', true); 50*08df3d18SGreg Roach } 5131bc7874SGreg Roach } 5231bc7874SGreg Roach 5331bc7874SGreg Roach /** 5431bc7874SGreg Roach * Read a value from the session 5531bc7874SGreg Roach * 5631bc7874SGreg Roach * @param string $name 5731bc7874SGreg Roach * @param mixed $default 5831bc7874SGreg Roach * 5931bc7874SGreg Roach * @return mixed 6031bc7874SGreg Roach */ 61c1010edaSGreg Roach public static function get($name, $default = null) 62c1010edaSGreg Roach { 6363485653SRico Sonntag return $_SESSION[$name] ?? $default; 6431bc7874SGreg Roach } 6531bc7874SGreg Roach 6631bc7874SGreg Roach /** 6731bc7874SGreg Roach * Write a value to the session 6831bc7874SGreg Roach * 6931bc7874SGreg Roach * @param string $name 7031bc7874SGreg Roach * @param mixed $value 7131bc7874SGreg Roach */ 72c1010edaSGreg Roach public static function put($name, $value) 73c1010edaSGreg Roach { 7431bc7874SGreg Roach $_SESSION[$name] = $value; 7531bc7874SGreg Roach } 7631bc7874SGreg Roach 7731bc7874SGreg Roach /** 7831bc7874SGreg Roach * Remove a value from the session 7931bc7874SGreg Roach * 8031bc7874SGreg Roach * @param string $name 8131bc7874SGreg Roach */ 82c1010edaSGreg Roach public static function forget($name) 83c1010edaSGreg Roach { 8431bc7874SGreg Roach unset($_SESSION[$name]); 8531bc7874SGreg Roach } 8631bc7874SGreg Roach 8731bc7874SGreg Roach /** 8831bc7874SGreg Roach * Does a session variable exist? 8931bc7874SGreg Roach * 9031bc7874SGreg Roach * @param string $name 9131bc7874SGreg Roach * 92cbc1590aSGreg Roach * @return bool 9331bc7874SGreg Roach */ 94c1010edaSGreg Roach public static function has($name) 95c1010edaSGreg Roach { 9691fb15f0SGreg Roach return isset($_SESSION[$name]); 9731bc7874SGreg Roach } 9831bc7874SGreg Roach 9931bc7874SGreg Roach /** 100f5004097SGreg Roach * Remove all stored data from the session. 101f5004097SGreg Roach */ 102c1010edaSGreg Roach public static function clear() 103c1010edaSGreg Roach { 10413abd6f3SGreg Roach $_SESSION = []; 105f5004097SGreg Roach } 106f5004097SGreg Roach 107f5004097SGreg Roach /** 10831bc7874SGreg Roach * After any change in authentication level, we should use a new session ID. 10931bc7874SGreg Roach * 11031bc7874SGreg Roach * @param bool $destroy 11131bc7874SGreg Roach */ 112c1010edaSGreg Roach public static function regenerate($destroy = false) 113c1010edaSGreg Roach { 114f5004097SGreg Roach if ($destroy) { 115f5004097SGreg Roach self::clear(); 116f5004097SGreg Roach } 11731bc7874SGreg Roach session_regenerate_id($destroy); 11831bc7874SGreg Roach } 11931bc7874SGreg Roach 12031bc7874SGreg Roach /** 12131bc7874SGreg Roach * Set an explicit session ID. Typically used for search robots. 12231bc7874SGreg Roach * 12331bc7874SGreg Roach * @param string $id 12431bc7874SGreg Roach */ 125c1010edaSGreg Roach public static function setId($id) 126c1010edaSGreg Roach { 12731bc7874SGreg Roach session_id($id); 12831bc7874SGreg Roach } 12957514a4fSGreg Roach 13057514a4fSGreg Roach /** 13157514a4fSGreg Roach * Initialise our session save handler 13257514a4fSGreg Roach */ 133*08df3d18SGreg Roach private static function setSaveHandler() 134c1010edaSGreg Roach { 13557514a4fSGreg Roach session_set_save_handler( 13657514a4fSGreg Roach function (): bool { 13757514a4fSGreg Roach return Session::open(); 13857514a4fSGreg Roach }, 13957514a4fSGreg Roach function (): bool { 14057514a4fSGreg Roach return Session::close(); 14157514a4fSGreg Roach }, 14257514a4fSGreg Roach function (string $id): string { 14357514a4fSGreg Roach return Session::read($id); 14457514a4fSGreg Roach }, 14557514a4fSGreg Roach function (string $id, string $data): bool { 14657514a4fSGreg Roach return Session::write($id, $data); 14757514a4fSGreg Roach }, 14857514a4fSGreg Roach function (string $id): bool { 14957514a4fSGreg Roach return Session::destroy($id); 15057514a4fSGreg Roach }, 15157514a4fSGreg Roach function (int $maxlifetime): bool { 15257514a4fSGreg Roach return Session::gc($maxlifetime); 15357514a4fSGreg Roach } 15457514a4fSGreg Roach ); 15557514a4fSGreg Roach } 15657514a4fSGreg Roach 15757514a4fSGreg Roach /** 15857514a4fSGreg Roach * For session_set_save_handler() 15957514a4fSGreg Roach * 16057514a4fSGreg Roach * @return bool 16157514a4fSGreg Roach */ 162c1010edaSGreg Roach private static function close() 163c1010edaSGreg Roach { 16457514a4fSGreg Roach return true; 16557514a4fSGreg Roach } 16657514a4fSGreg Roach 16757514a4fSGreg Roach /** 16857514a4fSGreg Roach * For session_set_save_handler() 16957514a4fSGreg Roach * 17057514a4fSGreg Roach * @param string $id 17157514a4fSGreg Roach * 17257514a4fSGreg Roach * @return bool 17357514a4fSGreg Roach */ 174c1010edaSGreg Roach private static function destroy(string $id) 175c1010edaSGreg Roach { 17657514a4fSGreg Roach Database::prepare( 17757514a4fSGreg Roach "DELETE FROM `##session` WHERE session_id = :session_id" 17857514a4fSGreg Roach )->execute([ 179c1010edaSGreg Roach 'session_id' => $id, 18057514a4fSGreg Roach ]); 18157514a4fSGreg Roach 18257514a4fSGreg Roach return true; 18357514a4fSGreg Roach } 18457514a4fSGreg Roach 18557514a4fSGreg Roach /** 18657514a4fSGreg Roach * For session_set_save_handler() 18757514a4fSGreg Roach * 18857514a4fSGreg Roach * @param int $maxlifetime 18957514a4fSGreg Roach * 19057514a4fSGreg Roach * @return bool 19157514a4fSGreg Roach */ 192c1010edaSGreg Roach private static function gc(int $maxlifetime) 193c1010edaSGreg Roach { 19457514a4fSGreg Roach Database::prepare( 19557514a4fSGreg Roach "DELETE FROM `##session` WHERE session_time < DATE_SUB(NOW(), INTERVAL :maxlifetime SECOND)" 19657514a4fSGreg Roach )->execute([ 197c1010edaSGreg Roach 'maxlifetime' => $maxlifetime, 19857514a4fSGreg Roach ]); 19957514a4fSGreg Roach 20057514a4fSGreg Roach return true; 20157514a4fSGreg Roach } 20257514a4fSGreg Roach 20357514a4fSGreg Roach /** 20457514a4fSGreg Roach * For session_set_save_handler() 20557514a4fSGreg Roach * 20657514a4fSGreg Roach * @return bool 20757514a4fSGreg Roach */ 208c1010edaSGreg Roach private static function open() 209c1010edaSGreg Roach { 21057514a4fSGreg Roach return true; 21157514a4fSGreg Roach } 21257514a4fSGreg Roach 21357514a4fSGreg Roach /** 21457514a4fSGreg Roach * For session_set_save_handler() 21557514a4fSGreg Roach * 21657514a4fSGreg Roach * @param string $id 21757514a4fSGreg Roach * 21857514a4fSGreg Roach * @return string 21957514a4fSGreg Roach */ 220c1010edaSGreg Roach private static function read(string $id): string 221c1010edaSGreg Roach { 22257514a4fSGreg Roach return (string) Database::prepare( 22357514a4fSGreg Roach "SELECT session_data FROM `##session` WHERE session_id = :session_id" 22457514a4fSGreg Roach )->execute([ 225c1010edaSGreg Roach 'session_id' => $id, 22657514a4fSGreg Roach ])->fetchOne(); 22757514a4fSGreg Roach } 22857514a4fSGreg Roach 22957514a4fSGreg Roach /** 23057514a4fSGreg Roach * For session_set_save_handler() 23157514a4fSGreg Roach * 23257514a4fSGreg Roach * @param string $id 23357514a4fSGreg Roach * @param string $data 23457514a4fSGreg Roach * 23557514a4fSGreg Roach * @return bool 23657514a4fSGreg Roach */ 237c1010edaSGreg Roach private static function write(string $id, string $data): bool 238c1010edaSGreg Roach { 2394c891c40SGreg Roach $request = Request::createFromGlobals(); 2404c891c40SGreg Roach 24157514a4fSGreg Roach // Only update the session table once per minute, unless the session data has actually changed. 24257514a4fSGreg Roach Database::prepare( 24357514a4fSGreg Roach "INSERT INTO `##session` (session_id, user_id, ip_address, session_data, session_time)" . 2444c891c40SGreg Roach " VALUES (:session_id, :user_id, :ip_address, :data, CURRENT_TIMESTAMP - SECOND(CURRENT_TIMESTAMP))" . 24557514a4fSGreg Roach " ON DUPLICATE KEY UPDATE" . 24657514a4fSGreg Roach " user_id = VALUES(user_id)," . 24757514a4fSGreg Roach " ip_address = VALUES(ip_address)," . 24857514a4fSGreg Roach " session_data = VALUES(session_data)," . 24957514a4fSGreg Roach " session_time = CURRENT_TIMESTAMP - SECOND(CURRENT_TIMESTAMP)" 25057514a4fSGreg Roach )->execute([ 2514c891c40SGreg Roach 'session_id' => $id, 2524c891c40SGreg Roach 'user_id' => (int) Auth::id(), 2534c891c40SGreg Roach 'ip_address' => $request->getClientIp(), 2544c891c40SGreg Roach 'data' => $data, 2554c891c40SGreg Roach ]); 25657514a4fSGreg Roach 25757514a4fSGreg Roach return true; 25857514a4fSGreg Roach } 25931bc7874SGreg Roach} 260