131bc7874SGreg Roach<?php 23976b470SGreg Roach 331bc7874SGreg Roach/** 431bc7874SGreg Roach * webtrees: online genealogy 58fcd0d32SGreg Roach * Copyright (C) 2019 webtrees development team 631bc7874SGreg Roach * This program is free software: you can redistribute it and/or modify 731bc7874SGreg Roach * it under the terms of the GNU General Public License as published by 831bc7874SGreg Roach * the Free Software Foundation, either version 3 of the License, or 931bc7874SGreg Roach * (at your option) any later version. 1031bc7874SGreg Roach * This program is distributed in the hope that it will be useful, 1131bc7874SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 1231bc7874SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1331bc7874SGreg Roach * GNU General Public License for more details. 1431bc7874SGreg Roach * You should have received a copy of the GNU General Public License 1531bc7874SGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>. 1631bc7874SGreg Roach */ 17fcfa147eSGreg Roach 18e7f56f2aSGreg Roachdeclare(strict_types=1); 19e7f56f2aSGreg Roach 2076692c8bSGreg Roachnamespace Fisharebest\Webtrees; 2131bc7874SGreg Roach 22deb4b685SGreg Roachuse Illuminate\Support\Str; 236ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface; 243976b470SGreg Roach 254d7dd147SGreg Roachuse function array_map; 264d7dd147SGreg Roachuse function explode; 274d7dd147SGreg Roachuse function implode; 284d7dd147SGreg Roachuse function parse_url; 294d7dd147SGreg Roachuse function session_name; 304d7dd147SGreg Roachuse function session_regenerate_id; 314d7dd147SGreg Roachuse function session_register_shutdown; 324d7dd147SGreg Roachuse function session_set_cookie_params; 334d7dd147SGreg Roachuse function session_set_save_handler; 344d7dd147SGreg Roachuse function session_start; 35fe3a9054SGreg Roachuse function session_status; 36*dc6b8e0eSGreg Roachuse function session_write_close; 373976b470SGreg Roach 38*dc6b8e0eSGreg Roachuse const PHP_SESSION_ACTIVE; 394d7dd147SGreg Roachuse const PHP_URL_HOST; 404d7dd147SGreg Roachuse const PHP_URL_PATH; 414d7dd147SGreg Roachuse const PHP_URL_SCHEME; 424c891c40SGreg Roach 4331bc7874SGreg Roach/** 4442af74e7SGreg Roach * Session handling 4531bc7874SGreg Roach */ 46c1010edaSGreg Roachclass Session 47c1010edaSGreg Roach{ 489683b471SGreg Roach private const SESSION_NAME = 'WT2_SESSION'; 49*dc6b8e0eSGreg Roach 5031bc7874SGreg Roach /** 5131bc7874SGreg Roach * Start a session 52c7ff4153SGreg Roach * 534d7dd147SGreg Roach * @param ServerRequestInterface $request 544d7dd147SGreg Roach * 55c7ff4153SGreg Roach * @return void 5631bc7874SGreg Roach */ 574d7dd147SGreg Roach public static function start(ServerRequestInterface $request): void 58c1010edaSGreg Roach { 594d7dd147SGreg Roach // Store sessions in the database 604d7dd147SGreg Roach session_set_save_handler(new SessionDatabaseHandler($request)); 614d7dd147SGreg Roach 629e5d8e6fSGreg Roach $url = $request->getAttribute('base_url'); 634d7dd147SGreg Roach $secure = parse_url($url, PHP_URL_SCHEME) === 'https'; 644d7dd147SGreg Roach $domain = parse_url($url, PHP_URL_HOST); 659e5d8e6fSGreg Roach $path = parse_url($url, PHP_URL_PATH) ?? ''; 6608df3d18SGreg Roach 6708df3d18SGreg Roach // Paths containing UTF-8 characters need special handling. 6808df3d18SGreg Roach $path = implode('/', array_map('rawurlencode', explode('/', $path))); 6908df3d18SGreg Roach 709683b471SGreg Roach session_name(self::SESSION_NAME); 7131bc7874SGreg Roach session_register_shutdown(); 72cf7dfd3dSGreg Roach session_set_cookie_params(0, $path . '/', $domain, $secure, true); 7331bc7874SGreg Roach session_start(); 7408df3d18SGreg Roach 7508df3d18SGreg Roach // A new session? Prevent session fixation attacks by choosing a new session ID. 76b262b3d3SGreg Roach if (self::get('initiated') !== true) { 7708df3d18SGreg Roach self::regenerate(true); 7808df3d18SGreg Roach self::put('initiated', true); 7908df3d18SGreg Roach } 8031bc7874SGreg Roach } 8131bc7874SGreg Roach 8231bc7874SGreg Roach /** 83*dc6b8e0eSGreg Roach * Save/close the session. This releases the session lock. 84*dc6b8e0eSGreg Roach * Closing early can help concurrent connections. 85*dc6b8e0eSGreg Roach */ 86*dc6b8e0eSGreg Roach public static function save(): void 87*dc6b8e0eSGreg Roach { 88*dc6b8e0eSGreg Roach if (session_status() === PHP_SESSION_ACTIVE) { 89*dc6b8e0eSGreg Roach session_write_close(); 90*dc6b8e0eSGreg Roach } 91*dc6b8e0eSGreg Roach } 92*dc6b8e0eSGreg Roach 93*dc6b8e0eSGreg Roach /** 946ccdf4f0SGreg Roach * Read a value from the session 9557514a4fSGreg Roach * 966ccdf4f0SGreg Roach * @param string $name 976ccdf4f0SGreg Roach * @param mixed $default 986ccdf4f0SGreg Roach * 996ccdf4f0SGreg Roach * @return mixed 10057514a4fSGreg Roach */ 1016ccdf4f0SGreg Roach public static function get(string $name, $default = null) 102c1010edaSGreg Roach { 1036ccdf4f0SGreg Roach return $_SESSION[$name] ?? $default; 10457514a4fSGreg Roach } 10557514a4fSGreg Roach 10657514a4fSGreg Roach /** 107fd8bd3bcSGreg Roach * Read a value from the session and remove it. 108fd8bd3bcSGreg Roach * 109fd8bd3bcSGreg Roach * @param string $name 110fd8bd3bcSGreg Roach * @param mixed $default 111fd8bd3bcSGreg Roach * 112fd8bd3bcSGreg Roach * @return mixed 113fd8bd3bcSGreg Roach */ 114fd8bd3bcSGreg Roach public static function pull(string $name, $default = null) 115fd8bd3bcSGreg Roach { 116fd8bd3bcSGreg Roach $value = self::get($name, $default); 117fd8bd3bcSGreg Roach self::forget($name); 118fd8bd3bcSGreg Roach 119fd8bd3bcSGreg Roach return $value; 120fd8bd3bcSGreg Roach } 121fd8bd3bcSGreg Roach 122fd8bd3bcSGreg Roach /** 1236ccdf4f0SGreg Roach * After any change in authentication level, we should use a new session ID. 12457514a4fSGreg Roach * 1256ccdf4f0SGreg Roach * @param bool $destroy 12657514a4fSGreg Roach * 1276ccdf4f0SGreg Roach * @return void 12857514a4fSGreg Roach */ 1296ccdf4f0SGreg Roach public static function regenerate(bool $destroy = false): void 130c1010edaSGreg Roach { 1316ccdf4f0SGreg Roach if ($destroy) { 1326ccdf4f0SGreg Roach self::clear(); 1336ccdf4f0SGreg Roach } 1346ccdf4f0SGreg Roach 1356ccdf4f0SGreg Roach if (session_status() === PHP_SESSION_ACTIVE) { 1366ccdf4f0SGreg Roach session_regenerate_id($destroy); 1376ccdf4f0SGreg Roach } 13857514a4fSGreg Roach } 13957514a4fSGreg Roach 14057514a4fSGreg Roach /** 1416ccdf4f0SGreg Roach * Remove all stored data from the session. 1426ccdf4f0SGreg Roach * 1436ccdf4f0SGreg Roach * @return void 1446ccdf4f0SGreg Roach */ 1456ccdf4f0SGreg Roach public static function clear(): void 1466ccdf4f0SGreg Roach { 1476ccdf4f0SGreg Roach $_SESSION = []; 1486ccdf4f0SGreg Roach } 1496ccdf4f0SGreg Roach 1506ccdf4f0SGreg Roach /** 1516ccdf4f0SGreg Roach * Write a value to the session 1526ccdf4f0SGreg Roach * 1536ccdf4f0SGreg Roach * @param string $name 1546ccdf4f0SGreg Roach * @param mixed $value 1556ccdf4f0SGreg Roach * 1566ccdf4f0SGreg Roach * @return void 1576ccdf4f0SGreg Roach */ 1586ccdf4f0SGreg Roach public static function put(string $name, $value): void 1596ccdf4f0SGreg Roach { 1606ccdf4f0SGreg Roach $_SESSION[$name] = $value; 1616ccdf4f0SGreg Roach } 1626ccdf4f0SGreg Roach 1636ccdf4f0SGreg Roach /** 1646ccdf4f0SGreg Roach * Remove a value from the session 1656ccdf4f0SGreg Roach * 1666ccdf4f0SGreg Roach * @param string $name 1676ccdf4f0SGreg Roach * 1686ccdf4f0SGreg Roach * @return void 1696ccdf4f0SGreg Roach */ 1706ccdf4f0SGreg Roach public static function forget(string $name): void 1716ccdf4f0SGreg Roach { 1726ccdf4f0SGreg Roach unset($_SESSION[$name]); 1736ccdf4f0SGreg Roach } 1746ccdf4f0SGreg Roach 1756ccdf4f0SGreg Roach /** 176a45f9889SGreg Roach * Cross-Site Request Forgery tokens - ensure that the user is submitting 177a45f9889SGreg Roach * a form that was generated by the current session. 178a45f9889SGreg Roach * 179a45f9889SGreg Roach * @return string 180a45f9889SGreg Roach */ 1818f53f488SRico Sonntag public static function getCsrfToken(): string 182a45f9889SGreg Roach { 183e364afe4SGreg Roach if (!self::has('CSRF_TOKEN')) { 184e364afe4SGreg Roach self::put('CSRF_TOKEN', Str::random(32)); 185a45f9889SGreg Roach } 186a45f9889SGreg Roach 187e364afe4SGreg Roach return self::get('CSRF_TOKEN'); 188a45f9889SGreg Roach } 1896ccdf4f0SGreg Roach 1906ccdf4f0SGreg Roach /** 1916ccdf4f0SGreg Roach * Does a session variable exist? 1926ccdf4f0SGreg Roach * 1936ccdf4f0SGreg Roach * @param string $name 1946ccdf4f0SGreg Roach * 1956ccdf4f0SGreg Roach * @return bool 1966ccdf4f0SGreg Roach */ 1976ccdf4f0SGreg Roach public static function has(string $name): bool 1986ccdf4f0SGreg Roach { 1996ccdf4f0SGreg Roach return isset($_SESSION[$name]); 2006ccdf4f0SGreg Roach } 20131bc7874SGreg Roach} 202