xref: /webtrees/app/Session.php (revision fd8bd3bc762a6ba2d4b93537bc7798d0beb4ae07)
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