]>
Commit | Line | Data |
---|---|---|
1 | <?php | |
2 | namespace Shaarli\Security; | |
3 | ||
4 | use Shaarli\Config\ConfigManager; | |
5 | ||
6 | /** | |
7 | * Manages the server-side session | |
8 | */ | |
9 | class SessionManager | |
10 | { | |
11 | public const KEY_LINKS_PER_PAGE = 'LINKS_PER_PAGE'; | |
12 | public const KEY_VISIBILITY = 'visibility'; | |
13 | public const KEY_UNTAGGED_ONLY = 'untaggedonly'; | |
14 | ||
15 | public const KEY_SUCCESS_MESSAGES = 'successes'; | |
16 | public const KEY_WARNING_MESSAGES = 'warnings'; | |
17 | public const KEY_ERROR_MESSAGES = 'errors'; | |
18 | ||
19 | /** @var int Session expiration timeout, in seconds */ | |
20 | public static $SHORT_TIMEOUT = 3600; // 1 hour | |
21 | ||
22 | /** @var int Session expiration timeout, in seconds */ | |
23 | public static $LONG_TIMEOUT = 31536000; // 1 year | |
24 | ||
25 | /** @var array Local reference to the global $_SESSION array */ | |
26 | protected $session = []; | |
27 | ||
28 | /** @var ConfigManager Configuration Manager instance **/ | |
29 | protected $conf = null; | |
30 | ||
31 | /** @var bool Whether the user should stay signed in (LONG_TIMEOUT) */ | |
32 | protected $staySignedIn = false; | |
33 | ||
34 | /** @var string */ | |
35 | protected $savePath; | |
36 | ||
37 | /** | |
38 | * Constructor | |
39 | * | |
40 | * @param array $session The $_SESSION array (reference) | |
41 | * @param ConfigManager $conf ConfigManager instance | |
42 | * @param string $savePath Session save path returned by builtin function session_save_path() | |
43 | */ | |
44 | public function __construct(&$session, $conf, string $savePath) | |
45 | { | |
46 | $this->session = &$session; | |
47 | $this->conf = $conf; | |
48 | $this->savePath = $savePath; | |
49 | } | |
50 | ||
51 | /** | |
52 | * Initialize XSRF token and links per page session variables. | |
53 | */ | |
54 | public function initialize(): void | |
55 | { | |
56 | if (!isset($this->session['tokens'])) { | |
57 | $this->session['tokens'] = []; | |
58 | } | |
59 | ||
60 | if (!isset($this->session['LINKS_PER_PAGE'])) { | |
61 | $this->session['LINKS_PER_PAGE'] = $this->conf->get('general.links_per_page', 20); | |
62 | } | |
63 | } | |
64 | ||
65 | /** | |
66 | * Define whether the user should stay signed in across browser sessions | |
67 | * | |
68 | * @param bool $staySignedIn Keep the user signed in | |
69 | */ | |
70 | public function setStaySignedIn($staySignedIn) | |
71 | { | |
72 | $this->staySignedIn = $staySignedIn; | |
73 | } | |
74 | ||
75 | /** | |
76 | * Generates a session token | |
77 | * | |
78 | * @return string token | |
79 | */ | |
80 | public function generateToken() | |
81 | { | |
82 | $token = sha1(uniqid('', true) .'_'. mt_rand() . $this->conf->get('credentials.salt')); | |
83 | $this->session['tokens'][$token] = 1; | |
84 | return $token; | |
85 | } | |
86 | ||
87 | /** | |
88 | * Checks the validity of a session token, and destroys it afterwards | |
89 | * | |
90 | * @param string $token The token to check | |
91 | * | |
92 | * @return bool true if the token is valid, else false | |
93 | */ | |
94 | public function checkToken($token) | |
95 | { | |
96 | if (! isset($this->session['tokens'][$token])) { | |
97 | // the token is wrong, or has already been used | |
98 | return false; | |
99 | } | |
100 | ||
101 | // destroy the token to prevent future use | |
102 | unset($this->session['tokens'][$token]); | |
103 | return true; | |
104 | } | |
105 | ||
106 | /** | |
107 | * Validate session ID to prevent Full Path Disclosure. | |
108 | * | |
109 | * See #298. | |
110 | * The session ID's format depends on the hash algorithm set in PHP settings | |
111 | * | |
112 | * @param string $sessionId Session ID | |
113 | * | |
114 | * @return true if valid, false otherwise. | |
115 | * | |
116 | * @see http://php.net/manual/en/function.hash-algos.php | |
117 | * @see http://php.net/manual/en/session.configuration.php | |
118 | */ | |
119 | public static function checkId($sessionId) | |
120 | { | |
121 | if (empty($sessionId)) { | |
122 | return false; | |
123 | } | |
124 | ||
125 | if (!$sessionId) { | |
126 | return false; | |
127 | } | |
128 | ||
129 | if (!preg_match('/^[a-zA-Z0-9,-]{2,128}$/', $sessionId)) { | |
130 | return false; | |
131 | } | |
132 | ||
133 | return true; | |
134 | } | |
135 | ||
136 | /** | |
137 | * Store user login information after a successful login | |
138 | * | |
139 | * @param string $clientIpId Client IP address identifier | |
140 | */ | |
141 | public function storeLoginInfo($clientIpId) | |
142 | { | |
143 | $this->session['ip'] = $clientIpId; | |
144 | $this->session['username'] = $this->conf->get('credentials.login'); | |
145 | $this->extendTimeValidityBy(self::$SHORT_TIMEOUT); | |
146 | } | |
147 | ||
148 | /** | |
149 | * Extend session validity | |
150 | */ | |
151 | public function extendSession() | |
152 | { | |
153 | if ($this->staySignedIn) { | |
154 | return $this->extendTimeValidityBy(self::$LONG_TIMEOUT); | |
155 | } | |
156 | return $this->extendTimeValidityBy(self::$SHORT_TIMEOUT); | |
157 | } | |
158 | ||
159 | /** | |
160 | * Extend expiration time | |
161 | * | |
162 | * @param int $duration Expiration time extension (seconds) | |
163 | * | |
164 | * @return int New session expiration time | |
165 | */ | |
166 | protected function extendTimeValidityBy($duration) | |
167 | { | |
168 | $expirationTime = time() + $duration; | |
169 | $this->session['expires_on'] = $expirationTime; | |
170 | return $expirationTime; | |
171 | } | |
172 | ||
173 | /** | |
174 | * Logout a user by unsetting all login information | |
175 | * | |
176 | * See: | |
177 | * - https://secure.php.net/manual/en/function.setcookie.php | |
178 | */ | |
179 | public function logout() | |
180 | { | |
181 | if (isset($this->session)) { | |
182 | unset($this->session['ip']); | |
183 | unset($this->session['expires_on']); | |
184 | unset($this->session['username']); | |
185 | unset($this->session['visibility']); | |
186 | } | |
187 | } | |
188 | ||
189 | /** | |
190 | * Check whether the session has expired | |
191 | * | |
192 | * @param string $clientIpId Client IP address identifier | |
193 | * | |
194 | * @return bool true if the session has expired, false otherwise | |
195 | */ | |
196 | public function hasSessionExpired() | |
197 | { | |
198 | if (empty($this->session['expires_on'])) { | |
199 | return true; | |
200 | } | |
201 | if (time() >= $this->session['expires_on']) { | |
202 | return true; | |
203 | } | |
204 | return false; | |
205 | } | |
206 | ||
207 | /** | |
208 | * Check whether the client IP address has changed | |
209 | * | |
210 | * @param string $clientIpId Client IP address identifier | |
211 | * | |
212 | * @return bool true if the IP has changed, false if it has not, or | |
213 | * if session protection has been disabled | |
214 | */ | |
215 | public function hasClientIpChanged($clientIpId) | |
216 | { | |
217 | if ($this->conf->get('security.session_protection_disabled') === true) { | |
218 | return false; | |
219 | } | |
220 | if (isset($this->session['ip']) && $this->session['ip'] === $clientIpId) { | |
221 | return false; | |
222 | } | |
223 | return true; | |
224 | } | |
225 | ||
226 | /** @return array Local reference to the global $_SESSION array */ | |
227 | public function getSession(): array | |
228 | { | |
229 | return $this->session; | |
230 | } | |
231 | ||
232 | /** | |
233 | * @param mixed $default value which will be returned if the $key is undefined | |
234 | * | |
235 | * @return mixed Content stored in session | |
236 | */ | |
237 | public function getSessionParameter(string $key, $default = null) | |
238 | { | |
239 | return $this->session[$key] ?? $default; | |
240 | } | |
241 | ||
242 | /** | |
243 | * Store a variable in user session. | |
244 | * | |
245 | * @param string $key Session key | |
246 | * @param mixed $value Session value to store | |
247 | * | |
248 | * @return $this | |
249 | */ | |
250 | public function setSessionParameter(string $key, $value): self | |
251 | { | |
252 | $this->session[$key] = $value; | |
253 | ||
254 | return $this; | |
255 | } | |
256 | ||
257 | /** | |
258 | * Store a variable in user session. | |
259 | * | |
260 | * @param string $key Session key | |
261 | * | |
262 | * @return $this | |
263 | */ | |
264 | public function deleteSessionParameter(string $key): self | |
265 | { | |
266 | unset($this->session[$key]); | |
267 | ||
268 | return $this; | |
269 | } | |
270 | ||
271 | public function getSavePath(): string | |
272 | { | |
273 | return $this->savePath; | |
274 | } | |
275 | ||
276 | /* | |
277 | * Next public functions wrapping native PHP session API. | |
278 | */ | |
279 | ||
280 | public function destroy(): bool | |
281 | { | |
282 | $this->session = []; | |
283 | ||
284 | return session_destroy(); | |
285 | } | |
286 | ||
287 | public function start(): bool | |
288 | { | |
289 | if (session_status() === PHP_SESSION_ACTIVE) { | |
290 | $this->destroy(); | |
291 | } | |
292 | ||
293 | return session_start(); | |
294 | } | |
295 | ||
296 | public function cookieParameters(int $lifeTime, string $path, string $domain): bool | |
297 | { | |
298 | return session_set_cookie_params($lifeTime, $path, $domain); | |
299 | } | |
300 | ||
301 | public function regenerateId(bool $deleteOldSession = false): bool | |
302 | { | |
303 | return session_regenerate_id($deleteOldSession); | |
304 | } | |
305 | } |