Passed
Push — 2.x ( f11ac1...7015c1 )
by Terry
02:01
created

SessionTrait::limitSession()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 1
b 0
f 0
nc 1
nop 3
dl 0
loc 6
rs 10
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\Kernel;
24
25
use Shieldon\Firewall\Kernel;
26
use function Shieldon\Firewall\get_session;
27
use function microtime;
28
use function str_replace;
29
use function time;
30
31
/*
32
 * The main functionality for this trait is to limit the online session amount.
33
 */
34
trait SessionTrait
35
{
36
    /**
37
     *   Public methods       | Desctiotion
38
     *  ----------------------|---------------------------------------------
39
     *   limitSession         | Limit the amount of the online users.
40
     *   getSessionCount      | Get the amount of the sessions.
41
     *   removeSessionsByIp   | Remove sessions using the same IP address.
42
     *  ----------------------|---------------------------------------------
43
     */
44
45
    /**
46
     * Are you willing to limit the online session amount?
47
     *
48
     * @var array
49
     */
50
    protected $sessionLimit = [
51
52
        // How many sessions will be available?
53
        // 0 = no limit.
54
        'count' => 0,
55
56
        // How many minutes will a session be availe to visit?
57
        // 0 = no limit.
58
        'period' => 0,
59
60
        // Only allow one session per IP address.
61
        // If this option is set to true, user with multiple sessions will be
62
        // removed from the session table.
63
        'unique_only' => false,
64
    ];
65
66
    /**
67
     * Record the online session status.
68
     * This will be enabled when $sessionLimit[count] > 0
69
     *
70
     * This array is recording a live data, not a setting value.
71
     *
72
     * @var array
73
     */
74
    protected $sessionStatus = [
75
76
        // Online session count.
77
        'count' => 0,
78
79
        // Current session order.
80
        'order' => 0,
81
82
        // Current waiting queue.
83
        'queue' => 0,
84
    ];
85
86
87
    /**
88
     * Current user's session data.
89
     *
90
     * @var array
91
     */
92
    protected $sessionData = [];
93
94
    /**
95
     * Limt online sessions.
96
     *
97
     * @param int $count  The amount of online users. If reached, users will be
98
     *                    in queue.
99
     * @param int $period The period of time allows users browsering. 
100
     *                    (unit: second)
101
     *
102
     * @return void
103
     */
104
    public function limitSession(int $count = 1000, int $period = 300, bool $unique = false): void
105
    {
106
        $this->sessionLimit = [
107
            'count' => $count,
108
            'period' => $period,
109
            'unique_only' => $unique,
110
        ];
111
    }
112
113
    /**
114
     * Get online people count. If enable limitSession.
115
     *
116
     * @return int
117
     */
118
    public function getSessionCount(): int
119
    {
120
        return $this->sessionStatus['count'];
121
    }
122
123
    /**
124
     * Deal with online sessions.
125
     *
126
     * @param int $statusCode The response code.
127
     *
128
     * @return int The response code.
129
     */
130
    protected function sessionHandler($statusCode): int
131
    {
132
        if (Kernel::RESPONSE_ALLOW !== $statusCode) {
133
            return $statusCode;
134
        }
135
136
        // If you don't enable `limit traffic`, ignore the following steps.
137
        if (empty($this->sessionLimit['count'])) {
138
            return Kernel::RESPONSE_ALLOW;
139
140
        } else {
141
142
            // Get the proerties.
143
            $limit = (int) ($this->sessionLimit['count'] ?? 0);
144
            $period = (int) ($this->sessionLimit['period'] ?? 300);
145
            $now = time();
146
147
            $this->sessionData = $this->driver->getAll('session');
148
            $sessionPools = [];
149
150
            $i = 1;
151
            $sessionOrder = 0;
152
153
            if (!empty($this->sessionData)) {
154
                foreach ($this->sessionData as $v) {
155
                    $sessionPools[] = $v['id'];
156
                    $lasttime = (int) $v['time'];
157
    
158
                    if (get_session()->get('id') === $v['id']) {
159
                        $sessionOrder = $i;
160
                    }
161
    
162
                    // Remove session if it expires.
163
                    if ($now - $lasttime > $period) {
164
                        $this->driver->delete($v['id'], 'session');
165
                    }
166
                    $i++;
167
                }
168
169
                if (0 === $sessionOrder) {
170
                    $sessionOrder = $i;
171
                }
172
            } else {
173
                $sessionOrder = 0;
174
            }
175
176
            // Count the online sessions.
177
            $this->sessionStatus['count'] = count($sessionPools);
178
            $this->sessionStatus['order'] = $sessionOrder;
179
            $this->sessionStatus['queue'] = $sessionOrder - $limit;
180
181
            if (!in_array(get_session()->get('id'), $sessionPools)) {
182
                $this->sessionStatus['count']++;
183
184
                $data = [];
185
186
                // New session, record this data.
187
                $data['id'] = get_session()->get('id');
188
                $data['ip'] = $this->ip;
189
                $data['time'] = $now;
190
191
                $microtimesamp = explode(' ', microtime());
192
                $microtimesamp = $microtimesamp[1] . str_replace('0.', '', $microtimesamp[0]);
193
                $data['microtimesamp'] = $microtimesamp;
194
195
                $this->driver->save(get_session()->get('id'), $data, 'session');
196
            }
197
198
            // Online session count reached the limit. So return RESPONSE_LIMIT_SESSION response code.
199
            if ($sessionOrder >= $limit) {
200
                return Kernel::RESPONSE_LIMIT_SESSION;
201
            }
202
        }
203
204
        return Kernel::RESPONSE_ALLOW;
205
    }
206
207
    /**
208
     * Remove sessions using the same IP address.
209
     * This method must be run after `sessionHandler`.
210
     * 
211
     * @param string $ip An IP address
212
     *
213
     * @return void
214
     */
215
    protected function removeSessionsByIp(string $ip): void
216
    {
217
        if ($this->sessionLimit['unique_only']) {
218
            foreach ($this->sessionData as $v) {
219
                if ($v['ip'] === $ip) {
220
                    $this->driver->delete($v['id'], 'session');
221
                }
222
            }
223
        }
224
    }
225
226
    // @codeCoverageIgnoreStart
227
228
    /**
229
     * For testing propose.
230
     *
231
     * @param string $sessionId The session Id.
232
     *
233
     * @return void
234
     */
235
    protected function setSessionId(string $sessionId = ''): void
236
    {
237
        if ('' !== $sessionId) {
238
            get_session()->set('id', $sessionId);
239
        }
240
    }
241
242
    // @codeCoverageIgnoreEnd
243
}
244