Completed
Push — master ( c55245...5de713 )
by Nazar
04:00
created

Management::del()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package   CleverStyle Framework
4
 * @author    Nazar Mokrynskyi <[email protected]>
5
 * @copyright Copyright (c) 2011-2016, Nazar Mokrynskyi
6
 * @license   MIT License, see license.txt
7
 */
8
namespace cs\Session;
9
use
10
	cs\Config,
11
	cs\Event,
12
	cs\Language,
13
	cs\Page,
14
	cs\Request,
15
	cs\Response,
16
	cs\User;
17
18
/**
19
 * @method \cs\DB\_Abstract db()
20
 * @method \cs\DB\_Abstract db_prime()
21
 */
22
trait Management {
23
	/**
24
	 * Id of current session
25
	 *
26
	 * @var false|string
27
	 */
28
	protected $session_id;
29
	/**
30
	 * User id of current session
31
	 *
32
	 * @var int
33
	 */
34
	protected $user_id;
35
	/**
36
	 * @var bool
37
	 */
38
	protected $is_admin;
39
	/**
40
	 * @var bool
41
	 */
42
	protected $is_user;
43
	/**
44
	 * @var bool
45
	 */
46
	protected $is_guest;
47
	/**
48
	 * Use cookie as source of session id, load session
49
	 */
50 26
	protected function init_session () {
51 26
		$Request = Request::instance();
52
		/**
53
		 * If session exists
54
		 */
55 26
		if ($Request->cookie('session')) {
56
			$this->user_id = $this->load();
57
		}
58 26
		$this->update_user_is();
59 26
	}
60
	/**
61
	 * Updates information about who is user accessed by methods ::guest() ::user() admin()
62
	 */
63 26
	protected function update_user_is () {
64 26
		$this->is_guest = $this->user_id == User::GUEST_ID;
65 26
		$this->is_user  = false;
66 26
		$this->is_admin = false;
67 26
		if ($this->is_guest) {
68 26
			return;
69
		}
70
		/**
71
		 * Checking of user type
72
		 */
73 18
		$groups = User::instance()->get_groups($this->user_id) ?: [];
74 18
		if (in_array(User::ADMIN_GROUP_ID, $groups)) {
75 4
			$this->is_admin = true;
76 4
			$this->is_user  = true;
77 14
		} elseif (in_array(User::USER_GROUP_ID, $groups)) {
78 14
			$this->is_user = true;
79
		}
80 18
	}
81
	/**
82
	 * Is admin
83
	 *
84
	 * @return bool
85
	 */
86 8
	function admin () {
87 8
		return $this->is_admin;
88
	}
89
	/**
90
	 * Is user
91
	 *
92
	 * @return bool
93
	 */
94
	function user () {
95
		return $this->is_user;
96
	}
97
	/**
98
	 * Is guest
99
	 *
100
	 * @return bool
101
	 */
102 6
	function guest () {
103 6
		return $this->is_guest;
104
	}
105
	/**
106
	 * Returns id of current session
107
	 *
108
	 * @return false|string
109
	 */
110 22
	function get_id () {
111 22
		return $this->session_id ?: false;
112
	}
113
	/**
114
	 * Returns user id of current session
115
	 *
116
	 * @return int
117
	 */
118 26
	function get_user () {
119 26
		return $this->user_id;
120
	}
121
	/**
122
	 * Returns session details by session id
123
	 *
124
	 * @param false|null|string $session_id If `null` - loaded from `$this->session_id`, and if that also empty - from cookies
125
	 *
126
	 * @return false|array
127
	 */
128
	function get ($session_id) {
129
		$session_data = $this->get_internal($session_id);
130
		unset($session_data['data']);
131
		return $session_data;
132
	}
133
	/**
134
	 * @param false|null|string $session_id
135
	 *
136
	 * @return false|array
137
	 */
138
	protected function get_internal ($session_id) {
139
		if (!$session_id) {
140
			if (!$this->session_id) {
141
				$this->session_id = Request::instance()->cookie('session');
142
			}
143
			$session_id = $this->session_id;
144
		}
145
		if (!is_md5($session_id)) {
146
			return false;
147
		}
148
		$data = $this->cache->get(
149
			$session_id,
150
			function () use ($session_id) {
151
				$data = $this->read($session_id);
152
				if (!$data || $data['expire'] <= time()) {
153
					return false;
154
				}
155
				$data['data'] = $data['data'] ?: [];
156
				return $data;
157
			}
158
		);
159
		return $this->is_good_session($data) ? $data : false;
160
	}
161
	/**
162
	 * Check whether session was not expired, user agent and IP corresponds to what is expected and user is actually active
163
	 *
164
	 * @param mixed $session_data
165
	 *
166
	 * @return bool
167
	 */
168
	protected function is_good_session ($session_data) {
169
		return
170
			isset($session_data['expire'], $session_data['user']) &&
171
			$session_data['expire'] > time() &&
172
			$this->is_user_active($session_data['user']);
173
	}
174
	/**
175
	 * Whether session data belongs to current visitor (user agent, remote addr and ip check)
176
	 *
177
	 * @param string $session_id
178
	 * @param string $user_agent
179
	 * @param string $remote_addr
180
	 * @param string $ip
181
	 *
182
	 * @return bool
183
	 */
184
	function is_session_owner ($session_id, $user_agent, $remote_addr, $ip) {
185
		$session_data = $this->get($session_id);
186
		return $session_data ? $this->is_session_owner_internal($session_data, $user_agent, $remote_addr, $ip) : false;
187
	}
188
	/**
189
	 * Whether session data belongs to current visitor (user agent, remote addr and ip check)
190
	 *
191
	 * @param array       $session_data
192
	 * @param string|null $user_agent
193
	 * @param string|null $remote_addr
194
	 * @param string|null $ip
195
	 *
196
	 * @return bool
197
	 */
198
	protected function is_session_owner_internal ($session_data, $user_agent = null, $remote_addr = null, $ip = null) {
199
		/**
200
		 * md5() as protection against timing attacks
201
		 */
202
		if ($user_agent === null && $remote_addr === null && $ip === null) {
203
			$Request     = Request::instance();
204
			$user_agent  = $Request->header('user-agent');
205
			$remote_addr = $Request->remote_addr;
206
			$ip          = $Request->ip;
207
		}
208
		return
209
			md5($session_data['user_agent']) == md5($user_agent) &&
210
			(
211
				!Config::instance()->core['remember_user_ip'] ||
212
				(
213
					md5($session_data['remote_addr']) == md5(ip2hex($remote_addr)) &&
214
					md5($session_data['ip']) == md5(ip2hex($ip))
215
				)
216
			);
217
	}
218
	/**
219
	 * Load session by id and return id of session owner (user), update session expiration
220
	 *
221
	 * @param false|null|string $session_id If not specified - loaded from `$this->session_id`, and if that also empty - from cookies
222
	 *
223
	 * @return int User id
224
	 */
225
	function load ($session_id = null) {
226
		$session_data = $this->get_internal($session_id);
227
		if (!$session_data || !$this->is_session_owner_internal($session_data)) {
228
			$this->add(User::GUEST_ID);
229
			return User::GUEST_ID;
230
		}
231
		/**
232
		 * Updating last online time and ip
233
		 */
234
		$Config = Config::instance();
235
		$time   = time();
236
		if ($session_data['expire'] - $time < $Config->core['session_expire'] * $Config->core['update_ratio'] / 100) {
237
			$session_data['expire'] = $time + $Config->core['session_expire'];
238
			$this->update($session_data);
239
			$this->cache->set($session_data['id'], $session_data);
240
		}
241
		unset($session_data['data']);
242
		Event::instance()->fire(
243
			'System/Session/load',
244
			[
245
				'session_data' => $session_data
246
			]
247
		);
248
		return $this->load_initialization($session_data['id'], $session_data['user']);
249
	}
250
	/**
251
	 * Initialize session (set user id, session id and update who user is)
252
	 *
253
	 * @param string $session_id
254
	 * @param int    $user_id
255
	 *
256
	 * @return int User id
257
	 */
258 18
	protected function load_initialization ($session_id, $user_id) {
259 18
		$this->session_id = $session_id;
260 18
		$this->user_id    = $user_id;
261 18
		$this->update_user_is();
262 18
		return $user_id;
263
	}
264
	/**
265
	 * Whether profile is activated, not disabled and not blocked
266
	 *
267
	 * @param int $user
268
	 *
269
	 * @return bool
270
	 */
271 18
	protected function is_user_active ($user) {
272
		/**
273
		 * Optimization, more data requested than actually used here, because data will be requested later, and it would be nice to have that data cached
274
		 */
275 18
		$data = User::instance()->get(
276
			[
277 18
				'login',
278
				'username',
279
				'language',
280
				'timezone',
281
				'status',
282
				'block_until',
283
				'avatar'
284
			],
285
			$user
286
		);
287 18
		if (!$data) {
288
			return false;
289
		}
290 18
		$L    = Language::prefix('system_profile_sign_in_');
291 18
		$Page = Page::instance();
292 18
		switch ($data['status']) {
293 18
			case User::STATUS_INACTIVE:
294
				/**
295
				 * If user is disabled
296
				 */
297 2
				$Page->warning($L->your_account_disabled);
298 2
				return false;
299 18
			case User::STATUS_NOT_ACTIVATED:
300
				/**
301
				 * If user is not active
302
				 */
303
				$Page->warning($L->your_account_is_not_active);
304
				return false;
305
		}
306 18
		if ($data['block_until'] > time()) {
307
			/**
308
			 * If user if blocked
309
			 */
310
			$Page->warning($L->your_account_blocked_until(date($L->_datetime, $data['block_until'])));
311
			return false;
312
		}
313 18
		return true;
314
	}
315
	/**
316
	 * Create the session for the user with specified id
317
	 *
318
	 * @param int  $user
319
	 * @param bool $delete_current_session
320
	 *
321
	 * @return false|string Session id on success, `false` otherwise
322
	 */
323 18
	function add ($user, $delete_current_session = true) {
324 18
		$user = (int)$user;
325 18
		if (!$user) {
326
			return false;
327
		}
328 18
		if ($delete_current_session && is_md5($this->session_id)) {
329 6
			$this->del_internal($this->session_id, false);
330
		}
331 18
		if (!$this->is_user_active($user)) {
332
			/**
333
			 * If data was not loaded or account is not active - create guest session
334
			 */
335 2
			return $this->add(User::GUEST_ID);
336
		}
337 18
		$session_data = $this->create_unique_session($user);
338 18
		Response::instance()->cookie('session', $session_data['id'], $session_data['expire'], true);
339 18
		$this->load_initialization($session_data['id'], $session_data['user']);
340
		/**
341
		 * Delete old sessions using probability and system configuration of inserts limits and update ratio
342
		 */
343 18
		$Config = Config::instance();
344 18
		if (mt_rand(0, $Config->core['inserts_limit']) < $Config->core['inserts_limit'] / 100 * (100 - $Config->core['update_ratio']) / 5) {
345
			$this->delete_old_sessions();
346
		}
347 18
		Event::instance()->fire(
348 18
			'System/Session/add',
349
			[
350 18
				'session_data' => $session_data
351
			]
352
		);
353 18
		return $session_data['id'];
354
	}
355
	/**
356
	 * @param int $user
357
	 *
358
	 * @return array Session data
359
	 */
360 18
	protected function create_unique_session ($user) {
361 18
		$Config      = Config::instance();
362 18
		$Request     = Request::instance();
363 18
		$remote_addr = ip2hex($Request->remote_addr);
364 18
		$ip          = ip2hex($Request->ip);
365
		/**
366
		 * Many guests open only one page (or do not store any cookies), so create guest session only for 5 minutes max initially
367
		 */
368 18
		$expire_in = $user == User::GUEST_ID ? min($Config->core['session_expire'], self::INITIAL_SESSION_EXPIRATION) : $Config->core['session_expire'];
369 18
		$expire    = time() + $expire_in;
370
		/**
371
		 * Create unique session
372
		 */
373
		$session_data = [
374 18
			'id'          => null,
375 18
			'user'        => $user,
376 18
			'created'     => time(),
377 18
			'expire'      => $expire,
378 18
			'user_agent'  => $Request->header('user-agent'),
379 18
			'remote_addr' => $remote_addr,
380 18
			'ip'          => $ip,
381
			'data'        => []
382
		];
383
		do {
384 18
			$session_data['id'] = md5(random_bytes(1000));
385 18
		} while (!$this->create($session_data));
386 18
		return $session_data;
387
	}
388
	/**
389
	 * Destroying of the session
390
	 *
391
	 * @param null|string $session_id
392
	 *
393
	 * @return bool
394
	 */
395 6
	function del ($session_id = null) {
396 6
		return (bool)$this->del_internal($session_id);
397
	}
398
	/**
399
	 * Deletion of the session
400
	 *
401
	 * @param string|null $session_id
402
	 * @param bool        $create_guest_session
403
	 *
404
	 * @return bool
405
	 */
406 8
	protected function del_internal ($session_id = null, $create_guest_session = true) {
407 8
		$session_id = $session_id ?: $this->session_id;
408 8
		if (!is_md5($session_id)) {
409
			return false;
410
		}
411 8
		Event::instance()->fire(
412 8
			'System/Session/del/before',
413
			[
414 8
				'id' => $session_id
415
			]
416
		);
417 8
		unset($this->cache->$session_id);
418 8
		if ($session_id == $this->session_id) {
419 8
			$this->session_id = false;
420 8
			$this->user_id    = User::GUEST_ID;
421
		}
422 8
		Response::instance()->cookie('session', '');
423 8
		$result = $this->delete($session_id);
424 8
		if ($result) {
425 8
			if ($create_guest_session) {
426 6
				return (bool)$this->add(User::GUEST_ID);
427
			}
428 6
			Event::instance()->fire(
429 6
				'System/Session/del/after',
430
				[
431 6
					'id' => $session_id
432
				]
433
			);
434
		}
435 6
		return (bool)$result;
436
	}
437
	/**
438
	 * Delete all old sessions from DB
439
	 */
440
	protected function delete_old_sessions () {
441
		$this->db_prime()->q(
442
			"DELETE FROM `[prefix]sessions`
443
			WHERE `expire` < ".time()
444
		);
445
	}
446
	/**
447
	 * Deletion of all user sessions
448
	 *
449
	 * @param false|int $user If not specified - current user assumed
450
	 *
451
	 * @return bool
452
	 */
453 14
	function del_all ($user = false) {
454 14
		$user = $user ?: $this->user_id;
455 14
		if ($user == User::GUEST_ID) {
456
			return false;
457
		}
458 14
		Event::instance()->fire(
459 14
			'System/Session/del_all',
460
			[
461 14
				'id' => $user
462
			]
463
		);
464 14
		$sessions = $this->db_prime()->qfas(
465
			"SELECT `id`
466
			FROM `[prefix]sessions`
467 14
			WHERE `user` = '$user'"
468
		);
469 14
		foreach ($sessions ?: [] as $session) {
470 4
			if (!$this->del($session)) {
471 4
				return false;
472
			}
473
		}
474 14
		return true;
475
	}
476
}
477