Passed
Push — master ( 3d1824...01304e )
by Tomasz
03:12
created

UserAPI::detectOS()   D

Complexity

Conditions 10
Paths 6

Size

Total Lines 28
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 22
nc 6
nop 0
dl 0
loc 28
rs 4.8196
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
Bug introduced by
The variable $cache does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
87
        } else {
88
            $myInstaller = $this->generateNewInstaller($device, $profile, $generatedFor, $token, $password);
89
            $installerProperties['mime'] = $myInstaller['mime'];
90
            $installerProperties['link'] = $myInstaller['link'];
91
        }
92
        $this->languageInstance->setTextDomain("web_user");
93
        return($installerProperties);
94
    }
95
96
    /**
97
     * This function tries to find a cached copy of an installer for a given
98
     * combination of Profile and device
99
     * @param string $device
100
     * @param AbstractProfile $profile
101
     * @return boolean|string the string with the path to the cached copy, or FALSE if no cached copy exists
102
     */
103
    private function getCachedPath($device, $profile) {
104
        $deviceList = \devices\Devices::listDevices();
105
        $deviceConfig = $deviceList[$device];
106
        $noCache = (isset(\devices\Devices::$Options['no_cache']) && \devices\Devices::$Options['no_cache']) ? 1 : 0;
107
        if (isset($deviceConfig['options']['no_cache'])) {
108
            $noCache = $deviceConfig['options']['no_cache'] ? 1 : 0;
109
        }
110
        if ($noCache) {
111
            $this->loggerInstance->debug(5, "getCachedPath: the no_cache option set for this device\n");
112
            return(FALSE);
113
        }
114
        $this->loggerInstance->debug(5, "getCachedPath: caching option set for this device\n");
115
        $cache = $profile->testCache($device);
116
        $iPath = $cache['cache'];
117
        if ($iPath && is_file($iPath)) {
118
            return($iPath);
119
        }
120
        return(FALSE);
121
    }
122
123
    /**
124
     * Generates a new installer for the given combination of device and Profile
125
     * 
126
     * @param string $device
127
     * @param AbstractProfile $profile
128
     * @return array info about the new installer (mime and link)
129
     */
130
    private function generateNewInstaller($device, $profile, $generatedFor, $token, $password) {
131
        $this->loggerInstance->debug(5, "generateNewInstaller() - Enter");
132
        $factory = new DeviceFactory($device);
133
        $this->loggerInstance->debug(5, "generateNewInstaller() - created Device");
134
        $dev = $factory->device;
135
        $out = [];
136
        if (isset($dev)) {
137
            $dev->setup($profile, $token, $password);
138
            $this->loggerInstance->debug(5, "generateNewInstaller() - Device setup done");
139
            $installer = $dev->writeInstaller();
0 ignored issues
show
Bug introduced by
The method writeInstaller() cannot be called from this context as it is declared protected in class core\DeviceConfig.

This check looks for access to methods that are not accessible from the current context.

If you need to make a method accessible to another context you can raise its visibility level in the defining class.

Loading history...
140
            $this->loggerInstance->debug(5, "generateNewInstaller() - writeInstaller complete");
141
            $iPath = $dev->FPATH . '/tmp/' . $installer;
142
            if ($iPath && is_file($iPath)) {
143
                if (isset($dev->options['mime'])) {
144
                    $out['mime'] = $dev->options['mime'];
145
                } else {
146
                    $info = new \finfo();
147
                    $out['mime'] = $info->file($iPath, FILEINFO_MIME_TYPE);
148
                }
149
                $this->installerPath = $dev->FPATH . '/' . $installer;
150
                rename($iPath, $this->installerPath);
151
                $integerEap = (new \core\common\EAP($dev->selectedEap))->getIntegerRep();
152
                $profile->updateCache($device, $this->installerPath, $out['mime'], $integerEap);
153
                if (CONFIG['DEBUG_LEVEL'] < 4) {
154
                    \core\common\Entity::rrmdir($dev->FPATH . '/tmp');
155
                }
156
                $this->loggerInstance->debug(4, "Generated installer: " . $this->installerPath . ": for: $device, EAP:" . $integerEap . "\n");
157
                $out['link'] = "API.php?action=downloadInstaller&lang=" . $this->languageInstance->getLang() . "&profile=" . $profile->identifier . "&device=$device&generatedfor=$generatedFor";
158
            } else {
159
                $this->loggerInstance->debug(2, "Installer generation failed for: " . $profile->identifier . ":$device:" . $this->languageInstance->getLang() . "\n");
160
                $out['link'] = 0;
161
            }
162
        }
163
        return($out);
164
    }
165
166
    /**
167
     * interface to Devices::listDevices() 
168
     */
169
    public function listDevices($showHidden = 0) {
170
        $dev = \devices\Devices::listDevices();
171
        $returnList = [];
172
        $count = 0;
173
        if ($showHidden !== 0 && $showHidden != 1) {
174
            throw new Exception("show_hidden is only be allowed to be 0 or 1, but it is $showHidden!");
175
        }
176
        foreach ($dev as $device => $deviceProperties) {
177
            if (isset($deviceProperties['options']['hidden']) && $deviceProperties['options']['hidden'] && $showHidden == 0) {
178
                continue;
179
            }
180
            $count ++;
181
182
            $deviceProperties['device'] = $device;
183
184
            $group = isset($deviceProperties['group']) ? $deviceProperties['group'] : 'other';
185
            if (!isset($returnList[$group])) {
186
                $returnList[$group] = [];
187
            }
188
            $returnList[$group][$device] = $deviceProperties;
189
        }
190
        return $returnList;
191
    }
192
193
    public function deviceInfo($device, $profileId) {
194
        $this->languageInstance->setTextDomain("devices");
195
        $validator = new \web\lib\common\InputValidation();
196
        $out = 0;
197
        $profile = $validator->Profile($profileId);
198
        $factory = new DeviceFactory($device);
199
        $dev = $factory->device;
200
        if (isset($dev)) {
201
            $dev->setup($profile);
202
            $out = $dev->writeDeviceInfo();
203
        }
204
        $this->languageInstance->setTextDomain("web_user");
205
        echo $out;
206
    }
207
208
    /**
209
     * Prepare the support data for a given profile
210
     *
211
     * @param int $profId profile identifier
212
     * @return array
213
     * array with the following fields:
214
     * - local_email
215
     * - local_phone
216
     * - local_url
217
     * - description
218
     * - devices - an array of device names and their statuses (for a given profile)
219
     */
220
    public function profileAttributes($profId) {
221
        $this->languageInstance->setTextDomain("devices");
222
        $validator = new \web\lib\common\InputValidation();
223
        $profile = $validator->Profile($profId);
224
        $attribs = $profile->getCollapsedAttributes();
225
        $returnArray = [];
226
        $returnArray['silverbullet'] = $profile instanceof ProfileSilverbullet ? 1 : 0;
227
        if (isset($attribs['support:email'])) {
228
            $returnArray['local_email'] = $attribs['support:email'][0];
229
        }
230
        if (isset($attribs['support:phone'])) {
231
            $returnArray['local_phone'] = $attribs['support:phone'][0];
232
        }
233
        if (isset($attribs['support:url'])) {
234
            $returnArray['local_url'] = $attribs['support:url'][0];
235
        }
236
        if (isset($attribs['profile:description'])) {
237
            $returnArray['description'] = $attribs['profile:description'][0];
238
        }
239
        $returnArray['devices'] = $profile->listDevices();
240
        $this->languageInstance->setTextDomain("web_user");
241
        return($returnArray);
242
    }
243
244
    /*
245
      this method needs to be used with care, it could give wrong results in some
246
      cicumstances
247
     */
248
249
    private function GetRootURL() {
250
        $backtrace = debug_backtrace();
251
        $backtraceFileInfo = array_pop($backtrace);
252
        $fileTemp = $backtraceFileInfo['file'];
253
        $file = substr($fileTemp, strlen(dirname(__DIR__)));
254
        while (substr($file, 0, 1) == '/') {
255
            $file = substr($file, 1);
256
        }
257
        $slashCount = count(explode('/', $file));
258
        $out = $_SERVER['SCRIPT_NAME'];
259
        for ($iterator = 0; $iterator < $slashCount; $iterator++) {
260
            $out = dirname($out);
261
        }
262
        if ($out == '/') {
263
            $out = '';
264
        }
265
        return '//' . $_SERVER['SERVER_NAME'] . $out;
266
    }
267
268
    /* JSON functions */
269
270
    public function return_json($data, $status = 1) {
271
        $returnArray = [];
272
        $returnArray['status'] = $status;
273
        $returnArray['data'] = $data;
274
        $returnArray['tou'] = "Please consult Terms of Use at: " . $this->GetRootURL() . "/tou.php";
275
        return(json_encode($returnArray));
276
    }
277
278
    /**
279
     * Return the list of supported languages.
280
     *
281
     * 
282
     */
283
    public function JSON_listLanguages() {
284
        $returnArray = [];
285
        foreach (CONFIG['LANGUAGES'] as $id => $val) {
286
            $returnArray[] = ['lang' => $id, 'display' => $val['display'], 'locale' => $val['locale']];
287
        }
288
        echo $this->return_json($returnArray);
289
    }
290
291
    /**
292
     * Return the list of countiers with configured IdPs
293
     *
294
     * @return string JSON encoded data
295
     */
296
    public function JSON_listCountries() {
297
        $federations = $this->printCountryList(1);
298
        $returnArray = [];
299
        foreach ($federations as $id => $val) {
300
            $returnArray[] = ['federation' => $id, 'display' => $val];
301
        }
302
        echo $this->return_json($returnArray);
303
    }
304
305
    /**
306
     * Return the list of IdPs in a given country
307
     *
308
     * @param string $country the country we are interested in
309
     * @return string JSON encoded data
310
     */
311
    public function JSON_listIdentityProviders($country) {
312
        $idps = $this->listAllIdentityProviders(1, $country);
313
        $returnArray = [];
314 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...
315
            $returnArray[] = ['idp' => $idp['entityID'], 'display' => $idp['title']];
316
        }
317
        echo $this->return_json($returnArray);
318
    }
319
320
    /**
321
     * return the list of all active IdPs
322
     *
323
     * The IdP list is formatted for DiscoJuice
324
     * @return string JSON encoded data
325
     */
326
    public function JSON_listIdentityProvidersForDisco() {
327
        $idps = $this->listAllIdentityProviders(1);
328
        $returnArray = [];
329
        foreach ($idps as $idp) {
330
            $idp['idp'] = $idp['entityID'];
331
            $returnArray[] = $idp;
332
        }
333
        echo json_encode($returnArray);
334
    }
335
336
    /**
337
     * Return the list of IdPs in a given country ordered with respect to the user location
338
     *
339
     * @return string JSON encoded data
340
     */
341
    public function JSON_orderIdentityProviders($country, $location = NULL) {
342
        $idps = $this->orderIdentityProviders($country, $location);
343
        $returnArray = [];
344 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...
345
            $returnArray[] = ['idp' => $idp['id'], 'display' => $idp['title']];
346
        }
347
        echo $this->return_json($returnArray);
348
    }
349
350
    /**
351
     * Produce a list of profiles available for a given IdP
352
     *
353
     * @param int $idpIdentifier the IdP identifier
354
     * @param int $sort should the result set be sorted? 0 = no, 1 = yes
355
     * @return string JSON encoded data
356
     */
357
    public function JSON_listProfiles($idpIdentifier, $sort = 0) {
358
        $this->languageInstance->setTextDomain("web_user");
359
        $returnArray = [];
360
        try {
361
            $idp = new IdP($idpIdentifier);
362
        } catch (\Exception $fail) {
363
            echo $this->return_json($returnArray, 0);
364
            return;
365
        }
366
        $hasLogo = FALSE;
367
        $logo = $idp->getAttributes('general:logo_file');
368
        if (count($logo) > 0) {
369
            $hasLogo = 1;
370
        }
371
        $profiles = $idp->listProfiles(TRUE);
372
        if ($sort == 1) {
373
            usort($profiles, ["UserAPI", "profile_sort"]);
374
        }
375
        foreach ($profiles as $profile) {
376
            $returnArray[] = ['profile' => $profile->identifier, 'display' => $profile->name, 'idp_name' => $profile->instName, 'logo' => $hasLogo];
377
        }
378
        echo $this->return_json($returnArray);
379
    }
380
381
    /**
382
     * Return the list of devices available for the given profile
383
     *
384
     * @param int $profileId the Profile identifier
385
     * @return string JSON encoded data
386
     */
387
    public function JSON_listDevices($profileId) {
388
        $this->languageInstance->setTextDomain("web_user");
389
        $returnArray = [];
390
        $profileAttributes = $this->profileAttributes($profileId);
391
        $thedevices = $profileAttributes['devices'];
392
        if (!isset($profile_redirect) || !$profile_redirect) {
0 ignored issues
show
Bug introduced by
The variable $profile_redirect seems only to be defined at a later point. As such the call to isset() seems to always evaluate to false.

This check marks calls to isset(...) or empty(...) that are found before the variable itself is defined. These will always have the same result.

This is likely the result of code being shifted around. Consider removing these calls.

Loading history...
393
            $profile_redirect = 0;
0 ignored issues
show
Unused Code introduced by
$profile_redirect is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
394
            foreach ($thedevices as $D) {
395
                if (isset($D['options']) && isset($D['options']['hidden']) && $D['options']['hidden']) {
396
                    continue;
397
                }
398
                $disp = $D['display'];
399
                if ($D['device'] === '0') {
400
                    $profile_redirect = 1;
0 ignored issues
show
Unused Code introduced by
$profile_redirect is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
401
                    $disp = $c;
0 ignored issues
show
Bug introduced by
The variable $c does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
402
                }
403
                $returnArray[] = ['device' => $D['id'], 'display' => $disp, 'status' => $D['status'], 'redirect' => $D['redirect']];
404
            }
405
        }
406
        echo $this->return_json($returnArray);
407
    }
408
409
    /**
410
     * Call installer generation and return the link
411
     *
412
     * @param string $device identifier as in {@link devices.php}
413
     * @param int $prof_id profile identifier
414
     * @return string JSON encoded data
415
     */
416
    public function JSON_generateInstaller($device, $prof_id) {
417
        $this->loggerInstance->debug(4, "JSON::generateInstaller arguments: $device,$prof_id\n");
418
        $output = $this->generateInstaller($device, $prof_id);
419
        $this->loggerInstance->debug(4, "output from GUI::generateInstaller:");
420
        $this->loggerInstance->debug(4, print_r($output, true));
421
        $this->loggerInstance->debug(4, json_encode($output));
422
//    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...
423
        echo $this->return_json($output);
424
    }
425
426
    /**
427
     * Generate and send the installer
428
     *
429
     * @param string $device identifier as in {@link devices.php}
430
     * @param int $prof_id profile identifier
431
     * @return binary installerFile
432
     */
433
    public function downloadInstaller($device, $prof_id, $generated_for = 'user', $token = NULL, $password = NULL) {
434
        $this->loggerInstance->debug(4, "downloadInstaller arguments: $device,$prof_id,$generated_for\n");
435
        $output = $this->generateInstaller($device, $prof_id, $generated_for, $token, $password);
436
        $this->loggerInstance->debug(4, "output from GUI::generateInstaller:");
437
        $this->loggerInstance->debug(4, print_r($output, true));
438
        if (!$output['link']) {
439
            header("HTTP/1.0 404 Not Found");
440
            return;
441
        }
442
        $validator = new \web\lib\common\InputValidation();
443
        $profile = $validator->Profile($prof_id);
444
        $profile->incrementDownloadStats($device, $generated_for);
445
        $file = $this->installerPath;
446
        $filetype = $output['mime'];
447
        $this->loggerInstance->debug(4, "installer MIME type:$filetype\n");
448
        header("Content-type: " . $filetype);
449
        header('Content-Disposition: inline; filename="' . basename($file) . '"');
450
        header('Content-Length: ' . filesize($file));
451
        ob_clean();
452
        flush();
453
        readfile($file);
454
    }
455
456
    private function processImage($inputImage, $destFile, $width, $height, $resize) {
457
        $info = new \finfo();
458
        $filetype = $info->buffer($inputImage, FILEINFO_MIME_TYPE);
459
        $offset = 60 * 60 * 24 * 30;
460
        $expiresString = "Expires: " . gmdate("D, d M Y H:i:s", time() + $offset) . " GMT";
461
        $blob = $inputImage;
462
463
        if ($resize) {
464
            $image = new \Imagick();
465
            $image->readImageBlob($inputImage);
466
            $image->setImageFormat('PNG');
467
            $image->thumbnailImage($width, $height, 1);
468
            $blob = $image->getImageBlob();
469
            $this->loggerInstance->debug(4, "Writing cached logo $destFile for IdP/Federation.\n");
470
            file_put_contents($destFile, $blob);
471
        }
472
        
473
        return ["filetype" => $filetype, "expires" => $expiresString, "blob" => $blob];
474
    }
475
476
    /**
477
     * Get and prepare logo file 
478
     *
479
     * When called for DiscoJuice, first check if file cache exists
480
     * If not then generate the file and save it in the cache
481
     * @param int $idp IdP identifier
482
     * @param int $disco flag turning on image generation for DiscoJuice
483
     * @param int $width maximum width of the generated image 
0 ignored issues
show
Bug introduced by
There is no parameter named $disco. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
484
     * @param int $height  maximum height of the generated image
485
     * if one of these is 0 then it is treated as no upper bound
486
     *
487
     */
488
    
489 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...
490
        $expiresString = '';
491
        $resize = 0;
492
        $logoFile = "";
493
        $validator = new \web\lib\common\InputValidation();
494
        $idpInstance = $validator->IdP($idp);
495
        $filetype = 'image/png'; // default, only one code path where it can become different
496
        if (($width || $height) && is_numeric($width) && is_numeric($height)) {
497
            $resize = 1;
498
            if ($height == 0) {
499
                $height = 10000;
500
            }
501
            if ($width == 0) {
502
                $width = 10000;
503
            }
504
            $logoFile = ROOT . '/web/downloads/logos/' . $idp . '_' . $width . '_' . $height . '.png';
505
        }
506
        if ($resize && is_file($logoFile)) {
507
            $this->loggerInstance->debug(4, "Using cached logo $logoFile for: " . $idp . "\n");
508
            $blob = file_get_contents($logoFile);
509
        } else {
510
            $logoAttribute = $idpInstance->getAttributes('general:logo_file');
511
            if (count($logoAttribute) == 0) {
512
                return;
513
            }
514
            $meta = $this->processImage($logoAttribute[0]['value'], $logoFile, $width, $height, $resize);
515
            $filetype = $meta['filetype'];
516
            $expiresString = $meta['expires'];
517
            $blob = $meta['blob'];
518
        }
519
        return ["filetype" => $filetype, "expires" => $expiresString, "blob" => $blob];
520
    }
521
522
    /**
523
     * Get and prepare logo file 
524
     *
525
     * When called for DiscoJuice, first check if file cache exists
526
     * If not then generate the file and save it in the cache
527
     * @param string $fedIdentifier federation identifier
528
     * @param int $width maximum width of the generated image 
529
     * @param int $height  maximum height of the generated image
530
     * if one of these is 0 then it is treated as no upper bound
531
     *
532
     */
533 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...
534
        $expiresString = '';
535
        $resize = 0;
536
        $logoFile = "";
537
        $validator = new \web\lib\common\InputValidation();
538
        $federation = $validator->Federation($fedIdentifier);
539
        if (($width || $height) && is_numeric($width) && is_numeric($height)) {
540
            $resize = 1;
541
            if ($height == 0) {
542
                $height = 10000;
543
            }
544
            if ($width == 0) {
545
                $width = 10000;
546
            }
547
            $logoFile = ROOT . '/web/downloads/logos/' . $fedIdenifier . '_' . $width . '_' . $height . '.png';
0 ignored issues
show
Bug introduced by
The variable $fedIdenifier does not exist. Did you mean $fedIdentifier?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
548
        }
549
        if ($resize && is_file($logoFile)) {
550
            $this->loggerInstance->debug(4, "Using cached logo $logoFile for: " . $fedIdentifier . "\n");
551
            $blob = file_get_contents($logoFile);
552
            $filetype = 'image/png';
553
        } else {
554
            $logoAttribute = $federation->getAttributes('fed:logo_file');
555
            if (count($logoAttribute) == 0) {
556
                return;
557
            }
558
            $meta = $this->processImage($logoAttribute[0]['value'], $logoFile, $width, $height, $resize);
559
            $filetype = $meta['filetype'];
560
            $expiresString = $meta['expires'];
561
            $blob = $meta['blob'];
562
        }
563
        return ["filetype" => $filetype, "expires" => $expiresString, "blob" => $blob];
564
    }
565
566
567
    public function sendLogo($identifier, $type, $width = 0, $height = 0){
568
        if ($type === "federation") {
569
            $logo = $this->getFedLogo($identifier, $width, $height);
570
        }
571
        if ($type === "idp") {
572
            $logo = $this->getIdpLogo($identifier, $width, $height);
573
        }
574
        header("Content-type: " . $logo['filetype']);
0 ignored issues
show
Bug introduced by
The variable $logo does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
575
        header("Cache-Control:max-age=36000, must-revalidate");
576
        header($logo['expires']);
577
        echo $logo['blob'];
578
    }
579
    
580
    public function locateUser() {
581 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...
582
            return ['status' => 'error', 'error' => 'Function for GEOIPv1 called, but config says this is not the version to use!'];
583
        }
584
        //$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...
585
        $host = input_filter(INPUT_SERVER,'REMOTE_ADDR',FILTER_VALIDATE_IP);
586
        $record = geoip_record_by_name($host);
587
        if ($record === FALSE) {
588
            return ['status' => 'error', 'error' => 'Problem listing countries'];
589
        }
590
        $result = ['status' => 'ok'];
591
        $result['country'] = $record['country_code'];
592
//  the two lines below are a dirty hack to take of the error in naming the UK federation
593
        if ($result['country'] == 'GB') {
594
            $result['country'] = 'UK';
595
        }
596
        $result['region'] = $record['region'];
597
        $result['geo'] = ['lat' => (float) $record['latitude'], 'lon' => (float) $record['longitude']];
598
        return($result);
599
    }
600
601
    public function locateUser2() {
602 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...
603
            return ['status' => 'error', 'error' => 'Function for GEOIPv2 called, but config says this is not the version to use!'];
604
        }
605
        require_once CONFIG['GEOIP']['geoip2-path-to-autoloader'];
606
        $reader = new Reader(CONFIG['GEOIP']['geoip2-path-to-db']);
607
        $host = $_SERVER['REMOTE_ADDR'];
608
        try {
609
            $record = $reader->city($host);
610
        } catch (\Exception $e) {
611
            $result = ['status' => 'error', 'error' => 'Problem listing countries'];
612
            return($result);
613
        }
614
        $result = ['status' => 'ok'];
615
        $result['country'] = $record->country->isoCode;
616
//  the two lines below are a dirty hack to take of the error in naming the UK federation
617
        if ($result['country'] == 'GB') {
618
            $result['country'] = 'UK';
619
        }
620
        $result['region'] = $record->continent->name;
621
622
        $result['geo'] = ['lat' => (float) $record->location->latitude, 'lon' => (float) $record->location->longitude];
623
        return($result);
624
    }
625
626
    public function JSON_locateUser() {
627
        header('Content-type: application/json; utf-8');
628
629
        $geoipVersion = CONFIG['GEOIP']['version'] ?? 0;
630
631
        switch ($geoipVersion) {
632
            case 0:
633
                echo json_encode(['status' => 'error', 'error' => 'Geolocation not supported']);
634
                break;
635
            case 1:
636
                echo json_encode($this->locateUser());
637
                break;
638
            case 2:
639
                echo json_encode($this->locateUser2());
640
                break;
641
            default:
642
                throw new Exception("This version of GeoIP is not known!");
643
        }
644
    }
645
646
    /**
647
     * Produce support data prepared within {@link GUI::profileAttributes()}
648
     * @return string JSON encoded data
649
     */
650
    public function JSON_profileAttributes($prof_id) {
651
//    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...
652
        echo $this->return_json($this->profileAttributes($prof_id));
653
    }
654
655
    /**
656
     * Calculate the distence in km between two points given their
657
     * geo coordinates.
658
     * @param array $point1 - first point as an 'lat', 'lon' array 
659
     * @param array $profile1 - second point as an 'lat', 'lon' array 
660
     * @return float distance in km
661
     */
662
    private function geoDistance($point1, $profile1) {
663
664
        $distIntermediate = sin(deg2rad($point1['lat'])) * sin(deg2rad($profile1['lat'])) +
665
                cos(deg2rad($point1['lat'])) * cos(deg2rad($profile1['lat'])) * cos(deg2rad($point1['lon'] - $profile1['lon']));
666
        $dist = rad2deg(acos($distIntermediate)) * 60 * 1.1852;
667
        return(round($dist));
668
    }
669
670
    /**
671
     * Order active identity providers according to their distance and name
672
     * @param array $currentLocation - current location
673
     * @return array $IdPs -  list of arrays ('id', 'name');
674
     */
675
    public function orderIdentityProviders($country, $currentLocation = NULL) {
676
        $idps = $this->listAllIdentityProviders(1, $country);
677
678
        if (is_null($currentLocation)) {
679
            $currentLocation = ['lat' => "90", 'lon' => "0"];
680
            $userLocation = $this->locateUser();
681
            if ($userLocation['status'] == 'ok') {
682
                $currentLocation = $userLocation['geo'];
683
            }
684
        }
685
        $idpTitle = [];
686
        $resultSet = [];
687
        foreach ($idps as $idp) {
688
            $idpTitle[$idp['entityID']] = $idp['title'];
689
            $dist = 10000;
690
            if (isset($idp['geo'])) {
691
                $G = $idp['geo'];
692
                if (isset($G['lon'])) {
693
                    $d1 = $this->geoDistance($currentLocation, $G);
694
                    if ($d1 < $dist) {
695
                        $dist = $d1;
696
                    }
697
                } else {
698
                    foreach ($G as $g) {
699
                        $d1 = $this->geoDistance($currentLocation, $g);
700
                        if ($d1 < $dist) {
701
                            $dist = $d1;
702
                        }
703
                    }
704
                }
705
            }
706
            if ($dist > 100) {
707
                $dist = 10000;
708
            }
709
            $d = sprintf("%06d", $dist);
710
            $resultSet[$idp['entityID']] = $d . " " . $idp['title'];
711
        }
712
        asort($resultSet);
713
        $outarray = [];
714
        foreach (array_keys($resultSet) as $r) {
715
            $outarray[] = ['idp' => $r, 'title' => $idpTitle[$r]];
716
        }
717
        return($outarray);
718
    }
719
720
    /**
721
     * Detect the best device driver form the browser
722
     *
723
     * Detects the operating system and returns its id 
724
     * display name and group membership (as in devices.php)
725
     * @return array indexed by 'id', 'display', 'group'
726
     */
727
    public function detectOS() {
728
        $oldDomain = $this->languageInstance->setTextDomain("devices");
729
        $Dev = \devices\Devices::listDevices();
730
        $this->languageInstance->setTextDomain($oldDomain);
731
        if (isset($_REQUEST['device']) && isset($Dev[$_REQUEST['device']]) && (!isset($device['options']['hidden']) || $device['options']['hidden'] == 0)) {
0 ignored issues
show
Bug introduced by
The variable $device seems only to be defined at a later point. As such the call to isset() seems to always evaluate to false.

This check marks calls to isset(...) or empty(...) that are found before the variable itself is defined. These will always have the same result.

This is likely the result of code being shifted around. Consider removing these calls.

Loading history...
Bug introduced by
The variable $device seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
732
            $dev_id = $_REQUEST['device'];
733
            $device = $Dev[$dev_id];
734
            return(['device' => $dev_id, 'display' => $device['display'], 'group' => $device['group']]);
735
        }
736
        $browser = $_SERVER['HTTP_USER_AGENT'];
737
        $this->loggerInstance->debug(4, "HTTP_USER_AGENT=$browser\n");
738
        foreach ($Dev as $dev_id => $device) {
739
            if (!isset($device['match'])) {
740
                continue;
741
            }
742
            if (preg_match('/' . $device['match'] . '/', $browser)) {
743
                if (!isset($device['options']['hidden']) || $device['options']['hidden'] == 0) {
744
                    $this->loggerInstance->debug(4, "Browser_id: $dev_id\n");
745
                    return(['device' => $dev_id, 'display' => $device['display'], 'group' => $device['group']]);
746
                } else {
747
                    $this->loggerInstance->debug(2, "Unrecognised system: " . filter_input(INPUT_SERVER,'HTTP_USER_AGENT', FILTER_SANITIZE_STRING) . "\n");
748
                    return(false);
749
                }
750
            }
751
        }
752
        $this->loggerInstance->debug(2, "Unrecognised system: " . filter_input(INPUT_SERVER,'HTTP_USER_AGENT', FILTER_SANITIZE_STRING) . "\n");
753
        return(false);
754
    }
755
756
    public function JSON_detectOS() {
757
        $returnArray = $this->detectOS();
758
        if ($returnArray) {
759
            $status = 1;
760
        } else {
761
            $status = 0;
762
        }
763
        echo $this->return_json($returnArray, $status);
764
    }
765
    
766
    public function getUserCerts($token) {
767
        $validator = new \web\lib\common\InputValidation();
768
        $cleanToken = $validator->token($token);
769
        if ($cleanToken) {
770
            // check status of this silverbullet token according to info in DB:
771
            // it can be VALID (exists and not redeemed, EXPIRED, REDEEMED or INVALID (non existent)
772
            $tokenStatus = \core\ProfileSilverbullet::tokenStatus($cleanToken);
773
        } else {
774
            return false;
775
        }
776
        $profile = new \core\ProfileSilverbullet($tokenStatus['profile'], NULL);
777
        $userdata = $profile->userStatus($tokenStatus['user']);
778
        $allcerts = [];
779
        foreach ($userdata as $index => $content) {
780
            $allcerts = array_merge($allcerts, $content['cert_status']);
781
        }
782
        return $allcerts;
783
    }
784
785
    public function JSON_getUserCerts($token) {
786
        $returnArray = $this->getUserCerts($token);
787
        if ($returnArray) {
788
            $status = 1;
789
        } else {
790
            $status = 0;
791
        }
792
        echo $this->return_json($returnArray, $status);
793
    }
794
795
    public $device;
796
    private $installerPath;
797
798
    private static function profile_sort($profile1, $profile2) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
799
        return strcasecmp($profile1->name, $profile2->name);
800
    }
801
802
}
803