Passed
Push — master ( c4e371...cb3b3a )
by Stefan
09:02
created

InputValidation::hugeInteger()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php
2
3
/*
4
 * ******************************************************************************
5
 * Copyright 2011-2017 DANTE Ltd. and GÉANT on behalf of the GN3, GN3+, GN4-1 
6
 * and GN4-2 consortia
7
 *
8
 * License: see the web/copyright.php file in the file structure
9
 * ******************************************************************************
10
 */
11
12
namespace web\lib\common;
13
14
use \Exception;
15
16
/**
17
 * performs validation of user inputs
18
 */
19
class InputValidation {
20
21
    /**
22
     * returns a simple HTML <p> element with basic explanations about what was
23
     * wrong with the input
24
     * 
25
     * @param string $customtext explanation provided by the validator function
26
     * @return string
27
     */
28
    private function inputValidationError($customtext) {
29
        return "<p>" . _("Input validation error: ") . $customtext . "</p>";
30
    }
31
32
    /**
33
     * Is this a known Federation? Optionally, also check if the authenticated
34
     * user is a federation admin of that federation
35
     * @param mixed $input the ISO code of the federation
0 ignored issues
show
Coding Style introduced by
Expected 7 spaces after parameter type; 1 found
Loading history...
36
     * @param string|NULL $owner the authenticated username, optional
37
     * @return \core\Federation
38
     * @throws Exception
39
     */
40
    public function Federation($input, $owner = NULL) {
0 ignored issues
show
Coding Style introduced by
Method name "InputValidation::Federation" is not in camel caps format
Loading history...
41
42
        $cat = new \core\CAT();
43
        $fedIdentifiers = array_keys($cat->knownFederations);
44
        if (!in_array(strtoupper($input), $fedIdentifiers)) {
45
            throw new Exception($this->inputValidationError(sprintf("This %s does not exist!", $cat->nomenclature_fed)));
46
        }
47
        // totally circular, but this hopefully *finally* make Scrutinizer happier
48
        $correctIndex = array_search(strtoupper($input), $fedIdentifiers);
49
        $postFed = $fedIdentifiers[$correctIndex];
50
51
        $temp = new \core\Federation($postFed);
52
        if ($owner === NULL) {
53
            return $temp;
54
        }
55
56
        foreach ($temp->listFederationAdmins() as $oneowner) {
57
            if ($oneowner == $owner) {
58
                return $temp;
59
            }
60
        }
61
        throw new Exception($this->inputValidationError(sprintf("User is not %s administrator!", $cat->nomenclature_fed)));
62
    }
63
64
    /**
65
     * Is this a known IdP? Optionally, also check if the authenticated
66
     * user is an admin of that IdP
67
     * @param mixed $input the numeric ID of the IdP in the system
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
68
     * @param string $owner the authenticated username, optional
69
     * @return \core\IdP
70
     * @throws Exception
71
     */
72
    public function IdP($input, $owner = NULL) {
0 ignored issues
show
Coding Style introduced by
Method name "InputValidation::IdP" is not in camel caps format
Loading history...
73
        $clean = $this->integer($input);
74
        if ($clean === FALSE) {
75
            throw new Exception($this->inputValidationError("Value for IdP is not an integer!"));
76
        }
77
78
        $temp = new \core\IdP($input); // constructor throws an exception if NX, game over
79
80
        if ($owner !== NULL) { // check if the authenticated user is allowed to see this institution
81
            foreach ($temp->listOwners() as $oneowner) {
82
                if ($oneowner['ID'] == $owner) {
83
                    return $temp;
84
                }
85
            }
86
            throw new Exception($this->inputValidationError("This IdP identifier is not accessible!"));
87
        }
88
        return $temp;
89
    }
90
91
    /**
92
     * Checks if the input refers to a known Profile. Optionally also takes an
93
     * IdP identifier and then checks if the Profile belongs to the refernced 
94
     * IdP
95
     * 
96
     * @param mixed $input the numeric ID of the Profile in the system
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Expected 9 spaces after parameter name; 1 found
Loading history...
97
     * @param int|NULL $idpIdentifier the numeric ID of the IdP in the system, optional
98
     * @return \core\AbstractProfile
99
     * @throws Exception
100
     */
101
    public function Profile($input, $idpIdentifier = NULL) {
0 ignored issues
show
Coding Style introduced by
Method name "InputValidation::Profile" is not in camel caps format
Loading history...
102
        $clean = $this->integer($input);
103
        if ($clean === FALSE) {
104
            throw new Exception("Non-integer was passed to Profile validator!");
105
        }
106
        $temp = \core\ProfileFactory::instantiate($clean); // constructor throws an exception if NX, game over
107
108
        if ($idpIdentifier !== NULL && $temp->institution != $idpIdentifier) {
109
            throw new Exception($this->inputValidationError("The profile does not belong to the IdP!"));
110
        }
111
        return $temp;
112
    }
113
114
    /**
115
     * Checks if this is a device known to the system
116
     * @param mixed $input the name of the device (index in the Devices.php array)
117
     * @return string returns the same string on success, throws an Exception on failure
118
     * @throws Exception
119
     */
120
    public function Device($input) {
0 ignored issues
show
Coding Style introduced by
Method name "InputValidation::Device" is not in camel caps format
Loading history...
121
        $devicelist = \devices\Devices::listDevices();
122
        $keyArray = array_keys($devicelist);
123
        if (!isset($devicelist[$input])) {
124
            throw new Exception($this->inputValidationError("This device does not exist!"));
125
        }
126
        $correctIndex = array_search($input, $keyArray);
127
        return $keyArray[$correctIndex];
128
    }
129
130
    /**
131
     * Checks if the input was a valid string.
132
     * 
133
     * @param mixed $input a string to be made SQL-safe
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Expected 11 spaces after parameter name; 1 found
Loading history...
134
     * @param boolean $allowWhitespace whether some whitespace (e.g. newlines should be preserved (true) or redacted (false)
135
     * @return string the massaged string
136
     */
137
    public function string($input, $allowWhitespace = FALSE) {
138
    // always chop out invalid characters, and surrounding whitespace
139
    $retvalStep0 =  iconv("UTF-8", "UTF-8//TRANSLIT", $input);
140
    if ($retvalStep0 === FALSE) {
141
        throw new Exception("iconv failure for string sanitisation. With TRANSLIT, this should never happen!");
142
    }
143
    $retvalStep1 = trim($retvalStep0);
144
    // if some funny person wants to inject markup tags, remove them
145
    $retval = filter_var($retvalStep1, FILTER_SANITIZE_STRING, ["flags" => FILTER_FLAG_NO_ENCODE_QUOTES]);
146
    if ($retval === FALSE) {
147
        throw new Exception("filter_var failure for string sanitisation.");
148
    }
149
    // unless explicitly wanted, take away intermediate disturbing whitespace
150
    // a simple "space" is NOT disturbing :-)
151
    if ($allowWhitespace === FALSE) {
152
        $afterWhitespace = preg_replace('/(\0|\r|\x0b|\t|\n)/', '', $retval);
153
    } else {
154
        // even if we allow whitespace, not pathological ones!
155
        $afterWhitespace = preg_replace('/(\0|\r|\x0b)/', '', $retval);
156
    }
157
    if (is_array($afterWhitespace)) {
158
        throw new Exception("This function has to be given a string and returns a string. preg_replace has generated an array instead!");
159
    }
160
    return (string) $afterWhitespace;
0 ignored issues
show
Coding Style introduced by
A cast statement should not be followed as per the coding-style.
Loading history...
161
}
162
163
/**
164
 * Is this an integer, or a string that represents an integer?
165
 * 
166
 * @param mixed $input
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
167
 * @return boolean|int returns the input, or FALSE if it is not an integer-like value
168
 */
169
public function integer($input) {
170
    if (is_numeric($input)) {
171
        return (int) $input;
0 ignored issues
show
Coding Style introduced by
A cast statement should not be followed as per the coding-style.
Loading history...
172
    }
173
    return FALSE;
174
}
175
176
/**
177
 * Is this a string representing a potentially more than 64-Bit length integer?
178
 * 
179
 * @param string $input
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
180
 * @return boolean|string returns the input, or FALSE if it is not an integer-like string
181
 */
182
public function hugeInteger($input) {
183
    if (is_numeric($input)) {
184
        return $input;
185
    }
186
    return FALSE;
187
}
188
189
/**
190
 * Checks if the input is the hex representation of a Consortium OI (i.e. three
191
 * or five bytes)
192
 * 
193
 * @param mixed $input
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
194
 * @return boolean|string returns the input, or FALSE on validation failure
195
 */
196
public function consortiumOI($input) {
197
    $shallow = $this->string($input);
198
    if (strlen($shallow) != 6 && strlen($shallow) != 10) {
199
        return FALSE;
200
    }
201
    if (!preg_match("/^[a-fA-F0-9]+$/", $shallow)) {
202
        return FALSE;
203
    }
204
    return $shallow;
205
}
206
207
/**
208
 * Is the input an NAI realm? Throws HTML error and returns FALSE if not.
209
 * 
210
 * @param mixed $input the input to check
211
 * @return boolean|string returns the realm, or FALSE if it was malformed
212
 */
213
public function realm($input) {
214
    if (strlen($input) == 0) {
215
        echo $this->inputValidationError(_("Realm is empty!"));
216
        return FALSE;
217
    }
218
219
    // basic string checks
220
    $check = $this->string($input);
221
    // list of things to check, and the error they produce
222
    $pregCheck = [
223
        "/@/" => _("Realm contains an @ sign!"),
224
        "/^\./" => _("Realm begins with a . (dot)!"),
225
        "/\.$/" => _("Realm ends with a . (dot)!"),
226
        "/ /" => _("Realm contains spaces!"),
227
    ];
228
229
    // bark on invalid constructs
230
    foreach ($pregCheck as $search => $error) {
231
        if (preg_match($search, $check) == 1) {
232
            echo $this->inputValidationError($error);
233
            return FALSE;
234
        }
235
    }
236
237
    if (preg_match("/\./", $check) == 0) {
238
        echo $this->inputValidationError(_("Realm does not contain at least one . (dot)!"));
239
        return FALSE;
240
    }
241
242
    // none of the special HTML entities should be here. In case someone wants
243
    // to mount a CSS attack by providing something that matches the realm constructs
244
    // below but has interesting stuff between, mangle the input so that these
245
    // characters do not do any harm.
246
    return htmlentities($check, ENT_QUOTES);
247
}
248
249
/**
250
 * could this be a valid username? 
251
 * 
252
 * Only checks correct form, not if the user actually exists in the system.
253
 * 
254
 * @param mixed $input
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
255
 * @return string echoes back the input string, or throws an Exception if bogus
256
 * @throws Exception
257
 */
258
public function User($input) {
0 ignored issues
show
Coding Style introduced by
Method name "InputValidation::User" is not in camel caps format
Loading history...
259
    $retvalStep0 = iconv("UTF-8", "UTF-8//TRANSLIT", $input);
260
    if ($retvalStep0 === FALSE) {
261
        throw new Exception("iconv failure for string sanitisation. With TRANSLIT, this should never happen!");
262
    }
263
    $retvalStep1 = trim($retvalStep0);
264
265
    $retval = preg_replace('/(\0|\r|\x0b|\t|\n)/', '', $retvalStep1);
266
    if ($retval != "" && !ctype_print($retval)) {
267
        throw new Exception($this->inputValidationError("The user identifier is not an ASCII string!"));
268
    }
269
270
    return $retval;
271
}
272
273
/**
274
 * could this be a valid token? 
275
 * 
276
 * Only checks correct form, not if the token actually exists in the system.
277
 * @param mixed $input
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
278
 * @return string echoes back the input string, or throws an Exception if bogus
279
 * @throws Exception
280
 */
281
public function token($input) {
282
    $retval = $input;
283
    if ($input != "" && preg_match('/[^0-9a-fA-F]/', $input) != 0) {
284
        throw new Exception($this->inputValidationError("Token is not a hexadecimal string!"));
285
    }
286
    return $retval;
287
}
288
289
/**
290
 * Is this be a valid coordinate vector on one axis?
291
 * 
292
 * @param mixed $input a numeric value in range of a geo coordinate [-180;180]
293
 * @return string returns back the input if all is good; throws an Exception if out of bounds or not numeric
294
 * @throws Exception
295
 */
296
public function coordinate($input) {
297
    $oldlocale = setlocale(LC_NUMERIC, 0);
298
    setlocale(LC_NUMERIC, "en_GB");
299
    if (!is_numeric($input)) {
300
        throw new Exception($this->inputValidationError("Coordinate is not a numeric value!"));
301
    }
302
    setlocale(LC_NUMERIC, $oldlocale);
303
    // lat and lon are always in the range of [-180;+180]
304
    if ($input < -180 || $input > 180) {
305
        throw new Exception($this->inputValidationError("Coordinate is out of bounds. Which planet are you from?"));
306
    }
307
    return $input;
308
}
309
310
/**
311
 * Is this a valid coordinate pair in JSON encoded representation?
312
 * 
313
 * @param mixed $input the string to be checked: is this a serialised array with lat/lon keys in a valid number range?
314
 * @return string returns $input if checks have passed; throws an Exception if something's wrong
315
 * @throws Exception
316
 */
317
public function coordJsonEncoded($input) {
318
    $tentative = json_decode($input, true);
319
    if (is_array($tentative)) {
320
        if (isset($tentative['lon']) && isset($tentative['lat']) && $this->coordinate($tentative['lon']) && $this->coordinate($tentative['lat'])) {
321
            return $input;
322
        }
323
    }
324
    throw new Exception($this->inputValidationError(_("Wrong coordinate encoding (2.0 uses JSON, not serialize)!")));
325
}
326
327
/**
328
 * This checks the state of a HTML GET/POST "boolean".
329
 * 
330
 * If not checked, no value is submitted at all; if checked, has the word "on". 
331
 * Anything else is a big error.
332
 * 
333
 * @param mixed $input the string to test
334
 * @return boolean TRUE if the input was "on". It is not possible in HTML to signal "off"
335
 * @throws Exception
336
 */
337
public function boolean($input) {
338
    if ($input != "on") {
339
        throw new Exception($this->inputValidationError("Unknown state of boolean option!"));
340
    }
341
    return TRUE;
342
}
343
344
const TABLEMAPPING = [
345
    "IdP" => "institution_option",
346
    "Profile" => "profile_option",
347
    "FED" => "federation_option",
348
];
349
350
/**
351
 * Is this a valid database reference? Has the form <tablename>-<rowID> and there
352
 * needs to be actual data at that place
353
 * 
354
 * @param mixed $input the reference to check
355
 * @return boolean|array the reference split up into "table" and "rowindex", or FALSE
356
 */
357
public function databaseReference($input) {
358
    $pregMatches = [];
359
    if (preg_match("/^ROWID-(IdP|Profile|FED)-([0-9]+)$/", $input, $pregMatches) === FALSE) {
360
        return FALSE;
361
    }
362
    return ["table" => self::TABLEMAPPING[$pregMatches[1]], "rowindex" => $pregMatches[2]];
363
}
364
365
/**
366
 * is this a valid hostname?
367
 * 
368
 * @param mixed $input
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
369
 * @return boolean|string echoes the hostname, or FALSE if bogus
370
 */
371
public function hostname($input) {
372
    // is it a valid IP address (IPv4 or IPv6), or a hostname?
373
    if (filter_var($input, FILTER_VALIDATE_IP) || $this->email("stefan@" . $input) !== FALSE) {
374
        // if it's a verified IP address or hostname then it does not contain
375
        // rubbish of course. But just to be sure, run htmlspecialchars around it
376
        return htmlspecialchars($input, ENT_QUOTES);
377
    }
378
    return FALSE;
379
}
380
381
/**
382
 * is this a valid email address?
383
 * 
384
 * @param mixed $input
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
385
 * @return boolean|string echoes the mail address, or FALSE if bogus
386
 */
387
public function email($input) {
388
389
    if (filter_var($this->string($input), FILTER_VALIDATE_EMAIL)) {
390
        return $input;
391
    }
392
    // if we get here, it's bogus
393
    return FALSE;
394
}
395
396
public function sms($input) {
0 ignored issues
show
Coding Style introduced by
Missing function doc comment
Loading history...
397
    $number = str_replace(' ', '', str_replace(".", "", str_replace("+", "", $input)));
398
    if (!is_numeric($number)) {
399
        return FALSE;
400
    }
401
    return $number;
402
}
403
404
/**
405
 * Is this is a language we support? If not, sanitise to our configured default language.
406
 * 
407
 * @param mixed $input the candidate language identifier
408
 * @return string
409
 */
410
public function supportedLanguage($input) {
411
    if (!array_key_exists($input, CONFIG['LANGUAGES'])) {
412
        return CONFIG['APPEARANCE']['defaultlocale'];
413
    }
414
    // otherwise, use the inversion trick to convince Scrutinizer that this is
415
    // a vetted value
416
    $retval = array_search(CONFIG['LANGUAGES'][$input], CONFIG['LANGUAGES']);
417
    if ($retval === FALSE) {
418
        throw new Exception("Impossible: the value we are searching for does exist, because we reference it directly.");
419
    }
420
    return $retval;
421
}
422
423
/**
424
 * Makes sure we are not receiving a bogus option name. The called function throws
425
 * an assertion if the name is not known.
426
 * 
427
 * @param mixed $input
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
428
 * @return string
429
 */
430
public function optionName($input) {
431
    $object = \core\Options::instance();
432
    return $object->assertValidOptionName($input);
433
}
434
435
/**
436
 * Checks to see if the input is a valid image of sorts
437
 * 
438
 * @param mixed $binary blob that may or may not be a parseable image
439
 * @return boolean
440
 */
441
public function image($binary) {
442
    $image = new \Imagick();
443
    try {
444
        $image->readImageBlob($binary);
445
    } catch (\ImagickException $exception) {
446
        echo "Error" . $exception->getMessage();
447
        return FALSE;
448
    }
449
    // image survived the sanity check
450
    return TRUE;
451
}
452
453
}
454