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