Passed
Push — master ( 32b95a...2c2bb9 )
by Stefan
05:45 queued 29s
created

UserAPI::generateNewInstaller()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 34
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 29
nc 6
nop 5
dl 0
loc 34
rs 8.439
c 0
b 0
f 0
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
    public function __construct() {
35
        parent::__construct();
36
    }
37
38
    /**
39
     * Prepare the device module environment and send back the link
40
     * This method creates a device module instance via the {@link DeviceFactory} call, 
41
     * then sets up the device module environment for the specific profile by calling 
42
     * {@link DeviceConfig::setup()} method and finally, called the devide writeInstaller meethod
43
     * passing the returned path name.
44
     * 
45
     * @param string $device identifier as in {@link devices.php}
46
     * @param int $profileId profile identifier
47
     *
48
     * @return array 
49
     *  array with the following fields: 
50
     *  profile - the profile identifier; 
51
     *  device - the device identifier; 
52
     *  link - the path name of the resulting installer
53
     *  mime - the mimetype of the installer
54
     */
55
    public function generateInstaller($device, $profileId, $generatedFor = "user", $token = NULL, $password = NULL) {
56
        $this->languageInstance->setTextDomain("devices");
57
        $this->loggerInstance->debug(4, "installer:$device:$profileId\n");
58
        $validator = new \web\lib\common\InputValidation();
59
        $profile = $validator->Profile($profileId);
60
        $attribs = $profile->getCollapsedAttributes();
61
        // test if the profile is production-ready and if not if the authenticated user is an owner
62
        if (!isset($attribs['profile:production']) || (isset($attribs['profile:production']) && $attribs['profile:production'][0] != "on")) {
63
            $this->loggerInstance->debug(4, "Attempt to download a non-production ready installer fir profile: $profileId\n");
64
            $auth = new \web\lib\admin\Authentication();
65
            if (!$auth->isAuthenticated()) {
66
                $this->loggerInstance->debug(2, "User NOT authenticated, rejecting request for a non-production installer\n");
67
                header("HTTP/1.0 403 Not Authorized");
68
                return;
69
            }
70
71
            $userObject = new User($_SESSION['user']);
72
            if (!$userObject->isIdPOwner($profile->institution)) {
73
                $this->loggerInstance->debug(2, "User not an owner of a non-production profile - access forbidden\n");
74
                header("HTTP/1.0 403 Not Authorized");
75
                return;
76
            }
77
            $this->loggerInstance->debug(4, "User is the owner - allowing access\n");
78
        }
79
        $installerProperties = [];
80
        $installerProperties['profile'] = $profileId;
81
        $installerProperties['device'] = $device;
82
        $this->installerPath = $this->getCachedPath($device, $profile);
83
        if ($this->installerPath && $token == NULL && $password == NULL) {
84
            $this->loggerInstance->debug(4, "Using cached installer for: $device\n");
85
            $installerProperties['link'] = "API.php?action=downloadInstaller&lang=" . $this->languageInstance->getLang() . "&profile=$profileId&device=$device&generatedfor=$generatedFor";
86
            $installerProperties['mime'] = $cache['mime'];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $cache seems to be never defined.
Loading history...
87
        } else {
88
            $myInstaller = $this->generateNewInstaller($device, $profile, $generatedFor, $token, $password);
89
            if ($myInstaller['link'] != 0) {
90
                $installerProperties['mime'] = $myInstaller['mime'];
91
            }
92
            $installerProperties['link'] = $myInstaller['link'];
93
        }
94
        $this->languageInstance->setTextDomain("web_user");
95
        return($installerProperties);
96
    }
97
98
    /**
99
     * This function tries to find a cached copy of an installer for a given
100
     * combination of Profile and device
101
     * @param string $device
102
     * @param AbstractProfile $profile
103
     * @return boolean|string the string with the path to the cached copy, or FALSE if no cached copy exists
104
     */
105
    private function getCachedPath($device, $profile) {
106
        $deviceList = \devices\Devices::listDevices();
107
        $deviceConfig = $deviceList[$device];
108
        $noCache = (isset(\devices\Devices::$Options['no_cache']) && \devices\Devices::$Options['no_cache']) ? 1 : 0;
109
        if (isset($deviceConfig['options']['no_cache'])) {
110
            $noCache = $deviceConfig['options']['no_cache'] ? 1 : 0;
111
        }
112
        if ($noCache) {
113
            $this->loggerInstance->debug(5, "getCachedPath: the no_cache option set for this device\n");
114
            return(FALSE);
115
        }
116
        $this->loggerInstance->debug(5, "getCachedPath: caching option set for this device\n");
117
        $cache = $profile->testCache($device);
118
        $iPath = $cache['cache'];
119
        if ($iPath && is_file($iPath)) {
120
            return($iPath);
121
        }
122
        return(FALSE);
123
    }
124
125
    /**
126
     * Generates a new installer for the given combination of device and Profile
127
     * 
128
     * @param string $device
129
     * @param AbstractProfile $profile
130
     * @return array info about the new installer (mime and link)
131
     */
132
    private function generateNewInstaller($device, $profile, $generatedFor, $token, $password) {
133
        $this->loggerInstance->debug(5, "generateNewInstaller() - Enter");
134
        $factory = new DeviceFactory($device);
135
        $this->loggerInstance->debug(5, "generateNewInstaller() - created Device");
136
        $dev = $factory->device;
137
        $out = [];
138
        if (isset($dev)) {
139
            $dev->setup($profile, $token, $password);
140
            $this->loggerInstance->debug(5, "generateNewInstaller() - Device setup done");
141
            $installer = $dev->writeInstaller();
142
            $this->loggerInstance->debug(5, "generateNewInstaller() - writeInstaller complete");
143
            $iPath = $dev->FPATH . '/tmp/' . $installer;
144
            if ($iPath && is_file($iPath)) {
145
                if (isset($dev->options['mime'])) {
146
                    $out['mime'] = $dev->options['mime'];
147
                } else {
148
                    $info = new \finfo();
149
                    $out['mime'] = $info->file($iPath, FILEINFO_MIME_TYPE);
150
                }
151
                $this->installerPath = $dev->FPATH . '/' . $installer;
152
                rename($iPath, $this->installerPath);
153
                $integerEap = (new \core\common\EAP($dev->selectedEap))->getIntegerRep();
154
                $profile->updateCache($device, $this->installerPath, $out['mime'], $integerEap);
155
                if (CONFIG['DEBUG_LEVEL'] < 4) {
156
                    \core\common\Entity::rrmdir($dev->FPATH . '/tmp');
157
                }
158
                $this->loggerInstance->debug(4, "Generated installer: " . $this->installerPath . ": for: $device, EAP:" . $integerEap . "\n");
159
                $out['link'] = "API.php?action=downloadInstaller&lang=" . $this->languageInstance->getLang() . "&profile=" . $profile->identifier . "&device=$device&generatedfor=$generatedFor";
160
            } else {
161
                $this->loggerInstance->debug(2, "Installer generation failed for: " . $profile->identifier . ":$device:" . $this->languageInstance->getLang() . "\n");
162
                $out['link'] = 0;
163
            }
164
        }
165
        return($out);
166
    }
167
168
    /**
169
     * interface to Devices::listDevices() 
170
     */
171
    public function listDevices($showHidden = 0) {
172
        $dev = \devices\Devices::listDevices();
173
        $returnList = [];
174
        $count = 0;
175
        if ($showHidden !== 0 && $showHidden != 1) {
176
            throw new Exception("show_hidden is only be allowed to be 0 or 1, but it is $showHidden!");
177
        }
178
        foreach ($dev as $device => $deviceProperties) {
179
            if (isset($deviceProperties['options']['hidden']) && $deviceProperties['options']['hidden'] && $showHidden == 0) {
180
                continue;
181
            }
182
            $count++;
183
184
            $deviceProperties['device'] = $device;
185
186
            $group = isset($deviceProperties['group']) ? $deviceProperties['group'] : 'other';
187
            if (!isset($returnList[$group])) {
188
                $returnList[$group] = [];
189
            }
190
            $returnList[$group][$device] = $deviceProperties;
191
        }
192
        return $returnList;
193
    }
194
195
    public function deviceInfo($device, $profileId) {
196
        $this->languageInstance->setTextDomain("devices");
197
        $validator = new \web\lib\common\InputValidation();
198
        $out = 0;
199
        $profile = $validator->Profile($profileId);
200
        $factory = new DeviceFactory($device);
201
        $dev = $factory->device;
202
        if (isset($dev)) {
203
            $dev->setup($profile);
204
            $out = $dev->writeDeviceInfo();
205
        }
206
        $this->languageInstance->setTextDomain("web_user");
207
        echo $out;
208
    }
209
210
    /**
211
     * Prepare the support data for a given profile
212
     *
213
     * @param int $profId profile identifier
214
     * @return array
215
     * array with the following fields:
216
     * - local_email
217
     * - local_phone
218
     * - local_url
219
     * - description
220
     * - devices - an array of device names and their statuses (for a given profile)
221
     */
222
    public function profileAttributes($profId) {
223
        $this->languageInstance->setTextDomain("devices");
224
        $validator = new \web\lib\common\InputValidation();
225
        $profile = $validator->Profile($profId);
226
        $attribs = $profile->getCollapsedAttributes();
227
        $returnArray = [];
228
        $returnArray['silverbullet'] = $profile instanceof ProfileSilverbullet ? 1 : 0;
229
        if (isset($attribs['support:email'])) {
230
            $returnArray['local_email'] = $attribs['support:email'][0];
231
        }
232
        if (isset($attribs['support:phone'])) {
233
            $returnArray['local_phone'] = $attribs['support:phone'][0];
234
        }
235
        if (isset($attribs['support:url'])) {
236
            $returnArray['local_url'] = $attribs['support:url'][0];
237
        }
238
        if (isset($attribs['profile:description'])) {
239
            $returnArray['description'] = $attribs['profile:description'][0];
240
        }
241
        $returnArray['devices'] = $profile->listDevices();
242
        $this->languageInstance->setTextDomain("web_user");
243
        return($returnArray);
244
    }
245
246
    /*
247
      this method needs to be used with care, it could give wrong results in some
248
      cicumstances
249
     */
250
251
    private function getRootURL() {
252
        $backtrace = debug_backtrace();
253
        $backtraceFileInfo = array_pop($backtrace);
254
        $fileTemp = $backtraceFileInfo['file'];
255
        $file = substr($fileTemp, strlen(dirname(__DIR__)));
256
        if ($file === FALSE) {
257
            throw new Exception("No idea what's going wrong - filename cropping returned FALSE!");
258
        }
259
        while (substr($file, 0, 1) == '/') {
0 ignored issues
show
Bug introduced by
It seems like $file can also be of type false; however, parameter $string of substr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

259
        while (substr(/** @scrutinizer ignore-type */ $file, 0, 1) == '/') {
Loading history...
260
            $file = substr($file, 1);
261
            if ($file === FALSE) {
262
                throw new Exception("Unable to crop leading / from a string known to start with / ???");
263
            }
264
        }
265
        $slashCount = count(explode('/', $file));
0 ignored issues
show
Bug introduced by
It seems like $file can also be of type false; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

265
        $slashCount = count(explode('/', /** @scrutinizer ignore-type */ $file));
Loading history...
266
        $out = $_SERVER['SCRIPT_NAME'];
267
        for ($iterator = 0; $iterator < $slashCount; $iterator++) {
268
            $out = dirname($out);
269
        }
270
        if ($out == '/') {
271
            $out = '';
272
        }
273
        return '//' . $_SERVER['SERVER_NAME'] . $out;
274
    }
275
    
276
    /* JSON functions */
277
278
    public function return_json($data, $status = 1) {
279
        $returnArray = [];
280
        $returnArray['status'] = $status;
281
        $returnArray['data'] = $data;
282
        $returnArray['tou'] = "Please consult Terms of Use at: " . $this->getRootURL() . "/tou.php";
283
        return(json_encode($returnArray));
284
    }
285
286
    /**
287
     * Return the list of supported languages.
288
     *
289
     * 
290
     */
291
    public function JSON_listLanguages() {
292
        $returnArray = [];
293
        foreach (CONFIG['LANGUAGES'] as $id => $val) {
294
            $returnArray[] = ['lang' => $id, 'display' => $val['display'], 'locale' => $val['locale']];
295
        }
296
        echo $this->return_json($returnArray);
297
    }
298
299
    /**
300
     * Return the list of countiers with configured IdPs
301
     *
302
     * @return string JSON encoded data
303
     */
304
    public function JSON_listCountries() {
305
        $federations = $this->printCountryList(1);
306
        $returnArray = [];
307
        foreach ($federations as $id => $val) {
308
            $returnArray[] = ['federation' => $id, 'display' => $val];
309
        }
310
        echo $this->return_json($returnArray);
311
    }
312
313
    /**
314
     * Return the list of IdPs in a given country
315
     *
316
     * @param string $country the country we are interested in
317
     * @return string JSON encoded data
318
     */
319
    public function JSON_listIdentityProviders($country) {
320
        $idps = $this->listAllIdentityProviders(1, $country);
321
        $returnArray = [];
322 View Code Duplication
        foreach ($idps as $idp) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
323
            $returnArray[] = ['idp' => $idp['entityID'], 'display' => $idp['title']];
324
        }
325
        echo $this->return_json($returnArray);
326
    }
327
328
    /**
329
     * return the list of all active IdPs
330
     *
331
     * The IdP list is formatted for DiscoJuice
332
     * @return string JSON encoded data
333
     */
334
    public function JSON_listIdentityProvidersForDisco() {
335
        $idps = $this->listAllIdentityProviders(1);
336
        $returnArray = [];
337
        foreach ($idps as $idp) {
338
            $idp['idp'] = $idp['entityID'];
339
            $returnArray[] = $idp;
340
        }
341
        echo json_encode($returnArray);
342
    }
343
344
    /**
345
     * Return the list of IdPs in a given country ordered with respect to the user location
346
     *
347
     * @return string JSON encoded data
348
     */
349
    public function JSON_orderIdentityProviders($country, $location = NULL) {
350
        $idps = $this->orderIdentityProviders($country, $location);
351
        $returnArray = [];
352 View Code Duplication
        foreach ($idps as $idp) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
353
            $returnArray[] = ['idp' => $idp['id'], 'display' => $idp['title']];
354
        }
355
        echo $this->return_json($returnArray);
356
    }
357
358
    /**
359
     * Produce a list of profiles available for a given IdP
360
     *
361
     * @param int $idpIdentifier the IdP identifier
362
     * @param int $sort should the result set be sorted? 0 = no, 1 = yes
363
     * @return string JSON encoded data
364
     */
365
    public function JSON_listProfiles($idpIdentifier, $sort = 0) {
366
        $this->languageInstance->setTextDomain("web_user");
367
        $returnArray = [];
368
        try {
369
            $idp = new IdP($idpIdentifier);
370
        } catch (\Exception $fail) {
371
            echo $this->return_json($returnArray, 0);
372
            return;
373
        }
374
        $hasLogo = FALSE;
375
        $logo = $idp->getAttributes('general:logo_file');
376
        if (count($logo) > 0) {
377
            $hasLogo = 1;
378
        }
379
        $profiles = $idp->listProfiles(TRUE);
380
        if ($sort == 1) {
381
            usort($profiles, ["UserAPI", "profileSort"]);
382
        }
383
        foreach ($profiles as $profile) {
384
            $returnArray[] = ['profile' => $profile->identifier, 'display' => $profile->name, 'idp_name' => $profile->instName, 'logo' => $hasLogo];
385
        }
386
        echo $this->return_json($returnArray);
387
    }
388
389
    /**
390
     * Return the list of devices available for the given profile
391
     *
392
     * @param int $profileId the Profile identifier
393
     * @return string JSON encoded data
394
     */
395
    public function JSON_listDevices($profileId) {
396
        $this->languageInstance->setTextDomain("web_user");
397
        $returnArray = [];
398
        $profileAttributes = $this->profileAttributes($profileId);
399
        $thedevices = $profileAttributes['devices'];
400
        if (!isset($profile_redirect) || !$profile_redirect) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $profile_redirect seems to never exist and therefore isset should always be false.
Loading history...
401
            $profile_redirect = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $profile_redirect is dead and can be removed.
Loading history...
402
            foreach ($thedevices as $D) {
403
                if (isset($D['options']) && isset($D['options']['hidden']) && $D['options']['hidden']) {
404
                    continue;
405
                }
406
                $disp = $D['display'];
407
                if ($D['device'] === '0') {
408
                    $profile_redirect = 1;
409
                    $disp = $c;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $c seems to be never defined.
Loading history...
410
                }
411
                $returnArray[] = ['device' => $D['id'], 'display' => $disp, 'status' => $D['status'], 'redirect' => $D['redirect']];
412
            }
413
        }
414
        echo $this->return_json($returnArray);
415
    }
416
417
    /**
418
     * Call installer generation and return the link
419
     *
420
     * @param string $device identifier as in {@link devices.php}
421
     * @param int $prof_id profile identifier
422
     * @return string JSON encoded data
423
     */
424
    public function JSON_generateInstaller($device, $prof_id) {
425
        $this->loggerInstance->debug(4, "JSON::generateInstaller arguments: $device,$prof_id\n");
426
        $output = $this->generateInstaller($device, $prof_id);
427
        $this->loggerInstance->debug(4, "output from GUI::generateInstaller:");
428
        $this->loggerInstance->debug(4, print_r($output, true));
429
        $this->loggerInstance->debug(4, json_encode($output));
430
//    header('Content-type: application/json; utf-8');
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
431
        echo $this->return_json($output);
432
    }
433
434
    /**
435
     * Generate and send the installer
436
     *
437
     * @param string $device identifier as in {@link devices.php}
438
     * @param int $prof_id profile identifier
439
     * @return string binary stream: installerFile
440
     */
441
    public function downloadInstaller($device, $prof_id, $generated_for = 'user', $token = NULL, $password = NULL) {
442
        $this->loggerInstance->debug(4, "downloadInstaller arguments: $device,$prof_id,$generated_for\n");
443
        $output = $this->generateInstaller($device, $prof_id, $generated_for, $token, $password);
444
        $this->loggerInstance->debug(4, "output from GUI::generateInstaller:");
445
        $this->loggerInstance->debug(4, print_r($output, true));
446
        if (!$output['link']) {
447
            header("HTTP/1.0 404 Not Found");
448
            return;
449
        }
450
        $validator = new \web\lib\common\InputValidation();
451
        $profile = $validator->Profile($prof_id);
452
        $profile->incrementDownloadStats($device, $generated_for);
453
        $file = $this->installerPath;
454
        $filetype = $output['mime'];
455
        $this->loggerInstance->debug(4, "installer MIME type:$filetype\n");
456
        header("Content-type: " . $filetype);
457
        header('Content-Disposition: inline; filename="' . basename($file) . '"');
458
        header('Content-Length: ' . filesize($file));
459
        ob_clean();
460
        flush();
461
        readfile($file);
462
    }
463
464
    private function processImage($inputImage, $destFile, $width, $height, $resize) {
465
        $info = new \finfo();
466
        $filetype = $info->buffer($inputImage, FILEINFO_MIME_TYPE);
467
        $offset = 60 * 60 * 24 * 30;
468
        // gmdate cannot fail here - time() is its default argument (and integer), and we are adding an integer to it
469
        $expiresString = "Expires: " . /** @scrutinizer ignore-type */ gmdate("D, d M Y H:i:s", time() + $offset) . " GMT";
470
        $blob = $inputImage;
471
472
        if ($resize) {
473
            $image = new \Imagick();
474
            $image->readImageBlob($inputImage);
475
            $image->setImageFormat('PNG');
476
            $image->thumbnailImage($width, $height, 1);
477
            $blob = $image->getImageBlob();
478
            $this->loggerInstance->debug(4, "Writing cached logo $destFile for IdP/Federation.\n");
479
            file_put_contents($destFile, $blob);
480
        }
481
        
482
        return ["filetype" => $filetype, "expires" => $expiresString, "blob" => $blob];
483
    }
484
485
    /**
486
     * Get and prepare logo file 
487
     *
488
     * When called for DiscoJuice, first check if file cache exists
489
     * If not then generate the file and save it in the cache
490
     * @param int $idp IdP identifier
491
     * @param int $disco flag turning on image generation for DiscoJuice
492
     * @param int $width maximum width of the generated image 
493
     * @param int $height  maximum height of the generated image
494
     * if one of these is 0 then it is treated as no upper bound
495
     *
496
     */
497
    
498 View Code Duplication
        public function getIdpLogo($idp, $width = 0, $height = 0) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
499
        $expiresString = '';
500
        $resize = 0;
501
        $logoFile = "";
502
        $validator = new \web\lib\common\InputValidation();
503
        $idpInstance = $validator->IdP($idp);
504
        $filetype = 'image/png'; // default, only one code path where it can become different
505
        if (($width || $height) && is_numeric($width) && is_numeric($height)) {
506
            $resize = 1;
507
            if ($height == 0) {
508
                $height = 10000;
509
            }
510
            if ($width == 0) {
511
                $width = 10000;
512
            }
513
            $logoFile = ROOT . '/web/downloads/logos/' . $idp . '_' . $width . '_' . $height . '.png';
514
        }
515
        if ($resize && is_file($logoFile)) {
516
            $this->loggerInstance->debug(4, "Using cached logo $logoFile for: " . $idp . "\n");
517
            $blob = file_get_contents($logoFile);
518
        } else {
519
            $logoAttribute = $idpInstance->getAttributes('general:logo_file');
520
            if (count($logoAttribute) == 0) {
521
                return;
522
            }
523
            $meta = $this->processImage($logoAttribute[0]['value'], $logoFile, $width, $height, $resize);
524
            $filetype = $meta['filetype'];
525
            $expiresString = $meta['expires'];
526
            $blob = $meta['blob'];
527
        }
528
        return ["filetype" => $filetype, "expires" => $expiresString, "blob" => $blob];
529
    }
530
531
    /**
532
     * Get and prepare logo file 
533
     *
534
     * When called for DiscoJuice, first check if file cache exists
535
     * If not then generate the file and save it in the cache
536
     * @param string $fedIdentifier federation identifier
537
     * @param int $width maximum width of the generated image 
538
     * @param int $height  maximum height of the generated image
539
     * if one of these is 0 then it is treated as no upper bound
540
     *
541
     */
542 View Code Duplication
    public function getFedLogo($fedIdentifier, $width = 0, $height = 0) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
543
        $expiresString = '';
544
        $resize = 0;
545
        $logoFile = "";
546
        $validator = new \web\lib\common\InputValidation();
547
        $federation = $validator->Federation($fedIdentifier);
548
        if (($width || $height) && is_numeric($width) && is_numeric($height)) {
549
            $resize = 1;
550
            if ($height == 0) {
551
                $height = 10000;
552
            }
553
            if ($width == 0) {
554
                $width = 10000;
555
            }
556
            $logoFile = ROOT . '/web/downloads/logos/' . $fedIdenifier . '_' . $width . '_' . $height . '.png';
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $fedIdenifier does not exist. Did you maybe mean $fedIdentifier?
Loading history...
557
        }
558
        if ($resize && is_file($logoFile)) {
559
            $this->loggerInstance->debug(4, "Using cached logo $logoFile for: " . $fedIdentifier . "\n");
560
            $blob = file_get_contents($logoFile);
561
            $filetype = 'image/png';
562
        } else {
563
            $logoAttribute = $federation->getAttributes('fed:logo_file');
564
            if (count($logoAttribute) == 0) {
565
                return;
566
            }
567
            $meta = $this->processImage($logoAttribute[0]['value'], $logoFile, $width, $height, $resize);
568
            $filetype = $meta['filetype'];
569
            $expiresString = $meta['expires'];
570
            $blob = $meta['blob'];
571
        }
572
        return ["filetype" => $filetype, "expires" => $expiresString, "blob" => $blob];
573
    }
574
575
576
    public function sendLogo($identifier, $type, $width = 0, $height = 0) {
577
        if ($type === "federation") {
578
            $logo = $this->getFedLogo($identifier, $width, $height);
579
        }
580
        if ($type === "idp") {
581
            $logo = $this->getIdpLogo($identifier, $width, $height);
582
        }
583
        header("Content-type: " . $logo['filetype']);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $logo does not seem to be defined for all execution paths leading up to this point.
Loading history...
584
        header("Cache-Control:max-age=36000, must-revalidate");
585
        header($logo['expires']);
586
        echo $logo['blob'];
587
    }
588
    
589
    public function locateUser() {
590 View Code Duplication
        if (CONFIG['GEOIP']['version'] != 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
591
            return ['status' => 'error', 'error' => 'Function for GEOIPv1 called, but config says this is not the version to use!'];
592
        }
593
        //$host = $_SERVER['REMOTE_ADDR'];
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
594
        $host = filter_input(INPUT_SERVER, 'REMOTE_ADDR', FILTER_VALIDATE_IP);
595
        $record = geoip_record_by_name($host);
596
        if ($record === FALSE) {
597
            return ['status' => 'error', 'error' => 'Problem listing countries'];
598
        }
599
        $result = ['status' => 'ok'];
600
        $result['country'] = $record['country_code'];
601
//  the two lines below are a dirty hack to take of the error in naming the UK federation
602
        if ($result['country'] == 'GB') {
603
            $result['country'] = 'UK';
604
        }
605
        $result['region'] = $record['region'];
606
        $result['geo'] = ['lat' => (float) $record['latitude'], 'lon' => (float) $record['longitude']];
607
        return($result);
608
    }
609
610
    public function locateUser2() {
611 View Code Duplication
        if (CONFIG['GEOIP']['version'] != 2) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
612
            return ['status' => 'error', 'error' => 'Function for GEOIPv2 called, but config says this is not the version to use!'];
613
        }
614
        require_once CONFIG['GEOIP']['geoip2-path-to-autoloader'];
615
        $reader = new Reader(CONFIG['GEOIP']['geoip2-path-to-db']);
616
        $host = $_SERVER['REMOTE_ADDR'];
617
        try {
618
            $record = $reader->city($host);
619
        } catch (\Exception $e) {
620
            $result = ['status' => 'error', 'error' => 'Problem listing countries'];
621
            return($result);
622
        }
623
        $result = ['status' => 'ok'];
624
        $result['country'] = $record->country->isoCode;
625
//  the two lines below are a dirty hack to take of the error in naming the UK federation
626
        if ($result['country'] == 'GB') {
627
            $result['country'] = 'UK';
628
        }
629
        $result['region'] = $record->continent->name;
630
631
        $result['geo'] = ['lat' => (float) $record->location->latitude, 'lon' => (float) $record->location->longitude];
632
        return($result);
633
    }
634
635
    public function JSON_locateUser() {
636
        header('Content-type: application/json; utf-8');
637
638
        $geoipVersion = CONFIG['GEOIP']['version'] ?? 0;
639
640
        switch ($geoipVersion) {
641
            case 0:
642
                echo json_encode(['status' => 'error', 'error' => 'Geolocation not supported']);
643
                break;
644
            case 1:
645
                echo json_encode($this->locateUser());
646
                break;
647
            case 2:
648
                echo json_encode($this->locateUser2());
649
                break;
650
            default:
651
                throw new Exception("This version of GeoIP is not known!");
652
        }
653
    }
654
655
    /**
656
     * Produce support data prepared within {@link GUI::profileAttributes()}
657
     * @return string JSON encoded data
658
     */
659
    public function JSON_profileAttributes($prof_id) {
660
//    header('Content-type: application/json; utf-8');
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
661
        echo $this->return_json($this->profileAttributes($prof_id));
662
    }
663
664
    /**
665
     * Calculate the distence in km between two points given their
666
     * geo coordinates.
667
     * @param array $point1 - first point as an 'lat', 'lon' array 
668
     * @param array $profile1 - second point as an 'lat', 'lon' array 
669
     * @return float distance in km
670
     */
671
    private function geoDistance($point1, $profile1) {
672
673
        $distIntermediate = sin(deg2rad($point1['lat'])) * sin(deg2rad($profile1['lat'])) +
674
                cos(deg2rad($point1['lat'])) * cos(deg2rad($profile1['lat'])) * cos(deg2rad($point1['lon'] - $profile1['lon']));
675
        $dist = rad2deg(acos($distIntermediate)) * 60 * 1.1852;
676
        return(round($dist));
677
    }
678
679
    /**
680
     * Order active identity providers according to their distance and name
681
     * @param array $currentLocation - current location
682
     * @return array $IdPs -  list of arrays ('id', 'name');
683
     */
684
    public function orderIdentityProviders($country, $currentLocation = NULL) {
685
        $idps = $this->listAllIdentityProviders(1, $country);
686
687
        if (is_null($currentLocation)) {
688
            $currentLocation = ['lat' => "90", 'lon' => "0"];
689
            $userLocation = $this->locateUser();
690
            if ($userLocation['status'] == 'ok') {
691
                $currentLocation = $userLocation['geo'];
692
            }
693
        }
694
        $idpTitle = [];
695
        $resultSet = [];
696
        foreach ($idps as $idp) {
697
            $idpTitle[$idp['entityID']] = $idp['title'];
698
            $dist = 10000;
699
            if (isset($idp['geo'])) {
700
                $G = $idp['geo'];
701
                if (isset($G['lon'])) {
702
                    $d1 = $this->geoDistance($currentLocation, $G);
703
                    if ($d1 < $dist) {
704
                        $dist = $d1;
705
                    }
706
                } else {
707
                    foreach ($G as $g) {
708
                        $d1 = $this->geoDistance($currentLocation, $g);
709
                        if ($d1 < $dist) {
710
                            $dist = $d1;
711
                        }
712
                    }
713
                }
714
            }
715
            if ($dist > 100) {
716
                $dist = 10000;
717
            }
718
            $d = sprintf("%06d", $dist);
719
            $resultSet[$idp['entityID']] = $d . " " . $idp['title'];
720
        }
721
        asort($resultSet);
722
        $outarray = [];
723
        foreach (array_keys($resultSet) as $r) {
724
            $outarray[] = ['idp' => $r, 'title' => $idpTitle[$r]];
725
        }
726
        return($outarray);
727
    }
728
729
    /**
730
     * Detect the best device driver form the browser
731
     *
732
     * Detects the operating system and returns its id 
733
     * display name and group membership (as in devices.php)
734
     * @return array|false OS information, indexed by 'id', 'display', 'group'
735
     */
736
    public function detectOS() {
737
        $oldDomain = $this->languageInstance->setTextDomain("devices");
738
        $Dev = \devices\Devices::listDevices();
739
        $this->languageInstance->setTextDomain($oldDomain);
740
        if (isset($_REQUEST['device']) && isset($Dev[$_REQUEST['device']]) && (!isset($device['options']['hidden']) || $device['options']['hidden'] == 0)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $device seems to never exist and therefore isset should always be false.
Loading history...
741
            $dev_id = $_REQUEST['device'];
742
            $device = $Dev[$dev_id];
743
            return(['device' => $dev_id, 'display' => $device['display'], 'group' => $device['group']]);
744
        }
745
        $browser = $_SERVER['HTTP_USER_AGENT'];
746
        $this->loggerInstance->debug(4, "HTTP_USER_AGENT=$browser\n");
747
        foreach ($Dev as $dev_id => $device) {
748
            if (!isset($device['match'])) {
749
                continue;
750
            }
751
            if (preg_match('/' . $device['match'] . '/', $browser)) {
752
                if (!isset($device['options']['hidden']) || $device['options']['hidden'] == 0) {
753
                    $this->loggerInstance->debug(4, "Browser_id: $dev_id\n");
754
                    return(['device' => $dev_id, 'display' => $device['display'], 'group' => $device['group']]);
755
                } else {
756
                    $this->loggerInstance->debug(2, "Unrecognised system: " . filter_input(INPUT_SERVER, 'HTTP_USER_AGENT', FILTER_SANITIZE_STRING) . "\n");
757
                    return(false);
758
                }
759
            }
760
        }
761
        $this->loggerInstance->debug(2, "Unrecognised system: " . filter_input(INPUT_SERVER, 'HTTP_USER_AGENT', FILTER_SANITIZE_STRING) . "\n");
762
        return(false);
763
    }
764
765
    public function JSON_detectOS() {
766
        $returnArray = $this->detectOS();
767
        if (is_array($returnArray)) {
768
            $status = 1;
769
        } else {
770
            $status = 0;
771
        }
772
        echo $this->return_json($returnArray, $status);
773
    }
774
    
775
    public function getUserCerts($token) {
776
        $validator = new \web\lib\common\InputValidation();
777
        $cleanToken = $validator->token($token);
778
        if ($cleanToken) {
779
            // check status of this silverbullet token according to info in DB:
780
            // it can be VALID (exists and not redeemed, EXPIRED, REDEEMED or INVALID (non existent)
781
            $tokenStatus = \core\ProfileSilverbullet::tokenStatus($cleanToken);
782
        } else {
783
            return false;
784
        }
785
        $profile = new \core\ProfileSilverbullet($tokenStatus['profile'], NULL);
786
        $userdata = $profile->userStatus($tokenStatus['db_id']);
787
        $allcerts = [];
788
        foreach ($userdata as $index => $content) {
789
            $allcerts = array_merge($allcerts, $content['cert_status']);
790
        }
791
        return $allcerts;
792
    }
793
794
    public function JSON_getUserCerts($token) {
795
        $returnArray = $this->getUserCerts($token);
796
        if ($returnArray) {
797
            $status = 1;
798
        } else {
799
            $status = 0;
800
        }
801
        echo $this->return_json($returnArray, $status);
802
    }
803
804
    public $device;
805
    private $installerPath;
806
807
    private static function profileSort($profile1, $profile2) {
808
        return strcasecmp($profile1->name, $profile2->name);
809
    }
810
811
}
812