131bc7874SGreg Roach<?php 231bc7874SGreg Roach/** 331bc7874SGreg Roach * webtrees: online genealogy 48fcd0d32SGreg Roach * Copyright (C) 2019 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 */ 16e7f56f2aSGreg Roachdeclare(strict_types=1); 17e7f56f2aSGreg Roach 1876692c8bSGreg Roachnamespace Fisharebest\Webtrees; 1931bc7874SGreg Roach 20fe3a9054SGreg Roachuse Carbon\Carbon; 21fe3a9054SGreg Roachuse Illuminate\Database\Capsule\Manager as DB; 22*deb4b685SGreg Roachuse Illuminate\Support\Str; 234c891c40SGreg Roachuse Symfony\Component\HttpFoundation\Request; 24fe3a9054SGreg Roachuse function session_status; 254c891c40SGreg Roach 2631bc7874SGreg Roach/** 2742af74e7SGreg Roach * Session handling 2831bc7874SGreg Roach */ 29c1010edaSGreg Roachclass Session 30c1010edaSGreg Roach{ 3131bc7874SGreg Roach /** 3231bc7874SGreg Roach * Start a session 33c7ff4153SGreg Roach * 34c7ff4153SGreg Roach * @return void 3531bc7874SGreg Roach */ 3608df3d18SGreg Roach public static function start() 37c1010edaSGreg Roach { 3808df3d18SGreg Roach $domain = ''; 3908df3d18SGreg Roach $path = parse_url(WT_BASE_URL, PHP_URL_PATH); 4008df3d18SGreg Roach $secure = parse_url(WT_BASE_URL, PHP_URL_SCHEME) === 'https'; 4108df3d18SGreg Roach $httponly = true; 4208df3d18SGreg Roach 4308df3d18SGreg Roach // Paths containing UTF-8 characters need special handling. 4408df3d18SGreg Roach $path = implode('/', array_map('rawurlencode', explode('/', $path))); 4508df3d18SGreg Roach 4608df3d18SGreg Roach self::setSaveHandler(); 4708df3d18SGreg Roach 4808df3d18SGreg Roach session_name('WT_SESSION'); 4931bc7874SGreg Roach session_register_shutdown(); 50a0dfa978SGreg Roach session_set_cookie_params(0, $path, $domain, $secure, $httponly); 5131bc7874SGreg Roach session_start(); 5208df3d18SGreg Roach 5308df3d18SGreg Roach // A new session? Prevent session fixation attacks by choosing a new session ID. 5408df3d18SGreg Roach if (!self::get('initiated')) { 5508df3d18SGreg Roach self::regenerate(true); 5608df3d18SGreg Roach self::put('initiated', true); 5708df3d18SGreg Roach } 5831bc7874SGreg Roach } 5931bc7874SGreg Roach 6031bc7874SGreg Roach /** 6131bc7874SGreg Roach * Read a value from the session 6231bc7874SGreg Roach * 6331bc7874SGreg Roach * @param string $name 6431bc7874SGreg Roach * @param mixed $default 6531bc7874SGreg Roach * 6631bc7874SGreg Roach * @return mixed 6731bc7874SGreg Roach */ 68c7ff4153SGreg Roach public static function get(string $name, $default = null) 69c1010edaSGreg Roach { 7063485653SRico Sonntag return $_SESSION[$name] ?? $default; 7131bc7874SGreg Roach } 7231bc7874SGreg Roach 7331bc7874SGreg Roach /** 7431bc7874SGreg Roach * Write a value to the session 7531bc7874SGreg Roach * 7631bc7874SGreg Roach * @param string $name 7731bc7874SGreg Roach * @param mixed $value 78c7ff4153SGreg Roach * 79c7ff4153SGreg Roach * @return void 8031bc7874SGreg Roach */ 81c7ff4153SGreg Roach public static function put(string $name, $value) 82c1010edaSGreg Roach { 8331bc7874SGreg Roach $_SESSION[$name] = $value; 8431bc7874SGreg Roach } 8531bc7874SGreg Roach 8631bc7874SGreg Roach /** 8731bc7874SGreg Roach * Remove a value from the session 8831bc7874SGreg Roach * 8931bc7874SGreg Roach * @param string $name 90c7ff4153SGreg Roach * 91c7ff4153SGreg Roach * @return void 9231bc7874SGreg Roach */ 93c7ff4153SGreg Roach public static function forget(string $name) 94c1010edaSGreg Roach { 9531bc7874SGreg Roach unset($_SESSION[$name]); 9631bc7874SGreg Roach } 9731bc7874SGreg Roach 9831bc7874SGreg Roach /** 9931bc7874SGreg Roach * Does a session variable exist? 10031bc7874SGreg Roach * 10131bc7874SGreg Roach * @param string $name 10231bc7874SGreg Roach * 103cbc1590aSGreg Roach * @return bool 10431bc7874SGreg Roach */ 105c7ff4153SGreg Roach public static function has(string $name): bool 106c1010edaSGreg Roach { 10791fb15f0SGreg Roach return isset($_SESSION[$name]); 10831bc7874SGreg Roach } 10931bc7874SGreg Roach 11031bc7874SGreg Roach /** 111f5004097SGreg Roach * Remove all stored data from the session. 112c7ff4153SGreg Roach * 113c7ff4153SGreg Roach * @return void 114f5004097SGreg Roach */ 115c1010edaSGreg Roach public static function clear() 116c1010edaSGreg Roach { 11713abd6f3SGreg Roach $_SESSION = []; 118f5004097SGreg Roach } 119f5004097SGreg Roach 120f5004097SGreg Roach /** 12131bc7874SGreg Roach * After any change in authentication level, we should use a new session ID. 12231bc7874SGreg Roach * 12331bc7874SGreg Roach * @param bool $destroy 124c7ff4153SGreg Roach * 125c7ff4153SGreg Roach * @return void 12631bc7874SGreg Roach */ 127c7ff4153SGreg Roach public static function regenerate(bool $destroy = false) 128c1010edaSGreg Roach { 129f5004097SGreg Roach if ($destroy) { 130f5004097SGreg Roach self::clear(); 131f5004097SGreg Roach } 13201461f86SGreg Roach 13301461f86SGreg Roach if (session_status() === PHP_SESSION_ACTIVE) { 13431bc7874SGreg Roach session_regenerate_id($destroy); 13531bc7874SGreg Roach } 13601461f86SGreg Roach } 13731bc7874SGreg Roach 13831bc7874SGreg Roach /** 13931bc7874SGreg Roach * Set an explicit session ID. Typically used for search robots. 14031bc7874SGreg Roach * 14131bc7874SGreg Roach * @param string $id 142c7ff4153SGreg Roach * 143c7ff4153SGreg Roach * @return void 14431bc7874SGreg Roach */ 145c7ff4153SGreg Roach public static function setId(string $id) 146c1010edaSGreg Roach { 14731bc7874SGreg Roach session_id($id); 14831bc7874SGreg Roach } 14957514a4fSGreg Roach 15057514a4fSGreg Roach /** 15157514a4fSGreg Roach * Initialise our session save handler 152c7ff4153SGreg Roach * 153c7ff4153SGreg Roach * @return void 15457514a4fSGreg Roach */ 15508df3d18SGreg Roach private static function setSaveHandler() 156c1010edaSGreg Roach { 15757514a4fSGreg Roach session_set_save_handler( 15857514a4fSGreg Roach function (): bool { 15957514a4fSGreg Roach return Session::open(); 16057514a4fSGreg Roach }, 16157514a4fSGreg Roach function (): bool { 16257514a4fSGreg Roach return Session::close(); 16357514a4fSGreg Roach }, 16457514a4fSGreg Roach function (string $id): string { 16557514a4fSGreg Roach return Session::read($id); 16657514a4fSGreg Roach }, 16757514a4fSGreg Roach function (string $id, string $data): bool { 16857514a4fSGreg Roach return Session::write($id, $data); 16957514a4fSGreg Roach }, 17057514a4fSGreg Roach function (string $id): bool { 17157514a4fSGreg Roach return Session::destroy($id); 17257514a4fSGreg Roach }, 17357514a4fSGreg Roach function (int $maxlifetime): bool { 17457514a4fSGreg Roach return Session::gc($maxlifetime); 17557514a4fSGreg Roach } 17657514a4fSGreg Roach ); 17757514a4fSGreg Roach } 17857514a4fSGreg Roach 17957514a4fSGreg Roach /** 18057514a4fSGreg Roach * For session_set_save_handler() 18157514a4fSGreg Roach * 18257514a4fSGreg Roach * @return bool 18357514a4fSGreg Roach */ 1848f53f488SRico Sonntag private static function close(): bool 185c1010edaSGreg Roach { 18657514a4fSGreg Roach return true; 18757514a4fSGreg Roach } 18857514a4fSGreg Roach 18957514a4fSGreg Roach /** 19057514a4fSGreg Roach * For session_set_save_handler() 19157514a4fSGreg Roach * 19257514a4fSGreg Roach * @param string $id 19357514a4fSGreg Roach * 19457514a4fSGreg Roach * @return bool 19557514a4fSGreg Roach */ 1968f53f488SRico Sonntag private static function destroy(string $id): bool 197c1010edaSGreg Roach { 198fe3a9054SGreg Roach DB::table('session') 199fe3a9054SGreg Roach ->where('session_id', '=', $id) 200fe3a9054SGreg Roach ->delete(); 20157514a4fSGreg Roach 20257514a4fSGreg Roach return true; 20357514a4fSGreg Roach } 20457514a4fSGreg Roach 20557514a4fSGreg Roach /** 20657514a4fSGreg Roach * For session_set_save_handler() 20757514a4fSGreg Roach * 20857514a4fSGreg Roach * @param int $maxlifetime 20957514a4fSGreg Roach * 21057514a4fSGreg Roach * @return bool 21157514a4fSGreg Roach */ 2128f53f488SRico Sonntag private static function gc(int $maxlifetime): bool 213c1010edaSGreg Roach { 214fe3a9054SGreg Roach DB::table('session') 215fe3a9054SGreg Roach ->where('session_time', '<', Carbon::now()->subSeconds($maxlifetime)) 216fe3a9054SGreg Roach ->delete(); 21757514a4fSGreg Roach 21857514a4fSGreg Roach return true; 21957514a4fSGreg Roach } 22057514a4fSGreg Roach 22157514a4fSGreg Roach /** 22257514a4fSGreg Roach * For session_set_save_handler() 22357514a4fSGreg Roach * 22457514a4fSGreg Roach * @return bool 22557514a4fSGreg Roach */ 2268f53f488SRico Sonntag private static function open(): bool 227c1010edaSGreg Roach { 22857514a4fSGreg Roach return true; 22957514a4fSGreg Roach } 23057514a4fSGreg Roach 23157514a4fSGreg Roach /** 23257514a4fSGreg Roach * For session_set_save_handler() 23357514a4fSGreg Roach * 23457514a4fSGreg Roach * @param string $id 23557514a4fSGreg Roach * 23657514a4fSGreg Roach * @return string 23757514a4fSGreg Roach */ 238c1010edaSGreg Roach private static function read(string $id): string 239c1010edaSGreg Roach { 240fe3a9054SGreg Roach return (string) DB::table('session') 241fe3a9054SGreg Roach ->where('session_id', '=', $id) 242fe3a9054SGreg Roach ->value('session_data'); 24357514a4fSGreg Roach } 24457514a4fSGreg Roach 24557514a4fSGreg Roach /** 24657514a4fSGreg Roach * For session_set_save_handler() 24757514a4fSGreg Roach * 24857514a4fSGreg Roach * @param string $id 24957514a4fSGreg Roach * @param string $data 25057514a4fSGreg Roach * 25157514a4fSGreg Roach * @return bool 25257514a4fSGreg Roach */ 253c1010edaSGreg Roach private static function write(string $id, string $data): bool 254c1010edaSGreg Roach { 2554c891c40SGreg Roach $request = Request::createFromGlobals(); 2564c891c40SGreg Roach 257fe3a9054SGreg Roach DB::table('session')->updateOrInsert([ 2584c891c40SGreg Roach 'session_id' => $id, 259fe3a9054SGreg Roach ], [ 260fe3a9054SGreg Roach 'session_time' => Carbon::now(), 2614c891c40SGreg Roach 'user_id' => (int) Auth::id(), 2624c891c40SGreg Roach 'ip_address' => $request->getClientIp(), 263fe3a9054SGreg Roach 'session_data' => $data, 2644c891c40SGreg Roach ]); 26557514a4fSGreg Roach 26657514a4fSGreg Roach return true; 26757514a4fSGreg Roach } 268a45f9889SGreg Roach 269a45f9889SGreg Roach /** 270a45f9889SGreg Roach * Cross-Site Request Forgery tokens - ensure that the user is submitting 271a45f9889SGreg Roach * a form that was generated by the current session. 272a45f9889SGreg Roach * 273a45f9889SGreg Roach * @return string 274a45f9889SGreg Roach */ 2758f53f488SRico Sonntag public static function getCsrfToken(): string 276a45f9889SGreg Roach { 277a45f9889SGreg Roach if (!Session::has('CSRF_TOKEN')) { 278*deb4b685SGreg Roach Session::put('CSRF_TOKEN', Str::random(32)); 279a45f9889SGreg Roach } 280a45f9889SGreg Roach 281a45f9889SGreg Roach return Session::get('CSRF_TOKEN'); 282a45f9889SGreg Roach } 28331bc7874SGreg Roach} 284