aboutsummaryrefslogtreecommitdiffhomepage
path: root/application/security/SessionManager.php
blob: f957b91a06db98a4d351d206d3804041057b7208 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
<?php

namespace Shaarli\Security;

use Shaarli\Config\ConfigManager;

/**
 * Manages the server-side session
 */
class SessionManager
{
    public const KEY_LINKS_PER_PAGE = 'LINKS_PER_PAGE';
    public const KEY_VISIBILITY = 'visibility';
    public const KEY_UNTAGGED_ONLY = 'untaggedonly';

    public const KEY_SUCCESS_MESSAGES = 'successes';
    public const KEY_WARNING_MESSAGES = 'warnings';
    public const KEY_ERROR_MESSAGES = 'errors';

    /** @var int Session expiration timeout, in seconds */
    public static $SHORT_TIMEOUT = 3600;    // 1 hour

    /** @var int Session expiration timeout, in seconds */
    public static $LONG_TIMEOUT = 31536000; // 1 year

    /** @var array Local reference to the global $_SESSION array */
    protected $session = [];

    /** @var ConfigManager Configuration Manager instance **/
    protected $conf = null;

    /** @var bool Whether the user should stay signed in (LONG_TIMEOUT) */
    protected $staySignedIn = false;

    /** @var string */
    protected $savePath;

    /**
     * Constructor
     *
     * @param array         $session  The $_SESSION array (reference)
     * @param ConfigManager $conf     ConfigManager instance
     * @param string        $savePath Session save path returned by builtin function session_save_path()
     */
    public function __construct(&$session, $conf, string $savePath)
    {
        $this->session = &$session;
        $this->conf = $conf;
        $this->savePath = $savePath;
    }

    /**
     * Initialize XSRF token and links per page session variables.
     */
    public function initialize(): void
    {
        if (!isset($this->session['tokens'])) {
            $this->session['tokens'] = [];
        }

        if (!isset($this->session['LINKS_PER_PAGE'])) {
            $this->session['LINKS_PER_PAGE'] = $this->conf->get('general.links_per_page', 20);
        }
    }

    /**
     * Define whether the user should stay signed in across browser sessions
     *
     * @param bool $staySignedIn Keep the user signed in
     */
    public function setStaySignedIn($staySignedIn)
    {
        $this->staySignedIn = $staySignedIn;
    }

    /**
     * Generates a session token
     *
     * @return string token
     */
    public function generateToken()
    {
        $token = sha1(uniqid('', true) . '_' . mt_rand() . $this->conf->get('credentials.salt'));
        $this->session['tokens'][$token] = 1;
        return $token;
    }

    /**
     * Checks the validity of a session token, and destroys it afterwards
     *
     * @param string $token The token to check
     *
     * @return bool true if the token is valid, else false
     */
    public function checkToken($token)
    {
        if (! isset($this->session['tokens'][$token])) {
            // the token is wrong, or has already been used
            return false;
        }

        // destroy the token to prevent future use
        unset($this->session['tokens'][$token]);
        return true;
    }

    /**
     * Validate session ID to prevent Full Path Disclosure.
     *
     * See #298.
     * The session ID's format depends on the hash algorithm set in PHP settings
     *
     * @param string $sessionId Session ID
     *
     * @return true if valid, false otherwise.
     *
     * @see http://php.net/manual/en/function.hash-algos.php
     * @see http://php.net/manual/en/session.configuration.php
     */
    public static function checkId($sessionId)
    {
        if (empty($sessionId)) {
            return false;
        }

        if (!$sessionId) {
            return false;
        }

        if (!preg_match('/^[a-zA-Z0-9,-]{2,128}$/', $sessionId)) {
            return false;
        }

        return true;
    }

    /**
     * Store user login information after a successful login
     *
     * @param string $clientIpId Client IP address identifier
     */
    public function storeLoginInfo($clientIpId)
    {
        $this->session['ip'] = $clientIpId;
        $this->session['username'] = $this->conf->get('credentials.login');
        $this->extendTimeValidityBy(self::$SHORT_TIMEOUT);
    }

    /**
     * Extend session validity
     */
    public function extendSession()
    {
        if ($this->staySignedIn) {
            return $this->extendTimeValidityBy(self::$LONG_TIMEOUT);
        }
        return $this->extendTimeValidityBy(self::$SHORT_TIMEOUT);
    }

    /**
     * Extend expiration time
     *
     * @param int $duration Expiration time extension (seconds)
     *
     * @return int New session expiration time
     */
    protected function extendTimeValidityBy($duration)
    {
        $expirationTime = time() + $duration;
        $this->session['expires_on'] = $expirationTime;
        return $expirationTime;
    }

    /**
     * Logout a user by unsetting all login information
     *
     * See:
     * - https://secure.php.net/manual/en/function.setcookie.php
     */
    public function logout()
    {
        if (isset($this->session)) {
            unset($this->session['ip']);
            unset($this->session['expires_on']);
            unset($this->session['username']);
            unset($this->session['visibility']);
        }
    }

    /**
     * Check whether the session has expired
     *
     * @param string $clientIpId Client IP address identifier
     *
     * @return bool true if the session has expired, false otherwise
     */
    public function hasSessionExpired()
    {
        if (empty($this->session['expires_on'])) {
            return true;
        }
        if (time() >= $this->session['expires_on']) {
            return true;
        }
        return false;
    }

    /**
     * Check whether the client IP address has changed
     *
     * @param string $clientIpId Client IP address identifier
     *
     * @return bool true if the IP has changed, false if it has not, or
     *              if session protection has been disabled
     */
    public function hasClientIpChanged($clientIpId)
    {
        if ($this->conf->get('security.session_protection_disabled') === true) {
            return false;
        }
        if (isset($this->session['ip']) && $this->session['ip'] === $clientIpId) {
            return false;
        }
        return true;
    }

    /** @return array Local reference to the global $_SESSION array */
    public function getSession(): array
    {
        return $this->session;
    }

    /**
     * @param mixed $default value which will be returned if the $key is undefined
     *
     * @return mixed Content stored in session
     */
    public function getSessionParameter(string $key, $default = null)
    {
        return $this->session[$key] ?? $default;
    }

    /**
     * Store a variable in user session.
     *
     * @param string $key   Session key
     * @param mixed  $value Session value to store
     *
     * @return $this
     */
    public function setSessionParameter(string $key, $value): self
    {
        $this->session[$key] = $value;

        return $this;
    }

    /**
     * Store a variable in user session.
     *
     * @param string $key   Session key
     *
     * @return $this
     */
    public function deleteSessionParameter(string $key): self
    {
        unset($this->session[$key]);

        return $this;
    }

    public function getSavePath(): string
    {
        return $this->savePath;
    }

    /*
     * Next public functions wrapping native PHP session API.
     */

    public function destroy(): bool
    {
        $this->session = [];

        return session_destroy();
    }

    public function start(): bool
    {
        if (session_status() === PHP_SESSION_ACTIVE) {
            $this->destroy();
        }

        return session_start();
    }

    /**
     * Be careful, return type of session_set_cookie_params() changed between PHP 7.1 and 7.2.
     */
    public function cookieParameters(int $lifeTime, string $path, string $domain): void
    {
        session_set_cookie_params($lifeTime, $path, $domain);
    }

    public function regenerateId(bool $deleteOldSession = false): bool
    {
        return session_regenerate_id($deleteOldSession);
    }
}