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 27*c7ff4153SGreg Roach * 28*c7ff4153SGreg Roach * @return void 2931bc7874SGreg Roach */ 3008df3d18SGreg Roach public static function start() 31c1010edaSGreg Roach { 3208df3d18SGreg Roach $domain = ''; 3308df3d18SGreg Roach $path = parse_url(WT_BASE_URL, PHP_URL_PATH); 3408df3d18SGreg Roach $secure = parse_url(WT_BASE_URL, PHP_URL_SCHEME) === 'https'; 3508df3d18SGreg Roach $httponly = true; 3608df3d18SGreg Roach 3708df3d18SGreg Roach // Paths containing UTF-8 characters need special handling. 3808df3d18SGreg Roach $path = implode('/', array_map('rawurlencode', explode('/', $path))); 3908df3d18SGreg Roach 4008df3d18SGreg Roach self::setSaveHandler(); 4108df3d18SGreg Roach 4208df3d18SGreg Roach session_name('WT_SESSION'); 4331bc7874SGreg Roach session_register_shutdown(); 44a0dfa978SGreg Roach session_set_cookie_params(0, $path, $domain, $secure, $httponly); 4531bc7874SGreg Roach session_start(); 4608df3d18SGreg Roach 4708df3d18SGreg Roach // A new session? Prevent session fixation attacks by choosing a new session ID. 4808df3d18SGreg Roach if (!self::get('initiated')) { 4908df3d18SGreg Roach self::regenerate(true); 5008df3d18SGreg Roach self::put('initiated', true); 5108df3d18SGreg Roach } 5231bc7874SGreg Roach } 5331bc7874SGreg Roach 5431bc7874SGreg Roach /** 5531bc7874SGreg Roach * Read a value from the session 5631bc7874SGreg Roach * 5731bc7874SGreg Roach * @param string $name 5831bc7874SGreg Roach * @param mixed $default 5931bc7874SGreg Roach * 6031bc7874SGreg Roach * @return mixed 6131bc7874SGreg Roach */ 62*c7ff4153SGreg Roach public static function get(string $name, $default = null) 63c1010edaSGreg Roach { 6463485653SRico Sonntag return $_SESSION[$name] ?? $default; 6531bc7874SGreg Roach } 6631bc7874SGreg Roach 6731bc7874SGreg Roach /** 6831bc7874SGreg Roach * Write a value to the session 6931bc7874SGreg Roach * 7031bc7874SGreg Roach * @param string $name 7131bc7874SGreg Roach * @param mixed $value 72*c7ff4153SGreg Roach * 73*c7ff4153SGreg Roach * @return void 7431bc7874SGreg Roach */ 75*c7ff4153SGreg Roach public static function put(string $name, $value) 76c1010edaSGreg Roach { 7731bc7874SGreg Roach $_SESSION[$name] = $value; 7831bc7874SGreg Roach } 7931bc7874SGreg Roach 8031bc7874SGreg Roach /** 8131bc7874SGreg Roach * Remove a value from the session 8231bc7874SGreg Roach * 8331bc7874SGreg Roach * @param string $name 84*c7ff4153SGreg Roach * 85*c7ff4153SGreg Roach * @return void 8631bc7874SGreg Roach */ 87*c7ff4153SGreg Roach public static function forget(string $name) 88c1010edaSGreg Roach { 8931bc7874SGreg Roach unset($_SESSION[$name]); 9031bc7874SGreg Roach } 9131bc7874SGreg Roach 9231bc7874SGreg Roach /** 9331bc7874SGreg Roach * Does a session variable exist? 9431bc7874SGreg Roach * 9531bc7874SGreg Roach * @param string $name 9631bc7874SGreg Roach * 97cbc1590aSGreg Roach * @return bool 9831bc7874SGreg Roach */ 99*c7ff4153SGreg Roach public static function has(string $name): bool 100c1010edaSGreg Roach { 10191fb15f0SGreg Roach return isset($_SESSION[$name]); 10231bc7874SGreg Roach } 10331bc7874SGreg Roach 10431bc7874SGreg Roach /** 105f5004097SGreg Roach * Remove all stored data from the session. 106*c7ff4153SGreg Roach * 107*c7ff4153SGreg Roach * @return void 108f5004097SGreg Roach */ 109c1010edaSGreg Roach public static function clear() 110c1010edaSGreg Roach { 11113abd6f3SGreg Roach $_SESSION = []; 112f5004097SGreg Roach } 113f5004097SGreg Roach 114f5004097SGreg Roach /** 11531bc7874SGreg Roach * After any change in authentication level, we should use a new session ID. 11631bc7874SGreg Roach * 11731bc7874SGreg Roach * @param bool $destroy 118*c7ff4153SGreg Roach * 119*c7ff4153SGreg Roach * @return void 12031bc7874SGreg Roach */ 121*c7ff4153SGreg Roach public static function regenerate(bool $destroy = false) 122c1010edaSGreg Roach { 123f5004097SGreg Roach if ($destroy) { 124f5004097SGreg Roach self::clear(); 125f5004097SGreg Roach } 12631bc7874SGreg Roach session_regenerate_id($destroy); 12731bc7874SGreg Roach } 12831bc7874SGreg Roach 12931bc7874SGreg Roach /** 13031bc7874SGreg Roach * Set an explicit session ID. Typically used for search robots. 13131bc7874SGreg Roach * 13231bc7874SGreg Roach * @param string $id 133*c7ff4153SGreg Roach * 134*c7ff4153SGreg Roach * @return void 13531bc7874SGreg Roach */ 136*c7ff4153SGreg Roach public static function setId(string $id) 137c1010edaSGreg Roach { 13831bc7874SGreg Roach session_id($id); 13931bc7874SGreg Roach } 14057514a4fSGreg Roach 14157514a4fSGreg Roach /** 14257514a4fSGreg Roach * Initialise our session save handler 143*c7ff4153SGreg Roach * 144*c7ff4153SGreg Roach * @return void 14557514a4fSGreg Roach */ 14608df3d18SGreg Roach private static function setSaveHandler() 147c1010edaSGreg Roach { 14857514a4fSGreg Roach session_set_save_handler( 14957514a4fSGreg Roach function (): bool { 15057514a4fSGreg Roach return Session::open(); 15157514a4fSGreg Roach }, 15257514a4fSGreg Roach function (): bool { 15357514a4fSGreg Roach return Session::close(); 15457514a4fSGreg Roach }, 15557514a4fSGreg Roach function (string $id): string { 15657514a4fSGreg Roach return Session::read($id); 15757514a4fSGreg Roach }, 15857514a4fSGreg Roach function (string $id, string $data): bool { 15957514a4fSGreg Roach return Session::write($id, $data); 16057514a4fSGreg Roach }, 16157514a4fSGreg Roach function (string $id): bool { 16257514a4fSGreg Roach return Session::destroy($id); 16357514a4fSGreg Roach }, 16457514a4fSGreg Roach function (int $maxlifetime): bool { 16557514a4fSGreg Roach return Session::gc($maxlifetime); 16657514a4fSGreg Roach } 16757514a4fSGreg Roach ); 16857514a4fSGreg Roach } 16957514a4fSGreg Roach 17057514a4fSGreg Roach /** 17157514a4fSGreg Roach * For session_set_save_handler() 17257514a4fSGreg Roach * 17357514a4fSGreg Roach * @return bool 17457514a4fSGreg Roach */ 1758f53f488SRico Sonntag private static function close(): bool 176c1010edaSGreg Roach { 17757514a4fSGreg Roach return true; 17857514a4fSGreg Roach } 17957514a4fSGreg Roach 18057514a4fSGreg Roach /** 18157514a4fSGreg Roach * For session_set_save_handler() 18257514a4fSGreg Roach * 18357514a4fSGreg Roach * @param string $id 18457514a4fSGreg Roach * 18557514a4fSGreg Roach * @return bool 18657514a4fSGreg Roach */ 1878f53f488SRico Sonntag private static function destroy(string $id): bool 188c1010edaSGreg Roach { 18957514a4fSGreg Roach Database::prepare( 19057514a4fSGreg Roach "DELETE FROM `##session` WHERE session_id = :session_id" 19157514a4fSGreg Roach )->execute([ 192c1010edaSGreg Roach 'session_id' => $id, 19357514a4fSGreg Roach ]); 19457514a4fSGreg Roach 19557514a4fSGreg Roach return true; 19657514a4fSGreg Roach } 19757514a4fSGreg Roach 19857514a4fSGreg Roach /** 19957514a4fSGreg Roach * For session_set_save_handler() 20057514a4fSGreg Roach * 20157514a4fSGreg Roach * @param int $maxlifetime 20257514a4fSGreg Roach * 20357514a4fSGreg Roach * @return bool 20457514a4fSGreg Roach */ 2058f53f488SRico Sonntag private static function gc(int $maxlifetime): bool 206c1010edaSGreg Roach { 20757514a4fSGreg Roach Database::prepare( 20857514a4fSGreg Roach "DELETE FROM `##session` WHERE session_time < DATE_SUB(NOW(), INTERVAL :maxlifetime SECOND)" 20957514a4fSGreg Roach )->execute([ 210c1010edaSGreg Roach 'maxlifetime' => $maxlifetime, 21157514a4fSGreg Roach ]); 21257514a4fSGreg Roach 21357514a4fSGreg Roach return true; 21457514a4fSGreg Roach } 21557514a4fSGreg Roach 21657514a4fSGreg Roach /** 21757514a4fSGreg Roach * For session_set_save_handler() 21857514a4fSGreg Roach * 21957514a4fSGreg Roach * @return bool 22057514a4fSGreg Roach */ 2218f53f488SRico Sonntag private static function open(): bool 222c1010edaSGreg Roach { 22357514a4fSGreg Roach return true; 22457514a4fSGreg Roach } 22557514a4fSGreg Roach 22657514a4fSGreg Roach /** 22757514a4fSGreg Roach * For session_set_save_handler() 22857514a4fSGreg Roach * 22957514a4fSGreg Roach * @param string $id 23057514a4fSGreg Roach * 23157514a4fSGreg Roach * @return string 23257514a4fSGreg Roach */ 233c1010edaSGreg Roach private static function read(string $id): string 234c1010edaSGreg Roach { 23557514a4fSGreg Roach return (string) Database::prepare( 23657514a4fSGreg Roach "SELECT session_data FROM `##session` WHERE session_id = :session_id" 23757514a4fSGreg Roach )->execute([ 238c1010edaSGreg Roach 'session_id' => $id, 23957514a4fSGreg Roach ])->fetchOne(); 24057514a4fSGreg Roach } 24157514a4fSGreg Roach 24257514a4fSGreg Roach /** 24357514a4fSGreg Roach * For session_set_save_handler() 24457514a4fSGreg Roach * 24557514a4fSGreg Roach * @param string $id 24657514a4fSGreg Roach * @param string $data 24757514a4fSGreg Roach * 24857514a4fSGreg Roach * @return bool 24957514a4fSGreg Roach */ 250c1010edaSGreg Roach private static function write(string $id, string $data): bool 251c1010edaSGreg Roach { 2524c891c40SGreg Roach $request = Request::createFromGlobals(); 2534c891c40SGreg Roach 25457514a4fSGreg Roach // Only update the session table once per minute, unless the session data has actually changed. 25557514a4fSGreg Roach Database::prepare( 25657514a4fSGreg Roach "INSERT INTO `##session` (session_id, user_id, ip_address, session_data, session_time)" . 2574c891c40SGreg Roach " VALUES (:session_id, :user_id, :ip_address, :data, CURRENT_TIMESTAMP - SECOND(CURRENT_TIMESTAMP))" . 25857514a4fSGreg Roach " ON DUPLICATE KEY UPDATE" . 25957514a4fSGreg Roach " user_id = VALUES(user_id)," . 26057514a4fSGreg Roach " ip_address = VALUES(ip_address)," . 26157514a4fSGreg Roach " session_data = VALUES(session_data)," . 26257514a4fSGreg Roach " session_time = CURRENT_TIMESTAMP - SECOND(CURRENT_TIMESTAMP)" 26357514a4fSGreg Roach )->execute([ 2644c891c40SGreg Roach 'session_id' => $id, 2654c891c40SGreg Roach 'user_id' => (int) Auth::id(), 2664c891c40SGreg Roach 'ip_address' => $request->getClientIp(), 2674c891c40SGreg Roach 'data' => $data, 2684c891c40SGreg Roach ]); 26957514a4fSGreg Roach 27057514a4fSGreg Roach return true; 27157514a4fSGreg Roach } 272a45f9889SGreg Roach 273a45f9889SGreg Roach 274a45f9889SGreg Roach /** 275a45f9889SGreg Roach * Cross-Site Request Forgery tokens - ensure that the user is submitting 276a45f9889SGreg Roach * a form that was generated by the current session. 277a45f9889SGreg Roach * 278a45f9889SGreg Roach * @return string 279a45f9889SGreg Roach */ 2808f53f488SRico Sonntag public static function getCsrfToken(): string 281a45f9889SGreg Roach { 282a45f9889SGreg Roach if (!Session::has('CSRF_TOKEN')) { 283a45f9889SGreg Roach $charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcedfghijklmnopqrstuvwxyz0123456789'; 284a45f9889SGreg Roach $csrf_token = ''; 285a45f9889SGreg Roach for ($n = 0; $n < 32; ++$n) { 286a45f9889SGreg Roach $csrf_token .= substr($charset, random_int(0, 61), 1); 287a45f9889SGreg Roach } 288a45f9889SGreg Roach Session::put('CSRF_TOKEN', $csrf_token); 289a45f9889SGreg Roach } 290a45f9889SGreg Roach 291a45f9889SGreg Roach return Session::get('CSRF_TOKEN'); 292a45f9889SGreg Roach } 29331bc7874SGreg Roach} 294