xref: /webtrees/app/Session.php (revision dc6b8e0e34c11d8c180e3f025ad944f76f47e05d)
131bc7874SGreg Roach<?php
23976b470SGreg Roach
331bc7874SGreg Roach/**
431bc7874SGreg Roach * webtrees: online genealogy
58fcd0d32SGreg Roach * Copyright (C) 2019 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
1531bc7874SGreg Roach * along with this program. If not, see <http://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;
284d7dd147SGreg Roachuse function parse_url;
294d7dd147SGreg Roachuse function session_name;
304d7dd147SGreg Roachuse function session_regenerate_id;
314d7dd147SGreg Roachuse function session_register_shutdown;
324d7dd147SGreg Roachuse function session_set_cookie_params;
334d7dd147SGreg Roachuse function session_set_save_handler;
344d7dd147SGreg Roachuse function session_start;
35fe3a9054SGreg Roachuse function session_status;
36*dc6b8e0eSGreg Roachuse function session_write_close;
373976b470SGreg Roach
38*dc6b8e0eSGreg Roachuse const PHP_SESSION_ACTIVE;
394d7dd147SGreg Roachuse const PHP_URL_HOST;
404d7dd147SGreg Roachuse const PHP_URL_PATH;
414d7dd147SGreg Roachuse const PHP_URL_SCHEME;
424c891c40SGreg Roach
4331bc7874SGreg Roach/**
4442af74e7SGreg Roach * Session handling
4531bc7874SGreg Roach */
46c1010edaSGreg Roachclass Session
47c1010edaSGreg Roach{
489683b471SGreg Roach    private const SESSION_NAME = 'WT2_SESSION';
49*dc6b8e0eSGreg Roach
5031bc7874SGreg Roach    /**
5131bc7874SGreg Roach     * Start a session
52c7ff4153SGreg Roach     *
534d7dd147SGreg Roach     * @param ServerRequestInterface $request
544d7dd147SGreg Roach     *
55c7ff4153SGreg Roach     * @return void
5631bc7874SGreg Roach     */
574d7dd147SGreg Roach    public static function start(ServerRequestInterface $request): void
58c1010edaSGreg Roach    {
594d7dd147SGreg Roach        // Store sessions in the database
604d7dd147SGreg Roach        session_set_save_handler(new SessionDatabaseHandler($request));
614d7dd147SGreg Roach
629e5d8e6fSGreg Roach        $url    = $request->getAttribute('base_url');
634d7dd147SGreg Roach        $secure = parse_url($url, PHP_URL_SCHEME) === 'https';
644d7dd147SGreg Roach        $domain = parse_url($url, PHP_URL_HOST);
659e5d8e6fSGreg Roach        $path   = parse_url($url, PHP_URL_PATH) ?? '';
6608df3d18SGreg Roach
6708df3d18SGreg Roach        // Paths containing UTF-8 characters need special handling.
6808df3d18SGreg Roach        $path = implode('/', array_map('rawurlencode', explode('/', $path)));
6908df3d18SGreg Roach
709683b471SGreg Roach        session_name(self::SESSION_NAME);
7131bc7874SGreg Roach        session_register_shutdown();
72cf7dfd3dSGreg Roach        session_set_cookie_params(0, $path . '/', $domain, $secure, true);
7331bc7874SGreg Roach        session_start();
7408df3d18SGreg Roach
7508df3d18SGreg Roach        // A new session? Prevent session fixation attacks by choosing a new session ID.
76b262b3d3SGreg Roach        if (self::get('initiated') !== true) {
7708df3d18SGreg Roach            self::regenerate(true);
7808df3d18SGreg Roach            self::put('initiated', true);
7908df3d18SGreg Roach        }
8031bc7874SGreg Roach    }
8131bc7874SGreg Roach
8231bc7874SGreg Roach    /**
83*dc6b8e0eSGreg Roach     * Save/close the session.  This releases the session lock.
84*dc6b8e0eSGreg Roach     * Closing early can help concurrent connections.
85*dc6b8e0eSGreg Roach     */
86*dc6b8e0eSGreg Roach    public static function save(): void
87*dc6b8e0eSGreg Roach    {
88*dc6b8e0eSGreg Roach        if (session_status() === PHP_SESSION_ACTIVE) {
89*dc6b8e0eSGreg Roach            session_write_close();
90*dc6b8e0eSGreg Roach        }
91*dc6b8e0eSGreg Roach    }
92*dc6b8e0eSGreg Roach
93*dc6b8e0eSGreg Roach    /**
946ccdf4f0SGreg Roach     * Read a value from the session
9557514a4fSGreg Roach     *
966ccdf4f0SGreg Roach     * @param string $name
976ccdf4f0SGreg Roach     * @param mixed  $default
986ccdf4f0SGreg Roach     *
996ccdf4f0SGreg Roach     * @return mixed
10057514a4fSGreg Roach     */
1016ccdf4f0SGreg Roach    public static function get(string $name, $default = null)
102c1010edaSGreg Roach    {
1036ccdf4f0SGreg Roach        return $_SESSION[$name] ?? $default;
10457514a4fSGreg Roach    }
10557514a4fSGreg Roach
10657514a4fSGreg Roach    /**
107fd8bd3bcSGreg Roach     * Read a value from the session and remove it.
108fd8bd3bcSGreg Roach     *
109fd8bd3bcSGreg Roach     * @param string $name
110fd8bd3bcSGreg Roach     * @param mixed  $default
111fd8bd3bcSGreg Roach     *
112fd8bd3bcSGreg Roach     * @return mixed
113fd8bd3bcSGreg Roach     */
114fd8bd3bcSGreg Roach    public static function pull(string $name, $default = null)
115fd8bd3bcSGreg Roach    {
116fd8bd3bcSGreg Roach        $value = self::get($name, $default);
117fd8bd3bcSGreg Roach        self::forget($name);
118fd8bd3bcSGreg Roach
119fd8bd3bcSGreg Roach        return $value;
120fd8bd3bcSGreg Roach    }
121fd8bd3bcSGreg Roach
122fd8bd3bcSGreg Roach    /**
1236ccdf4f0SGreg Roach     * After any change in authentication level, we should use a new session ID.
12457514a4fSGreg Roach     *
1256ccdf4f0SGreg Roach     * @param bool $destroy
12657514a4fSGreg Roach     *
1276ccdf4f0SGreg Roach     * @return void
12857514a4fSGreg Roach     */
1296ccdf4f0SGreg Roach    public static function regenerate(bool $destroy = false): void
130c1010edaSGreg Roach    {
1316ccdf4f0SGreg Roach        if ($destroy) {
1326ccdf4f0SGreg Roach            self::clear();
1336ccdf4f0SGreg Roach        }
1346ccdf4f0SGreg Roach
1356ccdf4f0SGreg Roach        if (session_status() === PHP_SESSION_ACTIVE) {
1366ccdf4f0SGreg Roach            session_regenerate_id($destroy);
1376ccdf4f0SGreg Roach        }
13857514a4fSGreg Roach    }
13957514a4fSGreg Roach
14057514a4fSGreg Roach    /**
1416ccdf4f0SGreg Roach     * Remove all stored data from the session.
1426ccdf4f0SGreg Roach     *
1436ccdf4f0SGreg Roach     * @return void
1446ccdf4f0SGreg Roach     */
1456ccdf4f0SGreg Roach    public static function clear(): void
1466ccdf4f0SGreg Roach    {
1476ccdf4f0SGreg Roach        $_SESSION = [];
1486ccdf4f0SGreg Roach    }
1496ccdf4f0SGreg Roach
1506ccdf4f0SGreg Roach    /**
1516ccdf4f0SGreg Roach     * Write a value to the session
1526ccdf4f0SGreg Roach     *
1536ccdf4f0SGreg Roach     * @param string $name
1546ccdf4f0SGreg Roach     * @param mixed  $value
1556ccdf4f0SGreg Roach     *
1566ccdf4f0SGreg Roach     * @return void
1576ccdf4f0SGreg Roach     */
1586ccdf4f0SGreg Roach    public static function put(string $name, $value): void
1596ccdf4f0SGreg Roach    {
1606ccdf4f0SGreg Roach        $_SESSION[$name] = $value;
1616ccdf4f0SGreg Roach    }
1626ccdf4f0SGreg Roach
1636ccdf4f0SGreg Roach    /**
1646ccdf4f0SGreg Roach     * Remove a value from the session
1656ccdf4f0SGreg Roach     *
1666ccdf4f0SGreg Roach     * @param string $name
1676ccdf4f0SGreg Roach     *
1686ccdf4f0SGreg Roach     * @return void
1696ccdf4f0SGreg Roach     */
1706ccdf4f0SGreg Roach    public static function forget(string $name): void
1716ccdf4f0SGreg Roach    {
1726ccdf4f0SGreg Roach        unset($_SESSION[$name]);
1736ccdf4f0SGreg Roach    }
1746ccdf4f0SGreg Roach
1756ccdf4f0SGreg Roach    /**
176a45f9889SGreg Roach     * Cross-Site Request Forgery tokens - ensure that the user is submitting
177a45f9889SGreg Roach     * a form that was generated by the current session.
178a45f9889SGreg Roach     *
179a45f9889SGreg Roach     * @return string
180a45f9889SGreg Roach     */
1818f53f488SRico Sonntag    public static function getCsrfToken(): string
182a45f9889SGreg Roach    {
183e364afe4SGreg Roach        if (!self::has('CSRF_TOKEN')) {
184e364afe4SGreg Roach            self::put('CSRF_TOKEN', Str::random(32));
185a45f9889SGreg Roach        }
186a45f9889SGreg Roach
187e364afe4SGreg Roach        return self::get('CSRF_TOKEN');
188a45f9889SGreg Roach    }
1896ccdf4f0SGreg Roach
1906ccdf4f0SGreg Roach    /**
1916ccdf4f0SGreg Roach     * Does a session variable exist?
1926ccdf4f0SGreg Roach     *
1936ccdf4f0SGreg Roach     * @param string $name
1946ccdf4f0SGreg Roach     *
1956ccdf4f0SGreg Roach     * @return bool
1966ccdf4f0SGreg Roach     */
1976ccdf4f0SGreg Roach    public static function has(string $name): bool
1986ccdf4f0SGreg Roach    {
1996ccdf4f0SGreg Roach        return isset($_SESSION[$name]);
2006ccdf4f0SGreg Roach    }
20131bc7874SGreg Roach}
202