1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2019 webtrees development team 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18declare(strict_types=1); 19 20namespace Fisharebest\Webtrees; 21 22use Illuminate\Support\Str; 23use Psr\Http\Message\ServerRequestInterface; 24 25use function array_map; 26use function explode; 27use function implode; 28use function parse_url; 29use function session_name; 30use function session_regenerate_id; 31use function session_register_shutdown; 32use function session_set_cookie_params; 33use function session_set_save_handler; 34use function session_start; 35use function session_status; 36 37use const PHP_URL_HOST; 38use const PHP_URL_PATH; 39use const PHP_URL_SCHEME; 40 41/** 42 * Session handling 43 */ 44class Session 45{ 46 private const SESSION_NAME = 'WT2_SESSION'; 47 /** 48 * Start a session 49 * 50 * @param ServerRequestInterface $request 51 * 52 * @return void 53 */ 54 public static function start(ServerRequestInterface $request): void 55 { 56 // Store sessions in the database 57 session_set_save_handler(new SessionDatabaseHandler($request)); 58 59 $url = $request->getAttribute('base_url'); 60 $secure = parse_url($url, PHP_URL_SCHEME) === 'https'; 61 $domain = parse_url($url, PHP_URL_HOST); 62 $path = parse_url($url, PHP_URL_PATH) ?? ''; 63 64 // Paths containing UTF-8 characters need special handling. 65 $path = implode('/', array_map('rawurlencode', explode('/', $path))); 66 67 session_name(self::SESSION_NAME); 68 session_register_shutdown(); 69 session_set_cookie_params(0, $path . '/', $domain, $secure, true); 70 session_start(); 71 72 // A new session? Prevent session fixation attacks by choosing a new session ID. 73 if (self::get('initiated') !== true) { 74 self::regenerate(true); 75 self::put('initiated', true); 76 } 77 } 78 79 /** 80 * Read a value from the session 81 * 82 * @param string $name 83 * @param mixed $default 84 * 85 * @return mixed 86 */ 87 public static function get(string $name, $default = null) 88 { 89 return $_SESSION[$name] ?? $default; 90 } 91 92 /** 93 * Read a value from the session and remove it. 94 * 95 * @param string $name 96 * @param mixed $default 97 * 98 * @return mixed 99 */ 100 public static function pull(string $name, $default = null) 101 { 102 $value = self::get($name, $default); 103 self::forget($name); 104 105 return $value; 106 } 107 108 /** 109 * After any change in authentication level, we should use a new session ID. 110 * 111 * @param bool $destroy 112 * 113 * @return void 114 */ 115 public static function regenerate(bool $destroy = false): void 116 { 117 if ($destroy) { 118 self::clear(); 119 } 120 121 if (session_status() === PHP_SESSION_ACTIVE) { 122 session_regenerate_id($destroy); 123 } 124 } 125 126 /** 127 * Remove all stored data from the session. 128 * 129 * @return void 130 */ 131 public static function clear(): void 132 { 133 $_SESSION = []; 134 } 135 136 /** 137 * Write a value to the session 138 * 139 * @param string $name 140 * @param mixed $value 141 * 142 * @return void 143 */ 144 public static function put(string $name, $value): void 145 { 146 $_SESSION[$name] = $value; 147 } 148 149 /** 150 * Remove a value from the session 151 * 152 * @param string $name 153 * 154 * @return void 155 */ 156 public static function forget(string $name): void 157 { 158 unset($_SESSION[$name]); 159 } 160 161 /** 162 * Cross-Site Request Forgery tokens - ensure that the user is submitting 163 * a form that was generated by the current session. 164 * 165 * @return string 166 */ 167 public static function getCsrfToken(): string 168 { 169 if (!self::has('CSRF_TOKEN')) { 170 self::put('CSRF_TOKEN', Str::random(32)); 171 } 172 173 return self::get('CSRF_TOKEN'); 174 } 175 176 /** 177 * Does a session variable exist? 178 * 179 * @param string $name 180 * 181 * @return bool 182 */ 183 public static function has(string $name): bool 184 { 185 return isset($_SESSION[$name]); 186 } 187} 188