xref: /webtrees/app/Session.php (revision d11be7027e34e3121be11cc025421873364403f9)
131bc7874SGreg Roach<?php
23976b470SGreg Roach
331bc7874SGreg Roach/**
431bc7874SGreg Roach * webtrees: online genealogy
5*d11be702SGreg Roach * Copyright (C) 2023 webtrees development team
631bc7874SGreg Roach * This program is free software: you can redistribute it and/or modify
731bc7874SGreg Roach * it under the terms of the GNU General Public License as published by
831bc7874SGreg Roach * the Free Software Foundation, either version 3 of the License, or
931bc7874SGreg Roach * (at your option) any later version.
1031bc7874SGreg Roach * This program is distributed in the hope that it will be useful,
1131bc7874SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of
1231bc7874SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1331bc7874SGreg Roach * GNU General Public License for more details.
1431bc7874SGreg Roach * You should have received a copy of the GNU General Public License
1589f7189bSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>.
1631bc7874SGreg Roach */
17fcfa147eSGreg Roach
18e7f56f2aSGreg Roachdeclare(strict_types=1);
19e7f56f2aSGreg Roach
2076692c8bSGreg Roachnamespace Fisharebest\Webtrees;
2131bc7874SGreg Roach
22deb4b685SGreg Roachuse Illuminate\Support\Str;
236ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface;
243976b470SGreg Roach
254d7dd147SGreg Roachuse function array_map;
264d7dd147SGreg Roachuse function explode;
274d7dd147SGreg Roachuse function implode;
28d8809d62SGreg Roachuse function is_string;
294d7dd147SGreg Roachuse function parse_url;
301a9891fdSGreg Roachuse function rawurlencode;
314d7dd147SGreg Roachuse function session_name;
324d7dd147SGreg Roachuse function session_regenerate_id;
334d7dd147SGreg Roachuse function session_register_shutdown;
344d7dd147SGreg Roachuse function session_set_cookie_params;
354d7dd147SGreg Roachuse function session_set_save_handler;
364d7dd147SGreg Roachuse function session_start;
37fe3a9054SGreg Roachuse function session_status;
38dc6b8e0eSGreg Roachuse function session_write_close;
393976b470SGreg Roach
40dc6b8e0eSGreg Roachuse const PHP_SESSION_ACTIVE;
414d7dd147SGreg Roachuse const PHP_URL_HOST;
424d7dd147SGreg Roachuse const PHP_URL_PATH;
434d7dd147SGreg Roachuse const PHP_URL_SCHEME;
444c891c40SGreg Roach
4531bc7874SGreg Roach/**
4642af74e7SGreg Roach * Session handling
4731bc7874SGreg Roach */
48c1010edaSGreg Roachclass Session
49c1010edaSGreg Roach{
50a51e953eSGreg Roach    // Use the secure prefix with HTTPS.
519683b471SGreg Roach    private const SESSION_NAME        = 'WT2_SESSION';
52a51e953eSGreg Roach    private const SECURE_SESSION_NAME = '__Secure-WT-ID';
53dc6b8e0eSGreg Roach
5431bc7874SGreg Roach    /**
5531bc7874SGreg Roach     * Start a session
56c7ff4153SGreg Roach     *
574d7dd147SGreg Roach     * @param ServerRequestInterface $request
584d7dd147SGreg Roach     *
59c7ff4153SGreg Roach     * @return void
6031bc7874SGreg Roach     */
614d7dd147SGreg Roach    public static function start(ServerRequestInterface $request): void
62c1010edaSGreg Roach    {
634d7dd147SGreg Roach        // Store sessions in the database
644d7dd147SGreg Roach        session_set_save_handler(new SessionDatabaseHandler($request));
654d7dd147SGreg Roach
66b55cbc6bSGreg Roach        $url    = Validator::attributes($request)->string('base_url');
674d7dd147SGreg Roach        $secure = parse_url($url, PHP_URL_SCHEME) === 'https';
684831cc24SGreg Roach        $domain = (string) parse_url($url, PHP_URL_HOST);
694831cc24SGreg Roach        $path   = (string) parse_url($url, PHP_URL_PATH);
7008df3d18SGreg Roach
7108df3d18SGreg Roach        // Paths containing UTF-8 characters need special handling.
7205babb96SGreg Roach        $path = implode('/', array_map(static fn (string $x): string => rawurlencode($x), explode('/', $path)));
7308df3d18SGreg Roach
74a51e953eSGreg Roach        session_name($secure ? self::SECURE_SESSION_NAME : self::SESSION_NAME);
7531bc7874SGreg Roach        session_register_shutdown();
7618c571c3SGreg Roach        session_set_cookie_params([
7718c571c3SGreg Roach            'lifetime' => 0,
7818c571c3SGreg Roach            'path'     => $path . '/',
7918c571c3SGreg Roach            'domain'   => $domain,
8018c571c3SGreg Roach            'secure'   => $secure,
8118c571c3SGreg Roach            'httponly' => true,
8218c571c3SGreg Roach            'samesite' => 'Lax',
8318c571c3SGreg Roach        ]);
8431bc7874SGreg Roach        session_start();
8508df3d18SGreg Roach
8608df3d18SGreg Roach        // A new session? Prevent session fixation attacks by choosing a new session ID.
87b262b3d3SGreg Roach        if (self::get('initiated') !== true) {
8808df3d18SGreg Roach            self::regenerate(true);
8908df3d18SGreg Roach            self::put('initiated', true);
9008df3d18SGreg Roach        }
9131bc7874SGreg Roach    }
9231bc7874SGreg Roach
9331bc7874SGreg Roach    /**
94dc6b8e0eSGreg Roach     * Save/close the session.  This releases the session lock.
95dc6b8e0eSGreg Roach     * Closing early can help concurrent connections.
96dc6b8e0eSGreg Roach     */
97dc6b8e0eSGreg Roach    public static function save(): void
98dc6b8e0eSGreg Roach    {
99dc6b8e0eSGreg Roach        if (session_status() === PHP_SESSION_ACTIVE) {
100dc6b8e0eSGreg Roach            session_write_close();
101dc6b8e0eSGreg Roach        }
102dc6b8e0eSGreg Roach    }
103dc6b8e0eSGreg Roach
104dc6b8e0eSGreg Roach    /**
1056ccdf4f0SGreg Roach     * Read a value from the session
10657514a4fSGreg Roach     *
1076ccdf4f0SGreg Roach     * @param string $name
1086ccdf4f0SGreg Roach     * @param mixed  $default
1096ccdf4f0SGreg Roach     *
1106ccdf4f0SGreg Roach     * @return mixed
11157514a4fSGreg Roach     */
1126ccdf4f0SGreg Roach    public static function get(string $name, $default = null)
113c1010edaSGreg Roach    {
1146ccdf4f0SGreg Roach        return $_SESSION[$name] ?? $default;
11557514a4fSGreg Roach    }
11657514a4fSGreg Roach
11757514a4fSGreg Roach    /**
118fd8bd3bcSGreg Roach     * Read a value from the session and remove it.
119fd8bd3bcSGreg Roach     *
120fd8bd3bcSGreg Roach     * @param string $name
121fd8bd3bcSGreg Roach     *
122fd8bd3bcSGreg Roach     * @return mixed
123fd8bd3bcSGreg Roach     */
124d8809d62SGreg Roach    public static function pull(string $name)
125fd8bd3bcSGreg Roach    {
126d8809d62SGreg Roach        $value = self::get($name);
127fd8bd3bcSGreg Roach        self::forget($name);
128fd8bd3bcSGreg Roach
129fd8bd3bcSGreg Roach        return $value;
130fd8bd3bcSGreg Roach    }
131fd8bd3bcSGreg Roach
132fd8bd3bcSGreg Roach    /**
1336ccdf4f0SGreg Roach     * After any change in authentication level, we should use a new session ID.
13457514a4fSGreg Roach     *
1356ccdf4f0SGreg Roach     * @param bool $destroy
13657514a4fSGreg Roach     *
1376ccdf4f0SGreg Roach     * @return void
13857514a4fSGreg Roach     */
1396ccdf4f0SGreg Roach    public static function regenerate(bool $destroy = false): void
140c1010edaSGreg Roach    {
1416ccdf4f0SGreg Roach        if ($destroy) {
1426ccdf4f0SGreg Roach            self::clear();
1436ccdf4f0SGreg Roach        }
1446ccdf4f0SGreg Roach
1456ccdf4f0SGreg Roach        if (session_status() === PHP_SESSION_ACTIVE) {
1466ccdf4f0SGreg Roach            session_regenerate_id($destroy);
1476ccdf4f0SGreg Roach        }
14857514a4fSGreg Roach    }
14957514a4fSGreg Roach
15057514a4fSGreg Roach    /**
1516ccdf4f0SGreg Roach     * Remove all stored data from the session.
1526ccdf4f0SGreg Roach     *
1536ccdf4f0SGreg Roach     * @return void
1546ccdf4f0SGreg Roach     */
1556ccdf4f0SGreg Roach    public static function clear(): void
1566ccdf4f0SGreg Roach    {
1576ccdf4f0SGreg Roach        $_SESSION = [];
1586ccdf4f0SGreg Roach    }
1596ccdf4f0SGreg Roach
1606ccdf4f0SGreg Roach    /**
1616ccdf4f0SGreg Roach     * Write a value to the session
1626ccdf4f0SGreg Roach     *
1636ccdf4f0SGreg Roach     * @param string $name
1646ccdf4f0SGreg Roach     * @param mixed  $value
1656ccdf4f0SGreg Roach     *
1666ccdf4f0SGreg Roach     * @return void
1676ccdf4f0SGreg Roach     */
1686ccdf4f0SGreg Roach    public static function put(string $name, $value): void
1696ccdf4f0SGreg Roach    {
1706ccdf4f0SGreg Roach        $_SESSION[$name] = $value;
1716ccdf4f0SGreg Roach    }
1726ccdf4f0SGreg Roach
1736ccdf4f0SGreg Roach    /**
1746ccdf4f0SGreg Roach     * Remove a value from the session
1756ccdf4f0SGreg Roach     *
1766ccdf4f0SGreg Roach     * @param string $name
1776ccdf4f0SGreg Roach     *
1786ccdf4f0SGreg Roach     * @return void
1796ccdf4f0SGreg Roach     */
1806ccdf4f0SGreg Roach    public static function forget(string $name): void
1816ccdf4f0SGreg Roach    {
1826ccdf4f0SGreg Roach        unset($_SESSION[$name]);
1836ccdf4f0SGreg Roach    }
1846ccdf4f0SGreg Roach
1856ccdf4f0SGreg Roach    /**
186a45f9889SGreg Roach     * Cross-Site Request Forgery tokens - ensure that the user is submitting
187a45f9889SGreg Roach     * a form that was generated by the current session.
188a45f9889SGreg Roach     *
189a45f9889SGreg Roach     * @return string
190a45f9889SGreg Roach     */
1918f53f488SRico Sonntag    public static function getCsrfToken(): string
192a45f9889SGreg Roach    {
193d8809d62SGreg Roach        $csrf_token = self::get('CSRF_TOKEN');
194d8809d62SGreg Roach
195d8809d62SGreg Roach        if (is_string($csrf_token)) {
196d8809d62SGreg Roach            return $csrf_token;
197a45f9889SGreg Roach        }
198a45f9889SGreg Roach
199d8809d62SGreg Roach        $csrf_token = Str::random(32);
200d8809d62SGreg Roach
201d8809d62SGreg Roach        self::put('CSRF_TOKEN', $csrf_token);
202d8809d62SGreg Roach
203d8809d62SGreg Roach        return $csrf_token;
204a45f9889SGreg Roach    }
2056ccdf4f0SGreg Roach
2066ccdf4f0SGreg Roach    /**
2076ccdf4f0SGreg Roach     * Does a session variable exist?
2086ccdf4f0SGreg Roach     *
2096ccdf4f0SGreg Roach     * @param string $name
2106ccdf4f0SGreg Roach     *
2116ccdf4f0SGreg Roach     * @return bool
2126ccdf4f0SGreg Roach     */
2136ccdf4f0SGreg Roach    public static function has(string $name): bool
2146ccdf4f0SGreg Roach    {
2156ccdf4f0SGreg Roach        return isset($_SESSION[$name]);
2166ccdf4f0SGreg Roach    }
21731bc7874SGreg Roach}
218