Passed
Push — master ( 86c8ba...4a8f0a )
by Tomasz
20:05 queued 07:20
created

UserAPI::setCurrentLocation()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 3
nop 1
dl 0
loc 10
rs 9.4285
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
 * @author Stefan Winter <[email protected]>
16
 * @package UserAPI
17
 *
18
 * Parts of this code are based on simpleSAMLPhp discojuice module.
19
 * This product includes GeoLite data created by MaxMind, available from
20
 * http://www.maxmind.com
21
 */
22
23
namespace core;
24
25
use GeoIp2\Database\Reader;
26
use \Exception;
27
28
/**
29
 * The basic methoods for the user GUI
30
 * @package UserAPI
31
 *
32
 */
33
class UserAPI extends CAT {
34
35
    /**
36
     * nothing special to be done here.
37
     */
38
    public function __construct() {
39
        parent::__construct();
40
    }
41
42
    /**
43
     * Prepare the device module environment and send back the link
44
     * This method creates a device module instance via the {@link DeviceFactory} call, 
45
     * then sets up the device module environment for the specific profile by calling 
46
     * {@link DeviceConfig::setup()} method and finally, called the devide writeInstaller meethod
47
     * passing the returned path name.
48
     * 
49
     * @param string $device identifier as in {@link devices.php}
50
     * @param int $profileId profile identifier
51
     *
52
     * @return array 
53
     *  array with the following fields: 
54
     *  profile - the profile identifier; 
55
     *  device - the device identifier; 
56
     *  link - the path name of the resulting installer
57
     *  mime - the mimetype of the installer
58
     */
59
    public function generateInstaller($device, $profileId, $generatedFor = "user", $token = NULL, $password = NULL) {
60
        $this->languageInstance->setTextDomain("devices");
61
        $this->loggerInstance->debug(4, "installer:$device:$profileId\n");
62
        $validator = new \web\lib\common\InputValidation();
63
        $profile = $validator->Profile($profileId);
64
        // test if the profile is production-ready and if not if the authenticated user is an owner
65
        if ($this->verifyDownloadAccess($profile) === FALSE) {
66
            return;
67
        }
68
        $installerProperties = [];
69
        $installerProperties['profile'] = $profileId;
70
        $installerProperties['device'] = $device;
71
        $cache = $this->getCache($device, $profile);
72
        $this->installerPath = $cache['path'];
73
        if ($this->installerPath && $token == NULL && $password == NULL) {
74
            $this->loggerInstance->debug(4, "Using cached installer for: $device\n");
75
            $installerProperties['link'] = "API.php?action=downloadInstaller&lang=" . $this->languageInstance->getLang() . "&profile=$profileId&device=$device&generatedfor=$generatedFor";
76
            $installerProperties['mime'] = $cache['mime'];
77
        } else {
78
            $myInstaller = $this->generateNewInstaller($device, $profile, $generatedFor, $token, $password);
79
            if ($myInstaller['link'] !== 0) {
80
                $installerProperties['mime'] = $myInstaller['mime'];
81
            }
82
            $installerProperties['link'] = $myInstaller['link'];
83
        }
84
        $this->languageInstance->setTextDomain("web_user");
85
        return($installerProperties);
86
    }
87
    
88
    private function verifyDownloadAccess($profile) {
89
        $attribs = $profile->getCollapsedAttributes();
90
        if (\core\common\Entity::getAttributeValue($attribs, 'profile:production', 0) !== 'on') {
91
            $this->loggerInstance->debug(4, "Attempt to download a non-production ready installer for profile: $profile->identifier\n");
92
            $auth = new \web\lib\admin\Authentication();
93
            if (!$auth->isAuthenticated()) {
94
                $this->loggerInstance->debug(2, "User NOT authenticated, rejecting request for a non-production installer\n");
95
                header("HTTP/1.0 403 Not Authorized");
96
                return(FALSE);
97
            }
98
            $userObject = new User($_SESSION['user']);
99
            if (!$userObject->isIdPOwner($profile->institution)) {
100
                $this->loggerInstance->debug(2, "User not an owner of a non-production profile - access forbidden\n");
101
                header("HTTP/1.0 403 Not Authorized");
102
                return(FALSE);
103
            }
104
            $this->loggerInstance->debug(4, "User is the owner - allowing access\n");
105
        }
106
        return(TRUE);
107
    }
108
109
    /**
110
     * This function tries to find a cached copy of an installer for a given
111
     * combination of Profile and device
112
     * @param string $device
113
     * @param AbstractProfile $profile
114
     * @return boolean|string the string with the path to the cached copy, or FALSE if no cached copy exists
115
     */
116
    private function getCache($device, $profile) {
117
        $deviceList = \devices\Devices::listDevices();
118
        $deviceConfig = $deviceList[$device];
119
        $noCache = (isset(\devices\Devices::$Options['no_cache']) && \devices\Devices::$Options['no_cache']) ? 1 : 0;
120
        if (isset($deviceConfig['options']['no_cache'])) {
121
            $noCache = $deviceConfig['options']['no_cache'] ? 1 : 0;
122
        }
123
        if ($noCache) {
124
            $this->loggerInstance->debug(5, "getCache: the no_cache option set for this device\n");
125
            return(FALSE);
126
        }
127
        $this->loggerInstance->debug(5, "getCache: caching option set for this device\n");
128
        $cache = $profile->testCache($device);
129
        $iPath = $cache['cache'];
130
        if ($iPath && is_file($iPath)) {
131
            return(['path' => $iPath, 'mime' => $cache['mime']]);
132
        }
133
        return(FALSE);
134
    }
135
136
    /**
137
     * Generates a new installer for the given combination of device and Profile
138
     * 
139
     * @param string $device
140
     * @param AbstractProfile $profile
141
     * @return array info about the new installer (mime and link)
142
     */
143
    private function generateNewInstaller($device, $profile, $generatedFor, $token, $password) {
144
        $this->loggerInstance->debug(5, "generateNewInstaller() - Enter");
145
        $factory = new DeviceFactory($device);
146
        $this->loggerInstance->debug(5, "generateNewInstaller() - created Device");
147
        $dev = $factory->device;
148
        $out = [];
149
        if (isset($dev)) {
150
            $dev->setup($profile, $token, $password);
151
            $this->loggerInstance->debug(5, "generateNewInstaller() - Device setup done");
152
            $installer = $dev->writeInstaller();
153
            $this->loggerInstance->debug(5, "generateNewInstaller() - writeInstaller complete");
154
            $iPath = $dev->FPATH . '/tmp/' . $installer;
155
            if ($iPath && is_file($iPath)) {
156
                if (isset($dev->options['mime'])) {
157
                    $out['mime'] = $dev->options['mime'];
158
                } else {
159
                    $info = new \finfo();
160
                    $out['mime'] = $info->file($iPath, FILEINFO_MIME_TYPE);
161
                }
162
                $this->installerPath = $dev->FPATH . '/' . $installer;
163
                rename($iPath, $this->installerPath);
164
                $integerEap = (new \core\common\EAP($dev->selectedEap))->getIntegerRep();
165
                $profile->updateCache($device, $this->installerPath, $out['mime'], $integerEap);
166
                if (CONFIG['DEBUG_LEVEL'] < 4) {
167
                    \core\common\Entity::rrmdir($dev->FPATH . '/tmp');
168
                }
169
                $this->loggerInstance->debug(4, "Generated installer: " . $this->installerPath . ": for: $device, EAP:" . $integerEap . "\n");
170
                $out['link'] = "API.php?action=downloadInstaller&lang=" . $this->languageInstance->getLang() . "&profile=" . $profile->identifier . "&device=$device&generatedfor=$generatedFor";
171
            } else {
172
                $this->loggerInstance->debug(2, "Installer generation failed for: " . $profile->identifier . ":$device:" . $this->languageInstance->getLang() . "\n");
173
                $out['link'] = 0;
174
            }
175
        }
176
        return($out);
177
    }
178
179
    /**
180
     * interface to Devices::listDevices() 
181
     */
182
    public function listDevices($showHidden = 0) {
183
        $dev = \devices\Devices::listDevices();
184
        $returnList = [];
185
        $count = 0;
186
        if ($showHidden !== 0 && $showHidden != 1) {
187
            throw new Exception("show_hidden is only be allowed to be 0 or 1, but it is $showHidden!");
188
        }
189
        foreach ($dev as $device => $deviceProperties) {
190
            if (\core\common\Entity::getAttributeValue($deviceProperties, 'options', 'hidden') === 1 && $showHidden === 0) {
191
                continue;
192
            }
193
            $count++;
194
            $deviceProperties['device'] = $device;
195
            $group = isset($deviceProperties['group']) ? $deviceProperties['group'] : 'other';
196
            if (!isset($returnList[$group])) {
197
                $returnList[$group] = [];
198
            }
199
            $returnList[$group][$device] = $deviceProperties;
200
        }
201
        return $returnList;
202
    }
203
204
    /**
205
     * 
206
     * @param string $device
207
     * @param int $profileId
208
     */
209
    public function deviceInfo($device, $profileId) {
210
        $this->languageInstance->setTextDomain("devices");
211
        $validator = new \web\lib\common\InputValidation();
212
        $out = 0;
213
        $profile = $validator->Profile($profileId);
214
        $factory = new DeviceFactory($device);
215
        $dev = $factory->device;
216
        if (isset($dev)) {
217
            $dev->setup($profile);
218
            $out = $dev->writeDeviceInfo();
219
        }
220
        $this->languageInstance->setTextDomain("web_user");
221
        echo $out;
222
    }
223
224
    /**
225
     * Prepare the support data for a given profile
226
     *
227
     * @param int $profId profile identifier
228
     * @return array
229
     * array with the following fields:
230
     * - local_email
231
     * - local_phone
232
     * - local_url
233
     * - description
234
     * - devices - an array of device names and their statuses (for a given profile)
235
     */
236
    public function profileAttributes($profId) {
237
        $this->languageInstance->setTextDomain("devices");
238
        $validator = new \web\lib\common\InputValidation();
239
        $profile = $validator->Profile($profId);
240
        $attribs = $profile->getCollapsedAttributes();
241
        $returnArray = [];
242
        $returnArray['silverbullet'] = $profile instanceof ProfileSilverbullet ? 1 : 0;
243
        if (isset($attribs['support:email'])) {
244
            $returnArray['local_email'] = $attribs['support:email'][0];
245
        }
246
        if (isset($attribs['support:phone'])) {
247
            $returnArray['local_phone'] = $attribs['support:phone'][0];
248
        }
249
        if (isset($attribs['support:url'])) {
250
            $returnArray['local_url'] = $attribs['support:url'][0];
251
        }
252
        if (isset($attribs['profile:description'])) {
253
            $returnArray['description'] = $attribs['profile:description'][0];
254
        }
255
        $returnArray['devices'] = $profile->listDevices();
256
        $this->languageInstance->setTextDomain("web_user");
257
        return($returnArray);
258
    }
259
260
    /**
261
     * Generate and send the installer
262
     *
263
     * @param string $device identifier as in {@link devices.php}
264
     * @param int $prof_id profile identifier
265
     * @return string binary stream: installerFile
266
     */
267
    public function downloadInstaller($device, $prof_id, $generated_for = 'user', $token = NULL, $password = NULL) {
268
        $this->loggerInstance->debug(4, "downloadInstaller arguments: $device,$prof_id,$generated_for\n");
269
        $output = $this->generateInstaller($device, $prof_id, $generated_for, $token, $password);
270
        $this->loggerInstance->debug(4, "output from GUI::generateInstaller:");
271
        $this->loggerInstance->debug(4, print_r($output, true));
272
        if (empty($output['link']) || $output['link'] === 0) {
273
            header("HTTP/1.0 404 Not Found");
274
            return;
275
        }
276
        $validator = new \web\lib\common\InputValidation();
277
        $profile = $validator->Profile($prof_id);
278
        $profile->incrementDownloadStats($device, $generated_for);
279
        $file = $this->installerPath;
280
        $filetype = $output['mime'];
281
        $this->loggerInstance->debug(4, "installer MIME type:$filetype\n");
282
        header("Content-type: " . $filetype);
283
        header('Content-Disposition: inline; filename="' . basename($file) . '"');
284
        header('Content-Length: ' . filesize($file));
285
        ob_clean();
286
        flush();
287
        readfile($file);
288
    }
289
290
    /**
291
     * resizes image files
292
     * 
293
     * @param string $inputImage
294
     * @param string $destFile
295
     * @param int $width
296
     * @param int $height
297
     * @param bool $resize shall we do resizing? width and height are ignored otherwise
298
     * @return array
299
     */
300
    private function processImage($inputImage, $destFile, $width, $height, $resize) {
301
        $info = new \finfo();
302
        $filetype = $info->buffer($inputImage, FILEINFO_MIME_TYPE);
303
        $offset = 60 * 60 * 24 * 30;
304
        // gmdate cannot fail here - time() is its default argument (and integer), and we are adding an integer to it
305
        $expiresString = "Expires: " . /** @scrutinizer ignore-type */ gmdate("D, d M Y H:i:s", time() + $offset) . " GMT";
306
        $blob = $inputImage;
307
308
        if ($resize === TRUE) {
309
            $image = new \Imagick();
310
            $image->readImageBlob($inputImage);
311
            $image->setImageFormat('PNG');
312
            $image->thumbnailImage($width, $height, 1);
313
            $blob = $image->getImageBlob();
314
            $this->loggerInstance->debug(4, "Writing cached logo $destFile for IdP/Federation.\n");
315
            file_put_contents($destFile, $blob);
316
        }
317
318
        return ["filetype" => $filetype, "expires" => $expiresString, "blob" => $blob];
319
    }
320
321
    /**
322
     * Get and prepare logo file 
323
     *
324
     * When called for DiscoJuice, first check if file cache exists
325
     * If not then generate the file and save it in the cache
326
     * @param int $identifier IdP of Federation identifier
327
     * @param string either 'idp' or 'federation' is allowed 
328
     * @param int $width maximum width of the generated image - if 0 then it is treated as no upper bound
329
     * @param int $height  maximum height of the generated image - if 0 then it is treated as no upper bound
330
     * @return array|null array with image information or NULL if there is no logo
331
     */
332
    protected function getLogo($identifier, $type, $width = 0, $height = 0) {
333
        $expiresString = '';
334
        $resize = FALSE;
335
        $attributeName = [
336
            'federation' => "fed:logo_file",
337
            'idp' => "general:logo_file",
338
        ];
339
        
340
        $logoFile = "";
341
        $validator = new \web\lib\common\InputValidation();
342
        switch ($type) {
343
            case "federation":
344
                $entity = $validator->Federation($identifier);
345
                break;
346
            case "idp":
347
                $entity = $validator->IdP($identifier);
348
                break;
349
            default:
350
                throw new Exception("Unknown type of logo requested!");
351
        }
352
        $filetype = 'image/png'; // default, only one code path where it can become different
353
        list($width, $height, $resize) = $this->testForResize($width, $height);
354
        if ($resize) {
355
            $logoFile = ROOT . '/web/downloads/logos/' . $identifier . '_' . $width . '_' . $height . '.png';
356
        }
357
        if (is_file($logoFile)) { // $logoFile could be an empty string but then we will get a FALSE
358
            $this->loggerInstance->debug(4, "Using cached logo $logoFile for: $identifier\n");
359
            $blob = file_get_contents($logoFile);
360
        } else {
361
            $logoAttribute = $entity->getAttributes($attributeName[$type]);
362
            if (count($logoAttribute) == 0) {
363
                return(NULL);
364
            }
365
            $this->loggerInstance->debug(4,"RESIZE:$width:$height\n");
366
            $meta = $this->processImage($logoAttribute[0]['value'], $logoFile, $width, $height, $resize);
367
            $filetype = $meta['filetype'];
368
            $expiresString = $meta['expires'];
369
            $blob = $meta['blob'];
370
        }
371
        return ["filetype" => $filetype, "expires" => $expiresString, "blob" => $blob];
372
    }
373
    
374
    private function testForResize($width, $height) {
375
        if (is_numeric($width) && is_numeric($height) && ($width > 0 || $height > 0)) {
376
            if ($height == 0) {
377
                $height = 10000;
378
            }
379
            if ($width == 0) {
380
                $width = 10000;
381
            }
382
            $resize = TRUE;
383
        } else {
384
            $width = 0;
385
            $height = 0;
386
            $resize = FALSE;
387
        }
388
        return ([$width, $height, $resize]);
389
    }
390
391
    /**
392
     * find out where the user is currently located
393
     * @return array
394
     */
395
    public function locateUser() {
396
        $geoipVersion = CONFIG['GEOIP']['version'] ?? 0;
397
        switch ($geoipVersion) {
398
            case 0:
399
                return(['status' => 'error', 'error' => 'Geolocation not supported']);
400
            case 1:
401
                return($this->locateUser1());
402
            case 2:
403
                return($this->locateUser2());
404
            default:
405
                throw new Exception("This version of GeoIP is not known!");
406
        }
407
    }
408
    
409
    private function locateUser1() {
410 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...
411
            return ['status' => 'error', 'error' => 'Function for GEOIPv1 called, but config says this is not the version to use!'];
412
        }
413
        //$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...
414
        $host = filter_input(INPUT_SERVER, 'REMOTE_ADDR', FILTER_VALIDATE_IP);
415
        $record = geoip_record_by_name($host);
416
        if ($record === FALSE) {
417
            return ['status' => 'error', 'error' => 'Problem listing countries'];
418
        }
419
        $result = ['status' => 'ok'];
420
        $result['country'] = $record['country_code'];
421
//  the two lines below are a dirty hack to take of the error in naming the UK federation
422
        if ($result['country'] == 'GB') {
423
            $result['country'] = 'UK';
424
        }
425
        $result['region'] = $record['region'];
426
        $result['geo'] = ['lat' => (float) $record['latitude'], 'lon' => (float) $record['longitude']];
427
        return($result);
428
    }
429
    
430
    /**
431
     * find out where the user is currently located, using GeoIP2
432
     * @return array
433
     */
434
    private function locateUser2() {
435 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...
436
            return ['status' => 'error', 'error' => 'Function for GEOIPv2 called, but config says this is not the version to use!'];
437
        }
438
        require_once CONFIG['GEOIP']['geoip2-path-to-autoloader'];
439
        $reader = new Reader(CONFIG['GEOIP']['geoip2-path-to-db']);
440
        $host = $_SERVER['REMOTE_ADDR'];
441
        try {
442
            $record = $reader->city($host);
443
        } catch (\Exception $e) {
444
            $result = ['status' => 'error', 'error' => 'Problem listing countries'];
445
            return($result);
446
        }
447
        $result = ['status' => 'ok'];
448
        $result['country'] = $record->country->isoCode;
449
//  the two lines below are a dirty hack to take of the error in naming the UK federation
450
        if ($result['country'] == 'GB') {
451
            $result['country'] = 'UK';
452
        }
453
        $result['region'] = $record->continent->name;
454
455
        $result['geo'] = ['lat' => (float) $record->location->latitude, 'lon' => (float) $record->location->longitude];
456
        return($result);
457
    }
458
459
    /**
460
     * Calculate the distance in km between two points given their
461
     * geo coordinates.
462
     * @param array $point1 - first point as an 'lat', 'lon' array 
463
     * @param array $profile1 - second point as an 'lat', 'lon' array 
464
     * @return float distance in km
465
     */
466
    private function geoDistance($point1, $profile1) {
467
468
        $distIntermediate = sin(deg2rad($point1['lat'])) * sin(deg2rad($profile1['lat'])) +
469
                cos(deg2rad($point1['lat'])) * cos(deg2rad($profile1['lat'])) * cos(deg2rad($point1['lon'] - $profile1['lon']));
470
        $dist = rad2deg(acos($distIntermediate)) * 60 * 1.1852;
471
        return(round($dist));
472
    }
473
474
    /**
475
     * Order active identity providers according to their distance and name
476
     * @param array $currentLocation - current location
477
     * @return array $IdPs -  list of arrays ('id', 'name');
478
     */
479
    public function orderIdentityProviders($country, $currentLocation = NULL) {
0 ignored issues
show
Unused Code introduced by
The parameter $currentLocation is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
480
        $idps = $this->listAllIdentityProviders(1, $country);
481
        $here = $this->setCurrentLocation(setCurrentLocation);
482
        $idpTitle = [];
483
        $resultSet = [];
484
        foreach ($idps as $idp) {
485
            $idpTitle[$idp['entityID']] = $idp['title'];
486
            $d = $this->getIdpDistance($idp, $here);
487
            $resultSet[$idp['entityID']] = $d . " " . $idp['title'];
488
        }
489
        asort($resultSet);
490
        $outarray = [];
491
        foreach (array_keys($resultSet) as $r) {
492
            $outarray[] = ['idp' => $r, 'title' => $idpTitle[$r]];
493
        }
494
        return($outarray);
495
    }
496
497
    private function setCurrentLocation($currentLocation) {
498
        if (is_null($currentLocation)) {
499
            $currentLocation = ['lat' => "90", 'lon' => "0"];
500
            $userLocation = $this->locateUser();
501
            if ($userLocation['status'] == 'ok') {
502
                $currentLocation = $userLocation['geo'];
503
            }
504
        }
505
        return($currentLocation);
506
    }
507
    
508
    private function getIdpDistance($idp, $location) {
509
        $dist = 10000;
510
        if (isset($idp['geo'])) {
511
            $G = $idp['geo'];
512
            if (isset($G['lon'])) {
513
                $d1 = $this->geoDistance($location, $G);
514
                if ($d1 < $dist) {
515
                    $dist = $d1;
516
                }
517
            } else {
518
                foreach ($G as $g) {
519
                    $d1 = $this->geoDistance($location, $g);
520
                    if ($d1 < $dist) {
521
                        $dist = $d1;
522
                    }
523
                }
524
            }
525
        }
526
        if ($dist > 100) {
527
            $dist = 10000;
528
        }
529
        return(sprintf("%06d", $dist));
530
    }
531
532
    /**
533
     * Detect the best device driver form the browser
534
     * Detects the operating system and returns its id 
535
     * display name and group membership (as in devices.php)
536
     * @return array|FALSE OS information, indexed by 'id', 'display', 'group'
537
     */
538
    public function detectOS() {
539
        $oldDomain = $this->languageInstance->setTextDomain("devices");
540
        $Dev = \devices\Devices::listDevices();
541
        $this->languageInstance->setTextDomain($oldDomain);
542
        $devId = $this->deviceFromRequest();
543
        if ($devId !== FALSE) {
544
            $ret = $this->returnDevice($devId, $Dev[$devId]);
545
            if ($ret !== FALSE) {
546
                return($ret);
547
            } 
548
        }
549
// the device has not been specified or not specified correctly, try to detect if from the browser ID
550
        $browser = filter_input(INPUT_SERVER, 'HTTP_USER_AGENT', FILTER_SANITIZE_STRING);
551
        $this->loggerInstance->debug(4, "HTTP_USER_AGENT=$browser\n");
552
        foreach ($Dev as $devId => $device) {
553
            if (!isset($device['match'])) {
554
                continue;
555
            }
556
            if (preg_match('/' . $device['match'] . '/', $browser)) {
557
                return ($this->returnDevice($devId, $device));
558
            }
559
        }
560
        $this->loggerInstance->debug(2, "Unrecognised system: $browser\n");
561
        return(false);
562
    }
563
    
564
    /*
565
     * test if devise is defined and is not hidden. If all is fine return extracted information.
566
     * Return FALSE if the device has not been correctly specified
567
     */
568
    private function returnDevice($devId, $device) {
569
        if (\core\common\Entity::getAttributeValue($device, 'options', 'hidden') !== 1) {
570
            $this->loggerInstance->debug(4, "Browser_id: $devId\n");
571
            return(['device' => $devId, 'display' => $device['display'], 'group' => $device['group']]);
572
        }
573
        return(FALSE);
574
    }
575
   
576
    /**
577
     * This methods cheks if the devide has been specified as the HTTP parameters
578
     * @return device id|FALSE if correcty specified or FALSE otherwise
579
     */
580
    private function deviceFromRequest() {
581
        $devId = filter_input(INPUT_GET, 'device', FILTER_SANITIZE_STRING) ?? filter_input(INPUT_POST, 'device', FILTER_SANITIZE_STRING);
582
        if ($devId === NULL || $devId === FALSE) {
583
            $this->loggerInstance->debug(2, "Invalid device id provided\n");
584
            return(FALSE);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return FALSE; (false) is incompatible with the return type documented by core\UserAPI::deviceFromRequest of type core\device.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
585
        }
586
        $Dev = \devices\Devices::listDevices();
587
        if (!isset($Dev['$devId'])) {
588
            $this->loggerInstance->debug(2, "Unrecognised system: $devId\n");
589
            return(FALSE);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return FALSE; (false) is incompatible with the return type documented by core\UserAPI::deviceFromRequest of type core\device.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
590
        }
591
        return($devId);
592
    }
593
594
    /**
595
     * finds all the user certificates that originated in a given token
596
     * @param string $token
597
     * @return array|false returns FALSE if a token is invalid, otherwise array of certs
598
     */
599
    public function getUserCerts($token) {
600
        $validator = new \web\lib\common\InputValidation();
601
        $cleanToken = $validator->token($token);
602
        if ($cleanToken) {
603
            // check status of this silverbullet token according to info in DB:
604
            // it can be VALID (exists and not redeemed, EXPIRED, REDEEMED or INVALID (non existent)
605
            $tokenStatus = \core\ProfileSilverbullet::tokenStatus($cleanToken);
606
        } else {
607
            return false;
608
        }
609
        $profile = new \core\ProfileSilverbullet($tokenStatus['profile'], NULL);
610
        $userdata = $profile->userStatus($tokenStatus['db_id']);
611
        $allcerts = [];
612
        foreach ($userdata as $index => $content) {
613
            $allcerts = array_merge($allcerts, $content['cert_status']);
614
        }
615
        return $allcerts;
616
    }
617
618
619
    public $device;
620
    private $installerPath;
621
622
    /**
623
     * helper function to sort profiles by their name
624
     * @param \core\AbstractProfile $profile1 the first profile's information
625
     * @param \core\AbstractProfile $profile2 the second profile's information
626
     * @return int
627
     */
628
    private static function profileSort($profile1, $profile2) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
629
        return strcasecmp($profile1->name, $profile2->name);
630
    }
631
632
}
633