|
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
|
|
|
|