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