Completed
Push — master ( 962dc5...7d1125 )
by Nazar
05:02
created

Management::is_session_owner_internal()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 7

Importance

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