1 <?php
2
3 namespace Alo\Statics;
4
5 use Alo\Session\AbstractSession;
6
7 if (!defined('GEN_START')) {
8 http_response_code(404);
9 } else {
10
11 /**
12 * Handles hashing, tokens, randomising and other security operations
13 *
14 * @author Art <a.molcanovas@gmail.com>
15 */
16 abstract class Security {
17
18 /**
19 * Defines the ascii charset subset as "the entire set"
20 *
21 * @var int
22 */
23 const ASCII_ALL = 0;
24
25 /**
26 * Defines the ascii charset subset as "only alphanumeric"
27 *
28 * @var int
29 */
30 const ASCII_ALPHANUM = 1;
31
32 /**
33 * Defines the ascii charset subset as "only non-alphanumeric"
34 *
35 * @var int
36 */
37 const ASCII_NONALPHANUM = 2;
38
39 /**
40 * Array of ASCII alphanumeric characters
41 *
42 * @var array
43 */
44 protected static $asciiAlphanum = ['a',
45 'b',
46 'c',
47 'd',
48 'e',
49 'f',
50 'g',
51 'h',
52 'i',
53 'j',
54 'k',
55 'l',
56 'm',
57 'n',
58 'o',
59 'p',
60 'q',
61 'r',
62 's',
63 't',
64 'u',
65 'v',
66 'w',
67 'x',
68 'y',
69 'z',
70 'A',
71 'B',
72 'C',
73 'D',
74 'E',
75 'F',
76 'G',
77 'H',
78 'I',
79 'J',
80 'K',
81 'L',
82 'M',
83 'N',
84 'O',
85 'P',
86 'Q',
87 'R',
88 'S',
89 'T',
90 'U',
91 'V',
92 'W',
93 'X',
94 'Y',
95 'Z',
96 0,
97 1,
98 2,
99 3,
100 4,
101 5,
102 6,
103 7,
104 8,
105 9];
106 /**
107 * The rest of the ASCII charset
108 *
109 * @var array
110 */
111 protected static $asciiRest = [' ',
112 '!',
113 '"',
114 '#',
115 '$',
116 '%',
117 '\'',
118 '(',
119 ')',
120 '*',
121 '+',
122 ',',
123 '.',
124 '/',
125 ':',
126 ';',
127 '<',
128 '=',
129 '>',
130 '?',
131 '@',
132 '[',
133 '\\',
134 ']',
135 '^',
136 '_',
137 '`',
138 '-',
139 '{',
140 '|',
141 '}',
142 '~'];
143
144 /**
145 * Escapes a string or array (recursively) from XSS attacks
146 *
147 * @author Art <a.molcanovas@gmail.com>
148 *
149 * @param string|array $item The item to be escaped
150 *
151 * @return string|array
152 */
153 static function unXss($item) {
154 if (is_array($item)) {
155 foreach ($item as &$v) {
156 $v = self::unXss($item);
157 }
158
159 return $item;
160 } else {
161 return is_scalar($item) ? htmlspecialchars($item, ENT_QUOTES | ENT_HTML5, 'UTF-8', false) : null;
162 }
163 }
164
165 /**
166 * Generates a token and sets it in session
167 *
168 * @author Art <a.molcanovas@gmail.com>
169 *
170 * @param string $tokenName The token name
171 * @param string $hash Which hash algorithm to use
172 *
173 * @return string The generated token
174 */
175 static function tokenGet($tokenName, $hash = 'md5') {
176 $token = self::getUniqid($hash, 'token_' . $tokenName);
177
178 if (!AbstractSession::isActive()) {
179 phpWarning('A session is not currently active - tokens are unusable.');
180 } else {
181 $_SESSION[$tokenName] = $token;
182 }
183
184 return $token;
185 }
186
187 /**
188 * Generates a unique identifier
189 *
190 * @author Art <a.molcanovas@gmail.com>
191 *
192 * @param string $hash Hash algorithm
193 * @param string|int $prefix Prefix for the identifier
194 * @param int $entropy Number of pseudo bytes used in entropy
195 *
196 * @return string
197 */
198 static function getUniqid($hash = 'md5', $prefix = null, $entropy = 50) {
199 $str = uniqid(mt_rand(PHP_INT_MIN, PHP_INT_MAX) . json_encode([$_COOKIE,
200 $_REQUEST,
201 $_FILES,
202 $_ENV,
203 $_GET,
204 $_POST,
205 $_SERVER]), true) . $prefix .
206 self::asciiRand($entropy);
207
208 if (function_exists('\openssl_random_pseudo_bytes')) {
209 $str .= \openssl_random_pseudo_bytes($entropy);
210 }
211
212 return hash($hash, $str);
213 }
214
215 /**
216 * Generates a string of random ASCII characters
217 *
218 * @author Art <a.molcanovas@gmail.com>
219 *
220 * @param int $length The length of the string
221 * @param int $subset Which subset to use - see class' ASCII_* constants
222 *
223 * @return string
224 */
225 static function asciiRand($length, $subset = self::ASCII_ALL) {
226 switch ($subset) {
227 case self::ASCII_ALPHANUM:
228 $subset = self::$asciiAlphanum;
229 break;
230 case self::ASCII_NONALPHANUM:
231 $subset = self::$asciiRest;
232 break;
233 default:
234 $subset = array_merge(self::$asciiAlphanum, self::$asciiRest);
235 }
236
237 $count = count($subset) - 1;
238
239 $r = '';
240
241 for ($i = 0; $i < $length; $i++) {
242 $r .= $subset[mt_rand(0, $count)];
243 }
244
245 return $r;
246 }
247
248 /**
249 * Checks if a token is valid
250 *
251 * @author Art <a.molcanovas@gmail.com>
252 *
253 * @param string $tokenName The token name
254 * @param array $dataArray Which data array to check. Defaults to $_POST
255 *
256 * @return bool TRUE if the token is valid, false if not
257 */
258 static function tokenValid($tokenName, array $dataArray = null) {
259 if (!AbstractSession::isActive()) {
260 phpWarning('Session isn\'t initialised - tokens unavailable.');
261
262 return false;
263 } else {
264 if ($dataArray === null) {
265 $dataArray = $_POST;
266 }
267
268 return $_SESSION[$tokenName] && \get($dataArray[$tokenName]) &&
269 $_SESSION[$tokenName] == $dataArray[$tokenName];
270 }
271 }
272
273 /**
274 * Removes a token from session data
275 *
276 * @author Art <a.molcanovas@gmail.com>
277 *
278 * @param string $tokenName The token's name
279 *
280 * @return bool TRUE if the session handler was loaded, false if not
281 */
282 static function tokenRemove($tokenName) {
283 if (AbstractSession::isActive()) {
284 unset($_SESSION[$tokenName]);
285
286 return true;
287 } else {
288 phpWarning('Session not initialised - tokens unavailable.');
289
290 return false;
291 }
292 }
293
294 /**
295 * Returns an unhashed browser/IP fingerprint
296 *
297 * @author Art <a.molcanovas@gmail.com>
298 * @return string
299 */
300 static function getFingerprint() {
301 return '$%c0hYlc$kn!rZF' . \get($_SERVER['HTTP_USER_AGENT']) . \get($_SERVER['HTTP_DNT']) .
302 '^#J!kCRh&H4CKav' . \get($_SERVER['HTTP_ACCEPT_LANGUAGE']) . 'h0&ThYYxk4YOD!g' .
303 \get($_SERVER['REMOTE_ADDR']);
304 }
305
306 }
307 }
308