Completed
Push — master ( 8e5c84...4079f3 )
by Nazar
27:21
created

Management::del_internal()   B

Complexity

Conditions 6
Paths 14

Size

Total Lines 31
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
eloc 20
nc 14
nop 2
dl 0
loc 31
ccs 20
cts 20
cp 1
crap 6
rs 8.439
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A Management::delete_old_sessions() 0 6 1
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 30
	protected function init_session () {
51 30
		$Request = Request::instance();
52
		/**
53
		 * If session exists
54
		 */
55 30
		if ($Request->cookie('session')) {
56 2
			$this->user_id = $this->load();
57
		}
58 30
		$this->update_user_is();
59 30
	}
60
	/**
61
	 * Updates information about who is user accessed by methods ::guest() ::user() admin()
62
	 */
63 30
	protected function update_user_is () {
64 30
		$this->is_guest = $this->user_id == User::GUEST_ID;
65 30
		$this->is_user  = false;
66 30
		$this->is_admin = false;
67 30
		if ($this->is_guest) {
68 30
			return;
69
		}
70
		/**
71
		 * Checking of user type
72
		 */
73 22
		$groups = User::instance()->get_groups($this->user_id) ?: [];
74 22
		if (in_array(User::ADMIN_GROUP_ID, $groups)) {
75 8
			$this->is_admin = true;
76 8
			$this->is_user  = true;
77 16
		} elseif (in_array(User::USER_GROUP_ID, $groups)) {
78 16
			$this->is_user = true;
79
		}
80 22
	}
81
	/**
82
	 * Is admin
83
	 *
84
	 * @return bool
85
	 */
86 10
	public function admin () {
87 10
		return $this->is_admin;
88
	}
89
	/**
90
	 * Is user
91
	 *
92
	 * @return bool
93
	 */
94 2
	public function user () {
95 2
		return $this->is_user;
96
	}
97
	/**
98
	 * Is guest
99
	 *
100
	 * @return bool
101
	 */
102 8
	public function guest () {
103 8
		return $this->is_guest;
104
	}
105
	/**
106
	 * Returns id of current session
107
	 *
108
	 * @return false|string
109
	 */
110 26
	public function get_id () {
111 26
		return $this->session_id ?: false;
112
	}
113
	/**
114
	 * Returns user id of current session
115
	 *
116
	 * @return int
117
	 */
118 30
	public function get_user () {
119 30
		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 2
	public function get ($session_id) {
129 2
		$session_data = $this->get_internal($session_id);
130 2
		unset($session_data['data']);
131 2
		return $session_data;
132
	}
133
	/**
134
	 * @param false|null|string $session_id
135
	 *
136
	 * @return false|array
137
	 */
138 4
	protected function get_internal ($session_id) {
139 4
		if (!$session_id) {
140 4
			if (!$this->session_id) {
141 4
				$this->session_id = Request::instance()->cookie('session');
142
			}
143 4
			$session_id = $this->session_id;
144
		}
145 4
		if (!is_md5($session_id)) {
146 4
			return false;
147
		}
148 4
		$data = $this->cache->get(
149
			$session_id,
150 4
			function () use ($session_id) {
151 4
				$data = $this->read($session_id);
152 4
				if (!$data || $data['expire'] <= time()) {
153 2
					return false;
154
				}
155 4
				$data['data'] = $data['data'] ?: [];
156 4
				return $data;
157 4
			}
158
		);
159 4
		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 4
	protected function is_good_session ($session_data) {
169
		return
170 4
			isset($session_data['expire'], $session_data['user']) &&
171 4
			$session_data['expire'] > time() &&
172 4
			$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 2
	public function is_session_owner ($session_id, $user_agent, $remote_addr, $ip) {
185 2
		$session_data = $this->get($session_id);
186 2
		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 2
	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 2
		if ($user_agent === null && $remote_addr === null && $ip === null) {
203 2
			$Request     = Request::instance();
204 2
			$user_agent  = $Request->header('user-agent');
205 2
			$remote_addr = $Request->remote_addr;
206 2
			$ip          = $Request->ip;
207
		}
208
		return
209 2
			md5($session_data['user_agent']) == md5($user_agent) &&
210
			(
211 2
				!Config::instance()->core['remember_user_ip'] ||
212
				(
213 2
					md5($session_data['remote_addr']) == md5(ip2hex($remote_addr)) &&
214 2
					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 2
	public function load ($session_id = null) {
226 2
		$session_data = $this->get_internal($session_id);
227 2
		if (!$session_data || !$this->is_session_owner_internal($session_data)) {
228 2
			$this->add(User::GUEST_ID);
229 2
			return User::GUEST_ID;
230
		}
231
		/**
232
		 * Updating last online time and ip
233
		 */
234 2
		$Config = Config::instance();
235 2
		$time   = time();
236 2
		if ($session_data['expire'] - $time < $Config->core['session_expire'] * $Config->core['update_ratio'] / 100) {
237 2
			$session_data['expire'] = $time + $Config->core['session_expire'];
238 2
			$this->update($session_data);
239 2
			$this->cache->set($session_data['id'], $session_data);
240 2
			Response::instance()->cookie('session', $session_data['id'], $session_data['expire'], true);
241
		}
242 2
		unset($session_data['data']);
243 2
		Event::instance()->fire(
244 2
			'System/Session/load',
245
			[
246 2
				'session_data' => $session_data
247
			]
248
		);
249 2
		return $this->load_initialization($session_data['id'], $session_data['user']);
250
	}
251
	/**
252
	 * Initialize session (set user id, session id and update who user is)
253
	 *
254
	 * @param string $session_id
255
	 * @param int    $user_id
256
	 *
257
	 * @return int User id
258
	 */
259 22
	protected function load_initialization ($session_id, $user_id) {
260 22
		$this->session_id = $session_id;
261 22
		$this->user_id    = $user_id;
262 22
		$this->update_user_is();
263 22
		return $user_id;
264
	}
265
	/**
266
	 * Whether profile is activated, not disabled and not blocked
267
	 *
268
	 * @param int $user
269
	 *
270
	 * @return bool
271
	 */
272 22
	protected function is_user_active ($user) {
273
		/**
274
		 * Optimization, more data requested than actually used here, because data will be requested later, and it would be nice to have that data cached
275
		 */
276 22
		$data = User::instance()->get(
277
			[
278 22
				'login',
279
				'username',
280
				'language',
281
				'timezone',
282
				'status',
283
				'block_until',
284
				'avatar'
285
			],
286
			$user
287
		);
288 22
		if (!$data) {
289 2
			return false;
290
		}
291 22
		$L    = Language::prefix('system_profile_sign_in_');
292 22
		$Page = Page::instance();
293 22
		switch ($data['status']) {
294 22
			case User::STATUS_INACTIVE:
295
				/**
296
				 * If user is disabled
297
				 */
298 4
				$Page->warning($L->your_account_disabled);
299 4
				return false;
300 22
			case User::STATUS_NOT_ACTIVATED:
301
				/**
302
				 * If user is not active
303
				 */
304 2
				$Page->warning($L->your_account_is_not_active);
305 2
				return false;
306
		}
307 22
		if ($data['block_until'] > time()) {
308
			/**
309
			 * If user if blocked
310
			 */
311 2
			$Page->warning($L->your_account_blocked_until(date($L->_datetime, $data['block_until'])));
312 2
			return false;
313
		}
314 22
		return true;
315
	}
316
	/**
317
	 * Create the session for the user with specified id
318
	 *
319
	 * @param int  $user
320
	 * @param bool $delete_current_session
321
	 *
322
	 * @return false|string Session id on success, `false` otherwise
323
	 */
324 22
	public function add ($user, $delete_current_session = true) {
325 22
		$user = (int)$user ?: User::GUEST_ID;
326 22
		if ($delete_current_session && is_md5($this->session_id)) {
327 4
			$this->del($this->session_id);
328
		}
329 22
		if (!$this->is_user_active($user)) {
330
			/**
331
			 * If data was not loaded or account is not active - create guest session
332
			 */
333 4
			return $this->add(User::GUEST_ID);
334
		}
335 22
		$session_data = $this->create_unique_session($user);
336 22
		Response::instance()->cookie('session', $session_data['id'], $session_data['expire'], true);
337 22
		$this->load_initialization($session_data['id'], $session_data['user']);
338
		/**
339
		 * Delete old sessions using probability and system configuration of inserts limits and update ratio
340
		 */
341 22
		$Config = Config::instance();
342 22
		if (mt_rand(0, $Config->core['inserts_limit']) < $Config->core['inserts_limit'] / 100 * (100 - $Config->core['update_ratio']) / 5) {
343 4
			$this->delete_old_sessions();
344
		}
345 22
		Event::instance()->fire(
346 22
			'System/Session/add',
347
			[
348 22
				'session_data' => $session_data
349
			]
350
		);
351 22
		return $session_data['id'];
352
	}
353
	/**
354
	 * @param int $user
355
	 *
356
	 * @return array Session data
357
	 */
358 22
	protected function create_unique_session ($user) {
359 22
		$Config      = Config::instance();
360 22
		$Request     = Request::instance();
361 22
		$remote_addr = ip2hex($Request->remote_addr);
362 22
		$ip          = ip2hex($Request->ip);
363
		/**
364
		 * Many guests open only one page (or do not store any cookies), so create guest session only for 5 minutes max initially
365
		 */
366 22
		$expire_in = $user == User::GUEST_ID ? min($Config->core['session_expire'], self::INITIAL_SESSION_EXPIRATION) : $Config->core['session_expire'];
367 22
		$expire    = time() + $expire_in;
368
		/**
369
		 * Create unique session
370
		 */
371
		$session_data = [
372 22
			'id'          => null,
373 22
			'user'        => $user,
374 22
			'created'     => time(),
375 22
			'expire'      => $expire,
376 22
			'user_agent'  => $Request->header('user-agent'),
377 22
			'remote_addr' => $remote_addr,
378 22
			'ip'          => $ip,
379
			'data'        => []
380
		];
381
		do {
382 22
			$session_data['id'] = md5(random_bytes(1000));
383 22
		} while (!$this->create($session_data));
384 22
		return $session_data;
385
	}
386
	/**
387
	 * Destroying of the session
388
	 *
389
	 * @param null|string $session_id
390
	 *
391
	 * @return bool
392
	 */
393 10
	public function del ($session_id = null) {
394 10
		$session_id = $session_id ?: $this->session_id;
395 10
		if (!is_md5($session_id)) {
396 2
			return false;
397
		}
398 10
		Event::instance()->fire(
399 10
			'System/Session/del/before',
400
			[
401 10
				'id' => $session_id
402
			]
403
		);
404 10
		$this->cache->del($session_id);
405 10
		if ($session_id == $this->session_id) {
406 10
			$this->session_id = false;
407 10
			$this->user_id    = User::GUEST_ID;
408
		}
409 10
		if (Request::instance()->cookie('session') === $session_id) {
410 10
			Response::instance()->cookie('session', '');
411
		}
412 10
		$result = $this->delete($session_id);
413 10
		if ($result) {
414 10
			Event::instance()->fire(
415 10
				'System/Session/del/after',
416
				[
417 10
					'id' => $session_id
418
				]
419
			);
420
		}
421 10
		return (bool)$result;
422
	}
423
	/**
424
	 * Delete all old sessions from DB
425
	 */
426 4
	protected function delete_old_sessions () {
427 4
		$this->db_prime()->q(
428
			'DELETE FROM `[prefix]sessions`
429 4
			WHERE `expire` < '.time()
430
		);
431 4
	}
432
	/**
433
	 * Deletion of all user sessions
434
	 *
435
	 * @param false|int $user If not specified - current user assumed
436
	 *
437
	 * @return bool
438
	 */
439 16
	public function del_all ($user = false) {
440 16
		$user = (int)$user ?: $this->user_id;
441 16
		if ($user == User::GUEST_ID) {
442 2
			return false;
443
		}
444 16
		Event::instance()->fire(
445 16
			'System/Session/del_all',
446
			[
447 16
				'id' => $user
448
			]
449
		);
450 16
		$cdb   = $this->db_prime();
451
		$query =
452
			"SELECT `id`
453
			FROM `[prefix]sessions`
454 16
			WHERE `user` = '$user'";
455 16
		while ($session = $cdb->qfs($query)) {
456 6
			if (!$this->del($session)) {
457
				return false;
458
			}
459
		}
460 16
		return true;
461
	}
462
}
463