xref: /webtrees/app/Session.php (revision fe3a9054e4733c44cc717a83cc994ecf7eac66cd)
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
20*fe3a9054SGreg Roachuse Carbon\Carbon;
21*fe3a9054SGreg Roachuse Illuminate\Database\Capsule\Manager as DB;
224c891c40SGreg Roachuse Symfony\Component\HttpFoundation\Request;
23*fe3a9054SGreg 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     */
3508df3d18SGreg Roach    public static function start()
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    /**
6031bc7874SGreg Roach     * Read a value from the session
6131bc7874SGreg Roach     *
6231bc7874SGreg Roach     * @param string $name
6331bc7874SGreg Roach     * @param mixed  $default
6431bc7874SGreg Roach     *
6531bc7874SGreg Roach     * @return mixed
6631bc7874SGreg Roach     */
67c7ff4153SGreg Roach    public static function get(string $name, $default = null)
68c1010edaSGreg Roach    {
6963485653SRico Sonntag        return $_SESSION[$name] ?? $default;
7031bc7874SGreg Roach    }
7131bc7874SGreg Roach
7231bc7874SGreg Roach    /**
7331bc7874SGreg Roach     * Write a value to the session
7431bc7874SGreg Roach     *
7531bc7874SGreg Roach     * @param string $name
7631bc7874SGreg Roach     * @param mixed  $value
77c7ff4153SGreg Roach     *
78c7ff4153SGreg Roach     * @return void
7931bc7874SGreg Roach     */
80c7ff4153SGreg Roach    public static function put(string $name, $value)
81c1010edaSGreg Roach    {
8231bc7874SGreg Roach        $_SESSION[$name] = $value;
8331bc7874SGreg Roach    }
8431bc7874SGreg Roach
8531bc7874SGreg Roach    /**
8631bc7874SGreg Roach     * Remove a value from the session
8731bc7874SGreg Roach     *
8831bc7874SGreg Roach     * @param string $name
89c7ff4153SGreg Roach     *
90c7ff4153SGreg Roach     * @return void
9131bc7874SGreg Roach     */
92c7ff4153SGreg Roach    public static function forget(string $name)
93c1010edaSGreg Roach    {
9431bc7874SGreg Roach        unset($_SESSION[$name]);
9531bc7874SGreg Roach    }
9631bc7874SGreg Roach
9731bc7874SGreg Roach    /**
9831bc7874SGreg Roach     * Does a session variable exist?
9931bc7874SGreg Roach     *
10031bc7874SGreg Roach     * @param string $name
10131bc7874SGreg Roach     *
102cbc1590aSGreg Roach     * @return bool
10331bc7874SGreg Roach     */
104c7ff4153SGreg Roach    public static function has(string $name): bool
105c1010edaSGreg Roach    {
10691fb15f0SGreg Roach        return isset($_SESSION[$name]);
10731bc7874SGreg Roach    }
10831bc7874SGreg Roach
10931bc7874SGreg Roach    /**
110f5004097SGreg Roach     * Remove all stored data from the session.
111c7ff4153SGreg Roach     *
112c7ff4153SGreg Roach     * @return void
113f5004097SGreg Roach     */
114c1010edaSGreg Roach    public static function clear()
115c1010edaSGreg Roach    {
11613abd6f3SGreg Roach        $_SESSION = [];
117f5004097SGreg Roach    }
118f5004097SGreg Roach
119f5004097SGreg Roach    /**
12031bc7874SGreg Roach     * After any change in authentication level, we should use a new session ID.
12131bc7874SGreg Roach     *
12231bc7874SGreg Roach     * @param bool $destroy
123c7ff4153SGreg Roach     *
124c7ff4153SGreg Roach     * @return void
12531bc7874SGreg Roach     */
126c7ff4153SGreg Roach    public static function regenerate(bool $destroy = false)
127c1010edaSGreg Roach    {
128f5004097SGreg Roach        if ($destroy) {
129f5004097SGreg Roach            self::clear();
130f5004097SGreg Roach        }
13101461f86SGreg Roach
13201461f86SGreg Roach        if (session_status() === PHP_SESSION_ACTIVE) {
13331bc7874SGreg Roach            session_regenerate_id($destroy);
13431bc7874SGreg Roach        }
13501461f86SGreg Roach    }
13631bc7874SGreg Roach
13731bc7874SGreg Roach    /**
13831bc7874SGreg Roach     * Set an explicit session ID. Typically used for search robots.
13931bc7874SGreg Roach     *
14031bc7874SGreg Roach     * @param string $id
141c7ff4153SGreg Roach     *
142c7ff4153SGreg Roach     * @return void
14331bc7874SGreg Roach     */
144c7ff4153SGreg Roach    public static function setId(string $id)
145c1010edaSGreg Roach    {
14631bc7874SGreg Roach        session_id($id);
14731bc7874SGreg Roach    }
14857514a4fSGreg Roach
14957514a4fSGreg Roach    /**
15057514a4fSGreg Roach     * Initialise our session save handler
151c7ff4153SGreg Roach     *
152c7ff4153SGreg Roach     * @return void
15357514a4fSGreg Roach     */
15408df3d18SGreg Roach    private static function setSaveHandler()
155c1010edaSGreg Roach    {
15657514a4fSGreg Roach        session_set_save_handler(
15757514a4fSGreg Roach            function (): bool {
15857514a4fSGreg Roach                return Session::open();
15957514a4fSGreg Roach            },
16057514a4fSGreg Roach            function (): bool {
16157514a4fSGreg Roach                return Session::close();
16257514a4fSGreg Roach            },
16357514a4fSGreg Roach            function (string $id): string {
16457514a4fSGreg Roach                return Session::read($id);
16557514a4fSGreg Roach            },
16657514a4fSGreg Roach            function (string $id, string $data): bool {
16757514a4fSGreg Roach                return Session::write($id, $data);
16857514a4fSGreg Roach            },
16957514a4fSGreg Roach            function (string $id): bool {
17057514a4fSGreg Roach                return Session::destroy($id);
17157514a4fSGreg Roach            },
17257514a4fSGreg Roach            function (int $maxlifetime): bool {
17357514a4fSGreg Roach                return Session::gc($maxlifetime);
17457514a4fSGreg Roach            }
17557514a4fSGreg Roach        );
17657514a4fSGreg Roach    }
17757514a4fSGreg Roach
17857514a4fSGreg Roach    /**
17957514a4fSGreg Roach     * For session_set_save_handler()
18057514a4fSGreg Roach     *
18157514a4fSGreg Roach     * @return bool
18257514a4fSGreg Roach     */
1838f53f488SRico Sonntag    private static function close(): bool
184c1010edaSGreg Roach    {
18557514a4fSGreg Roach        return true;
18657514a4fSGreg Roach    }
18757514a4fSGreg Roach
18857514a4fSGreg Roach    /**
18957514a4fSGreg Roach     * For session_set_save_handler()
19057514a4fSGreg Roach     *
19157514a4fSGreg Roach     * @param string $id
19257514a4fSGreg Roach     *
19357514a4fSGreg Roach     * @return bool
19457514a4fSGreg Roach     */
1958f53f488SRico Sonntag    private static function destroy(string $id): bool
196c1010edaSGreg Roach    {
197*fe3a9054SGreg Roach        DB::table('session')
198*fe3a9054SGreg Roach            ->where('session_id', '=', $id)
199*fe3a9054SGreg Roach            ->delete();
20057514a4fSGreg Roach
20157514a4fSGreg Roach        return true;
20257514a4fSGreg Roach    }
20357514a4fSGreg Roach
20457514a4fSGreg Roach    /**
20557514a4fSGreg Roach     * For session_set_save_handler()
20657514a4fSGreg Roach     *
20757514a4fSGreg Roach     * @param int $maxlifetime
20857514a4fSGreg Roach     *
20957514a4fSGreg Roach     * @return bool
21057514a4fSGreg Roach     */
2118f53f488SRico Sonntag    private static function gc(int $maxlifetime): bool
212c1010edaSGreg Roach    {
213*fe3a9054SGreg Roach        DB::table('session')
214*fe3a9054SGreg Roach            ->where('session_time', '<', Carbon::now()->subSeconds($maxlifetime))
215*fe3a9054SGreg Roach            ->delete();
21657514a4fSGreg Roach
21757514a4fSGreg Roach        return true;
21857514a4fSGreg Roach    }
21957514a4fSGreg Roach
22057514a4fSGreg Roach    /**
22157514a4fSGreg Roach     * For session_set_save_handler()
22257514a4fSGreg Roach     *
22357514a4fSGreg Roach     * @return bool
22457514a4fSGreg Roach     */
2258f53f488SRico Sonntag    private static function open(): bool
226c1010edaSGreg Roach    {
22757514a4fSGreg Roach        return true;
22857514a4fSGreg Roach    }
22957514a4fSGreg Roach
23057514a4fSGreg Roach    /**
23157514a4fSGreg Roach     * For session_set_save_handler()
23257514a4fSGreg Roach     *
23357514a4fSGreg Roach     * @param string $id
23457514a4fSGreg Roach     *
23557514a4fSGreg Roach     * @return string
23657514a4fSGreg Roach     */
237c1010edaSGreg Roach    private static function read(string $id): string
238c1010edaSGreg Roach    {
239*fe3a9054SGreg Roach        return (string) DB::table('session')
240*fe3a9054SGreg Roach            ->where('session_id', '=', $id)
241*fe3a9054SGreg Roach            ->value('session_data');
24257514a4fSGreg Roach    }
24357514a4fSGreg Roach
24457514a4fSGreg Roach    /**
24557514a4fSGreg Roach     * For session_set_save_handler()
24657514a4fSGreg Roach     *
24757514a4fSGreg Roach     * @param string $id
24857514a4fSGreg Roach     * @param string $data
24957514a4fSGreg Roach     *
25057514a4fSGreg Roach     * @return bool
25157514a4fSGreg Roach     */
252c1010edaSGreg Roach    private static function write(string $id, string $data): bool
253c1010edaSGreg Roach    {
2544c891c40SGreg Roach        $request = Request::createFromGlobals();
2554c891c40SGreg Roach
256*fe3a9054SGreg Roach        DB::table('session')->updateOrInsert([
2574c891c40SGreg Roach            'session_id' => $id,
258*fe3a9054SGreg Roach        ], [
259*fe3a9054SGreg Roach            'session_time' => Carbon::now(),
2604c891c40SGreg Roach            'user_id'      => (int) Auth::id(),
2614c891c40SGreg Roach            'ip_address'   => $request->getClientIp(),
262*fe3a9054SGreg Roach            'session_data' => $data,
2634c891c40SGreg Roach        ]);
26457514a4fSGreg Roach
26557514a4fSGreg Roach        return true;
26657514a4fSGreg Roach    }
267a45f9889SGreg Roach
268a45f9889SGreg Roach    /**
269a45f9889SGreg Roach     * Cross-Site Request Forgery tokens - ensure that the user is submitting
270a45f9889SGreg Roach     * a form that was generated by the current session.
271a45f9889SGreg Roach     *
272a45f9889SGreg Roach     * @return string
273a45f9889SGreg Roach     */
2748f53f488SRico Sonntag    public static function getCsrfToken(): string
275a45f9889SGreg Roach    {
276a45f9889SGreg Roach        if (!Session::has('CSRF_TOKEN')) {
277a45f9889SGreg Roach            $charset    = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcedfghijklmnopqrstuvwxyz0123456789';
278a45f9889SGreg Roach            $csrf_token = '';
279a45f9889SGreg Roach            for ($n = 0; $n < 32; ++$n) {
280a45f9889SGreg Roach                $csrf_token .= substr($charset, random_int(0, 61), 1);
281a45f9889SGreg Roach            }
282a45f9889SGreg Roach            Session::put('CSRF_TOKEN', $csrf_token);
283a45f9889SGreg Roach        }
284a45f9889SGreg Roach
285a45f9889SGreg Roach        return Session::get('CSRF_TOKEN');
286a45f9889SGreg Roach    }
28731bc7874SGreg Roach}
288