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 Illuminate\Database\Capsule\Manager as DB; 21deb4b685SGreg Roachuse Illuminate\Support\Str; 22*6ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface; 23fe3a9054SGreg Roachuse function session_status; 244c891c40SGreg Roach 2531bc7874SGreg Roach/** 2642af74e7SGreg Roach * Session handling 2731bc7874SGreg Roach */ 28c1010edaSGreg Roachclass Session 29c1010edaSGreg Roach{ 3031bc7874SGreg Roach /** 3131bc7874SGreg Roach * Start a session 32c7ff4153SGreg Roach * 33c7ff4153SGreg Roach * @return void 3431bc7874SGreg Roach */ 35e364afe4SGreg Roach public static function start(): void 36c1010edaSGreg Roach { 3708df3d18SGreg Roach $domain = ''; 3808df3d18SGreg Roach $path = parse_url(WT_BASE_URL, PHP_URL_PATH); 3908df3d18SGreg Roach $secure = parse_url(WT_BASE_URL, PHP_URL_SCHEME) === 'https'; 4008df3d18SGreg Roach $httponly = true; 4108df3d18SGreg Roach 4208df3d18SGreg Roach // Paths containing UTF-8 characters need special handling. 4308df3d18SGreg Roach $path = implode('/', array_map('rawurlencode', explode('/', $path))); 4408df3d18SGreg Roach 4508df3d18SGreg Roach self::setSaveHandler(); 4608df3d18SGreg Roach 4708df3d18SGreg Roach session_name('WT_SESSION'); 4831bc7874SGreg Roach session_register_shutdown(); 49a0dfa978SGreg Roach session_set_cookie_params(0, $path, $domain, $secure, $httponly); 5031bc7874SGreg Roach session_start(); 5108df3d18SGreg Roach 5208df3d18SGreg Roach // A new session? Prevent session fixation attacks by choosing a new session ID. 5308df3d18SGreg Roach if (!self::get('initiated')) { 5408df3d18SGreg Roach self::regenerate(true); 5508df3d18SGreg Roach self::put('initiated', true); 5608df3d18SGreg Roach } 5731bc7874SGreg Roach } 5831bc7874SGreg Roach 5931bc7874SGreg Roach /** 6057514a4fSGreg Roach * Initialise our session save handler 61c7ff4153SGreg Roach * 62c7ff4153SGreg Roach * @return void 6357514a4fSGreg Roach */ 64e364afe4SGreg Roach private static function setSaveHandler(): void 65c1010edaSGreg Roach { 6657514a4fSGreg Roach session_set_save_handler( 67*6ccdf4f0SGreg Roach static function (): bool { 6857514a4fSGreg Roach return Session::open(); 6957514a4fSGreg Roach }, 70*6ccdf4f0SGreg Roach static function (): bool { 7157514a4fSGreg Roach return Session::close(); 7257514a4fSGreg Roach }, 73*6ccdf4f0SGreg Roach static function (string $id): string { 7457514a4fSGreg Roach return Session::read($id); 7557514a4fSGreg Roach }, 76*6ccdf4f0SGreg Roach static function (string $id, string $data): bool { 7757514a4fSGreg Roach return Session::write($id, $data); 7857514a4fSGreg Roach }, 79*6ccdf4f0SGreg Roach static function (string $id): bool { 8057514a4fSGreg Roach return Session::destroy($id); 8157514a4fSGreg Roach }, 82*6ccdf4f0SGreg Roach static function (int $maxlifetime): bool { 8357514a4fSGreg Roach return Session::gc($maxlifetime); 8457514a4fSGreg Roach } 8557514a4fSGreg Roach ); 8657514a4fSGreg Roach } 8757514a4fSGreg Roach 8857514a4fSGreg Roach /** 8957514a4fSGreg Roach * For session_set_save_handler() 9057514a4fSGreg Roach * 9157514a4fSGreg Roach * @return bool 9257514a4fSGreg Roach */ 93*6ccdf4f0SGreg Roach private static function open(): bool 94*6ccdf4f0SGreg Roach { 95*6ccdf4f0SGreg Roach return true; 96*6ccdf4f0SGreg Roach } 97*6ccdf4f0SGreg Roach 98*6ccdf4f0SGreg Roach /** 99*6ccdf4f0SGreg Roach * For session_set_save_handler() 100*6ccdf4f0SGreg Roach * 101*6ccdf4f0SGreg Roach * @return bool 102*6ccdf4f0SGreg Roach */ 1038f53f488SRico Sonntag private static function close(): bool 104c1010edaSGreg Roach { 10557514a4fSGreg Roach return true; 10657514a4fSGreg Roach } 10757514a4fSGreg Roach 10857514a4fSGreg Roach /** 10957514a4fSGreg Roach * For session_set_save_handler() 11057514a4fSGreg Roach * 11157514a4fSGreg Roach * @param string $id 11257514a4fSGreg Roach * 113*6ccdf4f0SGreg Roach * @return string 114*6ccdf4f0SGreg Roach */ 115*6ccdf4f0SGreg Roach private static function read(string $id): string 116*6ccdf4f0SGreg Roach { 117*6ccdf4f0SGreg Roach return (string) DB::table('session') 118*6ccdf4f0SGreg Roach ->where('session_id', '=', $id) 119*6ccdf4f0SGreg Roach ->value('session_data'); 120*6ccdf4f0SGreg Roach } 121*6ccdf4f0SGreg Roach 122*6ccdf4f0SGreg Roach /** 123*6ccdf4f0SGreg Roach * For session_set_save_handler() 124*6ccdf4f0SGreg Roach * 125*6ccdf4f0SGreg Roach * @param string $id 126*6ccdf4f0SGreg Roach * @param string $data 127*6ccdf4f0SGreg Roach * 128*6ccdf4f0SGreg Roach * @return bool 129*6ccdf4f0SGreg Roach */ 130*6ccdf4f0SGreg Roach private static function write(string $id, string $data): bool 131*6ccdf4f0SGreg Roach { 132*6ccdf4f0SGreg Roach $request = app(ServerRequestInterface::class); 133*6ccdf4f0SGreg Roach 134*6ccdf4f0SGreg Roach DB::table('session')->updateOrInsert([ 135*6ccdf4f0SGreg Roach 'session_id' => $id, 136*6ccdf4f0SGreg Roach ], [ 137*6ccdf4f0SGreg Roach 'session_time' => Carbon::now(), 138*6ccdf4f0SGreg Roach 'user_id' => (int) Auth::id(), 139*6ccdf4f0SGreg Roach 'ip_address' => $request->getClientIp(), 140*6ccdf4f0SGreg Roach 'session_data' => $data, 141*6ccdf4f0SGreg Roach ]); 142*6ccdf4f0SGreg Roach 143*6ccdf4f0SGreg Roach return true; 144*6ccdf4f0SGreg Roach } 145*6ccdf4f0SGreg Roach 146*6ccdf4f0SGreg Roach /** 147*6ccdf4f0SGreg Roach * For session_set_save_handler() 148*6ccdf4f0SGreg Roach * 149*6ccdf4f0SGreg Roach * @param string $id 150*6ccdf4f0SGreg Roach * 15157514a4fSGreg Roach * @return bool 15257514a4fSGreg Roach */ 1538f53f488SRico Sonntag private static function destroy(string $id): bool 154c1010edaSGreg Roach { 155fe3a9054SGreg Roach DB::table('session') 156fe3a9054SGreg Roach ->where('session_id', '=', $id) 157fe3a9054SGreg Roach ->delete(); 15857514a4fSGreg Roach 15957514a4fSGreg Roach return true; 16057514a4fSGreg Roach } 16157514a4fSGreg Roach 16257514a4fSGreg Roach /** 16357514a4fSGreg Roach * For session_set_save_handler() 16457514a4fSGreg Roach * 16557514a4fSGreg Roach * @param int $maxlifetime 16657514a4fSGreg Roach * 16757514a4fSGreg Roach * @return bool 16857514a4fSGreg Roach */ 1698f53f488SRico Sonntag private static function gc(int $maxlifetime): bool 170c1010edaSGreg Roach { 171fe3a9054SGreg Roach DB::table('session') 172fe3a9054SGreg Roach ->where('session_time', '<', Carbon::now()->subSeconds($maxlifetime)) 173fe3a9054SGreg Roach ->delete(); 17457514a4fSGreg Roach 17557514a4fSGreg Roach return true; 17657514a4fSGreg Roach } 17757514a4fSGreg Roach 17857514a4fSGreg Roach /** 179*6ccdf4f0SGreg Roach * Read a value from the session 18057514a4fSGreg Roach * 181*6ccdf4f0SGreg Roach * @param string $name 182*6ccdf4f0SGreg Roach * @param mixed $default 183*6ccdf4f0SGreg Roach * 184*6ccdf4f0SGreg Roach * @return mixed 18557514a4fSGreg Roach */ 186*6ccdf4f0SGreg Roach public static function get(string $name, $default = null) 187c1010edaSGreg Roach { 188*6ccdf4f0SGreg Roach return $_SESSION[$name] ?? $default; 18957514a4fSGreg Roach } 19057514a4fSGreg Roach 19157514a4fSGreg Roach /** 192*6ccdf4f0SGreg Roach * After any change in authentication level, we should use a new session ID. 19357514a4fSGreg Roach * 194*6ccdf4f0SGreg Roach * @param bool $destroy 19557514a4fSGreg Roach * 196*6ccdf4f0SGreg Roach * @return void 19757514a4fSGreg Roach */ 198*6ccdf4f0SGreg Roach public static function regenerate(bool $destroy = false): void 199c1010edaSGreg Roach { 200*6ccdf4f0SGreg Roach if ($destroy) { 201*6ccdf4f0SGreg Roach self::clear(); 202*6ccdf4f0SGreg Roach } 203*6ccdf4f0SGreg Roach 204*6ccdf4f0SGreg Roach if (session_status() === PHP_SESSION_ACTIVE) { 205*6ccdf4f0SGreg Roach session_regenerate_id($destroy); 206*6ccdf4f0SGreg Roach } 20757514a4fSGreg Roach } 20857514a4fSGreg Roach 20957514a4fSGreg Roach /** 210*6ccdf4f0SGreg Roach * Remove all stored data from the session. 211*6ccdf4f0SGreg Roach * 212*6ccdf4f0SGreg Roach * @return void 213*6ccdf4f0SGreg Roach */ 214*6ccdf4f0SGreg Roach public static function clear(): void 215*6ccdf4f0SGreg Roach { 216*6ccdf4f0SGreg Roach $_SESSION = []; 217*6ccdf4f0SGreg Roach } 218*6ccdf4f0SGreg Roach 219*6ccdf4f0SGreg Roach /** 220*6ccdf4f0SGreg Roach * Write a value to the session 221*6ccdf4f0SGreg Roach * 222*6ccdf4f0SGreg Roach * @param string $name 223*6ccdf4f0SGreg Roach * @param mixed $value 224*6ccdf4f0SGreg Roach * 225*6ccdf4f0SGreg Roach * @return void 226*6ccdf4f0SGreg Roach */ 227*6ccdf4f0SGreg Roach public static function put(string $name, $value): void 228*6ccdf4f0SGreg Roach { 229*6ccdf4f0SGreg Roach $_SESSION[$name] = $value; 230*6ccdf4f0SGreg Roach } 231*6ccdf4f0SGreg Roach 232*6ccdf4f0SGreg Roach /** 233*6ccdf4f0SGreg Roach * Remove a value from the session 234*6ccdf4f0SGreg Roach * 235*6ccdf4f0SGreg Roach * @param string $name 236*6ccdf4f0SGreg Roach * 237*6ccdf4f0SGreg Roach * @return void 238*6ccdf4f0SGreg Roach */ 239*6ccdf4f0SGreg Roach public static function forget(string $name): void 240*6ccdf4f0SGreg Roach { 241*6ccdf4f0SGreg Roach unset($_SESSION[$name]); 242*6ccdf4f0SGreg Roach } 243*6ccdf4f0SGreg Roach 244*6ccdf4f0SGreg Roach /** 245*6ccdf4f0SGreg Roach * Set an explicit session ID. Typically used for search robots. 24657514a4fSGreg Roach * 24757514a4fSGreg Roach * @param string $id 24857514a4fSGreg Roach * 249*6ccdf4f0SGreg Roach * @return void 25057514a4fSGreg Roach */ 251*6ccdf4f0SGreg Roach public static function setId(string $id): void 252c1010edaSGreg Roach { 253*6ccdf4f0SGreg Roach session_id($id); 25457514a4fSGreg Roach } 255a45f9889SGreg Roach 256a45f9889SGreg Roach /** 257a45f9889SGreg Roach * Cross-Site Request Forgery tokens - ensure that the user is submitting 258a45f9889SGreg Roach * a form that was generated by the current session. 259a45f9889SGreg Roach * 260a45f9889SGreg Roach * @return string 261a45f9889SGreg Roach */ 2628f53f488SRico Sonntag public static function getCsrfToken(): string 263a45f9889SGreg Roach { 264e364afe4SGreg Roach if (!self::has('CSRF_TOKEN')) { 265e364afe4SGreg Roach self::put('CSRF_TOKEN', Str::random(32)); 266a45f9889SGreg Roach } 267a45f9889SGreg Roach 268e364afe4SGreg Roach return self::get('CSRF_TOKEN'); 269a45f9889SGreg Roach } 270*6ccdf4f0SGreg Roach 271*6ccdf4f0SGreg Roach /** 272*6ccdf4f0SGreg Roach * Does a session variable exist? 273*6ccdf4f0SGreg Roach * 274*6ccdf4f0SGreg Roach * @param string $name 275*6ccdf4f0SGreg Roach * 276*6ccdf4f0SGreg Roach * @return bool 277*6ccdf4f0SGreg Roach */ 278*6ccdf4f0SGreg Roach public static function has(string $name): bool 279*6ccdf4f0SGreg Roach { 280*6ccdf4f0SGreg Roach return isset($_SESSION[$name]); 281*6ccdf4f0SGreg Roach } 28231bc7874SGreg Roach} 283