Completed
Push — master ( 4fa9cf...1263a5 )
by Nazar
04:53
created

Management::delete_old_sessions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 0
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 1
rs 9.4285
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 46
	protected function init_session () {
51 46
		$Request = Request::instance();
52
		/**
53
		 * If session exists
54
		 */
55 46
		if ($Request->cookie('session')) {
56 16
			$this->user_id = $this->load();
57
		}
58 46
		$this->update_user_is();
59 46
	}
60
	/**
61
	 * Updates information about who is user accessed by methods ::guest() ::user() admin()
62
	 */
63 46
	protected function update_user_is () {
64 46
		$this->is_guest = $this->user_id == User::GUEST_ID;
65 46
		$this->is_user  = false;
66 46
		$this->is_admin = false;
67 46
		if ($this->is_guest) {
68 46
			return;
69
		}
70
		/**
71
		 * Checking of user type
72
		 */
73 32
		$groups = User::instance()->get_groups($this->user_id) ?: [];
74 32
		if (in_array(User::ADMIN_GROUP_ID, $groups)) {
75 6
			$this->is_admin = true;
76 6
			$this->is_user  = true;
77 28
		} elseif (in_array(User::USER_GROUP_ID, $groups)) {
78 28
			$this->is_user = true;
79
		}
80 32
	}
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 4
	public function user () {
95 4
		return $this->is_user;
96
	}
97
	/**
98
	 * Is guest
99
	 *
100
	 * @return bool
101
	 */
102 16
	public function guest () {
103 16
		return $this->is_guest;
104
	}
105
	/**
106
	 * Returns id of current session
107
	 *
108
	 * @return false|string
109
	 */
110 38
	public function get_id () {
111 38
		return $this->session_id ?: false;
112
	}
113
	/**
114
	 * Returns user id of current session
115
	 *
116
	 * @return int
117
	 */
118 46
	public function get_user () {
119 46
		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 18
	protected function get_internal ($session_id) {
139 18
		if (!$session_id) {
140 18
			if (!$this->session_id) {
141 18
				$this->session_id = Request::instance()->cookie('session');
142
			}
143 18
			$session_id = $this->session_id;
144
		}
145 18
		if (!is_md5($session_id)) {
146 4
			return false;
147
		}
148 18
		$data = $this->cache->get(
149
			$session_id,
150 18
			function () use ($session_id) {
151 18
				$data = $this->read($session_id);
152 18
				if (!$data || $data['expire'] <= time()) {
153 2
					return false;
154
				}
155 18
				$data['data'] = $data['data'] ?: [];
156 18
				return $data;
157 18
			}
158
		);
159 18
		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 18
	protected function is_good_session ($session_data) {
169
		return
170 18
			isset($session_data['expire'], $session_data['user']) &&
171 18
			$session_data['expire'] > time() &&
172 18
			$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 16
	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 16
		if ($user_agent === null && $remote_addr === null && $ip === null) {
203 16
			$Request     = Request::instance();
204 16
			$user_agent  = $Request->header('user-agent');
205 16
			$remote_addr = $Request->remote_addr;
206 16
			$ip          = $Request->ip;
207
		}
208
		return
209 16
			md5($session_data['user_agent']) == md5($user_agent) &&
210
			(
211 16
				!Config::instance()->core['remember_user_ip'] ||
212
				(
213 2
					md5($session_data['remote_addr']) == md5(ip2hex($remote_addr)) &&
214 16
					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 16
	public function load ($session_id = null) {
226 16
		$session_data = $this->get_internal($session_id);
227 16
		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 16
		$Config = Config::instance();
235 16
		$time   = time();
236 16
		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 16
		unset($session_data['data']);
243 16
		Event::instance()->fire(
244 16
			'System/Session/load',
245
			[
246 16
				'session_data' => $session_data
247
			]
248
		);
249 16
		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 32
	protected function load_initialization ($session_id, $user_id) {
260 32
		$this->session_id = $session_id;
261 32
		$this->user_id    = $user_id;
262 32
		$this->update_user_is();
263 32
		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 32
	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 32
		$data = User::instance()->get(
277
			[
278 32
				'login',
279
				'username',
280
				'language',
281
				'timezone',
282
				'status',
283
				'block_until',
284
				'avatar'
285
			],
286
			$user
287
		);
288
		return
289 32
			$data &&
290 32
			$data['status'] == User::STATUS_ACTIVE &&
291 32
			$data['block_until'] <= time();
292
	}
293
	/**
294
	 * Create the session for the user with specified id
295
	 *
296
	 * @param int  $user
297
	 * @param bool $delete_current_session
298
	 *
299
	 * @return false|string Session id on success, `false` otherwise
300
	 */
301 32
	public function add ($user, $delete_current_session = true) {
302 32
		$user = (int)$user ?: User::GUEST_ID;
303 32
		if ($delete_current_session && is_md5($this->session_id)) {
304 4
			$this->del($this->session_id);
305
		}
306 32
		if (!$this->is_user_active($user)) {
307
			/**
308
			 * If data was not loaded or account is not active - create guest session
309
			 */
310 4
			return $this->add(User::GUEST_ID);
311
		}
312 32
		$session_data = $this->create_unique_session($user);
313 32
		Response::instance()->cookie('session', $session_data['id'], $session_data['expire'], true);
314 32
		$this->load_initialization($session_data['id'], $session_data['user']);
315
		/**
316
		 * Delete old sessions using probability and system configuration of inserts limits and update ratio
317
		 */
318 32
		$Config = Config::instance();
319 32
		if (mt_rand(0, $Config->core['inserts_limit']) < $Config->core['inserts_limit'] / 100 * (100 - $Config->core['update_ratio']) / 5) {
320 2
			$this->delete_old_sessions();
321
		}
322 32
		Event::instance()->fire(
323 32
			'System/Session/add',
324
			[
325 32
				'session_data' => $session_data
326
			]
327
		);
328 32
		return $session_data['id'];
329
	}
330
	/**
331
	 * @param int $user
332
	 *
333
	 * @return array Session data
334
	 */
335 32
	protected function create_unique_session ($user) {
336 32
		$Config      = Config::instance();
337 32
		$Request     = Request::instance();
338 32
		$remote_addr = ip2hex($Request->remote_addr);
339 32
		$ip          = ip2hex($Request->ip);
340
		/**
341
		 * Many guests open only one page (or do not store any cookies), so create guest session only for 5 minutes max initially
342
		 */
343 32
		$expire_in = $user == User::GUEST_ID ? min($Config->core['session_expire'], self::INITIAL_SESSION_EXPIRATION) : $Config->core['session_expire'];
344 32
		$expire    = time() + $expire_in;
345
		/**
346
		 * Create unique session
347
		 */
348
		$session_data = [
349 32
			'id'          => null,
350 32
			'user'        => $user,
351 32
			'created'     => time(),
352 32
			'expire'      => $expire,
353 32
			'user_agent'  => $Request->header('user-agent'),
354 32
			'remote_addr' => $remote_addr,
355 32
			'ip'          => $ip,
356
			'data'        => []
357
		];
358
		do {
359 32
			$session_data['id'] = md5(random_bytes(1000));
360 32
		} while (!$this->create($session_data));
361 32
		return $session_data;
362
	}
363
	/**
364
	 * Destroying of the session
365
	 *
366
	 * @param null|string $session_id
367
	 *
368
	 * @return bool
369
	 */
370 16
	public function del ($session_id = null) {
371 16
		$session_id = $session_id ?: $this->session_id;
372 16
		if (!is_md5($session_id)) {
373 2
			return false;
374
		}
375 16
		Event::instance()->fire(
376 16
			'System/Session/del/before',
377
			[
378 16
				'id' => $session_id
379
			]
380
		);
381 16
		$this->cache->del($session_id);
382 16
		if ($session_id == $this->session_id) {
383 16
			$this->session_id = false;
384 16
			$this->user_id    = User::GUEST_ID;
385
		}
386 16
		if (Request::instance()->cookie('session') === $session_id) {
387 16
			Response::instance()->cookie('session', '');
388
		}
389 16
		$result = $this->delete($session_id);
390 16
		if ($result) {
391 16
			Event::instance()->fire(
392 16
				'System/Session/del/after',
393
				[
394 16
					'id' => $session_id
395
				]
396
			);
397
		}
398 16
		return (bool)$result;
399
	}
400
	/**
401
	 * Delete all old sessions from DB
402
	 */
403 2
	protected function delete_old_sessions () {
404 2
		$this->db_prime()->q(
405
			'DELETE FROM `[prefix]sessions`
406 2
			WHERE `expire` < '.time()
407
		);
408 2
	}
409
	/**
410
	 * Deletion of all user sessions
411
	 *
412
	 * @param false|int $user If not specified - current user assumed
413
	 *
414
	 * @return bool
415
	 */
416 28
	public function del_all ($user = false) {
417 28
		$user = (int)$user ?: $this->user_id;
418 28
		if ($user == User::GUEST_ID) {
419 2
			return false;
420
		}
421 28
		Event::instance()->fire(
422 28
			'System/Session/del_all',
423
			[
424 28
				'id' => $user
425
			]
426
		);
427 28
		$cdb   = $this->db_prime();
428
		$query =
429
			"SELECT `id`
430
			FROM `[prefix]sessions`
431 28
			WHERE `user` = '$user'";
432 28
		while ($session = $cdb->qfs($query)) {
433 10
			if (!$this->del($session)) {
434
				return false;
435
			}
436
		}
437 28
		return true;
438
	}
439
}
440