1<?php 2/** 3 * webtrees: online genealogy 4 * Copyright (C) 2019 webtrees development team 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * You should have received a copy of the GNU General Public License 14 * along with this program. If not, see <http://www.gnu.org/licenses/>. 15 */ 16declare(strict_types=1); 17 18namespace Fisharebest\Webtrees; 19 20use Illuminate\Database\Capsule\Manager as DB; 21use Illuminate\Support\Str; 22use Psr\Http\Message\ServerRequestInterface; 23use function session_status; 24 25/** 26 * Session handling 27 */ 28class Session 29{ 30 /** 31 * Start a session 32 * 33 * @return void 34 */ 35 public static function start(): void 36 { 37 $domain = ''; 38 $path = parse_url(WT_BASE_URL, PHP_URL_PATH); 39 $secure = parse_url(WT_BASE_URL, PHP_URL_SCHEME) === 'https'; 40 $httponly = true; 41 42 // Paths containing UTF-8 characters need special handling. 43 $path = implode('/', array_map('rawurlencode', explode('/', $path))); 44 45 self::setSaveHandler(); 46 47 session_name('WT_SESSION'); 48 session_register_shutdown(); 49 session_set_cookie_params(0, $path, $domain, $secure, $httponly); 50 session_start(); 51 52 // A new session? Prevent session fixation attacks by choosing a new session ID. 53 if (!self::get('initiated')) { 54 self::regenerate(true); 55 self::put('initiated', true); 56 } 57 } 58 59 /** 60 * Initialise our session save handler 61 * 62 * @return void 63 */ 64 private static function setSaveHandler(): void 65 { 66 session_set_save_handler( 67 static function (): bool { 68 return Session::open(); 69 }, 70 static function (): bool { 71 return Session::close(); 72 }, 73 static function (string $id): string { 74 return Session::read($id); 75 }, 76 static function (string $id, string $data): bool { 77 return Session::write($id, $data); 78 }, 79 static function (string $id): bool { 80 return Session::destroy($id); 81 }, 82 static function (int $maxlifetime): bool { 83 return Session::gc($maxlifetime); 84 } 85 ); 86 } 87 88 /** 89 * For session_set_save_handler() 90 * 91 * @return bool 92 */ 93 private static function open(): bool 94 { 95 return true; 96 } 97 98 /** 99 * For session_set_save_handler() 100 * 101 * @return bool 102 */ 103 private static function close(): bool 104 { 105 return true; 106 } 107 108 /** 109 * For session_set_save_handler() 110 * 111 * @param string $id 112 * 113 * @return string 114 */ 115 private static function read(string $id): string 116 { 117 return (string) DB::table('session') 118 ->where('session_id', '=', $id) 119 ->value('session_data'); 120 } 121 122 /** 123 * For session_set_save_handler() 124 * 125 * @param string $id 126 * @param string $data 127 * 128 * @return bool 129 */ 130 private static function write(string $id, string $data): bool 131 { 132 $request = app(ServerRequestInterface::class); 133 134 DB::table('session')->updateOrInsert([ 135 'session_id' => $id, 136 ], [ 137 'session_time' => Carbon::now(), 138 'user_id' => (int) Auth::id(), 139 'ip_address' => $request->getClientIp(), 140 'session_data' => $data, 141 ]); 142 143 return true; 144 } 145 146 /** 147 * For session_set_save_handler() 148 * 149 * @param string $id 150 * 151 * @return bool 152 */ 153 private static function destroy(string $id): bool 154 { 155 DB::table('session') 156 ->where('session_id', '=', $id) 157 ->delete(); 158 159 return true; 160 } 161 162 /** 163 * For session_set_save_handler() 164 * 165 * @param int $maxlifetime 166 * 167 * @return bool 168 */ 169 private static function gc(int $maxlifetime): bool 170 { 171 DB::table('session') 172 ->where('session_time', '<', Carbon::now()->subSeconds($maxlifetime)) 173 ->delete(); 174 175 return true; 176 } 177 178 /** 179 * Read a value from the session 180 * 181 * @param string $name 182 * @param mixed $default 183 * 184 * @return mixed 185 */ 186 public static function get(string $name, $default = null) 187 { 188 return $_SESSION[$name] ?? $default; 189 } 190 191 /** 192 * After any change in authentication level, we should use a new session ID. 193 * 194 * @param bool $destroy 195 * 196 * @return void 197 */ 198 public static function regenerate(bool $destroy = false): void 199 { 200 if ($destroy) { 201 self::clear(); 202 } 203 204 if (session_status() === PHP_SESSION_ACTIVE) { 205 session_regenerate_id($destroy); 206 } 207 } 208 209 /** 210 * Remove all stored data from the session. 211 * 212 * @return void 213 */ 214 public static function clear(): void 215 { 216 $_SESSION = []; 217 } 218 219 /** 220 * Write a value to the session 221 * 222 * @param string $name 223 * @param mixed $value 224 * 225 * @return void 226 */ 227 public static function put(string $name, $value): void 228 { 229 $_SESSION[$name] = $value; 230 } 231 232 /** 233 * Remove a value from the session 234 * 235 * @param string $name 236 * 237 * @return void 238 */ 239 public static function forget(string $name): void 240 { 241 unset($_SESSION[$name]); 242 } 243 244 /** 245 * Set an explicit session ID. Typically used for search robots. 246 * 247 * @param string $id 248 * 249 * @return void 250 */ 251 public static function setId(string $id): void 252 { 253 session_id($id); 254 } 255 256 /** 257 * Cross-Site Request Forgery tokens - ensure that the user is submitting 258 * a form that was generated by the current session. 259 * 260 * @return string 261 */ 262 public static function getCsrfToken(): string 263 { 264 if (!self::has('CSRF_TOKEN')) { 265 self::put('CSRF_TOKEN', Str::random(32)); 266 } 267 268 return self::get('CSRF_TOKEN'); 269 } 270 271 /** 272 * Does a session variable exist? 273 * 274 * @param string $name 275 * 276 * @return bool 277 */ 278 public static function has(string $name): bool 279 { 280 return isset($_SESSION[$name]); 281 } 282} 283