Passed
Push — master ( 7e1766...5979ae )
by Tomasz
07:38 queued 02:51
created

UserAPI::geoDistance()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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