1 | <?php |
||||||
2 | /** |
||||||
3 | * This file is part of the Shieldon package. |
||||||
4 | * |
||||||
5 | * (c) Terry L. <[email protected]> |
||||||
6 | * |
||||||
7 | * For the full copyright and license information, please view the LICENSE |
||||||
8 | * file that was distributed with this source code. |
||||||
9 | * |
||||||
10 | * php version 7.1.0 |
||||||
11 | * |
||||||
12 | * @category Web-security |
||||||
13 | * @package Shieldon |
||||||
14 | * @author Terry Lin <[email protected]> |
||||||
15 | * @copyright 2019 terrylinooo |
||||||
16 | * @license https://github.com/terrylinooo/shieldon/blob/2.x/LICENSE MIT |
||||||
17 | * @link https://github.com/terrylinooo/shieldon |
||||||
18 | * @see https://shieldon.io |
||||||
19 | */ |
||||||
20 | |||||||
21 | declare(strict_types=1); |
||||||
22 | |||||||
23 | namespace Shieldon\Firewall; |
||||||
24 | |||||||
25 | use Shieldon\Firewall\Driver\DirverProvider; |
||||||
0 ignored issues
–
show
|
|||||||
26 | use Shieldon\Firewall\Container; |
||||||
27 | use RuntimeException; |
||||||
28 | use function Shieldon\Firewall\get_request; |
||||||
29 | use function Shieldon\Firewall\get_response; |
||||||
30 | use function Shieldon\Firewall\set_response; |
||||||
31 | use function Shieldon\Firewall\get_microtimesamp; |
||||||
32 | use function Shieldon\Firewall\create_session_id; |
||||||
33 | use function Shieldon\Firewall\get_ip; |
||||||
34 | use function time; |
||||||
35 | use function rand; |
||||||
36 | use function intval; |
||||||
37 | use function setcookie; |
||||||
38 | |||||||
39 | /* |
||||||
40 | * Session for the use of Shieldon. |
||||||
41 | */ |
||||||
42 | class Session |
||||||
43 | { |
||||||
44 | /** |
||||||
45 | * The session data. |
||||||
46 | * |
||||||
47 | * @var array |
||||||
48 | */ |
||||||
49 | protected $data = []; |
||||||
50 | |||||||
51 | /** |
||||||
52 | * The session data will be removed after expiring. |
||||||
53 | * Time unit: second. |
||||||
54 | * |
||||||
55 | * @var int |
||||||
56 | */ |
||||||
57 | protected $expire = 600; |
||||||
58 | |||||||
59 | /** |
||||||
60 | * The Shieldon kernel. |
||||||
61 | * |
||||||
62 | * @var Kernel|null |
||||||
63 | */ |
||||||
64 | protected $kernel; |
||||||
65 | |||||||
66 | /** |
||||||
67 | * The data driver. |
||||||
68 | * |
||||||
69 | * @var DirverProvider|null |
||||||
70 | */ |
||||||
71 | protected $driver; |
||||||
72 | |||||||
73 | /** |
||||||
74 | * Make sure the init() run first. |
||||||
75 | * |
||||||
76 | * @var bool |
||||||
77 | */ |
||||||
78 | protected static $status = false; |
||||||
79 | |||||||
80 | /** |
||||||
81 | * A session Id. |
||||||
82 | * |
||||||
83 | * @var string |
||||||
84 | */ |
||||||
85 | protected static $id = '_php_cli_'; |
||||||
86 | |||||||
87 | /** |
||||||
88 | * Constructor. |
||||||
89 | * |
||||||
90 | * @param string $id Session ID |
||||||
91 | */ |
||||||
92 | public function __construct(string $sessionId = '') |
||||||
93 | { |
||||||
94 | $this->setId($sessionId); |
||||||
95 | |||||||
96 | /** |
||||||
97 | * Store the session data back into the database table when the |
||||||
98 | * Shieldon Kernel workflow is reaching the end of the process. |
||||||
99 | */ |
||||||
100 | add_listener('kernel_end', [$this, 'save'], 10); |
||||||
101 | |||||||
102 | /** |
||||||
103 | * Store the session data back into the database table when the |
||||||
104 | * user is logged successfully. |
||||||
105 | */ |
||||||
106 | add_listener('user_login', [$this, 'save'], 10); |
||||||
107 | |||||||
108 | self::log(); |
||||||
109 | } |
||||||
110 | |||||||
111 | /** |
||||||
112 | * Log. |
||||||
113 | * |
||||||
114 | * @return void |
||||||
115 | */ |
||||||
116 | protected static function log($text = '') |
||||||
117 | { |
||||||
118 | if (php_sapi_name() !== 'cli') { |
||||||
119 | return; |
||||||
120 | } |
||||||
121 | |||||||
122 | $dir = BOOTSTRAP_DIR . '/../tmp/shieldon/session_logs'; |
||||||
123 | $file = $dir . '/' . date('Y-m-d') . '.json'; |
||||||
124 | |||||||
125 | $originalUmask = umask(0); |
||||||
126 | |||||||
127 | if (!is_dir($dir)) { |
||||||
128 | mkdir($dir, 0777, true); |
||||||
129 | } |
||||||
130 | |||||||
131 | umask($originalUmask); |
||||||
132 | |||||||
133 | $method = debug_backtrace()[1]['function']; |
||||||
134 | |||||||
135 | $content = date('Y-m-d H:i:s') . ' - [' . $method . '] ' . $text; |
||||||
136 | file_put_contents($file, $content . PHP_EOL, FILE_APPEND); |
||||||
137 | } |
||||||
138 | |||||||
139 | /** |
||||||
140 | * Get session ID. |
||||||
141 | * |
||||||
142 | * @return string |
||||||
143 | */ |
||||||
144 | public function getId(): string |
||||||
145 | { |
||||||
146 | return self::$id; |
||||||
147 | } |
||||||
148 | |||||||
149 | /** |
||||||
150 | * Set session ID. |
||||||
151 | * |
||||||
152 | * @param string $id Session Id. |
||||||
153 | * |
||||||
154 | * @return void |
||||||
155 | */ |
||||||
156 | public function setId(string $id): void |
||||||
157 | { |
||||||
158 | self::$id = $id; |
||||||
159 | |||||||
160 | // We store this session ID into the container for the use of other functions. |
||||||
161 | Container::set('session_id', $id, true); |
||||||
162 | |||||||
163 | self::log($id); |
||||||
164 | } |
||||||
165 | |||||||
166 | /** |
||||||
167 | * Initialize. |
||||||
168 | * |
||||||
169 | * @param object $driver The data driver. |
||||||
170 | * @param int $gcExpires The time of expiring. |
||||||
171 | * @param int $gcProbability GC setting, |
||||||
172 | * @param int $gcDivisor GC setting, |
||||||
173 | * @param bool $psr7 Reset the cookie the PSR-7 way? |
||||||
174 | * |
||||||
175 | * @return void |
||||||
176 | */ |
||||||
177 | public function init( |
||||||
178 | $driver, |
||||||
179 | int $gcExpires = 300, |
||||||
180 | int $gcProbability = 1, |
||||||
181 | int $gcDivisor = 100, |
||||||
182 | bool $psr7 = false |
||||||
183 | ): void { |
||||||
184 | $this->driver = $driver; |
||||||
185 | |||||||
186 | $cookie = get_request()->getCookieParams(); |
||||||
187 | |||||||
188 | $this->gc($gcExpires, $gcProbability, $gcDivisor); |
||||||
189 | |||||||
190 | // New visitor? Create a new session. |
||||||
191 | if (php_sapi_name() !== 'cli' && empty($cookie['_shieldon'])) { |
||||||
192 | self::resetCookie($psr7); |
||||||
193 | $this->create(); |
||||||
194 | self::$status = true; |
||||||
195 | return; |
||||||
196 | } |
||||||
197 | |||||||
198 | $this->data = $this->driver->get(self::$id, 'session'); |
||||||
199 | |||||||
200 | if (empty($this->data)) { |
||||||
201 | self::resetCookie($psr7); |
||||||
202 | $this->create(); |
||||||
203 | } |
||||||
204 | |||||||
205 | $this->parsedData(); |
||||||
206 | |||||||
207 | self::$status = true; |
||||||
208 | |||||||
209 | self::log(self::$id); |
||||||
210 | } |
||||||
211 | |||||||
212 | /** |
||||||
213 | * Check the initialization status. |
||||||
214 | * |
||||||
215 | * @return bool |
||||||
216 | */ |
||||||
217 | public function IsInitialized(): bool |
||||||
218 | { |
||||||
219 | return $this->status; |
||||||
220 | } |
||||||
221 | |||||||
222 | /** |
||||||
223 | * Get specific value from session by key. |
||||||
224 | * |
||||||
225 | * @param string $key The key of a data field. |
||||||
226 | * |
||||||
227 | * @return mixed |
||||||
228 | */ |
||||||
229 | public function get(string $key) |
||||||
230 | { |
||||||
231 | $this->assertInit(); |
||||||
232 | |||||||
233 | return $this->data['parsed_data'][$key] ?? ''; |
||||||
234 | } |
||||||
235 | |||||||
236 | /** |
||||||
237 | * Parse JSON data and store it into parsed_data field. |
||||||
238 | * |
||||||
239 | * @return void |
||||||
240 | */ |
||||||
241 | protected function parsedData() |
||||||
242 | { |
||||||
243 | if (empty($this->data['data'])) { |
||||||
244 | $this->data['data'] = '{}'; |
||||||
245 | } |
||||||
246 | $this->data['parsed_data'] = json_decode($this->data['data'], true); |
||||||
247 | } |
||||||
248 | |||||||
249 | /** |
||||||
250 | * To store data in the session. |
||||||
251 | * |
||||||
252 | * @param string $key The key of a data field. |
||||||
253 | * @param mixed $value The value of a data field. |
||||||
254 | * |
||||||
255 | * @return void |
||||||
256 | */ |
||||||
257 | public function set(string $key, $value): void |
||||||
258 | { |
||||||
259 | $this->assertInit(); |
||||||
260 | |||||||
261 | $this->data['parsed_data'][$key] = $value; |
||||||
262 | } |
||||||
263 | |||||||
264 | /** |
||||||
265 | * To delete data from the session. |
||||||
266 | * |
||||||
267 | * @param string $key The key of a data field. |
||||||
268 | * |
||||||
269 | * @return void |
||||||
270 | */ |
||||||
271 | public function remove(string $key): void |
||||||
272 | { |
||||||
273 | $this->assertInit(); |
||||||
274 | |||||||
275 | if (isset($this->data['parsed_data'][$key])) { |
||||||
276 | unset($this->data['parsed_data'][$key]); |
||||||
277 | } |
||||||
278 | } |
||||||
279 | |||||||
280 | /** |
||||||
281 | * To determine if an item is present in the session. |
||||||
282 | * |
||||||
283 | * @param string $key The key of a data field. |
||||||
284 | * |
||||||
285 | * @return bool |
||||||
286 | */ |
||||||
287 | public function has($key): bool |
||||||
288 | { |
||||||
289 | $this->assertInit(); |
||||||
290 | |||||||
291 | return isset($this->data['parsed_data'][$key]); |
||||||
292 | } |
||||||
293 | |||||||
294 | /** |
||||||
295 | * Clear all data in the session array. |
||||||
296 | * |
||||||
297 | * @return void |
||||||
298 | */ |
||||||
299 | public function clear(): void |
||||||
300 | { |
||||||
301 | $this->assertInit(); |
||||||
302 | |||||||
303 | $this->data = []; |
||||||
304 | } |
||||||
305 | |||||||
306 | /** |
||||||
307 | * Perform session data garbage collection. |
||||||
308 | * |
||||||
309 | * @param int $expires The time of expiring. |
||||||
310 | * @param int $probability Numerator. |
||||||
311 | * @param int $divisor Denominator. |
||||||
312 | * |
||||||
313 | * @return bool |
||||||
314 | */ |
||||||
315 | protected function gc(int $expires, int $probability, int $divisor): bool |
||||||
316 | { |
||||||
317 | $chance = intval($divisor / $probability); |
||||||
318 | $hit = rand(1, $chance); |
||||||
319 | |||||||
320 | if ($hit === 1) { |
||||||
321 | |||||||
322 | $sessionData = $this->driver->getAll('session'); |
||||||
0 ignored issues
–
show
The method
getAll() does not exist on null .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed.
Loading history...
|
|||||||
323 | |||||||
324 | if (!empty($sessionData)) { |
||||||
325 | foreach ($sessionData as $v) { |
||||||
326 | $lasttime = (int) $v['time']; |
||||||
327 | |||||||
328 | if (time() - $lasttime > $expires) { |
||||||
329 | $this->driver->delete($v['id'], 'session'); |
||||||
330 | } |
||||||
331 | } |
||||||
332 | } |
||||||
333 | return true; |
||||||
334 | } |
||||||
335 | return false; |
||||||
336 | } |
||||||
337 | |||||||
338 | /** |
||||||
339 | * Reset cookie. |
||||||
340 | * |
||||||
341 | * @param bool $psr7 Reset the cookie the PSR-7 way, otherwise native. |
||||||
342 | * |
||||||
343 | * @return void |
||||||
344 | */ |
||||||
345 | public static function resetCookie(bool $psr7 = true): void |
||||||
346 | { |
||||||
347 | $sessionHashId = create_session_id(); |
||||||
348 | $cookieName = '_shieldon'; |
||||||
349 | $expiredTime = time() + 3600; |
||||||
350 | |||||||
351 | if ($psr7) { |
||||||
352 | $expires = date('D, d M Y H:i:s', $expiredTime) . ' GMT'; |
||||||
353 | $response = get_response()->withHeader( |
||||||
354 | 'Set-Cookie', |
||||||
355 | $cookieName . '=' . $sessionHashId . '; Path=/; Expires=' . $expires |
||||||
356 | ); |
||||||
357 | set_response($response); |
||||||
358 | } else { |
||||||
359 | setcookie($cookieName, $sessionHashId, $expiredTime, '/'); |
||||||
360 | } |
||||||
361 | |||||||
362 | self::$id = $sessionHashId; |
||||||
363 | |||||||
364 | self::log($sessionHashId); |
||||||
365 | } |
||||||
366 | |||||||
367 | /** |
||||||
368 | * Create session data structure. |
||||||
369 | * |
||||||
370 | * @return void |
||||||
371 | */ |
||||||
372 | protected function create(): void |
||||||
373 | { |
||||||
374 | // Initialize new session data. |
||||||
375 | $data['id'] = self::$id; |
||||||
376 | $data['ip'] = get_ip(); |
||||||
377 | $data['time'] = time(); |
||||||
378 | $data['microtimesamp'] = get_microtimesamp(); |
||||||
379 | |||||||
380 | // This field is a JSON string. |
||||||
381 | $data['data'] = '{}'; |
||||||
382 | $data['parsed_data'] = []; |
||||||
383 | |||||||
384 | $this->data = $data; |
||||||
385 | $this->save(); |
||||||
386 | |||||||
387 | self::log(json_encode($this->data)); |
||||||
388 | } |
||||||
389 | |||||||
390 | /** |
||||||
391 | * Save session data into database. |
||||||
392 | * |
||||||
393 | * @return void |
||||||
394 | */ |
||||||
395 | public function save(): void |
||||||
396 | { |
||||||
397 | $data['id'] = self::$id; |
||||||
398 | $data['ip'] = get_ip(); |
||||||
399 | $data['time'] = (string) time(); |
||||||
400 | $data['microtimesamp'] = (string) get_microtimesamp(); |
||||||
401 | |||||||
402 | $data['data'] = json_encode($this->data['parsed_data']); |
||||||
403 | |||||||
404 | $this->driver->save(self::$id, $data, 'session'); |
||||||
405 | |||||||
406 | self::log(self::$id . "\n" . $this->data['data']); |
||||||
407 | } |
||||||
408 | |||||||
409 | /** |
||||||
410 | * Make sure init run first. |
||||||
411 | * |
||||||
412 | * @return void |
||||||
413 | */ |
||||||
414 | protected function assertInit(): void |
||||||
415 | { |
||||||
416 | if (!self::$status) { |
||||||
417 | throw new RuntimeException( |
||||||
418 | 'The init method is supposed to run first.' |
||||||
419 | ); |
||||||
420 | } |
||||||
421 | } |
||||||
422 | } |
||||||
423 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths