1 <?php
2
3 namespace Alo\Statics;
4
5 if(!defined('GEN_START')) {
6 http_response_code(404);
7 die();
8 }
9
10 /**
11 * Handles hashing, tokens, randomising and other security operations
12 *
13 * @author Art <a.molcanovas@gmail.com>
14 */
15 abstract class Security {
16
17 /**
18 * Defines the ascii charset subset as "the entire set"
19 *
20 * @var int
21 */
22 const ASCII_ALL = 0;
23 /**
24 * Defines the ascii charset subset as "only alphanumeric"
25 *
26 * @var int
27 */
28 const ASCII_ALPHANUM = 1;
29 /**
30 * Defines the ascii charset subset as "only non-alphanumeric"
31 *
32 * @var int
33 */
34 const ASCII_NONALPHANUM = 2;
35 /**
36 * Array of ASCII alphanumeric characters
37 *
38 * @var array
39 */
40 protected static $ascii_alphanum = ['a',
41 'b',
42 'c',
43 'd',
44 'e',
45 'f',
46 'g',
47 'h',
48 'i',
49 'j',
50 'k',
51 'l',
52 'm',
53 'n',
54 'o',
55 'p',
56 'q',
57 'r',
58 's',
59 't',
60 'u',
61 'v',
62 'w',
63 'x',
64 'y',
65 'z',
66 'A',
67 'B',
68 'C',
69 'D',
70 'E',
71 'F',
72 'G',
73 'H',
74 'I',
75 'J',
76 'K',
77 'L',
78 'M',
79 'N',
80 'O',
81 'P',
82 'Q',
83 'R',
84 'S',
85 'T',
86 'U',
87 'V',
88 'W',
89 'X',
90 'Y',
91 'Z',
92 0,
93 1,
94 2,
95 3,
96 4,
97 5,
98 6,
99 7,
100 8,
101 9];
102 /**
103 * The rest of the ASCII charset
104 *
105 * @var array
106 */
107 protected static $ascii_rest = [' ',
108 '!',
109 '"',
110 '#',
111 '$',
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 * Escapes a string or array (recursively) from XSS attacks
142 *
143 * @author Art <a.molcanovas@gmail.com>
144 *
145 * @param string|array $item The item to be escaped
146 *
147 * @return string|array
148 */
149 static function un_xss($item) {
150 if(is_array($item)) {
151 foreach($item as &$v) {
152 $v = escape($item);
153 }
154
155 return $item;
156 } else {
157 return is_scalar($item) ? htmlspecialchars($item, ENT_QUOTES | ENT_HTML5, 'UTF-8', false) : null;
158 }
159 }
160
161 /**
162 * Generates a token and sets it in session
163 *
164 * @author Art <a.molcanovas@gmail.com>
165 *
166 * @param string $token_name The token name
167 * @param string $hash Which hash algorithm to use
168 *
169 * @return string The generated token
170 */
171 static function tokenGet($token_name, $hash = 'md5') {
172 $token = self::getUniqid($hash, 'token_' . $token_name);
173
174 if(!\Alo::$session) {
175 trigger_error('Session handler not initialised or not assigned to \\Alo::$session. Token not saved in session.',
176 E_USER_WARNING);
177 } else {
178 \Alo::$session->{$token_name} = $token;
179 }
180
181 return $token;
182 }
183
184 /**
185 * Generates a unique identifier
186 *
187 * @author Art <a.molcanovas@gmail.com>
188 *
189 * @param string $hash Hash algorithm
190 * @param string|int $prefix Prefix for the identifier
191 * @param int $entropy Number of pseudo bytes used in entropy
192 *
193 * @return string
194 */
195 static function getUniqid($hash = 'md5', $prefix = null, $entropy = 50) {
196 $str = uniqid(mt_rand(PHP_INT_MIN, PHP_INT_MAX) . json_encode([
197 $_COOKIE,
198 $_REQUEST,
199 $_FILES,
200 $_ENV,
201 $_GET,
202 $_POST,
203 $_SERVER
204 ]),
205 true)
206 . $prefix . self::ascii_rand($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 ascii_rand($length, $subset = self::ASCII_ALL) {
226 switch($subset) {
227 case self::ASCII_ALPHANUM:
228 $subset = self::$ascii_alphanum;
229 break;
230 case self::ASCII_NONALPHANUM:
231 $subset = self::$ascii_rest;
232 break;
233 default:
234 $subset = array_merge(self::$ascii_alphanum, self::$ascii_rest);
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 $token_name The token name
254 * @param array $data_array 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($token_name, array $data_array = null) {
259 if(!\Alo::$session) {
260 trigger_error('Session handler not initialised or not assigned to \\Alo::$session. FALSE will be returned. ',
261 E_USER_WARNING);
262
263 return false;
264 } else {
265 if($data_array === null) {
266 $data_array = $_POST;
267 }
268
269 $sess_token = \Alo::$session->{$token_name};
270
271 return $sess_token && \get($data_array[$token_name]) && $sess_token == $data_array[$token_name];
272 }
273 }
274
275 /**
276 * Removes a token from session data
277 *
278 * @author Art <a.molcanovas@gmail.com>
279 *
280 * @param string $token_name The token's name
281 *
282 * @return bool TRUE if the session handler was loaded, false if not
283 */
284 static function tokenRemove($token_name) {
285 if(\Alo::$session) {
286 \Alo::$session->delete($token_name);
287
288 return true;
289 }
290
291 return false;
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'])
302 . \get($_SERVER['HTTP_DNT']) . '^#J!kCRh&H4CKav'
303 . \get($_SERVER['HTTP_ACCEPT_LANGUAGE']) . 'h0&ThYYxk4YOD!g' . \get($_SERVER['REMOTE_ADDR']);
304 }
305
306 }