1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* ***************************************************************************** |
5
|
|
|
* Contributions to this work were made on behalf of the GÉANT project, a |
6
|
|
|
* project that has received funding from the European Union’s Framework |
7
|
|
|
* Programme 7 under Grant Agreements No. 238875 (GN3) and No. 605243 (GN3plus), |
8
|
|
|
* Horizon 2020 research and innovation programme under Grant Agreements No. |
9
|
|
|
* 691567 (GN4-1) and No. 731122 (GN4-2). |
10
|
|
|
* On behalf of the aforementioned projects, GEANT Association is the sole owner |
11
|
|
|
* of the copyright in all material which was developed by a member of the GÉANT |
12
|
|
|
* project. GÉANT Vereniging (Association) is registered with the Chamber of |
13
|
|
|
* Commerce in Amsterdam with registration number 40535155 and operates in the |
14
|
|
|
* UK as a branch of GÉANT Vereniging. |
15
|
|
|
* |
16
|
|
|
* Registered office: Hoekenrode 3, 1102BR Amsterdam, The Netherlands. |
17
|
|
|
* UK branch address: City House, 126-130 Hills Road, Cambridge CB2 1PQ, UK |
18
|
|
|
* |
19
|
|
|
* License: see the web/copyright.inc.php file in the file structure or |
20
|
|
|
* <base_url>/copyright.php after deploying the software |
21
|
|
|
*/ |
22
|
|
|
|
23
|
|
|
namespace web\lib\admin; |
24
|
|
|
|
25
|
|
|
use Exception; |
26
|
|
|
|
27
|
|
|
?> |
28
|
|
|
<?php |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* This class parses HTML field input from POST and FILES and extracts valid and authorised options to be set. |
32
|
|
|
* |
33
|
|
|
* @author Stefan Winter <[email protected]> |
34
|
|
|
*/ |
35
|
|
|
class OptionParser extends \core\common\Entity { |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* an instance of the InputValidation class which we use heavily for syntax checks. |
39
|
|
|
* |
40
|
|
|
* @var \web\lib\common\InputValidation |
41
|
|
|
*/ |
42
|
|
|
private $validator; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* an instance of the UIElements() class to draw some UI widgets from. |
46
|
|
|
* |
47
|
|
|
* @var UIElements |
48
|
|
|
*/ |
49
|
|
|
private $uiElements; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* a handle for the Options singleton |
53
|
|
|
* |
54
|
|
|
* @var \core\Options |
55
|
|
|
*/ |
56
|
|
|
private $optioninfoObject; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* initialises the various handles. |
60
|
|
|
*/ |
61
|
|
|
public function __construct() { |
62
|
|
|
$this->validator = new \web\lib\common\InputValidation(); |
63
|
|
|
$this->uiElements = new UIElements(); |
64
|
|
|
$this->optioninfoObject = \core\Options::instance(); |
65
|
|
|
$this->loggerInstance = new \core\common\Logging(); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* Verifies whether an incoming upload was actually valid data |
70
|
|
|
* |
71
|
|
|
* @param string $optiontype for which option was the data uploaded |
72
|
|
|
* @param string $incomingBinary the uploaded data |
73
|
|
|
* @return array ['result'=>boolean whether the data was valid, 'details'=>string description of the problem] |
74
|
|
|
*/ |
75
|
|
|
private function checkUploadSanity(string $optiontype, string $incomingBinary) { |
76
|
|
|
switch ($optiontype) { |
77
|
|
|
case "general:logo_file": |
78
|
|
|
case "fed:logo_file": |
79
|
|
|
case "internal:logo_from_url": |
80
|
|
|
$result = $this->validator->image($incomingBinary); |
81
|
|
|
// we check logo_file with ImageMagick |
82
|
|
|
if ($result) { |
83
|
|
|
return ['result'=>TRUE, 'details'=>'']; |
84
|
|
|
} |
85
|
|
|
return ['result'=>FALSE, 'details'=>_('unsupported image type')]; |
86
|
|
|
case "eap:ca_file": |
87
|
|
|
// fall-through intended: both CA types are treated the same |
88
|
|
|
case "fed:minted_ca_file": |
89
|
|
|
// echo "Checking $optiontype with file $filename"; |
90
|
|
|
$cert = (new \core\common\X509)->processCertificate($incomingBinary); |
91
|
|
|
if ($cert !== FALSE) { // could also be FALSE if it was incorrect incoming data |
92
|
|
|
$fail = false; |
93
|
|
|
if ($cert['full_details']['type'] == 'server') { |
94
|
|
|
$reason = _("%s - server certificate (<a href='%s'>more info</a>)"); |
95
|
|
|
$fail = true; |
96
|
|
|
} elseif($cert['basicconstraints_set'] === 0) { |
97
|
|
|
$reason = _("%s - missing required CA extensions (<a href='%s'>more info</a>)"); |
98
|
|
|
$fail = true; |
99
|
|
|
} |
100
|
|
|
if ($fail) { |
101
|
|
|
if (\config\ConfAssistant::CERT_GUIDELINES === '') { |
102
|
|
|
$ret_val = sprintf(preg_replace('/\(<a.*>\)/', '', $reason), $cert['full_details']['subject']['CN']); |
|
|
|
|
103
|
|
|
} else { |
104
|
|
|
$ret_val = sprintf($reason, $cert['full_details']['subject']['CN'], \config\ConfAssistant::CERT_GUIDELINES); |
105
|
|
|
} |
106
|
|
|
return ['result'=>FALSE, 'details'=>$ret_val]; |
107
|
|
|
} |
108
|
|
|
return ['result'=>TRUE, 'details'=>'']; |
109
|
|
|
} |
110
|
|
|
// the certificate seems broken |
111
|
|
|
return ['result'=>FALSE, 'details'=>'']; |
112
|
|
|
case "support:info_file": |
113
|
|
|
$info = new \finfo(); |
114
|
|
|
$filetype = $info->buffer($incomingBinary, FILEINFO_MIME_TYPE); |
115
|
|
|
|
116
|
|
|
// we only take plain text files in UTF-8! |
117
|
|
|
if ($filetype == "text/plain" && iconv("UTF-8", "UTF-8", $incomingBinary) !== FALSE) { |
118
|
|
|
return ['result'=>TRUE, '']; |
119
|
|
|
} |
120
|
|
|
return ['result'=>FALSE, 'details'=>_("incorrect file type - must be UTF8 text")]; |
121
|
|
|
case "media:openroaming": // and any other enum_* data type actually |
122
|
|
|
$optionClass = \core\Options::instance(); |
123
|
|
|
$optionProps = $optionClass->optionType($optiontype); |
124
|
|
|
$allowedValues = explode(',', substr($optionProps["flags"], 7)); |
125
|
|
|
if (in_array($incomingBinary,$allowedValues)) { |
126
|
|
|
return ['result'=>TRUE, 'details'=>'']; |
127
|
|
|
} |
128
|
|
|
return ['result'=>FALSE, 'details'=>'']; |
129
|
|
|
default: |
130
|
|
|
return ['result'=>FALSE, 'details'=>'']; |
131
|
|
|
} |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* Known-good options are sometimes converted, this function takes care of that. |
136
|
|
|
* |
137
|
|
|
* Cases in point: |
138
|
|
|
* - CA import by URL reference: fetch cert from URL and store it as CA file instead |
139
|
|
|
* - Logo import by URL reference: fetch logo from URL and store it as logo file instead |
140
|
|
|
* - CA file: mangle the content so that *only* the valid content remains (raw input may contain line breaks or spaces which are valid, but some supplicants choke upon) |
141
|
|
|
* |
142
|
|
|
* @param array $options the list of options we got |
143
|
|
|
* @param array $good by-reference: the future list of actually imported options |
144
|
|
|
* @param array $bad by-reference: the future list of submitted but rejected options |
145
|
|
|
* @return array the options, post-processed |
146
|
|
|
*/ |
147
|
|
|
private function postProcessValidAttributes(array $options, array &$good, array &$bad) { |
148
|
|
|
foreach ($options as $index => $iterateOption) { |
149
|
|
|
foreach ($iterateOption as $name => $optionPayload) { |
150
|
|
|
switch ($name) { |
151
|
|
|
case "eap:ca_url": // eap:ca_url becomes eap:ca_file by downloading the file |
152
|
|
|
$finalOptionname = "eap:ca_file"; |
153
|
|
|
// intentional fall-through, treatment identical to logo_url |
154
|
|
|
case "general:logo_url": // logo URLs become logo files by downloading the file |
155
|
|
|
$finalOptionname = $finalOptionname ?? "general:logo_file"; |
156
|
|
|
if (empty($optionPayload['content'])) { |
157
|
|
|
break; |
158
|
|
|
} |
159
|
|
|
$bindata = \core\common\OutsideComm::downloadFile($optionPayload['content']); |
160
|
|
|
unset($options[$index]); |
161
|
|
|
if ($bindata === FALSE) { |
162
|
|
|
$bad[] = ['type'=>$name, 'details'=>_("missing content")]; |
163
|
|
|
break; |
164
|
|
|
} |
165
|
|
|
if ($this->checkUploadSanity($finalOptionname, $bindata)['result']) { |
166
|
|
|
$good[] = $name; |
167
|
|
|
$options[] = [$finalOptionname => ['lang' => NULL, 'content' => base64_encode($bindata)]]; |
168
|
|
|
} else { |
169
|
|
|
$bad[] = ['type'=>$name, 'details'=>'']; |
170
|
|
|
} |
171
|
|
|
break; |
172
|
|
|
case "eap:ca_file": |
173
|
|
|
case "fed:minted_ca_file": |
174
|
|
|
// CA files get split (PEM files can contain more than one CA cert) |
175
|
|
|
// the data being processed here is always "good": |
176
|
|
|
// if it was eap:ca_file initially then its sanity was checked in step 1; |
177
|
|
|
// if it was eap:ca_url then it was checked after we downloaded it |
178
|
|
|
if (empty($optionPayload['content'])) { |
179
|
|
|
break; |
180
|
|
|
} |
181
|
|
|
if (preg_match('/^ROWID-/', $optionPayload['content'])) { |
182
|
|
|
// accounted for, already in DB |
183
|
|
|
$good[] = $name; |
184
|
|
|
break; |
185
|
|
|
} |
186
|
|
|
$content = base64_decode($optionPayload['content']); |
187
|
|
|
unset($options[$index]); |
188
|
|
|
$x509 = new \core\common\X509(); |
189
|
|
|
$cAFiles = $x509->splitCertificate($content); |
190
|
|
|
foreach ($cAFiles as $cAFile) { |
191
|
|
|
$options[] = [$name => ['lang' => NULL, 'content' => base64_encode($x509->pem2der($cAFile))]]; |
192
|
|
|
} |
193
|
|
|
$good[] = $name; |
194
|
|
|
break; |
195
|
|
|
default: |
196
|
|
|
$good[] = $name; // all other options were checked and are sane in step 1 already |
197
|
|
|
break; |
198
|
|
|
} |
199
|
|
|
} |
200
|
|
|
} |
201
|
|
|
return $options; |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* extracts a coordinate pair from _POST (if any) and returns it in our |
206
|
|
|
* standard attribute notation |
207
|
|
|
* |
208
|
|
|
* @param array $postArray data as sent by POST |
209
|
|
|
* @param array $good options which have been successfully parsed |
210
|
|
|
* @return array |
211
|
|
|
*/ |
212
|
|
|
private function postProcessCoordinates(array $postArray, array &$good) { |
213
|
|
|
if (!empty($postArray['geo_long']) && !empty($postArray['geo_lat'])) { |
214
|
|
|
|
215
|
|
|
$lat = $this->validator->coordinate($postArray['geo_lat']); |
216
|
|
|
$lon = $this->validator->coordinate($postArray['geo_long']); |
217
|
|
|
$good[] = ("general:geo_coordinates"); |
218
|
|
|
return [0 => ["general:geo_coordinates" => ['lang' => NULL, 'content' => json_encode(["lon" => $lon, "lat" => $lat])]]]; |
219
|
|
|
} |
220
|
|
|
return []; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
/** |
224
|
|
|
* creates HTML code for a user-readable summary of the imports |
225
|
|
|
* @param array $good list of actually imported options |
226
|
|
|
* @param array $bad list of submitted but rejected options |
227
|
|
|
* @param array $mlAttribsWithC list of language-variant options |
228
|
|
|
* @return string HTML code |
229
|
|
|
*/ |
230
|
|
|
private function displaySummaryInUI(array $good, array $bad, array $mlAttribsWithC) { |
231
|
|
|
\core\common\Entity::intoThePotatoes(); |
232
|
|
|
$retval = ""; |
233
|
|
|
// don't do your own table - only the <tr>s here |
234
|
|
|
// list all attributes that were set correctly |
235
|
|
|
$listGood = array_count_values($good); |
236
|
|
|
$uiElements = new UIElements(); |
237
|
|
|
foreach ($listGood as $name => $count) { |
238
|
|
|
/// number of times attribute is present, and its name |
239
|
|
|
/// Example: "5x Support E-Mail" |
240
|
|
|
$retval .= $this->uiElements->boxOkay(sprintf(_("%dx %s"), $count, $uiElements->displayName($name))); |
241
|
|
|
} |
242
|
|
|
// list all attributes that had errors |
243
|
|
|
|
244
|
|
|
foreach ($bad as $badInstance) { |
245
|
|
|
$details = $badInstance['details'] === '' ? '' : ' - '.$badInstance['details']; |
246
|
|
|
$retval .= $this->uiElements->boxError(sprintf(_("%s"), $uiElements->displayName($badInstance['type']).$details)); |
247
|
|
|
} |
248
|
|
|
// list multilang without default |
249
|
|
|
foreach ($mlAttribsWithC as $attribName => $isitsetornot) { |
250
|
|
|
if ($isitsetornot == FALSE) { |
251
|
|
|
$retval .= $this->uiElements->boxWarning(sprintf(_("You did not set a 'default language' value for %s. This means we can only display this string for installers which are <strong>exactly</strong> in the language you configured. For the sake of all other languages, you may want to edit the profile again and populate the 'default/other' language field."), $uiElements->displayName($attribName))); |
252
|
|
|
} |
253
|
|
|
} |
254
|
|
|
\core\common\Entity::outOfThePotatoes(); |
255
|
|
|
return $retval; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
/** |
259
|
|
|
* Incoming data is in $_POST and possibly in $_FILES. Collate values into |
260
|
|
|
* one array according to our name and numbering scheme. |
261
|
|
|
* |
262
|
|
|
* @param array $postArray _POST |
263
|
|
|
* @param array $filesArray _FILES |
264
|
|
|
* @return array |
265
|
|
|
*/ |
266
|
|
|
private function collateOptionArrays(array $postArray, array $filesArray) { |
267
|
|
|
|
268
|
|
|
$optionarray = $postArray['option'] ?? []; |
269
|
|
|
$valuearray = $postArray['value'] ?? []; |
270
|
|
|
$filesarray = $filesArray['value']['tmp_name'] ?? []; |
271
|
|
|
|
272
|
|
|
$iterator = array_merge($optionarray, $valuearray, $filesarray); |
273
|
|
|
|
274
|
|
|
return $iterator; |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
/** |
278
|
|
|
* The very end of the processing: clean input data gets sent to the database |
279
|
|
|
* for storage |
280
|
|
|
* |
281
|
|
|
* @param mixed $object for which object are the options |
282
|
|
|
* @param array $options the options to store |
283
|
|
|
* @param array $pendingattributes list of attributes which are already stored but may need to be deleted |
284
|
|
|
* @param string $device when the $object is Profile, this indicates device-specific attributes |
285
|
|
|
* @param int $eaptype when the $object is Profile, this indicates eap-specific attributes |
286
|
|
|
* @return array list of attributes which were previously stored but are to be deleted now |
287
|
|
|
* @throws Exception |
288
|
|
|
*/ |
289
|
|
|
private function sendOptionsToDatabase($object, array $options, array $pendingattributes, string $device = NULL, int $eaptype = NULL) { |
290
|
|
|
$retval = []; |
291
|
|
|
foreach ($options as $iterateOption) { |
292
|
|
|
foreach ($iterateOption as $name => $optionPayload) { |
293
|
|
|
$optiontype = $this->optioninfoObject->optionType($name); |
294
|
|
|
// some attributes are in the DB and were only called by reference |
295
|
|
|
// keep those which are still referenced, throw the rest away |
296
|
|
|
if ($optiontype["type"] == \core\Options::TYPECODE_FILE && preg_match("/^ROWID-.*-([0-9]+)/", $optionPayload['content'], $retval)) { |
297
|
|
|
unset($pendingattributes[$retval[1]]); |
298
|
|
|
continue; |
299
|
|
|
} |
300
|
|
|
switch (get_class($object)) { |
301
|
|
|
case 'core\\ProfileRADIUS': |
302
|
|
|
if ($device !== NULL) { |
303
|
|
|
$object->addAttributeDeviceSpecific($name, $optionPayload['lang'], $optionPayload['content'], $device); |
304
|
|
|
} elseif ($eaptype !== NULL) { |
305
|
|
|
$object->addAttributeEAPSpecific($name, $optionPayload['lang'], $optionPayload['content'], $eaptype); |
306
|
|
|
} else { |
307
|
|
|
$object->addAttribute($name, $optionPayload['lang'], $optionPayload['content']); |
308
|
|
|
} |
309
|
|
|
break; |
310
|
|
|
case 'core\\IdP': |
311
|
|
|
case 'core\\User': |
312
|
|
|
case 'core\\Federation': |
313
|
|
|
case 'core\\DeploymentManaged': |
314
|
|
|
$object->addAttribute($name, $optionPayload['lang'], $optionPayload['content']); |
315
|
|
|
break; |
316
|
|
|
default: |
317
|
|
|
throw new Exception("This type of object can't have options that are parsed by this file!"); |
318
|
|
|
} |
319
|
|
|
} |
320
|
|
|
} |
321
|
|
|
return $pendingattributes; |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
/** many of the content check cases in sanitiseInputs condense due to |
325
|
|
|
* identical treatment except which validator function to call and |
326
|
|
|
* where in POST the content is. |
327
|
|
|
* |
328
|
|
|
* This is a map between datatype and validation function. |
329
|
|
|
* |
330
|
|
|
* @var array |
331
|
|
|
*/ |
332
|
|
|
private const VALIDATOR_FUNCTIONS = [ |
333
|
|
|
\core\Options::TYPECODE_TEXT => ["function" => "string", "field" => \core\Options::TYPECODE_TEXT, "extraarg" => [TRUE]], |
334
|
|
|
\core\Options::TYPECODE_COORDINATES => ["function" => "coordJsonEncoded", "field" => \core\Options::TYPECODE_TEXT, "extraarg" => []], |
335
|
|
|
\core\Options::TYPECODE_BOOLEAN => ["function" => "boolean", "field" => \core\Options::TYPECODE_BOOLEAN, "extraarg" => []], |
336
|
|
|
\core\Options::TYPECODE_INTEGER => ["function" => "integer", "field" => \core\Options::TYPECODE_INTEGER, "extraarg" => []], |
337
|
|
|
]; |
338
|
|
|
|
339
|
|
|
/** |
340
|
|
|
* filters the input to find syntactically correctly submitted attributes |
341
|
|
|
* |
342
|
|
|
* @param array $listOfEntries list of POST and FILES entries |
343
|
|
|
* @return array sanitised list of options |
344
|
|
|
* @throws Exception |
345
|
|
|
*/ |
346
|
|
|
private function sanitiseInputs(array $listOfEntries) { |
347
|
|
|
$retval = []; |
348
|
|
|
$bad = []; |
349
|
|
|
$multilangAttrsWithC = []; |
350
|
|
|
foreach ($listOfEntries as $objId => $objValueRaw) { |
351
|
|
|
// pick those without dash - they indicate a new value |
352
|
|
|
if (preg_match('/^S[0123456789]*$/', $objId) != 1) { // no match |
353
|
|
|
continue; |
354
|
|
|
} |
355
|
|
|
$objValue = $this->validator->optionName(preg_replace('/#.*$/', '', $objValueRaw)); |
356
|
|
|
$optioninfo = $this->optioninfoObject->optionType($objValue); |
357
|
|
|
$languageFlag = NULL; |
358
|
|
|
if ($optioninfo["flag"] == "ML") { |
359
|
|
|
if (!isset($listOfEntries["$objId-lang"])) { |
360
|
|
|
$bad[] = ['type'=>$objValue, 'details'=>'']; |
361
|
|
|
continue; |
362
|
|
|
} |
363
|
|
|
$languageFlag = $this->validator->string($listOfEntries["$objId-lang"]); |
364
|
|
|
$this->determineLanguages($objValue, $listOfEntries["$objId-lang"], $multilangAttrsWithC); |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
switch ($optioninfo["type"]) { |
368
|
|
|
case \core\Options::TYPECODE_TEXT: |
369
|
|
|
case \core\Options::TYPECODE_COORDINATES: |
370
|
|
|
case \core\Options::TYPECODE_INTEGER: |
371
|
|
|
$varName = $listOfEntries["$objId-" . self::VALIDATOR_FUNCTIONS[$optioninfo['type']]['field']]; |
372
|
|
|
if (!empty($varName)) { |
373
|
|
|
$content = call_user_func_array([$this->validator, self::VALIDATOR_FUNCTIONS[$optioninfo['type']]['function']], array_merge([$varName], self::VALIDATOR_FUNCTIONS[$optioninfo['type']]['extraarg'])); |
374
|
|
|
break; |
375
|
|
|
} |
376
|
|
|
continue 2; |
377
|
|
|
case \core\Options::TYPECODE_BOOLEAN: |
378
|
|
|
$varName = $listOfEntries["$objId-" . \core\Options::TYPECODE_BOOLEAN]; |
379
|
|
|
if (!empty($varName)) { |
380
|
|
|
$contentValid = $this->validator->boolean($varName); |
381
|
|
|
if ($contentValid) { |
382
|
|
|
$content = "on"; |
383
|
|
|
} else { |
384
|
|
|
$bad[] = ['type'=>$objValue, 'details'=>'']; |
385
|
|
|
continue 2; |
386
|
|
|
} |
387
|
|
|
break; |
388
|
|
|
} |
389
|
|
|
continue 2; |
390
|
|
|
case \core\Options::TYPECODE_STRING: |
391
|
|
|
$previsionalContent = $listOfEntries["$objId-" . \core\Options::TYPECODE_STRING]; |
392
|
|
|
if (!empty(trim($previsionalContent))) { |
393
|
|
|
$content = $this->furtherStringChecks($objValue, $previsionalContent, $bad); |
394
|
|
|
if ($content === FALSE) { |
395
|
|
|
continue 2; |
396
|
|
|
} |
397
|
|
|
break; |
398
|
|
|
} |
399
|
|
|
continue 2; |
400
|
|
|
|
401
|
|
|
case \core\Options::TYPECODE_ENUM_OPENROAMING: |
402
|
|
|
$previsionalContent = $listOfEntries["$objId-" . \core\Options::TYPECODE_ENUM_OPENROAMING]; |
403
|
|
|
if (!empty($previsionalContent)) { |
404
|
|
|
$content = $this->furtherStringChecks($objValue, $previsionalContent, $bad); |
405
|
|
|
if ($content === FALSE) { |
406
|
|
|
continue 2; |
407
|
|
|
} |
408
|
|
|
break; |
409
|
|
|
} |
410
|
|
|
continue 2; |
411
|
|
|
case \core\Options::TYPECODE_FILE: |
412
|
|
|
// this is either actually an uploaded file, or a reference to a DB entry of a previously uploaded file |
413
|
|
|
$reference = $listOfEntries["$objId-" . \core\Options::TYPECODE_STRING]; |
414
|
|
|
if (!empty($reference)) { // was already in, by ROWID reference, extract |
415
|
|
|
// ROWID means it's a multi-line string (simple strings are inline in the form; so allow whitespace) |
416
|
|
|
$content = $this->validator->string(urldecode($reference), TRUE); |
417
|
|
|
break; |
418
|
|
|
} |
419
|
|
|
$fileName = $listOfEntries["$objId-" . \core\Options::TYPECODE_FILE] ?? ""; |
420
|
|
|
if ($fileName != "") { // let's do the download |
421
|
|
|
$rawContent = \core\common\OutsideComm::downloadFile("file:///" . $fileName); |
422
|
|
|
$sanity = $this->checkUploadSanity($objValue, $rawContent); |
|
|
|
|
423
|
|
|
if ($rawContent === FALSE || !$sanity['result']) { |
424
|
|
|
$bad[] = ['type'=>$objValue, 'details'=>$sanity['details']]; |
425
|
|
|
continue 2; |
426
|
|
|
} |
427
|
|
|
$content = base64_encode($rawContent); |
428
|
|
|
break; |
429
|
|
|
} |
430
|
|
|
continue 2; |
431
|
|
|
default: |
432
|
|
|
throw new Exception("Internal Error: Unknown option type " . $objValue . "!"); |
433
|
|
|
} |
434
|
|
|
// lang can be NULL here, if it's not a multilang attribute, or a ROWID reference. Never mind that. |
435
|
|
|
$retval[] = ["$objValue" => ["lang" => $languageFlag, "content" => $content]]; |
436
|
|
|
} |
437
|
|
|
return [$retval, $multilangAttrsWithC, $bad]; |
438
|
|
|
} |
439
|
|
|
|
440
|
|
|
/** |
441
|
|
|
* find out which languages were submitted, and whether a default language was in the set |
442
|
|
|
* @param string $attribute the name of the attribute we are looking at |
443
|
|
|
* @param string $languageFlag which language flag was submitted |
444
|
|
|
* @param array $multilangAttrsWithC by-reference: add to this if we found a C language variant |
445
|
|
|
* @return void |
446
|
|
|
*/ |
447
|
|
|
private function determineLanguages($attribute, $languageFlag, &$multilangAttrsWithC) { |
448
|
|
|
if (!isset($multilangAttrsWithC[$attribute])) { // on first sight, initialise the attribute as "no C language set" |
449
|
|
|
$multilangAttrsWithC[$attribute] = FALSE; |
450
|
|
|
} |
451
|
|
|
if ($languageFlag == "") { // user forgot to select a language |
452
|
|
|
$languageFlag = "C"; |
453
|
|
|
} |
454
|
|
|
// did we get a C language? set corresponding value to TRUE |
455
|
|
|
if ($languageFlag == "C") { |
456
|
|
|
$multilangAttrsWithC[$attribute] = TRUE; |
457
|
|
|
} |
458
|
|
|
} |
459
|
|
|
|
460
|
|
|
/** |
461
|
|
|
* |
462
|
|
|
* @param string $attribute which attribute was sent? |
463
|
|
|
* @param string $previsionalContent which content was sent? |
464
|
|
|
* @param array $bad list of malformed attributes, by-reference |
465
|
|
|
* @return string|false FALSE if value is not in expected format, else the content itself |
466
|
|
|
*/ |
467
|
|
|
private function furtherStringChecks($attribute, $previsionalContent, &$bad) { |
468
|
|
|
$content = FALSE; |
469
|
|
|
switch ($attribute) { |
470
|
|
|
case "media:consortium_OI": |
471
|
|
|
$content = $this->validator->consortiumOI($previsionalContent); |
472
|
|
|
if ($content === FALSE) { |
473
|
|
|
$bad[] = ['type'=>$attribute, 'details'=>'']; |
474
|
|
|
return FALSE; |
475
|
|
|
} |
476
|
|
|
break; |
477
|
|
|
case "media:remove_SSID": |
478
|
|
|
$content = $this->validator->string($previsionalContent); |
479
|
|
|
if ($content == "eduroam") { |
480
|
|
|
$bad[] = ['type'=>$attribute, 'details'=>'']; |
481
|
|
|
return FALSE; |
482
|
|
|
} |
483
|
|
|
break; |
484
|
|
|
case "media:force_proxy": |
485
|
|
|
$content = $this->validator->string($previsionalContent); |
486
|
|
|
$serverAndPort = explode(':', strrev($content), 2); |
487
|
|
|
if (count($serverAndPort) != 2) { |
488
|
|
|
$bad[] = ['type'=>$attribute, 'details'=>'']; |
489
|
|
|
return FALSE; |
490
|
|
|
} |
491
|
|
|
$port = strrev($serverAndPort[0]); |
492
|
|
|
if (!is_numeric($port)) { |
493
|
|
|
$bad[] = ['type'=>$attribute, 'details'=>'']; |
494
|
|
|
return FALSE; |
495
|
|
|
} |
496
|
|
|
break; |
497
|
|
|
case "support:url": |
498
|
|
|
$content = $this->validator->string($previsionalContent); |
499
|
|
|
if (preg_match("/^http/", $content) != 1) { |
500
|
|
|
$bad[] = ['type'=>$attribute, 'details'=>'']; |
501
|
|
|
return FALSE; |
502
|
|
|
} |
503
|
|
|
break; |
504
|
|
|
case "support:email": |
505
|
|
|
$content = $this->validator->email($previsionalContent); |
506
|
|
|
if ($content === FALSE) { |
507
|
|
|
$bad[] = ['type'=>$attribute, 'details'=>'']; |
508
|
|
|
return FALSE; |
509
|
|
|
} |
510
|
|
|
break; |
511
|
|
|
case "managedsp:operatorname": |
512
|
|
|
$content = $previsionalContent; |
513
|
|
|
if (!preg_match("/^1.*\..*/", $content)) { |
514
|
|
|
$bad[] = ['type'=>$attribute, 'details'=>'']; |
515
|
|
|
return FALSE; |
516
|
|
|
} |
517
|
|
|
break; |
518
|
|
|
default: |
519
|
|
|
$content = $this->validator->string($previsionalContent); |
520
|
|
|
break; |
521
|
|
|
} |
522
|
|
|
return $content; |
523
|
|
|
} |
524
|
|
|
|
525
|
|
|
/** |
526
|
|
|
* The main function: takes all HTML field inputs, makes sense of them and stores valid data in the database |
527
|
|
|
* |
528
|
|
|
* @param mixed $object The object for which attributes were submitted |
529
|
|
|
* @param array $postArray incoming attribute names and values as submitted with $_POST |
530
|
|
|
* @param array $filesArray incoming attribute names and values as submitted with $_FILES |
531
|
|
|
* @param int $eaptype for eap-specific attributes (only used where $object is a ProfileRADIUS instance) |
532
|
|
|
* @param string $device for device-specific attributes (only used where $object is a ProfileRADIUS instance) |
533
|
|
|
* @return string text to be displayed in UI with the summary of attributes added |
534
|
|
|
* @throws Exception |
535
|
|
|
*/ |
536
|
|
|
public function processSubmittedFields($object, array $postArray, array $filesArray, int $eaptype = NULL, string $device = NULL) { |
537
|
|
|
$good = []; |
538
|
|
|
// Step 1: collate option names, option values and uploaded files (by |
539
|
|
|
// filename reference) into one array for later handling |
540
|
|
|
|
541
|
|
|
$iterator = $this->collateOptionArrays($postArray, $filesArray); |
542
|
|
|
|
543
|
|
|
// Step 2: sieve out malformed input |
544
|
|
|
// $multilangAttrsWithC is a helper array to keep track of multilang |
545
|
|
|
// options that were set in a specific language but are not |
546
|
|
|
// accompanied by a "default" language setting |
547
|
|
|
// if there are some without C by the end of processing, we need to warn |
548
|
|
|
// the admin that this attribute is "invisible" in certain languages |
549
|
|
|
// attrib_name -> boolean |
550
|
|
|
// $bad contains the attributes which failed input validation |
551
|
|
|
|
552
|
|
|
list($cleanData, $multilangAttrsWithC, $bad) = $this->sanitiseInputs($iterator); |
553
|
|
|
|
554
|
|
|
// Step 3: now we have clean input data. Some attributes need special care: |
555
|
|
|
// URL-based attributes need to be downloaded to get their actual content |
556
|
|
|
// CA files may need to be split (PEM can contain multiple CAs |
557
|
|
|
|
558
|
|
|
$optionsStep2 = $this->postProcessValidAttributes($cleanData, $good, $bad); |
559
|
|
|
|
560
|
|
|
// Step 4: coordinates do not follow the usual POST array as they are |
561
|
|
|
// two values forming one attribute; extract those two as an extra step |
562
|
|
|
|
563
|
|
|
$options = array_merge($optionsStep2, $this->postProcessCoordinates($postArray, $good)); |
564
|
|
|
|
565
|
|
|
// Step 5: push all the received options to the database. Keep mind of |
566
|
|
|
// the list of existing database entries that are to be deleted. |
567
|
|
|
// 5a: first deletion step: purge all old content except file-based attributes; |
568
|
|
|
// then take note of which file-based attributes are now stale |
569
|
|
|
if ($device === NULL && $eaptype === NULL) { |
570
|
|
|
$remaining = $object->beginflushAttributes(); |
571
|
|
|
$killlist = $this->sendOptionsToDatabase($object, $options, $remaining); |
572
|
|
|
} elseif ($device !== NULL) { |
573
|
|
|
$remaining = $object->beginFlushMethodLevelAttributes(0, $device); |
574
|
|
|
$killlist = $this->sendOptionsToDatabase($object, $options, $remaining, $device); |
575
|
|
|
} else { |
576
|
|
|
$remaining = $object->beginFlushMethodLevelAttributes($eaptype, ""); |
577
|
|
|
$killlist = $this->sendOptionsToDatabase($object, $options, $remaining, NULL, $eaptype); |
578
|
|
|
} |
579
|
|
|
// 5b: finally, kill the stale file-based attributes which are not wanted any more. |
580
|
|
|
$object->commitFlushAttributes($killlist); |
581
|
|
|
|
582
|
|
|
// finally: return HTML code that gives feedback about what we did. |
583
|
|
|
// In some cases, callers won't actually want to display it; so simply |
584
|
|
|
// do not echo the return value. Reasons not to do this is if we working |
585
|
|
|
// e.g. from inside an overlay |
586
|
|
|
return $this->displaySummaryInUI($good, $bad, $multilangAttrsWithC); |
587
|
|
|
} |
588
|
|
|
|
589
|
|
|
} |
590
|
|
|
|