Passed
Push — main ( 2b512f...2c6f1b )
by Greg
21:20 queued 13:11
created

Session::clear()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2023 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 <https://www.gnu.org/licenses/>.
16
 */
17
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees;
21
22
use Illuminate\Support\Str;
23
use Psr\Http\Message\ServerRequestInterface;
24
25
use function array_map;
26
use function explode;
27
use function implode;
28
use function is_string;
29
use function parse_url;
30
use function rawurlencode;
31
use function session_name;
32
use function session_regenerate_id;
33
use function session_register_shutdown;
34
use function session_set_cookie_params;
35
use function session_set_save_handler;
36
use function session_start;
37
use function session_status;
38
use function session_write_close;
39
40
use const PHP_SESSION_ACTIVE;
41
use const PHP_URL_HOST;
42
use const PHP_URL_PATH;
43
use const PHP_URL_SCHEME;
44
45
/**
46
 * Session handling
47
 */
48
class Session
49
{
50
    // Use the secure prefix with HTTPS.
51
    private const SESSION_NAME        = 'WT2_SESSION';
52
    private const SECURE_SESSION_NAME = '__Secure-WT-ID';
53
54
    /**
55
     * Start a session
56
     *
57
     * @param ServerRequestInterface $request
58
     *
59
     * @return void
60
     */
61
    public static function start(ServerRequestInterface $request): void
62
    {
63
        // Store sessions in the database
64
        session_set_save_handler(new SessionDatabaseHandler($request));
65
66
        $url    = Validator::attributes($request)->string('base_url');
67
        $secure = parse_url($url, PHP_URL_SCHEME) === 'https';
68
        $domain = (string) parse_url($url, PHP_URL_HOST);
69
        $path   = (string) parse_url($url, PHP_URL_PATH);
70
71
        // Paths containing UTF-8 characters need special handling.
72
        $path = implode('/', array_map(static fn (string $x): string => rawurlencode($x), explode('/', $path)));
73
74
        session_name($secure ? self::SECURE_SESSION_NAME : self::SESSION_NAME);
75
        session_register_shutdown();
76
        session_set_cookie_params([
77
            'lifetime' => 0,
78
            'path'     => $path . '/',
79
            'domain'   => $domain,
80
            'secure'   => $secure,
81
            'httponly' => true,
82
            'samesite' => 'Lax',
83
        ]);
84
        session_start();
85
86
        // A new session? Prevent session fixation attacks by choosing a new session ID.
87
        if (self::get('initiated') !== true) {
88
            self::regenerate(true);
89
            self::put('initiated', true);
90
        }
91
    }
92
93
    /**
94
     * Save/close the session.  This releases the session lock.
95
     * Closing early can help concurrent connections.
96
     */
97
    public static function save(): void
98
    {
99
        if (session_status() === PHP_SESSION_ACTIVE) {
100
            session_write_close();
101
        }
102
    }
103
104
    /**
105
     * Read a value from the session
106
     *
107
     * @param string $name
108
     * @param mixed  $default
109
     *
110
     * @return mixed
111
     */
112
    public static function get(string $name, $default = null)
113
    {
114
        return $_SESSION[$name] ?? $default;
115
    }
116
117
    /**
118
     * Read a value from the session and remove it.
119
     *
120
     * @param string $name
121
     *
122
     * @return mixed
123
     */
124
    public static function pull(string $name)
125
    {
126
        $value = self::get($name);
127
        self::forget($name);
128
129
        return $value;
130
    }
131
132
    /**
133
     * After any change in authentication level, we should use a new session ID.
134
     *
135
     * @param bool $destroy
136
     *
137
     * @return void
138
     */
139
    public static function regenerate(bool $destroy = false): void
140
    {
141
        if ($destroy) {
142
            self::clear();
143
        }
144
145
        if (session_status() === PHP_SESSION_ACTIVE) {
146
            session_regenerate_id($destroy);
147
        }
148
    }
149
150
    /**
151
     * Remove all stored data from the session.
152
     *
153
     * @return void
154
     */
155
    public static function clear(): void
156
    {
157
        $_SESSION = [];
158
    }
159
160
    /**
161
     * Write a value to the session
162
     *
163
     * @param string $name
164
     * @param mixed  $value
165
     *
166
     * @return void
167
     */
168
    public static function put(string $name, $value): void
169
    {
170
        $_SESSION[$name] = $value;
171
    }
172
173
    /**
174
     * Remove a value from the session
175
     *
176
     * @param string $name
177
     *
178
     * @return void
179
     */
180
    public static function forget(string $name): void
181
    {
182
        unset($_SESSION[$name]);
183
    }
184
185
    /**
186
     * Cross-Site Request Forgery tokens - ensure that the user is submitting
187
     * a form that was generated by the current session.
188
     *
189
     * @return string
190
     */
191
    public static function getCsrfToken(): string
192
    {
193
        $csrf_token = self::get('CSRF_TOKEN');
194
195
        if (is_string($csrf_token)) {
196
            return $csrf_token;
197
        }
198
199
        $csrf_token = Str::random(32);
200
201
        self::put('CSRF_TOKEN', $csrf_token);
202
203
        return $csrf_token;
204
    }
205
206
    /**
207
     * Does a session variable exist?
208
     *
209
     * @param string $name
210
     *
211
     * @return bool
212
     */
213
    public static function has(string $name): bool
214
    {
215
        return isset($_SESSION[$name]);
216
    }
217
}
218