XoopsSecurity   A
last analyzed

Complexity

Total Complexity 40

Size/Duplication

Total Lines 268
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 103
c 1
b 0
f 0
dl 0
loc 268
rs 9.2
wmc 40

12 Methods

Rating   Name   Duplication   Size   Complexity  
A check() 0 3 1
A checkSuperglobals() 0 34 3
A getErrors() 0 13 4
A clearTokens() 0 3 1
A checkBadips() 0 10 6
B validateToken() 0 40 11
A getTokenHTML() 0 6 1
A checkReferer() 0 10 3
A setErrors() 0 3 1
A createToken() 0 20 4
A garbageCollection() 0 5 3
A filterToken() 0 3 2

How to fix   Complexity   

Complex Class

Complex classes like XoopsSecurity often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use XoopsSecurity, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
use Xmf\IPAddress;
4
5
/**
6
 * XOOPS security handler
7
 *
8
 * You may not change or alter any portion of this comment or credits
9
 * of supporting developers from this source code or any supporting source code
10
 * which is considered copyrighted (c) material of the original comment or credit authors.
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
 *
15
 * @author    Kazumi Ono <[email protected]>
16
 * @author    Jan Pedersen <[email protected]>
17
 * @author    John Neill <[email protected]>
18
 * @copyright (c) 2000-2025 XOOPS Project (https://xoops.org)
19
 * @license   GNU GPL 2 (https://www.gnu.org/licenses/gpl-2.0.html)
20
 * @package   kernel
21
 * @since     2.0.0
22
 */
23
24
defined('XOOPS_ROOT_PATH') || exit('Restricted access');
25
26
/**
27
 * Class XoopsSecurity
28
 */
29
class XoopsSecurity
30
{
31
    public $errors = [];
32
33
    /**
34
     * Check if there is a valid token in $_REQUEST[$name . '_REQUEST'] - can be expanded for more wide use, later (Mith)
35
     *
36
     * @param bool         $clearIfValid whether to clear the token after validation
37
     * @param string|false $token        token to validate
38
     * @param string       $name         name of session variable
39
     *
40
     * @return bool
41
     */
42
    public function check($clearIfValid = true, $token = false, $name = 'XOOPS_TOKEN')
43
    {
44
        return $this->validateToken($token, $clearIfValid, $name);
45
    }
46
47
    /**
48
     * Create a token in the user's session
49
     *
50
     * @param int|string    $timeout time in seconds the token should be valid
51
     * @param string $name    name of session variable
52
     *
53
     * @return string token value
54
     */
55
    public function createToken($timeout = 0, $name = 'XOOPS_TOKEN')
56
    {
57
        $this->garbageCollection($name);
58
        if ($timeout == 0) {
59
            $expire  = @ini_get('session.gc_maxlifetime');
60
            $timeout = ($expire > 0) ? $expire : 900;
61
        }
62
        $token_id = md5(uniqid(mt_rand(), true));
63
        // save token data on the server
64
        if (!isset($_SESSION[$name . '_SESSION'])) {
65
            $_SESSION[$name . '_SESSION'] = [];
66
        }
67
        $token_data = [
68
            'id'     => $token_id,
69
            'expire' => time() + (int) $timeout,
70
        ];
71
        $_SESSION[$name . '_SESSION'][] = $token_data;
72
        // Force update of session in base
73
//        session_write_close();
74
        return md5($token_id . $_SERVER['HTTP_USER_AGENT'] . XOOPS_DB_PREFIX);
75
    }
76
77
    /**
78
     * Check if a token is valid. If no token is specified, $_REQUEST[$name . '_REQUEST'] is checked
79
     *
80
     * @param string|false $token        token to validate
81
     * @param bool         $clearIfValid whether to clear the token value if valid
82
     * @param string       $name         session name to validate
83
     *
84
     * @return bool
85
     */
86
    public function validateToken($token = false, $clearIfValid = true, $name = 'XOOPS_TOKEN')
87
    {
88
        // Optional: Ensure a session is active, keep this as a safeguard, but it’s likely unnecessary
89
        if (session_status() !== PHP_SESSION_ACTIVE) {
90
            session_start();
91
        }
92
93
        global $xoopsLogger;
94
        $token = ($token !== false) ? $token : ($_REQUEST[$name . '_REQUEST'] ?? '');
95
        if (empty($token) || empty($_SESSION[$name . '_SESSION'])) {
96
            $xoopsLogger->addExtra('Token Validation', 'No valid token found in request/session');
97
98
            return false;
99
        }
100
        $validFound = false;
101
        $token_data = &$_SESSION[$name . '_SESSION'];
102
        foreach (array_keys($token_data) as $i) {
103
            if ($token === md5($token_data[$i]['id'] . $_SERVER['HTTP_USER_AGENT'] . XOOPS_DB_PREFIX)) {
104
                if ($this->filterToken($token_data[$i])) {
105
                    if ($clearIfValid) {
106
                        // token should be valid once, so clear it once validated
107
                        unset($token_data[$i]);
108
                    }
109
                    $xoopsLogger->addExtra('Token Validation', 'Valid token found');
110
                    $validFound = true;
111
                } else {
112
                    $str = 'Valid token expired';
113
                    $this->setErrors($str);
114
                    $xoopsLogger->addExtra('Token Validation', $str);
115
                }
116
            }
117
        }
118
        if (!$validFound && !isset($str)) {
119
            $str = 'No valid token found';
120
            $this->setErrors($str);
121
            $xoopsLogger->addExtra('Token Validation', $str);
122
        }
123
        $this->garbageCollection($name);
124
125
        return $validFound;
126
    }
127
128
    /**
129
     * Clear all token values from user's session
130
     *
131
     * @param string $name session name
132
     *
133
     * @return void
134
     */
135
    public function clearTokens($name = 'XOOPS_TOKEN')
136
    {
137
        $_SESSION[$name . '_SESSION'] = [];
138
    }
139
140
    /**
141
     * Check whether a token value is expired or not
142
     *
143
     * @param string $token token
144
     *
145
     * @return bool
146
     */
147
    public function filterToken($token)
148
    {
149
        return (!empty($token['expire']) && $token['expire'] >= time());
150
    }
151
152
    /**
153
     * Perform garbage collection, clearing expired tokens
154
     *
155
     * @param string $name session name
156
     *
157
     * @return void
158
     */
159
    public function garbageCollection($name = 'XOOPS_TOKEN')
160
    {
161
        $sessionName = $name . '_SESSION';
162
        if (!empty($_SESSION[$sessionName]) && \is_array($_SESSION[$sessionName])) {
163
            $_SESSION[$sessionName] = array_filter($_SESSION[$sessionName], [$this, 'filterToken']);
164
        }
165
    }
166
167
    /**
168
     * Check the user agent's HTTP REFERER against XOOPS_URL
169
     *
170
     * @param int $docheck 0 to not check the referer (used with XML-RPC), 1 to actively check it
171
     *
172
     * @return bool
173
     */
174
    public function checkReferer($docheck = 1)
175
    {
176
        $ref = xoops_getenv('HTTP_REFERER');
177
        if ($docheck == 0) {
178
            return true;
179
        }
180
        if ($ref == '') {
181
            return false;
182
        }
183
        return !(strpos($ref, XOOPS_URL) !== 0);
184
    }
185
186
    /**
187
     * Check superglobals for contamination
188
     *
189
     * @return void
190
     **/
191
    public function checkSuperglobals()
192
    {
193
        foreach (
194
            [
195
                'GLOBALS',
196
                '_SESSION',
197
                'HTTP_SESSION_VARS',
198
                '_GET',
199
                'HTTP_GET_VARS',
200
                '_POST',
201
                'HTTP_POST_VARS',
202
                '_COOKIE',
203
                'HTTP_COOKIE_VARS',
204
                '_REQUEST',
205
                '_SERVER',
206
                'HTTP_SERVER_VARS',
207
                '_ENV',
208
                'HTTP_ENV_VARS',
209
                '_FILES',
210
                'HTTP_POST_FILES',
211
                'xoopsDB',
212
                'xoopsUser',
213
                'xoopsUserId',
214
                'xoopsUserGroups',
215
                'xoopsUserIsAdmin',
216
                'xoopsConfig',
217
                'xoopsOption',
218
                'xoopsModule',
219
                'xoopsModuleConfig',
220
                'xoopsRequestUri',
221
            ] as $bad_global) {
222
            if (isset($_REQUEST[$bad_global])) {
223
                header('Location: ' . XOOPS_URL . '/');
224
                exit();
225
            }
226
        }
227
    }
228
229
    /**
230
     * Check if visitor's IP address is banned
231
     * Should be changed to return bool and let the action be up to the calling script
232
     *
233
     * @return void
234
     */
235
    public function checkBadips()
236
    {
237
        global $xoopsConfig;
238
239
        $addr = IPAddress::fromRequest();
240
        $ip = $addr->asReadable();
241
        if ($xoopsConfig['enable_badips'] == 1 && $ip != '0.0.0.0') {
242
            foreach ($xoopsConfig['bad_ips'] as $bi) {
243
                if (!empty($bi) && preg_match('/' . $bi . '/', $ip)) {
244
                    exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
245
                }
246
            }
247
        }
248
    }
249
250
    /**
251
     * Get the HTML code for a XoopsFormHiddenToken object - used in forms that do not use XoopsForm elements
252
     *
253
     * @param string $name session token name
254
     *
255
     * @return string
256
     */
257
    public function getTokenHTML($name = 'XOOPS_TOKEN')
258
    {
259
        require_once XOOPS_ROOT_PATH . '/class/xoopsformloader.php';
260
        $token = new XoopsFormHiddenToken($name);
261
262
        return $token->render();
263
    }
264
265
    /**
266
     * Add an error
267
     *
268
     * @param string $error message
269
     *
270
     * @return void
271
     */
272
    public function setErrors($error)
273
    {
274
        $this->errors[] = trim($error);
275
    }
276
277
    /**
278
     * Get generated errors
279
     *
280
     * @param bool $ashtml Format using HTML?
281
     *
282
     * @return array|string Array of array messages OR HTML string
283
     */
284
    public function &getErrors($ashtml = false)
285
    {
286
        if (!$ashtml) {
287
            return $this->errors;
288
        } else {
289
            $ret = '';
290
            if (count($this->errors) > 0) {
291
                foreach ($this->errors as $error) {
292
                    $ret .= $error . '<br>';
293
                }
294
            }
295
296
            return $ret;
297
        }
298
    }
299
}
300