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
|
|
|
/** |
13
|
|
|
* This is the collection of methods dedicated for the user GUI |
14
|
|
|
* @author Tomasz Wolniewicz <[email protected]> |
15
|
|
|
* @package UserAPI |
16
|
|
|
* |
17
|
|
|
* Parts of this code are based on simpleSAMLPhp discojuice module. |
18
|
|
|
* This product includes GeoLite data created by MaxMind, available from |
19
|
|
|
* http://www.maxmind.com |
20
|
|
|
*/ |
21
|
|
|
|
22
|
|
|
namespace core; |
23
|
|
|
|
24
|
|
|
use GeoIp2\Database\Reader; |
25
|
|
|
use \Exception; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* The basic methoods for the user GUI |
29
|
|
|
* @package UserAPI |
30
|
|
|
* |
31
|
|
|
*/ |
32
|
|
|
class UserAPI extends CAT { |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* nothing special to be done here. |
36
|
|
|
*/ |
37
|
|
|
public function __construct() { |
38
|
|
|
parent::__construct(); |
39
|
|
|
} |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* Prepare the device module environment and send back the link |
43
|
|
|
* This method creates a device module instance via the {@link DeviceFactory} call, |
44
|
|
|
* then sets up the device module environment for the specific profile by calling |
45
|
|
|
* {@link DeviceConfig::setup()} method and finally, called the devide writeInstaller meethod |
46
|
|
|
* passing the returned path name. |
47
|
|
|
* |
48
|
|
|
* @param string $device identifier as in {@link devices.php} |
49
|
|
|
* @param int $profileId profile identifier |
50
|
|
|
* |
51
|
|
|
* @return array |
52
|
|
|
* array with the following fields: |
53
|
|
|
* profile - the profile identifier; |
54
|
|
|
* device - the device identifier; |
55
|
|
|
* link - the path name of the resulting installer |
56
|
|
|
* mime - the mimetype of the installer |
57
|
|
|
*/ |
58
|
|
|
public function generateInstaller($device, $profileId, $generatedFor = "user", $token = NULL, $password = NULL) { |
59
|
|
|
$this->languageInstance->setTextDomain("devices"); |
60
|
|
|
$this->loggerInstance->debug(4, "installer:$device:$profileId\n"); |
61
|
|
|
$validator = new \web\lib\common\InputValidation(); |
62
|
|
|
$profile = $validator->Profile($profileId); |
63
|
|
|
// test if the profile is production-ready and if not if the authenticated user is an owner |
64
|
|
|
if ($this->verifyDownloadAccess($profile) === FALSE) { |
65
|
|
|
return; |
66
|
|
|
} |
67
|
|
|
$installerProperties = []; |
68
|
|
|
$installerProperties['profile'] = $profileId; |
69
|
|
|
$installerProperties['device'] = $device; |
70
|
|
|
$cache = $this->getCache($device, $profile); |
71
|
|
|
$this->installerPath = $cache['path']; |
72
|
|
|
if ($this->installerPath && $token == NULL && $password == NULL) { |
73
|
|
|
$this->loggerInstance->debug(4, "Using cached installer for: $device\n"); |
74
|
|
|
$installerProperties['link'] = "API.php?action=downloadInstaller&lang=" . $this->languageInstance->getLang() . "&profile=$profileId&device=$device&generatedfor=$generatedFor"; |
75
|
|
|
$installerProperties['mime'] = $cache['mime']; |
76
|
|
|
} else { |
77
|
|
|
$myInstaller = $this->generateNewInstaller($device, $profile, $generatedFor, $token, $password); |
78
|
|
|
if ($myInstaller['link'] !== 0) { |
79
|
|
|
$installerProperties['mime'] = $myInstaller['mime']; |
80
|
|
|
} |
81
|
|
|
$installerProperties['link'] = $myInstaller['link']; |
82
|
|
|
} |
83
|
|
|
$this->languageInstance->setTextDomain("web_user"); |
84
|
|
|
return($installerProperties); |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
private function verifyDownloadAccess($profile) { |
88
|
|
|
$attribs = $profile->getCollapsedAttributes(); |
89
|
|
|
if (!isset($attribs['profile:production']) || (isset($attribs['profile:production']) && $attribs['profile:production'][0] != "on")) { |
90
|
|
|
$this->loggerInstance->debug(4, "Attempt to download a non-production ready installer for profile: $profile->identifier\n"); |
91
|
|
|
$auth = new \web\lib\admin\Authentication(); |
92
|
|
|
if (!$auth->isAuthenticated()) { |
93
|
|
|
$this->loggerInstance->debug(2, "User NOT authenticated, rejecting request for a non-production installer\n"); |
94
|
|
|
header("HTTP/1.0 403 Not Authorized"); |
95
|
|
|
return(FALSE); |
96
|
|
|
} |
97
|
|
|
$userObject = new User($_SESSION['user']); |
98
|
|
|
if (!$userObject->isIdPOwner($profile->institution)) { |
99
|
|
|
$this->loggerInstance->debug(2, "User not an owner of a non-production profile - access forbidden\n"); |
100
|
|
|
header("HTTP/1.0 403 Not Authorized"); |
101
|
|
|
return(FALSE); |
102
|
|
|
} |
103
|
|
|
$this->loggerInstance->debug(4, "User is the owner - allowing access\n"); |
104
|
|
|
} |
105
|
|
|
return(TRUE); |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* This function tries to find a cached copy of an installer for a given |
110
|
|
|
* combination of Profile and device |
111
|
|
|
* @param string $device |
112
|
|
|
* @param AbstractProfile $profile |
113
|
|
|
* @return boolean|string the string with the path to the cached copy, or FALSE if no cached copy exists |
114
|
|
|
*/ |
115
|
|
|
private function getCache($device, $profile) { |
116
|
|
|
$deviceList = \devices\Devices::listDevices(); |
117
|
|
|
$deviceConfig = $deviceList[$device]; |
118
|
|
|
$noCache = (isset(\devices\Devices::$Options['no_cache']) && \devices\Devices::$Options['no_cache']) ? 1 : 0; |
119
|
|
|
if (isset($deviceConfig['options']['no_cache'])) { |
120
|
|
|
$noCache = $deviceConfig['options']['no_cache'] ? 1 : 0; |
121
|
|
|
} |
122
|
|
|
if ($noCache) { |
123
|
|
|
$this->loggerInstance->debug(5, "getCache: the no_cache option set for this device\n"); |
124
|
|
|
return(FALSE); |
125
|
|
|
} |
126
|
|
|
$this->loggerInstance->debug(5, "getCache: caching option set for this device\n"); |
127
|
|
|
$cache = $profile->testCache($device); |
128
|
|
|
$iPath = $cache['cache']; |
129
|
|
|
if ($iPath && is_file($iPath)) { |
130
|
|
|
return(['path' => $iPath, 'mime' => $cache['mime']]); |
131
|
|
|
} |
132
|
|
|
return(FALSE); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* Generates a new installer for the given combination of device and Profile |
137
|
|
|
* |
138
|
|
|
* @param string $device |
139
|
|
|
* @param AbstractProfile $profile |
140
|
|
|
* @return array info about the new installer (mime and link) |
141
|
|
|
*/ |
142
|
|
|
private function generateNewInstaller($device, $profile, $generatedFor, $token, $password) { |
143
|
|
|
$this->loggerInstance->debug(5, "generateNewInstaller() - Enter"); |
144
|
|
|
$factory = new DeviceFactory($device); |
145
|
|
|
$this->loggerInstance->debug(5, "generateNewInstaller() - created Device"); |
146
|
|
|
$dev = $factory->device; |
147
|
|
|
$out = []; |
148
|
|
|
if (isset($dev)) { |
149
|
|
|
$dev->setup($profile, $token, $password); |
150
|
|
|
$this->loggerInstance->debug(5, "generateNewInstaller() - Device setup done"); |
151
|
|
|
$installer = $dev->writeInstaller(); |
152
|
|
|
$this->loggerInstance->debug(5, "generateNewInstaller() - writeInstaller complete"); |
153
|
|
|
$iPath = $dev->FPATH . '/tmp/' . $installer; |
154
|
|
|
if ($iPath && is_file($iPath)) { |
155
|
|
|
if (isset($dev->options['mime'])) { |
156
|
|
|
$out['mime'] = $dev->options['mime']; |
157
|
|
|
} else { |
158
|
|
|
$info = new \finfo(); |
159
|
|
|
$out['mime'] = $info->file($iPath, FILEINFO_MIME_TYPE); |
160
|
|
|
} |
161
|
|
|
$this->installerPath = $dev->FPATH . '/' . $installer; |
162
|
|
|
rename($iPath, $this->installerPath); |
163
|
|
|
$integerEap = (new \core\common\EAP($dev->selectedEap))->getIntegerRep(); |
164
|
|
|
$profile->updateCache($device, $this->installerPath, $out['mime'], $integerEap); |
165
|
|
|
if (CONFIG['DEBUG_LEVEL'] < 4) { |
166
|
|
|
\core\common\Entity::rrmdir($dev->FPATH . '/tmp'); |
167
|
|
|
} |
168
|
|
|
$this->loggerInstance->debug(4, "Generated installer: " . $this->installerPath . ": for: $device, EAP:" . $integerEap . "\n"); |
169
|
|
|
$out['link'] = "API.php?action=downloadInstaller&lang=" . $this->languageInstance->getLang() . "&profile=" . $profile->identifier . "&device=$device&generatedfor=$generatedFor"; |
170
|
|
|
} else { |
171
|
|
|
$this->loggerInstance->debug(2, "Installer generation failed for: " . $profile->identifier . ":$device:" . $this->languageInstance->getLang() . "\n"); |
172
|
|
|
$out['link'] = 0; |
173
|
|
|
} |
174
|
|
|
} |
175
|
|
|
return($out); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* interface to Devices::listDevices() |
180
|
|
|
*/ |
181
|
|
|
public function listDevices($showHidden = 0) { |
182
|
|
|
$dev = \devices\Devices::listDevices(); |
183
|
|
|
$returnList = []; |
184
|
|
|
$count = 0; |
185
|
|
|
if ($showHidden !== 0 && $showHidden != 1) { |
186
|
|
|
throw new Exception("show_hidden is only be allowed to be 0 or 1, but it is $showHidden!"); |
187
|
|
|
} |
188
|
|
|
foreach ($dev as $device => $deviceProperties) { |
189
|
|
|
if (isset($deviceProperties['options']['hidden']) && $deviceProperties['options']['hidden'] && $showHidden == 0) { |
190
|
|
|
continue; |
191
|
|
|
} |
192
|
|
|
$count++; |
193
|
|
|
|
194
|
|
|
$deviceProperties['device'] = $device; |
195
|
|
|
|
196
|
|
|
$group = isset($deviceProperties['group']) ? $deviceProperties['group'] : 'other'; |
197
|
|
|
if (!isset($returnList[$group])) { |
198
|
|
|
$returnList[$group] = []; |
199
|
|
|
} |
200
|
|
|
$returnList[$group][$device] = $deviceProperties; |
201
|
|
|
} |
202
|
|
|
return $returnList; |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
/** |
206
|
|
|
* |
207
|
|
|
* @param string $device |
208
|
|
|
* @param int $profileId |
209
|
|
|
*/ |
210
|
|
|
public function deviceInfo($device, $profileId) { |
211
|
|
|
$this->languageInstance->setTextDomain("devices"); |
212
|
|
|
$validator = new \web\lib\common\InputValidation(); |
213
|
|
|
$out = 0; |
214
|
|
|
$profile = $validator->Profile($profileId); |
215
|
|
|
$factory = new DeviceFactory($device); |
216
|
|
|
$dev = $factory->device; |
217
|
|
|
if (isset($dev)) { |
218
|
|
|
$dev->setup($profile); |
219
|
|
|
$out = $dev->writeDeviceInfo(); |
220
|
|
|
} |
221
|
|
|
$this->languageInstance->setTextDomain("web_user"); |
222
|
|
|
echo $out; |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* Prepare the support data for a given profile |
227
|
|
|
* |
228
|
|
|
* @param int $profId profile identifier |
229
|
|
|
* @return array |
230
|
|
|
* array with the following fields: |
231
|
|
|
* - local_email |
232
|
|
|
* - local_phone |
233
|
|
|
* - local_url |
234
|
|
|
* - description |
235
|
|
|
* - devices - an array of device names and their statuses (for a given profile) |
236
|
|
|
*/ |
237
|
|
|
public function profileAttributes($profId) { |
238
|
|
|
$this->languageInstance->setTextDomain("devices"); |
239
|
|
|
$validator = new \web\lib\common\InputValidation(); |
240
|
|
|
$profile = $validator->Profile($profId); |
241
|
|
|
$attribs = $profile->getCollapsedAttributes(); |
242
|
|
|
$returnArray = []; |
243
|
|
|
$returnArray['silverbullet'] = $profile instanceof ProfileSilverbullet ? 1 : 0; |
244
|
|
|
if (isset($attribs['support:email'])) { |
245
|
|
|
$returnArray['local_email'] = $attribs['support:email'][0]; |
246
|
|
|
} |
247
|
|
|
if (isset($attribs['support:phone'])) { |
248
|
|
|
$returnArray['local_phone'] = $attribs['support:phone'][0]; |
249
|
|
|
} |
250
|
|
|
if (isset($attribs['support:url'])) { |
251
|
|
|
$returnArray['local_url'] = $attribs['support:url'][0]; |
252
|
|
|
} |
253
|
|
|
if (isset($attribs['profile:description'])) { |
254
|
|
|
$returnArray['description'] = $attribs['profile:description'][0]; |
255
|
|
|
} |
256
|
|
|
$returnArray['devices'] = $profile->listDevices(); |
257
|
|
|
$this->languageInstance->setTextDomain("web_user"); |
258
|
|
|
return($returnArray); |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
/** |
262
|
|
|
this method needs to be used with care, it could give wrong results in some |
263
|
|
|
cicumstances |
264
|
|
|
*/ |
265
|
|
|
private function getRootURL() { |
266
|
|
|
$backtrace = debug_backtrace(); |
267
|
|
|
$backtraceFileInfo = array_pop($backtrace); |
268
|
|
|
$fileTemp = $backtraceFileInfo['file']; |
269
|
|
|
$file = substr($fileTemp, strlen(dirname(__DIR__))); |
270
|
|
|
if ($file === FALSE) { |
271
|
|
|
throw new Exception("No idea what's going wrong - filename cropping returned FALSE!"); |
272
|
|
|
} |
273
|
|
|
while (substr($file, 0, 1) == '/') { |
274
|
|
|
$file = substr($file, 1); |
275
|
|
|
if ($file === FALSE) { |
276
|
|
|
throw new Exception("Unable to crop leading / from a string known to start with / ???"); |
277
|
|
|
} |
278
|
|
|
} |
279
|
|
|
$slashCount = count(explode('/', $file)); |
280
|
|
|
$out = $_SERVER['SCRIPT_NAME']; |
281
|
|
|
for ($iterator = 0; $iterator < $slashCount; $iterator++) { |
282
|
|
|
$out = dirname($out); |
283
|
|
|
} |
284
|
|
|
if ($out == '/') { |
285
|
|
|
$out = ''; |
286
|
|
|
} |
287
|
|
|
return '//' . $_SERVER['SERVER_NAME'] . $out; |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* wrapper JSON function |
292
|
|
|
* |
293
|
|
|
* @param array|bool|null $data the core data to be converted to JSON |
294
|
|
|
* @param int $status extra status information, defaults to 1 |
295
|
|
|
* @return string JSON encoded data |
296
|
|
|
*/ |
297
|
|
|
public function return_json($data, $status = 1) { |
298
|
|
|
$returnArray = []; |
299
|
|
|
$returnArray['status'] = $status; |
300
|
|
|
$returnArray['data'] = $data; |
301
|
|
|
$returnArray['tou'] = "Please consult Terms of Use at: " . $this->getRootURL() . "/tou.php"; |
302
|
|
|
return(json_encode($returnArray)); |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
/** |
306
|
|
|
* outputs the list of supported languages. |
307
|
|
|
*/ |
308
|
|
|
public function JSON_listLanguages() { |
309
|
|
|
$returnArray = []; |
310
|
|
|
foreach (CONFIG['LANGUAGES'] as $id => $val) { |
311
|
|
|
$returnArray[] = ['lang' => $id, 'display' => $val['display'], 'locale' => $val['locale']]; |
312
|
|
|
} |
313
|
|
|
echo $this->return_json($returnArray); |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* outputs the list of countries with configured IdPs |
318
|
|
|
* |
319
|
|
|
*/ |
320
|
|
|
public function JSON_listCountries() { |
321
|
|
|
$federations = $this->printCountryList(1); |
322
|
|
|
$returnArray = []; |
323
|
|
|
foreach ($federations as $id => $val) { |
324
|
|
|
$returnArray[] = ['federation' => $id, 'display' => $val]; |
325
|
|
|
} |
326
|
|
|
echo $this->return_json($returnArray); |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
/** |
330
|
|
|
* outputs the list of IdPs in a given country |
331
|
|
|
* |
332
|
|
|
* @param string $country the country we are interested in |
333
|
|
|
*/ |
334
|
|
|
public function JSON_listIdentityProviders($country) { |
335
|
|
|
$idps = $this->listAllIdentityProviders(1, $country); |
336
|
|
|
$returnArray = []; |
337
|
|
View Code Duplication |
foreach ($idps as $idp) { |
|
|
|
|
338
|
|
|
$returnArray[] = ['idp' => $idp['entityID'], 'display' => $idp['title']]; |
339
|
|
|
} |
340
|
|
|
echo $this->return_json($returnArray); |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
/** |
344
|
|
|
* outputs the list of all active IdPs |
345
|
|
|
* |
346
|
|
|
* The IdP list is formatted for DiscoJuice consumption |
347
|
|
|
*/ |
348
|
|
|
public function JSON_listIdentityProvidersForDisco() { |
349
|
|
|
$idps = $this->listAllIdentityProviders(1); |
350
|
|
|
$returnArray = []; |
351
|
|
|
foreach ($idps as $idp) { |
352
|
|
|
$idp['idp'] = $idp['entityID']; |
353
|
|
|
$returnArray[] = $idp; |
354
|
|
|
} |
355
|
|
|
echo json_encode($returnArray); |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
/** |
359
|
|
|
* outputs the list of IdPs in a given country ordered with respect to their distance to the user's location |
360
|
|
|
* |
361
|
|
|
* @param string $country the country in question |
362
|
|
|
* @param array $location the coordinates of the approximate user location |
363
|
|
|
* |
364
|
|
|
*/ |
365
|
|
|
public function JSON_orderIdentityProviders($country, $location = NULL) { |
366
|
|
|
$idps = $this->orderIdentityProviders($country, $location); |
367
|
|
|
$returnArray = []; |
368
|
|
View Code Duplication |
foreach ($idps as $idp) { |
|
|
|
|
369
|
|
|
$returnArray[] = ['idp' => $idp['id'], 'display' => $idp['title']]; |
370
|
|
|
} |
371
|
|
|
echo $this->return_json($returnArray); |
372
|
|
|
} |
373
|
|
|
|
374
|
|
|
/** |
375
|
|
|
* outputs a list of profiles available for a given IdP |
376
|
|
|
* |
377
|
|
|
* @param int $idpIdentifier the IdP identifier |
378
|
|
|
* @param int $sort should the result set be sorted? 0 = no, 1 = yes |
379
|
|
|
*/ |
380
|
|
|
public function JSON_listProfiles($idpIdentifier, $sort = 0) { |
381
|
|
|
$this->languageInstance->setTextDomain("web_user"); |
382
|
|
|
$returnArray = []; |
383
|
|
|
try { |
384
|
|
|
$idp = new IdP($idpIdentifier); |
385
|
|
|
} catch (\Exception $fail) { |
386
|
|
|
echo $this->return_json($returnArray, 0); |
387
|
|
|
return; |
388
|
|
|
} |
389
|
|
|
$hasLogo = FALSE; |
390
|
|
|
$logo = $idp->getAttributes('general:logo_file'); |
391
|
|
|
if (count($logo) > 0) { |
392
|
|
|
$hasLogo = 1; |
393
|
|
|
} |
394
|
|
|
$profiles = $idp->listProfiles(TRUE); |
395
|
|
|
if ($sort == 1) { |
396
|
|
|
usort($profiles, ["UserAPI", "profileSort"]); |
397
|
|
|
} |
398
|
|
|
foreach ($profiles as $profile) { |
399
|
|
|
$returnArray[] = ['profile' => $profile->identifier, 'display' => $profile->name, 'idp_name' => $profile->instName, 'logo' => $hasLogo]; |
400
|
|
|
} |
401
|
|
|
echo $this->return_json($returnArray); |
402
|
|
|
} |
403
|
|
|
|
404
|
|
|
/** |
405
|
|
|
* outputs the list of devices available for the given profile |
406
|
|
|
* |
407
|
|
|
* @param int $profileId the Profile identifier |
408
|
|
|
*/ |
409
|
|
|
public function JSON_listDevices($profileId) { |
410
|
|
|
$this->languageInstance->setTextDomain("web_user"); |
411
|
|
|
$returnArray = []; |
412
|
|
|
$profileAttributes = $this->profileAttributes($profileId); |
413
|
|
|
$thedevices = $profileAttributes['devices']; |
414
|
|
|
foreach ($thedevices as $D) { |
415
|
|
|
if (isset($D['options']) && isset($D['options']['hidden']) && $D['options']['hidden']) { |
416
|
|
|
continue; |
417
|
|
|
} |
418
|
|
|
if ($D['device'] === '0') { |
419
|
|
|
$disp = ''; |
420
|
|
|
} else { |
421
|
|
|
$disp = $D['display']; |
422
|
|
|
} |
423
|
|
|
$returnArray[] = ['device' => $D['id'], 'display' => $disp, 'status' => $D['status'], 'redirect' => $D['redirect']]; |
424
|
|
|
} |
425
|
|
|
echo $this->return_json($returnArray); |
426
|
|
|
} |
427
|
|
|
|
428
|
|
|
/** |
429
|
|
|
* outputs the link to the installers (additionally, actually generates it or takes it from cache) |
430
|
|
|
* |
431
|
|
|
* @param string $device identifier as in {@link devices.php} |
432
|
|
|
* @param int $prof_id profile identifier |
433
|
|
|
*/ |
434
|
|
|
public function JSON_generateInstaller($device, $prof_id) { |
435
|
|
|
$this->loggerInstance->debug(4, "JSON::generateInstaller arguments: $device,$prof_id\n"); |
436
|
|
|
$output = $this->generateInstaller($device, $prof_id); |
437
|
|
|
$this->loggerInstance->debug(4, "output from GUI::generateInstaller:"); |
438
|
|
|
$this->loggerInstance->debug(4, print_r($output, true)); |
439
|
|
|
$this->loggerInstance->debug(4, json_encode($output)); |
440
|
|
|
// header('Content-type: application/json; utf-8'); |
|
|
|
|
441
|
|
|
echo $this->return_json($output); |
442
|
|
|
} |
443
|
|
|
|
444
|
|
|
/** |
445
|
|
|
* Generate and send the installer |
446
|
|
|
* |
447
|
|
|
* @param string $device identifier as in {@link devices.php} |
448
|
|
|
* @param int $prof_id profile identifier |
449
|
|
|
* @return string binary stream: installerFile |
450
|
|
|
*/ |
451
|
|
|
public function downloadInstaller($device, $prof_id, $generated_for = 'user', $token = NULL, $password = NULL) { |
452
|
|
|
$this->loggerInstance->debug(4, "downloadInstaller arguments: $device,$prof_id,$generated_for\n"); |
453
|
|
|
$output = $this->generateInstaller($device, $prof_id, $generated_for, $token, $password); |
454
|
|
|
$this->loggerInstance->debug(4, "output from GUI::generateInstaller:"); |
455
|
|
|
$this->loggerInstance->debug(4, print_r($output, true)); |
456
|
|
|
if (empty($output['link']) || $output['link'] === 0) { |
457
|
|
|
header("HTTP/1.0 404 Not Found"); |
458
|
|
|
return; |
459
|
|
|
} |
460
|
|
|
$validator = new \web\lib\common\InputValidation(); |
461
|
|
|
$profile = $validator->Profile($prof_id); |
462
|
|
|
$profile->incrementDownloadStats($device, $generated_for); |
463
|
|
|
$file = $this->installerPath; |
464
|
|
|
$filetype = $output['mime']; |
465
|
|
|
$this->loggerInstance->debug(4, "installer MIME type:$filetype\n"); |
466
|
|
|
header("Content-type: " . $filetype); |
467
|
|
|
header('Content-Disposition: inline; filename="' . basename($file) . '"'); |
468
|
|
|
header('Content-Length: ' . filesize($file)); |
469
|
|
|
ob_clean(); |
470
|
|
|
flush(); |
471
|
|
|
readfile($file); |
472
|
|
|
} |
473
|
|
|
|
474
|
|
|
/** |
475
|
|
|
* resizes image files |
476
|
|
|
* |
477
|
|
|
* @param string $inputImage |
478
|
|
|
* @param string $destFile |
479
|
|
|
* @param int $width |
480
|
|
|
* @param int $height |
481
|
|
|
* @param bool $resize shall we do resizing? width and height are ignored otherwise |
482
|
|
|
* @return array |
483
|
|
|
*/ |
484
|
|
|
private function processImage($inputImage, $destFile, $width, $height, $resize) { |
485
|
|
|
$info = new \finfo(); |
486
|
|
|
$filetype = $info->buffer($inputImage, FILEINFO_MIME_TYPE); |
487
|
|
|
$offset = 60 * 60 * 24 * 30; |
488
|
|
|
// gmdate cannot fail here - time() is its default argument (and integer), and we are adding an integer to it |
489
|
|
|
$expiresString = "Expires: " . /** @scrutinizer ignore-type */ gmdate("D, d M Y H:i:s", time() + $offset) . " GMT"; |
490
|
|
|
$blob = $inputImage; |
491
|
|
|
|
492
|
|
|
if ($resize === TRUE) { |
493
|
|
|
$image = new \Imagick(); |
494
|
|
|
$image->readImageBlob($inputImage); |
495
|
|
|
$image->setImageFormat('PNG'); |
496
|
|
|
$image->thumbnailImage($width, $height, 1); |
497
|
|
|
$blob = $image->getImageBlob(); |
498
|
|
|
$this->loggerInstance->debug(4, "Writing cached logo $destFile for IdP/Federation.\n"); |
499
|
|
|
file_put_contents($destFile, $blob); |
500
|
|
|
} |
501
|
|
|
|
502
|
|
|
return ["filetype" => $filetype, "expires" => $expiresString, "blob" => $blob]; |
503
|
|
|
} |
504
|
|
|
|
505
|
|
|
/** |
506
|
|
|
* Get and prepare logo file |
507
|
|
|
* |
508
|
|
|
* When called for DiscoJuice, first check if file cache exists |
509
|
|
|
* If not then generate the file and save it in the cache |
510
|
|
|
* @param int $idp IdP identifier |
|
|
|
|
511
|
|
|
* @param int $width maximum width of the generated image - if 0 then it is treated as no upper bound |
512
|
|
|
* @param int $height maximum height of the generated image - if 0 then it is treated as no upper bound |
513
|
|
|
* @return array|null array with image information or NULL if there is no logo |
514
|
|
|
*/ |
515
|
|
|
private function getLogo($identifier, $type, $width = 0, $height = 0) { |
516
|
|
|
$expiresString = ''; |
517
|
|
|
$resize = FALSE; |
518
|
|
|
$logoFile = ""; |
519
|
|
|
$attributeName = ""; |
|
|
|
|
520
|
|
|
$validator = new \web\lib\common\InputValidation(); |
521
|
|
|
switch ($type) { |
522
|
|
|
case "federation": |
523
|
|
|
if (is_int($identifier)) { |
524
|
|
|
throw new Exception("Federation identifiers are strings!"); |
525
|
|
|
} |
526
|
|
|
$entity = $validator->Federation($identifier); |
527
|
|
|
$attributeName = "fed:logo_file"; |
528
|
|
|
break; |
529
|
|
|
case "idp": |
530
|
|
|
if (!is_int($identifier)) { |
531
|
|
|
throw new Exception("Institution identifiers are integers!"); |
532
|
|
|
} |
533
|
|
|
$entity = $validator->IdP($identifier); |
534
|
|
|
$attributeName = "general:logo_file"; |
535
|
|
|
break; |
536
|
|
|
default: |
537
|
|
|
throw new Exception("Unknown type of logo requested!"); |
538
|
|
|
} |
539
|
|
|
$filetype = 'image/png'; // default, only one code path where it can become different |
540
|
|
|
if (($width || $height) && is_numeric($width) && is_numeric($height)) { |
541
|
|
|
$resize = TRUE; |
542
|
|
|
if ($height == 0) { |
543
|
|
|
$height = 10000; |
544
|
|
|
} |
545
|
|
|
if ($width == 0) { |
546
|
|
|
$width = 10000; |
547
|
|
|
} |
548
|
|
|
$logoFile = ROOT . '/web/downloads/logos/' . $identifier . '_' . $width . '_' . $height . '.png'; |
549
|
|
|
} |
550
|
|
|
if ($resize === TRUE && is_file($logoFile)) { |
551
|
|
|
$this->loggerInstance->debug(4, "Using cached logo $logoFile for: $identifier\n"); |
552
|
|
|
$blob = file_get_contents($logoFile); |
553
|
|
|
} else { |
554
|
|
|
$logoAttribute = $entity->getAttributes($attributeName); |
555
|
|
|
if (count($logoAttribute) == 0) { |
556
|
|
|
return(NULL); |
557
|
|
|
} |
558
|
|
|
$this->loggerInstance->debug(4,"RESIZE:$width:$height\n"); |
559
|
|
|
$meta = $this->processImage($logoAttribute[0]['value'], $logoFile, $width, $height, $resize); |
560
|
|
|
$filetype = $meta['filetype']; |
561
|
|
|
$expiresString = $meta['expires']; |
562
|
|
|
$blob = $meta['blob']; |
563
|
|
|
} |
564
|
|
|
return ["filetype" => $filetype, "expires" => $expiresString, "blob" => $blob]; |
565
|
|
|
} |
566
|
|
|
|
567
|
|
|
/** |
568
|
|
|
* outputs a logo |
569
|
|
|
* |
570
|
|
|
* @param string|int $identifier |
571
|
|
|
* @param string $type "federation" or "idp" |
572
|
|
|
* @param int $width |
573
|
|
|
* @param int $height |
574
|
|
|
*/ |
575
|
|
|
public function sendLogo($identifier, $type, $width = 0, $height = 0) { |
576
|
|
|
$logo = $this->getLogo($identifier, $type, $width, $height); |
577
|
|
|
header("Content-type: " . $logo['filetype']); |
578
|
|
|
header("Cache-Control:max-age=36000, must-revalidate"); |
579
|
|
|
header($logo['expires']); |
580
|
|
|
echo $logo['blob']; |
581
|
|
|
} |
582
|
|
|
|
583
|
|
|
/** |
584
|
|
|
* find out where the user is currently located |
585
|
|
|
* @return array |
586
|
|
|
*/ |
587
|
|
|
public function locateUser() { |
588
|
|
View Code Duplication |
if (CONFIG['GEOIP']['version'] != 1) { |
|
|
|
|
589
|
|
|
return ['status' => 'error', 'error' => 'Function for GEOIPv1 called, but config says this is not the version to use!']; |
590
|
|
|
} |
591
|
|
|
//$host = $_SERVER['REMOTE_ADDR']; |
|
|
|
|
592
|
|
|
$host = filter_input(INPUT_SERVER, 'REMOTE_ADDR', FILTER_VALIDATE_IP); |
593
|
|
|
$record = geoip_record_by_name($host); |
594
|
|
|
if ($record === FALSE) { |
595
|
|
|
return ['status' => 'error', 'error' => 'Problem listing countries']; |
596
|
|
|
} |
597
|
|
|
$result = ['status' => 'ok']; |
598
|
|
|
$result['country'] = $record['country_code']; |
599
|
|
|
// the two lines below are a dirty hack to take of the error in naming the UK federation |
600
|
|
|
if ($result['country'] == 'GB') { |
601
|
|
|
$result['country'] = 'UK'; |
602
|
|
|
} |
603
|
|
|
$result['region'] = $record['region']; |
604
|
|
|
$result['geo'] = ['lat' => (float) $record['latitude'], 'lon' => (float) $record['longitude']]; |
605
|
|
|
return($result); |
606
|
|
|
} |
607
|
|
|
|
608
|
|
|
/** |
609
|
|
|
* find out where the user is currently located, using GeoIP2 |
610
|
|
|
* @return array |
611
|
|
|
*/ |
612
|
|
|
public function locateUser2() { |
613
|
|
View Code Duplication |
if (CONFIG['GEOIP']['version'] != 2) { |
|
|
|
|
614
|
|
|
return ['status' => 'error', 'error' => 'Function for GEOIPv2 called, but config says this is not the version to use!']; |
615
|
|
|
} |
616
|
|
|
require_once CONFIG['GEOIP']['geoip2-path-to-autoloader']; |
617
|
|
|
$reader = new Reader(CONFIG['GEOIP']['geoip2-path-to-db']); |
618
|
|
|
$host = $_SERVER['REMOTE_ADDR']; |
619
|
|
|
try { |
620
|
|
|
$record = $reader->city($host); |
621
|
|
|
} catch (\Exception $e) { |
622
|
|
|
$result = ['status' => 'error', 'error' => 'Problem listing countries']; |
623
|
|
|
return($result); |
624
|
|
|
} |
625
|
|
|
$result = ['status' => 'ok']; |
626
|
|
|
$result['country'] = $record->country->isoCode; |
627
|
|
|
// the two lines below are a dirty hack to take of the error in naming the UK federation |
628
|
|
|
if ($result['country'] == 'GB') { |
629
|
|
|
$result['country'] = 'UK'; |
630
|
|
|
} |
631
|
|
|
$result['region'] = $record->continent->name; |
632
|
|
|
|
633
|
|
|
$result['geo'] = ['lat' => (float) $record->location->latitude, 'lon' => (float) $record->location->longitude]; |
634
|
|
|
return($result); |
635
|
|
|
} |
636
|
|
|
|
637
|
|
|
/** |
638
|
|
|
* outputs the user location as JSON |
639
|
|
|
* @throws Exception |
640
|
|
|
*/ |
641
|
|
|
public function JSON_locateUser() { |
642
|
|
|
header('Content-type: application/json; utf-8'); |
643
|
|
|
|
644
|
|
|
$geoipVersion = CONFIG['GEOIP']['version'] ?? 0; |
645
|
|
|
|
646
|
|
|
switch ($geoipVersion) { |
647
|
|
|
case 0: |
648
|
|
|
echo json_encode(['status' => 'error', 'error' => 'Geolocation not supported']); |
649
|
|
|
break; |
650
|
|
|
case 1: |
651
|
|
|
echo json_encode($this->locateUser()); |
652
|
|
|
break; |
653
|
|
|
case 2: |
654
|
|
|
echo json_encode($this->locateUser2()); |
655
|
|
|
break; |
656
|
|
|
default: |
657
|
|
|
throw new Exception("This version of GeoIP is not known!"); |
658
|
|
|
} |
659
|
|
|
} |
660
|
|
|
|
661
|
|
|
/** |
662
|
|
|
* outputs support data prepared within {@link GUI::profileAttributes()} |
663
|
|
|
*/ |
664
|
|
|
public function JSON_profileAttributes($prof_id) { |
665
|
|
|
// header('Content-type: application/json; utf-8'); |
|
|
|
|
666
|
|
|
echo $this->return_json($this->profileAttributes($prof_id)); |
667
|
|
|
} |
668
|
|
|
|
669
|
|
|
/** |
670
|
|
|
* Calculate the distance in km between two points given their |
671
|
|
|
* geo coordinates. |
672
|
|
|
* @param array $point1 - first point as an 'lat', 'lon' array |
673
|
|
|
* @param array $profile1 - second point as an 'lat', 'lon' array |
674
|
|
|
* @return float distance in km |
675
|
|
|
*/ |
676
|
|
|
private function geoDistance($point1, $profile1) { |
677
|
|
|
|
678
|
|
|
$distIntermediate = sin(deg2rad($point1['lat'])) * sin(deg2rad($profile1['lat'])) + |
679
|
|
|
cos(deg2rad($point1['lat'])) * cos(deg2rad($profile1['lat'])) * cos(deg2rad($point1['lon'] - $profile1['lon'])); |
680
|
|
|
$dist = rad2deg(acos($distIntermediate)) * 60 * 1.1852; |
681
|
|
|
return(round($dist)); |
682
|
|
|
} |
683
|
|
|
|
684
|
|
|
/** |
685
|
|
|
* Order active identity providers according to their distance and name |
686
|
|
|
* @param array $currentLocation - current location |
687
|
|
|
* @return array $IdPs - list of arrays ('id', 'name'); |
688
|
|
|
*/ |
689
|
|
|
public function orderIdentityProviders($country, $currentLocation = NULL) { |
690
|
|
|
$idps = $this->listAllIdentityProviders(1, $country); |
691
|
|
|
|
692
|
|
|
if (is_null($currentLocation)) { |
693
|
|
|
$currentLocation = ['lat' => "90", 'lon' => "0"]; |
694
|
|
|
$userLocation = $this->locateUser(); |
695
|
|
|
if ($userLocation['status'] == 'ok') { |
696
|
|
|
$currentLocation = $userLocation['geo']; |
697
|
|
|
} |
698
|
|
|
} |
699
|
|
|
$idpTitle = []; |
700
|
|
|
$resultSet = []; |
701
|
|
|
foreach ($idps as $idp) { |
702
|
|
|
$idpTitle[$idp['entityID']] = $idp['title']; |
703
|
|
|
$dist = 10000; |
704
|
|
|
if (isset($idp['geo'])) { |
705
|
|
|
$G = $idp['geo']; |
706
|
|
|
if (isset($G['lon'])) { |
707
|
|
|
$d1 = $this->geoDistance($currentLocation, $G); |
708
|
|
|
if ($d1 < $dist) { |
709
|
|
|
$dist = $d1; |
710
|
|
|
} |
711
|
|
|
} else { |
712
|
|
|
foreach ($G as $g) { |
713
|
|
|
$d1 = $this->geoDistance($currentLocation, $g); |
714
|
|
|
if ($d1 < $dist) { |
715
|
|
|
$dist = $d1; |
716
|
|
|
} |
717
|
|
|
} |
718
|
|
|
} |
719
|
|
|
} |
720
|
|
|
if ($dist > 100) { |
721
|
|
|
$dist = 10000; |
722
|
|
|
} |
723
|
|
|
$d = sprintf("%06d", $dist); |
724
|
|
|
$resultSet[$idp['entityID']] = $d . " " . $idp['title']; |
725
|
|
|
} |
726
|
|
|
asort($resultSet); |
727
|
|
|
$outarray = []; |
728
|
|
|
foreach (array_keys($resultSet) as $r) { |
729
|
|
|
$outarray[] = ['idp' => $r, 'title' => $idpTitle[$r]]; |
730
|
|
|
} |
731
|
|
|
return($outarray); |
732
|
|
|
} |
733
|
|
|
|
734
|
|
|
/** |
735
|
|
|
* Detect the best device driver form the browser |
736
|
|
|
* |
737
|
|
|
* Detects the operating system and returns its id |
738
|
|
|
* display name and group membership (as in devices.php) |
739
|
|
|
* @return array|false OS information, indexed by 'id', 'display', 'group' |
740
|
|
|
*/ |
741
|
|
|
public function detectOS() { |
742
|
|
|
$oldDomain = $this->languageInstance->setTextDomain("devices"); |
743
|
|
|
$Dev = \devices\Devices::listDevices(); |
744
|
|
|
$this->languageInstance->setTextDomain($oldDomain); |
745
|
|
|
$devId = $this->deviceFromRequest(); |
746
|
|
|
if ($devId !== FALSE) { |
747
|
|
|
$ret = $this->returnDevice($devId, $Dev[$devId]); |
748
|
|
|
if ($ret !== FALSE) { |
749
|
|
|
return($ret); |
750
|
|
|
} |
751
|
|
|
} |
752
|
|
|
// the device has not been specified or not specified correctly, try to detect if from the browser ID |
753
|
|
|
$browser = filter_input(INPUT_SERVER, 'HTTP_USER_AGENT', FILTER_SANITIZE_STRING); |
754
|
|
|
$this->loggerInstance->debug(4, "HTTP_USER_AGENT=$browser\n"); |
755
|
|
|
foreach ($Dev as $devId => $device) { |
756
|
|
|
if (!isset($device['match'])) { |
757
|
|
|
continue; |
758
|
|
|
} |
759
|
|
|
if (preg_match('/' . $device['match'] . '/', $browser)) { |
760
|
|
|
return ($this->returnDevice($devId, $device)); |
761
|
|
|
} |
762
|
|
|
} |
763
|
|
|
$this->loggerInstance->debug(2, "Unrecognised system: $browser\n"); |
764
|
|
|
return(false); |
765
|
|
|
} |
766
|
|
|
|
767
|
|
|
/* |
768
|
|
|
* test if devise is defined and is not hidden. If all is fine return extracted information. |
769
|
|
|
* Return FALSE if the device has not been correctly specified |
770
|
|
|
*/ |
771
|
|
|
private function returnDevice($devId, $device) { |
772
|
|
|
if (!isset($device['options']['hidden']) || $device['options']['hidden'] == 0) { |
773
|
|
|
$this->loggerInstance->debug(4, "Browser_id: $devId\n"); |
774
|
|
|
return(['device' => $devId, 'display' => $device['display'], 'group' => $device['group']]); |
775
|
|
|
} |
776
|
|
|
return(FALSE); |
777
|
|
|
} |
778
|
|
|
|
779
|
|
|
/** |
780
|
|
|
* This methods cheks if the devide has been specified as the HTTP parameters |
781
|
|
|
* @return device id is correcty specified or FALSE otherwise |
782
|
|
|
*/ |
783
|
|
|
private function deviceFromRequest() { |
784
|
|
|
$devId = filter_input(INPUT_GET, 'device', FILTER_SANITIZE_STRING) ?? filter_input(INPUT_POST, 'device', FILTER_SANITIZE_STRING); |
785
|
|
|
if ($devId === NULL || $devId === FALSE) { |
786
|
|
|
$this->loggerInstance->debug(2, "Invalid device id provided\n"); |
787
|
|
|
return(FALSE); |
|
|
|
|
788
|
|
|
} |
789
|
|
|
$Dev = \devices\Devices::listDevices(); |
790
|
|
|
if (!isset($Dev['$devId'])) { |
791
|
|
|
$this->loggerInstance->debug(2, "Unrecognised system: $devId\n"); |
792
|
|
|
return(FALSE); |
|
|
|
|
793
|
|
|
} |
794
|
|
|
return($devId); |
795
|
|
|
} |
796
|
|
|
|
797
|
|
|
|
798
|
|
|
|
799
|
|
|
/** |
800
|
|
|
* outputs OS guess in JSON |
801
|
|
|
*/ |
802
|
|
|
public function JSON_detectOS() { |
803
|
|
|
$returnArray = $this->detectOS(); |
804
|
|
|
if (is_array($returnArray)) { |
805
|
|
|
$status = 1; |
806
|
|
|
} else { |
807
|
|
|
$status = 0; |
808
|
|
|
} |
809
|
|
|
echo $this->return_json($returnArray, $status); |
810
|
|
|
} |
811
|
|
|
|
812
|
|
|
/** |
813
|
|
|
* finds all the user certificates that originated in a given token |
814
|
|
|
* @param string $token |
815
|
|
|
* @return array|false returns FALSE if a token is invalid, otherwise array of certs |
816
|
|
|
*/ |
817
|
|
|
public function getUserCerts($token) { |
818
|
|
|
$validator = new \web\lib\common\InputValidation(); |
819
|
|
|
$cleanToken = $validator->token($token); |
820
|
|
|
if ($cleanToken) { |
821
|
|
|
// check status of this silverbullet token according to info in DB: |
822
|
|
|
// it can be VALID (exists and not redeemed, EXPIRED, REDEEMED or INVALID (non existent) |
823
|
|
|
$tokenStatus = \core\ProfileSilverbullet::tokenStatus($cleanToken); |
824
|
|
|
} else { |
825
|
|
|
return false; |
826
|
|
|
} |
827
|
|
|
$profile = new \core\ProfileSilverbullet($tokenStatus['profile'], NULL); |
828
|
|
|
$userdata = $profile->userStatus($tokenStatus['db_id']); |
829
|
|
|
$allcerts = []; |
830
|
|
|
foreach ($userdata as $index => $content) { |
831
|
|
|
$allcerts = array_merge($allcerts, $content['cert_status']); |
832
|
|
|
} |
833
|
|
|
return $allcerts; |
834
|
|
|
} |
835
|
|
|
|
836
|
|
|
/** |
837
|
|
|
* outputs user certificates pertaining to a given token in JSON |
838
|
|
|
* @param string $token |
839
|
|
|
*/ |
840
|
|
|
public function JSON_getUserCerts($token) { |
841
|
|
|
$returnArray = $this->getUserCerts($token); |
842
|
|
|
if ($returnArray) { |
843
|
|
|
$status = 1; |
844
|
|
|
} else { |
845
|
|
|
$status = 0; |
846
|
|
|
} |
847
|
|
|
echo $this->return_json($returnArray, $status); |
848
|
|
|
} |
849
|
|
|
|
850
|
|
|
public $device; |
851
|
|
|
private $installerPath; |
852
|
|
|
|
853
|
|
|
/** |
854
|
|
|
* helper function to sort profiles by their name |
855
|
|
|
* @param \core\AbstractProfile $profile1 the first profile's information |
856
|
|
|
* @param \core\AbstractProfile $profile2 the second profile's information |
857
|
|
|
* @return int |
858
|
|
|
*/ |
859
|
|
|
private static function profileSort($profile1, $profile2) { |
|
|
|
|
860
|
|
|
return strcasecmp($profile1->name, $profile2->name); |
861
|
|
|
} |
862
|
|
|
|
863
|
|
|
} |
864
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.